From 4abd73a692a89ee65ce768d805bd41de270ab615 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Wed, 20 Apr 2016 22:31:28 +0200 Subject: [PATCH 0001/2308] Implement step 0 --- Makefile | 4 +++- chuck/step0_repl.ck | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 chuck/step0_repl.ck diff --git a/Makefile b/Makefile index 4ed3fee54d..84ba71b5b7 100644 --- a/Makefile +++ b/Makefile @@ -70,7 +70,7 @@ DOCKERIZE = # Settings # -IMPLS = ada awk bash c d clojure coffee cpp crystal cs erlang elisp \ +IMPLS = ada awk bash c d chuck clojure coffee cpp crystal cs erlang elisp \ elixir es6 factor forth fsharp go groovy guile haskell haxe \ io java julia js kotlin lua make mal ocaml matlab miniMAL \ nim objc objpascal perl php ps python r racket rpython ruby \ @@ -143,6 +143,7 @@ awk_STEP_TO_PROG = awk/$($(1)).awk bash_STEP_TO_PROG = bash/$($(1)).sh c_STEP_TO_PROG = c/$($(1)) d_STEP_TO_PROG = d/$($(1)) +chuck_STEP_TO_PROG = chuck/$($(1)).ck clojure_STEP_TO_PROG = clojure/src/$($(1)).clj coffee_STEP_TO_PROG = coffee/$($(1)).coffee cpp_STEP_TO_PROG = cpp/$($(1)) @@ -206,6 +207,7 @@ awk_RUNSTEP = awk -O -f ../$(2) $(3) bash_RUNSTEP = bash ../$(2) $(3) c_RUNSTEP = ../$(2) $(3) d_RUNSTEP = ../$(2) $(3) +chuck_RUNSTEP = chuck --silent ../$(2) $(3) clojure_RUNSTEP = lein with-profile +$(1) trampoline run $(3) coffee_RUNSTEP = coffee ../$(2) $(3) cpp_RUNSTEP = ../$(2) $(3) diff --git a/chuck/step0_repl.ck b/chuck/step0_repl.ck new file mode 100644 index 0000000000..9e05a753bb --- /dev/null +++ b/chuck/step0_repl.ck @@ -0,0 +1,34 @@ +fun string READ(string input) +{ + return input; +} + +fun string EVAL(string input) +{ + return input; +} + +fun string PRINT(string input) +{ + return input; +} + +fun string rep(string input) +{ + return input => READ => EVAL => PRINT; +} + +fun void main() +{ + ConsoleInput stdin; + string input; + + while( true ) + { + stdin.prompt("user>") => now; + stdin.getLine() => input; + chout <= rep(input) + "\n"; + } +} + +main(); From 916b30b98cd79ffe5f8f836968d3e91a6fec047c Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 25 Apr 2016 14:20:39 +0200 Subject: [PATCH 0002/2308] Write a runner script This script serves the following purposes: - A --silent flag for testing purposes - Support for @import comments (to work around no modules) - Accessing extra arguments from CHUCK_ARGUMENTS --- Makefile | 2 +- chuck/run_chuck.sh | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100755 chuck/run_chuck.sh diff --git a/Makefile b/Makefile index 84ba71b5b7..19f35980d2 100644 --- a/Makefile +++ b/Makefile @@ -207,7 +207,7 @@ awk_RUNSTEP = awk -O -f ../$(2) $(3) bash_RUNSTEP = bash ../$(2) $(3) c_RUNSTEP = ../$(2) $(3) d_RUNSTEP = ../$(2) $(3) -chuck_RUNSTEP = chuck --silent ../$(2) $(3) +chuck_RUNSTEP = ./run_chuck.sh --silent ../$(2) $(3) clojure_RUNSTEP = lein with-profile +$(1) trampoline run $(3) coffee_RUNSTEP = coffee ../$(2) $(3) cpp_RUNSTEP = ../$(2) $(3) diff --git a/chuck/run_chuck.sh b/chuck/run_chuck.sh new file mode 100755 index 0000000000..78dd1a6f5c --- /dev/null +++ b/chuck/run_chuck.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +chuck_options="" + +if [[ $1 == --silent ]]; then + shift + chuck_options="--silent" +fi + +script_file=$1 + +export CHUCK_ARGS="$@" + +chuck $chuck_options $(awk "match(\$0,\"^ *// *@import (.+)\",m) {printf \"%s \",m[1]} END {print \"$script_file\"}" $script_file) From e83d6df703a2d93c6846fbb2d683009fc817483b Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Thu, 28 Apr 2016 09:20:09 +0200 Subject: [PATCH 0003/2308] Implement step 1 --- chuck/printer.ck | 64 +++++++++ chuck/reader.ck | 240 ++++++++++++++++++++++++++++++++++ chuck/step1_read_print.ck | 78 +++++++++++ chuck/types/boxed/Int.ck | 11 ++ chuck/types/boxed/String.ck | 73 +++++++++++ chuck/types/mal/MalAtom.ck | 22 ++++ chuck/types/mal/MalError.ck | 35 +++++ chuck/types/mal/MalFalse.ck | 17 +++ chuck/types/mal/MalHashMap.ck | 22 ++++ chuck/types/mal/MalInt.ck | 22 ++++ chuck/types/mal/MalKeyword.ck | 22 ++++ chuck/types/mal/MalList.ck | 22 ++++ chuck/types/mal/MalNil.ck | 17 +++ chuck/types/mal/MalObject.ck | 32 +++++ chuck/types/mal/MalString.ck | 22 ++++ chuck/types/mal/MalSymbol.ck | 22 ++++ chuck/types/mal/MalTrue.ck | 17 +++ chuck/types/mal/MalVector.ck | 22 ++++ chuck/util/Status.ck | 38 ++++++ 19 files changed, 798 insertions(+) create mode 100644 chuck/printer.ck create mode 100644 chuck/reader.ck create mode 100644 chuck/step1_read_print.ck create mode 100644 chuck/types/boxed/Int.ck create mode 100644 chuck/types/boxed/String.ck create mode 100644 chuck/types/mal/MalAtom.ck create mode 100644 chuck/types/mal/MalError.ck create mode 100644 chuck/types/mal/MalFalse.ck create mode 100644 chuck/types/mal/MalHashMap.ck create mode 100644 chuck/types/mal/MalInt.ck create mode 100644 chuck/types/mal/MalKeyword.ck create mode 100644 chuck/types/mal/MalList.ck create mode 100644 chuck/types/mal/MalNil.ck create mode 100644 chuck/types/mal/MalObject.ck create mode 100644 chuck/types/mal/MalString.ck create mode 100644 chuck/types/mal/MalSymbol.ck create mode 100644 chuck/types/mal/MalTrue.ck create mode 100644 chuck/types/mal/MalVector.ck create mode 100644 chuck/util/Status.ck diff --git a/chuck/printer.ck b/chuck/printer.ck new file mode 100644 index 0000000000..15afeb11da --- /dev/null +++ b/chuck/printer.ck @@ -0,0 +1,64 @@ +public class Printer +{ + fun static string pr_str(MalObject m, int print_readably) + { + m.type => string type; + + if( type == "true" || type == "false" || type == "nil" ) + { + return type; + } + else if( type == "int" ) + { + return Std.itoa((m$MalInt).value()); + } + else if( type == "string" ) + { + (m$MalString).value() => string value; + if( print_readably ) + { + return String.repr(value); + } + else + { + return value; + } + } + else if( type == "symbol" ) + { + return (m$MalSymbol).value(); + } + else if( type == "keyword" ) + { + return ":" + (m$MalKeyword).value(); + } + else if( type == "atom" ) + { + return "(atom " + pr_str((m$MalAtom).value(), print_readably) + ")"; + } + else if( type == "list" ) + { + return pr_list((m$MalList).value(), print_readably, "(", ")"); + } + else if( type == "vector" ) + { + return pr_list((m$MalVector).value(), print_readably, "[", "]"); + } + else if( type == "hashmap" ) + { + return pr_list((m$MalHashMap).value(), print_readably, "{", "}"); + } + } + + fun static string pr_list(MalObject m[], int print_readably, string start, string end) + { + string parts[m.size()]; + + for( 0 => int i; i < m.size(); i++ ) + { + pr_str(m[i], print_readably) => parts[i]; + } + + return start + String.join(parts, " ") + end; + } +} diff --git a/chuck/reader.ck b/chuck/reader.ck new file mode 100644 index 0000000000..5bdb53e049 --- /dev/null +++ b/chuck/reader.ck @@ -0,0 +1,240 @@ +public class Reader +{ + 0 => int position; + string tokens[]; + + fun string peek() + { + return tokens[position]; + } + + fun string next() + { + return tokens[position++]; + } + + fun static string[] tokenizer(string input) + { + "^[ ,]*(~@|[][{}()'`~^@]|\"(\\\\.|[^\\\"])*\"|;.*|[^][ {}()'`~@,;\"]*)" => string tokenRe; + "^([ ,]*|;.*)$" => string blankRe; + + string tokens[0]; + + while( true ) + { + string matches[1]; + RegEx.match(tokenRe, input, matches); + matches[1] => string token; + + if( token.length() == 0 && !RegEx.match(blankRe, input) ) + { + tokens << input; + break; + } + + if( !RegEx.match(blankRe, token) ) + { + tokens << token; + } + + matches[0].length() => int tokenStart; + String.slice(input, tokenStart) => input; + + if( input.length() == 0 ) + { + break; + } + } + + return tokens; + } + + fun static MalObject read_str(string input) + { + Reader reader; + tokenizer(input) @=> reader.tokens; + + if( reader.tokens.size() == 0 ) + { + return MalError.create(Status.EMPTY_INPUT); + } + else + { + return read_form(reader); + } + } + + fun static MalObject read_form(Reader reader) + { + reader.peek() => string token; + if( token == "(" ) + { + return read_list(reader, "(", ")"); + } + else if( token == "[" ) + { + return read_list(reader, "[", "]"); + } + else if( token == "{" ) + { + return read_list(reader, "{", "}"); + } + else if( token == ")" || token == "]" || token == "}" ) + { + return MalError.create(Status.UNEXPECTED_TERMINATOR, token); + } + else if( token == "'" ) + { + return read_simple_reader_macro(reader, "quote"); + } + else if( token == "`" ) + { + return read_simple_reader_macro(reader, "quasiquote"); + } + else if( token == "~" ) + { + return read_simple_reader_macro(reader, "unquote"); + } + else if( token == "~@" ) + { + return read_simple_reader_macro(reader, "splice-unquote"); + } + else if( token == "@" ) + { + return read_simple_reader_macro(reader, "deref"); + } + else if( token == "^" ) + { + return read_meta_reader_macro(reader); + } + else + { + return read_atom(reader); + } + } + + fun static MalObject read_list(Reader reader, string start, string end) + { + MalObject items[0]; + + reader.next(); // discard list start token + + while( true ) + { + // HACK: avoid checking for reader.peek() returning null + // (as doing that directly isn't possible and too + // bothersome to do indirectly) + if( reader.position == reader.tokens.size() ) + { + return MalError.create(Status.EXPECTED_TERMINATOR, end); + } + + if( reader.peek() == end ) + { + break; + } + + read_form(reader) @=> MalObject item; + + if( item.type == "error" ) + { + return item; + } + else + { + items << item; + } + } + + reader.next(); // discard list end token + + if( start == "(" ) + { + return MalList.create(items); + } + else if( start == "[" ) + { + return MalVector.create(items); + } + else if( start == "{" ) + { + return MalHashMap.create(items); + } + } + + fun static MalObject read_atom(Reader reader) + { + "^[+-]?[0-9]+$" => string intRe; + "^\"(\\\\.|[^\\\"])*\"$" => string stringRe; + + reader.next() => string token; + + if( token == "true" ) + { + return MalTrue.create(); + } + else if( token == "false" ) + { + return MalFalse.create(); + } + else if( token == "nil" ) + { + return MalNil.create(); + } + else if( RegEx.match(intRe, token) ) + { + return MalInt.create(Std.atoi(token)); + } + else if( token.substring(0, 1) == "\"" ) + { + if( RegEx.match(stringRe, token) ) + { + return MalString.create(String.parse(token)); + } + else + { + return MalError.create(Status.EXPECTED_TERMINATOR, "\""); + } + } + else if( token.substring(0, 1) == ":" ) + { + return MalKeyword.create(String.slice(token, 1)); + } + else + { + return MalSymbol.create(token); + } + } + + fun static MalObject read_simple_reader_macro(Reader reader, string symbol) + { + reader.next(); // discard reader macro token + + read_form(reader) @=> MalObject form; + if( form.type == "error" ) + { + return form; + } + + return MalList.create([MalSymbol.create(symbol), form]); + } + + fun static MalObject read_meta_reader_macro(Reader reader) + { + reader.next(); // discard reader macro token + + read_form(reader) @=> MalObject meta; + if( meta.type == "error" ) + { + return meta; + } + + read_form(reader) @=> MalObject form; + if( form.type == "error" ) + { + return meta; + } + + return MalList.create([MalSymbol.create("with-meta"), form, meta]); + } +} diff --git a/chuck/step1_read_print.ck b/chuck/step1_read_print.ck new file mode 100644 index 0000000000..d87203686b --- /dev/null +++ b/chuck/step1_read_print.ck @@ -0,0 +1,78 @@ +// @import types/boxed/String.ck +// @import types/boxed/Int.ck + +// @import types/mal/MalObject.ck +// @import types/mal/MalError.ck + +// @import types/mal/MalAtom.ck + +// @import types/mal/MalTrue.ck +// @import types/mal/MalFalse.ck +// @import types/mal/MalNil.ck + +// @import types/mal/MalInt.ck +// @import types/mal/MalString.ck +// @import types/mal/MalSymbol.ck +// @import types/mal/MalKeyword.ck + +// @import types/mal/MalList.ck +// @import types/mal/MalVector.ck +// @import types/mal/MalHashMap.ck + +// @import util/Status.ck + +// @import reader.ck +// @import printer.ck + +fun MalObject READ(string input) +{ + return Reader.read_str(input); +} + +fun MalObject EVAL(MalObject m) +{ + return m; +} + +fun string PRINT(MalObject m) +{ + return Printer.pr_str(m, true); +} + +fun string rep(string input) +{ + READ(input) @=> MalObject m; + + if( m.type == "error" ) + { + return Status.toMessage(m$MalError); + } + else + { + return PRINT(EVAL(m)); + } +} + +fun void main() +{ + ConsoleInput stdin; + string input; + + while( true ) + { + stdin.prompt("user>") => now; + stdin.getLine() => input; + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + chout <= output + "\n"; + } + } +} + +main(); diff --git a/chuck/types/boxed/Int.ck b/chuck/types/boxed/Int.ck new file mode 100644 index 0000000000..d2c3b81f42 --- /dev/null +++ b/chuck/types/boxed/Int.ck @@ -0,0 +1,11 @@ +public class Int +{ + int value; + + fun static Int create(int value) + { + Int i; + value => i.value; + return i; + } +} diff --git a/chuck/types/boxed/String.ck b/chuck/types/boxed/String.ck new file mode 100644 index 0000000000..0957a00b2f --- /dev/null +++ b/chuck/types/boxed/String.ck @@ -0,0 +1,73 @@ +public class String +{ + string value; + + fun static String create(string value) + { + String s; + value => s.value; + return s; + } + + // helpers + + // "x".substring(1) errors out (bug?), this doesn't + fun static string slice(string input, int index) + { + if( index == input.length() ) + { + return ""; + } + else + { + return input.substring(index); + } + } + + fun static string slice(string input, int start, int end) + { + if( start == input.length() ) + { + return ""; + } + else + { + return input.substring(start, end - start); + } + } + + fun static string join(string parts[], string separator) + { + if( parts.size() == 0 ) + { + return ""; + } + + parts[0] => string output; + + for( 1 => int i; i < parts.size(); i++ ) + { + output + separator + parts[i] => output; + } + + return output; + } + + fun static string parse(string input) + { + slice(input, 1, input.length() - 1) => string output; + RegEx.replaceAll("\\\\\"", "\"", output) => output; + RegEx.replaceAll("\\\\n", "\n", output) => output; + RegEx.replaceAll("\\\\\\\\", "\\", output) => output; + return output; + } + + fun static string repr(string input) + { + input => string output; + RegEx.replaceAll("\\\\", "\\\\", output) => output; + RegEx.replaceAll("\n", "\\n", output) => output; + RegEx.replaceAll("\"", "\\\"", output) => output; + return "\"" + output + "\""; + } +} diff --git a/chuck/types/mal/MalAtom.ck b/chuck/types/mal/MalAtom.ck new file mode 100644 index 0000000000..984213dd61 --- /dev/null +++ b/chuck/types/mal/MalAtom.ck @@ -0,0 +1,22 @@ +public class MalAtom extends MalObject +{ + "atom" => type; + MalObject meta; + + fun MalObject value() + { + return object$MalObject; + } + + fun void init(MalObject value) + { + value @=> object; + } + + fun static MalObject create(MalObject value) + { + MalAtom m; + m.init(value); + return m; + } +} diff --git a/chuck/types/mal/MalError.ck b/chuck/types/mal/MalError.ck new file mode 100644 index 0000000000..1a1165999b --- /dev/null +++ b/chuck/types/mal/MalError.ck @@ -0,0 +1,35 @@ +public class MalError extends MalObject +{ + "error" => type; + string data; + + fun int value() + { + return (object$Int).value; + } + + fun void init(int value) + { + Int.create(value) @=> object; + } + + fun void init(int value, string arg) + { + Int.create(value) @=> object; + arg => data; + } + + fun static MalError create(int value) + { + MalError m; + m.init(value); + return m; + } + + fun static MalError create(int value, string data) + { + MalError m; + m.init(value, data); + return m; + } +} diff --git a/chuck/types/mal/MalFalse.ck b/chuck/types/mal/MalFalse.ck new file mode 100644 index 0000000000..28b4e51987 --- /dev/null +++ b/chuck/types/mal/MalFalse.ck @@ -0,0 +1,17 @@ +public class MalFalse extends MalObject +{ + "false" => type; + MalObject meta; + + fun void init() + { + Int.create(0) @=> object; + } + + fun static MalFalse create() + { + MalFalse m; + m.init(); + return m; + } +} diff --git a/chuck/types/mal/MalHashMap.ck b/chuck/types/mal/MalHashMap.ck new file mode 100644 index 0000000000..9db58d7b5b --- /dev/null +++ b/chuck/types/mal/MalHashMap.ck @@ -0,0 +1,22 @@ +public class MalHashMap extends MalObject +{ + "hashmap" => type; + MalObject meta; + + fun MalObject[] value() + { + return MalObject.toMalObjectArray(objects); + } + + fun void init(MalObject values[]) + { + MalObject.toObjectArray(values) @=> objects; + } + + fun static MalHashMap create(MalObject values[]) + { + MalHashMap m; + m.init(values); + return m; + } +} diff --git a/chuck/types/mal/MalInt.ck b/chuck/types/mal/MalInt.ck new file mode 100644 index 0000000000..569f8a188f --- /dev/null +++ b/chuck/types/mal/MalInt.ck @@ -0,0 +1,22 @@ +public class MalInt extends MalObject +{ + "int" => type; + MalObject meta; + + fun int value() + { + return (object$Int).value; + } + + fun void init(int value) + { + Int.create(value) @=> object; + } + + fun static MalInt create(int value) + { + MalInt m; + m.init(value); + return m; + } +} diff --git a/chuck/types/mal/MalKeyword.ck b/chuck/types/mal/MalKeyword.ck new file mode 100644 index 0000000000..0730328cc8 --- /dev/null +++ b/chuck/types/mal/MalKeyword.ck @@ -0,0 +1,22 @@ +public class MalKeyword extends MalObject +{ + "keyword" => type; + MalObject meta; + + fun string value() + { + return (object$String).value; + } + + fun void init(string value) + { + String.create(value) @=> object; + } + + fun static MalKeyword create(string value) + { + MalKeyword m; + m.init(value); + return m; + } +} diff --git a/chuck/types/mal/MalList.ck b/chuck/types/mal/MalList.ck new file mode 100644 index 0000000000..b558ff3897 --- /dev/null +++ b/chuck/types/mal/MalList.ck @@ -0,0 +1,22 @@ +public class MalList extends MalObject +{ + "list" => type; + MalObject meta; + + fun MalObject[] value() + { + return MalObject.toMalObjectArray(objects); + } + + fun void init(MalObject values[]) + { + MalObject.toObjectArray(values) @=> objects; + } + + fun static MalList create(MalObject values[]) + { + MalList m; + m.init(values); + return m; + } +} diff --git a/chuck/types/mal/MalNil.ck b/chuck/types/mal/MalNil.ck new file mode 100644 index 0000000000..9e31e73d0e --- /dev/null +++ b/chuck/types/mal/MalNil.ck @@ -0,0 +1,17 @@ +public class MalNil extends MalObject +{ + "nil" => type; + MalObject meta; + + fun void init() + { + Int.create(-1) @=> object; + } + + fun static MalNil create() + { + MalNil m; + m.init(); + return m; + } +} diff --git a/chuck/types/mal/MalObject.ck b/chuck/types/mal/MalObject.ck new file mode 100644 index 0000000000..ff20f7d527 --- /dev/null +++ b/chuck/types/mal/MalObject.ck @@ -0,0 +1,32 @@ +public class MalObject +{ + string type; + Object object; + Object objects[]; + // no meta here because types can't be self-referential + + // helpers for sequence types + fun static MalObject[] toMalObjectArray(Object objects[]) + { + MalObject values[objects.size()]; + + for( 0 => int i; i < objects.size(); i++ ) + { + objects[i]$MalObject @=> values[i]; + } + + return values; + } + + fun static Object[] toObjectArray(MalObject objects[]) + { + Object values[objects.size()]; + + for( 0 => int i; i < objects.size(); i++ ) + { + objects[i]$Object @=> values[i]; + } + + return values; + } +} diff --git a/chuck/types/mal/MalString.ck b/chuck/types/mal/MalString.ck new file mode 100644 index 0000000000..e7fc788ac3 --- /dev/null +++ b/chuck/types/mal/MalString.ck @@ -0,0 +1,22 @@ +public class MalString extends MalObject +{ + "string" => type; + MalObject meta; + + fun string value() + { + return (object$String).value; + } + + fun void init(string value) + { + String.create(value) @=> object; + } + + fun static MalString create(string value) + { + MalString m; + m.init(value); + return m; + } +} diff --git a/chuck/types/mal/MalSymbol.ck b/chuck/types/mal/MalSymbol.ck new file mode 100644 index 0000000000..9220935445 --- /dev/null +++ b/chuck/types/mal/MalSymbol.ck @@ -0,0 +1,22 @@ +public class MalSymbol extends MalObject +{ + "symbol" => type; + MalObject meta; + + fun string value() + { + return (object$String).value; + } + + fun void init(string value) + { + String.create(value) @=> object; + } + + fun static MalSymbol create(string value) + { + MalSymbol m; + m.init(value); + return m; + } +} diff --git a/chuck/types/mal/MalTrue.ck b/chuck/types/mal/MalTrue.ck new file mode 100644 index 0000000000..12b6e047e1 --- /dev/null +++ b/chuck/types/mal/MalTrue.ck @@ -0,0 +1,17 @@ +public class MalTrue extends MalObject +{ + "true" => type; + MalObject meta; + + fun void init() + { + Int.create(1) @=> object; + } + + fun static MalTrue create() + { + MalTrue m; + m.init(); + return m; + } +} diff --git a/chuck/types/mal/MalVector.ck b/chuck/types/mal/MalVector.ck new file mode 100644 index 0000000000..c3381806b6 --- /dev/null +++ b/chuck/types/mal/MalVector.ck @@ -0,0 +1,22 @@ +public class MalVector extends MalObject +{ + "vector" => type; + MalObject meta; + + fun MalObject[] value() + { + return MalObject.toMalObjectArray(objects); + } + + fun void init(MalObject values[]) + { + MalObject.toObjectArray(values) @=> objects; + } + + fun static MalVector create(MalObject values[]) + { + MalVector m; + m.init(values); + return m; + } +} diff --git a/chuck/util/Status.ck b/chuck/util/Status.ck new file mode 100644 index 0000000000..7631eaca8e --- /dev/null +++ b/chuck/util/Status.ck @@ -0,0 +1,38 @@ +public class Status +{ + static int SUCCESS; + static int EMPTY_INPUT; + static int UNEXPECTED_TERMINATOR; + static int EXPECTED_TERMINATOR; + + static string status_codes[]; + + fun static string toMessage(MalError m) + { + m.value() => int status_code; + m.data => string data; + + if( status_code < status_codes.size() ) + { + status_codes[status_code] => string message; + // NOTE: for some reason, the string replacement API is + // different from the regex one, so I'm using the latter + RegEx.replace("%", data, message) => message; + return message; + } + else + { + return "Undefined status code"; + } + } +} + +0 => Status.SUCCESS; +1 => Status.EMPTY_INPUT; +2 => Status.UNEXPECTED_TERMINATOR; +3 => Status.EXPECTED_TERMINATOR; + +["success", + "empty input", + "unexpected '%'", + "expected '%', got EOF"] @=> Status.status_codes; From b12906e3bbf6d8d70e83c6259944bb1687136a59 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Thu, 28 Apr 2016 10:17:59 +0200 Subject: [PATCH 0004/2308] Add notes --- chuck/chuck.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ chuck/notes.md | 15 +++++++++++ 2 files changed, 85 insertions(+) create mode 100644 chuck/chuck.md create mode 100644 chuck/notes.md diff --git a/chuck/chuck.md b/chuck/chuck.md new file mode 100644 index 0000000000..b2e9c1be18 --- /dev/null +++ b/chuck/chuck.md @@ -0,0 +1,70 @@ +- I've found a potential bug in their substring function: + https://github.com/ccrma/chuck/issues/55 +- the manual isn't up to date, so you need to look at `VERSIONS` and + the examples instead, sometimes the sources, too +- the manual only speaks of the debug syntax for printing + (`<<>>;` which goes to stderr), I've found a `chout` object you + can send strings to for outputting to stdout +- quitting is done via `C-c` only +- you'll want to use `--silent` to disable audio errors/processing, + but then the process will use 100% CPU and ignore any waiting +- stdin handling is terrible: + - the manual shows a keyboard example with HID devices, but it + doesn't work on linux + - there's a "hacked" `ConsoleInput` class with only an example file + for it, it works for most of the part, but doesn't accept `C-d` + - the obvious alternative is printing a prompt manually, then + waiting for `KBHit` events and printing them, but that's rather + tedious as you'd have to convert the ascii numbers into chars + yourself and make a buffer-like thing + - I've also considered writing a thing sending OSC events per + keyboard hit and processing these in ChucK as they come in, but + that would most likely not work with the test harness ._. +- the OOP system is seriously weird + - influenced by C++ *and* java + - one public class per file + - to export functionality, you must use a public class (and static + functions/variables) + - if you use static variables, you can't assign values to them + directly, you'll have to do that after the class has been defined + - no interfaces + - no generics (copy/paste code for all types you need!) + - no unions (use Object, then cast to the correct type) + - there is no obvious way of casting to arrays of types + - no private (things are public by default, public keyword is used + to export code) + - no self-references in classes (so no trees, static "constructors" + work though) + - no meaningful way of working with null for primitive types (mutate + a reference and look at the return code instead) + - no boxed versions of primitive types + - no automatic boxing/unboxing +- No module system + - `Machine.add(file)` is the only mechanism available from code (no + read all file contents and eval), but if you use it, it defers + loading the files until the file it's used in, rendering it + useless + - Therefore the only way to make use of it is writing a file that + only consists of these instructions + - The only practical alternative is specifying all files you need + loaded in the right order when starting chuck + - That's why I wrote a runner script extracting `// @import file.ck` + lines (hello JS!) and running chuck with them +- No real exception system + - The VM is able to throw exceptions (out of bounds, nullpointer), + but you can't do anything about them and only get a hint what kind + of operation caused it (no stacktrace or anything) + - No user-definable exceptions, no mechanism to catch or throw them + (other than intentionally doing something illegal) + - This means that you must use C-style error checking by converting + the potentially erroneous functions into returning a status code + and mutating a reference passed to them as argument which is + highly weird in a otherwise Java-like language +- Other oddities + - strict distinction between assigning values and references with + two separate operators for them (`<<` for array append doesn't + seem to care though) + - strings are supposedly reference types, yet you can assign them + with the regular operator... + - `<<` on an `type[]` gives you a weird error as you need to use an + `type[0]` (and a `type[]` is merely a reference...) diff --git a/chuck/notes.md b/chuck/notes.md new file mode 100644 index 0000000000..0a53eb7823 --- /dev/null +++ b/chuck/notes.md @@ -0,0 +1,15 @@ +# Step 1 + +- What if I don't have an OOP language? +- types.qx could be more promently mentioned... +- A table with all types and suggested object names would be hugely + useful +- Same for a list of all errors and their messages +- Mention return types and argument types consistently +- More on int/float and their grammar (int is mentioned implicitly in + the ASCII art, nothing on signs or bases or their lack of) +- Note that a string must be parsed for the `print_readably` thing to + work and mention how one could do that (like, by using a `read` or + `eval`-like thing or alternatively, chopping off the surrounding + quotes and doing the inverse transformation of the printing) +- How is an atom printed? From 80a2a73801064e050931d9cd3ce294b9e7e57aeb Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Fri, 29 Apr 2016 20:40:11 +0200 Subject: [PATCH 0005/2308] Implement step 2 --- chuck/chuck.md | 18 +++- chuck/notes.md | 14 +++ chuck/printer.ck | 8 ++ chuck/step2_eval.ck | 188 +++++++++++++++++++++++++++++++++++ chuck/types/mal/MalObject.ck | 12 +++ chuck/types/subr/MalAdd.ck | 10 ++ chuck/types/subr/MalDiv.ck | 10 ++ chuck/types/subr/MalMul.ck | 10 ++ chuck/types/subr/MalSub.ck | 10 ++ chuck/types/subr/MalSubr.ck | 9 ++ chuck/util/Status.ck | 5 +- 11 files changed, 289 insertions(+), 5 deletions(-) create mode 100644 chuck/step2_eval.ck create mode 100644 chuck/types/subr/MalAdd.ck create mode 100644 chuck/types/subr/MalDiv.ck create mode 100644 chuck/types/subr/MalMul.ck create mode 100644 chuck/types/subr/MalSub.ck create mode 100644 chuck/types/subr/MalSubr.ck diff --git a/chuck/chuck.md b/chuck/chuck.md index b2e9c1be18..5ec2e5af0d 100644 --- a/chuck/chuck.md +++ b/chuck/chuck.md @@ -56,10 +56,13 @@ of operation caused it (no stacktrace or anything) - No user-definable exceptions, no mechanism to catch or throw them (other than intentionally doing something illegal) - - This means that you must use C-style error checking by converting - the potentially erroneous functions into returning a status code - and mutating a reference passed to them as argument which is - highly weird in a otherwise Java-like language + - This means that you should use C-style error checking by + converting the potentially erroneous functions into returning a + status code and mutating a reference passed to them as argument + which is highly weird in a otherwise Java-like language + - An alternative is defining an error object (which belongs to the + same supertype as the other legal return values) and checking its + type by inspecting the user-tracked type field - Other oddities - strict distinction between assigning values and references with two separate operators for them (`<<` for array append doesn't @@ -68,3 +71,10 @@ with the regular operator... - `<<` on an `type[]` gives you a weird error as you need to use an `type[0]` (and a `type[]` is merely a reference...) + - The compiler will find lots of mistakes for you, but cannot figure + out code branches not returning anything which means that return + type violations will blow up in your face unless there's a + reasonable default value (null for `Object` isn't, 0 for `int` and + "" for `string` is) + - If you abuse the type system too much, chances are you get a + segfault or assert instead of an exception... diff --git a/chuck/notes.md b/chuck/notes.md index 0a53eb7823..e77c7979a9 100644 --- a/chuck/notes.md +++ b/chuck/notes.md @@ -13,3 +13,17 @@ `eval`-like thing or alternatively, chopping off the surrounding quotes and doing the inverse transformation of the printing) - How is an atom printed? + +# Step 2 + +- What if my language doesn't support lambdas, let alone passing + around named functions? Ideally write something about + implementing/using functors/delegates or replacing that namespace + with a big switch as with VHDL. Another problem is that if you + choose a different solution in step 4, step 2 could end up no longer + functional... +- What kind of error (read: what message?) is raised when no value can + be looked up for the symbol? Is it arbitrary? Do I need to extend + my error handling to allow for format strings? +- It would be worth a mention that you should extend the printer to + handle "native" functions (or in oldtimey terms, subrs) diff --git a/chuck/printer.ck b/chuck/printer.ck index 15afeb11da..9003e10f7a 100644 --- a/chuck/printer.ck +++ b/chuck/printer.ck @@ -36,6 +36,10 @@ public class Printer { return "(atom " + pr_str((m$MalAtom).value(), print_readably) + ")"; } + else if( type == "subr" ) + { + return "#"; + } else if( type == "list" ) { return pr_list((m$MalList).value(), print_readably, "(", ")"); @@ -48,6 +52,10 @@ public class Printer { return pr_list((m$MalHashMap).value(), print_readably, "{", "}"); } + else + { + return "Unknown type"; + } } fun static string pr_list(MalObject m[], int print_readably, string start, string end) diff --git a/chuck/step2_eval.ck b/chuck/step2_eval.ck new file mode 100644 index 0000000000..6eea8c3263 --- /dev/null +++ b/chuck/step2_eval.ck @@ -0,0 +1,188 @@ +// @import types/boxed/String.ck +// @import types/boxed/Int.ck + +// @import types/mal/MalObject.ck +// @import types/mal/MalError.ck + +// @import types/mal/MalAtom.ck + +// @import types/mal/MalTrue.ck +// @import types/mal/MalFalse.ck +// @import types/mal/MalNil.ck + +// @import types/mal/MalInt.ck +// @import types/mal/MalString.ck +// @import types/mal/MalSymbol.ck +// @import types/mal/MalKeyword.ck + +// @import types/mal/MalList.ck +// @import types/mal/MalVector.ck +// @import types/mal/MalHashMap.ck + +// @import types/subr/MalSubr.ck + +// @import types/subr/MalAdd.ck +// @import types/subr/MalSub.ck +// @import types/subr/MalMul.ck +// @import types/subr/MalDiv.ck + +// @import util/Status.ck + +// @import reader.ck +// @import printer.ck + +fun MalObject READ(string input) +{ + return Reader.read_str(input); +} + +fun MalObject EVAL(MalObject m, MalSubr env[]) +{ + if( m.type == "list" ) + { + if( (m$MalList).value().size() == 0 ) + { + return m; + } + + eval_ast(m, env) @=> MalObject result; + if( result.type == "error" ) + { + return result; + } + + (result$MalList).value() @=> MalObject values[]; + values[0]$MalSubr @=> MalSubr subr; + MalObject.slice(values, 1) @=> MalObject args[]; + + return subr.call(args); + } + else + { + return eval_ast(m, env); + } +} + +fun MalObject eval_ast(MalObject m, MalSubr env[]) +{ + m.type => string type; + + if( type == "symbol" ) + { + (m$MalSymbol).value() => string symbol; + env[symbol] @=> MalSubr subr; + + if( subr == null ) + { + return MalError.create(Status.SYMBOL_NOT_FOUND, symbol); + } + else + { + return subr; + } + } + else if( type == "list" || type == "vector" || type == "hashmap" ) + { + (m$MalList).value() @=> MalObject values[]; + MalObject results[values.size()]; + + if( type != "hashmap" ) + { + for( 0 => int i; i < values.size(); i++ ) + { + EVAL(values[i], env) @=> MalObject result; + + if( result.type == "error" ) + { + return result; + } + + result @=> results[i]; + } + } + else + { + for( 0 => int i; i < values.size(); i++ ) + { + if( i % 2 == 0 ) + { + values[i] @=> results[i]; + } + else + { + EVAL(values[i], env) @=> results[i]; + } + } + } + + if( type == "list" ) + { + return MalList.create(results); + } + else if( type == "vector" ) + { + return MalVector.create(results); + } + else if( type == "hashmap" ) + { + return MalHashMap.create(results); + } + } + else + { + return m; + } +} + +fun string PRINT(MalObject m) +{ + return Printer.pr_str(m, true); +} + +MalSubr repl_env[0]; +new MalAdd @=> repl_env["+"]; +new MalSub @=> repl_env["-"]; +new MalMul @=> repl_env["*"]; +new MalDiv @=> repl_env["/"]; + +fun string rep(string input) +{ + READ(input) @=> MalObject m; + + if( m.type == "error" ) + { + return Status.toMessage(m$MalError); + } + + EVAL(m, repl_env) @=> MalObject result; + if( result.type == "error" ) + { + return Status.toMessage(result$MalError); + } + + return PRINT(result); +} + +fun void main() +{ + ConsoleInput stdin; + string input; + + while( true ) + { + stdin.prompt("user>") => now; + stdin.getLine() => input; + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + chout <= output + "\n"; + } + } +} + +main(); diff --git a/chuck/types/mal/MalObject.ck b/chuck/types/mal/MalObject.ck index ff20f7d527..fc222445ca 100644 --- a/chuck/types/mal/MalObject.ck +++ b/chuck/types/mal/MalObject.ck @@ -29,4 +29,16 @@ public class MalObject return values; } + + fun static MalObject[] slice(MalObject objects[], int index) + { + MalObject values[objects.size() - index]; + + for( index => int i; i < objects.size(); i++ ) + { + objects[i] @=> values[i - index]; + } + + return values; + } } diff --git a/chuck/types/subr/MalAdd.ck b/chuck/types/subr/MalAdd.ck new file mode 100644 index 0000000000..7caf80f8fa --- /dev/null +++ b/chuck/types/subr/MalAdd.ck @@ -0,0 +1,10 @@ +public class MalAdd extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0]$MalInt @=> MalInt a; + args[1]$MalInt @=> MalInt b; + + return MalInt.create(a.value() + b.value()); + } +} diff --git a/chuck/types/subr/MalDiv.ck b/chuck/types/subr/MalDiv.ck new file mode 100644 index 0000000000..50b5603cb4 --- /dev/null +++ b/chuck/types/subr/MalDiv.ck @@ -0,0 +1,10 @@ +public class MalDiv extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0]$MalInt @=> MalInt a; + args[1]$MalInt @=> MalInt b; + + return MalInt.create(a.value() / b.value()); + } +} diff --git a/chuck/types/subr/MalMul.ck b/chuck/types/subr/MalMul.ck new file mode 100644 index 0000000000..42a3d86234 --- /dev/null +++ b/chuck/types/subr/MalMul.ck @@ -0,0 +1,10 @@ +public class MalMul extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0]$MalInt @=> MalInt a; + args[1]$MalInt @=> MalInt b; + + return MalInt.create(a.value() * b.value()); + } +} diff --git a/chuck/types/subr/MalSub.ck b/chuck/types/subr/MalSub.ck new file mode 100644 index 0000000000..e0045a09c9 --- /dev/null +++ b/chuck/types/subr/MalSub.ck @@ -0,0 +1,10 @@ +public class MalSub extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0]$MalInt @=> MalInt a; + args[1]$MalInt @=> MalInt b; + + return MalInt.create(a.value() - b.value()); + } +} diff --git a/chuck/types/subr/MalSubr.ck b/chuck/types/subr/MalSubr.ck new file mode 100644 index 0000000000..ad833130f9 --- /dev/null +++ b/chuck/types/subr/MalSubr.ck @@ -0,0 +1,9 @@ +public class MalSubr extends MalObject +{ + "subr" => type; + + fun MalObject call(MalObject args[]) + { + return new MalObject; + } +} diff --git a/chuck/util/Status.ck b/chuck/util/Status.ck index 7631eaca8e..325d5b886f 100644 --- a/chuck/util/Status.ck +++ b/chuck/util/Status.ck @@ -4,6 +4,7 @@ public class Status static int EMPTY_INPUT; static int UNEXPECTED_TERMINATOR; static int EXPECTED_TERMINATOR; + static int SYMBOL_NOT_FOUND; static string status_codes[]; @@ -31,8 +32,10 @@ public class Status 1 => Status.EMPTY_INPUT; 2 => Status.UNEXPECTED_TERMINATOR; 3 => Status.EXPECTED_TERMINATOR; +4 => Status.SYMBOL_NOT_FOUND; ["success", "empty input", "unexpected '%'", - "expected '%', got EOF"] @=> Status.status_codes; + "expected '%', got EOF", + "'%' not found"] @=> Status.status_codes; From 6a287d6291e02833dcba6bb525869ff81a765628 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 30 Apr 2016 15:16:12 +0200 Subject: [PATCH 0006/2308] Replace runner script to allow for globbing --- Makefile | 2 +- chuck/run_chuck.rb | 14 ++++++++++++ chuck/run_chuck.sh | 14 ------------ chuck/step1_read_print.ck | 25 +++------------------- chuck/step2_eval.ck | 34 +++++------------------------- chuck/types/{mal => }/MalObject.ck | 0 chuck/types/{subr => }/MalSubr.ck | 0 7 files changed, 23 insertions(+), 66 deletions(-) create mode 100755 chuck/run_chuck.rb delete mode 100755 chuck/run_chuck.sh rename chuck/types/{mal => }/MalObject.ck (100%) rename chuck/types/{subr => }/MalSubr.ck (100%) diff --git a/Makefile b/Makefile index 19f35980d2..a45365b895 100644 --- a/Makefile +++ b/Makefile @@ -207,7 +207,7 @@ awk_RUNSTEP = awk -O -f ../$(2) $(3) bash_RUNSTEP = bash ../$(2) $(3) c_RUNSTEP = ../$(2) $(3) d_RUNSTEP = ../$(2) $(3) -chuck_RUNSTEP = ./run_chuck.sh --silent ../$(2) $(3) +chuck_RUNSTEP = ./run_chuck.rb --silent ../$(2) $(3) clojure_RUNSTEP = lein with-profile +$(1) trampoline run $(3) coffee_RUNSTEP = coffee ../$(2) $(3) cpp_RUNSTEP = ../$(2) $(3) diff --git a/chuck/run_chuck.rb b/chuck/run_chuck.rb new file mode 100755 index 0000000000..7e431e54e9 --- /dev/null +++ b/chuck/run_chuck.rb @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +cmdline = ['chuck', '--caution-to-the-wind'] +cmdline << ARGV.shift if ARGV[0] == '--silent' + +scriptfile = ARGV[0] +script = File.readlines(scriptfile) +imports = script.grep(%r{^ *// *@import (.+)}) { $1 } +import_files = imports.flat_map { |i| Dir[i] } +cmdline += import_files +cmdline << scriptfile + +ENV['CHUCK_ARGS'] = ARGV.join(' ') +exec(*cmdline) diff --git a/chuck/run_chuck.sh b/chuck/run_chuck.sh deleted file mode 100755 index 78dd1a6f5c..0000000000 --- a/chuck/run_chuck.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -chuck_options="" - -if [[ $1 == --silent ]]; then - shift - chuck_options="--silent" -fi - -script_file=$1 - -export CHUCK_ARGS="$@" - -chuck $chuck_options $(awk "match(\$0,\"^ *// *@import (.+)\",m) {printf \"%s \",m[1]} END {print \"$script_file\"}" $script_file) diff --git a/chuck/step1_read_print.ck b/chuck/step1_read_print.ck index d87203686b..8bbb0b760d 100644 --- a/chuck/step1_read_print.ck +++ b/chuck/step1_read_print.ck @@ -1,26 +1,7 @@ -// @import types/boxed/String.ck -// @import types/boxed/Int.ck - -// @import types/mal/MalObject.ck -// @import types/mal/MalError.ck - -// @import types/mal/MalAtom.ck - -// @import types/mal/MalTrue.ck -// @import types/mal/MalFalse.ck -// @import types/mal/MalNil.ck - -// @import types/mal/MalInt.ck -// @import types/mal/MalString.ck -// @import types/mal/MalSymbol.ck -// @import types/mal/MalKeyword.ck - -// @import types/mal/MalList.ck -// @import types/mal/MalVector.ck -// @import types/mal/MalHashMap.ck - +// @import types/boxed/*.ck +// @import types/MalObject.ck +// @import types/mal/*.ck // @import util/Status.ck - // @import reader.ck // @import printer.ck diff --git a/chuck/step2_eval.ck b/chuck/step2_eval.ck index 6eea8c3263..8d793a1dea 100644 --- a/chuck/step2_eval.ck +++ b/chuck/step2_eval.ck @@ -1,33 +1,9 @@ -// @import types/boxed/String.ck -// @import types/boxed/Int.ck - -// @import types/mal/MalObject.ck -// @import types/mal/MalError.ck - -// @import types/mal/MalAtom.ck - -// @import types/mal/MalTrue.ck -// @import types/mal/MalFalse.ck -// @import types/mal/MalNil.ck - -// @import types/mal/MalInt.ck -// @import types/mal/MalString.ck -// @import types/mal/MalSymbol.ck -// @import types/mal/MalKeyword.ck - -// @import types/mal/MalList.ck -// @import types/mal/MalVector.ck -// @import types/mal/MalHashMap.ck - -// @import types/subr/MalSubr.ck - -// @import types/subr/MalAdd.ck -// @import types/subr/MalSub.ck -// @import types/subr/MalMul.ck -// @import types/subr/MalDiv.ck - +// @import types/boxed/*.ck +// @import types/MalObject.ck +// @import types/mal/*.ck +// @import types/MalSubr.ck +// @import types/subr/*.ck // @import util/Status.ck - // @import reader.ck // @import printer.ck diff --git a/chuck/types/mal/MalObject.ck b/chuck/types/MalObject.ck similarity index 100% rename from chuck/types/mal/MalObject.ck rename to chuck/types/MalObject.ck diff --git a/chuck/types/subr/MalSubr.ck b/chuck/types/MalSubr.ck similarity index 100% rename from chuck/types/subr/MalSubr.ck rename to chuck/types/MalSubr.ck From 0c8b871ae895ef9d9f23a928b8b4d057c0872c4c Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 30 Apr 2016 20:12:07 +0200 Subject: [PATCH 0007/2308] Implement step 3 --- chuck/chuck.md | 14 ++++ chuck/env.ck | 54 +++++++++++++ chuck/notes.md | 10 +++ chuck/step3_env.ck | 194 +++++++++++++++++++++++++++++++++++++++++++++ chuck/util/Util.ck | 14 ++++ 5 files changed, 286 insertions(+) create mode 100644 chuck/env.ck create mode 100644 chuck/step3_env.ck create mode 100644 chuck/util/Util.ck diff --git a/chuck/chuck.md b/chuck/chuck.md index 5ec2e5af0d..a915f9ea71 100644 --- a/chuck/chuck.md +++ b/chuck/chuck.md @@ -63,6 +63,16 @@ - An alternative is defining an error object (which belongs to the same supertype as the other legal return values) and checking its type by inspecting the user-tracked type field +- No function pointers/functors/closures + - This is a bit unexpected as if you leave away the parentheses + holding the argument list and debug print a function, you'll see + it being recognized as a function, yet you can't store it anywhere + for passing it around + - So you get to implement functors and closures yourself... + - A functor is a class with a call method taking an argument list + and executing the code of the function you intend to pass around + - To use it, store an instance of its class somewhere, then use its + call method with an argument list - Other oddities - strict distinction between assigning values and references with two separate operators for them (`<<` for array append doesn't @@ -78,3 +88,7 @@ "" for `string` is) - If you abuse the type system too much, chances are you get a segfault or assert instead of an exception... + - Debug print shows the object and its type if you pass one + argument, if you pass more than one, it prints the concatenation + of their representations instead, so it's a bit hard to make out + what is a debug print and what isn't diff --git a/chuck/env.ck b/chuck/env.ck new file mode 100644 index 0000000000..53d58c516d --- /dev/null +++ b/chuck/env.ck @@ -0,0 +1,54 @@ +public class Env extends MalObject +{ + MalObject outer; // this would ideally be Env, but isn't supported + MalObject data[0]; + + fun void init(MalObject env) + { + env @=> outer; + } + + fun static Env create(MalObject env) + { + Env e; + e.init(env); + return e; + } + + fun void set(string key, MalObject value) + { + value @=> data[key]; + } + + fun MalObject find(string key) + { + data[key] @=> MalObject value; + + if( value != null ) + { + return value; + } + else if( outer != null ) + { + return (outer$Env).find(key); + } + else + { + return null; + } + } + + fun MalObject get(string key) + { + find(key) @=> MalObject value; + + if( value != null ) + { + return value; + } + else + { + return MalError.create(Status.SYMBOL_NOT_FOUND, key); + } + } +} diff --git a/chuck/notes.md b/chuck/notes.md index e77c7979a9..22e744495d 100644 --- a/chuck/notes.md +++ b/chuck/notes.md @@ -27,3 +27,13 @@ my error handling to allow for format strings? - It would be worth a mention that you should extend the printer to handle "native" functions (or in oldtimey terms, subrs) + +# Step 3 + +- You should modify both eval_ast *and* EVAL +- Suggest the trick with destructuring the AST into `a0`, `a1`, + etc. variables for easier access. Perhaps this can be used to clear + up the general language used with AST manipulation (like, first + parameter and second list element)? +- What does def! return? Emacs Lisp for instance returns the symbol + whereas the tests suggest the value should be returned instead... diff --git a/chuck/step3_env.ck b/chuck/step3_env.ck new file mode 100644 index 0000000000..7922239362 --- /dev/null +++ b/chuck/step3_env.ck @@ -0,0 +1,194 @@ +// @import types/boxed/*.ck +// @import types/MalObject.ck +// @import types/mal/*.ck +// @import types/MalSubr.ck +// @import types/subr/*.ck +// @import util/*.ck +// @import reader.ck +// @import printer.ck +// @import env.ck + +fun MalObject READ(string input) +{ + return Reader.read_str(input); +} + +fun MalObject EVAL(MalObject m, Env env) +{ + if( m.type == "list" ) + { + if( (m$MalList).value().size() == 0 ) + { + return m; + } + + (m$MalList).value() @=> MalObject ast[]; + (ast[0]$MalSymbol).value() => string a0; + + if( a0 == "def!" ) + { + (ast[1]$MalSymbol).value() => string a1; + + EVAL(ast[2], env) @=> MalObject value; + if( value.type == "error" ) + { + return value; + } + + env.set(a1, value); + return value; + } + else if( a0 == "let*" ) + { + Env.create(env) @=> Env let_env; + Util.sequenceToMalObjectArray(ast[1]) @=> MalObject bindings[]; + + for( 0 => int i; i < bindings.size(); 2 +=> i) + { + (bindings[i]$MalSymbol).value() => string symbol; + EVAL(bindings[i+1], let_env) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + let_env.set(symbol, value); + } + + return EVAL(ast[2], let_env); + } + + eval_ast(m, env) @=> MalObject result; + if( result.type == "error" ) + { + return result; + } + + (result$MalList).value() @=> MalObject values[]; + values[0]$MalSubr @=> MalSubr subr; + MalObject.slice(values, 1) @=> MalObject args[]; + + return subr.call(args); + } + else + { + eval_ast(m, env) @=> MalObject result; + return result; + } +} + +fun MalObject eval_ast(MalObject m, Env env) +{ + m.type => string type; + + if( type == "symbol" ) + { + (m$MalSymbol).value() => string symbol; + return env.get(symbol); + } + else if( type == "list" || type == "vector" || type == "hashmap" ) + { + (m$MalList).value() @=> MalObject values[]; + MalObject results[values.size()]; + + if( type != "hashmap" ) + { + for( 0 => int i; i < values.size(); i++ ) + { + EVAL(values[i], env) @=> MalObject result; + + if( result.type == "error" ) + { + return result; + } + + result @=> results[i]; + } + } + else + { + for( 0 => int i; i < values.size(); i++ ) + { + if( i % 2 == 0 ) + { + values[i] @=> results[i]; + } + else + { + EVAL(values[i], env) @=> results[i]; + } + } + } + + if( type == "list" ) + { + return MalList.create(results); + } + else if( type == "vector" ) + { + return MalVector.create(results); + } + else if( type == "hashmap" ) + { + return MalHashMap.create(results); + } + } + else + { + return m; + } +} + +fun string PRINT(MalObject m) +{ + return Printer.pr_str(m, true); +} + +Env.create(null) @=> Env repl_env; +repl_env.set("+", new MalAdd); +repl_env.set("-", new MalSub); +repl_env.set("*", new MalMul); +repl_env.set("/", new MalDiv); + +fun string rep(string input) +{ + READ(input) @=> MalObject m; + + if( m.type == "error" ) + { + return Status.toMessage(m$MalError); + } + + EVAL(m, repl_env) @=> MalObject result; + if( result.type == "error" ) + { + return Status.toMessage(result$MalError); + } + + return PRINT(result); +} + +fun void main() +{ + ConsoleInput stdin; + string input; + + while( true ) + { + stdin.prompt("user>") => now; + stdin.getLine() => input; + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + chout <= output + "\n"; + } + } +} + +main(); diff --git a/chuck/util/Util.ck b/chuck/util/Util.ck new file mode 100644 index 0000000000..041a8d32fd --- /dev/null +++ b/chuck/util/Util.ck @@ -0,0 +1,14 @@ +public class Util +{ + fun static MalObject[] sequenceToMalObjectArray(MalObject m) + { + if( m.type == "list" ) + { + return (m$MalList).value(); + } + else if( m.type == "vector" ) + { + return (m$MalVector).value(); + } + } +} From 674e1c56b66e93af969eedad2f0b1dc37ec48a40 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Thu, 12 May 2016 10:01:09 +0200 Subject: [PATCH 0008/2308] Implement step 4 --- chuck/chuck.md | 13 ++ chuck/core.ck | 32 ++++ chuck/env.ck | 28 +++ chuck/func.ck | 21 +++ chuck/notes.md | 12 ++ chuck/printer.ck | 4 + chuck/step4_if_fn_do.ck | 266 ++++++++++++++++++++++++++++ chuck/types/boxed/String.ck | 39 +++- chuck/types/subr/MalCount.ck | 16 ++ chuck/types/subr/MalEqual.ck | 91 ++++++++++ chuck/types/subr/MalGreater.ck | 17 ++ chuck/types/subr/MalGreaterEqual.ck | 17 ++ chuck/types/subr/MalIsEmpty.ck | 15 ++ chuck/types/subr/MalIsList.ck | 14 ++ chuck/types/subr/MalLess.ck | 17 ++ chuck/types/subr/MalLessEqual.ck | 17 ++ chuck/types/subr/MalListify.ck | 7 + chuck/types/subr/MalPrStr.ck | 14 ++ chuck/types/subr/MalPrintln.ck | 15 ++ chuck/types/subr/MalPrn.ck | 15 ++ chuck/types/subr/MalStr.ck | 14 ++ chuck/util/Util.ck | 10 ++ 22 files changed, 688 insertions(+), 6 deletions(-) create mode 100644 chuck/core.ck create mode 100644 chuck/func.ck create mode 100644 chuck/step4_if_fn_do.ck create mode 100644 chuck/types/subr/MalCount.ck create mode 100644 chuck/types/subr/MalEqual.ck create mode 100644 chuck/types/subr/MalGreater.ck create mode 100644 chuck/types/subr/MalGreaterEqual.ck create mode 100644 chuck/types/subr/MalIsEmpty.ck create mode 100644 chuck/types/subr/MalIsList.ck create mode 100644 chuck/types/subr/MalLess.ck create mode 100644 chuck/types/subr/MalLessEqual.ck create mode 100644 chuck/types/subr/MalListify.ck create mode 100644 chuck/types/subr/MalPrStr.ck create mode 100644 chuck/types/subr/MalPrintln.ck create mode 100644 chuck/types/subr/MalPrn.ck create mode 100644 chuck/types/subr/MalStr.ck diff --git a/chuck/chuck.md b/chuck/chuck.md index a915f9ea71..49f5bd2d9c 100644 --- a/chuck/chuck.md +++ b/chuck/chuck.md @@ -1,5 +1,10 @@ - I've found a potential bug in their substring function: https://github.com/ccrma/chuck/issues/55 +- later I've found one in their regex replace function, too: + https://github.com/ccrma/chuck/issues/60 +- this suggests there hasn't been much testing done on things + unrelated to audio which is not that unexpected in an audio + programming language, but still... - the manual isn't up to date, so you need to look at `VERSIONS` and the examples instead, sometimes the sources, too - the manual only speaks of the debug syntax for printing @@ -27,6 +32,9 @@ functions/variables) - if you use static variables, you can't assign values to them directly, you'll have to do that after the class has been defined + - worse, you can't even declare anything that's not a primitive, so + if you want to declare a reference type, use the reference + operator instead... - no interfaces - no generics (copy/paste code for all types you need!) - no unions (use Object, then cast to the correct type) @@ -68,11 +76,16 @@ holding the argument list and debug print a function, you'll see it being recognized as a function, yet you can't store it anywhere for passing it around + - This is not quite right as you can store it in an `Object`, just + not call it in any way or cast it to a function type - So you get to implement functors and closures yourself... - A functor is a class with a call method taking an argument list and executing the code of the function you intend to pass around - To use it, store an instance of its class somewhere, then use its call method with an argument list + - Closures can be implemented with a data structure holding a + snapshot of the current environment, the parameter list and AST, + the last two being a way of representing an anonymous function. - Other oddities - strict distinction between assigning values and references with two separate operators for them (`<<` for array append doesn't diff --git a/chuck/core.ck b/chuck/core.ck new file mode 100644 index 0000000000..8b78207704 --- /dev/null +++ b/chuck/core.ck @@ -0,0 +1,32 @@ +public class Core +{ + static string names[]; + static MalSubr ns[]; +} + +["+", "-", "*", "/", + "list", "list?", "empty?", "count", + "=", "<", "<=", ">", ">=", + "pr-str", "str", "prn", "println"] @=> Core.names; +MalSubr ns[0] @=> Core.ns; + +new MalAdd @=> Core.ns["+"]; +new MalSub @=> Core.ns["-"]; +new MalMul @=> Core.ns["*"]; +new MalDiv @=> Core.ns["/"]; + +new MalListify @=> Core.ns["list"]; +new MalIsList @=> Core.ns["list?"]; +new MalIsEmpty @=> Core.ns["empty?"]; +new MalCount @=> Core.ns["count"]; + +new MalEqual @=> Core.ns["="]; +new MalLess @=> Core.ns["<"]; +new MalLessEqual @=> Core.ns["<="]; +new MalGreater @=> Core.ns[">"]; +new MalGreaterEqual @=> Core.ns[">="]; + +new MalPrStr @=> Core.ns["pr-str"]; +new MalStr @=> Core.ns["str"]; +new MalPrn @=> Core.ns["prn"]; +new MalPrintln @=> Core.ns["println"]; diff --git a/chuck/env.ck b/chuck/env.ck index 53d58c516d..1564fb6510 100644 --- a/chuck/env.ck +++ b/chuck/env.ck @@ -8,6 +8,27 @@ public class Env extends MalObject env @=> outer; } + fun void init(MalObject env, string binds[], MalObject exprs[]) + { + env @=> outer; + + for( 0 => int i; i < binds.size(); i++ ) + { + binds[i] => string bind; + + if( bind == "&" ) + { + MalObject.slice(exprs, i) @=> MalObject rest_binds[]; + MalList.create(rest_binds) @=> data[binds[i+1]]; + break; + } + else + { + exprs[i] @=> data[bind]; + } + } + } + fun static Env create(MalObject env) { Env e; @@ -15,6 +36,13 @@ public class Env extends MalObject return e; } + fun static Env create(MalObject env, string binds[], MalObject exprs[]) + { + Env e; + e.init(env, binds, exprs); + return e; + } + fun void set(string key, MalObject value) { value @=> data[key]; diff --git a/chuck/func.ck b/chuck/func.ck new file mode 100644 index 0000000000..fcb42b3735 --- /dev/null +++ b/chuck/func.ck @@ -0,0 +1,21 @@ +public class Func extends MalObject +{ + "func" => type; + Env env; + string args[]; + MalObject ast; + + fun void init(Env _env, string _args[], MalObject _ast) + { + _env @=> env; + _args @=> args; + _ast @=> ast; + } + + fun static Func create(Env _env, string _args[], MalObject _ast) + { + Func func; + func.init(_env, _args, _ast); + return func; + } +} diff --git a/chuck/notes.md b/chuck/notes.md index 22e744495d..07a629b877 100644 --- a/chuck/notes.md +++ b/chuck/notes.md @@ -37,3 +37,15 @@ parameter and second list element)? - What does def! return? Emacs Lisp for instance returns the symbol whereas the tests suggest the value should be returned instead... + +# Step 4 + +- "Implement the strings functions" +- The "no closures" paragraph isn't quite clear. Asides from that, do + native functions don't really need to be wrapped the same way as the + `fn*` objects, just introduce another type (like, a Subr and a Func + type) and do a check before applying the arguments to it +- Why does the guide say that the first argument of `count` can be + treated as list, yet there's a test performing `(count nil)` and + expecting zero as result? +- Does it make sense to compare, say, atoms in `=`? diff --git a/chuck/printer.ck b/chuck/printer.ck index 9003e10f7a..6895609cb0 100644 --- a/chuck/printer.ck +++ b/chuck/printer.ck @@ -40,6 +40,10 @@ public class Printer { return "#"; } + else if( type == "func" ) + { + return "#"; + } else if( type == "list" ) { return pr_list((m$MalList).value(), print_readably, "(", ")"); diff --git a/chuck/step4_if_fn_do.ck b/chuck/step4_if_fn_do.ck new file mode 100644 index 0000000000..7c59e77fa6 --- /dev/null +++ b/chuck/step4_if_fn_do.ck @@ -0,0 +1,266 @@ +// @import types/boxed/*.ck +// @import types/MalObject.ck +// @import types/mal/*.ck +// @import util/*.ck +// @import reader.ck +// @import printer.ck +// @import types/MalSubr.ck +// @import types/subr/*.ck +// @import env.ck +// @import core.ck +// @import func.ck + +fun MalObject READ(string input) +{ + return Reader.read_str(input); +} + +fun MalObject EVAL(MalObject m, Env env) +{ + if( m.type == "list" ) + { + if( (m$MalList).value().size() == 0 ) + { + return m; + } + + (m$MalList).value() @=> MalObject ast[]; + + if( ast[0].type == "symbol" ) + { + (ast[0]$MalSymbol).value() => string a0; + + if( a0 == "def!" ) + { + (ast[1]$MalSymbol).value() => string a1; + + EVAL(ast[2], env) @=> MalObject value; + if( value.type == "error" ) + { + return value; + } + + env.set(a1, value); + return value; + } + else if( a0 == "let*" ) + { + Env.create(env) @=> Env let_env; + Util.sequenceToMalObjectArray(ast[1]) @=> MalObject bindings[]; + + for( 0 => int i; i < bindings.size(); 2 +=> i) + { + (bindings[i]$MalSymbol).value() => string symbol; + EVAL(bindings[i+1], let_env) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + let_env.set(symbol, value); + } + + return EVAL(ast[2], let_env); + } + else if( a0 == "do" ) + { + MalObject.slice(ast, 1) @=> MalObject forms[]; + eval_ast(MalList.create(forms), env) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + (value$MalList).value() @=> MalObject values[]; + + return values[values.size()-1]; + } + else if( a0 == "if" ) + { + EVAL(ast[1], env) @=> MalObject condition; + + if( condition.type == "error" ) + { + return condition; + } + + if( !(condition.type == "nil") && !(condition.type == "false") ) + { + return EVAL(ast[2], env); + } + else + { + if( ast.size() < 4 ) + { + return MalNil.create(); + } + else + { + return EVAL(ast[3], env); + } + } + } + else if( a0 == "fn*" ) + { + (ast[1]$MalList).value() @=> MalObject arg_values[]; + string args[arg_values.size()]; + + for( 0 => int i; i < arg_values.size(); i++ ) + { + (arg_values[i]$MalSymbol).value() => args[i]; + } + + ast[2] @=> MalObject _ast; + + return Func.create(env, args, _ast); + } + } + + eval_ast(m, env) @=> MalObject result; + if( result.type == "error" ) + { + return result; + } + + (result$MalList).value() @=> MalObject values[]; + values[0].type => string type; + MalObject.slice(values, 1) @=> MalObject args[]; + + if( type == "subr" ) + { + values[0]$MalSubr @=> MalSubr subr; + return subr.call(args); + } + else // type == "func" + { + values[0]$Func @=> Func func; + Env.create(func.env, func.args, args) @=> Env eval_env; + return EVAL(func.ast, eval_env); + } + } + else + { + eval_ast(m, env) @=> MalObject result; + return result; + } +} + +fun MalObject eval_ast(MalObject m, Env env) +{ + m.type => string type; + + if( type == "symbol" ) + { + (m$MalSymbol).value() => string symbol; + return env.get(symbol); + } + else if( type == "list" || type == "vector" || type == "hashmap" ) + { + (m$MalList).value() @=> MalObject values[]; + MalObject results[values.size()]; + + if( type != "hashmap" ) + { + for( 0 => int i; i < values.size(); i++ ) + { + EVAL(values[i], env) @=> MalObject result; + + if( result.type == "error" ) + { + return result; + } + + result @=> results[i]; + } + } + else + { + for( 0 => int i; i < values.size(); i++ ) + { + if( i % 2 == 0 ) + { + values[i] @=> results[i]; + } + else + { + EVAL(values[i], env) @=> results[i]; + } + } + } + + if( type == "list" ) + { + return MalList.create(results); + } + else if( type == "vector" ) + { + return MalVector.create(results); + } + else if( type == "hashmap" ) + { + return MalHashMap.create(results); + } + } + else + { + return m; + } +} + +fun string PRINT(MalObject m) +{ + return Printer.pr_str(m, true); +} + +Env.create(null) @=> Env repl_env; +for( 0 => int i; i < Core.names.size(); i++ ) +{ + Core.names[i] => string name; + repl_env.set(name, Core.ns[name]); +} + +fun string rep(string input) +{ + READ(input) @=> MalObject m; + + if( m.type == "error" ) + { + return Status.toMessage(m$MalError); + } + + EVAL(m, repl_env) @=> MalObject result; + if( result.type == "error" ) + { + return Status.toMessage(result$MalError); + } + + return PRINT(result); +} + +rep("(def! not (fn* (a) (if a false true)))"); + +fun void main() +{ + ConsoleInput stdin; + string input; + + while( true ) + { + stdin.prompt("user>") => now; + stdin.getLine() => input; + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } + } +} + +main(); diff --git a/chuck/types/boxed/String.ck b/chuck/types/boxed/String.ck index 0957a00b2f..61c54cc88c 100644 --- a/chuck/types/boxed/String.ck +++ b/chuck/types/boxed/String.ck @@ -53,21 +53,48 @@ public class String return output; } + fun static string replaceAll(string input, string pat, string rep) + { + 0 => int offset; + input => string output; + int index; + + while( true ) + { + if( offset >= output.length() ) + { + break; + } + + output.find(pat, offset) => index; + + if( index == -1 ) + { + break; + } + + output.replace(index, pat.length(), rep); + index + rep.length() => offset; + } + + return output; + } + fun static string parse(string input) { slice(input, 1, input.length() - 1) => string output; - RegEx.replaceAll("\\\\\"", "\"", output) => output; - RegEx.replaceAll("\\\\n", "\n", output) => output; - RegEx.replaceAll("\\\\\\\\", "\\", output) => output; + replaceAll(output, "\\\"", "\"") => output; + replaceAll(output, "\\n", "\n") => output; + replaceAll(output, "\\\\", "\\") => output; return output; } fun static string repr(string input) { input => string output; - RegEx.replaceAll("\\\\", "\\\\", output) => output; - RegEx.replaceAll("\n", "\\n", output) => output; - RegEx.replaceAll("\"", "\\\"", output) => output; + replaceAll(output, "\\", "\\\\") => output; + replaceAll(output, "\n", "\\n") => output; + replaceAll(output, "\"", "\\\"") => output; return "\"" + output + "\""; } } diff --git a/chuck/types/subr/MalCount.ck b/chuck/types/subr/MalCount.ck new file mode 100644 index 0000000000..eda90751a3 --- /dev/null +++ b/chuck/types/subr/MalCount.ck @@ -0,0 +1,16 @@ +public class MalCount extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0].type => string kind; + if( kind == "list" || kind == "vector" ) + { + Util.sequenceToMalObjectArray(args[0]) @=> MalObject values[]; + return MalInt.create(values.size()); + } + else + { + return MalInt.create(0); + } + } +} diff --git a/chuck/types/subr/MalEqual.ck b/chuck/types/subr/MalEqual.ck new file mode 100644 index 0000000000..61b66ba52a --- /dev/null +++ b/chuck/types/subr/MalEqual.ck @@ -0,0 +1,91 @@ +public class MalEqual extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject a; + args[1] @=> MalObject b; + + if( ( a.type == "list" || a.type == "vector" ) && + ( b.type == "list" || b.type == "vector" ) ) + { + Util.sequenceToMalObjectArray(a) @=> MalObject as[]; + Util.sequenceToMalObjectArray(b) @=> MalObject bs[]; + + if( as.size() != bs.size() ) + { + return MalFalse.create(); + } + + for( 0 => int i; i < as.size(); i++ ) + { + call([as[i], bs[i]]) @=> MalObject value; + if( value.type != "true" ) + { + return MalFalse.create(); + } + } + + return MalTrue.create(); + } + + if( a.type != b.type ) + { + return MalFalse.create(); + } + + // NOTE: normally I'd go for a type variable, but its scope + // isn't handled properly in the presence of a member variable + a.type => string kind; + if( kind == "true" || kind == "false" || kind == "nil" ) + { + return MalTrue.create(); + } + else if( kind == "int" ) + { + if( (a$MalInt).value() == (b$MalInt).value() ) + { + return MalTrue.create(); + } + else + { + return MalFalse.create(); + } + } + else if( kind == "string" ) + { + if( (a$MalString).value() == (b$MalString).value() ) + { + return MalTrue.create(); + } + else + { + return MalFalse.create(); + } + } + else if( kind == "symbol" ) + { + if( (a$MalSymbol).value() == (b$MalSymbol).value() ) + { + return MalTrue.create(); + } + else + { + return MalFalse.create(); + } + } + else if( kind == "keyword" ) + { + if( (a$MalKeyword).value() == (b$MalKeyword).value() ) + { + return MalTrue.create(); + } + else + { + return MalFalse.create(); + } + } + + // HACK: return false for everything unknown for now + return MalFalse.create(); + } +} diff --git a/chuck/types/subr/MalGreater.ck b/chuck/types/subr/MalGreater.ck new file mode 100644 index 0000000000..f3f27e5a0e --- /dev/null +++ b/chuck/types/subr/MalGreater.ck @@ -0,0 +1,17 @@ +public class MalGreater extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0]$MalInt @=> MalInt a; + args[1]$MalInt @=> MalInt b; + + if( a.value() > b.value() ) + { + return MalTrue.create(); + } + else + { + return MalFalse.create(); + } + } +} diff --git a/chuck/types/subr/MalGreaterEqual.ck b/chuck/types/subr/MalGreaterEqual.ck new file mode 100644 index 0000000000..fc3471fbc1 --- /dev/null +++ b/chuck/types/subr/MalGreaterEqual.ck @@ -0,0 +1,17 @@ +public class MalGreaterEqual extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0]$MalInt @=> MalInt a; + args[1]$MalInt @=> MalInt b; + + if( a.value() >= b.value() ) + { + return MalTrue.create(); + } + else + { + return MalFalse.create(); + } + } +} diff --git a/chuck/types/subr/MalIsEmpty.ck b/chuck/types/subr/MalIsEmpty.ck new file mode 100644 index 0000000000..39bef13775 --- /dev/null +++ b/chuck/types/subr/MalIsEmpty.ck @@ -0,0 +1,15 @@ +public class MalIsEmpty extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + (args[0]$MalList).value() @=> MalObject values[]; + if( values.size() == 0 ) + { + return MalTrue.create(); + } + else + { + return MalFalse.create(); + } + } +} diff --git a/chuck/types/subr/MalIsList.ck b/chuck/types/subr/MalIsList.ck new file mode 100644 index 0000000000..0e29e255c2 --- /dev/null +++ b/chuck/types/subr/MalIsList.ck @@ -0,0 +1,14 @@ +public class MalIsList extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + if( args[0].type == "list" ) + { + return MalTrue.create(); + } + else + { + return MalFalse.create(); + } + } +} diff --git a/chuck/types/subr/MalLess.ck b/chuck/types/subr/MalLess.ck new file mode 100644 index 0000000000..55e606319c --- /dev/null +++ b/chuck/types/subr/MalLess.ck @@ -0,0 +1,17 @@ +public class MalLess extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0]$MalInt @=> MalInt a; + args[1]$MalInt @=> MalInt b; + + if( a.value() < b.value() ) + { + return MalTrue.create(); + } + else + { + return MalFalse.create(); + } + } +} diff --git a/chuck/types/subr/MalLessEqual.ck b/chuck/types/subr/MalLessEqual.ck new file mode 100644 index 0000000000..2b2079cac9 --- /dev/null +++ b/chuck/types/subr/MalLessEqual.ck @@ -0,0 +1,17 @@ +public class MalLessEqual extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0]$MalInt @=> MalInt a; + args[1]$MalInt @=> MalInt b; + + if( a.value() <= b.value() ) + { + return MalTrue.create(); + } + else + { + return MalFalse.create(); + } + } +} diff --git a/chuck/types/subr/MalListify.ck b/chuck/types/subr/MalListify.ck new file mode 100644 index 0000000000..81a5cde1eb --- /dev/null +++ b/chuck/types/subr/MalListify.ck @@ -0,0 +1,7 @@ +public class MalListify extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + return MalList.create(args); + } +} diff --git a/chuck/types/subr/MalPrStr.ck b/chuck/types/subr/MalPrStr.ck new file mode 100644 index 0000000000..22376c28a4 --- /dev/null +++ b/chuck/types/subr/MalPrStr.ck @@ -0,0 +1,14 @@ +public class MalPrStr extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + string values[args.size()]; + + for( 0 => int i; i < values.size(); i++ ) + { + Printer.pr_str(args[i], true) => values[i]; + } + + return MalString.create(String.join(values, " ")); + } +} diff --git a/chuck/types/subr/MalPrintln.ck b/chuck/types/subr/MalPrintln.ck new file mode 100644 index 0000000000..b46009e71b --- /dev/null +++ b/chuck/types/subr/MalPrintln.ck @@ -0,0 +1,15 @@ +public class MalPrintln extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + string values[args.size()]; + + for( 0 => int i; i < values.size(); i++ ) + { + Printer.pr_str(args[i], false) => values[i]; + } + + Util.println(String.join(values, " ")); + return MalNil.create(); + } +} diff --git a/chuck/types/subr/MalPrn.ck b/chuck/types/subr/MalPrn.ck new file mode 100644 index 0000000000..c9aec77274 --- /dev/null +++ b/chuck/types/subr/MalPrn.ck @@ -0,0 +1,15 @@ +public class MalPrn extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + string values[args.size()]; + + for( 0 => int i; i < values.size(); i++ ) + { + Printer.pr_str(args[i], true) => values[i]; + } + + Util.println(String.join(values, " ")); + return MalNil.create(); + } +} diff --git a/chuck/types/subr/MalStr.ck b/chuck/types/subr/MalStr.ck new file mode 100644 index 0000000000..c6477dd254 --- /dev/null +++ b/chuck/types/subr/MalStr.ck @@ -0,0 +1,14 @@ +public class MalStr extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + string values[args.size()]; + + for( 0 => int i; i < values.size(); i++ ) + { + Printer.pr_str(args[i], false) => values[i]; + } + + return MalString.create(String.join(values, "")); + } +} diff --git a/chuck/util/Util.ck b/chuck/util/Util.ck index 041a8d32fd..244dfa5362 100644 --- a/chuck/util/Util.ck +++ b/chuck/util/Util.ck @@ -11,4 +11,14 @@ public class Util return (m$MalVector).value(); } } + + fun static void print(string message) + { + chout <= message; + } + + fun static void println(string message) + { + chout <= message + "\n"; + } } From f823ec25d37f0afca5becfa4a9b54d6b5b4af25a Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Thu, 12 May 2016 20:04:22 +0200 Subject: [PATCH 0009/2308] Use truthiness constants --- chuck/reader.ck | 6 +++--- chuck/step1_read_print.ck | 2 +- chuck/step2_eval.ck | 6 +++--- chuck/step3_env.ck | 4 ++-- chuck/step4_if_fn_do.ck | 2 +- chuck/types/subr/MalEqual.ck | 28 ++++++++++++++-------------- chuck/types/subr/MalGreater.ck | 4 ++-- chuck/types/subr/MalGreaterEqual.ck | 4 ++-- chuck/types/subr/MalIsEmpty.ck | 4 ++-- chuck/types/subr/MalIsList.ck | 4 ++-- chuck/types/subr/MalLess.ck | 4 ++-- chuck/types/subr/MalLessEqual.ck | 4 ++-- chuck/types/subr/MalPrintln.ck | 2 +- chuck/types/subr/MalPrn.ck | 2 +- chuck/util/Constants.ck | 10 ++++++++++ 15 files changed, 48 insertions(+), 38 deletions(-) create mode 100644 chuck/util/Constants.ck diff --git a/chuck/reader.ck b/chuck/reader.ck index 5bdb53e049..6c3fd9e492 100644 --- a/chuck/reader.ck +++ b/chuck/reader.ck @@ -171,15 +171,15 @@ public class Reader if( token == "true" ) { - return MalTrue.create(); + return Constants.TRUE; } else if( token == "false" ) { - return MalFalse.create(); + return Constants.FALSE; } else if( token == "nil" ) { - return MalNil.create(); + return Constants.NIL; } else if( RegEx.match(intRe, token) ) { diff --git a/chuck/step1_read_print.ck b/chuck/step1_read_print.ck index 8bbb0b760d..8348e0acff 100644 --- a/chuck/step1_read_print.ck +++ b/chuck/step1_read_print.ck @@ -1,7 +1,7 @@ // @import types/boxed/*.ck // @import types/MalObject.ck // @import types/mal/*.ck -// @import util/Status.ck +// @import util/*.ck // @import reader.ck // @import printer.ck diff --git a/chuck/step2_eval.ck b/chuck/step2_eval.ck index 8d793a1dea..15f3090eee 100644 --- a/chuck/step2_eval.ck +++ b/chuck/step2_eval.ck @@ -1,11 +1,11 @@ // @import types/boxed/*.ck // @import types/MalObject.ck // @import types/mal/*.ck -// @import types/MalSubr.ck -// @import types/subr/*.ck -// @import util/Status.ck +// @import util/*.ck // @import reader.ck // @import printer.ck +// @import types/MalSubr.ck +// @import types/subr/*.ck fun MalObject READ(string input) { diff --git a/chuck/step3_env.ck b/chuck/step3_env.ck index 7922239362..400d26e501 100644 --- a/chuck/step3_env.ck +++ b/chuck/step3_env.ck @@ -1,11 +1,11 @@ // @import types/boxed/*.ck // @import types/MalObject.ck // @import types/mal/*.ck -// @import types/MalSubr.ck -// @import types/subr/*.ck // @import util/*.ck // @import reader.ck // @import printer.ck +// @import types/MalSubr.ck +// @import types/subr/*.ck // @import env.ck fun MalObject READ(string input) diff --git a/chuck/step4_if_fn_do.ck b/chuck/step4_if_fn_do.ck index 7c59e77fa6..57f1a0fef1 100644 --- a/chuck/step4_if_fn_do.ck +++ b/chuck/step4_if_fn_do.ck @@ -94,7 +94,7 @@ fun MalObject EVAL(MalObject m, Env env) { if( ast.size() < 4 ) { - return MalNil.create(); + return Constants.NIL; } else { diff --git a/chuck/types/subr/MalEqual.ck b/chuck/types/subr/MalEqual.ck index 61b66ba52a..86d47d4cba 100644 --- a/chuck/types/subr/MalEqual.ck +++ b/chuck/types/subr/MalEqual.ck @@ -13,7 +13,7 @@ public class MalEqual extends MalSubr if( as.size() != bs.size() ) { - return MalFalse.create(); + return Constants.FALSE; } for( 0 => int i; i < as.size(); i++ ) @@ -21,16 +21,16 @@ public class MalEqual extends MalSubr call([as[i], bs[i]]) @=> MalObject value; if( value.type != "true" ) { - return MalFalse.create(); + return Constants.FALSE; } } - return MalTrue.create(); + return Constants.TRUE; } if( a.type != b.type ) { - return MalFalse.create(); + return Constants.FALSE; } // NOTE: normally I'd go for a type variable, but its scope @@ -38,54 +38,54 @@ public class MalEqual extends MalSubr a.type => string kind; if( kind == "true" || kind == "false" || kind == "nil" ) { - return MalTrue.create(); + return Constants.TRUE; } else if( kind == "int" ) { if( (a$MalInt).value() == (b$MalInt).value() ) { - return MalTrue.create(); + return Constants.TRUE; } else { - return MalFalse.create(); + return Constants.FALSE; } } else if( kind == "string" ) { if( (a$MalString).value() == (b$MalString).value() ) { - return MalTrue.create(); + return Constants.TRUE; } else { - return MalFalse.create(); + return Constants.FALSE; } } else if( kind == "symbol" ) { if( (a$MalSymbol).value() == (b$MalSymbol).value() ) { - return MalTrue.create(); + return Constants.TRUE; } else { - return MalFalse.create(); + return Constants.FALSE; } } else if( kind == "keyword" ) { if( (a$MalKeyword).value() == (b$MalKeyword).value() ) { - return MalTrue.create(); + return Constants.TRUE; } else { - return MalFalse.create(); + return Constants.FALSE; } } // HACK: return false for everything unknown for now - return MalFalse.create(); + return Constants.FALSE; } } diff --git a/chuck/types/subr/MalGreater.ck b/chuck/types/subr/MalGreater.ck index f3f27e5a0e..31a43b0eb5 100644 --- a/chuck/types/subr/MalGreater.ck +++ b/chuck/types/subr/MalGreater.ck @@ -7,11 +7,11 @@ public class MalGreater extends MalSubr if( a.value() > b.value() ) { - return MalTrue.create(); + return Constants.TRUE; } else { - return MalFalse.create(); + return Constants.FALSE; } } } diff --git a/chuck/types/subr/MalGreaterEqual.ck b/chuck/types/subr/MalGreaterEqual.ck index fc3471fbc1..27df8a9ab4 100644 --- a/chuck/types/subr/MalGreaterEqual.ck +++ b/chuck/types/subr/MalGreaterEqual.ck @@ -7,11 +7,11 @@ public class MalGreaterEqual extends MalSubr if( a.value() >= b.value() ) { - return MalTrue.create(); + return Constants.TRUE; } else { - return MalFalse.create(); + return Constants.FALSE; } } } diff --git a/chuck/types/subr/MalIsEmpty.ck b/chuck/types/subr/MalIsEmpty.ck index 39bef13775..0e5ba43a97 100644 --- a/chuck/types/subr/MalIsEmpty.ck +++ b/chuck/types/subr/MalIsEmpty.ck @@ -5,11 +5,11 @@ public class MalIsEmpty extends MalSubr (args[0]$MalList).value() @=> MalObject values[]; if( values.size() == 0 ) { - return MalTrue.create(); + return Constants.TRUE; } else { - return MalFalse.create(); + return Constants.FALSE; } } } diff --git a/chuck/types/subr/MalIsList.ck b/chuck/types/subr/MalIsList.ck index 0e29e255c2..63dd165c22 100644 --- a/chuck/types/subr/MalIsList.ck +++ b/chuck/types/subr/MalIsList.ck @@ -4,11 +4,11 @@ public class MalIsList extends MalSubr { if( args[0].type == "list" ) { - return MalTrue.create(); + return Constants.TRUE; } else { - return MalFalse.create(); + return Constants.FALSE; } } } diff --git a/chuck/types/subr/MalLess.ck b/chuck/types/subr/MalLess.ck index 55e606319c..076bcee892 100644 --- a/chuck/types/subr/MalLess.ck +++ b/chuck/types/subr/MalLess.ck @@ -7,11 +7,11 @@ public class MalLess extends MalSubr if( a.value() < b.value() ) { - return MalTrue.create(); + return Constants.TRUE; } else { - return MalFalse.create(); + return Constants.FALSE; } } } diff --git a/chuck/types/subr/MalLessEqual.ck b/chuck/types/subr/MalLessEqual.ck index 2b2079cac9..b3aad19996 100644 --- a/chuck/types/subr/MalLessEqual.ck +++ b/chuck/types/subr/MalLessEqual.ck @@ -7,11 +7,11 @@ public class MalLessEqual extends MalSubr if( a.value() <= b.value() ) { - return MalTrue.create(); + return Constants.TRUE; } else { - return MalFalse.create(); + return Constants.FALSE; } } } diff --git a/chuck/types/subr/MalPrintln.ck b/chuck/types/subr/MalPrintln.ck index b46009e71b..30d56a15f1 100644 --- a/chuck/types/subr/MalPrintln.ck +++ b/chuck/types/subr/MalPrintln.ck @@ -10,6 +10,6 @@ public class MalPrintln extends MalSubr } Util.println(String.join(values, " ")); - return MalNil.create(); + return Constants.NIL; } } diff --git a/chuck/types/subr/MalPrn.ck b/chuck/types/subr/MalPrn.ck index c9aec77274..f2137fcb22 100644 --- a/chuck/types/subr/MalPrn.ck +++ b/chuck/types/subr/MalPrn.ck @@ -10,6 +10,6 @@ public class MalPrn extends MalSubr } Util.println(String.join(values, " ")); - return MalNil.create(); + return Constants.NIL; } } diff --git a/chuck/util/Constants.ck b/chuck/util/Constants.ck new file mode 100644 index 0000000000..9e89854d44 --- /dev/null +++ b/chuck/util/Constants.ck @@ -0,0 +1,10 @@ +public class Constants +{ + static MalTrue @ TRUE; + static MalFalse @ FALSE; + static MalNil @ NIL; +} + +MalTrue.create() @=> Constants.TRUE; +MalFalse.create() @=> Constants.FALSE; +MalNil.create() @=> Constants.NIL; From b95c6e2dbf377739909e4fc8c717375481d28e5f Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 14 May 2016 03:02:07 +0200 Subject: [PATCH 0010/2308] Implement step 5 --- chuck/notes.md | 15 +++ chuck/step5_tco.ck | 272 ++++++++++++++++++++++++++++++++++++++ chuck/tests/step5_tco.mal | 2 + chuck/types/MalObject.ck | 13 ++ 4 files changed, 302 insertions(+) create mode 100644 chuck/step5_tco.ck create mode 100644 chuck/tests/step5_tco.mal diff --git a/chuck/notes.md b/chuck/notes.md index 07a629b877..4cf9deeca4 100644 --- a/chuck/notes.md +++ b/chuck/notes.md @@ -49,3 +49,18 @@ treated as list, yet there's a test performing `(count nil)` and expecting zero as result? - Does it make sense to compare, say, atoms in `=`? + +# Step 5 + +- "This is especially important in Lisp languages because they tend to + prefer using recursion instead of iteration for control structures." + <- I'd argue it's less of a lisp thing (see everything else related + to CL) and more a thing functional programming proponents have + considered more elegant than introducing iteration constructs (see + haskell, ocaml, erlang) +- It's not really clear that the TCO change for `let*` involves the + form you'd normally pass to `EVAL` to become the new `ast`. I had to + reread this a few more times to understand that the "second `ast`" + is actually its third argument... +- Where did the check for `do` not being broken by TCO go? +- What's the deal with the `quux/tests/step5_tco.qx` file? diff --git a/chuck/step5_tco.ck b/chuck/step5_tco.ck new file mode 100644 index 0000000000..4f948d3ac7 --- /dev/null +++ b/chuck/step5_tco.ck @@ -0,0 +1,272 @@ +// @import types/boxed/*.ck +// @import types/MalObject.ck +// @import types/mal/*.ck +// @import util/*.ck +// @import reader.ck +// @import printer.ck +// @import types/MalSubr.ck +// @import types/subr/*.ck +// @import env.ck +// @import core.ck +// @import func.ck + +fun MalObject READ(string input) +{ + return Reader.read_str(input); +} + +fun MalObject EVAL(MalObject m, Env env) +{ + while( true ) + { + if( m.type != "list" ) + { + return eval_ast(m, env); + } + + if( (m$MalList).value().size() == 0 ) + { + return m; + } + + (m$MalList).value() @=> MalObject ast[]; + + if( ast[0].type == "symbol" ) + { + (ast[0]$MalSymbol).value() => string a0; + + if( a0 == "def!" ) + { + (ast[1]$MalSymbol).value() => string a1; + + EVAL(ast[2], env) @=> MalObject value; + if( value.type == "error" ) + { + return value; + } + + env.set(a1, value); + return value; + } + else if( a0 == "let*" ) + { + Env.create(env) @=> Env let_env; + Util.sequenceToMalObjectArray(ast[1]) @=> MalObject bindings[]; + + for( 0 => int i; i < bindings.size(); 2 +=> i) + { + (bindings[i]$MalSymbol).value() => string symbol; + EVAL(bindings[i+1], let_env) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + let_env.set(symbol, value); + } + + let_env @=> env; + ast[2] @=> m; + continue; // TCO + } + else if( a0 == "do" ) + { + MalObject.slice(ast, 1, ast.size()) @=> MalObject forms[]; + eval_ast(MalList.create(forms), env) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + // HACK: this assumes do gets at least one argument... + ast[ast.size()-1] @=> m; + continue; // TCO + } + else if( a0 == "if" ) + { + EVAL(ast[1], env) @=> MalObject condition; + + if( condition.type == "error" ) + { + return condition; + } + + if( !(condition.type == "nil") && !(condition.type == "false") ) + { + ast[2] @=> m; + continue; // TCO + } + else + { + if( ast.size() < 4 ) + { + return Constants.NIL; + } + else + { + ast[3] @=> m; + continue; // TCO + } + } + } + else if( a0 == "fn*" ) + { + (ast[1]$MalList).value() @=> MalObject arg_values[]; + string args[arg_values.size()]; + + for( 0 => int i; i < arg_values.size(); i++ ) + { + (arg_values[i]$MalSymbol).value() => args[i]; + } + + ast[2] @=> MalObject _ast; + + return Func.create(env, args, _ast); + } + } + + eval_ast(m, env) @=> MalObject result; + if( result.type == "error" ) + { + return result; + } + + (result$MalList).value() @=> MalObject values[]; + values[0].type => string type; + MalObject.slice(values, 1) @=> MalObject args[]; + + if( type == "subr" ) + { + values[0]$MalSubr @=> MalSubr subr; + return subr.call(args); + } + else // type == "func" + { + values[0]$Func @=> Func func; + Env.create(func.env, func.args, args) @=> Env eval_env; + eval_env @=> env; + func.ast @=> m; + continue; // TCO + } + } +} + +fun MalObject eval_ast(MalObject m, Env env) +{ + m.type => string type; + + if( type == "symbol" ) + { + (m$MalSymbol).value() => string symbol; + return env.get(symbol); + } + else if( type == "list" || type == "vector" || type == "hashmap" ) + { + (m$MalList).value() @=> MalObject values[]; + MalObject results[values.size()]; + + if( type != "hashmap" ) + { + for( 0 => int i; i < values.size(); i++ ) + { + EVAL(values[i], env) @=> MalObject result; + + if( result.type == "error" ) + { + return result; + } + + result @=> results[i]; + } + } + else + { + for( 0 => int i; i < values.size(); i++ ) + { + if( i % 2 == 0 ) + { + values[i] @=> results[i]; + } + else + { + EVAL(values[i], env) @=> results[i]; + } + } + } + + if( type == "list" ) + { + return MalList.create(results); + } + else if( type == "vector" ) + { + return MalVector.create(results); + } + else if( type == "hashmap" ) + { + return MalHashMap.create(results); + } + } + else + { + return m; + } +} + +fun string PRINT(MalObject m) +{ + return Printer.pr_str(m, true); +} + +Env.create(null) @=> Env repl_env; +for( 0 => int i; i < Core.names.size(); i++ ) +{ + Core.names[i] => string name; + repl_env.set(name, Core.ns[name]); +} + +fun string rep(string input) +{ + READ(input) @=> MalObject m; + + if( m.type == "error" ) + { + return Status.toMessage(m$MalError); + } + + EVAL(m, repl_env) @=> MalObject result; + if( result.type == "error" ) + { + return Status.toMessage(result$MalError); + } + + return PRINT(result); +} + +rep("(def! not (fn* (a) (if a false true)))"); + +fun void main() +{ + ConsoleInput stdin; + string input; + + while( true ) + { + stdin.prompt("user>") => now; + stdin.getLine() => input; + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } + } +} + +main(); diff --git a/chuck/tests/step5_tco.mal b/chuck/tests/step5_tco.mal new file mode 100644 index 0000000000..c4a73cc207 --- /dev/null +++ b/chuck/tests/step5_tco.mal @@ -0,0 +1,2 @@ +;; ChucK: skipping non-TCO recursion +;; Reason: stackoverflow (non-recoverable) diff --git a/chuck/types/MalObject.ck b/chuck/types/MalObject.ck index fc222445ca..439d281ced 100644 --- a/chuck/types/MalObject.ck +++ b/chuck/types/MalObject.ck @@ -41,4 +41,17 @@ public class MalObject return values; } + + fun static MalObject[] slice(MalObject objects[], int from, int to) + { + Math.max(to - from, 0)$int => int size; + MalObject values[size]; + + for( from => int i; i < size; i++ ) + { + objects[i] @=> values[i - from]; + } + + return values; + } } From 4eb88ef29518ad5ce34748c1fe71abcd13140378 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Wed, 11 May 2016 13:32:51 -0400 Subject: [PATCH 0011/2308] Logo implementation Tested on UCBLogo 6.0 with some minor tweaks (for performance and adding a `timems` function). The tweaks are performed during Docker image creation (see Dockerfile). Tests of step 5 are skipped because UCBLogo is too slow. Interop is available via `(logo-eval "logo code to run")`. The `examples` directory contains a Mal example of drawing a tree using turtle graphics. --- .travis.yml | 1 + Makefile | 4 +- README.md | 14 +- logo/Dockerfile | 49 +++++ logo/Makefile | 28 +++ logo/core.lg | 413 +++++++++++++++++++++++++++++++++++++++ logo/env.lg | 51 +++++ logo/examples/tree.mal | 25 +++ logo/printer.lg | 54 +++++ logo/reader.lg | 221 +++++++++++++++++++++ logo/readline.lg | 27 +++ logo/run | 2 + logo/step0_repl.lg | 31 +++ logo/step1_read_print.lg | 41 ++++ logo/step2_eval.lg | 78 ++++++++ logo/step3_env.lg | 96 +++++++++ logo/step4_if_fn_do.lg | 113 +++++++++++ logo/step5_tco.lg | 123 ++++++++++++ logo/step6_file.lg | 151 ++++++++++++++ logo/step7_quote.lg | 178 +++++++++++++++++ logo/step8_macros.lg | 213 ++++++++++++++++++++ logo/step9_try.lg | 228 +++++++++++++++++++++ logo/stepA_mal.lg | 232 ++++++++++++++++++++++ logo/tests/stepA_mal.mal | 30 +++ logo/types.lg | 175 +++++++++++++++++ 25 files changed, 2576 insertions(+), 2 deletions(-) create mode 100644 logo/Dockerfile create mode 100644 logo/Makefile create mode 100644 logo/core.lg create mode 100644 logo/env.lg create mode 100644 logo/examples/tree.mal create mode 100644 logo/printer.lg create mode 100644 logo/reader.lg create mode 100644 logo/readline.lg create mode 100755 logo/run create mode 100644 logo/step0_repl.lg create mode 100644 logo/step1_read_print.lg create mode 100644 logo/step2_eval.lg create mode 100644 logo/step3_env.lg create mode 100644 logo/step4_if_fn_do.lg create mode 100644 logo/step5_tco.lg create mode 100644 logo/step6_file.lg create mode 100644 logo/step7_quote.lg create mode 100644 logo/step8_macros.lg create mode 100644 logo/step9_try.lg create mode 100644 logo/stepA_mal.lg create mode 100644 logo/tests/stepA_mal.mal create mode 100644 logo/types.lg diff --git a/.travis.yml b/.travis.yml index 8ab564836f..198a926f31 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,7 @@ matrix: - {env: IMPL=js, services: [docker]} - {env: IMPL=julia, services: [docker]} - {env: IMPL=kotlin, services: [docker]} + - {env: IMPL=logo, services: [docker]} - {env: IMPL=lua, services: [docker]} - {env: IMPL=make, services: [docker]} - {env: IMPL=mal BUILD_IMPL=js NO_PERF=1, services: [docker]} diff --git a/Makefile b/Makefile index 9272606aeb..a64855089a 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,7 @@ DEFERRABLE=1 OPTIONAL=1 # Extra implementation specific options to pass to runtest.py +logo_TEST_OPTS = --start-timeout 60 --test-timeout 120 mal_TEST_OPTS = --start-timeout 60 --test-timeout 120 miniMAL_TEST_OPTS = --start-timeout 60 --test-timeout 120 plpgsql_TEST_OPTS = --start-timeout 60 --test-timeout 180 @@ -78,7 +79,7 @@ DOCKERIZE = IMPLS = ada awk bash c d clojure coffee cpp crystal cs erlang elisp \ elixir es6 factor forth fsharp go groovy guile haskell haxe \ - io java julia js kotlin lua make mal ocaml matlab miniMAL \ + io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php plpgsql plsql ps python r \ racket rpython ruby rust scala swift swift3 tcl vb vhdl vimscript @@ -107,6 +108,7 @@ regress_step9 = $(regress_step8) step9 regress_stepA = $(regress_step9) stepA test_EXCLUDES += test^bash^step5 # never completes at 10,000 +test_EXCLUDES += test^logo^step5 # too slow for 10,000 test_EXCLUDES += test^make^step5 # no TCO capability (iteration or recursion) test_EXCLUDES += test^mal^step5 # host impl dependent test_EXCLUDES += test^matlab^step5 # never completes at 10,000 diff --git a/README.md b/README.md index 42e084ae1a..aa6cf26767 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 55 languages: +Mal is implemented in 56 languages: * Ada * GNU awk @@ -35,6 +35,7 @@ Mal is implemented in 55 languages: * JavaScript ([Online Demo](http://kanaka.github.io/mal)) * Julia * Kotlin +* Logo * Lua * GNU Make * mal itself @@ -448,6 +449,17 @@ make java -jar stepX_YYY.jar ``` +### Logo + +*The Logo implementation was created by [Dov Murik](https://github.com/dubek)* + +The Logo implementation of mal has been tested with UCBLogo 6.0. + +``` +cd logo +logo stepX_YYY.lg +``` + ### Lua Running the Lua implementation of mal requires lua 5.1 or later, diff --git a/logo/Dockerfile b/logo/Dockerfile new file mode 100644 index 0000000000..c9ca5c27f7 --- /dev/null +++ b/logo/Dockerfile @@ -0,0 +1,49 @@ +FROM ubuntu:vivid +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# Install g++ for any C/C++ based implementations +RUN apt-get -y install g++ + +# Install UCBLogo 6.0: +# * Fix the makefile to build correctly +# * Tweat GC settings to improve performance (it's still very slow) +# * Add the timems function implemented in C +RUN apt-get -y install libx11-dev \ + && cd /tmp \ + && curl -O -J -L http://www.cs.berkeley.edu/~bh/downloads/ucblogo.tar.gz \ + && tar xf ucblogo.tar.gz \ + && cd /tmp/ucblogo-6.0 \ + && rm -rf csls/CVS \ + && ./configure \ + && sed -i -e 's/svnversion/echo 206/' -e 's/^\s*(cd docs/#\0/' makefile \ + && echo "all: everything" >> makefile \ + && sed -i -e 's/^#define *SEG_SIZE *16000 /#define SEG_SIZE 6400000 /' logo.h \ + && sed -i -e 's/^#define GCMAX 16000$/#define GCMAX 16000000/' mem.c \ + && echo "extern NODE *ltimems(NODE *);" >> globals.h \ + && echo "NODE *ltimems(NODE *args) { struct timeval tv; gettimeofday(&tv, NULL); return(make_floatnode(((FLONUM)tv.tv_sec) * 1000.0 + (tv.tv_usec / 1000))); }" >> coms.c \ + && sed -i -e 's/^\(.*lthrow.*\)$/\1 {"timems", 0, 0, 0, PREFIX_PRIORITY, ltimems},/' init.c \ + && make install \ + && cd /tmp \ + && rm -rf /tmp/ucblogo.tar.gz /tmp/ucblogo-6.0 + +ENV HOME /mal diff --git a/logo/Makefile b/logo/Makefile new file mode 100644 index 0000000000..2beda63bde --- /dev/null +++ b/logo/Makefile @@ -0,0 +1,28 @@ +SOURCES_BASE = readline.lg types.lg reader.lg printer.lg +SOURCES_LISP = env.lg core.lg stepA_mal.lg +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +.PHONY: all dist clean stats stats-lisp + +all: + @true + +dist: mal.lg mal + +mal.lg: $(SOURCES) + cat $+ | grep -v "^load " > $@ + +mal: mal.lg + echo "#!/usr/bin/env logo" > $@ + cat $< >> $@ + chmod +x $@ + +clean: + rm -f mal.lg mal + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/logo/core.lg b/logo/core.lg new file mode 100644 index 0000000000..2d807fd662 --- /dev/null +++ b/logo/core.lg @@ -0,0 +1,413 @@ +load "../logo/types.lg +load "../logo/reader.lg +load "../logo/printer.lg + +make "global_exception [] + +to bool_to_mal :bool +output ifelse :bool [true_new] [false_new] +end + +to mal_equal_q :a :b +output bool_to_mal equal_q :a :b +end + +to mal_throw :a +make "global_exception :a +(throw "error "_mal_exception_) +end + +to mal_nil_q :a +output bool_to_mal ((obj_type :a) = "nil) +end + +to mal_true_q :a +output bool_to_mal ((obj_type :a) = "true) +end + +to mal_false_q :a +output bool_to_mal ((obj_type :a) = "false) +end + +to mal_string_q :a +output bool_to_mal ((obj_type :a) = "string) +end + +to mal_symbol :a +output symbol_new obj_val :a +end + +to mal_symbol_q :a +output bool_to_mal ((obj_type :a) = "symbol) +end + +to mal_keyword :a +output obj_new "keyword obj_val :a +end + +to mal_keyword_q :a +output bool_to_mal ((obj_type :a) = "keyword) +end + +to mal_pr_str [:args] +output obj_new "string pr_seq :args "true " " :space_char +end + +to mal_str [:args] +output obj_new "string pr_seq :args "false " " " +end + +to mal_prn [:args] +print pr_seq :args "true " " :space_char +output nil_new +end + +to mal_println [:args] +print pr_seq :args "false " " :space_char +output nil_new +end + +to mal_read_string :str +output read_str obj_val :str +end + +to mal_readline :prompt +localmake "line readline obj_val :prompt +if :line=[] [output nil_new] +output obj_new "string :line +end + +to mal_slurp :str +openread obj_val :str +setread obj_val :str +localmake "content " +while [not eofp] [ + make "content word :content readchar +] +close obj_val :str +output obj_new "string :content +end + +to mal_lt :a :b +output bool_to_mal ((obj_val :a) < (obj_val :b)) +end + +to mal_lte :a :b +output bool_to_mal ((obj_val :a) <= (obj_val :b)) +end + +to mal_gt :a :b +output bool_to_mal ((obj_val :a) > (obj_val :b)) +end + +to mal_gte :a :b +output bool_to_mal ((obj_val :a) >= (obj_val :b)) +end + +to mal_add :a :b +output obj_new "number ((obj_val :a) + (obj_val :b)) +end + +to mal_sub :a :b +output obj_new "number ((obj_val :a) - (obj_val :b)) +end + +to mal_mul :a :b +output obj_new "number ((obj_val :a) * (obj_val :b)) +end + +to mal_div :a :b +output obj_new "number ((obj_val :a) / (obj_val :b)) +end + +to mal_time_ms +; Native function timems is added to coms.c (see Dockerfile) +output obj_new "number timems +end + +to mal_list [:args] +output obj_new "list :args +end + +to mal_list_q :a +output bool_to_mal ((obj_type :a) = "list) +end + +to mal_vector [:args] +output obj_new "vector :args +end + +to mal_vector_q :a +output bool_to_mal ((obj_type :a) = "vector) +end + +to mal_hash_map [:args] +localmake "h [] +localmake "i 1 +while [:i < count :args] [ + make "h hashmap_put :h item :i :args item (:i + 1) :args + make "i (:i + 2) +] +output obj_new "hashmap :h +end + +to mal_map_q :a +output bool_to_mal ((obj_type :a) = "hashmap) +end + +to mal_assoc :map [:args] +localmake "h obj_val :map +localmake "i 1 +while [:i < count :args] [ + make "h hashmap_put :h item :i :args item (:i + 1) :args + make "i (:i + 2) +] +output obj_new "hashmap :h +end + +to mal_dissoc :map [:args] +localmake "h obj_val :map +foreach :args [make "h hashmap_delete :h ?] +output obj_new "hashmap :h +end + +to mal_get :map :key +localmake "val hashmap_get obj_val :map :key +if emptyp :val [output nil_new] +output :val +end + +to mal_contains_q :map :key +localmake "val hashmap_get obj_val :map :key +output bool_to_mal not emptyp :val +end + +to mal_keys :map +localmake "h obj_val :map +localmake "keys [] +localmake "i 1 +while [:i <= count :h] [ + make "keys lput item :i :h :keys + make "i (:i + 2) +] +output obj_new "list :keys +end + +to mal_vals :map +localmake "h obj_val :map +localmake "values [] +localmake "i 2 +while [:i <= count :h] [ + make "values lput item :i :h :values + make "i (:i + 2) +] +output obj_new "list :values +end + +to mal_sequential_q :a +output bool_to_mal sequentialp :a +end + +to mal_cons :a :b +output obj_new "list fput :a obj_val :b +end + +to mal_concat [:args] +output obj_new "list apply "sentence map [obj_val ?] :args +end + +to mal_nth :a :i +if (obj_val :i) >= _count :a [(throw "error [nth: index out of range])] +output nth :a obj_val :i +end + +to mal_first :a +output cond [ + [[(obj_type :a) = "nil] nil_new] + [[(_count :a) = 0] nil_new] + [else first obj_val :a] +] +end + +to mal_rest :a +output obj_new "list cond [ + [[(obj_type :a) = "nil] []] + [[(_count :a) = 0] []] + [else butfirst obj_val :a] +] +end + +to mal_empty_q :a +output bool_to_mal (emptyp obj_val :a) +end + +to mal_count :a +output obj_new "number _count :a +end + +to mal_apply :f [:args] +localmake "callargs obj_new "list sentence butlast :args obj_val last :args +output invoke_fn :f :callargs +end + +to mal_map :f :seq +output obj_new "list map [invoke_fn :f obj_new "list (list ?)] obj_val :seq +end + +to mal_conj :a0 [:rest] +case obj_type :a0 [ + [[list] localmake "newlist :a0 + foreach :rest [make "newlist mal_cons ? :newlist] + output :newlist ] + [[vector] output obj_new "vector sentence obj_val :a0 :rest ] + [else (throw "error [conj requires list or vector]) ] +] +end + +to mal_seq :a +case obj_type :a [ + [[string] + if (_count :a) = 0 [output nil_new] + localmake "chars [] + foreach obj_val :a [ make "chars lput obj_new "string ? :chars ] + output obj_new "list :chars ] + [[list] + if (_count :a) = 0 [output nil_new] + output :a ] + [[vector] + if (_count :a) = 0 [output nil_new] + output obj_new "list obj_val :a ] + [[nil] output nil_new ] + [else (throw "error [seq requires string or list or vector or nil]) ] +] +end + +to mal_meta :a +localmake "m obj_meta :a +if emptyp :m [output nil_new] +output :m +end + +to mal_with_meta :a :new_meta +localmake "m ifelse (obj_type :new_meta) = "nil [[]] [:new_meta] +output obj_new_with_meta obj_type :a obj_val :a :m +end + +to mal_atom :a +output obj_new "atom :a +end + +to mal_atom_q :a +output bool_to_mal ((obj_type :a) = "atom) +end + +to mal_deref :a +output obj_val :a +end + +to mal_reset_bang :a :val +.setfirst butfirst :a :val +output :val +end + +to invoke_fn :f :callargs +output case obj_type :f [ + [[nativefn] + apply obj_val :f obj_val :callargs ] + [[fn] + _eval fn_body :f env_new fn_env :f fn_args :f :callargs ] + [else + (throw "error [Wrong type for apply])] +] +end + +to mal_swap_bang :atom :f [:args] +localmake "callargs obj_new "list fput mal_deref :atom :args +output mal_reset_bang :atom invoke_fn :f :callargs +end + +to logo_to_mal :a +output cond [ + [[:a = "true] true_new] + [[:a = "false] false_new] + [[numberp :a] obj_new "number :a] + [[wordp :a] obj_new "string :a] + [[listp :a] obj_new "list map [logo_to_mal ?] :a] + [else nil_new] +] +end + +to mal_logo_eval :str +make "res runresult obj_val :str +if emptyp :res [output nil_new] +output logo_to_mal first :res +end + +make "core_ns [ + [[symbol =] [nativefn mal_equal_q]] + [[symbol throw] [nativefn mal_throw]] + + [[symbol nil?] [nativefn mal_nil_q]] + [[symbol true?] [nativefn mal_true_q]] + [[symbol false?] [nativefn mal_false_q]] + [[symbol string?] [nativefn mal_string_q]] + [[symbol symbol] [nativefn mal_symbol]] + [[symbol symbol?] [nativefn mal_symbol_q]] + [[symbol keyword] [nativefn mal_keyword]] + [[symbol keyword?] [nativefn mal_keyword_q]] + + [[symbol pr-str] [nativefn mal_pr_str]] + [[symbol str] [nativefn mal_str]] + [[symbol prn] [nativefn mal_prn]] + [[symbol println] [nativefn mal_println]] + [[symbol read-string] [nativefn mal_read_string]] + [[symbol readline] [nativefn mal_readline]] + [[symbol slurp] [nativefn mal_slurp]] + + [[symbol <] [nativefn mal_lt]] + [[symbol <=] [nativefn mal_lte]] + [[symbol >] [nativefn mal_gt]] + [[symbol >=] [nativefn mal_gte]] + [[symbol +] [nativefn mal_add]] + [[symbol -] [nativefn mal_sub]] + [[symbol *] [nativefn mal_mul]] + [[symbol /] [nativefn mal_div]] + [[symbol time-ms] [nativefn mal_time_ms]] + + [[symbol list] [nativefn mal_list]] + [[symbol list?] [nativefn mal_list_q]] + [[symbol vector] [nativefn mal_vector]] + [[symbol vector?] [nativefn mal_vector_q]] + [[symbol hash-map] [nativefn mal_hash_map]] + [[symbol map?] [nativefn mal_map_q]] + [[symbol assoc] [nativefn mal_assoc]] + [[symbol dissoc] [nativefn mal_dissoc]] + [[symbol get] [nativefn mal_get]] + [[symbol contains?] [nativefn mal_contains_q]] + [[symbol keys] [nativefn mal_keys]] + [[symbol vals] [nativefn mal_vals]] + + [[symbol sequential?] [nativefn mal_sequential_q]] + [[symbol cons] [nativefn mal_cons]] + [[symbol concat] [nativefn mal_concat]] + [[symbol nth] [nativefn mal_nth]] + [[symbol first] [nativefn mal_first]] + [[symbol rest] [nativefn mal_rest]] + [[symbol empty?] [nativefn mal_empty_q]] + [[symbol count] [nativefn mal_count]] + [[symbol apply] [nativefn mal_apply]] + [[symbol map] [nativefn mal_map]] + + [[symbol conj] [nativefn mal_conj]] + [[symbol seq] [nativefn mal_seq]] + + [[symbol meta] [nativefn mal_meta]] + [[symbol with-meta] [nativefn mal_with_meta]] + [[symbol atom] [nativefn mal_atom]] + [[symbol atom?] [nativefn mal_atom_q]] + [[symbol deref] [nativefn mal_deref]] + [[symbol reset!] [nativefn mal_reset_bang]] + [[symbol swap!] [nativefn mal_swap_bang]] + + [[symbol logo-eval] [nativefn mal_logo_eval]] +] diff --git a/logo/env.lg b/logo/env.lg new file mode 100644 index 0000000000..b3f5b74e89 --- /dev/null +++ b/logo/env.lg @@ -0,0 +1,51 @@ +load "../logo/printer.lg +load "../logo/types.lg + +to env_new :outer :binds :exprs +localmake "data [] +if not emptyp :binds [ + localmake "i 0 + while [:i < _count :binds] [ + ifelse (nth :binds :i) = [symbol &] [ + localmake "val drop :exprs :i + make "i (:i + 1) + localmake "key nth :binds :i + ] [ + localmake "val nth :exprs :i + localmake "key nth :binds :i + ] + make "data hashmap_put :data :key :val + make "i (:i + 1) + ] +] +output listtoarray list :outer :data +end + +to env_outer :env +output item 1 :env +end + +to env_data :env +output item 2 :env +end + +to env_find :env :key +if emptyp :env [output []] +localmake "val hashmap_get env_data :env :key +ifelse emptyp :val [ + output env_find env_outer :env :key +] [ + output :env +] +end + +to env_get :env :key +localmake "foundenv env_find :env :key +if emptyp :foundenv [(throw "error sentence (word "' pr_str :key "true "' ) [not found])] +output hashmap_get env_data :foundenv :key +end + +to env_set :env :key :val +.setitem 2 :env hashmap_put env_data :env :key :val +output :val +end diff --git a/logo/examples/tree.mal b/logo/examples/tree.mal new file mode 100644 index 0000000000..5813ad3257 --- /dev/null +++ b/logo/examples/tree.mal @@ -0,0 +1,25 @@ +; Draw a tree +; +; The classic Logo demo for recursive functions - now in Mal! + +; White background with blue pen +(logo-eval "setbackground 7") +(logo-eval "setpencolor 1") + +; Initialize turtle location +(logo-eval "penup setxy 0 -100 pendown") + +; Expose Logo drawing functions to Mal code +(def! fd (fn* [size] (logo-eval (str "fd " size)))) +(def! bk (fn* [size] (logo-eval (str "bk " size)))) +(def! lt (fn* [size] (logo-eval (str "lt " size)))) +(def! rt (fn* [size] (logo-eval (str "rt " size)))) + +; Tree parts +(def! leaf (fn* [size] (do (fd size) (bk size)))) +(def! branch (fn* [size] (do (fd size) (draw-tree size) (bk size)))) +(def! two-branches (fn* [size] (do (lt 10) (branch size) (rt 40) (branch size) (lt 30)))) +(def! draw-tree (fn* [size] (if (< size 5) (leaf size) (two-branches (/ size 2))))) + +; Draw it +(draw-tree 250) diff --git a/logo/printer.lg b/logo/printer.lg new file mode 100644 index 0000000000..efe1339854 --- /dev/null +++ b/logo/printer.lg @@ -0,0 +1,54 @@ +load "../logo/types.lg + +to pr_str :exp :readable +if emptyp :exp [output []] +output case obj_type :exp [ + [[nil] "nil] + [[true] "true] + [[false] "false] + [[number] obj_val :exp] + [[symbol] obj_val :exp] + [[keyword] word ": obj_val :exp] + [[string] print_string :exp :readable] + [[list] pr_seq obj_val :exp :readable "\( "\) :space_char] + [[vector] pr_seq obj_val :exp :readable "\[ "\] :space_char] + [[hashmap] pr_seq obj_val :exp :readable "\{ "\} :space_char] + [[atom] (word "\(atom :space_char pr_str obj_val :exp :readable "\) ) ] + [[nativefn] (word "#) ] + [[fn] (word "#) ] + [else (throw "error (sentence [unknown type] obj_type :exp))] +] +end + +to escape_string :s +localmake "i 1 +localmake "res " +while [:i <= count :s] [ + localmake "c item :i :s + make "res word :res cond [ + [[ :c = "\\ ] "\\\\ ] + [[ :c = char 10 ] "\\n ] + [[ :c = "\" ] "\\\" ] + [else :c ] + ] + make "i (:i + 1) +] +output :res +end + +to print_string :exp :readable +ifelse :readable [ + output (word "\" escape_string obj_val :exp "\" ) +] [ + output obj_val :exp +] +end + +to pr_seq :seq :readable :start_char :end_char :delim_char +localmake "res :start_char +foreach :seq [ + if # > 1 [make "res word :res :delim_char] + make "res word :res pr_str ? :readable +] +output word :res :end_char +end diff --git a/logo/reader.lg b/logo/reader.lg new file mode 100644 index 0000000000..dc4c85171e --- /dev/null +++ b/logo/reader.lg @@ -0,0 +1,221 @@ +load "../logo/types.lg + +make "open_paren_char char 40 +make "close_paren_char char 41 +make "open_bracket_char char 91 +make "close_bracket_char char 93 +make "open_brace_char char 123 +make "close_brace_char char 125 + +to newlinep :char +output case ascii :char [ + [[10 13] "true] + [else "false] +] +end + +to whitespacep :char +output case ascii :char [ + [[9 10 13 32] "true] + [else "false] +] +end + +to singlechartokenp :char +output case :char [ + [[ ( ) \[ \] \{ \} ' ` \^ @ ] "true] + [else "false] +] +end + +to separatorp :char +output ifelse whitespacep :char [ + "true +] [ + case :char [ + [[ ( ) \[ \] \{ \} ' \" ` , \; ] "true] + [else "false] + ] +] +end + +to read_comment_token :s +localmake "rest :s +while [not emptyp :rest] [ + localmake "c first :rest + ifelse newlinep :c [ + output list " butfirst :rest + ] [ + make "rest butfirst :rest + ] +] +output list " :rest +end + +to read_word_token :s +localmake "w " +localmake "rest :s +while [not emptyp :rest] [ + localmake "c first :rest + ifelse separatorp :c [ + output list :w :rest + ] [ + make "w word :w :c + make "rest butfirst :rest + ] +] +output list :w :rest +end + +to read_string_token :s +localmake "w first :s +localmake "rest butfirst :s +while [not emptyp :rest] [ + localmake "c first :rest + if :c = "" [ + make "w word :w :c + output list :w butfirst :rest + ] + if :c = "\\ [ + make "w word :w :c + make "rest butfirst :rest + make "c first :rest + ] + make "w word :w :c + make "rest butfirst :rest +] +(throw "error [Expected closing quotes]) +end + +to read_next_token :s +localmake "c first :s +localmake "rest butfirst :s +output cond [ + [[whitespacep :c] list " :rest] + [[:c = ",] list " :rest] + [[:c = "~] ifelse ((first :rest) = "@) [list "~@ butfirst :rest] [list "~ :rest] ] + [[singlechartokenp :c] list :c :rest] + [[:c = "\;] read_comment_token :s] + [[:c = ""] read_string_token :s] + [else read_word_token :s] +] +output list first :s butfirst :s +end + +to tokenize :str +localmake "tokens [] +localmake "s :str +while [not emptyp :s] [ + localmake "res read_next_token :s + localmake "token first :res + make "s last :res + if not emptyp :token [ + make "tokens lput :token :tokens + ] +] +output :tokens +end + +to reader_new :tokens +output listtoarray list :tokens 1 +end + +to reader_peek :reader +localmake "tokens item 1 :reader +localmake "pos item 2 :reader +if :pos > count :tokens [output []] +output item :pos :tokens +end + +to reader_next :reader +make "token reader_peek :reader +localmake "pos item 2 :reader +setitem 2 :reader (1 + :pos) +output :token +end + +to unescape_string :token +localmake "s butfirst butlast :token ; remove surrounding double-quotes +localmake "i 1 +localmake "res " +while [:i <= count :s] [ + localmake "c item :i :s + ifelse :c = "\\ [ + make "i (:i + 1) + make "c item :i :s + make "res word :res case :c [ + [[ n ] char 10] + [[ " ] "\" ] + [[ \\ ] "\\ ] + [else :c] + ] + ] [ + make "res word :res :c + ] + make "i (:i + 1) +] +output :res +end + +to read_atom :reader +localmake "token reader_next :reader +output cond [ + [[:token = "nil] nil_new] + [[:token = "true] true_new] + [[:token = "false] false_new] + [[numberp :token] obj_new "number :token] + [[(first :token) = ": ] obj_new "keyword butfirst :token] + [[(first :token) = "\" ] obj_new "string unescape_string :token] + [else symbol_new :token] +] +end + +to read_seq :reader :value_type :start_char :end_char +localmake "token reader_next :reader +if :token <> :start_char [(throw "error sentence "expected (word "' :start_char "'))] +localmake "seq [] +make "token reader_peek :reader +while [:token <> :end_char] [ + if emptyp :token [(throw "error (sentence [expected] (word "' :end_char "',) [got EOF]))] + make "seq lput read_form :reader :seq + make "token reader_peek :reader +] +ignore reader_next :reader +output obj_new :value_type :seq +end + +to reader_macro :reader :symbol_name +ignore reader_next :reader +output obj_new "list list symbol_new :symbol_name read_form :reader +end + +to with_meta_reader_macro :reader +ignore reader_next :reader +localmake "meta read_form :reader +output obj_new "list (list symbol_new "with-meta read_form :reader :meta) +end + +to read_form :reader +output case reader_peek :reader [ + [[ ' ] reader_macro :reader "quote ] + [[ ` ] reader_macro :reader "quasiquote ] + [[ ~ ] reader_macro :reader "unquote ] + [[ ~@ ] reader_macro :reader "splice-unquote ] + [[ \^ ] with_meta_reader_macro :reader ] + [[ @ ] reader_macro :reader "deref ] + [[ ( ] read_seq :reader "list :open_paren_char :close_paren_char ] + [[ ) ] (throw "error sentence [unexpected] (word "' :close_paren_char "')) ] + [[ \[ ] read_seq :reader "vector :open_bracket_char :close_bracket_char ] + [[ \] ] (throw "error sentence [unexpected] (word "' :close_bracket_char "')) ] + [[ \{ ] read_seq :reader "hashmap :open_brace_char :close_brace_char ] + [[ \} ] (throw "error sentence [unexpected] (word "' :close_brace_char "')) ] + [else read_atom :reader] +] +end + +to read_str :str +localmake "tokens tokenize :str +if emptyp :tokens [output []] +localmake "reader reader_new :tokens +output read_form :reader +end diff --git a/logo/readline.lg b/logo/readline.lg new file mode 100644 index 0000000000..b015ff397f --- /dev/null +++ b/logo/readline.lg @@ -0,0 +1,27 @@ +make "backspace_char char 8 +make "space_char char 32 + +to readline :prompt +type :prompt +wait 0 ; flush standard output +localmake "line " +forever [ + localmake "c readchar + ifelse emptyp :c [ + output [] + ] [ + localmake "ascii rawascii :c + case :ascii [ + [[4] output []] + [[10] type :c + output :line] + [[127] if not emptyp :line [ + type (word :backspace_char :space_char :backspace_char) + make "line butlast :line + ]] + [else type :c + make "line word :line :c] + ] + ] +] +end diff --git a/logo/run b/logo/run new file mode 100755 index 0000000000..5d90e8a7ab --- /dev/null +++ b/logo/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec logo $(dirname $0)/${STEP:-stepA_mal}.lg - "${@}" diff --git a/logo/step0_repl.lg b/logo/step0_repl.lg new file mode 100644 index 0000000000..f62cd8d675 --- /dev/null +++ b/logo/step0_repl.lg @@ -0,0 +1,31 @@ +load "../logo/readline.lg + +to _read :str +output :str +end + +to _eval :ast :env +output :ast +end + +to _print :exp +output :exp +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + print _print _eval _read :line [] + ] + ] +] +end + +repl +bye diff --git a/logo/step1_read_print.lg b/logo/step1_read_print.lg new file mode 100644 index 0000000000..c3e5e61008 --- /dev/null +++ b/logo/step1_read_print.lg @@ -0,0 +1,41 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg + +to _read :str +output read_str :str +end + +to _eval :ast :env +output :ast +end + +to _print :exp +output pr_str :exp "true +end + +to rep :str +output _print _eval _read :str [] +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + localmake "exception error + if not emptyp :exception [ + (print "Error: first butfirst :exception) + ] + ] + ] +] +end + +repl +bye diff --git a/logo/step2_eval.lg b/logo/step2_eval.lg new file mode 100644 index 0000000000..de1be205a2 --- /dev/null +++ b/logo/step2_eval.lg @@ -0,0 +1,78 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg + +to _read :str +output read_str :str +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] localmake "val hashmap_get :env :ast + if emptyp :val [(throw "error sentence (word "' obj_val :ast "' ) [not found])] + :val ] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :ast :env +if (obj_type :ast) <> "list [output eval_ast :ast :env] +if emptyp obj_val :ast [output :ast] +make "el obj_val eval_ast :ast :env +output apply first :el butfirst :el +end + +to _print :exp +output pr_str :exp "true +end + +to rep :str +output _print _eval _read :str :repl_env +end + +to mal_add :a :b +output obj_new "number ((obj_val :a) + (obj_val :b)) +end + +to mal_sub :a :b +output obj_new "number ((obj_val :a) - (obj_val :b)) +end + +to mal_mul :a :b +output obj_new "number ((obj_val :a) * (obj_val :b)) +end + +to mal_div :a :b +output obj_new "number ((obj_val :a) / (obj_val :b)) +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + localmake "exception error + if not emptyp :exception [ + (print "Error: first butfirst :exception) + ] + ] + ] +] +end + +make "repl_env [] +make "repl_env hashmap_put :repl_env symbol_new "+ "mal_add +make "repl_env hashmap_put :repl_env symbol_new "- "mal_sub +make "repl_env hashmap_put :repl_env symbol_new "* "mal_mul +make "repl_env hashmap_put :repl_env symbol_new "/ "mal_div +repl +bye diff --git a/logo/step3_env.lg b/logo/step3_env.lg new file mode 100644 index 0000000000..05147038d6 --- /dev/null +++ b/logo/step3_env.lg @@ -0,0 +1,96 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg + +to _read :str +output read_str :str +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :ast :env +if (obj_type :ast) <> "list [output eval_ast :ast :env] +if emptyp obj_val :ast [output :ast] +localmake "a0 nth :ast 0 +case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + output _eval nth :ast 2 :letenv ] + + [else + make "el obj_val eval_ast :ast :env + output apply first :el butfirst :el ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to rep :str +output _print _eval _read :str :repl_env +end + +to mal_add :a :b +output obj_new "number ((obj_val :a) + (obj_val :b)) +end + +to mal_sub :a :b +output obj_new "number ((obj_val :a) - (obj_val :b)) +end + +to mal_mul :a :b +output obj_new "number ((obj_val :a) * (obj_val :b)) +end + +to mal_div :a :b +output obj_new "number ((obj_val :a) / (obj_val :b)) +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + localmake "exception error + if not emptyp :exception [ + (print "Error: first butfirst :exception) + ] + ] + ] +] +end + +make "repl_env env_new [] [] [] +ignore env_set :repl_env obj_new "symbol "+ "mal_add +ignore env_set :repl_env obj_new "symbol "- "mal_sub +ignore env_set :repl_env obj_new "symbol "* "mal_mul +ignore env_set :repl_env obj_new "symbol "/ "mal_div +repl +bye diff --git a/logo/step4_if_fn_do.lg b/logo/step4_if_fn_do.lg new file mode 100644 index 0000000000..fd1293ea3d --- /dev/null +++ b/logo/step4_if_fn_do.lg @@ -0,0 +1,113 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg +load "../logo/core.lg + +to _read :str +output read_str :str +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :ast :env +if (obj_type :ast) <> "list [output eval_ast :ast :env] +if emptyp obj_val :ast [output :ast] +localmake "a0 nth :ast 0 +case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + output _eval nth :ast 2 :letenv ] + + [[[symbol do]] + output last obj_val eval_ast rest :ast :env ] + + [[[symbol if]] + localmake "a1 nth :ast 1 + localmake "cond _eval :a1 :env + output case obj_type :cond [ + [[nil false] ifelse (_count :ast) > 3 [ + _eval nth :ast 3 :env + ] [ + nil_new + ]] + [else _eval nth :ast 2 :env] + ]] + + [[[symbol fn*]] + output fn_new nth :ast 1 :env nth :ast 2 ] + + [else + localmake "el eval_ast :ast :env + localmake "f nth :el 0 + case obj_type :f [ + [[nativefn] + output apply obj_val :f butfirst obj_val :el ] + [[fn] + localmake "funcenv env_new fn_env :f fn_args :f rest :el + output _eval fn_body :f :funcenv ] + [else + (throw "error [Wrong type for apply])] + ] ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to re :str +output _eval _read :str :repl_env +end + +to rep :str +output _print re :str +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + localmake "exception error + if not emptyp :exception [ + (print "Error: first butfirst :exception) + ] + ] + ] +] +end + +make "repl_env env_new [] [] [] +foreach :core_ns [ + ignore env_set :repl_env first ? first butfirst ? +] +; core.mal: defined using the language itself +ignore re "|(def! not (fn* (a) (if a false true)))| +repl +bye diff --git a/logo/step5_tco.lg b/logo/step5_tco.lg new file mode 100644 index 0000000000..a9171f39a2 --- /dev/null +++ b/logo/step5_tco.lg @@ -0,0 +1,123 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg +load "../logo/core.lg + +to _read :str +output read_str :str +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :a_ast :a_env +localmake "ast :a_ast +localmake "env :a_env +forever [ + if (obj_type :ast) <> "list [output eval_ast :ast :env] + if emptyp obj_val :ast [output :ast] + localmake "a0 nth :ast 0 + case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + make "env :letenv + make "ast nth :ast 2 ] ; TCO + + [[[symbol do]] + localmake "i 1 + while [:i < ((_count :ast) - 1)] [ + ignore _eval nth :ast :i :env + make "i (:i + 1) + ] + make "ast last obj_val :ast ] ; TCO + + [[[symbol if]] + localmake "a1 nth :ast 1 + localmake "cond _eval :a1 :env + case obj_type :cond [ + [[nil false] ifelse (_count :ast) > 3 [ + make "ast nth :ast 3 ; TCO + ] [ + output nil_new + ]] + [else make "ast nth :ast 2] ; TCO + ]] + + [[[symbol fn*]] + output fn_new nth :ast 1 :env nth :ast 2 ] + + [else + localmake "el eval_ast :ast :env + localmake "f nth :el 0 + case obj_type :f [ + [[nativefn] + output apply obj_val :f butfirst obj_val :el ] + [[fn] + make "env env_new fn_env :f fn_args :f rest :el + make "ast fn_body :f ] ; TCO + [else + (throw "error [Wrong type for apply])] + ] ] + ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to re :str +output _eval _read :str :repl_env +end + +to rep :str +output _print re :str +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + localmake "exception error + if not emptyp :exception [ + (print "Error: first butfirst :exception) + ] + ] + ] +] +end + +make "repl_env env_new [] [] [] +foreach :core_ns [ + ignore env_set :repl_env first ? first butfirst ? +] +; core.mal: defined using the language itself +ignore re "|(def! not (fn* (a) (if a false true)))| +repl +bye diff --git a/logo/step6_file.lg b/logo/step6_file.lg new file mode 100644 index 0000000000..2bf753ea91 --- /dev/null +++ b/logo/step6_file.lg @@ -0,0 +1,151 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg +load "../logo/core.lg + +to _read :str +output read_str :str +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :a_ast :a_env +localmake "ast :a_ast +localmake "env :a_env +forever [ + if (obj_type :ast) <> "list [output eval_ast :ast :env] + if emptyp obj_val :ast [output :ast] + localmake "a0 nth :ast 0 + case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + make "env :letenv + make "ast nth :ast 2 ] ; TCO + + [[[symbol do]] + localmake "i 1 + while [:i < ((_count :ast) - 1)] [ + ignore _eval nth :ast :i :env + make "i (:i + 1) + ] + make "ast last obj_val :ast ] ; TCO + + [[[symbol if]] + localmake "a1 nth :ast 1 + localmake "cond _eval :a1 :env + case obj_type :cond [ + [[nil false] ifelse (_count :ast) > 3 [ + make "ast nth :ast 3 ; TCO + ] [ + output nil_new + ]] + [else make "ast nth :ast 2] ; TCO + ]] + + [[[symbol fn*]] + output fn_new nth :ast 1 :env nth :ast 2 ] + + [else + localmake "el eval_ast :ast :env + localmake "f nth :el 0 + case obj_type :f [ + [[nativefn] + output apply obj_val :f butfirst obj_val :el ] + [[fn] + make "env env_new fn_env :f fn_args :f rest :el + make "ast fn_body :f ] ; TCO + [else + (throw "error [Wrong type for apply])] + ] ] + ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to re :str +output _eval _read :str :repl_env +end + +to rep :str +output _print re :str +end + +to print_exception :exception +if not emptyp :exception [ + localmake "e first butfirst :exception + ifelse :e = "_mal_exception_ [ + (print "Error: pr_str :global_exception "false) + ] [ + (print "Error: :e) + ] +] +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + print_exception error + ] + ] +] +end + +to mal_eval :a +output _eval :a :repl_env +end + +to argv_list +localmake "argv ifelse emptyp :command.line [[]] [butfirst :command.line] +output obj_new "list map [obj_new "string ?] :argv +end + +make "repl_env env_new [] [] [] +foreach :core_ns [ + ignore env_set :repl_env first ? first butfirst ? +] +ignore env_set :repl_env [symbol eval] [nativefn mal_eval] +ignore env_set :repl_env [symbol *ARGV*] argv_list + +; core.mal: defined using the language itself +ignore re "|(def! not (fn* (a) (if a false true)))| +ignore re "|(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))| + +if not emptyp :command.line [ + catch "error [ignore re (word "\(load-file :space_char "\" first :command.line "\"\) )] + print_exception error + bye +] + +repl +bye diff --git a/logo/step7_quote.lg b/logo/step7_quote.lg new file mode 100644 index 0000000000..5a29b7f535 --- /dev/null +++ b/logo/step7_quote.lg @@ -0,0 +1,178 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg +load "../logo/core.lg + +to _read :str +output read_str :str +end + +to pairp :obj +output and sequentialp :obj ((_count :obj) > 0) +end + +to quasiquote :ast +if not pairp :ast [output (mal_list symbol_new "quote :ast)] +localmake "a0 nth :ast 0 +if symbolnamedp "unquote :a0 [output nth :ast 1] +if pairp :a0 [ + localmake "a00 nth :a0 0 + if symbolnamedp "splice-unquote :a00 [ + localmake "a01 nth :a0 1 + output (mal_list symbol_new "concat :a01 (mal_list symbol_new "quasiquote rest :ast)) + ] +] +output (mal_list symbol_new "cons (mal_list symbol_new "quasiquote :a0) (mal_list symbol_new "quasiquote rest :ast)) +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :a_ast :a_env +localmake "ast :a_ast +localmake "env :a_env +forever [ + if (obj_type :ast) <> "list [output eval_ast :ast :env] + if emptyp obj_val :ast [output :ast] + localmake "a0 nth :ast 0 + case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + make "env :letenv + make "ast nth :ast 2 ] ; TCO + + [[[symbol quote]] + output nth :ast 1 ] + + [[[symbol quasiquote]] + make "ast quasiquote nth :ast 1 ] ; TCO + + [[[symbol do]] + localmake "i 1 + while [:i < ((_count :ast) - 1)] [ + ignore _eval nth :ast :i :env + make "i (:i + 1) + ] + make "ast last obj_val :ast ] ; TCO + + [[[symbol if]] + localmake "a1 nth :ast 1 + localmake "cond _eval :a1 :env + case obj_type :cond [ + [[nil false] ifelse (_count :ast) > 3 [ + make "ast nth :ast 3 ; TCO + ] [ + output nil_new + ]] + [else make "ast nth :ast 2] ; TCO + ]] + + [[[symbol fn*]] + output fn_new nth :ast 1 :env nth :ast 2 ] + + [else + localmake "el eval_ast :ast :env + localmake "f nth :el 0 + case obj_type :f [ + [[nativefn] + output apply obj_val :f butfirst obj_val :el ] + [[fn] + make "env env_new fn_env :f fn_args :f rest :el + make "ast fn_body :f ] ; TCO + [else + (throw "error [Wrong type for apply])] + ] ] + ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to re :str +output _eval _read :str :repl_env +end + +to rep :str +output _print re :str +end + +to print_exception :exception +if not emptyp :exception [ + localmake "e first butfirst :exception + ifelse :e = "_mal_exception_ [ + (print "Error: pr_str :global_exception "false) + ] [ + (print "Error: :e) + ] +] +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + localmake "exception error + if not emptyp :exception [ + (print "Error: first butfirst :exception) + ] + ] + ] +] +end + +to mal_eval :a +output _eval :a :repl_env +end + +to argv_list +localmake "argv ifelse emptyp :command.line [[]] [butfirst :command.line] +output obj_new "list map [obj_new "string ?] :argv +end + +make "repl_env env_new [] [] [] +foreach :core_ns [ + ignore env_set :repl_env first ? first butfirst ? +] +ignore env_set :repl_env [symbol eval] [nativefn mal_eval] +ignore env_set :repl_env [symbol *ARGV*] argv_list + +; core.mal: defined using the language itself +ignore re "|(def! not (fn* (a) (if a false true)))| +ignore re "|(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))| + +if not emptyp :command.line [ + catch "error [ignore re (word "\(load-file :space_char "\" first :command.line "\"\) )] + print_exception error + bye +] + +repl +bye diff --git a/logo/step8_macros.lg b/logo/step8_macros.lg new file mode 100644 index 0000000000..885eff3fbd --- /dev/null +++ b/logo/step8_macros.lg @@ -0,0 +1,213 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg +load "../logo/core.lg + +to _read :str +output read_str :str +end + +to pairp :obj +output and sequentialp :obj ((_count :obj) > 0) +end + +to quasiquote :ast +if not pairp :ast [output (mal_list symbol_new "quote :ast)] +localmake "a0 nth :ast 0 +if symbolnamedp "unquote :a0 [output nth :ast 1] +if pairp :a0 [ + localmake "a00 nth :a0 0 + if symbolnamedp "splice-unquote :a00 [ + localmake "a01 nth :a0 1 + output (mal_list symbol_new "concat :a01 (mal_list symbol_new "quasiquote rest :ast)) + ] +] +output (mal_list symbol_new "cons (mal_list symbol_new "quasiquote :a0) (mal_list symbol_new "quasiquote rest :ast)) +end + +to macrocallp :ast :env +if (obj_type :ast) = "list [ + if (_count :ast) > 0 [ + localmake "a0 nth :ast 0 + if (obj_type :a0) = "symbol [ + if not emptyp env_find :env :a0 [ + localmake "f env_get :env :a0 + if (obj_type :f) = "fn [ + output fn_is_macro :f + ] + ] + ] + ] +] +output "false +end + +to _macroexpand :ast :env +if not macrocallp :ast :env [output :ast] +localmake "a0 nth :ast 0 +localmake "f env_get :env :a0 +output _macroexpand invoke_fn :f rest :ast :env +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :a_ast :a_env +localmake "ast :a_ast +localmake "env :a_env +forever [ + if (obj_type :ast) <> "list [output eval_ast :ast :env] + make "ast _macroexpand :ast :env + if (obj_type :ast) <> "list [output eval_ast :ast :env] + if emptyp obj_val :ast [output :ast] + localmake "a0 nth :ast 0 + case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + make "env :letenv + make "ast nth :ast 2 ] ; TCO + + [[[symbol quote]] + output nth :ast 1 ] + + [[[symbol quasiquote]] + make "ast quasiquote nth :ast 1 ] ; TCO + + [[[symbol defmacro!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + localmake "macro_fn _eval :a2 :env + fn_set_macro :macro_fn + output env_set :env :a1 :macro_fn ] + + [[[symbol macroexpand]] + output _macroexpand nth :ast 1 :env ] + + [[[symbol do]] + localmake "i 1 + while [:i < ((_count :ast) - 1)] [ + ignore _eval nth :ast :i :env + make "i (:i + 1) + ] + make "ast last obj_val :ast ] ; TCO + + [[[symbol if]] + localmake "a1 nth :ast 1 + localmake "cond _eval :a1 :env + case obj_type :cond [ + [[nil false] ifelse (_count :ast) > 3 [ + make "ast nth :ast 3 ; TCO + ] [ + output nil_new + ]] + [else make "ast nth :ast 2] ; TCO + ]] + + [[[symbol fn*]] + output fn_new nth :ast 1 :env nth :ast 2 ] + + [else + localmake "el eval_ast :ast :env + localmake "f nth :el 0 + case obj_type :f [ + [[nativefn] + output apply obj_val :f butfirst obj_val :el ] + [[fn] + make "env env_new fn_env :f fn_args :f rest :el + make "ast fn_body :f ] ; TCO + [else + (throw "error [Wrong type for apply])] + ] ] + ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to re :str +output _eval _read :str :repl_env +end + +to rep :str +output _print re :str +end + +to print_exception :exception +if not emptyp :exception [ + localmake "e first butfirst :exception + ifelse :e = "_mal_exception_ [ + (print "Error: pr_str :global_exception "false) + ] [ + (print "Error: :e) + ] +] +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + print_exception error + ] + ] +] +end + +to mal_eval :a +output _eval :a :repl_env +end + +to argv_list +localmake "argv ifelse emptyp :command.line [[]] [butfirst :command.line] +output obj_new "list map [obj_new "string ?] :argv +end + +make "repl_env env_new [] [] [] +foreach :core_ns [ + ignore env_set :repl_env first ? first butfirst ? +] +ignore env_set :repl_env [symbol eval] [nativefn mal_eval] +ignore env_set :repl_env [symbol *ARGV*] argv_list + +; core.mal: defined using the language itself +ignore re "|(def! not (fn* (a) (if a false true)))| +ignore re "|(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))| +ignore re "|(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))| +ignore re "|(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))| + +if not emptyp :command.line [ + catch "error [ignore re (word "\(load-file :space_char "\" first :command.line "\"\) )] + print_exception error + bye +] + +repl +bye diff --git a/logo/step9_try.lg b/logo/step9_try.lg new file mode 100644 index 0000000000..7639ff47c1 --- /dev/null +++ b/logo/step9_try.lg @@ -0,0 +1,228 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg +load "../logo/core.lg + +to _read :str +output read_str :str +end + +to pairp :obj +output and sequentialp :obj ((_count :obj) > 0) +end + +to quasiquote :ast +if not pairp :ast [output (mal_list symbol_new "quote :ast)] +localmake "a0 nth :ast 0 +if symbolnamedp "unquote :a0 [output nth :ast 1] +if pairp :a0 [ + localmake "a00 nth :a0 0 + if symbolnamedp "splice-unquote :a00 [ + localmake "a01 nth :a0 1 + output (mal_list symbol_new "concat :a01 (mal_list symbol_new "quasiquote rest :ast)) + ] +] +output (mal_list symbol_new "cons (mal_list symbol_new "quasiquote :a0) (mal_list symbol_new "quasiquote rest :ast)) +end + +to macrocallp :ast :env +if (obj_type :ast) = "list [ + if (_count :ast) > 0 [ + localmake "a0 nth :ast 0 + if (obj_type :a0) = "symbol [ + if not emptyp env_find :env :a0 [ + localmake "f env_get :env :a0 + if (obj_type :f) = "fn [ + output fn_is_macro :f + ] + ] + ] + ] +] +output "false +end + +to _macroexpand :ast :env +if not macrocallp :ast :env [output :ast] +localmake "a0 nth :ast 0 +localmake "f env_get :env :a0 +output _macroexpand invoke_fn :f rest :ast :env +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :a_ast :a_env +localmake "ast :a_ast +localmake "env :a_env +forever [ + if (obj_type :ast) <> "list [output eval_ast :ast :env] + make "ast _macroexpand :ast :env + if (obj_type :ast) <> "list [output eval_ast :ast :env] + if emptyp obj_val :ast [output :ast] + localmake "a0 nth :ast 0 + case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + make "env :letenv + make "ast nth :ast 2 ] ; TCO + + [[[symbol quote]] + output nth :ast 1 ] + + [[[symbol quasiquote]] + make "ast quasiquote nth :ast 1 ] ; TCO + + [[[symbol defmacro!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + localmake "macro_fn _eval :a2 :env + fn_set_macro :macro_fn + output env_set :env :a1 :macro_fn ] + + [[[symbol macroexpand]] + output _macroexpand nth :ast 1 :env ] + + [[[symbol try*]] + localmake "result nil_new + catch "error [make "result _eval nth :ast 1 :env] + localmake "exception error + ifelse or emptyp :exception ((_count :ast) < 3) [ + output :result + ] [ + localmake "e first butfirst :exception + localmake "exception_obj ifelse :e = "_mal_exception_ [:global_exception] [obj_new "string :e] + localmake "a2 nth :ast 2 + localmake "catchenv env_new :env [] [] + ignore env_set :catchenv nth :a2 1 :exception_obj + output _eval nth :a2 2 :catchenv + ] ] + + [[[symbol do]] + localmake "i 1 + while [:i < ((_count :ast) - 1)] [ + ignore _eval nth :ast :i :env + make "i (:i + 1) + ] + make "ast last obj_val :ast ] ; TCO + + [[[symbol if]] + localmake "a1 nth :ast 1 + localmake "cond _eval :a1 :env + case obj_type :cond [ + [[nil false] ifelse (_count :ast) > 3 [ + make "ast nth :ast 3 ; TCO + ] [ + output nil_new + ]] + [else make "ast nth :ast 2] ; TCO + ]] + + [[[symbol fn*]] + output fn_new nth :ast 1 :env nth :ast 2 ] + + [else + localmake "el eval_ast :ast :env + localmake "f nth :el 0 + case obj_type :f [ + [[nativefn] + output apply obj_val :f butfirst obj_val :el ] + [[fn] + make "env env_new fn_env :f fn_args :f rest :el + make "ast fn_body :f ] ; TCO + [else + (throw "error [Wrong type for apply])] + ] ] + ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to re :str +output _eval _read :str :repl_env +end + +to rep :str +output _print re :str +end + +to print_exception :exception +if not emptyp :exception [ + localmake "e first butfirst :exception + ifelse :e = "_mal_exception_ [ + (print "Error: pr_str :global_exception "false) + ] [ + (print "Error: :e) + ] +] +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + print_exception error + ] + ] +] +end + +to mal_eval :a +output _eval :a :repl_env +end + +to argv_list +localmake "argv ifelse emptyp :command.line [[]] [butfirst :command.line] +output obj_new "list map [obj_new "string ?] :argv +end + +make "repl_env env_new [] [] [] +foreach :core_ns [ + ignore env_set :repl_env first ? first butfirst ? +] +ignore env_set :repl_env [symbol eval] [nativefn mal_eval] +ignore env_set :repl_env [symbol *ARGV*] argv_list + +; core.mal: defined using the language itself +ignore re "|(def! not (fn* (a) (if a false true)))| +ignore re "|(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))| +ignore re "|(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))| +ignore re "|(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))| + +if not emptyp :command.line [ + catch "error [ignore re (word "\(load-file :space_char "\" first :command.line "\"\) )] + print_exception error + bye +] + +repl +bye diff --git a/logo/stepA_mal.lg b/logo/stepA_mal.lg new file mode 100644 index 0000000000..25db73000b --- /dev/null +++ b/logo/stepA_mal.lg @@ -0,0 +1,232 @@ +load "../logo/readline.lg +load "../logo/reader.lg +load "../logo/printer.lg +load "../logo/types.lg +load "../logo/env.lg +load "../logo/core.lg + +to _read :str +output read_str :str +end + +to pairp :obj +output and sequentialp :obj ((_count :obj) > 0) +end + +to quasiquote :ast +if not pairp :ast [output (mal_list symbol_new "quote :ast)] +localmake "a0 nth :ast 0 +if symbolnamedp "unquote :a0 [output nth :ast 1] +if pairp :a0 [ + localmake "a00 nth :a0 0 + if symbolnamedp "splice-unquote :a00 [ + localmake "a01 nth :a0 1 + output (mal_list symbol_new "concat :a01 (mal_list symbol_new "quasiquote rest :ast)) + ] +] +output (mal_list symbol_new "cons (mal_list symbol_new "quasiquote :a0) (mal_list symbol_new "quasiquote rest :ast)) +end + +to macrocallp :ast :env +if (obj_type :ast) = "list [ + if (_count :ast) > 0 [ + localmake "a0 nth :ast 0 + if (obj_type :a0) = "symbol [ + if not emptyp env_find :env :a0 [ + localmake "f env_get :env :a0 + if (obj_type :f) = "fn [ + output fn_is_macro :f + ] + ] + ] + ] +] +output "false +end + +to _macroexpand :ast :env +if not macrocallp :ast :env [output :ast] +localmake "a0 nth :ast 0 +localmake "f env_get :env :a0 +output _macroexpand invoke_fn :f rest :ast :env +end + +to eval_ast :ast :env +output case (obj_type :ast) [ + [[symbol] env_get :env :ast] + [[list] obj_new "list map [_eval ? :env] obj_val :ast] + [[vector] obj_new "vector map [_eval ? :env] obj_val :ast] + [[hashmap] obj_new "hashmap map [_eval ? :env] obj_val :ast] + [else :ast] +] +end + +to _eval :a_ast :a_env +localmake "ast :a_ast +localmake "env :a_env +forever [ + if (obj_type :ast) <> "list [output eval_ast :ast :env] + make "ast _macroexpand :ast :env + if (obj_type :ast) <> "list [output eval_ast :ast :env] + if emptyp obj_val :ast [output :ast] + localmake "a0 nth :ast 0 + case list obj_type :a0 obj_val :a0 [ + [[[symbol def!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + output env_set :env :a1 _eval :a2 :env ] + + [[[symbol let*]] + localmake "a1 nth :ast 1 + localmake "letenv env_new :env [] [] + localmake "i 0 + while [:i < _count :a1] [ + ignore env_set :letenv nth :a1 :i _eval nth :a1 (:i + 1) :letenv + make "i (:i + 2) + ] + make "env :letenv + make "ast nth :ast 2 ] ; TCO + + [[[symbol quote]] + output nth :ast 1 ] + + [[[symbol quasiquote]] + make "ast quasiquote nth :ast 1 ] ; TCO + + [[[symbol defmacro!]] + localmake "a1 nth :ast 1 + localmake "a2 nth :ast 2 + localmake "macro_fn _eval :a2 :env + fn_set_macro :macro_fn + output env_set :env :a1 :macro_fn ] + + [[[symbol macroexpand]] + output _macroexpand nth :ast 1 :env ] + + [[[symbol try*]] + localmake "result nil_new + catch "error [make "result _eval nth :ast 1 :env] + localmake "exception error + ifelse or emptyp :exception ((_count :ast) < 3) [ + output :result + ] [ + localmake "e first butfirst :exception + localmake "exception_obj ifelse :e = "_mal_exception_ [:global_exception] [obj_new "string :e] + localmake "a2 nth :ast 2 + localmake "catchenv env_new :env [] [] + ignore env_set :catchenv nth :a2 1 :exception_obj + output _eval nth :a2 2 :catchenv + ] ] + + [[[symbol do]] + localmake "i 1 + while [:i < ((_count :ast) - 1)] [ + ignore _eval nth :ast :i :env + make "i (:i + 1) + ] + make "ast last obj_val :ast ] ; TCO + + [[[symbol if]] + localmake "a1 nth :ast 1 + localmake "cond _eval :a1 :env + case obj_type :cond [ + [[nil false] ifelse (_count :ast) > 3 [ + make "ast nth :ast 3 ; TCO + ] [ + output nil_new + ]] + [else make "ast nth :ast 2] ; TCO + ]] + + [[[symbol fn*]] + output fn_new nth :ast 1 :env nth :ast 2 ] + + [else + localmake "el eval_ast :ast :env + localmake "f nth :el 0 + case obj_type :f [ + [[nativefn] + output apply obj_val :f butfirst obj_val :el ] + [[fn] + make "env env_new fn_env :f fn_args :f rest :el + make "ast fn_body :f ] ; TCO + [else + (throw "error [Wrong type for apply])] + ] ] + ] +] +end + +to _print :exp +output pr_str :exp "true +end + +to re :str +output _eval _read :str :repl_env +end + +to rep :str +output _print re :str +end + +to print_exception :exception +if not emptyp :exception [ + localmake "e first butfirst :exception + ifelse :e = "_mal_exception_ [ + (print "Error: pr_str :global_exception "false) + ] [ + (print "Error: :e) + ] +] +end + +to repl +localmake "running "true +while [:running] [ + localmake "line readline word "user> :space_char + ifelse :line=[] [ + print " + make "running "false + ] [ + if not emptyp :line [ + catch "error [print rep :line] + print_exception error + ] + ] +] +end + +to mal_eval :a +output _eval :a :repl_env +end + +to argv_list +localmake "argv ifelse emptyp :command.line [[]] [butfirst :command.line] +output obj_new "list map [obj_new "string ?] :argv +end + +make "repl_env env_new [] [] [] +foreach :core_ns [ + ignore env_set :repl_env first ? first butfirst ? +] +ignore env_set :repl_env [symbol eval] [nativefn mal_eval] +ignore env_set :repl_env [symbol *ARGV*] argv_list + +; core.mal: defined using the language itself +ignore re "|(def! *host-language* "logo")| +ignore re "|(def! not (fn* (a) (if a false true)))| +ignore re "|(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))| +ignore re "|(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))| +ignore re "|(def! *gensym-counter* (atom 0))| +ignore re "|(def! gensym (fn* [] (symbol (str "G__" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))| +ignore re "|(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))| + +if not emptyp :command.line [ + catch "error [ignore re (word "\(load-file :space_char "\" first :command.line "\"\) )] + print_exception error + bye +] + +ignore re "|(println (str "Mal [" *host-language* "]"))| +repl +bye diff --git a/logo/tests/stepA_mal.mal b/logo/tests/stepA_mal.mal new file mode 100644 index 0000000000..904b7db3bf --- /dev/null +++ b/logo/tests/stepA_mal.mal @@ -0,0 +1,30 @@ +;; Testing basic Logo interop + +(logo-eval "7") +;=>7 + +(logo-eval "\"hello") +;=>"hello" + +(logo-eval "[7 8 9]") +;=>(7 8 9) + +(logo-eval "123 = 123") +;=>true + +(logo-eval "not emptyp []") +;=>false + +(logo-eval "print [hello world]") +; hello world +;=>nil + +(logo-eval "make \"foo 8") +(logo-eval ":foo") +;=>8 + +(logo-eval "apply \"word map [reverse ?] [Abc Abcd Abcde]") +;=>"cbAdcbAedcbA" + +(logo-eval "map [1 + ?] [1 2 3]") +;=>(2 3 4) diff --git a/logo/types.lg b/logo/types.lg new file mode 100644 index 0000000000..e7bffd16ac --- /dev/null +++ b/logo/types.lg @@ -0,0 +1,175 @@ +; Make Logo's string-comparison case sensitive +make "caseignoredp "false + +; Load the 'case' library macro +case "dummy [] + +; Redefine 'case' macro to not override caseignoredp +.macro case :case.value :case.clauses +catch "case.error [output case.helper :case.value :case.clauses] +(throw "error [Empty CASE clause]) +end + +to obj_new :type :val +output list :type :val +end + +to obj_new_with_meta :type :val :meta +output (list :type :val :meta) +end + +to obj_type :obj +output first :obj +end + +to obj_val :obj +output item 2 :obj +end + +to obj_meta :obj +if (count :obj) < 3 [output []] +output item 3 :obj +end + +make "global_nil obj_new "nil [] + +to nil_new +output :global_nil +end + +make "global_true obj_new "true [] + +to true_new +output :global_true +end + +make "global_false obj_new "false [] + +to false_new +output :global_false +end + +to symbol_new :name +output obj_new "symbol :name +end + +to hashmap_get :h :key +localmake "i 1 +while [:i < count :h] [ + if equal_q item :i :h :key [ + output item (:i + 1) :h + ] + make "i (:i + 2) +] +output [] +end + +; Returns a new list with the key-val pair set +to hashmap_put :h :key :val +localmake "res hashmap_delete :h :key +make "res lput :key :res +make "res lput :val :res +output :res +end + +; Returns a new list without the key-val pair set +to hashmap_delete :h :key +localmake "res [] +localmake "i 1 +while [:i < count :h] [ + if (item :i :h) <> :key [ + make "res lput item :i :h :res + make "res lput item (:i + 1) :h :res + ] + make "i (:i + 2) +] +output :res +end + +to fn_new :args :env :body +output obj_new "fn (list :args :env :body "false) +end + +to fn_args :fn +output item 1 obj_val :fn +end + +to fn_env :fn +output item 2 obj_val :fn +end + +to fn_body :fn +output item 3 obj_val :fn +end + +to fn_is_macro :fn +output item 4 obj_val :fn +end + +to fn_set_macro :fn +.setfirst butfirst butfirst butfirst obj_val :fn "true +end + +; zero-based sequence addressing +to nth :seq :index +output item (:index + 1) obj_val :seq +end + +to _count :seq +output count obj_val :seq +end + +to rest :seq +output obj_new obj_type :seq butfirst obj_val :seq +end + +to drop :seq :num +if or :num = 0 (_count :seq) = 0 [output :seq] +foreach obj_val :seq [ + if # >= :num [output obj_new obj_type :seq ?rest] +] +end + +to sequentialp :obj +output or ((obj_type :obj) = "list) ((obj_type :obj) = "vector) +end + +to equal_sequential_q :a :b +if (_count :a) <> (_count :b) [output "false] +(foreach obj_val :a obj_val :b [ + if not equal_q ?1 ?2 [output "false] +]) +output "true +end + +to equal_hashmap_q :a :b +if (_count :a) <> (_count :b) [output "false] +localmake "a_keys obj_val mal_keys :a +foreach :a_keys [ + localmake "a_val hashmap_get obj_val :a ? + localmake "b_val hashmap_get obj_val :b ? + if emptyp :b_val [output "false] + if not equal_q :a_val :b_val [output "false] +] +output "true +end + +to equal_q :a :b +output cond [ + [[and sequentialp :a sequentialp :b] + equal_sequential_q :a :b] + [[((obj_type :a) = (obj_type :b))] + case obj_type :a [ + [[true false nil] "true] + [[number string keyword symbol] ((obj_val :a) = (obj_val :b))] + [[hashmap] equal_hashmap_q :a :b] + [[atom] equal_q obj_val :a obj_val :b] + [else "false] + ]] + [else "false] +] +end + +to symbolnamedp :name :obj +output and ((obj_type :obj) = "symbol) ((obj_val :obj) = :name) +end From d10848bb1da85bcdc4e24a69e098ab0c24882b0b Mon Sep 17 00:00:00 2001 From: rhysd Date: Sat, 25 Jun 2016 08:21:15 +0900 Subject: [PATCH 0012/2308] Crystal: subcommand is changed from 'build' to 'compile' --- README.md | 2 +- crystal/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aa6cf26767..04b6cada38 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ coffee ./stepX_YYY *The Crystal implementation of mal was created by [Linda_pp](https://github.com/rhysd)* -The Crystal implementation of mal has been tested with Crystal 0.17.4. +The Crystal implementation of mal has been tested with Crystal 0.18.4. ``` cd crystal diff --git a/crystal/Makefile b/crystal/Makefile index ba96eadb6a..e33eadbef6 100644 --- a/crystal/Makefile +++ b/crystal/Makefile @@ -13,7 +13,7 @@ mal: $(LAST_STEP_BIN) cp $< $@ $(STEP_BINS): %: %.cr $(MAL_LIB) - crystal build --release $< + crystal compile --release $< clean: rm -rf $(STEP_BINS) mal .crystal From 7546ae18d2a2ea427797c144ccb505415bbfd454 Mon Sep 17 00:00:00 2001 From: rhysd Date: Sat, 25 Jun 2016 08:21:36 +0900 Subject: [PATCH 0013/2308] Crystal: Use Int64 instead of Int32 because time_ms requires 64bit integer This fixes a test case for stepA. --- crystal/core.cr | 10 +++++----- crystal/printer.cr | 2 +- crystal/reader.cr | 2 +- crystal/step2_eval.cr | 10 +++++----- crystal/step3_env.cr | 10 +++++----- crystal/types.cr | 4 ++-- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crystal/core.cr b/crystal/core.cr index ce5ad3a39d..b7a5c3c931 100644 --- a/crystal/core.cr +++ b/crystal/core.cr @@ -11,7 +11,7 @@ module Mal macro calc_op(op) -> (args : Array(Mal::Type)) { x, y = args[0].unwrap, args[1].unwrap - eval_error "invalid arguments for binary operator {{op.id}}" unless x.is_a?(Int32) && y.is_a?(Int32) + eval_error "invalid arguments for binary operator {{op.id}}" unless x.is_a?(Int64) && y.is_a?(Int64) Mal::Type.new(x {{op.id}} y) } end @@ -33,9 +33,9 @@ def self.count(args) a = args.first.unwrap case a when Array - a.size as Int32 + a.size.to_i64 when Nil - 0 + 0i64 else eval_error "invalid argument for function 'count'" end @@ -92,7 +92,7 @@ end def self.nth(args) a0, a1 = args[0].unwrap, args[1].unwrap eval_error "1st argument of nth must be list or vector" unless a0.is_a? Array - eval_error "2nd argument of nth must be integer" unless a1.is_a? Int32 + eval_error "2nd argument of nth must be integer" unless a1.is_a? Int64 a0[a1] end @@ -362,7 +362,7 @@ def self.seq(args) end def self.time_ms(args) - Time.now.epoch_ms.to_i32 + Time.now.epoch_ms.to_i64 end # Note: diff --git a/crystal/printer.cr b/crystal/printer.cr index 7444cb272a..cb9bf3611b 100644 --- a/crystal/printer.cr +++ b/crystal/printer.cr @@ -4,7 +4,7 @@ def pr_str(value, print_readably = true) case value when Nil then "nil" when Bool then value.to_s - when Int32 then value.to_s + when Int64 then value.to_s when Mal::List then "(#{value.map{|v| pr_str(v, print_readably) as String}.join(" ")})" when Mal::Vector then "[#{value.map{|v| pr_str(v, print_readably) as String}.join(" ")}]" when Mal::Symbol then value.str.to_s diff --git a/crystal/reader.cr b/crystal/reader.cr index e26a646dd4..b02c47a39b 100644 --- a/crystal/reader.cr +++ b/crystal/reader.cr @@ -77,7 +77,7 @@ class Reader parse_error "expected Atom but got EOF" unless token Mal::Type.new case - when token =~ /^-?\d+$/ then token.to_i + when token =~ /^-?\d+$/ then token.to_i64 when token == "true" then true when token == "false" then false when token == "nil" then nil diff --git a/crystal/step2_eval.cr b/crystal/step2_eval.cr index c41f50388a..f93b957e73 100755 --- a/crystal/step2_eval.cr +++ b/crystal/step2_eval.cr @@ -18,7 +18,7 @@ module Mal def num_func(func) -> (args : Array(Mal::Type)) { x, y = args[0].unwrap, args[1].unwrap - eval_error "invalid arguments" unless x.is_a?(Int32) && y.is_a?(Int32) + eval_error "invalid arguments" unless x.is_a?(Int64) && y.is_a?(Int64) Mal::Type.new func.call(x, y) } end @@ -79,10 +79,10 @@ module Mal end $repl_env = { - "+" => Mal.num_func(->(x : Int32, y : Int32){ x + y }), - "-" => Mal.num_func(->(x : Int32, y : Int32){ x - y }), - "*" => Mal.num_func(->(x : Int32, y : Int32){ x * y }), - "/" => Mal.num_func(->(x : Int32, y : Int32){ x / y }), + "+" => Mal.num_func(->(x : Int64, y : Int64){ x + y }), + "-" => Mal.num_func(->(x : Int64, y : Int64){ x - y }), + "*" => Mal.num_func(->(x : Int64, y : Int64){ x * y }), + "/" => Mal.num_func(->(x : Int64, y : Int64){ x / y }), } of String => Mal::Func while line = my_readline("user> ") diff --git a/crystal/step3_env.cr b/crystal/step3_env.cr index dd41af5a66..62543e81e4 100755 --- a/crystal/step3_env.cr +++ b/crystal/step3_env.cr @@ -16,16 +16,16 @@ end def num_func(func) -> (args : Array(Mal::Type)) { x, y = args[0].unwrap, args[1].unwrap - eval_error "invalid arguments" unless x.is_a?(Int32) && y.is_a?(Int32) + eval_error "invalid arguments" unless x.is_a?(Int64) && y.is_a?(Int64) Mal::Type.new func.call(x, y) } end $repl_env = Mal::Env.new nil -$repl_env.set("+", Mal::Type.new num_func(->(x : Int32, y : Int32){ x + y })) -$repl_env.set("-", Mal::Type.new num_func(->(x : Int32, y : Int32){ x - y })) -$repl_env.set("*", Mal::Type.new num_func(->(x : Int32, y : Int32){ x * y })) -$repl_env.set("/", Mal::Type.new num_func(->(x : Int32, y : Int32){ x / y })) +$repl_env.set("+", Mal::Type.new num_func(->(x : Int64, y : Int64){ x + y })) +$repl_env.set("-", Mal::Type.new num_func(->(x : Int64, y : Int64){ x - y })) +$repl_env.set("*", Mal::Type.new num_func(->(x : Int64, y : Int64){ x * y })) +$repl_env.set("/", Mal::Type.new num_func(->(x : Int64, y : Int64){ x / y })) module Mal extend self diff --git a/crystal/types.cr b/crystal/types.cr index 4c3590ac61..0c879f923f 100644 --- a/crystal/types.cr +++ b/crystal/types.cr @@ -38,7 +38,7 @@ module Mal class Type alias Func = (Array(Type) -> Type) - alias ValueType = Nil | Bool | Int32 | String | Symbol | List | Vector | HashMap | Func | Closure | Atom + alias ValueType = Nil | Bool | Int64 | String | Symbol | List | Vector | HashMap | Func | Closure | Atom property :is_macro, :meta @@ -80,7 +80,7 @@ module Mal {% for op in ops %} def {{op.id}}(other : Mal::Type) l, r = @val, other.unwrap - {% for t in [Int32, String] %} + {% for t in [Int64, String] %} if l.is_a?({{t}}) && r.is_a?({{t}}) return (l) {{op.id}} (r) end From 14678c3c9aab1cb2602c27a3882f150c095eb192 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 29 Jul 2016 10:48:13 -0500 Subject: [PATCH 0014/2308] perl6: update/add missing Dockerfile. Stats target. - Update docker image from rakudo 2016-04 to 2016-07 --- perl6/Dockerfile | 34 ++++++++++++++++++++++++++++++++++ perl6/Makefile | 16 ++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 perl6/Dockerfile diff --git a/perl6/Dockerfile b/perl6/Dockerfile new file mode 100644 index 0000000000..ea081e882a --- /dev/null +++ b/perl6/Dockerfile @@ -0,0 +1,34 @@ +FROM ubuntu:vivid +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# Perl6 build deps +RUN apt-get -y install libfile-copy-recursive-perl build-essential git + +RUN curl -O http://rakudo.org/downloads/star/rakudo-star-2016.07.tar.gz && \ + tar xzf rakudo-star-2016.07.tar.gz && \ + cd rakudo-star-2016.07 && \ + perl Configure.pl --prefix=/usr --gen-moar --gen-nqp --backends=moar && \ + make && \ + make install && \ + cd .. && \ + rm -rf rakudo-star-2016.07* diff --git a/perl6/Makefile b/perl6/Makefile index 09f99ce2e1..bd88eee87c 100644 --- a/perl6/Makefile +++ b/perl6/Makefile @@ -1,2 +1,18 @@ + +SOURCES_BASE = types.pm reader.pm printer.pm +SOURCES_LISP = env.pm core.pm stepA_mal.pl +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + all: @true + +.PHONY: stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" + + From 976547740f18dfb914b45a01078d371f42d136a5 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 29 Jul 2016 10:49:13 -0500 Subject: [PATCH 0015/2308] ada: stats target. Also, fix Crystal dockerfile build. --- ada/Makefile | 14 ++++++++++++++ crystal/Dockerfile | 1 + 2 files changed, 15 insertions(+) diff --git a/ada/Makefile b/ada/Makefile index 2610b49eff..6b1ad2978f 100644 --- a/ada/Makefile +++ b/ada/Makefile @@ -9,6 +9,9 @@ STEP2_DEPS=${STEP1_DEPS} STEP3_DEPS=${STEP2_DEPS} envs.ad[bs] eval_callback.ads STEP4_DEPS=${STEP3_DEPS} core.ad[bs] +SOURCES = $(filter-out $(STEP0_DEPS),$(STEP4_DEPS)) stepA_mal.gpr stepa_mal.adb +SOURCES_LISP = $(filter-out $(STEP2_DEPS),$(SOURCES)) + all: ${DIRS} ${PROGS} ${DIRS}: @@ -32,3 +35,14 @@ stepA_mal: stepa_mal.adb ${STEP4_DEPS} clean: rm -f ${PROGS} rm -rf obj + +.PHONY: stats stats-lisp + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*--|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" + +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*--|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" + diff --git a/crystal/Dockerfile b/crystal/Dockerfile index 367804ebd1..1c24bbd64d 100644 --- a/crystal/Dockerfile +++ b/crystal/Dockerfile @@ -25,5 +25,6 @@ WORKDIR /mal RUN apt-get -y install g++ # Crystal +RUN apt-get -y install apt-transport-https RUN curl http://dist.crystal-lang.org/apt/setup.sh | bash RUN apt-get -y install crystal From 8903188f8b384106e1ebae7a8babfb232f46a4ea Mon Sep 17 00:00:00 2001 From: Fred Im Date: Fri, 29 Jul 2016 18:37:32 +0000 Subject: [PATCH 0016/2308] Squashed commits, updated to latest swift3 (swift-3.0-PREVIEW-2) Main changes: * consistency of func arguments, while every argument has both an outer and an inner name, but the first argument's outer was "unnamed" by default in swift<2. now all arguments are consistent and requires the initial "_" to declare the outer "unnamed" for the first argument * indexes are now simpler types, the Array.index function computes successor/predecessor * many, many API changes, that result in shorter "verb" names of functions with named arguments ex: Array.joinWithSeparator(String) -> Array.joined(separator: String) --- swift3/Sources/core.swift | 30 ++++------ swift3/Sources/env.swift | 10 ++-- swift3/Sources/printer.swift | 19 +++---- swift3/Sources/reader.swift | 65 ++++++++++++---------- swift3/Sources/step0_repl/main.swift | 2 +- swift3/Sources/step1_read_print/main.swift | 10 ++-- swift3/Sources/step2_eval/main.swift | 14 ++--- swift3/Sources/step3_env/main.swift | 18 +++--- swift3/Sources/step4_if_fn_do/main.swift | 20 +++---- swift3/Sources/step5_tco/main.swift | 20 +++---- swift3/Sources/step6_file/main.swift | 24 ++++---- swift3/Sources/step7_quote/main.swift | 28 +++++----- swift3/Sources/step8_macros/main.swift | 32 +++++------ swift3/Sources/step9_try/main.swift | 32 +++++------ swift3/Sources/stepA_mal/main.swift | 32 +++++------ swift3/Sources/types.swift | 56 ++++++++++--------- 16 files changed, 203 insertions(+), 209 deletions(-) diff --git a/swift3/Sources/core.swift b/swift3/Sources/core.swift index 46bb032a02..fc4235b74c 100644 --- a/swift3/Sources/core.swift +++ b/swift3/Sources/core.swift @@ -6,7 +6,7 @@ import Glibc import Darwin #endif -func IntOp(op: (Int, Int) -> Int, _ a: MalVal, _ b: MalVal) throws -> MalVal { +func IntOp(_ op: (Int, Int) -> Int, _ a: MalVal, _ b: MalVal) throws -> MalVal { switch (a, b) { case (MV.MalInt(let i1), MV.MalInt(let i2)): return MV.MalInt(op(i1, i2)) @@ -15,7 +15,7 @@ func IntOp(op: (Int, Int) -> Int, _ a: MalVal, _ b: MalVal) throws -> MalVal { } } -func CmpOp(op: (Int, Int) -> Bool, _ a: MalVal, _ b: MalVal) throws -> MalVal { +func CmpOp(_ op: (Int, Int) -> Bool, _ a: MalVal, _ b: MalVal) throws -> MalVal { switch (a, b) { case (MV.MalInt(let i1), MV.MalInt(let i2)): return wraptf(op(i1, i2)) @@ -95,20 +95,20 @@ let core_ns: Dictionary) throws -> MalVal> = [ // let core_ns: [String: (Array) throws -> MalVal] = [ // ^ - let s = $0.map { pr_str($0,true) }.joinWithSeparator(" ") + let s = $0.map { pr_str($0,true) }.joined(separator: " ") return MV.MalString(s) }, "str": { // The comment for "pr-str" applies here, too. - let s = $0.map { pr_str($0,false) }.joinWithSeparator("") + let s = $0.map { pr_str($0,false) }.joined(separator: "") return MV.MalString(s) }, "prn": { - print($0.map { pr_str($0,true) }.joinWithSeparator(" ")) + print($0.map { pr_str($0,true) }.joined(separator: " ")) return MV.MalNil }, "println": { - print($0.map { pr_str($0,false) }.joinWithSeparator(" ")) + print($0.map { pr_str($0,false) }.joined(separator: " ")) return MV.MalNil }, "read-string": { @@ -121,7 +121,7 @@ let core_ns: Dictionary) throws -> MalVal> = [ switch $0[0] { case MV.MalString(let prompt): print(prompt, terminator: "") - let line = readLine(stripNewline: true) + let line = readLine(strippingNewline: true) if line == nil { return MV.MalNil } return MV.MalString(line!) default: throw MalError.General(msg: "Invalid readline call") @@ -130,17 +130,7 @@ let core_ns: Dictionary) throws -> MalVal> = [ "slurp": { switch $0[0] { case MV.MalString(let file): - // TODO: replace with this when it is available - // let data = try String(contentsOfFile: file, encoding: NSUTF8StringEncoding) - - let BUFSIZE = 1024 - var pp = popen("cat " + file, "r") - var buf = [CChar](count:BUFSIZE, repeatedValue:CChar(0)) - var data = String() - - while fgets(&buf, Int32(BUFSIZE), pp) != nil { - data = data + String.fromCString(buf)!; - } + let data = try String(contentsOfFile: file, encoding: String.Encoding.utf8) return MV.MalString(data) default: throw MalError.General(msg: "Invalid slurp call") } @@ -156,7 +146,7 @@ let core_ns: Dictionary) throws -> MalVal> = [ "*": { try IntOp({ $0 * $1}, $0[0], $0[1]) }, "/": { try IntOp({ $0 / $1}, $0[0], $0[1]) }, "time-ms": { - $0; // no parameters + let read = $0; // no parameters // TODO: replace with something more like this // return MV.MalInt(NSDate().timeIntervalSince1970 ) @@ -359,7 +349,7 @@ let core_ns: Dictionary) throws -> MalVal> = [ if $0.count < 1 { throw MalError.General(msg: "Invalid conj call") } switch $0[0] { case MV.MalList(let lst, _): - let a = Array($0[1..<$0.endIndex]).reverse() + let a = Array($0[1..<$0.endIndex]).reversed() return list(a + lst) case MV.MalVector(let lst, _): return vector(lst + $0[1..<$0.endIndex]) diff --git a/swift3/Sources/env.swift b/swift3/Sources/env.swift index 6c88d0a45e..1014f1d6d9 100644 --- a/swift3/Sources/env.swift +++ b/swift3/Sources/env.swift @@ -25,7 +25,7 @@ class Env { let b = bs[pos] switch b { case MalVal.MalSymbol("&"): - switch bs[pos.successor()] { + switch bs[bs.index(after: pos)] { case MalVal.MalSymbol(let sym): if pos < es.endIndex { let slc = es[pos.. Env? { + func find(_ key: MalVal) throws -> Env? { switch key { case MalVal.MalSymbol(let str): if data[str] != nil { @@ -63,7 +63,7 @@ class Env { } } - func get(key: MalVal) throws -> MalVal { + func get(_ key: MalVal) throws -> MalVal { switch key { case MalVal.MalSymbol(let str): let env = try self.find(key) @@ -76,7 +76,7 @@ class Env { } } - func set(key: MalVal, _ val: MalVal) throws -> MalVal { + func set(_ key: MalVal, _ val: MalVal) throws -> MalVal { switch key { case MalVal.MalSymbol(let str): data[str] = val diff --git a/swift3/Sources/printer.swift b/swift3/Sources/printer.swift index 01e8a23788..0f0dd29445 100644 --- a/swift3/Sources/printer.swift +++ b/swift3/Sources/printer.swift @@ -1,29 +1,26 @@ -func pr_str(obj: MalVal, _ print_readably: Bool = true) -> String { +func pr_str(_ obj: MalVal, _ print_readably: Bool = true) -> String { switch obj { case MalVal.MalList(let lst, _): let elems = lst.map { pr_str($0, print_readably) } - return "(" + elems.joinWithSeparator(" ") + ")" + return "(" + elems.joined(separator: " ") + ")" case MalVal.MalVector(let lst, _): let elems = lst.map { pr_str($0, print_readably) } - return "[" + elems.joinWithSeparator(" ") + "]" + return "[" + elems.joined(separator: " ") + "]" case MalVal.MalHashMap(let dict, _): let elems = dict.map { pr_str(MalVal.MalString($0), print_readably) + " " + pr_str($1, print_readably) } - return "{" + elems.joinWithSeparator(" ") + "}" + return "{" + elems.joined(separator: " ") + "}" case MalVal.MalString(let str): //print("kw: '\(str[str.startIndex])'") if str.characters.count > 0 && str[str.startIndex] == "\u{029e}" { - return ":" + str[str.startIndex.successor().. MalVal { +func read_int(_ rdr: Reader) -> MalVal { let start = rdr.pos - for cidx in rdr.pos.. MalVal { } } -func skip_whitespace_and_comments(rdr: Reader) { +func skip_whitespace_and_comments(_ rdr: Reader) { var in_comment = false - for cidx in rdr.pos.. MalVal { +func read_string(_ rdr: Reader) throws -> MalVal { let start = rdr.pos var escaped = false if rdr.str[rdr.pos] != "\"" { throw MalError.Reader(msg: "read_string call on non-string") } - for cidx in rdr.pos.successor().. rdr.str.endIndex { throw MalError.Reader(msg: "Expected '\"', got EOF") } - let matchStr = rdr.str.substringWithRange( - start.successor().. String { +func read_token(_ rdr: Reader) -> String { let start = rdr.pos - for cidx in rdr.pos.. MalVal { +func read_symbol(_ rdr: Reader) throws -> MalVal { let tok = read_token(rdr) switch tok { case "nil": return MalVal.MalNil @@ -102,12 +107,12 @@ func read_symbol(rdr: Reader) throws -> MalVal { } } -func read_atom(rdr: Reader) throws -> MalVal { +func read_atom(_ rdr: Reader) throws -> MalVal { if rdr.str.characters.count == 0 { throw MalError.Reader(msg: "Empty string passed to read_atom") } switch rdr.str[rdr.pos] { - case "-" where !int_char.contains(rdr.str[rdr.pos.successor()]): + case "-" where !int_char.contains(rdr.str[rdr.str.index(after: rdr.pos)]): return try read_symbol(rdr) case let c where int_char.contains(c): return read_int(rdr) @@ -121,7 +126,7 @@ func read_atom(rdr: Reader) throws -> MalVal { } } -func read_list(rdr: Reader, start: Character = "(", end: Character = ")") throws -> Array { +func read_list(_ rdr: Reader, start: Character = "(", end: Character = ")") throws -> Array { if rdr.str[rdr.pos] != start { throw MalError.Reader(msg: "expected '\(start)'") } @@ -138,7 +143,7 @@ func read_list(rdr: Reader, start: Character = "(", end: Character = ")") throws return lst } -func read_form(rdr: Reader) throws -> MalVal { +func read_form(_ rdr: Reader) throws -> MalVal { if rdr.str.characters.count == 0 { throw MalError.Reader(msg: "Empty string passed to read_form") } @@ -154,7 +159,7 @@ func read_form(rdr: Reader) throws -> MalVal { rdr.next() return list([MalVal.MalSymbol("quasiquote"), try read_form(rdr)]) case "~": - switch rdr.str[rdr.pos.successor()] { + switch rdr.str[rdr.str.index(after: rdr.pos)] { case "@": rdr.next() rdr.next() @@ -195,6 +200,6 @@ func read_form(rdr: Reader) throws -> MalVal { return res } -func read_str(str: String) throws -> MalVal { +func read_str(_ str: String) throws -> MalVal { return try read_form(Reader(str)) } diff --git a/swift3/Sources/step0_repl/main.swift b/swift3/Sources/step0_repl/main.swift index 2fadc16c56..f850fa793a 100644 --- a/swift3/Sources/step0_repl/main.swift +++ b/swift3/Sources/step0_repl/main.swift @@ -2,7 +2,7 @@ import Foundation while true { print("user> ", terminator: "") - let line = readLine(stripNewline: true) + let line = readLine(strippingNewline: true) if line == nil { break } if line == "" { continue } diff --git a/swift3/Sources/step1_read_print/main.swift b/swift3/Sources/step1_read_print/main.swift index a37d0e7876..07d79d1327 100644 --- a/swift3/Sources/step1_read_print/main.swift +++ b/swift3/Sources/step1_read_print/main.swift @@ -1,29 +1,29 @@ import Foundation // read -func READ(str: String) throws -> MalVal { +func READ(_ str: String) throws -> MalVal { return try read_str(str) } // eval -func EVAL(ast: MalVal, _ env: String) throws -> MalVal { +func EVAL(_ ast: MalVal, _ env: String) throws -> MalVal { return ast } // print -func PRINT(exp: MalVal) -> String { +func PRINT(_ exp: MalVal) -> String { return pr_str(exp, true) } // repl -func rep(str:String) throws -> String { +func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), "")) } while true { print("user> ", terminator: "") - let line = readLine(stripNewline: true) + let line = readLine(strippingNewline: true) if line == nil { break } if line == "" { continue } diff --git a/swift3/Sources/step2_eval/main.swift b/swift3/Sources/step2_eval/main.swift index 1d7203976a..7a3e49ad04 100644 --- a/swift3/Sources/step2_eval/main.swift +++ b/swift3/Sources/step2_eval/main.swift @@ -1,12 +1,12 @@ import Foundation // read -func READ(str: String) throws -> MalVal { +func READ(_ str: String) throws -> MalVal { return try read_str(str) } // eval -func eval_ast(ast: MalVal, _ env: Dictionary) throws -> MalVal { +func eval_ast(_ ast: MalVal, _ env: Dictionary) throws -> MalVal { switch ast { case MalVal.MalSymbol(let sym): if env[sym] == nil { @@ -26,7 +26,7 @@ func eval_ast(ast: MalVal, _ env: Dictionary) throws -> MalVal { } } -func EVAL(ast: MalVal, _ env: Dictionary) throws -> MalVal { +func EVAL(_ ast: MalVal, _ env: Dictionary) throws -> MalVal { switch ast { case MalVal.MalList(let lst, _): if lst.count == 0 { return ast } default: return try eval_ast(ast, env) @@ -46,17 +46,17 @@ func EVAL(ast: MalVal, _ env: Dictionary) throws -> MalVal { } // print -func PRINT(exp: MalVal) -> String { +func PRINT(_ exp: MalVal) -> String { return pr_str(exp, true) } // repl -func rep(str:String) throws -> String { +func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } -func IntOp(op: (Int, Int) -> Int, _ a: MalVal, _ b: MalVal) throws -> MalVal { +func IntOp(_ op: (Int, Int) -> Int, _ a: MalVal, _ b: MalVal) throws -> MalVal { switch (a, b) { case (MalVal.MalInt(let i1), MalVal.MalInt(let i2)): return MalVal.MalInt(op(i1, i2)) @@ -74,7 +74,7 @@ var repl_env: Dictionary = [ while true { print("user> ", terminator: "") - let line = readLine(stripNewline: true) + let line = readLine(strippingNewline: true) if line == nil { break } if line == "" { continue } diff --git a/swift3/Sources/step3_env/main.swift b/swift3/Sources/step3_env/main.swift index f1a762addc..8f37521770 100644 --- a/swift3/Sources/step3_env/main.swift +++ b/swift3/Sources/step3_env/main.swift @@ -1,12 +1,12 @@ import Foundation // read -func READ(str: String) throws -> MalVal { +func READ(_ str: String) throws -> MalVal { return try read_str(str) } // eval -func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { +func eval_ast(_ ast: MalVal, _ env: Env) throws -> MalVal { switch ast { case MalVal.MalSymbol: return try env.get(ast) @@ -23,7 +23,7 @@ func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { } } -func EVAL(ast: MalVal, _ env: Env) throws -> MalVal { +func EVAL(_ ast: MalVal, _ env: Env) throws -> MalVal { switch ast { case MalVal.MalList(let lst, _): if lst.count == 0 { return ast } default: return try eval_ast(ast, env) @@ -45,9 +45,9 @@ func EVAL(ast: MalVal, _ env: Env) throws -> MalVal { } var idx = binds.startIndex while idx < binds.endIndex { - let v = try EVAL(binds[idx.successor()], let_env) + let v = try EVAL(binds[binds.index(after: idx)], let_env) try let_env.set(binds[idx], v) - idx = idx.successor().successor() + idx = binds.index(idx, offsetBy: 2) } return try EVAL(lst[2], let_env) default: @@ -69,17 +69,17 @@ func EVAL(ast: MalVal, _ env: Env) throws -> MalVal { } // print -func PRINT(exp: MalVal) -> String { +func PRINT(_ exp: MalVal) -> String { return pr_str(exp, true) } // repl -func rep(str:String) throws -> String { +func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } -func IntOp(op: (Int, Int) -> Int, _ a: MalVal, _ b: MalVal) throws -> MalVal { +func IntOp(_ op: (Int, Int) -> Int, _ a: MalVal, _ b: MalVal) throws -> MalVal { switch (a, b) { case (MalVal.MalInt(let i1), MalVal.MalInt(let i2)): return MalVal.MalInt(op(i1, i2)) @@ -101,7 +101,7 @@ try repl_env.set(MalVal.MalSymbol("/"), while true { print("user> ", terminator: "") - let line = readLine(stripNewline: true) + let line = readLine(strippingNewline: true) if line == nil { break } if line == "" { continue } diff --git a/swift3/Sources/step4_if_fn_do/main.swift b/swift3/Sources/step4_if_fn_do/main.swift index 782e66a9f6..daab64c879 100644 --- a/swift3/Sources/step4_if_fn_do/main.swift +++ b/swift3/Sources/step4_if_fn_do/main.swift @@ -1,12 +1,12 @@ import Foundation // read -func READ(str: String) throws -> MalVal { +func READ(_ str: String) throws -> MalVal { return try read_str(str) } // eval -func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { +func eval_ast(_ ast: MalVal, _ env: Env) throws -> MalVal { switch ast { case MalVal.MalSymbol: return try env.get(ast) @@ -23,7 +23,7 @@ func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { } } -func EVAL(ast: MalVal, _ env: Env) throws -> MalVal { +func EVAL(_ ast: MalVal, _ env: Env) throws -> MalVal { switch ast { case MalVal.MalList(let lst, _): if lst.count == 0 { return ast } default: return try eval_ast(ast, env) @@ -45,16 +45,16 @@ func EVAL(ast: MalVal, _ env: Env) throws -> MalVal { } var idx = binds.startIndex while idx < binds.endIndex { - let v = try EVAL(binds[idx.successor()], let_env) + let v = try EVAL(binds[binds.index(after: idx)], let_env) try let_env.set(binds[idx], v) - idx = idx.successor().successor() + idx = binds.index(idx, offsetBy: 2) } return try EVAL(lst[2], let_env) case MalVal.MalSymbol("do"): - let slc = lst[lst.startIndex.successor().. MalVal { } // print -func PRINT(exp: MalVal) -> String { +func PRINT(_ exp: MalVal) -> String { return pr_str(exp, true) } // repl -func rep(str:String) throws -> String { +func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } @@ -116,7 +116,7 @@ try rep("(def! not (fn* (a) (if a false true)))") while true { print("user> ", terminator: "") - let line = readLine(stripNewline: true) + let line = readLine(strippingNewline: true) if line == nil { break } if line == "" { continue } diff --git a/swift3/Sources/step5_tco/main.swift b/swift3/Sources/step5_tco/main.swift index 866afbcc04..f632cd8af6 100644 --- a/swift3/Sources/step5_tco/main.swift +++ b/swift3/Sources/step5_tco/main.swift @@ -1,12 +1,12 @@ import Foundation // read -func READ(str: String) throws -> MalVal { +func READ(_ str: String) throws -> MalVal { return try read_str(str) } // eval -func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { +func eval_ast(_ ast: MalVal, _ env: Env) throws -> MalVal { switch ast { case MalVal.MalSymbol: return try env.get(ast) @@ -23,7 +23,7 @@ func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { } } -func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { +func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { var ast = orig_ast, env = orig_env while true { switch ast { @@ -47,16 +47,16 @@ func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { } var idx = binds.startIndex while idx < binds.endIndex { - let v = try EVAL(binds[idx.successor()], let_env) + let v = try EVAL(binds[binds.index(after: idx)], let_env) try let_env.set(binds[idx], v) - idx = idx.successor().successor() + idx = binds.index(idx, offsetBy: 2) } env = let_env ast = lst[2] // TCO case MalVal.MalSymbol("do"): - let slc = lst[1.. MalVal { } // print -func PRINT(exp: MalVal) -> String { +func PRINT(_ exp: MalVal) -> String { return pr_str(exp, true) } // repl -func rep(str:String) throws -> String { +func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } @@ -121,7 +121,7 @@ try rep("(def! not (fn* (a) (if a false true)))") while true { print("user> ", terminator: "") - let line = readLine(stripNewline: true) + let line = readLine(strippingNewline: true) if line == nil { break } if line == "" { continue } diff --git a/swift3/Sources/step6_file/main.swift b/swift3/Sources/step6_file/main.swift index 54451235bd..a9618cef87 100644 --- a/swift3/Sources/step6_file/main.swift +++ b/swift3/Sources/step6_file/main.swift @@ -1,12 +1,12 @@ import Foundation // read -func READ(str: String) throws -> MalVal { +func READ(_ str: String) throws -> MalVal { return try read_str(str) } // eval -func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { +func eval_ast(_ ast: MalVal, _ env: Env) throws -> MalVal { switch ast { case MalVal.MalSymbol: return try env.get(ast) @@ -23,7 +23,7 @@ func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { } } -func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { +func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { var ast = orig_ast, env = orig_env while true { switch ast { @@ -47,16 +47,16 @@ func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { } var idx = binds.startIndex while idx < binds.endIndex { - let v = try EVAL(binds[idx.successor()], let_env) + let v = try EVAL(binds[binds.index(after: idx)], let_env) try let_env.set(binds[idx], v) - idx = idx.successor().successor() + idx = binds.index(idx, offsetBy: 2) } env = let_env ast = lst[2] // TCO case MalVal.MalSymbol("do"): - let slc = lst[1.. MalVal { } // print -func PRINT(exp: MalVal) -> String { +func PRINT(_ exp: MalVal) -> String { return pr_str(exp, true) } // repl -func rep(str:String) throws -> String { +func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } @@ -119,8 +119,8 @@ try repl_env.set(MalVal.MalSymbol("eval"), let pargs = Process.arguments.map { MalVal.MalString($0) } // TODO: weird way to get empty list, fix this var args = pargs[pargs.startIndex.. 1 { while true { print("user> ", terminator: "") - let line = readLine(stripNewline: true) + let line = readLine(strippingNewline: true) if line == nil { break } if line == "" { continue } diff --git a/swift3/Sources/step7_quote/main.swift b/swift3/Sources/step7_quote/main.swift index 2ba74fa15f..53ba82c157 100644 --- a/swift3/Sources/step7_quote/main.swift +++ b/swift3/Sources/step7_quote/main.swift @@ -1,12 +1,12 @@ import Foundation // read -func READ(str: String) throws -> MalVal { +func READ(_ str: String) throws -> MalVal { return try read_str(str) } // eval -func is_pair(ast: MalVal) -> Bool { +func is_pair(_ ast: MalVal) -> Bool { switch ast { case MalVal.MalList(let lst, _): return lst.count > 0 case MalVal.MalVector(let lst, _): return lst.count > 0 @@ -14,7 +14,7 @@ func is_pair(ast: MalVal) -> Bool { } } -func quasiquote(ast: MalVal) -> MalVal { +func quasiquote(_ ast: MalVal) -> MalVal { if !is_pair(ast) { return list([MalVal.MalSymbol("quote"), ast]) } @@ -40,7 +40,7 @@ func quasiquote(ast: MalVal) -> MalVal { quasiquote(try! rest(ast))]) } -func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { +func eval_ast(_ ast: MalVal, _ env: Env) throws -> MalVal { switch ast { case MalVal.MalSymbol: return try env.get(ast) @@ -57,7 +57,7 @@ func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { } } -func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { +func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { var ast = orig_ast, env = orig_env while true { switch ast { @@ -81,9 +81,9 @@ func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { } var idx = binds.startIndex while idx < binds.endIndex { - let v = try EVAL(binds[idx.successor()], let_env) + let v = try EVAL(binds[binds.index(after: idx)], let_env) try let_env.set(binds[idx], v) - idx = idx.successor().successor() + idx = binds.index(idx, offsetBy: 2) } env = let_env ast = lst[2] // TCO @@ -92,9 +92,9 @@ func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { case MalVal.MalSymbol("quasiquote"): ast = quasiquote(lst[1]) // TCO case MalVal.MalSymbol("do"): - let slc = lst[1.. MalVal { } // print -func PRINT(exp: MalVal) -> String { +func PRINT(_ exp: MalVal) -> String { return pr_str(exp, true) } // repl -func rep(str:String) throws -> String { +func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } @@ -157,8 +157,8 @@ try repl_env.set(MalVal.MalSymbol("eval"), let pargs = Process.arguments.map { MalVal.MalString($0) } // TODO: weird way to get empty list, fix this var args = pargs[pargs.startIndex.. 1 { while true { print("user> ", terminator: "") - let line = readLine(stripNewline: true) + let line = readLine(strippingNewline: true) if line == nil { break } if line == "" { continue } diff --git a/swift3/Sources/step8_macros/main.swift b/swift3/Sources/step8_macros/main.swift index 5830f7d6c1..b799ba546c 100644 --- a/swift3/Sources/step8_macros/main.swift +++ b/swift3/Sources/step8_macros/main.swift @@ -1,12 +1,12 @@ import Foundation // read -func READ(str: String) throws -> MalVal { +func READ(_ str: String) throws -> MalVal { return try read_str(str) } // eval -func is_pair(ast: MalVal) -> Bool { +func is_pair(_ ast: MalVal) -> Bool { switch ast { case MalVal.MalList(let lst, _): return lst.count > 0 case MalVal.MalVector(let lst, _): return lst.count > 0 @@ -14,7 +14,7 @@ func is_pair(ast: MalVal) -> Bool { } } -func quasiquote(ast: MalVal) -> MalVal { +func quasiquote(_ ast: MalVal) -> MalVal { if !is_pair(ast) { return list([MalVal.MalSymbol("quote"), ast]) } @@ -40,7 +40,7 @@ func quasiquote(ast: MalVal) -> MalVal { quasiquote(try! rest(ast))]) } -func is_macro(ast: MalVal, _ env: Env) -> Bool { +func is_macro(_ ast: MalVal, _ env: Env) -> Bool { switch ast { case MalVal.MalList(let lst, _) where lst.count > 0: let a0 = lst[lst.startIndex] @@ -62,7 +62,7 @@ func is_macro(ast: MalVal, _ env: Env) -> Bool { } } -func macroexpand(orig_ast: MalVal, _ env: Env) throws -> MalVal { +func macroexpand(_ orig_ast: MalVal, _ env: Env) throws -> MalVal { var ast: MalVal = orig_ast while is_macro(ast, env) { switch try! env.get(try! _nth(ast, 0)) { @@ -74,7 +74,7 @@ func macroexpand(orig_ast: MalVal, _ env: Env) throws -> MalVal { return ast } -func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { +func eval_ast(_ ast: MalVal, _ env: Env) throws -> MalVal { switch ast { case MalVal.MalSymbol: return try env.get(ast) @@ -91,7 +91,7 @@ func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { } } -func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { +func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { var ast = orig_ast, env = orig_env while true { switch ast { @@ -121,9 +121,9 @@ func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { } var idx = binds.startIndex while idx < binds.endIndex { - let v = try EVAL(binds[idx.successor()], let_env) + let v = try EVAL(binds[binds.index(after: idx)], let_env) try let_env.set(binds[idx], v) - idx = idx.successor().successor() + idx = binds.index(idx, offsetBy: 2) } env = let_env ast = lst[2] // TCO @@ -142,9 +142,9 @@ func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { case MalVal.MalSymbol("macroexpand"): return try macroexpand(lst[1], env) case MalVal.MalSymbol("do"): - let slc = lst[1.. MalVal { } // print -func PRINT(exp: MalVal) -> String { +func PRINT(_ exp: MalVal) -> String { return pr_str(exp, true) } // repl -func rep(str:String) throws -> String { +func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } @@ -207,8 +207,8 @@ try repl_env.set(MalVal.MalSymbol("eval"), let pargs = Process.arguments.map { MalVal.MalString($0) } // TODO: weird way to get empty list, fix this var args = pargs[pargs.startIndex.. 1 { while true { print("user> ", terminator: "") - let line = readLine(stripNewline: true) + let line = readLine(strippingNewline: true) if line == nil { break } if line == "" { continue } diff --git a/swift3/Sources/step9_try/main.swift b/swift3/Sources/step9_try/main.swift index d7b04147e2..0d926c4581 100644 --- a/swift3/Sources/step9_try/main.swift +++ b/swift3/Sources/step9_try/main.swift @@ -1,12 +1,12 @@ import Foundation // read -func READ(str: String) throws -> MalVal { +func READ(_ str: String) throws -> MalVal { return try read_str(str) } // eval -func is_pair(ast: MalVal) -> Bool { +func is_pair(_ ast: MalVal) -> Bool { switch ast { case MalVal.MalList(let lst, _): return lst.count > 0 case MalVal.MalVector(let lst, _): return lst.count > 0 @@ -14,7 +14,7 @@ func is_pair(ast: MalVal) -> Bool { } } -func quasiquote(ast: MalVal) -> MalVal { +func quasiquote(_ ast: MalVal) -> MalVal { if !is_pair(ast) { return list([MalVal.MalSymbol("quote"), ast]) } @@ -40,7 +40,7 @@ func quasiquote(ast: MalVal) -> MalVal { quasiquote(try! rest(ast))]) } -func is_macro(ast: MalVal, _ env: Env) -> Bool { +func is_macro(_ ast: MalVal, _ env: Env) -> Bool { switch ast { case MalVal.MalList(let lst, _) where lst.count > 0: let a0 = lst[lst.startIndex] @@ -62,7 +62,7 @@ func is_macro(ast: MalVal, _ env: Env) -> Bool { } } -func macroexpand(orig_ast: MalVal, _ env: Env) throws -> MalVal { +func macroexpand(_ orig_ast: MalVal, _ env: Env) throws -> MalVal { var ast: MalVal = orig_ast while is_macro(ast, env) { switch try! env.get(try! _nth(ast, 0)) { @@ -74,7 +74,7 @@ func macroexpand(orig_ast: MalVal, _ env: Env) throws -> MalVal { return ast } -func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { +func eval_ast(_ ast: MalVal, _ env: Env) throws -> MalVal { switch ast { case MalVal.MalSymbol: return try env.get(ast) @@ -91,7 +91,7 @@ func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { } } -func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { +func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { var ast = orig_ast, env = orig_env while true { switch ast { @@ -121,9 +121,9 @@ func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { } var idx = binds.startIndex while idx < binds.endIndex { - let v = try EVAL(binds[idx.successor()], let_env) + let v = try EVAL(binds[binds.index(after: idx)], let_env) try let_env.set(binds[idx], v) - idx = idx.successor().successor() + idx = binds.index(idx, offsetBy: 2) } env = let_env ast = lst[2] // TCO @@ -175,9 +175,9 @@ func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { throw exc } case MalVal.MalSymbol("do"): - let slc = lst[1.. MalVal { } // print -func PRINT(exp: MalVal) -> String { +func PRINT(_ exp: MalVal) -> String { return pr_str(exp, true) } // repl -func rep(str:String) throws -> String { +func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } @@ -240,8 +240,8 @@ try repl_env.set(MalVal.MalSymbol("eval"), let pargs = Process.arguments.map { MalVal.MalString($0) } // TODO: weird way to get empty list, fix this var args = pargs[pargs.startIndex.. 1 { while true { print("user> ", terminator: "") - let line = readLine(stripNewline: true) + let line = readLine(strippingNewline: true) if line == nil { break } if line == "" { continue } diff --git a/swift3/Sources/stepA_mal/main.swift b/swift3/Sources/stepA_mal/main.swift index 47d6751453..5ebcc78692 100644 --- a/swift3/Sources/stepA_mal/main.swift +++ b/swift3/Sources/stepA_mal/main.swift @@ -1,12 +1,12 @@ import Foundation // read -func READ(str: String) throws -> MalVal { +func READ(_ str: String) throws -> MalVal { return try read_str(str) } // eval -func is_pair(ast: MalVal) -> Bool { +func is_pair(_ ast: MalVal) -> Bool { switch ast { case MalVal.MalList(let lst, _): return lst.count > 0 case MalVal.MalVector(let lst, _): return lst.count > 0 @@ -14,7 +14,7 @@ func is_pair(ast: MalVal) -> Bool { } } -func quasiquote(ast: MalVal) -> MalVal { +func quasiquote(_ ast: MalVal) -> MalVal { if !is_pair(ast) { return list([MalVal.MalSymbol("quote"), ast]) } @@ -40,7 +40,7 @@ func quasiquote(ast: MalVal) -> MalVal { quasiquote(try! rest(ast))]) } -func is_macro(ast: MalVal, _ env: Env) -> Bool { +func is_macro(_ ast: MalVal, _ env: Env) -> Bool { switch ast { case MalVal.MalList(let lst, _) where lst.count > 0: let a0 = lst[lst.startIndex] @@ -62,7 +62,7 @@ func is_macro(ast: MalVal, _ env: Env) -> Bool { } } -func macroexpand(orig_ast: MalVal, _ env: Env) throws -> MalVal { +func macroexpand(_ orig_ast: MalVal, _ env: Env) throws -> MalVal { var ast: MalVal = orig_ast while is_macro(ast, env) { switch try! env.get(try! _nth(ast, 0)) { @@ -74,7 +74,7 @@ func macroexpand(orig_ast: MalVal, _ env: Env) throws -> MalVal { return ast } -func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { +func eval_ast(_ ast: MalVal, _ env: Env) throws -> MalVal { switch ast { case MalVal.MalSymbol: return try env.get(ast) @@ -91,7 +91,7 @@ func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal { } } -func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { +func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { var ast = orig_ast, env = orig_env while true { switch ast { @@ -121,9 +121,9 @@ func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { } var idx = binds.startIndex while idx < binds.endIndex { - let v = try EVAL(binds[idx.successor()], let_env) + let v = try EVAL(binds[binds.index(after: idx)], let_env) try let_env.set(binds[idx], v) - idx = idx.successor().successor() + idx = binds.index(idx, offsetBy: 2) } env = let_env ast = lst[2] // TCO @@ -175,9 +175,9 @@ func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { throw exc } case MalVal.MalSymbol("do"): - let slc = lst[1.. MalVal { } // print -func PRINT(exp: MalVal) -> String { +func PRINT(_ exp: MalVal) -> String { return pr_str(exp, true) } // repl -func rep(str:String) throws -> String { +func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } @@ -240,8 +240,8 @@ try repl_env.set(MalVal.MalSymbol("eval"), let pargs = Process.arguments.map { MalVal.MalString($0) } // TODO: weird way to get empty list, fix this var args = pargs[pargs.startIndex.. 1 { while true { print("user> ", terminator: "") - let line = readLine(stripNewline: true) + let line = readLine(strippingNewline: true) if line == nil { break } if line == "" { continue } diff --git a/swift3/Sources/types.swift b/swift3/Sources/types.swift index 33c7be9f9e..6960f5b1a1 100644 --- a/swift3/Sources/types.swift +++ b/swift3/Sources/types.swift @@ -1,5 +1,5 @@ -enum MalError: ErrorType { +enum MalError: ErrorProtocol { case Reader(msg: String) case General(msg: String) case MalException(obj: MalVal) @@ -38,23 +38,23 @@ typealias MV = MalVal // General functions -func wraptf(a: Bool) -> MalVal { +func wraptf(_ a: Bool) -> MalVal { return a ? MV.MalTrue : MV.MalFalse } // equality functions -func cmp_seqs(a: Array, _ b: Array) -> Bool { +func cmp_seqs(_ a: Array, _ b: Array) -> Bool { if a.count != b.count { return false } var idx = a.startIndex while idx < a.endIndex { if !equal_Q(a[idx], b[idx]) { return false } - idx = idx.successor() + idx = a.index(after:idx) } return true } -func cmp_maps(a: Dictionary, +func cmp_maps(_ a: Dictionary, _ b: Dictionary) -> Bool { if a.count != b.count { return false } for (k,v1) in a { @@ -64,7 +64,7 @@ func cmp_maps(a: Dictionary, return true } -func equal_Q(a: MalVal, _ b: MalVal) -> Bool { +func equal_Q(_ a: MalVal, _ b: MalVal) -> Bool { switch (a, b) { case (MV.MalNil, MV.MalNil): return true case (MV.MalFalse, MV.MalFalse): return true @@ -88,24 +88,24 @@ func equal_Q(a: MalVal, _ b: MalVal) -> Bool { } // list and vector functions -func list(lst: Array) -> MalVal { +func list(_ lst: Array) -> MalVal { return MV.MalList(lst, meta:nil) } -func list(lst: Array, meta: MalVal) -> MalVal { +func list(_ lst: Array, meta: MalVal) -> MalVal { return MV.MalList(lst, meta:[meta]) } -func vector(lst: Array) -> MalVal { +func vector(_ lst: Array) -> MalVal { return MV.MalVector(lst, meta:nil) } -func vector(lst: Array, meta: MalVal) -> MalVal { +func vector(_ lst: Array, meta: MalVal) -> MalVal { return MV.MalVector(lst, meta:[meta]) } // hash-map functions -func _assoc(src: Dictionary, _ mvs: Array) +func _assoc(_ src: Dictionary, _ mvs: Array) throws -> Dictionary { var d = src if mvs.count % 2 != 0 { @@ -124,12 +124,12 @@ func _assoc(src: Dictionary, _ mvs: Array) return d } -func _dissoc(src: Dictionary, _ mvs: Array) +func _dissoc(_ src: Dictionary, _ mvs: Array) throws -> Dictionary { var d = src for mv in mvs { switch mv { - case MV.MalString(let k): d.removeValueForKey(k) + case MV.MalString(let k): d.removeValue(forKey: k) default: throw MalError.General(msg: "Invalid _dissoc call") } } @@ -137,33 +137,33 @@ func _dissoc(src: Dictionary, _ mvs: Array) } -func hash_map(dict: Dictionary) -> MalVal { +func hash_map(_ dict: Dictionary) -> MalVal { return MV.MalHashMap(dict, meta:nil) } -func hash_map(dict: Dictionary, meta:MalVal) -> MalVal { +func hash_map(_ dict: Dictionary, meta:MalVal) -> MalVal { return MV.MalHashMap(dict, meta:[meta]) } -func hash_map(arr: Array) throws -> MalVal { +func hash_map(_ arr: Array) throws -> MalVal { let d = Dictionary(); return MV.MalHashMap(try _assoc(d, arr), meta:nil) } // function functions -func malfunc(fn: (Array) throws -> MalVal) -> MalVal { +func malfunc(_ fn: (Array) throws -> MalVal) -> MalVal { return MV.MalFunc(fn, ast: nil, env: nil, params: nil, macro: false, meta: nil) } -func malfunc(fn: (Array) throws -> MalVal, +func malfunc(_ fn: (Array) throws -> MalVal, ast: Array?, env: Env?, params: Array?) -> MalVal { return MV.MalFunc(fn, ast: ast, env: env, params: params, macro: false, meta: nil) } -func malfunc(fn: (Array) throws -> MalVal, +func malfunc(_ fn: (Array) throws -> MalVal, ast: Array?, env: Env?, params: Array?, @@ -172,7 +172,7 @@ func malfunc(fn: (Array) throws -> MalVal, return MV.MalFunc(fn, ast: ast, env: env, params: params, macro: macro, meta: meta != nil ? [meta!] : nil) } -func malfunc(fn: (Array) throws -> MalVal, +func malfunc(_ fn: (Array) throws -> MalVal, ast: Array?, env: Env?, params: Array?, @@ -184,27 +184,29 @@ func malfunc(fn: (Array) throws -> MalVal, // sequence functions -func _rest(a: MalVal) throws -> Array { +func _rest(_ a: MalVal) throws -> Array { switch a { case MV.MalList(let lst,_): - let slc = lst[lst.startIndex.successor().. MalVal { +func rest(_ a: MalVal) throws -> MalVal { return list(try _rest(a)) } -func _nth(a: MalVal, _ idx: Int) throws -> MalVal { +func _nth(_ a: MalVal, _ idx: Int) throws -> MalVal { switch a { - case MV.MalList(let l,_): return l[l.startIndex.advancedBy(idx)] - case MV.MalVector(let l,_): return l[l.startIndex.advancedBy(idx)] + case MV.MalList(let l,_): return l[l.startIndex.advanced(by: idx)] + case MV.MalVector(let l,_): return l[l.startIndex.advanced(by: idx)] default: throw MalError.General(msg: "Invalid nth call") } } From aa0ac94f0b2a3eae42d94f40abd58b7ddabe3021 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 30 Jul 2016 00:50:12 +0200 Subject: [PATCH 0017/2308] Implement step 6 --- chuck/core.ck | 13 +- chuck/notes.md | 8 + chuck/reader.ck | 4 +- chuck/run_chuck.rb | 2 +- chuck/step2_eval.ck | 1 + chuck/step3_env.ck | 2 +- chuck/step4_if_fn_do.ck | 2 +- chuck/step5_tco.ck | 2 +- chuck/step6_file.ck | 319 +++++++++++++++++++++++++++++++++ chuck/types/MalObject.ck | 17 ++ chuck/types/MalSubr.ck | 11 ++ chuck/types/boxed/String.ck | 29 +++ chuck/types/subr/MalAtomify.ck | 8 + chuck/types/subr/MalDeref.ck | 7 + chuck/types/subr/MalDoReset.ck | 12 ++ chuck/types/subr/MalDoSwap.ck | 15 ++ chuck/types/subr/MalIsAtom.ck | 14 ++ chuck/types/subr/MalReadStr.ck | 8 + chuck/types/subr/MalSlurp.ck | 20 +++ 19 files changed, 487 insertions(+), 7 deletions(-) create mode 100644 chuck/step6_file.ck create mode 100644 chuck/types/subr/MalAtomify.ck create mode 100644 chuck/types/subr/MalDeref.ck create mode 100644 chuck/types/subr/MalDoReset.ck create mode 100644 chuck/types/subr/MalDoSwap.ck create mode 100644 chuck/types/subr/MalIsAtom.ck create mode 100644 chuck/types/subr/MalReadStr.ck create mode 100644 chuck/types/subr/MalSlurp.ck diff --git a/chuck/core.ck b/chuck/core.ck index 8b78207704..564a99d108 100644 --- a/chuck/core.ck +++ b/chuck/core.ck @@ -7,7 +7,9 @@ public class Core ["+", "-", "*", "/", "list", "list?", "empty?", "count", "=", "<", "<=", ">", ">=", - "pr-str", "str", "prn", "println"] @=> Core.names; + "pr-str", "str", "prn", "println", + "read-string", "slurp", + "atom", "atom?", "deref", "reset!", "swap!"] @=> Core.names; MalSubr ns[0] @=> Core.ns; new MalAdd @=> Core.ns["+"]; @@ -30,3 +32,12 @@ new MalPrStr @=> Core.ns["pr-str"]; new MalStr @=> Core.ns["str"]; new MalPrn @=> Core.ns["prn"]; new MalPrintln @=> Core.ns["println"]; + +new MalReadStr @=> Core.ns["read-string"]; +new MalSlurp @=> Core.ns["slurp"]; + +new MalAtomify @=> Core.ns["atom"]; +new MalIsAtom @=> Core.ns["atom?"]; +new MalDeref @=> Core.ns["deref"]; +new MalDoReset @=> Core.ns["reset!"]; +new MalDoSwap @=> Core.ns["swap!"]; diff --git a/chuck/notes.md b/chuck/notes.md index 4cf9deeca4..9a6c867580 100644 --- a/chuck/notes.md +++ b/chuck/notes.md @@ -64,3 +64,11 @@ is actually its third argument... - Where did the check for `do` not being broken by TCO go? - What's the deal with the `quux/tests/step5_tco.qx` file? + +# Step 6 + +- "The closure calls the your EVAL function […]." +- I still don't have any closures. How the heck do I implement + `eval`? What about `swap!`? +- It would be useful to mention that `swap!` sort of requires + implementing `apply` first... diff --git a/chuck/reader.ck b/chuck/reader.ck index 6c3fd9e492..9e4aede4b4 100644 --- a/chuck/reader.ck +++ b/chuck/reader.ck @@ -15,8 +15,8 @@ public class Reader fun static string[] tokenizer(string input) { - "^[ ,]*(~@|[][{}()'`~^@]|\"(\\\\.|[^\\\"])*\"|;.*|[^][ {}()'`~@,;\"]*)" => string tokenRe; - "^([ ,]*|;.*)$" => string blankRe; + "^[ \n,]*(~@|[][{}()'`~^@]|\"(\\\\.|[^\\\"])*\"|;[^\n]*|[^][ {}()'`~@,;\"]*)" => string tokenRe; + "^([ \n,]*|;[^\n]*)$" => string blankRe; string tokens[0]; diff --git a/chuck/run_chuck.rb b/chuck/run_chuck.rb index 7e431e54e9..44e5b4b936 100755 --- a/chuck/run_chuck.rb +++ b/chuck/run_chuck.rb @@ -10,5 +10,5 @@ cmdline += import_files cmdline << scriptfile -ENV['CHUCK_ARGS'] = ARGV.join(' ') +ENV['CHUCK_ARGS'] = ARGV.join("\a") exec(*cmdline) diff --git a/chuck/step2_eval.ck b/chuck/step2_eval.ck index 15f3090eee..65d45c6ec3 100644 --- a/chuck/step2_eval.ck +++ b/chuck/step2_eval.ck @@ -4,6 +4,7 @@ // @import util/*.ck // @import reader.ck // @import printer.ck +// @import env.ck // @import types/MalSubr.ck // @import types/subr/*.ck diff --git a/chuck/step3_env.ck b/chuck/step3_env.ck index 400d26e501..40ec01299e 100644 --- a/chuck/step3_env.ck +++ b/chuck/step3_env.ck @@ -4,9 +4,9 @@ // @import util/*.ck // @import reader.ck // @import printer.ck +// @import env.ck // @import types/MalSubr.ck // @import types/subr/*.ck -// @import env.ck fun MalObject READ(string input) { diff --git a/chuck/step4_if_fn_do.ck b/chuck/step4_if_fn_do.ck index 57f1a0fef1..0cbef5de50 100644 --- a/chuck/step4_if_fn_do.ck +++ b/chuck/step4_if_fn_do.ck @@ -4,9 +4,9 @@ // @import util/*.ck // @import reader.ck // @import printer.ck +// @import env.ck // @import types/MalSubr.ck // @import types/subr/*.ck -// @import env.ck // @import core.ck // @import func.ck diff --git a/chuck/step5_tco.ck b/chuck/step5_tco.ck index 4f948d3ac7..da7aefba29 100644 --- a/chuck/step5_tco.ck +++ b/chuck/step5_tco.ck @@ -4,9 +4,9 @@ // @import util/*.ck // @import reader.ck // @import printer.ck +// @import env.ck // @import types/MalSubr.ck // @import types/subr/*.ck -// @import env.ck // @import core.ck // @import func.ck diff --git a/chuck/step6_file.ck b/chuck/step6_file.ck new file mode 100644 index 0000000000..87a75e9d30 --- /dev/null +++ b/chuck/step6_file.ck @@ -0,0 +1,319 @@ +// @import types/boxed/*.ck +// @import types/MalObject.ck +// @import types/mal/*.ck +// @import util/*.ck +// @import reader.ck +// @import printer.ck +// @import env.ck +// @import types/MalSubr.ck +// @import types/subr/*.ck +// @import core.ck +// @import func.ck + +fun MalObject READ(string input) +{ + return Reader.read_str(input); +} + +fun MalObject EVAL(MalObject m, Env env) +{ + while( true ) + { + if( m.type != "list" ) + { + return eval_ast(m, env); + } + + if( (m$MalList).value().size() == 0 ) + { + return m; + } + + (m$MalList).value() @=> MalObject ast[]; + + if( ast[0].type == "symbol" ) + { + (ast[0]$MalSymbol).value() => string a0; + + if( a0 == "def!" ) + { + (ast[1]$MalSymbol).value() => string a1; + + EVAL(ast[2], env) @=> MalObject value; + if( value.type == "error" ) + { + return value; + } + + env.set(a1, value); + return value; + } + else if( a0 == "let*" ) + { + Env.create(env) @=> Env let_env; + Util.sequenceToMalObjectArray(ast[1]) @=> MalObject bindings[]; + + for( 0 => int i; i < bindings.size(); 2 +=> i) + { + (bindings[i]$MalSymbol).value() => string symbol; + EVAL(bindings[i+1], let_env) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + let_env.set(symbol, value); + } + + let_env @=> env; + ast[2] @=> m; + continue; // TCO + } + else if( a0 == "do" ) + { + MalObject.slice(ast, 1, ast.size()) @=> MalObject forms[]; + eval_ast(MalList.create(forms), env) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + // HACK: this assumes do gets at least one argument... + ast[ast.size()-1] @=> m; + continue; // TCO + } + else if( a0 == "if" ) + { + EVAL(ast[1], env) @=> MalObject condition; + + if( condition.type == "error" ) + { + return condition; + } + + if( !(condition.type == "nil") && !(condition.type == "false") ) + { + ast[2] @=> m; + continue; // TCO + } + else + { + if( ast.size() < 4 ) + { + return Constants.NIL; + } + else + { + ast[3] @=> m; + continue; // TCO + } + } + } + else if( a0 == "fn*" ) + { + (ast[1]$MalList).value() @=> MalObject arg_values[]; + string args[arg_values.size()]; + + for( 0 => int i; i < arg_values.size(); i++ ) + { + (arg_values[i]$MalSymbol).value() => args[i]; + } + + ast[2] @=> MalObject _ast; + + return Func.create(env, args, _ast); + } + } + + eval_ast(m, env) @=> MalObject result; + if( result.type == "error" ) + { + return result; + } + + (result$MalList).value() @=> MalObject values[]; + values[0].type => string type; + MalObject.slice(values, 1) @=> MalObject args[]; + + if( type == "subr" ) + { + values[0]$MalSubr @=> MalSubr subr; + subr.name => string name; + + if( name == "eval" ) + { + return EVAL(args[0], subr.env); + } + else if( name == "swap!") + { + args[0]$MalAtom @=> MalAtom atom; + atom.value() @=> MalObject value; + args[1] @=> MalObject f; + MalObject.slice(args, 2) @=> MalObject _args[]; + MalObject.append([f, value], _args) @=> _args; + EVAL(MalList.create(_args), env) @=> MalObject _value; + // NOTE: the DoSwap subr only puts a value into an atom + return subr.call([atom, _value]); + } + else + { + return subr.call(args); + } + } + else // type == "func" + { + values[0]$Func @=> Func func; + Env.create(func.env, func.args, args) @=> Env eval_env; + eval_env @=> env; + func.ast @=> m; + continue; // TCO + } + } +} + +fun MalObject eval_ast(MalObject m, Env env) +{ + m.type => string type; + + if( type == "symbol" ) + { + (m$MalSymbol).value() => string symbol; + return env.get(symbol); + } + else if( type == "list" || type == "vector" || type == "hashmap" ) + { + (m$MalList).value() @=> MalObject values[]; + MalObject results[values.size()]; + + if( type != "hashmap" ) + { + for( 0 => int i; i < values.size(); i++ ) + { + EVAL(values[i], env) @=> MalObject result; + + if( result.type == "error" ) + { + return result; + } + + result @=> results[i]; + } + } + else + { + for( 0 => int i; i < values.size(); i++ ) + { + if( i % 2 == 0 ) + { + values[i] @=> results[i]; + } + else + { + EVAL(values[i], env) @=> results[i]; + } + } + } + + if( type == "list" ) + { + return MalList.create(results); + } + else if( type == "vector" ) + { + return MalVector.create(results); + } + else if( type == "hashmap" ) + { + return MalHashMap.create(results); + } + } + else + { + return m; + } +} + +fun string PRINT(MalObject m) +{ + return Printer.pr_str(m, true); +} + +Env.create(null) @=> Env repl_env; +for( 0 => int i; i < Core.names.size(); i++ ) +{ + Core.names[i] => string name; + repl_env.set(name, Core.ns[name]); +} + +repl_env.set("eval", MalSubr.create("eval", repl_env)); + +fun MalObject[] MalArgv(string args[]) +{ + MalObject values[args.size()-1]; + + for( 1 => int i; i < args.size(); i++ ) + { + MalString.create(args[i]) @=> values[i-1]; + } + + return values; +} + +// NOTE: normally I'd use \0, but strings are null-terminated... +String.split(Std.getenv("CHUCK_ARGS"), "\a") @=> string args[]; +repl_env.set("*ARGV*", MalList.create(MalArgv(args))); + +fun string rep(string input) +{ + READ(input) @=> MalObject m; + + if( m.type == "error" ) + { + return Status.toMessage(m$MalError); + } + + EVAL(m, repl_env) @=> MalObject result; + if( result.type == "error" ) + { + return Status.toMessage(result$MalError); + } + + return PRINT(result); +} + +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); + +fun void main() +{ + ConsoleInput stdin; + string input; + + while( true ) + { + stdin.prompt("user>") => now; + stdin.getLine() => input; + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } + } +} + +if( args.size() > 1 ) +{ + args[1] => string filename; + rep("(load-file \"" + filename + "\")"); +} +else +{ + main(); +} diff --git a/chuck/types/MalObject.ck b/chuck/types/MalObject.ck index 439d281ced..314eaf6732 100644 --- a/chuck/types/MalObject.ck +++ b/chuck/types/MalObject.ck @@ -54,4 +54,21 @@ public class MalObject return values; } + + fun static MalObject[] append(MalObject as[], MalObject bs[]) + { + MalObject output[as.size()+bs.size()]; + + for( 0 => int i; i < as.size(); i++ ) + { + as[i] @=> output[i]; + } + + for( 0 => int i; i < bs.size(); i++ ) + { + bs[i] @=> output[as.size()+i]; + } + + return output; + } } diff --git a/chuck/types/MalSubr.ck b/chuck/types/MalSubr.ck index ad833130f9..874a122f17 100644 --- a/chuck/types/MalSubr.ck +++ b/chuck/types/MalSubr.ck @@ -1,9 +1,20 @@ public class MalSubr extends MalObject { "subr" => type; + string name; + Env env; fun MalObject call(MalObject args[]) { return new MalObject; } + + // HACK: necessary for providing eval with repl_env + fun static MalSubr create(string name, Env env) + { + MalSubr subr; + name => subr.name; + env @=> subr.env; + return subr; + } } diff --git a/chuck/types/boxed/String.ck b/chuck/types/boxed/String.ck index 61c54cc88c..c0ec6f71e2 100644 --- a/chuck/types/boxed/String.ck +++ b/chuck/types/boxed/String.ck @@ -53,6 +53,35 @@ public class String return output; } + fun static string[] split(string input, string separator) + { + string output[0]; + + if( input == "" ) + { + return output; + } + + 0 => int offset; + int index; + + while( true ) + { + input.find(separator, offset) => index; + + if( index == -1 ) + { + output << input.substring(offset); + break; + } + + output << input.substring(offset, index - offset); + index + separator.length() => offset; + } + + return output; + } + fun static string replaceAll(string input, string pat, string rep) { 0 => int offset; diff --git a/chuck/types/subr/MalAtomify.ck b/chuck/types/subr/MalAtomify.ck new file mode 100644 index 0000000000..3ec8b21733 --- /dev/null +++ b/chuck/types/subr/MalAtomify.ck @@ -0,0 +1,8 @@ +public class MalAtomify extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject m; + return MalAtom.create(m); + } +} diff --git a/chuck/types/subr/MalDeref.ck b/chuck/types/subr/MalDeref.ck new file mode 100644 index 0000000000..325c62c824 --- /dev/null +++ b/chuck/types/subr/MalDeref.ck @@ -0,0 +1,7 @@ +public class MalDeref extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + return (args[0]$MalAtom).value(); + } +} diff --git a/chuck/types/subr/MalDoReset.ck b/chuck/types/subr/MalDoReset.ck new file mode 100644 index 0000000000..74838e3b4a --- /dev/null +++ b/chuck/types/subr/MalDoReset.ck @@ -0,0 +1,12 @@ +public class MalDoReset extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0]$MalAtom @=> MalAtom atom; + args[1]$MalObject @=> MalObject value; + + value @=> atom.object; + + return value; + } +} diff --git a/chuck/types/subr/MalDoSwap.ck b/chuck/types/subr/MalDoSwap.ck new file mode 100644 index 0000000000..e57826c8b8 --- /dev/null +++ b/chuck/types/subr/MalDoSwap.ck @@ -0,0 +1,15 @@ +public class MalDoSwap extends MalSubr +{ + // HACK: necessary for apply step + "swap!" => name; + + fun MalObject call(MalObject args[]) + { + args[0]$MalAtom @=> MalAtom atom; + args[1]$MalObject @=> MalObject value; + + value @=> atom.object; + + return value; + } +} diff --git a/chuck/types/subr/MalIsAtom.ck b/chuck/types/subr/MalIsAtom.ck new file mode 100644 index 0000000000..3377edb782 --- /dev/null +++ b/chuck/types/subr/MalIsAtom.ck @@ -0,0 +1,14 @@ +public class MalIsAtom extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + if( args[0].type == "atom" ) + { + return Constants.TRUE; + } + else + { + return Constants.FALSE; + } + } +} diff --git a/chuck/types/subr/MalReadStr.ck b/chuck/types/subr/MalReadStr.ck new file mode 100644 index 0000000000..6d3fa71f84 --- /dev/null +++ b/chuck/types/subr/MalReadStr.ck @@ -0,0 +1,8 @@ +public class MalReadStr extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + (args[0]$MalString).value() => string input; + return Reader.read_str(input); + } +} diff --git a/chuck/types/subr/MalSlurp.ck b/chuck/types/subr/MalSlurp.ck new file mode 100644 index 0000000000..228fe2912e --- /dev/null +++ b/chuck/types/subr/MalSlurp.ck @@ -0,0 +1,20 @@ +public class MalSlurp extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + (args[0]$MalString).value() => string filename; + FileIO f; + string output[0]; + + f.open(filename, FileIO.READ); + + while( f.more() ) + { + output << f.readLine(); + } + + f.close(); + + return MalString.create(String.join(output, "\n")); + } +} From 34f7b3dbe2a364df2f3a782b0b1acfbbedcec1a8 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 30 Jul 2016 21:53:24 +0200 Subject: [PATCH 0018/2308] Implement step 7 --- chuck/core.ck | 6 +- chuck/notes.md | 24 +++ chuck/step7_quote.ck | 374 ++++++++++++++++++++++++++++++++++ chuck/types/subr/MalConcat.ck | 15 ++ chuck/types/subr/MalCons.ck | 9 + 5 files changed, 427 insertions(+), 1 deletion(-) create mode 100644 chuck/step7_quote.ck create mode 100644 chuck/types/subr/MalConcat.ck create mode 100644 chuck/types/subr/MalCons.ck diff --git a/chuck/core.ck b/chuck/core.ck index 564a99d108..4c64bcaf91 100644 --- a/chuck/core.ck +++ b/chuck/core.ck @@ -9,7 +9,8 @@ public class Core "=", "<", "<=", ">", ">=", "pr-str", "str", "prn", "println", "read-string", "slurp", - "atom", "atom?", "deref", "reset!", "swap!"] @=> Core.names; + "atom", "atom?", "deref", "reset!", "swap!", + "cons", "concat"] @=> Core.names; MalSubr ns[0] @=> Core.ns; new MalAdd @=> Core.ns["+"]; @@ -41,3 +42,6 @@ new MalIsAtom @=> Core.ns["atom?"]; new MalDeref @=> Core.ns["deref"]; new MalDoReset @=> Core.ns["reset!"]; new MalDoSwap @=> Core.ns["swap!"]; + +new MalCons @=> Core.ns["cons"]; +new MalConcat @=> Core.ns["concat"]; diff --git a/chuck/notes.md b/chuck/notes.md index 9a6c867580..9fb4715126 100644 --- a/chuck/notes.md +++ b/chuck/notes.md @@ -72,3 +72,27 @@ `eval`? What about `swap!`? - It would be useful to mention that `swap!` sort of requires implementing `apply` first... + +# Step 7 + +- Why the scare quotes for splicing? +- "Before implementing the quoting forms, you will need to implement + some supporting functions in the core namespace:" should be one list + item +- "this function takes a list as its second parameter and returns a + new list that has the first argument prepended to it." reads backwards +- The quasiquote paragraph is hard to read +- It's rather confusing to refer to the argument of `ast` and to an + `ast` parameter, perhaps name the latter a form? +- What could also help would be a visualization of the four + conditionals: + - \`42, \`() + - \`~foo + - \`(~@foo) and more + - \`(42 ~@foo) and everything else +- Mal/mal is inconsistently capitalized +- "Expand the conditional with reader `read_form` function to add the + following four cases" is again weird, better refer to the + `read_form` function in reader.qx +- "concat should support concatenation of lists, vectors, or a mix or + both." <- "or a mix or both" is redundant diff --git a/chuck/step7_quote.ck b/chuck/step7_quote.ck new file mode 100644 index 0000000000..c11c618820 --- /dev/null +++ b/chuck/step7_quote.ck @@ -0,0 +1,374 @@ +// @import types/boxed/*.ck +// @import types/MalObject.ck +// @import types/mal/*.ck +// @import util/*.ck +// @import reader.ck +// @import printer.ck +// @import env.ck +// @import types/MalSubr.ck +// @import types/subr/*.ck +// @import core.ck +// @import func.ck + +fun MalObject READ(string input) +{ + return Reader.read_str(input); +} + +fun int isPair(MalObject m) +{ + if( (m.type == "list" || m.type == "vector") && + Util.sequenceToMalObjectArray(m).size() > 0 ) + { + return true; + } + else + { + return false; + } +} + +fun MalObject quasiquote(MalObject ast) +{ + if( !isPair(ast) ) + { + return MalList.create([MalSymbol.create("quote"), ast]); + } + + Util.sequenceToMalObjectArray(ast) @=> MalObject a[]; + a[0] @=> MalObject a0; + + if( a0.type == "symbol" && (a0$MalSymbol).value() == "unquote" ) + { + return a[1]; + } + + if( isPair(a0) ) + { + Util.sequenceToMalObjectArray(a0) @=> MalObject a0_[]; + a0_[0] @=> MalObject a0_0; + + if( a0_0.type == "symbol" && (a0_0$MalSymbol).value() == "splice-unquote" ) + { + return MalList.create( + [MalSymbol.create("concat"), a0_[1], + quasiquote(MalList.create(MalObject.slice(a, 1)))]); + } + } + + return MalList.create( + [MalSymbol.create("cons"), quasiquote(a[0]), + quasiquote(MalList.create(MalObject.slice(a, 1)))]); +} + +fun MalObject EVAL(MalObject m, Env env) +{ + while( true ) + { + if( m.type != "list" ) + { + return eval_ast(m, env); + } + + if( (m$MalList).value().size() == 0 ) + { + return m; + } + + (m$MalList).value() @=> MalObject ast[]; + + if( ast[0].type == "symbol" ) + { + (ast[0]$MalSymbol).value() => string a0; + + if( a0 == "def!" ) + { + (ast[1]$MalSymbol).value() => string a1; + + EVAL(ast[2], env) @=> MalObject value; + if( value.type == "error" ) + { + return value; + } + + env.set(a1, value); + return value; + } + else if( a0 == "let*" ) + { + Env.create(env) @=> Env let_env; + Util.sequenceToMalObjectArray(ast[1]) @=> MalObject bindings[]; + + for( 0 => int i; i < bindings.size(); 2 +=> i) + { + (bindings[i]$MalSymbol).value() => string symbol; + EVAL(bindings[i+1], let_env) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + let_env.set(symbol, value); + } + + let_env @=> env; + ast[2] @=> m; + continue; // TCO + } + else if( a0 == "quote" ) + { + return ast[1]; + } + else if( a0 == "quasiquote" ) + { + quasiquote(ast[1]) @=> m; + continue; // TCO + } + else if( a0 == "do" ) + { + MalObject.slice(ast, 1, ast.size()) @=> MalObject forms[]; + eval_ast(MalList.create(forms), env) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + // HACK: this assumes do gets at least one argument... + ast[ast.size()-1] @=> m; + continue; // TCO + } + else if( a0 == "if" ) + { + EVAL(ast[1], env) @=> MalObject condition; + + if( condition.type == "error" ) + { + return condition; + } + + if( !(condition.type == "nil") && !(condition.type == "false") ) + { + ast[2] @=> m; + continue; // TCO + } + else + { + if( ast.size() < 4 ) + { + return Constants.NIL; + } + else + { + ast[3] @=> m; + continue; // TCO + } + } + } + else if( a0 == "fn*" ) + { + (ast[1]$MalList).value() @=> MalObject arg_values[]; + string args[arg_values.size()]; + + for( 0 => int i; i < arg_values.size(); i++ ) + { + (arg_values[i]$MalSymbol).value() => args[i]; + } + + ast[2] @=> MalObject _ast; + + return Func.create(env, args, _ast); + } + } + + eval_ast(m, env) @=> MalObject result; + if( result.type == "error" ) + { + return result; + } + + (result$MalList).value() @=> MalObject values[]; + values[0].type => string type; + MalObject.slice(values, 1) @=> MalObject args[]; + + if( type == "subr" ) + { + values[0]$MalSubr @=> MalSubr subr; + subr.name => string name; + + if( name == "eval" ) + { + return EVAL(args[0], subr.env); + } + else if( name == "swap!") + { + args[0]$MalAtom @=> MalAtom atom; + atom.value() @=> MalObject value; + args[1] @=> MalObject f; + MalObject.slice(args, 2) @=> MalObject _args[]; + MalObject.append([f, value], _args) @=> _args; + EVAL(MalList.create(_args), env) @=> MalObject _value; + // NOTE: the DoSwap subr only puts a value into an atom + return subr.call([atom, _value]); + } + else + { + return subr.call(args); + } + } + else // type == "func" + { + values[0]$Func @=> Func func; + Env.create(func.env, func.args, args) @=> Env eval_env; + eval_env @=> env; + func.ast @=> m; + continue; // TCO + } + } +} + +fun MalObject eval_ast(MalObject m, Env env) +{ + m.type => string type; + + if( type == "symbol" ) + { + (m$MalSymbol).value() => string symbol; + return env.get(symbol); + } + else if( type == "list" || type == "vector" || type == "hashmap" ) + { + (m$MalList).value() @=> MalObject values[]; + MalObject results[values.size()]; + + if( type != "hashmap" ) + { + for( 0 => int i; i < values.size(); i++ ) + { + EVAL(values[i], env) @=> MalObject result; + + if( result.type == "error" ) + { + return result; + } + + result @=> results[i]; + } + } + else + { + for( 0 => int i; i < values.size(); i++ ) + { + if( i % 2 == 0 ) + { + values[i] @=> results[i]; + } + else + { + EVAL(values[i], env) @=> results[i]; + } + } + } + + if( type == "list" ) + { + return MalList.create(results); + } + else if( type == "vector" ) + { + return MalVector.create(results); + } + else if( type == "hashmap" ) + { + return MalHashMap.create(results); + } + } + else + { + return m; + } +} + +fun string PRINT(MalObject m) +{ + return Printer.pr_str(m, true); +} + +Env.create(null) @=> Env repl_env; +for( 0 => int i; i < Core.names.size(); i++ ) +{ + Core.names[i] => string name; + repl_env.set(name, Core.ns[name]); +} + +repl_env.set("eval", MalSubr.create("eval", repl_env)); + +fun MalObject[] MalArgv(string args[]) +{ + MalObject values[args.size()-1]; + + for( 1 => int i; i < args.size(); i++ ) + { + MalString.create(args[i]) @=> values[i-1]; + } + + return values; +} + +// NOTE: normally I'd use \0, but strings are null-terminated... +String.split(Std.getenv("CHUCK_ARGS"), "\a") @=> string args[]; +repl_env.set("*ARGV*", MalList.create(MalArgv(args))); + +fun string rep(string input) +{ + READ(input) @=> MalObject m; + + if( m.type == "error" ) + { + return Status.toMessage(m$MalError); + } + + EVAL(m, repl_env) @=> MalObject result; + if( result.type == "error" ) + { + return Status.toMessage(result$MalError); + } + + return PRINT(result); +} + +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); + +fun void main() +{ + ConsoleInput stdin; + string input; + + while( true ) + { + stdin.prompt("user>") => now; + stdin.getLine() => input; + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } + } +} + +if( args.size() > 1 ) +{ + args[1] => string filename; + rep("(load-file \"" + filename + "\")"); +} +else +{ + main(); +} diff --git a/chuck/types/subr/MalConcat.ck b/chuck/types/subr/MalConcat.ck new file mode 100644 index 0000000000..8ea97778ac --- /dev/null +++ b/chuck/types/subr/MalConcat.ck @@ -0,0 +1,15 @@ +public class MalConcat extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + MalObject value[0]; + + for( 0 => int i; i < args.size(); i++ ) + { + Util.sequenceToMalObjectArray(args[i]) @=> MalObject list[]; + MalObject.append(value, list) @=> value; + } + + return MalList.create(value); + } +} diff --git a/chuck/types/subr/MalCons.ck b/chuck/types/subr/MalCons.ck new file mode 100644 index 0000000000..500c37c7b7 --- /dev/null +++ b/chuck/types/subr/MalCons.ck @@ -0,0 +1,9 @@ +public class MalCons extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject arg; + Util.sequenceToMalObjectArray(args[1]) @=> MalObject list[]; + return MalList.create(MalObject.append([arg], list)); + } +} From a3a6f6809815efe921b571c11f34dfd7d088d811 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sun, 31 Jul 2016 23:25:23 -0500 Subject: [PATCH 0019/2308] Swift3: update to Swift 3 Preview 3. --- .travis.yml | 2 +- README.md | 3 +-- swift3/Dockerfile | 11 ++--------- swift3/Sources/env.swift | 1 + swift3/Sources/step4_if_fn_do/main.swift | 1 + swift3/Sources/step5_tco/main.swift | 3 ++- swift3/Sources/step6_file/main.swift | 3 ++- swift3/Sources/step7_quote/main.swift | 7 ++++--- swift3/Sources/step8_macros/main.swift | 9 +++++---- swift3/Sources/step9_try/main.swift | 13 +++++++------ swift3/Sources/stepA_mal/main.swift | 13 +++++++------ 11 files changed, 33 insertions(+), 33 deletions(-) diff --git a/.travis.yml b/.travis.yml index 198a926f31..88525e7efa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,7 +58,7 @@ matrix: - {env: IMPL=scala, services: [docker]} - {env: IMPL=swift NO_DOCKER=1, os: osx, osx_image: xcode7} - {env: IMPL=swift3, services: [docker]} - - {env: IMPL=swift3 NO_DOCKER=1, os: osx, osx_image: xcode7} + - {env: IMPL=swift3 NO_DOCKER=1, os: osx, osx_image: xcode8} - {env: IMPL=tcl, services: [docker]} - {env: IMPL=vb, services: [docker]} - {env: IMPL=vhdl, services: [docker]} diff --git a/README.md b/README.md index 04b6cada38..1e4042342d 100644 --- a/README.md +++ b/README.md @@ -731,8 +731,7 @@ make ### Swift 3 The Swift 3 implementation of mal requires the Swift 3.0 compiler. It -has been tested with the development version of the Swift 3 from -2016-02-08. +has been tested with Swift 3 Preview 3. ``` cd swift3 diff --git a/swift3/Dockerfile b/swift3/Dockerfile index cc70fb788c..039d4adde3 100644 --- a/swift3/Dockerfile +++ b/swift3/Dockerfile @@ -27,21 +27,14 @@ RUN apt-get -y install clang-3.6 cmake pkg-config \ libbsd-dev libedit-dev libxml2-dev libsqlite3-dev \ swig libpython-dev libncurses5-dev -ENV SWIFT_PREFIX swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a +ENV SWIFT_PREFIX swift-3.0-PREVIEW-3 ENV SWIFT_RELEASE ${SWIFT_PREFIX}-ubuntu15.10 RUN cd /opt && \ - curl -O https://swift.org/builds/development/ubuntu1510/${SWIFT_PREFIX}/${SWIFT_RELEASE}.tar.gz && \ + curl -O https://swift.org/builds/swift-3.0-preview-3/ubuntu1510/${SWIFT_PREFIX}/${SWIFT_RELEASE}.tar.gz && \ tar xvzf ${SWIFT_RELEASE}.tar.gz && \ rm ${SWIFT_RELEASE}.tar.gz -# tar xvzf ${SWIFT_RELEASE}.tar.gz --directory / --strip-components 1 && \ -#RUN find /usr -type f | xargs -ifoo chmod go+r foo && \ -# find /usr -type d | xargs -ifoo chmod go+rx foo - -#RUN find /opt/${SWIFT_RELEASE}/ -type f | xargs -ifoo chmod go+r foo && \ -# find /opt/${SWIFT_RELEASE}/ -type d | xargs -ifoo chmod go+rx foo - ENV PATH /opt/${SWIFT_RELEASE}/usr/bin/:$PATH # TODO: better way to do this? And move up. diff --git a/swift3/Sources/env.swift b/swift3/Sources/env.swift index 1014f1d6d9..f080dce800 100644 --- a/swift3/Sources/env.swift +++ b/swift3/Sources/env.swift @@ -76,6 +76,7 @@ class Env { } } + @discardableResult func set(_ key: MalVal, _ val: MalVal) throws -> MalVal { switch key { case MalVal.MalSymbol(let str): diff --git a/swift3/Sources/step4_if_fn_do/main.swift b/swift3/Sources/step4_if_fn_do/main.swift index daab64c879..dabb4f2c1a 100644 --- a/swift3/Sources/step4_if_fn_do/main.swift +++ b/swift3/Sources/step4_if_fn_do/main.swift @@ -99,6 +99,7 @@ func PRINT(_ exp: MalVal) -> String { // repl +@discardableResult func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } diff --git a/swift3/Sources/step5_tco/main.swift b/swift3/Sources/step5_tco/main.swift index f632cd8af6..ae6e12a6bd 100644 --- a/swift3/Sources/step5_tco/main.swift +++ b/swift3/Sources/step5_tco/main.swift @@ -55,7 +55,7 @@ func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { ast = lst[2] // TCO case MalVal.MalSymbol("do"): let slc = lst[1.. String { // repl +@discardableResult func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } diff --git a/swift3/Sources/step6_file/main.swift b/swift3/Sources/step6_file/main.swift index a9618cef87..aea53fdf4e 100644 --- a/swift3/Sources/step6_file/main.swift +++ b/swift3/Sources/step6_file/main.swift @@ -55,7 +55,7 @@ func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { ast = lst[2] // TCO case MalVal.MalSymbol("do"): let slc = lst[1.. String { // repl +@discardableResult func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } diff --git a/swift3/Sources/step7_quote/main.swift b/swift3/Sources/step7_quote/main.swift index 53ba82c157..10205d265e 100644 --- a/swift3/Sources/step7_quote/main.swift +++ b/swift3/Sources/step7_quote/main.swift @@ -22,7 +22,7 @@ func quasiquote(_ ast: MalVal) -> MalVal { switch a0 { case MalVal.MalSymbol("unquote"): return try! _nth(ast, 1) - default: true // fallthrough + default: break } if is_pair(a0) { let a00 = try! _nth(a0, 0) @@ -31,7 +31,7 @@ func quasiquote(_ ast: MalVal) -> MalVal { return list([MalVal.MalSymbol("concat"), try! _nth(a0, 1), quasiquote(try! rest(ast))]) - default: true // fallthrough + default: break } } @@ -93,7 +93,7 @@ func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { ast = quasiquote(lst[1]) // TCO case MalVal.MalSymbol("do"): let slc = lst[1.. String { // repl +@discardableResult func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } diff --git a/swift3/Sources/step8_macros/main.swift b/swift3/Sources/step8_macros/main.swift index b799ba546c..933f028bea 100644 --- a/swift3/Sources/step8_macros/main.swift +++ b/swift3/Sources/step8_macros/main.swift @@ -22,7 +22,7 @@ func quasiquote(_ ast: MalVal) -> MalVal { switch a0 { case MalVal.MalSymbol("unquote"): return try! _nth(ast, 1) - default: true // fallthrough + default: break } if is_pair(a0) { let a00 = try! _nth(a0, 0) @@ -31,7 +31,7 @@ func quasiquote(_ ast: MalVal) -> MalVal { return list([MalVal.MalSymbol("concat"), try! _nth(a0, 1), quasiquote(try! rest(ast))]) - default: true // fallthrough + default: break } } @@ -101,7 +101,7 @@ func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { ast = try macroexpand(ast, env) switch ast { - case MalVal.MalList: true + case MalVal.MalList: break default: return try eval_ast(ast, env) } @@ -143,7 +143,7 @@ func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { return try macroexpand(lst[1], env) case MalVal.MalSymbol("do"): let slc = lst[1.. String { // repl +@discardableResult func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } diff --git a/swift3/Sources/step9_try/main.swift b/swift3/Sources/step9_try/main.swift index 0d926c4581..a02af0e459 100644 --- a/swift3/Sources/step9_try/main.swift +++ b/swift3/Sources/step9_try/main.swift @@ -22,7 +22,7 @@ func quasiquote(_ ast: MalVal) -> MalVal { switch a0 { case MalVal.MalSymbol("unquote"): return try! _nth(ast, 1) - default: true // fallthrough + default: break } if is_pair(a0) { let a00 = try! _nth(a0, 0) @@ -31,7 +31,7 @@ func quasiquote(_ ast: MalVal) -> MalVal { return list([MalVal.MalSymbol("concat"), try! _nth(a0, 1), quasiquote(try! rest(ast))]) - default: true // fallthrough + default: break } } @@ -101,7 +101,7 @@ func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { ast = try macroexpand(ast, env) switch ast { - case MalVal.MalList: true + case MalVal.MalList: break default: return try eval_ast(ast, env) } @@ -167,16 +167,16 @@ func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { } return try EVAL(a22, Env(env, binds: list([a21]), exprs: list([err]))) - default: true // fall through + default: break } - default: true // fall through + default: break } } throw exc } case MalVal.MalSymbol("do"): let slc = lst[1.. String { // repl +@discardableResult func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } diff --git a/swift3/Sources/stepA_mal/main.swift b/swift3/Sources/stepA_mal/main.swift index 5ebcc78692..2e338ff64b 100644 --- a/swift3/Sources/stepA_mal/main.swift +++ b/swift3/Sources/stepA_mal/main.swift @@ -22,7 +22,7 @@ func quasiquote(_ ast: MalVal) -> MalVal { switch a0 { case MalVal.MalSymbol("unquote"): return try! _nth(ast, 1) - default: true // fallthrough + default: break } if is_pair(a0) { let a00 = try! _nth(a0, 0) @@ -31,7 +31,7 @@ func quasiquote(_ ast: MalVal) -> MalVal { return list([MalVal.MalSymbol("concat"), try! _nth(a0, 1), quasiquote(try! rest(ast))]) - default: true // fallthrough + default: break } } @@ -101,7 +101,7 @@ func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { ast = try macroexpand(ast, env) switch ast { - case MalVal.MalList: true + case MalVal.MalList: break default: return try eval_ast(ast, env) } @@ -167,16 +167,16 @@ func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { } return try EVAL(a22, Env(env, binds: list([a21]), exprs: list([err]))) - default: true // fall through + default: break } - default: true // fall through + default: break } } throw exc } case MalVal.MalSymbol("do"): let slc = lst[1.. String { // repl +@discardableResult func rep(_ str:String) throws -> String { return PRINT(try EVAL(try READ(str), repl_env)) } From 65634b37d4922fbd0d14f1254330b43728a8266b Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 1 Aug 2016 10:40:32 +0200 Subject: [PATCH 0020/2308] Implement step 8 --- chuck/core.ck | 7 +- chuck/func.ck | 13 +- chuck/notes.md | 15 ++ chuck/reader.ck | 2 +- chuck/step8_macros.ck | 448 +++++++++++++++++++++++++++++++++++ chuck/types/subr/MalFirst.ck | 23 ++ chuck/types/subr/MalNth.ck | 17 ++ chuck/types/subr/MalRest.ck | 22 ++ chuck/util/Status.ck | 5 +- 9 files changed, 543 insertions(+), 9 deletions(-) create mode 100644 chuck/step8_macros.ck create mode 100644 chuck/types/subr/MalFirst.ck create mode 100644 chuck/types/subr/MalNth.ck create mode 100644 chuck/types/subr/MalRest.ck diff --git a/chuck/core.ck b/chuck/core.ck index 4c64bcaf91..8573c6705c 100644 --- a/chuck/core.ck +++ b/chuck/core.ck @@ -10,7 +10,8 @@ public class Core "pr-str", "str", "prn", "println", "read-string", "slurp", "atom", "atom?", "deref", "reset!", "swap!", - "cons", "concat"] @=> Core.names; + "cons", "concat", + "nth", "first", "rest"] @=> Core.names; MalSubr ns[0] @=> Core.ns; new MalAdd @=> Core.ns["+"]; @@ -45,3 +46,7 @@ new MalDoSwap @=> Core.ns["swap!"]; new MalCons @=> Core.ns["cons"]; new MalConcat @=> Core.ns["concat"]; + +new MalNth @=> Core.ns["nth"]; +new MalFirst @=> Core.ns["first"]; +new MalRest @=> Core.ns["rest"]; diff --git a/chuck/func.ck b/chuck/func.ck index fcb42b3735..014d78c235 100644 --- a/chuck/func.ck +++ b/chuck/func.ck @@ -4,18 +4,19 @@ public class Func extends MalObject Env env; string args[]; MalObject ast; + int isMacro; - fun void init(Env _env, string _args[], MalObject _ast) + fun void init(Env env, string args[], MalObject ast) { - _env @=> env; - _args @=> args; - _ast @=> ast; + env @=> this.env; + args @=> this.args; + ast @=> this.ast; } - fun static Func create(Env _env, string _args[], MalObject _ast) + fun static Func create(Env env, string args[], MalObject ast) { Func func; - func.init(_env, _args, _ast); + func.init(env, args, ast); return func; } } diff --git a/chuck/notes.md b/chuck/notes.md index 9fb4715126..58529a553b 100644 --- a/chuck/notes.md +++ b/chuck/notes.md @@ -96,3 +96,18 @@ `read_form` function in reader.qx - "concat should support concatenation of lists, vectors, or a mix or both." <- "or a mix or both" is redundant + +# Step 8 + +- "In the previous step, quoting enabled some simple manipulation [of] + data structures" +- The macroexpand function step refers to call/apply, it's unclear how + to proceed if you don't have such a thing +- How should the exception for invalid `nth` access look like? Also, + why is it an exception and not an error like with the reader? +- How can `first` take a list (or vector), but work on `nil`? +- The description of `rest` is inconsistent with the tests +- "In the main program, use the rep function to define two new control + structures macros." +- Why does the definition of `cond` use `throw` although it's only + introduced in the next chapter? diff --git a/chuck/reader.ck b/chuck/reader.ck index 9e4aede4b4..3a75d5107a 100644 --- a/chuck/reader.ck +++ b/chuck/reader.ck @@ -15,7 +15,7 @@ public class Reader fun static string[] tokenizer(string input) { - "^[ \n,]*(~@|[][{}()'`~^@]|\"(\\\\.|[^\\\"])*\"|;[^\n]*|[^][ {}()'`~@,;\"]*)" => string tokenRe; + "^[ \n,]*(~@|[][{}()'`~^@]|\"(\\\\.|[^\\\"])*\"|;[^\n]*|[^][ \n{}()'`~@,;\"]*)" => string tokenRe; "^([ \n,]*|;[^\n]*)$" => string blankRe; string tokens[0]; diff --git a/chuck/step8_macros.ck b/chuck/step8_macros.ck new file mode 100644 index 0000000000..f1d273f295 --- /dev/null +++ b/chuck/step8_macros.ck @@ -0,0 +1,448 @@ +// @import types/boxed/*.ck +// @import types/MalObject.ck +// @import types/mal/*.ck +// @import util/*.ck +// @import reader.ck +// @import printer.ck +// @import env.ck +// @import types/MalSubr.ck +// @import types/subr/*.ck +// @import core.ck +// @import func.ck + +fun MalObject READ(string input) +{ + return Reader.read_str(input); +} + +fun int isPair(MalObject m) +{ + if( (m.type == "list" || m.type == "vector") && + Util.sequenceToMalObjectArray(m).size() > 0 ) + { + return true; + } + else + { + return false; + } +} + +fun MalObject quasiquote(MalObject ast) +{ + if( !isPair(ast) ) + { + return MalList.create([MalSymbol.create("quote"), ast]); + } + + Util.sequenceToMalObjectArray(ast) @=> MalObject a[]; + a[0] @=> MalObject a0; + + if( a0.type == "symbol" && (a0$MalSymbol).value() == "unquote" ) + { + return a[1]; + } + + if( isPair(a0) ) + { + Util.sequenceToMalObjectArray(a0) @=> MalObject a0_[]; + a0_[0] @=> MalObject a0_0; + + if( a0_0.type == "symbol" && (a0_0$MalSymbol).value() == "splice-unquote" ) + { + return MalList.create( + [MalSymbol.create("concat"), a0_[1], + quasiquote(MalList.create(MalObject.slice(a, 1)))]); + } + } + + return MalList.create( + [MalSymbol.create("cons"), quasiquote(a[0]), + quasiquote(MalList.create(MalObject.slice(a, 1)))]); +} + +fun int isMacroCall(MalObject ast, Env env) +{ + if( ast.type == "list" ) + { + (ast$MalList).value() @=> MalObject a[]; + + if( a[0].type == "symbol" ) + { + (a[0]$MalSymbol).value() => string name; + env.find(name) @=> MalObject value; + + if( value != null && value.type == "func" && (value$Func).isMacro ) + { + return true; + } + } + } + + return false; +} + +fun MalObject macroexpand(MalObject ast, Env env) +{ + while( isMacroCall(ast, env) ) + { + Util.sequenceToMalObjectArray(ast) @=> MalObject list[]; + (list[0]$MalSymbol).value() => string name; + env.get(name) @=> MalObject macro; + MalObject.slice(list, 1) @=> MalObject args[]; + + if( macro.type == "subr" ) + { + (macro$MalSubr).call(args) @=> ast; + } + else // macro.type == "func" + { + macro$Func @=> Func func; + Env.create(func.env, func.args, args) @=> Env eval_env; + EVAL(func.ast, eval_env) @=> ast;; + } + } + + return ast; +} + +fun MalObject EVAL(MalObject m, Env env) +{ + while( true ) + { + if( m.type != "list" ) + { + return eval_ast(m, env); + } + + if( (m$MalList).value().size() == 0 ) + { + return m; + } + + macroexpand(m, env) @=> m; + + if( m.type != "list" ) + { + return eval_ast(m, env); + } + + (m$MalList).value() @=> MalObject ast[]; + + if( ast[0].type == "symbol" ) + { + (ast[0]$MalSymbol).value() => string a0; + + if( a0 == "def!" ) + { + (ast[1]$MalSymbol).value() => string a1; + + EVAL(ast[2], env) @=> MalObject value; + if( value.type == "error" ) + { + return value; + } + + env.set(a1, value); + return value; + } + else if( a0 == "let*" ) + { + Env.create(env) @=> Env let_env; + Util.sequenceToMalObjectArray(ast[1]) @=> MalObject bindings[]; + + for( 0 => int i; i < bindings.size(); 2 +=> i) + { + (bindings[i]$MalSymbol).value() => string symbol; + EVAL(bindings[i+1], let_env) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + let_env.set(symbol, value); + } + + let_env @=> env; + ast[2] @=> m; + continue; // TCO + } + else if( a0 == "quote" ) + { + return ast[1]; + } + else if( a0 == "quasiquote" ) + { + quasiquote(ast[1]) @=> m; + continue; // TCO + } + else if( a0 == "defmacro!" ) + { + (ast[1]$MalSymbol).value() => string a1; + + EVAL(ast[2], env) @=> MalObject value; + if( value.type == "error" ) + { + return value; + } + + true => (value$Func).isMacro; + + env.set(a1, value); + return value; + } + else if( a0 == "macroexpand" ) + { + return macroexpand(ast[1], env); + } + else if( a0 == "do" ) + { + MalObject.slice(ast, 1, ast.size()) @=> MalObject forms[]; + eval_ast(MalList.create(forms), env) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + // HACK: this assumes do gets at least one argument... + ast[ast.size()-1] @=> m; + continue; // TCO + } + else if( a0 == "if" ) + { + EVAL(ast[1], env) @=> MalObject condition; + + if( condition.type == "error" ) + { + return condition; + } + + if( !(condition.type == "nil") && !(condition.type == "false") ) + { + ast[2] @=> m; + continue; // TCO + } + else + { + if( ast.size() < 4 ) + { + return Constants.NIL; + } + else + { + ast[3] @=> m; + continue; // TCO + } + } + } + else if( a0 == "fn*" ) + { + (ast[1]$MalList).value() @=> MalObject arg_values[]; + string args[arg_values.size()]; + + for( 0 => int i; i < arg_values.size(); i++ ) + { + (arg_values[i]$MalSymbol).value() => args[i]; + } + + ast[2] @=> MalObject _ast; + + return Func.create(env, args, _ast); + } + } + + eval_ast(m, env) @=> MalObject result; + if( result.type == "error" ) + { + return result; + } + + (result$MalList).value() @=> MalObject values[]; + values[0].type => string type; + MalObject.slice(values, 1) @=> MalObject args[]; + + if( type == "subr" ) + { + values[0]$MalSubr @=> MalSubr subr; + subr.name => string name; + + if( name == "eval" ) + { + return EVAL(args[0], subr.env); + } + else if( name == "swap!") + { + args[0]$MalAtom @=> MalAtom atom; + atom.value() @=> MalObject value; + args[1] @=> MalObject f; + MalObject.slice(args, 2) @=> MalObject _args[]; + MalObject.append([f, value], _args) @=> _args; + EVAL(MalList.create(_args), env) @=> MalObject _value; + // NOTE: the DoSwap subr only puts a value into an atom + return subr.call([atom, _value]); + } + else + { + return subr.call(args); + } + } + else // type == "func" + { + values[0]$Func @=> Func func; + Env.create(func.env, func.args, args) @=> Env eval_env; + eval_env @=> env; + func.ast @=> m; + continue; // TCO + } + } +} + +fun MalObject eval_ast(MalObject m, Env env) +{ + m.type => string type; + + if( type == "symbol" ) + { + (m$MalSymbol).value() => string symbol; + return env.get(symbol); + } + else if( type == "list" || type == "vector" || type == "hashmap" ) + { + (m$MalList).value() @=> MalObject values[]; + MalObject results[values.size()]; + + if( type != "hashmap" ) + { + for( 0 => int i; i < values.size(); i++ ) + { + EVAL(values[i], env) @=> MalObject result; + + if( result.type == "error" ) + { + return result; + } + + result @=> results[i]; + } + } + else + { + for( 0 => int i; i < values.size(); i++ ) + { + if( i % 2 == 0 ) + { + values[i] @=> results[i]; + } + else + { + EVAL(values[i], env) @=> results[i]; + } + } + } + + if( type == "list" ) + { + return MalList.create(results); + } + else if( type == "vector" ) + { + return MalVector.create(results); + } + else if( type == "hashmap" ) + { + return MalHashMap.create(results); + } + } + else + { + return m; + } +} + +fun string PRINT(MalObject m) +{ + return Printer.pr_str(m, true); +} + +Env.create(null) @=> Env repl_env; +for( 0 => int i; i < Core.names.size(); i++ ) +{ + Core.names[i] => string name; + repl_env.set(name, Core.ns[name]); +} + +repl_env.set("eval", MalSubr.create("eval", repl_env)); + +fun MalObject[] MalArgv(string args[]) +{ + MalObject values[args.size()-1]; + + for( 1 => int i; i < args.size(); i++ ) + { + MalString.create(args[i]) @=> values[i-1]; + } + + return values; +} + +// NOTE: normally I'd use \0, but strings are null-terminated... +String.split(Std.getenv("CHUCK_ARGS"), "\a") @=> string args[]; +repl_env.set("*ARGV*", MalList.create(MalArgv(args))); + +fun string rep(string input) +{ + READ(input) @=> MalObject m; + + if( m.type == "error" ) + { + return Status.toMessage(m$MalError); + } + + EVAL(m, repl_env) @=> MalObject result; + if( result.type == "error" ) + { + return Status.toMessage(result$MalError); + } + + return PRINT(result); +} + +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); + +rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))"); +rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); + +fun void main() +{ + ConsoleInput stdin; + string input; + + while( true ) + { + stdin.prompt("user>") => now; + stdin.getLine() => input; + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } + } +} + +if( args.size() > 1 ) +{ + args[1] => string filename; + rep("(load-file \"" + filename + "\")"); +} +else +{ + main(); +} diff --git a/chuck/types/subr/MalFirst.ck b/chuck/types/subr/MalFirst.ck new file mode 100644 index 0000000000..1958d68331 --- /dev/null +++ b/chuck/types/subr/MalFirst.ck @@ -0,0 +1,23 @@ +public class MalFirst extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject arg; + + if( arg.type == "nil" ) + { + return Constants.NIL; + } + + Util.sequenceToMalObjectArray(arg) @=> MalObject list[]; + + if( list.size() > 0 ) + { + return list[0]; + } + else + { + return Constants.NIL; + } + } +} diff --git a/chuck/types/subr/MalNth.ck b/chuck/types/subr/MalNth.ck new file mode 100644 index 0000000000..7bd8e52833 --- /dev/null +++ b/chuck/types/subr/MalNth.ck @@ -0,0 +1,17 @@ +public class MalNth extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + Util.sequenceToMalObjectArray(args[0]) @=> MalObject list[]; + (args[1]$MalInt).value() @=> int n; + + if( n < list.size() ) + { + return list[n]; + } + else + { + return MalError.create(Status.OUT_OF_BOUNDS); + } + } +} diff --git a/chuck/types/subr/MalRest.ck b/chuck/types/subr/MalRest.ck new file mode 100644 index 0000000000..0c51fd4cbd --- /dev/null +++ b/chuck/types/subr/MalRest.ck @@ -0,0 +1,22 @@ +public class MalRest extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject arg; + MalObject result[0]; + + if( arg.type == "nil" ) + { + return MalList.create(result); + } + + Util.sequenceToMalObjectArray(args[0]) @=> MalObject list[]; + + if( list.size() > 0 ) + { + MalObject.slice(list, 1) @=> result; + } + + return MalList.create(result); + } +} diff --git a/chuck/util/Status.ck b/chuck/util/Status.ck index 325d5b886f..d89139c248 100644 --- a/chuck/util/Status.ck +++ b/chuck/util/Status.ck @@ -5,6 +5,7 @@ public class Status static int UNEXPECTED_TERMINATOR; static int EXPECTED_TERMINATOR; static int SYMBOL_NOT_FOUND; + static int OUT_OF_BOUNDS; static string status_codes[]; @@ -33,9 +34,11 @@ public class Status 2 => Status.UNEXPECTED_TERMINATOR; 3 => Status.EXPECTED_TERMINATOR; 4 => Status.SYMBOL_NOT_FOUND; +5 => Status.OUT_OF_BOUNDS; ["success", "empty input", "unexpected '%'", "expected '%', got EOF", - "'%' not found"] @=> Status.status_codes; + "'%' not found", + "out of bounds"] @=> Status.status_codes; From 98c1ecf2a1679c0f86bcb73a7321a6c26d4e3257 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 6 Aug 2016 00:34:18 +0200 Subject: [PATCH 0021/2308] Implement step 9 --- chuck/chuck.md | 10 + chuck/core.ck | 35 ++- chuck/env.ck | 2 +- chuck/notes.md | 26 ++ chuck/reader.ck | 8 +- chuck/step1_read_print.ck | 29 +- chuck/step2_eval.ck | 33 +- chuck/step3_env.ck | 31 +- chuck/step4_if_fn_do.ck | 31 +- chuck/step5_tco.ck | 33 +- chuck/step6_file.ck | 83 +++-- chuck/step7_quote.ck | 83 +++-- chuck/step8_macros.ck | 83 +++-- chuck/step9_try.ck | 496 ++++++++++++++++++++++++++++++ chuck/types/MalObject.ck | 7 +- chuck/types/MalSubr.ck | 11 +- chuck/types/mal/MalError.ck | 24 +- chuck/types/mal/MalHashMap.ck | 40 ++- chuck/types/subr/MalApply.ck | 12 + chuck/types/subr/MalAssoc.ck | 45 +++ chuck/types/subr/MalDissoc.ck | 33 ++ chuck/types/subr/MalDoSwap.ck | 10 +- chuck/types/subr/MalEqual.ck | 35 +++ chuck/types/subr/MalGet.ck | 41 +++ chuck/types/subr/MalHashMapify.ck | 7 + chuck/types/subr/MalIsContains.ck | 35 +++ chuck/types/subr/MalIsFalse.ck | 16 + chuck/types/subr/MalIsHashMap.ck | 16 + chuck/types/subr/MalIsKeyword.ck | 16 + chuck/types/subr/MalIsNil.ck | 16 + chuck/types/subr/MalIsSymbol.ck | 16 + chuck/types/subr/MalIsTrue.ck | 16 + chuck/types/subr/MalIsVector.ck | 16 + chuck/types/subr/MalKeys.ck | 15 + chuck/types/subr/MalKeywordify.ck | 8 + chuck/types/subr/MalMap.ck | 22 ++ chuck/types/subr/MalNth.ck | 2 +- chuck/types/subr/MalSequential.ck | 16 + chuck/types/subr/MalSymbolify.ck | 8 + chuck/types/subr/MalThrow.ck | 7 + chuck/types/subr/MalVals.ck | 15 + chuck/types/subr/MalVectorify.ck | 7 + chuck/util/Status.ck | 44 --- chuck/util/Util.ck | 11 +- 44 files changed, 1367 insertions(+), 183 deletions(-) create mode 100644 chuck/step9_try.ck create mode 100644 chuck/types/subr/MalApply.ck create mode 100644 chuck/types/subr/MalAssoc.ck create mode 100644 chuck/types/subr/MalDissoc.ck create mode 100644 chuck/types/subr/MalGet.ck create mode 100644 chuck/types/subr/MalHashMapify.ck create mode 100644 chuck/types/subr/MalIsContains.ck create mode 100644 chuck/types/subr/MalIsFalse.ck create mode 100644 chuck/types/subr/MalIsHashMap.ck create mode 100644 chuck/types/subr/MalIsKeyword.ck create mode 100644 chuck/types/subr/MalIsNil.ck create mode 100644 chuck/types/subr/MalIsSymbol.ck create mode 100644 chuck/types/subr/MalIsTrue.ck create mode 100644 chuck/types/subr/MalIsVector.ck create mode 100644 chuck/types/subr/MalKeys.ck create mode 100644 chuck/types/subr/MalKeywordify.ck create mode 100644 chuck/types/subr/MalMap.ck create mode 100644 chuck/types/subr/MalSequential.ck create mode 100644 chuck/types/subr/MalSymbolify.ck create mode 100644 chuck/types/subr/MalThrow.ck create mode 100644 chuck/types/subr/MalVals.ck create mode 100644 chuck/types/subr/MalVectorify.ck delete mode 100644 chuck/util/Status.ck diff --git a/chuck/chuck.md b/chuck/chuck.md index 49f5bd2d9c..e87f384258 100644 --- a/chuck/chuck.md +++ b/chuck/chuck.md @@ -47,6 +47,7 @@ a reference and look at the return code instead) - no boxed versions of primitive types - no automatic boxing/unboxing + - no upcasting/downcasting - No module system - `Machine.add(file)` is the only mechanism available from code (no read all file contents and eval), but if you use it, it defers @@ -105,3 +106,12 @@ argument, if you pass more than one, it prints the concatenation of their representations instead, so it's a bit hard to make out what is a debug print and what isn't + - there are no hash maps, just the possibility to use a string key + on an array for storing and fetching contents (like in PHP, eww) + and no way of retrieving keys/values or even iterating over these + - I think I've spotted a weird scoping bug that prefers a member + variable over a local variable after nesting scopes, therefore I + consider the language to not implement proper lexical scoping + - another proof of it is declaring variables in consequent if-blocks + as that gives you an error instead of being permitted as they + should be in different local scopes... diff --git a/chuck/core.ck b/chuck/core.ck index 8573c6705c..970499f984 100644 --- a/chuck/core.ck +++ b/chuck/core.ck @@ -11,7 +11,13 @@ public class Core "read-string", "slurp", "atom", "atom?", "deref", "reset!", "swap!", "cons", "concat", - "nth", "first", "rest"] @=> Core.names; + "nth", "first", "rest", + "throw", + "apply", "map", + "nil?", "true?", "false?", "symbol?", "keyword?", "vector?", "map?", + "symbol", "keyword", "vector", "hash-map", + "assoc", "dissoc", "get", "contains?", "keys", "vals", + "sequential?"] @=> Core.names; MalSubr ns[0] @=> Core.ns; new MalAdd @=> Core.ns["+"]; @@ -50,3 +56,30 @@ new MalConcat @=> Core.ns["concat"]; new MalNth @=> Core.ns["nth"]; new MalFirst @=> Core.ns["first"]; new MalRest @=> Core.ns["rest"]; + +new MalThrow @=> Core.ns["throw"]; + +new MalApply @=> Core.ns["apply"]; +new MalMap @=> Core.ns["map"]; + +new MalIsNil @=> Core.ns["nil?"]; +new MalIsTrue @=> Core.ns["true?"]; +new MalIsFalse @=> Core.ns["false?"]; +new MalIsSymbol @=> Core.ns["symbol?"]; +new MalIsKeyword @=> Core.ns["keyword?"]; +new MalIsVector @=> Core.ns["vector?"]; +new MalIsHashMap @=> Core.ns["map?"]; + +new MalSymbolify @=> Core.ns["symbol"]; +new MalKeywordify @=> Core.ns["keyword"]; +new MalVectorify @=> Core.ns["vector"]; +new MalHashMapify @=> Core.ns["hash-map"]; + +new MalAssoc @=> Core.ns["assoc"]; +new MalDissoc @=> Core.ns["dissoc"]; +new MalGet @=> Core.ns["get"]; +new MalIsContains @=> Core.ns["contains?"]; +new MalKeys @=> Core.ns["keys"]; +new MalVals @=> Core.ns["vals"]; + +new MalSequential @=> Core.ns["sequential?"]; diff --git a/chuck/env.ck b/chuck/env.ck index 1564fb6510..c68f2d9b65 100644 --- a/chuck/env.ck +++ b/chuck/env.ck @@ -76,7 +76,7 @@ public class Env extends MalObject } else { - return MalError.create(Status.SYMBOL_NOT_FOUND, key); + return MalError.create(MalString.create("'" + key + "' not found")); } } } diff --git a/chuck/notes.md b/chuck/notes.md index 58529a553b..2445ce0139 100644 --- a/chuck/notes.md +++ b/chuck/notes.md @@ -111,3 +111,29 @@ structures macros." - Why does the definition of `cond` use `throw` although it's only introduced in the next chapter? + +# Step 9 + +- It's not really clear that you really just have a `try*` special + form, with `catch*` merely existing inside it... +- Another thing to clarify is that the exception value is a string + containing the message you'd see (unless you're using `throw`) +- Generally, it would be better to explain the general exception + handling mechanism (with some examples), then showing how one + implements it for both languages with and without exceptions +- Another way than using a global variable is introducing an error + type next to the other MAL types and checking whether something a + function returned is one, although the hint about returning one at + each use of `EVAL` still stands... +- Shouldn't either trick be mentioned at the beginning, simply because + you'll need it in a language without exceptions to do error handling? +- Why this bizarre treatment for `keyword`? Why is there no test for + it? +- Is there a test for whether hash maps deduplicate identical keys + when using `hash-map` or `assoc`? +- What exactly are keys the specification for `dissoc`, `get` and + `contains?` are speaking of? Can I assume these are either strings + or keywords? +- Why is it not documented that `get` may take `nil` instead of a map? +- Perhaps it's worth adding more tests involving symbols to ensure + that functions using apply internally don't evaluate their args? diff --git a/chuck/reader.ck b/chuck/reader.ck index 3a75d5107a..529a0acd62 100644 --- a/chuck/reader.ck +++ b/chuck/reader.ck @@ -56,7 +56,7 @@ public class Reader if( reader.tokens.size() == 0 ) { - return MalError.create(Status.EMPTY_INPUT); + return MalError.create(MalString.create("empty input")); } else { @@ -81,7 +81,7 @@ public class Reader } else if( token == ")" || token == "]" || token == "}" ) { - return MalError.create(Status.UNEXPECTED_TERMINATOR, token); + return MalError.create(MalString.create("unexpected '" + token + "'")); } else if( token == "'" ) { @@ -126,7 +126,7 @@ public class Reader // bothersome to do indirectly) if( reader.position == reader.tokens.size() ) { - return MalError.create(Status.EXPECTED_TERMINATOR, end); + return MalError.create(MalString.create("expected '" + end + "', got EOF")); } if( reader.peek() == end ) @@ -193,7 +193,7 @@ public class Reader } else { - return MalError.create(Status.EXPECTED_TERMINATOR, "\""); + return MalError.create(MalString.create("expected '\"', got EOF")); } } else if( token.substring(0, 1) == ":" ) diff --git a/chuck/step1_read_print.ck b/chuck/step1_read_print.ck index 8348e0acff..323478363c 100644 --- a/chuck/step1_read_print.ck +++ b/chuck/step1_read_print.ck @@ -1,6 +1,17 @@ // @import types/boxed/*.ck // @import types/MalObject.ck -// @import types/mal/*.ck +// @import types/mal/MalAtom.ck +// @import types/mal/MalError.ck +// @import types/mal/MalNil.ck +// @import types/mal/MalFalse.ck +// @import types/mal/MalTrue.ck +// @import types/mal/MalInt.ck +// @import types/mal/MalString.ck +// @import types/mal/MalSymbol.ck +// @import types/mal/MalKeyword.ck +// @import types/mal/MalList.ck +// @import types/mal/MalVector.ck +// @import types/mal/MalHashMap.ck // @import util/*.ck // @import reader.ck // @import printer.ck @@ -20,13 +31,27 @@ fun string PRINT(MalObject m) return Printer.pr_str(m, true); } +fun string errorMessage(MalObject m) +{ + (m$MalError).value() @=> MalObject value; + + if( value.type == "string" ) + { + return Printer.pr_str(value, false); + } + else + { + return "exception: " + Printer.pr_str(value, true); + } +} + fun string rep(string input) { READ(input) @=> MalObject m; if( m.type == "error" ) { - return Status.toMessage(m$MalError); + return errorMessage(m); } else { diff --git a/chuck/step2_eval.ck b/chuck/step2_eval.ck index 65d45c6ec3..9134d24a9f 100644 --- a/chuck/step2_eval.ck +++ b/chuck/step2_eval.ck @@ -1,6 +1,17 @@ // @import types/boxed/*.ck // @import types/MalObject.ck -// @import types/mal/*.ck +// @import types/mal/MalAtom.ck +// @import types/mal/MalError.ck +// @import types/mal/MalNil.ck +// @import types/mal/MalFalse.ck +// @import types/mal/MalTrue.ck +// @import types/mal/MalInt.ck +// @import types/mal/MalString.ck +// @import types/mal/MalSymbol.ck +// @import types/mal/MalKeyword.ck +// @import types/mal/MalList.ck +// @import types/mal/MalVector.ck +// @import types/mal/MalHashMap.ck // @import util/*.ck // @import reader.ck // @import printer.ck @@ -51,7 +62,7 @@ fun MalObject eval_ast(MalObject m, MalSubr env[]) if( subr == null ) { - return MalError.create(Status.SYMBOL_NOT_FOUND, symbol); + return MalError.create(MalString.create("'" + symbol + "' not found")); } else { @@ -122,19 +133,33 @@ new MalSub @=> repl_env["-"]; new MalMul @=> repl_env["*"]; new MalDiv @=> repl_env["/"]; +fun string errorMessage(MalObject m) +{ + (m$MalError).value() @=> MalObject value; + + if( value.type == "string" ) + { + return Printer.pr_str(value, false); + } + else + { + return "exception: " + Printer.pr_str(value, true); + } +} + fun string rep(string input) { READ(input) @=> MalObject m; if( m.type == "error" ) { - return Status.toMessage(m$MalError); + return errorMessage(m); } EVAL(m, repl_env) @=> MalObject result; if( result.type == "error" ) { - return Status.toMessage(result$MalError); + return errorMessage(result); } return PRINT(result); diff --git a/chuck/step3_env.ck b/chuck/step3_env.ck index 40ec01299e..a552ee3c32 100644 --- a/chuck/step3_env.ck +++ b/chuck/step3_env.ck @@ -1,6 +1,17 @@ // @import types/boxed/*.ck // @import types/MalObject.ck -// @import types/mal/*.ck +// @import types/mal/MalAtom.ck +// @import types/mal/MalError.ck +// @import types/mal/MalNil.ck +// @import types/mal/MalFalse.ck +// @import types/mal/MalTrue.ck +// @import types/mal/MalInt.ck +// @import types/mal/MalString.ck +// @import types/mal/MalSymbol.ck +// @import types/mal/MalKeyword.ck +// @import types/mal/MalList.ck +// @import types/mal/MalVector.ck +// @import types/mal/MalHashMap.ck // @import util/*.ck // @import reader.ck // @import printer.ck @@ -151,19 +162,33 @@ repl_env.set("-", new MalSub); repl_env.set("*", new MalMul); repl_env.set("/", new MalDiv); +fun string errorMessage(MalObject m) +{ + (m$MalError).value() @=> MalObject value; + + if( value.type == "string" ) + { + return Printer.pr_str(value, false); + } + else + { + return "exception: " + Printer.pr_str(value, true); + } +} + fun string rep(string input) { READ(input) @=> MalObject m; if( m.type == "error" ) { - return Status.toMessage(m$MalError); + return errorMessage(m); } EVAL(m, repl_env) @=> MalObject result; if( result.type == "error" ) { - return Status.toMessage(result$MalError); + return errorMessage(result); } return PRINT(result); diff --git a/chuck/step4_if_fn_do.ck b/chuck/step4_if_fn_do.ck index 0cbef5de50..5ea4f9bdc6 100644 --- a/chuck/step4_if_fn_do.ck +++ b/chuck/step4_if_fn_do.ck @@ -1,6 +1,17 @@ // @import types/boxed/*.ck // @import types/MalObject.ck -// @import types/mal/*.ck +// @import types/mal/MalAtom.ck +// @import types/mal/MalError.ck +// @import types/mal/MalNil.ck +// @import types/mal/MalFalse.ck +// @import types/mal/MalTrue.ck +// @import types/mal/MalInt.ck +// @import types/mal/MalString.ck +// @import types/mal/MalSymbol.ck +// @import types/mal/MalKeyword.ck +// @import types/mal/MalList.ck +// @import types/mal/MalVector.ck +// @import types/mal/MalHashMap.ck // @import util/*.ck // @import reader.ck // @import printer.ck @@ -221,19 +232,33 @@ for( 0 => int i; i < Core.names.size(); i++ ) repl_env.set(name, Core.ns[name]); } +fun string errorMessage(MalObject m) +{ + (m$MalError).value() @=> MalObject value; + + if( value.type == "string" ) + { + return Printer.pr_str(value, false); + } + else + { + return "exception: " + Printer.pr_str(value, true); + } +} + fun string rep(string input) { READ(input) @=> MalObject m; if( m.type == "error" ) { - return Status.toMessage(m$MalError); + return errorMessage(m); } EVAL(m, repl_env) @=> MalObject result; if( result.type == "error" ) { - return Status.toMessage(result$MalError); + return errorMessage(result); } return PRINT(result); diff --git a/chuck/step5_tco.ck b/chuck/step5_tco.ck index da7aefba29..b701fb1605 100644 --- a/chuck/step5_tco.ck +++ b/chuck/step5_tco.ck @@ -1,6 +1,17 @@ // @import types/boxed/*.ck // @import types/MalObject.ck -// @import types/mal/*.ck +// @import types/mal/MalAtom.ck +// @import types/mal/MalError.ck +// @import types/mal/MalNil.ck +// @import types/mal/MalFalse.ck +// @import types/mal/MalTrue.ck +// @import types/mal/MalInt.ck +// @import types/mal/MalString.ck +// @import types/mal/MalSymbol.ck +// @import types/mal/MalKeyword.ck +// @import types/mal/MalList.ck +// @import types/mal/MalVector.ck +// @import types/mal/MalHashMap.ck // @import util/*.ck // @import reader.ck // @import printer.ck @@ -72,7 +83,7 @@ fun MalObject EVAL(MalObject m, Env env) } else if( a0 == "do" ) { - MalObject.slice(ast, 1, ast.size()) @=> MalObject forms[]; + MalObject.slice(ast, 1, ast.size()-1) @=> MalObject forms[]; eval_ast(MalList.create(forms), env) @=> MalObject value; if( value.type == "error" ) @@ -227,19 +238,33 @@ for( 0 => int i; i < Core.names.size(); i++ ) repl_env.set(name, Core.ns[name]); } +fun string errorMessage(MalObject m) +{ + (m$MalError).value() @=> MalObject value; + + if( value.type == "string" ) + { + return Printer.pr_str(value, false); + } + else + { + return "exception: " + Printer.pr_str(value, true); + } +} + fun string rep(string input) { READ(input) @=> MalObject m; if( m.type == "error" ) { - return Status.toMessage(m$MalError); + return errorMessage(m); } EVAL(m, repl_env) @=> MalObject result; if( result.type == "error" ) { - return Status.toMessage(result$MalError); + return errorMessage(result); } return PRINT(result); diff --git a/chuck/step6_file.ck b/chuck/step6_file.ck index 87a75e9d30..fe4b3a6e46 100644 --- a/chuck/step6_file.ck +++ b/chuck/step6_file.ck @@ -1,6 +1,17 @@ // @import types/boxed/*.ck // @import types/MalObject.ck -// @import types/mal/*.ck +// @import types/mal/MalAtom.ck +// @import types/mal/MalError.ck +// @import types/mal/MalNil.ck +// @import types/mal/MalFalse.ck +// @import types/mal/MalTrue.ck +// @import types/mal/MalInt.ck +// @import types/mal/MalString.ck +// @import types/mal/MalSymbol.ck +// @import types/mal/MalKeyword.ck +// @import types/mal/MalList.ck +// @import types/mal/MalVector.ck +// @import types/mal/MalHashMap.ck // @import util/*.ck // @import reader.ck // @import printer.ck @@ -72,7 +83,7 @@ fun MalObject EVAL(MalObject m, Env env) } else if( a0 == "do" ) { - MalObject.slice(ast, 1, ast.size()) @=> MalObject forms[]; + MalObject.slice(ast, 1, ast.size()-1) @=> MalObject forms[]; eval_ast(MalList.create(forms), env) @=> MalObject value; if( value.type == "error" ) @@ -140,27 +151,7 @@ fun MalObject EVAL(MalObject m, Env env) if( type == "subr" ) { values[0]$MalSubr @=> MalSubr subr; - subr.name => string name; - - if( name == "eval" ) - { - return EVAL(args[0], subr.env); - } - else if( name == "swap!") - { - args[0]$MalAtom @=> MalAtom atom; - atom.value() @=> MalObject value; - args[1] @=> MalObject f; - MalObject.slice(args, 2) @=> MalObject _args[]; - MalObject.append([f, value], _args) @=> _args; - EVAL(MalList.create(_args), env) @=> MalObject _value; - // NOTE: the DoSwap subr only puts a value into an atom - return subr.call([atom, _value]); - } - else - { - return subr.call(args); - } + return subr.call(args); } else // type == "func" { @@ -247,7 +238,33 @@ for( 0 => int i; i < Core.names.size(); i++ ) repl_env.set(name, Core.ns[name]); } -repl_env.set("eval", MalSubr.create("eval", repl_env)); +// HACK, HACK, HACK +class MalEval extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject m; + return EVAL(args[0], repl_env); + } + + fun MalObject apply(MalObject f, MalObject args[]) + { + if( f.type == "subr" ) + { + return (f$MalSubr).call(args); + } + else // f.type == "func" + { + f$Func @=> Func func; + Env.create(func.env, func.args, args) @=> Env eval_env; + return EVAL(func.ast, eval_env); + } + } +} + +new MalEval @=> MalEval eval; +repl_env.set("eval", new MalEval); +eval @=> (repl_env.get("swap!")$MalSubr).eval; fun MalObject[] MalArgv(string args[]) { @@ -265,19 +282,33 @@ fun MalObject[] MalArgv(string args[]) String.split(Std.getenv("CHUCK_ARGS"), "\a") @=> string args[]; repl_env.set("*ARGV*", MalList.create(MalArgv(args))); +fun string errorMessage(MalObject m) +{ + (m$MalError).value() @=> MalObject value; + + if( value.type == "string" ) + { + return Printer.pr_str(value, false); + } + else + { + return "exception: " + Printer.pr_str(value, true); + } +} + fun string rep(string input) { READ(input) @=> MalObject m; if( m.type == "error" ) { - return Status.toMessage(m$MalError); + return errorMessage(m); } EVAL(m, repl_env) @=> MalObject result; if( result.type == "error" ) { - return Status.toMessage(result$MalError); + return errorMessage(result); } return PRINT(result); diff --git a/chuck/step7_quote.ck b/chuck/step7_quote.ck index c11c618820..a1f5082028 100644 --- a/chuck/step7_quote.ck +++ b/chuck/step7_quote.ck @@ -1,6 +1,17 @@ // @import types/boxed/*.ck // @import types/MalObject.ck -// @import types/mal/*.ck +// @import types/mal/MalAtom.ck +// @import types/mal/MalError.ck +// @import types/mal/MalNil.ck +// @import types/mal/MalFalse.ck +// @import types/mal/MalTrue.ck +// @import types/mal/MalInt.ck +// @import types/mal/MalString.ck +// @import types/mal/MalSymbol.ck +// @import types/mal/MalKeyword.ck +// @import types/mal/MalList.ck +// @import types/mal/MalVector.ck +// @import types/mal/MalHashMap.ck // @import util/*.ck // @import reader.ck // @import printer.ck @@ -127,7 +138,7 @@ fun MalObject EVAL(MalObject m, Env env) } else if( a0 == "do" ) { - MalObject.slice(ast, 1, ast.size()) @=> MalObject forms[]; + MalObject.slice(ast, 1, ast.size()-1) @=> MalObject forms[]; eval_ast(MalList.create(forms), env) @=> MalObject value; if( value.type == "error" ) @@ -195,27 +206,7 @@ fun MalObject EVAL(MalObject m, Env env) if( type == "subr" ) { values[0]$MalSubr @=> MalSubr subr; - subr.name => string name; - - if( name == "eval" ) - { - return EVAL(args[0], subr.env); - } - else if( name == "swap!") - { - args[0]$MalAtom @=> MalAtom atom; - atom.value() @=> MalObject value; - args[1] @=> MalObject f; - MalObject.slice(args, 2) @=> MalObject _args[]; - MalObject.append([f, value], _args) @=> _args; - EVAL(MalList.create(_args), env) @=> MalObject _value; - // NOTE: the DoSwap subr only puts a value into an atom - return subr.call([atom, _value]); - } - else - { - return subr.call(args); - } + return subr.call(args); } else // type == "func" { @@ -302,7 +293,33 @@ for( 0 => int i; i < Core.names.size(); i++ ) repl_env.set(name, Core.ns[name]); } -repl_env.set("eval", MalSubr.create("eval", repl_env)); +// HACK, HACK, HACK +class MalEval extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject m; + return EVAL(args[0], repl_env); + } + + fun MalObject apply(MalObject f, MalObject args[]) + { + if( f.type == "subr" ) + { + return (f$MalSubr).call(args); + } + else // f.type == "func" + { + f$Func @=> Func func; + Env.create(func.env, func.args, args) @=> Env eval_env; + return EVAL(func.ast, eval_env); + } + } +} + +new MalEval @=> MalEval eval; +repl_env.set("eval", new MalEval); +eval @=> (repl_env.get("swap!")$MalSubr).eval; fun MalObject[] MalArgv(string args[]) { @@ -320,19 +337,33 @@ fun MalObject[] MalArgv(string args[]) String.split(Std.getenv("CHUCK_ARGS"), "\a") @=> string args[]; repl_env.set("*ARGV*", MalList.create(MalArgv(args))); +fun string errorMessage(MalObject m) +{ + (m$MalError).value() @=> MalObject value; + + if( value.type == "string" ) + { + return Printer.pr_str(value, false); + } + else + { + return "exception: " + Printer.pr_str(value, true); + } +} + fun string rep(string input) { READ(input) @=> MalObject m; if( m.type == "error" ) { - return Status.toMessage(m$MalError); + return errorMessage(m); } EVAL(m, repl_env) @=> MalObject result; if( result.type == "error" ) { - return Status.toMessage(result$MalError); + return errorMessage(result); } return PRINT(result); diff --git a/chuck/step8_macros.ck b/chuck/step8_macros.ck index f1d273f295..5964458317 100644 --- a/chuck/step8_macros.ck +++ b/chuck/step8_macros.ck @@ -1,6 +1,17 @@ // @import types/boxed/*.ck // @import types/MalObject.ck -// @import types/mal/*.ck +// @import types/mal/MalAtom.ck +// @import types/mal/MalError.ck +// @import types/mal/MalNil.ck +// @import types/mal/MalFalse.ck +// @import types/mal/MalTrue.ck +// @import types/mal/MalInt.ck +// @import types/mal/MalString.ck +// @import types/mal/MalSymbol.ck +// @import types/mal/MalKeyword.ck +// @import types/mal/MalList.ck +// @import types/mal/MalVector.ck +// @import types/mal/MalHashMap.ck // @import util/*.ck // @import reader.ck // @import printer.ck @@ -198,7 +209,7 @@ fun MalObject EVAL(MalObject m, Env env) } else if( a0 == "do" ) { - MalObject.slice(ast, 1, ast.size()) @=> MalObject forms[]; + MalObject.slice(ast, 1, ast.size()-1) @=> MalObject forms[]; eval_ast(MalList.create(forms), env) @=> MalObject value; if( value.type == "error" ) @@ -266,27 +277,7 @@ fun MalObject EVAL(MalObject m, Env env) if( type == "subr" ) { values[0]$MalSubr @=> MalSubr subr; - subr.name => string name; - - if( name == "eval" ) - { - return EVAL(args[0], subr.env); - } - else if( name == "swap!") - { - args[0]$MalAtom @=> MalAtom atom; - atom.value() @=> MalObject value; - args[1] @=> MalObject f; - MalObject.slice(args, 2) @=> MalObject _args[]; - MalObject.append([f, value], _args) @=> _args; - EVAL(MalList.create(_args), env) @=> MalObject _value; - // NOTE: the DoSwap subr only puts a value into an atom - return subr.call([atom, _value]); - } - else - { - return subr.call(args); - } + return subr.call(args); } else // type == "func" { @@ -373,7 +364,33 @@ for( 0 => int i; i < Core.names.size(); i++ ) repl_env.set(name, Core.ns[name]); } -repl_env.set("eval", MalSubr.create("eval", repl_env)); +// HACK, HACK, HACK +class MalEval extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject m; + return EVAL(args[0], repl_env); + } + + fun MalObject apply(MalObject f, MalObject args[]) + { + if( f.type == "subr" ) + { + return (f$MalSubr).call(args); + } + else // f.type == "func" + { + f$Func @=> Func func; + Env.create(func.env, func.args, args) @=> Env eval_env; + return EVAL(func.ast, eval_env); + } + } +} + +new MalEval @=> MalEval eval; +repl_env.set("eval", new MalEval); +eval @=> (repl_env.get("swap!")$MalSubr).eval; fun MalObject[] MalArgv(string args[]) { @@ -391,19 +408,33 @@ fun MalObject[] MalArgv(string args[]) String.split(Std.getenv("CHUCK_ARGS"), "\a") @=> string args[]; repl_env.set("*ARGV*", MalList.create(MalArgv(args))); +fun string errorMessage(MalObject m) +{ + (m$MalError).value() @=> MalObject value; + + if( value.type == "string" ) + { + return Printer.pr_str(value, false); + } + else + { + return "exception: " + Printer.pr_str(value, true); + } +} + fun string rep(string input) { READ(input) @=> MalObject m; if( m.type == "error" ) { - return Status.toMessage(m$MalError); + return errorMessage(m); } EVAL(m, repl_env) @=> MalObject result; if( result.type == "error" ) { - return Status.toMessage(result$MalError); + return errorMessage(result); } return PRINT(result); diff --git a/chuck/step9_try.ck b/chuck/step9_try.ck new file mode 100644 index 0000000000..543090dd7c --- /dev/null +++ b/chuck/step9_try.ck @@ -0,0 +1,496 @@ +// @import types/boxed/*.ck +// @import types/MalObject.ck +// @import types/mal/MalAtom.ck +// @import types/mal/MalError.ck +// @import types/mal/MalNil.ck +// @import types/mal/MalFalse.ck +// @import types/mal/MalTrue.ck +// @import types/mal/MalInt.ck +// @import types/mal/MalString.ck +// @import types/mal/MalSymbol.ck +// @import types/mal/MalKeyword.ck +// @import types/mal/MalList.ck +// @import types/mal/MalVector.ck +// @import types/mal/MalHashMap.ck +// @import util/*.ck +// @import reader.ck +// @import printer.ck +// @import env.ck +// @import types/MalSubr.ck +// @import types/subr/*.ck +// @import core.ck +// @import func.ck + +fun MalObject READ(string input) +{ + return Reader.read_str(input); +} + +fun int isPair(MalObject m) +{ + if( (m.type == "list" || m.type == "vector") && + Util.sequenceToMalObjectArray(m).size() > 0 ) + { + return true; + } + else + { + return false; + } +} + +fun MalObject quasiquote(MalObject ast) +{ + if( !isPair(ast) ) + { + return MalList.create([MalSymbol.create("quote"), ast]); + } + + Util.sequenceToMalObjectArray(ast) @=> MalObject a[]; + a[0] @=> MalObject a0; + + if( a0.type == "symbol" && (a0$MalSymbol).value() == "unquote" ) + { + return a[1]; + } + + if( isPair(a0) ) + { + Util.sequenceToMalObjectArray(a0) @=> MalObject a0_[]; + a0_[0] @=> MalObject a0_0; + + if( a0_0.type == "symbol" && (a0_0$MalSymbol).value() == "splice-unquote" ) + { + return MalList.create( + [MalSymbol.create("concat"), a0_[1], + quasiquote(MalList.create(MalObject.slice(a, 1)))]); + } + } + + return MalList.create( + [MalSymbol.create("cons"), quasiquote(a[0]), + quasiquote(MalList.create(MalObject.slice(a, 1)))]); +} + +fun int isMacroCall(MalObject ast, Env env) +{ + if( ast.type == "list" ) + { + (ast$MalList).value() @=> MalObject a[]; + + if( a[0].type == "symbol" ) + { + (a[0]$MalSymbol).value() => string name; + env.find(name) @=> MalObject value; + + if( value != null && value.type == "func" && (value$Func).isMacro ) + { + return true; + } + } + } + + return false; +} + +fun MalObject macroexpand(MalObject ast, Env env) +{ + while( isMacroCall(ast, env) ) + { + Util.sequenceToMalObjectArray(ast) @=> MalObject list[]; + (list[0]$MalSymbol).value() => string name; + env.get(name) @=> MalObject macro; + MalObject.slice(list, 1) @=> MalObject args[]; + + if( macro.type == "subr" ) + { + (macro$MalSubr).call(args) @=> ast; + } + else // macro.type == "func" + { + macro$Func @=> Func func; + Env.create(func.env, func.args, args) @=> Env eval_env; + EVAL(func.ast, eval_env) @=> ast; + } + } + + return ast; +} + +fun MalObject EVAL(MalObject m, Env env) +{ + while( true ) + { + if( m.type != "list" ) + { + return eval_ast(m, env); + } + + if( (m$MalList).value().size() == 0 ) + { + return m; + } + + macroexpand(m, env) @=> m; + + if( m.type != "list" ) + { + return eval_ast(m, env); + } + + (m$MalList).value() @=> MalObject ast[]; + + if( ast[0].type == "symbol" ) + { + (ast[0]$MalSymbol).value() => string a0; + + if( a0 == "def!" ) + { + (ast[1]$MalSymbol).value() => string a1; + + EVAL(ast[2], env) @=> MalObject value; + if( value.type == "error" ) + { + return value; + } + + env.set(a1, value); + return value; + } + else if( a0 == "let*" ) + { + Env.create(env) @=> Env let_env; + Util.sequenceToMalObjectArray(ast[1]) @=> MalObject bindings[]; + + for( 0 => int i; i < bindings.size(); 2 +=> i) + { + (bindings[i]$MalSymbol).value() => string symbol; + EVAL(bindings[i+1], let_env) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + let_env.set(symbol, value); + } + + let_env @=> env; + ast[2] @=> m; + continue; // TCO + } + else if( a0 == "quote" ) + { + return ast[1]; + } + else if( a0 == "quasiquote" ) + { + quasiquote(ast[1]) @=> m; + continue; // TCO + } + else if( a0 == "defmacro!" ) + { + (ast[1]$MalSymbol).value() => string a1; + + EVAL(ast[2], env) @=> MalObject value; + if( value.type == "error" ) + { + return value; + } + + true => (value$Func).isMacro; + + env.set(a1, value); + return value; + } + else if( a0 == "macroexpand" ) + { + return macroexpand(ast[1], env); + } + else if( a0 == "try*" ) + { + EVAL(ast[1], env) @=> MalObject value; + + if( value.type != "error" ) + { + return value; + } + + (ast[2]$MalList).value() @=> MalObject form[]; + (form[1]$MalSymbol).value() => string name; + + Env.create(env, [name], [(value$MalError).value()]) @=> Env error_env; + return EVAL(form[2], error_env); + } + else if( a0 == "do" ) + { + MalObject.slice(ast, 1, ast.size()-1) @=> MalObject forms[]; + eval_ast(MalList.create(forms), env) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + // HACK: this assumes do gets at least one argument... + ast[ast.size()-1] @=> m; + continue; // TCO + } + else if( a0 == "if" ) + { + EVAL(ast[1], env) @=> MalObject condition; + + if( condition.type == "error" ) + { + return condition; + } + + if( !(condition.type == "nil") && !(condition.type == "false") ) + { + ast[2] @=> m; + continue; // TCO + } + else + { + if( ast.size() < 4 ) + { + return Constants.NIL; + } + else + { + ast[3] @=> m; + continue; // TCO + } + } + } + else if( a0 == "fn*" ) + { + (ast[1]$MalList).value() @=> MalObject arg_values[]; + string args[arg_values.size()]; + + for( 0 => int i; i < arg_values.size(); i++ ) + { + (arg_values[i]$MalSymbol).value() => args[i]; + } + + ast[2] @=> MalObject _ast; + + return Func.create(env, args, _ast); + } + } + + eval_ast(m, env) @=> MalObject result; + if( result.type == "error" ) + { + return result; + } + + (result$MalList).value() @=> MalObject values[]; + values[0].type => string type; + MalObject.slice(values, 1) @=> MalObject args[]; + + if( type == "subr" ) + { + values[0]$MalSubr @=> MalSubr subr; + return subr.call(args); + } + else // type == "func" + { + values[0]$Func @=> Func func; + Env.create(func.env, func.args, args) @=> Env eval_env; + eval_env @=> env; + func.ast @=> m; + continue; // TCO + } + } +} + +fun MalObject eval_ast(MalObject m, Env env) +{ + m.type => string type; + + if( type == "symbol" ) + { + (m$MalSymbol).value() => string symbol; + return env.get(symbol); + } + else if( type == "list" || type == "vector" || type == "hashmap" ) + { + (m$MalList).value() @=> MalObject values[]; + MalObject results[values.size()]; + + if( type != "hashmap" ) + { + for( 0 => int i; i < values.size(); i++ ) + { + EVAL(values[i], env) @=> MalObject result; + + if( result.type == "error" ) + { + return result; + } + + result @=> results[i]; + } + } + else + { + for( 0 => int i; i < values.size(); i++ ) + { + if( i % 2 == 0 ) + { + values[i] @=> results[i]; + } + else + { + EVAL(values[i], env) @=> results[i]; + } + } + } + + if( type == "list" ) + { + return MalList.create(results); + } + else if( type == "vector" ) + { + return MalVector.create(results); + } + else if( type == "hashmap" ) + { + return MalHashMap.create(results); + } + } + else + { + return m; + } +} + +fun string PRINT(MalObject m) +{ + return Printer.pr_str(m, true); +} + +Env.create(null) @=> Env repl_env; +for( 0 => int i; i < Core.names.size(); i++ ) +{ + Core.names[i] => string name; + repl_env.set(name, Core.ns[name]); +} + +// HACK, HACK, HACK +class MalEval extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject m; + return EVAL(args[0], repl_env); + } + + fun MalObject apply(MalObject f, MalObject args[]) + { + if( f.type == "subr" ) + { + return (f$MalSubr).call(args); + } + else // f.type == "func" + { + f$Func @=> Func func; + Env.create(func.env, func.args, args) @=> Env eval_env; + return EVAL(func.ast, eval_env); + } + } +} + +new MalEval @=> MalEval eval; +repl_env.set("eval", new MalEval); +eval @=> (repl_env.get("swap!")$MalSubr).eval; +eval @=> (repl_env.get("apply")$MalSubr).eval; +eval @=> (repl_env.get("map")$MalSubr).eval; + +fun MalObject[] MalArgv(string args[]) +{ + MalObject values[args.size()-1]; + + for( 1 => int i; i < args.size(); i++ ) + { + MalString.create(args[i]) @=> values[i-1]; + } + + return values; +} + +// NOTE: normally I'd use \0, but strings are null-terminated... +String.split(Std.getenv("CHUCK_ARGS"), "\a") @=> string args[]; +repl_env.set("*ARGV*", MalList.create(MalArgv(args))); + +fun string errorMessage(MalObject m) +{ + (m$MalError).value() @=> MalObject value; + + if( value.type == "string" ) + { + return Printer.pr_str(value, false); + } + else + { + return "exception: " + Printer.pr_str(value, true); + } +} + +fun string rep(string input) +{ + READ(input) @=> MalObject m; + + if( m.type == "error" ) + { + return errorMessage(m); + } + + EVAL(m, repl_env) @=> MalObject result; + if( result.type == "error" ) + { + return errorMessage(result); + } + + return PRINT(result); +} + +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); + +rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))"); +rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); + +fun void main() +{ + ConsoleInput stdin; + string input; + + while( true ) + { + stdin.prompt("user>") => now; + stdin.getLine() => input; + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } + } +} + +if( args.size() > 1 ) +{ + args[1] => string filename; + rep("(load-file \"" + filename + "\")"); +} +else +{ + main(); +} diff --git a/chuck/types/MalObject.ck b/chuck/types/MalObject.ck index 314eaf6732..ea1b6af780 100644 --- a/chuck/types/MalObject.ck +++ b/chuck/types/MalObject.ck @@ -44,12 +44,11 @@ public class MalObject fun static MalObject[] slice(MalObject objects[], int from, int to) { - Math.max(to - from, 0)$int => int size; - MalObject values[size]; + MalObject values[0]; - for( from => int i; i < size; i++ ) + for( from => int i; i < to; i++ ) { - objects[i] @=> values[i - from]; + values << objects[i]; } return values; diff --git a/chuck/types/MalSubr.ck b/chuck/types/MalSubr.ck index 874a122f17..0dc99a3762 100644 --- a/chuck/types/MalSubr.ck +++ b/chuck/types/MalSubr.ck @@ -2,19 +2,16 @@ public class MalSubr extends MalObject { "subr" => type; string name; - Env env; + // HACK + MalObject eval; fun MalObject call(MalObject args[]) { return new MalObject; } - // HACK: necessary for providing eval with repl_env - fun static MalSubr create(string name, Env env) + fun MalObject apply(MalObject f, MalObject args[]) { - MalSubr subr; - name => subr.name; - env @=> subr.env; - return subr; + return new MalObject; } } diff --git a/chuck/types/mal/MalError.ck b/chuck/types/mal/MalError.ck index 1a1165999b..7b22f9c707 100644 --- a/chuck/types/mal/MalError.ck +++ b/chuck/types/mal/MalError.ck @@ -1,35 +1,21 @@ public class MalError extends MalObject { "error" => type; - string data; - fun int value() + fun MalObject value() { - return (object$Int).value; + return object$MalObject; } - fun void init(int value) + fun void init(MalObject value) { - Int.create(value) @=> object; + value @=> object; } - fun void init(int value, string arg) - { - Int.create(value) @=> object; - arg => data; - } - - fun static MalError create(int value) + fun static MalError create(MalObject value) { MalError m; m.init(value); return m; } - - fun static MalError create(int value, string data) - { - MalError m; - m.init(value, data); - return m; - } } diff --git a/chuck/types/mal/MalHashMap.ck b/chuck/types/mal/MalHashMap.ck index 9db58d7b5b..6a43887ac5 100644 --- a/chuck/types/mal/MalHashMap.ck +++ b/chuck/types/mal/MalHashMap.ck @@ -1,3 +1,16 @@ +// HACK: it's hard to pull in util before data types +fun string keyName(MalObject m) +{ + if( m.type == "string" ) + { + return (m$MalString).value(); + } + else if (m.type == "keyword" ) + { + return (m$MalKeyword).value(); + } +} + public class MalHashMap extends MalObject { "hashmap" => type; @@ -10,7 +23,32 @@ public class MalHashMap extends MalObject fun void init(MalObject values[]) { - MalObject.toObjectArray(values) @=> objects; + MalObject result[0]; + MalObject cachedKeys[0]; + MalObject cachedValues[0]; + string keys[0]; + + for( 0 => int i; i < values.size(); 2 +=> i ) + { + keyName(values[i]) => string key; + + if( cachedValues[key] == null ) + { + keys << key; + } + + values[i] @=> cachedKeys[key]; + values[i+1] @=> cachedValues[key]; + } + + for( 0 => int i; i < keys.size(); i++ ) + { + keys[i] => string key; + result << cachedKeys[key]; + result << cachedValues[key]; + } + + MalObject.toObjectArray(result) @=> objects; } fun static MalHashMap create(MalObject values[]) diff --git a/chuck/types/subr/MalApply.ck b/chuck/types/subr/MalApply.ck new file mode 100644 index 0000000000..62f0b807a4 --- /dev/null +++ b/chuck/types/subr/MalApply.ck @@ -0,0 +1,12 @@ +public class MalApply extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject f; + MalObject.slice(args, 1, args.size()-1) @=> MalObject _args[]; + (args[args.size()-1]$MalList).value() @=> MalObject rest[]; + + MalObject.append(_args, rest) @=> _args; + return (eval$MalSubr).apply(f, _args); + } +} diff --git a/chuck/types/subr/MalAssoc.ck b/chuck/types/subr/MalAssoc.ck new file mode 100644 index 0000000000..1b0142e14f --- /dev/null +++ b/chuck/types/subr/MalAssoc.ck @@ -0,0 +1,45 @@ +public class MalAssoc extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + (args[0]$MalHashMap).value() @=> MalObject map[]; + MalObject.slice(args, 1) @=> MalObject kvs[]; + + MalObject result[0]; + MalObject cachedKeys[0]; + MalObject cachedValues[0]; + string keys[0]; + + for( 0 => int i; i < map.size(); 2 +=> i ) + { + Util.keyName(map[i]) => string key; + + keys << key; + + map[i] @=> cachedKeys[key]; + map[i+1] @=> cachedValues[key]; + } + + for( 0 => int i; i < kvs.size(); 2 +=> i ) + { + Util.keyName(kvs[i]) => string key; + + if( cachedValues[key] == null ) + { + keys << key; + } + + kvs[i] @=> cachedKeys[key]; + kvs[i+1] @=> cachedValues[key]; + } + + for( 0 => int i; i < keys.size(); i++ ) + { + keys[i] => string key; + result << cachedKeys[key]; + result << cachedValues[key]; + } + + return MalHashMap.create(result); + } +} diff --git a/chuck/types/subr/MalDissoc.ck b/chuck/types/subr/MalDissoc.ck new file mode 100644 index 0000000000..7f993981ea --- /dev/null +++ b/chuck/types/subr/MalDissoc.ck @@ -0,0 +1,33 @@ +public class MalDissoc extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + (args[0]$MalHashMap).value() @=> MalObject map[]; + MalObject.slice(args, 1) @=> MalObject ks[]; + + MalObject result[0]; + int cachedKeys[0]; + + for( 0 => int i; i < ks.size(); i++ ) + { + Util.keyName(ks[i]) => string key; + true => cachedKeys[key]; + } + + for( 0 => int i; i < map.size(); 2 +=> i ) + { + map[i] @=> MalObject key; + map[i+1] @=> MalObject value; + // HACK: using name doesn't work in a nested scope + Util.keyName(key) => string keyName; + + if( !cachedKeys[keyName] ) + { + result << key; + result << value; + } + } + + return MalHashMap.create(result); + } +} diff --git a/chuck/types/subr/MalDoSwap.ck b/chuck/types/subr/MalDoSwap.ck index e57826c8b8..586db80be6 100644 --- a/chuck/types/subr/MalDoSwap.ck +++ b/chuck/types/subr/MalDoSwap.ck @@ -1,15 +1,15 @@ public class MalDoSwap extends MalSubr { - // HACK: necessary for apply step - "swap!" => name; - fun MalObject call(MalObject args[]) { args[0]$MalAtom @=> MalAtom atom; - args[1]$MalObject @=> MalObject value; + atom.value() @=> MalObject value; + args[1] @=> MalObject f; + MalObject.slice(args, 2) @=> MalObject _args[]; + MalObject.append([value], _args) @=> _args; + (eval$MalSubr).apply(f, _args) @=> value; value @=> atom.object; - return value; } } diff --git a/chuck/types/subr/MalEqual.ck b/chuck/types/subr/MalEqual.ck index 86d47d4cba..33412842ce 100644 --- a/chuck/types/subr/MalEqual.ck +++ b/chuck/types/subr/MalEqual.ck @@ -28,6 +28,41 @@ public class MalEqual extends MalSubr return Constants.TRUE; } + if( a.type == "hashmap" && b.type == "hashmap" ) + { + (a$MalHashMap).value() @=> MalObject akvs[]; + (b$MalHashMap).value() @=> MalObject bkvs[]; + + if( akvs.size() != bkvs.size() ) + { + return Constants.FALSE; + } + + MalObject bmap[0]; + + for( 0 => int i; i < bkvs.size(); 2 +=> i ) + { + Util.keyName(bkvs[i]) => string keyName; + bkvs[i+1] @=> bmap[keyName]; + } + + + for( 0 => int i; i < akvs.size(); 2 +=> i ) + { + akvs[i] @=> MalObject key; + akvs[i+1] @=> MalObject value; + Util.keyName(key) => string keyName; + + if( bmap[keyName] == null || + call([value, bmap[keyName]]).type != "true" ) + { + return Constants.FALSE; + } + } + + return Constants.TRUE; + } + if( a.type != b.type ) { return Constants.FALSE; diff --git a/chuck/types/subr/MalGet.ck b/chuck/types/subr/MalGet.ck new file mode 100644 index 0000000000..67aa0a88fb --- /dev/null +++ b/chuck/types/subr/MalGet.ck @@ -0,0 +1,41 @@ +public class MalGet extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + if( args[0].type == "nil" ) + { + return Constants.NIL; + } + + (args[0]$MalHashMap).value() @=> MalObject map[]; + Util.keyName(args[1]) => string keyName; + + MalObject mapKey; + MalObject mapValue; + false => int isKeyPresent; + 0 => int i; + + while( !isKeyPresent && i < map.size() ) + { + map[i] @=> mapKey; + map[i+1] @=> mapValue; + Util.keyName(mapKey) => string mapKeyName; + + if( keyName == mapKeyName ) + { + true => isKeyPresent; + } + + 2 +=> i; + } + + if( isKeyPresent ) + { + return mapValue; + } + else + { + return Constants.NIL; + } + } +} diff --git a/chuck/types/subr/MalHashMapify.ck b/chuck/types/subr/MalHashMapify.ck new file mode 100644 index 0000000000..0c4b4adc7e --- /dev/null +++ b/chuck/types/subr/MalHashMapify.ck @@ -0,0 +1,7 @@ +public class MalHashMapify extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + return MalHashMap.create(args); + } +} diff --git a/chuck/types/subr/MalIsContains.ck b/chuck/types/subr/MalIsContains.ck new file mode 100644 index 0000000000..ed0e852a3a --- /dev/null +++ b/chuck/types/subr/MalIsContains.ck @@ -0,0 +1,35 @@ +public class MalIsContains extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + (args[0]$MalHashMap).value() @=> MalObject map[]; + Util.keyName(args[1]) => string keyName; + + MalObject mapKey; + MalObject mapValue; + false => int isKeyPresent; + 0 => int i; + + while( !isKeyPresent && i < map.size() ) + { + map[i] @=> mapKey; + Util.keyName(mapKey) => string mapKeyName; + + if( keyName == mapKeyName ) + { + true => isKeyPresent; + } + + 2 +=> i; + } + + if( isKeyPresent ) + { + return Constants.TRUE; + } + else + { + return Constants.FALSE; + } + } +} diff --git a/chuck/types/subr/MalIsFalse.ck b/chuck/types/subr/MalIsFalse.ck new file mode 100644 index 0000000000..b4866d1d21 --- /dev/null +++ b/chuck/types/subr/MalIsFalse.ck @@ -0,0 +1,16 @@ +public class MalIsFalse extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject arg; + + if( arg.type == "false" ) + { + return Constants.TRUE; + } + else + { + return Constants.FALSE; + } + } +} diff --git a/chuck/types/subr/MalIsHashMap.ck b/chuck/types/subr/MalIsHashMap.ck new file mode 100644 index 0000000000..00dfe7e7df --- /dev/null +++ b/chuck/types/subr/MalIsHashMap.ck @@ -0,0 +1,16 @@ +public class MalIsHashMap extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject arg; + + if( arg.type == "hashmap" ) + { + return Constants.TRUE; + } + else + { + return Constants.FALSE; + } + } +} diff --git a/chuck/types/subr/MalIsKeyword.ck b/chuck/types/subr/MalIsKeyword.ck new file mode 100644 index 0000000000..4e76dc4de8 --- /dev/null +++ b/chuck/types/subr/MalIsKeyword.ck @@ -0,0 +1,16 @@ +public class MalIsKeyword extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject arg; + + if( arg.type == "keyword" ) + { + return Constants.TRUE; + } + else + { + return Constants.FALSE; + } + } +} diff --git a/chuck/types/subr/MalIsNil.ck b/chuck/types/subr/MalIsNil.ck new file mode 100644 index 0000000000..32940d3b3b --- /dev/null +++ b/chuck/types/subr/MalIsNil.ck @@ -0,0 +1,16 @@ +public class MalIsNil extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject arg; + + if( arg.type == "nil" ) + { + return Constants.TRUE; + } + else + { + return Constants.FALSE; + } + } +} diff --git a/chuck/types/subr/MalIsSymbol.ck b/chuck/types/subr/MalIsSymbol.ck new file mode 100644 index 0000000000..3ebb65698c --- /dev/null +++ b/chuck/types/subr/MalIsSymbol.ck @@ -0,0 +1,16 @@ +public class MalIsSymbol extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject arg; + + if( arg.type == "symbol" ) + { + return Constants.TRUE; + } + else + { + return Constants.FALSE; + } + } +} diff --git a/chuck/types/subr/MalIsTrue.ck b/chuck/types/subr/MalIsTrue.ck new file mode 100644 index 0000000000..913e4b61b9 --- /dev/null +++ b/chuck/types/subr/MalIsTrue.ck @@ -0,0 +1,16 @@ +public class MalIsTrue extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject arg; + + if( arg.type == "true" ) + { + return Constants.TRUE; + } + else + { + return Constants.FALSE; + } + } +} diff --git a/chuck/types/subr/MalIsVector.ck b/chuck/types/subr/MalIsVector.ck new file mode 100644 index 0000000000..e74ffc08ea --- /dev/null +++ b/chuck/types/subr/MalIsVector.ck @@ -0,0 +1,16 @@ +public class MalIsVector extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject arg; + + if( arg.type == "vector" ) + { + return Constants.TRUE; + } + else + { + return Constants.FALSE; + } + } +} diff --git a/chuck/types/subr/MalKeys.ck b/chuck/types/subr/MalKeys.ck new file mode 100644 index 0000000000..e5ee6776fa --- /dev/null +++ b/chuck/types/subr/MalKeys.ck @@ -0,0 +1,15 @@ +public class MalKeys extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + (args[0]$MalHashMap).value() @=> MalObject map[]; + MalObject results[0]; + + for( 0 => int i; i < map.size(); 2 +=> i ) + { + results << map[i]; + } + + return MalList.create(results); + } +} diff --git a/chuck/types/subr/MalKeywordify.ck b/chuck/types/subr/MalKeywordify.ck new file mode 100644 index 0000000000..bc70e5340d --- /dev/null +++ b/chuck/types/subr/MalKeywordify.ck @@ -0,0 +1,8 @@ +public class MalKeywordify extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + (args[0]$MalString).value() => string name; + return MalKeyword.create(name); + } +} diff --git a/chuck/types/subr/MalMap.ck b/chuck/types/subr/MalMap.ck new file mode 100644 index 0000000000..f25f686797 --- /dev/null +++ b/chuck/types/subr/MalMap.ck @@ -0,0 +1,22 @@ +public class MalMap extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject f; + Util.sequenceToMalObjectArray(args[1]) @=> MalObject list[]; + + for( 0 => int i; i < list.size(); i++ ) + { + (eval$MalSubr).apply(f, [list[i]]) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + value @=> list[i]; + } + + return MalList.create(list); + } +} diff --git a/chuck/types/subr/MalNth.ck b/chuck/types/subr/MalNth.ck index 7bd8e52833..f013875eca 100644 --- a/chuck/types/subr/MalNth.ck +++ b/chuck/types/subr/MalNth.ck @@ -11,7 +11,7 @@ public class MalNth extends MalSubr } else { - return MalError.create(Status.OUT_OF_BOUNDS); + return MalError.create(MalString.create("out of bounds")); } } } diff --git a/chuck/types/subr/MalSequential.ck b/chuck/types/subr/MalSequential.ck new file mode 100644 index 0000000000..7587499841 --- /dev/null +++ b/chuck/types/subr/MalSequential.ck @@ -0,0 +1,16 @@ +public class MalSequential extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject arg; + + if( arg.type == "list" || arg.type == "vector" ) + { + return Constants.TRUE; + } + else + { + return Constants.FALSE; + } + } +} diff --git a/chuck/types/subr/MalSymbolify.ck b/chuck/types/subr/MalSymbolify.ck new file mode 100644 index 0000000000..f61ea250cd --- /dev/null +++ b/chuck/types/subr/MalSymbolify.ck @@ -0,0 +1,8 @@ +public class MalSymbolify extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + (args[0]$MalString).value() => string name; + return MalSymbol.create(name); + } +} diff --git a/chuck/types/subr/MalThrow.ck b/chuck/types/subr/MalThrow.ck new file mode 100644 index 0000000000..3d1dcee147 --- /dev/null +++ b/chuck/types/subr/MalThrow.ck @@ -0,0 +1,7 @@ +public class MalThrow extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + return MalError.create(args[0]); + } +} diff --git a/chuck/types/subr/MalVals.ck b/chuck/types/subr/MalVals.ck new file mode 100644 index 0000000000..ca5d35cb05 --- /dev/null +++ b/chuck/types/subr/MalVals.ck @@ -0,0 +1,15 @@ +public class MalVals extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + (args[0]$MalHashMap).value() @=> MalObject map[]; + MalObject results[0]; + + for( 1 => int i; i < map.size(); 2 +=> i ) + { + results << map[i]; + } + + return MalList.create(results); + } +} diff --git a/chuck/types/subr/MalVectorify.ck b/chuck/types/subr/MalVectorify.ck new file mode 100644 index 0000000000..97c8439d33 --- /dev/null +++ b/chuck/types/subr/MalVectorify.ck @@ -0,0 +1,7 @@ +public class MalVectorify extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + return MalVector.create(args); + } +} diff --git a/chuck/util/Status.ck b/chuck/util/Status.ck deleted file mode 100644 index d89139c248..0000000000 --- a/chuck/util/Status.ck +++ /dev/null @@ -1,44 +0,0 @@ -public class Status -{ - static int SUCCESS; - static int EMPTY_INPUT; - static int UNEXPECTED_TERMINATOR; - static int EXPECTED_TERMINATOR; - static int SYMBOL_NOT_FOUND; - static int OUT_OF_BOUNDS; - - static string status_codes[]; - - fun static string toMessage(MalError m) - { - m.value() => int status_code; - m.data => string data; - - if( status_code < status_codes.size() ) - { - status_codes[status_code] => string message; - // NOTE: for some reason, the string replacement API is - // different from the regex one, so I'm using the latter - RegEx.replace("%", data, message) => message; - return message; - } - else - { - return "Undefined status code"; - } - } -} - -0 => Status.SUCCESS; -1 => Status.EMPTY_INPUT; -2 => Status.UNEXPECTED_TERMINATOR; -3 => Status.EXPECTED_TERMINATOR; -4 => Status.SYMBOL_NOT_FOUND; -5 => Status.OUT_OF_BOUNDS; - -["success", - "empty input", - "unexpected '%'", - "expected '%', got EOF", - "'%' not found", - "out of bounds"] @=> Status.status_codes; diff --git a/chuck/util/Util.ck b/chuck/util/Util.ck index 244dfa5362..46892e85c2 100644 --- a/chuck/util/Util.ck +++ b/chuck/util/Util.ck @@ -12,9 +12,16 @@ public class Util } } - fun static void print(string message) + fun static string keyName(MalObject m) { - chout <= message; + if( m.type == "string" ) + { + return (m$MalString).value(); + } + else if (m.type == "keyword" ) + { + return (m$MalKeyword).value(); + } } fun static void println(string message) From 2e3d457eb096e84c6bdfcd202a71668692da40c3 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 6 Aug 2016 17:42:56 +0200 Subject: [PATCH 0022/2308] Add a self-made readline implementation --- chuck/readline.ck | 63 +++++++++++++++++++++++++++++++++++++++ chuck/step0_repl.ck | 20 +++++++++---- chuck/step1_read_print.ck | 25 ++++++++++------ chuck/step2_eval.ck | 25 ++++++++++------ chuck/step3_env.ck | 25 ++++++++++------ chuck/step4_if_fn_do.ck | 25 ++++++++++------ chuck/step5_tco.ck | 25 ++++++++++------ chuck/step6_file.ck | 25 ++++++++++------ chuck/step7_quote.ck | 25 ++++++++++------ chuck/step8_macros.ck | 25 ++++++++++------ chuck/step9_try.ck | 25 ++++++++++------ 11 files changed, 221 insertions(+), 87 deletions(-) create mode 100644 chuck/readline.ck diff --git a/chuck/readline.ck b/chuck/readline.ck new file mode 100644 index 0000000000..8aefe0d249 --- /dev/null +++ b/chuck/readline.ck @@ -0,0 +1,63 @@ +public class Readline +{ + fun static string readline(string prompt) + { + int done; + string input; + KBHit kb; + int char; + string repr; + + ["NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", + "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", + "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", + "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US", + " ", "!", "\"", "#", "$", "%", "&", "'", + "(", ")", "*", "+", ",", "-", ".", "/", + "0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", ":", ";", "<", "=", ">", "?", + "@", "A", "B", "C", "D", "E", "F", "G", + "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", + "X", "Y", "Z", "[", "\\", "]", "^", "_", + "`", "a", "b", "c", "d", "e", "f", "g", + "h", "i", "j", "k", "l", "m", "n", "o", + "p", "q", "r", "s", "t", "u", "v", "w", + "x", "y", "z", "{", "|", "}", "~", "DEL"] @=> string asciiTable[]; + + chout <= prompt; + chout.flush(); + + while( !done ) + { + kb => now; + + while( kb.more() && !done ) + { + kb.getchar() => char; + asciiTable[char] => repr; + + if( repr == "EOT" || repr == "LF" || repr == "CR" ) + { + true => done; + } + else + { + chout <= repr; + chout.flush(); + repr +=> input; + } + } + } + + chout <= "\n"; + + if( repr == "EOT" ) + { + return null; + } + + return input; + } +} + diff --git a/chuck/step0_repl.ck b/chuck/step0_repl.ck index 9e05a753bb..7591170706 100644 --- a/chuck/step0_repl.ck +++ b/chuck/step0_repl.ck @@ -1,3 +1,5 @@ +// @import readline.ck + fun string READ(string input) { return input; @@ -20,14 +22,20 @@ fun string rep(string input) fun void main() { - ConsoleInput stdin; - string input; + int done; - while( true ) + while( !done ) { - stdin.prompt("user>") => now; - stdin.getLine() => input; - chout <= rep(input) + "\n"; + Readline.readline("user> ") => string input; + + if( input != null ) + { + chout <= rep(input) + "\n"; + } + else + { + true => done; + } } } diff --git a/chuck/step1_read_print.ck b/chuck/step1_read_print.ck index 323478363c..b173cf36c4 100644 --- a/chuck/step1_read_print.ck +++ b/chuck/step1_read_print.ck @@ -1,3 +1,4 @@ +// @import readline.ck // @import types/boxed/*.ck // @import types/MalObject.ck // @import types/mal/MalAtom.ck @@ -61,22 +62,28 @@ fun string rep(string input) fun void main() { - ConsoleInput stdin; - string input; + int done; - while( true ) + while( !done ) { - stdin.prompt("user>") => now; - stdin.getLine() => input; - rep(input) => string output; + Readline.readline("user> ") => string input; - if( output == "empty input" ) + if( input != null ) { - // proceed immediately with prompt + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } } else { - chout <= output + "\n"; + true => done; } } } diff --git a/chuck/step2_eval.ck b/chuck/step2_eval.ck index 9134d24a9f..6795783153 100644 --- a/chuck/step2_eval.ck +++ b/chuck/step2_eval.ck @@ -1,3 +1,4 @@ +// @import readline.ck // @import types/boxed/*.ck // @import types/MalObject.ck // @import types/mal/MalAtom.ck @@ -167,22 +168,28 @@ fun string rep(string input) fun void main() { - ConsoleInput stdin; - string input; + int done; - while( true ) + while( !done ) { - stdin.prompt("user>") => now; - stdin.getLine() => input; - rep(input) => string output; + Readline.readline("user> ") => string input; - if( output == "empty input" ) + if( input != null ) { - // proceed immediately with prompt + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } } else { - chout <= output + "\n"; + true => done; } } } diff --git a/chuck/step3_env.ck b/chuck/step3_env.ck index a552ee3c32..2d2b1bae1f 100644 --- a/chuck/step3_env.ck +++ b/chuck/step3_env.ck @@ -1,3 +1,4 @@ +// @import readline.ck // @import types/boxed/*.ck // @import types/MalObject.ck // @import types/mal/MalAtom.ck @@ -196,22 +197,28 @@ fun string rep(string input) fun void main() { - ConsoleInput stdin; - string input; + int done; - while( true ) + while( !done ) { - stdin.prompt("user>") => now; - stdin.getLine() => input; - rep(input) => string output; + Readline.readline("user> ") => string input; - if( output == "empty input" ) + if( input != null ) { - // proceed immediately with prompt + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } } else { - chout <= output + "\n"; + true => done; } } } diff --git a/chuck/step4_if_fn_do.ck b/chuck/step4_if_fn_do.ck index 5ea4f9bdc6..09c0ae5667 100644 --- a/chuck/step4_if_fn_do.ck +++ b/chuck/step4_if_fn_do.ck @@ -1,3 +1,4 @@ +// @import readline.ck // @import types/boxed/*.ck // @import types/MalObject.ck // @import types/mal/MalAtom.ck @@ -268,22 +269,28 @@ rep("(def! not (fn* (a) (if a false true)))"); fun void main() { - ConsoleInput stdin; - string input; + int done; - while( true ) + while( !done ) { - stdin.prompt("user>") => now; - stdin.getLine() => input; - rep(input) => string output; + Readline.readline("user> ") => string input; - if( output == "empty input" ) + if( input != null ) { - // proceed immediately with prompt + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } } else { - Util.println(output); + true => done; } } } diff --git a/chuck/step5_tco.ck b/chuck/step5_tco.ck index b701fb1605..da2a23008f 100644 --- a/chuck/step5_tco.ck +++ b/chuck/step5_tco.ck @@ -1,3 +1,4 @@ +// @import readline.ck // @import types/boxed/*.ck // @import types/MalObject.ck // @import types/mal/MalAtom.ck @@ -274,22 +275,28 @@ rep("(def! not (fn* (a) (if a false true)))"); fun void main() { - ConsoleInput stdin; - string input; + int done; - while( true ) + while( !done ) { - stdin.prompt("user>") => now; - stdin.getLine() => input; - rep(input) => string output; + Readline.readline("user> ") => string input; - if( output == "empty input" ) + if( input != null ) { - // proceed immediately with prompt + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } } else { - Util.println(output); + true => done; } } } diff --git a/chuck/step6_file.ck b/chuck/step6_file.ck index fe4b3a6e46..31d2d8a95c 100644 --- a/chuck/step6_file.ck +++ b/chuck/step6_file.ck @@ -1,3 +1,4 @@ +// @import readline.ck // @import types/boxed/*.ck // @import types/MalObject.ck // @import types/mal/MalAtom.ck @@ -319,22 +320,28 @@ rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")) fun void main() { - ConsoleInput stdin; - string input; + int done; - while( true ) + while( !done ) { - stdin.prompt("user>") => now; - stdin.getLine() => input; - rep(input) => string output; + Readline.readline("user> ") => string input; - if( output == "empty input" ) + if( input != null ) { - // proceed immediately with prompt + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } } else { - Util.println(output); + true => done; } } } diff --git a/chuck/step7_quote.ck b/chuck/step7_quote.ck index a1f5082028..9c9fb38524 100644 --- a/chuck/step7_quote.ck +++ b/chuck/step7_quote.ck @@ -1,3 +1,4 @@ +// @import readline.ck // @import types/boxed/*.ck // @import types/MalObject.ck // @import types/mal/MalAtom.ck @@ -374,22 +375,28 @@ rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")) fun void main() { - ConsoleInput stdin; - string input; + int done; - while( true ) + while( !done ) { - stdin.prompt("user>") => now; - stdin.getLine() => input; - rep(input) => string output; + Readline.readline("user> ") => string input; - if( output == "empty input" ) + if( input != null ) { - // proceed immediately with prompt + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } } else { - Util.println(output); + true => done; } } } diff --git a/chuck/step8_macros.ck b/chuck/step8_macros.ck index 5964458317..704669a971 100644 --- a/chuck/step8_macros.ck +++ b/chuck/step8_macros.ck @@ -1,3 +1,4 @@ +// @import readline.ck // @import types/boxed/*.ck // @import types/MalObject.ck // @import types/mal/MalAtom.ck @@ -448,22 +449,28 @@ rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first x fun void main() { - ConsoleInput stdin; - string input; + int done; - while( true ) + while( !done ) { - stdin.prompt("user>") => now; - stdin.getLine() => input; - rep(input) => string output; + Readline.readline("user> ") => string input; - if( output == "empty input" ) + if( input != null ) { - // proceed immediately with prompt + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } } else { - Util.println(output); + true => done; } } } diff --git a/chuck/step9_try.ck b/chuck/step9_try.ck index 543090dd7c..392812da4e 100644 --- a/chuck/step9_try.ck +++ b/chuck/step9_try.ck @@ -1,3 +1,4 @@ +// @import readline.ck // @import types/boxed/*.ck // @import types/MalObject.ck // @import types/mal/MalAtom.ck @@ -465,22 +466,28 @@ rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first x fun void main() { - ConsoleInput stdin; - string input; + int done; - while( true ) + while( !done ) { - stdin.prompt("user>") => now; - stdin.getLine() => input; - rep(input) => string output; + Readline.readline("user> ") => string input; - if( output == "empty input" ) + if( input != null ) { - // proceed immediately with prompt + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } } else { - Util.println(output); + true => done; } } } From beb353114116f2663d70e638637b17bec676f806 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 6 Aug 2016 21:25:56 +0200 Subject: [PATCH 0023/2308] Implement step A --- chuck/core.ck | 13 +- chuck/env.ck | 10 + chuck/func.ck | 13 + chuck/notes.md | 16 + chuck/stepA_mal.ck | 509 ++++++++++++++++++++++++++++++++ chuck/types/MalObject.ck | 29 +- chuck/types/mal/MalAtom.ck | 13 +- chuck/types/mal/MalFalse.ck | 13 +- chuck/types/mal/MalHashMap.ck | 13 +- chuck/types/mal/MalInt.ck | 13 +- chuck/types/mal/MalKeyword.ck | 13 +- chuck/types/mal/MalList.ck | 13 +- chuck/types/mal/MalNil.ck | 13 +- chuck/types/mal/MalString.ck | 13 +- chuck/types/mal/MalSymbol.ck | 13 +- chuck/types/mal/MalTrue.ck | 13 +- chuck/types/mal/MalVector.ck | 13 +- chuck/types/subr/MalConj.ck | 17 ++ chuck/types/subr/MalIsString.ck | 14 + chuck/types/subr/MalMeta.ck | 16 + chuck/types/subr/MalReadline.ck | 17 ++ chuck/types/subr/MalSeq.ck | 45 +++ chuck/types/subr/MalTimeMs.ck | 16 + chuck/types/subr/MalWithMeta.ck | 15 + 24 files changed, 860 insertions(+), 13 deletions(-) create mode 100644 chuck/stepA_mal.ck create mode 100644 chuck/types/subr/MalConj.ck create mode 100644 chuck/types/subr/MalIsString.ck create mode 100644 chuck/types/subr/MalMeta.ck create mode 100644 chuck/types/subr/MalReadline.ck create mode 100644 chuck/types/subr/MalSeq.ck create mode 100644 chuck/types/subr/MalTimeMs.ck create mode 100644 chuck/types/subr/MalWithMeta.ck diff --git a/chuck/core.ck b/chuck/core.ck index 970499f984..7c1eb63388 100644 --- a/chuck/core.ck +++ b/chuck/core.ck @@ -17,7 +17,9 @@ public class Core "nil?", "true?", "false?", "symbol?", "keyword?", "vector?", "map?", "symbol", "keyword", "vector", "hash-map", "assoc", "dissoc", "get", "contains?", "keys", "vals", - "sequential?"] @=> Core.names; + "sequential?", + "readline", "meta", "with-meta", + "time-ms", "conj", "string?", "seq"] @=> Core.names; MalSubr ns[0] @=> Core.ns; new MalAdd @=> Core.ns["+"]; @@ -83,3 +85,12 @@ new MalKeys @=> Core.ns["keys"]; new MalVals @=> Core.ns["vals"]; new MalSequential @=> Core.ns["sequential?"]; + +new MalReadline @=> Core.ns["readline"]; +new MalMeta @=> Core.ns["meta"]; +new MalWithMeta @=> Core.ns["with-meta"]; + +new MalTimeMs @=> Core.ns["time-ms"]; +new MalConj @=> Core.ns["conj"]; +new MalIsString @=> Core.ns["string?"]; +new MalSeq @=> Core.ns["seq"]; diff --git a/chuck/env.ck b/chuck/env.ck index c68f2d9b65..e03ccf2b8e 100644 --- a/chuck/env.ck +++ b/chuck/env.ck @@ -43,6 +43,16 @@ public class Env extends MalObject return e; } + fun MalObject clone() + { + Env value; + + this.outer @=> value.outer; + this.data @=> value.data; + + return value; + } + fun void set(string key, MalObject value) { value @=> data[key]; diff --git a/chuck/func.ck b/chuck/func.ck index 014d78c235..15d4bd9df3 100644 --- a/chuck/func.ck +++ b/chuck/func.ck @@ -19,4 +19,17 @@ public class Func extends MalObject func.init(env, args, ast); return func; } + + fun MalObject clone() + { + Func value; + + this.type => value.type; + this.env @=> value.env; + this.args @=> value.args; + this.ast @=> value.ast; + this.isMacro @=> value.isMacro; + + return value; + } } diff --git a/chuck/notes.md b/chuck/notes.md index 2445ce0139..e897610d2a 100644 --- a/chuck/notes.md +++ b/chuck/notes.md @@ -137,3 +137,19 @@ - Why is it not documented that `get` may take `nil` instead of a map? - Perhaps it's worth adding more tests involving symbols to ensure that functions using apply internally don't evaluate their args? + +# Step A + +- "Add meta-data support to mal functions." <- Shouldn't you mention + that this involves implementing `with-meta` and `meta`? +- "TODO. Should be separate from the function macro flag." <- Why is + this even related? +- It would be worth to mention that `with-meta` shall clone its + argument to avoid one of the more sneaky test failure reasons +- "The value of this entry should be a mal string containing thename + of the current implementation." +- "When the REPL starts up (as opposed to when it is called with a + script and/or arguments), call the rep function with this string to + print a startup header: `"(println (str \"Mal + [\" *host-language* \"]\"))".`" <- proof that you better quote these + because the asterisks just disappear... diff --git a/chuck/stepA_mal.ck b/chuck/stepA_mal.ck new file mode 100644 index 0000000000..8845385c00 --- /dev/null +++ b/chuck/stepA_mal.ck @@ -0,0 +1,509 @@ +// @import readline.ck +// @import types/boxed/*.ck +// @import types/MalObject.ck +// @import types/mal/MalAtom.ck +// @import types/mal/MalError.ck +// @import types/mal/MalNil.ck +// @import types/mal/MalFalse.ck +// @import types/mal/MalTrue.ck +// @import types/mal/MalInt.ck +// @import types/mal/MalString.ck +// @import types/mal/MalSymbol.ck +// @import types/mal/MalKeyword.ck +// @import types/mal/MalList.ck +// @import types/mal/MalVector.ck +// @import types/mal/MalHashMap.ck +// @import util/*.ck +// @import reader.ck +// @import printer.ck +// @import env.ck +// @import func.ck +// @import types/MalSubr.ck +// @import types/subr/*.ck +// @import core.ck + +fun MalObject READ(string input) +{ + return Reader.read_str(input); +} + +fun int isPair(MalObject m) +{ + if( (m.type == "list" || m.type == "vector") && + Util.sequenceToMalObjectArray(m).size() > 0 ) + { + return true; + } + else + { + return false; + } +} + +fun MalObject quasiquote(MalObject ast) +{ + if( !isPair(ast) ) + { + return MalList.create([MalSymbol.create("quote"), ast]); + } + + Util.sequenceToMalObjectArray(ast) @=> MalObject a[]; + a[0] @=> MalObject a0; + + if( a0.type == "symbol" && (a0$MalSymbol).value() == "unquote" ) + { + return a[1]; + } + + if( isPair(a0) ) + { + Util.sequenceToMalObjectArray(a0) @=> MalObject a0_[]; + a0_[0] @=> MalObject a0_0; + + if( a0_0.type == "symbol" && (a0_0$MalSymbol).value() == "splice-unquote" ) + { + return MalList.create( + [MalSymbol.create("concat"), a0_[1], + quasiquote(MalList.create(MalObject.slice(a, 1)))]); + } + } + + return MalList.create( + [MalSymbol.create("cons"), quasiquote(a[0]), + quasiquote(MalList.create(MalObject.slice(a, 1)))]); +} + +fun int isMacroCall(MalObject ast, Env env) +{ + if( ast.type == "list" ) + { + (ast$MalList).value() @=> MalObject a[]; + + if( a[0].type == "symbol" ) + { + (a[0]$MalSymbol).value() => string name; + env.find(name) @=> MalObject value; + + if( value != null && value.type == "func" && (value$Func).isMacro ) + { + return true; + } + } + } + + return false; +} + +fun MalObject macroexpand(MalObject ast, Env env) +{ + while( isMacroCall(ast, env) ) + { + Util.sequenceToMalObjectArray(ast) @=> MalObject list[]; + (list[0]$MalSymbol).value() => string name; + env.get(name) @=> MalObject macro; + MalObject.slice(list, 1) @=> MalObject args[]; + + if( macro.type == "subr" ) + { + (macro$MalSubr).call(args) @=> ast; + } + else // macro.type == "func" + { + macro$Func @=> Func func; + Env.create(func.env, func.args, args) @=> Env eval_env; + EVAL(func.ast, eval_env) @=> ast; + } + } + + return ast; +} + +fun MalObject EVAL(MalObject m, Env env) +{ + while( true ) + { + if( m.type != "list" ) + { + return eval_ast(m, env); + } + + if( (m$MalList).value().size() == 0 ) + { + return m; + } + + macroexpand(m, env) @=> m; + + if( m.type != "list" ) + { + return eval_ast(m, env); + } + + (m$MalList).value() @=> MalObject ast[]; + + if( ast[0].type == "symbol" ) + { + (ast[0]$MalSymbol).value() => string a0; + + if( a0 == "def!" ) + { + (ast[1]$MalSymbol).value() => string a1; + + EVAL(ast[2], env) @=> MalObject value; + if( value.type == "error" ) + { + return value; + } + + env.set(a1, value); + return value; + } + else if( a0 == "let*" ) + { + Env.create(env) @=> Env let_env; + Util.sequenceToMalObjectArray(ast[1]) @=> MalObject bindings[]; + + for( 0 => int i; i < bindings.size(); 2 +=> i) + { + (bindings[i]$MalSymbol).value() => string symbol; + EVAL(bindings[i+1], let_env) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + let_env.set(symbol, value); + } + + let_env @=> env; + ast[2] @=> m; + continue; // TCO + } + else if( a0 == "quote" ) + { + return ast[1]; + } + else if( a0 == "quasiquote" ) + { + quasiquote(ast[1]) @=> m; + continue; // TCO + } + else if( a0 == "defmacro!" ) + { + (ast[1]$MalSymbol).value() => string a1; + + EVAL(ast[2], env) @=> MalObject value; + if( value.type == "error" ) + { + return value; + } + + true => (value$Func).isMacro; + + env.set(a1, value); + return value; + } + else if( a0 == "macroexpand" ) + { + return macroexpand(ast[1], env); + } + else if( a0 == "try*" ) + { + EVAL(ast[1], env) @=> MalObject value; + + if( value.type != "error" ) + { + return value; + } + + (ast[2]$MalList).value() @=> MalObject form[]; + (form[1]$MalSymbol).value() => string name; + + Env.create(env, [name], [(value$MalError).value()]) @=> Env error_env; + return EVAL(form[2], error_env); + } + else if( a0 == "do" ) + { + MalObject.slice(ast, 1, ast.size()-1) @=> MalObject forms[]; + eval_ast(MalList.create(forms), env) @=> MalObject value; + + if( value.type == "error" ) + { + return value; + } + + // HACK: this assumes do gets at least one argument... + ast[ast.size()-1] @=> m; + continue; // TCO + } + else if( a0 == "if" ) + { + EVAL(ast[1], env) @=> MalObject condition; + + if( condition.type == "error" ) + { + return condition; + } + + if( !(condition.type == "nil") && !(condition.type == "false") ) + { + ast[2] @=> m; + continue; // TCO + } + else + { + if( ast.size() < 4 ) + { + return Constants.NIL; + } + else + { + ast[3] @=> m; + continue; // TCO + } + } + } + else if( a0 == "fn*" ) + { + (ast[1]$MalList).value() @=> MalObject arg_values[]; + string args[arg_values.size()]; + + for( 0 => int i; i < arg_values.size(); i++ ) + { + (arg_values[i]$MalSymbol).value() => args[i]; + } + + ast[2] @=> MalObject _ast; + + return Func.create(env, args, _ast); + } + } + + eval_ast(m, env) @=> MalObject result; + if( result.type == "error" ) + { + return result; + } + + (result$MalList).value() @=> MalObject values[]; + values[0].type => string type; + MalObject.slice(values, 1) @=> MalObject args[]; + + if( type == "subr" ) + { + values[0]$MalSubr @=> MalSubr subr; + return subr.call(args); + } + else // type == "func" + { + values[0]$Func @=> Func func; + Env.create(func.env, func.args, args) @=> Env eval_env; + eval_env @=> env; + func.ast @=> m; + continue; // TCO + } + } +} + +fun MalObject eval_ast(MalObject m, Env env) +{ + m.type => string type; + + if( type == "symbol" ) + { + (m$MalSymbol).value() => string symbol; + return env.get(symbol); + } + else if( type == "list" || type == "vector" || type == "hashmap" ) + { + (m$MalList).value() @=> MalObject values[]; + MalObject results[values.size()]; + + if( type != "hashmap" ) + { + for( 0 => int i; i < values.size(); i++ ) + { + EVAL(values[i], env) @=> MalObject result; + + if( result.type == "error" ) + { + return result; + } + + result @=> results[i]; + } + } + else + { + for( 0 => int i; i < values.size(); i++ ) + { + if( i % 2 == 0 ) + { + values[i] @=> results[i]; + } + else + { + EVAL(values[i], env) @=> results[i]; + } + } + } + + if( type == "list" ) + { + return MalList.create(results); + } + else if( type == "vector" ) + { + return MalVector.create(results); + } + else if( type == "hashmap" ) + { + return MalHashMap.create(results); + } + } + else + { + return m; + } +} + +fun string PRINT(MalObject m) +{ + return Printer.pr_str(m, true); +} + +Env.create(null) @=> Env repl_env; +for( 0 => int i; i < Core.names.size(); i++ ) +{ + Core.names[i] => string name; + repl_env.set(name, Core.ns[name]); +} + +// HACK, HACK, HACK +class MalEval extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject m; + return EVAL(args[0], repl_env); + } + + fun MalObject apply(MalObject f, MalObject args[]) + { + if( f.type == "subr" ) + { + return (f$MalSubr).call(args); + } + else // f.type == "func" + { + f$Func @=> Func func; + Env.create(func.env, func.args, args) @=> Env eval_env; + return EVAL(func.ast, eval_env); + } + } +} + +new MalEval @=> MalEval eval; +repl_env.set("eval", new MalEval); +eval @=> (repl_env.get("swap!")$MalSubr).eval; +eval @=> (repl_env.get("apply")$MalSubr).eval; +eval @=> (repl_env.get("map")$MalSubr).eval; + +fun MalObject[] MalArgv(string args[]) +{ + MalObject values[args.size()-1]; + + for( 1 => int i; i < args.size(); i++ ) + { + MalString.create(args[i]) @=> values[i-1]; + } + + return values; +} + +// NOTE: normally I'd use \0, but strings are null-terminated... +String.split(Std.getenv("CHUCK_ARGS"), "\a") @=> string args[]; +repl_env.set("*ARGV*", MalList.create(MalArgv(args))); + +repl_env.set("*host-language*", MalString.create("chuck")); + +fun string errorMessage(MalObject m) +{ + (m$MalError).value() @=> MalObject value; + + if( value.type == "string" ) + { + return Printer.pr_str(value, false); + } + else + { + return "exception: " + Printer.pr_str(value, true); + } +} + +fun string rep(string input) +{ + READ(input) @=> MalObject m; + + if( m.type == "error" ) + { + return errorMessage(m); + } + + EVAL(m, repl_env) @=> MalObject result; + if( result.type == "error" ) + { + return errorMessage(result); + } + + return PRINT(result); +} + +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); + +rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))"); + +rep("(def! *gensym-counter* (atom 0))"); +rep("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))"); +rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))"); + +fun void main() +{ + int done; + + while( !done ) + { + Readline.readline("user> ") => string input; + + if( input != null ) + { + rep(input) => string output; + + if( output == "empty input" ) + { + // proceed immediately with prompt + } + else + { + Util.println(output); + } + } + else + { + true => done; + } + } +} + +if( args.size() > 1 ) +{ + args[1] => string filename; + rep("(load-file \"" + filename + "\")"); +} +else +{ + rep("(println (str \"Mal [\" *host-language* \"]\"))"); + main(); +} diff --git a/chuck/types/MalObject.ck b/chuck/types/MalObject.ck index ea1b6af780..9d200f4d0b 100644 --- a/chuck/types/MalObject.ck +++ b/chuck/types/MalObject.ck @@ -3,9 +3,24 @@ public class MalObject string type; Object object; Object objects[]; - // no meta here because types can't be self-referential + // HACK: data types can't be self-referential + // NOTE: an object member does *not* default to null... + null => Object meta; + + fun MalObject clone() + { + MalObject value; + + this.type => value.type; + this.object @=> value.object; + this.objects @=> value.objects; + this.meta @=> value.meta; + + return value; + } // helpers for sequence types + fun static MalObject[] toMalObjectArray(Object objects[]) { MalObject values[objects.size()]; @@ -70,4 +85,16 @@ public class MalObject return output; } + + fun static MalObject[] reverse(MalObject objects[]) + { + MalObject output[objects.size()]; + + for( 0 => int i; i < output.size(); i++ ) + { + objects[i] @=> output[output.size()-i-1]; + } + + return output; + } } diff --git a/chuck/types/mal/MalAtom.ck b/chuck/types/mal/MalAtom.ck index 984213dd61..d6b3a84df7 100644 --- a/chuck/types/mal/MalAtom.ck +++ b/chuck/types/mal/MalAtom.ck @@ -1,7 +1,6 @@ public class MalAtom extends MalObject { "atom" => type; - MalObject meta; fun MalObject value() { @@ -19,4 +18,16 @@ public class MalAtom extends MalObject m.init(value); return m; } + + fun MalObject clone() + { + MalAtom value; + + this.type => value.type; + this.object @=> value.object; + this.objects @=> value.objects; + this.meta @=> value.meta; + + return value; + } } diff --git a/chuck/types/mal/MalFalse.ck b/chuck/types/mal/MalFalse.ck index 28b4e51987..a07e078bae 100644 --- a/chuck/types/mal/MalFalse.ck +++ b/chuck/types/mal/MalFalse.ck @@ -1,7 +1,6 @@ public class MalFalse extends MalObject { "false" => type; - MalObject meta; fun void init() { @@ -14,4 +13,16 @@ public class MalFalse extends MalObject m.init(); return m; } + + fun MalObject clone() + { + MalFalse value; + + this.type => value.type; + this.object @=> value.object; + this.objects @=> value.objects; + this.meta @=> value.meta; + + return value; + } } diff --git a/chuck/types/mal/MalHashMap.ck b/chuck/types/mal/MalHashMap.ck index 6a43887ac5..94ede0e5e2 100644 --- a/chuck/types/mal/MalHashMap.ck +++ b/chuck/types/mal/MalHashMap.ck @@ -14,7 +14,6 @@ fun string keyName(MalObject m) public class MalHashMap extends MalObject { "hashmap" => type; - MalObject meta; fun MalObject[] value() { @@ -57,4 +56,16 @@ public class MalHashMap extends MalObject m.init(values); return m; } + + fun MalObject clone() + { + MalHashMap value; + + this.type => value.type; + this.object @=> value.object; + this.objects @=> value.objects; + this.meta @=> value.meta; + + return value; + } } diff --git a/chuck/types/mal/MalInt.ck b/chuck/types/mal/MalInt.ck index 569f8a188f..1e00a02d82 100644 --- a/chuck/types/mal/MalInt.ck +++ b/chuck/types/mal/MalInt.ck @@ -1,7 +1,6 @@ public class MalInt extends MalObject { "int" => type; - MalObject meta; fun int value() { @@ -19,4 +18,16 @@ public class MalInt extends MalObject m.init(value); return m; } + + fun MalObject clone() + { + MalInt value; + + this.type => value.type; + this.object @=> value.object; + this.objects @=> value.objects; + this.meta @=> value.meta; + + return value; + } } diff --git a/chuck/types/mal/MalKeyword.ck b/chuck/types/mal/MalKeyword.ck index 0730328cc8..398540ffa2 100644 --- a/chuck/types/mal/MalKeyword.ck +++ b/chuck/types/mal/MalKeyword.ck @@ -1,7 +1,6 @@ public class MalKeyword extends MalObject { "keyword" => type; - MalObject meta; fun string value() { @@ -19,4 +18,16 @@ public class MalKeyword extends MalObject m.init(value); return m; } + + fun MalObject clone() + { + MalKeyword value; + + this.type => value.type; + this.object @=> value.object; + this.objects @=> value.objects; + this.meta @=> value.meta; + + return value; + } } diff --git a/chuck/types/mal/MalList.ck b/chuck/types/mal/MalList.ck index b558ff3897..9e6c323bce 100644 --- a/chuck/types/mal/MalList.ck +++ b/chuck/types/mal/MalList.ck @@ -1,7 +1,6 @@ public class MalList extends MalObject { "list" => type; - MalObject meta; fun MalObject[] value() { @@ -19,4 +18,16 @@ public class MalList extends MalObject m.init(values); return m; } + + fun MalObject clone() + { + MalList value; + + this.type => value.type; + this.object @=> value.object; + this.objects @=> value.objects; + this.meta @=> value.meta; + + return value; + } } diff --git a/chuck/types/mal/MalNil.ck b/chuck/types/mal/MalNil.ck index 9e31e73d0e..49e125ee12 100644 --- a/chuck/types/mal/MalNil.ck +++ b/chuck/types/mal/MalNil.ck @@ -1,7 +1,6 @@ public class MalNil extends MalObject { "nil" => type; - MalObject meta; fun void init() { @@ -14,4 +13,16 @@ public class MalNil extends MalObject m.init(); return m; } + + fun MalObject clone() + { + MalNil value; + + this.type => value.type; + this.object @=> value.object; + this.objects @=> value.objects; + this.meta @=> value.meta; + + return value; + } } diff --git a/chuck/types/mal/MalString.ck b/chuck/types/mal/MalString.ck index e7fc788ac3..03da4dd369 100644 --- a/chuck/types/mal/MalString.ck +++ b/chuck/types/mal/MalString.ck @@ -1,7 +1,6 @@ public class MalString extends MalObject { "string" => type; - MalObject meta; fun string value() { @@ -19,4 +18,16 @@ public class MalString extends MalObject m.init(value); return m; } + + fun MalObject clone() + { + MalString value; + + this.type => value.type; + this.object @=> value.object; + this.objects @=> value.objects; + this.meta @=> value.meta; + + return value; + } } diff --git a/chuck/types/mal/MalSymbol.ck b/chuck/types/mal/MalSymbol.ck index 9220935445..9a306c43ff 100644 --- a/chuck/types/mal/MalSymbol.ck +++ b/chuck/types/mal/MalSymbol.ck @@ -1,7 +1,6 @@ public class MalSymbol extends MalObject { "symbol" => type; - MalObject meta; fun string value() { @@ -19,4 +18,16 @@ public class MalSymbol extends MalObject m.init(value); return m; } + + fun MalObject clone() + { + MalSymbol value; + + this.type => value.type; + this.object @=> value.object; + this.objects @=> value.objects; + this.meta @=> value.meta; + + return value; + } } diff --git a/chuck/types/mal/MalTrue.ck b/chuck/types/mal/MalTrue.ck index 12b6e047e1..2675929afc 100644 --- a/chuck/types/mal/MalTrue.ck +++ b/chuck/types/mal/MalTrue.ck @@ -1,7 +1,6 @@ public class MalTrue extends MalObject { "true" => type; - MalObject meta; fun void init() { @@ -14,4 +13,16 @@ public class MalTrue extends MalObject m.init(); return m; } + + fun MalObject clone() + { + MalTrue value; + + this.type => value.type; + this.object @=> value.object; + this.objects @=> value.objects; + this.meta @=> value.meta; + + return value; + } } diff --git a/chuck/types/mal/MalVector.ck b/chuck/types/mal/MalVector.ck index c3381806b6..521658c9a9 100644 --- a/chuck/types/mal/MalVector.ck +++ b/chuck/types/mal/MalVector.ck @@ -1,7 +1,6 @@ public class MalVector extends MalObject { "vector" => type; - MalObject meta; fun MalObject[] value() { @@ -19,4 +18,16 @@ public class MalVector extends MalObject m.init(values); return m; } + + fun MalObject clone() + { + MalVector value; + + this.type => value.type; + this.object @=> value.object; + this.objects @=> value.objects; + this.meta @=> value.meta; + + return value; + } } diff --git a/chuck/types/subr/MalConj.ck b/chuck/types/subr/MalConj.ck new file mode 100644 index 0000000000..436fda6063 --- /dev/null +++ b/chuck/types/subr/MalConj.ck @@ -0,0 +1,17 @@ +public class MalConj extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + Util.sequenceToMalObjectArray(args[0]) @=> MalObject list[]; + MalObject.slice(args, 1) @=> MalObject rest[]; + + if( args[0].type == "list" ) + { + return MalList.create(MalObject.append(MalObject.reverse(rest), list)); + } + else // args[0].type == "vector" + { + return MalVector.create(MalObject.append(list, rest)); + } + } +} diff --git a/chuck/types/subr/MalIsString.ck b/chuck/types/subr/MalIsString.ck new file mode 100644 index 0000000000..d85d58c3ce --- /dev/null +++ b/chuck/types/subr/MalIsString.ck @@ -0,0 +1,14 @@ +public class MalIsString extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + if( args[0].type == "string" ) + { + return Constants.TRUE; + } + else + { + return Constants.FALSE; + } + } +} diff --git a/chuck/types/subr/MalMeta.ck b/chuck/types/subr/MalMeta.ck new file mode 100644 index 0000000000..05689629d2 --- /dev/null +++ b/chuck/types/subr/MalMeta.ck @@ -0,0 +1,16 @@ +public class MalMeta extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject arg; + + if( arg.meta == null ) + { + return Constants.NIL; + } + else + { + return (arg.meta)$MalObject; + } + } +} diff --git a/chuck/types/subr/MalReadline.ck b/chuck/types/subr/MalReadline.ck new file mode 100644 index 0000000000..2e817ac4da --- /dev/null +++ b/chuck/types/subr/MalReadline.ck @@ -0,0 +1,17 @@ +public class MalReadline extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + (args[0]$MalString).value() => string prompt; + Readline.readline(prompt) => string input; + + if( input == null ) + { + return Constants.NIL; + } + else + { + return MalString.create(input); + } + } +} diff --git a/chuck/types/subr/MalSeq.ck b/chuck/types/subr/MalSeq.ck new file mode 100644 index 0000000000..37748c944c --- /dev/null +++ b/chuck/types/subr/MalSeq.ck @@ -0,0 +1,45 @@ +public class MalSeq extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject arg; + + if( arg.type == "nil" ) + { + return Constants.NIL; + } + else if( arg.type == "list" || arg.type == "vector" ) + { + Util.sequenceToMalObjectArray(args[0]) @=> MalObject list[]; + + if( list.size() > 0 ) + { + return MalList.create(list); + } + else + { + return Constants.NIL; + } + } + else if( arg.type == "string" ) + { + (args[0]$MalString).value() => string value; + + if( value.length() > 0 ) + { + MalObject chars[value.length()]; + + for( 0 => int i; i < value.length(); i++ ) + { + MalString.create(value.substring(i, 1)) @=> chars[i]; + } + + return MalList.create(chars); + } + else + { + return Constants.NIL; + } + } + } +} diff --git a/chuck/types/subr/MalTimeMs.ck b/chuck/types/subr/MalTimeMs.ck new file mode 100644 index 0000000000..6c548d3e84 --- /dev/null +++ b/chuck/types/subr/MalTimeMs.ck @@ -0,0 +1,16 @@ +public class MalTimeMs extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + // HACK: Std.system returns the status code only... + "/tmp/chuck-date." + Std.rand2(1000,9999) => string temp_file; + Std.system("date +%s%3N > " + temp_file); + + FileIO f; + f.open(temp_file, FileIO.READ); + f => int timestamp; + f.close(); + + return MalInt.create(timestamp); + } +} diff --git a/chuck/types/subr/MalWithMeta.ck b/chuck/types/subr/MalWithMeta.ck new file mode 100644 index 0000000000..7e8fba1cec --- /dev/null +++ b/chuck/types/subr/MalWithMeta.ck @@ -0,0 +1,15 @@ +public class MalWithMeta extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + args[0] @=> MalObject arg; + args[1] @=> MalObject meta; + + MalObject value; + arg.clone() @=> value; + + meta$Object @=> value.meta; + + return value; + } +} From 35163c1b322c24ae2b9d4b7752f9e96cc1add945 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 6 Aug 2016 22:41:00 +0200 Subject: [PATCH 0024/2308] Self-hosting fix --- chuck/run_chuck.rb | 2 +- chuck/step6_file.ck | 4 ++-- chuck/step7_quote.ck | 4 ++-- chuck/step8_macros.ck | 4 ++-- chuck/step9_try.ck | 4 ++-- chuck/stepA_mal.ck | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/chuck/run_chuck.rb b/chuck/run_chuck.rb index 44e5b4b936..82ed1eb487 100755 --- a/chuck/run_chuck.rb +++ b/chuck/run_chuck.rb @@ -10,5 +10,5 @@ cmdline += import_files cmdline << scriptfile -ENV['CHUCK_ARGS'] = ARGV.join("\a") +ENV['CHUCK_ARGS'] = ARGV.drop(1).join("\a") exec(*cmdline) diff --git a/chuck/step6_file.ck b/chuck/step6_file.ck index 31d2d8a95c..6e401b02a6 100644 --- a/chuck/step6_file.ck +++ b/chuck/step6_file.ck @@ -346,9 +346,9 @@ fun void main() } } -if( args.size() > 1 ) +if( args.size() > 0 ) { - args[1] => string filename; + args[0] => string filename; rep("(load-file \"" + filename + "\")"); } else diff --git a/chuck/step7_quote.ck b/chuck/step7_quote.ck index 9c9fb38524..277e0e5a17 100644 --- a/chuck/step7_quote.ck +++ b/chuck/step7_quote.ck @@ -401,9 +401,9 @@ fun void main() } } -if( args.size() > 1 ) +if( args.size() > 0 ) { - args[1] => string filename; + args[0] => string filename; rep("(load-file \"" + filename + "\")"); } else diff --git a/chuck/step8_macros.ck b/chuck/step8_macros.ck index 704669a971..a01535453c 100644 --- a/chuck/step8_macros.ck +++ b/chuck/step8_macros.ck @@ -475,9 +475,9 @@ fun void main() } } -if( args.size() > 1 ) +if( args.size() > 0 ) { - args[1] => string filename; + args[0] => string filename; rep("(load-file \"" + filename + "\")"); } else diff --git a/chuck/step9_try.ck b/chuck/step9_try.ck index 392812da4e..e57a67e948 100644 --- a/chuck/step9_try.ck +++ b/chuck/step9_try.ck @@ -492,9 +492,9 @@ fun void main() } } -if( args.size() > 1 ) +if( args.size() > 0 ) { - args[1] => string filename; + args[0] => string filename; rep("(load-file \"" + filename + "\")"); } else diff --git a/chuck/stepA_mal.ck b/chuck/stepA_mal.ck index 8845385c00..9906981e40 100644 --- a/chuck/stepA_mal.ck +++ b/chuck/stepA_mal.ck @@ -497,9 +497,9 @@ fun void main() } } -if( args.size() > 1 ) +if( args.size() > 0 ) { - args[1] => string filename; + args[0] => string filename; rep("(load-file \"" + filename + "\")"); } else From 1b349ccb6c9349041411e5b96acd20a7d4ef0640 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 6 Aug 2016 23:57:14 +0200 Subject: [PATCH 0025/2308] Add run script, fix argv bug --- chuck/run | 2 ++ chuck/step6_file.ck | 4 ++-- chuck/step7_quote.ck | 4 ++-- chuck/step8_macros.ck | 4 ++-- chuck/step9_try.ck | 4 ++-- chuck/stepA_mal.ck | 4 ++-- 6 files changed, 12 insertions(+), 10 deletions(-) create mode 100755 chuck/run diff --git a/chuck/run b/chuck/run new file mode 100755 index 0000000000..e90e12959c --- /dev/null +++ b/chuck/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec ./run_chuck.rb --silent $(dirname $0)/${STEP:-stepA_mal}.ck "${@}" diff --git a/chuck/step6_file.ck b/chuck/step6_file.ck index 6e401b02a6..0102daad5d 100644 --- a/chuck/step6_file.ck +++ b/chuck/step6_file.ck @@ -269,11 +269,11 @@ eval @=> (repl_env.get("swap!")$MalSubr).eval; fun MalObject[] MalArgv(string args[]) { - MalObject values[args.size()-1]; + MalObject values[0]; for( 1 => int i; i < args.size(); i++ ) { - MalString.create(args[i]) @=> values[i-1]; + values << MalString.create(args[i]); } return values; diff --git a/chuck/step7_quote.ck b/chuck/step7_quote.ck index 277e0e5a17..c89d91d604 100644 --- a/chuck/step7_quote.ck +++ b/chuck/step7_quote.ck @@ -324,11 +324,11 @@ eval @=> (repl_env.get("swap!")$MalSubr).eval; fun MalObject[] MalArgv(string args[]) { - MalObject values[args.size()-1]; + MalObject values[0]; for( 1 => int i; i < args.size(); i++ ) { - MalString.create(args[i]) @=> values[i-1]; + values << MalString.create(args[i]); } return values; diff --git a/chuck/step8_macros.ck b/chuck/step8_macros.ck index a01535453c..a9020ea681 100644 --- a/chuck/step8_macros.ck +++ b/chuck/step8_macros.ck @@ -395,11 +395,11 @@ eval @=> (repl_env.get("swap!")$MalSubr).eval; fun MalObject[] MalArgv(string args[]) { - MalObject values[args.size()-1]; + MalObject values[0]; for( 1 => int i; i < args.size(); i++ ) { - MalString.create(args[i]) @=> values[i-1]; + values << MalString.create(args[i]); } return values; diff --git a/chuck/step9_try.ck b/chuck/step9_try.ck index e57a67e948..a95cb5a935 100644 --- a/chuck/step9_try.ck +++ b/chuck/step9_try.ck @@ -412,11 +412,11 @@ eval @=> (repl_env.get("map")$MalSubr).eval; fun MalObject[] MalArgv(string args[]) { - MalObject values[args.size()-1]; + MalObject values[0]; for( 1 => int i; i < args.size(); i++ ) { - MalString.create(args[i]) @=> values[i-1]; + values << MalString.create(args[i]); } return values; diff --git a/chuck/stepA_mal.ck b/chuck/stepA_mal.ck index 9906981e40..59accd51b1 100644 --- a/chuck/stepA_mal.ck +++ b/chuck/stepA_mal.ck @@ -412,11 +412,11 @@ eval @=> (repl_env.get("map")$MalSubr).eval; fun MalObject[] MalArgv(string args[]) { - MalObject values[args.size()-1]; + MalObject values[0]; for( 1 => int i; i < args.size(); i++ ) { - MalString.create(args[i]) @=> values[i-1]; + values << MalString.create(args[i]); } return values; From b0837516f7de86d21658b286671654302e7cba6c Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 6 Aug 2016 23:57:38 +0200 Subject: [PATCH 0026/2308] Remove temp file --- chuck/types/subr/MalTimeMs.ck | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chuck/types/subr/MalTimeMs.ck b/chuck/types/subr/MalTimeMs.ck index 6c548d3e84..3a2b91afc1 100644 --- a/chuck/types/subr/MalTimeMs.ck +++ b/chuck/types/subr/MalTimeMs.ck @@ -11,6 +11,8 @@ public class MalTimeMs extends MalSubr f => int timestamp; f.close(); + Std.system("rm " + temp_file); + return MalInt.create(timestamp); } } From 90a93e413271eab882f12d42905ac6e97a25ba7f Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 8 Aug 2016 01:09:39 +0200 Subject: [PATCH 0027/2308] Move run_chuck.rb to run --- chuck/run | 23 +++++++++++++++++++++-- chuck/run_chuck.rb | 14 -------------- 2 files changed, 21 insertions(+), 16 deletions(-) delete mode 100755 chuck/run_chuck.rb diff --git a/chuck/run b/chuck/run index e90e12959c..556706262e 100755 --- a/chuck/run +++ b/chuck/run @@ -1,2 +1,21 @@ -#!/bin/bash -exec ./run_chuck.rb --silent $(dirname $0)/${STEP:-stepA_mal}.ck "${@}" +#!/usr/bin/env ruby + +cmdline = ['chuck', '--caution-to-the-wind', '--silent'] + +# HACK: makes `make MAL_IMPL=chuck "test^mal"` work +scriptpath = File.expand_path(File.dirname(__FILE__)) +Dir.chdir(scriptpath) + +scriptfile = (ENV['STEP'] || 'stepA_mal') + '.ck' + +script = File.readlines(scriptfile) +imports = script.grep(%r{^ *// *@import (.+)}) { $1 } +import_files = imports.flat_map { |i| Dir[i] } +cmdline += import_files +cmdline << scriptfile + +File.write('/tmp/chuck-cmdline', cmdline.join(' ')) +File.write('/tmp/chuck-args', ARGV.join(' ')) + +ENV['CHUCK_ARGS'] = ARGV.join("\a") +exec(*cmdline) diff --git a/chuck/run_chuck.rb b/chuck/run_chuck.rb deleted file mode 100755 index 82ed1eb487..0000000000 --- a/chuck/run_chuck.rb +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env ruby - -cmdline = ['chuck', '--caution-to-the-wind'] -cmdline << ARGV.shift if ARGV[0] == '--silent' - -scriptfile = ARGV[0] -script = File.readlines(scriptfile) -imports = script.grep(%r{^ *// *@import (.+)}) { $1 } -import_files = imports.flat_map { |i| Dir[i] } -cmdline += import_files -cmdline << scriptfile - -ENV['CHUCK_ARGS'] = ARGV.drop(1).join("\a") -exec(*cmdline) From 385a08eb7623b889a56927559d5b167cde101649 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 8 Aug 2016 01:19:05 +0200 Subject: [PATCH 0028/2308] Mention in README --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e4042342d..cded113b88 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 56 languages: +Mal is implemented in 57 languages: * Ada * GNU awk @@ -14,6 +14,7 @@ Mal is implemented in 56 languages: * C * C++ * C# +* ChucK * Clojure * CoffeeScript * Crystal @@ -191,6 +192,17 @@ make mono ./stepX_YYY.exe ``` +### ChucK + +*The ChucK implementation was created by [Vasilij Schneidermann](https://github.com/wasamasa)* + +The ChucK implementation has been tested with ChucK 1.3.5.2 on Arch +Linux. + +``` +cd chuck +./run +``` ### Clojure From c514d04ab4b227272c6ea3662eb31a565583d2f7 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 8 Aug 2016 21:32:30 +0200 Subject: [PATCH 0029/2308] Support backspace in readline implementation --- chuck/readline.ck | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/chuck/readline.ck b/chuck/readline.ck index 8aefe0d249..7f3881ac14 100644 --- a/chuck/readline.ck +++ b/chuck/readline.ck @@ -41,6 +41,15 @@ public class Readline { true => done; } + else if( repr == "DEL" && Std.getenv("TERM") != "dumb") + { + if( input.length() > 0) + { + chout <= "\033[1D\033[0K"; + chout.flush(); + input.substring(0, input.length()-1) => input; + } + } else { chout <= repr; From 2b52a2a5d4ad36751610fefc81353b226602775a Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 9 Aug 2016 19:25:33 +0200 Subject: [PATCH 0030/2308] Add Dockerfile fixes and change runner to bash Thanks to @kanaka --- Makefile | 4 ++-- chuck/Dockerfile | 32 ++++++++++++++++++++++++++++++++ chuck/run | 26 +++++--------------------- 3 files changed, 39 insertions(+), 23 deletions(-) create mode 100644 chuck/Dockerfile diff --git a/Makefile b/Makefile index 93e45bc75a..6404148a8a 100644 --- a/Makefile +++ b/Makefile @@ -221,7 +221,7 @@ get_build_prefix = $(if $(strip $(DOCKERIZE)),docker run -it --rm -u $(shell id # Returns a command prefix (docker command and environment variables) # necessary to launch the given impl and step get_run_prefix = $(strip $(if $(strip $(DOCKERIZE)),\ - docker run -e STEP=$($2) \ + docker run -e STEP=$($2) -e MAL_IMPL=$(MAL_IMPL) \ -it --rm -u $(shell id -u) \ -v $(dir $(abspath $(lastword $(MAKEFILE_LIST)))):/mal \ -w /mal/$(call actual_impl,$(1)) \ @@ -230,7 +230,7 @@ get_run_prefix = $(strip $(if $(strip $(DOCKERIZE)),\ $(foreach env,$(3),-e $(env)) \ $(call impl_to_image,$(call actual_impl,$(1))) \ ,\ - env STEP=$($2) \ + env STEP=$($2) MAL_IMPL=$(MAL_IMPL) \ $(if $(filter haxe,$(1)),HAXE_MODE=$(HAXE_MODE),) \ $(if $(filter factor,$(1)),FACTOR_ROOTS=$(FACTOR_ROOTS),) \ $(3))) diff --git a/chuck/Dockerfile b/chuck/Dockerfile new file mode 100644 index 0000000000..b7dc62385c --- /dev/null +++ b/chuck/Dockerfile @@ -0,0 +1,32 @@ +FROM ubuntu:vivid +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# Chuck +RUN apt-get -y install bison gcc g++ flex +RUN apt-get -y install libasound2-dev libsndfile1-dev +RUN cd /tmp && curl -O http://chuck.cs.princeton.edu/release/files/chuck-1.3.5.2.tgz \ + && tar xvzf /tmp/chuck-1.3.5.2.tgz && cd chuck-1.3.5.2/src \ + && make linux-alsa && make install \ + && rm -r /tmp/chuck-1.3.5.2* + +ENV HOME /mal diff --git a/chuck/run b/chuck/run index 556706262e..51d120c416 100755 --- a/chuck/run +++ b/chuck/run @@ -1,21 +1,5 @@ -#!/usr/bin/env ruby - -cmdline = ['chuck', '--caution-to-the-wind', '--silent'] - -# HACK: makes `make MAL_IMPL=chuck "test^mal"` work -scriptpath = File.expand_path(File.dirname(__FILE__)) -Dir.chdir(scriptpath) - -scriptfile = (ENV['STEP'] || 'stepA_mal') + '.ck' - -script = File.readlines(scriptfile) -imports = script.grep(%r{^ *// *@import (.+)}) { $1 } -import_files = imports.flat_map { |i| Dir[i] } -cmdline += import_files -cmdline << scriptfile - -File.write('/tmp/chuck-cmdline', cmdline.join(' ')) -File.write('/tmp/chuck-args', ARGV.join(' ')) - -ENV['CHUCK_ARGS'] = ARGV.join("\a") -exec(*cmdline) +#!/bin/bash +imports=$(grep "^ *// *@import" "$(dirname $0)/${STEP:-stepA_mal}.ck" | awk '{print $3}') +imports=$(for i in ${imports}; do ls $(dirname $0)/${i}; done) +old_IFS="${IFS}"; IFS=$'\a'; export CHUCK_ARGS="${*}"; IFS="${old_IFS}" +exec chuck --caution-to-the-wind --silent ${imports} $(dirname $0)/${STEP:-stepA_mal}.ck From bbff44bcc216cfa2b5166e75dcb4c7f2bf80ccac Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 9 Aug 2016 19:48:54 +0200 Subject: [PATCH 0031/2308] Add chuck to Travis builds --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 88525e7efa..b0c7e77c09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ matrix: - {env: IMPL=cpp, services: [docker]} - {env: IMPL=coffee, services: [docker]} - {env: IMPL=cs, services: [docker]} + - {env: IMPL=chuck, services: [docker]} - {env: IMPL=clojure, services: [docker]} - {env: IMPL=crystal, services: [docker]} - {env: IMPL=d, services: [docker]} From 77a1e7d9ba5612fe375f314eba26cd0b6188847a Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 9 Aug 2016 20:45:33 +0200 Subject: [PATCH 0032/2308] Add basic Makefile --- chuck/Makefile | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 chuck/Makefile diff --git a/chuck/Makefile b/chuck/Makefile new file mode 100644 index 0000000000..f851639feb --- /dev/null +++ b/chuck/Makefile @@ -0,0 +1,18 @@ +SOURCES_BASE = readline.ck reader.ck printer.ck \ + types/MalObject.ck types/MalSubr.ck \ + types/**/*.ck util/*.ck +SOURCES_LISP = env.ck core.ck stepA_mal.ck +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +all: + +clean: + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" + +.PHONY: all clean stats stats-lisp From d7d197f96e72c0053c55d1ecd64a8da78fd27c01 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 20 Aug 2016 15:09:19 -0500 Subject: [PATCH 0033/2308] PowerShell: steps 0-3 --- Makefile | 6 ++- powershell/env.psm1 | 51 +++++++++++++++++++++++ powershell/printer.psm1 | 22 ++++++++++ powershell/reader.psm1 | 70 ++++++++++++++++++++++++++++++++ powershell/run | 2 + powershell/step0_repl.ps1 | 8 ++++ powershell/step1_read_print.ps1 | 33 +++++++++++++++ powershell/step2_eval.ps1 | 55 +++++++++++++++++++++++++ powershell/step3_env.ps1 | 72 +++++++++++++++++++++++++++++++++ powershell/types.psm1 | 47 +++++++++++++++++++++ 10 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 powershell/env.psm1 create mode 100644 powershell/printer.psm1 create mode 100644 powershell/reader.psm1 create mode 100755 powershell/run create mode 100644 powershell/step0_repl.ps1 create mode 100644 powershell/step1_read_print.ps1 create mode 100644 powershell/step2_eval.ps1 create mode 100644 powershell/step3_env.ps1 create mode 100644 powershell/types.psm1 diff --git a/Makefile b/Makefile index 6404148a8a..c2d147e103 100644 --- a/Makefile +++ b/Makefile @@ -80,8 +80,9 @@ DOCKERIZE = IMPLS = ada awk bash c d chuck clojure coffee cpp crystal cs erlang elisp \ elixir es6 factor forth fsharp go groovy guile haskell haxe \ io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ - nim objc objpascal perl perl6 php plpgsql plsql ps python r \ - racket rpython ruby rust scala swift swift3 tcl vb vhdl vimscript + nim objc objpascal perl perl6 php plpgsql plsql powershell ps \ + python r racket rpython ruby rust scala swift swift3 tcl vb vhdl \ + vimscript step0 = step0_repl step1 = step1_read_print @@ -182,6 +183,7 @@ perl6_STEP_TO_PROG = perl6/$($(1)).pl php_STEP_TO_PROG = php/$($(1)).php plpgsql_STEP_TO_PROG = plpgsql/$($(1)).sql plsql_STEP_TO_PROG = plsql/$($(1)).sql +powershell_STEP_TO_PROG = powershell/$($(1)).ps1 ps_STEP_TO_PROG = ps/$($(1)).ps python_STEP_TO_PROG = python/$($(1)).py r_STEP_TO_PROG = r/$($(1)).r diff --git a/powershell/env.psm1 b/powershell/env.psm1 new file mode 100644 index 0000000000..1f687d6c8c --- /dev/null +++ b/powershell/env.psm1 @@ -0,0 +1,51 @@ +Import-Module $PSScriptRoot/types.psm1 + +Class Env { + [HashTable] $data + [Env] $outer + + Env() { + # Case-sensitive hash table + $this.data = New-Object System.Collections.HashTable + $this.outer = $null + } + + Env([Env] $out) { + # Case-sensitive hash table + $this.data = New-Object System.Collections.HashTable + $this.outer = $out + } + + [Object] set($key, $value) { + $this.data[$key.value] = $value + return $value + } + + [Env] find($key) { + if ($this.data.Contains($key.value)) { + return $this + } elseif ($this.outer -ne $null) { + return $this.outer.find($key) + } else { + return $null + } + } + + [Object] get($key) { + $e = $this.find($key) + if ($e -ne $null) { + return $e.data[$key.value] + } else { + throw "'$($key.value)' not found" + } + } +} + +function new-env { + [Env]::new() +} + +function new-env([Env] $out) { + [Env]::new($out) +} + diff --git a/powershell/printer.psm1 b/powershell/printer.psm1 new file mode 100644 index 0000000000..80a449912e --- /dev/null +++ b/powershell/printer.psm1 @@ -0,0 +1,22 @@ + +function pr_str($obj, $print_readably) { + if ($obj -eq $null) { + return "nil" + } + #Write-Host ("type:" + $obj.GetType().Name) + switch ($obj.GetType().Name) { + "String" { + return "`"$obj`"" + } + "List" { + $res = $obj.values | ForEach { (pr_str $_ $print_readably) } + return "(" + ($res -join " ") + ")" + } + "Symbol" { + return $obj.value + } + default { + return $obj.ToString() + } + } +} diff --git a/powershell/reader.psm1 b/powershell/reader.psm1 new file mode 100644 index 0000000000..6fcfdc9d78 --- /dev/null +++ b/powershell/reader.psm1 @@ -0,0 +1,70 @@ +Import-Module $PSScriptRoot/types.psm1 + +Class Reader { + [String[]] $tokens + [int] $pos + + Reader([String[]] $toks) { + $this.tokens = $toks + $this.pos = 0 + } + + [String] peek() { + return $this.tokens[$this.pos] + } + + [String] next() { + return $this.tokens[$this.pos++] + } +} + + +function tokenize { + $r = [regex]"[\s,]*(~@|[\[\]{}()'``~^@]|`"(?:\\.|[^\\`"])*`"|;.*|[^\s\[\]{}('`"``,;)]*)" + $r.Matches($args) | + Where-Object { $_.Groups.Item(1).Value.Length -gt 0 } | + Foreach-Object { $_.Groups.Item(1).Value } +} + +function read_atom([Reader] $rdr) { + $token = $rdr.next() + if ($token -match "^-?[0-9]+$") { + return [convert]::ToInt32($token, 10) + } elseif ($token -match "^`".*`"") { + return $token.Substring(1,$token.Length-2) + } else { + return new-symbol($token) + } +} + +function read_list([Reader] $rdr) { + $ast = new-list(@()) + $token = $rdr.next() + if ($token -ne '(') { + throw "expected '('" + } + while (($token = $rdr.peek()) -ne ')') { + if ($token -eq "") { + throw "expected ')', got EOF" + } + $form = read_form $rdr + $ast.push($form) + } + $token = $rdr.next() + return $ast +} + +function read_form([Reader] $rdr) { + $token = $rdr.peek() + switch ($token) { + ")" { throw "unexpected ')'" } + "(" { return read_list($rdr) } + default { return read_atom($rdr) } + } +} + +function read_str { + $toks = tokenize($args[0]) + if ($toks.Length -eq 0) { return } + read_form([Reader]::new($toks)) +} diff --git a/powershell/run b/powershell/run new file mode 100755 index 0000000000..4a52cc6ac3 --- /dev/null +++ b/powershell/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec powershell $(dirname $0)/${STEP:-stepA_mal}.ps1 "${@}" diff --git a/powershell/step0_repl.ps1 b/powershell/step0_repl.ps1 new file mode 100644 index 0000000000..e02f371f13 --- /dev/null +++ b/powershell/step0_repl.ps1 @@ -0,0 +1,8 @@ +while ($true) { + Write-Host "user> " -NoNewline + $line = [Console]::ReadLine() + if ($line -eq $null) { + break + } + "$line" +} diff --git a/powershell/step1_read_print.ps1 b/powershell/step1_read_print.ps1 new file mode 100644 index 0000000000..4b5bb13c6e --- /dev/null +++ b/powershell/step1_read_print.ps1 @@ -0,0 +1,33 @@ +$ErrorActionPreference = "Stop" + +Import-Module $PSScriptRoot/reader.psm1 +Import-Module $PSScriptRoot/printer.psm1 + +function READ([String] $str) { + return read_str($str) +} + +function EVAL($ast, $env) { + return $ast +} + +function PRINT($exp) { + return pr_str $exp $true +} + +function REPL([String] $str) { + return PRINT (EVAL (READ $str) @{}) +} + +while ($true) { + Write-Host "user> " -NoNewline + $line = [Console]::ReadLine() + if ($line -eq $null) { + break + } + try { + Write-Host (REPL($line)) + } catch { + Write-Host "Exception: $($_.Exception.Message)" + } +} diff --git a/powershell/step2_eval.ps1 b/powershell/step2_eval.ps1 new file mode 100644 index 0000000000..77674f026e --- /dev/null +++ b/powershell/step2_eval.ps1 @@ -0,0 +1,55 @@ +$ErrorActionPreference = "Stop" + +Import-Module $PSScriptRoot/types.psm1 +Import-Module $PSScriptRoot/reader.psm1 +Import-Module $PSScriptRoot/printer.psm1 + +function READ([String] $str) { + return read_str($str) +} + +function eval_ast($ast, $env) { + switch ($ast.GetType().Name) { + "Symbol" { return $env[$ast.value] } + "List" { return new-list($ast.values | ForEach { EVAL $_ $env } ) } + default { return $ast } + } +} + +function EVAL($ast, $env) { + if (-not (list? $ast)) { + return (eval_ast $ast $env) + } + if (empty? $ast) { return $ast } + + $el = (eval_ast $ast $env).values + $f, $params = $el[0], $el[1..$el.Length] + return &$f @params +} + +function PRINT($exp) { + return pr_str $exp $true +} + +$repl_env = @{ + "+" = { param($a, $b); $a + $b }; + "-" = { param($a, $b); $a - $b }; + "*" = { param($a, $b); $a * $b }; + "/" = { param($a, $b); $a / $b }} + +function REPL([String] $str) { + return PRINT (EVAL (READ $str) $repl_env) +} + +while ($true) { + Write-Host "user> " -NoNewline + $line = [Console]::ReadLine() + if ($line -eq $null) { + break + } + try { + Write-Host (REPL($line)) + } catch { + Write-Host "Exception: $($_.Exception.Message)" + } +} diff --git a/powershell/step3_env.ps1 b/powershell/step3_env.ps1 new file mode 100644 index 0000000000..481785fff3 --- /dev/null +++ b/powershell/step3_env.ps1 @@ -0,0 +1,72 @@ +$ErrorActionPreference = "Stop" + +Import-Module $PSScriptRoot/types.psm1 +Import-Module $PSScriptRoot/reader.psm1 +Import-Module $PSScriptRoot/printer.psm1 +Import-Module $PSScriptRoot/env.psm1 + +function READ([String] $str) { + return read_str($str) +} + +function eval_ast($ast, $env) { + switch ($ast.GetType().Name) { + "Symbol" { return $env.get($ast) } + "List" { return new-list($ast.values | ForEach { EVAL $_ $env } ) } + default { return $ast } + } +} + +function EVAL($ast, $env) { + #Write-Host "EVAL $(pr_str $ast $true)" + if (-not (list? $ast)) { + return (eval_ast $ast $env) + } + if (empty? $ast) { return $ast } + + $a0, $a1, $a2 = $ast.nth(0), $ast.nth(1), $ast.nth(2) + switch ($a0.value) { + "def!" { + return $env.set($a1, (EVAL $a2 $env)) + } + "let*" { + $let_env = new-env $env + for ($i=0; $i -lt $a1.values.Length; $i+=2) { + $_ = $let_env.set($a1.nth($i), (EVAL $a1.nth(($i+1)) $let_env)) + } + return EVAL $a2 $let_env + } + default { + $el = (eval_ast $ast $env) + $f, $params = $el.first(), $el.rest() + return &$f @params + } + } +} + +function PRINT($exp) { + return pr_str $exp $true +} + +$repl_env = new-env +$_ = $repl_env.set((new-symbol "+"), { param($a, $b); $a + $b }) +$_ = $repl_env.set((new-symbol "-"), { param($a, $b); $a - $b }) +$_ = $repl_env.set((new-symbol "*"), { param($a, $b); $a * $b }) +$_ = $repl_env.set((new-symbol "/"), { param($a, $b); $a / $b }) + +function REPL([String] $str) { + return PRINT (EVAL (READ $str) $repl_env) +} + +while ($true) { + Write-Host "user> " -NoNewline + $line = [Console]::ReadLine() + if ($line -eq $null) { + break + } + try { + Write-Host (REPL($line)) + } catch { + Write-Host "Exception: $($_.Exception.Message)" + } +} diff --git a/powershell/types.psm1 b/powershell/types.psm1 new file mode 100644 index 0000000000..e300c4f622 --- /dev/null +++ b/powershell/types.psm1 @@ -0,0 +1,47 @@ +Class Symbol { + [String] $value + + Symbol([String] $val) { + $this.value = $val + } +} + +function new-symbol([string] $val) { + [Symbol]::new($val) +} + +Class List { + [Object[]] $values + + List([Object[]] $vals) { + $this.values = $vals + } + + [void] push([Object] $val) { + $this.values += $val + } + + [Object] first() { + return $this.values[0] + } + + [Object[]] rest() { + return $this.values[1..($this.values.Length-1)] + } + + [Object] nth([int] $idx) { + return $this.values[$idx] + } +} + +function new-list([Object[]] $vals) { + [List]::new($vals) +} + +function list?($obj) { + $obj -is [List] +} + +function empty?($obj) { + $obj.values.Count -eq 0 +} From f6146aef75f71a134ffa9aac44b4aff7c05baa70 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sun, 21 Aug 2016 17:14:01 -0500 Subject: [PATCH 0034/2308] PowerShell: steps 4-A, optional, and self-hosting --- Makefile | 3 + powershell/Dockerfile | 36 ++++ powershell/core.psm1 | 166 ++++++++++++++++++ powershell/env.psm1 | 32 ++-- powershell/printer.psm1 | 43 ++++- powershell/reader.psm1 | 83 +++++++-- powershell/step1_read_print.ps1 | 8 +- powershell/step2_eval.ps1 | 40 +++-- powershell/step3_env.ps1 | 32 ++-- powershell/step4_if_fn_do.ps1 | 109 ++++++++++++ powershell/step5_tco.ps1 | 122 +++++++++++++ powershell/step6_file.ps1 | 131 ++++++++++++++ powershell/step7_quote.ps1 | 162 +++++++++++++++++ powershell/step8_macros.ps1 | 193 +++++++++++++++++++++ powershell/step9_try.ps1 | 213 +++++++++++++++++++++++ powershell/stepA_mal.ps1 | 217 +++++++++++++++++++++++ powershell/types.psm1 | 299 +++++++++++++++++++++++++++++++- 17 files changed, 1829 insertions(+), 60 deletions(-) create mode 100644 powershell/Dockerfile create mode 100644 powershell/core.psm1 create mode 100644 powershell/step4_if_fn_do.ps1 create mode 100644 powershell/step5_tco.ps1 create mode 100644 powershell/step6_file.ps1 create mode 100644 powershell/step7_quote.ps1 create mode 100644 powershell/step8_macros.ps1 create mode 100644 powershell/step9_try.ps1 create mode 100644 powershell/stepA_mal.ps1 diff --git a/Makefile b/Makefile index c2d147e103..e208d1405f 100644 --- a/Makefile +++ b/Makefile @@ -115,6 +115,7 @@ test_EXCLUDES += test^mal^step5 # host impl dependent test_EXCLUDES += test^matlab^step5 # never completes at 10,000 test_EXCLUDES += test^plpgsql^step5 # too slow for 10,000 test_EXCLUDES += test^plsql^step5 # too slow for 10,000 +test_EXCLUDES += test^powershell^step5 # too slow for 10,000 perf_EXCLUDES = mal # TODO: fix this @@ -249,6 +250,8 @@ get_argvtest_cmd = $(call get_run_prefix,$(1),$(2)) ../run_argv_test.sh vimscript_TEST_OPTS = --test-timeout 30 ifeq ($(MAL_IMPL),vimscript) mal_TEST_OPTS = --start-timeout 60 --test-timeout 180 +else ifeq ($(MAL_IMPL),powershell) +mal_TEST_OPTS = --start-timeout 60 --test-timeout 180 endif # Derived lists diff --git a/powershell/Dockerfile b/powershell/Dockerfile new file mode 100644 index 0000000000..f4649a6172 --- /dev/null +++ b/powershell/Dockerfile @@ -0,0 +1,36 @@ +FROM ubuntu:vivid +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# Nothing additional needed for python +RUN apt-get -y install libunwind8 libicu52 +#RUN apt-get -y install libunwind8 libicu55 + +# For dist packaging +RUN curl -L -O https://github.com/PowerShell/PowerShell/releases/download/v6.0.0-alpha.9/powershell_6.0.0-alpha.9-1ubuntu1.14.04.1_amd64.deb && \ + dpkg -i powershell_6.0.0-alpha.9-1ubuntu1.14.04.1_amd64.deb && \ + rm powershell_6.0.0-alpha.9-1ubuntu1.14.04.1_amd64.deb +#RUN curl -L -O https://github.com/PowerShell/PowerShell/releases/download/v6.0.0-alpha.9/powershell_6.0.0-alpha.9-1ubuntu1.16.04.1_amd64.deb && \ +# dpkg -i powershell_6.0.0-alpha.9-1ubuntu1.16.04.1_amd64.deb && \ +# rm powershell_6.0.0-alpha.9-1ubuntu1.16.04.1_amd64.deb + +ENV HOME=/mal diff --git a/powershell/core.psm1 b/powershell/core.psm1 new file mode 100644 index 0000000000..f95464cffa --- /dev/null +++ b/powershell/core.psm1 @@ -0,0 +1,166 @@ +function time_ms { + $ms = [double]::Parse((Get-Date (get-date).ToUniversalTime() -UFormat %s)) + [int64] ($ms * 1000) +} + +function get($hm, $key) { + if ($hm -eq $null) { + $null + } else { + $hm.values.Item($key) + } +} + +function concat { + $res = @() + foreach($a in $args) { + $res = $res + $a.values + } + new-list $res +} + +function nth($lst, $idx) { + if ($idx -ge $lst.values.Count) { + throw "nth: index out of range" + } + $lst.nth($idx) +} + + +function do_map($f, $l) { + if (malfunc?($f)) { + $f = $f.fn + } + new-list ($l.values | foreach { &$f $_ }) +} + +function do_apply($f) { + if (malfunc?($f)) { + $f = $f.fn + } + if ($args.Count -gt 1) { + $fargs = $args[0..($args.Count-2)] + $args[-1].values + } else { + $fargs = $args[$args.Count-1].values + } + &$f @fargs +} + +function conj($lst) { + if (list? $lst) { + [Array]::Reverse($args) + return new-list ($args + $lst.values) + } else { + return new-vector ($lst.values + $args) + } +} + +function seq($obj) { + if ($obj -eq $null) { + return $null + } elseif (list? $obj) { + if ($obj.values.Count -gt 0) { + return $obj + } else { + return $null + } + } elseif (vector? $obj) { + if ($obj.values.Count -gt 0) { + return new-list $obj.values + } else { + return $null + } + } elseif (string? $obj) { + if ($obj.Length -gt 0) { + return new-list ($obj -split '')[1..$obj.Length] + } else { + return $null + } + return new-list $obj + } else { + throw "seq: called on non-sequence" + } +} + +function swap_BANG($a, $f) { + if (malfunc?($f)) { + $f = $f.fn + } + $fargs = @($a.value) + $args + if ($fargs.Count -eq 0) { + $a.value = &$f + } else { + $a.value = &$f @fargs + } + $a.value +} + + +$core_ns = @{ + "=" = { param($a, $b); equal? $a $b }; + "throw" = Get-Command mal_throw; + + "nil?" = { param($a); $a -eq $null }; + "true?" = { param($a); $a -eq $true }; + "false?" = { param($a); $a -eq $false }; + "string?" = { param($a); string? $a }; + "symbol" = Get-Command new-symbol; + "symbol?" = { param($a); symbol? $a }; + "keyword" = Get-Command new-keyword; + "keyword?" = { param($a); keyword? $a }; + + "pr-str" = { pr_seq $args $true " " }; + "str" = { pr_seq $args $false "" }; + "prn" = { Write-Host (pr_seq $args $true " ") }; + "println" = { Write-Host (pr_seq $args $false " ") }; + "read-string" = { read_str $args[0] }; + "readline" = { Write-Host $args[0] -NoNewline; [Console]::Readline() }; + "slurp" = { Get-Content -Path $args[0] -Raw }; + + "<" = { param($a, $b); $a -lt $b }; + "<=" = { param($a, $b); $a -le $b }; + ">" = { param($a, $b); $a -gt $b }; + ">=" = { param($a, $b); $a -ge $b }; + "+" = { param($a, $b); $a + $b }; + "-" = { param($a, $b); $a - $b }; + "*" = { param($a, $b); $a * $b }; + "/" = { param($a, $b); $a / $b }; + "time-ms" = Get-Command time_ms; + + "list" = { new-list $args }; + "list?" = Get-Command list?; + "vector" = { new-vector $args }; + "vector?" = Get-Command vector?; + "hash-map" = { new-hashmap $args }; + "map?" = Get-Command hashmap?; + "assoc" = { param($a); assoc_BANG $a.copy() $args }; + "dissoc" = { param($a); dissoc_BANG $a.copy() $args }; + "get" = { param($a,$b); get $a $b }; + "contains?" = { param($a,$b); $a.values.Contains($b) }; + "keys" = Get-Command keys; + "vals" = Get-Command vals; + + "sequential?" = Get-Command sequential?; + "cons" = { param($a, $b); new-list (@($a) + $b.values) }; + "concat" = Get-Command concat; + "nth" = Get-Command nth; + "first" = { param($a); if ($a -eq $null) { $null } else { $a.first() } }; + "rest" = { param($a); if ($a -eq $null) { new-list @() } else { $a.rest() } }; + "empty?" = { param($a); $a -eq $null -or $a.values.Count -eq 0 }; + "count" = { param($a); $a.values.Count }; + "apply" = Get-Command do_apply; + "map" = Get-Command do_map; + + "conj" = Get-Command conj; + "seq" = Get-Command seq; + + "meta" = { param($a); $a.meta }; + "with-meta" = { param($a, $b); $c = $a.copy(); $c.meta = $b; $c }; + "atom" = { param($a); new-atom($a) }; + "atom?" = { param($a); atom?($a) }; + "deref" = { param($a); $a.value }; + "reset!" = { param($a, $b); $a.value = $b; $b }; + "swap!" = Get-Command swap_BANG; +} + +Export-ModuleMember -Variable core_ns diff --git a/powershell/env.psm1 b/powershell/env.psm1 index 1f687d6c8c..deb10fd563 100644 --- a/powershell/env.psm1 +++ b/powershell/env.psm1 @@ -4,16 +4,26 @@ Class Env { [HashTable] $data [Env] $outer - Env() { - # Case-sensitive hash table - $this.data = New-Object System.Collections.HashTable - $this.outer = $null - } - - Env([Env] $out) { + Env([Env] $out, $binds, $exprs) { # Case-sensitive hash table $this.data = New-Object System.Collections.HashTable $this.outer = $out + + if ($binds -ne $null) { + for ($i = 0; $i -lt $binds.Length; $i++) { + if ($binds[$i].value -eq "&") { + if ($exprs.Length -gt $i) { + $rest = $exprs[$i..($exprs.Length-1)] + } else { + $rest = @() + } + $this.data[$binds[($i+1)].value] = new-list($rest) + break + } else { + $this.data[$binds[$i].value] = $exprs[$i] + } + } + } } [Object] set($key, $value) { @@ -41,11 +51,7 @@ Class Env { } } -function new-env { - [Env]::new() -} - -function new-env([Env] $out) { - [Env]::new($out) +function new-env([Env] $out, $binds, $exprs) { + [Env]::new($out, $binds, $exprs) } diff --git a/powershell/printer.psm1 b/powershell/printer.psm1 index 80a449912e..1c63a253ba 100644 --- a/powershell/printer.psm1 +++ b/powershell/printer.psm1 @@ -1,22 +1,59 @@ -function pr_str($obj, $print_readably) { +function pr_str { + param($obj, $print_readably = $true) if ($obj -eq $null) { return "nil" } - #Write-Host ("type:" + $obj.GetType().Name) + switch ($obj.GetType().Name) { "String" { - return "`"$obj`"" + if ($obj[0] -eq "$([char]0x29e)") { + return ":$($obj.substring(1))" + } elseif ($print_readably) { + $s = $obj -replace "\\", "\\" + $s = $s -replace "`"", "\`"" + $s = $s -replace "`n", "\n" + return "`"$s`"" + } else { + return "$obj" + } + } + "Vector" { + $res = $obj.values | ForEach { (pr_str $_ $print_readably) } + return "[" + ($res -join " ") + "]" } "List" { $res = $obj.values | ForEach { (pr_str $_ $print_readably) } return "(" + ($res -join " ") + ")" } + "HashMap" { + $res = @() + foreach ($k in $obj.values.Keys) { + $res += pr_str $k $print_readably + $res += pr_str $obj.values[$k] $print_readably + } + return "{" + ($res -join " ") + "}" + } "Symbol" { return $obj.value } + "Boolean" { + return $obj.ToString().ToLower() + } + "Atom" { + return "(atom $(pr_str $obj.value $print_readably))" + } + "PSCustomObject" { + return "(fn* $(pr_str (new-list $obj.params) $print_readably) $(pr_str $obj.ast $print_readably))" + } default { return $obj.ToString() } } } + +function pr_seq { + param($seq, $print_readably, $sep) + $lst = foreach($a in $seq) { pr_str $a $print_readably } + $lst -join $sep +} diff --git a/powershell/reader.psm1 b/powershell/reader.psm1 index 6fcfdc9d78..88712e0e25 100644 --- a/powershell/reader.psm1 +++ b/powershell/reader.psm1 @@ -22,7 +22,8 @@ Class Reader { function tokenize { $r = [regex]"[\s,]*(~@|[\[\]{}()'``~^@]|`"(?:\\.|[^\\`"])*`"|;.*|[^\s\[\]{}('`"``,;)]*)" $r.Matches($args) | - Where-Object { $_.Groups.Item(1).Value.Length -gt 0 } | + Where-Object { $_.Groups.Item(1).Value.Length -gt 0 -and + $_.Groups.Item(1).Value[0] -ne ";" } | Foreach-Object { $_.Groups.Item(1).Value } } @@ -31,40 +32,96 @@ function read_atom([Reader] $rdr) { if ($token -match "^-?[0-9]+$") { return [convert]::ToInt32($token, 10) } elseif ($token -match "^`".*`"") { - return $token.Substring(1,$token.Length-2) + $s = $token.Substring(1,$token.Length-2) + $s = $s -replace "\\`"", "`"" + $s = $s -replace "\\n", "`n" + $s = $s -replace "\\\\", "\" + return $s + } elseif ($token -match ":.*") { + return "$([char]0x29e)$($token.substring(1))" + } elseif ($token -eq "true") { + return $true + } elseif ($token -eq "false") { + return $false + } elseif ($token -eq "nil") { + return $null } else { return new-symbol($token) } } -function read_list([Reader] $rdr) { - $ast = new-list(@()) +function read_seq([Reader] $rdr, $start, $end) { + $seq = @() $token = $rdr.next() - if ($token -ne '(') { - throw "expected '('" + if ($token -ne $start) { + throw "expected '$start'" } - while (($token = $rdr.peek()) -ne ')') { + while (($token = $rdr.peek()) -ne $end) { if ($token -eq "") { - throw "expected ')', got EOF" + throw "expected '$end', got EOF" } $form = read_form $rdr - $ast.push($form) + $seq += $form } $token = $rdr.next() - return $ast + return ,$seq +} + +function read_list([Reader] $rdr) { + return new-list (read_seq $rdr "(" ")") +} + +function read_vector([Reader] $rdr) { + return new-vector (read_seq $rdr "[" "]") +} + +function read_hash_map([Reader] $rdr) { + return new-hashmap (read_seq $rdr "{" "}") } function read_form([Reader] $rdr) { $token = $rdr.peek() switch ($token) { + # reader macros/transforms + "'" { $_ = $rdr.next(); + return new-list @((new-symbol "quote"), + (read_form $rdr)) } + "``" { $_ = $rdr.next(); + return new-list @((new-symbol "quasiquote"), + (read_form $rdr)) } + "~" { $_ = $rdr.next(); + return (new-list @((new-symbol "unquote"), + (read_form $rdr))) } + "~@" { $_ = $rdr.next(); + return (new-list @((new-symbol "splice-unquote"), + (read_form $rdr))) } + "^" { $_ = $rdr.next(); + $meta = read_form $rdr + return (new-list @((new-symbol "with-meta"), + (read_form $rdr), + $meta)) } + "@" { $_ = $rdr.next(); + return (new-list @((new-symbol "deref"), + (read_form $rdr))) } + + # list ")" { throw "unexpected ')'" } - "(" { return read_list($rdr) } - default { return read_atom($rdr) } + "(" { return read_list $rdr } + + # vector + "]" { throw "unexpected ']'" } + "[" { return read_vector $rdr } + + # hashmap + "}" { throw "unexpected '}'" } + "{" { return read_hash_map $rdr } + + default { return read_atom $rdr } } } function read_str { $toks = tokenize($args[0]) - if ($toks.Length -eq 0) { return } + if ($toks.Length -eq 0) { return $null } read_form([Reader]::new($toks)) } diff --git a/powershell/step1_read_print.ps1 b/powershell/step1_read_print.ps1 index 4b5bb13c6e..b34ab774a1 100644 --- a/powershell/step1_read_print.ps1 +++ b/powershell/step1_read_print.ps1 @@ -3,19 +3,23 @@ $ErrorActionPreference = "Stop" Import-Module $PSScriptRoot/reader.psm1 Import-Module $PSScriptRoot/printer.psm1 +# READ function READ([String] $str) { return read_str($str) } +# EVAL function EVAL($ast, $env) { return $ast } +# PRINT function PRINT($exp) { return pr_str $exp $true } -function REPL([String] $str) { +# REPL +function REP([String] $str) { return PRINT (EVAL (READ $str) @{}) } @@ -26,7 +30,7 @@ while ($true) { break } try { - Write-Host (REPL($line)) + Write-Host (REP($line)) } catch { Write-Host "Exception: $($_.Exception.Message)" } diff --git a/powershell/step2_eval.ps1 b/powershell/step2_eval.ps1 index 77674f026e..71db91499a 100644 --- a/powershell/step2_eval.ps1 +++ b/powershell/step2_eval.ps1 @@ -4,40 +4,54 @@ Import-Module $PSScriptRoot/types.psm1 Import-Module $PSScriptRoot/reader.psm1 Import-Module $PSScriptRoot/printer.psm1 +# READ function READ([String] $str) { return read_str($str) } +# EVAL function eval_ast($ast, $env) { switch ($ast.GetType().Name) { - "Symbol" { return $env[$ast.value] } - "List" { return new-list($ast.values | ForEach { EVAL $_ $env } ) } - default { return $ast } + "Symbol" { return $env[$ast.value] } + "List" { return new-list ($ast.values | ForEach { EVAL $_ $env }) } + "Vector" { return new-vector ($ast.values | ForEach { EVAL $_ $env }) } + "HashMap" { + $hm = new-hashmap @() + foreach ($k in $ast.values.Keys) { + $hm.values[$k] = EVAL $ast.values[$k] $env + } + return $hm + } + default { return $ast } } } function EVAL($ast, $env) { + #Write-Host "EVAL $(pr_str $ast)" if (-not (list? $ast)) { return (eval_ast $ast $env) } if (empty? $ast) { return $ast } - $el = (eval_ast $ast $env).values - $f, $params = $el[0], $el[1..$el.Length] - return &$f @params + $el = (eval_ast $ast $env) + $f, $fargs = $el.first(), $el.rest().values + return &$f @fargs } +# PRINT function PRINT($exp) { return pr_str $exp $true } -$repl_env = @{ - "+" = { param($a, $b); $a + $b }; - "-" = { param($a, $b); $a - $b }; - "*" = { param($a, $b); $a * $b }; - "/" = { param($a, $b); $a / $b }} +# REPL +# Case sensitive hashtable +$repl_env = New-Object System.Collections.HashTable +$repl_env["+"] = { param($a, $b); $a + $b } +$repl_env["-"] = { param($a, $b); $a - $b } +$repl_env["*"] = { param($a, $b); $a * $b } +$repl_env["/"] = { param($a, $b); $a / $b } -function REPL([String] $str) { +function REP([String] $str) { return PRINT (EVAL (READ $str) $repl_env) } @@ -48,7 +62,7 @@ while ($true) { break } try { - Write-Host (REPL($line)) + Write-Host (REP($line)) } catch { Write-Host "Exception: $($_.Exception.Message)" } diff --git a/powershell/step3_env.ps1 b/powershell/step3_env.ps1 index 481785fff3..6b045edb36 100644 --- a/powershell/step3_env.ps1 +++ b/powershell/step3_env.ps1 @@ -5,56 +5,68 @@ Import-Module $PSScriptRoot/reader.psm1 Import-Module $PSScriptRoot/printer.psm1 Import-Module $PSScriptRoot/env.psm1 +# READ function READ([String] $str) { return read_str($str) } +# EVAL function eval_ast($ast, $env) { switch ($ast.GetType().Name) { - "Symbol" { return $env.get($ast) } - "List" { return new-list($ast.values | ForEach { EVAL $_ $env } ) } - default { return $ast } + "Symbol" { return $env.get($ast) } + "List" { return new-list ($ast.values | ForEach { EVAL $_ $env }) } + "Vector" { return new-vector ($ast.values | ForEach { EVAL $_ $env }) } + "HashMap" { + $hm = new-hashmap @() + foreach ($k in $ast.values.Keys) { + $hm.values[$k] = EVAL $ast.values[$k] $env + } + return $hm + } + default { return $ast } } } function EVAL($ast, $env) { - #Write-Host "EVAL $(pr_str $ast $true)" + #Write-Host "EVAL $(pr_str $ast)" if (-not (list? $ast)) { return (eval_ast $ast $env) } if (empty? $ast) { return $ast } $a0, $a1, $a2 = $ast.nth(0), $ast.nth(1), $ast.nth(2) - switch ($a0.value) { + switch -casesensitive ($a0.value) { "def!" { return $env.set($a1, (EVAL $a2 $env)) } "let*" { $let_env = new-env $env - for ($i=0; $i -lt $a1.values.Length; $i+=2) { + for ($i=0; $i -lt $a1.values.Count; $i+=2) { $_ = $let_env.set($a1.nth($i), (EVAL $a1.nth(($i+1)) $let_env)) } return EVAL $a2 $let_env } default { $el = (eval_ast $ast $env) - $f, $params = $el.first(), $el.rest() - return &$f @params + $f, $fargs = $el.first(), $el.rest().values + return &$f @fargs } } } +# PRINT function PRINT($exp) { return pr_str $exp $true } +# REPL $repl_env = new-env $_ = $repl_env.set((new-symbol "+"), { param($a, $b); $a + $b }) $_ = $repl_env.set((new-symbol "-"), { param($a, $b); $a - $b }) $_ = $repl_env.set((new-symbol "*"), { param($a, $b); $a * $b }) $_ = $repl_env.set((new-symbol "/"), { param($a, $b); $a / $b }) -function REPL([String] $str) { +function REP([String] $str) { return PRINT (EVAL (READ $str) $repl_env) } @@ -65,7 +77,7 @@ while ($true) { break } try { - Write-Host (REPL($line)) + Write-Host (REP($line)) } catch { Write-Host "Exception: $($_.Exception.Message)" } diff --git a/powershell/step4_if_fn_do.ps1 b/powershell/step4_if_fn_do.ps1 new file mode 100644 index 0000000000..e21e4f7381 --- /dev/null +++ b/powershell/step4_if_fn_do.ps1 @@ -0,0 +1,109 @@ +$ErrorActionPreference = "Stop" + +Import-Module $PSScriptRoot/types.psm1 +Import-Module $PSScriptRoot/reader.psm1 +Import-Module $PSScriptRoot/printer.psm1 +Import-Module $PSScriptRoot/env.psm1 +Import-Module $PSScriptRoot/core.psm1 + +# READ +function READ([String] $str) { + return read_str($str) +} + +# EVAL +function eval_ast($ast, $env) { + if ($ast -eq $null) { return $ast } + switch ($ast.GetType().Name) { + "Symbol" { return $env.get($ast) } + "List" { return new-list ($ast.values | ForEach { EVAL $_ $env }) } + "Vector" { return new-vector ($ast.values | ForEach { EVAL $_ $env }) } + "HashMap" { + $hm = new-hashmap @() + foreach ($k in $ast.values.Keys) { + $hm.values[$k] = EVAL $ast.values[$k] $env + } + return $hm + } + default { return $ast } + } +} + +function EVAL($ast, $env) { + #Write-Host "EVAL $(pr_str $ast)" + if (-not (list? $ast)) { + return (eval_ast $ast $env) + } + if (empty? $ast) { return $ast } + + $a0, $a1, $a2 = $ast.nth(0), $ast.nth(1), $ast.nth(2) + switch -casesensitive ($a0.value) { + "def!" { + return $env.set($a1, (EVAL $a2 $env)) + } + "let*" { + $let_env = new-env $env + for ($i=0; $i -lt $a1.values.Count; $i+=2) { + $_ = $let_env.set($a1.nth($i), (EVAL $a1.nth(($i+1)) $let_env)) + } + return EVAL $a2 $let_env + } + "do" { + return (eval_ast $ast.rest() $env).last() + } + "if" { + $cond = (EVAL $a1 $env) + if ($cond -eq $null -or + ($cond -is [Boolean] -and $cond -eq $false)) { + return (EVAL $ast.nth(3) $env) + } else { + return (EVAL $a2 $env) + } + } + "fn*" { + # Save EVAL into a variable that will get closed over + $feval = Get-Command EVAL + return { + return (&$feval $a2 (new-env $env $a1.values $args)) + }.GetNewClosure() + } + default { + $el = (eval_ast $ast $env) + $f, $fargs = $el.first(), $el.rest().values + return &$f @fargs + } + } +} + +# PRINT +function PRINT($exp) { + return pr_str $exp $true +} + +# REPL +$repl_env = new-env + +function REP([String] $str) { + return PRINT (EVAL (READ $str) $repl_env) +} + +# core.EXT: defined using PowerShell +foreach ($kv in $core_ns.GetEnumerator()) { + $_ = $repl_env.set((new-symbol $kv.Key), $kv.Value) +} + +# core.mal: defined using the language itself +$_ = REP('(def! not (fn* (a) (if a false true)))') + +while ($true) { + Write-Host "user> " -NoNewline + $line = [Console]::ReadLine() + if ($line -eq $null) { + break + } + try { + Write-Host (REP($line)) + } catch { + Write-Host "Exception: $($_.Exception.Message)" + } +} diff --git a/powershell/step5_tco.ps1 b/powershell/step5_tco.ps1 new file mode 100644 index 0000000000..476254955a --- /dev/null +++ b/powershell/step5_tco.ps1 @@ -0,0 +1,122 @@ +$ErrorActionPreference = "Stop" + +Import-Module $PSScriptRoot/types.psm1 +Import-Module $PSScriptRoot/reader.psm1 +Import-Module $PSScriptRoot/printer.psm1 +Import-Module $PSScriptRoot/env.psm1 +Import-Module $PSScriptRoot/core.psm1 + +# READ +function READ([String] $str) { + return read_str($str) +} + +# EVAL +function eval_ast($ast, $env) { + if ($ast -eq $null) { return $ast } + switch ($ast.GetType().Name) { + "Symbol" { return $env.get($ast) } + "List" { return new-list ($ast.values | ForEach { EVAL $_ $env }) } + "Vector" { return new-vector ($ast.values | ForEach { EVAL $_ $env }) } + "HashMap" { + $hm = new-hashmap @() + foreach ($k in $ast.values.Keys) { + $hm.values[$k] = EVAL $ast.values[$k] $env + } + return $hm + } + default { return $ast } + } +} + +function EVAL($ast, $env) { + while ($true) { + #Write-Host "EVAL $(pr_str $ast)" + if (-not (list? $ast)) { + return (eval_ast $ast $env) + } + if (empty? $ast) { return $ast } + + $a0, $a1, $a2 = $ast.nth(0), $ast.nth(1), $ast.nth(2) + switch -casesensitive ($a0.value) { + "def!" { + return $env.set($a1, (EVAL $a2 $env)) + } + "let*" { + $let_env = new-env $env + for ($i=0; $i -lt $a1.values.Count; $i+=2) { + $_ = $let_env.set($a1.nth($i), (EVAL $a1.nth(($i+1)) $let_env)) + } + $env = $let_env + $ast = $a2 # TCO + } + "do" { + if ($ast.values.Count -gt 2) { + $middle = new-list $ast.values[1..($ast.values.Count-2)] + $_ = eval_ast $middle $env + } + $ast = $ast.last() # TCO + } + "if" { + $cond = (EVAL $a1 $env) + if ($cond -eq $null -or + ($cond -is [Boolean] -and $cond -eq $false)) { + $ast = $ast.nth(3) # TCO + } else { + $ast = $a2 # TCO + } + } + "fn*" { + # Save EVAL into a variable that will get closed over + $feval = Get-Command EVAL + $fn = { + return (&$feval $a2 (new-env $env $a1.values $args)) + }.GetNewClosure() + return new-malfunc $a2 $a1.values $env $fn + } + default { + $el = (eval_ast $ast $env) + $f, $fargs = $el.first(), $el.rest().values + if (malfunc? $f) { + $env = (new-env $f.env $f.params $fargs) + $ast = $f.ast # TCO + } else { + return &$f @fargs + } + } + } + } +} + +# PRINT +function PRINT($exp) { + return pr_str $exp $true +} + +# REPL +$repl_env = new-env + +function REP([String] $str) { + return PRINT (EVAL (READ $str) $repl_env) +} + +# core.EXT: defined using PowerShell +foreach ($kv in $core_ns.GetEnumerator()) { + $_ = $repl_env.set((new-symbol $kv.Key), $kv.Value) +} + +# core.mal: defined using the language itself +$_ = REP('(def! not (fn* (a) (if a false true)))') + +while ($true) { + Write-Host "user> " -NoNewline + $line = [Console]::ReadLine() + if ($line -eq $null) { + break + } + try { + Write-Host (REP($line)) + } catch { + Write-Host "Exception: $($_.Exception.Message)" + } +} diff --git a/powershell/step6_file.ps1 b/powershell/step6_file.ps1 new file mode 100644 index 0000000000..9223ad47c6 --- /dev/null +++ b/powershell/step6_file.ps1 @@ -0,0 +1,131 @@ +$ErrorActionPreference = "Stop" + +Import-Module $PSScriptRoot/types.psm1 +Import-Module $PSScriptRoot/reader.psm1 +Import-Module $PSScriptRoot/printer.psm1 +Import-Module $PSScriptRoot/env.psm1 +Import-Module $PSScriptRoot/core.psm1 + +# READ +function READ([String] $str) { + return read_str($str) +} + +# EVAL +function eval_ast($ast, $env) { + if ($ast -eq $null) { return $ast } + switch ($ast.GetType().Name) { + "Symbol" { return $env.get($ast) } + "List" { return new-list ($ast.values | ForEach { EVAL $_ $env }) } + "Vector" { return new-vector ($ast.values | ForEach { EVAL $_ $env }) } + "HashMap" { + $hm = new-hashmap @() + foreach ($k in $ast.values.Keys) { + $hm.values[$k] = EVAL $ast.values[$k] $env + } + return $hm + } + default { return $ast } + } +} + +function EVAL($ast, $env) { + while ($true) { + #Write-Host "EVAL $(pr_str $ast)" + if (-not (list? $ast)) { + return (eval_ast $ast $env) + } + if (empty? $ast) { return $ast } + + $a0, $a1, $a2 = $ast.nth(0), $ast.nth(1), $ast.nth(2) + switch -casesensitive ($a0.value) { + "def!" { + return $env.set($a1, (EVAL $a2 $env)) + } + "let*" { + $let_env = new-env $env + for ($i=0; $i -lt $a1.values.Count; $i+=2) { + $_ = $let_env.set($a1.nth($i), (EVAL $a1.nth(($i+1)) $let_env)) + } + $env = $let_env + $ast = $a2 # TCO + } + "do" { + if ($ast.values.Count -gt 2) { + $middle = new-list $ast.values[1..($ast.values.Count-2)] + $_ = eval_ast $middle $env + } + $ast = $ast.last() # TCO + } + "if" { + $cond = (EVAL $a1 $env) + if ($cond -eq $null -or + ($cond -is [Boolean] -and $cond -eq $false)) { + $ast = $ast.nth(3) # TCO + } else { + $ast = $a2 # TCO + } + } + "fn*" { + # Save EVAL into a variable that will get closed over + $feval = Get-Command EVAL + $fn = { + return (&$feval $a2 (new-env $env $a1.values $args)) + }.GetNewClosure() + return new-malfunc $a2 $a1.values $env $fn + } + default { + $el = (eval_ast $ast $env) + $f, $fargs = $el.first(), $el.rest().values + if (malfunc? $f) { + $env = (new-env $f.env $f.params $fargs) + $ast = $f.ast # TCO + } else { + return &$f @fargs + } + } + } + } +} + +# PRINT +function PRINT($exp) { + return pr_str $exp $true +} + +# REPL +$repl_env = new-env + +function REP([String] $str) { + return PRINT (EVAL (READ $str) $repl_env) +} + +# core.EXT: defined using PowerShell +foreach ($kv in $core_ns.GetEnumerator()) { + $_ = $repl_env.set((new-symbol $kv.Key), $kv.Value) +} +$_ = $repl_env.set((new-symbol "eval"), { param($a); (EVAL $a $repl_env) }) +$_ = $repl_env.set((new-symbol "*ARGV*"), (new-list $args[1..$args.Count])) + +# core.mal: defined using the language itself +$_ = REP('(def! not (fn* (a) (if a false true)))') +$_ = REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))') + + +if ($args.Count -gt 0) { + $_ = REP('(load-file "' + $args[0] + '")') + exit 0 +} + +while ($true) { + Write-Host "user> " -NoNewline + $line = [Console]::ReadLine() + if ($line -eq $null) { + break + } + try { + Write-Host (REP($line)) + } catch { + Write-Host "Exception: $($_.Exception.Message)" + } +} diff --git a/powershell/step7_quote.ps1 b/powershell/step7_quote.ps1 new file mode 100644 index 0000000000..a300db396e --- /dev/null +++ b/powershell/step7_quote.ps1 @@ -0,0 +1,162 @@ +$ErrorActionPreference = "Stop" + +Import-Module $PSScriptRoot/types.psm1 +Import-Module $PSScriptRoot/reader.psm1 +Import-Module $PSScriptRoot/printer.psm1 +Import-Module $PSScriptRoot/env.psm1 +Import-Module $PSScriptRoot/core.psm1 + +# READ +function READ([String] $str) { + return read_str($str) +} + +# EVAL +function pair?($ast) { + (sequential? $ast) -and $ast.values.Count -gt 0 +} + +function quasiquote($ast) { + if (-not (pair? $ast)) { + return (new-list @((new-symbol "quote"), $ast)) + } else { + $a0 = $ast.nth(0) + if ((symbol? $a0) -and $a0.value -ceq "unquote") { + return $ast.nth(1) + } elseif (pair? $a0) { + $a00 = $a0.nth(0) + if ((symbol? $a00) -and $a00.value -ceq "splice-unquote") { + return (new-list @((new-symbol "concat"), + $a0.nth(1), + (quasiquote $ast.rest()))) + } + } + return (new-list @((new-symbol "cons"), + (quasiquote $a0), + (quasiquote $ast.rest()))) + } +} + +function eval_ast($ast, $env) { + if ($ast -eq $null) { return $ast } + switch ($ast.GetType().Name) { + "Symbol" { return $env.get($ast) } + "List" { return new-list ($ast.values | ForEach { EVAL $_ $env }) } + "Vector" { return new-vector ($ast.values | ForEach { EVAL $_ $env }) } + "HashMap" { + $hm = new-hashmap @() + foreach ($k in $ast.values.Keys) { + $hm.values[$k] = EVAL $ast.values[$k] $env + } + return $hm + } + default { return $ast } + } +} + +function EVAL($ast, $env) { + while ($true) { + #Write-Host "EVAL $(pr_str $ast)" + if (-not (list? $ast)) { + return (eval_ast $ast $env) + } + if (empty? $ast) { return $ast } + + $a0, $a1, $a2 = $ast.nth(0), $ast.nth(1), $ast.nth(2) + switch -casesensitive ($a0.value) { + "def!" { + return $env.set($a1, (EVAL $a2 $env)) + } + "let*" { + $let_env = new-env $env + for ($i=0; $i -lt $a1.values.Count; $i+=2) { + $_ = $let_env.set($a1.nth($i), (EVAL $a1.nth(($i+1)) $let_env)) + } + $env = $let_env + $ast = $a2 # TCO + } + "quote" { + return $a1 + } + "quasiquote" { + $ast = quasiquote $a1 + } + "do" { + if ($ast.values.Count -gt 2) { + $middle = new-list $ast.values[1..($ast.values.Count-2)] + $_ = eval_ast $middle $env + } + $ast = $ast.last() # TCO + } + "if" { + $cond = (EVAL $a1 $env) + if ($cond -eq $null -or + ($cond -is [Boolean] -and $cond -eq $false)) { + $ast = $ast.nth(3) # TCO + } else { + $ast = $a2 # TCO + } + } + "fn*" { + # Save EVAL into a variable that will get closed over + $feval = Get-Command EVAL + $fn = { + return (&$feval $a2 (new-env $env $a1.values $args)) + }.GetNewClosure() + return new-malfunc $a2 $a1.values $env $fn + } + default { + $el = (eval_ast $ast $env) + $f, $fargs = $el.first(), $el.rest().values + if (malfunc? $f) { + $env = (new-env $f.env $f.params $fargs) + $ast = $f.ast # TCO + } else { + return &$f @fargs + } + } + } + } +} + +# PRINT +function PRINT($exp) { + return pr_str $exp $true +} + +# REPL +$repl_env = new-env + +function REP([String] $str) { + return PRINT (EVAL (READ $str) $repl_env) +} + +# core.EXT: defined using PowerShell +foreach ($kv in $core_ns.GetEnumerator()) { + $_ = $repl_env.set((new-symbol $kv.Key), $kv.Value) +} +$_ = $repl_env.set((new-symbol "eval"), { param($a); (EVAL $a $repl_env) }) +$_ = $repl_env.set((new-symbol "*ARGV*"), (new-list $args[1..$args.Count])) + +# core.mal: defined using the language itself +$_ = REP('(def! not (fn* (a) (if a false true)))') +$_ = REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))') + + +if ($args.Count -gt 0) { + $_ = REP('(load-file "' + $args[0] + '")') + exit 0 +} + +while ($true) { + Write-Host "user> " -NoNewline + $line = [Console]::ReadLine() + if ($line -eq $null) { + break + } + try { + Write-Host (REP($line)) + } catch { + Write-Host "Exception: $($_.Exception.Message)" + } +} diff --git a/powershell/step8_macros.ps1 b/powershell/step8_macros.ps1 new file mode 100644 index 0000000000..295c91bd02 --- /dev/null +++ b/powershell/step8_macros.ps1 @@ -0,0 +1,193 @@ +$ErrorActionPreference = "Stop" + +Import-Module $PSScriptRoot/types.psm1 +Import-Module $PSScriptRoot/reader.psm1 +Import-Module $PSScriptRoot/printer.psm1 +Import-Module $PSScriptRoot/env.psm1 +Import-Module $PSScriptRoot/core.psm1 + +# READ +function READ([String] $str) { + return read_str($str) +} + +# EVAL +function pair?($ast) { + (sequential? $ast) -and $ast.values.Count -gt 0 +} + +function quasiquote($ast) { + if (-not (pair? $ast)) { + return (new-list @((new-symbol "quote"), $ast)) + } else { + $a0 = $ast.nth(0) + if ((symbol? $a0) -and $a0.value -ceq "unquote") { + return $ast.nth(1) + } elseif (pair? $a0) { + $a00 = $a0.nth(0) + if ((symbol? $a00) -and $a00.value -ceq "splice-unquote") { + return (new-list @((new-symbol "concat"), + $a0.nth(1), + (quasiquote $ast.rest()))) + } + } + return (new-list @((new-symbol "cons"), + (quasiquote $a0), + (quasiquote $ast.rest()))) + } +} + +function macro?($ast, $env) { + return (list? $ast) -and + (symbol? $ast.nth(0)) -and + $env.find($ast.nth(0)) -and + $env.get($ast.nth(0)).macro +} + +function macroexpand($ast, $env) { + while (macro? $ast $env) { + $mac = $env.get($ast.nth(0)).fn + $margs = $ast.rest().values + $ast = &$mac @margs + } + return $ast +} + +function eval_ast($ast, $env) { + if ($ast -eq $null) { return $ast } + switch ($ast.GetType().Name) { + "Symbol" { return $env.get($ast) } + "List" { return new-list ($ast.values | ForEach { EVAL $_ $env }) } + "Vector" { return new-vector ($ast.values | ForEach { EVAL $_ $env }) } + "HashMap" { + $hm = new-hashmap @() + foreach ($k in $ast.values.Keys) { + $hm.values[$k] = EVAL $ast.values[$k] $env + } + return $hm + } + default { return $ast } + } +} + +function EVAL($ast, $env) { + while ($true) { + #Write-Host "EVAL $(pr_str $ast)" + if (-not (list? $ast)) { + return (eval_ast $ast $env) + } + + $ast = (macroexpand $ast $env) + if (-not (list? $ast)) { + return (eval_ast $ast $env) + } + if (empty? $ast) { return $ast } + + $a0, $a1, $a2 = $ast.nth(0), $ast.nth(1), $ast.nth(2) + switch -casesensitive ($a0.value) { + "def!" { + return $env.set($a1, (EVAL $a2 $env)) + } + "let*" { + $let_env = new-env $env + for ($i=0; $i -lt $a1.values.Count; $i+=2) { + $_ = $let_env.set($a1.nth($i), (EVAL $a1.nth(($i+1)) $let_env)) + } + $env = $let_env + $ast = $a2 # TCO + } + "quote" { + return $a1 + } + "quasiquote" { + $ast = quasiquote $a1 + } + "defmacro!" { + $m = EVAL $a2 $env + $m.macro = $true + return $env.set($a1, $m) + } + "macroexpand" { + return (macroexpand $a1 $env) + } + "do" { + if ($ast.values.Count -gt 2) { + $middle = new-list $ast.values[1..($ast.values.Count-2)] + $_ = eval_ast $middle $env + } + $ast = $ast.last() # TCO + } + "if" { + $cond = (EVAL $a1 $env) + if ($cond -eq $null -or + ($cond -is [Boolean] -and $cond -eq $false)) { + $ast = $ast.nth(3) # TCO + } else { + $ast = $a2 # TCO + } + } + "fn*" { + # Save EVAL into a variable that will get closed over + $feval = Get-Command EVAL + $fn = { + return (&$feval $a2 (new-env $env $a1.values $args)) + }.GetNewClosure() + return new-malfunc $a2 $a1.values $env $fn + } + default { + $el = (eval_ast $ast $env) + $f, $fargs = $el.first(), $el.rest().values + if (malfunc? $f) { + $env = (new-env $f.env $f.params $fargs) + $ast = $f.ast # TCO + } else { + return &$f @fargs + } + } + } + } +} + +# PRINT +function PRINT($exp) { + return pr_str $exp $true +} + +# REPL +$repl_env = new-env + +function REP([String] $str) { + return PRINT (EVAL (READ $str) $repl_env) +} + +# core.EXT: defined using PowerShell +foreach ($kv in $core_ns.GetEnumerator()) { + $_ = $repl_env.set((new-symbol $kv.Key), $kv.Value) +} +$_ = $repl_env.set((new-symbol "eval"), { param($a); (EVAL $a $repl_env) }) +$_ = $repl_env.set((new-symbol "*ARGV*"), (new-list $args[1..$args.Count])) + +# core.mal: defined using the language itself +$_ = REP('(def! not (fn* (a) (if a false true)))') +$_ = REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))') +$_ = REP("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw `"odd number of forms to cond`")) (cons 'cond (rest (rest xs)))))))") +$_ = REP('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))') + + +if ($args.Count -gt 0) { + $_ = REP('(load-file "' + $args[0] + '")') + exit 0 +} + +while ($true) { + Write-Host "user> " -NoNewline + $line = [Console]::ReadLine() + if ($line -eq $null) { + break + } + try { + Write-Host (REP($line)) + } catch { + Write-Host "Exception: $($_.Exception.Message)" + } +} diff --git a/powershell/step9_try.ps1 b/powershell/step9_try.ps1 new file mode 100644 index 0000000000..e913d14f73 --- /dev/null +++ b/powershell/step9_try.ps1 @@ -0,0 +1,213 @@ +$ErrorActionPreference = "Stop" + +Import-Module $PSScriptRoot/types.psm1 +Import-Module $PSScriptRoot/reader.psm1 +Import-Module $PSScriptRoot/printer.psm1 +Import-Module $PSScriptRoot/env.psm1 +Import-Module $PSScriptRoot/core.psm1 + +# READ +function READ([String] $str) { + return read_str($str) +} + +# EVAL +function pair?($ast) { + (sequential? $ast) -and $ast.values.Count -gt 0 +} + +function quasiquote($ast) { + if (-not (pair? $ast)) { + return (new-list @((new-symbol "quote"), $ast)) + } else { + $a0 = $ast.nth(0) + if ((symbol? $a0) -and $a0.value -ceq "unquote") { + return $ast.nth(1) + } elseif (pair? $a0) { + $a00 = $a0.nth(0) + if ((symbol? $a00) -and $a00.value -ceq "splice-unquote") { + return (new-list @((new-symbol "concat"), + $a0.nth(1), + (quasiquote $ast.rest()))) + } + } + return (new-list @((new-symbol "cons"), + (quasiquote $a0), + (quasiquote $ast.rest()))) + } +} + +function macro?($ast, $env) { + return (list? $ast) -and + (symbol? $ast.nth(0)) -and + $env.find($ast.nth(0)) -and + $env.get($ast.nth(0)).macro +} + +function macroexpand($ast, $env) { + while (macro? $ast $env) { + $mac = $env.get($ast.nth(0)).fn + $margs = $ast.rest().values + $ast = &$mac @margs + } + return $ast +} + +function eval_ast($ast, $env) { + if ($ast -eq $null) { return $ast } + switch ($ast.GetType().Name) { + "Symbol" { return $env.get($ast) } + "List" { return new-list ($ast.values | ForEach { EVAL $_ $env }) } + "Vector" { return new-vector ($ast.values | ForEach { EVAL $_ $env }) } + "HashMap" { + $hm = new-hashmap @() + foreach ($k in $ast.values.Keys) { + $hm.values[$k] = EVAL $ast.values[$k] $env + } + return $hm + } + default { return $ast } + } +} + +function EVAL($ast, $env) { + while ($true) { + #Write-Host "EVAL $(pr_str $ast)" + if (-not (list? $ast)) { + return (eval_ast $ast $env) + } + + $ast = (macroexpand $ast $env) + if (-not (list? $ast)) { + return (eval_ast $ast $env) + } + if (empty? $ast) { return $ast } + + $a0, $a1, $a2 = $ast.nth(0), $ast.nth(1), $ast.nth(2) + switch -casesensitive ($a0.value) { + "def!" { + return $env.set($a1, (EVAL $a2 $env)) + } + "let*" { + $let_env = new-env $env + for ($i=0; $i -lt $a1.values.Count; $i+=2) { + $_ = $let_env.set($a1.nth($i), (EVAL $a1.nth(($i+1)) $let_env)) + } + $env = $let_env + $ast = $a2 # TCO + } + "quote" { + return $a1 + } + "quasiquote" { + $ast = quasiquote $a1 + } + "defmacro!" { + $m = EVAL $a2 $env + $m.macro = $true + return $env.set($a1, $m) + } + "macroexpand" { + return (macroexpand $a1 $env) + } + "try*" { + try { + return EVAL $a1 $env + } catch { + if ($a2 -and ($a2.nth(0).value -ceq "catch*")) { + if ($_.Exception.GetType().Name -eq "MalException") { + $e = @($_.Exception.object) + } else { + $e = @($_.Exception.Message) + } + return (EVAL $a2.nth(2) (new-env $env @($a2.nth(1)) $e)) + } else { + throw + } + } + } + "do" { + if ($ast.values.Count -gt 2) { + $middle = new-list $ast.values[1..($ast.values.Count-2)] + $_ = eval_ast $middle $env + } + $ast = $ast.last() # TCO + } + "if" { + $cond = (EVAL $a1 $env) + if ($cond -eq $null -or + ($cond -is [Boolean] -and $cond -eq $false)) { + $ast = $ast.nth(3) # TCO + } else { + $ast = $a2 # TCO + } + } + "fn*" { + # Save EVAL into a variable that will get closed over + $feval = Get-Command EVAL + $fn = { + return (&$feval $a2 (new-env $env $a1.values $args)) + }.GetNewClosure() + return new-malfunc $a2 $a1.values $env $fn + } + default { + $el = (eval_ast $ast $env) + $f, $fargs = $el.first(), $el.rest().values + if (malfunc? $f) { + $env = (new-env $f.env $f.params $fargs) + $ast = $f.ast # TCO + } else { + return &$f @fargs + } + } + } + } +} + +# PRINT +function PRINT($exp) { + return pr_str $exp $true +} + +# REPL +$repl_env = new-env + +function REP([String] $str) { + return PRINT (EVAL (READ $str) $repl_env) +} + +# core.EXT: defined using PowerShell +foreach ($kv in $core_ns.GetEnumerator()) { + $_ = $repl_env.set((new-symbol $kv.Key), $kv.Value) +} +$_ = $repl_env.set((new-symbol "eval"), { param($a); (EVAL $a $repl_env) }) +$_ = $repl_env.set((new-symbol "*ARGV*"), (new-list $args[1..$args.Count])) + +# core.mal: defined using the language itself +$_ = REP('(def! not (fn* (a) (if a false true)))') +$_ = REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))') +$_ = REP("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw `"odd number of forms to cond`")) (cons 'cond (rest (rest xs)))))))") +$_ = REP('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))') + + +if ($args.Count -gt 0) { + $_ = REP('(load-file "' + $args[0] + '")') + exit 0 +} + +while ($true) { + Write-Host "user> " -NoNewline + $line = [Console]::ReadLine() + if ($line -eq $null) { + break + } + try { + Write-Host (REP($line)) + } catch { + if ($_.Exception.GetType().Name -eq "MalException") { + Write-Host "Exception: $(pr_str $_.Exception.object)" + } else { + Write-Host "Exception: $($_.Exception.Message)" + } + } +} diff --git a/powershell/stepA_mal.ps1 b/powershell/stepA_mal.ps1 new file mode 100644 index 0000000000..5087d013ec --- /dev/null +++ b/powershell/stepA_mal.ps1 @@ -0,0 +1,217 @@ +$ErrorActionPreference = "Stop" + +Import-Module $PSScriptRoot/types.psm1 +Import-Module $PSScriptRoot/reader.psm1 +Import-Module $PSScriptRoot/printer.psm1 +Import-Module $PSScriptRoot/env.psm1 +Import-Module $PSScriptRoot/core.psm1 + +# READ +function READ([String] $str) { + return read_str($str) +} + +# EVAL +function pair?($ast) { + (sequential? $ast) -and $ast.values.Count -gt 0 +} + +function quasiquote($ast) { + if (-not (pair? $ast)) { + return (new-list @((new-symbol "quote"), $ast)) + } else { + $a0 = $ast.nth(0) + if ((symbol? $a0) -and $a0.value -ceq "unquote") { + return $ast.nth(1) + } elseif (pair? $a0) { + $a00 = $a0.nth(0) + if ((symbol? $a00) -and $a00.value -ceq "splice-unquote") { + return (new-list @((new-symbol "concat"), + $a0.nth(1), + (quasiquote $ast.rest()))) + } + } + return (new-list @((new-symbol "cons"), + (quasiquote $a0), + (quasiquote $ast.rest()))) + } +} + +function macro?($ast, $env) { + return (list? $ast) -and + (symbol? $ast.nth(0)) -and + $env.find($ast.nth(0)) -and + $env.get($ast.nth(0)).macro +} + +function macroexpand($ast, $env) { + while (macro? $ast $env) { + $mac = $env.get($ast.nth(0)).fn + $margs = $ast.rest().values + $ast = &$mac @margs + } + return $ast +} + +function eval_ast($ast, $env) { + if ($ast -eq $null) { return $ast } + switch ($ast.GetType().Name) { + "Symbol" { return $env.get($ast) } + "List" { return new-list ($ast.values | ForEach { EVAL $_ $env }) } + "Vector" { return new-vector ($ast.values | ForEach { EVAL $_ $env }) } + "HashMap" { + $hm = new-hashmap @() + foreach ($k in $ast.values.Keys) { + $hm.values[$k] = EVAL $ast.values[$k] $env + } + return $hm + } + default { return $ast } + } +} + +function EVAL($ast, $env) { + while ($true) { + #Write-Host "EVAL $(pr_str $ast)" + if (-not (list? $ast)) { + return (eval_ast $ast $env) + } + + $ast = (macroexpand $ast $env) + if (-not (list? $ast)) { + return (eval_ast $ast $env) + } + if (empty? $ast) { return $ast } + + $a0, $a1, $a2 = $ast.nth(0), $ast.nth(1), $ast.nth(2) + switch -casesensitive ($a0.value) { + "def!" { + return $env.set($a1, (EVAL $a2 $env)) + } + "let*" { + $let_env = new-env $env + for ($i=0; $i -lt $a1.values.Count; $i+=2) { + $_ = $let_env.set($a1.nth($i), (EVAL $a1.nth(($i+1)) $let_env)) + } + $env = $let_env + $ast = $a2 # TCO + } + "quote" { + return $a1 + } + "quasiquote" { + $ast = quasiquote $a1 + } + "defmacro!" { + $m = EVAL $a2 $env + $m.macro = $true + return $env.set($a1, $m) + } + "macroexpand" { + return (macroexpand $a1 $env) + } + "try*" { + try { + return EVAL $a1 $env + } catch { + if ($a2 -and ($a2.nth(0).value -ceq "catch*")) { + if ($_.Exception.GetType().Name -eq "MalException") { + $e = @($_.Exception.object) + } else { + $e = @($_.Exception.Message) + } + return (EVAL $a2.nth(2) (new-env $env @($a2.nth(1)) $e)) + } else { + throw + } + } + } + "do" { + if ($ast.values.Count -gt 2) { + $middle = new-list $ast.values[1..($ast.values.Count-2)] + $_ = eval_ast $middle $env + } + $ast = $ast.last() # TCO + } + "if" { + $cond = (EVAL $a1 $env) + if ($cond -eq $null -or + ($cond -is [Boolean] -and $cond -eq $false)) { + $ast = $ast.nth(3) # TCO + } else { + $ast = $a2 # TCO + } + } + "fn*" { + # Save EVAL into a variable that will get closed over + $feval = Get-Command EVAL + $fn = { + return (&$feval $a2 (new-env $env $a1.values $args)) + }.GetNewClosure() + return new-malfunc $a2 $a1.values $env $fn + } + default { + $el = (eval_ast $ast $env) + $f, $fargs = $el.first(), $el.rest().values + if (malfunc? $f) { + $env = (new-env $f.env $f.params $fargs) + $ast = $f.ast # TCO + } else { + return &$f @fargs + } + } + } + } +} + +# PRINT +function PRINT($exp) { + return pr_str $exp $true +} + +# REPL +$repl_env = new-env + +function REP([String] $str) { + return PRINT (EVAL (READ $str) $repl_env) +} + +# core.EXT: defined using PowerShell +foreach ($kv in $core_ns.GetEnumerator()) { + $_ = $repl_env.set((new-symbol $kv.Key), $kv.Value) +} +$_ = $repl_env.set((new-symbol "eval"), { param($a); (EVAL $a $repl_env) }) +$_ = $repl_env.set((new-symbol "*ARGV*"), (new-list $args[1..$args.Count])) + +# core.mal: defined using the language itself +$_ = REP('(def! *host-language* "powershell")') +$_ = REP('(def! not (fn* (a) (if a false true)))') +$_ = REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))') +$_ = REP("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw `"odd number of forms to cond`")) (cons 'cond (rest (rest xs)))))))") +$_ = REP('(def! *gensym-counter* (atom 0))') +$_ = REP('(def! gensym (fn* [] (symbol (str "G__" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))') +$_ = REP('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))') + + +if ($args.Count -gt 0) { + $_ = REP('(load-file "' + $args[0] + '")') + exit 0 +} + +$_ = REP('(println (str "Mal [" *host-language* "]"))') +while ($true) { + Write-Host "user> " -NoNewline + $line = [Console]::ReadLine() + if ($line -eq $null) { + break + } + try { + Write-Host (REP($line)) + } catch { + if ($_.Exception.GetType().Name -eq "MalException") { + Write-Host "Exception: $(pr_str $_.Exception.object)" + } else { + Write-Host "Exception: $($_.Exception.Message)" + } + } +} diff --git a/powershell/types.psm1 b/powershell/types.psm1 index e300c4f622..e2fb735ec2 100644 --- a/powershell/types.psm1 +++ b/powershell/types.psm1 @@ -1,47 +1,334 @@ +# +# Exceptions +# +Class MalException : Exception { + [Object] $object + + MalException($obj) { + $this.object = $obj + } +} + +function mal_throw($obj) { + throw [MalException] $obj +} + +# +# Symbols +# + Class Symbol { [String] $value Symbol([String] $val) { $this.value = $val } + + copy() { $this } } -function new-symbol([string] $val) { +function new-symbol([String] $val) { [Symbol]::new($val) } +function symbol?($obj) { + $obj -is [Symbol] +} + +# +# Strings +# + +function string?($obj) { + ($obj -is [String]) -and ($obj[0] -ne "$([char]0x29e)") +} + +# +# Keywords +# + +function new-keyword($obj) { + if (keyword? $obj) { + $obj + } else { + "$([char]0x29e)$obj" + } +} + +function keyword?($obj) { + ($obj -is [String]) -and ($obj[0] -eq "$([char]0x29e)") +} + + +# +# Lists +# + Class List { + #[System.Collections.ArrayList] $values [Object[]] $values + [Object] $meta + + List() { + $this.values = @() + #$this.values = New-Object System.Collections.ArrayList + } List([Object[]] $vals) { + #List([System.Collections.ArrayList] $vals) { $this.values = $vals } + [List] copy() { + return [List]::new($this.values) + } + [void] push([Object] $val) { - $this.values += $val + $this.values.Add($val) } [Object] first() { return $this.values[0] } - [Object[]] rest() { - return $this.values[1..($this.values.Length-1)] + [List] rest() { + if ($this.values.Count -le 1) { + return [List]::new(@()) + } else { + return [List]::new($this.values[1..($this.values.Count)]) + } + } + + [Object] last() { + if ($this.values.Count -eq 0) { + return $null + } else { + return $this.values[$this.values.Count-1] + } } - [Object] nth([int] $idx) { + [Object] nth([int64] $idx) { return $this.values[$idx] } } function new-list([Object[]] $vals) { - [List]::new($vals) +#function new-list([System.Collections.ArrayList] $vals) { + if ($vals.Count -eq 0) { + return [List]::new() + } else { + return [List]::new($vals) + } } function list?($obj) { + $obj -is [List] -and -not ($obj -is [Vector]) +} + + +# +# Vectors +# + +Class Vector : List { + Vector() { + $this.values = @() + #$this.values = New-Object System.Collections.ArrayList + } + + Vector([Object[]] $vals) { + #Vector([System.Collections.ArrayList] $vals) { + $this.values = $vals + } + + [Vector] copy() { + return [Vector]::new($this.values) + } + +} + +function new-vector([Object[]] $vals) { + if ($vals.Count -eq 0) { + return [Vector]::new() + } else { + return [Vector]::new($vals) + } +} + +function vector?($obj) { + $obj -is [Vector] +} + + +# +# HashMaps +# + +Class HashMap { + [Hashtable] $values + [Object] $meta + + HashMap() { + # Case-sensitive hashtable + $this.values = New-Object System.Collections.HashTable + } + + HashMap([Hashtable] $vals) { + $this.values = $vals + } + + [HashMap] copy() { + return [HashMap]::new($this.values.clone()) + } + +} + +function assoc_BANG($hm, $kvs) { + $ht = $hm.values + for ($i = 0; $i -lt $kvs.Count; $i+=2) { + $ht[$kvs[$i]] = $kvs[($i+1)] + } + return $hm +} + +function dissoc_BANG($hm, $ks) { + $ht = $hm.values + foreach ($k in $ks) { + $ht.Remove($k) + } + return $hm +} + + +function new-hashmap([Object[]] $vals) { + $hm = [HashMap]::new() + assoc_BANG $hm $vals +} + +function hashmap?($obj) { + $obj -is [HashMap] +} + +function keys($hm) { + return new-list ($hm.values.GetEnumerator() | ForEach { $_.Key }) +} + +function vals($hm) { + return new-list ($hm.values.GetEnumerator() | ForEach { $_.Value }) +} + + +# +# Atoms + +Class Atom { + [Object] $value + + Atom([Object] $val) { + $this.value = $val + } +} + +function new-atom([Object] $val) { + [Atom]::new($val) +} + +function atom?($obj) { + $obj -is [Atom] +} + + +# Functions + +Class MalFunc { + [Object] $ast + [Object[]] $params + [Object] $env + [scriptBlock] $fn + [Boolean] $macro + [Object] $meta + + MalFunc($ast, $params, $env, $fn, $macro, $meta){ + $this.ast = $ast + $this.params = $params + $this.env = $env + $this.fn = $fn + $this.macro = $macro + $this.meta = $meta + } + + [MalFunc] copy() { + return [MalFunc]::new($this.ast, $this.params, $this.env, $this.fn, + $this.macro, $this.meta) + } + +} + +function new-malfunc($ast, $params, $env, $fn, $macro, $meta) { + [MalFunc]::new($ast, $params, $env, $fn, $macro, $meta) +} + +function malfunc?($obj) { + $obj -is [MalFunc] +} +# +# General functions +# +function equal?($a, $b) { + if ($a -eq $null -and $b -eq $null) { + return $true + } elseif ($a -eq $null -or $b -eq $null) { + return $false + } + $ta, $tb = $a.GetType().Name, $b.GetType().Name + if (-not (($ta -eq $tb) -or ((sequential?($a)) -and (sequential?($b))))) { + return $false + } + switch ($ta) { + { $_ -eq "List" -or $_ -eq "Vector" } { + if ($a.values.Count -ne $b.values.Count) { + return $false + } + for ($i = 0; $i -lt $a.value.Count; $i++) { + if (-not (equal? $a.values[$i] $b.values[$i])) { + return $false + } + } + return $true + } + "HashMap" { + $hta, $htb = $a.values, $b.values + $alen = ($hta.GetEnumerator | Measure-Object).Count + $blen = ($htb.GetEnumerator | Measure-Object).Count + if ($alen -ne $blen) { + return $false + } + foreach ($kv in $hta.GetEnumerator()) { + if (-not (equal? $kv.Value $htb[$kv.Key])) { + return $false + } + } + return $true + } + "Symbol" { + return $a.value -ceq $b.value + } + default { + return $a -ceq $b + } + } +} + + +# +# Sequence functions +# +function sequential?($obj) { $obj -is [List] } function empty?($obj) { $obj.values.Count -eq 0 } + + From a7ed71b9e222cbc7e24f2ada193043b557b4e986 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 24 Aug 2016 17:39:24 -0500 Subject: [PATCH 0035/2308] tests: additional tests based on PowerShell impl - step1: empty list and vector - step4: special forms case-sensitivity - step9: apply with an empty list/vector --- tests/step1_read_print.mal | 4 ++++ tests/step4_if_fn_do.mal | 4 ++++ tests/step9_try.mal | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/tests/step1_read_print.mal b/tests/step1_read_print.mal index 69e4336bc8..94d84f5294 100644 --- a/tests/step1_read_print.mal +++ b/tests/step1_read_print.mal @@ -25,6 +25,8 @@ abc-def ;; Testing read of lists (+ 1 2) ;=>(+ 1 2) +() +;=>() ((3 4)) ;=>((3 4)) (+ 1 (+ 2 3)) @@ -111,6 +113,8 @@ false ;; Testing read of vectors [+ 1 2] ;=>[+ 1 2] +[] +;=>[] [[3 4]] ;=>[[3 4]] [+ 1 [+ 2 3]] diff --git a/tests/step4_if_fn_do.mal b/tests/step4_if_fn_do.mal index 87e39d0f48..efa41f1650 100644 --- a/tests/step4_if_fn_do.mal +++ b/tests/step4_if_fn_do.mal @@ -182,6 +182,10 @@ a ;=>6 +;; Testing special form case-sensitivity +(def! DO (fn* () 7)) +(DO 3) +;=>7 ;; Testing recursive sumdown function (def! sumdown (fn* (N) (if (> N 0) (+ N (sumdown (- N 1))) 0))) diff --git a/tests/step9_try.mal b/tests/step9_try.mal index 3d6f86ea5f..254aed07de 100644 --- a/tests/step9_try.mal +++ b/tests/step9_try.mal @@ -53,6 +53,8 @@ (apply prn 1 2 (list "3" (list))) ; 1 2 "3" () ;=>nil +(apply list (list)) +;=>() ;; Testing apply function with user functions (apply (fn* (a b) (+ a b)) (list 2 3)) @@ -122,6 +124,8 @@ (apply prn 1 2 ["3" 4]) ; 1 2 "3" 4 ;=>nil +(apply list []) +;=>() ;; Testing apply function with user functions and arguments in vector (apply (fn* (a b) (+ a b)) [2 3]) ;=>5 From 5c34565f8d5bc0ce707609552c2a99b4781f944e Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 24 Aug 2016 17:42:19 -0500 Subject: [PATCH 0036/2308] README: add PowerShell, implementation 58. --- README.md | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index cded113b88..7555932094 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 57 languages: +Mal is implemented in 58 languages: * Ada * GNU awk @@ -52,6 +52,7 @@ Mal is implemented in 57 languages: * PL/pgSQL (Postgres) * PL/SQL (Oracle) * Postscript +* PowerShell * Python * RPython * R @@ -610,16 +611,6 @@ cd php php stepX_YYY.php ``` -### Postscript Level 2/3 - -The Postscript implementation of mal requires ghostscript to run. It -has been tested with ghostscript 9.10. - -``` -cd ps -gs -q -dNODISPLAY -I./ stepX_YYY.ps -``` - ### PL/pgSQL (Postgres SQL Procedural Language) The PL/pgSQL implementation of mal requires a running Postgres server @@ -659,6 +650,26 @@ cd plsql ORACLE_LOGON=myuser/mypass@ORCL ./wrap.sh stepX_YYY.sql ``` +### Postscript Level 2/3 + +The Postscript implementation of mal requires ghostscript to run. It +has been tested with ghostscript 9.10. + +``` +cd ps +gs -q -dNODISPLAY -I./ stepX_YYY.ps +``` + +### PowerShell + +The PowerShell implementation of mal requires the PowerShell script +language. It has been tested with PowerShell 6.0.0 Alpha 9 on Linux. + +``` +cd powershell +powershell ./stepX_YYY.ps1 +``` + ### Python (2.X or 3.X) ``` From b1dc6b71f80cc47dd433b0cad05fa2d10a03d10b Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 24 Aug 2016 17:45:38 -0500 Subject: [PATCH 0037/2308] PowerShell: add to Travis build. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index b0c7e77c09..f7e923516e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,6 +50,7 @@ matrix: - {env: IMPL=plpgsql, services: [docker]} # - {env: IMPL=plsql, services: [docker]} - {env: IMPL=ps, services: [docker]} + - {env: IMPL=powershell, services: [docker]} - {env: IMPL=python, services: [docker]} - {env: IMPL=r, services: [docker]} - {env: IMPL=racket, services: [docker]} From 5e38bc99224b8932fe922b3bd674f179cfcd2393 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 24 Aug 2016 17:48:56 -0500 Subject: [PATCH 0038/2308] Fix step4 case sensitivity test. --- tests/step4_if_fn_do.mal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/step4_if_fn_do.mal b/tests/step4_if_fn_do.mal index efa41f1650..7b0b9a015a 100644 --- a/tests/step4_if_fn_do.mal +++ b/tests/step4_if_fn_do.mal @@ -183,7 +183,7 @@ a ;=>6 ;; Testing special form case-sensitivity -(def! DO (fn* () 7)) +(def! DO (fn* (a) 7)) (DO 3) ;=>7 From e0f8ff4767e1f55b9dbbd36940f81b4bc01e9a76 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 25 Aug 2016 11:17:33 -0500 Subject: [PATCH 0039/2308] PowerShell: add missing Makefile. --- powershell/Makefile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 powershell/Makefile diff --git a/powershell/Makefile b/powershell/Makefile new file mode 100644 index 0000000000..24dd3614d6 --- /dev/null +++ b/powershell/Makefile @@ -0,0 +1,15 @@ +SOURCES_BASE = types.psm1 reader.psm1 printer.psm1 +SOURCES_LISP = env.psm1 core.psm1 stepA_mal.ps1 +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +all: + true + +.PHONY: stats + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" From 1c7168cfd90d369c9d84ea71f83d2733b2e71240 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 14 Aug 2016 01:07:22 +0530 Subject: [PATCH 0040/2308] Add common-lisp step0 --- Makefile | 3 ++- common_lisp/run | 2 ++ common_lisp/step0_repl.lisp | 28 ++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100755 common_lisp/run create mode 100644 common_lisp/step0_repl.lisp diff --git a/Makefile b/Makefile index e208d1405f..2d949b52f4 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ DOCKERIZE = # Settings # -IMPLS = ada awk bash c d chuck clojure coffee cpp crystal cs erlang elisp \ +IMPLS = ada awk bash c d chuck clojure coffee common_lisp cpp crystal cs erlang elisp \ elixir es6 factor forth fsharp go groovy guile haskell haxe \ io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php plpgsql plsql powershell ps \ @@ -151,6 +151,7 @@ d_STEP_TO_PROG = d/$($(1)) chuck_STEP_TO_PROG = chuck/$($(1)).ck clojure_STEP_TO_PROG = clojure/target/$($(1)).jar coffee_STEP_TO_PROG = coffee/$($(1)).coffee +common_lisp_STEP_TO_PROG = common_lisp/$($(1)).lisp cpp_STEP_TO_PROG = cpp/$($(1)) crystal_STEP_TO_PROG = crystal/$($(1)) cs_STEP_TO_PROG = cs/$($(1)).exe diff --git a/common_lisp/run b/common_lisp/run new file mode 100755 index 0000000000..7950b67b57 --- /dev/null +++ b/common_lisp/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec clisp $(dirname $0)/${STEP:-stepA_mal}.lisp "${@}" diff --git a/common_lisp/step0_repl.lisp b/common_lisp/step0_repl.lisp new file mode 100644 index 0000000000..6d031cf7b4 --- /dev/null +++ b/common_lisp/step0_repl.lisp @@ -0,0 +1,28 @@ +(defpackage :mal + (:use :common-lisp)) + +(in-package :mal) + +(defun mal-read (string) + string) + +(defun mal-eval (ast env) + ast) + +(defun mal-print (expression) + expression) + +(defun rep (string) + (mal-print (mal-eval (mal-read string) + (make-hash-table :test #'equal)))) + +(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) + (format out-stream prompt) + (force-output out-stream) + (read-line in-stream nil)) + +(defun main () + (loop do (let ((line (readline "user> "))) + (if line (write-line (rep line)) (return))))) + +(main) From 74fa635bb0d22cad1888fcac3ee013e2f69e6abe Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 15 Aug 2016 13:44:32 +0530 Subject: [PATCH 0041/2308] Implementation of step1 without the optional functionality --- common_lisp/.dir-locals.el | 2 + common_lisp/Dockerfile | 25 +++++++ common_lisp/printer.lisp | 22 ++++++ common_lisp/reader.lisp | 109 ++++++++++++++++++++++++++++++ common_lisp/step1_read_print.lisp | 36 ++++++++++ common_lisp/types.lisp | 36 ++++++++++ 6 files changed, 230 insertions(+) create mode 100644 common_lisp/.dir-locals.el create mode 100644 common_lisp/Dockerfile create mode 100644 common_lisp/printer.lisp create mode 100644 common_lisp/reader.lisp create mode 100644 common_lisp/step1_read_print.lisp create mode 100644 common_lisp/types.lisp diff --git a/common_lisp/.dir-locals.el b/common_lisp/.dir-locals.el new file mode 100644 index 0000000000..96c665ed30 --- /dev/null +++ b/common_lisp/.dir-locals.el @@ -0,0 +1,2 @@ +((lisp-mode + (inferior-lisp-program . "clisp"))) \ No newline at end of file diff --git a/common_lisp/Dockerfile b/common_lisp/Dockerfile new file mode 100644 index 0000000000..711244bd65 --- /dev/null +++ b/common_lisp/Dockerfile @@ -0,0 +1,25 @@ +FROM ubuntu:vivid +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# Install clisp +RUN apt-get -y install clisp diff --git a/common_lisp/printer.lisp b/common_lisp/printer.lisp new file mode 100644 index 0000000000..fbda2f4414 --- /dev/null +++ b/common_lisp/printer.lisp @@ -0,0 +1,22 @@ +(require "types") + +(defpackage :printer + (:use :common-lisp :types) + (:export :pr-str)) + +(in-package :printer) + +(defun pr-str (ast) + (when ast + (case (types::mal-type ast) + ('number (format nil "~d" (types::mal-value ast))) + ('boolean (if (types::mal-value ast) "true" "false")) + ('nil "nil") + ('string (format nil "~s" (types::mal-value ast))) + ('symbol (format nil "~a" (types::mal-value ast))) + ('list (concatenate 'string + "(" + (format nil + "~{~A~^ ~}" + (mapcar #'pr-str (types::mal-value ast))) + ")"))))) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp new file mode 100644 index 0000000000..2052b5fb58 --- /dev/null +++ b/common_lisp/reader.lisp @@ -0,0 +1,109 @@ +(require "types") + +(defpackage :reader + (:use :regexp :common-lisp :types) + (:export :read-str)) + +(in-package :reader) + +(defvar *two-char-token* "~@" + "RE two char") + +(defvar *single-char-token* "\\(\\[\\|\\]\\|[{}()`'^@]\\)" + "RE single char") + +(defvar *string-re* "\"\\(?:\\\\\\(?:.\\|\n\\)\\|[^\"\\]\\)*\"" + "RE string") + +(defvar *comment-re* ";[^ +]*" + "RE comment") + +(defvar *identifier-re* "[^[:space:]{}()`'\";]\\+" + "RE identifier") + +(defvar *tokenizer-re* "[[:space:],]*\\(~@\\|\\(\\[\\|\\]\\|[{}()`'^@]\\)\\|\"\\(\\\\\\(.\\|\n\\)\\|[^\"\\]\\)*\"\\|;[^ +]*\\|[^[:space:]{}()`'\";]\\+\\)" + "RE") + +(define-condition eof (error) + ((text :initarg :text))) + +(defun test-re (re string) + (let ((match (regexp:match re string))) + (when match + (regexp:match-string string match)))) + +(defvar *whitespace-chars* + '(#\Space #\Newline #\Backspace #\Tab + #\Linefeed #\Page #\Return #\Rubout #\,)) + +(defun tokenize (string) + (remove-if (lambda (token) + (or (zerop (length token)) + (char= (char token 0) #\;))) + (loop + with end = (length string) + for start = 0 then (regexp:match-end match) + for match = (ignore-errors + (regexp:match *tokenizer-re* string :start start)) + while (and match (< start end)) + collect (string-trim *whitespace-chars* + (regexp:match-string string match))))) + +(defstruct (token-reader) + (tokens nil)) + +(defun peek (reader) + (car (token-reader-tokens reader))) + +(defun next (reader) + (pop (token-reader-tokens reader))) + +(defun consume (reader) + (pop (token-reader-tokens reader)) + reader) + +(defun read-from-string-preserving-case (string) + (let ((*readtable* (copy-readtable nil))) + (setf (readtable-case *readtable*) :preserve) + (read-from-string string))) + +(defun read-str (string) + (read-form (make-token-reader :tokens (tokenize string)))) + +(defun read-form (reader) + (let ((token (peek reader))) + (cond + ((null token) nil) + ((string= token "(") (read-list reader)) + (t (read-atom reader))))) + +(defun read-list (reader) + ;; Consume the open brace + (consume reader) + (let (forms) + (loop + for token = (peek reader) + while (cond + ((null token) (error 'eof :text "EOF encountered while reading list")) + ((string= token ")") (return)) + (t (push (read-form reader) forms)))) + ;; Consume the closing brace + (consume reader) + (make-mal-list (nreverse forms)))) + +(defun read-atom (reader) + (let ((token (next reader))) + (cond + ((regexp:match "^[[:digit:]]\\+$" token) + (make-mal-number (read-from-string token))) + ((string= token "false") + (make-mal-boolean nil)) + ((string= token "true") + (make-mal-boolean t)) + ((string= token "nil") + (make-mal-nil nil)) + ((char= (char token 0) #\") + (make-mal-string (read-from-string token))) + (t (make-mal-symbol (read-from-string-preserving-case token)))))) diff --git a/common_lisp/step1_read_print.lisp b/common_lisp/step1_read_print.lisp new file mode 100644 index 0000000000..fd3a3234ff --- /dev/null +++ b/common_lisp/step1_read_print.lisp @@ -0,0 +1,36 @@ +(require "reader") +(require "printer") + +(defpackage :mal + (:use :common-lisp :reader :printer)) + +(in-package :mal) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + ast) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case (mal-print (mal-eval (mal-read string) + (make-hash-table :test #'equal))) + (reader::eof () "EOF occured"))) + +(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) + (format out-stream prompt) + (force-output out-stream) + (read-line in-stream nil)) + +(defun writeline (string) + (when string + (write-line string))) + +(defun main () + (loop do (let ((line (readline "user> "))) + (if line (writeline (rep line)) (return))))) + +(main) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp new file mode 100644 index 0000000000..7148a3ed06 --- /dev/null +++ b/common_lisp/types.lisp @@ -0,0 +1,36 @@ +(defpackage :types + (:use :common-lisp)) + +(in-package :types) + +(defclass mal-type () + ((value :accessor mal-value :initarg :value) + (type :accessor mal-type :initarg :type))) + +(defmacro define-mal-type (type) + ;; Create a class for given type and a convenience constructor and also export + ;; them + (let ((name (intern (string-upcase (concatenate 'string + "mal-" + (symbol-name type))))) + (constructor (intern (string-upcase (concatenate 'string + "make-mal-" + (symbol-name type)))))) + `(progn (defclass ,name (mal-type) + ((type :accessor mal-type + :initarg :type + :initform ',type))) + + (defun ,constructor (value) + (make-instance ',name + :value value)) + + (export ',name) + (export ',constructor)))) + +(define-mal-type list) +(define-mal-type number) +(define-mal-type symbol) +(define-mal-type string) +(define-mal-type boolean) +(define-mal-type nil) From ceec6ccd77473785d2a62803cef6cdf2142f3024 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 15 Aug 2016 14:43:14 +0530 Subject: [PATCH 0042/2308] Fix tokenizer regex to treat [] as two separate tokens ('[' and ']') --- common_lisp/reader.lisp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index 2052b5fb58..4578931549 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -9,7 +9,7 @@ (defvar *two-char-token* "~@" "RE two char") -(defvar *single-char-token* "\\(\\[\\|\\]\\|[{}()`'^@]\\)" +(defvar *single-char-token* "[][{}()`'^@]" "RE single char") (defvar *string-re* "\"\\(?:\\\\\\(?:.\\|\n\\)\\|[^\"\\]\\)*\"" @@ -19,11 +19,11 @@ ]*" "RE comment") -(defvar *identifier-re* "[^[:space:]{}()`'\";]\\+" +(defvar *identifier-re* "[^][[:space:]{}()`'\";]\\+" "RE identifier") -(defvar *tokenizer-re* "[[:space:],]*\\(~@\\|\\(\\[\\|\\]\\|[{}()`'^@]\\)\\|\"\\(\\\\\\(.\\|\n\\)\\|[^\"\\]\\)*\"\\|;[^ -]*\\|[^[:space:]{}()`'\";]\\+\\)" +(defvar *tokenizer-re* "[[:space:],]*\\(~@\\|[][{}()`'^@]\\|\"\\(\\\\\\(.\\|\n\\)\\|[^\"\\]\\)*\"\\|;[^ +]*\\|[^][[:space:]{}()`'\";]\\+\\)" "RE") (define-condition eof (error) @@ -34,6 +34,10 @@ (when match (regexp:match-string string match)))) +(defun test-tokenizer (re string) + (let ((*tokenizer-re* re)) + (tokenize string))) + (defvar *whitespace-chars* '(#\Space #\Newline #\Backspace #\Tab #\Linefeed #\Page #\Return #\Rubout #\,)) From b36b2ffda71b06c565b9911800d47f5b0c67cf8a Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 15 Aug 2016 14:44:28 +0530 Subject: [PATCH 0043/2308] Teach MAL to read and print vectors --- common_lisp/printer.lisp | 16 ++++++++++------ common_lisp/reader.lisp | 12 +++++++----- common_lisp/types.lisp | 1 + 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/common_lisp/printer.lisp b/common_lisp/printer.lisp index fbda2f4414..ec2ffa6998 100644 --- a/common_lisp/printer.lisp +++ b/common_lisp/printer.lisp @@ -6,6 +6,14 @@ (in-package :printer) +(defun pr-mal-sequence (start-delimiter sequence end-delimiter) + (concatenate 'string + start-delimiter + (format nil + "~{~A~^ ~}" + (mapcar #'pr-str (types::mal-value sequence))) + end-delimiter)) + (defun pr-str (ast) (when ast (case (types::mal-type ast) @@ -14,9 +22,5 @@ ('nil "nil") ('string (format nil "~s" (types::mal-value ast))) ('symbol (format nil "~a" (types::mal-value ast))) - ('list (concatenate 'string - "(" - (format nil - "~{~A~^ ~}" - (mapcar #'pr-str (types::mal-value ast))) - ")"))))) + ('list (pr-mal-sequence "(" ast ")")) + ('vector (pr-mal-sequence "[" ast "]"))))) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index 4578931549..03285cd6a2 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -80,22 +80,24 @@ (let ((token (peek reader))) (cond ((null token) nil) - ((string= token "(") (read-list reader)) + ((string= token "(") (make-mal-list (read-mal-sequence reader))) + ((string= token "[") (make-mal-vector (read-mal-sequence reader "]"))) (t (read-atom reader))))) -(defun read-list (reader) +(defun read-mal-sequence (reader &optional (delimiter ")")) ;; Consume the open brace (consume reader) (let (forms) (loop for token = (peek reader) while (cond - ((null token) (error 'eof :text "EOF encountered while reading list")) - ((string= token ")") (return)) + ((null token) (error 'eof :text (format "EOF encountered while reading list, expected ~a" + delimiter))) + ((string= token delimiter) (return)) (t (push (read-form reader) forms)))) ;; Consume the closing brace (consume reader) - (make-mal-list (nreverse forms)))) + (nreverse forms))) (defun read-atom (reader) (let ((token (next reader))) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 7148a3ed06..fd3253e2f5 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -29,6 +29,7 @@ (export ',constructor)))) (define-mal-type list) +(define-mal-type vector) (define-mal-type number) (define-mal-type symbol) (define-mal-type string) From 8db930794d278e8c2eb2086cc6d0f8ef4e979e4a Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 15 Aug 2016 15:46:26 +0530 Subject: [PATCH 0044/2308] Improve error reporting for EOF errors --- common_lisp/reader.lisp | 12 +++++++++--- common_lisp/step1_read_print.lisp | 10 +++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index 03285cd6a2..ae95ab2163 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -27,7 +27,11 @@ "RE") (define-condition eof (error) - ((text :initarg :text))) + ((context :initarg :context :reader context)) + (:report (lambda (condition stream) + (format stream + "EOF encountered while reading ~a" + (context condition))))) (defun test-re (re string) (let ((match (regexp:match re string))) @@ -91,8 +95,10 @@ (loop for token = (peek reader) while (cond - ((null token) (error 'eof :text (format "EOF encountered while reading list, expected ~a" - delimiter))) + ((null token) (error 'eof + :context (if (string= delimiter ")") + "list" + "vector"))) ((string= token delimiter) (return)) (t (push (read-form reader) forms)))) ;; Consume the closing brace diff --git a/common_lisp/step1_read_print.lisp b/common_lisp/step1_read_print.lisp index fd3a3234ff..e30cf5a605 100644 --- a/common_lisp/step1_read_print.lisp +++ b/common_lisp/step1_read_print.lisp @@ -16,9 +16,13 @@ (printer:pr-str expression)) (defun rep (string) - (handler-case (mal-print (mal-eval (mal-read string) - (make-hash-table :test #'equal))) - (reader::eof () "EOF occured"))) + (handler-case + (mal-print (mal-eval (mal-read string) + (make-hash-table :test #'equal))) + (reader::eof (condition) + (format nil + "~a" + condition)))) (defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) (format out-stream prompt) From 9b09c8a689ea94d06babeac1ab5a301525848ac9 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 15 Aug 2016 17:40:40 +0530 Subject: [PATCH 0045/2308] Correctly report unterminated strings --- common_lisp/reader.lisp | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index ae95ab2163..a8e518518c 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -6,24 +6,13 @@ (in-package :reader) -(defvar *two-char-token* "~@" - "RE two char") - -(defvar *single-char-token* "[][{}()`'^@]" - "RE single char") - -(defvar *string-re* "\"\\(?:\\\\\\(?:.\\|\n\\)\\|[^\"\\]\\)*\"" +(defvar *string-re* "^\"\\(\\\\\\(.\\| +\\)\\|[^\"\\]\\)*\"$" "RE string") -(defvar *comment-re* ";[^ -]*" - "RE comment") - -(defvar *identifier-re* "[^][[:space:]{}()`'\";]\\+" - "RE identifier") - -(defvar *tokenizer-re* "[[:space:],]*\\(~@\\|[][{}()`'^@]\\|\"\\(\\\\\\(.\\|\n\\)\\|[^\"\\]\\)*\"\\|;[^ -]*\\|[^][[:space:]{}()`'\";]\\+\\)" +(defvar *tokenizer-re* "[[:space:],]*\\(~@\\|[][{}()`'^@]\\|\"\\(\\\\\\(.\\| +\\)\\|[^\"\\]\\)*\"\\?\\|;[^ +]*\\|[^][[:space:]{}()`'\";]*\\)" "RE") (define-condition eof (error) @@ -33,6 +22,15 @@ "EOF encountered while reading ~a" (context condition))))) +(defun parse-string (token) + (if (and (> (length token) 1) + (regexp:match *string-re* token)) + (read-from-string token) + ;; A bit inaccurate + (error 'eof + :context "string"))) + +;; Useful to debug regexps (defun test-re (re string) (let ((match (regexp:match re string))) (when match @@ -105,6 +103,7 @@ (consume reader) (nreverse forms))) + (defun read-atom (reader) (let ((token (next reader))) (cond @@ -117,5 +116,5 @@ ((string= token "nil") (make-mal-nil nil)) ((char= (char token 0) #\") - (make-mal-string (read-from-string token))) + (make-mal-string (parse-string token))) (t (make-mal-symbol (read-from-string-preserving-case token)))))) From d3ca7f2dff3176bd1d8c930540585c81550e4ca1 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 15 Aug 2016 21:48:39 +0530 Subject: [PATCH 0046/2308] Refactor reading and printing of vectors and lists --- common_lisp/reader.lisp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index a8e518518c..e6a4d1dfe2 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -82,12 +82,16 @@ (let ((token (peek reader))) (cond ((null token) nil) - ((string= token "(") (make-mal-list (read-mal-sequence reader))) - ((string= token "[") (make-mal-vector (read-mal-sequence reader "]"))) + ((string= token "(") (make-mal-list (read-mal-sequence reader + ")" + 'list))) + ((string= token "[") (make-mal-vector (read-mal-sequence reader + "]" + 'vector))) (t (read-atom reader))))) -(defun read-mal-sequence (reader &optional (delimiter ")")) - ;; Consume the open brace +(defun read-mal-sequence (reader &optional (delimiter ")") (constructor 'list)) + ;; Consume the opening brace (consume reader) (let (forms) (loop @@ -101,7 +105,7 @@ (t (push (read-form reader) forms)))) ;; Consume the closing brace (consume reader) - (nreverse forms))) + (apply constructor (nreverse forms)))) (defun read-atom (reader) From 70cfc0a852f1e6e6d8610b18dabf19fef66bf63e Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 15 Aug 2016 21:49:30 +0530 Subject: [PATCH 0047/2308] Use fully qualified symbol names printer switch --- common_lisp/printer.lisp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/common_lisp/printer.lisp b/common_lisp/printer.lisp index ec2ffa6998..96764c5840 100644 --- a/common_lisp/printer.lisp +++ b/common_lisp/printer.lisp @@ -17,10 +17,10 @@ (defun pr-str (ast) (when ast (case (types::mal-type ast) - ('number (format nil "~d" (types::mal-value ast))) - ('boolean (if (types::mal-value ast) "true" "false")) - ('nil "nil") - ('string (format nil "~s" (types::mal-value ast))) - ('symbol (format nil "~a" (types::mal-value ast))) - ('list (pr-mal-sequence "(" ast ")")) - ('vector (pr-mal-sequence "[" ast "]"))))) + (types::number (format nil "~d" (types::mal-value ast))) + (types::boolean (if (types::mal-value ast) "true" "false")) + (types::nil "nil") + (types::string (format nil "~s" (types::mal-value ast))) + (types::symbol (format nil "~a" (types::mal-value ast))) + (types::list (pr-mal-sequence "(" ast ")")) + (types::vector (pr-mal-sequence "[" ast "]"))))) From 23a2c88b0ad86f28864de40ce67e1ab74df9c6d6 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 15 Aug 2016 21:50:42 +0530 Subject: [PATCH 0048/2308] Add support for reading and printing hash-maps --- common_lisp/printer.lisp | 19 ++++++++++++++++++- common_lisp/reader.lisp | 24 ++++++++++++++++++++++++ common_lisp/types.lisp | 36 +++++++++++++++++++++++++++++++++--- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/common_lisp/printer.lisp b/common_lisp/printer.lisp index 96764c5840..354d835764 100644 --- a/common_lisp/printer.lisp +++ b/common_lisp/printer.lisp @@ -14,6 +14,22 @@ (mapcar #'pr-str (types::mal-value sequence))) end-delimiter)) +(defun pr-mal-hash-map (hash-map) + (let ((hash-map-value (types::mal-value hash-map))) + (concatenate 'string + "{" + (format nil + "~{~A~^ ~}" + (mapcar (lambda (key-value) + (format nil + "~a ~a" + (pr-str (car key-value)) + (pr-str (cdr key-value)))) + (loop + for key being the hash-keys of hash-map-value + collect (cons key (gethash key hash-map-value))))) + "}"))) + (defun pr-str (ast) (when ast (case (types::mal-type ast) @@ -23,4 +39,5 @@ (types::string (format nil "~s" (types::mal-value ast))) (types::symbol (format nil "~a" (types::mal-value ast))) (types::list (pr-mal-sequence "(" ast ")")) - (types::vector (pr-mal-sequence "[" ast "]"))))) + (types::vector (pr-mal-sequence "[" ast "]")) + (types::hash-map (pr-mal-hash-map ast))))) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index e6a4d1dfe2..8c520044aa 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -88,6 +88,7 @@ ((string= token "[") (make-mal-vector (read-mal-sequence reader "]" 'vector))) + ((string= token "{") (make-mal-hash-map (read-hash-map reader))) (t (read-atom reader))))) (defun read-mal-sequence (reader &optional (delimiter ")") (constructor 'list)) @@ -107,6 +108,29 @@ (consume reader) (apply constructor (nreverse forms)))) +(defun read-hash-map (reader) + ;; Consume the open brace + (consume reader) + (let (forms + (hash-map (make-hash-table :test 'types:mal-value=))) + (loop + for token = (peek reader) + while (cond + ((null token) (error 'eof + :context "hash-map")) + ((string= token "}") (return)) + (t (let ((key (read-form reader)) + (value (read-form reader))) + (if (null value) + (error 'eof + :context "hash-map") + (push (cons key value) forms)))))) + ;; Consume the closing brace + (consume reader) + ;; Construct the hash table + (dolist (key-value forms) + (setf (gethash (car key-value) hash-map) (cdr key-value))) + hash-map)) (defun read-atom (reader) (let ((token (next reader))) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index fd3253e2f5..f0eeace1f6 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -1,5 +1,24 @@ (defpackage :types - (:use :common-lisp)) + (:use :common-lisp) + (:export :mal-value= + ;; Accessors + :mal-value + :mal-type + :mal-meta + ;; Mal values + :number + :boolean + :nil + :string + :symbol + :keyword + :list + :vector + :hash-map + :any + ;; Helpers + :apply-unwrapped-values + :switch-mal-type)) (in-package :types) @@ -7,6 +26,16 @@ ((value :accessor mal-value :initarg :value) (type :accessor mal-type :initarg :type))) +(defun mal-value= (value1 value2) + (and (equal (mal-type value1) (mal-type value2)) + (equal (mal-value value1) (mal-value value2)))) + +(defun hash-mal-value (value) + (sxhash (mal-value value))) + +#+sbcl (sb-ext:define-hash-table-test mal-value= hash-mal-value) +#+clisp (ext:define-hash-table-test mal-value= mal-value= hash-mal-value) + (defmacro define-mal-type (type) ;; Create a class for given type and a convenience constructor and also export ;; them @@ -28,10 +57,11 @@ (export ',name) (export ',constructor)))) -(define-mal-type list) -(define-mal-type vector) (define-mal-type number) (define-mal-type symbol) (define-mal-type string) (define-mal-type boolean) +(define-mal-type list) +(define-mal-type vector) +(define-mal-type hash-map) (define-mal-type nil) From 3ccd8eaa68b3fbb18f1aa484d5d06e09bc380434 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 15 Aug 2016 22:39:59 +0530 Subject: [PATCH 0049/2308] Add support for reading and printing keywords --- common_lisp/printer.lisp | 1 + common_lisp/reader.lisp | 2 ++ common_lisp/types.lisp | 1 + 3 files changed, 4 insertions(+) diff --git a/common_lisp/printer.lisp b/common_lisp/printer.lisp index 354d835764..16f91093fe 100644 --- a/common_lisp/printer.lisp +++ b/common_lisp/printer.lisp @@ -38,6 +38,7 @@ (types::nil "nil") (types::string (format nil "~s" (types::mal-value ast))) (types::symbol (format nil "~a" (types::mal-value ast))) + (types::keyword (format nil ":~a" (types::mal-value ast))) (types::list (pr-mal-sequence "(" ast ")")) (types::vector (pr-mal-sequence "[" ast "]")) (types::hash-map (pr-mal-hash-map ast))))) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index 8c520044aa..3c6951d245 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -145,4 +145,6 @@ (make-mal-nil nil)) ((char= (char token 0) #\") (make-mal-string (parse-string token))) + ((char= (char token 0) #\:) + (make-mal-keyword (read-from-string-preserving-case token))) (t (make-mal-symbol (read-from-string-preserving-case token)))))) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index f0eeace1f6..0c36f9d61d 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -59,6 +59,7 @@ (define-mal-type number) (define-mal-type symbol) +(define-mal-type keyword) (define-mal-type string) (define-mal-type boolean) (define-mal-type list) From fa78d99b185cf7410d3c6ea160574cbc47a37e92 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 15 Aug 2016 23:10:58 +0530 Subject: [PATCH 0050/2308] Expand quote, quasiquote, unquote and splice-unquote --- common_lisp/reader.lisp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index 3c6951d245..1229a83385 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -10,9 +10,9 @@ \\)\\|[^\"\\]\\)*\"$" "RE string") -(defvar *tokenizer-re* "[[:space:],]*\\(~@\\|[][{}()`'^@]\\|\"\\(\\\\\\(.\\| +(defvar *tokenizer-re* "[[:space:],]*\\(~@\\|[][{}()~`'^@]\\|\"\\(\\\\\\(.\\| \\)\\|[^\"\\]\\)*\"\\?\\|;[^ -]*\\|[^][[:space:]{}()`'\";]*\\)" +]*\\|[^][[:space:]~{}()`'\";]*\\)" "RE") (define-condition eof (error) @@ -89,8 +89,21 @@ "]" 'vector))) ((string= token "{") (make-mal-hash-map (read-hash-map reader))) + ((string= token "'") (expand-quote reader)) + ((string= token "`") (expand-quote reader)) + ((string= token "~") (expand-quote reader)) + ((string= token "~@") (expand-quote reader)) (t (read-atom reader))))) +(defun expand-quote (reader) + (let ((quote (next reader))) + (make-mal-list (list (make-mal-symbol (cond + ((string= quote "'") "quote") + ((string= quote "`") "quasiquote") + ((string= quote "~") "unquote") + ((string= quote "~@") "splice-unquote"))) + (read-form reader))))) + (defun read-mal-sequence (reader &optional (delimiter ")") (constructor 'list)) ;; Consume the opening brace (consume reader) From 0baefce0382292b86e371cc8b777ef3f37a400b7 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 15 Aug 2016 23:37:26 +0530 Subject: [PATCH 0051/2308] Add switch-mal-type, use it select printing strategy --- common_lisp/printer.lisp | 20 ++++++++++---------- common_lisp/types.lisp | 8 ++++++++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/common_lisp/printer.lisp b/common_lisp/printer.lisp index 16f91093fe..3250ecb0f0 100644 --- a/common_lisp/printer.lisp +++ b/common_lisp/printer.lisp @@ -32,13 +32,13 @@ (defun pr-str (ast) (when ast - (case (types::mal-type ast) - (types::number (format nil "~d" (types::mal-value ast))) - (types::boolean (if (types::mal-value ast) "true" "false")) - (types::nil "nil") - (types::string (format nil "~s" (types::mal-value ast))) - (types::symbol (format nil "~a" (types::mal-value ast))) - (types::keyword (format nil ":~a" (types::mal-value ast))) - (types::list (pr-mal-sequence "(" ast ")")) - (types::vector (pr-mal-sequence "[" ast "]")) - (types::hash-map (pr-mal-hash-map ast))))) + (switch-mal-type ast + ('number (format nil "~d" (types::mal-value ast))) + ('boolean (if (types::mal-value ast) "true" "false")) + ('nil "nil") + ('string (format nil "~s" (types::mal-value ast))) + ('symbol (format nil "~a" (types::mal-value ast))) + ('keyword (format nil ":~a" (types::mal-value ast))) + ('list (pr-mal-sequence "(" ast ")")) + ('vector (pr-mal-sequence "[" ast "]")) + ('hash-map (pr-mal-hash-map ast))))) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 0c36f9d61d..5a28c09ee8 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -66,3 +66,11 @@ (define-mal-type vector) (define-mal-type hash-map) (define-mal-type nil) + +(defmacro switch-mal-type (ast &body forms) + `(let ((type (types::mal-type ,ast))) + (cond + ,@(mapcar (lambda (form) + (list (list 'equal (car form) 'type) + (cadr form))) + forms)))) From 1e9ac59c541c4c3278b80f722f79764e53451135 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 16 Aug 2016 11:51:09 +0530 Subject: [PATCH 0052/2308] Fix printing of vectors, use fully qualified symbol names in printing switch --- common_lisp/printer.lisp | 20 ++++++++++---------- common_lisp/types.lisp | 4 ++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/common_lisp/printer.lisp b/common_lisp/printer.lisp index 3250ecb0f0..a010b6c8d0 100644 --- a/common_lisp/printer.lisp +++ b/common_lisp/printer.lisp @@ -11,7 +11,7 @@ start-delimiter (format nil "~{~A~^ ~}" - (mapcar #'pr-str (types::mal-value sequence))) + (map 'list #'pr-str (types::mal-value sequence))) end-delimiter)) (defun pr-mal-hash-map (hash-map) @@ -33,12 +33,12 @@ (defun pr-str (ast) (when ast (switch-mal-type ast - ('number (format nil "~d" (types::mal-value ast))) - ('boolean (if (types::mal-value ast) "true" "false")) - ('nil "nil") - ('string (format nil "~s" (types::mal-value ast))) - ('symbol (format nil "~a" (types::mal-value ast))) - ('keyword (format nil ":~a" (types::mal-value ast))) - ('list (pr-mal-sequence "(" ast ")")) - ('vector (pr-mal-sequence "[" ast "]")) - ('hash-map (pr-mal-hash-map ast))))) + ('types::number (format nil "~d" (types::mal-value ast))) + ('types::boolean (if (types::mal-value ast) "true" "false")) + ('types::nil "nil") + ('types::string (format nil "~s" (types::mal-value ast))) + ('types::symbol (format nil "~a" (types::mal-value ast))) + ('types::keyword (format nil ":~a" (types::mal-value ast))) + ('types::list (pr-mal-sequence "(" ast ")")) + ('types::vector (pr-mal-sequence "[" ast "]")) + ('types::hash-map (pr-mal-hash-map ast))))) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 5a28c09ee8..84d18a5503 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -26,6 +26,10 @@ ((value :accessor mal-value :initarg :value) (type :accessor mal-type :initarg :type))) +(defmethod print-object ((obj mal-type) out) + (with-slots (value type) obj + (format out "#" type value))) + (defun mal-value= (value1 value2) (and (equal (mal-type value1) (mal-type value2)) (equal (mal-value value1) (mal-value value2)))) From 335375395ff5db2c79cf453a6c47001f750e36a5 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 16 Aug 2016 12:35:00 +0530 Subject: [PATCH 0053/2308] Add support for reading print metadata associated with values --- common_lisp/printer.lisp | 24 ++++++++++++++---------- common_lisp/reader.lisp | 23 +++++++++++++++++------ common_lisp/types.lisp | 17 ++++++++++++----- 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/common_lisp/printer.lisp b/common_lisp/printer.lisp index a010b6c8d0..e79310adf6 100644 --- a/common_lisp/printer.lisp +++ b/common_lisp/printer.lisp @@ -32,13 +32,17 @@ (defun pr-str (ast) (when ast - (switch-mal-type ast - ('types::number (format nil "~d" (types::mal-value ast))) - ('types::boolean (if (types::mal-value ast) "true" "false")) - ('types::nil "nil") - ('types::string (format nil "~s" (types::mal-value ast))) - ('types::symbol (format nil "~a" (types::mal-value ast))) - ('types::keyword (format nil ":~a" (types::mal-value ast))) - ('types::list (pr-mal-sequence "(" ast ")")) - ('types::vector (pr-mal-sequence "[" ast "]")) - ('types::hash-map (pr-mal-hash-map ast))))) + (let ((repr (switch-mal-type ast + ('types::number (format nil "~d" (types::mal-value ast))) + ('types::boolean (if (types::mal-value ast) "true" "false")) + ('types::nil "nil") + ('types::string (format nil "~s" (types::mal-value ast))) + ('types::symbol (format nil "~a" (types::mal-value ast))) + ('types::keyword (format nil ":~a" (types::mal-value ast))) + ('types::list (pr-mal-sequence "(" ast ")")) + ('types::vector (pr-mal-sequence "[" ast "]")) + ('types::hash-map (pr-mal-hash-map ast)))) + (meta (pr-str (types::mal-meta ast)))) + (if meta + (format nil "(with-meta ~a ~a)" repr meta) + repr)))) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index 1229a83385..5d8980399e 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -12,7 +12,7 @@ (defvar *tokenizer-re* "[[:space:],]*\\(~@\\|[][{}()~`'^@]\\|\"\\(\\\\\\(.\\| \\)\\|[^\"\\]\\)*\"\\?\\|;[^ -]*\\|[^][[:space:]~{}()`'\";]*\\)" +]*\\|[^][[:space:]~{}()@^`'\";]*\\)" "RE") (define-condition eof (error) @@ -89,19 +89,30 @@ "]" 'vector))) ((string= token "{") (make-mal-hash-map (read-hash-map reader))) - ((string= token "'") (expand-quote reader)) - ((string= token "`") (expand-quote reader)) - ((string= token "~") (expand-quote reader)) - ((string= token "~@") (expand-quote reader)) + ((member token '("'" "`" "~" "~@" "@") :test #'string= ) (expand-quote reader)) + ((string= token "^") (read-form-with-meta reader)) (t (read-atom reader))))) +(defun read-form-with-meta (reader) + (consume reader) + (let ((meta (read-form reader)) + (value (read-form reader))) + + (when (or (null meta) + (null value)) + (error 'eof + :context "object metadata")) + + (types:add-mal-meta value meta))) + (defun expand-quote (reader) (let ((quote (next reader))) (make-mal-list (list (make-mal-symbol (cond ((string= quote "'") "quote") ((string= quote "`") "quasiquote") ((string= quote "~") "unquote") - ((string= quote "~@") "splice-unquote"))) + ((string= quote "~@") "splice-unquote") + ((string= quote "@") "deref"))) (read-form reader))))) (defun read-mal-sequence (reader &optional (delimiter ")") (constructor 'list)) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 84d18a5503..3f94d8dcb3 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -18,17 +18,23 @@ :any ;; Helpers :apply-unwrapped-values - :switch-mal-type)) + :switch-mal-type + :add-mal-meta)) (in-package :types) (defclass mal-type () ((value :accessor mal-value :initarg :value) + (meta :accessor mal-meta :initarg :meta) (type :accessor mal-type :initarg :type))) (defmethod print-object ((obj mal-type) out) - (with-slots (value type) obj - (format out "#" type value))) + (with-slots (value type meta) obj + (format out "#" type value meta))) + +(defun add-mal-meta (value meta) + (setf (slot-value value 'meta) meta) + value) (defun mal-value= (value1 value2) (and (equal (mal-type value1) (mal-type value2)) @@ -54,9 +60,10 @@ :initarg :type :initform ',type))) - (defun ,constructor (value) + (defun ,constructor (value &optional meta) (make-instance ',name - :value value)) + :value value + :meta meta)) (export ',name) (export ',constructor)))) From 220f511f6cc0ef35e0087b7ada6180a84e1bd023 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 16 Aug 2016 21:09:19 +0530 Subject: [PATCH 0054/2308] Make sure last duplicate entry override first entry in hash maps This completes mandatory as well as optional requirements for step 1 --- common_lisp/reader.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index 5d8980399e..a1810f6e27 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -152,7 +152,7 @@ ;; Consume the closing brace (consume reader) ;; Construct the hash table - (dolist (key-value forms) + (dolist (key-value (nreverse forms)) (setf (gethash (car key-value) hash-map) (cdr key-value))) hash-map)) From d53f21d7663cb2bb9b5ed00bce80cf7eefe3a2db Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 16 Aug 2016 21:53:34 +0530 Subject: [PATCH 0055/2308] Export eof error from reader --- common_lisp/reader.lisp | 3 ++- common_lisp/step1_read_print.lisp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index a1810f6e27..72f4ac9cb1 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -2,7 +2,8 @@ (defpackage :reader (:use :regexp :common-lisp :types) - (:export :read-str)) + (:export :read-str + :eof)) (in-package :reader) diff --git a/common_lisp/step1_read_print.lisp b/common_lisp/step1_read_print.lisp index e30cf5a605..767c3ac009 100644 --- a/common_lisp/step1_read_print.lisp +++ b/common_lisp/step1_read_print.lisp @@ -19,7 +19,7 @@ (handler-case (mal-print (mal-eval (mal-read string) (make-hash-table :test #'equal))) - (reader::eof (condition) + (reader:eof (condition) (format nil "~a" condition)))) From 2645186303dc23865a4428ea59b13791d3ce2d58 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 16 Aug 2016 22:09:17 +0530 Subject: [PATCH 0056/2308] Export MAL types and MAL accessors from types package --- common_lisp/printer.lisp | 24 ++++++++++++------------ common_lisp/types.lisp | 17 +++++++++++++++-- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/common_lisp/printer.lisp b/common_lisp/printer.lisp index e79310adf6..c7279d8103 100644 --- a/common_lisp/printer.lisp +++ b/common_lisp/printer.lisp @@ -11,11 +11,11 @@ start-delimiter (format nil "~{~A~^ ~}" - (map 'list #'pr-str (types::mal-value sequence))) + (map 'list #'pr-str (types:mal-value sequence))) end-delimiter)) (defun pr-mal-hash-map (hash-map) - (let ((hash-map-value (types::mal-value hash-map))) + (let ((hash-map-value (types:mal-value hash-map))) (concatenate 'string "{" (format nil @@ -33,16 +33,16 @@ (defun pr-str (ast) (when ast (let ((repr (switch-mal-type ast - ('types::number (format nil "~d" (types::mal-value ast))) - ('types::boolean (if (types::mal-value ast) "true" "false")) - ('types::nil "nil") - ('types::string (format nil "~s" (types::mal-value ast))) - ('types::symbol (format nil "~a" (types::mal-value ast))) - ('types::keyword (format nil ":~a" (types::mal-value ast))) - ('types::list (pr-mal-sequence "(" ast ")")) - ('types::vector (pr-mal-sequence "[" ast "]")) - ('types::hash-map (pr-mal-hash-map ast)))) - (meta (pr-str (types::mal-meta ast)))) + ('types:number (format nil "~d" (types:mal-value ast))) + ('types:boolean (if (types:mal-value ast) "true" "false")) + ('types:nil "nil") + ('types:string (format nil "~s" (types:mal-value ast))) + ('types:symbol (format nil "~a" (types:mal-value ast))) + ('types:keyword (format nil ":~a" (types:mal-value ast))) + ('types:list (pr-mal-sequence "(" ast ")")) + ('types:vector (pr-mal-sequence "[" ast "]")) + ('types:hash-map (pr-mal-hash-map ast)))) + (meta (pr-str (types:mal-meta ast)))) (if meta (format nil "(with-meta ~a ~a)" repr meta) repr)))) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 3f94d8dcb3..c237e1240f 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -19,7 +19,20 @@ ;; Helpers :apply-unwrapped-values :switch-mal-type - :add-mal-meta)) + :add-mal-meta + :mal-value + :mal-type + :mal-meta + ;; Mal values + :number + :boolean + :nil + :string + :symbol + :keyword + :list + :vector + :hash-map)) (in-package :types) @@ -79,7 +92,7 @@ (define-mal-type nil) (defmacro switch-mal-type (ast &body forms) - `(let ((type (types::mal-type ,ast))) + `(let ((type (types:mal-type ,ast))) (cond ,@(mapcar (lambda (form) (list (list 'equal (car form) 'type) From 362360df42467a6a1d094d8df372f566ee387075 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 16 Aug 2016 23:10:27 +0530 Subject: [PATCH 0057/2308] Remove obsolete 'add-mal-meta' function --- common_lisp/reader.lisp | 3 ++- common_lisp/types.lisp | 17 +---------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index 72f4ac9cb1..934378b94a 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -104,7 +104,8 @@ (error 'eof :context "object metadata")) - (types:add-mal-meta value meta))) + (setf (types:mal-meta value) meta) + value)) (defun expand-quote (reader) (let ((quote (next reader))) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index c237e1240f..f736c9e5ae 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -15,24 +15,9 @@ :list :vector :hash-map - :any ;; Helpers :apply-unwrapped-values - :switch-mal-type - :add-mal-meta - :mal-value - :mal-type - :mal-meta - ;; Mal values - :number - :boolean - :nil - :string - :symbol - :keyword - :list - :vector - :hash-map)) + :switch-mal-type)) (in-package :types) From 9e69646e7bfa770418456980c9145ba1fea820a2 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 17 Aug 2016 00:31:31 +0530 Subject: [PATCH 0058/2308] Remove the need to quote symbols in switch-mal-type --- common_lisp/printer.lisp | 18 +++++++++--------- common_lisp/types.lisp | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/common_lisp/printer.lisp b/common_lisp/printer.lisp index c7279d8103..a6f123fbe9 100644 --- a/common_lisp/printer.lisp +++ b/common_lisp/printer.lisp @@ -33,15 +33,15 @@ (defun pr-str (ast) (when ast (let ((repr (switch-mal-type ast - ('types:number (format nil "~d" (types:mal-value ast))) - ('types:boolean (if (types:mal-value ast) "true" "false")) - ('types:nil "nil") - ('types:string (format nil "~s" (types:mal-value ast))) - ('types:symbol (format nil "~a" (types:mal-value ast))) - ('types:keyword (format nil ":~a" (types:mal-value ast))) - ('types:list (pr-mal-sequence "(" ast ")")) - ('types:vector (pr-mal-sequence "[" ast "]")) - ('types:hash-map (pr-mal-hash-map ast)))) + (types:number (format nil "~d" (types:mal-value ast))) + (types:boolean (if (types:mal-value ast) "true" "false")) + (types:nil "nil") + (types:string (format nil "~s" (types:mal-value ast))) + (types:symbol (format nil "~a" (types:mal-value ast))) + (types:keyword (format nil ":~a" (types:mal-value ast))) + (types:list (pr-mal-sequence "(" ast ")")) + (types:vector (pr-mal-sequence "[" ast "]")) + (types:hash-map (pr-mal-hash-map ast)))) (meta (pr-str (types:mal-meta ast)))) (if meta (format nil "(with-meta ~a ~a)" repr meta) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index f736c9e5ae..0b0a7e9495 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -80,6 +80,6 @@ `(let ((type (types:mal-type ,ast))) (cond ,@(mapcar (lambda (form) - (list (list 'equal (car form) 'type) + (list (list 'equal (list 'quote (car form)) 'type) (cadr form))) forms)))) From a85bb5e211c9b0830fc64f325a545c5529abe843 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 17 Aug 2016 00:33:49 +0530 Subject: [PATCH 0059/2308] Support clause any in switch-mal-type to execute form for any type --- common_lisp/types.lisp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 0b0a7e9495..a35a895377 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -15,6 +15,7 @@ :list :vector :hash-map + :any ;; Helpers :apply-unwrapped-values :switch-mal-type)) @@ -76,10 +77,17 @@ (define-mal-type hash-map) (define-mal-type nil) +;; Generic type +(defvar any "any-type") + (defmacro switch-mal-type (ast &body forms) `(let ((type (types:mal-type ,ast))) (cond ,@(mapcar (lambda (form) - (list (list 'equal (list 'quote (car form)) 'type) + (list (if (or (equal (car form) t) + (equal (car form) 'any)) + t + (list 'equal (list 'quote (car form)) 'type)) (cadr form))) forms)))) + From dc3f4a5d573cab76954f4074157e8f7415ba00f0 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 17 Aug 2016 07:46:37 +0530 Subject: [PATCH 0060/2308] Change the way meta is handled This might require some more modifications in future --- common_lisp/printer.lisp | 24 ++++++++++-------------- common_lisp/reader.lisp | 3 +-- common_lisp/types.lisp | 6 +----- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/common_lisp/printer.lisp b/common_lisp/printer.lisp index a6f123fbe9..21e582dd00 100644 --- a/common_lisp/printer.lisp +++ b/common_lisp/printer.lisp @@ -32,17 +32,13 @@ (defun pr-str (ast) (when ast - (let ((repr (switch-mal-type ast - (types:number (format nil "~d" (types:mal-value ast))) - (types:boolean (if (types:mal-value ast) "true" "false")) - (types:nil "nil") - (types:string (format nil "~s" (types:mal-value ast))) - (types:symbol (format nil "~a" (types:mal-value ast))) - (types:keyword (format nil ":~a" (types:mal-value ast))) - (types:list (pr-mal-sequence "(" ast ")")) - (types:vector (pr-mal-sequence "[" ast "]")) - (types:hash-map (pr-mal-hash-map ast)))) - (meta (pr-str (types:mal-meta ast)))) - (if meta - (format nil "(with-meta ~a ~a)" repr meta) - repr)))) + (switch-mal-type ast + (types:number (format nil "~d" (types:mal-value ast))) + (types:boolean (if (types:mal-value ast) "true" "false")) + (types:nil "nil") + (types:string (format nil "~s" (types:mal-value ast))) + (types:symbol (format nil "~a" (types:mal-value ast))) + (types:keyword (format nil ":~a" (types:mal-value ast))) + (types:list (pr-mal-sequence "(" ast ")")) + (types:vector (pr-mal-sequence "[" ast "]")) + (types:hash-map (pr-mal-hash-map ast))))) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index 934378b94a..c138bdec25 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -104,8 +104,7 @@ (error 'eof :context "object metadata")) - (setf (types:mal-meta value) meta) - value)) + (make-mal-list (list (make-mal-symbol '|with-meta|) value meta)))) (defun expand-quote (reader) (let ((quote (next reader))) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index a35a895377..1fab8f5e11 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -24,17 +24,13 @@ (defclass mal-type () ((value :accessor mal-value :initarg :value) - (meta :accessor mal-meta :initarg :meta) + (meta :accessor mal-meta :initarg :meta :initform nil) (type :accessor mal-type :initarg :type))) (defmethod print-object ((obj mal-type) out) (with-slots (value type meta) obj (format out "#" type value meta))) -(defun add-mal-meta (value meta) - (setf (slot-value value 'meta) meta) - value) - (defun mal-value= (value1 value2) (and (equal (mal-type value1) (mal-type value2)) (equal (mal-value value1) (mal-value value2)))) From 60072c86fc0fb678c04e9a48453aec453ff46728 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 17 Aug 2016 08:28:43 +0530 Subject: [PATCH 0061/2308] Teach MAL about number signs --- common_lisp/reader.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index c138bdec25..0cdaeba07d 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -160,7 +160,7 @@ (defun read-atom (reader) (let ((token (next reader))) (cond - ((regexp:match "^[[:digit:]]\\+$" token) + ((regexp:match "^\\(-\\|+\\)\\?[[:digit:]]\\+$" token) (make-mal-number (read-from-string token))) ((string= token "false") (make-mal-boolean nil)) From 24c07f4c13e35dd2d1147a59caf14b6d901a1129 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 17 Aug 2016 08:28:59 +0530 Subject: [PATCH 0062/2308] Export predicates for checking MAL types from types package --- common_lisp/types.lisp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 1fab8f5e11..8c5fc478d3 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -49,7 +49,11 @@ (symbol-name type))))) (constructor (intern (string-upcase (concatenate 'string "make-mal-" - (symbol-name type)))))) + (symbol-name type))))) + (predicate (intern (string-upcase (concatenate 'string + "mal-" + (symbol-name type) + "-p"))))) `(progn (defclass ,name (mal-type) ((type :accessor mal-type :initarg :type @@ -59,9 +63,12 @@ (make-instance ',name :value value :meta meta)) + (defun ,predicate (value) + (equal (mal-type value) ',type)) (export ',name) - (export ',constructor)))) + (export ',constructor) + (export ',predicate)))) (define-mal-type number) (define-mal-type symbol) @@ -87,3 +94,5 @@ (cadr form))) forms)))) +(defun apply-unwrapped-values (op &rest values) + (apply op (mapcar #'mal-value values))) From 3747635d6e8c743094d0af3cbae77077eb720415 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 20 Aug 2016 22:15:22 +0530 Subject: [PATCH 0063/2308] Add printer for built-in functions --- common_lisp/printer.lisp | 3 ++- common_lisp/types.lisp | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/common_lisp/printer.lisp b/common_lisp/printer.lisp index 21e582dd00..7a1021f9d8 100644 --- a/common_lisp/printer.lisp +++ b/common_lisp/printer.lisp @@ -41,4 +41,5 @@ (types:keyword (format nil ":~a" (types:mal-value ast))) (types:list (pr-mal-sequence "(" ast ")")) (types:vector (pr-mal-sequence "[" ast "]")) - (types:hash-map (pr-mal-hash-map ast))))) + (types:hash-map (pr-mal-hash-map ast)) + (types:builtin-fn "#")))) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 8c5fc478d3..b97ae8ca36 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -15,6 +15,7 @@ :list :vector :hash-map + :builtin-fn :any ;; Helpers :apply-unwrapped-values @@ -79,6 +80,7 @@ (define-mal-type vector) (define-mal-type hash-map) (define-mal-type nil) +(define-mal-type builtin-fn) ;; Generic type (defvar any "any-type") From b15826e999a51ff271d169f5431a1564c1f3f706 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 17 Aug 2016 08:53:12 +0530 Subject: [PATCH 0064/2308] Complete implmentation of step 2 (eval) --- common_lisp/env.lisp | 15 +++++ common_lisp/step2_eval.lisp | 109 ++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 common_lisp/env.lisp create mode 100644 common_lisp/step2_eval.lisp diff --git a/common_lisp/env.lisp b/common_lisp/env.lisp new file mode 100644 index 0000000000..a5ce21b8a6 --- /dev/null +++ b/common_lisp/env.lisp @@ -0,0 +1,15 @@ +(require "types") + +(defpackage :env + (:use :common-lisp :types) + (:export :lookup-env + :undefined-symbol)) + +(in-package :env) + +(define-condition undefined-symbol (error) + ((symbol :initarg :symbol :reader symbol)) + (:report (lambda (condition stream) + (format stream + "Symbol ~a is undefined" + (symbol condition))))) diff --git a/common_lisp/step2_eval.lisp b/common_lisp/step2_eval.lisp new file mode 100644 index 0000000000..4c3a63a6ca --- /dev/null +++ b/common_lisp/step2_eval.lisp @@ -0,0 +1,109 @@ +(require "reader") +(require "printer") +(require "types") +(require "env") + +(defpackage :mal + (:use :common-lisp :types :env :reader :printer)) + +(in-package :mal) + +;; Environment + +(defvar *repl-env* (make-hash-table :test 'types:mal-value=)) + +(setf (gethash (types:make-mal-symbol '+) *repl-env*) + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:make-mal-number (apply-unwrapped-values '+ + value1 + value2))))) + +(setf (gethash (types:make-mal-symbol '-) *repl-env*) + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:make-mal-number (apply-unwrapped-values '- + value1 + value2))))) + +(setf (gethash (types:make-mal-symbol '*) *repl-env*) + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:make-mal-number (apply-unwrapped-values '* + value1 + value2))))) + +(setf (gethash (types:make-mal-symbol '/) *repl-env*) + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:make-mal-number (apply-unwrapped-values '/ + value1 + value2))))) + +(defun lookup-env (symbol env) + (let ((value (gethash symbol env))) + (if value + value + (error 'env:undefined-symbol + :symbol (format nil "~a" (types:mal-value symbol)))))) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + (cond + ((not (types:mal-list-p ast)) (eval-ast ast env)) + ((zerop (length (mal-value ast))) ast) + (t (progn + (let ((evaluated-list (eval-ast ast env))) + (apply (mal-value (car evaluated-list)) + (cdr evaluated-list))))))) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (mal-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (mal-value hash-map)) + (new-hash-table (make-hash-table :test 'types:mal-value=))) + (loop + for key being the hash-keys of hash-map-value + do (setf (gethash key new-hash-table) + (mal-eval (gethash key hash-map-value) env))) + (make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (lookup-env ast env)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env )) + (types:any ast))) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (reader:eof (condition) + (format nil + "~a" + condition)) + (env:undefined-symbol (condition) + (format nil + "~a" + condition)))) + +(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) + (format out-stream prompt) + (force-output out-stream) + (read-line in-stream nil)) + +(defun writeline (string) + (when string + (write-line string))) + +(defun main () + (loop do (let ((line (readline "user> "))) + (if line (writeline (rep line)) (return))))) + +(main) From c9d4d92e8cc8d841ca110900533fb9d6d2c57bd1 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 17 Aug 2016 16:08:46 +0530 Subject: [PATCH 0065/2308] Update apply-unwrapped-values to also wrap the result into a mal type --- common_lisp/step2_eval.lisp | 24 ++++++++++++------------ common_lisp/types.lisp | 13 ++++++++++++- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/common_lisp/step2_eval.lisp b/common_lisp/step2_eval.lisp index 4c3a63a6ca..8a4e41dc52 100644 --- a/common_lisp/step2_eval.lisp +++ b/common_lisp/step2_eval.lisp @@ -14,27 +14,27 @@ (setf (gethash (types:make-mal-symbol '+) *repl-env*) (types:make-mal-builtin-fn (lambda (value1 value2) - (types:make-mal-number (apply-unwrapped-values '+ - value1 - value2))))) + (apply-unwrapped-values '+ + value1 + value2)))) (setf (gethash (types:make-mal-symbol '-) *repl-env*) (types:make-mal-builtin-fn (lambda (value1 value2) - (types:make-mal-number (apply-unwrapped-values '- - value1 - value2))))) + (apply-unwrapped-values '- + value1 + value2)))) (setf (gethash (types:make-mal-symbol '*) *repl-env*) (types:make-mal-builtin-fn (lambda (value1 value2) - (types:make-mal-number (apply-unwrapped-values '* - value1 - value2))))) + (apply-unwrapped-values '* + value1 + value2)))) (setf (gethash (types:make-mal-symbol '/) *repl-env*) (types:make-mal-builtin-fn (lambda (value1 value2) - (types:make-mal-number (apply-unwrapped-values '/ - value1 - value2))))) + (apply-unwrapped-values '/ + value1 + value2)))) (defun lookup-env (symbol env) (let ((value (gethash symbol env))) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index b97ae8ca36..faa3982342 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -97,4 +97,15 @@ forms)))) (defun apply-unwrapped-values (op &rest values) - (apply op (mapcar #'mal-value values))) + (let ((value (apply op (mapcar #'mal-value values)))) + (funcall (typecase value + (number #'make-mal-number) + (symbol #'make-mal-number) + (keyword #'make-mal-keyword) + (string #'make-mal-string) + (boolean #'make-mal-boolean) + (list #'make-mal-list) + (vector #'make-mal-vector) + (hash-map #'make-mal-hash-map) + (null #'make-mal-nil)) + value))) From e2e12ba1509c4ac7194d7b24c75eaf9faeb34009 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 17 Aug 2016 16:17:27 +0530 Subject: [PATCH 0066/2308] Add mal-environment class --- common_lisp/env.lisp | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/common_lisp/env.lisp b/common_lisp/env.lisp index a5ce21b8a6..238ce16720 100644 --- a/common_lisp/env.lisp +++ b/common_lisp/env.lisp @@ -2,8 +2,10 @@ (defpackage :env (:use :common-lisp :types) - (:export :lookup-env - :undefined-symbol)) + (:export :undefined-symbol + :mal-environment + :get-env + :set-env)) (in-package :env) @@ -13,3 +15,38 @@ (format stream "Symbol ~a is undefined" (symbol condition))))) + +(defclass mal-environment () + ((bindings :initarg :bindings + :accessor mal-env-bindings + :initform (make-hash-table :test 'types:mal-value=)) + (parent :initarg :parent + :accessor mal-env-parent + :initform nil))) + +(defgeneric find-env (mal-environment symbol) + (:documentation "Find value of a symbol in given environment, return nil if not binding is found")) + +(defgeneric get-env (mal-environment symbol) + (:documentation "Get value of a symbol in given environment, raises undefined-symbol error if lookup fails")) + +(defgeneric set-env (mal-environment symbol value) + (:documentation "Set the value for a symbol in given environment")) + +(defmethod find-env ((env mal-environment) symbol) + (let ((value (gethash symbol (mal-env-bindings env))) + (parent (mal-env-parent env))) + (cond + (value value) + (parent (find-env parent symbol)) + (t nil)))) + +(defmethod get-env ((env mal-environment) symbol) + (let ((value (find-env env symbol))) + (if value + value + (error 'undefined-symbol + :symbol (format nil "~a" (types:mal-value symbol)))))) + +(defmethod set-env ((env mal-environment) symbol value) + (setf (gethash symbol (mal-env-bindings env)) value)) From 49b184f03885bce3d334a67c0b7641235617e52c Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 17 Aug 2016 22:19:03 +0530 Subject: [PATCH 0067/2308] Implement step 3 (environment) --- common_lisp/step3_env.lisp | 126 +++++++++++++++++++++++++++++++++++++ common_lisp/types.lisp | 2 +- 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 common_lisp/step3_env.lisp diff --git a/common_lisp/step3_env.lisp b/common_lisp/step3_env.lisp new file mode 100644 index 0000000000..2a2ab9ad3f --- /dev/null +++ b/common_lisp/step3_env.lisp @@ -0,0 +1,126 @@ +(require "reader") +(require "printer") +(require "types") +(require "env") + +(defpackage :mal + (:use :common-lisp :types :env :reader :printer)) + +(in-package :mal) + +(defvar *repl-env* (make-instance 'env:mal-environment)) + +(set-env *repl-env* + (types:make-mal-symbol '+) + (types:make-mal-builtin-fn (lambda (value1 value2) + (apply-unwrapped-values '+ value1 value2)))) + +(set-env *repl-env* + (types:make-mal-symbol '-) + (types:make-mal-builtin-fn (lambda (value1 value2) + (apply-unwrapped-values '- value1 value2)))) + +(set-env *repl-env* + (types:make-mal-symbol '*) + (types:make-mal-builtin-fn (lambda (value1 value2) + (apply-unwrapped-values '* value1 value2)))) + +(set-env *repl-env* + (types:make-mal-symbol '/) + (types:make-mal-builtin-fn (lambda (value1 value2) + (apply-unwrapped-values '/ value1 value2)))) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (mal-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (mal-value hash-map)) + (new-hash-table (make-hash-table :test 'types:mal-value=))) + (loop + for key being the hash-keys of hash-map-value + do (setf (gethash key new-hash-table) + (mal-eval (gethash key hash-map-value) env))) + (make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env )) + (types:any ast))) + +(defun eval-let* (forms env) + (let ((new-env (make-instance 'env:mal-environment + :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (types:mal-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + (types:make-mal-nil nil)) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + + (mal-eval (third forms) new-env))) + +(defun eval-list (ast env) + (let ((forms (mal-value ast))) + (cond + ((mal-value= (make-mal-symbol '|def!|) (first forms)) + (env:set-env env (second forms) (mal-eval (third forms) env))) + ((mal-value= (make-mal-symbol '|let*|) (first forms)) + (eval-let* forms env)) + (t (let ((evaluated-list (eval-ast ast env))) + (apply (types:mal-value (car evaluated-list)) + (cdr evaluated-list))))))) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + (cond + ((null ast) (make-mal-nil nil)) + ((not (types:mal-list-p ast)) (eval-ast ast env)) + ((zerop (length (mal-value ast))) ast) + (t (eval-list ast env)))) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (reader:eof (condition) + (format nil + "~a" + condition)) + (env:undefined-symbol (condition) + (format nil + "~a" + condition)))) + +(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) + (format out-stream prompt) + (force-output out-stream) + (read-line in-stream nil)) + +(defun writeline (string) + (when string + (write-line string))) + +(defun main () + (loop do (let ((line (readline "user> "))) + (if line (writeline (rep line)) (return))))) + +(main) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index faa3982342..3853ac4413 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -83,7 +83,7 @@ (define-mal-type builtin-fn) ;; Generic type -(defvar any "any-type") +(defvar any) (defmacro switch-mal-type (ast &body forms) `(let ((type (types:mal-type ,ast))) From e4d7c6acc3c1b279234e0801e91892de9c68d64a Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Thu, 18 Aug 2016 00:01:44 +0530 Subject: [PATCH 0068/2308] Add support for printing functions --- common_lisp/printer.lisp | 1 + common_lisp/types.lisp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/common_lisp/printer.lisp b/common_lisp/printer.lisp index 7a1021f9d8..18a3c1cc50 100644 --- a/common_lisp/printer.lisp +++ b/common_lisp/printer.lisp @@ -42,4 +42,5 @@ (types:list (pr-mal-sequence "(" ast ")")) (types:vector (pr-mal-sequence "[" ast "]")) (types:hash-map (pr-mal-hash-map ast)) + (types:fn "#") (types:builtin-fn "#")))) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 3853ac4413..34979b960b 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -75,11 +75,15 @@ (define-mal-type symbol) (define-mal-type keyword) (define-mal-type string) +;; TODO true, false and nil should ideally be singleton (define-mal-type boolean) +(define-mal-type nil) + (define-mal-type list) (define-mal-type vector) (define-mal-type hash-map) -(define-mal-type nil) + +(define-mal-type fn) (define-mal-type builtin-fn) ;; Generic type From d3a0ddde0ea216cb415c9f870190d91b7c6a1dd3 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Thu, 18 Aug 2016 00:01:59 +0530 Subject: [PATCH 0069/2308] Allow specifying initial set of bindings while creating new environment --- common_lisp/env.lisp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/common_lisp/env.lisp b/common_lisp/env.lisp index 238ce16720..880bab5120 100644 --- a/common_lisp/env.lisp +++ b/common_lisp/env.lisp @@ -50,3 +50,17 @@ (defmethod set-env ((env mal-environment) symbol value) (setf (gethash symbol (mal-env-bindings env)) value)) + +(defmethod initialize-instance :after ((env mal-environment) + &key (bindings nil) + (parent nil) + (binds nil) + (exprs nil)) + (let ((arg-params (loop + for x in binds + for y in exprs + collect (cons x y)))) + (dolist (arg-param arg-params) + (set-env env + (car arg-param) + (cdr arg-param))))) From 579201bbf3ff2fa63518cc81beecb92b7e88d4c7 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Thu, 18 Aug 2016 00:02:43 +0530 Subject: [PATCH 0070/2308] Implement fn*, do and let* --- common_lisp/step4_if_fn_do.lisp | 150 ++++++++++++++++++++++++++++++++ common_lisp/types.lisp | 4 +- 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 common_lisp/step4_if_fn_do.lisp diff --git a/common_lisp/step4_if_fn_do.lisp b/common_lisp/step4_if_fn_do.lisp new file mode 100644 index 0000000000..f919e56717 --- /dev/null +++ b/common_lisp/step4_if_fn_do.lisp @@ -0,0 +1,150 @@ +(require "reader") +(require "printer") +(require "types") +(require "env") + +(defpackage :mal + (:use :common-lisp :types :env :reader :printer)) + +(in-package :mal) + +(defvar *repl-env* (make-instance 'env:mal-environment)) + +(set-env *repl-env* + (types:make-mal-symbol '+) + (types:make-mal-builtin-fn (lambda (value1 value2) + (apply-unwrapped-values '+ value1 value2)))) + +(set-env *repl-env* + (types:make-mal-symbol '-) + (types:make-mal-builtin-fn (lambda (value1 value2) + (apply-unwrapped-values '- value1 value2)))) + +(set-env *repl-env* + (types:make-mal-symbol '*) + (types:make-mal-builtin-fn (lambda (value1 value2) + (apply-unwrapped-values '* value1 value2)))) + +(set-env *repl-env* + (types:make-mal-symbol '/) + (types:make-mal-builtin-fn (lambda (value1 value2) + (apply-unwrapped-values '/ value1 value2)))) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (mal-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (mal-value hash-map)) + (new-hash-table (make-hash-table :test 'types:mal-value=))) + (loop + for key being the hash-keys of hash-map-value + do (setf (gethash key new-hash-table) + (mal-eval (gethash key hash-map-value) env))) + (make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env)) + (types:any ast))) + +(defun eval-let* (forms env) + (let ((new-env (make-instance 'env:mal-environment + :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (mal-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + (types:make-mal-nil nil)) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + + (mal-eval (third forms) new-env))) + +(defun eval-list (ast env) + (let ((forms (mal-value ast))) + (cond + ((mal-value= (make-mal-symbol '|def!|) (first forms)) + (env:set-env env (second forms) (mal-eval (third forms) env))) + ((mal-value= (make-mal-symbol '|let*|) (first forms)) + (eval-let* forms env)) + ((mal-value= (make-mal-symbol '|do|) (first forms)) + (car (last (mapcar (lambda (form) (mal-eval form env)) + (cdr forms))))) + ((mal-value= (make-mal-symbol '|if|) (first forms)) + (let ((predicate (mal-eval (second forms) env))) + (mal-eval (if (or (mal-value= predicate (types:make-mal-nil nil)) + (mal-value= predicate (types:make-mal-boolean nil))) + (fourth forms) + (third forms)) + env))) + ((mal-value= (make-mal-symbol '|fn*|) (first forms)) + (types:make-mal-fn (let ((arglist (second forms)) + (body (third forms))) + (lambda (&rest args) + (mal-eval body (make-instance 'env:mal-environment + :parent env + :binds (map 'list + #'identity + (mal-value arglist)) + :exprs args)))))) + (t (let* ((evaluated-list (eval-ast ast env)) + (function (car evaluated-list))) + ;; If first element is a mal function unwrap it + (apply (if (types:mal-fn-p function) + (mal-value function) + function) + (cdr evaluated-list))))))) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + (cond + ((null ast) (make-mal-nil nil)) + ((not (types:mal-list-p ast)) (eval-ast ast env)) + ((zerop (length (mal-value ast))) ast) + (t (eval-list ast env)))) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (reader:eof (condition) + (format nil + "~a" + condition)) + (env:undefined-symbol (condition) + (format nil + "~a" + condition)))) + +(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) + (format out-stream prompt) + (force-output out-stream) + (read-line in-stream nil)) + +(defun writeline (string) + (when string + (write-line string))) + +(defun main () + (loop do (let ((line (readline "user> "))) + (if line (writeline (rep line)) (return))))) + +(main) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 34979b960b..7f288802cc 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -15,6 +15,7 @@ :list :vector :hash-map + :fn :builtin-fn :any ;; Helpers @@ -65,7 +66,8 @@ :value value :meta meta)) (defun ,predicate (value) - (equal (mal-type value) ',type)) + (when (typep value 'mal-type) + (equal (mal-type value) ',type))) (export ',name) (export ',constructor) From 623c5d2fa8e3ec3bafa68ba6f35f86be4c750b90 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Thu, 18 Aug 2016 18:58:58 +0530 Subject: [PATCH 0071/2308] Initial attempt at adding core functions required for step 4 --- common_lisp/core.lisp | 61 +++++++++++++++++++++++++++++++++ common_lisp/reader.lisp | 18 +++++++++- common_lisp/step4_if_fn_do.lisp | 37 +++++++------------- common_lisp/types.lisp | 27 ++++++++------- 4 files changed, 106 insertions(+), 37 deletions(-) create mode 100644 common_lisp/core.lisp diff --git a/common_lisp/core.lisp b/common_lisp/core.lisp new file mode 100644 index 0000000000..7795738fed --- /dev/null +++ b/common_lisp/core.lisp @@ -0,0 +1,61 @@ +(load "types") + +(defpackage :core + (:use :common-lisp :types) + (:export :ns)) + +(in-package :core) + +(defvar ns + (list + (cons (types:make-mal-symbol '+) + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:apply-unwrapped-values '+ value1 value2)))) + + (cons (types:make-mal-symbol '-) + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:apply-unwrapped-values '- value1 value2)))) + + (cons (types:make-mal-symbol '*) + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:apply-unwrapped-values '* value1 value2)))) + + (cons (types:make-mal-symbol '/) + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:apply-unwrapped-values '/ value1 value2)))) + + (cons (types:make-mal-symbol '|list|) + (types:make-mal-builtin-fn (lambda (&rest values) + (make-mal-list values)))) + + (cons (types:make-mal-symbol '|list?|) + (types:make-mal-builtin-fn (lambda (value) + (types:wrap-value (types:mal-list-p value))))) + + (cons (types:make-mal-symbol '|empty?|) + (types:make-mal-builtin-fn (lambda (value) + (types:apply-unwrapped-values 'null value)))) + + (cons (types:make-mal-symbol '|count|) + (types:make-mal-builtin-fn (lambda (value) + (types:apply-unwrapped-values 'length value)))) + + (cons (types:make-mal-symbol '=) + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:wrap-value (types:mal-value= value1 value2))))) + + (cons (types:make-mal-symbol '<) + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:apply-unwrapped-values '< value1 value2)))) + + (cons (types:make-mal-symbol '>) + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:apply-unwrapped-values '> value1 value2)))) + + (cons (types:make-mal-symbol '<=) + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:apply-unwrapped-values '<= value1 value2)))) + + (cons (types:make-mal-symbol '>=) + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:apply-unwrapped-values '>= value1 value2)))))) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index 0cdaeba07d..e34b6bec67 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -23,10 +23,26 @@ "EOF encountered while reading ~a" (context condition))))) +(defun replace-all (string part replacement &key (test #'char=)) +"Returns a new string in which all the occurences of the part +is replaced with replacement." + (with-output-to-string (out) + (loop with part-length = (length part) + for old-pos = 0 then (+ pos part-length) + for pos = (search part string + :start2 old-pos + :test test) + do (write-string string out + :start old-pos + :end (or pos (length string))) + when pos do (write-string replacement out) + while pos))) + (defun parse-string (token) (if (and (> (length token) 1) (regexp:match *string-re* token)) - (read-from-string token) + (read-from-string (replace-all token "\\n" " +")) ;; A bit inaccurate (error 'eof :context "string"))) diff --git a/common_lisp/step4_if_fn_do.lisp b/common_lisp/step4_if_fn_do.lisp index f919e56717..a2f7e7fe84 100644 --- a/common_lisp/step4_if_fn_do.lisp +++ b/common_lisp/step4_if_fn_do.lisp @@ -2,33 +2,24 @@ (require "printer") (require "types") (require "env") +(require "core") (defpackage :mal - (:use :common-lisp :types :env :reader :printer)) + (:use :common-lisp + :types + :env + :reader + :printer + :core)) (in-package :mal) (defvar *repl-env* (make-instance 'env:mal-environment)) -(set-env *repl-env* - (types:make-mal-symbol '+) - (types:make-mal-builtin-fn (lambda (value1 value2) - (apply-unwrapped-values '+ value1 value2)))) - -(set-env *repl-env* - (types:make-mal-symbol '-) - (types:make-mal-builtin-fn (lambda (value1 value2) - (apply-unwrapped-values '- value1 value2)))) - -(set-env *repl-env* - (types:make-mal-symbol '*) - (types:make-mal-builtin-fn (lambda (value1 value2) - (apply-unwrapped-values '* value1 value2)))) - -(set-env *repl-env* - (types:make-mal-symbol '/) - (types:make-mal-builtin-fn (lambda (value1 value2) - (apply-unwrapped-values '/ value1 value2)))) +(dolist (binding core:ns) + (env:set-env *repl-env* + (car binding) + (cdr binding))) (defun eval-sequence (sequence env) (map 'list @@ -103,9 +94,7 @@ (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) ;; If first element is a mal function unwrap it - (apply (if (types:mal-fn-p function) - (mal-value function) - function) + (apply (mal-value function) (cdr evaluated-list))))))) (defun mal-read (string) @@ -147,4 +136,4 @@ (loop do (let ((line (readline "user> "))) (if line (writeline (rep line)) (return))))) -(main) +;(main) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 7f288802cc..ace9716b26 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -19,6 +19,7 @@ :builtin-fn :any ;; Helpers + :wrap-value :apply-unwrapped-values :switch-mal-type)) @@ -102,16 +103,18 @@ (cadr form))) forms)))) +(defun wrap-value (value) + (funcall (typecase value + (number #'make-mal-number) + (symbol #'make-mal-number) + (keyword #'make-mal-keyword) + (string #'make-mal-string) + (boolean #'make-mal-boolean) + (list #'make-mal-list) + (vector #'make-mal-vector) + (hash-map #'make-mal-hash-map) + (null #'make-mal-nil)) + value)) + (defun apply-unwrapped-values (op &rest values) - (let ((value (apply op (mapcar #'mal-value values)))) - (funcall (typecase value - (number #'make-mal-number) - (symbol #'make-mal-number) - (keyword #'make-mal-keyword) - (string #'make-mal-string) - (boolean #'make-mal-boolean) - (list #'make-mal-list) - (vector #'make-mal-vector) - (hash-map #'make-mal-hash-map) - (null #'make-mal-nil)) - value))) + (wrap-value (apply op (mapcar #'mal-value values)))) From 5cd6b623abc2b7a5704bb785db57bf4dd22673d4 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Thu, 18 Aug 2016 20:58:14 +0530 Subject: [PATCH 0072/2308] Use symbol name while comparing mal-symbols --- common_lisp/types.lisp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index ace9716b26..9ca5430182 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -36,7 +36,10 @@ (defun mal-value= (value1 value2) (and (equal (mal-type value1) (mal-type value2)) - (equal (mal-value value1) (mal-value value2)))) + (if (mal-symbol-p value1) + (string= (symbol-name (mal-value value1)) + (symbol-name (mal-value value2))) + (equal (mal-value value1) (mal-value value2))))) (defun hash-mal-value (value) (sxhash (mal-value value))) From de8ef209c284ddf73e53bc82fe7e979d7bdcdff0 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Thu, 18 Aug 2016 22:08:14 +0530 Subject: [PATCH 0073/2308] Teach mal-value= to compare list and hash-maps --- common_lisp/types.lisp | 58 ++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 9ca5430182..020e159c0c 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -34,19 +34,6 @@ (with-slots (value type meta) obj (format out "#" type value meta))) -(defun mal-value= (value1 value2) - (and (equal (mal-type value1) (mal-type value2)) - (if (mal-symbol-p value1) - (string= (symbol-name (mal-value value1)) - (symbol-name (mal-value value2))) - (equal (mal-value value1) (mal-value value2))))) - -(defun hash-mal-value (value) - (sxhash (mal-value value))) - -#+sbcl (sb-ext:define-hash-table-test mal-value= hash-mal-value) -#+clisp (ext:define-hash-table-test mal-value= mal-value= hash-mal-value) - (defmacro define-mal-type (type) ;; Create a class for given type and a convenience constructor and also export ;; them @@ -106,7 +93,50 @@ (cadr form))) forms)))) -(defun wrap-value (value) +(defun mal-symbol= (value1 value2) + (string= (symbol-name (mal-value value1)) + (symbol-name (mal-value value2)))) + +(defun mal-sequence= (value1 value2) + (let ((sequence1 (map 'list #'identity (mal-value value1))) + (sequence2 (map 'list #'identity (mal-value value2)))) + (when (= (length sequence1) (length sequence2)) + (every #'identity + (loop + for x in sequence1 + for y in sequence2 + collect (mal-value= x y)))))) + +(defun mal-hash-map= (value1 value2) + (let ((map1 (mal-value value1)) + (map2 (mal-value value2))) + (when (= (hash-table-count map1) (hash-table-count map2)) + (every #'identity + (loop + for key being the hash-keys of map1 + collect (mal-value= (gethash key map1) + (gethash key map2))))))) + +(defun mal-value= (value1 value2) + (when (equal (mal-type value1) (mal-type value2)) + (switch-mal-type value1 + (number (= (mal-value value1) (mal-value value2))) + (boolean (equal (mal-value value1) (mal-value value2))) + (nil (equal (mal-value value1) (mal-value value2))) + (string (string= (mal-value value1) (mal-value value2))) + (symbol (mal-symbol= value1 value2)) + (keyword (mal-symbol= value1 value2)) + (list (mal-sequence= value1 value2)) + (vector (mal-sequence= value1 value2)) + (hash-map (mal-hash-map= value1 value2)) + (any nil)))) + +(defun hash-mal-value (value) + (sxhash (mal-value value))) + +#+sbcl (sb-ext:define-hash-table-test mal-value= hash-mal-value) +#+clisp (ext:define-hash-table-test mal-value= mal-value= hash-mal-value) + (funcall (typecase value (number #'make-mal-number) (symbol #'make-mal-number) From d584319abb78e946a8c23038737d1030934e0c35 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Thu, 18 Aug 2016 22:08:57 +0530 Subject: [PATCH 0074/2308] Fix wrapping of Common Lisp value into MAL values 1) Since nil and t are symbols also, we need the specific clauses i.e. null and boolean before symbol clause 2) Since nil stands for both an empty list as well as false in common lisp as opposed to MAL where these are distinct values, we need to use explicit flag while wrapping them --- common_lisp/core.lisp | 23 ++++++++++++++++------- common_lisp/types.lisp | 16 +++++++++++++--- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/common_lisp/core.lisp b/common_lisp/core.lisp index 7795738fed..bec29e5a8f 100644 --- a/common_lisp/core.lisp +++ b/common_lisp/core.lisp @@ -30,11 +30,12 @@ (cons (types:make-mal-symbol '|list?|) (types:make-mal-builtin-fn (lambda (value) - (types:wrap-value (types:mal-list-p value))))) + (types:make-mal-boolean (types:mal-list-p value))))) (cons (types:make-mal-symbol '|empty?|) (types:make-mal-builtin-fn (lambda (value) - (types:apply-unwrapped-values 'null value)))) + (types:apply-unwrapped-values-prefer-bool 'null + value)))) (cons (types:make-mal-symbol '|count|) (types:make-mal-builtin-fn (lambda (value) @@ -42,20 +43,28 @@ (cons (types:make-mal-symbol '=) (types:make-mal-builtin-fn (lambda (value1 value2) - (types:wrap-value (types:mal-value= value1 value2))))) + (types:make-mal-boolean (types:mal-value= value1 value2))))) (cons (types:make-mal-symbol '<) (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values '< value1 value2)))) + (types:apply-unwrapped-values-prefer-bool '< + value1 + value2)))) (cons (types:make-mal-symbol '>) (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values '> value1 value2)))) + (types:apply-unwrapped-values-prefer-bool '> + value1 + value2)))) (cons (types:make-mal-symbol '<=) (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values '<= value1 value2)))) + (types:apply-unwrapped-values-prefer-bool '<= + value1 + value2)))) (cons (types:make-mal-symbol '>=) (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values '>= value1 value2)))))) + (types:apply-unwrapped-values-prefer-bool '>= + value1 + value2)))))) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 020e159c0c..72195f987a 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -21,6 +21,7 @@ ;; Helpers :wrap-value :apply-unwrapped-values + :apply-unwrapped-values-prefer-bool :switch-mal-type)) (in-package :types) @@ -137,17 +138,26 @@ #+sbcl (sb-ext:define-hash-table-test mal-value= hash-mal-value) #+clisp (ext:define-hash-table-test mal-value= mal-value= hash-mal-value) +(defun wrap-value (value &key booleanp) (funcall (typecase value (number #'make-mal-number) - (symbol #'make-mal-number) + ;; This needs to before symbol since nil is a symbol + (null (if booleanp + #'make-mal-boolean + #'make-mal-nil)) + ;; This needs to before symbol since nil is a symbol + (boolean #'make-mal-boolean) + (symbol #'make-mal-symbol) (keyword #'make-mal-keyword) (string #'make-mal-string) - (boolean #'make-mal-boolean) (list #'make-mal-list) (vector #'make-mal-vector) - (hash-map #'make-mal-hash-map) + (hash-table #'make-mal-hash-map) (null #'make-mal-nil)) value)) (defun apply-unwrapped-values (op &rest values) (wrap-value (apply op (mapcar #'mal-value values)))) + +(defun apply-unwrapped-values-prefer-bool (op &rest values) + (wrap-value (apply op (mapcar #'mal-value values)) :booleanp t)) From d9c57b736ced46780f0514a9ba8843cb319caa39 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Thu, 18 Aug 2016 22:12:01 +0530 Subject: [PATCH 0075/2308] Make sure the REPL does not crash on unknown errors --- common_lisp/step4_if_fn_do.lisp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common_lisp/step4_if_fn_do.lisp b/common_lisp/step4_if_fn_do.lisp index a2f7e7fe84..9ab2a3fdd6 100644 --- a/common_lisp/step4_if_fn_do.lisp +++ b/common_lisp/step4_if_fn_do.lisp @@ -119,6 +119,10 @@ "~a" condition)) (env:undefined-symbol (condition) + (format nil + "~a" + condition)) + (error (condition) (format nil "~a" condition)))) @@ -136,4 +140,4 @@ (loop do (let ((line (readline "user> "))) (if line (writeline (rep line)) (return))))) -;(main) +(main) From c678a4fa5793c870cd3cbd3c2bc080331a20e205 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Fri, 19 Aug 2016 00:28:34 +0530 Subject: [PATCH 0076/2308] Add support for variadic arguments --- common_lisp/env.lisp | 55 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/common_lisp/env.lisp b/common_lisp/env.lisp index 880bab5120..0b7a035e7e 100644 --- a/common_lisp/env.lisp +++ b/common_lisp/env.lisp @@ -16,6 +16,15 @@ "Symbol ~a is undefined" (symbol condition))))) +(define-condition arity-mismatch (error) + ((required :initarg :required :reader required) + (provided :initarg :provided :reader provided)) + (:report (lambda (condition stream) + (format stream + "Unexpected number of arguments provided, expected ~a, got ~a" + (required condition) + (provided condition))))) + (defclass mal-environment () ((bindings :initarg :bindings :accessor mal-env-bindings @@ -56,11 +65,41 @@ (parent nil) (binds nil) (exprs nil)) - (let ((arg-params (loop - for x in binds - for y in exprs - collect (cons x y)))) - (dolist (arg-param arg-params) - (set-env env - (car arg-param) - (cdr arg-param))))) + (let ((varidiac-position (position (types:make-mal-symbol '&) + binds + :test #'mal-value=))) + (when varidiac-position + (setf (subseq binds varidiac-position (length binds)) + (list (nth (1+ varidiac-position) binds))) + (setf binds (subseq binds 0 (1+ varidiac-position))) + + (let* ((no-of-args (length exprs)) + ;; There are enough arguments for variadic operator + ;; to consume + (rest-args (cond ((>= no-of-args (1+ varidiac-position)) + (make-mal-list (subseq exprs + varidiac-position + (length exprs)))) + ;; There are enough parameters to satisfy the + ;; normal arguments, set rest-args to a nil value + ((= no-of-args varidiac-position) + (make-mal-nil nil))))) + (handler-case + (setf exprs (concatenate 'list + (subseq exprs 0 varidiac-position) + (list rest-args))) + (simple-type-error (condition) + (error 'arity-mismatch + :required (length binds) + :provided (length exprs)))))) + + (when (not (= (length binds) (length exprs))) + (error 'arity-mismatch + :required (length binds) + :provided (length exprs))) + + (let ((arg-params (map 'list #'cons binds exprs))) + (dolist (arg-param arg-params) + (set-env env + (car arg-param) + (cdr arg-param)))))) From b5af03eea3d4d872f5b7fe28f4943b11884f37d7 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Fri, 19 Aug 2016 00:29:27 +0530 Subject: [PATCH 0077/2308] Treat nil as a list in list? --- common_lisp/core.lisp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common_lisp/core.lisp b/common_lisp/core.lisp index bec29e5a8f..c6b03c2547 100644 --- a/common_lisp/core.lisp +++ b/common_lisp/core.lisp @@ -30,12 +30,12 @@ (cons (types:make-mal-symbol '|list?|) (types:make-mal-builtin-fn (lambda (value) - (types:make-mal-boolean (types:mal-list-p value))))) + (types:make-mal-boolean (or (types:mal-nil-p value) + (types:mal-list-p value)))))) (cons (types:make-mal-symbol '|empty?|) (types:make-mal-builtin-fn (lambda (value) - (types:apply-unwrapped-values-prefer-bool 'null - value)))) + (types:make-mal-boolean (zerop (length (mal-value value))))))) (cons (types:make-mal-symbol '|count|) (types:make-mal-builtin-fn (lambda (value) From 460d52f828b4d8e5ea2338b1a5f2471588c0112b Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Fri, 19 Aug 2016 00:30:02 +0530 Subject: [PATCH 0078/2308] Implement `not` in MAL --- common_lisp/step4_if_fn_do.lisp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common_lisp/step4_if_fn_do.lisp b/common_lisp/step4_if_fn_do.lisp index 9ab2a3fdd6..00e073532d 100644 --- a/common_lisp/step4_if_fn_do.lisp +++ b/common_lisp/step4_if_fn_do.lisp @@ -127,6 +127,8 @@ "~a" condition)))) +(rep "(def! not (fn* (a) (if a false true)))") + (defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) (format out-stream prompt) (force-output out-stream) From dc1a9c479fb2c41a82e7753811e18e03d72b9470 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Fri, 19 Aug 2016 00:30:14 +0530 Subject: [PATCH 0079/2308] Implement equality between vectors and list --- common_lisp/types.lisp | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 72195f987a..46c393ae2e 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -119,18 +119,21 @@ (gethash key map2))))))) (defun mal-value= (value1 value2) - (when (equal (mal-type value1) (mal-type value2)) - (switch-mal-type value1 - (number (= (mal-value value1) (mal-value value2))) - (boolean (equal (mal-value value1) (mal-value value2))) - (nil (equal (mal-value value1) (mal-value value2))) - (string (string= (mal-value value1) (mal-value value2))) - (symbol (mal-symbol= value1 value2)) - (keyword (mal-symbol= value1 value2)) - (list (mal-sequence= value1 value2)) - (vector (mal-sequence= value1 value2)) - (hash-map (mal-hash-map= value1 value2)) - (any nil)))) + (if (equal (mal-type value1) (mal-type value2)) + (switch-mal-type value1 + (number (= (mal-value value1) (mal-value value2))) + (boolean (equal (mal-value value1) (mal-value value2))) + (nil (equal (mal-value value1) (mal-value value2))) + (string (string= (mal-value value1) (mal-value value2))) + (symbol (mal-symbol= value1 value2)) + (keyword (mal-symbol= value1 value2)) + (list (mal-sequence= value1 value2)) + (vector (mal-sequence= value1 value2)) + (hash-map (mal-hash-map= value1 value2)) + (any nil)) + (when (or (and (mal-list-p value1) (mal-vector-p value2)) + (and (mal-list-p value2) (mal-vector-p value1))) + (mal-sequence= value1 value2)))) (defun hash-mal-value (value) (sxhash (mal-value value))) From e7337b4cd01ab27fbb556e099882573fb74a2741 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Fri, 19 Aug 2016 17:11:21 +0530 Subject: [PATCH 0080/2308] Implement print_readably, completes step 4 --- common_lisp/core.lisp | 37 ++++++++++++++++++++++++++++++++----- common_lisp/printer.lisp | 37 ++++++++++++++++++++++++------------- common_lisp/reader.lisp | 25 +++++++------------------ common_lisp/utils.lisp | 20 ++++++++++++++++++++ 4 files changed, 83 insertions(+), 36 deletions(-) create mode 100644 common_lisp/utils.lisp diff --git a/common_lisp/core.lisp b/common_lisp/core.lisp index c6b03c2547..e9ee10c70a 100644 --- a/common_lisp/core.lisp +++ b/common_lisp/core.lisp @@ -1,7 +1,8 @@ -(load "types") +(require "types") +(require "printer") (defpackage :core - (:use :common-lisp :types) + (:use :common-lisp :types :printer) (:export :ns)) (in-package :core) @@ -24,9 +25,35 @@ (types:make-mal-builtin-fn (lambda (value1 value2) (types:apply-unwrapped-values '/ value1 value2)))) - (cons (types:make-mal-symbol '|list|) - (types:make-mal-builtin-fn (lambda (&rest values) - (make-mal-list values)))) + (cons (types:make-mal-symbol '|prn|) + (types:make-mal-builtin-fn (lambda (&rest strings) + (write-line (format nil + "~{~a~^ ~}" + (mapcar (lambda (string) (printer:pr-str string t)) + strings))) + (types:make-mal-nil nil)))) + + (cons (types:make-mal-symbol '|println|) + (types:make-mal-builtin-fn (lambda (&rest strings) + (write-line (format nil + "~{~a~^ ~}" + (mapcar (lambda (string) (printer:pr-str string nil)) + strings))) + (types:make-mal-nil nil)))) + + (cons (types:make-mal-symbol '|pr-str|) + (types:make-mal-builtin-fn (lambda (&rest strings) + (types:make-mal-string (format nil + "~{~a~^ ~}" + (mapcar (lambda (string) (printer:pr-str string t)) + strings)))))) + + (cons (types:make-mal-symbol '|str|) + (types:make-mal-builtin-fn (lambda (&rest strings) + (types:make-mal-string (format nil + "~{~a~}" + (mapcar (lambda (string) (printer:pr-str string nil)) + strings)))))) (cons (types:make-mal-symbol '|list?|) (types:make-mal-builtin-fn (lambda (value) diff --git a/common_lisp/printer.lisp b/common_lisp/printer.lisp index 18a3c1cc50..15c0e65f42 100644 --- a/common_lisp/printer.lisp +++ b/common_lisp/printer.lisp @@ -1,46 +1,57 @@ (require "types") +(require "utils") (defpackage :printer - (:use :common-lisp :types) + (:use :common-lisp :utils :types) (:export :pr-str)) (in-package :printer) -(defun pr-mal-sequence (start-delimiter sequence end-delimiter) +(defun pr-mal-sequence (start-delimiter sequence end-delimiter &optional (print-readably t)) (concatenate 'string start-delimiter (format nil - "~{~A~^ ~}" - (map 'list #'pr-str (types:mal-value sequence))) + "~{~a~^ ~}" + (map 'list (lambda (value) + (pr-str value print-readably)) + (types:mal-value sequence))) end-delimiter)) -(defun pr-mal-hash-map (hash-map) +(defun pr-mal-hash-map (hash-map &optional (print-readably t)) (let ((hash-map-value (types:mal-value hash-map))) (concatenate 'string "{" (format nil - "~{~A~^ ~}" + "~{~a~^ ~}" (mapcar (lambda (key-value) (format nil "~a ~a" - (pr-str (car key-value)) - (pr-str (cdr key-value)))) + (pr-str (car key-value) print-readably) + (pr-str (cdr key-value) print-readably))) (loop for key being the hash-keys of hash-map-value collect (cons key (gethash key hash-map-value))))) "}"))) -(defun pr-str (ast) +(defun pr-string (ast &optional (print-readably t)) + (if print-readably + (utils:replace-all (prin1-to-string (types:mal-value ast)) + " +" + "\\n") + (types:mal-value ast))) + +(defun pr-str (ast &optional (print-readably t)) (when ast (switch-mal-type ast (types:number (format nil "~d" (types:mal-value ast))) (types:boolean (if (types:mal-value ast) "true" "false")) (types:nil "nil") - (types:string (format nil "~s" (types:mal-value ast))) + (types:string (pr-string ast print-readably)) (types:symbol (format nil "~a" (types:mal-value ast))) (types:keyword (format nil ":~a" (types:mal-value ast))) - (types:list (pr-mal-sequence "(" ast ")")) - (types:vector (pr-mal-sequence "[" ast "]")) - (types:hash-map (pr-mal-hash-map ast)) + (types:list (pr-mal-sequence "(" ast ")" print-readably)) + (types:vector (pr-mal-sequence "[" ast "]" print-readably)) + (types:hash-map (pr-mal-hash-map ast print-readably)) (types:fn "#") (types:builtin-fn "#")))) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index e34b6bec67..82d3b6391f 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -1,7 +1,8 @@ (require "types") +(require "utils") (defpackage :reader - (:use :regexp :common-lisp :types) + (:use :common-lisp :regexp :utils :types) (:export :read-str :eof)) @@ -23,26 +24,14 @@ "EOF encountered while reading ~a" (context condition))))) -(defun replace-all (string part replacement &key (test #'char=)) -"Returns a new string in which all the occurences of the part -is replaced with replacement." - (with-output-to-string (out) - (loop with part-length = (length part) - for old-pos = 0 then (+ pos part-length) - for pos = (search part string - :start2 old-pos - :test test) - do (write-string string out - :start old-pos - :end (or pos (length string))) - when pos do (write-string replacement out) - while pos))) - (defun parse-string (token) (if (and (> (length token) 1) (regexp:match *string-re* token)) - (read-from-string (replace-all token "\\n" " -")) + (progn + (read-from-string (utils:replace-all token + "\\n" + " +"))) ;; A bit inaccurate (error 'eof :context "string"))) diff --git a/common_lisp/utils.lisp b/common_lisp/utils.lisp new file mode 100644 index 0000000000..0ba81e7064 --- /dev/null +++ b/common_lisp/utils.lisp @@ -0,0 +1,20 @@ +(defpackage :utils + (:use :common-lisp) + (:export :replace-all)) + +(in-package :utils) + +(defun replace-all (string part replacement &key (test #'char=)) + "Returns a new string in which all the occurences of the part +is replaced with replacement." + (with-output-to-string (out) + (loop with part-length = (length part) + for old-pos = 0 then (+ pos part-length) + for pos = (search part string + :start2 old-pos + :test test) + do (write-string string out + :start old-pos + :end (or pos (length string))) + when pos do (write-string replacement out) + while pos))) From 6a036a31580eeccd54819f24eb08fe9db19a72a1 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 20 Aug 2016 12:38:46 +0530 Subject: [PATCH 0081/2308] Implement tail calls for 'let*', 'if' and 'do' --- common_lisp/step5_tco.lisp | 140 +++++++++++++++++++++++++++++++++++++ common_lisp/types.lisp | 2 +- 2 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 common_lisp/step5_tco.lisp diff --git a/common_lisp/step5_tco.lisp b/common_lisp/step5_tco.lisp new file mode 100644 index 0000000000..3d7580d810 --- /dev/null +++ b/common_lisp/step5_tco.lisp @@ -0,0 +1,140 @@ +(require "reader") +(require "printer") +(require "types") +(require "env") +(require "core") + +(defpackage :mal + (:use :common-lisp + :types + :env + :reader + :printer + :core)) + +(in-package :mal) + +(defvar *repl-env* (make-instance 'env:mal-environment)) + +(dolist (binding core:ns) + (env:set-env *repl-env* + (car binding) + (cdr binding))) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (mal-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (mal-value hash-map)) + (new-hash-table (make-hash-table :test 'types:mal-value=))) + (loop + for key being the hash-keys of hash-map-value + do (setf (gethash key new-hash-table) + (mal-eval (gethash key hash-map-value) env))) + (make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env)) + (types:any ast))) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + (loop + do (cond + ((null ast) (return (make-mal-nil nil))) + ((not (types:mal-list-p ast)) (return (eval-ast ast env))) + ((zerop (length (mal-value ast))) (return ast)) + (t (let ((forms (mal-value ast))) + (cond + ((mal-value= (make-mal-symbol '|def!|) (first forms)) + (return (env:set-env env (second forms) (mal-eval (third forms) env)))) + ((mal-value= (make-mal-symbol '|let*|) (first forms)) + (let ((new-env (make-instance 'env:mal-environment + :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (mal-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + (types:make-mal-nil nil)) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + (setf ast (third forms) + env new-env))) + ((mal-value= (make-mal-symbol '|do|) (first forms)) + (mapc (lambda (form) (mal-eval form env)) + (butlast (cdr forms))) + (setf ast (car (last forms)))) + ((mal-value= (make-mal-symbol '|if|) (first forms)) + (let ((predicate (mal-eval (second forms) env))) + (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) + (mal-value= predicate (types:make-mal-boolean nil))) + (fourth forms) + (third forms))))) + ((mal-value= (make-mal-symbol '|fn*|) (first forms)) + (return (types:make-mal-fn (let ((arglist (second forms)) + (body (third forms))) + (lambda (&rest args) + (mal-eval body (make-instance 'env:mal-environment + :parent env + :binds (map 'list + #'identity + (mal-value arglist)) + :exprs args))))))) + (t (let* ((evaluated-list (eval-ast ast env)) + (function (car evaluated-list))) + ;; If first element is a mal function unwrap it + (return (apply (mal-value function) + (cdr evaluated-list))))))))))) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (reader:eof (condition) + (format nil + "~a" + condition)) + (env:undefined-symbol (condition) + (format nil + "~a" + condition)) + (error (condition) + (format nil + "~a" + condition)))) + +(rep "(def! not (fn* (a) (if a false true)))") + +(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) + (format out-stream prompt) + (force-output out-stream) + (read-line in-stream nil)) + +(defun writeline (string) + (when string + (write-line string))) + +(defun main () + (loop do (let ((line (readline "user> "))) + (if line (writeline (rep line)) (return))))) + +(main) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 46c393ae2e..8cfe85b194 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -148,7 +148,7 @@ (null (if booleanp #'make-mal-boolean #'make-mal-nil)) - ;; This needs to before symbol since nil is a symbol + ;; This needs to before symbol since t, nil are symbols (boolean #'make-mal-boolean) (symbol #'make-mal-symbol) (keyword #'make-mal-keyword) From 1161823b0b328540cab54e6de4fce2ff381fc241 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 20 Aug 2016 13:03:16 +0530 Subject: [PATCH 0082/2308] Implement tail calls for functions, completes step 5 --- common_lisp/step5_tco.lisp | 29 +++++++++++++++++++++++------ common_lisp/types.lisp | 13 ++++++++----- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/common_lisp/step5_tco.lisp b/common_lisp/step5_tco.lisp index 3d7580d810..31eb643e17 100644 --- a/common_lisp/step5_tco.lisp +++ b/common_lisp/step5_tco.lisp @@ -56,6 +56,7 @@ (cond ((mal-value= (make-mal-symbol '|def!|) (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) + ((mal-value= (make-mal-symbol '|let*|) (first forms)) (let ((new-env (make-instance 'env:mal-environment :parent env)) @@ -76,31 +77,47 @@ collect (cons symbol value))) (setf ast (third forms) env new-env))) + ((mal-value= (make-mal-symbol '|do|) (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) + ((mal-value= (make-mal-symbol '|if|) (first forms)) (let ((predicate (mal-eval (second forms) env))) (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) (mal-value= predicate (types:make-mal-boolean nil))) (fourth forms) (third forms))))) + ((mal-value= (make-mal-symbol '|fn*|) (first forms)) - (return (types:make-mal-fn (let ((arglist (second forms)) - (body (third forms))) - (lambda (&rest args) + (return (let ((arglist (second forms)) + (body (third forms))) + (types:make-mal-fn (lambda (&rest args) (mal-eval body (make-instance 'env:mal-environment :parent env :binds (map 'list #'identity (mal-value arglist)) - :exprs args))))))) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env)))))) + (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) ;; If first element is a mal function unwrap it - (return (apply (mal-value function) - (cdr evaluated-list))))))))))) + (if (not (types:mal-fn-p function)) + (return (apply (mal-value function) + (cdr evaluated-list))) + (let* ((attrs (types:mal-attrs function))) + (setf ast (cdr (assoc 'ast attrs)) + env (make-instance 'env:mal-environment + :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))))))))))) (defun mal-print (expression) (printer:pr-str expression)) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 8cfe85b194..0bb7087b06 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -5,6 +5,7 @@ :mal-value :mal-type :mal-meta + :mal-attrs ;; Mal values :number :boolean @@ -29,11 +30,12 @@ (defclass mal-type () ((value :accessor mal-value :initarg :value) (meta :accessor mal-meta :initarg :meta :initform nil) - (type :accessor mal-type :initarg :type))) + (type :accessor mal-type :initarg :type) + (attrs :accessor mal-attrs :initarg :attrs))) (defmethod print-object ((obj mal-type) out) - (with-slots (value type meta) obj - (format out "#" type value meta))) + (with-slots (value type meta attrs) obj + (format out "#" type value meta attrs))) (defmacro define-mal-type (type) ;; Create a class for given type and a convenience constructor and also export @@ -53,10 +55,11 @@ :initarg :type :initform ',type))) - (defun ,constructor (value &optional meta) + (defun ,constructor (value &key meta attrs) (make-instance ',name :value value - :meta meta)) + :meta meta + :attrs attrs)) (defun ,predicate (value) (when (typep value 'mal-type) (equal (mal-type value) ',type))) From 903cf3ffd9e86a88d119103825d9b4acc7aca1f9 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 20 Aug 2016 13:40:26 +0530 Subject: [PATCH 0083/2308] Implement slurp and read-string --- common_lisp/core.lisp | 25 +++++- common_lisp/step6_file.lisp | 156 ++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 common_lisp/step6_file.lisp diff --git a/common_lisp/core.lisp b/common_lisp/core.lisp index e9ee10c70a..f30fdf8be8 100644 --- a/common_lisp/core.lisp +++ b/common_lisp/core.lisp @@ -1,12 +1,19 @@ (require "types") +(require "reader") (require "printer") (defpackage :core - (:use :common-lisp :types :printer) + (:use :common-lisp :types :reader :printer) (:export :ns)) (in-package :core) +(defun get-file-contents (filename) + (with-open-file (stream filename) + (let ((data (make-string (file-length stream)))) + (read-sequence data stream) + data))) + (defvar ns (list (cons (types:make-mal-symbol '+) @@ -55,10 +62,14 @@ (mapcar (lambda (string) (printer:pr-str string nil)) strings)))))) + (cons (types:make-mal-symbol '|list|) + (types:make-mal-builtin-fn (lambda (&rest values) + (make-mal-list values)))) + (cons (types:make-mal-symbol '|list?|) (types:make-mal-builtin-fn (lambda (value) (types:make-mal-boolean (or (types:mal-nil-p value) - (types:mal-list-p value)))))) + (types:mal-list-p value)))))) (cons (types:make-mal-symbol '|empty?|) (types:make-mal-builtin-fn (lambda (value) @@ -94,4 +105,12 @@ (types:make-mal-builtin-fn (lambda (value1 value2) (types:apply-unwrapped-values-prefer-bool '>= value1 - value2)))))) + value2)))) + + (cons (types:make-mal-symbol '|read-string|) + (types:make-mal-builtin-fn (lambda (value) + (reader:read-str (types:mal-value value))))) + + (cons (types:make-mal-symbol '|slurp|) + (types:make-mal-builtin-fn (lambda (filename) + (types:apply-unwrapped-values 'get-file-contents filename)))))) diff --git a/common_lisp/step6_file.lisp b/common_lisp/step6_file.lisp new file mode 100644 index 0000000000..63a087980b --- /dev/null +++ b/common_lisp/step6_file.lisp @@ -0,0 +1,156 @@ +(require "reader") +(require "printer") +(require "types") +(require "env") +(require "core") + +(defpackage :mal + (:use :common-lisp + :types + :env + :reader + :printer + :core)) + +(in-package :mal) + +(defvar *repl-env* (make-instance 'env:mal-environment)) + +(dolist (binding core:ns) + (env:set-env *repl-env* + (car binding) + (cdr binding))) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (mal-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (mal-value hash-map)) + (new-hash-table (make-hash-table :test 'types:mal-value=))) + (loop + for key being the hash-keys of hash-map-value + do (setf (gethash key new-hash-table) + (mal-eval (gethash key hash-map-value) env))) + (make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env)) + (types:any ast))) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + (loop + do (cond + ((null ast) (return (make-mal-nil nil))) + ((not (types:mal-list-p ast)) (return (eval-ast ast env))) + ((zerop (length (mal-value ast))) (return ast)) + (t (let ((forms (mal-value ast))) + (cond + ((mal-value= (make-mal-symbol '|def!|) (first forms)) + (return (env:set-env env (second forms) (mal-eval (third forms) env)))) + + ((mal-value= (make-mal-symbol '|let*|) (first forms)) + (let ((new-env (make-instance 'env:mal-environment + :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (mal-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + (types:make-mal-nil nil)) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + (setf ast (third forms) + env new-env))) + + ((mal-value= (make-mal-symbol '|do|) (first forms)) + (mapc (lambda (form) (mal-eval form env)) + (butlast (cdr forms))) + (setf ast (car (last forms)))) + + ((mal-value= (make-mal-symbol '|if|) (first forms)) + (let ((predicate (mal-eval (second forms) env))) + (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) + (mal-value= predicate (types:make-mal-boolean nil))) + (fourth forms) + (third forms))))) + + ((mal-value= (make-mal-symbol '|fn*|) (first forms)) + (return (let ((arglist (second forms)) + (body (third forms))) + (types:make-mal-fn (lambda (&rest args) + (mal-eval body (make-instance 'env:mal-environment + :parent env + :binds (map 'list + #'identity + (mal-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env)))))) + + (t (let* ((evaluated-list (eval-ast ast env)) + (function (car evaluated-list))) + ;; If first element is a mal function unwrap it + (if (not (types:mal-fn-p function)) + (return (apply function (cdr evaluated-list))) + (let* ((attrs (types:mal-attrs function))) + (setf ast (cdr (assoc 'ast attrs)) + env (make-instance 'env:mal-environment + :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))))))))))) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (reader:eof (condition) + (format nil + "~a" + condition)) + (env:undefined-symbol (condition) + (format nil + "~a" + condition)) + (error (condition) + (format nil + "~a" + condition)))) + +(rep "(def! not (fn* (a) (if a false true)))") + +(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) + (format out-stream prompt) + (force-output out-stream) + (read-line in-stream nil)) + +(defun writeline (string) + (when string + (write-line string))) + +(defun main () + (loop do (let ((line (readline "user> "))) + (if line (writeline (rep line)) (return))))) + +;(main) From fd53ac3cac59805c7068f5c4074acf764af4ce8a Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 20 Aug 2016 21:51:32 +0530 Subject: [PATCH 0084/2308] Handle non-mal datatypes in mal-value= --- common_lisp/types.lisp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 0bb7087b06..2a7be8a1c6 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -122,7 +122,9 @@ (gethash key map2))))))) (defun mal-value= (value1 value2) - (if (equal (mal-type value1) (mal-type value2)) + (when (and (typep value1 'mal-type) + (typep value2 'mal-type)) + (if (equal (mal-type value1) (mal-type value2)) (switch-mal-type value1 (number (= (mal-value value1) (mal-value value2))) (boolean (equal (mal-value value1) (mal-value value2))) @@ -136,7 +138,7 @@ (any nil)) (when (or (and (mal-list-p value1) (mal-vector-p value2)) (and (mal-list-p value2) (mal-vector-p value1))) - (mal-sequence= value1 value2)))) + (mal-sequence= value1 value2))))) (defun hash-mal-value (value) (sxhash (mal-value value))) From 4eb913bffb44e10d50e779e3e9775846c66cf851 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 20 Aug 2016 23:12:04 +0530 Subject: [PATCH 0085/2308] Add `eval` builtin --- common_lisp/step6_file.lisp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/common_lisp/step6_file.lisp b/common_lisp/step6_file.lisp index 63a087980b..eb14e766b4 100644 --- a/common_lisp/step6_file.lisp +++ b/common_lisp/step6_file.lisp @@ -21,6 +21,11 @@ (car binding) (cdr binding))) +(env:set-env *repl-env* + (types:make-mal-symbol '|eval|) + (types:make-mal-builtin-fn (lambda (ast) + (mal-eval ast *repl-env*)))) + (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) @@ -108,7 +113,8 @@ (function (car evaluated-list))) ;; If first element is a mal function unwrap it (if (not (types:mal-fn-p function)) - (return (apply function (cdr evaluated-list))) + (return (apply (mal-value function) + (cdr evaluated-list))) (let* ((attrs (types:mal-attrs function))) (setf ast (cdr (assoc 'ast attrs)) env (make-instance 'env:mal-environment @@ -153,4 +159,4 @@ (loop do (let ((line (readline "user> "))) (if line (writeline (rep line)) (return))))) -;(main) +(main) From bb02455cccc3026fadd38417b97acc42bad4ec72 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 20 Aug 2016 23:54:16 +0530 Subject: [PATCH 0086/2308] Add load-file function --- common_lisp/step6_file.lisp | 1 + 1 file changed, 1 insertion(+) diff --git a/common_lisp/step6_file.lisp b/common_lisp/step6_file.lisp index eb14e766b4..c6b48a6e7d 100644 --- a/common_lisp/step6_file.lisp +++ b/common_lisp/step6_file.lisp @@ -145,6 +145,7 @@ condition)))) (rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") (defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) (format out-stream prompt) From b9de97b68311b7838d013f054e9b31391026b515 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 20 Aug 2016 23:54:28 +0530 Subject: [PATCH 0087/2308] Add atom type --- common_lisp/printer.lisp | 1 + common_lisp/types.lisp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/common_lisp/printer.lisp b/common_lisp/printer.lisp index 15c0e65f42..f5fffbca26 100644 --- a/common_lisp/printer.lisp +++ b/common_lisp/printer.lisp @@ -53,5 +53,6 @@ (types:list (pr-mal-sequence "(" ast ")" print-readably)) (types:vector (pr-mal-sequence "[" ast "]" print-readably)) (types:hash-map (pr-mal-hash-map ast print-readably)) + (types:atom (format nil "(atom ~a)" (pr-str (types:mal-value ast)))) (types:fn "#") (types:builtin-fn "#")))) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 2a7be8a1c6..720a9a538b 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -16,6 +16,7 @@ :list :vector :hash-map + :atom :fn :builtin-fn :any @@ -80,6 +81,8 @@ (define-mal-type vector) (define-mal-type hash-map) +(define-mal-type atom) + (define-mal-type fn) (define-mal-type builtin-fn) From ae90ccdcd8612031ab3c30888e29cc6422163dab Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 20 Aug 2016 23:54:37 +0530 Subject: [PATCH 0088/2308] Fix reading of quotes --- common_lisp/reader.lisp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index 82d3b6391f..5ca9ed27ca 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -114,11 +114,11 @@ (defun expand-quote (reader) (let ((quote (next reader))) (make-mal-list (list (make-mal-symbol (cond - ((string= quote "'") "quote") - ((string= quote "`") "quasiquote") - ((string= quote "~") "unquote") - ((string= quote "~@") "splice-unquote") - ((string= quote "@") "deref"))) + ((string= quote "'") '|quote|) + ((string= quote "`") '|quasiquote|) + ((string= quote "~") '|unquote|) + ((string= quote "~@") '|splice-unquote|) + ((string= quote "@") '|deref|))) (read-form reader))))) (defun read-mal-sequence (reader &optional (delimiter ")") (constructor 'list)) From d3b9d34e56a9cbc7aae5595792e8acbf6b1eadc7 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 21 Aug 2016 00:09:18 +0530 Subject: [PATCH 0089/2308] Add all required atom functions --- common_lisp/core.lisp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/common_lisp/core.lisp b/common_lisp/core.lisp index f30fdf8be8..5b2e93746e 100644 --- a/common_lisp/core.lisp +++ b/common_lisp/core.lisp @@ -113,4 +113,27 @@ (cons (types:make-mal-symbol '|slurp|) (types:make-mal-builtin-fn (lambda (filename) - (types:apply-unwrapped-values 'get-file-contents filename)))))) + (types:apply-unwrapped-values 'get-file-contents filename)))) + + (cons (types:make-mal-symbol '|atom|) + (types:make-mal-builtin-fn (lambda (value) + (types:make-mal-atom value)))) + + (cons (types:make-mal-symbol '|atom?|) + (types:make-mal-builtin-fn (lambda (value) + (types:make-mal-boolean (types:mal-atom-p value))))) + + (cons (types:make-mal-symbol '|deref|) + (types:make-mal-builtin-fn (lambda (atom) + (types:mal-value atom)))) + + (cons (types:make-mal-symbol '|reset!|) + (types:make-mal-builtin-fn (lambda (atom value) + (setf (types:mal-value atom) value)))) + + (cons (types:make-mal-symbol '|swap!|) + (types:make-mal-builtin-fn (lambda (atom fn &rest args) + (setf (types:mal-value atom) + (apply (mal-value fn) + (append (list (types:mal-value atom)) + args)))))))) From 130e1c944158894bc0a88670bd9a90818f8098c8 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 21 Aug 2016 00:12:58 +0530 Subject: [PATCH 0090/2308] Add *ARGV*, completes step 6 --- common_lisp/step6_file.lisp | 12 ++++++++++- common_lisp/types.lisp | 43 ++++++++++++++++++++++--------------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/common_lisp/step6_file.lisp b/common_lisp/step6_file.lisp index c6b48a6e7d..ad5bf1fb18 100644 --- a/common_lisp/step6_file.lisp +++ b/common_lisp/step6_file.lisp @@ -146,6 +146,7 @@ (rep "(def! not (fn* (a) (if a false true)))") (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +(rep "(def! *ARGV* (list))") (defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) (format out-stream prompt) @@ -160,4 +161,13 @@ (loop do (let ((line (readline "user> "))) (if line (writeline (rep line)) (return))))) -(main) +(env:set-env *repl-env* + (types:make-mal-symbol '|*ARGV*|) + (types:wrap-value (cdr common-lisp-user::*args*) + :listp t)) + +(if (null common-lisp-user::*args*) + (main) + (rep (format nil + "(load-file \"~a\")" + (car common-lisp-user::*args*)))) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 720a9a538b..a5d2bdc920 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -149,23 +149,32 @@ #+sbcl (sb-ext:define-hash-table-test mal-value= hash-mal-value) #+clisp (ext:define-hash-table-test mal-value= mal-value= hash-mal-value) -(defun wrap-value (value &key booleanp) - (funcall (typecase value - (number #'make-mal-number) - ;; This needs to before symbol since nil is a symbol - (null (if booleanp - #'make-mal-boolean - #'make-mal-nil)) - ;; This needs to before symbol since t, nil are symbols - (boolean #'make-mal-boolean) - (symbol #'make-mal-symbol) - (keyword #'make-mal-keyword) - (string #'make-mal-string) - (list #'make-mal-list) - (vector #'make-mal-vector) - (hash-table #'make-mal-hash-map) - (null #'make-mal-nil)) - value)) +(defun wrap-hash-value (value) + (let ((new-hash-table (make-hash-table :test 'mal-value=))) + (loop + for key being the hash-keys of value + do (setf (gethash (wrap-value key) new-hash-table) + (wrap-value (gethash key value)))) + new-hash-table)) + +(defun wrap-value (value &key booleanp listp) + (typecase value + (number (make-mal-number value)) + ;; This needs to before symbol since nil is a symbol + (null (funcall (cond + (booleanp #'make-mal-boolean) + (listp #'make-mal-list) + (t #'make-mal-nil)) + value)) + ;; This needs to before symbol since t, nil are symbols + (boolean (make-mal-boolean value)) + (symbol (make-mal-symbol value)) + (keyword (make-mal-keyword value)) + (string (make-mal-string value)) + (list (make-mal-list (map 'list #'wrap-value value))) + (vector (make-mal-vector (map 'vector #'wrap-value value))) + (hash-table (make-mal-hash-map (wrap-hash-value value))) + (null (make-mal-nil value)))) (defun apply-unwrapped-values (op &rest values) (wrap-value (apply op (mapcar #'mal-value values)))) From 4ccd278d37d3dd0d37fdfd882bd3770de1f9eeca Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 21 Aug 2016 11:29:59 +0530 Subject: [PATCH 0091/2308] Eval keys of hash-map as well --- common_lisp/step6_file.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common_lisp/step6_file.lisp b/common_lisp/step6_file.lisp index ad5bf1fb18..8edcd90ac4 100644 --- a/common_lisp/step6_file.lisp +++ b/common_lisp/step6_file.lisp @@ -36,7 +36,7 @@ (new-hash-table (make-hash-table :test 'types:mal-value=))) (loop for key being the hash-keys of hash-map-value - do (setf (gethash key new-hash-table) + do (setf (gethash (mal-eval key env) new-hash-table) (mal-eval (gethash key hash-map-value) env))) (make-mal-hash-map new-hash-table))) From cc9b97ef3743f0b03ed68225f6d71ae944ce90dd Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 21 Aug 2016 13:35:48 +0530 Subject: [PATCH 0092/2308] Add unwrap value to convert from mal type to native types --- common_lisp/types.lisp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index a5d2bdc920..6e0855cb6f 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -176,8 +176,21 @@ (hash-table (make-mal-hash-map (wrap-hash-value value))) (null (make-mal-nil value)))) +(defun unwrap-value (value) + (switch-mal-type value + (list (mapcar #'unwrap-value (mal-value value))) + (vector (map 'vector #'unwrap-value (mal-value value))) + (hash-map (let ((hash-table (make-hash-table)) + (hash-map-value (mal-value value))) + (loop + for key being the hash-keys of hash-map-value + do (setf (gethash (mal-value key) hash-table) + (mal-value (gethash key hash-map-value)))) + hash-table)) + (any (mal-value value)))) + (defun apply-unwrapped-values (op &rest values) - (wrap-value (apply op (mapcar #'mal-value values)))) + (wrap-value (apply op (mapcar #'unwrap-value values)))) (defun apply-unwrapped-values-prefer-bool (op &rest values) - (wrap-value (apply op (mapcar #'mal-value values)) :booleanp t)) + (wrap-value (apply op (mapcar #'unwrap-value values)) :booleanp t)) From 4accd3ef607da5e1a4641c57f537a3ee0bc5e88e Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 21 Aug 2016 13:38:40 +0530 Subject: [PATCH 0093/2308] Add cons and concat --- common_lisp/core.lisp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/common_lisp/core.lisp b/common_lisp/core.lisp index 5b2e93746e..6ee7f12e1e 100644 --- a/common_lisp/core.lisp +++ b/common_lisp/core.lisp @@ -136,4 +136,17 @@ (setf (types:mal-value atom) (apply (mal-value fn) (append (list (types:mal-value atom)) - args)))))))) + args)))))) + + (cons (types:make-mal-symbol '|cons|) + (types:make-mal-builtin-fn (lambda (element list) + (types:make-mal-list (cons element + (map 'list + #'identity + (mal-value list))))))) + + (cons (types:make-mal-symbol '|concat|) + (types:make-mal-builtin-fn (lambda (&rest lists) + (types:make-mal-list (apply #'concatenate + 'list + (mapcar #'types:mal-value lists)))))))) From c5064079c4b8d8d5450d34a30c3d5a264293632b Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 21 Aug 2016 14:39:01 +0530 Subject: [PATCH 0094/2308] Implement quasiquote, completes step 7 --- common_lisp/step7_quote.lisp | 204 +++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 common_lisp/step7_quote.lisp diff --git a/common_lisp/step7_quote.lisp b/common_lisp/step7_quote.lisp new file mode 100644 index 0000000000..76c2b591eb --- /dev/null +++ b/common_lisp/step7_quote.lisp @@ -0,0 +1,204 @@ +(require "reader") +(require "printer") +(require "types") +(require "env") +(require "core") + +(defpackage :mal + (:use :common-lisp + :types + :env + :reader + :printer + :core)) + +(in-package :mal) + +(defvar *repl-env* (make-instance 'env:mal-environment)) + +(dolist (binding core:ns) + (env:set-env *repl-env* + (car binding) + (cdr binding))) + +(env:set-env *repl-env* + (types:make-mal-symbol '|eval|) + (types:make-mal-builtin-fn (lambda (ast) + (mal-eval ast *repl-env*)))) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (mal-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (mal-value hash-map)) + (new-hash-table (make-hash-table :test 'types:mal-value=))) + (loop + for key being the hash-keys of hash-map-value + do (setf (gethash (mal-eval key env) new-hash-table) + (mal-eval (gethash key hash-map-value) env))) + (make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env)) + (types:any ast))) + +(defun is-pair (value) + (and (or (mal-list-p value) + (mal-vector-p value)) + (not (zerop (length (mal-value value)))))) + +(defun quasiquote (ast) + (if (not (is-pair ast)) + (types:make-mal-list (list (types:make-mal-symbol '|quote|) + ast)) + (let ((forms (map 'list #'identity (mal-value ast)))) + (cond + ((mal-value= (make-mal-symbol '|unquote|) (first forms)) + (second forms)) + + ((and (is-pair (first forms)) + (mal-value= (make-mal-symbol '|splice-unquote|) + (first (mal-value (first forms))))) + (types:make-mal-list (list (types:make-mal-symbol '|concat|) + (second (mal-value (first forms))) + (quasiquote (make-mal-list (cdr forms)))))) + + (t (types:make-mal-list (list (types:make-mal-symbol '|cons|) + (quasiquote (first forms)) + (quasiquote (make-mal-list (cdr forms)))))))))) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + (loop + do (cond + ((null ast) (return (make-mal-nil nil))) + ((not (types:mal-list-p ast)) (return (eval-ast ast env))) + ((zerop (length (mal-value ast))) (return ast)) + (t (let ((forms (mal-value ast))) + (cond + ((mal-value= (make-mal-symbol '|quote|) (first forms)) + (return (second forms))) + + ((mal-value= (make-mal-symbol '|quasiquote|) (first forms)) + (setf ast (quasiquote (second forms)))) + + ((mal-value= (make-mal-symbol '|def!|) (first forms)) + (return (env:set-env env (second forms) (mal-eval (third forms) env)))) + + ((mal-value= (make-mal-symbol '|let*|) (first forms)) + (let ((new-env (make-instance 'env:mal-environment + :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (mal-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + (types:make-mal-nil nil)) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + (setf ast (third forms) + env new-env))) + + ((mal-value= (make-mal-symbol '|do|) (first forms)) + (mapc (lambda (form) (mal-eval form env)) + (butlast (cdr forms))) + (setf ast (car (last forms)))) + + ((mal-value= (make-mal-symbol '|if|) (first forms)) + (let ((predicate (mal-eval (second forms) env))) + (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) + (mal-value= predicate (types:make-mal-boolean nil))) + (fourth forms) + (third forms))))) + + ((mal-value= (make-mal-symbol '|fn*|) (first forms)) + (return (let ((arglist (second forms)) + (body (third forms))) + (types:make-mal-fn (lambda (&rest args) + (mal-eval body (make-instance 'env:mal-environment + :parent env + :binds (map 'list + #'identity + (mal-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env)))))) + + (t (let* ((evaluated-list (eval-ast ast env)) + (function (car evaluated-list))) + ;; If first element is a mal function unwrap it + (if (not (types:mal-fn-p function)) + (return (apply (mal-value function) + (cdr evaluated-list))) + (let* ((attrs (types:mal-attrs function))) + (setf ast (cdr (assoc 'ast attrs)) + env (make-instance 'env:mal-environment + :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))))))))))) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (reader:eof (condition) + (format nil + "~a" + condition)) + (env:undefined-symbol (condition) + (format nil + "~a" + condition)) + (error (condition) + (format nil + "~a" + condition)))) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +(rep "(def! *ARGV* (list))") + +(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) + (format out-stream prompt) + (force-output out-stream) + (read-line in-stream nil)) + +(defun writeline (string) + (when string + (write-line string))) + +(defun main () + (loop do (let ((line (readline "user> "))) + (if line (writeline (rep line)) (return))))) + +(env:set-env *repl-env* + (types:make-mal-symbol '|*ARGV*|) + (types:wrap-value (cdr common-lisp-user::*args*) + :listp t)) + +(if (null common-lisp-user::*args*) + (main) + (rep (format nil + "(load-file \"~a\")" + (car common-lisp-user::*args*)))) From f0cc89b26f0f9608a225a04eed389bbb5d4df12c Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 21 Aug 2016 19:39:45 +0530 Subject: [PATCH 0095/2308] Add support for macros and macro expansion --- common_lisp/step8_macros.lisp | 237 ++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 common_lisp/step8_macros.lisp diff --git a/common_lisp/step8_macros.lisp b/common_lisp/step8_macros.lisp new file mode 100644 index 0000000000..c3228211ff --- /dev/null +++ b/common_lisp/step8_macros.lisp @@ -0,0 +1,237 @@ +(require "reader") +(require "printer") +(require "types") +(require "env") +(require "core") + +(defpackage :mal + (:use :common-lisp + :types + :env + :reader + :printer + :core)) + +(in-package :mal) + +(defvar *repl-env* (make-instance 'env:mal-environment)) + +(dolist (binding core:ns) + (env:set-env *repl-env* + (car binding) + (cdr binding))) + +(env:set-env *repl-env* + (types:make-mal-symbol '|eval|) + (types:make-mal-builtin-fn (lambda (ast) + (mal-eval ast *repl-env*)))) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (mal-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (mal-value hash-map)) + (new-hash-table (make-hash-table :test 'types:mal-value=))) + (loop + for key being the hash-keys of hash-map-value + do (setf (gethash (mal-eval key env) new-hash-table) + (mal-eval (gethash key hash-map-value) env))) + (make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env)) + (types:any ast))) + +(defun is-pair (value) + (and (or (mal-list-p value) + (mal-vector-p value)) + (not (zerop (length (mal-value value)))))) + +(defun quasiquote (ast) + (if (not (is-pair ast)) + (types:make-mal-list (list (types:make-mal-symbol '|quote|) + ast)) + (let ((forms (map 'list #'identity (mal-value ast)))) + (cond + ((mal-value= (make-mal-symbol '|unquote|) (first forms)) + (second forms)) + + ((and (is-pair (first forms)) + (mal-value= (make-mal-symbol '|splice-unquote|) + (first (mal-value (first forms))))) + (types:make-mal-list (list (types:make-mal-symbol '|concat|) + (second (mal-value (first forms))) + (quasiquote (make-mal-list (cdr forms)))))) + + (t (types:make-mal-list (list (types:make-mal-symbol '|cons|) + (quasiquote (first forms)) + (quasiquote (make-mal-list (cdr forms)))))))))) + +(defun is-macro-call (ast env) + (when (and (types:mal-list-p ast) + (not (zerop (length (mal-value ast))))) + (let* ((func-symbol (first (mal-value ast))) + (func (when (types:mal-symbol-p func-symbol) + (ignore-errors (env:get-env env func-symbol))))) + (and func + (types:mal-fn-p func) + (cdr (assoc 'is-macro (types:mal-attrs func))))))) + +(defun mal-macroexpand (ast env) + (loop + while (is-macro-call ast env) + do (let* ((forms (types:mal-value ast)) + (func (env:get-env env (first forms)))) + (setf ast (apply (mal-value func) + (cdr forms))))) + ast) + +(defun mal-eval (ast env) + (loop + do (setf ast (mal-macroexpand ast env)) + do (cond + ((null ast) (return (make-mal-nil nil))) + ((not (types:mal-list-p ast)) (return (eval-ast ast env))) + ((zerop (length (mal-value ast))) (return ast)) + (t (let ((forms (mal-value ast))) + (cond + ((mal-value= (make-mal-symbol '|quote|) (first forms)) + (return (second forms))) + + ((mal-value= (make-mal-symbol '|quasiquote|) (first forms)) + (setf ast (quasiquote (second forms)))) + + ((mal-value= (make-mal-symbol '|macroexpand|) (first forms)) + (return (mal-macroexpand (second forms) env))) + + ((mal-value= (make-mal-symbol '|def!|) (first forms)) + (return (env:set-env env (second forms) (mal-eval (third forms) env)))) + + ((mal-value= (make-mal-symbol '|defmacro!|) (first forms)) + (let ((value (mal-eval (third forms) env))) + (return (if (types:mal-fn-p value) + (env:set-env env (second forms) + (progn + (setf (cdr (assoc 'is-macro (types:mal-attrs value))) t) + value)) + (error "Not a function"))))) + + ((mal-value= (make-mal-symbol '|let*|) (first forms)) + (let ((new-env (make-instance 'env:mal-environment + :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (mal-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + (types:make-mal-nil nil)) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + (setf ast (third forms) + env new-env))) + + ((mal-value= (make-mal-symbol '|do|) (first forms)) + (mapc (lambda (form) (mal-eval form env)) + (butlast (cdr forms))) + (setf ast (car (last forms)))) + + ((mal-value= (make-mal-symbol '|if|) (first forms)) + (let ((predicate (mal-eval (second forms) env))) + (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) + (mal-value= predicate (types:make-mal-boolean nil))) + (fourth forms) + (third forms))))) + + ((mal-value= (make-mal-symbol '|fn*|) (first forms)) + (return (let ((arglist (second forms)) + (body (third forms))) + (types:make-mal-fn (lambda (&rest args) + (mal-eval body (make-instance 'env:mal-environment + :parent env + :binds (map 'list + #'identity + (mal-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env) + (cons 'is-macro nil)))))) + + (t (let* ((evaluated-list (eval-ast ast env)) + (function (car evaluated-list))) + ;; If first element is a mal function unwrap it + (if (not (types:mal-fn-p function)) + (return (apply (mal-value function) + (cdr evaluated-list))) + (let* ((attrs (types:mal-attrs function))) + (setf ast (cdr (assoc 'ast attrs)) + env (make-instance 'env:mal-environment + :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))))))))))) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (reader:eof (condition) + (format nil + "~a" + condition)) + (env:undefined-symbol (condition) + (format nil + "~a" + condition)) + (error (condition) + (format nil + "~a" + condition)))) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +(rep "(def! *ARGV* (list))") + +(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) + (format out-stream prompt) + (force-output out-stream) + (read-line in-stream nil)) + +(defun writeline (string) + (when string + (write-line string))) + +(defun main () + (loop do (let ((line (readline "user> "))) + (if line (writeline (rep line)) (return))))) + +(env:set-env *repl-env* + (types:make-mal-symbol '|*ARGV*|) + (types:wrap-value (cdr common-lisp-user::*args*) + :listp t)) + +(if (null common-lisp-user::*args*) + (main) + (rep (format nil + "(load-file \"~a\")" + (car common-lisp-user::*args*)))) From 3dec1ccf6856f2622d885f6050751cd25eb197ed Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 21 Aug 2016 19:47:26 +0530 Subject: [PATCH 0096/2308] Implement nth, first and rest --- common_lisp/core.lisp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/common_lisp/core.lisp b/common_lisp/core.lisp index 6ee7f12e1e..d475590fed 100644 --- a/common_lisp/core.lisp +++ b/common_lisp/core.lisp @@ -149,4 +149,22 @@ (types:make-mal-builtin-fn (lambda (&rest lists) (types:make-mal-list (apply #'concatenate 'list - (mapcar #'types:mal-value lists)))))))) + (mapcar #'types:mal-value lists)))))) + + + (cons (types:make-mal-symbol '|nth|) + (types:make-mal-builtin-fn (lambda (sequence index) + (or (nth (mal-value index) + (map 'list #'identity (mal-value sequence))) + (error "Index out of range"))))) + + (cons (types:make-mal-symbol '|first|) + (types:make-mal-builtin-fn (lambda (sequence) + (or (first (map 'list #'identity (mal-value sequence))) + (types:make-mal-nil nil))))) + + (cons (types:make-mal-symbol '|rest|) + (types:make-mal-builtin-fn (lambda (sequence) + (types:make-mal-list (rest (map 'list + #'identity + (mal-value sequence))))))))) From 07b4ed9fa733c01ed14a82cefdb4cd221dbe0e1b Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 21 Aug 2016 20:22:09 +0530 Subject: [PATCH 0097/2308] Implement or and cond macros, completes step 8 --- common_lisp/step8_macros.lisp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common_lisp/step8_macros.lisp b/common_lisp/step8_macros.lisp index c3228211ff..da2b18355e 100644 --- a/common_lisp/step8_macros.lisp +++ b/common_lisp/step8_macros.lisp @@ -211,6 +211,8 @@ (rep "(def! not (fn* (a) (if a false true)))") (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") (rep "(def! *ARGV* (list))") +(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") (defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) (format out-stream prompt) From aa333009ae7cee8dd5158b412bff70d7b940b34f Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 21 Aug 2016 22:39:55 +0530 Subject: [PATCH 0098/2308] Improve error handling --- common_lisp/core.lisp | 18 ++++++++++-- common_lisp/env.lisp | 4 +-- common_lisp/reader.lisp | 2 +- common_lisp/step8_macros.lisp | 52 ++++++++++++++++++++++------------- common_lisp/types.lisp | 5 ++++ 5 files changed, 57 insertions(+), 24 deletions(-) diff --git a/common_lisp/core.lisp b/common_lisp/core.lisp index d475590fed..8659aedae8 100644 --- a/common_lisp/core.lisp +++ b/common_lisp/core.lisp @@ -8,6 +8,17 @@ (in-package :core) +(define-condition index-error (types:mal-error) + ((size :initarg :size :reader size) + (index :initarg :index :reader index) + (sequence :initarg :sequence :reader sequence)) + (:report (lambda (condition stream) + (format stream + "Index out of range (~a), length is ~a but index given was ~a" + (printer:pr-str (sequence condition)) + (size condition) + (index condition))))) + (defun get-file-contents (filename) (with-open-file (stream filename) (let ((data (make-string (file-length stream)))) @@ -29,7 +40,7 @@ (types:apply-unwrapped-values '* value1 value2)))) (cons (types:make-mal-symbol '/) - (types:make-mal-builtin-fn (lambda (value1 value2) + (types:make-mal-builtin-fn ( lambda (value1 value2) (types:apply-unwrapped-values '/ value1 value2)))) (cons (types:make-mal-symbol '|prn|) @@ -156,7 +167,10 @@ (types:make-mal-builtin-fn (lambda (sequence index) (or (nth (mal-value index) (map 'list #'identity (mal-value sequence))) - (error "Index out of range"))))) + (error 'index-error + :size (length (mal-value sequence)) + :index (mal-value index) + :sequence sequence))))) (cons (types:make-mal-symbol '|first|) (types:make-mal-builtin-fn (lambda (sequence) diff --git a/common_lisp/env.lisp b/common_lisp/env.lisp index 0b7a035e7e..59aa3f2e44 100644 --- a/common_lisp/env.lisp +++ b/common_lisp/env.lisp @@ -9,14 +9,14 @@ (in-package :env) -(define-condition undefined-symbol (error) +(define-condition undefined-symbol (types:mal-error) ((symbol :initarg :symbol :reader symbol)) (:report (lambda (condition stream) (format stream "Symbol ~a is undefined" (symbol condition))))) -(define-condition arity-mismatch (error) +(define-condition arity-mismatch (types:mal-error) ((required :initarg :required :reader required) (provided :initarg :provided :reader provided)) (:report (lambda (condition stream) diff --git a/common_lisp/reader.lisp b/common_lisp/reader.lisp index 5ca9ed27ca..9a90bcdbc8 100644 --- a/common_lisp/reader.lisp +++ b/common_lisp/reader.lisp @@ -17,7 +17,7 @@ ]*\\|[^][[:space:]~{}()@^`'\";]*\\)" "RE") -(define-condition eof (error) +(define-condition eof (types:mal-error) ((context :initarg :context :reader context)) (:report (lambda (condition stream) (format stream diff --git a/common_lisp/step8_macros.lisp b/common_lisp/step8_macros.lisp index da2b18355e..ae67284775 100644 --- a/common_lisp/step8_macros.lisp +++ b/common_lisp/step8_macros.lisp @@ -14,6 +14,17 @@ (in-package :mal) +(define-condition invalid-function (types:mal-error) + ((form :initarg :form :reader form) + (context :initarg :context :reader context)) + (:report (lambda (condition stream) + (format stream + "Invalid function '~a' provided while ~a" + (printer:pr-str (form condition)) + (if (string= (context condition) "apply") + "applying" + "defining macro"))))) + (defvar *repl-env* (make-instance 'env:mal-environment)) (dolist (binding core:ns) @@ -116,11 +127,14 @@ ((mal-value= (make-mal-symbol '|defmacro!|) (first forms)) (let ((value (mal-eval (third forms) env))) (return (if (types:mal-fn-p value) - (env:set-env env (second forms) + (env:set-env env + (second forms) (progn (setf (cdr (assoc 'is-macro (types:mal-attrs value))) t) value)) - (error "Not a function"))))) + (error 'invalid-function + :form value + :context "macro"))))) ((mal-value= (make-mal-symbol '|let*|) (first forms)) (let ((new-env (make-instance 'env:mal-environment @@ -173,17 +187,21 @@ (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) ;; If first element is a mal function unwrap it - (if (not (types:mal-fn-p function)) - (return (apply (mal-value function) - (cdr evaluated-list))) - (let* ((attrs (types:mal-attrs function))) - (setf ast (cdr (assoc 'ast attrs)) - env (make-instance 'env:mal-environment - :parent (cdr (assoc 'env attrs)) - :binds (map 'list - #'identity - (mal-value (cdr (assoc 'params attrs)))) - :exprs (cdr evaluated-list))))))))))))) + (cond ((types:mal-fn-p function) + (let* ((attrs (types:mal-attrs function))) + (setf ast (cdr (assoc 'ast attrs)) + env (make-instance 'env:mal-environment + :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))) + ((types:mal-builtin-fn-p function) + (return (apply (mal-value function) + (cdr evaluated-list)))) + (t (error 'invalid-function + :form function + :context "apply"))))))))))) (defun mal-read (string) (reader:read-str string)) @@ -195,17 +213,13 @@ (handler-case (mal-print (mal-eval (mal-read string) *repl-env*)) - (reader:eof (condition) - (format nil - "~a" - condition)) - (env:undefined-symbol (condition) + (types:mal-error (condition) (format nil "~a" condition)) (error (condition) (format nil - "~a" + "Internal error: ~a" condition)))) (rep "(def! not (fn* (a) (if a false true)))") diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 6e0855cb6f..9a4a83ba38 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -20,6 +20,8 @@ :fn :builtin-fn :any + ;; Error + :mal-error ;; Helpers :wrap-value :apply-unwrapped-values @@ -28,6 +30,9 @@ (in-package :types) +(define-condition mal-error (error) + nil) + (defclass mal-type () ((value :accessor mal-value :initarg :value) (meta :accessor mal-meta :initarg :meta :initform nil) From e844f5c872a4ed1e455920700b01bf5be7a895b4 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 22 Aug 2016 22:49:11 +0530 Subject: [PATCH 0099/2308] Introduce types mal-runtime-exception and mal-user-exception types All the exceptions generated by runtime inherit from mal-user-exception while user exceptions inherit from mal-user-exception --- common_lisp/core.lisp | 2 +- common_lisp/env.lisp | 6 +++--- common_lisp/types.lisp | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/common_lisp/core.lisp b/common_lisp/core.lisp index 8659aedae8..f86c1af3be 100644 --- a/common_lisp/core.lisp +++ b/common_lisp/core.lisp @@ -8,7 +8,7 @@ (in-package :core) -(define-condition index-error (types:mal-error) +(define-condition index-error (types:mal-runtime-exception) ((size :initarg :size :reader size) (index :initarg :index :reader index) (sequence :initarg :sequence :reader sequence)) diff --git a/common_lisp/env.lisp b/common_lisp/env.lisp index 59aa3f2e44..bf59ba4457 100644 --- a/common_lisp/env.lisp +++ b/common_lisp/env.lisp @@ -9,14 +9,14 @@ (in-package :env) -(define-condition undefined-symbol (types:mal-error) +(define-condition undefined-symbol (types:mal-runtime-exception) ((symbol :initarg :symbol :reader symbol)) (:report (lambda (condition stream) (format stream - "Symbol ~a is undefined" + "'~a' not found" (symbol condition))))) -(define-condition arity-mismatch (types:mal-error) +(define-condition arity-mismatch (types:mal-runtime-exception) ((required :initarg :required :reader required) (provided :initarg :provided :reader provided)) (:report (lambda (condition stream) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index 9a4a83ba38..d8a3988941 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -20,6 +20,11 @@ :fn :builtin-fn :any + :mal-exception + ;; User exceptions + :mal-user-exception + ;; Exceptions raised by the runtime itself + :mal-runtime-exception ;; Error :mal-error ;; Helpers @@ -33,6 +38,15 @@ (define-condition mal-error (error) nil) +(define-condition mal-exception (error) + nil) + +(define-condition mal-runtime-exception (mal-exception) + nil) + +(define-condition mal-user-exception (mal-exception) + ((data :accessor mal-exception-data :initarg :data))) + (defclass mal-type () ((value :accessor mal-value :initarg :value) (meta :accessor mal-meta :initarg :meta :initform nil) From 147cc3611b5b26a6968e12532d4ff9bf8778d3e4 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 22 Aug 2016 22:51:06 +0530 Subject: [PATCH 0100/2308] Add try* special form --- common_lisp/step9_try.lisp | 278 +++++++++++++++++++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 common_lisp/step9_try.lisp diff --git a/common_lisp/step9_try.lisp b/common_lisp/step9_try.lisp new file mode 100644 index 0000000000..be6852624a --- /dev/null +++ b/common_lisp/step9_try.lisp @@ -0,0 +1,278 @@ +(require "reader") +(require "printer") +(require "types") +(require "env") +(require "core") + +(defpackage :mal + (:use :common-lisp + :types + :env + :reader + :printer + :core)) + +(in-package :mal) + +(define-condition invalid-function (types:mal-runtime-exception) + ((form :initarg :form :reader form) + (context :initarg :context :reader context)) + (:report (lambda (condition stream) + (format stream + "Invalid function '~a' provided while ~a" + (printer:pr-str (form condition)) + (if (string= (context condition) "apply") + "applying" + "defining macro"))))) + +(defvar *repl-env* (make-instance 'env:mal-environment)) + +(dolist (binding core:ns) + (env:set-env *repl-env* + (car binding) + (cdr binding))) + +(env:set-env *repl-env* + (types:make-mal-symbol '|eval|) + (types:make-mal-builtin-fn (lambda (ast) + (mal-eval ast *repl-env*)))) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (mal-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (mal-value hash-map)) + (new-hash-table (make-hash-table :test 'types:mal-value=))) + (loop + for key being the hash-keys of hash-map-value + do (setf (gethash (mal-eval key env) new-hash-table) + (mal-eval (gethash key hash-map-value) env))) + (make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env)) + (types:any ast))) + +(defun is-pair (value) + (and (or (mal-list-p value) + (mal-vector-p value)) + (not (zerop (length (mal-value value)))))) + +(defun quasiquote (ast) + (if (not (is-pair ast)) + (types:make-mal-list (list (types:make-mal-symbol '|quote|) + ast)) + (let ((forms (map 'list #'identity (mal-value ast)))) + (cond + ((mal-value= (make-mal-symbol '|unquote|) (first forms)) + (second forms)) + + ((and (is-pair (first forms)) + (mal-value= (make-mal-symbol '|splice-unquote|) + (first (mal-value (first forms))))) + (types:make-mal-list (list (types:make-mal-symbol '|concat|) + (second (mal-value (first forms))) + (quasiquote (make-mal-list (cdr forms)))))) + + (t (types:make-mal-list (list (types:make-mal-symbol '|cons|) + (quasiquote (first forms)) + (quasiquote (make-mal-list (cdr forms)))))))))) + +(defun is-macro-call (ast env) + (when (and (types:mal-list-p ast) + (not (zerop (length (mal-value ast))))) + (let* ((func-symbol (first (mal-value ast))) + (func (when (types:mal-symbol-p func-symbol) + (ignore-errors (env:get-env env func-symbol))))) + (and func + (types:mal-fn-p func) + (cdr (assoc 'is-macro (types:mal-attrs func))))))) + +(defun mal-macroexpand (ast env) + (loop + while (is-macro-call ast env) + do (let* ((forms (types:mal-value ast)) + (func (env:get-env env (first forms)))) + (setf ast (apply (mal-value func) + (cdr forms))))) + ast) + +(defun mal-eval (ast env) + (loop + do (setf ast (mal-macroexpand ast env)) + do (cond + ((null ast) (return (make-mal-nil nil))) + ((not (types:mal-list-p ast)) (return (eval-ast ast env))) + ((zerop (length (mal-value ast))) (return ast)) + (t (let ((forms (mal-value ast))) + (cond + ((mal-value= (make-mal-symbol '|quote|) (first forms)) + (return (second forms))) + + ((mal-value= (make-mal-symbol '|quasiquote|) (first forms)) + (setf ast (quasiquote (second forms)))) + + ((mal-value= (make-mal-symbol '|macroexpand|) (first forms)) + (return (mal-macroexpand (second forms) env))) + + ((mal-value= (make-mal-symbol '|def!|) (first forms)) + (return (env:set-env env (second forms) (mal-eval (third forms) env)))) + + ((mal-value= (make-mal-symbol '|defmacro!|) (first forms)) + (let ((value (mal-eval (third forms) env))) + (return (if (types:mal-fn-p value) + (env:set-env env + (second forms) + (progn + (setf (cdr (assoc 'is-macro (types:mal-attrs value))) t) + value)) + (error 'invalid-function + :form value + :context "macro"))))) + + ((mal-value= (make-mal-symbol '|let*|) (first forms)) + (let ((new-env (make-instance 'env:mal-environment + :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (mal-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + (types:make-mal-nil nil)) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + (setf ast (third forms) + env new-env))) + + ((mal-value= (make-mal-symbol '|do|) (first forms)) + (mapc (lambda (form) (mal-eval form env)) + (butlast (cdr forms))) + (setf ast (car (last forms)))) + + ((mal-value= (make-mal-symbol '|if|) (first forms)) + (let ((predicate (mal-eval (second forms) env))) + (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) + (mal-value= predicate (types:make-mal-boolean nil))) + (fourth forms) + (third forms))))) + + ((mal-value= (make-mal-symbol '|fn*|) (first forms)) + (return (let ((arglist (second forms)) + (body (third forms))) + (types:make-mal-fn (lambda (&rest args) + (mal-eval body (make-instance 'env:mal-environment + :parent env + :binds (map 'list + #'identity + (mal-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env) + (cons 'is-macro nil)))))) + + ((mal-value= (make-mal-symbol '|try*|) (first forms)) + (handler-case + (return (mal-eval (second forms) env)) + (types:mal-exception (condition) + (when (third forms) + (let ((catch-forms (types:mal-value (third forms)))) + (when (mal-value= (make-mal-symbol '|catch*|) + (first catch-forms)) + (return (mal-eval (third catch-forms) + (make-instance 'env:mal-environment + :parent env + :binds (list (second catch-forms)) + :exprs (list (if (typep condition 'types:mal-runtime-exception) + (types:make-mal-string (format nil "~a" condition)) + (types::mal-exception-data condition))))))))) + (error condition)))) + + (t (let* ((evaluated-list (eval-ast ast env)) + (function (car evaluated-list))) + ;; If first element is a mal function unwrap it + (cond ((types:mal-fn-p function) + (let* ((attrs (types:mal-attrs function))) + (setf ast (cdr (assoc 'ast attrs)) + env (make-instance 'env:mal-environment + :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))) + ((types:mal-builtin-fn-p function) + (return (apply (mal-value function) + (cdr evaluated-list)))) + (t (error 'invalid-function + :form function + :context "apply"))))))))))) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (types:mal-error (condition) + (format nil + "Error: ~a" + condition)) + (types:mal-runtime-exception (condition) + (format nil + "Exception: ~a" + condition)) + (types:mal-user-exception (condition) + (format nil + "Exception: ~a" + (pr-str (types::mal-exception-data condition)))) + (error (condition) + (format nil + "Internal error: ~a" + condition)))) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +(rep "(def! *ARGV* (list))") +(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") + +(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) + (format out-stream prompt) + (force-output out-stream) + (read-line in-stream nil)) + +(defun writeline (string) + (when string + (write-line string))) + +(defun main () + (loop do (let ((line (readline "user> "))) + (if line (writeline (rep line)) (return))))) + +(env:set-env *repl-env* + (types:make-mal-symbol '|*ARGV*|) + (types:wrap-value (cdr common-lisp-user::*args*) + :listp t)) + +(if (null common-lisp-user::*args*) + (main) + (rep (format nil + "(load-file \"~a\")" + (car common-lisp-user::*args*)))) From 3a470921a0a1eba5dc8bd3e43c34887a51a9e066 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 22 Aug 2016 22:51:50 +0530 Subject: [PATCH 0101/2308] Add throw core function --- common_lisp/core.lisp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common_lisp/core.lisp b/common_lisp/core.lisp index f86c1af3be..42ddeb4ef8 100644 --- a/common_lisp/core.lisp +++ b/common_lisp/core.lisp @@ -181,4 +181,9 @@ (types:make-mal-builtin-fn (lambda (sequence) (types:make-mal-list (rest (map 'list #'identity - (mal-value sequence))))))))) + (mal-value sequence))))))) + + (cons (types:make-mal-symbol '|throw|) + (types:make-mal-builtin-fn (lambda (value) + (error 'types:mal-user-exception + :data value)))))) From a9e59041f746238ee4172dce1024ede411bb66ef Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 22 Aug 2016 23:08:12 +0530 Subject: [PATCH 0102/2308] Add 'apply' and 'map' core functions --- common_lisp/core.lisp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/common_lisp/core.lisp b/common_lisp/core.lisp index 42ddeb4ef8..4ef9e05d6b 100644 --- a/common_lisp/core.lisp +++ b/common_lisp/core.lisp @@ -186,4 +186,21 @@ (cons (types:make-mal-symbol '|throw|) (types:make-mal-builtin-fn (lambda (value) (error 'types:mal-user-exception - :data value)))))) + :data value)))) + + (cons (types:make-mal-symbol '|apply|) + (types:make-mal-builtin-fn (lambda (fn &rest values) + (let ((final-arg (map 'list + #'identity + (types:mal-value (car (last values))))) + (butlast-args (butlast values))) + (apply (types:mal-value fn) + (append butlast-args final-arg)))))) + + (cons (types:make-mal-symbol '|map|) + (types:make-mal-builtin-fn (lambda (fn sequence) + (let ((applicants (map 'list + #'identity + (types:mal-value sequence)))) + (types:make-mal-list (mapcar (types:mal-value fn) + applicants)))))))) From efc3d24c323d4aea362e6e05617a5098be025e48 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 22 Aug 2016 23:14:26 +0530 Subject: [PATCH 0103/2308] Add nil?, true?, false? and symbol? --- common_lisp/core.lisp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/common_lisp/core.lisp b/common_lisp/core.lisp index 4ef9e05d6b..cdb6162f4a 100644 --- a/common_lisp/core.lisp +++ b/common_lisp/core.lisp @@ -203,4 +203,22 @@ #'identity (types:mal-value sequence)))) (types:make-mal-list (mapcar (types:mal-value fn) - applicants)))))))) + applicants)))))) + + (cons (types:make-mal-symbol '|nil?|) + (types:make-mal-builtin-fn (lambda (value) + (types:make-mal-boolean (types:mal-nil-p value))))) + + (cons (types:make-mal-symbol '|true?|) + (types:make-mal-builtin-fn (lambda (value) + (types:make-mal-boolean (and (types:mal-boolean-p value) + (types:mal-value value)))))) + + (cons (types:make-mal-symbol '|false?|) + (types:make-mal-builtin-fn (lambda (value) + (types:make-mal-boolean (and (types:mal-boolean-p value) + (not (types:mal-value value))))))) + + (cons (types:make-mal-symbol '|symbol?|) + (types:make-mal-builtin-fn (lambda (value) + (types:make-mal-boolean (types:mal-symbol-p value))))))) From 381ff1a47a33ca58128a34172e9d43748f0be8a0 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 23 Aug 2016 00:02:03 +0530 Subject: [PATCH 0104/2308] Implement all core functions required for step 9, completes step 9 --- common_lisp/core.lisp | 104 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/common_lisp/core.lisp b/common_lisp/core.lisp index cdb6162f4a..476ccbc306 100644 --- a/common_lisp/core.lisp +++ b/common_lisp/core.lisp @@ -221,4 +221,106 @@ (cons (types:make-mal-symbol '|symbol?|) (types:make-mal-builtin-fn (lambda (value) - (types:make-mal-boolean (types:mal-symbol-p value))))))) + (types:make-mal-boolean (types:mal-symbol-p value))))) + + (cons (types:make-mal-symbol '|symbol|) + (types:make-mal-builtin-fn (lambda (string) + (types:make-mal-symbol (reader::read-from-string-preserving-case + (types:mal-value string)))))) + + (cons (types:make-mal-symbol '|keyword|) + (types:make-mal-builtin-fn (lambda (keyword) + (if (types:mal-keyword-p keyword) + keyword + (types:make-mal-keyword (reader::read-from-string-preserving-case + (format nil + ":~a" + (types:mal-value keyword)))))))) + + (cons (types:make-mal-symbol '|keyword?|) + (types:make-mal-builtin-fn (lambda (value) + (types:make-mal-boolean (types:mal-keyword-p value))))) + + (cons (types:make-mal-symbol '|vector|) + (types:make-mal-builtin-fn (lambda (&rest elements) + (types:make-mal-vector (map 'vector #'identity elements))))) + + (cons (types:make-mal-symbol '|vector?|) + (types:make-mal-builtin-fn (lambda (value) + (types:make-mal-boolean (types:mal-vector-p value))))) + + (cons (types:make-mal-symbol '|hash-map|) + (types:make-mal-builtin-fn (lambda (&rest elements) + (let ((hash-map (make-hash-table :test 'types:mal-value=))) + (loop + for (key value) on elements + by #'cddr + do (setf (gethash key hash-map) value)) + (types:make-mal-hash-map hash-map))))) + + (cons (types:make-mal-symbol '|map?|) + (types:make-mal-builtin-fn (lambda (value) + (types:make-mal-boolean (types:mal-hash-map-p value))))) + + (cons (types:make-mal-symbol '|assoc|) + (types:make-mal-builtin-fn (lambda (hash-map &rest elements) + (let ((hash-map-value (types:mal-value hash-map)) + (new-hash-map (make-hash-table :test 'types:mal-value=))) + + (loop + for key being the hash-keys of hash-map-value + do (setf (gethash key new-hash-map) + (gethash key hash-map-value))) + + (loop + for (key value) on elements + by #'cddr + do (setf (gethash key new-hash-map) value)) + + (types:make-mal-hash-map new-hash-map))))) + + (cons (types:make-mal-symbol '|dissoc|) + (types:make-mal-builtin-fn (lambda (hash-map &rest elements) + (let ((hash-map-value (types:mal-value hash-map)) + (new-hash-map (make-hash-table :test 'types:mal-value=))) + + (loop + for key being the hash-keys of hash-map-value + do (when (not (member key elements :test #'types:mal-value=)) + (setf (gethash key new-hash-map) + (gethash key hash-map-value)))) + + (types:make-mal-hash-map new-hash-map))))) + + (cons (types:make-mal-symbol '|get|) + (types:make-mal-builtin-fn (lambda (hash-map key) + (or (and (types:mal-hash-map-p hash-map) + (gethash key (types:mal-value hash-map))) + (types:make-mal-nil nil))))) + + (cons (types:make-mal-symbol '|contains?|) + (types:make-mal-builtin-fn (lambda (hash-map key) + (if (gethash key (types:mal-value hash-map)) + (types:make-mal-boolean t) + (types:make-mal-boolean nil))))) + + (cons (types:make-mal-symbol '|keys|) + (types:make-mal-builtin-fn (lambda (hash-map) + (let ((hash-map-value (types:mal-value hash-map))) + (types:make-mal-list (loop + for key being the hash-keys of hash-map-value + collect key)))))) + + (cons (types:make-mal-symbol '|vals|) + (types:make-mal-builtin-fn (lambda (hash-map) + (let ((hash-map-value (types:mal-value hash-map))) + (types:make-mal-list (loop + for key being the hash-keys of hash-map-value + collect (gethash key hash-map-value))))))) + + (cons (types:make-mal-symbol '|sequential?|) + (types:make-mal-builtin-fn (lambda (value) + (if (or (types:mal-vector-p value) + (types:mal-list-p value)) + (types:make-mal-boolean t) + (types:make-mal-boolean nil))))))) From 5745b6ef3e23e68f44d594daf3ae97ab82384b9b Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 23 Aug 2016 22:34:00 +0530 Subject: [PATCH 0105/2308] Implement step A --- common_lisp/core.lisp | 65 ++++++++- common_lisp/stepA_mal.lisp | 281 +++++++++++++++++++++++++++++++++++++ 2 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 common_lisp/stepA_mal.lisp diff --git a/common_lisp/core.lisp b/common_lisp/core.lisp index 476ccbc306..e6122e390f 100644 --- a/common_lisp/core.lisp +++ b/common_lisp/core.lisp @@ -323,4 +323,67 @@ (if (or (types:mal-vector-p value) (types:mal-list-p value)) (types:make-mal-boolean t) - (types:make-mal-boolean nil))))))) + (types:make-mal-boolean nil))))) + + (cons (types:make-mal-symbol '|readline|) + (types:make-mal-builtin-fn (lambda (prompt) + (format *standard-output* (types:mal-value prompt)) + (force-output *standard-output*) + (types:wrap-value (read-line *standard-input* nil))))) + + (cons (types:make-mal-symbol '|string?|) + (types:make-mal-builtin-fn (lambda (value) + (types:make-mal-boolean (types:mal-string-p value))))) + + (cons (types:make-mal-symbol '|time-ms|) + (types:make-mal-builtin-fn (lambda () + + (types:make-mal-number (floor (/ (get-internal-real-time) + (/ internal-time-units-per-second + 1000))))))) + + (cons (types:make-mal-symbol '|conj|) + (types:make-mal-builtin-fn (lambda (value &rest elements) + (cond ((types:mal-list-p value) + (types:make-mal-list (append (nreverse elements) + (types:mal-value value)))) + ((types:mal-vector-p value) + (types:make-mal-vector (concatenate 'vector + (types:mal-value value) + elements))) + (t (error 'types:mal-user-exception)))))) + (cons (types:make-mal-symbol '|seq|) + (types:make-mal-builtin-fn (lambda (value) + (if (zerop (length (types:mal-value value))) + (types:make-mal-nil nil) + (cond ((types:mal-list-p value) + value) + ((types:mal-vector-p value) + (types:make-mal-list (map 'list + #'identity + (types:mal-value value)))) + ((types:mal-string-p value) + (types:make-mal-list (map 'list + (lambda (char) + (types:make-mal-string (make-string 1 :initial-element char))) + (types:mal-value value)))) + (t (error 'types:mal-user-exception))))))) + + (cons (types:make-mal-symbol '|with-meta|) + (types:make-mal-builtin-fn (lambda (value meta) + (funcall (switch-mal-type value + (types:string #'types:make-mal-string) + (types:symbol #'types:make-mal-symbol) + (types:list #'types:make-mal-list) + (types:vector #'types:make-mal-vector) + (types:hash-map #'types:make-mal-hash-map) + (types:fn #'types:make-mal-fn) + (types:builtin-fn #'types:make-mal-builtin-fn)) + (types:mal-value value) + :meta meta + :attrs (types:mal-attrs value))))) + + (cons (types:make-mal-symbol '|meta|) + (types:make-mal-builtin-fn (lambda (value) + (or (types:mal-meta value) + (types:make-mal-nil nil))))))) diff --git a/common_lisp/stepA_mal.lisp b/common_lisp/stepA_mal.lisp new file mode 100644 index 0000000000..1da65da783 --- /dev/null +++ b/common_lisp/stepA_mal.lisp @@ -0,0 +1,281 @@ +(require "reader") +(require "printer") +(require "types") +(require "env") +(require "core") + +(defpackage :mal + (:use :common-lisp + :types + :env + :reader + :printer + :core)) + +(in-package :mal) + +(define-condition invalid-function (types:mal-runtime-exception) + ((form :initarg :form :reader form) + (context :initarg :context :reader context)) + (:report (lambda (condition stream) + (format stream + "Invalid function '~a' provided while ~a" + (printer:pr-str (form condition)) + (if (string= (context condition) "apply") + "applying" + "defining macro"))))) + +(defvar *repl-env* (make-instance 'env:mal-environment)) + +(dolist (binding core:ns) + (env:set-env *repl-env* + (car binding) + (cdr binding))) + +(env:set-env *repl-env* + (types:make-mal-symbol '|eval|) + (types:make-mal-builtin-fn (lambda (ast) + (mal-eval ast *repl-env*)))) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (mal-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (mal-value hash-map)) + (new-hash-table (make-hash-table :test 'types:mal-value=))) + (loop + for key being the hash-keys of hash-map-value + do (setf (gethash (mal-eval key env) new-hash-table) + (mal-eval (gethash key hash-map-value) env))) + (make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env)) + (types:any ast))) + +(defun is-pair (value) + (and (or (mal-list-p value) + (mal-vector-p value)) + (not (zerop (length (mal-value value)))))) + +(defun quasiquote (ast) + (if (not (is-pair ast)) + (types:make-mal-list (list (types:make-mal-symbol '|quote|) + ast)) + (let ((forms (map 'list #'identity (mal-value ast)))) + (cond + ((mal-value= (make-mal-symbol '|unquote|) (first forms)) + (second forms)) + + ((and (is-pair (first forms)) + (mal-value= (make-mal-symbol '|splice-unquote|) + (first (mal-value (first forms))))) + (types:make-mal-list (list (types:make-mal-symbol '|concat|) + (second (mal-value (first forms))) + (quasiquote (make-mal-list (cdr forms)))))) + + (t (types:make-mal-list (list (types:make-mal-symbol '|cons|) + (quasiquote (first forms)) + (quasiquote (make-mal-list (cdr forms)))))))))) + +(defun is-macro-call (ast env) + (when (and (types:mal-list-p ast) + (not (zerop (length (mal-value ast))))) + (let* ((func-symbol (first (mal-value ast))) + (func (when (types:mal-symbol-p func-symbol) + (ignore-errors (env:get-env env func-symbol))))) + (and func + (types:mal-fn-p func) + (cdr (assoc 'is-macro (types:mal-attrs func))))))) + +(defun mal-macroexpand (ast env) + (loop + while (is-macro-call ast env) + do (let* ((forms (types:mal-value ast)) + (func (env:get-env env (first forms)))) + (setf ast (apply (mal-value func) + (cdr forms))))) + ast) + +(defun mal-eval (ast env) + (loop + do (setf ast (mal-macroexpand ast env)) + do (cond + ((null ast) (return (make-mal-nil nil))) + ((not (types:mal-list-p ast)) (return (eval-ast ast env))) + ((zerop (length (mal-value ast))) (return ast)) + (t (let ((forms (mal-value ast))) + (cond + ((mal-value= (make-mal-symbol '|quote|) (first forms)) + (return (second forms))) + + ((mal-value= (make-mal-symbol '|quasiquote|) (first forms)) + (setf ast (quasiquote (second forms)))) + + ((mal-value= (make-mal-symbol '|macroexpand|) (first forms)) + (return (mal-macroexpand (second forms) env))) + + ((mal-value= (make-mal-symbol '|def!|) (first forms)) + (return (env:set-env env (second forms) (mal-eval (third forms) env)))) + + ((mal-value= (make-mal-symbol '|defmacro!|) (first forms)) + (let ((value (mal-eval (third forms) env))) + (return (if (types:mal-fn-p value) + (env:set-env env + (second forms) + (progn + (setf (cdr (assoc 'is-macro (types:mal-attrs value))) t) + value)) + (error 'invalid-function + :form value + :context "macro"))))) + + ((mal-value= (make-mal-symbol '|let*|) (first forms)) + (let ((new-env (make-instance 'env:mal-environment + :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (mal-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + (types:make-mal-nil nil)) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + (setf ast (third forms) + env new-env))) + + ((mal-value= (make-mal-symbol '|do|) (first forms)) + (mapc (lambda (form) (mal-eval form env)) + (butlast (cdr forms))) + (setf ast (car (last forms)))) + + ((mal-value= (make-mal-symbol '|if|) (first forms)) + (let ((predicate (mal-eval (second forms) env))) + (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) + (mal-value= predicate (types:make-mal-boolean nil))) + (fourth forms) + (third forms))))) + + ((mal-value= (make-mal-symbol '|fn*|) (first forms)) + (return (let ((arglist (second forms)) + (body (third forms))) + (types:make-mal-fn (lambda (&rest args) + (mal-eval body (make-instance 'env:mal-environment + :parent env + :binds (map 'list + #'identity + (mal-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env) + (cons 'is-macro nil)))))) + + ((mal-value= (make-mal-symbol '|try*|) (first forms)) + (handler-case + (return (mal-eval (second forms) env)) + (types:mal-exception (condition) + (when (third forms) + (let ((catch-forms (types:mal-value (third forms)))) + (when (mal-value= (make-mal-symbol '|catch*|) + (first catch-forms)) + (return (mal-eval (third catch-forms) + (make-instance 'env:mal-environment + :parent env + :binds (list (second catch-forms)) + :exprs (list (if (typep condition 'types:mal-runtime-exception) + (types:make-mal-string (format nil "~a" condition)) + (types::mal-exception-data condition))))))))) + (error condition)))) + + (t (let* ((evaluated-list (eval-ast ast env)) + (function (car evaluated-list))) + ;; If first element is a mal function unwrap it + (cond ((types:mal-fn-p function) + (let* ((attrs (types:mal-attrs function))) + (setf ast (cdr (assoc 'ast attrs)) + env (make-instance 'env:mal-environment + :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))) + ((types:mal-builtin-fn-p function) + (return (apply (mal-value function) + (cdr evaluated-list)))) + (t (error 'invalid-function + :form function + :context "apply"))))))))))) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (types:mal-error (condition) + (format nil + "Error: ~a" + condition)) + (types:mal-runtime-exception (condition) + (format nil + "Exception: ~a" + condition)) + (types:mal-user-exception (condition) + (format nil + "Exception: ~a" + (pr-str (types::mal-exception-data condition)))) + (error (condition) + (format nil + "Internal error: ~a" + condition)))) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +(rep "(def! *ARGV* (list))") +(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +(rep "(def! *host-language* \"clisp\")") +(rep "(def! *gensym-counter* (atom 0))") +(rep "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))") +(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))") + +(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) + (format out-stream prompt) + (force-output out-stream) + (read-line in-stream nil)) + +(defun writeline (string) + (when string + (write-line string))) + +(defun main () + (loop do (let ((line (readline "user> "))) + (if line (writeline (rep line)) (return))))) + +(env:set-env *repl-env* + (types:make-mal-symbol '|*ARGV*|) + (types:wrap-value (cdr common-lisp-user::*args*) + :listp t)) + +(if (null common-lisp-user::*args*) + (main) + (rep (format nil + "(load-file \"~a\")" + (car common-lisp-user::*args*)))) From fd834e86a8adf48502f8fa3e7fb7bd667a76c637 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 23 Aug 2016 22:58:46 +0530 Subject: [PATCH 0106/2308] Make mal-errors catchable, self hosting is now complete Remove implementation of print-object for MAL objects. Also note the slowness of the implementation --- common_lisp/README.md | 4 ++++ common_lisp/stepA_mal.lisp | 7 ++++--- common_lisp/types.lisp | 4 ---- 3 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 common_lisp/README.md diff --git a/common_lisp/README.md b/common_lisp/README.md new file mode 100644 index 0000000000..18109fbd86 --- /dev/null +++ b/common_lisp/README.md @@ -0,0 +1,4 @@ +Implementation of MAL in Common Lisp + +- This implementation is not portable and works only with CLISP +- It is terribly (embarrassingly) slow diff --git a/common_lisp/stepA_mal.lisp b/common_lisp/stepA_mal.lisp index 1da65da783..0b9443ff42 100644 --- a/common_lisp/stepA_mal.lisp +++ b/common_lisp/stepA_mal.lisp @@ -187,7 +187,7 @@ ((mal-value= (make-mal-symbol '|try*|) (first forms)) (handler-case (return (mal-eval (second forms) env)) - (types:mal-exception (condition) + ((or types:mal-exception types:mal-error) (condition) (when (third forms) (let ((catch-forms (types:mal-value (third forms)))) (when (mal-value= (make-mal-symbol '|catch*|) @@ -196,9 +196,10 @@ (make-instance 'env:mal-environment :parent env :binds (list (second catch-forms)) - :exprs (list (if (typep condition 'types:mal-runtime-exception) + :exprs (list (if (or (typep condition 'types:mal-runtime-exception) + (typep condition 'types:mal-error)) (types:make-mal-string (format nil "~a" condition)) - (types::mal-exception-data condition))))))))) + (types::mal-exception-data condition))))))))) (error condition)))) (t (let* ((evaluated-list (eval-ast ast env)) diff --git a/common_lisp/types.lisp b/common_lisp/types.lisp index d8a3988941..ab0ebbd99b 100644 --- a/common_lisp/types.lisp +++ b/common_lisp/types.lisp @@ -53,10 +53,6 @@ (type :accessor mal-type :initarg :type) (attrs :accessor mal-attrs :initarg :attrs))) -(defmethod print-object ((obj mal-type) out) - (with-slots (value type meta attrs) obj - (format out "#" type value meta attrs))) - (defmacro define-mal-type (type) ;; Create a class for given type and a convenience constructor and also export ;; them From c505538df9eec092d3d66aaa8a30b6fd72e6e8a5 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 23 Aug 2016 23:20:03 +0530 Subject: [PATCH 0107/2308] Rename common_lisp to clisp --- Makefile | 4 ++-- {common_lisp => clisp}/.dir-locals.el | 0 {common_lisp => clisp}/Dockerfile | 0 {common_lisp => clisp}/README.md | 0 {common_lisp => clisp}/core.lisp | 0 {common_lisp => clisp}/env.lisp | 0 {common_lisp => clisp}/printer.lisp | 0 {common_lisp => clisp}/reader.lisp | 0 {common_lisp => clisp}/run | 0 {common_lisp => clisp}/step0_repl.lisp | 0 {common_lisp => clisp}/step1_read_print.lisp | 0 {common_lisp => clisp}/step2_eval.lisp | 0 {common_lisp => clisp}/step3_env.lisp | 0 {common_lisp => clisp}/step4_if_fn_do.lisp | 0 {common_lisp => clisp}/step5_tco.lisp | 0 {common_lisp => clisp}/step6_file.lisp | 0 {common_lisp => clisp}/step7_quote.lisp | 0 {common_lisp => clisp}/step8_macros.lisp | 0 {common_lisp => clisp}/step9_try.lisp | 0 {common_lisp => clisp}/stepA_mal.lisp | 0 {common_lisp => clisp}/types.lisp | 0 {common_lisp => clisp}/utils.lisp | 0 22 files changed, 2 insertions(+), 2 deletions(-) rename {common_lisp => clisp}/.dir-locals.el (100%) rename {common_lisp => clisp}/Dockerfile (100%) rename {common_lisp => clisp}/README.md (100%) rename {common_lisp => clisp}/core.lisp (100%) rename {common_lisp => clisp}/env.lisp (100%) rename {common_lisp => clisp}/printer.lisp (100%) rename {common_lisp => clisp}/reader.lisp (100%) rename {common_lisp => clisp}/run (100%) rename {common_lisp => clisp}/step0_repl.lisp (100%) rename {common_lisp => clisp}/step1_read_print.lisp (100%) rename {common_lisp => clisp}/step2_eval.lisp (100%) rename {common_lisp => clisp}/step3_env.lisp (100%) rename {common_lisp => clisp}/step4_if_fn_do.lisp (100%) rename {common_lisp => clisp}/step5_tco.lisp (100%) rename {common_lisp => clisp}/step6_file.lisp (100%) rename {common_lisp => clisp}/step7_quote.lisp (100%) rename {common_lisp => clisp}/step8_macros.lisp (100%) rename {common_lisp => clisp}/step9_try.lisp (100%) rename {common_lisp => clisp}/stepA_mal.lisp (100%) rename {common_lisp => clisp}/types.lisp (100%) rename {common_lisp => clisp}/utils.lisp (100%) diff --git a/Makefile b/Makefile index 2d949b52f4..f1f54fdcd9 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ DOCKERIZE = # Settings # -IMPLS = ada awk bash c d chuck clojure coffee common_lisp cpp crystal cs erlang elisp \ +IMPLS = ada awk bash c d chuck clojure coffee clisp cpp crystal cs erlang elisp \ elixir es6 factor forth fsharp go groovy guile haskell haxe \ io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php plpgsql plsql powershell ps \ @@ -151,7 +151,7 @@ d_STEP_TO_PROG = d/$($(1)) chuck_STEP_TO_PROG = chuck/$($(1)).ck clojure_STEP_TO_PROG = clojure/target/$($(1)).jar coffee_STEP_TO_PROG = coffee/$($(1)).coffee -common_lisp_STEP_TO_PROG = common_lisp/$($(1)).lisp +clisp_STEP_TO_PROG = clisp/$($(1)).lisp cpp_STEP_TO_PROG = cpp/$($(1)) crystal_STEP_TO_PROG = crystal/$($(1)) cs_STEP_TO_PROG = cs/$($(1)).exe diff --git a/common_lisp/.dir-locals.el b/clisp/.dir-locals.el similarity index 100% rename from common_lisp/.dir-locals.el rename to clisp/.dir-locals.el diff --git a/common_lisp/Dockerfile b/clisp/Dockerfile similarity index 100% rename from common_lisp/Dockerfile rename to clisp/Dockerfile diff --git a/common_lisp/README.md b/clisp/README.md similarity index 100% rename from common_lisp/README.md rename to clisp/README.md diff --git a/common_lisp/core.lisp b/clisp/core.lisp similarity index 100% rename from common_lisp/core.lisp rename to clisp/core.lisp diff --git a/common_lisp/env.lisp b/clisp/env.lisp similarity index 100% rename from common_lisp/env.lisp rename to clisp/env.lisp diff --git a/common_lisp/printer.lisp b/clisp/printer.lisp similarity index 100% rename from common_lisp/printer.lisp rename to clisp/printer.lisp diff --git a/common_lisp/reader.lisp b/clisp/reader.lisp similarity index 100% rename from common_lisp/reader.lisp rename to clisp/reader.lisp diff --git a/common_lisp/run b/clisp/run similarity index 100% rename from common_lisp/run rename to clisp/run diff --git a/common_lisp/step0_repl.lisp b/clisp/step0_repl.lisp similarity index 100% rename from common_lisp/step0_repl.lisp rename to clisp/step0_repl.lisp diff --git a/common_lisp/step1_read_print.lisp b/clisp/step1_read_print.lisp similarity index 100% rename from common_lisp/step1_read_print.lisp rename to clisp/step1_read_print.lisp diff --git a/common_lisp/step2_eval.lisp b/clisp/step2_eval.lisp similarity index 100% rename from common_lisp/step2_eval.lisp rename to clisp/step2_eval.lisp diff --git a/common_lisp/step3_env.lisp b/clisp/step3_env.lisp similarity index 100% rename from common_lisp/step3_env.lisp rename to clisp/step3_env.lisp diff --git a/common_lisp/step4_if_fn_do.lisp b/clisp/step4_if_fn_do.lisp similarity index 100% rename from common_lisp/step4_if_fn_do.lisp rename to clisp/step4_if_fn_do.lisp diff --git a/common_lisp/step5_tco.lisp b/clisp/step5_tco.lisp similarity index 100% rename from common_lisp/step5_tco.lisp rename to clisp/step5_tco.lisp diff --git a/common_lisp/step6_file.lisp b/clisp/step6_file.lisp similarity index 100% rename from common_lisp/step6_file.lisp rename to clisp/step6_file.lisp diff --git a/common_lisp/step7_quote.lisp b/clisp/step7_quote.lisp similarity index 100% rename from common_lisp/step7_quote.lisp rename to clisp/step7_quote.lisp diff --git a/common_lisp/step8_macros.lisp b/clisp/step8_macros.lisp similarity index 100% rename from common_lisp/step8_macros.lisp rename to clisp/step8_macros.lisp diff --git a/common_lisp/step9_try.lisp b/clisp/step9_try.lisp similarity index 100% rename from common_lisp/step9_try.lisp rename to clisp/step9_try.lisp diff --git a/common_lisp/stepA_mal.lisp b/clisp/stepA_mal.lisp similarity index 100% rename from common_lisp/stepA_mal.lisp rename to clisp/stepA_mal.lisp diff --git a/common_lisp/types.lisp b/clisp/types.lisp similarity index 100% rename from common_lisp/types.lisp rename to clisp/types.lisp diff --git a/common_lisp/utils.lisp b/clisp/utils.lisp similarity index 100% rename from common_lisp/utils.lisp rename to clisp/utils.lisp From 844ac738133c64ec6003a4dee1d0232b7e8d29c8 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 27 Aug 2016 18:04:54 +0530 Subject: [PATCH 0108/2308] Add travis config for clisp, also add stats target for clisp --- .travis.yml | 1 + clisp/Makefile | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 clisp/Makefile diff --git a/.travis.yml b/.travis.yml index f7e923516e..8780a9d018 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ matrix: - {env: IMPL=coffee, services: [docker]} - {env: IMPL=cs, services: [docker]} - {env: IMPL=chuck, services: [docker]} + - {env: IMPL=clisp, services: [docker]} - {env: IMPL=clojure, services: [docker]} - {env: IMPL=crystal, services: [docker]} - {env: IMPL=d, services: [docker]} diff --git a/clisp/Makefile b/clisp/Makefile new file mode 100644 index 0000000000..102683966b --- /dev/null +++ b/clisp/Makefile @@ -0,0 +1,16 @@ +SOURCES_BASE = utils.lisp types.lisp reader.lisp printer.lisp +SOURCES_LISP = env.lisp core.lisp stepA_mal.lisp +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +all: + true + +.PHONY: stats + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" + +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" From 098095fa3e5d17375565191ad05f76699824152a Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 27 Aug 2016 23:33:29 +0530 Subject: [PATCH 0109/2308] Optimize tokenizer a bit Avoid ignore-errors forms, apparently the ignore-errors form cost around 100 milliseconds in step 1 tests --- clisp/reader.lisp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/clisp/reader.lisp b/clisp/reader.lisp index 9a90bcdbc8..2a31c2638c 100644 --- a/clisp/reader.lisp +++ b/clisp/reader.lisp @@ -50,18 +50,21 @@ '(#\Space #\Newline #\Backspace #\Tab #\Linefeed #\Page #\Return #\Rubout #\,)) + (defun tokenize (string) - (remove-if (lambda (token) - (or (zerop (length token)) - (char= (char token 0) #\;))) - (loop - with end = (length string) - for start = 0 then (regexp:match-end match) - for match = (ignore-errors - (regexp:match *tokenizer-re* string :start start)) - while (and match (< start end)) - collect (string-trim *whitespace-chars* - (regexp:match-string string match))))) + (let (tokens) + (loop + with end = (length string) + for start = 0 then (regexp:match-end match) + for match = (when (< start end) + (regexp:match *tokenizer-re* string :start start)) + while match + do (let ((token (string-trim *whitespace-chars* + (regexp:match-string string match)))) + (unless (or (zerop (length token)) + (char= (char token 0) #\;)) + (push token tokens)))) + (nreverse tokens))) (defstruct (token-reader) (tokens nil)) From 961c035bc3b2c7a6c74c5d35202595e6d8d6b994 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 27 Aug 2016 23:52:27 +0530 Subject: [PATCH 0110/2308] Trying out defstruct instead of defclass for MAL types --- clisp/types.lisp | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/clisp/types.lisp b/clisp/types.lisp index ab0ebbd99b..04a599ea40 100644 --- a/clisp/types.lisp +++ b/clisp/types.lisp @@ -47,40 +47,44 @@ (define-condition mal-user-exception (mal-exception) ((data :accessor mal-exception-data :initarg :data))) -(defclass mal-type () - ((value :accessor mal-value :initarg :value) - (meta :accessor mal-meta :initarg :meta :initform nil) - (type :accessor mal-type :initarg :type) - (attrs :accessor mal-attrs :initarg :attrs))) +(defstruct mal-type + (type nil) + (value nil) + meta + attrs) + +(defun mal-value (mal-data) + (mal-type-value mal-data)) + +(defun mal-type (mal-data) + (mal-type-type mal-data)) + +(defun mal-attrs (mal-data) + (mal-type-attrs mal-data)) + +(defun mal-meta (mal-data) + (mal-type-meta mal-data)) (defmacro define-mal-type (type) ;; Create a class for given type and a convenience constructor and also export ;; them - (let ((name (intern (string-upcase (concatenate 'string - "mal-" - (symbol-name type))))) - (constructor (intern (string-upcase (concatenate 'string + (let ((constructor (intern (string-upcase (concatenate 'string "make-mal-" (symbol-name type))))) (predicate (intern (string-upcase (concatenate 'string "mal-" (symbol-name type) "-p"))))) - `(progn (defclass ,name (mal-type) - ((type :accessor mal-type - :initarg :type - :initform ',type))) - - (defun ,constructor (value &key meta attrs) - (make-instance ',name + `(progn (defun ,constructor (value &key meta attrs) + (make-mal-type :type ',type :value value :meta meta :attrs attrs)) + (defun ,predicate (value) (when (typep value 'mal-type) (equal (mal-type value) ',type))) - (export ',name) (export ',constructor) (export ',predicate)))) From e929d5be8ea42f1c868b386a34a49460d9e8c560 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 10:45:58 +0530 Subject: [PATCH 0111/2308] Use compiled regex instead of raw ones --- clisp/reader.lisp | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/clisp/reader.lisp b/clisp/reader.lisp index 2a31c2638c..3ab4b72f0e 100644 --- a/clisp/reader.lisp +++ b/clisp/reader.lisp @@ -12,9 +12,9 @@ \\)\\|[^\"\\]\\)*\"$" "RE string") -(defvar *tokenizer-re* "[[:space:],]*\\(~@\\|[][{}()~`'^@]\\|\"\\(\\\\\\(.\\| +(defvar *tokenizer-re* (regexp:regexp-compile "[[:space:],]*\\(~@\\|[][{}()~`'^@]\\|\"\\(\\\\\\(.\\| \\)\\|[^\"\\]\\)*\"\\?\\|;[^ -]*\\|[^][[:space:]~{}()@^`'\";]*\\)" +]*\\|[^][[:space:]~{}()@^`'\";]*\\)") "RE") (define-condition eof (types:mal-error) @@ -50,20 +50,21 @@ '(#\Space #\Newline #\Backspace #\Tab #\Linefeed #\Page #\Return #\Rubout #\,)) - (defun tokenize (string) (let (tokens) - (loop - with end = (length string) - for start = 0 then (regexp:match-end match) - for match = (when (< start end) - (regexp:match *tokenizer-re* string :start start)) - while match - do (let ((token (string-trim *whitespace-chars* - (regexp:match-string string match)))) - (unless (or (zerop (length token)) - (char= (char token 0) #\;)) - (push token tokens)))) + (do* ((start 0) + (end (length string)) + (match t)) + ((not match)) + (setf match (when (< start end) + (nth-value 1 + (regexp:regexp-exec *tokenizer-re* string :start start)))) + (when match + (setf start (regexp:match-end match)) + (let ((token (regexp:match-string string match))) + (unless (or (zerop (length token)) + (char= (char token 0) #\;)) + (push token tokens))))) (nreverse tokens))) (defstruct (token-reader) From f2539b37e50e04b51c4bb7781f9fc7810fb26d7f Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 11:19:45 +0530 Subject: [PATCH 0112/2308] Use compiled string and digit regular expressions --- clisp/reader.lisp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/clisp/reader.lisp b/clisp/reader.lisp index 3ab4b72f0e..dbfc9263f9 100644 --- a/clisp/reader.lisp +++ b/clisp/reader.lisp @@ -8,14 +8,17 @@ (in-package :reader) -(defvar *string-re* "^\"\\(\\\\\\(.\\| -\\)\\|[^\"\\]\\)*\"$" - "RE string") +(defvar *string-re* (regexp:regexp-compile "^\"\\(\\\\\\(.\\| +\\)\\|[^\"\\]\\)*\"$") + "Regular expression to match string") + +(defvar *digit-re* (regexp:regexp-compile "^\\(-\\|+\\)\\?[[:digit:]]\\+$") + "Regular expression to match digits") (defvar *tokenizer-re* (regexp:regexp-compile "[[:space:],]*\\(~@\\|[][{}()~`'^@]\\|\"\\(\\\\\\(.\\| \\)\\|[^\"\\]\\)*\"\\?\\|;[^ ]*\\|[^][[:space:]~{}()@^`'\";]*\\)") - "RE") + "Regular expression to match LISP code") (define-condition eof (types:mal-error) ((context :initarg :context :reader context)) @@ -26,7 +29,7 @@ (defun parse-string (token) (if (and (> (length token) 1) - (regexp:match *string-re* token)) + (regexp:regexp-exec *string-re* token)) (progn (read-from-string (utils:replace-all token "\\n" @@ -169,7 +172,7 @@ (defun read-atom (reader) (let ((token (next reader))) (cond - ((regexp:match "^\\(-\\|+\\)\\?[[:digit:]]\\+$" token) + ((regexp:regexp-exec *digit-re* token) (make-mal-number (read-from-string token))) ((string= token "false") (make-mal-boolean nil)) From adaa0a00479622f07da3f3bb3655c71ff6e92268 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 14:15:58 +0530 Subject: [PATCH 0113/2308] Use maphash to print hash-tables --- clisp/printer.lisp | 17 +++++++++-------- clisp/reader.lisp | 4 ++-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/clisp/printer.lisp b/clisp/printer.lisp index f5fffbca26..587c1e68f8 100644 --- a/clisp/printer.lisp +++ b/clisp/printer.lisp @@ -23,14 +23,15 @@ "{" (format nil "~{~a~^ ~}" - (mapcar (lambda (key-value) - (format nil - "~a ~a" - (pr-str (car key-value) print-readably) - (pr-str (cdr key-value) print-readably))) - (loop - for key being the hash-keys of hash-map-value - collect (cons key (gethash key hash-map-value))))) + (let (entries) + (maphash (lambda (key value) + (push (format nil + "~a ~a" + (pr-str key print-readably) + (pr-str value print-readably)) + entries)) + hash-map-value) + (nreverse entries))) "}"))) (defun pr-string (ast &optional (print-readably t)) diff --git a/clisp/reader.lisp b/clisp/reader.lisp index dbfc9263f9..1a35b6547c 100644 --- a/clisp/reader.lisp +++ b/clisp/reader.lisp @@ -172,8 +172,6 @@ (defun read-atom (reader) (let ((token (next reader))) (cond - ((regexp:regexp-exec *digit-re* token) - (make-mal-number (read-from-string token))) ((string= token "false") (make-mal-boolean nil)) ((string= token "true") @@ -184,4 +182,6 @@ (make-mal-string (parse-string token))) ((char= (char token 0) #\:) (make-mal-keyword (read-from-string-preserving-case token))) + ((regexp:regexp-exec *digit-re* token) + (make-mal-number (read-from-string token))) (t (make-mal-symbol (read-from-string-preserving-case token)))))) From b54ada313a334c595f1981f348106706dd0f4f21 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 17:58:06 +0530 Subject: [PATCH 0114/2308] Rename mal-type to mal-data --- clisp/core.lisp | 76 ++++++++++++++++++------------------- clisp/env.lisp | 2 +- clisp/printer.lisp | 18 ++++----- clisp/step1_read_print.lisp | 2 +- clisp/step2_eval.lisp | 10 ++--- clisp/step3_env.lisp | 12 +++--- clisp/step4_if_fn_do.lisp | 14 +++---- clisp/step5_tco.lisp | 18 ++++----- clisp/step6_file.lisp | 18 ++++----- clisp/step7_quote.lisp | 26 ++++++------- clisp/step8_macros.lisp | 38 +++++++++---------- clisp/step9_try.lisp | 40 +++++++++---------- clisp/stepA_mal.lisp | 40 +++++++++---------- clisp/types.lisp | 72 +++++++++++++++-------------------- 14 files changed, 187 insertions(+), 199 deletions(-) diff --git a/clisp/core.lisp b/clisp/core.lisp index e6122e390f..b18e59812c 100644 --- a/clisp/core.lisp +++ b/clisp/core.lisp @@ -84,7 +84,7 @@ (cons (types:make-mal-symbol '|empty?|) (types:make-mal-builtin-fn (lambda (value) - (types:make-mal-boolean (zerop (length (mal-value value))))))) + (types:make-mal-boolean (zerop (length (mal-data-value value))))))) (cons (types:make-mal-symbol '|count|) (types:make-mal-builtin-fn (lambda (value) @@ -120,7 +120,7 @@ (cons (types:make-mal-symbol '|read-string|) (types:make-mal-builtin-fn (lambda (value) - (reader:read-str (types:mal-value value))))) + (reader:read-str (types:mal-data-value value))))) (cons (types:make-mal-symbol '|slurp|) (types:make-mal-builtin-fn (lambda (filename) @@ -136,17 +136,17 @@ (cons (types:make-mal-symbol '|deref|) (types:make-mal-builtin-fn (lambda (atom) - (types:mal-value atom)))) + (types:mal-data-value atom)))) (cons (types:make-mal-symbol '|reset!|) (types:make-mal-builtin-fn (lambda (atom value) - (setf (types:mal-value atom) value)))) + (setf (types:mal-data-value atom) value)))) (cons (types:make-mal-symbol '|swap!|) (types:make-mal-builtin-fn (lambda (atom fn &rest args) - (setf (types:mal-value atom) - (apply (mal-value fn) - (append (list (types:mal-value atom)) + (setf (types:mal-data-value atom) + (apply (mal-data-value fn) + (append (list (types:mal-data-value atom)) args)))))) (cons (types:make-mal-symbol '|cons|) @@ -154,34 +154,34 @@ (types:make-mal-list (cons element (map 'list #'identity - (mal-value list))))))) + (mal-data-value list))))))) (cons (types:make-mal-symbol '|concat|) (types:make-mal-builtin-fn (lambda (&rest lists) (types:make-mal-list (apply #'concatenate 'list - (mapcar #'types:mal-value lists)))))) + (mapcar #'types:mal-data-value lists)))))) (cons (types:make-mal-symbol '|nth|) (types:make-mal-builtin-fn (lambda (sequence index) - (or (nth (mal-value index) - (map 'list #'identity (mal-value sequence))) + (or (nth (mal-data-value index) + (map 'list #'identity (mal-data-value sequence))) (error 'index-error - :size (length (mal-value sequence)) - :index (mal-value index) + :size (length (mal-data-value sequence)) + :index (mal-data-value index) :sequence sequence))))) (cons (types:make-mal-symbol '|first|) (types:make-mal-builtin-fn (lambda (sequence) - (or (first (map 'list #'identity (mal-value sequence))) + (or (first (map 'list #'identity (mal-data-value sequence))) (types:make-mal-nil nil))))) (cons (types:make-mal-symbol '|rest|) (types:make-mal-builtin-fn (lambda (sequence) (types:make-mal-list (rest (map 'list #'identity - (mal-value sequence))))))) + (mal-data-value sequence))))))) (cons (types:make-mal-symbol '|throw|) (types:make-mal-builtin-fn (lambda (value) @@ -192,17 +192,17 @@ (types:make-mal-builtin-fn (lambda (fn &rest values) (let ((final-arg (map 'list #'identity - (types:mal-value (car (last values))))) + (types:mal-data-value (car (last values))))) (butlast-args (butlast values))) - (apply (types:mal-value fn) + (apply (types:mal-data-value fn) (append butlast-args final-arg)))))) (cons (types:make-mal-symbol '|map|) (types:make-mal-builtin-fn (lambda (fn sequence) (let ((applicants (map 'list #'identity - (types:mal-value sequence)))) - (types:make-mal-list (mapcar (types:mal-value fn) + (types:mal-data-value sequence)))) + (types:make-mal-list (mapcar (types:mal-data-value fn) applicants)))))) (cons (types:make-mal-symbol '|nil?|) @@ -212,12 +212,12 @@ (cons (types:make-mal-symbol '|true?|) (types:make-mal-builtin-fn (lambda (value) (types:make-mal-boolean (and (types:mal-boolean-p value) - (types:mal-value value)))))) + (types:mal-data-value value)))))) (cons (types:make-mal-symbol '|false?|) (types:make-mal-builtin-fn (lambda (value) (types:make-mal-boolean (and (types:mal-boolean-p value) - (not (types:mal-value value))))))) + (not (types:mal-data-value value))))))) (cons (types:make-mal-symbol '|symbol?|) (types:make-mal-builtin-fn (lambda (value) @@ -226,7 +226,7 @@ (cons (types:make-mal-symbol '|symbol|) (types:make-mal-builtin-fn (lambda (string) (types:make-mal-symbol (reader::read-from-string-preserving-case - (types:mal-value string)))))) + (types:mal-data-value string)))))) (cons (types:make-mal-symbol '|keyword|) (types:make-mal-builtin-fn (lambda (keyword) @@ -235,7 +235,7 @@ (types:make-mal-keyword (reader::read-from-string-preserving-case (format nil ":~a" - (types:mal-value keyword)))))))) + (types:mal-data-value keyword)))))))) (cons (types:make-mal-symbol '|keyword?|) (types:make-mal-builtin-fn (lambda (value) @@ -264,7 +264,7 @@ (cons (types:make-mal-symbol '|assoc|) (types:make-mal-builtin-fn (lambda (hash-map &rest elements) - (let ((hash-map-value (types:mal-value hash-map)) + (let ((hash-map-value (types:mal-data-value hash-map)) (new-hash-map (make-hash-table :test 'types:mal-value=))) (loop @@ -281,7 +281,7 @@ (cons (types:make-mal-symbol '|dissoc|) (types:make-mal-builtin-fn (lambda (hash-map &rest elements) - (let ((hash-map-value (types:mal-value hash-map)) + (let ((hash-map-value (types:mal-data-value hash-map)) (new-hash-map (make-hash-table :test 'types:mal-value=))) (loop @@ -295,25 +295,25 @@ (cons (types:make-mal-symbol '|get|) (types:make-mal-builtin-fn (lambda (hash-map key) (or (and (types:mal-hash-map-p hash-map) - (gethash key (types:mal-value hash-map))) + (gethash key (types:mal-data-value hash-map))) (types:make-mal-nil nil))))) (cons (types:make-mal-symbol '|contains?|) (types:make-mal-builtin-fn (lambda (hash-map key) - (if (gethash key (types:mal-value hash-map)) + (if (gethash key (types:mal-data-value hash-map)) (types:make-mal-boolean t) (types:make-mal-boolean nil))))) (cons (types:make-mal-symbol '|keys|) (types:make-mal-builtin-fn (lambda (hash-map) - (let ((hash-map-value (types:mal-value hash-map))) + (let ((hash-map-value (types:mal-data-value hash-map))) (types:make-mal-list (loop for key being the hash-keys of hash-map-value collect key)))))) (cons (types:make-mal-symbol '|vals|) (types:make-mal-builtin-fn (lambda (hash-map) - (let ((hash-map-value (types:mal-value hash-map))) + (let ((hash-map-value (types:mal-data-value hash-map))) (types:make-mal-list (loop for key being the hash-keys of hash-map-value collect (gethash key hash-map-value))))))) @@ -327,7 +327,7 @@ (cons (types:make-mal-symbol '|readline|) (types:make-mal-builtin-fn (lambda (prompt) - (format *standard-output* (types:mal-value prompt)) + (format *standard-output* (types:mal-data-value prompt)) (force-output *standard-output*) (types:wrap-value (read-line *standard-input* nil))))) @@ -346,27 +346,27 @@ (types:make-mal-builtin-fn (lambda (value &rest elements) (cond ((types:mal-list-p value) (types:make-mal-list (append (nreverse elements) - (types:mal-value value)))) + (types:mal-data-value value)))) ((types:mal-vector-p value) (types:make-mal-vector (concatenate 'vector - (types:mal-value value) + (types:mal-data-value value) elements))) (t (error 'types:mal-user-exception)))))) (cons (types:make-mal-symbol '|seq|) (types:make-mal-builtin-fn (lambda (value) - (if (zerop (length (types:mal-value value))) + (if (zerop (length (types:mal-data-value value))) (types:make-mal-nil nil) (cond ((types:mal-list-p value) value) ((types:mal-vector-p value) (types:make-mal-list (map 'list #'identity - (types:mal-value value)))) + (types:mal-data-value value)))) ((types:mal-string-p value) (types:make-mal-list (map 'list (lambda (char) (types:make-mal-string (make-string 1 :initial-element char))) - (types:mal-value value)))) + (types:mal-data-value value)))) (t (error 'types:mal-user-exception))))))) (cons (types:make-mal-symbol '|with-meta|) @@ -379,11 +379,11 @@ (types:hash-map #'types:make-mal-hash-map) (types:fn #'types:make-mal-fn) (types:builtin-fn #'types:make-mal-builtin-fn)) - (types:mal-value value) + (types:mal-data-value value) :meta meta - :attrs (types:mal-attrs value))))) + :attrs (types:mal-data-attrs value))))) (cons (types:make-mal-symbol '|meta|) (types:make-mal-builtin-fn (lambda (value) - (or (types:mal-meta value) + (or (types:mal-data-meta value) (types:make-mal-nil nil))))))) diff --git a/clisp/env.lisp b/clisp/env.lisp index bf59ba4457..7b1a185805 100644 --- a/clisp/env.lisp +++ b/clisp/env.lisp @@ -55,7 +55,7 @@ (if value value (error 'undefined-symbol - :symbol (format nil "~a" (types:mal-value symbol)))))) + :symbol (format nil "~a" (types:mal-data-value symbol)))))) (defmethod set-env ((env mal-environment) symbol value) (setf (gethash symbol (mal-env-bindings env)) value)) diff --git a/clisp/printer.lisp b/clisp/printer.lisp index 587c1e68f8..c6038fd192 100644 --- a/clisp/printer.lisp +++ b/clisp/printer.lisp @@ -14,11 +14,11 @@ "~{~a~^ ~}" (map 'list (lambda (value) (pr-str value print-readably)) - (types:mal-value sequence))) + (types:mal-data-value sequence))) end-delimiter)) (defun pr-mal-hash-map (hash-map &optional (print-readably t)) - (let ((hash-map-value (types:mal-value hash-map))) + (let ((hash-map-value (types:mal-data-value hash-map))) (concatenate 'string "{" (format nil @@ -36,24 +36,24 @@ (defun pr-string (ast &optional (print-readably t)) (if print-readably - (utils:replace-all (prin1-to-string (types:mal-value ast)) + (utils:replace-all (prin1-to-string (types:mal-data-value ast)) " " "\\n") - (types:mal-value ast))) + (types:mal-data-value ast))) (defun pr-str (ast &optional (print-readably t)) (when ast (switch-mal-type ast - (types:number (format nil "~d" (types:mal-value ast))) - (types:boolean (if (types:mal-value ast) "true" "false")) + (types:number (format nil "~d" (types:mal-data-value ast))) + (types:boolean (if (types:mal-data-value ast) "true" "false")) (types:nil "nil") (types:string (pr-string ast print-readably)) - (types:symbol (format nil "~a" (types:mal-value ast))) - (types:keyword (format nil ":~a" (types:mal-value ast))) + (types:symbol (format nil "~a" (types:mal-data-value ast))) + (types:keyword (format nil ":~a" (types:mal-data-value ast))) (types:list (pr-mal-sequence "(" ast ")" print-readably)) (types:vector (pr-mal-sequence "[" ast "]" print-readably)) (types:hash-map (pr-mal-hash-map ast print-readably)) - (types:atom (format nil "(atom ~a)" (pr-str (types:mal-value ast)))) + (types:atom (format nil "(atom ~a)" (pr-str (types:mal-data-value ast)))) (types:fn "#") (types:builtin-fn "#")))) diff --git a/clisp/step1_read_print.lisp b/clisp/step1_read_print.lisp index 767c3ac009..5913fb424d 100644 --- a/clisp/step1_read_print.lisp +++ b/clisp/step1_read_print.lisp @@ -18,7 +18,7 @@ (defun rep (string) (handler-case (mal-print (mal-eval (mal-read string) - (make-hash-table :test #'equal))) + nil)) (reader:eof (condition) (format nil "~a" diff --git a/clisp/step2_eval.lisp b/clisp/step2_eval.lisp index 8a4e41dc52..ae0f1e4be7 100644 --- a/clisp/step2_eval.lisp +++ b/clisp/step2_eval.lisp @@ -41,7 +41,7 @@ (if value value (error 'env:undefined-symbol - :symbol (format nil "~a" (types:mal-value symbol)))))) + :symbol (format nil "~a" (types:mal-data-value symbol)))))) (defun mal-read (string) (reader:read-str string)) @@ -49,10 +49,10 @@ (defun mal-eval (ast env) (cond ((not (types:mal-list-p ast)) (eval-ast ast env)) - ((zerop (length (mal-value ast))) ast) + ((zerop (length (mal-data-value ast))) ast) (t (progn (let ((evaluated-list (eval-ast ast env))) - (apply (mal-value (car evaluated-list)) + (apply (mal-data-value (car evaluated-list)) (cdr evaluated-list))))))) (defun mal-print (expression) @@ -61,10 +61,10 @@ (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) - (mal-value sequence))) + (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-value hash-map)) + (let ((hash-map-value (mal-data-value hash-map)) (new-hash-table (make-hash-table :test 'types:mal-value=))) (loop for key being the hash-keys of hash-map-value diff --git a/clisp/step3_env.lisp b/clisp/step3_env.lisp index 2a2ab9ad3f..550a7ae3b7 100644 --- a/clisp/step3_env.lisp +++ b/clisp/step3_env.lisp @@ -33,10 +33,10 @@ (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) - (mal-value sequence))) + (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-value hash-map)) + (let ((hash-map-value (mal-data-value hash-map)) (new-hash-table (make-hash-table :test 'types:mal-value=))) (loop for key being the hash-keys of hash-map-value @@ -58,7 +58,7 @@ ;; Convert a potential vector to a list (bindings (map 'list #'identity - (types:mal-value (second forms))))) + (types:mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env @@ -74,14 +74,14 @@ (mal-eval (third forms) new-env))) (defun eval-list (ast env) - (let ((forms (mal-value ast))) + (let ((forms (mal-data-value ast))) (cond ((mal-value= (make-mal-symbol '|def!|) (first forms)) (env:set-env env (second forms) (mal-eval (third forms) env))) ((mal-value= (make-mal-symbol '|let*|) (first forms)) (eval-let* forms env)) (t (let ((evaluated-list (eval-ast ast env))) - (apply (types:mal-value (car evaluated-list)) + (apply (types:mal-data-value (car evaluated-list)) (cdr evaluated-list))))))) (defun mal-read (string) @@ -91,7 +91,7 @@ (cond ((null ast) (make-mal-nil nil)) ((not (types:mal-list-p ast)) (eval-ast ast env)) - ((zerop (length (mal-value ast))) ast) + ((zerop (length (mal-data-value ast))) ast) (t (eval-list ast env)))) (defun mal-print (expression) diff --git a/clisp/step4_if_fn_do.lisp b/clisp/step4_if_fn_do.lisp index 00e073532d..c23b8e698a 100644 --- a/clisp/step4_if_fn_do.lisp +++ b/clisp/step4_if_fn_do.lisp @@ -24,10 +24,10 @@ (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) - (mal-value sequence))) + (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-value hash-map)) + (let ((hash-map-value (mal-data-value hash-map)) (new-hash-table (make-hash-table :test 'types:mal-value=))) (loop for key being the hash-keys of hash-map-value @@ -49,7 +49,7 @@ ;; Convert a potential vector to a list (bindings (map 'list #'identity - (mal-value (second forms))))) + (mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env @@ -65,7 +65,7 @@ (mal-eval (third forms) new-env))) (defun eval-list (ast env) - (let ((forms (mal-value ast))) + (let ((forms (mal-data-value ast))) (cond ((mal-value= (make-mal-symbol '|def!|) (first forms)) (env:set-env env (second forms) (mal-eval (third forms) env))) @@ -89,12 +89,12 @@ :parent env :binds (map 'list #'identity - (mal-value arglist)) + (mal-data-value arglist)) :exprs args)))))) (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) ;; If first element is a mal function unwrap it - (apply (mal-value function) + (apply (mal-data-value function) (cdr evaluated-list))))))) (defun mal-read (string) @@ -104,7 +104,7 @@ (cond ((null ast) (make-mal-nil nil)) ((not (types:mal-list-p ast)) (eval-ast ast env)) - ((zerop (length (mal-value ast))) ast) + ((zerop (length (mal-data-value ast))) ast) (t (eval-list ast env)))) (defun mal-print (expression) diff --git a/clisp/step5_tco.lisp b/clisp/step5_tco.lisp index 31eb643e17..ea64973369 100644 --- a/clisp/step5_tco.lisp +++ b/clisp/step5_tco.lisp @@ -24,10 +24,10 @@ (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) - (mal-value sequence))) + (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-value hash-map)) + (let ((hash-map-value (mal-data-value hash-map)) (new-hash-table (make-hash-table :test 'types:mal-value=))) (loop for key being the hash-keys of hash-map-value @@ -51,8 +51,8 @@ do (cond ((null ast) (return (make-mal-nil nil))) ((not (types:mal-list-p ast)) (return (eval-ast ast env))) - ((zerop (length (mal-value ast))) (return ast)) - (t (let ((forms (mal-value ast))) + ((zerop (length (mal-data-value ast))) (return ast)) + (t (let ((forms (mal-data-value ast))) (cond ((mal-value= (make-mal-symbol '|def!|) (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) @@ -63,7 +63,7 @@ ;; Convert a potential vector to a list (bindings (map 'list #'identity - (mal-value (second forms))))) + (mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env @@ -98,7 +98,7 @@ :parent env :binds (map 'list #'identity - (mal-value arglist)) + (mal-data-value arglist)) :exprs args))) :attrs (list (cons 'params arglist) (cons 'ast body) @@ -108,15 +108,15 @@ (function (car evaluated-list))) ;; If first element is a mal function unwrap it (if (not (types:mal-fn-p function)) - (return (apply (mal-value function) + (return (apply (mal-data-value function) (cdr evaluated-list))) - (let* ((attrs (types:mal-attrs function))) + (let* ((attrs (types:mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) env (make-instance 'env:mal-environment :parent (cdr (assoc 'env attrs)) :binds (map 'list #'identity - (mal-value (cdr (assoc 'params attrs)))) + (mal-data-value (cdr (assoc 'params attrs)))) :exprs (cdr evaluated-list))))))))))))) (defun mal-print (expression) diff --git a/clisp/step6_file.lisp b/clisp/step6_file.lisp index 8edcd90ac4..51e07835cd 100644 --- a/clisp/step6_file.lisp +++ b/clisp/step6_file.lisp @@ -29,10 +29,10 @@ (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) - (mal-value sequence))) + (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-value hash-map)) + (let ((hash-map-value (mal-data-value hash-map)) (new-hash-table (make-hash-table :test 'types:mal-value=))) (loop for key being the hash-keys of hash-map-value @@ -56,8 +56,8 @@ do (cond ((null ast) (return (make-mal-nil nil))) ((not (types:mal-list-p ast)) (return (eval-ast ast env))) - ((zerop (length (mal-value ast))) (return ast)) - (t (let ((forms (mal-value ast))) + ((zerop (length (mal-data-value ast))) (return ast)) + (t (let ((forms (mal-data-value ast))) (cond ((mal-value= (make-mal-symbol '|def!|) (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) @@ -68,7 +68,7 @@ ;; Convert a potential vector to a list (bindings (map 'list #'identity - (mal-value (second forms))))) + (mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env @@ -103,7 +103,7 @@ :parent env :binds (map 'list #'identity - (mal-value arglist)) + (mal-data-value arglist)) :exprs args))) :attrs (list (cons 'params arglist) (cons 'ast body) @@ -113,15 +113,15 @@ (function (car evaluated-list))) ;; If first element is a mal function unwrap it (if (not (types:mal-fn-p function)) - (return (apply (mal-value function) + (return (apply (mal-data-value function) (cdr evaluated-list))) - (let* ((attrs (types:mal-attrs function))) + (let* ((attrs (types:mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) env (make-instance 'env:mal-environment :parent (cdr (assoc 'env attrs)) :binds (map 'list #'identity - (mal-value (cdr (assoc 'params attrs)))) + (mal-data-value (cdr (assoc 'params attrs)))) :exprs (cdr evaluated-list))))))))))))) (defun mal-print (expression) diff --git a/clisp/step7_quote.lisp b/clisp/step7_quote.lisp index 76c2b591eb..d97bf4d16f 100644 --- a/clisp/step7_quote.lisp +++ b/clisp/step7_quote.lisp @@ -29,10 +29,10 @@ (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) - (mal-value sequence))) + (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-value hash-map)) + (let ((hash-map-value (mal-data-value hash-map)) (new-hash-table (make-hash-table :test 'types:mal-value=))) (loop for key being the hash-keys of hash-map-value @@ -51,22 +51,22 @@ (defun is-pair (value) (and (or (mal-list-p value) (mal-vector-p value)) - (not (zerop (length (mal-value value)))))) + (not (zerop (length (mal-data-value value)))))) (defun quasiquote (ast) (if (not (is-pair ast)) (types:make-mal-list (list (types:make-mal-symbol '|quote|) ast)) - (let ((forms (map 'list #'identity (mal-value ast)))) + (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond ((mal-value= (make-mal-symbol '|unquote|) (first forms)) (second forms)) ((and (is-pair (first forms)) (mal-value= (make-mal-symbol '|splice-unquote|) - (first (mal-value (first forms))))) + (first (mal-data-value (first forms))))) (types:make-mal-list (list (types:make-mal-symbol '|concat|) - (second (mal-value (first forms))) + (second (mal-data-value (first forms))) (quasiquote (make-mal-list (cdr forms)))))) (t (types:make-mal-list (list (types:make-mal-symbol '|cons|) @@ -81,8 +81,8 @@ do (cond ((null ast) (return (make-mal-nil nil))) ((not (types:mal-list-p ast)) (return (eval-ast ast env))) - ((zerop (length (mal-value ast))) (return ast)) - (t (let ((forms (mal-value ast))) + ((zerop (length (mal-data-value ast))) (return ast)) + (t (let ((forms (mal-data-value ast))) (cond ((mal-value= (make-mal-symbol '|quote|) (first forms)) (return (second forms))) @@ -99,7 +99,7 @@ ;; Convert a potential vector to a list (bindings (map 'list #'identity - (mal-value (second forms))))) + (mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env @@ -134,7 +134,7 @@ :parent env :binds (map 'list #'identity - (mal-value arglist)) + (mal-data-value arglist)) :exprs args))) :attrs (list (cons 'params arglist) (cons 'ast body) @@ -144,15 +144,15 @@ (function (car evaluated-list))) ;; If first element is a mal function unwrap it (if (not (types:mal-fn-p function)) - (return (apply (mal-value function) + (return (apply (mal-data-value function) (cdr evaluated-list))) - (let* ((attrs (types:mal-attrs function))) + (let* ((attrs (types:mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) env (make-instance 'env:mal-environment :parent (cdr (assoc 'env attrs)) :binds (map 'list #'identity - (mal-value (cdr (assoc 'params attrs)))) + (mal-data-value (cdr (assoc 'params attrs)))) :exprs (cdr evaluated-list))))))))))))) (defun mal-print (expression) diff --git a/clisp/step8_macros.lisp b/clisp/step8_macros.lisp index ae67284775..a4e490a881 100644 --- a/clisp/step8_macros.lisp +++ b/clisp/step8_macros.lisp @@ -40,10 +40,10 @@ (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) - (mal-value sequence))) + (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-value hash-map)) + (let ((hash-map-value (mal-data-value hash-map)) (new-hash-table (make-hash-table :test 'types:mal-value=))) (loop for key being the hash-keys of hash-map-value @@ -62,22 +62,22 @@ (defun is-pair (value) (and (or (mal-list-p value) (mal-vector-p value)) - (not (zerop (length (mal-value value)))))) + (not (zerop (length (mal-data-value value)))))) (defun quasiquote (ast) (if (not (is-pair ast)) (types:make-mal-list (list (types:make-mal-symbol '|quote|) ast)) - (let ((forms (map 'list #'identity (mal-value ast)))) + (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond ((mal-value= (make-mal-symbol '|unquote|) (first forms)) (second forms)) ((and (is-pair (first forms)) (mal-value= (make-mal-symbol '|splice-unquote|) - (first (mal-value (first forms))))) + (first (mal-data-value (first forms))))) (types:make-mal-list (list (types:make-mal-symbol '|concat|) - (second (mal-value (first forms))) + (second (mal-data-value (first forms))) (quasiquote (make-mal-list (cdr forms)))))) (t (types:make-mal-list (list (types:make-mal-symbol '|cons|) @@ -86,20 +86,20 @@ (defun is-macro-call (ast env) (when (and (types:mal-list-p ast) - (not (zerop (length (mal-value ast))))) - (let* ((func-symbol (first (mal-value ast))) + (not (zerop (length (mal-data-value ast))))) + (let* ((func-symbol (first (mal-data-value ast))) (func (when (types:mal-symbol-p func-symbol) (ignore-errors (env:get-env env func-symbol))))) (and func (types:mal-fn-p func) - (cdr (assoc 'is-macro (types:mal-attrs func))))))) + (cdr (assoc 'is-macro (types:mal-data-attrs func))))))) (defun mal-macroexpand (ast env) (loop while (is-macro-call ast env) - do (let* ((forms (types:mal-value ast)) + do (let* ((forms (types:mal-data-value ast)) (func (env:get-env env (first forms)))) - (setf ast (apply (mal-value func) + (setf ast (apply (mal-data-value func) (cdr forms))))) ast) @@ -109,8 +109,8 @@ do (cond ((null ast) (return (make-mal-nil nil))) ((not (types:mal-list-p ast)) (return (eval-ast ast env))) - ((zerop (length (mal-value ast))) (return ast)) - (t (let ((forms (mal-value ast))) + ((zerop (length (mal-data-value ast))) (return ast)) + (t (let ((forms (mal-data-value ast))) (cond ((mal-value= (make-mal-symbol '|quote|) (first forms)) (return (second forms))) @@ -130,7 +130,7 @@ (env:set-env env (second forms) (progn - (setf (cdr (assoc 'is-macro (types:mal-attrs value))) t) + (setf (cdr (assoc 'is-macro (types:mal-data-attrs value))) t) value)) (error 'invalid-function :form value @@ -142,7 +142,7 @@ ;; Convert a potential vector to a list (bindings (map 'list #'identity - (mal-value (second forms))))) + (mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env @@ -177,7 +177,7 @@ :parent env :binds (map 'list #'identity - (mal-value arglist)) + (mal-data-value arglist)) :exprs args))) :attrs (list (cons 'params arglist) (cons 'ast body) @@ -188,16 +188,16 @@ (function (car evaluated-list))) ;; If first element is a mal function unwrap it (cond ((types:mal-fn-p function) - (let* ((attrs (types:mal-attrs function))) + (let* ((attrs (types:mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) env (make-instance 'env:mal-environment :parent (cdr (assoc 'env attrs)) :binds (map 'list #'identity - (mal-value (cdr (assoc 'params attrs)))) + (mal-data-value (cdr (assoc 'params attrs)))) :exprs (cdr evaluated-list))))) ((types:mal-builtin-fn-p function) - (return (apply (mal-value function) + (return (apply (mal-data-value function) (cdr evaluated-list)))) (t (error 'invalid-function :form function diff --git a/clisp/step9_try.lisp b/clisp/step9_try.lisp index be6852624a..0f5a360e83 100644 --- a/clisp/step9_try.lisp +++ b/clisp/step9_try.lisp @@ -40,10 +40,10 @@ (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) - (mal-value sequence))) + (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-value hash-map)) + (let ((hash-map-value (mal-data-value hash-map)) (new-hash-table (make-hash-table :test 'types:mal-value=))) (loop for key being the hash-keys of hash-map-value @@ -62,22 +62,22 @@ (defun is-pair (value) (and (or (mal-list-p value) (mal-vector-p value)) - (not (zerop (length (mal-value value)))))) + (not (zerop (length (mal-data-value value)))))) (defun quasiquote (ast) (if (not (is-pair ast)) (types:make-mal-list (list (types:make-mal-symbol '|quote|) ast)) - (let ((forms (map 'list #'identity (mal-value ast)))) + (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond ((mal-value= (make-mal-symbol '|unquote|) (first forms)) (second forms)) ((and (is-pair (first forms)) (mal-value= (make-mal-symbol '|splice-unquote|) - (first (mal-value (first forms))))) + (first (mal-data-value (first forms))))) (types:make-mal-list (list (types:make-mal-symbol '|concat|) - (second (mal-value (first forms))) + (second (mal-data-value (first forms))) (quasiquote (make-mal-list (cdr forms)))))) (t (types:make-mal-list (list (types:make-mal-symbol '|cons|) @@ -86,20 +86,20 @@ (defun is-macro-call (ast env) (when (and (types:mal-list-p ast) - (not (zerop (length (mal-value ast))))) - (let* ((func-symbol (first (mal-value ast))) + (not (zerop (length (mal-data-value ast))))) + (let* ((func-symbol (first (mal-data-value ast))) (func (when (types:mal-symbol-p func-symbol) (ignore-errors (env:get-env env func-symbol))))) (and func (types:mal-fn-p func) - (cdr (assoc 'is-macro (types:mal-attrs func))))))) + (cdr (assoc 'is-macro (types:mal-data-attrs func))))))) (defun mal-macroexpand (ast env) (loop while (is-macro-call ast env) - do (let* ((forms (types:mal-value ast)) + do (let* ((forms (types:mal-data-value ast)) (func (env:get-env env (first forms)))) - (setf ast (apply (mal-value func) + (setf ast (apply (mal-data-value func) (cdr forms))))) ast) @@ -109,8 +109,8 @@ do (cond ((null ast) (return (make-mal-nil nil))) ((not (types:mal-list-p ast)) (return (eval-ast ast env))) - ((zerop (length (mal-value ast))) (return ast)) - (t (let ((forms (mal-value ast))) + ((zerop (length (mal-data-value ast))) (return ast)) + (t (let ((forms (mal-data-value ast))) (cond ((mal-value= (make-mal-symbol '|quote|) (first forms)) (return (second forms))) @@ -130,7 +130,7 @@ (env:set-env env (second forms) (progn - (setf (cdr (assoc 'is-macro (types:mal-attrs value))) t) + (setf (cdr (assoc 'is-macro (types:mal-data-attrs value))) t) value)) (error 'invalid-function :form value @@ -142,7 +142,7 @@ ;; Convert a potential vector to a list (bindings (map 'list #'identity - (mal-value (second forms))))) + (mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env @@ -177,7 +177,7 @@ :parent env :binds (map 'list #'identity - (mal-value arglist)) + (mal-data-value arglist)) :exprs args))) :attrs (list (cons 'params arglist) (cons 'ast body) @@ -189,7 +189,7 @@ (return (mal-eval (second forms) env)) (types:mal-exception (condition) (when (third forms) - (let ((catch-forms (types:mal-value (third forms)))) + (let ((catch-forms (types:mal-data-value (third forms)))) (when (mal-value= (make-mal-symbol '|catch*|) (first catch-forms)) (return (mal-eval (third catch-forms) @@ -205,16 +205,16 @@ (function (car evaluated-list))) ;; If first element is a mal function unwrap it (cond ((types:mal-fn-p function) - (let* ((attrs (types:mal-attrs function))) + (let* ((attrs (types:mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) env (make-instance 'env:mal-environment :parent (cdr (assoc 'env attrs)) :binds (map 'list #'identity - (mal-value (cdr (assoc 'params attrs)))) + (mal-data-value (cdr (assoc 'params attrs)))) :exprs (cdr evaluated-list))))) ((types:mal-builtin-fn-p function) - (return (apply (mal-value function) + (return (apply (mal-data-value function) (cdr evaluated-list)))) (t (error 'invalid-function :form function diff --git a/clisp/stepA_mal.lisp b/clisp/stepA_mal.lisp index 0b9443ff42..ba2cbc20e8 100644 --- a/clisp/stepA_mal.lisp +++ b/clisp/stepA_mal.lisp @@ -40,10 +40,10 @@ (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) - (mal-value sequence))) + (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-value hash-map)) + (let ((hash-map-value (mal-data-value hash-map)) (new-hash-table (make-hash-table :test 'types:mal-value=))) (loop for key being the hash-keys of hash-map-value @@ -62,22 +62,22 @@ (defun is-pair (value) (and (or (mal-list-p value) (mal-vector-p value)) - (not (zerop (length (mal-value value)))))) + (not (zerop (length (mal-data-value value)))))) (defun quasiquote (ast) (if (not (is-pair ast)) (types:make-mal-list (list (types:make-mal-symbol '|quote|) ast)) - (let ((forms (map 'list #'identity (mal-value ast)))) + (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond ((mal-value= (make-mal-symbol '|unquote|) (first forms)) (second forms)) ((and (is-pair (first forms)) (mal-value= (make-mal-symbol '|splice-unquote|) - (first (mal-value (first forms))))) + (first (mal-data-value (first forms))))) (types:make-mal-list (list (types:make-mal-symbol '|concat|) - (second (mal-value (first forms))) + (second (mal-data-value (first forms))) (quasiquote (make-mal-list (cdr forms)))))) (t (types:make-mal-list (list (types:make-mal-symbol '|cons|) @@ -86,20 +86,20 @@ (defun is-macro-call (ast env) (when (and (types:mal-list-p ast) - (not (zerop (length (mal-value ast))))) - (let* ((func-symbol (first (mal-value ast))) + (not (zerop (length (mal-data-value ast))))) + (let* ((func-symbol (first (mal-data-value ast))) (func (when (types:mal-symbol-p func-symbol) (ignore-errors (env:get-env env func-symbol))))) (and func (types:mal-fn-p func) - (cdr (assoc 'is-macro (types:mal-attrs func))))))) + (cdr (assoc 'is-macro (types:mal-data-attrs func))))))) (defun mal-macroexpand (ast env) (loop while (is-macro-call ast env) - do (let* ((forms (types:mal-value ast)) + do (let* ((forms (types:mal-data-value ast)) (func (env:get-env env (first forms)))) - (setf ast (apply (mal-value func) + (setf ast (apply (mal-data-value func) (cdr forms))))) ast) @@ -109,8 +109,8 @@ do (cond ((null ast) (return (make-mal-nil nil))) ((not (types:mal-list-p ast)) (return (eval-ast ast env))) - ((zerop (length (mal-value ast))) (return ast)) - (t (let ((forms (mal-value ast))) + ((zerop (length (mal-data-value ast))) (return ast)) + (t (let ((forms (mal-data-value ast))) (cond ((mal-value= (make-mal-symbol '|quote|) (first forms)) (return (second forms))) @@ -130,7 +130,7 @@ (env:set-env env (second forms) (progn - (setf (cdr (assoc 'is-macro (types:mal-attrs value))) t) + (setf (cdr (assoc 'is-macro (types:mal-data-attrs value))) t) value)) (error 'invalid-function :form value @@ -142,7 +142,7 @@ ;; Convert a potential vector to a list (bindings (map 'list #'identity - (mal-value (second forms))))) + (mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env @@ -177,7 +177,7 @@ :parent env :binds (map 'list #'identity - (mal-value arglist)) + (mal-data-value arglist)) :exprs args))) :attrs (list (cons 'params arglist) (cons 'ast body) @@ -189,7 +189,7 @@ (return (mal-eval (second forms) env)) ((or types:mal-exception types:mal-error) (condition) (when (third forms) - (let ((catch-forms (types:mal-value (third forms)))) + (let ((catch-forms (types:mal-data-value (third forms)))) (when (mal-value= (make-mal-symbol '|catch*|) (first catch-forms)) (return (mal-eval (third catch-forms) @@ -206,16 +206,16 @@ (function (car evaluated-list))) ;; If first element is a mal function unwrap it (cond ((types:mal-fn-p function) - (let* ((attrs (types:mal-attrs function))) + (let* ((attrs (types:mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) env (make-instance 'env:mal-environment :parent (cdr (assoc 'env attrs)) :binds (map 'list #'identity - (mal-value (cdr (assoc 'params attrs)))) + (mal-data-value (cdr (assoc 'params attrs)))) :exprs (cdr evaluated-list))))) ((types:mal-builtin-fn-p function) - (return (apply (mal-value function) + (return (apply (mal-data-value function) (cdr evaluated-list)))) (t (error 'invalid-function :form function diff --git a/clisp/types.lisp b/clisp/types.lisp index 04a599ea40..548a2e33ae 100644 --- a/clisp/types.lisp +++ b/clisp/types.lisp @@ -2,10 +2,10 @@ (:use :common-lisp) (:export :mal-value= ;; Accessors - :mal-value - :mal-type - :mal-meta - :mal-attrs + :mal-data-value + :mal-data-type + :mal-data-meta + :mal-data-attrs ;; Mal values :number :boolean @@ -47,24 +47,12 @@ (define-condition mal-user-exception (mal-exception) ((data :accessor mal-exception-data :initarg :data))) -(defstruct mal-type - (type nil) +(defstruct mal-data + (type nil :read-only t) (value nil) meta attrs) -(defun mal-value (mal-data) - (mal-type-value mal-data)) - -(defun mal-type (mal-data) - (mal-type-type mal-data)) - -(defun mal-attrs (mal-data) - (mal-type-attrs mal-data)) - -(defun mal-meta (mal-data) - (mal-type-meta mal-data)) - (defmacro define-mal-type (type) ;; Create a class for given type and a convenience constructor and also export ;; them @@ -76,14 +64,14 @@ (symbol-name type) "-p"))))) `(progn (defun ,constructor (value &key meta attrs) - (make-mal-type :type ',type + (make-mal-data :type ',type :value value :meta meta :attrs attrs)) (defun ,predicate (value) - (when (typep value 'mal-type) - (equal (mal-type value) ',type))) + (when (typep value 'mal-data) + (equal (mal-data-type value) ',type))) (export ',constructor) (export ',predicate)))) @@ -109,7 +97,7 @@ (defvar any) (defmacro switch-mal-type (ast &body forms) - `(let ((type (types:mal-type ,ast))) + `(let ((type (types:mal-data-type ,ast))) (cond ,@(mapcar (lambda (form) (list (if (or (equal (car form) t) @@ -120,12 +108,12 @@ forms)))) (defun mal-symbol= (value1 value2) - (string= (symbol-name (mal-value value1)) - (symbol-name (mal-value value2)))) + (string= (symbol-name (mal-data-value value1)) + (symbol-name (mal-data-value value2)))) (defun mal-sequence= (value1 value2) - (let ((sequence1 (map 'list #'identity (mal-value value1))) - (sequence2 (map 'list #'identity (mal-value value2)))) + (let ((sequence1 (map 'list #'identity (mal-data-value value1))) + (sequence2 (map 'list #'identity (mal-data-value value2)))) (when (= (length sequence1) (length sequence2)) (every #'identity (loop @@ -134,8 +122,8 @@ collect (mal-value= x y)))))) (defun mal-hash-map= (value1 value2) - (let ((map1 (mal-value value1)) - (map2 (mal-value value2))) + (let ((map1 (mal-data-value value1)) + (map2 (mal-data-value value2))) (when (= (hash-table-count map1) (hash-table-count map2)) (every #'identity (loop @@ -144,14 +132,14 @@ (gethash key map2))))))) (defun mal-value= (value1 value2) - (when (and (typep value1 'mal-type) - (typep value2 'mal-type)) - (if (equal (mal-type value1) (mal-type value2)) + (when (and (typep value1 'mal-data) + (typep value2 'mal-data)) + (if (equal (mal-data-type value1) (mal-data-type value2)) (switch-mal-type value1 - (number (= (mal-value value1) (mal-value value2))) - (boolean (equal (mal-value value1) (mal-value value2))) - (nil (equal (mal-value value1) (mal-value value2))) - (string (string= (mal-value value1) (mal-value value2))) + (number (= (mal-data-value value1) (mal-data-value value2))) + (boolean (equal (mal-data-value value1) (mal-data-value value2))) + (nil (equal (mal-data-value value1) (mal-data-value value2))) + (string (string= (mal-data-value value1) (mal-data-value value2))) (symbol (mal-symbol= value1 value2)) (keyword (mal-symbol= value1 value2)) (list (mal-sequence= value1 value2)) @@ -163,7 +151,7 @@ (mal-sequence= value1 value2))))) (defun hash-mal-value (value) - (sxhash (mal-value value))) + (sxhash (mal-data-value value))) #+sbcl (sb-ext:define-hash-table-test mal-value= hash-mal-value) #+clisp (ext:define-hash-table-test mal-value= mal-value= hash-mal-value) @@ -197,16 +185,16 @@ (defun unwrap-value (value) (switch-mal-type value - (list (mapcar #'unwrap-value (mal-value value))) - (vector (map 'vector #'unwrap-value (mal-value value))) + (list (mapcar #'unwrap-value (mal-data-value value))) + (vector (map 'vector #'unwrap-value (mal-data-value value))) (hash-map (let ((hash-table (make-hash-table)) - (hash-map-value (mal-value value))) + (hash-map-value (mal-data-value value))) (loop for key being the hash-keys of hash-map-value - do (setf (gethash (mal-value key) hash-table) - (mal-value (gethash key hash-map-value)))) + do (setf (gethash (mal-data-value key) hash-table) + (mal-data-value (gethash key hash-map-value)))) hash-table)) - (any (mal-value value)))) + (any (mal-data-value value)))) (defun apply-unwrapped-values (op &rest values) (wrap-value (apply op (mapcar #'unwrap-value values)))) From 97c2c420d5015ff2e8eba553bd5ffc554d0fe1d7 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 19:32:11 +0530 Subject: [PATCH 0115/2308] Store symbols as strings --- clisp/core.lisp | 122 ++++++++++++++++++-------------------- clisp/env.lisp | 2 +- clisp/printer.lisp | 4 +- clisp/reader.lisp | 38 ++++-------- clisp/step2_eval.lisp | 8 +-- clisp/step3_env.lisp | 12 ++-- clisp/step4_if_fn_do.lisp | 10 ++-- clisp/step5_tco.lisp | 10 ++-- clisp/step6_file.lisp | 14 ++--- clisp/step7_quote.lisp | 28 ++++----- clisp/step8_macros.lisp | 32 +++++----- clisp/step9_try.lisp | 36 +++++------ clisp/stepA_mal.lisp | 36 +++++------ clisp/types.lisp | 6 +- 14 files changed, 171 insertions(+), 187 deletions(-) diff --git a/clisp/core.lisp b/clisp/core.lisp index b18e59812c..a54fdc4543 100644 --- a/clisp/core.lisp +++ b/clisp/core.lisp @@ -27,23 +27,23 @@ (defvar ns (list - (cons (types:make-mal-symbol '+) + (cons (types:make-mal-symbol "+") (types:make-mal-builtin-fn (lambda (value1 value2) (types:apply-unwrapped-values '+ value1 value2)))) - (cons (types:make-mal-symbol '-) + (cons (types:make-mal-symbol "-") (types:make-mal-builtin-fn (lambda (value1 value2) (types:apply-unwrapped-values '- value1 value2)))) - (cons (types:make-mal-symbol '*) + (cons (types:make-mal-symbol "*") (types:make-mal-builtin-fn (lambda (value1 value2) (types:apply-unwrapped-values '* value1 value2)))) - (cons (types:make-mal-symbol '/) + (cons (types:make-mal-symbol "/") (types:make-mal-builtin-fn ( lambda (value1 value2) (types:apply-unwrapped-values '/ value1 value2)))) - (cons (types:make-mal-symbol '|prn|) + (cons (types:make-mal-symbol "prn") (types:make-mal-builtin-fn (lambda (&rest strings) (write-line (format nil "~{~a~^ ~}" @@ -51,7 +51,7 @@ strings))) (types:make-mal-nil nil)))) - (cons (types:make-mal-symbol '|println|) + (cons (types:make-mal-symbol "println") (types:make-mal-builtin-fn (lambda (&rest strings) (write-line (format nil "~{~a~^ ~}" @@ -59,111 +59,111 @@ strings))) (types:make-mal-nil nil)))) - (cons (types:make-mal-symbol '|pr-str|) + (cons (types:make-mal-symbol "pr-str") (types:make-mal-builtin-fn (lambda (&rest strings) (types:make-mal-string (format nil "~{~a~^ ~}" (mapcar (lambda (string) (printer:pr-str string t)) strings)))))) - (cons (types:make-mal-symbol '|str|) + (cons (types:make-mal-symbol "str") (types:make-mal-builtin-fn (lambda (&rest strings) (types:make-mal-string (format nil "~{~a~}" (mapcar (lambda (string) (printer:pr-str string nil)) strings)))))) - (cons (types:make-mal-symbol '|list|) + (cons (types:make-mal-symbol "list") (types:make-mal-builtin-fn (lambda (&rest values) (make-mal-list values)))) - (cons (types:make-mal-symbol '|list?|) + (cons (types:make-mal-symbol "list?") (types:make-mal-builtin-fn (lambda (value) (types:make-mal-boolean (or (types:mal-nil-p value) (types:mal-list-p value)))))) - (cons (types:make-mal-symbol '|empty?|) + (cons (types:make-mal-symbol "empty?") (types:make-mal-builtin-fn (lambda (value) (types:make-mal-boolean (zerop (length (mal-data-value value))))))) - (cons (types:make-mal-symbol '|count|) + (cons (types:make-mal-symbol "count") (types:make-mal-builtin-fn (lambda (value) (types:apply-unwrapped-values 'length value)))) - (cons (types:make-mal-symbol '=) + (cons (types:make-mal-symbol "=") (types:make-mal-builtin-fn (lambda (value1 value2) (types:make-mal-boolean (types:mal-value= value1 value2))))) - (cons (types:make-mal-symbol '<) + (cons (types:make-mal-symbol "<") (types:make-mal-builtin-fn (lambda (value1 value2) (types:apply-unwrapped-values-prefer-bool '< value1 value2)))) - (cons (types:make-mal-symbol '>) + (cons (types:make-mal-symbol ">") (types:make-mal-builtin-fn (lambda (value1 value2) (types:apply-unwrapped-values-prefer-bool '> value1 value2)))) - (cons (types:make-mal-symbol '<=) + (cons (types:make-mal-symbol "<=") (types:make-mal-builtin-fn (lambda (value1 value2) (types:apply-unwrapped-values-prefer-bool '<= value1 value2)))) - (cons (types:make-mal-symbol '>=) + (cons (types:make-mal-symbol ">=") (types:make-mal-builtin-fn (lambda (value1 value2) (types:apply-unwrapped-values-prefer-bool '>= value1 value2)))) - (cons (types:make-mal-symbol '|read-string|) + (cons (types:make-mal-symbol "read-string") (types:make-mal-builtin-fn (lambda (value) (reader:read-str (types:mal-data-value value))))) - (cons (types:make-mal-symbol '|slurp|) + (cons (types:make-mal-symbol "slurp") (types:make-mal-builtin-fn (lambda (filename) (types:apply-unwrapped-values 'get-file-contents filename)))) - (cons (types:make-mal-symbol '|atom|) + (cons (types:make-mal-symbol "atom") (types:make-mal-builtin-fn (lambda (value) (types:make-mal-atom value)))) - (cons (types:make-mal-symbol '|atom?|) + (cons (types:make-mal-symbol "atom?") (types:make-mal-builtin-fn (lambda (value) (types:make-mal-boolean (types:mal-atom-p value))))) - (cons (types:make-mal-symbol '|deref|) + (cons (types:make-mal-symbol "deref") (types:make-mal-builtin-fn (lambda (atom) (types:mal-data-value atom)))) - (cons (types:make-mal-symbol '|reset!|) + (cons (types:make-mal-symbol "reset!") (types:make-mal-builtin-fn (lambda (atom value) (setf (types:mal-data-value atom) value)))) - (cons (types:make-mal-symbol '|swap!|) + (cons (types:make-mal-symbol "swap!") (types:make-mal-builtin-fn (lambda (atom fn &rest args) (setf (types:mal-data-value atom) (apply (mal-data-value fn) (append (list (types:mal-data-value atom)) args)))))) - (cons (types:make-mal-symbol '|cons|) + (cons (types:make-mal-symbol "cons") (types:make-mal-builtin-fn (lambda (element list) (types:make-mal-list (cons element (map 'list #'identity (mal-data-value list))))))) - (cons (types:make-mal-symbol '|concat|) + (cons (types:make-mal-symbol "concat") (types:make-mal-builtin-fn (lambda (&rest lists) (types:make-mal-list (apply #'concatenate 'list (mapcar #'types:mal-data-value lists)))))) - (cons (types:make-mal-symbol '|nth|) + (cons (types:make-mal-symbol "nth") (types:make-mal-builtin-fn (lambda (sequence index) (or (nth (mal-data-value index) (map 'list #'identity (mal-data-value sequence))) @@ -172,23 +172,23 @@ :index (mal-data-value index) :sequence sequence))))) - (cons (types:make-mal-symbol '|first|) + (cons (types:make-mal-symbol "first") (types:make-mal-builtin-fn (lambda (sequence) (or (first (map 'list #'identity (mal-data-value sequence))) (types:make-mal-nil nil))))) - (cons (types:make-mal-symbol '|rest|) + (cons (types:make-mal-symbol "rest") (types:make-mal-builtin-fn (lambda (sequence) (types:make-mal-list (rest (map 'list #'identity (mal-data-value sequence))))))) - (cons (types:make-mal-symbol '|throw|) + (cons (types:make-mal-symbol "throw") (types:make-mal-builtin-fn (lambda (value) (error 'types:mal-user-exception :data value)))) - (cons (types:make-mal-symbol '|apply|) + (cons (types:make-mal-symbol "apply") (types:make-mal-builtin-fn (lambda (fn &rest values) (let ((final-arg (map 'list #'identity @@ -197,7 +197,7 @@ (apply (types:mal-data-value fn) (append butlast-args final-arg)))))) - (cons (types:make-mal-symbol '|map|) + (cons (types:make-mal-symbol "map") (types:make-mal-builtin-fn (lambda (fn sequence) (let ((applicants (map 'list #'identity @@ -205,51 +205,47 @@ (types:make-mal-list (mapcar (types:mal-data-value fn) applicants)))))) - (cons (types:make-mal-symbol '|nil?|) + (cons (types:make-mal-symbol "nil?") (types:make-mal-builtin-fn (lambda (value) (types:make-mal-boolean (types:mal-nil-p value))))) - (cons (types:make-mal-symbol '|true?|) + (cons (types:make-mal-symbol "true?") (types:make-mal-builtin-fn (lambda (value) (types:make-mal-boolean (and (types:mal-boolean-p value) (types:mal-data-value value)))))) - (cons (types:make-mal-symbol '|false?|) + (cons (types:make-mal-symbol "false?") (types:make-mal-builtin-fn (lambda (value) (types:make-mal-boolean (and (types:mal-boolean-p value) (not (types:mal-data-value value))))))) - (cons (types:make-mal-symbol '|symbol?|) + (cons (types:make-mal-symbol "symbol?") (types:make-mal-builtin-fn (lambda (value) (types:make-mal-boolean (types:mal-symbol-p value))))) - (cons (types:make-mal-symbol '|symbol|) + (cons (types:make-mal-symbol "symbol") (types:make-mal-builtin-fn (lambda (string) - (types:make-mal-symbol (reader::read-from-string-preserving-case - (types:mal-data-value string)))))) + (types:make-mal-symbol (types:mal-data-value string))))) - (cons (types:make-mal-symbol '|keyword|) + (cons (types:make-mal-symbol "keyword") (types:make-mal-builtin-fn (lambda (keyword) (if (types:mal-keyword-p keyword) keyword - (types:make-mal-keyword (reader::read-from-string-preserving-case - (format nil - ":~a" - (types:mal-data-value keyword)))))))) + (types:make-mal-keyword (format nil ":~a" (types:mal-data-value keyword))))))) - (cons (types:make-mal-symbol '|keyword?|) + (cons (types:make-mal-symbol "keyword?") (types:make-mal-builtin-fn (lambda (value) (types:make-mal-boolean (types:mal-keyword-p value))))) - (cons (types:make-mal-symbol '|vector|) + (cons (types:make-mal-symbol "vector") (types:make-mal-builtin-fn (lambda (&rest elements) (types:make-mal-vector (map 'vector #'identity elements))))) - (cons (types:make-mal-symbol '|vector?|) + (cons (types:make-mal-symbol "vector?") (types:make-mal-builtin-fn (lambda (value) (types:make-mal-boolean (types:mal-vector-p value))))) - (cons (types:make-mal-symbol '|hash-map|) + (cons (types:make-mal-symbol "hash-map") (types:make-mal-builtin-fn (lambda (&rest elements) (let ((hash-map (make-hash-table :test 'types:mal-value=))) (loop @@ -258,11 +254,11 @@ do (setf (gethash key hash-map) value)) (types:make-mal-hash-map hash-map))))) - (cons (types:make-mal-symbol '|map?|) + (cons (types:make-mal-symbol "map?") (types:make-mal-builtin-fn (lambda (value) (types:make-mal-boolean (types:mal-hash-map-p value))))) - (cons (types:make-mal-symbol '|assoc|) + (cons (types:make-mal-symbol "assoc") (types:make-mal-builtin-fn (lambda (hash-map &rest elements) (let ((hash-map-value (types:mal-data-value hash-map)) (new-hash-map (make-hash-table :test 'types:mal-value=))) @@ -279,7 +275,7 @@ (types:make-mal-hash-map new-hash-map))))) - (cons (types:make-mal-symbol '|dissoc|) + (cons (types:make-mal-symbol "dissoc") (types:make-mal-builtin-fn (lambda (hash-map &rest elements) (let ((hash-map-value (types:mal-data-value hash-map)) (new-hash-map (make-hash-table :test 'types:mal-value=))) @@ -292,57 +288,57 @@ (types:make-mal-hash-map new-hash-map))))) - (cons (types:make-mal-symbol '|get|) + (cons (types:make-mal-symbol "get") (types:make-mal-builtin-fn (lambda (hash-map key) (or (and (types:mal-hash-map-p hash-map) (gethash key (types:mal-data-value hash-map))) (types:make-mal-nil nil))))) - (cons (types:make-mal-symbol '|contains?|) + (cons (types:make-mal-symbol "contains?") (types:make-mal-builtin-fn (lambda (hash-map key) (if (gethash key (types:mal-data-value hash-map)) (types:make-mal-boolean t) (types:make-mal-boolean nil))))) - (cons (types:make-mal-symbol '|keys|) + (cons (types:make-mal-symbol "keys") (types:make-mal-builtin-fn (lambda (hash-map) (let ((hash-map-value (types:mal-data-value hash-map))) (types:make-mal-list (loop for key being the hash-keys of hash-map-value collect key)))))) - (cons (types:make-mal-symbol '|vals|) + (cons (types:make-mal-symbol "vals") (types:make-mal-builtin-fn (lambda (hash-map) (let ((hash-map-value (types:mal-data-value hash-map))) (types:make-mal-list (loop for key being the hash-keys of hash-map-value collect (gethash key hash-map-value))))))) - (cons (types:make-mal-symbol '|sequential?|) + (cons (types:make-mal-symbol "sequential?") (types:make-mal-builtin-fn (lambda (value) (if (or (types:mal-vector-p value) (types:mal-list-p value)) (types:make-mal-boolean t) (types:make-mal-boolean nil))))) - (cons (types:make-mal-symbol '|readline|) + (cons (types:make-mal-symbol "readline") (types:make-mal-builtin-fn (lambda (prompt) (format *standard-output* (types:mal-data-value prompt)) (force-output *standard-output*) (types:wrap-value (read-line *standard-input* nil))))) - (cons (types:make-mal-symbol '|string?|) + (cons (types:make-mal-symbol "string?") (types:make-mal-builtin-fn (lambda (value) (types:make-mal-boolean (types:mal-string-p value))))) - (cons (types:make-mal-symbol '|time-ms|) + (cons (types:make-mal-symbol "time-ms") (types:make-mal-builtin-fn (lambda () (types:make-mal-number (floor (/ (get-internal-real-time) (/ internal-time-units-per-second 1000))))))) - (cons (types:make-mal-symbol '|conj|) + (cons (types:make-mal-symbol "conj") (types:make-mal-builtin-fn (lambda (value &rest elements) (cond ((types:mal-list-p value) (types:make-mal-list (append (nreverse elements) @@ -352,7 +348,7 @@ (types:mal-data-value value) elements))) (t (error 'types:mal-user-exception)))))) - (cons (types:make-mal-symbol '|seq|) + (cons (types:make-mal-symbol "seq") (types:make-mal-builtin-fn (lambda (value) (if (zerop (length (types:mal-data-value value))) (types:make-mal-nil nil) @@ -369,7 +365,7 @@ (types:mal-data-value value)))) (t (error 'types:mal-user-exception))))))) - (cons (types:make-mal-symbol '|with-meta|) + (cons (types:make-mal-symbol "with-meta") (types:make-mal-builtin-fn (lambda (value meta) (funcall (switch-mal-type value (types:string #'types:make-mal-string) @@ -383,7 +379,7 @@ :meta meta :attrs (types:mal-data-attrs value))))) - (cons (types:make-mal-symbol '|meta|) + (cons (types:make-mal-symbol "meta") (types:make-mal-builtin-fn (lambda (value) (or (types:mal-data-meta value) (types:make-mal-nil nil))))))) diff --git a/clisp/env.lisp b/clisp/env.lisp index 7b1a185805..faa63cef75 100644 --- a/clisp/env.lisp +++ b/clisp/env.lisp @@ -65,7 +65,7 @@ (parent nil) (binds nil) (exprs nil)) - (let ((varidiac-position (position (types:make-mal-symbol '&) + (let ((varidiac-position (position (types:make-mal-symbol "&") binds :test #'mal-value=))) (when varidiac-position diff --git a/clisp/printer.lisp b/clisp/printer.lisp index c6038fd192..b51defbd89 100644 --- a/clisp/printer.lisp +++ b/clisp/printer.lisp @@ -49,8 +49,8 @@ (types:boolean (if (types:mal-data-value ast) "true" "false")) (types:nil "nil") (types:string (pr-string ast print-readably)) - (types:symbol (format nil "~a" (types:mal-data-value ast))) - (types:keyword (format nil ":~a" (types:mal-data-value ast))) + (types:symbol (types:mal-data-value ast)) + (types:keyword (types:mal-data-value ast)) (types:list (pr-mal-sequence "(" ast ")" print-readably)) (types:vector (pr-mal-sequence "[" ast "]" print-readably)) (types:hash-map (pr-mal-hash-map ast print-readably)) diff --git a/clisp/reader.lisp b/clisp/reader.lisp index 1a35b6547c..5dfe412a13 100644 --- a/clisp/reader.lisp +++ b/clisp/reader.lisp @@ -49,10 +49,6 @@ (let ((*tokenizer-re* re)) (tokenize string))) -(defvar *whitespace-chars* - '(#\Space #\Newline #\Backspace #\Tab - #\Linefeed #\Page #\Return #\Rubout #\,)) - (defun tokenize (string) (let (tokens) (do* ((start 0) @@ -64,7 +60,7 @@ (regexp:regexp-exec *tokenizer-re* string :start start)))) (when match (setf start (regexp:match-end match)) - (let ((token (regexp:match-string string match))) + (let ((token (string-trim "," (regexp:match-string string match)))) (unless (or (zerop (length token)) (char= (char token 0) #\;)) (push token tokens))))) @@ -83,11 +79,6 @@ (pop (token-reader-tokens reader)) reader) -(defun read-from-string-preserving-case (string) - (let ((*readtable* (copy-readtable nil))) - (setf (readtable-case *readtable*) :preserve) - (read-from-string string))) - (defun read-str (string) (read-form (make-token-reader :tokens (tokenize string)))) @@ -102,8 +93,8 @@ "]" 'vector))) ((string= token "{") (make-mal-hash-map (read-hash-map reader))) - ((member token '("'" "`" "~" "~@" "@") :test #'string= ) (expand-quote reader)) ((string= token "^") (read-form-with-meta reader)) + ((member token '("'" "`" "~" "~@" "@") :test #'string= ) (expand-quote reader)) (t (read-atom reader))))) (defun read-form-with-meta (reader) @@ -116,16 +107,16 @@ (error 'eof :context "object metadata")) - (make-mal-list (list (make-mal-symbol '|with-meta|) value meta)))) + (make-mal-list (list (make-mal-symbol "with-meta") value meta)))) (defun expand-quote (reader) (let ((quote (next reader))) (make-mal-list (list (make-mal-symbol (cond - ((string= quote "'") '|quote|) - ((string= quote "`") '|quasiquote|) - ((string= quote "~") '|unquote|) - ((string= quote "~@") '|splice-unquote|) - ((string= quote "@") '|deref|))) + ((string= quote "'") "quote") + ((string= quote "`") "quasiquote") + ((string= quote "~") "unquote") + ((string= quote "~@") "splice-unquote") + ((string= quote "@") "deref"))) (read-form reader))))) (defun read-mal-sequence (reader &optional (delimiter ")") (constructor 'list)) @@ -148,8 +139,7 @@ (defun read-hash-map (reader) ;; Consume the open brace (consume reader) - (let (forms - (hash-map (make-hash-table :test 'types:mal-value=))) + (let (forms) (loop for token = (peek reader) while (cond @@ -164,10 +154,8 @@ (push (cons key value) forms)))))) ;; Consume the closing brace (consume reader) - ;; Construct the hash table - (dolist (key-value (nreverse forms)) - (setf (gethash (car key-value) hash-map) (cdr key-value))) - hash-map)) + (make-hash-table :test 'types:mal-value= + :initial-contents (nreverse forms)))) (defun read-atom (reader) (let ((token (next reader))) @@ -181,7 +169,7 @@ ((char= (char token 0) #\") (make-mal-string (parse-string token))) ((char= (char token 0) #\:) - (make-mal-keyword (read-from-string-preserving-case token))) + (make-mal-keyword token)) ((regexp:regexp-exec *digit-re* token) (make-mal-number (read-from-string token))) - (t (make-mal-symbol (read-from-string-preserving-case token)))))) + (t (make-mal-symbol token))))) diff --git a/clisp/step2_eval.lisp b/clisp/step2_eval.lisp index ae0f1e4be7..4b75a67583 100644 --- a/clisp/step2_eval.lisp +++ b/clisp/step2_eval.lisp @@ -12,25 +12,25 @@ (defvar *repl-env* (make-hash-table :test 'types:mal-value=)) -(setf (gethash (types:make-mal-symbol '+) *repl-env*) +(setf (gethash (types:make-mal-symbol "+") *repl-env*) (types:make-mal-builtin-fn (lambda (value1 value2) (apply-unwrapped-values '+ value1 value2)))) -(setf (gethash (types:make-mal-symbol '-) *repl-env*) +(setf (gethash (types:make-mal-symbol "-") *repl-env*) (types:make-mal-builtin-fn (lambda (value1 value2) (apply-unwrapped-values '- value1 value2)))) -(setf (gethash (types:make-mal-symbol '*) *repl-env*) +(setf (gethash (types:make-mal-symbol "*") *repl-env*) (types:make-mal-builtin-fn (lambda (value1 value2) (apply-unwrapped-values '* value1 value2)))) -(setf (gethash (types:make-mal-symbol '/) *repl-env*) +(setf (gethash (types:make-mal-symbol "/") *repl-env*) (types:make-mal-builtin-fn (lambda (value1 value2) (apply-unwrapped-values '/ value1 diff --git a/clisp/step3_env.lisp b/clisp/step3_env.lisp index 550a7ae3b7..ce97c632f4 100644 --- a/clisp/step3_env.lisp +++ b/clisp/step3_env.lisp @@ -11,22 +11,22 @@ (defvar *repl-env* (make-instance 'env:mal-environment)) (set-env *repl-env* - (types:make-mal-symbol '+) + (types:make-mal-symbol "+") (types:make-mal-builtin-fn (lambda (value1 value2) (apply-unwrapped-values '+ value1 value2)))) (set-env *repl-env* - (types:make-mal-symbol '-) + (types:make-mal-symbol "-") (types:make-mal-builtin-fn (lambda (value1 value2) (apply-unwrapped-values '- value1 value2)))) (set-env *repl-env* - (types:make-mal-symbol '*) + (types:make-mal-symbol "*") (types:make-mal-builtin-fn (lambda (value1 value2) (apply-unwrapped-values '* value1 value2)))) (set-env *repl-env* - (types:make-mal-symbol '/) + (types:make-mal-symbol "/") (types:make-mal-builtin-fn (lambda (value1 value2) (apply-unwrapped-values '/ value1 value2)))) @@ -76,9 +76,9 @@ (defun eval-list (ast env) (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol '|def!|) (first forms)) + ((mal-value= (make-mal-symbol "def!") (first forms)) (env:set-env env (second forms) (mal-eval (third forms) env))) - ((mal-value= (make-mal-symbol '|let*|) (first forms)) + ((mal-value= (make-mal-symbol "let*") (first forms)) (eval-let* forms env)) (t (let ((evaluated-list (eval-ast ast env))) (apply (types:mal-data-value (car evaluated-list)) diff --git a/clisp/step4_if_fn_do.lisp b/clisp/step4_if_fn_do.lisp index c23b8e698a..f7963a37e8 100644 --- a/clisp/step4_if_fn_do.lisp +++ b/clisp/step4_if_fn_do.lisp @@ -67,21 +67,21 @@ (defun eval-list (ast env) (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol '|def!|) (first forms)) + ((mal-value= (make-mal-symbol "def!") (first forms)) (env:set-env env (second forms) (mal-eval (third forms) env))) - ((mal-value= (make-mal-symbol '|let*|) (first forms)) + ((mal-value= (make-mal-symbol "let*") (first forms)) (eval-let* forms env)) - ((mal-value= (make-mal-symbol '|do|) (first forms)) + ((mal-value= (make-mal-symbol "do") (first forms)) (car (last (mapcar (lambda (form) (mal-eval form env)) (cdr forms))))) - ((mal-value= (make-mal-symbol '|if|) (first forms)) + ((mal-value= (make-mal-symbol "if") (first forms)) (let ((predicate (mal-eval (second forms) env))) (mal-eval (if (or (mal-value= predicate (types:make-mal-nil nil)) (mal-value= predicate (types:make-mal-boolean nil))) (fourth forms) (third forms)) env))) - ((mal-value= (make-mal-symbol '|fn*|) (first forms)) + ((mal-value= (make-mal-symbol "fn*") (first forms)) (types:make-mal-fn (let ((arglist (second forms)) (body (third forms))) (lambda (&rest args) diff --git a/clisp/step5_tco.lisp b/clisp/step5_tco.lisp index ea64973369..939f576596 100644 --- a/clisp/step5_tco.lisp +++ b/clisp/step5_tco.lisp @@ -54,10 +54,10 @@ ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol '|def!|) (first forms)) + ((mal-value= (make-mal-symbol "def!") (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - ((mal-value= (make-mal-symbol '|let*|) (first forms)) + ((mal-value= (make-mal-symbol "let*") (first forms)) (let ((new-env (make-instance 'env:mal-environment :parent env)) ;; Convert a potential vector to a list @@ -78,19 +78,19 @@ (setf ast (third forms) env new-env))) - ((mal-value= (make-mal-symbol '|do|) (first forms)) + ((mal-value= (make-mal-symbol "do") (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) - ((mal-value= (make-mal-symbol '|if|) (first forms)) + ((mal-value= (make-mal-symbol "if") (first forms)) (let ((predicate (mal-eval (second forms) env))) (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) (mal-value= predicate (types:make-mal-boolean nil))) (fourth forms) (third forms))))) - ((mal-value= (make-mal-symbol '|fn*|) (first forms)) + ((mal-value= (make-mal-symbol "fn*") (first forms)) (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) diff --git a/clisp/step6_file.lisp b/clisp/step6_file.lisp index 51e07835cd..82128cc982 100644 --- a/clisp/step6_file.lisp +++ b/clisp/step6_file.lisp @@ -22,7 +22,7 @@ (cdr binding))) (env:set-env *repl-env* - (types:make-mal-symbol '|eval|) + (types:make-mal-symbol "eval") (types:make-mal-builtin-fn (lambda (ast) (mal-eval ast *repl-env*)))) @@ -59,10 +59,10 @@ ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol '|def!|) (first forms)) + ((mal-value= (make-mal-symbol "def!") (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - ((mal-value= (make-mal-symbol '|let*|) (first forms)) + ((mal-value= (make-mal-symbol "let*") (first forms)) (let ((new-env (make-instance 'env:mal-environment :parent env)) ;; Convert a potential vector to a list @@ -83,19 +83,19 @@ (setf ast (third forms) env new-env))) - ((mal-value= (make-mal-symbol '|do|) (first forms)) + ((mal-value= (make-mal-symbol "do") (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) - ((mal-value= (make-mal-symbol '|if|) (first forms)) + ((mal-value= (make-mal-symbol "if") (first forms)) (let ((predicate (mal-eval (second forms) env))) (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) (mal-value= predicate (types:make-mal-boolean nil))) (fourth forms) (third forms))))) - ((mal-value= (make-mal-symbol '|fn*|) (first forms)) + ((mal-value= (make-mal-symbol "fn*") (first forms)) (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) @@ -162,7 +162,7 @@ (if line (writeline (rep line)) (return))))) (env:set-env *repl-env* - (types:make-mal-symbol '|*ARGV*|) + (types:make-mal-symbol "*ARGV*") (types:wrap-value (cdr common-lisp-user::*args*) :listp t)) diff --git a/clisp/step7_quote.lisp b/clisp/step7_quote.lisp index d97bf4d16f..dc27ab39c0 100644 --- a/clisp/step7_quote.lisp +++ b/clisp/step7_quote.lisp @@ -22,7 +22,7 @@ (cdr binding))) (env:set-env *repl-env* - (types:make-mal-symbol '|eval|) + (types:make-mal-symbol "eval") (types:make-mal-builtin-fn (lambda (ast) (mal-eval ast *repl-env*)))) @@ -55,21 +55,21 @@ (defun quasiquote (ast) (if (not (is-pair ast)) - (types:make-mal-list (list (types:make-mal-symbol '|quote|) + (types:make-mal-list (list (types:make-mal-symbol "quote") ast)) (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond - ((mal-value= (make-mal-symbol '|unquote|) (first forms)) + ((mal-value= (make-mal-symbol "unquote") (first forms)) (second forms)) ((and (is-pair (first forms)) - (mal-value= (make-mal-symbol '|splice-unquote|) + (mal-value= (make-mal-symbol "splice-unquote") (first (mal-data-value (first forms))))) - (types:make-mal-list (list (types:make-mal-symbol '|concat|) + (types:make-mal-list (list (types:make-mal-symbol "concat") (second (mal-data-value (first forms))) (quasiquote (make-mal-list (cdr forms)))))) - (t (types:make-mal-list (list (types:make-mal-symbol '|cons|) + (t (types:make-mal-list (list (types:make-mal-symbol "cons") (quasiquote (first forms)) (quasiquote (make-mal-list (cdr forms)))))))))) @@ -84,16 +84,16 @@ ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol '|quote|) (first forms)) + ((mal-value= (make-mal-symbol "quote") (first forms)) (return (second forms))) - ((mal-value= (make-mal-symbol '|quasiquote|) (first forms)) + ((mal-value= (make-mal-symbol "quasiquote") (first forms)) (setf ast (quasiquote (second forms)))) - ((mal-value= (make-mal-symbol '|def!|) (first forms)) + ((mal-value= (make-mal-symbol "def!") (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - ((mal-value= (make-mal-symbol '|let*|) (first forms)) + ((mal-value= (make-mal-symbol "let*") (first forms)) (let ((new-env (make-instance 'env:mal-environment :parent env)) ;; Convert a potential vector to a list @@ -114,19 +114,19 @@ (setf ast (third forms) env new-env))) - ((mal-value= (make-mal-symbol '|do|) (first forms)) + ((mal-value= (make-mal-symbol "do") (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) - ((mal-value= (make-mal-symbol '|if|) (first forms)) + ((mal-value= (make-mal-symbol "if") (first forms)) (let ((predicate (mal-eval (second forms) env))) (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) (mal-value= predicate (types:make-mal-boolean nil))) (fourth forms) (third forms))))) - ((mal-value= (make-mal-symbol '|fn*|) (first forms)) + ((mal-value= (make-mal-symbol "fn*") (first forms)) (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) @@ -193,7 +193,7 @@ (if line (writeline (rep line)) (return))))) (env:set-env *repl-env* - (types:make-mal-symbol '|*ARGV*|) + (types:make-mal-symbol "*ARGV*") (types:wrap-value (cdr common-lisp-user::*args*) :listp t)) diff --git a/clisp/step8_macros.lisp b/clisp/step8_macros.lisp index a4e490a881..bc3250f039 100644 --- a/clisp/step8_macros.lisp +++ b/clisp/step8_macros.lisp @@ -33,7 +33,7 @@ (cdr binding))) (env:set-env *repl-env* - (types:make-mal-symbol '|eval|) + (types:make-mal-symbol "eval") (types:make-mal-builtin-fn (lambda (ast) (mal-eval ast *repl-env*)))) @@ -66,21 +66,21 @@ (defun quasiquote (ast) (if (not (is-pair ast)) - (types:make-mal-list (list (types:make-mal-symbol '|quote|) + (types:make-mal-list (list (types:make-mal-symbol "quote") ast)) (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond - ((mal-value= (make-mal-symbol '|unquote|) (first forms)) + ((mal-value= (make-mal-symbol "unquote") (first forms)) (second forms)) ((and (is-pair (first forms)) - (mal-value= (make-mal-symbol '|splice-unquote|) + (mal-value= (make-mal-symbol "splice-unquote") (first (mal-data-value (first forms))))) - (types:make-mal-list (list (types:make-mal-symbol '|concat|) + (types:make-mal-list (list (types:make-mal-symbol "concat") (second (mal-data-value (first forms))) (quasiquote (make-mal-list (cdr forms)))))) - (t (types:make-mal-list (list (types:make-mal-symbol '|cons|) + (t (types:make-mal-list (list (types:make-mal-symbol "cons") (quasiquote (first forms)) (quasiquote (make-mal-list (cdr forms)))))))))) @@ -112,19 +112,19 @@ ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol '|quote|) (first forms)) + ((mal-value= (make-mal-symbol "quote") (first forms)) (return (second forms))) - ((mal-value= (make-mal-symbol '|quasiquote|) (first forms)) + ((mal-value= (make-mal-symbol "quasiquote") (first forms)) (setf ast (quasiquote (second forms)))) - ((mal-value= (make-mal-symbol '|macroexpand|) (first forms)) + ((mal-value= (make-mal-symbol "macroexpand") (first forms)) (return (mal-macroexpand (second forms) env))) - ((mal-value= (make-mal-symbol '|def!|) (first forms)) + ((mal-value= (make-mal-symbol "def!") (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - ((mal-value= (make-mal-symbol '|defmacro!|) (first forms)) + ((mal-value= (make-mal-symbol "defmacro!") (first forms)) (let ((value (mal-eval (third forms) env))) (return (if (types:mal-fn-p value) (env:set-env env @@ -136,7 +136,7 @@ :form value :context "macro"))))) - ((mal-value= (make-mal-symbol '|let*|) (first forms)) + ((mal-value= (make-mal-symbol "let*") (first forms)) (let ((new-env (make-instance 'env:mal-environment :parent env)) ;; Convert a potential vector to a list @@ -157,19 +157,19 @@ (setf ast (third forms) env new-env))) - ((mal-value= (make-mal-symbol '|do|) (first forms)) + ((mal-value= (make-mal-symbol "do") (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) - ((mal-value= (make-mal-symbol '|if|) (first forms)) + ((mal-value= (make-mal-symbol "if") (first forms)) (let ((predicate (mal-eval (second forms) env))) (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) (mal-value= predicate (types:make-mal-boolean nil))) (fourth forms) (third forms))))) - ((mal-value= (make-mal-symbol '|fn*|) (first forms)) + ((mal-value= (make-mal-symbol "fn*") (first forms)) (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) @@ -242,7 +242,7 @@ (if line (writeline (rep line)) (return))))) (env:set-env *repl-env* - (types:make-mal-symbol '|*ARGV*|) + (types:make-mal-symbol "*ARGV*") (types:wrap-value (cdr common-lisp-user::*args*) :listp t)) diff --git a/clisp/step9_try.lisp b/clisp/step9_try.lisp index 0f5a360e83..858b0bb63c 100644 --- a/clisp/step9_try.lisp +++ b/clisp/step9_try.lisp @@ -33,7 +33,7 @@ (cdr binding))) (env:set-env *repl-env* - (types:make-mal-symbol '|eval|) + (types:make-mal-symbol "eval") (types:make-mal-builtin-fn (lambda (ast) (mal-eval ast *repl-env*)))) @@ -66,21 +66,21 @@ (defun quasiquote (ast) (if (not (is-pair ast)) - (types:make-mal-list (list (types:make-mal-symbol '|quote|) + (types:make-mal-list (list (types:make-mal-symbol "quote") ast)) (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond - ((mal-value= (make-mal-symbol '|unquote|) (first forms)) + ((mal-value= (make-mal-symbol "unquote") (first forms)) (second forms)) ((and (is-pair (first forms)) - (mal-value= (make-mal-symbol '|splice-unquote|) + (mal-value= (make-mal-symbol "splice-unquote") (first (mal-data-value (first forms))))) - (types:make-mal-list (list (types:make-mal-symbol '|concat|) + (types:make-mal-list (list (types:make-mal-symbol "concat") (second (mal-data-value (first forms))) (quasiquote (make-mal-list (cdr forms)))))) - (t (types:make-mal-list (list (types:make-mal-symbol '|cons|) + (t (types:make-mal-list (list (types:make-mal-symbol "cons") (quasiquote (first forms)) (quasiquote (make-mal-list (cdr forms)))))))))) @@ -112,19 +112,19 @@ ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol '|quote|) (first forms)) + ((mal-value= (make-mal-symbol "quote") (first forms)) (return (second forms))) - ((mal-value= (make-mal-symbol '|quasiquote|) (first forms)) + ((mal-value= (make-mal-symbol "quasiquote") (first forms)) (setf ast (quasiquote (second forms)))) - ((mal-value= (make-mal-symbol '|macroexpand|) (first forms)) + ((mal-value= (make-mal-symbol "macroexpand") (first forms)) (return (mal-macroexpand (second forms) env))) - ((mal-value= (make-mal-symbol '|def!|) (first forms)) + ((mal-value= (make-mal-symbol "def!") (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - ((mal-value= (make-mal-symbol '|defmacro!|) (first forms)) + ((mal-value= (make-mal-symbol "defmacro!") (first forms)) (let ((value (mal-eval (third forms) env))) (return (if (types:mal-fn-p value) (env:set-env env @@ -136,7 +136,7 @@ :form value :context "macro"))))) - ((mal-value= (make-mal-symbol '|let*|) (first forms)) + ((mal-value= (make-mal-symbol "let*") (first forms)) (let ((new-env (make-instance 'env:mal-environment :parent env)) ;; Convert a potential vector to a list @@ -157,19 +157,19 @@ (setf ast (third forms) env new-env))) - ((mal-value= (make-mal-symbol '|do|) (first forms)) + ((mal-value= (make-mal-symbol "do") (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) - ((mal-value= (make-mal-symbol '|if|) (first forms)) + ((mal-value= (make-mal-symbol "if") (first forms)) (let ((predicate (mal-eval (second forms) env))) (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) (mal-value= predicate (types:make-mal-boolean nil))) (fourth forms) (third forms))))) - ((mal-value= (make-mal-symbol '|fn*|) (first forms)) + ((mal-value= (make-mal-symbol "fn*") (first forms)) (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) @@ -184,13 +184,13 @@ (cons 'env env) (cons 'is-macro nil)))))) - ((mal-value= (make-mal-symbol '|try*|) (first forms)) + ((mal-value= (make-mal-symbol "try*") (first forms)) (handler-case (return (mal-eval (second forms) env)) (types:mal-exception (condition) (when (third forms) (let ((catch-forms (types:mal-data-value (third forms)))) - (when (mal-value= (make-mal-symbol '|catch*|) + (when (mal-value= (make-mal-symbol "catch*") (first catch-forms)) (return (mal-eval (third catch-forms) (make-instance 'env:mal-environment @@ -267,7 +267,7 @@ (if line (writeline (rep line)) (return))))) (env:set-env *repl-env* - (types:make-mal-symbol '|*ARGV*|) + (types:make-mal-symbol "*ARGV*") (types:wrap-value (cdr common-lisp-user::*args*) :listp t)) diff --git a/clisp/stepA_mal.lisp b/clisp/stepA_mal.lisp index ba2cbc20e8..73812b5a8f 100644 --- a/clisp/stepA_mal.lisp +++ b/clisp/stepA_mal.lisp @@ -33,7 +33,7 @@ (cdr binding))) (env:set-env *repl-env* - (types:make-mal-symbol '|eval|) + (types:make-mal-symbol "eval") (types:make-mal-builtin-fn (lambda (ast) (mal-eval ast *repl-env*)))) @@ -66,21 +66,21 @@ (defun quasiquote (ast) (if (not (is-pair ast)) - (types:make-mal-list (list (types:make-mal-symbol '|quote|) + (types:make-mal-list (list (types:make-mal-symbol "quote") ast)) (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond - ((mal-value= (make-mal-symbol '|unquote|) (first forms)) + ((mal-value= (make-mal-symbol "unquote") (first forms)) (second forms)) ((and (is-pair (first forms)) - (mal-value= (make-mal-symbol '|splice-unquote|) + (mal-value= (make-mal-symbol "splice-unquote") (first (mal-data-value (first forms))))) - (types:make-mal-list (list (types:make-mal-symbol '|concat|) + (types:make-mal-list (list (types:make-mal-symbol "concat") (second (mal-data-value (first forms))) (quasiquote (make-mal-list (cdr forms)))))) - (t (types:make-mal-list (list (types:make-mal-symbol '|cons|) + (t (types:make-mal-list (list (types:make-mal-symbol "cons") (quasiquote (first forms)) (quasiquote (make-mal-list (cdr forms)))))))))) @@ -112,19 +112,19 @@ ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol '|quote|) (first forms)) + ((mal-value= (make-mal-symbol "quote") (first forms)) (return (second forms))) - ((mal-value= (make-mal-symbol '|quasiquote|) (first forms)) + ((mal-value= (make-mal-symbol "quasiquote") (first forms)) (setf ast (quasiquote (second forms)))) - ((mal-value= (make-mal-symbol '|macroexpand|) (first forms)) + ((mal-value= (make-mal-symbol "macroexpand") (first forms)) (return (mal-macroexpand (second forms) env))) - ((mal-value= (make-mal-symbol '|def!|) (first forms)) + ((mal-value= (make-mal-symbol "def!") (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - ((mal-value= (make-mal-symbol '|defmacro!|) (first forms)) + ((mal-value= (make-mal-symbol "defmacro!") (first forms)) (let ((value (mal-eval (third forms) env))) (return (if (types:mal-fn-p value) (env:set-env env @@ -136,7 +136,7 @@ :form value :context "macro"))))) - ((mal-value= (make-mal-symbol '|let*|) (first forms)) + ((mal-value= (make-mal-symbol "let*") (first forms)) (let ((new-env (make-instance 'env:mal-environment :parent env)) ;; Convert a potential vector to a list @@ -157,19 +157,19 @@ (setf ast (third forms) env new-env))) - ((mal-value= (make-mal-symbol '|do|) (first forms)) + ((mal-value= (make-mal-symbol "do") (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) - ((mal-value= (make-mal-symbol '|if|) (first forms)) + ((mal-value= (make-mal-symbol "if") (first forms)) (let ((predicate (mal-eval (second forms) env))) (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) (mal-value= predicate (types:make-mal-boolean nil))) (fourth forms) (third forms))))) - ((mal-value= (make-mal-symbol '|fn*|) (first forms)) + ((mal-value= (make-mal-symbol "fn*") (first forms)) (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) @@ -184,13 +184,13 @@ (cons 'env env) (cons 'is-macro nil)))))) - ((mal-value= (make-mal-symbol '|try*|) (first forms)) + ((mal-value= (make-mal-symbol "try*") (first forms)) (handler-case (return (mal-eval (second forms) env)) ((or types:mal-exception types:mal-error) (condition) (when (third forms) (let ((catch-forms (types:mal-data-value (third forms)))) - (when (mal-value= (make-mal-symbol '|catch*|) + (when (mal-value= (make-mal-symbol "catch*") (first catch-forms)) (return (mal-eval (third catch-forms) (make-instance 'env:mal-environment @@ -271,7 +271,7 @@ (if line (writeline (rep line)) (return))))) (env:set-env *repl-env* - (types:make-mal-symbol '|*ARGV*|) + (types:make-mal-symbol "*ARGV*") (types:wrap-value (cdr common-lisp-user::*args*) :listp t)) diff --git a/clisp/types.lisp b/clisp/types.lisp index 548a2e33ae..0e25e26311 100644 --- a/clisp/types.lisp +++ b/clisp/types.lisp @@ -108,8 +108,8 @@ forms)))) (defun mal-symbol= (value1 value2) - (string= (symbol-name (mal-data-value value1)) - (symbol-name (mal-data-value value2)))) + (string= (mal-data-value value1) + (mal-data-value value2))) (defun mal-sequence= (value1 value2) (let ((sequence1 (map 'list #'identity (mal-data-value value1))) @@ -175,7 +175,7 @@ value)) ;; This needs to before symbol since t, nil are symbols (boolean (make-mal-boolean value)) - (symbol (make-mal-symbol value)) + (symbol (make-mal-symbol (symbol-name value))) (keyword (make-mal-keyword value)) (string (make-mal-string value)) (list (make-mal-list (map 'list #'wrap-value value))) From b6fce2eea0fb3638a76c2f77c4bf23788422f9cd Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 19:57:09 +0530 Subject: [PATCH 0116/2308] Cleanup mal-value= --- clisp/types.lisp | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/clisp/types.lisp b/clisp/types.lisp index 0e25e26311..aac18b9b9e 100644 --- a/clisp/types.lisp +++ b/clisp/types.lisp @@ -135,20 +135,14 @@ (when (and (typep value1 'mal-data) (typep value2 'mal-data)) (if (equal (mal-data-type value1) (mal-data-type value2)) - (switch-mal-type value1 - (number (= (mal-data-value value1) (mal-data-value value2))) - (boolean (equal (mal-data-value value1) (mal-data-value value2))) - (nil (equal (mal-data-value value1) (mal-data-value value2))) - (string (string= (mal-data-value value1) (mal-data-value value2))) - (symbol (mal-symbol= value1 value2)) - (keyword (mal-symbol= value1 value2)) - (list (mal-sequence= value1 value2)) - (vector (mal-sequence= value1 value2)) - (hash-map (mal-hash-map= value1 value2)) - (any nil)) - (when (or (and (mal-list-p value1) (mal-vector-p value2)) - (and (mal-list-p value2) (mal-vector-p value1))) - (mal-sequence= value1 value2))))) + (switch-mal-type value1 + (list (mal-sequence= value1 value2)) + (vector (mal-sequence= value1 value2)) + (hash-map (mal-hash-map= value1 value2)) + (any (equal (mal-data-value value1) (mal-data-value value2)))) + (when (or (and (mal-list-p value1) (mal-vector-p value2)) + (and (mal-list-p value2) (mal-vector-p value1))) + (mal-sequence= value1 value2))))) (defun hash-mal-value (value) (sxhash (mal-data-value value))) From 20bd0392b97d94b391dfa54bd52dc638c64a2050 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 20:09:20 +0530 Subject: [PATCH 0117/2308] Use find-env instead of get-env in is-macro-call Apparently the earlier approach with all its error handling is slower --- clisp/env.lisp | 1 + clisp/step8_macros.lisp | 2 +- clisp/step9_try.lisp | 2 +- clisp/stepA_mal.lisp | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/clisp/env.lisp b/clisp/env.lisp index faa63cef75..f339eee41e 100644 --- a/clisp/env.lisp +++ b/clisp/env.lisp @@ -5,6 +5,7 @@ (:export :undefined-symbol :mal-environment :get-env + :find-env :set-env)) (in-package :env) diff --git a/clisp/step8_macros.lisp b/clisp/step8_macros.lisp index bc3250f039..4c88ce2780 100644 --- a/clisp/step8_macros.lisp +++ b/clisp/step8_macros.lisp @@ -89,7 +89,7 @@ (not (zerop (length (mal-data-value ast))))) (let* ((func-symbol (first (mal-data-value ast))) (func (when (types:mal-symbol-p func-symbol) - (ignore-errors (env:get-env env func-symbol))))) + (env:find-env env func-symbol)))) (and func (types:mal-fn-p func) (cdr (assoc 'is-macro (types:mal-data-attrs func))))))) diff --git a/clisp/step9_try.lisp b/clisp/step9_try.lisp index 858b0bb63c..9985c61e84 100644 --- a/clisp/step9_try.lisp +++ b/clisp/step9_try.lisp @@ -89,7 +89,7 @@ (not (zerop (length (mal-data-value ast))))) (let* ((func-symbol (first (mal-data-value ast))) (func (when (types:mal-symbol-p func-symbol) - (ignore-errors (env:get-env env func-symbol))))) + (env:find-env env func-symbol)))) (and func (types:mal-fn-p func) (cdr (assoc 'is-macro (types:mal-data-attrs func))))))) diff --git a/clisp/stepA_mal.lisp b/clisp/stepA_mal.lisp index 73812b5a8f..93e6f6fba8 100644 --- a/clisp/stepA_mal.lisp +++ b/clisp/stepA_mal.lisp @@ -89,7 +89,7 @@ (not (zerop (length (mal-data-value ast))))) (let* ((func-symbol (first (mal-data-value ast))) (func (when (types:mal-symbol-p func-symbol) - (ignore-errors (env:get-env env func-symbol))))) + (env:find-env env func-symbol)))) (and func (types:mal-fn-p func) (cdr (assoc 'is-macro (types:mal-data-attrs func))))))) From f6da27368c7a96179bd8d2f8fb727a6b38176f8a Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 20:39:21 +0530 Subject: [PATCH 0118/2308] Coerce results of division to a non fractional value --- clisp/core.lisp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clisp/core.lisp b/clisp/core.lisp index a54fdc4543..1136c4c696 100644 --- a/clisp/core.lisp +++ b/clisp/core.lisp @@ -40,8 +40,9 @@ (types:apply-unwrapped-values '* value1 value2)))) (cons (types:make-mal-symbol "/") - (types:make-mal-builtin-fn ( lambda (value1 value2) - (types:apply-unwrapped-values '/ value1 value2)))) + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:make-mal-number (float (/ (types:mal-data-value value1) + (types:mal-data-value value2))))))) (cons (types:make-mal-symbol "prn") (types:make-mal-builtin-fn (lambda (&rest strings) From 3b97a1a78baaa753e0b3c0a9034c5932564efe14 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 20:43:15 +0530 Subject: [PATCH 0119/2308] Make sure dependencies are loaded only once --- clisp/core.lisp | 4 ---- clisp/dependencies.lisp | 6 ++++++ clisp/env.lisp | 2 -- clisp/printer.lisp | 3 --- clisp/reader.lisp | 3 --- clisp/step1_read_print.lisp | 3 +-- clisp/step2_eval.lisp | 5 +---- clisp/step3_env.lisp | 5 +---- clisp/step4_if_fn_do.lisp | 6 +----- clisp/step5_tco.lisp | 6 +----- clisp/step6_file.lisp | 6 +----- clisp/step7_quote.lisp | 6 +----- clisp/step8_macros.lisp | 6 +----- clisp/step9_try.lisp | 6 +----- clisp/stepA_mal.lisp | 6 +----- 15 files changed, 16 insertions(+), 57 deletions(-) create mode 100644 clisp/dependencies.lisp diff --git a/clisp/core.lisp b/clisp/core.lisp index 1136c4c696..bd17673f28 100644 --- a/clisp/core.lisp +++ b/clisp/core.lisp @@ -1,7 +1,3 @@ -(require "types") -(require "reader") -(require "printer") - (defpackage :core (:use :common-lisp :types :reader :printer) (:export :ns)) diff --git a/clisp/dependencies.lisp b/clisp/dependencies.lisp new file mode 100644 index 0000000000..4d822253e4 --- /dev/null +++ b/clisp/dependencies.lisp @@ -0,0 +1,6 @@ +(require "utils") +(require "types") +(require "env") +(require "reader") +(require "printer") +(require "core") diff --git a/clisp/env.lisp b/clisp/env.lisp index f339eee41e..5f85ac6491 100644 --- a/clisp/env.lisp +++ b/clisp/env.lisp @@ -1,5 +1,3 @@ -(require "types") - (defpackage :env (:use :common-lisp :types) (:export :undefined-symbol diff --git a/clisp/printer.lisp b/clisp/printer.lisp index b51defbd89..afc05dc352 100644 --- a/clisp/printer.lisp +++ b/clisp/printer.lisp @@ -1,6 +1,3 @@ -(require "types") -(require "utils") - (defpackage :printer (:use :common-lisp :utils :types) (:export :pr-str)) diff --git a/clisp/reader.lisp b/clisp/reader.lisp index 5dfe412a13..b10bab8e54 100644 --- a/clisp/reader.lisp +++ b/clisp/reader.lisp @@ -1,6 +1,3 @@ -(require "types") -(require "utils") - (defpackage :reader (:use :common-lisp :regexp :utils :types) (:export :read-str diff --git a/clisp/step1_read_print.lisp b/clisp/step1_read_print.lisp index 5913fb424d..e9e602aa68 100644 --- a/clisp/step1_read_print.lisp +++ b/clisp/step1_read_print.lisp @@ -1,5 +1,4 @@ -(require "reader") -(require "printer") +(require "dependencies") (defpackage :mal (:use :common-lisp :reader :printer)) diff --git a/clisp/step2_eval.lisp b/clisp/step2_eval.lisp index 4b75a67583..f7ef76baf5 100644 --- a/clisp/step2_eval.lisp +++ b/clisp/step2_eval.lisp @@ -1,7 +1,4 @@ -(require "reader") -(require "printer") -(require "types") -(require "env") +(require "dependencies") (defpackage :mal (:use :common-lisp :types :env :reader :printer)) diff --git a/clisp/step3_env.lisp b/clisp/step3_env.lisp index ce97c632f4..c20fec1420 100644 --- a/clisp/step3_env.lisp +++ b/clisp/step3_env.lisp @@ -1,7 +1,4 @@ -(require "reader") -(require "printer") -(require "types") -(require "env") +(require "dependencies") (defpackage :mal (:use :common-lisp :types :env :reader :printer)) diff --git a/clisp/step4_if_fn_do.lisp b/clisp/step4_if_fn_do.lisp index f7963a37e8..c3d1af3ed9 100644 --- a/clisp/step4_if_fn_do.lisp +++ b/clisp/step4_if_fn_do.lisp @@ -1,8 +1,4 @@ -(require "reader") -(require "printer") -(require "types") -(require "env") -(require "core") +(require "dependencies") (defpackage :mal (:use :common-lisp diff --git a/clisp/step5_tco.lisp b/clisp/step5_tco.lisp index 939f576596..188bdb117b 100644 --- a/clisp/step5_tco.lisp +++ b/clisp/step5_tco.lisp @@ -1,8 +1,4 @@ -(require "reader") -(require "printer") -(require "types") -(require "env") -(require "core") +(require "dependencies") (defpackage :mal (:use :common-lisp diff --git a/clisp/step6_file.lisp b/clisp/step6_file.lisp index 82128cc982..377bb2aa0c 100644 --- a/clisp/step6_file.lisp +++ b/clisp/step6_file.lisp @@ -1,8 +1,4 @@ -(require "reader") -(require "printer") -(require "types") -(require "env") -(require "core") +(require "dependencies") (defpackage :mal (:use :common-lisp diff --git a/clisp/step7_quote.lisp b/clisp/step7_quote.lisp index dc27ab39c0..73a1cffcbb 100644 --- a/clisp/step7_quote.lisp +++ b/clisp/step7_quote.lisp @@ -1,8 +1,4 @@ -(require "reader") -(require "printer") -(require "types") -(require "env") -(require "core") +(require "dependencies") (defpackage :mal (:use :common-lisp diff --git a/clisp/step8_macros.lisp b/clisp/step8_macros.lisp index 4c88ce2780..8ac32cba71 100644 --- a/clisp/step8_macros.lisp +++ b/clisp/step8_macros.lisp @@ -1,8 +1,4 @@ -(require "reader") -(require "printer") -(require "types") -(require "env") -(require "core") +(require "dependencies") (defpackage :mal (:use :common-lisp diff --git a/clisp/step9_try.lisp b/clisp/step9_try.lisp index 9985c61e84..9ea3a543a7 100644 --- a/clisp/step9_try.lisp +++ b/clisp/step9_try.lisp @@ -1,8 +1,4 @@ -(require "reader") -(require "printer") -(require "types") -(require "env") -(require "core") +(require "dependencies") (defpackage :mal (:use :common-lisp diff --git a/clisp/stepA_mal.lisp b/clisp/stepA_mal.lisp index 93e6f6fba8..37b08c58ad 100644 --- a/clisp/stepA_mal.lisp +++ b/clisp/stepA_mal.lisp @@ -1,8 +1,4 @@ -(require "reader") -(require "printer") -(require "types") -(require "env") -(require "core") +(require "dependencies") (defpackage :mal (:use :common-lisp From 29cb42a450682b092a9c3c8c9831a972ccfe44b2 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 21:04:01 +0530 Subject: [PATCH 0120/2308] Avoid creating unnecessary symbols for special froms in eval --- clisp/step3_env.lisp | 7 ++++-- clisp/step4_if_fn_do.lisp | 16 ++++++++---- clisp/step5_tco.lisp | 16 ++++++++---- clisp/step6_file.lisp | 16 ++++++++---- clisp/step7_quote.lisp | 37 +++++++++++++++++++--------- clisp/step8_macros.lisp | 42 +++++++++++++++++++++----------- clisp/step9_try.lisp | 48 ++++++++++++++++++++++++------------ clisp/stepA_mal.lisp | 51 +++++++++++++++++++++++++-------------- 8 files changed, 156 insertions(+), 77 deletions(-) diff --git a/clisp/step3_env.lisp b/clisp/step3_env.lisp index c20fec1420..c8b420343c 100644 --- a/clisp/step3_env.lisp +++ b/clisp/step3_env.lisp @@ -27,6 +27,9 @@ (types:make-mal-builtin-fn (lambda (value1 value2) (apply-unwrapped-values '/ value1 value2)))) +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-let* (make-mal-symbol "let*")) + (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) @@ -73,9 +76,9 @@ (defun eval-list (ast env) (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol "def!") (first forms)) + ((mal-value= mal-def! (first forms)) (env:set-env env (second forms) (mal-eval (third forms) env))) - ((mal-value= (make-mal-symbol "let*") (first forms)) + ((mal-value= mal-let* (first forms)) (eval-let* forms env)) (t (let ((evaluated-list (eval-ast ast env))) (apply (types:mal-data-value (car evaluated-list)) diff --git a/clisp/step4_if_fn_do.lisp b/clisp/step4_if_fn_do.lisp index c3d1af3ed9..a81e15e4ea 100644 --- a/clisp/step4_if_fn_do.lisp +++ b/clisp/step4_if_fn_do.lisp @@ -17,6 +17,12 @@ (car binding) (cdr binding))) +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-let* (make-mal-symbol "let*")) +(defvar mal-do (make-mal-symbol "do")) +(defvar mal-if (make-mal-symbol "if")) +(defvar mal-fn* (make-mal-symbol "fn*")) + (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) @@ -63,21 +69,21 @@ (defun eval-list (ast env) (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol "def!") (first forms)) + ((mal-value= mal-def! (first forms)) (env:set-env env (second forms) (mal-eval (third forms) env))) - ((mal-value= (make-mal-symbol "let*") (first forms)) + ((mal-value= mal-let* (first forms)) (eval-let* forms env)) - ((mal-value= (make-mal-symbol "do") (first forms)) + ((mal-value= mal-do (first forms)) (car (last (mapcar (lambda (form) (mal-eval form env)) (cdr forms))))) - ((mal-value= (make-mal-symbol "if") (first forms)) + ((mal-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) (mal-eval (if (or (mal-value= predicate (types:make-mal-nil nil)) (mal-value= predicate (types:make-mal-boolean nil))) (fourth forms) (third forms)) env))) - ((mal-value= (make-mal-symbol "fn*") (first forms)) + ((mal-value= mal-fn* (first forms)) (types:make-mal-fn (let ((arglist (second forms)) (body (third forms))) (lambda (&rest args) diff --git a/clisp/step5_tco.lisp b/clisp/step5_tco.lisp index 188bdb117b..e570954b0d 100644 --- a/clisp/step5_tco.lisp +++ b/clisp/step5_tco.lisp @@ -17,6 +17,12 @@ (car binding) (cdr binding))) +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-let* (make-mal-symbol "let*")) +(defvar mal-do (make-mal-symbol "do")) +(defvar mal-if (make-mal-symbol "if")) +(defvar mal-fn* (make-mal-symbol "fn*")) + (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) @@ -50,10 +56,10 @@ ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol "def!") (first forms)) + ((mal-value= mal-def! (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - ((mal-value= (make-mal-symbol "let*") (first forms)) + ((mal-value= mal-let* (first forms)) (let ((new-env (make-instance 'env:mal-environment :parent env)) ;; Convert a potential vector to a list @@ -74,19 +80,19 @@ (setf ast (third forms) env new-env))) - ((mal-value= (make-mal-symbol "do") (first forms)) + ((mal-value= mal-do (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) - ((mal-value= (make-mal-symbol "if") (first forms)) + ((mal-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) (mal-value= predicate (types:make-mal-boolean nil))) (fourth forms) (third forms))))) - ((mal-value= (make-mal-symbol "fn*") (first forms)) + ((mal-value= mal-fn* (first forms)) (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) diff --git a/clisp/step6_file.lisp b/clisp/step6_file.lisp index 377bb2aa0c..22d68787c5 100644 --- a/clisp/step6_file.lisp +++ b/clisp/step6_file.lisp @@ -17,6 +17,12 @@ (car binding) (cdr binding))) +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-let* (make-mal-symbol "let*")) +(defvar mal-do (make-mal-symbol "do")) +(defvar mal-if (make-mal-symbol "if")) +(defvar mal-fn* (make-mal-symbol "fn*")) + (env:set-env *repl-env* (types:make-mal-symbol "eval") (types:make-mal-builtin-fn (lambda (ast) @@ -55,10 +61,10 @@ ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol "def!") (first forms)) + ((mal-value= mal-def! (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - ((mal-value= (make-mal-symbol "let*") (first forms)) + ((mal-value= mal-let* (first forms)) (let ((new-env (make-instance 'env:mal-environment :parent env)) ;; Convert a potential vector to a list @@ -79,19 +85,19 @@ (setf ast (third forms) env new-env))) - ((mal-value= (make-mal-symbol "do") (first forms)) + ((mal-value= mal-do (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) - ((mal-value= (make-mal-symbol "if") (first forms)) + ((mal-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) (mal-value= predicate (types:make-mal-boolean nil))) (fourth forms) (third forms))))) - ((mal-value= (make-mal-symbol "fn*") (first forms)) + ((mal-value= mal-fn* (first forms)) (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) diff --git a/clisp/step7_quote.lisp b/clisp/step7_quote.lisp index 73a1cffcbb..f402a4495c 100644 --- a/clisp/step7_quote.lisp +++ b/clisp/step7_quote.lisp @@ -17,6 +17,18 @@ (car binding) (cdr binding))) +(defvar mal-quote (make-mal-symbol "quote")) +(defvar mal-quasiquote (make-mal-symbol "quasiquote")) +(defvar mal-unquote (make-mal-symbol "unquote")) +(defvar mal-splice-unquote (make-mal-symbol "splice-unquote")) +(defvar mal-cons (make-mal-symbol "cons")) +(defvar mal-concat (make-mal-symbol "concat")) +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-let* (make-mal-symbol "let*")) +(defvar mal-do (make-mal-symbol "do")) +(defvar mal-if (make-mal-symbol "if")) +(defvar mal-fn* (make-mal-symbol "fn*")) + (env:set-env *repl-env* (types:make-mal-symbol "eval") (types:make-mal-builtin-fn (lambda (ast) @@ -44,6 +56,7 @@ (types:hash-map (eval-hash-map ast env)) (types:any ast))) + (defun is-pair (value) (and (or (mal-list-p value) (mal-vector-p value)) @@ -51,21 +64,21 @@ (defun quasiquote (ast) (if (not (is-pair ast)) - (types:make-mal-list (list (types:make-mal-symbol "quote") + (types:make-mal-list (list mal-quote ast)) (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond - ((mal-value= (make-mal-symbol "unquote") (first forms)) + ((mal-value= mal-unquote (first forms)) (second forms)) ((and (is-pair (first forms)) - (mal-value= (make-mal-symbol "splice-unquote") + (mal-value= mal-splice-unquote (first (mal-data-value (first forms))))) - (types:make-mal-list (list (types:make-mal-symbol "concat") + (types:make-mal-list (list mal-concat (second (mal-data-value (first forms))) (quasiquote (make-mal-list (cdr forms)))))) - (t (types:make-mal-list (list (types:make-mal-symbol "cons") + (t (types:make-mal-list (list mal-cons (quasiquote (first forms)) (quasiquote (make-mal-list (cdr forms)))))))))) @@ -80,16 +93,16 @@ ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol "quote") (first forms)) + ((mal-value= mal-quote (first forms)) (return (second forms))) - ((mal-value= (make-mal-symbol "quasiquote") (first forms)) + ((mal-value= mal-quasiquote (first forms)) (setf ast (quasiquote (second forms)))) - ((mal-value= (make-mal-symbol "def!") (first forms)) + ((mal-value= mal-def! (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - ((mal-value= (make-mal-symbol "let*") (first forms)) + ((mal-value= mal-let* (first forms)) (let ((new-env (make-instance 'env:mal-environment :parent env)) ;; Convert a potential vector to a list @@ -110,19 +123,19 @@ (setf ast (third forms) env new-env))) - ((mal-value= (make-mal-symbol "do") (first forms)) + ((mal-value= mal-do (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) - ((mal-value= (make-mal-symbol "if") (first forms)) + ((mal-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) (mal-value= predicate (types:make-mal-boolean nil))) (fourth forms) (third forms))))) - ((mal-value= (make-mal-symbol "fn*") (first forms)) + ((mal-value= mal-fn* (first forms)) (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) diff --git a/clisp/step8_macros.lisp b/clisp/step8_macros.lisp index 8ac32cba71..ce0a2b11f0 100644 --- a/clisp/step8_macros.lisp +++ b/clisp/step8_macros.lisp @@ -28,6 +28,20 @@ (car binding) (cdr binding))) +(defvar mal-quote (make-mal-symbol "quote")) +(defvar mal-quasiquote (make-mal-symbol "quasiquote")) +(defvar mal-unquote (make-mal-symbol "unquote")) +(defvar mal-splice-unquote (make-mal-symbol "splice-unquote")) +(defvar mal-cons (make-mal-symbol "cons")) +(defvar mal-concat (make-mal-symbol "concat")) +(defvar mal-macroexpand (make-mal-symbol "macroexpand")) +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-defmacro! (make-mal-symbol "defmacro!")) +(defvar mal-let* (make-mal-symbol "let*")) +(defvar mal-do (make-mal-symbol "do")) +(defvar mal-if (make-mal-symbol "if")) +(defvar mal-fn* (make-mal-symbol "fn*")) + (env:set-env *repl-env* (types:make-mal-symbol "eval") (types:make-mal-builtin-fn (lambda (ast) @@ -62,21 +76,21 @@ (defun quasiquote (ast) (if (not (is-pair ast)) - (types:make-mal-list (list (types:make-mal-symbol "quote") + (types:make-mal-list (list mal-quote ast)) (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond - ((mal-value= (make-mal-symbol "unquote") (first forms)) + ((mal-value= mal-unquote (first forms)) (second forms)) ((and (is-pair (first forms)) - (mal-value= (make-mal-symbol "splice-unquote") + (mal-value= mal-splice-unquote (first (mal-data-value (first forms))))) - (types:make-mal-list (list (types:make-mal-symbol "concat") + (types:make-mal-list (list mal-concat (second (mal-data-value (first forms))) (quasiquote (make-mal-list (cdr forms)))))) - (t (types:make-mal-list (list (types:make-mal-symbol "cons") + (t (types:make-mal-list (list mal-cons (quasiquote (first forms)) (quasiquote (make-mal-list (cdr forms)))))))))) @@ -108,19 +122,19 @@ ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol "quote") (first forms)) + ((mal-value= mal-quote (first forms)) (return (second forms))) - ((mal-value= (make-mal-symbol "quasiquote") (first forms)) + ((mal-value= mal-quasiquote (first forms)) (setf ast (quasiquote (second forms)))) - ((mal-value= (make-mal-symbol "macroexpand") (first forms)) + ((mal-value= mal-macroexpand (first forms)) (return (mal-macroexpand (second forms) env))) - ((mal-value= (make-mal-symbol "def!") (first forms)) + ((mal-value= mal-def! (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - ((mal-value= (make-mal-symbol "defmacro!") (first forms)) + ((mal-value= mal-defmacro! (first forms)) (let ((value (mal-eval (third forms) env))) (return (if (types:mal-fn-p value) (env:set-env env @@ -132,7 +146,7 @@ :form value :context "macro"))))) - ((mal-value= (make-mal-symbol "let*") (first forms)) + ((mal-value= mal-let* (first forms)) (let ((new-env (make-instance 'env:mal-environment :parent env)) ;; Convert a potential vector to a list @@ -153,19 +167,19 @@ (setf ast (third forms) env new-env))) - ((mal-value= (make-mal-symbol "do") (first forms)) + ((mal-value= mal-do (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) - ((mal-value= (make-mal-symbol "if") (first forms)) + ((mal-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) (mal-value= predicate (types:make-mal-boolean nil))) (fourth forms) (third forms))))) - ((mal-value= (make-mal-symbol "fn*") (first forms)) + ((mal-value= mal-fn* (first forms)) (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) diff --git a/clisp/step9_try.lisp b/clisp/step9_try.lisp index 9ea3a543a7..d79bde82d4 100644 --- a/clisp/step9_try.lisp +++ b/clisp/step9_try.lisp @@ -33,6 +33,22 @@ (types:make-mal-builtin-fn (lambda (ast) (mal-eval ast *repl-env*)))) +(defvar mal-quote (make-mal-symbol "quote")) +(defvar mal-quasiquote (make-mal-symbol "quasiquote")) +(defvar mal-unquote (make-mal-symbol "unquote")) +(defvar mal-splice-unquote (make-mal-symbol "splice-unquote")) +(defvar mal-cons (make-mal-symbol "cons")) +(defvar mal-concat (make-mal-symbol "concat")) +(defvar mal-macroexpand (make-mal-symbol "macroexpand")) +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-defmacro! (make-mal-symbol "defmacro!")) +(defvar mal-let* (make-mal-symbol "let*")) +(defvar mal-do (make-mal-symbol "do")) +(defvar mal-if (make-mal-symbol "if")) +(defvar mal-fn* (make-mal-symbol "fn*")) +(defvar mal-try* (make-mal-symbol "try*")) +(defvar mal-catch* (make-mal-symbol "catch*")) + (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) @@ -62,21 +78,21 @@ (defun quasiquote (ast) (if (not (is-pair ast)) - (types:make-mal-list (list (types:make-mal-symbol "quote") + (types:make-mal-list (list mal-quote ast)) (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond - ((mal-value= (make-mal-symbol "unquote") (first forms)) + ((mal-value= mal-unquote (first forms)) (second forms)) ((and (is-pair (first forms)) - (mal-value= (make-mal-symbol "splice-unquote") + (mal-value= mal-splice-unquote (first (mal-data-value (first forms))))) - (types:make-mal-list (list (types:make-mal-symbol "concat") + (types:make-mal-list (list mal-concat (second (mal-data-value (first forms))) (quasiquote (make-mal-list (cdr forms)))))) - (t (types:make-mal-list (list (types:make-mal-symbol "cons") + (t (types:make-mal-list (list mal-cons (quasiquote (first forms)) (quasiquote (make-mal-list (cdr forms)))))))))) @@ -108,19 +124,19 @@ ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol "quote") (first forms)) + ((mal-value= mal-quote (first forms)) (return (second forms))) - ((mal-value= (make-mal-symbol "quasiquote") (first forms)) + ((mal-value= mal-quasiquote (first forms)) (setf ast (quasiquote (second forms)))) - ((mal-value= (make-mal-symbol "macroexpand") (first forms)) + ((mal-value= mal-macroexpand (first forms)) (return (mal-macroexpand (second forms) env))) - ((mal-value= (make-mal-symbol "def!") (first forms)) + ((mal-value= mal-def! (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - ((mal-value= (make-mal-symbol "defmacro!") (first forms)) + ((mal-value= mal-defmacro! (first forms)) (let ((value (mal-eval (third forms) env))) (return (if (types:mal-fn-p value) (env:set-env env @@ -132,7 +148,7 @@ :form value :context "macro"))))) - ((mal-value= (make-mal-symbol "let*") (first forms)) + ((mal-value= mal-let* (first forms)) (let ((new-env (make-instance 'env:mal-environment :parent env)) ;; Convert a potential vector to a list @@ -153,19 +169,19 @@ (setf ast (third forms) env new-env))) - ((mal-value= (make-mal-symbol "do") (first forms)) + ((mal-value= mal-do (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) - ((mal-value= (make-mal-symbol "if") (first forms)) + ((mal-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) (mal-value= predicate (types:make-mal-boolean nil))) (fourth forms) (third forms))))) - ((mal-value= (make-mal-symbol "fn*") (first forms)) + ((mal-value= mal-fn* (first forms)) (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) @@ -180,13 +196,13 @@ (cons 'env env) (cons 'is-macro nil)))))) - ((mal-value= (make-mal-symbol "try*") (first forms)) + ((mal-value= mal-try* (first forms)) (handler-case (return (mal-eval (second forms) env)) (types:mal-exception (condition) (when (third forms) (let ((catch-forms (types:mal-data-value (third forms)))) - (when (mal-value= (make-mal-symbol "catch*") + (when (mal-value= mal-catch* (first catch-forms)) (return (mal-eval (third catch-forms) (make-instance 'env:mal-environment diff --git a/clisp/stepA_mal.lisp b/clisp/stepA_mal.lisp index 37b08c58ad..34d6277547 100644 --- a/clisp/stepA_mal.lisp +++ b/clisp/stepA_mal.lisp @@ -28,6 +28,22 @@ (car binding) (cdr binding))) +(defvar mal-quote (make-mal-symbol "quote")) +(defvar mal-quasiquote (make-mal-symbol "quasiquote")) +(defvar mal-unquote (make-mal-symbol "unquote")) +(defvar mal-splice-unquote (make-mal-symbol "splice-unquote")) +(defvar mal-cons (make-mal-symbol "cons")) +(defvar mal-concat (make-mal-symbol "concat")) +(defvar mal-macroexpand (make-mal-symbol "macroexpand")) +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-defmacro! (make-mal-symbol "defmacro!")) +(defvar mal-let* (make-mal-symbol "let*")) +(defvar mal-do (make-mal-symbol "do")) +(defvar mal-if (make-mal-symbol "if")) +(defvar mal-fn* (make-mal-symbol "fn*")) +(defvar mal-try* (make-mal-symbol "try*")) +(defvar mal-catch* (make-mal-symbol "catch*")) + (env:set-env *repl-env* (types:make-mal-symbol "eval") (types:make-mal-builtin-fn (lambda (ast) @@ -62,21 +78,20 @@ (defun quasiquote (ast) (if (not (is-pair ast)) - (types:make-mal-list (list (types:make-mal-symbol "quote") - ast)) + (types:make-mal-list (list mal-quote ast)) (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond - ((mal-value= (make-mal-symbol "unquote") (first forms)) + ((mal-value= mal-unquote (first forms)) (second forms)) ((and (is-pair (first forms)) - (mal-value= (make-mal-symbol "splice-unquote") + (mal-value= mal-splice-unquote (first (mal-data-value (first forms))))) - (types:make-mal-list (list (types:make-mal-symbol "concat") + (types:make-mal-list (list mal-concat (second (mal-data-value (first forms))) (quasiquote (make-mal-list (cdr forms)))))) - (t (types:make-mal-list (list (types:make-mal-symbol "cons") + (t (types:make-mal-list (list mal-cons (quasiquote (first forms)) (quasiquote (make-mal-list (cdr forms)))))))))) @@ -108,19 +123,19 @@ ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) (cond - ((mal-value= (make-mal-symbol "quote") (first forms)) + ((mal-value= mal-quote (first forms)) (return (second forms))) - ((mal-value= (make-mal-symbol "quasiquote") (first forms)) + ((mal-value= mal-quasiquote (first forms)) (setf ast (quasiquote (second forms)))) - ((mal-value= (make-mal-symbol "macroexpand") (first forms)) + ((mal-value= mal-macroexpand (first forms)) (return (mal-macroexpand (second forms) env))) - ((mal-value= (make-mal-symbol "def!") (first forms)) + ((mal-value= mal-def! (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - ((mal-value= (make-mal-symbol "defmacro!") (first forms)) + ((mal-value= mal-defmacro! (first forms)) (let ((value (mal-eval (third forms) env))) (return (if (types:mal-fn-p value) (env:set-env env @@ -132,7 +147,7 @@ :form value :context "macro"))))) - ((mal-value= (make-mal-symbol "let*") (first forms)) + ((mal-value= mal-let* (first forms)) (let ((new-env (make-instance 'env:mal-environment :parent env)) ;; Convert a potential vector to a list @@ -153,19 +168,19 @@ (setf ast (third forms) env new-env))) - ((mal-value= (make-mal-symbol "do") (first forms)) + ((mal-value= mal-do (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) - ((mal-value= (make-mal-symbol "if") (first forms)) + ((mal-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) (mal-value= predicate (types:make-mal-boolean nil))) (fourth forms) (third forms))))) - ((mal-value= (make-mal-symbol "fn*") (first forms)) + ((mal-value= mal-fn* (first forms)) (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) @@ -180,13 +195,13 @@ (cons 'env env) (cons 'is-macro nil)))))) - ((mal-value= (make-mal-symbol "try*") (first forms)) + ((mal-value= mal-try* (first forms)) (handler-case (return (mal-eval (second forms) env)) ((or types:mal-exception types:mal-error) (condition) (when (third forms) (let ((catch-forms (types:mal-data-value (third forms)))) - (when (mal-value= (make-mal-symbol "catch*") + (when (mal-value= mal-catch* (first catch-forms)) (return (mal-eval (third catch-forms) (make-instance 'env:mal-environment @@ -196,7 +211,7 @@ (typep condition 'types:mal-error)) (types:make-mal-string (format nil "~a" condition)) (types::mal-exception-data condition))))))))) - (error condition)))) + (error condition)))) (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) From d4fe5ef0a50c763b42534427eb7a7d52758150f3 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 21:46:37 +0530 Subject: [PATCH 0121/2308] Use singleton values for nil, true and false --- clisp/core.lisp | 53 +++++++++++++++++++++------------------ clisp/env.lisp | 2 +- clisp/reader.lisp | 6 ++--- clisp/step3_env.lisp | 4 +-- clisp/step4_if_fn_do.lisp | 8 +++--- clisp/step5_tco.lisp | 8 +++--- clisp/step6_file.lisp | 8 +++--- clisp/step7_quote.lisp | 8 +++--- clisp/step8_macros.lisp | 8 +++--- clisp/step9_try.lisp | 8 +++--- clisp/stepA_mal.lisp | 8 +++--- clisp/types.lisp | 29 +++++++++++++++------ 12 files changed, 84 insertions(+), 66 deletions(-) diff --git a/clisp/core.lisp b/clisp/core.lisp index bd17673f28..b54c451942 100644 --- a/clisp/core.lisp +++ b/clisp/core.lisp @@ -21,6 +21,11 @@ (read-sequence data stream) data))) +(defmacro wrap-boolean (form) + `(if ,form + types:mal-true + types:mal-false)) + (defvar ns (list (cons (types:make-mal-symbol "+") @@ -46,7 +51,7 @@ "~{~a~^ ~}" (mapcar (lambda (string) (printer:pr-str string t)) strings))) - (types:make-mal-nil nil)))) + types:mal-nil))) (cons (types:make-mal-symbol "println") (types:make-mal-builtin-fn (lambda (&rest strings) @@ -54,7 +59,7 @@ "~{~a~^ ~}" (mapcar (lambda (string) (printer:pr-str string nil)) strings))) - (types:make-mal-nil nil)))) + types:mal-nil))) (cons (types:make-mal-symbol "pr-str") (types:make-mal-builtin-fn (lambda (&rest strings) @@ -76,12 +81,12 @@ (cons (types:make-mal-symbol "list?") (types:make-mal-builtin-fn (lambda (value) - (types:make-mal-boolean (or (types:mal-nil-p value) - (types:mal-list-p value)))))) + (wrap-boolean (or (types:mal-nil-p value) + (types:mal-list-p value)))))) (cons (types:make-mal-symbol "empty?") (types:make-mal-builtin-fn (lambda (value) - (types:make-mal-boolean (zerop (length (mal-data-value value))))))) + (wrap-boolean (zerop (length (mal-data-value value))))))) (cons (types:make-mal-symbol "count") (types:make-mal-builtin-fn (lambda (value) @@ -89,7 +94,7 @@ (cons (types:make-mal-symbol "=") (types:make-mal-builtin-fn (lambda (value1 value2) - (types:make-mal-boolean (types:mal-value= value1 value2))))) + (wrap-boolean (types:mal-value= value1 value2))))) (cons (types:make-mal-symbol "<") (types:make-mal-builtin-fn (lambda (value1 value2) @@ -129,7 +134,7 @@ (cons (types:make-mal-symbol "atom?") (types:make-mal-builtin-fn (lambda (value) - (types:make-mal-boolean (types:mal-atom-p value))))) + (wrap-boolean (types:mal-atom-p value))))) (cons (types:make-mal-symbol "deref") (types:make-mal-builtin-fn (lambda (atom) @@ -172,7 +177,7 @@ (cons (types:make-mal-symbol "first") (types:make-mal-builtin-fn (lambda (sequence) (or (first (map 'list #'identity (mal-data-value sequence))) - (types:make-mal-nil nil))))) + types:mal-nil)))) (cons (types:make-mal-symbol "rest") (types:make-mal-builtin-fn (lambda (sequence) @@ -204,21 +209,21 @@ (cons (types:make-mal-symbol "nil?") (types:make-mal-builtin-fn (lambda (value) - (types:make-mal-boolean (types:mal-nil-p value))))) + (wrap-boolean (types:mal-nil-p value))))) (cons (types:make-mal-symbol "true?") (types:make-mal-builtin-fn (lambda (value) - (types:make-mal-boolean (and (types:mal-boolean-p value) + (wrap-boolean (and (types:mal-boolean-p value) (types:mal-data-value value)))))) (cons (types:make-mal-symbol "false?") (types:make-mal-builtin-fn (lambda (value) - (types:make-mal-boolean (and (types:mal-boolean-p value) + (wrap-boolean (and (types:mal-boolean-p value) (not (types:mal-data-value value))))))) (cons (types:make-mal-symbol "symbol?") (types:make-mal-builtin-fn (lambda (value) - (types:make-mal-boolean (types:mal-symbol-p value))))) + (wrap-boolean (types:mal-symbol-p value))))) (cons (types:make-mal-symbol "symbol") (types:make-mal-builtin-fn (lambda (string) @@ -232,7 +237,7 @@ (cons (types:make-mal-symbol "keyword?") (types:make-mal-builtin-fn (lambda (value) - (types:make-mal-boolean (types:mal-keyword-p value))))) + (wrap-boolean (types:mal-keyword-p value))))) (cons (types:make-mal-symbol "vector") (types:make-mal-builtin-fn (lambda (&rest elements) @@ -240,7 +245,7 @@ (cons (types:make-mal-symbol "vector?") (types:make-mal-builtin-fn (lambda (value) - (types:make-mal-boolean (types:mal-vector-p value))))) + (wrap-boolean (types:mal-vector-p value))))) (cons (types:make-mal-symbol "hash-map") (types:make-mal-builtin-fn (lambda (&rest elements) @@ -253,7 +258,7 @@ (cons (types:make-mal-symbol "map?") (types:make-mal-builtin-fn (lambda (value) - (types:make-mal-boolean (types:mal-hash-map-p value))))) + (wrap-boolean (types:mal-hash-map-p value))))) (cons (types:make-mal-symbol "assoc") (types:make-mal-builtin-fn (lambda (hash-map &rest elements) @@ -289,13 +294,13 @@ (types:make-mal-builtin-fn (lambda (hash-map key) (or (and (types:mal-hash-map-p hash-map) (gethash key (types:mal-data-value hash-map))) - (types:make-mal-nil nil))))) + types:mal-nil)))) (cons (types:make-mal-symbol "contains?") (types:make-mal-builtin-fn (lambda (hash-map key) (if (gethash key (types:mal-data-value hash-map)) - (types:make-mal-boolean t) - (types:make-mal-boolean nil))))) + types:mal-true + types:mal-false)))) (cons (types:make-mal-symbol "keys") (types:make-mal-builtin-fn (lambda (hash-map) @@ -313,10 +318,8 @@ (cons (types:make-mal-symbol "sequential?") (types:make-mal-builtin-fn (lambda (value) - (if (or (types:mal-vector-p value) - (types:mal-list-p value)) - (types:make-mal-boolean t) - (types:make-mal-boolean nil))))) + (wrap-boolean (or (types:mal-vector-p value) + (types:mal-list-p value)))))) (cons (types:make-mal-symbol "readline") (types:make-mal-builtin-fn (lambda (prompt) @@ -326,7 +329,7 @@ (cons (types:make-mal-symbol "string?") (types:make-mal-builtin-fn (lambda (value) - (types:make-mal-boolean (types:mal-string-p value))))) + (wrap-boolean (types:mal-string-p value))))) (cons (types:make-mal-symbol "time-ms") (types:make-mal-builtin-fn (lambda () @@ -348,7 +351,7 @@ (cons (types:make-mal-symbol "seq") (types:make-mal-builtin-fn (lambda (value) (if (zerop (length (types:mal-data-value value))) - (types:make-mal-nil nil) + types:mal-nil (cond ((types:mal-list-p value) value) ((types:mal-vector-p value) @@ -379,4 +382,4 @@ (cons (types:make-mal-symbol "meta") (types:make-mal-builtin-fn (lambda (value) (or (types:mal-data-meta value) - (types:make-mal-nil nil))))))) + types:mal-nil)))))) diff --git a/clisp/env.lisp b/clisp/env.lisp index 5f85ac6491..27888f8d40 100644 --- a/clisp/env.lisp +++ b/clisp/env.lisp @@ -82,7 +82,7 @@ ;; There are enough parameters to satisfy the ;; normal arguments, set rest-args to a nil value ((= no-of-args varidiac-position) - (make-mal-nil nil))))) + types:mal-nil)))) (handler-case (setf exprs (concatenate 'list (subseq exprs 0 varidiac-position) diff --git a/clisp/reader.lisp b/clisp/reader.lisp index b10bab8e54..5e9e7b66a7 100644 --- a/clisp/reader.lisp +++ b/clisp/reader.lisp @@ -158,11 +158,11 @@ (let ((token (next reader))) (cond ((string= token "false") - (make-mal-boolean nil)) + types:mal-false) ((string= token "true") - (make-mal-boolean t)) + types:mal-true) ((string= token "nil") - (make-mal-nil nil)) + types:mal-nil) ((char= (char token 0) #\") (make-mal-string (parse-string token))) ((char= (char token 0) #\:) diff --git a/clisp/step3_env.lisp b/clisp/step3_env.lisp index c8b420343c..bd9b81de42 100644 --- a/clisp/step3_env.lisp +++ b/clisp/step3_env.lisp @@ -64,7 +64,7 @@ (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - (types:make-mal-nil nil)) + types:mal-nil) new-env))) (loop for (symbol value) on bindings @@ -89,7 +89,7 @@ (defun mal-eval (ast env) (cond - ((null ast) (make-mal-nil nil)) + ((null ast) types:mal-nil) ((not (types:mal-list-p ast)) (eval-ast ast env)) ((zerop (length (mal-data-value ast))) ast) (t (eval-list ast env)))) diff --git a/clisp/step4_if_fn_do.lisp b/clisp/step4_if_fn_do.lisp index a81e15e4ea..387d991d15 100644 --- a/clisp/step4_if_fn_do.lisp +++ b/clisp/step4_if_fn_do.lisp @@ -57,7 +57,7 @@ (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - (types:make-mal-nil nil)) + types:mal-nil) new-env))) (loop for (symbol value) on bindings @@ -78,8 +78,8 @@ (cdr forms))))) ((mal-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) - (mal-eval (if (or (mal-value= predicate (types:make-mal-nil nil)) - (mal-value= predicate (types:make-mal-boolean nil))) + (mal-eval (if (or (mal-value= predicate types:mal-nil) + (mal-value= predicate types:mal-false)) (fourth forms) (third forms)) env))) @@ -104,7 +104,7 @@ (defun mal-eval (ast env) (cond - ((null ast) (make-mal-nil nil)) + ((null ast) types:mal-nil) ((not (types:mal-list-p ast)) (eval-ast ast env)) ((zerop (length (mal-data-value ast))) ast) (t (eval-list ast env)))) diff --git a/clisp/step5_tco.lisp b/clisp/step5_tco.lisp index e570954b0d..2c22c016bb 100644 --- a/clisp/step5_tco.lisp +++ b/clisp/step5_tco.lisp @@ -51,7 +51,7 @@ (defun mal-eval (ast env) (loop do (cond - ((null ast) (return (make-mal-nil nil))) + ((null ast) (return types:mal-nil)) ((not (types:mal-list-p ast)) (return (eval-ast ast env))) ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) @@ -71,7 +71,7 @@ (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - (types:make-mal-nil nil)) + types:mal-nil) new-env))) (loop for (symbol value) on bindings @@ -87,8 +87,8 @@ ((mal-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) - (mal-value= predicate (types:make-mal-boolean nil))) + (setf ast (if (or (mal-value= predicate types:mal-nil) + (mal-value= predicate types:mal-false)) (fourth forms) (third forms))))) diff --git a/clisp/step6_file.lisp b/clisp/step6_file.lisp index 22d68787c5..466a985f91 100644 --- a/clisp/step6_file.lisp +++ b/clisp/step6_file.lisp @@ -56,7 +56,7 @@ (defun mal-eval (ast env) (loop do (cond - ((null ast) (return (make-mal-nil nil))) + ((null ast) (return types:mal-nil)) ((not (types:mal-list-p ast)) (return (eval-ast ast env))) ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) @@ -76,7 +76,7 @@ (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - (types:make-mal-nil nil)) + types:mal-nil) new-env))) (loop for (symbol value) on bindings @@ -92,8 +92,8 @@ ((mal-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) - (mal-value= predicate (types:make-mal-boolean nil))) + (setf ast (if (or (mal-value= predicate types:mal-nil) + (mal-value= predicate types:mal-false)) (fourth forms) (third forms))))) diff --git a/clisp/step7_quote.lisp b/clisp/step7_quote.lisp index f402a4495c..cdbe1ebc0e 100644 --- a/clisp/step7_quote.lisp +++ b/clisp/step7_quote.lisp @@ -88,7 +88,7 @@ (defun mal-eval (ast env) (loop do (cond - ((null ast) (return (make-mal-nil nil))) + ((null ast) (return types:mal-nil)) ((not (types:mal-list-p ast)) (return (eval-ast ast env))) ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) @@ -114,7 +114,7 @@ (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - (types:make-mal-nil nil)) + types:mal-nil) new-env))) (loop for (symbol value) on bindings @@ -130,8 +130,8 @@ ((mal-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) - (mal-value= predicate (types:make-mal-boolean nil))) + (setf ast (if (or (mal-value= predicate types:mal-nil) + (mal-value= predicate types:mal-false)) (fourth forms) (third forms))))) diff --git a/clisp/step8_macros.lisp b/clisp/step8_macros.lisp index ce0a2b11f0..97621a20ca 100644 --- a/clisp/step8_macros.lisp +++ b/clisp/step8_macros.lisp @@ -117,7 +117,7 @@ (loop do (setf ast (mal-macroexpand ast env)) do (cond - ((null ast) (return (make-mal-nil nil))) + ((null ast) (return types:mal-nil)) ((not (types:mal-list-p ast)) (return (eval-ast ast env))) ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) @@ -158,7 +158,7 @@ (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - (types:make-mal-nil nil)) + types:mal-nil) new-env))) (loop for (symbol value) on bindings @@ -174,8 +174,8 @@ ((mal-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) - (mal-value= predicate (types:make-mal-boolean nil))) + (setf ast (if (or (mal-value= predicate types:mal-nil) + (mal-value= predicate types:mal-false)) (fourth forms) (third forms))))) diff --git a/clisp/step9_try.lisp b/clisp/step9_try.lisp index d79bde82d4..77db07bb5c 100644 --- a/clisp/step9_try.lisp +++ b/clisp/step9_try.lisp @@ -119,7 +119,7 @@ (loop do (setf ast (mal-macroexpand ast env)) do (cond - ((null ast) (return (make-mal-nil nil))) + ((null ast) (return types:mal-nil)) ((not (types:mal-list-p ast)) (return (eval-ast ast env))) ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) @@ -160,7 +160,7 @@ (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - (types:make-mal-nil nil)) + types:mal-nil) new-env))) (loop for (symbol value) on bindings @@ -176,8 +176,8 @@ ((mal-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) - (mal-value= predicate (types:make-mal-boolean nil))) + (setf ast (if (or (mal-value= predicate types:mal-nil) + (mal-value= predicate types:mal-false)) (fourth forms) (third forms))))) diff --git a/clisp/stepA_mal.lisp b/clisp/stepA_mal.lisp index 34d6277547..9895fe20ee 100644 --- a/clisp/stepA_mal.lisp +++ b/clisp/stepA_mal.lisp @@ -118,7 +118,7 @@ (loop do (setf ast (mal-macroexpand ast env)) do (cond - ((null ast) (return (make-mal-nil nil))) + ((null ast) (return types:mal-nil)) ((not (types:mal-list-p ast)) (return (eval-ast ast env))) ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) @@ -159,7 +159,7 @@ (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - (types:make-mal-nil nil)) + types:mal-nil) new-env))) (loop for (symbol value) on bindings @@ -175,8 +175,8 @@ ((mal-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (mal-value= predicate (types:make-mal-nil nil)) - (mal-value= predicate (types:make-mal-boolean nil))) + (setf ast (if (or (mal-value= predicate types:mal-nil) + (mal-value= predicate types:mal-false)) (fourth forms) (third forms))))) diff --git a/clisp/types.lisp b/clisp/types.lisp index aac18b9b9e..c31293c531 100644 --- a/clisp/types.lisp +++ b/clisp/types.lisp @@ -1,11 +1,13 @@ (defpackage :types (:use :common-lisp) (:export :mal-value= + ;; Accessors :mal-data-value :mal-data-type :mal-data-meta :mal-data-attrs + ;; Mal values :number :boolean @@ -20,13 +22,23 @@ :fn :builtin-fn :any + + ;; Singleton values + :mal-nil + :mal-true + :mal-false + :mal-exception + ;; User exceptions :mal-user-exception + ;; Exceptions raised by the runtime itself :mal-runtime-exception + ;; Error :mal-error + ;; Helpers :wrap-value :apply-unwrapped-values @@ -93,6 +105,10 @@ (define-mal-type fn) (define-mal-type builtin-fn) +(defvar mal-nil (make-mal-nil nil)) +(defvar mal-true (make-mal-boolean t)) +(defvar mal-false (make-mal-boolean nil)) + ;; Generic type (defvar any) @@ -162,20 +178,19 @@ (typecase value (number (make-mal-number value)) ;; This needs to before symbol since nil is a symbol - (null (funcall (cond - (booleanp #'make-mal-boolean) - (listp #'make-mal-list) - (t #'make-mal-nil)) - value)) + (null (cond + (booleanp mal-false) + (listp (make-mal-list nil)) + (t mal-nil))) ;; This needs to before symbol since t, nil are symbols - (boolean (make-mal-boolean value)) + (boolean (if value mal-true mal-false)) (symbol (make-mal-symbol (symbol-name value))) (keyword (make-mal-keyword value)) (string (make-mal-string value)) (list (make-mal-list (map 'list #'wrap-value value))) (vector (make-mal-vector (map 'vector #'wrap-value value))) (hash-table (make-mal-hash-map (wrap-hash-value value))) - (null (make-mal-nil value)))) + (null mal-nil))) (defun unwrap-value (value) (switch-mal-type value From 1d0a626d1f34017a5c76117e1eec2690c303a121 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 22:03:54 +0530 Subject: [PATCH 0122/2308] Move exports from types package to the top --- clisp/types.lisp | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/clisp/types.lisp b/clisp/types.lisp index c31293c531..38ac255c59 100644 --- a/clisp/types.lisp +++ b/clisp/types.lisp @@ -10,17 +10,51 @@ ;; Mal values :number + :make-mal-number + :mal-number-p + :boolean + :mal-boolean-p + :nil + :mal-nil-p + :string + :make-mal-string + :mal-string-p + :symbol + :make-mal-symbol + :mal-symbol-p + :keyword + :make-mal-keyword + :mal-keyword-p + :list + :make-mal-list + :mal-list-p + :vector + :make-mal-vector + :mal-vector-p + :hash-map + :make-mal-hash-map + :mal-hash-map-p + :atom + :make-mal-atom + :mal-atom-p + :fn + :make-mal-fn + :mal-fn-p + :builtin-fn + :make-mal-builtin-fn + :mal-builtin-fn-p + :any ;; Singleton values @@ -83,10 +117,7 @@ (defun ,predicate (value) (when (typep value 'mal-data) - (equal (mal-data-type value) ',type))) - - (export ',constructor) - (export ',predicate)))) + (equal (mal-data-type value) ',type)))))) (define-mal-type number) (define-mal-type symbol) From 3283e402d9c50c89a56bc15300877f2b4d290f42 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 22:10:14 +0530 Subject: [PATCH 0123/2308] Compile clisp files before running This step gives really good speedups, there still seem to be some bottlenecks around evaluation which need attention --- .gitignore | 2 ++ Makefile | 2 +- clisp/Makefile | 3 +++ clisp/run | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index aab935dbf4..3571dcf1bc 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,5 @@ tcl/mal.tcl vb/*.exe vb/*.dll vimscript/mal.vim +clisp/*.fas +clisp/*.lib diff --git a/Makefile b/Makefile index f1f54fdcd9..f1727fb9b7 100644 --- a/Makefile +++ b/Makefile @@ -151,7 +151,7 @@ d_STEP_TO_PROG = d/$($(1)) chuck_STEP_TO_PROG = chuck/$($(1)).ck clojure_STEP_TO_PROG = clojure/target/$($(1)).jar coffee_STEP_TO_PROG = coffee/$($(1)).coffee -clisp_STEP_TO_PROG = clisp/$($(1)).lisp +clisp_STEP_TO_PROG = clisp/$($(1)).fas cpp_STEP_TO_PROG = cpp/$($(1)) crystal_STEP_TO_PROG = crystal/$($(1)) cs_STEP_TO_PROG = cs/$($(1)).exe diff --git a/clisp/Makefile b/clisp/Makefile index 102683966b..18c59805d4 100644 --- a/clisp/Makefile +++ b/clisp/Makefile @@ -7,6 +7,9 @@ all: .PHONY: stats +step%.fas : step%.lisp dependencies.lisp utils.lisp types.lisp env.lisp printer.lisp reader.lisp + clisp -q -c $< + stats: $(SOURCES) @wc $^ @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/clisp/run b/clisp/run index 7950b67b57..95220201e3 100755 --- a/clisp/run +++ b/clisp/run @@ -1,2 +1,2 @@ #!/bin/bash -exec clisp $(dirname $0)/${STEP:-stepA_mal}.lisp "${@}" +exec clisp $(dirname $0)/${STEP:-stepA_mal}.fas "${@}" From d65385bf2d9f3fe181b1f0436380c4e9e4ae9408 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 23:22:57 +0530 Subject: [PATCH 0124/2308] Use only clisp specific features --- clisp/types.lisp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clisp/types.lisp b/clisp/types.lisp index 38ac255c59..d552d2ec98 100644 --- a/clisp/types.lisp +++ b/clisp/types.lisp @@ -194,8 +194,7 @@ (defun hash-mal-value (value) (sxhash (mal-data-value value))) -#+sbcl (sb-ext:define-hash-table-test mal-value= hash-mal-value) -#+clisp (ext:define-hash-table-test mal-value= mal-value= hash-mal-value) +(ext:define-hash-table-test mal-value= mal-value= hash-mal-value) (defun wrap-hash-value (value) (let ((new-hash-table (make-hash-table :test 'mal-value=))) From abb46bb21de6cba61ed0ca745df0cfffdc414046 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 23:23:25 +0530 Subject: [PATCH 0125/2308] Add readline integration support to the REPL --- clisp/step0_repl.lisp | 51 +++++++++++++++++++++++---- clisp/step1_read_print.lisp | 51 ++++++++++++++++++++++----- clisp/step2_eval.lisp | 53 +++++++++++++++++++++++----- clisp/step3_env.lisp | 53 +++++++++++++++++++++++----- clisp/step4_if_fn_do.lisp | 47 +++++++++++++++++++++---- clisp/step5_tco.lisp | 47 +++++++++++++++++++++---- clisp/step6_file.lisp | 70 ++++++++++++++++++++++++++++--------- clisp/step7_quote.lisp | 70 ++++++++++++++++++++++++++++--------- clisp/step8_macros.lisp | 70 ++++++++++++++++++++++++++++--------- clisp/step9_try.lisp | 70 ++++++++++++++++++++++++++++--------- clisp/stepA_mal.lisp | 70 ++++++++++++++++++++++++++++--------- 11 files changed, 522 insertions(+), 130 deletions(-) diff --git a/clisp/step0_repl.lisp b/clisp/step0_repl.lisp index 6d031cf7b4..992a29810c 100644 --- a/clisp/step0_repl.lisp +++ b/clisp/step0_repl.lisp @@ -1,5 +1,6 @@ (defpackage :mal - (:use :common-lisp)) + (:use :common-lisp + :readline)) (in-package :mal) @@ -16,13 +17,49 @@ (mal-print (mal-eval (mal-read string) (make-hash-table :test #'equal)))) -(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) - (format out-stream prompt) - (force-output out-stream) - (read-line in-stream nil)) +;; Readline setup +;;; The test runner sets this environment variable, in which case we do +;;; use readline since tests do not work with the readline interface +(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) + +(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) + ".mal-clisp-history"))) + +(defun load-history () + (readline:read-history *history-file*)) + +(defun save-history () + (readline:write-history *history-file*)) + +;; Setup history +(when use-readline-p + (load-history)) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (let ((input (if use-readline-p + (readline:readline prompt) + (raw-input prompt)))) + (when (and use-readline-p + input + (not (zerop (length input)))) + (readline:add-history input)) + input)) + +(defun mal-writeline (string) + (when string + (write-line string))) (defun main () - (loop do (let ((line (readline "user> "))) - (if line (write-line (rep line)) (return))))) + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return)))) + (when use-readline-p + (save-history))) (main) diff --git a/clisp/step1_read_print.lisp b/clisp/step1_read_print.lisp index e9e602aa68..c3c7358ceb 100644 --- a/clisp/step1_read_print.lisp +++ b/clisp/step1_read_print.lisp @@ -1,7 +1,10 @@ (require "dependencies") (defpackage :mal - (:use :common-lisp :reader :printer)) + (:use :common-lisp + :readline + :reader + :printer)) (in-package :mal) @@ -23,17 +26,49 @@ "~a" condition)))) -(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) - (format out-stream prompt) - (force-output out-stream) - (read-line in-stream nil)) +;; Readline setup +;;; The test runner sets this environment variable, in which case we do +;;; use readline since tests do not work with the readline interface +(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defun writeline (string) +(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) + ".mal-clisp-history"))) + +(defun load-history () + (readline:read-history *history-file*)) + +(defun save-history () + (readline:write-history *history-file*)) + +;; Setup history +(when use-readline-p + (load-history)) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (let ((input (if use-readline-p + (readline:readline prompt) + (raw-input prompt)))) + (when (and use-readline-p + input + (not (zerop (length input)))) + (readline:add-history input)) + input)) + +(defun mal-writeline (string) (when string (write-line string))) (defun main () - (loop do (let ((line (readline "user> "))) - (if line (writeline (rep line)) (return))))) + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return)))) + (when use-readline-p + (save-history))) (main) diff --git a/clisp/step2_eval.lisp b/clisp/step2_eval.lisp index f7ef76baf5..452da8fc05 100644 --- a/clisp/step2_eval.lisp +++ b/clisp/step2_eval.lisp @@ -1,7 +1,12 @@ (require "dependencies") (defpackage :mal - (:use :common-lisp :types :env :reader :printer)) + (:use :common-lisp + :readline + :types + :env + :reader + :printer)) (in-package :mal) @@ -90,17 +95,49 @@ "~a" condition)))) -(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) - (format out-stream prompt) - (force-output out-stream) - (read-line in-stream nil)) +;; Readline setup +;;; The test runner sets this environment variable, in which case we do +;;; use readline since tests do not work with the readline interface +(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defun writeline (string) +(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) + ".mal-clisp-history"))) + +(defun load-history () + (readline:read-history *history-file*)) + +(defun save-history () + (readline:write-history *history-file*)) + +;; Setup history +(when use-readline-p + (load-history)) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (let ((input (if use-readline-p + (readline:readline prompt) + (raw-input prompt)))) + (when (and use-readline-p + input + (not (zerop (length input)))) + (readline:add-history input)) + input)) + +(defun mal-writeline (string) (when string (write-line string))) (defun main () - (loop do (let ((line (readline "user> "))) - (if line (writeline (rep line)) (return))))) + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return)))) + (when use-readline-p + (save-history))) (main) diff --git a/clisp/step3_env.lisp b/clisp/step3_env.lisp index bd9b81de42..c5bbb1394f 100644 --- a/clisp/step3_env.lisp +++ b/clisp/step3_env.lisp @@ -1,7 +1,12 @@ (require "dependencies") (defpackage :mal - (:use :common-lisp :types :env :reader :printer)) + (:use :common-lisp + :readline + :types + :env + :reader + :printer)) (in-package :mal) @@ -110,17 +115,49 @@ "~a" condition)))) -(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) - (format out-stream prompt) - (force-output out-stream) - (read-line in-stream nil)) +;; Readline setup +;;; The test runner sets this environment variable, in which case we do +;;; use readline since tests do not work with the readline interface +(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defun writeline (string) +(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) + ".mal-clisp-history"))) + +(defun load-history () + (readline:read-history *history-file*)) + +(defun save-history () + (readline:write-history *history-file*)) + +;; Setup history +(when use-readline-p + (load-history)) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (let ((input (if use-readline-p + (readline:readline prompt) + (raw-input prompt)))) + (when (and use-readline-p + input + (not (zerop (length input)))) + (readline:add-history input)) + input)) + +(defun mal-writeline (string) (when string (write-line string))) (defun main () - (loop do (let ((line (readline "user> "))) - (if line (writeline (rep line)) (return))))) + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return)))) + (when use-readline-p + (save-history))) (main) diff --git a/clisp/step4_if_fn_do.lisp b/clisp/step4_if_fn_do.lisp index 387d991d15..f504188d91 100644 --- a/clisp/step4_if_fn_do.lisp +++ b/clisp/step4_if_fn_do.lisp @@ -2,6 +2,7 @@ (defpackage :mal (:use :common-lisp + :readline :types :env :reader @@ -131,17 +132,49 @@ (rep "(def! not (fn* (a) (if a false true)))") -(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) - (format out-stream prompt) - (force-output out-stream) - (read-line in-stream nil)) +;; Readline setup +;;; The test runner sets this environment variable, in which case we do +;;; use readline since tests do not work with the readline interface +(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defun writeline (string) +(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) + ".mal-clisp-history"))) + +(defun load-history () + (readline:read-history *history-file*)) + +(defun save-history () + (readline:write-history *history-file*)) + +;; Setup history +(when use-readline-p + (load-history)) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (let ((input (if use-readline-p + (readline:readline prompt) + (raw-input prompt)))) + (when (and use-readline-p + input + (not (zerop (length input)))) + (readline:add-history input)) + input)) + +(defun mal-writeline (string) (when string (write-line string))) (defun main () - (loop do (let ((line (readline "user> "))) - (if line (writeline (rep line)) (return))))) + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return)))) + (when use-readline-p + (save-history))) (main) diff --git a/clisp/step5_tco.lisp b/clisp/step5_tco.lisp index 2c22c016bb..feca227239 100644 --- a/clisp/step5_tco.lisp +++ b/clisp/step5_tco.lisp @@ -2,6 +2,7 @@ (defpackage :mal (:use :common-lisp + :readline :types :env :reader @@ -143,17 +144,49 @@ (rep "(def! not (fn* (a) (if a false true)))") -(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) - (format out-stream prompt) - (force-output out-stream) - (read-line in-stream nil)) +;; Readline setup +;;; The test runner sets this environment variable, in which case we do +;;; use readline since tests do not work with the readline interface +(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defun writeline (string) +(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) + ".mal-clisp-history"))) + +(defun load-history () + (readline:read-history *history-file*)) + +(defun save-history () + (readline:write-history *history-file*)) + +;; Setup history +(when use-readline-p + (load-history)) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (let ((input (if use-readline-p + (readline:readline prompt) + (raw-input prompt)))) + (when (and use-readline-p + input + (not (zerop (length input)))) + (readline:add-history input)) + input)) + +(defun mal-writeline (string) (when string (write-line string))) (defun main () - (loop do (let ((line (readline "user> "))) - (if line (writeline (rep line)) (return))))) + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return)))) + (when use-readline-p + (save-history))) (main) diff --git a/clisp/step6_file.lisp b/clisp/step6_file.lisp index 466a985f91..d4f5cbf030 100644 --- a/clisp/step6_file.lisp +++ b/clisp/step6_file.lisp @@ -2,6 +2,7 @@ (defpackage :mal (:use :common-lisp + :readline :types :env :reader @@ -150,26 +151,61 @@ (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") (rep "(def! *ARGV* (list))") -(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) - (format out-stream prompt) - (force-output out-stream) - (read-line in-stream nil)) +(env:set-env *repl-env* + (types:make-mal-symbol "*ARGV*") + (types:wrap-value (cdr common-lisp-user::*args*) + :listp t)) + +;; Readline setup +;;; The test runner sets this environment variable, in which case we do +;;; use readline since tests do not work with the readline interface +(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) + +(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) + ".mal-clisp-history"))) + +(defun load-history () + (readline:read-history *history-file*)) + +(defun save-history () + (readline:write-history *history-file*)) + +;; Setup history +(when use-readline-p + (load-history)) -(defun writeline (string) +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (let ((input (if use-readline-p + (readline:readline prompt) + (raw-input prompt)))) + (when (and use-readline-p + input + (not (zerop (length input)))) + (readline:add-history input)) + input)) + +(defun mal-writeline (string) (when string (write-line string))) -(defun main () - (loop do (let ((line (readline "user> "))) - (if line (writeline (rep line)) (return))))) +(defun repl () + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return)))) + (when use-readline-p + (save-history))) -(env:set-env *repl-env* - (types:make-mal-symbol "*ARGV*") - (types:wrap-value (cdr common-lisp-user::*args*) - :listp t)) +(defun main () + (if (null common-lisp-user::*args*) + (repl) + (rep (format nil + "(load-file \"~a\")" + (car common-lisp-user::*args*))))) -(if (null common-lisp-user::*args*) - (main) - (rep (format nil - "(load-file \"~a\")" - (car common-lisp-user::*args*)))) +(main) diff --git a/clisp/step7_quote.lisp b/clisp/step7_quote.lisp index cdbe1ebc0e..32dc5cce56 100644 --- a/clisp/step7_quote.lisp +++ b/clisp/step7_quote.lisp @@ -2,6 +2,7 @@ (defpackage :mal (:use :common-lisp + :readline :types :env :reader @@ -188,26 +189,61 @@ (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") (rep "(def! *ARGV* (list))") -(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) - (format out-stream prompt) - (force-output out-stream) - (read-line in-stream nil)) +(env:set-env *repl-env* + (types:make-mal-symbol "*ARGV*") + (types:wrap-value (cdr common-lisp-user::*args*) + :listp t)) + +;; Readline setup +;;; The test runner sets this environment variable, in which case we do +;;; use readline since tests do not work with the readline interface +(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) + +(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) + ".mal-clisp-history"))) + +(defun load-history () + (readline:read-history *history-file*)) + +(defun save-history () + (readline:write-history *history-file*)) + +;; Setup history +(when use-readline-p + (load-history)) -(defun writeline (string) +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (let ((input (if use-readline-p + (readline:readline prompt) + (raw-input prompt)))) + (when (and use-readline-p + input + (not (zerop (length input)))) + (readline:add-history input)) + input)) + +(defun mal-writeline (string) (when string (write-line string))) -(defun main () - (loop do (let ((line (readline "user> "))) - (if line (writeline (rep line)) (return))))) +(defun repl () + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return)))) + (when use-readline-p + (save-history))) -(env:set-env *repl-env* - (types:make-mal-symbol "*ARGV*") - (types:wrap-value (cdr common-lisp-user::*args*) - :listp t)) +(defun main () + (if (null common-lisp-user::*args*) + (repl) + (rep (format nil + "(load-file \"~a\")" + (car common-lisp-user::*args*))))) -(if (null common-lisp-user::*args*) - (main) - (rep (format nil - "(load-file \"~a\")" - (car common-lisp-user::*args*)))) +(main) diff --git a/clisp/step8_macros.lisp b/clisp/step8_macros.lisp index 97621a20ca..84a6d3af39 100644 --- a/clisp/step8_macros.lisp +++ b/clisp/step8_macros.lisp @@ -2,6 +2,7 @@ (defpackage :mal (:use :common-lisp + :readline :types :env :reader @@ -238,26 +239,61 @@ (rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") (rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") -(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) - (format out-stream prompt) - (force-output out-stream) - (read-line in-stream nil)) +(env:set-env *repl-env* + (types:make-mal-symbol "*ARGV*") + (types:wrap-value (cdr common-lisp-user::*args*) + :listp t)) + +;; Readline setup +;;; The test runner sets this environment variable, in which case we do +;;; use readline since tests do not work with the readline interface +(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) + +(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) + ".mal-clisp-history"))) + +(defun load-history () + (readline:read-history *history-file*)) + +(defun save-history () + (readline:write-history *history-file*)) + +;; Setup history +(when use-readline-p + (load-history)) -(defun writeline (string) +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (let ((input (if use-readline-p + (readline:readline prompt) + (raw-input prompt)))) + (when (and use-readline-p + input + (not (zerop (length input)))) + (readline:add-history input)) + input)) + +(defun mal-writeline (string) (when string (write-line string))) -(defun main () - (loop do (let ((line (readline "user> "))) - (if line (writeline (rep line)) (return))))) +(defun repl () + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return)))) + (when use-readline-p + (save-history))) -(env:set-env *repl-env* - (types:make-mal-symbol "*ARGV*") - (types:wrap-value (cdr common-lisp-user::*args*) - :listp t)) +(defun main () + (if (null common-lisp-user::*args*) + (repl) + (rep (format nil + "(load-file \"~a\")" + (car common-lisp-user::*args*))))) -(if (null common-lisp-user::*args*) - (main) - (rep (format nil - "(load-file \"~a\")" - (car common-lisp-user::*args*)))) +(main) diff --git a/clisp/step9_try.lisp b/clisp/step9_try.lisp index 77db07bb5c..ee820081ab 100644 --- a/clisp/step9_try.lisp +++ b/clisp/step9_try.lisp @@ -2,6 +2,7 @@ (defpackage :mal (:use :common-lisp + :readline :types :env :reader @@ -265,26 +266,61 @@ (rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") (rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") -(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) - (format out-stream prompt) - (force-output out-stream) - (read-line in-stream nil)) +(env:set-env *repl-env* + (types:make-mal-symbol "*ARGV*") + (types:wrap-value (cdr common-lisp-user::*args*) + :listp t)) + +;; Readline setup +;;; The test runner sets this environment variable, in which case we do +;;; use readline since tests do not work with the readline interface +(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) + +(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) + ".mal-clisp-history"))) + +(defun load-history () + (readline:read-history *history-file*)) + +(defun save-history () + (readline:write-history *history-file*)) + +;; Setup history +(when use-readline-p + (load-history)) -(defun writeline (string) +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (let ((input (if use-readline-p + (readline:readline prompt) + (raw-input prompt)))) + (when (and use-readline-p + input + (not (zerop (length input)))) + (readline:add-history input)) + input)) + +(defun mal-writeline (string) (when string (write-line string))) -(defun main () - (loop do (let ((line (readline "user> "))) - (if line (writeline (rep line)) (return))))) +(defun repl () + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return)))) + (when use-readline-p + (save-history))) -(env:set-env *repl-env* - (types:make-mal-symbol "*ARGV*") - (types:wrap-value (cdr common-lisp-user::*args*) - :listp t)) +(defun main () + (if (null common-lisp-user::*args*) + (repl) + (rep (format nil + "(load-file \"~a\")" + (car common-lisp-user::*args*))))) -(if (null common-lisp-user::*args*) - (main) - (rep (format nil - "(load-file \"~a\")" - (car common-lisp-user::*args*)))) +(main) diff --git a/clisp/stepA_mal.lisp b/clisp/stepA_mal.lisp index 9895fe20ee..199e6f7eee 100644 --- a/clisp/stepA_mal.lisp +++ b/clisp/stepA_mal.lisp @@ -2,6 +2,7 @@ (defpackage :mal (:use :common-lisp + :readline :types :env :reader @@ -268,26 +269,61 @@ (rep "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))") (rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))") -(defun readline (prompt &optional (in-stream *standard-input*) (out-stream *standard-output*)) - (format out-stream prompt) - (force-output out-stream) - (read-line in-stream nil)) +(env:set-env *repl-env* + (types:make-mal-symbol "*ARGV*") + (types:wrap-value (cdr common-lisp-user::*args*) + :listp t)) + +;; Readline setup +;;; The test runner sets this environment variable, in which case we do +;;; use readline since tests do not work with the readline interface +(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) + +(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) + ".mal-clisp-history"))) + +(defun load-history () + (readline:read-history *history-file*)) + +(defun save-history () + (readline:write-history *history-file*)) + +;; Setup history +(when use-readline-p + (load-history)) -(defun writeline (string) +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (let ((input (if use-readline-p + (readline:readline prompt) + (raw-input prompt)))) + (when (and use-readline-p + input + (not (zerop (length input)))) + (readline:add-history input)) + input)) + +(defun mal-writeline (string) (when string (write-line string))) -(defun main () - (loop do (let ((line (readline "user> "))) - (if line (writeline (rep line)) (return))))) +(defun repl () + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return)))) + (when use-readline-p + (save-history))) -(env:set-env *repl-env* - (types:make-mal-symbol "*ARGV*") - (types:wrap-value (cdr common-lisp-user::*args*) - :listp t)) +(defun main () + (if (null common-lisp-user::*args*) + (repl) + (rep (format nil + "(load-file \"~a\")" + (car common-lisp-user::*args*))))) -(if (null common-lisp-user::*args*) - (main) - (rep (format nil - "(load-file \"~a\")" - (car common-lisp-user::*args*)))) +(main) From cfa139a7a1e253a286f57be17a8cb6cc6d2aa521 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 28 Aug 2016 23:33:49 +0530 Subject: [PATCH 0126/2308] Add make target to clean generated files --- clisp/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clisp/Makefile b/clisp/Makefile index 18c59805d4..067a1bc611 100644 --- a/clisp/Makefile +++ b/clisp/Makefile @@ -10,6 +10,9 @@ all: step%.fas : step%.lisp dependencies.lisp utils.lisp types.lisp env.lisp printer.lisp reader.lisp clisp -q -c $< +clean: + rm *.fas *.lib + stats: $(SOURCES) @wc $^ @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" From c1ad20675e58373e8161858fba28fa79a0f9121c Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 29 Aug 2016 16:37:00 +0530 Subject: [PATCH 0127/2308] Fix path of history file for readline --- clisp/step0_repl.lisp | 2 +- clisp/step1_read_print.lisp | 2 +- clisp/step2_eval.lisp | 2 +- clisp/step3_env.lisp | 2 +- clisp/step4_if_fn_do.lisp | 2 +- clisp/step5_tco.lisp | 2 +- clisp/step6_file.lisp | 2 +- clisp/step7_quote.lisp | 2 +- clisp/step8_macros.lisp | 2 +- clisp/step9_try.lisp | 2 +- clisp/stepA_mal.lisp | 4 ++-- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/clisp/step0_repl.lisp b/clisp/step0_repl.lisp index 992a29810c..0e07af40dd 100644 --- a/clisp/step0_repl.lisp +++ b/clisp/step0_repl.lisp @@ -22,7 +22,7 @@ ;;; use readline since tests do not work with the readline interface (defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) +(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) ".mal-clisp-history"))) (defun load-history () diff --git a/clisp/step1_read_print.lisp b/clisp/step1_read_print.lisp index c3c7358ceb..9b7b590693 100644 --- a/clisp/step1_read_print.lisp +++ b/clisp/step1_read_print.lisp @@ -31,7 +31,7 @@ ;;; use readline since tests do not work with the readline interface (defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) +(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) ".mal-clisp-history"))) (defun load-history () diff --git a/clisp/step2_eval.lisp b/clisp/step2_eval.lisp index 452da8fc05..98dcadd0b7 100644 --- a/clisp/step2_eval.lisp +++ b/clisp/step2_eval.lisp @@ -100,7 +100,7 @@ ;;; use readline since tests do not work with the readline interface (defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) +(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) ".mal-clisp-history"))) (defun load-history () diff --git a/clisp/step3_env.lisp b/clisp/step3_env.lisp index c5bbb1394f..41cc5b1331 100644 --- a/clisp/step3_env.lisp +++ b/clisp/step3_env.lisp @@ -120,7 +120,7 @@ ;;; use readline since tests do not work with the readline interface (defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) +(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) ".mal-clisp-history"))) (defun load-history () diff --git a/clisp/step4_if_fn_do.lisp b/clisp/step4_if_fn_do.lisp index f504188d91..910987dcd9 100644 --- a/clisp/step4_if_fn_do.lisp +++ b/clisp/step4_if_fn_do.lisp @@ -137,7 +137,7 @@ ;;; use readline since tests do not work with the readline interface (defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) +(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) ".mal-clisp-history"))) (defun load-history () diff --git a/clisp/step5_tco.lisp b/clisp/step5_tco.lisp index feca227239..bdc9495092 100644 --- a/clisp/step5_tco.lisp +++ b/clisp/step5_tco.lisp @@ -149,7 +149,7 @@ ;;; use readline since tests do not work with the readline interface (defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) +(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) ".mal-clisp-history"))) (defun load-history () diff --git a/clisp/step6_file.lisp b/clisp/step6_file.lisp index d4f5cbf030..317438d5ce 100644 --- a/clisp/step6_file.lisp +++ b/clisp/step6_file.lisp @@ -161,7 +161,7 @@ ;;; use readline since tests do not work with the readline interface (defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) +(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) ".mal-clisp-history"))) (defun load-history () diff --git a/clisp/step7_quote.lisp b/clisp/step7_quote.lisp index 32dc5cce56..5e4255538b 100644 --- a/clisp/step7_quote.lisp +++ b/clisp/step7_quote.lisp @@ -199,7 +199,7 @@ ;;; use readline since tests do not work with the readline interface (defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) +(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) ".mal-clisp-history"))) (defun load-history () diff --git a/clisp/step8_macros.lisp b/clisp/step8_macros.lisp index 84a6d3af39..cc96728a39 100644 --- a/clisp/step8_macros.lisp +++ b/clisp/step8_macros.lisp @@ -249,7 +249,7 @@ ;;; use readline since tests do not work with the readline interface (defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) +(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) ".mal-clisp-history"))) (defun load-history () diff --git a/clisp/step9_try.lisp b/clisp/step9_try.lisp index ee820081ab..c050585363 100644 --- a/clisp/step9_try.lisp +++ b/clisp/step9_try.lisp @@ -276,7 +276,7 @@ ;;; use readline since tests do not work with the readline interface (defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) +(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) ".mal-clisp-history"))) (defun load-history () diff --git a/clisp/stepA_mal.lisp b/clisp/stepA_mal.lisp index 199e6f7eee..c0822d1fe6 100644 --- a/clisp/stepA_mal.lisp +++ b/clisp/stepA_mal.lisp @@ -279,8 +279,8 @@ ;;; use readline since tests do not work with the readline interface (defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) -(defvar *history-file* (file-namestring (merge-pathnames (user-homedir-pathname) - ".mal-clisp-history"))) +(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) + ".mal-clisp-history"))) (defun load-history () (readline:read-history *history-file*)) From 32dda0de2143a45d45fa3fd920e4f1b879d45082 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 29 Aug 2016 16:37:11 +0530 Subject: [PATCH 0128/2308] Print startup header when REPL starts --- clisp/stepA_mal.lisp | 1 + 1 file changed, 1 insertion(+) diff --git a/clisp/stepA_mal.lisp b/clisp/stepA_mal.lisp index c0822d1fe6..ef5bfb8937 100644 --- a/clisp/stepA_mal.lisp +++ b/clisp/stepA_mal.lisp @@ -312,6 +312,7 @@ (write-line string))) (defun repl () + (rep "(println (str \"Mal [\" *host-language* \"]\"))"); (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) From 5f6ad97006b6bb5c36d96c499c932a73666c4ef3 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 29 Aug 2016 20:10:42 +0530 Subject: [PATCH 0129/2308] Intern symbols in mal-user package while unwraping MAL values --- clisp/types.lisp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/clisp/types.lisp b/clisp/types.lisp index d552d2ec98..fa1f0e1261 100644 --- a/clisp/types.lisp +++ b/clisp/types.lisp @@ -1,3 +1,7 @@ +;; Dummy package where MAL variables are interned +(defpackage :mal-user + (:use :common-lisp)) + (defpackage :types (:use :common-lisp) (:export :mal-value= @@ -75,6 +79,7 @@ ;; Helpers :wrap-value + :unwrap-value :apply-unwrapped-values :apply-unwrapped-values-prefer-bool :switch-mal-type)) @@ -205,6 +210,7 @@ new-hash-table)) (defun wrap-value (value &key booleanp listp) + "Convert a Common Lisp value to MAL value" (typecase value (number (make-mal-number value)) ;; This needs to before symbol since nil is a symbol @@ -223,6 +229,7 @@ (null mal-nil))) (defun unwrap-value (value) + "Convert a MAL value to native Common Lisp value" (switch-mal-type value (list (mapcar #'unwrap-value (mal-data-value value))) (vector (map 'vector #'unwrap-value (mal-data-value value))) @@ -233,6 +240,15 @@ do (setf (gethash (mal-data-value key) hash-table) (mal-data-value (gethash key hash-map-value)))) hash-table)) + ;; Unfortunately below means even symbols that user indented to use + ;; from the common lisp are interned in lowercase thus runtime + ;; will not find them as such users need to explicitly upcase the + ;; symbols from common lisp + (symbol (intern (mal-data-value value) :mal-user)) + ;; In case of a keyword strip the first colon, and intern the symbol in + ;; keyword package + (keyword (intern (string-upcase (subseq (mal-data-value value) 1)) + :keyword)) (any (mal-data-value value)))) (defun apply-unwrapped-values (op &rest values) From 798b5717f65a6cf3f345e1394248df1680590dd3 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 29 Aug 2016 20:11:22 +0530 Subject: [PATCH 0130/2308] Add interop - clisp-eval: allows executing Common Lisp - define-builtin: allows defining builtin functions on the fly --- clisp/Makefile | 2 +- clisp/core.lisp | 19 +++++++++- clisp/stepA_mal.lisp | 1 + clisp/tests/stepA_mal.mal | 75 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 clisp/tests/stepA_mal.mal diff --git a/clisp/Makefile b/clisp/Makefile index 067a1bc611..5e08ae3f61 100644 --- a/clisp/Makefile +++ b/clisp/Makefile @@ -7,7 +7,7 @@ all: .PHONY: stats -step%.fas : step%.lisp dependencies.lisp utils.lisp types.lisp env.lisp printer.lisp reader.lisp +step%.fas : step%.lisp dependencies.lisp utils.lisp types.lisp env.lisp printer.lisp reader.lisp core.lisp clisp -q -c $< clean: diff --git a/clisp/core.lisp b/clisp/core.lisp index b54c451942..a477967e8e 100644 --- a/clisp/core.lisp +++ b/clisp/core.lisp @@ -382,4 +382,21 @@ (cons (types:make-mal-symbol "meta") (types:make-mal-builtin-fn (lambda (value) (or (types:mal-data-meta value) - types:mal-nil)))))) + types:mal-nil)))) + + ;; Since a nil in clisp may mean an empty list or boolean false or simply nil, the + ;; caller can specify the preferred type while evaluating an expression + (cons (types:make-mal-symbol "clisp-eval") + (types:make-mal-builtin-fn (lambda (code &optional booleanp listp) + (types:wrap-value (eval (read-from-string (types:mal-data-value code))) + :booleanp (and booleanp (types:mal-data-value booleanp)) + :listp (and listp (types:mal-data-value listp)))))) + + (cons (types:make-mal-symbol "define-builtin") + (types:make-mal-builtin-fn (lambda (arglist &rest body) + (let* ((func-args (types:unwrap-value arglist)) + (func-body (mapcar #'types:unwrap-value body)) + (func (eval `(lambda ,func-args ,@func-body)))) + (types:make-mal-builtin-fn (lambda (&rest args) + (types:wrap-value (apply func + (mapcar #'types:unwrap-value args))))))))))) diff --git a/clisp/stepA_mal.lisp b/clisp/stepA_mal.lisp index ef5bfb8937..9f81168aae 100644 --- a/clisp/stepA_mal.lisp +++ b/clisp/stepA_mal.lisp @@ -268,6 +268,7 @@ (rep "(def! *gensym-counter* (atom 0))") (rep "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))") (rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))") +(rep "(defmacro! defbuiltin! (fn* (arglist & forms) `(define-builtin '~arglist '~@forms)))") (env:set-env *repl-env* (types:make-mal-symbol "*ARGV*") diff --git a/clisp/tests/stepA_mal.mal b/clisp/tests/stepA_mal.mal new file mode 100644 index 0000000000..2bb5d54f39 --- /dev/null +++ b/clisp/tests/stepA_mal.mal @@ -0,0 +1,75 @@ +;; Testing clisp interop + +(clisp-eval "42") +;=>42 + +(clisp-eval "(+ 1 1)") +;=>2 + +(clisp-eval "(setq foo 1 bar 2 baz 3)") + +(clisp-eval "(list foo bar baz)") +;=>(1 2 3) + +(clisp-eval "7") +;=>7 + +;; +;; Testing boolean flag +(clisp-eval "(= 123 123)" true) +;=>true + +(clisp-eval "(= 123 456)") +;=>nil + +(clisp-eval "(= 123 456)" true) +;=>false + +;; +;; Testing list flag +(clisp-eval "(last nil)" false true) +;=>() + +(clisp-eval "nil" false true) +;=>() + +(clisp-eval "nil") +;=>nil + +;; +;; Testing creation of Common Lisp Objects +(clisp-eval "#(1 2)") +;=>[1 2] + +;;; Not testing with elements since order in hashtable cannot be guaranteed +(clisp-eval "(make-hash-table)") +;=>{} + +(clisp-eval "(defun redundant-identity (x) x)")) +;=>REDUNDANT-IDENTITY + +(clisp-eval "(redundant-identity 2)")) +;=>2 + +(clisp-eval "(defun range (max &key (min 0) (step 1)) (loop for n from min below max by step collect n))") +;=>RANGE + +(clisp-eval "(range 10 :min 0 :step 1)") +;=>(0 1 2 3 4 5 6 7 8 9) + +(clisp-eval "(mapcar #'1+ (range 10 :min 0 :step 1))") +;=>(1 2 3 4 5 6 7 8 9 10) + +;; +;; Testing defbuiltin! +(def! make-native-hash-map (defbuiltin! (&REST args) (MAKE-HASH-TABLE :initial-contents (LOOP FOR (KEY VALUE) ON args BY (FUNCTION CDDR) COLLECT (CONS KEY VALUE))))) +;=># + +(make-native-hash-map 1 2) +;=>{1 2} + +(def! native-range (defbuiltin! (max &KEY (MIN 0) (STEP 1)) (LOOP FOR n FROM MIN BELOW max BY STEP COLLECT n))) +;=># + +(native-range 10 :MIN 2 :STEP 2) +;=>(2 4 6 8) \ No newline at end of file From 68511d82dc1a293116b69c4d68f8facb0c1287d0 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 29 Aug 2016 20:14:15 +0530 Subject: [PATCH 0131/2308] Do not start MAL REPL if code is executed in Emacs (specially via SLIME) --- clisp/step0_repl.lisp | 4 +++- clisp/step1_read_print.lisp | 4 +++- clisp/step2_eval.lisp | 4 +++- clisp/step3_env.lisp | 4 +++- clisp/step4_if_fn_do.lisp | 4 +++- clisp/step5_tco.lisp | 4 +++- clisp/step6_file.lisp | 4 +++- clisp/step7_quote.lisp | 4 +++- clisp/step8_macros.lisp | 4 +++- clisp/step9_try.lisp | 4 +++- clisp/stepA_mal.lisp | 4 +++- 11 files changed, 33 insertions(+), 11 deletions(-) diff --git a/clisp/step0_repl.lisp b/clisp/step0_repl.lisp index 0e07af40dd..960ed2eef7 100644 --- a/clisp/step0_repl.lisp +++ b/clisp/step0_repl.lisp @@ -62,4 +62,6 @@ (when use-readline-p (save-history))) -(main) +;; Do not start REPL inside Emacs +(unless (member :swank *features*) + (main)) diff --git a/clisp/step1_read_print.lisp b/clisp/step1_read_print.lisp index 9b7b590693..f1d2bb9f6e 100644 --- a/clisp/step1_read_print.lisp +++ b/clisp/step1_read_print.lisp @@ -71,4 +71,6 @@ (when use-readline-p (save-history))) -(main) +;; Do not start REPL inside Emacs +(unless (member :swank *features*) + (main)) diff --git a/clisp/step2_eval.lisp b/clisp/step2_eval.lisp index 98dcadd0b7..148b566398 100644 --- a/clisp/step2_eval.lisp +++ b/clisp/step2_eval.lisp @@ -140,4 +140,6 @@ (when use-readline-p (save-history))) -(main) +;; Do not start REPL inside Emacs +(unless (member :swank *features*) + (main)) diff --git a/clisp/step3_env.lisp b/clisp/step3_env.lisp index 41cc5b1331..51abec79da 100644 --- a/clisp/step3_env.lisp +++ b/clisp/step3_env.lisp @@ -160,4 +160,6 @@ (when use-readline-p (save-history))) -(main) +;; Do not start REPL inside Emacs +(unless (member :swank *features*) + (main)) diff --git a/clisp/step4_if_fn_do.lisp b/clisp/step4_if_fn_do.lisp index 910987dcd9..64150ed41f 100644 --- a/clisp/step4_if_fn_do.lisp +++ b/clisp/step4_if_fn_do.lisp @@ -177,4 +177,6 @@ (when use-readline-p (save-history))) -(main) +;; Do not start REPL inside Emacs +(unless (member :swank *features*) + (main)) diff --git a/clisp/step5_tco.lisp b/clisp/step5_tco.lisp index bdc9495092..ce8d98ee2b 100644 --- a/clisp/step5_tco.lisp +++ b/clisp/step5_tco.lisp @@ -189,4 +189,6 @@ (when use-readline-p (save-history))) -(main) +;; Do not start REPL inside Emacs +(unless (member :swank *features*) + (main)) diff --git a/clisp/step6_file.lisp b/clisp/step6_file.lisp index 317438d5ce..52d519a8ff 100644 --- a/clisp/step6_file.lisp +++ b/clisp/step6_file.lisp @@ -203,7 +203,9 @@ (defun main () (if (null common-lisp-user::*args*) - (repl) + ;; Do not start REPL inside Emacs + (unless (member :swank *features*) + (repl)) (rep (format nil "(load-file \"~a\")" (car common-lisp-user::*args*))))) diff --git a/clisp/step7_quote.lisp b/clisp/step7_quote.lisp index 5e4255538b..a96c34ff59 100644 --- a/clisp/step7_quote.lisp +++ b/clisp/step7_quote.lisp @@ -241,7 +241,9 @@ (defun main () (if (null common-lisp-user::*args*) - (repl) + ;; Do not start REPL inside Emacs + (unless (member :swank *features*) + (repl)) (rep (format nil "(load-file \"~a\")" (car common-lisp-user::*args*))))) diff --git a/clisp/step8_macros.lisp b/clisp/step8_macros.lisp index cc96728a39..13d6fb9ba3 100644 --- a/clisp/step8_macros.lisp +++ b/clisp/step8_macros.lisp @@ -291,7 +291,9 @@ (defun main () (if (null common-lisp-user::*args*) - (repl) + ;; Do not start REPL inside Emacs + (unless (member :swank *features*) + (repl)) (rep (format nil "(load-file \"~a\")" (car common-lisp-user::*args*))))) diff --git a/clisp/step9_try.lisp b/clisp/step9_try.lisp index c050585363..79c9d83d83 100644 --- a/clisp/step9_try.lisp +++ b/clisp/step9_try.lisp @@ -318,7 +318,9 @@ (defun main () (if (null common-lisp-user::*args*) - (repl) + ;; Do not start REPL inside Emacs + (unless (member :swank *features*) + (repl)) (rep (format nil "(load-file \"~a\")" (car common-lisp-user::*args*))))) diff --git a/clisp/stepA_mal.lisp b/clisp/stepA_mal.lisp index 9f81168aae..a8341b2cc0 100644 --- a/clisp/stepA_mal.lisp +++ b/clisp/stepA_mal.lisp @@ -323,7 +323,9 @@ (defun main () (if (null common-lisp-user::*args*) - (repl) + ;; Do not start REPL inside Emacs + (unless (member :swank *features*) + (repl)) (rep (format nil "(load-file \"~a\")" (car common-lisp-user::*args*))))) From b0e083743efdb12adb7d66dbd991f976464fcdf5 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 29 Aug 2016 20:14:52 +0530 Subject: [PATCH 0132/2308] Add GNU CLISP to the list of implementations --- README.md | 15 ++++++++++++++- clisp/Makefile | 3 +-- clisp/README.md | 1 - 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7555932094..143fc1ed2b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 58 languages: +Mal is implemented in 59 languages: * Ada * GNU awk @@ -15,6 +15,7 @@ Mal is implemented in 58 languages: * C++ * C# * ChucK +* GNU CLISP * Clojure * CoffeeScript * Crystal @@ -205,6 +206,18 @@ cd chuck ./run ``` +### GNU CLISP + +*The GNU CLISP implementation was created by [Iqbal Ansari](https://github.com/iqbalansari)* + +The implementation has been tested with GNU CLISP v2.49 on Ubuntu 16.04, 14.04 and 12.04 + +``` +cd clisp +make +./run +``` + ### Clojure For the most part the Clojure implementation requires Clojure 1.5, diff --git a/clisp/Makefile b/clisp/Makefile index 5e08ae3f61..1f35911729 100644 --- a/clisp/Makefile +++ b/clisp/Makefile @@ -2,8 +2,7 @@ SOURCES_BASE = utils.lisp types.lisp reader.lisp printer.lisp SOURCES_LISP = env.lisp core.lisp stepA_mal.lisp SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -all: - true +all : stepA_mal.fas .PHONY: stats diff --git a/clisp/README.md b/clisp/README.md index 18109fbd86..120e3d6f3c 100644 --- a/clisp/README.md +++ b/clisp/README.md @@ -1,4 +1,3 @@ Implementation of MAL in Common Lisp - This implementation is not portable and works only with CLISP -- It is terribly (embarrassingly) slow From 47def37ef4d580a7ab1126fcfbd18f74be3ca820 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sun, 4 Sep 2016 20:23:56 -0500 Subject: [PATCH 0133/2308] Basic (C64 v2): step0 This works with cbmbasic from https://github.com/mist64/cbmbasic. The cbmbasic interpreter needs to be on the PATH. The actually sources are *.in.bas which are "compiled" to *.bas using the qb2cbm.sh. qb2cbm.sh translates from a QBasic-ish format to a line numbered source with include files inlined (REM $INCLUDE: 'file.bas'). One additional advantage is that the *.in.bas versions can also be indented and qb2cbm.sh will remove the indenting in the translated code. --- Makefile | 3 +- basic/Makefile | 3 ++ basic/qb2cbm.sh | 73 +++++++++++++++++++++++++++++++++++++++++ basic/readline.in.bas | 30 +++++++++++++++++ basic/run | 2 ++ basic/step0_repl.in.bas | 34 +++++++++++++++++++ 6 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 basic/Makefile create mode 100755 basic/qb2cbm.sh create mode 100644 basic/readline.in.bas create mode 100755 basic/run create mode 100755 basic/step0_repl.in.bas diff --git a/Makefile b/Makefile index f1727fb9b7..a78842794b 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ DOCKERIZE = # Settings # -IMPLS = ada awk bash c d chuck clojure coffee clisp cpp crystal cs erlang elisp \ +IMPLS = ada awk bash basic c d chuck clojure coffee clisp cpp crystal cs erlang elisp \ elixir es6 factor forth fsharp go groovy guile haskell haxe \ io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php plpgsql plsql powershell ps \ @@ -146,6 +146,7 @@ STEP_TEST_FILES = $(strip $(wildcard \ ada_STEP_TO_PROG = ada/$($(1)) awk_STEP_TO_PROG = awk/$($(1)).awk bash_STEP_TO_PROG = bash/$($(1)).sh +basic_STEP_TO_PROG = basic/$($(1)).bas c_STEP_TO_PROG = c/$($(1)) d_STEP_TO_PROG = d/$($(1)) chuck_STEP_TO_PROG = chuck/$($(1)).ck diff --git a/basic/Makefile b/basic/Makefile new file mode 100644 index 0000000000..1e50294f96 --- /dev/null +++ b/basic/Makefile @@ -0,0 +1,3 @@ + +step%.bas: step%.in.bas readline.in.bas + ./qb2cbm.sh $< > $@ diff --git a/basic/qb2cbm.sh b/basic/qb2cbm.sh new file mode 100755 index 0000000000..4a3105e7b5 --- /dev/null +++ b/basic/qb2cbm.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +set -e + +DEBUG=${DEBUG:-} + +infile=$1 + +die () { + echo >&2 "$*" + exit 1 +} + +[ "${infile}" ] || die "Usage: " + +input=$(cat ${infile}) + +[ "${DEBUG}" ] && echo >&2 "Processing includes" + +full="${input}" +declare -A included + +while [[ ${input} =~ REM\ \$INCLUDE:\ \'.*\' ]]; do + full="" + while read -r line; do + if [[ ${line} =~ REM\ \$INCLUDE:\ \'.*\' ]]; then + include=${line#REM \$INCLUDE: \'} + include=${include%\'} + # Only include it once + if [ "${included[${include}]}" ];then + [ "${DEBUG}" ] && echo >&2 "already included: ${include}" + continue + fi + [ "${DEBUG}" ] && echo >&2 "including: ${include}" + included[${include}]="done" + full="${full}\nREM vvv BEGIN '${include}' vvv\n$(cat ${include})\nREM vvv END '${include}' vvv\n" + else + full="${full}${line}\n" + fi + done < <(echo -e "${input}") + input="${full}" +done + + +[ "${DEBUG}" ] && echo >&2 "Renumbering" + +data="" +declare -A labels + +lnum=10 +while read -r line; do + if [[ ${line} =~ ^\ *$ ]]; then + [ "${DEBUG}" ] && echo >&2 "found blank line after $lnum" + data="${data}\n" + continue + elif [[ ${line} =~ ^[A-Za-z_]*:$ ]]; then + label=${line%:} + [ "${DEBUG}" ] && echo >&2 "found label ${label} at $lnum" + labels[${label}]=$lnum + data="${data}${lnum} REM ${label}:\n" + else + data="${data}${lnum} ${line}\n" + fi + lnum=$(( lnum + 10 )) +done < <(echo -e "${input}") + +for label in "${!labels[@]}"; do + [ "${DEBUG}" ] && echo >&2 "Updating label: ${label}" + lnum=${labels[${label}]} + data=$(echo "${data}" | sed "s/\(THEN\|GOTO\|GOSUB\) ${label}\>/\1 ${lnum}: REM \1 ${label}/g") +done + +echo -en "${data}" diff --git a/basic/readline.in.bas b/basic/readline.in.bas new file mode 100644 index 0000000000..3d0999b344 --- /dev/null +++ b/basic/readline.in.bas @@ -0,0 +1,30 @@ +EOF=0 + +REM READLINE(A$) -> R$ +READLINE: + EOF=0 + PROMPT$=A$ + PRINT PROMPT$; + CH$="": LINE$="": CH=0 + READCH: + GET CH$: IF CH$="" THEN READCH + CH=ASC(CH$) + IF (CH=4 OR CH=0) THEN EOF=1: GOTO RL_DONE: REM EOF + IF (CH=127) THEN GOSUB RL_BACKSPACE + IF (CH=127) THEN GOTO READCH + IF (CH<32 OR CH>127) AND CH<>13 THEN READCH + IF LEN(LINE$)<255 AND CH$<>CHR$(13) THEN LINE$=LINE$+CH$ + IF LEN(LINE$)<255 AND CH$<>CHR$(13) THEN GOTO READCH + RL_DONE: + R$=LINE$ + RETURN + + REM Assumes LINE$ has input buffer + RL_BACKSPACE: + IF LEN(LINE$)=0 THEN RL_BACKSPACE_ONCE: + PRINT CHR$(157) + CHR$(157) + " " + CHR$(157) + CHR$(157); + LINE$=LEFT$(LINE$, LEN(LINE$)-1) + RETURN + RL_BACKSPACE_ONCE: + PRINT CHR$(157) + " " + CHR$(157); + RETURN diff --git a/basic/run b/basic/run new file mode 100755 index 0000000000..2fe259b544 --- /dev/null +++ b/basic/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec cbmbasic $(dirname $0)/${STEP:-stepA_mal}.bas "${@}" diff --git a/basic/step0_repl.in.bas b/basic/step0_repl.in.bas new file mode 100755 index 0000000000..706bfd2166 --- /dev/null +++ b/basic/step0_repl.in.bas @@ -0,0 +1,34 @@ +GOTO MAIN + +REM $INCLUDE: 'readline.in.bas' + +REM /* READ(A$) -> R$ */ +MAL_READ: + R$=A$ + RETURN + +REM /* EVAL(A$, E%) -> R$ */ +EVAL: + GOSUB MAL_READ: REM /* call READ */ + RETURN + +REM /* PRINT(A$) -> R$ */ +MAL_PRINT: + GOSUB EVAL: REM /* call EVAL */ + RETURN + +REM /* REP(A$) -> R$ */ +REP: + GOSUB MAL_PRINT: REM /* call PRINT */ + PRINT R$ + RETURN + +REM /* main program loop */ +MAIN: + A$="user> " + GOSUB READLINE: REM /* call input parser */ + IF EOF=1 THEN END + A$=R$ + GOSUB REP: REM /* call REP */ + GOTO MAIN + From 11f94d2e74cfd9de062441fb410fdb994fca856c Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sun, 4 Sep 2016 20:42:50 -0500 Subject: [PATCH 0134/2308] Basic: step1 basics. --- basic/Makefile | 6 +- basic/printer.in.bas | 67 ++++++++++++++++++ basic/qb2cbm.sh | 5 +- basic/reader.in.bas | 127 ++++++++++++++++++++++++++++++++++ basic/step1_read_print.in.bas | 53 ++++++++++++++ basic/types.in.bas | 45 ++++++++++++ 6 files changed, 301 insertions(+), 2 deletions(-) create mode 100644 basic/printer.in.bas create mode 100644 basic/reader.in.bas create mode 100755 basic/step1_read_print.in.bas create mode 100644 basic/types.in.bas diff --git a/basic/Makefile b/basic/Makefile index 1e50294f96..42ed31f528 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -1,3 +1,7 @@ -step%.bas: step%.in.bas readline.in.bas +step%.bas: step%.in.bas ./qb2cbm.sh $< > $@ + +step0_repl.bas: readline.in.bas + +step1_read_print.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas diff --git a/basic/printer.in.bas b/basic/printer.in.bas new file mode 100644 index 0000000000..af9a85f05b --- /dev/null +++ b/basic/printer.in.bas @@ -0,0 +1,67 @@ +REM PR_STR(A%) -> R$ +PR_STR: + T%=ZT%(A%) + REM PRINT "A%: " + STR$(A%) + ", T%: " + STR$(T%) + IF T%=0 THEN R$="nil": RETURN + IF T%=1 THEN R$="false": RETURN + IF T%=2 THEN R$="true": RETURN + IF T%=3 THEN PR_INTEGER + IF T%=5 THEN PR_STRING + IF T%=6 THEN PR_KEYWORD + IF T%=7 THEN PR_SYMBOL + IF T%=8 THEN PR_LIST + R$="#" + RETURN + + PR_INTEGER: + T%=ZV%(A%) + R$=STR$(T%) + IF T%<0 THEN RETURN + REM Remove initial space + R$=RIGHT$(R$, LEN(R$)-1) + RETURN + PR_STRING: + R$=CHR$(34) + ZS$(ZV%(A%)) + CHR$(34) + RETURN + PR_KEYWORD: + R$=":keyword" + RETURN + PR_SYMBOL: + R$=ZS$(ZV%(A%)) + RETURN + PR_LIST: + IF PT%=-1 THEN RR$="" + RR$=RR$+"(" + REM keep track of where we are in the list + PT%=PT%+1 + PS%(PT%)= A% + PR_LIST_LOOP: + IF ZV%(A%) = 0 THEN PR_LIST_DONE + A%=A%+1 + REM Push whether we are rendering a list on stack + PT%=PT%+1 + IF ZT%(A%) = 8 THEN PS%(PT%) = 1 + IF ZT%(A%) <> 8 THEN PS%(PT%) = 0 + GOSUB PR_STR + REM check append then pop off stack + IF PS%(PT%) = 1 THEN RR$=RR$ + IF PS%(PT%) = 0 THEN RR$=RR$+R$ + PT%=PT%-1 + REM Go to next list element + A%=ZV%(PS%(PT%)) + PS%(PT%) = A% + IF ZV%(A%) <> 0 THEN RR$=RR$+" " + GOTO PR_LIST_LOOP + PR_LIST_DONE: + PT%=PT%-1 + RR$=RR$+")" + IF PT%=-1 THEN R$=RR$ + RETURN + + +PR_MEMORY: + PRINT "Memory:" + FOR I=0 TO ZI%-1 + PRINT " " + STR$(I) + ": type: " + STR$(ZT%(I)) + ", value: " + STR$(ZV%(I)) + NEXT I + RETURN diff --git a/basic/qb2cbm.sh b/basic/qb2cbm.sh index 4a3105e7b5..9b3cd3f430 100755 --- a/basic/qb2cbm.sh +++ b/basic/qb2cbm.sh @@ -49,7 +49,10 @@ declare -A labels lnum=10 while read -r line; do - if [[ ${line} =~ ^\ *$ ]]; then + if [[ ${line} =~ ^\ *# ]]; then + [ "${DEBUG}" ] && echo >&2 "ignoring # style comment after $lnum" + continue + elif [[ ${line} =~ ^\ *$ ]]; then [ "${DEBUG}" ] && echo >&2 "found blank line after $lnum" data="${data}\n" continue diff --git a/basic/reader.in.bas b/basic/reader.in.bas new file mode 100644 index 0000000000..b50aa9ce31 --- /dev/null +++ b/basic/reader.in.bas @@ -0,0 +1,127 @@ +REM READ_TOKEN(A$, IDX%) -> T$ +READ_TOKEN: + CUR%=IDX% + REM PRINT "READ_TOKEN: " + STR$(CUR%) + ", " + MID$(A$,CUR%,1) + T$=MID$(A$,CUR%,1) + IF (T$="(" OR T$=")") THEN RETURN + IF (T$="[" OR T$="]") THEN RETURN + IF (T$="{" OR T$="}") THEN RETURN + S1=0: S2=0: REM S1: INSTRING?, S2: ESCAPED? + IF (T$=CHR$(34)) THEN S1=1 + CUR%=CUR%+1 + READ_TOKEN_LOOP: + IF CUR% > LEN(A$) THEN RETURN + CH$=MID$(A$,CUR%,1) + IF S2 THEN GOTO READ_TOKEN_CONT + IF S1 THEN GOTO READ_TOKEN_CONT + IF (CH$=" " OR CH$=",") THEN RETURN + IF (CH$="(" OR CH$=")") THEN RETURN + IF (CH$="[" OR CH$="]") THEN RETURN + IF (CH$="{" OR CH$="}") THEN RETURN + READ_TOKEN_CONT: + T$=T$+CH$ + CUR%=CUR%+1 + IF S1 AND S2 THEN S2=0: GOTO READ_TOKEN_LOOP + IF S1 AND (S2=0) AND (CH$=CHR$(92)) THEN S2=1: GOTO READ_TOKEN_LOOP + IF S1 AND (S2=0) AND (CH$=CHR$(34)) THEN RETURN + GOTO READ_TOKEN_LOOP + +SKIP_SPACES: + CH$=MID$(A$,IDX%,1) + IF (CH$<>" " AND CH$<>",") THEN RETURN + IDX%=IDX%+1 + GOTO SKIP_SPACES + + +READ_ATOM: + R%=0 + RETURN + +REM READ_FORM(A$, IDX%) -> R% +READ_FORM: + IF ER% THEN RETURN + GOSUB SKIP_SPACES + GOSUB READ_TOKEN + REM PRINT "READ_FORM T$: [" + T$ + "]" + IF (T$="") THEN R%=0: GOTO READ_FORM_DONE + IF (T$="nil") THEN R%=0: GOTO READ_FORM_DONE + IF (T$="false") THEN R%=1: GOTO READ_FORM_DONE + IF (T$="true") THEN R%=2: GOTO READ_FORM_DONE + CH$=MID$(T$,1,1) + REM PRINT "CH$: [" + CH$ + "](" + STR$(ASC(CH$)) + ")" + IF (CH$ >= "0") AND (CH$ <= "9") OR (CH$ = "-") THEN READ_NUMBER + IF (CH$ = CHR$(34)) THEN READ_STRING + IF (CH$ = "(") THEN READ_LIST + IF (CH$ = ")") THEN READ_LIST_END + GOTO READ_SYMBOL + + READ_NUMBER: + REM PRINT "READ_NUMBER" + ZT%(ZI%) = 3 + ZV%(ZI%) = VAL(T$) + R%=ZI% + ZI%=ZI%+1 + GOTO READ_FORM_DONE + READ_STRING: + REM PRINT "READ_STRING" + ZT%(ZI%) = 5 + ZV%(ZI%) = ZJ% + R%=ZI% + ZI%=ZI%+1 + ZS$(ZJ%) = MID$(T$, 2, LEN(T$)-2) + REM ZS$(ZJ%) = T$ + ZJ%=ZJ%+1 + GOTO READ_FORM_DONE + READ_SYMBOL: + REM PRINT "READ_SYMBOL" + ZT%(ZI%) = 7 + ZV%(ZI%) = ZJ% + R%=ZI% + ZI%=ZI%+1 + ZS$(ZJ%) = T$ + ZJ%=ZJ%+1 + GOTO READ_FORM_DONE + + READ_LIST: + REM PRINT "READ_LIST" + REM push start ptr on the stack + PT%=PT%+1 + PS%(PT%) = ZI% + REM push current ptr on the stack + PT%=PT%+1 + PS%(PT%) = ZI% + GOTO READ_FORM_DONE + + READ_LIST_END: + REM PRINT "READ_LIST_END" + IF PT%=-1 THEN ER%=1: ER$="unexpected ')'": RETURN + REM Set return value to current list + PT%=PT%-1: REM pop current ptr off the stack + R%=PS%(PT%): REM start ptr to list + PT%=PT%-1: REM pop start ptr off the stack + GOTO READ_FORM_DONE + + + READ_FORM_DONE: + IDX%=IDX%+LEN(T$) + REM check PS% stack + IF PT%=-1 THEN RETURN + IF T$="" THEN ER%=1: ER$="unexpected EOF": RETURN + REM add list end entry (next pointer is 0 for now) + REM PRINT "READ_FORM_DONE next list entry" + ZT%(ZI%) = 8 + ZV%(ZI%) = 0 + REM update prior pointer if not first + IF PS%(PT%)<>ZI% THEN ZV%(PS%(PT%)) = ZI% + REM update previous pointer to outself + PS%(PT%) = ZI% + ZI%=ZI%+1: REM slot for list element + GOTO READ_FORM + + +REM READ_STR(A$) -> R% +READ_STR: + IDX%=1 + PT%=-1 + GOSUB READ_FORM + RETURN diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas new file mode 100755 index 0000000000..76cd86151c --- /dev/null +++ b/basic/step1_read_print.in.bas @@ -0,0 +1,53 @@ +GOTO MAIN + +REM $INCLUDE: 'readline.in.bas' +REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'reader.in.bas' +REM $INCLUDE: 'printer.in.bas' + +REM /* READ(A$) -> R% */ +MAL_READ: + GOSUB READ_STR + RETURN + +REM /* EVAL(A%, E%) -> R% */ +EVAL: + R%=A% + RETURN + +REM /* PRINT(A%) -> R$ */ +MAL_PRINT: + GOSUB PR_STR + RETURN + +REM /* REP(A$) -> R$ */ +REP: + GOSUB MAL_READ + IF ER% THEN RETURN + A%=R% + GOSUB EVAL + IF ER% THEN RETURN + A%=R% + GOSUB MAL_PRINT + IF ER% THEN RETURN + PRINT R$ + RETURN + +REM /* main program loop */ +MAIN: + GOSUB INIT_MEMORY + MAIN_LOOP: + A$="user> " + GOSUB READLINE: REM /* call input parser */ + IF EOF=1 THEN END + A$=R$ + GOSUB REP: REM /* call REP */ + IF ER% THEN GOTO ERROR + GOTO MAIN_LOOP + + ERROR: + PRINT "Error: " + ER$ + ER%=0 + ER$="" + GOTO MAIN_LOOP + diff --git a/basic/types.in.bas b/basic/types.in.bas new file mode 100644 index 0000000000..302f0bf53e --- /dev/null +++ b/basic/types.in.bas @@ -0,0 +1,45 @@ +REM TYPE% -> VALUE% +REM nil 0 -> (unused) +REM false 1 -> (unused) +REM true 2 -> (unused) +REM integer 3 -> int value +REM float 4 -> ??? +REM string 5 -> ZS$ index +REM keyword 6 -> ZS$ index +REM symbol 7 -> ZS$ index +REM list next 8 -> ZT% index / or 0 +REM followed by value unless empty +REM vector next 9 -> ZT% index / or 0 +REM followed by value unless empty +REM hashmap 12 -> ??? +REM mal function 13 -> ??? +REM atom 14 -> TYPE% index + +INIT_MEMORY: + REM global error state + ER%=0 + ER$="" + + REM boxes memory elements + SZ%=4096 + DIM ZT%(SZ%): REM TYPE ARRAY + DIM ZV%(SZ%): REM VALUE ARRAY + + REM Predefine nil, false, true + ZT%(0) = 0 + ZT%(1) = 1 + ZT%(2) = 2 + ZI%=3 + + REM string memory + ZJ%=0 + DIM ZS$(1024) + + REM logic stack + PT%=-1: REM index of top of PS% stack + DIM PS%(128): REM stack of ZT% indexes + + REM environment + REM DIM EKEYS$(1024) + REM DIM EVALS%(1024) + RETURN From daa68f17c7bf7770e1820d51b3e8cc09d569362d Mon Sep 17 00:00:00 2001 From: Chris M Moore Date: Sat, 10 Sep 2016 18:35:10 +0100 Subject: [PATCH 0135/2308] Nicholas Boulenguez : Replace all GNAT projects with -D gnatmake option --- ada/Makefile | 8 +++++--- ada/step0_repl.gpr | 7 ------- ada/step1_read_print.gpr | 7 ------- ada/step2_eval.gpr | 7 ------- ada/step3_env.gpr | 7 ------- ada/step4_if_fn_do.gpr | 7 ------- ada/step5_tco.gpr | 7 ------- ada/step6_file.gpr | 7 ------- ada/step7_quote.gpr | 7 ------- ada/step8_macros.gpr | 7 ------- ada/step9_try.gpr | 7 ------- ada/stepA_mal.gpr | 7 ------- 12 files changed, 5 insertions(+), 80 deletions(-) delete mode 100644 ada/step0_repl.gpr delete mode 100644 ada/step1_read_print.gpr delete mode 100644 ada/step2_eval.gpr delete mode 100644 ada/step3_env.gpr delete mode 100644 ada/step4_if_fn_do.gpr delete mode 100644 ada/step5_tco.gpr delete mode 100644 ada/step6_file.gpr delete mode 100644 ada/step7_quote.gpr delete mode 100644 ada/step8_macros.gpr delete mode 100644 ada/step9_try.gpr delete mode 100644 ada/stepA_mal.gpr diff --git a/ada/Makefile b/ada/Makefile index 6b1ad2978f..b878b7771b 100644 --- a/ada/Makefile +++ b/ada/Makefile @@ -9,7 +9,7 @@ STEP2_DEPS=${STEP1_DEPS} STEP3_DEPS=${STEP2_DEPS} envs.ad[bs] eval_callback.ads STEP4_DEPS=${STEP3_DEPS} core.ad[bs] -SOURCES = $(filter-out $(STEP0_DEPS),$(STEP4_DEPS)) stepA_mal.gpr stepa_mal.adb +SOURCES = $(filter-out $(STEP0_DEPS),$(STEP4_DEPS)) stepa_mal.adb SOURCES_LISP = $(filter-out $(STEP2_DEPS),$(SOURCES)) all: ${DIRS} ${PROGS} @@ -18,7 +18,7 @@ ${DIRS}: mkdir -p $@ step%: - gnatmake -O3 -gnata -o $@ -P$@ + gnatmake -O3 -gnata $@.adb -D obj step0_repl: step0_repl.adb ${STEP0_DEPS} step1_read_print: step1_read_print.adb ${STEP1_DEPS} @@ -30,7 +30,9 @@ step6_file: step6_file.adb ${STEP4_DEPS} step7_quote: step7_quote.adb ${STEP4_DEPS} step8_macros: step8_macros.adb ${STEP4_DEPS} step9_try: step9_try.adb ${STEP4_DEPS} -stepA_mal: stepa_mal.adb ${STEP4_DEPS} +stepa_mal: stepa_mal.adb ${STEP4_DEPS} +stepA_mal: stepa_mal + mv $< $@ clean: rm -f ${PROGS} diff --git a/ada/step0_repl.gpr b/ada/step0_repl.gpr deleted file mode 100644 index 0140abd4a9..0000000000 --- a/ada/step0_repl.gpr +++ /dev/null @@ -1,7 +0,0 @@ -project Step0_Repl is - - for Object_Dir use "obj"; - for Exec_Dir use "."; - for Main use ("step0_repl.adb"); - -end Step0_Repl; diff --git a/ada/step1_read_print.gpr b/ada/step1_read_print.gpr deleted file mode 100644 index c5bde41a40..0000000000 --- a/ada/step1_read_print.gpr +++ /dev/null @@ -1,7 +0,0 @@ -project Step1_Read_Print is - - for Object_Dir use "obj"; - for Exec_Dir use "."; - for Main use ("step1_read_print.adb"); - -end Step1_Read_Print; diff --git a/ada/step2_eval.gpr b/ada/step2_eval.gpr deleted file mode 100644 index bebfa3642e..0000000000 --- a/ada/step2_eval.gpr +++ /dev/null @@ -1,7 +0,0 @@ -project Step2_Eval is - - for Object_Dir use "obj"; - for Exec_Dir use "."; - for Main use ("step2_eval.adb"); - -end Step2_Eval; diff --git a/ada/step3_env.gpr b/ada/step3_env.gpr deleted file mode 100644 index 6dd89cc95d..0000000000 --- a/ada/step3_env.gpr +++ /dev/null @@ -1,7 +0,0 @@ -project Step3_Env is - - for Object_Dir use "obj"; - for Exec_Dir use "."; - for Main use ("step3_env.adb"); - -end Step3_Env; diff --git a/ada/step4_if_fn_do.gpr b/ada/step4_if_fn_do.gpr deleted file mode 100644 index 54a7367d93..0000000000 --- a/ada/step4_if_fn_do.gpr +++ /dev/null @@ -1,7 +0,0 @@ -project Step4_If_Fn_Do is - - for Object_Dir use "obj"; - for Exec_Dir use "."; - for Main use ("step4_if_fn_do.adb"); - -end Step4_If_Fn_Do; diff --git a/ada/step5_tco.gpr b/ada/step5_tco.gpr deleted file mode 100644 index 1602166769..0000000000 --- a/ada/step5_tco.gpr +++ /dev/null @@ -1,7 +0,0 @@ -project Step5_TCO is - - for Object_Dir use "obj"; - for Exec_Dir use "."; - for Main use ("step5_tco.adb"); - -end Step5_TCO; diff --git a/ada/step6_file.gpr b/ada/step6_file.gpr deleted file mode 100644 index dfb2612853..0000000000 --- a/ada/step6_file.gpr +++ /dev/null @@ -1,7 +0,0 @@ -project Step6_File is - - for Object_Dir use "obj"; - for Exec_Dir use "."; - for Main use ("step6_file.adb"); - -end Step6_File; diff --git a/ada/step7_quote.gpr b/ada/step7_quote.gpr deleted file mode 100644 index e74a116c21..0000000000 --- a/ada/step7_quote.gpr +++ /dev/null @@ -1,7 +0,0 @@ -project Step7_Quote is - - for Object_Dir use "obj"; - for Exec_Dir use "."; - for Main use ("step7_quote.adb"); - -end Step7_Quote; diff --git a/ada/step8_macros.gpr b/ada/step8_macros.gpr deleted file mode 100644 index 3f2f0e2e35..0000000000 --- a/ada/step8_macros.gpr +++ /dev/null @@ -1,7 +0,0 @@ -project Step8_Macros is - - for Object_Dir use "obj"; - for Exec_Dir use "."; - for Main use ("step8_macros.adb"); - -end Step8_Macros; diff --git a/ada/step9_try.gpr b/ada/step9_try.gpr deleted file mode 100644 index 2dac74aed4..0000000000 --- a/ada/step9_try.gpr +++ /dev/null @@ -1,7 +0,0 @@ -project Step9_Try is - - for Object_Dir use "obj"; - for Exec_Dir use "."; - for Main use ("step9_try.adb"); - -end Step9_Try; diff --git a/ada/stepA_mal.gpr b/ada/stepA_mal.gpr deleted file mode 100644 index c08b0bd49d..0000000000 --- a/ada/stepA_mal.gpr +++ /dev/null @@ -1,7 +0,0 @@ -project StepA_Mal is - - for Object_Dir use "obj"; - for Exec_Dir use "."; - for Main use ("stepa_mal.adb"); - -end StepA_Mal; From ae2747024f299abb71b48fd615c495d063c01acb Mon Sep 17 00:00:00 2001 From: Chris M Moore Date: Sat, 10 Sep 2016 20:45:06 +0100 Subject: [PATCH 0136/2308] Nicholas Boulenguez : Delegate Ada dependency handling to gnatmake --- ada/Makefile | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/ada/Makefile b/ada/Makefile index b878b7771b..02e02edba9 100644 --- a/ada/Makefile +++ b/ada/Makefile @@ -1,36 +1,22 @@ DIRS=obj PROGS=step0_repl step1_read_print step2_eval step3_env step4_if_fn_do \ - step5_tco step6_file step7_quote step8_macros step9_try stepA_mal + step5_tco step6_file step7_quote step8_macros step9_try -STEP0_DEPS=${DIRS} -STEP1_DEPS=${STEP0_DEPS} types.ad[bs] types-vector.ad[bs] types-hash_map.ad[bs] \ - reader.ad[bs] printer.ad[bs] smart_pointers.ad[bs] -STEP2_DEPS=${STEP1_DEPS} -STEP3_DEPS=${STEP2_DEPS} envs.ad[bs] eval_callback.ads -STEP4_DEPS=${STEP3_DEPS} core.ad[bs] +SOURCES_LISP=envs.ad[bs] eval_callback.ads core.ad[bs] stepa_mal.adb +SOURCES=$(SOURCES_LISP) \ + types.ad[bs] types-vector.ad[bs] types-hash_map.ad[bs] \ + reader.ad[bs] printer.ad[bs] smart_pointers.ad[bs] -SOURCES = $(filter-out $(STEP0_DEPS),$(STEP4_DEPS)) stepa_mal.adb -SOURCES_LISP = $(filter-out $(STEP2_DEPS),$(SOURCES)) - -all: ${DIRS} ${PROGS} +all: ${DIRS} ${PROGS} stepA_mal ${DIRS}: mkdir -p $@ -step%: +# stepA_mal is awkward because GNAT requires the filename to be lowercase +${PROGS} stepa_mal: force gnatmake -O3 -gnata $@.adb -D obj -step0_repl: step0_repl.adb ${STEP0_DEPS} -step1_read_print: step1_read_print.adb ${STEP1_DEPS} -step2_eval: step2_eval.adb ${STEP2_DEPS} -step3_env: step3_env.adb eval_callback.ads ${STEP3_DEPS} -step4_if_fn_do: step4_if_fn_do.adb ${STEP4_DEPS} -step5_tco: step5_tco.adb ${STEP4_DEPS} -step6_file: step6_file.adb ${STEP4_DEPS} -step7_quote: step7_quote.adb ${STEP4_DEPS} -step8_macros: step8_macros.adb ${STEP4_DEPS} -step9_try: step9_try.adb ${STEP4_DEPS} -stepa_mal: stepa_mal.adb ${STEP4_DEPS} +# so we make stepa_mal and just move it. stepA_mal: stepa_mal mv $< $@ @@ -38,7 +24,7 @@ clean: rm -f ${PROGS} rm -rf obj -.PHONY: stats stats-lisp +.PHONY: stats stats-lisp force stats: $(SOURCES) @wc $^ @@ -48,3 +34,4 @@ stats-lisp: $(SOURCES_LISP) @wc $^ @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*--|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +force: From 311cbfc03cc3abb66d9147aa65c63859bf71402b Mon Sep 17 00:00:00 2001 From: Chris M Moore Date: Sat, 10 Sep 2016 21:06:47 +0100 Subject: [PATCH 0137/2308] Nicholas Boulenguez : Use Get_Line function instead of insisting on a maximum string size --- ada/core.adb | 26 ++++++++++++-------------- ada/reader.ads | 4 +--- ada/step0_repl.adb | 15 +++------------ ada/step1_read_print.adb | 15 +++------------ ada/step2_eval.adb | 16 +++------------- ada/step3_env.adb | 14 +++----------- ada/step4_if_fn_do.adb | 16 ++++------------ ada/step5_tco.adb | 19 +++++-------------- ada/step6_file.adb | 18 +++++------------- ada/step7_quote.adb | 18 +++++------------- ada/step8_macros.adb | 18 +++++------------- ada/step9_try.adb | 18 +++++------------- ada/stepa_mal.adb | 14 +++----------- 13 files changed, 57 insertions(+), 154 deletions(-) diff --git a/ada/core.adb b/ada/core.adb index ff17eed428..68f45830f8 100644 --- a/ada/core.adb +++ b/ada/core.adb @@ -30,9 +30,9 @@ package body Core is Res : Boolean; begin case Deref (MH).Sym_Type is - when Bool => + when Bool => Res := Deref_Bool (MH).Get_Bool; - when Nil => + when Nil => Res := False; -- when List => -- declare @@ -489,7 +489,7 @@ package body Core is ((1 => Func_Handle, 2 => Make_New_List ((1 => Car (Deref_List_Class (List_Handle).all))))); - + List_Handle := Cdr (Deref_List_Class (List_Handle).all); Append @@ -838,16 +838,13 @@ package body Core is return Types.Mal_Handle is Rest_List : Types.List_Mal_Type; First_Param : Mal_Handle; - S : String (1..Reader.Max_Line_Len); - Last : Natural; begin Rest_List := Deref_List (Rest_Handle).all; First_Param := Car (Rest_List); -- Output the prompt. Ada.Text_IO.Put (Deref_String (First_Param).Get_String); -- Get the text. - Ada.Text_IO.Get_Line (S, Last); - return New_String_Mal_Type (S (1 .. Last)); + return New_String_Mal_Type (Ada.Text_IO.Get_Line); end Read_Line; @@ -862,19 +859,20 @@ package body Core is Unquoted_Str : String := Deref_String (First_Param).Get_String; use Ada.Text_IO; Fn : Ada.Text_IO.File_Type; - Line_Str : String (1..Reader.Max_Line_Len); File_Str : Ada.Strings.Unbounded.Unbounded_String := Ada.Strings.Unbounded.Null_Unbounded_String; - Last : Natural; I : Natural := 0; begin Ada.Text_IO.Open (Fn, In_File, Unquoted_Str); while not End_Of_File (Fn) loop - Get_Line (Fn, Line_Str, Last); - if Last > 0 then - Ada.Strings.Unbounded.Append (File_Str, Line_Str (1 .. Last)); - Ada.Strings.Unbounded.Append (File_Str, Ada.Characters.Latin_1.LF); - end if; + declare + Line_Str : constant String := Get_Line (Fn); + begin + if Line_Str'Length > 0 then + Ada.Strings.Unbounded.Append (File_Str, Line_Str); + Ada.Strings.Unbounded.Append (File_Str, Ada.Characters.Latin_1.LF); + end if; + end; end loop; Ada.Text_IO.Close (Fn); return New_String_Mal_Type (Ada.Strings.Unbounded.To_String (File_Str)); diff --git a/ada/reader.ads b/ada/reader.ads index 4f5d6cc0f7..402b3aabbe 100644 --- a/ada/reader.ads +++ b/ada/reader.ads @@ -2,11 +2,9 @@ with Types; package Reader is - Max_Line_Len : constant := 2048; - -- This is the Parser (returns an AST) function Read_Str (S : String) return Types.Mal_Handle; - + private procedure Lex_Init (S : String); diff --git a/ada/step0_repl.adb b/ada/step0_repl.adb index ea4ce9d8b5..456b8a302e 100644 --- a/ada/step0_repl.adb +++ b/ada/step0_repl.adb @@ -1,5 +1,4 @@ with Ada.Text_IO; -with Ada.IO_Exceptions; procedure Step0_Repl is @@ -24,20 +23,12 @@ procedure Step0_Repl is Print_Str : String := Print (Eval_Str); begin return Print_Str; - end Rep; - - S : String (1..1024); - Last : Natural; + end Rep; begin - loop Ada.Text_IO.Put ("user> "); - Ada.Text_IO.Get_Line (S, Last); - Ada.Text_IO.Put_Line (Rep (S (1..Last))); + exit when Ada.Text_IO.End_Of_File; + Ada.Text_IO.Put_Line (Rep (Ada.Text_IO.Get_Line)); end loop; - -exception - when Ada.IO_Exceptions.End_Error => null; - -- i.e. exit without textual output end Step0_Repl; diff --git a/ada/step1_read_print.adb b/ada/step1_read_print.adb index 53cf37de8b..4969ad34c3 100644 --- a/ada/step1_read_print.adb +++ b/ada/step1_read_print.adb @@ -1,5 +1,4 @@ with Ada.Text_IO; -with Ada.IO_Exceptions; with Printer; with Reader; with Types; @@ -34,20 +33,12 @@ procedure Step1_Read_Print is return Print (Evaluated_AST); end if; - end Rep; - - S : String (1..Reader.Max_Line_Len); - Last : Natural; + end Rep; begin - loop Ada.Text_IO.Put ("user> "); - Ada.Text_IO.Get_Line (S, Last); - Ada.Text_IO.Put_Line (Rep (S (1..Last))); + exit when Ada.Text_IO.End_Of_File; + Ada.Text_IO.Put_Line (Rep (Ada.Text_IO.Get_Line)); end loop; - -exception - when Ada.IO_Exceptions.End_Error => null; - -- i.e. exit without textual output end Step1_Read_Print; diff --git a/ada/step2_eval.adb b/ada/step2_eval.adb index dee425ad42..6f0e281d8a 100644 --- a/ada/step2_eval.adb +++ b/ada/step2_eval.adb @@ -1,7 +1,6 @@ with Ada.Containers.Hashed_Maps; with Ada.Strings.Unbounded.Hash; with Ada.Text_IO; -with Ada.IO_Exceptions; with Ada.Exceptions; with Printer; with Reader; @@ -198,11 +197,7 @@ procedure Step2_Eval is return Print (Evaluated_AST); end if; - end Rep; - - - S : String (1..Reader.Max_Line_Len); - Last : Natural; + end Rep; begin @@ -229,18 +224,13 @@ begin loop begin Ada.Text_IO.Put ("user> "); - Ada.Text_IO.Get_Line (S, Last); - Ada.Text_IO.Put_Line (Rep (S (1..Last), Repl_Env)); + exit when Ada.Text_IO.End_Of_File; + Ada.Text_IO.Put_Line (Rep (Ada.Text_IO.Get_Line, Repl_Env)); exception - when Ada.IO_Exceptions.End_Error => raise; when E : others => Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, Ada.Exceptions.Exception_Information (E)); end; end loop; - -exception - when Ada.IO_Exceptions.End_Error => null; - -- i.e. exit without textual output end Step2_Eval; diff --git a/ada/step3_env.adb b/ada/step3_env.adb index 9535a51aee..a5dcf9601b 100644 --- a/ada/step3_env.adb +++ b/ada/step3_env.adb @@ -1,6 +1,5 @@ with Ada.Command_Line; with Ada.Text_IO; -with Ada.IO_Exceptions; with Envs; with Eval_Callback; with Printer; @@ -211,7 +210,7 @@ procedure Step3_Env is return Print (Evaluated_AST); end if; - end Rep; + end Rep; procedure Init (Env : Envs.Env_Handle) is @@ -237,9 +236,6 @@ procedure Step3_Env is Repl_Env : Envs.Env_Handle; - S : String (1..Reader.Max_Line_Len); - Last : Natural; - begin -- Save a function pointer back to the Eval function. @@ -259,11 +255,7 @@ begin loop Ada.Text_IO.Put ("user> "); - Ada.Text_IO.Get_Line (S, Last); - Ada.Text_IO.Put_Line (Rep (S (1..Last), Repl_Env)); + exit when Ada.Text_IO.End_Of_File; + Ada.Text_IO.Put_Line (Rep (Ada.Text_IO.Get_Line, Repl_Env)); end loop; - -exception - when Ada.IO_Exceptions.End_Error => null; - -- i.e. exit without textual output end Step3_Env; diff --git a/ada/step4_if_fn_do.adb b/ada/step4_if_fn_do.adb index 27b7339b3c..3fafd98b30 100644 --- a/ada/step4_if_fn_do.adb +++ b/ada/step4_if_fn_do.adb @@ -1,7 +1,6 @@ with Ada.Command_Line; with Ada.Exceptions; with Ada.Text_IO; -with Ada.IO_Exceptions; with Core; with Envs; with Eval_Callback; @@ -75,7 +74,7 @@ procedure Step4_If_Fn_Do is Res : Boolean; begin case Deref (MH).Sym_Type is - when Bool => + when Bool => Res := Deref_Bool (MH).Get_Bool; when Nil => return False; @@ -263,7 +262,7 @@ procedure Step4_If_Fn_Do is return Print (Evaluated_AST); end if; - end Rep; + end Rep; Repl_Env : Envs.Env_Handle; @@ -279,8 +278,6 @@ procedure Step4_If_Fn_Do is end RE; - S : String (1..Reader.Max_Line_Len); - Last : Natural; Cmd_Args : Natural; begin @@ -309,18 +306,13 @@ begin loop begin Ada.Text_IO.Put ("user> "); - Ada.Text_IO.Get_Line (S, Last); - Ada.Text_IO.Put_Line (Rep (S (1..Last), Repl_Env)); + exit when Ada.Text_IO.End_Of_File; + Ada.Text_IO.Put_Line (Rep (Ada.Text_IO.Get_Line, Repl_Env)); exception - when Ada.IO_Exceptions.End_Error => raise; when E : others => Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, Ada.Exceptions.Exception_Information (E)); end; end loop; - -exception - when Ada.IO_Exceptions.End_Error => null; - -- i.e. exit without textual output end Step4_If_Fn_Do; diff --git a/ada/step5_tco.adb b/ada/step5_tco.adb index 67ffb35941..a0f76098f4 100644 --- a/ada/step5_tco.adb +++ b/ada/step5_tco.adb @@ -1,7 +1,6 @@ with Ada.Command_Line; with Ada.Exceptions; with Ada.Text_IO; -with Ada.IO_Exceptions; with Core; with Envs; with Eval_Callback; @@ -44,7 +43,7 @@ procedure Step5_TCO is Res : Boolean; begin case Deref (MH).Sym_Type is - when Bool => + when Bool => Res := Deref_Bool (MH).Get_Bool; when Nil => return False; @@ -261,7 +260,7 @@ procedure Step5_TCO is Param := L.Get_Expr; Env := E; goto Tail_Call_Opt; - -- was: return Eval (L.Get_Expr, E); + -- was: return Eval (L.Get_Expr, E); else @@ -306,7 +305,7 @@ procedure Step5_TCO is return Print (Evaluated_AST); end if; - end Rep; + end Rep; Repl_Env : Envs.Env_Handle; @@ -331,9 +330,6 @@ procedure Step5_TCO is return Eval_Callback.Eval.all (First_Param, Repl_Env); end Do_Eval; - - S : String (1..Reader.Max_Line_Len); - Last : Natural; Cmd_Args : Natural; begin @@ -362,18 +358,13 @@ begin loop begin Ada.Text_IO.Put ("user> "); - Ada.Text_IO.Get_Line (S, Last); - Ada.Text_IO.Put_Line (Rep (S (1..Last), Repl_Env)); + exit when Ada.Text_IO.End_Of_File; + Ada.Text_IO.Put_Line (Rep (Ada.Text_IO.Get_Line, Repl_Env)); exception - when Ada.IO_Exceptions.End_Error => raise; when E : others => Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, Ada.Exceptions.Exception_Information (E)); end; end loop; - -exception - when Ada.IO_Exceptions.End_Error => null; - -- i.e. exit without textual output end Step5_TCO; diff --git a/ada/step6_file.adb b/ada/step6_file.adb index 2e925b41b3..40ba474415 100644 --- a/ada/step6_file.adb +++ b/ada/step6_file.adb @@ -1,7 +1,6 @@ with Ada.Command_Line; with Ada.Exceptions; with Ada.Text_IO; -with Ada.IO_Exceptions; with Core; with Envs; with Eval_Callback; @@ -49,7 +48,7 @@ procedure Step6_File is Res : Boolean; begin case Deref (MH).Sym_Type is - when Bool => + when Bool => Res := Deref_Bool (MH).Get_Bool; when Nil => return False; @@ -266,7 +265,7 @@ procedure Step6_File is Param := L.Get_Expr; Env := E; goto Tail_Call_Opt; - -- was: return Eval (L.Get_Expr, E); + -- was: return Eval (L.Get_Expr, E); else @@ -312,7 +311,7 @@ procedure Step6_File is return Print (Evaluated_AST); end if; - end Rep; + end Rep; Repl_Env : Envs.Env_Handle; @@ -339,8 +338,6 @@ procedure Step6_File is end Do_Eval; - S : String (1..Reader.Max_Line_Len); - Last : Natural; Cmd_Args, File_Param : Natural; Command_Args : Types.Mal_Handle; Command_List : Types.List_Ptr; @@ -396,10 +393,9 @@ begin loop begin Ada.Text_IO.Put ("user> "); - Ada.Text_IO.Get_Line (S, Last); - Ada.Text_IO.Put_Line (Rep (S (1..Last), Repl_Env)); + exit when Ada.Text_IO.End_Of_File; + Ada.Text_IO.Put_Line (Rep (Ada.Text_IO.Get_Line, Repl_Env)); exception - when Ada.IO_Exceptions.End_Error => raise; when E : others => Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, @@ -407,8 +403,4 @@ begin end; end loop; end if; - -exception - when Ada.IO_Exceptions.End_Error => null; - -- i.e. exit without textual output end Step6_File; diff --git a/ada/step7_quote.adb b/ada/step7_quote.adb index 6eae882d26..70b2544633 100644 --- a/ada/step7_quote.adb +++ b/ada/step7_quote.adb @@ -1,7 +1,6 @@ with Ada.Command_Line; with Ada.Exceptions; with Ada.Text_IO; -with Ada.IO_Exceptions; with Core; with Envs; with Eval_Callback; @@ -44,7 +43,7 @@ procedure Step7_Quote is Res : Boolean; begin case Deref (MH).Sym_Type is - when Bool => + when Bool => Res := Deref_Bool (MH).Get_Bool; when Nil => return False; @@ -357,7 +356,7 @@ procedure Step7_Quote is Param := L.Get_Expr; Env := E; goto Tail_Call_Opt; - -- was: return Eval (L.Get_Expr, E); + -- was: return Eval (L.Get_Expr, E); else @@ -402,7 +401,7 @@ procedure Step7_Quote is return Print (Evaluated_AST); end if; - end Rep; + end Rep; Repl_Env : Envs.Env_Handle; @@ -429,8 +428,6 @@ procedure Step7_Quote is end Do_Eval; - S : String (1..Reader.Max_Line_Len); - Last : Natural; Cmd_Args, File_Param : Natural; Command_Args : Types.Mal_Handle; Command_List : Types.List_Ptr; @@ -486,10 +483,9 @@ begin loop begin Ada.Text_IO.Put ("user> "); - Ada.Text_IO.Get_Line (S, Last); - Ada.Text_IO.Put_Line (Rep (S (1..Last), Repl_Env)); + exit when Ada.Text_IO.End_Of_File; + Ada.Text_IO.Put_Line (Rep (Ada.Text_IO.Get_Line, Repl_Env)); exception - when Ada.IO_Exceptions.End_Error => raise; when E : others => Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, @@ -497,8 +493,4 @@ begin end; end loop; end if; - -exception - when Ada.IO_Exceptions.End_Error => null; - -- i.e. exit without textual output end Step7_Quote; diff --git a/ada/step8_macros.adb b/ada/step8_macros.adb index d6bd40b1b7..2c68e57a73 100644 --- a/ada/step8_macros.adb +++ b/ada/step8_macros.adb @@ -1,7 +1,6 @@ with Ada.Command_Line; with Ada.Exceptions; with Ada.Text_IO; -with Ada.IO_Exceptions; with Core; with Envs; with Eval_Callback; @@ -91,7 +90,7 @@ procedure Step8_Macros is Params := Deref_List (LP.Get_Params).all; if Envs.Bind (E, Params, Deref_List (Fn_List).all) then - Res := Eval (LP.Get_Expr, E); + Res := Eval (LP.Get_Expr, E); end if; @@ -108,7 +107,7 @@ procedure Step8_Macros is Res : Boolean; begin case Deref (MH).Sym_Type is - when Bool => + when Bool => Res := Deref_Bool (MH).Get_Bool; when Nil => return False; @@ -476,7 +475,7 @@ procedure Step8_Macros is return Print (Evaluated_AST); end if; - end Rep; + end Rep; Repl_Env : Envs.Env_Handle; @@ -503,8 +502,6 @@ procedure Step8_Macros is end Do_Eval; - S : String (1..Reader.Max_Line_Len); - Last : Natural; Cmd_Args, File_Param : Natural; Command_Args : Types.Mal_Handle; Command_List : Types.List_Ptr; @@ -561,10 +558,9 @@ begin loop begin Ada.Text_IO.Put ("user> "); - Ada.Text_IO.Get_Line (S, Last); - Ada.Text_IO.Put_Line (Rep (S (1..Last), Repl_Env)); + exit when Ada.Text_IO.End_Of_Line; + Ada.Text_IO.Put_Line (Rep (Ada.Text_IO.Get_Line, Repl_Env)); exception - when Ada.IO_Exceptions.End_Error => raise; when E : others => Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, @@ -572,8 +568,4 @@ begin end; end loop; end if; - -exception - when Ada.IO_Exceptions.End_Error => null; - -- i.e. exit without textual output end Step8_Macros; diff --git a/ada/step9_try.adb b/ada/step9_try.adb index 7c32f4f3b8..4ea09c386a 100644 --- a/ada/step9_try.adb +++ b/ada/step9_try.adb @@ -1,7 +1,6 @@ with Ada.Command_Line; with Ada.Exceptions; with Ada.Text_IO; -with Ada.IO_Exceptions; with Core; with Envs; with Eval_Callback; @@ -91,7 +90,7 @@ procedure Step9_Try is Params := Deref_List (LP.Get_Params).all; if Envs.Bind (E, Params, Deref_List (Fn_List).all) then - Res := Eval (LP.Get_Expr, E); + Res := Eval (LP.Get_Expr, E); end if; @@ -108,7 +107,7 @@ procedure Step9_Try is Res : Boolean; begin case Deref (MH).Sym_Type is - when Bool => + when Bool => Res := Deref_Bool (MH).Get_Bool; when Nil => return False; @@ -526,7 +525,7 @@ procedure Step9_Try is return Print (Evaluated_AST); end if; - end Rep; + end Rep; Repl_Env : Envs.Env_Handle; @@ -553,8 +552,6 @@ procedure Step9_Try is end Do_Eval; - S : String (1..Reader.Max_Line_Len); - Last : Natural; Cmd_Args, File_Param : Natural; Command_Args : Types.Mal_Handle; Command_List : Types.List_Ptr; @@ -611,10 +608,9 @@ begin loop begin Ada.Text_IO.Put ("user> "); - Ada.Text_IO.Get_Line (S, Last); - Ada.Text_IO.Put_Line (Rep (S (1..Last), Repl_Env)); + exit when Ada.Text_IO.End_Of_File; + Ada.Text_IO.Put_Line (Rep (Ada.Text_IO.Get_Line, Repl_Env)); exception - when Ada.IO_Exceptions.End_Error => raise; when E : others => Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, @@ -622,8 +618,4 @@ begin end; end loop; end if; - -exception - when Ada.IO_Exceptions.End_Error => null; - -- i.e. exit without textual output end Step9_Try; diff --git a/ada/stepa_mal.adb b/ada/stepa_mal.adb index 53ac5ef838..0b015b28d9 100644 --- a/ada/stepa_mal.adb +++ b/ada/stepa_mal.adb @@ -1,7 +1,6 @@ with Ada.Command_Line; with Ada.Exceptions; with Ada.Text_IO; -with Ada.IO_Exceptions; with Core; with Envs; with Eval_Callback; @@ -527,7 +526,7 @@ procedure StepA_Mal is return Print (Evaluated_AST); end if; - end Rep; + end Rep; Repl_Env : Envs.Env_Handle; @@ -554,8 +553,6 @@ procedure StepA_Mal is end Do_Eval; - S : String (1..Reader.Max_Line_Len); - Last : Natural; Cmd_Args, File_Param : Natural; Command_Args : Types.Mal_Handle; Command_List : Types.List_Ptr; @@ -617,10 +614,9 @@ begin loop begin Ada.Text_IO.Put ("user> "); - Ada.Text_IO.Get_Line (S, Last); - Ada.Text_IO.Put_Line (Rep (S (1..Last), Repl_Env)); + exit when Ada.Text_IO.End_Of_File; + Ada.Text_IO.Put_Line (Rep (Ada.Text_IO.Get_Line, Repl_Env)); exception - when Ada.IO_Exceptions.End_Error => raise; when E : others => Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, @@ -628,8 +624,4 @@ begin end; end loop; end if; - -exception - when Ada.IO_Exceptions.End_Error => null; - -- i.e. exit without textual output end StepA_Mal; From b7b1787f831856f96523b756bed61aff90558b4c Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 10 Sep 2016 22:13:27 -0500 Subject: [PATCH 0138/2308] Basic: step2 basics. Vectors and hash-maps. Adjust step2 tests to keep values within 2 byte int range. --- basic/Makefile | 6 +- basic/printer.in.bas | 88 +++++++------ basic/qb2cbm.sh | 36 ++++-- basic/reader.in.bas | 51 +++++--- basic/step0_repl.in.bas | 36 +++--- basic/step1_read_print.in.bas | 30 ++--- basic/step2_eval.in.bas | 226 ++++++++++++++++++++++++++++++++++ basic/types.in.bas | 173 +++++++++++++++++++++----- tests/step2_eval.mal | 8 +- 9 files changed, 528 insertions(+), 126 deletions(-) create mode 100755 basic/step2_eval.in.bas diff --git a/basic/Makefile b/basic/Makefile index 42ed31f528..2cfdbf3510 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -3,5 +3,9 @@ step%.bas: step%.in.bas ./qb2cbm.sh $< > $@ step0_repl.bas: readline.in.bas - step1_read_print.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas +step2_eval.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas + +tests/%.bas: tests/%.in.bas readline.in.bas types.in.bas reader.in.bas printer.in.bas + ./qb2cbm.sh $< > $@ + diff --git a/basic/printer.in.bas b/basic/printer.in.bas index af9a85f05b..3c4c412bd6 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -1,67 +1,81 @@ -REM PR_STR(A%) -> R$ +REM PR_STR(AZ%) -> R$ PR_STR: - T%=ZT%(A%) - REM PRINT "A%: " + STR$(A%) + ", T%: " + STR$(T%) + T%=Z%(AZ%,0) + REM PRINT "AZ%: " + STR$(AZ%) + ", T%: " + STR$(T%) + IF T%=15 THEN AZ%=Z%(AZ%,1): GOTO PR_STR IF T%=0 THEN R$="nil": RETURN - IF T%=1 THEN R$="false": RETURN - IF T%=2 THEN R$="true": RETURN - IF T%=3 THEN PR_INTEGER - IF T%=5 THEN PR_STRING - IF T%=6 THEN PR_KEYWORD - IF T%=7 THEN PR_SYMBOL - IF T%=8 THEN PR_LIST + IF (T%=1) AND (Z%(AZ%,1)=0) THEN R$="false": RETURN + IF (T%=1) AND (Z%(AZ%,1)=1) THEN R$="true": RETURN + IF T%=2 THEN PR_INTEGER + IF T%=4 THEN PR_STRING + IF T%=5 THEN PR_SYMBOL + IF T%=6 THEN PR_SEQ + IF T%=8 THEN PR_SEQ + IF T%=10 THEN PR_SEQ + IF T%=12 THEN PR_FUNCTION R$="#" RETURN PR_INTEGER: - T%=ZV%(A%) + T%=Z%(AZ%,1) R$=STR$(T%) IF T%<0 THEN RETURN REM Remove initial space R$=RIGHT$(R$, LEN(R$)-1) RETURN PR_STRING: - R$=CHR$(34) + ZS$(ZV%(A%)) + CHR$(34) - RETURN - PR_KEYWORD: - R$=":keyword" + R$=CHR$(34) + ZS$(Z%(AZ%,1)) + CHR$(34) RETURN PR_SYMBOL: - R$=ZS$(ZV%(A%)) + R$=ZS$(Z%(AZ%,1)) RETURN - PR_LIST: + PR_SEQ: IF PT%=-1 THEN RR$="" - RR$=RR$+"(" - REM keep track of where we are in the list + IF T%=6 THEN RR$=RR$+"(" + IF T%=8 THEN RR$=RR$+"[" + IF T%=10 THEN RR$=RR$+"{" + REM push where we are in the sequence PT%=PT%+1 - PS%(PT%)= A% - PR_LIST_LOOP: - IF ZV%(A%) = 0 THEN PR_LIST_DONE - A%=A%+1 - REM Push whether we are rendering a list on stack + PS%(PT%)= AZ% + PR_SEQ_LOOP: + IF Z%(AZ%,1) = 0 THEN PR_SEQ_DONE + AZ%=AZ%+1 + REM Push type we are rendering on the stack PT%=PT%+1 - IF ZT%(A%) = 8 THEN PS%(PT%) = 1 - IF ZT%(A%) <> 8 THEN PS%(PT%) = 0 + PS%(PT%) = Z%(AZ%,0) GOSUB PR_STR - REM check append then pop off stack - IF PS%(PT%) = 1 THEN RR$=RR$ - IF PS%(PT%) = 0 THEN RR$=RR$+R$ + REM check type and pop off stack + T%=PS%(PT%) + IF (T% >= 6) AND (T% <= 11) THEN RR$=RR$ + IF (T% < 6) OR (T% > 11) THEN RR$=RR$+R$ PT%=PT%-1 REM Go to next list element - A%=ZV%(PS%(PT%)) - PS%(PT%) = A% - IF ZV%(A%) <> 0 THEN RR$=RR$+" " - GOTO PR_LIST_LOOP - PR_LIST_DONE: + AZ%=Z%(PS%(PT%),1) + PS%(PT%) = AZ% + IF Z%(AZ%,1) <> 0 THEN RR$=RR$+" " + GOTO PR_SEQ_LOOP + PR_SEQ_DONE: + T%=Z%(PS%(PT%),0) PT%=PT%-1 - RR$=RR$+")" + IF T%=6 THEN RR$=RR$+")" + IF T%=8 THEN RR$=RR$+"]" + IF T%=10 THEN RR$=RR$+"}" IF PT%=-1 THEN R$=RR$ RETURN + PR_FUNCTION: + T1%=Z%(AZ%,1) + R$="#" + RETURN + PR_MEMORY: - PRINT "Memory:" + PRINT "Value Memory (Z%):" FOR I=0 TO ZI%-1 - PRINT " " + STR$(I) + ": type: " + STR$(ZT%(I)) + ", value: " + STR$(ZV%(I)) + PRINT " " + STR$(I) + ": type: " + STR$(Z%(I,0)) + ", value: " + STR$(Z%(I,1)) + NEXT I + PRINT "String Memory (ZS%):" + FOR I=0 TO ZJ%-1 + PRINT " " + STR$(I) + ": '" + ZS$(I) + "'" NEXT I RETURN diff --git a/basic/qb2cbm.sh b/basic/qb2cbm.sh index 9b3cd3f430..7c5930cfd4 100755 --- a/basic/qb2cbm.sh +++ b/basic/qb2cbm.sh @@ -3,6 +3,8 @@ set -e DEBUG=${DEBUG:-} +KEEP_REM=${KEEP_REM:-} +KEEP_REM_LABELS=${KEEP_REM_LABELS:-} infile=$1 @@ -33,7 +35,11 @@ while [[ ${input} =~ REM\ \$INCLUDE:\ \'.*\' ]]; do fi [ "${DEBUG}" ] && echo >&2 "including: ${include}" included[${include}]="done" - full="${full}\nREM vvv BEGIN '${include}' vvv\n$(cat ${include})\nREM vvv END '${include}' vvv\n" + if [ "${KEEP_REM}" ]; then + full="${full}\nREM vvv BEGIN '${include}' vvv\n$(cat ${include})\nREM vvv END '${include}' vvv\n" + else + full="${full}\n$(cat ${include})\n" + fi else full="${full}${line}\n" fi @@ -50,27 +56,43 @@ declare -A labels lnum=10 while read -r line; do if [[ ${line} =~ ^\ *# ]]; then - [ "${DEBUG}" ] && echo >&2 "ignoring # style comment after $lnum" + [ "${DEBUG}" ] && echo >&2 "ignoring # style comment at $lnum" + continue + elif [[ -z "${KEEP_REM}" && ${line} =~ ^\ *REM ]]; then + [ "${DEBUG}" ] && echo >&2 "dropping REM comment: ${line}" continue elif [[ ${line} =~ ^\ *$ ]]; then - [ "${DEBUG}" ] && echo >&2 "found blank line after $lnum" + [ "${DEBUG}" ] && echo >&2 "found blank line at $lnum" data="${data}\n" continue - elif [[ ${line} =~ ^[A-Za-z_]*:$ ]]; then + elif [[ ${line} =~ ^[A-Za-z_][A-Za-z0-9_]*:$ ]]; then label=${line%:} [ "${DEBUG}" ] && echo >&2 "found label ${label} at $lnum" labels[${label}]=$lnum - data="${data}${lnum} REM ${label}:\n" + if [ -n "${KEEP_REM_LABELS}" ]; then + data="${data}${lnum} REM ${label}:\n" + else + continue + fi else data="${data}${lnum} ${line}\n" fi lnum=$(( lnum + 10 )) done < <(echo -e "${input}") +if [[ -z "${KEEP_REM}" ]]; then + [ "${DEBUG}" ] && echo >&2 "Dropping line ending REMs" + data=$(echo -e "${data}" | sed "s/: REM [^\n]*$//") +fi + for label in "${!labels[@]}"; do [ "${DEBUG}" ] && echo >&2 "Updating label: ${label}" lnum=${labels[${label}]} - data=$(echo "${data}" | sed "s/\(THEN\|GOTO\|GOSUB\) ${label}\>/\1 ${lnum}: REM \1 ${label}/g") + if [ -n "${KEEP_REM_LABELS}" ]; then + data=$(echo "${data}" | sed "s/\(THEN\|GOTO\|GOSUB\) ${label}\>/\1 ${lnum}: REM ${label}/g") + else + data=$(echo "${data}" | sed "s/\(THEN\|GOTO\|GOSUB\) ${label}\>/\1 ${lnum}/g") + fi done -echo -en "${data}" +echo -e "${data}" diff --git a/basic/reader.in.bas b/basic/reader.in.bas index b50aa9ce31..40d3d684a9 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -49,56 +49,69 @@ READ_FORM: IF (T$="true") THEN R%=2: GOTO READ_FORM_DONE CH$=MID$(T$,1,1) REM PRINT "CH$: [" + CH$ + "](" + STR$(ASC(CH$)) + ")" - IF (CH$ >= "0") AND (CH$ <= "9") OR (CH$ = "-") THEN READ_NUMBER + IF (CH$ >= "0") AND (CH$ <= "9") THEN READ_NUMBER + IF (CH$ = "-") THEN READ_SYMBOL_MAYBE + IF (CH$ = CHR$(34)) THEN READ_STRING - IF (CH$ = "(") THEN READ_LIST - IF (CH$ = ")") THEN READ_LIST_END + IF (CH$ = "(") THEN T%=6: GOTO READ_SEQ + IF (CH$ = ")") THEN T%=6: GOTO READ_SEQ_END + IF (CH$ = "[") THEN T%=8: GOTO READ_SEQ + IF (CH$ = "]") THEN T%=8: GOTO READ_SEQ_END + IF (CH$ = "{") THEN T%=10: GOTO READ_SEQ + IF (CH$ = "}") THEN T%=10: GOTO READ_SEQ_END GOTO READ_SYMBOL READ_NUMBER: REM PRINT "READ_NUMBER" - ZT%(ZI%) = 3 - ZV%(ZI%) = VAL(T$) + Z%(ZI%,0) = 2 + Z%(ZI%,1) = VAL(T$) R%=ZI% ZI%=ZI%+1 GOTO READ_FORM_DONE READ_STRING: REM PRINT "READ_STRING" - ZT%(ZI%) = 5 - ZV%(ZI%) = ZJ% + Z%(ZI%,0) = 4 + Z%(ZI%,1) = ZJ% R%=ZI% ZI%=ZI%+1 ZS$(ZJ%) = MID$(T$, 2, LEN(T$)-2) REM ZS$(ZJ%) = T$ ZJ%=ZJ%+1 GOTO READ_FORM_DONE + READ_SYMBOL_MAYBE: + CH$=MID$(T$,2,1) + IF (CH$ >= "0") AND (CH$ <= "9") THEN READ_NUMBER READ_SYMBOL: REM PRINT "READ_SYMBOL" - ZT%(ZI%) = 7 - ZV%(ZI%) = ZJ% + Z%(ZI%,0) = 5 + Z%(ZI%,1) = ZJ% R%=ZI% ZI%=ZI%+1 ZS$(ZJ%) = T$ ZJ%=ZJ%+1 GOTO READ_FORM_DONE - READ_LIST: - REM PRINT "READ_LIST" + READ_SEQ: + REM PRINT "READ_SEQ" REM push start ptr on the stack PT%=PT%+1 PS%(PT%) = ZI% + REM push current sequence type + PT%=PT%+1 + PS%(PT%) = T% REM push current ptr on the stack PT%=PT%+1 PS%(PT%) = ZI% GOTO READ_FORM_DONE - READ_LIST_END: - REM PRINT "READ_LIST_END" + READ_SEQ_END: + REM PRINT "READ_SEQ_END" IF PT%=-1 THEN ER%=1: ER$="unexpected ')'": RETURN - REM Set return value to current list - PT%=PT%-1: REM pop current ptr off the stack - R%=PS%(PT%): REM start ptr to list + REM Set return value to current sequence + PT%=PT%-2: REM pop current ptr and type off the stack + R%=PS%(PT%): REM ptr to start of sequence to return PT%=PT%-1: REM pop start ptr off the stack + IF (PS%(PT%+2)) <> T% THEN ER%=1: ER$="sequence mismatch": RETURN GOTO READ_FORM_DONE @@ -109,10 +122,10 @@ READ_FORM: IF T$="" THEN ER%=1: ER$="unexpected EOF": RETURN REM add list end entry (next pointer is 0 for now) REM PRINT "READ_FORM_DONE next list entry" - ZT%(ZI%) = 8 - ZV%(ZI%) = 0 + Z%(ZI%,0) = PS%(PT%- 1) + Z%(ZI%,1) = 0 REM update prior pointer if not first - IF PS%(PT%)<>ZI% THEN ZV%(PS%(PT%)) = ZI% + IF PS%(PT%)<>ZI% THEN Z%(PS%(PT%),1) = ZI% REM update previous pointer to outself PS%(PT%) = ZI% ZI%=ZI%+1: REM slot for list element diff --git a/basic/step0_repl.in.bas b/basic/step0_repl.in.bas index 706bfd2166..7398f149e7 100755 --- a/basic/step0_repl.in.bas +++ b/basic/step0_repl.in.bas @@ -2,33 +2,39 @@ GOTO MAIN REM $INCLUDE: 'readline.in.bas' -REM /* READ(A$) -> R$ */ +REM READ(A$) -> R$ MAL_READ: R$=A$ RETURN -REM /* EVAL(A$, E%) -> R$ */ +REM EVAL(A$, E%) -> R$ EVAL: - GOSUB MAL_READ: REM /* call READ */ + R$=A$ RETURN -REM /* PRINT(A$) -> R$ */ +REM PRINT(A$) -> R$ MAL_PRINT: - GOSUB EVAL: REM /* call EVAL */ + R$=A$ RETURN -REM /* REP(A$) -> R$ */ +REM REP(A$) -> R$ REP: - GOSUB MAL_PRINT: REM /* call PRINT */ - PRINT R$ + GOSUB MAL_READ + A%=R%: GOSUB EVAL + A%=R%: GOSUB MAL_PRINT RETURN -REM /* main program loop */ +REM MAIN program MAIN: - A$="user> " - GOSUB READLINE: REM /* call input parser */ - IF EOF=1 THEN END - A$=R$ - GOSUB REP: REM /* call REP */ - GOTO MAIN + MAIN_LOOP: + A$="user> " + GOSUB READLINE: REM /* call input parser */ + IF EOF=1 THEN GOTO MAIN_DONE + A$=R$: GOSUB REP: REM /* call REP */ + PRINT R$ + GOTO MAIN_LOOP + + MAIN_DONE: + PRINT "Free: " + STR$(FRE(0)) + END diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index 76cd86151c..9ada93f7ac 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -5,44 +5,42 @@ REM $INCLUDE: 'types.in.bas' REM $INCLUDE: 'reader.in.bas' REM $INCLUDE: 'printer.in.bas' -REM /* READ(A$) -> R% */ +REM READ(A$) -> R% MAL_READ: GOSUB READ_STR RETURN -REM /* EVAL(A%, E%) -> R% */ +REM EVAL(A%, E%) -> R% EVAL: R%=A% RETURN -REM /* PRINT(A%) -> R$ */ +REM PRINT(A%) -> R$ MAL_PRINT: - GOSUB PR_STR + AZ%=A%: GOSUB PR_STR RETURN -REM /* REP(A$) -> R$ */ +REM REP(A$) -> R$ REP: GOSUB MAL_READ IF ER% THEN RETURN - A%=R% - GOSUB EVAL + A%=R%: GOSUB EVAL IF ER% THEN RETURN - A%=R% - GOSUB MAL_PRINT + A%=R%: GOSUB MAL_PRINT IF ER% THEN RETURN - PRINT R$ RETURN -REM /* main program loop */ +REM MAIN program MAIN: GOSUB INIT_MEMORY + MAIN_LOOP: A$="user> " GOSUB READLINE: REM /* call input parser */ - IF EOF=1 THEN END - A$=R$ - GOSUB REP: REM /* call REP */ + IF EOF=1 THEN GOTO MAIN_DONE + A$=R$: GOSUB REP: REM /* call REP */ IF ER% THEN GOTO ERROR + PRINT R$ GOTO MAIN_LOOP ERROR: @@ -51,3 +49,7 @@ MAIN: ER$="" GOTO MAIN_LOOP + MAIN_DONE: + PRINT "Free: " + STR$(FRE(0)) + END + diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas new file mode 100755 index 0000000000..9a9e48b6f3 --- /dev/null +++ b/basic/step2_eval.in.bas @@ -0,0 +1,226 @@ +GOTO MAIN + +REM $INCLUDE: 'readline.in.bas' +REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'reader.in.bas' +REM $INCLUDE: 'printer.in.bas' + +REM READ(A$) -> R% +MAL_READ: + GOSUB READ_STR + RETURN + +REM EVAL_AST(A%, E%) -> R% +EVAL_AST: + ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + IF ER%=1 THEN GOTO EVAL_AST_RETURN + + REM AZ%=A%: GOSUB PR_STR + REM PRINT "EVAL_AST: " + R$ + "(" + STR$(R%) + ")" + + T%=Z%(A%,0) + IF T%=5 THEN EVAL_AST_SYMBOL + IF T%=6 THEN EVAL_AST_LIST + R%=A% + GOTO EVAL_AST_RETURN + + EVAL_AST_SYMBOL: + HM%=E%: K%=A%: GOSUB HASHMAP_GET + IF T3%=0 THEN ER%=1: ER$="'" + ZS$(Z%(A%,1)) + "' not found" + GOTO EVAL_AST_RETURN + + EVAL_AST_LIST: + REM push future return value (new list) + ZL%=ZL%+1 + ZZ%(ZL%)=ZI% + REM push previous new list entry + ZL%=ZL%+1 + ZZ%(ZL%)=ZI% + + EVAL_AST_LIST_LOOP: + REM create new list entry + Z%(ZI%,0)=6 + Z%(ZI%,1)=0 + ZI%=ZI%+1 + + REM check if we are done evaluating the list + IF Z%(A%,1)=0 THEN GOTO EVAL_AST_LIST_LOOP_DONE + + REM create value ptr placeholder + Z%(ZI%,0)=15 + Z%(ZI%,1)=0 + ZI%=ZI%+1 + + REM call EVAL for each entry + A%=A%+1: GOSUB EVAL + A%=A%-1 + + REM update previous list entry to point to current entry + Z%(ZZ%(ZL%),1)=ZI% + REM update previous value pointer to evaluated entry + Z%(ZZ%(ZL%)+1,1)=R% + REM update previous ptr to current entry + ZZ%(ZL%)=ZI% + + REM process the next list entry + A%=Z%(A%,1) + + GOTO EVAL_AST_LIST_LOOP + EVAL_AST_LIST_LOOP_DONE: + REM pop previous new list entry value + ZL%=ZL%-1 + REM pop return value (new list) + R%=ZZ%(ZL%) + ZL%=ZL%-1 + GOTO EVAL_AST_RETURN + + EVAL_AST_RETURN: + E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + RETURN + +REM EVAL(A%, E%)) -> R% +EVAL: + ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + IF ER%=1 THEN GOTO EVAL_RETURN + + REM AZ%=A%: GOSUB PR_STR + REM PRINT "EVAL: " + R$ + "(" + STR$(R%) + ")" + + GOSUB LIST_Q + IF R% THEN GOTO APPLY_LIST + REM ELSE + GOSUB EVAL_AST + GOTO EVAL_RETURN + + APPLY_LIST: + GOSUB EMPTY_Q + IF R% THEN R%=A%: GOTO EVAL_RETURN + + GOSUB EVAL_AST + IF ER%=1 THEN GOTO EVAL_RETURN + F%=R%+1 + AR%=Z%(R%,1): REM REST + R%=F%: GOSUB DEREF + F%=R% + IF Z%(F%,0)<>12 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + GOSUB DO_FUNCTION + + GOTO EVAL_RETURN + + EVAL_RETURN: + E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + RETURN + +REM DO_FUNCTION(F%, AR%) +DO_FUNCTION: + AZ%=F%: GOSUB PR_STR + F$=R$ + AZ%=AR%: GOSUB PR_STR + AR$=R$ + + REM Get the function number + FF%=Z%(F%,1) + + REM Get argument values + R%=AR%+1: GOSUB DEREF + AA%=Z%(R%,1) + R%=Z%(AR%,1)+1: GOSUB DEREF + AB%=Z%(R%,1) + + REM Allocate the return value + R%=ZI% + ZI%=ZI%+1 + + REM Switch on the function number + IF FF%=1 THEN DO_ADD + IF FF%=2 THEN DO_SUB + IF FF%=3 THEN DO_MULT + IF FF%=4 THEN DO_DIV + ER%=1: ER$="unknown function" + STR$(FF%): RETURN + + DO_ADD: + Z%(R%,0)=2 + Z%(R%,1)=AA%+AB% + GOTO DO_FUNCTION_DONE + DO_SUB: + Z%(R%,0)=2 + Z%(R%,1)=AA%-AB% + GOTO DO_FUNCTION_DONE + DO_MULT: + Z%(R%,0)=2 + Z%(R%,1)=AA%*AB% + GOTO DO_FUNCTION_DONE + DO_DIV: + Z%(R%,0)=2 + Z%(R%,1)=AA%/AB% + GOTO DO_FUNCTION_DONE + + DO_FUNCTION_DONE: + RETURN + +REM PRINT(A%) -> R$ +MAL_PRINT: + AZ%=A%: GOSUB PR_STR + RETURN + +REM REP(A$) -> R$ +REM Assume RE% has repl_env +REP: + GOSUB MAL_READ + IF ER% THEN RETURN + A%=R%: E%=RE%: GOSUB EVAL + IF ER% THEN RETURN + A%=R%: GOSUB MAL_PRINT + IF ER% THEN RETURN + RETURN + +REM MAIN program +MAIN: + GOSUB INIT_MEMORY + + REM repl_env + GOSUB HASHMAP + RE%=R% + + REM + function + A%=1: GOSUB NATIVE_FUNCTION + HM%=RE%: K$="+": V%=R%: GOSUB ASSOC1_S + RE%=R% + + REM - function + A%=2: GOSUB NATIVE_FUNCTION + HM%=RE%: K$="-": V%=R%: GOSUB ASSOC1_S + RE%=R% + + REM * function + A%=3: GOSUB NATIVE_FUNCTION + HM%=RE%: K$="*": V%=R%: GOSUB ASSOC1_S + RE%=R% + + REM / function + A%=4: GOSUB NATIVE_FUNCTION + HM%=RE%: K$="/": V%=R%: GOSUB ASSOC1_S + RE%=R% + + AZ%=RE%: GOSUB PR_STR + PRINT "env: " + R$ + "(" + STR$(RE%) + ")" + + MAIN_LOOP: + A$="user> " + GOSUB READLINE: REM /* call input parser */ + IF EOF=1 THEN GOTO MAIN_DONE + A$=R$: GOSUB REP: REM /* call REP */ + IF ER% THEN GOTO ERROR + PRINT R$ + GOTO MAIN_LOOP + + ERROR: + PRINT "Error: " + ER$ + ER%=0 + ER$="" + GOTO MAIN_LOOP + + MAIN_DONE: + PRINT "Free: " + STR$(FRE(0)): REM abc + END + diff --git a/basic/types.in.bas b/basic/types.in.bas index 302f0bf53e..e7ee253ded 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -1,45 +1,160 @@ -REM TYPE% -> VALUE% -REM nil 0 -> (unused) -REM false 1 -> (unused) -REM true 2 -> (unused) -REM integer 3 -> int value -REM float 4 -> ??? -REM string 5 -> ZS$ index -REM keyword 6 -> ZS$ index -REM symbol 7 -> ZS$ index -REM list next 8 -> ZT% index / or 0 -REM followed by value unless empty -REM vector next 9 -> ZT% index / or 0 -REM followed by value unless empty -REM hashmap 12 -> ??? -REM mal function 13 -> ??? -REM atom 14 -> TYPE% index +REM Z 0 -> 1 +REM nil 0 -> (unused) +REM boolean 1 -> 0: false, 1: true +REM integer 2 -> int value +REM float 3 -> ??? +REM string/kw 4 -> ZS$ index +REM symbol 5 -> ZS$ index +REM list next/val 6 -> next Z% index / or 0 +REM followed by value (unless empty) +REM vector next/val 8 -> next Z% index / or 0 +REM followed by value (unless empty) +REM hashmap next/val 10 -> next Z% index / or 0 +REM followed by key or value (alternating) +REM function 12 -> function index +REM mal function 13 -> ??? +REM atom 14 -> Z% index +REM reference/ptr 15 -> Z% index / or 0 INIT_MEMORY: + T%=FRE(0) + + S1%=4096: REM Z% (boxed memory) size (X2) + S2%=512: REM ZS% (string memory) size + S3%=64: REM PS% (logic stack) size + S4%=256: REM ZE% (environments) size + S5%=512: REM ZZ% (call stack) size + REM global error state ER%=0 ER$="" - REM boxes memory elements - SZ%=4096 - DIM ZT%(SZ%): REM TYPE ARRAY - DIM ZV%(SZ%): REM VALUE ARRAY + REM boxed element memory + DIM Z%(S1%,1): REM TYPE ARRAY REM Predefine nil, false, true - ZT%(0) = 0 - ZT%(1) = 1 - ZT%(2) = 2 + Z%(0,0) = 0 + Z%(1,0) = 1 + Z%(1,1) = 0 + Z%(2,0) = 1 + Z%(2,1) = 1 ZI%=3 - REM string memory + REM string memory storage ZJ%=0 - DIM ZS$(1024) + DIM ZS$(S2%) + + REM environments + ZK%=0 + DIM ZE%(S4%): REM data hashmap Z% index + DIM ZO%(S4%): REM outer ZE% index (or -1) + + REM call stack + ZL%=-1 + DIM ZZ%(S5%): REM stack of Z% indexes REM logic stack PT%=-1: REM index of top of PS% stack - DIM PS%(128): REM stack of ZT% indexes + DIM PS%(S3%): REM stack of Z% indexes + + REM PRINT "Lisp data memory: " + STR$(T%-FRE(0)) + REM PRINT "Interpreter working memory: " + STR$(FRE(0)) + RETURN + +REM DEREF(R%) -> R% +DEREF: + IF Z%(R%,0)=15 THEN R%=Z%(R%,1): GOTO DEREF + RETURN + + +REM LIST functions + +LIST_Q: + R%=0 + IF Z%(A%,0)=6 THEN R%=1 + RETURN + +EMPTY_Q: + R%=0 + IF Z%(A%,1)=0 THEN R%=1 + RETURN + +REM HASHMAP functions + +REM HASHMAP() -> R% +HASHMAP: + Z%(ZI%,0) = 10 + Z%(ZI%,1) = 0 + R%=ZI% + ZI%=ZI%+1 + RETURN + +REM ASSOC1(HM%, K%, V%) -> R% +ASSOC1: + R%=ZI% + REM key ptr + Z%(ZI%,0) = 10 + Z%(ZI%,1) = ZI%+2: REM value + ZI%=ZI%+1 + Z%(ZI%,0) = 15 + Z%(ZI%,1) = K% + ZI%=ZI%+1 + REM value ptr + Z%(ZI%,0) = 10 + Z%(ZI%,1) = HM%: REM hashmap to assoc onto + ZI%=ZI%+1 + Z%(ZI%,0) = 15 + Z%(ZI%,1) = V% + ZI%=ZI%+1 + RETURN + +REM ASSOC1(HM%, K$, V%) -> R% +ASSOC1_S: + REM add the key string, then call ASSOC1 + K%=ZI% + ZS$(ZJ%) = K$ + Z%(ZI%,0) = 4 + Z%(ZI%,1) = ZJ% + ZI%=ZI%+1 + ZJ%=ZJ%+1 + GOSUB ASSOC1 + RETURN + +REM HASHMAP_GET(HM%, K%) -> R% +HASHMAP_GET: + H2%=HM% + T1$=ZS$(Z%(K%,1)): REM search key string + T3%=0: REM whether found or not (for HASHMAP_CONTAINS) + R%=0 + HASHMAP_GET_LOOP: + REM no matching key found + IF Z%(H2%,1)=0 THEN R%=0: RETURN + REM follow value ptrs + T2%=H2%+1 + HASHMAP_GET_DEREF: + IF Z%(T2%,0)=15 THEN T2%=Z%(T2%,1): GOTO HASHMAP_GET_DEREF + REM get key string + T2$=ZS$(Z%(T2%,1)) + REM if they are equal, we found it + IF T1$=T2$ THEN T3%=1: R%=Z%(H2%,1)+1: RETURN + REM skip to next key + H2%=Z%(Z%(H2%,1),1) + GOTO HASHMAP_GET_LOOP + +REM HASHMAP_CONTAINS(HM%, K%) -> R% +HASHMAP_CONTAINS: + GOSUB HASHMAP_GET + R%=1: REM false + IF T3%=1 THEN R%=2: REM true + RETURN + +REM NATIVE_FUNCTION(A%) -> R% +NATIVE_FUNCTION: + Z%(ZI%,0) = 12 + Z%(ZI%,1) = A% + R%=ZI% + ZI%=ZI%+1 + RETURN - REM environment - REM DIM EKEYS$(1024) - REM DIM EVALS%(1024) +MAL_FUNCTION: RETURN diff --git a/tests/step2_eval.mal b/tests/step2_eval.mal index a077d20363..c92fa844ca 100644 --- a/tests/step2_eval.mal +++ b/tests/step2_eval.mal @@ -11,14 +11,14 @@ (/ (- (+ 5 (* 2 3)) 3) 4) ;=>2 -(/ (- (+ 515 (* 222 311)) 302) 27) -;=>2565 +(/ (- (+ 515 (* 87 311)) 302) 27) +;=>1010 (* -3 6) ;=>-18 -(/ (- (+ 515 (* -222 311)) 296) 27) -;=>-2549 +(/ (- (+ 515 (* -87 311)) 296) 27) +;=>-994 (abc 1 2 3) ; .*\'abc\' not found.* From 0cb556e024b3c8e6ba3b87075bf7b6b5602a2e2a Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sun, 11 Sep 2016 00:11:55 -0500 Subject: [PATCH 0139/2308] Basic: add step3, vector/hash-map evaluation. Also: - qb2cbm.sh: Add KEEP_REM level variable to tweak which REM/comments are emitted/skipped. - fix printer issue with recursive sequences after evaluation (with refeerences in them) - add stats target --- basic/Makefile | 14 ++ basic/env.in.bas | 52 +++++++ basic/printer.in.bas | 16 ++- basic/qb2cbm.sh | 19 ++- basic/step2_eval.in.bas | 71 ++++++---- basic/step3_env.in.bas | 292 ++++++++++++++++++++++++++++++++++++++++ basic/types.in.bas | 3 +- 7 files changed, 424 insertions(+), 43 deletions(-) create mode 100644 basic/env.in.bas create mode 100755 basic/step3_env.in.bas diff --git a/basic/Makefile b/basic/Makefile index 2cfdbf3510..ce9b17b59a 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -5,7 +5,21 @@ step%.bas: step%.in.bas step0_repl.bas: readline.in.bas step1_read_print.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas step2_eval.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas +step3_env.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas env.in.bas tests/%.bas: tests/%.in.bas readline.in.bas types.in.bas reader.in.bas printer.in.bas ./qb2cbm.sh $< > $@ + +SOURCES_LISP = env.in.bas step3_env.in.bas +SOURCES = readline.in.bas types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP) + +.PHONY: stats + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*REM |^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*REM |^[[:space:]]*$$" $^ | wc` "[comments/blanks]" + diff --git a/basic/env.in.bas b/basic/env.in.bas new file mode 100644 index 0000000000..039938135f --- /dev/null +++ b/basic/env.in.bas @@ -0,0 +1,52 @@ + +REM ENV_NEW(EO%) -> R% +ENV_NEW: + REM allocate the data hashmap + GOSUB HASHMAP + + REM set the data and outer pointer + ZE%(ZK%)=R% + ZO%(ZK%)=EO% + + REM update environment pointer and return new environment + R%=ZK% + ZK%=ZK%+1 + RETURN + +REM ENV_SET(E%, K%, V%) -> R% +ENV_SET: + HM%=ZE%(E%) + GOSUB ASSOC1 + ZE%(E%)=R% + R%=V% + RETURN + +REM ENV_SET_S(E%, K$, V%) -> R% +ENV_SET_S: + HM%=ZE%(E%) + GOSUB ASSOC1_S + ZE%(E%)=R% + R%=V% + RETURN + +REM ENV_FIND(E%, K%) -> R% +ENV_FIND: + EF%=E% + ENV_FIND_LOOP: + HM%=ZE%(EF%) + REM More efficient to use GET for value (R%) and contains? (T3%) + GOSUB HASHMAP_GET + REM if we found it, save value in T4% for ENV_GET + IF T3%=1 THEN T4%=R%: GOTO ENV_FIND_DONE + EF%=ZO%(EF%): REM get outer environment + IF EF%<>-1 THEN GOTO ENV_FIND_LOOP + ENV_FIND_DONE: + R%=EF% + RETURN + +REM ENV_GET(E%, K%) -> R% +ENV_GET: + GOSUB ENV_FIND + IF R%=-1 THEN ER%=1: ER$="'" + ZS$(Z%(K%,1)) + "' not found": RETURN + R%=T4% + RETURN diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 3c4c412bd6..3e05d0d988 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -1,7 +1,7 @@ REM PR_STR(AZ%) -> R$ PR_STR: T%=Z%(AZ%,0) - REM PRINT "AZ%: " + STR$(AZ%) + ", T%: " + STR$(T%) + REM PRINT "AZ%: " + STR$(AZ%) + ", T%: " + STR$(T%) + ", V%: " + STR$(Z%(AZ%,1)) IF T%=15 THEN AZ%=Z%(AZ%,1): GOTO PR_STR IF T%=0 THEN R$="nil": RETURN IF (T%=1) AND (Z%(AZ%,1)=0) THEN R$="false": RETURN @@ -17,9 +17,9 @@ PR_STR: RETURN PR_INTEGER: - T%=Z%(AZ%,1) - R$=STR$(T%) - IF T%<0 THEN RETURN + T5%=Z%(AZ%,1) + R$=STR$(T5%) + IF T5%<0 THEN RETURN REM Remove initial space R$=RIGHT$(R$, LEN(R$)-1) RETURN @@ -44,10 +44,10 @@ PR_STR: PT%=PT%+1 PS%(PT%) = Z%(AZ%,0) GOSUB PR_STR - REM check type and pop off stack - T%=PS%(PT%) - IF (T% >= 6) AND (T% <= 11) THEN RR$=RR$ + REM if we just rendered a non-sequence, then append it IF (T% < 6) OR (T% > 11) THEN RR$=RR$+R$ + REM pop type off stack and check it + T%=PS%(PT%) PT%=PT%-1 REM Go to next list element AZ%=Z%(PS%(PT%),1) @@ -55,7 +55,9 @@ PR_STR: IF Z%(AZ%,1) <> 0 THEN RR$=RR$+" " GOTO PR_SEQ_LOOP PR_SEQ_DONE: + REM get current type T%=Z%(PS%(PT%),0) + REM pop where we are the sequence PT%=PT%-1 IF T%=6 THEN RR$=RR$+")" IF T%=8 THEN RR$=RR$+"]" diff --git a/basic/qb2cbm.sh b/basic/qb2cbm.sh index 7c5930cfd4..2bf6356f1b 100755 --- a/basic/qb2cbm.sh +++ b/basic/qb2cbm.sh @@ -3,8 +3,12 @@ set -e DEBUG=${DEBUG:-} -KEEP_REM=${KEEP_REM:-} -KEEP_REM_LABELS=${KEEP_REM_LABELS:-} +KEEP_REM=${KEEP_REM:-1} +# 0 - drop all REMs +# 1 - keep LABEL and INCLUDE REMs +# 2 - keep LABEL, INCLUDE, and GOTO REMs +# 3 - keep LABEL, INCLUDE, GOTO, and whole line REMs +# 4 - keep all REMS (end of line REMs too) infile=$1 @@ -35,7 +39,7 @@ while [[ ${input} =~ REM\ \$INCLUDE:\ \'.*\' ]]; do fi [ "${DEBUG}" ] && echo >&2 "including: ${include}" included[${include}]="done" - if [ "${KEEP_REM}" ]; then + if [ "${KEEP_REM}" -ge 1 ]; then full="${full}\nREM vvv BEGIN '${include}' vvv\n$(cat ${include})\nREM vvv END '${include}' vvv\n" else full="${full}\n$(cat ${include})\n" @@ -58,7 +62,8 @@ while read -r line; do if [[ ${line} =~ ^\ *# ]]; then [ "${DEBUG}" ] && echo >&2 "ignoring # style comment at $lnum" continue - elif [[ -z "${KEEP_REM}" && ${line} =~ ^\ *REM ]]; then + elif [[ "${KEEP_REM}" -lt 3 && ${line} =~ ^\ *REM && \ + ! ${line} =~ REM\ vvv && ! ${line} =~ REM\ ^^^ ]]; then [ "${DEBUG}" ] && echo >&2 "dropping REM comment: ${line}" continue elif [[ ${line} =~ ^\ *$ ]]; then @@ -69,7 +74,7 @@ while read -r line; do label=${line%:} [ "${DEBUG}" ] && echo >&2 "found label ${label} at $lnum" labels[${label}]=$lnum - if [ -n "${KEEP_REM_LABELS}" ]; then + if [ "${KEEP_REM}" -ge 1 ]; then data="${data}${lnum} REM ${label}:\n" else continue @@ -80,7 +85,7 @@ while read -r line; do lnum=$(( lnum + 10 )) done < <(echo -e "${input}") -if [[ -z "${KEEP_REM}" ]]; then +if [[ "${KEEP_REM}" -lt 4 ]]; then [ "${DEBUG}" ] && echo >&2 "Dropping line ending REMs" data=$(echo -e "${data}" | sed "s/: REM [^\n]*$//") fi @@ -88,7 +93,7 @@ fi for label in "${!labels[@]}"; do [ "${DEBUG}" ] && echo >&2 "Updating label: ${label}" lnum=${labels[${label}]} - if [ -n "${KEEP_REM_LABELS}" ]; then + if [ "${KEEP_REM}" -ge 2 ]; then data=$(echo "${data}" | sed "s/\(THEN\|GOTO\|GOSUB\) ${label}\>/\1 ${lnum}: REM ${label}/g") else data=$(echo "${data}" | sed "s/\(THEN\|GOTO\|GOSUB\) ${label}\>/\1 ${lnum}/g") diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index 9a9e48b6f3..3bdcabced1 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -20,7 +20,9 @@ EVAL_AST: T%=Z%(A%,0) IF T%=5 THEN EVAL_AST_SYMBOL - IF T%=6 THEN EVAL_AST_LIST + IF T%=6 THEN EVAL_AST_SEQ + IF T%=8 THEN EVAL_AST_SEQ + IF T%=10 THEN EVAL_AST_SEQ R%=A% GOTO EVAL_AST_RETURN @@ -29,49 +31,64 @@ EVAL_AST: IF T3%=0 THEN ER%=1: ER$="'" + ZS$(Z%(A%,1)) + "' not found" GOTO EVAL_AST_RETURN - EVAL_AST_LIST: - REM push future return value (new list) + EVAL_AST_SEQ: + REM push type of sequence + ZL%=ZL%+1 + ZZ%(ZL%)=T% + REM push sequence index + ZL%=ZL%+1 + ZZ%(ZL%)=-1 + REM push future return value (new sequence) ZL%=ZL%+1 ZZ%(ZL%)=ZI% - REM push previous new list entry + REM push previous new sequence entry ZL%=ZL%+1 ZZ%(ZL%)=ZI% - EVAL_AST_LIST_LOOP: - REM create new list entry - Z%(ZI%,0)=6 + EVAL_AST_SEQ_LOOP: + REM create new sequence entry + Z%(ZI%,0)=ZZ%(ZL%-3) Z%(ZI%,1)=0 ZI%=ZI%+1 - REM check if we are done evaluating the list - IF Z%(A%,1)=0 THEN GOTO EVAL_AST_LIST_LOOP_DONE + REM update index + ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + + REM check if we are done evaluating the sequence + IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM create value ptr placeholder Z%(ZI%,0)=15 Z%(ZI%,1)=0 ZI%=ZI%+1 + REM if hashmap, skip eval of even entries (keys) + R%=A%+1 + IF (ZZ%(ZL%-3)=10) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_SEQ_SKIP + REM call EVAL for each entry A%=A%+1: GOSUB EVAL A%=A%-1 - REM update previous list entry to point to current entry + EVAL_AST_SEQ_SKIP: + + REM update previous sequence entry to point to current entry Z%(ZZ%(ZL%),1)=ZI% REM update previous value pointer to evaluated entry Z%(ZZ%(ZL%)+1,1)=R% REM update previous ptr to current entry ZZ%(ZL%)=ZI% - REM process the next list entry + REM process the next sequence entry A%=Z%(A%,1) - GOTO EVAL_AST_LIST_LOOP - EVAL_AST_LIST_LOOP_DONE: - REM pop previous new list entry value + GOTO EVAL_AST_SEQ_LOOP + EVAL_AST_SEQ_LOOP_DONE: + REM pop previous new sequence entry value ZL%=ZL%-1 - REM pop return value (new list) + REM pop return value (new seq), index, and seq type R%=ZZ%(ZL%) - ZL%=ZL%-1 + ZL%=ZL%-3 GOTO EVAL_AST_RETURN EVAL_AST_RETURN: @@ -96,16 +113,16 @@ EVAL: GOSUB EMPTY_Q IF R% THEN R%=A%: GOTO EVAL_RETURN - GOSUB EVAL_AST - IF ER%=1 THEN GOTO EVAL_RETURN - F%=R%+1 - AR%=Z%(R%,1): REM REST - R%=F%: GOSUB DEREF - F%=R% - IF Z%(F%,0)<>12 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN - GOSUB DO_FUNCTION - - GOTO EVAL_RETURN + EVAL_INVOKE: + GOSUB EVAL_AST + IF ER%=1 THEN GOTO EVAL_RETURN + F%=R%+1 + AR%=Z%(R%,1): REM REST + R%=F%: GOSUB DEREF + F%=R% + IF Z%(F%,0)<>12 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + GOSUB DO_FUNCTION + GOTO EVAL_RETURN EVAL_RETURN: E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 @@ -221,6 +238,6 @@ MAIN: GOTO MAIN_LOOP MAIN_DONE: - PRINT "Free: " + STR$(FRE(0)): REM abc + PRINT "Free: " + STR$(FRE(0)) END diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas new file mode 100755 index 0000000000..4900771c6a --- /dev/null +++ b/basic/step3_env.in.bas @@ -0,0 +1,292 @@ +GOTO MAIN + +REM $INCLUDE: 'readline.in.bas' +REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'reader.in.bas' +REM $INCLUDE: 'printer.in.bas' +REM $INCLUDE: 'env.in.bas' + +REM READ(A$) -> R% +MAL_READ: + GOSUB READ_STR + RETURN + +REM EVAL_AST(A%, E%) -> R% +EVAL_AST: + ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + IF ER%=1 THEN GOTO EVAL_AST_RETURN + + REM AZ%=A%: GOSUB PR_STR + REM PRINT "EVAL_AST: " + R$ + "(" + STR$(R%) + ")" + + T%=Z%(A%,0) + IF T%=5 THEN EVAL_AST_SYMBOL + IF T%=6 THEN EVAL_AST_SEQ + IF T%=8 THEN EVAL_AST_SEQ + IF T%=10 THEN EVAL_AST_SEQ + R%=A% + GOTO EVAL_AST_RETURN + + EVAL_AST_SYMBOL: + K%=A%: GOSUB ENV_GET + GOTO EVAL_AST_RETURN + + EVAL_AST_SEQ: + REM push type of sequence + ZL%=ZL%+1 + ZZ%(ZL%)=T% + REM push sequence index + ZL%=ZL%+1 + ZZ%(ZL%)=-1 + REM push future return value (new sequence) + ZL%=ZL%+1 + ZZ%(ZL%)=ZI% + REM push previous new sequence entry + ZL%=ZL%+1 + ZZ%(ZL%)=ZI% + + EVAL_AST_SEQ_LOOP: + REM create new sequence entry + Z%(ZI%,0)=ZZ%(ZL%-3) + Z%(ZI%,1)=0 + ZI%=ZI%+1 + + REM update index + ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + + REM check if we are done evaluating the sequence + IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + + REM create value ptr placeholder + Z%(ZI%,0)=15 + Z%(ZI%,1)=0 + ZI%=ZI%+1 + + REM if hashmap, skip eval of even entries (keys) + R%=A%+1 + IF (ZZ%(ZL%-3)=10) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_SEQ_SKIP + + REM call EVAL for each entry + A%=A%+1: GOSUB EVAL + A%=A%-1 + + EVAL_AST_SEQ_SKIP: + + REM update previous sequence entry to point to current entry + Z%(ZZ%(ZL%),1)=ZI% + REM update previous value pointer to evaluated entry + Z%(ZZ%(ZL%)+1,1)=R% + REM update previous ptr to current entry + ZZ%(ZL%)=ZI% + + REM process the next sequence entry + A%=Z%(A%,1) + + GOTO EVAL_AST_SEQ_LOOP + EVAL_AST_SEQ_LOOP_DONE: + REM pop previous new sequence entry value + ZL%=ZL%-1 + REM pop return value (new seq), index, and seq type + R%=ZZ%(ZL%) + ZL%=ZL%-3 + GOTO EVAL_AST_RETURN + + EVAL_AST_RETURN: + E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + RETURN + +REM EVAL(A%, E%)) -> R% +EVAL: + ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + IF ER%=1 THEN GOTO EVAL_RETURN + + REM AZ%=A%: GOSUB PR_STR + REM PRINT "EVAL: " + R$ + "(" + STR$(R%) + ")" + + GOSUB LIST_Q + IF R% THEN GOTO APPLY_LIST + REM ELSE + GOSUB EVAL_AST + GOTO EVAL_RETURN + + APPLY_LIST: + GOSUB EMPTY_Q + IF R% THEN R%=A%: GOTO EVAL_RETURN + + A0% = A%+1 + R%=A0%: GOSUB DEREF + A0%=R% + + REM get symbol in A$ + IF Z%(A0%,0)<>5 THEN A$="" + IF Z%(A0%,0)=5 THEN A$=ZS$(Z%(A0%,1)) + + IF A$="def!" THEN GOTO EVAL_DEF + IF A$="let*" THEN GOTO EVAL_LET + GOTO EVAL_INVOKE + + EVAL_GET_A3: + A3% = Z%(Z%(Z%(A%,1),1),1)+1 + R%=A3%: GOSUB DEREF + A3%=R% + EVAL_GET_A2: + A2% = Z%(Z%(A%,1),1)+1 + R%=A2%: GOSUB DEREF + A2%=R% + EVAL_GET_A1: + A1% = Z%(A%,1)+1 + R%=A1%: GOSUB DEREF + A1%=R% + RETURN + + EVAL_DEF: + REM PRINT "def!" + GOSUB EVAL_GET_A2: REM set a1% and a2% + A%=A2%: GOSUB EVAL: REM eval a2 + K%=A1%: V%=R%: GOSUB ENV_SET: REM set a1 in env to a2 + RETURN + EVAL_LET: + GOSUB EVAL_GET_A2: REM set a1% and a2% + REM create new environment with outer as current environment + EO%=E%: GOSUB ENV_NEW + E%=R% + EVAL_LET_LOOP: + IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + REM push A1% + ZL%=ZL%+1: ZZ%(ZL%)=A1% + REM eval current A1 odd element + A%=Z%(A1%,1)+1: GOSUB EVAL + REM pop A1% + A1%=ZZ%(ZL%): ZL%=ZL%-1 + REM set environment: even A1% key to odd A1% eval'd above + K%=A1%+1: V%=R%: GOSUB ENV_SET + REM skip to the next pair of A1% elements + A1%=Z%(Z%(A1%,1),1) + GOTO EVAL_LET_LOOP + EVAL_LET_LOOP_DONE: + A%=A2%: GOSUB EVAL: REM eval a2 using let_env + RETURN + EVAL_INVOKE: + GOSUB EVAL_AST + IF ER%=1 THEN GOTO EVAL_RETURN + F%=R%+1 + AR%=Z%(R%,1): REM REST + R%=F%: GOSUB DEREF + F%=R% + IF Z%(F%,0)<>12 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + GOSUB DO_FUNCTION + GOTO EVAL_RETURN + + EVAL_RETURN: + E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + RETURN + +REM DO_FUNCTION(F%, AR%) +DO_FUNCTION: + AZ%=F%: GOSUB PR_STR + F$=R$ + AZ%=AR%: GOSUB PR_STR + AR$=R$ + + REM Get the function number + FF%=Z%(F%,1) + + REM Get argument values + R%=AR%+1: GOSUB DEREF + AA%=Z%(R%,1) + R%=Z%(AR%,1)+1: GOSUB DEREF + AB%=Z%(R%,1) + + REM Allocate the return value + R%=ZI% + ZI%=ZI%+1 + + REM Switch on the function number + IF FF%=1 THEN DO_ADD + IF FF%=2 THEN DO_SUB + IF FF%=3 THEN DO_MULT + IF FF%=4 THEN DO_DIV + ER%=1: ER$="unknown function" + STR$(FF%): RETURN + + DO_ADD: + Z%(R%,0)=2 + Z%(R%,1)=AA%+AB% + GOTO DO_FUNCTION_DONE + DO_SUB: + Z%(R%,0)=2 + Z%(R%,1)=AA%-AB% + GOTO DO_FUNCTION_DONE + DO_MULT: + Z%(R%,0)=2 + Z%(R%,1)=AA%*AB% + GOTO DO_FUNCTION_DONE + DO_DIV: + Z%(R%,0)=2 + Z%(R%,1)=AA%/AB% + GOTO DO_FUNCTION_DONE + + DO_FUNCTION_DONE: + RETURN + +REM PRINT(A%) -> R$ +MAL_PRINT: + AZ%=A%: GOSUB PR_STR + RETURN + +REM REP(A$) -> R$ +REM Assume RE% has repl_env +REP: + GOSUB MAL_READ + IF ER% THEN RETURN + A%=R%: E%=RE%: GOSUB EVAL + IF ER% THEN RETURN + A%=R%: GOSUB MAL_PRINT + IF ER% THEN RETURN + RETURN + +REM MAIN program +MAIN: + GOSUB INIT_MEMORY + + REM repl_env + EO%=-1: GOSUB ENV_NEW + RE%=R% + + REM + function + A%=1: GOSUB NATIVE_FUNCTION + E%=RE%: K$="+": V%=R%: GOSUB ENV_SET_S + + REM - function + A%=2: GOSUB NATIVE_FUNCTION + E%=RE%: K$="-": V%=R%: GOSUB ENV_SET_S + + REM * function + A%=3: GOSUB NATIVE_FUNCTION + E%=RE%: K$="*": V%=R%: GOSUB ENV_SET_S + + REM / function + A%=4: GOSUB NATIVE_FUNCTION + E%=RE%: K$="/": V%=R%: GOSUB ENV_SET_S + + AZ%=ZE%(RE%): GOSUB PR_STR + PRINT "env: " + R$ + "(" + STR$(RE%) + ")" + + MAIN_LOOP: + A$="user> " + GOSUB READLINE: REM /* call input parser */ + IF EOF=1 THEN GOTO MAIN_DONE + A$=R$: GOSUB REP: REM /* call REP */ + IF ER% THEN GOTO ERROR + PRINT R$ + GOTO MAIN_LOOP + + ERROR: + PRINT "Error: " + ER$ + ER%=0 + ER$="" + GOTO MAIN_LOOP + + MAIN_DONE: + PRINT "Free: " + STR$(FRE(0)) + END + diff --git a/basic/types.in.bas b/basic/types.in.bas index e7ee253ded..4886fd66b8 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -144,8 +144,7 @@ HASHMAP_GET: REM HASHMAP_CONTAINS(HM%, K%) -> R% HASHMAP_CONTAINS: GOSUB HASHMAP_GET - R%=1: REM false - IF T3%=1 THEN R%=2: REM true + R%=T3% RETURN REM NATIVE_FUNCTION(A%) -> R% From 241d5d574a147043b8ebc3e98cfc74b1ae3e5727 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sun, 11 Sep 2016 21:36:15 -0500 Subject: [PATCH 0140/2308] Basic: most of step4. --- basic/Makefile | 11 +- basic/core.in.bas | 175 +++++++++++++++++++ basic/env.in.bas | 36 ++++ basic/printer.in.bas | 40 +++-- basic/reader.in.bas | 39 +++-- basic/step1_read_print.in.bas | 4 +- basic/step2_eval.in.bas | 4 +- basic/step3_env.in.bas | 12 +- basic/step4_if_fn_do.in.bas | 318 ++++++++++++++++++++++++++++++++++ basic/types.in.bas | 105 +++++++++-- 10 files changed, 690 insertions(+), 54 deletions(-) create mode 100644 basic/core.in.bas create mode 100755 basic/step4_if_fn_do.in.bas diff --git a/basic/Makefile b/basic/Makefile index ce9b17b59a..e366da4d6b 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -2,16 +2,23 @@ step%.bas: step%.in.bas ./qb2cbm.sh $< > $@ +step%.prg: step%.bas + petcat -text -w2 -o $@ $< + step0_repl.bas: readline.in.bas step1_read_print.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas step2_eval.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas step3_env.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas env.in.bas +step4_if_fn_do.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas env.in.bas core.in.bas -tests/%.bas: tests/%.in.bas readline.in.bas types.in.bas reader.in.bas printer.in.bas +tests/%.bas: tests/%.in.bas ./qb2cbm.sh $< > $@ +tests/%.prg: tests/%.bas + petcat -text -w2 -o $@ $< + -SOURCES_LISP = env.in.bas step3_env.in.bas +SOURCES_LISP = env.in.bas core.in.bas step4_if_fn_do.in.bas SOURCES = readline.in.bas types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP) .PHONY: stats diff --git a/basic/core.in.bas b/basic/core.in.bas new file mode 100644 index 0000000000..415149e5a9 --- /dev/null +++ b/basic/core.in.bas @@ -0,0 +1,175 @@ + +REM DO_FUNCTION(F%, AR%) +DO_FUNCTION: + REM Get the function number + FF%=Z%(F%,1) + + REM Get argument values + R%=AR%+1: GOSUB DEREF + AA%=R% + R%=Z%(AR%,1)+1: GOSUB DEREF + AB%=R% + + REM Switch on the function number + IF FF%=1 THEN DO_EQUAL_Q + + IF FF%=11 THEN DO_PR_STR + IF FF%=12 THEN DO_STR + IF FF%=13 THEN DO_PRN + IF FF%=14 THEN DO_PRINTLN + + IF FF%=18 THEN DO_LT + IF FF%=19 THEN DO_LTE + IF FF%=20 THEN DO_GT + IF FF%=21 THEN DO_GTE + IF FF%=22 THEN DO_ADD + IF FF%=23 THEN DO_SUB + IF FF%=24 THEN DO_MULT + IF FF%=25 THEN DO_DIV + + IF FF%=27 THEN DO_LIST + IF FF%=28 THEN DO_LIST_Q + + IF FF%=45 THEN DO_EMPTY_Q + IF FF%=46 THEN DO_COUNT + + IF FF%=58 THEN DO_PR_MEMORY + IF FF%=59 THEN DO_PR_MEMORY_SUMMARY + ER%=1: ER$="unknown function" + STR$(FF%): RETURN + + DO_EQUAL_Q: + A%=AA%: B%=AB%: GOSUB EQUAL_Q + R%=R%+1 + RETURN + + DO_PR_STR: + AZ%=AR%: PR%=1: SE$=" ": GOSUB PR_STR_SEQ + AS$=R$: GOSUB STRING + Z%(ZI%,0) = 4 + Z%(ZI%,1) = R% + R%=ZI% + ZI%=ZI%+1 + RETURN + DO_STR: + AZ%=AR%: PR%=0: SE$="": GOSUB PR_STR_SEQ + AS$=R$: GOSUB STRING + Z%(ZI%,0) = 4 + Z%(ZI%,1) = R% + R%=ZI% + ZI%=ZI%+1 + RETURN + DO_PRN: + AZ%=AR%: PR%=1: SE$=" ": GOSUB PR_STR_SEQ + PRINT R$ + R%=0 + RETURN + DO_PRINTLN: + AZ%=AA%: PR%=0: SE$=" ": GOSUB PR_STR + PRINT R$ + R%=0 + RETURN + + DO_LT: + R%=1 + IF Z%(AA%,1)Z%(AB%,1) THEN R%=2 + RETURN + DO_GTE: + R%=1 + IF Z%(AA%,1)>=Z%(AB%,1) THEN R%=2 + RETURN + + DO_ADD: + R%=ZI%: ZI%=ZI%+1: REM Allocate result value + Z%(R%,0)=2 + Z%(R%,1)=Z%(AA%,1)+Z%(AB%,1) + RETURN + DO_SUB: + R%=ZI%: ZI%=ZI%+1: REM Allocate result value + Z%(R%,0)=2 + Z%(R%,1)=Z%(AA%,1)-Z%(AB%,1) + RETURN + DO_MULT: + R%=ZI%: ZI%=ZI%+1: REM Allocate result value + Z%(R%,0)=2 + Z%(R%,1)=Z%(AA%,1)*Z%(AB%,1) + RETURN + DO_DIV: + R%=ZI%: ZI%=ZI%+1: REM Allocate result value + Z%(R%,0)=2 + Z%(R%,1)=Z%(AA%,1)/Z%(AB%,1) + RETURN + + DO_LIST: + R%=AR% + RETURN + DO_LIST_Q: + A%=AA%: GOSUB LIST_Q + R%=R%+1: REM map to mal false/true + RETURN + + DO_EMPTY_Q: + R%=1 + IF Z%(AA%,1)=0 THEN R%=2 + RETURN + DO_COUNT: + R%=-1 + DO_COUNT_LOOP: + R%=R%+1 + IF Z%(AA%,1)<>0 THEN AA%=Z%(AA%,1): GOTO DO_COUNT_LOOP + Z%(ZI%,0) = 2 + Z%(ZI%,1) = R% + R%=ZI%: ZI%=ZI%+1: REM Allocate result value + RETURN + + DO_PR_MEMORY: + GOSUB PR_MEMORY + RETURN + + DO_PR_MEMORY_SUMMARY: + GOSUB PR_MEMORY_SUMMARY + RETURN + +INIT_CORE_SET_FUNCTION: + GOSUB NATIVE_FUNCTION + V%=R%: GOSUB ENV_SET_S + RETURN + +REM INIT_CORE_NS(E%) +INIT_CORE_NS: + REM create the environment mapping + REM must match DO_FUNCTION mappings + + K$="=": A%=1: GOSUB INIT_CORE_SET_FUNCTION + + K$="pr-str": A%=11: GOSUB INIT_CORE_SET_FUNCTION + K$="str": A%=12: GOSUB INIT_CORE_SET_FUNCTION + K$="prn": A%=13: GOSUB INIT_CORE_SET_FUNCTION + K$="println": A%=14: GOSUB INIT_CORE_SET_FUNCTION + + K$="<": A%=18: GOSUB INIT_CORE_SET_FUNCTION + K$="<=": A%=19: GOSUB INIT_CORE_SET_FUNCTION + K$=">": A%=20: GOSUB INIT_CORE_SET_FUNCTION + K$=">=": A%=21: GOSUB INIT_CORE_SET_FUNCTION + K$="+": A%=22: GOSUB INIT_CORE_SET_FUNCTION + K$="-": A%=23: GOSUB INIT_CORE_SET_FUNCTION + K$="*": A%=24: GOSUB INIT_CORE_SET_FUNCTION + K$="/": A%=25: GOSUB INIT_CORE_SET_FUNCTION + + K$="list": A%=27: GOSUB INIT_CORE_SET_FUNCTION + K$="list?": A%=28: GOSUB INIT_CORE_SET_FUNCTION + + K$="empty?": A%=45: GOSUB INIT_CORE_SET_FUNCTION + K$="count": A%=46: GOSUB INIT_CORE_SET_FUNCTION + + K$="pr-memory": A%=58: GOSUB INIT_CORE_SET_FUNCTION + K$="pr-memory-summary": A%=59: GOSUB INIT_CORE_SET_FUNCTION + + RETURN diff --git a/basic/env.in.bas b/basic/env.in.bas index 039938135f..55a0c9ab21 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -13,6 +13,42 @@ ENV_NEW: ZK%=ZK%+1 RETURN +REM ENV_NEW_BINDS(EO%, BI%, EX%) -> R% +ENV_NEW_BINDS: + GOSUB ENV_NEW + E%=R% + REM process bindings + ENV_NEW_BINDS_LOOP: + IF Z%(BI%,1)=0 THEN R%=E%: RETURN + REM get/deref the key from BI% + R%=BI%+1: GOSUB DEREF + K%=R% + + IF ZS$(Z%(K%,1))="&" THEN EVAL_NEW_BINDS_VARGS: + + EVAL_NEW_BINDS_1x1: + REM get/deref the key from EX% + R%=EX%+1: GOSUB DEREF + V%=R% + REM set the binding in the environment data + GOSUB ENV_SET + REM go to next element of BI% and EX% + BI%=Z%(BI%,1) + EX%=Z%(EX%,1) + GOTO ENV_NEW_BINDS_LOOP + + EVAL_NEW_BINDS_VARGS: + REM get/deref the key from next element of BI% + BI%=Z%(BI%,1) + R%=BI%+1: GOSUB DEREF + K%=R% + REM the value is the remaining list in EX% + V%=EX% + REM set the binding in the environment data + GOSUB ENV_SET + R%=E% + RETURN + REM ENV_SET(E%, K%, V%) -> R% ENV_SET: HM%=ZE%(E%) diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 3e05d0d988..69ae55673e 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -1,4 +1,4 @@ -REM PR_STR(AZ%) -> R$ +REM PR_STR(AZ%, PR%) -> R$ PR_STR: T%=Z%(AZ%,0) REM PRINT "AZ%: " + STR$(AZ%) + ", T%: " + STR$(T%) + ", V%: " + STR$(Z%(AZ%,1)) @@ -7,12 +7,14 @@ PR_STR: IF (T%=1) AND (Z%(AZ%,1)=0) THEN R$="false": RETURN IF (T%=1) AND (Z%(AZ%,1)=1) THEN R$="true": RETURN IF T%=2 THEN PR_INTEGER - IF T%=4 THEN PR_STRING + IF (T%=4) AND (PR%=0) THEN PR_STRING + IF (T%=4) AND (PR%=1) THEN PR_STRING_READABLY IF T%=5 THEN PR_SYMBOL IF T%=6 THEN PR_SEQ IF T%=8 THEN PR_SEQ IF T%=10 THEN PR_SEQ IF T%=12 THEN PR_FUNCTION + IF T%=13 THEN PR_MAL_FUNCTION R$="#" RETURN @@ -24,6 +26,9 @@ PR_STR: R$=RIGHT$(R$, LEN(R$)-1) RETURN PR_STRING: + R$=ZS$(Z%(AZ%,1)) + RETURN + PR_STRING_READABLY: R$=CHR$(34) + ZS$(Z%(AZ%,1)) + CHR$(34) RETURN PR_SYMBOL: @@ -68,16 +73,23 @@ PR_STR: T1%=Z%(AZ%,1) R$="#" RETURN + PR_MAL_FUNCTION: + T1%=AZ% + AZ%=Z%(T1%+1,0): GOSUB PR_STR + T7$="(fn* " + R$ + AZ%=Z%(T1%,1): GOSUB PR_STR + R$=T7$ + " " + R$ + ")" + RETURN - - -PR_MEMORY: - PRINT "Value Memory (Z%):" - FOR I=0 TO ZI%-1 - PRINT " " + STR$(I) + ": type: " + STR$(Z%(I,0)) + ", value: " + STR$(Z%(I,1)) - NEXT I - PRINT "String Memory (ZS%):" - FOR I=0 TO ZJ%-1 - PRINT " " + STR$(I) + ": '" + ZS$(I) + "'" - NEXT I - RETURN +REM PR_STR_SEQ(AZ%, PR%, SE$) -> R$ +PR_STR_SEQ: + T9%=AZ% + R1$="" + PR_STR_SEQ_LOOP: + IF Z%(T9%,1)=0 THEN R$=R1$: RETURN + AZ%=T9%+1: GOSUB PR_STR + REM goto the next sequence element + T9%=Z%(T9%,1) + IF Z%(T9%,1)=0 THEN R1$=R1$+R$ + IF Z%(T9%,1)<>0 THEN R1$=R1$+R$+SE$ + GOTO PR_STR_SEQ_LOOP diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 40d3d684a9..75276b8a89 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -3,9 +3,9 @@ READ_TOKEN: CUR%=IDX% REM PRINT "READ_TOKEN: " + STR$(CUR%) + ", " + MID$(A$,CUR%,1) T$=MID$(A$,CUR%,1) - IF (T$="(" OR T$=")") THEN RETURN - IF (T$="[" OR T$="]") THEN RETURN - IF (T$="{" OR T$="}") THEN RETURN + IF (T$="(") OR (T$=")") THEN RETURN + IF (T$="[") OR (T$="]") THEN RETURN + IF (T$="{") OR (T$="}") THEN RETURN S1=0: S2=0: REM S1: INSTRING?, S2: ESCAPED? IF (T$=CHR$(34)) THEN S1=1 CUR%=CUR%+1 @@ -14,10 +14,10 @@ READ_TOKEN: CH$=MID$(A$,CUR%,1) IF S2 THEN GOTO READ_TOKEN_CONT IF S1 THEN GOTO READ_TOKEN_CONT - IF (CH$=" " OR CH$=",") THEN RETURN - IF (CH$="(" OR CH$=")") THEN RETURN - IF (CH$="[" OR CH$="]") THEN RETURN - IF (CH$="{" OR CH$="}") THEN RETURN + IF (CH$=" ") OR (CH$=",") THEN RETURN + IF (CH$="(") OR (CH$=")") THEN RETURN + IF (CH$="[") OR (CH$="]") THEN RETURN + IF (CH$="{") OR (CH$="}") THEN RETURN READ_TOKEN_CONT: T$=T$+CH$ CUR%=CUR%+1 @@ -44,9 +44,9 @@ READ_FORM: GOSUB READ_TOKEN REM PRINT "READ_FORM T$: [" + T$ + "]" IF (T$="") THEN R%=0: GOTO READ_FORM_DONE - IF (T$="nil") THEN R%=0: GOTO READ_FORM_DONE - IF (T$="false") THEN R%=1: GOTO READ_FORM_DONE - IF (T$="true") THEN R%=2: GOTO READ_FORM_DONE + IF (T$="nil") THEN T%=0: GOTO READ_SCALAR + IF (T$="false") THEN T%=1: GOTO READ_SCALAR + IF (T$="true") THEN T%=2: GOTO READ_SCALAR CH$=MID$(T$,1,1) REM PRINT "CH$: [" + CH$ + "](" + STR$(ASC(CH$)) + ")" IF (CH$ >= "0") AND (CH$ <= "9") THEN READ_NUMBER @@ -61,6 +61,12 @@ READ_FORM: IF (CH$ = "}") THEN T%=10: GOTO READ_SEQ_END GOTO READ_SYMBOL + READ_SCALAR: + Z%(ZI%,0) = 15 + Z%(ZI%,1) = T% + R%=ZI% + ZI%=ZI%+1 + GOTO READ_FORM_DONE READ_NUMBER: REM PRINT "READ_NUMBER" Z%(ZI%,0) = 2 @@ -70,25 +76,24 @@ READ_FORM: GOTO READ_FORM_DONE READ_STRING: REM PRINT "READ_STRING" + REM intern string value + AS$=MID$(T$, 2, LEN(T$)-2): GOSUB STRING Z%(ZI%,0) = 4 - Z%(ZI%,1) = ZJ% + Z%(ZI%,1) = R% R%=ZI% ZI%=ZI%+1 - ZS$(ZJ%) = MID$(T$, 2, LEN(T$)-2) - REM ZS$(ZJ%) = T$ - ZJ%=ZJ%+1 GOTO READ_FORM_DONE READ_SYMBOL_MAYBE: CH$=MID$(T$,2,1) IF (CH$ >= "0") AND (CH$ <= "9") THEN READ_NUMBER READ_SYMBOL: REM PRINT "READ_SYMBOL" + REM intern string value + AS$=T$: GOSUB STRING Z%(ZI%,0) = 5 - Z%(ZI%,1) = ZJ% + Z%(ZI%,1) = R% R%=ZI% ZI%=ZI%+1 - ZS$(ZJ%) = T$ - ZJ%=ZJ%+1 GOTO READ_FORM_DONE READ_SEQ: diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index 9ada93f7ac..4b359ea504 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -17,7 +17,7 @@ EVAL: REM PRINT(A%) -> R$ MAL_PRINT: - AZ%=A%: GOSUB PR_STR + AZ%=A%: PR%=1: GOSUB PR_STR RETURN REM REP(A$) -> R$ @@ -50,6 +50,6 @@ MAIN: GOTO MAIN_LOOP MAIN_DONE: - PRINT "Free: " + STR$(FRE(0)) + GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index 3bdcabced1..37e8333a8d 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -177,7 +177,7 @@ DO_FUNCTION: REM PRINT(A%) -> R$ MAL_PRINT: - AZ%=A%: GOSUB PR_STR + AZ%=A%: PR%=1: GOSUB PR_STR RETURN REM REP(A$) -> R$ @@ -238,6 +238,6 @@ MAIN: GOTO MAIN_LOOP MAIN_DONE: - PRINT "Free: " + STR$(FRE(0)) + GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index 4900771c6a..685a86d1a0 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -230,7 +230,7 @@ DO_FUNCTION: REM PRINT(A%) -> R$ MAL_PRINT: - AZ%=A%: GOSUB PR_STR + AZ%=A%: PR%=1: GOSUB PR_STR RETURN REM REP(A$) -> R$ @@ -254,19 +254,19 @@ MAIN: REM + function A%=1: GOSUB NATIVE_FUNCTION - E%=RE%: K$="+": V%=R%: GOSUB ENV_SET_S + K$="+": V%=R%: GOSUB ENV_SET_S REM - function A%=2: GOSUB NATIVE_FUNCTION - E%=RE%: K$="-": V%=R%: GOSUB ENV_SET_S + K$="-": V%=R%: GOSUB ENV_SET_S REM * function A%=3: GOSUB NATIVE_FUNCTION - E%=RE%: K$="*": V%=R%: GOSUB ENV_SET_S + K$="*": V%=R%: GOSUB ENV_SET_S REM / function A%=4: GOSUB NATIVE_FUNCTION - E%=RE%: K$="/": V%=R%: GOSUB ENV_SET_S + K$="/": V%=R%: GOSUB ENV_SET_S AZ%=ZE%(RE%): GOSUB PR_STR PRINT "env: " + R$ + "(" + STR$(RE%) + ")" @@ -287,6 +287,6 @@ MAIN: GOTO MAIN_LOOP MAIN_DONE: - PRINT "Free: " + STR$(FRE(0)) + GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas new file mode 100755 index 0000000000..393f652d41 --- /dev/null +++ b/basic/step4_if_fn_do.in.bas @@ -0,0 +1,318 @@ +GOTO MAIN + +REM $INCLUDE: 'readline.in.bas' +REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'reader.in.bas' +REM $INCLUDE: 'printer.in.bas' +REM $INCLUDE: 'env.in.bas' +REM $INCLUDE: 'core.in.bas' + +REM READ(A$) -> R% +MAL_READ: + GOSUB READ_STR + RETURN + +REM EVAL_AST(A%, E%) -> R% +REM called using GOTO to avoid basic return address stack usage +REM top of stack should have return label index +EVAL_AST: + REM push A% and E% on the stack + ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + + IF ER%=1 THEN GOTO EVAL_AST_RETURN + + REM AZ%=A%: GOSUB PR_STR + REM PRINT "EVAL_AST: " + R$ + "(" + STR$(R%) + ")" + + T%=Z%(A%,0) + IF T%=5 THEN EVAL_AST_SYMBOL + IF T%=6 THEN EVAL_AST_SEQ + IF T%=8 THEN EVAL_AST_SEQ + IF T%=10 THEN EVAL_AST_SEQ + R%=A% + GOTO EVAL_AST_RETURN + + EVAL_AST_SYMBOL: + K%=A%: GOSUB ENV_GET + GOTO EVAL_AST_RETURN + + EVAL_AST_SEQ: + REM push type of sequence + ZL%=ZL%+1 + ZZ%(ZL%)=T% + REM push sequence index + ZL%=ZL%+1 + ZZ%(ZL%)=-1 + REM push future return value (new sequence) + ZL%=ZL%+1 + ZZ%(ZL%)=ZI% + REM push previous new sequence entry + ZL%=ZL%+1 + ZZ%(ZL%)=ZI% + + EVAL_AST_SEQ_LOOP: + REM create new sequence entry + Z%(ZI%,0)=ZZ%(ZL%-3) + Z%(ZI%,1)=0 + ZI%=ZI%+1 + + REM update index + ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + + REM check if we are done evaluating the sequence + IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + + REM create value ptr placeholder + Z%(ZI%,0)=15 + Z%(ZI%,1)=0 + ZI%=ZI%+1 + + REM if hashmap, skip eval of even entries (keys) + R%=A%+1 + IF (ZZ%(ZL%-3)=10) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_SEQ_SKIP + + REM call EVAL for each entry + A%=A%+1: GOSUB EVAL + A%=A%-1 + + EVAL_AST_SEQ_SKIP: + + REM update previous sequence entry to point to current entry + Z%(ZZ%(ZL%),1)=ZI% + REM update previous value pointer to evaluated entry + Z%(ZZ%(ZL%)+1,1)=R% + REM update previous ptr to current entry + ZZ%(ZL%)=ZI% + + REM process the next sequence entry + A%=Z%(A%,1) + + GOTO EVAL_AST_SEQ_LOOP + EVAL_AST_SEQ_LOOP_DONE: + REM pop previous new sequence entry value + ZL%=ZL%-1 + REM pop return value (new seq), index, and seq type + R%=ZZ%(ZL%) + ZL%=ZL%-3 + GOTO EVAL_AST_RETURN + + EVAL_AST_RETURN: + REM pop A% and E% off the stack + E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + + RN%=ZZ%(ZL%): ZL%=ZL%-1 + IF RN%=1 GOTO EVAL_AST_RETURN_1 + IF RN%=2 GOTO EVAL_AST_RETURN_2 + IF RN%=3 GOTO EVAL_AST_RETURN_3 + RETURN + +REM EVAL(A%, E%)) -> R% +EVAL: + LV%=LV%+1: REM track basic return stack level + + REM push A% and E% on the stack + ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + + EVAL_TCO_RECUR: + + IF ER%=1 THEN GOTO EVAL_RETURN + + REM AZ%=A%: GOSUB PR_STR + REM PRINT "EVAL: " + R$ + "(" + STR$(R%) + "), DEPTH: " + STR$(LV%) + REM PRINT "EVAL level: " + STR$(LV%) + + GOSUB LIST_Q + IF R% THEN GOTO APPLY_LIST + REM ELSE + REM push EVAL_AST return label/address + ZL%=ZL%+1: ZZ%(ZL%)=1 + GOTO EVAL_AST + EVAL_AST_RETURN_1: + + GOTO EVAL_RETURN + + APPLY_LIST: + GOSUB EMPTY_Q + IF R% THEN R%=A%: GOTO EVAL_RETURN + + A0% = A%+1 + R%=A0%: GOSUB DEREF + A0%=R% + + REM get symbol in A$ + IF Z%(A0%,0)<>5 THEN A$="" + IF Z%(A0%,0)=5 THEN A$=ZS$(Z%(A0%,1)) + + IF A$="def!" THEN GOTO EVAL_DEF + IF A$="let*" THEN GOTO EVAL_LET + IF A$="do" THEN GOTO EVAL_DO + IF A$="if" THEN GOTO EVAL_IF + IF A$="fn*" THEN GOTO EVAL_FN + GOTO EVAL_INVOKE + + EVAL_GET_A3: + A3% = Z%(Z%(Z%(A%,1),1),1)+1 + R%=A3%: GOSUB DEREF + A3%=R% + EVAL_GET_A2: + A2% = Z%(Z%(A%,1),1)+1 + R%=A2%: GOSUB DEREF + A2%=R% + EVAL_GET_A1: + A1% = Z%(A%,1)+1 + R%=A1%: GOSUB DEREF + A1%=R% + RETURN + + EVAL_DEF: + REM PRINT "def!" + GOSUB EVAL_GET_A2: REM set a1% and a2% + REM push A1% + ZL%=ZL%+1: ZZ%(ZL%)=A1% + A%=A2%: GOSUB EVAL: REM eval a2 + REM pop A1% + A1%=ZZ%(ZL%): ZL%=ZL%-1 + REM set a1 in env to a2 + K%=A1%: V%=R%: GOSUB ENV_SET + GOTO EVAL_RETURN + EVAL_LET: + GOSUB EVAL_GET_A2: REM set a1% and a2% + REM create new environment with outer as current environment + EO%=E%: GOSUB ENV_NEW + E%=R% + EVAL_LET_LOOP: + IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + REM push A1% + ZL%=ZL%+1: ZZ%(ZL%)=A1% + REM eval current A1 odd element + A%=Z%(A1%,1)+1: GOSUB EVAL + REM pop A1% + A1%=ZZ%(ZL%): ZL%=ZL%-1 + REM set environment: even A1% key to odd A1% eval'd above + K%=A1%+1: V%=R%: GOSUB ENV_SET + REM skip to the next pair of A1% elements + A1%=Z%(Z%(A1%,1),1) + GOTO EVAL_LET_LOOP + EVAL_LET_LOOP_DONE: + A%=A2%: GOSUB EVAL: REM eval a2 using let_env + GOTO EVAL_RETURN + EVAL_DO: + A%=Z%(A%,1): REM rest + + REM push EVAL_AST return label/address + ZL%=ZL%+1: ZZ%(ZL%)=2 + GOTO EVAL_AST + EVAL_AST_RETURN_2: + + A%=R%: GOSUB LAST: REM return the last element + GOTO EVAL_RETURN + EVAL_IF: + GOSUB EVAL_GET_A1: REM set a1% + REM push A% + ZL%=ZL%+1: ZZ%(ZL%)=A% + A%=A1%: GOSUB EVAL + REM pop A% + A%=ZZ%(ZL%): ZL%=ZL%-1 + IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE + + EVAL_IF_TRUE: + GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL + A%=A2%: GOTO EVAL_TCO_RECUR + EVAL_IF_FALSE: + REM if no false case (A3%), return nil + IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0: GOTO EVAL_RETURN + GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL + A%=A3%: GOTO EVAL_TCO_RECUR + EVAL_FN: + GOSUB EVAL_GET_A2: REM set a1% and a2% + A%=A2%: P%=A1%: GOSUB MAL_FUNCTION + GOTO EVAL_RETURN + EVAL_INVOKE: + REM push EVAL_AST return label/address + ZL%=ZL%+1: ZZ%(ZL%)=3 + GOTO EVAL_AST + EVAL_AST_RETURN_3: + + IF ER%=1 THEN GOTO EVAL_RETURN + F%=R%+1 + AR%=Z%(R%,1): REM rest + R%=F%: GOSUB DEREF + F%=R% + + IF Z%(F%,0)=12 THEN GOTO EVAL_DO_FUNCTION + IF Z%(F%,0)=13 THEN GOTO EVAL_DO_MAL_FUNCTION + ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + EVAL_DO_FUNCTION: + GOSUB DO_FUNCTION + GOTO EVAL_RETURN + EVAL_DO_MAL_FUNCTION: + EO%=Z%(F%+1,1): BI%=Z%(F%+1,0): EX%=AR%: GOSUB ENV_NEW_BINDS + A%=Z%(F%,1): E%=R%: GOTO EVAL_TCO_RECUR + + EVAL_RETURN: + REM trigger GC + T8%=FRE(0) + REM pop A% and E% off the stack + E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + + LV%=LV%-1: REM track basic return stack level + RETURN + +REM PRINT(A%) -> R$ +MAL_PRINT: + AZ%=A%: PR%=1: GOSUB PR_STR + RETURN + +REM REP(A$) -> R$ +REM Assume RE% has repl_env +REP: + LV%=LV%+1: REM track basic return stack level + + GOSUB MAL_READ + IF ER% THEN GOTO REP_RETURN + A%=R%: E%=RE%: GOSUB EVAL + IF ER% THEN GOTO REP_RETURN + A%=R%: GOSUB MAL_PRINT + IF ER% THEN GOTO REP_RETURN + REP_RETURN: + LV%=LV%-1: REM track basic return stack level + RETURN + +REM MAIN program +MAIN: + GOSUB INIT_MEMORY + + LV%=0 + + REM create repl_env + EO%=-1: GOSUB ENV_NEW + RE%=R% + + REM set core functions in repl_env + E%=RE%: GOSUB INIT_CORE_NS + + REM AZ%=ZE%(RE%): GOSUB PR_STR + REM PRINT "env: " + R$ + "(" + STR$(RE%) + ")" + + REM B% = PEEK(57) + PEEK(58) * 256 + REM PRINT "57/58%: " + STR$(B%) + + MAIN_LOOP: + A$="user> " + GOSUB READLINE: REM /* call input parser */ + IF EOF=1 THEN GOTO MAIN_DONE + A$=R$: GOSUB REP: REM /* call REP */ + IF ER% THEN GOTO ERROR + PRINT R$ + GOTO MAIN_LOOP + + ERROR: + PRINT "Error: " + ER$ + ER%=0 + ER$="" + GOTO MAIN_LOOP + + MAIN_DONE: + GOSUB PR_MEMORY_SUMMARY + END + diff --git a/basic/types.in.bas b/basic/types.in.bas index 4886fd66b8..dd48bc590d 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -19,11 +19,11 @@ REM reference/ptr 15 -> Z% index / or 0 INIT_MEMORY: T%=FRE(0) - S1%=4096: REM Z% (boxed memory) size (X2) - S2%=512: REM ZS% (string memory) size - S3%=64: REM PS% (logic stack) size - S4%=256: REM ZE% (environments) size - S5%=512: REM ZZ% (call stack) size + S1%=4096+512: REM Z% (boxed memory) size (X2) + S2%=256: REM ZS% (string memory) size + S3%=256: REM ZE%,ZO% (environments) size + S4%=256: REM ZZ% (call stack) size + S5%=64: REM PS% (logic stack) size REM global error state ER%=0 @@ -34,6 +34,7 @@ INIT_MEMORY: REM Predefine nil, false, true Z%(0,0) = 0 + Z%(0,1) = 0 Z%(1,0) = 1 Z%(1,1) = 0 Z%(2,0) = 1 @@ -46,40 +47,115 @@ INIT_MEMORY: REM environments ZK%=0 - DIM ZE%(S4%): REM data hashmap Z% index - DIM ZO%(S4%): REM outer ZE% index (or -1) + DIM ZE%(S3%): REM data hashmap Z% index + DIM ZO%(S3%): REM outer ZE% index (or -1) REM call stack ZL%=-1 - DIM ZZ%(S5%): REM stack of Z% indexes + DIM ZZ%(S4%): REM stack of Z% indexes REM logic stack PT%=-1: REM index of top of PS% stack - DIM PS%(S3%): REM stack of Z% indexes + DIM PS%(S5%): REM stack of Z% indexes REM PRINT "Lisp data memory: " + STR$(T%-FRE(0)) REM PRINT "Interpreter working memory: " + STR$(FRE(0)) RETURN +REM general functions + +PR_MEMORY_SUMMARY: + PRINT + PRINT "Free memory (FRE) : " + STR$(FRE(0)) + PRINT "Boxed values (Z%) : " + STR$(ZI%) + " /" + STR$(S1%) + PRINT "String values (ZS$) : " + STR$(ZJ%) + " /" + STR$(S2%) + PRINT "Environments (ZE%) : " + STR$(ZK%) + " /" + STR$(S3%) + PRINT "Call stack size (ZZ%) : " + STR$(ZL%+1) + " /" + STR$(S4%) + PRINT "Logic stack size (PS%) : " + STR$(PT%+1) + " /" + STR$(S5%) + RETURN + +PR_MEMORY: + PRINT "Value Memory (Z%):" + FOR I=0 TO ZI%-1 + PRINT " " + STR$(I) + ": type: " + STR$(Z%(I,0)) + ", value: " + STR$(Z%(I,1)) + NEXT I + PRINT "String Memory (ZS%):" + FOR I=0 TO ZJ%-1 + PRINT " " + STR$(I) + ": '" + ZS$(I) + "'" + NEXT I + RETURN + REM DEREF(R%) -> R% DEREF: IF Z%(R%,0)=15 THEN R%=Z%(R%,1): GOTO DEREF RETURN +REM EQUAL_Q(A%, B%) -> R% +EQUAL_Q: + R%=0 + U1%=Z%(A%,0): U2%=Z%(B%,0) + IF NOT ((U1%=U2%) OR ((U1%=6 OR U1%=8) AND (U2%=6 OR U2%=8))) THEN RETURN + IF U1%=6 THEN GOTO EQUAL_Q_SEQ + IF U1%=8 THEN GOTO EQUAL_Q_SEQ + IF U1%=10 THEN GOTO EQUAL_Q_HM + + IF Z%(A%,1)=Z%(B%,1) THEN R%=1 + RETURN + + EQUAL_Q_SEQ: + R%=0 + RETURN + EQUAL_Q_HM: + R%=0 + RETURN + +REM string functions -REM LIST functions +REM STRING_(AS$) -> R% +REM intern string (returns string index, not Z% index) +STRING: + IF ZJ%=0 THEN GOTO STRING_NOT_FOUND + REM search for matching string in ZS$ + FOR I=0 TO ZJ%-1 + IF AS$=ZS$(I) THEN R%=I: RETURN + NEXT I + + STRING_NOT_FOUND: + ZS$(ZJ%) = AS$ + R%=ZJ% + ZJ%=ZJ%+1 + RETURN + + + + +REM list functions + +REM LIST_Q(A%) -> R% LIST_Q: R%=0 IF Z%(A%,0)=6 THEN R%=1 RETURN +REM LIST_Q(A%) -> R% EMPTY_Q: R%=0 IF Z%(A%,1)=0 THEN R%=1 RETURN -REM HASHMAP functions +REM LAST(A%) -> R% +LAST: + REM TODO check that actually a list/vector + IF Z%(A%,1)=0 THEN R%=0: RETURN: REM empty seq, return nil + T6%=0 + LAST_LOOP: + IF Z%(A%,1)=0 THEN R%=T6%+1: RETURN: REM end, return previous value + T6%=A%: REM current becomes previous entry + A%=Z%(A%,1): REM next entry + GOTO LAST_LOOP + +REM hashmap functions REM HASHMAP() -> R% HASHMAP: @@ -155,5 +231,12 @@ NATIVE_FUNCTION: ZI%=ZI%+1 RETURN +REM NATIVE_FUNCTION(A%, P%, E%) -> R% MAL_FUNCTION: + Z%(ZI%,0) = 13 + Z%(ZI%,1) = A% + Z%(ZI%+1,0) = P% + Z%(ZI%+1,1) = E% + R%=ZI% + ZI%=ZI%+2 RETURN From 6e8fc83537062c2df0c6cd7f5193c29f621b95af Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Thu, 15 Sep 2016 09:43:03 +0530 Subject: [PATCH 0141/2308] Fix a typo in core division function --- clisp/core.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clisp/core.lisp b/clisp/core.lisp index a477967e8e..5ac7cfdf1c 100644 --- a/clisp/core.lisp +++ b/clisp/core.lisp @@ -42,7 +42,7 @@ (cons (types:make-mal-symbol "/") (types:make-mal-builtin-fn (lambda (value1 value2) - (types:make-mal-number (float (/ (types:mal-data-value value1) + (types:make-mal-number (floor (/ (types:mal-data-value value1) (types:mal-data-value value2))))))) (cons (types:make-mal-symbol "prn") From 184c16ad0e7803449f5920dfee1f3f18e4ea60e1 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Thu, 15 Sep 2016 09:55:16 +0530 Subject: [PATCH 0142/2308] Do not use custom hashtable for storing MAL environment --- clisp/env.lisp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clisp/env.lisp b/clisp/env.lisp index 27888f8d40..254eaaadae 100644 --- a/clisp/env.lisp +++ b/clisp/env.lisp @@ -27,7 +27,7 @@ (defclass mal-environment () ((bindings :initarg :bindings :accessor mal-env-bindings - :initform (make-hash-table :test 'types:mal-value=)) + :initform (make-hash-table :test 'equal)) (parent :initarg :parent :accessor mal-env-parent :initform nil))) @@ -42,7 +42,7 @@ (:documentation "Set the value for a symbol in given environment")) (defmethod find-env ((env mal-environment) symbol) - (let ((value (gethash symbol (mal-env-bindings env))) + (let ((value (gethash (types:mal-data-value symbol) (mal-env-bindings env))) (parent (mal-env-parent env))) (cond (value value) @@ -57,7 +57,7 @@ :symbol (format nil "~a" (types:mal-data-value symbol)))))) (defmethod set-env ((env mal-environment) symbol value) - (setf (gethash symbol (mal-env-bindings env)) value)) + (setf (gethash (types:mal-data-value symbol) (mal-env-bindings env)) value)) (defmethod initialize-instance :after ((env mal-environment) &key (bindings nil) From 08e86b3cd045e660be9634013cfcb9769eb210bb Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Thu, 15 Sep 2016 20:16:32 +0530 Subject: [PATCH 0143/2308] Store enviroments as struct instead of classes Seems to give some good speedups --- clisp/env.lisp | 50 +++++++++++++++------------------------ clisp/step3_env.lisp | 5 ++-- clisp/step4_if_fn_do.lisp | 16 ++++++------- clisp/step5_tco.lisp | 27 ++++++++++----------- clisp/step6_file.lisp | 27 ++++++++++----------- clisp/step7_quote.lisp | 27 ++++++++++----------- clisp/step8_macros.lisp | 27 ++++++++++----------- clisp/step9_try.lisp | 38 +++++++++++++---------------- clisp/stepA_mal.lisp | 40 ++++++++++++++----------------- 9 files changed, 111 insertions(+), 146 deletions(-) diff --git a/clisp/env.lisp b/clisp/env.lisp index 254eaaadae..74604d22d8 100644 --- a/clisp/env.lisp +++ b/clisp/env.lisp @@ -1,7 +1,8 @@ (defpackage :env (:use :common-lisp :types) (:export :undefined-symbol - :mal-environment + :mal-env + :create-mal-env :get-env :find-env :set-env)) @@ -24,46 +25,32 @@ (required condition) (provided condition))))) -(defclass mal-environment () - ((bindings :initarg :bindings - :accessor mal-env-bindings - :initform (make-hash-table :test 'equal)) - (parent :initarg :parent - :accessor mal-env-parent - :initform nil))) +(defstruct mal-env + (bindings (make-hash-table :test 'equal) :read-only t) + (parent nil :read-only t)) -(defgeneric find-env (mal-environment symbol) - (:documentation "Find value of a symbol in given environment, return nil if not binding is found")) - -(defgeneric get-env (mal-environment symbol) - (:documentation "Get value of a symbol in given environment, raises undefined-symbol error if lookup fails")) - -(defgeneric set-env (mal-environment symbol value) - (:documentation "Set the value for a symbol in given environment")) - -(defmethod find-env ((env mal-environment) symbol) - (let ((value (gethash (types:mal-data-value symbol) (mal-env-bindings env))) +(defun find-env (env symbol) + (let ((value (gethash (types:mal-data-value symbol) + (mal-env-bindings env))) (parent (mal-env-parent env))) (cond (value value) (parent (find-env parent symbol)) (t nil)))) -(defmethod get-env ((env mal-environment) symbol) +(defun get-env (env symbol) (let ((value (find-env env symbol))) (if value value (error 'undefined-symbol :symbol (format nil "~a" (types:mal-data-value symbol)))))) -(defmethod set-env ((env mal-environment) symbol value) - (setf (gethash (types:mal-data-value symbol) (mal-env-bindings env)) value)) +(defun set-env (env symbol value) + (setf (gethash (types:mal-data-value symbol) + (mal-env-bindings env)) + value)) -(defmethod initialize-instance :after ((env mal-environment) - &key (bindings nil) - (parent nil) - (binds nil) - (exprs nil)) +(defun create-mal-env (&key (parent nil) (binds nil) (exprs nil)) (let ((varidiac-position (position (types:make-mal-symbol "&") binds :test #'mal-value=))) @@ -97,8 +84,9 @@ :required (length binds) :provided (length exprs))) - (let ((arg-params (map 'list #'cons binds exprs))) + (let ((arg-params (map 'list #'cons binds exprs)) + (bindings (make-hash-table :test 'equal))) (dolist (arg-param arg-params) - (set-env env - (car arg-param) - (cdr arg-param)))))) + (setf (gethash (types:mal-data-value (car arg-param)) bindings) + (cdr arg-param))) + (make-mal-env :bindings bindings :parent parent)))) diff --git a/clisp/step3_env.lisp b/clisp/step3_env.lisp index 51abec79da..a3aed4a7ea 100644 --- a/clisp/step3_env.lisp +++ b/clisp/step3_env.lisp @@ -10,7 +10,7 @@ (in-package :mal) -(defvar *repl-env* (make-instance 'env:mal-environment)) +(defvar *repl-env* (env:create-mal-env)) (set-env *repl-env* (types:make-mal-symbol "+") @@ -58,8 +58,7 @@ (types:any ast))) (defun eval-let* (forms env) - (let ((new-env (make-instance 'env:mal-environment - :parent env)) + (let ((new-env (env:create-mal-env :parent env)) ;; Convert a potential vector to a list (bindings (map 'list #'identity diff --git a/clisp/step4_if_fn_do.lisp b/clisp/step4_if_fn_do.lisp index 64150ed41f..0d9657191d 100644 --- a/clisp/step4_if_fn_do.lisp +++ b/clisp/step4_if_fn_do.lisp @@ -11,7 +11,7 @@ (in-package :mal) -(defvar *repl-env* (make-instance 'env:mal-environment)) +(defvar *repl-env* (env:create-mal-env)) (dolist (binding core:ns) (env:set-env *repl-env* @@ -47,8 +47,7 @@ (types:any ast))) (defun eval-let* (forms env) - (let ((new-env (make-instance 'env:mal-environment - :parent env)) + (let ((new-env (env:create-mal-env :parent env)) ;; Convert a potential vector to a list (bindings (map 'list #'identity @@ -88,12 +87,11 @@ (types:make-mal-fn (let ((arglist (second forms)) (body (third forms))) (lambda (&rest args) - (mal-eval body (make-instance 'env:mal-environment - :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args)))))) + (mal-eval body (env:create-mal-env :parent env + :binds (map 'list + #'identity + (mal-data-value arglist)) + :exprs args)))))) (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) ;; If first element is a mal function unwrap it diff --git a/clisp/step5_tco.lisp b/clisp/step5_tco.lisp index ce8d98ee2b..d25e1d0306 100644 --- a/clisp/step5_tco.lisp +++ b/clisp/step5_tco.lisp @@ -11,7 +11,7 @@ (in-package :mal) -(defvar *repl-env* (make-instance 'env:mal-environment)) +(defvar *repl-env* (env:create-mal-env)) (dolist (binding core:ns) (env:set-env *repl-env* @@ -61,8 +61,7 @@ (return (env:set-env env (second forms) (mal-eval (third forms) env)))) ((mal-value= mal-let* (first forms)) - (let ((new-env (make-instance 'env:mal-environment - :parent env)) + (let ((new-env (env:create-mal-env :parent env)) ;; Convert a potential vector to a list (bindings (map 'list #'identity @@ -97,12 +96,11 @@ (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) - (mal-eval body (make-instance 'env:mal-environment - :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args))) + (mal-eval body (env:create-mal-env :parent env + :binds (map 'list + #'identity + (mal-data-value arglist)) + :exprs args))) :attrs (list (cons 'params arglist) (cons 'ast body) (cons 'env env)))))) @@ -115,12 +113,11 @@ (cdr evaluated-list))) (let* ((attrs (types:mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) - env (make-instance 'env:mal-environment - :parent (cdr (assoc 'env attrs)) - :binds (map 'list - #'identity - (mal-data-value (cdr (assoc 'params attrs)))) - :exprs (cdr evaluated-list))))))))))))) + env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-data-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))))))))))) (defun mal-print (expression) (printer:pr-str expression)) diff --git a/clisp/step6_file.lisp b/clisp/step6_file.lisp index 52d519a8ff..095f664a5e 100644 --- a/clisp/step6_file.lisp +++ b/clisp/step6_file.lisp @@ -11,7 +11,7 @@ (in-package :mal) -(defvar *repl-env* (make-instance 'env:mal-environment)) +(defvar *repl-env* (env:create-mal-env)) (dolist (binding core:ns) (env:set-env *repl-env* @@ -66,8 +66,7 @@ (return (env:set-env env (second forms) (mal-eval (third forms) env)))) ((mal-value= mal-let* (first forms)) - (let ((new-env (make-instance 'env:mal-environment - :parent env)) + (let ((new-env (env:create-mal-env :parent env)) ;; Convert a potential vector to a list (bindings (map 'list #'identity @@ -102,12 +101,11 @@ (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) - (mal-eval body (make-instance 'env:mal-environment - :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args))) + (mal-eval body (env:create-mal-env :parent env + :binds (map 'list + #'identity + (mal-data-value arglist)) + :exprs args))) :attrs (list (cons 'params arglist) (cons 'ast body) (cons 'env env)))))) @@ -120,12 +118,11 @@ (cdr evaluated-list))) (let* ((attrs (types:mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) - env (make-instance 'env:mal-environment - :parent (cdr (assoc 'env attrs)) - :binds (map 'list - #'identity - (mal-data-value (cdr (assoc 'params attrs)))) - :exprs (cdr evaluated-list))))))))))))) + env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-data-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))))))))))) (defun mal-print (expression) (printer:pr-str expression)) diff --git a/clisp/step7_quote.lisp b/clisp/step7_quote.lisp index a96c34ff59..d73e4f0f20 100644 --- a/clisp/step7_quote.lisp +++ b/clisp/step7_quote.lisp @@ -11,7 +11,7 @@ (in-package :mal) -(defvar *repl-env* (make-instance 'env:mal-environment)) +(defvar *repl-env* (env:create-mal-env)) (dolist (binding core:ns) (env:set-env *repl-env* @@ -104,8 +104,7 @@ (return (env:set-env env (second forms) (mal-eval (third forms) env)))) ((mal-value= mal-let* (first forms)) - (let ((new-env (make-instance 'env:mal-environment - :parent env)) + (let ((new-env (env:create-mal-env :parent env)) ;; Convert a potential vector to a list (bindings (map 'list #'identity @@ -140,12 +139,11 @@ (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) - (mal-eval body (make-instance 'env:mal-environment - :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args))) + (mal-eval body (env:create-mal-env :parent env + :binds (map 'list + #'identity + (mal-data-value arglist)) + :exprs args))) :attrs (list (cons 'params arglist) (cons 'ast body) (cons 'env env)))))) @@ -158,12 +156,11 @@ (cdr evaluated-list))) (let* ((attrs (types:mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) - env (make-instance 'env:mal-environment - :parent (cdr (assoc 'env attrs)) - :binds (map 'list - #'identity - (mal-data-value (cdr (assoc 'params attrs)))) - :exprs (cdr evaluated-list))))))))))))) + env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-data-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))))))))))) (defun mal-print (expression) (printer:pr-str expression)) diff --git a/clisp/step8_macros.lisp b/clisp/step8_macros.lisp index 13d6fb9ba3..6264e30769 100644 --- a/clisp/step8_macros.lisp +++ b/clisp/step8_macros.lisp @@ -22,7 +22,7 @@ "applying" "defining macro"))))) -(defvar *repl-env* (make-instance 'env:mal-environment)) +(defvar *repl-env* (env:create-mal-env)) (dolist (binding core:ns) (env:set-env *repl-env* @@ -148,8 +148,7 @@ :context "macro"))))) ((mal-value= mal-let* (first forms)) - (let ((new-env (make-instance 'env:mal-environment - :parent env)) + (let ((new-env (env:create-mal-env :parent env)) ;; Convert a potential vector to a list (bindings (map 'list #'identity @@ -184,12 +183,11 @@ (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) - (mal-eval body (make-instance 'env:mal-environment - :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args))) + (mal-eval body (env:create-mal-env :parent env + :binds (map 'list + #'identity + (mal-data-value arglist)) + :exprs args))) :attrs (list (cons 'params arglist) (cons 'ast body) (cons 'env env) @@ -201,12 +199,11 @@ (cond ((types:mal-fn-p function) (let* ((attrs (types:mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) - env (make-instance 'env:mal-environment - :parent (cdr (assoc 'env attrs)) - :binds (map 'list - #'identity - (mal-data-value (cdr (assoc 'params attrs)))) - :exprs (cdr evaluated-list))))) + env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-data-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))) ((types:mal-builtin-fn-p function) (return (apply (mal-data-value function) (cdr evaluated-list)))) diff --git a/clisp/step9_try.lisp b/clisp/step9_try.lisp index 79c9d83d83..2066e95b46 100644 --- a/clisp/step9_try.lisp +++ b/clisp/step9_try.lisp @@ -22,7 +22,7 @@ "applying" "defining macro"))))) -(defvar *repl-env* (make-instance 'env:mal-environment)) +(defvar *repl-env* (env:create-mal-env)) (dolist (binding core:ns) (env:set-env *repl-env* @@ -150,8 +150,7 @@ :context "macro"))))) ((mal-value= mal-let* (first forms)) - (let ((new-env (make-instance 'env:mal-environment - :parent env)) + (let ((new-env (env:create-mal-env :parent env)) ;; Convert a potential vector to a list (bindings (map 'list #'identity @@ -186,12 +185,11 @@ (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) - (mal-eval body (make-instance 'env:mal-environment - :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args))) + (mal-eval body (env:create-mal-env :parent env + :binds (map 'list + #'identity + (mal-data-value arglist)) + :exprs args))) :attrs (list (cons 'params arglist) (cons 'ast body) (cons 'env env) @@ -206,12 +204,11 @@ (when (mal-value= mal-catch* (first catch-forms)) (return (mal-eval (third catch-forms) - (make-instance 'env:mal-environment - :parent env - :binds (list (second catch-forms)) - :exprs (list (if (typep condition 'types:mal-runtime-exception) - (types:make-mal-string (format nil "~a" condition)) - (types::mal-exception-data condition))))))))) + (env:create-mal-env :parent env + :binds (list (second catch-forms)) + :exprs (list (if (typep condition 'types:mal-runtime-exception) + (types:make-mal-string (format nil "~a" condition)) + (types::mal-exception-data condition))))))))) (error condition)))) (t (let* ((evaluated-list (eval-ast ast env)) @@ -220,12 +217,11 @@ (cond ((types:mal-fn-p function) (let* ((attrs (types:mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) - env (make-instance 'env:mal-environment - :parent (cdr (assoc 'env attrs)) - :binds (map 'list - #'identity - (mal-data-value (cdr (assoc 'params attrs)))) - :exprs (cdr evaluated-list))))) + env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-data-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))) ((types:mal-builtin-fn-p function) (return (apply (mal-data-value function) (cdr evaluated-list)))) diff --git a/clisp/stepA_mal.lisp b/clisp/stepA_mal.lisp index a8341b2cc0..3c7135edd5 100644 --- a/clisp/stepA_mal.lisp +++ b/clisp/stepA_mal.lisp @@ -22,7 +22,7 @@ "applying" "defining macro"))))) -(defvar *repl-env* (make-instance 'env:mal-environment)) +(defvar *repl-env* (env:create-mal-env)) (dolist (binding core:ns) (env:set-env *repl-env* @@ -149,8 +149,7 @@ :context "macro"))))) ((mal-value= mal-let* (first forms)) - (let ((new-env (make-instance 'env:mal-environment - :parent env)) + (let ((new-env (env:create-mal-env :parent env)) ;; Convert a potential vector to a list (bindings (map 'list #'identity @@ -185,12 +184,11 @@ (return (let ((arglist (second forms)) (body (third forms))) (types:make-mal-fn (lambda (&rest args) - (mal-eval body (make-instance 'env:mal-environment - :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args))) + (mal-eval body (env:create-mal-env :parent env + :binds (map 'list + #'identity + (mal-data-value arglist)) + :exprs args))) :attrs (list (cons 'params arglist) (cons 'ast body) (cons 'env env) @@ -205,13 +203,12 @@ (when (mal-value= mal-catch* (first catch-forms)) (return (mal-eval (third catch-forms) - (make-instance 'env:mal-environment - :parent env - :binds (list (second catch-forms)) - :exprs (list (if (or (typep condition 'types:mal-runtime-exception) - (typep condition 'types:mal-error)) - (types:make-mal-string (format nil "~a" condition)) - (types::mal-exception-data condition))))))))) + (env:create-mal-env :parent env + :binds (list (second catch-forms)) + :exprs (list (if (or (typep condition 'types:mal-runtime-exception) + (typep condition 'types:mal-error)) + (types:make-mal-string (format nil "~a" condition)) + (types::mal-exception-data condition))))))))) (error condition)))) (t (let* ((evaluated-list (eval-ast ast env)) @@ -220,12 +217,11 @@ (cond ((types:mal-fn-p function) (let* ((attrs (types:mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) - env (make-instance 'env:mal-environment - :parent (cdr (assoc 'env attrs)) - :binds (map 'list - #'identity - (mal-data-value (cdr (assoc 'params attrs)))) - :exprs (cdr evaluated-list))))) + env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-data-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))) ((types:mal-builtin-fn-p function) (return (apply (mal-data-value function) (cdr evaluated-list)))) From 60270667b04c94f7c8b3cce1d08e4ede74c95531 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 15 Sep 2016 23:58:55 -0500 Subject: [PATCH 0144/2308] Basic: move environment into normal memory. --- basic/env.in.bas | 26 +++++++++--------- basic/printer.in.bas | 49 ++++++++++++++++----------------- basic/reader.in.bas | 44 +++++++++++++++--------------- basic/step2_eval.in.bas | 6 ++--- basic/step3_env.in.bas | 9 ++++--- basic/step4_if_fn_do.in.bas | 10 +++---- basic/types.in.bas | 54 +++++++++++++++---------------------- 7 files changed, 97 insertions(+), 101 deletions(-) diff --git a/basic/env.in.bas b/basic/env.in.bas index 55a0c9ab21..4e45e299be 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -4,13 +4,15 @@ ENV_NEW: REM allocate the data hashmap GOSUB HASHMAP - REM set the data and outer pointer - ZE%(ZK%)=R% - ZO%(ZK%)=EO% + REM set the outer and data pointer + Z%(ZI%,0) = 14 + Z%(ZI%,1) = R% + Z%(ZI%+1,0) = 14 + Z%(ZI%+1,1) = EO% - REM update environment pointer and return new environment - R%=ZK% - ZK%=ZK%+1 + REM allocate space and return new environment + R%=ZI% + ZI%=ZI%+2 RETURN REM ENV_NEW_BINDS(EO%, BI%, EX%) -> R% @@ -51,17 +53,17 @@ ENV_NEW_BINDS: REM ENV_SET(E%, K%, V%) -> R% ENV_SET: - HM%=ZE%(E%) + HM%=Z%(E%,1) GOSUB ASSOC1 - ZE%(E%)=R% + Z%(E%,1)=R% R%=V% RETURN REM ENV_SET_S(E%, K$, V%) -> R% ENV_SET_S: - HM%=ZE%(E%) + HM%=Z%(E%,1) GOSUB ASSOC1_S - ZE%(E%)=R% + Z%(E%,1)=R% R%=V% RETURN @@ -69,12 +71,12 @@ REM ENV_FIND(E%, K%) -> R% ENV_FIND: EF%=E% ENV_FIND_LOOP: - HM%=ZE%(EF%) + HM%=Z%(EF%,1) REM More efficient to use GET for value (R%) and contains? (T3%) GOSUB HASHMAP_GET REM if we found it, save value in T4% for ENV_GET IF T3%=1 THEN T4%=R%: GOTO ENV_FIND_DONE - EF%=ZO%(EF%): REM get outer environment + EF%=Z%(EF%+1,1): REM get outer environment IF EF%<>-1 THEN GOTO ENV_FIND_LOOP ENV_FIND_DONE: R%=EF% diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 69ae55673e..0413989219 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -1,8 +1,10 @@ REM PR_STR(AZ%, PR%) -> R$ PR_STR: + RR$="" + PR_STR_RECUR: T%=Z%(AZ%,0) REM PRINT "AZ%: " + STR$(AZ%) + ", T%: " + STR$(T%) + ", V%: " + STR$(Z%(AZ%,1)) - IF T%=15 THEN AZ%=Z%(AZ%,1): GOTO PR_STR + IF T%=15 THEN AZ%=Z%(AZ%,1): GOTO PR_STR_RECUR IF T%=0 THEN R$="nil": RETURN IF (T%=1) AND (Z%(AZ%,1)=0) THEN R$="false": RETURN IF (T%=1) AND (Z%(AZ%,1)=1) THEN R$="true": RETURN @@ -11,10 +13,10 @@ PR_STR: IF (T%=4) AND (PR%=1) THEN PR_STRING_READABLY IF T%=5 THEN PR_SYMBOL IF T%=6 THEN PR_SEQ + IF T%=7 THEN PR_SEQ IF T%=8 THEN PR_SEQ - IF T%=10 THEN PR_SEQ - IF T%=12 THEN PR_FUNCTION - IF T%=13 THEN PR_MAL_FUNCTION + IF T%=9 THEN PR_FUNCTION + IF T%=10 THEN PR_MAL_FUNCTION R$="#" RETURN @@ -35,39 +37,38 @@ PR_STR: R$=ZS$(Z%(AZ%,1)) RETURN PR_SEQ: - IF PT%=-1 THEN RR$="" IF T%=6 THEN RR$=RR$+"(" - IF T%=8 THEN RR$=RR$+"[" - IF T%=10 THEN RR$=RR$+"{" + IF T%=7 THEN RR$=RR$+"[" + IF T%=8 THEN RR$=RR$+"{" REM push where we are in the sequence - PT%=PT%+1 - PS%(PT%)= AZ% + ZL%=ZL%+1 + ZZ%(ZL%)= AZ% PR_SEQ_LOOP: IF Z%(AZ%,1) = 0 THEN PR_SEQ_DONE AZ%=AZ%+1 REM Push type we are rendering on the stack - PT%=PT%+1 - PS%(PT%) = Z%(AZ%,0) - GOSUB PR_STR + ZL%=ZL%+1 + ZZ%(ZL%) = Z%(AZ%,0) + GOSUB PR_STR_RECUR REM if we just rendered a non-sequence, then append it - IF (T% < 6) OR (T% > 11) THEN RR$=RR$+R$ + IF (T% < 6) OR (T% > 8) THEN RR$=RR$+R$ REM pop type off stack and check it - T%=PS%(PT%) - PT%=PT%-1 + T%=ZZ%(ZL%) + ZL%=ZL%-1 REM Go to next list element - AZ%=Z%(PS%(PT%),1) - PS%(PT%) = AZ% + AZ%=Z%(ZZ%(ZL%),1) + ZZ%(ZL%) = AZ% IF Z%(AZ%,1) <> 0 THEN RR$=RR$+" " GOTO PR_SEQ_LOOP PR_SEQ_DONE: REM get current type - T%=Z%(PS%(PT%),0) + T%=Z%(ZZ%(ZL%),0) REM pop where we are the sequence - PT%=PT%-1 + ZL%=ZL%-1 IF T%=6 THEN RR$=RR$+")" - IF T%=8 THEN RR$=RR$+"]" - IF T%=10 THEN RR$=RR$+"}" - IF PT%=-1 THEN R$=RR$ + IF T%=7 THEN RR$=RR$+"]" + IF T%=8 THEN RR$=RR$+"}" + R$=RR$ RETURN PR_FUNCTION: T1%=Z%(AZ%,1) @@ -75,9 +76,9 @@ PR_STR: RETURN PR_MAL_FUNCTION: T1%=AZ% - AZ%=Z%(T1%+1,0): GOSUB PR_STR + AZ%=Z%(T1%+1,0): GOSUB PR_STR_RECUR T7$="(fn* " + R$ - AZ%=Z%(T1%,1): GOSUB PR_STR + AZ%=Z%(T1%,1): GOSUB PR_STR_RECUR R$=T7$ + " " + R$ + ")" RETURN diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 75276b8a89..7858bb9917 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -55,10 +55,10 @@ READ_FORM: IF (CH$ = CHR$(34)) THEN READ_STRING IF (CH$ = "(") THEN T%=6: GOTO READ_SEQ IF (CH$ = ")") THEN T%=6: GOTO READ_SEQ_END - IF (CH$ = "[") THEN T%=8: GOTO READ_SEQ - IF (CH$ = "]") THEN T%=8: GOTO READ_SEQ_END - IF (CH$ = "{") THEN T%=10: GOTO READ_SEQ - IF (CH$ = "}") THEN T%=10: GOTO READ_SEQ_END + IF (CH$ = "[") THEN T%=7: GOTO READ_SEQ + IF (CH$ = "]") THEN T%=7: GOTO READ_SEQ_END + IF (CH$ = "{") THEN T%=8: GOTO READ_SEQ + IF (CH$ = "}") THEN T%=8: GOTO READ_SEQ_END GOTO READ_SYMBOL READ_SCALAR: @@ -98,41 +98,43 @@ READ_FORM: READ_SEQ: REM PRINT "READ_SEQ" + SD%=SD%+1: REM increase read sequence depth REM push start ptr on the stack - PT%=PT%+1 - PS%(PT%) = ZI% + ZL%=ZL%+1 + ZZ%(ZL%) = ZI% REM push current sequence type - PT%=PT%+1 - PS%(PT%) = T% + ZL%=ZL%+1 + ZZ%(ZL%) = T% REM push current ptr on the stack - PT%=PT%+1 - PS%(PT%) = ZI% + ZL%=ZL%+1 + ZZ%(ZL%) = ZI% GOTO READ_FORM_DONE READ_SEQ_END: REM PRINT "READ_SEQ_END" - IF PT%=-1 THEN ER%=1: ER$="unexpected ')'": RETURN + IF SD%=0 THEN ER%=1: ER$="unexpected '" + CH$ + "'": RETURN + SD%=SD%-1: REM increase read sequence depth REM Set return value to current sequence - PT%=PT%-2: REM pop current ptr and type off the stack - R%=PS%(PT%): REM ptr to start of sequence to return - PT%=PT%-1: REM pop start ptr off the stack - IF (PS%(PT%+2)) <> T% THEN ER%=1: ER$="sequence mismatch": RETURN + ZL%=ZL%-2: REM pop current ptr and type off the stack + R%=ZZ%(ZL%): REM ptr to start of sequence to return + ZL%=ZL%-1: REM pop start ptr off the stack + IF (ZZ%(ZL%+2)) <> T% THEN ER%=1: ER$="sequence mismatch": RETURN GOTO READ_FORM_DONE READ_FORM_DONE: IDX%=IDX%+LEN(T$) - REM check PS% stack - IF PT%=-1 THEN RETURN + REM check read sequence depth + IF SD%=0 THEN RETURN IF T$="" THEN ER%=1: ER$="unexpected EOF": RETURN REM add list end entry (next pointer is 0 for now) REM PRINT "READ_FORM_DONE next list entry" - Z%(ZI%,0) = PS%(PT%- 1) + Z%(ZI%,0) = ZZ%(ZL%- 1) Z%(ZI%,1) = 0 REM update prior pointer if not first - IF PS%(PT%)<>ZI% THEN Z%(PS%(PT%),1) = ZI% + IF ZZ%(ZL%)<>ZI% THEN Z%(ZZ%(ZL%),1) = ZI% REM update previous pointer to outself - PS%(PT%) = ZI% + ZZ%(ZL%) = ZI% ZI%=ZI%+1: REM slot for list element GOTO READ_FORM @@ -140,6 +142,6 @@ READ_FORM: REM READ_STR(A$) -> R% READ_STR: IDX%=1 - PT%=-1 + SD%=0: REM sequence read depth GOSUB READ_FORM RETURN diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index 37e8333a8d..d2dee131b4 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -21,8 +21,8 @@ EVAL_AST: T%=Z%(A%,0) IF T%=5 THEN EVAL_AST_SYMBOL IF T%=6 THEN EVAL_AST_SEQ + IF T%=7 THEN EVAL_AST_SEQ IF T%=8 THEN EVAL_AST_SEQ - IF T%=10 THEN EVAL_AST_SEQ R%=A% GOTO EVAL_AST_RETURN @@ -64,7 +64,7 @@ EVAL_AST: REM if hashmap, skip eval of even entries (keys) R%=A%+1 - IF (ZZ%(ZL%-3)=10) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_SEQ_SKIP + IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_SEQ_SKIP REM call EVAL for each entry A%=A%+1: GOSUB EVAL @@ -120,7 +120,7 @@ EVAL: AR%=Z%(R%,1): REM REST R%=F%: GOSUB DEREF F%=R% - IF Z%(F%,0)<>12 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + IF Z%(F%,0)<>9 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN GOSUB DO_FUNCTION GOTO EVAL_RETURN diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index 685a86d1a0..528d98ad84 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -22,8 +22,8 @@ EVAL_AST: T%=Z%(A%,0) IF T%=5 THEN EVAL_AST_SYMBOL IF T%=6 THEN EVAL_AST_SEQ + IF T%=7 THEN EVAL_AST_SEQ IF T%=8 THEN EVAL_AST_SEQ - IF T%=10 THEN EVAL_AST_SEQ R%=A% GOTO EVAL_AST_RETURN @@ -64,7 +64,7 @@ EVAL_AST: REM if hashmap, skip eval of even entries (keys) R%=A%+1 - IF (ZZ%(ZL%-3)=10) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_SEQ_SKIP + IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_SEQ_SKIP REM call EVAL for each entry A%=A%+1: GOSUB EVAL @@ -173,7 +173,7 @@ EVAL: AR%=Z%(R%,1): REM REST R%=F%: GOSUB DEREF F%=R% - IF Z%(F%,0)<>12 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + IF Z%(F%,0)<>9 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN GOSUB DO_FUNCTION GOTO EVAL_RETURN @@ -252,6 +252,7 @@ MAIN: EO%=-1: GOSUB ENV_NEW RE%=R% + E%=RE% REM + function A%=1: GOSUB NATIVE_FUNCTION K$="+": V%=R%: GOSUB ENV_SET_S @@ -268,7 +269,7 @@ MAIN: A%=4: GOSUB NATIVE_FUNCTION K$="/": V%=R%: GOSUB ENV_SET_S - AZ%=ZE%(RE%): GOSUB PR_STR + AZ%=Z%(RE%,1): GOSUB PR_STR PRINT "env: " + R$ + "(" + STR$(RE%) + ")" MAIN_LOOP: diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index 393f652d41..a363bb7d61 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -27,8 +27,8 @@ EVAL_AST: T%=Z%(A%,0) IF T%=5 THEN EVAL_AST_SYMBOL IF T%=6 THEN EVAL_AST_SEQ + IF T%=7 THEN EVAL_AST_SEQ IF T%=8 THEN EVAL_AST_SEQ - IF T%=10 THEN EVAL_AST_SEQ R%=A% GOTO EVAL_AST_RETURN @@ -69,7 +69,7 @@ EVAL_AST: REM if hashmap, skip eval of even entries (keys) R%=A%+1 - IF (ZZ%(ZL%-3)=10) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_SEQ_SKIP + IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_SEQ_SKIP REM call EVAL for each entry A%=A%+1: GOSUB EVAL @@ -239,8 +239,8 @@ EVAL: R%=F%: GOSUB DEREF F%=R% - IF Z%(F%,0)=12 THEN GOTO EVAL_DO_FUNCTION - IF Z%(F%,0)=13 THEN GOTO EVAL_DO_MAL_FUNCTION + IF Z%(F%,0)=9 THEN GOTO EVAL_DO_FUNCTION + IF Z%(F%,0)=10 THEN GOTO EVAL_DO_MAL_FUNCTION ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION @@ -291,7 +291,7 @@ MAIN: REM set core functions in repl_env E%=RE%: GOSUB INIT_CORE_NS - REM AZ%=ZE%(RE%): GOSUB PR_STR + REM AZ%=Z%(RE%,1): GOSUB PR_STR REM PRINT "env: " + R$ + "(" + STR$(RE%) + ")" REM B% = PEEK(57) + PEEK(58) * 256 diff --git a/basic/types.in.bas b/basic/types.in.bas index dd48bc590d..87359014a7 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -5,25 +5,26 @@ REM integer 2 -> int value REM float 3 -> ??? REM string/kw 4 -> ZS$ index REM symbol 5 -> ZS$ index -REM list next/val 6 -> next Z% index / or 0 +REM list next/val 6 -> next Z% index (0 for last) REM followed by value (unless empty) -REM vector next/val 8 -> next Z% index / or 0 +REM vector next/val 7 -> next Z% index (0 for last) REM followed by value (unless empty) -REM hashmap next/val 10 -> next Z% index / or 0 +REM hashmap next/val 8 -> next Z% index (0 for last) REM followed by key or value (alternating) -REM function 12 -> function index -REM mal function 13 -> ??? -REM atom 14 -> Z% index +REM function 9 -> function index +REM mal function 10 -> ??? +REM atom 11 -> Z% index +REM environment 14 -> data/hashmap Z% index +REM followed by 14 and outer Z% index (-1 for none) REM reference/ptr 15 -> Z% index / or 0 INIT_MEMORY: T%=FRE(0) - S1%=4096+512: REM Z% (boxed memory) size (X2) + S1%=4096+512+256: REM Z% (boxed memory) size (X2) S2%=256: REM ZS% (string memory) size - S3%=256: REM ZE%,ZO% (environments) size - S4%=256: REM ZZ% (call stack) size - S5%=64: REM PS% (logic stack) size + S3%=256: REM ZZ% (call stack) size + S4%=64: REM PS% (logic stack) size REM global error state ER%=0 @@ -45,18 +46,9 @@ INIT_MEMORY: ZJ%=0 DIM ZS$(S2%) - REM environments - ZK%=0 - DIM ZE%(S3%): REM data hashmap Z% index - DIM ZO%(S3%): REM outer ZE% index (or -1) - - REM call stack + REM call/logic stack ZL%=-1 - DIM ZZ%(S4%): REM stack of Z% indexes - - REM logic stack - PT%=-1: REM index of top of PS% stack - DIM PS%(S5%): REM stack of Z% indexes + DIM ZZ%(S3%): REM stack of Z% indexes REM PRINT "Lisp data memory: " + STR$(T%-FRE(0)) REM PRINT "Interpreter working memory: " + STR$(FRE(0)) @@ -69,9 +61,7 @@ PR_MEMORY_SUMMARY: PRINT "Free memory (FRE) : " + STR$(FRE(0)) PRINT "Boxed values (Z%) : " + STR$(ZI%) + " /" + STR$(S1%) PRINT "String values (ZS$) : " + STR$(ZJ%) + " /" + STR$(S2%) - PRINT "Environments (ZE%) : " + STR$(ZK%) + " /" + STR$(S3%) - PRINT "Call stack size (ZZ%) : " + STR$(ZL%+1) + " /" + STR$(S4%) - PRINT "Logic stack size (PS%) : " + STR$(PT%+1) + " /" + STR$(S5%) + PRINT "Call stack size (ZZ%) : " + STR$(ZL%+1) + " /" + STR$(S3%) RETURN PR_MEMORY: @@ -94,10 +84,10 @@ REM EQUAL_Q(A%, B%) -> R% EQUAL_Q: R%=0 U1%=Z%(A%,0): U2%=Z%(B%,0) - IF NOT ((U1%=U2%) OR ((U1%=6 OR U1%=8) AND (U2%=6 OR U2%=8))) THEN RETURN + IF NOT ((U1%=U2%) OR ((U1%=6 OR U1%=7) AND (U2%=6 OR U2%=7))) THEN RETURN IF U1%=6 THEN GOTO EQUAL_Q_SEQ - IF U1%=8 THEN GOTO EQUAL_Q_SEQ - IF U1%=10 THEN GOTO EQUAL_Q_HM + IF U1%=7 THEN GOTO EQUAL_Q_SEQ + IF U1%=8 THEN GOTO EQUAL_Q_HM IF Z%(A%,1)=Z%(B%,1) THEN R%=1 RETURN @@ -159,7 +149,7 @@ REM hashmap functions REM HASHMAP() -> R% HASHMAP: - Z%(ZI%,0) = 10 + Z%(ZI%,0) = 8 Z%(ZI%,1) = 0 R%=ZI% ZI%=ZI%+1 @@ -169,14 +159,14 @@ REM ASSOC1(HM%, K%, V%) -> R% ASSOC1: R%=ZI% REM key ptr - Z%(ZI%,0) = 10 + Z%(ZI%,0) = 8 Z%(ZI%,1) = ZI%+2: REM value ZI%=ZI%+1 Z%(ZI%,0) = 15 Z%(ZI%,1) = K% ZI%=ZI%+1 REM value ptr - Z%(ZI%,0) = 10 + Z%(ZI%,0) = 8 Z%(ZI%,1) = HM%: REM hashmap to assoc onto ZI%=ZI%+1 Z%(ZI%,0) = 15 @@ -225,7 +215,7 @@ HASHMAP_CONTAINS: REM NATIVE_FUNCTION(A%) -> R% NATIVE_FUNCTION: - Z%(ZI%,0) = 12 + Z%(ZI%,0) = 9 Z%(ZI%,1) = A% R%=ZI% ZI%=ZI%+1 @@ -233,7 +223,7 @@ NATIVE_FUNCTION: REM NATIVE_FUNCTION(A%, P%, E%) -> R% MAL_FUNCTION: - Z%(ZI%,0) = 13 + Z%(ZI%,0) = 10 Z%(ZI%,1) = A% Z%(ZI%+1,0) = P% Z%(ZI%+1,1) = E% From 4b84a23b07aa30ce3f1badf149759d5197923006 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 16 Sep 2016 01:00:58 -0500 Subject: [PATCH 0145/2308] Basic: Add memory management. More step4. Also: - More/better detail in PR_MEMORY/PR_MEMORY_SUMMARY - Fix *.prg image build (for running under Vice). Have to lower-case everything since what C64/Vice shows as capitals are actually lowercase. --- basic/Makefile | 9 +- basic/core.in.bas | 51 +++-- basic/env.in.bas | 28 +-- basic/printer.in.bas | 8 +- basic/reader.in.bas | 123 ++++++++---- basic/step1_read_print.in.bas | 8 +- basic/step2_eval.in.bas | 187 ++++++++++++------- basic/step3_env.in.bas | 230 +++++++++++++++-------- basic/step4_if_fn_do.in.bas | 199 +++++++++++++------- basic/types.in.bas | 340 ++++++++++++++++++++++++++++------ 10 files changed, 836 insertions(+), 347 deletions(-) diff --git a/basic/Makefile b/basic/Makefile index e366da4d6b..2d01061b68 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -3,7 +3,10 @@ step%.bas: step%.in.bas ./qb2cbm.sh $< > $@ step%.prg: step%.bas - petcat -text -w2 -o $@ $< + cat $< | tr "A-Z" "a-z" > $<.tmp + #cat $< | sed 's/["]\@["]\@!/\L&/g' > $<.tmp + petcat -text -w2 -o $@ $<.tmp + #rm $<.tmp step0_repl.bas: readline.in.bas step1_read_print.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas @@ -15,7 +18,9 @@ tests/%.bas: tests/%.in.bas ./qb2cbm.sh $< > $@ tests/%.prg: tests/%.bas - petcat -text -w2 -o $@ $< + cat $< | tr "A-Z" "a-z" > $<.tmp + petcat -text -w2 -o $@ $<.tmp + rm $<.tmp SOURCES_LISP = env.in.bas core.in.bas step4_if_fn_do.in.bas diff --git a/basic/core.in.bas b/basic/core.in.bas index 415149e5a9..933a28fe62 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -5,10 +5,8 @@ DO_FUNCTION: FF%=Z%(F%,1) REM Get argument values - R%=AR%+1: GOSUB DEREF - AA%=R% - R%=Z%(AR%,1)+1: GOSUB DEREF - AB%=R% + R%=AR%+1: GOSUB DEREF_R: AA%=R% + R%=Z%(AR%,1)+1: GOSUB DEREF_R: AB%=R% REM Switch on the function number IF FF%=1 THEN DO_EQUAL_Q @@ -45,18 +43,18 @@ DO_FUNCTION: DO_PR_STR: AZ%=AR%: PR%=1: SE$=" ": GOSUB PR_STR_SEQ AS$=R$: GOSUB STRING - Z%(ZI%,0) = 4 - Z%(ZI%,1) = R% - R%=ZI% - ZI%=ZI%+1 + R4%=R% + SZ%=1: GOSUB ALLOC + Z%(R%,0) = 4+16 + Z%(R%,1) = R4% RETURN DO_STR: AZ%=AR%: PR%=0: SE$="": GOSUB PR_STR_SEQ AS$=R$: GOSUB STRING - Z%(ZI%,0) = 4 - Z%(ZI%,1) = R% - R%=ZI% - ZI%=ZI%+1 + R4%=R% + SZ%=1: GOSUB ALLOC + Z%(R%,0) = 4+16 + Z%(R%,1) = R4% RETURN DO_PRN: AZ%=AR%: PR%=1: SE$=" ": GOSUB PR_STR_SEQ @@ -87,28 +85,29 @@ DO_FUNCTION: RETURN DO_ADD: - R%=ZI%: ZI%=ZI%+1: REM Allocate result value - Z%(R%,0)=2 + SZ%=1: GOSUB ALLOC + Z%(R%,0)=2+16 Z%(R%,1)=Z%(AA%,1)+Z%(AB%,1) RETURN DO_SUB: - R%=ZI%: ZI%=ZI%+1: REM Allocate result value - Z%(R%,0)=2 + SZ%=1: GOSUB ALLOC + Z%(R%,0)=2+16 Z%(R%,1)=Z%(AA%,1)-Z%(AB%,1) RETURN DO_MULT: - R%=ZI%: ZI%=ZI%+1: REM Allocate result value - Z%(R%,0)=2 + SZ%=1: GOSUB ALLOC + Z%(R%,0)=2+16 Z%(R%,1)=Z%(AA%,1)*Z%(AB%,1) RETURN DO_DIV: - R%=ZI%: ZI%=ZI%+1: REM Allocate result value - Z%(R%,0)=2 + SZ%=1: GOSUB ALLOC + Z%(R%,0)=2+16 Z%(R%,1)=Z%(AA%,1)/Z%(AB%,1) RETURN DO_LIST: R%=AR% + Z%(R%,0)=Z%(R%,0)+16 RETURN DO_LIST_Q: A%=AA%: GOSUB LIST_Q @@ -120,13 +119,11 @@ DO_FUNCTION: IF Z%(AA%,1)=0 THEN R%=2 RETURN DO_COUNT: - R%=-1 - DO_COUNT_LOOP: - R%=R%+1 - IF Z%(AA%,1)<>0 THEN AA%=Z%(AA%,1): GOTO DO_COUNT_LOOP - Z%(ZI%,0) = 2 - Z%(ZI%,1) = R% - R%=ZI%: ZI%=ZI%+1: REM Allocate result value + A%=AA%: GOSUB COUNT + R4%=R% + SZ%=1: GOSUB ALLOC + Z%(R%,0) = 2+16 + Z%(R%,1) = R4% RETURN DO_PR_MEMORY: diff --git a/basic/env.in.bas b/basic/env.in.bas index 4e45e299be..c16ea6fc5a 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -3,18 +3,19 @@ REM ENV_NEW(EO%) -> R% ENV_NEW: REM allocate the data hashmap GOSUB HASHMAP + E1%=R% REM set the outer and data pointer - Z%(ZI%,0) = 14 - Z%(ZI%,1) = R% - Z%(ZI%+1,0) = 14 - Z%(ZI%+1,1) = EO% - - REM allocate space and return new environment - R%=ZI% - ZI%=ZI%+2 + SZ%=2: GOSUB ALLOC + Z%(R%,0) = 13+16 + Z%(R%,1) = E1% + Z%(R%+1,0) = 13 + Z%(R%+1,1) = EO% + IF EO%<>-1 THEN Z%(EO%,0)=Z%(EO%,0)+16 RETURN +REM see RELEASE types.in.bas for environment cleanup + REM ENV_NEW_BINDS(EO%, BI%, EX%) -> R% ENV_NEW_BINDS: GOSUB ENV_NEW @@ -23,14 +24,14 @@ ENV_NEW_BINDS: ENV_NEW_BINDS_LOOP: IF Z%(BI%,1)=0 THEN R%=E%: RETURN REM get/deref the key from BI% - R%=BI%+1: GOSUB DEREF + R%=BI%+1: GOSUB DEREF_R K%=R% IF ZS$(Z%(K%,1))="&" THEN EVAL_NEW_BINDS_VARGS: EVAL_NEW_BINDS_1x1: REM get/deref the key from EX% - R%=EX%+1: GOSUB DEREF + R%=EX%+1: GOSUB DEREF_R V%=R% REM set the binding in the environment data GOSUB ENV_SET @@ -42,7 +43,7 @@ ENV_NEW_BINDS: EVAL_NEW_BINDS_VARGS: REM get/deref the key from next element of BI% BI%=Z%(BI%,1) - R%=BI%+1: GOSUB DEREF + R%=BI%+1: GOSUB DEREF_R K%=R% REM the value is the remaining list in EX% V%=EX% @@ -85,6 +86,7 @@ ENV_FIND: REM ENV_GET(E%, K%) -> R% ENV_GET: GOSUB ENV_FIND - IF R%=-1 THEN ER%=1: ER$="'" + ZS$(Z%(K%,1)) + "' not found": RETURN - R%=T4% + IF R%=-1 THEN R%=0: ER%=1: ER$="'" + ZS$(Z%(K%,1)) + "' not found": RETURN + R%=T4%: GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 RETURN diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 0413989219..6d2e768cb9 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -2,9 +2,9 @@ REM PR_STR(AZ%, PR%) -> R$ PR_STR: RR$="" PR_STR_RECUR: - T%=Z%(AZ%,0) + T%=Z%(AZ%,0)AND15 REM PRINT "AZ%: " + STR$(AZ%) + ", T%: " + STR$(T%) + ", V%: " + STR$(Z%(AZ%,1)) - IF T%=15 THEN AZ%=Z%(AZ%,1): GOTO PR_STR_RECUR + IF T%=14 THEN AZ%=Z%(AZ%,1): GOTO PR_STR_RECUR IF T%=0 THEN R$="nil": RETURN IF (T%=1) AND (Z%(AZ%,1)=0) THEN R$="false": RETURN IF (T%=1) AND (Z%(AZ%,1)=1) THEN R$="true": RETURN @@ -48,7 +48,7 @@ PR_STR: AZ%=AZ%+1 REM Push type we are rendering on the stack ZL%=ZL%+1 - ZZ%(ZL%) = Z%(AZ%,0) + ZZ%(ZL%) = Z%(AZ%,0)AND15 GOSUB PR_STR_RECUR REM if we just rendered a non-sequence, then append it IF (T% < 6) OR (T% > 8) THEN RR$=RR$+R$ @@ -62,7 +62,7 @@ PR_STR: GOTO PR_SEQ_LOOP PR_SEQ_DONE: REM get current type - T%=Z%(ZZ%(ZL%),0) + T%=Z%(ZZ%(ZL%),0)AND15 REM pop where we are the sequence ZL%=ZL%-1 IF T%=6 THEN RR$=RR$+")" diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 7858bb9917..1a1d3d1cb1 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -44,9 +44,9 @@ READ_FORM: GOSUB READ_TOKEN REM PRINT "READ_FORM T$: [" + T$ + "]" IF (T$="") THEN R%=0: GOTO READ_FORM_DONE - IF (T$="nil") THEN T%=0: GOTO READ_SCALAR - IF (T$="false") THEN T%=1: GOTO READ_SCALAR - IF (T$="true") THEN T%=2: GOTO READ_SCALAR + IF (T$="nil") THEN T%=0: GOTO READ_NIL_BOOL + IF (T$="false") THEN T%=1: GOTO READ_NIL_BOOL + IF (T$="true") THEN T%=2: GOTO READ_NIL_BOOL CH$=MID$(T$,1,1) REM PRINT "CH$: [" + CH$ + "](" + STR$(ASC(CH$)) + ")" IF (CH$ >= "0") AND (CH$ <= "9") THEN READ_NUMBER @@ -61,27 +61,28 @@ READ_FORM: IF (CH$ = "}") THEN T%=8: GOTO READ_SEQ_END GOTO READ_SYMBOL - READ_SCALAR: - Z%(ZI%,0) = 15 - Z%(ZI%,1) = T% - R%=ZI% - ZI%=ZI%+1 + READ_NIL_BOOL: + REM PRINT "READ_NIL_BOOL" + SZ%=1: GOSUB ALLOC + Z%(R%,0) = 14+16 + Z%(R%,1) = T% GOTO READ_FORM_DONE READ_NUMBER: REM PRINT "READ_NUMBER" - Z%(ZI%,0) = 2 - Z%(ZI%,1) = VAL(T$) - R%=ZI% - ZI%=ZI%+1 + SZ%=1: GOSUB ALLOC + Z%(R%,0) = 2+16 + Z%(R%,1) = VAL(T$) GOTO READ_FORM_DONE READ_STRING: + T7$=MID$(T$,LEN(T$),1) + IF T7$<>CHR$(34) THEN ER$="expected '"+CHR$(34)+"'": GOTO READ_FORM_ABORT REM PRINT "READ_STRING" REM intern string value AS$=MID$(T$, 2, LEN(T$)-2): GOSUB STRING - Z%(ZI%,0) = 4 - Z%(ZI%,1) = R% - R%=ZI% - ZI%=ZI%+1 + T7%=R% + SZ%=1: GOSUB ALLOC + Z%(R%,0) = 4+16 + Z%(R%,1) = T7% GOTO READ_FORM_DONE READ_SYMBOL_MAYBE: CH$=MID$(T$,2,1) @@ -90,54 +91,96 @@ READ_FORM: REM PRINT "READ_SYMBOL" REM intern string value AS$=T$: GOSUB STRING - Z%(ZI%,0) = 5 - Z%(ZI%,1) = R% - R%=ZI% - ZI%=ZI%+1 + T7%=R% + SZ%=1: GOSUB ALLOC + Z%(R%,0) = 5+16 + Z%(R%,1) = T7% GOTO READ_FORM_DONE READ_SEQ: REM PRINT "READ_SEQ" SD%=SD%+1: REM increase read sequence depth + + REM allocate first sequence entry and space for value + SZ%=2: GOSUB ALLOC + + REM set reference value/pointer to new embedded sequence + IF SD%>1 THEN Z%(ZZ%(ZL%)+1,1)=R% + + REM set the type (with 1 ref cnt) and next pointer to current end + Z%(R%,0) = T%+16 + Z%(R%,1) = 0 + Z%(R%+1,0) = 14 + Z%(R%+1,1) = 0 + REM push start ptr on the stack ZL%=ZL%+1 - ZZ%(ZL%) = ZI% + ZZ%(ZL%) = R% REM push current sequence type ZL%=ZL%+1 ZZ%(ZL%) = T% - REM push current ptr on the stack + REM push previous ptr on the stack ZL%=ZL%+1 - ZZ%(ZL%) = ZI% - GOTO READ_FORM_DONE + ZZ%(ZL%) = R% + + IDX%=IDX%+LEN(T$) + GOTO READ_FORM READ_SEQ_END: REM PRINT "READ_SEQ_END" - IF SD%=0 THEN ER%=1: ER$="unexpected '" + CH$ + "'": RETURN - SD%=SD%-1: REM increase read sequence depth - REM Set return value to current sequence - ZL%=ZL%-2: REM pop current ptr and type off the stack - R%=ZZ%(ZL%): REM ptr to start of sequence to return - ZL%=ZL%-1: REM pop start ptr off the stack - IF (ZZ%(ZL%+2)) <> T% THEN ER%=1: ER$="sequence mismatch": RETURN + IF SD%=0 THEN ER$="unexpected '" + CH$ + "'": GOTO READ_FORM_ABORT + IF ZZ%(ZL%-1)<>T% THEN ER$="sequence mismatch": GOTO READ_FORM_ABORT + SD%=SD%-1: REM decrease read sequence depth + R%=ZZ%(ZL%-2): REM ptr to start of sequence to return + T%=ZZ%(ZL%-1): REM type prior to recur + ZL%=ZL%-3: REM pop previous, type, and start off the stack GOTO READ_FORM_DONE READ_FORM_DONE: IDX%=IDX%+LEN(T$) + + T8%=R%: REM save previous value + REM check read sequence depth IF SD%=0 THEN RETURN - IF T$="" THEN ER%=1: ER$="unexpected EOF": RETURN - REM add list end entry (next pointer is 0 for now) + IF T$="" THEN ER$="unexpected EOF": GOTO READ_FORM_ABORT REM PRINT "READ_FORM_DONE next list entry" - Z%(ZI%,0) = ZZ%(ZL%- 1) - Z%(ZI%,1) = 0 - REM update prior pointer if not first - IF ZZ%(ZL%)<>ZI% THEN Z%(ZZ%(ZL%),1) = ZI% - REM update previous pointer to outself - ZZ%(ZL%) = ZI% - ZI%=ZI%+1: REM slot for list element + + REM allocate new sequence entry and space for value + SZ%=2: GOSUB ALLOC + + REM previous element + T7%=ZZ%(ZL%) + REM set previous list element to point to new element + Z%(T7%,1) = R% + REM set the list value pointer + Z%(T7%+1,1)=T8% + REM set type to previous type, with ref count of 1 (from previous) + Z%(R%,0) = ZZ%(ZL%-1)+16 + Z%(R%,1) = 0: REM current end of sequence + Z%(R%+1,0) = 14 + Z%(R%+1,1) = 0 + + IF T7%=ZZ%(ZL%-2) THEN GOTO READ_FORM_SKIP_FIRST + Z%(T7%,1) = R% + + READ_FORM_SKIP_FIRST: + REM update previous pointer to current element + ZZ%(ZL%) = R% GOTO READ_FORM + READ_FORM_ABORT: + ER%=1 + R%=0 + READ_FORM_ABORT_UNWIND: + IF SD%=0 THEN RETURN + ZL%=ZL%-3: REM pop previous, type, and start off the stack + SD%=SD%-1 + IF SD%=0 THEN AY%=ZZ%(ZL%+1): GOSUB RELEASE + GOTO READ_FORM_ABORT_UNWIND + + REM READ_STR(A$) -> R% READ_STR: diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index 4b359ea504..d0a99647b3 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -27,13 +27,18 @@ REP: A%=R%: GOSUB EVAL IF ER% THEN RETURN A%=R%: GOSUB MAL_PRINT - IF ER% THEN RETURN + + REM Release memory from EVAL + AY%=R%: GOSUB RELEASE + RETURN REM MAIN program MAIN: GOSUB INIT_MEMORY + ZT%=ZI%: REM top of memory after repl_env + MAIN_LOOP: A$="user> " GOSUB READLINE: REM /* call input parser */ @@ -50,6 +55,7 @@ MAIN: GOTO MAIN_LOOP MAIN_DONE: + P1%=ZT%: P2%=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index d2dee131b4..aa6dbd3be1 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -12,96 +12,124 @@ MAL_READ: REM EVAL_AST(A%, E%) -> R% EVAL_AST: + LV%=LV%+1 + ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% IF ER%=1 THEN GOTO EVAL_AST_RETURN REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL_AST: " + R$ + "(" + STR$(R%) + ")" + REM PRINT "EVAL_AST: " + R$ + "(" + STR$(A%) + ")" + REM PRINT "EVAL_AST level: " + STR$(LV%) + + GOSUB DEREF_A - T%=Z%(A%,0) + T%=Z%(A%,0)AND15 IF T%=5 THEN EVAL_AST_SYMBOL IF T%=6 THEN EVAL_AST_SEQ IF T%=7 THEN EVAL_AST_SEQ IF T%=8 THEN EVAL_AST_SEQ - R%=A% + + REM scalar: deref to actual value and inc ref cnt + R%=A%: GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: HM%=E%: K%=A%: GOSUB HASHMAP_GET - IF T3%=0 THEN ER%=1: ER$="'" + ZS$(Z%(A%,1)) + "' not found" + GOSUB DEREF_R + IF T3%=0 THEN ER%=1: ER$="'"+ZS$(Z%(A%,1))+"' not found": GOTO EVAL_AST_RETURN + Z%(R%,0)=Z%(R%,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SEQ: + REM allocate the first entry + SZ%=2: GOSUB ALLOC + + REM make space on the stack + ZL%=ZL%+4 REM push type of sequence - ZL%=ZL%+1 - ZZ%(ZL%)=T% + ZZ%(ZL%-3)=T% REM push sequence index - ZL%=ZL%+1 - ZZ%(ZL%)=-1 + ZZ%(ZL%-2)=-1 REM push future return value (new sequence) - ZL%=ZL%+1 - ZZ%(ZL%)=ZI% + ZZ%(ZL%-1)=R% REM push previous new sequence entry - ZL%=ZL%+1 - ZZ%(ZL%)=ZI% + ZZ%(ZL%)=R% EVAL_AST_SEQ_LOOP: - REM create new sequence entry - Z%(ZI%,0)=ZZ%(ZL%-3) - Z%(ZI%,1)=0 - ZI%=ZI%+1 + REM set new sequence entry type (with 1 ref cnt) + Z%(R%,0)=ZZ%(ZL%-3)+16 + Z%(R%,1)=0 + REM create value ptr placeholder + Z%(R%+1,0)=14 + Z%(R%+1,1)=0 REM update index ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 - REM check if we are done evaluating the sequence + REM check if we are done evaluating the source sequence IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE - REM create value ptr placeholder - Z%(ZI%,0)=15 - Z%(ZI%,1)=0 - ZI%=ZI%+1 - REM if hashmap, skip eval of even entries (keys) - R%=A%+1 - IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_SEQ_SKIP - - REM call EVAL for each entry - A%=A%+1: GOSUB EVAL - A%=A%-1 + IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + GOTO EVAL_AST_DO_EVAL + + EVAL_AST_DO_REF: + R%=A%+1: GOSUB DEREF_R: REM deref to target of referred entry + Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + GOTO EVAL_AST_ADD_VALUE + + EVAL_AST_DO_EVAL: + REM call EVAL for each entry + A%=A%+1: GOSUB EVAL + A%=A%-1 + IF ER%=1 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + GOSUB DEREF_R: REM deref to target of evaluated entry + + EVAL_AST_ADD_VALUE: + + REM update previous value pointer to evaluated entry + Z%(ZZ%(ZL%)+1,1)=R% - EVAL_AST_SEQ_SKIP: + REM allocate the next entry + SZ%=2: GOSUB ALLOC - REM update previous sequence entry to point to current entry - Z%(ZZ%(ZL%),1)=ZI% - REM update previous value pointer to evaluated entry - Z%(ZZ%(ZL%)+1,1)=R% + REM update previous sequence entry value to point to new entry + Z%(ZZ%(ZL%),1)=R% REM update previous ptr to current entry - ZZ%(ZL%)=ZI% + ZZ%(ZL%)=R% - REM process the next sequence entry + REM process the next sequence entry from source list A%=Z%(A%,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - REM pop previous new sequence entry value - ZL%=ZL%-1 - REM pop return value (new seq), index, and seq type - R%=ZZ%(ZL%) - ZL%=ZL%-3 + REM get return value (new seq), index, and seq type + R%=ZZ%(ZL%-1) + REM pop previous, return, index and type + ZL%=ZL%-4 GOTO EVAL_AST_RETURN EVAL_AST_RETURN: + REM pop A% and E% off the stack E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + + LV%=LV%-1 RETURN REM EVAL(A%, E%)) -> R% EVAL: + LV%=LV%+1: REM track basic return stack level + + REM push A% and E% on the stack ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% IF ER%=1 THEN GOTO EVAL_RETURN REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: " + R$ + "(" + STR$(R%) + ")" + REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + ")" + REM PRINT "EVAL level: " + STR$(LV%) + + GOSUB DEREF_A GOSUB LIST_Q IF R% THEN GOTO APPLY_LIST @@ -111,21 +139,33 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R% THEN R%=A%: GOTO EVAL_RETURN + IF R% THEN R%=A%: Z%(R%,0)=Z%(R%,0)+16: GOTO EVAL_RETURN EVAL_INVOKE: GOSUB EVAL_AST + R3%=R% + IF ER%=1 THEN GOTO EVAL_RETURN F%=R%+1 - AR%=Z%(R%,1): REM REST - R%=F%: GOSUB DEREF - F%=R% - IF Z%(F%,0)<>9 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + AR%=Z%(R%,1): REM rest + R%=F%: GOSUB DEREF_R: F%=R% + IF (Z%(F%,0)AND15)<>9 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN GOSUB DO_FUNCTION + AY%=R3%: GOSUB RELEASE GOTO EVAL_RETURN EVAL_RETURN: + REM an error occured, free up any new value + IF ER%=1 THEN AY%=R%: GOSUB RELEASE + + REM trigger GC + TA%=FRE(0) + + REM pop A% and E% off the stack E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + + LV%=LV%-1: REM track basic return stack level + RETURN REM DO_FUNCTION(F%, AR%) @@ -139,14 +179,11 @@ DO_FUNCTION: FF%=Z%(F%,1) REM Get argument values - R%=AR%+1: GOSUB DEREF - AA%=Z%(R%,1) - R%=Z%(AR%,1)+1: GOSUB DEREF - AB%=Z%(R%,1) + R%=AR%+1: GOSUB DEREF_R: AA%=Z%(R%,1) + R%=Z%(AR%,1)+1: GOSUB DEREF_R: AB%=Z%(R%,1) REM Allocate the return value - R%=ZI% - ZI%=ZI%+1 + SZ%=1: GOSUB ALLOC REM Switch on the function number IF FF%=1 THEN DO_ADD @@ -156,19 +193,19 @@ DO_FUNCTION: ER%=1: ER$="unknown function" + STR$(FF%): RETURN DO_ADD: - Z%(R%,0)=2 + Z%(R%,0)=2+16 Z%(R%,1)=AA%+AB% GOTO DO_FUNCTION_DONE DO_SUB: - Z%(R%,0)=2 + Z%(R%,0)=2+16 Z%(R%,1)=AA%-AB% GOTO DO_FUNCTION_DONE DO_MULT: - Z%(R%,0)=2 + Z%(R%,0)=2+16 Z%(R%,1)=AA%*AB% GOTO DO_FUNCTION_DONE DO_DIV: - Z%(R%,0)=2 + Z%(R%,0)=2+16 Z%(R%,1)=AA%/AB% GOTO DO_FUNCTION_DONE @@ -183,13 +220,32 @@ MAL_PRINT: REM REP(A$) -> R$ REM Assume RE% has repl_env REP: + R1%=0: R2%=0 GOSUB MAL_READ - IF ER% THEN RETURN + IF ER% THEN GOTO REP_DONE + R1%=R% + + REM PRINT "After read:" + REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY + A%=R%: E%=RE%: GOSUB EVAL - IF ER% THEN RETURN + IF ER% THEN GOTO REP_DONE + R2%=R% + + REM PRINT "After eval, before print:" + REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY + A%=R%: GOSUB MAL_PRINT - IF ER% THEN RETURN - RETURN + + REP_DONE: + REM Release memory from MAL_READ and EVAL + IF R2%<>0 THEN AY%=R2%: GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + + REM PRINT "After releases:" + REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY + + RETURN REM MAIN program MAIN: @@ -219,14 +275,20 @@ MAIN: HM%=RE%: K$="/": V%=R%: GOSUB ASSOC1_S RE%=R% - AZ%=RE%: GOSUB PR_STR - PRINT "env: " + R$ + "(" + STR$(RE%) + ")" + ZT%=ZI%: REM top of memory after repl_env + + REM AZ%=RE%: GOSUB PR_STR + REM PRINT "env: " + R$ + "(" + STR$(RE%) + ")" MAIN_LOOP: A$="user> " GOSUB READLINE: REM /* call input parser */ IF EOF=1 THEN GOTO MAIN_DONE A$=R$: GOSUB REP: REM /* call REP */ + + REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + REM GOSUB PR_MEMORY_SUMMARY + IF ER% THEN GOTO ERROR PRINT R$ GOTO MAIN_LOOP @@ -238,6 +300,7 @@ MAIN: GOTO MAIN_LOOP MAIN_DONE: + P1%=ZT%: P2%=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index 528d98ad84..9a20900959 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -13,18 +13,28 @@ MAL_READ: REM EVAL_AST(A%, E%) -> R% EVAL_AST: + LV%=LV%+1 + + REM push A% and E% on the stack ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + IF ER%=1 THEN GOTO EVAL_AST_RETURN REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL_AST: " + R$ + "(" + STR$(R%) + ")" + REM PRINT "EVAL_AST: " + R$ + "(" + STR$(A%) + ")" + REM PRINT "EVAL_AST level: " + STR$(LV%) + + GOSUB DEREF_A - T%=Z%(A%,0) + T%=Z%(A%,0)AND15 IF T%=5 THEN EVAL_AST_SYMBOL IF T%=6 THEN EVAL_AST_SEQ IF T%=7 THEN EVAL_AST_SEQ IF T%=8 THEN EVAL_AST_SEQ - R%=A% + + REM scalar: deref to actual value and inc ref cnt + R%=A%: GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -32,76 +42,97 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: + REM allocate the first entry + SZ%=2: GOSUB ALLOC + + REM make space on the stack + ZL%=ZL%+4 REM push type of sequence - ZL%=ZL%+1 - ZZ%(ZL%)=T% + ZZ%(ZL%-3)=T% REM push sequence index - ZL%=ZL%+1 - ZZ%(ZL%)=-1 + ZZ%(ZL%-2)=-1 REM push future return value (new sequence) - ZL%=ZL%+1 - ZZ%(ZL%)=ZI% + ZZ%(ZL%-1)=R% REM push previous new sequence entry - ZL%=ZL%+1 - ZZ%(ZL%)=ZI% + ZZ%(ZL%)=R% EVAL_AST_SEQ_LOOP: - REM create new sequence entry - Z%(ZI%,0)=ZZ%(ZL%-3) - Z%(ZI%,1)=0 - ZI%=ZI%+1 + REM set new sequence entry type (with 1 ref cnt) + Z%(R%,0)=ZZ%(ZL%-3)+16 + Z%(R%,1)=0 + REM create value ptr placeholder + Z%(R%+1,0)=14 + Z%(R%+1,1)=0 REM update index ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 - REM check if we are done evaluating the sequence + REM check if we are done evaluating the source sequence IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE - REM create value ptr placeholder - Z%(ZI%,0)=15 - Z%(ZI%,1)=0 - ZI%=ZI%+1 - REM if hashmap, skip eval of even entries (keys) - R%=A%+1 - IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_SEQ_SKIP - - REM call EVAL for each entry - A%=A%+1: GOSUB EVAL - A%=A%-1 + IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + GOTO EVAL_AST_DO_EVAL + + EVAL_AST_DO_REF: + R%=A%+1: GOSUB DEREF_R: REM deref to target of referred entry + Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + GOTO EVAL_AST_ADD_VALUE + + EVAL_AST_DO_EVAL: + REM call EVAL for each entry + A%=A%+1: GOSUB EVAL + A%=A%-1 + IF ER%=1 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + GOSUB DEREF_R: REM deref to target of evaluated entry + + EVAL_AST_ADD_VALUE: + + REM update previous value pointer to evaluated entry + Z%(ZZ%(ZL%)+1,1)=R% - EVAL_AST_SEQ_SKIP: + REM allocate the next entry + SZ%=2: GOSUB ALLOC - REM update previous sequence entry to point to current entry - Z%(ZZ%(ZL%),1)=ZI% - REM update previous value pointer to evaluated entry - Z%(ZZ%(ZL%)+1,1)=R% + REM update previous sequence entry value to point to new entry + Z%(ZZ%(ZL%),1)=R% REM update previous ptr to current entry - ZZ%(ZL%)=ZI% + ZZ%(ZL%)=R% - REM process the next sequence entry + REM process the next sequence entry from source list A%=Z%(A%,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - REM pop previous new sequence entry value - ZL%=ZL%-1 - REM pop return value (new seq), index, and seq type - R%=ZZ%(ZL%) - ZL%=ZL%-3 + REM if no error, get return value (new seq) + IF ER%=0 THEN R%=ZZ%(ZL%-1) + REM otherwise, free the return value and return nil + IF ER%=1 THEN R%=0: AY%=ZZ%(ZL%-1): GOSUB RELEASE + + REM pop previous, return, index and type + ZL%=ZL%-4 GOTO EVAL_AST_RETURN EVAL_AST_RETURN: + REM pop A% and E% off the stack E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + + LV%=LV%-1 RETURN REM EVAL(A%, E%)) -> R% EVAL: + LV%=LV%+1: REM track basic return stack level + + REM push A% and E% on the stack ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% IF ER%=1 THEN GOTO EVAL_RETURN REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: " + R$ + "(" + STR$(R%) + ")" + REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + ")" + REM PRINT "EVAL level: " + STR$(LV%) + + GOSUB DEREF_A GOSUB LIST_Q IF R% THEN GOTO APPLY_LIST @@ -111,15 +142,14 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R% THEN R%=A%: GOTO EVAL_RETURN + IF R% THEN R%=A%: Z%(R%,0)=Z%(R%,0)+16: GOTO EVAL_RETURN - A0% = A%+1 - R%=A0%: GOSUB DEREF - A0%=R% + A0%=A%+1 + R%=A0%: GOSUB DEREF_R: A0%=R% REM get symbol in A$ - IF Z%(A0%,0)<>5 THEN A$="" - IF Z%(A0%,0)=5 THEN A$=ZS$(Z%(A0%,1)) + IF (Z%(A0%,0)AND15)<>5 THEN A$="" + IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -127,25 +157,33 @@ EVAL: EVAL_GET_A3: A3% = Z%(Z%(Z%(A%,1),1),1)+1 - R%=A3%: GOSUB DEREF - A3%=R% + R%=A3%: GOSUB DEREF_R: A3%=R% EVAL_GET_A2: A2% = Z%(Z%(A%,1),1)+1 - R%=A2%: GOSUB DEREF - A2%=R% + R%=A2%: GOSUB DEREF_R: A2%=R% EVAL_GET_A1: A1% = Z%(A%,1)+1 - R%=A1%: GOSUB DEREF - A1%=R% + R%=A1%: GOSUB DEREF_R: A1%=R% RETURN EVAL_DEF: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% + + REM push A1% + ZL%=ZL%+1: ZZ%(ZL%)=A1% + A%=A2%: GOSUB EVAL: REM eval a2 - K%=A1%: V%=R%: GOSUB ENV_SET: REM set a1 in env to a2 - RETURN + + REM pop A1% + A1%=ZZ%(ZL%): ZL%=ZL%-1 + + REM set a1 in env to a2 + K%=A1%: V%=R%: GOSUB ENV_SET + + GOTO EVAL_RETURN EVAL_LET: + REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% REM create new environment with outer as current environment EO%=E%: GOSUB ENV_NEW @@ -160,25 +198,40 @@ EVAL: A1%=ZZ%(ZL%): ZL%=ZL%-1 REM set environment: even A1% key to odd A1% eval'd above K%=A1%+1: V%=R%: GOSUB ENV_SET + AY%=R%: GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: A%=A2%: GOSUB EVAL: REM eval a2 using let_env - RETURN + REM release the let env + AY%=E%: GOSUB RELEASE + GOTO EVAL_RETURN EVAL_INVOKE: GOSUB EVAL_AST + R3%=R% + IF ER%=1 THEN GOTO EVAL_RETURN F%=R%+1 - AR%=Z%(R%,1): REM REST - R%=F%: GOSUB DEREF - F%=R% - IF Z%(F%,0)<>9 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + AR%=Z%(R%,1): REM rest + R%=F%: GOSUB DEREF_R: F%=R% + IF (Z%(F%,0)AND15)<>9 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN GOSUB DO_FUNCTION + AY%=R3%: GOSUB RELEASE GOTO EVAL_RETURN EVAL_RETURN: + REM an error occured, free up any new value + IF ER%=1 THEN AY%=R%: GOSUB RELEASE + + REM trigger GC + TA%=FRE(0) + + REM pop A% and E% off the stack E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + + LV%=LV%-1: REM track basic return stack level + RETURN REM DO_FUNCTION(F%, AR%) @@ -192,14 +245,11 @@ DO_FUNCTION: FF%=Z%(F%,1) REM Get argument values - R%=AR%+1: GOSUB DEREF - AA%=Z%(R%,1) - R%=Z%(AR%,1)+1: GOSUB DEREF - AB%=Z%(R%,1) + R%=AR%+1: GOSUB DEREF_R: AA%=Z%(R%,1) + R%=Z%(AR%,1)+1: GOSUB DEREF_R: AB%=Z%(R%,1) REM Allocate the return value - R%=ZI% - ZI%=ZI%+1 + SZ%=1: GOSUB ALLOC REM Switch on the function number IF FF%=1 THEN DO_ADD @@ -209,19 +259,19 @@ DO_FUNCTION: ER%=1: ER$="unknown function" + STR$(FF%): RETURN DO_ADD: - Z%(R%,0)=2 + Z%(R%,0)=2+16 Z%(R%,1)=AA%+AB% GOTO DO_FUNCTION_DONE DO_SUB: - Z%(R%,0)=2 + Z%(R%,0)=2+16 Z%(R%,1)=AA%-AB% GOTO DO_FUNCTION_DONE DO_MULT: - Z%(R%,0)=2 + Z%(R%,0)=2+16 Z%(R%,1)=AA%*AB% GOTO DO_FUNCTION_DONE DO_DIV: - Z%(R%,0)=2 + Z%(R%,0)=2+16 Z%(R%,1)=AA%/AB% GOTO DO_FUNCTION_DONE @@ -236,19 +286,40 @@ MAL_PRINT: REM REP(A$) -> R$ REM Assume RE% has repl_env REP: + R1%=0: R2%=0 GOSUB MAL_READ - IF ER% THEN RETURN + IF ER% THEN GOTO REP_DONE + R1%=R% + + REM PRINT "After read:" + REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY + A%=R%: E%=RE%: GOSUB EVAL - IF ER% THEN RETURN + IF ER% THEN GOTO REP_DONE + R2%=R% + + REM PRINT "After eval, before print:" + REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY + A%=R%: GOSUB MAL_PRINT - IF ER% THEN RETURN - RETURN + + REP_DONE: + REM Release memory from MAL_READ and EVAL + IF R2%<>0 THEN AY%=R2%: GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + + REM PRINT "After releases:" + REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY + + RETURN REM MAIN program MAIN: GOSUB INIT_MEMORY - REM repl_env + LV%=0 + + REM create repl_env EO%=-1: GOSUB ENV_NEW RE%=R% @@ -269,14 +340,20 @@ MAIN: A%=4: GOSUB NATIVE_FUNCTION K$="/": V%=R%: GOSUB ENV_SET_S - AZ%=Z%(RE%,1): GOSUB PR_STR - PRINT "env: " + R$ + "(" + STR$(RE%) + ")" + ZT%=ZI%: REM top of memory after repl_env + + REM AZ%=Z%(RE%,1): GOSUB PR_STR + REM PRINT "env: " + R$ + "(" + STR$(RE%) + ")" MAIN_LOOP: A$="user> " GOSUB READLINE: REM /* call input parser */ IF EOF=1 THEN GOTO MAIN_DONE A$=R$: GOSUB REP: REM /* call REP */ + + REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + REM GOSUB PR_MEMORY_SUMMARY + IF ER% THEN GOTO ERROR PRINT R$ GOTO MAIN_LOOP @@ -288,6 +365,7 @@ MAIN: GOTO MAIN_LOOP MAIN_DONE: + P1%=ZT%: P2%=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index a363bb7d61..9323e742b3 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -22,14 +22,19 @@ EVAL_AST: IF ER%=1 THEN GOTO EVAL_AST_RETURN REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL_AST: " + R$ + "(" + STR$(R%) + ")" + REM PRINT "EVAL_AST: " + R$ + "(" + STR$(A%) + ")" - T%=Z%(A%,0) + GOSUB DEREF_A + + T%=Z%(A%,0)AND15 IF T%=5 THEN EVAL_AST_SYMBOL IF T%=6 THEN EVAL_AST_SEQ IF T%=7 THEN EVAL_AST_SEQ IF T%=8 THEN EVAL_AST_SEQ - R%=A% + + REM scalar: deref to actual value and inc ref cnt + R%=A%: GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -37,69 +42,82 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: + REM allocate the first entry + SZ%=2: GOSUB ALLOC + + REM make space on the stack + ZL%=ZL%+4 REM push type of sequence - ZL%=ZL%+1 - ZZ%(ZL%)=T% + ZZ%(ZL%-3)=T% REM push sequence index - ZL%=ZL%+1 - ZZ%(ZL%)=-1 + ZZ%(ZL%-2)=-1 REM push future return value (new sequence) - ZL%=ZL%+1 - ZZ%(ZL%)=ZI% + ZZ%(ZL%-1)=R% REM push previous new sequence entry - ZL%=ZL%+1 - ZZ%(ZL%)=ZI% + ZZ%(ZL%)=R% EVAL_AST_SEQ_LOOP: - REM create new sequence entry - Z%(ZI%,0)=ZZ%(ZL%-3) - Z%(ZI%,1)=0 - ZI%=ZI%+1 + REM set new sequence entry type (with 1 ref cnt) + Z%(R%,0)=ZZ%(ZL%-3)+16 + Z%(R%,1)=0 + REM create value ptr placeholder + Z%(R%+1,0)=14 + Z%(R%+1,1)=0 REM update index ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 - REM check if we are done evaluating the sequence + REM check if we are done evaluating the source sequence IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE - REM create value ptr placeholder - Z%(ZI%,0)=15 - Z%(ZI%,1)=0 - ZI%=ZI%+1 - REM if hashmap, skip eval of even entries (keys) - R%=A%+1 - IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_SEQ_SKIP - - REM call EVAL for each entry - A%=A%+1: GOSUB EVAL - A%=A%-1 + IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + GOTO EVAL_AST_DO_EVAL + + EVAL_AST_DO_REF: + R%=A%+1: GOSUB DEREF_R: REM deref to target of referred entry + Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + GOTO EVAL_AST_ADD_VALUE + + EVAL_AST_DO_EVAL: + REM call EVAL for each entry + A%=A%+1: GOSUB EVAL + A%=A%-1 + IF ER%=1 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + GOSUB DEREF_R: REM deref to target of evaluated entry + + EVAL_AST_ADD_VALUE: + + REM update previous value pointer to evaluated entry + Z%(ZZ%(ZL%)+1,1)=R% - EVAL_AST_SEQ_SKIP: + REM allocate the next entry + SZ%=2: GOSUB ALLOC - REM update previous sequence entry to point to current entry - Z%(ZZ%(ZL%),1)=ZI% - REM update previous value pointer to evaluated entry - Z%(ZZ%(ZL%)+1,1)=R% + REM update previous sequence entry value to point to new entry + Z%(ZZ%(ZL%),1)=R% REM update previous ptr to current entry - ZZ%(ZL%)=ZI% + ZZ%(ZL%)=R% - REM process the next sequence entry + REM process the next sequence entry from source list A%=Z%(A%,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - REM pop previous new sequence entry value - ZL%=ZL%-1 - REM pop return value (new seq), index, and seq type - R%=ZZ%(ZL%) - ZL%=ZL%-3 + REM if no error, get return value (new seq) + IF ER%=0 THEN R%=ZZ%(ZL%-1) + REM otherwise, free the return value and return nil + IF ER%=1 THEN R%=0: AY%=ZZ%(ZL%-1): GOSUB RELEASE + + REM pop previous, return, index and type + ZL%=ZL%-4 GOTO EVAL_AST_RETURN EVAL_AST_RETURN: REM pop A% and E% off the stack E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + REM pop EVAL AST return label/address RN%=ZZ%(ZL%): ZL%=ZL%-1 IF RN%=1 GOTO EVAL_AST_RETURN_1 IF RN%=2 GOTO EVAL_AST_RETURN_2 @@ -118,9 +136,11 @@ EVAL: IF ER%=1 THEN GOTO EVAL_RETURN REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: " + R$ + "(" + STR$(R%) + "), DEPTH: " + STR$(LV%) + REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + ")" REM PRINT "EVAL level: " + STR$(LV%) + GOSUB DEREF_A + GOSUB LIST_Q IF R% THEN GOTO APPLY_LIST REM ELSE @@ -133,15 +153,14 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R% THEN R%=A%: GOTO EVAL_RETURN + IF R% THEN R%=A%: Z%(R%,0)=Z%(R%,0)+16: GOTO EVAL_RETURN - A0% = A%+1 - R%=A0%: GOSUB DEREF - A0%=R% + A0%=A%+1 + R%=A0%: GOSUB DEREF_R: A0%=R% REM get symbol in A$ - IF Z%(A0%,0)<>5 THEN A$="" - IF Z%(A0%,0)=5 THEN A$=ZS$(Z%(A0%,1)) + IF (Z%(A0%,0)AND15)<>5 THEN A$="" + IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -152,30 +171,33 @@ EVAL: EVAL_GET_A3: A3% = Z%(Z%(Z%(A%,1),1),1)+1 - R%=A3%: GOSUB DEREF - A3%=R% + R%=A3%: GOSUB DEREF_R: A3%=R% EVAL_GET_A2: A2% = Z%(Z%(A%,1),1)+1 - R%=A2%: GOSUB DEREF - A2%=R% + R%=A2%: GOSUB DEREF_R: A2%=R% EVAL_GET_A1: A1% = Z%(A%,1)+1 - R%=A1%: GOSUB DEREF - A1%=R% + R%=A1%: GOSUB DEREF_R: A1%=R% RETURN EVAL_DEF: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% + REM push A1% ZL%=ZL%+1: ZZ%(ZL%)=A1% + A%=A2%: GOSUB EVAL: REM eval a2 + REM pop A1% A1%=ZZ%(ZL%): ZL%=ZL%-1 + REM set a1 in env to a2 K%=A1%: V%=R%: GOSUB ENV_SET + GOTO EVAL_RETURN EVAL_LET: + REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% REM create new environment with outer as current environment EO%=E%: GOSUB ENV_NEW @@ -190,11 +212,14 @@ EVAL: A1%=ZZ%(ZL%): ZL%=ZL%-1 REM set environment: even A1% key to odd A1% eval'd above K%=A1%+1: V%=R%: GOSUB ENV_SET + AY%=R%: GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: A%=A2%: GOSUB EVAL: REM eval a2 using let_env + REM release the let env + AY%=E%: GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO: A%=Z%(A%,1): REM rest @@ -204,6 +229,7 @@ EVAL: GOTO EVAL_AST EVAL_AST_RETURN_2: + ZM%=ZM%+1: ZR%(ZM%)=R%: REM release evaluated list A%=R%: GOSUB LAST: REM return the last element GOTO EVAL_RETURN EVAL_IF: @@ -216,9 +242,11 @@ EVAL: IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: + AY%=R%: GOSUB RELEASE GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL A%=A2%: GOTO EVAL_TCO_RECUR EVAL_IF_FALSE: + AY%=R%: GOSUB RELEASE REM if no false case (A3%), return nil IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0: GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL @@ -232,30 +260,43 @@ EVAL: ZL%=ZL%+1: ZZ%(ZL%)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: + ZM%=ZM%+1: ZR%(ZM%)=R%: REM release f/args on return IF ER%=1 THEN GOTO EVAL_RETURN F%=R%+1 AR%=Z%(R%,1): REM rest - R%=F%: GOSUB DEREF - F%=R% + R%=F%: GOSUB DEREF_R: F%=R% - IF Z%(F%,0)=9 THEN GOTO EVAL_DO_FUNCTION - IF Z%(F%,0)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: EO%=Z%(F%+1,1): BI%=Z%(F%+1,0): EX%=AR%: GOSUB ENV_NEW_BINDS + ZM%=ZM%+1: ZR%(ZM%)=R%: REM release environment on return A%=Z%(F%,1): E%=R%: GOTO EVAL_TCO_RECUR EVAL_RETURN: + REM an error occured, free up any new value + IF ER%=1 THEN AY%=R%: GOSUB RELEASE + REM trigger GC - T8%=FRE(0) + TA%=FRE(0) + REM pop A% and E% off the stack E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + REM AZ%=R%: GOSUB PR_STR + REM PRINT "EVAL return: " + R$ + "(" + STR$(R%) + ")" + REM PRINT "EVAL return level: " + STR$(LV%) + LV%=LV%-1: REM track basic return stack level + + REM release pending queued during TCO recursion + IF LV%=0 THEN GOSUB RELEASE_PEND + RETURN REM PRINT(A%) -> R$ @@ -266,16 +307,31 @@ MAL_PRINT: REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - LV%=LV%+1: REM track basic return stack level - + R1%=0: R2%=0 GOSUB MAL_READ - IF ER% THEN GOTO REP_RETURN + IF ER% THEN GOTO REP_DONE + R1%=R% + + REM PRINT "After read:" + REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY + A%=R%: E%=RE%: GOSUB EVAL - IF ER% THEN GOTO REP_RETURN + IF ER% THEN GOTO REP_DONE + R2%=R% + + REM PRINT "After eval, before print:" + REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY + A%=R%: GOSUB MAL_PRINT - IF ER% THEN GOTO REP_RETURN - REP_RETURN: - LV%=LV%-1: REM track basic return stack level + + REP_DONE: + REM Release memory from MAL_READ and EVAL + IF R2%<>0 THEN AY%=R2%: GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + + REM PRINT "After releases:" + REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY + RETURN REM MAIN program @@ -288,8 +344,14 @@ MAIN: EO%=-1: GOSUB ENV_NEW RE%=R% - REM set core functions in repl_env - E%=RE%: GOSUB INIT_CORE_NS + REM core.EXT: defined in Basic + E%=RE%: GOSUB INIT_CORE_NS: REM set core functions in repl_env + + ZT%=ZI%: REM top of memory after base repl_env + + REM core.mal: defined using the language itself + A$="(def! not (fn* (a) (if a false true)))" + GOSUB REP REM AZ%=Z%(RE%,1): GOSUB PR_STR REM PRINT "env: " + R$ + "(" + STR$(RE%) + ")" @@ -302,6 +364,10 @@ MAIN: GOSUB READLINE: REM /* call input parser */ IF EOF=1 THEN GOTO MAIN_DONE A$=R$: GOSUB REP: REM /* call REP */ + + REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + REM GOSUB PR_MEMORY_SUMMARY + IF ER% THEN GOTO ERROR PRINT R$ GOTO MAIN_LOOP @@ -313,6 +379,7 @@ MAIN: GOTO MAIN_LOOP MAIN_DONE: + P1%=ZT%: P2%=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/types.in.bas b/basic/types.in.bas index 87359014a7..fb8f1ffed1 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -12,19 +12,22 @@ REM followed by value (unless empty) REM hashmap next/val 8 -> next Z% index (0 for last) REM followed by key or value (alternating) REM function 9 -> function index -REM mal function 10 -> ??? +REM mal function 10 -> body AST Z% index +REM followed by param and env Z% index REM atom 11 -> Z% index -REM environment 14 -> data/hashmap Z% index -REM followed by 14 and outer Z% index (-1 for none) -REM reference/ptr 15 -> Z% index / or 0 +REM environment 13 -> data/hashmap Z% index +REM followed by 13 and outer Z% index (-1 for none) +REM reference/ptr 14 -> Z% index / or 0 +REM next free ptr 15 -> Z% index / or 0 INIT_MEMORY: T%=FRE(0) - - S1%=4096+512+256: REM Z% (boxed memory) size (X2) + + S1%=3072: REM Z% (boxed memory) size (X2) + REM S1%=4096: REM Z% (boxed memory) size (X2) S2%=256: REM ZS% (string memory) size S3%=256: REM ZZ% (call stack) size - S4%=64: REM PS% (logic stack) size + S4%=64: REM ZR% (pending release stack) size REM global error state ER%=0 @@ -40,8 +43,13 @@ INIT_MEMORY: Z%(1,1) = 0 Z%(2,0) = 1 Z%(2,1) = 1 + + REM start of unused memory ZI%=3 + REM start of free list + ZK%=3 + REM string memory storage ZJ%=0 DIM ZS$(S2%) @@ -50,40 +58,232 @@ INIT_MEMORY: ZL%=-1 DIM ZZ%(S3%): REM stack of Z% indexes + REM pending release stack + ZM%=-1 + DIM ZR%(S4%): REM stack of Z% indexes + REM PRINT "Lisp data memory: " + STR$(T%-FRE(0)) REM PRINT "Interpreter working memory: " + STR$(FRE(0)) RETURN -REM general functions +REM memory functions + +REM ALLOC(SZ%) -> R% +ALLOC: + REM PRINT "ALLOC SZ%: "+STR$(SZ%)+", ZK%: "+STR$(ZK%) + U3%=ZK% + U4%=ZK% + ALLOC_LOOP: + IF U4%=ZI% THEN GOTO ALLOC_UNUSED + REM TODO sanity check that type is 15 + IF ((Z%(U4%,0)AND-16)/16)=SZ% THEN GOTO ALLOC_MIDDLE + REM PRINT "ALLOC search: U3%: "+STR$(U3%)+", U4%: "+STR$(U4%) + U3%=U4%: REM previous set to current + U4%=Z%(U4%,1): REM current set to next + GOTO ALLOC_LOOP + ALLOC_MIDDLE: + REM PRINT "ALLOC_MIDDLE: U3%: "+STR$(U3%)+", U4%: "+STR$(U4%) + R%=U4% + REM set free pointer (ZK%) to next free + IF U4%=ZK% THEN ZK%=Z%(U4%,1) + REM set previous free to next free + IF U4%<>ZK% THEN Z%(U3%,1)=Z%(U4%,1) + RETURN + ALLOC_UNUSED: + REM PRINT "ALLOC_UNUSED ZI%: "+STR$(ZI%)+", U3%: "+STR$(U3%)+", U4%: "+STR$(U4%) + R%=U4% + ZI%=ZI%+SZ% + IF U3%=U4% THEN ZK%=ZI% + REM set previous free to new memory top + IF U3%<>U4% THEN Z%(U3%,1)=ZI% + RETURN + +REM FREE(AY%, SZ%) -> nil +FREE: + REM assumes reference count cleanup already (see RELEASE) + Z%(AY%,0) = (SZ%*16)+15: REM set type(15) and size + Z%(AY%,1) = ZK% + IF SZ%>=2 THEN Z%(AY%+1,0)=0 + IF SZ%>=2 THEN Z%(AY%+1,1)=0 + IF SZ%>=3 THEN Z%(AY%+2,0)=0 + IF SZ%>=3 THEN Z%(AY%+2,1)=0 + ZK%=AY% + RETURN + + +REM RELEASE(AY%) -> nil +RELEASE: + RC%=0 + + GOTO RELEASE_ONE + + RELEASE_TOP: + + IF RC%=0 THEN RETURN + + REM pop next object to release, decrease remaining count + AY%=ZZ%(ZL%): ZL%=ZL%-1 + RC%=RC%-1 + + RELEASE_ONE: + + REM nil, false, true + IF AY%<3 THEN GOTO RELEASE_TOP + + REM sanity check not already freed + IF (Z%(AY%,0)AND15)=15 THEN ER%=1: ER$="Free of free mem: " + STR$(AY%): RETURN + IF Z%(AY%,0)<16 THEN ER%=1: ER$="Free of freed object: " + STR$(AY%): RETURN + + REM decrease reference count by one + Z%(AY%,0)=Z%(AY%,0)-16 + + REM our reference count is not 0, so don't release + IF Z%(AY%,0)>=16 GOTO RELEASE_TOP + + REM switch on type + U6%=Z%(AY%,0)AND15: REM type + IF (U6%<=5) OR (U6%=9) THEN GOTO RELEASE_SIMPLE + IF (U6%>=6) AND (U6%<=8) THEN GOTO RELEASE_SEQ + IF U6%=10 THEN GOTO RELEASE_MAL_FUNCTION + IF U6%=13 THEN GOTO RELEASE_ENV + IF U6%=14 THEN GOTO RELEASE_REFERENCE + IF U6%=15 THEN ER%=1: ER$="RELEASE of already freed: "+STR$(AY%): RETURN + ER%=1: ER$="RELEASE not defined for type " + STR$(U6%): RETURN + + RELEASE_SIMPLE: + REM simple type (no recursing), just call FREE on it + SZ%=1: GOSUB FREE + GOTO RELEASE_TOP + RELEASE_SIMPLE_2: + REM free the current element and continue + SZ%=2: GOSUB FREE + GOTO RELEASE_TOP + RELEASE_SEQ: + IF Z%(AY%,1)=0 THEN GOTO RELEASE_SIMPLE_2 + IF Z%(AY%+1,0)<>14 THEN ER%=1: ER$="invalid list value"+STR$(AY%+1): RETURN + REM add value and next element to stack + RC%=RC%+2: ZL%=ZL%+2: ZZ%(ZL%-1)=Z%(AY%+1,1): ZZ%(ZL%)=Z%(AY%,1) + GOTO RELEASE_SIMPLE_2 + RELEASE_MAL_FUNCTION: + REM add ast, params and environment to stack + RC%=RC%+3: ZL%=ZL%+3 + ZZ%(ZL%-2)=Z%(AY%,1): ZZ%(ZL%-1)=Z%(AY%+1,0): ZZ%(ZL%)=Z%(AY%+1,1) + REM free the current 2 element mal_function and continue + SZ%=2: GOSUB FREE + GOTO RELEASE_TOP + RELEASE_ENV: + REM add the hashmap data to the stack + RC%=RC%+1: ZL%=ZL%+1: ZZ%(ZL%)=Z%(AY%,1) + REM if no outer set + IF Z%(AY%+1,1)=-1 THEN GOTO RELEASE_ENV_FREE + REM add outer environment to the stack + RC%=RC%+1: ZL%=ZL%+1: ZZ%(ZL%)=Z%(AY%+1,1) + RELEASE_ENV_FREE: + REM free the current 2 element environment and continue + SZ%=2: GOSUB FREE + GOTO RELEASE_TOP + RELEASE_REFERENCE: + IF Z%(AY%,1)=0 THEN GOTO RELEASE_SIMPLE + REM add the referred element to the stack + RC%=RC%+1: ZL%=ZL%+1: ZZ%(ZL%)=Z%(AY%,1) + REM free the current element and continue + SZ%=1: GOSUB FREE + GOTO RELEASE_TOP + +REM RELEASE_PEND() -> nil +RELEASE_PEND: + IF ZM%<0 THEN RETURN + AY%=ZR%(ZM%): GOSUB RELEASE + ZM%=ZM%-1 + GOTO RELEASE_PEND + +REM DEREF_R(R%) -> R% +DEREF_R: + IF (Z%(R%,0)AND15)=14 THEN R%=Z%(R%,1): GOTO DEREF_R + RETURN + +REM DEREF_A(A%) -> A% +DEREF_A: + IF (Z%(A%,0)AND15)=14 THEN A%=Z%(A%,1): GOTO DEREF_A + RETURN + +REM DEREF_B(B%) -> B% +DEREF_B: + IF (Z%(B%,0)AND15)=14 THEN B%=Z%(B%,1): GOTO DEREF_B + RETURN + +CHECK_FREE_LIST: + P1%=ZK%: P2%=0: REM start and accumulator + CHECK_FREE_LIST_LOOP: + IF P1%>=ZI% THEN GOTO CHECK_FREE_LIST_DONE + IF (Z%(P1%,0)AND15)<>15 THEN P2%=-1: GOTO CHECK_FREE_LIST_DONE + P2%=P2%+(Z%(P1%,0)AND-16)/16 + P1%=Z%(P1%,1) + GOTO CHECK_FREE_LIST_LOOP + CHECK_FREE_LIST_DONE: + IF P2%=-1 THEN PRINT "corrupt free list at "+STR$(P1%) + RETURN PR_MEMORY_SUMMARY: + GOSUB CHECK_FREE_LIST: REM get count in P2% PRINT PRINT "Free memory (FRE) : " + STR$(FRE(0)) - PRINT "Boxed values (Z%) : " + STR$(ZI%) + " /" + STR$(S1%) + PRINT "Value memory (Z%) : " + STR$(ZI%-1) + " /" + STR$(S1%) + PRINT " "; + PRINT " used:"+STR$(ZI%-1-P2%)+", freed:"+STR$(P2%); + PRINT ", post repl_env:"+STR$(ZT%) PRINT "String values (ZS$) : " + STR$(ZJ%) + " /" + STR$(S2%) PRINT "Call stack size (ZZ%) : " + STR$(ZL%+1) + " /" + STR$(S3%) RETURN +REM PR_MEMORY(P1%, P2%) -> nil PR_MEMORY: - PRINT "Value Memory (Z%):" - FOR I=0 TO ZI%-1 - PRINT " " + STR$(I) + ": type: " + STR$(Z%(I,0)) + ", value: " + STR$(Z%(I,1)) - NEXT I - PRINT "String Memory (ZS%):" + IF P2%"+STR$(P2%); + PRINT " (ZI%: "+STR$(ZI%)+", ZK%: "+STR$(ZK%)+"):" + IF P2%P2% THEN GOTO PR_MEMORY_AFTER_VALUES + PRINT " " + STR$(I); + IF (Z%(I,0)AND15)=15 THEN GOTO PR_MEMORY_FREE + PRINT ": ref cnt: " + STR$((Z%(I,0)AND-16)/16); + PRINT ", type: " + STR$(Z%(I,0)AND15) + ", value: " + STR$(Z%(I,1)) + I=I+1 + GOTO PR_MEMORY_VALUE_LOOP + PR_MEMORY_FREE: + PRINT ": FREE size: "+STR$((Z%(I,0)AND-16)/16)+", next: "+STR$(Z%(I,1)); + IF I=ZK% THEN PRINT " (free list start)"; + PRINT + IF (Z%(I,0)AND-16)=32 THEN I=I+1: PRINT " " + STR$(I) + ": ---" + I=I+1 + GOTO PR_MEMORY_VALUE_LOOP + PR_MEMORY_AFTER_VALUES: + PRINT "ZS% String Memory (ZJ%: " + STR$(ZJ%) + "):" + IF ZJ%<=0 THEN PRINT " ---": GOTO PR_MEMORY_SKIP_STRINGS FOR I=0 TO ZJ%-1 PRINT " " + STR$(I) + ": '" + ZS$(I) + "'" NEXT I + PR_MEMORY_SKIP_STRINGS: + PRINT "ZZ% Stack Memory (ZL%: " + STR$(ZL%) + "):" + IF ZL%<0 THEN PRINT " ---": GOTO PR_MEMORY_SKIP_STACK + FOR I=0 TO ZL% + PRINT " "+STR$(I)+": "+STR$(ZZ%(I)) + NEXT I + PR_MEMORY_SKIP_STACK: + PRINT "^^^^^^" RETURN -REM DEREF(R%) -> R% -DEREF: - IF Z%(R%,0)=15 THEN R%=Z%(R%,1): GOTO DEREF - RETURN + +REM general functions REM EQUAL_Q(A%, B%) -> R% EQUAL_Q: + GOSUB DEREF_A: GOSUB DEREF_B + R%=0 - U1%=Z%(A%,0): U2%=Z%(B%,0) + U1%=(Z%(A%,0)AND15): U2%=(Z%(B%,0)AND15) IF NOT ((U1%=U2%) OR ((U1%=6 OR U1%=7) AND (U2%=6 OR U2%=7))) THEN RETURN IF U1%=6 THEN GOTO EQUAL_Q_SEQ IF U1%=7 THEN GOTO EQUAL_Q_SEQ @@ -93,8 +293,18 @@ EQUAL_Q: RETURN EQUAL_Q_SEQ: - R%=0 - RETURN + IF (Z%(A%,1)=0) AND (Z%(B%,1)=0) THEN R%=1: RETURN + IF (Z%(A%,1)=0) OR (Z%(B%,1)=0) THEN R%=0: RETURN + + REM push A% and B% + ZL%=ZL%+2: ZZ%(ZL%-1)=A%: ZZ%(ZL%)=B% + A%=Z%(A%+1,1): B%=Z%(B%+1,1): GOSUB EQUAL_Q + REM pop A% and B% + A%=ZZ%(ZL%-1): B%=ZZ%(ZL%): ZL%=ZL%-2 + IF R%=0 THEN RETURN + + REM next elements of the sequences + A%=Z%(A%,1): B%=Z%(B%,1): GOTO EQUAL_Q_SEQ EQUAL_Q_HM: R%=0 RETURN @@ -125,63 +335,79 @@ REM list functions REM LIST_Q(A%) -> R% LIST_Q: R%=0 - IF Z%(A%,0)=6 THEN R%=1 + IF (Z%(A%,0)AND15)=6 THEN R%=1 RETURN -REM LIST_Q(A%) -> R% +REM EMPTY_Q(A%) -> R% EMPTY_Q: R%=0 IF Z%(A%,1)=0 THEN R%=1 RETURN +REM COUNT(A%) -> R% +COUNT: + R%=-1 + DO_COUNT_LOOP: + R%=R%+1 + IF Z%(A%,1)<>0 THEN A%=Z%(A%,1): GOTO DO_COUNT_LOOP + RETURN + REM LAST(A%) -> R% LAST: REM TODO check that actually a list/vector IF Z%(A%,1)=0 THEN R%=0: RETURN: REM empty seq, return nil T6%=0 LAST_LOOP: - IF Z%(A%,1)=0 THEN R%=T6%+1: RETURN: REM end, return previous value + IF Z%(A%,1)=0 THEN GOTO LAST_DONE: REM end, return previous value T6%=A%: REM current becomes previous entry A%=Z%(A%,1): REM next entry GOTO LAST_LOOP + LAST_DONE: + R%=T6%+1: GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + RETURN REM hashmap functions REM HASHMAP() -> R% HASHMAP: - Z%(ZI%,0) = 8 - Z%(ZI%,1) = 0 - R%=ZI% - ZI%=ZI%+1 + SZ%=2: GOSUB ALLOC + Z%(R%,0) = 8+16 + Z%(R%,1) = 0 + Z%(R%+1,0) = 14 + Z%(R%+1,1) = 0 RETURN REM ASSOC1(HM%, K%, V%) -> R% ASSOC1: - R%=ZI% + REM deref to actual key and value + R%=K%: GOSUB DEREF_R: K%=R% + R%=V%: GOSUB DEREF_R: V%=R% + + REM inc ref count of key and value + Z%(K%,0)=Z%(K%,0)+16 + Z%(V%,0)=Z%(V%,0)+16 + SZ%=4: GOSUB ALLOC REM key ptr - Z%(ZI%,0) = 8 - Z%(ZI%,1) = ZI%+2: REM value - ZI%=ZI%+1 - Z%(ZI%,0) = 15 - Z%(ZI%,1) = K% - ZI%=ZI%+1 + Z%(R%,0) = 8+16 + Z%(R%,1) = R%+2: REM point to next element (value) + Z%(R%+1,0) = 14 + Z%(R%+1,1) = K% REM value ptr - Z%(ZI%,0) = 8 - Z%(ZI%,1) = HM%: REM hashmap to assoc onto - ZI%=ZI%+1 - Z%(ZI%,0) = 15 - Z%(ZI%,1) = V% - ZI%=ZI%+1 + Z%(R%+2,0) = 8+16 + Z%(R%+2,1) = HM%: REM hashmap to assoc onto + Z%(R%+3,0) = 14 + Z%(R%+3,1) = V% RETURN REM ASSOC1(HM%, K$, V%) -> R% ASSOC1_S: REM add the key string, then call ASSOC1 - K%=ZI% + SZ%=1: GOSUB ALLOC + K%=R% ZS$(ZJ%) = K$ - Z%(ZI%,0) = 4 - Z%(ZI%,1) = ZJ% - ZI%=ZI%+1 + Z%(R%,0) = 4: REM key ref cnt will be inc'd by ASSOC1 + Z%(R%,1) = ZJ% ZJ%=ZJ%+1 GOSUB ASSOC1 RETURN @@ -198,7 +424,7 @@ HASHMAP_GET: REM follow value ptrs T2%=H2%+1 HASHMAP_GET_DEREF: - IF Z%(T2%,0)=15 THEN T2%=Z%(T2%,1): GOTO HASHMAP_GET_DEREF + IF Z%(T2%,0)=14 THEN T2%=Z%(T2%,1): GOTO HASHMAP_GET_DEREF REM get key string T2$=ZS$(Z%(T2%,1)) REM if they are equal, we found it @@ -215,18 +441,20 @@ HASHMAP_CONTAINS: REM NATIVE_FUNCTION(A%) -> R% NATIVE_FUNCTION: - Z%(ZI%,0) = 9 - Z%(ZI%,1) = A% - R%=ZI% - ZI%=ZI%+1 + SZ%=1: GOSUB ALLOC + Z%(R%,0) = 9+16 + Z%(R%,1) = A% RETURN REM NATIVE_FUNCTION(A%, P%, E%) -> R% MAL_FUNCTION: - Z%(ZI%,0) = 10 - Z%(ZI%,1) = A% - Z%(ZI%+1,0) = P% - Z%(ZI%+1,1) = E% - R%=ZI% - ZI%=ZI%+2 + SZ%=2: GOSUB ALLOC + Z%(A%,0)=Z%(A%,0)+16 + Z%(P%,0)=Z%(P%,0)+16 + Z%(E%,0)=Z%(E%,0)+16 + + Z%(R%,0) = 10+16 + Z%(R%,1) = A% + Z%(R%+1,0) = P% + Z%(R%+1,1) = E% RETURN From 01903266d283b8e151631d17306f5ef7be55b049 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 16 Sep 2016 01:38:44 -0500 Subject: [PATCH 0146/2308] Basic: fix readline to work on C64 (Vice). Also, add cbmbasic console patch that makes it work more like the C64 where GET does not echo. --- basic/cbmbasic_console.patch | 26 ++++++++++++++++++++++++++ basic/readline.in.bas | 11 +++++------ 2 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 basic/cbmbasic_console.patch diff --git a/basic/cbmbasic_console.patch b/basic/cbmbasic_console.patch new file mode 100644 index 0000000000..d20dce1c83 --- /dev/null +++ b/basic/cbmbasic_console.patch @@ -0,0 +1,26 @@ +diff --git a/runtime.c b/runtime.c +index 3066580..c635bd4 100644 +--- a/runtime.c ++++ b/runtime.c +@@ -535,7 +535,8 @@ printf("CHROUT: %d @ %x,%x,%x,%x\n", A, a, b, c, d); + left_cursor(); + break; + case '"': +- kernal_quote = 1; ++ // jdm: this doesn't match C64 behavior ++ //kernal_quote = 1; + // fallthrough + default: + putchar(A); +@@ -838,8 +839,10 @@ GETIN() { + /*Notice that EOF is also turned off in non-canonical mode*/ + A = getchar(); + if (A == 255) { A = 4; } // map actual EOF to 4 ++ ++ // jdm: this doesn't match C64 behavior + /* Simulate echo */ +- if (A != 0 && A != 4) { putchar(A); } ++ //if (A != 0 && A != 4) { putchar(A); } + + /*restore the old settings*/ + tcsetattr( STDIN_FILENO, TCSANOW, &oldt); diff --git a/basic/readline.in.bas b/basic/readline.in.bas index 3d0999b344..510bcb4c93 100644 --- a/basic/readline.in.bas +++ b/basic/readline.in.bas @@ -9,10 +9,12 @@ READLINE: READCH: GET CH$: IF CH$="" THEN READCH CH=ASC(CH$) + REM PRINT CH IF (CH=4 OR CH=0) THEN EOF=1: GOTO RL_DONE: REM EOF - IF (CH=127) THEN GOSUB RL_BACKSPACE - IF (CH=127) THEN GOTO READCH + IF (CH=127) OR (CH=20) THEN GOSUB RL_BACKSPACE + IF (CH=127) OR (CH=20) THEN GOTO READCH IF (CH<32 OR CH>127) AND CH<>13 THEN READCH + PRINT CH$; IF LEN(LINE$)<255 AND CH$<>CHR$(13) THEN LINE$=LINE$+CH$ IF LEN(LINE$)<255 AND CH$<>CHR$(13) THEN GOTO READCH RL_DONE: @@ -21,10 +23,7 @@ READLINE: REM Assumes LINE$ has input buffer RL_BACKSPACE: - IF LEN(LINE$)=0 THEN RL_BACKSPACE_ONCE: - PRINT CHR$(157) + CHR$(157) + " " + CHR$(157) + CHR$(157); + IF LEN(LINE$)=0 THEN RETURN LINE$=LEFT$(LINE$, LEN(LINE$)-1) - RETURN - RL_BACKSPACE_ONCE: PRINT CHR$(157) + " " + CHR$(157); RETURN From 412e7348e3d213e5e68826ff5b7d2b2b5bf4c367 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 17 Sep 2016 23:03:30 -0500 Subject: [PATCH 0147/2308] Basic: add step5, fix recursive/error memory issues. - Add TCO recur to let* - Release of all memory from recursive function and let* calls. - Fix a number of cases of trying to release already freed memory. - Print function in memory as 2 byte unit rather than two separate memory locations. --- basic/Makefile | 3 +- basic/core.in.bas | 2 +- basic/env.in.bas | 4 +- basic/printer.in.bas | 8 + basic/step4_if_fn_do.in.bas | 130 +++++++----- basic/step5_tco.in.bas | 409 ++++++++++++++++++++++++++++++++++++ basic/types.in.bas | 21 +- 7 files changed, 511 insertions(+), 66 deletions(-) create mode 100755 basic/step5_tco.in.bas diff --git a/basic/Makefile b/basic/Makefile index 2d01061b68..c0fd4f27bd 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -13,6 +13,7 @@ step1_read_print.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas step2_eval.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas step3_env.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas env.in.bas step4_if_fn_do.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas env.in.bas core.in.bas +step5_tco.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas env.in.bas core.in.bas tests/%.bas: tests/%.in.bas ./qb2cbm.sh $< > $@ @@ -23,7 +24,7 @@ tests/%.prg: tests/%.bas rm $<.tmp -SOURCES_LISP = env.in.bas core.in.bas step4_if_fn_do.in.bas +SOURCES_LISP = env.in.bas core.in.bas step5_tco.in.bas SOURCES = readline.in.bas types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP) .PHONY: stats diff --git a/basic/core.in.bas b/basic/core.in.bas index 933a28fe62..1d963e6404 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -127,7 +127,7 @@ DO_FUNCTION: RETURN DO_PR_MEMORY: - GOSUB PR_MEMORY + P1%=ZT%: P2%=-1: GOSUB PR_MEMORY RETURN DO_PR_MEMORY_SUMMARY: diff --git a/basic/env.in.bas b/basic/env.in.bas index c16ea6fc5a..0458441f19 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -3,12 +3,12 @@ REM ENV_NEW(EO%) -> R% ENV_NEW: REM allocate the data hashmap GOSUB HASHMAP - E1%=R% + ET%=R% REM set the outer and data pointer SZ%=2: GOSUB ALLOC Z%(R%,0) = 13+16 - Z%(R%,1) = E1% + Z%(R%,1) = ET% Z%(R%+1,0) = 13 Z%(R%+1,1) = EO% IF EO%<>-1 THEN Z%(EO%,0)=Z%(EO%,0)+16 diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 6d2e768cb9..892b5a3f31 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -17,6 +17,8 @@ PR_STR: IF T%=8 THEN PR_SEQ IF T%=9 THEN PR_FUNCTION IF T%=10 THEN PR_MAL_FUNCTION + IF T%=13 THEN PR_ENV + IF T%=15 THEN PR_FREE R$="#" RETURN @@ -81,6 +83,12 @@ PR_STR: AZ%=Z%(T1%,1): GOSUB PR_STR_RECUR R$=T7$ + " " + R$ + ")" RETURN + PR_ENV: + R$="#" + RETURN + PR_FREE: + R$="#" + RETURN REM PR_STR_SEQ(AZ%, PR%, SE$) -> R$ PR_STR_SEQ: diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index 9323e742b3..5dd4914b31 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -19,10 +19,7 @@ EVAL_AST: REM push A% and E% on the stack ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% - IF ER%=1 THEN GOTO EVAL_AST_RETURN - - REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL_AST: " + R$ + "(" + STR$(A%) + ")" + IF ER%<>0 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A @@ -40,7 +37,7 @@ EVAL_AST: EVAL_AST_SYMBOL: K%=A%: GOSUB ENV_GET GOTO EVAL_AST_RETURN - + EVAL_AST_SEQ: REM allocate the first entry SZ%=2: GOSUB ALLOC @@ -73,7 +70,7 @@ EVAL_AST: REM if hashmap, skip eval of even entries (keys) IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL - + EVAL_AST_DO_REF: R%=A%+1: GOSUB DEREF_R: REM deref to target of referred entry Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value @@ -83,14 +80,15 @@ EVAL_AST: REM call EVAL for each entry A%=A%+1: GOSUB EVAL A%=A%-1 - IF ER%=1 THEN GOTO EVAL_AST_SEQ_LOOP_DONE GOSUB DEREF_R: REM deref to target of evaluated entry EVAL_AST_ADD_VALUE: - REM update previous value pointer to evaluated entry + REM update previous value pointer to evaluated entry Z%(ZZ%(ZL%)+1,1)=R% + IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM allocate the next entry SZ%=2: GOSUB ALLOC @@ -104,10 +102,8 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - REM if no error, get return value (new seq) - IF ER%=0 THEN R%=ZZ%(ZL%-1) - REM otherwise, free the return value and return nil - IF ER%=1 THEN R%=0: AY%=ZZ%(ZL%-1): GOSUB RELEASE + REM get return value (new seq) + R%=ZZ%(ZL%-1) REM pop previous, return, index and type ZL%=ZL%-4 @@ -133,11 +129,8 @@ EVAL: EVAL_TCO_RECUR: - IF ER%=1 THEN GOTO EVAL_RETURN - REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + ")" - REM PRINT "EVAL level: " + STR$(LV%) + REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + "), LV%:"+STR$(LV%) GOSUB DEREF_A @@ -184,18 +177,14 @@ EVAL: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% - REM push A1% - ZL%=ZL%+1: ZZ%(ZL%)=A1% - + ZL%=ZL%+1: ZZ%(ZL%)=A1%: REM push A1% A%=A2%: GOSUB EVAL: REM eval a2 - - REM pop A1% - A1%=ZZ%(ZL%): ZL%=ZL%-1 + A1%=ZZ%(ZL%): ZL%=ZL%-1: REM pop A1% REM set a1 in env to a2 K%=A1%: V%=R%: GOSUB ENV_SET - GOTO EVAL_RETURN + EVAL_LET: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% @@ -204,22 +193,25 @@ EVAL: E%=R% EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + REM push A1% ZL%=ZL%+1: ZZ%(ZL%)=A1% REM eval current A1 odd element A%=Z%(A1%,1)+1: GOSUB EVAL REM pop A1% A1%=ZZ%(ZL%): ZL%=ZL%-1 + REM set environment: even A1% key to odd A1% eval'd above K%=A1%+1: V%=R%: GOSUB ENV_SET AY%=R%: GOSUB RELEASE: REM release our use, ENV_SET took ownership + REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: A%=A2%: GOSUB EVAL: REM eval a2 using let_env - REM release the let env - AY%=E%: GOSUB RELEASE + REM REM release the let env + REM AY%=E%: GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO: A%=Z%(A%,1): REM rest @@ -229,9 +221,12 @@ EVAL: GOTO EVAL_AST EVAL_AST_RETURN_2: - ZM%=ZM%+1: ZR%(ZM%)=R%: REM release evaluated list + ZL%=ZL%+1: ZZ%(ZL%)=R%: REM push eval'd list A%=R%: GOSUB LAST: REM return the last element + AY%=ZZ%(ZL%): ZL%=ZL%-1: REM pop eval'd list + GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN + EVAL_IF: GOSUB EVAL_GET_A1: REM set a1% REM push A% @@ -244,43 +239,81 @@ EVAL: EVAL_IF_TRUE: AY%=R%: GOSUB RELEASE GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A%=A2%: GOTO EVAL_TCO_RECUR + A%=A2%: GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: AY%=R%: GOSUB RELEASE REM if no false case (A3%), return nil IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0: GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A%=A3%: GOTO EVAL_TCO_RECUR + A%=A3%: GOTO EVAL_TCO_RECUR: REM TCO loop + EVAL_FN: GOSUB EVAL_GET_A2: REM set a1% and a2% A%=A2%: P%=A1%: GOSUB MAL_FUNCTION GOTO EVAL_RETURN + EVAL_INVOKE: REM push EVAL_AST return label/address ZL%=ZL%+1: ZZ%(ZL%)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: - ZM%=ZM%+1: ZR%(ZM%)=R%: REM release f/args on return - IF ER%=1 THEN GOTO EVAL_RETURN + REM if error, return f/args for release by caller + IF ER%<>0 THEN GOTO EVAL_RETURN + + REM push f/args for release after call + ZL%=ZL%+1: ZZ%(ZL%)=R% + F%=R%+1 + AR%=Z%(R%,1): REM rest R%=F%: GOSUB DEREF_R: F%=R% IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + + REM if error, pop and return f/args for release by caller + R%=ZZ%(ZL%): ZL%=ZL%-1 ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + EVAL_DO_FUNCTION: GOSUB DO_FUNCTION + + REM pop and release f/args + AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE GOTO EVAL_RETURN + EVAL_DO_MAL_FUNCTION: + E4%=E%: REM save the current environment for release + + REM create new environ using env stored with function EO%=Z%(F%+1,1): BI%=Z%(F%+1,0): EX%=AR%: GOSUB ENV_NEW_BINDS - ZM%=ZM%+1: ZR%(ZM%)=R%: REM release environment on return - A%=Z%(F%,1): E%=R%: GOTO EVAL_TCO_RECUR + + REM release previous env if it is not the top one on the + REM stack (ZZ%(ZL%-2)) because our new env refers to it and + REM we no longer need to track it (since we are TCO recurring) + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%: GOSUB RELEASE + + REM claim the AST before releasing the list containing it + A%=Z%(F%,1): Z%(A%,0)=Z%(A%,0)+16 + REM add AST to pending release queue to free later + ZM%=ZM%+1: ZR%(ZM%)=A% + + REM pop and release f/args + AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + + REM A% set above + E%=R%: GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: - REM an error occured, free up any new value - IF ER%=1 THEN AY%=R%: GOSUB RELEASE + REM release environment if not the top one on the stack + IF E%<>ZZ%(ZL%-1) THEN AY%=E%: GOSUB RELEASE + + REM release everything we couldn't release earlier + GOSUB RELEASE_PEND + + REM AZ%=R%: PR%=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) REM trigger GC TA%=FRE(0) @@ -288,15 +321,8 @@ EVAL: REM pop A% and E% off the stack E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 - REM AZ%=R%: GOSUB PR_STR - REM PRINT "EVAL return: " + R$ + "(" + STR$(R%) + ")" - REM PRINT "EVAL return level: " + STR$(LV%) - LV%=LV%-1: REM track basic return stack level - REM release pending queued during TCO recursion - IF LV%=0 THEN GOSUB RELEASE_PEND - RETURN REM PRINT(A%) -> R$ @@ -309,29 +335,21 @@ REM Assume RE% has repl_env REP: R1%=0: R2%=0 GOSUB MAL_READ - IF ER% THEN GOTO REP_DONE R1%=R% - - REM PRINT "After read:" - REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY + IF ER%<>0 THEN GOTO REP_DONE A%=R%: E%=RE%: GOSUB EVAL - IF ER% THEN GOTO REP_DONE R2%=R% - - REM PRINT "After eval, before print:" - REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY + IF ER%<>0 THEN GOTO REP_DONE A%=R%: GOSUB MAL_PRINT + RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL IF R2%<>0 THEN AY%=R2%: GOSUB RELEASE IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE - - REM PRINT "After releases:" - REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY - + R$=RT$ RETURN REM MAIN program @@ -363,10 +381,8 @@ MAIN: A$="user> " GOSUB READLINE: REM /* call input parser */ IF EOF=1 THEN GOTO MAIN_DONE - A$=R$: GOSUB REP: REM /* call REP */ - REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY - REM GOSUB PR_MEMORY_SUMMARY + A$=R$: GOSUB REP: REM /* call REP */ IF ER% THEN GOTO ERROR PRINT R$ @@ -379,7 +395,7 @@ MAIN: GOTO MAIN_LOOP MAIN_DONE: - P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas new file mode 100755 index 0000000000..cb27504e62 --- /dev/null +++ b/basic/step5_tco.in.bas @@ -0,0 +1,409 @@ +GOTO MAIN + +REM $INCLUDE: 'readline.in.bas' +REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'reader.in.bas' +REM $INCLUDE: 'printer.in.bas' +REM $INCLUDE: 'env.in.bas' +REM $INCLUDE: 'core.in.bas' + +REM READ(A$) -> R% +MAL_READ: + GOSUB READ_STR + RETURN + +REM EVAL_AST(A%, E%) -> R% +REM called using GOTO to avoid basic return address stack usage +REM top of stack should have return label index +EVAL_AST: + REM push A% and E% on the stack + ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + + IF ER%<>0 THEN GOTO EVAL_AST_RETURN + + GOSUB DEREF_A + + T%=Z%(A%,0)AND15 + IF T%=5 THEN EVAL_AST_SYMBOL + IF T%=6 THEN EVAL_AST_SEQ + IF T%=7 THEN EVAL_AST_SEQ + IF T%=8 THEN EVAL_AST_SEQ + + REM scalar: deref to actual value and inc ref cnt + R%=A%: GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + GOTO EVAL_AST_RETURN + + EVAL_AST_SYMBOL: + K%=A%: GOSUB ENV_GET + GOTO EVAL_AST_RETURN + + EVAL_AST_SEQ: + REM allocate the first entry + SZ%=2: GOSUB ALLOC + + REM make space on the stack + ZL%=ZL%+4 + REM push type of sequence + ZZ%(ZL%-3)=T% + REM push sequence index + ZZ%(ZL%-2)=-1 + REM push future return value (new sequence) + ZZ%(ZL%-1)=R% + REM push previous new sequence entry + ZZ%(ZL%)=R% + + EVAL_AST_SEQ_LOOP: + REM set new sequence entry type (with 1 ref cnt) + Z%(R%,0)=ZZ%(ZL%-3)+16 + Z%(R%,1)=0 + REM create value ptr placeholder + Z%(R%+1,0)=14 + Z%(R%+1,1)=0 + + REM update index + ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + + REM check if we are done evaluating the source sequence + IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + + REM if hashmap, skip eval of even entries (keys) + IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + GOTO EVAL_AST_DO_EVAL + + EVAL_AST_DO_REF: + R%=A%+1: GOSUB DEREF_R: REM deref to target of referred entry + Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + GOTO EVAL_AST_ADD_VALUE + + EVAL_AST_DO_EVAL: + REM call EVAL for each entry + A%=A%+1: GOSUB EVAL + A%=A%-1 + GOSUB DEREF_R: REM deref to target of evaluated entry + + EVAL_AST_ADD_VALUE: + + REM update previous value pointer to evaluated entry + Z%(ZZ%(ZL%)+1,1)=R% + + IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + + REM allocate the next entry + SZ%=2: GOSUB ALLOC + + REM update previous sequence entry value to point to new entry + Z%(ZZ%(ZL%),1)=R% + REM update previous ptr to current entry + ZZ%(ZL%)=R% + + REM process the next sequence entry from source list + A%=Z%(A%,1) + + GOTO EVAL_AST_SEQ_LOOP + EVAL_AST_SEQ_LOOP_DONE: + REM get return value (new seq) + R%=ZZ%(ZL%-1) + + REM pop previous, return, index and type + ZL%=ZL%-4 + GOTO EVAL_AST_RETURN + + EVAL_AST_RETURN: + REM pop A% and E% off the stack + E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + + REM pop EVAL AST return label/address + RN%=ZZ%(ZL%): ZL%=ZL%-1 + IF RN%=1 GOTO EVAL_AST_RETURN_1 + IF RN%=2 GOTO EVAL_AST_RETURN_2 + IF RN%=3 GOTO EVAL_AST_RETURN_3 + RETURN + +REM EVAL(A%, E%)) -> R% +EVAL: + LV%=LV%+1: REM track basic return stack level + + REM push A% and E% on the stack + ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + + EVAL_TCO_RECUR: + + REM AZ%=A%: GOSUB PR_STR + REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + "), LV%:"+STR$(LV%) + + GOSUB DEREF_A + + GOSUB LIST_Q + IF R% THEN GOTO APPLY_LIST + REM ELSE + REM push EVAL_AST return label/address + ZL%=ZL%+1: ZZ%(ZL%)=1 + GOTO EVAL_AST + EVAL_AST_RETURN_1: + + GOTO EVAL_RETURN + + APPLY_LIST: + GOSUB EMPTY_Q + IF R% THEN R%=A%: Z%(R%,0)=Z%(R%,0)+16: GOTO EVAL_RETURN + + A0%=A%+1 + R%=A0%: GOSUB DEREF_R: A0%=R% + + REM get symbol in A$ + IF (Z%(A0%,0)AND15)<>5 THEN A$="" + IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) + + IF A$="def!" THEN GOTO EVAL_DEF + IF A$="let*" THEN GOTO EVAL_LET + IF A$="do" THEN GOTO EVAL_DO + IF A$="if" THEN GOTO EVAL_IF + IF A$="fn*" THEN GOTO EVAL_FN + GOTO EVAL_INVOKE + + EVAL_GET_A3: + A3% = Z%(Z%(Z%(A%,1),1),1)+1 + R%=A3%: GOSUB DEREF_R: A3%=R% + EVAL_GET_A2: + A2% = Z%(Z%(A%,1),1)+1 + R%=A2%: GOSUB DEREF_R: A2%=R% + EVAL_GET_A1: + A1% = Z%(A%,1)+1 + R%=A1%: GOSUB DEREF_R: A1%=R% + RETURN + + EVAL_DEF: + REM PRINT "def!" + GOSUB EVAL_GET_A2: REM set a1% and a2% + + ZL%=ZL%+1: ZZ%(ZL%)=A1%: REM push A1% + A%=A2%: GOSUB EVAL: REM eval a2 + A1%=ZZ%(ZL%): ZL%=ZL%-1: REM pop A1% + + REM set a1 in env to a2 + K%=A1%: V%=R%: GOSUB ENV_SET + GOTO EVAL_RETURN + + EVAL_LET: + REM PRINT "let*" + GOSUB EVAL_GET_A2: REM set a1% and a2% + + E4%=E%: REM save the current environment for release + + REM create new environment with outer as current environment + EO%=E%: GOSUB ENV_NEW + E%=R% + EVAL_LET_LOOP: + IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + + REM push A1% + ZL%=ZL%+1: ZZ%(ZL%)=A1% + REM eval current A1 odd element + A%=Z%(A1%,1)+1: GOSUB EVAL + REM pop A1% + A1%=ZZ%(ZL%): ZL%=ZL%-1 + + REM set environment: even A1% key to odd A1% eval'd above + K%=A1%+1: V%=R%: GOSUB ENV_SET + AY%=R%: GOSUB RELEASE: REM release our use, ENV_SET took ownership + + REM skip to the next pair of A1% elements + A1%=Z%(Z%(A1%,1),1) + GOTO EVAL_LET_LOOP + EVAL_LET_LOOP_DONE: + REM release previous env (if not root repl_env) because our + REM new env refers to it and we no longer need to track it + REM (since we are TCO recurring) + IF E4%<>RE% THEN AY%=E4%: GOSUB RELEASE + + A%=A2%: GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_DO: + A%=Z%(A%,1): REM rest + + REM TODO: TCO + + REM push EVAL_AST return label/address + ZL%=ZL%+1: ZZ%(ZL%)=2 + GOTO EVAL_AST + EVAL_AST_RETURN_2: + + ZL%=ZL%+1: ZZ%(ZL%)=R%: REM push eval'd list + A%=R%: GOSUB LAST: REM return the last element + AY%=ZZ%(ZL%): ZL%=ZL%-1: REM pop eval'd list + GOSUB RELEASE: REM release the eval'd list + GOTO EVAL_RETURN + + EVAL_IF: + GOSUB EVAL_GET_A1: REM set a1% + REM push A% + ZL%=ZL%+1: ZZ%(ZL%)=A% + A%=A1%: GOSUB EVAL + REM pop A% + A%=ZZ%(ZL%): ZL%=ZL%-1 + IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE + + EVAL_IF_TRUE: + AY%=R%: GOSUB RELEASE + GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL + A%=A2%: GOTO EVAL_TCO_RECUR: REM TCO loop + EVAL_IF_FALSE: + AY%=R%: GOSUB RELEASE + REM if no false case (A3%), return nil + IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0: GOTO EVAL_RETURN + GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL + A%=A3%: GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_FN: + GOSUB EVAL_GET_A2: REM set a1% and a2% + A%=A2%: P%=A1%: GOSUB MAL_FUNCTION + GOTO EVAL_RETURN + + EVAL_INVOKE: + REM push EVAL_AST return label/address + ZL%=ZL%+1: ZZ%(ZL%)=3 + GOTO EVAL_AST + EVAL_AST_RETURN_3: + + REM if error, return f/args for release by caller + IF ER%<>0 THEN GOTO EVAL_RETURN + + REM push f/args for release after call + ZL%=ZL%+1: ZZ%(ZL%)=R% + + F%=R%+1 + + AR%=Z%(R%,1): REM rest + R%=F%: GOSUB DEREF_R: F%=R% + + IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + + REM if error, pop and return f/args for release by caller + R%=ZZ%(ZL%): ZL%=ZL%-1 + ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + + EVAL_DO_FUNCTION: + GOSUB DO_FUNCTION + + REM pop and release f/args + AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + GOTO EVAL_RETURN + + EVAL_DO_MAL_FUNCTION: + E4%=E%: REM save the current environment for release + + REM create new environ using env stored with function + EO%=Z%(F%+1,1): BI%=Z%(F%+1,0): EX%=AR%: GOSUB ENV_NEW_BINDS + + REM release previous env if it is not the top one on the + REM stack (ZZ%(ZL%-2)) because our new env refers to it and + REM we no longer need to track it (since we are TCO recurring) + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%: GOSUB RELEASE + + REM claim the AST before releasing the list containing it + A%=Z%(F%,1): Z%(A%,0)=Z%(A%,0)+16 + REM add AST to pending release queue to free later + ZM%=ZM%+1: ZR%(ZM%)=A% + + REM pop and release f/args + AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + + REM A% set above + E%=R%: GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_RETURN: + REM release environment if not the top one on the stack + IF E%<>ZZ%(ZL%-1) THEN AY%=E%: GOSUB RELEASE + + REM release everything we couldn't release earlier + GOSUB RELEASE_PEND + + REM AZ%=R%: PR%=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + + REM trigger GC + TA%=FRE(0) + + REM pop A% and E% off the stack + E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + + LV%=LV%-1: REM track basic return stack level + + RETURN + +REM PRINT(A%) -> R$ +MAL_PRINT: + AZ%=A%: PR%=1: GOSUB PR_STR + RETURN + +REM REP(A$) -> R$ +REM Assume RE% has repl_env +REP: + R1%=0: R2%=0 + GOSUB MAL_READ + R1%=R% + IF ER%<>0 THEN GOTO REP_DONE + + A%=R%: E%=RE%: GOSUB EVAL + R2%=R% + IF ER%<>0 THEN GOTO REP_DONE + + A%=R%: GOSUB MAL_PRINT + RT$=R$ + + REP_DONE: + REM Release memory from MAL_READ and EVAL + IF R2%<>0 THEN AY%=R2%: GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + R$=RT$ + RETURN + +REM MAIN program +MAIN: + GOSUB INIT_MEMORY + + LV%=0 + + REM create repl_env + EO%=-1: GOSUB ENV_NEW + RE%=R% + + REM core.EXT: defined in Basic + E%=RE%: GOSUB INIT_CORE_NS: REM set core functions in repl_env + + ZT%=ZI%: REM top of memory after base repl_env + + REM core.mal: defined using the language itself + A$="(def! not (fn* (a) (if a false true)))" + GOSUB REP + + REM AZ%=Z%(RE%,1): GOSUB PR_STR + REM PRINT "env: " + R$ + "(" + STR$(RE%) + ")" + + REM B% = PEEK(57) + PEEK(58) * 256 + REM PRINT "57/58%: " + STR$(B%) + + MAIN_LOOP: + A$="user> " + GOSUB READLINE: REM /* call input parser */ + IF EOF=1 THEN GOTO MAIN_DONE + + A$=R$: GOSUB REP: REM /* call REP */ + + IF ER% THEN GOTO ERROR + PRINT R$ + GOTO MAIN_LOOP + + ERROR: + PRINT "Error: " + ER$ + ER%=0 + ER$="" + GOTO MAIN_LOOP + + MAIN_DONE: + REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + GOSUB PR_MEMORY_SUMMARY + END + diff --git a/basic/types.in.bas b/basic/types.in.bas index fb8f1ffed1..56cc29536c 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -27,7 +27,7 @@ INIT_MEMORY: REM S1%=4096: REM Z% (boxed memory) size (X2) S2%=256: REM ZS% (string memory) size S3%=256: REM ZZ% (call stack) size - S4%=64: REM ZR% (pending release stack) size + S4%=128: REM ZR% (release stack) size REM global error state ER%=0 @@ -112,6 +112,7 @@ FREE: REM RELEASE(AY%) -> nil +REM R% should not be affected by this call RELEASE: RC%=0 @@ -130,9 +131,14 @@ RELEASE: REM nil, false, true IF AY%<3 THEN GOTO RELEASE_TOP + U6%=Z%(AY%,0)AND15: REM type + + REM AZ%=AY%: PR%=1: GOSUB PR_STR + REM PRINT "RELEASE AY%:"+STR$(AY%)+"["+R$+"] (byte0:"+STR$(Z%(AY%,0))+")" + REM sanity check not already freed - IF (Z%(AY%,0)AND15)=15 THEN ER%=1: ER$="Free of free mem: " + STR$(AY%): RETURN - IF Z%(AY%,0)<16 THEN ER%=1: ER$="Free of freed object: " + STR$(AY%): RETURN + IF (U6%)=15 THEN ER%=1: ER$="Free of free memory: " + STR$(AY%): RETURN + IF Z%(AY%,0)<15 THEN ER%=1: ER$="Free of freed object: " + STR$(AY%): RETURN REM decrease reference count by one Z%(AY%,0)=Z%(AY%,0)-16 @@ -141,7 +147,6 @@ RELEASE: IF Z%(AY%,0)>=16 GOTO RELEASE_TOP REM switch on type - U6%=Z%(AY%,0)AND15: REM type IF (U6%<=5) OR (U6%=9) THEN GOTO RELEASE_SIMPLE IF (U6%>=6) AND (U6%<=8) THEN GOTO RELEASE_SEQ IF U6%=10 THEN GOTO RELEASE_MAL_FUNCTION @@ -192,7 +197,9 @@ RELEASE: REM RELEASE_PEND() -> nil RELEASE_PEND: + REM REM IF ER%<>0 THEN RETURN IF ZM%<0 THEN RETURN + REM PRINT "here2 RELEASE_PEND releasing:"+STR$(ZR%(ZM%)) AY%=ZR%(ZM%): GOSUB RELEASE ZM%=ZM%-1 GOTO RELEASE_PEND @@ -251,12 +258,16 @@ PR_MEMORY: PRINT ": ref cnt: " + STR$((Z%(I,0)AND-16)/16); PRINT ", type: " + STR$(Z%(I,0)AND15) + ", value: " + STR$(Z%(I,1)) I=I+1 + IF (Z%(I-1,0)AND15)<>10 THEN GOTO PR_MEMORY_VALUE_LOOP + PRINT " "+STR$(I)+": "; + PRINT "params: "+STR$(Z%(I+1,0))+", env:"+STR$(Z%(I+1,1)) + I=I+1 GOTO PR_MEMORY_VALUE_LOOP PR_MEMORY_FREE: PRINT ": FREE size: "+STR$((Z%(I,0)AND-16)/16)+", next: "+STR$(Z%(I,1)); IF I=ZK% THEN PRINT " (free list start)"; PRINT - IF (Z%(I,0)AND-16)=32 THEN I=I+1: PRINT " " + STR$(I) + ": ---" + IF (Z%(I,0)AND-16)=32 THEN I=I+1: PRINT " "+STR$(I)+": ---" I=I+1 GOTO PR_MEMORY_VALUE_LOOP PR_MEMORY_AFTER_VALUES: From 85d70fb79111f901e5ac3243769199918835ff6a Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 19 Sep 2016 21:23:21 -0500 Subject: [PATCH 0148/2308] Basic: step6 basics and atoms. Fix strings. Also: - command lines arguments are implemented by creating a file ".args.mal" that contains a list of arguments and that is loaded by the run script (load-file) into -*ARGS*-. The rest is put in *ARGV* and the first element is pulled out and used as the script name. - fix string reading/printing using new REPLACE function - add RE function to skip printing and to get back evaluated value (result must be freed by caller). Needed for step6 to get first argument pointer for scripting. - Sync earlier steps - add cons, first, rest to support parsing the command line. - eval is implemented as standard function in core.in.mal - fix println bug (using PR_STR rather than PR_STR_SEQ) - change sequence printing to save the initial sequence type on the stack and use that for the ending sequence delimeter. This way sequences of one type can still use the tail of sequences of a different type but still be considered the initial type. --- basic/.args.mal | 1 + basic/Makefile | 4 +- basic/core.in.bas | 151 +++++++++++- basic/printer.in.bas | 36 +-- basic/qb2cbm.sh | 4 +- basic/reader.in.bas | 10 +- basic/run | 1 + basic/step1_read_print.in.bas | 46 ++-- basic/step2_eval.in.bas | 74 +++--- basic/step3_env.in.bas | 90 +++---- basic/step4_if_fn_do.in.bas | 48 ++-- basic/step5_tco.in.bas | 46 ++-- basic/step6_file.in.bas | 450 ++++++++++++++++++++++++++++++++++ basic/types.in.bas | 35 ++- 14 files changed, 819 insertions(+), 177 deletions(-) create mode 100644 basic/.args.mal create mode 100755 basic/step6_file.in.bas diff --git a/basic/.args.mal b/basic/.args.mal new file mode 100644 index 0000000000..ff9a4f6107 --- /dev/null +++ b/basic/.args.mal @@ -0,0 +1 @@ +(list ) diff --git a/basic/Makefile b/basic/Makefile index c0fd4f27bd..68bbe2241c 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -1,3 +1,4 @@ +export KEEP_REM=0 step%.bas: step%.in.bas ./qb2cbm.sh $< > $@ @@ -14,6 +15,7 @@ step2_eval.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas step3_env.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas env.in.bas step4_if_fn_do.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas env.in.bas core.in.bas step5_tco.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas env.in.bas core.in.bas +step6_file.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas env.in.bas core.in.bas tests/%.bas: tests/%.in.bas ./qb2cbm.sh $< > $@ @@ -24,7 +26,7 @@ tests/%.prg: tests/%.bas rm $<.tmp -SOURCES_LISP = env.in.bas core.in.bas step5_tco.in.bas +SOURCES_LISP = env.in.bas core.in.bas step6_file.in.bas SOURCES = readline.in.bas types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP) .PHONY: stats diff --git a/basic/core.in.bas b/basic/core.in.bas index 1d963e6404..dcc8b9ea83 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -15,6 +15,8 @@ DO_FUNCTION: IF FF%=12 THEN DO_STR IF FF%=13 THEN DO_PRN IF FF%=14 THEN DO_PRINTLN + IF FF%=16 THEN DO_READ_STRING + IF FF%=17 THEN DO_SLURP IF FF%=18 THEN DO_LT IF FF%=19 THEN DO_LTE @@ -28,11 +30,21 @@ DO_FUNCTION: IF FF%=27 THEN DO_LIST IF FF%=28 THEN DO_LIST_Q + IF FF%=39 THEN DO_CONS + IF FF%=43 THEN DO_FIRST + IF FF%=44 THEN DO_REST IF FF%=45 THEN DO_EMPTY_Q IF FF%=46 THEN DO_COUNT + IF FF%=53 THEN DO_ATOM + IF FF%=54 THEN DO_ATOM_Q + IF FF%=55 THEN DO_DEREF + IF FF%=56 THEN DO_RESET_BANG + IF FF%=57 THEN DO_SWAP_BANG + IF FF%=58 THEN DO_PR_MEMORY IF FF%=59 THEN DO_PR_MEMORY_SUMMARY + IF FF%=60 THEN DO_EVAL ER%=1: ER$="unknown function" + STR$(FF%): RETURN DO_EQUAL_Q: @@ -62,10 +74,35 @@ DO_FUNCTION: R%=0 RETURN DO_PRINTLN: - AZ%=AA%: PR%=0: SE$=" ": GOSUB PR_STR + AZ%=AR%: PR%=0: SE$=" ": GOSUB PR_STR_SEQ PRINT R$ R%=0 RETURN + DO_READ_STRING: + A$=ZS$(Z%(AA%,1)) + GOSUB READ_STR + RETURN + DO_SLURP: + R$="" + REM OPEN 1,8,2,ZS$(Z%(AA%,1))+",SEQ,R" + REM OPEN 1,8,2,ZS$(Z%(AA%,1)) + OPEN 1,8,0,ZS$(Z%(AA%,1)) + DO_SLURP_LOOP: + A$="" + GET#1,A$ + IF ASC(A$)=10 THEN R$=R$+CHR$(13) + IF (ASC(A$)<>10) AND (A$<>"") THEN R$=R$+A$ + IF (ST AND 64) THEN GOTO DO_SLURP_DONE + IF (ST AND 255) THEN ER%=-1: ER%="File read error "+STR$(ST): RETURN + GOTO DO_SLURP_LOOP + DO_SLURP_DONE: + CLOSE 1 + AS$=R$: GOSUB STRING + R4%=R% + SZ%=1: GOSUB ALLOC + Z%(R%,0) = 4+16 + Z%(R%,1) = R4% + RETURN DO_LT: R%=1 @@ -114,6 +151,19 @@ DO_FUNCTION: R%=R%+1: REM map to mal false/true RETURN + DO_CONS: + A%=AA%: B%=AB%: GOSUB CONS + RETURN + DO_FIRST: + IF Z%(AA%,1)=0 THEN R%=0 + IF Z%(AA%,1)<>0 THEN R%=AA%+1: GOSUB DEREF_R + IF R%<>0 THEN Z%(R%,0)=Z%(R%,0)+16 + RETURN + DO_REST: + IF Z%(AA%,1)=0 THEN R%=AA% + IF Z%(AA%,1)<>0 THEN R%=Z%(AA%,1) + Z%(R%,0)=Z%(R%,0)+16 + RETURN DO_EMPTY_Q: R%=1 IF Z%(AA%,1)=0 THEN R%=2 @@ -126,14 +176,99 @@ DO_FUNCTION: Z%(R%,1) = R4% RETURN + DO_ATOM: + SZ%=1: GOSUB ALLOC + Z%(AA%,0)=Z%(AA%,0)+16: REM inc ref cnt of contained value + Z%(R%,0) = 11+16 + Z%(R%,1) = AA% + RETURN + DO_ATOM_Q: + R%=1 + IF (Z%(AA%,0)AND15)=11 THEN R%=2 + RETURN + DO_DEREF: + R%=Z%(AA%,1): GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + RETURN + DO_RESET_BANG: + R%=AB% + REM release current value + AY%=Z%(AA%,1): GOSUB RELEASE + REM inc ref by 2 for atom ownership and since we are returning it + Z%(R%,0)=Z%(R%,0)+32 + REM update value + Z%(AA%,1)=R% + RETURN + DO_SWAP_BANG: + F%=AB% + + REM add atom to front of the args list + A%=Z%(AA%,1): B%=Z%(Z%(AR%,1),1): GOSUB CONS + AR%=R% + + REM push args for release after + ZL%=ZL%+1: ZZ%(ZL%)=AR% + + REM TODO: break this out into APPLY + IF (Z%(F%,0)AND15)=9 THEN GOTO DO_SWAP_FUNCTION + IF (Z%(F%,0)AND15)=10 THEN GOTO DO_SWAP_MAL_FUNCTION + + DO_SWAP_FUNCTION: + REM push atom + ZL%=ZL%+1: ZZ%(ZL%)=AA% + + GOSUB DO_FUNCTION + + REM pop atom + AA%=ZZ%(ZL%): ZL%=ZL%-1 + + REM pop and release args + AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + + GOTO DO_SWAP_DONE + + DO_SWAP_MAL_FUNCTION: + REM push current environment for later release + ZL%=ZL%+1: ZZ%(ZL%)=E% + + REM create new environ using env stored with function + EO%=Z%(F%+1,1): BI%=Z%(F%+1,0): EX%=AR%: GOSUB ENV_NEW_BINDS + + REM push atom + ZL%=ZL%+1: ZZ%(ZL%)=AA% + + A%=Z%(F%,1): E%=R%: GOSUB EVAL + + REM pop atom + AA%=ZZ%(ZL%): ZL%=ZL%-1 + + REM pop and release args + AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + + REM pop and release previous env + AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + + GOTO DO_SWAP_DONE + + DO_SWAP_DONE: + REM use reset to update the value + AB%=R%: GOSUB DO_RESET_BANG + REM but decrease ref cnt of return by 1 (not sure why) + AY%=R%: GOSUB RELEASE + RETURN + DO_PR_MEMORY: P1%=ZT%: P2%=-1: GOSUB PR_MEMORY RETURN - DO_PR_MEMORY_SUMMARY: GOSUB PR_MEMORY_SUMMARY RETURN + DO_EVAL: + AZ%=AA%: PR%=1: GOSUB PR_STR + A%=AA%: E%=RE%: GOSUB EVAL + RETURN + INIT_CORE_SET_FUNCTION: GOSUB NATIVE_FUNCTION V%=R%: GOSUB ENV_SET_S @@ -150,6 +285,8 @@ INIT_CORE_NS: K$="str": A%=12: GOSUB INIT_CORE_SET_FUNCTION K$="prn": A%=13: GOSUB INIT_CORE_SET_FUNCTION K$="println": A%=14: GOSUB INIT_CORE_SET_FUNCTION + K$="read-string": A%=16: GOSUB INIT_CORE_SET_FUNCTION + K$="slurp": A%=17: GOSUB INIT_CORE_SET_FUNCTION K$="<": A%=18: GOSUB INIT_CORE_SET_FUNCTION K$="<=": A%=19: GOSUB INIT_CORE_SET_FUNCTION @@ -163,10 +300,20 @@ INIT_CORE_NS: K$="list": A%=27: GOSUB INIT_CORE_SET_FUNCTION K$="list?": A%=28: GOSUB INIT_CORE_SET_FUNCTION + K$="cons": A%=39: GOSUB INIT_CORE_SET_FUNCTION + K$="first": A%=43: GOSUB INIT_CORE_SET_FUNCTION + K$="rest": A%=44: GOSUB INIT_CORE_SET_FUNCTION K$="empty?": A%=45: GOSUB INIT_CORE_SET_FUNCTION K$="count": A%=46: GOSUB INIT_CORE_SET_FUNCTION + K$="atom": A%=53: GOSUB INIT_CORE_SET_FUNCTION + K$="atom?": A%=54: GOSUB INIT_CORE_SET_FUNCTION + K$="deref": A%=55: GOSUB INIT_CORE_SET_FUNCTION + K$="reset!": A%=56: GOSUB INIT_CORE_SET_FUNCTION + K$="swap!": A%=57: GOSUB INIT_CORE_SET_FUNCTION + K$="pr-memory": A%=58: GOSUB INIT_CORE_SET_FUNCTION K$="pr-memory-summary": A%=59: GOSUB INIT_CORE_SET_FUNCTION + K$="eval": A%=60: GOSUB INIT_CORE_SET_FUNCTION RETURN diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 892b5a3f31..96654cba0d 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -17,6 +17,7 @@ PR_STR: IF T%=8 THEN PR_SEQ IF T%=9 THEN PR_FUNCTION IF T%=10 THEN PR_MAL_FUNCTION + IF T%=11 THEN PR_ATOM IF T%=13 THEN PR_ENV IF T%=15 THEN PR_FREE R$="#" @@ -33,7 +34,11 @@ PR_STR: R$=ZS$(Z%(AZ%,1)) RETURN PR_STRING_READABLY: - R$=CHR$(34) + ZS$(Z%(AZ%,1)) + CHR$(34) + R$=ZS$(Z%(AZ%,1)) + S1$=CHR$(92): S2$=CHR$(92)+CHR$(92): GOSUB REPLACE: REM escape backslash + S1$=CHR$(34): S2$=CHR$(92)+CHR$(34): GOSUB REPLACE: REM escape quotes + S1$=CHR$(13): S2$=CHR$(92)+"n": GOSUB REPLACE: REM escape newlines + R$=CHR$(34)+R$+CHR$(34) RETURN PR_SYMBOL: R$=ZS$(Z%(AZ%,1)) @@ -42,31 +47,28 @@ PR_STR: IF T%=6 THEN RR$=RR$+"(" IF T%=7 THEN RR$=RR$+"[" IF T%=8 THEN RR$=RR$+"{" - REM push where we are in the sequence - ZL%=ZL%+1 - ZZ%(ZL%)= AZ% + REM push the type and where we are in the sequence + ZL%=ZL%+2 + ZZ%(ZL%-1)=T% + ZZ%(ZL%)=AZ% PR_SEQ_LOOP: - IF Z%(AZ%,1) = 0 THEN PR_SEQ_DONE + IF Z%(AZ%,1)=0 THEN PR_SEQ_DONE AZ%=AZ%+1 - REM Push type we are rendering on the stack - ZL%=ZL%+1 - ZZ%(ZL%) = Z%(AZ%,0)AND15 GOSUB PR_STR_RECUR REM if we just rendered a non-sequence, then append it IF (T% < 6) OR (T% > 8) THEN RR$=RR$+R$ - REM pop type off stack and check it - T%=ZZ%(ZL%) - ZL%=ZL%-1 + REM restore current seq type + T%=ZZ%(ZL%-1) REM Go to next list element AZ%=Z%(ZZ%(ZL%),1) ZZ%(ZL%) = AZ% IF Z%(AZ%,1) <> 0 THEN RR$=RR$+" " GOTO PR_SEQ_LOOP PR_SEQ_DONE: - REM get current type - T%=Z%(ZZ%(ZL%),0)AND15 - REM pop where we are the sequence - ZL%=ZL%-1 + REM get type + T%=ZZ%(ZL%-1) + REM pop where we are the sequence and type + ZL%=ZL%-2 IF T%=6 THEN RR$=RR$+")" IF T%=7 THEN RR$=RR$+"]" IF T%=8 THEN RR$=RR$+"}" @@ -83,6 +85,10 @@ PR_STR: AZ%=Z%(T1%,1): GOSUB PR_STR_RECUR R$=T7$ + " " + R$ + ")" RETURN + PR_ATOM: + AZ%=Z%(AZ%,1): GOSUB PR_STR_RECUR + R$="(atom " + R$ + ")" + RETURN PR_ENV: R$="#" RETURN diff --git a/basic/qb2cbm.sh b/basic/qb2cbm.sh index 2bf6356f1b..e7ac6fbafa 100755 --- a/basic/qb2cbm.sh +++ b/basic/qb2cbm.sh @@ -57,7 +57,7 @@ done data="" declare -A labels -lnum=10 +lnum=1 while read -r line; do if [[ ${line} =~ ^\ *# ]]; then [ "${DEBUG}" ] && echo >&2 "ignoring # style comment at $lnum" @@ -82,7 +82,7 @@ while read -r line; do else data="${data}${lnum} ${line}\n" fi - lnum=$(( lnum + 10 )) + lnum=$(( lnum + 1 )) done < <(echo -e "${input}") if [[ "${KEEP_REM}" -lt 4 ]]; then diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 1a1d3d1cb1..4996c891ea 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -28,7 +28,7 @@ READ_TOKEN: SKIP_SPACES: CH$=MID$(A$,IDX%,1) - IF (CH$<>" " AND CH$<>",") THEN RETURN + IF (CH$<>" ") AND (CH$<>",") AND (CH$<>CHR$(13)) AND (CH$<>CHR$(10)) THEN RETURN IDX%=IDX%+1 GOTO SKIP_SPACES @@ -74,11 +74,15 @@ READ_FORM: Z%(R%,1) = VAL(T$) GOTO READ_FORM_DONE READ_STRING: + REM PRINT "READ_STRING" T7$=MID$(T$,LEN(T$),1) IF T7$<>CHR$(34) THEN ER$="expected '"+CHR$(34)+"'": GOTO READ_FORM_ABORT - REM PRINT "READ_STRING" + R$=MID$(T$,2,LEN(T$)-2) + S1$=CHR$(92)+CHR$(34): S2$=CHR$(34): GOSUB REPLACE: REM unescape quotes + S1$=CHR$(92)+"n": S2$=CHR$(13): GOSUB REPLACE: REM unescape newlines + S1$=CHR$(92)+CHR$(92): S2$=CHR$(92): GOSUB REPLACE: REM unescape backslashes REM intern string value - AS$=MID$(T$, 2, LEN(T$)-2): GOSUB STRING + AS$=R$: GOSUB STRING T7%=R% SZ%=1: GOSUB ALLOC Z%(R%,0) = 4+16 diff --git a/basic/run b/basic/run index 2fe259b544..6c2e48acb9 100755 --- a/basic/run +++ b/basic/run @@ -1,2 +1,3 @@ #!/bin/bash +(echo "(list $(for a in "${@}"; do echo -n "\"${a}\""; done))") > $(dirname $0)/.args.mal exec cbmbasic $(dirname $0)/${STEP:-stepA_mal}.bas "${@}" diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index d0a99647b3..d9176edaa2 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -23,39 +23,45 @@ MAL_PRINT: REM REP(A$) -> R$ REP: GOSUB MAL_READ - IF ER% THEN RETURN + IF ER%<>0 THEN GOTO REP_DONE + A%=R%: GOSUB EVAL - IF ER% THEN RETURN - A%=R%: GOSUB MAL_PRINT + IF ER%<>0 THEN GOTO REP_DONE - REM Release memory from EVAL - AY%=R%: GOSUB RELEASE + A%=R%: GOSUB MAL_PRINT + RT$=R$ - RETURN + REP_DONE: + REM Release memory from EVAL + AY%=R%: GOSUB RELEASE + R$=RT$ + RETURN REM MAIN program MAIN: GOSUB INIT_MEMORY - ZT%=ZI%: REM top of memory after repl_env + ZT%=ZI%: REM top of memory after base repl_env - MAIN_LOOP: + REPL_LOOP: A$="user> " GOSUB READLINE: REM /* call input parser */ - IF EOF=1 THEN GOTO MAIN_DONE - A$=R$: GOSUB REP: REM /* call REP */ - IF ER% THEN GOTO ERROR - PRINT R$ - GOTO MAIN_LOOP + IF EOF=1 THEN GOTO QUIT + + A$=R$: GOSUB REP: REM call REP - ERROR: - PRINT "Error: " + ER$ - ER%=0 - ER$="" - GOTO MAIN_LOOP + IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP + PRINT R$ + GOTO REPL_LOOP - MAIN_DONE: - P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + QUIT: + REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END + PRINT_ERROR: + PRINT "Error: " + ER$ + ER%=0 + ER$="" + RETURN + diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index aa6dbd3be1..1b165aeac8 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -14,8 +14,10 @@ REM EVAL_AST(A%, E%) -> R% EVAL_AST: LV%=LV%+1 + REM push A% and E% on the stack ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% - IF ER%=1 THEN GOTO EVAL_AST_RETURN + + IF ER%<>0 THEN GOTO EVAL_AST_RETURN REM AZ%=A%: GOSUB PR_STR REM PRINT "EVAL_AST: " + R$ + "(" + STR$(A%) + ")" @@ -40,7 +42,7 @@ EVAL_AST: IF T3%=0 THEN ER%=1: ER$="'"+ZS$(Z%(A%,1))+"' not found": GOTO EVAL_AST_RETURN Z%(R%,0)=Z%(R%,0)+16 GOTO EVAL_AST_RETURN - + EVAL_AST_SEQ: REM allocate the first entry SZ%=2: GOSUB ALLOC @@ -73,7 +75,7 @@ EVAL_AST: REM if hashmap, skip eval of even entries (keys) IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL - + EVAL_AST_DO_REF: R%=A%+1: GOSUB DEREF_R: REM deref to target of referred entry Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value @@ -83,14 +85,15 @@ EVAL_AST: REM call EVAL for each entry A%=A%+1: GOSUB EVAL A%=A%-1 - IF ER%=1 THEN GOTO EVAL_AST_SEQ_LOOP_DONE GOSUB DEREF_R: REM deref to target of evaluated entry EVAL_AST_ADD_VALUE: - REM update previous value pointer to evaluated entry + REM update previous value pointer to evaluated entry Z%(ZZ%(ZL%)+1,1)=R% + IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM allocate the next entry SZ%=2: GOSUB ALLOC @@ -123,11 +126,9 @@ EVAL: REM push A% and E% on the stack ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% - IF ER%=1 THEN GOTO EVAL_RETURN REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + ")" - REM PRINT "EVAL level: " + STR$(LV%) + REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + "), LV%:"+STR$(LV%) GOSUB DEREF_A @@ -145,8 +146,10 @@ EVAL: GOSUB EVAL_AST R3%=R% - IF ER%=1 THEN GOTO EVAL_RETURN + REM if error, return f/args for release by caller + IF ER%<>0 THEN GOTO EVAL_RETURN F%=R%+1 + AR%=Z%(R%,1): REM rest R%=F%: GOSUB DEREF_R: F%=R% IF (Z%(F%,0)AND15)<>9 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN @@ -222,36 +225,30 @@ REM Assume RE% has repl_env REP: R1%=0: R2%=0 GOSUB MAL_READ - IF ER% THEN GOTO REP_DONE R1%=R% - - REM PRINT "After read:" - REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY + IF ER%<>0 THEN GOTO REP_DONE A%=R%: E%=RE%: GOSUB EVAL - IF ER% THEN GOTO REP_DONE R2%=R% - - REM PRINT "After eval, before print:" - REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY + IF ER%<>0 THEN GOTO REP_DONE A%=R%: GOSUB MAL_PRINT + RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL IF R2%<>0 THEN AY%=R2%: GOSUB RELEASE IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE - - REM PRINT "After releases:" - REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY - + R$=RT$ RETURN REM MAIN program MAIN: GOSUB INIT_MEMORY - REM repl_env + LV%=0 + + REM create repl_env GOSUB HASHMAP RE%=R% @@ -275,32 +272,27 @@ MAIN: HM%=RE%: K$="/": V%=R%: GOSUB ASSOC1_S RE%=R% - ZT%=ZI%: REM top of memory after repl_env + ZT%=ZI%: REM top of memory after base repl_env - REM AZ%=RE%: GOSUB PR_STR - REM PRINT "env: " + R$ + "(" + STR$(RE%) + ")" - - MAIN_LOOP: + REPL_LOOP: A$="user> " GOSUB READLINE: REM /* call input parser */ - IF EOF=1 THEN GOTO MAIN_DONE - A$=R$: GOSUB REP: REM /* call REP */ + IF EOF=1 THEN GOTO QUIT - REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY - REM GOSUB PR_MEMORY_SUMMARY + A$=R$: GOSUB REP: REM call REP - IF ER% THEN GOTO ERROR + IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP PRINT R$ - GOTO MAIN_LOOP + GOTO REPL_LOOP - ERROR: - PRINT "Error: " + ER$ - ER%=0 - ER$="" - GOTO MAIN_LOOP - - MAIN_DONE: - P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + QUIT: + REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END + PRINT_ERROR: + PRINT "Error: " + ER$ + ER%=0 + ER$="" + RETURN + diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index 9a20900959..d87542642b 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -18,7 +18,7 @@ EVAL_AST: REM push A% and E% on the stack ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% - IF ER%=1 THEN GOTO EVAL_AST_RETURN + IF ER%<>0 THEN GOTO EVAL_AST_RETURN REM AZ%=A%: GOSUB PR_STR REM PRINT "EVAL_AST: " + R$ + "(" + STR$(A%) + ")" @@ -40,7 +40,7 @@ EVAL_AST: EVAL_AST_SYMBOL: K%=A%: GOSUB ENV_GET GOTO EVAL_AST_RETURN - + EVAL_AST_SEQ: REM allocate the first entry SZ%=2: GOSUB ALLOC @@ -73,7 +73,7 @@ EVAL_AST: REM if hashmap, skip eval of even entries (keys) IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL - + EVAL_AST_DO_REF: R%=A%+1: GOSUB DEREF_R: REM deref to target of referred entry Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value @@ -83,14 +83,15 @@ EVAL_AST: REM call EVAL for each entry A%=A%+1: GOSUB EVAL A%=A%-1 - IF ER%=1 THEN GOTO EVAL_AST_SEQ_LOOP_DONE GOSUB DEREF_R: REM deref to target of evaluated entry EVAL_AST_ADD_VALUE: - REM update previous value pointer to evaluated entry + REM update previous value pointer to evaluated entry Z%(ZZ%(ZL%)+1,1)=R% + IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM allocate the next entry SZ%=2: GOSUB ALLOC @@ -107,7 +108,7 @@ EVAL_AST: REM if no error, get return value (new seq) IF ER%=0 THEN R%=ZZ%(ZL%-1) REM otherwise, free the return value and return nil - IF ER%=1 THEN R%=0: AY%=ZZ%(ZL%-1): GOSUB RELEASE + IF ER%<>0 THEN R%=0: AY%=ZZ%(ZL%-1): GOSUB RELEASE REM pop previous, return, index and type ZL%=ZL%-4 @@ -126,11 +127,9 @@ EVAL: REM push A% and E% on the stack ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% - IF ER%=1 THEN GOTO EVAL_RETURN REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + ")" - REM PRINT "EVAL level: " + STR$(LV%) + REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + "), LV%:"+STR$(LV%) GOSUB DEREF_A @@ -170,18 +169,14 @@ EVAL: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% - REM push A1% - ZL%=ZL%+1: ZZ%(ZL%)=A1% - + ZL%=ZL%+1: ZZ%(ZL%)=A1%: REM push A1% A%=A2%: GOSUB EVAL: REM eval a2 - - REM pop A1% - A1%=ZZ%(ZL%): ZL%=ZL%-1 + A1%=ZZ%(ZL%): ZL%=ZL%-1: REM pop A1% REM set a1 in env to a2 K%=A1%: V%=R%: GOSUB ENV_SET - GOTO EVAL_RETURN + EVAL_LET: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% @@ -190,29 +185,32 @@ EVAL: E%=R% EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + REM push A1% ZL%=ZL%+1: ZZ%(ZL%)=A1% REM eval current A1 odd element A%=Z%(A1%,1)+1: GOSUB EVAL REM pop A1% A1%=ZZ%(ZL%): ZL%=ZL%-1 + REM set environment: even A1% key to odd A1% eval'd above K%=A1%+1: V%=R%: GOSUB ENV_SET AY%=R%: GOSUB RELEASE: REM release our use, ENV_SET took ownership + REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: A%=A2%: GOSUB EVAL: REM eval a2 using let_env - REM release the let env - AY%=E%: GOSUB RELEASE GOTO EVAL_RETURN EVAL_INVOKE: GOSUB EVAL_AST R3%=R% - IF ER%=1 THEN GOTO EVAL_RETURN + REM if error, return f/args for release by caller + IF ER%<>0 THEN GOTO EVAL_RETURN F%=R%+1 + AR%=Z%(R%,1): REM rest R%=F%: GOSUB DEREF_R: F%=R% IF (Z%(F%,0)AND15)<>9 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN @@ -221,8 +219,9 @@ EVAL: GOTO EVAL_RETURN EVAL_RETURN: - REM an error occured, free up any new value - IF ER%=1 THEN AY%=R%: GOSUB RELEASE + REM release environment if not the top one on the stack + IF E%<>ZZ%(ZL%-1) THEN AY%=E%: GOSUB RELEASE + REM trigger GC TA%=FRE(0) @@ -288,29 +287,21 @@ REM Assume RE% has repl_env REP: R1%=0: R2%=0 GOSUB MAL_READ - IF ER% THEN GOTO REP_DONE R1%=R% - - REM PRINT "After read:" - REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY + IF ER%<>0 THEN GOTO REP_DONE A%=R%: E%=RE%: GOSUB EVAL - IF ER% THEN GOTO REP_DONE R2%=R% - - REM PRINT "After eval, before print:" - REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY + IF ER%<>0 THEN GOTO REP_DONE A%=R%: GOSUB MAL_PRINT + RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL IF R2%<>0 THEN AY%=R2%: GOSUB RELEASE IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE - - REM PRINT "After releases:" - REM P1%=ZT%: P2%=0: GOSUB PR_MEMORY - + R$=RT$ RETURN REM MAIN program @@ -340,32 +331,27 @@ MAIN: A%=4: GOSUB NATIVE_FUNCTION K$="/": V%=R%: GOSUB ENV_SET_S - ZT%=ZI%: REM top of memory after repl_env - - REM AZ%=Z%(RE%,1): GOSUB PR_STR - REM PRINT "env: " + R$ + "(" + STR$(RE%) + ")" + ZT%=ZI%: REM top of memory after base repl_env - MAIN_LOOP: + REPL_LOOP: A$="user> " GOSUB READLINE: REM /* call input parser */ - IF EOF=1 THEN GOTO MAIN_DONE - A$=R$: GOSUB REP: REM /* call REP */ + IF EOF=1 THEN GOTO QUIT - REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY - REM GOSUB PR_MEMORY_SUMMARY + A$=R$: GOSUB REP: REM call REP - IF ER% THEN GOTO ERROR + IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP PRINT R$ - GOTO MAIN_LOOP - - ERROR: - PRINT "Error: " + ER$ - ER%=0 - ER$="" - GOTO MAIN_LOOP + GOTO REPL_LOOP - MAIN_DONE: - P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + QUIT: + REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END + PRINT_ERROR: + PRINT "Error: " + ER$ + ER%=0 + ER$="" + RETURN + diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index 5dd4914b31..6c53370749 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -210,8 +210,6 @@ EVAL: GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: A%=A2%: GOSUB EVAL: REM eval a2 using let_env - REM REM release the let env - REM AY%=E%: GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO: A%=Z%(A%,1): REM rest @@ -330,6 +328,22 @@ MAL_PRINT: AZ%=A%: PR%=1: GOSUB PR_STR RETURN +REM RE(A$) -> R% +REM Assume RE% has repl_env +REM caller must release result +RE: + R1%=0 + GOSUB MAL_READ + R1%=R% + IF ER%<>0 THEN GOTO REP_DONE + + A%=R%: E%=RE%: GOSUB EVAL + + REP_DONE: + REM Release memory from MAL_READ + IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + RETURN: REM caller must release result of EVAL + REM REP(A$) -> R$ REM Assume RE% has repl_env REP: @@ -369,33 +383,27 @@ MAIN: REM core.mal: defined using the language itself A$="(def! not (fn* (a) (if a false true)))" - GOSUB REP - - REM AZ%=Z%(RE%,1): GOSUB PR_STR - REM PRINT "env: " + R$ + "(" + STR$(RE%) + ")" - - REM B% = PEEK(57) + PEEK(58) * 256 - REM PRINT "57/58%: " + STR$(B%) + GOSUB RE: AY%=R%: GOSUB RELEASE - MAIN_LOOP: + REPL_LOOP: A$="user> " GOSUB READLINE: REM /* call input parser */ - IF EOF=1 THEN GOTO MAIN_DONE + IF EOF=1 THEN GOTO QUIT A$=R$: GOSUB REP: REM /* call REP */ - IF ER% THEN GOTO ERROR + IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP PRINT R$ - GOTO MAIN_LOOP + GOTO REPL_LOOP - ERROR: - PRINT "Error: " + ER$ - ER%=0 - ER$="" - GOTO MAIN_LOOP - - MAIN_DONE: + QUIT: REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END + PRINT_ERROR: + PRINT "Error: " + ER$ + ER%=0 + ER$="" + RETURN + diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index cb27504e62..aa29bc77ea 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -338,6 +338,22 @@ MAL_PRINT: AZ%=A%: PR%=1: GOSUB PR_STR RETURN +REM RE(A$) -> R% +REM Assume RE% has repl_env +REM caller must release result +RE: + R1%=0 + GOSUB MAL_READ + R1%=R% + IF ER%<>0 THEN GOTO REP_DONE + + A%=R%: E%=RE%: GOSUB EVAL + + REP_DONE: + REM Release memory from MAL_READ + IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + RETURN: REM caller must release result of EVAL + REM REP(A$) -> R$ REM Assume RE% has repl_env REP: @@ -377,33 +393,27 @@ MAIN: REM core.mal: defined using the language itself A$="(def! not (fn* (a) (if a false true)))" - GOSUB REP - - REM AZ%=Z%(RE%,1): GOSUB PR_STR - REM PRINT "env: " + R$ + "(" + STR$(RE%) + ")" - - REM B% = PEEK(57) + PEEK(58) * 256 - REM PRINT "57/58%: " + STR$(B%) + GOSUB RE: AY%=R%: GOSUB RELEASE - MAIN_LOOP: + REPL_LOOP: A$="user> " GOSUB READLINE: REM /* call input parser */ - IF EOF=1 THEN GOTO MAIN_DONE + IF EOF=1 THEN GOTO QUIT A$=R$: GOSUB REP: REM /* call REP */ - IF ER% THEN GOTO ERROR + IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP PRINT R$ - GOTO MAIN_LOOP + GOTO REPL_LOOP - ERROR: - PRINT "Error: " + ER$ - ER%=0 - ER$="" - GOTO MAIN_LOOP - - MAIN_DONE: + QUIT: REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END + PRINT_ERROR: + PRINT "Error: " + ER$ + ER%=0 + ER$="" + RETURN + diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas new file mode 100755 index 0000000000..5cc8cd1205 --- /dev/null +++ b/basic/step6_file.in.bas @@ -0,0 +1,450 @@ +REM POKE 1, PEEK(1)AND248: REM enable all ROM areas as RAM +REM POKE 55,0: POKE 56,192: CLR: REM move BASIC end from $A000 to $C000 +GOTO MAIN + +REM $INCLUDE: 'readline.in.bas' +REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'reader.in.bas' +REM $INCLUDE: 'printer.in.bas' +REM $INCLUDE: 'env.in.bas' +REM $INCLUDE: 'core.in.bas' + +REM READ(A$) -> R% +MAL_READ: + GOSUB READ_STR + RETURN + +REM EVAL_AST(A%, E%) -> R% +REM called using GOTO to avoid basic return address stack usage +REM top of stack should have return label index +EVAL_AST: + REM push A% and E% on the stack + ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + + IF ER%<>0 THEN GOTO EVAL_AST_RETURN + + GOSUB DEREF_A + + T%=Z%(A%,0)AND15 + IF T%=5 THEN EVAL_AST_SYMBOL + IF T%=6 THEN EVAL_AST_SEQ + IF T%=7 THEN EVAL_AST_SEQ + IF T%=8 THEN EVAL_AST_SEQ + + REM scalar: deref to actual value and inc ref cnt + R%=A%: GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + GOTO EVAL_AST_RETURN + + EVAL_AST_SYMBOL: + K%=A%: GOSUB ENV_GET + GOTO EVAL_AST_RETURN + + EVAL_AST_SEQ: + REM allocate the first entry + SZ%=2: GOSUB ALLOC + + REM make space on the stack + ZL%=ZL%+4 + REM push type of sequence + ZZ%(ZL%-3)=T% + REM push sequence index + ZZ%(ZL%-2)=-1 + REM push future return value (new sequence) + ZZ%(ZL%-1)=R% + REM push previous new sequence entry + ZZ%(ZL%)=R% + + EVAL_AST_SEQ_LOOP: + REM set new sequence entry type (with 1 ref cnt) + Z%(R%,0)=ZZ%(ZL%-3)+16 + Z%(R%,1)=0 + REM create value ptr placeholder + Z%(R%+1,0)=14 + Z%(R%+1,1)=0 + + REM update index + ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + + REM check if we are done evaluating the source sequence + IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + + REM if hashmap, skip eval of even entries (keys) + IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + GOTO EVAL_AST_DO_EVAL + + EVAL_AST_DO_REF: + R%=A%+1: GOSUB DEREF_R: REM deref to target of referred entry + Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + GOTO EVAL_AST_ADD_VALUE + + EVAL_AST_DO_EVAL: + REM call EVAL for each entry + A%=A%+1: GOSUB EVAL + A%=A%-1 + GOSUB DEREF_R: REM deref to target of evaluated entry + + EVAL_AST_ADD_VALUE: + + REM update previous value pointer to evaluated entry + Z%(ZZ%(ZL%)+1,1)=R% + + IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + + REM allocate the next entry + SZ%=2: GOSUB ALLOC + + REM update previous sequence entry value to point to new entry + Z%(ZZ%(ZL%),1)=R% + REM update previous ptr to current entry + ZZ%(ZL%)=R% + + REM process the next sequence entry from source list + A%=Z%(A%,1) + + GOTO EVAL_AST_SEQ_LOOP + EVAL_AST_SEQ_LOOP_DONE: + REM get return value (new seq) + R%=ZZ%(ZL%-1) + + REM pop previous, return, index and type + ZL%=ZL%-4 + GOTO EVAL_AST_RETURN + + EVAL_AST_RETURN: + REM pop A% and E% off the stack + E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + + REM pop EVAL AST return label/address + RN%=ZZ%(ZL%): ZL%=ZL%-1 + IF RN%=1 GOTO EVAL_AST_RETURN_1 + IF RN%=2 GOTO EVAL_AST_RETURN_2 + IF RN%=3 GOTO EVAL_AST_RETURN_3 + RETURN + +REM EVAL(A%, E%)) -> R% +EVAL: + LV%=LV%+1: REM track basic return stack level + + REM push A% and E% on the stack + ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + + EVAL_TCO_RECUR: + + REM AZ%=A%: GOSUB PR_STR + REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + "), LV%:"+STR$(LV%) + + GOSUB DEREF_A + + GOSUB LIST_Q + IF R% THEN GOTO APPLY_LIST + REM ELSE + REM push EVAL_AST return label/address + ZL%=ZL%+1: ZZ%(ZL%)=1 + GOTO EVAL_AST + EVAL_AST_RETURN_1: + + GOTO EVAL_RETURN + + APPLY_LIST: + GOSUB EMPTY_Q + IF R% THEN R%=A%: Z%(R%,0)=Z%(R%,0)+16: GOTO EVAL_RETURN + + A0%=A%+1 + R%=A0%: GOSUB DEREF_R: A0%=R% + + REM get symbol in A$ + IF (Z%(A0%,0)AND15)<>5 THEN A$="" + IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) + + IF A$="def!" THEN GOTO EVAL_DEF + IF A$="let*" THEN GOTO EVAL_LET + IF A$="do" THEN GOTO EVAL_DO + IF A$="if" THEN GOTO EVAL_IF + IF A$="fn*" THEN GOTO EVAL_FN + GOTO EVAL_INVOKE + + EVAL_GET_A3: + A3% = Z%(Z%(Z%(A%,1),1),1)+1 + R%=A3%: GOSUB DEREF_R: A3%=R% + EVAL_GET_A2: + A2% = Z%(Z%(A%,1),1)+1 + R%=A2%: GOSUB DEREF_R: A2%=R% + EVAL_GET_A1: + A1% = Z%(A%,1)+1 + R%=A1%: GOSUB DEREF_R: A1%=R% + RETURN + + EVAL_DEF: + REM PRINT "def!" + GOSUB EVAL_GET_A2: REM set a1% and a2% + + ZL%=ZL%+1: ZZ%(ZL%)=A1%: REM push A1% + A%=A2%: GOSUB EVAL: REM eval a2 + A1%=ZZ%(ZL%): ZL%=ZL%-1: REM pop A1% + + REM set a1 in env to a2 + K%=A1%: V%=R%: GOSUB ENV_SET + GOTO EVAL_RETURN + + EVAL_LET: + REM PRINT "let*" + GOSUB EVAL_GET_A2: REM set a1% and a2% + + E4%=E%: REM save the current environment for release + + REM create new environment with outer as current environment + EO%=E%: GOSUB ENV_NEW + E%=R% + EVAL_LET_LOOP: + IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + + REM push A1% + ZL%=ZL%+1: ZZ%(ZL%)=A1% + REM eval current A1 odd element + A%=Z%(A1%,1)+1: GOSUB EVAL + REM pop A1% + A1%=ZZ%(ZL%): ZL%=ZL%-1 + + REM set environment: even A1% key to odd A1% eval'd above + K%=A1%+1: V%=R%: GOSUB ENV_SET + AY%=R%: GOSUB RELEASE: REM release our use, ENV_SET took ownership + + REM skip to the next pair of A1% elements + A1%=Z%(Z%(A1%,1),1) + GOTO EVAL_LET_LOOP + EVAL_LET_LOOP_DONE: + REM release previous env (if not root repl_env) because our + REM new env refers to it and we no longer need to track it + REM (since we are TCO recurring) + IF E4%<>RE% THEN AY%=E4%: GOSUB RELEASE + + A%=A2%: GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_DO: + A%=Z%(A%,1): REM rest + + REM TODO: TCO + + REM push EVAL_AST return label/address + ZL%=ZL%+1: ZZ%(ZL%)=2 + GOTO EVAL_AST + EVAL_AST_RETURN_2: + + ZL%=ZL%+1: ZZ%(ZL%)=R%: REM push eval'd list + A%=R%: GOSUB LAST: REM return the last element + AY%=ZZ%(ZL%): ZL%=ZL%-1: REM pop eval'd list + GOSUB RELEASE: REM release the eval'd list + GOTO EVAL_RETURN + + EVAL_IF: + GOSUB EVAL_GET_A1: REM set a1% + REM push A% + ZL%=ZL%+1: ZZ%(ZL%)=A% + A%=A1%: GOSUB EVAL + REM pop A% + A%=ZZ%(ZL%): ZL%=ZL%-1 + IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE + + EVAL_IF_TRUE: + AY%=R%: GOSUB RELEASE + GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL + A%=A2%: GOTO EVAL_TCO_RECUR: REM TCO loop + EVAL_IF_FALSE: + AY%=R%: GOSUB RELEASE + REM if no false case (A3%), return nil + IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0: GOTO EVAL_RETURN + GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL + A%=A3%: GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_FN: + GOSUB EVAL_GET_A2: REM set a1% and a2% + A%=A2%: P%=A1%: GOSUB MAL_FUNCTION + GOTO EVAL_RETURN + + EVAL_INVOKE: + REM push EVAL_AST return label/address + ZL%=ZL%+1: ZZ%(ZL%)=3 + GOTO EVAL_AST + EVAL_AST_RETURN_3: + + REM if error, return f/args for release by caller + IF ER%<>0 THEN GOTO EVAL_RETURN + + REM push f/args for release after call + ZL%=ZL%+1: ZZ%(ZL%)=R% + + F%=R%+1 + + AR%=Z%(R%,1): REM rest + R%=F%: GOSUB DEREF_R: F%=R% + + IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + + REM if error, pop and return f/args for release by caller + R%=ZZ%(ZL%): ZL%=ZL%-1 + ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + + EVAL_DO_FUNCTION: + GOSUB DO_FUNCTION + + REM pop and release f/args + AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + GOTO EVAL_RETURN + + EVAL_DO_MAL_FUNCTION: + E4%=E%: REM save the current environment for release + + REM create new environ using env stored with function + EO%=Z%(F%+1,1): BI%=Z%(F%+1,0): EX%=AR%: GOSUB ENV_NEW_BINDS + + REM release previous env if it is not the top one on the + REM stack (ZZ%(ZL%-2)) because our new env refers to it and + REM we no longer need to track it (since we are TCO recurring) + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%: GOSUB RELEASE + + REM claim the AST before releasing the list containing it + A%=Z%(F%,1): Z%(A%,0)=Z%(A%,0)+16 + REM add AST to pending release queue to free later + ZM%=ZM%+1: ZR%(ZM%)=A% + + REM pop and release f/args + AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + + REM A% set above + E%=R%: GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_RETURN: + REM release environment if not the top one on the stack + IF E%<>ZZ%(ZL%-1) THEN AY%=E%: GOSUB RELEASE + + REM release everything we couldn't release earlier + GOSUB RELEASE_PEND + + REM AZ%=R%: PR%=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + + REM trigger GC + TA%=FRE(0) + + REM pop A% and E% off the stack + E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + + LV%=LV%-1: REM track basic return stack level + + RETURN + +REM PRINT(A%) -> R$ +MAL_PRINT: + AZ%=A%: PR%=1: GOSUB PR_STR + RETURN + +REM RE(A$) -> R% +REM Assume RE% has repl_env +REM caller must release result +RE: + R1%=0 + GOSUB MAL_READ + R1%=R% + IF ER%<>0 THEN GOTO REP_DONE + + A%=R%: E%=RE%: GOSUB EVAL + + REP_DONE: + REM Release memory from MAL_READ + IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + RETURN: REM caller must release result of EVAL + +REM REP(A$) -> R$ +REM Assume RE% has repl_env +REP: + R1%=0: R2%=0 + GOSUB MAL_READ + R1%=R% + IF ER%<>0 THEN GOTO REP_DONE + + A%=R%: E%=RE%: GOSUB EVAL + R2%=R% + IF ER%<>0 THEN GOTO REP_DONE + + A%=R%: GOSUB MAL_PRINT + RT$=R$ + + REP_DONE: + REM Release memory from MAL_READ and EVAL + IF R2%<>0 THEN AY%=R2%: GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + R$=RT$ + RETURN + +REM MAIN program +MAIN: + GOSUB INIT_MEMORY + + LV%=0 + + REM create repl_env + EO%=-1: GOSUB ENV_NEW + RE%=R% + + REM core.EXT: defined in Basic + E%=RE%: GOSUB INIT_CORE_NS: REM set core functions in repl_env + + ZT%=ZI%: REM top of memory after base repl_env + + REM core.mal: defined using the language itself + A$="(def! not (fn* (a) (if a false true)))" + GOSUB RE: AY%=R%: GOSUB RELEASE + + A$="(def! load-file (fn* (f) (eval (read-string (str " + A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) " + A$=A$+CHR$(34)+")"+CHR$(34)+")))))" + GOSUB RE: AY%=R%: GOSUB RELEASE + + REM load the args file + A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" + GOSUB RE: AY%=R%: GOSUB RELEASE + + REM set the argument list + A$="(def! *ARGV* (rest -*ARGS*-))" + GOSUB RE: AY%=R%: GOSUB RELEASE + + REM get the first argument + A$="(first -*ARGS*-)" + GOSUB RE + + REM if there is an argument, then run it as a program + IF R%<>0 THEN AY%=R%: GOSUB RELEASE: GOTO RUN_PROG + REM no arguments, start REPL loop + IF R%=0 THEN GOTO REPL_LOOP + + RUN_PROG: + REM run a single mal program and exit + A$="(load-file (first -*ARGS*-))" + GOSUB REP + IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO QUIT + IF ER%=0 THEN PRINT R$: GOTO QUIT + + REPL_LOOP: + A$="user> " + GOSUB READLINE: REM /* call input parser */ + IF EOF=1 THEN GOTO QUIT + + A$=R$: GOSUB REP: REM /* call REP */ + + IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP + PRINT R$ + GOTO REPL_LOOP + + QUIT: + REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + GOSUB PR_MEMORY_SUMMARY + END + + PRINT_ERROR: + PRINT "Error: " + ER$ + ER%=0 + ER$="" + RETURN + diff --git a/basic/types.in.bas b/basic/types.in.bas index 56cc29536c..5bfb44b249 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -23,11 +23,11 @@ REM next free ptr 15 -> Z% index / or 0 INIT_MEMORY: T%=FRE(0) - S1%=3072: REM Z% (boxed memory) size (X2) + S1%=2048+512: REM Z% (boxed memory) size (X2) REM S1%=4096: REM Z% (boxed memory) size (X2) S2%=256: REM ZS% (string memory) size S3%=256: REM ZZ% (call stack) size - S4%=128: REM ZR% (release stack) size + S4%=64: REM ZR% (release stack) size REM global error state ER%=0 @@ -150,6 +150,7 @@ RELEASE: IF (U6%<=5) OR (U6%=9) THEN GOTO RELEASE_SIMPLE IF (U6%>=6) AND (U6%<=8) THEN GOTO RELEASE_SEQ IF U6%=10 THEN GOTO RELEASE_MAL_FUNCTION + IF U6%=11 THEN GOTO RELEASE_ATOM IF U6%=13 THEN GOTO RELEASE_ENV IF U6%=14 THEN GOTO RELEASE_REFERENCE IF U6%=15 THEN ER%=1: ER$="RELEASE of already freed: "+STR$(AY%): RETURN @@ -169,6 +170,11 @@ RELEASE: REM add value and next element to stack RC%=RC%+2: ZL%=ZL%+2: ZZ%(ZL%-1)=Z%(AY%+1,1): ZZ%(ZL%)=Z%(AY%,1) GOTO RELEASE_SIMPLE_2 + RELEASE_ATOM: + REM add contained/referred value + RC%=RC%+1: ZL%=ZL%+1: ZZ%(ZL%)=Z%(AY%,1) + REM free the atom itself + GOTO RELEASE_SIMPLE RELEASE_MAL_FUNCTION: REM add ast, params and environment to stack RC%=RC%+3: ZL%=ZL%+3 @@ -338,7 +344,17 @@ STRING: ZJ%=ZJ%+1 RETURN - +REM REPLACE(R$, S1$, S2$) -> R$ +REPLACE: + T3$=R$ + R$="" + I=1: J=LEN(T3$) + REPLACE_LOOP: + IF I>J THEN RETURN + CH$=MID$(T3$,I,LEN(S1$)) + IF CH$=S1$ THEN R$=R$+S2$: I=I+LEN(S1$) + IF CH$<>S1$ THEN R$=R$+MID$(T3$,I,1): I=I+1 + GOTO REPLACE_LOOP REM list functions @@ -378,6 +394,19 @@ LAST: Z%(R%,0)=Z%(R%,0)+16 RETURN +REM CONS(A%,B%) -> R% +CONS: + SZ%=2: GOSUB ALLOC + Z%(R%,0)=6+16 + Z%(R%,1)=B% + Z%(R%+1,0)=14 + Z%(R%+1,1)=A% + REM inc ref cnt of item we are including + Z%(A%,0)=Z%(A%,0)+16 + REM inc ref cnt of list we are prepending + Z%(B%,0)=Z%(B%,0)+16 + RETURN + REM hashmap functions REM HASHMAP() -> R% From 9f558de9abcc5b49dc6fdb0f3884b59ab977885a Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 20 Sep 2016 17:54:30 +0530 Subject: [PATCH 0149/2308] Compile guile files before running them --- guile/run | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guile/run b/guile/run index 7f1f2ba330..26eb986017 100755 --- a/guile/run +++ b/guile/run @@ -1,2 +1,3 @@ #!/bin/bash -exec guile --no-auto-compile -L $(dirname $0) $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" +# XDG_CACHE_HOME is where guile stores the compiled files +XDG_CACHE_HOME=.cache/ exec guile -L $(dirname $0) $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" From bf8d1f7d6c2fd87bedda694ef16b549093b6e994 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 20 Sep 2016 21:11:46 -0500 Subject: [PATCH 0150/2308] Basic: reduce memory usage by 614 bytes. - combine some lines - remove some unnecessary spaces and parens - add string allocations to single routine in types Also: - remove blank lines in qb2cbm.sh output (does not save memory) --- basic/core.in.bas | 30 ++----- basic/env.in.bas | 10 +-- basic/printer.in.bas | 18 ++-- basic/qb2cbm.sh | 8 +- basic/reader.in.bas | 105 ++++++++++------------ basic/readline.in.bas | 18 ++-- basic/step0_repl.in.bas | 2 +- basic/step1_read_print.in.bas | 2 +- basic/step2_eval.in.bas | 10 +-- basic/step3_env.in.bas | 16 ++-- basic/step4_if_fn_do.in.bas | 10 +-- basic/step5_tco.in.bas | 10 +-- basic/step6_file.in.bas | 10 +-- basic/types.in.bas | 165 ++++++++++++++++++++-------------- 14 files changed, 211 insertions(+), 203 deletions(-) diff --git a/basic/core.in.bas b/basic/core.in.bas index dcc8b9ea83..58ef902d49 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -45,7 +45,7 @@ DO_FUNCTION: IF FF%=58 THEN DO_PR_MEMORY IF FF%=59 THEN DO_PR_MEMORY_SUMMARY IF FF%=60 THEN DO_EVAL - ER%=1: ER$="unknown function" + STR$(FF%): RETURN + ER%=1: ER$="unknown function"+STR$(FF%): RETURN DO_EQUAL_Q: A%=AA%: B%=AB%: GOSUB EQUAL_Q @@ -54,19 +54,11 @@ DO_FUNCTION: DO_PR_STR: AZ%=AR%: PR%=1: SE$=" ": GOSUB PR_STR_SEQ - AS$=R$: GOSUB STRING - R4%=R% - SZ%=1: GOSUB ALLOC - Z%(R%,0) = 4+16 - Z%(R%,1) = R4% + AS$=R$: T%=4: GOSUB STRING RETURN DO_STR: AZ%=AR%: PR%=0: SE$="": GOSUB PR_STR_SEQ - AS$=R$: GOSUB STRING - R4%=R% - SZ%=1: GOSUB ALLOC - Z%(R%,0) = 4+16 - Z%(R%,1) = R4% + AS$=R$: T%=4: GOSUB STRING RETURN DO_PRN: AZ%=AR%: PR%=1: SE$=" ": GOSUB PR_STR_SEQ @@ -97,11 +89,7 @@ DO_FUNCTION: GOTO DO_SLURP_LOOP DO_SLURP_DONE: CLOSE 1 - AS$=R$: GOSUB STRING - R4%=R% - SZ%=1: GOSUB ALLOC - Z%(R%,0) = 4+16 - Z%(R%,1) = R4% + AS$=R$: T%=4: GOSUB STRING RETURN DO_LT: @@ -172,19 +160,19 @@ DO_FUNCTION: A%=AA%: GOSUB COUNT R4%=R% SZ%=1: GOSUB ALLOC - Z%(R%,0) = 2+16 - Z%(R%,1) = R4% + Z%(R%,0)=2+16 + Z%(R%,1)=R4% RETURN DO_ATOM: SZ%=1: GOSUB ALLOC Z%(AA%,0)=Z%(AA%,0)+16: REM inc ref cnt of contained value - Z%(R%,0) = 11+16 - Z%(R%,1) = AA% + Z%(R%,0)=12+16 + Z%(R%,1)=AA% RETURN DO_ATOM_Q: R%=1 - IF (Z%(AA%,0)AND15)=11 THEN R%=2 + IF (Z%(AA%,0)AND15)=12 THEN R%=2 RETURN DO_DEREF: R%=Z%(AA%,1): GOSUB DEREF_R diff --git a/basic/env.in.bas b/basic/env.in.bas index 0458441f19..45a6415763 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -7,10 +7,10 @@ ENV_NEW: REM set the outer and data pointer SZ%=2: GOSUB ALLOC - Z%(R%,0) = 13+16 - Z%(R%,1) = ET% - Z%(R%+1,0) = 13 - Z%(R%+1,1) = EO% + Z%(R%,0)=13+16 + Z%(R%,1)=ET% + Z%(R%+1,0)=13 + Z%(R%+1,1)=EO% IF EO%<>-1 THEN Z%(EO%,0)=Z%(EO%,0)+16 RETURN @@ -86,7 +86,7 @@ ENV_FIND: REM ENV_GET(E%, K%) -> R% ENV_GET: GOSUB ENV_FIND - IF R%=-1 THEN R%=0: ER%=1: ER$="'" + ZS$(Z%(K%,1)) + "' not found": RETURN + IF R%=-1 THEN R%=0: ER%=1: ER$="'"+ZS$(Z%(K%,1))+"' not found": RETURN R%=T4%: GOSUB DEREF_R Z%(R%,0)=Z%(R%,0)+16 RETURN diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 96654cba0d..2be152ac0d 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -3,7 +3,7 @@ PR_STR: RR$="" PR_STR_RECUR: T%=Z%(AZ%,0)AND15 - REM PRINT "AZ%: " + STR$(AZ%) + ", T%: " + STR$(T%) + ", V%: " + STR$(Z%(AZ%,1)) + REM PRINT "AZ%: "+STR$(AZ%)+", T%: "+STR$(T%)+", V%: "+STR$(Z%(AZ%,1)) IF T%=14 THEN AZ%=Z%(AZ%,1): GOTO PR_STR_RECUR IF T%=0 THEN R$="nil": RETURN IF (T%=1) AND (Z%(AZ%,1)=0) THEN R$="false": RETURN @@ -17,7 +17,7 @@ PR_STR: IF T%=8 THEN PR_SEQ IF T%=9 THEN PR_FUNCTION IF T%=10 THEN PR_MAL_FUNCTION - IF T%=11 THEN PR_ATOM + IF T%=12 THEN PR_ATOM IF T%=13 THEN PR_ENV IF T%=15 THEN PR_FREE R$="#" @@ -56,13 +56,13 @@ PR_STR: AZ%=AZ%+1 GOSUB PR_STR_RECUR REM if we just rendered a non-sequence, then append it - IF (T% < 6) OR (T% > 8) THEN RR$=RR$+R$ + IF (T%<6) OR (T%>8) THEN RR$=RR$+R$ REM restore current seq type T%=ZZ%(ZL%-1) REM Go to next list element AZ%=Z%(ZZ%(ZL%),1) - ZZ%(ZL%) = AZ% - IF Z%(AZ%,1) <> 0 THEN RR$=RR$+" " + ZZ%(ZL%)=AZ% + IF Z%(AZ%,1)<>0 THEN RR$=RR$+" " GOTO PR_SEQ_LOOP PR_SEQ_DONE: REM get type @@ -76,18 +76,18 @@ PR_STR: RETURN PR_FUNCTION: T1%=Z%(AZ%,1) - R$="#" + R$="#" RETURN PR_MAL_FUNCTION: T1%=AZ% AZ%=Z%(T1%+1,0): GOSUB PR_STR_RECUR - T7$="(fn* " + R$ + T7$="(fn* "+R$ AZ%=Z%(T1%,1): GOSUB PR_STR_RECUR - R$=T7$ + " " + R$ + ")" + R$=T7$+" "+R$+")" RETURN PR_ATOM: AZ%=Z%(AZ%,1): GOSUB PR_STR_RECUR - R$="(atom " + R$ + ")" + R$="(atom "+R$+")" RETURN PR_ENV: R$="#" diff --git a/basic/qb2cbm.sh b/basic/qb2cbm.sh index e7ac6fbafa..b507853654 100755 --- a/basic/qb2cbm.sh +++ b/basic/qb2cbm.sh @@ -5,7 +5,7 @@ set -e DEBUG=${DEBUG:-} KEEP_REM=${KEEP_REM:-1} # 0 - drop all REMs -# 1 - keep LABEL and INCLUDE REMs +# 1 - keep LABEL and INCLUDE REMs (and blank lines) # 2 - keep LABEL, INCLUDE, and GOTO REMs # 3 - keep LABEL, INCLUDE, GOTO, and whole line REMs # 4 - keep all REMS (end of line REMs too) @@ -67,9 +67,13 @@ while read -r line; do [ "${DEBUG}" ] && echo >&2 "dropping REM comment: ${line}" continue elif [[ ${line} =~ ^\ *$ ]]; then + if [ "${KEEP_REM}" -ge 1 ]; then [ "${DEBUG}" ] && echo >&2 "found blank line at $lnum" data="${data}\n" - continue + else + [ "${DEBUG}" ] && echo >&2 "ignoring blank line at $lnum" + fi + continue elif [[ ${line} =~ ^[A-Za-z_][A-Za-z0-9_]*:$ ]]; then label=${line%:} [ "${DEBUG}" ] && echo >&2 "found label ${label} at $lnum" diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 4996c891ea..dd452ee149 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -1,23 +1,23 @@ REM READ_TOKEN(A$, IDX%) -> T$ READ_TOKEN: CUR%=IDX% - REM PRINT "READ_TOKEN: " + STR$(CUR%) + ", " + MID$(A$,CUR%,1) + REM PRINT "READ_TOKEN: "+STR$(CUR%)+", "+MID$(A$,CUR%,1) T$=MID$(A$,CUR%,1) - IF (T$="(") OR (T$=")") THEN RETURN - IF (T$="[") OR (T$="]") THEN RETURN - IF (T$="{") OR (T$="}") THEN RETURN + IF T$="(" OR T$=")" THEN RETURN + IF T$="[" OR T$="]" THEN RETURN + IF T$="{" OR T$="}" THEN RETURN S1=0: S2=0: REM S1: INSTRING?, S2: ESCAPED? - IF (T$=CHR$(34)) THEN S1=1 + IF T$=CHR$(34) THEN S1=1 CUR%=CUR%+1 READ_TOKEN_LOOP: - IF CUR% > LEN(A$) THEN RETURN + IF CUR%>LEN(A$) THEN RETURN CH$=MID$(A$,CUR%,1) IF S2 THEN GOTO READ_TOKEN_CONT IF S1 THEN GOTO READ_TOKEN_CONT - IF (CH$=" ") OR (CH$=",") THEN RETURN - IF (CH$="(") OR (CH$=")") THEN RETURN - IF (CH$="[") OR (CH$="]") THEN RETURN - IF (CH$="{") OR (CH$="}") THEN RETURN + IF CH$=" " OR CH$="," THEN RETURN + IF CH$="(" OR CH$=")" THEN RETURN + IF CH$="[" OR CH$="]" THEN RETURN + IF CH$="{" OR CH$="}" THEN RETURN READ_TOKEN_CONT: T$=T$+CH$ CUR%=CUR%+1 @@ -42,36 +42,36 @@ READ_FORM: IF ER% THEN RETURN GOSUB SKIP_SPACES GOSUB READ_TOKEN - REM PRINT "READ_FORM T$: [" + T$ + "]" - IF (T$="") THEN R%=0: GOTO READ_FORM_DONE - IF (T$="nil") THEN T%=0: GOTO READ_NIL_BOOL - IF (T$="false") THEN T%=1: GOTO READ_NIL_BOOL - IF (T$="true") THEN T%=2: GOTO READ_NIL_BOOL + REM PRINT "READ_FORM T$: ["+T$+"]" + IF T$="" THEN R%=0: GOTO READ_FORM_DONE + IF T$="nil" THEN T%=0: GOTO READ_NIL_BOOL + IF T$="false" THEN T%=1: GOTO READ_NIL_BOOL + IF T$="true" THEN T%=2: GOTO READ_NIL_BOOL CH$=MID$(T$,1,1) - REM PRINT "CH$: [" + CH$ + "](" + STR$(ASC(CH$)) + ")" - IF (CH$ >= "0") AND (CH$ <= "9") THEN READ_NUMBER - IF (CH$ = "-") THEN READ_SYMBOL_MAYBE - - IF (CH$ = CHR$(34)) THEN READ_STRING - IF (CH$ = "(") THEN T%=6: GOTO READ_SEQ - IF (CH$ = ")") THEN T%=6: GOTO READ_SEQ_END - IF (CH$ = "[") THEN T%=7: GOTO READ_SEQ - IF (CH$ = "]") THEN T%=7: GOTO READ_SEQ_END - IF (CH$ = "{") THEN T%=8: GOTO READ_SEQ - IF (CH$ = "}") THEN T%=8: GOTO READ_SEQ_END + REM PRINT "CH$: ["+CH$+"]("+STR$(ASC(CH$))+")" + IF CH$>="0" AND CH$ <= "9" THEN READ_NUMBER + IF CH$="-" THEN READ_SYMBOL_MAYBE + + IF CH$=CHR$(34) THEN READ_STRING + IF CH$="(" THEN T%=6: GOTO READ_SEQ + IF CH$=")" THEN T%=6: GOTO READ_SEQ_END + IF CH$="[" THEN T%=7: GOTO READ_SEQ + IF CH$="]" THEN T%=7: GOTO READ_SEQ_END + IF CH$="{" THEN T%=8: GOTO READ_SEQ + IF CH$="}" THEN T%=8: GOTO READ_SEQ_END GOTO READ_SYMBOL READ_NIL_BOOL: REM PRINT "READ_NIL_BOOL" SZ%=1: GOSUB ALLOC - Z%(R%,0) = 14+16 - Z%(R%,1) = T% + Z%(R%,0)=14+16 + Z%(R%,1)=T% GOTO READ_FORM_DONE READ_NUMBER: REM PRINT "READ_NUMBER" SZ%=1: GOSUB ALLOC - Z%(R%,0) = 2+16 - Z%(R%,1) = VAL(T$) + Z%(R%,0)=2+16 + Z%(R%,1)=VAL(T$) GOTO READ_FORM_DONE READ_STRING: REM PRINT "READ_STRING" @@ -82,23 +82,14 @@ READ_FORM: S1$=CHR$(92)+"n": S2$=CHR$(13): GOSUB REPLACE: REM unescape newlines S1$=CHR$(92)+CHR$(92): S2$=CHR$(92): GOSUB REPLACE: REM unescape backslashes REM intern string value - AS$=R$: GOSUB STRING - T7%=R% - SZ%=1: GOSUB ALLOC - Z%(R%,0) = 4+16 - Z%(R%,1) = T7% + AS$=R$: T%=4: GOSUB STRING GOTO READ_FORM_DONE READ_SYMBOL_MAYBE: CH$=MID$(T$,2,1) - IF (CH$ >= "0") AND (CH$ <= "9") THEN READ_NUMBER + IF CH$>="0" AND CH$<="9" THEN READ_NUMBER READ_SYMBOL: REM PRINT "READ_SYMBOL" - REM intern string value - AS$=T$: GOSUB STRING - T7%=R% - SZ%=1: GOSUB ALLOC - Z%(R%,0) = 5+16 - Z%(R%,1) = T7% + AS$=T$: T%=5: GOSUB STRING GOTO READ_FORM_DONE READ_SEQ: @@ -112,27 +103,27 @@ READ_FORM: IF SD%>1 THEN Z%(ZZ%(ZL%)+1,1)=R% REM set the type (with 1 ref cnt) and next pointer to current end - Z%(R%,0) = T%+16 - Z%(R%,1) = 0 - Z%(R%+1,0) = 14 - Z%(R%+1,1) = 0 + Z%(R%,0)=T%+16 + Z%(R%,1)=0 + Z%(R%+1,0)=14 + Z%(R%+1,1)=0 REM push start ptr on the stack ZL%=ZL%+1 - ZZ%(ZL%) = R% + ZZ%(ZL%)=R% REM push current sequence type ZL%=ZL%+1 - ZZ%(ZL%) = T% + ZZ%(ZL%)=T% REM push previous ptr on the stack ZL%=ZL%+1 - ZZ%(ZL%) = R% + ZZ%(ZL%)=R% IDX%=IDX%+LEN(T$) GOTO READ_FORM READ_SEQ_END: REM PRINT "READ_SEQ_END" - IF SD%=0 THEN ER$="unexpected '" + CH$ + "'": GOTO READ_FORM_ABORT + IF SD%=0 THEN ER$="unexpected '"+CH$+"'": GOTO READ_FORM_ABORT IF ZZ%(ZL%-1)<>T% THEN ER$="sequence mismatch": GOTO READ_FORM_ABORT SD%=SD%-1: REM decrease read sequence depth R%=ZZ%(ZL%-2): REM ptr to start of sequence to return @@ -157,21 +148,21 @@ READ_FORM: REM previous element T7%=ZZ%(ZL%) REM set previous list element to point to new element - Z%(T7%,1) = R% + Z%(T7%,1)=R% REM set the list value pointer Z%(T7%+1,1)=T8% REM set type to previous type, with ref count of 1 (from previous) - Z%(R%,0) = ZZ%(ZL%-1)+16 - Z%(R%,1) = 0: REM current end of sequence - Z%(R%+1,0) = 14 - Z%(R%+1,1) = 0 + Z%(R%,0)=ZZ%(ZL%-1)+16 + Z%(R%,1)=0: REM current end of sequence + Z%(R%+1,0)=14 + Z%(R%+1,1)=0 IF T7%=ZZ%(ZL%-2) THEN GOTO READ_FORM_SKIP_FIRST - Z%(T7%,1) = R% + Z%(T7%,1)=R% READ_FORM_SKIP_FIRST: REM update previous pointer to current element - ZZ%(ZL%) = R% + ZZ%(ZL%)=R% GOTO READ_FORM READ_FORM_ABORT: diff --git a/basic/readline.in.bas b/basic/readline.in.bas index 510bcb4c93..f53362961d 100644 --- a/basic/readline.in.bas +++ b/basic/readline.in.bas @@ -1,11 +1,9 @@ -EOF=0 - REM READLINE(A$) -> R$ READLINE: EOF=0 PROMPT$=A$ PRINT PROMPT$; - CH$="": LINE$="": CH=0 + CH$="": LI$="": CH=0 READCH: GET CH$: IF CH$="" THEN READCH CH=ASC(CH$) @@ -15,15 +13,15 @@ READLINE: IF (CH=127) OR (CH=20) THEN GOTO READCH IF (CH<32 OR CH>127) AND CH<>13 THEN READCH PRINT CH$; - IF LEN(LINE$)<255 AND CH$<>CHR$(13) THEN LINE$=LINE$+CH$ - IF LEN(LINE$)<255 AND CH$<>CHR$(13) THEN GOTO READCH + IF LEN(LI$)<255 AND CH$<>CHR$(13) THEN LI$=LI$+CH$ + IF LEN(LI$)<255 AND CH$<>CHR$(13) THEN GOTO READCH RL_DONE: - R$=LINE$ + R$=LI$ RETURN - REM Assumes LINE$ has input buffer + REM Assumes LI$ has input buffer RL_BACKSPACE: - IF LEN(LINE$)=0 THEN RETURN - LINE$=LEFT$(LINE$, LEN(LINE$)-1) - PRINT CHR$(157) + " " + CHR$(157); + IF LEN(LI$)=0 THEN RETURN + LI$=LEFT$(LI$, LEN(LI$)-1) + PRINT CHR$(157)+" "+CHR$(157); RETURN diff --git a/basic/step0_repl.in.bas b/basic/step0_repl.in.bas index 7398f149e7..032f0cb5cf 100755 --- a/basic/step0_repl.in.bas +++ b/basic/step0_repl.in.bas @@ -35,6 +35,6 @@ MAIN: GOTO MAIN_LOOP MAIN_DONE: - PRINT "Free: " + STR$(FRE(0)) + PRINT "Free: "+STR$(FRE(0)) END diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index d9176edaa2..9df83f9ed6 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -60,7 +60,7 @@ MAIN: END PRINT_ERROR: - PRINT "Error: " + ER$ + PRINT "Error: "+ER$ ER%=0 ER$="" RETURN diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index 1b165aeac8..a1d596def5 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -20,8 +20,8 @@ EVAL_AST: IF ER%<>0 THEN GOTO EVAL_AST_RETURN REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL_AST: " + R$ + "(" + STR$(A%) + ")" - REM PRINT "EVAL_AST level: " + STR$(LV%) + REM PRINT "EVAL_AST: "+R$+"("+STR$(A%)+")" + REM PRINT "EVAL_AST level: "+STR$(LV%) GOSUB DEREF_A @@ -128,7 +128,7 @@ EVAL: ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + "), LV%:"+STR$(LV%) + REM PRINT "EVAL: "+R$+"("+STR$(A%)+"), LV%:"+STR$(LV%) GOSUB DEREF_A @@ -193,7 +193,7 @@ DO_FUNCTION: IF FF%=2 THEN DO_SUB IF FF%=3 THEN DO_MULT IF FF%=4 THEN DO_DIV - ER%=1: ER$="unknown function" + STR$(FF%): RETURN + ER%=1: ER$="unknown function"+STR$(FF%): RETURN DO_ADD: Z%(R%,0)=2+16 @@ -291,7 +291,7 @@ MAIN: END PRINT_ERROR: - PRINT "Error: " + ER$ + PRINT "Error: "+ER$ ER%=0 ER$="" RETURN diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index d87542642b..b5f605cb33 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -21,8 +21,8 @@ EVAL_AST: IF ER%<>0 THEN GOTO EVAL_AST_RETURN REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL_AST: " + R$ + "(" + STR$(A%) + ")" - REM PRINT "EVAL_AST level: " + STR$(LV%) + REM PRINT "EVAL_AST: "+R$+"("+STR$(A%)+")" + REM PRINT "EVAL_AST level: "+STR$(LV%) GOSUB DEREF_A @@ -129,7 +129,7 @@ EVAL: ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + "), LV%:"+STR$(LV%) + REM PRINT "EVAL: "+R$+"("+STR$(A%)+"), LV%:"+STR$(LV%) GOSUB DEREF_A @@ -155,13 +155,13 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3% = Z%(Z%(Z%(A%,1),1),1)+1 + A3%=Z%(Z%(Z%(A%,1),1),1)+1 R%=A3%: GOSUB DEREF_R: A3%=R% EVAL_GET_A2: - A2% = Z%(Z%(A%,1),1)+1 + A2%=Z%(Z%(A%,1),1)+1 R%=A2%: GOSUB DEREF_R: A2%=R% EVAL_GET_A1: - A1% = Z%(A%,1)+1 + A1%=Z%(A%,1)+1 R%=A1%: GOSUB DEREF_R: A1%=R% RETURN @@ -255,7 +255,7 @@ DO_FUNCTION: IF FF%=2 THEN DO_SUB IF FF%=3 THEN DO_MULT IF FF%=4 THEN DO_DIV - ER%=1: ER$="unknown function" + STR$(FF%): RETURN + ER%=1: ER$="unknown function"+STR$(FF%): RETURN DO_ADD: Z%(R%,0)=2+16 @@ -350,7 +350,7 @@ MAIN: END PRINT_ERROR: - PRINT "Error: " + ER$ + PRINT "Error: "+ER$ ER%=0 ER$="" RETURN diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index 6c53370749..5950ea4c2e 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -130,7 +130,7 @@ EVAL: EVAL_TCO_RECUR: REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + "), LV%:"+STR$(LV%) + REM PRINT "EVAL: "+R$+"("+STR$(A%)+"), LV%:"+STR$(LV%) GOSUB DEREF_A @@ -163,13 +163,13 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3% = Z%(Z%(Z%(A%,1),1),1)+1 + A3%=Z%(Z%(Z%(A%,1),1),1)+1 R%=A3%: GOSUB DEREF_R: A3%=R% EVAL_GET_A2: - A2% = Z%(Z%(A%,1),1)+1 + A2%=Z%(Z%(A%,1),1)+1 R%=A2%: GOSUB DEREF_R: A2%=R% EVAL_GET_A1: - A1% = Z%(A%,1)+1 + A1%=Z%(A%,1)+1 R%=A1%: GOSUB DEREF_R: A1%=R% RETURN @@ -402,7 +402,7 @@ MAIN: END PRINT_ERROR: - PRINT "Error: " + ER$ + PRINT "Error: "+ER$ ER%=0 ER$="" RETURN diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index aa29bc77ea..63d3a794e5 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -130,7 +130,7 @@ EVAL: EVAL_TCO_RECUR: REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + "), LV%:"+STR$(LV%) + REM PRINT "EVAL: "+R$+"("+STR$(A%)+"), LV%:"+STR$(LV%) GOSUB DEREF_A @@ -163,13 +163,13 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3% = Z%(Z%(Z%(A%,1),1),1)+1 + A3%=Z%(Z%(Z%(A%,1),1),1)+1 R%=A3%: GOSUB DEREF_R: A3%=R% EVAL_GET_A2: - A2% = Z%(Z%(A%,1),1)+1 + A2%=Z%(Z%(A%,1),1)+1 R%=A2%: GOSUB DEREF_R: A2%=R% EVAL_GET_A1: - A1% = Z%(A%,1)+1 + A1%=Z%(A%,1)+1 R%=A1%: GOSUB DEREF_R: A1%=R% RETURN @@ -412,7 +412,7 @@ MAIN: END PRINT_ERROR: - PRINT "Error: " + ER$ + PRINT "Error: "+ER$ ER%=0 ER$="" RETURN diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index 5cc8cd1205..3eeaf07d26 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -132,7 +132,7 @@ EVAL: EVAL_TCO_RECUR: REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: " + R$ + "(" + STR$(A%) + "), LV%:"+STR$(LV%) + REM PRINT "EVAL: "+R$+"("+STR$(A%)+"), LV%:"+STR$(LV%) GOSUB DEREF_A @@ -165,13 +165,13 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3% = Z%(Z%(Z%(A%,1),1),1)+1 + A3%=Z%(Z%(Z%(A%,1),1),1)+1 R%=A3%: GOSUB DEREF_R: A3%=R% EVAL_GET_A2: - A2% = Z%(Z%(A%,1),1)+1 + A2%=Z%(Z%(A%,1),1)+1 R%=A2%: GOSUB DEREF_R: A2%=R% EVAL_GET_A1: - A1% = Z%(A%,1)+1 + A1%=Z%(A%,1)+1 R%=A1%: GOSUB DEREF_R: A1%=R% RETURN @@ -443,7 +443,7 @@ MAIN: END PRINT_ERROR: - PRINT "Error: " + ER$ + PRINT "Error: "+ER$ ER%=0 ER$="" RETURN diff --git a/basic/types.in.bas b/basic/types.in.bas index 5bfb44b249..686744527c 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -14,7 +14,7 @@ REM followed by key or value (alternating) REM function 9 -> function index REM mal function 10 -> body AST Z% index REM followed by param and env Z% index -REM atom 11 -> Z% index +REM atom 12 -> Z% index REM environment 13 -> data/hashmap Z% index REM followed by 13 and outer Z% index (-1 for none) REM reference/ptr 14 -> Z% index / or 0 @@ -23,26 +23,21 @@ REM next free ptr 15 -> Z% index / or 0 INIT_MEMORY: T%=FRE(0) - S1%=2048+512: REM Z% (boxed memory) size (X2) - REM S1%=4096: REM Z% (boxed memory) size (X2) - S2%=256: REM ZS% (string memory) size - S3%=256: REM ZZ% (call stack) size - S4%=64: REM ZR% (release stack) size + S1%=2048+512: REM Z% (boxed memory) size (4 bytes each) + S2%=256: REM ZS% (string memory) size (3 bytes each) + S3%=256: REM ZZ% (call stack) size (2 bytes each) + S4%=64: REM ZR% (release stack) size (2 bytes each) REM global error state - ER%=0 - ER$="" + ER%=0: ER$="" REM boxed element memory DIM Z%(S1%,1): REM TYPE ARRAY REM Predefine nil, false, true - Z%(0,0) = 0 - Z%(0,1) = 0 - Z%(1,0) = 1 - Z%(1,1) = 0 - Z%(2,0) = 1 - Z%(2,1) = 1 + Z%(0,0)=0: Z%(0,1)=0 + Z%(1,0)=1: Z%(1,1)=0 + Z%(2,0)=1: Z%(2,1)=1 REM start of unused memory ZI%=3 @@ -51,19 +46,16 @@ INIT_MEMORY: ZK%=3 REM string memory storage - ZJ%=0 - DIM ZS$(S2%) + ZJ%=0: DIM ZS$(S2%) REM call/logic stack - ZL%=-1 - DIM ZZ%(S3%): REM stack of Z% indexes + ZL%=-1: DIM ZZ%(S3%): REM stack of Z% indexes REM pending release stack - ZM%=-1 - DIM ZR%(S4%): REM stack of Z% indexes + ZM%=-1: DIM ZR%(S4%): REM stack of Z% indexes - REM PRINT "Lisp data memory: " + STR$(T%-FRE(0)) - REM PRINT "Interpreter working memory: " + STR$(FRE(0)) + REM PRINT "Lisp data memory: "+STR$(T%-FRE(0)) + REM PRINT "Interpreter working memory: "+STR$(FRE(0)) RETURN REM memory functions @@ -71,8 +63,7 @@ REM memory functions REM ALLOC(SZ%) -> R% ALLOC: REM PRINT "ALLOC SZ%: "+STR$(SZ%)+", ZK%: "+STR$(ZK%) - U3%=ZK% - U4%=ZK% + U3%=ZK%: U4%=ZK% ALLOC_LOOP: IF U4%=ZI% THEN GOTO ALLOC_UNUSED REM TODO sanity check that type is 15 @@ -101,13 +92,10 @@ ALLOC: REM FREE(AY%, SZ%) -> nil FREE: REM assumes reference count cleanup already (see RELEASE) - Z%(AY%,0) = (SZ%*16)+15: REM set type(15) and size - Z%(AY%,1) = ZK% - IF SZ%>=2 THEN Z%(AY%+1,0)=0 - IF SZ%>=2 THEN Z%(AY%+1,1)=0 - IF SZ%>=3 THEN Z%(AY%+2,0)=0 - IF SZ%>=3 THEN Z%(AY%+2,1)=0 - ZK%=AY% + Z%(AY%,0)=(SZ%*16)+15: REM set type(15) and size + Z%(AY%,1)=ZK%: ZK%=AY% + IF SZ%>=2 THEN Z%(AY%+1,0)=0: Z%(AY%+1,1)=0 + IF SZ%>=3 THEN Z%(AY%+2,0)=0: Z%(AY%+2,1)=0 RETURN @@ -137,8 +125,8 @@ RELEASE: REM PRINT "RELEASE AY%:"+STR$(AY%)+"["+R$+"] (byte0:"+STR$(Z%(AY%,0))+")" REM sanity check not already freed - IF (U6%)=15 THEN ER%=1: ER$="Free of free memory: " + STR$(AY%): RETURN - IF Z%(AY%,0)<15 THEN ER%=1: ER$="Free of freed object: " + STR$(AY%): RETURN + IF (U6%)=15 THEN ER%=1: ER$="Free of free memory: "+STR$(AY%): RETURN + IF Z%(AY%,0)<15 THEN ER%=1: ER$="Free of freed object: "+STR$(AY%): RETURN REM decrease reference count by one Z%(AY%,0)=Z%(AY%,0)-16 @@ -150,11 +138,11 @@ RELEASE: IF (U6%<=5) OR (U6%=9) THEN GOTO RELEASE_SIMPLE IF (U6%>=6) AND (U6%<=8) THEN GOTO RELEASE_SEQ IF U6%=10 THEN GOTO RELEASE_MAL_FUNCTION - IF U6%=11 THEN GOTO RELEASE_ATOM + IF U6%=12 THEN GOTO RELEASE_ATOM IF U6%=13 THEN GOTO RELEASE_ENV IF U6%=14 THEN GOTO RELEASE_REFERENCE IF U6%=15 THEN ER%=1: ER$="RELEASE of already freed: "+STR$(AY%): RETURN - ER%=1: ER$="RELEASE not defined for type " + STR$(U6%): RETURN + ER%=1: ER$="RELEASE not defined for type "+STR$(U6%): RETURN RELEASE_SIMPLE: REM simple type (no recursing), just call FREE on it @@ -205,7 +193,7 @@ REM RELEASE_PEND() -> nil RELEASE_PEND: REM REM IF ER%<>0 THEN RETURN IF ZM%<0 THEN RETURN - REM PRINT "here2 RELEASE_PEND releasing:"+STR$(ZR%(ZM%)) + REM PRINT "RELEASE_PEND releasing:"+STR$(ZR%(ZM%)) AY%=ZR%(ZM%): GOSUB RELEASE ZM%=ZM%-1 GOTO RELEASE_PEND @@ -240,13 +228,13 @@ CHECK_FREE_LIST: PR_MEMORY_SUMMARY: GOSUB CHECK_FREE_LIST: REM get count in P2% PRINT - PRINT "Free memory (FRE) : " + STR$(FRE(0)) - PRINT "Value memory (Z%) : " + STR$(ZI%-1) + " /" + STR$(S1%) + PRINT "Free memory (FRE) : "+STR$(FRE(0)) + PRINT "Value memory (Z%) : "+STR$(ZI%-1)+" /"+STR$(S1%) PRINT " "; PRINT " used:"+STR$(ZI%-1-P2%)+", freed:"+STR$(P2%); PRINT ", post repl_env:"+STR$(ZT%) - PRINT "String values (ZS$) : " + STR$(ZJ%) + " /" + STR$(S2%) - PRINT "Call stack size (ZZ%) : " + STR$(ZL%+1) + " /" + STR$(S3%) + PRINT "String values (ZS$) : "+STR$(ZJ%)+" /"+STR$(S2%) + PRINT "Call stack size (ZZ%) : "+STR$(ZL%+1)+" /"+STR$(S3%) RETURN REM PR_MEMORY(P1%, P2%) -> nil @@ -259,10 +247,10 @@ PR_MEMORY: I=P1% PR_MEMORY_VALUE_LOOP: IF I>P2% THEN GOTO PR_MEMORY_AFTER_VALUES - PRINT " " + STR$(I); + PRINT " "+STR$(I); IF (Z%(I,0)AND15)=15 THEN GOTO PR_MEMORY_FREE - PRINT ": ref cnt: " + STR$((Z%(I,0)AND-16)/16); - PRINT ", type: " + STR$(Z%(I,0)AND15) + ", value: " + STR$(Z%(I,1)) + PRINT ": ref cnt: "+STR$((Z%(I,0)AND-16)/16); + PRINT ", type: "+STR$(Z%(I,0)AND15)+", value: "+STR$(Z%(I,1)) I=I+1 IF (Z%(I-1,0)AND15)<>10 THEN GOTO PR_MEMORY_VALUE_LOOP PRINT " "+STR$(I)+": "; @@ -277,13 +265,13 @@ PR_MEMORY: I=I+1 GOTO PR_MEMORY_VALUE_LOOP PR_MEMORY_AFTER_VALUES: - PRINT "ZS% String Memory (ZJ%: " + STR$(ZJ%) + "):" + PRINT "ZS% String Memory (ZJ%: "+STR$(ZJ%)+"):" IF ZJ%<=0 THEN PRINT " ---": GOTO PR_MEMORY_SKIP_STRINGS FOR I=0 TO ZJ%-1 - PRINT " " + STR$(I) + ": '" + ZS$(I) + "'" + PRINT " "+STR$(I)+": '"+ZS$(I)+"'" NEXT I PR_MEMORY_SKIP_STRINGS: - PRINT "ZZ% Stack Memory (ZL%: " + STR$(ZL%) + "):" + PRINT "ZZ% Stack Memory (ZL%: "+STR$(ZL%)+"):" IF ZL%<0 THEN PRINT " ---": GOTO PR_MEMORY_SKIP_STACK FOR I=0 TO ZL% PRINT " "+STR$(I)+": "+STR$(ZZ%(I)) @@ -330,7 +318,7 @@ REM string functions REM STRING_(AS$) -> R% REM intern string (returns string index, not Z% index) -STRING: +STRING_: IF ZJ%=0 THEN GOTO STRING_NOT_FOUND REM search for matching string in ZS$ @@ -339,11 +327,21 @@ STRING: NEXT I STRING_NOT_FOUND: - ZS$(ZJ%) = AS$ + ZS$(ZJ%)=AS$ R%=ZJ% ZJ%=ZJ%+1 RETURN +REM STRING(AS$, T%) -> R% +REM intern string and allocate reference (return Z% index) +STRING: + GOSUB STRING_ + T7%=R% + SZ%=1: GOSUB ALLOC + Z%(R%,0)=T%+16 + Z%(R%,1)=T7% + RETURN + REM REPLACE(R$, S1$, S2$) -> R$ REPLACE: T3$=R$ @@ -407,15 +405,44 @@ CONS: Z%(B%,0)=Z%(B%,0)+16 RETURN +REM LIST2(B2%,B1%) -> R% +LIST2: + REM terminator + SZ%=2: GOSUB ALLOC: TB%=R% + Z%(R%,0)=6+16: Z%(R%,1)=0: Z%(R%+1,0)=0: Z%(R%+1,1)=0 + + REM second element is B1% + SZ%=2: GOSUB ALLOC: TC%=R% + Z%(R%,0)=6+16: Z%(R%,1)=TB%: Z%(R%+1,0)=14: Z%(R%+1,1)=B1% + Z%(B1%,0)=Z%(B1%,0)+16 + + REM first element is B2% + SZ%=2: GOSUB ALLOC + Z%(R%,0)=6+16: Z%(R%,1)=TC%: Z%(R%+1,0)=14: Z%(R%+1,1)=B2% + Z%(B2%,0)=Z%(B2%,0)+16 + + RETURN + +REM LIST3(B3%,B2%,B1%) -> R% +LIST3: + GOSUB LIST2: TC%=R% + + REM first element is B3% + SZ%=2: GOSUB ALLOC + Z%(R%,0)=6+16: Z%(R%,1)=TC%: Z%(R%+1,0)=14: Z%(R%+1,1)=B3% + Z%(B3%,0)=Z%(B3%,0)+16 + + RETURN + REM hashmap functions REM HASHMAP() -> R% HASHMAP: SZ%=2: GOSUB ALLOC - Z%(R%,0) = 8+16 - Z%(R%,1) = 0 - Z%(R%+1,0) = 14 - Z%(R%+1,1) = 0 + Z%(R%,0)=8+16 + Z%(R%,1)=0 + Z%(R%+1,0)=14 + Z%(R%+1,1)=0 RETURN REM ASSOC1(HM%, K%, V%) -> R% @@ -429,15 +456,15 @@ ASSOC1: Z%(V%,0)=Z%(V%,0)+16 SZ%=4: GOSUB ALLOC REM key ptr - Z%(R%,0) = 8+16 - Z%(R%,1) = R%+2: REM point to next element (value) - Z%(R%+1,0) = 14 - Z%(R%+1,1) = K% + Z%(R%,0)=8+16 + Z%(R%,1)=R%+2: REM point to next element (value) + Z%(R%+1,0)=14 + Z%(R%+1,1)=K% REM value ptr - Z%(R%+2,0) = 8+16 - Z%(R%+2,1) = HM%: REM hashmap to assoc onto - Z%(R%+3,0) = 14 - Z%(R%+3,1) = V% + Z%(R%+2,0)=8+16 + Z%(R%+2,1)=HM%: REM hashmap to assoc onto + Z%(R%+3,0)=14 + Z%(R%+3,1)=V% RETURN REM ASSOC1(HM%, K$, V%) -> R% @@ -445,9 +472,9 @@ ASSOC1_S: REM add the key string, then call ASSOC1 SZ%=1: GOSUB ALLOC K%=R% - ZS$(ZJ%) = K$ - Z%(R%,0) = 4: REM key ref cnt will be inc'd by ASSOC1 - Z%(R%,1) = ZJ% + ZS$(ZJ%)=K$ + Z%(R%,0)=4: REM key ref cnt will be inc'd by ASSOC1 + Z%(R%,1)=ZJ% ZJ%=ZJ%+1 GOSUB ASSOC1 RETURN @@ -482,8 +509,8 @@ HASHMAP_CONTAINS: REM NATIVE_FUNCTION(A%) -> R% NATIVE_FUNCTION: SZ%=1: GOSUB ALLOC - Z%(R%,0) = 9+16 - Z%(R%,1) = A% + Z%(R%,0)=9+16 + Z%(R%,1)=A% RETURN REM NATIVE_FUNCTION(A%, P%, E%) -> R% @@ -493,8 +520,8 @@ MAL_FUNCTION: Z%(P%,0)=Z%(P%,0)+16 Z%(E%,0)=Z%(E%,0)+16 - Z%(R%,0) = 10+16 - Z%(R%,1) = A% - Z%(R%+1,0) = P% - Z%(R%+1,1) = E% + Z%(R%,0)=10+16 + Z%(R%,1)=A% + Z%(R%+1,0)=P% + Z%(R%+1,1)=E% RETURN From 9e8f521118e65199b07f050f495337e42ae72a72 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 21 Sep 2016 23:27:12 -0500 Subject: [PATCH 0151/2308] Basic: step7 basics, reader macros. step1,3 tests. Also: - Add some step1 and step3 tests that were discovered during Basic development. - Move PR_MEMORY* to debug.in.bas - Simplify Makefile deps - Fix freeing in steps4-7 when error at deeper level i.e. (prn (abc)) - add SLICE function to support concat implementation. --- basic/Makefile | 24 +- basic/core.in.bas | 54 +++- basic/debug.in.bas | 79 +++++ basic/reader.in.bas | 41 ++- basic/step0_repl.in.bas | 17 +- basic/step1_read_print.in.bas | 8 +- basic/step2_eval.in.bas | 32 +- basic/step3_env.in.bas | 22 +- basic/step4_if_fn_do.in.bas | 37 +-- basic/step5_tco.in.bas | 37 +-- basic/step6_file.in.bas | 50 ++-- basic/step7_quote.in.bas | 534 ++++++++++++++++++++++++++++++++++ basic/types.in.bas | 107 +++---- tests/step1_read_print.mal | 4 + tests/step3_env.mal | 7 + tests/step5_tco.mal | 2 + 16 files changed, 869 insertions(+), 186 deletions(-) create mode 100644 basic/debug.in.bas create mode 100755 basic/step7_quote.in.bas diff --git a/basic/Makefile b/basic/Makefile index 68bbe2241c..2ba285b659 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -9,13 +9,19 @@ step%.prg: step%.bas petcat -text -w2 -o $@ $<.tmp #rm $<.tmp -step0_repl.bas: readline.in.bas -step1_read_print.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas -step2_eval.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas -step3_env.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas env.in.bas -step4_if_fn_do.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas env.in.bas core.in.bas -step5_tco.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas env.in.bas core.in.bas -step6_file.bas: readline.in.bas types.in.bas reader.in.bas printer.in.bas env.in.bas core.in.bas +STEP0_DEPS = readline.in.bas +STEP1_DEPS = $(STEP0_DEPS) debug.in.bas types.in.bas reader.in.bas printer.in.bas +STEP3_DEPS = $(STEP1_DEPS) env.in.bas +STEP4_DEPS = $(STEP3_DEPS) core.in.bas + +step0_repl.bas: $(STEP0_DEPS) +step1_read_print.bas: $(STEP1_DEPS) +step2_eval.bas: $(STEP1_DEPS) +step3_env.bas: $(STEP3_DEPS) +step4_if_fn_do.bas: $(STEP4_DEPS) +step5_tco.bas: $(STEP4_DEPS) +step6_file.bas: $(STEP4_DEPS) +step7_quote.bas: $(STEP4_DEPS) tests/%.bas: tests/%.in.bas ./qb2cbm.sh $< > $@ @@ -25,8 +31,10 @@ tests/%.prg: tests/%.bas petcat -text -w2 -o $@ $<.tmp rm $<.tmp +mal.prg: step7_quote.prg + cp $< $@ -SOURCES_LISP = env.in.bas core.in.bas step6_file.in.bas +SOURCES_LISP = env.in.bas core.in.bas step7_quote.in.bas SOURCES = readline.in.bas types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP) .PHONY: stats diff --git a/basic/core.in.bas b/basic/core.in.bas index 58ef902d49..1628cc750b 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -31,6 +31,7 @@ DO_FUNCTION: IF FF%=28 THEN DO_LIST_Q IF FF%=39 THEN DO_CONS + IF FF%=40 THEN DO_CONCAT IF FF%=43 THEN DO_FIRST IF FF%=44 THEN DO_REST IF FF%=45 THEN DO_EMPTY_Q @@ -54,11 +55,11 @@ DO_FUNCTION: DO_PR_STR: AZ%=AR%: PR%=1: SE$=" ": GOSUB PR_STR_SEQ - AS$=R$: T%=4: GOSUB STRING + AS$=R$: T%=4+16: GOSUB STRING RETURN DO_STR: AZ%=AR%: PR%=0: SE$="": GOSUB PR_STR_SEQ - AS$=R$: T%=4: GOSUB STRING + AS$=R$: T%=4+16: GOSUB STRING RETURN DO_PRN: AZ%=AR%: PR%=1: SE$=" ": GOSUB PR_STR_SEQ @@ -89,7 +90,7 @@ DO_FUNCTION: GOTO DO_SLURP_LOOP DO_SLURP_DONE: CLOSE 1 - AS$=R$: T%=4: GOSUB STRING + AS$=R$: T%=4+16: GOSUB STRING RETURN DO_LT: @@ -142,6 +143,51 @@ DO_FUNCTION: DO_CONS: A%=AA%: B%=AB%: GOSUB CONS RETURN + DO_CONCAT: + REM if empty arguments, return empty list + IF Z%(AR%,1)=0 THEN R%=3: Z%(R%,0)=Z%(R%,0)+16: RETURN + + REM single argument + IF Z%(Z%(AR%,1),1)<>0 THEN GOTO DO_CONCAT_MULT + REM if single argument and it's a list, return it + IF (Z%(AA%,0)AND15)=6 THEN R%=AA%: Z%(R%,0)=Z%(R%,0)+16: RETURN + REM otherwise, copy first element to turn it into a list + B%=AA%+1: GOSUB DEREF_B: REM value to copy + SZ%=2: GOSUB ALLOC + Z%(R%,0)=6+16: Z%(R%,1)=Z%(AA%,1) + Z%(R%+1,0)=14: Z%(R%+1,1)=B% + REM inc ref count of trailing list part and of copied value + Z%(Z%(AA%,1),0)=Z%(Z%(AA%,1),0)+16 + Z%(B%,0)=Z%(B%,0)+16 + RETURN + + REM multiple arguments + DO_CONCAT_MULT: + CZ%=ZL%: REM save current stack position + REM push arguments onto the stack + DO_CONCAT_STACK: + R%=AR%+1: GOSUB DEREF_R + ZL%=ZL%+1: ZZ%(ZL%)=R%: REM push sequence + AR%=Z%(AR%,1) + IF Z%(AR%,1)<>0 THEN GOTO DO_CONCAT_STACK + + REM pop last argument as our seq to prepend to + AB%=ZZ%(ZL%): ZL%=ZL%-1 + REM last arg/seq is not copied so we need to inc ref to it + Z%(AB%,0)=Z%(AB%,0)+16 + DO_CONCAT_LOOP: + IF ZL%=CZ% THEN R%=AB%: RETURN + AA%=ZZ%(ZL%): ZL%=ZL%-1: REM pop off next seq to prepend + A%=AA%: B%=0: C%=-1: GOSUB SLICE + + REM release the terminator of new list (we skip over it) + AY%=Z%(R6%,1): GOSUB RELEASE + REM attach new list element before terminator (last actual + REM element to the next sequence + Z%(R6%,1)=AB% + + AB%=R% + GOTO DO_CONCAT_LOOP DO_FIRST: IF Z%(AA%,1)=0 THEN R%=0 IF Z%(AA%,1)<>0 THEN R%=AA%+1: GOSUB DEREF_R @@ -253,7 +299,6 @@ DO_FUNCTION: RETURN DO_EVAL: - AZ%=AA%: PR%=1: GOSUB PR_STR A%=AA%: E%=RE%: GOSUB EVAL RETURN @@ -289,6 +334,7 @@ INIT_CORE_NS: K$="list?": A%=28: GOSUB INIT_CORE_SET_FUNCTION K$="cons": A%=39: GOSUB INIT_CORE_SET_FUNCTION + K$="concat": A%=40: GOSUB INIT_CORE_SET_FUNCTION K$="first": A%=43: GOSUB INIT_CORE_SET_FUNCTION K$="rest": A%=44: GOSUB INIT_CORE_SET_FUNCTION K$="empty?": A%=45: GOSUB INIT_CORE_SET_FUNCTION diff --git a/basic/debug.in.bas b/basic/debug.in.bas new file mode 100644 index 0000000000..3a385be8e5 --- /dev/null +++ b/basic/debug.in.bas @@ -0,0 +1,79 @@ +PR_MEMORY_SUMMARY: + GOSUB CHECK_FREE_LIST: REM get count in P2% + PRINT + PRINT "Free memory (FRE) : "+STR$(FRE(0)) + PRINT "Value memory (Z%) : "+STR$(ZI%-1)+" /"+STR$(S1%) + PRINT " "; + PRINT " used:"+STR$(ZI%-1-P2%)+", freed:"+STR$(P2%); + PRINT ", post repl_env:"+STR$(ZT%) + PRINT "String values (ZS$) : "+STR$(ZJ%)+" /"+STR$(S2%) + PRINT "Call stack size (ZZ%) : "+STR$(ZL%+1)+" /"+STR$(S3%) + RETURN + +REM REM PR_MEMORY(P1%, P2%) -> nil +REM PR_MEMORY: +REM IF P2%"+STR$(P2%); +REM PRINT " (ZI%: "+STR$(ZI%)+", ZK%: "+STR$(ZK%)+"):" +REM IF P2%P2% THEN GOTO PR_MEMORY_AFTER_VALUES +REM PRINT " "+STR$(I); +REM IF (Z%(I,0)AND15)=15 THEN GOTO PR_MEMORY_FREE +REM PRINT ": ref cnt: "+STR$((Z%(I,0)AND-16)/16); +REM PRINT ", type: "+STR$(Z%(I,0)AND15)+", value: "+STR$(Z%(I,1)); +REM IF (Z%(I,0)AND15)=4 THEN PRINT " '"+ZS$(Z%(I,1))+"'"; +REM IF (Z%(I,0)AND15)=5 THEN PRINT " "+ZS$(Z%(I,1))+""; +REM PRINT +REM I=I+1 +REM IF (Z%(I-1,0)AND15)<>10 THEN GOTO PR_MEMORY_VALUE_LOOP +REM PRINT " "+STR$(I)+": "; +REM PRINT "params: "+STR$(Z%(I+1,0))+", env:"+STR$(Z%(I+1,1)) +REM I=I+1 +REM GOTO PR_MEMORY_VALUE_LOOP +REM PR_MEMORY_FREE: +REM PRINT ": FREE size: "+STR$((Z%(I,0)AND-16)/16)+", next: "+STR$(Z%(I,1)); +REM IF I=ZK% THEN PRINT " (free list start)"; +REM PRINT +REM IF (Z%(I,0)AND-16)=32 THEN I=I+1: PRINT " "+STR$(I)+": ---" +REM I=I+1 +REM GOTO PR_MEMORY_VALUE_LOOP +REM PR_MEMORY_AFTER_VALUES: +REM PRINT "ZS% String Memory (ZJ%: "+STR$(ZJ%)+"):" +REM IF ZJ%<=0 THEN PRINT " ---": GOTO PR_MEMORY_SKIP_STRINGS +REM FOR I=0 TO ZJ%-1 +REM PRINT " "+STR$(I)+": '"+ZS$(I)+"'" +REM NEXT I +REM PR_MEMORY_SKIP_STRINGS: +REM PRINT "ZZ% Stack Memory (ZL%: "+STR$(ZL%)+"):" +REM IF ZL%<0 THEN PRINT " ---": GOTO PR_MEMORY_SKIP_STACK +REM FOR I=0 TO ZL% +REM PRINT " "+STR$(I)+": "+STR$(ZZ%(I)) +REM NEXT I +REM PR_MEMORY_SKIP_STACK: +REM PRINT "^^^^^^" +REM RETURN + +REM PR_OBJECT(P1%) -> nil +PR_OBJECT: + RC%=0 + + RC%=RC%+1: ZL%=ZL%+1: ZZ%(ZL%)=P1% + + PR_OBJ_LOOP: + IF RC%=0 THEN RETURN + I=ZZ%(ZL%): RC%=RC%-1: ZL%=ZL%-1 + + P2%=Z%(I,0)AND15 + PRINT " "+STR$(I); + PRINT ": ref cnt: "+STR$((Z%(I,0)AND-16)/16); + PRINT ", type: "+STR$(P2%)+", value: "+STR$(Z%(I,1)); + IF P2%=4 THEN PRINT " '"+ZS$(Z%(I,1))+"'"; + IF P2%=5 THEN PRINT " "+ZS$(Z%(I,1))+""; + PRINT + IF P2%<=5 OR P2%=9 THEN GOTO PR_OBJ_LOOP + IF Z%(I,1)<>0 THEN RC%=RC%+1: ZL%=ZL%+1: ZZ%(ZL%)=Z%(I,1) + IF P2%>=6 AND P2%<=8 THEN RC%=RC%+1: ZL%=ZL%+1: ZZ%(ZL%)=I+1 + GOTO PR_OBJ_LOOP diff --git a/basic/reader.in.bas b/basic/reader.in.bas index dd452ee149..03cf5b5150 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -6,6 +6,8 @@ READ_TOKEN: IF T$="(" OR T$=")" THEN RETURN IF T$="[" OR T$="]" THEN RETURN IF T$="{" OR T$="}" THEN RETURN + IF (T$="'") OR (T$="`") OR (T$="@") THEN RETURN + IF (T$="~") AND NOT MID$(A$,CUR%+1,1)="@" THEN RETURN S1=0: S2=0: REM S1: INSTRING?, S2: ESCAPED? IF T$=CHR$(34) THEN S1=1 CUR%=CUR%+1 @@ -20,6 +22,7 @@ READ_TOKEN: IF CH$="{" OR CH$="}" THEN RETURN READ_TOKEN_CONT: T$=T$+CH$ + IF T$="~@" THEN RETURN CUR%=CUR%+1 IF S1 AND S2 THEN S2=0: GOTO READ_TOKEN_LOOP IF S1 AND (S2=0) AND (CH$=CHR$(92)) THEN S2=1: GOTO READ_TOKEN_LOOP @@ -42,13 +45,20 @@ READ_FORM: IF ER% THEN RETURN GOSUB SKIP_SPACES GOSUB READ_TOKEN + IF T$="" AND SD%>0 THEN ER$="unexpected EOF": GOTO READ_FORM_ABORT REM PRINT "READ_FORM T$: ["+T$+"]" IF T$="" THEN R%=0: GOTO READ_FORM_DONE IF T$="nil" THEN T%=0: GOTO READ_NIL_BOOL IF T$="false" THEN T%=1: GOTO READ_NIL_BOOL IF T$="true" THEN T%=2: GOTO READ_NIL_BOOL + IF T$="'" THEN AS$="quote": GOTO READ_MACRO + IF T$="`" THEN AS$="quasiquote": GOTO READ_MACRO + IF T$="~" THEN AS$="unquote": GOTO READ_MACRO + IF T$="~@" THEN AS$="splice-unquote": GOTO READ_MACRO + IF T$="@" THEN AS$="deref": GOTO READ_MACRO CH$=MID$(T$,1,1) REM PRINT "CH$: ["+CH$+"]("+STR$(ASC(CH$))+")" + IF (CH$=";") THEN R%=0: GOTO READ_TO_EOL IF CH$>="0" AND CH$ <= "9" THEN READ_NUMBER IF CH$="-" THEN READ_SYMBOL_MAYBE @@ -61,6 +71,11 @@ READ_FORM: IF CH$="}" THEN T%=8: GOTO READ_SEQ_END GOTO READ_SYMBOL + READ_TO_EOL: + CH$=MID$(A$,IDX%+1,1) + IDX%=IDX%+1 + IF CH$="" OR CH$=CHR$(13) OR CH$=CHR$(10) THEN GOTO READ_FORM + GOTO READ_TO_EOL READ_NIL_BOOL: REM PRINT "READ_NIL_BOOL" SZ%=1: GOSUB ALLOC @@ -73,6 +88,27 @@ READ_FORM: Z%(R%,0)=2+16 Z%(R%,1)=VAL(T$) GOTO READ_FORM_DONE + READ_MACRO: + IDX%=IDX%+LEN(T$) + T%=5: GOSUB STRING: REM AS$ set above + + REM to call READ_FORM recursively, SD% needs to be saved, set to + REM 0 for the call and then restored afterwards. + ZL%=ZL%+2: ZZ%(ZL%-1)=SD%: ZZ%(ZL%)=R%: REM push SD% and symbol + SD%=0: GOSUB READ_FORM: B1%=R% + SD%=ZZ%(ZL%-1): B2%=ZZ%(ZL%): ZL%=ZL%-2: REM pop SD%, pop symbol into B2% + +REM AZ%=R%: PR%=1: GOSUB PR_STR +REM PRINT "obj: ["+R$+"] ("+STR$(R%)+")" + + GOSUB LIST2 + AY%=B1%: GOSUB RELEASE: REM release value, list has ownership +REM +REM AZ%=R%: PR%=1: GOSUB PR_STR +REM PRINT "list: ["+R$+"] ("+STR$(R%)+")" + + T$="" + GOTO READ_FORM_DONE READ_STRING: REM PRINT "READ_STRING" T7$=MID$(T$,LEN(T$),1) @@ -82,14 +118,14 @@ READ_FORM: S1$=CHR$(92)+"n": S2$=CHR$(13): GOSUB REPLACE: REM unescape newlines S1$=CHR$(92)+CHR$(92): S2$=CHR$(92): GOSUB REPLACE: REM unescape backslashes REM intern string value - AS$=R$: T%=4: GOSUB STRING + AS$=R$: T%=4+16: GOSUB STRING GOTO READ_FORM_DONE READ_SYMBOL_MAYBE: CH$=MID$(T$,2,1) IF CH$>="0" AND CH$<="9" THEN READ_NUMBER READ_SYMBOL: REM PRINT "READ_SYMBOL" - AS$=T$: T%=5: GOSUB STRING + AS$=T$: T%=5+16: GOSUB STRING GOTO READ_FORM_DONE READ_SEQ: @@ -139,7 +175,6 @@ READ_FORM: REM check read sequence depth IF SD%=0 THEN RETURN - IF T$="" THEN ER$="unexpected EOF": GOTO READ_FORM_ABORT REM PRINT "READ_FORM_DONE next list entry" REM allocate new sequence entry and space for value diff --git a/basic/step0_repl.in.bas b/basic/step0_repl.in.bas index 032f0cb5cf..82c4bca153 100755 --- a/basic/step0_repl.in.bas +++ b/basic/step0_repl.in.bas @@ -2,6 +2,8 @@ GOTO MAIN REM $INCLUDE: 'readline.in.bas' +REM $INCLUDE: 'debug.in.bas' + REM READ(A$) -> R$ MAL_READ: R$=A$ @@ -26,15 +28,16 @@ REP: REM MAIN program MAIN: - MAIN_LOOP: - A$="user> " - GOSUB READLINE: REM /* call input parser */ - IF EOF=1 THEN GOTO MAIN_DONE - A$=R$: GOSUB REP: REM /* call REP */ + REPL_LOOP: + A$="user> ": GOSUB READLINE: REM call input parser + IF EOF=1 THEN GOTO QUIT + + A$=R$: GOSUB REP: REM call REP + PRINT R$ - GOTO MAIN_LOOP + GOTO REPL_LOOP - MAIN_DONE: + QUIT: PRINT "Free: "+STR$(FRE(0)) END diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index 9df83f9ed6..240013b21c 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -5,6 +5,8 @@ REM $INCLUDE: 'types.in.bas' REM $INCLUDE: 'reader.in.bas' REM $INCLUDE: 'printer.in.bas' +REM $INCLUDE: 'debug.in.bas' + REM READ(A$) -> R% MAL_READ: GOSUB READ_STR @@ -44,8 +46,7 @@ MAIN: ZT%=ZI%: REM top of memory after base repl_env REPL_LOOP: - A$="user> " - GOSUB READLINE: REM /* call input parser */ + A$="user> ": GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT A$=R$: GOSUB REP: REM call REP @@ -61,7 +62,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0 - ER$="" + ER%=0: ER$="" RETURN diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index a1d596def5..d05fd4e4ae 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -5,6 +5,8 @@ REM $INCLUDE: 'types.in.bas' REM $INCLUDE: 'reader.in.bas' REM $INCLUDE: 'printer.in.bas' +REM $INCLUDE: 'debug.in.bas' + REM READ(A$) -> R% MAL_READ: GOSUB READ_STR @@ -19,10 +21,6 @@ EVAL_AST: IF ER%<>0 THEN GOTO EVAL_AST_RETURN - REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL_AST: "+R$+"("+STR$(A%)+")" - REM PRINT "EVAL_AST level: "+STR$(LV%) - GOSUB DEREF_A T%=Z%(A%,0)AND15 @@ -161,14 +159,15 @@ EVAL: REM an error occured, free up any new value IF ER%=1 THEN AY%=R%: GOSUB RELEASE + LV%=LV%-1: REM track basic return stack level + + REM trigger GC TA%=FRE(0) REM pop A% and E% off the stack E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 - LV%=LV%-1: REM track basic return stack level - RETURN REM DO_FUNCTION(F%, AR%) @@ -249,34 +248,28 @@ MAIN: LV%=0 REM create repl_env - GOSUB HASHMAP - RE%=R% + GOSUB HASHMAP: RE%=R% REM + function A%=1: GOSUB NATIVE_FUNCTION - HM%=RE%: K$="+": V%=R%: GOSUB ASSOC1_S - RE%=R% + HM%=RE%: K$="+": V%=R%: GOSUB ASSOC1_S: RE%=R% REM - function A%=2: GOSUB NATIVE_FUNCTION - HM%=RE%: K$="-": V%=R%: GOSUB ASSOC1_S - RE%=R% + HM%=RE%: K$="-": V%=R%: GOSUB ASSOC1_S: RE%=R% REM * function A%=3: GOSUB NATIVE_FUNCTION - HM%=RE%: K$="*": V%=R%: GOSUB ASSOC1_S - RE%=R% + HM%=RE%: K$="*": V%=R%: GOSUB ASSOC1_S: RE%=R% REM / function A%=4: GOSUB NATIVE_FUNCTION - HM%=RE%: K$="/": V%=R%: GOSUB ASSOC1_S - RE%=R% + HM%=RE%: K$="/": V%=R%: GOSUB ASSOC1_S: RE%=R% ZT%=ZI%: REM top of memory after base repl_env REPL_LOOP: - A$="user> " - GOSUB READLINE: REM /* call input parser */ + A$="user> ": GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT A$=R$: GOSUB REP: REM call REP @@ -292,7 +285,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0 - ER$="" + ER%=0: ER$="" RETURN diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index b5f605cb33..a0f7395567 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -6,6 +6,8 @@ REM $INCLUDE: 'reader.in.bas' REM $INCLUDE: 'printer.in.bas' REM $INCLUDE: 'env.in.bas' +REM $INCLUDE: 'debug.in.bas' + REM READ(A$) -> R% MAL_READ: GOSUB READ_STR @@ -20,10 +22,6 @@ EVAL_AST: IF ER%<>0 THEN GOTO EVAL_AST_RETURN - REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL_AST: "+R$+"("+STR$(A%)+")" - REM PRINT "EVAL_AST level: "+STR$(LV%) - GOSUB DEREF_A T%=Z%(A%,0)AND15 @@ -219,9 +217,14 @@ EVAL: GOTO EVAL_RETURN EVAL_RETURN: + REM AZ%=R%: PR%=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + REM release environment if not the top one on the stack IF E%<>ZZ%(ZL%-1) THEN AY%=E%: GOSUB RELEASE + LV%=LV%-1: REM track basic return stack level + REM trigger GC TA%=FRE(0) @@ -229,8 +232,6 @@ EVAL: REM pop A% and E% off the stack E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 - LV%=LV%-1: REM track basic return stack level - RETURN REM DO_FUNCTION(F%, AR%) @@ -311,8 +312,7 @@ MAIN: LV%=0 REM create repl_env - EO%=-1: GOSUB ENV_NEW - RE%=R% + EO%=-1: GOSUB ENV_NEW: RE%=R% E%=RE% REM + function @@ -334,8 +334,7 @@ MAIN: ZT%=ZI%: REM top of memory after base repl_env REPL_LOOP: - A$="user> " - GOSUB READLINE: REM /* call input parser */ + A$="user> ": GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT A$=R$: GOSUB REP: REM call REP @@ -351,7 +350,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0 - ER$="" + ER%=0: ER$="" RETURN diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index 5950ea4c2e..28b537048c 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -7,6 +7,8 @@ REM $INCLUDE: 'printer.in.bas' REM $INCLUDE: 'env.in.bas' REM $INCLUDE: 'core.in.bas' +REM $INCLUDE: 'debug.in.bas' + REM READ(A$) -> R% MAL_READ: GOSUB READ_STR @@ -102,8 +104,10 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - REM get return value (new seq) - R%=ZZ%(ZL%-1) + REM if no error, get return value (new seq) + IF ER%=0 THEN R%=ZZ%(ZL%-1) + REM otherwise, free the return value and return nil + IF ER%<>0 THEN R%=0: AY%=ZZ%(ZL%-1): GOSUB RELEASE REM pop previous, return, index and type ZL%=ZL%-4 @@ -294,8 +298,9 @@ EVAL: REM claim the AST before releasing the list containing it A%=Z%(F%,1): Z%(A%,0)=Z%(A%,0)+16 - REM add AST to pending release queue to free later - ZM%=ZM%+1: ZR%(ZM%)=A% + REM add AST to pending release queue to free as soon as EVAL + REM actually returns (LV%+1) + ZM%=ZM%+1: ZR%(ZM%,0)=A%: ZR%(ZM%,1)=LV%+1 REM pop and release f/args AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE @@ -304,23 +309,23 @@ EVAL: E%=R%: GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: + REM AZ%=R%: PR%=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + REM release environment if not the top one on the stack IF E%<>ZZ%(ZL%-1) THEN AY%=E%: GOSUB RELEASE + LV%=LV%-1: REM track basic return stack level + REM release everything we couldn't release earlier GOSUB RELEASE_PEND - REM AZ%=R%: PR%=1: GOSUB PR_STR - REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) - REM trigger GC TA%=FRE(0) REM pop A% and E% off the stack E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 - LV%=LV%-1: REM track basic return stack level - RETURN REM PRINT(A%) -> R$ @@ -373,8 +378,7 @@ MAIN: LV%=0 REM create repl_env - EO%=-1: GOSUB ENV_NEW - RE%=R% + EO%=-1: GOSUB ENV_NEW: RE%=R% REM core.EXT: defined in Basic E%=RE%: GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -382,15 +386,13 @@ MAIN: ZT%=ZI%: REM top of memory after base repl_env REM core.mal: defined using the language itself - A$="(def! not (fn* (a) (if a false true)))" - GOSUB RE: AY%=R%: GOSUB RELEASE + A$="(def! not (fn* (a) (if a false true)))": GOSUB RE: AY%=R%: GOSUB RELEASE REPL_LOOP: - A$="user> " - GOSUB READLINE: REM /* call input parser */ + A$="user> ": GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$: GOSUB REP: REM /* call REP */ + A$=R$: GOSUB REP: REM call REP IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP PRINT R$ @@ -403,7 +405,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0 - ER$="" + ER%=0: ER$="" RETURN diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index 63d3a794e5..797575648c 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -7,6 +7,8 @@ REM $INCLUDE: 'printer.in.bas' REM $INCLUDE: 'env.in.bas' REM $INCLUDE: 'core.in.bas' +REM $INCLUDE: 'debug.in.bas' + REM READ(A$) -> R% MAL_READ: GOSUB READ_STR @@ -102,8 +104,10 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - REM get return value (new seq) - R%=ZZ%(ZL%-1) + REM if no error, get return value (new seq) + IF ER%=0 THEN R%=ZZ%(ZL%-1) + REM otherwise, free the return value and return nil + IF ER%<>0 THEN R%=0: AY%=ZZ%(ZL%-1): GOSUB RELEASE REM pop previous, return, index and type ZL%=ZL%-4 @@ -304,8 +308,9 @@ EVAL: REM claim the AST before releasing the list containing it A%=Z%(F%,1): Z%(A%,0)=Z%(A%,0)+16 - REM add AST to pending release queue to free later - ZM%=ZM%+1: ZR%(ZM%)=A% + REM add AST to pending release queue to free as soon as EVAL + REM actually returns (LV%+1) + ZM%=ZM%+1: ZR%(ZM%,0)=A%: ZR%(ZM%,1)=LV%+1 REM pop and release f/args AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE @@ -314,23 +319,23 @@ EVAL: E%=R%: GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: + REM AZ%=R%: PR%=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + REM release environment if not the top one on the stack IF E%<>ZZ%(ZL%-1) THEN AY%=E%: GOSUB RELEASE + LV%=LV%-1: REM track basic return stack level + REM release everything we couldn't release earlier GOSUB RELEASE_PEND - REM AZ%=R%: PR%=1: GOSUB PR_STR - REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) - REM trigger GC TA%=FRE(0) REM pop A% and E% off the stack E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 - LV%=LV%-1: REM track basic return stack level - RETURN REM PRINT(A%) -> R$ @@ -383,8 +388,7 @@ MAIN: LV%=0 REM create repl_env - EO%=-1: GOSUB ENV_NEW - RE%=R% + EO%=-1: GOSUB ENV_NEW: RE%=R% REM core.EXT: defined in Basic E%=RE%: GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -392,15 +396,13 @@ MAIN: ZT%=ZI%: REM top of memory after base repl_env REM core.mal: defined using the language itself - A$="(def! not (fn* (a) (if a false true)))" - GOSUB RE: AY%=R%: GOSUB RELEASE + A$="(def! not (fn* (a) (if a false true)))": GOSUB RE: AY%=R%: GOSUB RELEASE REPL_LOOP: - A$="user> " - GOSUB READLINE: REM /* call input parser */ + A$="user> ": GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$: GOSUB REP: REM /* call REP */ + A$=R$: GOSUB REP: REM call REP IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP PRINT R$ @@ -413,7 +415,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0 - ER$="" + ER%=0: ER$="" RETURN diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index 3eeaf07d26..7eecce33b3 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -9,6 +9,8 @@ REM $INCLUDE: 'printer.in.bas' REM $INCLUDE: 'env.in.bas' REM $INCLUDE: 'core.in.bas' +REM $INCLUDE: 'debug.in.bas' + REM READ(A$) -> R% MAL_READ: GOSUB READ_STR @@ -104,8 +106,10 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - REM get return value (new seq) - R%=ZZ%(ZL%-1) + REM if no error, get return value (new seq) + IF ER%=0 THEN R%=ZZ%(ZL%-1) + REM otherwise, free the return value and return nil + IF ER%<>0 THEN R%=0: AY%=ZZ%(ZL%-1): GOSUB RELEASE REM pop previous, return, index and type ZL%=ZL%-4 @@ -306,8 +310,9 @@ EVAL: REM claim the AST before releasing the list containing it A%=Z%(F%,1): Z%(A%,0)=Z%(A%,0)+16 - REM add AST to pending release queue to free later - ZM%=ZM%+1: ZR%(ZM%)=A% + REM add AST to pending release queue to free as soon as EVAL + REM actually returns (LV%+1) + ZM%=ZM%+1: ZR%(ZM%,0)=A%: ZR%(ZM%,1)=LV%+1 REM pop and release f/args AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE @@ -316,23 +321,23 @@ EVAL: E%=R%: GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: + REM AZ%=R%: PR%=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + REM release environment if not the top one on the stack IF E%<>ZZ%(ZL%-1) THEN AY%=E%: GOSUB RELEASE + LV%=LV%-1: REM track basic return stack level + REM release everything we couldn't release earlier GOSUB RELEASE_PEND - REM AZ%=R%: PR%=1: GOSUB PR_STR - REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) - REM trigger GC TA%=FRE(0) REM pop A% and E% off the stack E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 - LV%=LV%-1: REM track basic return stack level - RETURN REM PRINT(A%) -> R$ @@ -385,8 +390,7 @@ MAIN: LV%=0 REM create repl_env - EO%=-1: GOSUB ENV_NEW - RE%=R% + EO%=-1: GOSUB ENV_NEW: RE%=R% REM core.EXT: defined in Basic E%=RE%: GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -394,8 +398,7 @@ MAIN: ZT%=ZI%: REM top of memory after base repl_env REM core.mal: defined using the language itself - A$="(def! not (fn* (a) (if a false true)))" - GOSUB RE: AY%=R%: GOSUB RELEASE + A$="(def! not (fn* (a) (if a false true)))": GOSUB RE: AY%=R%: GOSUB RELEASE A$="(def! load-file (fn* (f) (eval (read-string (str " A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) " @@ -407,12 +410,10 @@ MAIN: GOSUB RE: AY%=R%: GOSUB RELEASE REM set the argument list - A$="(def! *ARGV* (rest -*ARGS*-))" - GOSUB RE: AY%=R%: GOSUB RELEASE + A$="(def! *ARGV* (rest -*ARGS*-))": GOSUB RE: AY%=R%: GOSUB RELEASE REM get the first argument - A$="(first -*ARGS*-)" - GOSUB RE + A$="(first -*ARGS*-)": GOSUB RE REM if there is an argument, then run it as a program IF R%<>0 THEN AY%=R%: GOSUB RELEASE: GOTO RUN_PROG @@ -421,17 +422,15 @@ MAIN: RUN_PROG: REM run a single mal program and exit - A$="(load-file (first -*ARGS*-))" - GOSUB REP - IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO QUIT - IF ER%=0 THEN PRINT R$: GOTO QUIT + A$="(load-file (first -*ARGS*-))": GOSUB RE + IF ER%<>0 THEN GOSUB PRINT_ERROR + END REPL_LOOP: - A$="user> " - GOSUB READLINE: REM /* call input parser */ + A$="user> ": GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$: GOSUB REP: REM /* call REP */ + A$=R$: GOSUB REP: REM call REP IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP PRINT R$ @@ -444,7 +443,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0 - ER$="" + ER%=0: ER$="" RETURN diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas new file mode 100755 index 0000000000..b7b975a9e6 --- /dev/null +++ b/basic/step7_quote.in.bas @@ -0,0 +1,534 @@ +REM POKE 1, PEEK(1)AND248: REM enable all ROM areas as RAM +REM POKE 55,0: POKE 56,192: CLR: REM move BASIC end from $A000 to $C000 +GOTO MAIN + +REM $INCLUDE: 'readline.in.bas' +REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'reader.in.bas' +REM $INCLUDE: 'printer.in.bas' +REM $INCLUDE: 'env.in.bas' +REM $INCLUDE: 'core.in.bas' + +REM $INCLUDE: 'debug.in.bas' + +REM READ(A$) -> R% +MAL_READ: + GOSUB READ_STR + RETURN + +REM PAIR_Q(B%) -> R% +PAIR_Q: + R%=0 + IF (Z%(B%,0)AND15)<>6 AND (Z%(B%,0)AND15)<>7 THEN RETURN + IF (Z%(B%,1)=0) THEN RETURN + R%=1 + RETURN + +REM QUASIQUOTE(A%) -> R% +QUASIQUOTE: + B%=A%: GOSUB PAIR_Q + IF R%=1 THEN GOTO QQ_UNQUOTE + REM ['quote, ast] + AS$="quote": T%=5: GOSUB STRING + B2%=R%: B1%=A%: GOSUB LIST2 + + RETURN + + QQ_UNQUOTE: + R%=A%+1: GOSUB DEREF_R + IF (Z%(R%,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF ZS$(Z%(R%,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE + REM [ast[1]] + R%=Z%(A%,1)+1: GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + + RETURN + + QQ_SPLICE_UNQUOTE: + REM push A% on the stack + ZL%=ZL%+1: ZZ%(ZL%)=A% + REM rest of cases call quasiquote on ast[1..] + A%=Z%(A%,1): GOSUB QUASIQUOTE: T6%=R% + REM pop A% off the stack + A%=ZZ%(ZL%): ZL%=ZL%-1 + + REM set A% to ast[0] for last two cases + A%=A%+1: GOSUB DEREF_A + + B%=A%: GOSUB PAIR_Q + IF R%=0 THEN GOTO QQ_DEFAULT + B%=A%+1: GOSUB DEREF_B + IF (Z%(B%,0)AND15)<>5 THEN GOTO QQ_DEFAULT + IF ZS$(Z%(B%,1))<>"splice-unquote" THEN QQ_DEFAULT + REM ['concat, ast[0][1], quasiquote(ast[1..])] + + B%=Z%(A%,1)+1: GOSUB DEREF_B: B2%=B% + AS$="concat": T%=5: GOSUB STRING: B3%=R% + B1%=T6%: GOSUB LIST3 + REM release inner quasiquoted since outer list takes ownership + AY%=B1%: GOSUB RELEASE + RETURN + + QQ_DEFAULT: + REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] + + REM push T6% on the stack + ZL%=ZL%+1: ZZ%(ZL%)=T6% + REM A% set above to ast[0] + GOSUB QUASIQUOTE: B2%=R% + REM pop T6% off the stack + T6%=ZZ%(ZL%): ZL%=ZL%-1 + + AS$="cons": T%=5: GOSUB STRING: B3%=R% + B1%=T6%: GOSUB LIST3 + REM release inner quasiquoted since outer list takes ownership + AY%=B1%: GOSUB RELEASE: AY%=B2%: GOSUB RELEASE + RETURN + + +REM EVAL_AST(A%, E%) -> R% +REM called using GOTO to avoid basic return address stack usage +REM top of stack should have return label index +EVAL_AST: + REM push A% and E% on the stack + ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + + IF ER%<>0 THEN GOTO EVAL_AST_RETURN + + GOSUB DEREF_A + + T%=Z%(A%,0)AND15 + IF T%=5 THEN EVAL_AST_SYMBOL + IF T%=6 THEN EVAL_AST_SEQ + IF T%=7 THEN EVAL_AST_SEQ + IF T%=8 THEN EVAL_AST_SEQ + + REM scalar: deref to actual value and inc ref cnt + R%=A%: GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + GOTO EVAL_AST_RETURN + + EVAL_AST_SYMBOL: + K%=A%: GOSUB ENV_GET + GOTO EVAL_AST_RETURN + + EVAL_AST_SEQ: + REM allocate the first entry + SZ%=2: GOSUB ALLOC + + REM make space on the stack + ZL%=ZL%+4 + REM push type of sequence + ZZ%(ZL%-3)=T% + REM push sequence index + ZZ%(ZL%-2)=-1 + REM push future return value (new sequence) + ZZ%(ZL%-1)=R% + REM push previous new sequence entry + ZZ%(ZL%)=R% + + EVAL_AST_SEQ_LOOP: + REM set new sequence entry type (with 1 ref cnt) + Z%(R%,0)=ZZ%(ZL%-3)+16 + Z%(R%,1)=0 + REM create value ptr placeholder + Z%(R%+1,0)=14 + Z%(R%+1,1)=0 + + REM update index + ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + + REM check if we are done evaluating the source sequence + IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + + REM if hashmap, skip eval of even entries (keys) + IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + GOTO EVAL_AST_DO_EVAL + + EVAL_AST_DO_REF: + R%=A%+1: GOSUB DEREF_R: REM deref to target of referred entry + Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + GOTO EVAL_AST_ADD_VALUE + + EVAL_AST_DO_EVAL: + REM call EVAL for each entry + A%=A%+1: GOSUB EVAL + A%=A%-1 + GOSUB DEREF_R: REM deref to target of evaluated entry + + EVAL_AST_ADD_VALUE: + + REM update previous value pointer to evaluated entry + Z%(ZZ%(ZL%)+1,1)=R% + + IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + + REM allocate the next entry + SZ%=2: GOSUB ALLOC + + REM update previous sequence entry value to point to new entry + Z%(ZZ%(ZL%),1)=R% + REM update previous ptr to current entry + ZZ%(ZL%)=R% + + REM process the next sequence entry from source list + A%=Z%(A%,1) + + GOTO EVAL_AST_SEQ_LOOP + EVAL_AST_SEQ_LOOP_DONE: + REM if no error, get return value (new seq) + IF ER%=0 THEN R%=ZZ%(ZL%-1) + REM otherwise, free the return value and return nil + IF ER%<>0 THEN R%=0: AY%=ZZ%(ZL%-1): GOSUB RELEASE + + REM pop previous, return, index and type + ZL%=ZL%-4 + GOTO EVAL_AST_RETURN + + EVAL_AST_RETURN: + REM pop A% and E% off the stack + E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + + REM pop EVAL AST return label/address + RN%=ZZ%(ZL%): ZL%=ZL%-1 + IF RN%=1 GOTO EVAL_AST_RETURN_1 + IF RN%=2 GOTO EVAL_AST_RETURN_2 + IF RN%=3 GOTO EVAL_AST_RETURN_3 + RETURN + +REM EVAL(A%, E%)) -> R% +EVAL: + LV%=LV%+1: REM track basic return stack level + + REM push A% and E% on the stack + ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + + EVAL_TCO_RECUR: + + REM AZ%=A%: GOSUB PR_STR + REM PRINT "EVAL: "+R$+"("+STR$(A%)+"), LV%:"+STR$(LV%) + + GOSUB DEREF_A + + GOSUB LIST_Q + IF R% THEN GOTO APPLY_LIST + REM ELSE + REM push EVAL_AST return label/address + ZL%=ZL%+1: ZZ%(ZL%)=1 + GOTO EVAL_AST + EVAL_AST_RETURN_1: + + GOTO EVAL_RETURN + + APPLY_LIST: + GOSUB EMPTY_Q + IF R% THEN R%=A%: Z%(R%,0)=Z%(R%,0)+16: GOTO EVAL_RETURN + + A0%=A%+1 + R%=A0%: GOSUB DEREF_R: A0%=R% + + REM get symbol in A$ + IF (Z%(A0%,0)AND15)<>5 THEN A$="" + IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) + + IF A$="def!" THEN GOTO EVAL_DEF + IF A$="let*" THEN GOTO EVAL_LET + IF A$="quote" THEN GOTO EVAL_QUOTE + IF A$="quasiquote" THEN GOTO EVAL_QUASIQUOTE + IF A$="do" THEN GOTO EVAL_DO + IF A$="if" THEN GOTO EVAL_IF + IF A$="fn*" THEN GOTO EVAL_FN + GOTO EVAL_INVOKE + + EVAL_GET_A3: + A3%=Z%(Z%(Z%(A%,1),1),1)+1 + R%=A3%: GOSUB DEREF_R: A3%=R% + EVAL_GET_A2: + A2%=Z%(Z%(A%,1),1)+1 + R%=A2%: GOSUB DEREF_R: A2%=R% + EVAL_GET_A1: + A1%=Z%(A%,1)+1 + R%=A1%: GOSUB DEREF_R: A1%=R% + RETURN + + EVAL_DEF: + REM PRINT "def!" + GOSUB EVAL_GET_A2: REM set a1% and a2% + + ZL%=ZL%+1: ZZ%(ZL%)=A1%: REM push A1% + A%=A2%: GOSUB EVAL: REM eval a2 + A1%=ZZ%(ZL%): ZL%=ZL%-1: REM pop A1% + + REM set a1 in env to a2 + K%=A1%: V%=R%: GOSUB ENV_SET + GOTO EVAL_RETURN + + EVAL_LET: + REM PRINT "let*" + GOSUB EVAL_GET_A2: REM set a1% and a2% + + E4%=E%: REM save the current environment for release + + REM create new environment with outer as current environment + EO%=E%: GOSUB ENV_NEW + E%=R% + EVAL_LET_LOOP: + IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + + REM push A1% + ZL%=ZL%+1: ZZ%(ZL%)=A1% + REM eval current A1 odd element + A%=Z%(A1%,1)+1: GOSUB EVAL + REM pop A1% + A1%=ZZ%(ZL%): ZL%=ZL%-1 + + REM set environment: even A1% key to odd A1% eval'd above + K%=A1%+1: V%=R%: GOSUB ENV_SET + AY%=R%: GOSUB RELEASE: REM release our use, ENV_SET took ownership + + REM skip to the next pair of A1% elements + A1%=Z%(Z%(A1%,1),1) + GOTO EVAL_LET_LOOP + EVAL_LET_LOOP_DONE: + REM release previous env (if not root repl_env) because our + REM new env refers to it and we no longer need to track it + REM (since we are TCO recurring) + IF E4%<>RE% THEN AY%=E4%: GOSUB RELEASE + + A%=A2%: GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_DO: + A%=Z%(A%,1): REM rest + + REM TODO: TCO + + REM push EVAL_AST return label/address + ZL%=ZL%+1: ZZ%(ZL%)=2 + GOTO EVAL_AST + EVAL_AST_RETURN_2: + + ZL%=ZL%+1: ZZ%(ZL%)=R%: REM push eval'd list + A%=R%: GOSUB LAST: REM return the last element + AY%=ZZ%(ZL%): ZL%=ZL%-1: REM pop eval'd list + GOSUB RELEASE: REM release the eval'd list + GOTO EVAL_RETURN + + EVAL_QUOTE: + R%=Z%(A%,1)+1: GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + GOTO EVAL_RETURN + + EVAL_QUASIQUOTE: + R%=Z%(A%,1)+1: GOSUB DEREF_R + A%=R%: GOSUB QUASIQUOTE + REM add quasiquote result to pending release queue to free when + REM next lower EVAL level returns (LV%) + ZM%=ZM%+1: ZR%(ZM%,0)=R%: ZR%(ZM%,1)=LV% + + A%=R%: GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_IF: + GOSUB EVAL_GET_A1: REM set a1% + REM push A% + ZL%=ZL%+1: ZZ%(ZL%)=A% + A%=A1%: GOSUB EVAL + REM pop A% + A%=ZZ%(ZL%): ZL%=ZL%-1 + IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE + + EVAL_IF_TRUE: + AY%=R%: GOSUB RELEASE + GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL + A%=A2%: GOTO EVAL_TCO_RECUR: REM TCO loop + EVAL_IF_FALSE: + AY%=R%: GOSUB RELEASE + REM if no false case (A3%), return nil + IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0: GOTO EVAL_RETURN + GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL + A%=A3%: GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_FN: + GOSUB EVAL_GET_A2: REM set a1% and a2% + A%=A2%: P%=A1%: GOSUB MAL_FUNCTION + GOTO EVAL_RETURN + + EVAL_INVOKE: + REM push EVAL_AST return label/address + ZL%=ZL%+1: ZZ%(ZL%)=3 + GOTO EVAL_AST + EVAL_AST_RETURN_3: + + REM if error, return f/args for release by caller + IF ER%<>0 THEN GOTO EVAL_RETURN + + REM push f/args for release after call + ZL%=ZL%+1: ZZ%(ZL%)=R% + + F%=R%+1 + + AR%=Z%(R%,1): REM rest + R%=F%: GOSUB DEREF_R: F%=R% + + IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + + REM if error, pop and return f/args for release by caller + R%=ZZ%(ZL%): ZL%=ZL%-1 + ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + + EVAL_DO_FUNCTION: + GOSUB DO_FUNCTION + + REM pop and release f/args + AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + GOTO EVAL_RETURN + + EVAL_DO_MAL_FUNCTION: + E4%=E%: REM save the current environment for release + + REM create new environ using env stored with function + EO%=Z%(F%+1,1): BI%=Z%(F%+1,0): EX%=AR%: GOSUB ENV_NEW_BINDS + + REM release previous env if it is not the top one on the + REM stack (ZZ%(ZL%-2)) because our new env refers to it and + REM we no longer need to track it (since we are TCO recurring) + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%: GOSUB RELEASE + + REM claim the AST before releasing the list containing it + A%=Z%(F%,1): Z%(A%,0)=Z%(A%,0)+16 + REM add AST to pending release queue to free as soon as EVAL + REM actually returns (LV%+1) + ZM%=ZM%+1: ZR%(ZM%,0)=A%: ZR%(ZM%,1)=LV%+1 + + REM pop and release f/args + AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + + REM A% set above + E%=R%: GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_RETURN: + REM AZ%=R%: PR%=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + + REM release environment if not the top one on the stack + IF E%<>ZZ%(ZL%-1) THEN AY%=E%: GOSUB RELEASE + + LV%=LV%-1: REM track basic return stack level + + REM release everything we couldn't release earlier + GOSUB RELEASE_PEND + + REM trigger GC + TA%=FRE(0) + + REM pop A% and E% off the stack + E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + + RETURN + +REM PRINT(A%) -> R$ +MAL_PRINT: + AZ%=A%: PR%=1: GOSUB PR_STR + RETURN + +REM RE(A$) -> R% +REM Assume RE% has repl_env +REM caller must release result +RE: + R1%=0 + GOSUB MAL_READ + R1%=R% + IF ER%<>0 THEN GOTO REP_DONE + + A%=R%: E%=RE%: GOSUB EVAL + + REP_DONE: + REM Release memory from MAL_READ + IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + RETURN: REM caller must release result of EVAL + +REM REP(A$) -> R$ +REM Assume RE% has repl_env +REP: + R1%=0: R2%=0 + GOSUB MAL_READ + R1%=R% + IF ER%<>0 THEN GOTO REP_DONE + + A%=R%: E%=RE%: GOSUB EVAL + R2%=R% + IF ER%<>0 THEN GOTO REP_DONE + + A%=R%: GOSUB MAL_PRINT + RT$=R$ + + REP_DONE: + REM Release memory from MAL_READ and EVAL + IF R2%<>0 THEN AY%=R2%: GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + R$=RT$ + RETURN + +REM MAIN program +MAIN: + GOSUB INIT_MEMORY + + LV%=0 + + REM create repl_env + EO%=-1: GOSUB ENV_NEW: RE%=R% + + REM core.EXT: defined in Basic + E%=RE%: GOSUB INIT_CORE_NS: REM set core functions in repl_env + + ZT%=ZI%: REM top of memory after base repl_env + + REM core.mal: defined using the language itself + A$="(def! not (fn* (a) (if a false true)))": GOSUB RE: AY%=R%: GOSUB RELEASE + + A$="(def! load-file (fn* (f) (eval (read-string (str " + A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) " + A$=A$+CHR$(34)+")"+CHR$(34)+")))))" + GOSUB RE: AY%=R%: GOSUB RELEASE + + REM load the args file + A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" + GOSUB RE: AY%=R%: GOSUB RELEASE + + REM set the argument list + A$="(def! *ARGV* (rest -*ARGS*-))": GOSUB RE: AY%=R%: GOSUB RELEASE + + REM get the first argument + A$="(first -*ARGS*-)": GOSUB RE + + REM if there is an argument, then run it as a program + IF R%<>0 THEN AY%=R%: GOSUB RELEASE: GOTO RUN_PROG + REM no arguments, start REPL loop + IF R%=0 THEN GOTO REPL_LOOP + + RUN_PROG: + REM run a single mal program and exit + A$="(load-file (first -*ARGS*-))": GOSUB RE + IF ER%<>0 THEN GOSUB PRINT_ERROR + END + + REPL_LOOP: + A$="user> ": GOSUB READLINE: REM call input parser + IF EOF=1 THEN GOTO QUIT + + A$=R$: GOSUB REP: REM call REP + + IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP + PRINT R$ + GOTO REPL_LOOP + + QUIT: + REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + GOSUB PR_MEMORY_SUMMARY + END + + PRINT_ERROR: + PRINT "Error: "+ER$ + ER%=0: ER$="" + RETURN + diff --git a/basic/types.in.bas b/basic/types.in.bas index 686744527c..ffa720ab50 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -26,7 +26,7 @@ INIT_MEMORY: S1%=2048+512: REM Z% (boxed memory) size (4 bytes each) S2%=256: REM ZS% (string memory) size (3 bytes each) S3%=256: REM ZZ% (call stack) size (2 bytes each) - S4%=64: REM ZR% (release stack) size (2 bytes each) + S4%=64: REM ZR% (release stack) size (4 bytes each) REM global error state ER%=0: ER$="" @@ -34,16 +34,17 @@ INIT_MEMORY: REM boxed element memory DIM Z%(S1%,1): REM TYPE ARRAY - REM Predefine nil, false, true + REM Predefine nil, false, true, and an empty list Z%(0,0)=0: Z%(0,1)=0 Z%(1,0)=1: Z%(1,1)=0 Z%(2,0)=1: Z%(2,1)=1 + Z%(3,0)=6+16: Z%(3,1)=0: Z%(4,0)=0: Z%(4,1)=0 REM start of unused memory - ZI%=3 + ZI%=5 REM start of free list - ZK%=3 + ZK%=5 REM string memory storage ZJ%=0: DIM ZS$(S2%) @@ -52,7 +53,7 @@ INIT_MEMORY: ZL%=-1: DIM ZZ%(S3%): REM stack of Z% indexes REM pending release stack - ZM%=-1: DIM ZR%(S4%): REM stack of Z% indexes + ZM%=-1: DIM ZR%(S4%,1): REM stack of Z% indexes REM PRINT "Lisp data memory: "+STR$(T%-FRE(0)) REM PRINT "Interpreter working memory: "+STR$(FRE(0)) @@ -189,12 +190,13 @@ RELEASE: SZ%=1: GOSUB FREE GOTO RELEASE_TOP -REM RELEASE_PEND() -> nil +REM RELEASE_PEND(LV%) -> nil RELEASE_PEND: REM REM IF ER%<>0 THEN RETURN IF ZM%<0 THEN RETURN - REM PRINT "RELEASE_PEND releasing:"+STR$(ZR%(ZM%)) - AY%=ZR%(ZM%): GOSUB RELEASE + IF ZR%(ZM%,1)<=LV% THEN RETURN + REM PRINT "RELEASE_PEND releasing:"+STR$(ZR%(ZM%,0)) + AY%=ZR%(ZM%,0): GOSUB RELEASE ZM%=ZM%-1 GOTO RELEASE_PEND @@ -225,61 +227,6 @@ CHECK_FREE_LIST: IF P2%=-1 THEN PRINT "corrupt free list at "+STR$(P1%) RETURN -PR_MEMORY_SUMMARY: - GOSUB CHECK_FREE_LIST: REM get count in P2% - PRINT - PRINT "Free memory (FRE) : "+STR$(FRE(0)) - PRINT "Value memory (Z%) : "+STR$(ZI%-1)+" /"+STR$(S1%) - PRINT " "; - PRINT " used:"+STR$(ZI%-1-P2%)+", freed:"+STR$(P2%); - PRINT ", post repl_env:"+STR$(ZT%) - PRINT "String values (ZS$) : "+STR$(ZJ%)+" /"+STR$(S2%) - PRINT "Call stack size (ZZ%) : "+STR$(ZL%+1)+" /"+STR$(S3%) - RETURN - -REM PR_MEMORY(P1%, P2%) -> nil -PR_MEMORY: - IF P2%"+STR$(P2%); - PRINT " (ZI%: "+STR$(ZI%)+", ZK%: "+STR$(ZK%)+"):" - IF P2%P2% THEN GOTO PR_MEMORY_AFTER_VALUES - PRINT " "+STR$(I); - IF (Z%(I,0)AND15)=15 THEN GOTO PR_MEMORY_FREE - PRINT ": ref cnt: "+STR$((Z%(I,0)AND-16)/16); - PRINT ", type: "+STR$(Z%(I,0)AND15)+", value: "+STR$(Z%(I,1)) - I=I+1 - IF (Z%(I-1,0)AND15)<>10 THEN GOTO PR_MEMORY_VALUE_LOOP - PRINT " "+STR$(I)+": "; - PRINT "params: "+STR$(Z%(I+1,0))+", env:"+STR$(Z%(I+1,1)) - I=I+1 - GOTO PR_MEMORY_VALUE_LOOP - PR_MEMORY_FREE: - PRINT ": FREE size: "+STR$((Z%(I,0)AND-16)/16)+", next: "+STR$(Z%(I,1)); - IF I=ZK% THEN PRINT " (free list start)"; - PRINT - IF (Z%(I,0)AND-16)=32 THEN I=I+1: PRINT " "+STR$(I)+": ---" - I=I+1 - GOTO PR_MEMORY_VALUE_LOOP - PR_MEMORY_AFTER_VALUES: - PRINT "ZS% String Memory (ZJ%: "+STR$(ZJ%)+"):" - IF ZJ%<=0 THEN PRINT " ---": GOTO PR_MEMORY_SKIP_STRINGS - FOR I=0 TO ZJ%-1 - PRINT " "+STR$(I)+": '"+ZS$(I)+"'" - NEXT I - PR_MEMORY_SKIP_STRINGS: - PRINT "ZZ% Stack Memory (ZL%: "+STR$(ZL%)+"):" - IF ZL%<0 THEN PRINT " ---": GOTO PR_MEMORY_SKIP_STACK - FOR I=0 TO ZL% - PRINT " "+STR$(I)+": "+STR$(ZZ%(I)) - NEXT I - PR_MEMORY_SKIP_STACK: - PRINT "^^^^^^" - RETURN - REM general functions @@ -336,10 +283,10 @@ REM STRING(AS$, T%) -> R% REM intern string and allocate reference (return Z% index) STRING: GOSUB STRING_ - T7%=R% + TS%=R% SZ%=1: GOSUB ALLOC - Z%(R%,0)=T%+16 - Z%(R%,1)=T7% + Z%(R%,0)=T% + Z%(R%,1)=TS% RETURN REM REPLACE(R$, S1$, S2$) -> R$ @@ -405,6 +352,34 @@ CONS: Z%(B%,0)=Z%(B%,0)+16 RETURN +REM SLICE(A%,B%,C%) -> R% +REM make copy of sequence A% from index B% to C% +SLICE: + I=0 + R5%=-1: REM temporary for return as R% + R6%=0: REM previous list element + SLICE_LOOP: + REM always allocate at list one list element + SZ%=2: GOSUB ALLOC + Z%(R%,0)=6+16: Z%(R%,1)=0: Z%(R%+1,0)=14: Z%(R%+1,1)=0 + IF R5%=-1 THEN R5%=R% + IF R5%<>-1 THEN Z%(R6%,1)=R% + REM advance A% to position B% + SLICE_FIND_B: + IF I0 THEN A%=Z%(A%,1): I=I+1: GOTO SLICE_FIND_B + REM if current position is C%, then return + IF C%<>-1 AND I>=C% THEN R%=R5%: RETURN + REM if we reached end of A%, then return + IF Z%(A%,1)=0 THEN R%=R5%: RETURN + R6%=R%: REM save previous list element + REM copy value and inc ref cnt + Z%(R6%+1,1)=Z%(A%+1,1) + R%=A%+1: GOSUB DEREF_R: Z%(R%,0)=Z%(R%,0)+16 + REM advance to next element of A% + A%=Z%(A%,1) + I=I+1 + GOTO SLICE_LOOP + REM LIST2(B2%,B1%) -> R% LIST2: REM terminator diff --git a/tests/step1_read_print.mal b/tests/step1_read_print.mal index 94d84f5294..a4d40a0503 100644 --- a/tests/step1_read_print.mal +++ b/tests/step1_read_print.mal @@ -27,6 +27,8 @@ abc-def ;=>(+ 1 2) () ;=>() +(nil) +;=>(nil) ((3 4)) ;=>((3 4)) (+ 1 (+ 2 3)) @@ -96,6 +98,8 @@ false ;=>(unquote 1) ~(1 2 3) ;=>(unquote (1 2 3)) +`(1 ~a 3) +;=>(quasiquote (1 (unquote a) 3)) ~@(1 2 3) ;=>(splice-unquote (1 2 3)) diff --git a/tests/step3_env.mal b/tests/step3_env.mal index ab2aa57569..9487a958c0 100644 --- a/tests/step3_env.mal +++ b/tests/step3_env.mal @@ -29,6 +29,10 @@ mynum MYNUM ;=>222 +;; Check env lookup non-fatal error +(abc 1 2 3) +; .*\'abc\' not found.* + ;; Testing let* (let* (z 9) z) @@ -41,6 +45,9 @@ x ;=>6 (let* (p (+ 2 3) q (+ 2 p)) (+ p q)) ;=>12 +(def! y (let* (z 7) z)) +y +;=>7 ;; Testing outer environment (def! a 4) diff --git a/tests/step5_tco.mal b/tests/step5_tco.mal index 42c7fa421a..0e87b5babc 100644 --- a/tests/step5_tco.mal +++ b/tests/step5_tco.mal @@ -2,6 +2,8 @@ (def! sum2 (fn* (n acc) (if (= n 0) acc (sum2 (- n 1) (+ n acc))))) +;; TODO: test let*, and do for TCO + (sum2 10 0) ;=>55 From 8be49ba8ef24378d03f158d47475863646e96e20 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 22 Sep 2016 22:14:08 -0500 Subject: [PATCH 0152/2308] Basic: switch to python preprocessor. - Adds ON GOTO, ON GOSUB support. - Simplifies REM keep/drop to just yes/no --- Makefile | 1 + basic/Makefile | 4 +- basic/basicpp.py | 132 ++++++++++++++++++++++++++++++++++++++++ basic/step0_repl.in.bas | 2 - 4 files changed, 135 insertions(+), 4 deletions(-) create mode 100755 basic/basicpp.py diff --git a/Makefile b/Makefile index a78842794b..a6631b5444 100644 --- a/Makefile +++ b/Makefile @@ -109,6 +109,7 @@ regress_step9 = $(regress_step8) step9 regress_stepA = $(regress_step9) stepA test_EXCLUDES += test^bash^step5 # never completes at 10,000 +test_EXCLUDES += test^basic^step5 # too slow, and limited to ints of 2^16 test_EXCLUDES += test^logo^step5 # too slow for 10,000 test_EXCLUDES += test^make^step5 # no TCO capability (iteration or recursion) test_EXCLUDES += test^mal^step5 # host impl dependent diff --git a/basic/Makefile b/basic/Makefile index 2ba285b659..bfadfb1e67 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -1,7 +1,7 @@ export KEEP_REM=0 step%.bas: step%.in.bas - ./qb2cbm.sh $< > $@ + ./basicpp.py --number-lines $< > $@ step%.prg: step%.bas cat $< | tr "A-Z" "a-z" > $<.tmp @@ -24,7 +24,7 @@ step6_file.bas: $(STEP4_DEPS) step7_quote.bas: $(STEP4_DEPS) tests/%.bas: tests/%.in.bas - ./qb2cbm.sh $< > $@ + ./basicpp.py --number-lines $< > $@ tests/%.prg: tests/%.bas cat $< | tr "A-Z" "a-z" > $<.tmp diff --git a/basic/basicpp.py b/basic/basicpp.py new file mode 100755 index 0000000000..adae4efd2c --- /dev/null +++ b/basic/basicpp.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python + +from __future__ import print_function +import argparse +import re +import sys + +def debug(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + +def parse_args(): + parser = argparse.ArgumentParser(description='Preprocess Basic code.') + parser.add_argument('infile', type=str, + help='the Basic file to preprocess') + parser.add_argument('--keep-rems', action='store_true', default=False, + help='The type of REMs to keep (0 (none) -> 4 (all)') + parser.add_argument('--keep-blank-lines', action='store_true', default=False, + help='Keep blank lines from the original file') + parser.add_argument('--keep-indent', action='store_true', default=False, + help='Keep line identing') + parser.add_argument('--number-lines', action='store_true', default=False, + help='Number the lines') + parser.add_argument('--keep-labels', action='store_true', default=False, + help='Keep string labels instead of replacing with line numbers') + + return parser.parse_args() + +# pull in include files +def resolve_includes(orig_lines, keep_rems=0): + included = {} + lines = [] + for line in orig_lines: + m = re.match(r"^ *REM \$INCLUDE: '([^']*)' *$", line) + if m and m.group(1) not in included: + f = m.group(1) + if f not in included: + ilines = [l.rstrip() for l in open(f).readlines()] + if keep_rems: lines.append("REM vvv BEGIN '%s' vvv" % f) + lines.extend(ilines) + if keep_rems: lines.append("REM ^^^ END '%s' ^^^" % f) + else: + debug("Ignoring already included file: %s" % f) + else: + lines.append(line) + return lines + +def drop_blank_lines(orig_lines): + lines = [] + for line in orig_lines: + if re.match(r"^\w*$", line): continue + lines.append(line) + return lines + + +def drop_rems(orig_lines): + lines = [] + for line in orig_lines: + if re.match(r"^ *REM", line): + continue + m = re.match(r"^(.*): *REM .*$", line) + if m: + lines.append(m.group(1)) + else: + lines.append(line) + return lines + +def remove_indent(orig_lines): + lines = [] + for line in orig_lines: + m = re.match(r"^ *([^ ].*)$", line) + lines.append(m.group(1)) + return lines + +def number_lines(orig_lines, keep_labels=True): + # number lines + lnum=1 + labels = {} + lines = [] + for line in orig_lines: + if not keep_labels: + m = re.match(r"^ *([^ ]*): *$", line) + if m: + labels[m.groups(1)] = lnum + continue + lines.append("%s %s" % (lnum, line)) + lnum += 1 + + if not keep_labels: + text = "\n".join(lines) + # search for and replace GOTO/GOSUBs + for label, lnum in labels.items(): + text = re.sub(r"(THEN) %s\b" % label, r"THEN %s" % lnum, text) + text = re.sub(r"(ON [^:]* GOTO [^:]*) %s\b" % label, r"\1 %s" % lnum, text) + text = re.sub(r"(ON [^:]* GOSUB [^:]*) %s\b" % label, r"\2 %s" % lnum, text) + text = re.sub(r"(GOSUB) %s\b" % label, r"\1 %s" % lnum, text) + text = re.sub(r"(GOTO) %s\b" % label, r"\1 %s" % lnum, text) + return text.split("\n") + else: + return lines + +if __name__ == '__main__': + args = parse_args() + + debug("Preprocessing basic file '"+args.infile+"'") + + # read in lines + lines = [l.rstrip() for l in open(args.infile).readlines()] + debug("Number of original lines: %s" % len(lines)) + + # pull in include files + lines = resolve_includes(lines, keep_rems=args.keep_rems) + debug("Number of lines after includes: %s" % len(lines)) + + # drop blank lines + if not args.keep_blank_lines: + lines = drop_blank_lines(lines) + debug("Number of lines after dropping blank lines: %s" % len(lines)) + + # keep/drop REMs + if not args.keep_rems: + lines = drop_rems(lines) + debug("Number of lines after dropping REMs: %s" % len(lines)) + + # keep/remove the indenting + if not args.keep_indent: + lines = remove_indent(lines) + + # number lines + if args.number_lines: + lines = number_lines(lines, keep_labels=args.keep_labels) + + print("\n".join(lines)) diff --git a/basic/step0_repl.in.bas b/basic/step0_repl.in.bas index 82c4bca153..cdf2018333 100755 --- a/basic/step0_repl.in.bas +++ b/basic/step0_repl.in.bas @@ -2,8 +2,6 @@ GOTO MAIN REM $INCLUDE: 'readline.in.bas' -REM $INCLUDE: 'debug.in.bas' - REM READ(A$) -> R$ MAL_READ: R$=A$ From 60ef223c3cbc7b11a5e3ef666f38ac6f0a242e1e Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 23 Sep 2016 22:36:17 -0500 Subject: [PATCH 0153/2308] Basic: basicpp adds, other misc. Shaves 3031 bytes. - basicpp.py: - Fix "ON GOTO/GOSUB" label replacment - Add combine line capability - Change "THEN GOTO" to "THEN" - Remove some spaces and unnecessary parens - Restructure several places with multiple "GOTO/GOSUBs" statements into fewer "ON GOTO/GOSUB" statements --- basic/Makefile | 6 +- basic/basicpp.py | 119 ++++++++++---- basic/core.in.bas | 286 +++++++++++++++++++--------------- basic/debug.in.bas | 16 +- basic/env.in.bas | 18 +-- basic/printer.in.bas | 49 +++--- basic/reader.in.bas | 107 ++++++------- basic/readline.in.bas | 12 +- basic/step0_repl.in.bas | 8 +- basic/step1_read_print.in.bas | 16 +- basic/step2_eval.in.bas | 96 ++++++------ basic/step3_env.in.bas | 110 +++++++------ basic/step4_if_fn_do.in.bas | 151 +++++++++--------- basic/step5_tco.in.bas | 153 +++++++++--------- basic/step6_file.in.bas | 173 ++++++++++---------- basic/step7_quote.in.bas | 224 +++++++++++++------------- basic/types.in.bas | 165 ++++++++++---------- 17 files changed, 891 insertions(+), 818 deletions(-) diff --git a/basic/Makefile b/basic/Makefile index bfadfb1e67..06b53d39c9 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -1,7 +1,7 @@ -export KEEP_REM=0 +BASICPP_OPTS = --number-lines --combine-lines step%.bas: step%.in.bas - ./basicpp.py --number-lines $< > $@ + ./basicpp.py $(BASICPP_OPTS) $< > $@ step%.prg: step%.bas cat $< | tr "A-Z" "a-z" > $<.tmp @@ -24,7 +24,7 @@ step6_file.bas: $(STEP4_DEPS) step7_quote.bas: $(STEP4_DEPS) tests/%.bas: tests/%.in.bas - ./basicpp.py --number-lines $< > $@ + ./basicpp.py $(BASICPP_OPTS) $< > $@ tests/%.prg: tests/%.bas cat $< | tr "A-Z" "a-z" > $<.tmp diff --git a/basic/basicpp.py b/basic/basicpp.py index adae4efd2c..59d004fb3d 100755 --- a/basic/basicpp.py +++ b/basic/basicpp.py @@ -18,12 +18,20 @@ def parse_args(): help='Keep blank lines from the original file') parser.add_argument('--keep-indent', action='store_true', default=False, help='Keep line identing') + parser.add_argument('--skip-misc-fixups', action='store_true', default=False, + help='Skip miscellaneous fixup/shrink fixups') parser.add_argument('--number-lines', action='store_true', default=False, help='Number the lines') parser.add_argument('--keep-labels', action='store_true', default=False, help='Keep string labels instead of replacing with line numbers') + parser.add_argument('--combine-lines', action='store_true', default=False, + help='Combine lines using the ":" separator') - return parser.parse_args() + args = parser.parse_args() + if args.combine_lines and args.keep_rems: + parser.error("--combine-lines and --keep-rems are mutually exclusive") + + return args # pull in include files def resolve_includes(orig_lines, keep_rems=0): @@ -71,32 +79,86 @@ def remove_indent(orig_lines): lines.append(m.group(1)) return lines -def number_lines(orig_lines, keep_labels=True): +def misc_fixups(orig_lines): + text = "\n".join(orig_lines) + text = re.sub(r"\bTHEN GOTO\b", r"THEN", text) + return text.split("\n") + +def finalize(lines, args): + labels_lines = {} + lines_labels = {} + # number lines - lnum=1 - labels = {} - lines = [] - for line in orig_lines: - if not keep_labels: - m = re.match(r"^ *([^ ]*): *$", line) - if m: - labels[m.groups(1)] = lnum - continue - lines.append("%s %s" % (lnum, line)) - lnum += 1 - - if not keep_labels: + if args.number_lines: + src_lines = lines + lines = [] + lnum=1 + for line in src_lines: + if not args.keep_labels: + m = re.match(r"^ *([^ ]*): *$", line) + if m: + labels_lines[m.groups(1)[0]] = lnum + lines_labels[lnum] = m.groups(1)[0] + continue + lines.append("%s %s" % (lnum, line)) + lnum += 1 + + if not args.keep_labels: + src_lines = lines text = "\n".join(lines) # search for and replace GOTO/GOSUBs - for label, lnum in labels.items(): - text = re.sub(r"(THEN) %s\b" % label, r"THEN %s" % lnum, text) - text = re.sub(r"(ON [^:]* GOTO [^:]*) %s\b" % label, r"\1 %s" % lnum, text) - text = re.sub(r"(ON [^:]* GOSUB [^:]*) %s\b" % label, r"\2 %s" % lnum, text) - text = re.sub(r"(GOSUB) %s\b" % label, r"\1 %s" % lnum, text) - text = re.sub(r"(GOTO) %s\b" % label, r"\1 %s" % lnum, text) - return text.split("\n") - else: - return lines + for label, lnum in labels_lines.items(): + stext = "" + while stext != text: + stext = text + text = re.sub(r"(THEN) %s\b" % label, r"THEN %s" % lnum, stext) + text = re.sub(r"(ON [^:]* GOTO [^:]*)\b%s\b" % label, r"\g<1>%s" % lnum, text) + text = re.sub(r"(ON [^:]* GOSUB [^:]*)\b%s\b" % label, r"\g<2>%s" % lnum, text) + text = re.sub(r"(GOSUB) %s\b" % label, r"\1 %s" % lnum, text) + text = re.sub(r"(GOTO) %s\b" % label, r"\1 %s" % lnum, text) + lines = text.split("\n") + + if args.combine_lines: + src_lines = lines + lines = [] + pos = 0 + acc_line = "" + while pos < len(src_lines): + line = src_lines[pos] + # TODO: handle args.keep_labels and (not args.number_lines) + m = re.match(r"^([0-9]*) (.*)$", line) + lnum = int(m.group(1)) + rest_line = m.group(2) + + if acc_line == "": + # Starting a new line + acc_line = line + elif lnum in lines_labels: + # This is a GOTO/GOSUB target line so it must be on + # a line by itself + lines.append(acc_line) + acc_line = line + elif re.match(r".*\b(?:GOTO|THEN|RETURN)\b.*", acc_line): + lines.append(acc_line) + acc_line = line + elif len(acc_line) + 1 + len(rest_line) < 80: + # Continue building up the line + acc_line = acc_line + ":" + rest_line + # GOTO/IF/RETURN must be the last things on a line so + # start a new line + if re.match(r".*\b(?:GOTO|THEN|RETURN)\b.*", line): + lines.append(acc_line) + acc_line = "" + else: + # Too long so start a new line + lines.append(acc_line) + acc_line = line + pos += 1 + if acc_line != "": + lines.append(acc_line) + + + return lines if __name__ == '__main__': args = parse_args() @@ -125,8 +187,11 @@ def number_lines(orig_lines, keep_labels=True): if not args.keep_indent: lines = remove_indent(lines) - # number lines - if args.number_lines: - lines = number_lines(lines, keep_labels=args.keep_labels) + # apply some miscellaneous simple fixups/regex transforms + if not args.skip_misc_fixups: + lines = misc_fixups(lines) + + # number lines, drop/keep labels, combine lines + lines = finalize(lines, args) print("\n".join(lines)) diff --git a/basic/core.in.bas b/basic/core.in.bas index 1628cc750b..f32031c571 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -5,72 +5,94 @@ DO_FUNCTION: FF%=Z%(F%,1) REM Get argument values - R%=AR%+1: GOSUB DEREF_R: AA%=R% - R%=Z%(AR%,1)+1: GOSUB DEREF_R: AB%=R% + R%=AR%+1:GOSUB DEREF_R:AA%=R% + R%=Z%(AR%,1)+1:GOSUB DEREF_R:AB%=R% REM Switch on the function number - IF FF%=1 THEN DO_EQUAL_Q - - IF FF%=11 THEN DO_PR_STR - IF FF%=12 THEN DO_STR - IF FF%=13 THEN DO_PRN - IF FF%=14 THEN DO_PRINTLN - IF FF%=16 THEN DO_READ_STRING - IF FF%=17 THEN DO_SLURP - - IF FF%=18 THEN DO_LT - IF FF%=19 THEN DO_LTE - IF FF%=20 THEN DO_GT - IF FF%=21 THEN DO_GTE - IF FF%=22 THEN DO_ADD - IF FF%=23 THEN DO_SUB - IF FF%=24 THEN DO_MULT - IF FF%=25 THEN DO_DIV - - IF FF%=27 THEN DO_LIST - IF FF%=28 THEN DO_LIST_Q - - IF FF%=39 THEN DO_CONS - IF FF%=40 THEN DO_CONCAT - IF FF%=43 THEN DO_FIRST - IF FF%=44 THEN DO_REST - IF FF%=45 THEN DO_EMPTY_Q - IF FF%=46 THEN DO_COUNT - - IF FF%=53 THEN DO_ATOM - IF FF%=54 THEN DO_ATOM_Q - IF FF%=55 THEN DO_DEREF - IF FF%=56 THEN DO_RESET_BANG - IF FF%=57 THEN DO_SWAP_BANG - - IF FF%=58 THEN DO_PR_MEMORY - IF FF%=59 THEN DO_PR_MEMORY_SUMMARY - IF FF%=60 THEN DO_EVAL - ER%=1: ER$="unknown function"+STR$(FF%): RETURN + IF FF%>=61 THEN ER%=1:ER$="unknown function"+STR$(FF%):RETURN + IF FF%>=53 THEN DO_53 + IF FF%>=39 THEN DO_39 + IF FF%>=27 THEN DO_27 + IF FF%>=18 THEN DO_18 + IF FF%>=11 THEN DO_11 + + ON FF% GOTO DO_EQUAL_Q + REM IF FF%=1 THEN DO_EQUAL_Q + + DO_11: + ON FF%-10 GOTO DO_PR_STR,DO_STR,DO_PRN,DO_PRINTLN,DO_READLINE,DO_READ_STRING,DO_SLURP + REM IF FF%=11 THEN DO_PR_STR + REM IF FF%=12 THEN DO_STR + REM IF FF%=13 THEN DO_PRN + REM IF FF%=14 THEN DO_PRINTLN + REM IF FF%=15 THEN DO_READLINE + REM IF FF%=16 THEN DO_READ_STRING + REM IF FF%=17 THEN DO_SLURP + + DO_18: + ON FF%-17 GOTO DO_LT,DO_LTE,DO_GT,DO_GTE,DO_ADD,DO_SUB,DO_MULT,DO_DIV,DO_TIME_MS + REM IF FF%=18 THEN DO_LT + REM IF FF%=19 THEN DO_LTE + REM IF FF%=20 THEN DO_GT + REM IF FF%=21 THEN DO_GTE + REM IF FF%=22 THEN DO_ADD + REM IF FF%=23 THEN DO_SUB + REM IF FF%=24 THEN DO_MULT + REM IF FF%=25 THEN DO_DIV + REM IF FF%=26 THEN DO_TIME_MS + + DO_27: + ON FF%-26 GOTO DO_LIST,DO_LIST_Q + REM IF FF%=27 THEN DO_LIST + REM IF FF%=28 THEN DO_LIST_Q + + DO_39: + ON FF%-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT + REM IF FF%=40 THEN DO_CONS + REM IF FF%=41 THEN DO_CONCAT + REM IF FF%=42 THEN DO_NTH + REM IF FF%=43 THEN DO_FIRST + REM IF FF%=44 THEN DO_REST + REM IF FF%=45 THEN DO_EMPTY_Q + REM IF FF%=46 THEN DO_COUNT + + DO_53: + ON FF%-52 GOTO DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_SWAP_BANG,DO_PR_MEMORY,DO_PR_MEMORY_SUMMARY,DO_EVAL + REM IF FF%=53 THEN DO_ATOM + REM IF FF%=54 THEN DO_ATOM_Q + REM IF FF%=55 THEN DO_DEREF + REM IF FF%=56 THEN DO_RESET_BANG + REM IF FF%=57 THEN DO_SWAP_BANG + + REM IF FF%=58 THEN DO_PR_MEMORY + REM IF FF%=59 THEN DO_PR_MEMORY_SUMMARY + REM IF FF%=60 THEN DO_EVAL DO_EQUAL_Q: - A%=AA%: B%=AB%: GOSUB EQUAL_Q + A%=AA%:B%=AB%:GOSUB EQUAL_Q R%=R%+1 RETURN DO_PR_STR: - AZ%=AR%: PR%=1: SE$=" ": GOSUB PR_STR_SEQ - AS$=R$: T%=4+16: GOSUB STRING + AZ%=AR%:PR%=1:SE$=" ":GOSUB PR_STR_SEQ + AS$=R$:T%=4+16:GOSUB STRING RETURN DO_STR: - AZ%=AR%: PR%=0: SE$="": GOSUB PR_STR_SEQ - AS$=R$: T%=4+16: GOSUB STRING + AZ%=AR%:PR%=0:SE$="":GOSUB PR_STR_SEQ + AS$=R$:T%=4+16:GOSUB STRING RETURN DO_PRN: - AZ%=AR%: PR%=1: SE$=" ": GOSUB PR_STR_SEQ + AZ%=AR%:PR%=1:SE$=" ":GOSUB PR_STR_SEQ PRINT R$ R%=0 RETURN DO_PRINTLN: - AZ%=AR%: PR%=0: SE$=" ": GOSUB PR_STR_SEQ + AZ%=AR%:PR%=0:SE$=" ":GOSUB PR_STR_SEQ PRINT R$ R%=0 RETURN + DO_READLINE: + RETURN DO_READ_STRING: A$=ZS$(Z%(AA%,1)) GOSUB READ_STR @@ -86,11 +108,11 @@ DO_FUNCTION: IF ASC(A$)=10 THEN R$=R$+CHR$(13) IF (ASC(A$)<>10) AND (A$<>"") THEN R$=R$+A$ IF (ST AND 64) THEN GOTO DO_SLURP_DONE - IF (ST AND 255) THEN ER%=-1: ER%="File read error "+STR$(ST): RETURN + IF (ST AND 255) THEN ER%=-1:ER%="File read error "+STR$(ST):RETURN GOTO DO_SLURP_LOOP DO_SLURP_DONE: CLOSE 1 - AS$=R$: T%=4+16: GOSUB STRING + AS$=R$:T%=4+16:GOSUB STRING RETURN DO_LT: @@ -111,51 +133,53 @@ DO_FUNCTION: RETURN DO_ADD: - SZ%=1: GOSUB ALLOC + SZ%=1:GOSUB ALLOC Z%(R%,0)=2+16 Z%(R%,1)=Z%(AA%,1)+Z%(AB%,1) RETURN DO_SUB: - SZ%=1: GOSUB ALLOC + SZ%=1:GOSUB ALLOC Z%(R%,0)=2+16 Z%(R%,1)=Z%(AA%,1)-Z%(AB%,1) RETURN DO_MULT: - SZ%=1: GOSUB ALLOC + SZ%=1:GOSUB ALLOC Z%(R%,0)=2+16 Z%(R%,1)=Z%(AA%,1)*Z%(AB%,1) RETURN DO_DIV: - SZ%=1: GOSUB ALLOC + SZ%=1:GOSUB ALLOC Z%(R%,0)=2+16 Z%(R%,1)=Z%(AA%,1)/Z%(AB%,1) RETURN + DO_TIME_MS: + RETURN DO_LIST: R%=AR% Z%(R%,0)=Z%(R%,0)+16 RETURN DO_LIST_Q: - A%=AA%: GOSUB LIST_Q + A%=AA%:GOSUB LIST_Q R%=R%+1: REM map to mal false/true RETURN DO_CONS: - A%=AA%: B%=AB%: GOSUB CONS + A%=AA%:B%=AB%:GOSUB CONS RETURN DO_CONCAT: REM if empty arguments, return empty list - IF Z%(AR%,1)=0 THEN R%=3: Z%(R%,0)=Z%(R%,0)+16: RETURN + IF Z%(AR%,1)=0 THEN R%=3:Z%(R%,0)=Z%(R%,0)+16:RETURN REM single argument IF Z%(Z%(AR%,1),1)<>0 THEN GOTO DO_CONCAT_MULT REM if single argument and it's a list, return it - IF (Z%(AA%,0)AND15)=6 THEN R%=AA%: Z%(R%,0)=Z%(R%,0)+16: RETURN + IF (Z%(AA%,0)AND15)=6 THEN R%=AA%:Z%(R%,0)=Z%(R%,0)+16:RETURN REM otherwise, copy first element to turn it into a list - B%=AA%+1: GOSUB DEREF_B: REM value to copy - SZ%=2: GOSUB ALLOC - Z%(R%,0)=6+16: Z%(R%,1)=Z%(AA%,1) - Z%(R%+1,0)=14: Z%(R%+1,1)=B% + B%=AA%+1:GOSUB DEREF_B: REM value to copy + SZ%=2:GOSUB ALLOC + Z%(R%,0)=6+16:Z%(R%,1)=Z%(AA%,1) + Z%(R%+1,0)=14:Z%(R%+1,1)=B% REM inc ref count of trailing list part and of copied value Z%(Z%(AA%,1),0)=Z%(Z%(AA%,1),0)+16 Z%(B%,0)=Z%(B%,0)+16 @@ -166,31 +190,33 @@ DO_FUNCTION: CZ%=ZL%: REM save current stack position REM push arguments onto the stack DO_CONCAT_STACK: - R%=AR%+1: GOSUB DEREF_R - ZL%=ZL%+1: ZZ%(ZL%)=R%: REM push sequence + R%=AR%+1:GOSUB DEREF_R + ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push sequence AR%=Z%(AR%,1) IF Z%(AR%,1)<>0 THEN GOTO DO_CONCAT_STACK REM pop last argument as our seq to prepend to - AB%=ZZ%(ZL%): ZL%=ZL%-1 + AB%=ZZ%(ZL%):ZL%=ZL%-1 REM last arg/seq is not copied so we need to inc ref to it Z%(AB%,0)=Z%(AB%,0)+16 DO_CONCAT_LOOP: - IF ZL%=CZ% THEN R%=AB%: RETURN - AA%=ZZ%(ZL%): ZL%=ZL%-1: REM pop off next seq to prepend - A%=AA%: B%=0: C%=-1: GOSUB SLICE + IF ZL%=CZ% THEN R%=AB%:RETURN + AA%=ZZ%(ZL%):ZL%=ZL%-1: REM pop off next seq to prepend + A%=AA%:B%=0:C%=-1:GOSUB SLICE REM release the terminator of new list (we skip over it) - AY%=Z%(R6%,1): GOSUB RELEASE + AY%=Z%(R6%,1):GOSUB RELEASE REM attach new list element before terminator (last actual REM element to the next sequence Z%(R6%,1)=AB% AB%=R% GOTO DO_CONCAT_LOOP + DO_NTH: + RETURN DO_FIRST: IF Z%(AA%,1)=0 THEN R%=0 - IF Z%(AA%,1)<>0 THEN R%=AA%+1: GOSUB DEREF_R + IF Z%(AA%,1)<>0 THEN R%=AA%+1:GOSUB DEREF_R IF R%<>0 THEN Z%(R%,0)=Z%(R%,0)+16 RETURN DO_REST: @@ -203,15 +229,14 @@ DO_FUNCTION: IF Z%(AA%,1)=0 THEN R%=2 RETURN DO_COUNT: - A%=AA%: GOSUB COUNT - R4%=R% - SZ%=1: GOSUB ALLOC + A%=AA%:GOSUB COUNT:R4%=R% + SZ%=1:GOSUB ALLOC Z%(R%,0)=2+16 Z%(R%,1)=R4% RETURN DO_ATOM: - SZ%=1: GOSUB ALLOC + SZ%=1:GOSUB ALLOC Z%(AA%,0)=Z%(AA%,0)+16: REM inc ref cnt of contained value Z%(R%,0)=12+16 Z%(R%,1)=AA% @@ -221,13 +246,13 @@ DO_FUNCTION: IF (Z%(AA%,0)AND15)=12 THEN R%=2 RETURN DO_DEREF: - R%=Z%(AA%,1): GOSUB DEREF_R + R%=Z%(AA%,1):GOSUB DEREF_R Z%(R%,0)=Z%(R%,0)+16 RETURN DO_RESET_BANG: R%=AB% REM release current value - AY%=Z%(AA%,1): GOSUB RELEASE + AY%=Z%(AA%,1):GOSUB RELEASE REM inc ref by 2 for atom ownership and since we are returning it Z%(R%,0)=Z%(R%,0)+32 REM update value @@ -237,11 +262,11 @@ DO_FUNCTION: F%=AB% REM add atom to front of the args list - A%=Z%(AA%,1): B%=Z%(Z%(AR%,1),1): GOSUB CONS + A%=Z%(AA%,1):B%=Z%(Z%(AR%,1),1):GOSUB CONS AR%=R% REM push args for release after - ZL%=ZL%+1: ZZ%(ZL%)=AR% + ZL%=ZL%+1:ZZ%(ZL%)=AR% REM TODO: break this out into APPLY IF (Z%(F%,0)AND15)=9 THEN GOTO DO_SWAP_FUNCTION @@ -249,62 +274,62 @@ DO_FUNCTION: DO_SWAP_FUNCTION: REM push atom - ZL%=ZL%+1: ZZ%(ZL%)=AA% + ZL%=ZL%+1:ZZ%(ZL%)=AA% GOSUB DO_FUNCTION REM pop atom - AA%=ZZ%(ZL%): ZL%=ZL%-1 + AA%=ZZ%(ZL%):ZL%=ZL%-1 REM pop and release args - AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE GOTO DO_SWAP_DONE DO_SWAP_MAL_FUNCTION: REM push current environment for later release - ZL%=ZL%+1: ZZ%(ZL%)=E% + ZL%=ZL%+1:ZZ%(ZL%)=E% REM create new environ using env stored with function - EO%=Z%(F%+1,1): BI%=Z%(F%+1,0): EX%=AR%: GOSUB ENV_NEW_BINDS + EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS REM push atom - ZL%=ZL%+1: ZZ%(ZL%)=AA% + ZL%=ZL%+1:ZZ%(ZL%)=AA% - A%=Z%(F%,1): E%=R%: GOSUB EVAL + A%=Z%(F%,1):E%=R%:GOSUB EVAL REM pop atom - AA%=ZZ%(ZL%): ZL%=ZL%-1 + AA%=ZZ%(ZL%):ZL%=ZL%-1 REM pop and release args - AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE REM pop and release previous env - AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE GOTO DO_SWAP_DONE DO_SWAP_DONE: REM use reset to update the value - AB%=R%: GOSUB DO_RESET_BANG + AB%=R%:GOSUB DO_RESET_BANG REM but decrease ref cnt of return by 1 (not sure why) - AY%=R%: GOSUB RELEASE + AY%=R%:GOSUB RELEASE RETURN DO_PR_MEMORY: - P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + P1%=ZT%:P2%=-1:GOSUB PR_MEMORY RETURN DO_PR_MEMORY_SUMMARY: GOSUB PR_MEMORY_SUMMARY RETURN DO_EVAL: - A%=AA%: E%=RE%: GOSUB EVAL + A%=AA%:E%=RE%:GOSUB EVAL RETURN INIT_CORE_SET_FUNCTION: GOSUB NATIVE_FUNCTION - V%=R%: GOSUB ENV_SET_S + V%=R%:GOSUB ENV_SET_S RETURN REM INIT_CORE_NS(E%) @@ -312,42 +337,45 @@ INIT_CORE_NS: REM create the environment mapping REM must match DO_FUNCTION mappings - K$="=": A%=1: GOSUB INIT_CORE_SET_FUNCTION - - K$="pr-str": A%=11: GOSUB INIT_CORE_SET_FUNCTION - K$="str": A%=12: GOSUB INIT_CORE_SET_FUNCTION - K$="prn": A%=13: GOSUB INIT_CORE_SET_FUNCTION - K$="println": A%=14: GOSUB INIT_CORE_SET_FUNCTION - K$="read-string": A%=16: GOSUB INIT_CORE_SET_FUNCTION - K$="slurp": A%=17: GOSUB INIT_CORE_SET_FUNCTION - - K$="<": A%=18: GOSUB INIT_CORE_SET_FUNCTION - K$="<=": A%=19: GOSUB INIT_CORE_SET_FUNCTION - K$=">": A%=20: GOSUB INIT_CORE_SET_FUNCTION - K$=">=": A%=21: GOSUB INIT_CORE_SET_FUNCTION - K$="+": A%=22: GOSUB INIT_CORE_SET_FUNCTION - K$="-": A%=23: GOSUB INIT_CORE_SET_FUNCTION - K$="*": A%=24: GOSUB INIT_CORE_SET_FUNCTION - K$="/": A%=25: GOSUB INIT_CORE_SET_FUNCTION - - K$="list": A%=27: GOSUB INIT_CORE_SET_FUNCTION - K$="list?": A%=28: GOSUB INIT_CORE_SET_FUNCTION - - K$="cons": A%=39: GOSUB INIT_CORE_SET_FUNCTION - K$="concat": A%=40: GOSUB INIT_CORE_SET_FUNCTION - K$="first": A%=43: GOSUB INIT_CORE_SET_FUNCTION - K$="rest": A%=44: GOSUB INIT_CORE_SET_FUNCTION - K$="empty?": A%=45: GOSUB INIT_CORE_SET_FUNCTION - K$="count": A%=46: GOSUB INIT_CORE_SET_FUNCTION - - K$="atom": A%=53: GOSUB INIT_CORE_SET_FUNCTION - K$="atom?": A%=54: GOSUB INIT_CORE_SET_FUNCTION - K$="deref": A%=55: GOSUB INIT_CORE_SET_FUNCTION - K$="reset!": A%=56: GOSUB INIT_CORE_SET_FUNCTION - K$="swap!": A%=57: GOSUB INIT_CORE_SET_FUNCTION - - K$="pr-memory": A%=58: GOSUB INIT_CORE_SET_FUNCTION - K$="pr-memory-summary": A%=59: GOSUB INIT_CORE_SET_FUNCTION - K$="eval": A%=60: GOSUB INIT_CORE_SET_FUNCTION + K$="=":A%=1:GOSUB INIT_CORE_SET_FUNCTION + + K$="pr-str":A%=11:GOSUB INIT_CORE_SET_FUNCTION + K$="str":A%=12:GOSUB INIT_CORE_SET_FUNCTION + K$="prn":A%=13:GOSUB INIT_CORE_SET_FUNCTION + K$="println":A%=14:GOSUB INIT_CORE_SET_FUNCTION + K$="readline":A%=15:GOSUB INIT_CORE_SET_FUNCTION + K$="read-string":A%=16:GOSUB INIT_CORE_SET_FUNCTION + K$="slurp":A%=17:GOSUB INIT_CORE_SET_FUNCTION + + K$="<":A%=18:GOSUB INIT_CORE_SET_FUNCTION + K$="<=":A%=19:GOSUB INIT_CORE_SET_FUNCTION + K$=">":A%=20:GOSUB INIT_CORE_SET_FUNCTION + K$=">=":A%=21:GOSUB INIT_CORE_SET_FUNCTION + K$="+":A%=22:GOSUB INIT_CORE_SET_FUNCTION + K$="-":A%=23:GOSUB INIT_CORE_SET_FUNCTION + K$="*":A%=24:GOSUB INIT_CORE_SET_FUNCTION + K$="/":A%=25:GOSUB INIT_CORE_SET_FUNCTION + K$="time-ms":A%=26:GOSUB INIT_CORE_SET_FUNCTION + + K$="list":A%=27:GOSUB INIT_CORE_SET_FUNCTION + K$="list?":A%=28:GOSUB INIT_CORE_SET_FUNCTION + + K$="cons":A%=40:GOSUB INIT_CORE_SET_FUNCTION + K$="concat":A%=41:GOSUB INIT_CORE_SET_FUNCTION + K$="nth":A%=42:GOSUB INIT_CORE_SET_FUNCTION + K$="first":A%=43:GOSUB INIT_CORE_SET_FUNCTION + K$="rest":A%=44:GOSUB INIT_CORE_SET_FUNCTION + K$="empty?":A%=45:GOSUB INIT_CORE_SET_FUNCTION + K$="count":A%=46:GOSUB INIT_CORE_SET_FUNCTION + + K$="atom":A%=53:GOSUB INIT_CORE_SET_FUNCTION + K$="atom?":A%=54:GOSUB INIT_CORE_SET_FUNCTION + K$="deref":A%=55:GOSUB INIT_CORE_SET_FUNCTION + K$="reset!":A%=56:GOSUB INIT_CORE_SET_FUNCTION + K$="swap!":A%=57:GOSUB INIT_CORE_SET_FUNCTION + + K$="pr-memory":A%=58:GOSUB INIT_CORE_SET_FUNCTION + K$="pr-memory-summary":A%=59:GOSUB INIT_CORE_SET_FUNCTION + K$="eval":A%=60:GOSUB INIT_CORE_SET_FUNCTION RETURN diff --git a/basic/debug.in.bas b/basic/debug.in.bas index 3a385be8e5..7de2b91968 100644 --- a/basic/debug.in.bas +++ b/basic/debug.in.bas @@ -16,7 +16,7 @@ REM IF P2%"+STR$(P2%); REM PRINT " (ZI%: "+STR$(ZI%)+", ZK%: "+STR$(ZK%)+"):" -REM IF P2%P2% THEN GOTO PR_MEMORY_AFTER_VALUES @@ -37,18 +37,18 @@ REM PR_MEMORY_FREE: REM PRINT ": FREE size: "+STR$((Z%(I,0)AND-16)/16)+", next: "+STR$(Z%(I,1)); REM IF I=ZK% THEN PRINT " (free list start)"; REM PRINT -REM IF (Z%(I,0)AND-16)=32 THEN I=I+1: PRINT " "+STR$(I)+": ---" +REM IF (Z%(I,0)AND-16)=32 THEN I=I+1:PRINT " "+STR$(I)+": ---" REM I=I+1 REM GOTO PR_MEMORY_VALUE_LOOP REM PR_MEMORY_AFTER_VALUES: REM PRINT "ZS% String Memory (ZJ%: "+STR$(ZJ%)+"):" -REM IF ZJ%<=0 THEN PRINT " ---": GOTO PR_MEMORY_SKIP_STRINGS +REM IF ZJ%<=0 THEN PRINT " ---":GOTO PR_MEMORY_SKIP_STRINGS REM FOR I=0 TO ZJ%-1 REM PRINT " "+STR$(I)+": '"+ZS$(I)+"'" REM NEXT I REM PR_MEMORY_SKIP_STRINGS: REM PRINT "ZZ% Stack Memory (ZL%: "+STR$(ZL%)+"):" -REM IF ZL%<0 THEN PRINT " ---": GOTO PR_MEMORY_SKIP_STACK +REM IF ZL%<0 THEN PRINT " ---":GOTO PR_MEMORY_SKIP_STACK REM FOR I=0 TO ZL% REM PRINT " "+STR$(I)+": "+STR$(ZZ%(I)) REM NEXT I @@ -60,11 +60,11 @@ REM PR_OBJECT(P1%) -> nil PR_OBJECT: RC%=0 - RC%=RC%+1: ZL%=ZL%+1: ZZ%(ZL%)=P1% + RC%=RC%+1:ZL%=ZL%+1:ZZ%(ZL%)=P1% PR_OBJ_LOOP: IF RC%=0 THEN RETURN - I=ZZ%(ZL%): RC%=RC%-1: ZL%=ZL%-1 + I=ZZ%(ZL%):RC%=RC%-1:ZL%=ZL%-1 P2%=Z%(I,0)AND15 PRINT " "+STR$(I); @@ -74,6 +74,6 @@ PR_OBJECT: IF P2%=5 THEN PRINT " "+ZS$(Z%(I,1))+""; PRINT IF P2%<=5 OR P2%=9 THEN GOTO PR_OBJ_LOOP - IF Z%(I,1)<>0 THEN RC%=RC%+1: ZL%=ZL%+1: ZZ%(ZL%)=Z%(I,1) - IF P2%>=6 AND P2%<=8 THEN RC%=RC%+1: ZL%=ZL%+1: ZZ%(ZL%)=I+1 + IF Z%(I,1)<>0 THEN RC%=RC%+1:ZL%=ZL%+1:ZZ%(ZL%)=Z%(I,1) + IF P2%>=6 AND P2%<=8 THEN RC%=RC%+1:ZL%=ZL%+1:ZZ%(ZL%)=I+1 GOTO PR_OBJ_LOOP diff --git a/basic/env.in.bas b/basic/env.in.bas index 45a6415763..cf6b41fa75 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -6,7 +6,7 @@ ENV_NEW: ET%=R% REM set the outer and data pointer - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC Z%(R%,0)=13+16 Z%(R%,1)=ET% Z%(R%+1,0)=13 @@ -22,16 +22,16 @@ ENV_NEW_BINDS: E%=R% REM process bindings ENV_NEW_BINDS_LOOP: - IF Z%(BI%,1)=0 THEN R%=E%: RETURN + IF Z%(BI%,1)=0 THEN R%=E%:RETURN REM get/deref the key from BI% - R%=BI%+1: GOSUB DEREF_R + R%=BI%+1:GOSUB DEREF_R K%=R% - IF ZS$(Z%(K%,1))="&" THEN EVAL_NEW_BINDS_VARGS: + IF ZS$(Z%(K%,1))="&" THEN GOTO EVAL_NEW_BINDS_VARGS EVAL_NEW_BINDS_1x1: REM get/deref the key from EX% - R%=EX%+1: GOSUB DEREF_R + R%=EX%+1:GOSUB DEREF_R V%=R% REM set the binding in the environment data GOSUB ENV_SET @@ -43,7 +43,7 @@ ENV_NEW_BINDS: EVAL_NEW_BINDS_VARGS: REM get/deref the key from next element of BI% BI%=Z%(BI%,1) - R%=BI%+1: GOSUB DEREF_R + R%=BI%+1:GOSUB DEREF_R K%=R% REM the value is the remaining list in EX% V%=EX% @@ -76,7 +76,7 @@ ENV_FIND: REM More efficient to use GET for value (R%) and contains? (T3%) GOSUB HASHMAP_GET REM if we found it, save value in T4% for ENV_GET - IF T3%=1 THEN T4%=R%: GOTO ENV_FIND_DONE + IF T3%=1 THEN T4%=R%:GOTO ENV_FIND_DONE EF%=Z%(EF%+1,1): REM get outer environment IF EF%<>-1 THEN GOTO ENV_FIND_LOOP ENV_FIND_DONE: @@ -86,7 +86,7 @@ ENV_FIND: REM ENV_GET(E%, K%) -> R% ENV_GET: GOSUB ENV_FIND - IF R%=-1 THEN R%=0: ER%=1: ER$="'"+ZS$(Z%(K%,1))+"' not found": RETURN - R%=T4%: GOSUB DEREF_R + IF R%=-1 THEN R%=0:ER%=1:ER$="'"+ZS$(Z%(K%,1))+"' not found":RETURN + R%=T4%:GOSUB DEREF_R Z%(R%,0)=Z%(R%,0)+16 RETURN diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 2be152ac0d..aa00b3cba8 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -4,25 +4,19 @@ PR_STR: PR_STR_RECUR: T%=Z%(AZ%,0)AND15 REM PRINT "AZ%: "+STR$(AZ%)+", T%: "+STR$(T%)+", V%: "+STR$(Z%(AZ%,1)) - IF T%=14 THEN AZ%=Z%(AZ%,1): GOTO PR_STR_RECUR - IF T%=0 THEN R$="nil": RETURN - IF (T%=1) AND (Z%(AZ%,1)=0) THEN R$="false": RETURN - IF (T%=1) AND (Z%(AZ%,1)=1) THEN R$="true": RETURN - IF T%=2 THEN PR_INTEGER - IF (T%=4) AND (PR%=0) THEN PR_STRING - IF (T%=4) AND (PR%=1) THEN PR_STRING_READABLY - IF T%=5 THEN PR_SYMBOL - IF T%=6 THEN PR_SEQ - IF T%=7 THEN PR_SEQ - IF T%=8 THEN PR_SEQ - IF T%=9 THEN PR_FUNCTION - IF T%=10 THEN PR_MAL_FUNCTION - IF T%=12 THEN PR_ATOM - IF T%=13 THEN PR_ENV - IF T%=15 THEN PR_FREE - R$="#" - RETURN + IF T%=0 THEN R$="nil":RETURN + ON T% GOTO PR_BOOLEAN,PR_INTEGER,PR_UNKNOWN,PR_STRING,PR_SYMBOL,PR_SEQ,PR_SEQ,PR_SEQ,PR_FUNCTION,PR_MAL_FUNCTION,PR_UNKNOWN,PR_ATOM,PR_ENV,PR_RECUR,PR_FREE + PR_UNKNOWN: + R$="#" + RETURN + PR_RECUR: + AZ%=Z%(AZ%,1) + GOTO PR_STR_RECUR + PR_BOOLEAN: + R$="true" + IF Z%(AZ%,1)=0 THEN R$="false" + RETURN PR_INTEGER: T5%=Z%(AZ%,1) R$=STR$(T5%) @@ -31,13 +25,14 @@ PR_STR: R$=RIGHT$(R$, LEN(R$)-1) RETURN PR_STRING: + IF PR%=1 THEN PR_STRING_READABLY R$=ZS$(Z%(AZ%,1)) RETURN PR_STRING_READABLY: R$=ZS$(Z%(AZ%,1)) - S1$=CHR$(92): S2$=CHR$(92)+CHR$(92): GOSUB REPLACE: REM escape backslash - S1$=CHR$(34): S2$=CHR$(92)+CHR$(34): GOSUB REPLACE: REM escape quotes - S1$=CHR$(13): S2$=CHR$(92)+"n": GOSUB REPLACE: REM escape newlines + S1$=CHR$(92):S2$=CHR$(92)+CHR$(92):GOSUB REPLACE: REM escape backslash + S1$=CHR$(34):S2$=CHR$(92)+CHR$(34):GOSUB REPLACE: REM escape quotes + S1$=CHR$(13):S2$=CHR$(92)+"n":GOSUB REPLACE: REM escape newlines R$=CHR$(34)+R$+CHR$(34) RETURN PR_SYMBOL: @@ -56,7 +51,7 @@ PR_STR: AZ%=AZ%+1 GOSUB PR_STR_RECUR REM if we just rendered a non-sequence, then append it - IF (T%<6) OR (T%>8) THEN RR$=RR$+R$ + IF T%<6 OR T%>8 THEN RR$=RR$+R$ REM restore current seq type T%=ZZ%(ZL%-1) REM Go to next list element @@ -80,13 +75,13 @@ PR_STR: RETURN PR_MAL_FUNCTION: T1%=AZ% - AZ%=Z%(T1%+1,0): GOSUB PR_STR_RECUR + AZ%=Z%(T1%+1,0):GOSUB PR_STR_RECUR T7$="(fn* "+R$ - AZ%=Z%(T1%,1): GOSUB PR_STR_RECUR + AZ%=Z%(T1%,1):GOSUB PR_STR_RECUR R$=T7$+" "+R$+")" RETURN PR_ATOM: - AZ%=Z%(AZ%,1): GOSUB PR_STR_RECUR + AZ%=Z%(AZ%,1):GOSUB PR_STR_RECUR R$="(atom "+R$+")" RETURN PR_ENV: @@ -101,8 +96,8 @@ PR_STR_SEQ: T9%=AZ% R1$="" PR_STR_SEQ_LOOP: - IF Z%(T9%,1)=0 THEN R$=R1$: RETURN - AZ%=T9%+1: GOSUB PR_STR + IF Z%(T9%,1)=0 THEN R$=R1$:RETURN + AZ%=T9%+1:GOSUB PR_STR REM goto the next sequence element T9%=Z%(T9%,1) IF Z%(T9%,1)=0 THEN R1$=R1$+R$ diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 03cf5b5150..96d9040058 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -3,12 +3,10 @@ READ_TOKEN: CUR%=IDX% REM PRINT "READ_TOKEN: "+STR$(CUR%)+", "+MID$(A$,CUR%,1) T$=MID$(A$,CUR%,1) - IF T$="(" OR T$=")" THEN RETURN - IF T$="[" OR T$="]" THEN RETURN - IF T$="{" OR T$="}" THEN RETURN - IF (T$="'") OR (T$="`") OR (T$="@") THEN RETURN - IF (T$="~") AND NOT MID$(A$,CUR%+1,1)="@" THEN RETURN - S1=0: S2=0: REM S1: INSTRING?, S2: ESCAPED? + IF T$="(" OR T$=")" OR T$="[" OR T$="]" OR T$="{" OR T$="}" THEN RETURN + IF T$="'" OR T$="`" OR T$="@" THEN RETURN + IF T$="~" AND NOT MID$(A$,CUR%+1,1)="@" THEN RETURN + S1=0:S2=0: REM S1: INSTRING?, S2: ESCAPED? IF T$=CHR$(34) THEN S1=1 CUR%=CUR%+1 READ_TOKEN_LOOP: @@ -17,16 +15,14 @@ READ_TOKEN: IF S2 THEN GOTO READ_TOKEN_CONT IF S1 THEN GOTO READ_TOKEN_CONT IF CH$=" " OR CH$="," THEN RETURN - IF CH$="(" OR CH$=")" THEN RETURN - IF CH$="[" OR CH$="]" THEN RETURN - IF CH$="{" OR CH$="}" THEN RETURN + IF CH$="(" OR CH$=")" OR CH$="[" OR CH$="]" OR CH$="{" OR CH$="}" THEN RETURN READ_TOKEN_CONT: T$=T$+CH$ IF T$="~@" THEN RETURN CUR%=CUR%+1 - IF S1 AND S2 THEN S2=0: GOTO READ_TOKEN_LOOP - IF S1 AND (S2=0) AND (CH$=CHR$(92)) THEN S2=1: GOTO READ_TOKEN_LOOP - IF S1 AND (S2=0) AND (CH$=CHR$(34)) THEN RETURN + IF S1 AND S2 THEN S2=0:GOTO READ_TOKEN_LOOP + IF S1 AND S2=0 AND CH$=CHR$(92) THEN S2=1:GOTO READ_TOKEN_LOOP + IF S1 AND S2=0 AND CH$=CHR$(34) THEN RETURN GOTO READ_TOKEN_LOOP SKIP_SPACES: @@ -45,30 +41,30 @@ READ_FORM: IF ER% THEN RETURN GOSUB SKIP_SPACES GOSUB READ_TOKEN - IF T$="" AND SD%>0 THEN ER$="unexpected EOF": GOTO READ_FORM_ABORT + IF T$="" AND SD%>0 THEN ER$="unexpected EOF":GOTO READ_FORM_ABORT REM PRINT "READ_FORM T$: ["+T$+"]" - IF T$="" THEN R%=0: GOTO READ_FORM_DONE - IF T$="nil" THEN T%=0: GOTO READ_NIL_BOOL - IF T$="false" THEN T%=1: GOTO READ_NIL_BOOL - IF T$="true" THEN T%=2: GOTO READ_NIL_BOOL - IF T$="'" THEN AS$="quote": GOTO READ_MACRO - IF T$="`" THEN AS$="quasiquote": GOTO READ_MACRO - IF T$="~" THEN AS$="unquote": GOTO READ_MACRO - IF T$="~@" THEN AS$="splice-unquote": GOTO READ_MACRO - IF T$="@" THEN AS$="deref": GOTO READ_MACRO + IF T$="" THEN R%=0:GOTO READ_FORM_DONE + IF T$="nil" THEN T%=0:GOTO READ_NIL_BOOL + IF T$="false" THEN T%=1:GOTO READ_NIL_BOOL + IF T$="true" THEN T%=2:GOTO READ_NIL_BOOL + IF T$="'" THEN AS$="quote":GOTO READ_MACRO + IF T$="`" THEN AS$="quasiquote":GOTO READ_MACRO + IF T$="~" THEN AS$="unquote":GOTO READ_MACRO + IF T$="~@" THEN AS$="splice-unquote":GOTO READ_MACRO + IF T$="@" THEN AS$="deref":GOTO READ_MACRO CH$=MID$(T$,1,1) REM PRINT "CH$: ["+CH$+"]("+STR$(ASC(CH$))+")" - IF (CH$=";") THEN R%=0: GOTO READ_TO_EOL - IF CH$>="0" AND CH$ <= "9" THEN READ_NUMBER - IF CH$="-" THEN READ_SYMBOL_MAYBE - - IF CH$=CHR$(34) THEN READ_STRING - IF CH$="(" THEN T%=6: GOTO READ_SEQ - IF CH$=")" THEN T%=6: GOTO READ_SEQ_END - IF CH$="[" THEN T%=7: GOTO READ_SEQ - IF CH$="]" THEN T%=7: GOTO READ_SEQ_END - IF CH$="{" THEN T%=8: GOTO READ_SEQ - IF CH$="}" THEN T%=8: GOTO READ_SEQ_END + IF (CH$=";") THEN R%=0:GOTO READ_TO_EOL + IF CH$>="0" AND CH$<="9" THEN GOTO READ_NUMBER + IF CH$="-" THEN GOTO READ_SYMBOL_MAYBE + + IF CH$=CHR$(34) THEN GOTO READ_STRING + IF CH$="(" THEN T%=6:GOTO READ_SEQ + IF CH$=")" THEN T%=6:GOTO READ_SEQ_END + IF CH$="[" THEN T%=7:GOTO READ_SEQ + IF CH$="]" THEN T%=7:GOTO READ_SEQ_END + IF CH$="{" THEN T%=8:GOTO READ_SEQ + IF CH$="}" THEN T%=8:GOTO READ_SEQ_END GOTO READ_SYMBOL READ_TO_EOL: @@ -78,54 +74,48 @@ READ_FORM: GOTO READ_TO_EOL READ_NIL_BOOL: REM PRINT "READ_NIL_BOOL" - SZ%=1: GOSUB ALLOC + SZ%=1:GOSUB ALLOC Z%(R%,0)=14+16 Z%(R%,1)=T% GOTO READ_FORM_DONE READ_NUMBER: REM PRINT "READ_NUMBER" - SZ%=1: GOSUB ALLOC + SZ%=1:GOSUB ALLOC Z%(R%,0)=2+16 Z%(R%,1)=VAL(T$) GOTO READ_FORM_DONE READ_MACRO: IDX%=IDX%+LEN(T$) - T%=5: GOSUB STRING: REM AS$ set above + T%=5:GOSUB STRING: REM AS$ set above REM to call READ_FORM recursively, SD% needs to be saved, set to REM 0 for the call and then restored afterwards. - ZL%=ZL%+2: ZZ%(ZL%-1)=SD%: ZZ%(ZL%)=R%: REM push SD% and symbol - SD%=0: GOSUB READ_FORM: B1%=R% - SD%=ZZ%(ZL%-1): B2%=ZZ%(ZL%): ZL%=ZL%-2: REM pop SD%, pop symbol into B2% - -REM AZ%=R%: PR%=1: GOSUB PR_STR -REM PRINT "obj: ["+R$+"] ("+STR$(R%)+")" + ZL%=ZL%+2:ZZ%(ZL%-1)=SD%:ZZ%(ZL%)=R%: REM push SD% and symbol + SD%=0:GOSUB READ_FORM:B1%=R% + SD%=ZZ%(ZL%-1):B2%=ZZ%(ZL%):ZL%=ZL%-2: REM pop SD%, pop symbol into B2% GOSUB LIST2 - AY%=B1%: GOSUB RELEASE: REM release value, list has ownership -REM -REM AZ%=R%: PR%=1: GOSUB PR_STR -REM PRINT "list: ["+R$+"] ("+STR$(R%)+")" + AY%=B1%:GOSUB RELEASE: REM release value, list has ownership T$="" GOTO READ_FORM_DONE READ_STRING: REM PRINT "READ_STRING" T7$=MID$(T$,LEN(T$),1) - IF T7$<>CHR$(34) THEN ER$="expected '"+CHR$(34)+"'": GOTO READ_FORM_ABORT + IF T7$<>CHR$(34) THEN ER$="expected '"+CHR$(34)+"'":GOTO READ_FORM_ABORT R$=MID$(T$,2,LEN(T$)-2) - S1$=CHR$(92)+CHR$(34): S2$=CHR$(34): GOSUB REPLACE: REM unescape quotes - S1$=CHR$(92)+"n": S2$=CHR$(13): GOSUB REPLACE: REM unescape newlines - S1$=CHR$(92)+CHR$(92): S2$=CHR$(92): GOSUB REPLACE: REM unescape backslashes + S1$=CHR$(92)+CHR$(34):S2$=CHR$(34):GOSUB REPLACE: REM unescape quotes + S1$=CHR$(92)+"n":S2$=CHR$(13):GOSUB REPLACE: REM unescape newlines + S1$=CHR$(92)+CHR$(92):S2$=CHR$(92):GOSUB REPLACE: REM unescape backslashes REM intern string value - AS$=R$: T%=4+16: GOSUB STRING + AS$=R$:T%=4+16:GOSUB STRING GOTO READ_FORM_DONE READ_SYMBOL_MAYBE: CH$=MID$(T$,2,1) - IF CH$>="0" AND CH$<="9" THEN READ_NUMBER + IF CH$>="0" AND CH$<="9" THEN GOTO READ_NUMBER READ_SYMBOL: REM PRINT "READ_SYMBOL" - AS$=T$: T%=5+16: GOSUB STRING + AS$=T$:T%=5+16:GOSUB STRING GOTO READ_FORM_DONE READ_SEQ: @@ -133,7 +123,7 @@ REM PRINT "list: ["+R$+"] ("+STR$(R%)+")" SD%=SD%+1: REM increase read sequence depth REM allocate first sequence entry and space for value - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC REM set reference value/pointer to new embedded sequence IF SD%>1 THEN Z%(ZZ%(ZL%)+1,1)=R% @@ -159,8 +149,8 @@ REM PRINT "list: ["+R$+"] ("+STR$(R%)+")" READ_SEQ_END: REM PRINT "READ_SEQ_END" - IF SD%=0 THEN ER$="unexpected '"+CH$+"'": GOTO READ_FORM_ABORT - IF ZZ%(ZL%-1)<>T% THEN ER$="sequence mismatch": GOTO READ_FORM_ABORT + IF SD%=0 THEN ER$="unexpected '"+CH$+"'":GOTO READ_FORM_ABORT + IF ZZ%(ZL%-1)<>T% THEN ER$="sequence mismatch":GOTO READ_FORM_ABORT SD%=SD%-1: REM decrease read sequence depth R%=ZZ%(ZL%-2): REM ptr to start of sequence to return T%=ZZ%(ZL%-1): REM type prior to recur @@ -178,7 +168,7 @@ REM PRINT "list: ["+R$+"] ("+STR$(R%)+")" REM PRINT "READ_FORM_DONE next list entry" REM allocate new sequence entry and space for value - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC REM previous element T7%=ZZ%(ZL%) @@ -207,11 +197,10 @@ REM PRINT "list: ["+R$+"] ("+STR$(R%)+")" IF SD%=0 THEN RETURN ZL%=ZL%-3: REM pop previous, type, and start off the stack SD%=SD%-1 - IF SD%=0 THEN AY%=ZZ%(ZL%+1): GOSUB RELEASE + IF SD%=0 THEN AY%=ZZ%(ZL%+1):GOSUB RELEASE GOTO READ_FORM_ABORT_UNWIND - REM READ_STR(A$) -> R% READ_STR: IDX%=1 diff --git a/basic/readline.in.bas b/basic/readline.in.bas index f53362961d..945684dffa 100644 --- a/basic/readline.in.bas +++ b/basic/readline.in.bas @@ -3,15 +3,15 @@ READLINE: EOF=0 PROMPT$=A$ PRINT PROMPT$; - CH$="": LI$="": CH=0 + CH$="":LI$="":CH=0 READCH: - GET CH$: IF CH$="" THEN READCH + GET CH$:IF CH$="" THEN GOTO READCH CH=ASC(CH$) REM PRINT CH - IF (CH=4 OR CH=0) THEN EOF=1: GOTO RL_DONE: REM EOF - IF (CH=127) OR (CH=20) THEN GOSUB RL_BACKSPACE - IF (CH=127) OR (CH=20) THEN GOTO READCH - IF (CH<32 OR CH>127) AND CH<>13 THEN READCH + IF CH=4 OR CH=0 THEN EOF=1:GOTO RL_DONE: REM EOF + IF CH=127 OR CH=20 THEN GOSUB RL_BACKSPACE + IF CH=127 OR CH=20 THEN GOTO READCH + IF (CH<32 OR CH>127) AND CH<>13 THEN GOTO READCH PRINT CH$; IF LEN(LI$)<255 AND CH$<>CHR$(13) THEN LI$=LI$+CH$ IF LEN(LI$)<255 AND CH$<>CHR$(13) THEN GOTO READCH diff --git a/basic/step0_repl.in.bas b/basic/step0_repl.in.bas index cdf2018333..a9636564bb 100755 --- a/basic/step0_repl.in.bas +++ b/basic/step0_repl.in.bas @@ -20,17 +20,17 @@ MAL_PRINT: REM REP(A$) -> R$ REP: GOSUB MAL_READ - A%=R%: GOSUB EVAL - A%=R%: GOSUB MAL_PRINT + A%=R%:GOSUB EVAL + A%=R%:GOSUB MAL_PRINT RETURN REM MAIN program MAIN: REPL_LOOP: - A$="user> ": GOSUB READLINE: REM call input parser + A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$: GOSUB REP: REM call REP + A$=R$:GOSUB REP: REM call REP PRINT R$ GOTO REPL_LOOP diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index 240013b21c..415fd06df5 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -19,7 +19,7 @@ EVAL: REM PRINT(A%) -> R$ MAL_PRINT: - AZ%=A%: PR%=1: GOSUB PR_STR + AZ%=A%:PR%=1:GOSUB PR_STR RETURN REM REP(A$) -> R$ @@ -27,15 +27,15 @@ REP: GOSUB MAL_READ IF ER%<>0 THEN GOTO REP_DONE - A%=R%: GOSUB EVAL + A%=R%:GOSUB EVAL IF ER%<>0 THEN GOTO REP_DONE - A%=R%: GOSUB MAL_PRINT + A%=R%:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from EVAL - AY%=R%: GOSUB RELEASE + AY%=R%:GOSUB RELEASE R$=RT$ RETURN @@ -46,12 +46,12 @@ MAIN: ZT%=ZI%: REM top of memory after base repl_env REPL_LOOP: - A$="user> ": GOSUB READLINE: REM call input parser + A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$: GOSUB REP: REM call REP + A$=R$:GOSUB REP: REM call REP - IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP + IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -62,6 +62,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0: ER$="" + ER%=0:ER$="" RETURN diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index d05fd4e4ae..3c1c2469cf 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -17,33 +17,31 @@ EVAL_AST: LV%=LV%+1 REM push A% and E% on the stack - ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% IF ER%<>0 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A T%=Z%(A%,0)AND15 - IF T%=5 THEN EVAL_AST_SYMBOL - IF T%=6 THEN EVAL_AST_SEQ - IF T%=7 THEN EVAL_AST_SEQ - IF T%=8 THEN EVAL_AST_SEQ + IF T%=5 THEN GOTO EVAL_AST_SYMBOL + IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R%=A%: GOSUB DEREF_R + R%=A%:GOSUB DEREF_R Z%(R%,0)=Z%(R%,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - HM%=E%: K%=A%: GOSUB HASHMAP_GET + HM%=E%:K%=A%:GOSUB HASHMAP_GET GOSUB DEREF_R - IF T3%=0 THEN ER%=1: ER$="'"+ZS$(Z%(A%,1))+"' not found": GOTO EVAL_AST_RETURN + IF T3%=0 THEN ER%=1:ER$="'"+ZS$(Z%(A%,1))+"' not found":GOTO EVAL_AST_RETURN Z%(R%,0)=Z%(R%,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SEQ: REM allocate the first entry - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC REM make space on the stack ZL%=ZL%+4 @@ -75,13 +73,13 @@ EVAL_AST: GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: - R%=A%+1: GOSUB DEREF_R: REM deref to target of referred entry + R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: REM call EVAL for each entry - A%=A%+1: GOSUB EVAL + A%=A%+1:GOSUB EVAL A%=A%-1 GOSUB DEREF_R: REM deref to target of evaluated entry @@ -93,7 +91,7 @@ EVAL_AST: IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC REM update previous sequence entry value to point to new entry Z%(ZZ%(ZL%),1)=R% @@ -113,7 +111,7 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A% and E% off the stack - E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 LV%=LV%-1 RETURN @@ -123,7 +121,7 @@ EVAL: LV%=LV%+1: REM track basic return stack level REM push A% and E% on the stack - ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% REM AZ%=A%: GOSUB PR_STR REM PRINT "EVAL: "+R$+"("+STR$(A%)+"), LV%:"+STR$(LV%) @@ -138,7 +136,7 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R% THEN R%=A%: Z%(R%,0)=Z%(R%,0)+16: GOTO EVAL_RETURN + IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN EVAL_INVOKE: GOSUB EVAL_AST @@ -149,15 +147,15 @@ EVAL: F%=R%+1 AR%=Z%(R%,1): REM rest - R%=F%: GOSUB DEREF_R: F%=R% - IF (Z%(F%,0)AND15)<>9 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + R%=F%:GOSUB DEREF_R:F%=R% + IF (Z%(F%,0)AND15)<>9 THEN ER%=1:ER$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION - AY%=R3%: GOSUB RELEASE + AY%=R3%:GOSUB RELEASE GOTO EVAL_RETURN EVAL_RETURN: REM an error occured, free up any new value - IF ER%=1 THEN AY%=R%: GOSUB RELEASE + IF ER%=1 THEN AY%=R%:GOSUB RELEASE LV%=LV%-1: REM track basic return stack level @@ -166,33 +164,33 @@ EVAL: TA%=FRE(0) REM pop A% and E% off the stack - E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 RETURN REM DO_FUNCTION(F%, AR%) DO_FUNCTION: - AZ%=F%: GOSUB PR_STR + AZ%=F%:GOSUB PR_STR F$=R$ - AZ%=AR%: GOSUB PR_STR + AZ%=AR%:GOSUB PR_STR AR$=R$ REM Get the function number FF%=Z%(F%,1) REM Get argument values - R%=AR%+1: GOSUB DEREF_R: AA%=Z%(R%,1) - R%=Z%(AR%,1)+1: GOSUB DEREF_R: AB%=Z%(R%,1) + R%=AR%+1:GOSUB DEREF_R:AA%=Z%(R%,1) + R%=Z%(AR%,1)+1:GOSUB DEREF_R:AB%=Z%(R%,1) REM Allocate the return value - SZ%=1: GOSUB ALLOC + SZ%=1:GOSUB ALLOC REM Switch on the function number - IF FF%=1 THEN DO_ADD - IF FF%=2 THEN DO_SUB - IF FF%=3 THEN DO_MULT - IF FF%=4 THEN DO_DIV - ER%=1: ER$="unknown function"+STR$(FF%): RETURN + IF FF%=1 THEN GOTO DO_ADD + IF FF%=2 THEN GOTO DO_SUB + IF FF%=3 THEN GOTO DO_MULT + IF FF%=4 THEN GOTO DO_DIV + ER%=1:ER$="unknown function"+STR$(FF%):RETURN DO_ADD: Z%(R%,0)=2+16 @@ -216,28 +214,28 @@ DO_FUNCTION: REM PRINT(A%) -> R$ MAL_PRINT: - AZ%=A%: PR%=1: GOSUB PR_STR + AZ%=A%:PR%=1:GOSUB PR_STR RETURN REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - R1%=0: R2%=0 + R1%=0:R2%=0 GOSUB MAL_READ R1%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: E%=RE%: GOSUB EVAL + A%=R%:E%=RE%:GOSUB EVAL R2%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: GOSUB MAL_PRINT + A%=R%:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2%<>0 THEN AY%=R2%: GOSUB RELEASE - IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE R$=RT$ RETURN @@ -248,33 +246,33 @@ MAIN: LV%=0 REM create repl_env - GOSUB HASHMAP: RE%=R% + GOSUB HASHMAP:RE%=R% REM + function - A%=1: GOSUB NATIVE_FUNCTION - HM%=RE%: K$="+": V%=R%: GOSUB ASSOC1_S: RE%=R% + A%=1:GOSUB NATIVE_FUNCTION + HM%=RE%:K$="+":V%=R%:GOSUB ASSOC1_S:RE%=R% REM - function - A%=2: GOSUB NATIVE_FUNCTION - HM%=RE%: K$="-": V%=R%: GOSUB ASSOC1_S: RE%=R% + A%=2:GOSUB NATIVE_FUNCTION + HM%=RE%:K$="-":V%=R%:GOSUB ASSOC1_S:RE%=R% REM * function - A%=3: GOSUB NATIVE_FUNCTION - HM%=RE%: K$="*": V%=R%: GOSUB ASSOC1_S: RE%=R% + A%=3:GOSUB NATIVE_FUNCTION + HM%=RE%:K$="*":V%=R%:GOSUB ASSOC1_S:RE%=R% REM / function - A%=4: GOSUB NATIVE_FUNCTION - HM%=RE%: K$="/": V%=R%: GOSUB ASSOC1_S: RE%=R% + A%=4:GOSUB NATIVE_FUNCTION + HM%=RE%:K$="/":V%=R%:GOSUB ASSOC1_S:RE%=R% ZT%=ZI%: REM top of memory after base repl_env REPL_LOOP: - A$="user> ": GOSUB READLINE: REM call input parser + A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$: GOSUB REP: REM call REP + A$=R$:GOSUB REP: REM call REP - IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP + IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -285,6 +283,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0: ER$="" + ER%=0:ER$="" RETURN diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index a0f7395567..f661cb104e 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -18,30 +18,28 @@ EVAL_AST: LV%=LV%+1 REM push A% and E% on the stack - ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% IF ER%<>0 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A T%=Z%(A%,0)AND15 - IF T%=5 THEN EVAL_AST_SYMBOL - IF T%=6 THEN EVAL_AST_SEQ - IF T%=7 THEN EVAL_AST_SEQ - IF T%=8 THEN EVAL_AST_SEQ + IF T%=5 THEN GOTO EVAL_AST_SYMBOL + IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R%=A%: GOSUB DEREF_R + R%=A%:GOSUB DEREF_R Z%(R%,0)=Z%(R%,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K%=A%: GOSUB ENV_GET + K%=A%:GOSUB ENV_GET GOTO EVAL_AST_RETURN EVAL_AST_SEQ: REM allocate the first entry - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC REM make space on the stack ZL%=ZL%+4 @@ -73,13 +71,13 @@ EVAL_AST: GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: - R%=A%+1: GOSUB DEREF_R: REM deref to target of referred entry + R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: REM call EVAL for each entry - A%=A%+1: GOSUB EVAL + A%=A%+1:GOSUB EVAL A%=A%-1 GOSUB DEREF_R: REM deref to target of evaluated entry @@ -91,7 +89,7 @@ EVAL_AST: IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC REM update previous sequence entry value to point to new entry Z%(ZZ%(ZL%),1)=R% @@ -106,7 +104,7 @@ EVAL_AST: REM if no error, get return value (new seq) IF ER%=0 THEN R%=ZZ%(ZL%-1) REM otherwise, free the return value and return nil - IF ER%<>0 THEN R%=0: AY%=ZZ%(ZL%-1): GOSUB RELEASE + IF ER%<>0 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE REM pop previous, return, index and type ZL%=ZL%-4 @@ -114,7 +112,7 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A% and E% off the stack - E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 LV%=LV%-1 RETURN @@ -124,7 +122,7 @@ EVAL: LV%=LV%+1: REM track basic return stack level REM push A% and E% on the stack - ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% REM AZ%=A%: GOSUB PR_STR REM PRINT "EVAL: "+R$+"("+STR$(A%)+"), LV%:"+STR$(LV%) @@ -139,10 +137,10 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R% THEN R%=A%: Z%(R%,0)=Z%(R%,0)+16: GOTO EVAL_RETURN + IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN A0%=A%+1 - R%=A0%: GOSUB DEREF_R: A0%=R% + R%=A0%:GOSUB DEREF_R:A0%=R% REM get symbol in A$ IF (Z%(A0%,0)AND15)<>5 THEN A$="" @@ -154,52 +152,52 @@ EVAL: EVAL_GET_A3: A3%=Z%(Z%(Z%(A%,1),1),1)+1 - R%=A3%: GOSUB DEREF_R: A3%=R% + R%=A3%:GOSUB DEREF_R:A3%=R% EVAL_GET_A2: A2%=Z%(Z%(A%,1),1)+1 - R%=A2%: GOSUB DEREF_R: A2%=R% + R%=A2%:GOSUB DEREF_R:A2%=R% EVAL_GET_A1: A1%=Z%(A%,1)+1 - R%=A1%: GOSUB DEREF_R: A1%=R% + R%=A1%:GOSUB DEREF_R:A1%=R% RETURN EVAL_DEF: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1: ZZ%(ZL%)=A1%: REM push A1% - A%=A2%: GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%): ZL%=ZL%-1: REM pop A1% + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + A%=A2%:GOSUB EVAL: REM eval a2 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% REM set a1 in env to a2 - K%=A1%: V%=R%: GOSUB ENV_SET + K%=A1%:V%=R%:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% REM create new environment with outer as current environment - EO%=E%: GOSUB ENV_NEW + EO%=E%:GOSUB ENV_NEW E%=R% EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE REM push A1% - ZL%=ZL%+1: ZZ%(ZL%)=A1% + ZL%=ZL%+1:ZZ%(ZL%)=A1% REM eval current A1 odd element - A%=Z%(A1%,1)+1: GOSUB EVAL + A%=Z%(A1%,1)+1:GOSUB EVAL REM pop A1% - A1%=ZZ%(ZL%): ZL%=ZL%-1 + A1%=ZZ%(ZL%):ZL%=ZL%-1 REM set environment: even A1% key to odd A1% eval'd above - K%=A1%+1: V%=R%: GOSUB ENV_SET - AY%=R%: GOSUB RELEASE: REM release our use, ENV_SET took ownership + K%=A1%+1:V%=R%:GOSUB ENV_SET + AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - A%=A2%: GOSUB EVAL: REM eval a2 using let_env + A%=A2%:GOSUB EVAL: REM eval a2 using let_env GOTO EVAL_RETURN EVAL_INVOKE: GOSUB EVAL_AST @@ -210,10 +208,10 @@ EVAL: F%=R%+1 AR%=Z%(R%,1): REM rest - R%=F%: GOSUB DEREF_R: F%=R% - IF (Z%(F%,0)AND15)<>9 THEN ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + R%=F%:GOSUB DEREF_R:F%=R% + IF (Z%(F%,0)AND15)<>9 THEN ER%=1:ER$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION - AY%=R3%: GOSUB RELEASE + AY%=R3%:GOSUB RELEASE GOTO EVAL_RETURN EVAL_RETURN: @@ -221,7 +219,7 @@ EVAL: REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) REM release environment if not the top one on the stack - IF E%<>ZZ%(ZL%-1) THEN AY%=E%: GOSUB RELEASE + IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE LV%=LV%-1: REM track basic return stack level @@ -230,33 +228,33 @@ EVAL: TA%=FRE(0) REM pop A% and E% off the stack - E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 RETURN REM DO_FUNCTION(F%, AR%) DO_FUNCTION: - AZ%=F%: GOSUB PR_STR + AZ%=F%:GOSUB PR_STR F$=R$ - AZ%=AR%: GOSUB PR_STR + AZ%=AR%:GOSUB PR_STR AR$=R$ REM Get the function number FF%=Z%(F%,1) REM Get argument values - R%=AR%+1: GOSUB DEREF_R: AA%=Z%(R%,1) - R%=Z%(AR%,1)+1: GOSUB DEREF_R: AB%=Z%(R%,1) + R%=AR%+1:GOSUB DEREF_R:AA%=Z%(R%,1) + R%=Z%(AR%,1)+1:GOSUB DEREF_R:AB%=Z%(R%,1) REM Allocate the return value - SZ%=1: GOSUB ALLOC + SZ%=1:GOSUB ALLOC REM Switch on the function number - IF FF%=1 THEN DO_ADD - IF FF%=2 THEN DO_SUB - IF FF%=3 THEN DO_MULT - IF FF%=4 THEN DO_DIV - ER%=1: ER$="unknown function"+STR$(FF%): RETURN + IF FF%=1 THEN GOTO DO_ADD + IF FF%=2 THEN GOTO DO_SUB + IF FF%=3 THEN GOTO DO_MULT + IF FF%=4 THEN GOTO DO_DIV + ER%=1:ER$="unknown function"+STR$(FF%):RETURN DO_ADD: Z%(R%,0)=2+16 @@ -280,28 +278,28 @@ DO_FUNCTION: REM PRINT(A%) -> R$ MAL_PRINT: - AZ%=A%: PR%=1: GOSUB PR_STR + AZ%=A%:PR%=1:GOSUB PR_STR RETURN REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - R1%=0: R2%=0 + R1%=0:R2%=0 GOSUB MAL_READ R1%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: E%=RE%: GOSUB EVAL + A%=R%:E%=RE%:GOSUB EVAL R2%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: GOSUB MAL_PRINT + A%=R%:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2%<>0 THEN AY%=R2%: GOSUB RELEASE - IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE R$=RT$ RETURN @@ -312,7 +310,7 @@ MAIN: LV%=0 REM create repl_env - EO%=-1: GOSUB ENV_NEW: RE%=R% + EO%=-1:GOSUB ENV_NEW:RE%=R% E%=RE% REM + function @@ -334,12 +332,12 @@ MAIN: ZT%=ZI%: REM top of memory after base repl_env REPL_LOOP: - A$="user> ": GOSUB READLINE: REM call input parser + A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$: GOSUB REP: REM call REP + A$=R$:GOSUB REP: REM call REP - IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP + IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -350,6 +348,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0: ER$="" + ER%=0:ER$="" RETURN diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index 28b537048c..ca208b9c62 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -19,30 +19,28 @@ REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: REM push A% and E% on the stack - ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% IF ER%<>0 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A T%=Z%(A%,0)AND15 - IF T%=5 THEN EVAL_AST_SYMBOL - IF T%=6 THEN EVAL_AST_SEQ - IF T%=7 THEN EVAL_AST_SEQ - IF T%=8 THEN EVAL_AST_SEQ + IF T%=5 THEN GOTO EVAL_AST_SYMBOL + IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R%=A%: GOSUB DEREF_R + R%=A%:GOSUB DEREF_R Z%(R%,0)=Z%(R%,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K%=A%: GOSUB ENV_GET + K%=A%:GOSUB ENV_GET GOTO EVAL_AST_RETURN EVAL_AST_SEQ: REM allocate the first entry - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC REM make space on the stack ZL%=ZL%+4 @@ -74,13 +72,13 @@ EVAL_AST: GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: - R%=A%+1: GOSUB DEREF_R: REM deref to target of referred entry + R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: REM call EVAL for each entry - A%=A%+1: GOSUB EVAL + A%=A%+1:GOSUB EVAL A%=A%-1 GOSUB DEREF_R: REM deref to target of evaluated entry @@ -92,7 +90,7 @@ EVAL_AST: IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC REM update previous sequence entry value to point to new entry Z%(ZZ%(ZL%),1)=R% @@ -107,7 +105,7 @@ EVAL_AST: REM if no error, get return value (new seq) IF ER%=0 THEN R%=ZZ%(ZL%-1) REM otherwise, free the return value and return nil - IF ER%<>0 THEN R%=0: AY%=ZZ%(ZL%-1): GOSUB RELEASE + IF ER%<>0 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE REM pop previous, return, index and type ZL%=ZL%-4 @@ -115,13 +113,11 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A% and E% off the stack - E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 REM pop EVAL AST return label/address - RN%=ZZ%(ZL%): ZL%=ZL%-1 - IF RN%=1 GOTO EVAL_AST_RETURN_1 - IF RN%=2 GOTO EVAL_AST_RETURN_2 - IF RN%=3 GOTO EVAL_AST_RETURN_3 + RN%=ZZ%(ZL%):ZL%=ZL%-1 + ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN REM EVAL(A%, E%)) -> R% @@ -129,7 +125,7 @@ EVAL: LV%=LV%+1: REM track basic return stack level REM push A% and E% on the stack - ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% EVAL_TCO_RECUR: @@ -142,7 +138,7 @@ EVAL: IF R% THEN GOTO APPLY_LIST REM ELSE REM push EVAL_AST return label/address - ZL%=ZL%+1: ZZ%(ZL%)=1 + ZL%=ZL%+1:ZZ%(ZL%)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -150,10 +146,10 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R% THEN R%=A%: Z%(R%,0)=Z%(R%,0)+16: GOTO EVAL_RETURN + IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN A0%=A%+1 - R%=A0%: GOSUB DEREF_R: A0%=R% + R%=A0%:GOSUB DEREF_R:A0%=R% REM get symbol in A$ IF (Z%(A0%,0)AND15)<>5 THEN A$="" @@ -168,95 +164,95 @@ EVAL: EVAL_GET_A3: A3%=Z%(Z%(Z%(A%,1),1),1)+1 - R%=A3%: GOSUB DEREF_R: A3%=R% + R%=A3%:GOSUB DEREF_R:A3%=R% EVAL_GET_A2: A2%=Z%(Z%(A%,1),1)+1 - R%=A2%: GOSUB DEREF_R: A2%=R% + R%=A2%:GOSUB DEREF_R:A2%=R% EVAL_GET_A1: A1%=Z%(A%,1)+1 - R%=A1%: GOSUB DEREF_R: A1%=R% + R%=A1%:GOSUB DEREF_R:A1%=R% RETURN EVAL_DEF: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1: ZZ%(ZL%)=A1%: REM push A1% - A%=A2%: GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%): ZL%=ZL%-1: REM pop A1% + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + A%=A2%:GOSUB EVAL: REM eval a2 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% REM set a1 in env to a2 - K%=A1%: V%=R%: GOSUB ENV_SET + K%=A1%:V%=R%:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% REM create new environment with outer as current environment - EO%=E%: GOSUB ENV_NEW + EO%=E%:GOSUB ENV_NEW E%=R% EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE REM push A1% - ZL%=ZL%+1: ZZ%(ZL%)=A1% + ZL%=ZL%+1:ZZ%(ZL%)=A1% REM eval current A1 odd element - A%=Z%(A1%,1)+1: GOSUB EVAL + A%=Z%(A1%,1)+1:GOSUB EVAL REM pop A1% - A1%=ZZ%(ZL%): ZL%=ZL%-1 + A1%=ZZ%(ZL%):ZL%=ZL%-1 REM set environment: even A1% key to odd A1% eval'd above - K%=A1%+1: V%=R%: GOSUB ENV_SET - AY%=R%: GOSUB RELEASE: REM release our use, ENV_SET took ownership + K%=A1%+1:V%=R%:GOSUB ENV_SET + AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - A%=A2%: GOSUB EVAL: REM eval a2 using let_env + A%=A2%:GOSUB EVAL: REM eval a2 using let_env GOTO EVAL_RETURN EVAL_DO: A%=Z%(A%,1): REM rest REM push EVAL_AST return label/address - ZL%=ZL%+1: ZZ%(ZL%)=2 + ZL%=ZL%+1:ZZ%(ZL%)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - ZL%=ZL%+1: ZZ%(ZL%)=R%: REM push eval'd list - A%=R%: GOSUB LAST: REM return the last element - AY%=ZZ%(ZL%): ZL%=ZL%-1: REM pop eval'd list + ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push eval'd list + A%=R%:GOSUB LAST: REM return the last element + AY%=ZZ%(ZL%):ZL%=ZL%-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_IF: GOSUB EVAL_GET_A1: REM set a1% REM push A% - ZL%=ZL%+1: ZZ%(ZL%)=A% - A%=A1%: GOSUB EVAL + ZL%=ZL%+1:ZZ%(ZL%)=A% + A%=A1%:GOSUB EVAL REM pop A% - A%=ZZ%(ZL%): ZL%=ZL%-1 + A%=ZZ%(ZL%):ZL%=ZL%-1 IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: - AY%=R%: GOSUB RELEASE + AY%=R%:GOSUB RELEASE GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A%=A2%: GOTO EVAL_TCO_RECUR: REM TCO loop + A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: - AY%=R%: GOSUB RELEASE + AY%=R%:GOSUB RELEASE REM if no false case (A3%), return nil - IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0: GOTO EVAL_RETURN + IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A%=A3%: GOTO EVAL_TCO_RECUR: REM TCO loop + A%=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set a1% and a2% - A%=A2%: P%=A1%: GOSUB MAL_FUNCTION + A%=A2%:P%=A1%:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - ZL%=ZL%+1: ZZ%(ZL%)=3 + ZL%=ZL%+1:ZZ%(ZL%)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: @@ -264,56 +260,56 @@ EVAL: IF ER%<>0 THEN GOTO EVAL_RETURN REM push f/args for release after call - ZL%=ZL%+1: ZZ%(ZL%)=R% + ZL%=ZL%+1:ZZ%(ZL%)=R% F%=R%+1 AR%=Z%(R%,1): REM rest - R%=F%: GOSUB DEREF_R: F%=R% + R%=F%:GOSUB DEREF_R:F%=R% IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R%=ZZ%(ZL%): ZL%=ZL%-1 - ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + R%=ZZ%(ZL%):ZL%=ZL%-1 + ER%=1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: E4%=E%: REM save the current environment for release REM create new environ using env stored with function - EO%=Z%(F%+1,1): BI%=Z%(F%+1,0): EX%=AR%: GOSUB ENV_NEW_BINDS + EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (ZZ%(ZL%-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%: GOSUB RELEASE + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE REM claim the AST before releasing the list containing it - A%=Z%(F%,1): Z%(A%,0)=Z%(A%,0)+16 + A%=Z%(F%,1):Z%(A%,0)=Z%(A%,0)+16 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV%+1) - ZM%=ZM%+1: ZR%(ZM%,0)=A%: ZR%(ZM%,1)=LV%+1 + ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV%+1 REM pop and release f/args - AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE REM A% set above - E%=R%: GOTO EVAL_TCO_RECUR: REM TCO loop + E%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: REM AZ%=R%: PR%=1: GOSUB PR_STR REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) REM release environment if not the top one on the stack - IF E%<>ZZ%(ZL%-1) THEN AY%=E%: GOSUB RELEASE + IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE LV%=LV%-1: REM track basic return stack level @@ -324,13 +320,13 @@ EVAL: TA%=FRE(0) REM pop A% and E% off the stack - E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 RETURN REM PRINT(A%) -> R$ MAL_PRINT: - AZ%=A%: PR%=1: GOSUB PR_STR + AZ%=A%:PR%=1:GOSUB PR_STR RETURN REM RE(A$) -> R% @@ -342,32 +338,32 @@ RE: R1%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: E%=RE%: GOSUB EVAL + A%=R%:E%=RE%:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ - IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - R1%=0: R2%=0 + R1%=0:R2%=0 GOSUB MAL_READ R1%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: E%=RE%: GOSUB EVAL + A%=R%:E%=RE%:GOSUB EVAL R2%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: GOSUB MAL_PRINT + A%=R%:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2%<>0 THEN AY%=R2%: GOSUB RELEASE - IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE R$=RT$ RETURN @@ -378,23 +374,24 @@ MAIN: LV%=0 REM create repl_env - EO%=-1: GOSUB ENV_NEW: RE%=R% + EO%=-1:GOSUB ENV_NEW:RE%=R% REM core.EXT: defined in Basic - E%=RE%: GOSUB INIT_CORE_NS: REM set core functions in repl_env + E%=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env ZT%=ZI%: REM top of memory after base repl_env REM core.mal: defined using the language itself - A$="(def! not (fn* (a) (if a false true)))": GOSUB RE: AY%=R%: GOSUB RELEASE + A$="(def! not (fn* (a) (if a false true)))" + GOSUB RE:AY%=R%:GOSUB RELEASE REPL_LOOP: - A$="user> ": GOSUB READLINE: REM call input parser + A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$: GOSUB REP: REM call REP + A$=R$:GOSUB REP: REM call REP - IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP + IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -405,6 +402,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0: ER$="" + ER%=0:ER$="" RETURN diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index 797575648c..1825cf845d 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -19,30 +19,28 @@ REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: REM push A% and E% on the stack - ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% IF ER%<>0 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A T%=Z%(A%,0)AND15 - IF T%=5 THEN EVAL_AST_SYMBOL - IF T%=6 THEN EVAL_AST_SEQ - IF T%=7 THEN EVAL_AST_SEQ - IF T%=8 THEN EVAL_AST_SEQ + IF T%=5 THEN GOTO EVAL_AST_SYMBOL + IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R%=A%: GOSUB DEREF_R + R%=A%:GOSUB DEREF_R Z%(R%,0)=Z%(R%,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K%=A%: GOSUB ENV_GET + K%=A%:GOSUB ENV_GET GOTO EVAL_AST_RETURN EVAL_AST_SEQ: REM allocate the first entry - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC REM make space on the stack ZL%=ZL%+4 @@ -74,13 +72,13 @@ EVAL_AST: GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: - R%=A%+1: GOSUB DEREF_R: REM deref to target of referred entry + R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: REM call EVAL for each entry - A%=A%+1: GOSUB EVAL + A%=A%+1:GOSUB EVAL A%=A%-1 GOSUB DEREF_R: REM deref to target of evaluated entry @@ -92,7 +90,7 @@ EVAL_AST: IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC REM update previous sequence entry value to point to new entry Z%(ZZ%(ZL%),1)=R% @@ -107,7 +105,7 @@ EVAL_AST: REM if no error, get return value (new seq) IF ER%=0 THEN R%=ZZ%(ZL%-1) REM otherwise, free the return value and return nil - IF ER%<>0 THEN R%=0: AY%=ZZ%(ZL%-1): GOSUB RELEASE + IF ER%<>0 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE REM pop previous, return, index and type ZL%=ZL%-4 @@ -115,13 +113,11 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A% and E% off the stack - E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 REM pop EVAL AST return label/address - RN%=ZZ%(ZL%): ZL%=ZL%-1 - IF RN%=1 GOTO EVAL_AST_RETURN_1 - IF RN%=2 GOTO EVAL_AST_RETURN_2 - IF RN%=3 GOTO EVAL_AST_RETURN_3 + RN%=ZZ%(ZL%):ZL%=ZL%-1 + ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN REM EVAL(A%, E%)) -> R% @@ -129,7 +125,7 @@ EVAL: LV%=LV%+1: REM track basic return stack level REM push A% and E% on the stack - ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% EVAL_TCO_RECUR: @@ -142,7 +138,7 @@ EVAL: IF R% THEN GOTO APPLY_LIST REM ELSE REM push EVAL_AST return label/address - ZL%=ZL%+1: ZZ%(ZL%)=1 + ZL%=ZL%+1:ZZ%(ZL%)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -150,10 +146,10 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R% THEN R%=A%: Z%(R%,0)=Z%(R%,0)+16: GOTO EVAL_RETURN + IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN A0%=A%+1 - R%=A0%: GOSUB DEREF_R: A0%=R% + R%=A0%:GOSUB DEREF_R:A0%=R% REM get symbol in A$ IF (Z%(A0%,0)AND15)<>5 THEN A$="" @@ -168,25 +164,25 @@ EVAL: EVAL_GET_A3: A3%=Z%(Z%(Z%(A%,1),1),1)+1 - R%=A3%: GOSUB DEREF_R: A3%=R% + R%=A3%:GOSUB DEREF_R:A3%=R% EVAL_GET_A2: A2%=Z%(Z%(A%,1),1)+1 - R%=A2%: GOSUB DEREF_R: A2%=R% + R%=A2%:GOSUB DEREF_R:A2%=R% EVAL_GET_A1: A1%=Z%(A%,1)+1 - R%=A1%: GOSUB DEREF_R: A1%=R% + R%=A1%:GOSUB DEREF_R:A1%=R% RETURN EVAL_DEF: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1: ZZ%(ZL%)=A1%: REM push A1% - A%=A2%: GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%): ZL%=ZL%-1: REM pop A1% + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + A%=A2%:GOSUB EVAL: REM eval a2 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% REM set a1 in env to a2 - K%=A1%: V%=R%: GOSUB ENV_SET + K%=A1%:V%=R%:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: @@ -196,21 +192,21 @@ EVAL: E4%=E%: REM save the current environment for release REM create new environment with outer as current environment - EO%=E%: GOSUB ENV_NEW + EO%=E%:GOSUB ENV_NEW E%=R% EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE REM push A1% - ZL%=ZL%+1: ZZ%(ZL%)=A1% + ZL%=ZL%+1:ZZ%(ZL%)=A1% REM eval current A1 odd element - A%=Z%(A1%,1)+1: GOSUB EVAL + A%=Z%(A1%,1)+1:GOSUB EVAL REM pop A1% - A1%=ZZ%(ZL%): ZL%=ZL%-1 + A1%=ZZ%(ZL%):ZL%=ZL%-1 REM set environment: even A1% key to odd A1% eval'd above - K%=A1%+1: V%=R%: GOSUB ENV_SET - AY%=R%: GOSUB RELEASE: REM release our use, ENV_SET took ownership + K%=A1%+1:V%=R%:GOSUB ENV_SET + AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) @@ -219,9 +215,9 @@ EVAL: REM release previous env (if not root repl_env) because our REM new env refers to it and we no longer need to track it REM (since we are TCO recurring) - IF E4%<>RE% THEN AY%=E4%: GOSUB RELEASE + IF E4%<>RE% THEN AY%=E4%:GOSUB RELEASE - A%=A2%: GOTO EVAL_TCO_RECUR: REM TCO loop + A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: A%=Z%(A%,1): REM rest @@ -229,44 +225,44 @@ EVAL: REM TODO: TCO REM push EVAL_AST return label/address - ZL%=ZL%+1: ZZ%(ZL%)=2 + ZL%=ZL%+1:ZZ%(ZL%)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - ZL%=ZL%+1: ZZ%(ZL%)=R%: REM push eval'd list - A%=R%: GOSUB LAST: REM return the last element - AY%=ZZ%(ZL%): ZL%=ZL%-1: REM pop eval'd list + ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push eval'd list + A%=R%:GOSUB LAST: REM return the last element + AY%=ZZ%(ZL%):ZL%=ZL%-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_IF: GOSUB EVAL_GET_A1: REM set a1% REM push A% - ZL%=ZL%+1: ZZ%(ZL%)=A% - A%=A1%: GOSUB EVAL + ZL%=ZL%+1:ZZ%(ZL%)=A% + A%=A1%:GOSUB EVAL REM pop A% - A%=ZZ%(ZL%): ZL%=ZL%-1 + A%=ZZ%(ZL%):ZL%=ZL%-1 IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: - AY%=R%: GOSUB RELEASE + AY%=R%:GOSUB RELEASE GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A%=A2%: GOTO EVAL_TCO_RECUR: REM TCO loop + A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: - AY%=R%: GOSUB RELEASE + AY%=R%:GOSUB RELEASE REM if no false case (A3%), return nil - IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0: GOTO EVAL_RETURN + IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A%=A3%: GOTO EVAL_TCO_RECUR: REM TCO loop + A%=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set a1% and a2% - A%=A2%: P%=A1%: GOSUB MAL_FUNCTION + A%=A2%:P%=A1%:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - ZL%=ZL%+1: ZZ%(ZL%)=3 + ZL%=ZL%+1:ZZ%(ZL%)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: @@ -274,56 +270,56 @@ EVAL: IF ER%<>0 THEN GOTO EVAL_RETURN REM push f/args for release after call - ZL%=ZL%+1: ZZ%(ZL%)=R% + ZL%=ZL%+1:ZZ%(ZL%)=R% F%=R%+1 AR%=Z%(R%,1): REM rest - R%=F%: GOSUB DEREF_R: F%=R% + R%=F%:GOSUB DEREF_R:F%=R% IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R%=ZZ%(ZL%): ZL%=ZL%-1 - ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + R%=ZZ%(ZL%):ZL%=ZL%-1 + ER%=1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: E4%=E%: REM save the current environment for release REM create new environ using env stored with function - EO%=Z%(F%+1,1): BI%=Z%(F%+1,0): EX%=AR%: GOSUB ENV_NEW_BINDS + EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (ZZ%(ZL%-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%: GOSUB RELEASE + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE REM claim the AST before releasing the list containing it - A%=Z%(F%,1): Z%(A%,0)=Z%(A%,0)+16 + A%=Z%(F%,1):Z%(A%,0)=Z%(A%,0)+16 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV%+1) - ZM%=ZM%+1: ZR%(ZM%,0)=A%: ZR%(ZM%,1)=LV%+1 + ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV%+1 REM pop and release f/args - AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE REM A% set above - E%=R%: GOTO EVAL_TCO_RECUR: REM TCO loop + E%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: REM AZ%=R%: PR%=1: GOSUB PR_STR REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) REM release environment if not the top one on the stack - IF E%<>ZZ%(ZL%-1) THEN AY%=E%: GOSUB RELEASE + IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE LV%=LV%-1: REM track basic return stack level @@ -334,13 +330,13 @@ EVAL: TA%=FRE(0) REM pop A% and E% off the stack - E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 RETURN REM PRINT(A%) -> R$ MAL_PRINT: - AZ%=A%: PR%=1: GOSUB PR_STR + AZ%=A%:PR%=1:GOSUB PR_STR RETURN REM RE(A$) -> R% @@ -352,32 +348,32 @@ RE: R1%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: E%=RE%: GOSUB EVAL + A%=R%:E%=RE%:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ - IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - R1%=0: R2%=0 + R1%=0:R2%=0 GOSUB MAL_READ R1%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: E%=RE%: GOSUB EVAL + A%=R%:E%=RE%:GOSUB EVAL R2%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: GOSUB MAL_PRINT + A%=R%:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2%<>0 THEN AY%=R2%: GOSUB RELEASE - IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE R$=RT$ RETURN @@ -388,23 +384,24 @@ MAIN: LV%=0 REM create repl_env - EO%=-1: GOSUB ENV_NEW: RE%=R% + EO%=-1:GOSUB ENV_NEW:RE%=R% REM core.EXT: defined in Basic - E%=RE%: GOSUB INIT_CORE_NS: REM set core functions in repl_env + E%=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env ZT%=ZI%: REM top of memory after base repl_env REM core.mal: defined using the language itself - A$="(def! not (fn* (a) (if a false true)))": GOSUB RE: AY%=R%: GOSUB RELEASE + A$="(def! not (fn* (a) (if a false true)))" + GOSUB RE:AY%=R%:GOSUB RELEASE REPL_LOOP: - A$="user> ": GOSUB READLINE: REM call input parser + A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$: GOSUB REP: REM call REP + A$=R$:GOSUB REP: REM call REP - IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP + IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -415,6 +412,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0: ER$="" + ER%=0:ER$="" RETURN diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index 7eecce33b3..27850b18e8 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -1,5 +1,3 @@ -REM POKE 1, PEEK(1)AND248: REM enable all ROM areas as RAM -REM POKE 55,0: POKE 56,192: CLR: REM move BASIC end from $A000 to $C000 GOTO MAIN REM $INCLUDE: 'readline.in.bas' @@ -21,30 +19,28 @@ REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: REM push A% and E% on the stack - ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% IF ER%<>0 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A T%=Z%(A%,0)AND15 - IF T%=5 THEN EVAL_AST_SYMBOL - IF T%=6 THEN EVAL_AST_SEQ - IF T%=7 THEN EVAL_AST_SEQ - IF T%=8 THEN EVAL_AST_SEQ + IF T%=5 THEN GOTO EVAL_AST_SYMBOL + IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R%=A%: GOSUB DEREF_R + R%=A%:GOSUB DEREF_R Z%(R%,0)=Z%(R%,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K%=A%: GOSUB ENV_GET + K%=A%:GOSUB ENV_GET GOTO EVAL_AST_RETURN EVAL_AST_SEQ: REM allocate the first entry - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC REM make space on the stack ZL%=ZL%+4 @@ -76,13 +72,13 @@ EVAL_AST: GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: - R%=A%+1: GOSUB DEREF_R: REM deref to target of referred entry + R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: REM call EVAL for each entry - A%=A%+1: GOSUB EVAL + A%=A%+1:GOSUB EVAL A%=A%-1 GOSUB DEREF_R: REM deref to target of evaluated entry @@ -94,7 +90,7 @@ EVAL_AST: IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC REM update previous sequence entry value to point to new entry Z%(ZZ%(ZL%),1)=R% @@ -109,7 +105,7 @@ EVAL_AST: REM if no error, get return value (new seq) IF ER%=0 THEN R%=ZZ%(ZL%-1) REM otherwise, free the return value and return nil - IF ER%<>0 THEN R%=0: AY%=ZZ%(ZL%-1): GOSUB RELEASE + IF ER%<>0 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE REM pop previous, return, index and type ZL%=ZL%-4 @@ -117,13 +113,11 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A% and E% off the stack - E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 REM pop EVAL AST return label/address - RN%=ZZ%(ZL%): ZL%=ZL%-1 - IF RN%=1 GOTO EVAL_AST_RETURN_1 - IF RN%=2 GOTO EVAL_AST_RETURN_2 - IF RN%=3 GOTO EVAL_AST_RETURN_3 + RN%=ZZ%(ZL%):ZL%=ZL%-1 + ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN REM EVAL(A%, E%)) -> R% @@ -131,7 +125,7 @@ EVAL: LV%=LV%+1: REM track basic return stack level REM push A% and E% on the stack - ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% EVAL_TCO_RECUR: @@ -144,7 +138,7 @@ EVAL: IF R% THEN GOTO APPLY_LIST REM ELSE REM push EVAL_AST return label/address - ZL%=ZL%+1: ZZ%(ZL%)=1 + ZL%=ZL%+1:ZZ%(ZL%)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -152,10 +146,10 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R% THEN R%=A%: Z%(R%,0)=Z%(R%,0)+16: GOTO EVAL_RETURN + IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN A0%=A%+1 - R%=A0%: GOSUB DEREF_R: A0%=R% + R%=A0%:GOSUB DEREF_R:A0%=R% REM get symbol in A$ IF (Z%(A0%,0)AND15)<>5 THEN A$="" @@ -170,25 +164,25 @@ EVAL: EVAL_GET_A3: A3%=Z%(Z%(Z%(A%,1),1),1)+1 - R%=A3%: GOSUB DEREF_R: A3%=R% + R%=A3%:GOSUB DEREF_R:A3%=R% EVAL_GET_A2: A2%=Z%(Z%(A%,1),1)+1 - R%=A2%: GOSUB DEREF_R: A2%=R% + R%=A2%:GOSUB DEREF_R:A2%=R% EVAL_GET_A1: A1%=Z%(A%,1)+1 - R%=A1%: GOSUB DEREF_R: A1%=R% + R%=A1%:GOSUB DEREF_R:A1%=R% RETURN EVAL_DEF: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1: ZZ%(ZL%)=A1%: REM push A1% - A%=A2%: GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%): ZL%=ZL%-1: REM pop A1% + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + A%=A2%:GOSUB EVAL: REM eval a2 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% REM set a1 in env to a2 - K%=A1%: V%=R%: GOSUB ENV_SET + K%=A1%:V%=R%:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: @@ -198,21 +192,21 @@ EVAL: E4%=E%: REM save the current environment for release REM create new environment with outer as current environment - EO%=E%: GOSUB ENV_NEW + EO%=E%:GOSUB ENV_NEW E%=R% EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE REM push A1% - ZL%=ZL%+1: ZZ%(ZL%)=A1% + ZL%=ZL%+1:ZZ%(ZL%)=A1% REM eval current A1 odd element - A%=Z%(A1%,1)+1: GOSUB EVAL + A%=Z%(A1%,1)+1:GOSUB EVAL REM pop A1% - A1%=ZZ%(ZL%): ZL%=ZL%-1 + A1%=ZZ%(ZL%):ZL%=ZL%-1 REM set environment: even A1% key to odd A1% eval'd above - K%=A1%+1: V%=R%: GOSUB ENV_SET - AY%=R%: GOSUB RELEASE: REM release our use, ENV_SET took ownership + K%=A1%+1:V%=R%:GOSUB ENV_SET + AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) @@ -221,9 +215,9 @@ EVAL: REM release previous env (if not root repl_env) because our REM new env refers to it and we no longer need to track it REM (since we are TCO recurring) - IF E4%<>RE% THEN AY%=E4%: GOSUB RELEASE + IF E4%<>RE% THEN AY%=E4%:GOSUB RELEASE - A%=A2%: GOTO EVAL_TCO_RECUR: REM TCO loop + A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: A%=Z%(A%,1): REM rest @@ -231,44 +225,44 @@ EVAL: REM TODO: TCO REM push EVAL_AST return label/address - ZL%=ZL%+1: ZZ%(ZL%)=2 + ZL%=ZL%+1:ZZ%(ZL%)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - ZL%=ZL%+1: ZZ%(ZL%)=R%: REM push eval'd list - A%=R%: GOSUB LAST: REM return the last element - AY%=ZZ%(ZL%): ZL%=ZL%-1: REM pop eval'd list + ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push eval'd list + A%=R%:GOSUB LAST: REM return the last element + AY%=ZZ%(ZL%):ZL%=ZL%-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_IF: GOSUB EVAL_GET_A1: REM set a1% REM push A% - ZL%=ZL%+1: ZZ%(ZL%)=A% - A%=A1%: GOSUB EVAL + ZL%=ZL%+1:ZZ%(ZL%)=A% + A%=A1%:GOSUB EVAL REM pop A% - A%=ZZ%(ZL%): ZL%=ZL%-1 + A%=ZZ%(ZL%):ZL%=ZL%-1 IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: - AY%=R%: GOSUB RELEASE + AY%=R%:GOSUB RELEASE GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A%=A2%: GOTO EVAL_TCO_RECUR: REM TCO loop + A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: - AY%=R%: GOSUB RELEASE + AY%=R%:GOSUB RELEASE REM if no false case (A3%), return nil - IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0: GOTO EVAL_RETURN + IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A%=A3%: GOTO EVAL_TCO_RECUR: REM TCO loop + A%=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set a1% and a2% - A%=A2%: P%=A1%: GOSUB MAL_FUNCTION + A%=A2%:P%=A1%:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - ZL%=ZL%+1: ZZ%(ZL%)=3 + ZL%=ZL%+1:ZZ%(ZL%)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: @@ -276,56 +270,56 @@ EVAL: IF ER%<>0 THEN GOTO EVAL_RETURN REM push f/args for release after call - ZL%=ZL%+1: ZZ%(ZL%)=R% + ZL%=ZL%+1:ZZ%(ZL%)=R% F%=R%+1 AR%=Z%(R%,1): REM rest - R%=F%: GOSUB DEREF_R: F%=R% + R%=F%:GOSUB DEREF_R:F%=R% IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R%=ZZ%(ZL%): ZL%=ZL%-1 - ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + R%=ZZ%(ZL%):ZL%=ZL%-1 + ER%=1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: E4%=E%: REM save the current environment for release REM create new environ using env stored with function - EO%=Z%(F%+1,1): BI%=Z%(F%+1,0): EX%=AR%: GOSUB ENV_NEW_BINDS + EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (ZZ%(ZL%-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%: GOSUB RELEASE + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE REM claim the AST before releasing the list containing it - A%=Z%(F%,1): Z%(A%,0)=Z%(A%,0)+16 + A%=Z%(F%,1):Z%(A%,0)=Z%(A%,0)+16 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV%+1) - ZM%=ZM%+1: ZR%(ZM%,0)=A%: ZR%(ZM%,1)=LV%+1 + ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV%+1 REM pop and release f/args - AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE REM A% set above - E%=R%: GOTO EVAL_TCO_RECUR: REM TCO loop + E%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: REM AZ%=R%: PR%=1: GOSUB PR_STR REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) REM release environment if not the top one on the stack - IF E%<>ZZ%(ZL%-1) THEN AY%=E%: GOSUB RELEASE + IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE LV%=LV%-1: REM track basic return stack level @@ -336,13 +330,13 @@ EVAL: TA%=FRE(0) REM pop A% and E% off the stack - E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 RETURN REM PRINT(A%) -> R$ MAL_PRINT: - AZ%=A%: PR%=1: GOSUB PR_STR + AZ%=A%:PR%=1:GOSUB PR_STR RETURN REM RE(A$) -> R% @@ -354,32 +348,32 @@ RE: R1%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: E%=RE%: GOSUB EVAL + A%=R%:E%=RE%:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ - IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - R1%=0: R2%=0 + R1%=0:R2%=0 GOSUB MAL_READ R1%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: E%=RE%: GOSUB EVAL + A%=R%:E%=RE%:GOSUB EVAL R2%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: GOSUB MAL_PRINT + A%=R%:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2%<>0 THEN AY%=R2%: GOSUB RELEASE - IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE R$=RT$ RETURN @@ -390,49 +384,52 @@ MAIN: LV%=0 REM create repl_env - EO%=-1: GOSUB ENV_NEW: RE%=R% + EO%=-1:GOSUB ENV_NEW:RE%=R% REM core.EXT: defined in Basic - E%=RE%: GOSUB INIT_CORE_NS: REM set core functions in repl_env + E%=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env ZT%=ZI%: REM top of memory after base repl_env REM core.mal: defined using the language itself - A$="(def! not (fn* (a) (if a false true)))": GOSUB RE: AY%=R%: GOSUB RELEASE + A$="(def! not (fn* (a) (if a false true)))" + GOSUB RE:AY%=R%:GOSUB RELEASE A$="(def! load-file (fn* (f) (eval (read-string (str " - A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) " - A$=A$+CHR$(34)+")"+CHR$(34)+")))))" - GOSUB RE: AY%=R%: GOSUB RELEASE + A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))" + GOSUB RE:AY%=R%:GOSUB RELEASE REM load the args file A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" - GOSUB RE: AY%=R%: GOSUB RELEASE + GOSUB RE:AY%=R%:GOSUB RELEASE REM set the argument list - A$="(def! *ARGV* (rest -*ARGS*-))": GOSUB RE: AY%=R%: GOSUB RELEASE + A$="(def! *ARGV* (rest -*ARGS*-))" + GOSUB RE:AY%=R%:GOSUB RELEASE REM get the first argument - A$="(first -*ARGS*-)": GOSUB RE + A$="(first -*ARGS*-)" + GOSUB RE REM if there is an argument, then run it as a program - IF R%<>0 THEN AY%=R%: GOSUB RELEASE: GOTO RUN_PROG + IF R%<>0 THEN AY%=R%:GOSUB RELEASE:GOTO RUN_PROG REM no arguments, start REPL loop IF R%=0 THEN GOTO REPL_LOOP RUN_PROG: REM run a single mal program and exit - A$="(load-file (first -*ARGS*-))": GOSUB RE + A$="(load-file (first -*ARGS*-))" + GOSUB RE IF ER%<>0 THEN GOSUB PRINT_ERROR END REPL_LOOP: - A$="user> ": GOSUB READLINE: REM call input parser + A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$: GOSUB REP: REM call REP + A$=R$:GOSUB REP: REM call REP - IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP + IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -443,6 +440,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0: ER$="" + ER%=0:ER$="" RETURN diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index b7b975a9e6..2315a3da02 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -26,63 +26,64 @@ PAIR_Q: REM QUASIQUOTE(A%) -> R% QUASIQUOTE: - B%=A%: GOSUB PAIR_Q + B%=A%:GOSUB PAIR_Q IF R%=1 THEN GOTO QQ_UNQUOTE REM ['quote, ast] - AS$="quote": T%=5: GOSUB STRING - B2%=R%: B1%=A%: GOSUB LIST2 + AS$="quote":T%=5:GOSUB STRING + B2%=R%:B1%=A%:GOSUB LIST2 RETURN QQ_UNQUOTE: - R%=A%+1: GOSUB DEREF_R + R%=A%+1:GOSUB DEREF_R IF (Z%(R%,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE IF ZS$(Z%(R%,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] - R%=Z%(A%,1)+1: GOSUB DEREF_R + R%=Z%(A%,1)+1:GOSUB DEREF_R Z%(R%,0)=Z%(R%,0)+16 RETURN QQ_SPLICE_UNQUOTE: REM push A% on the stack - ZL%=ZL%+1: ZZ%(ZL%)=A% + ZL%=ZL%+1:ZZ%(ZL%)=A% REM rest of cases call quasiquote on ast[1..] - A%=Z%(A%,1): GOSUB QUASIQUOTE: T6%=R% + A%=Z%(A%,1):GOSUB QUASIQUOTE:T6%=R% REM pop A% off the stack - A%=ZZ%(ZL%): ZL%=ZL%-1 + A%=ZZ%(ZL%):ZL%=ZL%-1 REM set A% to ast[0] for last two cases - A%=A%+1: GOSUB DEREF_A + A%=A%+1:GOSUB DEREF_A - B%=A%: GOSUB PAIR_Q + B%=A%:GOSUB PAIR_Q IF R%=0 THEN GOTO QQ_DEFAULT - B%=A%+1: GOSUB DEREF_B + B%=A%+1:GOSUB DEREF_B IF (Z%(B%,0)AND15)<>5 THEN GOTO QQ_DEFAULT IF ZS$(Z%(B%,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B%=Z%(A%,1)+1: GOSUB DEREF_B: B2%=B% - AS$="concat": T%=5: GOSUB STRING: B3%=R% - B1%=T6%: GOSUB LIST3 + B%=Z%(A%,1)+1:GOSUB DEREF_B:B2%=B% + AS$="concat":T%=5:GOSUB STRING:B3%=R% + B1%=T6%:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY%=B1%: GOSUB RELEASE + AY%=B1%:GOSUB RELEASE RETURN QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] REM push T6% on the stack - ZL%=ZL%+1: ZZ%(ZL%)=T6% + ZL%=ZL%+1:ZZ%(ZL%)=T6% REM A% set above to ast[0] - GOSUB QUASIQUOTE: B2%=R% + GOSUB QUASIQUOTE:B2%=R% REM pop T6% off the stack - T6%=ZZ%(ZL%): ZL%=ZL%-1 + T6%=ZZ%(ZL%):ZL%=ZL%-1 - AS$="cons": T%=5: GOSUB STRING: B3%=R% - B1%=T6%: GOSUB LIST3 + AS$="cons":T%=5:GOSUB STRING:B3%=R% + B1%=T6%:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY%=B1%: GOSUB RELEASE: AY%=B2%: GOSUB RELEASE + AY%=B1%:GOSUB RELEASE + AY%=B2%:GOSUB RELEASE RETURN @@ -91,30 +92,28 @@ REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: REM push A% and E% on the stack - ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% IF ER%<>0 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A T%=Z%(A%,0)AND15 - IF T%=5 THEN EVAL_AST_SYMBOL - IF T%=6 THEN EVAL_AST_SEQ - IF T%=7 THEN EVAL_AST_SEQ - IF T%=8 THEN EVAL_AST_SEQ + IF T%=5 THEN GOTO EVAL_AST_SYMBOL + IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R%=A%: GOSUB DEREF_R + R%=A%:GOSUB DEREF_R Z%(R%,0)=Z%(R%,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K%=A%: GOSUB ENV_GET + K%=A%:GOSUB ENV_GET GOTO EVAL_AST_RETURN EVAL_AST_SEQ: REM allocate the first entry - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC REM make space on the stack ZL%=ZL%+4 @@ -146,13 +145,13 @@ EVAL_AST: GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: - R%=A%+1: GOSUB DEREF_R: REM deref to target of referred entry + R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: REM call EVAL for each entry - A%=A%+1: GOSUB EVAL + A%=A%+1:GOSUB EVAL A%=A%-1 GOSUB DEREF_R: REM deref to target of evaluated entry @@ -164,7 +163,7 @@ EVAL_AST: IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC REM update previous sequence entry value to point to new entry Z%(ZZ%(ZL%),1)=R% @@ -179,7 +178,7 @@ EVAL_AST: REM if no error, get return value (new seq) IF ER%=0 THEN R%=ZZ%(ZL%-1) REM otherwise, free the return value and return nil - IF ER%<>0 THEN R%=0: AY%=ZZ%(ZL%-1): GOSUB RELEASE + IF ER%<>0 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE REM pop previous, return, index and type ZL%=ZL%-4 @@ -187,13 +186,11 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A% and E% off the stack - E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 REM pop EVAL AST return label/address - RN%=ZZ%(ZL%): ZL%=ZL%-1 - IF RN%=1 GOTO EVAL_AST_RETURN_1 - IF RN%=2 GOTO EVAL_AST_RETURN_2 - IF RN%=3 GOTO EVAL_AST_RETURN_3 + RN%=ZZ%(ZL%):ZL%=ZL%-1 + ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN REM EVAL(A%, E%)) -> R% @@ -201,7 +198,7 @@ EVAL: LV%=LV%+1: REM track basic return stack level REM push A% and E% on the stack - ZL%=ZL%+2: ZZ%(ZL%-1)=E%: ZZ%(ZL%)=A% + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% EVAL_TCO_RECUR: @@ -214,7 +211,7 @@ EVAL: IF R% THEN GOTO APPLY_LIST REM ELSE REM push EVAL_AST return label/address - ZL%=ZL%+1: ZZ%(ZL%)=1 + ZL%=ZL%+1:ZZ%(ZL%)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -222,10 +219,10 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R% THEN R%=A%: Z%(R%,0)=Z%(R%,0)+16: GOTO EVAL_RETURN + IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN A0%=A%+1 - R%=A0%: GOSUB DEREF_R: A0%=R% + R%=A0%:GOSUB DEREF_R:A0%=R% REM get symbol in A$ IF (Z%(A0%,0)AND15)<>5 THEN A$="" @@ -242,25 +239,25 @@ EVAL: EVAL_GET_A3: A3%=Z%(Z%(Z%(A%,1),1),1)+1 - R%=A3%: GOSUB DEREF_R: A3%=R% + R%=A3%:GOSUB DEREF_R:A3%=R% EVAL_GET_A2: A2%=Z%(Z%(A%,1),1)+1 - R%=A2%: GOSUB DEREF_R: A2%=R% + R%=A2%:GOSUB DEREF_R:A2%=R% EVAL_GET_A1: A1%=Z%(A%,1)+1 - R%=A1%: GOSUB DEREF_R: A1%=R% + R%=A1%:GOSUB DEREF_R:A1%=R% RETURN EVAL_DEF: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1: ZZ%(ZL%)=A1%: REM push A1% - A%=A2%: GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%): ZL%=ZL%-1: REM pop A1% + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + A%=A2%:GOSUB EVAL: REM eval a2 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% REM set a1 in env to a2 - K%=A1%: V%=R%: GOSUB ENV_SET + K%=A1%:V%=R%:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: @@ -270,21 +267,21 @@ EVAL: E4%=E%: REM save the current environment for release REM create new environment with outer as current environment - EO%=E%: GOSUB ENV_NEW + EO%=E%:GOSUB ENV_NEW E%=R% EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE REM push A1% - ZL%=ZL%+1: ZZ%(ZL%)=A1% + ZL%=ZL%+1:ZZ%(ZL%)=A1% REM eval current A1 odd element - A%=Z%(A1%,1)+1: GOSUB EVAL + A%=Z%(A1%,1)+1:GOSUB EVAL REM pop A1% - A1%=ZZ%(ZL%): ZL%=ZL%-1 + A1%=ZZ%(ZL%):ZL%=ZL%-1 REM set environment: even A1% key to odd A1% eval'd above - K%=A1%+1: V%=R%: GOSUB ENV_SET - AY%=R%: GOSUB RELEASE: REM release our use, ENV_SET took ownership + K%=A1%+1:V%=R%:GOSUB ENV_SET + AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) @@ -293,9 +290,9 @@ EVAL: REM release previous env (if not root repl_env) because our REM new env refers to it and we no longer need to track it REM (since we are TCO recurring) - IF E4%<>RE% THEN AY%=E4%: GOSUB RELEASE + IF E4%<>RE% THEN AY%=E4%:GOSUB RELEASE - A%=A2%: GOTO EVAL_TCO_RECUR: REM TCO loop + A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: A%=Z%(A%,1): REM rest @@ -303,58 +300,58 @@ EVAL: REM TODO: TCO REM push EVAL_AST return label/address - ZL%=ZL%+1: ZZ%(ZL%)=2 + ZL%=ZL%+1:ZZ%(ZL%)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - ZL%=ZL%+1: ZZ%(ZL%)=R%: REM push eval'd list - A%=R%: GOSUB LAST: REM return the last element - AY%=ZZ%(ZL%): ZL%=ZL%-1: REM pop eval'd list + ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push eval'd list + A%=R%:GOSUB LAST: REM return the last element + AY%=ZZ%(ZL%):ZL%=ZL%-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_QUOTE: - R%=Z%(A%,1)+1: GOSUB DEREF_R + R%=Z%(A%,1)+1:GOSUB DEREF_R Z%(R%,0)=Z%(R%,0)+16 GOTO EVAL_RETURN EVAL_QUASIQUOTE: - R%=Z%(A%,1)+1: GOSUB DEREF_R - A%=R%: GOSUB QUASIQUOTE + R%=Z%(A%,1)+1:GOSUB DEREF_R + A%=R%:GOSUB QUASIQUOTE REM add quasiquote result to pending release queue to free when REM next lower EVAL level returns (LV%) - ZM%=ZM%+1: ZR%(ZM%,0)=R%: ZR%(ZM%,1)=LV% + ZM%=ZM%+1:ZR%(ZM%,0)=R%:ZR%(ZM%,1)=LV% - A%=R%: GOTO EVAL_TCO_RECUR: REM TCO loop + A%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF: GOSUB EVAL_GET_A1: REM set a1% REM push A% - ZL%=ZL%+1: ZZ%(ZL%)=A% - A%=A1%: GOSUB EVAL + ZL%=ZL%+1:ZZ%(ZL%)=A% + A%=A1%:GOSUB EVAL REM pop A% - A%=ZZ%(ZL%): ZL%=ZL%-1 + A%=ZZ%(ZL%):ZL%=ZL%-1 IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: - AY%=R%: GOSUB RELEASE + AY%=R%:GOSUB RELEASE GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A%=A2%: GOTO EVAL_TCO_RECUR: REM TCO loop + A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: - AY%=R%: GOSUB RELEASE + AY%=R%:GOSUB RELEASE REM if no false case (A3%), return nil - IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0: GOTO EVAL_RETURN + IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A%=A3%: GOTO EVAL_TCO_RECUR: REM TCO loop + A%=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set a1% and a2% - A%=A2%: P%=A1%: GOSUB MAL_FUNCTION + A%=A2%:P%=A1%:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - ZL%=ZL%+1: ZZ%(ZL%)=3 + ZL%=ZL%+1:ZZ%(ZL%)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: @@ -362,56 +359,56 @@ EVAL: IF ER%<>0 THEN GOTO EVAL_RETURN REM push f/args for release after call - ZL%=ZL%+1: ZZ%(ZL%)=R% + ZL%=ZL%+1:ZZ%(ZL%)=R% F%=R%+1 AR%=Z%(R%,1): REM rest - R%=F%: GOSUB DEREF_R: F%=R% + R%=F%:GOSUB DEREF_R:F%=R% IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R%=ZZ%(ZL%): ZL%=ZL%-1 - ER%=1: ER$="apply of non-function": GOTO EVAL_RETURN + R%=ZZ%(ZL%):ZL%=ZL%-1 + ER%=1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: E4%=E%: REM save the current environment for release REM create new environ using env stored with function - EO%=Z%(F%+1,1): BI%=Z%(F%+1,0): EX%=AR%: GOSUB ENV_NEW_BINDS + EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (ZZ%(ZL%-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%: GOSUB RELEASE + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE REM claim the AST before releasing the list containing it - A%=Z%(F%,1): Z%(A%,0)=Z%(A%,0)+16 + A%=Z%(F%,1):Z%(A%,0)=Z%(A%,0)+16 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV%+1) - ZM%=ZM%+1: ZR%(ZM%,0)=A%: ZR%(ZM%,1)=LV%+1 + ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV%+1 REM pop and release f/args - AY%=ZZ%(ZL%): ZL%=ZL%-1: GOSUB RELEASE + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE REM A% set above - E%=R%: GOTO EVAL_TCO_RECUR: REM TCO loop + E%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: REM AZ%=R%: PR%=1: GOSUB PR_STR REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) REM release environment if not the top one on the stack - IF E%<>ZZ%(ZL%-1) THEN AY%=E%: GOSUB RELEASE + IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE LV%=LV%-1: REM track basic return stack level @@ -422,13 +419,13 @@ EVAL: TA%=FRE(0) REM pop A% and E% off the stack - E%=ZZ%(ZL%-1): A%=ZZ%(ZL%): ZL%=ZL%-2 + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 RETURN REM PRINT(A%) -> R$ MAL_PRINT: - AZ%=A%: PR%=1: GOSUB PR_STR + AZ%=A%:PR%=1:GOSUB PR_STR RETURN REM RE(A$) -> R% @@ -440,32 +437,32 @@ RE: R1%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: E%=RE%: GOSUB EVAL + A%=R%:E%=RE%:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ - IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - R1%=0: R2%=0 + R1%=0:R2%=0 GOSUB MAL_READ R1%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: E%=RE%: GOSUB EVAL + A%=R%:E%=RE%:GOSUB EVAL R2%=R% IF ER%<>0 THEN GOTO REP_DONE - A%=R%: GOSUB MAL_PRINT + A%=R%:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2%<>0 THEN AY%=R2%: GOSUB RELEASE - IF R1%<>0 THEN AY%=R1%: GOSUB RELEASE + IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE R$=RT$ RETURN @@ -476,49 +473,52 @@ MAIN: LV%=0 REM create repl_env - EO%=-1: GOSUB ENV_NEW: RE%=R% + EO%=-1:GOSUB ENV_NEW:RE%=R% REM core.EXT: defined in Basic - E%=RE%: GOSUB INIT_CORE_NS: REM set core functions in repl_env + E%=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env ZT%=ZI%: REM top of memory after base repl_env REM core.mal: defined using the language itself - A$="(def! not (fn* (a) (if a false true)))": GOSUB RE: AY%=R%: GOSUB RELEASE + A$="(def! not (fn* (a) (if a false true)))" + GOSUB RE:AY%=R%:GOSUB RELEASE A$="(def! load-file (fn* (f) (eval (read-string (str " - A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) " - A$=A$+CHR$(34)+")"+CHR$(34)+")))))" - GOSUB RE: AY%=R%: GOSUB RELEASE + A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))" + GOSUB RE:AY%=R%:GOSUB RELEASE REM load the args file A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" - GOSUB RE: AY%=R%: GOSUB RELEASE + GOSUB RE:AY%=R%:GOSUB RELEASE REM set the argument list - A$="(def! *ARGV* (rest -*ARGS*-))": GOSUB RE: AY%=R%: GOSUB RELEASE + A$="(def! *ARGV* (rest -*ARGS*-))" + GOSUB RE:AY%=R%:GOSUB RELEASE REM get the first argument - A$="(first -*ARGS*-)": GOSUB RE + A$="(first -*ARGS*-)" + GOSUB RE REM if there is an argument, then run it as a program - IF R%<>0 THEN AY%=R%: GOSUB RELEASE: GOTO RUN_PROG + IF R%<>0 THEN AY%=R%:GOSUB RELEASE:GOTO RUN_PROG REM no arguments, start REPL loop IF R%=0 THEN GOTO REPL_LOOP RUN_PROG: REM run a single mal program and exit - A$="(load-file (first -*ARGS*-))": GOSUB RE + A$="(load-file (first -*ARGS*-))" + GOSUB RE IF ER%<>0 THEN GOSUB PRINT_ERROR END REPL_LOOP: - A$="user> ": GOSUB READLINE: REM call input parser + A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$: GOSUB REP: REM call REP + A$=R$:GOSUB REP: REM call REP - IF ER%<>0 THEN GOSUB PRINT_ERROR: GOTO REPL_LOOP + IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -529,6 +529,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0: ER$="" + ER%=0:ER$="" RETURN diff --git a/basic/types.in.bas b/basic/types.in.bas index ffa720ab50..10a268eda8 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -29,16 +29,17 @@ INIT_MEMORY: S4%=64: REM ZR% (release stack) size (4 bytes each) REM global error state - ER%=0: ER$="" + ER%=0:ER$="" REM boxed element memory DIM Z%(S1%,1): REM TYPE ARRAY REM Predefine nil, false, true, and an empty list - Z%(0,0)=0: Z%(0,1)=0 - Z%(1,0)=1: Z%(1,1)=0 - Z%(2,0)=1: Z%(2,1)=1 - Z%(3,0)=6+16: Z%(3,1)=0: Z%(4,0)=0: Z%(4,1)=0 + Z%(0,0)=0:Z%(0,1)=0 + Z%(1,0)=1:Z%(1,1)=0 + Z%(2,0)=1:Z%(2,1)=1 + Z%(3,0)=6+16:Z%(3,1)=0 + Z%(4,0)=0:Z%(4,1)=0 REM start of unused memory ZI%=5 @@ -47,13 +48,13 @@ INIT_MEMORY: ZK%=5 REM string memory storage - ZJ%=0: DIM ZS$(S2%) + ZJ%=0:DIM ZS$(S2%) REM call/logic stack - ZL%=-1: DIM ZZ%(S3%): REM stack of Z% indexes + ZL%=-1:DIM ZZ%(S3%): REM stack of Z% indexes REM pending release stack - ZM%=-1: DIM ZR%(S4%,1): REM stack of Z% indexes + ZM%=-1:DIM ZR%(S4%,1): REM stack of Z% indexes REM PRINT "Lisp data memory: "+STR$(T%-FRE(0)) REM PRINT "Interpreter working memory: "+STR$(FRE(0)) @@ -64,7 +65,8 @@ REM memory functions REM ALLOC(SZ%) -> R% ALLOC: REM PRINT "ALLOC SZ%: "+STR$(SZ%)+", ZK%: "+STR$(ZK%) - U3%=ZK%: U4%=ZK% + U3%=ZK% + U4%=ZK% ALLOC_LOOP: IF U4%=ZI% THEN GOTO ALLOC_UNUSED REM TODO sanity check that type is 15 @@ -94,9 +96,10 @@ REM FREE(AY%, SZ%) -> nil FREE: REM assumes reference count cleanup already (see RELEASE) Z%(AY%,0)=(SZ%*16)+15: REM set type(15) and size - Z%(AY%,1)=ZK%: ZK%=AY% - IF SZ%>=2 THEN Z%(AY%+1,0)=0: Z%(AY%+1,1)=0 - IF SZ%>=3 THEN Z%(AY%+2,0)=0: Z%(AY%+2,1)=0 + Z%(AY%,1)=ZK% + ZK%=AY% + IF SZ%>=2 THEN Z%(AY%+1,0)=0:Z%(AY%+1,1)=0 + IF SZ%>=3 THEN Z%(AY%+2,0)=0:Z%(AY%+2,1)=0 RETURN @@ -112,7 +115,7 @@ RELEASE: IF RC%=0 THEN RETURN REM pop next object to release, decrease remaining count - AY%=ZZ%(ZL%): ZL%=ZL%-1 + AY%=ZZ%(ZL%):ZL%=ZL%-1 RC%=RC%-1 RELEASE_ONE: @@ -126,8 +129,8 @@ RELEASE: REM PRINT "RELEASE AY%:"+STR$(AY%)+"["+R$+"] (byte0:"+STR$(Z%(AY%,0))+")" REM sanity check not already freed - IF (U6%)=15 THEN ER%=1: ER$="Free of free memory: "+STR$(AY%): RETURN - IF Z%(AY%,0)<15 THEN ER%=1: ER$="Free of freed object: "+STR$(AY%): RETURN + IF (U6%)=15 THEN ER%=1:ER$="Free of free memory: "+STR$(AY%):RETURN + IF Z%(AY%,0)<15 THEN ER%=1:ER$="Free of freed object: "+STR$(AY%):RETURN REM decrease reference count by one Z%(AY%,0)=Z%(AY%,0)-16 @@ -142,52 +145,52 @@ RELEASE: IF U6%=12 THEN GOTO RELEASE_ATOM IF U6%=13 THEN GOTO RELEASE_ENV IF U6%=14 THEN GOTO RELEASE_REFERENCE - IF U6%=15 THEN ER%=1: ER$="RELEASE of already freed: "+STR$(AY%): RETURN - ER%=1: ER$="RELEASE not defined for type "+STR$(U6%): RETURN + IF U6%=15 THEN ER%=1:ER$="RELEASE of already freed: "+STR$(AY%):RETURN + ER%=1:ER$="RELEASE not defined for type "+STR$(U6%):RETURN RELEASE_SIMPLE: REM simple type (no recursing), just call FREE on it - SZ%=1: GOSUB FREE + SZ%=1:GOSUB FREE GOTO RELEASE_TOP RELEASE_SIMPLE_2: REM free the current element and continue - SZ%=2: GOSUB FREE + SZ%=2:GOSUB FREE GOTO RELEASE_TOP RELEASE_SEQ: IF Z%(AY%,1)=0 THEN GOTO RELEASE_SIMPLE_2 - IF Z%(AY%+1,0)<>14 THEN ER%=1: ER$="invalid list value"+STR$(AY%+1): RETURN + IF Z%(AY%+1,0)<>14 THEN ER%=1:ER$="invalid list value"+STR$(AY%+1):RETURN REM add value and next element to stack - RC%=RC%+2: ZL%=ZL%+2: ZZ%(ZL%-1)=Z%(AY%+1,1): ZZ%(ZL%)=Z%(AY%,1) + RC%=RC%+2:ZL%=ZL%+2:ZZ%(ZL%-1)=Z%(AY%+1,1):ZZ%(ZL%)=Z%(AY%,1) GOTO RELEASE_SIMPLE_2 RELEASE_ATOM: REM add contained/referred value - RC%=RC%+1: ZL%=ZL%+1: ZZ%(ZL%)=Z%(AY%,1) + RC%=RC%+1:ZL%=ZL%+1:ZZ%(ZL%)=Z%(AY%,1) REM free the atom itself GOTO RELEASE_SIMPLE RELEASE_MAL_FUNCTION: REM add ast, params and environment to stack - RC%=RC%+3: ZL%=ZL%+3 - ZZ%(ZL%-2)=Z%(AY%,1): ZZ%(ZL%-1)=Z%(AY%+1,0): ZZ%(ZL%)=Z%(AY%+1,1) + RC%=RC%+3:ZL%=ZL%+3 + ZZ%(ZL%-2)=Z%(AY%,1):ZZ%(ZL%-1)=Z%(AY%+1,0):ZZ%(ZL%)=Z%(AY%+1,1) REM free the current 2 element mal_function and continue - SZ%=2: GOSUB FREE + SZ%=2:GOSUB FREE GOTO RELEASE_TOP RELEASE_ENV: REM add the hashmap data to the stack - RC%=RC%+1: ZL%=ZL%+1: ZZ%(ZL%)=Z%(AY%,1) + RC%=RC%+1:ZL%=ZL%+1:ZZ%(ZL%)=Z%(AY%,1) REM if no outer set IF Z%(AY%+1,1)=-1 THEN GOTO RELEASE_ENV_FREE REM add outer environment to the stack - RC%=RC%+1: ZL%=ZL%+1: ZZ%(ZL%)=Z%(AY%+1,1) + RC%=RC%+1:ZL%=ZL%+1:ZZ%(ZL%)=Z%(AY%+1,1) RELEASE_ENV_FREE: REM free the current 2 element environment and continue - SZ%=2: GOSUB FREE + SZ%=2:GOSUB FREE GOTO RELEASE_TOP RELEASE_REFERENCE: IF Z%(AY%,1)=0 THEN GOTO RELEASE_SIMPLE REM add the referred element to the stack - RC%=RC%+1: ZL%=ZL%+1: ZZ%(ZL%)=Z%(AY%,1) + RC%=RC%+1:ZL%=ZL%+1:ZZ%(ZL%)=Z%(AY%,1) REM free the current element and continue - SZ%=1: GOSUB FREE + SZ%=1:GOSUB FREE GOTO RELEASE_TOP REM RELEASE_PEND(LV%) -> nil @@ -196,30 +199,32 @@ RELEASE_PEND: IF ZM%<0 THEN RETURN IF ZR%(ZM%,1)<=LV% THEN RETURN REM PRINT "RELEASE_PEND releasing:"+STR$(ZR%(ZM%,0)) - AY%=ZR%(ZM%,0): GOSUB RELEASE + AY%=ZR%(ZM%,0):GOSUB RELEASE ZM%=ZM%-1 GOTO RELEASE_PEND REM DEREF_R(R%) -> R% DEREF_R: - IF (Z%(R%,0)AND15)=14 THEN R%=Z%(R%,1): GOTO DEREF_R + IF (Z%(R%,0)AND15)=14 THEN R%=Z%(R%,1):GOTO DEREF_R RETURN REM DEREF_A(A%) -> A% DEREF_A: - IF (Z%(A%,0)AND15)=14 THEN A%=Z%(A%,1): GOTO DEREF_A + IF (Z%(A%,0)AND15)=14 THEN A%=Z%(A%,1):GOTO DEREF_A RETURN REM DEREF_B(B%) -> B% DEREF_B: - IF (Z%(B%,0)AND15)=14 THEN B%=Z%(B%,1): GOTO DEREF_B + IF (Z%(B%,0)AND15)=14 THEN B%=Z%(B%,1):GOTO DEREF_B RETURN CHECK_FREE_LIST: - P1%=ZK%: P2%=0: REM start and accumulator + REM start and accumulator + P1%=ZK% + P2%=0 CHECK_FREE_LIST_LOOP: IF P1%>=ZI% THEN GOTO CHECK_FREE_LIST_DONE - IF (Z%(P1%,0)AND15)<>15 THEN P2%=-1: GOTO CHECK_FREE_LIST_DONE + IF (Z%(P1%,0)AND15)<>15 THEN P2%=-1:GOTO CHECK_FREE_LIST_DONE P2%=P2%+(Z%(P1%,0)AND-16)/16 P1%=Z%(P1%,1) GOTO CHECK_FREE_LIST_LOOP @@ -232,10 +237,12 @@ REM general functions REM EQUAL_Q(A%, B%) -> R% EQUAL_Q: - GOSUB DEREF_A: GOSUB DEREF_B + GOSUB DEREF_A + GOSUB DEREF_B R%=0 - U1%=(Z%(A%,0)AND15): U2%=(Z%(B%,0)AND15) + U1%=(Z%(A%,0)AND15) + U2%=(Z%(B%,0)AND15) IF NOT ((U1%=U2%) OR ((U1%=6 OR U1%=7) AND (U2%=6 OR U2%=7))) THEN RETURN IF U1%=6 THEN GOTO EQUAL_Q_SEQ IF U1%=7 THEN GOTO EQUAL_Q_SEQ @@ -245,18 +252,19 @@ EQUAL_Q: RETURN EQUAL_Q_SEQ: - IF (Z%(A%,1)=0) AND (Z%(B%,1)=0) THEN R%=1: RETURN - IF (Z%(A%,1)=0) OR (Z%(B%,1)=0) THEN R%=0: RETURN + IF (Z%(A%,1)=0) AND (Z%(B%,1)=0) THEN R%=1:RETURN + IF (Z%(A%,1)=0) OR (Z%(B%,1)=0) THEN R%=0:RETURN REM push A% and B% - ZL%=ZL%+2: ZZ%(ZL%-1)=A%: ZZ%(ZL%)=B% - A%=Z%(A%+1,1): B%=Z%(B%+1,1): GOSUB EQUAL_Q + ZL%=ZL%+2:ZZ%(ZL%-1)=A%:ZZ%(ZL%)=B% + REM compare the elements + A%=Z%(A%+1,1):B%=Z%(B%+1,1):GOSUB EQUAL_Q REM pop A% and B% - A%=ZZ%(ZL%-1): B%=ZZ%(ZL%): ZL%=ZL%-2 + A%=ZZ%(ZL%-1):B%=ZZ%(ZL%):ZL%=ZL%-2 IF R%=0 THEN RETURN REM next elements of the sequences - A%=Z%(A%,1): B%=Z%(B%,1): GOTO EQUAL_Q_SEQ + A%=Z%(A%,1):B%=Z%(B%,1):GOTO EQUAL_Q_SEQ EQUAL_Q_HM: R%=0 RETURN @@ -270,7 +278,7 @@ STRING_: REM search for matching string in ZS$ FOR I=0 TO ZJ%-1 - IF AS$=ZS$(I) THEN R%=I: RETURN + IF AS$=ZS$(I) THEN R%=I:RETURN NEXT I STRING_NOT_FOUND: @@ -284,7 +292,7 @@ REM intern string and allocate reference (return Z% index) STRING: GOSUB STRING_ TS%=R% - SZ%=1: GOSUB ALLOC + SZ%=1:GOSUB ALLOC Z%(R%,0)=T% Z%(R%,1)=TS% RETURN @@ -293,12 +301,13 @@ REM REPLACE(R$, S1$, S2$) -> R$ REPLACE: T3$=R$ R$="" - I=1: J=LEN(T3$) + I=1 + J=LEN(T3$) REPLACE_LOOP: IF I>J THEN RETURN CH$=MID$(T3$,I,LEN(S1$)) - IF CH$=S1$ THEN R$=R$+S2$: I=I+LEN(S1$) - IF CH$<>S1$ THEN R$=R$+MID$(T3$,I,1): I=I+1 + IF CH$=S1$ THEN R$=R$+S2$:I=I+LEN(S1$) + IF CH$<>S1$ THEN R$=R$+MID$(T3$,I,1):I=I+1 GOTO REPLACE_LOOP @@ -321,13 +330,13 @@ COUNT: R%=-1 DO_COUNT_LOOP: R%=R%+1 - IF Z%(A%,1)<>0 THEN A%=Z%(A%,1): GOTO DO_COUNT_LOOP + IF Z%(A%,1)<>0 THEN A%=Z%(A%,1):GOTO DO_COUNT_LOOP RETURN REM LAST(A%) -> R% LAST: REM TODO check that actually a list/vector - IF Z%(A%,1)=0 THEN R%=0: RETURN: REM empty seq, return nil + IF Z%(A%,1)=0 THEN R%=0:RETURN: REM empty seq, return nil T6%=0 LAST_LOOP: IF Z%(A%,1)=0 THEN GOTO LAST_DONE: REM end, return previous value @@ -335,13 +344,13 @@ LAST: A%=Z%(A%,1): REM next entry GOTO LAST_LOOP LAST_DONE: - R%=T6%+1: GOSUB DEREF_R + R%=T6%+1:GOSUB DEREF_R Z%(R%,0)=Z%(R%,0)+16 RETURN REM CONS(A%,B%) -> R% CONS: - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC Z%(R%,0)=6+16 Z%(R%,1)=B% Z%(R%+1,0)=14 @@ -360,21 +369,21 @@ SLICE: R6%=0: REM previous list element SLICE_LOOP: REM always allocate at list one list element - SZ%=2: GOSUB ALLOC - Z%(R%,0)=6+16: Z%(R%,1)=0: Z%(R%+1,0)=14: Z%(R%+1,1)=0 + SZ%=2:GOSUB ALLOC + Z%(R%,0)=6+16:Z%(R%,1)=0:Z%(R%+1,0)=14:Z%(R%+1,1)=0 IF R5%=-1 THEN R5%=R% IF R5%<>-1 THEN Z%(R6%,1)=R% REM advance A% to position B% SLICE_FIND_B: - IF I0 THEN A%=Z%(A%,1): I=I+1: GOTO SLICE_FIND_B + IF I0 THEN A%=Z%(A%,1):I=I+1:GOTO SLICE_FIND_B REM if current position is C%, then return - IF C%<>-1 AND I>=C% THEN R%=R5%: RETURN + IF C%<>-1 AND I>=C% THEN R%=R5%:RETURN REM if we reached end of A%, then return - IF Z%(A%,1)=0 THEN R%=R5%: RETURN + IF Z%(A%,1)=0 THEN R%=R5%:RETURN R6%=R%: REM save previous list element REM copy value and inc ref cnt Z%(R6%+1,1)=Z%(A%+1,1) - R%=A%+1: GOSUB DEREF_R: Z%(R%,0)=Z%(R%,0)+16 + R%=A%+1:GOSUB DEREF_R:Z%(R%,0)=Z%(R%,0)+16 REM advance to next element of A% A%=Z%(A%,1) I=I+1 @@ -383,28 +392,28 @@ SLICE: REM LIST2(B2%,B1%) -> R% LIST2: REM terminator - SZ%=2: GOSUB ALLOC: TB%=R% - Z%(R%,0)=6+16: Z%(R%,1)=0: Z%(R%+1,0)=0: Z%(R%+1,1)=0 + SZ%=2:GOSUB ALLOC:TB%=R% + Z%(R%,0)=6+16:Z%(R%,1)=0:Z%(R%+1,0)=0:Z%(R%+1,1)=0 REM second element is B1% - SZ%=2: GOSUB ALLOC: TC%=R% - Z%(R%,0)=6+16: Z%(R%,1)=TB%: Z%(R%+1,0)=14: Z%(R%+1,1)=B1% + SZ%=2:GOSUB ALLOC:TC%=R% + Z%(R%,0)=6+16:Z%(R%,1)=TB%:Z%(R%+1,0)=14:Z%(R%+1,1)=B1% Z%(B1%,0)=Z%(B1%,0)+16 REM first element is B2% - SZ%=2: GOSUB ALLOC - Z%(R%,0)=6+16: Z%(R%,1)=TC%: Z%(R%+1,0)=14: Z%(R%+1,1)=B2% + SZ%=2:GOSUB ALLOC + Z%(R%,0)=6+16:Z%(R%,1)=TC%:Z%(R%+1,0)=14:Z%(R%+1,1)=B2% Z%(B2%,0)=Z%(B2%,0)+16 RETURN REM LIST3(B3%,B2%,B1%) -> R% LIST3: - GOSUB LIST2: TC%=R% + GOSUB LIST2:TC%=R% REM first element is B3% - SZ%=2: GOSUB ALLOC - Z%(R%,0)=6+16: Z%(R%,1)=TC%: Z%(R%+1,0)=14: Z%(R%+1,1)=B3% + SZ%=2:GOSUB ALLOC + Z%(R%,0)=6+16:Z%(R%,1)=TC%:Z%(R%+1,0)=14:Z%(R%+1,1)=B3% Z%(B3%,0)=Z%(B3%,0)+16 RETURN @@ -413,7 +422,7 @@ REM hashmap functions REM HASHMAP() -> R% HASHMAP: - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC Z%(R%,0)=8+16 Z%(R%,1)=0 Z%(R%+1,0)=14 @@ -423,13 +432,13 @@ HASHMAP: REM ASSOC1(HM%, K%, V%) -> R% ASSOC1: REM deref to actual key and value - R%=K%: GOSUB DEREF_R: K%=R% - R%=V%: GOSUB DEREF_R: V%=R% + R%=K%:GOSUB DEREF_R:K%=R% + R%=V%:GOSUB DEREF_R:V%=R% REM inc ref count of key and value Z%(K%,0)=Z%(K%,0)+16 Z%(V%,0)=Z%(V%,0)+16 - SZ%=4: GOSUB ALLOC + SZ%=4:GOSUB ALLOC REM key ptr Z%(R%,0)=8+16 Z%(R%,1)=R%+2: REM point to next element (value) @@ -445,7 +454,7 @@ ASSOC1: REM ASSOC1(HM%, K$, V%) -> R% ASSOC1_S: REM add the key string, then call ASSOC1 - SZ%=1: GOSUB ALLOC + SZ%=1:GOSUB ALLOC K%=R% ZS$(ZJ%)=K$ Z%(R%,0)=4: REM key ref cnt will be inc'd by ASSOC1 @@ -462,15 +471,15 @@ HASHMAP_GET: R%=0 HASHMAP_GET_LOOP: REM no matching key found - IF Z%(H2%,1)=0 THEN R%=0: RETURN + IF Z%(H2%,1)=0 THEN R%=0:RETURN REM follow value ptrs T2%=H2%+1 HASHMAP_GET_DEREF: - IF Z%(T2%,0)=14 THEN T2%=Z%(T2%,1): GOTO HASHMAP_GET_DEREF + IF Z%(T2%,0)=14 THEN T2%=Z%(T2%,1):GOTO HASHMAP_GET_DEREF REM get key string T2$=ZS$(Z%(T2%,1)) REM if they are equal, we found it - IF T1$=T2$ THEN T3%=1: R%=Z%(H2%,1)+1: RETURN + IF T1$=T2$ THEN T3%=1:R%=Z%(H2%,1)+1:RETURN REM skip to next key H2%=Z%(Z%(H2%,1),1) GOTO HASHMAP_GET_LOOP @@ -483,14 +492,14 @@ HASHMAP_CONTAINS: REM NATIVE_FUNCTION(A%) -> R% NATIVE_FUNCTION: - SZ%=1: GOSUB ALLOC + SZ%=1:GOSUB ALLOC Z%(R%,0)=9+16 Z%(R%,1)=A% RETURN REM NATIVE_FUNCTION(A%, P%, E%) -> R% MAL_FUNCTION: - SZ%=2: GOSUB ALLOC + SZ%=2:GOSUB ALLOC Z%(A%,0)=Z%(A%,0)+16 Z%(P%,0)=Z%(P%,0)+16 Z%(E%,0)=Z%(E%,0)+16 From 42f304e8c9b4fba6a9a06058e434495468794b0a Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Fri, 30 Sep 2016 10:05:43 +0200 Subject: [PATCH 0154/2308] Implement step 0 --- Makefile | 3 ++- pil/Makefile | 17 +++++++++++++++++ pil/readline.l | 17 +++++++++++++++++ pil/run | 2 ++ pil/step0_repl.l | 24 ++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 pil/Makefile create mode 100644 pil/readline.l create mode 100755 pil/run create mode 100644 pil/step0_repl.l diff --git a/Makefile b/Makefile index f1727fb9b7..618680d955 100644 --- a/Makefile +++ b/Makefile @@ -80,7 +80,7 @@ DOCKERIZE = IMPLS = ada awk bash c d chuck clojure coffee clisp cpp crystal cs erlang elisp \ elixir es6 factor forth fsharp go groovy guile haskell haxe \ io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ - nim objc objpascal perl perl6 php plpgsql plsql powershell ps \ + nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ python r racket rpython ruby rust scala swift swift3 tcl vb vhdl \ vimscript @@ -183,6 +183,7 @@ objpascal_STEP_TO_PROG = objpascal/$($(1)) perl_STEP_TO_PROG = perl/$($(1)).pl perl6_STEP_TO_PROG = perl6/$($(1)).pl php_STEP_TO_PROG = php/$($(1)).php +pil_STEP_TO_PROG = pil/$($(1)).l plpgsql_STEP_TO_PROG = plpgsql/$($(1)).sql plsql_STEP_TO_PROG = plsql/$($(1)).sql powershell_STEP_TO_PROG = powershell/$($(1)).ps1 diff --git a/pil/Makefile b/pil/Makefile new file mode 100644 index 0000000000..af2701370b --- /dev/null +++ b/pil/Makefile @@ -0,0 +1,17 @@ +SOURCES_BASE = reader.l printer.l types.l +SOURCES_LISP = env.l func.l core.l stepA_mal.l +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +all: + +clean: + +.PHONY: stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" + diff --git a/pil/readline.l b/pil/readline.l new file mode 100644 index 0000000000..65185928aa --- /dev/null +++ b/pil/readline.l @@ -0,0 +1,17 @@ +(de load-history (File) + (when (info File) + (in File + (until (eof) + (native "libreadline.so" "add_history" NIL (line T)) ) ) ) ) + +(de save-to-history (Input) + (when Input + (native "libreadline.so" "add_history" NIL Input) + (out "+.mal_history" + (prinl Input) ) ) ) + +(de readline (Prompt) + (prog1 + (native "libreadline.so" "readline" 'S Prompt) + (save-to-history @) ) ) + diff --git a/pil/run b/pil/run new file mode 100755 index 0000000000..7412791dab --- /dev/null +++ b/pil/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec pil $(dirname $0)/${STEP:-stepA_mal}.l "${@}" diff --git a/pil/step0_repl.l b/pil/step0_repl.l new file mode 100644 index 0000000000..a4ce69df16 --- /dev/null +++ b/pil/step0_repl.l @@ -0,0 +1,24 @@ +(de load-relative (Path) + (load (pack (car (file)) Path)) ) + +(load-relative "readline.l") + +(de READ (String) + String) + +(de EVAL (Ast) + Ast) + +(de PRINT (Ast) + Ast) + +(de rep (String) + (PRINT (EVAL (READ String))) ) + +(load-history ".mal_history") + +(while (readline "user> ") + (prinl (rep @)) ) + +(prinl) +(bye) From ed976bf7b0c1555cf337594389419a8bb1933d3a Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Fri, 30 Sep 2016 10:39:42 +0200 Subject: [PATCH 0155/2308] Distinguish EOF from empty input --- pil/readline.l | 10 ++++++---- pil/step0_repl.l | 8 ++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pil/readline.l b/pil/readline.l index 65185928aa..777e7d3e98 100644 --- a/pil/readline.l +++ b/pil/readline.l @@ -11,7 +11,9 @@ (prinl Input) ) ) ) (de readline (Prompt) - (prog1 - (native "libreadline.so" "readline" 'S Prompt) - (save-to-history @) ) ) - + (let Input (native "libreadline.so" "readline" 'N Prompt) + (if (=0 Input) + 0 + (prog1 + (struct Input 'S) + (save-to-history @) ) ) ) ) diff --git a/pil/step0_repl.l b/pil/step0_repl.l index a4ce69df16..0f40e9bd0f 100644 --- a/pil/step0_repl.l +++ b/pil/step0_repl.l @@ -17,8 +17,12 @@ (load-history ".mal_history") -(while (readline "user> ") - (prinl (rep @)) ) +(use Eof + (until Eof + (let Input (readline "user> ") + (if (=0 Input) + (setq Eof T) + (prinl (rep Input)) ) ) ) ) (prinl) (bye) From a870ad3f5e6dd3990bd0f5d7e9a447432b652973 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 2 Oct 2016 15:55:19 +0200 Subject: [PATCH 0156/2308] Implement step 1 --- pil/printer.l | 16 ++++++ pil/reader.l | 124 +++++++++++++++++++++++++++++++++++++++++ pil/step1_read_print.l | 36 ++++++++++++ pil/types.l | 58 +++++++++++++++++++ 4 files changed, 234 insertions(+) create mode 100644 pil/printer.l create mode 100644 pil/reader.l create mode 100644 pil/step1_read_print.l create mode 100644 pil/types.l diff --git a/pil/printer.l b/pil/printer.l new file mode 100644 index 0000000000..5090f23e2a --- /dev/null +++ b/pil/printer.l @@ -0,0 +1,16 @@ +(de pr-str (Ast PrintReadably) + (let Value (get Ast 'value) + (case (get Ast 'type) + ((true false nil) + (sym @) ) + (string (if PrintReadably (if Value (sym Value) "\"\"") Value)) + (keyword (pack ":" Value)) + ((number symbol) Value) + (list (pr-list Value PrintReadably "(" ")")) + (vector (pr-list Value PrintReadably "[" "]")) + (map (pr-list Value PrintReadably "{" "}")) + (T (throw 'err (new '(+MALError) "[pr-str] unimplemented type"))) ) ) ) + +(de pr-list (Forms PrintReadably Starter Ender) + (let Values (mapcar '((Form) (pr-str Form PrintReadably)) Forms) + (pack Starter (glue " " Values) Ender) ) ) diff --git a/pil/reader.l b/pil/reader.l new file mode 100644 index 0000000000..5be9d3dc6c --- /dev/null +++ b/pil/reader.l @@ -0,0 +1,124 @@ +(class +Reader) +# tokens +(dm T (Tokens) + (=: tokens Tokens) ) + +(dm next> () + (pop (:: tokens)) ) + +(dm peek> () + (car (: tokens)) ) + +(de read-str (String) + (let (Tokens (tokenizer String) + Reader (new '(+Reader) Tokens) ) + (read-form Reader) ) ) + +(de tokenizer (String) + # [\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*) + (let (Special " []{}()'\"`,;" ) + (make + (for (Chars (chop String) Chars) + (let Char (pop 'Chars) + (cond + ((member Char '(" " ",")) + # do nothing, whitespace + ) + ((and (= Char "~") (= (car Chars) "@")) + (link "~@") + (pop 'Chars) ) # remove @ token + ((index Char (chop "[]{}()'`~^\@")) + (link Char) ) + ((= Char "\"") + (link + (pack + (make + (link Char) + (let (Char (car Chars) Done NIL) + (while (and (not Done) Chars) + (link (pop 'Chars)) + (cond + ((= Char "\\") + (link (pop 'Chars)) # add char after \ + (setq Char (car Chars)) ) + ((<> Char "\"") + (setq Char (car Chars)) ) + ((= Char "\"") + (setq Done T) ) ) ) ) ) ) ) ) + ((= Char ";") + (while (and Chars (<> Char "\n")) + (setq Char (pop 'Chars)) ) ) + ((not (index Char (chop Special))) + (link + (pack + (make + (link Char) + (let Char (car Chars) + (while (and Chars (not (index Char (chop Special)))) + (link (pop 'Chars)) + (setq Char (car Chars)) ) ) ) ) ) ) ) ) ) ) ) ) + +(de read-form (Reader) + (case (peek> Reader) + ("'" (read-macro Reader 'quote)) + ("`" (read-macro Reader 'quasiquote)) + ("~" (read-macro Reader 'unquote)) + ("~@" (read-macro Reader 'splice-unquote)) + ("@" (read-macro Reader 'deref)) + ("\^" (read-meta Reader)) + ("(" (read-list Reader 'list ")")) + ("[" (read-list Reader 'vector "]")) + ("{" (read-list Reader 'map "}")) + (T (read-atom Reader)) ) ) + +(de read-macro (Reader symbol) + (next> Reader) # pop reader macro token + (new '(+MALList) (list (new '(+MALSymbol) symbol) (read-form Reader))) ) + +(de read-meta (Reader) + (next> Reader) # pop reader macro token + (let Form (read-form Reader) + (new '(+MALList) + (list (new '(+MALSymbol) 'with-meta) (read-form Reader) Form) ) ) ) + +(de read-list (Reader Type Ender) + (next> Reader) # pop list start + (new (list (case Type + (list '+MALList) + (vector '+MALVector) + (map '+MALMap) ) ) + (make + (use Done + (while (not Done) + (let Token (peek> Reader) + (cond + ((= Token Ender) + (next> Reader) # pop list end + (setq Done T) ) + ((not Token) + (let (Msg (pack "expected '" Ender "', got EOF") + Err (new '(+MALError) Msg)) + (throw 'err Err) ) ) + (T (link (read-form Reader))) ) ) ) ) ) ) ) + +(de read-atom (Reader) + (let (Token (next> Reader) + Chars (chop Token)) + (cond + ((= Token "true") + *MAL-true) + ((= Token "false") + *MAL-false) + ((= Token "nil") + *MAL-nil) + ((format Token) + (new '(+MALNumber) @) ) + ((= (car Chars) "\"") + (if (= (last Chars) "\"") + (new '(+MALString) (any Token)) + (throw 'err (new '(+MALError) "expected '\"', got EOF")) ) ) + ((= (car Chars) ":") + (new '(+MALKeyword) (pack (cdr Chars))) ) + ((not Token) + (throw 'err (new '(+MALError) "end of token stream")) ) + (T (new '(+MALSymbol) Token)) ) ) ) diff --git a/pil/step1_read_print.l b/pil/step1_read_print.l new file mode 100644 index 0000000000..042fb5a0e6 --- /dev/null +++ b/pil/step1_read_print.l @@ -0,0 +1,36 @@ +(de load-relative (Path) + (load (pack (car (file)) Path)) ) + +(load-relative "readline.l") +(load-relative "types.l") +(load-relative "reader.l") +(load-relative "printer.l") + +(de READ (String) + (read-str String) ) + +(de EVAL (Ast) + Ast) + +(de PRINT (Ast) + (pr-str Ast T) ) + +(de rep (String) + (PRINT (EVAL (READ String))) ) + +(load-history ".mal_history") + +(use Eof + (until Eof + (let Input (readline "user> ") + (if (=0 Input) + (setq Eof T) + (let Output (catch 'err (rep Input)) + (if (isa '+MALError Output) + (let Message (get Output 'value) + (unless (= Message "end of token stream") + (prinl "[error] " Message) ) ) + (prinl Output) ) ) ) ) ) ) + +(prinl) +(bye) diff --git a/pil/types.l b/pil/types.l new file mode 100644 index 0000000000..3ff078b59c --- /dev/null +++ b/pil/types.l @@ -0,0 +1,58 @@ +(class +MAL) +# type value meta +(dm T (Type Value Meta) + (=: type Type) + (=: value Value) + (=: meta Meta) ) + +(class +MALTrue +MAL) +(dm T () + (super 'true T NIL) ) + +(class +MALFalse +MAL) +(dm T () + (super 'false NIL NIL) ) + +(class +MALNil +MAL) +(dm T () + (super 'nil NIL NIL) ) + +(def '*MAL-true (new '(+MALTrue))) +(def '*MAL-false (new '(+MALFalse))) +(def '*MAL-nil (new '(+MALNil))) + +(class +MALNumber +MAL) +(dm T (Number) + (super 'number Number NIL) ) + +(class +MALString +MAL) +(dm T (String) + (super 'string String NIL) ) + +(class +MALSymbol +MAL) +(dm T (String) + (super 'symbol String NIL) ) + +(class +MALKeyword +MAL) +(dm T (String) + (super 'keyword String NIL) ) + +(class +MALList +MAL) +(dm T (Values) + (super 'list Values NIL) ) + +(class +MALVector +MAL) +(dm T (Values) + (super 'vector Values NIL) ) + +(class +MALMap +MAL) +(dm T (Values) + (super 'map Values NIL) ) + +(class +MALAtom +MAL) +(dm T (Value) + (super 'atom Value NIL) ) + +(class +MALError +MAL) +(dm T (Value) + (super 'error Value NIL) ) From 879a1d5d4e4a0650b0bc34eb66ff2a3b5b941277 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 2 Oct 2016 15:58:23 +0200 Subject: [PATCH 0157/2308] Update Makefile to include readline --- pil/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pil/Makefile b/pil/Makefile index af2701370b..2ee2780a22 100644 --- a/pil/Makefile +++ b/pil/Makefile @@ -1,4 +1,4 @@ -SOURCES_BASE = reader.l printer.l types.l +SOURCES_BASE = readline.l reader.l printer.l types.l SOURCES_LISP = env.l func.l core.l stepA_mal.l SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) From 8d229b7c807874e95baf723db728d98dfe2511c4 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 2 Oct 2016 19:41:49 +0200 Subject: [PATCH 0158/2308] Implement step 2 --- pil/printer.l | 6 ++--- pil/reader.l | 22 +++++++--------- pil/step1_read_print.l | 2 +- pil/step2_eval.l | 59 ++++++++++++++++++++++++++++++++++++++++++ pil/types.l | 36 ++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 pil/step2_eval.l diff --git a/pil/printer.l b/pil/printer.l index 5090f23e2a..58a9515507 100644 --- a/pil/printer.l +++ b/pil/printer.l @@ -1,6 +1,6 @@ (de pr-str (Ast PrintReadably) - (let Value (get Ast 'value) - (case (get Ast 'type) + (let Value (MAL-value Ast) + (case (MAL-type Ast) ((true false nil) (sym @) ) (string (if PrintReadably (if Value (sym Value) "\"\"") Value)) @@ -9,7 +9,7 @@ (list (pr-list Value PrintReadably "(" ")")) (vector (pr-list Value PrintReadably "[" "]")) (map (pr-list Value PrintReadably "{" "}")) - (T (throw 'err (new '(+MALError) "[pr-str] unimplemented type"))) ) ) ) + (T (throw 'err (MAL-error "[pr-str] unimplemented type"))) ) ) ) (de pr-list (Forms PrintReadably Starter Ender) (let Values (mapcar '((Form) (pr-str Form PrintReadably)) Forms) diff --git a/pil/reader.l b/pil/reader.l index 5be9d3dc6c..accbb5bacf 100644 --- a/pil/reader.l +++ b/pil/reader.l @@ -73,13 +73,12 @@ (de read-macro (Reader symbol) (next> Reader) # pop reader macro token - (new '(+MALList) (list (new '(+MALSymbol) symbol) (read-form Reader))) ) + (MAL-list (list (MAL-symbol symbol) (read-form Reader))) ) (de read-meta (Reader) (next> Reader) # pop reader macro token (let Form (read-form Reader) - (new '(+MALList) - (list (new '(+MALSymbol) 'with-meta) (read-form Reader) Form) ) ) ) + (MAL-list (list (MAL-symbol 'with-meta) (read-form Reader) Form) ) ) ) (de read-list (Reader Type Ender) (next> Reader) # pop list start @@ -96,9 +95,8 @@ (next> Reader) # pop list end (setq Done T) ) ((not Token) - (let (Msg (pack "expected '" Ender "', got EOF") - Err (new '(+MALError) Msg)) - (throw 'err Err) ) ) + (let Msg (pack "expected '" Ender "', got EOF") + (throw 'err (MAL-error Msg)) ) ) (T (link (read-form Reader))) ) ) ) ) ) ) ) (de read-atom (Reader) @@ -112,13 +110,13 @@ ((= Token "nil") *MAL-nil) ((format Token) - (new '(+MALNumber) @) ) + (MAL-number @) ) ((= (car Chars) "\"") (if (= (last Chars) "\"") - (new '(+MALString) (any Token)) - (throw 'err (new '(+MALError) "expected '\"', got EOF")) ) ) + (MAL-string (any Token)) + (throw 'err (MAL-error "expected '\"', got EOF")) ) ) ((= (car Chars) ":") - (new '(+MALKeyword) (pack (cdr Chars))) ) + (MAL-keyword (pack (cdr Chars))) ) ((not Token) - (throw 'err (new '(+MALError) "end of token stream")) ) - (T (new '(+MALSymbol) Token)) ) ) ) + (throw 'err (MAL-error "end of token stream")) ) + (T (MAL-symbol Token)) ) ) ) diff --git a/pil/step1_read_print.l b/pil/step1_read_print.l index 042fb5a0e6..48341605a1 100644 --- a/pil/step1_read_print.l +++ b/pil/step1_read_print.l @@ -27,7 +27,7 @@ (setq Eof T) (let Output (catch 'err (rep Input)) (if (isa '+MALError Output) - (let Message (get Output 'value) + (let Message (MAL-value Output) (unless (= Message "end of token stream") (prinl "[error] " Message) ) ) (prinl Output) ) ) ) ) ) ) diff --git a/pil/step2_eval.l b/pil/step2_eval.l new file mode 100644 index 0000000000..9deab7fe28 --- /dev/null +++ b/pil/step2_eval.l @@ -0,0 +1,59 @@ +(de load-relative (Path) + (load (pack (car (file)) Path)) ) + +(load-relative "readline.l") +(load-relative "types.l") +(load-relative "reader.l") +(load-relative "printer.l") + +(de READ (String) + (read-str String) ) + +(def 'repl-env + '((+ . ((A B) (MAL-number (+ (MAL-value A) (MAL-value B))))) + (- . ((A B) (MAL-number (- (MAL-value A) (MAL-value B))))) + (* . ((A B) (MAL-number (* (MAL-value A) (MAL-value B))))) + (/ . ((A B) (MAL-number (/ (MAL-value A) (MAL-value B))))) ) ) + +(de EVAL (Ast Env) + (if (= (MAL-type Ast) 'list) + (if (not (MAL-value Ast)) + Ast + (let Value (MAL-value (eval-ast Ast Env)) + (apply (car Value) (cdr Value)) ) ) + (eval-ast Ast Env) ) ) + +(de eval-ast (Ast Env) + (let Value (MAL-value Ast) + (case (MAL-type Ast) + (symbol + (if (assoc Value Env) + (cdr @) + (throw 'err (MAL-error (pack "'" Value "' not found"))) ) ) + (list (MAL-list (mapcar '((Form) (EVAL Form Env)) Value))) + (vector (MAL-vector (mapcar '((Form) (EVAL Form Env)) Value))) + (map (MAL-map (mapcar '((Form) (EVAL Form Env)) Value))) + (T Ast) ) ) ) + +(de PRINT (Ast) + (pr-str Ast T) ) + +(de rep (String Env) + (PRINT (EVAL (READ String) Env)) ) + +(load-history ".mal_history") + +(use Eof + (until Eof + (let Input (readline "user> ") + (if (=0 Input) + (setq Eof T) + (let Output (catch 'err (rep Input repl-env)) + (if (isa '+MALError Output) + (let Message (MAL-value Output) + (unless (= Message "end of token stream") + (prinl "[error] " Message) ) ) + (prinl Output) ) ) ) ) ) ) + +(prinl) +(bye) diff --git a/pil/types.l b/pil/types.l index 3ff078b59c..4ce4bbe450 100644 --- a/pil/types.l +++ b/pil/types.l @@ -5,6 +5,15 @@ (=: value Value) (=: meta Meta) ) +(de MAL-type (MAL) + (get MAL 'type) ) + +(de MAL-value (MAL) + (get MAL 'value) ) + +(de MAL-meta (MAL) + (get MAL 'meta) ) + (class +MALTrue +MAL) (dm T () (super 'true T NIL) ) @@ -25,34 +34,61 @@ (dm T (Number) (super 'number Number NIL) ) +(de MAL-number (N) + (new '(+MALNumber) N) ) + (class +MALString +MAL) (dm T (String) (super 'string String NIL) ) +(de MAL-string (N) + (new '(+MALString) N) ) + (class +MALSymbol +MAL) (dm T (String) (super 'symbol String NIL) ) +(de MAL-symbol (N) + (new '(+MALSymbol) N) ) + (class +MALKeyword +MAL) (dm T (String) (super 'keyword String NIL) ) +(de MAL-keyword (N) + (new '(+MALKeyword) N) ) + (class +MALList +MAL) (dm T (Values) (super 'list Values NIL) ) +(de MAL-list (N) + (new '(+MALList) N) ) + (class +MALVector +MAL) (dm T (Values) (super 'vector Values NIL) ) +(de MAL-vector (N) + (new '(+MALVector) N) ) + (class +MALMap +MAL) (dm T (Values) (super 'map Values NIL) ) +(de MAL-map (N) + (new '(+MALMap) N) ) + (class +MALAtom +MAL) (dm T (Value) (super 'atom Value NIL) ) +(de MAL-atom (N) + (new '(+MALAtom) N) ) + (class +MALError +MAL) (dm T (Value) (super 'error Value NIL) ) + +(de MAL-error (Value) + (new '(+MALError) Value) ) From 53db2d63cbab254def4831539fe7cacf34912c2d Mon Sep 17 00:00:00 2001 From: Prat Date: Sun, 2 Oct 2016 13:49:28 -0400 Subject: [PATCH 0159/2308] Support GHC 7.10.1+. Update error handling --- haskell/Core.hs | 2 +- haskell/Types.hs | 10 ++++------ haskell/step1_read_print.hs | 4 ++-- haskell/step2_eval.hs | 4 ++-- haskell/step3_env.hs | 4 ++-- haskell/step4_if_fn_do.hs | 8 ++++---- haskell/step5_tco.hs | 8 ++++---- haskell/step6_file.hs | 14 +++++++------- haskell/step7_quote.hs | 14 +++++++------- haskell/step8_macros.hs | 24 ++++++++++++------------ haskell/step9_try.hs | 26 +++++++++++++------------- haskell/stepA_mal.hs | 32 ++++++++++++++++---------------- 12 files changed, 74 insertions(+), 76 deletions(-) diff --git a/haskell/Core.hs b/haskell/Core.hs index da7a2dd00b..99a2bfa30f 100644 --- a/haskell/Core.hs +++ b/haskell/Core.hs @@ -275,7 +275,7 @@ ns = [ ("*", _func $ num_op (*)), ("/", _func $ num_op (div)), ("time-ms", _func $ time_ms), - + ("list", _func $ list), ("list?", _func $ run_1 _list_Q), ("vector", _func $ vector), diff --git a/haskell/Types.hs b/haskell/Types.hs index fb9812bb1c..fc26ddb32c 100644 --- a/haskell/Types.hs +++ b/haskell/Types.hs @@ -9,7 +9,7 @@ where import Data.IORef (IORef) import qualified Data.Map as Map import Control.Exception as CE -import Control.Monad.Error (ErrorT, Error, noMsg, strMsg, throwError) +import Control.Monad.Except -- Base Mal types -- @@ -55,13 +55,11 @@ instance Eq MalVal where data MalError = StringError String | MalValError MalVal -type IOThrows = ErrorT MalError IO - -instance Error MalError where - noMsg = StringError "An error has occurred" - strMsg = StringError +type IOThrows = ExceptT MalError IO +throwStr :: String -> IOThrows a throwStr str = throwError $ StringError str +throwMalVal :: MalVal -> IOThrows a throwMalVal mv = throwError $ MalValError mv -- Env types -- diff --git a/haskell/step1_read_print.hs b/haskell/step1_read_print.hs index c7a4eef5ce..4f396d69fa 100644 --- a/haskell/step1_read_print.hs +++ b/haskell/step1_read_print.hs @@ -1,5 +1,5 @@ import System.IO (hFlush, stdout) -import Control.Monad.Error (runErrorT) +import Control.Monad.Except (runExceptT) import Readline (readline, load_history) import Types @@ -31,7 +31,7 @@ repl_loop = do Nothing -> return () Just "" -> repl_loop Just str -> do - res <- runErrorT $ rep str + res <- runExceptT $ rep str out <- case res of Left (StringError str) -> return $ "Error: " ++ str Left (MalValError mv) -> return $ "Error: " ++ (show mv) diff --git a/haskell/step2_eval.hs b/haskell/step2_eval.hs index bc40f17833..e02e21c8f1 100644 --- a/haskell/step2_eval.hs +++ b/haskell/step2_eval.hs @@ -1,6 +1,6 @@ import System.IO (hFlush, stdout) import Control.Monad (mapM) -import Control.Monad.Error (runErrorT) +import Control.Monad.Except (runExceptT) import qualified Data.Map as Map import qualified Data.Traversable as DT @@ -81,7 +81,7 @@ repl_loop = do Nothing -> return () Just "" -> repl_loop Just str -> do - res <- runErrorT $ rep str + res <- runExceptT $ rep str out <- case res of Left (StringError str) -> return $ "Error: " ++ str Left (MalValError mv) -> return $ "Error: " ++ (show mv) diff --git a/haskell/step3_env.hs b/haskell/step3_env.hs index 428027fdd2..be065fa148 100644 --- a/haskell/step3_env.hs +++ b/haskell/step3_env.hs @@ -1,6 +1,6 @@ import System.IO (hFlush, stdout) import Control.Monad (mapM) -import Control.Monad.Error (runErrorT) +import Control.Monad.Except (runExceptT) import Control.Monad.Trans (liftIO) import qualified Data.Map as Map import qualified Data.Traversable as DT @@ -95,7 +95,7 @@ repl_loop env = do Nothing -> return () Just "" -> repl_loop env Just str -> do - res <- runErrorT $ rep env str + res <- runExceptT $ rep env str out <- case res of Left (StringError str) -> return $ "Error: " ++ str Left (MalValError mv) -> return $ "Error: " ++ (show mv) diff --git a/haskell/step4_if_fn_do.hs b/haskell/step4_if_fn_do.hs index 5624716f5a..526f99e680 100644 --- a/haskell/step4_if_fn_do.hs +++ b/haskell/step4_if_fn_do.hs @@ -1,6 +1,6 @@ import System.IO (hFlush, stdout) import Control.Monad (mapM) -import Control.Monad.Error (runErrorT) +import Control.Monad.Except (runExceptT) import Control.Monad.Trans (liftIO) import qualified Data.Map as Map import qualified Data.Traversable as DT @@ -61,7 +61,7 @@ apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do el <- eval_ast (MalList args Nil) env case el of (MalList lst _) -> return $ last lst - + apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do case args of (a1 : a2 : a3 : []) -> do @@ -119,7 +119,7 @@ repl_loop env = do Nothing -> return () Just "" -> repl_loop env Just str -> do - res <- runErrorT $ rep env str + res <- runExceptT $ rep env str out <- case res of Left (StringError str) -> return $ "Error: " ++ str Left (MalValError mv) -> return $ "Error: " ++ (show mv) @@ -137,6 +137,6 @@ main = do (mapM (\(k,v) -> (env_set repl_env (MalSymbol k) v)) Core.ns) -- core.mal: defined using the language itself - runErrorT $ rep repl_env "(def! not (fn* (a) (if a false true)))" + runExceptT $ rep repl_env "(def! not (fn* (a) (if a false true)))" repl_loop repl_env diff --git a/haskell/step5_tco.hs b/haskell/step5_tco.hs index 4e9def5c38..8b9ef32c2b 100644 --- a/haskell/step5_tco.hs +++ b/haskell/step5_tco.hs @@ -1,6 +1,6 @@ import System.IO (hFlush, stdout) import Control.Monad (mapM) -import Control.Monad.Error (runErrorT) +import Control.Monad.Except (runExceptT) import Control.Monad.Trans (liftIO) import qualified Data.Map as Map import qualified Data.Traversable as DT @@ -61,7 +61,7 @@ apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do el <- eval_ast (MalList args Nil) env case el of (MalList lst _) -> return $ last lst - + apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do case args of (a1 : a2 : a3 : []) -> do @@ -123,7 +123,7 @@ repl_loop env = do Nothing -> return () Just "" -> repl_loop env Just str -> do - res <- runErrorT $ rep env str + res <- runExceptT $ rep env str out <- case res of Left (StringError str) -> return $ "Error: " ++ str Left (MalValError mv) -> return $ "Error: " ++ (show mv) @@ -141,6 +141,6 @@ main = do (mapM (\(k,v) -> (env_set repl_env (MalSymbol k) v)) Core.ns) -- core.mal: defined using the language itself - runErrorT $ rep repl_env "(def! not (fn* (a) (if a false true)))" + runExceptT $ rep repl_env "(def! not (fn* (a) (if a false true)))" repl_loop repl_env diff --git a/haskell/step6_file.hs b/haskell/step6_file.hs index bc2897cf33..692077720e 100644 --- a/haskell/step6_file.hs +++ b/haskell/step6_file.hs @@ -1,7 +1,7 @@ import System.IO (hFlush, stdout) import System.Environment (getArgs) import Control.Monad (mapM) -import Control.Monad.Error (runErrorT) +import Control.Monad.Except (runExceptT) import Control.Monad.Trans (liftIO) import qualified Data.Map as Map import qualified Data.Traversable as DT @@ -62,7 +62,7 @@ apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do el <- eval_ast (MalList args Nil) env case el of (MalList lst _) -> return $ last lst - + apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do case args of (a1 : a2 : a3 : []) -> do @@ -124,7 +124,7 @@ repl_loop env = do Nothing -> return () Just "" -> repl_loop env Just str -> do - res <- runErrorT $ rep env str + res <- runExceptT $ rep env str out <- case res of Left (StringError str) -> return $ "Error: " ++ str Left (MalValError mv) -> return $ "Error: " ++ (show mv) @@ -145,12 +145,12 @@ main = do env_set repl_env (MalSymbol "*ARGV*") (MalList [] Nil) -- core.mal: defined using the language itself - runErrorT $ rep repl_env "(def! not (fn* (a) (if a false true)))" - runErrorT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" + runExceptT $ rep repl_env "(def! not (fn* (a) (if a false true)))" + runExceptT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" if length args > 0 then do env_set repl_env (MalSymbol "*ARGV*") (MalList (map MalString (drop 1 args)) Nil) - runErrorT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")" + runExceptT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")" return () - else + else repl_loop repl_env diff --git a/haskell/step7_quote.hs b/haskell/step7_quote.hs index 60158c30f8..b944c74add 100644 --- a/haskell/step7_quote.hs +++ b/haskell/step7_quote.hs @@ -1,7 +1,7 @@ import System.IO (hFlush, stdout) import System.Environment (getArgs) import Control.Monad (mapM) -import Control.Monad.Error (runErrorT) +import Control.Monad.Except (runExceptT) import Control.Monad.Trans (liftIO) import qualified Data.Map as Map import qualified Data.Traversable as DT @@ -91,7 +91,7 @@ apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do el <- eval_ast (MalList args Nil) env case el of (MalList lst _) -> return $ last lst - + apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do case args of (a1 : a2 : a3 : []) -> do @@ -153,7 +153,7 @@ repl_loop env = do Nothing -> return () Just "" -> repl_loop env Just str -> do - res <- runErrorT $ rep env str + res <- runExceptT $ rep env str out <- case res of Left (StringError str) -> return $ "Error: " ++ str Left (MalValError mv) -> return $ "Error: " ++ (show mv) @@ -174,12 +174,12 @@ main = do env_set repl_env (MalSymbol "*ARGV*") (MalList [] Nil) -- core.mal: defined using the language itself - runErrorT $ rep repl_env "(def! not (fn* (a) (if a false true)))" - runErrorT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" + runExceptT $ rep repl_env "(def! not (fn* (a) (if a false true)))" + runExceptT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" if length args > 0 then do env_set repl_env (MalSymbol "*ARGV*") (MalList (map MalString (drop 1 args)) Nil) - runErrorT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")" + runExceptT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")" return () - else + else repl_loop repl_env diff --git a/haskell/step8_macros.hs b/haskell/step8_macros.hs index 64a42d539c..9c31391542 100644 --- a/haskell/step8_macros.hs +++ b/haskell/step8_macros.hs @@ -1,7 +1,7 @@ import System.IO (hFlush, stdout) import System.Environment (getArgs) import Control.Monad (mapM) -import Control.Monad.Error (runErrorT) +import Control.Monad.Except (runExceptT) import Control.Monad.Trans (liftIO) import qualified Data.Map as Map import qualified Data.Traversable as DT @@ -55,7 +55,7 @@ macroexpand ast@(MalList (a0 : args) _) env = do mc <- is_macro_call ast env if mc then do mac <- env_get env a0 - case mac of + case mac of MalFunc {fn=(Fn f)} -> do new_ast <- f args macroexpand new_ast env @@ -122,11 +122,11 @@ apply_ast ast@(MalList (MalSymbol "defmacro!" : args) _) env = do meta=Nil} in liftIO $ env_set env a1 new_func _ -> throwStr "defmacro! on non-function" - _ -> throwStr "invalid defmacro!" + _ -> throwStr "invalid defmacro!" apply_ast ast@(MalList (MalSymbol "macroexpand" : args) _) env = do case args of (a1 : []) -> macroexpand a1 env - _ -> throwStr "invalid macroexpand" + _ -> throwStr "invalid macroexpand" apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do case args of ([]) -> return Nil @@ -134,7 +134,7 @@ apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do el <- eval_ast (MalList args Nil) env case el of (MalList lst _) -> return $ last lst - + apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do case args of (a1 : a2 : a3 : []) -> do @@ -206,7 +206,7 @@ repl_loop env = do Nothing -> return () Just "" -> repl_loop env Just str -> do - res <- runErrorT $ rep env str + res <- runExceptT $ rep env str out <- case res of Left (StringError str) -> return $ "Error: " ++ str Left (MalValError mv) -> return $ "Error: " ++ (show mv) @@ -227,14 +227,14 @@ main = do env_set repl_env (MalSymbol "*ARGV*") (MalList [] Nil) -- core.mal: defined using the language itself - runErrorT $ rep repl_env "(def! not (fn* (a) (if a false true)))" - runErrorT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" - runErrorT $ rep repl_env "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" - runErrorT $ rep repl_env "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" + runExceptT $ rep repl_env "(def! not (fn* (a) (if a false true)))" + runExceptT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" + runExceptT $ rep repl_env "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" + runExceptT $ rep repl_env "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" if length args > 0 then do env_set repl_env (MalSymbol "*ARGV*") (MalList (map MalString (drop 1 args)) Nil) - runErrorT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")" + runExceptT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")" return () - else + else repl_loop repl_env diff --git a/haskell/step9_try.hs b/haskell/step9_try.hs index ddcabdca87..f4688e7cd6 100644 --- a/haskell/step9_try.hs +++ b/haskell/step9_try.hs @@ -1,7 +1,7 @@ import System.IO (hFlush, stdout) import System.Environment (getArgs) import Control.Monad (mapM) -import Control.Monad.Error (runErrorT) +import Control.Monad.Except (runExceptT) import Control.Monad.Trans (liftIO) import qualified Data.Map as Map import qualified Data.Traversable as DT @@ -55,7 +55,7 @@ macroexpand ast@(MalList (a0 : args) _) env = do mc <- is_macro_call ast env if mc then do mac <- env_get env a0 - case mac of + case mac of MalFunc {fn=(Fn f)} -> do new_ast <- f args macroexpand new_ast env @@ -122,16 +122,16 @@ apply_ast ast@(MalList (MalSymbol "defmacro!" : args) _) env = do meta=Nil} in liftIO $ env_set env a1 new_func _ -> throwStr "defmacro! on non-function" - _ -> throwStr "invalid defmacro!" + _ -> throwStr "invalid defmacro!" apply_ast ast@(MalList (MalSymbol "macroexpand" : args) _) env = do case args of (a1 : []) -> macroexpand a1 env - _ -> throwStr "invalid macroexpand" + _ -> throwStr "invalid macroexpand" apply_ast ast@(MalList (MalSymbol "try*" : args) _) env = do case args of (a1 : []) -> eval a1 env (a1 : (MalList ((MalSymbol "catch*") : a21 : a22 : []) _) : []) -> do - res <- liftIO $ runErrorT $ eval a1 env + res <- liftIO $ runExceptT $ eval a1 env case res of Right val -> return val Left err -> do @@ -149,7 +149,7 @@ apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do el <- eval_ast (MalList args Nil) env case el of (MalList lst _) -> return $ last lst - + apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do case args of (a1 : a2 : a3 : []) -> do @@ -221,7 +221,7 @@ repl_loop env = do Nothing -> return () Just "" -> repl_loop env Just str -> do - res <- runErrorT $ rep env str + res <- runExceptT $ rep env str out <- case res of Left (StringError str) -> return $ "Error: " ++ str Left (MalValError mv) -> return $ "Error: " ++ (show mv) @@ -242,14 +242,14 @@ main = do env_set repl_env (MalSymbol "*ARGV*") (MalList [] Nil) -- core.mal: defined using the language itself - runErrorT $ rep repl_env "(def! not (fn* (a) (if a false true)))" - runErrorT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" - runErrorT $ rep repl_env "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" - runErrorT $ rep repl_env "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" + runExceptT $ rep repl_env "(def! not (fn* (a) (if a false true)))" + runExceptT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" + runExceptT $ rep repl_env "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" + runExceptT $ rep repl_env "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" if length args > 0 then do env_set repl_env (MalSymbol "*ARGV*") (MalList (map MalString (drop 1 args)) Nil) - runErrorT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")" + runExceptT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")" return () - else + else repl_loop repl_env diff --git a/haskell/stepA_mal.hs b/haskell/stepA_mal.hs index 42fb67819b..f914ac89c9 100644 --- a/haskell/stepA_mal.hs +++ b/haskell/stepA_mal.hs @@ -1,7 +1,7 @@ import System.IO (hFlush, stdout) import System.Environment (getArgs) import Control.Monad (mapM) -import Control.Monad.Error (runErrorT) +import Control.Monad.Except (runExceptT) import Control.Monad.Trans (liftIO) import qualified Data.Map as Map import qualified Data.Traversable as DT @@ -55,7 +55,7 @@ macroexpand ast@(MalList (a0 : args) _) env = do mc <- is_macro_call ast env if mc then do mac <- env_get env a0 - case mac of + case mac of MalFunc {fn=(Fn f)} -> do new_ast <- f args macroexpand new_ast env @@ -122,16 +122,16 @@ apply_ast ast@(MalList (MalSymbol "defmacro!" : args) _) env = do meta=Nil} in liftIO $ env_set env a1 new_func _ -> throwStr "defmacro! on non-function" - _ -> throwStr "invalid defmacro!" + _ -> throwStr "invalid defmacro!" apply_ast ast@(MalList (MalSymbol "macroexpand" : args) _) env = do case args of (a1 : []) -> macroexpand a1 env - _ -> throwStr "invalid macroexpand" + _ -> throwStr "invalid macroexpand" apply_ast ast@(MalList (MalSymbol "try*" : args) _) env = do case args of (a1 : []) -> eval a1 env (a1 : (MalList ((MalSymbol "catch*") : a21 : a22 : []) _) : []) -> do - res <- liftIO $ runErrorT $ eval a1 env + res <- liftIO $ runExceptT $ eval a1 env case res of Right val -> return val Left err -> do @@ -149,7 +149,7 @@ apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do el <- eval_ast (MalList args Nil) env case el of (MalList lst _) -> return $ last lst - + apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do case args of (a1 : a2 : a3 : []) -> do @@ -221,7 +221,7 @@ repl_loop env = do Nothing -> return () Just "" -> repl_loop env Just str -> do - res <- runErrorT $ rep env str + res <- runExceptT $ rep env str out <- case res of Left (StringError str) -> return $ "Error: " ++ str Left (MalValError mv) -> return $ "Error: " ++ (show mv) @@ -242,18 +242,18 @@ main = do env_set repl_env (MalSymbol "*ARGV*") (MalList [] Nil) -- core.mal: defined using the language itself - runErrorT $ rep repl_env "(def! *host-language* \"haskell\")" - runErrorT $ rep repl_env "(def! not (fn* (a) (if a false true)))" - runErrorT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" - runErrorT $ rep repl_env "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" - runErrorT $ rep repl_env "(def! *gensym-counter* (atom 0))" - runErrorT $ rep repl_env "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))" - runErrorT $ rep repl_env "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))" + runExceptT $ rep repl_env "(def! *host-language* \"haskell\")" + runExceptT $ rep repl_env "(def! not (fn* (a) (if a false true)))" + runExceptT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" + runExceptT $ rep repl_env "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" + runExceptT $ rep repl_env "(def! *gensym-counter* (atom 0))" + runExceptT $ rep repl_env "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))" + runExceptT $ rep repl_env "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))" if length args > 0 then do env_set repl_env (MalSymbol "*ARGV*") (MalList (map MalString (drop 1 args)) Nil) - runErrorT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")" + runExceptT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")" return () else do - runErrorT $ rep repl_env "(println (str \"Mal [\" *host-language* \"]\"))" + runExceptT $ rep repl_env "(println (str \"Mal [\" *host-language* \"]\"))" repl_loop repl_env From f5763ca16a719691c1991303b0cb1d60abb896d6 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 3 Oct 2016 00:31:43 +0200 Subject: [PATCH 0160/2308] Implement step 3 --- pil/env.l | 19 +++++++++++++ pil/reader.l | 4 +-- pil/step3_env.l | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 pil/env.l create mode 100644 pil/step3_env.l diff --git a/pil/env.l b/pil/env.l new file mode 100644 index 0000000000..b745dade45 --- /dev/null +++ b/pil/env.l @@ -0,0 +1,19 @@ +(class +Env) +# data outer +(dm T (Outer) + (=: data (new)) + (=: outer Outer) ) + +(de MAL-env (Outer) + (new '(+Env) Outer) ) + +(dm set> (Key Value) + (put (: data) Key Value) ) + +(dm find> (Key) + (or (get (: data) Key) + (and (: outer) (find> @ Key)) ) ) + +(dm get> (Key) + (or (find> This Key) + (throw 'err (MAL-error (pack "'" Key "' not found"))) ) ) diff --git a/pil/reader.l b/pil/reader.l index accbb5bacf..dc0cde8fc2 100644 --- a/pil/reader.l +++ b/pil/reader.l @@ -116,7 +116,7 @@ (MAL-string (any Token)) (throw 'err (MAL-error "expected '\"', got EOF")) ) ) ((= (car Chars) ":") - (MAL-keyword (pack (cdr Chars))) ) + (MAL-keyword (intern (pack (cdr Chars)))) ) ((not Token) (throw 'err (MAL-error "end of token stream")) ) - (T (MAL-symbol Token)) ) ) ) + (T (MAL-symbol (intern Token))) ) ) ) diff --git a/pil/step3_env.l b/pil/step3_env.l new file mode 100644 index 0000000000..d810df0704 --- /dev/null +++ b/pil/step3_env.l @@ -0,0 +1,71 @@ +(de load-relative (Path) + (load (pack (car (file)) Path)) ) + +(load-relative "readline.l") +(load-relative "types.l") +(load-relative "reader.l") +(load-relative "printer.l") +(load-relative "env.l") + +(de READ (String) + (read-str String) ) + +(def 'repl-env (MAL-env NIL)) +(set> repl-env '+ '((A B) (MAL-number (+ (MAL-value A) (MAL-value B))))) +(set> repl-env '- '((A B) (MAL-number (- (MAL-value A) (MAL-value B))))) +(set> repl-env '* '((A B) (MAL-number (* (MAL-value A) (MAL-value B))))) +(set> repl-env '/ '((A B) (MAL-number (/ (MAL-value A) (MAL-value B))))) + +(de EVAL (Ast Env) + (if (= (MAL-type Ast) 'list) + (if (not (MAL-value Ast)) + Ast + (let (Ast* (MAL-value Ast) + A0* (MAL-value (car Ast*)) + A1* (MAL-value (cadr Ast*)) + A2 (caddr Ast*)) + (cond + ((= A0* 'def!) + (set> Env A1* (EVAL A2 Env)) ) + ((= A0* 'let*) + (let Env* (MAL-env Env) + (for (Bindings A1* Bindings) + (let (Key (MAL-value (pop 'Bindings)) + Value (EVAL (pop 'Bindings) Env*)) + (set> Env* Key Value) ) ) + (EVAL A2 Env*) ) ) + (T (let Value (MAL-value (eval-ast Ast Env)) + (apply (car Value) (cdr Value)) ) ) ) ) ) + (eval-ast Ast Env) ) ) + +(de eval-ast (Ast Env) + (let Value (MAL-value Ast) + (case (MAL-type Ast) + (symbol (get> Env Value)) + (list (MAL-list (mapcar '((Form) (EVAL Form Env)) Value))) + (vector (MAL-vector (mapcar '((Form) (EVAL Form Env)) Value))) + (map (MAL-map (mapcar '((Form) (EVAL Form Env)) Value))) + (T Ast) ) ) ) + +(de PRINT (Ast) + (pr-str Ast T) ) + +(de rep (String Env) + (PRINT (EVAL (READ String) Env)) ) + +(load-history ".mal_history") + +(use Eof + (until Eof + (let Input (readline "user> ") + (if (=0 Input) + (setq Eof T) + (let Output (catch 'err (rep Input repl-env)) + (if (isa '+MALError Output) + (let Message (MAL-value Output) + (unless (= Message "end of token stream") + (prinl "[error] " Message) ) ) + (prinl Output) ) ) ) ) ) ) + +(prinl) +(bye) From 118269ab74ae06c6e4b62a3cb035491215582d8b Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 4 Oct 2016 22:10:55 +0200 Subject: [PATCH 0161/2308] Implement step 4 --- pil/core.l | 46 ++++++++++++++++++++++ pil/env.l | 13 +++++-- pil/func.l | 10 +++++ pil/printer.l | 14 ++++++- pil/step4_if_fn_do.l | 90 ++++++++++++++++++++++++++++++++++++++++++++ pil/types.l | 13 +++++-- 6 files changed, 177 insertions(+), 9 deletions(-) create mode 100644 pil/core.l create mode 100644 pil/func.l create mode 100644 pil/step4_if_fn_do.l diff --git a/pil/core.l b/pil/core.l new file mode 100644 index 0000000000..ac761f7fcd --- /dev/null +++ b/pil/core.l @@ -0,0 +1,46 @@ +(de MAL-= (A B) + (let (A* (MAL-type A) + B* (MAL-type B)) + (cond + ((and (= A* 'map) (= B* 'map)) + # TODO + NIL) + ((and (memq A* '(list vector)) (memq B* '(list vector))) + (MAL-seq-= (MAL-value A) (MAL-value B)) ) + ((= A* B*) + (= (MAL-value A) (MAL-value B)) ) + (T NIL) ) ) ) + +(de MAL-seq-= (As Bs) + (when (= (length As) (length Bs)) + (catch 'result + (while As + (ifn (MAL-= (pop 'As) (pop 'Bs)) + (throw 'result NIL) ) ) + T) ) ) + +(de MAL-seq? (X) + (memq (MAL-type X) '(list vector)) ) + +(def 'ns + '((+ . `(MAL-fn '((A B) (MAL-number (+ (MAL-value A) (MAL-value B)))))) + (- . `(MAL-fn '((A B) (MAL-number (- (MAL-value A) (MAL-value B)))))) + (* . `(MAL-fn '((A B) (MAL-number (* (MAL-value A) (MAL-value B)))))) + (/ . `(MAL-fn '((A B) (MAL-number (/ (MAL-value A) (MAL-value B)))))) + + (< . `(MAL-fn '((A B) (if (< (MAL-value A) (MAL-value B)) *MAL-true *MAL-false)))) + (<= . `(MAL-fn '((A B) (if (<= (MAL-value A) (MAL-value B)) *MAL-true *MAL-false)))) + (> . `(MAL-fn '((A B) (if (> (MAL-value A) (MAL-value B)) *MAL-true *MAL-false)))) + (>= . `(MAL-fn '((A B) (if (>= (MAL-value A) (MAL-value B)) *MAL-true *MAL-false)))) + + (= . `(MAL-fn '((A B) (if (MAL-= A B) *MAL-true *MAL-false)))) + + (list . `(MAL-fn '(@ (MAL-list (rest))))) + (list? . `(MAL-fn '((X) (if (= (MAL-type X) 'list) *MAL-true *MAL-false)))) + (empty? . `(MAL-fn '((X) (if (and (MAL-seq? X) (not (MAL-value X))) *MAL-true *MAL-false)))) + (count . `(MAL-fn '((X) (if (MAL-seq? X) (MAL-number (length (MAL-value X))) (MAL-number 0))))) + + (pr-str . `(MAL-fn '(@ (MAL-string (glue " " (mapcar '((X) (pr-str X T)) (rest))))))) + (str . `(MAL-fn '(@ (MAL-string (pack (mapcar pr-str (rest))))))) + (prn . `(MAL-fn '(@ (prinl (glue " " (mapcar '((X) (pr-str X T)) (rest)))) *MAL-nil))) + (println . `(MAL-fn '(@ (prinl (glue " " (mapcar pr-str (rest)))) *MAL-nil))) ) ) diff --git a/pil/env.l b/pil/env.l index b745dade45..9474698771 100644 --- a/pil/env.l +++ b/pil/env.l @@ -1,11 +1,16 @@ (class +Env) # data outer -(dm T (Outer) +(dm T (Outer Binds Exprs) (=: data (new)) - (=: outer Outer) ) + (=: outer Outer) + (for (Binds Binds Binds) + (if (<> (car Binds) '&) + (set> This (pop 'Binds) (pop 'Exprs)) + (pop 'Binds) + (set> This (pop 'Binds) (MAL-list Exprs)) ) ) ) -(de MAL-env (Outer) - (new '(+Env) Outer) ) +(de MAL-env (Outer Binds Exprs) + (new '(+Env) Outer Binds Exprs) ) (dm set> (Key Value) (put (: data) Key Value) ) diff --git a/pil/func.l b/pil/func.l new file mode 100644 index 0000000000..e63bae5f09 --- /dev/null +++ b/pil/func.l @@ -0,0 +1,10 @@ +(class +Func) +# env ast params fn +(dm T (Env Ast Params Fn) + (=: env Env) + (=: ast Ast) + (=: params Params) + (=: fn Fn) ) + +(de MAL-func (Env Ast Params Fn) + (new '(+Func) Env Ast Params Fn) ) diff --git a/pil/printer.l b/pil/printer.l index 58a9515507..3b54b1771c 100644 --- a/pil/printer.l +++ b/pil/printer.l @@ -3,13 +3,23 @@ (case (MAL-type Ast) ((true false nil) (sym @) ) - (string (if PrintReadably (if Value (sym Value) "\"\"") Value)) + (string (if PrintReadably (repr Value) Value)) (keyword (pack ":" Value)) ((number symbol) Value) + (fn "#") (list (pr-list Value PrintReadably "(" ")")) (vector (pr-list Value PrintReadably "[" "]")) (map (pr-list Value PrintReadably "{" "}")) - (T (throw 'err (MAL-error "[pr-str] unimplemented type"))) ) ) ) + (T (pretty Value) (throw 'err (MAL-error "[pr-str] unimplemented type"))) ) ) ) + +(de repr (X) + (let Chars (chop X) + (if (not X) + "\"\"" + (setq Chars (replace Chars "\\" "\\\\")) + (setq Chars (replace Chars "\"" "\\\"")) + (setq Chars (replace Chars "\n" "\\n")) + (pack "\"" Chars "\"") ) ) ) (de pr-list (Forms PrintReadably Starter Ender) (let Values (mapcar '((Form) (pr-str Form PrintReadably)) Forms) diff --git a/pil/step4_if_fn_do.l b/pil/step4_if_fn_do.l new file mode 100644 index 0000000000..f5ee441e96 --- /dev/null +++ b/pil/step4_if_fn_do.l @@ -0,0 +1,90 @@ +(de load-relative (Path) + (load (pack (car (file)) Path)) ) + +(load-relative "readline.l") +(load-relative "types.l") +(load-relative "reader.l") +(load-relative "printer.l") +(load-relative "env.l") +(load-relative "func.l") +(load-relative "core.l") + +(de READ (String) + (read-str String) ) + +(def 'repl-env (MAL-env NIL)) +(for Bind ns (set> repl-env (car Bind) (cdr Bind))) + +(de EVAL (Ast Env) + (if (= (MAL-type Ast) 'list) + (if (not (MAL-value Ast)) + Ast + (let (Ast* (MAL-value Ast) + A0* (MAL-value (car Ast*)) + A1 (cadr Ast*) + A1* (MAL-value A1) + A2 (caddr Ast*) + A3 (cadddr Ast*) ) + (cond + ((= A0* 'def!) + (set> Env A1* (EVAL A2 Env)) ) + ((= A0* 'let*) + (let Env* (MAL-env Env) + (for (Bindings A1* Bindings) + (let (Key (MAL-value (pop 'Bindings)) + Value (EVAL (pop 'Bindings) Env*)) + (set> Env* Key Value) ) ) + (EVAL A2 Env*) ) ) + ((= A0* 'do) + (for Form (cdr Ast*) + (EVAL Form Env) ) ) + ((= A0* 'if) + (if (not (memq (MAL-type (EVAL A1 Env)) '(nil false))) + (EVAL A2 Env) + (if A3 + (EVAL A3 Env) + *MAL-nil ) ) ) + ((= A0* 'fn*) + (let (Binds (mapcar MAL-value A1*) + Body A2) + (MAL-fn + (curry (Env Binds Body) @ + (let Env* (MAL-env Env Binds (rest)) + (EVAL Body Env*) ) ) ) ) ) + (T + (let (Ast* (MAL-value (eval-ast Ast Env)) + Fn (MAL-value (car Ast*)) + Args (cdr Ast*)) + (apply Fn Args) ) ) ) ) ) + (eval-ast Ast Env) ) ) + +(de eval-ast (Ast Env) + (let Value (MAL-value Ast) + (case (MAL-type Ast) + (symbol (get> Env Value)) + (list (MAL-list (mapcar '((Form) (EVAL Form Env)) Value))) + (vector (MAL-vector (mapcar '((Form) (EVAL Form Env)) Value))) + (map (MAL-map (mapcar '((Form) (EVAL Form Env)) Value))) + (T Ast) ) ) ) + +(de PRINT (Ast) + (pr-str Ast T) ) + +(de rep (String Env) + (PRINT (EVAL (READ String) Env)) ) + +(rep "(def! not (fn* (a) (if a false true)))" repl-env) + +(load-history ".mal_history") + +(use Input + (until (=0 (setq Input (readline "user> "))) + (let Output (catch 'err (rep Input repl-env)) + (if (isa '+MALError Output) + (let Message (MAL-value Output) + (unless (= Message "end of token stream") + (prinl "[error] " Message) ) ) + (prinl Output) ) ) ) ) + +(prinl) +(bye) diff --git a/pil/types.l b/pil/types.l index 4ce4bbe450..299a071531 100644 --- a/pil/types.l +++ b/pil/types.l @@ -16,15 +16,15 @@ (class +MALTrue +MAL) (dm T () - (super 'true T NIL) ) + (super 'true 'true NIL) ) (class +MALFalse +MAL) (dm T () - (super 'false NIL NIL) ) + (super 'false 'false NIL) ) (class +MALNil +MAL) (dm T () - (super 'nil NIL NIL) ) + (super 'nil 'nil NIL) ) (def '*MAL-true (new '(+MALTrue))) (def '*MAL-false (new '(+MALFalse))) @@ -86,6 +86,13 @@ (de MAL-atom (N) (new '(+MALAtom) N) ) +(class +MALFn +MAL) +(dm T (Fn) + (super 'fn Fn NIL) ) + +(de MAL-fn (Fn) + (new '(+MALFn) Fn) ) + (class +MALError +MAL) (dm T (Value) (super 'error Value NIL) ) From f60f33e0d8559fe22f992b7a653b1c5b22aae15b Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 6 Oct 2016 17:55:58 -0500 Subject: [PATCH 0162/2308] Haskell: update docker image to 7.10.3 This goes along with https://github.com/kanaka/mal/pull/238 --- README.md | 6 ++---- haskell/Dockerfile | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 143fc1ed2b..b4d3f00f45 100644 --- a/README.md +++ b/README.md @@ -390,10 +390,8 @@ guile -L ./ stepX_YYY.scm ### Haskell -Install the Haskell compiler (ghc/ghci), the Haskell platform and -either the editline package (BSD) or the readline package (GPL). On -Ubuntu these packages are: ghc, haskell-platform, -libghc-readline-dev/libghc-editline-dev +The Haskell implementation requires the ghc compiler version 7.10.1 or +later and also the Haskell parsec and readline (or editline) packages. ``` cd haskell diff --git a/haskell/Dockerfile b/haskell/Dockerfile index 022985be46..73650f205a 100644 --- a/haskell/Dockerfile +++ b/haskell/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:wily MAINTAINER Joel Martin ########################################################## @@ -22,4 +22,14 @@ WORKDIR /mal ########################################################## # Haskell -RUN apt-get -y install ghc haskell-platform libghc-readline-dev libghc-editline-dev +RUN apt-get install -y software-properties-common && \ + add-apt-repository -y ppa:hvr/ghc && \ + apt-get update && \ + apt-get install -y cabal-install-1.22 ghc-7.10.3 + +ENV PATH /opt/cabal/1.22/bin:/opt/ghc/7.10.3/bin:$PATH + +RUN cabal update && cabal install --global readline +# TODO: editline when compile bug fixed +RUN cabal install --global parsec + From 70f29a2b3c28ea4ad56597a886baad90e117c824 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 6 Oct 2016 22:22:57 -0500 Subject: [PATCH 0163/2308] Basic: step8 basics. Fix def!, let*, concat, scalars. - Move apply logic in swap! to APPLY function in types and use that for macroexpand - Abort def! if error before updating the environment - let* wasn't properly saving A2% for the final eval. Also, the environment release check should be against the top-level EVAL env, not the root repl env. - (concat (list) ...) was broken so fix it to ignore empty lists that aren't in the trailing position. - nil, false and true in the reader were always being returned as references (with an ref cnt) but we have the assumption that references (14) are not ref cnt'd and are always part of a compound type so fix the reader to just return the interned addresses. --- basic/Makefile | 13 +- basic/core.in.bas | 68 ++-- basic/debug.in.bas | 44 +-- basic/env.in.bas | 2 + basic/printer.in.bas | 2 +- basic/reader.in.bas | 5 +- basic/step3_env.in.bas | 16 +- basic/step4_if_fn_do.in.bas | 16 +- basic/step5_tco.in.bas | 25 +- basic/step6_file.in.bas | 25 +- basic/step7_quote.in.bas | 25 +- basic/step8_macros.in.bas | 613 ++++++++++++++++++++++++++++++++++++ basic/types.in.bas | 33 +- tests/step3_env.mal | 6 +- 14 files changed, 770 insertions(+), 123 deletions(-) create mode 100755 basic/step8_macros.in.bas diff --git a/basic/Makefile b/basic/Makefile index 06b53d39c9..d1cfd6669c 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -15,13 +15,10 @@ STEP3_DEPS = $(STEP1_DEPS) env.in.bas STEP4_DEPS = $(STEP3_DEPS) core.in.bas step0_repl.bas: $(STEP0_DEPS) -step1_read_print.bas: $(STEP1_DEPS) -step2_eval.bas: $(STEP1_DEPS) +step1_read_print.bas step2_eval.bas: $(STEP1_DEPS) step3_env.bas: $(STEP3_DEPS) -step4_if_fn_do.bas: $(STEP4_DEPS) -step5_tco.bas: $(STEP4_DEPS) -step6_file.bas: $(STEP4_DEPS) -step7_quote.bas: $(STEP4_DEPS) +step4_if_fn_do.bas step5_tco.bas step6_file.bas step7_quote.bas: $(STEP4_DEPS) +step8_macros.bas: $(STEP4_DEPS) tests/%.bas: tests/%.in.bas ./basicpp.py $(BASICPP_OPTS) $< > $@ @@ -31,10 +28,10 @@ tests/%.prg: tests/%.bas petcat -text -w2 -o $@ $<.tmp rm $<.tmp -mal.prg: step7_quote.prg +mal.prg: step8_macros.prg cp $< $@ -SOURCES_LISP = env.in.bas core.in.bas step7_quote.in.bas +SOURCES_LISP = env.in.bas core.in.bas step8_macros.in.bas SOURCES = readline.in.bas types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP) .PHONY: stats diff --git a/basic/core.in.bas b/basic/core.in.bas index f32031c571..fc744c3c1b 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -153,6 +153,7 @@ DO_FUNCTION: Z%(R%,1)=Z%(AA%,1)/Z%(AB%,1) RETURN DO_TIME_MS: + R%=0 RETURN DO_LIST: @@ -202,6 +203,7 @@ DO_FUNCTION: DO_CONCAT_LOOP: IF ZL%=CZ% THEN R%=AB%:RETURN AA%=ZZ%(ZL%):ZL%=ZL%-1: REM pop off next seq to prepend + IF Z%(AA%,1)=0 THEN GOTO DO_CONCAT_LOOP: REM skip empty seqs A%=AA%:B%=0:C%=-1:GOSUB SLICE REM release the terminator of new list (we skip over it) @@ -213,7 +215,18 @@ DO_FUNCTION: AB%=R% GOTO DO_CONCAT_LOOP DO_NTH: - RETURN + B%=Z%(AB%,1) + A%=AA%:GOSUB COUNT + IF R%<=B% THEN R%=0:ER%=1:ER$="nth: index out of range":RETURN + DO_NTH_LOOP: + IF B%=0 THEN GOTO DO_NTH_DONE + B%=B%-1 + AA%=Z%(AA%,1) + GOTO DO_NTH_LOOP + DO_NTH_DONE: + R%=Z%(AA%+1,1) + Z%(R%,0)=Z%(R%,0)+16 + RETURN DO_FIRST: IF Z%(AA%,1)=0 THEN R%=0 IF Z%(AA%,1)<>0 THEN R%=AA%+1:GOSUB DEREF_R @@ -268,53 +281,24 @@ DO_FUNCTION: REM push args for release after ZL%=ZL%+1:ZZ%(ZL%)=AR% - REM TODO: break this out into APPLY - IF (Z%(F%,0)AND15)=9 THEN GOTO DO_SWAP_FUNCTION - IF (Z%(F%,0)AND15)=10 THEN GOTO DO_SWAP_MAL_FUNCTION - - DO_SWAP_FUNCTION: - REM push atom - ZL%=ZL%+1:ZZ%(ZL%)=AA% - - GOSUB DO_FUNCTION - - REM pop atom - AA%=ZZ%(ZL%):ZL%=ZL%-1 + REM push atom + ZL%=ZL%+1:ZZ%(ZL%)=AA% - REM pop and release args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE - - GOTO DO_SWAP_DONE + GOSUB APPLY - DO_SWAP_MAL_FUNCTION: - REM push current environment for later release - ZL%=ZL%+1:ZZ%(ZL%)=E% + REM pop atom + AA%=ZZ%(ZL%):ZL%=ZL%-1 - REM create new environ using env stored with function - EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS + REM pop and release args + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE - REM push atom - ZL%=ZL%+1:ZZ%(ZL%)=AA% + REM use reset to update the value + AB%=R%:GOSUB DO_RESET_BANG - A%=Z%(F%,1):E%=R%:GOSUB EVAL + REM but decrease ref cnt of return by 1 (not sure why) + AY%=R%:GOSUB RELEASE - REM pop atom - AA%=ZZ%(ZL%):ZL%=ZL%-1 - - REM pop and release args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE - - REM pop and release previous env - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE - - GOTO DO_SWAP_DONE - - DO_SWAP_DONE: - REM use reset to update the value - AB%=R%:GOSUB DO_RESET_BANG - REM but decrease ref cnt of return by 1 (not sure why) - AY%=R%:GOSUB RELEASE - RETURN + RETURN DO_PR_MEMORY: P1%=ZT%:P2%=-1:GOSUB PR_MEMORY diff --git a/basic/debug.in.bas b/basic/debug.in.bas index 7de2b91968..345807463a 100644 --- a/basic/debug.in.bas +++ b/basic/debug.in.bas @@ -55,25 +55,25 @@ REM NEXT I REM PR_MEMORY_SKIP_STACK: REM PRINT "^^^^^^" REM RETURN - -REM PR_OBJECT(P1%) -> nil -PR_OBJECT: - RC%=0 - - RC%=RC%+1:ZL%=ZL%+1:ZZ%(ZL%)=P1% - - PR_OBJ_LOOP: - IF RC%=0 THEN RETURN - I=ZZ%(ZL%):RC%=RC%-1:ZL%=ZL%-1 - - P2%=Z%(I,0)AND15 - PRINT " "+STR$(I); - PRINT ": ref cnt: "+STR$((Z%(I,0)AND-16)/16); - PRINT ", type: "+STR$(P2%)+", value: "+STR$(Z%(I,1)); - IF P2%=4 THEN PRINT " '"+ZS$(Z%(I,1))+"'"; - IF P2%=5 THEN PRINT " "+ZS$(Z%(I,1))+""; - PRINT - IF P2%<=5 OR P2%=9 THEN GOTO PR_OBJ_LOOP - IF Z%(I,1)<>0 THEN RC%=RC%+1:ZL%=ZL%+1:ZZ%(ZL%)=Z%(I,1) - IF P2%>=6 AND P2%<=8 THEN RC%=RC%+1:ZL%=ZL%+1:ZZ%(ZL%)=I+1 - GOTO PR_OBJ_LOOP +REM +REM REM PR_OBJECT(P1%) -> nil +REM PR_OBJECT: +REM RD%=0 +REM +REM RD%=RD%+1:ZL%=ZL%+1:ZZ%(ZL%)=P1% +REM +REM PR_OBJ_LOOP: +REM IF RD%=0 THEN RETURN +REM I=ZZ%(ZL%):RD%=RD%-1:ZL%=ZL%-1 +REM +REM P2%=Z%(I,0)AND15 +REM PRINT " "+STR$(I); +REM PRINT ": ref cnt: "+STR$((Z%(I,0)AND-16)/16); +REM PRINT ", type: "+STR$(P2%)+", value: "+STR$(Z%(I,1)); +REM IF P2%=4 THEN PRINT " '"+ZS$(Z%(I,1))+"'"; +REM IF P2%=5 THEN PRINT " "+ZS$(Z%(I,1))+""; +REM PRINT +REM IF P2%<=5 OR P2%=9 THEN GOTO PR_OBJ_LOOP +REM IF Z%(I,1)<>0 THEN RD%=RD%+1:ZL%=ZL%+1:ZZ%(ZL%)=Z%(I,1) +REM IF P2%>=6 AND P2%<=8 THEN RD%=RD%+1:ZL%=ZL%+1:ZZ%(ZL%)=I+1 +REM GOTO PR_OBJ_LOOP diff --git a/basic/env.in.bas b/basic/env.in.bas index cf6b41fa75..c2f0c4e760 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -69,6 +69,8 @@ ENV_SET_S: RETURN REM ENV_FIND(E%, K%) -> R% +REM Returns environment (R%) containing K%. If found, value found is +REM in T4% ENV_FIND: EF%=E% ENV_FIND_LOOP: diff --git a/basic/printer.in.bas b/basic/printer.in.bas index aa00b3cba8..7fb2c741bc 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -5,7 +5,7 @@ PR_STR: T%=Z%(AZ%,0)AND15 REM PRINT "AZ%: "+STR$(AZ%)+", T%: "+STR$(T%)+", V%: "+STR$(Z%(AZ%,1)) IF T%=0 THEN R$="nil":RETURN - ON T% GOTO PR_BOOLEAN,PR_INTEGER,PR_UNKNOWN,PR_STRING,PR_SYMBOL,PR_SEQ,PR_SEQ,PR_SEQ,PR_FUNCTION,PR_MAL_FUNCTION,PR_UNKNOWN,PR_ATOM,PR_ENV,PR_RECUR,PR_FREE + ON T% GOTO PR_BOOLEAN,PR_INTEGER,PR_UNKNOWN,PR_STRING,PR_SYMBOL,PR_SEQ,PR_SEQ,PR_SEQ,PR_FUNCTION,PR_MAL_FUNCTION,PR_MAL_FUNCTION,PR_ATOM,PR_ENV,PR_RECUR,PR_FREE PR_UNKNOWN: R$="#" diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 96d9040058..7454ef6ecb 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -74,9 +74,8 @@ READ_FORM: GOTO READ_TO_EOL READ_NIL_BOOL: REM PRINT "READ_NIL_BOOL" - SZ%=1:GOSUB ALLOC - Z%(R%,0)=14+16 - Z%(R%,1)=T% + R%=T% + Z%(R%,0)=Z%(R%,0)+16 GOTO READ_FORM_DONE READ_NUMBER: REM PRINT "READ_NUMBER" diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index f661cb104e..fcb24d19f2 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -124,8 +124,8 @@ EVAL: REM push A% and E% on the stack ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% - REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: "+R$+"("+STR$(A%)+"), LV%:"+STR$(LV%) + REM AZ%=A%:PR%=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" GOSUB DEREF_A @@ -169,6 +169,8 @@ EVAL: A%=A2%:GOSUB EVAL: REM eval a2 A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + IF ER%<>0 THEN GOTO EVAL_RETURN + REM set a1 in env to a2 K%=A1%:V%=R%:GOSUB ENV_SET GOTO EVAL_RETURN @@ -176,18 +178,18 @@ EVAL: EVAL_LET: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% + + ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% REM create new environment with outer as current environment EO%=E%:GOSUB ENV_NEW E%=R% EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - REM push A1% - ZL%=ZL%+1:ZZ%(ZL%)=A1% + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% REM eval current A1 odd element A%=Z%(A1%,1)+1:GOSUB EVAL - REM pop A1% - A1%=ZZ%(ZL%):ZL%=ZL%-1 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% REM set environment: even A1% key to odd A1% eval'd above K%=A1%+1:V%=R%:GOSUB ENV_SET @@ -196,7 +198,9 @@ EVAL: REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP + EVAL_LET_LOOP_DONE: + A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% A%=A2%:GOSUB EVAL: REM eval a2 using let_env GOTO EVAL_RETURN EVAL_INVOKE: diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index ca208b9c62..024ed72ab7 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -129,8 +129,8 @@ EVAL: EVAL_TCO_RECUR: - REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: "+R$+"("+STR$(A%)+"), LV%:"+STR$(LV%) + REM AZ%=A%:PR%=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" GOSUB DEREF_A @@ -181,6 +181,8 @@ EVAL: A%=A2%:GOSUB EVAL: REM eval a2 A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + IF ER%<>0 THEN GOTO EVAL_RETURN + REM set a1 in env to a2 K%=A1%:V%=R%:GOSUB ENV_SET GOTO EVAL_RETURN @@ -188,18 +190,18 @@ EVAL: EVAL_LET: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% + + ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% REM create new environment with outer as current environment EO%=E%:GOSUB ENV_NEW E%=R% EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - REM push A1% - ZL%=ZL%+1:ZZ%(ZL%)=A1% + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% REM eval current A1 odd element A%=Z%(A1%,1)+1:GOSUB EVAL - REM pop A1% - A1%=ZZ%(ZL%):ZL%=ZL%-1 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% REM set environment: even A1% key to odd A1% eval'd above K%=A1%+1:V%=R%:GOSUB ENV_SET @@ -208,7 +210,9 @@ EVAL: REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP + EVAL_LET_LOOP_DONE: + A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% A%=A2%:GOSUB EVAL: REM eval a2 using let_env GOTO EVAL_RETURN EVAL_DO: diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index 1825cf845d..2dbb3c11a0 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -129,8 +129,8 @@ EVAL: EVAL_TCO_RECUR: - REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: "+R$+"("+STR$(A%)+"), LV%:"+STR$(LV%) + REM AZ%=A%:PR%=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" GOSUB DEREF_A @@ -181,6 +181,8 @@ EVAL: A%=A2%:GOSUB EVAL: REM eval a2 A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + IF ER%<>0 THEN GOTO EVAL_RETURN + REM set a1 in env to a2 K%=A1%:V%=R%:GOSUB ENV_SET GOTO EVAL_RETURN @@ -189,7 +191,8 @@ EVAL: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% - E4%=E%: REM save the current environment for release + ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% + ZL%=ZL%+1:ZZ%(ZL%)=E%: REM push env for for later release REM create new environment with outer as current environment EO%=E%:GOSUB ENV_NEW @@ -197,12 +200,10 @@ EVAL: EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - REM push A1% - ZL%=ZL%+1:ZZ%(ZL%)=A1% + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% REM eval current A1 odd element A%=Z%(A1%,1)+1:GOSUB EVAL - REM pop A1% - A1%=ZZ%(ZL%):ZL%=ZL%-1 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% REM set environment: even A1% key to odd A1% eval'd above K%=A1%+1:V%=R%:GOSUB ENV_SET @@ -211,12 +212,14 @@ EVAL: REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP + EVAL_LET_LOOP_DONE: - REM release previous env (if not root repl_env) because our - REM new env refers to it and we no longer need to track it - REM (since we are TCO recurring) - IF E4%<>RE% THEN AY%=E4%:GOSUB RELEASE + E4%=ZZ%(ZL%):ZL%=ZL%-1: REM pop previous env + + REM release previous environment if not the current EVAL env + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index 27850b18e8..4e197e7eed 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -129,8 +129,8 @@ EVAL: EVAL_TCO_RECUR: - REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: "+R$+"("+STR$(A%)+"), LV%:"+STR$(LV%) + REM AZ%=A%:PR%=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" GOSUB DEREF_A @@ -181,6 +181,8 @@ EVAL: A%=A2%:GOSUB EVAL: REM eval a2 A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + IF ER%<>0 THEN GOTO EVAL_RETURN + REM set a1 in env to a2 K%=A1%:V%=R%:GOSUB ENV_SET GOTO EVAL_RETURN @@ -189,7 +191,8 @@ EVAL: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% - E4%=E%: REM save the current environment for release + ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% + ZL%=ZL%+1:ZZ%(ZL%)=E%: REM push env for for later release REM create new environment with outer as current environment EO%=E%:GOSUB ENV_NEW @@ -197,12 +200,10 @@ EVAL: EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - REM push A1% - ZL%=ZL%+1:ZZ%(ZL%)=A1% + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% REM eval current A1 odd element A%=Z%(A1%,1)+1:GOSUB EVAL - REM pop A1% - A1%=ZZ%(ZL%):ZL%=ZL%-1 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% REM set environment: even A1% key to odd A1% eval'd above K%=A1%+1:V%=R%:GOSUB ENV_SET @@ -211,12 +212,14 @@ EVAL: REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP + EVAL_LET_LOOP_DONE: - REM release previous env (if not root repl_env) because our - REM new env refers to it and we no longer need to track it - REM (since we are TCO recurring) - IF E4%<>RE% THEN AY%=E4%:GOSUB RELEASE + E4%=ZZ%(ZL%):ZL%=ZL%-1: REM pop previous env + + REM release previous environment if not the current EVAL env + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index 2315a3da02..2bbc29a3ae 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -202,8 +202,8 @@ EVAL: EVAL_TCO_RECUR: - REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: "+R$+"("+STR$(A%)+"), LV%:"+STR$(LV%) + REM AZ%=A%:PR%=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" GOSUB DEREF_A @@ -256,6 +256,8 @@ EVAL: A%=A2%:GOSUB EVAL: REM eval a2 A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + IF ER%<>0 THEN GOTO EVAL_RETURN + REM set a1 in env to a2 K%=A1%:V%=R%:GOSUB ENV_SET GOTO EVAL_RETURN @@ -264,7 +266,8 @@ EVAL: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% - E4%=E%: REM save the current environment for release + ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% + ZL%=ZL%+1:ZZ%(ZL%)=E%: REM push env for for later release REM create new environment with outer as current environment EO%=E%:GOSUB ENV_NEW @@ -272,12 +275,10 @@ EVAL: EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - REM push A1% - ZL%=ZL%+1:ZZ%(ZL%)=A1% + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% REM eval current A1 odd element A%=Z%(A1%,1)+1:GOSUB EVAL - REM pop A1% - A1%=ZZ%(ZL%):ZL%=ZL%-1 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% REM set environment: even A1% key to odd A1% eval'd above K%=A1%+1:V%=R%:GOSUB ENV_SET @@ -286,12 +287,14 @@ EVAL: REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP + EVAL_LET_LOOP_DONE: - REM release previous env (if not root repl_env) because our - REM new env refers to it and we no longer need to track it - REM (since we are TCO recurring) - IF E4%<>RE% THEN AY%=E4%:GOSUB RELEASE + E4%=ZZ%(ZL%):ZL%=ZL%-1: REM pop previous env + + REM release previous environment if not the current EVAL env + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas new file mode 100755 index 0000000000..5c38aedb06 --- /dev/null +++ b/basic/step8_macros.in.bas @@ -0,0 +1,613 @@ +REM POKE 1, PEEK(1)AND248: REM enable all ROM areas as RAM +REM POKE 55,0: POKE 56,192: CLR: REM move BASIC end from $A000 to $C000 +GOTO MAIN + +REM $INCLUDE: 'readline.in.bas' +REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'reader.in.bas' +REM $INCLUDE: 'printer.in.bas' +REM $INCLUDE: 'env.in.bas' +REM $INCLUDE: 'core.in.bas' + +REM $INCLUDE: 'debug.in.bas' + +REM READ(A$) -> R% +MAL_READ: + GOSUB READ_STR + RETURN + +REM PAIR_Q(B%) -> R% +PAIR_Q: + R%=0 + IF (Z%(B%,0)AND15)<>6 AND (Z%(B%,0)AND15)<>7 THEN RETURN + IF (Z%(B%,1)=0) THEN RETURN + R%=1 + RETURN + +REM QUASIQUOTE(A%) -> R% +QUASIQUOTE: + B%=A%:GOSUB PAIR_Q + IF R%=1 THEN GOTO QQ_UNQUOTE + REM ['quote, ast] + AS$="quote":T%=5:GOSUB STRING + B2%=R%:B1%=A%:GOSUB LIST2 + + RETURN + + QQ_UNQUOTE: + R%=A%+1:GOSUB DEREF_R + IF (Z%(R%,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF ZS$(Z%(R%,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE + REM [ast[1]] + R%=Z%(A%,1)+1:GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + + RETURN + + QQ_SPLICE_UNQUOTE: + REM push A% on the stack + ZL%=ZL%+1:ZZ%(ZL%)=A% + REM rest of cases call quasiquote on ast[1..] + A%=Z%(A%,1):GOSUB QUASIQUOTE:T6%=R% + REM pop A% off the stack + A%=ZZ%(ZL%):ZL%=ZL%-1 + + REM set A% to ast[0] for last two cases + A%=A%+1:GOSUB DEREF_A + + B%=A%:GOSUB PAIR_Q + IF R%=0 THEN GOTO QQ_DEFAULT + B%=A%+1:GOSUB DEREF_B + IF (Z%(B%,0)AND15)<>5 THEN GOTO QQ_DEFAULT + IF ZS$(Z%(B%,1))<>"splice-unquote" THEN QQ_DEFAULT + REM ['concat, ast[0][1], quasiquote(ast[1..])] + + B%=Z%(A%,1)+1:GOSUB DEREF_B:B2%=B% + AS$="concat":T%=5:GOSUB STRING:B3%=R% + B1%=T6%:GOSUB LIST3 + REM release inner quasiquoted since outer list takes ownership + AY%=B1%:GOSUB RELEASE + RETURN + + QQ_DEFAULT: + REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] + + REM push T6% on the stack + ZL%=ZL%+1:ZZ%(ZL%)=T6% + REM A% set above to ast[0] + GOSUB QUASIQUOTE:B2%=R% + REM pop T6% off the stack + T6%=ZZ%(ZL%):ZL%=ZL%-1 + + AS$="cons":T%=5:GOSUB STRING:B3%=R% + B1%=T6%:GOSUB LIST3 + REM release inner quasiquoted since outer list takes ownership + AY%=B1%:GOSUB RELEASE + AY%=B2%:GOSUB RELEASE + RETURN + +REM MACROEXPAND(A%, E%) -> A%: +MACROEXPAND: + REM push original A% + ZL%=ZL%+1:ZZ%(ZL%)=A% + + MACROEXPAND_LOOP: + REM list? + IF (Z%(A%,0)AND15)<>6 THEN GOTO MACROEXPAND_DONE + REM non-empty? + IF Z%(A%,1)=0 THEN GOTO MACROEXPAND_DONE + B%=A%+1:GOSUB DEREF_B + REM symbol? in first position + IF (Z%(B%,0)AND15)<>5 THEN GOTO MACROEXPAND_DONE + REM defined in environment? + K%=B%:GOSUB ENV_FIND + IF R%=-1 THEN GOTO MACROEXPAND_DONE + B%=T4%:GOSUB DEREF_B + REM macro? + IF (Z%(B%,0)AND15)<>11 THEN GOTO MACROEXPAND_DONE + + REM apply + F%=B%:AR%=Z%(A%,1):GOSUB APPLY + A%=R% + + AY%=ZZ%(ZL%) + REM if previous A% was not the first A% into macroexpand (i.e. an + REM intermediate form) then free it + IF A%<>AY% THEN ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV% + + IF ER%<>0 THEN GOTO MACROEXPAND_DONE + GOTO MACROEXPAND_LOOP + + MACROEXPAND_DONE: + ZL%=ZL%-1: REM pop original A% + RETURN + +REM EVAL_AST(A%, E%) -> R% +REM called using GOTO to avoid basic return address stack usage +REM top of stack should have return label index +EVAL_AST: + REM push A% and E% on the stack + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + + IF ER%<>0 THEN GOTO EVAL_AST_RETURN + + GOSUB DEREF_A + + T%=Z%(A%,0)AND15 + IF T%=5 THEN GOTO EVAL_AST_SYMBOL + IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ + + REM scalar: deref to actual value and inc ref cnt + R%=A%:GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + GOTO EVAL_AST_RETURN + + EVAL_AST_SYMBOL: + K%=A%:GOSUB ENV_GET + GOTO EVAL_AST_RETURN + + EVAL_AST_SEQ: + REM allocate the first entry + SZ%=2:GOSUB ALLOC + + REM make space on the stack + ZL%=ZL%+4 + REM push type of sequence + ZZ%(ZL%-3)=T% + REM push sequence index + ZZ%(ZL%-2)=-1 + REM push future return value (new sequence) + ZZ%(ZL%-1)=R% + REM push previous new sequence entry + ZZ%(ZL%)=R% + + EVAL_AST_SEQ_LOOP: + REM set new sequence entry type (with 1 ref cnt) + Z%(R%,0)=ZZ%(ZL%-3)+16 + Z%(R%,1)=0 + REM create value ptr placeholder + Z%(R%+1,0)=14 + Z%(R%+1,1)=0 + + REM update index + ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + + REM check if we are done evaluating the source sequence + IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + + REM if hashmap, skip eval of even entries (keys) + IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + GOTO EVAL_AST_DO_EVAL + + EVAL_AST_DO_REF: + R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry + Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + GOTO EVAL_AST_ADD_VALUE + + EVAL_AST_DO_EVAL: + REM call EVAL for each entry + A%=A%+1:GOSUB EVAL + A%=A%-1 + GOSUB DEREF_R: REM deref to target of evaluated entry + + EVAL_AST_ADD_VALUE: + + REM update previous value pointer to evaluated entry + Z%(ZZ%(ZL%)+1,1)=R% + + IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + + REM allocate the next entry + SZ%=2:GOSUB ALLOC + + REM update previous sequence entry value to point to new entry + Z%(ZZ%(ZL%),1)=R% + REM update previous ptr to current entry + ZZ%(ZL%)=R% + + REM process the next sequence entry from source list + A%=Z%(A%,1) + + GOTO EVAL_AST_SEQ_LOOP + EVAL_AST_SEQ_LOOP_DONE: + REM if no error, get return value (new seq) + IF ER%=0 THEN R%=ZZ%(ZL%-1) + REM otherwise, free the return value and return nil + IF ER%<>0 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + + REM pop previous, return, index and type + ZL%=ZL%-4 + GOTO EVAL_AST_RETURN + + EVAL_AST_RETURN: + REM pop A% and E% off the stack + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + + REM pop EVAL AST return label/address + RN%=ZZ%(ZL%):ZL%=ZL%-1 + ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 + RETURN + +REM EVAL(A%, E%)) -> R% +EVAL: + LV%=LV%+1: REM track basic return stack level + + REM push A% and E% on the stack + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + + EVAL_TCO_RECUR: + + REM AZ%=A%:PR%=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" + + GOSUB DEREF_A + + GOSUB LIST_Q + IF R% THEN GOTO APPLY_LIST + EVAL_NOT_LIST: + REM ELSE + REM push EVAL_AST return label/address + ZL%=ZL%+1:ZZ%(ZL%)=1 + GOTO EVAL_AST + EVAL_AST_RETURN_1: + + GOTO EVAL_RETURN + + APPLY_LIST: + GOSUB MACROEXPAND + + GOSUB LIST_Q + IF R%<>1 THEN GOTO EVAL_NOT_LIST + + GOSUB EMPTY_Q + IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN + + A0%=A%+1 + R%=A0%:GOSUB DEREF_R:A0%=R% + + REM get symbol in A$ + IF (Z%(A0%,0)AND15)<>5 THEN A$="" + IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) + + IF A$="def!" THEN GOTO EVAL_DEF + IF A$="let*" THEN GOTO EVAL_LET + IF A$="quote" THEN GOTO EVAL_QUOTE + IF A$="quasiquote" THEN GOTO EVAL_QUASIQUOTE + IF A$="defmacro!" THEN GOTO EVAL_DEFMACRO + IF A$="macroexpand" THEN GOTO EVAL_MACROEXPAND + IF A$="do" THEN GOTO EVAL_DO + IF A$="if" THEN GOTO EVAL_IF + IF A$="fn*" THEN GOTO EVAL_FN + GOTO EVAL_INVOKE + + EVAL_GET_A3: + A3%=Z%(Z%(Z%(A%,1),1),1)+1 + R%=A3%:GOSUB DEREF_R:A3%=R% + EVAL_GET_A2: + A2%=Z%(Z%(A%,1),1)+1 + R%=A2%:GOSUB DEREF_R:A2%=R% + EVAL_GET_A1: + A1%=Z%(A%,1)+1 + R%=A1%:GOSUB DEREF_R:A1%=R% + RETURN + + EVAL_DEF: + REM PRINT "def!" + GOSUB EVAL_GET_A2: REM set a1% and a2% + + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + A%=A2%:GOSUB EVAL: REM eval a2 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + + IF ER%<>0 THEN GOTO EVAL_RETURN + + REM set a1 in env to a2 + K%=A1%:V%=R%:GOSUB ENV_SET + GOTO EVAL_RETURN + + EVAL_LET: + REM PRINT "let*" + GOSUB EVAL_GET_A2: REM set a1% and a2% + + ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% + ZL%=ZL%+1:ZZ%(ZL%)=E%: REM push env for for later release + + REM create new environment with outer as current environment + EO%=E%:GOSUB ENV_NEW + E%=R% + EVAL_LET_LOOP: + IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + REM eval current A1 odd element + A%=Z%(A1%,1)+1:GOSUB EVAL + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + + REM set environment: even A1% key to odd A1% eval'd above + K%=A1%+1:V%=R%:GOSUB ENV_SET + AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership + + REM skip to the next pair of A1% elements + A1%=Z%(Z%(A1%,1),1) + GOTO EVAL_LET_LOOP + + EVAL_LET_LOOP_DONE: + E4%=ZZ%(ZL%):ZL%=ZL%-1: REM pop previous env + + REM release previous environment if not the current EVAL env + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + + A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% + A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_DO: + A%=Z%(A%,1): REM rest + + REM TODO: TCO + + REM push EVAL_AST return label/address + ZL%=ZL%+1:ZZ%(ZL%)=2 + GOTO EVAL_AST + EVAL_AST_RETURN_2: + + ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push eval'd list + A%=R%:GOSUB LAST: REM return the last element + AY%=ZZ%(ZL%):ZL%=ZL%-1: REM pop eval'd list + GOSUB RELEASE: REM release the eval'd list + GOTO EVAL_RETURN + + EVAL_QUOTE: + R%=Z%(A%,1)+1:GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + GOTO EVAL_RETURN + + EVAL_QUASIQUOTE: + R%=Z%(A%,1)+1:GOSUB DEREF_R + A%=R%:GOSUB QUASIQUOTE + REM add quasiquote result to pending release queue to free when + REM next lower EVAL level returns (LV%) + ZM%=ZM%+1:ZR%(ZM%,0)=R%:ZR%(ZM%,1)=LV% + + A%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_DEFMACRO: + REM PRINT "defmacro!" + GOSUB EVAL_GET_A2: REM set a1% and a2% + + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + A%=A2%:GOSUB EVAL: REM eval a2 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + + REM change function to macro + Z%(R%,0)=Z%(R%,0)+1 + + REM set a1 in env to a2 + K%=A1%:V%=R%:GOSUB ENV_SET + GOTO EVAL_RETURN + + EVAL_MACROEXPAND: + REM PRINT "macroexpand" + R%=Z%(A%,1)+1:GOSUB DEREF_R + A%=R%:GOSUB MACROEXPAND:R%=A% + + REM since we are returning it unevaluated, inc the ref cnt + Z%(R%,0)=Z%(R%,0)+16 + GOTO EVAL_RETURN + + EVAL_IF: + GOSUB EVAL_GET_A1: REM set a1% + REM push A% + ZL%=ZL%+1:ZZ%(ZL%)=A% + A%=A1%:GOSUB EVAL + REM pop A% + A%=ZZ%(ZL%):ZL%=ZL%-1 + IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE + + EVAL_IF_TRUE: + AY%=R%:GOSUB RELEASE + GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL + A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + EVAL_IF_FALSE: + AY%=R%:GOSUB RELEASE + REM if no false case (A3%), return nil + IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0:GOTO EVAL_RETURN + GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL + A%=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_FN: + GOSUB EVAL_GET_A2: REM set a1% and a2% + A%=A2%:P%=A1%:GOSUB MAL_FUNCTION + GOTO EVAL_RETURN + + EVAL_INVOKE: + REM push EVAL_AST return label/address + ZL%=ZL%+1:ZZ%(ZL%)=3 + GOTO EVAL_AST + EVAL_AST_RETURN_3: + + REM if error, return f/args for release by caller + IF ER%<>0 THEN GOTO EVAL_RETURN + + REM push f/args for release after call + ZL%=ZL%+1:ZZ%(ZL%)=R% + + F%=R%+1 + + AR%=Z%(R%,1): REM rest + R%=F%:GOSUB DEREF_R:F%=R% + + IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + + REM if error, pop and return f/args for release by caller + R%=ZZ%(ZL%):ZL%=ZL%-1 + ER%=1:ER$="apply of non-function":GOTO EVAL_RETURN + + EVAL_DO_FUNCTION: + GOSUB DO_FUNCTION + + REM pop and release f/args + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + GOTO EVAL_RETURN + + EVAL_DO_MAL_FUNCTION: + E4%=E%: REM save the current environment for release + + REM create new environ using env stored with function + EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS + + REM release previous env if it is not the top one on the + REM stack (ZZ%(ZL%-2)) because our new env refers to it and + REM we no longer need to track it (since we are TCO recurring) + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + + REM claim the AST before releasing the list containing it + A%=Z%(F%,1):Z%(A%,0)=Z%(A%,0)+16 + REM add AST to pending release queue to free as soon as EVAL + REM actually returns (LV%+1) + ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV%+1 + + REM pop and release f/args + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + + REM A% set above + E%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_RETURN: + REM AZ%=R%: PR%=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + + REM release environment if not the top one on the stack + IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE + + LV%=LV%-1: REM track basic return stack level + + REM release everything we couldn't release earlier + GOSUB RELEASE_PEND + + REM trigger GC + TA%=FRE(0) + + REM pop A% and E% off the stack + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + + RETURN + +REM PRINT(A%) -> R$ +MAL_PRINT: + AZ%=A%:PR%=1:GOSUB PR_STR + RETURN + +REM RE(A$) -> R% +REM Assume RE% has repl_env +REM caller must release result +RE: + R1%=0 + GOSUB MAL_READ + R1%=R% + IF ER%<>0 THEN GOTO REP_DONE + + A%=R%:E%=RE%:GOSUB EVAL + + REP_DONE: + REM Release memory from MAL_READ + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + RETURN: REM caller must release result of EVAL + +REM REP(A$) -> R$ +REM Assume RE% has repl_env +REP: + R1%=0:R2%=0 + GOSUB MAL_READ + R1%=R% + IF ER%<>0 THEN GOTO REP_DONE + + A%=R%:E%=RE%:GOSUB EVAL + R2%=R% + IF ER%<>0 THEN GOTO REP_DONE + + A%=R%:GOSUB MAL_PRINT + RT$=R$ + + REP_DONE: + REM Release memory from MAL_READ and EVAL + IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + R$=RT$ + RETURN + +REM MAIN program +MAIN: + GOSUB INIT_MEMORY + + LV%=0 + + REM create repl_env + EO%=-1:GOSUB ENV_NEW:RE%=R% + + REM core.EXT: defined in Basic + E%=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + + ZT%=ZI%: REM top of memory after base repl_env + + REM core.mal: defined using the language itself + A$="(def! not (fn* (a) (if a false true)))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + A$="(def! load-file (fn* (f) (eval (read-string (str " + A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + A$="(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs)" + A$=A$+" (if (> (count xs) 1) (nth xs 1) (throw "+CHR$(34)+"odd number of" + A$=A$+" forms to cond"+CHR$(34)+")) (cons 'cond (rest (rest xs)))))))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + A$="(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs)" + A$=A$+" `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + REM load the args file + A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + REM set the argument list + A$="(def! *ARGV* (rest -*ARGS*-))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + REM get the first argument + A$="(first -*ARGS*-)" + GOSUB RE + + REM if there is an argument, then run it as a program + IF R%<>0 THEN AY%=R%:GOSUB RELEASE:GOTO RUN_PROG + REM no arguments, start REPL loop + IF R%=0 THEN GOTO REPL_LOOP + + RUN_PROG: + REM run a single mal program and exit + A$="(load-file (first -*ARGS*-))" + GOSUB RE + IF ER%<>0 THEN GOSUB PRINT_ERROR + END + + REPL_LOOP: + A$="user> ":GOSUB READLINE: REM call input parser + IF EOF=1 THEN GOTO QUIT + + A$=R$:GOSUB REP: REM call REP + + IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + PRINT R$ + GOTO REPL_LOOP + + QUIT: + REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + GOSUB PR_MEMORY_SUMMARY + END + + PRINT_ERROR: + PRINT "Error: "+ER$ + ER%=0:ER$="" + RETURN + diff --git a/basic/types.in.bas b/basic/types.in.bas index 10a268eda8..fcb02a27bf 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -14,6 +14,8 @@ REM followed by key or value (alternating) REM function 9 -> function index REM mal function 10 -> body AST Z% index REM followed by param and env Z% index +REM macro (same as 10) 11 -> body AST Z% index +REM followed by param and env Z% index REM atom 12 -> Z% index REM environment 13 -> data/hashmap Z% index REM followed by 13 and outer Z% index (-1 for none) @@ -130,6 +132,7 @@ RELEASE: REM sanity check not already freed IF (U6%)=15 THEN ER%=1:ER$="Free of free memory: "+STR$(AY%):RETURN + IF U6%=14 THEN GOTO RELEASE_REFERENCE IF Z%(AY%,0)<15 THEN ER%=1:ER$="Free of freed object: "+STR$(AY%):RETURN REM decrease reference count by one @@ -142,9 +145,9 @@ RELEASE: IF (U6%<=5) OR (U6%=9) THEN GOTO RELEASE_SIMPLE IF (U6%>=6) AND (U6%<=8) THEN GOTO RELEASE_SEQ IF U6%=10 THEN GOTO RELEASE_MAL_FUNCTION + IF U6%=11 THEN GOTO RELEASE_MAL_FUNCTION IF U6%=12 THEN GOTO RELEASE_ATOM IF U6%=13 THEN GOTO RELEASE_ENV - IF U6%=14 THEN GOTO RELEASE_REFERENCE IF U6%=15 THEN ER%=1:ER$="RELEASE of already freed: "+STR$(AY%):RETURN ER%=1:ER$="RELEASE not defined for type "+STR$(U6%):RETURN @@ -509,3 +512,31 @@ MAL_FUNCTION: Z%(R%+1,0)=P% Z%(R%+1,1)=E% RETURN + +REM APPLY(F%, AR%) -> R% +REM restores E% +APPLY: + IF (Z%(F%,0)AND15)=9 THEN GOTO DO_APPLY_FUNCTION + IF (Z%(F%,0)AND15)=10 THEN GOTO DO_APPLY_MAL_FUNCTION + IF (Z%(F%,0)AND15)=11 THEN GOTO DO_APPLY_MAL_FUNCTION + + DO_APPLY_FUNCTION: + GOSUB DO_FUNCTION + + RETURN + + DO_APPLY_MAL_FUNCTION: + ZL%=ZL%+1:ZZ%(ZL%)=E%: REM save the current environment + + REM create new environ using env and params stored in the + REM function and bind the params to the apply arguments + EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS + + A%=Z%(F%,1):E%=R%:GOSUB EVAL + + AY%=E%:GOSUB RELEASE: REM release the new environment + + E%=ZZ%(ZL%):ZL%=ZL%-1: REM pop/restore the saved environment + + RETURN + diff --git a/tests/step3_env.mal b/tests/step3_env.mal index 9487a958c0..ebd2e8ab7e 100644 --- a/tests/step3_env.mal +++ b/tests/step3_env.mal @@ -32,7 +32,11 @@ MYNUM ;; Check env lookup non-fatal error (abc 1 2 3) ; .*\'abc\' not found.* - +;; Check that error aborts def! +(def! w 123) +(def! w (abc)) +w +;=>123 ;; Testing let* (let* (z 9) z) From 16ca60daf91b5de0ae468d06c3cc8c048ca48907 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 8 Oct 2016 12:23:10 -0500 Subject: [PATCH 0164/2308] guide: incorporate feedback from Nicolas Boulenguez --- process/guide.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/process/guide.md b/process/guide.md index eba18051bf..17447a433c 100644 --- a/process/guide.md +++ b/process/guide.md @@ -896,7 +896,9 @@ diff -urp ../process/step4_if_fn_do.txt ../process/step5_tco.txt * `env`: the current value of the `env` parameter of `EVAL`. * `fn`: the original function value (i.e. what was return by `fn*` in step 4). Note that this is deferrable until step 9 when it is - needed for the `map` and `apply` core functions). + required for the `map` and `apply` core functions). You will also + need it in step 6 if you choose to not to defer atoms/`swap!` from + that step. * The default "apply"/invoke case of `EVAL` must now be changed to account for the new object/structure returned by the `fn*` form. @@ -1518,7 +1520,7 @@ diff -urp ../process/step9_try.txt ../process/stepA_mal.txt * When the REPL starts up (as opposed to when it is called with a script and/or arguments), call the `rep` function with this string to print a startup header: - "(println (str \"Mal [\" *host-language* \"]\"))". + "(println (str \"Mal [\" \*host-language\* \"]\"))". Now go to the top level, run the step A tests: @@ -1601,8 +1603,8 @@ For extra information read [Peter Seibel's thorough discussion about #### Optional additions -* Add metadata support to composite data types, symbols and native - functions. TODO +* Add metadata support to mal functions, other composite data + types, and native functions. * Add the following new core functions: * `time-ms`: takes no arguments and returns the number of milliseconds since epoch (00:00:00 UTC January 1, 1970), or, if @@ -1639,7 +1641,9 @@ For extra information read [Peter Seibel's thorough discussion about ## TODO: * simplify: "X argument (list element Y)" -> ast[Y] -* list of types with metadata: list, vector, hash-map, mal functions +* list of types with metadata: mal functions (required for + self-hosting), list, vector, hash-map, native functions (optional + for self-hosting). * more clarity about when to peek and poke in read_list and read_form * tokenizer: use first group rather than whole match (to eliminate whitespace/commas) From 5e5ca0d43818e06c91970b78265239feff9ed4a3 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 8 Oct 2016 23:41:33 -0500 Subject: [PATCH 0165/2308] Basic: step9 basics - Change ER% to be -2 for no error, -1 for raw string error, and >=0 as pointer to an error object. --- basic/Makefile | 6 +- basic/core.in.bas | 193 +++++++--- basic/env.in.bas | 2 +- basic/reader.in.bas | 4 +- basic/step1_read_print.in.bas | 8 +- basic/step2_eval.in.bas | 26 +- basic/step3_env.in.bas | 40 +-- basic/step4_if_fn_do.in.bas | 24 +- basic/step5_tco.in.bas | 24 +- basic/step6_file.in.bas | 26 +- basic/step7_quote.in.bas | 26 +- basic/step8_macros.in.bas | 28 +- basic/step9_try.in.bas | 647 ++++++++++++++++++++++++++++++++++ basic/types.in.bas | 21 +- 14 files changed, 913 insertions(+), 162 deletions(-) create mode 100755 basic/step9_try.in.bas diff --git a/basic/Makefile b/basic/Makefile index d1cfd6669c..f39c5633bf 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -18,7 +18,7 @@ step0_repl.bas: $(STEP0_DEPS) step1_read_print.bas step2_eval.bas: $(STEP1_DEPS) step3_env.bas: $(STEP3_DEPS) step4_if_fn_do.bas step5_tco.bas step6_file.bas step7_quote.bas: $(STEP4_DEPS) -step8_macros.bas: $(STEP4_DEPS) +step8_macros.bas step9_try.bas: $(STEP4_DEPS) tests/%.bas: tests/%.in.bas ./basicpp.py $(BASICPP_OPTS) $< > $@ @@ -28,10 +28,10 @@ tests/%.prg: tests/%.bas petcat -text -w2 -o $@ $<.tmp rm $<.tmp -mal.prg: step8_macros.prg +mal.prg: step9_try.prg cp $< $@ -SOURCES_LISP = env.in.bas core.in.bas step8_macros.in.bas +SOURCES_LISP = env.in.bas core.in.bas step9_try.in.bas SOURCES = readline.in.bas types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP) .PHONY: stats diff --git a/basic/core.in.bas b/basic/core.in.bas index fc744c3c1b..cc1629b395 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -9,69 +9,57 @@ DO_FUNCTION: R%=Z%(AR%,1)+1:GOSUB DEREF_R:AB%=R% REM Switch on the function number - IF FF%>=61 THEN ER%=1:ER$="unknown function"+STR$(FF%):RETURN + IF FF%>=61 THEN ER%=-1:ER$="unknown function"+STR$(FF%):RETURN IF FF%>=53 THEN DO_53 IF FF%>=39 THEN DO_39 IF FF%>=27 THEN DO_27 IF FF%>=18 THEN DO_18 IF FF%>=11 THEN DO_11 - ON FF% GOTO DO_EQUAL_Q - REM IF FF%=1 THEN DO_EQUAL_Q - + ON FF% GOTO DO_EQUAL_Q,DO_THROW,DO_NIL_Q,DO_TRUE_Q,DO_FALSE_Q,DO_STRING_Q,DO_SYMBOL,DO_SYMBOL_Q DO_11: ON FF%-10 GOTO DO_PR_STR,DO_STR,DO_PRN,DO_PRINTLN,DO_READLINE,DO_READ_STRING,DO_SLURP - REM IF FF%=11 THEN DO_PR_STR - REM IF FF%=12 THEN DO_STR - REM IF FF%=13 THEN DO_PRN - REM IF FF%=14 THEN DO_PRINTLN - REM IF FF%=15 THEN DO_READLINE - REM IF FF%=16 THEN DO_READ_STRING - REM IF FF%=17 THEN DO_SLURP - DO_18: ON FF%-17 GOTO DO_LT,DO_LTE,DO_GT,DO_GTE,DO_ADD,DO_SUB,DO_MULT,DO_DIV,DO_TIME_MS - REM IF FF%=18 THEN DO_LT - REM IF FF%=19 THEN DO_LTE - REM IF FF%=20 THEN DO_GT - REM IF FF%=21 THEN DO_GTE - REM IF FF%=22 THEN DO_ADD - REM IF FF%=23 THEN DO_SUB - REM IF FF%=24 THEN DO_MULT - REM IF FF%=25 THEN DO_DIV - REM IF FF%=26 THEN DO_TIME_MS - DO_27: - ON FF%-26 GOTO DO_LIST,DO_LIST_Q - REM IF FF%=27 THEN DO_LIST - REM IF FF%=28 THEN DO_LIST_Q - + ON FF%-26 GOTO DO_LIST,DO_LIST_Q,DO_VECTOR,DO_VECTOR_Q,DO_HASH_MAP,DO_MAP_Q DO_39: - ON FF%-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT - REM IF FF%=40 THEN DO_CONS - REM IF FF%=41 THEN DO_CONCAT - REM IF FF%=42 THEN DO_NTH - REM IF FF%=43 THEN DO_FIRST - REM IF FF%=44 THEN DO_REST - REM IF FF%=45 THEN DO_EMPTY_Q - REM IF FF%=46 THEN DO_COUNT - + ON FF%-38 GOTO DO_SEQUENTIAL_Q,DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_APPLY,DO_MAP DO_53: ON FF%-52 GOTO DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_SWAP_BANG,DO_PR_MEMORY,DO_PR_MEMORY_SUMMARY,DO_EVAL - REM IF FF%=53 THEN DO_ATOM - REM IF FF%=54 THEN DO_ATOM_Q - REM IF FF%=55 THEN DO_DEREF - REM IF FF%=56 THEN DO_RESET_BANG - REM IF FF%=57 THEN DO_SWAP_BANG - - REM IF FF%=58 THEN DO_PR_MEMORY - REM IF FF%=59 THEN DO_PR_MEMORY_SUMMARY - REM IF FF%=60 THEN DO_EVAL DO_EQUAL_Q: A%=AA%:B%=AB%:GOSUB EQUAL_Q R%=R%+1 RETURN + DO_THROW: + ER%=AA% + Z%(ER%,0)=Z%(ER%,0)+16 + R%=0 + RETURN + DO_NIL_Q: + R%=1 + IF AA%=0 THEN R%=2 + RETURN + DO_TRUE_Q: + R%=1 + IF AA%=2 THEN R%=2 + RETURN + DO_FALSE_Q: + R%=1 + IF AA%=1 THEN R%=2 + RETURN + DO_STRING_Q: + R%=1 + IF (Z%(AA%,0)AND15)=4 THEN R%=2 + RETURN + DO_SYMBOL: + R%=0 + RETURN + DO_SYMBOL_Q: + R%=1 + IF (Z%(AA%,0)AND15)=5 THEN R%=2 + RETURN DO_PR_STR: AZ%=AR%:PR%=1:SE$=" ":GOSUB PR_STR_SEQ @@ -108,7 +96,7 @@ DO_FUNCTION: IF ASC(A$)=10 THEN R$=R$+CHR$(13) IF (ASC(A$)<>10) AND (A$<>"") THEN R$=R$+A$ IF (ST AND 64) THEN GOTO DO_SLURP_DONE - IF (ST AND 255) THEN ER%=-1:ER%="File read error "+STR$(ST):RETURN + IF (ST AND 255) THEN ER%=-1:ER$="File read error "+STR$(ST):RETURN GOTO DO_SLURP_LOOP DO_SLURP_DONE: CLOSE 1 @@ -164,7 +152,25 @@ DO_FUNCTION: A%=AA%:GOSUB LIST_Q R%=R%+1: REM map to mal false/true RETURN + DO_VECTOR: + R%=0 + RETURN + DO_VECTOR_Q: + R%=1 + IF (Z%(AA%,0)AND15)=7 THEN R%=2 + RETURN + DO_HASH_MAP: + R%=0 + RETURN + DO_MAP_Q: + R%=1 + IF (Z%(AA%,0)AND15)=8 THEN R%=2 + RETURN + DO_SEQUENTIAL_Q: + R%=1 + IF (Z%(AA%,0)AND15)=6 OR (Z%(AA%,0)AND15)=7 THEN R%=2 + RETURN DO_CONS: A%=AA%:B%=AB%:GOSUB CONS RETURN @@ -217,7 +223,7 @@ DO_FUNCTION: DO_NTH: B%=Z%(AB%,1) A%=AA%:GOSUB COUNT - IF R%<=B% THEN R%=0:ER%=1:ER$="nth: index out of range":RETURN + IF R%<=B% THEN R%=0:ER%=-1:ER$="nth: index out of range":RETURN DO_NTH_LOOP: IF B%=0 THEN GOTO DO_NTH_DONE B%=B%-1 @@ -247,6 +253,87 @@ DO_FUNCTION: Z%(R%,0)=2+16 Z%(R%,1)=R4% RETURN + DO_APPLY: + F%=AA% + AR%=Z%(AR%,1) + A%=AR%:GOSUB COUNT:R4%=R% + + REM no intermediate args, just call APPLY directly + IF R4%<=1 THEN AR%=Z%(AR%+1,1):GOSUB APPLY:RETURN + + REM prepend intermediate args to final args element + A%=AR%:B%=0:C%=R4%-1:GOSUB SLICE + REM release the terminator of new list (we skip over it) + AY%=Z%(R6%,1):GOSUB RELEASE + REM attach end of slice to final args element + Z%(R6%,1)=Z%(A%+1,1) + Z%(Z%(A%+1,1),0)=Z%(Z%(A%+1,1),0)+16 + + ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push/save new args for release + AR%=R%:GOSUB APPLY + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE: REM pop/release new args + RETURN + DO_MAP: + F%=AA% + + REM first result list element + SZ%=2:GOSUB ALLOC + + REM push future return val, prior entry, F% and AB% + ZL%=ZL%+4:ZZ%(ZL%-3)=R%:ZZ%(ZL%-2)=0:ZZ%(ZL%-1)=F%:ZZ%(ZL%)=AB% + + DO_MAP_LOOP: + REM set base values + Z%(R%,0)=6+16:Z%(R%,1)=0 + Z%(R%+1,0)=14:Z%(R%+1,1)=0 + + REM set previous to current if not the first element + IF ZZ%(ZL%-2)<>0 THEN Z%(ZZ%(ZL%-2),1)=R% + REM update previous reference to current + ZZ%(ZL%-2)=R% + + IF Z%(AB%,1)=0 THEN GOTO DO_MAP_DONE + + REM create argument list for apply call + SZ%=2:GOSUB ALLOC + Z%(R%,0)=6+16:Z%(R%,1)=0 + Z%(R%+1,0)=14:Z%(R%+1,1)=0 + AR%=R%: REM save end of list temporarily + SZ%=2:GOSUB ALLOC + Z%(R%,0)=6+16:Z%(R%,1)=AR% + REM inc ref cnt of referred argument + A%=Z%(AB%+1,1): Z%(A%,0)=Z%(A%,0)+16 + Z%(R%+1,0)=14:Z%(R%+1,1)=A% + + REM push argument list + ZL%=ZL%+1:ZZ%(ZL%)=R% + + AR%=R%:GOSUB APPLY + + REM pop apply args are release them + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + + REM set the result value + Z%(ZZ%(ZL%-2)+1,1)=R% + + REM restore F% + F%=ZZ%(ZL%-1) + + REM update AB% to next source element + ZZ%(ZL%)=Z%(ZZ%(ZL%),1) + AB%=ZZ%(ZL%) + + REM allocate next element + SZ%=2:GOSUB ALLOC + + GOTO DO_MAP_LOOP + + DO_MAP_DONE: + REM get return val + R%=ZZ%(ZL%-3) + REM pop everything off stack + ZL%=ZL%-4 + RETURN DO_ATOM: SZ%=1:GOSUB ALLOC @@ -322,6 +409,13 @@ INIT_CORE_NS: REM must match DO_FUNCTION mappings K$="=":A%=1:GOSUB INIT_CORE_SET_FUNCTION + K$="throw":A%=2:GOSUB INIT_CORE_SET_FUNCTION + K$="nil?":A%=3:GOSUB INIT_CORE_SET_FUNCTION + K$="true?":A%=4:GOSUB INIT_CORE_SET_FUNCTION + K$="false?":A%=5:GOSUB INIT_CORE_SET_FUNCTION + K$="string?":A%=6:GOSUB INIT_CORE_SET_FUNCTION + K$="symbol":A%=7:GOSUB INIT_CORE_SET_FUNCTION + K$="symbol?":A%=8:GOSUB INIT_CORE_SET_FUNCTION K$="pr-str":A%=11:GOSUB INIT_CORE_SET_FUNCTION K$="str":A%=12:GOSUB INIT_CORE_SET_FUNCTION @@ -343,7 +437,12 @@ INIT_CORE_NS: K$="list":A%=27:GOSUB INIT_CORE_SET_FUNCTION K$="list?":A%=28:GOSUB INIT_CORE_SET_FUNCTION + K$="vector":A%=29:GOSUB INIT_CORE_SET_FUNCTION + K$="vector?":A%=30:GOSUB INIT_CORE_SET_FUNCTION + K$="hash-map":A%=31:GOSUB INIT_CORE_SET_FUNCTION + K$="map?":A%=32:GOSUB INIT_CORE_SET_FUNCTION + K$="sequential?":A%=39:GOSUB INIT_CORE_SET_FUNCTION K$="cons":A%=40:GOSUB INIT_CORE_SET_FUNCTION K$="concat":A%=41:GOSUB INIT_CORE_SET_FUNCTION K$="nth":A%=42:GOSUB INIT_CORE_SET_FUNCTION @@ -351,6 +450,8 @@ INIT_CORE_NS: K$="rest":A%=44:GOSUB INIT_CORE_SET_FUNCTION K$="empty?":A%=45:GOSUB INIT_CORE_SET_FUNCTION K$="count":A%=46:GOSUB INIT_CORE_SET_FUNCTION + K$="apply":A%=47:GOSUB INIT_CORE_SET_FUNCTION + K$="map":A%=48:GOSUB INIT_CORE_SET_FUNCTION K$="atom":A%=53:GOSUB INIT_CORE_SET_FUNCTION K$="atom?":A%=54:GOSUB INIT_CORE_SET_FUNCTION diff --git a/basic/env.in.bas b/basic/env.in.bas index c2f0c4e760..1a8be3fde5 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -88,7 +88,7 @@ ENV_FIND: REM ENV_GET(E%, K%) -> R% ENV_GET: GOSUB ENV_FIND - IF R%=-1 THEN R%=0:ER%=1:ER$="'"+ZS$(Z%(K%,1))+"' not found":RETURN + IF R%=-1 THEN R%=0:ER%=-1:ER$="'"+ZS$(Z%(K%,1))+"' not found":RETURN R%=T4%:GOSUB DEREF_R Z%(R%,0)=Z%(R%,0)+16 RETURN diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 7454ef6ecb..4f75b98861 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -38,7 +38,7 @@ READ_ATOM: REM READ_FORM(A$, IDX%) -> R% READ_FORM: - IF ER% THEN RETURN + IF ER%<>-2 THEN RETURN GOSUB SKIP_SPACES GOSUB READ_TOKEN IF T$="" AND SD%>0 THEN ER$="unexpected EOF":GOTO READ_FORM_ABORT @@ -190,7 +190,7 @@ READ_FORM: GOTO READ_FORM READ_FORM_ABORT: - ER%=1 + ER%=-1 R%=0 READ_FORM_ABORT_UNWIND: IF SD%=0 THEN RETURN diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index 415fd06df5..ae921bc38f 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -25,10 +25,10 @@ MAL_PRINT: REM REP(A$) -> R$ REP: GOSUB MAL_READ - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:GOSUB EVAL - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:GOSUB MAL_PRINT RT$=R$ @@ -51,7 +51,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -62,6 +62,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0:ER$="" + ER%=-2:ER$="" RETURN diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index 3c1c2469cf..41e91dbb6e 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -19,7 +19,7 @@ EVAL_AST: REM push A% and E% on the stack ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% - IF ER%<>0 THEN GOTO EVAL_AST_RETURN + IF ER%<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A @@ -35,7 +35,7 @@ EVAL_AST: EVAL_AST_SYMBOL: HM%=E%:K%=A%:GOSUB HASHMAP_GET GOSUB DEREF_R - IF T3%=0 THEN ER%=1:ER$="'"+ZS$(Z%(A%,1))+"' not found":GOTO EVAL_AST_RETURN + IF T3%=0 THEN ER%=-1:ER$="'"+ZS$(Z%(A%,1))+"' not found":GOTO EVAL_AST_RETURN Z%(R%,0)=Z%(R%,0)+16 GOTO EVAL_AST_RETURN @@ -88,7 +88,7 @@ EVAL_AST: REM update previous value pointer to evaluated entry Z%(ZZ%(ZL%)+1,1)=R% - IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry SZ%=2:GOSUB ALLOC @@ -123,8 +123,8 @@ EVAL: REM push A% and E% on the stack ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% - REM AZ%=A%: GOSUB PR_STR - REM PRINT "EVAL: "+R$+"("+STR$(A%)+"), LV%:"+STR$(LV%) + REM AZ%=A%:PR%=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" GOSUB DEREF_A @@ -143,19 +143,17 @@ EVAL: R3%=R% REM if error, return f/args for release by caller - IF ER%<>0 THEN GOTO EVAL_RETURN + IF ER%<>-2 THEN GOTO EVAL_RETURN F%=R%+1 AR%=Z%(R%,1): REM rest R%=F%:GOSUB DEREF_R:F%=R% - IF (Z%(F%,0)AND15)<>9 THEN ER%=1:ER$="apply of non-function":GOTO EVAL_RETURN + IF (Z%(F%,0)AND15)<>9 THEN ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION AY%=R3%:GOSUB RELEASE GOTO EVAL_RETURN EVAL_RETURN: - REM an error occured, free up any new value - IF ER%=1 THEN AY%=R%:GOSUB RELEASE LV%=LV%-1: REM track basic return stack level @@ -190,7 +188,7 @@ DO_FUNCTION: IF FF%=2 THEN GOTO DO_SUB IF FF%=3 THEN GOTO DO_MULT IF FF%=4 THEN GOTO DO_DIV - ER%=1:ER$="unknown function"+STR$(FF%):RETURN + ER%=-1:ER$="unknown function"+STR$(FF%):RETURN DO_ADD: Z%(R%,0)=2+16 @@ -223,11 +221,11 @@ REP: R1%=0:R2%=0 GOSUB MAL_READ R1%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:E%=RE%:GOSUB EVAL R2%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:GOSUB MAL_PRINT RT$=R$ @@ -272,7 +270,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -283,6 +281,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0:ER$="" + ER%=-2:ER$="" RETURN diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index fcb24d19f2..04c2fe9d5d 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -20,7 +20,7 @@ EVAL_AST: REM push A% and E% on the stack ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% - IF ER%<>0 THEN GOTO EVAL_AST_RETURN + IF ER%<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A @@ -86,7 +86,7 @@ EVAL_AST: REM update previous value pointer to evaluated entry Z%(ZZ%(ZL%)+1,1)=R% - IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry SZ%=2:GOSUB ALLOC @@ -102,9 +102,9 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER%=0 THEN R%=ZZ%(ZL%-1) + IF ER%=-2 THEN R%=ZZ%(ZL%-1) REM otherwise, free the return value and return nil - IF ER%<>0 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE REM pop previous, return, index and type ZL%=ZL%-4 @@ -169,7 +169,7 @@ EVAL: A%=A2%:GOSUB EVAL: REM eval a2 A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% - IF ER%<>0 THEN GOTO EVAL_RETURN + IF ER%<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 K%=A1%:V%=R%:GOSUB ENV_SET @@ -208,12 +208,12 @@ EVAL: R3%=R% REM if error, return f/args for release by caller - IF ER%<>0 THEN GOTO EVAL_RETURN + IF ER%<>-2 THEN GOTO EVAL_RETURN F%=R%+1 AR%=Z%(R%,1): REM rest R%=F%:GOSUB DEREF_R:F%=R% - IF (Z%(F%,0)AND15)<>9 THEN ER%=1:ER$="apply of non-function":GOTO EVAL_RETURN + IF (Z%(F%,0)AND15)<>9 THEN ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION AY%=R3%:GOSUB RELEASE GOTO EVAL_RETURN @@ -258,7 +258,7 @@ DO_FUNCTION: IF FF%=2 THEN GOTO DO_SUB IF FF%=3 THEN GOTO DO_MULT IF FF%=4 THEN GOTO DO_DIV - ER%=1:ER$="unknown function"+STR$(FF%):RETURN + ER%=-1:ER$="unknown function"+STR$(FF%):RETURN DO_ADD: Z%(R%,0)=2+16 @@ -291,11 +291,11 @@ REP: R1%=0:R2%=0 GOSUB MAL_READ R1%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:E%=RE%:GOSUB EVAL R2%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:GOSUB MAL_PRINT RT$=R$ @@ -318,20 +318,20 @@ MAIN: E%=RE% REM + function - A%=1: GOSUB NATIVE_FUNCTION - K$="+": V%=R%: GOSUB ENV_SET_S + A%=1:GOSUB NATIVE_FUNCTION + K$="+":V%=R%:GOSUB ENV_SET_S REM - function - A%=2: GOSUB NATIVE_FUNCTION - K$="-": V%=R%: GOSUB ENV_SET_S + A%=2:GOSUB NATIVE_FUNCTION + K$="-":V%=R%:GOSUB ENV_SET_S REM * function - A%=3: GOSUB NATIVE_FUNCTION - K$="*": V%=R%: GOSUB ENV_SET_S + A%=3:GOSUB NATIVE_FUNCTION + K$="*":V%=R%:GOSUB ENV_SET_S REM / function - A%=4: GOSUB NATIVE_FUNCTION - K$="/": V%=R%: GOSUB ENV_SET_S + A%=4:GOSUB NATIVE_FUNCTION + K$="/":V%=R%:GOSUB ENV_SET_S ZT%=ZI%: REM top of memory after base repl_env @@ -341,7 +341,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -352,6 +352,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0:ER$="" + ER%=-2:ER$="" RETURN diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index 024ed72ab7..4dd86f3dd7 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -21,7 +21,7 @@ EVAL_AST: REM push A% and E% on the stack ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% - IF ER%<>0 THEN GOTO EVAL_AST_RETURN + IF ER%<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A @@ -87,7 +87,7 @@ EVAL_AST: REM update previous value pointer to evaluated entry Z%(ZZ%(ZL%)+1,1)=R% - IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry SZ%=2:GOSUB ALLOC @@ -103,9 +103,9 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER%=0 THEN R%=ZZ%(ZL%-1) + IF ER%=-2 THEN R%=ZZ%(ZL%-1) REM otherwise, free the return value and return nil - IF ER%<>0 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE REM pop previous, return, index and type ZL%=ZL%-4 @@ -181,7 +181,7 @@ EVAL: A%=A2%:GOSUB EVAL: REM eval a2 A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% - IF ER%<>0 THEN GOTO EVAL_RETURN + IF ER%<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 K%=A1%:V%=R%:GOSUB ENV_SET @@ -261,7 +261,7 @@ EVAL: EVAL_AST_RETURN_3: REM if error, return f/args for release by caller - IF ER%<>0 THEN GOTO EVAL_RETURN + IF ER%<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call ZL%=ZL%+1:ZZ%(ZL%)=R% @@ -276,7 +276,7 @@ EVAL: REM if error, pop and return f/args for release by caller R%=ZZ%(ZL%):ZL%=ZL%-1 - ER%=1:ER$="apply of non-function":GOTO EVAL_RETURN + ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION @@ -340,7 +340,7 @@ RE: R1%=0 GOSUB MAL_READ R1%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:E%=RE%:GOSUB EVAL @@ -355,11 +355,11 @@ REP: R1%=0:R2%=0 GOSUB MAL_READ R1%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:E%=RE%:GOSUB EVAL R2%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:GOSUB MAL_PRINT RT$=R$ @@ -395,7 +395,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -406,6 +406,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0:ER$="" + ER%=-2:ER$="" RETURN diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index 2dbb3c11a0..1291befd2c 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -21,7 +21,7 @@ EVAL_AST: REM push A% and E% on the stack ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% - IF ER%<>0 THEN GOTO EVAL_AST_RETURN + IF ER%<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A @@ -87,7 +87,7 @@ EVAL_AST: REM update previous value pointer to evaluated entry Z%(ZZ%(ZL%)+1,1)=R% - IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry SZ%=2:GOSUB ALLOC @@ -103,9 +103,9 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER%=0 THEN R%=ZZ%(ZL%-1) + IF ER%=-2 THEN R%=ZZ%(ZL%-1) REM otherwise, free the return value and return nil - IF ER%<>0 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE REM pop previous, return, index and type ZL%=ZL%-4 @@ -181,7 +181,7 @@ EVAL: A%=A2%:GOSUB EVAL: REM eval a2 A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% - IF ER%<>0 THEN GOTO EVAL_RETURN + IF ER%<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 K%=A1%:V%=R%:GOSUB ENV_SET @@ -270,7 +270,7 @@ EVAL: EVAL_AST_RETURN_3: REM if error, return f/args for release by caller - IF ER%<>0 THEN GOTO EVAL_RETURN + IF ER%<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call ZL%=ZL%+1:ZZ%(ZL%)=R% @@ -285,7 +285,7 @@ EVAL: REM if error, pop and return f/args for release by caller R%=ZZ%(ZL%):ZL%=ZL%-1 - ER%=1:ER$="apply of non-function":GOTO EVAL_RETURN + ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION @@ -349,7 +349,7 @@ RE: R1%=0 GOSUB MAL_READ R1%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:E%=RE%:GOSUB EVAL @@ -364,11 +364,11 @@ REP: R1%=0:R2%=0 GOSUB MAL_READ R1%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:E%=RE%:GOSUB EVAL R2%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:GOSUB MAL_PRINT RT$=R$ @@ -404,7 +404,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -415,6 +415,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0:ER$="" + ER%=-2:ER$="" RETURN diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index 4e197e7eed..d75b86ac32 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -21,7 +21,7 @@ EVAL_AST: REM push A% and E% on the stack ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% - IF ER%<>0 THEN GOTO EVAL_AST_RETURN + IF ER%<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A @@ -87,7 +87,7 @@ EVAL_AST: REM update previous value pointer to evaluated entry Z%(ZZ%(ZL%)+1,1)=R% - IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry SZ%=2:GOSUB ALLOC @@ -103,9 +103,9 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER%=0 THEN R%=ZZ%(ZL%-1) + IF ER%=-2 THEN R%=ZZ%(ZL%-1) REM otherwise, free the return value and return nil - IF ER%<>0 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE REM pop previous, return, index and type ZL%=ZL%-4 @@ -181,7 +181,7 @@ EVAL: A%=A2%:GOSUB EVAL: REM eval a2 A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% - IF ER%<>0 THEN GOTO EVAL_RETURN + IF ER%<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 K%=A1%:V%=R%:GOSUB ENV_SET @@ -270,7 +270,7 @@ EVAL: EVAL_AST_RETURN_3: REM if error, return f/args for release by caller - IF ER%<>0 THEN GOTO EVAL_RETURN + IF ER%<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call ZL%=ZL%+1:ZZ%(ZL%)=R% @@ -285,7 +285,7 @@ EVAL: REM if error, pop and return f/args for release by caller R%=ZZ%(ZL%):ZL%=ZL%-1 - ER%=1:ER$="apply of non-function":GOTO EVAL_RETURN + ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION @@ -349,7 +349,7 @@ RE: R1%=0 GOSUB MAL_READ R1%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:E%=RE%:GOSUB EVAL @@ -364,11 +364,11 @@ REP: R1%=0:R2%=0 GOSUB MAL_READ R1%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:E%=RE%:GOSUB EVAL R2%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:GOSUB MAL_PRINT RT$=R$ @@ -423,7 +423,7 @@ MAIN: REM run a single mal program and exit A$="(load-file (first -*ARGS*-))" GOSUB RE - IF ER%<>0 THEN GOSUB PRINT_ERROR + IF ER%<>-2 THEN GOSUB PRINT_ERROR END REPL_LOOP: @@ -432,7 +432,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -443,6 +443,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0:ER$="" + ER%=-2:ER$="" RETURN diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index 2bbc29a3ae..2c496e492a 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -94,7 +94,7 @@ EVAL_AST: REM push A% and E% on the stack ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% - IF ER%<>0 THEN GOTO EVAL_AST_RETURN + IF ER%<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A @@ -160,7 +160,7 @@ EVAL_AST: REM update previous value pointer to evaluated entry Z%(ZZ%(ZL%)+1,1)=R% - IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry SZ%=2:GOSUB ALLOC @@ -176,9 +176,9 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER%=0 THEN R%=ZZ%(ZL%-1) + IF ER%=-2 THEN R%=ZZ%(ZL%-1) REM otherwise, free the return value and return nil - IF ER%<>0 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE REM pop previous, return, index and type ZL%=ZL%-4 @@ -256,7 +256,7 @@ EVAL: A%=A2%:GOSUB EVAL: REM eval a2 A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% - IF ER%<>0 THEN GOTO EVAL_RETURN + IF ER%<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 K%=A1%:V%=R%:GOSUB ENV_SET @@ -359,7 +359,7 @@ EVAL: EVAL_AST_RETURN_3: REM if error, return f/args for release by caller - IF ER%<>0 THEN GOTO EVAL_RETURN + IF ER%<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call ZL%=ZL%+1:ZZ%(ZL%)=R% @@ -374,7 +374,7 @@ EVAL: REM if error, pop and return f/args for release by caller R%=ZZ%(ZL%):ZL%=ZL%-1 - ER%=1:ER$="apply of non-function":GOTO EVAL_RETURN + ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION @@ -438,7 +438,7 @@ RE: R1%=0 GOSUB MAL_READ R1%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:E%=RE%:GOSUB EVAL @@ -453,11 +453,11 @@ REP: R1%=0:R2%=0 GOSUB MAL_READ R1%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:E%=RE%:GOSUB EVAL R2%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:GOSUB MAL_PRINT RT$=R$ @@ -512,7 +512,7 @@ MAIN: REM run a single mal program and exit A$="(load-file (first -*ARGS*-))" GOSUB RE - IF ER%<>0 THEN GOSUB PRINT_ERROR + IF ER%<>-2 THEN GOSUB PRINT_ERROR END REPL_LOOP: @@ -521,7 +521,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -532,6 +532,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0:ER$="" + ER%=-2:ER$="" RETURN diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index 5c38aedb06..f51a5d23d7 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -115,7 +115,7 @@ MACROEXPAND: REM intermediate form) then free it IF A%<>AY% THEN ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV% - IF ER%<>0 THEN GOTO MACROEXPAND_DONE + IF ER%<>-2 THEN GOTO MACROEXPAND_DONE GOTO MACROEXPAND_LOOP MACROEXPAND_DONE: @@ -129,7 +129,7 @@ EVAL_AST: REM push A% and E% on the stack ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% - IF ER%<>0 THEN GOTO EVAL_AST_RETURN + IF ER%<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A @@ -195,7 +195,7 @@ EVAL_AST: REM update previous value pointer to evaluated entry Z%(ZZ%(ZL%)+1,1)=R% - IF ER%<>0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry SZ%=2:GOSUB ALLOC @@ -211,9 +211,9 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER%=0 THEN R%=ZZ%(ZL%-1) + IF ER%=-2 THEN R%=ZZ%(ZL%-1) REM otherwise, free the return value and return nil - IF ER%<>0 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE REM pop previous, return, index and type ZL%=ZL%-4 @@ -299,7 +299,7 @@ EVAL: A%=A2%:GOSUB EVAL: REM eval a2 A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% - IF ER%<>0 THEN GOTO EVAL_RETURN + IF ER%<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 K%=A1%:V%=R%:GOSUB ENV_SET @@ -426,7 +426,7 @@ EVAL: EVAL_AST_RETURN_3: REM if error, return f/args for release by caller - IF ER%<>0 THEN GOTO EVAL_RETURN + IF ER%<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call ZL%=ZL%+1:ZZ%(ZL%)=R% @@ -441,7 +441,7 @@ EVAL: REM if error, pop and return f/args for release by caller R%=ZZ%(ZL%):ZL%=ZL%-1 - ER%=1:ER$="apply of non-function":GOTO EVAL_RETURN + ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION @@ -505,7 +505,7 @@ RE: R1%=0 GOSUB MAL_READ R1%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:E%=RE%:GOSUB EVAL @@ -520,11 +520,11 @@ REP: R1%=0:R2%=0 GOSUB MAL_READ R1%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:E%=RE%:GOSUB EVAL R2%=R% - IF ER%<>0 THEN GOTO REP_DONE + IF ER%<>-2 THEN GOTO REP_DONE A%=R%:GOSUB MAL_PRINT RT$=R$ @@ -588,7 +588,7 @@ MAIN: REM run a single mal program and exit A$="(load-file (first -*ARGS*-))" GOSUB RE - IF ER%<>0 THEN GOSUB PRINT_ERROR + IF ER%<>-2 THEN GOSUB PRINT_ERROR END REPL_LOOP: @@ -597,7 +597,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>0 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -608,6 +608,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=0:ER$="" + ER%=-2:ER$="" RETURN diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas new file mode 100755 index 0000000000..4b212fde2e --- /dev/null +++ b/basic/step9_try.in.bas @@ -0,0 +1,647 @@ +REM POKE 1, PEEK(1)AND248: REM enable all ROM areas as RAM +REM POKE 55,0: POKE 56,192: CLR: REM move BASIC end from $A000 to $C000 +GOTO MAIN + +REM $INCLUDE: 'readline.in.bas' +REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'reader.in.bas' +REM $INCLUDE: 'printer.in.bas' +REM $INCLUDE: 'env.in.bas' +REM $INCLUDE: 'core.in.bas' + +REM $INCLUDE: 'debug.in.bas' + +REM READ(A$) -> R% +MAL_READ: + GOSUB READ_STR + RETURN + +REM PAIR_Q(B%) -> R% +PAIR_Q: + R%=0 + IF (Z%(B%,0)AND15)<>6 AND (Z%(B%,0)AND15)<>7 THEN RETURN + IF (Z%(B%,1)=0) THEN RETURN + R%=1 + RETURN + +REM QUASIQUOTE(A%) -> R% +QUASIQUOTE: + B%=A%:GOSUB PAIR_Q + IF R%=1 THEN GOTO QQ_UNQUOTE + REM ['quote, ast] + AS$="quote":T%=5:GOSUB STRING + B2%=R%:B1%=A%:GOSUB LIST2 + + RETURN + + QQ_UNQUOTE: + R%=A%+1:GOSUB DEREF_R + IF (Z%(R%,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF ZS$(Z%(R%,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE + REM [ast[1]] + R%=Z%(A%,1)+1:GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + + RETURN + + QQ_SPLICE_UNQUOTE: + REM push A% on the stack + ZL%=ZL%+1:ZZ%(ZL%)=A% + REM rest of cases call quasiquote on ast[1..] + A%=Z%(A%,1):GOSUB QUASIQUOTE:T6%=R% + REM pop A% off the stack + A%=ZZ%(ZL%):ZL%=ZL%-1 + + REM set A% to ast[0] for last two cases + A%=A%+1:GOSUB DEREF_A + + B%=A%:GOSUB PAIR_Q + IF R%=0 THEN GOTO QQ_DEFAULT + B%=A%+1:GOSUB DEREF_B + IF (Z%(B%,0)AND15)<>5 THEN GOTO QQ_DEFAULT + IF ZS$(Z%(B%,1))<>"splice-unquote" THEN QQ_DEFAULT + REM ['concat, ast[0][1], quasiquote(ast[1..])] + + B%=Z%(A%,1)+1:GOSUB DEREF_B:B2%=B% + AS$="concat":T%=5:GOSUB STRING:B3%=R% + B1%=T6%:GOSUB LIST3 + REM release inner quasiquoted since outer list takes ownership + AY%=B1%:GOSUB RELEASE + RETURN + + QQ_DEFAULT: + REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] + + REM push T6% on the stack + ZL%=ZL%+1:ZZ%(ZL%)=T6% + REM A% set above to ast[0] + GOSUB QUASIQUOTE:B2%=R% + REM pop T6% off the stack + T6%=ZZ%(ZL%):ZL%=ZL%-1 + + AS$="cons":T%=5:GOSUB STRING:B3%=R% + B1%=T6%:GOSUB LIST3 + REM release inner quasiquoted since outer list takes ownership + AY%=B1%:GOSUB RELEASE + AY%=B2%:GOSUB RELEASE + RETURN + +REM MACROEXPAND(A%, E%) -> A%: +MACROEXPAND: + REM push original A% + ZL%=ZL%+1:ZZ%(ZL%)=A% + + MACROEXPAND_LOOP: + REM list? + IF (Z%(A%,0)AND15)<>6 THEN GOTO MACROEXPAND_DONE + REM non-empty? + IF Z%(A%,1)=0 THEN GOTO MACROEXPAND_DONE + B%=A%+1:GOSUB DEREF_B + REM symbol? in first position + IF (Z%(B%,0)AND15)<>5 THEN GOTO MACROEXPAND_DONE + REM defined in environment? + K%=B%:GOSUB ENV_FIND + IF R%=-1 THEN GOTO MACROEXPAND_DONE + B%=T4%:GOSUB DEREF_B + REM macro? + IF (Z%(B%,0)AND15)<>11 THEN GOTO MACROEXPAND_DONE + + REM apply + F%=B%:AR%=Z%(A%,1):GOSUB APPLY + A%=R% + + AY%=ZZ%(ZL%) + REM if previous A% was not the first A% into macroexpand (i.e. an + REM intermediate form) then free it + IF A%<>AY% THEN ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV% + + IF ER%<>-2 THEN GOTO MACROEXPAND_DONE + GOTO MACROEXPAND_LOOP + + MACROEXPAND_DONE: + ZL%=ZL%-1: REM pop original A% + RETURN + +REM EVAL_AST(A%, E%) -> R% +REM called using GOTO to avoid basic return address stack usage +REM top of stack should have return label index +EVAL_AST: + REM push A% and E% on the stack + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + + IF ER%<>-2 THEN GOTO EVAL_AST_RETURN + + GOSUB DEREF_A + + T%=Z%(A%,0)AND15 + IF T%=5 THEN GOTO EVAL_AST_SYMBOL + IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ + + REM scalar: deref to actual value and inc ref cnt + R%=A%:GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + GOTO EVAL_AST_RETURN + + EVAL_AST_SYMBOL: + K%=A%:GOSUB ENV_GET + GOTO EVAL_AST_RETURN + + EVAL_AST_SEQ: + REM allocate the first entry + SZ%=2:GOSUB ALLOC + + REM make space on the stack + ZL%=ZL%+4 + REM push type of sequence + ZZ%(ZL%-3)=T% + REM push sequence index + ZZ%(ZL%-2)=-1 + REM push future return value (new sequence) + ZZ%(ZL%-1)=R% + REM push previous new sequence entry + ZZ%(ZL%)=R% + + EVAL_AST_SEQ_LOOP: + REM set new sequence entry type (with 1 ref cnt) + Z%(R%,0)=ZZ%(ZL%-3)+16 + Z%(R%,1)=0 + REM create value ptr placeholder + Z%(R%+1,0)=14 + Z%(R%+1,1)=0 + + REM update index + ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + + REM check if we are done evaluating the source sequence + IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + + REM if hashmap, skip eval of even entries (keys) + IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + GOTO EVAL_AST_DO_EVAL + + EVAL_AST_DO_REF: + R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry + Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + GOTO EVAL_AST_ADD_VALUE + + EVAL_AST_DO_EVAL: + REM call EVAL for each entry + A%=A%+1:GOSUB EVAL + A%=A%-1 + GOSUB DEREF_R: REM deref to target of evaluated entry + + EVAL_AST_ADD_VALUE: + + REM update previous value pointer to evaluated entry + Z%(ZZ%(ZL%)+1,1)=R% + + IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + + REM allocate the next entry + SZ%=2:GOSUB ALLOC + + REM update previous sequence entry value to point to new entry + Z%(ZZ%(ZL%),1)=R% + REM update previous ptr to current entry + ZZ%(ZL%)=R% + + REM process the next sequence entry from source list + A%=Z%(A%,1) + + GOTO EVAL_AST_SEQ_LOOP + EVAL_AST_SEQ_LOOP_DONE: + REM if no error, get return value (new seq) + IF ER%=-2 THEN R%=ZZ%(ZL%-1) + REM otherwise, free the return value and return nil + IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + + REM pop previous, return, index and type + ZL%=ZL%-4 + GOTO EVAL_AST_RETURN + + EVAL_AST_RETURN: + REM pop A% and E% off the stack + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + + REM pop EVAL AST return label/address + RN%=ZZ%(ZL%):ZL%=ZL%-1 + ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 + RETURN + +REM EVAL(A%, E%)) -> R% +EVAL: + LV%=LV%+1: REM track basic return stack level + + REM push A% and E% on the stack + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + + EVAL_TCO_RECUR: + + REM AZ%=A%:PR%=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" + + GOSUB DEREF_A + + GOSUB LIST_Q + IF R% THEN GOTO APPLY_LIST + EVAL_NOT_LIST: + REM ELSE + REM push EVAL_AST return label/address + ZL%=ZL%+1:ZZ%(ZL%)=1 + GOTO EVAL_AST + EVAL_AST_RETURN_1: + + GOTO EVAL_RETURN + + APPLY_LIST: + GOSUB MACROEXPAND + + GOSUB LIST_Q + IF R%<>1 THEN GOTO EVAL_NOT_LIST + + GOSUB EMPTY_Q + IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN + + A0%=A%+1 + R%=A0%:GOSUB DEREF_R:A0%=R% + + REM get symbol in A$ + IF (Z%(A0%,0)AND15)<>5 THEN A$="" + IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) + + IF A$="def!" THEN GOTO EVAL_DEF + IF A$="let*" THEN GOTO EVAL_LET + IF A$="quote" THEN GOTO EVAL_QUOTE + IF A$="quasiquote" THEN GOTO EVAL_QUASIQUOTE + IF A$="defmacro!" THEN GOTO EVAL_DEFMACRO + IF A$="macroexpand" THEN GOTO EVAL_MACROEXPAND + IF A$="try*" THEN GOTO EVAL_TRY + IF A$="do" THEN GOTO EVAL_DO + IF A$="if" THEN GOTO EVAL_IF + IF A$="fn*" THEN GOTO EVAL_FN + GOTO EVAL_INVOKE + + EVAL_GET_A3: + A3%=Z%(Z%(Z%(A%,1),1),1)+1 + R%=A3%:GOSUB DEREF_R:A3%=R% + EVAL_GET_A2: + A2%=Z%(Z%(A%,1),1)+1 + R%=A2%:GOSUB DEREF_R:A2%=R% + EVAL_GET_A1: + A1%=Z%(A%,1)+1 + R%=A1%:GOSUB DEREF_R:A1%=R% + RETURN + + EVAL_DEF: + REM PRINT "def!" + GOSUB EVAL_GET_A2: REM set a1% and a2% + + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + A%=A2%:GOSUB EVAL: REM eval a2 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + + IF ER%<>-2 THEN GOTO EVAL_RETURN + + REM set a1 in env to a2 + K%=A1%:V%=R%:GOSUB ENV_SET + GOTO EVAL_RETURN + + EVAL_LET: + REM PRINT "let*" + GOSUB EVAL_GET_A2: REM set a1% and a2% + + ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% + ZL%=ZL%+1:ZZ%(ZL%)=E%: REM push env for for later release + + REM create new environment with outer as current environment + EO%=E%:GOSUB ENV_NEW + E%=R% + EVAL_LET_LOOP: + IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + REM eval current A1 odd element + A%=Z%(A1%,1)+1:GOSUB EVAL + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + + REM set environment: even A1% key to odd A1% eval'd above + K%=A1%+1:V%=R%:GOSUB ENV_SET + AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership + + REM skip to the next pair of A1% elements + A1%=Z%(Z%(A1%,1),1) + GOTO EVAL_LET_LOOP + + EVAL_LET_LOOP_DONE: + E4%=ZZ%(ZL%):ZL%=ZL%-1: REM pop previous env + + REM release previous environment if not the current EVAL env + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + + A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% + A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_DO: + A%=Z%(A%,1): REM rest + + REM TODO: TCO + + REM push EVAL_AST return label/address + ZL%=ZL%+1:ZZ%(ZL%)=2 + GOTO EVAL_AST + EVAL_AST_RETURN_2: + + ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push eval'd list + A%=R%:GOSUB LAST: REM return the last element + AY%=ZZ%(ZL%):ZL%=ZL%-1: REM pop eval'd list + GOSUB RELEASE: REM release the eval'd list + GOTO EVAL_RETURN + + EVAL_QUOTE: + R%=Z%(A%,1)+1:GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + GOTO EVAL_RETURN + + EVAL_QUASIQUOTE: + R%=Z%(A%,1)+1:GOSUB DEREF_R + A%=R%:GOSUB QUASIQUOTE + REM add quasiquote result to pending release queue to free when + REM next lower EVAL level returns (LV%) + ZM%=ZM%+1:ZR%(ZM%,0)=R%:ZR%(ZM%,1)=LV% + + A%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_DEFMACRO: + REM PRINT "defmacro!" + GOSUB EVAL_GET_A2: REM set a1% and a2% + + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + A%=A2%:GOSUB EVAL: REM eval a2 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + + REM change function to macro + Z%(R%,0)=Z%(R%,0)+1 + + REM set a1 in env to a2 + K%=A1%:V%=R%:GOSUB ENV_SET + GOTO EVAL_RETURN + + EVAL_MACROEXPAND: + REM PRINT "macroexpand" + R%=Z%(A%,1)+1:GOSUB DEREF_R + A%=R%:GOSUB MACROEXPAND:R%=A% + + REM since we are returning it unevaluated, inc the ref cnt + Z%(R%,0)=Z%(R%,0)+16 + GOTO EVAL_RETURN + + EVAL_TRY: + REM PRINT "try*" + GOSUB EVAL_GET_A1: REM set a1%, a2%, and a3% + + ZL%=ZL%+1:ZZ%(ZL%)=A%: REM push/save A% + A%=A1%:GOSUB EVAL: REM eval a1 + A%=ZZ%(ZL%):ZL%=ZL%-1: REM pop/restore A% + + REM if there is not error or catch block then return + IF ER%=-2 OR Z%(A%,1)=0 THEN GOTO EVAL_RETURN + + REM create environment for the catch block eval + EO%=E%:GOSUB ENV_NEW:E%=R% + + GOSUB EVAL_GET_A2: REM set a1% and a2% + A%=A2%:GOSUB EVAL_GET_A2: REM set a1% and a2% from catch block + + REM create object for ER%=-1 type raw string errors + IF ER%=-1 THEN AS$=ER$:T%=4:GOSUB STRING:ER%=R%:Z%(R%,0)=Z%(R%,0)+16 + + REM bind the catch symbol to the error object + K%=A1%:V%=ER%:GOSUB ENV_SET + AY%=R%:GOSUB RELEASE: REM release out use, env took ownership + + REM unset error for catch eval + ER%=-2:ER$="" + + A%=A2%:GOSUB EVAL + + GOTO EVAL_RETURN + + EVAL_IF: + GOSUB EVAL_GET_A1: REM set a1% + REM push A% + ZL%=ZL%+1:ZZ%(ZL%)=A% + A%=A1%:GOSUB EVAL + REM pop A% + A%=ZZ%(ZL%):ZL%=ZL%-1 + IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE + + EVAL_IF_TRUE: + AY%=R%:GOSUB RELEASE + GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL + A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + EVAL_IF_FALSE: + AY%=R%:GOSUB RELEASE + REM if no false case (A3%), return nil + IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0:GOTO EVAL_RETURN + GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL + A%=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_FN: + GOSUB EVAL_GET_A2: REM set a1% and a2% + A%=A2%:P%=A1%:GOSUB MAL_FUNCTION + GOTO EVAL_RETURN + + EVAL_INVOKE: + REM push EVAL_AST return label/address + ZL%=ZL%+1:ZZ%(ZL%)=3 + GOTO EVAL_AST + EVAL_AST_RETURN_3: + + REM if error, return f/args for release by caller + IF ER%<>-2 THEN GOTO EVAL_RETURN + + REM push f/args for release after call + ZL%=ZL%+1:ZZ%(ZL%)=R% + + F%=R%+1 + + AR%=Z%(R%,1): REM rest + R%=F%:GOSUB DEREF_R:F%=R% + + IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + + REM if error, pop and return f/args for release by caller + R%=ZZ%(ZL%):ZL%=ZL%-1 + ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN + + EVAL_DO_FUNCTION: + GOSUB DO_FUNCTION + + REM pop and release f/args + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + GOTO EVAL_RETURN + + EVAL_DO_MAL_FUNCTION: + E4%=E%: REM save the current environment for release + + REM create new environ using env stored with function + EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS + + REM release previous env if it is not the top one on the + REM stack (ZZ%(ZL%-2)) because our new env refers to it and + REM we no longer need to track it (since we are TCO recurring) + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + + REM claim the AST before releasing the list containing it + A%=Z%(F%,1):Z%(A%,0)=Z%(A%,0)+16 + REM add AST to pending release queue to free as soon as EVAL + REM actually returns (LV%+1) + ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV%+1 + + REM pop and release f/args + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + + REM A% set above + E%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_RETURN: + REM AZ%=R%: PR%=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + + REM release environment if not the top one on the stack + IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE + + LV%=LV%-1: REM track basic return stack level + + REM release everything we couldn't release earlier + GOSUB RELEASE_PEND + + REM trigger GC + TA%=FRE(0) + + REM pop A% and E% off the stack + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + + RETURN + +REM PRINT(A%) -> R$ +MAL_PRINT: + AZ%=A%:PR%=1:GOSUB PR_STR + RETURN + +REM RE(A$) -> R% +REM Assume RE% has repl_env +REM caller must release result +RE: + R1%=0 + GOSUB MAL_READ + R1%=R% + IF ER%<>-2 THEN GOTO REP_DONE + + A%=R%:E%=RE%:GOSUB EVAL + + REP_DONE: + REM Release memory from MAL_READ + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + RETURN: REM caller must release result of EVAL + +REM REP(A$) -> R$ +REM Assume RE% has repl_env +REP: + R1%=0:R2%=0 + GOSUB MAL_READ + R1%=R% + IF ER%<>-2 THEN GOTO REP_DONE + + A%=R%:E%=RE%:GOSUB EVAL + R2%=R% + IF ER%<>-2 THEN GOTO REP_DONE + + A%=R%:GOSUB MAL_PRINT + RT$=R$ + + REP_DONE: + REM Release memory from MAL_READ and EVAL + IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + R$=RT$ + RETURN + +REM MAIN program +MAIN: + GOSUB INIT_MEMORY + + LV%=0 + + REM create repl_env + EO%=-1:GOSUB ENV_NEW:RE%=R% + + REM core.EXT: defined in Basic + E%=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + + ZT%=ZI%: REM top of memory after base repl_env + + REM core.mal: defined using the language itself + A$="(def! not (fn* (a) (if a false true)))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + A$="(def! load-file (fn* (f) (eval (read-string (str " + A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + A$="(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs)" + A$=A$+" (if (> (count xs) 1) (nth xs 1) (throw "+CHR$(34)+"odd number of" + A$=A$+" forms to cond"+CHR$(34)+")) (cons 'cond (rest (rest xs)))))))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + A$="(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs)" + A$=A$+" `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + REM load the args file + A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + REM set the argument list + A$="(def! *ARGV* (rest -*ARGS*-))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + REM get the first argument + A$="(first -*ARGS*-)" + GOSUB RE + + REM if there is an argument, then run it as a program + IF R%<>0 THEN AY%=R%:GOSUB RELEASE:GOTO RUN_PROG + REM no arguments, start REPL loop + IF R%=0 THEN GOTO REPL_LOOP + + RUN_PROG: + REM run a single mal program and exit + A$="(load-file (first -*ARGS*-))" + GOSUB RE + IF ER%<>-2 THEN GOSUB PRINT_ERROR + END + + REPL_LOOP: + A$="user> ":GOSUB READLINE: REM call input parser + IF EOF=1 THEN GOTO QUIT + + A$=R$:GOSUB REP: REM call REP + + IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + PRINT R$ + GOTO REPL_LOOP + + QUIT: + REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + GOSUB PR_MEMORY_SUMMARY + END + + PRINT_ERROR: + REM if the error is an object, then print and free it + IF ER%>=0 THEN AZ%=ER%:PR%=0:GOSUB PR_STR:ER$=R$:AY%=ER%:GOSUB RELEASE + PRINT "Error: "+ER$ + ER%=-2:ER$="" + RETURN + diff --git a/basic/types.in.bas b/basic/types.in.bas index fcb02a27bf..13c7932ff0 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -31,7 +31,11 @@ INIT_MEMORY: S4%=64: REM ZR% (release stack) size (4 bytes each) REM global error state - ER%=0:ER$="" + REM -2 : no error + REM -1 : string error in ER$ + REM >=0 : pointer to error object + ER%=-2 + ER$="" REM boxed element memory DIM Z%(S1%,1): REM TYPE ARRAY @@ -131,9 +135,9 @@ RELEASE: REM PRINT "RELEASE AY%:"+STR$(AY%)+"["+R$+"] (byte0:"+STR$(Z%(AY%,0))+")" REM sanity check not already freed - IF (U6%)=15 THEN ER%=1:ER$="Free of free memory: "+STR$(AY%):RETURN + IF (U6%)=15 THEN ER%=-1:ER$="Free of free memory: "+STR$(AY%):RETURN IF U6%=14 THEN GOTO RELEASE_REFERENCE - IF Z%(AY%,0)<15 THEN ER%=1:ER$="Free of freed object: "+STR$(AY%):RETURN + IF Z%(AY%,0)<15 THEN ER%=-1:ER$="Free of freed object: "+STR$(AY%):RETURN REM decrease reference count by one Z%(AY%,0)=Z%(AY%,0)-16 @@ -148,8 +152,8 @@ RELEASE: IF U6%=11 THEN GOTO RELEASE_MAL_FUNCTION IF U6%=12 THEN GOTO RELEASE_ATOM IF U6%=13 THEN GOTO RELEASE_ENV - IF U6%=15 THEN ER%=1:ER$="RELEASE of already freed: "+STR$(AY%):RETURN - ER%=1:ER$="RELEASE not defined for type "+STR$(U6%):RETURN + IF U6%=15 THEN ER%=-1:ER$="RELEASE of already freed: "+STR$(AY%):RETURN + ER%=-1:ER$="RELEASE not defined for type "+STR$(U6%):RETURN RELEASE_SIMPLE: REM simple type (no recursing), just call FREE on it @@ -161,7 +165,7 @@ RELEASE: GOTO RELEASE_TOP RELEASE_SEQ: IF Z%(AY%,1)=0 THEN GOTO RELEASE_SIMPLE_2 - IF Z%(AY%+1,0)<>14 THEN ER%=1:ER$="invalid list value"+STR$(AY%+1):RETURN + IF Z%(AY%+1,0)<>14 THEN ER%=-1:ER$="invalid list value"+STR$(AY%+1):RETURN REM add value and next element to stack RC%=RC%+2:ZL%=ZL%+2:ZZ%(ZL%-1)=Z%(AY%+1,1):ZZ%(ZL%)=Z%(AY%,1) GOTO RELEASE_SIMPLE_2 @@ -198,7 +202,6 @@ RELEASE: REM RELEASE_PEND(LV%) -> nil RELEASE_PEND: - REM REM IF ER%<>0 THEN RETURN IF ZM%<0 THEN RETURN IF ZR%(ZM%,1)<=LV% THEN RETURN REM PRINT "RELEASE_PEND releasing:"+STR$(ZR%(ZM%,0)) @@ -366,12 +369,14 @@ CONS: REM SLICE(A%,B%,C%) -> R% REM make copy of sequence A% from index B% to C% +REM returns R6% as reference to last element of slice +REM returns A% as next element following slice (of original) SLICE: I=0 R5%=-1: REM temporary for return as R% R6%=0: REM previous list element SLICE_LOOP: - REM always allocate at list one list element + REM always allocate at least one list element SZ%=2:GOSUB ALLOC Z%(R%,0)=6+16:Z%(R%,1)=0:Z%(R%+1,0)=14:Z%(R%+1,1)=0 IF R5%=-1 THEN R5%=R% From 30a3d8286fcb3bf49fc06a4b1450640992da6b0a Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sun, 9 Oct 2016 20:31:22 -0500 Subject: [PATCH 0166/2308] Basic: stepA basics. --- basic/Makefile | 6 +- basic/core.in.bas | 14 +- basic/stepA_mal.in.bas | 656 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 668 insertions(+), 8 deletions(-) create mode 100755 basic/stepA_mal.in.bas diff --git a/basic/Makefile b/basic/Makefile index f39c5633bf..8263acdb9f 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -18,7 +18,7 @@ step0_repl.bas: $(STEP0_DEPS) step1_read_print.bas step2_eval.bas: $(STEP1_DEPS) step3_env.bas: $(STEP3_DEPS) step4_if_fn_do.bas step5_tco.bas step6_file.bas step7_quote.bas: $(STEP4_DEPS) -step8_macros.bas step9_try.bas: $(STEP4_DEPS) +step8_macros.bas step9_try.bas stepA_mal.bas: $(STEP4_DEPS) tests/%.bas: tests/%.in.bas ./basicpp.py $(BASICPP_OPTS) $< > $@ @@ -28,10 +28,10 @@ tests/%.prg: tests/%.bas petcat -text -w2 -o $@ $<.tmp rm $<.tmp -mal.prg: step9_try.prg +mal.prg: stepA_mal.prg cp $< $@ -SOURCES_LISP = env.in.bas core.in.bas step9_try.in.bas +SOURCES_LISP = env.in.bas core.in.bas stepA_mal.in.bas SOURCES = readline.in.bas types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP) .PHONY: stats diff --git a/basic/core.in.bas b/basic/core.in.bas index cc1629b395..d0c9405676 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -18,7 +18,7 @@ DO_FUNCTION: ON FF% GOTO DO_EQUAL_Q,DO_THROW,DO_NIL_Q,DO_TRUE_Q,DO_FALSE_Q,DO_STRING_Q,DO_SYMBOL,DO_SYMBOL_Q DO_11: - ON FF%-10 GOTO DO_PR_STR,DO_STR,DO_PRN,DO_PRINTLN,DO_READLINE,DO_READ_STRING,DO_SLURP + ON FF%-10 GOTO DO_PR_STR,DO_STR,DO_PRN,DO_PRINTLN,DO_READ_STRING,DO_READLINE,DO_SLURP DO_18: ON FF%-17 GOTO DO_LT,DO_LTE,DO_GT,DO_GTE,DO_ADD,DO_SUB,DO_MULT,DO_DIV,DO_TIME_MS DO_27: @@ -79,12 +79,16 @@ DO_FUNCTION: PRINT R$ R%=0 RETURN - DO_READLINE: - RETURN DO_READ_STRING: A$=ZS$(Z%(AA%,1)) GOSUB READ_STR RETURN + DO_READLINE: + A$=ZS$(Z%(AA%,1)):GOSUB READLINE + IF EOF=1 THEN EOF=0:R%=0:RETURN + AS$=R$:T%=4:GOSUB STRING + Z%(R%,0)=Z%(R%,0)+16 + RETURN DO_SLURP: R$="" REM OPEN 1,8,2,ZS$(Z%(AA%,1))+",SEQ,R" @@ -421,8 +425,8 @@ INIT_CORE_NS: K$="str":A%=12:GOSUB INIT_CORE_SET_FUNCTION K$="prn":A%=13:GOSUB INIT_CORE_SET_FUNCTION K$="println":A%=14:GOSUB INIT_CORE_SET_FUNCTION - K$="readline":A%=15:GOSUB INIT_CORE_SET_FUNCTION - K$="read-string":A%=16:GOSUB INIT_CORE_SET_FUNCTION + K$="read-string":A%=15:GOSUB INIT_CORE_SET_FUNCTION + K$="readline":A%=16:GOSUB INIT_CORE_SET_FUNCTION K$="slurp":A%=17:GOSUB INIT_CORE_SET_FUNCTION K$="<":A%=18:GOSUB INIT_CORE_SET_FUNCTION diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas new file mode 100755 index 0000000000..b5d53c8c17 --- /dev/null +++ b/basic/stepA_mal.in.bas @@ -0,0 +1,656 @@ +REM POKE 1, PEEK(1)AND248: REM enable all ROM areas as RAM +REM POKE 55,0: POKE 56,192: CLR: REM move BASIC end from $A000 to $C000 +GOTO MAIN + +REM $INCLUDE: 'readline.in.bas' +REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'reader.in.bas' +REM $INCLUDE: 'printer.in.bas' +REM $INCLUDE: 'env.in.bas' +REM $INCLUDE: 'core.in.bas' + +REM $INCLUDE: 'debug.in.bas' + +REM READ(A$) -> R% +MAL_READ: + GOSUB READ_STR + RETURN + +REM PAIR_Q(B%) -> R% +PAIR_Q: + R%=0 + IF (Z%(B%,0)AND15)<>6 AND (Z%(B%,0)AND15)<>7 THEN RETURN + IF (Z%(B%,1)=0) THEN RETURN + R%=1 + RETURN + +REM QUASIQUOTE(A%) -> R% +QUASIQUOTE: + B%=A%:GOSUB PAIR_Q + IF R%=1 THEN GOTO QQ_UNQUOTE + REM ['quote, ast] + AS$="quote":T%=5:GOSUB STRING + B2%=R%:B1%=A%:GOSUB LIST2 + + RETURN + + QQ_UNQUOTE: + R%=A%+1:GOSUB DEREF_R + IF (Z%(R%,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF ZS$(Z%(R%,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE + REM [ast[1]] + R%=Z%(A%,1)+1:GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + + RETURN + + QQ_SPLICE_UNQUOTE: + REM push A% on the stack + ZL%=ZL%+1:ZZ%(ZL%)=A% + REM rest of cases call quasiquote on ast[1..] + A%=Z%(A%,1):GOSUB QUASIQUOTE:T6%=R% + REM pop A% off the stack + A%=ZZ%(ZL%):ZL%=ZL%-1 + + REM set A% to ast[0] for last two cases + A%=A%+1:GOSUB DEREF_A + + B%=A%:GOSUB PAIR_Q + IF R%=0 THEN GOTO QQ_DEFAULT + B%=A%+1:GOSUB DEREF_B + IF (Z%(B%,0)AND15)<>5 THEN GOTO QQ_DEFAULT + IF ZS$(Z%(B%,1))<>"splice-unquote" THEN QQ_DEFAULT + REM ['concat, ast[0][1], quasiquote(ast[1..])] + + B%=Z%(A%,1)+1:GOSUB DEREF_B:B2%=B% + AS$="concat":T%=5:GOSUB STRING:B3%=R% + B1%=T6%:GOSUB LIST3 + REM release inner quasiquoted since outer list takes ownership + AY%=B1%:GOSUB RELEASE + RETURN + + QQ_DEFAULT: + REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] + + REM push T6% on the stack + ZL%=ZL%+1:ZZ%(ZL%)=T6% + REM A% set above to ast[0] + GOSUB QUASIQUOTE:B2%=R% + REM pop T6% off the stack + T6%=ZZ%(ZL%):ZL%=ZL%-1 + + AS$="cons":T%=5:GOSUB STRING:B3%=R% + B1%=T6%:GOSUB LIST3 + REM release inner quasiquoted since outer list takes ownership + AY%=B1%:GOSUB RELEASE + AY%=B2%:GOSUB RELEASE + RETURN + +REM MACROEXPAND(A%, E%) -> A%: +MACROEXPAND: + REM push original A% + ZL%=ZL%+1:ZZ%(ZL%)=A% + + MACROEXPAND_LOOP: + REM list? + IF (Z%(A%,0)AND15)<>6 THEN GOTO MACROEXPAND_DONE + REM non-empty? + IF Z%(A%,1)=0 THEN GOTO MACROEXPAND_DONE + B%=A%+1:GOSUB DEREF_B + REM symbol? in first position + IF (Z%(B%,0)AND15)<>5 THEN GOTO MACROEXPAND_DONE + REM defined in environment? + K%=B%:GOSUB ENV_FIND + IF R%=-1 THEN GOTO MACROEXPAND_DONE + B%=T4%:GOSUB DEREF_B + REM macro? + IF (Z%(B%,0)AND15)<>11 THEN GOTO MACROEXPAND_DONE + + REM apply + F%=B%:AR%=Z%(A%,1):GOSUB APPLY + A%=R% + + AY%=ZZ%(ZL%) + REM if previous A% was not the first A% into macroexpand (i.e. an + REM intermediate form) then free it + IF A%<>AY% THEN ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV% + + IF ER%<>-2 THEN GOTO MACROEXPAND_DONE + GOTO MACROEXPAND_LOOP + + MACROEXPAND_DONE: + ZL%=ZL%-1: REM pop original A% + RETURN + +REM EVAL_AST(A%, E%) -> R% +REM called using GOTO to avoid basic return address stack usage +REM top of stack should have return label index +EVAL_AST: + REM push A% and E% on the stack + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + + IF ER%<>-2 THEN GOTO EVAL_AST_RETURN + + GOSUB DEREF_A + + T%=Z%(A%,0)AND15 + IF T%=5 THEN GOTO EVAL_AST_SYMBOL + IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ + + REM scalar: deref to actual value and inc ref cnt + R%=A%:GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + GOTO EVAL_AST_RETURN + + EVAL_AST_SYMBOL: + K%=A%:GOSUB ENV_GET + GOTO EVAL_AST_RETURN + + EVAL_AST_SEQ: + REM allocate the first entry + SZ%=2:GOSUB ALLOC + + REM make space on the stack + ZL%=ZL%+4 + REM push type of sequence + ZZ%(ZL%-3)=T% + REM push sequence index + ZZ%(ZL%-2)=-1 + REM push future return value (new sequence) + ZZ%(ZL%-1)=R% + REM push previous new sequence entry + ZZ%(ZL%)=R% + + EVAL_AST_SEQ_LOOP: + REM set new sequence entry type (with 1 ref cnt) + Z%(R%,0)=ZZ%(ZL%-3)+16 + Z%(R%,1)=0 + REM create value ptr placeholder + Z%(R%+1,0)=14 + Z%(R%+1,1)=0 + + REM update index + ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + + REM check if we are done evaluating the source sequence + IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + + REM if hashmap, skip eval of even entries (keys) + IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + GOTO EVAL_AST_DO_EVAL + + EVAL_AST_DO_REF: + R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry + Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + GOTO EVAL_AST_ADD_VALUE + + EVAL_AST_DO_EVAL: + REM call EVAL for each entry + A%=A%+1:GOSUB EVAL + A%=A%-1 + GOSUB DEREF_R: REM deref to target of evaluated entry + + EVAL_AST_ADD_VALUE: + + REM update previous value pointer to evaluated entry + Z%(ZZ%(ZL%)+1,1)=R% + + IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + + REM allocate the next entry + SZ%=2:GOSUB ALLOC + + REM update previous sequence entry value to point to new entry + Z%(ZZ%(ZL%),1)=R% + REM update previous ptr to current entry + ZZ%(ZL%)=R% + + REM process the next sequence entry from source list + A%=Z%(A%,1) + + GOTO EVAL_AST_SEQ_LOOP + EVAL_AST_SEQ_LOOP_DONE: + REM if no error, get return value (new seq) + IF ER%=-2 THEN R%=ZZ%(ZL%-1) + REM otherwise, free the return value and return nil + IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + + REM pop previous, return, index and type + ZL%=ZL%-4 + GOTO EVAL_AST_RETURN + + EVAL_AST_RETURN: + REM pop A% and E% off the stack + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + + REM pop EVAL AST return label/address + RN%=ZZ%(ZL%):ZL%=ZL%-1 + ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 + RETURN + +REM EVAL(A%, E%)) -> R% +EVAL: + LV%=LV%+1: REM track basic return stack level + + REM push A% and E% on the stack + ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + + EVAL_TCO_RECUR: + + REM AZ%=A%:PR%=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" + + GOSUB DEREF_A + + GOSUB LIST_Q + IF R% THEN GOTO APPLY_LIST + EVAL_NOT_LIST: + REM ELSE + REM push EVAL_AST return label/address + ZL%=ZL%+1:ZZ%(ZL%)=1 + GOTO EVAL_AST + EVAL_AST_RETURN_1: + + GOTO EVAL_RETURN + + APPLY_LIST: + GOSUB MACROEXPAND + + GOSUB LIST_Q + IF R%<>1 THEN GOTO EVAL_NOT_LIST + + GOSUB EMPTY_Q + IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN + + A0%=A%+1 + R%=A0%:GOSUB DEREF_R:A0%=R% + + REM get symbol in A$ + IF (Z%(A0%,0)AND15)<>5 THEN A$="" + IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) + + IF A$="def!" THEN GOTO EVAL_DEF + IF A$="let*" THEN GOTO EVAL_LET + IF A$="quote" THEN GOTO EVAL_QUOTE + IF A$="quasiquote" THEN GOTO EVAL_QUASIQUOTE + IF A$="defmacro!" THEN GOTO EVAL_DEFMACRO + IF A$="macroexpand" THEN GOTO EVAL_MACROEXPAND + IF A$="try*" THEN GOTO EVAL_TRY + IF A$="do" THEN GOTO EVAL_DO + IF A$="if" THEN GOTO EVAL_IF + IF A$="fn*" THEN GOTO EVAL_FN + GOTO EVAL_INVOKE + + EVAL_GET_A3: + A3%=Z%(Z%(Z%(A%,1),1),1)+1 + R%=A3%:GOSUB DEREF_R:A3%=R% + EVAL_GET_A2: + A2%=Z%(Z%(A%,1),1)+1 + R%=A2%:GOSUB DEREF_R:A2%=R% + EVAL_GET_A1: + A1%=Z%(A%,1)+1 + R%=A1%:GOSUB DEREF_R:A1%=R% + RETURN + + EVAL_DEF: + REM PRINT "def!" + GOSUB EVAL_GET_A2: REM set a1% and a2% + + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + A%=A2%:GOSUB EVAL: REM eval a2 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + + IF ER%<>-2 THEN GOTO EVAL_RETURN + + REM set a1 in env to a2 + K%=A1%:V%=R%:GOSUB ENV_SET + GOTO EVAL_RETURN + + EVAL_LET: + REM PRINT "let*" + GOSUB EVAL_GET_A2: REM set a1% and a2% + + ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% + ZL%=ZL%+1:ZZ%(ZL%)=E%: REM push env for for later release + + REM create new environment with outer as current environment + EO%=E%:GOSUB ENV_NEW + E%=R% + EVAL_LET_LOOP: + IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + REM eval current A1 odd element + A%=Z%(A1%,1)+1:GOSUB EVAL + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + + REM set environment: even A1% key to odd A1% eval'd above + K%=A1%+1:V%=R%:GOSUB ENV_SET + AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership + + REM skip to the next pair of A1% elements + A1%=Z%(Z%(A1%,1),1) + GOTO EVAL_LET_LOOP + + EVAL_LET_LOOP_DONE: + E4%=ZZ%(ZL%):ZL%=ZL%-1: REM pop previous env + + REM release previous environment if not the current EVAL env + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + + A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% + A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_DO: + A%=Z%(A%,1): REM rest + + REM TODO: TCO + + REM push EVAL_AST return label/address + ZL%=ZL%+1:ZZ%(ZL%)=2 + GOTO EVAL_AST + EVAL_AST_RETURN_2: + + ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push eval'd list + A%=R%:GOSUB LAST: REM return the last element + AY%=ZZ%(ZL%):ZL%=ZL%-1: REM pop eval'd list + GOSUB RELEASE: REM release the eval'd list + GOTO EVAL_RETURN + + EVAL_QUOTE: + R%=Z%(A%,1)+1:GOSUB DEREF_R + Z%(R%,0)=Z%(R%,0)+16 + GOTO EVAL_RETURN + + EVAL_QUASIQUOTE: + R%=Z%(A%,1)+1:GOSUB DEREF_R + A%=R%:GOSUB QUASIQUOTE + REM add quasiquote result to pending release queue to free when + REM next lower EVAL level returns (LV%) + ZM%=ZM%+1:ZR%(ZM%,0)=R%:ZR%(ZM%,1)=LV% + + A%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_DEFMACRO: + REM PRINT "defmacro!" + GOSUB EVAL_GET_A2: REM set a1% and a2% + + ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + A%=A2%:GOSUB EVAL: REM eval a2 + A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + + REM change function to macro + Z%(R%,0)=Z%(R%,0)+1 + + REM set a1 in env to a2 + K%=A1%:V%=R%:GOSUB ENV_SET + GOTO EVAL_RETURN + + EVAL_MACROEXPAND: + REM PRINT "macroexpand" + R%=Z%(A%,1)+1:GOSUB DEREF_R + A%=R%:GOSUB MACROEXPAND:R%=A% + + REM since we are returning it unevaluated, inc the ref cnt + Z%(R%,0)=Z%(R%,0)+16 + GOTO EVAL_RETURN + + EVAL_TRY: + REM PRINT "try*" + GOSUB EVAL_GET_A1: REM set a1%, a2%, and a3% + + ZL%=ZL%+1:ZZ%(ZL%)=A%: REM push/save A% + A%=A1%:GOSUB EVAL: REM eval a1 + A%=ZZ%(ZL%):ZL%=ZL%-1: REM pop/restore A% + + REM if there is not error or catch block then return + IF ER%=-2 OR Z%(A%,1)=0 THEN GOTO EVAL_RETURN + + REM create environment for the catch block eval + EO%=E%:GOSUB ENV_NEW:E%=R% + + GOSUB EVAL_GET_A2: REM set a1% and a2% + A%=A2%:GOSUB EVAL_GET_A2: REM set a1% and a2% from catch block + + REM create object for ER%=-1 type raw string errors + IF ER%=-1 THEN AS$=ER$:T%=4:GOSUB STRING:ER%=R%:Z%(R%,0)=Z%(R%,0)+16 + + REM bind the catch symbol to the error object + K%=A1%:V%=ER%:GOSUB ENV_SET + AY%=R%:GOSUB RELEASE: REM release out use, env took ownership + + REM unset error for catch eval + ER%=-2:ER$="" + + A%=A2%:GOSUB EVAL + + GOTO EVAL_RETURN + + EVAL_IF: + GOSUB EVAL_GET_A1: REM set a1% + REM push A% + ZL%=ZL%+1:ZZ%(ZL%)=A% + A%=A1%:GOSUB EVAL + REM pop A% + A%=ZZ%(ZL%):ZL%=ZL%-1 + IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE + + EVAL_IF_TRUE: + AY%=R%:GOSUB RELEASE + GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL + A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + EVAL_IF_FALSE: + AY%=R%:GOSUB RELEASE + REM if no false case (A3%), return nil + IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0:GOTO EVAL_RETURN + GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL + A%=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_FN: + GOSUB EVAL_GET_A2: REM set a1% and a2% + A%=A2%:P%=A1%:GOSUB MAL_FUNCTION + GOTO EVAL_RETURN + + EVAL_INVOKE: + REM push EVAL_AST return label/address + ZL%=ZL%+1:ZZ%(ZL%)=3 + GOTO EVAL_AST + EVAL_AST_RETURN_3: + + REM if error, return f/args for release by caller + IF ER%<>-2 THEN GOTO EVAL_RETURN + + REM push f/args for release after call + ZL%=ZL%+1:ZZ%(ZL%)=R% + + F%=R%+1 + + AR%=Z%(R%,1): REM rest + R%=F%:GOSUB DEREF_R:F%=R% + + IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + + REM if error, pop and return f/args for release by caller + R%=ZZ%(ZL%):ZL%=ZL%-1 + ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN + + EVAL_DO_FUNCTION: + GOSUB DO_FUNCTION + + REM pop and release f/args + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + GOTO EVAL_RETURN + + EVAL_DO_MAL_FUNCTION: + E4%=E%: REM save the current environment for release + + REM create new environ using env stored with function + EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS + + REM release previous env if it is not the top one on the + REM stack (ZZ%(ZL%-2)) because our new env refers to it and + REM we no longer need to track it (since we are TCO recurring) + IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + + REM claim the AST before releasing the list containing it + A%=Z%(F%,1):Z%(A%,0)=Z%(A%,0)+16 + REM add AST to pending release queue to free as soon as EVAL + REM actually returns (LV%+1) + ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV%+1 + + REM pop and release f/args + AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + + REM A% set above + E%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + + EVAL_RETURN: + REM AZ%=R%: PR%=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + + REM release environment if not the top one on the stack + IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE + + LV%=LV%-1: REM track basic return stack level + + REM release everything we couldn't release earlier + GOSUB RELEASE_PEND + + REM trigger GC + TA%=FRE(0) + + REM pop A% and E% off the stack + E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + + RETURN + +REM PRINT(A%) -> R$ +MAL_PRINT: + AZ%=A%:PR%=1:GOSUB PR_STR + RETURN + +REM RE(A$) -> R% +REM Assume RE% has repl_env +REM caller must release result +RE: + R1%=0 + GOSUB MAL_READ + R1%=R% + IF ER%<>-2 THEN GOTO REP_DONE + + A%=R%:E%=RE%:GOSUB EVAL + + REP_DONE: + REM Release memory from MAL_READ + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + RETURN: REM caller must release result of EVAL + +REM REP(A$) -> R$ +REM Assume RE% has repl_env +REP: + R1%=0:R2%=0 + GOSUB MAL_READ + R1%=R% + IF ER%<>-2 THEN GOTO REP_DONE + + A%=R%:E%=RE%:GOSUB EVAL + R2%=R% + IF ER%<>-2 THEN GOTO REP_DONE + + A%=R%:GOSUB MAL_PRINT + RT$=R$ + + REP_DONE: + REM Release memory from MAL_READ and EVAL + IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE + IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + R$=RT$ + RETURN + +REM MAIN program +MAIN: + GOSUB INIT_MEMORY + + LV%=0 + + REM create repl_env + EO%=-1:GOSUB ENV_NEW:RE%=R% + + REM core.EXT: defined in Basic + E%=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + + ZT%=ZI%: REM top of memory after base repl_env + + REM core.mal: defined using the language itself + A$="(def! *host-language* "+CHR$(34)+"C64 Basic"+CHR$(34)+")" + GOSUB RE:AY%=R%:GOSUB RELEASE + + A$="(def! not (fn* (a) (if a false true)))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + A$="(def! load-file (fn* (f) (eval (read-string (str " + A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + A$="(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs)" + A$=A$+" (if (> (count xs) 1) (nth xs 1) (throw "+CHR$(34)+"odd number of" + A$=A$+" forms to cond"+CHR$(34)+")) (cons 'cond (rest (rest xs)))))))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + A$="(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs)" + A$=A$+" `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + REM load the args file + A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + REM set the argument list + A$="(def! *ARGV* (rest -*ARGS*-))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + REM get the first argument + A$="(first -*ARGS*-)" + GOSUB RE + + REM if there is an argument, then run it as a program + IF R%<>0 THEN AY%=R%:GOSUB RELEASE:GOTO RUN_PROG + REM no arguments, start REPL loop + IF R%=0 THEN GOTO REPL + + RUN_PROG: + REM run a single mal program and exit + A$="(load-file (first -*ARGS*-))" + GOSUB RE + IF ER%<>-2 THEN GOSUB PRINT_ERROR + END + + REPL: + REM print the REPL startup header + A$="(println (str "+CHR$(34)+"Mal ["+CHR$(34)+" *host-language* " + A$=A$+CHR$(34)+"]"+CHR$(34)+"))" + GOSUB RE:AY%=R%:GOSUB RELEASE + + REPL_LOOP: + A$="user> ":GOSUB READLINE: REM call input parser + IF EOF=1 THEN GOTO QUIT + + A$=R$:GOSUB REP: REM call REP + + IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + PRINT R$ + GOTO REPL_LOOP + + QUIT: + REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + GOSUB PR_MEMORY_SUMMARY + END + + PRINT_ERROR: + REM if the error is an object, then print and free it + IF ER%>=0 THEN AZ%=ER%:PR%=0:GOSUB PR_STR:ER$=R$:AY%=ER%:GOSUB RELEASE + PRINT "Error: "+ER$ + ER%=-2:ER$="" + RETURN + From 624e67cb6583c7dada2e31f58849f2b41590622f Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 11 Oct 2016 18:24:50 +0200 Subject: [PATCH 0167/2308] Implement step 5 --- pil/func.l | 1 + pil/printer.l | 1 + pil/step5_tco.l | 94 +++++++++++++++++++++++++++++++++++++++++ pil/tests/step5_tco.mal | 2 + 4 files changed, 98 insertions(+) create mode 100644 pil/step5_tco.l create mode 100644 pil/tests/step5_tco.mal diff --git a/pil/func.l b/pil/func.l index e63bae5f09..6142de46ca 100644 --- a/pil/func.l +++ b/pil/func.l @@ -1,6 +1,7 @@ (class +Func) # env ast params fn (dm T (Env Ast Params Fn) + (=: type 'func) # HACK (=: env Env) (=: ast Ast) (=: params Params) diff --git a/pil/printer.l b/pil/printer.l index 3b54b1771c..dbb67c3ea8 100644 --- a/pil/printer.l +++ b/pil/printer.l @@ -7,6 +7,7 @@ (keyword (pack ":" Value)) ((number symbol) Value) (fn "#") + (func "#") (list (pr-list Value PrintReadably "(" ")")) (vector (pr-list Value PrintReadably "[" "]")) (map (pr-list Value PrintReadably "{" "}")) diff --git a/pil/step5_tco.l b/pil/step5_tco.l new file mode 100644 index 0000000000..f845c0dcc8 --- /dev/null +++ b/pil/step5_tco.l @@ -0,0 +1,94 @@ +(de load-relative (Path) + (load (pack (car (file)) Path)) ) + +(load-relative "readline.l") +(load-relative "types.l") +(load-relative "reader.l") +(load-relative "printer.l") +(load-relative "env.l") +(load-relative "func.l") +(load-relative "core.l") + +(de READ (String) + (read-str String) ) + +(def 'repl-env (MAL-env NIL)) +(for Bind ns (set> repl-env (car Bind) (cdr Bind))) + +(de EVAL (Ast Env) + (catch 'done + (while t + (if (and (= (MAL-type Ast) 'list) (MAL-value Ast)) + (let (Ast* (MAL-value Ast) + A0* (MAL-value (car Ast*)) + A1 (cadr Ast*) + A1* (MAL-value A1) + A2 (caddr Ast*) + A3 (cadddr Ast*) ) + (cond + ((= A0* 'def!) + (throw 'done (set> Env A1* (EVAL A2 Env))) ) + ((= A0* 'let*) + (let Env* (MAL-env Env) + (for (Bindings A1* Bindings) + (let (Key (MAL-value (pop 'Bindings)) + Value (EVAL (pop 'Bindings) Env*) ) + (set> Env* Key Value) ) ) + (setq Env Env* Ast A2) ) ) # TCO + ((= A0* 'do) + (mapc '((Form) (EVAL Form Env)) (head -1 (cdr Ast*))) + (setq Ast (last Ast*)) ) # TCO + ((= A0* 'if) + (if (not (memq (MAL-type (EVAL A1 Env)) '(nil false))) + (setq Ast A2) # TCO + (if A3 + (setq Ast A3) # TCO + (throw 'done *MAL-nil) ) ) ) + ((= A0* 'fn*) + (let (Binds (mapcar MAL-value A1*) + Body A2 + Fn (MAL-fn + (curry (Env Binds Body) @ + (let Env* (MAL-env Env Binds (rest)) + (EVAL Body Env*) ) ) ) ) + (throw 'done (MAL-func Env Body Binds Fn)) ) ) + (T + (let (Ast* (MAL-value (eval-ast Ast Env)) + Fn (car Ast*) + Args (cdr Ast*) ) + (if (isa '+MALFn Fn) + (throw 'done (apply (MAL-value Fn) Args)) + (let Env* (MAL-env (get Fn 'env) (get Fn 'params) Args) + (setq Ast (get Fn 'ast) Env Env*) ) ) ) ) ) ) + (throw 'done (eval-ast Ast Env)) ) ) ) ) + +(de eval-ast (Ast Env) + (let Value (MAL-value Ast) + (case (MAL-type Ast) + (symbol (get> Env Value)) + (list (MAL-list (mapcar '((Form) (EVAL Form Env)) Value))) + (vector (MAL-vector (mapcar '((Form) (EVAL Form Env)) Value))) + (map (MAL-map (mapcar '((Form) (EVAL Form Env)) Value))) + (T Ast) ) ) ) + +(de PRINT (Ast) + (pr-str Ast T) ) + +(de rep (String Env) + (PRINT (EVAL (READ String) Env)) ) + +(rep "(def! not (fn* (a) (if a false true)))" repl-env) + +(load-history ".mal_history") + +(use Input + (until (=0 (setq Input (readline "user> "))) + (let Output (catch 'err (rep Input repl-env)) + (if (isa '+MALError Output) + (let Message (MAL-value Output) + (unless (= Message "end of token stream") + (prinl "[error] " Message) ) ) + (prinl Output) ) ) ) ) + +(prinl) +(bye) diff --git a/pil/tests/step5_tco.mal b/pil/tests/step5_tco.mal new file mode 100644 index 0000000000..901069482d --- /dev/null +++ b/pil/tests/step5_tco.mal @@ -0,0 +1,2 @@ +;; PIL: skipping non-TCO recursion +;; Reason: segfault (unrecoverable) From 0e9990bc0c73af5bdf32866d4ffa5ec0d966cb7c Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Fri, 14 Oct 2016 09:40:19 +0200 Subject: [PATCH 0168/2308] Fix naming --- pil/core.l | 2 +- pil/step2_eval.l | 4 ++-- pil/step3_env.l | 12 ++++++------ pil/step4_if_fn_do.l | 8 ++++---- pil/step5_tco.l | 8 ++++---- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pil/core.l b/pil/core.l index ac761f7fcd..645b278b4d 100644 --- a/pil/core.l +++ b/pil/core.l @@ -22,7 +22,7 @@ (de MAL-seq? (X) (memq (MAL-type X) '(list vector)) ) -(def 'ns +(def '*Ns '((+ . `(MAL-fn '((A B) (MAL-number (+ (MAL-value A) (MAL-value B)))))) (- . `(MAL-fn '((A B) (MAL-number (- (MAL-value A) (MAL-value B)))))) (* . `(MAL-fn '((A B) (MAL-number (* (MAL-value A) (MAL-value B)))))) diff --git a/pil/step2_eval.l b/pil/step2_eval.l index 9deab7fe28..c9f23a7331 100644 --- a/pil/step2_eval.l +++ b/pil/step2_eval.l @@ -9,7 +9,7 @@ (de READ (String) (read-str String) ) -(def 'repl-env +(def '*ReplEnv '((+ . ((A B) (MAL-number (+ (MAL-value A) (MAL-value B))))) (- . ((A B) (MAL-number (- (MAL-value A) (MAL-value B))))) (* . ((A B) (MAL-number (* (MAL-value A) (MAL-value B))))) @@ -48,7 +48,7 @@ (let Input (readline "user> ") (if (=0 Input) (setq Eof T) - (let Output (catch 'err (rep Input repl-env)) + (let Output (catch 'err (rep Input *ReplEnv)) (if (isa '+MALError Output) (let Message (MAL-value Output) (unless (= Message "end of token stream") diff --git a/pil/step3_env.l b/pil/step3_env.l index d810df0704..97be3a23c3 100644 --- a/pil/step3_env.l +++ b/pil/step3_env.l @@ -10,11 +10,11 @@ (de READ (String) (read-str String) ) -(def 'repl-env (MAL-env NIL)) -(set> repl-env '+ '((A B) (MAL-number (+ (MAL-value A) (MAL-value B))))) -(set> repl-env '- '((A B) (MAL-number (- (MAL-value A) (MAL-value B))))) -(set> repl-env '* '((A B) (MAL-number (* (MAL-value A) (MAL-value B))))) -(set> repl-env '/ '((A B) (MAL-number (/ (MAL-value A) (MAL-value B))))) +(def '*ReplEnv (MAL-env NIL)) +(set> *ReplEnv '+ '((A B) (MAL-number (+ (MAL-value A) (MAL-value B))))) +(set> *ReplEnv '- '((A B) (MAL-number (- (MAL-value A) (MAL-value B))))) +(set> *ReplEnv '* '((A B) (MAL-number (* (MAL-value A) (MAL-value B))))) +(set> *ReplEnv '/ '((A B) (MAL-number (/ (MAL-value A) (MAL-value B))))) (de EVAL (Ast Env) (if (= (MAL-type Ast) 'list) @@ -60,7 +60,7 @@ (let Input (readline "user> ") (if (=0 Input) (setq Eof T) - (let Output (catch 'err (rep Input repl-env)) + (let Output (catch 'err (rep Input *ReplEnv)) (if (isa '+MALError Output) (let Message (MAL-value Output) (unless (= Message "end of token stream") diff --git a/pil/step4_if_fn_do.l b/pil/step4_if_fn_do.l index f5ee441e96..7adcd9ec08 100644 --- a/pil/step4_if_fn_do.l +++ b/pil/step4_if_fn_do.l @@ -12,8 +12,8 @@ (de READ (String) (read-str String) ) -(def 'repl-env (MAL-env NIL)) -(for Bind ns (set> repl-env (car Bind) (cdr Bind))) +(def '*ReplEnv (MAL-env NIL)) +(for Bind *Ns (set> *ReplEnv (car Bind) (cdr Bind))) (de EVAL (Ast Env) (if (= (MAL-type Ast) 'list) @@ -73,13 +73,13 @@ (de rep (String Env) (PRINT (EVAL (READ String) Env)) ) -(rep "(def! not (fn* (a) (if a false true)))" repl-env) +(rep "(def! not (fn* (a) (if a false true)))" *ReplEnv) (load-history ".mal_history") (use Input (until (=0 (setq Input (readline "user> "))) - (let Output (catch 'err (rep Input repl-env)) + (let Output (catch 'err (rep Input *ReplEnv)) (if (isa '+MALError Output) (let Message (MAL-value Output) (unless (= Message "end of token stream") diff --git a/pil/step5_tco.l b/pil/step5_tco.l index f845c0dcc8..8bcdb36ba3 100644 --- a/pil/step5_tco.l +++ b/pil/step5_tco.l @@ -12,8 +12,8 @@ (de READ (String) (read-str String) ) -(def 'repl-env (MAL-env NIL)) -(for Bind ns (set> repl-env (car Bind) (cdr Bind))) +(def '*ReplEnv (MAL-env NIL)) +(for Bind *Ns (set> *ReplEnv (car Bind) (cdr Bind))) (de EVAL (Ast Env) (catch 'done @@ -77,13 +77,13 @@ (de rep (String Env) (PRINT (EVAL (READ String) Env)) ) -(rep "(def! not (fn* (a) (if a false true)))" repl-env) +(rep "(def! not (fn* (a) (if a false true)))" *ReplEnv) (load-history ".mal_history") (use Input (until (=0 (setq Input (readline "user> "))) - (let Output (catch 'err (rep Input repl-env)) + (let Output (catch 'err (rep Input *ReplEnv)) (if (isa '+MALError Output) (let Message (MAL-value Output) (unless (= Message "end of token stream") From 872ae9c4a67e2321b8b2ce114d52292ecc98c561 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Fri, 14 Oct 2016 10:16:44 +0200 Subject: [PATCH 0169/2308] Fix rep --- pil/step2_eval.l | 6 +++--- pil/step3_env.l | 6 +++--- pil/step4_if_fn_do.l | 8 ++++---- pil/step5_tco.l | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pil/step2_eval.l b/pil/step2_eval.l index c9f23a7331..cb7d9f2834 100644 --- a/pil/step2_eval.l +++ b/pil/step2_eval.l @@ -38,8 +38,8 @@ (de PRINT (Ast) (pr-str Ast T) ) -(de rep (String Env) - (PRINT (EVAL (READ String) Env)) ) +(de rep (String) + (PRINT (EVAL (READ String) *ReplEnv)) ) (load-history ".mal_history") @@ -48,7 +48,7 @@ (let Input (readline "user> ") (if (=0 Input) (setq Eof T) - (let Output (catch 'err (rep Input *ReplEnv)) + (let Output (catch 'err (rep Input)) (if (isa '+MALError Output) (let Message (MAL-value Output) (unless (= Message "end of token stream") diff --git a/pil/step3_env.l b/pil/step3_env.l index 97be3a23c3..f1d4e5fa4b 100644 --- a/pil/step3_env.l +++ b/pil/step3_env.l @@ -50,8 +50,8 @@ (de PRINT (Ast) (pr-str Ast T) ) -(de rep (String Env) - (PRINT (EVAL (READ String) Env)) ) +(de rep (String) + (PRINT (EVAL (READ String) *ReplEnv)) ) (load-history ".mal_history") @@ -60,7 +60,7 @@ (let Input (readline "user> ") (if (=0 Input) (setq Eof T) - (let Output (catch 'err (rep Input *ReplEnv)) + (let Output (catch 'err (rep Input)) (if (isa '+MALError Output) (let Message (MAL-value Output) (unless (= Message "end of token stream") diff --git a/pil/step4_if_fn_do.l b/pil/step4_if_fn_do.l index 7adcd9ec08..7c80de7378 100644 --- a/pil/step4_if_fn_do.l +++ b/pil/step4_if_fn_do.l @@ -70,16 +70,16 @@ (de PRINT (Ast) (pr-str Ast T) ) -(de rep (String Env) - (PRINT (EVAL (READ String) Env)) ) +(de rep (String) + (PRINT (EVAL (READ String) *ReplEnv)) ) -(rep "(def! not (fn* (a) (if a false true)))" *ReplEnv) +(rep "(def! not (fn* (a) (if a false true)))") (load-history ".mal_history") (use Input (until (=0 (setq Input (readline "user> "))) - (let Output (catch 'err (rep Input *ReplEnv)) + (let Output (catch 'err (rep Input)) (if (isa '+MALError Output) (let Message (MAL-value Output) (unless (= Message "end of token stream") diff --git a/pil/step5_tco.l b/pil/step5_tco.l index 8bcdb36ba3..caa174de31 100644 --- a/pil/step5_tco.l +++ b/pil/step5_tco.l @@ -74,16 +74,16 @@ (de PRINT (Ast) (pr-str Ast T) ) -(de rep (String Env) - (PRINT (EVAL (READ String) Env)) ) +(de rep (String) + (PRINT (EVAL (READ String) *ReplEnv)) ) -(rep "(def! not (fn* (a) (if a false true)))" *ReplEnv) +(rep "(def! not (fn* (a) (if a false true)))") (load-history ".mal_history") (use Input (until (=0 (setq Input (readline "user> "))) - (let Output (catch 'err (rep Input *ReplEnv)) + (let Output (catch 'err (rep Input)) (if (isa '+MALError Output) (let Message (MAL-value Output) (unless (= Message "end of token stream") From 30a55a912d21cf7c6668102faae8257be73499de Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Fri, 14 Oct 2016 10:39:49 +0200 Subject: [PATCH 0170/2308] Implement step 6 --- pil/core.l | 16 +++++++- pil/printer.l | 1 + pil/reader.l | 2 +- pil/run | 2 +- pil/step6_file.l | 100 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 pil/step6_file.l diff --git a/pil/core.l b/pil/core.l index 645b278b4d..95f5719a68 100644 --- a/pil/core.l +++ b/pil/core.l @@ -22,6 +22,11 @@ (de MAL-seq? (X) (memq (MAL-type X) '(list vector)) ) +(de MAL-swap! @ + (let (X (next) Fn (next) Args (rest) + F (MAL-value (if (isa '+Func Fn) (get Fn 'fn) Fn)) ) + (put X 'value (apply F Args (MAL-value X))) ) ) + (def '*Ns '((+ . `(MAL-fn '((A B) (MAL-number (+ (MAL-value A) (MAL-value B)))))) (- . `(MAL-fn '((A B) (MAL-number (- (MAL-value A) (MAL-value B)))))) @@ -43,4 +48,13 @@ (pr-str . `(MAL-fn '(@ (MAL-string (glue " " (mapcar '((X) (pr-str X T)) (rest))))))) (str . `(MAL-fn '(@ (MAL-string (pack (mapcar pr-str (rest))))))) (prn . `(MAL-fn '(@ (prinl (glue " " (mapcar '((X) (pr-str X T)) (rest)))) *MAL-nil))) - (println . `(MAL-fn '(@ (prinl (glue " " (mapcar pr-str (rest)))) *MAL-nil))) ) ) + (println . `(MAL-fn '(@ (prinl (glue " " (mapcar pr-str (rest)))) *MAL-nil))) + + (read-string . `(MAL-fn '((X) (read-str (MAL-value X))))) + (slurp . `(MAL-fn '((X) (MAL-string (in (MAL-value X) (till NIL T)))))) + + (atom . `(MAL-fn '((X) (MAL-atom X)))) + (atom? . `(MAL-fn '((X) (if (= (MAL-type X) 'atom) *MAL-true *MAL-false)))) + (deref . `(MAL-fn '((X) (MAL-value X)))) + (reset! . `(MAL-fn '((X Value) (put X 'value Value)))) + (swap! . `(MAL-fn MAL-swap!) ) ) ) diff --git a/pil/printer.l b/pil/printer.l index dbb67c3ea8..c964a94c4d 100644 --- a/pil/printer.l +++ b/pil/printer.l @@ -11,6 +11,7 @@ (list (pr-list Value PrintReadably "(" ")")) (vector (pr-list Value PrintReadably "[" "]")) (map (pr-list Value PrintReadably "{" "}")) + (atom (pack "(atom " (pr-str Value PrintReadably) ")")) (T (pretty Value) (throw 'err (MAL-error "[pr-str] unimplemented type"))) ) ) ) (de repr (X) diff --git a/pil/reader.l b/pil/reader.l index dc0cde8fc2..b076870fb7 100644 --- a/pil/reader.l +++ b/pil/reader.l @@ -21,7 +21,7 @@ (for (Chars (chop String) Chars) (let Char (pop 'Chars) (cond - ((member Char '(" " ",")) + ((member Char '(" " "," "\n")) # do nothing, whitespace ) ((and (= Char "~") (= (car Chars) "@")) diff --git a/pil/run b/pil/run index 7412791dab..e759e20f21 100755 --- a/pil/run +++ b/pil/run @@ -1,2 +1,2 @@ #!/bin/bash -exec pil $(dirname $0)/${STEP:-stepA_mal}.l "${@}" +exec pil $(dirname $0)/${STEP:-stepA_mal}.l - "${@}" diff --git a/pil/step6_file.l b/pil/step6_file.l new file mode 100644 index 0000000000..c566d5aba6 --- /dev/null +++ b/pil/step6_file.l @@ -0,0 +1,100 @@ +(de load-relative (Path) + (load (pack (car (file)) Path)) ) + +(load-relative "readline.l") +(load-relative "types.l") +(load-relative "reader.l") +(load-relative "printer.l") +(load-relative "env.l") +(load-relative "func.l") +(load-relative "core.l") + +(de READ (String) + (read-str String) ) + +(def '*ReplEnv (MAL-env NIL)) +(for Bind *Ns (set> *ReplEnv (car Bind) (cdr Bind))) + +(de EVAL (Ast Env) + (catch 'done + (while t + (if (and (= (MAL-type Ast) 'list) (MAL-value Ast)) + (let (Ast* (MAL-value Ast) + A0* (MAL-value (car Ast*)) + A1 (cadr Ast*) + A1* (MAL-value A1) + A2 (caddr Ast*) + A3 (cadddr Ast*) ) + (cond + ((= A0* 'def!) + (throw 'done (set> Env A1* (EVAL A2 Env))) ) + ((= A0* 'let*) + (let Env* (MAL-env Env) + (for (Bindings A1* Bindings) + (let (Key (MAL-value (pop 'Bindings)) + Value (EVAL (pop 'Bindings) Env*) ) + (set> Env* Key Value) ) ) + (setq Env Env* Ast A2) ) ) # TCO + ((= A0* 'do) + (mapc '((Form) (EVAL Form Env)) (head -1 (cdr Ast*))) + (setq Ast (last Ast*)) ) # TCO + ((= A0* 'if) + (if (not (memq (MAL-type (EVAL A1 Env)) '(nil false))) + (setq Ast A2) # TCO + (if A3 + (setq Ast A3) # TCO + (throw 'done *MAL-nil) ) ) ) + ((= A0* 'fn*) + (let (Binds (mapcar MAL-value A1*) + Body A2 + Fn (MAL-fn + (curry (Env Binds Body) @ + (let Env* (MAL-env Env Binds (rest)) + (EVAL Body Env*) ) ) ) ) + (throw 'done (MAL-func Env Body Binds Fn)) ) ) + (T + (let (Ast* (MAL-value (eval-ast Ast Env)) + Fn (car Ast*) + Args (cdr Ast*) ) + (if (isa '+MALFn Fn) + (throw 'done (apply (MAL-value Fn) Args)) + (let Env* (MAL-env (get Fn 'env) (get Fn 'params) Args) + (setq Ast (get Fn 'ast) Env Env*) ) ) ) ) ) ) + (throw 'done (eval-ast Ast Env)) ) ) ) ) + +(de eval-ast (Ast Env) + (let Value (MAL-value Ast) + (case (MAL-type Ast) + (symbol (get> Env Value)) + (list (MAL-list (mapcar '((Form) (EVAL Form Env)) Value))) + (vector (MAL-vector (mapcar '((Form) (EVAL Form Env)) Value))) + (map (MAL-map (mapcar '((Form) (EVAL Form Env)) Value))) + (T Ast) ) ) ) + +(set> *ReplEnv 'eval (MAL-fn (curry (*ReplEnv) (Form) (EVAL Form *ReplEnv)))) +(set> *ReplEnv '*ARGV* (MAL-list (mapcar MAL-string (argv)))) + +(de PRINT (Ast) + (pr-str Ast T) ) + +(de rep (String) + (PRINT (EVAL (READ String) *ReplEnv)) ) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + +(load-history ".mal_history") + +(if (argv) + (rep (pack "(load-file \"" (opt) "\")")) + (use Input + (until (=0 (setq Input (readline "user> "))) + (let Output (catch 'err (rep Input)) + (if (isa '+MALError Output) + (let Message (MAL-value Output) + (unless (= Message "end of token stream") + (prinl "[error] " Message) ) ) + (prinl Output) ) ) ) ) ) + +(prinl) +(bye) From cc9dbd92e3bce0d51905df1f810adfc32db7769e Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 14 Oct 2016 22:42:56 -0500 Subject: [PATCH 0171/2308] Basic: variable renaming. Save 2 kbytes. Also, add variables.txt file with start of documenting meanings of variables. List of renamings/savings: ZZ% -> S% : 131 bytes ZL% -> X : 550 bytes A% -> A : 192 bytes E% -> E : 32 bytes R% -> R : 381 bytes AR% -> AR : 30 bytes AY% -> AY : 71 bytes AZ% -> AZ : 33 bytes B% -> B : 47 bytes AA% -> AA : 64 bytes AB% -> AB : 25 bytes F% -> F : 21 bytes FF% -> FF : 14 bytes ER% -> ER : 41 bytes PR% -> PR : 7 bytes T% -> T : 46 bytes R0-9% -> R0-9 : 31 bytes T0-9% -> T0-9 : 42 bytes S1-4% -> S1-4 : 25 bytes U0-9% -> U0-9 : 44 bytes ZK% -> ZK : 10 bytes ZI% -> ZI : 10 bytes RC% -> RC : 16 bytes K%/V% -> K/V : 21 bytes SD% -> SD : 16 bytes ZS$ -> S$ : 40 bytes HM% -> H : 10 bytes SZ% -> SZ : 39 bytes LV% -> LV : 9 bytes EO% -> O : 18 bytes C% -> C : 4 bytes P% -> P : 4 bytes --- basic/core.in.bas | 492 +++++++++++++++++----------------- basic/debug.in.bas | 46 ++-- basic/env.in.bas | 82 +++--- basic/printer.in.bas | 94 +++---- basic/reader.in.bas | 136 +++++----- basic/step0_repl.in.bas | 6 +- basic/step1_read_print.in.bas | 26 +- basic/step2_eval.in.bas | 208 +++++++------- basic/step3_env.in.bas | 262 +++++++++--------- basic/step4_if_fn_do.in.bas | 282 +++++++++---------- basic/step5_tco.in.bas | 288 ++++++++++---------- basic/step6_file.in.bas | 300 ++++++++++----------- basic/step7_quote.in.bas | 400 +++++++++++++-------------- basic/step8_macros.in.bas | 458 +++++++++++++++---------------- basic/step9_try.in.bas | 484 ++++++++++++++++----------------- basic/stepA_mal.in.bas | 488 ++++++++++++++++----------------- basic/types.in.bas | 486 ++++++++++++++++----------------- basic/variables.txt | 48 ++++ 18 files changed, 2317 insertions(+), 2269 deletions(-) create mode 100644 basic/variables.txt diff --git a/basic/core.in.bas b/basic/core.in.bas index d0c9405676..fcbd6d32a1 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -1,393 +1,393 @@ -REM DO_FUNCTION(F%, AR%) +REM DO_FUNCTION(F, AR) DO_FUNCTION: REM Get the function number - FF%=Z%(F%,1) + FF=Z%(F,1) REM Get argument values - R%=AR%+1:GOSUB DEREF_R:AA%=R% - R%=Z%(AR%,1)+1:GOSUB DEREF_R:AB%=R% + R=AR+1:GOSUB DEREF_R:AA=R + R=Z%(AR,1)+1:GOSUB DEREF_R:AB=R REM Switch on the function number - IF FF%>=61 THEN ER%=-1:ER$="unknown function"+STR$(FF%):RETURN - IF FF%>=53 THEN DO_53 - IF FF%>=39 THEN DO_39 - IF FF%>=27 THEN DO_27 - IF FF%>=18 THEN DO_18 - IF FF%>=11 THEN DO_11 - - ON FF% GOTO DO_EQUAL_Q,DO_THROW,DO_NIL_Q,DO_TRUE_Q,DO_FALSE_Q,DO_STRING_Q,DO_SYMBOL,DO_SYMBOL_Q + IF FF>=61 THEN ER=-1:ER$="unknown function"+STR$(FF):RETURN + IF FF>=53 THEN DO_53 + IF FF>=39 THEN DO_39 + IF FF>=27 THEN DO_27 + IF FF>=18 THEN DO_18 + IF FF>=11 THEN DO_11 + + ON FF GOTO DO_EQUAL_Q,DO_THROW,DO_NIL_Q,DO_TRUE_Q,DO_FALSE_Q,DO_STRING_Q,DO_SYMBOL,DO_SYMBOL_Q DO_11: - ON FF%-10 GOTO DO_PR_STR,DO_STR,DO_PRN,DO_PRINTLN,DO_READ_STRING,DO_READLINE,DO_SLURP + ON FF-10 GOTO DO_PR_STR,DO_STR,DO_PRN,DO_PRINTLN,DO_READ_STRING,DO_READLINE,DO_SLURP DO_18: - ON FF%-17 GOTO DO_LT,DO_LTE,DO_GT,DO_GTE,DO_ADD,DO_SUB,DO_MULT,DO_DIV,DO_TIME_MS + ON FF-17 GOTO DO_LT,DO_LTE,DO_GT,DO_GTE,DO_ADD,DO_SUB,DO_MULT,DO_DIV,DO_TIME_MS DO_27: - ON FF%-26 GOTO DO_LIST,DO_LIST_Q,DO_VECTOR,DO_VECTOR_Q,DO_HASH_MAP,DO_MAP_Q + ON FF-26 GOTO DO_LIST,DO_LIST_Q,DO_VECTOR,DO_VECTOR_Q,DO_HASH_MAP,DO_MAP_Q DO_39: - ON FF%-38 GOTO DO_SEQUENTIAL_Q,DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_APPLY,DO_MAP + ON FF-38 GOTO DO_SEQUENTIAL_Q,DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_APPLY,DO_MAP DO_53: - ON FF%-52 GOTO DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_SWAP_BANG,DO_PR_MEMORY,DO_PR_MEMORY_SUMMARY,DO_EVAL + ON FF-52 GOTO DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_SWAP_BANG,DO_PR_MEMORY,DO_PR_MEMORY_SUMMARY,DO_EVAL DO_EQUAL_Q: - A%=AA%:B%=AB%:GOSUB EQUAL_Q - R%=R%+1 + A=AA:B=AB:GOSUB EQUAL_Q + R=R+1 RETURN DO_THROW: - ER%=AA% - Z%(ER%,0)=Z%(ER%,0)+16 - R%=0 + ER=AA + Z%(ER,0)=Z%(ER,0)+16 + R=0 RETURN DO_NIL_Q: - R%=1 - IF AA%=0 THEN R%=2 + R=1 + IF AA=0 THEN R=2 RETURN DO_TRUE_Q: - R%=1 - IF AA%=2 THEN R%=2 + R=1 + IF AA=2 THEN R=2 RETURN DO_FALSE_Q: - R%=1 - IF AA%=1 THEN R%=2 + R=1 + IF AA=1 THEN R=2 RETURN DO_STRING_Q: - R%=1 - IF (Z%(AA%,0)AND15)=4 THEN R%=2 + R=1 + IF (Z%(AA,0)AND15)=4 THEN R=2 RETURN DO_SYMBOL: - R%=0 + R=0 RETURN DO_SYMBOL_Q: - R%=1 - IF (Z%(AA%,0)AND15)=5 THEN R%=2 + R=1 + IF (Z%(AA,0)AND15)=5 THEN R=2 RETURN DO_PR_STR: - AZ%=AR%:PR%=1:SE$=" ":GOSUB PR_STR_SEQ - AS$=R$:T%=4+16:GOSUB STRING + AZ=AR:PR=1:SE$=" ":GOSUB PR_STR_SEQ + AS$=R$:T=4+16:GOSUB STRING RETURN DO_STR: - AZ%=AR%:PR%=0:SE$="":GOSUB PR_STR_SEQ - AS$=R$:T%=4+16:GOSUB STRING + AZ=AR:PR=0:SE$="":GOSUB PR_STR_SEQ + AS$=R$:T=4+16:GOSUB STRING RETURN DO_PRN: - AZ%=AR%:PR%=1:SE$=" ":GOSUB PR_STR_SEQ + AZ=AR:PR=1:SE$=" ":GOSUB PR_STR_SEQ PRINT R$ - R%=0 + R=0 RETURN DO_PRINTLN: - AZ%=AR%:PR%=0:SE$=" ":GOSUB PR_STR_SEQ + AZ=AR:PR=0:SE$=" ":GOSUB PR_STR_SEQ PRINT R$ - R%=0 + R=0 RETURN DO_READ_STRING: - A$=ZS$(Z%(AA%,1)) + A$=S$(Z%(AA,1)) GOSUB READ_STR RETURN DO_READLINE: - A$=ZS$(Z%(AA%,1)):GOSUB READLINE - IF EOF=1 THEN EOF=0:R%=0:RETURN - AS$=R$:T%=4:GOSUB STRING - Z%(R%,0)=Z%(R%,0)+16 + A$=S$(Z%(AA,1)):GOSUB READLINE + IF EOF=1 THEN EOF=0:R=0:RETURN + AS$=R$:T=4:GOSUB STRING + Z%(R,0)=Z%(R,0)+16 RETURN DO_SLURP: R$="" - REM OPEN 1,8,2,ZS$(Z%(AA%,1))+",SEQ,R" - REM OPEN 1,8,2,ZS$(Z%(AA%,1)) - OPEN 1,8,0,ZS$(Z%(AA%,1)) + REM OPEN 1,8,2,S$(Z%(AA,1))+",SEQ,R" + REM OPEN 1,8,2,S$(Z%(AA,1)) + OPEN 1,8,0,S$(Z%(AA,1)) DO_SLURP_LOOP: A$="" GET#1,A$ IF ASC(A$)=10 THEN R$=R$+CHR$(13) IF (ASC(A$)<>10) AND (A$<>"") THEN R$=R$+A$ IF (ST AND 64) THEN GOTO DO_SLURP_DONE - IF (ST AND 255) THEN ER%=-1:ER$="File read error "+STR$(ST):RETURN + IF (ST AND 255) THEN ER=-1:ER$="File read error "+STR$(ST):RETURN GOTO DO_SLURP_LOOP DO_SLURP_DONE: CLOSE 1 - AS$=R$:T%=4+16:GOSUB STRING + AS$=R$:T=4+16:GOSUB STRING RETURN DO_LT: - R%=1 - IF Z%(AA%,1)Z%(AB%,1) THEN R%=2 + R=1 + IF Z%(AA,1)>Z%(AB,1) THEN R=2 RETURN DO_GTE: - R%=1 - IF Z%(AA%,1)>=Z%(AB%,1) THEN R%=2 + R=1 + IF Z%(AA,1)>=Z%(AB,1) THEN R=2 RETURN DO_ADD: - SZ%=1:GOSUB ALLOC - Z%(R%,0)=2+16 - Z%(R%,1)=Z%(AA%,1)+Z%(AB%,1) + SZ=1:GOSUB ALLOC + Z%(R,0)=2+16 + Z%(R,1)=Z%(AA,1)+Z%(AB,1) RETURN DO_SUB: - SZ%=1:GOSUB ALLOC - Z%(R%,0)=2+16 - Z%(R%,1)=Z%(AA%,1)-Z%(AB%,1) + SZ=1:GOSUB ALLOC + Z%(R,0)=2+16 + Z%(R,1)=Z%(AA,1)-Z%(AB,1) RETURN DO_MULT: - SZ%=1:GOSUB ALLOC - Z%(R%,0)=2+16 - Z%(R%,1)=Z%(AA%,1)*Z%(AB%,1) + SZ=1:GOSUB ALLOC + Z%(R,0)=2+16 + Z%(R,1)=Z%(AA,1)*Z%(AB,1) RETURN DO_DIV: - SZ%=1:GOSUB ALLOC - Z%(R%,0)=2+16 - Z%(R%,1)=Z%(AA%,1)/Z%(AB%,1) + SZ=1:GOSUB ALLOC + Z%(R,0)=2+16 + Z%(R,1)=Z%(AA,1)/Z%(AB,1) RETURN DO_TIME_MS: - R%=0 + R=0 RETURN DO_LIST: - R%=AR% - Z%(R%,0)=Z%(R%,0)+16 + R=AR + Z%(R,0)=Z%(R,0)+16 RETURN DO_LIST_Q: - A%=AA%:GOSUB LIST_Q - R%=R%+1: REM map to mal false/true + A=AA:GOSUB LIST_Q + R=R+1: REM map to mal false/true RETURN DO_VECTOR: - R%=0 + R=0 RETURN DO_VECTOR_Q: - R%=1 - IF (Z%(AA%,0)AND15)=7 THEN R%=2 + R=1 + IF (Z%(AA,0)AND15)=7 THEN R=2 RETURN DO_HASH_MAP: - R%=0 + R=0 RETURN DO_MAP_Q: - R%=1 - IF (Z%(AA%,0)AND15)=8 THEN R%=2 + R=1 + IF (Z%(AA,0)AND15)=8 THEN R=2 RETURN DO_SEQUENTIAL_Q: - R%=1 - IF (Z%(AA%,0)AND15)=6 OR (Z%(AA%,0)AND15)=7 THEN R%=2 + R=1 + IF (Z%(AA,0)AND15)=6 OR (Z%(AA,0)AND15)=7 THEN R=2 RETURN DO_CONS: - A%=AA%:B%=AB%:GOSUB CONS + A=AA:B=AB:GOSUB CONS RETURN DO_CONCAT: REM if empty arguments, return empty list - IF Z%(AR%,1)=0 THEN R%=3:Z%(R%,0)=Z%(R%,0)+16:RETURN + IF Z%(AR,1)=0 THEN R=3:Z%(R,0)=Z%(R,0)+16:RETURN REM single argument - IF Z%(Z%(AR%,1),1)<>0 THEN GOTO DO_CONCAT_MULT + IF Z%(Z%(AR,1),1)<>0 THEN GOTO DO_CONCAT_MULT REM if single argument and it's a list, return it - IF (Z%(AA%,0)AND15)=6 THEN R%=AA%:Z%(R%,0)=Z%(R%,0)+16:RETURN + IF (Z%(AA,0)AND15)=6 THEN R=AA:Z%(R,0)=Z%(R,0)+16:RETURN REM otherwise, copy first element to turn it into a list - B%=AA%+1:GOSUB DEREF_B: REM value to copy - SZ%=2:GOSUB ALLOC - Z%(R%,0)=6+16:Z%(R%,1)=Z%(AA%,1) - Z%(R%+1,0)=14:Z%(R%+1,1)=B% + B=AA+1:GOSUB DEREF_B: REM value to copy + SZ=2:GOSUB ALLOC + Z%(R,0)=6+16:Z%(R,1)=Z%(AA,1) + Z%(R+1,0)=14:Z%(R+1,1)=B REM inc ref count of trailing list part and of copied value - Z%(Z%(AA%,1),0)=Z%(Z%(AA%,1),0)+16 - Z%(B%,0)=Z%(B%,0)+16 + Z%(Z%(AA,1),0)=Z%(Z%(AA,1),0)+16 + Z%(B,0)=Z%(B,0)+16 RETURN REM multiple arguments DO_CONCAT_MULT: - CZ%=ZL%: REM save current stack position + CZ%=X: REM save current stack position REM push arguments onto the stack DO_CONCAT_STACK: - R%=AR%+1:GOSUB DEREF_R - ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push sequence - AR%=Z%(AR%,1) - IF Z%(AR%,1)<>0 THEN GOTO DO_CONCAT_STACK + R=AR+1:GOSUB DEREF_R + X=X+1:S%(X)=R: REM push sequence + AR=Z%(AR,1) + IF Z%(AR,1)<>0 THEN GOTO DO_CONCAT_STACK REM pop last argument as our seq to prepend to - AB%=ZZ%(ZL%):ZL%=ZL%-1 + AB=S%(X):X=X-1 REM last arg/seq is not copied so we need to inc ref to it - Z%(AB%,0)=Z%(AB%,0)+16 + Z%(AB,0)=Z%(AB,0)+16 DO_CONCAT_LOOP: - IF ZL%=CZ% THEN R%=AB%:RETURN - AA%=ZZ%(ZL%):ZL%=ZL%-1: REM pop off next seq to prepend - IF Z%(AA%,1)=0 THEN GOTO DO_CONCAT_LOOP: REM skip empty seqs - A%=AA%:B%=0:C%=-1:GOSUB SLICE + IF X=CZ% THEN R=AB:RETURN + AA=S%(X):X=X-1: REM pop off next seq to prepend + IF Z%(AA,1)=0 THEN GOTO DO_CONCAT_LOOP: REM skip empty seqs + A=AA:B=0:C=-1:GOSUB SLICE REM release the terminator of new list (we skip over it) - AY%=Z%(R6%,1):GOSUB RELEASE + AY=Z%(R6,1):GOSUB RELEASE REM attach new list element before terminator (last actual REM element to the next sequence - Z%(R6%,1)=AB% + Z%(R6,1)=AB - AB%=R% + AB=R GOTO DO_CONCAT_LOOP DO_NTH: - B%=Z%(AB%,1) - A%=AA%:GOSUB COUNT - IF R%<=B% THEN R%=0:ER%=-1:ER$="nth: index out of range":RETURN + B=Z%(AB,1) + A=AA:GOSUB COUNT + IF R<=B THEN R=0:ER=-1:ER$="nth: index out of range":RETURN DO_NTH_LOOP: - IF B%=0 THEN GOTO DO_NTH_DONE - B%=B%-1 - AA%=Z%(AA%,1) + IF B=0 THEN GOTO DO_NTH_DONE + B=B-1 + AA=Z%(AA,1) GOTO DO_NTH_LOOP DO_NTH_DONE: - R%=Z%(AA%+1,1) - Z%(R%,0)=Z%(R%,0)+16 + R=Z%(AA+1,1) + Z%(R,0)=Z%(R,0)+16 RETURN DO_FIRST: - IF Z%(AA%,1)=0 THEN R%=0 - IF Z%(AA%,1)<>0 THEN R%=AA%+1:GOSUB DEREF_R - IF R%<>0 THEN Z%(R%,0)=Z%(R%,0)+16 + IF Z%(AA,1)=0 THEN R=0 + IF Z%(AA,1)<>0 THEN R=AA+1:GOSUB DEREF_R + IF R<>0 THEN Z%(R,0)=Z%(R,0)+16 RETURN DO_REST: - IF Z%(AA%,1)=0 THEN R%=AA% - IF Z%(AA%,1)<>0 THEN R%=Z%(AA%,1) - Z%(R%,0)=Z%(R%,0)+16 + IF Z%(AA,1)=0 THEN R=AA + IF Z%(AA,1)<>0 THEN R=Z%(AA,1) + Z%(R,0)=Z%(R,0)+16 RETURN DO_EMPTY_Q: - R%=1 - IF Z%(AA%,1)=0 THEN R%=2 + R=1 + IF Z%(AA,1)=0 THEN R=2 RETURN DO_COUNT: - A%=AA%:GOSUB COUNT:R4%=R% - SZ%=1:GOSUB ALLOC - Z%(R%,0)=2+16 - Z%(R%,1)=R4% + A=AA:GOSUB COUNT:R4=R + SZ=1:GOSUB ALLOC + Z%(R,0)=2+16 + Z%(R,1)=R4 RETURN DO_APPLY: - F%=AA% - AR%=Z%(AR%,1) - A%=AR%:GOSUB COUNT:R4%=R% + F=AA + AR=Z%(AR,1) + A=AR:GOSUB COUNT:R4=R REM no intermediate args, just call APPLY directly - IF R4%<=1 THEN AR%=Z%(AR%+1,1):GOSUB APPLY:RETURN + IF R4<=1 THEN AR=Z%(AR+1,1):GOSUB APPLY:RETURN REM prepend intermediate args to final args element - A%=AR%:B%=0:C%=R4%-1:GOSUB SLICE + A=AR:B=0:C=R4-1:GOSUB SLICE REM release the terminator of new list (we skip over it) - AY%=Z%(R6%,1):GOSUB RELEASE + AY=Z%(R6,1):GOSUB RELEASE REM attach end of slice to final args element - Z%(R6%,1)=Z%(A%+1,1) - Z%(Z%(A%+1,1),0)=Z%(Z%(A%+1,1),0)+16 + Z%(R6,1)=Z%(A+1,1) + Z%(Z%(A+1,1),0)=Z%(Z%(A+1,1),0)+16 - ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push/save new args for release - AR%=R%:GOSUB APPLY - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE: REM pop/release new args + X=X+1:S%(X)=R: REM push/save new args for release + AR=R:GOSUB APPLY + AY=S%(X):X=X-1:GOSUB RELEASE: REM pop/release new args RETURN DO_MAP: - F%=AA% + F=AA REM first result list element - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC - REM push future return val, prior entry, F% and AB% - ZL%=ZL%+4:ZZ%(ZL%-3)=R%:ZZ%(ZL%-2)=0:ZZ%(ZL%-1)=F%:ZZ%(ZL%)=AB% + REM push future return val, prior entry, F and AB + X=X+4:S%(X-3)=R:S%(X-2)=0:S%(X-1)=F:S%(X)=AB DO_MAP_LOOP: REM set base values - Z%(R%,0)=6+16:Z%(R%,1)=0 - Z%(R%+1,0)=14:Z%(R%+1,1)=0 + Z%(R,0)=6+16:Z%(R,1)=0 + Z%(R+1,0)=14:Z%(R+1,1)=0 REM set previous to current if not the first element - IF ZZ%(ZL%-2)<>0 THEN Z%(ZZ%(ZL%-2),1)=R% + IF S%(X-2)<>0 THEN Z%(S%(X-2),1)=R REM update previous reference to current - ZZ%(ZL%-2)=R% + S%(X-2)=R - IF Z%(AB%,1)=0 THEN GOTO DO_MAP_DONE + IF Z%(AB,1)=0 THEN GOTO DO_MAP_DONE REM create argument list for apply call - SZ%=2:GOSUB ALLOC - Z%(R%,0)=6+16:Z%(R%,1)=0 - Z%(R%+1,0)=14:Z%(R%+1,1)=0 - AR%=R%: REM save end of list temporarily - SZ%=2:GOSUB ALLOC - Z%(R%,0)=6+16:Z%(R%,1)=AR% + SZ=2:GOSUB ALLOC + Z%(R,0)=6+16:Z%(R,1)=0 + Z%(R+1,0)=14:Z%(R+1,1)=0 + AR=R: REM save end of list temporarily + SZ=2:GOSUB ALLOC + Z%(R,0)=6+16:Z%(R,1)=AR REM inc ref cnt of referred argument - A%=Z%(AB%+1,1): Z%(A%,0)=Z%(A%,0)+16 - Z%(R%+1,0)=14:Z%(R%+1,1)=A% + A=Z%(AB+1,1): Z%(A,0)=Z%(A,0)+16 + Z%(R+1,0)=14:Z%(R+1,1)=A REM push argument list - ZL%=ZL%+1:ZZ%(ZL%)=R% + X=X+1:S%(X)=R - AR%=R%:GOSUB APPLY + AR=R:GOSUB APPLY REM pop apply args are release them - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE REM set the result value - Z%(ZZ%(ZL%-2)+1,1)=R% + Z%(S%(X-2)+1,1)=R - REM restore F% - F%=ZZ%(ZL%-1) + REM restore F + F=S%(X-1) - REM update AB% to next source element - ZZ%(ZL%)=Z%(ZZ%(ZL%),1) - AB%=ZZ%(ZL%) + REM update AB to next source element + S%(X)=Z%(S%(X),1) + AB=S%(X) REM allocate next element - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC GOTO DO_MAP_LOOP DO_MAP_DONE: REM get return val - R%=ZZ%(ZL%-3) + R=S%(X-3) REM pop everything off stack - ZL%=ZL%-4 + X=X-4 RETURN DO_ATOM: - SZ%=1:GOSUB ALLOC - Z%(AA%,0)=Z%(AA%,0)+16: REM inc ref cnt of contained value - Z%(R%,0)=12+16 - Z%(R%,1)=AA% + SZ=1:GOSUB ALLOC + Z%(AA,0)=Z%(AA,0)+16: REM inc ref cnt of contained value + Z%(R,0)=12+16 + Z%(R,1)=AA RETURN DO_ATOM_Q: - R%=1 - IF (Z%(AA%,0)AND15)=12 THEN R%=2 + R=1 + IF (Z%(AA,0)AND15)=12 THEN R=2 RETURN DO_DEREF: - R%=Z%(AA%,1):GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=Z%(AA,1):GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 RETURN DO_RESET_BANG: - R%=AB% + R=AB REM release current value - AY%=Z%(AA%,1):GOSUB RELEASE + AY=Z%(AA,1):GOSUB RELEASE REM inc ref by 2 for atom ownership and since we are returning it - Z%(R%,0)=Z%(R%,0)+32 + Z%(R,0)=Z%(R,0)+32 REM update value - Z%(AA%,1)=R% + Z%(AA,1)=R RETURN DO_SWAP_BANG: - F%=AB% + F=AB REM add atom to front of the args list - A%=Z%(AA%,1):B%=Z%(Z%(AR%,1),1):GOSUB CONS - AR%=R% + A=Z%(AA,1):B=Z%(Z%(AR,1),1):GOSUB CONS + AR=R REM push args for release after - ZL%=ZL%+1:ZZ%(ZL%)=AR% + X=X+1:S%(X)=AR REM push atom - ZL%=ZL%+1:ZZ%(ZL%)=AA% + X=X+1:S%(X)=AA GOSUB APPLY REM pop atom - AA%=ZZ%(ZL%):ZL%=ZL%-1 + AA=S%(X):X=X-1 REM pop and release args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE REM use reset to update the value - AB%=R%:GOSUB DO_RESET_BANG + AB=R:GOSUB DO_RESET_BANG REM but decrease ref cnt of return by 1 (not sure why) - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE RETURN @@ -399,72 +399,72 @@ DO_FUNCTION: RETURN DO_EVAL: - A%=AA%:E%=RE%:GOSUB EVAL + A=AA:E=RE%:GOSUB EVAL RETURN INIT_CORE_SET_FUNCTION: GOSUB NATIVE_FUNCTION - V%=R%:GOSUB ENV_SET_S + V=R:GOSUB ENV_SET_S RETURN -REM INIT_CORE_NS(E%) +REM INIT_CORE_NS(E) INIT_CORE_NS: REM create the environment mapping REM must match DO_FUNCTION mappings - K$="=":A%=1:GOSUB INIT_CORE_SET_FUNCTION - K$="throw":A%=2:GOSUB INIT_CORE_SET_FUNCTION - K$="nil?":A%=3:GOSUB INIT_CORE_SET_FUNCTION - K$="true?":A%=4:GOSUB INIT_CORE_SET_FUNCTION - K$="false?":A%=5:GOSUB INIT_CORE_SET_FUNCTION - K$="string?":A%=6:GOSUB INIT_CORE_SET_FUNCTION - K$="symbol":A%=7:GOSUB INIT_CORE_SET_FUNCTION - K$="symbol?":A%=8:GOSUB INIT_CORE_SET_FUNCTION - - K$="pr-str":A%=11:GOSUB INIT_CORE_SET_FUNCTION - K$="str":A%=12:GOSUB INIT_CORE_SET_FUNCTION - K$="prn":A%=13:GOSUB INIT_CORE_SET_FUNCTION - K$="println":A%=14:GOSUB INIT_CORE_SET_FUNCTION - K$="read-string":A%=15:GOSUB INIT_CORE_SET_FUNCTION - K$="readline":A%=16:GOSUB INIT_CORE_SET_FUNCTION - K$="slurp":A%=17:GOSUB INIT_CORE_SET_FUNCTION - - K$="<":A%=18:GOSUB INIT_CORE_SET_FUNCTION - K$="<=":A%=19:GOSUB INIT_CORE_SET_FUNCTION - K$=">":A%=20:GOSUB INIT_CORE_SET_FUNCTION - K$=">=":A%=21:GOSUB INIT_CORE_SET_FUNCTION - K$="+":A%=22:GOSUB INIT_CORE_SET_FUNCTION - K$="-":A%=23:GOSUB INIT_CORE_SET_FUNCTION - K$="*":A%=24:GOSUB INIT_CORE_SET_FUNCTION - K$="/":A%=25:GOSUB INIT_CORE_SET_FUNCTION - K$="time-ms":A%=26:GOSUB INIT_CORE_SET_FUNCTION - - K$="list":A%=27:GOSUB INIT_CORE_SET_FUNCTION - K$="list?":A%=28:GOSUB INIT_CORE_SET_FUNCTION - K$="vector":A%=29:GOSUB INIT_CORE_SET_FUNCTION - K$="vector?":A%=30:GOSUB INIT_CORE_SET_FUNCTION - K$="hash-map":A%=31:GOSUB INIT_CORE_SET_FUNCTION - K$="map?":A%=32:GOSUB INIT_CORE_SET_FUNCTION - - K$="sequential?":A%=39:GOSUB INIT_CORE_SET_FUNCTION - K$="cons":A%=40:GOSUB INIT_CORE_SET_FUNCTION - K$="concat":A%=41:GOSUB INIT_CORE_SET_FUNCTION - K$="nth":A%=42:GOSUB INIT_CORE_SET_FUNCTION - K$="first":A%=43:GOSUB INIT_CORE_SET_FUNCTION - K$="rest":A%=44:GOSUB INIT_CORE_SET_FUNCTION - K$="empty?":A%=45:GOSUB INIT_CORE_SET_FUNCTION - K$="count":A%=46:GOSUB INIT_CORE_SET_FUNCTION - K$="apply":A%=47:GOSUB INIT_CORE_SET_FUNCTION - K$="map":A%=48:GOSUB INIT_CORE_SET_FUNCTION - - K$="atom":A%=53:GOSUB INIT_CORE_SET_FUNCTION - K$="atom?":A%=54:GOSUB INIT_CORE_SET_FUNCTION - K$="deref":A%=55:GOSUB INIT_CORE_SET_FUNCTION - K$="reset!":A%=56:GOSUB INIT_CORE_SET_FUNCTION - K$="swap!":A%=57:GOSUB INIT_CORE_SET_FUNCTION - - K$="pr-memory":A%=58:GOSUB INIT_CORE_SET_FUNCTION - K$="pr-memory-summary":A%=59:GOSUB INIT_CORE_SET_FUNCTION - K$="eval":A%=60:GOSUB INIT_CORE_SET_FUNCTION + K$="=":A=1:GOSUB INIT_CORE_SET_FUNCTION + K$="throw":A=2:GOSUB INIT_CORE_SET_FUNCTION + K$="nil?":A=3:GOSUB INIT_CORE_SET_FUNCTION + K$="true?":A=4:GOSUB INIT_CORE_SET_FUNCTION + K$="false?":A=5:GOSUB INIT_CORE_SET_FUNCTION + K$="string?":A=6:GOSUB INIT_CORE_SET_FUNCTION + K$="symbol":A=7:GOSUB INIT_CORE_SET_FUNCTION + K$="symbol?":A=8:GOSUB INIT_CORE_SET_FUNCTION + + K$="pr-str":A=11:GOSUB INIT_CORE_SET_FUNCTION + K$="str":A=12:GOSUB INIT_CORE_SET_FUNCTION + K$="prn":A=13:GOSUB INIT_CORE_SET_FUNCTION + K$="println":A=14:GOSUB INIT_CORE_SET_FUNCTION + K$="read-string":A=15:GOSUB INIT_CORE_SET_FUNCTION + K$="readline":A=16:GOSUB INIT_CORE_SET_FUNCTION + K$="slurp":A=17:GOSUB INIT_CORE_SET_FUNCTION + + K$="<":A=18:GOSUB INIT_CORE_SET_FUNCTION + K$="<=":A=19:GOSUB INIT_CORE_SET_FUNCTION + K$=">":A=20:GOSUB INIT_CORE_SET_FUNCTION + K$=">=":A=21:GOSUB INIT_CORE_SET_FUNCTION + K$="+":A=22:GOSUB INIT_CORE_SET_FUNCTION + K$="-":A=23:GOSUB INIT_CORE_SET_FUNCTION + K$="*":A=24:GOSUB INIT_CORE_SET_FUNCTION + K$="/":A=25:GOSUB INIT_CORE_SET_FUNCTION + K$="time-ms":A=26:GOSUB INIT_CORE_SET_FUNCTION + + K$="list":A=27:GOSUB INIT_CORE_SET_FUNCTION + K$="list?":A=28:GOSUB INIT_CORE_SET_FUNCTION + K$="vector":A=29:GOSUB INIT_CORE_SET_FUNCTION + K$="vector?":A=30:GOSUB INIT_CORE_SET_FUNCTION + K$="hash-map":A=31:GOSUB INIT_CORE_SET_FUNCTION + K$="map?":A=32:GOSUB INIT_CORE_SET_FUNCTION + + K$="sequential?":A=39:GOSUB INIT_CORE_SET_FUNCTION + K$="cons":A=40:GOSUB INIT_CORE_SET_FUNCTION + K$="concat":A=41:GOSUB INIT_CORE_SET_FUNCTION + K$="nth":A=42:GOSUB INIT_CORE_SET_FUNCTION + K$="first":A=43:GOSUB INIT_CORE_SET_FUNCTION + K$="rest":A=44:GOSUB INIT_CORE_SET_FUNCTION + K$="empty?":A=45:GOSUB INIT_CORE_SET_FUNCTION + K$="count":A=46:GOSUB INIT_CORE_SET_FUNCTION + K$="apply":A=47:GOSUB INIT_CORE_SET_FUNCTION + K$="map":A=48:GOSUB INIT_CORE_SET_FUNCTION + + K$="atom":A=53:GOSUB INIT_CORE_SET_FUNCTION + K$="atom?":A=54:GOSUB INIT_CORE_SET_FUNCTION + K$="deref":A=55:GOSUB INIT_CORE_SET_FUNCTION + K$="reset!":A=56:GOSUB INIT_CORE_SET_FUNCTION + K$="swap!":A=57:GOSUB INIT_CORE_SET_FUNCTION + + K$="pr-memory":A=58:GOSUB INIT_CORE_SET_FUNCTION + K$="pr-memory-summary":A=59:GOSUB INIT_CORE_SET_FUNCTION + K$="eval":A=60:GOSUB INIT_CORE_SET_FUNCTION RETURN diff --git a/basic/debug.in.bas b/basic/debug.in.bas index 345807463a..b300fe9ad8 100644 --- a/basic/debug.in.bas +++ b/basic/debug.in.bas @@ -2,20 +2,20 @@ PR_MEMORY_SUMMARY: GOSUB CHECK_FREE_LIST: REM get count in P2% PRINT PRINT "Free memory (FRE) : "+STR$(FRE(0)) - PRINT "Value memory (Z%) : "+STR$(ZI%-1)+" /"+STR$(S1%) + PRINT "Value memory (Z%) : "+STR$(ZI-1)+" /"+STR$(Z1) PRINT " "; - PRINT " used:"+STR$(ZI%-1-P2%)+", freed:"+STR$(P2%); + PRINT " used:"+STR$(ZI-1-P2%)+", freed:"+STR$(P2%); PRINT ", post repl_env:"+STR$(ZT%) - PRINT "String values (ZS$) : "+STR$(ZJ%)+" /"+STR$(S2%) - PRINT "Call stack size (ZZ%) : "+STR$(ZL%+1)+" /"+STR$(S3%) + PRINT "String values (S$) : "+STR$(ZJ)+" /"+STR$(Z2) + PRINT "Call stack size (S%) : "+STR$(X+1)+" /"+STR$(Z3) RETURN REM REM PR_MEMORY(P1%, P2%) -> nil REM PR_MEMORY: -REM IF P2%"+STR$(P2%); -REM PRINT " (ZI%: "+STR$(ZI%)+", ZK%: "+STR$(ZK%)+"):" +REM PRINT " (ZI: "+STR$(ZI)+", ZK: "+STR$(ZK)+"):" REM IF P2%10 THEN GOTO PR_MEMORY_VALUE_LOOP @@ -35,22 +35,22 @@ REM I=I+1 REM GOTO PR_MEMORY_VALUE_LOOP REM PR_MEMORY_FREE: REM PRINT ": FREE size: "+STR$((Z%(I,0)AND-16)/16)+", next: "+STR$(Z%(I,1)); -REM IF I=ZK% THEN PRINT " (free list start)"; +REM IF I=ZK THEN PRINT " (free list start)"; REM PRINT REM IF (Z%(I,0)AND-16)=32 THEN I=I+1:PRINT " "+STR$(I)+": ---" REM I=I+1 REM GOTO PR_MEMORY_VALUE_LOOP REM PR_MEMORY_AFTER_VALUES: -REM PRINT "ZS% String Memory (ZJ%: "+STR$(ZJ%)+"):" -REM IF ZJ%<=0 THEN PRINT " ---":GOTO PR_MEMORY_SKIP_STRINGS -REM FOR I=0 TO ZJ%-1 -REM PRINT " "+STR$(I)+": '"+ZS$(I)+"'" +REM PRINT "ZS% String Memory (ZJ: "+STR$(ZJ)+"):" +REM IF ZJ<=0 THEN PRINT " ---":GOTO PR_MEMORY_SKIP_STRINGS +REM FOR I=0 TO ZJ-1 +REM PRINT " "+STR$(I)+": '"+S$(I)+"'" REM NEXT I REM PR_MEMORY_SKIP_STRINGS: -REM PRINT "ZZ% Stack Memory (ZL%: "+STR$(ZL%)+"):" -REM IF ZL%<0 THEN PRINT " ---":GOTO PR_MEMORY_SKIP_STACK -REM FOR I=0 TO ZL% -REM PRINT " "+STR$(I)+": "+STR$(ZZ%(I)) +REM PRINT "S% Stack Memory (X: "+STR$(X)+"):" +REM IF X<0 THEN PRINT " ---":GOTO PR_MEMORY_SKIP_STACK +REM FOR I=0 TO X +REM PRINT " "+STR$(I)+": "+STR$(S%(I)) REM NEXT I REM PR_MEMORY_SKIP_STACK: REM PRINT "^^^^^^" @@ -60,20 +60,20 @@ REM REM PR_OBJECT(P1%) -> nil REM PR_OBJECT: REM RD%=0 REM -REM RD%=RD%+1:ZL%=ZL%+1:ZZ%(ZL%)=P1% +REM RD%=RD%+1:X=X+1:S%(X)=P1% REM REM PR_OBJ_LOOP: REM IF RD%=0 THEN RETURN -REM I=ZZ%(ZL%):RD%=RD%-1:ZL%=ZL%-1 +REM I=S%(X):RD%=RD%-1:X=X-1 REM REM P2%=Z%(I,0)AND15 REM PRINT " "+STR$(I); REM PRINT ": ref cnt: "+STR$((Z%(I,0)AND-16)/16); REM PRINT ", type: "+STR$(P2%)+", value: "+STR$(Z%(I,1)); -REM IF P2%=4 THEN PRINT " '"+ZS$(Z%(I,1))+"'"; -REM IF P2%=5 THEN PRINT " "+ZS$(Z%(I,1))+""; +REM IF P2%=4 THEN PRINT " '"+S$(Z%(I,1))+"'"; +REM IF P2%=5 THEN PRINT " "+S$(Z%(I,1))+""; REM PRINT REM IF P2%<=5 OR P2%=9 THEN GOTO PR_OBJ_LOOP -REM IF Z%(I,1)<>0 THEN RD%=RD%+1:ZL%=ZL%+1:ZZ%(ZL%)=Z%(I,1) -REM IF P2%>=6 AND P2%<=8 THEN RD%=RD%+1:ZL%=ZL%+1:ZZ%(ZL%)=I+1 +REM IF Z%(I,1)<>0 THEN RD%=RD%+1:X=X+1:S%(X)=Z%(I,1) +REM IF P2%>=6 AND P2%<=8 THEN RD%=RD%+1:X=X+1:S%(X)=I+1 REM GOTO PR_OBJ_LOOP diff --git a/basic/env.in.bas b/basic/env.in.bas index 1a8be3fde5..8a278f757d 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -1,38 +1,38 @@ -REM ENV_NEW(EO%) -> R% +REM ENV_NEW(O) -> R ENV_NEW: REM allocate the data hashmap GOSUB HASHMAP - ET%=R% + ET%=R REM set the outer and data pointer - SZ%=2:GOSUB ALLOC - Z%(R%,0)=13+16 - Z%(R%,1)=ET% - Z%(R%+1,0)=13 - Z%(R%+1,1)=EO% - IF EO%<>-1 THEN Z%(EO%,0)=Z%(EO%,0)+16 + SZ=2:GOSUB ALLOC + Z%(R,0)=13+16 + Z%(R,1)=ET% + Z%(R+1,0)=13 + Z%(R+1,1)=O + IF O<>-1 THEN Z%(O,0)=Z%(O,0)+16 RETURN REM see RELEASE types.in.bas for environment cleanup -REM ENV_NEW_BINDS(EO%, BI%, EX%) -> R% +REM ENV_NEW_BINDS(O, BI%, EX%) -> R ENV_NEW_BINDS: GOSUB ENV_NEW - E%=R% + E=R REM process bindings ENV_NEW_BINDS_LOOP: - IF Z%(BI%,1)=0 THEN R%=E%:RETURN + IF Z%(BI%,1)=0 THEN R=E:RETURN REM get/deref the key from BI% - R%=BI%+1:GOSUB DEREF_R - K%=R% + R=BI%+1:GOSUB DEREF_R + K=R - IF ZS$(Z%(K%,1))="&" THEN GOTO EVAL_NEW_BINDS_VARGS + IF S$(Z%(K,1))="&" THEN GOTO EVAL_NEW_BINDS_VARGS EVAL_NEW_BINDS_1x1: REM get/deref the key from EX% - R%=EX%+1:GOSUB DEREF_R - V%=R% + R=EX%+1:GOSUB DEREF_R + V=R REM set the binding in the environment data GOSUB ENV_SET REM go to next element of BI% and EX% @@ -43,52 +43,52 @@ ENV_NEW_BINDS: EVAL_NEW_BINDS_VARGS: REM get/deref the key from next element of BI% BI%=Z%(BI%,1) - R%=BI%+1:GOSUB DEREF_R - K%=R% + R=BI%+1:GOSUB DEREF_R + K=R REM the value is the remaining list in EX% - V%=EX% + V=EX% REM set the binding in the environment data GOSUB ENV_SET - R%=E% + R=E RETURN -REM ENV_SET(E%, K%, V%) -> R% +REM ENV_SET(E, K, V) -> R ENV_SET: - HM%=Z%(E%,1) + H=Z%(E,1) GOSUB ASSOC1 - Z%(E%,1)=R% - R%=V% + Z%(E,1)=R + R=V RETURN -REM ENV_SET_S(E%, K$, V%) -> R% +REM ENV_SET_S(E, K$, V) -> R ENV_SET_S: - HM%=Z%(E%,1) + H=Z%(E,1) GOSUB ASSOC1_S - Z%(E%,1)=R% - R%=V% + Z%(E,1)=R + R=V RETURN -REM ENV_FIND(E%, K%) -> R% -REM Returns environment (R%) containing K%. If found, value found is -REM in T4% +REM ENV_FIND(E, K) -> R +REM Returns environment (R) containing K. If found, value found is +REM in T4 ENV_FIND: - EF%=E% + EF%=E ENV_FIND_LOOP: - HM%=Z%(EF%,1) - REM More efficient to use GET for value (R%) and contains? (T3%) + H=Z%(EF%,1) + REM More efficient to use GET for value (R) and contains? (T3) GOSUB HASHMAP_GET - REM if we found it, save value in T4% for ENV_GET - IF T3%=1 THEN T4%=R%:GOTO ENV_FIND_DONE + REM if we found it, save value in T4 for ENV_GET + IF T3=1 THEN T4=R:GOTO ENV_FIND_DONE EF%=Z%(EF%+1,1): REM get outer environment IF EF%<>-1 THEN GOTO ENV_FIND_LOOP ENV_FIND_DONE: - R%=EF% + R=EF% RETURN -REM ENV_GET(E%, K%) -> R% +REM ENV_GET(E, K) -> R ENV_GET: GOSUB ENV_FIND - IF R%=-1 THEN R%=0:ER%=-1:ER$="'"+ZS$(Z%(K%,1))+"' not found":RETURN - R%=T4%:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + IF R=-1 THEN R=0:ER=-1:ER$="'"+S$(Z%(K,1))+"' not found":RETURN + R=T4:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 RETURN diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 7fb2c741bc..bde9ad4e83 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -1,105 +1,105 @@ -REM PR_STR(AZ%, PR%) -> R$ +REM PR_STR(AZ, PR) -> R$ PR_STR: RR$="" PR_STR_RECUR: - T%=Z%(AZ%,0)AND15 - REM PRINT "AZ%: "+STR$(AZ%)+", T%: "+STR$(T%)+", V%: "+STR$(Z%(AZ%,1)) - IF T%=0 THEN R$="nil":RETURN - ON T% GOTO PR_BOOLEAN,PR_INTEGER,PR_UNKNOWN,PR_STRING,PR_SYMBOL,PR_SEQ,PR_SEQ,PR_SEQ,PR_FUNCTION,PR_MAL_FUNCTION,PR_MAL_FUNCTION,PR_ATOM,PR_ENV,PR_RECUR,PR_FREE + T=Z%(AZ,0)AND15 + REM PRINT "AZ: "+STR$(AZ)+", T: "+STR$(T)+", V: "+STR$(Z%(AZ,1)) + IF T=0 THEN R$="nil":RETURN + ON T GOTO PR_BOOLEAN,PR_INTEGER,PR_UNKNOWN,PR_STRING,PR_SYMBOL,PR_SEQ,PR_SEQ,PR_SEQ,PR_FUNCTION,PR_MAL_FUNCTION,PR_MAL_FUNCTION,PR_ATOM,PR_ENV,PR_RECUR,PR_FREE PR_UNKNOWN: R$="#" RETURN PR_RECUR: - AZ%=Z%(AZ%,1) + AZ=Z%(AZ,1) GOTO PR_STR_RECUR PR_BOOLEAN: R$="true" - IF Z%(AZ%,1)=0 THEN R$="false" + IF Z%(AZ,1)=0 THEN R$="false" RETURN PR_INTEGER: - T5%=Z%(AZ%,1) - R$=STR$(T5%) - IF T5%<0 THEN RETURN + T5=Z%(AZ,1) + R$=STR$(T5) + IF T5<0 THEN RETURN REM Remove initial space R$=RIGHT$(R$, LEN(R$)-1) RETURN PR_STRING: - IF PR%=1 THEN PR_STRING_READABLY - R$=ZS$(Z%(AZ%,1)) + IF PR=1 THEN PR_STRING_READABLY + R$=S$(Z%(AZ,1)) RETURN PR_STRING_READABLY: - R$=ZS$(Z%(AZ%,1)) + R$=S$(Z%(AZ,1)) S1$=CHR$(92):S2$=CHR$(92)+CHR$(92):GOSUB REPLACE: REM escape backslash S1$=CHR$(34):S2$=CHR$(92)+CHR$(34):GOSUB REPLACE: REM escape quotes S1$=CHR$(13):S2$=CHR$(92)+"n":GOSUB REPLACE: REM escape newlines R$=CHR$(34)+R$+CHR$(34) RETURN PR_SYMBOL: - R$=ZS$(Z%(AZ%,1)) + R$=S$(Z%(AZ,1)) RETURN PR_SEQ: - IF T%=6 THEN RR$=RR$+"(" - IF T%=7 THEN RR$=RR$+"[" - IF T%=8 THEN RR$=RR$+"{" + IF T=6 THEN RR$=RR$+"(" + IF T=7 THEN RR$=RR$+"[" + IF T=8 THEN RR$=RR$+"{" REM push the type and where we are in the sequence - ZL%=ZL%+2 - ZZ%(ZL%-1)=T% - ZZ%(ZL%)=AZ% + X=X+2 + S%(X-1)=T + S%(X)=AZ PR_SEQ_LOOP: - IF Z%(AZ%,1)=0 THEN PR_SEQ_DONE - AZ%=AZ%+1 + IF Z%(AZ,1)=0 THEN PR_SEQ_DONE + AZ=AZ+1 GOSUB PR_STR_RECUR REM if we just rendered a non-sequence, then append it - IF T%<6 OR T%>8 THEN RR$=RR$+R$ + IF T<6 OR T>8 THEN RR$=RR$+R$ REM restore current seq type - T%=ZZ%(ZL%-1) + T=S%(X-1) REM Go to next list element - AZ%=Z%(ZZ%(ZL%),1) - ZZ%(ZL%)=AZ% - IF Z%(AZ%,1)<>0 THEN RR$=RR$+" " + AZ=Z%(S%(X),1) + S%(X)=AZ + IF Z%(AZ,1)<>0 THEN RR$=RR$+" " GOTO PR_SEQ_LOOP PR_SEQ_DONE: REM get type - T%=ZZ%(ZL%-1) + T=S%(X-1) REM pop where we are the sequence and type - ZL%=ZL%-2 - IF T%=6 THEN RR$=RR$+")" - IF T%=7 THEN RR$=RR$+"]" - IF T%=8 THEN RR$=RR$+"}" + X=X-2 + IF T=6 THEN RR$=RR$+")" + IF T=7 THEN RR$=RR$+"]" + IF T=8 THEN RR$=RR$+"}" R$=RR$ RETURN PR_FUNCTION: - T1%=Z%(AZ%,1) - R$="#" + T1=Z%(AZ,1) + R$="#" RETURN PR_MAL_FUNCTION: - T1%=AZ% - AZ%=Z%(T1%+1,0):GOSUB PR_STR_RECUR + T1=AZ + AZ=Z%(T1+1,0):GOSUB PR_STR_RECUR T7$="(fn* "+R$ - AZ%=Z%(T1%,1):GOSUB PR_STR_RECUR + AZ=Z%(T1,1):GOSUB PR_STR_RECUR R$=T7$+" "+R$+")" RETURN PR_ATOM: - AZ%=Z%(AZ%,1):GOSUB PR_STR_RECUR + AZ=Z%(AZ,1):GOSUB PR_STR_RECUR R$="(atom "+R$+")" RETURN PR_ENV: - R$="#" + R$="#" RETURN PR_FREE: - R$="#" + R$="#" RETURN -REM PR_STR_SEQ(AZ%, PR%, SE$) -> R$ +REM PR_STR_SEQ(AZ, PR, SE$) -> R$ PR_STR_SEQ: - T9%=AZ% + T9=AZ R1$="" PR_STR_SEQ_LOOP: - IF Z%(T9%,1)=0 THEN R$=R1$:RETURN - AZ%=T9%+1:GOSUB PR_STR + IF Z%(T9,1)=0 THEN R$=R1$:RETURN + AZ=T9+1:GOSUB PR_STR REM goto the next sequence element - T9%=Z%(T9%,1) - IF Z%(T9%,1)=0 THEN R1$=R1$+R$ - IF Z%(T9%,1)<>0 THEN R1$=R1$+R$+SE$ + T9=Z%(T9,1) + IF Z%(T9,1)=0 THEN R1$=R1$+R$ + IF Z%(T9,1)<>0 THEN R1$=R1$+R$+SE$ GOTO PR_STR_SEQ_LOOP diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 4f75b98861..9e67c131a9 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -33,20 +33,20 @@ SKIP_SPACES: READ_ATOM: - R%=0 + R=0 RETURN -REM READ_FORM(A$, IDX%) -> R% +REM READ_FORM(A$, IDX%) -> R READ_FORM: - IF ER%<>-2 THEN RETURN + IF ER<>-2 THEN RETURN GOSUB SKIP_SPACES GOSUB READ_TOKEN - IF T$="" AND SD%>0 THEN ER$="unexpected EOF":GOTO READ_FORM_ABORT + IF T$="" AND SD>0 THEN ER$="unexpected EOF":GOTO READ_FORM_ABORT REM PRINT "READ_FORM T$: ["+T$+"]" - IF T$="" THEN R%=0:GOTO READ_FORM_DONE - IF T$="nil" THEN T%=0:GOTO READ_NIL_BOOL - IF T$="false" THEN T%=1:GOTO READ_NIL_BOOL - IF T$="true" THEN T%=2:GOTO READ_NIL_BOOL + IF T$="" THEN R=0:GOTO READ_FORM_DONE + IF T$="nil" THEN T=0:GOTO READ_NIL_BOOL + IF T$="false" THEN T=1:GOTO READ_NIL_BOOL + IF T$="true" THEN T=2:GOTO READ_NIL_BOOL IF T$="'" THEN AS$="quote":GOTO READ_MACRO IF T$="`" THEN AS$="quasiquote":GOTO READ_MACRO IF T$="~" THEN AS$="unquote":GOTO READ_MACRO @@ -54,17 +54,17 @@ READ_FORM: IF T$="@" THEN AS$="deref":GOTO READ_MACRO CH$=MID$(T$,1,1) REM PRINT "CH$: ["+CH$+"]("+STR$(ASC(CH$))+")" - IF (CH$=";") THEN R%=0:GOTO READ_TO_EOL + IF (CH$=";") THEN R=0:GOTO READ_TO_EOL IF CH$>="0" AND CH$<="9" THEN GOTO READ_NUMBER IF CH$="-" THEN GOTO READ_SYMBOL_MAYBE IF CH$=CHR$(34) THEN GOTO READ_STRING - IF CH$="(" THEN T%=6:GOTO READ_SEQ - IF CH$=")" THEN T%=6:GOTO READ_SEQ_END - IF CH$="[" THEN T%=7:GOTO READ_SEQ - IF CH$="]" THEN T%=7:GOTO READ_SEQ_END - IF CH$="{" THEN T%=8:GOTO READ_SEQ - IF CH$="}" THEN T%=8:GOTO READ_SEQ_END + IF CH$="(" THEN T=6:GOTO READ_SEQ + IF CH$=")" THEN T=6:GOTO READ_SEQ_END + IF CH$="[" THEN T=7:GOTO READ_SEQ + IF CH$="]" THEN T=7:GOTO READ_SEQ_END + IF CH$="{" THEN T=8:GOTO READ_SEQ + IF CH$="}" THEN T=8:GOTO READ_SEQ_END GOTO READ_SYMBOL READ_TO_EOL: @@ -74,27 +74,27 @@ READ_FORM: GOTO READ_TO_EOL READ_NIL_BOOL: REM PRINT "READ_NIL_BOOL" - R%=T% - Z%(R%,0)=Z%(R%,0)+16 + R=T + Z%(R,0)=Z%(R,0)+16 GOTO READ_FORM_DONE READ_NUMBER: REM PRINT "READ_NUMBER" - SZ%=1:GOSUB ALLOC - Z%(R%,0)=2+16 - Z%(R%,1)=VAL(T$) + SZ=1:GOSUB ALLOC + Z%(R,0)=2+16 + Z%(R,1)=VAL(T$) GOTO READ_FORM_DONE READ_MACRO: IDX%=IDX%+LEN(T$) - T%=5:GOSUB STRING: REM AS$ set above + T=5:GOSUB STRING: REM AS$ set above - REM to call READ_FORM recursively, SD% needs to be saved, set to + REM to call READ_FORM recursively, SD needs to be saved, set to REM 0 for the call and then restored afterwards. - ZL%=ZL%+2:ZZ%(ZL%-1)=SD%:ZZ%(ZL%)=R%: REM push SD% and symbol - SD%=0:GOSUB READ_FORM:B1%=R% - SD%=ZZ%(ZL%-1):B2%=ZZ%(ZL%):ZL%=ZL%-2: REM pop SD%, pop symbol into B2% + X=X+2:S%(X-1)=SD:S%(X)=R: REM push SD and symbol + SD=0:GOSUB READ_FORM:B1%=R + SD=S%(X-1):B2%=S%(X):X=X-2: REM pop SD, pop symbol into B2% GOSUB LIST2 - AY%=B1%:GOSUB RELEASE: REM release value, list has ownership + AY=B1%:GOSUB RELEASE: REM release value, list has ownership T$="" GOTO READ_FORM_DONE @@ -107,102 +107,102 @@ READ_FORM: S1$=CHR$(92)+"n":S2$=CHR$(13):GOSUB REPLACE: REM unescape newlines S1$=CHR$(92)+CHR$(92):S2$=CHR$(92):GOSUB REPLACE: REM unescape backslashes REM intern string value - AS$=R$:T%=4+16:GOSUB STRING + AS$=R$:T=4+16:GOSUB STRING GOTO READ_FORM_DONE READ_SYMBOL_MAYBE: CH$=MID$(T$,2,1) IF CH$>="0" AND CH$<="9" THEN GOTO READ_NUMBER READ_SYMBOL: REM PRINT "READ_SYMBOL" - AS$=T$:T%=5+16:GOSUB STRING + AS$=T$:T=5+16:GOSUB STRING GOTO READ_FORM_DONE READ_SEQ: REM PRINT "READ_SEQ" - SD%=SD%+1: REM increase read sequence depth + SD=SD+1: REM increase read sequence depth REM allocate first sequence entry and space for value - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM set reference value/pointer to new embedded sequence - IF SD%>1 THEN Z%(ZZ%(ZL%)+1,1)=R% + IF SD>1 THEN Z%(S%(X)+1,1)=R REM set the type (with 1 ref cnt) and next pointer to current end - Z%(R%,0)=T%+16 - Z%(R%,1)=0 - Z%(R%+1,0)=14 - Z%(R%+1,1)=0 + Z%(R,0)=T+16 + Z%(R,1)=0 + Z%(R+1,0)=14 + Z%(R+1,1)=0 REM push start ptr on the stack - ZL%=ZL%+1 - ZZ%(ZL%)=R% + X=X+1 + S%(X)=R REM push current sequence type - ZL%=ZL%+1 - ZZ%(ZL%)=T% + X=X+1 + S%(X)=T REM push previous ptr on the stack - ZL%=ZL%+1 - ZZ%(ZL%)=R% + X=X+1 + S%(X)=R IDX%=IDX%+LEN(T$) GOTO READ_FORM READ_SEQ_END: REM PRINT "READ_SEQ_END" - IF SD%=0 THEN ER$="unexpected '"+CH$+"'":GOTO READ_FORM_ABORT - IF ZZ%(ZL%-1)<>T% THEN ER$="sequence mismatch":GOTO READ_FORM_ABORT - SD%=SD%-1: REM decrease read sequence depth - R%=ZZ%(ZL%-2): REM ptr to start of sequence to return - T%=ZZ%(ZL%-1): REM type prior to recur - ZL%=ZL%-3: REM pop previous, type, and start off the stack + IF SD=0 THEN ER$="unexpected '"+CH$+"'":GOTO READ_FORM_ABORT + IF S%(X-1)<>T THEN ER$="sequence mismatch":GOTO READ_FORM_ABORT + SD=SD-1: REM decrease read sequence depth + R=S%(X-2): REM ptr to start of sequence to return + T=S%(X-1): REM type prior to recur + X=X-3: REM pop previous, type, and start off the stack GOTO READ_FORM_DONE READ_FORM_DONE: IDX%=IDX%+LEN(T$) - T8%=R%: REM save previous value + T8=R: REM save previous value REM check read sequence depth - IF SD%=0 THEN RETURN + IF SD=0 THEN RETURN REM PRINT "READ_FORM_DONE next list entry" REM allocate new sequence entry and space for value - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM previous element - T7%=ZZ%(ZL%) + T7=S%(X) REM set previous list element to point to new element - Z%(T7%,1)=R% + Z%(T7,1)=R REM set the list value pointer - Z%(T7%+1,1)=T8% + Z%(T7+1,1)=T8 REM set type to previous type, with ref count of 1 (from previous) - Z%(R%,0)=ZZ%(ZL%-1)+16 - Z%(R%,1)=0: REM current end of sequence - Z%(R%+1,0)=14 - Z%(R%+1,1)=0 + Z%(R,0)=S%(X-1)+16 + Z%(R,1)=0: REM current end of sequence + Z%(R+1,0)=14 + Z%(R+1,1)=0 - IF T7%=ZZ%(ZL%-2) THEN GOTO READ_FORM_SKIP_FIRST - Z%(T7%,1)=R% + IF T7=S%(X-2) THEN GOTO READ_FORM_SKIP_FIRST + Z%(T7,1)=R READ_FORM_SKIP_FIRST: REM update previous pointer to current element - ZZ%(ZL%)=R% + S%(X)=R GOTO READ_FORM READ_FORM_ABORT: - ER%=-1 - R%=0 + ER=-1 + R=0 READ_FORM_ABORT_UNWIND: - IF SD%=0 THEN RETURN - ZL%=ZL%-3: REM pop previous, type, and start off the stack - SD%=SD%-1 - IF SD%=0 THEN AY%=ZZ%(ZL%+1):GOSUB RELEASE + IF SD=0 THEN RETURN + X=X-3: REM pop previous, type, and start off the stack + SD=SD-1 + IF SD=0 THEN AY=S%(X+1):GOSUB RELEASE GOTO READ_FORM_ABORT_UNWIND -REM READ_STR(A$) -> R% +REM READ_STR(A$) -> R READ_STR: IDX%=1 - SD%=0: REM sequence read depth + SD=0: REM sequence read depth GOSUB READ_FORM RETURN diff --git a/basic/step0_repl.in.bas b/basic/step0_repl.in.bas index a9636564bb..2431fd325c 100755 --- a/basic/step0_repl.in.bas +++ b/basic/step0_repl.in.bas @@ -7,7 +7,7 @@ MAL_READ: R$=A$ RETURN -REM EVAL(A$, E%) -> R$ +REM EVAL(A$, E) -> R$ EVAL: R$=A$ RETURN @@ -20,8 +20,8 @@ MAL_PRINT: REM REP(A$) -> R$ REP: GOSUB MAL_READ - A%=R%:GOSUB EVAL - A%=R%:GOSUB MAL_PRINT + A=R:GOSUB EVAL + A=R:GOSUB MAL_PRINT RETURN REM MAIN program diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index ae921bc38f..df6d7c595b 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -7,35 +7,35 @@ REM $INCLUDE: 'printer.in.bas' REM $INCLUDE: 'debug.in.bas' -REM READ(A$) -> R% +REM READ(A$) -> R MAL_READ: GOSUB READ_STR RETURN -REM EVAL(A%, E%) -> R% +REM EVAL(A, E) -> R EVAL: - R%=A% + R=A RETURN -REM PRINT(A%) -> R$ +REM PRINT(A) -> R$ MAL_PRINT: - AZ%=A%:PR%=1:GOSUB PR_STR + AZ=A:PR=1:GOSUB PR_STR RETURN REM REP(A$) -> R$ REP: GOSUB MAL_READ - IF ER%<>-2 THEN GOTO REP_DONE + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:GOSUB EVAL - IF ER%<>-2 THEN GOTO REP_DONE + A=R:GOSUB EVAL + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:GOSUB MAL_PRINT + A=R:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from EVAL - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE R$=RT$ RETURN @@ -43,7 +43,7 @@ REM MAIN program MAIN: GOSUB INIT_MEMORY - ZT%=ZI%: REM top of memory after base repl_env + ZT%=ZI: REM top of memory after base repl_env REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser @@ -51,7 +51,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -62,6 +62,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=-2:ER$="" + ER=-2:ER$="" RETURN diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index 41e91dbb6e..0098e1cc2b 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -7,233 +7,233 @@ REM $INCLUDE: 'printer.in.bas' REM $INCLUDE: 'debug.in.bas' -REM READ(A$) -> R% +REM READ(A$) -> R MAL_READ: GOSUB READ_STR RETURN -REM EVAL_AST(A%, E%) -> R% +REM EVAL_AST(A, E) -> R EVAL_AST: - LV%=LV%+1 + LV=LV+1 - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A - IF ER%<>-2 THEN GOTO EVAL_AST_RETURN + IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T%=Z%(A%,0)AND15 - IF T%=5 THEN GOTO EVAL_AST_SYMBOL - IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ + T=Z%(A,0)AND15 + IF T=5 THEN GOTO EVAL_AST_SYMBOL + IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R%=A%:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=A:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - HM%=E%:K%=A%:GOSUB HASHMAP_GET + H=E:K=A:GOSUB HASHMAP_GET GOSUB DEREF_R - IF T3%=0 THEN ER%=-1:ER$="'"+ZS$(Z%(A%,1))+"' not found":GOTO EVAL_AST_RETURN - Z%(R%,0)=Z%(R%,0)+16 + IF T3=0 THEN ER=-1:ER$="'"+S$(Z%(A,1))+"' not found":GOTO EVAL_AST_RETURN + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SEQ: REM allocate the first entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM make space on the stack - ZL%=ZL%+4 + X=X+4 REM push type of sequence - ZZ%(ZL%-3)=T% + S%(X-3)=T REM push sequence index - ZZ%(ZL%-2)=-1 + S%(X-2)=-1 REM push future return value (new sequence) - ZZ%(ZL%-1)=R% + S%(X-1)=R REM push previous new sequence entry - ZZ%(ZL%)=R% + S%(X)=R EVAL_AST_SEQ_LOOP: REM set new sequence entry type (with 1 ref cnt) - Z%(R%,0)=ZZ%(ZL%-3)+16 - Z%(R%,1)=0 + Z%(R,0)=S%(X-3)+16 + Z%(R,1)=0 REM create value ptr placeholder - Z%(R%+1,0)=14 - Z%(R%+1,1)=0 + Z%(R+1,0)=14 + Z%(R+1,1)=0 REM update index - ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + S%(X-2)=S%(X-2)+1 REM check if we are done evaluating the source sequence - IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: - R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + R=A+1:GOSUB DEREF_R: REM deref to target of referred entry + Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: REM call EVAL for each entry - A%=A%+1:GOSUB EVAL - A%=A%-1 + A=A+1:GOSUB EVAL + A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(ZZ%(ZL%)+1,1)=R% + Z%(S%(X)+1,1)=R - IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(ZZ%(ZL%),1)=R% + Z%(S%(X),1)=R REM update previous ptr to current entry - ZZ%(ZL%)=R% + S%(X)=R REM process the next sequence entry from source list - A%=Z%(A%,1) + A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM get return value (new seq), index, and seq type - R%=ZZ%(ZL%-1) + R=S%(X-1) REM pop previous, return, index and type - ZL%=ZL%-4 + X=X-4 GOTO EVAL_AST_RETURN EVAL_AST_RETURN: - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 - LV%=LV%-1 + LV=LV-1 RETURN -REM EVAL(A%, E%)) -> R% +REM EVAL(A, E)) -> R EVAL: - LV%=LV%+1: REM track basic return stack level + LV=LV+1: REM track basic return stack level - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A - REM AZ%=A%:PR%=1:GOSUB PR_STR - REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" + REM AZ=A:PR=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A GOSUB LIST_Q - IF R% THEN GOTO APPLY_LIST + IF R THEN GOTO APPLY_LIST REM ELSE GOSUB EVAL_AST GOTO EVAL_RETURN APPLY_LIST: GOSUB EMPTY_Q - IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN EVAL_INVOKE: GOSUB EVAL_AST - R3%=R% + R3=R REM if error, return f/args for release by caller - IF ER%<>-2 THEN GOTO EVAL_RETURN - F%=R%+1 + IF ER<>-2 THEN GOTO EVAL_RETURN + F=R+1 - AR%=Z%(R%,1): REM rest - R%=F%:GOSUB DEREF_R:F%=R% - IF (Z%(F%,0)AND15)<>9 THEN ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN + AR=Z%(R,1): REM rest + R=F:GOSUB DEREF_R:F=R + IF (Z%(F,0)AND15)<>9 THEN ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION - AY%=R3%:GOSUB RELEASE + AY=R3:GOSUB RELEASE GOTO EVAL_RETURN EVAL_RETURN: - LV%=LV%-1: REM track basic return stack level + LV=LV-1: REM track basic return stack level REM trigger GC TA%=FRE(0) - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 RETURN -REM DO_FUNCTION(F%, AR%) +REM DO_FUNCTION(F, AR) DO_FUNCTION: - AZ%=F%:GOSUB PR_STR + AZ=F:GOSUB PR_STR F$=R$ - AZ%=AR%:GOSUB PR_STR + AZ=AR:GOSUB PR_STR AR$=R$ REM Get the function number - FF%=Z%(F%,1) + FF=Z%(F,1) REM Get argument values - R%=AR%+1:GOSUB DEREF_R:AA%=Z%(R%,1) - R%=Z%(AR%,1)+1:GOSUB DEREF_R:AB%=Z%(R%,1) + R=AR+1:GOSUB DEREF_R:AA=Z%(R,1) + R=Z%(AR,1)+1:GOSUB DEREF_R:AB=Z%(R,1) REM Allocate the return value - SZ%=1:GOSUB ALLOC + SZ=1:GOSUB ALLOC REM Switch on the function number - IF FF%=1 THEN GOTO DO_ADD - IF FF%=2 THEN GOTO DO_SUB - IF FF%=3 THEN GOTO DO_MULT - IF FF%=4 THEN GOTO DO_DIV - ER%=-1:ER$="unknown function"+STR$(FF%):RETURN + IF FF=1 THEN GOTO DO_ADD + IF FF=2 THEN GOTO DO_SUB + IF FF=3 THEN GOTO DO_MULT + IF FF=4 THEN GOTO DO_DIV + ER=-1:ER$="unknown function"+STR$(FF):RETURN DO_ADD: - Z%(R%,0)=2+16 - Z%(R%,1)=AA%+AB% + Z%(R,0)=2+16 + Z%(R,1)=AA+AB GOTO DO_FUNCTION_DONE DO_SUB: - Z%(R%,0)=2+16 - Z%(R%,1)=AA%-AB% + Z%(R,0)=2+16 + Z%(R,1)=AA-AB GOTO DO_FUNCTION_DONE DO_MULT: - Z%(R%,0)=2+16 - Z%(R%,1)=AA%*AB% + Z%(R,0)=2+16 + Z%(R,1)=AA*AB GOTO DO_FUNCTION_DONE DO_DIV: - Z%(R%,0)=2+16 - Z%(R%,1)=AA%/AB% + Z%(R,0)=2+16 + Z%(R,1)=AA/AB GOTO DO_FUNCTION_DONE DO_FUNCTION_DONE: RETURN -REM PRINT(A%) -> R$ +REM PRINT(A) -> R$ MAL_PRINT: - AZ%=A%:PR%=1:GOSUB PR_STR + AZ=A:PR=1:GOSUB PR_STR RETURN REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - R1%=0:R2%=0 + R1=0:R2=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL - R2%=R% - IF ER%<>-2 THEN GOTO REP_DONE + A=R:E=RE%:GOSUB EVAL + R2=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:GOSUB MAL_PRINT + A=R:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R2<>0 THEN AY=R2:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ RETURN @@ -241,28 +241,28 @@ REM MAIN program MAIN: GOSUB INIT_MEMORY - LV%=0 + LV=0 REM create repl_env - GOSUB HASHMAP:RE%=R% + GOSUB HASHMAP:RE%=R REM + function - A%=1:GOSUB NATIVE_FUNCTION - HM%=RE%:K$="+":V%=R%:GOSUB ASSOC1_S:RE%=R% + A=1:GOSUB NATIVE_FUNCTION + H=RE%:K$="+":V=R:GOSUB ASSOC1_S:RE%=R REM - function - A%=2:GOSUB NATIVE_FUNCTION - HM%=RE%:K$="-":V%=R%:GOSUB ASSOC1_S:RE%=R% + A=2:GOSUB NATIVE_FUNCTION + H=RE%:K$="-":V=R:GOSUB ASSOC1_S:RE%=R REM * function - A%=3:GOSUB NATIVE_FUNCTION - HM%=RE%:K$="*":V%=R%:GOSUB ASSOC1_S:RE%=R% + A=3:GOSUB NATIVE_FUNCTION + H=RE%:K$="*":V=R:GOSUB ASSOC1_S:RE%=R REM / function - A%=4:GOSUB NATIVE_FUNCTION - HM%=RE%:K$="/":V%=R%:GOSUB ASSOC1_S:RE%=R% + A=4:GOSUB NATIVE_FUNCTION + H=RE%:K$="/":V=R:GOSUB ASSOC1_S:RE%=R - ZT%=ZI%: REM top of memory after base repl_env + ZT%=ZI: REM top of memory after base repl_env REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser @@ -270,7 +270,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -281,6 +281,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=-2:ER$="" + ER=-2:ER$="" RETURN diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index 04c2fe9d5d..af9481225b 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -8,302 +8,302 @@ REM $INCLUDE: 'env.in.bas' REM $INCLUDE: 'debug.in.bas' -REM READ(A$) -> R% +REM READ(A$) -> R MAL_READ: GOSUB READ_STR RETURN -REM EVAL_AST(A%, E%) -> R% +REM EVAL_AST(A, E) -> R EVAL_AST: - LV%=LV%+1 + LV=LV+1 - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A - IF ER%<>-2 THEN GOTO EVAL_AST_RETURN + IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T%=Z%(A%,0)AND15 - IF T%=5 THEN GOTO EVAL_AST_SYMBOL - IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ + T=Z%(A,0)AND15 + IF T=5 THEN GOTO EVAL_AST_SYMBOL + IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R%=A%:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=A:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K%=A%:GOSUB ENV_GET + K=A:GOSUB ENV_GET GOTO EVAL_AST_RETURN EVAL_AST_SEQ: REM allocate the first entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM make space on the stack - ZL%=ZL%+4 + X=X+4 REM push type of sequence - ZZ%(ZL%-3)=T% + S%(X-3)=T REM push sequence index - ZZ%(ZL%-2)=-1 + S%(X-2)=-1 REM push future return value (new sequence) - ZZ%(ZL%-1)=R% + S%(X-1)=R REM push previous new sequence entry - ZZ%(ZL%)=R% + S%(X)=R EVAL_AST_SEQ_LOOP: REM set new sequence entry type (with 1 ref cnt) - Z%(R%,0)=ZZ%(ZL%-3)+16 - Z%(R%,1)=0 + Z%(R,0)=S%(X-3)+16 + Z%(R,1)=0 REM create value ptr placeholder - Z%(R%+1,0)=14 - Z%(R%+1,1)=0 + Z%(R+1,0)=14 + Z%(R+1,1)=0 REM update index - ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + S%(X-2)=S%(X-2)+1 REM check if we are done evaluating the source sequence - IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: - R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + R=A+1:GOSUB DEREF_R: REM deref to target of referred entry + Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: REM call EVAL for each entry - A%=A%+1:GOSUB EVAL - A%=A%-1 + A=A+1:GOSUB EVAL + A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(ZZ%(ZL%)+1,1)=R% + Z%(S%(X)+1,1)=R - IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(ZZ%(ZL%),1)=R% + Z%(S%(X),1)=R REM update previous ptr to current entry - ZZ%(ZL%)=R% + S%(X)=R REM process the next sequence entry from source list - A%=Z%(A%,1) + A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER%=-2 THEN R%=ZZ%(ZL%-1) + IF ER=-2 THEN R=S%(X-1) REM otherwise, free the return value and return nil - IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE REM pop previous, return, index and type - ZL%=ZL%-4 + X=X-4 GOTO EVAL_AST_RETURN EVAL_AST_RETURN: - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 - LV%=LV%-1 + LV=LV-1 RETURN -REM EVAL(A%, E%)) -> R% +REM EVAL(A, E)) -> R EVAL: - LV%=LV%+1: REM track basic return stack level + LV=LV+1: REM track basic return stack level - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A - REM AZ%=A%:PR%=1:GOSUB PR_STR - REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" + REM AZ=A:PR=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A GOSUB LIST_Q - IF R% THEN GOTO APPLY_LIST + IF R THEN GOTO APPLY_LIST REM ELSE GOSUB EVAL_AST GOTO EVAL_RETURN APPLY_LIST: GOSUB EMPTY_Q - IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN - A0%=A%+1 - R%=A0%:GOSUB DEREF_R:A0%=R% + A0%=A+1 + R=A0%:GOSUB DEREF_R:A0%=R REM get symbol in A$ IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) + IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A%,1),1),1)+1 - R%=A3%:GOSUB DEREF_R:A3%=R% + A3%=Z%(Z%(Z%(A,1),1),1)+1 + R=A3%:GOSUB DEREF_R:A3%=R EVAL_GET_A2: - A2%=Z%(Z%(A%,1),1)+1 - R%=A2%:GOSUB DEREF_R:A2%=R% + A2%=Z%(Z%(A,1),1)+1 + R=A2%:GOSUB DEREF_R:A2%=R EVAL_GET_A1: - A1%=Z%(A%,1)+1 - R%=A1%:GOSUB DEREF_R:A1%=R% + A1%=Z%(A,1)+1 + R=A1%:GOSUB DEREF_R:A1%=R RETURN EVAL_DEF: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% - A%=A2%:GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + X=X+1:S%(X)=A1%: REM push A1% + A=A2%:GOSUB EVAL: REM eval a2 + A1%=S%(X):X=X-1: REM pop A1% - IF ER%<>-2 THEN GOTO EVAL_RETURN + IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K%=A1%:V%=R%:GOSUB ENV_SET + K=A1%:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% + X=X+1:S%(X)=A2%: REM push/save A2% REM create new environment with outer as current environment - EO%=E%:GOSUB ENV_NEW - E%=R% + O=E:GOSUB ENV_NEW + E=R EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + X=X+1:S%(X)=A1%: REM push A1% REM eval current A1 odd element - A%=Z%(A1%,1)+1:GOSUB EVAL - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + A=Z%(A1%,1)+1:GOSUB EVAL + A1%=S%(X):X=X-1: REM pop A1% REM set environment: even A1% key to odd A1% eval'd above - K%=A1%+1:V%=R%:GOSUB ENV_SET - AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership + K=A1%+1:V=R:GOSUB ENV_SET + AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% - A%=A2%:GOSUB EVAL: REM eval a2 using let_env + A2%=S%(X):X=X-1: REM pop A2% + A=A2%:GOSUB EVAL: REM eval a2 using let_env GOTO EVAL_RETURN EVAL_INVOKE: GOSUB EVAL_AST - R3%=R% + R3=R REM if error, return f/args for release by caller - IF ER%<>-2 THEN GOTO EVAL_RETURN - F%=R%+1 + IF ER<>-2 THEN GOTO EVAL_RETURN + F=R+1 - AR%=Z%(R%,1): REM rest - R%=F%:GOSUB DEREF_R:F%=R% - IF (Z%(F%,0)AND15)<>9 THEN ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN + AR=Z%(R,1): REM rest + R=F:GOSUB DEREF_R:F=R + IF (Z%(F,0)AND15)<>9 THEN ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION - AY%=R3%:GOSUB RELEASE + AY=R3:GOSUB RELEASE GOTO EVAL_RETURN EVAL_RETURN: - REM AZ%=R%: PR%=1: GOSUB PR_STR - REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + REM AZ=R: PR=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE + IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE - LV%=LV%-1: REM track basic return stack level + LV=LV-1: REM track basic return stack level REM trigger GC TA%=FRE(0) - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 RETURN -REM DO_FUNCTION(F%, AR%) +REM DO_FUNCTION(F, AR) DO_FUNCTION: - AZ%=F%:GOSUB PR_STR + AZ=F:GOSUB PR_STR F$=R$ - AZ%=AR%:GOSUB PR_STR + AZ=AR:GOSUB PR_STR AR$=R$ REM Get the function number - FF%=Z%(F%,1) + FF=Z%(F,1) REM Get argument values - R%=AR%+1:GOSUB DEREF_R:AA%=Z%(R%,1) - R%=Z%(AR%,1)+1:GOSUB DEREF_R:AB%=Z%(R%,1) + R=AR+1:GOSUB DEREF_R:AA=Z%(R,1) + R=Z%(AR,1)+1:GOSUB DEREF_R:AB=Z%(R,1) REM Allocate the return value - SZ%=1:GOSUB ALLOC + SZ=1:GOSUB ALLOC REM Switch on the function number - IF FF%=1 THEN GOTO DO_ADD - IF FF%=2 THEN GOTO DO_SUB - IF FF%=3 THEN GOTO DO_MULT - IF FF%=4 THEN GOTO DO_DIV - ER%=-1:ER$="unknown function"+STR$(FF%):RETURN + IF FF=1 THEN GOTO DO_ADD + IF FF=2 THEN GOTO DO_SUB + IF FF=3 THEN GOTO DO_MULT + IF FF=4 THEN GOTO DO_DIV + ER=-1:ER$="unknown function"+STR$(FF):RETURN DO_ADD: - Z%(R%,0)=2+16 - Z%(R%,1)=AA%+AB% + Z%(R,0)=2+16 + Z%(R,1)=AA+AB GOTO DO_FUNCTION_DONE DO_SUB: - Z%(R%,0)=2+16 - Z%(R%,1)=AA%-AB% + Z%(R,0)=2+16 + Z%(R,1)=AA-AB GOTO DO_FUNCTION_DONE DO_MULT: - Z%(R%,0)=2+16 - Z%(R%,1)=AA%*AB% + Z%(R,0)=2+16 + Z%(R,1)=AA*AB GOTO DO_FUNCTION_DONE DO_DIV: - Z%(R%,0)=2+16 - Z%(R%,1)=AA%/AB% + Z%(R,0)=2+16 + Z%(R,1)=AA/AB GOTO DO_FUNCTION_DONE DO_FUNCTION_DONE: RETURN -REM PRINT(A%) -> R$ +REM PRINT(A) -> R$ MAL_PRINT: - AZ%=A%:PR%=1:GOSUB PR_STR + AZ=A:PR=1:GOSUB PR_STR RETURN REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - R1%=0:R2%=0 + R1=0:R2=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL - R2%=R% - IF ER%<>-2 THEN GOTO REP_DONE + A=R:E=RE%:GOSUB EVAL + R2=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:GOSUB MAL_PRINT + A=R:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R2<>0 THEN AY=R2:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ RETURN @@ -311,29 +311,29 @@ REM MAIN program MAIN: GOSUB INIT_MEMORY - LV%=0 + LV=0 REM create repl_env - EO%=-1:GOSUB ENV_NEW:RE%=R% + O=-1:GOSUB ENV_NEW:RE%=R - E%=RE% + E=RE% REM + function - A%=1:GOSUB NATIVE_FUNCTION - K$="+":V%=R%:GOSUB ENV_SET_S + A=1:GOSUB NATIVE_FUNCTION + K$="+":V=R:GOSUB ENV_SET_S REM - function - A%=2:GOSUB NATIVE_FUNCTION - K$="-":V%=R%:GOSUB ENV_SET_S + A=2:GOSUB NATIVE_FUNCTION + K$="-":V=R:GOSUB ENV_SET_S REM * function - A%=3:GOSUB NATIVE_FUNCTION - K$="*":V%=R%:GOSUB ENV_SET_S + A=3:GOSUB NATIVE_FUNCTION + K$="*":V=R:GOSUB ENV_SET_S REM / function - A%=4:GOSUB NATIVE_FUNCTION - K$="/":V%=R%:GOSUB ENV_SET_S + A=4:GOSUB NATIVE_FUNCTION + K$="/":V=R:GOSUB ENV_SET_S - ZT%=ZI%: REM top of memory after base repl_env + ZT%=ZI: REM top of memory after base repl_env REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser @@ -341,7 +341,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -352,6 +352,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=-2:ER$="" + ER=-2:ER$="" RETURN diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index 4dd86f3dd7..159e96e368 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -9,136 +9,136 @@ REM $INCLUDE: 'core.in.bas' REM $INCLUDE: 'debug.in.bas' -REM READ(A$) -> R% +REM READ(A$) -> R MAL_READ: GOSUB READ_STR RETURN -REM EVAL_AST(A%, E%) -> R% +REM EVAL_AST(A, E) -> R REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A - IF ER%<>-2 THEN GOTO EVAL_AST_RETURN + IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T%=Z%(A%,0)AND15 - IF T%=5 THEN GOTO EVAL_AST_SYMBOL - IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ + T=Z%(A,0)AND15 + IF T=5 THEN GOTO EVAL_AST_SYMBOL + IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R%=A%:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=A:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K%=A%:GOSUB ENV_GET + K=A:GOSUB ENV_GET GOTO EVAL_AST_RETURN EVAL_AST_SEQ: REM allocate the first entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM make space on the stack - ZL%=ZL%+4 + X=X+4 REM push type of sequence - ZZ%(ZL%-3)=T% + S%(X-3)=T REM push sequence index - ZZ%(ZL%-2)=-1 + S%(X-2)=-1 REM push future return value (new sequence) - ZZ%(ZL%-1)=R% + S%(X-1)=R REM push previous new sequence entry - ZZ%(ZL%)=R% + S%(X)=R EVAL_AST_SEQ_LOOP: REM set new sequence entry type (with 1 ref cnt) - Z%(R%,0)=ZZ%(ZL%-3)+16 - Z%(R%,1)=0 + Z%(R,0)=S%(X-3)+16 + Z%(R,1)=0 REM create value ptr placeholder - Z%(R%+1,0)=14 - Z%(R%+1,1)=0 + Z%(R+1,0)=14 + Z%(R+1,1)=0 REM update index - ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + S%(X-2)=S%(X-2)+1 REM check if we are done evaluating the source sequence - IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: - R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + R=A+1:GOSUB DEREF_R: REM deref to target of referred entry + Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: REM call EVAL for each entry - A%=A%+1:GOSUB EVAL - A%=A%-1 + A=A+1:GOSUB EVAL + A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(ZZ%(ZL%)+1,1)=R% + Z%(S%(X)+1,1)=R - IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(ZZ%(ZL%),1)=R% + Z%(S%(X),1)=R REM update previous ptr to current entry - ZZ%(ZL%)=R% + S%(X)=R REM process the next sequence entry from source list - A%=Z%(A%,1) + A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER%=-2 THEN R%=ZZ%(ZL%-1) + IF ER=-2 THEN R=S%(X-1) REM otherwise, free the return value and return nil - IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE REM pop previous, return, index and type - ZL%=ZL%-4 + X=X-4 GOTO EVAL_AST_RETURN EVAL_AST_RETURN: - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 REM pop EVAL AST return label/address - RN%=ZZ%(ZL%):ZL%=ZL%-1 + RN%=S%(X):X=X-1 ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN -REM EVAL(A%, E%)) -> R% +REM EVAL(A, E)) -> R EVAL: - LV%=LV%+1: REM track basic return stack level + LV=LV+1: REM track basic return stack level - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A EVAL_TCO_RECUR: - REM AZ%=A%:PR%=1:GOSUB PR_STR - REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" + REM AZ=A:PR=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A GOSUB LIST_Q - IF R% THEN GOTO APPLY_LIST + IF R THEN GOTO APPLY_LIST REM ELSE REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=1 + X=X+1:S%(X)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -146,14 +146,14 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN - A0%=A%+1 - R%=A0%:GOSUB DEREF_R:A0%=R% + A0%=A+1 + R=A0%:GOSUB DEREF_R:A0%=R REM get symbol in A$ IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) + IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -163,159 +163,159 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A%,1),1),1)+1 - R%=A3%:GOSUB DEREF_R:A3%=R% + A3%=Z%(Z%(Z%(A,1),1),1)+1 + R=A3%:GOSUB DEREF_R:A3%=R EVAL_GET_A2: - A2%=Z%(Z%(A%,1),1)+1 - R%=A2%:GOSUB DEREF_R:A2%=R% + A2%=Z%(Z%(A,1),1)+1 + R=A2%:GOSUB DEREF_R:A2%=R EVAL_GET_A1: - A1%=Z%(A%,1)+1 - R%=A1%:GOSUB DEREF_R:A1%=R% + A1%=Z%(A,1)+1 + R=A1%:GOSUB DEREF_R:A1%=R RETURN EVAL_DEF: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% - A%=A2%:GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + X=X+1:S%(X)=A1%: REM push A1% + A=A2%:GOSUB EVAL: REM eval a2 + A1%=S%(X):X=X-1: REM pop A1% - IF ER%<>-2 THEN GOTO EVAL_RETURN + IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K%=A1%:V%=R%:GOSUB ENV_SET + K=A1%:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% + X=X+1:S%(X)=A2%: REM push/save A2% REM create new environment with outer as current environment - EO%=E%:GOSUB ENV_NEW - E%=R% + O=E:GOSUB ENV_NEW + E=R EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + X=X+1:S%(X)=A1%: REM push A1% REM eval current A1 odd element - A%=Z%(A1%,1)+1:GOSUB EVAL - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + A=Z%(A1%,1)+1:GOSUB EVAL + A1%=S%(X):X=X-1: REM pop A1% REM set environment: even A1% key to odd A1% eval'd above - K%=A1%+1:V%=R%:GOSUB ENV_SET - AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership + K=A1%+1:V=R:GOSUB ENV_SET + AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% - A%=A2%:GOSUB EVAL: REM eval a2 using let_env + A2%=S%(X):X=X-1: REM pop A2% + A=A2%:GOSUB EVAL: REM eval a2 using let_env GOTO EVAL_RETURN EVAL_DO: - A%=Z%(A%,1): REM rest + A=Z%(A,1): REM rest REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=2 + X=X+1:S%(X)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push eval'd list - A%=R%:GOSUB LAST: REM return the last element - AY%=ZZ%(ZL%):ZL%=ZL%-1: REM pop eval'd list + X=X+1:S%(X)=R: REM push eval'd list + A=R:GOSUB LAST: REM return the last element + AY=S%(X):X=X-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_IF: GOSUB EVAL_GET_A1: REM set a1% - REM push A% - ZL%=ZL%+1:ZZ%(ZL%)=A% - A%=A1%:GOSUB EVAL - REM pop A% - A%=ZZ%(ZL%):ZL%=ZL%-1 - IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE + REM push A + X=X+1:S%(X)=A + A=A1%:GOSUB EVAL + REM pop A + A=S%(X):X=X-1 + IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE REM if no false case (A3%), return nil - IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0:GOTO EVAL_RETURN + IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A%=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set a1% and a2% - A%=A2%:P%=A1%:GOSUB MAL_FUNCTION + A=A2%:P=A1%:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=3 + X=X+1:S%(X)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: REM if error, return f/args for release by caller - IF ER%<>-2 THEN GOTO EVAL_RETURN + IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - ZL%=ZL%+1:ZZ%(ZL%)=R% + X=X+1:S%(X)=R - F%=R%+1 + F=R+1 - AR%=Z%(R%,1): REM rest - R%=F%:GOSUB DEREF_R:F%=R% + AR=Z%(R,1): REM rest + R=F:GOSUB DEREF_R:F=R - IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R%=ZZ%(ZL%):ZL%=ZL%-1 - ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN + R=S%(X):X=X-1 + ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4%=E%: REM save the current environment for release + E4%=E: REM save the current environment for release REM create new environ using env stored with function - EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the - REM stack (ZZ%(ZL%-2)) because our new env refers to it and + REM stack (S%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE REM claim the AST before releasing the list containing it - A%=Z%(F%,1):Z%(A%,0)=Z%(A%,0)+16 + A=Z%(F,1):Z%(A,0)=Z%(A,0)+16 REM add AST to pending release queue to free as soon as EVAL - REM actually returns (LV%+1) - ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV%+1 + REM actually returns (LV+1) + ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV+1 REM pop and release f/args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE - REM A% set above - E%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + REM A set above + E=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: - REM AZ%=R%: PR%=1: GOSUB PR_STR - REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + REM AZ=R: PR=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE + IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE - LV%=LV%-1: REM track basic return stack level + LV=LV-1: REM track basic return stack level REM release everything we couldn't release earlier GOSUB RELEASE_PEND @@ -323,51 +323,51 @@ EVAL: REM trigger GC TA%=FRE(0) - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 RETURN -REM PRINT(A%) -> R$ +REM PRINT(A) -> R$ MAL_PRINT: - AZ%=A%:PR%=1:GOSUB PR_STR + AZ=A:PR=1:GOSUB PR_STR RETURN -REM RE(A$) -> R% +REM RE(A$) -> R REM Assume RE% has repl_env REM caller must release result RE: - R1%=0 + R1=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL + A=R:E=RE%:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - R1%=0:R2%=0 + R1=0:R2=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL - R2%=R% - IF ER%<>-2 THEN GOTO REP_DONE + A=R:E=RE%:GOSUB EVAL + R2=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:GOSUB MAL_PRINT + A=R:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R2<>0 THEN AY=R2:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ RETURN @@ -375,19 +375,19 @@ REM MAIN program MAIN: GOSUB INIT_MEMORY - LV%=0 + LV=0 REM create repl_env - EO%=-1:GOSUB ENV_NEW:RE%=R% + O=-1:GOSUB ENV_NEW:RE%=R REM core.EXT: defined in Basic - E%=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + E=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env - ZT%=ZI%: REM top of memory after base repl_env + ZT%=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself A$="(def! not (fn* (a) (if a false true)))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser @@ -395,7 +395,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -406,6 +406,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=-2:ER$="" + ER=-2:ER$="" RETURN diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index 1291befd2c..cc45c43085 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -9,136 +9,136 @@ REM $INCLUDE: 'core.in.bas' REM $INCLUDE: 'debug.in.bas' -REM READ(A$) -> R% +REM READ(A$) -> R MAL_READ: GOSUB READ_STR RETURN -REM EVAL_AST(A%, E%) -> R% +REM EVAL_AST(A, E) -> R REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A - IF ER%<>-2 THEN GOTO EVAL_AST_RETURN + IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T%=Z%(A%,0)AND15 - IF T%=5 THEN GOTO EVAL_AST_SYMBOL - IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ + T=Z%(A,0)AND15 + IF T=5 THEN GOTO EVAL_AST_SYMBOL + IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R%=A%:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=A:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K%=A%:GOSUB ENV_GET + K=A:GOSUB ENV_GET GOTO EVAL_AST_RETURN EVAL_AST_SEQ: REM allocate the first entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM make space on the stack - ZL%=ZL%+4 + X=X+4 REM push type of sequence - ZZ%(ZL%-3)=T% + S%(X-3)=T REM push sequence index - ZZ%(ZL%-2)=-1 + S%(X-2)=-1 REM push future return value (new sequence) - ZZ%(ZL%-1)=R% + S%(X-1)=R REM push previous new sequence entry - ZZ%(ZL%)=R% + S%(X)=R EVAL_AST_SEQ_LOOP: REM set new sequence entry type (with 1 ref cnt) - Z%(R%,0)=ZZ%(ZL%-3)+16 - Z%(R%,1)=0 + Z%(R,0)=S%(X-3)+16 + Z%(R,1)=0 REM create value ptr placeholder - Z%(R%+1,0)=14 - Z%(R%+1,1)=0 + Z%(R+1,0)=14 + Z%(R+1,1)=0 REM update index - ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + S%(X-2)=S%(X-2)+1 REM check if we are done evaluating the source sequence - IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: - R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + R=A+1:GOSUB DEREF_R: REM deref to target of referred entry + Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: REM call EVAL for each entry - A%=A%+1:GOSUB EVAL - A%=A%-1 + A=A+1:GOSUB EVAL + A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(ZZ%(ZL%)+1,1)=R% + Z%(S%(X)+1,1)=R - IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(ZZ%(ZL%),1)=R% + Z%(S%(X),1)=R REM update previous ptr to current entry - ZZ%(ZL%)=R% + S%(X)=R REM process the next sequence entry from source list - A%=Z%(A%,1) + A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER%=-2 THEN R%=ZZ%(ZL%-1) + IF ER=-2 THEN R=S%(X-1) REM otherwise, free the return value and return nil - IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE REM pop previous, return, index and type - ZL%=ZL%-4 + X=X-4 GOTO EVAL_AST_RETURN EVAL_AST_RETURN: - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 REM pop EVAL AST return label/address - RN%=ZZ%(ZL%):ZL%=ZL%-1 + RN%=S%(X):X=X-1 ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN -REM EVAL(A%, E%)) -> R% +REM EVAL(A, E)) -> R EVAL: - LV%=LV%+1: REM track basic return stack level + LV=LV+1: REM track basic return stack level - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A EVAL_TCO_RECUR: - REM AZ%=A%:PR%=1:GOSUB PR_STR - REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" + REM AZ=A:PR=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A GOSUB LIST_Q - IF R% THEN GOTO APPLY_LIST + IF R THEN GOTO APPLY_LIST REM ELSE REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=1 + X=X+1:S%(X)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -146,14 +146,14 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN - A0%=A%+1 - R%=A0%:GOSUB DEREF_R:A0%=R% + A0%=A+1 + R=A0%:GOSUB DEREF_R:A0%=R REM get symbol in A$ IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) + IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -163,168 +163,168 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A%,1),1),1)+1 - R%=A3%:GOSUB DEREF_R:A3%=R% + A3%=Z%(Z%(Z%(A,1),1),1)+1 + R=A3%:GOSUB DEREF_R:A3%=R EVAL_GET_A2: - A2%=Z%(Z%(A%,1),1)+1 - R%=A2%:GOSUB DEREF_R:A2%=R% + A2%=Z%(Z%(A,1),1)+1 + R=A2%:GOSUB DEREF_R:A2%=R EVAL_GET_A1: - A1%=Z%(A%,1)+1 - R%=A1%:GOSUB DEREF_R:A1%=R% + A1%=Z%(A,1)+1 + R=A1%:GOSUB DEREF_R:A1%=R RETURN EVAL_DEF: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% - A%=A2%:GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + X=X+1:S%(X)=A1%: REM push A1% + A=A2%:GOSUB EVAL: REM eval a2 + A1%=S%(X):X=X-1: REM pop A1% - IF ER%<>-2 THEN GOTO EVAL_RETURN + IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K%=A1%:V%=R%:GOSUB ENV_SET + K=A1%:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% - ZL%=ZL%+1:ZZ%(ZL%)=E%: REM push env for for later release + X=X+1:S%(X)=A2%: REM push/save A2% + X=X+1:S%(X)=E: REM push env for for later release REM create new environment with outer as current environment - EO%=E%:GOSUB ENV_NEW - E%=R% + O=E:GOSUB ENV_NEW + E=R EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + X=X+1:S%(X)=A1%: REM push A1% REM eval current A1 odd element - A%=Z%(A1%,1)+1:GOSUB EVAL - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + A=Z%(A1%,1)+1:GOSUB EVAL + A1%=S%(X):X=X-1: REM pop A1% REM set environment: even A1% key to odd A1% eval'd above - K%=A1%+1:V%=R%:GOSUB ENV_SET - AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership + K=A1%+1:V=R:GOSUB ENV_SET + AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4%=ZZ%(ZL%):ZL%=ZL%-1: REM pop previous env + E4%=S%(X):X=X-1: REM pop previous env REM release previous environment if not the current EVAL env - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE - A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% - A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A2%=S%(X):X=X-1: REM pop A2% + A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: - A%=Z%(A%,1): REM rest + A=Z%(A,1): REM rest REM TODO: TCO REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=2 + X=X+1:S%(X)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push eval'd list - A%=R%:GOSUB LAST: REM return the last element - AY%=ZZ%(ZL%):ZL%=ZL%-1: REM pop eval'd list + X=X+1:S%(X)=R: REM push eval'd list + A=R:GOSUB LAST: REM return the last element + AY=S%(X):X=X-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_IF: GOSUB EVAL_GET_A1: REM set a1% - REM push A% - ZL%=ZL%+1:ZZ%(ZL%)=A% - A%=A1%:GOSUB EVAL - REM pop A% - A%=ZZ%(ZL%):ZL%=ZL%-1 - IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE + REM push A + X=X+1:S%(X)=A + A=A1%:GOSUB EVAL + REM pop A + A=S%(X):X=X-1 + IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE REM if no false case (A3%), return nil - IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0:GOTO EVAL_RETURN + IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A%=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set a1% and a2% - A%=A2%:P%=A1%:GOSUB MAL_FUNCTION + A=A2%:P=A1%:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=3 + X=X+1:S%(X)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: REM if error, return f/args for release by caller - IF ER%<>-2 THEN GOTO EVAL_RETURN + IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - ZL%=ZL%+1:ZZ%(ZL%)=R% + X=X+1:S%(X)=R - F%=R%+1 + F=R+1 - AR%=Z%(R%,1): REM rest - R%=F%:GOSUB DEREF_R:F%=R% + AR=Z%(R,1): REM rest + R=F:GOSUB DEREF_R:F=R - IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R%=ZZ%(ZL%):ZL%=ZL%-1 - ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN + R=S%(X):X=X-1 + ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4%=E%: REM save the current environment for release + E4%=E: REM save the current environment for release REM create new environ using env stored with function - EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the - REM stack (ZZ%(ZL%-2)) because our new env refers to it and + REM stack (S%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE REM claim the AST before releasing the list containing it - A%=Z%(F%,1):Z%(A%,0)=Z%(A%,0)+16 + A=Z%(F,1):Z%(A,0)=Z%(A,0)+16 REM add AST to pending release queue to free as soon as EVAL - REM actually returns (LV%+1) - ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV%+1 + REM actually returns (LV+1) + ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV+1 REM pop and release f/args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE - REM A% set above - E%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + REM A set above + E=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: - REM AZ%=R%: PR%=1: GOSUB PR_STR - REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + REM AZ=R: PR=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE + IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE - LV%=LV%-1: REM track basic return stack level + LV=LV-1: REM track basic return stack level REM release everything we couldn't release earlier GOSUB RELEASE_PEND @@ -332,51 +332,51 @@ EVAL: REM trigger GC TA%=FRE(0) - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 RETURN -REM PRINT(A%) -> R$ +REM PRINT(A) -> R$ MAL_PRINT: - AZ%=A%:PR%=1:GOSUB PR_STR + AZ=A:PR=1:GOSUB PR_STR RETURN -REM RE(A$) -> R% +REM RE(A$) -> R REM Assume RE% has repl_env REM caller must release result RE: - R1%=0 + R1=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL + A=R:E=RE%:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - R1%=0:R2%=0 + R1=0:R2=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL - R2%=R% - IF ER%<>-2 THEN GOTO REP_DONE + A=R:E=RE%:GOSUB EVAL + R2=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:GOSUB MAL_PRINT + A=R:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R2<>0 THEN AY=R2:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ RETURN @@ -384,19 +384,19 @@ REM MAIN program MAIN: GOSUB INIT_MEMORY - LV%=0 + LV=0 REM create repl_env - EO%=-1:GOSUB ENV_NEW:RE%=R% + O=-1:GOSUB ENV_NEW:RE%=R REM core.EXT: defined in Basic - E%=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + E=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env - ZT%=ZI%: REM top of memory after base repl_env + ZT%=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself A$="(def! not (fn* (a) (if a false true)))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser @@ -404,7 +404,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -415,6 +415,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=-2:ER$="" + ER=-2:ER$="" RETURN diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index d75b86ac32..7b1cb419b0 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -9,136 +9,136 @@ REM $INCLUDE: 'core.in.bas' REM $INCLUDE: 'debug.in.bas' -REM READ(A$) -> R% +REM READ(A$) -> R MAL_READ: GOSUB READ_STR RETURN -REM EVAL_AST(A%, E%) -> R% +REM EVAL_AST(A, E) -> R REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A - IF ER%<>-2 THEN GOTO EVAL_AST_RETURN + IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T%=Z%(A%,0)AND15 - IF T%=5 THEN GOTO EVAL_AST_SYMBOL - IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ + T=Z%(A,0)AND15 + IF T=5 THEN GOTO EVAL_AST_SYMBOL + IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R%=A%:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=A:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K%=A%:GOSUB ENV_GET + K=A:GOSUB ENV_GET GOTO EVAL_AST_RETURN EVAL_AST_SEQ: REM allocate the first entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM make space on the stack - ZL%=ZL%+4 + X=X+4 REM push type of sequence - ZZ%(ZL%-3)=T% + S%(X-3)=T REM push sequence index - ZZ%(ZL%-2)=-1 + S%(X-2)=-1 REM push future return value (new sequence) - ZZ%(ZL%-1)=R% + S%(X-1)=R REM push previous new sequence entry - ZZ%(ZL%)=R% + S%(X)=R EVAL_AST_SEQ_LOOP: REM set new sequence entry type (with 1 ref cnt) - Z%(R%,0)=ZZ%(ZL%-3)+16 - Z%(R%,1)=0 + Z%(R,0)=S%(X-3)+16 + Z%(R,1)=0 REM create value ptr placeholder - Z%(R%+1,0)=14 - Z%(R%+1,1)=0 + Z%(R+1,0)=14 + Z%(R+1,1)=0 REM update index - ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + S%(X-2)=S%(X-2)+1 REM check if we are done evaluating the source sequence - IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: - R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + R=A+1:GOSUB DEREF_R: REM deref to target of referred entry + Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: REM call EVAL for each entry - A%=A%+1:GOSUB EVAL - A%=A%-1 + A=A+1:GOSUB EVAL + A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(ZZ%(ZL%)+1,1)=R% + Z%(S%(X)+1,1)=R - IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(ZZ%(ZL%),1)=R% + Z%(S%(X),1)=R REM update previous ptr to current entry - ZZ%(ZL%)=R% + S%(X)=R REM process the next sequence entry from source list - A%=Z%(A%,1) + A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER%=-2 THEN R%=ZZ%(ZL%-1) + IF ER=-2 THEN R=S%(X-1) REM otherwise, free the return value and return nil - IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE REM pop previous, return, index and type - ZL%=ZL%-4 + X=X-4 GOTO EVAL_AST_RETURN EVAL_AST_RETURN: - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 REM pop EVAL AST return label/address - RN%=ZZ%(ZL%):ZL%=ZL%-1 + RN%=S%(X):X=X-1 ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN -REM EVAL(A%, E%)) -> R% +REM EVAL(A, E)) -> R EVAL: - LV%=LV%+1: REM track basic return stack level + LV=LV+1: REM track basic return stack level - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A EVAL_TCO_RECUR: - REM AZ%=A%:PR%=1:GOSUB PR_STR - REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" + REM AZ=A:PR=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A GOSUB LIST_Q - IF R% THEN GOTO APPLY_LIST + IF R THEN GOTO APPLY_LIST REM ELSE REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=1 + X=X+1:S%(X)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -146,14 +146,14 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN - A0%=A%+1 - R%=A0%:GOSUB DEREF_R:A0%=R% + A0%=A+1 + R=A0%:GOSUB DEREF_R:A0%=R REM get symbol in A$ IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) + IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -163,168 +163,168 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A%,1),1),1)+1 - R%=A3%:GOSUB DEREF_R:A3%=R% + A3%=Z%(Z%(Z%(A,1),1),1)+1 + R=A3%:GOSUB DEREF_R:A3%=R EVAL_GET_A2: - A2%=Z%(Z%(A%,1),1)+1 - R%=A2%:GOSUB DEREF_R:A2%=R% + A2%=Z%(Z%(A,1),1)+1 + R=A2%:GOSUB DEREF_R:A2%=R EVAL_GET_A1: - A1%=Z%(A%,1)+1 - R%=A1%:GOSUB DEREF_R:A1%=R% + A1%=Z%(A,1)+1 + R=A1%:GOSUB DEREF_R:A1%=R RETURN EVAL_DEF: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% - A%=A2%:GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + X=X+1:S%(X)=A1%: REM push A1% + A=A2%:GOSUB EVAL: REM eval a2 + A1%=S%(X):X=X-1: REM pop A1% - IF ER%<>-2 THEN GOTO EVAL_RETURN + IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K%=A1%:V%=R%:GOSUB ENV_SET + K=A1%:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% - ZL%=ZL%+1:ZZ%(ZL%)=E%: REM push env for for later release + X=X+1:S%(X)=A2%: REM push/save A2% + X=X+1:S%(X)=E: REM push env for for later release REM create new environment with outer as current environment - EO%=E%:GOSUB ENV_NEW - E%=R% + O=E:GOSUB ENV_NEW + E=R EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + X=X+1:S%(X)=A1%: REM push A1% REM eval current A1 odd element - A%=Z%(A1%,1)+1:GOSUB EVAL - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + A=Z%(A1%,1)+1:GOSUB EVAL + A1%=S%(X):X=X-1: REM pop A1% REM set environment: even A1% key to odd A1% eval'd above - K%=A1%+1:V%=R%:GOSUB ENV_SET - AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership + K=A1%+1:V=R:GOSUB ENV_SET + AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4%=ZZ%(ZL%):ZL%=ZL%-1: REM pop previous env + E4%=S%(X):X=X-1: REM pop previous env REM release previous environment if not the current EVAL env - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE - A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% - A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A2%=S%(X):X=X-1: REM pop A2% + A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: - A%=Z%(A%,1): REM rest + A=Z%(A,1): REM rest REM TODO: TCO REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=2 + X=X+1:S%(X)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push eval'd list - A%=R%:GOSUB LAST: REM return the last element - AY%=ZZ%(ZL%):ZL%=ZL%-1: REM pop eval'd list + X=X+1:S%(X)=R: REM push eval'd list + A=R:GOSUB LAST: REM return the last element + AY=S%(X):X=X-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_IF: GOSUB EVAL_GET_A1: REM set a1% - REM push A% - ZL%=ZL%+1:ZZ%(ZL%)=A% - A%=A1%:GOSUB EVAL - REM pop A% - A%=ZZ%(ZL%):ZL%=ZL%-1 - IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE + REM push A + X=X+1:S%(X)=A + A=A1%:GOSUB EVAL + REM pop A + A=S%(X):X=X-1 + IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE REM if no false case (A3%), return nil - IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0:GOTO EVAL_RETURN + IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A%=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set a1% and a2% - A%=A2%:P%=A1%:GOSUB MAL_FUNCTION + A=A2%:P=A1%:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=3 + X=X+1:S%(X)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: REM if error, return f/args for release by caller - IF ER%<>-2 THEN GOTO EVAL_RETURN + IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - ZL%=ZL%+1:ZZ%(ZL%)=R% + X=X+1:S%(X)=R - F%=R%+1 + F=R+1 - AR%=Z%(R%,1): REM rest - R%=F%:GOSUB DEREF_R:F%=R% + AR=Z%(R,1): REM rest + R=F:GOSUB DEREF_R:F=R - IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R%=ZZ%(ZL%):ZL%=ZL%-1 - ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN + R=S%(X):X=X-1 + ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4%=E%: REM save the current environment for release + E4%=E: REM save the current environment for release REM create new environ using env stored with function - EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the - REM stack (ZZ%(ZL%-2)) because our new env refers to it and + REM stack (S%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE REM claim the AST before releasing the list containing it - A%=Z%(F%,1):Z%(A%,0)=Z%(A%,0)+16 + A=Z%(F,1):Z%(A,0)=Z%(A,0)+16 REM add AST to pending release queue to free as soon as EVAL - REM actually returns (LV%+1) - ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV%+1 + REM actually returns (LV+1) + ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV+1 REM pop and release f/args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE - REM A% set above - E%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + REM A set above + E=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: - REM AZ%=R%: PR%=1: GOSUB PR_STR - REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + REM AZ=R: PR=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE + IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE - LV%=LV%-1: REM track basic return stack level + LV=LV-1: REM track basic return stack level REM release everything we couldn't release earlier GOSUB RELEASE_PEND @@ -332,51 +332,51 @@ EVAL: REM trigger GC TA%=FRE(0) - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 RETURN -REM PRINT(A%) -> R$ +REM PRINT(A) -> R$ MAL_PRINT: - AZ%=A%:PR%=1:GOSUB PR_STR + AZ=A:PR=1:GOSUB PR_STR RETURN -REM RE(A$) -> R% +REM RE(A$) -> R REM Assume RE% has repl_env REM caller must release result RE: - R1%=0 + R1=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL + A=R:E=RE%:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - R1%=0:R2%=0 + R1=0:R2=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL - R2%=R% - IF ER%<>-2 THEN GOTO REP_DONE + A=R:E=RE%:GOSUB EVAL + R2=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:GOSUB MAL_PRINT + A=R:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R2<>0 THEN AY=R2:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ RETURN @@ -384,46 +384,46 @@ REM MAIN program MAIN: GOSUB INIT_MEMORY - LV%=0 + LV=0 REM create repl_env - EO%=-1:GOSUB ENV_NEW:RE%=R% + O=-1:GOSUB ENV_NEW:RE%=R REM core.EXT: defined in Basic - E%=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + E=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env - ZT%=ZI%: REM top of memory after base repl_env + ZT%=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself A$="(def! not (fn* (a) (if a false true)))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE A$="(def! load-file (fn* (f) (eval (read-string (str " A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REM load the args file A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REM set the argument list A$="(def! *ARGV* (rest -*ARGS*-))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REM get the first argument A$="(first -*ARGS*-)" GOSUB RE REM if there is an argument, then run it as a program - IF R%<>0 THEN AY%=R%:GOSUB RELEASE:GOTO RUN_PROG + IF R<>0 THEN AY=R:GOSUB RELEASE:GOTO RUN_PROG REM no arguments, start REPL loop - IF R%=0 THEN GOTO REPL_LOOP + IF R=0 THEN GOTO REPL_LOOP RUN_PROG: REM run a single mal program and exit A$="(load-file (first -*ARGS*-))" GOSUB RE - IF ER%<>-2 THEN GOSUB PRINT_ERROR + IF ER<>-2 THEN GOSUB PRINT_ERROR END REPL_LOOP: @@ -432,7 +432,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -443,6 +443,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=-2:ER$="" + ER=-2:ER$="" RETURN diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index 2c496e492a..c19f6edc93 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -11,207 +11,207 @@ REM $INCLUDE: 'core.in.bas' REM $INCLUDE: 'debug.in.bas' -REM READ(A$) -> R% +REM READ(A$) -> R MAL_READ: GOSUB READ_STR RETURN -REM PAIR_Q(B%) -> R% +REM PAIR_Q(B) -> R PAIR_Q: - R%=0 - IF (Z%(B%,0)AND15)<>6 AND (Z%(B%,0)AND15)<>7 THEN RETURN - IF (Z%(B%,1)=0) THEN RETURN - R%=1 + R=0 + IF (Z%(B,0)AND15)<>6 AND (Z%(B,0)AND15)<>7 THEN RETURN + IF (Z%(B,1)=0) THEN RETURN + R=1 RETURN -REM QUASIQUOTE(A%) -> R% +REM QUASIQUOTE(A) -> R QUASIQUOTE: - B%=A%:GOSUB PAIR_Q - IF R%=1 THEN GOTO QQ_UNQUOTE + B=A:GOSUB PAIR_Q + IF R=1 THEN GOTO QQ_UNQUOTE REM ['quote, ast] - AS$="quote":T%=5:GOSUB STRING - B2%=R%:B1%=A%:GOSUB LIST2 + AS$="quote":T=5:GOSUB STRING + B2%=R:B1%=A:GOSUB LIST2 RETURN QQ_UNQUOTE: - R%=A%+1:GOSUB DEREF_R - IF (Z%(R%,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE - IF ZS$(Z%(R%,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE + R=A+1:GOSUB DEREF_R + IF (Z%(R,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] - R%=Z%(A%,1)+1:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=Z%(A,1)+1:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 RETURN QQ_SPLICE_UNQUOTE: - REM push A% on the stack - ZL%=ZL%+1:ZZ%(ZL%)=A% + REM push A on the stack + X=X+1:S%(X)=A REM rest of cases call quasiquote on ast[1..] - A%=Z%(A%,1):GOSUB QUASIQUOTE:T6%=R% - REM pop A% off the stack - A%=ZZ%(ZL%):ZL%=ZL%-1 - - REM set A% to ast[0] for last two cases - A%=A%+1:GOSUB DEREF_A - - B%=A%:GOSUB PAIR_Q - IF R%=0 THEN GOTO QQ_DEFAULT - B%=A%+1:GOSUB DEREF_B - IF (Z%(B%,0)AND15)<>5 THEN GOTO QQ_DEFAULT - IF ZS$(Z%(B%,1))<>"splice-unquote" THEN QQ_DEFAULT + A=Z%(A,1):GOSUB QUASIQUOTE:T6=R + REM pop A off the stack + A=S%(X):X=X-1 + + REM set A to ast[0] for last two cases + A=A+1:GOSUB DEREF_A + + B=A:GOSUB PAIR_Q + IF R=0 THEN GOTO QQ_DEFAULT + B=A+1:GOSUB DEREF_B + IF (Z%(B,0)AND15)<>5 THEN GOTO QQ_DEFAULT + IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B%=Z%(A%,1)+1:GOSUB DEREF_B:B2%=B% - AS$="concat":T%=5:GOSUB STRING:B3%=R% - B1%=T6%:GOSUB LIST3 + B=Z%(A,1)+1:GOSUB DEREF_B:B2%=B + AS$="concat":T=5:GOSUB STRING:B3%=R + B1%=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY%=B1%:GOSUB RELEASE + AY=B1%:GOSUB RELEASE RETURN QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - REM push T6% on the stack - ZL%=ZL%+1:ZZ%(ZL%)=T6% - REM A% set above to ast[0] - GOSUB QUASIQUOTE:B2%=R% - REM pop T6% off the stack - T6%=ZZ%(ZL%):ZL%=ZL%-1 + REM push T6 on the stack + X=X+1:S%(X)=T6 + REM A set above to ast[0] + GOSUB QUASIQUOTE:B2%=R + REM pop T6 off the stack + T6=S%(X):X=X-1 - AS$="cons":T%=5:GOSUB STRING:B3%=R% - B1%=T6%:GOSUB LIST3 + AS$="cons":T=5:GOSUB STRING:B3%=R + B1%=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY%=B1%:GOSUB RELEASE - AY%=B2%:GOSUB RELEASE + AY=B1%:GOSUB RELEASE + AY=B2%:GOSUB RELEASE RETURN -REM EVAL_AST(A%, E%) -> R% +REM EVAL_AST(A, E) -> R REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A - IF ER%<>-2 THEN GOTO EVAL_AST_RETURN + IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T%=Z%(A%,0)AND15 - IF T%=5 THEN GOTO EVAL_AST_SYMBOL - IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ + T=Z%(A,0)AND15 + IF T=5 THEN GOTO EVAL_AST_SYMBOL + IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R%=A%:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=A:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K%=A%:GOSUB ENV_GET + K=A:GOSUB ENV_GET GOTO EVAL_AST_RETURN EVAL_AST_SEQ: REM allocate the first entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM make space on the stack - ZL%=ZL%+4 + X=X+4 REM push type of sequence - ZZ%(ZL%-3)=T% + S%(X-3)=T REM push sequence index - ZZ%(ZL%-2)=-1 + S%(X-2)=-1 REM push future return value (new sequence) - ZZ%(ZL%-1)=R% + S%(X-1)=R REM push previous new sequence entry - ZZ%(ZL%)=R% + S%(X)=R EVAL_AST_SEQ_LOOP: REM set new sequence entry type (with 1 ref cnt) - Z%(R%,0)=ZZ%(ZL%-3)+16 - Z%(R%,1)=0 + Z%(R,0)=S%(X-3)+16 + Z%(R,1)=0 REM create value ptr placeholder - Z%(R%+1,0)=14 - Z%(R%+1,1)=0 + Z%(R+1,0)=14 + Z%(R+1,1)=0 REM update index - ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + S%(X-2)=S%(X-2)+1 REM check if we are done evaluating the source sequence - IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: - R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + R=A+1:GOSUB DEREF_R: REM deref to target of referred entry + Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: REM call EVAL for each entry - A%=A%+1:GOSUB EVAL - A%=A%-1 + A=A+1:GOSUB EVAL + A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(ZZ%(ZL%)+1,1)=R% + Z%(S%(X)+1,1)=R - IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(ZZ%(ZL%),1)=R% + Z%(S%(X),1)=R REM update previous ptr to current entry - ZZ%(ZL%)=R% + S%(X)=R REM process the next sequence entry from source list - A%=Z%(A%,1) + A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER%=-2 THEN R%=ZZ%(ZL%-1) + IF ER=-2 THEN R=S%(X-1) REM otherwise, free the return value and return nil - IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE REM pop previous, return, index and type - ZL%=ZL%-4 + X=X-4 GOTO EVAL_AST_RETURN EVAL_AST_RETURN: - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 REM pop EVAL AST return label/address - RN%=ZZ%(ZL%):ZL%=ZL%-1 + RN%=S%(X):X=X-1 ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN -REM EVAL(A%, E%)) -> R% +REM EVAL(A, E)) -> R EVAL: - LV%=LV%+1: REM track basic return stack level + LV=LV+1: REM track basic return stack level - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A EVAL_TCO_RECUR: - REM AZ%=A%:PR%=1:GOSUB PR_STR - REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" + REM AZ=A:PR=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A GOSUB LIST_Q - IF R% THEN GOTO APPLY_LIST + IF R THEN GOTO APPLY_LIST REM ELSE REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=1 + X=X+1:S%(X)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -219,14 +219,14 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN - A0%=A%+1 - R%=A0%:GOSUB DEREF_R:A0%=R% + A0%=A+1 + R=A0%:GOSUB DEREF_R:A0%=R REM get symbol in A$ IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) + IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -238,182 +238,182 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A%,1),1),1)+1 - R%=A3%:GOSUB DEREF_R:A3%=R% + A3%=Z%(Z%(Z%(A,1),1),1)+1 + R=A3%:GOSUB DEREF_R:A3%=R EVAL_GET_A2: - A2%=Z%(Z%(A%,1),1)+1 - R%=A2%:GOSUB DEREF_R:A2%=R% + A2%=Z%(Z%(A,1),1)+1 + R=A2%:GOSUB DEREF_R:A2%=R EVAL_GET_A1: - A1%=Z%(A%,1)+1 - R%=A1%:GOSUB DEREF_R:A1%=R% + A1%=Z%(A,1)+1 + R=A1%:GOSUB DEREF_R:A1%=R RETURN EVAL_DEF: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% - A%=A2%:GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + X=X+1:S%(X)=A1%: REM push A1% + A=A2%:GOSUB EVAL: REM eval a2 + A1%=S%(X):X=X-1: REM pop A1% - IF ER%<>-2 THEN GOTO EVAL_RETURN + IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K%=A1%:V%=R%:GOSUB ENV_SET + K=A1%:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% - ZL%=ZL%+1:ZZ%(ZL%)=E%: REM push env for for later release + X=X+1:S%(X)=A2%: REM push/save A2% + X=X+1:S%(X)=E: REM push env for for later release REM create new environment with outer as current environment - EO%=E%:GOSUB ENV_NEW - E%=R% + O=E:GOSUB ENV_NEW + E=R EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + X=X+1:S%(X)=A1%: REM push A1% REM eval current A1 odd element - A%=Z%(A1%,1)+1:GOSUB EVAL - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + A=Z%(A1%,1)+1:GOSUB EVAL + A1%=S%(X):X=X-1: REM pop A1% REM set environment: even A1% key to odd A1% eval'd above - K%=A1%+1:V%=R%:GOSUB ENV_SET - AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership + K=A1%+1:V=R:GOSUB ENV_SET + AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4%=ZZ%(ZL%):ZL%=ZL%-1: REM pop previous env + E4%=S%(X):X=X-1: REM pop previous env REM release previous environment if not the current EVAL env - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE - A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% - A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A2%=S%(X):X=X-1: REM pop A2% + A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: - A%=Z%(A%,1): REM rest + A=Z%(A,1): REM rest REM TODO: TCO REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=2 + X=X+1:S%(X)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push eval'd list - A%=R%:GOSUB LAST: REM return the last element - AY%=ZZ%(ZL%):ZL%=ZL%-1: REM pop eval'd list + X=X+1:S%(X)=R: REM push eval'd list + A=R:GOSUB LAST: REM return the last element + AY=S%(X):X=X-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_QUOTE: - R%=Z%(A%,1)+1:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=Z%(A,1)+1:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_RETURN EVAL_QUASIQUOTE: - R%=Z%(A%,1)+1:GOSUB DEREF_R - A%=R%:GOSUB QUASIQUOTE + R=Z%(A,1)+1:GOSUB DEREF_R + A=R:GOSUB QUASIQUOTE REM add quasiquote result to pending release queue to free when - REM next lower EVAL level returns (LV%) - ZM%=ZM%+1:ZR%(ZM%,0)=R%:ZR%(ZM%,1)=LV% + REM next lower EVAL level returns (LV) + ZM%=ZM%+1:ZR%(ZM%,0)=R:ZR%(ZM%,1)=LV - A%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF: GOSUB EVAL_GET_A1: REM set a1% - REM push A% - ZL%=ZL%+1:ZZ%(ZL%)=A% - A%=A1%:GOSUB EVAL - REM pop A% - A%=ZZ%(ZL%):ZL%=ZL%-1 - IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE + REM push A + X=X+1:S%(X)=A + A=A1%:GOSUB EVAL + REM pop A + A=S%(X):X=X-1 + IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE REM if no false case (A3%), return nil - IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0:GOTO EVAL_RETURN + IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A%=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set a1% and a2% - A%=A2%:P%=A1%:GOSUB MAL_FUNCTION + A=A2%:P=A1%:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=3 + X=X+1:S%(X)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: REM if error, return f/args for release by caller - IF ER%<>-2 THEN GOTO EVAL_RETURN + IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - ZL%=ZL%+1:ZZ%(ZL%)=R% + X=X+1:S%(X)=R - F%=R%+1 + F=R+1 - AR%=Z%(R%,1): REM rest - R%=F%:GOSUB DEREF_R:F%=R% + AR=Z%(R,1): REM rest + R=F:GOSUB DEREF_R:F=R - IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R%=ZZ%(ZL%):ZL%=ZL%-1 - ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN + R=S%(X):X=X-1 + ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4%=E%: REM save the current environment for release + E4%=E: REM save the current environment for release REM create new environ using env stored with function - EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the - REM stack (ZZ%(ZL%-2)) because our new env refers to it and + REM stack (S%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE REM claim the AST before releasing the list containing it - A%=Z%(F%,1):Z%(A%,0)=Z%(A%,0)+16 + A=Z%(F,1):Z%(A,0)=Z%(A,0)+16 REM add AST to pending release queue to free as soon as EVAL - REM actually returns (LV%+1) - ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV%+1 + REM actually returns (LV+1) + ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV+1 REM pop and release f/args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE - REM A% set above - E%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + REM A set above + E=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: - REM AZ%=R%: PR%=1: GOSUB PR_STR - REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + REM AZ=R: PR=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE + IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE - LV%=LV%-1: REM track basic return stack level + LV=LV-1: REM track basic return stack level REM release everything we couldn't release earlier GOSUB RELEASE_PEND @@ -421,51 +421,51 @@ EVAL: REM trigger GC TA%=FRE(0) - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 RETURN -REM PRINT(A%) -> R$ +REM PRINT(A) -> R$ MAL_PRINT: - AZ%=A%:PR%=1:GOSUB PR_STR + AZ=A:PR=1:GOSUB PR_STR RETURN -REM RE(A$) -> R% +REM RE(A$) -> R REM Assume RE% has repl_env REM caller must release result RE: - R1%=0 + R1=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL + A=R:E=RE%:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - R1%=0:R2%=0 + R1=0:R2=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL - R2%=R% - IF ER%<>-2 THEN GOTO REP_DONE + A=R:E=RE%:GOSUB EVAL + R2=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:GOSUB MAL_PRINT + A=R:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R2<>0 THEN AY=R2:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ RETURN @@ -473,46 +473,46 @@ REM MAIN program MAIN: GOSUB INIT_MEMORY - LV%=0 + LV=0 REM create repl_env - EO%=-1:GOSUB ENV_NEW:RE%=R% + O=-1:GOSUB ENV_NEW:RE%=R REM core.EXT: defined in Basic - E%=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + E=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env - ZT%=ZI%: REM top of memory after base repl_env + ZT%=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself A$="(def! not (fn* (a) (if a false true)))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE A$="(def! load-file (fn* (f) (eval (read-string (str " A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REM load the args file A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REM set the argument list A$="(def! *ARGV* (rest -*ARGS*-))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REM get the first argument A$="(first -*ARGS*-)" GOSUB RE REM if there is an argument, then run it as a program - IF R%<>0 THEN AY%=R%:GOSUB RELEASE:GOTO RUN_PROG + IF R<>0 THEN AY=R:GOSUB RELEASE:GOTO RUN_PROG REM no arguments, start REPL loop - IF R%=0 THEN GOTO REPL_LOOP + IF R=0 THEN GOTO REPL_LOOP RUN_PROG: REM run a single mal program and exit A$="(load-file (first -*ARGS*-))" GOSUB RE - IF ER%<>-2 THEN GOSUB PRINT_ERROR + IF ER<>-2 THEN GOSUB PRINT_ERROR END REPL_LOOP: @@ -521,7 +521,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -532,6 +532,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=-2:ER$="" + ER=-2:ER$="" RETURN diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index f51a5d23d7..9ca0dc3518 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -11,243 +11,243 @@ REM $INCLUDE: 'core.in.bas' REM $INCLUDE: 'debug.in.bas' -REM READ(A$) -> R% +REM READ(A$) -> R MAL_READ: GOSUB READ_STR RETURN -REM PAIR_Q(B%) -> R% +REM PAIR_Q(B) -> R PAIR_Q: - R%=0 - IF (Z%(B%,0)AND15)<>6 AND (Z%(B%,0)AND15)<>7 THEN RETURN - IF (Z%(B%,1)=0) THEN RETURN - R%=1 + R=0 + IF (Z%(B,0)AND15)<>6 AND (Z%(B,0)AND15)<>7 THEN RETURN + IF (Z%(B,1)=0) THEN RETURN + R=1 RETURN -REM QUASIQUOTE(A%) -> R% +REM QUASIQUOTE(A) -> R QUASIQUOTE: - B%=A%:GOSUB PAIR_Q - IF R%=1 THEN GOTO QQ_UNQUOTE + B=A:GOSUB PAIR_Q + IF R=1 THEN GOTO QQ_UNQUOTE REM ['quote, ast] - AS$="quote":T%=5:GOSUB STRING - B2%=R%:B1%=A%:GOSUB LIST2 + AS$="quote":T=5:GOSUB STRING + B2%=R:B1%=A:GOSUB LIST2 RETURN QQ_UNQUOTE: - R%=A%+1:GOSUB DEREF_R - IF (Z%(R%,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE - IF ZS$(Z%(R%,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE + R=A+1:GOSUB DEREF_R + IF (Z%(R,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] - R%=Z%(A%,1)+1:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=Z%(A,1)+1:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 RETURN QQ_SPLICE_UNQUOTE: - REM push A% on the stack - ZL%=ZL%+1:ZZ%(ZL%)=A% + REM push A on the stack + X=X+1:S%(X)=A REM rest of cases call quasiquote on ast[1..] - A%=Z%(A%,1):GOSUB QUASIQUOTE:T6%=R% - REM pop A% off the stack - A%=ZZ%(ZL%):ZL%=ZL%-1 - - REM set A% to ast[0] for last two cases - A%=A%+1:GOSUB DEREF_A - - B%=A%:GOSUB PAIR_Q - IF R%=0 THEN GOTO QQ_DEFAULT - B%=A%+1:GOSUB DEREF_B - IF (Z%(B%,0)AND15)<>5 THEN GOTO QQ_DEFAULT - IF ZS$(Z%(B%,1))<>"splice-unquote" THEN QQ_DEFAULT + A=Z%(A,1):GOSUB QUASIQUOTE:T6=R + REM pop A off the stack + A=S%(X):X=X-1 + + REM set A to ast[0] for last two cases + A=A+1:GOSUB DEREF_A + + B=A:GOSUB PAIR_Q + IF R=0 THEN GOTO QQ_DEFAULT + B=A+1:GOSUB DEREF_B + IF (Z%(B,0)AND15)<>5 THEN GOTO QQ_DEFAULT + IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B%=Z%(A%,1)+1:GOSUB DEREF_B:B2%=B% - AS$="concat":T%=5:GOSUB STRING:B3%=R% - B1%=T6%:GOSUB LIST3 + B=Z%(A,1)+1:GOSUB DEREF_B:B2%=B + AS$="concat":T=5:GOSUB STRING:B3%=R + B1%=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY%=B1%:GOSUB RELEASE + AY=B1%:GOSUB RELEASE RETURN QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - REM push T6% on the stack - ZL%=ZL%+1:ZZ%(ZL%)=T6% - REM A% set above to ast[0] - GOSUB QUASIQUOTE:B2%=R% - REM pop T6% off the stack - T6%=ZZ%(ZL%):ZL%=ZL%-1 + REM push T6 on the stack + X=X+1:S%(X)=T6 + REM A set above to ast[0] + GOSUB QUASIQUOTE:B2%=R + REM pop T6 off the stack + T6=S%(X):X=X-1 - AS$="cons":T%=5:GOSUB STRING:B3%=R% - B1%=T6%:GOSUB LIST3 + AS$="cons":T=5:GOSUB STRING:B3%=R + B1%=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY%=B1%:GOSUB RELEASE - AY%=B2%:GOSUB RELEASE + AY=B1%:GOSUB RELEASE + AY=B2%:GOSUB RELEASE RETURN -REM MACROEXPAND(A%, E%) -> A%: +REM MACROEXPAND(A, E) -> A: MACROEXPAND: - REM push original A% - ZL%=ZL%+1:ZZ%(ZL%)=A% + REM push original A + X=X+1:S%(X)=A MACROEXPAND_LOOP: REM list? - IF (Z%(A%,0)AND15)<>6 THEN GOTO MACROEXPAND_DONE + IF (Z%(A,0)AND15)<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? - IF Z%(A%,1)=0 THEN GOTO MACROEXPAND_DONE - B%=A%+1:GOSUB DEREF_B + IF Z%(A,1)=0 THEN GOTO MACROEXPAND_DONE + B=A+1:GOSUB DEREF_B REM symbol? in first position - IF (Z%(B%,0)AND15)<>5 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND15)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? - K%=B%:GOSUB ENV_FIND - IF R%=-1 THEN GOTO MACROEXPAND_DONE - B%=T4%:GOSUB DEREF_B + K=B:GOSUB ENV_FIND + IF R=-1 THEN GOTO MACROEXPAND_DONE + B=T4:GOSUB DEREF_B REM macro? - IF (Z%(B%,0)AND15)<>11 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND15)<>11 THEN GOTO MACROEXPAND_DONE REM apply - F%=B%:AR%=Z%(A%,1):GOSUB APPLY - A%=R% + F=B:AR=Z%(A,1):GOSUB APPLY + A=R - AY%=ZZ%(ZL%) - REM if previous A% was not the first A% into macroexpand (i.e. an + AY=S%(X) + REM if previous A was not the first A into macroexpand (i.e. an REM intermediate form) then free it - IF A%<>AY% THEN ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV% + IF A<>AY THEN ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV - IF ER%<>-2 THEN GOTO MACROEXPAND_DONE + IF ER<>-2 THEN GOTO MACROEXPAND_DONE GOTO MACROEXPAND_LOOP MACROEXPAND_DONE: - ZL%=ZL%-1: REM pop original A% + X=X-1: REM pop original A RETURN -REM EVAL_AST(A%, E%) -> R% +REM EVAL_AST(A, E) -> R REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A - IF ER%<>-2 THEN GOTO EVAL_AST_RETURN + IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T%=Z%(A%,0)AND15 - IF T%=5 THEN GOTO EVAL_AST_SYMBOL - IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ + T=Z%(A,0)AND15 + IF T=5 THEN GOTO EVAL_AST_SYMBOL + IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R%=A%:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=A:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K%=A%:GOSUB ENV_GET + K=A:GOSUB ENV_GET GOTO EVAL_AST_RETURN EVAL_AST_SEQ: REM allocate the first entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM make space on the stack - ZL%=ZL%+4 + X=X+4 REM push type of sequence - ZZ%(ZL%-3)=T% + S%(X-3)=T REM push sequence index - ZZ%(ZL%-2)=-1 + S%(X-2)=-1 REM push future return value (new sequence) - ZZ%(ZL%-1)=R% + S%(X-1)=R REM push previous new sequence entry - ZZ%(ZL%)=R% + S%(X)=R EVAL_AST_SEQ_LOOP: REM set new sequence entry type (with 1 ref cnt) - Z%(R%,0)=ZZ%(ZL%-3)+16 - Z%(R%,1)=0 + Z%(R,0)=S%(X-3)+16 + Z%(R,1)=0 REM create value ptr placeholder - Z%(R%+1,0)=14 - Z%(R%+1,1)=0 + Z%(R+1,0)=14 + Z%(R+1,1)=0 REM update index - ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + S%(X-2)=S%(X-2)+1 REM check if we are done evaluating the source sequence - IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: - R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + R=A+1:GOSUB DEREF_R: REM deref to target of referred entry + Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: REM call EVAL for each entry - A%=A%+1:GOSUB EVAL - A%=A%-1 + A=A+1:GOSUB EVAL + A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(ZZ%(ZL%)+1,1)=R% + Z%(S%(X)+1,1)=R - IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(ZZ%(ZL%),1)=R% + Z%(S%(X),1)=R REM update previous ptr to current entry - ZZ%(ZL%)=R% + S%(X)=R REM process the next sequence entry from source list - A%=Z%(A%,1) + A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER%=-2 THEN R%=ZZ%(ZL%-1) + IF ER=-2 THEN R=S%(X-1) REM otherwise, free the return value and return nil - IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE REM pop previous, return, index and type - ZL%=ZL%-4 + X=X-4 GOTO EVAL_AST_RETURN EVAL_AST_RETURN: - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 REM pop EVAL AST return label/address - RN%=ZZ%(ZL%):ZL%=ZL%-1 + RN%=S%(X):X=X-1 ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN -REM EVAL(A%, E%)) -> R% +REM EVAL(A, E)) -> R EVAL: - LV%=LV%+1: REM track basic return stack level + LV=LV+1: REM track basic return stack level - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A EVAL_TCO_RECUR: - REM AZ%=A%:PR%=1:GOSUB PR_STR - REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" + REM AZ=A:PR=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A GOSUB LIST_Q - IF R% THEN GOTO APPLY_LIST + IF R THEN GOTO APPLY_LIST EVAL_NOT_LIST: REM ELSE REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=1 + X=X+1:S%(X)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -257,17 +257,17 @@ EVAL: GOSUB MACROEXPAND GOSUB LIST_Q - IF R%<>1 THEN GOTO EVAL_NOT_LIST + IF R<>1 THEN GOTO EVAL_NOT_LIST GOSUB EMPTY_Q - IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN - A0%=A%+1 - R%=A0%:GOSUB DEREF_R:A0%=R% + A0%=A+1 + R=A0%:GOSUB DEREF_R:A0%=R REM get symbol in A$ IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) + IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -281,206 +281,206 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A%,1),1),1)+1 - R%=A3%:GOSUB DEREF_R:A3%=R% + A3%=Z%(Z%(Z%(A,1),1),1)+1 + R=A3%:GOSUB DEREF_R:A3%=R EVAL_GET_A2: - A2%=Z%(Z%(A%,1),1)+1 - R%=A2%:GOSUB DEREF_R:A2%=R% + A2%=Z%(Z%(A,1),1)+1 + R=A2%:GOSUB DEREF_R:A2%=R EVAL_GET_A1: - A1%=Z%(A%,1)+1 - R%=A1%:GOSUB DEREF_R:A1%=R% + A1%=Z%(A,1)+1 + R=A1%:GOSUB DEREF_R:A1%=R RETURN EVAL_DEF: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% - A%=A2%:GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + X=X+1:S%(X)=A1%: REM push A1% + A=A2%:GOSUB EVAL: REM eval a2 + A1%=S%(X):X=X-1: REM pop A1% - IF ER%<>-2 THEN GOTO EVAL_RETURN + IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K%=A1%:V%=R%:GOSUB ENV_SET + K=A1%:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% - ZL%=ZL%+1:ZZ%(ZL%)=E%: REM push env for for later release + X=X+1:S%(X)=A2%: REM push/save A2% + X=X+1:S%(X)=E: REM push env for for later release REM create new environment with outer as current environment - EO%=E%:GOSUB ENV_NEW - E%=R% + O=E:GOSUB ENV_NEW + E=R EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + X=X+1:S%(X)=A1%: REM push A1% REM eval current A1 odd element - A%=Z%(A1%,1)+1:GOSUB EVAL - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + A=Z%(A1%,1)+1:GOSUB EVAL + A1%=S%(X):X=X-1: REM pop A1% REM set environment: even A1% key to odd A1% eval'd above - K%=A1%+1:V%=R%:GOSUB ENV_SET - AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership + K=A1%+1:V=R:GOSUB ENV_SET + AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4%=ZZ%(ZL%):ZL%=ZL%-1: REM pop previous env + E4%=S%(X):X=X-1: REM pop previous env REM release previous environment if not the current EVAL env - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE - A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% - A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A2%=S%(X):X=X-1: REM pop A2% + A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: - A%=Z%(A%,1): REM rest + A=Z%(A,1): REM rest REM TODO: TCO REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=2 + X=X+1:S%(X)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push eval'd list - A%=R%:GOSUB LAST: REM return the last element - AY%=ZZ%(ZL%):ZL%=ZL%-1: REM pop eval'd list + X=X+1:S%(X)=R: REM push eval'd list + A=R:GOSUB LAST: REM return the last element + AY=S%(X):X=X-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_QUOTE: - R%=Z%(A%,1)+1:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=Z%(A,1)+1:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_RETURN EVAL_QUASIQUOTE: - R%=Z%(A%,1)+1:GOSUB DEREF_R - A%=R%:GOSUB QUASIQUOTE + R=Z%(A,1)+1:GOSUB DEREF_R + A=R:GOSUB QUASIQUOTE REM add quasiquote result to pending release queue to free when - REM next lower EVAL level returns (LV%) - ZM%=ZM%+1:ZR%(ZM%,0)=R%:ZR%(ZM%,1)=LV% + REM next lower EVAL level returns (LV) + ZM%=ZM%+1:ZR%(ZM%,0)=R:ZR%(ZM%,1)=LV - A%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DEFMACRO: REM PRINT "defmacro!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% - A%=A2%:GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + X=X+1:S%(X)=A1%: REM push A1% + A=A2%:GOSUB EVAL: REM eval a2 + A1%=S%(X):X=X-1: REM pop A1% REM change function to macro - Z%(R%,0)=Z%(R%,0)+1 + Z%(R,0)=Z%(R,0)+1 REM set a1 in env to a2 - K%=A1%:V%=R%:GOSUB ENV_SET + K=A1%:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_MACROEXPAND: REM PRINT "macroexpand" - R%=Z%(A%,1)+1:GOSUB DEREF_R - A%=R%:GOSUB MACROEXPAND:R%=A% + R=Z%(A,1)+1:GOSUB DEREF_R + A=R:GOSUB MACROEXPAND:R=A REM since we are returning it unevaluated, inc the ref cnt - Z%(R%,0)=Z%(R%,0)+16 + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_RETURN EVAL_IF: GOSUB EVAL_GET_A1: REM set a1% - REM push A% - ZL%=ZL%+1:ZZ%(ZL%)=A% - A%=A1%:GOSUB EVAL - REM pop A% - A%=ZZ%(ZL%):ZL%=ZL%-1 - IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE + REM push A + X=X+1:S%(X)=A + A=A1%:GOSUB EVAL + REM pop A + A=S%(X):X=X-1 + IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE REM if no false case (A3%), return nil - IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0:GOTO EVAL_RETURN + IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A%=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set a1% and a2% - A%=A2%:P%=A1%:GOSUB MAL_FUNCTION + A=A2%:P=A1%:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=3 + X=X+1:S%(X)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: REM if error, return f/args for release by caller - IF ER%<>-2 THEN GOTO EVAL_RETURN + IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - ZL%=ZL%+1:ZZ%(ZL%)=R% + X=X+1:S%(X)=R - F%=R%+1 + F=R+1 - AR%=Z%(R%,1): REM rest - R%=F%:GOSUB DEREF_R:F%=R% + AR=Z%(R,1): REM rest + R=F:GOSUB DEREF_R:F=R - IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R%=ZZ%(ZL%):ZL%=ZL%-1 - ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN + R=S%(X):X=X-1 + ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4%=E%: REM save the current environment for release + E4%=E: REM save the current environment for release REM create new environ using env stored with function - EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the - REM stack (ZZ%(ZL%-2)) because our new env refers to it and + REM stack (S%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE REM claim the AST before releasing the list containing it - A%=Z%(F%,1):Z%(A%,0)=Z%(A%,0)+16 + A=Z%(F,1):Z%(A,0)=Z%(A,0)+16 REM add AST to pending release queue to free as soon as EVAL - REM actually returns (LV%+1) - ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV%+1 + REM actually returns (LV+1) + ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV+1 REM pop and release f/args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE - REM A% set above - E%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + REM A set above + E=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: - REM AZ%=R%: PR%=1: GOSUB PR_STR - REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + REM AZ=R: PR=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE + IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE - LV%=LV%-1: REM track basic return stack level + LV=LV-1: REM track basic return stack level REM release everything we couldn't release earlier GOSUB RELEASE_PEND @@ -488,51 +488,51 @@ EVAL: REM trigger GC TA%=FRE(0) - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 RETURN -REM PRINT(A%) -> R$ +REM PRINT(A) -> R$ MAL_PRINT: - AZ%=A%:PR%=1:GOSUB PR_STR + AZ=A:PR=1:GOSUB PR_STR RETURN -REM RE(A$) -> R% +REM RE(A$) -> R REM Assume RE% has repl_env REM caller must release result RE: - R1%=0 + R1=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL + A=R:E=RE%:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - R1%=0:R2%=0 + R1=0:R2=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL - R2%=R% - IF ER%<>-2 THEN GOTO REP_DONE + A=R:E=RE%:GOSUB EVAL + R2=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:GOSUB MAL_PRINT + A=R:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R2<>0 THEN AY=R2:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ RETURN @@ -540,55 +540,55 @@ REM MAIN program MAIN: GOSUB INIT_MEMORY - LV%=0 + LV=0 REM create repl_env - EO%=-1:GOSUB ENV_NEW:RE%=R% + O=-1:GOSUB ENV_NEW:RE%=R REM core.EXT: defined in Basic - E%=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + E=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env - ZT%=ZI%: REM top of memory after base repl_env + ZT%=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself A$="(def! not (fn* (a) (if a false true)))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE A$="(def! load-file (fn* (f) (eval (read-string (str " A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE A$="(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs)" A$=A$+" (if (> (count xs) 1) (nth xs 1) (throw "+CHR$(34)+"odd number of" A$=A$+" forms to cond"+CHR$(34)+")) (cons 'cond (rest (rest xs)))))))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE A$="(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs)" A$=A$+" `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REM load the args file A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REM set the argument list A$="(def! *ARGV* (rest -*ARGS*-))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REM get the first argument A$="(first -*ARGS*-)" GOSUB RE REM if there is an argument, then run it as a program - IF R%<>0 THEN AY%=R%:GOSUB RELEASE:GOTO RUN_PROG + IF R<>0 THEN AY=R:GOSUB RELEASE:GOTO RUN_PROG REM no arguments, start REPL loop - IF R%=0 THEN GOTO REPL_LOOP + IF R=0 THEN GOTO REPL_LOOP RUN_PROG: REM run a single mal program and exit A$="(load-file (first -*ARGS*-))" GOSUB RE - IF ER%<>-2 THEN GOSUB PRINT_ERROR + IF ER<>-2 THEN GOSUB PRINT_ERROR END REPL_LOOP: @@ -597,7 +597,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -608,6 +608,6 @@ MAIN: PRINT_ERROR: PRINT "Error: "+ER$ - ER%=-2:ER$="" + ER=-2:ER$="" RETURN diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index 4b212fde2e..f9a86f3501 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -11,243 +11,243 @@ REM $INCLUDE: 'core.in.bas' REM $INCLUDE: 'debug.in.bas' -REM READ(A$) -> R% +REM READ(A$) -> R MAL_READ: GOSUB READ_STR RETURN -REM PAIR_Q(B%) -> R% +REM PAIR_Q(B) -> R PAIR_Q: - R%=0 - IF (Z%(B%,0)AND15)<>6 AND (Z%(B%,0)AND15)<>7 THEN RETURN - IF (Z%(B%,1)=0) THEN RETURN - R%=1 + R=0 + IF (Z%(B,0)AND15)<>6 AND (Z%(B,0)AND15)<>7 THEN RETURN + IF (Z%(B,1)=0) THEN RETURN + R=1 RETURN -REM QUASIQUOTE(A%) -> R% +REM QUASIQUOTE(A) -> R QUASIQUOTE: - B%=A%:GOSUB PAIR_Q - IF R%=1 THEN GOTO QQ_UNQUOTE + B=A:GOSUB PAIR_Q + IF R=1 THEN GOTO QQ_UNQUOTE REM ['quote, ast] - AS$="quote":T%=5:GOSUB STRING - B2%=R%:B1%=A%:GOSUB LIST2 + AS$="quote":T=5:GOSUB STRING + B2%=R:B1%=A:GOSUB LIST2 RETURN QQ_UNQUOTE: - R%=A%+1:GOSUB DEREF_R - IF (Z%(R%,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE - IF ZS$(Z%(R%,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE + R=A+1:GOSUB DEREF_R + IF (Z%(R,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] - R%=Z%(A%,1)+1:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=Z%(A,1)+1:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 RETURN QQ_SPLICE_UNQUOTE: - REM push A% on the stack - ZL%=ZL%+1:ZZ%(ZL%)=A% + REM push A on the stack + X=X+1:S%(X)=A REM rest of cases call quasiquote on ast[1..] - A%=Z%(A%,1):GOSUB QUASIQUOTE:T6%=R% - REM pop A% off the stack - A%=ZZ%(ZL%):ZL%=ZL%-1 - - REM set A% to ast[0] for last two cases - A%=A%+1:GOSUB DEREF_A - - B%=A%:GOSUB PAIR_Q - IF R%=0 THEN GOTO QQ_DEFAULT - B%=A%+1:GOSUB DEREF_B - IF (Z%(B%,0)AND15)<>5 THEN GOTO QQ_DEFAULT - IF ZS$(Z%(B%,1))<>"splice-unquote" THEN QQ_DEFAULT + A=Z%(A,1):GOSUB QUASIQUOTE:T6=R + REM pop A off the stack + A=S%(X):X=X-1 + + REM set A to ast[0] for last two cases + A=A+1:GOSUB DEREF_A + + B=A:GOSUB PAIR_Q + IF R=0 THEN GOTO QQ_DEFAULT + B=A+1:GOSUB DEREF_B + IF (Z%(B,0)AND15)<>5 THEN GOTO QQ_DEFAULT + IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B%=Z%(A%,1)+1:GOSUB DEREF_B:B2%=B% - AS$="concat":T%=5:GOSUB STRING:B3%=R% - B1%=T6%:GOSUB LIST3 + B=Z%(A,1)+1:GOSUB DEREF_B:B2%=B + AS$="concat":T=5:GOSUB STRING:B3%=R + B1%=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY%=B1%:GOSUB RELEASE + AY=B1%:GOSUB RELEASE RETURN QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - REM push T6% on the stack - ZL%=ZL%+1:ZZ%(ZL%)=T6% - REM A% set above to ast[0] - GOSUB QUASIQUOTE:B2%=R% - REM pop T6% off the stack - T6%=ZZ%(ZL%):ZL%=ZL%-1 + REM push T6 on the stack + X=X+1:S%(X)=T6 + REM A set above to ast[0] + GOSUB QUASIQUOTE:B2%=R + REM pop T6 off the stack + T6=S%(X):X=X-1 - AS$="cons":T%=5:GOSUB STRING:B3%=R% - B1%=T6%:GOSUB LIST3 + AS$="cons":T=5:GOSUB STRING:B3%=R + B1%=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY%=B1%:GOSUB RELEASE - AY%=B2%:GOSUB RELEASE + AY=B1%:GOSUB RELEASE + AY=B2%:GOSUB RELEASE RETURN -REM MACROEXPAND(A%, E%) -> A%: +REM MACROEXPAND(A, E) -> A: MACROEXPAND: - REM push original A% - ZL%=ZL%+1:ZZ%(ZL%)=A% + REM push original A + X=X+1:S%(X)=A MACROEXPAND_LOOP: REM list? - IF (Z%(A%,0)AND15)<>6 THEN GOTO MACROEXPAND_DONE + IF (Z%(A,0)AND15)<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? - IF Z%(A%,1)=0 THEN GOTO MACROEXPAND_DONE - B%=A%+1:GOSUB DEREF_B + IF Z%(A,1)=0 THEN GOTO MACROEXPAND_DONE + B=A+1:GOSUB DEREF_B REM symbol? in first position - IF (Z%(B%,0)AND15)<>5 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND15)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? - K%=B%:GOSUB ENV_FIND - IF R%=-1 THEN GOTO MACROEXPAND_DONE - B%=T4%:GOSUB DEREF_B + K=B:GOSUB ENV_FIND + IF R=-1 THEN GOTO MACROEXPAND_DONE + B=T4:GOSUB DEREF_B REM macro? - IF (Z%(B%,0)AND15)<>11 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND15)<>11 THEN GOTO MACROEXPAND_DONE REM apply - F%=B%:AR%=Z%(A%,1):GOSUB APPLY - A%=R% + F=B:AR=Z%(A,1):GOSUB APPLY + A=R - AY%=ZZ%(ZL%) - REM if previous A% was not the first A% into macroexpand (i.e. an + AY=S%(X) + REM if previous A was not the first A into macroexpand (i.e. an REM intermediate form) then free it - IF A%<>AY% THEN ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV% + IF A<>AY THEN ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV - IF ER%<>-2 THEN GOTO MACROEXPAND_DONE + IF ER<>-2 THEN GOTO MACROEXPAND_DONE GOTO MACROEXPAND_LOOP MACROEXPAND_DONE: - ZL%=ZL%-1: REM pop original A% + X=X-1: REM pop original A RETURN -REM EVAL_AST(A%, E%) -> R% +REM EVAL_AST(A, E) -> R REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A - IF ER%<>-2 THEN GOTO EVAL_AST_RETURN + IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T%=Z%(A%,0)AND15 - IF T%=5 THEN GOTO EVAL_AST_SYMBOL - IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ + T=Z%(A,0)AND15 + IF T=5 THEN GOTO EVAL_AST_SYMBOL + IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R%=A%:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=A:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K%=A%:GOSUB ENV_GET + K=A:GOSUB ENV_GET GOTO EVAL_AST_RETURN EVAL_AST_SEQ: REM allocate the first entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM make space on the stack - ZL%=ZL%+4 + X=X+4 REM push type of sequence - ZZ%(ZL%-3)=T% + S%(X-3)=T REM push sequence index - ZZ%(ZL%-2)=-1 + S%(X-2)=-1 REM push future return value (new sequence) - ZZ%(ZL%-1)=R% + S%(X-1)=R REM push previous new sequence entry - ZZ%(ZL%)=R% + S%(X)=R EVAL_AST_SEQ_LOOP: REM set new sequence entry type (with 1 ref cnt) - Z%(R%,0)=ZZ%(ZL%-3)+16 - Z%(R%,1)=0 + Z%(R,0)=S%(X-3)+16 + Z%(R,1)=0 REM create value ptr placeholder - Z%(R%+1,0)=14 - Z%(R%+1,1)=0 + Z%(R+1,0)=14 + Z%(R+1,1)=0 REM update index - ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + S%(X-2)=S%(X-2)+1 REM check if we are done evaluating the source sequence - IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: - R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + R=A+1:GOSUB DEREF_R: REM deref to target of referred entry + Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: REM call EVAL for each entry - A%=A%+1:GOSUB EVAL - A%=A%-1 + A=A+1:GOSUB EVAL + A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(ZZ%(ZL%)+1,1)=R% + Z%(S%(X)+1,1)=R - IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(ZZ%(ZL%),1)=R% + Z%(S%(X),1)=R REM update previous ptr to current entry - ZZ%(ZL%)=R% + S%(X)=R REM process the next sequence entry from source list - A%=Z%(A%,1) + A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER%=-2 THEN R%=ZZ%(ZL%-1) + IF ER=-2 THEN R=S%(X-1) REM otherwise, free the return value and return nil - IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE REM pop previous, return, index and type - ZL%=ZL%-4 + X=X-4 GOTO EVAL_AST_RETURN EVAL_AST_RETURN: - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 REM pop EVAL AST return label/address - RN%=ZZ%(ZL%):ZL%=ZL%-1 + RN%=S%(X):X=X-1 ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN -REM EVAL(A%, E%)) -> R% +REM EVAL(A, E)) -> R EVAL: - LV%=LV%+1: REM track basic return stack level + LV=LV+1: REM track basic return stack level - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A EVAL_TCO_RECUR: - REM AZ%=A%:PR%=1:GOSUB PR_STR - REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" + REM AZ=A:PR=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A GOSUB LIST_Q - IF R% THEN GOTO APPLY_LIST + IF R THEN GOTO APPLY_LIST EVAL_NOT_LIST: REM ELSE REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=1 + X=X+1:S%(X)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -257,17 +257,17 @@ EVAL: GOSUB MACROEXPAND GOSUB LIST_Q - IF R%<>1 THEN GOTO EVAL_NOT_LIST + IF R<>1 THEN GOTO EVAL_NOT_LIST GOSUB EMPTY_Q - IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN - A0%=A%+1 - R%=A0%:GOSUB DEREF_R:A0%=R% + A0%=A+1 + R=A0%:GOSUB DEREF_R:A0%=R REM get symbol in A$ IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) + IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -282,237 +282,237 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A%,1),1),1)+1 - R%=A3%:GOSUB DEREF_R:A3%=R% + A3%=Z%(Z%(Z%(A,1),1),1)+1 + R=A3%:GOSUB DEREF_R:A3%=R EVAL_GET_A2: - A2%=Z%(Z%(A%,1),1)+1 - R%=A2%:GOSUB DEREF_R:A2%=R% + A2%=Z%(Z%(A,1),1)+1 + R=A2%:GOSUB DEREF_R:A2%=R EVAL_GET_A1: - A1%=Z%(A%,1)+1 - R%=A1%:GOSUB DEREF_R:A1%=R% + A1%=Z%(A,1)+1 + R=A1%:GOSUB DEREF_R:A1%=R RETURN EVAL_DEF: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% - A%=A2%:GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + X=X+1:S%(X)=A1%: REM push A1% + A=A2%:GOSUB EVAL: REM eval a2 + A1%=S%(X):X=X-1: REM pop A1% - IF ER%<>-2 THEN GOTO EVAL_RETURN + IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K%=A1%:V%=R%:GOSUB ENV_SET + K=A1%:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% - ZL%=ZL%+1:ZZ%(ZL%)=E%: REM push env for for later release + X=X+1:S%(X)=A2%: REM push/save A2% + X=X+1:S%(X)=E: REM push env for for later release REM create new environment with outer as current environment - EO%=E%:GOSUB ENV_NEW - E%=R% + O=E:GOSUB ENV_NEW + E=R EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + X=X+1:S%(X)=A1%: REM push A1% REM eval current A1 odd element - A%=Z%(A1%,1)+1:GOSUB EVAL - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + A=Z%(A1%,1)+1:GOSUB EVAL + A1%=S%(X):X=X-1: REM pop A1% REM set environment: even A1% key to odd A1% eval'd above - K%=A1%+1:V%=R%:GOSUB ENV_SET - AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership + K=A1%+1:V=R:GOSUB ENV_SET + AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4%=ZZ%(ZL%):ZL%=ZL%-1: REM pop previous env + E4%=S%(X):X=X-1: REM pop previous env REM release previous environment if not the current EVAL env - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE - A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% - A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A2%=S%(X):X=X-1: REM pop A2% + A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: - A%=Z%(A%,1): REM rest + A=Z%(A,1): REM rest REM TODO: TCO REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=2 + X=X+1:S%(X)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push eval'd list - A%=R%:GOSUB LAST: REM return the last element - AY%=ZZ%(ZL%):ZL%=ZL%-1: REM pop eval'd list + X=X+1:S%(X)=R: REM push eval'd list + A=R:GOSUB LAST: REM return the last element + AY=S%(X):X=X-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_QUOTE: - R%=Z%(A%,1)+1:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=Z%(A,1)+1:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_RETURN EVAL_QUASIQUOTE: - R%=Z%(A%,1)+1:GOSUB DEREF_R - A%=R%:GOSUB QUASIQUOTE + R=Z%(A,1)+1:GOSUB DEREF_R + A=R:GOSUB QUASIQUOTE REM add quasiquote result to pending release queue to free when - REM next lower EVAL level returns (LV%) - ZM%=ZM%+1:ZR%(ZM%,0)=R%:ZR%(ZM%,1)=LV% + REM next lower EVAL level returns (LV) + ZM%=ZM%+1:ZR%(ZM%,0)=R:ZR%(ZM%,1)=LV - A%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DEFMACRO: REM PRINT "defmacro!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% - A%=A2%:GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + X=X+1:S%(X)=A1%: REM push A1% + A=A2%:GOSUB EVAL: REM eval a2 + A1%=S%(X):X=X-1: REM pop A1% REM change function to macro - Z%(R%,0)=Z%(R%,0)+1 + Z%(R,0)=Z%(R,0)+1 REM set a1 in env to a2 - K%=A1%:V%=R%:GOSUB ENV_SET + K=A1%:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_MACROEXPAND: REM PRINT "macroexpand" - R%=Z%(A%,1)+1:GOSUB DEREF_R - A%=R%:GOSUB MACROEXPAND:R%=A% + R=Z%(A,1)+1:GOSUB DEREF_R + A=R:GOSUB MACROEXPAND:R=A REM since we are returning it unevaluated, inc the ref cnt - Z%(R%,0)=Z%(R%,0)+16 + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_RETURN EVAL_TRY: REM PRINT "try*" GOSUB EVAL_GET_A1: REM set a1%, a2%, and a3% - ZL%=ZL%+1:ZZ%(ZL%)=A%: REM push/save A% - A%=A1%:GOSUB EVAL: REM eval a1 - A%=ZZ%(ZL%):ZL%=ZL%-1: REM pop/restore A% + X=X+1:S%(X)=A: REM push/save A + A=A1%:GOSUB EVAL: REM eval a1 + A=S%(X):X=X-1: REM pop/restore A REM if there is not error or catch block then return - IF ER%=-2 OR Z%(A%,1)=0 THEN GOTO EVAL_RETURN + IF ER=-2 OR Z%(A,1)=0 THEN GOTO EVAL_RETURN REM create environment for the catch block eval - EO%=E%:GOSUB ENV_NEW:E%=R% + O=E:GOSUB ENV_NEW:E=R GOSUB EVAL_GET_A2: REM set a1% and a2% - A%=A2%:GOSUB EVAL_GET_A2: REM set a1% and a2% from catch block + A=A2%:GOSUB EVAL_GET_A2: REM set a1% and a2% from catch block - REM create object for ER%=-1 type raw string errors - IF ER%=-1 THEN AS$=ER$:T%=4:GOSUB STRING:ER%=R%:Z%(R%,0)=Z%(R%,0)+16 + REM create object for ER=-1 type raw string errors + IF ER=-1 THEN AS$=ER$:T=4:GOSUB STRING:ER=R:Z%(R,0)=Z%(R,0)+16 REM bind the catch symbol to the error object - K%=A1%:V%=ER%:GOSUB ENV_SET - AY%=R%:GOSUB RELEASE: REM release out use, env took ownership + K=A1%:V=ER:GOSUB ENV_SET + AY=R:GOSUB RELEASE: REM release out use, env took ownership REM unset error for catch eval - ER%=-2:ER$="" + ER=-2:ER$="" - A%=A2%:GOSUB EVAL + A=A2%:GOSUB EVAL GOTO EVAL_RETURN EVAL_IF: GOSUB EVAL_GET_A1: REM set a1% - REM push A% - ZL%=ZL%+1:ZZ%(ZL%)=A% - A%=A1%:GOSUB EVAL - REM pop A% - A%=ZZ%(ZL%):ZL%=ZL%-1 - IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE + REM push A + X=X+1:S%(X)=A + A=A1%:GOSUB EVAL + REM pop A + A=S%(X):X=X-1 + IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE REM if no false case (A3%), return nil - IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0:GOTO EVAL_RETURN + IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A%=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set a1% and a2% - A%=A2%:P%=A1%:GOSUB MAL_FUNCTION + A=A2%:P=A1%:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=3 + X=X+1:S%(X)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: REM if error, return f/args for release by caller - IF ER%<>-2 THEN GOTO EVAL_RETURN + IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - ZL%=ZL%+1:ZZ%(ZL%)=R% + X=X+1:S%(X)=R - F%=R%+1 + F=R+1 - AR%=Z%(R%,1): REM rest - R%=F%:GOSUB DEREF_R:F%=R% + AR=Z%(R,1): REM rest + R=F:GOSUB DEREF_R:F=R - IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R%=ZZ%(ZL%):ZL%=ZL%-1 - ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN + R=S%(X):X=X-1 + ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4%=E%: REM save the current environment for release + E4%=E: REM save the current environment for release REM create new environ using env stored with function - EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the - REM stack (ZZ%(ZL%-2)) because our new env refers to it and + REM stack (S%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE REM claim the AST before releasing the list containing it - A%=Z%(F%,1):Z%(A%,0)=Z%(A%,0)+16 + A=Z%(F,1):Z%(A,0)=Z%(A,0)+16 REM add AST to pending release queue to free as soon as EVAL - REM actually returns (LV%+1) - ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV%+1 + REM actually returns (LV+1) + ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV+1 REM pop and release f/args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE - REM A% set above - E%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + REM A set above + E=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: - REM AZ%=R%: PR%=1: GOSUB PR_STR - REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + REM AZ=R: PR=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE + IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE - LV%=LV%-1: REM track basic return stack level + LV=LV-1: REM track basic return stack level REM release everything we couldn't release earlier GOSUB RELEASE_PEND @@ -520,51 +520,51 @@ EVAL: REM trigger GC TA%=FRE(0) - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 RETURN -REM PRINT(A%) -> R$ +REM PRINT(A) -> R$ MAL_PRINT: - AZ%=A%:PR%=1:GOSUB PR_STR + AZ=A:PR=1:GOSUB PR_STR RETURN -REM RE(A$) -> R% +REM RE(A$) -> R REM Assume RE% has repl_env REM caller must release result RE: - R1%=0 + R1=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL + A=R:E=RE%:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - R1%=0:R2%=0 + R1=0:R2=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL - R2%=R% - IF ER%<>-2 THEN GOTO REP_DONE + A=R:E=RE%:GOSUB EVAL + R2=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:GOSUB MAL_PRINT + A=R:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R2<>0 THEN AY=R2:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ RETURN @@ -572,55 +572,55 @@ REM MAIN program MAIN: GOSUB INIT_MEMORY - LV%=0 + LV=0 REM create repl_env - EO%=-1:GOSUB ENV_NEW:RE%=R% + O=-1:GOSUB ENV_NEW:RE%=R REM core.EXT: defined in Basic - E%=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + E=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env - ZT%=ZI%: REM top of memory after base repl_env + ZT%=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself A$="(def! not (fn* (a) (if a false true)))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE A$="(def! load-file (fn* (f) (eval (read-string (str " A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE A$="(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs)" A$=A$+" (if (> (count xs) 1) (nth xs 1) (throw "+CHR$(34)+"odd number of" A$=A$+" forms to cond"+CHR$(34)+")) (cons 'cond (rest (rest xs)))))))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE A$="(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs)" A$=A$+" `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REM load the args file A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REM set the argument list A$="(def! *ARGV* (rest -*ARGS*-))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REM get the first argument A$="(first -*ARGS*-)" GOSUB RE REM if there is an argument, then run it as a program - IF R%<>0 THEN AY%=R%:GOSUB RELEASE:GOTO RUN_PROG + IF R<>0 THEN AY=R:GOSUB RELEASE:GOTO RUN_PROG REM no arguments, start REPL loop - IF R%=0 THEN GOTO REPL_LOOP + IF R=0 THEN GOTO REPL_LOOP RUN_PROG: REM run a single mal program and exit A$="(load-file (first -*ARGS*-))" GOSUB RE - IF ER%<>-2 THEN GOSUB PRINT_ERROR + IF ER<>-2 THEN GOSUB PRINT_ERROR END REPL_LOOP: @@ -629,7 +629,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -640,8 +640,8 @@ MAIN: PRINT_ERROR: REM if the error is an object, then print and free it - IF ER%>=0 THEN AZ%=ER%:PR%=0:GOSUB PR_STR:ER$=R$:AY%=ER%:GOSUB RELEASE + IF ER>=0 THEN AZ=ER:PR=0:GOSUB PR_STR:ER$=R$:AY=ER:GOSUB RELEASE PRINT "Error: "+ER$ - ER%=-2:ER$="" + ER=-2:ER$="" RETURN diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index b5d53c8c17..f2d0effb60 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -11,243 +11,243 @@ REM $INCLUDE: 'core.in.bas' REM $INCLUDE: 'debug.in.bas' -REM READ(A$) -> R% +REM READ(A$) -> R MAL_READ: GOSUB READ_STR RETURN -REM PAIR_Q(B%) -> R% +REM PAIR_Q(B) -> R PAIR_Q: - R%=0 - IF (Z%(B%,0)AND15)<>6 AND (Z%(B%,0)AND15)<>7 THEN RETURN - IF (Z%(B%,1)=0) THEN RETURN - R%=1 + R=0 + IF (Z%(B,0)AND15)<>6 AND (Z%(B,0)AND15)<>7 THEN RETURN + IF (Z%(B,1)=0) THEN RETURN + R=1 RETURN -REM QUASIQUOTE(A%) -> R% +REM QUASIQUOTE(A) -> R QUASIQUOTE: - B%=A%:GOSUB PAIR_Q - IF R%=1 THEN GOTO QQ_UNQUOTE + B=A:GOSUB PAIR_Q + IF R=1 THEN GOTO QQ_UNQUOTE REM ['quote, ast] - AS$="quote":T%=5:GOSUB STRING - B2%=R%:B1%=A%:GOSUB LIST2 + AS$="quote":T=5:GOSUB STRING + B2%=R:B1%=A:GOSUB LIST2 RETURN QQ_UNQUOTE: - R%=A%+1:GOSUB DEREF_R - IF (Z%(R%,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE - IF ZS$(Z%(R%,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE + R=A+1:GOSUB DEREF_R + IF (Z%(R,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] - R%=Z%(A%,1)+1:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=Z%(A,1)+1:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 RETURN QQ_SPLICE_UNQUOTE: - REM push A% on the stack - ZL%=ZL%+1:ZZ%(ZL%)=A% + REM push A on the stack + X=X+1:S%(X)=A REM rest of cases call quasiquote on ast[1..] - A%=Z%(A%,1):GOSUB QUASIQUOTE:T6%=R% - REM pop A% off the stack - A%=ZZ%(ZL%):ZL%=ZL%-1 - - REM set A% to ast[0] for last two cases - A%=A%+1:GOSUB DEREF_A - - B%=A%:GOSUB PAIR_Q - IF R%=0 THEN GOTO QQ_DEFAULT - B%=A%+1:GOSUB DEREF_B - IF (Z%(B%,0)AND15)<>5 THEN GOTO QQ_DEFAULT - IF ZS$(Z%(B%,1))<>"splice-unquote" THEN QQ_DEFAULT + A=Z%(A,1):GOSUB QUASIQUOTE:T6=R + REM pop A off the stack + A=S%(X):X=X-1 + + REM set A to ast[0] for last two cases + A=A+1:GOSUB DEREF_A + + B=A:GOSUB PAIR_Q + IF R=0 THEN GOTO QQ_DEFAULT + B=A+1:GOSUB DEREF_B + IF (Z%(B,0)AND15)<>5 THEN GOTO QQ_DEFAULT + IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B%=Z%(A%,1)+1:GOSUB DEREF_B:B2%=B% - AS$="concat":T%=5:GOSUB STRING:B3%=R% - B1%=T6%:GOSUB LIST3 + B=Z%(A,1)+1:GOSUB DEREF_B:B2%=B + AS$="concat":T=5:GOSUB STRING:B3%=R + B1%=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY%=B1%:GOSUB RELEASE + AY=B1%:GOSUB RELEASE RETURN QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - REM push T6% on the stack - ZL%=ZL%+1:ZZ%(ZL%)=T6% - REM A% set above to ast[0] - GOSUB QUASIQUOTE:B2%=R% - REM pop T6% off the stack - T6%=ZZ%(ZL%):ZL%=ZL%-1 + REM push T6 on the stack + X=X+1:S%(X)=T6 + REM A set above to ast[0] + GOSUB QUASIQUOTE:B2%=R + REM pop T6 off the stack + T6=S%(X):X=X-1 - AS$="cons":T%=5:GOSUB STRING:B3%=R% - B1%=T6%:GOSUB LIST3 + AS$="cons":T=5:GOSUB STRING:B3%=R + B1%=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY%=B1%:GOSUB RELEASE - AY%=B2%:GOSUB RELEASE + AY=B1%:GOSUB RELEASE + AY=B2%:GOSUB RELEASE RETURN -REM MACROEXPAND(A%, E%) -> A%: +REM MACROEXPAND(A, E) -> A: MACROEXPAND: - REM push original A% - ZL%=ZL%+1:ZZ%(ZL%)=A% + REM push original A + X=X+1:S%(X)=A MACROEXPAND_LOOP: REM list? - IF (Z%(A%,0)AND15)<>6 THEN GOTO MACROEXPAND_DONE + IF (Z%(A,0)AND15)<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? - IF Z%(A%,1)=0 THEN GOTO MACROEXPAND_DONE - B%=A%+1:GOSUB DEREF_B + IF Z%(A,1)=0 THEN GOTO MACROEXPAND_DONE + B=A+1:GOSUB DEREF_B REM symbol? in first position - IF (Z%(B%,0)AND15)<>5 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND15)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? - K%=B%:GOSUB ENV_FIND - IF R%=-1 THEN GOTO MACROEXPAND_DONE - B%=T4%:GOSUB DEREF_B + K=B:GOSUB ENV_FIND + IF R=-1 THEN GOTO MACROEXPAND_DONE + B=T4:GOSUB DEREF_B REM macro? - IF (Z%(B%,0)AND15)<>11 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND15)<>11 THEN GOTO MACROEXPAND_DONE REM apply - F%=B%:AR%=Z%(A%,1):GOSUB APPLY - A%=R% + F=B:AR=Z%(A,1):GOSUB APPLY + A=R - AY%=ZZ%(ZL%) - REM if previous A% was not the first A% into macroexpand (i.e. an + AY=S%(X) + REM if previous A was not the first A into macroexpand (i.e. an REM intermediate form) then free it - IF A%<>AY% THEN ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV% + IF A<>AY THEN ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV - IF ER%<>-2 THEN GOTO MACROEXPAND_DONE + IF ER<>-2 THEN GOTO MACROEXPAND_DONE GOTO MACROEXPAND_LOOP MACROEXPAND_DONE: - ZL%=ZL%-1: REM pop original A% + X=X-1: REM pop original A RETURN -REM EVAL_AST(A%, E%) -> R% +REM EVAL_AST(A, E) -> R REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A - IF ER%<>-2 THEN GOTO EVAL_AST_RETURN + IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T%=Z%(A%,0)AND15 - IF T%=5 THEN GOTO EVAL_AST_SYMBOL - IF T%>=6 AND T%<=8 THEN GOTO EVAL_AST_SEQ + T=Z%(A,0)AND15 + IF T=5 THEN GOTO EVAL_AST_SYMBOL + IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R%=A%:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=A:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K%=A%:GOSUB ENV_GET + K=A:GOSUB ENV_GET GOTO EVAL_AST_RETURN EVAL_AST_SEQ: REM allocate the first entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM make space on the stack - ZL%=ZL%+4 + X=X+4 REM push type of sequence - ZZ%(ZL%-3)=T% + S%(X-3)=T REM push sequence index - ZZ%(ZL%-2)=-1 + S%(X-2)=-1 REM push future return value (new sequence) - ZZ%(ZL%-1)=R% + S%(X-1)=R REM push previous new sequence entry - ZZ%(ZL%)=R% + S%(X)=R EVAL_AST_SEQ_LOOP: REM set new sequence entry type (with 1 ref cnt) - Z%(R%,0)=ZZ%(ZL%-3)+16 - Z%(R%,1)=0 + Z%(R,0)=S%(X-3)+16 + Z%(R,1)=0 REM create value ptr placeholder - Z%(R%+1,0)=14 - Z%(R%+1,1)=0 + Z%(R+1,0)=14 + Z%(R+1,1)=0 REM update index - ZZ%(ZL%-2)=ZZ%(ZL%-2)+1 + S%(X-2)=S%(X-2)+1 REM check if we are done evaluating the source sequence - IF Z%(A%,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (ZZ%(ZL%-3)=8) AND ((ZZ%(ZL%-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: - R%=A%+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R%,0)=Z%(R%,0)+16: REM inc ref cnt of referred value + R=A+1:GOSUB DEREF_R: REM deref to target of referred entry + Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: REM call EVAL for each entry - A%=A%+1:GOSUB EVAL - A%=A%-1 + A=A+1:GOSUB EVAL + A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(ZZ%(ZL%)+1,1)=R% + Z%(S%(X)+1,1)=R - IF ER%<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ%=2:GOSUB ALLOC + SZ=2:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(ZZ%(ZL%),1)=R% + Z%(S%(X),1)=R REM update previous ptr to current entry - ZZ%(ZL%)=R% + S%(X)=R REM process the next sequence entry from source list - A%=Z%(A%,1) + A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER%=-2 THEN R%=ZZ%(ZL%-1) + IF ER=-2 THEN R=S%(X-1) REM otherwise, free the return value and return nil - IF ER%<>-2 THEN R%=0:AY%=ZZ%(ZL%-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE REM pop previous, return, index and type - ZL%=ZL%-4 + X=X-4 GOTO EVAL_AST_RETURN EVAL_AST_RETURN: - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 REM pop EVAL AST return label/address - RN%=ZZ%(ZL%):ZL%=ZL%-1 + RN%=S%(X):X=X-1 ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN -REM EVAL(A%, E%)) -> R% +REM EVAL(A, E)) -> R EVAL: - LV%=LV%+1: REM track basic return stack level + LV=LV+1: REM track basic return stack level - REM push A% and E% on the stack - ZL%=ZL%+2:ZZ%(ZL%-1)=E%:ZZ%(ZL%)=A% + REM push A and E on the stack + X=X+2:S%(X-1)=E:S%(X)=A EVAL_TCO_RECUR: - REM AZ%=A%:PR%=1:GOSUB PR_STR - REM PRINT "EVAL: "+R$+" [A%:"+STR$(A%)+", LV%:"+STR$(LV%)+"]" + REM AZ=A:PR=1:GOSUB PR_STR + REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A GOSUB LIST_Q - IF R% THEN GOTO APPLY_LIST + IF R THEN GOTO APPLY_LIST EVAL_NOT_LIST: REM ELSE REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=1 + X=X+1:S%(X)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -257,17 +257,17 @@ EVAL: GOSUB MACROEXPAND GOSUB LIST_Q - IF R%<>1 THEN GOTO EVAL_NOT_LIST + IF R<>1 THEN GOTO EVAL_NOT_LIST GOSUB EMPTY_Q - IF R% THEN R%=A%:Z%(R%,0)=Z%(R%,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN - A0%=A%+1 - R%=A0%:GOSUB DEREF_R:A0%=R% + A0%=A+1 + R=A0%:GOSUB DEREF_R:A0%=R REM get symbol in A$ IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=ZS$(Z%(A0%,1)) + IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -282,237 +282,237 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A%,1),1),1)+1 - R%=A3%:GOSUB DEREF_R:A3%=R% + A3%=Z%(Z%(Z%(A,1),1),1)+1 + R=A3%:GOSUB DEREF_R:A3%=R EVAL_GET_A2: - A2%=Z%(Z%(A%,1),1)+1 - R%=A2%:GOSUB DEREF_R:A2%=R% + A2%=Z%(Z%(A,1),1)+1 + R=A2%:GOSUB DEREF_R:A2%=R EVAL_GET_A1: - A1%=Z%(A%,1)+1 - R%=A1%:GOSUB DEREF_R:A1%=R% + A1%=Z%(A,1)+1 + R=A1%:GOSUB DEREF_R:A1%=R RETURN EVAL_DEF: REM PRINT "def!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% - A%=A2%:GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + X=X+1:S%(X)=A1%: REM push A1% + A=A2%:GOSUB EVAL: REM eval a2 + A1%=S%(X):X=X-1: REM pop A1% - IF ER%<>-2 THEN GOTO EVAL_RETURN + IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K%=A1%:V%=R%:GOSUB ENV_SET + K=A1%:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A2%: REM push/save A2% - ZL%=ZL%+1:ZZ%(ZL%)=E%: REM push env for for later release + X=X+1:S%(X)=A2%: REM push/save A2% + X=X+1:S%(X)=E: REM push env for for later release REM create new environment with outer as current environment - EO%=E%:GOSUB ENV_NEW - E%=R% + O=E:GOSUB ENV_NEW + E=R EVAL_LET_LOOP: IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% + X=X+1:S%(X)=A1%: REM push A1% REM eval current A1 odd element - A%=Z%(A1%,1)+1:GOSUB EVAL - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + A=Z%(A1%,1)+1:GOSUB EVAL + A1%=S%(X):X=X-1: REM pop A1% REM set environment: even A1% key to odd A1% eval'd above - K%=A1%+1:V%=R%:GOSUB ENV_SET - AY%=R%:GOSUB RELEASE: REM release our use, ENV_SET took ownership + K=A1%+1:V=R:GOSUB ENV_SET + AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1% elements A1%=Z%(Z%(A1%,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4%=ZZ%(ZL%):ZL%=ZL%-1: REM pop previous env + E4%=S%(X):X=X-1: REM pop previous env REM release previous environment if not the current EVAL env - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE - A2%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A2% - A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A2%=S%(X):X=X-1: REM pop A2% + A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: - A%=Z%(A%,1): REM rest + A=Z%(A,1): REM rest REM TODO: TCO REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=2 + X=X+1:S%(X)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - ZL%=ZL%+1:ZZ%(ZL%)=R%: REM push eval'd list - A%=R%:GOSUB LAST: REM return the last element - AY%=ZZ%(ZL%):ZL%=ZL%-1: REM pop eval'd list + X=X+1:S%(X)=R: REM push eval'd list + A=R:GOSUB LAST: REM return the last element + AY=S%(X):X=X-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_QUOTE: - R%=Z%(A%,1)+1:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=Z%(A,1)+1:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_RETURN EVAL_QUASIQUOTE: - R%=Z%(A%,1)+1:GOSUB DEREF_R - A%=R%:GOSUB QUASIQUOTE + R=Z%(A,1)+1:GOSUB DEREF_R + A=R:GOSUB QUASIQUOTE REM add quasiquote result to pending release queue to free when - REM next lower EVAL level returns (LV%) - ZM%=ZM%+1:ZR%(ZM%,0)=R%:ZR%(ZM%,1)=LV% + REM next lower EVAL level returns (LV) + ZM%=ZM%+1:ZR%(ZM%,0)=R:ZR%(ZM%,1)=LV - A%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DEFMACRO: REM PRINT "defmacro!" GOSUB EVAL_GET_A2: REM set a1% and a2% - ZL%=ZL%+1:ZZ%(ZL%)=A1%: REM push A1% - A%=A2%:GOSUB EVAL: REM eval a2 - A1%=ZZ%(ZL%):ZL%=ZL%-1: REM pop A1% + X=X+1:S%(X)=A1%: REM push A1% + A=A2%:GOSUB EVAL: REM eval a2 + A1%=S%(X):X=X-1: REM pop A1% REM change function to macro - Z%(R%,0)=Z%(R%,0)+1 + Z%(R,0)=Z%(R,0)+1 REM set a1 in env to a2 - K%=A1%:V%=R%:GOSUB ENV_SET + K=A1%:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_MACROEXPAND: REM PRINT "macroexpand" - R%=Z%(A%,1)+1:GOSUB DEREF_R - A%=R%:GOSUB MACROEXPAND:R%=A% + R=Z%(A,1)+1:GOSUB DEREF_R + A=R:GOSUB MACROEXPAND:R=A REM since we are returning it unevaluated, inc the ref cnt - Z%(R%,0)=Z%(R%,0)+16 + Z%(R,0)=Z%(R,0)+16 GOTO EVAL_RETURN EVAL_TRY: REM PRINT "try*" GOSUB EVAL_GET_A1: REM set a1%, a2%, and a3% - ZL%=ZL%+1:ZZ%(ZL%)=A%: REM push/save A% - A%=A1%:GOSUB EVAL: REM eval a1 - A%=ZZ%(ZL%):ZL%=ZL%-1: REM pop/restore A% + X=X+1:S%(X)=A: REM push/save A + A=A1%:GOSUB EVAL: REM eval a1 + A=S%(X):X=X-1: REM pop/restore A REM if there is not error or catch block then return - IF ER%=-2 OR Z%(A%,1)=0 THEN GOTO EVAL_RETURN + IF ER=-2 OR Z%(A,1)=0 THEN GOTO EVAL_RETURN REM create environment for the catch block eval - EO%=E%:GOSUB ENV_NEW:E%=R% + O=E:GOSUB ENV_NEW:E=R GOSUB EVAL_GET_A2: REM set a1% and a2% - A%=A2%:GOSUB EVAL_GET_A2: REM set a1% and a2% from catch block + A=A2%:GOSUB EVAL_GET_A2: REM set a1% and a2% from catch block - REM create object for ER%=-1 type raw string errors - IF ER%=-1 THEN AS$=ER$:T%=4:GOSUB STRING:ER%=R%:Z%(R%,0)=Z%(R%,0)+16 + REM create object for ER=-1 type raw string errors + IF ER=-1 THEN AS$=ER$:T=4:GOSUB STRING:ER=R:Z%(R,0)=Z%(R,0)+16 REM bind the catch symbol to the error object - K%=A1%:V%=ER%:GOSUB ENV_SET - AY%=R%:GOSUB RELEASE: REM release out use, env took ownership + K=A1%:V=ER:GOSUB ENV_SET + AY=R:GOSUB RELEASE: REM release out use, env took ownership REM unset error for catch eval - ER%=-2:ER$="" + ER=-2:ER$="" - A%=A2%:GOSUB EVAL + A=A2%:GOSUB EVAL GOTO EVAL_RETURN EVAL_IF: GOSUB EVAL_GET_A1: REM set a1% - REM push A% - ZL%=ZL%+1:ZZ%(ZL%)=A% - A%=A1%:GOSUB EVAL - REM pop A% - A%=ZZ%(ZL%):ZL%=ZL%-1 - IF (R%=0) OR (R%=1) THEN GOTO EVAL_IF_FALSE + REM push A + X=X+1:S%(X)=A + A=A1%:GOSUB EVAL + REM pop A + A=S%(X):X=X-1 + IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A%=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: - AY%=R%:GOSUB RELEASE + AY=R:GOSUB RELEASE REM if no false case (A3%), return nil - IF Z%(Z%(Z%(A%,1),1),1)=0 THEN R%=0:GOTO EVAL_RETURN + IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A%=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + A=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set a1% and a2% - A%=A2%:P%=A1%:GOSUB MAL_FUNCTION + A=A2%:P=A1%:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - ZL%=ZL%+1:ZZ%(ZL%)=3 + X=X+1:S%(X)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: REM if error, return f/args for release by caller - IF ER%<>-2 THEN GOTO EVAL_RETURN + IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - ZL%=ZL%+1:ZZ%(ZL%)=R% + X=X+1:S%(X)=R - F%=R%+1 + F=R+1 - AR%=Z%(R%,1): REM rest - R%=F%:GOSUB DEREF_R:F%=R% + AR=Z%(R,1): REM rest + R=F:GOSUB DEREF_R:F=R - IF (Z%(F%,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F%,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R%=ZZ%(ZL%):ZL%=ZL%-1 - ER%=-1:ER$="apply of non-function":GOTO EVAL_RETURN + R=S%(X):X=X-1 + ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4%=E%: REM save the current environment for release + E4%=E: REM save the current environment for release REM create new environ using env stored with function - EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the - REM stack (ZZ%(ZL%-2)) because our new env refers to it and + REM stack (S%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>ZZ%(ZL%-2) THEN AY%=E4%:GOSUB RELEASE + IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE REM claim the AST before releasing the list containing it - A%=Z%(F%,1):Z%(A%,0)=Z%(A%,0)+16 + A=Z%(F,1):Z%(A,0)=Z%(A,0)+16 REM add AST to pending release queue to free as soon as EVAL - REM actually returns (LV%+1) - ZM%=ZM%+1:ZR%(ZM%,0)=A%:ZR%(ZM%,1)=LV%+1 + REM actually returns (LV+1) + ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV+1 REM pop and release f/args - AY%=ZZ%(ZL%):ZL%=ZL%-1:GOSUB RELEASE + AY=S%(X):X=X-1:GOSUB RELEASE - REM A% set above - E%=R%:GOTO EVAL_TCO_RECUR: REM TCO loop + REM A set above + E=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: - REM AZ%=R%: PR%=1: GOSUB PR_STR - REM PRINT "EVAL_RETURN R%: ["+R$+"] ("+STR$(R%)+"), LV%:"+STR$(LV%)+",ER%:"+STR$(ER%) + REM AZ=R: PR=1: GOSUB PR_STR + REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E%<>ZZ%(ZL%-1) THEN AY%=E%:GOSUB RELEASE + IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE - LV%=LV%-1: REM track basic return stack level + LV=LV-1: REM track basic return stack level REM release everything we couldn't release earlier GOSUB RELEASE_PEND @@ -520,51 +520,51 @@ EVAL: REM trigger GC TA%=FRE(0) - REM pop A% and E% off the stack - E%=ZZ%(ZL%-1):A%=ZZ%(ZL%):ZL%=ZL%-2 + REM pop A and E off the stack + E=S%(X-1):A=S%(X):X=X-2 RETURN -REM PRINT(A%) -> R$ +REM PRINT(A) -> R$ MAL_PRINT: - AZ%=A%:PR%=1:GOSUB PR_STR + AZ=A:PR=1:GOSUB PR_STR RETURN -REM RE(A$) -> R% +REM RE(A$) -> R REM Assume RE% has repl_env REM caller must release result RE: - R1%=0 + R1=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL + A=R:E=RE%:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume RE% has repl_env REP: - R1%=0:R2%=0 + R1=0:R2=0 GOSUB MAL_READ - R1%=R% - IF ER%<>-2 THEN GOTO REP_DONE + R1=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:E%=RE%:GOSUB EVAL - R2%=R% - IF ER%<>-2 THEN GOTO REP_DONE + A=R:E=RE%:GOSUB EVAL + R2=R + IF ER<>-2 THEN GOTO REP_DONE - A%=R%:GOSUB MAL_PRINT + A=R:GOSUB MAL_PRINT RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2%<>0 THEN AY%=R2%:GOSUB RELEASE - IF R1%<>0 THEN AY%=R1%:GOSUB RELEASE + IF R2<>0 THEN AY=R2:GOSUB RELEASE + IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ RETURN @@ -572,65 +572,65 @@ REM MAIN program MAIN: GOSUB INIT_MEMORY - LV%=0 + LV=0 REM create repl_env - EO%=-1:GOSUB ENV_NEW:RE%=R% + O=-1:GOSUB ENV_NEW:RE%=R REM core.EXT: defined in Basic - E%=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + E=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env - ZT%=ZI%: REM top of memory after base repl_env + ZT%=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself A$="(def! *host-language* "+CHR$(34)+"C64 Basic"+CHR$(34)+")" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE A$="(def! not (fn* (a) (if a false true)))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE A$="(def! load-file (fn* (f) (eval (read-string (str " A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE A$="(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs)" A$=A$+" (if (> (count xs) 1) (nth xs 1) (throw "+CHR$(34)+"odd number of" A$=A$+" forms to cond"+CHR$(34)+")) (cons 'cond (rest (rest xs)))))))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE A$="(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs)" A$=A$+" `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REM load the args file A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REM set the argument list A$="(def! *ARGV* (rest -*ARGS*-))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REM get the first argument A$="(first -*ARGS*-)" GOSUB RE REM if there is an argument, then run it as a program - IF R%<>0 THEN AY%=R%:GOSUB RELEASE:GOTO RUN_PROG + IF R<>0 THEN AY=R:GOSUB RELEASE:GOTO RUN_PROG REM no arguments, start REPL loop - IF R%=0 THEN GOTO REPL + IF R=0 THEN GOTO REPL RUN_PROG: REM run a single mal program and exit A$="(load-file (first -*ARGS*-))" GOSUB RE - IF ER%<>-2 THEN GOSUB PRINT_ERROR + IF ER<>-2 THEN GOSUB PRINT_ERROR END REPL: REM print the REPL startup header A$="(println (str "+CHR$(34)+"Mal ["+CHR$(34)+" *host-language* " A$=A$+CHR$(34)+"]"+CHR$(34)+"))" - GOSUB RE:AY%=R%:GOSUB RELEASE + GOSUB RE:AY=R:GOSUB RELEASE REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser @@ -638,7 +638,7 @@ MAIN: A$=R$:GOSUB REP: REM call REP - IF ER%<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP + IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ GOTO REPL_LOOP @@ -649,8 +649,8 @@ MAIN: PRINT_ERROR: REM if the error is an object, then print and free it - IF ER%>=0 THEN AZ%=ER%:PR%=0:GOSUB PR_STR:ER$=R$:AY%=ER%:GOSUB RELEASE + IF ER>=0 THEN AZ=ER:PR=0:GOSUB PR_STR:ER$=R$:AY=ER:GOSUB RELEASE PRINT "Error: "+ER$ - ER%=-2:ER$="" + ER=-2:ER$="" RETURN diff --git a/basic/types.in.bas b/basic/types.in.bas index 13c7932ff0..2c39624ce5 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -3,8 +3,8 @@ REM nil 0 -> (unused) REM boolean 1 -> 0: false, 1: true REM integer 2 -> int value REM float 3 -> ??? -REM string/kw 4 -> ZS$ index -REM symbol 5 -> ZS$ index +REM string/kw 4 -> S$ index +REM symbol 5 -> S$ index REM list next/val 6 -> next Z% index (0 for last) REM followed by value (unless empty) REM vector next/val 7 -> next Z% index (0 for last) @@ -23,22 +23,22 @@ REM reference/ptr 14 -> Z% index / or 0 REM next free ptr 15 -> Z% index / or 0 INIT_MEMORY: - T%=FRE(0) + T=FRE(0) - S1%=2048+512: REM Z% (boxed memory) size (4 bytes each) - S2%=256: REM ZS% (string memory) size (3 bytes each) - S3%=256: REM ZZ% (call stack) size (2 bytes each) - S4%=64: REM ZR% (release stack) size (4 bytes each) + Z1=2048+512: REM Z% (boxed memory) size (4 bytes each) + Z2=256: REM S$ (string memory) size (3 bytes each) + Z3=256: REM S% (call stack) size (2 bytes each) + Z4=64: REM ZR% (release stack) size (4 bytes each) REM global error state REM -2 : no error REM -1 : string error in ER$ REM >=0 : pointer to error object - ER%=-2 + ER=-2 ER$="" REM boxed element memory - DIM Z%(S1%,1): REM TYPE ARRAY + DIM Z%(Z1,1): REM TYPE ARRAY REM Predefine nil, false, true, and an empty list Z%(0,0)=0:Z%(0,1)=0 @@ -48,188 +48,188 @@ INIT_MEMORY: Z%(4,0)=0:Z%(4,1)=0 REM start of unused memory - ZI%=5 + ZI=5 REM start of free list - ZK%=5 + ZK=5 REM string memory storage - ZJ%=0:DIM ZS$(S2%) + ZJ=0:DIM S$(Z2) REM call/logic stack - ZL%=-1:DIM ZZ%(S3%): REM stack of Z% indexes + X=-1:DIM S%(Z3): REM stack of Z% indexes REM pending release stack - ZM%=-1:DIM ZR%(S4%,1): REM stack of Z% indexes + ZM%=-1:DIM ZR%(Z4,1): REM stack of Z% indexes - REM PRINT "Lisp data memory: "+STR$(T%-FRE(0)) + REM PRINT "Lisp data memory: "+STR$(T-FRE(0)) REM PRINT "Interpreter working memory: "+STR$(FRE(0)) RETURN REM memory functions -REM ALLOC(SZ%) -> R% +REM ALLOC(SZ) -> R ALLOC: - REM PRINT "ALLOC SZ%: "+STR$(SZ%)+", ZK%: "+STR$(ZK%) - U3%=ZK% - U4%=ZK% + REM PRINT "ALLOC SZ: "+STR$(SZ)+", ZK: "+STR$(ZK) + U3=ZK + U4=ZK ALLOC_LOOP: - IF U4%=ZI% THEN GOTO ALLOC_UNUSED + IF U4=ZI THEN GOTO ALLOC_UNUSED REM TODO sanity check that type is 15 - IF ((Z%(U4%,0)AND-16)/16)=SZ% THEN GOTO ALLOC_MIDDLE - REM PRINT "ALLOC search: U3%: "+STR$(U3%)+", U4%: "+STR$(U4%) - U3%=U4%: REM previous set to current - U4%=Z%(U4%,1): REM current set to next + IF ((Z%(U4,0)AND-16)/16)=SZ THEN GOTO ALLOC_MIDDLE + REM PRINT "ALLOC search: U3: "+STR$(U3)+", U4: "+STR$(U4) + U3=U4: REM previous set to current + U4=Z%(U4,1): REM current set to next GOTO ALLOC_LOOP ALLOC_MIDDLE: - REM PRINT "ALLOC_MIDDLE: U3%: "+STR$(U3%)+", U4%: "+STR$(U4%) - R%=U4% - REM set free pointer (ZK%) to next free - IF U4%=ZK% THEN ZK%=Z%(U4%,1) + REM PRINT "ALLOC_MIDDLE: U3: "+STR$(U3)+", U4: "+STR$(U4) + R=U4 + REM set free pointer (ZK) to next free + IF U4=ZK THEN ZK=Z%(U4,1) REM set previous free to next free - IF U4%<>ZK% THEN Z%(U3%,1)=Z%(U4%,1) + IF U4<>ZK THEN Z%(U3,1)=Z%(U4,1) RETURN ALLOC_UNUSED: - REM PRINT "ALLOC_UNUSED ZI%: "+STR$(ZI%)+", U3%: "+STR$(U3%)+", U4%: "+STR$(U4%) - R%=U4% - ZI%=ZI%+SZ% - IF U3%=U4% THEN ZK%=ZI% + REM PRINT "ALLOC_UNUSED ZI: "+STR$(ZI)+", U3: "+STR$(U3)+", U4: "+STR$(U4) + R=U4 + ZI=ZI+SZ + IF U3=U4 THEN ZK=ZI REM set previous free to new memory top - IF U3%<>U4% THEN Z%(U3%,1)=ZI% + IF U3<>U4 THEN Z%(U3,1)=ZI RETURN -REM FREE(AY%, SZ%) -> nil +REM FREE(AY, SZ) -> nil FREE: REM assumes reference count cleanup already (see RELEASE) - Z%(AY%,0)=(SZ%*16)+15: REM set type(15) and size - Z%(AY%,1)=ZK% - ZK%=AY% - IF SZ%>=2 THEN Z%(AY%+1,0)=0:Z%(AY%+1,1)=0 - IF SZ%>=3 THEN Z%(AY%+2,0)=0:Z%(AY%+2,1)=0 + Z%(AY,0)=(SZ*16)+15: REM set type(15) and size + Z%(AY,1)=ZK + ZK=AY + IF SZ>=2 THEN Z%(AY+1,0)=0:Z%(AY+1,1)=0 + IF SZ>=3 THEN Z%(AY+2,0)=0:Z%(AY+2,1)=0 RETURN -REM RELEASE(AY%) -> nil -REM R% should not be affected by this call +REM RELEASE(AY) -> nil +REM R should not be affected by this call RELEASE: - RC%=0 + RC=0 GOTO RELEASE_ONE RELEASE_TOP: - IF RC%=0 THEN RETURN + IF RC=0 THEN RETURN REM pop next object to release, decrease remaining count - AY%=ZZ%(ZL%):ZL%=ZL%-1 - RC%=RC%-1 + AY=S%(X):X=X-1 + RC=RC-1 RELEASE_ONE: REM nil, false, true - IF AY%<3 THEN GOTO RELEASE_TOP + IF AY<3 THEN GOTO RELEASE_TOP - U6%=Z%(AY%,0)AND15: REM type + U6=Z%(AY,0)AND15: REM type - REM AZ%=AY%: PR%=1: GOSUB PR_STR - REM PRINT "RELEASE AY%:"+STR$(AY%)+"["+R$+"] (byte0:"+STR$(Z%(AY%,0))+")" + REM AZ=AY: PR=1: GOSUB PR_STR + REM PRINT "RELEASE AY:"+STR$(AY)+"["+R$+"] (byte0:"+STR$(Z%(AY,0))+")" REM sanity check not already freed - IF (U6%)=15 THEN ER%=-1:ER$="Free of free memory: "+STR$(AY%):RETURN - IF U6%=14 THEN GOTO RELEASE_REFERENCE - IF Z%(AY%,0)<15 THEN ER%=-1:ER$="Free of freed object: "+STR$(AY%):RETURN + IF (U6)=15 THEN ER=-1:ER$="Free of free memory: "+STR$(AY):RETURN + IF U6=14 THEN GOTO RELEASE_REFERENCE + IF Z%(AY,0)<15 THEN ER=-1:ER$="Free of freed object: "+STR$(AY):RETURN REM decrease reference count by one - Z%(AY%,0)=Z%(AY%,0)-16 + Z%(AY,0)=Z%(AY,0)-16 REM our reference count is not 0, so don't release - IF Z%(AY%,0)>=16 GOTO RELEASE_TOP + IF Z%(AY,0)>=16 GOTO RELEASE_TOP REM switch on type - IF (U6%<=5) OR (U6%=9) THEN GOTO RELEASE_SIMPLE - IF (U6%>=6) AND (U6%<=8) THEN GOTO RELEASE_SEQ - IF U6%=10 THEN GOTO RELEASE_MAL_FUNCTION - IF U6%=11 THEN GOTO RELEASE_MAL_FUNCTION - IF U6%=12 THEN GOTO RELEASE_ATOM - IF U6%=13 THEN GOTO RELEASE_ENV - IF U6%=15 THEN ER%=-1:ER$="RELEASE of already freed: "+STR$(AY%):RETURN - ER%=-1:ER$="RELEASE not defined for type "+STR$(U6%):RETURN + IF (U6<=5) OR (U6=9) THEN GOTO RELEASE_SIMPLE + IF (U6>=6) AND (U6<=8) THEN GOTO RELEASE_SEQ + IF U6=10 THEN GOTO RELEASE_MAL_FUNCTION + IF U6=11 THEN GOTO RELEASE_MAL_FUNCTION + IF U6=12 THEN GOTO RELEASE_ATOM + IF U6=13 THEN GOTO RELEASE_ENV + IF U6=15 THEN ER=-1:ER$="RELEASE of already freed: "+STR$(AY):RETURN + ER=-1:ER$="RELEASE not defined for type "+STR$(U6):RETURN RELEASE_SIMPLE: REM simple type (no recursing), just call FREE on it - SZ%=1:GOSUB FREE + SZ=1:GOSUB FREE GOTO RELEASE_TOP RELEASE_SIMPLE_2: REM free the current element and continue - SZ%=2:GOSUB FREE + SZ=2:GOSUB FREE GOTO RELEASE_TOP RELEASE_SEQ: - IF Z%(AY%,1)=0 THEN GOTO RELEASE_SIMPLE_2 - IF Z%(AY%+1,0)<>14 THEN ER%=-1:ER$="invalid list value"+STR$(AY%+1):RETURN + IF Z%(AY,1)=0 THEN GOTO RELEASE_SIMPLE_2 + IF Z%(AY+1,0)<>14 THEN ER=-1:ER$="invalid list value"+STR$(AY+1):RETURN REM add value and next element to stack - RC%=RC%+2:ZL%=ZL%+2:ZZ%(ZL%-1)=Z%(AY%+1,1):ZZ%(ZL%)=Z%(AY%,1) + RC=RC+2:X=X+2:S%(X-1)=Z%(AY+1,1):S%(X)=Z%(AY,1) GOTO RELEASE_SIMPLE_2 RELEASE_ATOM: REM add contained/referred value - RC%=RC%+1:ZL%=ZL%+1:ZZ%(ZL%)=Z%(AY%,1) + RC=RC+1:X=X+1:S%(X)=Z%(AY,1) REM free the atom itself GOTO RELEASE_SIMPLE RELEASE_MAL_FUNCTION: REM add ast, params and environment to stack - RC%=RC%+3:ZL%=ZL%+3 - ZZ%(ZL%-2)=Z%(AY%,1):ZZ%(ZL%-1)=Z%(AY%+1,0):ZZ%(ZL%)=Z%(AY%+1,1) + RC=RC+3:X=X+3 + S%(X-2)=Z%(AY,1):S%(X-1)=Z%(AY+1,0):S%(X)=Z%(AY+1,1) REM free the current 2 element mal_function and continue - SZ%=2:GOSUB FREE + SZ=2:GOSUB FREE GOTO RELEASE_TOP RELEASE_ENV: REM add the hashmap data to the stack - RC%=RC%+1:ZL%=ZL%+1:ZZ%(ZL%)=Z%(AY%,1) + RC=RC+1:X=X+1:S%(X)=Z%(AY,1) REM if no outer set - IF Z%(AY%+1,1)=-1 THEN GOTO RELEASE_ENV_FREE + IF Z%(AY+1,1)=-1 THEN GOTO RELEASE_ENV_FREE REM add outer environment to the stack - RC%=RC%+1:ZL%=ZL%+1:ZZ%(ZL%)=Z%(AY%+1,1) + RC=RC+1:X=X+1:S%(X)=Z%(AY+1,1) RELEASE_ENV_FREE: REM free the current 2 element environment and continue - SZ%=2:GOSUB FREE + SZ=2:GOSUB FREE GOTO RELEASE_TOP RELEASE_REFERENCE: - IF Z%(AY%,1)=0 THEN GOTO RELEASE_SIMPLE + IF Z%(AY,1)=0 THEN GOTO RELEASE_SIMPLE REM add the referred element to the stack - RC%=RC%+1:ZL%=ZL%+1:ZZ%(ZL%)=Z%(AY%,1) + RC=RC+1:X=X+1:S%(X)=Z%(AY,1) REM free the current element and continue - SZ%=1:GOSUB FREE + SZ=1:GOSUB FREE GOTO RELEASE_TOP -REM RELEASE_PEND(LV%) -> nil +REM RELEASE_PEND(LV) -> nil RELEASE_PEND: IF ZM%<0 THEN RETURN - IF ZR%(ZM%,1)<=LV% THEN RETURN + IF ZR%(ZM%,1)<=LV THEN RETURN REM PRINT "RELEASE_PEND releasing:"+STR$(ZR%(ZM%,0)) - AY%=ZR%(ZM%,0):GOSUB RELEASE + AY=ZR%(ZM%,0):GOSUB RELEASE ZM%=ZM%-1 GOTO RELEASE_PEND -REM DEREF_R(R%) -> R% +REM DEREF_R(R) -> R DEREF_R: - IF (Z%(R%,0)AND15)=14 THEN R%=Z%(R%,1):GOTO DEREF_R + IF (Z%(R,0)AND15)=14 THEN R=Z%(R,1):GOTO DEREF_R RETURN -REM DEREF_A(A%) -> A% +REM DEREF_A(A) -> A DEREF_A: - IF (Z%(A%,0)AND15)=14 THEN A%=Z%(A%,1):GOTO DEREF_A + IF (Z%(A,0)AND15)=14 THEN A=Z%(A,1):GOTO DEREF_A RETURN -REM DEREF_B(B%) -> B% +REM DEREF_B(B) -> B DEREF_B: - IF (Z%(B%,0)AND15)=14 THEN B%=Z%(B%,1):GOTO DEREF_B + IF (Z%(B,0)AND15)=14 THEN B=Z%(B,1):GOTO DEREF_B RETURN CHECK_FREE_LIST: REM start and accumulator - P1%=ZK% + P1%=ZK P2%=0 CHECK_FREE_LIST_LOOP: - IF P1%>=ZI% THEN GOTO CHECK_FREE_LIST_DONE + IF P1%>=ZI THEN GOTO CHECK_FREE_LIST_DONE IF (Z%(P1%,0)AND15)<>15 THEN P2%=-1:GOTO CHECK_FREE_LIST_DONE P2%=P2%+(Z%(P1%,0)AND-16)/16 P1%=Z%(P1%,1) @@ -241,66 +241,66 @@ CHECK_FREE_LIST: REM general functions -REM EQUAL_Q(A%, B%) -> R% +REM EQUAL_Q(A, B) -> R EQUAL_Q: GOSUB DEREF_A GOSUB DEREF_B - R%=0 - U1%=(Z%(A%,0)AND15) - U2%=(Z%(B%,0)AND15) - IF NOT ((U1%=U2%) OR ((U1%=6 OR U1%=7) AND (U2%=6 OR U2%=7))) THEN RETURN - IF U1%=6 THEN GOTO EQUAL_Q_SEQ - IF U1%=7 THEN GOTO EQUAL_Q_SEQ - IF U1%=8 THEN GOTO EQUAL_Q_HM + R=0 + U1=(Z%(A,0)AND15) + U2=(Z%(B,0)AND15) + IF NOT ((U1=U2) OR ((U1=6 OR U1=7) AND (U2=6 OR U2=7))) THEN RETURN + IF U1=6 THEN GOTO EQUAL_Q_SEQ + IF U1=7 THEN GOTO EQUAL_Q_SEQ + IF U1=8 THEN GOTO EQUAL_Q_HM - IF Z%(A%,1)=Z%(B%,1) THEN R%=1 + IF Z%(A,1)=Z%(B,1) THEN R=1 RETURN EQUAL_Q_SEQ: - IF (Z%(A%,1)=0) AND (Z%(B%,1)=0) THEN R%=1:RETURN - IF (Z%(A%,1)=0) OR (Z%(B%,1)=0) THEN R%=0:RETURN + IF (Z%(A,1)=0) AND (Z%(B,1)=0) THEN R=1:RETURN + IF (Z%(A,1)=0) OR (Z%(B,1)=0) THEN R=0:RETURN - REM push A% and B% - ZL%=ZL%+2:ZZ%(ZL%-1)=A%:ZZ%(ZL%)=B% + REM push A and B + X=X+2:S%(X-1)=A:S%(X)=B REM compare the elements - A%=Z%(A%+1,1):B%=Z%(B%+1,1):GOSUB EQUAL_Q - REM pop A% and B% - A%=ZZ%(ZL%-1):B%=ZZ%(ZL%):ZL%=ZL%-2 - IF R%=0 THEN RETURN + A=Z%(A+1,1):B=Z%(B+1,1):GOSUB EQUAL_Q + REM pop A and B + A=S%(X-1):B=S%(X):X=X-2 + IF R=0 THEN RETURN REM next elements of the sequences - A%=Z%(A%,1):B%=Z%(B%,1):GOTO EQUAL_Q_SEQ + A=Z%(A,1):B=Z%(B,1):GOTO EQUAL_Q_SEQ EQUAL_Q_HM: - R%=0 + R=0 RETURN REM string functions -REM STRING_(AS$) -> R% +REM STRING_(AS$) -> R REM intern string (returns string index, not Z% index) STRING_: - IF ZJ%=0 THEN GOTO STRING_NOT_FOUND + IF ZJ=0 THEN GOTO STRING_NOT_FOUND - REM search for matching string in ZS$ - FOR I=0 TO ZJ%-1 - IF AS$=ZS$(I) THEN R%=I:RETURN + REM search for matching string in S$ + FOR I=0 TO ZJ-1 + IF AS$=S$(I) THEN R=I:RETURN NEXT I STRING_NOT_FOUND: - ZS$(ZJ%)=AS$ - R%=ZJ% - ZJ%=ZJ%+1 + S$(ZJ)=AS$ + R=ZJ + ZJ=ZJ+1 RETURN -REM STRING(AS$, T%) -> R% +REM STRING(AS$, T) -> R REM intern string and allocate reference (return Z% index) STRING: GOSUB STRING_ - TS%=R% - SZ%=1:GOSUB ALLOC - Z%(R%,0)=T% - Z%(R%,1)=TS% + TS%=R + SZ=1:GOSUB ALLOC + Z%(R,0)=T + Z%(R,1)=TS% RETURN REM REPLACE(R$, S1$, S2$) -> R$ @@ -319,211 +319,211 @@ REPLACE: REM list functions -REM LIST_Q(A%) -> R% +REM LIST_Q(A) -> R LIST_Q: - R%=0 - IF (Z%(A%,0)AND15)=6 THEN R%=1 + R=0 + IF (Z%(A,0)AND15)=6 THEN R=1 RETURN -REM EMPTY_Q(A%) -> R% +REM EMPTY_Q(A) -> R EMPTY_Q: - R%=0 - IF Z%(A%,1)=0 THEN R%=1 + R=0 + IF Z%(A,1)=0 THEN R=1 RETURN -REM COUNT(A%) -> R% +REM COUNT(A) -> R COUNT: - R%=-1 + R=-1 DO_COUNT_LOOP: - R%=R%+1 - IF Z%(A%,1)<>0 THEN A%=Z%(A%,1):GOTO DO_COUNT_LOOP + R=R+1 + IF Z%(A,1)<>0 THEN A=Z%(A,1):GOTO DO_COUNT_LOOP RETURN -REM LAST(A%) -> R% +REM LAST(A) -> R LAST: REM TODO check that actually a list/vector - IF Z%(A%,1)=0 THEN R%=0:RETURN: REM empty seq, return nil - T6%=0 + IF Z%(A,1)=0 THEN R=0:RETURN: REM empty seq, return nil + T6=0 LAST_LOOP: - IF Z%(A%,1)=0 THEN GOTO LAST_DONE: REM end, return previous value - T6%=A%: REM current becomes previous entry - A%=Z%(A%,1): REM next entry + IF Z%(A,1)=0 THEN GOTO LAST_DONE: REM end, return previous value + T6=A: REM current becomes previous entry + A=Z%(A,1): REM next entry GOTO LAST_LOOP LAST_DONE: - R%=T6%+1:GOSUB DEREF_R - Z%(R%,0)=Z%(R%,0)+16 + R=T6+1:GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+16 RETURN -REM CONS(A%,B%) -> R% +REM CONS(A,B) -> R CONS: - SZ%=2:GOSUB ALLOC - Z%(R%,0)=6+16 - Z%(R%,1)=B% - Z%(R%+1,0)=14 - Z%(R%+1,1)=A% + SZ=2:GOSUB ALLOC + Z%(R,0)=6+16 + Z%(R,1)=B + Z%(R+1,0)=14 + Z%(R+1,1)=A REM inc ref cnt of item we are including - Z%(A%,0)=Z%(A%,0)+16 + Z%(A,0)=Z%(A,0)+16 REM inc ref cnt of list we are prepending - Z%(B%,0)=Z%(B%,0)+16 + Z%(B,0)=Z%(B,0)+16 RETURN -REM SLICE(A%,B%,C%) -> R% -REM make copy of sequence A% from index B% to C% -REM returns R6% as reference to last element of slice -REM returns A% as next element following slice (of original) +REM SLICE(A,B,C) -> R +REM make copy of sequence A from index B to C +REM returns R6 as reference to last element of slice +REM returns A as next element following slice (of original) SLICE: I=0 - R5%=-1: REM temporary for return as R% - R6%=0: REM previous list element + R5=-1: REM temporary for return as R + R6=0: REM previous list element SLICE_LOOP: REM always allocate at least one list element - SZ%=2:GOSUB ALLOC - Z%(R%,0)=6+16:Z%(R%,1)=0:Z%(R%+1,0)=14:Z%(R%+1,1)=0 - IF R5%=-1 THEN R5%=R% - IF R5%<>-1 THEN Z%(R6%,1)=R% - REM advance A% to position B% + SZ=2:GOSUB ALLOC + Z%(R,0)=6+16:Z%(R,1)=0:Z%(R+1,0)=14:Z%(R+1,1)=0 + IF R5=-1 THEN R5=R + IF R5<>-1 THEN Z%(R6,1)=R + REM advance A to position B SLICE_FIND_B: - IF I0 THEN A%=Z%(A%,1):I=I+1:GOTO SLICE_FIND_B - REM if current position is C%, then return - IF C%<>-1 AND I>=C% THEN R%=R5%:RETURN - REM if we reached end of A%, then return - IF Z%(A%,1)=0 THEN R%=R5%:RETURN - R6%=R%: REM save previous list element + IF I0 THEN A=Z%(A,1):I=I+1:GOTO SLICE_FIND_B + REM if current position is C, then return + IF C<>-1 AND I>=C THEN R=R5:RETURN + REM if we reached end of A, then return + IF Z%(A,1)=0 THEN R=R5:RETURN + R6=R: REM save previous list element REM copy value and inc ref cnt - Z%(R6%+1,1)=Z%(A%+1,1) - R%=A%+1:GOSUB DEREF_R:Z%(R%,0)=Z%(R%,0)+16 - REM advance to next element of A% - A%=Z%(A%,1) + Z%(R6+1,1)=Z%(A+1,1) + R=A+1:GOSUB DEREF_R:Z%(R,0)=Z%(R,0)+16 + REM advance to next element of A + A=Z%(A,1) I=I+1 GOTO SLICE_LOOP -REM LIST2(B2%,B1%) -> R% +REM LIST2(B2%,B1%) -> R LIST2: REM terminator - SZ%=2:GOSUB ALLOC:TB%=R% - Z%(R%,0)=6+16:Z%(R%,1)=0:Z%(R%+1,0)=0:Z%(R%+1,1)=0 + SZ=2:GOSUB ALLOC:TB%=R + Z%(R,0)=6+16:Z%(R,1)=0:Z%(R+1,0)=0:Z%(R+1,1)=0 REM second element is B1% - SZ%=2:GOSUB ALLOC:TC%=R% - Z%(R%,0)=6+16:Z%(R%,1)=TB%:Z%(R%+1,0)=14:Z%(R%+1,1)=B1% + SZ=2:GOSUB ALLOC:TC%=R + Z%(R,0)=6+16:Z%(R,1)=TB%:Z%(R+1,0)=14:Z%(R+1,1)=B1% Z%(B1%,0)=Z%(B1%,0)+16 REM first element is B2% - SZ%=2:GOSUB ALLOC - Z%(R%,0)=6+16:Z%(R%,1)=TC%:Z%(R%+1,0)=14:Z%(R%+1,1)=B2% + SZ=2:GOSUB ALLOC + Z%(R,0)=6+16:Z%(R,1)=TC%:Z%(R+1,0)=14:Z%(R+1,1)=B2% Z%(B2%,0)=Z%(B2%,0)+16 RETURN -REM LIST3(B3%,B2%,B1%) -> R% +REM LIST3(B3%,B2%,B1%) -> R LIST3: - GOSUB LIST2:TC%=R% + GOSUB LIST2:TC%=R REM first element is B3% - SZ%=2:GOSUB ALLOC - Z%(R%,0)=6+16:Z%(R%,1)=TC%:Z%(R%+1,0)=14:Z%(R%+1,1)=B3% + SZ=2:GOSUB ALLOC + Z%(R,0)=6+16:Z%(R,1)=TC%:Z%(R+1,0)=14:Z%(R+1,1)=B3% Z%(B3%,0)=Z%(B3%,0)+16 RETURN REM hashmap functions -REM HASHMAP() -> R% +REM HASHMAP() -> R HASHMAP: - SZ%=2:GOSUB ALLOC - Z%(R%,0)=8+16 - Z%(R%,1)=0 - Z%(R%+1,0)=14 - Z%(R%+1,1)=0 + SZ=2:GOSUB ALLOC + Z%(R,0)=8+16 + Z%(R,1)=0 + Z%(R+1,0)=14 + Z%(R+1,1)=0 RETURN -REM ASSOC1(HM%, K%, V%) -> R% +REM ASSOC1(H, K, V) -> R ASSOC1: REM deref to actual key and value - R%=K%:GOSUB DEREF_R:K%=R% - R%=V%:GOSUB DEREF_R:V%=R% + R=K:GOSUB DEREF_R:K=R + R=V:GOSUB DEREF_R:V=R REM inc ref count of key and value - Z%(K%,0)=Z%(K%,0)+16 - Z%(V%,0)=Z%(V%,0)+16 - SZ%=4:GOSUB ALLOC + Z%(K,0)=Z%(K,0)+16 + Z%(V,0)=Z%(V,0)+16 + SZ=4:GOSUB ALLOC REM key ptr - Z%(R%,0)=8+16 - Z%(R%,1)=R%+2: REM point to next element (value) - Z%(R%+1,0)=14 - Z%(R%+1,1)=K% + Z%(R,0)=8+16 + Z%(R,1)=R+2: REM point to next element (value) + Z%(R+1,0)=14 + Z%(R+1,1)=K REM value ptr - Z%(R%+2,0)=8+16 - Z%(R%+2,1)=HM%: REM hashmap to assoc onto - Z%(R%+3,0)=14 - Z%(R%+3,1)=V% + Z%(R+2,0)=8+16 + Z%(R+2,1)=H: REM hashmap to assoc onto + Z%(R+3,0)=14 + Z%(R+3,1)=V RETURN -REM ASSOC1(HM%, K$, V%) -> R% +REM ASSOC1(H, K$, V) -> R ASSOC1_S: REM add the key string, then call ASSOC1 - SZ%=1:GOSUB ALLOC - K%=R% - ZS$(ZJ%)=K$ - Z%(R%,0)=4: REM key ref cnt will be inc'd by ASSOC1 - Z%(R%,1)=ZJ% - ZJ%=ZJ%+1 + SZ=1:GOSUB ALLOC + K=R + S$(ZJ)=K$ + Z%(R,0)=4: REM key ref cnt will be inc'd by ASSOC1 + Z%(R,1)=ZJ + ZJ=ZJ+1 GOSUB ASSOC1 RETURN -REM HASHMAP_GET(HM%, K%) -> R% +REM HASHMAP_GET(H, K) -> R HASHMAP_GET: - H2%=HM% - T1$=ZS$(Z%(K%,1)): REM search key string - T3%=0: REM whether found or not (for HASHMAP_CONTAINS) - R%=0 + H2%=H + T1$=S$(Z%(K,1)): REM search key string + T3=0: REM whether found or not (for HASHMAP_CONTAINS) + R=0 HASHMAP_GET_LOOP: REM no matching key found - IF Z%(H2%,1)=0 THEN R%=0:RETURN + IF Z%(H2%,1)=0 THEN R=0:RETURN REM follow value ptrs - T2%=H2%+1 + T2=H2%+1 HASHMAP_GET_DEREF: - IF Z%(T2%,0)=14 THEN T2%=Z%(T2%,1):GOTO HASHMAP_GET_DEREF + IF Z%(T2,0)=14 THEN T2=Z%(T2,1):GOTO HASHMAP_GET_DEREF REM get key string - T2$=ZS$(Z%(T2%,1)) + T2$=S$(Z%(T2,1)) REM if they are equal, we found it - IF T1$=T2$ THEN T3%=1:R%=Z%(H2%,1)+1:RETURN + IF T1$=T2$ THEN T3=1:R=Z%(H2%,1)+1:RETURN REM skip to next key H2%=Z%(Z%(H2%,1),1) GOTO HASHMAP_GET_LOOP -REM HASHMAP_CONTAINS(HM%, K%) -> R% +REM HASHMAP_CONTAINS(H, K) -> R HASHMAP_CONTAINS: GOSUB HASHMAP_GET - R%=T3% + R=T3 RETURN -REM NATIVE_FUNCTION(A%) -> R% +REM NATIVE_FUNCTION(A) -> R NATIVE_FUNCTION: - SZ%=1:GOSUB ALLOC - Z%(R%,0)=9+16 - Z%(R%,1)=A% + SZ=1:GOSUB ALLOC + Z%(R,0)=9+16 + Z%(R,1)=A RETURN -REM NATIVE_FUNCTION(A%, P%, E%) -> R% +REM MAL_FUNCTION(A, P, E) -> R MAL_FUNCTION: - SZ%=2:GOSUB ALLOC - Z%(A%,0)=Z%(A%,0)+16 - Z%(P%,0)=Z%(P%,0)+16 - Z%(E%,0)=Z%(E%,0)+16 - - Z%(R%,0)=10+16 - Z%(R%,1)=A% - Z%(R%+1,0)=P% - Z%(R%+1,1)=E% + SZ=2:GOSUB ALLOC + Z%(A,0)=Z%(A,0)+16 + Z%(P,0)=Z%(P,0)+16 + Z%(E,0)=Z%(E,0)+16 + + Z%(R,0)=10+16 + Z%(R,1)=A + Z%(R+1,0)=P + Z%(R+1,1)=E RETURN -REM APPLY(F%, AR%) -> R% -REM restores E% +REM APPLY(F, AR) -> R +REM restores E APPLY: - IF (Z%(F%,0)AND15)=9 THEN GOTO DO_APPLY_FUNCTION - IF (Z%(F%,0)AND15)=10 THEN GOTO DO_APPLY_MAL_FUNCTION - IF (Z%(F%,0)AND15)=11 THEN GOTO DO_APPLY_MAL_FUNCTION + IF (Z%(F,0)AND15)=9 THEN GOTO DO_APPLY_FUNCTION + IF (Z%(F,0)AND15)=10 THEN GOTO DO_APPLY_MAL_FUNCTION + IF (Z%(F,0)AND15)=11 THEN GOTO DO_APPLY_MAL_FUNCTION DO_APPLY_FUNCTION: GOSUB DO_FUNCTION @@ -531,17 +531,17 @@ APPLY: RETURN DO_APPLY_MAL_FUNCTION: - ZL%=ZL%+1:ZZ%(ZL%)=E%: REM save the current environment + X=X+1:S%(X)=E: REM save the current environment REM create new environ using env and params stored in the REM function and bind the params to the apply arguments - EO%=Z%(F%+1,1):BI%=Z%(F%+1,0):EX%=AR%:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS - A%=Z%(F%,1):E%=R%:GOSUB EVAL + A=Z%(F,1):E=R:GOSUB EVAL - AY%=E%:GOSUB RELEASE: REM release the new environment + AY=E:GOSUB RELEASE: REM release the new environment - E%=ZZ%(ZL%):ZL%=ZL%-1: REM pop/restore the saved environment + E=S%(X):X=X-1: REM pop/restore the saved environment RETURN diff --git a/basic/variables.txt b/basic/variables.txt new file mode 100644 index 0000000000..c11b7aa42b --- /dev/null +++ b/basic/variables.txt @@ -0,0 +1,48 @@ +Global Unique: + +Z% : boxed memory values +ZI : start of unused memory (index into Z%) +ZK : start of free list (index into Z%) + +S$ : string memory storage +ZJ : next free index in S$ + +S% : logic/call stack (Z% indexes) +X : top element of S% stack + +ZR% : pending release stack (index into Z%, eval level) +ZM% : top element of ZR% stack + +RE% : root repl environment + +ER : error type (-2: none, -1: string, >=0: object) +ER$ : error string (ER=-1) + +LV : EVAL stack call level/depth + +Calling arguments/temporaries: + +A : common call arguments (especially EVAL, EVAL_AST) +B : common call arguments +C : common call arguments +E : environment (EVAL, EVAL_AST) +F : function +H : hash map +K : hash map key (Z% index) +K$ : hash map key string +O : outer environment +P : MAL_FUNCTION +R : common return value +T : common temp, type +V : hash map value + +SZ : size argument to ALLOC + +Reused/temporaries: + +I : STRING, REPLACE, SLICE, PR_MEMORY, PR_OBJECT +J : REPLACE + +Unused: + +D, G, L, M, N, Q, U, W, Y From a742287e0e83294829293a4063adeb796e5100f3 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 14 Oct 2016 23:48:03 -0500 Subject: [PATCH 0172/2308] Basic: smarter ALLOC. Keywords. Vector fixes. - Modify ALLOC to take a type (rather than size) and take default values to set for the 1-3 values/pointers. Let alloc do the ownership taking of the referred values when appropriate. - Add FORCE_SEQ_TYPE function to coerce sequence to given type. Fixes apply and rest on vector. Simplifies concat. - Use a double ON GOTO structure for calling the native functions in DO_FUNCTION. - Add some stub core functions. - Move CHECK_FREE_LIST to debug.in.bas - All changes together save over 1K --- basic/core.in.bas | 156 ++++++++++++++++----------------- basic/debug.in.bas | 15 ++++ basic/env.in.bas | 10 +-- basic/printer.in.bas | 8 +- basic/reader.in.bas | 33 +++---- basic/step2_eval.in.bas | 29 ++---- basic/step3_env.in.bas | 29 ++---- basic/step4_if_fn_do.in.bas | 14 +-- basic/step5_tco.in.bas | 14 +-- basic/step6_file.in.bas | 14 +-- basic/step7_quote.in.bas | 17 ++-- basic/step8_macros.in.bas | 17 ++-- basic/step9_try.in.bas | 17 ++-- basic/stepA_mal.in.bas | 17 ++-- basic/types.in.bas | 170 ++++++++++++++++-------------------- basic/variables.txt | 3 + 16 files changed, 243 insertions(+), 320 deletions(-) diff --git a/basic/core.in.bas b/basic/core.in.bas index fcbd6d32a1..b3dccb6fb2 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -9,24 +9,21 @@ DO_FUNCTION: R=Z%(AR,1)+1:GOSUB DEREF_R:AB=R REM Switch on the function number - IF FF>=61 THEN ER=-1:ER$="unknown function"+STR$(FF):RETURN - IF FF>=53 THEN DO_53 - IF FF>=39 THEN DO_39 - IF FF>=27 THEN DO_27 - IF FF>=18 THEN DO_18 - IF FF>=11 THEN DO_11 - - ON FF GOTO DO_EQUAL_Q,DO_THROW,DO_NIL_Q,DO_TRUE_Q,DO_FALSE_Q,DO_STRING_Q,DO_SYMBOL,DO_SYMBOL_Q - DO_11: - ON FF-10 GOTO DO_PR_STR,DO_STR,DO_PRN,DO_PRINTLN,DO_READ_STRING,DO_READLINE,DO_SLURP - DO_18: - ON FF-17 GOTO DO_LT,DO_LTE,DO_GT,DO_GTE,DO_ADD,DO_SUB,DO_MULT,DO_DIV,DO_TIME_MS - DO_27: - ON FF-26 GOTO DO_LIST,DO_LIST_Q,DO_VECTOR,DO_VECTOR_Q,DO_HASH_MAP,DO_MAP_Q - DO_39: - ON FF-38 GOTO DO_SEQUENTIAL_Q,DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_APPLY,DO_MAP - DO_53: - ON FF-52 GOTO DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_SWAP_BANG,DO_PR_MEMORY,DO_PR_MEMORY_SUMMARY,DO_EVAL + IF FF>58 THEN ER=-1:ER$="unknown function"+STR$(FF):RETURN + ON FF/10+1 GOTO DO_1_9,DO_10_19,DO_20_29,DO_30_39,DO_40_49,DO_50_59 + + DO_1_9: + ON FF GOTO DO_EQUAL_Q,DO_THROW,DO_NIL_Q,DO_TRUE_Q,DO_FALSE_Q,DO_STRING_Q,DO_SYMBOL,DO_SYMBOL_Q,DO_KEYWORD + DO_10_19: + ON FF-9 GOTO DO_KEYWORD_Q,DO_PR_STR,DO_STR,DO_PRN,DO_PRINTLN,DO_READ_STRING,DO_READLINE,DO_SLURP,DO_LT,DO_LTE + DO_20_29: + ON FF-19 GOTO DO_GT,DO_GTE,DO_ADD,DO_SUB,DO_MULT,DO_DIV,DO_TIME_MS,DO_LIST,DO_LIST_Q,DO_VECTOR + DO_30_39: + ON FF-29 GOTO DO_VECTOR_Q,DO_HASH_MAP,DO_MAP_Q,DO_THROW,DO_THROW,DO_THROW,DO_THROW,DO_THROW,DO_THROW,DO_SEQUENTIAL_Q + DO_40_49: + ON FF-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_APPLY,DO_MAP,DO_THROW + DO_50_59: + ON FF-49 GOTO DO_THROW,DO_THROW,DO_THROW,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_SWAP_BANG,DO_EVAL DO_EQUAL_Q: A=AA:B=AB:GOSUB EQUAL_Q @@ -54,20 +51,33 @@ DO_FUNCTION: IF (Z%(AA,0)AND15)=4 THEN R=2 RETURN DO_SYMBOL: - R=0 + T=5:L=Z%(AA,1):GOSUB ALLOC RETURN DO_SYMBOL_Q: R=1 IF (Z%(AA,0)AND15)=5 THEN R=2 RETURN + DO_KEYWORD: + A=Z%(AA,1) + AS$=S$(A) + IF MID$(AS$,1,1)<>CHR$(127) THEN AS$=CHR$(127)+AS$ + GOSUB STRING_ + T=4:L=R:GOSUB ALLOC + RETURN + DO_KEYWORD_Q: + R=1 + IF (Z%(AA,0)AND15)<>4 THEN RETURN + IF MID$(S$(Z%(AA,1)),1,1)<>CHR$(127) THEN RETURN + R=2 + RETURN DO_PR_STR: AZ=AR:PR=1:SE$=" ":GOSUB PR_STR_SEQ - AS$=R$:T=4+16:GOSUB STRING + AS$=R$:T=4:GOSUB STRING RETURN DO_STR: AZ=AR:PR=0:SE$="":GOSUB PR_STR_SEQ - AS$=R$:T=4+16:GOSUB STRING + AS$=R$:T=4:GOSUB STRING RETURN DO_PRN: AZ=AR:PR=1:SE$=" ":GOSUB PR_STR_SEQ @@ -87,7 +97,6 @@ DO_FUNCTION: A$=S$(Z%(AA,1)):GOSUB READLINE IF EOF=1 THEN EOF=0:R=0:RETURN AS$=R$:T=4:GOSUB STRING - Z%(R,0)=Z%(R,0)+16 RETURN DO_SLURP: R$="" @@ -104,7 +113,7 @@ DO_FUNCTION: GOTO DO_SLURP_LOOP DO_SLURP_DONE: CLOSE 1 - AS$=R$:T=4+16:GOSUB STRING + AS$=R$:T=4:GOSUB STRING RETURN DO_LT: @@ -125,24 +134,16 @@ DO_FUNCTION: RETURN DO_ADD: - SZ=1:GOSUB ALLOC - Z%(R,0)=2+16 - Z%(R,1)=Z%(AA,1)+Z%(AB,1) + T=2:L=Z%(AA,1)+Z%(AB,1):GOSUB ALLOC RETURN DO_SUB: - SZ=1:GOSUB ALLOC - Z%(R,0)=2+16 - Z%(R,1)=Z%(AA,1)-Z%(AB,1) + T=2:L=Z%(AA,1)-Z%(AB,1):GOSUB ALLOC RETURN DO_MULT: - SZ=1:GOSUB ALLOC - Z%(R,0)=2+16 - Z%(R,1)=Z%(AA,1)*Z%(AB,1) + T=2:L=Z%(AA,1)*Z%(AB,1):GOSUB ALLOC RETURN DO_DIV: - SZ=1:GOSUB ALLOC - Z%(R,0)=2+16 - Z%(R,1)=Z%(AA,1)/Z%(AB,1) + T=2:L=Z%(AA,1)/Z%(AB,1):GOSUB ALLOC RETURN DO_TIME_MS: R=0 @@ -157,14 +158,14 @@ DO_FUNCTION: R=R+1: REM map to mal false/true RETURN DO_VECTOR: - R=0 + A=AR:T=7:GOSUB FORCE_SEQ_TYPE RETURN DO_VECTOR_Q: R=1 IF (Z%(AA,0)AND15)=7 THEN R=2 RETURN DO_HASH_MAP: - R=0 + A=AR:T=8:GOSUB FORCE_SEQ_TYPE RETURN DO_MAP_Q: R=1 @@ -176,7 +177,7 @@ DO_FUNCTION: IF (Z%(AA,0)AND15)=6 OR (Z%(AA,0)AND15)=7 THEN R=2 RETURN DO_CONS: - A=AA:B=AB:GOSUB CONS + T=6:L=AB:N=AA:GOSUB ALLOC RETURN DO_CONCAT: REM if empty arguments, return empty list @@ -184,16 +185,8 @@ DO_FUNCTION: REM single argument IF Z%(Z%(AR,1),1)<>0 THEN GOTO DO_CONCAT_MULT - REM if single argument and it's a list, return it - IF (Z%(AA,0)AND15)=6 THEN R=AA:Z%(R,0)=Z%(R,0)+16:RETURN - REM otherwise, copy first element to turn it into a list - B=AA+1:GOSUB DEREF_B: REM value to copy - SZ=2:GOSUB ALLOC - Z%(R,0)=6+16:Z%(R,1)=Z%(AA,1) - Z%(R+1,0)=14:Z%(R+1,1)=B - REM inc ref count of trailing list part and of copied value - Z%(Z%(AA,1),0)=Z%(Z%(AA,1),0)+16 - Z%(B,0)=Z%(B,0)+16 + REM force to list type + A=AA:T=6:GOSUB FORCE_SEQ_TYPE RETURN REM multiple arguments @@ -238,32 +231,35 @@ DO_FUNCTION: Z%(R,0)=Z%(R,0)+16 RETURN DO_FIRST: + IF AA=0 THEN R=0:RETURN IF Z%(AA,1)=0 THEN R=0 IF Z%(AA,1)<>0 THEN R=AA+1:GOSUB DEREF_R IF R<>0 THEN Z%(R,0)=Z%(R,0)+16 RETURN DO_REST: - IF Z%(AA,1)=0 THEN R=AA - IF Z%(AA,1)<>0 THEN R=Z%(AA,1) - Z%(R,0)=Z%(R,0)+16 + IF AA=0 THEN R=3:Z%(R,0)=Z%(R,0)+16:RETURN + IF Z%(AA,1)=0 THEN A=AA + IF Z%(AA,1)<>0 THEN A=Z%(AA,1) + T=6:GOSUB FORCE_SEQ_TYPE RETURN DO_EMPTY_Q: R=1 IF Z%(AA,1)=0 THEN R=2 RETURN DO_COUNT: - A=AA:GOSUB COUNT:R4=R - SZ=1:GOSUB ALLOC - Z%(R,0)=2+16 - Z%(R,1)=R4 + A=AA:GOSUB COUNT + T=2:L=R:GOSUB ALLOC RETURN DO_APPLY: F=AA AR=Z%(AR,1) A=AR:GOSUB COUNT:R4=R + A=Z%(AR+1,1) + REM no intermediate args, but not a list, so convert it first + IF R4<=1 AND (Z%(A,0)AND15)<>6 THEN :T=6:GOSUB FORCE_SEQ_TYPE:GOTO DO_APPLY_2 REM no intermediate args, just call APPLY directly - IF R4<=1 THEN AR=Z%(AR+1,1):GOSUB APPLY:RETURN + IF R4<=1 THEN AR=A:GOSUB APPLY:RETURN REM prepend intermediate args to final args element A=AR:B=0:C=R4-1:GOSUB SLICE @@ -273,24 +269,21 @@ DO_FUNCTION: Z%(R6,1)=Z%(A+1,1) Z%(Z%(A+1,1),0)=Z%(Z%(A+1,1),0)+16 - X=X+1:S%(X)=R: REM push/save new args for release - AR=R:GOSUB APPLY - AY=S%(X):X=X-1:GOSUB RELEASE: REM pop/release new args - RETURN + DO_APPLY_2: + X=X+1:S%(X)=R: REM push/save new args for release + AR=R:GOSUB APPLY + AY=S%(X):X=X-1:GOSUB RELEASE: REM pop/release new args + RETURN DO_MAP: F=AA REM first result list element - SZ=2:GOSUB ALLOC + T=6:L=0:N=0:GOSUB ALLOC REM push future return val, prior entry, F and AB X=X+4:S%(X-3)=R:S%(X-2)=0:S%(X-1)=F:S%(X)=AB DO_MAP_LOOP: - REM set base values - Z%(R,0)=6+16:Z%(R,1)=0 - Z%(R+1,0)=14:Z%(R+1,1)=0 - REM set previous to current if not the first element IF S%(X-2)<>0 THEN Z%(S%(X-2),1)=R REM update previous reference to current @@ -299,15 +292,9 @@ DO_FUNCTION: IF Z%(AB,1)=0 THEN GOTO DO_MAP_DONE REM create argument list for apply call - SZ=2:GOSUB ALLOC - Z%(R,0)=6+16:Z%(R,1)=0 - Z%(R+1,0)=14:Z%(R+1,1)=0 - AR=R: REM save end of list temporarily - SZ=2:GOSUB ALLOC - Z%(R,0)=6+16:Z%(R,1)=AR + Z%(3,0)=Z%(3,0)+16 REM inc ref cnt of referred argument - A=Z%(AB+1,1): Z%(A,0)=Z%(A,0)+16 - Z%(R+1,0)=14:Z%(R+1,1)=A + T=6:L=3:N=Z%(AB+1,1):GOSUB ALLOC REM push argument list X=X+1:S%(X)=R @@ -328,7 +315,7 @@ DO_FUNCTION: AB=S%(X) REM allocate next element - SZ=2:GOSUB ALLOC + T=6:L=0:N=0:GOSUB ALLOC GOTO DO_MAP_LOOP @@ -340,10 +327,7 @@ DO_FUNCTION: RETURN DO_ATOM: - SZ=1:GOSUB ALLOC - Z%(AA,0)=Z%(AA,0)+16: REM inc ref cnt of contained value - Z%(R,0)=12+16 - Z%(R,1)=AA + T=12:L=AA:GOSUB ALLOC RETURN DO_ATOM_Q: R=1 @@ -366,7 +350,7 @@ DO_FUNCTION: F=AB REM add atom to front of the args list - A=Z%(AA,1):B=Z%(Z%(AR,1),1):GOSUB CONS + T=6:L=Z%(Z%(AR,1),1):N=Z%(AA,1):GOSUB ALLOC: REM cons AR=R REM push args for release after @@ -420,6 +404,8 @@ INIT_CORE_NS: K$="string?":A=6:GOSUB INIT_CORE_SET_FUNCTION K$="symbol":A=7:GOSUB INIT_CORE_SET_FUNCTION K$="symbol?":A=8:GOSUB INIT_CORE_SET_FUNCTION + K$="keyword":A=9:GOSUB INIT_CORE_SET_FUNCTION + K$="keyword?":A=10:GOSUB INIT_CORE_SET_FUNCTION K$="pr-str":A=11:GOSUB INIT_CORE_SET_FUNCTION K$="str":A=12:GOSUB INIT_CORE_SET_FUNCTION @@ -445,6 +431,12 @@ INIT_CORE_NS: K$="vector?":A=30:GOSUB INIT_CORE_SET_FUNCTION K$="hash-map":A=31:GOSUB INIT_CORE_SET_FUNCTION K$="map?":A=32:GOSUB INIT_CORE_SET_FUNCTION + K$="assoc":A=33:GOSUB INIT_CORE_SET_FUNCTION + K$="dissoc":A=34:GOSUB INIT_CORE_SET_FUNCTION + K$="get":A=35:GOSUB INIT_CORE_SET_FUNCTION + K$="contains?":A=36:GOSUB INIT_CORE_SET_FUNCTION + K$="keys":A=37:GOSUB INIT_CORE_SET_FUNCTION + K$="vals":A=38:GOSUB INIT_CORE_SET_FUNCTION K$="sequential?":A=39:GOSUB INIT_CORE_SET_FUNCTION K$="cons":A=40:GOSUB INIT_CORE_SET_FUNCTION @@ -457,14 +449,14 @@ INIT_CORE_NS: K$="apply":A=47:GOSUB INIT_CORE_SET_FUNCTION K$="map":A=48:GOSUB INIT_CORE_SET_FUNCTION + K$="with-meta":A=51:GOSUB INIT_CORE_SET_FUNCTION + K$="meta":A=52:GOSUB INIT_CORE_SET_FUNCTION K$="atom":A=53:GOSUB INIT_CORE_SET_FUNCTION K$="atom?":A=54:GOSUB INIT_CORE_SET_FUNCTION K$="deref":A=55:GOSUB INIT_CORE_SET_FUNCTION K$="reset!":A=56:GOSUB INIT_CORE_SET_FUNCTION K$="swap!":A=57:GOSUB INIT_CORE_SET_FUNCTION - K$="pr-memory":A=58:GOSUB INIT_CORE_SET_FUNCTION - K$="pr-memory-summary":A=59:GOSUB INIT_CORE_SET_FUNCTION - K$="eval":A=60:GOSUB INIT_CORE_SET_FUNCTION + K$="eval":A=58:GOSUB INIT_CORE_SET_FUNCTION RETURN diff --git a/basic/debug.in.bas b/basic/debug.in.bas index b300fe9ad8..7f43dde634 100644 --- a/basic/debug.in.bas +++ b/basic/debug.in.bas @@ -1,3 +1,18 @@ +REM CHECK_FREE_LIST +CHECK_FREE_LIST: + REM start and accumulator + P1%=ZK + P2%=0 + CHECK_FREE_LIST_LOOP: + IF P1%>=ZI THEN GOTO CHECK_FREE_LIST_DONE + IF (Z%(P1%,0)AND15)<>15 THEN P2%=-1:GOTO CHECK_FREE_LIST_DONE + P2%=P2%+(Z%(P1%,0)AND-16)/16 + P1%=Z%(P1%,1) + GOTO CHECK_FREE_LIST_LOOP + CHECK_FREE_LIST_DONE: + IF P2%=-1 THEN PRINT "corrupt free list at "+STR$(P1%) + RETURN + PR_MEMORY_SUMMARY: GOSUB CHECK_FREE_LIST: REM get count in P2% PRINT diff --git a/basic/env.in.bas b/basic/env.in.bas index 8a278f757d..858f09b93b 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -3,15 +3,11 @@ REM ENV_NEW(O) -> R ENV_NEW: REM allocate the data hashmap GOSUB HASHMAP - ET%=R + ET=R REM set the outer and data pointer - SZ=2:GOSUB ALLOC - Z%(R,0)=13+16 - Z%(R,1)=ET% - Z%(R+1,0)=13 - Z%(R+1,1)=O - IF O<>-1 THEN Z%(O,0)=Z%(O,0)+16 + T=13:L=R:N=O:GOSUB ALLOC + AY=ET:GOSUB RELEASE: REM environment takes ownership RETURN REM see RELEASE types.in.bas for environment cleanup diff --git a/basic/printer.in.bas b/basic/printer.in.bas index bde9ad4e83..7eb0028c17 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -5,7 +5,7 @@ PR_STR: T=Z%(AZ,0)AND15 REM PRINT "AZ: "+STR$(AZ)+", T: "+STR$(T)+", V: "+STR$(Z%(AZ,1)) IF T=0 THEN R$="nil":RETURN - ON T GOTO PR_BOOLEAN,PR_INTEGER,PR_UNKNOWN,PR_STRING,PR_SYMBOL,PR_SEQ,PR_SEQ,PR_SEQ,PR_FUNCTION,PR_MAL_FUNCTION,PR_MAL_FUNCTION,PR_ATOM,PR_ENV,PR_RECUR,PR_FREE + ON T GOTO PR_BOOLEAN,PR_INTEGER,PR_UNKNOWN,PR_STRING_MAYBE,PR_SYMBOL,PR_SEQ,PR_SEQ,PR_SEQ,PR_FUNCTION,PR_MAL_FUNCTION,PR_MAL_FUNCTION,PR_ATOM,PR_ENV,PR_RECUR,PR_FREE PR_UNKNOWN: R$="#" @@ -24,12 +24,14 @@ PR_STR: REM Remove initial space R$=RIGHT$(R$, LEN(R$)-1) RETURN + PR_STRING_MAYBE: + R$=S$(Z%(AZ,1)) + IF LEN(R$)=0 THEN GOTO PR_STRING + IF MID$(R$,1,1)=CHR$(127) THEN R$=":"+MID$(R$,2,LEN(R$)-1):RETURN PR_STRING: IF PR=1 THEN PR_STRING_READABLY - R$=S$(Z%(AZ,1)) RETURN PR_STRING_READABLY: - R$=S$(Z%(AZ,1)) S1$=CHR$(92):S2$=CHR$(92)+CHR$(92):GOSUB REPLACE: REM escape backslash S1$=CHR$(34):S2$=CHR$(92)+CHR$(34):GOSUB REPLACE: REM escape quotes S1$=CHR$(13):S2$=CHR$(92)+"n":GOSUB REPLACE: REM escape newlines diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 9e67c131a9..f76e7b8c37 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -59,6 +59,7 @@ READ_FORM: IF CH$="-" THEN GOTO READ_SYMBOL_MAYBE IF CH$=CHR$(34) THEN GOTO READ_STRING + IF CH$=":" THEN GOTO READ_KEYWORD IF CH$="(" THEN T=6:GOTO READ_SEQ IF CH$=")" THEN T=6:GOTO READ_SEQ_END IF CH$="[" THEN T=7:GOTO READ_SEQ @@ -79,9 +80,7 @@ READ_FORM: GOTO READ_FORM_DONE READ_NUMBER: REM PRINT "READ_NUMBER" - SZ=1:GOSUB ALLOC - Z%(R,0)=2+16 - Z%(R,1)=VAL(T$) + T=2:L=VAL(T$):GOSUB ALLOC GOTO READ_FORM_DONE READ_MACRO: IDX%=IDX%+LEN(T$) @@ -94,7 +93,9 @@ READ_FORM: SD=S%(X-1):B2%=S%(X):X=X-2: REM pop SD, pop symbol into B2% GOSUB LIST2 - AY=B1%:GOSUB RELEASE: REM release value, list has ownership + REM release values, list has ownership + AY=B1%:GOSUB RELEASE + AY=B2%:GOSUB RELEASE T$="" GOTO READ_FORM_DONE @@ -107,14 +108,18 @@ READ_FORM: S1$=CHR$(92)+"n":S2$=CHR$(13):GOSUB REPLACE: REM unescape newlines S1$=CHR$(92)+CHR$(92):S2$=CHR$(92):GOSUB REPLACE: REM unescape backslashes REM intern string value - AS$=R$:T=4+16:GOSUB STRING + AS$=R$:T=4:GOSUB STRING + GOTO READ_FORM_DONE + READ_KEYWORD: + R$=CHR$(127)+MID$(T$,2,LEN(T$)-1) + AS$=R$:T=4:GOSUB STRING GOTO READ_FORM_DONE READ_SYMBOL_MAYBE: CH$=MID$(T$,2,1) IF CH$>="0" AND CH$<="9" THEN GOTO READ_NUMBER READ_SYMBOL: REM PRINT "READ_SYMBOL" - AS$=T$:T=5+16:GOSUB STRING + AS$=T$:T=5:GOSUB STRING GOTO READ_FORM_DONE READ_SEQ: @@ -122,17 +127,11 @@ READ_FORM: SD=SD+1: REM increase read sequence depth REM allocate first sequence entry and space for value - SZ=2:GOSUB ALLOC + L=0:N=0:GOSUB ALLOC: REM T alread set above REM set reference value/pointer to new embedded sequence IF SD>1 THEN Z%(S%(X)+1,1)=R - REM set the type (with 1 ref cnt) and next pointer to current end - Z%(R,0)=T+16 - Z%(R,1)=0 - Z%(R+1,0)=14 - Z%(R+1,1)=0 - REM push start ptr on the stack X=X+1 S%(X)=R @@ -167,7 +166,8 @@ READ_FORM: REM PRINT "READ_FORM_DONE next list entry" REM allocate new sequence entry and space for value - SZ=2:GOSUB ALLOC + REM set type to previous type, with ref count of 1 (from previous) + T=S%(X-1):L=0:N=0:GOSUB ALLOC REM previous element T7=S%(X) @@ -175,11 +175,6 @@ READ_FORM: Z%(T7,1)=R REM set the list value pointer Z%(T7+1,1)=T8 - REM set type to previous type, with ref count of 1 (from previous) - Z%(R,0)=S%(X-1)+16 - Z%(R,1)=0: REM current end of sequence - Z%(R+1,0)=14 - Z%(R+1,1)=0 IF T7=S%(X-2) THEN GOTO READ_FORM_SKIP_FIRST Z%(T7,1)=R diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index 0098e1cc2b..f4aa6ca106 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -40,8 +40,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry - SZ=2:GOSUB ALLOC + REM allocate the first entry (T already set above) + L=0:N=0:GOSUB ALLOC REM make space on the stack X=X+4 @@ -55,13 +55,6 @@ EVAL_AST: S%(X)=R EVAL_AST_SEQ_LOOP: - REM set new sequence entry type (with 1 ref cnt) - Z%(R,0)=S%(X-3)+16 - Z%(R,1)=0 - REM create value ptr placeholder - Z%(R+1,0)=14 - Z%(R+1,1)=0 - REM update index S%(X-2)=S%(X-2)+1 @@ -91,7 +84,8 @@ EVAL_AST: IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ=2:GOSUB ALLOC + REM same new sequence entry type + T=S%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry Z%(S%(X),1)=R @@ -180,9 +174,6 @@ DO_FUNCTION: R=AR+1:GOSUB DEREF_R:AA=Z%(R,1) R=Z%(AR,1)+1:GOSUB DEREF_R:AB=Z%(R,1) - REM Allocate the return value - SZ=1:GOSUB ALLOC - REM Switch on the function number IF FF=1 THEN GOTO DO_ADD IF FF=2 THEN GOTO DO_SUB @@ -191,20 +182,16 @@ DO_FUNCTION: ER=-1:ER$="unknown function"+STR$(FF):RETURN DO_ADD: - Z%(R,0)=2+16 - Z%(R,1)=AA+AB + T=2:L=AA+AB:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_SUB: - Z%(R,0)=2+16 - Z%(R,1)=AA-AB + T=2:L=AA-AB:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_MULT: - Z%(R,0)=2+16 - Z%(R,1)=AA*AB + T=2:L=AA*AB:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_DIV: - Z%(R,0)=2+16 - Z%(R,1)=AA/AB + T=2:L=AA/AB:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_FUNCTION_DONE: diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index af9481225b..231403cb38 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -38,8 +38,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry - SZ=2:GOSUB ALLOC + REM allocate the first entry (T already set above) + L=0:N=0:GOSUB ALLOC REM make space on the stack X=X+4 @@ -53,13 +53,6 @@ EVAL_AST: S%(X)=R EVAL_AST_SEQ_LOOP: - REM set new sequence entry type (with 1 ref cnt) - Z%(R,0)=S%(X-3)+16 - Z%(R,1)=0 - REM create value ptr placeholder - Z%(R+1,0)=14 - Z%(R+1,1)=0 - REM update index S%(X-2)=S%(X-2)+1 @@ -89,7 +82,8 @@ EVAL_AST: IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ=2:GOSUB ALLOC + REM same new sequence entry type + T=S%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry Z%(S%(X),1)=R @@ -250,9 +244,6 @@ DO_FUNCTION: R=AR+1:GOSUB DEREF_R:AA=Z%(R,1) R=Z%(AR,1)+1:GOSUB DEREF_R:AB=Z%(R,1) - REM Allocate the return value - SZ=1:GOSUB ALLOC - REM Switch on the function number IF FF=1 THEN GOTO DO_ADD IF FF=2 THEN GOTO DO_SUB @@ -261,20 +252,16 @@ DO_FUNCTION: ER=-1:ER$="unknown function"+STR$(FF):RETURN DO_ADD: - Z%(R,0)=2+16 - Z%(R,1)=AA+AB + T=2:L=AA+AB:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_SUB: - Z%(R,0)=2+16 - Z%(R,1)=AA-AB + T=2:L=AA-AB:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_MULT: - Z%(R,0)=2+16 - Z%(R,1)=AA*AB + T=2:L=AA*AB:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_DIV: - Z%(R,0)=2+16 - Z%(R,1)=AA/AB + T=2:L=AA/AB:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_FUNCTION_DONE: diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index 159e96e368..1c8bd3736f 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -39,8 +39,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry - SZ=2:GOSUB ALLOC + REM allocate the first entry (T already set above) + L=0:N=0:GOSUB ALLOC REM make space on the stack X=X+4 @@ -54,13 +54,6 @@ EVAL_AST: S%(X)=R EVAL_AST_SEQ_LOOP: - REM set new sequence entry type (with 1 ref cnt) - Z%(R,0)=S%(X-3)+16 - Z%(R,1)=0 - REM create value ptr placeholder - Z%(R+1,0)=14 - Z%(R+1,1)=0 - REM update index S%(X-2)=S%(X-2)+1 @@ -90,7 +83,8 @@ EVAL_AST: IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ=2:GOSUB ALLOC + REM same new sequence entry type + T=S%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry Z%(S%(X),1)=R diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index cc45c43085..d28fbd8840 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -39,8 +39,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry - SZ=2:GOSUB ALLOC + REM allocate the first entry (T already set above) + L=0:N=0:GOSUB ALLOC REM make space on the stack X=X+4 @@ -54,13 +54,6 @@ EVAL_AST: S%(X)=R EVAL_AST_SEQ_LOOP: - REM set new sequence entry type (with 1 ref cnt) - Z%(R,0)=S%(X-3)+16 - Z%(R,1)=0 - REM create value ptr placeholder - Z%(R+1,0)=14 - Z%(R+1,1)=0 - REM update index S%(X-2)=S%(X-2)+1 @@ -90,7 +83,8 @@ EVAL_AST: IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ=2:GOSUB ALLOC + REM same new sequence entry type + T=S%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry Z%(S%(X),1)=R diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index 7b1cb419b0..4241df47e5 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -39,8 +39,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry - SZ=2:GOSUB ALLOC + REM allocate the first entry (T already set above) + L=0:N=0:GOSUB ALLOC REM make space on the stack X=X+4 @@ -54,13 +54,6 @@ EVAL_AST: S%(X)=R EVAL_AST_SEQ_LOOP: - REM set new sequence entry type (with 1 ref cnt) - Z%(R,0)=S%(X-3)+16 - Z%(R,1)=0 - REM create value ptr placeholder - Z%(R+1,0)=14 - Z%(R+1,1)=0 - REM update index S%(X-2)=S%(X-2)+1 @@ -90,7 +83,8 @@ EVAL_AST: IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ=2:GOSUB ALLOC + REM same new sequence entry type + T=S%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry Z%(S%(X),1)=R diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index c19f6edc93..b6f4548de5 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -31,6 +31,7 @@ QUASIQUOTE: REM ['quote, ast] AS$="quote":T=5:GOSUB STRING B2%=R:B1%=A:GOSUB LIST2 + AY=B2%:GOSUB RELEASE RETURN @@ -67,6 +68,7 @@ QUASIQUOTE: B1%=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership AY=B1%:GOSUB RELEASE + AY=B3%:GOSUB RELEASE RETURN QQ_DEFAULT: @@ -84,6 +86,7 @@ QUASIQUOTE: REM release inner quasiquoted since outer list takes ownership AY=B1%:GOSUB RELEASE AY=B2%:GOSUB RELEASE + AY=B3%:GOSUB RELEASE RETURN @@ -112,8 +115,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry - SZ=2:GOSUB ALLOC + REM allocate the first entry (T already set above) + L=0:N=0:GOSUB ALLOC REM make space on the stack X=X+4 @@ -127,13 +130,6 @@ EVAL_AST: S%(X)=R EVAL_AST_SEQ_LOOP: - REM set new sequence entry type (with 1 ref cnt) - Z%(R,0)=S%(X-3)+16 - Z%(R,1)=0 - REM create value ptr placeholder - Z%(R+1,0)=14 - Z%(R+1,1)=0 - REM update index S%(X-2)=S%(X-2)+1 @@ -163,7 +159,8 @@ EVAL_AST: IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ=2:GOSUB ALLOC + REM same new sequence entry type + T=S%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry Z%(S%(X),1)=R diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index 9ca0dc3518..871d9490d2 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -31,6 +31,7 @@ QUASIQUOTE: REM ['quote, ast] AS$="quote":T=5:GOSUB STRING B2%=R:B1%=A:GOSUB LIST2 + AY=B2%:GOSUB RELEASE RETURN @@ -67,6 +68,7 @@ QUASIQUOTE: B1%=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership AY=B1%:GOSUB RELEASE + AY=B3%:GOSUB RELEASE RETURN QQ_DEFAULT: @@ -84,6 +86,7 @@ QUASIQUOTE: REM release inner quasiquoted since outer list takes ownership AY=B1%:GOSUB RELEASE AY=B2%:GOSUB RELEASE + AY=B3%:GOSUB RELEASE RETURN REM MACROEXPAND(A, E) -> A: @@ -147,8 +150,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry - SZ=2:GOSUB ALLOC + REM allocate the first entry (T already set above) + L=0:N=0:GOSUB ALLOC REM make space on the stack X=X+4 @@ -162,13 +165,6 @@ EVAL_AST: S%(X)=R EVAL_AST_SEQ_LOOP: - REM set new sequence entry type (with 1 ref cnt) - Z%(R,0)=S%(X-3)+16 - Z%(R,1)=0 - REM create value ptr placeholder - Z%(R+1,0)=14 - Z%(R+1,1)=0 - REM update index S%(X-2)=S%(X-2)+1 @@ -198,7 +194,8 @@ EVAL_AST: IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ=2:GOSUB ALLOC + REM same new sequence entry type + T=S%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry Z%(S%(X),1)=R diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index f9a86f3501..407d62d749 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -31,6 +31,7 @@ QUASIQUOTE: REM ['quote, ast] AS$="quote":T=5:GOSUB STRING B2%=R:B1%=A:GOSUB LIST2 + AY=B2%:GOSUB RELEASE RETURN @@ -67,6 +68,7 @@ QUASIQUOTE: B1%=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership AY=B1%:GOSUB RELEASE + AY=B3%:GOSUB RELEASE RETURN QQ_DEFAULT: @@ -84,6 +86,7 @@ QUASIQUOTE: REM release inner quasiquoted since outer list takes ownership AY=B1%:GOSUB RELEASE AY=B2%:GOSUB RELEASE + AY=B3%:GOSUB RELEASE RETURN REM MACROEXPAND(A, E) -> A: @@ -147,8 +150,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry - SZ=2:GOSUB ALLOC + REM allocate the first entry (T already set above) + L=0:N=0:GOSUB ALLOC REM make space on the stack X=X+4 @@ -162,13 +165,6 @@ EVAL_AST: S%(X)=R EVAL_AST_SEQ_LOOP: - REM set new sequence entry type (with 1 ref cnt) - Z%(R,0)=S%(X-3)+16 - Z%(R,1)=0 - REM create value ptr placeholder - Z%(R+1,0)=14 - Z%(R+1,1)=0 - REM update index S%(X-2)=S%(X-2)+1 @@ -198,7 +194,8 @@ EVAL_AST: IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ=2:GOSUB ALLOC + REM same new sequence entry type + T=S%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry Z%(S%(X),1)=R diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index f2d0effb60..ed39522c25 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -31,6 +31,7 @@ QUASIQUOTE: REM ['quote, ast] AS$="quote":T=5:GOSUB STRING B2%=R:B1%=A:GOSUB LIST2 + AY=B2%:GOSUB RELEASE RETURN @@ -67,6 +68,7 @@ QUASIQUOTE: B1%=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership AY=B1%:GOSUB RELEASE + AY=B3%:GOSUB RELEASE RETURN QQ_DEFAULT: @@ -84,6 +86,7 @@ QUASIQUOTE: REM release inner quasiquoted since outer list takes ownership AY=B1%:GOSUB RELEASE AY=B2%:GOSUB RELEASE + AY=B3%:GOSUB RELEASE RETURN REM MACROEXPAND(A, E) -> A: @@ -147,8 +150,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry - SZ=2:GOSUB ALLOC + REM allocate the first entry (T already set above) + L=0:N=0:GOSUB ALLOC REM make space on the stack X=X+4 @@ -162,13 +165,6 @@ EVAL_AST: S%(X)=R EVAL_AST_SEQ_LOOP: - REM set new sequence entry type (with 1 ref cnt) - Z%(R,0)=S%(X-3)+16 - Z%(R,1)=0 - REM create value ptr placeholder - Z%(R+1,0)=14 - Z%(R+1,1)=0 - REM update index S%(X-2)=S%(X-2)+1 @@ -198,7 +194,8 @@ EVAL_AST: IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry - SZ=2:GOSUB ALLOC + REM same new sequence entry type + T=S%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry Z%(S%(X),1)=R diff --git a/basic/types.in.bas b/basic/types.in.bas index 2c39624ce5..5b49f63267 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -6,11 +6,11 @@ REM float 3 -> ??? REM string/kw 4 -> S$ index REM symbol 5 -> S$ index REM list next/val 6 -> next Z% index (0 for last) -REM followed by value (unless empty) +REM followed by 14 and value (unless empty) REM vector next/val 7 -> next Z% index (0 for last) -REM followed by value (unless empty) +REM followed by 14 and value (unless empty) REM hashmap next/val 8 -> next Z% index (0 for last) -REM followed by key or value (alternating) +REM followed by 14 and key/value (alternating) REM function 9 -> function index REM mal function 10 -> body AST Z% index REM followed by param and env Z% index @@ -18,7 +18,7 @@ REM macro (same as 10) 11 -> body AST Z% index REM followed by param and env Z% index REM atom 12 -> Z% index REM environment 13 -> data/hashmap Z% index -REM followed by 13 and outer Z% index (-1 for none) +REM followed by 14 and outer Z% index (-1 for none) REM reference/ptr 14 -> Z% index / or 0 REM next free ptr 15 -> Z% index / or 0 @@ -66,11 +66,19 @@ INIT_MEMORY: REM PRINT "Interpreter working memory: "+STR$(FRE(0)) RETURN + REM memory functions -REM ALLOC(SZ) -> R +REM ALLOC(T,L) -> R +REM ALLOC(T,L,N) -> R +REM ALLOC(T,L,M,N) -> R +REM L is default for Z%(R,1) +REM M is default for Z%(R+1,0), if relevant for T +REM N is default for Z%(R+1,1), if relevant for T ALLOC: - REM PRINT "ALLOC SZ: "+STR$(SZ)+", ZK: "+STR$(ZK) + SZ=2 + IF T<6 OR T=9 OR T=12 OR T>13 THEN SZ=1 + REM PRINT "ALLOC T: "+STR$(T)+", SZ: "+STR$(SZ)+", ZK: "+STR$(ZK) U3=ZK U4=ZK ALLOC_LOOP: @@ -88,7 +96,7 @@ ALLOC: IF U4=ZK THEN ZK=Z%(U4,1) REM set previous free to next free IF U4<>ZK THEN Z%(U3,1)=Z%(U4,1) - RETURN + GOTO ALLOC_DONE ALLOC_UNUSED: REM PRINT "ALLOC_UNUSED ZI: "+STR$(ZI)+", U3: "+STR$(U3)+", U4: "+STR$(U4) R=U4 @@ -96,6 +104,22 @@ ALLOC: IF U3=U4 THEN ZK=ZI REM set previous free to new memory top IF U3<>U4 THEN Z%(U3,1)=ZI + GOTO ALLOC_DONE + ALLOC_DONE: + Z%(R,0)=T+16 + REM set Z%(R,1) to default L + IF T>=6 AND T<>9 AND L>0 THEN Z%(L,0)=Z%(L,0)+16 + Z%(R,1)=L + + IF SZ=1 THEN RETURN + Z%(R+1,0)=14: REM default for 6-8, and 13 + + REM function/macro sets Z%(R+1,0) to default M + IF T=10 OR T=11 THEN Z%(M,0)=Z%(M,0)+16:Z%(R+1,0)=M + + REM seq, function/macro, environment sets Z%(R+1,1) to default N + IF N>0 THEN Z%(N,0)=Z%(N,0)+16 + Z%(R+1,1)=N RETURN REM FREE(AY, SZ) -> nil @@ -224,20 +248,6 @@ DEREF_B: IF (Z%(B,0)AND15)=14 THEN B=Z%(B,1):GOTO DEREF_B RETURN -CHECK_FREE_LIST: - REM start and accumulator - P1%=ZK - P2%=0 - CHECK_FREE_LIST_LOOP: - IF P1%>=ZI THEN GOTO CHECK_FREE_LIST_DONE - IF (Z%(P1%,0)AND15)<>15 THEN P2%=-1:GOTO CHECK_FREE_LIST_DONE - P2%=P2%+(Z%(P1%,0)AND-16)/16 - P1%=Z%(P1%,1) - GOTO CHECK_FREE_LIST_LOOP - CHECK_FREE_LIST_DONE: - IF P2%=-1 THEN PRINT "corrupt free list at "+STR$(P1%) - RETURN - REM general functions @@ -247,9 +257,9 @@ EQUAL_Q: GOSUB DEREF_B R=0 - U1=(Z%(A,0)AND15) - U2=(Z%(B,0)AND15) - IF NOT ((U1=U2) OR ((U1=6 OR U1=7) AND (U2=6 OR U2=7))) THEN RETURN + U1=Z%(A,0)AND15 + U2=Z%(B,0)AND15 + IF NOT (U1=U2 OR ((U1=6 OR U1=7) AND (U2=6 OR U2=7))) THEN RETURN IF U1=6 THEN GOTO EQUAL_Q_SEQ IF U1=7 THEN GOTO EQUAL_Q_SEQ IF U1=8 THEN GOTO EQUAL_Q_HM @@ -297,10 +307,7 @@ REM STRING(AS$, T) -> R REM intern string and allocate reference (return Z% index) STRING: GOSUB STRING_ - TS%=R - SZ=1:GOSUB ALLOC - Z%(R,0)=T - Z%(R,1)=TS% + L=R:GOSUB ALLOC RETURN REM REPLACE(R$, S1$, S2$) -> R$ @@ -317,7 +324,18 @@ REPLACE: GOTO REPLACE_LOOP -REM list functions +REM sequence functions + +REM FORCE_SEQ_TYPE(A,T) -> R +FORCE_SEQ_TYPE: + REM if it's already the right type, inc ref cnt and return it + IF (Z%(A,0)AND15)=T THEN R=A:Z%(R,0)=Z%(R,0)+16:RETURN + REM otherwise, copy first element to turn it into correct type + B=A+1:GOSUB DEREF_B: REM value to copy + L=Z%(A,1):N=B:GOSUB ALLOC: REM T already set + IF Z%(A,1)=0 THEN RETURN + RETURN + REM LIST_Q(A) -> R LIST_Q: @@ -354,19 +372,6 @@ LAST: Z%(R,0)=Z%(R,0)+16 RETURN -REM CONS(A,B) -> R -CONS: - SZ=2:GOSUB ALLOC - Z%(R,0)=6+16 - Z%(R,1)=B - Z%(R+1,0)=14 - Z%(R+1,1)=A - REM inc ref cnt of item we are including - Z%(A,0)=Z%(A,0)+16 - REM inc ref cnt of list we are prepending - Z%(B,0)=Z%(B,0)+16 - RETURN - REM SLICE(A,B,C) -> R REM make copy of sequence A from index B to C REM returns R6 as reference to last element of slice @@ -377,8 +382,7 @@ SLICE: R6=0: REM previous list element SLICE_LOOP: REM always allocate at least one list element - SZ=2:GOSUB ALLOC - Z%(R,0)=6+16:Z%(R,1)=0:Z%(R+1,0)=14:Z%(R+1,1)=0 + T=6:L=0:N=0:GOSUB ALLOC IF R5=-1 THEN R5=R IF R5<>-1 THEN Z%(R6,1)=R REM advance A to position B @@ -399,76 +403,55 @@ SLICE: REM LIST2(B2%,B1%) -> R LIST2: - REM terminator - SZ=2:GOSUB ALLOC:TB%=R - Z%(R,0)=6+16:Z%(R,1)=0:Z%(R+1,0)=0:Z%(R+1,1)=0 - - REM second element is B1% - SZ=2:GOSUB ALLOC:TC%=R - Z%(R,0)=6+16:Z%(R,1)=TB%:Z%(R+1,0)=14:Z%(R+1,1)=B1% - Z%(B1%,0)=Z%(B1%,0)+16 + REM last element is 3 (empty list), second element is B1% + T=6:L=3:N=B1%:GOSUB ALLOC REM first element is B2% - SZ=2:GOSUB ALLOC - Z%(R,0)=6+16:Z%(R,1)=TC%:Z%(R+1,0)=14:Z%(R+1,1)=B2% - Z%(B2%,0)=Z%(B2%,0)+16 + T=6:L=R:N=B2%:GOSUB ALLOC + AY=L:GOSUB RELEASE: REM new list takes ownership of previous RETURN REM LIST3(B3%,B2%,B1%) -> R LIST3: - GOSUB LIST2:TC%=R + GOSUB LIST2 REM first element is B3% - SZ=2:GOSUB ALLOC - Z%(R,0)=6+16:Z%(R,1)=TC%:Z%(R+1,0)=14:Z%(R+1,1)=B3% - Z%(B3%,0)=Z%(B3%,0)+16 + T=6:L=R:N=B3%:GOSUB ALLOC + AY=L:GOSUB RELEASE: REM new list takes ownership of previous RETURN + REM hashmap functions REM HASHMAP() -> R HASHMAP: - SZ=2:GOSUB ALLOC - Z%(R,0)=8+16 - Z%(R,1)=0 - Z%(R+1,0)=14 - Z%(R+1,1)=0 + T=8:L=0:N=0:GOSUB ALLOC RETURN REM ASSOC1(H, K, V) -> R ASSOC1: - REM deref to actual key and value - R=K:GOSUB DEREF_R:K=R + REM deref K and V R=V:GOSUB DEREF_R:V=R + R=K:GOSUB DEREF_R:K=R - REM inc ref count of key and value - Z%(K,0)=Z%(K,0)+16 - Z%(V,0)=Z%(V,0)+16 - SZ=4:GOSUB ALLOC - REM key ptr - Z%(R,0)=8+16 - Z%(R,1)=R+2: REM point to next element (value) - Z%(R+1,0)=14 - Z%(R+1,1)=K REM value ptr - Z%(R+2,0)=8+16 - Z%(R+2,1)=H: REM hashmap to assoc onto - Z%(R+3,0)=14 - Z%(R+3,1)=V + T=8:L=H:N=V:GOSUB ALLOC + AY=L:GOSUB RELEASE: REM we took ownership of previous hashmap + REM key ptr + T=8:L=R:N=K:GOSUB ALLOC + AY=L:GOSUB RELEASE: REM we took ownership of previous hashmap RETURN REM ASSOC1(H, K$, V) -> R ASSOC1_S: - REM add the key string, then call ASSOC1 - SZ=1:GOSUB ALLOC - K=R S$(ZJ)=K$ - Z%(R,0)=4: REM key ref cnt will be inc'd by ASSOC1 - Z%(R,1)=ZJ + REM add the key string + T=4:L=ZJ:GOSUB ALLOC ZJ=ZJ+1 - GOSUB ASSOC1 + K=R:GOSUB ASSOC1 + AY=K:GOSUB RELEASE: REM map took ownership of key RETURN REM HASHMAP_GET(H, K) -> R @@ -498,24 +481,17 @@ HASHMAP_CONTAINS: R=T3 RETURN + +REM function functions + REM NATIVE_FUNCTION(A) -> R NATIVE_FUNCTION: - SZ=1:GOSUB ALLOC - Z%(R,0)=9+16 - Z%(R,1)=A + T=9:L=A:GOSUB ALLOC RETURN REM MAL_FUNCTION(A, P, E) -> R MAL_FUNCTION: - SZ=2:GOSUB ALLOC - Z%(A,0)=Z%(A,0)+16 - Z%(P,0)=Z%(P,0)+16 - Z%(E,0)=Z%(E,0)+16 - - Z%(R,0)=10+16 - Z%(R,1)=A - Z%(R+1,0)=P - Z%(R+1,1)=E + T=10:L=A:M=P:N=E:GOSUB ALLOC RETURN REM APPLY(F, AR) -> R diff --git a/basic/variables.txt b/basic/variables.txt index c11b7aa42b..a388602d30 100644 --- a/basic/variables.txt +++ b/basic/variables.txt @@ -30,6 +30,9 @@ F : function H : hash map K : hash map key (Z% index) K$ : hash map key string +L : ALLOC* Z%(R,1) default +M : ALLOC* Z%(R+1,0) default +N : ALLOC* Z%(R+1,1) default O : outer environment P : MAL_FUNCTION R : common return value From 32a75b868baba6a7bf5bce31125982df79ec7550 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 16 Oct 2016 00:11:07 +0200 Subject: [PATCH 0173/2308] Implement step 7 --- pil/core.l | 5 +- pil/step7_quote.l | 124 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 pil/step7_quote.l diff --git a/pil/core.l b/pil/core.l index 95f5719a68..1d4fd54169 100644 --- a/pil/core.l +++ b/pil/core.l @@ -57,4 +57,7 @@ (atom? . `(MAL-fn '((X) (if (= (MAL-type X) 'atom) *MAL-true *MAL-false)))) (deref . `(MAL-fn '((X) (MAL-value X)))) (reset! . `(MAL-fn '((X Value) (put X 'value Value)))) - (swap! . `(MAL-fn MAL-swap!) ) ) ) + (swap! . `(MAL-fn MAL-swap!)) + + (cons . `(MAL-fn '((X Seq) (MAL-list (cons X (MAL-value Seq)))))) + (concat . `(MAL-fn '(@ (MAL-list (apply append (mapcar MAL-value (rest))))))) ) ) diff --git a/pil/step7_quote.l b/pil/step7_quote.l new file mode 100644 index 0000000000..1eef310986 --- /dev/null +++ b/pil/step7_quote.l @@ -0,0 +1,124 @@ +(de load-relative (Path) + (load (pack (car (file)) Path)) ) + +(load-relative "readline.l") +(load-relative "types.l") +(load-relative "reader.l") +(load-relative "printer.l") +(load-relative "env.l") +(load-relative "func.l") +(load-relative "core.l") + +(de READ (String) + (read-str String) ) + +(def '*ReplEnv (MAL-env NIL)) +(for Bind *Ns (set> *ReplEnv (car Bind) (cdr Bind))) + +(de is-pair (Ast) + (and (memq (MAL-type Ast) '(list vector)) (MAL-value Ast) T) ) + +(de quasiquote (Ast) + (if (not (is-pair Ast)) + (MAL-list (list (MAL-symbol 'quote) Ast)) + (let A (MAL-value Ast) + (cond + ((= (MAL-value (car A)) 'unquote) + (cadr A) ) + ((and (is-pair (car A)) + (= (MAL-value (car (MAL-value (car A)))) 'splice-unquote) ) + (MAL-list (list (MAL-symbol 'concat) + (cadr (MAL-value (car A))) + (quasiquote (MAL-list (cdr A))) ) ) ) + (T + (MAL-list (list (MAL-symbol 'cons) + (quasiquote (car A)) + (quasiquote (MAL-list (cdr A))) ) ) ) ) ) ) ) + +(de EVAL (Ast Env) + (catch 'done + (while t + (if (and (= (MAL-type Ast) 'list) (MAL-value Ast)) + (let (Ast* (MAL-value Ast) + A0* (MAL-value (car Ast*)) + A1 (cadr Ast*) + A1* (MAL-value A1) + A2 (caddr Ast*) + A3 (cadddr Ast*) ) + (cond + ((= A0* 'def!) + (throw 'done (set> Env A1* (EVAL A2 Env))) ) + ((= A0* 'quote) + (throw 'done A1) ) + ((= A0* 'quasiquote) + (setq Ast (quasiquote A1)) ) # TCO + ((= A0* 'let*) + (let Env* (MAL-env Env) + (for (Bindings A1* Bindings) + (let (Key (MAL-value (pop 'Bindings)) + Value (EVAL (pop 'Bindings) Env*) ) + (set> Env* Key Value) ) ) + (setq Env Env* Ast A2) ) ) # TCO + ((= A0* 'do) + (mapc '((Form) (EVAL Form Env)) (head -1 (cdr Ast*))) + (setq Ast (last Ast*)) ) # TCO + ((= A0* 'if) + (if (not (memq (MAL-type (EVAL A1 Env)) '(nil false))) + (setq Ast A2) # TCO + (if A3 + (setq Ast A3) # TCO + (throw 'done *MAL-nil) ) ) ) + ((= A0* 'fn*) + (let (Binds (mapcar MAL-value A1*) + Body A2 + Fn (MAL-fn + (curry (Env Binds Body) @ + (let Env* (MAL-env Env Binds (rest)) + (EVAL Body Env*) ) ) ) ) + (throw 'done (MAL-func Env Body Binds Fn)) ) ) + (T + (let (Ast* (MAL-value (eval-ast Ast Env)) + Fn (car Ast*) + Args (cdr Ast*) ) + (if (isa '+MALFn Fn) + (throw 'done (apply (MAL-value Fn) Args)) + (let Env* (MAL-env (get Fn 'env) (get Fn 'params) Args) + (setq Ast (get Fn 'ast) Env Env*) ) ) ) ) ) ) + (throw 'done (eval-ast Ast Env)) ) ) ) ) + +(de eval-ast (Ast Env) + (let Value (MAL-value Ast) + (case (MAL-type Ast) + (symbol (get> Env Value)) + (list (MAL-list (mapcar '((Form) (EVAL Form Env)) Value))) + (vector (MAL-vector (mapcar '((Form) (EVAL Form Env)) Value))) + (map (MAL-map (mapcar '((Form) (EVAL Form Env)) Value))) + (T Ast) ) ) ) + +(set> *ReplEnv 'eval (MAL-fn (curry (*ReplEnv) (Form) (EVAL Form *ReplEnv)))) +(set> *ReplEnv '*ARGV* (MAL-list (mapcar MAL-string (argv)))) + +(de PRINT (Ast) + (pr-str Ast T) ) + +(de rep (String) + (PRINT (EVAL (READ String) *ReplEnv)) ) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + +(load-history ".mal_history") + +(if (argv) + (rep (pack "(load-file \"" (opt) "\")")) + (use Input + (until (=0 (setq Input (readline "user> "))) + (let Output (catch 'err (rep Input)) + (if (isa '+MALError Output) + (let Message (MAL-value Output) + (unless (= Message "end of token stream") + (prinl "[error] " Message) ) ) + (prinl Output) ) ) ) ) ) + +(prinl) +(bye) From 90670a6d20565adb535d2714adcee2a438417832 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 16 Oct 2016 22:58:01 +0200 Subject: [PATCH 0174/2308] Implement step 8 --- pil/core.l | 12 +++- pil/reader.l | 6 +- pil/step8_macros.l | 150 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 pil/step8_macros.l diff --git a/pil/core.l b/pil/core.l index 1d4fd54169..09fbdf6f6b 100644 --- a/pil/core.l +++ b/pil/core.l @@ -27,6 +27,12 @@ F (MAL-value (if (isa '+Func Fn) (get Fn 'fn) Fn)) ) (put X 'value (apply F Args (MAL-value X))) ) ) +(de MAL-nth (Seq N) + (let (Seq* (MAL-value Seq) N* (MAL-value N)) + (if (< N* (length Seq*)) + (nth Seq* (inc N*) 1) + (throw 'err (MAL-error "out of bounds")) ) ) ) + (def '*Ns '((+ . `(MAL-fn '((A B) (MAL-number (+ (MAL-value A) (MAL-value B)))))) (- . `(MAL-fn '((A B) (MAL-number (- (MAL-value A) (MAL-value B)))))) @@ -60,4 +66,8 @@ (swap! . `(MAL-fn MAL-swap!)) (cons . `(MAL-fn '((X Seq) (MAL-list (cons X (MAL-value Seq)))))) - (concat . `(MAL-fn '(@ (MAL-list (apply append (mapcar MAL-value (rest))))))) ) ) + (concat . `(MAL-fn '(@ (MAL-list (apply append (mapcar MAL-value (rest))))))) + + (nth . `(MAL-fn MAL-nth)) + (first . `(MAL-fn '((X) (if (MAL-seq? X) (or (car (MAL-value X)) *MAL-nil) *MAL-nil)))) + (rest . `(MAL-fn '((X) (if (MAL-seq? X) (MAL-list (cdr (MAL-value X))) (MAL-list NIL))))) ) ) diff --git a/pil/reader.l b/pil/reader.l index b076870fb7..b59b197856 100644 --- a/pil/reader.l +++ b/pil/reader.l @@ -21,7 +21,7 @@ (for (Chars (chop String) Chars) (let Char (pop 'Chars) (cond - ((member Char '(" " "," "\n")) + ((or (sp? Char) (= Char ",")) # do nothing, whitespace ) ((and (= Char "~") (= (car Chars) "@")) @@ -48,13 +48,13 @@ ((= Char ";") (while (and Chars (<> Char "\n")) (setq Char (pop 'Chars)) ) ) - ((not (index Char (chop Special))) + ((and (not (index Char (chop Special))) (not (sp? Char))) (link (pack (make (link Char) (let Char (car Chars) - (while (and Chars (not (index Char (chop Special)))) + (while (and Chars (not (index Char (chop Special))) (not (sp? Char))) (link (pop 'Chars)) (setq Char (car Chars)) ) ) ) ) ) ) ) ) ) ) ) ) diff --git a/pil/step8_macros.l b/pil/step8_macros.l new file mode 100644 index 0000000000..6919c1cd31 --- /dev/null +++ b/pil/step8_macros.l @@ -0,0 +1,150 @@ +(de load-relative (Path) + (load (pack (car (file)) Path)) ) + +(load-relative "readline.l") +(load-relative "types.l") +(load-relative "reader.l") +(load-relative "printer.l") +(load-relative "env.l") +(load-relative "func.l") +(load-relative "core.l") + +(de READ (String) + (read-str String) ) + +(def '*ReplEnv (MAL-env NIL)) +(for Bind *Ns (set> *ReplEnv (car Bind) (cdr Bind))) + +(de is-pair (Ast) + (and (memq (MAL-type Ast) '(list vector)) (MAL-value Ast) T) ) + +(de quasiquote (Ast) + (if (not (is-pair Ast)) + (MAL-list (list (MAL-symbol 'quote) Ast)) + (let A (MAL-value Ast) + (cond + ((= (MAL-value (car A)) 'unquote) + (cadr A) ) + ((and (is-pair (car A)) + (= (MAL-value (car (MAL-value (car A)))) 'splice-unquote) ) + (MAL-list (list (MAL-symbol 'concat) + (cadr (MAL-value (car A))) + (quasiquote (MAL-list (cdr A))) ) ) ) + (T + (MAL-list (list (MAL-symbol 'cons) + (quasiquote (car A)) + (quasiquote (MAL-list (cdr A))) ) ) ) ) ) ) ) + +(de is-macro-call (Ast Env) + (when (= (MAL-type Ast) 'list) + (let A0 (car (MAL-value Ast)) + (when (= (MAL-type A0) 'symbol) + (let Value (find> Env (MAL-value A0)) + (and (isa '+Func Value) (get Value 'is-macro) T) ) ) ) ) ) + +(de macroexpand (Ast Env) + (while (is-macro-call Ast Env) + (let (Ast* (MAL-value Ast) + Macro (get (find> Env (MAL-value (car Ast*))) 'fn) + Args (cdr Ast*) ) + (setq Ast (apply (MAL-value Macro) Args)) ) ) + Ast ) + +(de EVAL (Ast Env) + (catch 'done + (while t + (when (not (= (MAL-type Ast) 'list)) + (throw 'done (eval-ast Ast Env)) ) + (setq Ast (macroexpand Ast Env)) + (when (or (not (= (MAL-type Ast) 'list)) (not (MAL-value Ast))) + (throw 'done (eval-ast Ast Env)) ) + (let (Ast* (MAL-value Ast) + A0* (MAL-value (car Ast*)) + A1 (cadr Ast*) + A1* (MAL-value A1) + A2 (caddr Ast*) + A3 (cadddr Ast*) ) + (cond + ((= A0* 'def!) + (throw 'done (set> Env A1* (EVAL A2 Env))) ) + ((= A0* 'quote) + (throw 'done A1) ) + ((= A0* 'quasiquote) + (setq Ast (quasiquote A1)) ) # TCO + ((= A0* 'defmacro!) + (let Form (EVAL A2 Env) + (put Form 'is-macro T) + (throw 'done (set> Env A1* Form)) ) ) + ((= A0* 'macroexpand) + (throw 'done (macroexpand A1 Env)) ) + ((= A0* 'let*) + (let Env* (MAL-env Env) + (for (Bindings A1* Bindings) + (let (Key (MAL-value (pop 'Bindings)) + Value (EVAL (pop 'Bindings) Env*) ) + (set> Env* Key Value) ) ) + (setq Env Env* Ast A2) ) ) # TCO + ((= A0* 'do) + (mapc '((Form) (EVAL Form Env)) (head -1 (cdr Ast*))) + (setq Ast (last Ast*)) ) # TCO + ((= A0* 'if) + (if (not (memq (MAL-type (EVAL A1 Env)) '(nil false))) + (setq Ast A2) # TCO + (if A3 + (setq Ast A3) # TCO + (throw 'done *MAL-nil) ) ) ) + ((= A0* 'fn*) + (let (Binds (mapcar MAL-value A1*) + Body A2 + Fn (MAL-fn + (curry (Env Binds Body) @ + (let Env* (MAL-env Env Binds (rest)) + (EVAL Body Env*) ) ) ) ) + (throw 'done (MAL-func Env Body Binds Fn)) ) ) + (T + (let (Ast* (MAL-value (eval-ast Ast Env)) + Fn (car Ast*) + Args (cdr Ast*) ) + (if (isa '+MALFn Fn) + (throw 'done (apply (MAL-value Fn) Args)) + (let Env* (MAL-env (get Fn 'env) (get Fn 'params) Args) + (setq Ast (get Fn 'ast) Env Env*) ) ) ) ) ) ) ) ) ) + +(de eval-ast (Ast Env) + (let Value (MAL-value Ast) + (case (MAL-type Ast) + (symbol (get> Env Value)) + (list (MAL-list (mapcar '((Form) (EVAL Form Env)) Value))) + (vector (MAL-vector (mapcar '((Form) (EVAL Form Env)) Value))) + (map (MAL-map (mapcar '((Form) (EVAL Form Env)) Value))) + (T Ast) ) ) ) + +(set> *ReplEnv 'eval (MAL-fn (curry (*ReplEnv) (Form) (EVAL Form *ReplEnv)))) +(set> *ReplEnv '*ARGV* (MAL-list (mapcar MAL-string (argv)))) + +(de PRINT (Ast) + (pr-str Ast T) ) + +(de rep (String) + (PRINT (EVAL (READ String) *ReplEnv)) ) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") + +(load-history ".mal_history") + +(if (argv) + (rep (pack "(load-file \"" (opt) "\")")) + (use Input + (until (=0 (setq Input (readline "user> "))) + (let Output (catch 'err (rep Input)) + (if (isa '+MALError Output) + (let Message (MAL-value Output) + (unless (= Message "end of token stream") + (prinl "[error] " Message) ) ) + (prinl Output) ) ) ) ) ) + +(prinl) +(bye) From 42b8fe16833125b7005fa45892474d82ea605935 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 17 Oct 2016 22:37:42 -0500 Subject: [PATCH 0175/2308] Swift3: updates/fixes for Swift 3.0.1 --- swift3/Dockerfile | 15 ++++++++------- swift3/Sources/printer.swift | 2 +- swift3/Sources/step6_file/main.swift | 6 +++--- swift3/Sources/step7_quote/main.swift | 6 +++--- swift3/Sources/step8_macros/main.swift | 6 +++--- swift3/Sources/step9_try/main.swift | 8 ++++---- swift3/Sources/stepA_mal/main.swift | 8 ++++---- swift3/Sources/types.swift | 10 +++++----- 8 files changed, 31 insertions(+), 30 deletions(-) diff --git a/swift3/Dockerfile b/swift3/Dockerfile index 039d4adde3..67fed0a3a0 100644 --- a/swift3/Dockerfile +++ b/swift3/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:wily +FROM ubuntu:xenial MAINTAINER Joel Martin ########################################################## @@ -27,17 +27,18 @@ RUN apt-get -y install clang-3.6 cmake pkg-config \ libbsd-dev libedit-dev libxml2-dev libsqlite3-dev \ swig libpython-dev libncurses5-dev -ENV SWIFT_PREFIX swift-3.0-PREVIEW-3 -ENV SWIFT_RELEASE ${SWIFT_PREFIX}-ubuntu15.10 +# TODO: better way to do this? +RUN ln -sf /usr/lib/llvm-3.6/bin/clang++ /usr/bin/clang++ +RUN ln -sf /usr/lib/llvm-3.6/bin/clang /usr/bin/clang + +ENV SWIFT_PREFIX swift-3.0.1-PREVIEW-3 +ENV SWIFT_RELEASE ${SWIFT_PREFIX}-ubuntu16.04 RUN cd /opt && \ - curl -O https://swift.org/builds/swift-3.0-preview-3/ubuntu1510/${SWIFT_PREFIX}/${SWIFT_RELEASE}.tar.gz && \ + curl -O https://swift.org/builds/swift-3.0.1-preview-3/ubuntu1604/${SWIFT_PREFIX}/${SWIFT_RELEASE}.tar.gz && \ tar xvzf ${SWIFT_RELEASE}.tar.gz && \ rm ${SWIFT_RELEASE}.tar.gz ENV PATH /opt/${SWIFT_RELEASE}/usr/bin/:$PATH -# TODO: better way to do this? And move up. -RUN ln -sf /usr/lib/llvm-3.6/bin/clang++ /usr/bin/clang++ -RUN ln -sf /usr/lib/llvm-3.6/bin/clang /usr/bin/clang diff --git a/swift3/Sources/printer.swift b/swift3/Sources/printer.swift index 0f0dd29445..b4ec36a1d9 100644 --- a/swift3/Sources/printer.swift +++ b/swift3/Sources/printer.swift @@ -38,6 +38,6 @@ func pr_str(_ obj: MalVal, _ print_readably: Bool = true) -> String { case MalVal.MalAtom(let ma): return "(atom \(pr_str(ma.val, print_readably)))" default: - return String(obj) + return String(describing:obj) } } diff --git a/swift3/Sources/step6_file/main.swift b/swift3/Sources/step6_file/main.swift index aea53fdf4e..84667cf884 100644 --- a/swift3/Sources/step6_file/main.swift +++ b/swift3/Sources/step6_file/main.swift @@ -117,7 +117,7 @@ for (k, fn) in core_ns { } try repl_env.set(MalVal.MalSymbol("eval"), malfunc({ try EVAL($0[0], repl_env) })) -let pargs = Process.arguments.map { MalVal.MalString($0) } +let pargs = CommandLine.arguments.map { MalVal.MalString($0) } // TODO: weird way to get empty list, fix this var args = pargs[pargs.startIndex.. 1 { - try rep("(load-file \"" + Process.arguments[1] + "\")") +if CommandLine.arguments.count > 1 { + try rep("(load-file \"" + CommandLine.arguments[1] + "\")") exit(0) } diff --git a/swift3/Sources/step7_quote/main.swift b/swift3/Sources/step7_quote/main.swift index 10205d265e..d228591a71 100644 --- a/swift3/Sources/step7_quote/main.swift +++ b/swift3/Sources/step7_quote/main.swift @@ -155,7 +155,7 @@ for (k, fn) in core_ns { } try repl_env.set(MalVal.MalSymbol("eval"), malfunc({ try EVAL($0[0], repl_env) })) -let pargs = Process.arguments.map { MalVal.MalString($0) } +let pargs = CommandLine.arguments.map { MalVal.MalString($0) } // TODO: weird way to get empty list, fix this var args = pargs[pargs.startIndex.. 1 { - try rep("(load-file \"" + Process.arguments[1] + "\")") +if CommandLine.arguments.count > 1 { + try rep("(load-file \"" + CommandLine.arguments[1] + "\")") exit(0) } diff --git a/swift3/Sources/step8_macros/main.swift b/swift3/Sources/step8_macros/main.swift index 933f028bea..11e851cee8 100644 --- a/swift3/Sources/step8_macros/main.swift +++ b/swift3/Sources/step8_macros/main.swift @@ -205,7 +205,7 @@ for (k, fn) in core_ns { } try repl_env.set(MalVal.MalSymbol("eval"), malfunc({ try EVAL($0[0], repl_env) })) -let pargs = Process.arguments.map { MalVal.MalString($0) } +let pargs = CommandLine.arguments.map { MalVal.MalString($0) } // TODO: weird way to get empty list, fix this var args = pargs[pargs.startIndex.. (count xs) 0) (list 'if (first xs) ( try rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") -if Process.arguments.count > 1 { - try rep("(load-file \"" + Process.arguments[1] + "\")") +if CommandLine.arguments.count > 1 { + try rep("(load-file \"" + CommandLine.arguments[1] + "\")") exit(0) } diff --git a/swift3/Sources/step9_try/main.swift b/swift3/Sources/step9_try/main.swift index a02af0e459..6660900808 100644 --- a/swift3/Sources/step9_try/main.swift +++ b/swift3/Sources/step9_try/main.swift @@ -163,7 +163,7 @@ func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { case MalError.MalException(let obj): err = obj default: - err = MalVal.MalString(String(exc)) + err = MalVal.MalString(String(describing:exc)) } return try EVAL(a22, Env(env, binds: list([a21]), exprs: list([err]))) @@ -238,7 +238,7 @@ for (k, fn) in core_ns { } try repl_env.set(MalVal.MalSymbol("eval"), malfunc({ try EVAL($0[0], repl_env) })) -let pargs = Process.arguments.map { MalVal.MalString($0) } +let pargs = CommandLine.arguments.map { MalVal.MalString($0) } // TODO: weird way to get empty list, fix this var args = pargs[pargs.startIndex.. (count xs) 0) (list 'if (first xs) ( try rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") -if Process.arguments.count > 1 { - try rep("(load-file \"" + Process.arguments[1] + "\")") +if CommandLine.arguments.count > 1 { + try rep("(load-file \"" + CommandLine.arguments[1] + "\")") exit(0) } diff --git a/swift3/Sources/stepA_mal/main.swift b/swift3/Sources/stepA_mal/main.swift index 2e338ff64b..56d8fe4e88 100644 --- a/swift3/Sources/stepA_mal/main.swift +++ b/swift3/Sources/stepA_mal/main.swift @@ -163,7 +163,7 @@ func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { case MalError.MalException(let obj): err = obj default: - err = MalVal.MalString(String(exc)) + err = MalVal.MalString(String(describing:exc)) } return try EVAL(a22, Env(env, binds: list([a21]), exprs: list([err]))) @@ -238,7 +238,7 @@ for (k, fn) in core_ns { } try repl_env.set(MalVal.MalSymbol("eval"), malfunc({ try EVAL($0[0], repl_env) })) -let pargs = Process.arguments.map { MalVal.MalString($0) } +let pargs = CommandLine.arguments.map { MalVal.MalString($0) } // TODO: weird way to get empty list, fix this var args = pargs[pargs.startIndex.. 1 { - try rep("(load-file \"" + Process.arguments[1] + "\")") +if CommandLine.arguments.count > 1 { + try rep("(load-file \"" + CommandLine.arguments[1] + "\")") exit(0) } diff --git a/swift3/Sources/types.swift b/swift3/Sources/types.swift index 6960f5b1a1..25f1468d7e 100644 --- a/swift3/Sources/types.swift +++ b/swift3/Sources/types.swift @@ -1,5 +1,5 @@ -enum MalError: ErrorProtocol { +enum MalError: Error { case Reader(msg: String) case General(msg: String) case MalException(obj: MalVal) @@ -152,18 +152,18 @@ func hash_map(_ arr: Array) throws -> MalVal { // function functions -func malfunc(_ fn: (Array) throws -> MalVal) -> MalVal { +func malfunc(_ fn: @escaping (Array) throws -> MalVal) -> MalVal { return MV.MalFunc(fn, ast: nil, env: nil, params: nil, macro: false, meta: nil) } -func malfunc(_ fn: (Array) throws -> MalVal, +func malfunc(_ fn: @escaping (Array) throws -> MalVal, ast: Array?, env: Env?, params: Array?) -> MalVal { return MV.MalFunc(fn, ast: ast, env: env, params: params, macro: false, meta: nil) } -func malfunc(_ fn: (Array) throws -> MalVal, +func malfunc(_ fn: @escaping (Array) throws -> MalVal, ast: Array?, env: Env?, params: Array?, @@ -172,7 +172,7 @@ func malfunc(_ fn: (Array) throws -> MalVal, return MV.MalFunc(fn, ast: ast, env: env, params: params, macro: macro, meta: meta != nil ? [meta!] : nil) } -func malfunc(_ fn: (Array) throws -> MalVal, +func malfunc(_ fn: @escaping (Array) throws -> MalVal, ast: Array?, env: Env?, params: Array?, From 1809f9baba9bfdbd07a7bc0c425a6a1f4e3976f6 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 22 Oct 2016 12:37:24 +0200 Subject: [PATCH 0176/2308] Fix error handling --- pil/core.l | 2 +- pil/env.l | 2 +- pil/printer.l | 2 +- pil/reader.l | 6 +++--- pil/step1_read_print.l | 4 ++-- pil/step2_eval.l | 6 +++--- pil/step3_env.l | 4 ++-- pil/step4_if_fn_do.l | 4 ++-- pil/step5_tco.l | 4 ++-- pil/step6_file.l | 4 ++-- pil/step7_quote.l | 4 ++-- pil/step8_macros.l | 4 ++-- 12 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pil/core.l b/pil/core.l index 09fbdf6f6b..5c708b3976 100644 --- a/pil/core.l +++ b/pil/core.l @@ -31,7 +31,7 @@ (let (Seq* (MAL-value Seq) N* (MAL-value N)) (if (< N* (length Seq*)) (nth Seq* (inc N*) 1) - (throw 'err (MAL-error "out of bounds")) ) ) ) + (throw 'err (MAL-error (MAL-string "out of bounds"))) ) ) ) (def '*Ns '((+ . `(MAL-fn '((A B) (MAL-number (+ (MAL-value A) (MAL-value B)))))) diff --git a/pil/env.l b/pil/env.l index 9474698771..97581cf1c3 100644 --- a/pil/env.l +++ b/pil/env.l @@ -21,4 +21,4 @@ (dm get> (Key) (or (find> This Key) - (throw 'err (MAL-error (pack "'" Key "' not found"))) ) ) + (throw 'err (MAL-error (MAL-string (pack "'" Key "' not found")))) ) ) diff --git a/pil/printer.l b/pil/printer.l index c964a94c4d..81ad6f8396 100644 --- a/pil/printer.l +++ b/pil/printer.l @@ -12,7 +12,7 @@ (vector (pr-list Value PrintReadably "[" "]")) (map (pr-list Value PrintReadably "{" "}")) (atom (pack "(atom " (pr-str Value PrintReadably) ")")) - (T (pretty Value) (throw 'err (MAL-error "[pr-str] unimplemented type"))) ) ) ) + (T (pretty Value) (throw 'err (MAL-error (MAL-string "[pr-str] unimplemented type")))) ) ) ) (de repr (X) (let Chars (chop X) diff --git a/pil/reader.l b/pil/reader.l index b59b197856..328b488fa9 100644 --- a/pil/reader.l +++ b/pil/reader.l @@ -96,7 +96,7 @@ (setq Done T) ) ((not Token) (let Msg (pack "expected '" Ender "', got EOF") - (throw 'err (MAL-error Msg)) ) ) + (throw 'err (MAL-error (MAL-string Msg))) ) ) (T (link (read-form Reader))) ) ) ) ) ) ) ) (de read-atom (Reader) @@ -114,9 +114,9 @@ ((= (car Chars) "\"") (if (= (last Chars) "\"") (MAL-string (any Token)) - (throw 'err (MAL-error "expected '\"', got EOF")) ) ) + (throw 'err (MAL-error (MAL-string "expected '\"', got EOF"))) ) ) ((= (car Chars) ":") (MAL-keyword (intern (pack (cdr Chars)))) ) ((not Token) - (throw 'err (MAL-error "end of token stream")) ) + (throw 'err (MAL-error (MAL-string "end of token stream"))) ) (T (MAL-symbol (intern Token))) ) ) ) diff --git a/pil/step1_read_print.l b/pil/step1_read_print.l index 48341605a1..5e4008801a 100644 --- a/pil/step1_read_print.l +++ b/pil/step1_read_print.l @@ -28,8 +28,8 @@ (let Output (catch 'err (rep Input)) (if (isa '+MALError Output) (let Message (MAL-value Output) - (unless (= Message "end of token stream") - (prinl "[error] " Message) ) ) + (unless (= (MAL-value Message) "end of token stream") + (prinl "[error] " (pr-str Message)) ) ) (prinl Output) ) ) ) ) ) ) (prinl) diff --git a/pil/step2_eval.l b/pil/step2_eval.l index cb7d9f2834..424f4b2ddf 100644 --- a/pil/step2_eval.l +++ b/pil/step2_eval.l @@ -29,7 +29,7 @@ (symbol (if (assoc Value Env) (cdr @) - (throw 'err (MAL-error (pack "'" Value "' not found"))) ) ) + (throw 'err (MAL-error (MAL-string (pack "'" Value "' not found")))) ) ) (list (MAL-list (mapcar '((Form) (EVAL Form Env)) Value))) (vector (MAL-vector (mapcar '((Form) (EVAL Form Env)) Value))) (map (MAL-map (mapcar '((Form) (EVAL Form Env)) Value))) @@ -51,8 +51,8 @@ (let Output (catch 'err (rep Input)) (if (isa '+MALError Output) (let Message (MAL-value Output) - (unless (= Message "end of token stream") - (prinl "[error] " Message) ) ) + (unless (= (MAL-value Message) "end of token stream") + (prinl "[error] " (pr-str Message)) ) ) (prinl Output) ) ) ) ) ) ) (prinl) diff --git a/pil/step3_env.l b/pil/step3_env.l index f1d4e5fa4b..af3cb206f4 100644 --- a/pil/step3_env.l +++ b/pil/step3_env.l @@ -63,8 +63,8 @@ (let Output (catch 'err (rep Input)) (if (isa '+MALError Output) (let Message (MAL-value Output) - (unless (= Message "end of token stream") - (prinl "[error] " Message) ) ) + (unless (= (MAL-value Message) "end of token stream") + (prinl "[error] " (pr-str Message)) ) ) (prinl Output) ) ) ) ) ) ) (prinl) diff --git a/pil/step4_if_fn_do.l b/pil/step4_if_fn_do.l index 7c80de7378..8d70fdef5b 100644 --- a/pil/step4_if_fn_do.l +++ b/pil/step4_if_fn_do.l @@ -82,8 +82,8 @@ (let Output (catch 'err (rep Input)) (if (isa '+MALError Output) (let Message (MAL-value Output) - (unless (= Message "end of token stream") - (prinl "[error] " Message) ) ) + (unless (= (MAL-value Message) "end of token stream") + (prinl "[error] " (pr-str Message)) ) ) (prinl Output) ) ) ) ) (prinl) diff --git a/pil/step5_tco.l b/pil/step5_tco.l index caa174de31..5ae8bdc58e 100644 --- a/pil/step5_tco.l +++ b/pil/step5_tco.l @@ -86,8 +86,8 @@ (let Output (catch 'err (rep Input)) (if (isa '+MALError Output) (let Message (MAL-value Output) - (unless (= Message "end of token stream") - (prinl "[error] " Message) ) ) + (unless (= (MAL-value Message) "end of token stream") + (prinl "[error] " (pr-str Message)) ) ) (prinl Output) ) ) ) ) (prinl) diff --git a/pil/step6_file.l b/pil/step6_file.l index c566d5aba6..43ecba094e 100644 --- a/pil/step6_file.l +++ b/pil/step6_file.l @@ -92,8 +92,8 @@ (let Output (catch 'err (rep Input)) (if (isa '+MALError Output) (let Message (MAL-value Output) - (unless (= Message "end of token stream") - (prinl "[error] " Message) ) ) + (unless (= (MAL-value Message) "end of token stream") + (prinl "[error] " (pr-str Message)) ) ) (prinl Output) ) ) ) ) ) (prinl) diff --git a/pil/step7_quote.l b/pil/step7_quote.l index 1eef310986..1ec689af52 100644 --- a/pil/step7_quote.l +++ b/pil/step7_quote.l @@ -116,8 +116,8 @@ (let Output (catch 'err (rep Input)) (if (isa '+MALError Output) (let Message (MAL-value Output) - (unless (= Message "end of token stream") - (prinl "[error] " Message) ) ) + (unless (= (MAL-value Message) "end of token stream") + (prinl "[error] " (pr-str Message)) ) ) (prinl Output) ) ) ) ) ) (prinl) diff --git a/pil/step8_macros.l b/pil/step8_macros.l index 6919c1cd31..7931f97706 100644 --- a/pil/step8_macros.l +++ b/pil/step8_macros.l @@ -142,8 +142,8 @@ (let Output (catch 'err (rep Input)) (if (isa '+MALError Output) (let Message (MAL-value Output) - (unless (= Message "end of token stream") - (prinl "[error] " Message) ) ) + (unless (= (MAL-value Message) "end of token stream") + (prinl "[error] " (pr-str Message)) ) ) (prinl Output) ) ) ) ) ) (prinl) From cc49494448d6753b6ce0e7583e441633b7e1dba7 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 22 Oct 2016 12:52:01 +0200 Subject: [PATCH 0177/2308] Implement step 9 --- pil/core.l | 65 ++++++++++++++++--- pil/step9_try.l | 163 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+), 7 deletions(-) create mode 100644 pil/step9_try.l diff --git a/pil/core.l b/pil/core.l index 5c708b3976..22339e013c 100644 --- a/pil/core.l +++ b/pil/core.l @@ -3,29 +3,41 @@ B* (MAL-type B)) (cond ((and (= A* 'map) (= B* 'map)) - # TODO - NIL) + (MAL-map-= (MAL-value A) (MAL-value B)) ) ((and (memq A* '(list vector)) (memq B* '(list vector))) (MAL-seq-= (MAL-value A) (MAL-value B)) ) ((= A* B*) (= (MAL-value A) (MAL-value B)) ) (T NIL) ) ) ) +(de MAL-map-= (As Bs) + (when (= (length As) (length Bs)) + (let (As* (chunk As) Bs* (chunk Bs)) + (catch 'result + (while As* + (let (A (pop 'As*) Key (MAL-value (car A)) Val (cdr A) + B (find '((X) (= Key (MAL-value (car X)))) Bs*) ) + (when (or (not B) (not (MAL-= Val (cdr B)))) + (throw 'result NIL) ) ) ) + T ) ) ) ) + (de MAL-seq-= (As Bs) (when (= (length As) (length Bs)) (catch 'result (while As (ifn (MAL-= (pop 'As) (pop 'Bs)) (throw 'result NIL) ) ) - T) ) ) + T ) ) ) (de MAL-seq? (X) (memq (MAL-type X) '(list vector)) ) +(de MAL-f (X) + (MAL-value (if (isa '+Func X) (get X 'fn) X)) ) + (de MAL-swap! @ - (let (X (next) Fn (next) Args (rest) - F (MAL-value (if (isa '+Func Fn) (get Fn 'fn) Fn)) ) - (put X 'value (apply F Args (MAL-value X))) ) ) + (let (X (next) Fn (next) Args (rest)) + (put X 'value (apply (MAL-f Fn) Args (MAL-value X))) ) ) (de MAL-nth (Seq N) (let (Seq* (MAL-value Seq) N* (MAL-value N)) @@ -33,6 +45,19 @@ (nth Seq* (inc N*) 1) (throw 'err (MAL-error (MAL-string "out of bounds"))) ) ) ) +(de chunk (List) + (make + (for (L List L (cddr L)) + (link (cons (car L) (cadr L))) ) ) ) + +(de MAL-dissoc @ + (let (Map (next) Args (rest)) + (MAL-map + (make + (for (L (MAL-value Map) L (cddr L)) + (unless (find '((X) (MAL-= (car L) X)) Args) + (link (car L) (cadr L)) ) ) ) ) ) ) + (def '*Ns '((+ . `(MAL-fn '((A B) (MAL-number (+ (MAL-value A) (MAL-value B)))))) (- . `(MAL-fn '((A B) (MAL-number (- (MAL-value A) (MAL-value B)))))) @@ -70,4 +95,30 @@ (nth . `(MAL-fn MAL-nth)) (first . `(MAL-fn '((X) (if (MAL-seq? X) (or (car (MAL-value X)) *MAL-nil) *MAL-nil)))) - (rest . `(MAL-fn '((X) (if (MAL-seq? X) (MAL-list (cdr (MAL-value X))) (MAL-list NIL))))) ) ) + (rest . `(MAL-fn '((X) (if (MAL-seq? X) (MAL-list (cdr (MAL-value X))) (MAL-list NIL))))) + + (throw . `(MAL-fn '((X) (throw 'err (MAL-error X))))) + + (apply . `(MAL-fn '(@ (let (Fn (next) X (rest)) (apply (MAL-f Fn) (append (head -1 X) (MAL-value (last X)))))))) + (map . `(MAL-fn '((Fn Seq) (MAL-list (mapcar (MAL-f Fn) (MAL-value Seq)))))) + + (nil? . `(MAL-fn '((X) (if (= (MAL-type X) 'nil) *MAL-true *MAL-false)))) + (true? . `(MAL-fn '((X) (if (= (MAL-type X) 'true) *MAL-true *MAL-false)))) + (false? . `(MAL-fn '((X) (if (= (MAL-type X) 'false) *MAL-true *MAL-false)))) + (symbol? . `(MAL-fn '((X) (if (= (MAL-type X) 'symbol) *MAL-true *MAL-false)))) + (keyword? . `(MAL-fn '((X) (if (= (MAL-type X) 'keyword) *MAL-true *MAL-false)))) + (vector? . `(MAL-fn '((X) (if (= (MAL-type X) 'vector) *MAL-true *MAL-false)))) + (map? . `(MAL-fn '((X) (if (= (MAL-type X) 'map) *MAL-true *MAL-false)))) + (sequential? . `(MAL-fn '((X) (if (MAL-seq? X) *MAL-true *MAL-false)))) + + (symbol . `(MAL-fn '((Name) (MAL-symbol (MAL-value Name))))) + (keyword . `(MAL-fn '((X) (if (= (MAL-type X) 'keyword) X (MAL-keyword (MAL-value X)))))) + (vector . `(MAL-fn '(@ (MAL-vector (rest))))) + (hash-map . `(MAL-fn '(@ (MAL-map (rest))))) + + (assoc . `(MAL-fn '(@ (let (Map (next) Args (rest)) (MAL-map (append (MAL-value Map) Args)))))) + (dissoc . `(MAL-fn MAL-dissoc)) + (get . `(MAL-fn '((Map Key) (or (and (<> (MAL-type Map) 'nil) (cdr (find '((X) (MAL-= (car X) Key)) (chunk (MAL-value Map))))) *MAL-nil)))) + (contains? . `(MAL-fn '((Map Key) (if (find '((X) (MAL-= (car X) Key)) (chunk (MAL-value Map))) *MAL-true *MAL-false)))) + (keys . `(MAL-fn '((Map) (MAL-list (mapcar car (chunk (MAL-value Map))))))) + (vals . `(MAL-fn '((Map) (MAL-list (extract cdr (chunk (MAL-value Map))))))) ) ) diff --git a/pil/step9_try.l b/pil/step9_try.l new file mode 100644 index 0000000000..a33345ba14 --- /dev/null +++ b/pil/step9_try.l @@ -0,0 +1,163 @@ +(de load-relative (Path) + (load (pack (car (file)) Path)) ) + +(load-relative "readline.l") +(load-relative "types.l") +(load-relative "reader.l") +(load-relative "printer.l") +(load-relative "env.l") +(load-relative "func.l") +(load-relative "core.l") + +(de READ (String) + (read-str String) ) + +(def '*ReplEnv (MAL-env NIL)) +(for Bind *Ns (set> *ReplEnv (car Bind) (cdr Bind))) + +(de is-pair (Ast) + (and (memq (MAL-type Ast) '(list vector)) (MAL-value Ast) T) ) + +(de quasiquote (Ast) + (if (not (is-pair Ast)) + (MAL-list (list (MAL-symbol 'quote) Ast)) + (let A (MAL-value Ast) + (cond + ((= (MAL-value (car A)) 'unquote) + (cadr A) ) + ((and (is-pair (car A)) + (= (MAL-value (car (MAL-value (car A)))) 'splice-unquote) ) + (MAL-list (list (MAL-symbol 'concat) + (cadr (MAL-value (car A))) + (quasiquote (MAL-list (cdr A))) ) ) ) + (T + (MAL-list (list (MAL-symbol 'cons) + (quasiquote (car A)) + (quasiquote (MAL-list (cdr A))) ) ) ) ) ) ) ) + +(de is-macro-call (Ast Env) + (when (= (MAL-type Ast) 'list) + (let A0 (car (MAL-value Ast)) + (when (= (MAL-type A0) 'symbol) + (let Value (find> Env (MAL-value A0)) + (and (isa '+Func Value) (get Value 'is-macro) T) ) ) ) ) ) + +(de macroexpand (Ast Env) + (while (is-macro-call Ast Env) + (let (Ast* (MAL-value Ast) + Macro (get (find> Env (MAL-value (car Ast*))) 'fn) + Args (cdr Ast*) ) + (setq Ast (apply (MAL-value Macro) Args)) ) ) + Ast ) + +(de EVAL (Ast Env) + (catch 'done + (while t + (when (not (= (MAL-type Ast) 'list)) + (throw 'done (eval-ast Ast Env)) ) + (setq Ast (macroexpand Ast Env)) + (when (or (not (= (MAL-type Ast) 'list)) (not (MAL-value Ast))) + (throw 'done (eval-ast Ast Env)) ) + (let (Ast* (MAL-value Ast) + A0* (MAL-value (car Ast*)) + A1 (cadr Ast*) + A1* (MAL-value A1) + A2 (caddr Ast*) + A3 (cadddr Ast*) ) + (cond + ((= A0* 'def!) + (throw 'done (set> Env A1* (EVAL A2 Env))) ) + ((= A0* 'quote) + (throw 'done A1) ) + ((= A0* 'quasiquote) + (setq Ast (quasiquote A1)) ) # TCO + ((= A0* 'defmacro!) + (let Form (EVAL A2 Env) + (put Form 'is-macro T) + (throw 'done (set> Env A1* Form)) ) ) + ((= A0* 'macroexpand) + (throw 'done (macroexpand A1 Env)) ) + ((= A0* 'try*) + (let Result (catch 'err (throw 'done (EVAL A1 Env))) + (if (isa '+MALError Result) + (let A (MAL-value A2) + (if (and (= (MAL-type A2) 'list) + (= (MAL-value (car A)) 'catch*) ) + (let (Bind (MAL-value (cadr A)) + Exc (MAL-value Result) + Form (caddr A) + Env* (MAL-env Env (list Bind) (list Exc)) ) + (throw 'done (EVAL Form Env*)) ) + (throw 'err Result) ) ) + (throw 'done Result) ) ) ) + ((= A0* 'let*) + (let Env* (MAL-env Env) + (for (Bindings A1* Bindings) + (let (Key (MAL-value (pop 'Bindings)) + Value (EVAL (pop 'Bindings) Env*) ) + (set> Env* Key Value) ) ) + (setq Env Env* Ast A2) ) ) # TCO + ((= A0* 'do) + (mapc '((Form) (EVAL Form Env)) (head -1 (cdr Ast*))) + (setq Ast (last Ast*)) ) # TCO + ((= A0* 'if) + (if (not (memq (MAL-type (EVAL A1 Env)) '(nil false))) + (setq Ast A2) # TCO + (if A3 + (setq Ast A3) # TCO + (throw 'done *MAL-nil) ) ) ) + ((= A0* 'fn*) + (let (Binds (mapcar MAL-value A1*) + Body A2 + Fn (MAL-fn + (curry (Env Binds Body) @ + (let Env* (MAL-env Env Binds (rest)) + (EVAL Body Env*) ) ) ) ) + (throw 'done (MAL-func Env Body Binds Fn)) ) ) + (T + (let (Ast* (MAL-value (eval-ast Ast Env)) + Fn (car Ast*) + Args (cdr Ast*) ) + (if (isa '+MALFn Fn) + (throw 'done (apply (MAL-value Fn) Args)) + (let Env* (MAL-env (get Fn 'env) (get Fn 'params) Args) + (setq Ast (get Fn 'ast) Env Env*) ) ) ) ) ) ) ) ) ) + +(de eval-ast (Ast Env) + (let Value (MAL-value Ast) + (case (MAL-type Ast) + (symbol (get> Env Value)) + (list (MAL-list (mapcar '((Form) (EVAL Form Env)) Value))) + (vector (MAL-vector (mapcar '((Form) (EVAL Form Env)) Value))) + (map (MAL-map (mapcar '((Form) (EVAL Form Env)) Value))) + (T Ast) ) ) ) + +(set> *ReplEnv 'eval (MAL-fn (curry (*ReplEnv) (Form) (EVAL Form *ReplEnv)))) +(set> *ReplEnv '*ARGV* (MAL-list (mapcar MAL-string (argv)))) + +(de PRINT (Ast) + (pr-str Ast T) ) + +(de rep (String) + (PRINT (EVAL (READ String) *ReplEnv)) ) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") + +(load-history ".mal_history") + +(if (argv) + (rep (pack "(load-file \"" (opt) "\")")) + (use Input + (until (=0 (setq Input (readline "user> "))) + (let Output (catch 'err (rep Input)) + (if (isa '+MALError Output) + (let Message (MAL-value Output) + (unless (= (MAL-value Message) "end of token stream") + (prinl "[error] " (pr-str Message)) ) ) + (prinl Output) ) ) ) ) ) + +(prinl) +(bye) From fbe5bd7afa7d1f86a86b22087a482c14d8485512 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 22 Oct 2016 21:28:53 +0200 Subject: [PATCH 0178/2308] Fix argv handling --- pil/step6_file.l | 4 ++-- pil/step7_quote.l | 4 ++-- pil/step8_macros.l | 4 ++-- pil/step9_try.l | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pil/step6_file.l b/pil/step6_file.l index 43ecba094e..1c3b8896c9 100644 --- a/pil/step6_file.l +++ b/pil/step6_file.l @@ -72,7 +72,7 @@ (T Ast) ) ) ) (set> *ReplEnv 'eval (MAL-fn (curry (*ReplEnv) (Form) (EVAL Form *ReplEnv)))) -(set> *ReplEnv '*ARGV* (MAL-list (mapcar MAL-string (argv)))) +(set> *ReplEnv '*ARGV* (MAL-list (mapcar MAL-string (cdr (argv))))) (de PRINT (Ast) (pr-str Ast T) ) @@ -86,7 +86,7 @@ (load-history ".mal_history") (if (argv) - (rep (pack "(load-file \"" (opt) "\")")) + (rep (pack "(load-file \"" (car (argv)) "\")")) (use Input (until (=0 (setq Input (readline "user> "))) (let Output (catch 'err (rep Input)) diff --git a/pil/step7_quote.l b/pil/step7_quote.l index 1ec689af52..85d8854317 100644 --- a/pil/step7_quote.l +++ b/pil/step7_quote.l @@ -96,7 +96,7 @@ (T Ast) ) ) ) (set> *ReplEnv 'eval (MAL-fn (curry (*ReplEnv) (Form) (EVAL Form *ReplEnv)))) -(set> *ReplEnv '*ARGV* (MAL-list (mapcar MAL-string (argv)))) +(set> *ReplEnv '*ARGV* (MAL-list (mapcar MAL-string (cdr (argv))))) (de PRINT (Ast) (pr-str Ast T) ) @@ -110,7 +110,7 @@ (load-history ".mal_history") (if (argv) - (rep (pack "(load-file \"" (opt) "\")")) + (rep (pack "(load-file \"" (car (argv)) "\")")) (use Input (until (=0 (setq Input (readline "user> "))) (let Output (catch 'err (rep Input)) diff --git a/pil/step8_macros.l b/pil/step8_macros.l index 7931f97706..8bef4bb0b3 100644 --- a/pil/step8_macros.l +++ b/pil/step8_macros.l @@ -120,7 +120,7 @@ (T Ast) ) ) ) (set> *ReplEnv 'eval (MAL-fn (curry (*ReplEnv) (Form) (EVAL Form *ReplEnv)))) -(set> *ReplEnv '*ARGV* (MAL-list (mapcar MAL-string (argv)))) +(set> *ReplEnv '*ARGV* (MAL-list (mapcar MAL-string (cdr (argv))))) (de PRINT (Ast) (pr-str Ast T) ) @@ -136,7 +136,7 @@ (load-history ".mal_history") (if (argv) - (rep (pack "(load-file \"" (opt) "\")")) + (rep (pack "(load-file \"" (car (argv)) "\")")) (use Input (until (=0 (setq Input (readline "user> "))) (let Output (catch 'err (rep Input)) diff --git a/pil/step9_try.l b/pil/step9_try.l index a33345ba14..c5e42d438f 100644 --- a/pil/step9_try.l +++ b/pil/step9_try.l @@ -133,7 +133,7 @@ (T Ast) ) ) ) (set> *ReplEnv 'eval (MAL-fn (curry (*ReplEnv) (Form) (EVAL Form *ReplEnv)))) -(set> *ReplEnv '*ARGV* (MAL-list (mapcar MAL-string (argv)))) +(set> *ReplEnv '*ARGV* (MAL-list (mapcar MAL-string (cdr (argv))))) (de PRINT (Ast) (pr-str Ast T) ) @@ -149,7 +149,7 @@ (load-history ".mal_history") (if (argv) - (rep (pack "(load-file \"" (opt) "\")")) + (rep (pack "(load-file \"" (car (argv)) "\")")) (use Input (until (=0 (setq Input (readline "user> "))) (let Output (catch 'err (rep Input)) From bbab5c5d380886e0a59eff17599a0dc51b075c33 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 22 Oct 2016 16:11:46 -0500 Subject: [PATCH 0179/2308] Basic: hashmap functions. Basic metadata (on funcs) - Metadata support required expanding the type size (to 5 bits). This also implies that ref cnt now only has 11 bits (2048). - Added ^ reader macro (with-meta) which required some refactoring of READ_MACRO to share code. - Rename some more variables: ZJ -> S S% -> X% ZR% -> Y% ZM% -> Y RE% -> D This removes remaining % variables apart from the pre-allocated arrays Z%, S% and X%. --- basic/core.in.bas | 150 +++++++++++++------ basic/debug.in.bas | 88 +++++------ basic/env.in.bas | 42 +++--- basic/printer.in.bas | 16 +- basic/reader.in.bas | 98 ++++++------ basic/step1_read_print.in.bas | 4 +- basic/step2_eval.in.bas | 62 ++++---- basic/step3_env.in.bas | 112 +++++++------- basic/step4_if_fn_do.in.bas | 177 +++++++++++----------- basic/step5_tco.in.bas | 183 ++++++++++++----------- basic/step6_file.in.bas | 183 ++++++++++++----------- basic/step7_quote.in.bas | 231 ++++++++++++++-------------- basic/step8_macros.in.bas | 255 +++++++++++++++---------------- basic/step9_try.in.bas | 273 +++++++++++++++++----------------- basic/stepA_mal.in.bas | 273 +++++++++++++++++----------------- basic/types.in.bas | 162 ++++++++++---------- basic/variables.txt | 32 +++- 17 files changed, 1235 insertions(+), 1106 deletions(-) diff --git a/basic/core.in.bas b/basic/core.in.bas index b3dccb6fb2..3c4f2420e5 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -19,11 +19,11 @@ DO_FUNCTION: DO_20_29: ON FF-19 GOTO DO_GT,DO_GTE,DO_ADD,DO_SUB,DO_MULT,DO_DIV,DO_TIME_MS,DO_LIST,DO_LIST_Q,DO_VECTOR DO_30_39: - ON FF-29 GOTO DO_VECTOR_Q,DO_HASH_MAP,DO_MAP_Q,DO_THROW,DO_THROW,DO_THROW,DO_THROW,DO_THROW,DO_THROW,DO_SEQUENTIAL_Q + ON FF-29 GOTO DO_VECTOR_Q,DO_HASH_MAP,DO_MAP_Q,DO_ASSOC,DO_THROW,DO_GET,DO_CONTAINS,DO_KEYS,DO_VALS,DO_SEQUENTIAL_Q DO_40_49: ON FF-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_APPLY,DO_MAP,DO_THROW DO_50_59: - ON FF-49 GOTO DO_THROW,DO_THROW,DO_THROW,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_SWAP_BANG,DO_EVAL + ON FF-49 GOTO DO_THROW,DO_WITH_META,DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_SWAP_BANG,DO_EVAL DO_EQUAL_Q: A=AA:B=AB:GOSUB EQUAL_Q @@ -31,7 +31,7 @@ DO_FUNCTION: RETURN DO_THROW: ER=AA - Z%(ER,0)=Z%(ER,0)+16 + Z%(ER,0)=Z%(ER,0)+32 R=0 RETURN DO_NIL_Q: @@ -48,14 +48,16 @@ DO_FUNCTION: RETURN DO_STRING_Q: R=1 - IF (Z%(AA,0)AND15)=4 THEN R=2 + IF (Z%(AA,0)AND31)<>4 THEN RETURN + IF MID$(S$(Z%(AA,1)),1,1)=CHR$(127) THEN RETURN + R=2 RETURN DO_SYMBOL: T=5:L=Z%(AA,1):GOSUB ALLOC RETURN DO_SYMBOL_Q: R=1 - IF (Z%(AA,0)AND15)=5 THEN R=2 + IF (Z%(AA,0)AND31)=5 THEN R=2 RETURN DO_KEYWORD: A=Z%(AA,1) @@ -66,7 +68,7 @@ DO_FUNCTION: RETURN DO_KEYWORD_Q: R=1 - IF (Z%(AA,0)AND15)<>4 THEN RETURN + IF (Z%(AA,0)AND31)<>4 THEN RETURN IF MID$(S$(Z%(AA,1)),1,1)<>CHR$(127) THEN RETURN R=2 RETURN @@ -151,7 +153,7 @@ DO_FUNCTION: DO_LIST: R=AR - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 RETURN DO_LIST_Q: A=AA:GOSUB LIST_Q @@ -162,26 +164,75 @@ DO_FUNCTION: RETURN DO_VECTOR_Q: R=1 - IF (Z%(AA,0)AND15)=7 THEN R=2 + IF (Z%(AA,0)AND31)=7 THEN R=2 RETURN DO_HASH_MAP: A=AR:T=8:GOSUB FORCE_SEQ_TYPE RETURN DO_MAP_Q: R=1 - IF (Z%(AA,0)AND15)=8 THEN R=2 + IF (Z%(AA,0)AND31)=8 THEN R=2 + RETURN + DO_ASSOC: + H=AA + AR=Z%(AR,1) + DO_ASSOC_LOOP: + R=AR+1:GOSUB DEREF_R:K=R + R=Z%(AR,1)+1:GOSUB DEREF_R:V=R + Z%(H,0)=Z%(H,0)+32 + GOSUB ASSOC1:H=R + AR=Z%(Z%(AR,1),1) + IF AR=0 OR Z%(AR,1)=0 THEN RETURN + GOTO DO_ASSOC_LOOP + DO_GET: + IF AA=0 THEN R=0:RETURN + H=AA:K=AB:GOSUB HASHMAP_GET + GOSUB DEREF_R + Z%(R,0)=Z%(R,0)+32 RETURN + DO_CONTAINS: + H=AA:K=AB:GOSUB HASHMAP_CONTAINS + R=R+1 + RETURN + DO_KEYS: + GOTO DO_KEYS_VALS + DO_VALS: + AA=Z%(AA,1) + DO_KEYS_VALS: + REM first result list element + T=6:L=0:N=0:GOSUB ALLOC:T2=R + + DO_KEYS_VALS_LOOP: + IF AA=0 OR Z%(AA,1)=0 THEN R=T2:RETURN + + REM copy the value + T1=Z%(AA+1,1) + REM inc ref cnt of referred argument + Z%(T1,0)=Z%(T1,0)+32 + Z%(R+1,1)=T1 + + T1=R: REM save previous + REM allocate next element + T=6:L=0:N=0:GOSUB ALLOC + REM point previous element to this one + Z%(T1,1)=R + + IF Z%(Z%(AA,1),1)=0 THEN R=T2:RETURN + + AA=Z%(Z%(AA,1),1) + + GOTO DO_KEYS_VALS_LOOP DO_SEQUENTIAL_Q: R=1 - IF (Z%(AA,0)AND15)=6 OR (Z%(AA,0)AND15)=7 THEN R=2 + IF (Z%(AA,0)AND31)=6 OR (Z%(AA,0)AND31)=7 THEN R=2 RETURN DO_CONS: T=6:L=AB:N=AA:GOSUB ALLOC RETURN DO_CONCAT: REM if empty arguments, return empty list - IF Z%(AR,1)=0 THEN R=3:Z%(R,0)=Z%(R,0)+16:RETURN + IF Z%(AR,1)=0 THEN R=3:Z%(R,0)=Z%(R,0)+32:RETURN REM single argument IF Z%(Z%(AR,1),1)<>0 THEN GOTO DO_CONCAT_MULT @@ -191,21 +242,21 @@ DO_FUNCTION: REM multiple arguments DO_CONCAT_MULT: - CZ%=X: REM save current stack position + CZ=X: REM save current stack position REM push arguments onto the stack DO_CONCAT_STACK: R=AR+1:GOSUB DEREF_R - X=X+1:S%(X)=R: REM push sequence + X=X+1:X%(X)=R: REM push sequence AR=Z%(AR,1) IF Z%(AR,1)<>0 THEN GOTO DO_CONCAT_STACK REM pop last argument as our seq to prepend to - AB=S%(X):X=X-1 + AB=X%(X):X=X-1 REM last arg/seq is not copied so we need to inc ref to it - Z%(AB,0)=Z%(AB,0)+16 + Z%(AB,0)=Z%(AB,0)+32 DO_CONCAT_LOOP: - IF X=CZ% THEN R=AB:RETURN - AA=S%(X):X=X-1: REM pop off next seq to prepend + IF X=CZ THEN R=AB:RETURN + AA=X%(X):X=X-1: REM pop off next seq to prepend IF Z%(AA,1)=0 THEN GOTO DO_CONCAT_LOOP: REM skip empty seqs A=AA:B=0:C=-1:GOSUB SLICE @@ -228,16 +279,16 @@ DO_FUNCTION: GOTO DO_NTH_LOOP DO_NTH_DONE: R=Z%(AA+1,1) - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 RETURN DO_FIRST: IF AA=0 THEN R=0:RETURN IF Z%(AA,1)=0 THEN R=0 IF Z%(AA,1)<>0 THEN R=AA+1:GOSUB DEREF_R - IF R<>0 THEN Z%(R,0)=Z%(R,0)+16 + IF R<>0 THEN Z%(R,0)=Z%(R,0)+32 RETURN DO_REST: - IF AA=0 THEN R=3:Z%(R,0)=Z%(R,0)+16:RETURN + IF AA=0 THEN R=3:Z%(R,0)=Z%(R,0)+32:RETURN IF Z%(AA,1)=0 THEN A=AA IF Z%(AA,1)<>0 THEN A=Z%(AA,1) T=6:GOSUB FORCE_SEQ_TYPE @@ -257,7 +308,7 @@ DO_FUNCTION: A=Z%(AR+1,1) REM no intermediate args, but not a list, so convert it first - IF R4<=1 AND (Z%(A,0)AND15)<>6 THEN :T=6:GOSUB FORCE_SEQ_TYPE:GOTO DO_APPLY_2 + IF R4<=1 AND (Z%(A,0)AND31)<>6 THEN T=6:GOSUB FORCE_SEQ_TYPE:GOTO DO_APPLY_2 REM no intermediate args, just call APPLY directly IF R4<=1 THEN AR=A:GOSUB APPLY:RETURN @@ -267,12 +318,12 @@ DO_FUNCTION: AY=Z%(R6,1):GOSUB RELEASE REM attach end of slice to final args element Z%(R6,1)=Z%(A+1,1) - Z%(Z%(A+1,1),0)=Z%(Z%(A+1,1),0)+16 + Z%(Z%(A+1,1),0)=Z%(Z%(A+1,1),0)+32 DO_APPLY_2: - X=X+1:S%(X)=R: REM push/save new args for release + X=X+1:X%(X)=R: REM push/save new args for release AR=R:GOSUB APPLY - AY=S%(X):X=X-1:GOSUB RELEASE: REM pop/release new args + AY=X%(X):X=X-1:GOSUB RELEASE: REM pop/release new args RETURN DO_MAP: F=AA @@ -281,38 +332,38 @@ DO_FUNCTION: T=6:L=0:N=0:GOSUB ALLOC REM push future return val, prior entry, F and AB - X=X+4:S%(X-3)=R:S%(X-2)=0:S%(X-1)=F:S%(X)=AB + X=X+4:X%(X-3)=R:X%(X-2)=0:X%(X-1)=F:X%(X)=AB DO_MAP_LOOP: REM set previous to current if not the first element - IF S%(X-2)<>0 THEN Z%(S%(X-2),1)=R + IF X%(X-2)<>0 THEN Z%(X%(X-2),1)=R REM update previous reference to current - S%(X-2)=R + X%(X-2)=R IF Z%(AB,1)=0 THEN GOTO DO_MAP_DONE REM create argument list for apply call - Z%(3,0)=Z%(3,0)+16 + Z%(3,0)=Z%(3,0)+32 REM inc ref cnt of referred argument T=6:L=3:N=Z%(AB+1,1):GOSUB ALLOC REM push argument list - X=X+1:S%(X)=R + X=X+1:X%(X)=R AR=R:GOSUB APPLY REM pop apply args are release them - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE REM set the result value - Z%(S%(X-2)+1,1)=R + Z%(X%(X-2)+1,1)=R REM restore F - F=S%(X-1) + F=X%(X-1) REM update AB to next source element - S%(X)=Z%(S%(X),1) - AB=S%(X) + X%(X)=Z%(X%(X),1) + AB=X%(X) REM allocate next element T=6:L=0:N=0:GOSUB ALLOC @@ -321,28 +372,39 @@ DO_FUNCTION: DO_MAP_DONE: REM get return val - R=S%(X-3) + R=X%(X-3) REM pop everything off stack X=X-4 RETURN + DO_WITH_META: + T=Z%(AA,0)AND31 + REM remove existing metadata first + IF T>=16 THEN AA=Z%(AA,1):GOTO DO_WITH_META + T=T+16:L=AA:N=AB:GOSUB ALLOC + RETURN + DO_META: + IF (Z%(AA,0)AND31)<16 THEN R=0:RETURN + R=Z%(AA+1,1) + Z%(R,0)=Z%(R,0)+32 + RETURN DO_ATOM: T=12:L=AA:GOSUB ALLOC RETURN DO_ATOM_Q: R=1 - IF (Z%(AA,0)AND15)=12 THEN R=2 + IF (Z%(AA,0)AND31)=12 THEN R=2 RETURN DO_DEREF: R=Z%(AA,1):GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 RETURN DO_RESET_BANG: R=AB REM release current value AY=Z%(AA,1):GOSUB RELEASE REM inc ref by 2 for atom ownership and since we are returning it - Z%(R,0)=Z%(R,0)+32 + Z%(R,0)=Z%(R,0)+64 REM update value Z%(AA,1)=R RETURN @@ -354,18 +416,18 @@ DO_FUNCTION: AR=R REM push args for release after - X=X+1:S%(X)=AR + X=X+1:X%(X)=AR REM push atom - X=X+1:S%(X)=AA + X=X+1:X%(X)=AA GOSUB APPLY REM pop atom - AA=S%(X):X=X-1 + AA=X%(X):X=X-1 REM pop and release args - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE REM use reset to update the value AB=R:GOSUB DO_RESET_BANG @@ -376,14 +438,14 @@ DO_FUNCTION: RETURN DO_PR_MEMORY: - P1%=ZT%:P2%=-1:GOSUB PR_MEMORY + P1=ZT:P2=-1:GOSUB PR_MEMORY RETURN DO_PR_MEMORY_SUMMARY: GOSUB PR_MEMORY_SUMMARY RETURN DO_EVAL: - A=AA:E=RE%:GOSUB EVAL + A=AA:E=D:GOSUB EVAL RETURN INIT_CORE_SET_FUNCTION: diff --git a/basic/debug.in.bas b/basic/debug.in.bas index 7f43dde634..cafc3186e9 100644 --- a/basic/debug.in.bas +++ b/basic/debug.in.bas @@ -1,94 +1,94 @@ REM CHECK_FREE_LIST CHECK_FREE_LIST: REM start and accumulator - P1%=ZK - P2%=0 + P1=ZK + P2=0 CHECK_FREE_LIST_LOOP: - IF P1%>=ZI THEN GOTO CHECK_FREE_LIST_DONE - IF (Z%(P1%,0)AND15)<>15 THEN P2%=-1:GOTO CHECK_FREE_LIST_DONE - P2%=P2%+(Z%(P1%,0)AND-16)/16 - P1%=Z%(P1%,1) + IF P1>=ZI THEN GOTO CHECK_FREE_LIST_DONE + IF (Z%(P1,0)AND31)<>15 THEN P2=-1:GOTO CHECK_FREE_LIST_DONE + P2=P2+(Z%(P1,0)AND-32)/32 + P1=Z%(P1,1) GOTO CHECK_FREE_LIST_LOOP CHECK_FREE_LIST_DONE: - IF P2%=-1 THEN PRINT "corrupt free list at "+STR$(P1%) + IF P2=-1 THEN PRINT "corrupt free list at "+STR$(P1) RETURN PR_MEMORY_SUMMARY: - GOSUB CHECK_FREE_LIST: REM get count in P2% + GOSUB CHECK_FREE_LIST: REM get count in P2 PRINT PRINT "Free memory (FRE) : "+STR$(FRE(0)) PRINT "Value memory (Z%) : "+STR$(ZI-1)+" /"+STR$(Z1) PRINT " "; - PRINT " used:"+STR$(ZI-1-P2%)+", freed:"+STR$(P2%); - PRINT ", post repl_env:"+STR$(ZT%) - PRINT "String values (S$) : "+STR$(ZJ)+" /"+STR$(Z2) - PRINT "Call stack size (S%) : "+STR$(X+1)+" /"+STR$(Z3) + PRINT " used:"+STR$(ZI-1-P2)+", freed:"+STR$(P2); + PRINT ", post repl_env:"+STR$(ZT) + PRINT "String values (S$) : "+STR$(S)+" /"+STR$(Z2) + PRINT "Call stack size (X%) : "+STR$(X+1)+" /"+STR$(Z3) RETURN -REM REM PR_MEMORY(P1%, P2%) -> nil +REM REM PR_MEMORY(P1, P2) -> nil REM PR_MEMORY: -REM IF P2%"+STR$(P2%); +REM PRINT "Z% Value Memory"+STR$(P1)+"->"+STR$(P2); REM PRINT " (ZI: "+STR$(ZI)+", ZK: "+STR$(ZK)+"):" -REM IF P2%P2% THEN GOTO PR_MEMORY_AFTER_VALUES +REM IF I>P2 THEN GOTO PR_MEMORY_AFTER_VALUES REM PRINT " "+STR$(I); -REM IF (Z%(I,0)AND15)=15 THEN GOTO PR_MEMORY_FREE -REM PRINT ": ref cnt: "+STR$((Z%(I,0)AND-16)/16); -REM PRINT ", type: "+STR$(Z%(I,0)AND15)+", value: "+STR$(Z%(I,1)); -REM IF (Z%(I,0)AND15)=4 THEN PRINT " '"+S$(Z%(I,1))+"'"; -REM IF (Z%(I,0)AND15)=5 THEN PRINT " "+S$(Z%(I,1))+""; +REM IF (Z%(I,0)AND31)=15 THEN GOTO PR_MEMORY_FREE +REM PRINT ": ref cnt: "+STR$((Z%(I,0)AND-32)/32); +REM PRINT ", type: "+STR$(Z%(I,0)AND31)+", value: "+STR$(Z%(I,1)); +REM IF (Z%(I,0)AND31)=4 THEN PRINT " '"+S$(Z%(I,1))+"'"; +REM IF (Z%(I,0)AND31)=5 THEN PRINT " "+S$(Z%(I,1))+""; REM PRINT REM I=I+1 -REM IF (Z%(I-1,0)AND15)<>10 THEN GOTO PR_MEMORY_VALUE_LOOP +REM IF (Z%(I-1,0)AND31)<>10 THEN GOTO PR_MEMORY_VALUE_LOOP REM PRINT " "+STR$(I)+": "; REM PRINT "params: "+STR$(Z%(I+1,0))+", env:"+STR$(Z%(I+1,1)) REM I=I+1 REM GOTO PR_MEMORY_VALUE_LOOP REM PR_MEMORY_FREE: -REM PRINT ": FREE size: "+STR$((Z%(I,0)AND-16)/16)+", next: "+STR$(Z%(I,1)); +REM PRINT ": FREE size: "+STR$((Z%(I,0)AND-32)/32)+", next: "+STR$(Z%(I,1)); REM IF I=ZK THEN PRINT " (free list start)"; REM PRINT -REM IF (Z%(I,0)AND-16)=32 THEN I=I+1:PRINT " "+STR$(I)+": ---" +REM IF (Z%(I,0)AND-32)=64 THEN I=I+1:PRINT " "+STR$(I)+": ---" REM I=I+1 REM GOTO PR_MEMORY_VALUE_LOOP REM PR_MEMORY_AFTER_VALUES: -REM PRINT "ZS% String Memory (ZJ: "+STR$(ZJ)+"):" -REM IF ZJ<=0 THEN PRINT " ---":GOTO PR_MEMORY_SKIP_STRINGS -REM FOR I=0 TO ZJ-1 +REM PRINT "S$ String Memory (S: "+STR$(S)+"):" +REM IF S<=0 THEN PRINT " ---":GOTO PR_MEMORY_SKIP_STRINGS +REM FOR I=0 TO S-1 REM PRINT " "+STR$(I)+": '"+S$(I)+"'" REM NEXT I REM PR_MEMORY_SKIP_STRINGS: -REM PRINT "S% Stack Memory (X: "+STR$(X)+"):" +REM PRINT "X% Stack Memory (X: "+STR$(X)+"):" REM IF X<0 THEN PRINT " ---":GOTO PR_MEMORY_SKIP_STACK REM FOR I=0 TO X -REM PRINT " "+STR$(I)+": "+STR$(S%(I)) +REM PRINT " "+STR$(I)+": "+STR$(X%(I)) REM NEXT I REM PR_MEMORY_SKIP_STACK: REM PRINT "^^^^^^" REM RETURN REM -REM REM PR_OBJECT(P1%) -> nil +REM REM PR_OBJECT(P1) -> nil REM PR_OBJECT: -REM RD%=0 +REM RD=0 REM -REM RD%=RD%+1:X=X+1:S%(X)=P1% +REM RD=RD+1:X=X+1:X%(X)=P1 REM REM PR_OBJ_LOOP: -REM IF RD%=0 THEN RETURN -REM I=S%(X):RD%=RD%-1:X=X-1 +REM IF RD=0 THEN RETURN +REM I=X%(X):RD=RD-1:X=X-1 REM -REM P2%=Z%(I,0)AND15 +REM P2=Z%(I,0)AND31 REM PRINT " "+STR$(I); -REM PRINT ": ref cnt: "+STR$((Z%(I,0)AND-16)/16); -REM PRINT ", type: "+STR$(P2%)+", value: "+STR$(Z%(I,1)); -REM IF P2%=4 THEN PRINT " '"+S$(Z%(I,1))+"'"; -REM IF P2%=5 THEN PRINT " "+S$(Z%(I,1))+""; +REM PRINT ": ref cnt: "+STR$((Z%(I,0)AND-32)/32); +REM PRINT ", type: "+STR$(P2)+", value: "+STR$(Z%(I,1)); +REM IF P2=4 THEN PRINT " '"+S$(Z%(I,1))+"'"; +REM IF P2=5 THEN PRINT " "+S$(Z%(I,1))+""; REM PRINT -REM IF P2%<=5 OR P2%=9 THEN GOTO PR_OBJ_LOOP -REM IF Z%(I,1)<>0 THEN RD%=RD%+1:X=X+1:S%(X)=Z%(I,1) -REM IF P2%>=6 AND P2%<=8 THEN RD%=RD%+1:X=X+1:S%(X)=I+1 +REM IF P2<=5 OR P2=9 THEN GOTO PR_OBJ_LOOP +REM IF Z%(I,1)<>0 THEN RD=RD+1:X=X+1:X%(X)=Z%(I,1) +REM IF P2>=6 AND P2<=8 THEN RD=RD+1:X=X+1:X%(X)=I+1 REM GOTO PR_OBJ_LOOP diff --git a/basic/env.in.bas b/basic/env.in.bas index 858f09b93b..792f378032 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -12,40 +12,42 @@ ENV_NEW: REM see RELEASE types.in.bas for environment cleanup -REM ENV_NEW_BINDS(O, BI%, EX%) -> R +REM ENV_NEW_BINDS(O, BI, EX) -> R ENV_NEW_BINDS: GOSUB ENV_NEW E=R REM process bindings ENV_NEW_BINDS_LOOP: - IF Z%(BI%,1)=0 THEN R=E:RETURN - REM get/deref the key from BI% - R=BI%+1:GOSUB DEREF_R + IF Z%(BI,1)=0 THEN R=E:RETURN + REM get/deref the key from BI + R=BI+1:GOSUB DEREF_R K=R IF S$(Z%(K,1))="&" THEN GOTO EVAL_NEW_BINDS_VARGS EVAL_NEW_BINDS_1x1: - REM get/deref the key from EX% - R=EX%+1:GOSUB DEREF_R + REM get/deref the key from EX + R=EX+1:GOSUB DEREF_R V=R REM set the binding in the environment data GOSUB ENV_SET - REM go to next element of BI% and EX% - BI%=Z%(BI%,1) - EX%=Z%(EX%,1) + REM go to next element of BI and EX + BI=Z%(BI,1) + EX=Z%(EX,1) GOTO ENV_NEW_BINDS_LOOP EVAL_NEW_BINDS_VARGS: - REM get/deref the key from next element of BI% - BI%=Z%(BI%,1) - R=BI%+1:GOSUB DEREF_R + REM get/deref the key from next element of BI + BI=Z%(BI,1) + R=BI+1:GOSUB DEREF_R K=R - REM the value is the remaining list in EX% - V=EX% + REM the value is the remaining list in EX + A=EX:T=6:GOSUB FORCE_SEQ_TYPE + V=R REM set the binding in the environment data GOSUB ENV_SET R=E + AY=V:GOSUB RELEASE: REM list is owned by environment RETURN REM ENV_SET(E, K, V) -> R @@ -68,17 +70,17 @@ REM ENV_FIND(E, K) -> R REM Returns environment (R) containing K. If found, value found is REM in T4 ENV_FIND: - EF%=E + EF=E ENV_FIND_LOOP: - H=Z%(EF%,1) + H=Z%(EF,1) REM More efficient to use GET for value (R) and contains? (T3) GOSUB HASHMAP_GET REM if we found it, save value in T4 for ENV_GET IF T3=1 THEN T4=R:GOTO ENV_FIND_DONE - EF%=Z%(EF%+1,1): REM get outer environment - IF EF%<>-1 THEN GOTO ENV_FIND_LOOP + EF=Z%(EF+1,1): REM get outer environment + IF EF<>-1 THEN GOTO ENV_FIND_LOOP ENV_FIND_DONE: - R=EF% + R=EF RETURN REM ENV_GET(E, K) -> R @@ -86,5 +88,5 @@ ENV_GET: GOSUB ENV_FIND IF R=-1 THEN R=0:ER=-1:ER$="'"+S$(Z%(K,1))+"' not found":RETURN R=T4:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 RETURN diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 7eb0028c17..a686a6a1ed 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -2,9 +2,11 @@ REM PR_STR(AZ, PR) -> R$ PR_STR: RR$="" PR_STR_RECUR: - T=Z%(AZ,0)AND15 + T=Z%(AZ,0)AND31 REM PRINT "AZ: "+STR$(AZ)+", T: "+STR$(T)+", V: "+STR$(Z%(AZ,1)) IF T=0 THEN R$="nil":RETURN + REM if metadata, then get actual object + IF T>=16 THEN AZ=Z%(AZ,1):GOTO PR_STR_RECUR ON T GOTO PR_BOOLEAN,PR_INTEGER,PR_UNKNOWN,PR_STRING_MAYBE,PR_SYMBOL,PR_SEQ,PR_SEQ,PR_SEQ,PR_FUNCTION,PR_MAL_FUNCTION,PR_MAL_FUNCTION,PR_ATOM,PR_ENV,PR_RECUR,PR_FREE PR_UNKNOWN: @@ -46,8 +48,8 @@ PR_STR: IF T=8 THEN RR$=RR$+"{" REM push the type and where we are in the sequence X=X+2 - S%(X-1)=T - S%(X)=AZ + X%(X-1)=T + X%(X)=AZ PR_SEQ_LOOP: IF Z%(AZ,1)=0 THEN PR_SEQ_DONE AZ=AZ+1 @@ -55,15 +57,15 @@ PR_STR: REM if we just rendered a non-sequence, then append it IF T<6 OR T>8 THEN RR$=RR$+R$ REM restore current seq type - T=S%(X-1) + T=X%(X-1) REM Go to next list element - AZ=Z%(S%(X),1) - S%(X)=AZ + AZ=Z%(X%(X),1) + X%(X)=AZ IF Z%(AZ,1)<>0 THEN RR$=RR$+" " GOTO PR_SEQ_LOOP PR_SEQ_DONE: REM get type - T=S%(X-1) + T=X%(X-1) REM pop where we are the sequence and type X=X-2 IF T=6 THEN RR$=RR$+")" diff --git a/basic/reader.in.bas b/basic/reader.in.bas index f76e7b8c37..2db435d7c2 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -1,17 +1,17 @@ -REM READ_TOKEN(A$, IDX%) -> T$ +REM READ_TOKEN(A$, IDX) -> T$ READ_TOKEN: - CUR%=IDX% - REM PRINT "READ_TOKEN: "+STR$(CUR%)+", "+MID$(A$,CUR%,1) - T$=MID$(A$,CUR%,1) + CUR=IDX + REM PRINT "READ_TOKEN: "+STR$(CUR)+", "+MID$(A$,CUR,1) + T$=MID$(A$,CUR,1) IF T$="(" OR T$=")" OR T$="[" OR T$="]" OR T$="{" OR T$="}" THEN RETURN IF T$="'" OR T$="`" OR T$="@" THEN RETURN - IF T$="~" AND NOT MID$(A$,CUR%+1,1)="@" THEN RETURN + IF T$="~" AND NOT MID$(A$,CUR+1,1)="@" THEN RETURN S1=0:S2=0: REM S1: INSTRING?, S2: ESCAPED? IF T$=CHR$(34) THEN S1=1 - CUR%=CUR%+1 + CUR=CUR+1 READ_TOKEN_LOOP: - IF CUR%>LEN(A$) THEN RETURN - CH$=MID$(A$,CUR%,1) + IF CUR>LEN(A$) THEN RETURN + CH$=MID$(A$,CUR,1) IF S2 THEN GOTO READ_TOKEN_CONT IF S1 THEN GOTO READ_TOKEN_CONT IF CH$=" " OR CH$="," THEN RETURN @@ -19,16 +19,16 @@ READ_TOKEN: READ_TOKEN_CONT: T$=T$+CH$ IF T$="~@" THEN RETURN - CUR%=CUR%+1 + CUR=CUR+1 IF S1 AND S2 THEN S2=0:GOTO READ_TOKEN_LOOP IF S1 AND S2=0 AND CH$=CHR$(92) THEN S2=1:GOTO READ_TOKEN_LOOP IF S1 AND S2=0 AND CH$=CHR$(34) THEN RETURN GOTO READ_TOKEN_LOOP SKIP_SPACES: - CH$=MID$(A$,IDX%,1) + CH$=MID$(A$,IDX,1) IF (CH$<>" ") AND (CH$<>",") AND (CH$<>CHR$(13)) AND (CH$<>CHR$(10)) THEN RETURN - IDX%=IDX%+1 + IDX=IDX+1 GOTO SKIP_SPACES @@ -36,7 +36,7 @@ READ_ATOM: R=0 RETURN -REM READ_FORM(A$, IDX%) -> R +REM READ_FORM(A$, IDX) -> R READ_FORM: IF ER<>-2 THEN RETURN GOSUB SKIP_SPACES @@ -51,6 +51,7 @@ READ_FORM: IF T$="`" THEN AS$="quasiquote":GOTO READ_MACRO IF T$="~" THEN AS$="unquote":GOTO READ_MACRO IF T$="~@" THEN AS$="splice-unquote":GOTO READ_MACRO + IF T$="^" THEN AS$="with-meta":GOTO READ_MACRO IF T$="@" THEN AS$="deref":GOTO READ_MACRO CH$=MID$(T$,1,1) REM PRINT "CH$: ["+CH$+"]("+STR$(ASC(CH$))+")" @@ -69,36 +70,49 @@ READ_FORM: GOTO READ_SYMBOL READ_TO_EOL: - CH$=MID$(A$,IDX%+1,1) - IDX%=IDX%+1 + CH$=MID$(A$,IDX+1,1) + IDX=IDX+1 IF CH$="" OR CH$=CHR$(13) OR CH$=CHR$(10) THEN GOTO READ_FORM GOTO READ_TO_EOL READ_NIL_BOOL: REM PRINT "READ_NIL_BOOL" R=T - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO READ_FORM_DONE READ_NUMBER: REM PRINT "READ_NUMBER" T=2:L=VAL(T$):GOSUB ALLOC GOTO READ_FORM_DONE READ_MACRO: - IDX%=IDX%+LEN(T$) - T=5:GOSUB STRING: REM AS$ set above - + IDX=IDX+LEN(T$) REM to call READ_FORM recursively, SD needs to be saved, set to REM 0 for the call and then restored afterwards. - X=X+2:S%(X-1)=SD:S%(X)=R: REM push SD and symbol - SD=0:GOSUB READ_FORM:B1%=R - SD=S%(X-1):B2%=S%(X):X=X-2: REM pop SD, pop symbol into B2% + X=X+2:X%(X-1)=(T$="^"):X%(X)=SD: REM push macro type and SD - GOSUB LIST2 - REM release values, list has ownership - AY=B1%:GOSUB RELEASE - AY=B2%:GOSUB RELEASE + REM AS$ is set above + T=5:GOSUB STRING:X=X+1:X%(X)=R - T$="" - GOTO READ_FORM_DONE + SD=0:GOSUB READ_FORM:X=X+1:X%(X)=R + + IF X%(X-3) THEN GOTO READ_MACRO_3 + + READ_MACRO_2: + B2=X%(X-1):B1=X%(X):GOSUB LIST2 + GOTO READ_MACRO_DONE + + READ_MACRO_3: + SD=0:GOSUB READ_FORM + B3=X%(X-1):B2=R:B1=X%(X):GOSUB LIST3 + AY=B3:GOSUB RELEASE + + READ_MACRO_DONE: + REM release values, list has ownership + AY=B2:GOSUB RELEASE + AY=B1:GOSUB RELEASE + + SD=X%(X-2):X=X-4: REM get SD and pop the stack + T$="": REM necessary to prevent unexpected EOF errors + GOTO READ_FORM_DONE READ_STRING: REM PRINT "READ_STRING" T7$=MID$(T$,LEN(T$),1) @@ -130,34 +144,34 @@ READ_FORM: L=0:N=0:GOSUB ALLOC: REM T alread set above REM set reference value/pointer to new embedded sequence - IF SD>1 THEN Z%(S%(X)+1,1)=R + IF SD>1 THEN Z%(X%(X)+1,1)=R REM push start ptr on the stack X=X+1 - S%(X)=R + X%(X)=R REM push current sequence type X=X+1 - S%(X)=T + X%(X)=T REM push previous ptr on the stack X=X+1 - S%(X)=R + X%(X)=R - IDX%=IDX%+LEN(T$) + IDX=IDX+LEN(T$) GOTO READ_FORM READ_SEQ_END: REM PRINT "READ_SEQ_END" IF SD=0 THEN ER$="unexpected '"+CH$+"'":GOTO READ_FORM_ABORT - IF S%(X-1)<>T THEN ER$="sequence mismatch":GOTO READ_FORM_ABORT + IF X%(X-1)<>T THEN ER$="sequence mismatch":GOTO READ_FORM_ABORT SD=SD-1: REM decrease read sequence depth - R=S%(X-2): REM ptr to start of sequence to return - T=S%(X-1): REM type prior to recur + R=X%(X-2): REM ptr to start of sequence to return + T=X%(X-1): REM type prior to recur X=X-3: REM pop previous, type, and start off the stack GOTO READ_FORM_DONE READ_FORM_DONE: - IDX%=IDX%+LEN(T$) + IDX=IDX+LEN(T$) T8=R: REM save previous value @@ -167,21 +181,21 @@ READ_FORM: REM allocate new sequence entry and space for value REM set type to previous type, with ref count of 1 (from previous) - T=S%(X-1):L=0:N=0:GOSUB ALLOC + T=X%(X-1):L=0:N=0:GOSUB ALLOC REM previous element - T7=S%(X) + T7=X%(X) REM set previous list element to point to new element Z%(T7,1)=R REM set the list value pointer Z%(T7+1,1)=T8 - IF T7=S%(X-2) THEN GOTO READ_FORM_SKIP_FIRST + IF T7=X%(X-2) THEN GOTO READ_FORM_SKIP_FIRST Z%(T7,1)=R READ_FORM_SKIP_FIRST: REM update previous pointer to current element - S%(X)=R + X%(X)=R GOTO READ_FORM READ_FORM_ABORT: @@ -191,13 +205,13 @@ READ_FORM: IF SD=0 THEN RETURN X=X-3: REM pop previous, type, and start off the stack SD=SD-1 - IF SD=0 THEN AY=S%(X+1):GOSUB RELEASE + IF SD=0 THEN AY=X%(X+1):GOSUB RELEASE GOTO READ_FORM_ABORT_UNWIND REM READ_STR(A$) -> R READ_STR: - IDX%=1 + IDX=1 SD=0: REM sequence read depth GOSUB READ_FORM RETURN diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index df6d7c595b..b8bcf52e72 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -43,7 +43,7 @@ REM MAIN program MAIN: GOSUB INIT_MEMORY - ZT%=ZI: REM top of memory after base repl_env + ZT=ZI: REM top of memory after base repl_env REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser @@ -56,7 +56,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + REM P1=ZT: P2=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index f4aa6ca106..f8bfc7edde 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -17,26 +17,26 @@ EVAL_AST: LV=LV+1 REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T=Z%(A,0)AND15 + T=Z%(A,0)AND31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: H=E:K=A:GOSUB HASHMAP_GET GOSUB DEREF_R IF T3=0 THEN ER=-1:ER$="'"+S$(Z%(A,1))+"' not found":GOTO EVAL_AST_RETURN - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN EVAL_AST_SEQ: @@ -46,28 +46,28 @@ EVAL_AST: REM make space on the stack X=X+4 REM push type of sequence - S%(X-3)=T + X%(X-3)=T REM push sequence index - S%(X-2)=-1 + X%(X-2)=-1 REM push future return value (new sequence) - S%(X-1)=R + X%(X-1)=R REM push previous new sequence entry - S%(X)=R + X%(X)=R EVAL_AST_SEQ_LOOP: REM update index - S%(X-2)=S%(X-2)+1 + X%(X-2)=X%(X-2)+1 REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value + Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: @@ -79,18 +79,18 @@ EVAL_AST: EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(S%(X)+1,1)=R + Z%(X%(X)+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=S%(X-3):L=0:N=0:GOSUB ALLOC + T=X%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(S%(X),1)=R + Z%(X%(X),1)=R REM update previous ptr to current entry - S%(X)=R + X%(X)=R REM process the next sequence entry from source list A=Z%(A,1) @@ -98,14 +98,14 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM get return value (new seq), index, and seq type - R=S%(X-1) + R=X%(X-1) REM pop previous, return, index and type X=X-4 GOTO EVAL_AST_RETURN EVAL_AST_RETURN: REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 LV=LV-1 RETURN @@ -115,7 +115,7 @@ EVAL: LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A REM AZ=A:PR=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" @@ -130,7 +130,7 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN EVAL_INVOKE: GOSUB EVAL_AST @@ -142,7 +142,7 @@ EVAL: AR=Z%(R,1): REM rest R=F:GOSUB DEREF_R:F=R - IF (Z%(F,0)AND15)<>9 THEN ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN + IF (Z%(F,0)AND31)<>9 THEN ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION AY=R3:GOSUB RELEASE GOTO EVAL_RETURN @@ -153,10 +153,10 @@ EVAL: REM trigger GC - TA%=FRE(0) + TA=FRE(0) REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 RETURN @@ -203,14 +203,14 @@ MAL_PRINT: RETURN REM REP(A$) -> R$ -REM Assume RE% has repl_env +REM Assume D has repl_env REP: R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -231,25 +231,25 @@ MAIN: LV=0 REM create repl_env - GOSUB HASHMAP:RE%=R + GOSUB HASHMAP:D=R REM + function A=1:GOSUB NATIVE_FUNCTION - H=RE%:K$="+":V=R:GOSUB ASSOC1_S:RE%=R + H=D:K$="+":V=R:GOSUB ASSOC1_S:D=R REM - function A=2:GOSUB NATIVE_FUNCTION - H=RE%:K$="-":V=R:GOSUB ASSOC1_S:RE%=R + H=D:K$="-":V=R:GOSUB ASSOC1_S:D=R REM * function A=3:GOSUB NATIVE_FUNCTION - H=RE%:K$="*":V=R:GOSUB ASSOC1_S:RE%=R + H=D:K$="*":V=R:GOSUB ASSOC1_S:D=R REM / function A=4:GOSUB NATIVE_FUNCTION - H=RE%:K$="/":V=R:GOSUB ASSOC1_S:RE%=R + H=D:K$="/":V=R:GOSUB ASSOC1_S:D=R - ZT%=ZI: REM top of memory after base repl_env + ZT=ZI: REM top of memory after base repl_env REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser @@ -262,7 +262,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + REM P1=ZT: P2=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index 231403cb38..ddf5d99dbd 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -18,19 +18,19 @@ EVAL_AST: LV=LV+1 REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T=Z%(A,0)AND15 + T=Z%(A,0)AND31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -44,28 +44,28 @@ EVAL_AST: REM make space on the stack X=X+4 REM push type of sequence - S%(X-3)=T + X%(X-3)=T REM push sequence index - S%(X-2)=-1 + X%(X-2)=-1 REM push future return value (new sequence) - S%(X-1)=R + X%(X-1)=R REM push previous new sequence entry - S%(X)=R + X%(X)=R EVAL_AST_SEQ_LOOP: REM update index - S%(X-2)=S%(X-2)+1 + X%(X-2)=X%(X-2)+1 REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value + Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: @@ -77,18 +77,18 @@ EVAL_AST: EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(S%(X)+1,1)=R + Z%(X%(X)+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=S%(X-3):L=0:N=0:GOSUB ALLOC + T=X%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(S%(X),1)=R + Z%(X%(X),1)=R REM update previous ptr to current entry - S%(X)=R + X%(X)=R REM process the next sequence entry from source list A=Z%(A,1) @@ -96,9 +96,9 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER=-2 THEN R=S%(X-1) + IF ER=-2 THEN R=X%(X-1) REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE REM pop previous, return, index and type X=X-4 @@ -106,7 +106,7 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 LV=LV-1 RETURN @@ -116,7 +116,7 @@ EVAL: LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A REM AZ=A:PR=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" @@ -131,71 +131,71 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0%=A+1 - R=A0%:GOSUB DEREF_R:A0%=R + A0=A+1 + R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) + IF (Z%(A0,0)AND31)<>5 THEN A$="" + IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A,1),1),1)+1 - R=A3%:GOSUB DEREF_R:A3%=R + A3=Z%(Z%(Z%(A,1),1),1)+1 + R=A3:GOSUB DEREF_R:A3=R EVAL_GET_A2: - A2%=Z%(Z%(A,1),1)+1 - R=A2%:GOSUB DEREF_R:A2%=R + A2=Z%(Z%(A,1),1)+1 + R=A2:GOSUB DEREF_R:A2=R EVAL_GET_A1: - A1%=Z%(A,1)+1 - R=A1%:GOSUB DEREF_R:A1%=R + A1=Z%(A,1)+1 + R=A1:GOSUB DEREF_R:A1=R RETURN EVAL_DEF: REM PRINT "def!" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A1%: REM push A1% - A=A2%:GOSUB EVAL: REM eval a2 - A1%=S%(X):X=X-1: REM pop A1% + X=X+1:X%(X)=A1: REM push A1 + A=A2:GOSUB EVAL: REM eval a2 + A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1%:V=R:GOSUB ENV_SET + K=A1:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A2%: REM push/save A2% + X=X+1:X%(X)=A2: REM push/save A2 REM create new environment with outer as current environment O=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:S%(X)=A1%: REM push A1% + X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1%,1)+1:GOSUB EVAL - A1%=S%(X):X=X-1: REM pop A1% + A=Z%(A1,1)+1:GOSUB EVAL + A1=X%(X):X=X-1: REM pop A1 - REM set environment: even A1% key to odd A1% eval'd above - K=A1%+1:V=R:GOSUB ENV_SET + REM set environment: even A1 key to odd A1 eval'd above + K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership - REM skip to the next pair of A1% elements - A1%=Z%(Z%(A1%,1),1) + REM skip to the next pair of A1 elements + A1=Z%(Z%(A1,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - A2%=S%(X):X=X-1: REM pop A2% - A=A2%:GOSUB EVAL: REM eval a2 using let_env + A2=X%(X):X=X-1: REM pop A2 + A=A2:GOSUB EVAL: REM eval A2 using let_env GOTO EVAL_RETURN EVAL_INVOKE: GOSUB EVAL_AST @@ -207,7 +207,7 @@ EVAL: AR=Z%(R,1): REM rest R=F:GOSUB DEREF_R:F=R - IF (Z%(F,0)AND15)<>9 THEN ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN + IF (Z%(F,0)AND31)<>9 THEN ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION AY=R3:GOSUB RELEASE GOTO EVAL_RETURN @@ -217,16 +217,16 @@ EVAL: REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE + IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level REM trigger GC - TA%=FRE(0) + TA=FRE(0) REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 RETURN @@ -273,14 +273,14 @@ MAL_PRINT: RETURN REM REP(A$) -> R$ -REM Assume RE% has repl_env +REM Assume D has repl_env REP: R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -301,9 +301,9 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:RE%=R + O=-1:GOSUB ENV_NEW:D=R - E=RE% + E=D REM + function A=1:GOSUB NATIVE_FUNCTION K$="+":V=R:GOSUB ENV_SET_S @@ -320,7 +320,7 @@ MAIN: A=4:GOSUB NATIVE_FUNCTION K$="/":V=R:GOSUB ENV_SET_S - ZT%=ZI: REM top of memory after base repl_env + ZT=ZI: REM top of memory after base repl_env REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser @@ -333,7 +333,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + REM P1=ZT: P2=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index 1c8bd3736f..0421f8ad28 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -19,19 +19,19 @@ REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T=Z%(A,0)AND15 + T=Z%(A,0)AND31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -45,28 +45,28 @@ EVAL_AST: REM make space on the stack X=X+4 REM push type of sequence - S%(X-3)=T + X%(X-3)=T REM push sequence index - S%(X-2)=-1 + X%(X-2)=-1 REM push future return value (new sequence) - S%(X-1)=R + X%(X-1)=R REM push previous new sequence entry - S%(X)=R + X%(X)=R EVAL_AST_SEQ_LOOP: REM update index - S%(X-2)=S%(X-2)+1 + X%(X-2)=X%(X-2)+1 REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value + Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: @@ -78,18 +78,18 @@ EVAL_AST: EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(S%(X)+1,1)=R + Z%(X%(X)+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=S%(X-3):L=0:N=0:GOSUB ALLOC + T=X%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(S%(X),1)=R + Z%(X%(X),1)=R REM update previous ptr to current entry - S%(X)=R + X%(X)=R REM process the next sequence entry from source list A=Z%(A,1) @@ -97,9 +97,9 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER=-2 THEN R=S%(X-1) + IF ER=-2 THEN R=X%(X-1) REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE REM pop previous, return, index and type X=X-4 @@ -107,11 +107,11 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 REM pop EVAL AST return label/address - RN%=S%(X):X=X-1 - ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 + RN=X%(X):X=X-1 + ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN REM EVAL(A, E)) -> R @@ -119,7 +119,7 @@ EVAL: LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A EVAL_TCO_RECUR: @@ -132,7 +132,7 @@ EVAL: IF R THEN GOTO APPLY_LIST REM ELSE REM push EVAL_AST return label/address - X=X+1:S%(X)=1 + X=X+1:X%(X)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -140,14 +140,14 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0%=A+1 - R=A0%:GOSUB DEREF_R:A0%=R + A0=A+1 + R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) + IF (Z%(A0,0)AND31)<>5 THEN A$="" + IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -157,100 +157,100 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A,1),1),1)+1 - R=A3%:GOSUB DEREF_R:A3%=R + A3=Z%(Z%(Z%(A,1),1),1)+1 + R=A3:GOSUB DEREF_R:A3=R EVAL_GET_A2: - A2%=Z%(Z%(A,1),1)+1 - R=A2%:GOSUB DEREF_R:A2%=R + A2=Z%(Z%(A,1),1)+1 + R=A2:GOSUB DEREF_R:A2=R EVAL_GET_A1: - A1%=Z%(A,1)+1 - R=A1%:GOSUB DEREF_R:A1%=R + A1=Z%(A,1)+1 + R=A1:GOSUB DEREF_R:A1=R RETURN EVAL_DEF: REM PRINT "def!" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A1%: REM push A1% - A=A2%:GOSUB EVAL: REM eval a2 - A1%=S%(X):X=X-1: REM pop A1% + X=X+1:X%(X)=A1: REM push A1 + A=A2:GOSUB EVAL: REM eval a2 + A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1%:V=R:GOSUB ENV_SET + K=A1:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A2%: REM push/save A2% + X=X+1:X%(X)=A2: REM push/save A2 REM create new environment with outer as current environment O=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:S%(X)=A1%: REM push A1% + X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1%,1)+1:GOSUB EVAL - A1%=S%(X):X=X-1: REM pop A1% + A=Z%(A1,1)+1:GOSUB EVAL + A1=X%(X):X=X-1: REM pop A1 - REM set environment: even A1% key to odd A1% eval'd above - K=A1%+1:V=R:GOSUB ENV_SET + REM set environment: even A1 key to odd A1 eval'd above + K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership - REM skip to the next pair of A1% elements - A1%=Z%(Z%(A1%,1),1) + REM skip to the next pair of A1 elements + A1=Z%(Z%(A1,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - A2%=S%(X):X=X-1: REM pop A2% - A=A2%:GOSUB EVAL: REM eval a2 using let_env + A2=X%(X):X=X-1: REM pop A2 + A=A2:GOSUB EVAL: REM eval A2 using let_env GOTO EVAL_RETURN EVAL_DO: A=Z%(A,1): REM rest REM push EVAL_AST return label/address - X=X+1:S%(X)=2 + X=X+1:X%(X)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - X=X+1:S%(X)=R: REM push eval'd list + X=X+1:X%(X)=R: REM push eval'd list A=R:GOSUB LAST: REM return the last element - AY=S%(X):X=X-1: REM pop eval'd list + AY=X%(X):X=X-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_IF: - GOSUB EVAL_GET_A1: REM set a1% + GOSUB EVAL_GET_A1: REM set A1 REM push A - X=X+1:S%(X)=A - A=A1%:GOSUB EVAL + X=X+1:X%(X)=A + A=A1:GOSUB EVAL REM pop A - A=S%(X):X=X-1 + A=X%(X):X=X-1 IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: AY=R:GOSUB RELEASE - GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + GOSUB EVAL_GET_A2: REM set A1 and A2 after EVAL + A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: AY=R:GOSUB RELEASE - REM if no false case (A3%), return nil + REM if no false case (A3), return nil IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN - GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL + A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: - GOSUB EVAL_GET_A2: REM set a1% and a2% - A=A2%:P=A1%:GOSUB MAL_FUNCTION + GOSUB EVAL_GET_A2: REM set A1 and A2 + A=A2:P=A1:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - X=X+1:S%(X)=3 + X=X+1:X%(X)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: @@ -258,46 +258,49 @@ EVAL: IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - X=X+1:S%(X)=R + X=X+1:X%(X)=R F=R+1 AR=Z%(R,1): REM rest R=F:GOSUB DEREF_R:F=R - IF (Z%(F,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + REM if metadata, get the actual object + IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + + IF (Z%(F,0)AND31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R=S%(X):X=X-1 + R=X%(X):X=X-1 ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4%=E: REM save the current environment for release + E4=E: REM save the current environment for release REM create new environ using env stored with function - O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the - REM stack (S%(X-2)) because our new env refers to it and + REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE + IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE REM claim the AST before releasing the list containing it - A=Z%(F,1):Z%(A,0)=Z%(A,0)+16 + A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) - ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV+1 + Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV+1 REM pop and release f/args - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE REM A set above E=R:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -307,7 +310,7 @@ EVAL: REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE + IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level @@ -315,10 +318,10 @@ EVAL: GOSUB RELEASE_PEND REM trigger GC - TA%=FRE(0) + TA=FRE(0) REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 RETURN @@ -328,7 +331,7 @@ MAL_PRINT: RETURN REM RE(A$) -> R -REM Assume RE% has repl_env +REM Assume D has repl_env REM caller must release result RE: R1=0 @@ -336,7 +339,7 @@ RE: R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ @@ -344,14 +347,14 @@ RE: RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ -REM Assume RE% has repl_env +REM Assume D has repl_env REP: R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -372,12 +375,12 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:RE%=R + O=-1:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic - E=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env - ZT%=ZI: REM top of memory after base repl_env + ZT=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself A$="(def! not (fn* (a) (if a false true)))" @@ -394,7 +397,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + REM P1=ZT: P2=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index d28fbd8840..8ddce071de 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -19,19 +19,19 @@ REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T=Z%(A,0)AND15 + T=Z%(A,0)AND31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -45,28 +45,28 @@ EVAL_AST: REM make space on the stack X=X+4 REM push type of sequence - S%(X-3)=T + X%(X-3)=T REM push sequence index - S%(X-2)=-1 + X%(X-2)=-1 REM push future return value (new sequence) - S%(X-1)=R + X%(X-1)=R REM push previous new sequence entry - S%(X)=R + X%(X)=R EVAL_AST_SEQ_LOOP: REM update index - S%(X-2)=S%(X-2)+1 + X%(X-2)=X%(X-2)+1 REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value + Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: @@ -78,18 +78,18 @@ EVAL_AST: EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(S%(X)+1,1)=R + Z%(X%(X)+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=S%(X-3):L=0:N=0:GOSUB ALLOC + T=X%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(S%(X),1)=R + Z%(X%(X),1)=R REM update previous ptr to current entry - S%(X)=R + X%(X)=R REM process the next sequence entry from source list A=Z%(A,1) @@ -97,9 +97,9 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER=-2 THEN R=S%(X-1) + IF ER=-2 THEN R=X%(X-1) REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE REM pop previous, return, index and type X=X-4 @@ -107,11 +107,11 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 REM pop EVAL AST return label/address - RN%=S%(X):X=X-1 - ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 + RN=X%(X):X=X-1 + ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN REM EVAL(A, E)) -> R @@ -119,7 +119,7 @@ EVAL: LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A EVAL_TCO_RECUR: @@ -132,7 +132,7 @@ EVAL: IF R THEN GOTO APPLY_LIST REM ELSE REM push EVAL_AST return label/address - X=X+1:S%(X)=1 + X=X+1:X%(X)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -140,14 +140,14 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0%=A+1 - R=A0%:GOSUB DEREF_R:A0%=R + A0=A+1 + R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) + IF (Z%(A0,0)AND31)<>5 THEN A$="" + IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -157,64 +157,64 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A,1),1),1)+1 - R=A3%:GOSUB DEREF_R:A3%=R + A3=Z%(Z%(Z%(A,1),1),1)+1 + R=A3:GOSUB DEREF_R:A3=R EVAL_GET_A2: - A2%=Z%(Z%(A,1),1)+1 - R=A2%:GOSUB DEREF_R:A2%=R + A2=Z%(Z%(A,1),1)+1 + R=A2:GOSUB DEREF_R:A2=R EVAL_GET_A1: - A1%=Z%(A,1)+1 - R=A1%:GOSUB DEREF_R:A1%=R + A1=Z%(A,1)+1 + R=A1:GOSUB DEREF_R:A1=R RETURN EVAL_DEF: REM PRINT "def!" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A1%: REM push A1% - A=A2%:GOSUB EVAL: REM eval a2 - A1%=S%(X):X=X-1: REM pop A1% + X=X+1:X%(X)=A1: REM push A1 + A=A2:GOSUB EVAL: REM eval a2 + A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1%:V=R:GOSUB ENV_SET + K=A1:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A2%: REM push/save A2% - X=X+1:S%(X)=E: REM push env for for later release + X=X+1:X%(X)=A2: REM push/save A2 + X=X+1:X%(X)=E: REM push env for for later release REM create new environment with outer as current environment O=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:S%(X)=A1%: REM push A1% + X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1%,1)+1:GOSUB EVAL - A1%=S%(X):X=X-1: REM pop A1% + A=Z%(A1,1)+1:GOSUB EVAL + A1=X%(X):X=X-1: REM pop A1 - REM set environment: even A1% key to odd A1% eval'd above - K=A1%+1:V=R:GOSUB ENV_SET + REM set environment: even A1 key to odd A1 eval'd above + K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership - REM skip to the next pair of A1% elements - A1%=Z%(Z%(A1%,1),1) + REM skip to the next pair of A1 elements + A1=Z%(Z%(A1,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4%=S%(X):X=X-1: REM pop previous env + E4=X%(X):X=X-1: REM pop previous env REM release previous environment if not the current EVAL env - IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE + IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE - A2%=S%(X):X=X-1: REM pop A2% - A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A2=X%(X):X=X-1: REM pop A2 + A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: A=Z%(A,1): REM rest @@ -222,44 +222,44 @@ EVAL: REM TODO: TCO REM push EVAL_AST return label/address - X=X+1:S%(X)=2 + X=X+1:X%(X)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - X=X+1:S%(X)=R: REM push eval'd list + X=X+1:X%(X)=R: REM push eval'd list A=R:GOSUB LAST: REM return the last element - AY=S%(X):X=X-1: REM pop eval'd list + AY=X%(X):X=X-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_IF: - GOSUB EVAL_GET_A1: REM set a1% + GOSUB EVAL_GET_A1: REM set A1 REM push A - X=X+1:S%(X)=A - A=A1%:GOSUB EVAL + X=X+1:X%(X)=A + A=A1:GOSUB EVAL REM pop A - A=S%(X):X=X-1 + A=X%(X):X=X-1 IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: AY=R:GOSUB RELEASE - GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + GOSUB EVAL_GET_A2: REM set A1 and A2 after EVAL + A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: AY=R:GOSUB RELEASE - REM if no false case (A3%), return nil + REM if no false case (A3), return nil IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN - GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL + A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: - GOSUB EVAL_GET_A2: REM set a1% and a2% - A=A2%:P=A1%:GOSUB MAL_FUNCTION + GOSUB EVAL_GET_A2: REM set A1 and A2 + A=A2:P=A1:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - X=X+1:S%(X)=3 + X=X+1:X%(X)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: @@ -267,46 +267,49 @@ EVAL: IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - X=X+1:S%(X)=R + X=X+1:X%(X)=R F=R+1 AR=Z%(R,1): REM rest R=F:GOSUB DEREF_R:F=R - IF (Z%(F,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + REM if metadata, get the actual object + IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + + IF (Z%(F,0)AND31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R=S%(X):X=X-1 + R=X%(X):X=X-1 ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4%=E: REM save the current environment for release + E4=E: REM save the current environment for release REM create new environ using env stored with function - O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the - REM stack (S%(X-2)) because our new env refers to it and + REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE + IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE REM claim the AST before releasing the list containing it - A=Z%(F,1):Z%(A,0)=Z%(A,0)+16 + A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) - ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV+1 + Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV+1 REM pop and release f/args - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE REM A set above E=R:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -316,7 +319,7 @@ EVAL: REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE + IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level @@ -324,10 +327,10 @@ EVAL: GOSUB RELEASE_PEND REM trigger GC - TA%=FRE(0) + TA=FRE(0) REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 RETURN @@ -337,7 +340,7 @@ MAL_PRINT: RETURN REM RE(A$) -> R -REM Assume RE% has repl_env +REM Assume D has repl_env REM caller must release result RE: R1=0 @@ -345,7 +348,7 @@ RE: R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ @@ -353,14 +356,14 @@ RE: RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ -REM Assume RE% has repl_env +REM Assume D has repl_env REP: R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -381,12 +384,12 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:RE%=R + O=-1:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic - E=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env - ZT%=ZI: REM top of memory after base repl_env + ZT=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself A$="(def! not (fn* (a) (if a false true)))" @@ -403,7 +406,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + REM P1=ZT: P2=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index 4241df47e5..aed3b6058e 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -19,19 +19,19 @@ REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T=Z%(A,0)AND15 + T=Z%(A,0)AND31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -45,28 +45,28 @@ EVAL_AST: REM make space on the stack X=X+4 REM push type of sequence - S%(X-3)=T + X%(X-3)=T REM push sequence index - S%(X-2)=-1 + X%(X-2)=-1 REM push future return value (new sequence) - S%(X-1)=R + X%(X-1)=R REM push previous new sequence entry - S%(X)=R + X%(X)=R EVAL_AST_SEQ_LOOP: REM update index - S%(X-2)=S%(X-2)+1 + X%(X-2)=X%(X-2)+1 REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value + Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: @@ -78,18 +78,18 @@ EVAL_AST: EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(S%(X)+1,1)=R + Z%(X%(X)+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=S%(X-3):L=0:N=0:GOSUB ALLOC + T=X%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(S%(X),1)=R + Z%(X%(X),1)=R REM update previous ptr to current entry - S%(X)=R + X%(X)=R REM process the next sequence entry from source list A=Z%(A,1) @@ -97,9 +97,9 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER=-2 THEN R=S%(X-1) + IF ER=-2 THEN R=X%(X-1) REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE REM pop previous, return, index and type X=X-4 @@ -107,11 +107,11 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 REM pop EVAL AST return label/address - RN%=S%(X):X=X-1 - ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 + RN=X%(X):X=X-1 + ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN REM EVAL(A, E)) -> R @@ -119,7 +119,7 @@ EVAL: LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A EVAL_TCO_RECUR: @@ -132,7 +132,7 @@ EVAL: IF R THEN GOTO APPLY_LIST REM ELSE REM push EVAL_AST return label/address - X=X+1:S%(X)=1 + X=X+1:X%(X)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -140,14 +140,14 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0%=A+1 - R=A0%:GOSUB DEREF_R:A0%=R + A0=A+1 + R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) + IF (Z%(A0,0)AND31)<>5 THEN A$="" + IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -157,64 +157,64 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A,1),1),1)+1 - R=A3%:GOSUB DEREF_R:A3%=R + A3=Z%(Z%(Z%(A,1),1),1)+1 + R=A3:GOSUB DEREF_R:A3=R EVAL_GET_A2: - A2%=Z%(Z%(A,1),1)+1 - R=A2%:GOSUB DEREF_R:A2%=R + A2=Z%(Z%(A,1),1)+1 + R=A2:GOSUB DEREF_R:A2=R EVAL_GET_A1: - A1%=Z%(A,1)+1 - R=A1%:GOSUB DEREF_R:A1%=R + A1=Z%(A,1)+1 + R=A1:GOSUB DEREF_R:A1=R RETURN EVAL_DEF: REM PRINT "def!" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A1%: REM push A1% - A=A2%:GOSUB EVAL: REM eval a2 - A1%=S%(X):X=X-1: REM pop A1% + X=X+1:X%(X)=A1: REM push A1 + A=A2:GOSUB EVAL: REM eval a2 + A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1%:V=R:GOSUB ENV_SET + K=A1:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A2%: REM push/save A2% - X=X+1:S%(X)=E: REM push env for for later release + X=X+1:X%(X)=A2: REM push/save A2 + X=X+1:X%(X)=E: REM push env for for later release REM create new environment with outer as current environment O=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:S%(X)=A1%: REM push A1% + X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1%,1)+1:GOSUB EVAL - A1%=S%(X):X=X-1: REM pop A1% + A=Z%(A1,1)+1:GOSUB EVAL + A1=X%(X):X=X-1: REM pop A1 - REM set environment: even A1% key to odd A1% eval'd above - K=A1%+1:V=R:GOSUB ENV_SET + REM set environment: even A1 key to odd A1 eval'd above + K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership - REM skip to the next pair of A1% elements - A1%=Z%(Z%(A1%,1),1) + REM skip to the next pair of A1 elements + A1=Z%(Z%(A1,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4%=S%(X):X=X-1: REM pop previous env + E4=X%(X):X=X-1: REM pop previous env REM release previous environment if not the current EVAL env - IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE + IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE - A2%=S%(X):X=X-1: REM pop A2% - A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A2=X%(X):X=X-1: REM pop A2 + A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: A=Z%(A,1): REM rest @@ -222,44 +222,44 @@ EVAL: REM TODO: TCO REM push EVAL_AST return label/address - X=X+1:S%(X)=2 + X=X+1:X%(X)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - X=X+1:S%(X)=R: REM push eval'd list + X=X+1:X%(X)=R: REM push eval'd list A=R:GOSUB LAST: REM return the last element - AY=S%(X):X=X-1: REM pop eval'd list + AY=X%(X):X=X-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_IF: - GOSUB EVAL_GET_A1: REM set a1% + GOSUB EVAL_GET_A1: REM set A1 REM push A - X=X+1:S%(X)=A - A=A1%:GOSUB EVAL + X=X+1:X%(X)=A + A=A1:GOSUB EVAL REM pop A - A=S%(X):X=X-1 + A=X%(X):X=X-1 IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: AY=R:GOSUB RELEASE - GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + GOSUB EVAL_GET_A2: REM set A1 and A2 after EVAL + A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: AY=R:GOSUB RELEASE - REM if no false case (A3%), return nil + REM if no false case (A3), return nil IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN - GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL + A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: - GOSUB EVAL_GET_A2: REM set a1% and a2% - A=A2%:P=A1%:GOSUB MAL_FUNCTION + GOSUB EVAL_GET_A2: REM set A1 and A2 + A=A2:P=A1:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - X=X+1:S%(X)=3 + X=X+1:X%(X)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: @@ -267,46 +267,49 @@ EVAL: IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - X=X+1:S%(X)=R + X=X+1:X%(X)=R F=R+1 AR=Z%(R,1): REM rest R=F:GOSUB DEREF_R:F=R - IF (Z%(F,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + REM if metadata, get the actual object + IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + + IF (Z%(F,0)AND31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R=S%(X):X=X-1 + R=X%(X):X=X-1 ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4%=E: REM save the current environment for release + E4=E: REM save the current environment for release REM create new environ using env stored with function - O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the - REM stack (S%(X-2)) because our new env refers to it and + REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE + IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE REM claim the AST before releasing the list containing it - A=Z%(F,1):Z%(A,0)=Z%(A,0)+16 + A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) - ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV+1 + Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV+1 REM pop and release f/args - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE REM A set above E=R:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -316,7 +319,7 @@ EVAL: REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE + IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level @@ -324,10 +327,10 @@ EVAL: GOSUB RELEASE_PEND REM trigger GC - TA%=FRE(0) + TA=FRE(0) REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 RETURN @@ -337,7 +340,7 @@ MAL_PRINT: RETURN REM RE(A$) -> R -REM Assume RE% has repl_env +REM Assume D has repl_env REM caller must release result RE: R1=0 @@ -345,7 +348,7 @@ RE: R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ @@ -353,14 +356,14 @@ RE: RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ -REM Assume RE% has repl_env +REM Assume D has repl_env REP: R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -381,12 +384,12 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:RE%=R + O=-1:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic - E=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env - ZT%=ZI: REM top of memory after base repl_env + ZT=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself A$="(def! not (fn* (a) (if a false true)))" @@ -431,7 +434,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + REM P1=ZT: P2=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index b6f4548de5..7bf085bd54 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -1,5 +1,3 @@ -REM POKE 1, PEEK(1)AND248: REM enable all ROM areas as RAM -REM POKE 55,0: POKE 56,192: CLR: REM move BASIC end from $A000 to $C000 GOTO MAIN REM $INCLUDE: 'readline.in.bas' @@ -19,7 +17,7 @@ MAL_READ: REM PAIR_Q(B) -> R PAIR_Q: R=0 - IF (Z%(B,0)AND15)<>6 AND (Z%(B,0)AND15)<>7 THEN RETURN + IF (Z%(B,0)AND31)<>6 AND (Z%(B,0)AND31)<>7 THEN RETURN IF (Z%(B,1)=0) THEN RETURN R=1 RETURN @@ -30,28 +28,28 @@ QUASIQUOTE: IF R=1 THEN GOTO QQ_UNQUOTE REM ['quote, ast] AS$="quote":T=5:GOSUB STRING - B2%=R:B1%=A:GOSUB LIST2 - AY=B2%:GOSUB RELEASE + B2=R:B1=A:GOSUB LIST2 + AY=B2:GOSUB RELEASE RETURN QQ_UNQUOTE: R=A+1:GOSUB DEREF_R - IF (Z%(R,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF (Z%(R,0)AND31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] R=Z%(A,1)+1:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 RETURN QQ_SPLICE_UNQUOTE: REM push A on the stack - X=X+1:S%(X)=A + X=X+1:X%(X)=A REM rest of cases call quasiquote on ast[1..] A=Z%(A,1):GOSUB QUASIQUOTE:T6=R REM pop A off the stack - A=S%(X):X=X-1 + A=X%(X):X=X-1 REM set A to ast[0] for last two cases A=A+1:GOSUB DEREF_A @@ -59,34 +57,34 @@ QUASIQUOTE: B=A:GOSUB PAIR_Q IF R=0 THEN GOTO QQ_DEFAULT B=A+1:GOSUB DEREF_B - IF (Z%(B,0)AND15)<>5 THEN GOTO QQ_DEFAULT + IF (Z%(B,0)AND31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1)+1:GOSUB DEREF_B:B2%=B - AS$="concat":T=5:GOSUB STRING:B3%=R - B1%=T6:GOSUB LIST3 + B=Z%(A,1)+1:GOSUB DEREF_B:B2=B + AS$="concat":T=5:GOSUB STRING:B3=R + B1=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1%:GOSUB RELEASE - AY=B3%:GOSUB RELEASE + AY=B1:GOSUB RELEASE + AY=B3:GOSUB RELEASE RETURN QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] REM push T6 on the stack - X=X+1:S%(X)=T6 + X=X+1:X%(X)=T6 REM A set above to ast[0] - GOSUB QUASIQUOTE:B2%=R + GOSUB QUASIQUOTE:B2=R REM pop T6 off the stack - T6=S%(X):X=X-1 + T6=X%(X):X=X-1 - AS$="cons":T=5:GOSUB STRING:B3%=R - B1%=T6:GOSUB LIST3 + AS$="cons":T=5:GOSUB STRING:B3=R + B1=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1%:GOSUB RELEASE - AY=B2%:GOSUB RELEASE - AY=B3%:GOSUB RELEASE + AY=B1:GOSUB RELEASE + AY=B2:GOSUB RELEASE + AY=B3:GOSUB RELEASE RETURN @@ -95,19 +93,19 @@ REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T=Z%(A,0)AND15 + T=Z%(A,0)AND31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -121,28 +119,28 @@ EVAL_AST: REM make space on the stack X=X+4 REM push type of sequence - S%(X-3)=T + X%(X-3)=T REM push sequence index - S%(X-2)=-1 + X%(X-2)=-1 REM push future return value (new sequence) - S%(X-1)=R + X%(X-1)=R REM push previous new sequence entry - S%(X)=R + X%(X)=R EVAL_AST_SEQ_LOOP: REM update index - S%(X-2)=S%(X-2)+1 + X%(X-2)=X%(X-2)+1 REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value + Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: @@ -154,18 +152,18 @@ EVAL_AST: EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(S%(X)+1,1)=R + Z%(X%(X)+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=S%(X-3):L=0:N=0:GOSUB ALLOC + T=X%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(S%(X),1)=R + Z%(X%(X),1)=R REM update previous ptr to current entry - S%(X)=R + X%(X)=R REM process the next sequence entry from source list A=Z%(A,1) @@ -173,9 +171,9 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER=-2 THEN R=S%(X-1) + IF ER=-2 THEN R=X%(X-1) REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE REM pop previous, return, index and type X=X-4 @@ -183,11 +181,11 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 REM pop EVAL AST return label/address - RN%=S%(X):X=X-1 - ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 + RN=X%(X):X=X-1 + ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN REM EVAL(A, E)) -> R @@ -195,7 +193,7 @@ EVAL: LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A EVAL_TCO_RECUR: @@ -208,7 +206,7 @@ EVAL: IF R THEN GOTO APPLY_LIST REM ELSE REM push EVAL_AST return label/address - X=X+1:S%(X)=1 + X=X+1:X%(X)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -216,14 +214,14 @@ EVAL: APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0%=A+1 - R=A0%:GOSUB DEREF_R:A0%=R + A0=A+1 + R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) + IF (Z%(A0,0)AND31)<>5 THEN A$="" + IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -235,64 +233,64 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A,1),1),1)+1 - R=A3%:GOSUB DEREF_R:A3%=R + A3=Z%(Z%(Z%(A,1),1),1)+1 + R=A3:GOSUB DEREF_R:A3=R EVAL_GET_A2: - A2%=Z%(Z%(A,1),1)+1 - R=A2%:GOSUB DEREF_R:A2%=R + A2=Z%(Z%(A,1),1)+1 + R=A2:GOSUB DEREF_R:A2=R EVAL_GET_A1: - A1%=Z%(A,1)+1 - R=A1%:GOSUB DEREF_R:A1%=R + A1=Z%(A,1)+1 + R=A1:GOSUB DEREF_R:A1=R RETURN EVAL_DEF: REM PRINT "def!" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A1%: REM push A1% - A=A2%:GOSUB EVAL: REM eval a2 - A1%=S%(X):X=X-1: REM pop A1% + X=X+1:X%(X)=A1: REM push A1 + A=A2:GOSUB EVAL: REM eval a2 + A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1%:V=R:GOSUB ENV_SET + K=A1:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A2%: REM push/save A2% - X=X+1:S%(X)=E: REM push env for for later release + X=X+1:X%(X)=A2: REM push/save A2 + X=X+1:X%(X)=E: REM push env for for later release REM create new environment with outer as current environment O=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:S%(X)=A1%: REM push A1% + X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1%,1)+1:GOSUB EVAL - A1%=S%(X):X=X-1: REM pop A1% + A=Z%(A1,1)+1:GOSUB EVAL + A1=X%(X):X=X-1: REM pop A1 - REM set environment: even A1% key to odd A1% eval'd above - K=A1%+1:V=R:GOSUB ENV_SET + REM set environment: even A1 key to odd A1 eval'd above + K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership - REM skip to the next pair of A1% elements - A1%=Z%(Z%(A1%,1),1) + REM skip to the next pair of A1 elements + A1=Z%(Z%(A1,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4%=S%(X):X=X-1: REM pop previous env + E4=X%(X):X=X-1: REM pop previous env REM release previous environment if not the current EVAL env - IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE + IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE - A2%=S%(X):X=X-1: REM pop A2% - A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A2=X%(X):X=X-1: REM pop A2 + A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: A=Z%(A,1): REM rest @@ -300,19 +298,19 @@ EVAL: REM TODO: TCO REM push EVAL_AST return label/address - X=X+1:S%(X)=2 + X=X+1:X%(X)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - X=X+1:S%(X)=R: REM push eval'd list + X=X+1:X%(X)=R: REM push eval'd list A=R:GOSUB LAST: REM return the last element - AY=S%(X):X=X-1: REM pop eval'd list + AY=X%(X):X=X-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_QUOTE: R=Z%(A,1)+1:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_RETURN EVAL_QUASIQUOTE: @@ -320,38 +318,38 @@ EVAL: A=R:GOSUB QUASIQUOTE REM add quasiquote result to pending release queue to free when REM next lower EVAL level returns (LV) - ZM%=ZM%+1:ZR%(ZM%,0)=R:ZR%(ZM%,1)=LV + Y=Y+1:Y%(Y,0)=R:Y%(Y,1)=LV A=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF: - GOSUB EVAL_GET_A1: REM set a1% + GOSUB EVAL_GET_A1: REM set A1 REM push A - X=X+1:S%(X)=A - A=A1%:GOSUB EVAL + X=X+1:X%(X)=A + A=A1:GOSUB EVAL REM pop A - A=S%(X):X=X-1 + A=X%(X):X=X-1 IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: AY=R:GOSUB RELEASE - GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + GOSUB EVAL_GET_A2: REM set A1 and A2 after EVAL + A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: AY=R:GOSUB RELEASE - REM if no false case (A3%), return nil + REM if no false case (A3), return nil IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN - GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL + A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: - GOSUB EVAL_GET_A2: REM set a1% and a2% - A=A2%:P=A1%:GOSUB MAL_FUNCTION + GOSUB EVAL_GET_A2: REM set A1 and A2 + A=A2:P=A1:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - X=X+1:S%(X)=3 + X=X+1:X%(X)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: @@ -359,46 +357,49 @@ EVAL: IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - X=X+1:S%(X)=R + X=X+1:X%(X)=R F=R+1 AR=Z%(R,1): REM rest R=F:GOSUB DEREF_R:F=R - IF (Z%(F,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + REM if metadata, get the actual object + IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + + IF (Z%(F,0)AND31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R=S%(X):X=X-1 + R=X%(X):X=X-1 ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4%=E: REM save the current environment for release + E4=E: REM save the current environment for release REM create new environ using env stored with function - O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the - REM stack (S%(X-2)) because our new env refers to it and + REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE + IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE REM claim the AST before releasing the list containing it - A=Z%(F,1):Z%(A,0)=Z%(A,0)+16 + A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) - ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV+1 + Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV+1 REM pop and release f/args - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE REM A set above E=R:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -408,7 +409,7 @@ EVAL: REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE + IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level @@ -416,10 +417,10 @@ EVAL: GOSUB RELEASE_PEND REM trigger GC - TA%=FRE(0) + TA=FRE(0) REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 RETURN @@ -429,7 +430,7 @@ MAL_PRINT: RETURN REM RE(A$) -> R -REM Assume RE% has repl_env +REM Assume D has repl_env REM caller must release result RE: R1=0 @@ -437,7 +438,7 @@ RE: R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ @@ -445,14 +446,14 @@ RE: RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ -REM Assume RE% has repl_env +REM Assume D has repl_env REP: R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -473,12 +474,12 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:RE%=R + O=-1:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic - E=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env - ZT%=ZI: REM top of memory after base repl_env + ZT=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself A$="(def! not (fn* (a) (if a false true)))" @@ -523,7 +524,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + REM P1=ZT: P2=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index 871d9490d2..86741154ac 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -19,7 +19,7 @@ MAL_READ: REM PAIR_Q(B) -> R PAIR_Q: R=0 - IF (Z%(B,0)AND15)<>6 AND (Z%(B,0)AND15)<>7 THEN RETURN + IF (Z%(B,0)AND31)<>6 AND (Z%(B,0)AND31)<>7 THEN RETURN IF (Z%(B,1)=0) THEN RETURN R=1 RETURN @@ -30,28 +30,28 @@ QUASIQUOTE: IF R=1 THEN GOTO QQ_UNQUOTE REM ['quote, ast] AS$="quote":T=5:GOSUB STRING - B2%=R:B1%=A:GOSUB LIST2 - AY=B2%:GOSUB RELEASE + B2=R:B1=A:GOSUB LIST2 + AY=B2:GOSUB RELEASE RETURN QQ_UNQUOTE: R=A+1:GOSUB DEREF_R - IF (Z%(R,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF (Z%(R,0)AND31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] R=Z%(A,1)+1:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 RETURN QQ_SPLICE_UNQUOTE: REM push A on the stack - X=X+1:S%(X)=A + X=X+1:X%(X)=A REM rest of cases call quasiquote on ast[1..] A=Z%(A,1):GOSUB QUASIQUOTE:T6=R REM pop A off the stack - A=S%(X):X=X-1 + A=X%(X):X=X-1 REM set A to ast[0] for last two cases A=A+1:GOSUB DEREF_A @@ -59,64 +59,64 @@ QUASIQUOTE: B=A:GOSUB PAIR_Q IF R=0 THEN GOTO QQ_DEFAULT B=A+1:GOSUB DEREF_B - IF (Z%(B,0)AND15)<>5 THEN GOTO QQ_DEFAULT + IF (Z%(B,0)AND31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1)+1:GOSUB DEREF_B:B2%=B - AS$="concat":T=5:GOSUB STRING:B3%=R - B1%=T6:GOSUB LIST3 + B=Z%(A,1)+1:GOSUB DEREF_B:B2=B + AS$="concat":T=5:GOSUB STRING:B3=R + B1=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1%:GOSUB RELEASE - AY=B3%:GOSUB RELEASE + AY=B1:GOSUB RELEASE + AY=B3:GOSUB RELEASE RETURN QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] REM push T6 on the stack - X=X+1:S%(X)=T6 + X=X+1:X%(X)=T6 REM A set above to ast[0] - GOSUB QUASIQUOTE:B2%=R + GOSUB QUASIQUOTE:B2=R REM pop T6 off the stack - T6=S%(X):X=X-1 + T6=X%(X):X=X-1 - AS$="cons":T=5:GOSUB STRING:B3%=R - B1%=T6:GOSUB LIST3 + AS$="cons":T=5:GOSUB STRING:B3=R + B1=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1%:GOSUB RELEASE - AY=B2%:GOSUB RELEASE - AY=B3%:GOSUB RELEASE + AY=B1:GOSUB RELEASE + AY=B2:GOSUB RELEASE + AY=B3:GOSUB RELEASE RETURN REM MACROEXPAND(A, E) -> A: MACROEXPAND: REM push original A - X=X+1:S%(X)=A + X=X+1:X%(X)=A MACROEXPAND_LOOP: REM list? - IF (Z%(A,0)AND15)<>6 THEN GOTO MACROEXPAND_DONE + IF (Z%(A,0)AND31)<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? IF Z%(A,1)=0 THEN GOTO MACROEXPAND_DONE B=A+1:GOSUB DEREF_B REM symbol? in first position - IF (Z%(B,0)AND15)<>5 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND31)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? K=B:GOSUB ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE B=T4:GOSUB DEREF_B REM macro? - IF (Z%(B,0)AND15)<>11 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND31)<>11 THEN GOTO MACROEXPAND_DONE REM apply F=B:AR=Z%(A,1):GOSUB APPLY A=R - AY=S%(X) + AY=X%(X) REM if previous A was not the first A into macroexpand (i.e. an REM intermediate form) then free it - IF A<>AY THEN ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV + IF A<>AY THEN Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV IF ER<>-2 THEN GOTO MACROEXPAND_DONE GOTO MACROEXPAND_LOOP @@ -130,19 +130,19 @@ REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T=Z%(A,0)AND15 + T=Z%(A,0)AND31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -156,28 +156,28 @@ EVAL_AST: REM make space on the stack X=X+4 REM push type of sequence - S%(X-3)=T + X%(X-3)=T REM push sequence index - S%(X-2)=-1 + X%(X-2)=-1 REM push future return value (new sequence) - S%(X-1)=R + X%(X-1)=R REM push previous new sequence entry - S%(X)=R + X%(X)=R EVAL_AST_SEQ_LOOP: REM update index - S%(X-2)=S%(X-2)+1 + X%(X-2)=X%(X-2)+1 REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value + Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: @@ -189,18 +189,18 @@ EVAL_AST: EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(S%(X)+1,1)=R + Z%(X%(X)+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=S%(X-3):L=0:N=0:GOSUB ALLOC + T=X%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(S%(X),1)=R + Z%(X%(X),1)=R REM update previous ptr to current entry - S%(X)=R + X%(X)=R REM process the next sequence entry from source list A=Z%(A,1) @@ -208,9 +208,9 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER=-2 THEN R=S%(X-1) + IF ER=-2 THEN R=X%(X-1) REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE REM pop previous, return, index and type X=X-4 @@ -218,11 +218,11 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 REM pop EVAL AST return label/address - RN%=S%(X):X=X-1 - ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 + RN=X%(X):X=X-1 + ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN REM EVAL(A, E)) -> R @@ -230,7 +230,7 @@ EVAL: LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A EVAL_TCO_RECUR: @@ -244,7 +244,7 @@ EVAL: EVAL_NOT_LIST: REM ELSE REM push EVAL_AST return label/address - X=X+1:S%(X)=1 + X=X+1:X%(X)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -257,14 +257,14 @@ EVAL: IF R<>1 THEN GOTO EVAL_NOT_LIST GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0%=A+1 - R=A0%:GOSUB DEREF_R:A0%=R + A0=A+1 + R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) + IF (Z%(A0,0)AND31)<>5 THEN A$="" + IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -278,64 +278,64 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A,1),1),1)+1 - R=A3%:GOSUB DEREF_R:A3%=R + A3=Z%(Z%(Z%(A,1),1),1)+1 + R=A3:GOSUB DEREF_R:A3=R EVAL_GET_A2: - A2%=Z%(Z%(A,1),1)+1 - R=A2%:GOSUB DEREF_R:A2%=R + A2=Z%(Z%(A,1),1)+1 + R=A2:GOSUB DEREF_R:A2=R EVAL_GET_A1: - A1%=Z%(A,1)+1 - R=A1%:GOSUB DEREF_R:A1%=R + A1=Z%(A,1)+1 + R=A1:GOSUB DEREF_R:A1=R RETURN EVAL_DEF: REM PRINT "def!" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A1%: REM push A1% - A=A2%:GOSUB EVAL: REM eval a2 - A1%=S%(X):X=X-1: REM pop A1% + X=X+1:X%(X)=A1: REM push A1 + A=A2:GOSUB EVAL: REM eval a2 + A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1%:V=R:GOSUB ENV_SET + K=A1:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A2%: REM push/save A2% - X=X+1:S%(X)=E: REM push env for for later release + X=X+1:X%(X)=A2: REM push/save A2 + X=X+1:X%(X)=E: REM push env for for later release REM create new environment with outer as current environment O=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:S%(X)=A1%: REM push A1% + X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1%,1)+1:GOSUB EVAL - A1%=S%(X):X=X-1: REM pop A1% + A=Z%(A1,1)+1:GOSUB EVAL + A1=X%(X):X=X-1: REM pop A1 - REM set environment: even A1% key to odd A1% eval'd above - K=A1%+1:V=R:GOSUB ENV_SET + REM set environment: even A1 key to odd A1 eval'd above + K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership - REM skip to the next pair of A1% elements - A1%=Z%(Z%(A1%,1),1) + REM skip to the next pair of A1 elements + A1=Z%(Z%(A1,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4%=S%(X):X=X-1: REM pop previous env + E4=X%(X):X=X-1: REM pop previous env REM release previous environment if not the current EVAL env - IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE + IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE - A2%=S%(X):X=X-1: REM pop A2% - A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A2=X%(X):X=X-1: REM pop A2 + A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: A=Z%(A,1): REM rest @@ -343,19 +343,19 @@ EVAL: REM TODO: TCO REM push EVAL_AST return label/address - X=X+1:S%(X)=2 + X=X+1:X%(X)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - X=X+1:S%(X)=R: REM push eval'd list + X=X+1:X%(X)=R: REM push eval'd list A=R:GOSUB LAST: REM return the last element - AY=S%(X):X=X-1: REM pop eval'd list + AY=X%(X):X=X-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_QUOTE: R=Z%(A,1)+1:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_RETURN EVAL_QUASIQUOTE: @@ -363,23 +363,23 @@ EVAL: A=R:GOSUB QUASIQUOTE REM add quasiquote result to pending release queue to free when REM next lower EVAL level returns (LV) - ZM%=ZM%+1:ZR%(ZM%,0)=R:ZR%(ZM%,1)=LV + Y=Y+1:Y%(Y,0)=R:Y%(Y,1)=LV A=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DEFMACRO: REM PRINT "defmacro!" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A1%: REM push A1% - A=A2%:GOSUB EVAL: REM eval a2 - A1%=S%(X):X=X-1: REM pop A1% + X=X+1:X%(X)=A1: REM push A1 + A=A2:GOSUB EVAL: REM eval A2 + A1=X%(X):X=X-1: REM pop A1 REM change function to macro Z%(R,0)=Z%(R,0)+1 - REM set a1 in env to a2 - K=A1%:V=R:GOSUB ENV_SET + REM set A1 in env to A2 + K=A1:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_MACROEXPAND: @@ -388,37 +388,37 @@ EVAL: A=R:GOSUB MACROEXPAND:R=A REM since we are returning it unevaluated, inc the ref cnt - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_RETURN EVAL_IF: - GOSUB EVAL_GET_A1: REM set a1% + GOSUB EVAL_GET_A1: REM set A1 REM push A - X=X+1:S%(X)=A - A=A1%:GOSUB EVAL + X=X+1:X%(X)=A + A=A1:GOSUB EVAL REM pop A - A=S%(X):X=X-1 + A=X%(X):X=X-1 IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: AY=R:GOSUB RELEASE - GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + GOSUB EVAL_GET_A2: REM set A1 and A2 after EVAL + A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: AY=R:GOSUB RELEASE - REM if no false case (A3%), return nil + REM if no false case (A3), return nil IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN - GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL + A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: - GOSUB EVAL_GET_A2: REM set a1% and a2% - A=A2%:P=A1%:GOSUB MAL_FUNCTION + GOSUB EVAL_GET_A2: REM set A1 and A2 + A=A2:P=A1:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - X=X+1:S%(X)=3 + X=X+1:X%(X)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: @@ -426,46 +426,49 @@ EVAL: IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - X=X+1:S%(X)=R + X=X+1:X%(X)=R F=R+1 AR=Z%(R,1): REM rest R=F:GOSUB DEREF_R:F=R - IF (Z%(F,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + REM if metadata, get the actual object + IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + + IF (Z%(F,0)AND31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R=S%(X):X=X-1 + R=X%(X):X=X-1 ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4%=E: REM save the current environment for release + E4=E: REM save the current environment for release REM create new environ using env stored with function - O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the - REM stack (S%(X-2)) because our new env refers to it and + REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE + IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE REM claim the AST before releasing the list containing it - A=Z%(F,1):Z%(A,0)=Z%(A,0)+16 + A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) - ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV+1 + Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV+1 REM pop and release f/args - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE REM A set above E=R:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -475,7 +478,7 @@ EVAL: REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE + IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level @@ -483,10 +486,10 @@ EVAL: GOSUB RELEASE_PEND REM trigger GC - TA%=FRE(0) + TA=FRE(0) REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 RETURN @@ -496,7 +499,7 @@ MAL_PRINT: RETURN REM RE(A$) -> R -REM Assume RE% has repl_env +REM Assume D has repl_env REM caller must release result RE: R1=0 @@ -504,7 +507,7 @@ RE: R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ @@ -512,14 +515,14 @@ RE: RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ -REM Assume RE% has repl_env +REM Assume D has repl_env REP: R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -540,12 +543,12 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:RE%=R + O=-1:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic - E=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env - ZT%=ZI: REM top of memory after base repl_env + ZT=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself A$="(def! not (fn* (a) (if a false true)))" @@ -599,7 +602,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + REM P1=ZT: P2=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index 407d62d749..a7c50cc00c 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -19,7 +19,7 @@ MAL_READ: REM PAIR_Q(B) -> R PAIR_Q: R=0 - IF (Z%(B,0)AND15)<>6 AND (Z%(B,0)AND15)<>7 THEN RETURN + IF (Z%(B,0)AND31)<>6 AND (Z%(B,0)AND31)<>7 THEN RETURN IF (Z%(B,1)=0) THEN RETURN R=1 RETURN @@ -30,28 +30,28 @@ QUASIQUOTE: IF R=1 THEN GOTO QQ_UNQUOTE REM ['quote, ast] AS$="quote":T=5:GOSUB STRING - B2%=R:B1%=A:GOSUB LIST2 - AY=B2%:GOSUB RELEASE + B2=R:B1=A:GOSUB LIST2 + AY=B2:GOSUB RELEASE RETURN QQ_UNQUOTE: R=A+1:GOSUB DEREF_R - IF (Z%(R,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF (Z%(R,0)AND31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] R=Z%(A,1)+1:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 RETURN QQ_SPLICE_UNQUOTE: REM push A on the stack - X=X+1:S%(X)=A + X=X+1:X%(X)=A REM rest of cases call quasiquote on ast[1..] A=Z%(A,1):GOSUB QUASIQUOTE:T6=R REM pop A off the stack - A=S%(X):X=X-1 + A=X%(X):X=X-1 REM set A to ast[0] for last two cases A=A+1:GOSUB DEREF_A @@ -59,64 +59,64 @@ QUASIQUOTE: B=A:GOSUB PAIR_Q IF R=0 THEN GOTO QQ_DEFAULT B=A+1:GOSUB DEREF_B - IF (Z%(B,0)AND15)<>5 THEN GOTO QQ_DEFAULT + IF (Z%(B,0)AND31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1)+1:GOSUB DEREF_B:B2%=B - AS$="concat":T=5:GOSUB STRING:B3%=R - B1%=T6:GOSUB LIST3 + B=Z%(A,1)+1:GOSUB DEREF_B:B2=B + AS$="concat":T=5:GOSUB STRING:B3=R + B1=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1%:GOSUB RELEASE - AY=B3%:GOSUB RELEASE + AY=B1:GOSUB RELEASE + AY=B3:GOSUB RELEASE RETURN QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] REM push T6 on the stack - X=X+1:S%(X)=T6 + X=X+1:X%(X)=T6 REM A set above to ast[0] - GOSUB QUASIQUOTE:B2%=R + GOSUB QUASIQUOTE:B2=R REM pop T6 off the stack - T6=S%(X):X=X-1 + T6=X%(X):X=X-1 - AS$="cons":T=5:GOSUB STRING:B3%=R - B1%=T6:GOSUB LIST3 + AS$="cons":T=5:GOSUB STRING:B3=R + B1=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1%:GOSUB RELEASE - AY=B2%:GOSUB RELEASE - AY=B3%:GOSUB RELEASE + AY=B1:GOSUB RELEASE + AY=B2:GOSUB RELEASE + AY=B3:GOSUB RELEASE RETURN REM MACROEXPAND(A, E) -> A: MACROEXPAND: REM push original A - X=X+1:S%(X)=A + X=X+1:X%(X)=A MACROEXPAND_LOOP: REM list? - IF (Z%(A,0)AND15)<>6 THEN GOTO MACROEXPAND_DONE + IF (Z%(A,0)AND31)<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? IF Z%(A,1)=0 THEN GOTO MACROEXPAND_DONE B=A+1:GOSUB DEREF_B REM symbol? in first position - IF (Z%(B,0)AND15)<>5 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND31)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? K=B:GOSUB ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE B=T4:GOSUB DEREF_B REM macro? - IF (Z%(B,0)AND15)<>11 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND31)<>11 THEN GOTO MACROEXPAND_DONE REM apply F=B:AR=Z%(A,1):GOSUB APPLY A=R - AY=S%(X) + AY=X%(X) REM if previous A was not the first A into macroexpand (i.e. an REM intermediate form) then free it - IF A<>AY THEN ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV + IF A<>AY THEN Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV IF ER<>-2 THEN GOTO MACROEXPAND_DONE GOTO MACROEXPAND_LOOP @@ -130,19 +130,19 @@ REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T=Z%(A,0)AND15 + T=Z%(A,0)AND31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -156,28 +156,28 @@ EVAL_AST: REM make space on the stack X=X+4 REM push type of sequence - S%(X-3)=T + X%(X-3)=T REM push sequence index - S%(X-2)=-1 + X%(X-2)=-1 REM push future return value (new sequence) - S%(X-1)=R + X%(X-1)=R REM push previous new sequence entry - S%(X)=R + X%(X)=R EVAL_AST_SEQ_LOOP: REM update index - S%(X-2)=S%(X-2)+1 + X%(X-2)=X%(X-2)+1 REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value + Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: @@ -189,18 +189,18 @@ EVAL_AST: EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(S%(X)+1,1)=R + Z%(X%(X)+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=S%(X-3):L=0:N=0:GOSUB ALLOC + T=X%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(S%(X),1)=R + Z%(X%(X),1)=R REM update previous ptr to current entry - S%(X)=R + X%(X)=R REM process the next sequence entry from source list A=Z%(A,1) @@ -208,9 +208,9 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER=-2 THEN R=S%(X-1) + IF ER=-2 THEN R=X%(X-1) REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE REM pop previous, return, index and type X=X-4 @@ -218,11 +218,11 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 REM pop EVAL AST return label/address - RN%=S%(X):X=X-1 - ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 + RN=X%(X):X=X-1 + ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN REM EVAL(A, E)) -> R @@ -230,7 +230,7 @@ EVAL: LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A EVAL_TCO_RECUR: @@ -244,7 +244,7 @@ EVAL: EVAL_NOT_LIST: REM ELSE REM push EVAL_AST return label/address - X=X+1:S%(X)=1 + X=X+1:X%(X)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -257,14 +257,14 @@ EVAL: IF R<>1 THEN GOTO EVAL_NOT_LIST GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0%=A+1 - R=A0%:GOSUB DEREF_R:A0%=R + A0=A+1 + R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) + IF (Z%(A0,0)AND31)<>5 THEN A$="" + IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -279,64 +279,64 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A,1),1),1)+1 - R=A3%:GOSUB DEREF_R:A3%=R + A3=Z%(Z%(Z%(A,1),1),1)+1 + R=A3:GOSUB DEREF_R:A3=R EVAL_GET_A2: - A2%=Z%(Z%(A,1),1)+1 - R=A2%:GOSUB DEREF_R:A2%=R + A2=Z%(Z%(A,1),1)+1 + R=A2:GOSUB DEREF_R:A2=R EVAL_GET_A1: - A1%=Z%(A,1)+1 - R=A1%:GOSUB DEREF_R:A1%=R + A1=Z%(A,1)+1 + R=A1:GOSUB DEREF_R:A1=R RETURN EVAL_DEF: REM PRINT "def!" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A1%: REM push A1% - A=A2%:GOSUB EVAL: REM eval a2 - A1%=S%(X):X=X-1: REM pop A1% + X=X+1:X%(X)=A1: REM push A1 + A=A2:GOSUB EVAL: REM eval a2 + A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1%:V=R:GOSUB ENV_SET + K=A1:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A2%: REM push/save A2% - X=X+1:S%(X)=E: REM push env for for later release + X=X+1:X%(X)=A2: REM push/save A2 + X=X+1:X%(X)=E: REM push env for for later release REM create new environment with outer as current environment O=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:S%(X)=A1%: REM push A1% + X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1%,1)+1:GOSUB EVAL - A1%=S%(X):X=X-1: REM pop A1% + A=Z%(A1,1)+1:GOSUB EVAL + A1=X%(X):X=X-1: REM pop A1 - REM set environment: even A1% key to odd A1% eval'd above - K=A1%+1:V=R:GOSUB ENV_SET + REM set environment: even A1 key to odd A1 eval'd above + K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership - REM skip to the next pair of A1% elements - A1%=Z%(Z%(A1%,1),1) + REM skip to the next pair of A1 elements + A1=Z%(Z%(A1,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4%=S%(X):X=X-1: REM pop previous env + E4=X%(X):X=X-1: REM pop previous env REM release previous environment if not the current EVAL env - IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE + IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE - A2%=S%(X):X=X-1: REM pop A2% - A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A2=X%(X):X=X-1: REM pop A2 + A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: A=Z%(A,1): REM rest @@ -344,19 +344,19 @@ EVAL: REM TODO: TCO REM push EVAL_AST return label/address - X=X+1:S%(X)=2 + X=X+1:X%(X)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - X=X+1:S%(X)=R: REM push eval'd list + X=X+1:X%(X)=R: REM push eval'd list A=R:GOSUB LAST: REM return the last element - AY=S%(X):X=X-1: REM pop eval'd list + AY=X%(X):X=X-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_QUOTE: R=Z%(A,1)+1:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_RETURN EVAL_QUASIQUOTE: @@ -364,23 +364,23 @@ EVAL: A=R:GOSUB QUASIQUOTE REM add quasiquote result to pending release queue to free when REM next lower EVAL level returns (LV) - ZM%=ZM%+1:ZR%(ZM%,0)=R:ZR%(ZM%,1)=LV + Y=Y+1:Y%(Y,0)=R:Y%(Y,1)=LV A=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DEFMACRO: REM PRINT "defmacro!" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A1%: REM push A1% - A=A2%:GOSUB EVAL: REM eval a2 - A1%=S%(X):X=X-1: REM pop A1% + X=X+1:X%(X)=A1: REM push A1 + A=A2:GOSUB EVAL: REM eval A2 + A1=X%(X):X=X-1: REM pop A1 REM change function to macro Z%(R,0)=Z%(R,0)+1 - REM set a1 in env to a2 - K=A1%:V=R:GOSUB ENV_SET + REM set A1 in env to A2 + K=A1:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_MACROEXPAND: @@ -389,16 +389,16 @@ EVAL: A=R:GOSUB MACROEXPAND:R=A REM since we are returning it unevaluated, inc the ref cnt - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_RETURN EVAL_TRY: REM PRINT "try*" - GOSUB EVAL_GET_A1: REM set a1%, a2%, and a3% + GOSUB EVAL_GET_A1: REM set A1, A2, and A3 - X=X+1:S%(X)=A: REM push/save A - A=A1%:GOSUB EVAL: REM eval a1 - A=S%(X):X=X-1: REM pop/restore A + X=X+1:X%(X)=A: REM push/save A + A=A1:GOSUB EVAL: REM eval A1 + A=X%(X):X=X-1: REM pop/restore A REM if there is not error or catch block then return IF ER=-2 OR Z%(A,1)=0 THEN GOTO EVAL_RETURN @@ -406,51 +406,51 @@ EVAL: REM create environment for the catch block eval O=E:GOSUB ENV_NEW:E=R - GOSUB EVAL_GET_A2: REM set a1% and a2% - A=A2%:GOSUB EVAL_GET_A2: REM set a1% and a2% from catch block + GOSUB EVAL_GET_A2: REM set A1 and A2 + A=A2:GOSUB EVAL_GET_A2: REM set A1 and A2 from catch block REM create object for ER=-1 type raw string errors - IF ER=-1 THEN AS$=ER$:T=4:GOSUB STRING:ER=R:Z%(R,0)=Z%(R,0)+16 + IF ER=-1 THEN AS$=ER$:T=4:GOSUB STRING:ER=R:Z%(R,0)=Z%(R,0)+32 REM bind the catch symbol to the error object - K=A1%:V=ER:GOSUB ENV_SET + K=A1:V=ER:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release out use, env took ownership REM unset error for catch eval ER=-2:ER$="" - A=A2%:GOSUB EVAL + A=A2:GOSUB EVAL GOTO EVAL_RETURN EVAL_IF: - GOSUB EVAL_GET_A1: REM set a1% + GOSUB EVAL_GET_A1: REM set A1 REM push A - X=X+1:S%(X)=A - A=A1%:GOSUB EVAL + X=X+1:X%(X)=A + A=A1:GOSUB EVAL REM pop A - A=S%(X):X=X-1 + A=X%(X):X=X-1 IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: AY=R:GOSUB RELEASE - GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + GOSUB EVAL_GET_A2: REM set A1 and A2 after EVAL + A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: AY=R:GOSUB RELEASE - REM if no false case (A3%), return nil + REM if no false case (A3), return nil IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN - GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL + A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: - GOSUB EVAL_GET_A2: REM set a1% and a2% - A=A2%:P=A1%:GOSUB MAL_FUNCTION + GOSUB EVAL_GET_A2: REM set A1 and A2 + A=A2:P=A1:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - X=X+1:S%(X)=3 + X=X+1:X%(X)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: @@ -458,46 +458,49 @@ EVAL: IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - X=X+1:S%(X)=R + X=X+1:X%(X)=R F=R+1 AR=Z%(R,1): REM rest R=F:GOSUB DEREF_R:F=R - IF (Z%(F,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + REM if metadata, get the actual object + IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + + IF (Z%(F,0)AND31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R=S%(X):X=X-1 + R=X%(X):X=X-1 ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4%=E: REM save the current environment for release + E4=E: REM save the current environment for release REM create new environ using env stored with function - O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the - REM stack (S%(X-2)) because our new env refers to it and + REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE + IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE REM claim the AST before releasing the list containing it - A=Z%(F,1):Z%(A,0)=Z%(A,0)+16 + A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) - ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV+1 + Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV+1 REM pop and release f/args - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE REM A set above E=R:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -507,7 +510,7 @@ EVAL: REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE + IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level @@ -515,10 +518,10 @@ EVAL: GOSUB RELEASE_PEND REM trigger GC - TA%=FRE(0) + TA=FRE(0) REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 RETURN @@ -528,7 +531,7 @@ MAL_PRINT: RETURN REM RE(A$) -> R -REM Assume RE% has repl_env +REM Assume D has repl_env REM caller must release result RE: R1=0 @@ -536,7 +539,7 @@ RE: R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ @@ -544,14 +547,14 @@ RE: RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ -REM Assume RE% has repl_env +REM Assume D has repl_env REP: R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -572,12 +575,12 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:RE%=R + O=-1:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic - E=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env - ZT%=ZI: REM top of memory after base repl_env + ZT=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself A$="(def! not (fn* (a) (if a false true)))" @@ -631,7 +634,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + REM P1=ZT: P2=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index ed39522c25..c953e5fdb4 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -19,7 +19,7 @@ MAL_READ: REM PAIR_Q(B) -> R PAIR_Q: R=0 - IF (Z%(B,0)AND15)<>6 AND (Z%(B,0)AND15)<>7 THEN RETURN + IF (Z%(B,0)AND31)<>6 AND (Z%(B,0)AND31)<>7 THEN RETURN IF (Z%(B,1)=0) THEN RETURN R=1 RETURN @@ -30,28 +30,28 @@ QUASIQUOTE: IF R=1 THEN GOTO QQ_UNQUOTE REM ['quote, ast] AS$="quote":T=5:GOSUB STRING - B2%=R:B1%=A:GOSUB LIST2 - AY=B2%:GOSUB RELEASE + B2=R:B1=A:GOSUB LIST2 + AY=B2:GOSUB RELEASE RETURN QQ_UNQUOTE: R=A+1:GOSUB DEREF_R - IF (Z%(R,0)AND15)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF (Z%(R,0)AND31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] R=Z%(A,1)+1:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 RETURN QQ_SPLICE_UNQUOTE: REM push A on the stack - X=X+1:S%(X)=A + X=X+1:X%(X)=A REM rest of cases call quasiquote on ast[1..] A=Z%(A,1):GOSUB QUASIQUOTE:T6=R REM pop A off the stack - A=S%(X):X=X-1 + A=X%(X):X=X-1 REM set A to ast[0] for last two cases A=A+1:GOSUB DEREF_A @@ -59,64 +59,64 @@ QUASIQUOTE: B=A:GOSUB PAIR_Q IF R=0 THEN GOTO QQ_DEFAULT B=A+1:GOSUB DEREF_B - IF (Z%(B,0)AND15)<>5 THEN GOTO QQ_DEFAULT + IF (Z%(B,0)AND31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1)+1:GOSUB DEREF_B:B2%=B - AS$="concat":T=5:GOSUB STRING:B3%=R - B1%=T6:GOSUB LIST3 + B=Z%(A,1)+1:GOSUB DEREF_B:B2=B + AS$="concat":T=5:GOSUB STRING:B3=R + B1=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1%:GOSUB RELEASE - AY=B3%:GOSUB RELEASE + AY=B1:GOSUB RELEASE + AY=B3:GOSUB RELEASE RETURN QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] REM push T6 on the stack - X=X+1:S%(X)=T6 + X=X+1:X%(X)=T6 REM A set above to ast[0] - GOSUB QUASIQUOTE:B2%=R + GOSUB QUASIQUOTE:B2=R REM pop T6 off the stack - T6=S%(X):X=X-1 + T6=X%(X):X=X-1 - AS$="cons":T=5:GOSUB STRING:B3%=R - B1%=T6:GOSUB LIST3 + AS$="cons":T=5:GOSUB STRING:B3=R + B1=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1%:GOSUB RELEASE - AY=B2%:GOSUB RELEASE - AY=B3%:GOSUB RELEASE + AY=B1:GOSUB RELEASE + AY=B2:GOSUB RELEASE + AY=B3:GOSUB RELEASE RETURN REM MACROEXPAND(A, E) -> A: MACROEXPAND: REM push original A - X=X+1:S%(X)=A + X=X+1:X%(X)=A MACROEXPAND_LOOP: REM list? - IF (Z%(A,0)AND15)<>6 THEN GOTO MACROEXPAND_DONE + IF (Z%(A,0)AND31)<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? IF Z%(A,1)=0 THEN GOTO MACROEXPAND_DONE B=A+1:GOSUB DEREF_B REM symbol? in first position - IF (Z%(B,0)AND15)<>5 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND31)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? K=B:GOSUB ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE B=T4:GOSUB DEREF_B REM macro? - IF (Z%(B,0)AND15)<>11 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND31)<>11 THEN GOTO MACROEXPAND_DONE REM apply F=B:AR=Z%(A,1):GOSUB APPLY A=R - AY=S%(X) + AY=X%(X) REM if previous A was not the first A into macroexpand (i.e. an REM intermediate form) then free it - IF A<>AY THEN ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV + IF A<>AY THEN Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV IF ER<>-2 THEN GOTO MACROEXPAND_DONE GOTO MACROEXPAND_LOOP @@ -130,19 +130,19 @@ REM called using GOTO to avoid basic return address stack usage REM top of stack should have return label index EVAL_AST: REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A IF ER<>-2 THEN GOTO EVAL_AST_RETURN GOSUB DEREF_A - T=Z%(A,0)AND15 + T=Z%(A,0)AND31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -156,28 +156,28 @@ EVAL_AST: REM make space on the stack X=X+4 REM push type of sequence - S%(X-3)=T + X%(X-3)=T REM push sequence index - S%(X-2)=-1 + X%(X-2)=-1 REM push future return value (new sequence) - S%(X-1)=R + X%(X-1)=R REM push previous new sequence entry - S%(X)=R + X%(X)=R EVAL_AST_SEQ_LOOP: REM update index - S%(X-2)=S%(X-2)+1 + X%(X-2)=X%(X-2)+1 REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (S%(X-3)=8) AND ((S%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+16: REM inc ref cnt of referred value + Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value GOTO EVAL_AST_ADD_VALUE EVAL_AST_DO_EVAL: @@ -189,18 +189,18 @@ EVAL_AST: EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(S%(X)+1,1)=R + Z%(X%(X)+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=S%(X-3):L=0:N=0:GOSUB ALLOC + T=X%(X-3):L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(S%(X),1)=R + Z%(X%(X),1)=R REM update previous ptr to current entry - S%(X)=R + X%(X)=R REM process the next sequence entry from source list A=Z%(A,1) @@ -208,9 +208,9 @@ EVAL_AST: GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM if no error, get return value (new seq) - IF ER=-2 THEN R=S%(X-1) + IF ER=-2 THEN R=X%(X-1) REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=S%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE REM pop previous, return, index and type X=X-4 @@ -218,11 +218,11 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 REM pop EVAL AST return label/address - RN%=S%(X):X=X-1 - ON RN% GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 + RN=X%(X):X=X-1 + ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 RETURN REM EVAL(A, E)) -> R @@ -230,7 +230,7 @@ EVAL: LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:S%(X-1)=E:S%(X)=A + X=X+2:X%(X-1)=E:X%(X)=A EVAL_TCO_RECUR: @@ -244,7 +244,7 @@ EVAL: EVAL_NOT_LIST: REM ELSE REM push EVAL_AST return label/address - X=X+1:S%(X)=1 + X=X+1:X%(X)=1 GOTO EVAL_AST EVAL_AST_RETURN_1: @@ -257,14 +257,14 @@ EVAL: IF R<>1 THEN GOTO EVAL_NOT_LIST GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+16:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0%=A+1 - R=A0%:GOSUB DEREF_R:A0%=R + A0=A+1 + R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0%,0)AND15)<>5 THEN A$="" - IF (Z%(A0%,0)AND15)=5 THEN A$=S$(Z%(A0%,1)) + IF (Z%(A0,0)AND31)<>5 THEN A$="" + IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -279,64 +279,64 @@ EVAL: GOTO EVAL_INVOKE EVAL_GET_A3: - A3%=Z%(Z%(Z%(A,1),1),1)+1 - R=A3%:GOSUB DEREF_R:A3%=R + A3=Z%(Z%(Z%(A,1),1),1)+1 + R=A3:GOSUB DEREF_R:A3=R EVAL_GET_A2: - A2%=Z%(Z%(A,1),1)+1 - R=A2%:GOSUB DEREF_R:A2%=R + A2=Z%(Z%(A,1),1)+1 + R=A2:GOSUB DEREF_R:A2=R EVAL_GET_A1: - A1%=Z%(A,1)+1 - R=A1%:GOSUB DEREF_R:A1%=R + A1=Z%(A,1)+1 + R=A1:GOSUB DEREF_R:A1=R RETURN EVAL_DEF: REM PRINT "def!" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A1%: REM push A1% - A=A2%:GOSUB EVAL: REM eval a2 - A1%=S%(X):X=X-1: REM pop A1% + X=X+1:X%(X)=A1: REM push A1 + A=A2:GOSUB EVAL: REM eval a2 + A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1%:V=R:GOSUB ENV_SET + K=A1:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: REM PRINT "let*" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A2%: REM push/save A2% - X=X+1:S%(X)=E: REM push env for for later release + X=X+1:X%(X)=A2: REM push/save A2 + X=X+1:X%(X)=E: REM push env for for later release REM create new environment with outer as current environment O=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1%,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:S%(X)=A1%: REM push A1% + X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1%,1)+1:GOSUB EVAL - A1%=S%(X):X=X-1: REM pop A1% + A=Z%(A1,1)+1:GOSUB EVAL + A1=X%(X):X=X-1: REM pop A1 - REM set environment: even A1% key to odd A1% eval'd above - K=A1%+1:V=R:GOSUB ENV_SET + REM set environment: even A1 key to odd A1 eval'd above + K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership - REM skip to the next pair of A1% elements - A1%=Z%(Z%(A1%,1),1) + REM skip to the next pair of A1 elements + A1=Z%(Z%(A1,1),1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4%=S%(X):X=X-1: REM pop previous env + E4=X%(X):X=X-1: REM pop previous env REM release previous environment if not the current EVAL env - IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE + IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE - A2%=S%(X):X=X-1: REM pop A2% - A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + A2=X%(X):X=X-1: REM pop A2 + A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: A=Z%(A,1): REM rest @@ -344,19 +344,19 @@ EVAL: REM TODO: TCO REM push EVAL_AST return label/address - X=X+1:S%(X)=2 + X=X+1:X%(X)=2 GOTO EVAL_AST EVAL_AST_RETURN_2: - X=X+1:S%(X)=R: REM push eval'd list + X=X+1:X%(X)=R: REM push eval'd list A=R:GOSUB LAST: REM return the last element - AY=S%(X):X=X-1: REM pop eval'd list + AY=X%(X):X=X-1: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_QUOTE: R=Z%(A,1)+1:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_RETURN EVAL_QUASIQUOTE: @@ -364,23 +364,23 @@ EVAL: A=R:GOSUB QUASIQUOTE REM add quasiquote result to pending release queue to free when REM next lower EVAL level returns (LV) - ZM%=ZM%+1:ZR%(ZM%,0)=R:ZR%(ZM%,1)=LV + Y=Y+1:Y%(Y,0)=R:Y%(Y,1)=LV A=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DEFMACRO: REM PRINT "defmacro!" - GOSUB EVAL_GET_A2: REM set a1% and a2% + GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:S%(X)=A1%: REM push A1% - A=A2%:GOSUB EVAL: REM eval a2 - A1%=S%(X):X=X-1: REM pop A1% + X=X+1:X%(X)=A1: REM push A1 + A=A2:GOSUB EVAL: REM eval A2 + A1=X%(X):X=X-1: REM pop A1 REM change function to macro Z%(R,0)=Z%(R,0)+1 - REM set a1 in env to a2 - K=A1%:V=R:GOSUB ENV_SET + REM set A1 in env to A2 + K=A1:V=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_MACROEXPAND: @@ -389,16 +389,16 @@ EVAL: A=R:GOSUB MACROEXPAND:R=A REM since we are returning it unevaluated, inc the ref cnt - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 GOTO EVAL_RETURN EVAL_TRY: REM PRINT "try*" - GOSUB EVAL_GET_A1: REM set a1%, a2%, and a3% + GOSUB EVAL_GET_A1: REM set A1, A2, and A3 - X=X+1:S%(X)=A: REM push/save A - A=A1%:GOSUB EVAL: REM eval a1 - A=S%(X):X=X-1: REM pop/restore A + X=X+1:X%(X)=A: REM push/save A + A=A1:GOSUB EVAL: REM eval A1 + A=X%(X):X=X-1: REM pop/restore A REM if there is not error or catch block then return IF ER=-2 OR Z%(A,1)=0 THEN GOTO EVAL_RETURN @@ -406,51 +406,51 @@ EVAL: REM create environment for the catch block eval O=E:GOSUB ENV_NEW:E=R - GOSUB EVAL_GET_A2: REM set a1% and a2% - A=A2%:GOSUB EVAL_GET_A2: REM set a1% and a2% from catch block + GOSUB EVAL_GET_A2: REM set A1 and A2 + A=A2:GOSUB EVAL_GET_A2: REM set A1 and A2 from catch block REM create object for ER=-1 type raw string errors - IF ER=-1 THEN AS$=ER$:T=4:GOSUB STRING:ER=R:Z%(R,0)=Z%(R,0)+16 + IF ER=-1 THEN AS$=ER$:T=4:GOSUB STRING:ER=R:Z%(R,0)=Z%(R,0)+32 REM bind the catch symbol to the error object - K=A1%:V=ER:GOSUB ENV_SET + K=A1:V=ER:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release out use, env took ownership REM unset error for catch eval ER=-2:ER$="" - A=A2%:GOSUB EVAL + A=A2:GOSUB EVAL GOTO EVAL_RETURN EVAL_IF: - GOSUB EVAL_GET_A1: REM set a1% + GOSUB EVAL_GET_A1: REM set A1 REM push A - X=X+1:S%(X)=A - A=A1%:GOSUB EVAL + X=X+1:X%(X)=A + A=A1:GOSUB EVAL REM pop A - A=S%(X):X=X-1 + A=X%(X):X=X-1 IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: AY=R:GOSUB RELEASE - GOSUB EVAL_GET_A2: REM set a1% and a2% after EVAL - A=A2%:GOTO EVAL_TCO_RECUR: REM TCO loop + GOSUB EVAL_GET_A2: REM set A1 and A2 after EVAL + A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF_FALSE: AY=R:GOSUB RELEASE - REM if no false case (A3%), return nil + REM if no false case (A3), return nil IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN - GOSUB EVAL_GET_A3: REM set a1% - a3% after EVAL - A=A3%:GOTO EVAL_TCO_RECUR: REM TCO loop + GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL + A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: - GOSUB EVAL_GET_A2: REM set a1% and a2% - A=A2%:P=A1%:GOSUB MAL_FUNCTION + GOSUB EVAL_GET_A2: REM set A1 and A2 + A=A2:P=A1:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: REM push EVAL_AST return label/address - X=X+1:S%(X)=3 + X=X+1:X%(X)=3 GOTO EVAL_AST EVAL_AST_RETURN_3: @@ -458,46 +458,49 @@ EVAL: IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - X=X+1:S%(X)=R + X=X+1:X%(X)=R F=R+1 AR=Z%(R,1): REM rest R=F:GOSUB DEREF_R:F=R - IF (Z%(F,0)AND15)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND15)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + REM if metadata, get the actual object + IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + + IF (Z%(F,0)AND31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R=S%(X):X=X-1 + R=X%(X):X=X-1 ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: GOSUB DO_FUNCTION REM pop and release f/args - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4%=E: REM save the current environment for release + E4=E: REM save the current environment for release REM create new environ using env stored with function - O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the - REM stack (S%(X-2)) because our new env refers to it and + REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4%<>S%(X-2) THEN AY=E4%:GOSUB RELEASE + IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE REM claim the AST before releasing the list containing it - A=Z%(F,1):Z%(A,0)=Z%(A,0)+16 + A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) - ZM%=ZM%+1:ZR%(ZM%,0)=A:ZR%(ZM%,1)=LV+1 + Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV+1 REM pop and release f/args - AY=S%(X):X=X-1:GOSUB RELEASE + AY=X%(X):X=X-1:GOSUB RELEASE REM A set above E=R:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -507,7 +510,7 @@ EVAL: REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>S%(X-1) THEN AY=E:GOSUB RELEASE + IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level @@ -515,10 +518,10 @@ EVAL: GOSUB RELEASE_PEND REM trigger GC - TA%=FRE(0) + TA=FRE(0) REM pop A and E off the stack - E=S%(X-1):A=S%(X):X=X-2 + E=X%(X-1):A=X%(X):X=X-2 RETURN @@ -528,7 +531,7 @@ MAL_PRINT: RETURN REM RE(A$) -> R -REM Assume RE% has repl_env +REM Assume D has repl_env REM caller must release result RE: R1=0 @@ -536,7 +539,7 @@ RE: R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL REP_DONE: REM Release memory from MAL_READ @@ -544,14 +547,14 @@ RE: RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ -REM Assume RE% has repl_env +REM Assume D has repl_env REP: R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=RE%:GOSUB EVAL + A=R:E=D:GOSUB EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -572,12 +575,12 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:RE%=R + O=-1:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic - E=RE%:GOSUB INIT_CORE_NS: REM set core functions in repl_env + E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env - ZT%=ZI: REM top of memory after base repl_env + ZT=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself A$="(def! *host-language* "+CHR$(34)+"C64 Basic"+CHR$(34)+")" @@ -640,7 +643,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1%=ZT%: P2%=-1: GOSUB PR_MEMORY + REM P1=ZT: P2=-1: GOSUB PR_MEMORY GOSUB PR_MEMORY_SUMMARY END diff --git a/basic/types.in.bas b/basic/types.in.bas index 5b49f63267..174406d475 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -6,29 +6,31 @@ REM float 3 -> ??? REM string/kw 4 -> S$ index REM symbol 5 -> S$ index REM list next/val 6 -> next Z% index (0 for last) -REM followed by 14 and value (unless empty) +REM 14 value (unless empty) REM vector next/val 7 -> next Z% index (0 for last) -REM followed by 14 and value (unless empty) +REM 14 value (unless empty) REM hashmap next/val 8 -> next Z% index (0 for last) -REM followed by 14 and key/value (alternating) +REM 14 key/value (alternating) REM function 9 -> function index REM mal function 10 -> body AST Z% index -REM followed by param and env Z% index +REM param env Z% index REM macro (same as 10) 11 -> body AST Z% index -REM followed by param and env Z% index +REM param env Z% index REM atom 12 -> Z% index REM environment 13 -> data/hashmap Z% index -REM followed by 14 and outer Z% index (-1 for none) +REM 14 outer Z% index (-1 for none) REM reference/ptr 14 -> Z% index / or 0 REM next free ptr 15 -> Z% index / or 0 +REM metadata 16-31 -> Z% index of object with this metadata +REM 14 -> Z% index of metdata object INIT_MEMORY: T=FRE(0) Z1=2048+512: REM Z% (boxed memory) size (4 bytes each) Z2=256: REM S$ (string memory) size (3 bytes each) - Z3=256: REM S% (call stack) size (2 bytes each) - Z4=64: REM ZR% (release stack) size (4 bytes each) + Z3=256: REM X% (call stack) size (2 bytes each) + Z4=64: REM Y% (release stack) size (4 bytes each) REM global error state REM -2 : no error @@ -44,7 +46,7 @@ INIT_MEMORY: Z%(0,0)=0:Z%(0,1)=0 Z%(1,0)=1:Z%(1,1)=0 Z%(2,0)=1:Z%(2,1)=1 - Z%(3,0)=6+16:Z%(3,1)=0 + Z%(3,0)=6+32:Z%(3,1)=0 Z%(4,0)=0:Z%(4,1)=0 REM start of unused memory @@ -54,13 +56,13 @@ INIT_MEMORY: ZK=5 REM string memory storage - ZJ=0:DIM S$(Z2) + S=0:DIM S$(Z2) REM call/logic stack - X=-1:DIM S%(Z3): REM stack of Z% indexes + X=-1:DIM X%(Z3): REM stack of Z% indexes REM pending release stack - ZM%=-1:DIM ZR%(Z4,1): REM stack of Z% indexes + Y=-1:DIM Y%(Z4,1): REM stack of Z% indexes REM PRINT "Lisp data memory: "+STR$(T-FRE(0)) REM PRINT "Interpreter working memory: "+STR$(FRE(0)) @@ -77,14 +79,14 @@ REM M is default for Z%(R+1,0), if relevant for T REM N is default for Z%(R+1,1), if relevant for T ALLOC: SZ=2 - IF T<6 OR T=9 OR T=12 OR T>13 THEN SZ=1 + IF T<6 OR T=9 OR T=12 OR T=14 THEN SZ=1 REM PRINT "ALLOC T: "+STR$(T)+", SZ: "+STR$(SZ)+", ZK: "+STR$(ZK) U3=ZK U4=ZK ALLOC_LOOP: IF U4=ZI THEN GOTO ALLOC_UNUSED REM TODO sanity check that type is 15 - IF ((Z%(U4,0)AND-16)/16)=SZ THEN GOTO ALLOC_MIDDLE + IF ((Z%(U4,0)AND-32)/32)=SZ THEN GOTO ALLOC_MIDDLE REM PRINT "ALLOC search: U3: "+STR$(U3)+", U4: "+STR$(U4) U3=U4: REM previous set to current U4=Z%(U4,1): REM current set to next @@ -106,26 +108,26 @@ ALLOC: IF U3<>U4 THEN Z%(U3,1)=ZI GOTO ALLOC_DONE ALLOC_DONE: - Z%(R,0)=T+16 + Z%(R,0)=T+32 REM set Z%(R,1) to default L - IF T>=6 AND T<>9 AND L>0 THEN Z%(L,0)=Z%(L,0)+16 + IF T>=6 AND T<>9 AND L>0 THEN Z%(L,0)=Z%(L,0)+32 Z%(R,1)=L IF SZ=1 THEN RETURN - Z%(R+1,0)=14: REM default for 6-8, and 13 + Z%(R+1,0)=14: REM default for 6-8, and 13, and >=16 (metadata) REM function/macro sets Z%(R+1,0) to default M - IF T=10 OR T=11 THEN Z%(M,0)=Z%(M,0)+16:Z%(R+1,0)=M + IF T=10 OR T=11 THEN Z%(M,0)=Z%(M,0)+32:Z%(R+1,0)=M REM seq, function/macro, environment sets Z%(R+1,1) to default N - IF N>0 THEN Z%(N,0)=Z%(N,0)+16 + IF N>0 THEN Z%(N,0)=Z%(N,0)+32 Z%(R+1,1)=N RETURN REM FREE(AY, SZ) -> nil FREE: REM assumes reference count cleanup already (see RELEASE) - Z%(AY,0)=(SZ*16)+15: REM set type(15) and size + Z%(AY,0)=(SZ*32)+15: REM set type(15) and size Z%(AY,1)=ZK ZK=AY IF SZ>=2 THEN Z%(AY+1,0)=0:Z%(AY+1,1)=0 @@ -145,7 +147,7 @@ RELEASE: IF RC=0 THEN RETURN REM pop next object to release, decrease remaining count - AY=S%(X):X=X-1 + AY=X%(X):X=X-1 RC=RC-1 RELEASE_ONE: @@ -153,7 +155,7 @@ RELEASE: REM nil, false, true IF AY<3 THEN GOTO RELEASE_TOP - U6=Z%(AY,0)AND15: REM type + U6=Z%(AY,0)AND31: REM type REM AZ=AY: PR=1: GOSUB PR_STR REM PRINT "RELEASE AY:"+STR$(AY)+"["+R$+"] (byte0:"+STR$(Z%(AY,0))+")" @@ -164,16 +166,16 @@ RELEASE: IF Z%(AY,0)<15 THEN ER=-1:ER$="Free of freed object: "+STR$(AY):RETURN REM decrease reference count by one - Z%(AY,0)=Z%(AY,0)-16 + Z%(AY,0)=Z%(AY,0)-32 REM our reference count is not 0, so don't release - IF Z%(AY,0)>=16 GOTO RELEASE_TOP + IF Z%(AY,0)>=32 GOTO RELEASE_TOP REM switch on type IF (U6<=5) OR (U6=9) THEN GOTO RELEASE_SIMPLE IF (U6>=6) AND (U6<=8) THEN GOTO RELEASE_SEQ - IF U6=10 THEN GOTO RELEASE_MAL_FUNCTION - IF U6=11 THEN GOTO RELEASE_MAL_FUNCTION + IF U6=10 OR U6=11 THEN GOTO RELEASE_MAL_FUNCTION + IF U6>=16 THEN GOTO RELEASE_METADATA IF U6=12 THEN GOTO RELEASE_ATOM IF U6=13 THEN GOTO RELEASE_ENV IF U6=15 THEN ER=-1:ER$="RELEASE of already freed: "+STR$(AY):RETURN @@ -191,27 +193,34 @@ RELEASE: IF Z%(AY,1)=0 THEN GOTO RELEASE_SIMPLE_2 IF Z%(AY+1,0)<>14 THEN ER=-1:ER$="invalid list value"+STR$(AY+1):RETURN REM add value and next element to stack - RC=RC+2:X=X+2:S%(X-1)=Z%(AY+1,1):S%(X)=Z%(AY,1) + RC=RC+2:X=X+2 + X%(X-1)=Z%(AY+1,1):X%(X)=Z%(AY,1) GOTO RELEASE_SIMPLE_2 RELEASE_ATOM: REM add contained/referred value - RC=RC+1:X=X+1:S%(X)=Z%(AY,1) + RC=RC+1:X=X+1:X%(X)=Z%(AY,1) REM free the atom itself GOTO RELEASE_SIMPLE RELEASE_MAL_FUNCTION: REM add ast, params and environment to stack RC=RC+3:X=X+3 - S%(X-2)=Z%(AY,1):S%(X-1)=Z%(AY+1,0):S%(X)=Z%(AY+1,1) + X%(X-2)=Z%(AY,1):X%(X-1)=Z%(AY+1,0):X%(X)=Z%(AY+1,1) REM free the current 2 element mal_function and continue SZ=2:GOSUB FREE GOTO RELEASE_TOP + RELEASE_METADATA: + REM add object and metadata object + RC=RC+2:X=X+2 + X%(X-1)=Z%(AY,1):X%(X)=Z%(AY+1,1) + SZ=2:GOSUB FREE + GOTO RELEASE_TOP RELEASE_ENV: REM add the hashmap data to the stack - RC=RC+1:X=X+1:S%(X)=Z%(AY,1) + RC=RC+1:X=X+1:X%(X)=Z%(AY,1) REM if no outer set IF Z%(AY+1,1)=-1 THEN GOTO RELEASE_ENV_FREE REM add outer environment to the stack - RC=RC+1:X=X+1:S%(X)=Z%(AY+1,1) + RC=RC+1:X=X+1:X%(X)=Z%(AY+1,1) RELEASE_ENV_FREE: REM free the current 2 element environment and continue SZ=2:GOSUB FREE @@ -219,33 +228,33 @@ RELEASE: RELEASE_REFERENCE: IF Z%(AY,1)=0 THEN GOTO RELEASE_SIMPLE REM add the referred element to the stack - RC=RC+1:X=X+1:S%(X)=Z%(AY,1) + RC=RC+1:X=X+1:X%(X)=Z%(AY,1) REM free the current element and continue SZ=1:GOSUB FREE GOTO RELEASE_TOP REM RELEASE_PEND(LV) -> nil RELEASE_PEND: - IF ZM%<0 THEN RETURN - IF ZR%(ZM%,1)<=LV THEN RETURN - REM PRINT "RELEASE_PEND releasing:"+STR$(ZR%(ZM%,0)) - AY=ZR%(ZM%,0):GOSUB RELEASE - ZM%=ZM%-1 + IF Y<0 THEN RETURN + IF Y%(Y,1)<=LV THEN RETURN + REM PRINT "RELEASE_PEND releasing:"+STR$(Y%(Y,0)) + AY=Y%(Y,0):GOSUB RELEASE + Y=Y-1 GOTO RELEASE_PEND REM DEREF_R(R) -> R DEREF_R: - IF (Z%(R,0)AND15)=14 THEN R=Z%(R,1):GOTO DEREF_R + IF (Z%(R,0)AND31)=14 THEN R=Z%(R,1):GOTO DEREF_R RETURN REM DEREF_A(A) -> A DEREF_A: - IF (Z%(A,0)AND15)=14 THEN A=Z%(A,1):GOTO DEREF_A + IF (Z%(A,0)AND31)=14 THEN A=Z%(A,1):GOTO DEREF_A RETURN REM DEREF_B(B) -> B DEREF_B: - IF (Z%(B,0)AND15)=14 THEN B=Z%(B,1):GOTO DEREF_B + IF (Z%(B,0)AND31)=14 THEN B=Z%(B,1):GOTO DEREF_B RETURN @@ -257,8 +266,8 @@ EQUAL_Q: GOSUB DEREF_B R=0 - U1=Z%(A,0)AND15 - U2=Z%(B,0)AND15 + U1=Z%(A,0)AND31 + U2=Z%(B,0)AND31 IF NOT (U1=U2 OR ((U1=6 OR U1=7) AND (U2=6 OR U2=7))) THEN RETURN IF U1=6 THEN GOTO EQUAL_Q_SEQ IF U1=7 THEN GOTO EQUAL_Q_SEQ @@ -272,11 +281,11 @@ EQUAL_Q: IF (Z%(A,1)=0) OR (Z%(B,1)=0) THEN R=0:RETURN REM push A and B - X=X+2:S%(X-1)=A:S%(X)=B + X=X+2:X%(X-1)=A:X%(X)=B REM compare the elements A=Z%(A+1,1):B=Z%(B+1,1):GOSUB EQUAL_Q REM pop A and B - A=S%(X-1):B=S%(X):X=X-2 + A=X%(X-1):B=X%(X):X=X-2 IF R=0 THEN RETURN REM next elements of the sequences @@ -290,17 +299,17 @@ REM string functions REM STRING_(AS$) -> R REM intern string (returns string index, not Z% index) STRING_: - IF ZJ=0 THEN GOTO STRING_NOT_FOUND + IF S=0 THEN GOTO STRING_NOT_FOUND REM search for matching string in S$ - FOR I=0 TO ZJ-1 + FOR I=0 TO S-1 IF AS$=S$(I) THEN R=I:RETURN NEXT I STRING_NOT_FOUND: - S$(ZJ)=AS$ - R=ZJ - ZJ=ZJ+1 + S$(S)=AS$ + R=S + S=S+1 RETURN REM STRING(AS$, T) -> R @@ -329,7 +338,7 @@ REM sequence functions REM FORCE_SEQ_TYPE(A,T) -> R FORCE_SEQ_TYPE: REM if it's already the right type, inc ref cnt and return it - IF (Z%(A,0)AND15)=T THEN R=A:Z%(R,0)=Z%(R,0)+16:RETURN + IF (Z%(A,0)AND31)=T THEN R=A:Z%(R,0)=Z%(R,0)+32:RETURN REM otherwise, copy first element to turn it into correct type B=A+1:GOSUB DEREF_B: REM value to copy L=Z%(A,1):N=B:GOSUB ALLOC: REM T already set @@ -340,7 +349,7 @@ FORCE_SEQ_TYPE: REM LIST_Q(A) -> R LIST_Q: R=0 - IF (Z%(A,0)AND15)=6 THEN R=1 + IF (Z%(A,0)AND31)=6 THEN R=1 RETURN REM EMPTY_Q(A) -> R @@ -369,7 +378,7 @@ LAST: GOTO LAST_LOOP LAST_DONE: R=T6+1:GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+16 + Z%(R,0)=Z%(R,0)+32 RETURN REM SLICE(A,B,C) -> R @@ -395,29 +404,29 @@ SLICE: R6=R: REM save previous list element REM copy value and inc ref cnt Z%(R6+1,1)=Z%(A+1,1) - R=A+1:GOSUB DEREF_R:Z%(R,0)=Z%(R,0)+16 + R=A+1:GOSUB DEREF_R:Z%(R,0)=Z%(R,0)+32 REM advance to next element of A A=Z%(A,1) I=I+1 GOTO SLICE_LOOP -REM LIST2(B2%,B1%) -> R +REM LIST2(B2,B1) -> R LIST2: - REM last element is 3 (empty list), second element is B1% - T=6:L=3:N=B1%:GOSUB ALLOC + REM last element is 3 (empty list), second element is B1 + T=6:L=3:N=B1:GOSUB ALLOC - REM first element is B2% - T=6:L=R:N=B2%:GOSUB ALLOC + REM first element is B2 + T=6:L=R:N=B2:GOSUB ALLOC AY=L:GOSUB RELEASE: REM new list takes ownership of previous RETURN -REM LIST3(B3%,B2%,B1%) -> R +REM LIST3(B3,B2,B1) -> R LIST3: GOSUB LIST2 - REM first element is B3% - T=6:L=R:N=B3%:GOSUB ALLOC + REM first element is B3 + T=6:L=R:N=B3:GOSUB ALLOC AY=L:GOSUB RELEASE: REM new list takes ownership of previous RETURN @@ -446,33 +455,33 @@ ASSOC1: REM ASSOC1(H, K$, V) -> R ASSOC1_S: - S$(ZJ)=K$ + S$(S)=K$ REM add the key string - T=4:L=ZJ:GOSUB ALLOC - ZJ=ZJ+1 + T=4:L=S:GOSUB ALLOC + S=S+1 K=R:GOSUB ASSOC1 AY=K:GOSUB RELEASE: REM map took ownership of key RETURN REM HASHMAP_GET(H, K) -> R HASHMAP_GET: - H2%=H + H2=H T1$=S$(Z%(K,1)): REM search key string T3=0: REM whether found or not (for HASHMAP_CONTAINS) R=0 HASHMAP_GET_LOOP: REM no matching key found - IF Z%(H2%,1)=0 THEN R=0:RETURN + IF Z%(H2,1)=0 THEN R=0:RETURN REM follow value ptrs - T2=H2%+1 + T2=H2+1 HASHMAP_GET_DEREF: IF Z%(T2,0)=14 THEN T2=Z%(T2,1):GOTO HASHMAP_GET_DEREF REM get key string T2$=S$(Z%(T2,1)) REM if they are equal, we found it - IF T1$=T2$ THEN T3=1:R=Z%(H2%,1)+1:RETURN + IF T1$=T2$ THEN T3=1:R=Z%(H2,1)+1:RETURN REM skip to next key - H2%=Z%(Z%(H2%,1),1) + H2=Z%(Z%(H2,1),1) GOTO HASHMAP_GET_LOOP REM HASHMAP_CONTAINS(H, K) -> R @@ -497,9 +506,12 @@ MAL_FUNCTION: REM APPLY(F, AR) -> R REM restores E APPLY: - IF (Z%(F,0)AND15)=9 THEN GOTO DO_APPLY_FUNCTION - IF (Z%(F,0)AND15)=10 THEN GOTO DO_APPLY_MAL_FUNCTION - IF (Z%(F,0)AND15)=11 THEN GOTO DO_APPLY_MAL_FUNCTION + REM if metadata, get the actual object + IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + + IF (Z%(F,0)AND31)=9 THEN GOTO DO_APPLY_FUNCTION + IF (Z%(F,0)AND31)=10 THEN GOTO DO_APPLY_MAL_FUNCTION + IF (Z%(F,0)AND31)=11 THEN GOTO DO_APPLY_MAL_FUNCTION DO_APPLY_FUNCTION: GOSUB DO_FUNCTION @@ -507,17 +519,17 @@ APPLY: RETURN DO_APPLY_MAL_FUNCTION: - X=X+1:S%(X)=E: REM save the current environment + X=X+1:X%(X)=E: REM save the current environment REM create new environ using env and params stored in the REM function and bind the params to the apply arguments - O=Z%(F+1,1):BI%=Z%(F+1,0):EX%=AR:GOSUB ENV_NEW_BINDS + O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS A=Z%(F,1):E=R:GOSUB EVAL AY=E:GOSUB RELEASE: REM release the new environment - E=S%(X):X=X-1: REM pop/restore the saved environment + E=X%(X):X=X-1: REM pop/restore the saved environment RETURN diff --git a/basic/variables.txt b/basic/variables.txt index a388602d30..ade67d6983 100644 --- a/basic/variables.txt +++ b/basic/variables.txt @@ -3,23 +3,29 @@ Global Unique: Z% : boxed memory values ZI : start of unused memory (index into Z%) ZK : start of free list (index into Z%) +ZT : top of memory after repl env allocations S$ : string memory storage -ZJ : next free index in S$ +S : next free index in S$ -S% : logic/call stack (Z% indexes) -X : top element of S% stack +X% : logic/call stack (Z% indexes) +X : top element of X% stack -ZR% : pending release stack (index into Z%, eval level) -ZM% : top element of ZR% stack +Y% : pending release stack [index into Z%, eval level] +Y : top element of Y% stack -RE% : root repl environment +D : root repl environment ER : error type (-2: none, -1: string, >=0: object) ER$ : error string (ER=-1) +BI : ENV_NEW_BINDS binds list +EX : ENV_NEW_BINDS expressions list LV : EVAL stack call level/depth +IDX : reader current string position + + Calling arguments/temporaries: A : common call arguments (especially EVAL, EVAL_AST) @@ -39,6 +45,18 @@ R : common return value T : common temp, type V : hash map value +A0 : EVAL ast elements +A1 : EVAL ast elements +A2 : EVAL ast elements +A3 : EVAL ast elements +B1 : LIST2/LIST3 param +B2 : LIST2/LIST3 param +B3 : LIST3 param +CZ : DO_CONCAT stack position +CUR : READ_TOKEN current character index +EF : ENV_FIND cur env ptr +P1 : PR_MEMORY, CHECK_FREE_LIST start +P2 : PR_MEMORY, CHECK_FREE_LIST end SZ : size argument to ALLOC Reused/temporaries: @@ -48,4 +66,4 @@ J : REPLACE Unused: -D, G, L, M, N, Q, U, W, Y +G, L, M, N, Q, U, W From 49b192ddc3233caa2a726198ddf5a2270f6cdbf8 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 23 Oct 2016 20:07:49 +0200 Subject: [PATCH 0180/2308] Implement step A --- pil/core.l | 30 ++++++++- pil/stepA_mal.l | 168 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 pil/stepA_mal.l diff --git a/pil/core.l b/pil/core.l index 22339e013c..ea99f6143c 100644 --- a/pil/core.l +++ b/pil/core.l @@ -58,6 +58,25 @@ (unless (find '((X) (MAL-= (car L) X)) Args) (link (car L) (cadr L)) ) ) ) ) ) ) +(de MAL-seq (X) + (if (or (= (MAL-type X) 'nil) (not (MAL-value X))) + *MAL-nil + (case (MAL-type X) + (list X) + (vector (MAL-list (MAL-value X))) + (string (MAL-list (mapcar MAL-string (chop (MAL-value X))))) ) ) ) + +(de MAL-conj @ + (let (Seq (next) Args (rest)) + (if (= (MAL-type Seq) 'vector) + (MAL-vector (append (MAL-value Seq) Args)) + (MAL-list (append (reverse Args) (MAL-value Seq))) ) ) ) + +(de clone (X) + (let X* (new (val X)) + (maps '((C) (put X* (cdr C) (car C))) X) + X* ) ) + (def '*Ns '((+ . `(MAL-fn '((A B) (MAL-number (+ (MAL-value A) (MAL-value B)))))) (- . `(MAL-fn '((A B) (MAL-number (- (MAL-value A) (MAL-value B)))))) @@ -107,6 +126,7 @@ (false? . `(MAL-fn '((X) (if (= (MAL-type X) 'false) *MAL-true *MAL-false)))) (symbol? . `(MAL-fn '((X) (if (= (MAL-type X) 'symbol) *MAL-true *MAL-false)))) (keyword? . `(MAL-fn '((X) (if (= (MAL-type X) 'keyword) *MAL-true *MAL-false)))) + (string? . `(MAL-fn '((X) (if (= (MAL-type X) 'string) *MAL-true *MAL-false)))) (vector? . `(MAL-fn '((X) (if (= (MAL-type X) 'vector) *MAL-true *MAL-false)))) (map? . `(MAL-fn '((X) (if (= (MAL-type X) 'map) *MAL-true *MAL-false)))) (sequential? . `(MAL-fn '((X) (if (MAL-seq? X) *MAL-true *MAL-false)))) @@ -121,4 +141,12 @@ (get . `(MAL-fn '((Map Key) (or (and (<> (MAL-type Map) 'nil) (cdr (find '((X) (MAL-= (car X) Key)) (chunk (MAL-value Map))))) *MAL-nil)))) (contains? . `(MAL-fn '((Map Key) (if (find '((X) (MAL-= (car X) Key)) (chunk (MAL-value Map))) *MAL-true *MAL-false)))) (keys . `(MAL-fn '((Map) (MAL-list (mapcar car (chunk (MAL-value Map))))))) - (vals . `(MAL-fn '((Map) (MAL-list (extract cdr (chunk (MAL-value Map))))))) ) ) + (vals . `(MAL-fn '((Map) (MAL-list (extract cdr (chunk (MAL-value Map))))))) + + (with-meta . `(MAL-fn '((X Meta) (let X* (clone X) (put X* 'meta Meta) X*)))) + (meta . `(MAL-fn '((X) (or (MAL-meta X) *MAL-nil)))) + + (readline . `(MAL-fn '((Prompt) (let Output (readline (MAL-value Prompt)) (if (=0 Output) NIL (MAL-string Output)))))) + (time-ms . `(MAL-fn '(() (MAL-number (/ (usec) 1000))))) + (conj . `(MAL-fn MAL-conj)) + (seq . `(MAL-fn MAL-seq)) ) ) diff --git a/pil/stepA_mal.l b/pil/stepA_mal.l new file mode 100644 index 0000000000..507e273c9c --- /dev/null +++ b/pil/stepA_mal.l @@ -0,0 +1,168 @@ +(de load-relative (Path) + (load (pack (car (file)) Path)) ) + +(load-relative "readline.l") +(load-relative "types.l") +(load-relative "reader.l") +(load-relative "printer.l") +(load-relative "env.l") +(load-relative "func.l") +(load-relative "core.l") + +(de READ (String) + (read-str String) ) + +(def '*ReplEnv (MAL-env NIL)) +(for Bind *Ns (set> *ReplEnv (car Bind) (cdr Bind))) + +(de is-pair (Ast) + (and (memq (MAL-type Ast) '(list vector)) (MAL-value Ast) T) ) + +(de quasiquote (Ast) + (if (not (is-pair Ast)) + (MAL-list (list (MAL-symbol 'quote) Ast)) + (let A (MAL-value Ast) + (cond + ((= (MAL-value (car A)) 'unquote) + (cadr A) ) + ((and (is-pair (car A)) + (= (MAL-value (car (MAL-value (car A)))) 'splice-unquote) ) + (MAL-list (list (MAL-symbol 'concat) + (cadr (MAL-value (car A))) + (quasiquote (MAL-list (cdr A))) ) ) ) + (T + (MAL-list (list (MAL-symbol 'cons) + (quasiquote (car A)) + (quasiquote (MAL-list (cdr A))) ) ) ) ) ) ) ) + +(de is-macro-call (Ast Env) + (when (= (MAL-type Ast) 'list) + (let A0 (car (MAL-value Ast)) + (when (= (MAL-type A0) 'symbol) + (let Value (find> Env (MAL-value A0)) + (and (isa '+Func Value) (get Value 'is-macro) T) ) ) ) ) ) + +(de macroexpand (Ast Env) + (while (is-macro-call Ast Env) + (let (Ast* (MAL-value Ast) + Macro (get (find> Env (MAL-value (car Ast*))) 'fn) + Args (cdr Ast*) ) + (setq Ast (apply (MAL-value Macro) Args)) ) ) + Ast ) + +(de EVAL (Ast Env) + (catch 'done + (while t + (when (not (= (MAL-type Ast) 'list)) + (throw 'done (eval-ast Ast Env)) ) + (setq Ast (macroexpand Ast Env)) + (when (or (not (= (MAL-type Ast) 'list)) (not (MAL-value Ast))) + (throw 'done (eval-ast Ast Env)) ) + (let (Ast* (MAL-value Ast) + A0* (MAL-value (car Ast*)) + A1 (cadr Ast*) + A1* (MAL-value A1) + A2 (caddr Ast*) + A3 (cadddr Ast*) ) + (cond + ((= A0* 'def!) + (throw 'done (set> Env A1* (EVAL A2 Env))) ) + ((= A0* 'quote) + (throw 'done A1) ) + ((= A0* 'quasiquote) + (setq Ast (quasiquote A1)) ) # TCO + ((= A0* 'defmacro!) + (let Form (EVAL A2 Env) + (put Form 'is-macro T) + (throw 'done (set> Env A1* Form)) ) ) + ((= A0* 'macroexpand) + (throw 'done (macroexpand A1 Env)) ) + ((= A0* 'try*) + (let Result (catch 'err (throw 'done (EVAL A1 Env))) + (if (isa '+MALError Result) + (let A (MAL-value A2) + (if (and (= (MAL-type A2) 'list) + (= (MAL-value (car A)) 'catch*) ) + (let (Bind (MAL-value (cadr A)) + Exc (MAL-value Result) + Form (caddr A) + Env* (MAL-env Env (list Bind) (list Exc)) ) + (throw 'done (EVAL Form Env*)) ) + (throw 'err Result) ) ) + (throw 'done Result) ) ) ) + ((= A0* 'let*) + (let Env* (MAL-env Env) + (for (Bindings A1* Bindings) + (let (Key (MAL-value (pop 'Bindings)) + Value (EVAL (pop 'Bindings) Env*) ) + (set> Env* Key Value) ) ) + (setq Env Env* Ast A2) ) ) # TCO + ((= A0* 'do) + (mapc '((Form) (EVAL Form Env)) (head -1 (cdr Ast*))) + (setq Ast (last Ast*)) ) # TCO + ((= A0* 'if) + (if (not (memq (MAL-type (EVAL A1 Env)) '(nil false))) + (setq Ast A2) # TCO + (if A3 + (setq Ast A3) # TCO + (throw 'done *MAL-nil) ) ) ) + ((= A0* 'fn*) + (let (Binds (mapcar MAL-value A1*) + Body A2 + Fn (MAL-fn + (curry (Env Binds Body) @ + (let Env* (MAL-env Env Binds (rest)) + (EVAL Body Env*) ) ) ) ) + (throw 'done (MAL-func Env Body Binds Fn)) ) ) + (T + (let (Ast* (MAL-value (eval-ast Ast Env)) + Fn (car Ast*) + Args (cdr Ast*) ) + (if (isa '+MALFn Fn) + (throw 'done (apply (MAL-value Fn) Args)) + (let Env* (MAL-env (get Fn 'env) (get Fn 'params) Args) + (setq Ast (get Fn 'ast) Env Env*) ) ) ) ) ) ) ) ) ) + +(de eval-ast (Ast Env) + (let Value (MAL-value Ast) + (case (MAL-type Ast) + (symbol (get> Env Value)) + (list (MAL-list (mapcar '((Form) (EVAL Form Env)) Value))) + (vector (MAL-vector (mapcar '((Form) (EVAL Form Env)) Value))) + (map (MAL-map (mapcar '((Form) (EVAL Form Env)) Value))) + (T Ast) ) ) ) + +(set> *ReplEnv 'eval (MAL-fn (curry (*ReplEnv) (Form) (EVAL Form *ReplEnv)))) +(set> *ReplEnv '*ARGV* (MAL-list (mapcar MAL-string (cdr (argv))))) +(set> *ReplEnv '*host-language* (MAL-string "pil")) + +(de PRINT (Ast) + (pr-str Ast T) ) + +(de rep (String) + (PRINT (EVAL (READ String) *ReplEnv)) ) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") + +(rep "(def! *gensym-counter* (atom 0))") +(rep "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))") +(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))") + +(load-history ".mal_history") + +(if (argv) + (rep (pack "(load-file \"" (car (argv)) "\")")) + (use Input + (rep "(println (str \"Mal [\" *host-language* \"]\"))") + (until (=0 (setq Input (readline "user> "))) + (let Output (catch 'err (rep Input)) + (if (isa '+MALError Output) + (let Message (MAL-value Output) + (unless (= (MAL-value Message) "end of token stream") + (prinl "[error] " (pr-str Message)) ) ) + (prinl Output) ) ) ) ) ) + +(prinl) +(bye) From 5f4a09583029f98888ca4ff1169434d1df550e74 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 23 Oct 2016 20:39:29 +0200 Subject: [PATCH 0181/2308] Fix assoc --- pil/core.l | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pil/core.l b/pil/core.l index ea99f6143c..30bb697475 100644 --- a/pil/core.l +++ b/pil/core.l @@ -50,6 +50,18 @@ (for (L List L (cddr L)) (link (cons (car L) (cadr L))) ) ) ) +(de join (List) + (mapcan '((X) (list (car X) (cdr X))) List) ) + +(de MAL-assoc @ + (let (Map (next) Args (rest)) + (MAL-map + (append Args + (join + (filter '((X) (not (find '((Y) (MAL-= (car Y) (car X))) + (chunk Args) ) ) ) + (chunk (MAL-value Map)) ) ) ) ) ) ) + (de MAL-dissoc @ (let (Map (next) Args (rest)) (MAL-map @@ -136,7 +148,7 @@ (vector . `(MAL-fn '(@ (MAL-vector (rest))))) (hash-map . `(MAL-fn '(@ (MAL-map (rest))))) - (assoc . `(MAL-fn '(@ (let (Map (next) Args (rest)) (MAL-map (append (MAL-value Map) Args)))))) + (assoc . `(MAL-fn MAL-assoc)) (dissoc . `(MAL-fn MAL-dissoc)) (get . `(MAL-fn '((Map Key) (or (and (<> (MAL-type Map) 'nil) (cdr (find '((X) (MAL-= (car X) Key)) (chunk (MAL-value Map))))) *MAL-nil)))) (contains? . `(MAL-fn '((Map Key) (if (find '((X) (MAL-= (car X) Key)) (chunk (MAL-value Map))) *MAL-true *MAL-false)))) From 0e508fa51801b451e6a8968c6707eef13f5a603e Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sun, 23 Oct 2016 22:18:08 -0500 Subject: [PATCH 0182/2308] Basic: add read-file. Misc basicpp space savings. - Add read-file which is similar to read-string but from a file name rather than a string. This allows steps 0-2 to load although each one eventuall crashes with out of memory after evaluating "123" a few times. - basicpp: - Renumber the line numbers so they are ordinally increasing. This saves 150 or so bytes because GOTO/GOSUB calls have smaller line numbers. - Shrink 'IF 123' -> 'IF123' for almost 300 byte savings.[:w - Simplify PR_MEMORY_SUMMARY output. Save 75 bytes - Add missing runtest.py change that allows basic tests to pass. --- basic/basicpp.py | 64 +++++++++++++++++++++---------- basic/core.in.bas | 11 +++++- basic/debug.in.bas | 15 ++++---- basic/reader.in.bas | 76 ++++++++++++++++++++++++++----------- basic/step4_if_fn_do.in.bas | 4 +- basic/step5_tco.in.bas | 4 +- basic/step6_file.in.bas | 7 ++-- basic/step7_quote.in.bas | 7 ++-- basic/step8_macros.in.bas | 7 ++-- basic/step9_try.in.bas | 7 ++-- basic/stepA_mal.in.bas | 7 ++-- basic/types.in.bas | 2 +- basic/variables.txt | 35 +++++++++++++---- core.mal | 1 + runtest.py | 1 + 15 files changed, 163 insertions(+), 85 deletions(-) diff --git a/basic/basicpp.py b/basic/basicpp.py index 59d004fb3d..fcfc864fb2 100755 --- a/basic/basicpp.py +++ b/basic/basicpp.py @@ -81,7 +81,9 @@ def remove_indent(orig_lines): def misc_fixups(orig_lines): text = "\n".join(orig_lines) - text = re.sub(r"\bTHEN GOTO\b", r"THEN", text) + text = re.sub(r"\bTHEN GOTO\b", "THEN", text) + text = re.sub(r"\bPRINT \"", "PRINT\"", text) + text = re.sub(r"\bIF ", "IF", text) return text.split("\n") def finalize(lines, args): @@ -103,47 +105,60 @@ def finalize(lines, args): lines.append("%s %s" % (lnum, line)) lnum += 1 + def update_labels_lines(text, a,b): + stext = "" + while stext != text: + stext = text + text = re.sub(r"(THEN) %s\b" % a, r"THEN %s" % b, stext) + #text = re.sub(r"(THEN)%s\b" % a, r"THEN%s" % b, stext) + text = re.sub(r"(ON [^:]* GOTO [^:\n]*)\b%s\b" % a, r"\g<1>%s" % b, text) + text = re.sub(r"(ON [^:]* GOSUB [^:\n]*)\b%s\b" % a, r"\g<2>%s" % b, text) + text = re.sub(r"(GOSUB) %s\b" % a, r"\1 %s" % b, text) + text = re.sub(r"(GOTO) %s\b" % a, r"\1 %s" % b, text) + #text = re.sub(r"(GOTO)%s\b" % a, r"\1%s" % b, text) + return text + if not args.keep_labels: src_lines = lines text = "\n".join(lines) # search for and replace GOTO/GOSUBs for label, lnum in labels_lines.items(): - stext = "" - while stext != text: - stext = text - text = re.sub(r"(THEN) %s\b" % label, r"THEN %s" % lnum, stext) - text = re.sub(r"(ON [^:]* GOTO [^:]*)\b%s\b" % label, r"\g<1>%s" % lnum, text) - text = re.sub(r"(ON [^:]* GOSUB [^:]*)\b%s\b" % label, r"\g<2>%s" % lnum, text) - text = re.sub(r"(GOSUB) %s\b" % label, r"\1 %s" % lnum, text) - text = re.sub(r"(GOTO) %s\b" % label, r"\1 %s" % lnum, text) + text = update_labels_lines(text, label, lnum) lines = text.split("\n") if args.combine_lines: + renumber = {} src_lines = lines lines = [] pos = 0 acc_line = "" + def renum(line): + lnum = len(lines)+1 + renumber[old_num] = lnum + return "%s %s" % (lnum, line) while pos < len(src_lines): line = src_lines[pos] # TODO: handle args.keep_labels and (not args.number_lines) m = re.match(r"^([0-9]*) (.*)$", line) - lnum = int(m.group(1)) - rest_line = m.group(2) + old_num = int(m.group(1)) + line = m.group(2) if acc_line == "": # Starting a new line - acc_line = line - elif lnum in lines_labels: - # This is a GOTO/GOSUB target line so it must be on - # a line by itself + acc_line = renum(line) + elif old_num in lines_labels or re.match(r"^ *FOR\b.*", line): + # This is a GOTO/GOSUB target or FOR loop so it must + # be on a line by itself lines.append(acc_line) - acc_line = line + acc_line = renum(line) elif re.match(r".*\b(?:GOTO|THEN|RETURN)\b.*", acc_line): + # GOTO/THEN/RETURN are last thing on the line lines.append(acc_line) - acc_line = line - elif len(acc_line) + 1 + len(rest_line) < 80: + acc_line = renum(line) + # TODO: not sure why this is 88 rather than 80 + elif len(acc_line) + 1 + len(line) < 88: # Continue building up the line - acc_line = acc_line + ":" + rest_line + acc_line = acc_line + ":" + line # GOTO/IF/RETURN must be the last things on a line so # start a new line if re.match(r".*\b(?:GOTO|THEN|RETURN)\b.*", line): @@ -152,11 +167,20 @@ def finalize(lines, args): else: # Too long so start a new line lines.append(acc_line) - acc_line = line + acc_line = renum(line) pos += 1 if acc_line != "": lines.append(acc_line) + # Finally renumber GOTO/GOSUBS + src_lines = lines + text = "\n".join(lines) + # search for and replace GOTO/GOSUBs + for a in sorted(renumber.keys()): + b = renumber[a] + text = update_labels_lines(text, a, b) + lines = text.split("\n") + return lines diff --git a/basic/core.in.bas b/basic/core.in.bas index 3c4f2420e5..0cff8dc918 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -9,7 +9,7 @@ DO_FUNCTION: R=Z%(AR,1)+1:GOSUB DEREF_R:AB=R REM Switch on the function number - IF FF>58 THEN ER=-1:ER$="unknown function"+STR$(FF):RETURN + IF FF>59 THEN ER=-1:ER$="unknown function"+STR$(FF):RETURN ON FF/10+1 GOTO DO_1_9,DO_10_19,DO_20_29,DO_30_39,DO_40_49,DO_50_59 DO_1_9: @@ -23,7 +23,7 @@ DO_FUNCTION: DO_40_49: ON FF-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_APPLY,DO_MAP,DO_THROW DO_50_59: - ON FF-49 GOTO DO_THROW,DO_WITH_META,DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_SWAP_BANG,DO_EVAL + ON FF-49 GOTO DO_THROW,DO_WITH_META,DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_SWAP_BANG,DO_EVAL,DO_READ_FILE DO_EQUAL_Q: A=AA:B=AB:GOSUB EQUAL_Q @@ -448,6 +448,11 @@ DO_FUNCTION: A=AA:E=D:GOSUB EVAL RETURN + DO_READ_FILE: + A$=S$(Z%(AA,1)) + GOSUB READ_FILE + RETURN + INIT_CORE_SET_FUNCTION: GOSUB NATIVE_FUNCTION V=R:GOSUB ENV_SET_S @@ -521,4 +526,6 @@ INIT_CORE_NS: K$="eval":A=58:GOSUB INIT_CORE_SET_FUNCTION + K$="read-file":A=59:GOSUB INIT_CORE_SET_FUNCTION + RETURN diff --git a/basic/debug.in.bas b/basic/debug.in.bas index cafc3186e9..7f879e48e4 100644 --- a/basic/debug.in.bas +++ b/basic/debug.in.bas @@ -14,15 +14,14 @@ CHECK_FREE_LIST: RETURN PR_MEMORY_SUMMARY: - GOSUB CHECK_FREE_LIST: REM get count in P2 PRINT - PRINT "Free memory (FRE) : "+STR$(FRE(0)) - PRINT "Value memory (Z%) : "+STR$(ZI-1)+" /"+STR$(Z1) - PRINT " "; - PRINT " used:"+STR$(ZI-1-P2)+", freed:"+STR$(P2); - PRINT ", post repl_env:"+STR$(ZT) - PRINT "String values (S$) : "+STR$(S)+" /"+STR$(Z2) - PRINT "Call stack size (X%) : "+STR$(X+1)+" /"+STR$(Z3) + PRINT "Free (FRE) :"+STR$(FRE(0)) + PRINT "Values (Z%) :"+STR$(ZI-1)+" /"+STR$(Z1) + GOSUB CHECK_FREE_LIST: REM get count in P2 + PRINT " used:"+STR$(ZI-1-P2)+", freed:"+STR$(P2); + PRINT ", after repl_env:"+STR$(ZT) + PRINT "Strings (S$) :"+STR$(S)+" /"+STR$(Z2) + PRINT "Stack (X%) :"+STR$(X+1)+" /"+STR$(Z3) RETURN REM REM PR_MEMORY(P1, P2) -> nil diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 2db435d7c2..3779ea1e89 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -1,42 +1,63 @@ -REM READ_TOKEN(A$, IDX) -> T$ +REM READ_TOKEN(A$, RI, RF) -> T$ READ_TOKEN: - CUR=IDX - REM PRINT "READ_TOKEN: "+STR$(CUR)+", "+MID$(A$,CUR,1) - T$=MID$(A$,CUR,1) + RJ=RI + IF RF=1 THEN GOSUB READ_FILE_CHUNK + REM PRINT "READ_TOKEN: "+STR$(RJ)+", "+MID$(A$,RJ,1) + T$=MID$(A$,RJ,1) IF T$="(" OR T$=")" OR T$="[" OR T$="]" OR T$="{" OR T$="}" THEN RETURN IF T$="'" OR T$="`" OR T$="@" THEN RETURN - IF T$="~" AND NOT MID$(A$,CUR+1,1)="@" THEN RETURN + IF T$="~" AND NOT MID$(A$,RJ+1,1)="@" THEN RETURN S1=0:S2=0: REM S1: INSTRING?, S2: ESCAPED? IF T$=CHR$(34) THEN S1=1 - CUR=CUR+1 + RJ=RJ+1 READ_TOKEN_LOOP: - IF CUR>LEN(A$) THEN RETURN - CH$=MID$(A$,CUR,1) + IF RF=1 THEN GOSUB READ_FILE_CHUNK + IF RJ>LEN(A$) THEN RETURN + CH$=MID$(A$,RJ,1) IF S2 THEN GOTO READ_TOKEN_CONT IF S1 THEN GOTO READ_TOKEN_CONT IF CH$=" " OR CH$="," THEN RETURN + IF CH$=" " OR CH$="," OR CH$=CHR$(13) OR CH$=CHR$(10) THEN RETURN IF CH$="(" OR CH$=")" OR CH$="[" OR CH$="]" OR CH$="{" OR CH$="}" THEN RETURN READ_TOKEN_CONT: T$=T$+CH$ IF T$="~@" THEN RETURN - CUR=CUR+1 + RJ=RJ+1 IF S1 AND S2 THEN S2=0:GOTO READ_TOKEN_LOOP IF S1 AND S2=0 AND CH$=CHR$(92) THEN S2=1:GOTO READ_TOKEN_LOOP IF S1 AND S2=0 AND CH$=CHR$(34) THEN RETURN GOTO READ_TOKEN_LOOP +READ_FILE_CHUNK: + IF RS=1 THEN RETURN + IF RI>1 THEN A$=MID$(A$,RI,LEN(A$)-RI+1):RI=1:RJ=RJ-RI+1 + READ_FILE_CHUNK_LOOP: + IF LEN(A$)>RJ+9 THEN RETURN + GET#2,C$:A$=A$+C$ + IF (ST AND 64) THEN RS=1:A$=A$+CHR$(10)+")":RETURN + IF (ST AND 255) THEN RS=1:ER=-1:ER$="File read error "+STR$(ST):RETURN + GOTO READ_FILE_CHUNK_LOOP + SKIP_SPACES: - CH$=MID$(A$,IDX,1) - IF (CH$<>" ") AND (CH$<>",") AND (CH$<>CHR$(13)) AND (CH$<>CHR$(10)) THEN RETURN - IDX=IDX+1 + IF RF=1 THEN GOSUB READ_FILE_CHUNK + CH$=MID$(A$,RI,1) + IF CH$<>" " AND CH$<>"," AND CH$<>CHR$(13) AND CH$<>CHR$(10) THEN RETURN + RI=RI+1 GOTO SKIP_SPACES +SKIP_TO_EOL: + IF RF=1 THEN GOSUB READ_FILE_CHUNK + CH$=MID$(A$,RI+1,1) + RI=RI+1 + IF CH$="" OR CH$=CHR$(13) OR CH$=CHR$(10) THEN RETURN + GOTO SKIP_TO_EOL + READ_ATOM: R=0 RETURN -REM READ_FORM(A$, IDX) -> R +REM READ_FORM(A$, RI, RF) -> R READ_FORM: IF ER<>-2 THEN RETURN GOSUB SKIP_SPACES @@ -55,7 +76,7 @@ READ_FORM: IF T$="@" THEN AS$="deref":GOTO READ_MACRO CH$=MID$(T$,1,1) REM PRINT "CH$: ["+CH$+"]("+STR$(ASC(CH$))+")" - IF (CH$=";") THEN R=0:GOTO READ_TO_EOL + IF (CH$=";") THEN R=0:GOSUB SKIP_TO_EOL:GOTO READ_FORM IF CH$>="0" AND CH$<="9" THEN GOTO READ_NUMBER IF CH$="-" THEN GOTO READ_SYMBOL_MAYBE @@ -69,11 +90,6 @@ READ_FORM: IF CH$="}" THEN T=8:GOTO READ_SEQ_END GOTO READ_SYMBOL - READ_TO_EOL: - CH$=MID$(A$,IDX+1,1) - IDX=IDX+1 - IF CH$="" OR CH$=CHR$(13) OR CH$=CHR$(10) THEN GOTO READ_FORM - GOTO READ_TO_EOL READ_NIL_BOOL: REM PRINT "READ_NIL_BOOL" R=T @@ -84,7 +100,7 @@ READ_FORM: T=2:L=VAL(T$):GOSUB ALLOC GOTO READ_FORM_DONE READ_MACRO: - IDX=IDX+LEN(T$) + RI=RI+LEN(T$) REM to call READ_FORM recursively, SD needs to be saved, set to REM 0 for the call and then restored afterwards. X=X+2:X%(X-1)=(T$="^"):X%(X)=SD: REM push macro type and SD @@ -156,7 +172,7 @@ READ_FORM: X=X+1 X%(X)=R - IDX=IDX+LEN(T$) + RI=RI+LEN(T$) GOTO READ_FORM READ_SEQ_END: @@ -171,7 +187,7 @@ READ_FORM: READ_FORM_DONE: - IDX=IDX+LEN(T$) + RI=RI+LEN(T$) T8=R: REM save previous value @@ -211,7 +227,21 @@ READ_FORM: REM READ_STR(A$) -> R READ_STR: - IDX=1 + RI=1: REM index into A$ + RF=0: REM not reading from file SD=0: REM sequence read depth GOSUB READ_FORM RETURN + +REM READ_FILE(A$) -> R +READ_FILE: + RI=1: REM index into A$ + RJ=1: REM READ_TOKEN sub-index + RF=1: REM reading from file + RS=0: REM file read state (1: EOF) + SD=0: REM sequence read depth + OPEN 2,8,0,A$ + REM READ_FILE_CHUNK adds terminating ")" + A$="(do ":GOSUB READ_FORM + CLOSE 2 + RETURN diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index 0421f8ad28..ed61505756 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -337,11 +337,11 @@ RE: R1=0 GOSUB MAL_READ R1=R - IF ER<>-2 THEN GOTO REP_DONE + IF ER<>-2 THEN GOTO RE_DONE A=R:E=D:GOSUB EVAL - REP_DONE: + RE_DONE: REM Release memory from MAL_READ IF R1<>0 THEN AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index 8ddce071de..cc96817431 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -346,11 +346,11 @@ RE: R1=0 GOSUB MAL_READ R1=R - IF ER<>-2 THEN GOTO REP_DONE + IF ER<>-2 THEN GOTO RE_DONE A=R:E=D:GOSUB EVAL - REP_DONE: + RE_DONE: REM Release memory from MAL_READ IF R1<>0 THEN AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index aed3b6058e..d06e168a12 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -346,11 +346,11 @@ RE: R1=0 GOSUB MAL_READ R1=R - IF ER<>-2 THEN GOTO REP_DONE + IF ER<>-2 THEN GOTO RE_DONE A=R:E=D:GOSUB EVAL - REP_DONE: + RE_DONE: REM Release memory from MAL_READ IF R1<>0 THEN AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL @@ -395,8 +395,7 @@ MAIN: A$="(def! not (fn* (a) (if a false true)))" GOSUB RE:AY=R:GOSUB RELEASE - A$="(def! load-file (fn* (f) (eval (read-string (str " - A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))" + A$="(def! load-file (fn* (f) (eval (read-file f))))" GOSUB RE:AY=R:GOSUB RELEASE REM load the args file diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index 7bf085bd54..80aab0398f 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -436,11 +436,11 @@ RE: R1=0 GOSUB MAL_READ R1=R - IF ER<>-2 THEN GOTO REP_DONE + IF ER<>-2 THEN GOTO RE_DONE A=R:E=D:GOSUB EVAL - REP_DONE: + RE_DONE: REM Release memory from MAL_READ IF R1<>0 THEN AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL @@ -485,8 +485,7 @@ MAIN: A$="(def! not (fn* (a) (if a false true)))" GOSUB RE:AY=R:GOSUB RELEASE - A$="(def! load-file (fn* (f) (eval (read-string (str " - A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))" + A$="(def! load-file (fn* (f) (eval (read-file f))))" GOSUB RE:AY=R:GOSUB RELEASE REM load the args file diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index 86741154ac..0b52a438d4 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -505,11 +505,11 @@ RE: R1=0 GOSUB MAL_READ R1=R - IF ER<>-2 THEN GOTO REP_DONE + IF ER<>-2 THEN GOTO RE_DONE A=R:E=D:GOSUB EVAL - REP_DONE: + RE_DONE: REM Release memory from MAL_READ IF R1<>0 THEN AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL @@ -554,8 +554,7 @@ MAIN: A$="(def! not (fn* (a) (if a false true)))" GOSUB RE:AY=R:GOSUB RELEASE - A$="(def! load-file (fn* (f) (eval (read-string (str " - A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))" + A$="(def! load-file (fn* (f) (eval (read-file f))))" GOSUB RE:AY=R:GOSUB RELEASE A$="(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs)" diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index a7c50cc00c..024ebf078d 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -537,11 +537,11 @@ RE: R1=0 GOSUB MAL_READ R1=R - IF ER<>-2 THEN GOTO REP_DONE + IF ER<>-2 THEN GOTO RE_DONE A=R:E=D:GOSUB EVAL - REP_DONE: + RE_DONE: REM Release memory from MAL_READ IF R1<>0 THEN AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL @@ -586,8 +586,7 @@ MAIN: A$="(def! not (fn* (a) (if a false true)))" GOSUB RE:AY=R:GOSUB RELEASE - A$="(def! load-file (fn* (f) (eval (read-string (str " - A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))" + A$="(def! load-file (fn* (f) (eval (read-file f))))" GOSUB RE:AY=R:GOSUB RELEASE A$="(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs)" diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index c953e5fdb4..b5b5b57de5 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -537,11 +537,11 @@ RE: R1=0 GOSUB MAL_READ R1=R - IF ER<>-2 THEN GOTO REP_DONE + IF ER<>-2 THEN GOTO RE_DONE A=R:E=D:GOSUB EVAL - REP_DONE: + RE_DONE: REM Release memory from MAL_READ IF R1<>0 THEN AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL @@ -589,8 +589,7 @@ MAIN: A$="(def! not (fn* (a) (if a false true)))" GOSUB RE:AY=R:GOSUB RELEASE - A$="(def! load-file (fn* (f) (eval (read-string (str " - A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))" + A$="(def! load-file (fn* (f) (eval (read-file f))))" GOSUB RE:AY=R:GOSUB RELEASE A$="(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs)" diff --git a/basic/types.in.bas b/basic/types.in.bas index 174406d475..bc3483c840 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -27,7 +27,7 @@ REM 14 -> Z% index of metdata object INIT_MEMORY: T=FRE(0) - Z1=2048+512: REM Z% (boxed memory) size (4 bytes each) + Z1=2048+1024: REM Z% (boxed memory) size (4 bytes each) Z2=256: REM S$ (string memory) size (3 bytes each) Z3=256: REM X% (call stack) size (2 bytes each) Z4=64: REM Y% (release stack) size (4 bytes each) diff --git a/basic/variables.txt b/basic/variables.txt index ade67d6983..c64e68b696 100644 --- a/basic/variables.txt +++ b/basic/variables.txt @@ -23,7 +23,8 @@ BI : ENV_NEW_BINDS binds list EX : ENV_NEW_BINDS expressions list LV : EVAL stack call level/depth -IDX : reader current string position +RI : reader current string position +RJ : READ_TOKEN current character index Calling arguments/temporaries: @@ -45,15 +46,10 @@ R : common return value T : common temp, type V : hash map value -A0 : EVAL ast elements -A1 : EVAL ast elements -A2 : EVAL ast elements -A3 : EVAL ast elements B1 : LIST2/LIST3 param B2 : LIST2/LIST3 param B3 : LIST3 param CZ : DO_CONCAT stack position -CUR : READ_TOKEN current character index EF : ENV_FIND cur env ptr P1 : PR_MEMORY, CHECK_FREE_LIST start P2 : PR_MEMORY, CHECK_FREE_LIST end @@ -61,9 +57,34 @@ SZ : size argument to ALLOC Reused/temporaries: +A0 : EVAL ast elements +A1 : EVAL ast elements +A2 : EVAL ast elements +A3 : EVAL ast elements +CH$ : READ_TOKEN, SKIP_SPACES, SKIP_TO_EOL current character I : STRING, REPLACE, SLICE, PR_MEMORY, PR_OBJECT J : REPLACE +S1 : READ_TOKEN in a string? +S2 : READ_TOKEN escaped? +T$ : READ_* current token string +T1$ : HASHMAP_GET temp +T2$ : HASHMAP_GET temp +T1 : PR_STR, and core DO_KEYS_VALS temp +T2 : +T3 : +T4 : +T5 : +T6 : +T7 : READ_FORM and PR_STR temp +T8 : +T9 : +TA : +U1 : +U2 : +U3 : +U4 : +U6 : Unused: -G, L, M, N, Q, U, W +G, Q, U, W diff --git a/core.mal b/core.mal index ae9ec63afa..368805f4c6 100644 --- a/core.mal +++ b/core.mal @@ -84,3 +84,4 @@ (list form x)) `(->> (->> ~x ~form) ~@more)))))) +nil diff --git a/runtest.py b/runtest.py index 71ff268d10..eee1fcc728 100755 --- a/runtest.py +++ b/runtest.py @@ -127,6 +127,7 @@ def read_to_prompt(self, prompts, timeout): self.buf += new_data.replace("\n", "\r\n") else: self.buf += new_data + self.buf = self.buf.replace("\r\r", "\r") for prompt in prompts: regexp = re.compile(prompt) match = regexp.search(self.buf) From 3c7b63d2e141874649b5221941cba5c110e6c3b4 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 24 Oct 2016 21:49:07 +0200 Subject: [PATCH 0183/2308] Ensure assoc updates maps properly --- tests/step9_try.mal | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/step9_try.mal b/tests/step9_try.mal index 254aed07de..d7aa98675d 100644 --- a/tests/step9_try.mal +++ b/tests/step9_try.mal @@ -260,6 +260,15 @@ (keyword? (nth (vals {"a" :abc "b" :def}) 0)) ;=>true +;; Testing whether assoc updates properly +(def! hm4 (assoc {:a 1 :b 2} :a 3 :c 1)) +(get hm4 :a) +;=>3 +(get hm4 :b) +;=>2 +(get hm4 :c) +;=>1 + ;; Testing nil as hash-map values (contains? {:abc nil} :abc) ;=>true From fc06744f59c8787c54ab6ac8270a57f30b7ed0c9 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 24 Oct 2016 21:39:22 +0200 Subject: [PATCH 0184/2308] Emacs 25.1 fixes --- elisp/reader.el | 5 +++++ elisp/tests/stepA_mal.mal | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/elisp/reader.el b/elisp/reader.el index 26d8361199..e63b1aaced 100644 --- a/elisp/reader.el +++ b/elisp/reader.el @@ -1,3 +1,8 @@ +;; HACK: `text-quoting-style' prettifies quotes in error messages on +;; Emacs 25.1, but no longer does from 25.2 upwards... +(when (and (= emacs-major-version 25) (= emacs-minor-version 1)) + (setq text-quoting-style 'grave)) + (defvar tokens nil) (defun peek () diff --git a/elisp/tests/stepA_mal.mal b/elisp/tests/stepA_mal.mal index a6c9bca187..8f6e9a3b73 100644 --- a/elisp/tests/stepA_mal.mal +++ b/elisp/tests/stepA_mal.mal @@ -16,6 +16,6 @@ ; Hello World! ;=>nil -(elisp-eval "(setq emacs-version-re (rx \"24.\" digit \".\" digit))") +(elisp-eval "(setq emacs-version-re (rx (+ digit) \".\" digit \".\" digit))") (elisp-eval "(and (string-match-p emacs-version-re emacs-version) t)") ;=>true From 082158f9ee7434ba535c59d25df4c9c6c2c0b176 Mon Sep 17 00:00:00 2001 From: Chris McCormick Date: Sun, 16 Oct 2016 19:14:06 +0800 Subject: [PATCH 0185/2308] Allow web exec of a .mal file via symlinked php script. ln -s mal.php myscript.php Then create myscript.mal will get run when the user visits /myscript on a PHP capable webserver. --- php/readline.php | 61 +++++++++++++++++++++++++---------------------- php/stepA_mal.php | 13 ++++++++-- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/php/readline.php b/php/readline.php index a31210993a..8b3d28b9cb 100644 --- a/php/readline.php +++ b/php/readline.php @@ -1,38 +1,41 @@ diff --git a/php/stepA_mal.php b/php/stepA_mal.php index 3292645b97..05d63afe58 100644 --- a/php/stepA_mal.php +++ b/php/stepA_mal.php @@ -197,8 +197,10 @@ function rep($str) { global $repl_env; return MAL_EVAL($ast, $repl_env); })); $_argv = _list(); -for ($i=2; $i < count($argv); $i++) { - $_argv->append($argv[$i]); +if (isset($argv)) { + for ($i=2; $i < count($argv); $i++) { + $_argv->append($argv[$i]); + } } $repl_env->set(_symbol('*ARGV*'), $_argv); @@ -211,6 +213,13 @@ function rep($str) { rep("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))"); rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))"); +// if we're called in a webserver context, auto-resolve to mal file +if (php_sapi_name() != "cli") { + $malfile = str_replace(".php", ".mal", $_SERVER['SCRIPT_FILENAME']); + rep('(load-file "' . $malfile . '")'); + exit(0); +} + if (count($argv) > 1) { rep('(load-file "' . $argv[1] . '")'); exit(0); From 01e8850d434c79db6dc083d5ff6aada9b419de13 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 24 Oct 2016 23:29:27 -0500 Subject: [PATCH 0186/2308] Basic: Reduce GOSUB use. Partial self-host to step3 step4 runs out of space attempting to load the program. Step2 and step3 run out of memory (stack exhaustion) for more complicated forms. - Use GOTO with return label on our stack instead of GOSUB for: - APPLY function in types.in.bas - "apply", "map" and "swap!" core functions - Implement DO TCO. Change EVAL_AST to detect if we are called from DO and exit one element early. - Remove GOSUB recursion from EQUAL_Q - Inline PAIR_Q. Reduce REPLACE stack use. - Remove one level of GOSUB/stack by calling REP with GOTO - Simplify mal/step2_eval.mal to remove use of (or ) macro in eval_ast. - Fix ON GOTO/GOSUB line detection in basicpp --- basic/basicpp.py | 4 +- basic/core.in.bas | 339 +++++++++++++++++++++++------------- basic/run | 5 +- basic/step4_if_fn_do.in.bas | 10 +- basic/step5_tco.in.bas | 38 ++-- basic/step6_file.in.bas | 40 +++-- basic/step7_quote.in.bas | 40 +++-- basic/step8_macros.in.bas | 46 +++-- basic/step9_try.in.bas | 46 +++-- basic/stepA_mal.in.bas | 68 +++++--- basic/types.in.bas | 91 +++++----- basic/variables.txt | 6 +- mal/step2_eval.mal | 4 +- 13 files changed, 465 insertions(+), 272 deletions(-) diff --git a/basic/basicpp.py b/basic/basicpp.py index fcfc864fb2..c122cf9d00 100755 --- a/basic/basicpp.py +++ b/basic/basicpp.py @@ -111,8 +111,8 @@ def update_labels_lines(text, a,b): stext = text text = re.sub(r"(THEN) %s\b" % a, r"THEN %s" % b, stext) #text = re.sub(r"(THEN)%s\b" % a, r"THEN%s" % b, stext) - text = re.sub(r"(ON [^:]* GOTO [^:\n]*)\b%s\b" % a, r"\g<1>%s" % b, text) - text = re.sub(r"(ON [^:]* GOSUB [^:\n]*)\b%s\b" % a, r"\g<2>%s" % b, text) + text = re.sub(r"(ON [^:\n]* GOTO [^:\n]*)\b%s\b" % a, r"\g<1>%s" % b, text) + text = re.sub(r"(ON [^:\n]* GOSUB [^:\n]*)\b%s\b" % a, r"\g<2>%s" % b, text) text = re.sub(r"(GOSUB) %s\b" % a, r"\1 %s" % b, text) text = re.sub(r"(GOTO) %s\b" % a, r"\1 %s" % b, text) #text = re.sub(r"(GOTO)%s\b" % a, r"\1%s" % b, text) diff --git a/basic/core.in.bas b/basic/core.in.bas index 0cff8dc918..5e0bc876fc 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -1,3 +1,191 @@ +REM APPLY should really be in types.in.bas but it is here because it +REM has return labels into DO_TCO_FUNCTION so it will cause syntax +REM errors for steps1-3 if it is in types.in.bas because there are +REM unresolved labels. + +REM APPLY(F, AR) -> R +REM - restores E +REM - call using GOTO and with return label/address on the stack +APPLY: + REM if metadata, get the actual object + IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + + IF (Z%(F,0)AND31)=9 THEN GOTO APPLY_FUNCTION + IF (Z%(F,0)AND31)=10 THEN GOTO APPLY_MAL_FUNCTION + IF (Z%(F,0)AND31)=11 THEN GOTO APPLY_MAL_FUNCTION + + APPLY_FUNCTION: + REM regular function + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_APPLY + REM for recur functions (apply, map, swap!), use GOTO + IF Z%(F,1)>60 THEN X=X+1:X%(X)=1:GOTO DO_TCO_FUNCTION + DO_TCO_FUNCTION_RETURN_APPLY: + GOTO APPLY_DONE + + APPLY_MAL_FUNCTION: + X=X+1:X%(X)=E: REM save the current environment + + REM create new environ using env and params stored in the + REM function and bind the params to the apply arguments + O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS + + A=Z%(F,1):E=R:GOSUB EVAL + + AY=E:GOSUB RELEASE: REM release the new environment + + E=X%(X):X=X-1: REM pop/restore the saved environment + + APPLY_DONE: + REM pop APPLY return label/address + RN=X%(X):X=X-1 + ON RN GOTO APPLY_RETURN_1,APPLY_RETURN_2,APPLY_RETURN_MAP,APPLY_RETURN_SWAP,APPLY_RETURN_MACROEXPAND + + +REM DO_TCO_FUNCTION(F, AR) +REM - similar to DO_FUNCTION but non-GOSUB version for potentially +REM recursive function (apply, map, swap!) +DO_TCO_FUNCTION: + FF=Z%(F,1) + + REM Get argument values + R=AR+1:GOSUB DEREF_R:AA=R + R=Z%(AR,1)+1:GOSUB DEREF_R:AB=R + + ON FF-60 GOTO DO_APPLY,DO_MAP,DO_SWAP_BANG + + DO_APPLY: + F=AA + AR=Z%(AR,1) + A=AR:GOSUB COUNT:R4=R + + A=Z%(AR+1,1) + REM no intermediate args, but not a list, so convert it first + IF R4<=1 AND (Z%(A,0)AND31)<>6 THEN T=6:GOSUB FORCE_SEQ_TYPE:GOTO DO_APPLY_2 + REM no intermediate args, just call APPLY directly + IF R4<=1 THEN GOTO DO_APPLY_1 + + REM prepend intermediate args to final args element + A=AR:B=0:C=R4-1:GOSUB SLICE + REM release the terminator of new list (we skip over it) + AY=Z%(R6,1):GOSUB RELEASE + REM attach end of slice to final args element + Z%(R6,1)=Z%(A+1,1) + Z%(Z%(A+1,1),0)=Z%(Z%(A+1,1),0)+32 + + GOTO DO_APPLY_2 + + DO_APPLY_1: + X=X+1:X%(X)=1: REM push APPLY return label/address + AR=A:GOTO APPLY + REM APPLY return label/address popped by APPLY + APPLY_RETURN_1: + + GOTO DO_TCO_FUNCTION_RETURN + + DO_APPLY_2: + X=X+1:X%(X)=R: REM push/save new args for release + + X=X+1:X%(X)=2: REM push APPLY return label/address + AR=R:GOTO APPLY + REM APPLY return label/address popped by APPLY + APPLY_RETURN_2: + + AY=X%(X):X=X-1:GOSUB RELEASE: REM pop/release new args + GOTO DO_TCO_FUNCTION_RETURN + + DO_MAP: + F=AA + + REM first result list element + T=6:L=0:N=0:GOSUB ALLOC + + REM push future return val, prior entry, F and AB + X=X+4:X%(X-3)=R:X%(X-2)=0:X%(X-1)=F:X%(X)=AB + + DO_MAP_LOOP: + REM set previous to current if not the first element + IF X%(X-2)<>0 THEN Z%(X%(X-2),1)=R + REM update previous reference to current + X%(X-2)=R + + IF Z%(AB,1)=0 THEN GOTO DO_MAP_DONE + + REM create argument list for apply call + Z%(3,0)=Z%(3,0)+32 + REM inc ref cnt of referred argument + T=6:L=3:N=Z%(AB+1,1):GOSUB ALLOC + + REM push argument list + X=X+1:X%(X)=R + + X=X+1:X%(X)=3: REM push APPLY return label/address + AR=R:GOTO APPLY + REM APPLY return label/address popped by APPLY + APPLY_RETURN_MAP: + + REM pop apply args are release them + AY=X%(X):X=X-1:GOSUB RELEASE + + REM set the result value + Z%(X%(X-2)+1,1)=R + + REM restore F + F=X%(X-1) + + REM update AB to next source element + X%(X)=Z%(X%(X),1) + AB=X%(X) + + REM allocate next element + T=6:L=0:N=0:GOSUB ALLOC + + GOTO DO_MAP_LOOP + + DO_MAP_DONE: + REM get return val + R=X%(X-3) + REM pop everything off stack + X=X-4 + GOTO DO_TCO_FUNCTION_RETURN + + + DO_SWAP_BANG: + F=AB + + REM add atom to front of the args list + T=6:L=Z%(Z%(AR,1),1):N=Z%(AA,1):GOSUB ALLOC: REM cons + AR=R + + REM push args for release after + X=X+1:X%(X)=AR + + REM push atom + X=X+1:X%(X)=AA + + X=X+1:X%(X)=4: REM push APPLY return label/address + GOTO APPLY + REM APPLY return label/address popped by APPLY + APPLY_RETURN_SWAP: + + REM pop atom + AA=X%(X):X=X-1 + + REM pop and release args + AY=X%(X):X=X-1:GOSUB RELEASE + + REM use reset to update the value + AB=R:GOSUB DO_RESET_BANG + + REM but decrease ref cnt of return by 1 (not sure why) + AY=R:GOSUB RELEASE + + GOTO DO_TCO_FUNCTION_RETURN + + DO_TCO_FUNCTION_RETURN: + REM pop EVAL AST return label/address + RN=X%(X):X=X-1 + ON RN GOTO DO_TCO_FUNCTION_RETURN_APPLY,DO_TCO_FUNCTION_RETURN_EVAL + REM DO_FUNCTION(F, AR) DO_FUNCTION: @@ -10,7 +198,7 @@ DO_FUNCTION: REM Switch on the function number IF FF>59 THEN ER=-1:ER$="unknown function"+STR$(FF):RETURN - ON FF/10+1 GOTO DO_1_9,DO_10_19,DO_20_29,DO_30_39,DO_40_49,DO_50_59 + ON FF/10+1 GOTO DO_1_9,DO_10_19,DO_20_29,DO_30_39,DO_40_49,DO_50_56 DO_1_9: ON FF GOTO DO_EQUAL_Q,DO_THROW,DO_NIL_Q,DO_TRUE_Q,DO_FALSE_Q,DO_STRING_Q,DO_SYMBOL,DO_SYMBOL_Q,DO_KEYWORD @@ -21,9 +209,9 @@ DO_FUNCTION: DO_30_39: ON FF-29 GOTO DO_VECTOR_Q,DO_HASH_MAP,DO_MAP_Q,DO_ASSOC,DO_THROW,DO_GET,DO_CONTAINS,DO_KEYS,DO_VALS,DO_SEQUENTIAL_Q DO_40_49: - ON FF-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_APPLY,DO_MAP,DO_THROW - DO_50_59: - ON FF-49 GOTO DO_THROW,DO_WITH_META,DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_SWAP_BANG,DO_EVAL,DO_READ_FILE + ON FF-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_THROW,DO_THROW,DO_WITH_META + DO_50_56: + ON FF-49 GOTO DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_EVAL,DO_READ_FILE DO_EQUAL_Q: A=AA:B=AB:GOSUB EQUAL_Q @@ -148,7 +336,7 @@ DO_FUNCTION: T=2:L=Z%(AA,1)/Z%(AB,1):GOSUB ALLOC RETURN DO_TIME_MS: - R=0 + T=2:L=INT((TI-BT)*16.667):GOSUB ALLOC RETURN DO_LIST: @@ -301,81 +489,6 @@ DO_FUNCTION: A=AA:GOSUB COUNT T=2:L=R:GOSUB ALLOC RETURN - DO_APPLY: - F=AA - AR=Z%(AR,1) - A=AR:GOSUB COUNT:R4=R - - A=Z%(AR+1,1) - REM no intermediate args, but not a list, so convert it first - IF R4<=1 AND (Z%(A,0)AND31)<>6 THEN T=6:GOSUB FORCE_SEQ_TYPE:GOTO DO_APPLY_2 - REM no intermediate args, just call APPLY directly - IF R4<=1 THEN AR=A:GOSUB APPLY:RETURN - - REM prepend intermediate args to final args element - A=AR:B=0:C=R4-1:GOSUB SLICE - REM release the terminator of new list (we skip over it) - AY=Z%(R6,1):GOSUB RELEASE - REM attach end of slice to final args element - Z%(R6,1)=Z%(A+1,1) - Z%(Z%(A+1,1),0)=Z%(Z%(A+1,1),0)+32 - - DO_APPLY_2: - X=X+1:X%(X)=R: REM push/save new args for release - AR=R:GOSUB APPLY - AY=X%(X):X=X-1:GOSUB RELEASE: REM pop/release new args - RETURN - DO_MAP: - F=AA - - REM first result list element - T=6:L=0:N=0:GOSUB ALLOC - - REM push future return val, prior entry, F and AB - X=X+4:X%(X-3)=R:X%(X-2)=0:X%(X-1)=F:X%(X)=AB - - DO_MAP_LOOP: - REM set previous to current if not the first element - IF X%(X-2)<>0 THEN Z%(X%(X-2),1)=R - REM update previous reference to current - X%(X-2)=R - - IF Z%(AB,1)=0 THEN GOTO DO_MAP_DONE - - REM create argument list for apply call - Z%(3,0)=Z%(3,0)+32 - REM inc ref cnt of referred argument - T=6:L=3:N=Z%(AB+1,1):GOSUB ALLOC - - REM push argument list - X=X+1:X%(X)=R - - AR=R:GOSUB APPLY - - REM pop apply args are release them - AY=X%(X):X=X-1:GOSUB RELEASE - - REM set the result value - Z%(X%(X-2)+1,1)=R - - REM restore F - F=X%(X-1) - - REM update AB to next source element - X%(X)=Z%(X%(X),1) - AB=X%(X) - - REM allocate next element - T=6:L=0:N=0:GOSUB ALLOC - - GOTO DO_MAP_LOOP - - DO_MAP_DONE: - REM get return val - R=X%(X-3) - REM pop everything off stack - X=X-4 - RETURN DO_WITH_META: T=Z%(AA,0)AND31 @@ -408,41 +521,13 @@ DO_FUNCTION: REM update value Z%(AA,1)=R RETURN - DO_SWAP_BANG: - F=AB - - REM add atom to front of the args list - T=6:L=Z%(Z%(AR,1),1):N=Z%(AA,1):GOSUB ALLOC: REM cons - AR=R - - REM push args for release after - X=X+1:X%(X)=AR - - REM push atom - X=X+1:X%(X)=AA - - GOSUB APPLY - - REM pop atom - AA=X%(X):X=X-1 - - REM pop and release args - AY=X%(X):X=X-1:GOSUB RELEASE - - REM use reset to update the value - AB=R:GOSUB DO_RESET_BANG - - REM but decrease ref cnt of return by 1 (not sure why) - AY=R:GOSUB RELEASE - - RETURN - DO_PR_MEMORY: - P1=ZT:P2=-1:GOSUB PR_MEMORY - RETURN - DO_PR_MEMORY_SUMMARY: - GOSUB PR_MEMORY_SUMMARY - RETURN + REM DO_PR_MEMORY: + REM P1=ZT:P2=-1:GOSUB PR_MEMORY + REM RETURN + REM DO_PR_MEMORY_SUMMARY: + REM GOSUB PR_MEMORY_SUMMARY + REM RETURN DO_EVAL: A=AA:E=D:GOSUB EVAL @@ -513,19 +598,23 @@ INIT_CORE_NS: K$="rest":A=44:GOSUB INIT_CORE_SET_FUNCTION K$="empty?":A=45:GOSUB INIT_CORE_SET_FUNCTION K$="count":A=46:GOSUB INIT_CORE_SET_FUNCTION - K$="apply":A=47:GOSUB INIT_CORE_SET_FUNCTION - K$="map":A=48:GOSUB INIT_CORE_SET_FUNCTION - K$="with-meta":A=51:GOSUB INIT_CORE_SET_FUNCTION - K$="meta":A=52:GOSUB INIT_CORE_SET_FUNCTION - K$="atom":A=53:GOSUB INIT_CORE_SET_FUNCTION - K$="atom?":A=54:GOSUB INIT_CORE_SET_FUNCTION - K$="deref":A=55:GOSUB INIT_CORE_SET_FUNCTION - K$="reset!":A=56:GOSUB INIT_CORE_SET_FUNCTION - K$="swap!":A=57:GOSUB INIT_CORE_SET_FUNCTION + REM K$="conj":A=47:GOSUB INIT_CORE_SET_FUNCTION + REM K$="seq":A=48:GOSUB INIT_CORE_SET_FUNCTION + + K$="with-meta":A=49:GOSUB INIT_CORE_SET_FUNCTION + K$="meta":A=50:GOSUB INIT_CORE_SET_FUNCTION + K$="atom":A=51:GOSUB INIT_CORE_SET_FUNCTION + K$="atom?":A=52:GOSUB INIT_CORE_SET_FUNCTION + K$="deref":A=53:GOSUB INIT_CORE_SET_FUNCTION + K$="reset!":A=54:GOSUB INIT_CORE_SET_FUNCTION - K$="eval":A=58:GOSUB INIT_CORE_SET_FUNCTION + K$="eval":A=55:GOSUB INIT_CORE_SET_FUNCTION + K$="read-file":A=56:GOSUB INIT_CORE_SET_FUNCTION - K$="read-file":A=59:GOSUB INIT_CORE_SET_FUNCTION + REM these are in DO_TCO_FUNCTION + K$="apply":A=61:GOSUB INIT_CORE_SET_FUNCTION + K$="map":A=62:GOSUB INIT_CORE_SET_FUNCTION + K$="swap!":A=63:GOSUB INIT_CORE_SET_FUNCTION RETURN diff --git a/basic/run b/basic/run index 6c2e48acb9..7fe8318cc2 100755 --- a/basic/run +++ b/basic/run @@ -1,3 +1,4 @@ #!/bin/bash -(echo "(list $(for a in "${@}"; do echo -n "\"${a}\""; done))") > $(dirname $0)/.args.mal -exec cbmbasic $(dirname $0)/${STEP:-stepA_mal}.bas "${@}" +cd $(dirname $0) +(echo "(list $(for a in "${@}"; do echo -n "\"${a}\""; done))") > .args.mal +exec cbmbasic ${STEP:-stepA_mal}.bas "${@}" diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index ed61505756..1b10b86acd 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -112,7 +112,6 @@ EVAL_AST: REM pop EVAL AST return label/address RN=X%(X):X=X-1 ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 - RETURN REM EVAL(A, E)) -> R EVAL: @@ -121,6 +120,8 @@ EVAL: REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A + REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0)) + EVAL_TCO_RECUR: REM AZ=A:PR=1:GOSUB PR_STR @@ -215,6 +216,7 @@ EVAL: REM push EVAL_AST return label/address X=X+1:X%(X)=2 GOTO EVAL_AST + REM return label/address already popped by EVAL_AST EVAL_AST_RETURN_2: X=X+1:X%(X)=R: REM push eval'd list @@ -276,7 +278,11 @@ EVAL: ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: - GOSUB DO_FUNCTION + REM regular function + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL + REM for recur functions (apply, map, swap!), use GOTO + IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION + DO_TCO_FUNCTION_RETURN_EVAL: REM pop and release f/args AY=X%(X):X=X-1:GOSUB RELEASE diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index cc96817431..731e18c025 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -60,6 +60,9 @@ EVAL_AST: REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM if we are returning to DO, then skip last element + IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM if hashmap, skip eval of even entries (keys) IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL @@ -112,7 +115,6 @@ EVAL_AST: REM pop EVAL AST return label/address RN=X%(X):X=X-1 ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 - RETURN REM EVAL(A, E)) -> R EVAL: @@ -121,6 +123,8 @@ EVAL: REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A + REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0)) + EVAL_TCO_RECUR: REM AZ=A:PR=1:GOSUB PR_STR @@ -218,19 +222,26 @@ EVAL: EVAL_DO: A=Z%(A,1): REM rest - - REM TODO: TCO + X=X+1:X%(X)=A: REM push/save A REM push EVAL_AST return label/address X=X+1:X%(X)=2 GOTO EVAL_AST + REM return label/address already popped by EVAL_AST EVAL_AST_RETURN_2: - X=X+1:X%(X)=R: REM push eval'd list - A=R:GOSUB LAST: REM return the last element - AY=X%(X):X=X-1: REM pop eval'd list - GOSUB RELEASE: REM release the eval'd list - GOTO EVAL_RETURN + REM cleanup + AY=R: REM get eval'd list for release + + A=X%(X):X=X-1: REM pop/restore original A for LAST + GOSUB LAST: REM get last element for return + A=R: REM new recur AST + + REM cleanup + GOSUB RELEASE: REM release eval'd list + AY=A:GOSUB RELEASE: REM release LAST value (not sure why) + + GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF: GOSUB EVAL_GET_A1: REM set A1 @@ -285,7 +296,11 @@ EVAL: ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: - GOSUB DO_FUNCTION + REM regular function + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL + REM for recur functions (apply, map, swap!), use GOTO + IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION + DO_TCO_FUNCTION_RETURN_EVAL: REM pop and release f/args AY=X%(X):X=X-1:GOSUB RELEASE @@ -375,7 +390,7 @@ REP: IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ - RETURN + GOTO REP_RETURN REM MAIN program MAIN: @@ -399,7 +414,8 @@ MAIN: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOSUB REP: REM call REP + A$=R$:GOTO REP: REM call REP + REP_RETURN: IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index d06e168a12..6b8ee7ef33 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -60,6 +60,9 @@ EVAL_AST: REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM if we are returning to DO, then skip last element + IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM if hashmap, skip eval of even entries (keys) IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL @@ -112,7 +115,6 @@ EVAL_AST: REM pop EVAL AST return label/address RN=X%(X):X=X-1 ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 - RETURN REM EVAL(A, E)) -> R EVAL: @@ -121,6 +123,8 @@ EVAL: REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A + REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0)) + EVAL_TCO_RECUR: REM AZ=A:PR=1:GOSUB PR_STR @@ -218,19 +222,26 @@ EVAL: EVAL_DO: A=Z%(A,1): REM rest - - REM TODO: TCO + X=X+1:X%(X)=A: REM push/save A REM push EVAL_AST return label/address X=X+1:X%(X)=2 GOTO EVAL_AST + REM return label/address already popped by EVAL_AST EVAL_AST_RETURN_2: - X=X+1:X%(X)=R: REM push eval'd list - A=R:GOSUB LAST: REM return the last element - AY=X%(X):X=X-1: REM pop eval'd list - GOSUB RELEASE: REM release the eval'd list - GOTO EVAL_RETURN + REM cleanup + AY=R: REM get eval'd list for release + + A=X%(X):X=X-1: REM pop/restore original A for LAST + GOSUB LAST: REM get last element for return + A=R: REM new recur AST + + REM cleanup + GOSUB RELEASE: REM release eval'd list + AY=A:GOSUB RELEASE: REM release LAST value (not sure why) + + GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF: GOSUB EVAL_GET_A1: REM set A1 @@ -285,7 +296,11 @@ EVAL: ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: - GOSUB DO_FUNCTION + REM regular function + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL + REM for recur functions (apply, map, swap!), use GOTO + IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION + DO_TCO_FUNCTION_RETURN_EVAL: REM pop and release f/args AY=X%(X):X=X-1:GOSUB RELEASE @@ -375,7 +390,7 @@ REP: IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ - RETURN + GOTO REP_RETURN REM MAIN program MAIN: @@ -420,13 +435,14 @@ MAIN: A$="(load-file (first -*ARGS*-))" GOSUB RE IF ER<>-2 THEN GOSUB PRINT_ERROR - END + GOTO QUIT REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOSUB REP: REM call REP + A$=R$:GOTO REP: REM call REP + REP_RETURN: IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index 80aab0398f..f72c2b7b74 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -134,6 +134,9 @@ EVAL_AST: REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM if we are returning to DO, then skip last element + IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM if hashmap, skip eval of even entries (keys) IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL @@ -186,7 +189,6 @@ EVAL_AST: REM pop EVAL AST return label/address RN=X%(X):X=X-1 ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 - RETURN REM EVAL(A, E)) -> R EVAL: @@ -195,6 +197,8 @@ EVAL: REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A + REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0)) + EVAL_TCO_RECUR: REM AZ=A:PR=1:GOSUB PR_STR @@ -294,19 +298,26 @@ EVAL: EVAL_DO: A=Z%(A,1): REM rest - - REM TODO: TCO + X=X+1:X%(X)=A: REM push/save A REM push EVAL_AST return label/address X=X+1:X%(X)=2 GOTO EVAL_AST + REM return label/address already popped by EVAL_AST EVAL_AST_RETURN_2: - X=X+1:X%(X)=R: REM push eval'd list - A=R:GOSUB LAST: REM return the last element - AY=X%(X):X=X-1: REM pop eval'd list - GOSUB RELEASE: REM release the eval'd list - GOTO EVAL_RETURN + REM cleanup + AY=R: REM get eval'd list for release + + A=X%(X):X=X-1: REM pop/restore original A for LAST + GOSUB LAST: REM get last element for return + A=R: REM new recur AST + + REM cleanup + GOSUB RELEASE: REM release eval'd list + AY=A:GOSUB RELEASE: REM release LAST value (not sure why) + + GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_QUOTE: R=Z%(A,1)+1:GOSUB DEREF_R @@ -375,7 +386,11 @@ EVAL: ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: - GOSUB DO_FUNCTION + REM regular function + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL + REM for recur functions (apply, map, swap!), use GOTO + IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION + DO_TCO_FUNCTION_RETURN_EVAL: REM pop and release f/args AY=X%(X):X=X-1:GOSUB RELEASE @@ -465,7 +480,7 @@ REP: IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ - RETURN + GOTO REP_RETURN REM MAIN program MAIN: @@ -510,13 +525,14 @@ MAIN: A$="(load-file (first -*ARGS*-))" GOSUB RE IF ER<>-2 THEN GOSUB PRINT_ERROR - END + GOTO QUIT REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOSUB REP: REM call REP + A$=R$:GOTO REP: REM call REP + REP_RETURN: IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index 0b52a438d4..b33214dd2c 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -110,7 +110,11 @@ MACROEXPAND: IF (Z%(B,0)AND31)<>11 THEN GOTO MACROEXPAND_DONE REM apply - F=B:AR=Z%(A,1):GOSUB APPLY + X=X+1:X%(X)=5: REM push APPLY return label/address + F=B:AR=Z%(A,1):GOTO APPLY + REM APPLY return label/address popped by APPLY + APPLY_RETURN_MACROEXPAND: + A=R AY=X%(X) @@ -171,6 +175,9 @@ EVAL_AST: REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM if we are returning to DO, then skip last element + IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM if hashmap, skip eval of even entries (keys) IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL @@ -223,7 +230,6 @@ EVAL_AST: REM pop EVAL AST return label/address RN=X%(X):X=X-1 ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 - RETURN REM EVAL(A, E)) -> R EVAL: @@ -232,6 +238,8 @@ EVAL: REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A + REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0)) + EVAL_TCO_RECUR: REM AZ=A:PR=1:GOSUB PR_STR @@ -339,19 +347,26 @@ EVAL: EVAL_DO: A=Z%(A,1): REM rest - - REM TODO: TCO + X=X+1:X%(X)=A: REM push/save A REM push EVAL_AST return label/address X=X+1:X%(X)=2 GOTO EVAL_AST + REM return label/address already popped by EVAL_AST EVAL_AST_RETURN_2: - X=X+1:X%(X)=R: REM push eval'd list - A=R:GOSUB LAST: REM return the last element - AY=X%(X):X=X-1: REM pop eval'd list - GOSUB RELEASE: REM release the eval'd list - GOTO EVAL_RETURN + REM cleanup + AY=R: REM get eval'd list for release + + A=X%(X):X=X-1: REM pop/restore original A for LAST + GOSUB LAST: REM get last element for return + A=R: REM new recur AST + + REM cleanup + GOSUB RELEASE: REM release eval'd list + AY=A:GOSUB RELEASE: REM release LAST value (not sure why) + + GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_QUOTE: R=Z%(A,1)+1:GOSUB DEREF_R @@ -444,7 +459,11 @@ EVAL: ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: - GOSUB DO_FUNCTION + REM regular function + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL + REM for recur functions (apply, map, swap!), use GOTO + IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION + DO_TCO_FUNCTION_RETURN_EVAL: REM pop and release f/args AY=X%(X):X=X-1:GOSUB RELEASE @@ -534,7 +553,7 @@ REP: IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ - RETURN + GOTO REP_RETURN REM MAIN program MAIN: @@ -588,13 +607,14 @@ MAIN: A$="(load-file (first -*ARGS*-))" GOSUB RE IF ER<>-2 THEN GOSUB PRINT_ERROR - END + GOTO QUIT REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOSUB REP: REM call REP + A$=R$:GOTO REP: REM call REP + REP_RETURN: IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index 024ebf078d..4f7d443d50 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -110,7 +110,11 @@ MACROEXPAND: IF (Z%(B,0)AND31)<>11 THEN GOTO MACROEXPAND_DONE REM apply - F=B:AR=Z%(A,1):GOSUB APPLY + X=X+1:X%(X)=5: REM push APPLY return label/address + F=B:AR=Z%(A,1):GOTO APPLY + REM APPLY return label/address popped by APPLY + APPLY_RETURN_MACROEXPAND: + A=R AY=X%(X) @@ -171,6 +175,9 @@ EVAL_AST: REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM if we are returning to DO, then skip last element + IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM if hashmap, skip eval of even entries (keys) IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL @@ -223,7 +230,6 @@ EVAL_AST: REM pop EVAL AST return label/address RN=X%(X):X=X-1 ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 - RETURN REM EVAL(A, E)) -> R EVAL: @@ -232,6 +238,8 @@ EVAL: REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A + REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0)) + EVAL_TCO_RECUR: REM AZ=A:PR=1:GOSUB PR_STR @@ -340,19 +348,26 @@ EVAL: EVAL_DO: A=Z%(A,1): REM rest - - REM TODO: TCO + X=X+1:X%(X)=A: REM push/save A REM push EVAL_AST return label/address X=X+1:X%(X)=2 GOTO EVAL_AST + REM return label/address already popped by EVAL_AST EVAL_AST_RETURN_2: - X=X+1:X%(X)=R: REM push eval'd list - A=R:GOSUB LAST: REM return the last element - AY=X%(X):X=X-1: REM pop eval'd list - GOSUB RELEASE: REM release the eval'd list - GOTO EVAL_RETURN + REM cleanup + AY=R: REM get eval'd list for release + + A=X%(X):X=X-1: REM pop/restore original A for LAST + GOSUB LAST: REM get last element for return + A=R: REM new recur AST + + REM cleanup + GOSUB RELEASE: REM release eval'd list + AY=A:GOSUB RELEASE: REM release LAST value (not sure why) + + GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_QUOTE: R=Z%(A,1)+1:GOSUB DEREF_R @@ -476,7 +491,11 @@ EVAL: ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: - GOSUB DO_FUNCTION + REM regular function + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL + REM for recur functions (apply, map, swap!), use GOTO + IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION + DO_TCO_FUNCTION_RETURN_EVAL: REM pop and release f/args AY=X%(X):X=X-1:GOSUB RELEASE @@ -566,7 +585,7 @@ REP: IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ - RETURN + GOTO REP_RETURN REM MAIN program MAIN: @@ -620,13 +639,14 @@ MAIN: A$="(load-file (first -*ARGS*-))" GOSUB RE IF ER<>-2 THEN GOSUB PRINT_ERROR - END + GOTO QUIT REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOSUB REP: REM call REP + A$=R$:GOTO REP: REM call REP + REP_RETURN: IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index b5b5b57de5..18ffbab756 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -16,18 +16,14 @@ MAL_READ: GOSUB READ_STR RETURN -REM PAIR_Q(B) -> R -PAIR_Q: - R=0 - IF (Z%(B,0)AND31)<>6 AND (Z%(B,0)AND31)<>7 THEN RETURN - IF (Z%(B,1)=0) THEN RETURN - R=1 - RETURN - REM QUASIQUOTE(A) -> R QUASIQUOTE: - B=A:GOSUB PAIR_Q - IF R=1 THEN GOTO QQ_UNQUOTE + REM pair? + IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_QUOTE + IF (Z%(A,1)=0) THEN GOTO QQ_QUOTE + GOTO QQ_UNQUOTE + + QQ_QUOTE: REM ['quote, ast] AS$="quote":T=5:GOSUB STRING B2=R:B1=A:GOSUB LIST2 @@ -56,8 +52,10 @@ QUASIQUOTE: REM set A to ast[0] for last two cases A=A+1:GOSUB DEREF_A - B=A:GOSUB PAIR_Q - IF R=0 THEN GOTO QQ_DEFAULT + REM pair? + IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_DEFAULT + IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT + B=A+1:GOSUB DEREF_B IF (Z%(B,0)AND31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT @@ -110,7 +108,11 @@ MACROEXPAND: IF (Z%(B,0)AND31)<>11 THEN GOTO MACROEXPAND_DONE REM apply - F=B:AR=Z%(A,1):GOSUB APPLY + X=X+1:X%(X)=5: REM push APPLY return label/address + F=B:AR=Z%(A,1):GOTO APPLY + REM APPLY return label/address popped by APPLY + APPLY_RETURN_MACROEXPAND: + A=R AY=X%(X) @@ -171,6 +173,9 @@ EVAL_AST: REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM if we are returning to DO, then skip last element + IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM if hashmap, skip eval of even entries (keys) IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL @@ -223,7 +228,6 @@ EVAL_AST: REM pop EVAL AST return label/address RN=X%(X):X=X-1 ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 - RETURN REM EVAL(A, E)) -> R EVAL: @@ -232,6 +236,8 @@ EVAL: REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A + REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0)) + EVAL_TCO_RECUR: REM AZ=A:PR=1:GOSUB PR_STR @@ -340,19 +346,26 @@ EVAL: EVAL_DO: A=Z%(A,1): REM rest - - REM TODO: TCO + X=X+1:X%(X)=A: REM push/save A REM push EVAL_AST return label/address X=X+1:X%(X)=2 GOTO EVAL_AST + REM return label/address already popped by EVAL_AST EVAL_AST_RETURN_2: - X=X+1:X%(X)=R: REM push eval'd list - A=R:GOSUB LAST: REM return the last element - AY=X%(X):X=X-1: REM pop eval'd list - GOSUB RELEASE: REM release the eval'd list - GOTO EVAL_RETURN + REM cleanup + AY=R: REM get eval'd list for release + + A=X%(X):X=X-1: REM pop/restore original A for LAST + GOSUB LAST: REM get last element for return + A=R: REM new recur AST + + REM cleanup + GOSUB RELEASE: REM release eval'd list + AY=A:GOSUB RELEASE: REM release LAST value (not sure why) + + GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_QUOTE: R=Z%(A,1)+1:GOSUB DEREF_R @@ -476,7 +489,11 @@ EVAL: ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: - GOSUB DO_FUNCTION + REM regular function + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL + REM for recur functions (apply, map, swap!), use GOTO + IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION + DO_TCO_FUNCTION_RETURN_EVAL: REM pop and release f/args AY=X%(X):X=X-1:GOSUB RELEASE @@ -566,7 +583,7 @@ REP: IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ - RETURN + GOTO REP_RETURN REM MAIN program MAIN: @@ -623,7 +640,7 @@ MAIN: A$="(load-file (first -*ARGS*-))" GOSUB RE IF ER<>-2 THEN GOSUB PRINT_ERROR - END + GOTO QUIT REPL: REM print the REPL startup header @@ -635,7 +652,8 @@ MAIN: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOSUB REP: REM call REP + A$=R$:GOTO REP: REM call REP + REP_RETURN: IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ diff --git a/basic/types.in.bas b/basic/types.in.bas index bc3483c840..42795766b2 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -64,8 +64,8 @@ INIT_MEMORY: REM pending release stack Y=-1:DIM Y%(Z4,1): REM stack of Z% indexes - REM PRINT "Lisp data memory: "+STR$(T-FRE(0)) - REM PRINT "Interpreter working memory: "+STR$(FRE(0)) + BT=TI + RETURN @@ -262,37 +262,52 @@ REM general functions REM EQUAL_Q(A, B) -> R EQUAL_Q: + ED=0: REM recursion depth + R=-1: REM return value + + EQUAL_Q_RECUR: + GOSUB DEREF_A GOSUB DEREF_B - R=0 + REM push A and B + X=X+2:X%(X-1)=A:X%(X)=B + ED=ED+1 + U1=Z%(A,0)AND31 U2=Z%(B,0)AND31 - IF NOT (U1=U2 OR ((U1=6 OR U1=7) AND (U2=6 OR U2=7))) THEN RETURN - IF U1=6 THEN GOTO EQUAL_Q_SEQ - IF U1=7 THEN GOTO EQUAL_Q_SEQ - IF U1=8 THEN GOTO EQUAL_Q_HM + IF U1>5 AND U1<8 AND U2>5 AND U2<8 THEN GOTO EQUAL_Q_SEQ + IF U1=8 AND U2=8 THEN GOTO EQUAL_Q_HM - IF Z%(A,1)=Z%(B,1) THEN R=1 - RETURN + IF U1<>U2 OR Z%(A,1)<>Z%(B,1) THEN R=0 + GOTO EQUAL_Q_DONE EQUAL_Q_SEQ: - IF (Z%(A,1)=0) AND (Z%(B,1)=0) THEN R=1:RETURN - IF (Z%(A,1)=0) OR (Z%(B,1)=0) THEN R=0:RETURN + IF (Z%(A,1)=0) AND (Z%(B,1)=0) THEN GOTO EQUAL_Q_DONE + IF (Z%(A,1)=0) OR (Z%(B,1)=0) THEN R=0:GOTO EQUAL_Q_DONE - REM push A and B - X=X+2:X%(X-1)=A:X%(X)=B REM compare the elements - A=Z%(A+1,1):B=Z%(B+1,1):GOSUB EQUAL_Q - REM pop A and B - A=X%(X-1):B=X%(X):X=X-2 - IF R=0 THEN RETURN + A=Z%(A+1,1):B=Z%(B+1,1) + GOTO EQUAL_Q_RECUR + EQUAL_Q_SEQ_CONTINUE: REM next elements of the sequences - A=Z%(A,1):B=Z%(B,1):GOTO EQUAL_Q_SEQ + A=X%(X-1):B=X%(X) + A=Z%(A,1):B=Z%(B,1) + X%(X-1)=A:X%(X)=B + GOTO EQUAL_Q_SEQ + EQUAL_Q_HM: R=0 - RETURN + GOTO EQUAL_Q_DONE + + EQUAL_Q_DONE: + X=X-2: REM pop current A and B + ED=ED-1 + IF R>-1 AND ED>0 THEN GOTO EQUAL_Q_DONE: REM unwind + IF ED=0 AND R=-1 THEN R=1 + IF ED=0 THEN RETURN + GOTO EQUAL_Q_SEQ_CONTINUE REM string functions @@ -302,9 +317,12 @@ STRING_: IF S=0 THEN GOTO STRING_NOT_FOUND REM search for matching string in S$ - FOR I=0 TO S-1 + I=0 + STRING_LOOP: + IF I>S-1 THEN GOTO STRING_NOT_FOUND IF AS$=S$(I) THEN R=I:RETURN - NEXT I + I=I+1 + GOTO STRING_LOOP STRING_NOT_FOUND: S$(S)=AS$ @@ -502,34 +520,3 @@ REM MAL_FUNCTION(A, P, E) -> R MAL_FUNCTION: T=10:L=A:M=P:N=E:GOSUB ALLOC RETURN - -REM APPLY(F, AR) -> R -REM restores E -APPLY: - REM if metadata, get the actual object - IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) - - IF (Z%(F,0)AND31)=9 THEN GOTO DO_APPLY_FUNCTION - IF (Z%(F,0)AND31)=10 THEN GOTO DO_APPLY_MAL_FUNCTION - IF (Z%(F,0)AND31)=11 THEN GOTO DO_APPLY_MAL_FUNCTION - - DO_APPLY_FUNCTION: - GOSUB DO_FUNCTION - - RETURN - - DO_APPLY_MAL_FUNCTION: - X=X+1:X%(X)=E: REM save the current environment - - REM create new environ using env and params stored in the - REM function and bind the params to the apply arguments - O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS - - A=Z%(F,1):E=R:GOSUB EVAL - - AY=E:GOSUB RELEASE: REM release the new environment - - E=X%(X):X=X-1: REM pop/restore the saved environment - - RETURN - diff --git a/basic/variables.txt b/basic/variables.txt index c64e68b696..aebe3e49ac 100644 --- a/basic/variables.txt +++ b/basic/variables.txt @@ -56,11 +56,15 @@ P2 : PR_MEMORY, CHECK_FREE_LIST end SZ : size argument to ALLOC Reused/temporaries: - A0 : EVAL ast elements A1 : EVAL ast elements A2 : EVAL ast elements A3 : EVAL ast elements + +ED : EQUAL_Q recursion depth counter +RD : PR_OBJECT recursion depth +SD : READ_STR sequence read recursion depth + CH$ : READ_TOKEN, SKIP_SPACES, SKIP_TO_EOL current character I : STRING, REPLACE, SLICE, PR_MEMORY, PR_OBJECT J : REPLACE diff --git a/mal/step2_eval.mal b/mal/step2_eval.mal index 173b6b9ad0..499ff94e03 100644 --- a/mal/step2_eval.mal +++ b/mal/step2_eval.mal @@ -7,8 +7,8 @@ (def! eval-ast (fn* [ast env] (do ;;(do (prn "eval-ast" ast "/" (keys env)) ) (cond - (symbol? ast) (or (get env (str ast)) - (throw (str ast " not found"))) + (symbol? ast) (let* [res (get env (str ast))] + (if res res (throw (str ast " not found")))) (list? ast) (map (fn* [exp] (EVAL exp env)) ast) From b68230b821c4f170603785ddab972828489df0e8 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 25 Oct 2016 22:39:32 +0200 Subject: [PATCH 0187/2308] Add docker support --- .travis.yml | 1 + pil/Dockerfile | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 pil/Dockerfile diff --git a/.travis.yml b/.travis.yml index 8780a9d018..a6abfdcc94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,6 +48,7 @@ matrix: - {env: IMPL=perl, services: [docker]} - {env: IMPL=perl6, services: [docker]} - {env: IMPL=php, services: [docker]} + - {env: IMPL=pil, services: [docker]} - {env: IMPL=plpgsql, services: [docker]} # - {env: IMPL=plsql, services: [docker]} - {env: IMPL=ps, services: [docker]} diff --git a/pil/Dockerfile b/pil/Dockerfile new file mode 100644 index 0000000000..36282ddb5e --- /dev/null +++ b/pil/Dockerfile @@ -0,0 +1,26 @@ +FROM ubuntu:wily +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# picolisp +RUN apt-get -y install picolisp + From b6263859a99edf83a1093fc9465f783d0bd1965f Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 25 Oct 2016 23:43:32 +0200 Subject: [PATCH 0188/2308] Extend README --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 143fc1ed2b..bffc0d4c84 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 59 languages: +Mal is implemented in 60 languages: * Ada * GNU awk @@ -50,6 +50,7 @@ Mal is implemented in 59 languages: * Perl * Perl 6 * PHP +* Picolisp * PL/pgSQL (Postgres) * PL/SQL (Oracle) * Postscript @@ -624,6 +625,18 @@ cd php php stepX_YYY.php ``` +### Picolisp + +*The Picolisp implementation was created by [Vasilij Schneidermann](https://github.com/wasamasa)* + +The Picolisp implementation requires libreadline and Picolisp 3.1.11 +or later. + +``` +cd pil +./run +``` + ### PL/pgSQL (Postgres SQL Procedural Language) The PL/pgSQL implementation of mal requires a running Postgres server From 3e127081a459920971a966769495fd5037272265 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Wed, 26 Oct 2016 00:11:33 +0200 Subject: [PATCH 0189/2308] Add interop and tests --- pil/core.l | 14 +++++++++++++- pil/tests/stepA_mal.mal | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 pil/tests/stepA_mal.mal diff --git a/pil/core.l b/pil/core.l index 30bb697475..5a22963b2c 100644 --- a/pil/core.l +++ b/pil/core.l @@ -89,6 +89,16 @@ (maps '((C) (put X* (cdr C) (car C))) X) X* ) ) +(de pil-to-mal (X) + (cond + ((not X) *MAL-nil) + ((=T X) *MAL-true) + ((num? X) (MAL-number X)) + ((str? X) (MAL-string X)) + ((sym? X) (MAL-symbol X)) + ((lst? X) (MAL-list (mapcar pil-to-mal X))) + (T (MAL-string (sym X))) ) ) + (def '*Ns '((+ . `(MAL-fn '((A B) (MAL-number (+ (MAL-value A) (MAL-value B)))))) (- . `(MAL-fn '((A B) (MAL-number (- (MAL-value A) (MAL-value B)))))) @@ -161,4 +171,6 @@ (readline . `(MAL-fn '((Prompt) (let Output (readline (MAL-value Prompt)) (if (=0 Output) NIL (MAL-string Output)))))) (time-ms . `(MAL-fn '(() (MAL-number (/ (usec) 1000))))) (conj . `(MAL-fn MAL-conj)) - (seq . `(MAL-fn MAL-seq)) ) ) + (seq . `(MAL-fn MAL-seq)) + + (pil-eval . `(MAL-fn '((Input) (pil-to-mal (run (str (MAL-value Input))))))) ) ) diff --git a/pil/tests/stepA_mal.mal b/pil/tests/stepA_mal.mal new file mode 100644 index 0000000000..562c5703ea --- /dev/null +++ b/pil/tests/stepA_mal.mal @@ -0,0 +1,17 @@ +;; Testing basic pil interop + +(pil-eval "T") +;=>true + +(pil-eval "NIL") +;=>nil + +(pil-eval "(+ 1 1)") +;=>2 + +(pil-eval "(cons 1 2 3 NIL)") +;=>(1 2 3) + +(pil-eval "(use (@A @O) (match '(@A and @O) '(Alpha and Omega)) (prinl @A) (prinl @O))") +Alpha +Omega From ab1c5d944cae32a91a9067275a5e112161558893 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Tue, 25 Oct 2016 22:24:36 +0000 Subject: [PATCH 0190/2308] vimscript: Fix misinformation in comment --- vimscript/readline.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vimscript/readline.vim b/vimscript/readline.vim index 8c3b66b10e..af4d57f596 100644 --- a/vimscript/readline.vim +++ b/vimscript/readline.vim @@ -11,8 +11,8 @@ endfunction " Returns [is_eof, line_string] function Readline(prompt) - " Use the vimreadline() function defined in vimreadline.c and compiled - " into libvimreadline.so + " Use the vimreadline() function defined in vimextras.c and compiled + " into libvimextras.so call s:buildlibvimreadline() let res = libcall("libvimextras.so", "vimreadline", a:prompt) if res[0] == "E" From 816f0e2fc7da5d78d07a087f46db0acffaa9bfb4 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Tue, 25 Oct 2016 19:54:03 +0000 Subject: [PATCH 0191/2308] vimscript: Increase the unixtime base to make sure time-ms fits in a *positive* integer --- vimscript/vimextras.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vimscript/vimextras.c b/vimscript/vimextras.c index 339586a83b..15d9d5ad9c 100644 --- a/vimscript/vimextras.c +++ b/vimscript/vimextras.c @@ -27,10 +27,10 @@ char* vimreadline(char* prompt) { return buf; } -#define UNIXTIME_2000_01_01 946684800 +#define UNIXTIME_BASE 1451606400 /* = Unix time of 2016-01-01 */ /* - * Returns the number of milliseconds since 2000-01-01 00:00:00 UTC. + * Returns the number of milliseconds since 2016-01-01 00:00:00 UTC. * * This date is chosen (instead of the standard 1970 epoch) so the number of * milliseconds will not exceed a 32-bit integer, which is the limit for Vim @@ -40,5 +40,5 @@ int vimtimems(int dummy) { struct timeval tv; (void) dummy; /* unused */ gettimeofday(&tv, NULL); - return (tv.tv_sec - UNIXTIME_2000_01_01) * 1000 + (tv.tv_usec / 1000); + return (tv.tv_sec - UNIXTIME_BASE) * 1000 + (tv.tv_usec / 1000); } From aa62cbda718a6fedf6a30cba10fe01950e122860 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Tue, 25 Oct 2016 19:55:14 +0000 Subject: [PATCH 0192/2308] vimscript: Use Vim 8.0 lambdas to reduce boilerplate code --- README.md | 3 +- vimscript/core.vim | 284 ++++++---------------------------- vimscript/step2_eval.vim | 24 +-- vimscript/step3_env.vim | 24 +-- vimscript/tests/stepA_mal.mal | 8 +- vimscript/types.vim | 5 + 6 files changed, 64 insertions(+), 284 deletions(-) diff --git a/README.md b/README.md index 8c206e3677..db985f4cb5 100644 --- a/README.md +++ b/README.md @@ -814,8 +814,7 @@ make *The Vimscript implementation was created by [Dov Murik](https://github.com/dubek)* -The Vimscript implementation of mal requires Vim to run. It has been tested -with Vim 7.4. +The Vimscript implementation of mal requires Vim 8.0 to run. ``` cd vimscript diff --git a/vimscript/core.vim b/vimscript/core.vim index ad88a9c673..0c0794b1d2 100644 --- a/vimscript/core.vim +++ b/vimscript/core.vim @@ -1,82 +1,5 @@ " core module -function MalEqualQ(args) - return BoolNew(EqualQ(a:args[0], a:args[1])) -endfunction - -function MalLt(args) - return BoolNew(ObjValue(a:args[0]) < ObjValue(a:args[1])) -endfunction - -function MalLte(args) - return BoolNew(ObjValue(a:args[0]) <= ObjValue(a:args[1])) -endfunction - -function MalGt(args) - return BoolNew(ObjValue(a:args[0]) > ObjValue(a:args[1])) -endfunction - -function MalGte(args) - return BoolNew(ObjValue(a:args[0]) >= ObjValue(a:args[1])) -endfunction - -function MalAdd(args) - return IntegerNew(ObjValue(a:args[0]) + ObjValue(a:args[1])) -endfunction - -function MalSub(args) - return IntegerNew(ObjValue(a:args[0]) - ObjValue(a:args[1])) -endfunction - -function MalMul(args) - return IntegerNew(ObjValue(a:args[0]) * ObjValue(a:args[1])) -endfunction - -function MalDiv(args) - return IntegerNew(ObjValue(a:args[0]) / ObjValue(a:args[1])) -endfunction - -function MalTimeMs(args) - " vimtimems() is implemented in vimextras.c - return IntegerNew(libcallnr("libvimextras.so", "vimtimems", 0)) -endfunction - -function MalList(args) - return ListNew(a:args) -endfunction - -function MalListQ(args) - return BoolNew(ListQ(a:args[0])) -endfunction - -function MalVector(args) - return VectorNew(a:args) -endfunction - -function MalVectorQ(args) - return BoolNew(VectorQ(a:args[0])) -endfunction - -function MalSequentialQ(args) - return BoolNew(SequentialQ(a:args[0])) -endfunction - -function MalHashMap(args) - return HashBuild(a:args) -endfunction - -function MalMapQ(args) - return BoolNew(HashQ(a:args[0])) -endfunction - -function MalEmptyQ(args) - return BoolNew(EmptyQ(a:args[0])) -endfunction - -function MalCount(args) - return IntegerNew(ListCount(a:args[0])) -endfunction - function MalAssoc(args) let hash = copy(ObjValue(a:args[0])) let new_elements = HashBuild(a:args[1:]) @@ -122,43 +45,11 @@ function MalKeys(args) return ListNew(listobjs) endfunction -function MalVals(args) - return ListNew(values(ObjValue(a:args[0]))) -endfunction - -function MalPrStr(args) - return StringNew(join(map(copy(a:args), 'PrStr(v:val, 1)'), " ")) -endfunction - -function MalStr(args) - return StringNew(join(map(copy(a:args), 'PrStr(v:val, 0)'), "")) -endfunction - -function MalPrn(args) - call PrintLn(join(map(copy(a:args), 'PrStr(v:val, 1)'), " ")) - return g:MalNil -endfunction - -function MalPrintLn(args) - call PrintLn(join(map(copy(a:args), 'PrStr(v:val, 0)'), " ")) - return g:MalNil -endfunction - -function MalReadString(args) - return ReadStr(ObjValue(a:args[0])) -endfunction - function MalReadLine(args) let [eof, line] = Readline(ObjValue(a:args[0])) return eof ? g:MalNil : StringNew(line) endfunction -function MalSlurp(args) - let filename = ObjValue(a:args[0]) - let lines = readfile(filename, "b") - return StringNew(join(lines, "\n")) -endfunction - function MalCons(args) let items = copy(ObjValue(a:args[1])) call insert(items, a:args[0]) @@ -173,18 +64,6 @@ function MalConcat(args) return ListNew(res) endfunction -function MalFirst(args) - return NilQ(a:args[0]) ? g:MalNil : ListFirst(a:args[0]) -endfunction - -function MalNth(args) - return ListNth(a:args[0], ObjValue(a:args[1])) -endfunction - -function MalRest(args) - return NilQ(a:args[0]) ? ListNew([]) : ListRest(a:args[0]) -endfunction - function MalApply(args) let funcobj = a:args[0] let rest = a:args[1:] @@ -227,38 +106,6 @@ function MalThrow(args) throw "__MalException__" endfunction -function MalNilQ(args) - return BoolNew(NilQ(a:args[0])) -endfunction - -function MalTrueQ(args) - return BoolNew(TrueQ(a:args[0])) -endfunction - -function MalFalseQ(args) - return BoolNew(FalseQ(a:args[0])) -endfunction - -function MalSymbol(args) - return SymbolNew(ObjValue(a:args[0])) -endfunction - -function MalSymbolQ(args) - return BoolNew(SymbolQ(a:args[0])) -endfunction - -function MalStringQ(args) - return BoolNew(StringQ(a:args[0])) -endfunction - -function MalKeyword(args) - return KeywordNew(ObjValue(a:args[0])) -endfunction - -function MalKeywordQ(args) - return BoolNew(KeywordQ(a:args[0])) -endfunction - function ConjList(list, elements) let newlist = a:list for e in a:elements @@ -292,44 +139,11 @@ function MalSeq(args) elseif VectorQ(obj) return ListNew(ObjValue(obj)) elseif StringQ(obj) - return ListNew(map(split(ObjValue(obj), '\zs'), 'StringNew(v:val)')) + return ListNew(map(split(ObjValue(obj), '\zs'), {_, c -> StringNew(c)})) endif throw "seq requires string or list or vector or nil" endfunction -function MalMeta(args) - return ObjMeta(a:args[0]) -endfunction - -function MalWithMeta(args) - let obj = a:args[0] - return ObjNewWithMeta(ObjType(obj), copy(ObjValue(obj)), a:args[1]) -endfunction - -function MalAtom(args) - return AtomNew(a:args[0]) -endfunction - -function MalAtomQ(args) - return BoolNew(AtomQ(a:args[0])) -endfunction - -function MalDeref(args) - return ObjValue(a:args[0]) -endfunction - -function MalResetBang(args) - return ObjSetValue(a:args[0], a:args[1]) -endfunction - -function MalSwapBang(args) - let atomval = ObjValue(a:args[0]) - let funcobj = a:args[1] - let args = a:args[2:] - let res = MalApply([funcobj, ListNew([atomval] + args)]) - return ObjSetValue(a:args[0], res) -endfunction - function VimToMal(e) if type(a:e) == type(0) return IntegerNew(a:e) @@ -355,69 +169,63 @@ function VimToMal(e) endif endfunction -function MalVimStar(args) - let vimexpr = ObjValue(a:args[0]) - let vimres = eval(vimexpr) - return VimToMal(vimres) -endfunction - let CoreNs = { - \ "=": NewNativeFn("MalEqualQ"), - \ "<": NewNativeFn("MalLt"), - \ "<=": NewNativeFn("MalLte"), - \ ">": NewNativeFn("MalGt"), - \ ">=": NewNativeFn("MalGte"), - \ "+": NewNativeFn("MalAdd"), - \ "-": NewNativeFn("MalSub"), - \ "*": NewNativeFn("MalMul"), - \ "/": NewNativeFn("MalDiv"), - \ "time-ms": NewNativeFn("MalTimeMs"), - \ "nil?": NewNativeFn("MalNilQ"), - \ "true?": NewNativeFn("MalTrueQ"), - \ "false?": NewNativeFn("MalFalseQ"), - \ "symbol": NewNativeFn("MalSymbol"), - \ "symbol?": NewNativeFn("MalSymbolQ"), - \ "string?": NewNativeFn("MalStringQ"), - \ "keyword": NewNativeFn("MalKeyword"), - \ "keyword?": NewNativeFn("MalKeywordQ"), - \ "list": NewNativeFn("MalList"), - \ "list?": NewNativeFn("MalListQ"), - \ "vector": NewNativeFn("MalVector"), - \ "vector?": NewNativeFn("MalVectorQ"), - \ "sequential?": NewNativeFn("MalSequentialQ"), - \ "hash-map": NewNativeFn("MalHashMap"), - \ "map?": NewNativeFn("MalMapQ"), - \ "empty?": NewNativeFn("MalEmptyQ"), - \ "count": NewNativeFn("MalCount"), + \ "=": NewNativeFnLambda({a -> BoolNew(EqualQ(a[0], a[1]))}), + \ "<": NewNativeFnLambda({a -> BoolNew(ObjValue(a[0]) < ObjValue(a[1]))}), + \ "<=": NewNativeFnLambda({a -> BoolNew(ObjValue(a[0]) <= ObjValue(a[1]))}), + \ ">": NewNativeFnLambda({a -> BoolNew(ObjValue(a[0]) > ObjValue(a[1]))}), + \ ">=": NewNativeFnLambda({a -> BoolNew(ObjValue(a[0]) >= ObjValue(a[1]))}), + \ "+": NewNativeFnLambda({a -> IntegerNew(ObjValue(a[0]) + ObjValue(a[1]))}), + \ "-": NewNativeFnLambda({a -> IntegerNew(ObjValue(a[0]) - ObjValue(a[1]))}), + \ "*": NewNativeFnLambda({a -> IntegerNew(ObjValue(a[0]) * ObjValue(a[1]))}), + \ "/": NewNativeFnLambda({a -> IntegerNew(ObjValue(a[0]) / ObjValue(a[1]))}), + \ "time-ms": NewNativeFnLambda({a -> IntegerNew(libcallnr("libvimextras.so", "vimtimems", 0))}), + \ "nil?": NewNativeFnLambda({a -> BoolNew(NilQ(a[0]))}), + \ "true?": NewNativeFnLambda({a -> BoolNew(TrueQ(a[0]))}), + \ "false?": NewNativeFnLambda({a -> BoolNew(FalseQ(a[0]))}), + \ "symbol": NewNativeFnLambda({a -> SymbolNew(ObjValue(a[0]))}), + \ "symbol?": NewNativeFnLambda({a -> BoolNew(SymbolQ(a[0]))}), + \ "string?": NewNativeFnLambda({a -> BoolNew(StringQ(a[0]))}), + \ "keyword": NewNativeFnLambda({a -> KeywordNew(ObjValue(a[0]))}), + \ "keyword?": NewNativeFnLambda({a -> BoolNew(KeywordQ(a[0]))}), + \ "list": NewNativeFnLambda({a -> ListNew(a)}), + \ "list?": NewNativeFnLambda({a -> BoolNew(ListQ(a[0]))}), + \ "vector": NewNativeFnLambda({a -> VectorNew(a)}), + \ "vector?": NewNativeFnLambda({a -> BoolNew(VectorQ(a[0]))}), + \ "sequential?": NewNativeFnLambda({a -> BoolNew(SequentialQ(a[0]))}), + \ "hash-map": NewNativeFnLambda({a -> HashBuild(a)}), + \ "map?": NewNativeFnLambda({a -> BoolNew(HashQ(a[0]))}), + \ "empty?": NewNativeFnLambda({a -> BoolNew(EmptyQ(a[0]))}), + \ "count": NewNativeFnLambda({a -> IntegerNew(ListCount(a[0]))}), \ "assoc": NewNativeFn("MalAssoc"), \ "dissoc": NewNativeFn("MalDissoc"), \ "get": NewNativeFn("MalGet"), \ "contains?": NewNativeFn("MalContainsQ"), \ "keys": NewNativeFn("MalKeys"), - \ "vals": NewNativeFn("MalVals"), - \ "pr-str": NewNativeFn("MalPrStr"), - \ "str": NewNativeFn("MalStr"), - \ "prn": NewNativeFn("MalPrn"), - \ "println": NewNativeFn("MalPrintLn"), - \ "read-string": NewNativeFn("MalReadString"), + \ "vals": NewNativeFnLambda({a -> ListNew(values(ObjValue(a[0])))}), + \ "pr-str": NewNativeFnLambda({a -> StringNew(join(map(copy(a), {_, e -> PrStr(e, 1)}), " "))}), + \ "str": NewNativeFnLambda({a -> StringNew(join(map(copy(a), {_, e -> PrStr(e, 0)}), ""))}), + \ "prn": NewNativeFnLambda({a -> [PrintLn(join(map(copy(a), {_, e -> PrStr(e, 1)}), " ")), g:MalNil][1]}), + \ "println": NewNativeFnLambda({a -> [PrintLn(join(map(copy(a), {_, e -> PrStr(e, 0)}), " ")), g:MalNil][1]}), + \ "read-string": NewNativeFnLambda({a -> ReadStr(ObjValue(a[0]))}), \ "readline": NewNativeFn("MalReadLine"), - \ "slurp": NewNativeFn("MalSlurp"), + \ "slurp": NewNativeFnLambda({a -> StringNew(join(readfile(ObjValue(a[0]), "b"), "\n"))}), \ "cons": NewNativeFn("MalCons"), \ "concat": NewNativeFn("MalConcat"), - \ "first": NewNativeFn("MalFirst"), - \ "nth": NewNativeFn("MalNth"), - \ "rest": NewNativeFn("MalRest"), + \ "first": NewNativeFnLambda({a -> NilQ(a[0]) ? g:MalNil : ListFirst(a[0])}), + \ "nth": NewNativeFnLambda({a -> ListNth(a[0], ObjValue(a[1]))}), + \ "rest": NewNativeFnLambda({a -> NilQ(a[0]) ? ListNew([]) : ListRest(a[0])}), \ "apply": NewNativeFn("MalApply"), \ "map": NewNativeFn("MalMap"), \ "throw": NewNativeFn("MalThrow"), \ "conj": NewNativeFn("MalConj"), \ "seq": NewNativeFn("MalSeq"), - \ "meta": NewNativeFn("MalMeta"), - \ "with-meta": NewNativeFn("MalWithMeta"), - \ "atom": NewNativeFn("MalAtom"), - \ "atom?": NewNativeFn("MalAtomQ"), - \ "deref": NewNativeFn("MalDeref"), - \ "reset!": NewNativeFn("MalResetBang"), - \ "swap!": NewNativeFn("MalSwapBang"), - \ "vim*": NewNativeFn("MalVimStar") + \ "meta": NewNativeFnLambda({a -> ObjMeta(a[0])}), + \ "with-meta": NewNativeFnLambda({a -> ObjNewWithMeta(ObjType(a[0]), copy(ObjValue(a[0])), a[1])}), + \ "atom": NewNativeFnLambda({a -> AtomNew(a[0])}), + \ "atom?": NewNativeFnLambda({a -> BoolNew(AtomQ(a[0]))}), + \ "deref": NewNativeFnLambda({a -> ObjValue(a[0])}), + \ "reset!": NewNativeFnLambda({a -> ObjSetValue(a[0], a[1])}), + \ "swap!": NewNativeFnLambda({a -> ObjSetValue(a[0], MalApply([a[1], ListNew([ObjValue(a[0])] + a[2:])]))}), + \ "vim*": NewNativeFnLambda({a -> VimToMal(eval(ObjValue(a[0])))}) \ } diff --git a/vimscript/step2_eval.vim b/vimscript/step2_eval.vim index 54f7b34ef8..63566bc166 100644 --- a/vimscript/step2_eval.vim +++ b/vimscript/step2_eval.vim @@ -64,27 +64,11 @@ function REP(str, env) return PRINT(EVAL(READ(a:str), a:env)) endfunction -function MalAdd(args) - return IntegerNew(ObjValue(a:args[0]) + ObjValue(a:args[1])) -endfunction - -function MalSub(args) - return IntegerNew(ObjValue(a:args[0]) - ObjValue(a:args[1])) -endfunction - -function MalMul(args) - return IntegerNew(ObjValue(a:args[0]) * ObjValue(a:args[1])) -endfunction - -function MalDiv(args) - return IntegerNew(ObjValue(a:args[0]) / ObjValue(a:args[1])) -endfunction - let repl_env = {} -let repl_env["+"] = function("MalAdd") -let repl_env["-"] = function("MalSub") -let repl_env["*"] = function("MalMul") -let repl_env["/"] = function("MalDiv") +let repl_env["+"] = {a -> IntegerNew(a[0].val + a[1].val)} +let repl_env["-"] = {a -> IntegerNew(a[0].val - a[1].val)} +let repl_env["*"] = {a -> IntegerNew(a[0].val * a[1].val)} +let repl_env["/"] = {a -> IntegerNew(a[0].val / a[1].val)} while 1 let [eof, line] = Readline("user> ") diff --git a/vimscript/step3_env.vim b/vimscript/step3_env.vim index e8794ce536..42dee28cda 100644 --- a/vimscript/step3_env.vim +++ b/vimscript/step3_env.vim @@ -80,27 +80,11 @@ function REP(str, env) return PRINT(EVAL(READ(a:str), a:env)) endfunction -function MalAdd(args) - return IntegerNew(ObjValue(a:args[0]) + ObjValue(a:args[1])) -endfunction - -function MalSub(args) - return IntegerNew(ObjValue(a:args[0]) - ObjValue(a:args[1])) -endfunction - -function MalMul(args) - return IntegerNew(ObjValue(a:args[0]) * ObjValue(a:args[1])) -endfunction - -function MalDiv(args) - return IntegerNew(ObjValue(a:args[0]) / ObjValue(a:args[1])) -endfunction - let repl_env = NewEnv("") -call repl_env.set("+", function("MalAdd")) -call repl_env.set("-", function("MalSub")) -call repl_env.set("*", function("MalMul")) -call repl_env.set("/", function("MalDiv")) +call repl_env.set("+", {a -> IntegerNew(a[0].val + a[1].val)}) +call repl_env.set("-", {a -> IntegerNew(a[0].val - a[1].val)}) +call repl_env.set("*", {a -> IntegerNew(a[0].val * a[1].val)}) +call repl_env.set("/", {a -> IntegerNew(a[0].val / a[1].val)}) while 1 let [eof, line] = Readline("user> ") diff --git a/vimscript/tests/stepA_mal.mal b/vimscript/tests/stepA_mal.mal index 4cc645a303..da601484f9 100644 --- a/vimscript/tests/stepA_mal.mal +++ b/vimscript/tests/stepA_mal.mal @@ -33,9 +33,9 @@ ;; Test access to Vim predefined variables ;; -(vim* "v:progname") -;=>"vim" +;;; (vim* "v:progname") +;;; ;=>"vim" -;; v:version is 704 for Vim 7.4 -(> (vim* "v:version") 700) +;; v:version is 800 for Vim 8.0 +(>= (vim* "v:version") 800) ;=>true diff --git a/vimscript/types.vim b/vimscript/types.vim index b2480a8946..22e03fb07d 100644 --- a/vimscript/types.vim +++ b/vimscript/types.vim @@ -281,6 +281,11 @@ function NewNativeFn(funcname) return ObjNewWithMeta("nativefunction", fn, g:MalNil) endfunction +function NewNativeFnLambda(lambdaexpr) + let fn = {"Func": a:lambdaexpr, "name": "inline"} + return ObjNewWithMeta("nativefunction", fn, g:MalNil) +endfunction + let g:MalNil = NilNew() let g:MalTrue = TrueNew() let g:MalFalse = FalseNew() From 549763e9f6d9bc30f993f6e8cc58021fc8c2e2dd Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Tue, 25 Oct 2016 22:06:08 +0000 Subject: [PATCH 0193/2308] vimscript: Update Dockerfile to install Vim 8.0 --- vimscript/Dockerfile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/vimscript/Dockerfile b/vimscript/Dockerfile index edb0878247..35345f57fd 100644 --- a/vimscript/Dockerfile +++ b/vimscript/Dockerfile @@ -24,4 +24,11 @@ WORKDIR /mal # To build the readline plugin RUN apt-get -y install g++ -RUN apt-get -y install vim +# Vim 8.0 +RUN apt-get -y install bzip2 +RUN cd /tmp && curl -O ftp://ftp.vim.org/pub/vim/unix/vim-8.0.tar.bz2 \ + && tar xjf /tmp/vim-8.0.tar.bz2 \ + && cd vim80 && make && make install \ + && cd /tmp && rm -r /tmp/vim-8.0.tar.bz2 /tmp/vim80 + +ENV HOME /mal From 43d175390a65e981353cdad2c7fc6730e6184cdd Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Tue, 25 Oct 2016 22:43:28 +0000 Subject: [PATCH 0194/2308] vimscript: Simplify *ARGV* setting using map and lambda --- vimscript/step6_file.vim | 7 +------ vimscript/step7_quote.vim | 7 +------ vimscript/step8_macros.vim | 7 +------ vimscript/step9_try.vim | 7 +------ vimscript/stepA_mal.vim | 7 +------ 5 files changed, 5 insertions(+), 30 deletions(-) diff --git a/vimscript/step6_file.vim b/vimscript/step6_file.vim index 7cf7eec639..2ddf4bb35f 100644 --- a/vimscript/step6_file.vim +++ b/vimscript/step6_file.vim @@ -127,12 +127,7 @@ function REP(str, env) endfunction function GetArgvList() - let args = argv() - let list = [] - for arg in args[1:] - call add(list, StringNew(arg)) - endfor - return ListNew(list) + return ListNew(map(copy(argv()[1:]), {_, arg -> StringNew(arg)})) endfunction set maxfuncdepth=10000 diff --git a/vimscript/step7_quote.vim b/vimscript/step7_quote.vim index 390478fad0..0d33cf3a28 100644 --- a/vimscript/step7_quote.vim +++ b/vimscript/step7_quote.vim @@ -150,12 +150,7 @@ function REP(str, env) endfunction function GetArgvList() - let args = argv() - let list = [] - for arg in args[1:] - call add(list, StringNew(arg)) - endfor - return ListNew(list) + return ListNew(map(copy(argv()[1:]), {_, arg -> StringNew(arg)})) endfunction set maxfuncdepth=10000 diff --git a/vimscript/step8_macros.vim b/vimscript/step8_macros.vim index b04d873ae6..58f7cff40a 100644 --- a/vimscript/step8_macros.vim +++ b/vimscript/step8_macros.vim @@ -186,12 +186,7 @@ function REP(str, env) endfunction function GetArgvList() - let args = argv() - let list = [] - for arg in args[1:] - call add(list, StringNew(arg)) - endfor - return ListNew(list) + return ListNew(map(copy(argv()[1:]), {_, arg -> StringNew(arg)})) endfunction set maxfuncdepth=10000 diff --git a/vimscript/step9_try.vim b/vimscript/step9_try.vim index d74bda1447..688d1da648 100644 --- a/vimscript/step9_try.vim +++ b/vimscript/step9_try.vim @@ -218,12 +218,7 @@ function REP(str, env) endfunction function GetArgvList() - let args = argv() - let list = [] - for arg in args[1:] - call add(list, StringNew(arg)) - endfor - return ListNew(list) + return ListNew(map(copy(argv()[1:]), {_, arg -> StringNew(arg)})) endfunction set maxfuncdepth=10000 diff --git a/vimscript/stepA_mal.vim b/vimscript/stepA_mal.vim index 15c919d3cb..e28f0272c7 100644 --- a/vimscript/stepA_mal.vim +++ b/vimscript/stepA_mal.vim @@ -218,12 +218,7 @@ function REP(str, env) endfunction function GetArgvList() - let args = argv() - let list = [] - for arg in args[1:] - call add(list, StringNew(arg)) - endfor - return ListNew(list) + return ListNew(map(copy(argv()[1:]), {_, arg -> StringNew(arg)})) endfunction set maxfuncdepth=10000 From 80c5fbb13cf598ac88608bfd0c072ded1f858a86 Mon Sep 17 00:00:00 2001 From: Chris McCormick Date: Wed, 26 Oct 2016 08:56:57 +0800 Subject: [PATCH 0195/2308] Documentation for the web-run functionality. --- php/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 php/README.md diff --git a/php/README.md b/php/README.md new file mode 100644 index 0000000000..40dad01700 --- /dev/null +++ b/php/README.md @@ -0,0 +1,15 @@ +### Running .mal scripts on PHP hosting ### + +Create a symlink to `mal.php` with the same name as your `.mal` script and your script will be executed as if it was PHP. + +Here's an example using local dev: + + cd php + make mal.php + echo '(prn "Hello world!")' > myscript.mal + ln -s mal.php myscript.php + php -S 0.0.0.0:8000 + +Then browse to http://localhost:8000/myscript.php and you should see "Hello world!" in your browser as `myscript.mal` is run. + +You can do the same thing on live PHP web hosting by copying `mal.php` up and creating a symlink for each `.mal` file you want to be web-executable. From af621e3aecb235b13ccebd5051f91b269ae49f78 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 26 Oct 2016 01:26:05 -0500 Subject: [PATCH 0196/2308] Basic: implement CALL in basicpp.py and use it. - Add clean rule to Makefile and restructure deps. --- basic/Makefile | 25 ++++--- basic/basicpp.py | 113 ++++++++++++++++++++++-------- basic/core.in.bas | 58 +++++----------- basic/env.in.bas | 10 +-- basic/step0_repl.in.bas | 12 ++-- basic/step1_read_print.in.bas | 12 ++-- basic/step2_eval.in.bas | 24 +++---- basic/step3_env.in.bas | 33 ++++----- basic/step4_if_fn_do.in.bas | 61 ++++++---------- basic/step5_tco.in.bas | 60 ++++++---------- basic/step6_file.in.bas | 60 ++++++---------- basic/step7_quote.in.bas | 101 ++++++++++++--------------- basic/step8_macros.in.bas | 121 ++++++++++++++------------------ basic/step9_try.in.bas | 127 +++++++++++++++------------------- basic/stepA_mal.in.bas | 105 ++++++++++++---------------- 15 files changed, 423 insertions(+), 499 deletions(-) diff --git a/basic/Makefile b/basic/Makefile index 8263acdb9f..915e24cc4d 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -1,4 +1,4 @@ -BASICPP_OPTS = --number-lines --combine-lines +BASICPP_OPTS = --combine-lines step%.bas: step%.in.bas ./basicpp.py $(BASICPP_OPTS) $< > $@ @@ -9,16 +9,16 @@ step%.prg: step%.bas petcat -text -w2 -o $@ $<.tmp #rm $<.tmp -STEP0_DEPS = readline.in.bas -STEP1_DEPS = $(STEP0_DEPS) debug.in.bas types.in.bas reader.in.bas printer.in.bas -STEP3_DEPS = $(STEP1_DEPS) env.in.bas -STEP4_DEPS = $(STEP3_DEPS) core.in.bas +STEPS4_A = step4_if_fn_do.bas step5_tco.bas step6_file.bas \ + step7_quote.bas step8_macros.bas step9_try.bas stepA_mal.bas +STEPS3_A = step3_env.bas $(STEPS4_A) +STEPS1_A = step1_read_print.bas step2_eval.bas $(STEPS3_A) +STEPS0_A = step0_repl.bas $(STEPS1_A) -step0_repl.bas: $(STEP0_DEPS) -step1_read_print.bas step2_eval.bas: $(STEP1_DEPS) -step3_env.bas: $(STEP3_DEPS) -step4_if_fn_do.bas step5_tco.bas step6_file.bas step7_quote.bas: $(STEP4_DEPS) -step8_macros.bas step9_try.bas stepA_mal.bas: $(STEP4_DEPS) +$(STEPS0_A): readline.in.bas +$(STEPS1_A): debug.in.bas types.in.bas reader.in.bas printer.in.bas +$(STEPS3_A): env.in.bas +$(STEPS4_A): core.in.bas tests/%.bas: tests/%.in.bas ./basicpp.py $(BASICPP_OPTS) $< > $@ @@ -34,7 +34,10 @@ mal.prg: stepA_mal.prg SOURCES_LISP = env.in.bas core.in.bas stepA_mal.in.bas SOURCES = readline.in.bas types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP) -.PHONY: stats +.PHONY: clean stats + +clean: + rm -f $(STEPS0_A) $(subst .bas,.prg,$(STEPS0_A)) stats: $(SOURCES) @wc $^ diff --git a/basic/basicpp.py b/basic/basicpp.py index c122cf9d00..174e005d32 100755 --- a/basic/basicpp.py +++ b/basic/basicpp.py @@ -20,10 +20,6 @@ def parse_args(): help='Keep line identing') parser.add_argument('--skip-misc-fixups', action='store_true', default=False, help='Skip miscellaneous fixup/shrink fixups') - parser.add_argument('--number-lines', action='store_true', default=False, - help='Number the lines') - parser.add_argument('--keep-labels', action='store_true', default=False, - help='Keep string labels instead of replacing with line numbers') parser.add_argument('--combine-lines', action='store_true', default=False, help='Combine lines using the ":" separator') @@ -55,7 +51,7 @@ def resolve_includes(orig_lines, keep_rems=0): def drop_blank_lines(orig_lines): lines = [] for line in orig_lines: - if re.match(r"^\w*$", line): continue + if re.match(r"^\W*$", line): continue lines.append(line) return lines @@ -89,43 +85,105 @@ def misc_fixups(orig_lines): def finalize(lines, args): labels_lines = {} lines_labels = {} + call_index = {} - # number lines - if args.number_lines: - src_lines = lines - lines = [] - lnum=1 - for line in src_lines: - if not args.keep_labels: - m = re.match(r"^ *([^ ]*): *$", line) - if m: - labels_lines[m.groups(1)[0]] = lnum - lines_labels[lnum] = m.groups(1)[0] - continue - lines.append("%s %s" % (lnum, line)) + cur_sub = None + + # number lines, remove labels (but track line number), and replace + # CALLs with a stack based GOTO + src_lines = lines + lines = [] + lnum=1 + for line in src_lines: + + # Drop labels (track line number for GOTO/GOSUB) + m = re.match(r"^ *([^ ]*): *$", line) + if m: + label = m.groups(1)[0] + labels_lines[label] = lnum + lines_labels[lnum] = label + continue + + if re.match(r".*\bCALL *([^ :]*) *:", line): + raise Exception("CALL is not the last thing on line %s" % lnum) + + # Replace CALLs (track line number for replacement later) + #m = re.match(r"\bCALL *([^ :]*) *$", line) + m = re.match(r"(.*)\bCALL *([^ :]*) *$", line) + if m: + prefix = m.groups(1)[0] + sub = m.groups(1)[1] + if not call_index.has_key(sub): + call_index[sub] = 0 + call_index[sub] += 1 + label = sub+"_"+str(call_index[sub]) + + # Replace the CALL with stack based GOTO + lines.append("%s %sX=X+1:X%%(X)=%s:GOTO %s" % ( + lnum, prefix, call_index[sub], sub)) lnum += 1 - def update_labels_lines(text, a,b): + # Add the return spot + labels_lines[label] = lnum + lines_labels[lnum] = label + lines.append("%s X=X-1" % lnum) + lnum += 1 + continue + + lines.append("%s %s" % (lnum, line)) + lnum += 1 + + # remove SUB (but track lines), and replace END SUB with ON GOTO + # that returns to original caller + src_lines = lines + lines = [] + lnum=1 + for line in src_lines: + # Drop subroutine defs (track line number for CALLS) + m = re.match(r"^([0-9][0-9]*) *SUB *([^ ]*) *$", line) + if m: + lnum = int(m.groups(1)[0])+1 + label = m.groups(1)[1] + cur_sub = label + labels_lines[label] = lnum + lines_labels[lnum] = label + continue + + # Drop END SUB (track line number for replacement later) + m = re.match(r"^([0-9][0-9]*) *END SUB *$", line) + if m: + if cur_sub == None: + raise Exception("END SUB found without preceeding SUB") + lnum = int(m.groups(1)[0]) + index = call_index[cur_sub] + + ret_labels = [cur_sub+"_"+str(i) for i in range(1, index+1)] + line = "%s ON X%%(X) GOTO %s" % (lnum, ",".join(ret_labels)) + cur_sub = None + + lines.append(line) + + def update_labels_lines(text, a, b): stext = "" while stext != text: stext = text text = re.sub(r"(THEN) %s\b" % a, r"THEN %s" % b, stext) #text = re.sub(r"(THEN)%s\b" % a, r"THEN%s" % b, stext) text = re.sub(r"(ON [^:\n]* GOTO [^:\n]*)\b%s\b" % a, r"\g<1>%s" % b, text) - text = re.sub(r"(ON [^:\n]* GOSUB [^:\n]*)\b%s\b" % a, r"\g<2>%s" % b, text) + text = re.sub(r"(ON [^:\n]* GOSUB [^:\n]*)\b%s\b" % a, r"\g<1>%s" % b, text) text = re.sub(r"(GOSUB) %s\b" % a, r"\1 %s" % b, text) text = re.sub(r"(GOTO) %s\b" % a, r"\1 %s" % b, text) #text = re.sub(r"(GOTO)%s\b" % a, r"\1%s" % b, text) return text - if not args.keep_labels: - src_lines = lines - text = "\n".join(lines) - # search for and replace GOTO/GOSUBs - for label, lnum in labels_lines.items(): - text = update_labels_lines(text, label, lnum) - lines = text.split("\n") + # search for and replace GOTO/GOSUBs + src_lines = lines + text = "\n".join(lines) + for label, lnum in labels_lines.items(): + text = update_labels_lines(text, label, lnum) + lines = text.split("\n") + # combine lines if args.combine_lines: renumber = {} src_lines = lines @@ -138,7 +196,6 @@ def renum(line): return "%s %s" % (lnum, line) while pos < len(src_lines): line = src_lines[pos] - # TODO: handle args.keep_labels and (not args.number_lines) m = re.match(r"^([0-9]*) (.*)$", line) old_num = int(m.group(1)) line = m.group(2) diff --git a/basic/core.in.bas b/basic/core.in.bas index 5e0bc876fc..0f59cd5b12 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -1,12 +1,11 @@ REM APPLY should really be in types.in.bas but it is here because it -REM has return labels into DO_TCO_FUNCTION so it will cause syntax -REM errors for steps1-3 if it is in types.in.bas because there are -REM unresolved labels. +REM calls DO_TCO_FUNCTION so it will cause syntax errors for steps1-3 +REM if it is in types.in.bas because there are unresolved labels. REM APPLY(F, AR) -> R REM - restores E REM - call using GOTO and with return label/address on the stack -APPLY: +SUB APPLY REM if metadata, get the actual object IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) @@ -16,10 +15,9 @@ APPLY: APPLY_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_APPLY + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO APPLY_DONE REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN X=X+1:X%(X)=1:GOTO DO_TCO_FUNCTION - DO_TCO_FUNCTION_RETURN_APPLY: + IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION GOTO APPLY_DONE APPLY_MAL_FUNCTION: @@ -29,22 +27,18 @@ APPLY: REM function and bind the params to the apply arguments O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS - A=Z%(F,1):E=R:GOSUB EVAL + A=Z%(F,1):E=R:CALL EVAL AY=E:GOSUB RELEASE: REM release the new environment E=X%(X):X=X-1: REM pop/restore the saved environment APPLY_DONE: - REM pop APPLY return label/address - RN=X%(X):X=X-1 - ON RN GOTO APPLY_RETURN_1,APPLY_RETURN_2,APPLY_RETURN_MAP,APPLY_RETURN_SWAP,APPLY_RETURN_MACROEXPAND +END SUB REM DO_TCO_FUNCTION(F, AR) -REM - similar to DO_FUNCTION but non-GOSUB version for potentially -REM recursive function (apply, map, swap!) -DO_TCO_FUNCTION: +SUB DO_TCO_FUNCTION FF=Z%(F,1) REM Get argument values @@ -75,23 +69,17 @@ DO_TCO_FUNCTION: GOTO DO_APPLY_2 DO_APPLY_1: - X=X+1:X%(X)=1: REM push APPLY return label/address - AR=A:GOTO APPLY - REM APPLY return label/address popped by APPLY - APPLY_RETURN_1: + AR=A:CALL APPLY - GOTO DO_TCO_FUNCTION_RETURN + GOTO DO_TCO_FUNCTION_DONE DO_APPLY_2: X=X+1:X%(X)=R: REM push/save new args for release - X=X+1:X%(X)=2: REM push APPLY return label/address - AR=R:GOTO APPLY - REM APPLY return label/address popped by APPLY - APPLY_RETURN_2: + AR=R:CALL APPLY AY=X%(X):X=X-1:GOSUB RELEASE: REM pop/release new args - GOTO DO_TCO_FUNCTION_RETURN + GOTO DO_TCO_FUNCTION_DONE DO_MAP: F=AA @@ -118,10 +106,7 @@ DO_TCO_FUNCTION: REM push argument list X=X+1:X%(X)=R - X=X+1:X%(X)=3: REM push APPLY return label/address - AR=R:GOTO APPLY - REM APPLY return label/address popped by APPLY - APPLY_RETURN_MAP: + AR=R:CALL APPLY REM pop apply args are release them AY=X%(X):X=X-1:GOSUB RELEASE @@ -146,7 +131,7 @@ DO_TCO_FUNCTION: R=X%(X-3) REM pop everything off stack X=X-4 - GOTO DO_TCO_FUNCTION_RETURN + GOTO DO_TCO_FUNCTION_DONE DO_SWAP_BANG: @@ -162,10 +147,7 @@ DO_TCO_FUNCTION: REM push atom X=X+1:X%(X)=AA - X=X+1:X%(X)=4: REM push APPLY return label/address - GOTO APPLY - REM APPLY return label/address popped by APPLY - APPLY_RETURN_SWAP: + CALL APPLY REM pop atom AA=X%(X):X=X-1 @@ -179,12 +161,10 @@ DO_TCO_FUNCTION: REM but decrease ref cnt of return by 1 (not sure why) AY=R:GOSUB RELEASE - GOTO DO_TCO_FUNCTION_RETURN + GOTO DO_TCO_FUNCTION_DONE - DO_TCO_FUNCTION_RETURN: - REM pop EVAL AST return label/address - RN=X%(X):X=X-1 - ON RN GOTO DO_TCO_FUNCTION_RETURN_APPLY,DO_TCO_FUNCTION_RETURN_EVAL + DO_TCO_FUNCTION_DONE: +END SUB REM DO_FUNCTION(F, AR) @@ -530,7 +510,7 @@ DO_FUNCTION: REM RETURN DO_EVAL: - A=AA:E=D:GOSUB EVAL + A=AA:E=D:CALL EVAL RETURN DO_READ_FILE: diff --git a/basic/env.in.bas b/basic/env.in.bas index 792f378032..a637732f4e 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -69,7 +69,7 @@ ENV_SET_S: REM ENV_FIND(E, K) -> R REM Returns environment (R) containing K. If found, value found is REM in T4 -ENV_FIND: +SUB ENV_FIND EF=E ENV_FIND_LOOP: H=Z%(EF,1) @@ -81,12 +81,12 @@ ENV_FIND: IF EF<>-1 THEN GOTO ENV_FIND_LOOP ENV_FIND_DONE: R=EF - RETURN +END SUB REM ENV_GET(E, K) -> R ENV_GET: - GOSUB ENV_FIND - IF R=-1 THEN R=0:ER=-1:ER$="'"+S$(Z%(K,1))+"' not found":RETURN + CALL ENV_FIND + IF R=-1 THEN R=0:ER=-1:ER$="'"+S$(Z%(K,1))+"' not found":GOTO ENV_GET_RETURN R=T4:GOSUB DEREF_R Z%(R,0)=Z%(R,0)+32 - RETURN + GOTO ENV_GET_RETURN diff --git a/basic/step0_repl.in.bas b/basic/step0_repl.in.bas index 2431fd325c..1c3db7c4e0 100755 --- a/basic/step0_repl.in.bas +++ b/basic/step0_repl.in.bas @@ -8,9 +8,9 @@ MAL_READ: RETURN REM EVAL(A$, E) -> R$ -EVAL: +SUB EVAL R$=A$ - RETURN +END SUB REM PRINT(A$) -> R$ MAL_PRINT: @@ -18,11 +18,11 @@ MAL_PRINT: RETURN REM REP(A$) -> R$ -REP: +SUB REP GOSUB MAL_READ - A=R:GOSUB EVAL + A=R:CALL EVAL A=R:GOSUB MAL_PRINT - RETURN +END SUB REM MAIN program MAIN: @@ -30,7 +30,7 @@ MAIN: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOSUB REP: REM call REP + A$=R$:CALL REP: REM call REP PRINT R$ GOTO REPL_LOOP diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index b8bcf52e72..ae99f752cd 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -13,9 +13,9 @@ MAL_READ: RETURN REM EVAL(A, E) -> R -EVAL: +SUB EVAL R=A - RETURN +END SUB REM PRINT(A) -> R$ MAL_PRINT: @@ -23,11 +23,11 @@ MAL_PRINT: RETURN REM REP(A$) -> R$ -REP: +SUB REP GOSUB MAL_READ IF ER<>-2 THEN GOTO REP_DONE - A=R:GOSUB EVAL + A=R:CALL EVAL IF ER<>-2 THEN GOTO REP_DONE A=R:GOSUB MAL_PRINT @@ -37,7 +37,7 @@ REP: REM Release memory from EVAL AY=R:GOSUB RELEASE R$=RT$ - RETURN +END SUB REM MAIN program MAIN: @@ -49,7 +49,7 @@ MAIN: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOSUB REP: REM call REP + A$=R$:CALL REP: REM call REP IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index f8bfc7edde..7f399124e0 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -13,7 +13,7 @@ MAL_READ: RETURN REM EVAL_AST(A, E) -> R -EVAL_AST: +SUB EVAL_AST LV=LV+1 REM push A and E on the stack @@ -72,7 +72,7 @@ EVAL_AST: EVAL_AST_DO_EVAL: REM call EVAL for each entry - A=A+1:GOSUB EVAL + A=A+1:CALL EVAL A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry @@ -108,10 +108,10 @@ EVAL_AST: E=X%(X-1):A=X%(X):X=X-2 LV=LV-1 - RETURN +END SUB -REM EVAL(A, E)) -> R -EVAL: +REM EVAL(A, E) -> R +SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack @@ -125,7 +125,7 @@ EVAL: GOSUB LIST_Q IF R THEN GOTO APPLY_LIST REM ELSE - GOSUB EVAL_AST + CALL EVAL_AST GOTO EVAL_RETURN APPLY_LIST: @@ -133,7 +133,7 @@ EVAL: IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN EVAL_INVOKE: - GOSUB EVAL_AST + CALL EVAL_AST R3=R REM if error, return f/args for release by caller @@ -158,7 +158,7 @@ EVAL: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 - RETURN +END SUB REM DO_FUNCTION(F, AR) DO_FUNCTION: @@ -204,13 +204,13 @@ MAL_PRINT: REM REP(A$) -> R$ REM Assume D has repl_env -REP: +SUB REP R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -222,7 +222,7 @@ REP: IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ - RETURN +END SUB REM MAIN program MAIN: @@ -255,7 +255,7 @@ MAIN: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOSUB REP: REM call REP + A$=R$:CALL REP: REM call REP IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index ddf5d99dbd..5393eff194 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -14,7 +14,7 @@ MAL_READ: RETURN REM EVAL_AST(A, E) -> R -EVAL_AST: +SUB EVAL_AST LV=LV+1 REM push A and E on the stack @@ -34,7 +34,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K=A:GOSUB ENV_GET + K=A:GOTO ENV_GET + ENV_GET_RETURN: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: @@ -70,7 +71,7 @@ EVAL_AST: EVAL_AST_DO_EVAL: REM call EVAL for each entry - A=A+1:GOSUB EVAL + A=A+1:CALL EVAL A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry @@ -109,10 +110,10 @@ EVAL_AST: E=X%(X-1):A=X%(X):X=X-2 LV=LV-1 - RETURN +END SUB -REM EVAL(A, E)) -> R -EVAL: +REM EVAL(A, E) -> R +SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack @@ -126,7 +127,7 @@ EVAL: GOSUB LIST_Q IF R THEN GOTO APPLY_LIST REM ELSE - GOSUB EVAL_AST + CALL EVAL_AST GOTO EVAL_RETURN APPLY_LIST: @@ -160,7 +161,7 @@ EVAL: GOSUB EVAL_GET_A2: REM set A1 and A2 X=X+1:X%(X)=A1: REM push A1 - A=A2:GOSUB EVAL: REM eval a2 + A=A2:CALL EVAL: REM eval a2 A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN @@ -182,7 +183,7 @@ EVAL: X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:GOSUB EVAL + A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 REM set environment: even A1 key to odd A1 eval'd above @@ -195,10 +196,10 @@ EVAL: EVAL_LET_LOOP_DONE: A2=X%(X):X=X-1: REM pop A2 - A=A2:GOSUB EVAL: REM eval A2 using let_env + A=A2:CALL EVAL: REM eval A2 using let_env GOTO EVAL_RETURN EVAL_INVOKE: - GOSUB EVAL_AST + CALL EVAL_AST R3=R REM if error, return f/args for release by caller @@ -228,7 +229,7 @@ EVAL: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 - RETURN +END SUB REM DO_FUNCTION(F, AR) DO_FUNCTION: @@ -274,13 +275,13 @@ MAL_PRINT: REM REP(A$) -> R$ REM Assume D has repl_env -REP: +SUB REP R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -292,7 +293,7 @@ REP: IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ - RETURN +END SUB REM MAIN program MAIN: @@ -326,7 +327,7 @@ MAIN: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOSUB REP: REM call REP + A$=R$:CALL REP: REM call REP IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index 1b10b86acd..ab26bacb8d 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -15,9 +15,7 @@ MAL_READ: RETURN REM EVAL_AST(A, E) -> R -REM called using GOTO to avoid basic return address stack usage -REM top of stack should have return label index -EVAL_AST: +SUB EVAL_AST REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A @@ -35,7 +33,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K=A:GOSUB ENV_GET + K=A:GOTO ENV_GET + ENV_GET_RETURN: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: @@ -71,7 +70,7 @@ EVAL_AST: EVAL_AST_DO_EVAL: REM call EVAL for each entry - A=A+1:GOSUB EVAL + A=A+1:CALL EVAL A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry @@ -108,13 +107,10 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 +END SUB - REM pop EVAL AST return label/address - RN=X%(X):X=X-1 - ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 - -REM EVAL(A, E)) -> R -EVAL: +REM EVAL(A, E) -> R +SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack @@ -132,11 +128,7 @@ EVAL: GOSUB LIST_Q IF R THEN GOTO APPLY_LIST REM ELSE - REM push EVAL_AST return label/address - X=X+1:X%(X)=1 - GOTO EVAL_AST - EVAL_AST_RETURN_1: - + CALL EVAL_AST GOTO EVAL_RETURN APPLY_LIST: @@ -173,7 +165,7 @@ EVAL: GOSUB EVAL_GET_A2: REM set A1 and A2 X=X+1:X%(X)=A1: REM push A1 - A=A2:GOSUB EVAL: REM eval a2 + A=A2:CALL EVAL: REM eval a2 A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN @@ -195,7 +187,7 @@ EVAL: X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:GOSUB EVAL + A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 REM set environment: even A1 key to odd A1 eval'd above @@ -208,16 +200,12 @@ EVAL: EVAL_LET_LOOP_DONE: A2=X%(X):X=X-1: REM pop A2 - A=A2:GOSUB EVAL: REM eval A2 using let_env + A=A2:CALL EVAL: REM eval A2 using let_env GOTO EVAL_RETURN EVAL_DO: A=Z%(A,1): REM rest - REM push EVAL_AST return label/address - X=X+1:X%(X)=2 - GOTO EVAL_AST - REM return label/address already popped by EVAL_AST - EVAL_AST_RETURN_2: + CALL EVAL_AST X=X+1:X%(X)=R: REM push eval'd list A=R:GOSUB LAST: REM return the last element @@ -229,7 +217,7 @@ EVAL: GOSUB EVAL_GET_A1: REM set A1 REM push A X=X+1:X%(X)=A - A=A1:GOSUB EVAL + A=A1:CALL EVAL REM pop A A=X%(X):X=X-1 IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE @@ -251,10 +239,7 @@ EVAL: GOTO EVAL_RETURN EVAL_INVOKE: - REM push EVAL_AST return label/address - X=X+1:X%(X)=3 - GOTO EVAL_AST - EVAL_AST_RETURN_3: + CALL EVAL_AST REM if error, return f/args for release by caller IF ER<>-2 THEN GOTO EVAL_RETURN @@ -279,10 +264,10 @@ EVAL: EVAL_DO_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION - DO_TCO_FUNCTION_RETURN_EVAL: + IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION + EVAL_DO_FUNCTION_SKIP: REM pop and release f/args AY=X%(X):X=X-1:GOSUB RELEASE @@ -329,7 +314,7 @@ EVAL: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 - RETURN +END SUB REM PRINT(A) -> R$ MAL_PRINT: @@ -345,7 +330,7 @@ RE: R1=R IF ER<>-2 THEN GOTO RE_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL RE_DONE: REM Release memory from MAL_READ @@ -354,13 +339,13 @@ RE: REM REP(A$) -> R$ REM Assume D has repl_env -REP: +SUB REP R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -372,7 +357,7 @@ REP: IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ - RETURN +END SUB REM MAIN program MAIN: @@ -396,7 +381,7 @@ MAIN: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOSUB REP: REM call REP + A$=R$:CALL REP: REM call REP IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index 731e18c025..1e7e28098c 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -15,9 +15,7 @@ MAL_READ: RETURN REM EVAL_AST(A, E) -> R -REM called using GOTO to avoid basic return address stack usage -REM top of stack should have return label index -EVAL_AST: +SUB EVAL_AST REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A @@ -35,7 +33,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K=A:GOSUB ENV_GET + K=A:GOTO ENV_GET + ENV_GET_RETURN: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: @@ -74,7 +73,7 @@ EVAL_AST: EVAL_AST_DO_EVAL: REM call EVAL for each entry - A=A+1:GOSUB EVAL + A=A+1:CALL EVAL A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry @@ -111,13 +110,10 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 +END SUB - REM pop EVAL AST return label/address - RN=X%(X):X=X-1 - ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 - -REM EVAL(A, E)) -> R -EVAL: +REM EVAL(A, E) -> R +SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack @@ -135,11 +131,7 @@ EVAL: GOSUB LIST_Q IF R THEN GOTO APPLY_LIST REM ELSE - REM push EVAL_AST return label/address - X=X+1:X%(X)=1 - GOTO EVAL_AST - EVAL_AST_RETURN_1: - + CALL EVAL_AST GOTO EVAL_RETURN APPLY_LIST: @@ -176,7 +168,7 @@ EVAL: GOSUB EVAL_GET_A2: REM set A1 and A2 X=X+1:X%(X)=A1: REM push A1 - A=A2:GOSUB EVAL: REM eval a2 + A=A2:CALL EVAL: REM eval a2 A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN @@ -200,7 +192,7 @@ EVAL: X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:GOSUB EVAL + A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 REM set environment: even A1 key to odd A1 eval'd above @@ -224,11 +216,7 @@ EVAL: A=Z%(A,1): REM rest X=X+1:X%(X)=A: REM push/save A - REM push EVAL_AST return label/address - X=X+1:X%(X)=2 - GOTO EVAL_AST - REM return label/address already popped by EVAL_AST - EVAL_AST_RETURN_2: + CALL EVAL_AST REM cleanup AY=R: REM get eval'd list for release @@ -247,7 +235,7 @@ EVAL: GOSUB EVAL_GET_A1: REM set A1 REM push A X=X+1:X%(X)=A - A=A1:GOSUB EVAL + A=A1:CALL EVAL REM pop A A=X%(X):X=X-1 IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE @@ -269,10 +257,7 @@ EVAL: GOTO EVAL_RETURN EVAL_INVOKE: - REM push EVAL_AST return label/address - X=X+1:X%(X)=3 - GOTO EVAL_AST - EVAL_AST_RETURN_3: + CALL EVAL_AST REM if error, return f/args for release by caller IF ER<>-2 THEN GOTO EVAL_RETURN @@ -297,10 +282,10 @@ EVAL: EVAL_DO_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION - DO_TCO_FUNCTION_RETURN_EVAL: + IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION + EVAL_DO_FUNCTION_SKIP: REM pop and release f/args AY=X%(X):X=X-1:GOSUB RELEASE @@ -347,7 +332,7 @@ EVAL: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 - RETURN +END SUB REM PRINT(A) -> R$ MAL_PRINT: @@ -363,7 +348,7 @@ RE: R1=R IF ER<>-2 THEN GOTO RE_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL RE_DONE: REM Release memory from MAL_READ @@ -372,13 +357,13 @@ RE: REM REP(A$) -> R$ REM Assume D has repl_env -REP: +SUB REP R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -390,7 +375,7 @@ REP: IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ - GOTO REP_RETURN +END SUB REM MAIN program MAIN: @@ -414,8 +399,7 @@ MAIN: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOTO REP: REM call REP - REP_RETURN: + A$=R$:CALL REP: REM call REP IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index 6b8ee7ef33..0c80ae6db5 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -15,9 +15,7 @@ MAL_READ: RETURN REM EVAL_AST(A, E) -> R -REM called using GOTO to avoid basic return address stack usage -REM top of stack should have return label index -EVAL_AST: +SUB EVAL_AST REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A @@ -35,7 +33,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K=A:GOSUB ENV_GET + K=A:GOTO ENV_GET + ENV_GET_RETURN: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: @@ -74,7 +73,7 @@ EVAL_AST: EVAL_AST_DO_EVAL: REM call EVAL for each entry - A=A+1:GOSUB EVAL + A=A+1:CALL EVAL A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry @@ -111,13 +110,10 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 +END SUB - REM pop EVAL AST return label/address - RN=X%(X):X=X-1 - ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 - -REM EVAL(A, E)) -> R -EVAL: +REM EVAL(A, E) -> R +SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack @@ -135,11 +131,7 @@ EVAL: GOSUB LIST_Q IF R THEN GOTO APPLY_LIST REM ELSE - REM push EVAL_AST return label/address - X=X+1:X%(X)=1 - GOTO EVAL_AST - EVAL_AST_RETURN_1: - + CALL EVAL_AST GOTO EVAL_RETURN APPLY_LIST: @@ -176,7 +168,7 @@ EVAL: GOSUB EVAL_GET_A2: REM set A1 and A2 X=X+1:X%(X)=A1: REM push A1 - A=A2:GOSUB EVAL: REM eval a2 + A=A2:CALL EVAL: REM eval a2 A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN @@ -200,7 +192,7 @@ EVAL: X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:GOSUB EVAL + A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 REM set environment: even A1 key to odd A1 eval'd above @@ -224,11 +216,7 @@ EVAL: A=Z%(A,1): REM rest X=X+1:X%(X)=A: REM push/save A - REM push EVAL_AST return label/address - X=X+1:X%(X)=2 - GOTO EVAL_AST - REM return label/address already popped by EVAL_AST - EVAL_AST_RETURN_2: + CALL EVAL_AST REM cleanup AY=R: REM get eval'd list for release @@ -247,7 +235,7 @@ EVAL: GOSUB EVAL_GET_A1: REM set A1 REM push A X=X+1:X%(X)=A - A=A1:GOSUB EVAL + A=A1:CALL EVAL REM pop A A=X%(X):X=X-1 IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE @@ -269,10 +257,7 @@ EVAL: GOTO EVAL_RETURN EVAL_INVOKE: - REM push EVAL_AST return label/address - X=X+1:X%(X)=3 - GOTO EVAL_AST - EVAL_AST_RETURN_3: + CALL EVAL_AST REM if error, return f/args for release by caller IF ER<>-2 THEN GOTO EVAL_RETURN @@ -297,10 +282,10 @@ EVAL: EVAL_DO_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION - DO_TCO_FUNCTION_RETURN_EVAL: + IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION + EVAL_DO_FUNCTION_SKIP: REM pop and release f/args AY=X%(X):X=X-1:GOSUB RELEASE @@ -347,7 +332,7 @@ EVAL: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 - RETURN +END SUB REM PRINT(A) -> R$ MAL_PRINT: @@ -363,7 +348,7 @@ RE: R1=R IF ER<>-2 THEN GOTO RE_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL RE_DONE: REM Release memory from MAL_READ @@ -372,13 +357,13 @@ RE: REM REP(A$) -> R$ REM Assume D has repl_env -REP: +SUB REP R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -390,7 +375,7 @@ REP: IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ - GOTO REP_RETURN +END SUB REM MAIN program MAIN: @@ -441,8 +426,7 @@ MAIN: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOTO REP: REM call REP - REP_RETURN: + A$=R$:CALL REP: REM call REP IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index f72c2b7b74..e72e072451 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -14,24 +14,20 @@ MAL_READ: GOSUB READ_STR RETURN -REM PAIR_Q(B) -> R -PAIR_Q: - R=0 - IF (Z%(B,0)AND31)<>6 AND (Z%(B,0)AND31)<>7 THEN RETURN - IF (Z%(B,1)=0) THEN RETURN - R=1 - RETURN - REM QUASIQUOTE(A) -> R -QUASIQUOTE: - B=A:GOSUB PAIR_Q - IF R=1 THEN GOTO QQ_UNQUOTE +SUB QUASIQUOTE + REM pair? + IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_QUOTE + IF (Z%(A,1)=0) THEN GOTO QQ_QUOTE + GOTO QQ_UNQUOTE + + QQ_QUOTE: REM ['quote, ast] AS$="quote":T=5:GOSUB STRING B2=R:B1=A:GOSUB LIST2 AY=B2:GOSUB RELEASE - RETURN + GOTO QQ_DONE QQ_UNQUOTE: R=A+1:GOSUB DEREF_R @@ -41,21 +37,24 @@ QUASIQUOTE: R=Z%(A,1)+1:GOSUB DEREF_R Z%(R,0)=Z%(R,0)+32 - RETURN + GOTO QQ_DONE QQ_SPLICE_UNQUOTE: REM push A on the stack X=X+1:X%(X)=A REM rest of cases call quasiquote on ast[1..] - A=Z%(A,1):GOSUB QUASIQUOTE:T6=R + A=Z%(A,1):CALL QUASIQUOTE + T6=R REM pop A off the stack A=X%(X):X=X-1 REM set A to ast[0] for last two cases A=A+1:GOSUB DEREF_A - B=A:GOSUB PAIR_Q - IF R=0 THEN GOTO QQ_DEFAULT + REM pair? + IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_DEFAULT + IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT + B=A+1:GOSUB DEREF_B IF (Z%(B,0)AND31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT @@ -67,7 +66,7 @@ QUASIQUOTE: REM release inner quasiquoted since outer list takes ownership AY=B1:GOSUB RELEASE AY=B3:GOSUB RELEASE - RETURN + GOTO QQ_DONE QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] @@ -75,7 +74,8 @@ QUASIQUOTE: REM push T6 on the stack X=X+1:X%(X)=T6 REM A set above to ast[0] - GOSUB QUASIQUOTE:B2=R + CALL QUASIQUOTE + B2=R REM pop T6 off the stack T6=X%(X):X=X-1 @@ -85,13 +85,12 @@ QUASIQUOTE: AY=B1:GOSUB RELEASE AY=B2:GOSUB RELEASE AY=B3:GOSUB RELEASE - RETURN + QQ_DONE: +END SUB REM EVAL_AST(A, E) -> R -REM called using GOTO to avoid basic return address stack usage -REM top of stack should have return label index -EVAL_AST: +SUB EVAL_AST REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A @@ -109,7 +108,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K=A:GOSUB ENV_GET + K=A:GOTO ENV_GET + ENV_GET_RETURN: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: @@ -148,7 +148,7 @@ EVAL_AST: EVAL_AST_DO_EVAL: REM call EVAL for each entry - A=A+1:GOSUB EVAL + A=A+1:CALL EVAL A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry @@ -185,13 +185,10 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 +END SUB - REM pop EVAL AST return label/address - RN=X%(X):X=X-1 - ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 - -REM EVAL(A, E)) -> R -EVAL: +REM EVAL(A, E) -> R +SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack @@ -209,11 +206,7 @@ EVAL: GOSUB LIST_Q IF R THEN GOTO APPLY_LIST REM ELSE - REM push EVAL_AST return label/address - X=X+1:X%(X)=1 - GOTO EVAL_AST - EVAL_AST_RETURN_1: - + CALL EVAL_AST GOTO EVAL_RETURN APPLY_LIST: @@ -252,7 +245,7 @@ EVAL: GOSUB EVAL_GET_A2: REM set A1 and A2 X=X+1:X%(X)=A1: REM push A1 - A=A2:GOSUB EVAL: REM eval a2 + A=A2:CALL EVAL: REM eval a2 A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN @@ -276,7 +269,7 @@ EVAL: X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:GOSUB EVAL + A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 REM set environment: even A1 key to odd A1 eval'd above @@ -300,11 +293,7 @@ EVAL: A=Z%(A,1): REM rest X=X+1:X%(X)=A: REM push/save A - REM push EVAL_AST return label/address - X=X+1:X%(X)=2 - GOTO EVAL_AST - REM return label/address already popped by EVAL_AST - EVAL_AST_RETURN_2: + CALL EVAL_AST REM cleanup AY=R: REM get eval'd list for release @@ -326,7 +315,7 @@ EVAL: EVAL_QUASIQUOTE: R=Z%(A,1)+1:GOSUB DEREF_R - A=R:GOSUB QUASIQUOTE + A=R:CALL QUASIQUOTE REM add quasiquote result to pending release queue to free when REM next lower EVAL level returns (LV) Y=Y+1:Y%(Y,0)=R:Y%(Y,1)=LV @@ -337,7 +326,7 @@ EVAL: GOSUB EVAL_GET_A1: REM set A1 REM push A X=X+1:X%(X)=A - A=A1:GOSUB EVAL + A=A1:CALL EVAL REM pop A A=X%(X):X=X-1 IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE @@ -359,10 +348,7 @@ EVAL: GOTO EVAL_RETURN EVAL_INVOKE: - REM push EVAL_AST return label/address - X=X+1:X%(X)=3 - GOTO EVAL_AST - EVAL_AST_RETURN_3: + CALL EVAL_AST REM if error, return f/args for release by caller IF ER<>-2 THEN GOTO EVAL_RETURN @@ -387,10 +373,10 @@ EVAL: EVAL_DO_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION - DO_TCO_FUNCTION_RETURN_EVAL: + IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION + EVAL_DO_FUNCTION_SKIP: REM pop and release f/args AY=X%(X):X=X-1:GOSUB RELEASE @@ -437,7 +423,7 @@ EVAL: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 - RETURN +END SUB REM PRINT(A) -> R$ MAL_PRINT: @@ -453,7 +439,7 @@ RE: R1=R IF ER<>-2 THEN GOTO RE_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL RE_DONE: REM Release memory from MAL_READ @@ -462,13 +448,13 @@ RE: REM REP(A$) -> R$ REM Assume D has repl_env -REP: +SUB REP R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -480,7 +466,7 @@ REP: IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ - GOTO REP_RETURN +END SUB REM MAIN program MAIN: @@ -531,8 +517,7 @@ MAIN: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOTO REP: REM call REP - REP_RETURN: + A$=R$:CALL REP: REM call REP IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index b33214dd2c..225289f559 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -16,24 +16,20 @@ MAL_READ: GOSUB READ_STR RETURN -REM PAIR_Q(B) -> R -PAIR_Q: - R=0 - IF (Z%(B,0)AND31)<>6 AND (Z%(B,0)AND31)<>7 THEN RETURN - IF (Z%(B,1)=0) THEN RETURN - R=1 - RETURN - REM QUASIQUOTE(A) -> R -QUASIQUOTE: - B=A:GOSUB PAIR_Q - IF R=1 THEN GOTO QQ_UNQUOTE +SUB QUASIQUOTE + REM pair? + IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_QUOTE + IF (Z%(A,1)=0) THEN GOTO QQ_QUOTE + GOTO QQ_UNQUOTE + + QQ_QUOTE: REM ['quote, ast] AS$="quote":T=5:GOSUB STRING B2=R:B1=A:GOSUB LIST2 AY=B2:GOSUB RELEASE - RETURN + GOTO QQ_DONE QQ_UNQUOTE: R=A+1:GOSUB DEREF_R @@ -43,21 +39,24 @@ QUASIQUOTE: R=Z%(A,1)+1:GOSUB DEREF_R Z%(R,0)=Z%(R,0)+32 - RETURN + GOTO QQ_DONE QQ_SPLICE_UNQUOTE: REM push A on the stack X=X+1:X%(X)=A REM rest of cases call quasiquote on ast[1..] - A=Z%(A,1):GOSUB QUASIQUOTE:T6=R + A=Z%(A,1):CALL QUASIQUOTE + T6=R REM pop A off the stack A=X%(X):X=X-1 REM set A to ast[0] for last two cases A=A+1:GOSUB DEREF_A - B=A:GOSUB PAIR_Q - IF R=0 THEN GOTO QQ_DEFAULT + REM pair? + IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_DEFAULT + IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT + B=A+1:GOSUB DEREF_B IF (Z%(B,0)AND31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT @@ -69,7 +68,7 @@ QUASIQUOTE: REM release inner quasiquoted since outer list takes ownership AY=B1:GOSUB RELEASE AY=B3:GOSUB RELEASE - RETURN + GOTO QQ_DONE QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] @@ -77,7 +76,8 @@ QUASIQUOTE: REM push T6 on the stack X=X+1:X%(X)=T6 REM A set above to ast[0] - GOSUB QUASIQUOTE:B2=R + CALL QUASIQUOTE + B2=R REM pop T6 off the stack T6=X%(X):X=X-1 @@ -87,10 +87,11 @@ QUASIQUOTE: AY=B1:GOSUB RELEASE AY=B2:GOSUB RELEASE AY=B3:GOSUB RELEASE - RETURN + QQ_DONE: +END SUB REM MACROEXPAND(A, E) -> A: -MACROEXPAND: +SUB MACROEXPAND REM push original A X=X+1:X%(X)=A @@ -103,18 +104,13 @@ MACROEXPAND: REM symbol? in first position IF (Z%(B,0)AND31)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? - K=B:GOSUB ENV_FIND + K=B:CALL ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE B=T4:GOSUB DEREF_B REM macro? IF (Z%(B,0)AND31)<>11 THEN GOTO MACROEXPAND_DONE - REM apply - X=X+1:X%(X)=5: REM push APPLY return label/address - F=B:AR=Z%(A,1):GOTO APPLY - REM APPLY return label/address popped by APPLY - APPLY_RETURN_MACROEXPAND: - + F=B:AR=Z%(A,1):CALL APPLY A=R AY=X%(X) @@ -127,12 +123,10 @@ MACROEXPAND: MACROEXPAND_DONE: X=X-1: REM pop original A - RETURN +END SUB REM EVAL_AST(A, E) -> R -REM called using GOTO to avoid basic return address stack usage -REM top of stack should have return label index -EVAL_AST: +SUB EVAL_AST REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A @@ -150,7 +144,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K=A:GOSUB ENV_GET + K=A:GOTO ENV_GET + ENV_GET_RETURN: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: @@ -189,7 +184,7 @@ EVAL_AST: EVAL_AST_DO_EVAL: REM call EVAL for each entry - A=A+1:GOSUB EVAL + A=A+1:CALL EVAL A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry @@ -226,13 +221,10 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 +END SUB - REM pop EVAL AST return label/address - RN=X%(X):X=X-1 - ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 - -REM EVAL(A, E)) -> R -EVAL: +REM EVAL(A, E) -> R +SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack @@ -251,15 +243,11 @@ EVAL: IF R THEN GOTO APPLY_LIST EVAL_NOT_LIST: REM ELSE - REM push EVAL_AST return label/address - X=X+1:X%(X)=1 - GOTO EVAL_AST - EVAL_AST_RETURN_1: - + CALL EVAL_AST GOTO EVAL_RETURN APPLY_LIST: - GOSUB MACROEXPAND + CALL MACROEXPAND GOSUB LIST_Q IF R<>1 THEN GOTO EVAL_NOT_LIST @@ -301,7 +289,7 @@ EVAL: GOSUB EVAL_GET_A2: REM set A1 and A2 X=X+1:X%(X)=A1: REM push A1 - A=A2:GOSUB EVAL: REM eval a2 + A=A2:CALL EVAL: REM eval a2 A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN @@ -325,7 +313,7 @@ EVAL: X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:GOSUB EVAL + A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 REM set environment: even A1 key to odd A1 eval'd above @@ -349,11 +337,7 @@ EVAL: A=Z%(A,1): REM rest X=X+1:X%(X)=A: REM push/save A - REM push EVAL_AST return label/address - X=X+1:X%(X)=2 - GOTO EVAL_AST - REM return label/address already popped by EVAL_AST - EVAL_AST_RETURN_2: + CALL EVAL_AST REM cleanup AY=R: REM get eval'd list for release @@ -375,7 +359,7 @@ EVAL: EVAL_QUASIQUOTE: R=Z%(A,1)+1:GOSUB DEREF_R - A=R:GOSUB QUASIQUOTE + A=R:CALL QUASIQUOTE REM add quasiquote result to pending release queue to free when REM next lower EVAL level returns (LV) Y=Y+1:Y%(Y,0)=R:Y%(Y,1)=LV @@ -387,7 +371,7 @@ EVAL: GOSUB EVAL_GET_A2: REM set A1 and A2 X=X+1:X%(X)=A1: REM push A1 - A=A2:GOSUB EVAL: REM eval A2 + A=A2:CALL EVAL: REM eval A2 A1=X%(X):X=X-1: REM pop A1 REM change function to macro @@ -400,7 +384,8 @@ EVAL: EVAL_MACROEXPAND: REM PRINT "macroexpand" R=Z%(A,1)+1:GOSUB DEREF_R - A=R:GOSUB MACROEXPAND:R=A + A=R:CALL MACROEXPAND + R=A REM since we are returning it unevaluated, inc the ref cnt Z%(R,0)=Z%(R,0)+32 @@ -410,7 +395,7 @@ EVAL: GOSUB EVAL_GET_A1: REM set A1 REM push A X=X+1:X%(X)=A - A=A1:GOSUB EVAL + A=A1:CALL EVAL REM pop A A=X%(X):X=X-1 IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE @@ -432,10 +417,7 @@ EVAL: GOTO EVAL_RETURN EVAL_INVOKE: - REM push EVAL_AST return label/address - X=X+1:X%(X)=3 - GOTO EVAL_AST - EVAL_AST_RETURN_3: + CALL EVAL_AST REM if error, return f/args for release by caller IF ER<>-2 THEN GOTO EVAL_RETURN @@ -460,10 +442,10 @@ EVAL: EVAL_DO_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION - DO_TCO_FUNCTION_RETURN_EVAL: + IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION + EVAL_DO_FUNCTION_SKIP: REM pop and release f/args AY=X%(X):X=X-1:GOSUB RELEASE @@ -510,7 +492,7 @@ EVAL: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 - RETURN +END SUB REM PRINT(A) -> R$ MAL_PRINT: @@ -526,7 +508,7 @@ RE: R1=R IF ER<>-2 THEN GOTO RE_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL RE_DONE: REM Release memory from MAL_READ @@ -535,13 +517,13 @@ RE: REM REP(A$) -> R$ REM Assume D has repl_env -REP: +SUB REP R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -553,7 +535,7 @@ REP: IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ - GOTO REP_RETURN +END SUB REM MAIN program MAIN: @@ -613,8 +595,7 @@ MAIN: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOTO REP: REM call REP - REP_RETURN: + A$=R$:CALL REP: REM call REP IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index 4f7d443d50..583beb66b9 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -16,24 +16,20 @@ MAL_READ: GOSUB READ_STR RETURN -REM PAIR_Q(B) -> R -PAIR_Q: - R=0 - IF (Z%(B,0)AND31)<>6 AND (Z%(B,0)AND31)<>7 THEN RETURN - IF (Z%(B,1)=0) THEN RETURN - R=1 - RETURN - REM QUASIQUOTE(A) -> R -QUASIQUOTE: - B=A:GOSUB PAIR_Q - IF R=1 THEN GOTO QQ_UNQUOTE +SUB QUASIQUOTE + REM pair? + IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_QUOTE + IF (Z%(A,1)=0) THEN GOTO QQ_QUOTE + GOTO QQ_UNQUOTE + + QQ_QUOTE: REM ['quote, ast] AS$="quote":T=5:GOSUB STRING B2=R:B1=A:GOSUB LIST2 AY=B2:GOSUB RELEASE - RETURN + GOTO QQ_DONE QQ_UNQUOTE: R=A+1:GOSUB DEREF_R @@ -43,21 +39,24 @@ QUASIQUOTE: R=Z%(A,1)+1:GOSUB DEREF_R Z%(R,0)=Z%(R,0)+32 - RETURN + GOTO QQ_DONE QQ_SPLICE_UNQUOTE: REM push A on the stack X=X+1:X%(X)=A REM rest of cases call quasiquote on ast[1..] - A=Z%(A,1):GOSUB QUASIQUOTE:T6=R + A=Z%(A,1):CALL QUASIQUOTE + T6=R REM pop A off the stack A=X%(X):X=X-1 REM set A to ast[0] for last two cases A=A+1:GOSUB DEREF_A - B=A:GOSUB PAIR_Q - IF R=0 THEN GOTO QQ_DEFAULT + REM pair? + IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_DEFAULT + IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT + B=A+1:GOSUB DEREF_B IF (Z%(B,0)AND31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT @@ -69,7 +68,7 @@ QUASIQUOTE: REM release inner quasiquoted since outer list takes ownership AY=B1:GOSUB RELEASE AY=B3:GOSUB RELEASE - RETURN + GOTO QQ_DONE QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] @@ -77,7 +76,8 @@ QUASIQUOTE: REM push T6 on the stack X=X+1:X%(X)=T6 REM A set above to ast[0] - GOSUB QUASIQUOTE:B2=R + CALL QUASIQUOTE + B2=R REM pop T6 off the stack T6=X%(X):X=X-1 @@ -87,10 +87,11 @@ QUASIQUOTE: AY=B1:GOSUB RELEASE AY=B2:GOSUB RELEASE AY=B3:GOSUB RELEASE - RETURN + QQ_DONE: +END SUB REM MACROEXPAND(A, E) -> A: -MACROEXPAND: +SUB MACROEXPAND REM push original A X=X+1:X%(X)=A @@ -103,18 +104,13 @@ MACROEXPAND: REM symbol? in first position IF (Z%(B,0)AND31)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? - K=B:GOSUB ENV_FIND + K=B:CALL ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE B=T4:GOSUB DEREF_B REM macro? IF (Z%(B,0)AND31)<>11 THEN GOTO MACROEXPAND_DONE - REM apply - X=X+1:X%(X)=5: REM push APPLY return label/address - F=B:AR=Z%(A,1):GOTO APPLY - REM APPLY return label/address popped by APPLY - APPLY_RETURN_MACROEXPAND: - + F=B:AR=Z%(A,1):CALL APPLY A=R AY=X%(X) @@ -127,12 +123,10 @@ MACROEXPAND: MACROEXPAND_DONE: X=X-1: REM pop original A - RETURN +END SUB REM EVAL_AST(A, E) -> R -REM called using GOTO to avoid basic return address stack usage -REM top of stack should have return label index -EVAL_AST: +SUB EVAL_AST REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A @@ -150,7 +144,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K=A:GOSUB ENV_GET + K=A:GOTO ENV_GET + ENV_GET_RETURN: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: @@ -179,7 +174,7 @@ EVAL_AST: IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -189,7 +184,7 @@ EVAL_AST: EVAL_AST_DO_EVAL: REM call EVAL for each entry - A=A+1:GOSUB EVAL + A=A+1:CALL EVAL A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry @@ -226,13 +221,10 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 +END SUB - REM pop EVAL AST return label/address - RN=X%(X):X=X-1 - ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 - -REM EVAL(A, E)) -> R -EVAL: +REM EVAL(A, E) -> R +SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack @@ -251,15 +243,11 @@ EVAL: IF R THEN GOTO APPLY_LIST EVAL_NOT_LIST: REM ELSE - REM push EVAL_AST return label/address - X=X+1:X%(X)=1 - GOTO EVAL_AST - EVAL_AST_RETURN_1: - + CALL EVAL_AST GOTO EVAL_RETURN APPLY_LIST: - GOSUB MACROEXPAND + CALL MACROEXPAND GOSUB LIST_Q IF R<>1 THEN GOTO EVAL_NOT_LIST @@ -302,7 +290,7 @@ EVAL: GOSUB EVAL_GET_A2: REM set A1 and A2 X=X+1:X%(X)=A1: REM push A1 - A=A2:GOSUB EVAL: REM eval a2 + A=A2:CALL EVAL: REM eval a2 A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN @@ -326,7 +314,7 @@ EVAL: X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:GOSUB EVAL + A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 REM set environment: even A1 key to odd A1 eval'd above @@ -350,11 +338,7 @@ EVAL: A=Z%(A,1): REM rest X=X+1:X%(X)=A: REM push/save A - REM push EVAL_AST return label/address - X=X+1:X%(X)=2 - GOTO EVAL_AST - REM return label/address already popped by EVAL_AST - EVAL_AST_RETURN_2: + CALL EVAL_AST REM cleanup AY=R: REM get eval'd list for release @@ -376,7 +360,7 @@ EVAL: EVAL_QUASIQUOTE: R=Z%(A,1)+1:GOSUB DEREF_R - A=R:GOSUB QUASIQUOTE + A=R:CALL QUASIQUOTE REM add quasiquote result to pending release queue to free when REM next lower EVAL level returns (LV) Y=Y+1:Y%(Y,0)=R:Y%(Y,1)=LV @@ -388,7 +372,7 @@ EVAL: GOSUB EVAL_GET_A2: REM set A1 and A2 X=X+1:X%(X)=A1: REM push A1 - A=A2:GOSUB EVAL: REM eval A2 + A=A2:CALL EVAL: REM eval A2 A1=X%(X):X=X-1: REM pop A1 REM change function to macro @@ -401,7 +385,8 @@ EVAL: EVAL_MACROEXPAND: REM PRINT "macroexpand" R=Z%(A,1)+1:GOSUB DEREF_R - A=R:GOSUB MACROEXPAND:R=A + A=R:CALL MACROEXPAND + R=A REM since we are returning it unevaluated, inc the ref cnt Z%(R,0)=Z%(R,0)+32 @@ -412,7 +397,7 @@ EVAL: GOSUB EVAL_GET_A1: REM set A1, A2, and A3 X=X+1:X%(X)=A: REM push/save A - A=A1:GOSUB EVAL: REM eval A1 + A=A1:CALL EVAL: REM eval A1 A=X%(X):X=X-1: REM pop/restore A REM if there is not error or catch block then return @@ -434,7 +419,7 @@ EVAL: REM unset error for catch eval ER=-2:ER$="" - A=A2:GOSUB EVAL + A=A2:CALL EVAL GOTO EVAL_RETURN @@ -442,7 +427,7 @@ EVAL: GOSUB EVAL_GET_A1: REM set A1 REM push A X=X+1:X%(X)=A - A=A1:GOSUB EVAL + A=A1:CALL EVAL REM pop A A=X%(X):X=X-1 IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE @@ -464,10 +449,7 @@ EVAL: GOTO EVAL_RETURN EVAL_INVOKE: - REM push EVAL_AST return label/address - X=X+1:X%(X)=3 - GOTO EVAL_AST - EVAL_AST_RETURN_3: + CALL EVAL_AST REM if error, return f/args for release by caller IF ER<>-2 THEN GOTO EVAL_RETURN @@ -492,10 +474,10 @@ EVAL: EVAL_DO_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION - DO_TCO_FUNCTION_RETURN_EVAL: + IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION + EVAL_DO_FUNCTION_SKIP: REM pop and release f/args AY=X%(X):X=X-1:GOSUB RELEASE @@ -542,7 +524,7 @@ EVAL: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 - RETURN +END SUB REM PRINT(A) -> R$ MAL_PRINT: @@ -558,7 +540,7 @@ RE: R1=R IF ER<>-2 THEN GOTO RE_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL RE_DONE: REM Release memory from MAL_READ @@ -567,13 +549,13 @@ RE: REM REP(A$) -> R$ REM Assume D has repl_env -REP: +SUB REP R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -585,7 +567,7 @@ REP: IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ - GOTO REP_RETURN +END SUB REM MAIN program MAIN: @@ -645,8 +627,7 @@ MAIN: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOTO REP: REM call REP - REP_RETURN: + A$=R$:CALL REP: REM call REP IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index 18ffbab756..a077327964 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -17,7 +17,7 @@ MAL_READ: RETURN REM QUASIQUOTE(A) -> R -QUASIQUOTE: +SUB QUASIQUOTE REM pair? IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_QUOTE IF (Z%(A,1)=0) THEN GOTO QQ_QUOTE @@ -29,7 +29,7 @@ QUASIQUOTE: B2=R:B1=A:GOSUB LIST2 AY=B2:GOSUB RELEASE - RETURN + GOTO QQ_DONE QQ_UNQUOTE: R=A+1:GOSUB DEREF_R @@ -39,13 +39,14 @@ QUASIQUOTE: R=Z%(A,1)+1:GOSUB DEREF_R Z%(R,0)=Z%(R,0)+32 - RETURN + GOTO QQ_DONE QQ_SPLICE_UNQUOTE: REM push A on the stack X=X+1:X%(X)=A REM rest of cases call quasiquote on ast[1..] - A=Z%(A,1):GOSUB QUASIQUOTE:T6=R + A=Z%(A,1):CALL QUASIQUOTE + T6=R REM pop A off the stack A=X%(X):X=X-1 @@ -67,7 +68,7 @@ QUASIQUOTE: REM release inner quasiquoted since outer list takes ownership AY=B1:GOSUB RELEASE AY=B3:GOSUB RELEASE - RETURN + GOTO QQ_DONE QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] @@ -75,7 +76,8 @@ QUASIQUOTE: REM push T6 on the stack X=X+1:X%(X)=T6 REM A set above to ast[0] - GOSUB QUASIQUOTE:B2=R + CALL QUASIQUOTE + B2=R REM pop T6 off the stack T6=X%(X):X=X-1 @@ -85,10 +87,11 @@ QUASIQUOTE: AY=B1:GOSUB RELEASE AY=B2:GOSUB RELEASE AY=B3:GOSUB RELEASE - RETURN + QQ_DONE: +END SUB REM MACROEXPAND(A, E) -> A: -MACROEXPAND: +SUB MACROEXPAND REM push original A X=X+1:X%(X)=A @@ -101,18 +104,13 @@ MACROEXPAND: REM symbol? in first position IF (Z%(B,0)AND31)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? - K=B:GOSUB ENV_FIND + K=B:CALL ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE B=T4:GOSUB DEREF_B REM macro? IF (Z%(B,0)AND31)<>11 THEN GOTO MACROEXPAND_DONE - REM apply - X=X+1:X%(X)=5: REM push APPLY return label/address - F=B:AR=Z%(A,1):GOTO APPLY - REM APPLY return label/address popped by APPLY - APPLY_RETURN_MACROEXPAND: - + F=B:AR=Z%(A,1):CALL APPLY A=R AY=X%(X) @@ -125,12 +123,10 @@ MACROEXPAND: MACROEXPAND_DONE: X=X-1: REM pop original A - RETURN +END SUB REM EVAL_AST(A, E) -> R -REM called using GOTO to avoid basic return address stack usage -REM top of stack should have return label index -EVAL_AST: +SUB EVAL_AST REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A @@ -148,7 +144,8 @@ EVAL_AST: GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: - K=A:GOSUB ENV_GET + K=A:GOTO ENV_GET + ENV_GET_RETURN: GOTO EVAL_AST_RETURN EVAL_AST_SEQ: @@ -177,7 +174,7 @@ EVAL_AST: IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -187,7 +184,7 @@ EVAL_AST: EVAL_AST_DO_EVAL: REM call EVAL for each entry - A=A+1:GOSUB EVAL + A=A+1:CALL EVAL A=A-1 GOSUB DEREF_R: REM deref to target of evaluated entry @@ -224,13 +221,10 @@ EVAL_AST: EVAL_AST_RETURN: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 +END SUB - REM pop EVAL AST return label/address - RN=X%(X):X=X-1 - ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3 - -REM EVAL(A, E)) -> R -EVAL: +REM EVAL(A, E) -> R +SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack @@ -249,15 +243,11 @@ EVAL: IF R THEN GOTO APPLY_LIST EVAL_NOT_LIST: REM ELSE - REM push EVAL_AST return label/address - X=X+1:X%(X)=1 - GOTO EVAL_AST - EVAL_AST_RETURN_1: - + CALL EVAL_AST GOTO EVAL_RETURN APPLY_LIST: - GOSUB MACROEXPAND + CALL MACROEXPAND GOSUB LIST_Q IF R<>1 THEN GOTO EVAL_NOT_LIST @@ -300,7 +290,7 @@ EVAL: GOSUB EVAL_GET_A2: REM set A1 and A2 X=X+1:X%(X)=A1: REM push A1 - A=A2:GOSUB EVAL: REM eval a2 + A=A2:CALL EVAL: REM eval a2 A1=X%(X):X=X-1: REM pop A1 IF ER<>-2 THEN GOTO EVAL_RETURN @@ -324,7 +314,7 @@ EVAL: X=X+1:X%(X)=A1: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:GOSUB EVAL + A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 REM set environment: even A1 key to odd A1 eval'd above @@ -348,11 +338,7 @@ EVAL: A=Z%(A,1): REM rest X=X+1:X%(X)=A: REM push/save A - REM push EVAL_AST return label/address - X=X+1:X%(X)=2 - GOTO EVAL_AST - REM return label/address already popped by EVAL_AST - EVAL_AST_RETURN_2: + CALL EVAL_AST REM cleanup AY=R: REM get eval'd list for release @@ -374,7 +360,7 @@ EVAL: EVAL_QUASIQUOTE: R=Z%(A,1)+1:GOSUB DEREF_R - A=R:GOSUB QUASIQUOTE + A=R:CALL QUASIQUOTE REM add quasiquote result to pending release queue to free when REM next lower EVAL level returns (LV) Y=Y+1:Y%(Y,0)=R:Y%(Y,1)=LV @@ -386,7 +372,7 @@ EVAL: GOSUB EVAL_GET_A2: REM set A1 and A2 X=X+1:X%(X)=A1: REM push A1 - A=A2:GOSUB EVAL: REM eval A2 + A=A2:CALL EVAL: REM eval A2 A1=X%(X):X=X-1: REM pop A1 REM change function to macro @@ -399,7 +385,8 @@ EVAL: EVAL_MACROEXPAND: REM PRINT "macroexpand" R=Z%(A,1)+1:GOSUB DEREF_R - A=R:GOSUB MACROEXPAND:R=A + A=R:CALL MACROEXPAND + R=A REM since we are returning it unevaluated, inc the ref cnt Z%(R,0)=Z%(R,0)+32 @@ -410,7 +397,7 @@ EVAL: GOSUB EVAL_GET_A1: REM set A1, A2, and A3 X=X+1:X%(X)=A: REM push/save A - A=A1:GOSUB EVAL: REM eval A1 + A=A1:CALL EVAL: REM eval A1 A=X%(X):X=X-1: REM pop/restore A REM if there is not error or catch block then return @@ -432,7 +419,7 @@ EVAL: REM unset error for catch eval ER=-2:ER$="" - A=A2:GOSUB EVAL + A=A2:CALL EVAL GOTO EVAL_RETURN @@ -440,7 +427,7 @@ EVAL: GOSUB EVAL_GET_A1: REM set A1 REM push A X=X+1:X%(X)=A - A=A1:GOSUB EVAL + A=A1:CALL EVAL REM pop A A=X%(X):X=X-1 IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE @@ -462,10 +449,7 @@ EVAL: GOTO EVAL_RETURN EVAL_INVOKE: - REM push EVAL_AST return label/address - X=X+1:X%(X)=3 - GOTO EVAL_AST - EVAL_AST_RETURN_3: + CALL EVAL_AST REM if error, return f/args for release by caller IF ER<>-2 THEN GOTO EVAL_RETURN @@ -490,10 +474,10 @@ EVAL: EVAL_DO_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL + IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION - DO_TCO_FUNCTION_RETURN_EVAL: + IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION + EVAL_DO_FUNCTION_SKIP: REM pop and release f/args AY=X%(X):X=X-1:GOSUB RELEASE @@ -540,7 +524,7 @@ EVAL: REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 - RETURN +END SUB REM PRINT(A) -> R$ MAL_PRINT: @@ -556,7 +540,7 @@ RE: R1=R IF ER<>-2 THEN GOTO RE_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL RE_DONE: REM Release memory from MAL_READ @@ -565,13 +549,13 @@ RE: REM REP(A$) -> R$ REM Assume D has repl_env -REP: +SUB REP R1=0:R2=0 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE - A=R:E=D:GOSUB EVAL + A=R:E=D:CALL EVAL R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -583,7 +567,7 @@ REP: IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE R$=RT$ - GOTO REP_RETURN +END SUB REM MAIN program MAIN: @@ -652,8 +636,7 @@ MAIN: A$="user> ":GOSUB READLINE: REM call input parser IF EOF=1 THEN GOTO QUIT - A$=R$:GOTO REP: REM call REP - REP_RETURN: + A$=R$:CALL REP: REM call REP IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP PRINT R$ From 82641edb6525be0cdd8c9c37e3064536c0e77b23 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Wed, 26 Oct 2016 06:46:20 +0000 Subject: [PATCH 0197/2308] vimscript: Remove calls to ObjValue --- vimscript/core.vim | 66 ++++++++++++++++++------------------ vimscript/env.vim | 4 +-- vimscript/printer.vim | 24 ++++++------- vimscript/step2_eval.vim | 12 +++---- vimscript/step3_env.vim | 28 +++++++-------- vimscript/step4_if_fn_do.vim | 34 +++++++++---------- vimscript/step5_tco.vim | 36 ++++++++++---------- vimscript/step6_file.vim | 36 ++++++++++---------- vimscript/step7_quote.vim | 40 +++++++++++----------- vimscript/step8_macros.vim | 46 ++++++++++++------------- vimscript/step9_try.vim | 48 +++++++++++++------------- vimscript/stepA_mal.vim | 48 +++++++++++++------------- vimscript/types.vim | 48 ++++++++++++-------------- 13 files changed, 233 insertions(+), 237 deletions(-) diff --git a/vimscript/core.vim b/vimscript/core.vim index 0c0794b1d2..859ca2f37a 100644 --- a/vimscript/core.vim +++ b/vimscript/core.vim @@ -1,14 +1,14 @@ " core module function MalAssoc(args) - let hash = copy(ObjValue(a:args[0])) + let hash = copy(a:args[0].val) let new_elements = HashBuild(a:args[1:]) - call extend(hash, ObjValue(new_elements)) + call extend(hash, new_elements.val) return HashNew(hash) endfunction function MalDissoc(args) - let hash = copy(ObjValue(a:args[0])) + let hash = copy(a:args[0].val) for keyobj in a:args[1:] let key = HashMakeKey(keyobj) if has_key(hash, key) @@ -22,7 +22,7 @@ function MalGet(args) if !HashQ(a:args[0]) return g:MalNil endif - let hash = ObjValue(a:args[0]) + let hash = a:args[0].val let key = HashMakeKey(a:args[1]) return get(hash, key, g:MalNil) endfunction @@ -31,14 +31,14 @@ function MalContainsQ(args) if !HashQ(a:args[0]) return FalseNew() endif - let hash = ObjValue(a:args[0]) + let hash = a:args[0].val let key = HashMakeKey(a:args[1]) return BoolNew(has_key(hash, key)) endfunction function MalKeys(args) let listobjs = [] - for keyname in keys(ObjValue(a:args[0])) + for keyname in keys(a:args[0].val) let keyobj = HashParseKey(keyname) call add(listobjs, keyobj) endfor @@ -46,12 +46,12 @@ function MalKeys(args) endfunction function MalReadLine(args) - let [eof, line] = Readline(ObjValue(a:args[0])) + let [eof, line] = Readline(a:args[0].val) return eof ? g:MalNil : StringNew(line) endfunction function MalCons(args) - let items = copy(ObjValue(a:args[1])) + let items = copy(a:args[1].val) call insert(items, a:args[0]) return ListNew(items) endfunction @@ -59,7 +59,7 @@ endfunction function MalConcat(args) let res = [] for list in a:args - let res = res + ObjValue(list) + let res = res + list.val endfor return ListNew(res) endfunction @@ -70,9 +70,9 @@ function MalApply(args) if len(rest) == 0 let funcargs = [] elseif len(rest) == 1 - let funcargs = ObjValue(rest[-1]) + let funcargs = rest[-1].val else - let funcargs = rest[:-2] + ObjValue(rest[-1]) + let funcargs = rest[:-2] + rest[-1].val endif if NativeFunctionQ(funcobj) return NativeFuncInvoke(funcobj, ListNew(funcargs)) @@ -86,7 +86,7 @@ endfunction function MalMap(args) let funcobj = a:args[0] let res = [] - for item in ObjValue(a:args[1]) + for item in a:args[1].val unlet! mappeditem if NativeFunctionQ(funcobj) let mappeditem = NativeFuncInvoke(funcobj, ListNew([item])) @@ -115,7 +115,7 @@ function ConjList(list, elements) endfunction function ConjVector(vector, elements) - let items = copy(ObjValue(a:vector)) + let items = copy(a:vector.val) for e in a:elements call add(items, e) endfor @@ -137,9 +137,9 @@ function MalSeq(args) elseif ListQ(obj) return obj elseif VectorQ(obj) - return ListNew(ObjValue(obj)) + return ListNew(obj.val) elseif StringQ(obj) - return ListNew(map(split(ObjValue(obj), '\zs'), {_, c -> StringNew(c)})) + return ListNew(map(split(obj.val, '\zs'), {_, c -> StringNew(c)})) endif throw "seq requires string or list or vector or nil" endfunction @@ -171,22 +171,22 @@ endfunction let CoreNs = { \ "=": NewNativeFnLambda({a -> BoolNew(EqualQ(a[0], a[1]))}), - \ "<": NewNativeFnLambda({a -> BoolNew(ObjValue(a[0]) < ObjValue(a[1]))}), - \ "<=": NewNativeFnLambda({a -> BoolNew(ObjValue(a[0]) <= ObjValue(a[1]))}), - \ ">": NewNativeFnLambda({a -> BoolNew(ObjValue(a[0]) > ObjValue(a[1]))}), - \ ">=": NewNativeFnLambda({a -> BoolNew(ObjValue(a[0]) >= ObjValue(a[1]))}), - \ "+": NewNativeFnLambda({a -> IntegerNew(ObjValue(a[0]) + ObjValue(a[1]))}), - \ "-": NewNativeFnLambda({a -> IntegerNew(ObjValue(a[0]) - ObjValue(a[1]))}), - \ "*": NewNativeFnLambda({a -> IntegerNew(ObjValue(a[0]) * ObjValue(a[1]))}), - \ "/": NewNativeFnLambda({a -> IntegerNew(ObjValue(a[0]) / ObjValue(a[1]))}), + \ "<": NewNativeFnLambda({a -> BoolNew(a[0].val < a[1].val)}), + \ "<=": NewNativeFnLambda({a -> BoolNew(a[0].val <= a[1].val)}), + \ ">": NewNativeFnLambda({a -> BoolNew(a[0].val > a[1].val)}), + \ ">=": NewNativeFnLambda({a -> BoolNew(a[0].val >= a[1].val)}), + \ "+": NewNativeFnLambda({a -> IntegerNew(a[0].val + a[1].val)}), + \ "-": NewNativeFnLambda({a -> IntegerNew(a[0].val - a[1].val)}), + \ "*": NewNativeFnLambda({a -> IntegerNew(a[0].val * a[1].val)}), + \ "/": NewNativeFnLambda({a -> IntegerNew(a[0].val / a[1].val)}), \ "time-ms": NewNativeFnLambda({a -> IntegerNew(libcallnr("libvimextras.so", "vimtimems", 0))}), \ "nil?": NewNativeFnLambda({a -> BoolNew(NilQ(a[0]))}), \ "true?": NewNativeFnLambda({a -> BoolNew(TrueQ(a[0]))}), \ "false?": NewNativeFnLambda({a -> BoolNew(FalseQ(a[0]))}), - \ "symbol": NewNativeFnLambda({a -> SymbolNew(ObjValue(a[0]))}), + \ "symbol": NewNativeFnLambda({a -> SymbolNew(a[0].val)}), \ "symbol?": NewNativeFnLambda({a -> BoolNew(SymbolQ(a[0]))}), \ "string?": NewNativeFnLambda({a -> BoolNew(StringQ(a[0]))}), - \ "keyword": NewNativeFnLambda({a -> KeywordNew(ObjValue(a[0]))}), + \ "keyword": NewNativeFnLambda({a -> KeywordNew(a[0].val)}), \ "keyword?": NewNativeFnLambda({a -> BoolNew(KeywordQ(a[0]))}), \ "list": NewNativeFnLambda({a -> ListNew(a)}), \ "list?": NewNativeFnLambda({a -> BoolNew(ListQ(a[0]))}), @@ -202,18 +202,18 @@ let CoreNs = { \ "get": NewNativeFn("MalGet"), \ "contains?": NewNativeFn("MalContainsQ"), \ "keys": NewNativeFn("MalKeys"), - \ "vals": NewNativeFnLambda({a -> ListNew(values(ObjValue(a[0])))}), + \ "vals": NewNativeFnLambda({a -> ListNew(values(a[0].val))}), \ "pr-str": NewNativeFnLambda({a -> StringNew(join(map(copy(a), {_, e -> PrStr(e, 1)}), " "))}), \ "str": NewNativeFnLambda({a -> StringNew(join(map(copy(a), {_, e -> PrStr(e, 0)}), ""))}), \ "prn": NewNativeFnLambda({a -> [PrintLn(join(map(copy(a), {_, e -> PrStr(e, 1)}), " ")), g:MalNil][1]}), \ "println": NewNativeFnLambda({a -> [PrintLn(join(map(copy(a), {_, e -> PrStr(e, 0)}), " ")), g:MalNil][1]}), - \ "read-string": NewNativeFnLambda({a -> ReadStr(ObjValue(a[0]))}), + \ "read-string": NewNativeFnLambda({a -> ReadStr(a[0].val)}), \ "readline": NewNativeFn("MalReadLine"), - \ "slurp": NewNativeFnLambda({a -> StringNew(join(readfile(ObjValue(a[0]), "b"), "\n"))}), + \ "slurp": NewNativeFnLambda({a -> StringNew(join(readfile(a[0].val, "b"), "\n"))}), \ "cons": NewNativeFn("MalCons"), \ "concat": NewNativeFn("MalConcat"), \ "first": NewNativeFnLambda({a -> NilQ(a[0]) ? g:MalNil : ListFirst(a[0])}), - \ "nth": NewNativeFnLambda({a -> ListNth(a[0], ObjValue(a[1]))}), + \ "nth": NewNativeFnLambda({a -> ListNth(a[0], a[1].val)}), \ "rest": NewNativeFnLambda({a -> NilQ(a[0]) ? ListNew([]) : ListRest(a[0])}), \ "apply": NewNativeFn("MalApply"), \ "map": NewNativeFn("MalMap"), @@ -221,11 +221,11 @@ let CoreNs = { \ "conj": NewNativeFn("MalConj"), \ "seq": NewNativeFn("MalSeq"), \ "meta": NewNativeFnLambda({a -> ObjMeta(a[0])}), - \ "with-meta": NewNativeFnLambda({a -> ObjNewWithMeta(ObjType(a[0]), copy(ObjValue(a[0])), a[1])}), + \ "with-meta": NewNativeFnLambda({a -> ObjNewWithMeta(ObjType(a[0]), copy(a[0].val), a[1])}), \ "atom": NewNativeFnLambda({a -> AtomNew(a[0])}), \ "atom?": NewNativeFnLambda({a -> BoolNew(AtomQ(a[0]))}), - \ "deref": NewNativeFnLambda({a -> ObjValue(a[0])}), + \ "deref": NewNativeFnLambda({a -> a[0].val}), \ "reset!": NewNativeFnLambda({a -> ObjSetValue(a[0], a[1])}), - \ "swap!": NewNativeFnLambda({a -> ObjSetValue(a[0], MalApply([a[1], ListNew([ObjValue(a[0])] + a[2:])]))}), - \ "vim*": NewNativeFnLambda({a -> VimToMal(eval(ObjValue(a[0])))}) + \ "swap!": NewNativeFnLambda({a -> ObjSetValue(a[0], MalApply([a[1], ListNew([a[0].val] + a[2:])]))}), + \ "vim*": NewNativeFnLambda({a -> VimToMal(eval(a[0].val))}) \ } diff --git a/vimscript/env.vim b/vimscript/env.vim index 4ff7dbd3ff..75a03360ed 100644 --- a/vimscript/env.vim +++ b/vimscript/env.vim @@ -13,10 +13,10 @@ function NewEnvWithBinds(outer, binds, exprs) let env = NewEnv(a:outer) let i = 0 while i < ListCount(a:binds) - let varname = ObjValue(ListNth(a:binds, i)) + let varname = ListNth(a:binds, i).val if varname == "&" " TODO - let restvarname = ObjValue(ListNth(a:binds, i + 1)) + let restvarname = ListNth(a:binds, i + 1).val let restvarvalues = ListDrop(a:exprs, i) call env.set(restvarname, restvarvalues) break diff --git a/vimscript/printer.vim b/vimscript/printer.vim index 13249da27b..18d21b5597 100644 --- a/vimscript/printer.vim +++ b/vimscript/printer.vim @@ -5,46 +5,46 @@ function PrStr(ast, readable) let r = a:readable if ListQ(obj) let ret = [] - for e in ObjValue(obj) + for e in obj.val call add(ret, PrStr(e, r)) endfor return "(" . join(ret, " ") . ")" elseif VectorQ(obj) let ret = [] - for e in ObjValue(obj) + for e in obj.val call add(ret, PrStr(e, r)) endfor return "[" . join(ret, " ") . "]" elseif HashQ(obj) let ret = [] - for [k, v] in items(ObjValue(obj)) + for [k, v] in items(obj.val) let keyobj = HashParseKey(k) call add(ret, PrStr(keyobj, r)) call add(ret, PrStr(v, r)) endfor return "{" . join(ret, " ") . "}" elseif MacroQ(obj) - let numargs = ListCount(ObjValue(obj).params) + let numargs = ListCount(obj.val.params) return "" elseif FunctionQ(obj) - let numargs = ListCount(ObjValue(obj).params) + let numargs = ListCount(obj.val.params) return "" elseif NativeFunctionQ(obj) - let funcname = ObjValue(obj).name + let funcname = obj.val.name return "" elseif AtomQ(obj) - return "(atom " . PrStr(ObjValue(obj), 1) . ")" + return "(atom " . PrStr(obj.val, 1) . ")" elseif KeywordQ(obj) - return ':' . ObjValue(obj) + return ':' . obj.val elseif StringQ(obj) if r - let str = ObjValue(obj) + let str = obj.val let str = substitute(str, '\\', '\\\\', "g") let str = substitute(str, '"', '\\"', "g") let str = substitute(str, "\n", '\\n', "g") return '"' . str . '"' else - return ObjValue(obj) + return obj.val endif elseif NilQ(obj) return "nil" @@ -53,8 +53,8 @@ function PrStr(ast, readable) elseif FalseQ(obj) return "false" elseif IntegerQ(obj) || FloatQ(obj) - return string(ObjValue(obj)) + return string(obj.val) else - return ObjValue(obj) + return obj.val end endfunction diff --git a/vimscript/step2_eval.vim b/vimscript/step2_eval.vim index 63566bc166..4cbf8e592f 100644 --- a/vimscript/step2_eval.vim +++ b/vimscript/step2_eval.vim @@ -9,26 +9,26 @@ endfunction function EvalAst(ast, env) if SymbolQ(a:ast) - let varname = ObjValue(a:ast) + let varname = a:ast.val if !has_key(a:env, varname) throw "'" . varname . "' not found" end return a:env[varname] elseif ListQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return ListNew(ret) elseif VectorQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return VectorNew(ret) elseif HashQ(a:ast) let ret = {} - for [k,v] in items(ObjValue(a:ast)) + for [k,v] in items(a:ast.val) let keyobj = HashParseKey(k) let newkey = EVAL(keyobj, a:env) let newval = EVAL(v, a:env) @@ -52,8 +52,8 @@ function EVAL(ast, env) " apply list let el = EvalAst(a:ast, a:env) - let Fn = ObjValue(el)[0] - return Fn(ObjValue(el)[1:-1]) + let Fn = el.val[0] + return Fn(el.val[1:-1]) endfunction function PRINT(exp) diff --git a/vimscript/step3_env.vim b/vimscript/step3_env.vim index 42dee28cda..d5cc4e5bda 100644 --- a/vimscript/step3_env.vim +++ b/vimscript/step3_env.vim @@ -10,23 +10,23 @@ endfunction function EvalAst(ast, env) if SymbolQ(a:ast) - let varname = ObjValue(a:ast) + let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return ListNew(ret) elseif VectorQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return VectorNew(ret) elseif HashQ(a:ast) let ret = {} - for [k,v] in items(ObjValue(a:ast)) + for [k,v] in items(a:ast.val) let keyobj = HashParseKey(k) let newkey = EVAL(keyobj, a:env) let newval = EVAL(v, a:env) @@ -47,27 +47,27 @@ function EVAL(ast, env) return a:ast endif - let first_symbol = ObjValue(ObjValue(a:ast)[0]) + let first_symbol = a:ast.val[0].val if first_symbol == "def!" - let a1 = ObjValue(a:ast)[1] - let a2 = ObjValue(a:ast)[2] - return a:env.set(ObjValue(a1), EVAL(a2, a:env)) + let a1 = a:ast.val[1] + let a2 = a:ast.val[2] + return a:env.set(a1.val, EVAL(a2, a:env)) elseif first_symbol == "let*" - let a1 = ObjValue(a:ast)[1] - let a2 = ObjValue(a:ast)[2] + let a1 = a:ast.val[1] + let a2 = a:ast.val[2] let let_env = NewEnv(a:env) - let let_binds = ObjValue(a1) + let let_binds = a1.val let i = 0 while i < len(let_binds) - call let_env.set(ObjValue(let_binds[i]), EVAL(let_binds[i+1], let_env)) + call let_env.set(let_binds[i].val, EVAL(let_binds[i+1], let_env)) let i = i + 2 endwhile return EVAL(a2, let_env) else " apply list let el = EvalAst(a:ast, a:env) - let Fn = ObjValue(el)[0] - return Fn(ObjValue(el)[1:-1]) + let Fn = el.val[0] + return Fn(el.val[1:-1]) endif endfunction diff --git a/vimscript/step4_if_fn_do.vim b/vimscript/step4_if_fn_do.vim index 4f10c3e6ee..395ddd0ee9 100644 --- a/vimscript/step4_if_fn_do.vim +++ b/vimscript/step4_if_fn_do.vim @@ -11,23 +11,23 @@ endfunction function EvalAst(ast, env) if SymbolQ(a:ast) - let varname = ObjValue(a:ast) + let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return ListNew(ret) elseif VectorQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return VectorNew(ret) elseif HashQ(a:ast) let ret = {} - for [k,v] in items(ObjValue(a:ast)) + for [k,v] in items(a:ast.val) let keyobj = HashParseKey(k) let newkey = EVAL(keyobj, a:env) let newval = EVAL(v, a:env) @@ -49,37 +49,37 @@ function EVAL(ast, env) endif let first = ListFirst(a:ast) - let first_symbol = SymbolQ(first) ? ObjValue(first) : "" + let first_symbol = SymbolQ(first) ? first.val : "" if first_symbol == "def!" - let a1 = ObjValue(a:ast)[1] - let a2 = ObjValue(a:ast)[2] - let ret = a:env.set(ObjValue(a1), EVAL(a2, a:env)) + let a1 = a:ast.val[1] + let a2 = a:ast.val[2] + let ret = a:env.set(a1.val, EVAL(a2, a:env)) return ret elseif first_symbol == "let*" - let a1 = ObjValue(a:ast)[1] - let a2 = ObjValue(a:ast)[2] + let a1 = a:ast.val[1] + let a2 = a:ast.val[2] let let_env = NewEnv(a:env) - let let_binds = ObjValue(a1) + let let_binds = a1.val let i = 0 while i < len(let_binds) - call let_env.set(ObjValue(let_binds[i]), EVAL(let_binds[i+1], let_env)) + call let_env.set(let_binds[i].val, EVAL(let_binds[i+1], let_env)) let i = i + 2 endwhile return EVAL(a2, let_env) elseif first_symbol == "if" - let condvalue = EVAL(ObjValue(a:ast)[1], a:env) + let condvalue = EVAL(a:ast.val[1], a:env) if FalseQ(condvalue) || NilQ(condvalue) - if len(ObjValue(a:ast)) < 4 + if len(a:ast.val) < 4 return g:MalNil else - return EVAL(ObjValue(a:ast)[3], a:env) + return EVAL(a:ast.val[3], a:env) endif else - return EVAL(ObjValue(a:ast)[2], a:env) + return EVAL(a:ast.val[2], a:env) endif elseif first_symbol == "do" let el = EvalAst(ListRest(a:ast), a:env) - return ObjValue(el)[-1] + return el.val[-1] elseif first_symbol == "fn*" let fn = NewFn(ListNth(a:ast, 2), a:env, ListNth(a:ast, 1)) return fn diff --git a/vimscript/step5_tco.vim b/vimscript/step5_tco.vim index 8e9536e3ef..1581c9649d 100644 --- a/vimscript/step5_tco.vim +++ b/vimscript/step5_tco.vim @@ -11,23 +11,23 @@ endfunction function EvalAst(ast, env) if SymbolQ(a:ast) - let varname = ObjValue(a:ast) + let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return ListNew(ret) elseif VectorQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return VectorNew(ret) elseif HashQ(a:ast) let ret = {} - for [k,v] in items(ObjValue(a:ast)) + for [k,v] in items(a:ast.val) let keyobj = HashParseKey(k) let newkey = EVAL(keyobj, a:env) let newval = EVAL(v, a:env) @@ -53,38 +53,38 @@ function EVAL(ast, env) endif let first = ListFirst(ast) - let first_symbol = SymbolQ(first) ? ObjValue(first) : "" + let first_symbol = SymbolQ(first) ? first.val : "" if first_symbol == "def!" - let a1 = ObjValue(ast)[1] - let a2 = ObjValue(ast)[2] - let ret = env.set(ObjValue(a1), EVAL(a2, env)) + let a1 = ast.val[1] + let a2 = ast.val[2] + let ret = env.set(a1.val, EVAL(a2, env)) return ret elseif first_symbol == "let*" - let a1 = ObjValue(ast)[1] - let a2 = ObjValue(ast)[2] + let a1 = ast.val[1] + let a2 = ast.val[2] let env = NewEnv(env) - let let_binds = ObjValue(a1) + let let_binds = a1.val let i = 0 while i < len(let_binds) - call env.set(ObjValue(let_binds[i]), EVAL(let_binds[i+1], env)) + call env.set(let_binds[i].val, EVAL(let_binds[i+1], env)) let i = i + 2 endwhile let ast = a2 " TCO elseif first_symbol == "if" - let condvalue = EVAL(ObjValue(ast)[1], env) + let condvalue = EVAL(ast.val[1], env) if FalseQ(condvalue) || NilQ(condvalue) - if len(ObjValue(ast)) < 4 + if len(ast.val) < 4 return g:MalNil else - let ast = ObjValue(ast)[3] + let ast = ast.val[3] endif else - let ast = ObjValue(ast)[2] + let ast = ast.val[2] endif " TCO elseif first_symbol == "do" - let astlist = ObjValue(ast) + let astlist = ast.val call EvalAst(ListNew(astlist[1:-2]), env) let ast = astlist[-1] " TCO @@ -99,7 +99,7 @@ function EVAL(ast, env) if NativeFunctionQ(funcobj) return NativeFuncInvoke(funcobj, args) elseif FunctionQ(funcobj) - let fn = ObjValue(funcobj) + let fn = funcobj.val let ast = fn.ast let env = NewEnvWithBinds(fn.env, fn.params, args) " TCO diff --git a/vimscript/step6_file.vim b/vimscript/step6_file.vim index 2ddf4bb35f..92051c2d71 100644 --- a/vimscript/step6_file.vim +++ b/vimscript/step6_file.vim @@ -11,23 +11,23 @@ endfunction function EvalAst(ast, env) if SymbolQ(a:ast) - let varname = ObjValue(a:ast) + let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return ListNew(ret) elseif VectorQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return VectorNew(ret) elseif HashQ(a:ast) let ret = {} - for [k,v] in items(ObjValue(a:ast)) + for [k,v] in items(a:ast.val) let keyobj = HashParseKey(k) let newkey = EVAL(keyobj, a:env) let newval = EVAL(v, a:env) @@ -53,38 +53,38 @@ function EVAL(ast, env) endif let first = ListFirst(ast) - let first_symbol = SymbolQ(first) ? ObjValue(first) : "" + let first_symbol = SymbolQ(first) ? first.val : "" if first_symbol == "def!" - let a1 = ObjValue(ast)[1] - let a2 = ObjValue(ast)[2] - let ret = env.set(ObjValue(a1), EVAL(a2, env)) + let a1 = ast.val[1] + let a2 = ast.val[2] + let ret = env.set(a1.val, EVAL(a2, env)) return ret elseif first_symbol == "let*" - let a1 = ObjValue(ast)[1] - let a2 = ObjValue(ast)[2] + let a1 = ast.val[1] + let a2 = ast.val[2] let env = NewEnv(env) - let let_binds = ObjValue(a1) + let let_binds = a1.val let i = 0 while i < len(let_binds) - call env.set(ObjValue(let_binds[i]), EVAL(let_binds[i+1], env)) + call env.set(let_binds[i].val, EVAL(let_binds[i+1], env)) let i = i + 2 endwhile let ast = a2 " TCO elseif first_symbol == "if" - let condvalue = EVAL(ObjValue(ast)[1], env) + let condvalue = EVAL(ast.val[1], env) if FalseQ(condvalue) || NilQ(condvalue) - if len(ObjValue(ast)) < 4 + if len(ast.val) < 4 return g:MalNil else - let ast = ObjValue(ast)[3] + let ast = ast.val[3] endif else - let ast = ObjValue(ast)[2] + let ast = ast.val[2] endif " TCO elseif first_symbol == "do" - let astlist = ObjValue(ast) + let astlist = ast.val call EvalAst(ListNew(astlist[1:-2]), env) let ast = astlist[-1] " TCO @@ -103,7 +103,7 @@ function EVAL(ast, env) if NativeFunctionQ(funcobj) return NativeFuncInvoke(funcobj, args) elseif FunctionQ(funcobj) - let fn = ObjValue(funcobj) + let fn = funcobj.val let ast = fn.ast let env = NewEnvWithBinds(fn.env, fn.params, args) " TCO diff --git a/vimscript/step7_quote.vim b/vimscript/step7_quote.vim index 0d33cf3a28..243f887553 100644 --- a/vimscript/step7_quote.vim +++ b/vimscript/step7_quote.vim @@ -18,9 +18,9 @@ function Quasiquote(ast) return ListNew([SymbolNew("quote"), a:ast]) endif let a0 = ListFirst(a:ast) - if SymbolQ(a0) && ObjValue(a0) == "unquote" + if SymbolQ(a0) && a0.val == "unquote" return ListNth(a:ast, 1) - elseif PairQ(a0) && SymbolQ(ListFirst(a0)) && ObjValue(ListFirst(a0)) == "splice-unquote" + elseif PairQ(a0) && SymbolQ(ListFirst(a0)) && ListFirst(a0).val == "splice-unquote" return ListNew([SymbolNew("concat"), ListNth(a0, 1), Quasiquote(ListRest(a:ast))]) else return ListNew([SymbolNew("cons"), Quasiquote(a0), Quasiquote(ListRest(a:ast))]) @@ -29,23 +29,23 @@ endfunction function EvalAst(ast, env) if SymbolQ(a:ast) - let varname = ObjValue(a:ast) + let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return ListNew(ret) elseif VectorQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return VectorNew(ret) elseif HashQ(a:ast) let ret = {} - for [k,v] in items(ObjValue(a:ast)) + for [k,v] in items(a:ast.val) let keyobj = HashParseKey(k) let newkey = EVAL(keyobj, a:env) let newval = EVAL(v, a:env) @@ -71,20 +71,20 @@ function EVAL(ast, env) endif let first = ListFirst(ast) - let first_symbol = SymbolQ(first) ? ObjValue(first) : "" + let first_symbol = SymbolQ(first) ? first.val : "" if first_symbol == "def!" - let a1 = ObjValue(ast)[1] - let a2 = ObjValue(ast)[2] - let ret = env.set(ObjValue(a1), EVAL(a2, env)) + let a1 = ast.val[1] + let a2 = ast.val[2] + let ret = env.set(a1.val, EVAL(a2, env)) return ret elseif first_symbol == "let*" - let a1 = ObjValue(ast)[1] - let a2 = ObjValue(ast)[2] + let a1 = ast.val[1] + let a2 = ast.val[2] let env = NewEnv(env) - let let_binds = ObjValue(a1) + let let_binds = a1.val let i = 0 while i < len(let_binds) - call env.set(ObjValue(let_binds[i]), EVAL(let_binds[i+1], env)) + call env.set(let_binds[i].val, EVAL(let_binds[i+1], env)) let i = i + 2 endwhile let ast = a2 @@ -95,19 +95,19 @@ function EVAL(ast, env) let ast = Quasiquote(ListNth(ast, 1)) " TCO elseif first_symbol == "if" - let condvalue = EVAL(ObjValue(ast)[1], env) + let condvalue = EVAL(ast.val[1], env) if FalseQ(condvalue) || NilQ(condvalue) - if len(ObjValue(ast)) < 4 + if len(ast.val) < 4 return g:MalNil else - let ast = ObjValue(ast)[3] + let ast = ast.val[3] endif else - let ast = ObjValue(ast)[2] + let ast = ast.val[2] endif " TCO elseif first_symbol == "do" - let astlist = ObjValue(ast) + let astlist = ast.val call EvalAst(ListNew(astlist[1:-2]), env) let ast = astlist[-1] " TCO @@ -126,7 +126,7 @@ function EVAL(ast, env) if NativeFunctionQ(funcobj) return NativeFuncInvoke(funcobj, args) elseif FunctionQ(funcobj) - let fn = ObjValue(funcobj) + let fn = funcobj.val let ast = fn.ast let env = NewEnvWithBinds(fn.env, fn.params, args) " TCO diff --git a/vimscript/step8_macros.vim b/vimscript/step8_macros.vim index 58f7cff40a..080409085e 100644 --- a/vimscript/step8_macros.vim +++ b/vimscript/step8_macros.vim @@ -18,9 +18,9 @@ function Quasiquote(ast) return ListNew([SymbolNew("quote"), a:ast]) endif let a0 = ListFirst(a:ast) - if SymbolQ(a0) && ObjValue(a0) == "unquote" + if SymbolQ(a0) && a0.val == "unquote" return ListNth(a:ast, 1) - elseif PairQ(a0) && SymbolQ(ListFirst(a0)) && ObjValue(ListFirst(a0)) == "splice-unquote" + elseif PairQ(a0) && SymbolQ(ListFirst(a0)) && ListFirst(a0).val == "splice-unquote" return ListNew([SymbolNew("concat"), ListNth(a0, 1), Quasiquote(ListRest(a:ast))]) else return ListNew([SymbolNew("cons"), Quasiquote(a0), Quasiquote(ListRest(a:ast))]) @@ -35,7 +35,7 @@ function IsMacroCall(ast, env) if !SymbolQ(a0) return 0 endif - let macroname = ObjValue(a0) + let macroname = a0.val if empty(a:env.find(macroname)) return 0 endif @@ -45,7 +45,7 @@ endfunction function MacroExpand(ast, env) let ast = a:ast while IsMacroCall(ast, a:env) - let macroobj = a:env.get(ObjValue(ListFirst(ast))) + let macroobj = a:env.get(ListFirst(ast).val) let macroargs = ListRest(ast) let ast = FuncInvoke(macroobj, macroargs) endwhile @@ -54,23 +54,23 @@ endfunction function EvalAst(ast, env) if SymbolQ(a:ast) - let varname = ObjValue(a:ast) + let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return ListNew(ret) elseif VectorQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return VectorNew(ret) elseif HashQ(a:ast) let ret = {} - for [k,v] in items(ObjValue(a:ast)) + for [k,v] in items(a:ast.val) let keyobj = HashParseKey(k) let newkey = EVAL(keyobj, a:env) let newval = EVAL(v, a:env) @@ -101,19 +101,19 @@ function EVAL(ast, env) endif let first = ListFirst(ast) - let first_symbol = SymbolQ(first) ? ObjValue(first) : "" + let first_symbol = SymbolQ(first) ? first.val : "" if first_symbol == "def!" - let a1 = ObjValue(ast)[1] - let a2 = ObjValue(ast)[2] - return env.set(ObjValue(a1), EVAL(a2, env)) + let a1 = ast.val[1] + let a2 = ast.val[2] + return env.set(a1.val, EVAL(a2, env)) elseif first_symbol == "let*" - let a1 = ObjValue(ast)[1] - let a2 = ObjValue(ast)[2] + let a1 = ast.val[1] + let a2 = ast.val[2] let env = NewEnv(env) - let let_binds = ObjValue(a1) + let let_binds = a1.val let i = 0 while i < len(let_binds) - call env.set(ObjValue(let_binds[i]), EVAL(let_binds[i+1], env)) + call env.set(let_binds[i].val, EVAL(let_binds[i+1], env)) let i = i + 2 endwhile let ast = a2 @@ -127,23 +127,23 @@ function EVAL(ast, env) let a1 = ListNth(ast, 1) let a2 = ListNth(ast, 2) let macro = MarkAsMacro(EVAL(a2, env)) - return env.set(ObjValue(a1), macro) + return env.set(a1.val, macro) elseif first_symbol == "macroexpand" return MacroExpand(ListNth(ast, 1), env) elseif first_symbol == "if" - let condvalue = EVAL(ObjValue(ast)[1], env) + let condvalue = EVAL(ast.val[1], env) if FalseQ(condvalue) || NilQ(condvalue) - if len(ObjValue(ast)) < 4 + if len(ast.val) < 4 return g:MalNil else - let ast = ObjValue(ast)[3] + let ast = ast.val[3] endif else - let ast = ObjValue(ast)[2] + let ast = ast.val[2] endif " TCO elseif first_symbol == "do" - let astlist = ObjValue(ast) + let astlist = ast.val call EvalAst(ListNew(astlist[1:-2]), env) let ast = astlist[-1] " TCO @@ -162,7 +162,7 @@ function EVAL(ast, env) if NativeFunctionQ(funcobj) return NativeFuncInvoke(funcobj, args) elseif FunctionQ(funcobj) - let fn = ObjValue(funcobj) + let fn = funcobj.val let ast = fn.ast let env = NewEnvWithBinds(fn.env, fn.params, args) " TCO diff --git a/vimscript/step9_try.vim b/vimscript/step9_try.vim index 688d1da648..8ec81a7cb7 100644 --- a/vimscript/step9_try.vim +++ b/vimscript/step9_try.vim @@ -20,9 +20,9 @@ function Quasiquote(ast) return ListNew([SymbolNew("quote"), a:ast]) endif let a0 = ListFirst(a:ast) - if SymbolQ(a0) && ObjValue(a0) == "unquote" + if SymbolQ(a0) && a0.val == "unquote" return ListNth(a:ast, 1) - elseif PairQ(a0) && SymbolQ(ListFirst(a0)) && ObjValue(ListFirst(a0)) == "splice-unquote" + elseif PairQ(a0) && SymbolQ(ListFirst(a0)) && ListFirst(a0).val == "splice-unquote" return ListNew([SymbolNew("concat"), ListNth(a0, 1), Quasiquote(ListRest(a:ast))]) else return ListNew([SymbolNew("cons"), Quasiquote(a0), Quasiquote(ListRest(a:ast))]) @@ -37,7 +37,7 @@ function IsMacroCall(ast, env) if !SymbolQ(a0) return 0 endif - let macroname = ObjValue(a0) + let macroname = a0.val if empty(a:env.find(macroname)) return 0 endif @@ -47,7 +47,7 @@ endfunction function MacroExpand(ast, env) let ast = a:ast while IsMacroCall(ast, a:env) - let macroobj = a:env.get(ObjValue(ListFirst(ast))) + let macroobj = a:env.get(ListFirst(ast).val) let macroargs = ListRest(ast) let ast = FuncInvoke(macroobj, macroargs) endwhile @@ -56,23 +56,23 @@ endfunction function EvalAst(ast, env) if SymbolQ(a:ast) - let varname = ObjValue(a:ast) + let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return ListNew(ret) elseif VectorQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return VectorNew(ret) elseif HashQ(a:ast) let ret = {} - for [k,v] in items(ObjValue(a:ast)) + for [k,v] in items(a:ast.val) let keyobj = HashParseKey(k) let newkey = EVAL(keyobj, a:env) let newval = EVAL(v, a:env) @@ -115,19 +115,19 @@ function EVAL(ast, env) endif let first = ListFirst(ast) - let first_symbol = SymbolQ(first) ? ObjValue(first) : "" + let first_symbol = SymbolQ(first) ? first.val : "" if first_symbol == "def!" - let a1 = ObjValue(ast)[1] - let a2 = ObjValue(ast)[2] - return env.set(ObjValue(a1), EVAL(a2, env)) + let a1 = ast.val[1] + let a2 = ast.val[2] + return env.set(a1.val, EVAL(a2, env)) elseif first_symbol == "let*" - let a1 = ObjValue(ast)[1] - let a2 = ObjValue(ast)[2] + let a1 = ast.val[1] + let a2 = ast.val[2] let env = NewEnv(env) - let let_binds = ObjValue(a1) + let let_binds = a1.val let i = 0 while i < len(let_binds) - call env.set(ObjValue(let_binds[i]), EVAL(let_binds[i+1], env)) + call env.set(let_binds[i].val, EVAL(let_binds[i+1], env)) let i = i + 2 endwhile let ast = a2 @@ -141,19 +141,19 @@ function EVAL(ast, env) let a1 = ListNth(ast, 1) let a2 = ListNth(ast, 2) let macro = MarkAsMacro(EVAL(a2, env)) - return env.set(ObjValue(a1), macro) + return env.set(a1.val, macro) elseif first_symbol == "macroexpand" return MacroExpand(ListNth(ast, 1), env) elseif first_symbol == "if" - let condvalue = EVAL(ObjValue(ast)[1], env) + let condvalue = EVAL(ast.val[1], env) if FalseQ(condvalue) || NilQ(condvalue) - if len(ObjValue(ast)) < 4 + if len(ast.val) < 4 return g:MalNil else - let ast = ObjValue(ast)[3] + let ast = ast.val[3] endif else - let ast = ObjValue(ast)[2] + let ast = ast.val[2] endif " TCO elseif first_symbol == "try*" @@ -165,7 +165,7 @@ function EVAL(ast, env) throw v:exception endif - let exc_var = ObjValue(ListNth(catch_clause, 1)) + let exc_var = ListNth(catch_clause, 1).val if v:exception == "__MalException__" let exc_value = g:MalExceptionObj else @@ -175,7 +175,7 @@ function EVAL(ast, env) return EVAL(ListNth(catch_clause, 2), catch_env) endtry elseif first_symbol == "do" - let astlist = ObjValue(ast) + let astlist = ast.val call EvalAst(ListNew(astlist[1:-2]), env) let ast = astlist[-1] " TCO @@ -194,7 +194,7 @@ function EVAL(ast, env) if NativeFunctionQ(funcobj) return NativeFuncInvoke(funcobj, args) elseif FunctionQ(funcobj) - let fn = ObjValue(funcobj) + let fn = funcobj.val let ast = fn.ast let env = NewEnvWithBinds(fn.env, fn.params, args) " TCO diff --git a/vimscript/stepA_mal.vim b/vimscript/stepA_mal.vim index e28f0272c7..f42296dce8 100644 --- a/vimscript/stepA_mal.vim +++ b/vimscript/stepA_mal.vim @@ -20,9 +20,9 @@ function Quasiquote(ast) return ListNew([SymbolNew("quote"), a:ast]) endif let a0 = ListFirst(a:ast) - if SymbolQ(a0) && ObjValue(a0) == "unquote" + if SymbolQ(a0) && a0.val == "unquote" return ListNth(a:ast, 1) - elseif PairQ(a0) && SymbolQ(ListFirst(a0)) && ObjValue(ListFirst(a0)) == "splice-unquote" + elseif PairQ(a0) && SymbolQ(ListFirst(a0)) && ListFirst(a0).val == "splice-unquote" return ListNew([SymbolNew("concat"), ListNth(a0, 1), Quasiquote(ListRest(a:ast))]) else return ListNew([SymbolNew("cons"), Quasiquote(a0), Quasiquote(ListRest(a:ast))]) @@ -37,7 +37,7 @@ function IsMacroCall(ast, env) if !SymbolQ(a0) return 0 endif - let macroname = ObjValue(a0) + let macroname = a0.val if empty(a:env.find(macroname)) return 0 endif @@ -47,7 +47,7 @@ endfunction function MacroExpand(ast, env) let ast = a:ast while IsMacroCall(ast, a:env) - let macroobj = a:env.get(ObjValue(ListFirst(ast))) + let macroobj = a:env.get(ListFirst(ast).val) let macroargs = ListRest(ast) let ast = FuncInvoke(macroobj, macroargs) endwhile @@ -56,23 +56,23 @@ endfunction function EvalAst(ast, env) if SymbolQ(a:ast) - let varname = ObjValue(a:ast) + let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return ListNew(ret) elseif VectorQ(a:ast) let ret = [] - for e in ObjValue(a:ast) + for e in a:ast.val call add(ret, EVAL(e, a:env)) endfor return VectorNew(ret) elseif HashQ(a:ast) let ret = {} - for [k,v] in items(ObjValue(a:ast)) + for [k,v] in items(a:ast.val) let keyobj = HashParseKey(k) let newkey = EVAL(keyobj, a:env) let newval = EVAL(v, a:env) @@ -115,19 +115,19 @@ function EVAL(ast, env) endif let first = ListFirst(ast) - let first_symbol = SymbolQ(first) ? ObjValue(first) : "" + let first_symbol = SymbolQ(first) ? first.val : "" if first_symbol == "def!" - let a1 = ObjValue(ast)[1] - let a2 = ObjValue(ast)[2] - return env.set(ObjValue(a1), EVAL(a2, env)) + let a1 = ast.val[1] + let a2 = ast.val[2] + return env.set(a1.val, EVAL(a2, env)) elseif first_symbol == "let*" - let a1 = ObjValue(ast)[1] - let a2 = ObjValue(ast)[2] + let a1 = ast.val[1] + let a2 = ast.val[2] let env = NewEnv(env) - let let_binds = ObjValue(a1) + let let_binds = a1.val let i = 0 while i < len(let_binds) - call env.set(ObjValue(let_binds[i]), EVAL(let_binds[i+1], env)) + call env.set(let_binds[i].val, EVAL(let_binds[i+1], env)) let i = i + 2 endwhile let ast = a2 @@ -141,19 +141,19 @@ function EVAL(ast, env) let a1 = ListNth(ast, 1) let a2 = ListNth(ast, 2) let macro = MarkAsMacro(EVAL(a2, env)) - return env.set(ObjValue(a1), macro) + return env.set(a1.val, macro) elseif first_symbol == "macroexpand" return MacroExpand(ListNth(ast, 1), env) elseif first_symbol == "if" - let condvalue = EVAL(ObjValue(ast)[1], env) + let condvalue = EVAL(ast.val[1], env) if FalseQ(condvalue) || NilQ(condvalue) - if len(ObjValue(ast)) < 4 + if len(ast.val) < 4 return g:MalNil else - let ast = ObjValue(ast)[3] + let ast = ast.val[3] endif else - let ast = ObjValue(ast)[2] + let ast = ast.val[2] endif " TCO elseif first_symbol == "try*" @@ -165,7 +165,7 @@ function EVAL(ast, env) throw v:exception endif - let exc_var = ObjValue(ListNth(catch_clause, 1)) + let exc_var = ListNth(catch_clause, 1).val if v:exception == "__MalException__" let exc_value = g:MalExceptionObj else @@ -175,7 +175,7 @@ function EVAL(ast, env) return EVAL(ListNth(catch_clause, 2), catch_env) endtry elseif first_symbol == "do" - let astlist = ObjValue(ast) + let astlist = ast.val call EvalAst(ListNew(astlist[1:-2]), env) let ast = astlist[-1] " TCO @@ -194,7 +194,7 @@ function EVAL(ast, env) if NativeFunctionQ(funcobj) return NativeFuncInvoke(funcobj, args) elseif FunctionQ(funcobj) - let fn = ObjValue(funcobj) + let fn = funcobj.val let ast = fn.ast let env = NewEnvWithBinds(fn.env, fn.params, args) " TCO diff --git a/vimscript/types.vim b/vimscript/types.vim index 22e03fb07d..82da979574 100644 --- a/vimscript/types.vim +++ b/vimscript/types.vim @@ -12,10 +12,6 @@ function ObjType(obj) return a:obj["type"] endfunction -function ObjValue(obj) - return a:obj["val"] -endfunction - function ObjHasMeta(obj) return ObjQ(a:obj) && has_key(a:obj, "meta") endfunction @@ -91,11 +87,11 @@ function HashQ(obj) endfunction function FunctionQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "function" && !ObjValue(a:obj).is_macro + return ObjQ(a:obj) && ObjType(a:obj) == "function" && !a:obj.val.is_macro endfunction function MacroQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "function" && ObjValue(a:obj).is_macro + return ObjQ(a:obj) && ObjType(a:obj) == "function" && a:obj.val.is_macro endfunction function NativeFunctionQ(obj) @@ -158,7 +154,7 @@ function HashMakeKey(obj) if !StringQ(a:obj) && !KeywordQ(a:obj) throw "expected hash-map key string, got: " . ObjType(a:obj)); endif - return ObjType(a:obj) . "#" . ObjValue(a:obj) + return ObjType(a:obj) . "#" . a:obj.val endfunction function HashParseKey(str) @@ -186,12 +182,12 @@ function HashBuild(elements) endfunction function HashEqualQ(x, y) - if len(ObjValue(a:x)) != len(ObjValue(a:y)) + if len(a:x.val) != len(a:y.val) return 0 endif - for k in keys(ObjValue(a:x)) - let vx = ObjValue(a:x)[k] - let vy = ObjValue(a:y)[k] + for k in keys(a:x.val) + let vx = a:x.val[k] + let vy = a:y.val[k] if empty(vy) || !EqualQ(vx, vy) return 0 endif @@ -200,13 +196,13 @@ function HashEqualQ(x, y) endfunction function SequentialEqualQ(x, y) - if len(ObjValue(a:x)) != len(ObjValue(a:y)) + if len(a:x.val) != len(a:y.val) return 0 endif let i = 0 - while i < len(ObjValue(a:x)) - let ex = ObjValue(a:x)[i] - let ey = ObjValue(a:y)[i] + while i < len(a:x.val) + let ex = a:x.val[i] + let ey = a:y.val[i] if !EqualQ(ex, ey) return 0 endif @@ -223,31 +219,31 @@ function EqualQ(x, y) elseif ObjType(a:x) != ObjType(a:y) return 0 else - return ObjValue(a:x) == ObjValue(a:y) + return a:x.val == a:y.val endif endfunction function EmptyQ(list) - return empty(ObjValue(a:list)) + return empty(a:list.val) endfunction function ListCount(list) - return len(ObjValue(a:list)) + return len(a:list.val) endfunction function ListNth(list, index) - if a:index >= len(ObjValue(a:list)) + if a:index >= len(a:list.val) throw "nth: index out of range" endif - return ObjValue(a:list)[a:index] + return a:list.val[a:index] endfunction function ListFirst(list) - return get(ObjValue(a:list), 0, g:MalNil) + return get(a:list.val, 0, g:MalNil) endfunction function ListDrop(list, drop_elements) - return ListNew(ObjValue(a:list)[a:drop_elements :]) + return ListNew(a:list.val[a:drop_elements :]) endfunction function ListRest(list) @@ -255,18 +251,18 @@ function ListRest(list) endfunction function FuncInvoke(funcobj, args) - let fn = ObjValue(a:funcobj) + let fn = a:funcobj.val let funcenv = NewEnvWithBinds(fn.env, fn.params, a:args) return EVAL(fn.ast, funcenv) endfunction function NativeFuncInvoke(funcobj, argslist) - let fn = ObjValue(a:funcobj) - return fn.Func(ObjValue(a:argslist)) + let fn = a:funcobj.val + return fn.Func(a:argslist.val) endfunction function MarkAsMacro(funcobj) - let fn = ObjValue(a:funcobj) + let fn = a:funcobj.val let fn.is_macro = 1 return a:funcobj endfunction From b0b1e16947c22ae4c89dc5c0272c9ab162a098f0 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Wed, 26 Oct 2016 06:56:00 +0000 Subject: [PATCH 0198/2308] vimscript: Remove calls to ObjType --- vimscript/core.vim | 2 +- vimscript/types.vim | 40 ++++++++++++++++++---------------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/vimscript/core.vim b/vimscript/core.vim index 859ca2f37a..f85cd83139 100644 --- a/vimscript/core.vim +++ b/vimscript/core.vim @@ -221,7 +221,7 @@ let CoreNs = { \ "conj": NewNativeFn("MalConj"), \ "seq": NewNativeFn("MalSeq"), \ "meta": NewNativeFnLambda({a -> ObjMeta(a[0])}), - \ "with-meta": NewNativeFnLambda({a -> ObjNewWithMeta(ObjType(a[0]), copy(a[0].val), a[1])}), + \ "with-meta": NewNativeFnLambda({a -> ObjNewWithMeta(a[0].type, copy(a[0].val), a[1])}), \ "atom": NewNativeFnLambda({a -> AtomNew(a[0])}), \ "atom?": NewNativeFnLambda({a -> BoolNew(AtomQ(a[0]))}), \ "deref": NewNativeFnLambda({a -> a[0].val}), diff --git a/vimscript/types.vim b/vimscript/types.vim index 82da979574..40a19a61fc 100644 --- a/vimscript/types.vim +++ b/vimscript/types.vim @@ -8,10 +8,6 @@ function ObjNew(obj_type, obj_val) return {"type": a:obj_type, "val": a:obj_val} endfunction -function ObjType(obj) - return a:obj["type"] -endfunction - function ObjHasMeta(obj) return ObjQ(a:obj) && has_key(a:obj, "meta") endfunction @@ -35,47 +31,47 @@ function ObjQ(obj) endfunction function SymbolQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "symbol" + return ObjQ(a:obj) && a:obj.type == "symbol" endfunction function StringQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "string" + return ObjQ(a:obj) && a:obj.type == "string" endfunction function KeywordQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "keyword" + return ObjQ(a:obj) && a:obj.type == "keyword" endfunction function AtomQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "atom" + return ObjQ(a:obj) && a:obj.type == "atom" endfunction function NilQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "nil" + return ObjQ(a:obj) && a:obj.type == "nil" endfunction function TrueQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "true" + return ObjQ(a:obj) && a:obj.type == "true" endfunction function FalseQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "false" + return ObjQ(a:obj) && a:obj.type == "false" endfunction function IntegerQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "integer" + return ObjQ(a:obj) && a:obj.type == "integer" endfunction function FloatQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "float" + return ObjQ(a:obj) && a:obj.type == "float" endfunction function ListQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "list" + return ObjQ(a:obj) && a:obj.type == "list" endfunction function VectorQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "vector" + return ObjQ(a:obj) && a:obj.type == "vector" endfunction function SequentialQ(obj) @@ -83,19 +79,19 @@ function SequentialQ(obj) endfunction function HashQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "hash" + return ObjQ(a:obj) && a:obj.type == "hash" endfunction function FunctionQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "function" && !a:obj.val.is_macro + return ObjQ(a:obj) && a:obj.type == "function" && !a:obj.val.is_macro endfunction function MacroQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "function" && a:obj.val.is_macro + return ObjQ(a:obj) && a:obj.type == "function" && a:obj.val.is_macro endfunction function NativeFunctionQ(obj) - return ObjQ(a:obj) && ObjType(a:obj) == "nativefunction" + return ObjQ(a:obj) && a:obj.type == "nativefunction" endfunction function NilNew() @@ -152,9 +148,9 @@ endfunction function HashMakeKey(obj) if !StringQ(a:obj) && !KeywordQ(a:obj) - throw "expected hash-map key string, got: " . ObjType(a:obj)); + throw "expected hash-map key string, got: " . a:obj.type); endif - return ObjType(a:obj) . "#" . a:obj.val + return a:obj.type . "#" . a:obj.val endfunction function HashParseKey(str) @@ -216,7 +212,7 @@ function EqualQ(x, y) return SequentialEqualQ(a:x, a:y) elseif HashQ(a:x) && HashQ(a:y) return HashEqualQ(a:x, a:y) - elseif ObjType(a:x) != ObjType(a:y) + elseif a:x.type != a:y.type return 0 else return a:x.val == a:y.val From b628a220744550217356806ffca3ae72ad7f7d61 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Wed, 26 Oct 2016 07:33:22 +0000 Subject: [PATCH 0199/2308] vimscript: Remove useless calls to ObjQ --- vimscript/types.vim | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/vimscript/types.vim b/vimscript/types.vim index 40a19a61fc..eb39359e46 100644 --- a/vimscript/types.vim +++ b/vimscript/types.vim @@ -9,7 +9,7 @@ function ObjNew(obj_type, obj_val) endfunction function ObjHasMeta(obj) - return ObjQ(a:obj) && has_key(a:obj, "meta") + return has_key(a:obj, "meta") endfunction function ObjMeta(obj) @@ -26,72 +26,68 @@ function ObjSetMeta(obj, newmeta) return a:newmeta endfunction -function ObjQ(obj) - return type(a:obj) == type({}) -endfunction - function SymbolQ(obj) - return ObjQ(a:obj) && a:obj.type == "symbol" + return a:obj.type == "symbol" endfunction function StringQ(obj) - return ObjQ(a:obj) && a:obj.type == "string" + return a:obj.type == "string" endfunction function KeywordQ(obj) - return ObjQ(a:obj) && a:obj.type == "keyword" + return a:obj.type == "keyword" endfunction function AtomQ(obj) - return ObjQ(a:obj) && a:obj.type == "atom" + return a:obj.type == "atom" endfunction function NilQ(obj) - return ObjQ(a:obj) && a:obj.type == "nil" + return a:obj.type == "nil" endfunction function TrueQ(obj) - return ObjQ(a:obj) && a:obj.type == "true" + return a:obj.type == "true" endfunction function FalseQ(obj) - return ObjQ(a:obj) && a:obj.type == "false" + return a:obj.type == "false" endfunction function IntegerQ(obj) - return ObjQ(a:obj) && a:obj.type == "integer" + return a:obj.type == "integer" endfunction function FloatQ(obj) - return ObjQ(a:obj) && a:obj.type == "float" + return a:obj.type == "float" endfunction function ListQ(obj) - return ObjQ(a:obj) && a:obj.type == "list" + return a:obj.type == "list" endfunction function VectorQ(obj) - return ObjQ(a:obj) && a:obj.type == "vector" + return a:obj.type == "vector" endfunction function SequentialQ(obj) - return ObjQ(a:obj) && ListQ(a:obj) || VectorQ(a:obj) + return ListQ(a:obj) || VectorQ(a:obj) endfunction function HashQ(obj) - return ObjQ(a:obj) && a:obj.type == "hash" + return a:obj.type == "hash" endfunction function FunctionQ(obj) - return ObjQ(a:obj) && a:obj.type == "function" && !a:obj.val.is_macro + return a:obj.type == "function" && !a:obj.val.is_macro endfunction function MacroQ(obj) - return ObjQ(a:obj) && a:obj.type == "function" && a:obj.val.is_macro + return a:obj.type == "function" && a:obj.val.is_macro endfunction function NativeFunctionQ(obj) - return ObjQ(a:obj) && a:obj.type == "nativefunction" + return a:obj.type == "nativefunction" endfunction function NilNew() From 4fbbe5719cf038535d94d223e8ab55d55b5df8f5 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Wed, 26 Oct 2016 08:19:38 +0000 Subject: [PATCH 0200/2308] vimscript: Use map and lambda in EvalAst --- vimscript/env.vim | 1 - vimscript/step2_eval.vim | 12 ++---------- vimscript/step3_env.vim | 12 ++---------- vimscript/step4_if_fn_do.vim | 12 ++---------- vimscript/step5_tco.vim | 12 ++---------- vimscript/step6_file.vim | 12 ++---------- vimscript/step7_quote.vim | 12 ++---------- vimscript/step8_macros.vim | 12 ++---------- vimscript/step9_try.vim | 12 ++---------- vimscript/stepA_mal.vim | 12 ++---------- 10 files changed, 18 insertions(+), 91 deletions(-) diff --git a/vimscript/env.vim b/vimscript/env.vim index 75a03360ed..3316e19356 100644 --- a/vimscript/env.vim +++ b/vimscript/env.vim @@ -15,7 +15,6 @@ function NewEnvWithBinds(outer, binds, exprs) while i < ListCount(a:binds) let varname = ListNth(a:binds, i).val if varname == "&" - " TODO let restvarname = ListNth(a:binds, i + 1).val let restvarvalues = ListDrop(a:exprs, i) call env.set(restvarname, restvarvalues) diff --git a/vimscript/step2_eval.vim b/vimscript/step2_eval.vim index 4cbf8e592f..3219526e04 100644 --- a/vimscript/step2_eval.vim +++ b/vimscript/step2_eval.vim @@ -15,17 +15,9 @@ function EvalAst(ast, env) end return a:env[varname] elseif ListQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return ListNew(ret) + return ListNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif VectorQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return VectorNew(ret) + return VectorNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif HashQ(a:ast) let ret = {} for [k,v] in items(a:ast.val) diff --git a/vimscript/step3_env.vim b/vimscript/step3_env.vim index d5cc4e5bda..9697dd02bc 100644 --- a/vimscript/step3_env.vim +++ b/vimscript/step3_env.vim @@ -13,17 +13,9 @@ function EvalAst(ast, env) let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return ListNew(ret) + return ListNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif VectorQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return VectorNew(ret) + return VectorNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif HashQ(a:ast) let ret = {} for [k,v] in items(a:ast.val) diff --git a/vimscript/step4_if_fn_do.vim b/vimscript/step4_if_fn_do.vim index 395ddd0ee9..f213edaff7 100644 --- a/vimscript/step4_if_fn_do.vim +++ b/vimscript/step4_if_fn_do.vim @@ -14,17 +14,9 @@ function EvalAst(ast, env) let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return ListNew(ret) + return ListNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif VectorQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return VectorNew(ret) + return VectorNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif HashQ(a:ast) let ret = {} for [k,v] in items(a:ast.val) diff --git a/vimscript/step5_tco.vim b/vimscript/step5_tco.vim index 1581c9649d..de36ab9e59 100644 --- a/vimscript/step5_tco.vim +++ b/vimscript/step5_tco.vim @@ -14,17 +14,9 @@ function EvalAst(ast, env) let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return ListNew(ret) + return ListNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif VectorQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return VectorNew(ret) + return VectorNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif HashQ(a:ast) let ret = {} for [k,v] in items(a:ast.val) diff --git a/vimscript/step6_file.vim b/vimscript/step6_file.vim index 92051c2d71..e7c8c62a3a 100644 --- a/vimscript/step6_file.vim +++ b/vimscript/step6_file.vim @@ -14,17 +14,9 @@ function EvalAst(ast, env) let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return ListNew(ret) + return ListNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif VectorQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return VectorNew(ret) + return VectorNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif HashQ(a:ast) let ret = {} for [k,v] in items(a:ast.val) diff --git a/vimscript/step7_quote.vim b/vimscript/step7_quote.vim index 243f887553..7ee66b88f2 100644 --- a/vimscript/step7_quote.vim +++ b/vimscript/step7_quote.vim @@ -32,17 +32,9 @@ function EvalAst(ast, env) let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return ListNew(ret) + return ListNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif VectorQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return VectorNew(ret) + return VectorNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif HashQ(a:ast) let ret = {} for [k,v] in items(a:ast.val) diff --git a/vimscript/step8_macros.vim b/vimscript/step8_macros.vim index 080409085e..96e676ce17 100644 --- a/vimscript/step8_macros.vim +++ b/vimscript/step8_macros.vim @@ -57,17 +57,9 @@ function EvalAst(ast, env) let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return ListNew(ret) + return ListNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif VectorQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return VectorNew(ret) + return VectorNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif HashQ(a:ast) let ret = {} for [k,v] in items(a:ast.val) diff --git a/vimscript/step9_try.vim b/vimscript/step9_try.vim index 8ec81a7cb7..421e59cd74 100644 --- a/vimscript/step9_try.vim +++ b/vimscript/step9_try.vim @@ -59,17 +59,9 @@ function EvalAst(ast, env) let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return ListNew(ret) + return ListNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif VectorQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return VectorNew(ret) + return VectorNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif HashQ(a:ast) let ret = {} for [k,v] in items(a:ast.val) diff --git a/vimscript/stepA_mal.vim b/vimscript/stepA_mal.vim index f42296dce8..2f64d98747 100644 --- a/vimscript/stepA_mal.vim +++ b/vimscript/stepA_mal.vim @@ -59,17 +59,9 @@ function EvalAst(ast, env) let varname = a:ast.val return a:env.get(varname) elseif ListQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return ListNew(ret) + return ListNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif VectorQ(a:ast) - let ret = [] - for e in a:ast.val - call add(ret, EVAL(e, a:env)) - endfor - return VectorNew(ret) + return VectorNew(map(copy(a:ast.val), {_, e -> EVAL(e, a:env)})) elseif HashQ(a:ast) let ret = {} for [k,v] in items(a:ast.val) From de2f4de9b8700280c10e7d9b5a3e542e9f39fc0a Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 26 Oct 2016 20:43:17 -0500 Subject: [PATCH 0201/2308] Basic: use static empty sequences in reader. - Add vector and hash-map empty static sequences in types --- basic/reader.in.bas | 35 +++++++++++++++++------------------ basic/types.in.bas | 12 +++++++++--- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 3779ea1e89..0bc1674355 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -156,11 +156,9 @@ READ_FORM: REM PRINT "READ_SEQ" SD=SD+1: REM increase read sequence depth - REM allocate first sequence entry and space for value - L=0:N=0:GOSUB ALLOC: REM T alread set above - - REM set reference value/pointer to new embedded sequence - IF SD>1 THEN Z%(X%(X)+1,1)=R + REM point to empty sequence to start off + R=(T-5)*2+1: REM calculate location of empty seq + Z%(R,0)=Z%(R,0)+32 REM push start ptr on the stack X=X+1 @@ -182,34 +180,35 @@ READ_FORM: SD=SD-1: REM decrease read sequence depth R=X%(X-2): REM ptr to start of sequence to return T=X%(X-1): REM type prior to recur - X=X-3: REM pop previous, type, and start off the stack + X=X-3: REM pop start, type and previous off the stack GOTO READ_FORM_DONE READ_FORM_DONE: RI=RI+LEN(T$) - T8=R: REM save previous value - REM check read sequence depth IF SD=0 THEN RETURN - REM PRINT "READ_FORM_DONE next list entry" - - REM allocate new sequence entry and space for value - REM set type to previous type, with ref count of 1 (from previous) - T=X%(X-1):L=0:N=0:GOSUB ALLOC REM previous element T7=X%(X) - REM set previous list element to point to new element - Z%(T7,1)=R - REM set the list value pointer - Z%(T7+1,1)=T8 - IF T7=X%(X-2) THEN GOTO READ_FORM_SKIP_FIRST + REM allocate new sequence entry, set type to previous type, set + REM next to previous next or previous (if first) + L=Z%(T7,1) + IF T7<9 THEN L=T7 + T8=R: REM save previous value for release + T=X%(X-1):N=R:GOSUB ALLOC + AY=T8:GOSUB RELEASE: REM list takes ownership + + REM if previous element is the first element then set + REM the first to the new element + IF T7<9 THEN X%(X-2)=R:GOTO READ_FORM_SKIP_FIRST + REM set previous list element to point to new element Z%(T7,1)=R READ_FORM_SKIP_FIRST: + REM update previous pointer to current element X%(X)=R GOTO READ_FORM diff --git a/basic/types.in.bas b/basic/types.in.bas index 42795766b2..6d0bdb8817 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -48,12 +48,16 @@ INIT_MEMORY: Z%(2,0)=1:Z%(2,1)=1 Z%(3,0)=6+32:Z%(3,1)=0 Z%(4,0)=0:Z%(4,1)=0 + Z%(5,0)=7+32:Z%(5,1)=0 + Z%(6,0)=0:Z%(6,1)=0 + Z%(7,0)=8+32:Z%(7,1)=0 + Z%(8,0)=0:Z%(8,1)=0 REM start of unused memory - ZI=5 + ZI=9 REM start of free list - ZK=5 + ZK=9 REM string memory storage S=0:DIM S$(Z2) @@ -454,7 +458,9 @@ REM hashmap functions REM HASHMAP() -> R HASHMAP: - T=8:L=0:N=0:GOSUB ALLOC + REM just point to static empty hash-map + R=7 + Z%(R,0)=Z%(R,0)+32 RETURN REM ASSOC1(H, K, V) -> R From c7330b3d694f3cf6641193f65170ae5ef59b83b3 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 26 Oct 2016 22:29:09 -0500 Subject: [PATCH 0202/2308] Basic: fix errors, reader, if form. Self-host 0-3 - Stop let binding eval on error. Also don't continue into EVAL if error. - if without a false position was freeing up too much when it finished. - fix reader so that it doesn't keep incrementing ref cnt of static empty sequences. --- basic/core.in.bas | 23 +++++++++++++++-------- basic/reader.in.bas | 4 +++- basic/step2_eval.in.bas | 4 +++- basic/step3_env.in.bas | 6 +++++- basic/step4_if_fn_do.in.bas | 9 +++++++-- basic/step5_tco.in.bas | 9 +++++++-- basic/step6_file.in.bas | 9 +++++++-- basic/step7_quote.in.bas | 9 +++++++-- basic/step8_macros.in.bas | 9 +++++++-- basic/step9_try.in.bas | 9 +++++++-- basic/stepA_mal.in.bas | 9 +++++++-- basic/types.in.bas | 6 ++++-- 12 files changed, 79 insertions(+), 27 deletions(-) diff --git a/basic/core.in.bas b/basic/core.in.bas index 0f59cd5b12..490aae1efe 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -50,7 +50,7 @@ SUB DO_TCO_FUNCTION DO_APPLY: F=AA AR=Z%(AR,1) - A=AR:GOSUB COUNT:R4=R + B=AR:GOSUB COUNT:R4=R A=Z%(AR+1,1) REM no intermediate args, but not a list, so convert it first @@ -108,12 +108,14 @@ SUB DO_TCO_FUNCTION AR=R:CALL APPLY - REM pop apply args are release them + REM pop apply args and release them AY=X%(X):X=X-1:GOSUB RELEASE REM set the result value Z%(X%(X-2)+1,1)=R + IF ER<>-2 THEN GOTO DO_MAP_DONE + REM restore F F=X%(X-1) @@ -127,8 +129,11 @@ SUB DO_TCO_FUNCTION GOTO DO_MAP_LOOP DO_MAP_DONE: - REM get return val - R=X%(X-3) + REM if no error, get return val + IF ER=-2 THEN R=X%(X-3) + REM otherwise, free the return value and return nil + IF ER<>-2 THEN R=0:AY=X%(X-3):GOSUB RELEASE + REM pop everything off stack X=X-4 GOTO DO_TCO_FUNCTION_DONE @@ -178,7 +183,7 @@ DO_FUNCTION: REM Switch on the function number IF FF>59 THEN ER=-1:ER$="unknown function"+STR$(FF):RETURN - ON FF/10+1 GOTO DO_1_9,DO_10_19,DO_20_29,DO_30_39,DO_40_49,DO_50_56 + ON FF/10+1 GOTO DO_1_9,DO_10_19,DO_20_29,DO_30_39,DO_40_49,DO_50_59 DO_1_9: ON FF GOTO DO_EQUAL_Q,DO_THROW,DO_NIL_Q,DO_TRUE_Q,DO_FALSE_Q,DO_STRING_Q,DO_SYMBOL,DO_SYMBOL_Q,DO_KEYWORD @@ -190,8 +195,9 @@ DO_FUNCTION: ON FF-29 GOTO DO_VECTOR_Q,DO_HASH_MAP,DO_MAP_Q,DO_ASSOC,DO_THROW,DO_GET,DO_CONTAINS,DO_KEYS,DO_VALS,DO_SEQUENTIAL_Q DO_40_49: ON FF-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_THROW,DO_THROW,DO_WITH_META - DO_50_56: + DO_50_59: ON FF-49 GOTO DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_EVAL,DO_READ_FILE + REM ,DO_PR_MEMORY_SUMMARY DO_EQUAL_Q: A=AA:B=AB:GOSUB EQUAL_Q @@ -437,8 +443,8 @@ DO_FUNCTION: AB=R GOTO DO_CONCAT_LOOP DO_NTH: + B=AA:GOSUB COUNT B=Z%(AB,1) - A=AA:GOSUB COUNT IF R<=B THEN R=0:ER=-1:ER$="nth: index out of range":RETURN DO_NTH_LOOP: IF B=0 THEN GOTO DO_NTH_DONE @@ -466,7 +472,7 @@ DO_FUNCTION: IF Z%(AA,1)=0 THEN R=2 RETURN DO_COUNT: - A=AA:GOSUB COUNT + B=AA:GOSUB COUNT T=2:L=R:GOSUB ALLOC RETURN @@ -591,6 +597,7 @@ INIT_CORE_NS: K$="eval":A=55:GOSUB INIT_CORE_SET_FUNCTION K$="read-file":A=56:GOSUB INIT_CORE_SET_FUNCTION + REM K$="pr-memory-summary":A=57:GOSUB INIT_CORE_SET_FUNCTION REM these are in DO_TCO_FUNCTION K$="apply":A=61:GOSUB INIT_CORE_SET_FUNCTION diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 0bc1674355..5b50aeeac8 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -199,7 +199,9 @@ READ_FORM: IF T7<9 THEN L=T7 T8=R: REM save previous value for release T=X%(X-1):N=R:GOSUB ALLOC - AY=T8:GOSUB RELEASE: REM list takes ownership + REM list takes ownership + IF L<9 THEN AY=L:GOSUB RELEASE + AY=T8:GOSUB RELEASE REM if previous element is the first element then set REM the first to the new element diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index 7f399124e0..d637bf9f88 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -62,7 +62,7 @@ SUB EVAL_AST IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -117,6 +117,8 @@ SUB EVAL REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A + IF ER<>-2 THEN GOTO EVAL_RETURN + REM AZ=A:PR=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index 5393eff194..28215c8b8e 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -61,7 +61,7 @@ SUB EVAL_AST IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -119,6 +119,8 @@ SUB EVAL REM push A and E on the stack X=X+2:X%(X-1)=E:X%(X)=A + IF ER<>-2 THEN GOTO EVAL_RETURN + REM AZ=A:PR=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" @@ -186,6 +188,8 @@ SUB EVAL A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 + IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE + REM set environment: even A1 key to odd A1 eval'd above K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index ab26bacb8d..a247e1af41 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -60,7 +60,7 @@ SUB EVAL_AST IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -120,6 +120,8 @@ SUB EVAL EVAL_TCO_RECUR: + IF ER<>-2 THEN GOTO EVAL_RETURN + REM AZ=A:PR=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" @@ -190,6 +192,8 @@ SUB EVAL A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 + IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE + REM set environment: even A1 key to odd A1 eval'd above K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership @@ -229,7 +233,8 @@ SUB EVAL EVAL_IF_FALSE: AY=R:GOSUB RELEASE REM if no false case (A3), return nil - IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN + B=A:GOSUB COUNT + IF R<4 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index 1e7e28098c..668ab3d165 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -63,7 +63,7 @@ SUB EVAL_AST IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -123,6 +123,8 @@ SUB EVAL EVAL_TCO_RECUR: + IF ER<>-2 THEN GOTO EVAL_RETURN + REM AZ=A:PR=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" @@ -195,6 +197,8 @@ SUB EVAL A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 + IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE + REM set environment: even A1 key to odd A1 eval'd above K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership @@ -247,7 +251,8 @@ SUB EVAL EVAL_IF_FALSE: AY=R:GOSUB RELEASE REM if no false case (A3), return nil - IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN + B=A:GOSUB COUNT + IF R<4 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index 0c80ae6db5..bdf31e5835 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -63,7 +63,7 @@ SUB EVAL_AST IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -123,6 +123,8 @@ SUB EVAL EVAL_TCO_RECUR: + IF ER<>-2 THEN GOTO EVAL_RETURN + REM AZ=A:PR=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" @@ -195,6 +197,8 @@ SUB EVAL A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 + IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE + REM set environment: even A1 key to odd A1 eval'd above K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership @@ -247,7 +251,8 @@ SUB EVAL EVAL_IF_FALSE: AY=R:GOSUB RELEASE REM if no false case (A3), return nil - IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN + B=A:GOSUB COUNT + IF R<4 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index e72e072451..cc5abfdba5 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -138,7 +138,7 @@ SUB EVAL_AST IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -198,6 +198,8 @@ SUB EVAL EVAL_TCO_RECUR: + IF ER<>-2 THEN GOTO EVAL_RETURN + REM AZ=A:PR=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" @@ -272,6 +274,8 @@ SUB EVAL A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 + IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE + REM set environment: even A1 key to odd A1 eval'd above K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership @@ -338,7 +342,8 @@ SUB EVAL EVAL_IF_FALSE: AY=R:GOSUB RELEASE REM if no false case (A3), return nil - IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN + B=A:GOSUB COUNT + IF R<4 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index 225289f559..7484a3957e 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -174,7 +174,7 @@ SUB EVAL_AST IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -234,6 +234,8 @@ SUB EVAL EVAL_TCO_RECUR: + IF ER<>-2 THEN GOTO EVAL_RETURN + REM AZ=A:PR=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" @@ -316,6 +318,8 @@ SUB EVAL A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 + IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE + REM set environment: even A1 key to odd A1 eval'd above K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership @@ -407,7 +411,8 @@ SUB EVAL EVAL_IF_FALSE: AY=R:GOSUB RELEASE REM if no false case (A3), return nil - IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN + B=A:GOSUB COUNT + IF R<4 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index 583beb66b9..a96bf31545 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -234,6 +234,8 @@ SUB EVAL EVAL_TCO_RECUR: + IF ER<>-2 THEN GOTO EVAL_RETURN + REM AZ=A:PR=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" @@ -317,6 +319,8 @@ SUB EVAL A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 + IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE + REM set environment: even A1 key to odd A1 eval'd above K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership @@ -414,7 +418,7 @@ SUB EVAL REM bind the catch symbol to the error object K=A1:V=ER:GOSUB ENV_SET - AY=R:GOSUB RELEASE: REM release out use, env took ownership + AY=R:GOSUB RELEASE: REM release our use, env took ownership REM unset error for catch eval ER=-2:ER$="" @@ -439,7 +443,8 @@ SUB EVAL EVAL_IF_FALSE: AY=R:GOSUB RELEASE REM if no false case (A3), return nil - IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN + B=A:GOSUB COUNT + IF R<4 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index a077327964..bbc5503d1c 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -234,6 +234,8 @@ SUB EVAL EVAL_TCO_RECUR: + IF ER<>-2 THEN GOTO EVAL_RETURN + REM AZ=A:PR=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" @@ -317,6 +319,8 @@ SUB EVAL A=Z%(A1,1)+1:CALL EVAL A1=X%(X):X=X-1: REM pop A1 + IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE + REM set environment: even A1 key to odd A1 eval'd above K=A1+1:V=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership @@ -414,7 +418,7 @@ SUB EVAL REM bind the catch symbol to the error object K=A1:V=ER:GOSUB ENV_SET - AY=R:GOSUB RELEASE: REM release out use, env took ownership + AY=R:GOSUB RELEASE: REM release our use, env took ownership REM unset error for catch eval ER=-2:ER$="" @@ -439,7 +443,8 @@ SUB EVAL EVAL_IF_FALSE: AY=R:GOSUB RELEASE REM if no false case (A3), return nil - IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN + B=A:GOSUB COUNT + IF R<4 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop diff --git a/basic/types.in.bas b/basic/types.in.bas index 6d0bdb8817..0ce329db88 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -380,12 +380,14 @@ EMPTY_Q: IF Z%(A,1)=0 THEN R=1 RETURN -REM COUNT(A) -> R +REM COUNT(B) -> R +REM - returns length of list, not a Z% index +REM - modifies B COUNT: R=-1 DO_COUNT_LOOP: R=R+1 - IF Z%(A,1)<>0 THEN A=Z%(A,1):GOTO DO_COUNT_LOOP + IF Z%(B,1)<>0 THEN B=Z%(B,1):GOTO DO_COUNT_LOOP RETURN REM LAST(A) -> R From 4e7d8a1bcfcdf7947072e80de5a1f7e6133c4540 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 27 Oct 2016 23:47:05 -0500 Subject: [PATCH 0203/2308] Basic: fix step6 arg test. Gensym. Misc cleanup. - Strip linefeeds in run_argv_test.sh so that step6 arg test passes for basic. - Add gensym and convert or macro. - Add gitignore entries for transpiled basic sources. - Add conj/seq stubs so that step4 self-host loads (if non-step4 functions are commented out in core.mal) - Bump up Z% value space by 256 spaces (1K) - Remove old qb2cbm.sh --- .gitignore | 12 ++++ basic/core.in.bas | 12 +++- basic/qb2cbm.sh | 107 ---------------------------------- basic/step0_repl.in.bas | 2 +- basic/step1_read_print.in.bas | 3 +- basic/step2_eval.in.bas | 3 +- basic/step3_env.in.bas | 3 +- basic/step4_if_fn_do.in.bas | 3 +- basic/step5_tco.in.bas | 3 +- basic/step6_file.in.bas | 3 +- basic/step7_quote.in.bas | 3 +- basic/step8_macros.in.bas | 3 +- basic/step9_try.in.bas | 3 +- basic/stepA_mal.in.bas | 13 ++++- basic/types.in.bas | 2 +- run_argv_test.sh | 4 +- tests/perf3.mal | 10 ---- 17 files changed, 44 insertions(+), 145 deletions(-) delete mode 100755 basic/qb2cbm.sh diff --git a/.gitignore b/.gitignore index 3571dcf1bc..e5becc91af 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,15 @@ vb/*.dll vimscript/mal.vim clisp/*.fas clisp/*.lib +basic/step0_repl.bas +basic/step1_read_print.bas +basic/step2_eval.bas +basic/step3_env.bas +basic/step4_if_fn_do.bas +basic/step5_tco.bas +basic/step6_file.bas +basic/step7_quote.bas +basic/step8_macros.bas +basic/step9_try.bas +basic/stepA_mal.bas +basic/*.prg diff --git a/basic/core.in.bas b/basic/core.in.bas index 490aae1efe..1930fb8c39 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -194,7 +194,7 @@ DO_FUNCTION: DO_30_39: ON FF-29 GOTO DO_VECTOR_Q,DO_HASH_MAP,DO_MAP_Q,DO_ASSOC,DO_THROW,DO_GET,DO_CONTAINS,DO_KEYS,DO_VALS,DO_SEQUENTIAL_Q DO_40_49: - ON FF-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_THROW,DO_THROW,DO_WITH_META + ON FF-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_CONJ,DO_SEQ,DO_WITH_META DO_50_59: ON FF-49 GOTO DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_EVAL,DO_READ_FILE REM ,DO_PR_MEMORY_SUMMARY @@ -475,6 +475,12 @@ DO_FUNCTION: B=AA:GOSUB COUNT T=2:L=R:GOSUB ALLOC RETURN + DO_CONJ: + R=0 + RETURN + DO_SEQ: + R=0 + RETURN DO_WITH_META: T=Z%(AA,0)AND31 @@ -585,8 +591,8 @@ INIT_CORE_NS: K$="empty?":A=45:GOSUB INIT_CORE_SET_FUNCTION K$="count":A=46:GOSUB INIT_CORE_SET_FUNCTION - REM K$="conj":A=47:GOSUB INIT_CORE_SET_FUNCTION - REM K$="seq":A=48:GOSUB INIT_CORE_SET_FUNCTION + K$="conj":A=47:GOSUB INIT_CORE_SET_FUNCTION + K$="seq":A=48:GOSUB INIT_CORE_SET_FUNCTION K$="with-meta":A=49:GOSUB INIT_CORE_SET_FUNCTION K$="meta":A=50:GOSUB INIT_CORE_SET_FUNCTION diff --git a/basic/qb2cbm.sh b/basic/qb2cbm.sh deleted file mode 100755 index b507853654..0000000000 --- a/basic/qb2cbm.sh +++ /dev/null @@ -1,107 +0,0 @@ -#!/bin/bash - -set -e - -DEBUG=${DEBUG:-} -KEEP_REM=${KEEP_REM:-1} -# 0 - drop all REMs -# 1 - keep LABEL and INCLUDE REMs (and blank lines) -# 2 - keep LABEL, INCLUDE, and GOTO REMs -# 3 - keep LABEL, INCLUDE, GOTO, and whole line REMs -# 4 - keep all REMS (end of line REMs too) - -infile=$1 - -die () { - echo >&2 "$*" - exit 1 -} - -[ "${infile}" ] || die "Usage: " - -input=$(cat ${infile}) - -[ "${DEBUG}" ] && echo >&2 "Processing includes" - -full="${input}" -declare -A included - -while [[ ${input} =~ REM\ \$INCLUDE:\ \'.*\' ]]; do - full="" - while read -r line; do - if [[ ${line} =~ REM\ \$INCLUDE:\ \'.*\' ]]; then - include=${line#REM \$INCLUDE: \'} - include=${include%\'} - # Only include it once - if [ "${included[${include}]}" ];then - [ "${DEBUG}" ] && echo >&2 "already included: ${include}" - continue - fi - [ "${DEBUG}" ] && echo >&2 "including: ${include}" - included[${include}]="done" - if [ "${KEEP_REM}" -ge 1 ]; then - full="${full}\nREM vvv BEGIN '${include}' vvv\n$(cat ${include})\nREM vvv END '${include}' vvv\n" - else - full="${full}\n$(cat ${include})\n" - fi - else - full="${full}${line}\n" - fi - done < <(echo -e "${input}") - input="${full}" -done - - -[ "${DEBUG}" ] && echo >&2 "Renumbering" - -data="" -declare -A labels - -lnum=1 -while read -r line; do - if [[ ${line} =~ ^\ *# ]]; then - [ "${DEBUG}" ] && echo >&2 "ignoring # style comment at $lnum" - continue - elif [[ "${KEEP_REM}" -lt 3 && ${line} =~ ^\ *REM && \ - ! ${line} =~ REM\ vvv && ! ${line} =~ REM\ ^^^ ]]; then - [ "${DEBUG}" ] && echo >&2 "dropping REM comment: ${line}" - continue - elif [[ ${line} =~ ^\ *$ ]]; then - if [ "${KEEP_REM}" -ge 1 ]; then - [ "${DEBUG}" ] && echo >&2 "found blank line at $lnum" - data="${data}\n" - else - [ "${DEBUG}" ] && echo >&2 "ignoring blank line at $lnum" - fi - continue - elif [[ ${line} =~ ^[A-Za-z_][A-Za-z0-9_]*:$ ]]; then - label=${line%:} - [ "${DEBUG}" ] && echo >&2 "found label ${label} at $lnum" - labels[${label}]=$lnum - if [ "${KEEP_REM}" -ge 1 ]; then - data="${data}${lnum} REM ${label}:\n" - else - continue - fi - else - data="${data}${lnum} ${line}\n" - fi - lnum=$(( lnum + 1 )) -done < <(echo -e "${input}") - -if [[ "${KEEP_REM}" -lt 4 ]]; then - [ "${DEBUG}" ] && echo >&2 "Dropping line ending REMs" - data=$(echo -e "${data}" | sed "s/: REM [^\n]*$//") -fi - -for label in "${!labels[@]}"; do - [ "${DEBUG}" ] && echo >&2 "Updating label: ${label}" - lnum=${labels[${label}]} - if [ "${KEEP_REM}" -ge 2 ]; then - data=$(echo "${data}" | sed "s/\(THEN\|GOTO\|GOSUB\) ${label}\>/\1 ${lnum}: REM ${label}/g") - else - data=$(echo "${data}" | sed "s/\(THEN\|GOTO\|GOSUB\) ${label}\>/\1 ${lnum}/g") - fi -done - -echo -e "${data}" diff --git a/basic/step0_repl.in.bas b/basic/step0_repl.in.bas index 1c3db7c4e0..5d4cd44f6e 100755 --- a/basic/step0_repl.in.bas +++ b/basic/step0_repl.in.bas @@ -36,6 +36,6 @@ MAIN: GOTO REPL_LOOP QUIT: - PRINT "Free: "+STR$(FRE(0)) + REM PRINT "Free: "+STR$(FRE(0)) END diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index ae99f752cd..a243455afd 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -56,8 +56,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1=ZT: P2=-1: GOSUB PR_MEMORY - GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY END PRINT_ERROR: diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index d637bf9f88..893fcc9509 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -264,8 +264,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1=ZT: P2=-1: GOSUB PR_MEMORY - GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY END PRINT_ERROR: diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index 28215c8b8e..828fe0a029 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -338,8 +338,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1=ZT: P2=-1: GOSUB PR_MEMORY - GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY END PRINT_ERROR: diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index a247e1af41..fec1508177 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -393,8 +393,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1=ZT: P2=-1: GOSUB PR_MEMORY - GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY END PRINT_ERROR: diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index 668ab3d165..4083265392 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -411,8 +411,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1=ZT: P2=-1: GOSUB PR_MEMORY - GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY END PRINT_ERROR: diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index bdf31e5835..95cb2fd5e5 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -438,8 +438,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1=ZT: P2=-1: GOSUB PR_MEMORY - GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY END PRINT_ERROR: diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index cc5abfdba5..208256b414 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -529,8 +529,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1=ZT: P2=-1: GOSUB PR_MEMORY - GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY END PRINT_ERROR: diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index 7484a3957e..e698efa497 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -607,8 +607,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1=ZT: P2=-1: GOSUB PR_MEMORY - GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY END PRINT_ERROR: diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index a96bf31545..16ff0e0299 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -639,8 +639,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1=ZT: P2=-1: GOSUB PR_MEMORY - GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY END PRINT_ERROR: diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index bbc5503d1c..a1ec9fca7a 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -603,8 +603,16 @@ MAIN: A$=A$+" forms to cond"+CHR$(34)+")) (cons 'cond (rest (rest xs)))))))" GOSUB RE:AY=R:GOSUB RELEASE + A$="(def! *gensym-counter* (atom 0))" + GOSUB RE:AY=R:GOSUB RELEASE + + A$="(def! gensym (fn* [] (symbol (str "+CHR$(34)+"G__"+CHR$(34) + A$=A$+" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))" + GOSUB RE:AY=R:GOSUB RELEASE + A$="(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs)" - A$=A$+" `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" + A$=A$+" (let* (condvar (gensym)) `(let* (~condvar ~(first xs))" + A$=A$+" (if ~condvar ~condvar (or ~@(rest xs)))))))))" GOSUB RE:AY=R:GOSUB RELEASE REM load the args file @@ -648,8 +656,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM P1=ZT: P2=-1: GOSUB PR_MEMORY - GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY END PRINT_ERROR: diff --git a/basic/types.in.bas b/basic/types.in.bas index 0ce329db88..af03454639 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -27,7 +27,7 @@ REM 14 -> Z% index of metdata object INIT_MEMORY: T=FRE(0) - Z1=2048+1024: REM Z% (boxed memory) size (4 bytes each) + Z1=2048+1024+256: REM Z% (boxed memory) size (4 bytes each) Z2=256: REM S$ (string memory) size (3 bytes each) Z3=256: REM X% (call stack) size (2 bytes each) Z4=64: REM Y% (release stack) size (4 bytes each) diff --git a/run_argv_test.sh b/run_argv_test.sh index 3de3efd4a1..0e5db2adbe 100755 --- a/run_argv_test.sh +++ b/run_argv_test.sh @@ -23,7 +23,7 @@ fi root="$(dirname $0)" -out="$( $@ $root/tests/print_argv.mal aaa bbb ccc )" +out="$( $@ $root/tests/print_argv.mal aaa bbb ccc | tr -d '\r' )" assert_equal '("aaa" "bbb" "ccc")' "$out" # Note: The 'make' implementation cannot handle arguments with spaces in them, @@ -32,7 +32,7 @@ assert_equal '("aaa" "bbb" "ccc")' "$out" # out="$( $@ $root/tests/print_argv.mal aaa 'bbb ccc' ddd )" # assert_equal '("aaa" "bbb ccc" "ddd")' "$out" -out="$( $@ $root/tests/print_argv.mal )" +out="$( $@ $root/tests/print_argv.mal | tr -d '\r' )" assert_equal '()' "$out" echo 'Passed all *ARGV* tests' diff --git a/tests/perf3.mal b/tests/perf3.mal index be66239f06..5ac54146e3 100644 --- a/tests/perf3.mal +++ b/tests/perf3.mal @@ -15,14 +15,4 @@ (swap! atm (fn* [a] (concat (rest a) (list (first a))))))) 10)) -;;(def! sumdown (fn* (N) (if (> N 0) (+ N (sumdown (- N 1))) 0))) -;;(def! fib (fn* (N) (if (= N 0) 1 (if (= N 1) 1 (+ (fib (- N 1)) (fib (- N 2))))))) -;; -;;(println "iters/s:" -;; (run-fn-for -;; (fn* [] -;; (do -;; (sumdown 10) -;; (fib 12))) -;; 3)) ;;(prn "Done: basic macros/atom test") From 4dab2092b0f1b7becaf8af97312a31d5c9784177 Mon Sep 17 00:00:00 2001 From: Dennis Felsing Date: Fri, 28 Oct 2016 12:02:21 +0200 Subject: [PATCH 0204/2308] Update Nim to 0.15.2, clean up compiler warnings --- README.md | 4 ++-- nim/Dockerfile | 6 +++--- nim/core.nim | 6 +++--- nim/step2_eval.nim | 2 +- nim/step3_env.nim | 2 +- nim/step5_tco.nim | 4 ++-- nim/step6_file.nim | 4 ++-- nim/step7_quote.nim | 4 ++-- nim/step8_macros.nim | 4 ++-- nim/step9_try.nim | 4 ++-- nim/stepA_mal.nim | 4 ++-- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index db985f4cb5..736d64860c 100644 --- a/README.md +++ b/README.md @@ -515,11 +515,11 @@ cd make make -f stepX_YYY.mk ``` -### Nim 0.11.0 +### Nim 0.15.2 *The Nim implementation was created by [Dennis Felsing (def-)](https://github.com/def-)* -Running the Nim implementation of mal requires Nim 0.11.0 or later. +The Nim implementation of mal has been tested with Nim 0.15.2. ``` cd nim diff --git a/nim/Dockerfile b/nim/Dockerfile index 2f0918817d..261776d1d5 100644 --- a/nim/Dockerfile +++ b/nim/Dockerfile @@ -26,10 +26,10 @@ RUN apt-get -y install g++ # Nim RUN apt-get -y install xz-utils -RUN cd /tmp && curl -O http://nim-lang.org/download/nim-0.12.0.tar.xz \ - && tar xvJf /tmp/nim-0.12.0.tar.xz && cd nim-0.12.0 \ +RUN cd /tmp && curl -O http://nim-lang.org/download/nim-0.15.2.tar.xz \ + && tar xvJf /tmp/nim-0.15.2.tar.xz && cd nim-0.15.2 \ && make && sh install.sh /usr/local/bin \ && cp bin/nim /usr/local/bin/ \ - && rm -r /tmp/nim-0.12.0 + && rm -r /tmp/nim-0.15.2 ENV HOME /mal diff --git a/nim/core.nim b/nim/core.nim index 52c4d8854c..777e243e76 100644 --- a/nim/core.nim +++ b/nim/core.nim @@ -125,7 +125,7 @@ proc seq(xs: varargs[MalType]): MalType = if len(xs[0].str) == 0: return nilObj result = list() for i in countup(0, len(xs[0].str) - 1): - result.list.add(str xs[0].str.copy(i,i)) + result.list.add(str xs[0].str.substr(i,i)) elif xs[0] == nilObj: result = nilObj else: @@ -157,11 +157,11 @@ proc swap_bang(xs: varargs[MalType]): MalType = proc time_ms(xs: varargs[MalType]): MalType = number int(epochTime() * 1000) -template wrapNumberFun(op: expr): expr = +template wrapNumberFun(op): untyped = fun proc(xs: varargs[MalType]): MalType = number op(xs[0].number, xs[1].number) -template wrapBoolFun(op: expr): expr = +template wrapBoolFun(op): untyped = fun proc(xs: varargs[MalType]): MalType = if op(xs[0].number, xs[1].number): trueObj else: falseObj diff --git a/nim/step2_eval.nim b/nim/step2_eval.nim index 8d8c27352d..51ef3e1e15 100644 --- a/nim/step2_eval.nim +++ b/nim/step2_eval.nim @@ -32,7 +32,7 @@ proc eval(ast: MalType, env: Table[string, MalType]): MalType = proc print(exp: MalType): string = exp.pr_str -template wrapNumberFun(op: expr): expr = +template wrapNumberFun(op): untyped = fun proc(xs: varargs[MalType]): MalType = number op(xs[0].number, xs[1].number) let repl_env = toTable({ diff --git a/nim/step3_env.nim b/nim/step3_env.nim index 43f85816b7..5487bf2096 100644 --- a/nim/step3_env.nim +++ b/nim/step3_env.nim @@ -48,7 +48,7 @@ proc eval(ast: MalType, env: var Env): MalType = proc print(exp: MalType): string = exp.pr_str -template wrapNumberFun(op: expr): expr = +template wrapNumberFun(op): untyped = fun proc(xs: varargs[MalType]): MalType = number op(xs[0].number, xs[1].number) var repl_env = initEnv() diff --git a/nim/step5_tco.nim b/nim/step5_tco.nim index 4b41afdae4..f192ac612c 100644 --- a/nim/step5_tco.nim +++ b/nim/step5_tco.nim @@ -50,7 +50,7 @@ proc eval(ast: MalType, env: var Env): MalType = let a1 = ast.list[1] a2 = ast.list[2] - var let_env = Env(env) + var let_env = env case a1.kind of List, Vector: for i in countup(0, a1.list.high, 2): @@ -62,7 +62,7 @@ proc eval(ast: MalType, env: var Env): MalType = of "do": let last = ast.list.high - let el = (list ast.list[1 .. Date: Fri, 28 Oct 2016 21:49:29 -0500 Subject: [PATCH 0205/2308] Basic: d64 disk image build rules. - Include .args.mal file and core.mal into the image. --- basic/Makefile | 47 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/basic/Makefile b/basic/Makefile index 915e24cc4d..e9c5353e30 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -1,20 +1,16 @@ BASICPP_OPTS = --combine-lines -step%.bas: step%.in.bas - ./basicpp.py $(BASICPP_OPTS) $< > $@ - -step%.prg: step%.bas - cat $< | tr "A-Z" "a-z" > $<.tmp - #cat $< | sed 's/["]\@["]\@!/\L&/g' > $<.tmp - petcat -text -w2 -o $@ $<.tmp - #rm $<.tmp - STEPS4_A = step4_if_fn_do.bas step5_tco.bas step6_file.bas \ step7_quote.bas step8_macros.bas step9_try.bas stepA_mal.bas STEPS3_A = step3_env.bas $(STEPS4_A) STEPS1_A = step1_read_print.bas step2_eval.bas $(STEPS3_A) STEPS0_A = step0_repl.bas $(STEPS1_A) +all: $(STEPS0_A) + +step%.bas: step%.in.bas + ./basicpp.py $(BASICPP_OPTS) $< > $@ + $(STEPS0_A): readline.in.bas $(STEPS1_A): debug.in.bas types.in.bas reader.in.bas printer.in.bas $(STEPS3_A): env.in.bas @@ -23,21 +19,42 @@ $(STEPS4_A): core.in.bas tests/%.bas: tests/%.in.bas ./basicpp.py $(BASICPP_OPTS) $< > $@ -tests/%.prg: tests/%.bas + +# C64 image rules + +step%.prg: step%.bas cat $< | tr "A-Z" "a-z" > $<.tmp - petcat -text -w2 -o $@ $<.tmp - rm $<.tmp + #cat $< | sed 's/["]\@["]\@!/\L&/g' > $<.tmp + petcat -w2 -nc -o $@ $<.tmp + #rm $<.tmp mal.prg: stepA_mal.prg cp $< $@ -SOURCES_LISP = env.in.bas core.in.bas stepA_mal.in.bas -SOURCES = readline.in.bas types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP) +.args.mal.prg: .args.mal + petcat -text -w2 -o $@ $< + +core.mal.prg: ../core.mal + petcat -text -w2 -o $@ $< + +mal.d64: mal.prg .args.mal.prg core.mal.prg + c1541 -format "mal,01" d64 $@ \ + -attach $@ \ + -write $< mal \ + -write .args.mal.prg .args.mal \ + -write core.mal.prg core.mal + + +# Clean and Stats rules .PHONY: clean stats clean: - rm -f $(STEPS0_A) $(subst .bas,.prg,$(STEPS0_A)) + rm -f $(STEPS0_A) *.d64 *.prg + + +SOURCES_LISP = env.in.bas core.in.bas stepA_mal.in.bas +SOURCES = readline.in.bas types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP) stats: $(SOURCES) @wc $^ From 7381834f553d9baaf35ad818e7543f23b6c285bb Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 28 Oct 2016 21:57:35 -0500 Subject: [PATCH 0206/2308] Basic: add Dockerfile - installs patch cbmbasic --- basic/Dockerfile | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 basic/Dockerfile diff --git a/basic/Dockerfile b/basic/Dockerfile new file mode 100644 index 0000000000..95200cfb5d --- /dev/null +++ b/basic/Dockerfile @@ -0,0 +1,34 @@ +FROM ubuntu:wily +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# cbmbasic +RUN apt-get install -y gcc unzip patch +RUN cd /tmp && \ + curl -L https://github.com/kanaka/cbmbasic/archive/master.zip -o cbmbasic.zip && \ + unzip cbmbasic.zip && \ + cd cbmbasic-master && \ + make && \ + cp cbmbasic /usr/bin/cbmbasic && \ + cd .. && \ + rm -r cbmbasic* + From 37d75dc6ddb2563f1c800b75c1bfc4266901a839 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 28 Oct 2016 22:02:59 -0500 Subject: [PATCH 0207/2308] Basic: pass required tests. Printer fix. Also, move dissoc and hashmap equality tests to optional/soft since they aren't actually needed for self-hosting. --- basic/core.in.bas | 2 - basic/printer.in.bas | 1 + tests/step9_try.mal | 89 ++++++++++++++++++++++---------------------- 3 files changed, 46 insertions(+), 46 deletions(-) diff --git a/basic/core.in.bas b/basic/core.in.bas index 1930fb8c39..6090b245c6 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -276,8 +276,6 @@ DO_FUNCTION: RETURN DO_SLURP: R$="" - REM OPEN 1,8,2,S$(Z%(AA,1))+",SEQ,R" - REM OPEN 1,8,2,S$(Z%(AA,1)) OPEN 1,8,0,S$(Z%(AA,1)) DO_SLURP_LOOP: A$="" diff --git a/basic/printer.in.bas b/basic/printer.in.bas index a686a6a1ed..812d363785 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -81,6 +81,7 @@ PR_STR: T1=AZ AZ=Z%(T1+1,0):GOSUB PR_STR_RECUR T7$="(fn* "+R$ + RR$="" AZ=Z%(T1,1):GOSUB PR_STR_RECUR R$=T7$+" "+R$+")" RETURN diff --git a/tests/step9_try.mal b/tests/step9_try.mal index 254aed07de..cd73932c61 100644 --- a/tests/step9_try.mal +++ b/tests/step9_try.mal @@ -223,24 +223,6 @@ (count (keys (assoc hm2 "b" 2 "c" 3))) ;=>3 -(def! hm3 (assoc hm2 "b" 2)) -(count (keys hm3)) -;=>2 -(count (vals hm3)) -;=>2 - -(dissoc hm3 "a") -;=>{"b" 2} - -(dissoc hm3 "a" "b") -;=>{} - -(dissoc hm3 "a" "b" "c") -;=>{} - -(count (keys hm3)) -;=>2 - ;; Testing keywords as hash-map keys (get {:abc 123} :abc) ;=>123 @@ -250,8 +232,6 @@ ;=>false (assoc {} :bcd 234) ;=>{:bcd 234} -(dissoc {:cde 345 :fgh 456} :cde) -;=>{:fgh 456} (keyword? (nth (keys {:abc 123 :def 456}) 0)) ;=>true ;;; TODO: support : in strings in make impl @@ -265,30 +245,6 @@ ;=>true (assoc {} :bcd nil) ;=>{:bcd nil} -(dissoc {:cde nil :fgh 456} :cde) -;=>{:fgh 456} - -;; Testing equality of hash-maps -(= {} {}) -;=>true -(= {:a 11 :b 22} (hash-map :b 22 :a 11)) -;=>true -(= {:a 11 :b [22 33]} (hash-map :b [22 33] :a 11)) -;=>true -(= {:a 11 :b {:c 33}} (hash-map :b {:c 33} :a 11)) -;=>true -(= {:a 11 :b 22} (hash-map :b 23 :a 11)) -;=>false -(= {:a 11 :b 22} (hash-map :a 11)) -;=>false -(= {:a [11 22]} {:a (list 11 22)}) -;=>true -(= {:a 11 :b 22} (list :a 11 :b 22)) -;=>false -(= {} []) -;=>false -(= [] {}) -;=>false ;; ;; Additional str and pr-str tests @@ -334,8 +290,53 @@ ;;;; "exc is:" ["data" "foo"] ;;;;=>7 ;;;;=>7 +;; ;; Testing throwing non-strings (try* (throw (list 1 2 3)) (catch* exc (do (prn "err:" exc) 7))) ; "err:" (1 2 3) ;=>7 +;; +;; Testing dissoc +(def! hm3 (assoc hm2 "b" 2)) +(count (keys hm3)) +;=>2 +(count (vals hm3)) +;=>2 +(dissoc hm3 "a") +;=>{"b" 2} +(dissoc hm3 "a" "b") +;=>{} +(dissoc hm3 "a" "b" "c") +;=>{} +(count (keys hm3)) +;=>2 + +(dissoc {:cde 345 :fgh 456} :cde) +;=>{:fgh 456} +(dissoc {:cde nil :fgh 456} :cde) +;=>{:fgh 456} + +;; +;; Testing equality of hash-maps +(= {} {}) +;=>true +(= {:a 11 :b 22} (hash-map :b 22 :a 11)) +;=>true +(= {:a 11 :b [22 33]} (hash-map :b [22 33] :a 11)) +;=>true +(= {:a 11 :b {:c 33}} (hash-map :b {:c 33} :a 11)) +;=>true +(= {:a 11 :b 22} (hash-map :b 23 :a 11)) +;=>false +(= {:a 11 :b 22} (hash-map :a 11)) +;=>false +(= {:a [11 22]} {:a (list 11 22)}) +;=>true +(= {:a 11 :b 22} (list :a 11 :b 22)) +;=>false +(= {} []) +;=>false +(= [] {}) +;=>false + From 4b9b1d250657512b679717e8b1e9eab363eb810c Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 28 Oct 2016 22:05:30 -0500 Subject: [PATCH 0208/2308] Basic: enable Travis testing. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 8780a9d018..ea0331dfab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ matrix: - {env: IMPL=ada, services: [docker]} - {env: IMPL=awk, services: [docker]} - {env: IMPL=bash, services: [docker]} + - {env: IMPL=basic, services: [docker]} - {env: IMPL=c, services: [docker]} - {env: IMPL=cpp, services: [docker]} - {env: IMPL=coffee, services: [docker]} From 5f0857261ff407ecc8f4b100b30f23b5754950ae Mon Sep 17 00:00:00 2001 From: Chris McCormick Date: Sun, 16 Oct 2016 19:11:30 +0800 Subject: [PATCH 0209/2308] New functions for converting between PHP and mal types. --- php/interop.php | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 php/interop.php diff --git a/php/interop.php b/php/interop.php new file mode 100644 index 0000000000..897905fa82 --- /dev/null +++ b/php/interop.php @@ -0,0 +1,50 @@ + $v) { + $ret[_to_php($k)] = _to_php($v); + } + return $ret; + } elseif (is_string($obj)) { + if (strpos($obj, chr(0x7f)) === 0) { + return ":".substr($obj,1); + } else { + return $obj; + } + } elseif (_symbol_Q($obj)) { + return ${$obj->value}; + } elseif (_atom_Q($obj)) { + return $obj->value; + } elseif (_function_Q($obj)) { + return $obj->func; + } else { + return $obj; + } +} + +function _to_mal($obj) { + switch (gettype($obj)) { + case "object": + return _to_mal(get_object_vars($obj)); + case "array": + $obj_conv = array(); + foreach ($obj as $k => $v) { + $obj_conv[_to_mal($k)] = _to_mal($v); + } + if ($obj_conv !== array_values($obj_conv)) { + $new_obj = _hash_map(); + $new_obj->exchangeArray($obj_conv); + return $new_obj; + } else { + return call_user_func_array('_list', $obj_conv); + } + default: + return $obj; + } +} + +?> From f39b8db70287d8fdcd20d343062f2e96da0b061d Mon Sep 17 00:00:00 2001 From: Chris McCormick Date: Sun, 16 Oct 2016 19:12:11 +0800 Subject: [PATCH 0210/2308] Allow printing of more PHP types. In case return type from interop is a complex native PHP type. --- php/printer.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/php/printer.php b/php/printer.php index d4d53e0664..c82cbf316f 100644 --- a/php/printer.php +++ b/php/printer.php @@ -31,6 +31,8 @@ function _pr_str($obj, $print_readably=True) { } else { return $obj; } + } elseif (is_double($obj)) { + return $obj; } elseif (is_integer($obj)) { return $obj; } elseif ($obj === NULL) { @@ -47,6 +49,10 @@ function _pr_str($obj, $print_readably=True) { return "(fn* [...] ...)"; } elseif (is_callable($obj)) { // only step4 and below return "#"; + } elseif (is_object($obj)) { + return "#"; + } elseif (is_array($obj)) { + return "#"; } else { throw new Exception("_pr_str unknown type: " . gettype($obj)); } From 42f9de6a8a8d0342defc3096520cc3894cf82a8e Mon Sep 17 00:00:00 2001 From: Chris McCormick Date: Sun, 16 Oct 2016 19:14:53 +0800 Subject: [PATCH 0211/2308] Tighter PHP interop using '$' and '!' fns. ($ _SERVER) returns a mal hash-map holding the contents of the $_SERVER superglobal from PHP. (! file_get_contents "https://github.com") returns a string containing the github homepage. file_get_contents is a native PHP function. --- php/Makefile | 2 +- php/stepA_mal.php | 36 ++++++++++++++++++++++++------------ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/php/Makefile b/php/Makefile index 35f585458e..981b6fa7e7 100644 --- a/php/Makefile +++ b/php/Makefile @@ -1,7 +1,7 @@ TESTS = -SOURCES_BASE = readline.php types.php reader.php printer.php +SOURCES_BASE = readline.php types.php reader.php printer.php interop.php SOURCES_LISP = env.php core.php stepA_mal.php SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) diff --git a/php/stepA_mal.php b/php/stepA_mal.php index 3292645b97..2b80483b46 100644 --- a/php/stepA_mal.php +++ b/php/stepA_mal.php @@ -4,6 +4,7 @@ require_once 'types.php'; require_once 'reader.php'; require_once 'printer.php'; +require_once 'interop.php'; require_once 'env.php'; require_once 'core.php'; @@ -71,6 +72,7 @@ function eval_ast($ast, $env) { } function MAL_EVAL($ast, $env) { + $_SUPERGLOBALS = ["_SERVER", "_GET", "_POST", "_FILES", "_REQUEST", "_SESSION", "_ENV", "_COOKIE"]; while (true) { #echo "MAL_EVAL: " . _pr_str($ast) . "\n"; @@ -115,18 +117,7 @@ function MAL_EVAL($ast, $env) { return macroexpand($ast[1], $env); case "php*": $res = eval($ast[1]); - switch (gettype($res)) { - case "array": - if ($res !== array_values($res)) { - $new_res = _hash_map(); - $new_res->exchangeArray($res); - return $new_res; - } else { - return call_user_func_array('_list', $res); - } - default: - return $res; - } + return _to_mal($res); case "try*": $a1 = $ast[1]; $a2 = $ast[2]; @@ -161,6 +152,27 @@ function MAL_EVAL($ast, $env) { case "fn*": return _function('MAL_EVAL', 'native', $ast[2], $env, $ast[1]); + case "$": + $var = MAL_EVAL($ast[1], $env); + if (_symbol_Q($var)) { + $varname = $var->value; + } elseif (gettype($var) === "string") { + $varname = $var; + } else { + throw new Exception("$ arg unknown type: " . gettype($var)); + } + if (in_array($varname, $_SUPERGLOBALS)) { + $val = $GLOBALS[$varname]; + } else { + $val = ${$varname}; + } + return _to_mal($val); + case "!": + $fn = $ast[1]->value; + $el = eval_ast($ast->slice(2), $env); + $args = _to_php($el); + $res = call_user_func_array($fn, $args); + return _to_mal($res); default: $el = eval_ast($ast, $env); $f = $el[0]; From 07eb8b944ae8b4cd3e0d2bca627b601243aa80ca Mon Sep 17 00:00:00 2001 From: Chris McCormick Date: Sat, 29 Oct 2016 12:24:00 +0800 Subject: [PATCH 0212/2308] Tests for new PHP interop functions. --- php/tests/stepA_mal.mal | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/php/tests/stepA_mal.mal b/php/tests/stepA_mal.mal index 15f8a9488d..1dd58ff23a 100644 --- a/php/tests/stepA_mal.mal +++ b/php/tests/stepA_mal.mal @@ -23,3 +23,13 @@ (php* "global $f; $f = function($v) { return 1+$v; };") (php* "global $f; return array_map($f, array(1,2,3));") ;=>(2 3 4) + +;; testing native function calling + +(! date "Y-m-d" 0) +;=>"1970-01-01" + +;; testing superglobal variable access + +(get ($ "_SERVER") "PHP_SELF") +;=>"../php/stepA_mal.php" From 29ebdb9c48cee67815e4325535f835490bbeffe0 Mon Sep 17 00:00:00 2001 From: Chris McCormick Date: Sat, 29 Oct 2016 12:32:26 +0800 Subject: [PATCH 0213/2308] Added interop notes to README. --- php/README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/php/README.md b/php/README.md index 40dad01700..1333636620 100644 --- a/php/README.md +++ b/php/README.md @@ -13,3 +13,26 @@ Here's an example using local dev: Then browse to http://localhost:8000/myscript.php and you should see "Hello world!" in your browser as `myscript.mal` is run. You can do the same thing on live PHP web hosting by copying `mal.php` up and creating a symlink for each `.mal` file you want to be web-executable. + +### PHP interop ### + +In [stepA_mal.mal](./tests/stepA_mal.mal) you can find some examples of PHP interop. + +Eval PHP code: + + (php* "return 7;") + 7 + + (php* "return array(7,8,9);") + (7 8 9) + +Native function call: + + (! date "Y-m-d" 0) + 1970-01-01 + +Accessing PHP "superglobal" variables: + + (get ($ "_SERVER") "PHP_SELF") + ./mal + From 01975886275270353bcd57197d870d6ebc137cdf Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sun, 30 Oct 2016 19:15:24 -0500 Subject: [PATCH 0214/2308] Basic: add QBasic support. make MODE=qbasic stepA_mal.bas qb64 stepA_mal.bas --- basic/Makefile | 5 ++-- basic/basicpp.py | 49 +++++++++++++++++++++++++---------- basic/core.in.bas | 45 ++++++++++++++++++-------------- basic/debug.in.bas | 16 ++++++------ basic/printer.in.bas | 2 +- basic/reader.in.bas | 13 +++++++--- basic/readline.in.bas | 14 +++++++--- basic/step0_repl.in.bas | 2 +- basic/step1_read_print.in.bas | 2 +- basic/step2_eval.in.bas | 12 ++++----- basic/step3_env.in.bas | 16 ++++++------ basic/step4_if_fn_do.in.bas | 19 +++++++------- basic/step5_tco.in.bas | 19 +++++++------- basic/step6_file.in.bas | 19 +++++++------- basic/step7_quote.in.bas | 27 +++++++++---------- basic/step8_macros.in.bas | 33 +++++++++++------------ basic/step9_try.in.bas | 33 +++++++++++------------ basic/stepA_mal.in.bas | 38 ++++++++++++++------------- basic/types.in.bas | 19 +++++++------- basic/variables.txt | 1 + 20 files changed, 217 insertions(+), 167 deletions(-) diff --git a/basic/Makefile b/basic/Makefile index e9c5353e30..6d0a3c00dc 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -1,4 +1,5 @@ -BASICPP_OPTS = --combine-lines +MODE = cbm +BASICPP_OPTS = --mode $(MODE) STEPS4_A = step4_if_fn_do.bas step5_tco.bas step6_file.bas \ step7_quote.bas step8_macros.bas step9_try.bas stepA_mal.bas @@ -20,7 +21,7 @@ tests/%.bas: tests/%.in.bas ./basicpp.py $(BASICPP_OPTS) $< > $@ -# C64 image rules +# CBM/C64 image rules step%.prg: step%.bas cat $< | tr "A-Z" "a-z" > $<.tmp diff --git a/basic/basicpp.py b/basic/basicpp.py index 174e005d32..1e0afbe3c1 100755 --- a/basic/basicpp.py +++ b/basic/basicpp.py @@ -10,8 +10,9 @@ def debug(*args, **kwargs): def parse_args(): parser = argparse.ArgumentParser(description='Preprocess Basic code.') - parser.add_argument('infile', type=str, - help='the Basic file to preprocess') + parser.add_argument('infiles', type=str, nargs='+', + help='the Basic files to preprocess') + parser.add_argument('--mode', choices=["cbm", "qbasic"], default="cbm") parser.add_argument('--keep-rems', action='store_true', default=False, help='The type of REMs to keep (0 (none) -> 4 (all)') parser.add_argument('--keep-blank-lines', action='store_true', default=False, @@ -20,12 +21,17 @@ def parse_args(): help='Keep line identing') parser.add_argument('--skip-misc-fixups', action='store_true', default=False, help='Skip miscellaneous fixup/shrink fixups') - parser.add_argument('--combine-lines', action='store_true', default=False, - help='Combine lines using the ":" separator') + parser.add_argument('--skip-combine-lines', action='store_true', default=False, + help='Do not combine lines using the ":" separator') args = parser.parse_args() - if args.combine_lines and args.keep_rems: - parser.error("--combine-lines and --keep-rems are mutually exclusive") + if args.keep_rems and not args.skip_combine_lines: + debug("Option --keep-rems implies --skip-combine-lines ") + args.skip_combine_lines = True + + if args.mode == 'qbasic' and not args.skip_misc_fixups: + debug("Mode 'qbasic' implies --skip-misc-fixups") + args.skip_misc_fixups = True return args @@ -48,6 +54,17 @@ def resolve_includes(orig_lines, keep_rems=0): lines.append(line) return lines +def resolve_mode(orig_lines, mode): + lines = [] + for line in orig_lines: + m = re.match(r"^ *#([^ ]*) (.*)$", line) + if m: + if m.group(1) == mode: + lines.append(m.group(2)) + continue + lines.append(line) + return lines + def drop_blank_lines(orig_lines): lines = [] for line in orig_lines: @@ -80,6 +97,7 @@ def misc_fixups(orig_lines): text = re.sub(r"\bTHEN GOTO\b", "THEN", text) text = re.sub(r"\bPRINT \"", "PRINT\"", text) text = re.sub(r"\bIF ", "IF", text) + text = re.sub(r"AND ([0-9])", r"AND\g<1>", text) return text.split("\n") def finalize(lines, args): @@ -184,7 +202,7 @@ def update_labels_lines(text, a, b): lines = text.split("\n") # combine lines - if args.combine_lines: + if not args.skip_combine_lines: renumber = {} src_lines = lines lines = [] @@ -244,25 +262,29 @@ def renum(line): if __name__ == '__main__': args = parse_args() - debug("Preprocessing basic file '"+args.infile+"'") + debug("Preprocessing basic files: "+", ".join(args.infiles)) # read in lines - lines = [l.rstrip() for l in open(args.infile).readlines()] - debug("Number of original lines: %s" % len(lines)) + lines = [l.rstrip() for f in args.infiles + for l in open(f).readlines()] + debug("Original lines: %s" % len(lines)) # pull in include files lines = resolve_includes(lines, keep_rems=args.keep_rems) - debug("Number of lines after includes: %s" % len(lines)) + debug("Lines after includes: %s" % len(lines)) + + lines = resolve_mode(lines, mode=args.mode) + debug("Lines after resolving mode specific lines: %s" % len(lines)) # drop blank lines if not args.keep_blank_lines: lines = drop_blank_lines(lines) - debug("Number of lines after dropping blank lines: %s" % len(lines)) + debug("Lines after dropping blank lines: %s" % len(lines)) # keep/drop REMs if not args.keep_rems: lines = drop_rems(lines) - debug("Number of lines after dropping REMs: %s" % len(lines)) + debug("Lines after dropping REMs: %s" % len(lines)) # keep/remove the indenting if not args.keep_indent: @@ -274,5 +296,6 @@ def renum(line): # number lines, drop/keep labels, combine lines lines = finalize(lines, args) + debug("Lines after finalizing: %s" % len(lines)) print("\n".join(lines)) diff --git a/basic/core.in.bas b/basic/core.in.bas index 6090b245c6..a0077e2466 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -7,11 +7,11 @@ REM - restores E REM - call using GOTO and with return label/address on the stack SUB APPLY REM if metadata, get the actual object - IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) - IF (Z%(F,0)AND31)=9 THEN GOTO APPLY_FUNCTION - IF (Z%(F,0)AND31)=10 THEN GOTO APPLY_MAL_FUNCTION - IF (Z%(F,0)AND31)=11 THEN GOTO APPLY_MAL_FUNCTION + IF (Z%(F,0)AND 31)=9 THEN GOTO APPLY_FUNCTION + IF (Z%(F,0)AND 31)=10 THEN GOTO APPLY_MAL_FUNCTION + IF (Z%(F,0)AND 31)=11 THEN GOTO APPLY_MAL_FUNCTION APPLY_FUNCTION: REM regular function @@ -54,7 +54,7 @@ SUB DO_TCO_FUNCTION A=Z%(AR+1,1) REM no intermediate args, but not a list, so convert it first - IF R4<=1 AND (Z%(A,0)AND31)<>6 THEN T=6:GOSUB FORCE_SEQ_TYPE:GOTO DO_APPLY_2 + IF R4<=1 AND (Z%(A,0)AND 31)<>6 THEN T=6:GOSUB FORCE_SEQ_TYPE:GOTO DO_APPLY_2 REM no intermediate args, just call APPLY directly IF R4<=1 THEN GOTO DO_APPLY_1 @@ -183,7 +183,7 @@ DO_FUNCTION: REM Switch on the function number IF FF>59 THEN ER=-1:ER$="unknown function"+STR$(FF):RETURN - ON FF/10+1 GOTO DO_1_9,DO_10_19,DO_20_29,DO_30_39,DO_40_49,DO_50_59 + ON INT(FF/10)+1 GOTO DO_1_9,DO_10_19,DO_20_29,DO_30_39,DO_40_49,DO_50_59 DO_1_9: ON FF GOTO DO_EQUAL_Q,DO_THROW,DO_NIL_Q,DO_TRUE_Q,DO_FALSE_Q,DO_STRING_Q,DO_SYMBOL,DO_SYMBOL_Q,DO_KEYWORD @@ -222,7 +222,7 @@ DO_FUNCTION: RETURN DO_STRING_Q: R=1 - IF (Z%(AA,0)AND31)<>4 THEN RETURN + IF (Z%(AA,0)AND 31)<>4 THEN RETURN IF MID$(S$(Z%(AA,1)),1,1)=CHR$(127) THEN RETURN R=2 RETURN @@ -231,7 +231,7 @@ DO_FUNCTION: RETURN DO_SYMBOL_Q: R=1 - IF (Z%(AA,0)AND31)=5 THEN R=2 + IF (Z%(AA,0)AND 31)=5 THEN R=2 RETURN DO_KEYWORD: A=Z%(AA,1) @@ -242,7 +242,7 @@ DO_FUNCTION: RETURN DO_KEYWORD_Q: R=1 - IF (Z%(AA,0)AND31)<>4 THEN RETURN + IF (Z%(AA,0)AND 31)<>4 THEN RETURN IF MID$(S$(Z%(AA,1)),1,1)<>CHR$(127) THEN RETURN R=2 RETURN @@ -271,19 +271,24 @@ DO_FUNCTION: RETURN DO_READLINE: A$=S$(Z%(AA,1)):GOSUB READLINE - IF EOF=1 THEN EOF=0:R=0:RETURN + IF EZ=1 THEN EZ=0:R=0:RETURN AS$=R$:T=4:GOSUB STRING RETURN DO_SLURP: R$="" - OPEN 1,8,0,S$(Z%(AA,1)) + #cbm OPEN 1,8,0,S$(Z%(AA,1)) + #qbasic A$=S$(Z%(AA,1)) + #qbasic IF NOT _FILEEXISTS(A$) THEN ER=-1:ER$="File not found":RETURN + #qbasic OPEN A$ FOR INPUT AS #1 DO_SLURP_LOOP: A$="" - GET#1,A$ + #cbm GET#1,A$ + #qbasic A$=INPUT$(1,1) + #qbasic IF EOF(1) THEN RS=1:A$=A$+CHR$(10)+")":GOTO DO_SLURP_DONE IF ASC(A$)=10 THEN R$=R$+CHR$(13) IF (ASC(A$)<>10) AND (A$<>"") THEN R$=R$+A$ - IF (ST AND 64) THEN GOTO DO_SLURP_DONE - IF (ST AND 255) THEN ER=-1:ER$="File read error "+STR$(ST):RETURN + #cbm IF (ST AND 64) THEN GOTO DO_SLURP_DONE + #cbm IF (ST AND 255) THEN ER=-1:ER$="File read error "+STR$(ST):RETURN GOTO DO_SLURP_LOOP DO_SLURP_DONE: CLOSE 1 @@ -336,14 +341,14 @@ DO_FUNCTION: RETURN DO_VECTOR_Q: R=1 - IF (Z%(AA,0)AND31)=7 THEN R=2 + IF (Z%(AA,0)AND 31)=7 THEN R=2 RETURN DO_HASH_MAP: A=AR:T=8:GOSUB FORCE_SEQ_TYPE RETURN DO_MAP_Q: R=1 - IF (Z%(AA,0)AND31)=8 THEN R=2 + IF (Z%(AA,0)AND 31)=8 THEN R=2 RETURN DO_ASSOC: H=AA @@ -397,7 +402,7 @@ DO_FUNCTION: DO_SEQUENTIAL_Q: R=1 - IF (Z%(AA,0)AND31)=6 OR (Z%(AA,0)AND31)=7 THEN R=2 + IF (Z%(AA,0)AND 31)=6 OR (Z%(AA,0)AND 31)=7 THEN R=2 RETURN DO_CONS: T=6:L=AB:N=AA:GOSUB ALLOC @@ -481,13 +486,13 @@ DO_FUNCTION: RETURN DO_WITH_META: - T=Z%(AA,0)AND31 + T=Z%(AA,0)AND 31 REM remove existing metadata first IF T>=16 THEN AA=Z%(AA,1):GOTO DO_WITH_META T=T+16:L=AA:N=AB:GOSUB ALLOC RETURN DO_META: - IF (Z%(AA,0)AND31)<16 THEN R=0:RETURN + IF (Z%(AA,0)AND 31)<16 THEN R=0:RETURN R=Z%(AA+1,1) Z%(R,0)=Z%(R,0)+32 RETURN @@ -496,7 +501,7 @@ DO_FUNCTION: RETURN DO_ATOM_Q: R=1 - IF (Z%(AA,0)AND31)=12 THEN R=2 + IF (Z%(AA,0)AND 31)=12 THEN R=2 RETURN DO_DEREF: R=Z%(AA,1):GOSUB DEREF_R diff --git a/basic/debug.in.bas b/basic/debug.in.bas index 7f879e48e4..842bc5dbed 100644 --- a/basic/debug.in.bas +++ b/basic/debug.in.bas @@ -5,7 +5,7 @@ CHECK_FREE_LIST: P2=0 CHECK_FREE_LIST_LOOP: IF P1>=ZI THEN GOTO CHECK_FREE_LIST_DONE - IF (Z%(P1,0)AND31)<>15 THEN P2=-1:GOTO CHECK_FREE_LIST_DONE + IF (Z%(P1,0)AND 31)<>15 THEN P2=-1:GOTO CHECK_FREE_LIST_DONE P2=P2+(Z%(P1,0)AND-32)/32 P1=Z%(P1,1) GOTO CHECK_FREE_LIST_LOOP @@ -15,7 +15,7 @@ CHECK_FREE_LIST: PR_MEMORY_SUMMARY: PRINT - PRINT "Free (FRE) :"+STR$(FRE(0)) + #cbm PRINT "Free (FRE) :"+STR$(FRE(0)) PRINT "Values (Z%) :"+STR$(ZI-1)+" /"+STR$(Z1) GOSUB CHECK_FREE_LIST: REM get count in P2 PRINT " used:"+STR$(ZI-1-P2)+", freed:"+STR$(P2); @@ -35,14 +35,14 @@ REM I=P1 REM PR_MEMORY_VALUE_LOOP: REM IF I>P2 THEN GOTO PR_MEMORY_AFTER_VALUES REM PRINT " "+STR$(I); -REM IF (Z%(I,0)AND31)=15 THEN GOTO PR_MEMORY_FREE +REM IF (Z%(I,0)AND 31)=15 THEN GOTO PR_MEMORY_FREE REM PRINT ": ref cnt: "+STR$((Z%(I,0)AND-32)/32); -REM PRINT ", type: "+STR$(Z%(I,0)AND31)+", value: "+STR$(Z%(I,1)); -REM IF (Z%(I,0)AND31)=4 THEN PRINT " '"+S$(Z%(I,1))+"'"; -REM IF (Z%(I,0)AND31)=5 THEN PRINT " "+S$(Z%(I,1))+""; +REM PRINT ", type: "+STR$(Z%(I,0)AND 31)+", value: "+STR$(Z%(I,1)); +REM IF (Z%(I,0)AND 31)=4 THEN PRINT " '"+S$(Z%(I,1))+"'"; +REM IF (Z%(I,0)AND 31)=5 THEN PRINT " "+S$(Z%(I,1))+""; REM PRINT REM I=I+1 -REM IF (Z%(I-1,0)AND31)<>10 THEN GOTO PR_MEMORY_VALUE_LOOP +REM IF (Z%(I-1,0)AND 31)<>10 THEN GOTO PR_MEMORY_VALUE_LOOP REM PRINT " "+STR$(I)+": "; REM PRINT "params: "+STR$(Z%(I+1,0))+", env:"+STR$(Z%(I+1,1)) REM I=I+1 @@ -80,7 +80,7 @@ REM PR_OBJ_LOOP: REM IF RD=0 THEN RETURN REM I=X%(X):RD=RD-1:X=X-1 REM -REM P2=Z%(I,0)AND31 +REM P2=Z%(I,0)AND 31 REM PRINT " "+STR$(I); REM PRINT ": ref cnt: "+STR$((Z%(I,0)AND-32)/32); REM PRINT ", type: "+STR$(P2)+", value: "+STR$(Z%(I,1)); diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 812d363785..ca44658319 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -2,7 +2,7 @@ REM PR_STR(AZ, PR) -> R$ PR_STR: RR$="" PR_STR_RECUR: - T=Z%(AZ,0)AND31 + T=Z%(AZ,0)AND 31 REM PRINT "AZ: "+STR$(AZ)+", T: "+STR$(T)+", V: "+STR$(Z%(AZ,1)) IF T=0 THEN R$="nil":RETURN REM if metadata, then get actual object diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 5b50aeeac8..b927015425 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -33,9 +33,12 @@ READ_FILE_CHUNK: IF RI>1 THEN A$=MID$(A$,RI,LEN(A$)-RI+1):RI=1:RJ=RJ-RI+1 READ_FILE_CHUNK_LOOP: IF LEN(A$)>RJ+9 THEN RETURN - GET#2,C$:A$=A$+C$ - IF (ST AND 64) THEN RS=1:A$=A$+CHR$(10)+")":RETURN - IF (ST AND 255) THEN RS=1:ER=-1:ER$="File read error "+STR$(ST):RETURN + #cbm GET#2,C$ + #qbasic C$=INPUT$(1,2) + #qbasic IF EOF(2) THEN RS=1:A$=A$+CHR$(10)+")":RETURN + A$=A$+C$ + #cbm IF (ST AND 64) THEN RS=1:A$=A$+CHR$(10)+")":RETURN + #cbm IF (ST AND 255) THEN RS=1:ER=-1:ER$="File read error "+STR$(ST):RETURN GOTO READ_FILE_CHUNK_LOOP SKIP_SPACES: @@ -241,7 +244,9 @@ READ_FILE: RF=1: REM reading from file RS=0: REM file read state (1: EOF) SD=0: REM sequence read depth - OPEN 2,8,0,A$ + #cbm OPEN 2,8,0,A$ + #qbasic IF NOT _FILEEXISTS(A$) THEN ER=-1:ER$="File not found":RETURN + #qbasic OPEN A$ FOR INPUT AS #2 REM READ_FILE_CHUNK adds terminating ")" A$="(do ":GOSUB READ_FORM CLOSE 2 diff --git a/basic/readline.in.bas b/basic/readline.in.bas index 945684dffa..d525d53c07 100644 --- a/basic/readline.in.bas +++ b/basic/readline.in.bas @@ -1,14 +1,17 @@ REM READLINE(A$) -> R$ READLINE: - EOF=0 + EZ=0 PROMPT$=A$ PRINT PROMPT$; CH$="":LI$="":CH=0 READCH: - GET CH$:IF CH$="" THEN GOTO READCH + #cbm GET CH$ + #qbasic CH$=INKEY$ + IF CH$="" THEN GOTO READCH CH=ASC(CH$) REM PRINT CH - IF CH=4 OR CH=0 THEN EOF=1:GOTO RL_DONE: REM EOF + #qbasic IF ASC(CH$)=8 THEN CH=20:CH$=CHR$(20) + IF CH=4 OR CH=0 THEN EZ=1:GOTO RL_DONE: REM EOF IF CH=127 OR CH=20 THEN GOSUB RL_BACKSPACE IF CH=127 OR CH=20 THEN GOTO READCH IF (CH<32 OR CH>127) AND CH<>13 THEN GOTO READCH @@ -23,5 +26,8 @@ READLINE: RL_BACKSPACE: IF LEN(LI$)=0 THEN RETURN LI$=LEFT$(LI$, LEN(LI$)-1) - PRINT CHR$(157)+" "+CHR$(157); + #cbm PRINT CHR$(157)+" "+CHR$(157); + #qbasic LOCATE ,POS(0)-1 + #qbasic PRINT " "; + #qbasic LOCATE ,POS(0)-1 RETURN diff --git a/basic/step0_repl.in.bas b/basic/step0_repl.in.bas index 5d4cd44f6e..fd1a8e0fd1 100755 --- a/basic/step0_repl.in.bas +++ b/basic/step0_repl.in.bas @@ -28,7 +28,7 @@ REM MAIN program MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser - IF EOF=1 THEN GOTO QUIT + IF EZ=1 THEN GOTO QUIT A$=R$:CALL REP: REM call REP diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index a243455afd..74a364f219 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -47,7 +47,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser - IF EOF=1 THEN GOTO QUIT + IF EZ=1 THEN GOTO QUIT A$=R$:CALL REP: REM call REP diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index 893fcc9509..1a1111a163 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -23,7 +23,7 @@ SUB EVAL_AST GOSUB DEREF_A - T=Z%(A,0)AND31 + T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ @@ -62,7 +62,7 @@ SUB EVAL_AST IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -144,7 +144,7 @@ SUB EVAL AR=Z%(R,1): REM rest R=F:GOSUB DEREF_R:F=R - IF (Z%(F,0)AND31)<>9 THEN ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN + IF (Z%(F,0)AND 31)<>9 THEN ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION AY=R3:GOSUB RELEASE GOTO EVAL_RETURN @@ -153,9 +153,9 @@ SUB EVAL LV=LV-1: REM track basic return stack level - REM trigger GC - TA=FRE(0) + #cbm TA=FRE(0) + #qbasic TA=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -255,7 +255,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser - IF EOF=1 THEN GOTO QUIT + IF EZ=1 THEN GOTO QUIT A$=R$:CALL REP: REM call REP diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index 828fe0a029..d2e3cae0eb 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -24,7 +24,7 @@ SUB EVAL_AST GOSUB DEREF_A - T=Z%(A,0)AND31 + T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ @@ -61,7 +61,7 @@ SUB EVAL_AST IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -140,8 +140,8 @@ SUB EVAL R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0,0)AND31)<>5 THEN A$="" - IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0,0)AND 31)<>5 THEN A$="" + IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -212,7 +212,7 @@ SUB EVAL AR=Z%(R,1): REM rest R=F:GOSUB DEREF_R:F=R - IF (Z%(F,0)AND31)<>9 THEN ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN + IF (Z%(F,0)AND 31)<>9 THEN ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION AY=R3:GOSUB RELEASE GOTO EVAL_RETURN @@ -226,9 +226,9 @@ SUB EVAL LV=LV-1: REM track basic return stack level - REM trigger GC - TA=FRE(0) + #cbm TA=FRE(0) + #qbasic TA=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -329,7 +329,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser - IF EOF=1 THEN GOTO QUIT + IF EZ=1 THEN GOTO QUIT A$=R$:CALL REP: REM call REP diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index fec1508177..f25cba2d3e 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -23,7 +23,7 @@ SUB EVAL_AST GOSUB DEREF_A - T=Z%(A,0)AND31 + T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ @@ -60,7 +60,7 @@ SUB EVAL_AST IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -141,8 +141,8 @@ SUB EVAL R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0,0)AND31)<>5 THEN A$="" - IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0,0)AND 31)<>5 THEN A$="" + IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -258,10 +258,10 @@ SUB EVAL R=F:GOSUB DEREF_R:F=R REM if metadata, get the actual object - IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) - IF (Z%(F,0)AND31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F,0)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller R=X%(X):X=X-1 @@ -314,7 +314,8 @@ SUB EVAL GOSUB RELEASE_PEND REM trigger GC - TA=FRE(0) + #cbm TA=FRE(0) + #qbasic TA=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -384,7 +385,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser - IF EOF=1 THEN GOTO QUIT + IF EZ=1 THEN GOTO QUIT A$=R$:CALL REP: REM call REP diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index 4083265392..e4d4dfd6e2 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -23,7 +23,7 @@ SUB EVAL_AST GOSUB DEREF_A - T=Z%(A,0)AND31 + T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ @@ -63,7 +63,7 @@ SUB EVAL_AST IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -144,8 +144,8 @@ SUB EVAL R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0,0)AND31)<>5 THEN A$="" - IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0,0)AND 31)<>5 THEN A$="" + IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -276,10 +276,10 @@ SUB EVAL R=F:GOSUB DEREF_R:F=R REM if metadata, get the actual object - IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) - IF (Z%(F,0)AND31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F,0)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller R=X%(X):X=X-1 @@ -332,7 +332,8 @@ SUB EVAL GOSUB RELEASE_PEND REM trigger GC - TA=FRE(0) + #cbm TA=FRE(0) + #qbasic TA=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -402,7 +403,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser - IF EOF=1 THEN GOTO QUIT + IF EZ=1 THEN GOTO QUIT A$=R$:CALL REP: REM call REP diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index 95cb2fd5e5..a5eabb6e99 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -23,7 +23,7 @@ SUB EVAL_AST GOSUB DEREF_A - T=Z%(A,0)AND31 + T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ @@ -63,7 +63,7 @@ SUB EVAL_AST IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -144,8 +144,8 @@ SUB EVAL R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0,0)AND31)<>5 THEN A$="" - IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0,0)AND 31)<>5 THEN A$="" + IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -276,10 +276,10 @@ SUB EVAL R=F:GOSUB DEREF_R:F=R REM if metadata, get the actual object - IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) - IF (Z%(F,0)AND31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F,0)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller R=X%(X):X=X-1 @@ -332,7 +332,8 @@ SUB EVAL GOSUB RELEASE_PEND REM trigger GC - TA=FRE(0) + #cbm TA=FRE(0) + #qbasic TA=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -429,7 +430,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser - IF EOF=1 THEN GOTO QUIT + IF EZ=1 THEN GOTO QUIT A$=R$:CALL REP: REM call REP diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index 208256b414..fb749e552f 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -17,7 +17,7 @@ MAL_READ: REM QUASIQUOTE(A) -> R SUB QUASIQUOTE REM pair? - IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_QUOTE + IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_QUOTE IF (Z%(A,1)=0) THEN GOTO QQ_QUOTE GOTO QQ_UNQUOTE @@ -31,7 +31,7 @@ SUB QUASIQUOTE QQ_UNQUOTE: R=A+1:GOSUB DEREF_R - IF (Z%(R,0)AND31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF (Z%(R,0)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] R=Z%(A,1)+1:GOSUB DEREF_R @@ -52,11 +52,11 @@ SUB QUASIQUOTE A=A+1:GOSUB DEREF_A REM pair? - IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_DEFAULT + IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_DEFAULT IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT B=A+1:GOSUB DEREF_B - IF (Z%(B,0)AND31)<>5 THEN GOTO QQ_DEFAULT + IF (Z%(B,0)AND 31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] @@ -98,7 +98,7 @@ SUB EVAL_AST GOSUB DEREF_A - T=Z%(A,0)AND31 + T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ @@ -138,7 +138,7 @@ SUB EVAL_AST IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -219,8 +219,8 @@ SUB EVAL R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0,0)AND31)<>5 THEN A$="" - IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0,0)AND 31)<>5 THEN A$="" + IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -367,10 +367,10 @@ SUB EVAL R=F:GOSUB DEREF_R:F=R REM if metadata, get the actual object - IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) - IF (Z%(F,0)AND31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F,0)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller R=X%(X):X=X-1 @@ -423,7 +423,8 @@ SUB EVAL GOSUB RELEASE_PEND REM trigger GC - TA=FRE(0) + #cbm TA=FRE(0) + #qbasic TA=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -520,7 +521,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser - IF EOF=1 THEN GOTO QUIT + IF EZ=1 THEN GOTO QUIT A$=R$:CALL REP: REM call REP diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index e698efa497..c59e23830a 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -19,7 +19,7 @@ MAL_READ: REM QUASIQUOTE(A) -> R SUB QUASIQUOTE REM pair? - IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_QUOTE + IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_QUOTE IF (Z%(A,1)=0) THEN GOTO QQ_QUOTE GOTO QQ_UNQUOTE @@ -33,7 +33,7 @@ SUB QUASIQUOTE QQ_UNQUOTE: R=A+1:GOSUB DEREF_R - IF (Z%(R,0)AND31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF (Z%(R,0)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] R=Z%(A,1)+1:GOSUB DEREF_R @@ -54,11 +54,11 @@ SUB QUASIQUOTE A=A+1:GOSUB DEREF_A REM pair? - IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_DEFAULT + IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_DEFAULT IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT B=A+1:GOSUB DEREF_B - IF (Z%(B,0)AND31)<>5 THEN GOTO QQ_DEFAULT + IF (Z%(B,0)AND 31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] @@ -97,18 +97,18 @@ SUB MACROEXPAND MACROEXPAND_LOOP: REM list? - IF (Z%(A,0)AND31)<>6 THEN GOTO MACROEXPAND_DONE + IF (Z%(A,0)AND 31)<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? IF Z%(A,1)=0 THEN GOTO MACROEXPAND_DONE B=A+1:GOSUB DEREF_B REM symbol? in first position - IF (Z%(B,0)AND31)<>5 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND 31)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? K=B:CALL ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE B=T4:GOSUB DEREF_B REM macro? - IF (Z%(B,0)AND31)<>11 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND 31)<>11 THEN GOTO MACROEXPAND_DONE F=B:AR=Z%(A,1):CALL APPLY A=R @@ -134,7 +134,7 @@ SUB EVAL_AST GOSUB DEREF_A - T=Z%(A,0)AND31 + T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ @@ -174,7 +174,7 @@ SUB EVAL_AST IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -261,8 +261,8 @@ SUB EVAL R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0,0)AND31)<>5 THEN A$="" - IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0,0)AND 31)<>5 THEN A$="" + IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -436,10 +436,10 @@ SUB EVAL R=F:GOSUB DEREF_R:F=R REM if metadata, get the actual object - IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) - IF (Z%(F,0)AND31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F,0)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller R=X%(X):X=X-1 @@ -492,7 +492,8 @@ SUB EVAL GOSUB RELEASE_PEND REM trigger GC - TA=FRE(0) + #cbm TA=FRE(0) + #qbasic TA=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -598,7 +599,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser - IF EOF=1 THEN GOTO QUIT + IF EZ=1 THEN GOTO QUIT A$=R$:CALL REP: REM call REP diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index 16ff0e0299..43cb8dbe65 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -19,7 +19,7 @@ MAL_READ: REM QUASIQUOTE(A) -> R SUB QUASIQUOTE REM pair? - IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_QUOTE + IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_QUOTE IF (Z%(A,1)=0) THEN GOTO QQ_QUOTE GOTO QQ_UNQUOTE @@ -33,7 +33,7 @@ SUB QUASIQUOTE QQ_UNQUOTE: R=A+1:GOSUB DEREF_R - IF (Z%(R,0)AND31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF (Z%(R,0)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] R=Z%(A,1)+1:GOSUB DEREF_R @@ -54,11 +54,11 @@ SUB QUASIQUOTE A=A+1:GOSUB DEREF_A REM pair? - IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_DEFAULT + IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_DEFAULT IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT B=A+1:GOSUB DEREF_B - IF (Z%(B,0)AND31)<>5 THEN GOTO QQ_DEFAULT + IF (Z%(B,0)AND 31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] @@ -97,18 +97,18 @@ SUB MACROEXPAND MACROEXPAND_LOOP: REM list? - IF (Z%(A,0)AND31)<>6 THEN GOTO MACROEXPAND_DONE + IF (Z%(A,0)AND 31)<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? IF Z%(A,1)=0 THEN GOTO MACROEXPAND_DONE B=A+1:GOSUB DEREF_B REM symbol? in first position - IF (Z%(B,0)AND31)<>5 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND 31)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? K=B:CALL ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE B=T4:GOSUB DEREF_B REM macro? - IF (Z%(B,0)AND31)<>11 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND 31)<>11 THEN GOTO MACROEXPAND_DONE F=B:AR=Z%(A,1):CALL APPLY A=R @@ -134,7 +134,7 @@ SUB EVAL_AST GOSUB DEREF_A - T=Z%(A,0)AND31 + T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ @@ -174,7 +174,7 @@ SUB EVAL_AST IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -261,8 +261,8 @@ SUB EVAL R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0,0)AND31)<>5 THEN A$="" - IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0,0)AND 31)<>5 THEN A$="" + IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -468,10 +468,10 @@ SUB EVAL R=F:GOSUB DEREF_R:F=R REM if metadata, get the actual object - IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) - IF (Z%(F,0)AND31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F,0)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller R=X%(X):X=X-1 @@ -524,7 +524,8 @@ SUB EVAL GOSUB RELEASE_PEND REM trigger GC - TA=FRE(0) + #cbm TA=FRE(0) + #qbasic TA=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -630,7 +631,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser - IF EOF=1 THEN GOTO QUIT + IF EZ=1 THEN GOTO QUIT A$=R$:CALL REP: REM call REP diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index a1ec9fca7a..5a2b777e61 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -19,7 +19,7 @@ MAL_READ: REM QUASIQUOTE(A) -> R SUB QUASIQUOTE REM pair? - IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_QUOTE + IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_QUOTE IF (Z%(A,1)=0) THEN GOTO QQ_QUOTE GOTO QQ_UNQUOTE @@ -33,7 +33,7 @@ SUB QUASIQUOTE QQ_UNQUOTE: R=A+1:GOSUB DEREF_R - IF (Z%(R,0)AND31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF (Z%(R,0)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] R=Z%(A,1)+1:GOSUB DEREF_R @@ -54,11 +54,11 @@ SUB QUASIQUOTE A=A+1:GOSUB DEREF_A REM pair? - IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_DEFAULT + IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_DEFAULT IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT B=A+1:GOSUB DEREF_B - IF (Z%(B,0)AND31)<>5 THEN GOTO QQ_DEFAULT + IF (Z%(B,0)AND 31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] @@ -97,18 +97,18 @@ SUB MACROEXPAND MACROEXPAND_LOOP: REM list? - IF (Z%(A,0)AND31)<>6 THEN GOTO MACROEXPAND_DONE + IF (Z%(A,0)AND 31)<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? IF Z%(A,1)=0 THEN GOTO MACROEXPAND_DONE B=A+1:GOSUB DEREF_B REM symbol? in first position - IF (Z%(B,0)AND31)<>5 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND 31)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? K=B:CALL ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE B=T4:GOSUB DEREF_B REM macro? - IF (Z%(B,0)AND31)<>11 THEN GOTO MACROEXPAND_DONE + IF (Z%(B,0)AND 31)<>11 THEN GOTO MACROEXPAND_DONE F=B:AR=Z%(A,1):CALL APPLY A=R @@ -134,7 +134,7 @@ SUB EVAL_AST GOSUB DEREF_A - T=Z%(A,0)AND31 + T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ @@ -174,7 +174,7 @@ SUB EVAL_AST IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF + IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -261,8 +261,8 @@ SUB EVAL R=A0:GOSUB DEREF_R:A0=R REM get symbol in A$ - IF (Z%(A0,0)AND31)<>5 THEN A$="" - IF (Z%(A0,0)AND31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0,0)AND 31)<>5 THEN A$="" + IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -468,10 +468,10 @@ SUB EVAL R=F:GOSUB DEREF_R:F=R REM if metadata, get the actual object - IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1) + IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) - IF (Z%(F,0)AND31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F,0)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller R=X%(X):X=X-1 @@ -524,7 +524,8 @@ SUB EVAL GOSUB RELEASE_PEND REM trigger GC - TA=FRE(0) + #cbm TA=FRE(0) + #qbasic TA=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -589,7 +590,8 @@ MAIN: ZT=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself - A$="(def! *host-language* "+CHR$(34)+"C64 Basic"+CHR$(34)+")" + #cbm A$="(def! *host-language* "+CHR$(34)+"C64 Basic"+CHR$(34)+")" + #qbasic A$="(def! *host-language* "+CHR$(34)+"QBasic"+CHR$(34)+")" GOSUB RE:AY=R:GOSUB RELEASE A$="(def! not (fn* (a) (if a false true)))" @@ -647,7 +649,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser - IF EOF=1 THEN GOTO QUIT + IF EZ=1 THEN GOTO QUIT A$=R$:CALL REP: REM call REP @@ -656,7 +658,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM GOSUB PR_MEMORY_SUMMARY + GOSUB PR_MEMORY_SUMMARY END PRINT_ERROR: diff --git a/basic/types.in.bas b/basic/types.in.bas index af03454639..a6c806f87e 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -25,7 +25,8 @@ REM metadata 16-31 -> Z% index of object with this metadata REM 14 -> Z% index of metdata object INIT_MEMORY: - T=FRE(0) + #cbm TA=FRE(0) + #qbasic TA=0 Z1=2048+1024+256: REM Z% (boxed memory) size (4 bytes each) Z2=256: REM S$ (string memory) size (3 bytes each) @@ -159,7 +160,7 @@ RELEASE: REM nil, false, true IF AY<3 THEN GOTO RELEASE_TOP - U6=Z%(AY,0)AND31: REM type + U6=Z%(AY,0)AND 31: REM type REM AZ=AY: PR=1: GOSUB PR_STR REM PRINT "RELEASE AY:"+STR$(AY)+"["+R$+"] (byte0:"+STR$(Z%(AY,0))+")" @@ -248,17 +249,17 @@ RELEASE_PEND: REM DEREF_R(R) -> R DEREF_R: - IF (Z%(R,0)AND31)=14 THEN R=Z%(R,1):GOTO DEREF_R + IF (Z%(R,0)AND 31)=14 THEN R=Z%(R,1):GOTO DEREF_R RETURN REM DEREF_A(A) -> A DEREF_A: - IF (Z%(A,0)AND31)=14 THEN A=Z%(A,1):GOTO DEREF_A + IF (Z%(A,0)AND 31)=14 THEN A=Z%(A,1):GOTO DEREF_A RETURN REM DEREF_B(B) -> B DEREF_B: - IF (Z%(B,0)AND31)=14 THEN B=Z%(B,1):GOTO DEREF_B + IF (Z%(B,0)AND 31)=14 THEN B=Z%(B,1):GOTO DEREF_B RETURN @@ -278,8 +279,8 @@ EQUAL_Q: X=X+2:X%(X-1)=A:X%(X)=B ED=ED+1 - U1=Z%(A,0)AND31 - U2=Z%(B,0)AND31 + U1=Z%(A,0)AND 31 + U2=Z%(B,0)AND 31 IF U1>5 AND U1<8 AND U2>5 AND U2<8 THEN GOTO EQUAL_Q_SEQ IF U1=8 AND U2=8 THEN GOTO EQUAL_Q_HM @@ -360,7 +361,7 @@ REM sequence functions REM FORCE_SEQ_TYPE(A,T) -> R FORCE_SEQ_TYPE: REM if it's already the right type, inc ref cnt and return it - IF (Z%(A,0)AND31)=T THEN R=A:Z%(R,0)=Z%(R,0)+32:RETURN + IF (Z%(A,0)AND 31)=T THEN R=A:Z%(R,0)=Z%(R,0)+32:RETURN REM otherwise, copy first element to turn it into correct type B=A+1:GOSUB DEREF_B: REM value to copy L=Z%(A,1):N=B:GOSUB ALLOC: REM T already set @@ -371,7 +372,7 @@ FORCE_SEQ_TYPE: REM LIST_Q(A) -> R LIST_Q: R=0 - IF (Z%(A,0)AND31)=6 THEN R=1 + IF (Z%(A,0)AND 31)=6 THEN R=1 RETURN REM EMPTY_Q(A) -> R diff --git a/basic/variables.txt b/basic/variables.txt index aebe3e49ac..242b9f5d78 100644 --- a/basic/variables.txt +++ b/basic/variables.txt @@ -18,6 +18,7 @@ D : root repl environment ER : error type (-2: none, -1: string, >=0: object) ER$ : error string (ER=-1) +EZ : READLINE EOF BI : ENV_NEW_BINDS binds list EX : ENV_NEW_BINDS expressions list From 1b2453005e1af07d58b4170d58b75ff30b3b46d7 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 31 Oct 2016 17:38:28 -0500 Subject: [PATCH 0215/2308] Basic: update the README. --- README.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 143fc1ed2b..b4e7e7f09f 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,12 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 59 languages: +Mal is implemented in 60 languages: * Ada * GNU awk * Bash shell +* Basic (C64 and QBasic) * C * C++ * C# @@ -153,6 +154,35 @@ cd bash bash stepX_YYY.sh ``` +### Basic (C64 and QBasic) + +The Basic implementation uses a preprocessor that can generate Basic +code that is compatible with both C64 Basic (CBM v2) and QBasic. The +C64 mode has been tested with +[cbmbasic](https://github.com/kanaka/cbmbasic) (the patched version is +currently required to fix issues with line input) and the QBasic mode +has been tested with [qb64](http://www.qb64.net/). + +Generate C64 code and run it using cbmbasic: + +``` +cd basic +make stepX_YYY.bas +STEP=stepX_YYY ./run +``` + +Generate QBasic code and load it into qb64: + +``` +cd basic +make MODE=qbasic stepX_YYY.bas +./qb64 stepX_YYY.bas +``` + +Thanks to [Steven Syrek](https://github.com/sjsyrek) for the original +inspiration for this implementation. + + ### C The C implementation of mal requires the following libraries (lib and From 2bec1db017a2e8425807624cc25c32e25a175e71 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 31 Oct 2016 17:45:00 -0500 Subject: [PATCH 0216/2308] Basic: update to 61 impls after merge. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 24ba437cac..279b7868aa 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 60 languages: +Mal is implemented in 61 languages: * Ada * GNU awk From 2bcc46af48e213cfc8cfee2d12e752abecec24df Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 31 Oct 2016 17:50:13 -0500 Subject: [PATCH 0217/2308] Basic: capitalize BASIC I guess. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 279b7868aa..18a6fa0807 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Mal is implemented in 61 languages: * Ada * GNU awk * Bash shell -* Basic (C64 and QBasic) +* BASIC (C64 and QBasic) * C * C++ * C# @@ -155,10 +155,10 @@ cd bash bash stepX_YYY.sh ``` -### Basic (C64 and QBasic) +### BASIC (C64 and QBasic) -The Basic implementation uses a preprocessor that can generate Basic -code that is compatible with both C64 Basic (CBM v2) and QBasic. The +The BASIC implementation uses a preprocessor that can generate BASIC +code that is compatible with both C64 BASIC (CBM v2) and QBasic. The C64 mode has been tested with [cbmbasic](https://github.com/kanaka/cbmbasic) (the patched version is currently required to fix issues with line input) and the QBasic mode From fba3aeb2fba62ac351adf3bb20565ba769a3cf41 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 1 Nov 2016 10:44:49 -0500 Subject: [PATCH 0218/2308] Ada, C: fix step3 error handling. New step3 tests were added during basic implementation that broke Ada and C. --- ada/step3_env.adb | 14 +++++++++++--- c/step3_env.c | 1 + c/types.h | 2 ++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ada/step3_env.adb b/ada/step3_env.adb index a5dcf9601b..cca59b36f9 100644 --- a/ada/step3_env.adb +++ b/ada/step3_env.adb @@ -1,4 +1,5 @@ with Ada.Command_Line; +with Ada.Exceptions; with Ada.Text_IO; with Envs; with Eval_Callback; @@ -254,8 +255,15 @@ begin Init (Repl_Env); loop - Ada.Text_IO.Put ("user> "); - exit when Ada.Text_IO.End_Of_File; - Ada.Text_IO.Put_Line (Rep (Ada.Text_IO.Get_Line, Repl_Env)); + begin + Ada.Text_IO.Put ("user> "); + exit when Ada.Text_IO.End_Of_File; + Ada.Text_IO.Put_Line (Rep (Ada.Text_IO.Get_Line, Repl_Env)); + exception + when E : others => + Ada.Text_IO.Put_Line + (Ada.Text_IO.Standard_Error, + Ada.Exceptions.Exception_Information (E)); + end; end loop; end Step3_Env; diff --git a/c/step3_env.c b/c/step3_env.c index 8c5abfa6d7..ba932792ea 100644 --- a/c/step3_env.c +++ b/c/step3_env.c @@ -80,6 +80,7 @@ MalVal *EVAL(MalVal *ast, Env *env) { MalVal *a1 = _nth(ast, 1), *a2 = _nth(ast, 2); MalVal *res = EVAL(a2, env); + if (mal_error) return NULL; env_set(env, a1, res); return res; } else if (strcmp("let*", a0->val.string) == 0) { diff --git a/c/types.h b/c/types.h index 83ce3943ea..7f327b8e7c 100644 --- a/c/types.h +++ b/c/types.h @@ -6,6 +6,8 @@ #ifdef USE_GC #include +void nop_free(void* ptr); +void GC_setup(); char* GC_strdup(const char *src); #define MAL_GC_SETUP() GC_setup() #define MAL_GC_MALLOC GC_MALLOC From 259dd13c56b130dac08d333237dd89195489e6b4 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 2 Nov 2016 22:19:51 -0500 Subject: [PATCH 0219/2308] Basic: more aggressive space removal Saves over 1200 bytes so that Z% value space can be bumped up by 256 entries. --- basic/basicpp.py | 46 +++++++++++++++++++++++++++++-------------- basic/printer.in.bas | 2 +- basic/readline.in.bas | 2 +- basic/types.in.bas | 2 +- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/basic/basicpp.py b/basic/basicpp.py index 1e0afbe3c1..eed346ce40 100755 --- a/basic/basicpp.py +++ b/basic/basicpp.py @@ -95,12 +95,20 @@ def remove_indent(orig_lines): def misc_fixups(orig_lines): text = "\n".join(orig_lines) text = re.sub(r"\bTHEN GOTO\b", "THEN", text) - text = re.sub(r"\bPRINT \"", "PRINT\"", text) + #text = re.sub(r"AND ([0-9])", r"AND\g<1>", text) + + # More aggressive space removal text = re.sub(r"\bIF ", "IF", text) - text = re.sub(r"AND ([0-9])", r"AND\g<1>", text) + text = re.sub(r"\bPRINT *", "PRINT", text) + text = re.sub(r" *GOTO *", "GOTO", text) + text = re.sub(r" *GOSUB *", "GOSUB", text) + text = re.sub(r"\bDIM ", "DIM", text) + text = re.sub(r" *THEN *", r"THEN", text) + text = re.sub(r"([^A-Z]) *AND *", r"\g<1>AND", text) + text = re.sub(r"([^A-Z]) *OR *", r"\g<1>OR", text) return text.split("\n") -def finalize(lines, args): +def finalize(lines, args, mode): labels_lines = {} lines_labels = {} call_index = {} @@ -122,12 +130,12 @@ def finalize(lines, args): lines_labels[lnum] = label continue - if re.match(r".*\bCALL *([^ :]*) *:", line): + if re.match(r".*CALL *([^ :]*) *:", line): raise Exception("CALL is not the last thing on line %s" % lnum) # Replace CALLs (track line number for replacement later) #m = re.match(r"\bCALL *([^ :]*) *$", line) - m = re.match(r"(.*)\bCALL *([^ :]*) *$", line) + m = re.match(r"(.*)CALL *([^ :]*) *$", line) if m: prefix = m.groups(1)[0] sub = m.groups(1)[1] @@ -137,8 +145,12 @@ def finalize(lines, args): label = sub+"_"+str(call_index[sub]) # Replace the CALL with stack based GOTO - lines.append("%s %sX=X+1:X%%(X)=%s:GOTO %s" % ( - lnum, prefix, call_index[sub], sub)) + if mode == "cbm": + lines.append("%s %sX=X+1:X%%(X)=%s:GOTO%s" % ( + lnum, prefix, call_index[sub], sub)) + else: + lines.append("%s %sX=X+1:X%%(X)=%s:GOTO %s" % ( + lnum, prefix, call_index[sub], sub)) lnum += 1 # Add the return spot @@ -185,12 +197,16 @@ def update_labels_lines(text, a, b): stext = "" while stext != text: stext = text - text = re.sub(r"(THEN) %s\b" % a, r"THEN %s" % b, stext) + text = re.sub(r"(THEN *)%s\b" % a, r"\g<1>%s" % b, stext) #text = re.sub(r"(THEN)%s\b" % a, r"THEN%s" % b, stext) - text = re.sub(r"(ON [^:\n]* GOTO [^:\n]*)\b%s\b" % a, r"\g<1>%s" % b, text) - text = re.sub(r"(ON [^:\n]* GOSUB [^:\n]*)\b%s\b" % a, r"\g<1>%s" % b, text) - text = re.sub(r"(GOSUB) %s\b" % a, r"\1 %s" % b, text) - text = re.sub(r"(GOTO) %s\b" % a, r"\1 %s" % b, text) + if mode == "cbm": + text = re.sub(r"ON *([^:\n]*) *GOTO *([^:\n]*)\b%s\b" % a, r"ON\g<1>GOTO\g<2>%s" % b, text) + text = re.sub(r"ON *([^:\n]*) *GOSUB *([^:\n]*)\b%s\b" % a, r"ON\g<1>GOSUB\g<2>%s" % b, text) + else: + text = re.sub(r"(ON [^:\n]* *GOTO *[^:\n]*)\b%s\b" % a, r"\g<1>%s" % b, text) + text = re.sub(r"(ON [^:\n]* *GOSUB *[^:\n]*)\b%s\b" % a, r"\g<1>%s" % b, text) + text = re.sub(r"(GOSUB *)%s\b" % a, r"\g<1>%s" % b, text) + text = re.sub(r"(GOTO *)%s\b" % a, r"\g<1>%s" % b, text) #text = re.sub(r"(GOTO)%s\b" % a, r"\1%s" % b, text) return text @@ -226,7 +242,7 @@ def renum(line): # be on a line by itself lines.append(acc_line) acc_line = renum(line) - elif re.match(r".*\b(?:GOTO|THEN|RETURN)\b.*", acc_line): + elif re.match(r".*(?:GOTO|THEN|RETURN).*", acc_line): # GOTO/THEN/RETURN are last thing on the line lines.append(acc_line) acc_line = renum(line) @@ -236,7 +252,7 @@ def renum(line): acc_line = acc_line + ":" + line # GOTO/IF/RETURN must be the last things on a line so # start a new line - if re.match(r".*\b(?:GOTO|THEN|RETURN)\b.*", line): + if re.match(r".*(?:GOTO|THEN|RETURN).*", line): lines.append(acc_line) acc_line = "" else: @@ -295,7 +311,7 @@ def renum(line): lines = misc_fixups(lines) # number lines, drop/keep labels, combine lines - lines = finalize(lines, args) + lines = finalize(lines, args, mode=args.mode) debug("Lines after finalizing: %s" % len(lines)) print("\n".join(lines)) diff --git a/basic/printer.in.bas b/basic/printer.in.bas index ca44658319..7608c82413 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -24,7 +24,7 @@ PR_STR: R$=STR$(T5) IF T5<0 THEN RETURN REM Remove initial space - R$=RIGHT$(R$, LEN(R$)-1) + R$=RIGHT$(R$,LEN(R$)-1) RETURN PR_STRING_MAYBE: R$=S$(Z%(AZ,1)) diff --git a/basic/readline.in.bas b/basic/readline.in.bas index d525d53c07..ff9563fc89 100644 --- a/basic/readline.in.bas +++ b/basic/readline.in.bas @@ -25,7 +25,7 @@ READLINE: REM Assumes LI$ has input buffer RL_BACKSPACE: IF LEN(LI$)=0 THEN RETURN - LI$=LEFT$(LI$, LEN(LI$)-1) + LI$=LEFT$(LI$,LEN(LI$)-1) #cbm PRINT CHR$(157)+" "+CHR$(157); #qbasic LOCATE ,POS(0)-1 #qbasic PRINT " "; diff --git a/basic/types.in.bas b/basic/types.in.bas index a6c806f87e..1b02cfa1dc 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -28,7 +28,7 @@ INIT_MEMORY: #cbm TA=FRE(0) #qbasic TA=0 - Z1=2048+1024+256: REM Z% (boxed memory) size (4 bytes each) + Z1=2048+1024+512: REM Z% (boxed memory) size (4 bytes each) Z2=256: REM S$ (string memory) size (3 bytes each) Z3=256: REM X% (call stack) size (2 bytes each) Z4=64: REM Y% (release stack) size (4 bytes each) From 4fab6aa5174180799e85a588de1f8212778c2a89 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 2 Nov 2016 23:36:44 -0500 Subject: [PATCH 0220/2308] Basic: more memory savings - Do the pop of CALL return value at the end of the subroutine (callee) rather than at the call point (caller) - shorten some character variables (CH$->C$, CH->C) - remove spaces after OPEN, GET - print REPL header directly in BASIC code Together saves 404 bytes of memory. --- basic/basicpp.py | 21 +++++++++++----- basic/reader.in.bas | 56 +++++++++++++++++++++--------------------- basic/readline.in.bas | 31 +++++++++++------------ basic/stepA_mal.in.bas | 8 +++--- basic/types.in.bas | 8 +++--- basic/variables.txt | 4 +-- 6 files changed, 68 insertions(+), 60 deletions(-) diff --git a/basic/basicpp.py b/basic/basicpp.py index eed346ce40..f81a1d5e5a 100755 --- a/basic/basicpp.py +++ b/basic/basicpp.py @@ -94,18 +94,26 @@ def remove_indent(orig_lines): def misc_fixups(orig_lines): text = "\n".join(orig_lines) + + # Remove GOTO after THEN text = re.sub(r"\bTHEN GOTO\b", "THEN", text) - #text = re.sub(r"AND ([0-9])", r"AND\g<1>", text) - # More aggressive space removal + # Remove spaces after keywords text = re.sub(r"\bIF ", "IF", text) text = re.sub(r"\bPRINT *", "PRINT", text) + text = re.sub(r"\bDIM ", "DIM", text) + text = re.sub(r"\OPEN ", "OPEN", text) + text = re.sub(r"\bGET ", "GET", text) + + # Remove spaces around GOTO/GOSUB/THEN text = re.sub(r" *GOTO *", "GOTO", text) text = re.sub(r" *GOSUB *", "GOSUB", text) - text = re.sub(r"\bDIM ", "DIM", text) text = re.sub(r" *THEN *", r"THEN", text) + + # Remove spaces around AND/OR except after variables text = re.sub(r"([^A-Z]) *AND *", r"\g<1>AND", text) text = re.sub(r"([^A-Z]) *OR *", r"\g<1>OR", text) + return text.split("\n") def finalize(lines, args, mode): @@ -156,8 +164,6 @@ def finalize(lines, args, mode): # Add the return spot labels_lines[label] = lnum lines_labels[lnum] = label - lines.append("%s X=X-1" % lnum) - lnum += 1 continue lines.append("%s %s" % (lnum, line)) @@ -188,7 +194,10 @@ def finalize(lines, args, mode): index = call_index[cur_sub] ret_labels = [cur_sub+"_"+str(i) for i in range(1, index+1)] - line = "%s ON X%%(X) GOTO %s" % (lnum, ",".join(ret_labels)) + if mode == "cbm": + line = "%s X=X-1:ONX%%(X+1)GOTO%s" % (lnum, ",".join(ret_labels)) + else: + line = "%s X=X-1:ON X%%(X+1) GOTO %s" % (lnum, ",".join(ret_labels)) cur_sub = None lines.append(line) diff --git a/basic/reader.in.bas b/basic/reader.in.bas index b927015425..bdd79f47aa 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -13,19 +13,19 @@ READ_TOKEN: READ_TOKEN_LOOP: IF RF=1 THEN GOSUB READ_FILE_CHUNK IF RJ>LEN(A$) THEN RETURN - CH$=MID$(A$,RJ,1) + C$=MID$(A$,RJ,1) IF S2 THEN GOTO READ_TOKEN_CONT IF S1 THEN GOTO READ_TOKEN_CONT - IF CH$=" " OR CH$="," THEN RETURN - IF CH$=" " OR CH$="," OR CH$=CHR$(13) OR CH$=CHR$(10) THEN RETURN - IF CH$="(" OR CH$=")" OR CH$="[" OR CH$="]" OR CH$="{" OR CH$="}" THEN RETURN + IF C$=" " OR C$="," THEN RETURN + IF C$=" " OR C$="," OR C$=CHR$(13) OR C$=CHR$(10) THEN RETURN + IF C$="(" OR C$=")" OR C$="[" OR C$="]" OR C$="{" OR C$="}" THEN RETURN READ_TOKEN_CONT: - T$=T$+CH$ + T$=T$+C$ IF T$="~@" THEN RETURN RJ=RJ+1 IF S1 AND S2 THEN S2=0:GOTO READ_TOKEN_LOOP - IF S1 AND S2=0 AND CH$=CHR$(92) THEN S2=1:GOTO READ_TOKEN_LOOP - IF S1 AND S2=0 AND CH$=CHR$(34) THEN RETURN + IF S1 AND S2=0 AND C$=CHR$(92) THEN S2=1:GOTO READ_TOKEN_LOOP + IF S1 AND S2=0 AND C$=CHR$(34) THEN RETURN GOTO READ_TOKEN_LOOP READ_FILE_CHUNK: @@ -43,16 +43,16 @@ READ_FILE_CHUNK: SKIP_SPACES: IF RF=1 THEN GOSUB READ_FILE_CHUNK - CH$=MID$(A$,RI,1) - IF CH$<>" " AND CH$<>"," AND CH$<>CHR$(13) AND CH$<>CHR$(10) THEN RETURN + C$=MID$(A$,RI,1) + IF C$<>" " AND C$<>"," AND C$<>CHR$(13) AND C$<>CHR$(10) THEN RETURN RI=RI+1 GOTO SKIP_SPACES SKIP_TO_EOL: IF RF=1 THEN GOSUB READ_FILE_CHUNK - CH$=MID$(A$,RI+1,1) + C$=MID$(A$,RI+1,1) RI=RI+1 - IF CH$="" OR CH$=CHR$(13) OR CH$=CHR$(10) THEN RETURN + IF C$="" OR C$=CHR$(13) OR C$=CHR$(10) THEN RETURN GOTO SKIP_TO_EOL @@ -77,20 +77,20 @@ READ_FORM: IF T$="~@" THEN AS$="splice-unquote":GOTO READ_MACRO IF T$="^" THEN AS$="with-meta":GOTO READ_MACRO IF T$="@" THEN AS$="deref":GOTO READ_MACRO - CH$=MID$(T$,1,1) - REM PRINT "CH$: ["+CH$+"]("+STR$(ASC(CH$))+")" - IF (CH$=";") THEN R=0:GOSUB SKIP_TO_EOL:GOTO READ_FORM - IF CH$>="0" AND CH$<="9" THEN GOTO READ_NUMBER - IF CH$="-" THEN GOTO READ_SYMBOL_MAYBE - - IF CH$=CHR$(34) THEN GOTO READ_STRING - IF CH$=":" THEN GOTO READ_KEYWORD - IF CH$="(" THEN T=6:GOTO READ_SEQ - IF CH$=")" THEN T=6:GOTO READ_SEQ_END - IF CH$="[" THEN T=7:GOTO READ_SEQ - IF CH$="]" THEN T=7:GOTO READ_SEQ_END - IF CH$="{" THEN T=8:GOTO READ_SEQ - IF CH$="}" THEN T=8:GOTO READ_SEQ_END + C$=MID$(T$,1,1) + REM PRINT "C$: ["+C$+"]("+STR$(ASC(C$))+")" + IF (C$=";") THEN R=0:GOSUB SKIP_TO_EOL:GOTO READ_FORM + IF C$>="0" AND C$<="9" THEN GOTO READ_NUMBER + IF C$="-" THEN GOTO READ_SYMBOL_MAYBE + + IF C$=CHR$(34) THEN GOTO READ_STRING + IF C$=":" THEN GOTO READ_KEYWORD + IF C$="(" THEN T=6:GOTO READ_SEQ + IF C$=")" THEN T=6:GOTO READ_SEQ_END + IF C$="[" THEN T=7:GOTO READ_SEQ + IF C$="]" THEN T=7:GOTO READ_SEQ_END + IF C$="{" THEN T=8:GOTO READ_SEQ + IF C$="}" THEN T=8:GOTO READ_SEQ_END GOTO READ_SYMBOL READ_NIL_BOOL: @@ -148,8 +148,8 @@ READ_FORM: AS$=R$:T=4:GOSUB STRING GOTO READ_FORM_DONE READ_SYMBOL_MAYBE: - CH$=MID$(T$,2,1) - IF CH$>="0" AND CH$<="9" THEN GOTO READ_NUMBER + C$=MID$(T$,2,1) + IF C$>="0" AND C$<="9" THEN GOTO READ_NUMBER READ_SYMBOL: REM PRINT "READ_SYMBOL" AS$=T$:T=5:GOSUB STRING @@ -178,7 +178,7 @@ READ_FORM: READ_SEQ_END: REM PRINT "READ_SEQ_END" - IF SD=0 THEN ER$="unexpected '"+CH$+"'":GOTO READ_FORM_ABORT + IF SD=0 THEN ER$="unexpected '"+C$+"'":GOTO READ_FORM_ABORT IF X%(X-1)<>T THEN ER$="sequence mismatch":GOTO READ_FORM_ABORT SD=SD-1: REM decrease read sequence depth R=X%(X-2): REM ptr to start of sequence to return diff --git a/basic/readline.in.bas b/basic/readline.in.bas index ff9563fc89..1dbbcd16c6 100644 --- a/basic/readline.in.bas +++ b/basic/readline.in.bas @@ -1,23 +1,22 @@ REM READLINE(A$) -> R$ READLINE: EZ=0 - PROMPT$=A$ - PRINT PROMPT$; - CH$="":LI$="":CH=0 + PRINT A$; + C$="":LI$="":C=0 READCH: - #cbm GET CH$ - #qbasic CH$=INKEY$ - IF CH$="" THEN GOTO READCH - CH=ASC(CH$) - REM PRINT CH - #qbasic IF ASC(CH$)=8 THEN CH=20:CH$=CHR$(20) - IF CH=4 OR CH=0 THEN EZ=1:GOTO RL_DONE: REM EOF - IF CH=127 OR CH=20 THEN GOSUB RL_BACKSPACE - IF CH=127 OR CH=20 THEN GOTO READCH - IF (CH<32 OR CH>127) AND CH<>13 THEN GOTO READCH - PRINT CH$; - IF LEN(LI$)<255 AND CH$<>CHR$(13) THEN LI$=LI$+CH$ - IF LEN(LI$)<255 AND CH$<>CHR$(13) THEN GOTO READCH + #cbm GET C$ + #qbasic C$=INKEY$ + IF C$="" THEN GOTO READCH + C=ASC(C$) + REM PRINT C + #qbasic IF ASC(C$)=8 THEN C=20:C$=CHR$(20) + IF C=4 OR C=0 THEN EZ=1:GOTO RL_DONE: REM EOF + IF C=127 OR C=20 THEN GOSUB RL_BACKSPACE + IF C=127 OR C=20 THEN GOTO READCH + IF (C<32 OR C>127) AND C<>13 THEN GOTO READCH + PRINT C$; + IF LEN(LI$)<255 AND C$<>CHR$(13) THEN LI$=LI$+C$ + IF LEN(LI$)<255 AND C$<>CHR$(13) THEN GOTO READCH RL_DONE: R$=LI$ RETURN diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index 5a2b777e61..f63ca75ad6 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -590,7 +590,7 @@ MAIN: ZT=ZI: REM top of memory after base repl_env REM core.mal: defined using the language itself - #cbm A$="(def! *host-language* "+CHR$(34)+"C64 Basic"+CHR$(34)+")" + #cbm A$="(def! *host-language* "+CHR$(34)+"C64 BASIC"+CHR$(34)+")" #qbasic A$="(def! *host-language* "+CHR$(34)+"QBasic"+CHR$(34)+")" GOSUB RE:AY=R:GOSUB RELEASE @@ -643,9 +643,9 @@ MAIN: REPL: REM print the REPL startup header - A$="(println (str "+CHR$(34)+"Mal ["+CHR$(34)+" *host-language* " - A$=A$+CHR$(34)+"]"+CHR$(34)+"))" - GOSUB RE:AY=R:GOSUB RELEASE + REM save memory by printing this directly + #cbm PRINT "Mal [C64 BASIC]" + #qbasic PRINT "Mal [C64 QBasic]" REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser diff --git a/basic/types.in.bas b/basic/types.in.bas index 1b02cfa1dc..9b9b17528c 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -67,7 +67,7 @@ INIT_MEMORY: X=-1:DIM X%(Z3): REM stack of Z% indexes REM pending release stack - Y=-1:DIM Y%(Z4,1): REM stack of Z% indexes + Y=-1:DIM Y%(Z4,1): REM stack of Z% indexes and level/LV values BT=TI @@ -350,9 +350,9 @@ REPLACE: J=LEN(T3$) REPLACE_LOOP: IF I>J THEN RETURN - CH$=MID$(T3$,I,LEN(S1$)) - IF CH$=S1$ THEN R$=R$+S2$:I=I+LEN(S1$) - IF CH$<>S1$ THEN R$=R$+MID$(T3$,I,1):I=I+1 + C$=MID$(T3$,I,LEN(S1$)) + IF C$=S1$ THEN R$=R$+S2$:I=I+LEN(S1$) + IF C$<>S1$ THEN R$=R$+MID$(T3$,I,1):I=I+1 GOTO REPLACE_LOOP diff --git a/basic/variables.txt b/basic/variables.txt index 242b9f5d78..2335ae2384 100644 --- a/basic/variables.txt +++ b/basic/variables.txt @@ -32,7 +32,7 @@ Calling arguments/temporaries: A : common call arguments (especially EVAL, EVAL_AST) B : common call arguments -C : common call arguments +C : SLICE argument, READLINE temp. E : environment (EVAL, EVAL_AST) F : function H : hash map @@ -66,7 +66,7 @@ ED : EQUAL_Q recursion depth counter RD : PR_OBJECT recursion depth SD : READ_STR sequence read recursion depth -CH$ : READ_TOKEN, SKIP_SPACES, SKIP_TO_EOL current character +C$ : READ_TOKEN, SKIP_SPACES, SKIP_TO_EOL current character I : STRING, REPLACE, SLICE, PR_MEMORY, PR_OBJECT J : REPLACE S1 : READ_TOKEN in a string? From 6420f327cd335935f7b0898340547fefc4aedf4f Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 3 Nov 2016 23:27:42 -0500 Subject: [PATCH 0221/2308] Basic: add string memory mgt. Fix eval, symbol fns --- basic/core.in.bas | 11 +++--- basic/debug.in.bas | 48 ++++++++++++++++++++++---- basic/types.in.bas | 85 +++++++++++++++++++++++++++++----------------- 3 files changed, 102 insertions(+), 42 deletions(-) diff --git a/basic/core.in.bas b/basic/core.in.bas index a0077e2466..f89d4b3303 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -227,18 +227,17 @@ DO_FUNCTION: R=2 RETURN DO_SYMBOL: - T=5:L=Z%(AA,1):GOSUB ALLOC + AS$=S$(Z%(AA,1)) + T=5:GOSUB STRING RETURN DO_SYMBOL_Q: R=1 IF (Z%(AA,0)AND 31)=5 THEN R=2 RETURN DO_KEYWORD: - A=Z%(AA,1) - AS$=S$(A) + AS$=S$(Z%(AA,1)) IF MID$(AS$,1,1)<>CHR$(127) THEN AS$=CHR$(127)+AS$ - GOSUB STRING_ - T=4:L=R:GOSUB ALLOC + T=4:GOSUB STRING RETURN DO_KEYWORD_Q: R=1 @@ -525,7 +524,9 @@ DO_FUNCTION: REM RETURN DO_EVAL: + X=X+1:X%(X)=E: REM push/save environment A=AA:E=D:CALL EVAL + E=X%(X):X=X-1: REM pop/restore previous environment RETURN DO_READ_FILE: diff --git a/basic/debug.in.bas b/basic/debug.in.bas index 842bc5dbed..89db78d778 100644 --- a/basic/debug.in.bas +++ b/basic/debug.in.bas @@ -1,4 +1,4 @@ -REM CHECK_FREE_LIST +REM CHECK_FREE_LIST() -> P2 CHECK_FREE_LIST: REM start and accumulator P1=ZK @@ -13,17 +13,53 @@ CHECK_FREE_LIST: IF P2=-1 THEN PRINT "corrupt free list at "+STR$(P1) RETURN +REM COUNT_STRINGS() -> P2 +COUNT_STRINGS: + P1=0 + P2=0 + COUNT_STRINGS_LOOP: + IF P1>S-1 THEN RETURN + IF S%(P1)>0 THEN P2=P2+1 + P1=P1+1 + GOTO COUNT_STRINGS_LOOP + PR_MEMORY_SUMMARY: + #cbm P0=FRE(0) + PRINT - #cbm PRINT "Free (FRE) :"+STR$(FRE(0)) - PRINT "Values (Z%) :"+STR$(ZI-1)+" /"+STR$(Z1) + #cbm PRINT "Free (FRE) :"+STR$(P0) GOSUB CHECK_FREE_LIST: REM get count in P2 - PRINT " used:"+STR$(ZI-1-P2)+", freed:"+STR$(P2); - PRINT ", after repl_env:"+STR$(ZT) - PRINT "Strings (S$) :"+STR$(S)+" /"+STR$(Z2) + PRINT "Values (Z%) :"+STR$(ZI-1-P2)+" /"+STR$(Z1) + PRINT " max:"+STR$(ZI-1); + PRINT ", freed:"+STR$(P2)+", after repl_env:"+STR$(ZT) + GOSUB COUNT_STRINGS + PRINT "Strings (S$) :"+STR$(P2)+" /"+STR$(Z2) PRINT "Stack (X%) :"+STR$(X+1)+" /"+STR$(Z3) RETURN +REM #cbm PR_MEMORY_MAP: +REM #cbm PRINT +REM #cbm P1=PEEK(43)+PEEK(44)*256 +REM #cbm P2=PEEK(45)+PEEK(46)*256 +REM #cbm P3=PEEK(47)+PEEK(48)*256 +REM #cbm P4=PEEK(49)+PEEK(50)*256 +REM #cbm P5=PEEK(51)+PEEK(52)*256 +REM #cbm P6=PEEK(53)+PEEK(54)*256 +REM #cbm P7=PEEK(55)+PEEK(56)*256 +REM #cbm PRINT "BASIC beg. :"STR$(P1) +REM #cbm PRINT "Variable beg.:"STR$(P2) +REM #cbm PRINT "Array beg. :"STR$(P3) +REM #cbm PRINT "Array end :"STR$(P4) +REM #cbm PRINT "String beg. :"STR$(P5) +REM #cbm PRINT "String cur. :"STR$(P6) +REM #cbm PRINT "BASIC end :"STR$(P7) +REM #cbm PRINT +REM #cbm PRINT "Program Code :"STR$(P2-P1) +REM #cbm PRINT "Variables :"STR$(P3-P2) +REM #cbm PRINT "Arrays :"STR$(P4-P3) +REM #cbm PRINT "String Heap :"STR$(P7-P5) +REM #cbm RETURN + REM REM PR_MEMORY(P1, P2) -> nil REM PR_MEMORY: REM IF P2=32 GOTO RELEASE_TOP REM switch on type - IF (U6<=5) OR (U6=9) THEN GOTO RELEASE_SIMPLE - IF (U6>=6) AND (U6<=8) THEN GOTO RELEASE_SEQ + IF U6<=3 OR U6=9 THEN GOTO RELEASE_SIMPLE + IF U6=4 OR U6=5 THEN GOTO RELEASE_STRING + IF U6>=6 AND U6<=8 THEN GOTO RELEASE_SEQ IF U6=10 OR U6=11 THEN GOTO RELEASE_MAL_FUNCTION IF U6>=16 THEN GOTO RELEASE_METADATA IF U6=12 THEN GOTO RELEASE_ATOM @@ -194,34 +200,41 @@ RELEASE: REM free the current element and continue SZ=2:GOSUB FREE GOTO RELEASE_TOP + RELEASE_STRING: + REM string type, release interned string, then FREE reference + IF S%(U7)=0 THEN ER=-1:ER$="RELEASE of free string:"+STR$(S%(U7)):RETURN + S%(U7)=S%(U7)-1 + IF S%(U7)=0 THEN S$(U7)="": REM free BASIC string + REM free the atom itself + GOTO RELEASE_SIMPLE RELEASE_SEQ: - IF Z%(AY,1)=0 THEN GOTO RELEASE_SIMPLE_2 + IF U7=0 THEN GOTO RELEASE_SIMPLE_2 IF Z%(AY+1,0)<>14 THEN ER=-1:ER$="invalid list value"+STR$(AY+1):RETURN REM add value and next element to stack RC=RC+2:X=X+2 - X%(X-1)=Z%(AY+1,1):X%(X)=Z%(AY,1) + X%(X-1)=Z%(AY+1,1):X%(X)=U7 GOTO RELEASE_SIMPLE_2 RELEASE_ATOM: REM add contained/referred value - RC=RC+1:X=X+1:X%(X)=Z%(AY,1) + RC=RC+1:X=X+1:X%(X)=U7 REM free the atom itself GOTO RELEASE_SIMPLE RELEASE_MAL_FUNCTION: REM add ast, params and environment to stack RC=RC+3:X=X+3 - X%(X-2)=Z%(AY,1):X%(X-1)=Z%(AY+1,0):X%(X)=Z%(AY+1,1) + X%(X-2)=U7:X%(X-1)=Z%(AY+1,0):X%(X)=Z%(AY+1,1) REM free the current 2 element mal_function and continue SZ=2:GOSUB FREE GOTO RELEASE_TOP RELEASE_METADATA: REM add object and metadata object RC=RC+2:X=X+2 - X%(X-1)=Z%(AY,1):X%(X)=Z%(AY+1,1) + X%(X-1)=U7:X%(X)=Z%(AY+1,1) SZ=2:GOSUB FREE GOTO RELEASE_TOP RELEASE_ENV: REM add the hashmap data to the stack - RC=RC+1:X=X+1:X%(X)=Z%(AY,1) + RC=RC+1:X=X+1:X%(X)=U7 REM if no outer set IF Z%(AY+1,1)=-1 THEN GOTO RELEASE_ENV_FREE REM add outer environment to the stack @@ -231,9 +244,9 @@ RELEASE: SZ=2:GOSUB FREE GOTO RELEASE_TOP RELEASE_REFERENCE: - IF Z%(AY,1)=0 THEN GOTO RELEASE_SIMPLE + IF U7=0 THEN GOTO RELEASE_SIMPLE REM add the referred element to the stack - RC=RC+1:X=X+1:X%(X)=Z%(AY,1) + RC=RC+1:X=X+1:X%(X)=U7 REM free the current element and continue SZ=1:GOSUB FREE GOTO RELEASE_TOP @@ -316,31 +329,43 @@ EQUAL_Q: REM string functions -REM STRING_(AS$) -> R -REM intern string (returns string index, not Z% index) -STRING_: +REM STRING(AS$, T) -> R +REM intern string and allocate reference (return Z% index) +STRING: IF S=0 THEN GOTO STRING_NOT_FOUND REM search for matching string in S$ I=0 - STRING_LOOP: + STRING_FIND_LOOP: IF I>S-1 THEN GOTO STRING_NOT_FOUND - IF AS$=S$(I) THEN R=I:RETURN + IF S%(I)>0 AND AS$=S$(I) THEN GOTO STRING_DONE I=I+1 - GOTO STRING_LOOP + GOTO STRING_FIND_LOOP STRING_NOT_FOUND: - S$(S)=AS$ - R=S + I=S-1 + STRING_FIND_GAP_LOOP: + REM TODO: don't search core function names (store position) + IF I=-1 THEN GOTO STRING_NEW + IF S%(I)=0 THEN GOTO STRING_SET + I=I-1 + GOTO STRING_FIND_GAP_LOOP + + STRING_NEW: + I=S S=S+1 - RETURN + REM fallthrough -REM STRING(AS$, T) -> R -REM intern string and allocate reference (return Z% index) -STRING: - GOSUB STRING_ - L=R:GOSUB ALLOC - RETURN + STRING_SET: +REM IF I>85 THEN PRINT "STRING:"+STR$(I)+" "+AS$ + S$(I)=AS$ + REM fallthrough + + STRING_DONE: + S%(I)=S%(I)+1 +REM PRINT "STRING ref: "+S$(I)+" (idx:"+STR$(I)+", ref "+STR$(S%(I))+")" + L=I:GOSUB ALLOC + RETURN REM REPLACE(R$, S1$, S2$) -> R$ REPLACE: @@ -482,10 +507,8 @@ ASSOC1: REM ASSOC1(H, K$, V) -> R ASSOC1_S: - S$(S)=K$ REM add the key string - T=4:L=S:GOSUB ALLOC - S=S+1 + AS$=K$:T=4:GOSUB STRING K=R:GOSUB ASSOC1 AY=K:GOSUB RELEASE: REM map took ownership of key RETURN From c756af81965508220c716fcec4ea92a7d53dc812 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 4 Nov 2016 00:07:09 -0500 Subject: [PATCH 0222/2308] Basic: reduce variables. Fix func printing. - Save over 450 bytes. Bump up Z values by 128. - Fix function printing when function is embedded in something else by using strings storage as a stack while printing rather than using RR$ - Simplify some error messages and sanity checks in RELEASE. --- basic/core.in.bas | 56 +++++++++++------------ basic/debug.in.bas | 4 +- basic/env.in.bas | 58 ++++++++++++------------ basic/printer.in.bas | 84 +++++++++++++++++------------------ basic/reader.in.bas | 42 +++++++++--------- basic/step1_read_print.in.bas | 6 +-- basic/step2_eval.in.bas | 36 +++++++-------- basic/step3_env.in.bas | 44 +++++++++--------- basic/step4_if_fn_do.in.bas | 28 ++++++------ basic/step5_tco.in.bas | 28 ++++++------ basic/step6_file.in.bas | 28 ++++++------ basic/step7_quote.in.bas | 56 +++++++++++------------ basic/step8_macros.in.bas | 58 ++++++++++++------------ basic/step9_try.in.bas | 68 ++++++++++++++-------------- basic/stepA_mal.in.bas | 72 +++++++++++++++--------------- basic/types.in.bas | 77 +++++++++++++++----------------- basic/variables.txt | 65 +++++++++++++-------------- 17 files changed, 401 insertions(+), 409 deletions(-) diff --git a/basic/core.in.bas b/basic/core.in.bas index f89d4b3303..51ff1edee4 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -25,7 +25,7 @@ SUB APPLY REM create new environ using env and params stored in the REM function and bind the params to the apply arguments - O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS + C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS A=Z%(F,1):E=R:CALL EVAL @@ -39,13 +39,13 @@ END SUB REM DO_TCO_FUNCTION(F, AR) SUB DO_TCO_FUNCTION - FF=Z%(F,1) + G=Z%(F,1) REM Get argument values R=AR+1:GOSUB DEREF_R:AA=R R=Z%(AR,1)+1:GOSUB DEREF_R:AB=R - ON FF-60 GOTO DO_APPLY,DO_MAP,DO_SWAP_BANG + ON G-60 GOTO DO_APPLY,DO_MAP,DO_SWAP_BANG DO_APPLY: F=AA @@ -175,28 +175,28 @@ END SUB REM DO_FUNCTION(F, AR) DO_FUNCTION: REM Get the function number - FF=Z%(F,1) + G=Z%(F,1) REM Get argument values R=AR+1:GOSUB DEREF_R:AA=R R=Z%(AR,1)+1:GOSUB DEREF_R:AB=R REM Switch on the function number - IF FF>59 THEN ER=-1:ER$="unknown function"+STR$(FF):RETURN - ON INT(FF/10)+1 GOTO DO_1_9,DO_10_19,DO_20_29,DO_30_39,DO_40_49,DO_50_59 + IF G>59 THEN ER=-1:E$="unknown function"+STR$(G):RETURN + ON INT(G/10)+1 GOTO DO_1_9,DO_10_19,DO_20_29,DO_30_39,DO_40_49,DO_50_59 DO_1_9: - ON FF GOTO DO_EQUAL_Q,DO_THROW,DO_NIL_Q,DO_TRUE_Q,DO_FALSE_Q,DO_STRING_Q,DO_SYMBOL,DO_SYMBOL_Q,DO_KEYWORD + ON G GOTO DO_EQUAL_Q,DO_THROW,DO_NIL_Q,DO_TRUE_Q,DO_FALSE_Q,DO_STRING_Q,DO_SYMBOL,DO_SYMBOL_Q,DO_KEYWORD DO_10_19: - ON FF-9 GOTO DO_KEYWORD_Q,DO_PR_STR,DO_STR,DO_PRN,DO_PRINTLN,DO_READ_STRING,DO_READLINE,DO_SLURP,DO_LT,DO_LTE + ON G-9 GOTO DO_KEYWORD_Q,DO_PR_STR,DO_STR,DO_PRN,DO_PRINTLN,DO_READ_STRING,DO_READLINE,DO_SLURP,DO_LT,DO_LTE DO_20_29: - ON FF-19 GOTO DO_GT,DO_GTE,DO_ADD,DO_SUB,DO_MULT,DO_DIV,DO_TIME_MS,DO_LIST,DO_LIST_Q,DO_VECTOR + ON G-19 GOTO DO_GT,DO_GTE,DO_ADD,DO_SUB,DO_MULT,DO_DIV,DO_TIME_MS,DO_LIST,DO_LIST_Q,DO_VECTOR DO_30_39: - ON FF-29 GOTO DO_VECTOR_Q,DO_HASH_MAP,DO_MAP_Q,DO_ASSOC,DO_THROW,DO_GET,DO_CONTAINS,DO_KEYS,DO_VALS,DO_SEQUENTIAL_Q + ON G-29 GOTO DO_VECTOR_Q,DO_HASH_MAP,DO_MAP_Q,DO_ASSOC,DO_THROW,DO_GET,DO_CONTAINS,DO_KEYS,DO_VALS,DO_SEQUENTIAL_Q DO_40_49: - ON FF-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_CONJ,DO_SEQ,DO_WITH_META + ON G-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_CONJ,DO_SEQ,DO_WITH_META DO_50_59: - ON FF-49 GOTO DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_EVAL,DO_READ_FILE + ON G-49 GOTO DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_EVAL,DO_READ_FILE REM ,DO_PR_MEMORY_SUMMARY DO_EQUAL_Q: @@ -227,7 +227,7 @@ DO_FUNCTION: R=2 RETURN DO_SYMBOL: - AS$=S$(Z%(AA,1)) + B$=S$(Z%(AA,1)) T=5:GOSUB STRING RETURN DO_SYMBOL_Q: @@ -235,8 +235,8 @@ DO_FUNCTION: IF (Z%(AA,0)AND 31)=5 THEN R=2 RETURN DO_KEYWORD: - AS$=S$(Z%(AA,1)) - IF MID$(AS$,1,1)<>CHR$(127) THEN AS$=CHR$(127)+AS$ + B$=S$(Z%(AA,1)) + IF MID$(B$,1,1)<>CHR$(127) THEN B$=CHR$(127)+B$ T=4:GOSUB STRING RETURN DO_KEYWORD_Q: @@ -247,20 +247,20 @@ DO_FUNCTION: RETURN DO_PR_STR: - AZ=AR:PR=1:SE$=" ":GOSUB PR_STR_SEQ - AS$=R$:T=4:GOSUB STRING + AZ=AR:B=1:SE$=" ":GOSUB PR_STR_SEQ + B$=R$:T=4:GOSUB STRING RETURN DO_STR: - AZ=AR:PR=0:SE$="":GOSUB PR_STR_SEQ - AS$=R$:T=4:GOSUB STRING + AZ=AR:B=0:SE$="":GOSUB PR_STR_SEQ + B$=R$:T=4:GOSUB STRING RETURN DO_PRN: - AZ=AR:PR=1:SE$=" ":GOSUB PR_STR_SEQ + AZ=AR:B=1:SE$=" ":GOSUB PR_STR_SEQ PRINT R$ R=0 RETURN DO_PRINTLN: - AZ=AR:PR=0:SE$=" ":GOSUB PR_STR_SEQ + AZ=AR:B=0:SE$=" ":GOSUB PR_STR_SEQ PRINT R$ R=0 RETURN @@ -271,13 +271,13 @@ DO_FUNCTION: DO_READLINE: A$=S$(Z%(AA,1)):GOSUB READLINE IF EZ=1 THEN EZ=0:R=0:RETURN - AS$=R$:T=4:GOSUB STRING + B$=R$:T=4:GOSUB STRING RETURN DO_SLURP: R$="" #cbm OPEN 1,8,0,S$(Z%(AA,1)) #qbasic A$=S$(Z%(AA,1)) - #qbasic IF NOT _FILEEXISTS(A$) THEN ER=-1:ER$="File not found":RETURN + #qbasic IF NOT _FILEEXISTS(A$) THEN ER=-1:E$="File not found":RETURN #qbasic OPEN A$ FOR INPUT AS #1 DO_SLURP_LOOP: A$="" @@ -287,11 +287,11 @@ DO_FUNCTION: IF ASC(A$)=10 THEN R$=R$+CHR$(13) IF (ASC(A$)<>10) AND (A$<>"") THEN R$=R$+A$ #cbm IF (ST AND 64) THEN GOTO DO_SLURP_DONE - #cbm IF (ST AND 255) THEN ER=-1:ER$="File read error "+STR$(ST):RETURN + #cbm IF (ST AND 255) THEN ER=-1:E$="File read error "+STR$(ST):RETURN GOTO DO_SLURP_LOOP DO_SLURP_DONE: CLOSE 1 - AS$=R$:T=4:GOSUB STRING + B$=R$:T=4:GOSUB STRING RETURN DO_LT: @@ -354,7 +354,7 @@ DO_FUNCTION: AR=Z%(AR,1) DO_ASSOC_LOOP: R=AR+1:GOSUB DEREF_R:K=R - R=Z%(AR,1)+1:GOSUB DEREF_R:V=R + R=Z%(AR,1)+1:GOSUB DEREF_R:C=R Z%(H,0)=Z%(H,0)+32 GOSUB ASSOC1:H=R AR=Z%(Z%(AR,1),1) @@ -447,7 +447,7 @@ DO_FUNCTION: DO_NTH: B=AA:GOSUB COUNT B=Z%(AB,1) - IF R<=B THEN R=0:ER=-1:ER$="nth: index out of range":RETURN + IF R<=B THEN R=0:ER=-1:E$="nth: index out of range":RETURN DO_NTH_LOOP: IF B=0 THEN GOTO DO_NTH_DONE B=B-1 @@ -536,7 +536,7 @@ DO_FUNCTION: INIT_CORE_SET_FUNCTION: GOSUB NATIVE_FUNCTION - V=R:GOSUB ENV_SET_S + C=R:GOSUB ENV_SET_S RETURN REM INIT_CORE_NS(E) diff --git a/basic/debug.in.bas b/basic/debug.in.bas index 89db78d778..649ad497bf 100644 --- a/basic/debug.in.bas +++ b/basic/debug.in.bas @@ -30,8 +30,8 @@ PR_MEMORY_SUMMARY: #cbm PRINT "Free (FRE) :"+STR$(P0) GOSUB CHECK_FREE_LIST: REM get count in P2 PRINT "Values (Z%) :"+STR$(ZI-1-P2)+" /"+STR$(Z1) - PRINT " max:"+STR$(ZI-1); - PRINT ", freed:"+STR$(P2)+", after repl_env:"+STR$(ZT) + REM PRINT " max:"+STR$(ZI-1); + REM PRINT ", freed:"+STR$(P2)+", after repl_env:"+STR$(ZT) GOSUB COUNT_STRINGS PRINT "Strings (S$) :"+STR$(P2)+" /"+STR$(Z2) PRINT "Stack (X%) :"+STR$(X+1)+" /"+STR$(Z3) diff --git a/basic/env.in.bas b/basic/env.in.bas index a637732f4e..e2845cc91a 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -1,92 +1,92 @@ -REM ENV_NEW(O) -> R +REM ENV_NEW(C) -> R ENV_NEW: REM allocate the data hashmap GOSUB HASHMAP ET=R REM set the outer and data pointer - T=13:L=R:N=O:GOSUB ALLOC + T=13:L=R:N=C:GOSUB ALLOC AY=ET:GOSUB RELEASE: REM environment takes ownership RETURN REM see RELEASE types.in.bas for environment cleanup -REM ENV_NEW_BINDS(O, BI, EX) -> R +REM ENV_NEW_BINDS(C, A, B) -> R ENV_NEW_BINDS: GOSUB ENV_NEW E=R REM process bindings ENV_NEW_BINDS_LOOP: - IF Z%(BI,1)=0 THEN R=E:RETURN - REM get/deref the key from BI - R=BI+1:GOSUB DEREF_R + IF Z%(A,1)=0 THEN R=E:RETURN + REM get/deref the key from A + R=A+1:GOSUB DEREF_R K=R IF S$(Z%(K,1))="&" THEN GOTO EVAL_NEW_BINDS_VARGS EVAL_NEW_BINDS_1x1: - REM get/deref the key from EX - R=EX+1:GOSUB DEREF_R - V=R + REM get/deref the key from B + R=B+1:GOSUB DEREF_R + C=R REM set the binding in the environment data GOSUB ENV_SET - REM go to next element of BI and EX - BI=Z%(BI,1) - EX=Z%(EX,1) + REM go to next element of A and B + A=Z%(A,1) + B=Z%(B,1) GOTO ENV_NEW_BINDS_LOOP EVAL_NEW_BINDS_VARGS: - REM get/deref the key from next element of BI - BI=Z%(BI,1) - R=BI+1:GOSUB DEREF_R + REM get/deref the key from next element of A + A=Z%(A,1) + R=A+1:GOSUB DEREF_R K=R - REM the value is the remaining list in EX - A=EX:T=6:GOSUB FORCE_SEQ_TYPE - V=R + REM the value is the remaining list in B + A=B:T=6:GOSUB FORCE_SEQ_TYPE + C=R REM set the binding in the environment data GOSUB ENV_SET R=E - AY=V:GOSUB RELEASE: REM list is owned by environment + AY=C:GOSUB RELEASE: REM list is owned by environment RETURN -REM ENV_SET(E, K, V) -> R +REM ENV_SET(E, K, C) -> R ENV_SET: H=Z%(E,1) GOSUB ASSOC1 Z%(E,1)=R - R=V + R=C RETURN -REM ENV_SET_S(E, K$, V) -> R +REM ENV_SET_S(E, K$, C) -> R ENV_SET_S: H=Z%(E,1) GOSUB ASSOC1_S Z%(E,1)=R - R=V + R=C RETURN REM ENV_FIND(E, K) -> R REM Returns environment (R) containing K. If found, value found is REM in T4 SUB ENV_FIND - EF=E + T=E ENV_FIND_LOOP: - H=Z%(EF,1) + H=Z%(T,1) REM More efficient to use GET for value (R) and contains? (T3) GOSUB HASHMAP_GET REM if we found it, save value in T4 for ENV_GET IF T3=1 THEN T4=R:GOTO ENV_FIND_DONE - EF=Z%(EF+1,1): REM get outer environment - IF EF<>-1 THEN GOTO ENV_FIND_LOOP + T=Z%(T+1,1): REM get outer environment + IF T<>-1 THEN GOTO ENV_FIND_LOOP ENV_FIND_DONE: - R=EF + R=T END SUB REM ENV_GET(E, K) -> R ENV_GET: CALL ENV_FIND - IF R=-1 THEN R=0:ER=-1:ER$="'"+S$(Z%(K,1))+"' not found":GOTO ENV_GET_RETURN + IF R=-1 THEN R=0:ER=-1:E$="'"+S$(Z%(K,1))+"' not found":GOTO ENV_GET_RETURN R=T4:GOSUB DEREF_R Z%(R,0)=Z%(R,0)+32 GOTO ENV_GET_RETURN diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 7608c82413..85f7f33170 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -1,110 +1,110 @@ -REM PR_STR(AZ, PR) -> R$ +REM PR_STR(AZ, B) -> R$ PR_STR: - RR$="" + R$="" PR_STR_RECUR: T=Z%(AZ,0)AND 31 - REM PRINT "AZ: "+STR$(AZ)+", T: "+STR$(T)+", V: "+STR$(Z%(AZ,1)) + U=Z%(AZ,1) + REM PRINT "AZ: "+STR$(AZ)+", T: "+STR$(T)+", C: "+STR$(U) IF T=0 THEN R$="nil":RETURN REM if metadata, then get actual object - IF T>=16 THEN AZ=Z%(AZ,1):GOTO PR_STR_RECUR + IF T>=16 THEN AZ=U:GOTO PR_STR_RECUR ON T GOTO PR_BOOLEAN,PR_INTEGER,PR_UNKNOWN,PR_STRING_MAYBE,PR_SYMBOL,PR_SEQ,PR_SEQ,PR_SEQ,PR_FUNCTION,PR_MAL_FUNCTION,PR_MAL_FUNCTION,PR_ATOM,PR_ENV,PR_RECUR,PR_FREE PR_UNKNOWN: R$="#" RETURN PR_RECUR: - AZ=Z%(AZ,1) + AZ=U GOTO PR_STR_RECUR PR_BOOLEAN: R$="true" - IF Z%(AZ,1)=0 THEN R$="false" + IF U=0 THEN R$="false" RETURN PR_INTEGER: - T5=Z%(AZ,1) - R$=STR$(T5) - IF T5<0 THEN RETURN + T$=STR$(U) REM Remove initial space - R$=RIGHT$(R$,LEN(R$)-1) + IF U>=0 THEN T$=RIGHT$(T$,LEN(T$)-1) + R$=R$+T$ RETURN PR_STRING_MAYBE: - R$=S$(Z%(AZ,1)) + R$=S$(U) IF LEN(R$)=0 THEN GOTO PR_STRING IF MID$(R$,1,1)=CHR$(127) THEN R$=":"+MID$(R$,2,LEN(R$)-1):RETURN PR_STRING: - IF PR=1 THEN PR_STRING_READABLY + IF B=1 THEN PR_STRING_READABLY RETURN PR_STRING_READABLY: - S1$=CHR$(92):S2$=CHR$(92)+CHR$(92):GOSUB REPLACE: REM escape backslash - S1$=CHR$(34):S2$=CHR$(92)+CHR$(34):GOSUB REPLACE: REM escape quotes - S1$=CHR$(13):S2$=CHR$(92)+"n":GOSUB REPLACE: REM escape newlines + S1$="\":S2$="\\":GOSUB REPLACE: REM escape backslash " + S1$=CHR$(34):S2$="\"+CHR$(34):GOSUB REPLACE: REM escape quotes " + S1$=CHR$(13):S2$="\n":GOSUB REPLACE: REM escape newlines R$=CHR$(34)+R$+CHR$(34) RETURN PR_SYMBOL: - R$=S$(Z%(AZ,1)) + R$=S$(U) RETURN PR_SEQ: - IF T=6 THEN RR$=RR$+"(" - IF T=7 THEN RR$=RR$+"[" - IF T=8 THEN RR$=RR$+"{" REM push the type and where we are in the sequence X=X+2 X%(X-1)=T X%(X)=AZ + REM save the current rendered string + S$(S)=R$:S=S+1 PR_SEQ_LOOP: IF Z%(AZ,1)=0 THEN PR_SEQ_DONE - AZ=AZ+1 - GOSUB PR_STR_RECUR - REM if we just rendered a non-sequence, then append it - IF T<6 OR T>8 THEN RR$=RR$+R$ + AZ=AZ+1:GOSUB PR_STR + REM append what we just rendered it + S$(S-1)=S$(S-1)+R$ REM restore current seq type T=X%(X-1) REM Go to next list element AZ=Z%(X%(X),1) X%(X)=AZ - IF Z%(AZ,1)<>0 THEN RR$=RR$+" " + IF Z%(AZ,1)<>0 THEN S$(S-1)=S$(S-1)+" " GOTO PR_SEQ_LOOP PR_SEQ_DONE: + REM restore the current string + S=S-1:R$=S$(S) REM get type T=X%(X-1) REM pop where we are the sequence and type X=X-2 - IF T=6 THEN RR$=RR$+")" - IF T=7 THEN RR$=RR$+"]" - IF T=8 THEN RR$=RR$+"}" - R$=RR$ + IF T=6 THEN R$="("+R$+")" + IF T=7 THEN R$="["+R$+"]" + IF T=8 THEN R$="{"+R$+"}" RETURN PR_FUNCTION: - T1=Z%(AZ,1) + T1=U R$="#" RETURN PR_MAL_FUNCTION: T1=AZ - AZ=Z%(T1+1,0):GOSUB PR_STR_RECUR - T7$="(fn* "+R$ - RR$="" - AZ=Z%(T1,1):GOSUB PR_STR_RECUR - R$=T7$+" "+R$+")" + AZ=Z%(T1+1,0):GOSUB PR_STR + REM append what we just rendered it + S$(S)="(fn* "+R$:S=S+1 + AZ=Z%(T1,1):GOSUB PR_STR + S=S-1 + R$=S$(S)+" "+R$+")" RETURN PR_ATOM: - AZ=Z%(AZ,1):GOSUB PR_STR_RECUR + AZ=U:GOSUB PR_STR R$="(atom "+R$+")" RETURN PR_ENV: - R$="#" + R$="#" RETURN PR_FREE: - R$="#" + R$="#" RETURN -REM PR_STR_SEQ(AZ, PR, SE$) -> R$ +REM PR_STR_SEQ(AZ, B, SE$) -> R$ PR_STR_SEQ: T9=AZ - R1$="" + S$(S)="":S=S+1 PR_STR_SEQ_LOOP: - IF Z%(T9,1)=0 THEN R$=R1$:RETURN + IF Z%(T9,1)=0 THEN S=S-1:R$=S$(S):RETURN AZ=T9+1:GOSUB PR_STR REM goto the next sequence element T9=Z%(T9,1) - IF Z%(T9,1)=0 THEN R1$=R1$+R$ - IF Z%(T9,1)<>0 THEN R1$=R1$+R$+SE$ + IF Z%(T9,1)=0 THEN S$(S-1)=S$(S-1)+R$ + IF Z%(T9,1)<>0 THEN S$(S-1)=S$(S-1)+R$+SE$ GOTO PR_STR_SEQ_LOOP diff --git a/basic/reader.in.bas b/basic/reader.in.bas index bdd79f47aa..20ff4d9ad3 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -38,7 +38,7 @@ READ_FILE_CHUNK: #qbasic IF EOF(2) THEN RS=1:A$=A$+CHR$(10)+")":RETURN A$=A$+C$ #cbm IF (ST AND 64) THEN RS=1:A$=A$+CHR$(10)+")":RETURN - #cbm IF (ST AND 255) THEN RS=1:ER=-1:ER$="File read error "+STR$(ST):RETURN + #cbm IF (ST AND 255) THEN RS=1:ER=-1:E$="File read error "+STR$(ST):RETURN GOTO READ_FILE_CHUNK_LOOP SKIP_SPACES: @@ -65,18 +65,18 @@ READ_FORM: IF ER<>-2 THEN RETURN GOSUB SKIP_SPACES GOSUB READ_TOKEN - IF T$="" AND SD>0 THEN ER$="unexpected EOF":GOTO READ_FORM_ABORT + IF T$="" AND SD>0 THEN E$="unexpected EOF":GOTO READ_FORM_ABORT REM PRINT "READ_FORM T$: ["+T$+"]" IF T$="" THEN R=0:GOTO READ_FORM_DONE IF T$="nil" THEN T=0:GOTO READ_NIL_BOOL IF T$="false" THEN T=1:GOTO READ_NIL_BOOL IF T$="true" THEN T=2:GOTO READ_NIL_BOOL - IF T$="'" THEN AS$="quote":GOTO READ_MACRO - IF T$="`" THEN AS$="quasiquote":GOTO READ_MACRO - IF T$="~" THEN AS$="unquote":GOTO READ_MACRO - IF T$="~@" THEN AS$="splice-unquote":GOTO READ_MACRO - IF T$="^" THEN AS$="with-meta":GOTO READ_MACRO - IF T$="@" THEN AS$="deref":GOTO READ_MACRO + IF T$="'" THEN B$="quote":GOTO READ_MACRO + IF T$="`" THEN B$="quasiquote":GOTO READ_MACRO + IF T$="~" THEN B$="unquote":GOTO READ_MACRO + IF T$="~@" THEN B$="splice-unquote":GOTO READ_MACRO + IF T$="^" THEN B$="with-meta":GOTO READ_MACRO + IF T$="@" THEN B$="deref":GOTO READ_MACRO C$=MID$(T$,1,1) REM PRINT "C$: ["+C$+"]("+STR$(ASC(C$))+")" IF (C$=";") THEN R=0:GOSUB SKIP_TO_EOL:GOTO READ_FORM @@ -108,7 +108,7 @@ READ_FORM: REM 0 for the call and then restored afterwards. X=X+2:X%(X-1)=(T$="^"):X%(X)=SD: REM push macro type and SD - REM AS$ is set above + REM B$ is set above T=5:GOSUB STRING:X=X+1:X%(X)=R SD=0:GOSUB READ_FORM:X=X+1:X%(X)=R @@ -116,18 +116,18 @@ READ_FORM: IF X%(X-3) THEN GOTO READ_MACRO_3 READ_MACRO_2: - B2=X%(X-1):B1=X%(X):GOSUB LIST2 + B=X%(X-1):A=X%(X):GOSUB LIST2 GOTO READ_MACRO_DONE READ_MACRO_3: SD=0:GOSUB READ_FORM - B3=X%(X-1):B2=R:B1=X%(X):GOSUB LIST3 - AY=B3:GOSUB RELEASE + C=X%(X-1):B=R:A=X%(X):GOSUB LIST3 + AY=C:GOSUB RELEASE READ_MACRO_DONE: REM release values, list has ownership - AY=B2:GOSUB RELEASE - AY=B1:GOSUB RELEASE + AY=B:GOSUB RELEASE + AY=A:GOSUB RELEASE SD=X%(X-2):X=X-4: REM get SD and pop the stack T$="": REM necessary to prevent unexpected EOF errors @@ -135,24 +135,24 @@ READ_FORM: READ_STRING: REM PRINT "READ_STRING" T7$=MID$(T$,LEN(T$),1) - IF T7$<>CHR$(34) THEN ER$="expected '"+CHR$(34)+"'":GOTO READ_FORM_ABORT + IF T7$<>CHR$(34) THEN E$="expected '"+CHR$(34)+"'":GOTO READ_FORM_ABORT R$=MID$(T$,2,LEN(T$)-2) S1$=CHR$(92)+CHR$(34):S2$=CHR$(34):GOSUB REPLACE: REM unescape quotes S1$=CHR$(92)+"n":S2$=CHR$(13):GOSUB REPLACE: REM unescape newlines S1$=CHR$(92)+CHR$(92):S2$=CHR$(92):GOSUB REPLACE: REM unescape backslashes REM intern string value - AS$=R$:T=4:GOSUB STRING + B$=R$:T=4:GOSUB STRING GOTO READ_FORM_DONE READ_KEYWORD: R$=CHR$(127)+MID$(T$,2,LEN(T$)-1) - AS$=R$:T=4:GOSUB STRING + B$=R$:T=4:GOSUB STRING GOTO READ_FORM_DONE READ_SYMBOL_MAYBE: C$=MID$(T$,2,1) IF C$>="0" AND C$<="9" THEN GOTO READ_NUMBER READ_SYMBOL: REM PRINT "READ_SYMBOL" - AS$=T$:T=5:GOSUB STRING + B$=T$:T=5:GOSUB STRING GOTO READ_FORM_DONE READ_SEQ: @@ -178,8 +178,8 @@ READ_FORM: READ_SEQ_END: REM PRINT "READ_SEQ_END" - IF SD=0 THEN ER$="unexpected '"+C$+"'":GOTO READ_FORM_ABORT - IF X%(X-1)<>T THEN ER$="sequence mismatch":GOTO READ_FORM_ABORT + IF SD=0 THEN E$="unexpected '"+C$+"'":GOTO READ_FORM_ABORT + IF X%(X-1)<>T THEN E$="sequence mismatch":GOTO READ_FORM_ABORT SD=SD-1: REM decrease read sequence depth R=X%(X-2): REM ptr to start of sequence to return T=X%(X-1): REM type prior to recur @@ -245,7 +245,7 @@ READ_FILE: RS=0: REM file read state (1: EOF) SD=0: REM sequence read depth #cbm OPEN 2,8,0,A$ - #qbasic IF NOT _FILEEXISTS(A$) THEN ER=-1:ER$="File not found":RETURN + #qbasic IF NOT _FILEEXISTS(A$) THEN ER=-1:E$="File not found":RETURN #qbasic OPEN A$ FOR INPUT AS #2 REM READ_FILE_CHUNK adds terminating ")" A$="(do ":GOSUB READ_FORM diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index 74a364f219..5e2feaa753 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -19,7 +19,7 @@ END SUB REM PRINT(A) -> R$ MAL_PRINT: - AZ=A:PR=1:GOSUB PR_STR + AZ=A:B=1:GOSUB PR_STR RETURN REM REP(A$) -> R$ @@ -60,7 +60,7 @@ MAIN: END PRINT_ERROR: - PRINT "Error: "+ER$ - ER=-2:ER$="" + PRINT "Error: "+E$ + ER=-2:E$="" RETURN diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index 1a1111a163..ae1c535374 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -35,7 +35,7 @@ SUB EVAL_AST EVAL_AST_SYMBOL: H=E:K=A:GOSUB HASHMAP_GET GOSUB DEREF_R - IF T3=0 THEN ER=-1:ER$="'"+S$(Z%(A,1))+"' not found":GOTO EVAL_AST_RETURN + IF T3=0 THEN ER=-1:E$="'"+S$(Z%(A,1))+"' not found":GOTO EVAL_AST_RETURN Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN @@ -119,7 +119,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN - REM AZ=A:PR=1:GOSUB PR_STR + REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A @@ -144,7 +144,7 @@ SUB EVAL AR=Z%(R,1): REM rest R=F:GOSUB DEREF_R:F=R - IF (Z%(F,0)AND 31)<>9 THEN ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN + IF (Z%(F,0)AND 31)<>9 THEN ER=-1:E$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION AY=R3:GOSUB RELEASE GOTO EVAL_RETURN @@ -154,8 +154,8 @@ SUB EVAL LV=LV-1: REM track basic return stack level REM trigger GC - #cbm TA=FRE(0) - #qbasic TA=0 + #cbm T=FRE(0) + #qbasic T=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -170,18 +170,18 @@ DO_FUNCTION: AR$=R$ REM Get the function number - FF=Z%(F,1) + G=Z%(F,1) REM Get argument values R=AR+1:GOSUB DEREF_R:AA=Z%(R,1) R=Z%(AR,1)+1:GOSUB DEREF_R:AB=Z%(R,1) REM Switch on the function number - IF FF=1 THEN GOTO DO_ADD - IF FF=2 THEN GOTO DO_SUB - IF FF=3 THEN GOTO DO_MULT - IF FF=4 THEN GOTO DO_DIV - ER=-1:ER$="unknown function"+STR$(FF):RETURN + IF G=1 THEN GOTO DO_ADD + IF G=2 THEN GOTO DO_SUB + IF G=3 THEN GOTO DO_MULT + IF G=4 THEN GOTO DO_DIV + ER=-1:E$="unknown function"+STR$(G):RETURN DO_ADD: T=2:L=AA+AB:GOSUB ALLOC @@ -201,7 +201,7 @@ DO_FUNCTION: REM PRINT(A) -> R$ MAL_PRINT: - AZ=A:PR=1:GOSUB PR_STR + AZ=A:B=1:GOSUB PR_STR RETURN REM REP(A$) -> R$ @@ -237,19 +237,19 @@ MAIN: REM + function A=1:GOSUB NATIVE_FUNCTION - H=D:K$="+":V=R:GOSUB ASSOC1_S:D=R + H=D:K$="+":C=R:GOSUB ASSOC1_S:D=R REM - function A=2:GOSUB NATIVE_FUNCTION - H=D:K$="-":V=R:GOSUB ASSOC1_S:D=R + H=D:K$="-":C=R:GOSUB ASSOC1_S:D=R REM * function A=3:GOSUB NATIVE_FUNCTION - H=D:K$="*":V=R:GOSUB ASSOC1_S:D=R + H=D:K$="*":C=R:GOSUB ASSOC1_S:D=R REM / function A=4:GOSUB NATIVE_FUNCTION - H=D:K$="/":V=R:GOSUB ASSOC1_S:D=R + H=D:K$="/":C=R:GOSUB ASSOC1_S:D=R ZT=ZI: REM top of memory after base repl_env @@ -268,7 +268,7 @@ MAIN: END PRINT_ERROR: - PRINT "Error: "+ER$ - ER=-2:ER$="" + PRINT "Error: "+E$ + ER=-2:E$="" RETURN diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index d2e3cae0eb..95c33f1eb0 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -121,7 +121,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN - REM AZ=A:PR=1:GOSUB PR_STR + REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A @@ -169,7 +169,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1:V=R:GOSUB ENV_SET + K=A1:C=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: @@ -178,7 +178,7 @@ SUB EVAL X=X+1:X%(X)=A2: REM push/save A2 REM create new environment with outer as current environment - O=E:GOSUB ENV_NEW + C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE @@ -191,7 +191,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:V=R:GOSUB ENV_SET + K=A1+1:C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -212,13 +212,13 @@ SUB EVAL AR=Z%(R,1): REM rest R=F:GOSUB DEREF_R:F=R - IF (Z%(F,0)AND 31)<>9 THEN ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN + IF (Z%(F,0)AND 31)<>9 THEN ER=-1:E$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION AY=R3:GOSUB RELEASE GOTO EVAL_RETURN EVAL_RETURN: - REM AZ=R: PR=1: GOSUB PR_STR + REM AZ=R: B=1: GOSUB PR_STR REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack @@ -227,8 +227,8 @@ SUB EVAL LV=LV-1: REM track basic return stack level REM trigger GC - #cbm TA=FRE(0) - #qbasic TA=0 + #cbm T=FRE(0) + #qbasic T=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -243,18 +243,18 @@ DO_FUNCTION: AR$=R$ REM Get the function number - FF=Z%(F,1) + G=Z%(F,1) REM Get argument values R=AR+1:GOSUB DEREF_R:AA=Z%(R,1) R=Z%(AR,1)+1:GOSUB DEREF_R:AB=Z%(R,1) REM Switch on the function number - IF FF=1 THEN GOTO DO_ADD - IF FF=2 THEN GOTO DO_SUB - IF FF=3 THEN GOTO DO_MULT - IF FF=4 THEN GOTO DO_DIV - ER=-1:ER$="unknown function"+STR$(FF):RETURN + IF G=1 THEN GOTO DO_ADD + IF G=2 THEN GOTO DO_SUB + IF G=3 THEN GOTO DO_MULT + IF G=4 THEN GOTO DO_DIV + ER=-1:E$="unknown function"+STR$(G):RETURN DO_ADD: T=2:L=AA+AB:GOSUB ALLOC @@ -274,7 +274,7 @@ DO_FUNCTION: REM PRINT(A) -> R$ MAL_PRINT: - AZ=A:PR=1:GOSUB PR_STR + AZ=A:B=1:GOSUB PR_STR RETURN REM REP(A$) -> R$ @@ -306,24 +306,24 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:D=R + C=-1:GOSUB ENV_NEW:D=R E=D REM + function A=1:GOSUB NATIVE_FUNCTION - K$="+":V=R:GOSUB ENV_SET_S + K$="+":C=R:GOSUB ENV_SET_S REM - function A=2:GOSUB NATIVE_FUNCTION - K$="-":V=R:GOSUB ENV_SET_S + K$="-":C=R:GOSUB ENV_SET_S REM * function A=3:GOSUB NATIVE_FUNCTION - K$="*":V=R:GOSUB ENV_SET_S + K$="*":C=R:GOSUB ENV_SET_S REM / function A=4:GOSUB NATIVE_FUNCTION - K$="/":V=R:GOSUB ENV_SET_S + K$="/":C=R:GOSUB ENV_SET_S ZT=ZI: REM top of memory after base repl_env @@ -342,7 +342,7 @@ MAIN: END PRINT_ERROR: - PRINT "Error: "+ER$ - ER=-2:ER$="" + PRINT "Error: "+E$ + ER=-2:E$="" RETURN diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index f25cba2d3e..8e47bf202d 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -122,7 +122,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN - REM AZ=A:PR=1:GOSUB PR_STR + REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A @@ -173,7 +173,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1:V=R:GOSUB ENV_SET + K=A1:C=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: @@ -182,7 +182,7 @@ SUB EVAL X=X+1:X%(X)=A2: REM push/save A2 REM create new environment with outer as current environment - O=E:GOSUB ENV_NEW + C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE @@ -195,7 +195,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:V=R:GOSUB ENV_SET + K=A1+1:C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -240,7 +240,7 @@ SUB EVAL EVAL_FN: GOSUB EVAL_GET_A2: REM set A1 and A2 - A=A2:P=A1:GOSUB MAL_FUNCTION + A=A2:B=A1:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: @@ -265,7 +265,7 @@ SUB EVAL REM if error, pop and return f/args for release by caller R=X%(X):X=X-1 - ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN + ER=-1:E$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: REM regular function @@ -282,7 +282,7 @@ SUB EVAL E4=E: REM save the current environment for release REM create new environ using env stored with function - O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS + C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and @@ -302,7 +302,7 @@ SUB EVAL E=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: - REM AZ=R: PR=1: GOSUB PR_STR + REM AZ=R: B=1: GOSUB PR_STR REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack @@ -314,8 +314,8 @@ SUB EVAL GOSUB RELEASE_PEND REM trigger GC - #cbm TA=FRE(0) - #qbasic TA=0 + #cbm T=FRE(0) + #qbasic T=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -324,7 +324,7 @@ END SUB REM PRINT(A) -> R$ MAL_PRINT: - AZ=A:PR=1:GOSUB PR_STR + AZ=A:B=1:GOSUB PR_STR RETURN REM RE(A$) -> R @@ -372,7 +372,7 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:D=R + C=-1:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -398,7 +398,7 @@ MAIN: END PRINT_ERROR: - PRINT "Error: "+ER$ - ER=-2:ER$="" + PRINT "Error: "+E$ + ER=-2:E$="" RETURN diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index e4d4dfd6e2..b56af813af 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -125,7 +125,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN - REM AZ=A:PR=1:GOSUB PR_STR + REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A @@ -176,7 +176,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1:V=R:GOSUB ENV_SET + K=A1:C=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: @@ -187,7 +187,7 @@ SUB EVAL X=X+1:X%(X)=E: REM push env for for later release REM create new environment with outer as current environment - O=E:GOSUB ENV_NEW + C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE @@ -200,7 +200,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:V=R:GOSUB ENV_SET + K=A1+1:C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -258,7 +258,7 @@ SUB EVAL EVAL_FN: GOSUB EVAL_GET_A2: REM set A1 and A2 - A=A2:P=A1:GOSUB MAL_FUNCTION + A=A2:B=A1:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: @@ -283,7 +283,7 @@ SUB EVAL REM if error, pop and return f/args for release by caller R=X%(X):X=X-1 - ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN + ER=-1:E$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: REM regular function @@ -300,7 +300,7 @@ SUB EVAL E4=E: REM save the current environment for release REM create new environ using env stored with function - O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS + C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and @@ -320,7 +320,7 @@ SUB EVAL E=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: - REM AZ=R: PR=1: GOSUB PR_STR + REM AZ=R: B=1: GOSUB PR_STR REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack @@ -332,8 +332,8 @@ SUB EVAL GOSUB RELEASE_PEND REM trigger GC - #cbm TA=FRE(0) - #qbasic TA=0 + #cbm T=FRE(0) + #qbasic T=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -342,7 +342,7 @@ END SUB REM PRINT(A) -> R$ MAL_PRINT: - AZ=A:PR=1:GOSUB PR_STR + AZ=A:B=1:GOSUB PR_STR RETURN REM RE(A$) -> R @@ -390,7 +390,7 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:D=R + C=-1:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -416,7 +416,7 @@ MAIN: END PRINT_ERROR: - PRINT "Error: "+ER$ - ER=-2:ER$="" + PRINT "Error: "+E$ + ER=-2:E$="" RETURN diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index a5eabb6e99..6b4f23414b 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -125,7 +125,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN - REM AZ=A:PR=1:GOSUB PR_STR + REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A @@ -176,7 +176,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1:V=R:GOSUB ENV_SET + K=A1:C=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: @@ -187,7 +187,7 @@ SUB EVAL X=X+1:X%(X)=E: REM push env for for later release REM create new environment with outer as current environment - O=E:GOSUB ENV_NEW + C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE @@ -200,7 +200,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:V=R:GOSUB ENV_SET + K=A1+1:C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -258,7 +258,7 @@ SUB EVAL EVAL_FN: GOSUB EVAL_GET_A2: REM set A1 and A2 - A=A2:P=A1:GOSUB MAL_FUNCTION + A=A2:B=A1:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: @@ -283,7 +283,7 @@ SUB EVAL REM if error, pop and return f/args for release by caller R=X%(X):X=X-1 - ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN + ER=-1:E$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: REM regular function @@ -300,7 +300,7 @@ SUB EVAL E4=E: REM save the current environment for release REM create new environ using env stored with function - O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS + C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and @@ -320,7 +320,7 @@ SUB EVAL E=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: - REM AZ=R: PR=1: GOSUB PR_STR + REM AZ=R: B=1: GOSUB PR_STR REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack @@ -332,8 +332,8 @@ SUB EVAL GOSUB RELEASE_PEND REM trigger GC - #cbm TA=FRE(0) - #qbasic TA=0 + #cbm T=FRE(0) + #qbasic T=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -342,7 +342,7 @@ END SUB REM PRINT(A) -> R$ MAL_PRINT: - AZ=A:PR=1:GOSUB PR_STR + AZ=A:B=1:GOSUB PR_STR RETURN REM RE(A$) -> R @@ -390,7 +390,7 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:D=R + C=-1:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -443,7 +443,7 @@ MAIN: END PRINT_ERROR: - PRINT "Error: "+ER$ - ER=-2:ER$="" + PRINT "Error: "+E$ + ER=-2:E$="" RETURN diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index fb749e552f..95200809e6 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -23,9 +23,9 @@ SUB QUASIQUOTE QQ_QUOTE: REM ['quote, ast] - AS$="quote":T=5:GOSUB STRING - B2=R:B1=A:GOSUB LIST2 - AY=B2:GOSUB RELEASE + B$="quote":T=5:GOSUB STRING + B=R:A=A:GOSUB LIST2 + AY=B:GOSUB RELEASE GOTO QQ_DONE @@ -60,12 +60,12 @@ SUB QUASIQUOTE IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1)+1:GOSUB DEREF_B:B2=B - AS$="concat":T=5:GOSUB STRING:B3=R - B1=T6:GOSUB LIST3 + B=Z%(A,1)+1:GOSUB DEREF_B:B=B + B$="concat":T=5:GOSUB STRING:C=R + A=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1:GOSUB RELEASE - AY=B3:GOSUB RELEASE + AY=A:GOSUB RELEASE + AY=C:GOSUB RELEASE GOTO QQ_DONE QQ_DEFAULT: @@ -75,16 +75,16 @@ SUB QUASIQUOTE X=X+1:X%(X)=T6 REM A set above to ast[0] CALL QUASIQUOTE - B2=R + B=R REM pop T6 off the stack T6=X%(X):X=X-1 - AS$="cons":T=5:GOSUB STRING:B3=R - B1=T6:GOSUB LIST3 + B$="cons":T=5:GOSUB STRING:C=R + A=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1:GOSUB RELEASE - AY=B2:GOSUB RELEASE - AY=B3:GOSUB RELEASE + AY=A:GOSUB RELEASE + AY=B:GOSUB RELEASE + AY=C:GOSUB RELEASE QQ_DONE: END SUB @@ -200,7 +200,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN - REM AZ=A:PR=1:GOSUB PR_STR + REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A @@ -253,7 +253,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1:V=R:GOSUB ENV_SET + K=A1:C=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: @@ -264,7 +264,7 @@ SUB EVAL X=X+1:X%(X)=E: REM push env for for later release REM create new environment with outer as current environment - O=E:GOSUB ENV_NEW + C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE @@ -277,7 +277,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:V=R:GOSUB ENV_SET + K=A1+1:C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -349,7 +349,7 @@ SUB EVAL EVAL_FN: GOSUB EVAL_GET_A2: REM set A1 and A2 - A=A2:P=A1:GOSUB MAL_FUNCTION + A=A2:B=A1:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: @@ -374,7 +374,7 @@ SUB EVAL REM if error, pop and return f/args for release by caller R=X%(X):X=X-1 - ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN + ER=-1:E$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: REM regular function @@ -391,7 +391,7 @@ SUB EVAL E4=E: REM save the current environment for release REM create new environ using env stored with function - O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS + C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and @@ -411,7 +411,7 @@ SUB EVAL E=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: - REM AZ=R: PR=1: GOSUB PR_STR + REM AZ=R: B=1: GOSUB PR_STR REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack @@ -423,8 +423,8 @@ SUB EVAL GOSUB RELEASE_PEND REM trigger GC - #cbm TA=FRE(0) - #qbasic TA=0 + #cbm T=FRE(0) + #qbasic T=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -433,7 +433,7 @@ END SUB REM PRINT(A) -> R$ MAL_PRINT: - AZ=A:PR=1:GOSUB PR_STR + AZ=A:B=1:GOSUB PR_STR RETURN REM RE(A$) -> R @@ -481,7 +481,7 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:D=R + C=-1:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -534,7 +534,7 @@ MAIN: END PRINT_ERROR: - PRINT "Error: "+ER$ - ER=-2:ER$="" + PRINT "Error: "+E$ + ER=-2:E$="" RETURN diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index c59e23830a..bf824edabe 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -25,9 +25,9 @@ SUB QUASIQUOTE QQ_QUOTE: REM ['quote, ast] - AS$="quote":T=5:GOSUB STRING - B2=R:B1=A:GOSUB LIST2 - AY=B2:GOSUB RELEASE + B$="quote":T=5:GOSUB STRING + B=R:A=A:GOSUB LIST2 + AY=B:GOSUB RELEASE GOTO QQ_DONE @@ -62,12 +62,12 @@ SUB QUASIQUOTE IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1)+1:GOSUB DEREF_B:B2=B - AS$="concat":T=5:GOSUB STRING:B3=R - B1=T6:GOSUB LIST3 + B=Z%(A,1)+1:GOSUB DEREF_B:B=B + B$="concat":T=5:GOSUB STRING:C=R + A=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1:GOSUB RELEASE - AY=B3:GOSUB RELEASE + AY=A:GOSUB RELEASE + AY=C:GOSUB RELEASE GOTO QQ_DONE QQ_DEFAULT: @@ -77,16 +77,16 @@ SUB QUASIQUOTE X=X+1:X%(X)=T6 REM A set above to ast[0] CALL QUASIQUOTE - B2=R + B=R REM pop T6 off the stack T6=X%(X):X=X-1 - AS$="cons":T=5:GOSUB STRING:B3=R - B1=T6:GOSUB LIST3 + B$="cons":T=5:GOSUB STRING:C=R + A=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1:GOSUB RELEASE - AY=B2:GOSUB RELEASE - AY=B3:GOSUB RELEASE + AY=A:GOSUB RELEASE + AY=B:GOSUB RELEASE + AY=C:GOSUB RELEASE QQ_DONE: END SUB @@ -236,7 +236,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN - REM AZ=A:PR=1:GOSUB PR_STR + REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A @@ -297,7 +297,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1:V=R:GOSUB ENV_SET + K=A1:C=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: @@ -308,7 +308,7 @@ SUB EVAL X=X+1:X%(X)=E: REM push env for for later release REM create new environment with outer as current environment - O=E:GOSUB ENV_NEW + C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE @@ -321,7 +321,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:V=R:GOSUB ENV_SET + K=A1+1:C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -382,7 +382,7 @@ SUB EVAL Z%(R,0)=Z%(R,0)+1 REM set A1 in env to A2 - K=A1:V=R:GOSUB ENV_SET + K=A1:C=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_MACROEXPAND: @@ -418,7 +418,7 @@ SUB EVAL EVAL_FN: GOSUB EVAL_GET_A2: REM set A1 and A2 - A=A2:P=A1:GOSUB MAL_FUNCTION + A=A2:B=A1:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: @@ -443,7 +443,7 @@ SUB EVAL REM if error, pop and return f/args for release by caller R=X%(X):X=X-1 - ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN + ER=-1:E$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: REM regular function @@ -460,7 +460,7 @@ SUB EVAL E4=E: REM save the current environment for release REM create new environ using env stored with function - O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS + C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and @@ -480,7 +480,7 @@ SUB EVAL E=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: - REM AZ=R: PR=1: GOSUB PR_STR + REM AZ=R: B=1: GOSUB PR_STR REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack @@ -492,8 +492,8 @@ SUB EVAL GOSUB RELEASE_PEND REM trigger GC - #cbm TA=FRE(0) - #qbasic TA=0 + #cbm T=FRE(0) + #qbasic T=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -502,7 +502,7 @@ END SUB REM PRINT(A) -> R$ MAL_PRINT: - AZ=A:PR=1:GOSUB PR_STR + AZ=A:B=1:GOSUB PR_STR RETURN REM RE(A$) -> R @@ -550,7 +550,7 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:D=R + C=-1:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -612,7 +612,7 @@ MAIN: END PRINT_ERROR: - PRINT "Error: "+ER$ - ER=-2:ER$="" + PRINT "Error: "+E$ + ER=-2:E$="" RETURN diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index 43cb8dbe65..d72e65d8b5 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -25,9 +25,9 @@ SUB QUASIQUOTE QQ_QUOTE: REM ['quote, ast] - AS$="quote":T=5:GOSUB STRING - B2=R:B1=A:GOSUB LIST2 - AY=B2:GOSUB RELEASE + B$="quote":T=5:GOSUB STRING + B=R:A=A:GOSUB LIST2 + AY=B:GOSUB RELEASE GOTO QQ_DONE @@ -62,12 +62,12 @@ SUB QUASIQUOTE IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1)+1:GOSUB DEREF_B:B2=B - AS$="concat":T=5:GOSUB STRING:B3=R - B1=T6:GOSUB LIST3 + B=Z%(A,1)+1:GOSUB DEREF_B:B=B + B$="concat":T=5:GOSUB STRING:C=R + A=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1:GOSUB RELEASE - AY=B3:GOSUB RELEASE + AY=A:GOSUB RELEASE + AY=C:GOSUB RELEASE GOTO QQ_DONE QQ_DEFAULT: @@ -77,16 +77,16 @@ SUB QUASIQUOTE X=X+1:X%(X)=T6 REM A set above to ast[0] CALL QUASIQUOTE - B2=R + B=R REM pop T6 off the stack T6=X%(X):X=X-1 - AS$="cons":T=5:GOSUB STRING:B3=R - B1=T6:GOSUB LIST3 + B$="cons":T=5:GOSUB STRING:C=R + A=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1:GOSUB RELEASE - AY=B2:GOSUB RELEASE - AY=B3:GOSUB RELEASE + AY=A:GOSUB RELEASE + AY=B:GOSUB RELEASE + AY=C:GOSUB RELEASE QQ_DONE: END SUB @@ -236,7 +236,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN - REM AZ=A:PR=1:GOSUB PR_STR + REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A @@ -298,7 +298,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1:V=R:GOSUB ENV_SET + K=A1:C=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: @@ -309,7 +309,7 @@ SUB EVAL X=X+1:X%(X)=E: REM push env for for later release REM create new environment with outer as current environment - O=E:GOSUB ENV_NEW + C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE @@ -322,7 +322,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:V=R:GOSUB ENV_SET + K=A1+1:C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -383,7 +383,7 @@ SUB EVAL Z%(R,0)=Z%(R,0)+1 REM set A1 in env to A2 - K=A1:V=R:GOSUB ENV_SET + K=A1:C=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_MACROEXPAND: @@ -408,20 +408,20 @@ SUB EVAL IF ER=-2 OR Z%(A,1)=0 THEN GOTO EVAL_RETURN REM create environment for the catch block eval - O=E:GOSUB ENV_NEW:E=R + C=E:GOSUB ENV_NEW:E=R GOSUB EVAL_GET_A2: REM set A1 and A2 A=A2:GOSUB EVAL_GET_A2: REM set A1 and A2 from catch block REM create object for ER=-1 type raw string errors - IF ER=-1 THEN AS$=ER$:T=4:GOSUB STRING:ER=R:Z%(R,0)=Z%(R,0)+32 + IF ER=-1 THEN B$=E$:T=4:GOSUB STRING:ER=R:Z%(R,0)=Z%(R,0)+32 REM bind the catch symbol to the error object - K=A1:V=ER:GOSUB ENV_SET + K=A1:C=ER:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, env took ownership REM unset error for catch eval - ER=-2:ER$="" + ER=-2:E$="" A=A2:CALL EVAL @@ -450,7 +450,7 @@ SUB EVAL EVAL_FN: GOSUB EVAL_GET_A2: REM set A1 and A2 - A=A2:P=A1:GOSUB MAL_FUNCTION + A=A2:B=A1:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: @@ -475,7 +475,7 @@ SUB EVAL REM if error, pop and return f/args for release by caller R=X%(X):X=X-1 - ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN + ER=-1:E$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: REM regular function @@ -492,7 +492,7 @@ SUB EVAL E4=E: REM save the current environment for release REM create new environ using env stored with function - O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS + C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and @@ -512,7 +512,7 @@ SUB EVAL E=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: - REM AZ=R: PR=1: GOSUB PR_STR + REM AZ=R: B=1: GOSUB PR_STR REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack @@ -524,8 +524,8 @@ SUB EVAL GOSUB RELEASE_PEND REM trigger GC - #cbm TA=FRE(0) - #qbasic TA=0 + #cbm T=FRE(0) + #qbasic T=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -534,7 +534,7 @@ END SUB REM PRINT(A) -> R$ MAL_PRINT: - AZ=A:PR=1:GOSUB PR_STR + AZ=A:B=1:GOSUB PR_STR RETURN REM RE(A$) -> R @@ -582,7 +582,7 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:D=R + C=-1:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -645,8 +645,8 @@ MAIN: PRINT_ERROR: REM if the error is an object, then print and free it - IF ER>=0 THEN AZ=ER:PR=0:GOSUB PR_STR:ER$=R$:AY=ER:GOSUB RELEASE - PRINT "Error: "+ER$ - ER=-2:ER$="" + IF ER>=0 THEN AZ=ER:B=0:GOSUB PR_STR:E$=R$:AY=ER:GOSUB RELEASE + PRINT "Error: "+E$ + ER=-2:E$="" RETURN diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index f63ca75ad6..98994db80e 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -25,9 +25,9 @@ SUB QUASIQUOTE QQ_QUOTE: REM ['quote, ast] - AS$="quote":T=5:GOSUB STRING - B2=R:B1=A:GOSUB LIST2 - AY=B2:GOSUB RELEASE + B$="quote":T=5:GOSUB STRING + B=R:A=A:GOSUB LIST2 + AY=B:GOSUB RELEASE GOTO QQ_DONE @@ -62,12 +62,12 @@ SUB QUASIQUOTE IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1)+1:GOSUB DEREF_B:B2=B - AS$="concat":T=5:GOSUB STRING:B3=R - B1=T6:GOSUB LIST3 + B=Z%(A,1)+1:GOSUB DEREF_B:B=B + B$="concat":T=5:GOSUB STRING:C=R + A=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1:GOSUB RELEASE - AY=B3:GOSUB RELEASE + AY=A:GOSUB RELEASE + AY=C:GOSUB RELEASE GOTO QQ_DONE QQ_DEFAULT: @@ -77,16 +77,16 @@ SUB QUASIQUOTE X=X+1:X%(X)=T6 REM A set above to ast[0] CALL QUASIQUOTE - B2=R + B=R REM pop T6 off the stack T6=X%(X):X=X-1 - AS$="cons":T=5:GOSUB STRING:B3=R - B1=T6:GOSUB LIST3 + B$="cons":T=5:GOSUB STRING:C=R + A=T6:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership - AY=B1:GOSUB RELEASE - AY=B2:GOSUB RELEASE - AY=B3:GOSUB RELEASE + AY=A:GOSUB RELEASE + AY=B:GOSUB RELEASE + AY=C:GOSUB RELEASE QQ_DONE: END SUB @@ -236,7 +236,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN - REM AZ=A:PR=1:GOSUB PR_STR + REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" GOSUB DEREF_A @@ -298,7 +298,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN REM set a1 in env to a2 - K=A1:V=R:GOSUB ENV_SET + K=A1:C=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_LET: @@ -309,7 +309,7 @@ SUB EVAL X=X+1:X%(X)=E: REM push env for for later release REM create new environment with outer as current environment - O=E:GOSUB ENV_NEW + C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE @@ -322,7 +322,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:V=R:GOSUB ENV_SET + K=A1+1:C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -383,7 +383,7 @@ SUB EVAL Z%(R,0)=Z%(R,0)+1 REM set A1 in env to A2 - K=A1:V=R:GOSUB ENV_SET + K=A1:C=R:GOSUB ENV_SET GOTO EVAL_RETURN EVAL_MACROEXPAND: @@ -408,20 +408,20 @@ SUB EVAL IF ER=-2 OR Z%(A,1)=0 THEN GOTO EVAL_RETURN REM create environment for the catch block eval - O=E:GOSUB ENV_NEW:E=R + C=E:GOSUB ENV_NEW:E=R GOSUB EVAL_GET_A2: REM set A1 and A2 A=A2:GOSUB EVAL_GET_A2: REM set A1 and A2 from catch block REM create object for ER=-1 type raw string errors - IF ER=-1 THEN AS$=ER$:T=4:GOSUB STRING:ER=R:Z%(R,0)=Z%(R,0)+32 + IF ER=-1 THEN B$=E$:T=4:GOSUB STRING:ER=R:Z%(R,0)=Z%(R,0)+32 REM bind the catch symbol to the error object - K=A1:V=ER:GOSUB ENV_SET + K=A1:C=ER:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, env took ownership REM unset error for catch eval - ER=-2:ER$="" + ER=-2:E$="" A=A2:CALL EVAL @@ -450,7 +450,7 @@ SUB EVAL EVAL_FN: GOSUB EVAL_GET_A2: REM set A1 and A2 - A=A2:P=A1:GOSUB MAL_FUNCTION + A=A2:B=A1:GOSUB MAL_FUNCTION GOTO EVAL_RETURN EVAL_INVOKE: @@ -475,7 +475,7 @@ SUB EVAL REM if error, pop and return f/args for release by caller R=X%(X):X=X-1 - ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN + ER=-1:E$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: REM regular function @@ -492,7 +492,7 @@ SUB EVAL E4=E: REM save the current environment for release REM create new environ using env stored with function - O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS + C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and @@ -512,7 +512,7 @@ SUB EVAL E=R:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_RETURN: - REM AZ=R: PR=1: GOSUB PR_STR + REM AZ=R: B=1: GOSUB PR_STR REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack @@ -524,8 +524,8 @@ SUB EVAL GOSUB RELEASE_PEND REM trigger GC - #cbm TA=FRE(0) - #qbasic TA=0 + #cbm T=FRE(0) + #qbasic T=0 REM pop A and E off the stack E=X%(X-1):A=X%(X):X=X-2 @@ -534,7 +534,7 @@ END SUB REM PRINT(A) -> R$ MAL_PRINT: - AZ=A:PR=1:GOSUB PR_STR + AZ=A:B=1:GOSUB PR_STR RETURN REM RE(A$) -> R @@ -582,7 +582,7 @@ MAIN: LV=0 REM create repl_env - O=-1:GOSUB ENV_NEW:D=R + C=-1:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -613,8 +613,8 @@ MAIN: GOSUB RE:AY=R:GOSUB RELEASE A$="(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs)" - A$=A$+" (let* (condvar (gensym)) `(let* (~condvar ~(first xs))" - A$=A$+" (if ~condvar ~condvar (or ~@(rest xs)))))))))" + A$=A$+" (let* (c (gensym)) `(let* (~c ~(first xs))" + A$=A$+" (if ~c ~c (or ~@(rest xs)))))))))" GOSUB RE:AY=R:GOSUB RELEASE REM load the args file @@ -663,8 +663,8 @@ MAIN: PRINT_ERROR: REM if the error is an object, then print and free it - IF ER>=0 THEN AZ=ER:PR=0:GOSUB PR_STR:ER$=R$:AY=ER:GOSUB RELEASE - PRINT "Error: "+ER$ - ER=-2:ER$="" + IF ER>=0 THEN AZ=ER:B=0:GOSUB PR_STR:E$=R$:AY=ER:GOSUB RELEASE + PRINT "Error: "+E$ + ER=-2:E$="" RETURN diff --git a/basic/types.in.bas b/basic/types.in.bas index 847ade5b17..0a70f234cc 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -25,20 +25,20 @@ REM metadata 16-31 -> Z% index of object with this metadata REM 14 -> Z% index of metdata object INIT_MEMORY: - #cbm TA=FRE(0) - #qbasic TA=0 + #cbm T=FRE(0) + #qbasic T=0 - Z1=2048+1024+512: REM Z% (boxed memory) size (4 bytes each) + Z1=2048+1024+512+128: REM Z% (boxed memory) size (4 bytes each) Z2=200: REM S$/S% (string memory) size (3+2 bytes each) Z3=200: REM X% (call stack) size (2 bytes each) Z4=64: REM Y% (release stack) size (4 bytes each) REM global error state REM -2 : no error - REM -1 : string error in ER$ + REM -1 : string error in E$ REM >=0 : pointer to error object ER=-2 - ER$="" + E$="" REM TODO: for performance, define all/most non-array variables here REM so that the array area doesn't have to be shifted down everytime @@ -167,13 +167,13 @@ RELEASE: U6=Z%(AY,0)AND 31: REM type U7=Z%(AY,1): REM main value/reference - REM AZ=AY: PR=1: GOSUB PR_STR + REM AZ=AY: B=1: GOSUB PR_STR REM PRINT "RELEASE AY:"+STR$(AY)+"["+R$+"] (byte0:"+STR$(Z%(AY,0))+")" REM sanity check not already freed - IF (U6)=15 THEN ER=-1:ER$="Free of free memory: "+STR$(AY):RETURN + IF (U6)=15 THEN ER=-1:E$="RELEASE of free: "+STR$(AY):RETURN IF U6=14 THEN GOTO RELEASE_REFERENCE - IF Z%(AY,0)<15 THEN ER=-1:ER$="Free of freed object: "+STR$(AY):RETURN + IF Z%(AY,0)<15 THEN ER=-1:E$="Unowned object: "+STR$(AY):RETURN REM decrease reference count by one Z%(AY,0)=Z%(AY,0)-32 @@ -189,8 +189,6 @@ RELEASE: IF U6>=16 THEN GOTO RELEASE_METADATA IF U6=12 THEN GOTO RELEASE_ATOM IF U6=13 THEN GOTO RELEASE_ENV - IF U6=15 THEN ER=-1:ER$="RELEASE of already freed: "+STR$(AY):RETURN - ER=-1:ER$="RELEASE not defined for type "+STR$(U6):RETURN RELEASE_SIMPLE: REM simple type (no recursing), just call FREE on it @@ -202,14 +200,14 @@ RELEASE: GOTO RELEASE_TOP RELEASE_STRING: REM string type, release interned string, then FREE reference - IF S%(U7)=0 THEN ER=-1:ER$="RELEASE of free string:"+STR$(S%(U7)):RETURN + IF S%(U7)=0 THEN ER=-1:E$="RELEASE of free string:"+STR$(S%(U7)):RETURN S%(U7)=S%(U7)-1 IF S%(U7)=0 THEN S$(U7)="": REM free BASIC string REM free the atom itself GOTO RELEASE_SIMPLE RELEASE_SEQ: IF U7=0 THEN GOTO RELEASE_SIMPLE_2 - IF Z%(AY+1,0)<>14 THEN ER=-1:ER$="invalid list value"+STR$(AY+1):RETURN + IF Z%(AY+1,0)<>14 THEN ER=-1:E$="invalid list value"+STR$(AY+1):RETURN REM add value and next element to stack RC=RC+2:X=X+2 X%(X-1)=Z%(AY+1,1):X%(X)=U7 @@ -292,12 +290,12 @@ EQUAL_Q: X=X+2:X%(X-1)=A:X%(X)=B ED=ED+1 - U1=Z%(A,0)AND 31 - U2=Z%(B,0)AND 31 - IF U1>5 AND U1<8 AND U2>5 AND U2<8 THEN GOTO EQUAL_Q_SEQ - IF U1=8 AND U2=8 THEN GOTO EQUAL_Q_HM + T1=Z%(A,0)AND 31 + T2=Z%(B,0)AND 31 + IF T1>5 AND T1<8 AND T2>5 AND T2<8 THEN GOTO EQUAL_Q_SEQ + IF T1=8 AND T2=8 THEN GOTO EQUAL_Q_HM - IF U1<>U2 OR Z%(A,1)<>Z%(B,1) THEN R=0 + IF T1<>T2 OR Z%(A,1)<>Z%(B,1) THEN R=0 GOTO EQUAL_Q_DONE EQUAL_Q_SEQ: @@ -329,7 +327,7 @@ EQUAL_Q: REM string functions -REM STRING(AS$, T) -> R +REM STRING(B$, T) -> R REM intern string and allocate reference (return Z% index) STRING: IF S=0 THEN GOTO STRING_NOT_FOUND @@ -338,7 +336,7 @@ STRING: I=0 STRING_FIND_LOOP: IF I>S-1 THEN GOTO STRING_NOT_FOUND - IF S%(I)>0 AND AS$=S$(I) THEN GOTO STRING_DONE + IF S%(I)>0 AND B$=S$(I) THEN GOTO STRING_DONE I=I+1 GOTO STRING_FIND_LOOP @@ -357,8 +355,8 @@ STRING: REM fallthrough STRING_SET: -REM IF I>85 THEN PRINT "STRING:"+STR$(I)+" "+AS$ - S$(I)=AS$ +REM IF I>85 THEN PRINT "STRING:"+STR$(I)+" "+B$ + S$(I)=B$ REM fallthrough STRING_DONE: @@ -460,23 +458,23 @@ SLICE: I=I+1 GOTO SLICE_LOOP -REM LIST2(B2,B1) -> R +REM LIST2(B,A) -> R LIST2: - REM last element is 3 (empty list), second element is B1 - T=6:L=3:N=B1:GOSUB ALLOC + REM last element is 3 (empty list), second element is A + T=6:L=3:N=A:GOSUB ALLOC - REM first element is B2 - T=6:L=R:N=B2:GOSUB ALLOC + REM first element is B + T=6:L=R:N=B:GOSUB ALLOC AY=L:GOSUB RELEASE: REM new list takes ownership of previous RETURN -REM LIST3(B3,B2,B1) -> R +REM LIST3(C,B,A) -> R LIST3: GOSUB LIST2 - REM first element is B3 - T=6:L=R:N=B3:GOSUB ALLOC + REM first element is C + T=6:L=R:N=C:GOSUB ALLOC AY=L:GOSUB RELEASE: REM new list takes ownership of previous RETURN @@ -491,24 +489,24 @@ HASHMAP: Z%(R,0)=Z%(R,0)+32 RETURN -REM ASSOC1(H, K, V) -> R +REM ASSOC1(H, K, C) -> R ASSOC1: - REM deref K and V - R=V:GOSUB DEREF_R:V=R + REM deref K and C + R=C:GOSUB DEREF_R:C=R R=K:GOSUB DEREF_R:K=R REM value ptr - T=8:L=H:N=V:GOSUB ALLOC + T=8:L=H:N=C:GOSUB ALLOC AY=L:GOSUB RELEASE: REM we took ownership of previous hashmap REM key ptr T=8:L=R:N=K:GOSUB ALLOC AY=L:GOSUB RELEASE: REM we took ownership of previous hashmap RETURN -REM ASSOC1(H, K$, V) -> R +REM ASSOC1(H, K$, C) -> R ASSOC1_S: REM add the key string - AS$=K$:T=4:GOSUB STRING + B$=K$:T=4:GOSUB STRING K=R:GOSUB ASSOC1 AY=K:GOSUB RELEASE: REM map took ownership of key RETURN @@ -516,7 +514,7 @@ ASSOC1_S: REM HASHMAP_GET(H, K) -> R HASHMAP_GET: H2=H - T1$=S$(Z%(K,1)): REM search key string + B$=S$(Z%(K,1)): REM search key string T3=0: REM whether found or not (for HASHMAP_CONTAINS) R=0 HASHMAP_GET_LOOP: @@ -527,9 +525,8 @@ HASHMAP_GET: HASHMAP_GET_DEREF: IF Z%(T2,0)=14 THEN T2=Z%(T2,1):GOTO HASHMAP_GET_DEREF REM get key string - T2$=S$(Z%(T2,1)) REM if they are equal, we found it - IF T1$=T2$ THEN T3=1:R=Z%(H2,1)+1:RETURN + IF B$=S$(Z%(T2,1)) THEN T3=1:R=Z%(H2,1)+1:RETURN REM skip to next key H2=Z%(Z%(H2,1),1) GOTO HASHMAP_GET_LOOP @@ -548,7 +545,7 @@ NATIVE_FUNCTION: T=9:L=A:GOSUB ALLOC RETURN -REM MAL_FUNCTION(A, P, E) -> R +REM MAL_FUNCTION(A, B, E) -> R MAL_FUNCTION: - T=10:L=A:M=P:N=E:GOSUB ALLOC + T=10:L=A:M=B:N=E:GOSUB ALLOC RETURN diff --git a/basic/variables.txt b/basic/variables.txt index 2335ae2384..5013f22309 100644 --- a/basic/variables.txt +++ b/basic/variables.txt @@ -17,11 +17,9 @@ Y : top element of Y% stack D : root repl environment ER : error type (-2: none, -1: string, >=0: object) -ER$ : error string (ER=-1) +E$ : error string (ER=-1) EZ : READLINE EOF -BI : ENV_NEW_BINDS binds list -EX : ENV_NEW_BINDS expressions list LV : EVAL stack call level/depth RI : reader current string position @@ -30,66 +28,63 @@ RJ : READ_TOKEN current character index Calling arguments/temporaries: -A : common call arguments (especially EVAL, EVAL_AST) -B : common call arguments -C : SLICE argument, READLINE temp. +A : common call argument (especially EVAL, EVAL_AST) +A$ : common call argument (READLINE, reader, string temp, key value) +B : common call argument +B$ : STRING arg (HASHMAP_GET temp) +C : common call argument E : environment (EVAL, EVAL_AST) F : function H : hash map K : hash map key (Z% index) -K$ : hash map key string L : ALLOC* Z%(R,1) default M : ALLOC* Z%(R+1,0) default N : ALLOC* Z%(R+1,1) default -O : outer environment -P : MAL_FUNCTION R : common return value -T : common temp, type -V : hash map value +R$ : common string return value +T : type arg, common temp -B1 : LIST2/LIST3 param -B2 : LIST2/LIST3 param -B3 : LIST3 param -CZ : DO_CONCAT stack position -EF : ENV_FIND cur env ptr +AY : RELEASE/FREE arg +AZ : PR_STR arg P1 : PR_MEMORY, CHECK_FREE_LIST start P2 : PR_MEMORY, CHECK_FREE_LIST end SZ : size argument to ALLOC +S1$ : REPLACE needle +S2$ : REPLACE replacement -Reused/temporaries: +Other temporaries: A0 : EVAL ast elements A1 : EVAL ast elements A2 : EVAL ast elements A3 : EVAL ast elements +CZ : DO_CONCAT stack position ED : EQUAL_Q recursion depth counter RD : PR_OBJECT recursion depth SD : READ_STR sequence read recursion depth + C$ : READ_TOKEN, SKIP_SPACES, SKIP_TO_EOL current character +G : function value ON GOTO switch flag I : STRING, REPLACE, SLICE, PR_MEMORY, PR_OBJECT J : REPLACE S1 : READ_TOKEN in a string? S2 : READ_TOKEN escaped? T$ : READ_* current token string -T1$ : HASHMAP_GET temp -T2$ : HASHMAP_GET temp -T1 : PR_STR, and core DO_KEYS_VALS temp -T2 : -T3 : -T4 : -T5 : -T6 : -T7 : READ_FORM and PR_STR temp -T8 : -T9 : -TA : -U1 : -U2 : -U3 : -U4 : -U6 : +T1 : EQUAL_Q, PR_STR, and core DO_KEYS_VALS temp +T2 : EQUAL_Q, DO_KEY_VALS, HASH_MAP_GET +T3 : HASHMAP_GET temp and return value +T3$ : REPLACE temp +T4 : ENV_FIND temp and return value +T6 : LAST and QUASIQUOTE temp +T7 : READ_FORM temp +T8 : READ_FORM_DONE temp +T9 : PR_STR_SEQ temp +U3 : ALLOC +U4 : ALLOC +U6 : RELEASE +U7 : RELEASE Unused: -G, Q, U, W +O, Q, U, V, W From 935930128c7cf8b5cbfefb20ed05a76cd02b4ef0 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 4 Nov 2016 21:46:45 -0500 Subject: [PATCH 0223/2308] Basic: move logic/release stack to $C000 Uses stack PUSH*/POP*/PEEK* routines instead of direct X% and Y% access. Seems to be about the same performance (maybe a 5% performance hit at most). This gives us a larger stack (1920 2-byte words of $C000 rather than 200 words as before). The release stack at $CF00 stays the same size (64 4-byte addr/level entries). Also saves over 1K or program and array space. So take the opportunity to expand Z% entry space from 3712 to 3950. --- basic/basicpp.py | 19 +++-- basic/core.in.bas | 65 +++++++++------ basic/debug.in.bas | 3 +- basic/printer.in.bas | 17 ++-- basic/reader.in.bas | 64 ++++++++------ basic/step0_repl.in.bas | 12 +-- basic/step1_read_print.in.bas | 2 +- basic/step2_eval.in.bas | 49 ++++++----- basic/step3_env.in.bas | 66 ++++++++------- basic/step4_if_fn_do.in.bas | 91 +++++++++++--------- basic/step5_tco.in.bas | 101 ++++++++++++---------- basic/step6_file.in.bas | 101 ++++++++++++---------- basic/step7_quote.in.bas | 118 ++++++++++++++------------ basic/step8_macros.in.bas | 137 +++++++++++++++--------------- basic/step9_try.in.bas | 139 ++++++++++++++++--------------- basic/stepA_mal.in.bas | 139 ++++++++++++++++--------------- basic/types.in.bas | 152 +++++++++++++++++++++++++++------- basic/variables.txt | 3 +- 18 files changed, 739 insertions(+), 539 deletions(-) diff --git a/basic/basicpp.py b/basic/basicpp.py index f81a1d5e5a..dac2e0ed2b 100755 --- a/basic/basicpp.py +++ b/basic/basicpp.py @@ -40,7 +40,7 @@ def resolve_includes(orig_lines, keep_rems=0): included = {} lines = [] for line in orig_lines: - m = re.match(r"^ *REM \$INCLUDE: '([^']*)' *$", line) + m = re.match(r"^ *REM \$INCLUDE: '([^'\n]*)' *$", line) if m and m.group(1) not in included: f = m.group(1) if f not in included: @@ -57,7 +57,7 @@ def resolve_includes(orig_lines, keep_rems=0): def resolve_mode(orig_lines, mode): lines = [] for line in orig_lines: - m = re.match(r"^ *#([^ ]*) (.*)$", line) + m = re.match(r"^ *#([^ \n]*) *([^\n]*)$", line) if m: if m.group(1) == mode: lines.append(m.group(2)) @@ -88,7 +88,7 @@ def drop_rems(orig_lines): def remove_indent(orig_lines): lines = [] for line in orig_lines: - m = re.match(r"^ *([^ ].*)$", line) + m = re.match(r"^ *([^ \n].*)$", line) lines.append(m.group(1)) return lines @@ -104,6 +104,7 @@ def misc_fixups(orig_lines): text = re.sub(r"\bDIM ", "DIM", text) text = re.sub(r"\OPEN ", "OPEN", text) text = re.sub(r"\bGET ", "GET", text) + text = re.sub(r"\bPOKE ", "POKE", text) # Remove spaces around GOTO/GOSUB/THEN text = re.sub(r" *GOTO *", "GOTO", text) @@ -131,19 +132,19 @@ def finalize(lines, args, mode): for line in src_lines: # Drop labels (track line number for GOTO/GOSUB) - m = re.match(r"^ *([^ ]*): *$", line) + m = re.match(r"^ *([^ :\n]*): *$", line) if m: label = m.groups(1)[0] labels_lines[label] = lnum lines_labels[lnum] = label continue - if re.match(r".*CALL *([^ :]*) *:", line): + if re.match(r".*CALL *([^ :\n]*) *:", line): raise Exception("CALL is not the last thing on line %s" % lnum) # Replace CALLs (track line number for replacement later) #m = re.match(r"\bCALL *([^ :]*) *$", line) - m = re.match(r"(.*)CALL *([^ :]*) *$", line) + m = re.match(r"(.*)CALL *([^ :\n]*) *$", line) if m: prefix = m.groups(1)[0] sub = m.groups(1)[1] @@ -154,7 +155,7 @@ def finalize(lines, args, mode): # Replace the CALL with stack based GOTO if mode == "cbm": - lines.append("%s %sX=X+1:X%%(X)=%s:GOTO%s" % ( + lines.append("%s %sQ=%s:GOSUBPUSH_Q:GOTO%s" % ( lnum, prefix, call_index[sub], sub)) else: lines.append("%s %sX=X+1:X%%(X)=%s:GOTO %s" % ( @@ -176,7 +177,7 @@ def finalize(lines, args, mode): lnum=1 for line in src_lines: # Drop subroutine defs (track line number for CALLS) - m = re.match(r"^([0-9][0-9]*) *SUB *([^ ]*) *$", line) + m = re.match(r"^([0-9][0-9]*) *SUB *([^ \n]*) *$", line) if m: lnum = int(m.groups(1)[0])+1 label = m.groups(1)[1] @@ -195,7 +196,7 @@ def finalize(lines, args, mode): ret_labels = [cur_sub+"_"+str(i) for i in range(1, index+1)] if mode == "cbm": - line = "%s X=X-1:ONX%%(X+1)GOTO%s" % (lnum, ",".join(ret_labels)) + line = "%s GOSUBPOP_Q:ONQGOTO%s" % (lnum, ",".join(ret_labels)) else: line = "%s X=X-1:ON X%%(X+1) GOTO %s" % (lnum, ",".join(ret_labels)) cur_sub = None diff --git a/basic/core.in.bas b/basic/core.in.bas index 51ff1edee4..8788e3d1f0 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -21,7 +21,7 @@ SUB APPLY GOTO APPLY_DONE APPLY_MAL_FUNCTION: - X=X+1:X%(X)=E: REM save the current environment + Q=E:GOSUB PUSH_Q: REM save the current environment REM create new environ using env and params stored in the REM function and bind the params to the apply arguments @@ -31,7 +31,7 @@ SUB APPLY AY=E:GOSUB RELEASE: REM release the new environment - E=X%(X):X=X-1: REM pop/restore the saved environment + GOSUB POP_Q:E=Q: REM pop/restore the saved environment APPLY_DONE: END SUB @@ -74,11 +74,13 @@ SUB DO_TCO_FUNCTION GOTO DO_TCO_FUNCTION_DONE DO_APPLY_2: - X=X+1:X%(X)=R: REM push/save new args for release + GOSUB PUSH_R: REM push/save new args for release AR=R:CALL APPLY - AY=X%(X):X=X-1:GOSUB RELEASE: REM pop/release new args + REM pop/release new args + GOSUB POP_Q:AY=Q + GOSUB RELEASE GOTO DO_TCO_FUNCTION_DONE DO_MAP: @@ -88,13 +90,17 @@ SUB DO_TCO_FUNCTION T=6:L=0:N=0:GOSUB ALLOC REM push future return val, prior entry, F and AB - X=X+4:X%(X-3)=R:X%(X-2)=0:X%(X-1)=F:X%(X)=AB + GOSUB PUSH_R + Q=0:GOSUB PUSH_Q + Q=F:GOSUB PUSH_Q + Q=AB:GOSUB PUSH_Q DO_MAP_LOOP: REM set previous to current if not the first element - IF X%(X-2)<>0 THEN Z%(X%(X-2),1)=R + GOSUB PEEK_Q_2 + IF Q<>0 THEN Z%(Q,1)=R REM update previous reference to current - X%(X-2)=R + Q=R:GOSUB PUT_Q_2 IF Z%(AB,1)=0 THEN GOTO DO_MAP_DONE @@ -104,24 +110,28 @@ SUB DO_TCO_FUNCTION T=6:L=3:N=Z%(AB+1,1):GOSUB ALLOC REM push argument list - X=X+1:X%(X)=R + GOSUB PUSH_R AR=R:CALL APPLY REM pop apply args and release them - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE REM set the result value - Z%(X%(X-2)+1,1)=R + GOSUB PEEK_Q_2 + Z%(Q+1,1)=R IF ER<>-2 THEN GOTO DO_MAP_DONE REM restore F - F=X%(X-1) + GOSUB PEEK_Q_1:F=Q REM update AB to next source element - X%(X)=Z%(X%(X),1) - AB=X%(X) + GOSUB PEEK_Q + Q=Z%(Q,1) + AB=Q + GOSUB PUT_Q REM allocate next element T=6:L=0:N=0:GOSUB ALLOC @@ -129,13 +139,14 @@ SUB DO_TCO_FUNCTION GOTO DO_MAP_LOOP DO_MAP_DONE: - REM if no error, get return val - IF ER=-2 THEN R=X%(X-3) + Q=3:GOSUB PEEK_Q_Q: REM get return val + REM if no error, set the return val + IF ER=-2 THEN R=Q REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=X%(X-3):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE REM pop everything off stack - X=X-4 + GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q GOTO DO_TCO_FUNCTION_DONE @@ -147,18 +158,19 @@ SUB DO_TCO_FUNCTION AR=R REM push args for release after - X=X+1:X%(X)=AR + Q=AR:GOSUB PUSH_Q REM push atom - X=X+1:X%(X)=AA + Q=AA:GOSUB PUSH_Q CALL APPLY REM pop atom - AA=X%(X):X=X-1 + GOSUB POP_Q:AA=Q REM pop and release args - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE REM use reset to update the value AB=R:GOSUB DO_RESET_BANG @@ -418,21 +430,22 @@ DO_FUNCTION: REM multiple arguments DO_CONCAT_MULT: + REM TODO: something other than direct X access? CZ=X: REM save current stack position REM push arguments onto the stack DO_CONCAT_STACK: R=AR+1:GOSUB DEREF_R - X=X+1:X%(X)=R: REM push sequence + GOSUB PUSH_R: REM push sequence AR=Z%(AR,1) IF Z%(AR,1)<>0 THEN GOTO DO_CONCAT_STACK REM pop last argument as our seq to prepend to - AB=X%(X):X=X-1 + GOSUB POP_Q:AB=Q REM last arg/seq is not copied so we need to inc ref to it Z%(AB,0)=Z%(AB,0)+32 DO_CONCAT_LOOP: IF X=CZ THEN R=AB:RETURN - AA=X%(X):X=X-1: REM pop off next seq to prepend + GOSUB POP_Q:AA=Q: REM pop off next seq to prepend IF Z%(AA,1)=0 THEN GOTO DO_CONCAT_LOOP: REM skip empty seqs A=AA:B=0:C=-1:GOSUB SLICE @@ -524,9 +537,9 @@ DO_FUNCTION: REM RETURN DO_EVAL: - X=X+1:X%(X)=E: REM push/save environment + Q=E:GOSUB PUSH_Q: REM push/save environment A=AA:E=D:CALL EVAL - E=X%(X):X=X-1: REM pop/restore previous environment + GOSUB POP_Q:E=Q RETURN DO_READ_FILE: diff --git a/basic/debug.in.bas b/basic/debug.in.bas index 649ad497bf..5d199edbea 100644 --- a/basic/debug.in.bas +++ b/basic/debug.in.bas @@ -34,7 +34,8 @@ PR_MEMORY_SUMMARY: REM PRINT ", freed:"+STR$(P2)+", after repl_env:"+STR$(ZT) GOSUB COUNT_STRINGS PRINT "Strings (S$) :"+STR$(P2)+" /"+STR$(Z2) - PRINT "Stack (X%) :"+STR$(X+1)+" /"+STR$(Z3) + #qbasic PRINT "Stack (X%) :"+STR$(X+1)+" /"+STR$(Z3) + #cbm PRINT "Stack :"+STR$(X+2-Z3)+" / 1920" RETURN REM #cbm PR_MEMORY_MAP: diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 85f7f33170..06c90cd381 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -44,9 +44,8 @@ PR_STR: RETURN PR_SEQ: REM push the type and where we are in the sequence - X=X+2 - X%(X-1)=T - X%(X)=AZ + Q=T:GOSUB PUSH_Q + Q=AZ:GOSUB PUSH_Q REM save the current rendered string S$(S)=R$:S=S+1 PR_SEQ_LOOP: @@ -55,19 +54,19 @@ PR_STR: REM append what we just rendered it S$(S-1)=S$(S-1)+R$ REM restore current seq type - T=X%(X-1) + GOSUB PEEK_Q_1:T=Q REM Go to next list element - AZ=Z%(X%(X),1) - X%(X)=AZ + GOSUB PEEK_Q + AZ=Z%(Q,1) + Q=AZ:GOSUB PUT_Q IF Z%(AZ,1)<>0 THEN S$(S-1)=S$(S-1)+" " GOTO PR_SEQ_LOOP PR_SEQ_DONE: REM restore the current string S=S-1:R$=S$(S) - REM get type - T=X%(X-1) REM pop where we are the sequence and type - X=X-2 + GOSUB POP_Q + GOSUB POP_Q:T=Q: REM get type IF T=6 THEN R$="("+R$+")" IF T=7 THEN R$="["+R$+"]" IF T=8 THEN R$="{"+R$+"}" diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 20ff4d9ad3..198ab5bbbe 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -106,22 +106,32 @@ READ_FORM: RI=RI+LEN(T$) REM to call READ_FORM recursively, SD needs to be saved, set to REM 0 for the call and then restored afterwards. - X=X+2:X%(X-1)=(T$="^"):X%(X)=SD: REM push macro type and SD + REM push macro type and SD + Q=-1*(T$="^"):GOSUB PUSH_Q + Q=SD:GOSUB PUSH_Q REM B$ is set above - T=5:GOSUB STRING:X=X+1:X%(X)=R + T=5:GOSUB STRING + GOSUB PUSH_R - SD=0:GOSUB READ_FORM:X=X+1:X%(X)=R + SD=0:GOSUB READ_FORM + GOSUB PUSH_R - IF X%(X-3) THEN GOTO READ_MACRO_3 + Q=3:GOSUB PEEK_Q_Q + IF Q THEN GOTO READ_MACRO_3 READ_MACRO_2: - B=X%(X-1):A=X%(X):GOSUB LIST2 + GOSUB PEEK_Q_1:B=Q + GOSUB PEEK_Q:A=Q + GOSUB LIST2 GOTO READ_MACRO_DONE READ_MACRO_3: SD=0:GOSUB READ_FORM - C=X%(X-1):B=R:A=X%(X):GOSUB LIST3 + GOSUB PEEK_Q_1:C=Q + B=R + GOSUB PEEK_Q:A=Q + GOSUB LIST3 AY=C:GOSUB RELEASE READ_MACRO_DONE: @@ -129,7 +139,11 @@ READ_FORM: AY=B:GOSUB RELEASE AY=A:GOSUB RELEASE - SD=X%(X-2):X=X-4: REM get SD and pop the stack + REM get SD and pop the stack + GOSUB POP_Q + GOSUB POP_Q + GOSUB POP_Q:SD=Q + GOSUB POP_Q T$="": REM necessary to prevent unexpected EOF errors GOTO READ_FORM_DONE READ_STRING: @@ -164,14 +178,11 @@ READ_FORM: Z%(R,0)=Z%(R,0)+32 REM push start ptr on the stack - X=X+1 - X%(X)=R + GOSUB PUSH_R REM push current sequence type - X=X+1 - X%(X)=T + Q=T:GOSUB PUSH_Q REM push previous ptr on the stack - X=X+1 - X%(X)=R + GOSUB PUSH_R RI=RI+LEN(T$) GOTO READ_FORM @@ -179,11 +190,12 @@ READ_FORM: READ_SEQ_END: REM PRINT "READ_SEQ_END" IF SD=0 THEN E$="unexpected '"+C$+"'":GOTO READ_FORM_ABORT - IF X%(X-1)<>T THEN E$="sequence mismatch":GOTO READ_FORM_ABORT + GOSUB PEEK_Q_1 + IF Q<>T THEN E$="sequence mismatch":GOTO READ_FORM_ABORT SD=SD-1: REM decrease read sequence depth - R=X%(X-2): REM ptr to start of sequence to return - T=X%(X-1): REM type prior to recur - X=X-3: REM pop start, type and previous off the stack + GOSUB POP_Q: REM pop previous + GOSUB POP_Q:T=Q: REM type prior to recur + GOSUB POP_R: REM ptr to start of sequence to return GOTO READ_FORM_DONE @@ -194,28 +206,29 @@ READ_FORM: IF SD=0 THEN RETURN REM previous element - T7=X%(X) + GOSUB PEEK_Q:T7=Q REM allocate new sequence entry, set type to previous type, set REM next to previous next or previous (if first) L=Z%(T7,1) IF T7<9 THEN L=T7 T8=R: REM save previous value for release - T=X%(X-1):N=R:GOSUB ALLOC + GOSUB PEEK_Q_1:T=Q + N=R:GOSUB ALLOC REM list takes ownership IF L<9 THEN AY=L:GOSUB RELEASE AY=T8:GOSUB RELEASE REM if previous element is the first element then set REM the first to the new element - IF T7<9 THEN X%(X-2)=R:GOTO READ_FORM_SKIP_FIRST + IF T7<9 THEN Q=R:GOSUB PUT_Q_2:GOTO READ_FORM_SKIP_FIRST REM set previous list element to point to new element Z%(T7,1)=R READ_FORM_SKIP_FIRST: REM update previous pointer to current element - X%(X)=R + Q=R:GOSUB PUT_Q GOTO READ_FORM READ_FORM_ABORT: @@ -223,9 +236,12 @@ READ_FORM: R=0 READ_FORM_ABORT_UNWIND: IF SD=0 THEN RETURN - X=X-3: REM pop previous, type, and start off the stack - SD=SD-1 - IF SD=0 THEN AY=X%(X+1):GOSUB RELEASE + SD=SD-1: REM decrease read sequence depth + REM pop previous, type, and start off the stack + GOSUB POP_Q + GOSUB POP_Q + GOSUB POP_Q:AY=Q + IF SD=0 THEN GOSUB RELEASE GOTO READ_FORM_ABORT_UNWIND diff --git a/basic/step0_repl.in.bas b/basic/step0_repl.in.bas index fd1a8e0fd1..f722c91518 100755 --- a/basic/step0_repl.in.bas +++ b/basic/step0_repl.in.bas @@ -8,9 +8,9 @@ MAL_READ: RETURN REM EVAL(A$, E) -> R$ -SUB EVAL +EVAL: R$=A$ -END SUB + RETURN REM PRINT(A$) -> R$ MAL_PRINT: @@ -18,11 +18,11 @@ MAL_PRINT: RETURN REM REP(A$) -> R$ -SUB REP +REP: GOSUB MAL_READ - A=R:CALL EVAL + A=R:GOSUB EVAL A=R:GOSUB MAL_PRINT -END SUB + RETURN REM MAIN program MAIN: @@ -30,7 +30,7 @@ MAIN: A$="user> ":GOSUB READLINE: REM call input parser IF EZ=1 THEN GOTO QUIT - A$=R$:CALL REP: REM call REP + A$=R$:GOSUB REP: REM call REP PRINT R$ GOTO REPL_LOOP diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index 5e2feaa753..7c63b341cc 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -1,7 +1,7 @@ GOTO MAIN -REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' REM $INCLUDE: 'printer.in.bas' diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index ae1c535374..cb7eb5ce0a 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -1,7 +1,7 @@ GOTO MAIN -REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' REM $INCLUDE: 'printer.in.bas' @@ -17,7 +17,8 @@ SUB EVAL_AST LV=LV+1 REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A IF ER<>-2 THEN GOTO EVAL_AST_RETURN @@ -43,26 +44,25 @@ SUB EVAL_AST REM allocate the first entry (T already set above) L=0:N=0:GOSUB ALLOC - REM make space on the stack - X=X+4 REM push type of sequence - X%(X-3)=T + Q=T:GOSUB PUSH_Q REM push sequence index - X%(X-2)=-1 + Q=0:GOSUB PUSH_Q REM push future return value (new sequence) - X%(X-1)=R + GOSUB PUSH_R REM push previous new sequence entry - X%(X)=R + GOSUB PUSH_R EVAL_AST_SEQ_LOOP: - REM update index - X%(X-2)=X%(X-2)+1 - REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF + Q=3:GOSUB PEEK_Q_Q:T=Q + REM get and update index + GOSUB PEEK_Q_2 + Q=Q+1:GOSUB PUT_Q_2 + IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -79,18 +79,21 @@ SUB EVAL_AST EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(X%(X)+1,1)=R + GOSUB PEEK_Q + Z%(Q+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=X%(X-3):L=0:N=0:GOSUB ALLOC + Q=3:GOSUB PEEK_Q_Q:T=Q + L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(X%(X),1)=R + GOSUB PEEK_Q + Z%(Q,1)=R REM update previous ptr to current entry - X%(X)=R + Q=R:GOSUB PUT_Q REM process the next sequence entry from source list A=Z%(A,1) @@ -98,14 +101,16 @@ SUB EVAL_AST GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: REM get return value (new seq), index, and seq type - R=X%(X-1) + GOSUB PEEK_Q_1 + R=Q REM pop previous, return, index and type - X=X-4 + GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q GOTO EVAL_AST_RETURN EVAL_AST_RETURN: REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q LV=LV-1 END SUB @@ -115,7 +120,8 @@ SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A IF ER<>-2 THEN GOTO EVAL_RETURN @@ -158,7 +164,8 @@ SUB EVAL #qbasic T=0 REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index 95c33f1eb0..e11e7acab7 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -1,7 +1,7 @@ GOTO MAIN -REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' REM $INCLUDE: 'printer.in.bas' REM $INCLUDE: 'env.in.bas' @@ -18,7 +18,8 @@ SUB EVAL_AST LV=LV+1 REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A IF ER<>-2 THEN GOTO EVAL_AST_RETURN @@ -42,26 +43,25 @@ SUB EVAL_AST REM allocate the first entry (T already set above) L=0:N=0:GOSUB ALLOC - REM make space on the stack - X=X+4 REM push type of sequence - X%(X-3)=T + Q=T:GOSUB PUSH_Q REM push sequence index - X%(X-2)=-1 + Q=0:GOSUB PUSH_Q REM push future return value (new sequence) - X%(X-1)=R + GOSUB PUSH_R REM push previous new sequence entry - X%(X)=R + GOSUB PUSH_R EVAL_AST_SEQ_LOOP: - REM update index - X%(X-2)=X%(X-2)+1 - REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF + Q=3:GOSUB PEEK_Q_Q:T=Q + REM get and update index + GOSUB PEEK_Q_2 + Q=Q+1:GOSUB PUT_Q_2 + IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -78,36 +78,41 @@ SUB EVAL_AST EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(X%(X)+1,1)=R + GOSUB PEEK_Q + Z%(Q+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=X%(X-3):L=0:N=0:GOSUB ALLOC + Q=3:GOSUB PEEK_Q_Q:T=Q + L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(X%(X),1)=R + GOSUB PEEK_Q + Z%(Q,1)=R REM update previous ptr to current entry - X%(X)=R + Q=R:GOSUB PUT_Q REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: + GOSUB PEEK_Q_1 REM if no error, get return value (new seq) - IF ER=-2 THEN R=X%(X-1) + IF ER=-2 THEN R=Q REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE REM pop previous, return, index and type - X=X-4 + GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q GOTO EVAL_AST_RETURN EVAL_AST_RETURN: REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q LV=LV-1 END SUB @@ -117,7 +122,8 @@ SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A IF ER<>-2 THEN GOTO EVAL_RETURN @@ -162,9 +168,9 @@ SUB EVAL REM PRINT "def!" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q A=A2:CALL EVAL: REM eval a2 - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q IF ER<>-2 THEN GOTO EVAL_RETURN @@ -176,17 +182,17 @@ SUB EVAL REM PRINT "let*" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A2: REM push/save A2 + Q=A2:GOSUB PUSH_Q: REM push/save A2 REM create new environment with outer as current environment C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element A=Z%(A1,1)+1:CALL EVAL - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE @@ -199,7 +205,7 @@ SUB EVAL GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - A2=X%(X):X=X-1: REM pop A2 + GOSUB POP_Q:A2=Q: REM pop A2 A=A2:CALL EVAL: REM eval A2 using let_env GOTO EVAL_RETURN EVAL_INVOKE: @@ -222,7 +228,8 @@ SUB EVAL REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE + GOSUB PEEK_Q_1 + IF E<>Q THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level @@ -231,7 +238,8 @@ SUB EVAL #qbasic T=0 REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index 8e47bf202d..e32ab16eaa 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -1,7 +1,7 @@ GOTO MAIN -REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' REM $INCLUDE: 'printer.in.bas' REM $INCLUDE: 'env.in.bas' @@ -17,7 +17,8 @@ MAL_READ: REM EVAL_AST(A, E) -> R SUB EVAL_AST REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A IF ER<>-2 THEN GOTO EVAL_AST_RETURN @@ -41,26 +42,25 @@ SUB EVAL_AST REM allocate the first entry (T already set above) L=0:N=0:GOSUB ALLOC - REM make space on the stack - X=X+4 REM push type of sequence - X%(X-3)=T + Q=T:GOSUB PUSH_Q REM push sequence index - X%(X-2)=-1 + Q=0:GOSUB PUSH_Q REM push future return value (new sequence) - X%(X-1)=R + GOSUB PUSH_R REM push previous new sequence entry - X%(X)=R + GOSUB PUSH_R EVAL_AST_SEQ_LOOP: - REM update index - X%(X-2)=X%(X-2)+1 - REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF + Q=3:GOSUB PEEK_Q_Q:T=Q + REM get and update index + GOSUB PEEK_Q_2 + Q=Q+1:GOSUB PUT_Q_2 + IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -77,36 +77,41 @@ SUB EVAL_AST EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(X%(X)+1,1)=R + GOSUB PEEK_Q + Z%(Q+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=X%(X-3):L=0:N=0:GOSUB ALLOC + Q=3:GOSUB PEEK_Q_Q:T=Q + L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(X%(X),1)=R + GOSUB PEEK_Q + Z%(Q,1)=R REM update previous ptr to current entry - X%(X)=R + Q=R:GOSUB PUT_Q REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: + GOSUB PEEK_Q_1 REM if no error, get return value (new seq) - IF ER=-2 THEN R=X%(X-1) + IF ER=-2 THEN R=Q REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE REM pop previous, return, index and type - X=X-4 + GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q GOTO EVAL_AST_RETURN EVAL_AST_RETURN: REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB REM EVAL(A, E) -> R @@ -114,7 +119,8 @@ SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0)) @@ -166,9 +172,9 @@ SUB EVAL REM PRINT "def!" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q A=A2:CALL EVAL: REM eval a2 - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q IF ER<>-2 THEN GOTO EVAL_RETURN @@ -180,17 +186,17 @@ SUB EVAL REM PRINT "let*" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A2: REM push/save A2 + Q=A2:GOSUB PUSH_Q: REM push/save A2 REM create new environment with outer as current environment C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element A=Z%(A1,1)+1:CALL EVAL - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE @@ -203,7 +209,7 @@ SUB EVAL GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - A2=X%(X):X=X-1: REM pop A2 + GOSUB POP_Q:A2=Q: REM pop A2 A=A2:CALL EVAL: REM eval A2 using let_env GOTO EVAL_RETURN EVAL_DO: @@ -211,19 +217,17 @@ SUB EVAL CALL EVAL_AST - X=X+1:X%(X)=R: REM push eval'd list + GOSUB PUSH_R: REM push eval'd list A=R:GOSUB LAST: REM return the last element - AY=X%(X):X=X-1: REM pop eval'd list + GOSUB POP_Q:AY=Q: REM pop eval'd list GOSUB RELEASE: REM release the eval'd list GOTO EVAL_RETURN EVAL_IF: GOSUB EVAL_GET_A1: REM set A1 - REM push A - X=X+1:X%(X)=A + GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL - REM pop A - A=X%(X):X=X-1 + GOSUB POP_A: REM pop/restore A IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: @@ -250,7 +254,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - X=X+1:X%(X)=R + GOSUB PUSH_R F=R+1 @@ -264,7 +268,7 @@ SUB EVAL IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R=X%(X):X=X-1 + GOSUB POP_R ER=-1:E$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: @@ -275,7 +279,8 @@ SUB EVAL EVAL_DO_FUNCTION_SKIP: REM pop and release f/args - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: @@ -287,16 +292,18 @@ SUB EVAL REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE + GOSUB PEEK_Q_2 + IF E4<>Q THEN AY=E4:GOSUB RELEASE REM claim the AST before releasing the list containing it A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) - Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV+1 + LV=LV+1:GOSUB PEND_A_LV:LV=LV-1 REM pop and release f/args - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE REM A set above E=R:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -306,7 +313,8 @@ SUB EVAL REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE + GOSUB PEEK_Q_1 + IF E<>Q THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level @@ -318,7 +326,8 @@ SUB EVAL #qbasic T=0 REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index b56af813af..daba262a05 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -1,7 +1,7 @@ GOTO MAIN -REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' REM $INCLUDE: 'printer.in.bas' REM $INCLUDE: 'env.in.bas' @@ -17,7 +17,8 @@ MAL_READ: REM EVAL_AST(A, E) -> R SUB EVAL_AST REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A IF ER<>-2 THEN GOTO EVAL_AST_RETURN @@ -41,29 +42,29 @@ SUB EVAL_AST REM allocate the first entry (T already set above) L=0:N=0:GOSUB ALLOC - REM make space on the stack - X=X+4 REM push type of sequence - X%(X-3)=T + Q=T:GOSUB PUSH_Q REM push sequence index - X%(X-2)=-1 + Q=0:GOSUB PUSH_Q REM push future return value (new sequence) - X%(X-1)=R + GOSUB PUSH_R REM push previous new sequence entry - X%(X)=R + GOSUB PUSH_R EVAL_AST_SEQ_LOOP: - REM update index - X%(X-2)=X%(X-2)+1 - REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element - IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + Q=6:GOSUB PEEK_Q_Q + IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF + Q=3:GOSUB PEEK_Q_Q:T=Q + REM get and update index + GOSUB PEEK_Q_2 + Q=Q+1:GOSUB PUT_Q_2 + IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -80,36 +81,41 @@ SUB EVAL_AST EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(X%(X)+1,1)=R + GOSUB PEEK_Q + Z%(Q+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=X%(X-3):L=0:N=0:GOSUB ALLOC + Q=3:GOSUB PEEK_Q_Q:T=Q + L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(X%(X),1)=R + GOSUB PEEK_Q + Z%(Q,1)=R REM update previous ptr to current entry - X%(X)=R + Q=R:GOSUB PUT_Q REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: + GOSUB PEEK_Q_1 REM if no error, get return value (new seq) - IF ER=-2 THEN R=X%(X-1) + IF ER=-2 THEN R=Q REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE REM pop previous, return, index and type - X=X-4 + GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q GOTO EVAL_AST_RETURN EVAL_AST_RETURN: REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB REM EVAL(A, E) -> R @@ -117,7 +123,8 @@ SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0)) @@ -169,9 +176,9 @@ SUB EVAL REM PRINT "def!" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q A=A2:CALL EVAL: REM eval a2 - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q IF ER<>-2 THEN GOTO EVAL_RETURN @@ -183,8 +190,8 @@ SUB EVAL REM PRINT "let*" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A2: REM push/save A2 - X=X+1:X%(X)=E: REM push env for for later release + Q=A2:GOSUB PUSH_Q: REM push/save A2 + Q=E:GOSUB PUSH_Q: REM push env for for later release REM create new environment with outer as current environment C=E:GOSUB ENV_NEW @@ -192,10 +199,10 @@ SUB EVAL EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element A=Z%(A1,1)+1:CALL EVAL - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE @@ -208,24 +215,25 @@ SUB EVAL GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4=X%(X):X=X-1: REM pop previous env + GOSUB POP_Q:E4=Q: REM pop previous env REM release previous environment if not the current EVAL env - IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE + GOSUB PEEK_Q_2 + IF E4<>Q THEN AY=E4:GOSUB RELEASE - A2=X%(X):X=X-1: REM pop A2 + GOSUB POP_Q:A2=Q: REM pop A2 A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: A=Z%(A,1): REM rest - X=X+1:X%(X)=A: REM push/save A + GOSUB PUSH_A: REM push/save A CALL EVAL_AST REM cleanup AY=R: REM get eval'd list for release - A=X%(X):X=X-1: REM pop/restore original A for LAST + GOSUB POP_A: REM pop/restore original A for LAST GOSUB LAST: REM get last element for return A=R: REM new recur AST @@ -237,11 +245,9 @@ SUB EVAL EVAL_IF: GOSUB EVAL_GET_A1: REM set A1 - REM push A - X=X+1:X%(X)=A + GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL - REM pop A - A=X%(X):X=X-1 + GOSUB POP_A: REM pop/restore A IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: @@ -268,7 +274,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - X=X+1:X%(X)=R + GOSUB PUSH_R F=R+1 @@ -282,7 +288,7 @@ SUB EVAL IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R=X%(X):X=X-1 + GOSUB POP_R ER=-1:E$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: @@ -293,7 +299,8 @@ SUB EVAL EVAL_DO_FUNCTION_SKIP: REM pop and release f/args - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: @@ -305,16 +312,18 @@ SUB EVAL REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE + GOSUB PEEK_Q_2 + IF E4<>Q THEN AY=E4:GOSUB RELEASE REM claim the AST before releasing the list containing it A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) - Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV+1 + LV=LV+1:GOSUB PEND_A_LV:LV=LV-1 REM pop and release f/args - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE REM A set above E=R:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -324,7 +333,8 @@ SUB EVAL REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE + GOSUB PEEK_Q_1 + IF E<>Q THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level @@ -336,7 +346,8 @@ SUB EVAL #qbasic T=0 REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index 6b4f23414b..07b3020513 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -1,7 +1,7 @@ GOTO MAIN -REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' REM $INCLUDE: 'printer.in.bas' REM $INCLUDE: 'env.in.bas' @@ -17,7 +17,8 @@ MAL_READ: REM EVAL_AST(A, E) -> R SUB EVAL_AST REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A IF ER<>-2 THEN GOTO EVAL_AST_RETURN @@ -41,29 +42,29 @@ SUB EVAL_AST REM allocate the first entry (T already set above) L=0:N=0:GOSUB ALLOC - REM make space on the stack - X=X+4 REM push type of sequence - X%(X-3)=T + Q=T:GOSUB PUSH_Q REM push sequence index - X%(X-2)=-1 + Q=0:GOSUB PUSH_Q REM push future return value (new sequence) - X%(X-1)=R + GOSUB PUSH_R REM push previous new sequence entry - X%(X)=R + GOSUB PUSH_R EVAL_AST_SEQ_LOOP: - REM update index - X%(X-2)=X%(X-2)+1 - REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element - IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + Q=6:GOSUB PEEK_Q_Q + IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF + Q=3:GOSUB PEEK_Q_Q:T=Q + REM get and update index + GOSUB PEEK_Q_2 + Q=Q+1:GOSUB PUT_Q_2 + IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -80,36 +81,41 @@ SUB EVAL_AST EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(X%(X)+1,1)=R + GOSUB PEEK_Q + Z%(Q+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=X%(X-3):L=0:N=0:GOSUB ALLOC + Q=3:GOSUB PEEK_Q_Q:T=Q + L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(X%(X),1)=R + GOSUB PEEK_Q + Z%(Q,1)=R REM update previous ptr to current entry - X%(X)=R + Q=R:GOSUB PUT_Q REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: + GOSUB PEEK_Q_1 REM if no error, get return value (new seq) - IF ER=-2 THEN R=X%(X-1) + IF ER=-2 THEN R=Q REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE REM pop previous, return, index and type - X=X-4 + GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q GOTO EVAL_AST_RETURN EVAL_AST_RETURN: REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB REM EVAL(A, E) -> R @@ -117,7 +123,8 @@ SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0)) @@ -169,9 +176,9 @@ SUB EVAL REM PRINT "def!" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q A=A2:CALL EVAL: REM eval a2 - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q IF ER<>-2 THEN GOTO EVAL_RETURN @@ -183,8 +190,8 @@ SUB EVAL REM PRINT "let*" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A2: REM push/save A2 - X=X+1:X%(X)=E: REM push env for for later release + Q=A2:GOSUB PUSH_Q: REM push/save A2 + Q=E:GOSUB PUSH_Q: REM push env for for later release REM create new environment with outer as current environment C=E:GOSUB ENV_NEW @@ -192,10 +199,10 @@ SUB EVAL EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element A=Z%(A1,1)+1:CALL EVAL - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE @@ -208,24 +215,25 @@ SUB EVAL GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4=X%(X):X=X-1: REM pop previous env + GOSUB POP_Q:E4=Q: REM pop previous env REM release previous environment if not the current EVAL env - IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE + GOSUB PEEK_Q_2 + IF E4<>Q THEN AY=E4:GOSUB RELEASE - A2=X%(X):X=X-1: REM pop A2 + GOSUB POP_Q:A2=Q: REM pop A2 A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: A=Z%(A,1): REM rest - X=X+1:X%(X)=A: REM push/save A + GOSUB PUSH_A: REM push/save A CALL EVAL_AST REM cleanup AY=R: REM get eval'd list for release - A=X%(X):X=X-1: REM pop/restore original A for LAST + GOSUB POP_A: REM pop/restore original A for LAST GOSUB LAST: REM get last element for return A=R: REM new recur AST @@ -237,11 +245,9 @@ SUB EVAL EVAL_IF: GOSUB EVAL_GET_A1: REM set A1 - REM push A - X=X+1:X%(X)=A + GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL - REM pop A - A=X%(X):X=X-1 + GOSUB POP_A: REM pop/restore A IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: @@ -268,7 +274,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - X=X+1:X%(X)=R + GOSUB PUSH_R F=R+1 @@ -282,7 +288,7 @@ SUB EVAL IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R=X%(X):X=X-1 + GOSUB POP_R ER=-1:E$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: @@ -293,7 +299,8 @@ SUB EVAL EVAL_DO_FUNCTION_SKIP: REM pop and release f/args - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: @@ -305,16 +312,18 @@ SUB EVAL REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE + GOSUB PEEK_Q_2 + IF E4<>Q THEN AY=E4:GOSUB RELEASE REM claim the AST before releasing the list containing it A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) - Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV+1 + LV=LV+1:GOSUB PEND_A_LV:LV=LV-1 REM pop and release f/args - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE REM A set above E=R:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -324,7 +333,8 @@ SUB EVAL REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE + GOSUB PEEK_Q_1 + IF E<>Q THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level @@ -336,7 +346,8 @@ SUB EVAL #qbasic T=0 REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index 95200809e6..2d9e221b48 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -1,7 +1,7 @@ GOTO MAIN -REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' REM $INCLUDE: 'printer.in.bas' REM $INCLUDE: 'env.in.bas' @@ -40,13 +40,11 @@ SUB QUASIQUOTE GOTO QQ_DONE QQ_SPLICE_UNQUOTE: - REM push A on the stack - X=X+1:X%(X)=A + GOSUB PUSH_A REM rest of cases call quasiquote on ast[1..] A=Z%(A,1):CALL QUASIQUOTE T6=R - REM pop A off the stack - A=X%(X):X=X-1 + GOSUB POP_A REM set A to ast[0] for last two cases A=A+1:GOSUB DEREF_A @@ -71,13 +69,11 @@ SUB QUASIQUOTE QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - REM push T6 on the stack - X=X+1:X%(X)=T6 + Q=T6:GOSUB PUSH_Q REM A set above to ast[0] CALL QUASIQUOTE B=R - REM pop T6 off the stack - T6=X%(X):X=X-1 + GOSUB POP_Q:T6=Q B$="cons":T=5:GOSUB STRING:C=R A=T6:GOSUB LIST3 @@ -92,7 +88,8 @@ END SUB REM EVAL_AST(A, E) -> R SUB EVAL_AST REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A IF ER<>-2 THEN GOTO EVAL_AST_RETURN @@ -116,29 +113,29 @@ SUB EVAL_AST REM allocate the first entry (T already set above) L=0:N=0:GOSUB ALLOC - REM make space on the stack - X=X+4 REM push type of sequence - X%(X-3)=T + Q=T:GOSUB PUSH_Q REM push sequence index - X%(X-2)=-1 + Q=0:GOSUB PUSH_Q REM push future return value (new sequence) - X%(X-1)=R + GOSUB PUSH_R REM push previous new sequence entry - X%(X)=R + GOSUB PUSH_R EVAL_AST_SEQ_LOOP: - REM update index - X%(X-2)=X%(X-2)+1 - REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element - IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + Q=6:GOSUB PEEK_Q_Q + IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF + Q=3:GOSUB PEEK_Q_Q:T=Q + REM get and update index + GOSUB PEEK_Q_2 + Q=Q+1:GOSUB PUT_Q_2 + IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -155,36 +152,41 @@ SUB EVAL_AST EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(X%(X)+1,1)=R + GOSUB PEEK_Q + Z%(Q+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=X%(X-3):L=0:N=0:GOSUB ALLOC + Q=3:GOSUB PEEK_Q_Q:T=Q + L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(X%(X),1)=R + GOSUB PEEK_Q + Z%(Q,1)=R REM update previous ptr to current entry - X%(X)=R + Q=R:GOSUB PUT_Q REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: + GOSUB PEEK_Q_1 REM if no error, get return value (new seq) - IF ER=-2 THEN R=X%(X-1) + IF ER=-2 THEN R=Q REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE REM pop previous, return, index and type - X=X-4 + GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q GOTO EVAL_AST_RETURN EVAL_AST_RETURN: REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB REM EVAL(A, E) -> R @@ -192,7 +194,8 @@ SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0)) @@ -246,9 +249,9 @@ SUB EVAL REM PRINT "def!" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q A=A2:CALL EVAL: REM eval a2 - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q IF ER<>-2 THEN GOTO EVAL_RETURN @@ -260,8 +263,8 @@ SUB EVAL REM PRINT "let*" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A2: REM push/save A2 - X=X+1:X%(X)=E: REM push env for for later release + Q=A2:GOSUB PUSH_Q: REM push/save A2 + Q=E:GOSUB PUSH_Q: REM push env for for later release REM create new environment with outer as current environment C=E:GOSUB ENV_NEW @@ -269,10 +272,10 @@ SUB EVAL EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element A=Z%(A1,1)+1:CALL EVAL - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE @@ -285,24 +288,25 @@ SUB EVAL GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4=X%(X):X=X-1: REM pop previous env + GOSUB POP_Q:E4=Q: REM pop previous env REM release previous environment if not the current EVAL env - IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE + GOSUB PEEK_Q_2 + IF E4<>Q THEN AY=E4:GOSUB RELEASE - A2=X%(X):X=X-1: REM pop A2 + GOSUB POP_Q:A2=Q: REM pop A2 A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: A=Z%(A,1): REM rest - X=X+1:X%(X)=A: REM push/save A + GOSUB PUSH_A: REM push/save A CALL EVAL_AST REM cleanup AY=R: REM get eval'd list for release - A=X%(X):X=X-1: REM pop/restore original A for LAST + GOSUB POP_A: REM pop/restore original A for LAST GOSUB LAST: REM get last element for return A=R: REM new recur AST @@ -320,19 +324,18 @@ SUB EVAL EVAL_QUASIQUOTE: R=Z%(A,1)+1:GOSUB DEREF_R A=R:CALL QUASIQUOTE + A=R REM add quasiquote result to pending release queue to free when REM next lower EVAL level returns (LV) - Y=Y+1:Y%(Y,0)=R:Y%(Y,1)=LV + GOSUB PEND_A_LV - A=R:GOTO EVAL_TCO_RECUR: REM TCO loop + GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_IF: GOSUB EVAL_GET_A1: REM set A1 - REM push A - X=X+1:X%(X)=A + GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL - REM pop A - A=X%(X):X=X-1 + GOSUB POP_A: REM pop/restore A IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: @@ -359,7 +362,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - X=X+1:X%(X)=R + GOSUB PUSH_R F=R+1 @@ -373,7 +376,7 @@ SUB EVAL IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R=X%(X):X=X-1 + GOSUB POP_R ER=-1:E$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: @@ -384,7 +387,8 @@ SUB EVAL EVAL_DO_FUNCTION_SKIP: REM pop and release f/args - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: @@ -396,16 +400,18 @@ SUB EVAL REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE + GOSUB PEEK_Q_2 + IF E4<>Q THEN AY=E4:GOSUB RELEASE REM claim the AST before releasing the list containing it A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) - Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV+1 + LV=LV+1:GOSUB PEND_A_LV:LV=LV-1 REM pop and release f/args - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE REM A set above E=R:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -415,7 +421,8 @@ SUB EVAL REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE + GOSUB PEEK_Q_1 + IF E<>Q THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level @@ -427,7 +434,8 @@ SUB EVAL #qbasic T=0 REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index bf824edabe..4d61818f5b 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -1,9 +1,7 @@ -REM POKE 1, PEEK(1)AND248: REM enable all ROM areas as RAM -REM POKE 55,0: POKE 56,192: CLR: REM move BASIC end from $A000 to $C000 GOTO MAIN -REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' REM $INCLUDE: 'printer.in.bas' REM $INCLUDE: 'env.in.bas' @@ -42,13 +40,11 @@ SUB QUASIQUOTE GOTO QQ_DONE QQ_SPLICE_UNQUOTE: - REM push A on the stack - X=X+1:X%(X)=A + GOSUB PUSH_A REM rest of cases call quasiquote on ast[1..] A=Z%(A,1):CALL QUASIQUOTE T6=R - REM pop A off the stack - A=X%(X):X=X-1 + GOSUB POP_A REM set A to ast[0] for last two cases A=A+1:GOSUB DEREF_A @@ -73,13 +69,11 @@ SUB QUASIQUOTE QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - REM push T6 on the stack - X=X+1:X%(X)=T6 + Q=T6:GOSUB PUSH_Q REM A set above to ast[0] CALL QUASIQUOTE B=R - REM pop T6 off the stack - T6=X%(X):X=X-1 + GOSUB POP_Q:T6=Q B$="cons":T=5:GOSUB STRING:C=R A=T6:GOSUB LIST3 @@ -92,8 +86,7 @@ END SUB REM MACROEXPAND(A, E) -> A: SUB MACROEXPAND - REM push original A - X=X+1:X%(X)=A + GOSUB PUSH_A MACROEXPAND_LOOP: REM list? @@ -109,26 +102,27 @@ SUB MACROEXPAND B=T4:GOSUB DEREF_B REM macro? IF (Z%(B,0)AND 31)<>11 THEN GOTO MACROEXPAND_DONE - + F=B:AR=Z%(A,1):CALL APPLY A=R - AY=X%(X) + GOSUB PEEK_Q:AY=Q REM if previous A was not the first A into macroexpand (i.e. an REM intermediate form) then free it - IF A<>AY THEN Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV - + IF A<>AY THEN GOSUB PEND_A_LV + IF ER<>-2 THEN GOTO MACROEXPAND_DONE GOTO MACROEXPAND_LOOP MACROEXPAND_DONE: - X=X-1: REM pop original A + GOSUB POP_Q: REM pop original A END SUB REM EVAL_AST(A, E) -> R SUB EVAL_AST REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A IF ER<>-2 THEN GOTO EVAL_AST_RETURN @@ -152,29 +146,29 @@ SUB EVAL_AST REM allocate the first entry (T already set above) L=0:N=0:GOSUB ALLOC - REM make space on the stack - X=X+4 REM push type of sequence - X%(X-3)=T + Q=T:GOSUB PUSH_Q REM push sequence index - X%(X-2)=-1 + Q=0:GOSUB PUSH_Q REM push future return value (new sequence) - X%(X-1)=R + GOSUB PUSH_R REM push previous new sequence entry - X%(X)=R + GOSUB PUSH_R EVAL_AST_SEQ_LOOP: - REM update index - X%(X-2)=X%(X-2)+1 - REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element - IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + Q=6:GOSUB PEEK_Q_Q + IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF + Q=3:GOSUB PEEK_Q_Q:T=Q + REM get and update index + GOSUB PEEK_Q_2 + Q=Q+1:GOSUB PUT_Q_2 + IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -191,36 +185,41 @@ SUB EVAL_AST EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(X%(X)+1,1)=R + GOSUB PEEK_Q + Z%(Q+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=X%(X-3):L=0:N=0:GOSUB ALLOC + Q=3:GOSUB PEEK_Q_Q:T=Q + L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(X%(X),1)=R + GOSUB PEEK_Q + Z%(Q,1)=R REM update previous ptr to current entry - X%(X)=R + Q=R:GOSUB PUT_Q REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: + GOSUB PEEK_Q_1 REM if no error, get return value (new seq) - IF ER=-2 THEN R=X%(X-1) + IF ER=-2 THEN R=Q REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE REM pop previous, return, index and type - X=X-4 + GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q GOTO EVAL_AST_RETURN EVAL_AST_RETURN: REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB REM EVAL(A, E) -> R @@ -228,7 +227,8 @@ SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0)) @@ -290,9 +290,9 @@ SUB EVAL REM PRINT "def!" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q A=A2:CALL EVAL: REM eval a2 - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q IF ER<>-2 THEN GOTO EVAL_RETURN @@ -304,8 +304,8 @@ SUB EVAL REM PRINT "let*" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A2: REM push/save A2 - X=X+1:X%(X)=E: REM push env for for later release + Q=A2:GOSUB PUSH_Q: REM push/save A2 + Q=E:GOSUB PUSH_Q: REM push env for for later release REM create new environment with outer as current environment C=E:GOSUB ENV_NEW @@ -313,10 +313,10 @@ SUB EVAL EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element A=Z%(A1,1)+1:CALL EVAL - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE @@ -329,24 +329,25 @@ SUB EVAL GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4=X%(X):X=X-1: REM pop previous env + GOSUB POP_Q:E4=Q: REM pop previous env REM release previous environment if not the current EVAL env - IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE + GOSUB PEEK_Q_2 + IF E4<>Q THEN AY=E4:GOSUB RELEASE - A2=X%(X):X=X-1: REM pop A2 + GOSUB POP_Q:A2=Q: REM pop A2 A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: A=Z%(A,1): REM rest - X=X+1:X%(X)=A: REM push/save A + GOSUB PUSH_A: REM push/save A CALL EVAL_AST REM cleanup AY=R: REM get eval'd list for release - A=X%(X):X=X-1: REM pop/restore original A for LAST + GOSUB POP_A: REM pop/restore original A for LAST GOSUB LAST: REM get last element for return A=R: REM new recur AST @@ -364,19 +365,20 @@ SUB EVAL EVAL_QUASIQUOTE: R=Z%(A,1)+1:GOSUB DEREF_R A=R:CALL QUASIQUOTE + A=R REM add quasiquote result to pending release queue to free when REM next lower EVAL level returns (LV) - Y=Y+1:Y%(Y,0)=R:Y%(Y,1)=LV + GOSUB PEND_A_LV - A=R:GOTO EVAL_TCO_RECUR: REM TCO loop + GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DEFMACRO: REM PRINT "defmacro!" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q: REM push A1 A=A2:CALL EVAL: REM eval A2 - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q: REM pop A1 REM change function to macro Z%(R,0)=Z%(R,0)+1 @@ -397,11 +399,9 @@ SUB EVAL EVAL_IF: GOSUB EVAL_GET_A1: REM set A1 - REM push A - X=X+1:X%(X)=A + GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL - REM pop A - A=X%(X):X=X-1 + GOSUB POP_A: REM pop/restore A IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: @@ -428,7 +428,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - X=X+1:X%(X)=R + GOSUB PUSH_R F=R+1 @@ -442,7 +442,7 @@ SUB EVAL IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R=X%(X):X=X-1 + GOSUB POP_R ER=-1:E$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: @@ -453,7 +453,8 @@ SUB EVAL EVAL_DO_FUNCTION_SKIP: REM pop and release f/args - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: @@ -465,16 +466,18 @@ SUB EVAL REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE + GOSUB PEEK_Q_2 + IF E4<>Q THEN AY=E4:GOSUB RELEASE REM claim the AST before releasing the list containing it A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) - Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV+1 + LV=LV+1:GOSUB PEND_A_LV:LV=LV-1 REM pop and release f/args - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE REM A set above E=R:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -484,7 +487,8 @@ SUB EVAL REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE + GOSUB PEEK_Q_1 + IF E<>Q THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level @@ -496,7 +500,8 @@ SUB EVAL #qbasic T=0 REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index d72e65d8b5..2afccdb4cf 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -2,8 +2,8 @@ REM POKE 1, PEEK(1)AND248: REM enable all ROM areas as RAM REM POKE 55,0: POKE 56,192: CLR: REM move BASIC end from $A000 to $C000 GOTO MAIN -REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' REM $INCLUDE: 'printer.in.bas' REM $INCLUDE: 'env.in.bas' @@ -42,13 +42,11 @@ SUB QUASIQUOTE GOTO QQ_DONE QQ_SPLICE_UNQUOTE: - REM push A on the stack - X=X+1:X%(X)=A + GOSUB PUSH_A REM rest of cases call quasiquote on ast[1..] A=Z%(A,1):CALL QUASIQUOTE T6=R - REM pop A off the stack - A=X%(X):X=X-1 + GOSUB POP_A REM set A to ast[0] for last two cases A=A+1:GOSUB DEREF_A @@ -73,13 +71,11 @@ SUB QUASIQUOTE QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - REM push T6 on the stack - X=X+1:X%(X)=T6 + Q=T6:GOSUB PUSH_Q REM A set above to ast[0] CALL QUASIQUOTE B=R - REM pop T6 off the stack - T6=X%(X):X=X-1 + GOSUB POP_Q:T6=Q B$="cons":T=5:GOSUB STRING:C=R A=T6:GOSUB LIST3 @@ -92,8 +88,7 @@ END SUB REM MACROEXPAND(A, E) -> A: SUB MACROEXPAND - REM push original A - X=X+1:X%(X)=A + GOSUB PUSH_A MACROEXPAND_LOOP: REM list? @@ -109,26 +104,27 @@ SUB MACROEXPAND B=T4:GOSUB DEREF_B REM macro? IF (Z%(B,0)AND 31)<>11 THEN GOTO MACROEXPAND_DONE - + F=B:AR=Z%(A,1):CALL APPLY A=R - AY=X%(X) + GOSUB PEEK_Q:AY=Q REM if previous A was not the first A into macroexpand (i.e. an REM intermediate form) then free it - IF A<>AY THEN Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV - + IF A<>AY THEN GOSUB PEND_A_LV + IF ER<>-2 THEN GOTO MACROEXPAND_DONE GOTO MACROEXPAND_LOOP MACROEXPAND_DONE: - X=X-1: REM pop original A + GOSUB POP_Q: REM pop original A END SUB REM EVAL_AST(A, E) -> R SUB EVAL_AST REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A IF ER<>-2 THEN GOTO EVAL_AST_RETURN @@ -152,29 +148,29 @@ SUB EVAL_AST REM allocate the first entry (T already set above) L=0:N=0:GOSUB ALLOC - REM make space on the stack - X=X+4 REM push type of sequence - X%(X-3)=T + Q=T:GOSUB PUSH_Q REM push sequence index - X%(X-2)=-1 + Q=0:GOSUB PUSH_Q REM push future return value (new sequence) - X%(X-1)=R + GOSUB PUSH_R REM push previous new sequence entry - X%(X)=R + GOSUB PUSH_R EVAL_AST_SEQ_LOOP: - REM update index - X%(X-2)=X%(X-2)+1 - REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element - IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + Q=6:GOSUB PEEK_Q_Q + IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF + Q=3:GOSUB PEEK_Q_Q:T=Q + REM get and update index + GOSUB PEEK_Q_2 + Q=Q+1:GOSUB PUT_Q_2 + IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -191,36 +187,41 @@ SUB EVAL_AST EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(X%(X)+1,1)=R + GOSUB PEEK_Q + Z%(Q+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=X%(X-3):L=0:N=0:GOSUB ALLOC + Q=3:GOSUB PEEK_Q_Q:T=Q + L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(X%(X),1)=R + GOSUB PEEK_Q + Z%(Q,1)=R REM update previous ptr to current entry - X%(X)=R + Q=R:GOSUB PUT_Q REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: + GOSUB PEEK_Q_1 REM if no error, get return value (new seq) - IF ER=-2 THEN R=X%(X-1) + IF ER=-2 THEN R=Q REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE REM pop previous, return, index and type - X=X-4 + GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q GOTO EVAL_AST_RETURN EVAL_AST_RETURN: REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB REM EVAL(A, E) -> R @@ -228,7 +229,8 @@ SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0)) @@ -291,9 +293,9 @@ SUB EVAL REM PRINT "def!" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q A=A2:CALL EVAL: REM eval a2 - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q IF ER<>-2 THEN GOTO EVAL_RETURN @@ -305,8 +307,8 @@ SUB EVAL REM PRINT "let*" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A2: REM push/save A2 - X=X+1:X%(X)=E: REM push env for for later release + Q=A2:GOSUB PUSH_Q: REM push/save A2 + Q=E:GOSUB PUSH_Q: REM push env for for later release REM create new environment with outer as current environment C=E:GOSUB ENV_NEW @@ -314,10 +316,10 @@ SUB EVAL EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element A=Z%(A1,1)+1:CALL EVAL - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE @@ -330,24 +332,25 @@ SUB EVAL GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4=X%(X):X=X-1: REM pop previous env + GOSUB POP_Q:E4=Q: REM pop previous env REM release previous environment if not the current EVAL env - IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE + GOSUB PEEK_Q_2 + IF E4<>Q THEN AY=E4:GOSUB RELEASE - A2=X%(X):X=X-1: REM pop A2 + GOSUB POP_Q:A2=Q: REM pop A2 A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: A=Z%(A,1): REM rest - X=X+1:X%(X)=A: REM push/save A + GOSUB PUSH_A: REM push/save A CALL EVAL_AST REM cleanup AY=R: REM get eval'd list for release - A=X%(X):X=X-1: REM pop/restore original A for LAST + GOSUB POP_A: REM pop/restore original A for LAST GOSUB LAST: REM get last element for return A=R: REM new recur AST @@ -365,19 +368,20 @@ SUB EVAL EVAL_QUASIQUOTE: R=Z%(A,1)+1:GOSUB DEREF_R A=R:CALL QUASIQUOTE + A=R REM add quasiquote result to pending release queue to free when REM next lower EVAL level returns (LV) - Y=Y+1:Y%(Y,0)=R:Y%(Y,1)=LV + GOSUB PEND_A_LV - A=R:GOTO EVAL_TCO_RECUR: REM TCO loop + GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DEFMACRO: REM PRINT "defmacro!" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q: REM push A1 A=A2:CALL EVAL: REM eval A2 - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q: REM pop A1 REM change function to macro Z%(R,0)=Z%(R,0)+1 @@ -400,9 +404,9 @@ SUB EVAL REM PRINT "try*" GOSUB EVAL_GET_A1: REM set A1, A2, and A3 - X=X+1:X%(X)=A: REM push/save A + GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL: REM eval A1 - A=X%(X):X=X-1: REM pop/restore A + GOSUB POP_A: REM pop/restore A REM if there is not error or catch block then return IF ER=-2 OR Z%(A,1)=0 THEN GOTO EVAL_RETURN @@ -429,11 +433,9 @@ SUB EVAL EVAL_IF: GOSUB EVAL_GET_A1: REM set A1 - REM push A - X=X+1:X%(X)=A + GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL - REM pop A - A=X%(X):X=X-1 + GOSUB POP_A: REM pop/restore A IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: @@ -460,7 +462,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - X=X+1:X%(X)=R + GOSUB PUSH_R F=R+1 @@ -474,7 +476,7 @@ SUB EVAL IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R=X%(X):X=X-1 + GOSUB POP_R ER=-1:E$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: @@ -485,7 +487,8 @@ SUB EVAL EVAL_DO_FUNCTION_SKIP: REM pop and release f/args - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: @@ -497,16 +500,18 @@ SUB EVAL REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE + GOSUB PEEK_Q_2 + IF E4<>Q THEN AY=E4:GOSUB RELEASE REM claim the AST before releasing the list containing it A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) - Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV+1 + LV=LV+1:GOSUB PEND_A_LV:LV=LV-1 REM pop and release f/args - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE REM A set above E=R:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -516,7 +521,8 @@ SUB EVAL REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE + GOSUB PEEK_Q_1 + IF E<>Q THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level @@ -528,7 +534,8 @@ SUB EVAL #qbasic T=0 REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index 98994db80e..7f055a23ca 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -2,8 +2,8 @@ REM POKE 1, PEEK(1)AND248: REM enable all ROM areas as RAM REM POKE 55,0: POKE 56,192: CLR: REM move BASIC end from $A000 to $C000 GOTO MAIN -REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'types.in.bas' +REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' REM $INCLUDE: 'printer.in.bas' REM $INCLUDE: 'env.in.bas' @@ -42,13 +42,11 @@ SUB QUASIQUOTE GOTO QQ_DONE QQ_SPLICE_UNQUOTE: - REM push A on the stack - X=X+1:X%(X)=A + GOSUB PUSH_A REM rest of cases call quasiquote on ast[1..] A=Z%(A,1):CALL QUASIQUOTE T6=R - REM pop A off the stack - A=X%(X):X=X-1 + GOSUB POP_A REM set A to ast[0] for last two cases A=A+1:GOSUB DEREF_A @@ -73,13 +71,11 @@ SUB QUASIQUOTE QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - REM push T6 on the stack - X=X+1:X%(X)=T6 + Q=T6:GOSUB PUSH_Q REM A set above to ast[0] CALL QUASIQUOTE B=R - REM pop T6 off the stack - T6=X%(X):X=X-1 + GOSUB POP_Q:T6=Q B$="cons":T=5:GOSUB STRING:C=R A=T6:GOSUB LIST3 @@ -92,8 +88,7 @@ END SUB REM MACROEXPAND(A, E) -> A: SUB MACROEXPAND - REM push original A - X=X+1:X%(X)=A + GOSUB PUSH_A MACROEXPAND_LOOP: REM list? @@ -109,26 +104,27 @@ SUB MACROEXPAND B=T4:GOSUB DEREF_B REM macro? IF (Z%(B,0)AND 31)<>11 THEN GOTO MACROEXPAND_DONE - + F=B:AR=Z%(A,1):CALL APPLY A=R - AY=X%(X) + GOSUB PEEK_Q:AY=Q REM if previous A was not the first A into macroexpand (i.e. an REM intermediate form) then free it - IF A<>AY THEN Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV - + IF A<>AY THEN GOSUB PEND_A_LV + IF ER<>-2 THEN GOTO MACROEXPAND_DONE GOTO MACROEXPAND_LOOP MACROEXPAND_DONE: - X=X-1: REM pop original A + GOSUB POP_Q: REM pop original A END SUB REM EVAL_AST(A, E) -> R SUB EVAL_AST REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A IF ER<>-2 THEN GOTO EVAL_AST_RETURN @@ -152,29 +148,29 @@ SUB EVAL_AST REM allocate the first entry (T already set above) L=0:N=0:GOSUB ALLOC - REM make space on the stack - X=X+4 REM push type of sequence - X%(X-3)=T + Q=T:GOSUB PUSH_Q REM push sequence index - X%(X-2)=-1 + Q=0:GOSUB PUSH_Q REM push future return value (new sequence) - X%(X-1)=R + GOSUB PUSH_R REM push previous new sequence entry - X%(X)=R + GOSUB PUSH_R EVAL_AST_SEQ_LOOP: - REM update index - X%(X-2)=X%(X-2)+1 - REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element - IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + Q=6:GOSUB PEEK_Q_Q + IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if hashmap, skip eval of even entries (keys) - IF (X%(X-3)=8) AND ((X%(X-2)AND 1)=0) THEN GOTO EVAL_AST_DO_REF + Q=3:GOSUB PEEK_Q_Q:T=Q + REM get and update index + GOSUB PEEK_Q_2 + Q=Q+1:GOSUB PUT_Q_2 + IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF GOTO EVAL_AST_DO_EVAL EVAL_AST_DO_REF: @@ -191,36 +187,41 @@ SUB EVAL_AST EVAL_AST_ADD_VALUE: REM update previous value pointer to evaluated entry - Z%(X%(X)+1,1)=R + GOSUB PEEK_Q + Z%(Q+1,1)=R IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM allocate the next entry REM same new sequence entry type - T=X%(X-3):L=0:N=0:GOSUB ALLOC + Q=3:GOSUB PEEK_Q_Q:T=Q + L=0:N=0:GOSUB ALLOC REM update previous sequence entry value to point to new entry - Z%(X%(X),1)=R + GOSUB PEEK_Q + Z%(Q,1)=R REM update previous ptr to current entry - X%(X)=R + Q=R:GOSUB PUT_Q REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: + GOSUB PEEK_Q_1 REM if no error, get return value (new seq) - IF ER=-2 THEN R=X%(X-1) + IF ER=-2 THEN R=Q REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=X%(X-1):GOSUB RELEASE + IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE REM pop previous, return, index and type - X=X-4 + GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q GOTO EVAL_AST_RETURN EVAL_AST_RETURN: REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB REM EVAL(A, E) -> R @@ -228,7 +229,8 @@ SUB EVAL LV=LV+1: REM track basic return stack level REM push A and E on the stack - X=X+2:X%(X-1)=E:X%(X)=A + Q=E:GOSUB PUSH_Q + GOSUB PUSH_A REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0)) @@ -291,9 +293,9 @@ SUB EVAL REM PRINT "def!" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q A=A2:CALL EVAL: REM eval a2 - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q IF ER<>-2 THEN GOTO EVAL_RETURN @@ -305,8 +307,8 @@ SUB EVAL REM PRINT "let*" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A2: REM push/save A2 - X=X+1:X%(X)=E: REM push env for for later release + Q=A2:GOSUB PUSH_Q: REM push/save A2 + Q=E:GOSUB PUSH_Q: REM push env for for later release REM create new environment with outer as current environment C=E:GOSUB ENV_NEW @@ -314,10 +316,10 @@ SUB EVAL EVAL_LET_LOOP: IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element A=Z%(A1,1)+1:CALL EVAL - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE @@ -330,24 +332,25 @@ SUB EVAL GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - E4=X%(X):X=X-1: REM pop previous env + GOSUB POP_Q:E4=Q: REM pop previous env REM release previous environment if not the current EVAL env - IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE + GOSUB PEEK_Q_2 + IF E4<>Q THEN AY=E4:GOSUB RELEASE - A2=X%(X):X=X-1: REM pop A2 + GOSUB POP_Q:A2=Q: REM pop A2 A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: A=Z%(A,1): REM rest - X=X+1:X%(X)=A: REM push/save A + GOSUB PUSH_A: REM push/save A CALL EVAL_AST REM cleanup AY=R: REM get eval'd list for release - A=X%(X):X=X-1: REM pop/restore original A for LAST + GOSUB POP_A: REM pop/restore original A for LAST GOSUB LAST: REM get last element for return A=R: REM new recur AST @@ -365,19 +368,20 @@ SUB EVAL EVAL_QUASIQUOTE: R=Z%(A,1)+1:GOSUB DEREF_R A=R:CALL QUASIQUOTE + A=R REM add quasiquote result to pending release queue to free when REM next lower EVAL level returns (LV) - Y=Y+1:Y%(Y,0)=R:Y%(Y,1)=LV + GOSUB PEND_A_LV - A=R:GOTO EVAL_TCO_RECUR: REM TCO loop + GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DEFMACRO: REM PRINT "defmacro!" GOSUB EVAL_GET_A2: REM set A1 and A2 - X=X+1:X%(X)=A1: REM push A1 + Q=A1:GOSUB PUSH_Q: REM push A1 A=A2:CALL EVAL: REM eval A2 - A1=X%(X):X=X-1: REM pop A1 + GOSUB POP_Q:A1=Q: REM pop A1 REM change function to macro Z%(R,0)=Z%(R,0)+1 @@ -400,9 +404,9 @@ SUB EVAL REM PRINT "try*" GOSUB EVAL_GET_A1: REM set A1, A2, and A3 - X=X+1:X%(X)=A: REM push/save A + GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL: REM eval A1 - A=X%(X):X=X-1: REM pop/restore A + GOSUB POP_A: REM pop/restore A REM if there is not error or catch block then return IF ER=-2 OR Z%(A,1)=0 THEN GOTO EVAL_RETURN @@ -429,11 +433,9 @@ SUB EVAL EVAL_IF: GOSUB EVAL_GET_A1: REM set A1 - REM push A - X=X+1:X%(X)=A + GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL - REM pop A - A=X%(X):X=X-1 + GOSUB POP_A: REM pop/restore A IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: @@ -460,7 +462,7 @@ SUB EVAL IF ER<>-2 THEN GOTO EVAL_RETURN REM push f/args for release after call - X=X+1:X%(X)=R + GOSUB PUSH_R F=R+1 @@ -474,7 +476,7 @@ SUB EVAL IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller - R=X%(X):X=X-1 + GOSUB POP_R ER=-1:E$="apply of non-function":GOTO EVAL_RETURN EVAL_DO_FUNCTION: @@ -485,7 +487,8 @@ SUB EVAL EVAL_DO_FUNCTION_SKIP: REM pop and release f/args - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: @@ -497,16 +500,18 @@ SUB EVAL REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) - IF E4<>X%(X-2) THEN AY=E4:GOSUB RELEASE + GOSUB PEEK_Q_2 + IF E4<>Q THEN AY=E4:GOSUB RELEASE REM claim the AST before releasing the list containing it A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) - Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV+1 + LV=LV+1:GOSUB PEND_A_LV:LV=LV-1 REM pop and release f/args - AY=X%(X):X=X-1:GOSUB RELEASE + GOSUB POP_Q:AY=Q + GOSUB RELEASE REM A set above E=R:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -516,7 +521,8 @@ SUB EVAL REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) REM release environment if not the top one on the stack - IF E<>X%(X-1) THEN AY=E:GOSUB RELEASE + GOSUB PEEK_Q_1 + IF E<>Q THEN AY=E:GOSUB RELEASE LV=LV-1: REM track basic return stack level @@ -528,7 +534,8 @@ SUB EVAL #qbasic T=0 REM pop A and E off the stack - E=X%(X-1):A=X%(X):X=X-2 + GOSUB POP_A + GOSUB POP_Q:E=Q END SUB diff --git a/basic/types.in.bas b/basic/types.in.bas index 0a70f234cc..7676128e30 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -28,10 +28,12 @@ INIT_MEMORY: #cbm T=FRE(0) #qbasic T=0 - Z1=2048+1024+512+128: REM Z% (boxed memory) size (4 bytes each) + Z1=3950: REM Z% (boxed memory) size (4 bytes each) Z2=200: REM S$/S% (string memory) size (3+2 bytes each) - Z3=200: REM X% (call stack) size (2 bytes each) - Z4=64: REM Y% (release stack) size (4 bytes each) + #qbasic Z3=200: REM X% (call stack) size (2 bytes each) + #cbm Z3=49152: REM X starting point at $C000 (2 bytes each) + #qbasic Z4=64: REM Y% (release stack) size (4 bytes each) + #cbm Z4=52992: REM Y starting point at $CF00 (4 bytes each) REM global error state REM -2 : no error @@ -68,16 +70,78 @@ INIT_MEMORY: S=0:DIM S$(Z2):DIM S%(Z2) REM call/logic stack - X=-1:DIM X%(Z3): REM stack of Z% indexes + #qbasic X=-1:DIM X%(Z3): REM stack of Z% indexes + #cbm X=Z3-2: REM stack of 1920 Z% indexes at $C000 REM pending release stack - Y=-1:DIM Y%(Z4,1): REM stack of Z% indexes and level/LV values + #qbasic Y=-1:DIM Y%(Z4,1): REM stack of Z% indexes and level/LV values + #cbm Y=Z4-4: REM stack of 64 Y% indexes/levels at $CF00 BT=TI RETURN +REM stack functions + +#qbasic PUSH_A: +#qbasic X=X+1:X%(X)=A:RETURN +#qbasic POP_A: +#qbasic A=X%(X):X=X-1:RETURN +#qbasic +#qbasic PUSH_R: +#qbasic X=X+1:X%(X)=R:RETURN +#qbasic POP_R: +#qbasic R=X%(X):X=X-1:RETURN +#qbasic +#qbasic PUSH_Q: +#qbasic X=X+1:X%(X)=Q:RETURN +#qbasic POP_Q: +#qbasic Q=X%(X):X=X-1:RETURN +#qbasic PEEK_Q: +#qbasic Q=X%(X):RETURN +#qbasic PEEK_Q_1: +#qbasic Q=X%(X-1):RETURN +#qbasic PEEK_Q_2: +#qbasic Q=X%(X-2):RETURN +#qbasic PEEK_Q_Q: +#qbasic Q=X%(X-Q):RETURN +#qbasic PUT_Q: +#qbasic X%(X)=Q:RETURN +#qbasic PUT_Q_1: +#qbasic X%(X-1)=Q:RETURN +#qbasic PUT_Q_2: +#qbasic X%(X-2)=Q:RETURN + +#cbm PUSH_A: +#cbm X=X+2:POKE X,A AND255:POKE X+1,A/256:RETURN +#cbm POP_A: +#cbm A=PEEK(X)+PEEK(X+1)*256:X=X-2:RETURN +#cbm +#cbm PUSH_R: +#cbm X=X+2:POKE X,R AND255:POKE X+1,R/256:RETURN +#cbm POP_R: +#cbm R=PEEK(X)+PEEK(X+1)*256:X=X-2:RETURN +#cbm +#cbm PUSH_Q: +#cbm X=X+2:POKE X,Q AND255:POKE X+1,Q/256:RETURN +#cbm POP_Q: +#cbm Q=PEEK(X)+PEEK(X+1)*256:X=X-2:RETURN +#cbm PEEK_Q: +#cbm Q=PEEK(X)+PEEK(X+1)*256:RETURN +#cbm PEEK_Q_1: +#cbm Q=PEEK(X-2)+PEEK(X-1)*256:RETURN +#cbm PEEK_Q_2: +#cbm Q=PEEK(X-4)+PEEK(X-3)*256:RETURN +#cbm PEEK_Q_Q: +#cbm Q=PEEK(X-Q*2)+PEEK(X-Q*2+1)*256:RETURN +#cbm PUT_Q: +#cbm POKE X,Q AND255:POKE X+1,Q/256:RETURN +#cbm PUT_Q_1: +#cbm POKE X-2,Q AND255:POKE X-1,Q/256:RETURN +#cbm PUT_Q_2: +#cbm POKE X-4,Q AND255:POKE X-3,Q/256:RETURN + REM memory functions REM ALLOC(T,L) -> R @@ -156,7 +220,7 @@ RELEASE: IF RC=0 THEN RETURN REM pop next object to release, decrease remaining count - AY=X%(X):X=X-1 + GOSUB POP_Q:AY=Q RC=RC-1 RELEASE_ONE: @@ -209,34 +273,41 @@ RELEASE: IF U7=0 THEN GOTO RELEASE_SIMPLE_2 IF Z%(AY+1,0)<>14 THEN ER=-1:E$="invalid list value"+STR$(AY+1):RETURN REM add value and next element to stack - RC=RC+2:X=X+2 - X%(X-1)=Z%(AY+1,1):X%(X)=U7 + RC=RC+2 + Q=Z%(AY+1,1):GOSUB PUSH_Q + Q=U7:GOSUB PUSH_Q GOTO RELEASE_SIMPLE_2 RELEASE_ATOM: REM add contained/referred value - RC=RC+1:X=X+1:X%(X)=U7 + RC=RC+1 + Q=U7:GOSUB PUSH_Q REM free the atom itself GOTO RELEASE_SIMPLE RELEASE_MAL_FUNCTION: REM add ast, params and environment to stack - RC=RC+3:X=X+3 - X%(X-2)=U7:X%(X-1)=Z%(AY+1,0):X%(X)=Z%(AY+1,1) + RC=RC+3 + Q=U7:GOSUB PUSH_Q + Q=Z%(AY+1,0):GOSUB PUSH_Q + Q=Z%(AY+1,1):GOSUB PUSH_Q REM free the current 2 element mal_function and continue SZ=2:GOSUB FREE GOTO RELEASE_TOP RELEASE_METADATA: REM add object and metadata object - RC=RC+2:X=X+2 - X%(X-1)=U7:X%(X)=Z%(AY+1,1) + RC=RC+2 + Q=U7:GOSUB PUSH_Q + Q=Z%(AY+1,1):GOSUB PUSH_Q SZ=2:GOSUB FREE GOTO RELEASE_TOP RELEASE_ENV: REM add the hashmap data to the stack - RC=RC+1:X=X+1:X%(X)=U7 + RC=RC+1 + Q=U7:GOSUB PUSH_Q REM if no outer set IF Z%(AY+1,1)=-1 THEN GOTO RELEASE_ENV_FREE REM add outer environment to the stack - RC=RC+1:X=X+1:X%(X)=Z%(AY+1,1) + RC=RC+1 + Q=Z%(AY+1,1):GOSUB PUSH_Q RELEASE_ENV_FREE: REM free the current 2 element environment and continue SZ=2:GOSUB FREE @@ -244,19 +315,39 @@ RELEASE: RELEASE_REFERENCE: IF U7=0 THEN GOTO RELEASE_SIMPLE REM add the referred element to the stack - RC=RC+1:X=X+1:X%(X)=U7 + RC=RC+1 + Q=U7:GOSUB PUSH_Q REM free the current element and continue SZ=1:GOSUB FREE GOTO RELEASE_TOP -REM RELEASE_PEND(LV) -> nil -RELEASE_PEND: - IF Y<0 THEN RETURN - IF Y%(Y,1)<=LV THEN RETURN - REM PRINT "RELEASE_PEND releasing:"+STR$(Y%(Y,0)) - AY=Y%(Y,0):GOSUB RELEASE - Y=Y-1 - GOTO RELEASE_PEND + +REM release stack functions + +#qbasic PEND_A_LV: +#qbasic Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV:RETURN +#qbasic +#qbasic REM RELEASE_PEND(LV) -> nil +#qbasic RELEASE_PEND: +#qbasic IF Y<0 THEN RETURN +#qbasic IF Y%(Y,1)<=LV THEN RETURN +#qbasic REM PRINT "RELEASE_PEND releasing:"+STR$(Y%(Y,0)) +#qbasic AY=Y%(Y,0):GOSUB RELEASE +#qbasic Y=Y-1 +#qbasic GOTO RELEASE_PEND + +#cbm PEND_A_LV: +#cbm Y=Y+4:POKE Y,A AND255:POKE Y+1,A/256 +#cbm POKE Y+2,LV AND255:POKE Y+3,LV/256:RETURN +#cbm +#cbm REM RELEASE_PEND(LV) -> nil +#cbm RELEASE_PEND: +#cbm IF Y R DEREF_R: @@ -287,7 +378,8 @@ EQUAL_Q: GOSUB DEREF_B REM push A and B - X=X+2:X%(X-1)=A:X%(X)=B + GOSUB PUSH_A + Q=B:GOSUB PUSH_Q ED=ED+1 T1=Z%(A,0)AND 31 @@ -308,9 +400,11 @@ EQUAL_Q: EQUAL_Q_SEQ_CONTINUE: REM next elements of the sequences - A=X%(X-1):B=X%(X) + GOSUB PEEK_Q_1:A=Q + GOSUB PEEK_Q:B=Q A=Z%(A,1):B=Z%(B,1) - X%(X-1)=A:X%(X)=B + Q=A:GOSUB PUT_Q_1 + Q=B:GOSUB PUT_Q GOTO EQUAL_Q_SEQ EQUAL_Q_HM: @@ -318,7 +412,9 @@ EQUAL_Q: GOTO EQUAL_Q_DONE EQUAL_Q_DONE: - X=X-2: REM pop current A and B + REM pop current A and B + GOSUB POP_Q + GOSUB POP_Q ED=ED-1 IF R>-1 AND ED>0 THEN GOTO EQUAL_Q_DONE: REM unwind IF ED=0 AND R=-1 THEN R=1 diff --git a/basic/variables.txt b/basic/variables.txt index 5013f22309..35d917aa22 100644 --- a/basic/variables.txt +++ b/basic/variables.txt @@ -43,6 +43,7 @@ N : ALLOC* Z%(R+1,1) default R : common return value R$ : common string return value T : type arg, common temp +Q : PUSH*, POP*, PEEK* return value (and PEEK_Q_Q call arg) AY : RELEASE/FREE arg AZ : PR_STR arg @@ -87,4 +88,4 @@ U7 : RELEASE Unused: -O, Q, U, V, W +O, U, V, W From 332611f520a43204fc077fc1b8e7755b9c0f0776 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 4 Nov 2016 22:52:05 -0500 Subject: [PATCH 0224/2308] README: note ChucK tested on more than just Arch. --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 18a6fa0807..038f8dc6df 100644 --- a/README.md +++ b/README.md @@ -229,8 +229,7 @@ mono ./stepX_YYY.exe *The ChucK implementation was created by [Vasilij Schneidermann](https://github.com/wasamasa)* -The ChucK implementation has been tested with ChucK 1.3.5.2 on Arch -Linux. +The ChucK implementation has been tested with ChucK 1.3.5.2. ``` cd chuck From 1e7b6f841f0b0d2c27742f71d6d11a24e638f8c7 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 4 Nov 2016 22:52:58 -0500 Subject: [PATCH 0225/2308] Ada: add ada/obj to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e5becc91af..54c0158517 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ logs old +ada/obj/ awk/mal.awk bash/mal.sh clojure/mal.jar From 206e6197e8ddf88cfa7f53bc961922add4121806 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 4 Nov 2016 22:53:20 -0500 Subject: [PATCH 0226/2308] Basic: add pr-memory-summary core function. - Remove test POKE commands. --- basic/core.in.bas | 11 +++++------ basic/step9_try.in.bas | 2 -- basic/stepA_mal.in.bas | 2 -- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/basic/core.in.bas b/basic/core.in.bas index 8788e3d1f0..f0d14123d9 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -208,8 +208,7 @@ DO_FUNCTION: DO_40_49: ON G-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_CONJ,DO_SEQ,DO_WITH_META DO_50_59: - ON G-49 GOTO DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_EVAL,DO_READ_FILE - REM ,DO_PR_MEMORY_SUMMARY + ON G-49 GOTO DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_EVAL,DO_READ_FILE,DO_PR_MEMORY_SUMMARY DO_EQUAL_Q: A=AA:B=AB:GOSUB EQUAL_Q @@ -532,9 +531,9 @@ DO_FUNCTION: REM DO_PR_MEMORY: REM P1=ZT:P2=-1:GOSUB PR_MEMORY REM RETURN - REM DO_PR_MEMORY_SUMMARY: - REM GOSUB PR_MEMORY_SUMMARY - REM RETURN + DO_PR_MEMORY_SUMMARY: + GOSUB PR_MEMORY_SUMMARY + RETURN DO_EVAL: Q=E:GOSUB PUSH_Q: REM push/save environment @@ -620,7 +619,7 @@ INIT_CORE_NS: K$="eval":A=55:GOSUB INIT_CORE_SET_FUNCTION K$="read-file":A=56:GOSUB INIT_CORE_SET_FUNCTION - REM K$="pr-memory-summary":A=57:GOSUB INIT_CORE_SET_FUNCTION + K$="pr-memory-summary":A=57:GOSUB INIT_CORE_SET_FUNCTION REM these are in DO_TCO_FUNCTION K$="apply":A=61:GOSUB INIT_CORE_SET_FUNCTION diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index 2afccdb4cf..45a6d5b516 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -1,5 +1,3 @@ -REM POKE 1, PEEK(1)AND248: REM enable all ROM areas as RAM -REM POKE 55,0: POKE 56,192: CLR: REM move BASIC end from $A000 to $C000 GOTO MAIN REM $INCLUDE: 'types.in.bas' diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index 7f055a23ca..3ad10c30bf 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -1,5 +1,3 @@ -REM POKE 1, PEEK(1)AND248: REM enable all ROM areas as RAM -REM POKE 55,0: POKE 56,192: CLR: REM move BASIC end from $A000 to $C000 GOTO MAIN REM $INCLUDE: 'types.in.bas' From f9f1cec9cc45539fbf3698013f03791863dc931d Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sun, 6 Nov 2016 17:20:03 -0600 Subject: [PATCH 0227/2308] Basic: memory savings and variable simplifications. In core move incrementing of function index into INIT_CORE_SET_FUNCTION. Switch 3 IF GOTO to ON GOTO. Reuse some temporary variables. Saves about 480 bytes. Bump value array from 3950 to 4096. This allows step4 (sumdown 2) to pass. Previously only (sumdown 1) passed. --- basic/core.in.bas | 157 +++++++++++++++++++------------------- basic/env.in.bas | 14 ++-- basic/printer.in.bas | 20 ++--- basic/reader.in.bas | 16 ++-- basic/readline.in.bas | 13 ++-- basic/step2_eval.in.bas | 6 +- basic/step3_env.in.bas | 4 +- basic/step8_macros.in.bas | 2 +- basic/step9_try.in.bas | 2 +- basic/stepA_mal.in.bas | 13 ++-- basic/types.in.bas | 107 +++++++++++++------------- basic/variables.txt | 46 +++++++---- 12 files changed, 210 insertions(+), 190 deletions(-) diff --git a/basic/core.in.bas b/basic/core.in.bas index f0d14123d9..2d0863e531 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -9,9 +9,7 @@ SUB APPLY REM if metadata, get the actual object IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) - IF (Z%(F,0)AND 31)=9 THEN GOTO APPLY_FUNCTION - IF (Z%(F,0)AND 31)=10 THEN GOTO APPLY_MAL_FUNCTION - IF (Z%(F,0)AND 31)=11 THEN GOTO APPLY_MAL_FUNCTION + ON (Z%(F,0)AND 31)-8 GOTO APPLY_FUNCTION,APPLY_MAL_FUNCTION,APPLY_MAL_FUNCTION APPLY_FUNCTION: REM regular function @@ -50,16 +48,16 @@ SUB DO_TCO_FUNCTION DO_APPLY: F=AA AR=Z%(AR,1) - B=AR:GOSUB COUNT:R4=R + B=AR:GOSUB COUNT:C=R A=Z%(AR+1,1) REM no intermediate args, but not a list, so convert it first - IF R4<=1 AND (Z%(A,0)AND 31)<>6 THEN T=6:GOSUB FORCE_SEQ_TYPE:GOTO DO_APPLY_2 + IF C<=1 AND (Z%(A,0)AND 31)<>6 THEN T=6:GOSUB FORCE_SEQ_TYPE:GOTO DO_APPLY_2 REM no intermediate args, just call APPLY directly - IF R4<=1 THEN GOTO DO_APPLY_1 + IF C<=1 THEN GOTO DO_APPLY_1 REM prepend intermediate args to final args element - A=AR:B=0:C=R4-1:GOSUB SLICE + A=AR:B=0:C=C-1:GOSUB SLICE REM release the terminator of new list (we skip over it) AY=Z%(R6,1):GOSUB RELEASE REM attach end of slice to final args element @@ -258,20 +256,20 @@ DO_FUNCTION: RETURN DO_PR_STR: - AZ=AR:B=1:SE$=" ":GOSUB PR_STR_SEQ + AZ=AR:B=1:B$=" ":GOSUB PR_STR_SEQ B$=R$:T=4:GOSUB STRING RETURN DO_STR: - AZ=AR:B=0:SE$="":GOSUB PR_STR_SEQ + AZ=AR:B=0:B$="":GOSUB PR_STR_SEQ B$=R$:T=4:GOSUB STRING RETURN DO_PRN: - AZ=AR:B=1:SE$=" ":GOSUB PR_STR_SEQ + AZ=AR:B=1:B$=" ":GOSUB PR_STR_SEQ PRINT R$ R=0 RETURN DO_PRINTLN: - AZ=AR:B=0:SE$=" ":GOSUB PR_STR_SEQ + AZ=AR:B=0:B$=" ":GOSUB PR_STR_SEQ PRINT R$ R=0 RETURN @@ -549,6 +547,7 @@ DO_FUNCTION: INIT_CORE_SET_FUNCTION: GOSUB NATIVE_FUNCTION C=R:GOSUB ENV_SET_S + A=A+1 RETURN REM INIT_CORE_NS(E) @@ -556,74 +555,76 @@ INIT_CORE_NS: REM create the environment mapping REM must match DO_FUNCTION mappings - K$="=":A=1:GOSUB INIT_CORE_SET_FUNCTION - K$="throw":A=2:GOSUB INIT_CORE_SET_FUNCTION - K$="nil?":A=3:GOSUB INIT_CORE_SET_FUNCTION - K$="true?":A=4:GOSUB INIT_CORE_SET_FUNCTION - K$="false?":A=5:GOSUB INIT_CORE_SET_FUNCTION - K$="string?":A=6:GOSUB INIT_CORE_SET_FUNCTION - K$="symbol":A=7:GOSUB INIT_CORE_SET_FUNCTION - K$="symbol?":A=8:GOSUB INIT_CORE_SET_FUNCTION - K$="keyword":A=9:GOSUB INIT_CORE_SET_FUNCTION - K$="keyword?":A=10:GOSUB INIT_CORE_SET_FUNCTION - - K$="pr-str":A=11:GOSUB INIT_CORE_SET_FUNCTION - K$="str":A=12:GOSUB INIT_CORE_SET_FUNCTION - K$="prn":A=13:GOSUB INIT_CORE_SET_FUNCTION - K$="println":A=14:GOSUB INIT_CORE_SET_FUNCTION - K$="read-string":A=15:GOSUB INIT_CORE_SET_FUNCTION - K$="readline":A=16:GOSUB INIT_CORE_SET_FUNCTION - K$="slurp":A=17:GOSUB INIT_CORE_SET_FUNCTION - - K$="<":A=18:GOSUB INIT_CORE_SET_FUNCTION - K$="<=":A=19:GOSUB INIT_CORE_SET_FUNCTION - K$=">":A=20:GOSUB INIT_CORE_SET_FUNCTION - K$=">=":A=21:GOSUB INIT_CORE_SET_FUNCTION - K$="+":A=22:GOSUB INIT_CORE_SET_FUNCTION - K$="-":A=23:GOSUB INIT_CORE_SET_FUNCTION - K$="*":A=24:GOSUB INIT_CORE_SET_FUNCTION - K$="/":A=25:GOSUB INIT_CORE_SET_FUNCTION - K$="time-ms":A=26:GOSUB INIT_CORE_SET_FUNCTION - - K$="list":A=27:GOSUB INIT_CORE_SET_FUNCTION - K$="list?":A=28:GOSUB INIT_CORE_SET_FUNCTION - K$="vector":A=29:GOSUB INIT_CORE_SET_FUNCTION - K$="vector?":A=30:GOSUB INIT_CORE_SET_FUNCTION - K$="hash-map":A=31:GOSUB INIT_CORE_SET_FUNCTION - K$="map?":A=32:GOSUB INIT_CORE_SET_FUNCTION - K$="assoc":A=33:GOSUB INIT_CORE_SET_FUNCTION - K$="dissoc":A=34:GOSUB INIT_CORE_SET_FUNCTION - K$="get":A=35:GOSUB INIT_CORE_SET_FUNCTION - K$="contains?":A=36:GOSUB INIT_CORE_SET_FUNCTION - K$="keys":A=37:GOSUB INIT_CORE_SET_FUNCTION - K$="vals":A=38:GOSUB INIT_CORE_SET_FUNCTION - - K$="sequential?":A=39:GOSUB INIT_CORE_SET_FUNCTION - K$="cons":A=40:GOSUB INIT_CORE_SET_FUNCTION - K$="concat":A=41:GOSUB INIT_CORE_SET_FUNCTION - K$="nth":A=42:GOSUB INIT_CORE_SET_FUNCTION - K$="first":A=43:GOSUB INIT_CORE_SET_FUNCTION - K$="rest":A=44:GOSUB INIT_CORE_SET_FUNCTION - K$="empty?":A=45:GOSUB INIT_CORE_SET_FUNCTION - K$="count":A=46:GOSUB INIT_CORE_SET_FUNCTION - - K$="conj":A=47:GOSUB INIT_CORE_SET_FUNCTION - K$="seq":A=48:GOSUB INIT_CORE_SET_FUNCTION - - K$="with-meta":A=49:GOSUB INIT_CORE_SET_FUNCTION - K$="meta":A=50:GOSUB INIT_CORE_SET_FUNCTION - K$="atom":A=51:GOSUB INIT_CORE_SET_FUNCTION - K$="atom?":A=52:GOSUB INIT_CORE_SET_FUNCTION - K$="deref":A=53:GOSUB INIT_CORE_SET_FUNCTION - K$="reset!":A=54:GOSUB INIT_CORE_SET_FUNCTION - - K$="eval":A=55:GOSUB INIT_CORE_SET_FUNCTION - K$="read-file":A=56:GOSUB INIT_CORE_SET_FUNCTION - K$="pr-memory-summary":A=57:GOSUB INIT_CORE_SET_FUNCTION + A=1 + K$="=":GOSUB INIT_CORE_SET_FUNCTION: REM A=1 + K$="throw":GOSUB INIT_CORE_SET_FUNCTION: REM A=2 + K$="nil?":GOSUB INIT_CORE_SET_FUNCTION: REM A=3 + K$="true?":GOSUB INIT_CORE_SET_FUNCTION: REM A=4 + K$="false?":GOSUB INIT_CORE_SET_FUNCTION: REM A=5 + K$="string?":GOSUB INIT_CORE_SET_FUNCTION: REM A=6 + K$="symbol":GOSUB INIT_CORE_SET_FUNCTION: REM A=7 + K$="symbol?":GOSUB INIT_CORE_SET_FUNCTION: REM A=8 + K$="keyword":GOSUB INIT_CORE_SET_FUNCTION: REM A=9 + K$="keyword?":GOSUB INIT_CORE_SET_FUNCTION: REM A=10 + + K$="pr-str":GOSUB INIT_CORE_SET_FUNCTION: REM A=11 + K$="str":GOSUB INIT_CORE_SET_FUNCTION: REM A=12 + K$="prn":GOSUB INIT_CORE_SET_FUNCTION: REM A=13 + K$="println":GOSUB INIT_CORE_SET_FUNCTION: REM A=14 + K$="read-string":GOSUB INIT_CORE_SET_FUNCTION: REM A=15 + K$="readline":GOSUB INIT_CORE_SET_FUNCTION: REM A=16 + K$="slurp":GOSUB INIT_CORE_SET_FUNCTION: REM A=17 + + K$="<":GOSUB INIT_CORE_SET_FUNCTION: REM A=18 + K$="<=":GOSUB INIT_CORE_SET_FUNCTION: REM A=19 + K$=">":GOSUB INIT_CORE_SET_FUNCTION: REM A=20 + K$=">=":GOSUB INIT_CORE_SET_FUNCTION: REM A=21 + K$="+":GOSUB INIT_CORE_SET_FUNCTION: REM A=22 + K$="-":GOSUB INIT_CORE_SET_FUNCTION: REM A=23 + K$="*":GOSUB INIT_CORE_SET_FUNCTION: REM A=24 + K$="/":GOSUB INIT_CORE_SET_FUNCTION: REM A=25 + K$="time-ms":GOSUB INIT_CORE_SET_FUNCTION: REM A=26 + + K$="list":GOSUB INIT_CORE_SET_FUNCTION: REM A=27 + K$="list?":GOSUB INIT_CORE_SET_FUNCTION: REM A=28 + K$="vector":GOSUB INIT_CORE_SET_FUNCTION: REM A=29 + K$="vector?":GOSUB INIT_CORE_SET_FUNCTION: REM A=30 + K$="hash-map":GOSUB INIT_CORE_SET_FUNCTION: REM A=31 + K$="map?":GOSUB INIT_CORE_SET_FUNCTION: REM A=32 + K$="assoc":GOSUB INIT_CORE_SET_FUNCTION: REM A=33 + K$="dissoc":GOSUB INIT_CORE_SET_FUNCTION: REM A=34 + K$="get":GOSUB INIT_CORE_SET_FUNCTION: REM A=35 + K$="contains?":GOSUB INIT_CORE_SET_FUNCTION: REM A=36 + K$="keys":GOSUB INIT_CORE_SET_FUNCTION: REM A=37 + K$="vals":GOSUB INIT_CORE_SET_FUNCTION: REM A=38 + + K$="sequential?":GOSUB INIT_CORE_SET_FUNCTION: REM A=39 + K$="cons":GOSUB INIT_CORE_SET_FUNCTION: REM A=40 + K$="concat":GOSUB INIT_CORE_SET_FUNCTION: REM A=41 + K$="nth":GOSUB INIT_CORE_SET_FUNCTION: REM A=42 + K$="first":GOSUB INIT_CORE_SET_FUNCTION: REM A=43 + K$="rest":GOSUB INIT_CORE_SET_FUNCTION: REM A=44 + K$="empty?":GOSUB INIT_CORE_SET_FUNCTION: REM A=45 + K$="count":GOSUB INIT_CORE_SET_FUNCTION: REM A=46 + + K$="conj":GOSUB INIT_CORE_SET_FUNCTION: REM A=47 + K$="seq":GOSUB INIT_CORE_SET_FUNCTION: REM A=48 + + K$="with-meta":GOSUB INIT_CORE_SET_FUNCTION: REM A=49 + K$="meta":GOSUB INIT_CORE_SET_FUNCTION: REM A=50 + K$="atom":GOSUB INIT_CORE_SET_FUNCTION: REM A=51 + K$="atom?":GOSUB INIT_CORE_SET_FUNCTION: REM A=52 + K$="deref":GOSUB INIT_CORE_SET_FUNCTION: REM A=53 + K$="reset!":GOSUB INIT_CORE_SET_FUNCTION: REM A=54 + + K$="eval":GOSUB INIT_CORE_SET_FUNCTION: REM A=55 + K$="read-file":GOSUB INIT_CORE_SET_FUNCTION: REM A=56 + K$="pr-memory-summary":GOSUB INIT_CORE_SET_FUNCTION: REM A=57 REM these are in DO_TCO_FUNCTION - K$="apply":A=61:GOSUB INIT_CORE_SET_FUNCTION - K$="map":A=62:GOSUB INIT_CORE_SET_FUNCTION - K$="swap!":A=63:GOSUB INIT_CORE_SET_FUNCTION + A=61 + K$="apply":GOSUB INIT_CORE_SET_FUNCTION: REM A=61 + K$="map":GOSUB INIT_CORE_SET_FUNCTION: REM A=62 + K$="swap!":GOSUB INIT_CORE_SET_FUNCTION: REM A=63 RETURN diff --git a/basic/env.in.bas b/basic/env.in.bas index e2845cc91a..6c280f34c1 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -3,11 +3,11 @@ REM ENV_NEW(C) -> R ENV_NEW: REM allocate the data hashmap GOSUB HASHMAP - ET=R + AY=R REM set the outer and data pointer T=13:L=R:N=C:GOSUB ALLOC - AY=ET:GOSUB RELEASE: REM environment takes ownership + GOSUB RELEASE: REM environment takes ownership RETURN REM see RELEASE types.in.bas for environment cleanup @@ -68,15 +68,15 @@ ENV_SET_S: REM ENV_FIND(E, K) -> R REM Returns environment (R) containing K. If found, value found is -REM in T4 +REM in R4 SUB ENV_FIND T=E ENV_FIND_LOOP: H=Z%(T,1) - REM More efficient to use GET for value (R) and contains? (T3) + REM More efficient to use GET for value (R) and contains? (R3) GOSUB HASHMAP_GET - REM if we found it, save value in T4 for ENV_GET - IF T3=1 THEN T4=R:GOTO ENV_FIND_DONE + REM if we found it, save value in R4 for ENV_GET + IF R3=1 THEN R4=R:GOTO ENV_FIND_DONE T=Z%(T+1,1): REM get outer environment IF T<>-1 THEN GOTO ENV_FIND_LOOP ENV_FIND_DONE: @@ -87,6 +87,6 @@ REM ENV_GET(E, K) -> R ENV_GET: CALL ENV_FIND IF R=-1 THEN R=0:ER=-1:E$="'"+S$(Z%(K,1))+"' not found":GOTO ENV_GET_RETURN - R=T4:GOSUB DEREF_R + R=R4:GOSUB DEREF_R Z%(R,0)=Z%(R,0)+32 GOTO ENV_GET_RETURN diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 06c90cd381..ea83f5a88e 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -31,7 +31,7 @@ PR_STR: IF LEN(R$)=0 THEN GOTO PR_STRING IF MID$(R$,1,1)=CHR$(127) THEN R$=":"+MID$(R$,2,LEN(R$)-1):RETURN PR_STRING: - IF B=1 THEN PR_STRING_READABLY + IF B=1 THEN GOTO PR_STRING_READABLY RETURN PR_STRING_READABLY: S1$="\":S2$="\\":GOSUB REPLACE: REM escape backslash " @@ -49,7 +49,7 @@ PR_STR: REM save the current rendered string S$(S)=R$:S=S+1 PR_SEQ_LOOP: - IF Z%(AZ,1)=0 THEN PR_SEQ_DONE + IF Z%(AZ,1)=0 THEN GOTO PR_SEQ_DONE AZ=AZ+1:GOSUB PR_STR REM append what we just rendered it S$(S-1)=S$(S-1)+R$ @@ -95,15 +95,17 @@ PR_STR: R$="#" RETURN -REM PR_STR_SEQ(AZ, B, SE$) -> R$ +REM PR_STR_SEQ(AZ, B, B$) -> R$ +REM - B is print_readably +REM - B$ is the separator PR_STR_SEQ: - T9=AZ + V=AZ S$(S)="":S=S+1 PR_STR_SEQ_LOOP: - IF Z%(T9,1)=0 THEN S=S-1:R$=S$(S):RETURN - AZ=T9+1:GOSUB PR_STR + IF Z%(V,1)=0 THEN S=S-1:R$=S$(S):RETURN + AZ=V+1:GOSUB PR_STR REM goto the next sequence element - T9=Z%(T9,1) - IF Z%(T9,1)=0 THEN S$(S-1)=S$(S-1)+R$ - IF Z%(T9,1)<>0 THEN S$(S-1)=S$(S-1)+R$+SE$ + V=Z%(V,1) + IF Z%(V,1)=0 THEN S$(S-1)=S$(S-1)+R$ + IF Z%(V,1)<>0 THEN S$(S-1)=S$(S-1)+R$+B$ GOTO PR_STR_SEQ_LOOP diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 198ab5bbbe..0d536bc25a 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -205,25 +205,25 @@ READ_FORM: REM check read sequence depth IF SD=0 THEN RETURN - REM previous element - GOSUB PEEK_Q:T7=Q + GOSUB PEEK_Q: REM previous element REM allocate new sequence entry, set type to previous type, set REM next to previous next or previous (if first) - L=Z%(T7,1) - IF T7<9 THEN L=T7 - T8=R: REM save previous value for release + L=Z%(Q,1) + IF Q<9 THEN L=Q + AY=R: REM save previous value for release GOSUB PEEK_Q_1:T=Q N=R:GOSUB ALLOC REM list takes ownership + GOSUB RELEASE IF L<9 THEN AY=L:GOSUB RELEASE - AY=T8:GOSUB RELEASE REM if previous element is the first element then set REM the first to the new element - IF T7<9 THEN Q=R:GOSUB PUT_Q_2:GOTO READ_FORM_SKIP_FIRST + GOSUB PEEK_Q: REM previous element + IF Q<9 THEN Q=R:GOSUB PUT_Q_2:GOTO READ_FORM_SKIP_FIRST REM set previous list element to point to new element - Z%(T7,1)=R + Z%(Q,1)=R READ_FORM_SKIP_FIRST: diff --git a/basic/readline.in.bas b/basic/readline.in.bas index 1dbbcd16c6..75b7996343 100644 --- a/basic/readline.in.bas +++ b/basic/readline.in.bas @@ -2,7 +2,7 @@ REM READLINE(A$) -> R$ READLINE: EZ=0 PRINT A$; - C$="":LI$="":C=0 + C$="":R$="":C=0 READCH: #cbm GET C$ #qbasic C$=INKEY$ @@ -15,16 +15,15 @@ READLINE: IF C=127 OR C=20 THEN GOTO READCH IF (C<32 OR C>127) AND C<>13 THEN GOTO READCH PRINT C$; - IF LEN(LI$)<255 AND C$<>CHR$(13) THEN LI$=LI$+C$ - IF LEN(LI$)<255 AND C$<>CHR$(13) THEN GOTO READCH + IF LEN(R$)<255 AND C$<>CHR$(13) THEN R$=R$+C$ + IF LEN(R$)<255 AND C$<>CHR$(13) THEN GOTO READCH RL_DONE: - R$=LI$ RETURN - REM Assumes LI$ has input buffer + REM Assumes R$ has input buffer RL_BACKSPACE: - IF LEN(LI$)=0 THEN RETURN - LI$=LEFT$(LI$,LEN(LI$)-1) + IF LEN(R$)=0 THEN RETURN + R$=LEFT$(R$,LEN(R$)-1) #cbm PRINT CHR$(157)+" "+CHR$(157); #qbasic LOCATE ,POS(0)-1 #qbasic PRINT " "; diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index cb7eb5ce0a..5ea5bc58f0 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -36,7 +36,7 @@ SUB EVAL_AST EVAL_AST_SYMBOL: H=E:K=A:GOSUB HASHMAP_GET GOSUB DEREF_R - IF T3=0 THEN ER=-1:E$="'"+S$(Z%(A,1))+"' not found":GOTO EVAL_AST_RETURN + IF R3=0 THEN ER=-1:E$="'"+S$(Z%(A,1))+"' not found":GOTO EVAL_AST_RETURN Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN @@ -142,7 +142,7 @@ SUB EVAL EVAL_INVOKE: CALL EVAL_AST - R3=R + T6=R REM if error, return f/args for release by caller IF ER<>-2 THEN GOTO EVAL_RETURN @@ -152,7 +152,7 @@ SUB EVAL R=F:GOSUB DEREF_R:F=R IF (Z%(F,0)AND 31)<>9 THEN ER=-1:E$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION - AY=R3:GOSUB RELEASE + AY=T6:GOSUB RELEASE GOTO EVAL_RETURN EVAL_RETURN: diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index e11e7acab7..22a234f5ce 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -210,7 +210,7 @@ SUB EVAL GOTO EVAL_RETURN EVAL_INVOKE: CALL EVAL_AST - R3=R + T6=R REM if error, return f/args for release by caller IF ER<>-2 THEN GOTO EVAL_RETURN @@ -220,7 +220,7 @@ SUB EVAL R=F:GOSUB DEREF_R:F=R IF (Z%(F,0)AND 31)<>9 THEN ER=-1:E$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION - AY=R3:GOSUB RELEASE + AY=T6:GOSUB RELEASE GOTO EVAL_RETURN EVAL_RETURN: diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index 4d61818f5b..44cfd10550 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -99,7 +99,7 @@ SUB MACROEXPAND REM defined in environment? K=B:CALL ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE - B=T4:GOSUB DEREF_B + B=R4:GOSUB DEREF_B REM macro? IF (Z%(B,0)AND 31)<>11 THEN GOTO MACROEXPAND_DONE diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index 45a6d5b516..023e51939d 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -99,7 +99,7 @@ SUB MACROEXPAND REM defined in environment? K=B:CALL ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE - B=T4:GOSUB DEREF_B + B=R4:GOSUB DEREF_B REM macro? IF (Z%(B,0)AND 31)<>11 THEN GOTO MACROEXPAND_DONE diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index 3ad10c30bf..e4c3765ad9 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -99,7 +99,7 @@ SUB MACROEXPAND REM defined in environment? K=B:CALL ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE - B=T4:GOSUB DEREF_B + B=R4:GOSUB DEREF_B REM macro? IF (Z%(B,0)AND 31)<>11 THEN GOTO MACROEXPAND_DONE @@ -330,11 +330,11 @@ SUB EVAL GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - GOSUB POP_Q:E4=Q: REM pop previous env + GOSUB POP_Q:AY=Q: REM pop previous env REM release previous environment if not the current EVAL env GOSUB PEEK_Q_2 - IF E4<>Q THEN AY=E4:GOSUB RELEASE + IF AY<>Q THEN GOSUB RELEASE GOSUB POP_Q:A2=Q: REM pop A2 A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -490,7 +490,7 @@ SUB EVAL GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4=E: REM save the current environment for release + Q=E:GOSUB PUSH_Q: REM save the current environment for release REM create new environ using env stored with function C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS @@ -498,8 +498,9 @@ SUB EVAL REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) + GOSUB POP_Q:AY=Q GOSUB PEEK_Q_2 - IF E4<>Q THEN AY=E4:GOSUB RELEASE + IF AY<>Q THEN GOSUB RELEASE REM claim the AST before releasing the list containing it A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 @@ -571,13 +572,11 @@ SUB REP IF ER<>-2 THEN GOTO REP_DONE A=R:GOSUB MAL_PRINT - RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL IF R2<>0 THEN AY=R2:GOSUB RELEASE IF R1<>0 THEN AY=R1:GOSUB RELEASE - R$=RT$ END SUB REM MAIN program diff --git a/basic/types.in.bas b/basic/types.in.bas index 7676128e30..af9cdf3c51 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -28,7 +28,7 @@ INIT_MEMORY: #cbm T=FRE(0) #qbasic T=0 - Z1=3950: REM Z% (boxed memory) size (4 bytes each) + Z1=4096: REM Z% (boxed memory) size (4 bytes each) Z2=200: REM S$/S% (string memory) size (3+2 bytes each) #qbasic Z3=200: REM X% (call stack) size (2 bytes each) #cbm Z3=49152: REM X starting point at $C000 (2 bytes each) @@ -154,31 +154,31 @@ ALLOC: SZ=2 IF T<6 OR T=9 OR T=12 OR T=14 THEN SZ=1 REM PRINT "ALLOC T: "+STR$(T)+", SZ: "+STR$(SZ)+", ZK: "+STR$(ZK) - U3=ZK - U4=ZK + U=ZK + V=ZK ALLOC_LOOP: - IF U4=ZI THEN GOTO ALLOC_UNUSED + IF V=ZI THEN GOTO ALLOC_UNUSED REM TODO sanity check that type is 15 - IF ((Z%(U4,0)AND-32)/32)=SZ THEN GOTO ALLOC_MIDDLE - REM PRINT "ALLOC search: U3: "+STR$(U3)+", U4: "+STR$(U4) - U3=U4: REM previous set to current - U4=Z%(U4,1): REM current set to next + IF ((Z%(V,0)AND-32)/32)=SZ THEN GOTO ALLOC_MIDDLE + REM PRINT "ALLOC search: U: "+STR$(U)+", V: "+STR$(V) + U=V: REM previous set to current + V=Z%(V,1): REM current set to next GOTO ALLOC_LOOP ALLOC_MIDDLE: - REM PRINT "ALLOC_MIDDLE: U3: "+STR$(U3)+", U4: "+STR$(U4) - R=U4 + REM PRINT "ALLOC_MIDDLE: U: "+STR$(U)+", V: "+STR$(V) + R=V REM set free pointer (ZK) to next free - IF U4=ZK THEN ZK=Z%(U4,1) + IF V=ZK THEN ZK=Z%(V,1) REM set previous free to next free - IF U4<>ZK THEN Z%(U3,1)=Z%(U4,1) + IF V<>ZK THEN Z%(U,1)=Z%(V,1) GOTO ALLOC_DONE ALLOC_UNUSED: - REM PRINT "ALLOC_UNUSED ZI: "+STR$(ZI)+", U3: "+STR$(U3)+", U4: "+STR$(U4) - R=U4 + REM PRINT "ALLOC_UNUSED ZI: "+STR$(ZI)+", U: "+STR$(U)+", V: "+STR$(V) + R=V ZI=ZI+SZ - IF U3=U4 THEN ZK=ZI + IF U=V THEN ZK=ZI REM set previous free to new memory top - IF U3<>U4 THEN Z%(U3,1)=ZI + IF U<>V THEN Z%(U,1)=ZI GOTO ALLOC_DONE ALLOC_DONE: Z%(R,0)=T+32 @@ -228,15 +228,15 @@ RELEASE: REM nil, false, true IF AY<3 THEN GOTO RELEASE_TOP - U6=Z%(AY,0)AND 31: REM type - U7=Z%(AY,1): REM main value/reference + U=Z%(AY,0)AND 31: REM type + V=Z%(AY,1): REM main value/reference REM AZ=AY: B=1: GOSUB PR_STR REM PRINT "RELEASE AY:"+STR$(AY)+"["+R$+"] (byte0:"+STR$(Z%(AY,0))+")" REM sanity check not already freed - IF (U6)=15 THEN ER=-1:E$="RELEASE of free: "+STR$(AY):RETURN - IF U6=14 THEN GOTO RELEASE_REFERENCE + IF (U)=15 THEN ER=-1:E$="RELEASE of free: "+STR$(AY):RETURN + IF U=14 THEN GOTO RELEASE_REFERENCE IF Z%(AY,0)<15 THEN ER=-1:E$="Unowned object: "+STR$(AY):RETURN REM decrease reference count by one @@ -246,13 +246,13 @@ RELEASE: IF Z%(AY,0)>=32 GOTO RELEASE_TOP REM switch on type - IF U6<=3 OR U6=9 THEN GOTO RELEASE_SIMPLE - IF U6=4 OR U6=5 THEN GOTO RELEASE_STRING - IF U6>=6 AND U6<=8 THEN GOTO RELEASE_SEQ - IF U6=10 OR U6=11 THEN GOTO RELEASE_MAL_FUNCTION - IF U6>=16 THEN GOTO RELEASE_METADATA - IF U6=12 THEN GOTO RELEASE_ATOM - IF U6=13 THEN GOTO RELEASE_ENV + IF U<=3 OR U=9 THEN GOTO RELEASE_SIMPLE + IF U=4 OR U=5 THEN GOTO RELEASE_STRING + IF U>=6 AND U<=8 THEN GOTO RELEASE_SEQ + IF U=10 OR U=11 THEN GOTO RELEASE_MAL_FUNCTION + IF U>=16 THEN GOTO RELEASE_METADATA + IF U=12 THEN GOTO RELEASE_ATOM + IF U=13 THEN GOTO RELEASE_ENV RELEASE_SIMPLE: REM simple type (no recursing), just call FREE on it @@ -264,29 +264,29 @@ RELEASE: GOTO RELEASE_TOP RELEASE_STRING: REM string type, release interned string, then FREE reference - IF S%(U7)=0 THEN ER=-1:E$="RELEASE of free string:"+STR$(S%(U7)):RETURN - S%(U7)=S%(U7)-1 - IF S%(U7)=0 THEN S$(U7)="": REM free BASIC string + IF S%(V)=0 THEN ER=-1:E$="RELEASE of free string:"+STR$(S%(V)):RETURN + S%(V)=S%(V)-1 + IF S%(V)=0 THEN S$(V)="": REM free BASIC string REM free the atom itself GOTO RELEASE_SIMPLE RELEASE_SEQ: - IF U7=0 THEN GOTO RELEASE_SIMPLE_2 + IF V=0 THEN GOTO RELEASE_SIMPLE_2 IF Z%(AY+1,0)<>14 THEN ER=-1:E$="invalid list value"+STR$(AY+1):RETURN REM add value and next element to stack RC=RC+2 Q=Z%(AY+1,1):GOSUB PUSH_Q - Q=U7:GOSUB PUSH_Q + Q=V:GOSUB PUSH_Q GOTO RELEASE_SIMPLE_2 RELEASE_ATOM: REM add contained/referred value RC=RC+1 - Q=U7:GOSUB PUSH_Q + Q=V:GOSUB PUSH_Q REM free the atom itself GOTO RELEASE_SIMPLE RELEASE_MAL_FUNCTION: REM add ast, params and environment to stack RC=RC+3 - Q=U7:GOSUB PUSH_Q + Q=V:GOSUB PUSH_Q Q=Z%(AY+1,0):GOSUB PUSH_Q Q=Z%(AY+1,1):GOSUB PUSH_Q REM free the current 2 element mal_function and continue @@ -295,14 +295,14 @@ RELEASE: RELEASE_METADATA: REM add object and metadata object RC=RC+2 - Q=U7:GOSUB PUSH_Q + Q=V:GOSUB PUSH_Q Q=Z%(AY+1,1):GOSUB PUSH_Q SZ=2:GOSUB FREE GOTO RELEASE_TOP RELEASE_ENV: REM add the hashmap data to the stack RC=RC+1 - Q=U7:GOSUB PUSH_Q + Q=V:GOSUB PUSH_Q REM if no outer set IF Z%(AY+1,1)=-1 THEN GOTO RELEASE_ENV_FREE REM add outer environment to the stack @@ -313,10 +313,10 @@ RELEASE: SZ=2:GOSUB FREE GOTO RELEASE_TOP RELEASE_REFERENCE: - IF U7=0 THEN GOTO RELEASE_SIMPLE + IF V=0 THEN GOTO RELEASE_SIMPLE REM add the referred element to the stack RC=RC+1 - Q=U7:GOSUB PUSH_Q + Q=V:GOSUB PUSH_Q REM free the current element and continue SZ=1:GOSUB FREE GOTO RELEASE_TOP @@ -463,15 +463,15 @@ REM PRINT "STRING ref: "+S$(I)+" (idx:"+STR$(I)+", ref "+STR$(S%(I))+")" REM REPLACE(R$, S1$, S2$) -> R$ REPLACE: - T3$=R$ + R3$=R$ R$="" I=1 - J=LEN(T3$) + J=LEN(R3$) REPLACE_LOOP: IF I>J THEN RETURN - C$=MID$(T3$,I,LEN(S1$)) + C$=MID$(R3$,I,LEN(S1$)) IF C$=S1$ THEN R$=R$+S2$:I=I+LEN(S1$) - IF C$<>S1$ THEN R$=R$+MID$(T3$,I,1):I=I+1 + IF C$<>S1$ THEN R$=R$+MID$(R3$,I,1):I=I+1 GOTO REPLACE_LOOP @@ -531,20 +531,20 @@ REM returns R6 as reference to last element of slice REM returns A as next element following slice (of original) SLICE: I=0 - R5=-1: REM temporary for return as R + W=-1: REM temporary for return as R R6=0: REM previous list element SLICE_LOOP: REM always allocate at least one list element T=6:L=0:N=0:GOSUB ALLOC - IF R5=-1 THEN R5=R - IF R5<>-1 THEN Z%(R6,1)=R + IF W=-1 THEN W=R + IF W<>-1 THEN Z%(R6,1)=R REM advance A to position B SLICE_FIND_B: IF I0 THEN A=Z%(A,1):I=I+1:GOTO SLICE_FIND_B REM if current position is C, then return - IF C<>-1 AND I>=C THEN R=R5:RETURN + IF C<>-1 AND I>=C THEN R=W:RETURN REM if we reached end of A, then return - IF Z%(A,1)=0 THEN R=R5:RETURN + IF Z%(A,1)=0 THEN R=W:RETURN R6=R: REM save previous list element REM copy value and inc ref cnt Z%(R6+1,1)=Z%(A+1,1) @@ -609,28 +609,27 @@ ASSOC1_S: REM HASHMAP_GET(H, K) -> R HASHMAP_GET: - H2=H B$=S$(Z%(K,1)): REM search key string - T3=0: REM whether found or not (for HASHMAP_CONTAINS) + R3=0: REM whether found or not (for HASHMAP_CONTAINS) R=0 HASHMAP_GET_LOOP: REM no matching key found - IF Z%(H2,1)=0 THEN R=0:RETURN + IF Z%(H,1)=0 THEN R=0:RETURN REM follow value ptrs - T2=H2+1 + T2=H+1 HASHMAP_GET_DEREF: IF Z%(T2,0)=14 THEN T2=Z%(T2,1):GOTO HASHMAP_GET_DEREF REM get key string REM if they are equal, we found it - IF B$=S$(Z%(T2,1)) THEN T3=1:R=Z%(H2,1)+1:RETURN + IF B$=S$(Z%(T2,1)) THEN R3=1:R=Z%(H,1)+1:RETURN REM skip to next key - H2=Z%(Z%(H2,1),1) + H=Z%(Z%(H,1),1) GOTO HASHMAP_GET_LOOP REM HASHMAP_CONTAINS(H, K) -> R HASHMAP_CONTAINS: GOSUB HASHMAP_GET - R=T3 + R=R3 RETURN diff --git a/basic/variables.txt b/basic/variables.txt index 35d917aa22..aafa3367c2 100644 --- a/basic/variables.txt +++ b/basic/variables.txt @@ -1,6 +1,10 @@ Global Unique: Z% : boxed memory values +Z1 : Z% size +Z2 : S$ size +Z3 : stack start address (cbm) or X% size (qbasic) +Z4 : release stack start address (cbm) or Y% size (qbasic) ZI : start of unused memory (index into Z%) ZK : start of free list (index into Z%) ZT : top of memory after repl env allocations @@ -16,6 +20,7 @@ Y : top element of Y% stack D : root repl environment +BT : begin time (TI) ER : error type (-2: none, -1: string, >=0: object) E$ : error string (ER=-1) EZ : READLINE EOF @@ -31,12 +36,13 @@ Calling arguments/temporaries: A : common call argument (especially EVAL, EVAL_AST) A$ : common call argument (READLINE, reader, string temp, key value) B : common call argument -B$ : STRING arg (HASHMAP_GET temp) -C : common call argument +B$ : STRING arg (HASHMAP_GET temp), PR_STR_SEQ seperator +C : common call argument, DO_TCO_FUNCTION temp in DO_APPLY E : environment (EVAL, EVAL_AST) F : function H : hash map K : hash map key (Z% index) +K$ : INIT_CORE_SET_FUNCTION and ENV_SET_S L : ALLOC* Z%(R,1) default M : ALLOC* Z%(R+1,0) default N : ALLOC* Z%(R+1,1) default @@ -45,19 +51,29 @@ R$ : common string return value T : type arg, common temp Q : PUSH*, POP*, PEEK* return value (and PEEK_Q_Q call arg) +AR : APPLY, DO_*_FUNCTION arg list AY : RELEASE/FREE arg AZ : PR_STR arg P1 : PR_MEMORY, CHECK_FREE_LIST start P2 : PR_MEMORY, CHECK_FREE_LIST end +R1 : REP, RE - MAL_READ result temp +R2 : REP, RE - EVAL result temp +R3 : HASHMAP_GET temp and return value +R4 : ENV_FIND temp and return value +R6 : SLICE return value (last element) SZ : size argument to ALLOC S1$ : REPLACE needle S2$ : REPLACE replacement + Other temporaries: + A0 : EVAL ast elements A1 : EVAL ast elements A2 : EVAL ast elements A3 : EVAL ast elements +AA : DO_*_FUNCTION arg1 +AB : DO_*_FUNCTION arg2 CZ : DO_CONCAT stack position ED : EQUAL_Q recursion depth counter @@ -69,23 +85,27 @@ C$ : READ_TOKEN, SKIP_SPACES, SKIP_TO_EOL current character G : function value ON GOTO switch flag I : STRING, REPLACE, SLICE, PR_MEMORY, PR_OBJECT J : REPLACE +U : ALLOC, RELEASE, PR_STR temp +V : ALLOC, RELEASE, PR_STR_SEQ temp +W : SLICE temp +RC : RELEASE remaining number of elements to release +RF : reader reading from file flag +RS : reader EOF state (1=EOF) S1 : READ_TOKEN in a string? S2 : READ_TOKEN escaped? T$ : READ_* current token string T1 : EQUAL_Q, PR_STR, and core DO_KEYS_VALS temp T2 : EQUAL_Q, DO_KEY_VALS, HASH_MAP_GET -T3 : HASHMAP_GET temp and return value T3$ : REPLACE temp -T4 : ENV_FIND temp and return value -T6 : LAST and QUASIQUOTE temp -T7 : READ_FORM temp -T8 : READ_FORM_DONE temp -T9 : PR_STR_SEQ temp -U3 : ALLOC -U4 : ALLOC -U6 : RELEASE -U7 : RELEASE +T6 : LAST and QUASIQUOTE temp (step2-3 EVAL temp) +T7$ : READ_FORM:READ_STRING character temp + Unused: -O, U, V, W +O + + +Counting number of times each variable is assigned: + sed 's/:/\n /g' readline.in.bas types.in.bas reader.in.bas printer.in.bas env.in.bas core.in.bas stepA_mal.in.bas | grep "[A-Z][A-Z0-9]*[%$]*=" | sed 's/.*[^A-Z]\([A-Z][A-Z0-9]*[%$]*\)=.*/\1/g' | sort | uniq -c | sort -n + From 037815e0f36d14adb04bfd5f8e1f49f9c9f7d68a Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 10 Nov 2016 01:51:02 -0600 Subject: [PATCH 0228/2308] Basic: more reductions. RELEASE refactor. Save about 400 bytes. Increase value Z% array by 100 to 4195. Reduce string array by 1 (to 199) since in BASIC the value is the last index not the size. --- basic/core.in.bas | 317 ++++++++++++++++++------------------ basic/env.in.bas | 2 +- basic/reader.in.bas | 15 +- basic/step2_eval.in.bas | 24 +-- basic/step3_env.in.bas | 24 +-- basic/step4_if_fn_do.in.bas | 2 +- basic/step5_tco.in.bas | 2 +- basic/step6_file.in.bas | 2 +- basic/step7_quote.in.bas | 12 +- basic/step8_macros.in.bas | 12 +- basic/step9_try.in.bas | 12 +- basic/stepA_mal.in.bas | 12 +- basic/types.in.bas | 143 ++++++++-------- basic/variables.txt | 16 +- 14 files changed, 292 insertions(+), 303 deletions(-) diff --git a/basic/core.in.bas b/basic/core.in.bas index 2d0863e531..3f4a2e1d51 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -40,15 +40,15 @@ SUB DO_TCO_FUNCTION G=Z%(F,1) REM Get argument values - R=AR+1:GOSUB DEREF_R:AA=R - R=Z%(AR,1)+1:GOSUB DEREF_R:AB=R + R=AR+1:GOSUB DEREF_R:A=R + R=Z%(AR,1)+1:GOSUB DEREF_R:B=R ON G-60 GOTO DO_APPLY,DO_MAP,DO_SWAP_BANG DO_APPLY: - F=AA + F=A AR=Z%(AR,1) - B=AR:GOSUB COUNT:C=R + A=AR:GOSUB COUNT:C=R A=Z%(AR+1,1) REM no intermediate args, but not a list, so convert it first @@ -82,16 +82,16 @@ SUB DO_TCO_FUNCTION GOTO DO_TCO_FUNCTION_DONE DO_MAP: - F=AA + F=A REM first result list element T=6:L=0:N=0:GOSUB ALLOC - REM push future return val, prior entry, F and AB + REM push future return val, prior entry, F and B GOSUB PUSH_R Q=0:GOSUB PUSH_Q Q=F:GOSUB PUSH_Q - Q=AB:GOSUB PUSH_Q + Q=B:GOSUB PUSH_Q DO_MAP_LOOP: REM set previous to current if not the first element @@ -100,12 +100,12 @@ SUB DO_TCO_FUNCTION REM update previous reference to current Q=R:GOSUB PUT_Q_2 - IF Z%(AB,1)=0 THEN GOTO DO_MAP_DONE + IF Z%(B,1)=0 THEN GOTO DO_MAP_DONE REM create argument list for apply call Z%(3,0)=Z%(3,0)+32 REM inc ref cnt of referred argument - T=6:L=3:N=Z%(AB+1,1):GOSUB ALLOC + T=6:L=3:N=Z%(B+1,1):GOSUB ALLOC REM push argument list GOSUB PUSH_R @@ -125,10 +125,10 @@ SUB DO_TCO_FUNCTION REM restore F GOSUB PEEK_Q_1:F=Q - REM update AB to next source element + REM update B to next source element GOSUB PEEK_Q Q=Z%(Q,1) - AB=Q + B=Q GOSUB PUT_Q REM allocate next element @@ -149,29 +149,29 @@ SUB DO_TCO_FUNCTION DO_SWAP_BANG: - F=AB + F=B REM add atom to front of the args list - T=6:L=Z%(Z%(AR,1),1):N=Z%(AA,1):GOSUB ALLOC: REM cons + T=6:L=Z%(Z%(AR,1),1):N=Z%(A,1):GOSUB ALLOC: REM cons AR=R REM push args for release after Q=AR:GOSUB PUSH_Q REM push atom - Q=AA:GOSUB PUSH_Q + Q=A:GOSUB PUSH_Q CALL APPLY REM pop atom - GOSUB POP_Q:AA=Q + GOSUB POP_Q:A=Q REM pop and release args GOSUB POP_Q:AY=Q GOSUB RELEASE REM use reset to update the value - AB=R:GOSUB DO_RESET_BANG + B=R:GOSUB DO_RESET_BANG REM but decrease ref cnt of return by 1 (not sure why) AY=R:GOSUB RELEASE @@ -188,8 +188,8 @@ DO_FUNCTION: G=Z%(F,1) REM Get argument values - R=AR+1:GOSUB DEREF_R:AA=R - R=Z%(AR,1)+1:GOSUB DEREF_R:AB=R + R=AR+1:GOSUB DEREF_R:A=R + R=Z%(AR,1)+1:GOSUB DEREF_R:B=R REM Switch on the function number IF G>59 THEN ER=-1:E$="unknown function"+STR$(G):RETURN @@ -209,49 +209,49 @@ DO_FUNCTION: ON G-49 GOTO DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_EVAL,DO_READ_FILE,DO_PR_MEMORY_SUMMARY DO_EQUAL_Q: - A=AA:B=AB:GOSUB EQUAL_Q + GOSUB EQUAL_Q R=R+1 RETURN DO_THROW: - ER=AA + ER=A Z%(ER,0)=Z%(ER,0)+32 R=0 RETURN DO_NIL_Q: R=1 - IF AA=0 THEN R=2 + IF A=0 THEN R=2 RETURN DO_TRUE_Q: R=1 - IF AA=2 THEN R=2 + IF A=2 THEN R=2 RETURN DO_FALSE_Q: R=1 - IF AA=1 THEN R=2 + IF A=1 THEN R=2 RETURN DO_STRING_Q: R=1 - IF (Z%(AA,0)AND 31)<>4 THEN RETURN - IF MID$(S$(Z%(AA,1)),1,1)=CHR$(127) THEN RETURN + IF (Z%(A,0)AND 31)<>4 THEN RETURN + IF MID$(S$(Z%(A,1)),1,1)=CHR$(127) THEN RETURN R=2 RETURN DO_SYMBOL: - B$=S$(Z%(AA,1)) + B$=S$(Z%(A,1)) T=5:GOSUB STRING RETURN DO_SYMBOL_Q: R=1 - IF (Z%(AA,0)AND 31)=5 THEN R=2 + IF (Z%(A,0)AND 31)=5 THEN R=2 RETURN DO_KEYWORD: - B$=S$(Z%(AA,1)) + B$=S$(Z%(A,1)) IF MID$(B$,1,1)<>CHR$(127) THEN B$=CHR$(127)+B$ T=4:GOSUB STRING RETURN DO_KEYWORD_Q: R=1 - IF (Z%(AA,0)AND 31)<>4 THEN RETURN - IF MID$(S$(Z%(AA,1)),1,1)<>CHR$(127) THEN RETURN + IF (Z%(A,0)AND 31)<>4 THEN RETURN + IF MID$(S$(Z%(A,1)),1,1)<>CHR$(127) THEN RETURN R=2 RETURN @@ -274,25 +274,25 @@ DO_FUNCTION: R=0 RETURN DO_READ_STRING: - A$=S$(Z%(AA,1)) + A$=S$(Z%(A,1)) GOSUB READ_STR RETURN DO_READLINE: - A$=S$(Z%(AA,1)):GOSUB READLINE + A$=S$(Z%(A,1)):GOSUB READLINE IF EZ=1 THEN EZ=0:R=0:RETURN B$=R$:T=4:GOSUB STRING RETURN DO_SLURP: R$="" - #cbm OPEN 1,8,0,S$(Z%(AA,1)) - #qbasic A$=S$(Z%(AA,1)) + #cbm OPEN 1,8,0,S$(Z%(A,1)) + #qbasic A$=S$(Z%(A,1)) #qbasic IF NOT _FILEEXISTS(A$) THEN ER=-1:E$="File not found":RETURN #qbasic OPEN A$ FOR INPUT AS #1 DO_SLURP_LOOP: A$="" #cbm GET#1,A$ #qbasic A$=INPUT$(1,1) - #qbasic IF EOF(1) THEN RS=1:A$=A$+CHR$(10)+")":GOTO DO_SLURP_DONE + #qbasic IF EOF(1) THEN EZ=1:A$=A$+CHR$(10)+")":GOTO DO_SLURP_DONE IF ASC(A$)=10 THEN R$=R$+CHR$(13) IF (ASC(A$)<>10) AND (A$<>"") THEN R$=R$+A$ #cbm IF (ST AND 64) THEN GOTO DO_SLURP_DONE @@ -305,32 +305,32 @@ DO_FUNCTION: DO_LT: R=1 - IF Z%(AA,1)Z%(AB,1) THEN R=2 + IF Z%(A,1)>Z%(B,1) THEN R=2 RETURN DO_GTE: R=1 - IF Z%(AA,1)>=Z%(AB,1) THEN R=2 + IF Z%(A,1)>=Z%(B,1) THEN R=2 RETURN DO_ADD: - T=2:L=Z%(AA,1)+Z%(AB,1):GOSUB ALLOC + T=2:L=Z%(A,1)+Z%(B,1):GOSUB ALLOC RETURN DO_SUB: - T=2:L=Z%(AA,1)-Z%(AB,1):GOSUB ALLOC + T=2:L=Z%(A,1)-Z%(B,1):GOSUB ALLOC RETURN DO_MULT: - T=2:L=Z%(AA,1)*Z%(AB,1):GOSUB ALLOC + T=2:L=Z%(A,1)*Z%(B,1):GOSUB ALLOC RETURN DO_DIV: - T=2:L=Z%(AA,1)/Z%(AB,1):GOSUB ALLOC + T=2:L=Z%(A,1)/Z%(B,1):GOSUB ALLOC RETURN DO_TIME_MS: T=2:L=INT((TI-BT)*16.667):GOSUB ALLOC @@ -341,7 +341,7 @@ DO_FUNCTION: Z%(R,0)=Z%(R,0)+32 RETURN DO_LIST_Q: - A=AA:GOSUB LIST_Q + GOSUB LIST_Q R=R+1: REM map to mal false/true RETURN DO_VECTOR: @@ -349,17 +349,17 @@ DO_FUNCTION: RETURN DO_VECTOR_Q: R=1 - IF (Z%(AA,0)AND 31)=7 THEN R=2 + IF (Z%(A,0)AND 31)=7 THEN R=2 RETURN DO_HASH_MAP: A=AR:T=8:GOSUB FORCE_SEQ_TYPE RETURN DO_MAP_Q: R=1 - IF (Z%(AA,0)AND 31)=8 THEN R=2 + IF (Z%(A,0)AND 31)=8 THEN R=2 RETURN DO_ASSOC: - H=AA + H=A AR=Z%(AR,1) DO_ASSOC_LOOP: R=AR+1:GOSUB DEREF_R:K=R @@ -370,28 +370,28 @@ DO_FUNCTION: IF AR=0 OR Z%(AR,1)=0 THEN RETURN GOTO DO_ASSOC_LOOP DO_GET: - IF AA=0 THEN R=0:RETURN - H=AA:K=AB:GOSUB HASHMAP_GET + IF A=0 THEN R=0:RETURN + H=A:K=B:GOSUB HASHMAP_GET GOSUB DEREF_R Z%(R,0)=Z%(R,0)+32 RETURN DO_CONTAINS: - H=AA:K=AB:GOSUB HASHMAP_CONTAINS + H=A:K=B:GOSUB HASHMAP_CONTAINS R=R+1 RETURN DO_KEYS: GOTO DO_KEYS_VALS DO_VALS: - AA=Z%(AA,1) + A=Z%(A,1) DO_KEYS_VALS: REM first result list element T=6:L=0:N=0:GOSUB ALLOC:T2=R DO_KEYS_VALS_LOOP: - IF AA=0 OR Z%(AA,1)=0 THEN R=T2:RETURN + IF A=0 OR Z%(A,1)=0 THEN R=T2:RETURN REM copy the value - T1=Z%(AA+1,1) + T1=Z%(A+1,1) REM inc ref cnt of referred argument Z%(T1,0)=Z%(T1,0)+32 Z%(R+1,1)=T1 @@ -402,18 +402,18 @@ DO_FUNCTION: REM point previous element to this one Z%(T1,1)=R - IF Z%(Z%(AA,1),1)=0 THEN R=T2:RETURN + IF Z%(Z%(A,1),1)=0 THEN R=T2:RETURN - AA=Z%(Z%(AA,1),1) + A=Z%(Z%(A,1),1) GOTO DO_KEYS_VALS_LOOP DO_SEQUENTIAL_Q: R=1 - IF (Z%(AA,0)AND 31)=6 OR (Z%(AA,0)AND 31)=7 THEN R=2 + IF (Z%(A,0)AND 31)=6 OR (Z%(A,0)AND 31)=7 THEN R=2 RETURN DO_CONS: - T=6:L=AB:N=AA:GOSUB ALLOC + T=6:L=B:N=A:GOSUB ALLOC RETURN DO_CONCAT: REM if empty arguments, return empty list @@ -422,7 +422,7 @@ DO_FUNCTION: REM single argument IF Z%(Z%(AR,1),1)<>0 THEN GOTO DO_CONCAT_MULT REM force to list type - A=AA:T=6:GOSUB FORCE_SEQ_TYPE + T=6:GOSUB FORCE_SEQ_TYPE RETURN REM multiple arguments @@ -437,54 +437,55 @@ DO_FUNCTION: IF Z%(AR,1)<>0 THEN GOTO DO_CONCAT_STACK REM pop last argument as our seq to prepend to - GOSUB POP_Q:AB=Q + GOSUB POP_Q:B=Q REM last arg/seq is not copied so we need to inc ref to it - Z%(AB,0)=Z%(AB,0)+32 + Z%(B,0)=Z%(B,0)+32 DO_CONCAT_LOOP: - IF X=CZ THEN R=AB:RETURN - GOSUB POP_Q:AA=Q: REM pop off next seq to prepend - IF Z%(AA,1)=0 THEN GOTO DO_CONCAT_LOOP: REM skip empty seqs - A=AA:B=0:C=-1:GOSUB SLICE + IF X=CZ THEN R=B:RETURN + GOSUB POP_A: REM pop off next seq to prepend + IF Z%(A,1)=0 THEN GOTO DO_CONCAT_LOOP: REM skip empty seqs + Q=B:GOSUB PUSH_Q + B=0:C=-1:GOSUB SLICE + GOSUB POP_Q:B=Q REM release the terminator of new list (we skip over it) AY=Z%(R6,1):GOSUB RELEASE REM attach new list element before terminator (last actual REM element to the next sequence - Z%(R6,1)=AB + Z%(R6,1)=B - AB=R + B=R GOTO DO_CONCAT_LOOP DO_NTH: - B=AA:GOSUB COUNT - B=Z%(AB,1) + GOSUB COUNT + B=Z%(B,1) IF R<=B THEN R=0:ER=-1:E$="nth: index out of range":RETURN DO_NTH_LOOP: IF B=0 THEN GOTO DO_NTH_DONE B=B-1 - AA=Z%(AA,1) + A=Z%(A,1) GOTO DO_NTH_LOOP DO_NTH_DONE: - R=Z%(AA+1,1) + R=Z%(A+1,1) Z%(R,0)=Z%(R,0)+32 RETURN DO_FIRST: - IF AA=0 THEN R=0:RETURN - IF Z%(AA,1)=0 THEN R=0 - IF Z%(AA,1)<>0 THEN R=AA+1:GOSUB DEREF_R + IF A=0 THEN R=0:RETURN + IF Z%(A,1)=0 THEN R=0 + IF Z%(A,1)<>0 THEN R=A+1:GOSUB DEREF_R IF R<>0 THEN Z%(R,0)=Z%(R,0)+32 RETURN DO_REST: - IF AA=0 THEN R=3:Z%(R,0)=Z%(R,0)+32:RETURN - IF Z%(AA,1)=0 THEN A=AA - IF Z%(AA,1)<>0 THEN A=Z%(AA,1) + IF A=0 THEN R=3:Z%(R,0)=Z%(R,0)+32:RETURN + IF Z%(A,1)<>0 THEN A=Z%(A,1) T=6:GOSUB FORCE_SEQ_TYPE RETURN DO_EMPTY_Q: R=1 - IF Z%(AA,1)=0 THEN R=2 + IF Z%(A,1)=0 THEN R=2 RETURN DO_COUNT: - B=AA:GOSUB COUNT + GOSUB COUNT T=2:L=R:GOSUB ALLOC RETURN DO_CONJ: @@ -495,35 +496,35 @@ DO_FUNCTION: RETURN DO_WITH_META: - T=Z%(AA,0)AND 31 + T=Z%(A,0)AND 31 REM remove existing metadata first - IF T>=16 THEN AA=Z%(AA,1):GOTO DO_WITH_META - T=T+16:L=AA:N=AB:GOSUB ALLOC + IF T>=16 THEN A=Z%(A,1):GOTO DO_WITH_META + T=T+16:L=A:N=B:GOSUB ALLOC RETURN DO_META: - IF (Z%(AA,0)AND 31)<16 THEN R=0:RETURN - R=Z%(AA+1,1) + IF (Z%(A,0)AND 31)<16 THEN R=0:RETURN + R=Z%(A+1,1) Z%(R,0)=Z%(R,0)+32 RETURN DO_ATOM: - T=12:L=AA:GOSUB ALLOC + T=12:L=A:GOSUB ALLOC RETURN DO_ATOM_Q: R=1 - IF (Z%(AA,0)AND 31)=12 THEN R=2 + IF (Z%(A,0)AND 31)=12 THEN R=2 RETURN DO_DEREF: - R=Z%(AA,1):GOSUB DEREF_R + R=Z%(A,1):GOSUB DEREF_R Z%(R,0)=Z%(R,0)+32 RETURN DO_RESET_BANG: - R=AB + R=B REM release current value - AY=Z%(AA,1):GOSUB RELEASE + AY=Z%(A,1):GOSUB RELEASE REM inc ref by 2 for atom ownership and since we are returning it Z%(R,0)=Z%(R,0)+64 REM update value - Z%(AA,1)=R + Z%(A,1)=R RETURN REM DO_PR_MEMORY: @@ -535,12 +536,12 @@ DO_FUNCTION: DO_EVAL: Q=E:GOSUB PUSH_Q: REM push/save environment - A=AA:E=D:CALL EVAL + E=D:CALL EVAL GOSUB POP_Q:E=Q RETURN DO_READ_FILE: - A$=S$(Z%(AA,1)) + A$=S$(Z%(A,1)) GOSUB READ_FILE RETURN @@ -556,75 +557,75 @@ INIT_CORE_NS: REM must match DO_FUNCTION mappings A=1 - K$="=":GOSUB INIT_CORE_SET_FUNCTION: REM A=1 - K$="throw":GOSUB INIT_CORE_SET_FUNCTION: REM A=2 - K$="nil?":GOSUB INIT_CORE_SET_FUNCTION: REM A=3 - K$="true?":GOSUB INIT_CORE_SET_FUNCTION: REM A=4 - K$="false?":GOSUB INIT_CORE_SET_FUNCTION: REM A=5 - K$="string?":GOSUB INIT_CORE_SET_FUNCTION: REM A=6 - K$="symbol":GOSUB INIT_CORE_SET_FUNCTION: REM A=7 - K$="symbol?":GOSUB INIT_CORE_SET_FUNCTION: REM A=8 - K$="keyword":GOSUB INIT_CORE_SET_FUNCTION: REM A=9 - K$="keyword?":GOSUB INIT_CORE_SET_FUNCTION: REM A=10 - - K$="pr-str":GOSUB INIT_CORE_SET_FUNCTION: REM A=11 - K$="str":GOSUB INIT_CORE_SET_FUNCTION: REM A=12 - K$="prn":GOSUB INIT_CORE_SET_FUNCTION: REM A=13 - K$="println":GOSUB INIT_CORE_SET_FUNCTION: REM A=14 - K$="read-string":GOSUB INIT_CORE_SET_FUNCTION: REM A=15 - K$="readline":GOSUB INIT_CORE_SET_FUNCTION: REM A=16 - K$="slurp":GOSUB INIT_CORE_SET_FUNCTION: REM A=17 - - K$="<":GOSUB INIT_CORE_SET_FUNCTION: REM A=18 - K$="<=":GOSUB INIT_CORE_SET_FUNCTION: REM A=19 - K$=">":GOSUB INIT_CORE_SET_FUNCTION: REM A=20 - K$=">=":GOSUB INIT_CORE_SET_FUNCTION: REM A=21 - K$="+":GOSUB INIT_CORE_SET_FUNCTION: REM A=22 - K$="-":GOSUB INIT_CORE_SET_FUNCTION: REM A=23 - K$="*":GOSUB INIT_CORE_SET_FUNCTION: REM A=24 - K$="/":GOSUB INIT_CORE_SET_FUNCTION: REM A=25 - K$="time-ms":GOSUB INIT_CORE_SET_FUNCTION: REM A=26 - - K$="list":GOSUB INIT_CORE_SET_FUNCTION: REM A=27 - K$="list?":GOSUB INIT_CORE_SET_FUNCTION: REM A=28 - K$="vector":GOSUB INIT_CORE_SET_FUNCTION: REM A=29 - K$="vector?":GOSUB INIT_CORE_SET_FUNCTION: REM A=30 - K$="hash-map":GOSUB INIT_CORE_SET_FUNCTION: REM A=31 - K$="map?":GOSUB INIT_CORE_SET_FUNCTION: REM A=32 - K$="assoc":GOSUB INIT_CORE_SET_FUNCTION: REM A=33 - K$="dissoc":GOSUB INIT_CORE_SET_FUNCTION: REM A=34 - K$="get":GOSUB INIT_CORE_SET_FUNCTION: REM A=35 - K$="contains?":GOSUB INIT_CORE_SET_FUNCTION: REM A=36 - K$="keys":GOSUB INIT_CORE_SET_FUNCTION: REM A=37 - K$="vals":GOSUB INIT_CORE_SET_FUNCTION: REM A=38 - - K$="sequential?":GOSUB INIT_CORE_SET_FUNCTION: REM A=39 - K$="cons":GOSUB INIT_CORE_SET_FUNCTION: REM A=40 - K$="concat":GOSUB INIT_CORE_SET_FUNCTION: REM A=41 - K$="nth":GOSUB INIT_CORE_SET_FUNCTION: REM A=42 - K$="first":GOSUB INIT_CORE_SET_FUNCTION: REM A=43 - K$="rest":GOSUB INIT_CORE_SET_FUNCTION: REM A=44 - K$="empty?":GOSUB INIT_CORE_SET_FUNCTION: REM A=45 - K$="count":GOSUB INIT_CORE_SET_FUNCTION: REM A=46 - - K$="conj":GOSUB INIT_CORE_SET_FUNCTION: REM A=47 - K$="seq":GOSUB INIT_CORE_SET_FUNCTION: REM A=48 - - K$="with-meta":GOSUB INIT_CORE_SET_FUNCTION: REM A=49 - K$="meta":GOSUB INIT_CORE_SET_FUNCTION: REM A=50 - K$="atom":GOSUB INIT_CORE_SET_FUNCTION: REM A=51 - K$="atom?":GOSUB INIT_CORE_SET_FUNCTION: REM A=52 - K$="deref":GOSUB INIT_CORE_SET_FUNCTION: REM A=53 - K$="reset!":GOSUB INIT_CORE_SET_FUNCTION: REM A=54 - - K$="eval":GOSUB INIT_CORE_SET_FUNCTION: REM A=55 - K$="read-file":GOSUB INIT_CORE_SET_FUNCTION: REM A=56 - K$="pr-memory-summary":GOSUB INIT_CORE_SET_FUNCTION: REM A=57 + B$="=":GOSUB INIT_CORE_SET_FUNCTION: REM A=1 + B$="throw":GOSUB INIT_CORE_SET_FUNCTION: REM A=2 + B$="nil?":GOSUB INIT_CORE_SET_FUNCTION: REM A=3 + B$="true?":GOSUB INIT_CORE_SET_FUNCTION: REM A=4 + B$="false?":GOSUB INIT_CORE_SET_FUNCTION: REM A=5 + B$="string?":GOSUB INIT_CORE_SET_FUNCTION: REM A=6 + B$="symbol":GOSUB INIT_CORE_SET_FUNCTION: REM A=7 + B$="symbol?":GOSUB INIT_CORE_SET_FUNCTION: REM A=8 + B$="keyword":GOSUB INIT_CORE_SET_FUNCTION: REM A=9 + B$="keyword?":GOSUB INIT_CORE_SET_FUNCTION: REM A=10 + + B$="pr-str":GOSUB INIT_CORE_SET_FUNCTION: REM A=11 + B$="str":GOSUB INIT_CORE_SET_FUNCTION: REM A=12 + B$="prn":GOSUB INIT_CORE_SET_FUNCTION: REM A=13 + B$="println":GOSUB INIT_CORE_SET_FUNCTION: REM A=14 + B$="read-string":GOSUB INIT_CORE_SET_FUNCTION: REM A=15 + B$="readline":GOSUB INIT_CORE_SET_FUNCTION: REM A=16 + B$="slurp":GOSUB INIT_CORE_SET_FUNCTION: REM A=17 + + B$="<":GOSUB INIT_CORE_SET_FUNCTION: REM A=18 + B$="<=":GOSUB INIT_CORE_SET_FUNCTION: REM A=19 + B$=">":GOSUB INIT_CORE_SET_FUNCTION: REM A=20 + B$=">=":GOSUB INIT_CORE_SET_FUNCTION: REM A=21 + B$="+":GOSUB INIT_CORE_SET_FUNCTION: REM A=22 + B$="-":GOSUB INIT_CORE_SET_FUNCTION: REM A=23 + B$="*":GOSUB INIT_CORE_SET_FUNCTION: REM A=24 + B$="/":GOSUB INIT_CORE_SET_FUNCTION: REM A=25 + B$="time-ms":GOSUB INIT_CORE_SET_FUNCTION: REM A=26 + + B$="list":GOSUB INIT_CORE_SET_FUNCTION: REM A=27 + B$="list?":GOSUB INIT_CORE_SET_FUNCTION: REM A=28 + B$="vector":GOSUB INIT_CORE_SET_FUNCTION: REM A=29 + B$="vector?":GOSUB INIT_CORE_SET_FUNCTION: REM A=30 + B$="hash-map":GOSUB INIT_CORE_SET_FUNCTION: REM A=31 + B$="map?":GOSUB INIT_CORE_SET_FUNCTION: REM A=32 + B$="assoc":GOSUB INIT_CORE_SET_FUNCTION: REM A=33 + B$="dissoc":GOSUB INIT_CORE_SET_FUNCTION: REM A=34 + B$="get":GOSUB INIT_CORE_SET_FUNCTION: REM A=35 + B$="contains?":GOSUB INIT_CORE_SET_FUNCTION: REM A=36 + B$="keys":GOSUB INIT_CORE_SET_FUNCTION: REM A=37 + B$="vals":GOSUB INIT_CORE_SET_FUNCTION: REM A=38 + + B$="sequential?":GOSUB INIT_CORE_SET_FUNCTION: REM A=39 + B$="cons":GOSUB INIT_CORE_SET_FUNCTION: REM A=40 + B$="concat":GOSUB INIT_CORE_SET_FUNCTION: REM A=41 + B$="nth":GOSUB INIT_CORE_SET_FUNCTION: REM A=42 + B$="first":GOSUB INIT_CORE_SET_FUNCTION: REM A=43 + B$="rest":GOSUB INIT_CORE_SET_FUNCTION: REM A=44 + B$="empty?":GOSUB INIT_CORE_SET_FUNCTION: REM A=45 + B$="count":GOSUB INIT_CORE_SET_FUNCTION: REM A=46 + + B$="conj":GOSUB INIT_CORE_SET_FUNCTION: REM A=47 + B$="seq":GOSUB INIT_CORE_SET_FUNCTION: REM A=48 + + B$="with-meta":GOSUB INIT_CORE_SET_FUNCTION: REM A=49 + B$="meta":GOSUB INIT_CORE_SET_FUNCTION: REM A=50 + B$="atom":GOSUB INIT_CORE_SET_FUNCTION: REM A=51 + B$="atom?":GOSUB INIT_CORE_SET_FUNCTION: REM A=52 + B$="deref":GOSUB INIT_CORE_SET_FUNCTION: REM A=53 + B$="reset!":GOSUB INIT_CORE_SET_FUNCTION: REM A=54 + + B$="eval":GOSUB INIT_CORE_SET_FUNCTION: REM A=55 + B$="read-file":GOSUB INIT_CORE_SET_FUNCTION: REM A=56 + B$="pr-memory-summary":GOSUB INIT_CORE_SET_FUNCTION: REM A=57 REM these are in DO_TCO_FUNCTION A=61 - K$="apply":GOSUB INIT_CORE_SET_FUNCTION: REM A=61 - K$="map":GOSUB INIT_CORE_SET_FUNCTION: REM A=62 - K$="swap!":GOSUB INIT_CORE_SET_FUNCTION: REM A=63 + B$="apply":GOSUB INIT_CORE_SET_FUNCTION: REM A=61 + B$="map":GOSUB INIT_CORE_SET_FUNCTION: REM A=62 + B$="swap!":GOSUB INIT_CORE_SET_FUNCTION: REM A=63 RETURN diff --git a/basic/env.in.bas b/basic/env.in.bas index 6c280f34c1..cab37f3c7e 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -58,7 +58,7 @@ ENV_SET: R=C RETURN -REM ENV_SET_S(E, K$, C) -> R +REM ENV_SET_S(E, B$, C) -> R ENV_SET_S: H=Z%(E,1) GOSUB ASSOC1_S diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 0d536bc25a..8e1e522abc 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -29,16 +29,16 @@ READ_TOKEN: GOTO READ_TOKEN_LOOP READ_FILE_CHUNK: - IF RS=1 THEN RETURN + IF EZ=1 THEN RETURN IF RI>1 THEN A$=MID$(A$,RI,LEN(A$)-RI+1):RI=1:RJ=RJ-RI+1 READ_FILE_CHUNK_LOOP: IF LEN(A$)>RJ+9 THEN RETURN #cbm GET#2,C$ #qbasic C$=INPUT$(1,2) - #qbasic IF EOF(2) THEN RS=1:A$=A$+CHR$(10)+")":RETURN + #qbasic IF EOF(2) THEN EZ=1:A$=A$+CHR$(10)+")":RETURN A$=A$+C$ - #cbm IF (ST AND 64) THEN RS=1:A$=A$+CHR$(10)+")":RETURN - #cbm IF (ST AND 255) THEN RS=1:ER=-1:E$="File read error "+STR$(ST):RETURN + #cbm IF (ST AND 64) THEN EZ=1:A$=A$+CHR$(10)+")":RETURN + #cbm IF (ST AND 255) THEN EZ=1:ER=-1:E$="File read error "+STR$(ST):RETURN GOTO READ_FILE_CHUNK_LOOP SKIP_SPACES: @@ -148,8 +148,8 @@ READ_FORM: GOTO READ_FORM_DONE READ_STRING: REM PRINT "READ_STRING" - T7$=MID$(T$,LEN(T$),1) - IF T7$<>CHR$(34) THEN E$="expected '"+CHR$(34)+"'":GOTO READ_FORM_ABORT + C=ASC(MID$(T$,LEN(T$),1)) + IF C<>34 THEN E$="expected '"+CHR$(34)+"'":GOTO READ_FORM_ABORT R$=MID$(T$,2,LEN(T$)-2) S1$=CHR$(92)+CHR$(34):S2$=CHR$(34):GOSUB REPLACE: REM unescape quotes S1$=CHR$(92)+"n":S2$=CHR$(13):GOSUB REPLACE: REM unescape newlines @@ -258,7 +258,7 @@ READ_FILE: RI=1: REM index into A$ RJ=1: REM READ_TOKEN sub-index RF=1: REM reading from file - RS=0: REM file read state (1: EOF) + EZ=0: REM file read state (1: EOF) SD=0: REM sequence read depth #cbm OPEN 2,8,0,A$ #qbasic IF NOT _FILEEXISTS(A$) THEN ER=-1:E$="File not found":RETURN @@ -266,4 +266,5 @@ READ_FILE: REM READ_FILE_CHUNK adds terminating ")" A$="(do ":GOSUB READ_FORM CLOSE 2 + EZ=0 RETURN diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index 5ea5bc58f0..eeba70c428 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -142,7 +142,7 @@ SUB EVAL EVAL_INVOKE: CALL EVAL_AST - T6=R + W=R REM if error, return f/args for release by caller IF ER<>-2 THEN GOTO EVAL_RETURN @@ -152,7 +152,7 @@ SUB EVAL R=F:GOSUB DEREF_R:F=R IF (Z%(F,0)AND 31)<>9 THEN ER=-1:E$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION - AY=T6:GOSUB RELEASE + AY=W:GOSUB RELEASE GOTO EVAL_RETURN EVAL_RETURN: @@ -180,8 +180,8 @@ DO_FUNCTION: G=Z%(F,1) REM Get argument values - R=AR+1:GOSUB DEREF_R:AA=Z%(R,1) - R=Z%(AR,1)+1:GOSUB DEREF_R:AB=Z%(R,1) + R=AR+1:GOSUB DEREF_R:A=Z%(R,1) + R=Z%(AR,1)+1:GOSUB DEREF_R:B=Z%(R,1) REM Switch on the function number IF G=1 THEN GOTO DO_ADD @@ -191,16 +191,16 @@ DO_FUNCTION: ER=-1:E$="unknown function"+STR$(G):RETURN DO_ADD: - T=2:L=AA+AB:GOSUB ALLOC + T=2:L=A+B:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_SUB: - T=2:L=AA-AB:GOSUB ALLOC + T=2:L=A-B:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_MULT: - T=2:L=AA*AB:GOSUB ALLOC + T=2:L=A*B:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_DIV: - T=2:L=AA/AB:GOSUB ALLOC + T=2:L=A/B:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_FUNCTION_DONE: @@ -244,19 +244,19 @@ MAIN: REM + function A=1:GOSUB NATIVE_FUNCTION - H=D:K$="+":C=R:GOSUB ASSOC1_S:D=R + H=D:B$="+":C=R:GOSUB ASSOC1_S:D=R REM - function A=2:GOSUB NATIVE_FUNCTION - H=D:K$="-":C=R:GOSUB ASSOC1_S:D=R + H=D:B$="-":C=R:GOSUB ASSOC1_S:D=R REM * function A=3:GOSUB NATIVE_FUNCTION - H=D:K$="*":C=R:GOSUB ASSOC1_S:D=R + H=D:B$="*":C=R:GOSUB ASSOC1_S:D=R REM / function A=4:GOSUB NATIVE_FUNCTION - H=D:K$="/":C=R:GOSUB ASSOC1_S:D=R + H=D:B$="/":C=R:GOSUB ASSOC1_S:D=R ZT=ZI: REM top of memory after base repl_env diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index 22a234f5ce..b23fc0e292 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -210,7 +210,7 @@ SUB EVAL GOTO EVAL_RETURN EVAL_INVOKE: CALL EVAL_AST - T6=R + W=R REM if error, return f/args for release by caller IF ER<>-2 THEN GOTO EVAL_RETURN @@ -220,7 +220,7 @@ SUB EVAL R=F:GOSUB DEREF_R:F=R IF (Z%(F,0)AND 31)<>9 THEN ER=-1:E$="apply of non-function":GOTO EVAL_RETURN GOSUB DO_FUNCTION - AY=T6:GOSUB RELEASE + AY=W:GOSUB RELEASE GOTO EVAL_RETURN EVAL_RETURN: @@ -254,8 +254,8 @@ DO_FUNCTION: G=Z%(F,1) REM Get argument values - R=AR+1:GOSUB DEREF_R:AA=Z%(R,1) - R=Z%(AR,1)+1:GOSUB DEREF_R:AB=Z%(R,1) + R=AR+1:GOSUB DEREF_R:A=Z%(R,1) + R=Z%(AR,1)+1:GOSUB DEREF_R:B=Z%(R,1) REM Switch on the function number IF G=1 THEN GOTO DO_ADD @@ -265,16 +265,16 @@ DO_FUNCTION: ER=-1:E$="unknown function"+STR$(G):RETURN DO_ADD: - T=2:L=AA+AB:GOSUB ALLOC + T=2:L=A+B:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_SUB: - T=2:L=AA-AB:GOSUB ALLOC + T=2:L=A-B:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_MULT: - T=2:L=AA*AB:GOSUB ALLOC + T=2:L=A*B:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_DIV: - T=2:L=AA/AB:GOSUB ALLOC + T=2:L=A/B:GOSUB ALLOC GOTO DO_FUNCTION_DONE DO_FUNCTION_DONE: @@ -319,19 +319,19 @@ MAIN: E=D REM + function A=1:GOSUB NATIVE_FUNCTION - K$="+":C=R:GOSUB ENV_SET_S + B$="+":C=R:GOSUB ENV_SET_S REM - function A=2:GOSUB NATIVE_FUNCTION - K$="-":C=R:GOSUB ENV_SET_S + B$="-":C=R:GOSUB ENV_SET_S REM * function A=3:GOSUB NATIVE_FUNCTION - K$="*":C=R:GOSUB ENV_SET_S + B$="*":C=R:GOSUB ENV_SET_S REM / function A=4:GOSUB NATIVE_FUNCTION - K$="/":C=R:GOSUB ENV_SET_S + B$="/":C=R:GOSUB ENV_SET_S ZT=ZI: REM top of memory after base repl_env diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index e32ab16eaa..08dfb6f71d 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -237,7 +237,7 @@ SUB EVAL EVAL_IF_FALSE: AY=R:GOSUB RELEASE REM if no false case (A3), return nil - B=A:GOSUB COUNT + GOSUB COUNT IF R<4 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index daba262a05..b2b6ad44dd 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -257,7 +257,7 @@ SUB EVAL EVAL_IF_FALSE: AY=R:GOSUB RELEASE REM if no false case (A3), return nil - B=A:GOSUB COUNT + GOSUB COUNT IF R<4 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index 07b3020513..f30c6d96f9 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -257,7 +257,7 @@ SUB EVAL EVAL_IF_FALSE: AY=R:GOSUB RELEASE REM if no false case (A3), return nil - B=A:GOSUB COUNT + GOSUB COUNT IF R<4 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index 2d9e221b48..477ee6e60d 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -43,7 +43,7 @@ SUB QUASIQUOTE GOSUB PUSH_A REM rest of cases call quasiquote on ast[1..] A=Z%(A,1):CALL QUASIQUOTE - T6=R + W=R GOSUB POP_A REM set A to ast[0] for last two cases @@ -60,7 +60,7 @@ SUB QUASIQUOTE B=Z%(A,1)+1:GOSUB DEREF_B:B=B B$="concat":T=5:GOSUB STRING:C=R - A=T6:GOSUB LIST3 + A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership AY=A:GOSUB RELEASE AY=C:GOSUB RELEASE @@ -69,14 +69,14 @@ SUB QUASIQUOTE QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - Q=T6:GOSUB PUSH_Q + Q=W:GOSUB PUSH_Q REM A set above to ast[0] CALL QUASIQUOTE B=R - GOSUB POP_Q:T6=Q + GOSUB POP_Q:W=Q B$="cons":T=5:GOSUB STRING:C=R - A=T6:GOSUB LIST3 + A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership AY=A:GOSUB RELEASE AY=B:GOSUB RELEASE @@ -345,7 +345,7 @@ SUB EVAL EVAL_IF_FALSE: AY=R:GOSUB RELEASE REM if no false case (A3), return nil - B=A:GOSUB COUNT + GOSUB COUNT IF R<4 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index 44cfd10550..cbfe05f44f 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -43,7 +43,7 @@ SUB QUASIQUOTE GOSUB PUSH_A REM rest of cases call quasiquote on ast[1..] A=Z%(A,1):CALL QUASIQUOTE - T6=R + W=R GOSUB POP_A REM set A to ast[0] for last two cases @@ -60,7 +60,7 @@ SUB QUASIQUOTE B=Z%(A,1)+1:GOSUB DEREF_B:B=B B$="concat":T=5:GOSUB STRING:C=R - A=T6:GOSUB LIST3 + A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership AY=A:GOSUB RELEASE AY=C:GOSUB RELEASE @@ -69,14 +69,14 @@ SUB QUASIQUOTE QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - Q=T6:GOSUB PUSH_Q + Q=W:GOSUB PUSH_Q REM A set above to ast[0] CALL QUASIQUOTE B=R - GOSUB POP_Q:T6=Q + GOSUB POP_Q:W=Q B$="cons":T=5:GOSUB STRING:C=R - A=T6:GOSUB LIST3 + A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership AY=A:GOSUB RELEASE AY=B:GOSUB RELEASE @@ -411,7 +411,7 @@ SUB EVAL EVAL_IF_FALSE: AY=R:GOSUB RELEASE REM if no false case (A3), return nil - B=A:GOSUB COUNT + GOSUB COUNT IF R<4 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index 023e51939d..233c8c0341 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -43,7 +43,7 @@ SUB QUASIQUOTE GOSUB PUSH_A REM rest of cases call quasiquote on ast[1..] A=Z%(A,1):CALL QUASIQUOTE - T6=R + W=R GOSUB POP_A REM set A to ast[0] for last two cases @@ -60,7 +60,7 @@ SUB QUASIQUOTE B=Z%(A,1)+1:GOSUB DEREF_B:B=B B$="concat":T=5:GOSUB STRING:C=R - A=T6:GOSUB LIST3 + A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership AY=A:GOSUB RELEASE AY=C:GOSUB RELEASE @@ -69,14 +69,14 @@ SUB QUASIQUOTE QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - Q=T6:GOSUB PUSH_Q + Q=W:GOSUB PUSH_Q REM A set above to ast[0] CALL QUASIQUOTE B=R - GOSUB POP_Q:T6=Q + GOSUB POP_Q:W=Q B$="cons":T=5:GOSUB STRING:C=R - A=T6:GOSUB LIST3 + A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership AY=A:GOSUB RELEASE AY=B:GOSUB RELEASE @@ -443,7 +443,7 @@ SUB EVAL EVAL_IF_FALSE: AY=R:GOSUB RELEASE REM if no false case (A3), return nil - B=A:GOSUB COUNT + GOSUB COUNT IF R<4 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index e4c3765ad9..7e0d0d2fc8 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -43,7 +43,7 @@ SUB QUASIQUOTE GOSUB PUSH_A REM rest of cases call quasiquote on ast[1..] A=Z%(A,1):CALL QUASIQUOTE - T6=R + W=R GOSUB POP_A REM set A to ast[0] for last two cases @@ -60,7 +60,7 @@ SUB QUASIQUOTE B=Z%(A,1)+1:GOSUB DEREF_B:B=B B$="concat":T=5:GOSUB STRING:C=R - A=T6:GOSUB LIST3 + A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership AY=A:GOSUB RELEASE AY=C:GOSUB RELEASE @@ -69,14 +69,14 @@ SUB QUASIQUOTE QQ_DEFAULT: REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - Q=T6:GOSUB PUSH_Q + Q=W:GOSUB PUSH_Q REM A set above to ast[0] CALL QUASIQUOTE B=R - GOSUB POP_Q:T6=Q + GOSUB POP_Q:W=Q B$="cons":T=5:GOSUB STRING:C=R - A=T6:GOSUB LIST3 + A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership AY=A:GOSUB RELEASE AY=B:GOSUB RELEASE @@ -443,7 +443,7 @@ SUB EVAL EVAL_IF_FALSE: AY=R:GOSUB RELEASE REM if no false case (A3), return nil - B=A:GOSUB COUNT + GOSUB COUNT IF R<4 THEN R=0:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop diff --git a/basic/types.in.bas b/basic/types.in.bas index af9cdf3c51..b055c2bdcf 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -28,8 +28,8 @@ INIT_MEMORY: #cbm T=FRE(0) #qbasic T=0 - Z1=4096: REM Z% (boxed memory) size (4 bytes each) - Z2=200: REM S$/S% (string memory) size (3+2 bytes each) + Z1=4195: REM Z% (boxed memory) size (4 bytes each) + Z2=199: REM S$/S% (string memory) size (3+2 bytes each) #qbasic Z3=200: REM X% (call stack) size (2 bytes each) #cbm Z3=49152: REM X starting point at $C000 (2 bytes each) #qbasic Z4=64: REM Y% (release stack) size (4 bytes each) @@ -50,15 +50,22 @@ INIT_MEMORY: DIM Z%(Z1,1): REM TYPE ARRAY REM Predefine nil, false, true, and an empty list - Z%(0,0)=0:Z%(0,1)=0 - Z%(1,0)=1:Z%(1,1)=0 + FOR I=0 TO 8:Z%(I,0)=0:Z%(I,1)=0:NEXT I + Z%(1,0)=1 Z%(2,0)=1:Z%(2,1)=1 Z%(3,0)=6+32:Z%(3,1)=0 - Z%(4,0)=0:Z%(4,1)=0 Z%(5,0)=7+32:Z%(5,1)=0 - Z%(6,0)=0:Z%(6,1)=0 Z%(7,0)=8+32:Z%(7,1)=0 - Z%(8,0)=0:Z%(8,1)=0 + +REM Z%(0,0)=0:Z%(0,1)=0 +REM Z%(1,0)=1:Z%(1,1)=0 +REM Z%(2,0)=1:Z%(2,1)=1 +REM Z%(3,0)=6+32:Z%(3,1)=0 +REM Z%(4,0)=0:Z%(4,1)=0 +REM Z%(5,0)=7+32:Z%(5,1)=0 +REM Z%(6,0)=0:Z%(6,1)=0 +REM Z%(7,0)=8+32:Z%(7,1)=0 +REM Z%(8,0)=0:Z%(8,1)=0 REM start of unused memory ZI=9 @@ -155,30 +162,28 @@ ALLOC: IF T<6 OR T=9 OR T=12 OR T=14 THEN SZ=1 REM PRINT "ALLOC T: "+STR$(T)+", SZ: "+STR$(SZ)+", ZK: "+STR$(ZK) U=ZK - V=ZK + R=ZK ALLOC_LOOP: - IF V=ZI THEN GOTO ALLOC_UNUSED + IF R=ZI THEN GOTO ALLOC_UNUSED REM TODO sanity check that type is 15 - IF ((Z%(V,0)AND-32)/32)=SZ THEN GOTO ALLOC_MIDDLE - REM PRINT "ALLOC search: U: "+STR$(U)+", V: "+STR$(V) - U=V: REM previous set to current - V=Z%(V,1): REM current set to next + IF ((Z%(R,0)AND-32)/32)=SZ THEN GOTO ALLOC_MIDDLE + REM PRINT "ALLOC search: U: "+STR$(U)+", R: "+STR$(R) + U=R: REM previous set to current + R=Z%(R,1): REM current set to next GOTO ALLOC_LOOP ALLOC_MIDDLE: - REM PRINT "ALLOC_MIDDLE: U: "+STR$(U)+", V: "+STR$(V) - R=V + REM PRINT "ALLOC_MIDDLE: U: "+STR$(U)+", R: "+STR$(R) REM set free pointer (ZK) to next free - IF V=ZK THEN ZK=Z%(V,1) + IF R=ZK THEN ZK=Z%(R,1) REM set previous free to next free - IF V<>ZK THEN Z%(U,1)=Z%(V,1) + IF R<>ZK THEN Z%(U,1)=Z%(R,1) GOTO ALLOC_DONE ALLOC_UNUSED: - REM PRINT "ALLOC_UNUSED ZI: "+STR$(ZI)+", U: "+STR$(U)+", V: "+STR$(V) - R=V + REM PRINT "ALLOC_UNUSED ZI: "+STR$(ZI)+", U: "+STR$(U)+", R: "+STR$(R) ZI=ZI+SZ - IF U=V THEN ZK=ZI + IF U=R THEN ZK=ZI REM set previous free to new memory top - IF U<>V THEN Z%(U,1)=ZI + IF U<>R THEN Z%(U,1)=ZI GOTO ALLOC_DONE ALLOC_DONE: Z%(R,0)=T+32 @@ -236,8 +241,7 @@ RELEASE: REM sanity check not already freed IF (U)=15 THEN ER=-1:E$="RELEASE of free: "+STR$(AY):RETURN - IF U=14 THEN GOTO RELEASE_REFERENCE - IF Z%(AY,0)<15 THEN ER=-1:E$="Unowned object: "+STR$(AY):RETURN + IF Z%(AY,0)<15 THEN ER=-1:E$="Unowned: "+STR$(AY):RETURN REM decrease reference count by one Z%(AY,0)=Z%(AY,0)-32 @@ -246,43 +250,46 @@ RELEASE: IF Z%(AY,0)>=32 GOTO RELEASE_TOP REM switch on type - IF U<=3 OR U=9 THEN GOTO RELEASE_SIMPLE - IF U=4 OR U=5 THEN GOTO RELEASE_STRING - IF U>=6 AND U<=8 THEN GOTO RELEASE_SEQ - IF U=10 OR U=11 THEN GOTO RELEASE_MAL_FUNCTION - IF U>=16 THEN GOTO RELEASE_METADATA - IF U=12 THEN GOTO RELEASE_ATOM - IF U=13 THEN GOTO RELEASE_ENV + SZ=1: REM default FREE size, adjusted by RELEASE_* + IF U>=16 THEN GOSUB RELEASE_METADATA + +REM IF U<=3 OR U=9 THEN GOSUB RELEASE_SIMPLE +REM IF U=4 OR U=5 THEN GOSUB RELEASE_STRING +REM IF U>=6 AND U<=8 THEN GOSUB RELEASE_SEQ +REM IF U=10 OR U=11 THEN GOSUB RELEASE_MAL_FUNCTION +REM IF U>=16 THEN GOSUB RELEASE_METADATA +REM IF U=12 THEN GOSUB RELEASE_ATOM +REM IF U=13 THEN GOSUB RELEASE_ENV + + ON U+1 GOSUB RELEASE_SIMPLE,RELEASE_SIMPLE,RELEASE_SIMPLE,RELEASE_SIMPLE,RELEASE_STRING,RELEASE_STRING,RELEASE_SEQ,RELEASE_SEQ,RELEASE_SEQ,RELEASE_SIMPLE,RELEASE_MAL_FUNCTION,RELEASE_MAL_FUNCTION,RELEASE_ATOM,RELEASE_ENV + + REM free the current element and continue, SZ already set + GOSUB FREE + GOTO RELEASE_TOP RELEASE_SIMPLE: - REM simple type (no recursing), just call FREE on it - SZ=1:GOSUB FREE - GOTO RELEASE_TOP - RELEASE_SIMPLE_2: - REM free the current element and continue - SZ=2:GOSUB FREE - GOTO RELEASE_TOP + RETURN RELEASE_STRING: REM string type, release interned string, then FREE reference IF S%(V)=0 THEN ER=-1:E$="RELEASE of free string:"+STR$(S%(V)):RETURN S%(V)=S%(V)-1 IF S%(V)=0 THEN S$(V)="": REM free BASIC string REM free the atom itself - GOTO RELEASE_SIMPLE + RETURN RELEASE_SEQ: - IF V=0 THEN GOTO RELEASE_SIMPLE_2 + IF V=0 THEN SZ=2:RETURN IF Z%(AY+1,0)<>14 THEN ER=-1:E$="invalid list value"+STR$(AY+1):RETURN REM add value and next element to stack RC=RC+2 Q=Z%(AY+1,1):GOSUB PUSH_Q Q=V:GOSUB PUSH_Q - GOTO RELEASE_SIMPLE_2 + SZ=2:RETURN RELEASE_ATOM: REM add contained/referred value RC=RC+1 Q=V:GOSUB PUSH_Q REM free the atom itself - GOTO RELEASE_SIMPLE + RETURN RELEASE_MAL_FUNCTION: REM add ast, params and environment to stack RC=RC+3 @@ -290,36 +297,21 @@ RELEASE: Q=Z%(AY+1,0):GOSUB PUSH_Q Q=Z%(AY+1,1):GOSUB PUSH_Q REM free the current 2 element mal_function and continue - SZ=2:GOSUB FREE - GOTO RELEASE_TOP + SZ=2:RETURN RELEASE_METADATA: REM add object and metadata object RC=RC+2 Q=V:GOSUB PUSH_Q Q=Z%(AY+1,1):GOSUB PUSH_Q - SZ=2:GOSUB FREE - GOTO RELEASE_TOP + SZ=2:RETURN RELEASE_ENV: REM add the hashmap data to the stack RC=RC+1 Q=V:GOSUB PUSH_Q - REM if no outer set - IF Z%(AY+1,1)=-1 THEN GOTO RELEASE_ENV_FREE + REM if outer set, add outer env to stack + IF Z%(AY+1,1)<>-1 THEN RC=RC+1:Q=Z%(AY+1,1):GOSUB PUSH_Q REM add outer environment to the stack - RC=RC+1 - Q=Z%(AY+1,1):GOSUB PUSH_Q - RELEASE_ENV_FREE: - REM free the current 2 element environment and continue - SZ=2:GOSUB FREE - GOTO RELEASE_TOP - RELEASE_REFERENCE: - IF V=0 THEN GOTO RELEASE_SIMPLE - REM add the referred element to the stack - RC=RC+1 - Q=V:GOSUB PUSH_Q - REM free the current element and continue - SZ=1:GOSUB FREE - GOTO RELEASE_TOP + SZ=2:RETURN REM release stack functions @@ -391,8 +383,8 @@ EQUAL_Q: GOTO EQUAL_Q_DONE EQUAL_Q_SEQ: - IF (Z%(A,1)=0) AND (Z%(B,1)=0) THEN GOTO EQUAL_Q_DONE - IF (Z%(A,1)=0) OR (Z%(B,1)=0) THEN R=0:GOTO EQUAL_Q_DONE + IF Z%(A,1)=0 AND Z%(B,1)=0 THEN GOTO EQUAL_Q_DONE + IF Z%(A,1)=0 OR Z%(B,1)=0 THEN R=0:GOTO EQUAL_Q_DONE REM compare the elements A=Z%(A+1,1):B=Z%(B+1,1) @@ -463,15 +455,15 @@ REM PRINT "STRING ref: "+S$(I)+" (idx:"+STR$(I)+", ref "+STR$(S%(I))+")" REM REPLACE(R$, S1$, S2$) -> R$ REPLACE: - R3$=R$ + T3$=R$ R$="" I=1 - J=LEN(R3$) + J=LEN(T3$) REPLACE_LOOP: IF I>J THEN RETURN - C$=MID$(R3$,I,LEN(S1$)) + C$=MID$(T3$,I,LEN(S1$)) IF C$=S1$ THEN R$=R$+S2$:I=I+LEN(S1$) - IF C$<>S1$ THEN R$=R$+MID$(R3$,I,1):I=I+1 + IF C$<>S1$ THEN R$=R$+MID$(T3$,I,1):I=I+1 GOTO REPLACE_LOOP @@ -500,28 +492,29 @@ EMPTY_Q: IF Z%(A,1)=0 THEN R=1 RETURN -REM COUNT(B) -> R +REM COUNT(A) -> R REM - returns length of list, not a Z% index -REM - modifies B COUNT: + GOSUB PUSH_A R=-1 DO_COUNT_LOOP: R=R+1 - IF Z%(B,1)<>0 THEN B=Z%(B,1):GOTO DO_COUNT_LOOP + IF Z%(A,1)<>0 THEN A=Z%(A,1):GOTO DO_COUNT_LOOP + GOSUB POP_A RETURN REM LAST(A) -> R LAST: REM TODO check that actually a list/vector IF Z%(A,1)=0 THEN R=0:RETURN: REM empty seq, return nil - T6=0 + W=0 LAST_LOOP: IF Z%(A,1)=0 THEN GOTO LAST_DONE: REM end, return previous value - T6=A: REM current becomes previous entry + W=A: REM current becomes previous entry A=Z%(A,1): REM next entry GOTO LAST_LOOP LAST_DONE: - R=T6+1:GOSUB DEREF_R + R=W+1:GOSUB DEREF_R Z%(R,0)=Z%(R,0)+32 RETURN @@ -599,10 +592,10 @@ ASSOC1: AY=L:GOSUB RELEASE: REM we took ownership of previous hashmap RETURN -REM ASSOC1(H, K$, C) -> R +REM ASSOC1_S(H, B$, C) -> R ASSOC1_S: REM add the key string - B$=K$:T=4:GOSUB STRING + T=4:GOSUB STRING K=R:GOSUB ASSOC1 AY=K:GOSUB RELEASE: REM map took ownership of key RETURN diff --git a/basic/variables.txt b/basic/variables.txt index aafa3367c2..c4f84ee56c 100644 --- a/basic/variables.txt +++ b/basic/variables.txt @@ -23,7 +23,7 @@ D : root repl environment BT : begin time (TI) ER : error type (-2: none, -1: string, >=0: object) E$ : error string (ER=-1) -EZ : READLINE EOF +EZ : READLINE EOF return, READ_FILE EOF temp LV : EVAL stack call level/depth @@ -37,12 +37,12 @@ A : common call argument (especially EVAL, EVAL_AST) A$ : common call argument (READLINE, reader, string temp, key value) B : common call argument B$ : STRING arg (HASHMAP_GET temp), PR_STR_SEQ seperator + : INIT_CORE_SET_FUNCTION, ENV_SET_S, ASSOC1_S C : common call argument, DO_TCO_FUNCTION temp in DO_APPLY E : environment (EVAL, EVAL_AST) F : function H : hash map K : hash map key (Z% index) -K$ : INIT_CORE_SET_FUNCTION and ENV_SET_S L : ALLOC* Z%(R,1) default M : ALLOC* Z%(R+1,0) default N : ALLOC* Z%(R+1,1) default @@ -72,33 +72,27 @@ A0 : EVAL ast elements A1 : EVAL ast elements A2 : EVAL ast elements A3 : EVAL ast elements -AA : DO_*_FUNCTION arg1 -AB : DO_*_FUNCTION arg2 CZ : DO_CONCAT stack position ED : EQUAL_Q recursion depth counter RD : PR_OBJECT recursion depth SD : READ_STR sequence read recursion depth - C$ : READ_TOKEN, SKIP_SPACES, SKIP_TO_EOL current character G : function value ON GOTO switch flag I : STRING, REPLACE, SLICE, PR_MEMORY, PR_OBJECT J : REPLACE U : ALLOC, RELEASE, PR_STR temp -V : ALLOC, RELEASE, PR_STR_SEQ temp -W : SLICE temp +V : RELEASE, PR_STR_SEQ temp +W : SLICE, LAST, QUASIQUOTE, step2-3 EVAL temp RC : RELEASE remaining number of elements to release RF : reader reading from file flag -RS : reader EOF state (1=EOF) S1 : READ_TOKEN in a string? S2 : READ_TOKEN escaped? T$ : READ_* current token string -T1 : EQUAL_Q, PR_STR, and core DO_KEYS_VALS temp +T1 : EQUAL_Q, PR_STR, DO_KEYS_VALS temp T2 : EQUAL_Q, DO_KEY_VALS, HASH_MAP_GET T3$ : REPLACE temp -T6 : LAST and QUASIQUOTE temp (step2-3 EVAL temp) -T7$ : READ_FORM:READ_STRING character temp Unused: From 3934e3f82543e4bd3c4701957bcbf98f2c8e4bfb Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Sun, 13 Nov 2016 14:21:44 -0800 Subject: [PATCH 0229/2308] add Dart implementation --- Makefile | 3 +- dart/.analysis_options | 7 + dart/.packages | 2 + dart/core.dart | 287 ++++++++++++++++++++++++++++++++++++ dart/env.dart | 54 +++++++ dart/printer.dart | 47 ++++++ dart/pubspec.lock | 4 + dart/pubspec.yaml | 3 + dart/reader.dart | 144 +++++++++++++++++++ dart/run | 2 + dart/step0_repl.dart | 20 +++ dart/step1_read_print.dart | 37 +++++ dart/step2_eval.dart | 93 ++++++++++++ dart/step3_env.dart | 133 +++++++++++++++++ dart/step4_if_fn_do.dart | 146 +++++++++++++++++++ dart/step5_tco.dart | 161 +++++++++++++++++++++ dart/step6_file.dart | 172 ++++++++++++++++++++++ dart/step7_quote.dart | 205 ++++++++++++++++++++++++++ dart/step8_macros.dart | 257 +++++++++++++++++++++++++++++++++ dart/step9_try.dart | 277 +++++++++++++++++++++++++++++++++++ dart/stepA_mal.dart | 288 +++++++++++++++++++++++++++++++++++++ dart/types.dart | 278 +++++++++++++++++++++++++++++++++++ 22 files changed, 2619 insertions(+), 1 deletion(-) create mode 100644 dart/.analysis_options create mode 100644 dart/.packages create mode 100644 dart/core.dart create mode 100644 dart/env.dart create mode 100644 dart/printer.dart create mode 100644 dart/pubspec.lock create mode 100644 dart/pubspec.yaml create mode 100644 dart/reader.dart create mode 100755 dart/run create mode 100644 dart/step0_repl.dart create mode 100644 dart/step1_read_print.dart create mode 100644 dart/step2_eval.dart create mode 100644 dart/step3_env.dart create mode 100644 dart/step4_if_fn_do.dart create mode 100644 dart/step5_tco.dart create mode 100644 dart/step6_file.dart create mode 100644 dart/step7_quote.dart create mode 100644 dart/step8_macros.dart create mode 100644 dart/step9_try.dart create mode 100644 dart/stepA_mal.dart create mode 100644 dart/types.dart diff --git a/Makefile b/Makefile index 6404148a8a..0841fe09cf 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ DOCKERIZE = # Settings # -IMPLS = ada awk bash c d chuck clojure coffee cpp crystal cs erlang elisp \ +IMPLS = ada awk bash c d chuck clojure coffee cpp crystal cs dart erlang elisp \ elixir es6 factor forth fsharp go groovy guile haskell haxe \ io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php plpgsql plsql ps python r \ @@ -152,6 +152,7 @@ coffee_STEP_TO_PROG = coffee/$($(1)).coffee cpp_STEP_TO_PROG = cpp/$($(1)) crystal_STEP_TO_PROG = crystal/$($(1)) cs_STEP_TO_PROG = cs/$($(1)).exe +dart_STEP_TO_PROG = dart/$($(1)).dart elisp_STEP_TO_PROG = elisp/$($(1)).el elixir_STEP_TO_PROG = elixir/lib/mix/tasks/$($(1)).ex erlang_STEP_TO_PROG = erlang/$($(1)) diff --git a/dart/.analysis_options b/dart/.analysis_options new file mode 100644 index 0000000000..4a23f8bf57 --- /dev/null +++ b/dart/.analysis_options @@ -0,0 +1,7 @@ +analyzer: + strong-mode: true + exclude: + - step2_eval.dart + - step3_env.dart + - step4_if_fn_do.dart + - step5_tco.dart diff --git a/dart/.packages b/dart/.packages new file mode 100644 index 0000000000..92024203bd --- /dev/null +++ b/dart/.packages @@ -0,0 +1,2 @@ +# Generated by pub on 2016-08-20 13:39:08.695546. +mal:lib/ diff --git a/dart/core.dart b/dart/core.dart new file mode 100644 index 0000000000..9213a8034b --- /dev/null +++ b/dart/core.dart @@ -0,0 +1,287 @@ +import 'dart:io'; + +import 'printer.dart'; +import 'reader.dart' as reader; +import 'types.dart'; + +Map ns = { + new MalSymbol('+'): new MalBuiltin((List args) { + var a = args[0] as MalInt; + var b = args[1] as MalInt; + return new MalInt(a.value + b.value); + }), + new MalSymbol('-'): new MalBuiltin((List args) { + var a = args[0] as MalInt; + var b = args[1] as MalInt; + return new MalInt(a.value - b.value); + }), + new MalSymbol('*'): new MalBuiltin((List args) { + var a = args[0] as MalInt; + var b = args[1] as MalInt; + return new MalInt(a.value * b.value); + }), + new MalSymbol('/'): new MalBuiltin((List args) { + var a = args[0] as MalInt; + var b = args[1] as MalInt; + return new MalInt(a.value ~/ b.value); + }), + new MalSymbol('list'): + new MalBuiltin((List args) => new MalList(args.toList())), + new MalSymbol('list?'): new MalBuiltin( + (List args) => new MalBool(args.single is MalList)), + new MalSymbol('empty?'): new MalBuiltin((List args) { + var a = args.single as MalIterable; + return new MalBool(a.elements.isEmpty); + }), + new MalSymbol('count'): new MalBuiltin((List args) { + var a = args.first as MalIterable; + return new MalInt(a.elements.length); + }), + new MalSymbol('='): new MalBuiltin((List args) { + var a = args[0]; + var b = args[1]; + return new MalBool(a == b); + }), + new MalSymbol('<'): new MalBuiltin((List args) { + var a = args[0] as MalInt; + var b = args[1] as MalInt; + return new MalBool(a.value < b.value); + }), + new MalSymbol('<='): new MalBuiltin((List args) { + var a = args[0] as MalInt; + var b = args[1] as MalInt; + return new MalBool(a.value <= b.value); + }), + new MalSymbol('>'): new MalBuiltin((List args) { + var a = args[0] as MalInt; + var b = args[1] as MalInt; + return new MalBool(a.value > b.value); + }), + new MalSymbol('>='): new MalBuiltin((List args) { + var a = args[0] as MalInt; + var b = args[1] as MalInt; + return new MalBool(a.value >= b.value); + }), + new MalSymbol('pr-str'): new MalBuiltin((List args) { + return new MalString( + args.map((a) => pr_str(a, print_readably: true)).join(' ')); + }), + new MalSymbol('str'): new MalBuiltin((List args) { + return new MalString( + args.map((a) => pr_str(a, print_readably: false)).join()); + }), + new MalSymbol('prn'): new MalBuiltin((List args) { + print(args.map((a) => pr_str(a, print_readably: true)).join(' ')); + return new MalNil(); + }), + new MalSymbol('println'): new MalBuiltin((List args) { + print(args.map((a) => pr_str(a, print_readably: false)).join(' ')); + return new MalNil(); + }), + new MalSymbol('read-string'): new MalBuiltin((List args) { + var code = args.single as MalString; + return reader.read_str(code.value); + }), + new MalSymbol('slurp'): new MalBuiltin((List args) { + var fileName = args.single as MalString; + var file = new File(fileName.value); + return new MalString(file.readAsStringSync()); + }), + new MalSymbol('atom'): new MalBuiltin((List args) { + var value = args.single; + return new MalAtom(value); + }), + new MalSymbol('atom?'): new MalBuiltin((List args) { + var value = args.single; + return new MalBool(value is MalAtom); + }), + new MalSymbol('deref'): new MalBuiltin((List args) { + var atom = args.single as MalAtom; + return atom.value; + }), + new MalSymbol('reset!'): new MalBuiltin((List args) { + var atom = args[0] as MalAtom; + var newValue = args[1]; + atom.value = newValue; + return newValue; + }), + new MalSymbol('swap!'): new MalBuiltin((List args) { + var atom = args[0] as MalAtom; + var func = args[1] as MalCallable; + var fnArgs = [atom.value]..addAll(args.sublist(2)); + var result = func.call(fnArgs); + atom.value = result; + return result; + }), + new MalSymbol('cons'): new MalBuiltin((List args) { + var x = args[0]; + var xs = args[1] as MalIterable; + return new MalList([x]..addAll(xs)); + }), + new MalSymbol('concat'): new MalBuiltin((List args) { + var results = []; + for (MalIterable element in args) { + results.addAll(element); + } + return new MalList(results); + }), + new MalSymbol('nth'): new MalBuiltin((List args) { + var indexable = args[0] as MalIterable; + var index = args[1] as MalInt; + try { + return indexable[index.value]; + } on RangeError catch (e) { + throw new MalNativeException(e); + } + }), + new MalSymbol('first'): new MalBuiltin((List args) { + var list = args.first as MalIterable; + if (list.isEmpty) return new MalNil(); + return list.first; + }), + new MalSymbol('rest'): new MalBuiltin((List args) { + var list = args.first as MalIterable; + if (list.isEmpty) return new MalList([]); + return new MalList(list.sublist(1)); + }), + new MalSymbol('throw'): new MalBuiltin((List args) { + throw new MalException(args.first); + }), + new MalSymbol('nil?'): new MalBuiltin((List args) { + return new MalBool(args.first is MalNil); + }), + new MalSymbol('true?'): new MalBuiltin((List args) { + return new MalBool(args.first is MalBool && (args.first as MalBool).value); + }), + new MalSymbol('false?'): new MalBuiltin((List args) { + return new MalBool(args.first is MalBool && !(args.first as MalBool).value); + }), + new MalSymbol('symbol'): new MalBuiltin((List args) { + return new MalSymbol((args.first as MalString).value); + }), + new MalSymbol('symbol?'): new MalBuiltin((List args) { + return new MalBool(args.first is MalSymbol); + }), + new MalSymbol('keyword'): new MalBuiltin((List args) { + if (args.first is MalKeyword) return args.first; + return new MalKeyword((args.first as MalString).value); + }), + new MalSymbol('keyword?'): new MalBuiltin((List args) { + return new MalBool(args.first is MalKeyword); + }), + new MalSymbol('vector'): new MalBuiltin((List args) { + return new MalVector(args); + }), + new MalSymbol('vector?'): new MalBuiltin((List args) { + return new MalBool(args.first is MalVector); + }), + new MalSymbol('hash-map'): new MalBuiltin((List args) { + return new MalHashMap.fromSequence(args); + }), + new MalSymbol('map?'): new MalBuiltin((List args) { + return new MalBool(args.first is MalHashMap); + }), + new MalSymbol('assoc'): new MalBuiltin((List args) { + var map = args.first as MalHashMap; + var assoc = new MalHashMap.fromSequence(args.skip(1).toList()); + var newMap = new Map.from(map.value); + newMap.addAll(assoc.value); + return new MalHashMap(newMap); + }), + new MalSymbol('dissoc'): new MalBuiltin((List args) { + var map = args.first as MalHashMap; + var newMap = new Map.from(map.value); + for (var key in args.skip(1)) { + newMap.remove(key); + } + return new MalHashMap(newMap); + }), + new MalSymbol('get'): new MalBuiltin((List args) { + if (args[0] is MalNil) return new MalNil(); + var map = args[0] as MalHashMap; + var key = args[1]; + return map.value[key] ?? new MalNil(); + }), + new MalSymbol('contains?'): new MalBuiltin((List args) { + var map = args[0] as MalHashMap; + var key = args[1]; + return new MalBool(map.value.containsKey(key)); + }), + new MalSymbol('keys'): new MalBuiltin((List args) { + return new MalList((args.first as MalHashMap).value.keys.toList()); + }), + new MalSymbol('vals'): new MalBuiltin((List args) { + return new MalList((args.first as MalHashMap).value.values.toList()); + }), + new MalSymbol('sequential?'): new MalBuiltin((List args) { + return new MalBool(args.first is MalList || args.first is MalVector); + }), + new MalSymbol('readline'): new MalBuiltin((List args) { + var message = args.first as MalString; + stdout.write(message.value); + var input = stdin.readLineSync(); + if (input == null) return new MalNil(); + return new MalString(input); + }), + new MalSymbol('time-ms'): new MalBuiltin((List args) { + assert(args.isEmpty); + return new MalInt(new DateTime.now().millisecondsSinceEpoch); + }), + new MalSymbol('conj'): new MalBuiltin((List args) { + var collection = args.first; + var elements = args.sublist(1); + if (collection is MalList) { + return new MalList( + elements.reversed.toList()..addAll(collection.elements)); + } + if (collection is MalVector) { + return new MalVector(collection.elements.toList()..addAll(elements)); + } + throw new MalException(new MalString('"conj" takes a list or vector')); + }), + new MalSymbol('string?'): new MalBuiltin((List args) { + return new MalBool(args.first is MalString); + }), + new MalSymbol('seq'): new MalBuiltin((List args) { + var arg = args.first; + if (arg is MalIterable && arg.isEmpty) return new MalNil(); + if (arg is MalString && arg.value.isEmpty) return new MalNil(); + + if (arg is MalNil || arg is MalList) return arg; + if (arg is MalVector) return new MalList(arg.elements.toList()); + if (arg is MalString) { + var chars = []; + for (var i = 0; i < arg.value.length; i++) { + chars.add(new MalString(arg.value[i])); + } + return new MalList(chars); + } + throw new MalException(new MalString('bad argument to "seq"')); + }), + new MalSymbol('map'): new MalBuiltin((List args) { + var fn = args[0] as MalCallable; + var list = args[1] as MalIterable; + var newList = []; + for (var element in list) { + newList.add(fn.call([element])); + } + return new MalList(newList); + }), + new MalSymbol('apply'): new MalBuiltin((List args) { + var func = args.first as MalCallable; + var argList = args.last as MalIterable; + var newArgs = args.sublist(1, args.length - 1); + newArgs.addAll(argList); + return func.call(newArgs); + }), + new MalSymbol('meta'): new MalBuiltin((List args) { + var arg = args.first; + return arg.meta ?? new MalNil(); + }), + new MalSymbol('with-meta'): new MalBuiltin((List args) { + var evaled = args.first; + var evaledWithMeta = evaled.clone(); + evaledWithMeta.meta = args[1]; + return evaledWithMeta; + }), +}; diff --git a/dart/env.dart b/dart/env.dart new file mode 100644 index 0000000000..122d377838 --- /dev/null +++ b/dart/env.dart @@ -0,0 +1,54 @@ +import 'types.dart'; + +class Env { + final Env outer; + + final data = {}; + + Env([this.outer, List binds, List exprs]) { + if (binds == null) { + assert(exprs == null); + } else { + assert(exprs != null && + (binds.length == exprs.length || binds.contains(new MalSymbol('&')))); + for (var i = 0; i < binds.length; i++) { + if (binds[i] == new MalSymbol('&')) { + set(binds[i + 1], new MalList(exprs.sublist(i))); + break; + } + set(binds[i], exprs[i]); + } + } + } + + void set(MalSymbol key, MalType value) { + data[key] = value; + } + + Env find(MalSymbol key) { + if (data[key] != null) { + return this; + } + if (outer != null) { + return outer.find(key); + } + return null; + } + + MalType get(MalSymbol key) { + var env = find(key); + if (env != null) { + return env.data[key]; + } + throw new NotFoundException(key.value); + } +} + +class NotFoundException implements Exception { + /// The name of the symbol that was not found. + final String value; + + NotFoundException(this.value); + + String toString() => "'$value' not found"; +} diff --git a/dart/printer.dart b/dart/printer.dart new file mode 100644 index 0000000000..472d9b7f0b --- /dev/null +++ b/dart/printer.dart @@ -0,0 +1,47 @@ +import 'types.dart'; + +String pr_str(MalType data, {bool print_readably: true}) { + if (data is MalSymbol) { + return data.value; + } else if (data is MalInt) { + return '${data.value}'; + } else if (data is MalList) { + var printedElements = + data.elements.map((e) => pr_str(e, print_readably: print_readably)); + return '(${printedElements.join(" ")})'; + } else if (data is MalVector) { + var printedElements = + data.elements.map((e) => pr_str(e, print_readably: print_readably)); + return '[${printedElements.join(" ")}]'; + } else if (data is MalHashMap) { + var printedElements = []; + data.value.forEach((key, value) { + printedElements.add(pr_str(key, print_readably: print_readably)); + printedElements.add(pr_str(value, print_readably: print_readably)); + }); + return '{${printedElements.join(" ")}}'; + } else if (data is MalString) { + if (print_readably) { + var readableValue = data.value + .replaceAll('\\', r'\\') + .replaceAll('\n', r'\n') + .replaceAll('\"', r'\"'); + return '"$readableValue"'; + } else { + return '${data.value}'; + } + } else if (data is MalKeyword) { + return ':${data.value}'; + } else if (data is MalBool) { + return '${data.value}'; + } else if (data is MalNil) { + return 'nil'; + } else if (data is MalBuiltin) { + return '#'; + } else if (data is MalClosure) { + return '#'; + } else if (data is MalAtom) { + return "(atom ${pr_str(data.value, print_readably: print_readably)})"; + } + throw new ArgumentError("Unrecognized type: ${data.runtimeType}"); +} diff --git a/dart/pubspec.lock b/dart/pubspec.lock new file mode 100644 index 0000000000..655fcfbf0a --- /dev/null +++ b/dart/pubspec.lock @@ -0,0 +1,4 @@ +# Generated by pub +# See http://pub.dartlang.org/doc/glossary.html#lockfile +packages: {} +sdk: any diff --git a/dart/pubspec.yaml b/dart/pubspec.yaml new file mode 100644 index 0000000000..4b09f91b79 --- /dev/null +++ b/dart/pubspec.yaml @@ -0,0 +1,3 @@ +name: mal +author: Harry Terkelsen +version: 0.0.1 diff --git a/dart/reader.dart b/dart/reader.dart new file mode 100644 index 0000000000..5734fce0f8 --- /dev/null +++ b/dart/reader.dart @@ -0,0 +1,144 @@ +import 'types.dart'; + +final malRegExp = new RegExp( + r"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)"""); + +class Reader { + final List tokens; + int _position = 0; + + Reader(this.tokens); + + String next() { + var token = peek(); + _position++; + return token; + } + + String peek() { + if (_position >= tokens.length) return null; + return tokens[_position]; + } +} + +class ParseException implements Exception { + final String message; + + ParseException(this.message); +} + +class NoInputException implements Exception {} + +MalType read_str(String code) { + var tokens = tokenizer(code); + if (tokens.isEmpty) { + throw new NoInputException(); + } + var reader = new Reader(tokens); + return read_form(reader); +} + +List tokenizer(String code) { + var matches = malRegExp.allMatches(code); + return matches + .map((m) => m.group(1)) + .where((token) => token.isNotEmpty && !token.startsWith(';')) + .toList(); +} + +MalType read_form(Reader reader) { + const macros = const { + "'": 'quote', + '`': 'quasiquote', + '~': 'unquote', + '~@': 'splice-unquote', + '@': 'deref', + '^': 'with-meta', + }; + const sequenceStarters = const {'(': ')', '[': ']', '{': '}'}; + var token = reader.peek(); + if (sequenceStarters.containsKey(token)) { + var elements = read_sequence(reader, token, sequenceStarters[token]); + if (token == '(') { + return new MalList(elements); + } + if (token == '[') { + return new MalVector(elements); + } + + if (token == '{') { + return new MalHashMap.fromSequence(elements); + } + + throw new StateError("Impossible!"); + } else if (macros.containsKey(token)) { + var macro = new MalSymbol(macros[token]); + reader.next(); + var form = read_form(reader); + if (token == '^') { + var meta = read_form(reader); + return new MalList([macro, meta, form]); + } else { + return new MalList([macro, form]); + } + } else { + return read_atom(reader); + } +} + +List read_sequence(Reader reader, String open, String close) { + // Consume opening token + var actualOpen = reader.next(); + assert(actualOpen == open); + + var elements = []; + for (var token = reader.peek();; token = reader.peek()) { + if (token == null) { + throw new ParseException("expected '$close', got EOF"); + } + if (token == close) break; + elements.add(read_form(reader)); + } + + var actualClose = reader.next(); + assert(actualClose == close); + + return elements; +} + +MalType read_atom(Reader reader) { + var token = reader.next(); + + var intAtom = int.parse(token, onError: (_) => null); + if (intAtom != null) { + return new MalInt(intAtom); + } + + if (token[0] == '"') { + var sanitizedToken = token + // remove surrounding quotes + .substring(1, token.length - 1) + .replaceAll(r'\"', '"') + .replaceAll(r'\n', '\n') + .replaceAll(r'\\', '\\'); + return new MalString(sanitizedToken); + } + + if (token[0] == ':') { + return new MalKeyword(token.substring(1)); + } + + if (token == 'nil') { + return new MalNil(); + } + + if (token == 'true') { + return new MalBool(true); + } + + if (token == 'false') { + return new MalBool(false); + } + + return new MalSymbol(token); +} diff --git a/dart/run b/dart/run new file mode 100755 index 0000000000..fefdb5875d --- /dev/null +++ b/dart/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec dart --checked $(dirname $0)/${STEP:-stepA_mal}.dart "${@}" diff --git a/dart/step0_repl.dart b/dart/step0_repl.dart new file mode 100644 index 0000000000..3eb3414a71 --- /dev/null +++ b/dart/step0_repl.dart @@ -0,0 +1,20 @@ +import 'dart:io'; + +String READ(String x) => x; + +String EVAL(String x) => x; + +String PRINT(String x) => x; + +String rep(String x) => PRINT(EVAL(READ(x))); + +const prompt = 'user> '; +main() { + while (true) { + stdout.write(prompt); + var input = stdin.readLineSync(); + if (input == null) return; + var output = rep(input); + stdout.writeln(output); + } +} diff --git a/dart/step1_read_print.dart b/dart/step1_read_print.dart new file mode 100644 index 0000000000..0799fa0aa3 --- /dev/null +++ b/dart/step1_read_print.dart @@ -0,0 +1,37 @@ +import 'dart:io'; + +import 'printer.dart' as printer; +import 'reader.dart' as reader; +import 'types.dart'; + +MalType READ(String x) => reader.read_str(x); + +MalType EVAL(MalType x) => x; + +String PRINT(MalType x) => printer.pr_str(x); + +String rep(String x) { + var parsed; + try { + parsed = READ(x); + } on reader.ParseException catch (e) { + return e.message; + } + return PRINT(EVAL(parsed)); +} + +const prompt = 'user> '; +main() { + while (true) { + stdout.write(prompt); + var input = stdin.readLineSync(); + if (input == null) return; + var output; + try { + output = rep(input); + } on reader.NoInputException { + continue; + } + stdout.writeln(output); + } +} diff --git a/dart/step2_eval.dart b/dart/step2_eval.dart new file mode 100644 index 0000000000..cdc8870fd0 --- /dev/null +++ b/dart/step2_eval.dart @@ -0,0 +1,93 @@ +import 'dart:io'; + +import 'printer.dart' as printer; +import 'reader.dart' as reader; +import 'types.dart'; + +final Map replEnv = { + new MalSymbol('+'): (MalInt a, MalInt b) => new MalInt(a.value + b.value), + new MalSymbol('-'): (MalInt a, MalInt b) => new MalInt(a.value - b.value), + new MalSymbol('*'): (MalInt a, MalInt b) => new MalInt(a.value * b.value), + new MalSymbol('/'): (MalInt a, MalInt b) => new MalInt(a.value ~/ b.value), +}; + +MalType READ(String x) => reader.read_str(x); + +class NotFoundException implements Exception { + /// The name of the symbol that was not found. + final String value; + + NotFoundException(this.value); +} + +eval_ast(MalType ast, Map env) { + if (ast is MalSymbol) { + var result = env[ast]; + if (result == null) { + throw new NotFoundException(ast.value); + } + return result; + } else if (ast is MalList) { + return new MalList(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalVector) { + return new MalVector(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalHashMap) { + var newMap = new Map.from(ast.value); + for (var key in newMap.keys) { + newMap[key] = EVAL(newMap[key], env); + } + return new MalHashMap(newMap); + } else { + return ast; + } +} + +EVAL(MalType ast, Map env) { + if (ast is! MalList) { + return eval_ast(ast, env); + } else { + if ((ast as MalList).elements.isEmpty) { + return ast; + } else { + var newAst = eval_ast(ast, env) as MalList; + Function f = newAst.elements.first; + var args = newAst.elements.sublist(1); + return Function.apply(f, args); + } + } +} + +String PRINT(MalType x) => printer.pr_str(x); + +String rep(String x) { + var parsed; + try { + parsed = READ(x); + } on reader.ParseException catch (e) { + return e.message; + } + + var evaledAst; + try { + evaledAst = EVAL(parsed, replEnv); + } on NotFoundException catch (e) { + return "'${e.value}' not found"; + } + return PRINT(evaledAst); +} + +const prompt = 'user> '; +main() { + while (true) { + stdout.write(prompt); + var input = stdin.readLineSync(); + if (input == null) return; + var output; + try { + output = rep(input); + } on reader.NoInputException { + continue; + } + stdout.writeln(output); + } +} diff --git a/dart/step3_env.dart b/dart/step3_env.dart new file mode 100644 index 0000000000..5df9a9d416 --- /dev/null +++ b/dart/step3_env.dart @@ -0,0 +1,133 @@ +import 'dart:io'; + +import 'env.dart'; +import 'printer.dart' as printer; +import 'reader.dart' as reader; +import 'types.dart'; + +final Env replEnv = new Env(); + +void setupEnv() { + replEnv.set(new MalSymbol('+'), new MalBuiltin((List args) { + var a = args[0] as MalInt; + var b = args[1] as MalInt; + return new MalInt(a.value + b.value); + })); + replEnv.set(new MalSymbol('-'), new MalBuiltin((List args) { + var a = args[0] as MalInt; + var b = args[1] as MalInt; + return new MalInt(a.value - b.value); + })); + replEnv.set(new MalSymbol('*'), new MalBuiltin((List args) { + var a = args[0] as MalInt; + var b = args[1] as MalInt; + return new MalInt(a.value * b.value); + })); + replEnv.set(new MalSymbol('/'), new MalBuiltin((List args) { + var a = args[0] as MalInt; + var b = args[1] as MalInt; + return new MalInt(a.value ~/ b.value); + })); +} + +MalType READ(String x) => reader.read_str(x); + +MalType eval_ast(MalType ast, Env env) { + if (ast is MalSymbol) { + var result = env.get(ast); + if (result == null) { + throw new NotFoundException(ast.value); + } + return result; + } else if (ast is MalList) { + return new MalList(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalVector) { + return new MalVector(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalHashMap) { + var newMap = new Map.from(ast.value); + for (var key in newMap.keys) { + newMap[key] = EVAL(newMap[key], env); + } + return new MalHashMap(newMap); + } else { + return ast; + } +} + +MalType EVAL(MalType ast, Env env) { + if (ast is! MalList) { + return eval_ast(ast, env); + } else { + if ((ast as MalList).elements.isEmpty) { + return ast; + } else { + var list = ast as MalList; + if (list.elements.first is MalSymbol) { + var symbol = list.elements.first as MalSymbol; + var args = list.elements.sublist(1); + if (symbol.value == "def!") { + MalSymbol key = args.first; + MalType value = EVAL(args[1], env); + env.set(key, value); + return value; + } else if (symbol.value == "let*") { + // TODO(het): If elements.length is not even, give helpful error + Iterable> pairs(List elements) sync* { + for (var i = 0; i < elements.length; i += 2) { + yield [elements[i], elements[i + 1]]; + } + } + + var newEnv = new Env(env); + MalIterable bindings = args.first; + for (var pair in pairs(bindings.elements)) { + MalSymbol key = pair[0]; + MalType value = EVAL(pair[1], newEnv); + newEnv.set(key, value); + } + return EVAL(args[1], newEnv); + } + } + var newAst = eval_ast(ast, env) as MalList; + MalBuiltin f = newAst.elements.first; + var args = newAst.elements.sublist(1); + return f.call(args); + } + } +} + +String PRINT(MalType x) => printer.pr_str(x); + +String rep(String x) { + var parsed; + try { + parsed = READ(x); + } on reader.ParseException catch (e) { + return e.message; + } + + var evaledAst; + try { + evaledAst = EVAL(parsed, replEnv); + } on NotFoundException catch (e) { + return "'${e.value}' not found"; + } + return PRINT(evaledAst); +} + +const prompt = 'user> '; +main() { + setupEnv(); + while (true) { + stdout.write(prompt); + var input = stdin.readLineSync(); + if (input == null) return; + var output; + try { + output = rep(input); + } on reader.NoInputException { + continue; + } + stdout.writeln(output); + } +} diff --git a/dart/step4_if_fn_do.dart b/dart/step4_if_fn_do.dart new file mode 100644 index 0000000000..73bef70313 --- /dev/null +++ b/dart/step4_if_fn_do.dart @@ -0,0 +1,146 @@ +import 'dart:io'; + +import 'core.dart'; +import 'env.dart'; +import 'printer.dart' as printer; +import 'reader.dart' as reader; +import 'types.dart'; + +final Env replEnv = new Env(); + +void setupEnv() { + ns.forEach((sym, fun) => replEnv.set(sym, fun)); + + rep('(def! not (fn* (a) (if a false true)))'); +} + +MalType READ(String x) => reader.read_str(x); + +MalType eval_ast(MalType ast, Env env) { + if (ast is MalSymbol) { + var result = env.get(ast); + if (result == null) { + throw new NotFoundException(ast.value); + } + return result; + } else if (ast is MalList) { + return new MalList(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalVector) { + return new MalVector(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalHashMap) { + var newMap = new Map.from(ast.value); + for (var key in newMap.keys) { + newMap[key] = EVAL(newMap[key], env); + } + return new MalHashMap(newMap); + } else { + return ast; + } +} + +MalType EVAL(MalType ast, Env env) { + if (ast is! MalList) { + return eval_ast(ast, env); + } else { + if ((ast as MalList).elements.isEmpty) { + return ast; + } else { + var list = ast as MalList; + if (list.elements.first is MalSymbol) { + var symbol = list.elements.first as MalSymbol; + var args = list.elements.sublist(1); + if (symbol.value == "def!") { + MalSymbol key = args.first; + MalType value = EVAL(args[1], env); + env.set(key, value); + return value; + } else if (symbol.value == "let*") { + // TODO(het): If elements.length is not even, give helpful error + Iterable> pairs(List elements) sync* { + for (var i = 0; i < elements.length; i += 2) { + yield [elements[i], elements[i + 1]]; + } + } + + var newEnv = new Env(env); + MalIterable bindings = args.first; + for (var pair in pairs(bindings.elements)) { + MalSymbol key = pair[0]; + MalType value = EVAL(pair[1], newEnv); + newEnv.set(key, value); + } + return EVAL(args[1], newEnv); + } else if (symbol.value == "do") { + return args.map((e) => EVAL(e, env)).toList().last; + } else if (symbol.value == "if") { + var condition = EVAL(args[0], env); + if (condition is MalNil || + condition is MalBool && condition.value == false) { + // False side of branch + if (args.length < 3) { + return new MalNil(); + } + return EVAL(args[2], env); + } else { + // True side of branch + return EVAL(args[1], env); + } + } else if (symbol.value == "fn*") { + var params = (args[0] as MalIterable) + .elements + .map((e) => e as MalSymbol) + .toList(); + return new MalClosure( + params, + args[1], + env, + (List funcArgs) => + EVAL(args[1], new Env(env, params, funcArgs))); + } + } + var newAst = eval_ast(ast, env) as MalList; + var f = newAst.elements.first; + if (f is MalCallable) { + return f.call(newAst.elements.sublist(1)); + } else { + throw 'bad!'; + } + } + } +} + +String PRINT(MalType x) => printer.pr_str(x); + +String rep(String x) { + var parsed; + try { + parsed = READ(x); + } on reader.ParseException catch (e) { + return e.message; + } + + var evaledAst; + try { + evaledAst = EVAL(parsed, replEnv); + } on NotFoundException catch (e) { + return "'${e.value}' not found"; + } + return PRINT(evaledAst); +} + +const prompt = 'user> '; +main() { + setupEnv(); + while (true) { + stdout.write(prompt); + var input = stdin.readLineSync(); + if (input == null) return; + var output; + try { + output = rep(input); + } on reader.NoInputException { + continue; + } + stdout.writeln(output); + } +} diff --git a/dart/step5_tco.dart b/dart/step5_tco.dart new file mode 100644 index 0000000000..b8dd03b4a0 --- /dev/null +++ b/dart/step5_tco.dart @@ -0,0 +1,161 @@ +import 'dart:io'; + +import 'core.dart'; +import 'env.dart'; +import 'printer.dart' as printer; +import 'reader.dart' as reader; +import 'types.dart'; + +final Env replEnv = new Env(); + +void setupEnv() { + ns.forEach((sym, fun) => replEnv.set(sym, fun)); + + rep('(def! not (fn* (a) (if a false true)))'); +} + +MalType READ(String x) => reader.read_str(x); + +MalType eval_ast(MalType ast, Env env) { + if (ast is MalSymbol) { + var result = env.get(ast); + if (result == null) { + throw new NotFoundException(ast.value); + } + return result; + } else if (ast is MalList) { + return new MalList(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalVector) { + return new MalVector(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalHashMap) { + var newMap = new Map.from(ast.value); + for (var key in newMap.keys) { + newMap[key] = EVAL(newMap[key], env); + } + return new MalHashMap(newMap); + } else { + return ast; + } +} + +MalType EVAL(MalType ast, Env env) { + while (true) { + if (ast is! MalList) { + return eval_ast(ast, env); + } else { + if ((ast as MalList).elements.isEmpty) { + return ast; + } else { + var list = ast as MalList; + if (list.elements.first is MalSymbol) { + var symbol = list.elements.first as MalSymbol; + var args = list.elements.sublist(1); + if (symbol.value == "def!") { + MalSymbol key = args.first; + MalType value = EVAL(args[1], env); + env.set(key, value); + return value; + } else if (symbol.value == "let*") { + // TODO(het): If elements.length is not even, give helpful error + Iterable> pairs(List elements) sync* { + for (var i = 0; i < elements.length; i += 2) { + yield [elements[i], elements[i + 1]]; + } + } + + var newEnv = new Env(env); + MalIterable bindings = args.first; + for (var pair in pairs(bindings.elements)) { + MalSymbol key = pair[0]; + MalType value = EVAL(pair[1], newEnv); + newEnv.set(key, value); + } + ast = args[1]; + env = newEnv; + continue; + } else if (symbol.value == "do") { + for (var element in args.sublist(0, args.length - 1)) { + eval_ast(element, env); + } + ast = args.last; + continue; + } else if (symbol.value == "if") { + var condition = EVAL(args[0], env); + if (condition is MalNil || + condition is MalBool && condition.value == false) { + // False side of branch + if (args.length < 3) { + return new MalNil(); + } + ast = args[2]; + continue; + } else { + // True side of branch + ast = args[1]; + continue; + } + } else if (symbol.value == "fn*") { + var params = (args[0] as MalIterable) + .elements + .map((e) => e as MalSymbol) + .toList(); + return new MalClosure( + params, + args[1], + env, + (List funcArgs) => + EVAL(args[1], new Env(env, params, funcArgs))); + } + } + var newAst = eval_ast(ast, env) as MalList; + var f = newAst.elements.first; + var args = newAst.elements.sublist(1); + if (f is MalBuiltin) { + return f.call(args); + } else if (f is MalClosure) { + ast = f.ast; + env = new Env(f.env, f.params, args); + continue; + } else { + throw 'bad!'; + } + } + } + } +} + +String PRINT(MalType x) => printer.pr_str(x); + +String rep(String x) { + var parsed; + try { + parsed = READ(x); + } on reader.ParseException catch (e) { + return e.message; + } + + var evaledAst; + try { + evaledAst = EVAL(parsed, replEnv); + } on NotFoundException catch (e) { + return "'${e.value}' not found"; + } + return PRINT(evaledAst); +} + +const prompt = 'user> '; +main() { + setupEnv(); + while (true) { + stdout.write(prompt); + var input = stdin.readLineSync(); + if (input == null) return; + var output; + try { + output = rep(input); + } on reader.NoInputException { + continue; + } + stdout.writeln(output); + } +} diff --git a/dart/step6_file.dart b/dart/step6_file.dart new file mode 100644 index 0000000000..d4e5fb3a40 --- /dev/null +++ b/dart/step6_file.dart @@ -0,0 +1,172 @@ +import 'dart:io'; + +import 'core.dart'; +import 'env.dart'; +import 'printer.dart' as printer; +import 'reader.dart' as reader; +import 'types.dart'; + +final Env replEnv = new Env(); + +void setupEnv(List argv) { + // TODO(het): use replEnv#set once generalized tearoffs are implemented + ns.forEach((sym, fun) => replEnv.set(sym, fun)); + + replEnv.set(new MalSymbol('eval'), + new MalBuiltin((List args) => EVAL(args.single, replEnv))); + + replEnv.set(new MalSymbol('*ARGV*'), + new MalList(argv.map((s) => new MalString(s)).toList())); + + rep('(def! not (fn* (a) (if a false true)))'); + rep("(def! load-file " + "(fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); +} + +MalType READ(String x) => reader.read_str(x); + +MalType eval_ast(MalType ast, Env env) { + if (ast is MalSymbol) { + var result = env.get(ast); + if (result == null) { + throw new NotFoundException(ast.value); + } + return result; + } else if (ast is MalList) { + return new MalList(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalVector) { + return new MalVector(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalHashMap) { + var newMap = new Map.from(ast.value); + for (var key in newMap.keys) { + newMap[key] = EVAL(newMap[key], env); + } + return new MalHashMap(newMap); + } else { + return ast; + } +} + +MalType EVAL(MalType ast, Env env) { + while (true) { + if (ast is! MalList) { + return eval_ast(ast, env); + } else { + if ((ast as MalList).elements.isEmpty) { + return ast; + } else { + var list = ast as MalList; + if (list.elements.first is MalSymbol) { + var symbol = list.elements.first as MalSymbol; + var args = list.elements.sublist(1); + if (symbol.value == "def!") { + MalSymbol key = args.first; + MalType value = EVAL(args[1], env); + env.set(key, value); + return value; + } else if (symbol.value == "let*") { + // TODO(het): If elements.length is not even, give helpful error + Iterable> pairs(List elements) sync* { + for (var i = 0; i < elements.length; i += 2) { + yield [elements[i], elements[i + 1]]; + } + } + + var newEnv = new Env(env); + MalIterable bindings = args.first; + for (var pair in pairs(bindings.elements)) { + MalSymbol key = pair[0]; + MalType value = EVAL(pair[1], newEnv); + newEnv.set(key, value); + } + ast = args[1]; + env = newEnv; + continue; + } else if (symbol.value == "do") { + eval_ast(new MalList(args.sublist(0, args.length - 1)), env); + ast = args.last; + continue; + } else if (symbol.value == "if") { + var condition = EVAL(args[0], env); + if (condition is MalNil || + condition is MalBool && condition.value == false) { + // False side of branch + if (args.length < 3) { + return new MalNil(); + } + ast = args[2]; + continue; + } else { + // True side of branch + ast = args[1]; + continue; + } + } else if (symbol.value == "fn*") { + var params = (args[0] as MalIterable) + .elements + .map((e) => e as MalSymbol) + .toList(); + return new MalClosure( + params, + args[1], + env, + (List funcArgs) => + EVAL(args[1], new Env(env, params, funcArgs))); + } + } + var newAst = eval_ast(ast, env) as MalList; + var f = newAst.elements.first; + var args = newAst.elements.sublist(1); + if (f is MalBuiltin) { + return f.call(args); + } else if (f is MalClosure) { + ast = f.ast; + env = new Env(f.env, f.params, args); + continue; + } else { + throw 'bad!'; + } + } + } + } +} + +String PRINT(MalType x) => printer.pr_str(x); + +String rep(String x) { + var parsed; + try { + parsed = READ(x); + } on reader.ParseException catch (e) { + return e.message; + } + + var evaledAst; + try { + evaledAst = EVAL(parsed, replEnv); + } on NotFoundException catch (e) { + return "'${e.value}' not found"; + } + return PRINT(evaledAst); +} + +const prompt = 'user> '; +main(List args) { + setupEnv(args.isEmpty ? const [] : args.sublist(1)); + if (args.isNotEmpty) { + rep("(load-file \"${args.first}\")"); + return; + } + while (true) { + stdout.write(prompt); + var input = stdin.readLineSync(); + if (input == null) return; + var output; + try { + output = rep(input); + } on reader.NoInputException { + continue; + } + stdout.writeln(output); + } +} diff --git a/dart/step7_quote.dart b/dart/step7_quote.dart new file mode 100644 index 0000000000..a2b45f8b8e --- /dev/null +++ b/dart/step7_quote.dart @@ -0,0 +1,205 @@ +import 'dart:io'; + +import 'core.dart'; +import 'env.dart'; +import 'printer.dart' as printer; +import 'reader.dart' as reader; +import 'types.dart'; + +final Env replEnv = new Env(); + +void setupEnv(List argv) { + // TODO(het): use replEnv#set once generalized tearoffs are implemented + ns.forEach((sym, fun) => replEnv.set(sym, fun)); + + replEnv.set(new MalSymbol('eval'), + new MalBuiltin((List args) => EVAL(args.single, replEnv))); + + replEnv.set(new MalSymbol('*ARGV*'), + new MalList(argv.map((s) => new MalString(s)).toList())); + + rep('(def! not (fn* (a) (if a false true)))'); + rep("(def! load-file " + "(fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); +} + +MalType quasiquote(MalType ast) { + bool isPair(MalType ast) { + return ast is MalIterable && ast.isNotEmpty; + } + + if (!isPair(ast)) { + return new MalList([new MalSymbol("quote"), ast]); + } else { + var list = ast as MalIterable; + if (list.first == new MalSymbol("unquote")) { + return list[1]; + } else if (isPair(list.first) && + (list.first as MalIterable).first == new MalSymbol("splice-unquote")) { + return new MalList([ + new MalSymbol("concat"), + (list.first as MalIterable)[1], + quasiquote(new MalList(list.sublist(1))) + ]); + } else { + return new MalList([ + new MalSymbol("cons"), + quasiquote(list[0]), + quasiquote(new MalList(list.sublist(1))) + ]); + } + } +} + +MalType READ(String x) => reader.read_str(x); + +MalType eval_ast(MalType ast, Env env) { + if (ast is MalSymbol) { + var result = env.get(ast); + if (result == null) { + throw new NotFoundException(ast.value); + } + return result; + } else if (ast is MalList) { + return new MalList(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalVector) { + return new MalVector(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalHashMap) { + var newMap = new Map.from(ast.value); + for (var key in newMap.keys) { + newMap[key] = EVAL(newMap[key], env); + } + return new MalHashMap(newMap); + } else { + return ast; + } +} + +MalType EVAL(MalType ast, Env env) { + while (true) { + if (ast is! MalList) { + return eval_ast(ast, env); + } else { + if ((ast as MalList).elements.isEmpty) { + return ast; + } else { + var list = ast as MalList; + if (list.elements.first is MalSymbol) { + var symbol = list.elements.first as MalSymbol; + var args = list.elements.sublist(1); + if (symbol.value == "def!") { + MalSymbol key = args.first; + MalType value = EVAL(args[1], env); + env.set(key, value); + return value; + } else if (symbol.value == "let*") { + // TODO(het): If elements.length is not even, give helpful error + Iterable> pairs(List elements) sync* { + for (var i = 0; i < elements.length; i += 2) { + yield [elements[i], elements[i + 1]]; + } + } + + var newEnv = new Env(env); + MalIterable bindings = args.first; + for (var pair in pairs(bindings.elements)) { + MalSymbol key = pair[0]; + MalType value = EVAL(pair[1], newEnv); + newEnv.set(key, value); + } + ast = args[1]; + env = newEnv; + continue; + } else if (symbol.value == "do") { + eval_ast(new MalList(args.sublist(0, args.length - 1)), env); + ast = args.last; + continue; + } else if (symbol.value == "if") { + var condition = EVAL(args[0], env); + if (condition is MalNil || + condition is MalBool && condition.value == false) { + // False side of branch + if (args.length < 3) { + return new MalNil(); + } + ast = args[2]; + continue; + } else { + // True side of branch + ast = args[1]; + continue; + } + } else if (symbol.value == "fn*") { + var params = (args[0] as MalIterable) + .elements + .map((e) => e as MalSymbol) + .toList(); + return new MalClosure( + params, + args[1], + env, + (List funcArgs) => + EVAL(args[1], new Env(env, params, funcArgs))); + } else if (symbol.value == "quote") { + return args.single; + } else if (symbol.value == "quasiquote") { + ast = quasiquote(args.first); + continue; + } + } + var newAst = eval_ast(ast, env) as MalList; + var f = newAst.elements.first; + var args = newAst.elements.sublist(1); + if (f is MalBuiltin) { + return f.call(args); + } else if (f is MalClosure) { + ast = f.ast; + env = new Env(f.env, f.params, args); + continue; + } else { + throw 'bad!'; + } + } + } + } +} + +String PRINT(MalType x) => printer.pr_str(x); + +String rep(String x) { + var parsed; + try { + parsed = READ(x); + } on reader.ParseException catch (e) { + return e.message; + } + + var evaledAst; + try { + evaledAst = EVAL(parsed, replEnv); + } on NotFoundException catch (e) { + return "'${e.value}' not found"; + } + return PRINT(evaledAst); +} + +const prompt = 'user> '; +main(List args) { + setupEnv(args.isEmpty ? const [] : args.sublist(1)); + if (args.isNotEmpty) { + rep("(load-file \"${args.first}\")"); + return; + } + while (true) { + stdout.write(prompt); + var input = stdin.readLineSync(); + if (input == null) return; + var output; + try { + output = rep(input); + } on reader.NoInputException { + continue; + } + stdout.writeln(output); + } +} diff --git a/dart/step8_macros.dart b/dart/step8_macros.dart new file mode 100644 index 0000000000..558b0fd91b --- /dev/null +++ b/dart/step8_macros.dart @@ -0,0 +1,257 @@ +import 'dart:io'; + +import 'core.dart'; +import 'env.dart'; +import 'printer.dart' as printer; +import 'reader.dart' as reader; +import 'types.dart'; + +final Env replEnv = new Env(); + +void setupEnv(List argv) { + ns.forEach((sym, fun) => replEnv.set(sym, fun)); + + replEnv.set(new MalSymbol('eval'), + new MalBuiltin((List args) => EVAL(args.single, replEnv))); + + replEnv.set(new MalSymbol('*ARGV*'), + new MalList(argv.map((s) => new MalString(s)).toList())); + + rep('(def! not (fn* (a) (if a false true)))'); + rep("(def! load-file " + " (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); + rep("(defmacro! cond " + " (fn* (& xs) (if (> (count xs) 0) " + " (list 'if (first xs) " + " (if (> (count xs) 1) " + " (nth xs 1) " + " (throw \"odd number of forms to cond\")) " + " (cons 'cond (rest (rest xs)))))))"); + rep("(defmacro! or " + " (fn* (& xs) (if (empty? xs) nil " + " (if (= 1 (count xs)) " + " (first xs) " + " `(let* (or_FIXME ~(first xs)) " + " (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); +} + +/// Returns `true` if [ast] is a macro call. +/// +/// This checks that [ast] is a list whose first element is a symbol that refers +/// to a function in the current [env] that is a macro. +bool isMacroCall(MalType ast, Env env) { + if (ast is MalList) { + if (ast.isNotEmpty && ast.first is MalSymbol) { + try { + var value = env.get(ast.first); + if (value is MalCallable) { + return value.isMacro; + } + } on NotFoundException { + return false; + } + } + } + return false; +} + +MalType macroexpand(MalType ast, Env env) { + while (isMacroCall(ast, env)) { + var macroSymbol = (ast as MalList).first; + var macro = env.get(macroSymbol) as MalCallable; + ast = macro((ast as MalList).sublist(1)); + } + return ast; +} + +MalType quasiquote(MalType ast) { + bool isPair(MalType ast) { + return ast is MalIterable && ast.isNotEmpty; + } + + if (!isPair(ast)) { + return new MalList([new MalSymbol("quote"), ast]); + } else { + var list = ast as MalIterable; + if (list.first == new MalSymbol("unquote")) { + return list[1]; + } else if (isPair(list.first) && + (list.first as MalIterable).first == new MalSymbol("splice-unquote")) { + return new MalList([ + new MalSymbol("concat"), + (list.first as MalIterable)[1], + quasiquote(new MalList(list.sublist(1))) + ]); + } else { + return new MalList([ + new MalSymbol("cons"), + quasiquote(list[0]), + quasiquote(new MalList(list.sublist(1))) + ]); + } + } +} + +MalType READ(String x) => reader.read_str(x); + +MalType eval_ast(MalType ast, Env env) { + if (ast is MalSymbol) { + return env.get(ast); + } else if (ast is MalList) { + return new MalList(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalVector) { + return new MalVector(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalHashMap) { + var newMap = new Map.from(ast.value); + for (var key in newMap.keys) { + newMap[key] = EVAL(newMap[key], env); + } + return new MalHashMap(newMap); + } else { + return ast; + } +} + +MalType EVAL(MalType ast, Env env) { + while (true) { + if (ast is! MalList) { + return eval_ast(ast, env); + } else { + if ((ast as MalList).elements.isEmpty) { + return ast; + } else { + ast = macroexpand(ast, env); + if (ast is! MalList) return eval_ast(ast, env); + if ((ast as MalList).isEmpty) return ast; + + var list = ast as MalList; + + if (list.elements.first is MalSymbol) { + var symbol = list.elements.first as MalSymbol; + var args = list.elements.sublist(1); + if (symbol.value == "def!") { + MalSymbol key = args.first; + MalType value = EVAL(args[1], env); + env.set(key, value); + return value; + } else if (symbol.value == "defmacro!") { + MalSymbol key = args.first; + MalClosure macro = EVAL(args[1], env) as MalClosure; + macro.isMacro = true; + env.set(key, macro); + return macro; + } else if (symbol.value == "let*") { + // TODO(het): If elements.length is not even, give helpful error + Iterable> pairs(List elements) sync* { + for (var i = 0; i < elements.length; i += 2) { + yield [elements[i], elements[i + 1]]; + } + } + + var newEnv = new Env(env); + MalIterable bindings = args.first; + for (var pair in pairs(bindings.elements)) { + MalSymbol key = pair[0]; + MalType value = EVAL(pair[1], newEnv); + newEnv.set(key, value); + } + ast = args[1]; + env = newEnv; + continue; + } else if (symbol.value == "do") { + eval_ast(new MalList(args.sublist(0, args.length - 1)), env); + ast = args.last; + continue; + } else if (symbol.value == "if") { + var condition = EVAL(args[0], env); + if (condition is MalNil || + condition is MalBool && condition.value == false) { + // False side of branch + if (args.length < 3) { + return new MalNil(); + } + ast = args[2]; + continue; + } else { + // True side of branch + ast = args[1]; + continue; + } + } else if (symbol.value == "fn*") { + var params = (args[0] as MalIterable) + .elements + .map((e) => e as MalSymbol) + .toList(); + return new MalClosure( + params, + args[1], + env, + (List funcArgs) => + EVAL(args[1], new Env(env, params, funcArgs))); + } else if (symbol.value == "quote") { + return args.single; + } else if (symbol.value == "quasiquote") { + ast = quasiquote(args.first); + continue; + } else if (symbol.value == 'macroexpand') { + return macroexpand(args.first, env); + } + } + var newAst = eval_ast(ast, env) as MalList; + var f = newAst.elements.first; + var args = newAst.elements.sublist(1); + if (f is MalBuiltin) { + return f.call(args); + } else if (f is MalClosure) { + ast = f.ast; + env = new Env(f.env, f.params, args); + continue; + } else { + throw 'bad!'; + } + } + } + } +} + +String PRINT(MalType x) => printer.pr_str(x); + +String rep(String x) { + var parsed; + try { + parsed = READ(x); + } on reader.ParseException catch (e) { + return e.message; + } + + var evaledAst; + try { + evaledAst = EVAL(parsed, replEnv); + } on NotFoundException catch (e) { + return "'${e.value}' not found"; + } on MalNativeException catch (e) { + return "${e.error}"; + } + return PRINT(evaledAst); +} + +const prompt = 'user> '; +main(List args) { + setupEnv(args.isEmpty ? const [] : args.sublist(1)); + if (args.isNotEmpty) { + rep("(load-file \"${args.first}\")"); + return; + } + while (true) { + stdout.write(prompt); + var input = stdin.readLineSync(); + if (input == null) return; + var output; + try { + output = rep(input); + } on reader.NoInputException { + continue; + } + stdout.writeln(output); + } +} diff --git a/dart/step9_try.dart b/dart/step9_try.dart new file mode 100644 index 0000000000..aa8fece5e6 --- /dev/null +++ b/dart/step9_try.dart @@ -0,0 +1,277 @@ +import 'dart:io'; + +import 'core.dart'; +import 'env.dart'; +import 'printer.dart' as printer; +import 'reader.dart' as reader; +import 'types.dart'; + +final Env replEnv = new Env(); + +void setupEnv(List argv) { + ns.forEach((sym, fun) => replEnv.set(sym, fun)); + + replEnv.set(new MalSymbol('eval'), + new MalBuiltin((List args) => EVAL(args.single, replEnv))); + + replEnv.set(new MalSymbol('*ARGV*'), + new MalList(argv.map((s) => new MalString(s)).toList())); + + rep('(def! not (fn* (a) (if a false true)))'); + rep("(def! load-file " + " (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); + rep("(defmacro! cond " + " (fn* (& xs) (if (> (count xs) 0) " + " (list 'if (first xs) " + " (if (> (count xs) 1) " + " (nth xs 1) " + " (throw \"odd number of forms to cond\")) " + " (cons 'cond (rest (rest xs)))))))"); + rep("(defmacro! or " + " (fn* (& xs) (if (empty? xs) nil " + " (if (= 1 (count xs)) " + " (first xs) " + " `(let* (or_FIXME ~(first xs)) " + " (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); +} + +/// Returns `true` if [ast] is a macro call. +/// +/// This checks that [ast] is a list whose first element is a symbol that refers +/// to a function in the current [env] that is a macro. +bool isMacroCall(MalType ast, Env env) { + if (ast is MalList) { + if (ast.isNotEmpty && ast.first is MalSymbol) { + try { + var value = env.get(ast.first); + if (value is MalCallable) { + return value.isMacro; + } + } on NotFoundException { + return false; + } + } + } + return false; +} + +MalType macroexpand(MalType ast, Env env) { + while (isMacroCall(ast, env)) { + var macroSymbol = (ast as MalList).first; + var macro = env.get(macroSymbol) as MalCallable; + ast = macro((ast as MalList).sublist(1)); + } + return ast; +} + +MalType quasiquote(MalType ast) { + bool isPair(MalType ast) { + return ast is MalIterable && ast.isNotEmpty; + } + + if (!isPair(ast)) { + return new MalList([new MalSymbol("quote"), ast]); + } else { + var list = ast as MalIterable; + if (list.first == new MalSymbol("unquote")) { + return list[1]; + } else if (isPair(list.first) && + (list.first as MalIterable).first == new MalSymbol("splice-unquote")) { + return new MalList([ + new MalSymbol("concat"), + (list.first as MalIterable)[1], + quasiquote(new MalList(list.sublist(1))) + ]); + } else { + return new MalList([ + new MalSymbol("cons"), + quasiquote(list[0]), + quasiquote(new MalList(list.sublist(1))) + ]); + } + } +} + +MalType READ(String x) => reader.read_str(x); + +MalType eval_ast(MalType ast, Env env) { + if (ast is MalSymbol) { + return env.get(ast); + } else if (ast is MalList) { + return new MalList(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalVector) { + return new MalVector(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalHashMap) { + var newMap = new Map.from(ast.value); + for (var key in newMap.keys) { + newMap[key] = EVAL(newMap[key], env); + } + return new MalHashMap(newMap); + } else { + return ast; + } +} + +MalType EVAL(MalType ast, Env env) { + while (true) { + if (ast is! MalList) { + return eval_ast(ast, env); + } else { + if ((ast as MalList).elements.isEmpty) { + return ast; + } else { + ast = macroexpand(ast, env); + if (ast is! MalList) return eval_ast(ast, env); + if ((ast as MalList).isEmpty) return ast; + + var list = ast as MalList; + + if (list.elements.first is MalSymbol) { + var symbol = list.elements.first as MalSymbol; + var args = list.elements.sublist(1); + if (symbol.value == "def!") { + MalSymbol key = args.first; + MalType value = EVAL(args[1], env); + env.set(key, value); + return value; + } else if (symbol.value == "defmacro!") { + MalSymbol key = args.first; + MalClosure macro = EVAL(args[1], env) as MalClosure; + macro.isMacro = true; + env.set(key, macro); + return macro; + } else if (symbol.value == "let*") { + // TODO(het): If elements.length is not even, give helpful error + Iterable> pairs(List elements) sync* { + for (var i = 0; i < elements.length; i += 2) { + yield [elements[i], elements[i + 1]]; + } + } + + var newEnv = new Env(env); + MalIterable bindings = args.first; + for (var pair in pairs(bindings.elements)) { + MalSymbol key = pair[0]; + MalType value = EVAL(pair[1], newEnv); + newEnv.set(key, value); + } + ast = args[1]; + env = newEnv; + continue; + } else if (symbol.value == "do") { + eval_ast(new MalList(args.sublist(0, args.length - 1)), env); + ast = args.last; + continue; + } else if (symbol.value == "if") { + var condition = EVAL(args[0], env); + if (condition is MalNil || + condition is MalBool && condition.value == false) { + // False side of branch + if (args.length < 3) { + return new MalNil(); + } + ast = args[2]; + continue; + } else { + // True side of branch + ast = args[1]; + continue; + } + } else if (symbol.value == "fn*") { + var params = (args[0] as MalIterable) + .elements + .map((e) => e as MalSymbol) + .toList(); + return new MalClosure( + params, + args[1], + env, + (List funcArgs) => + EVAL(args[1], new Env(env, params, funcArgs))); + } else if (symbol.value == "quote") { + return args.single; + } else if (symbol.value == "quasiquote") { + ast = quasiquote(args.first); + continue; + } else if (symbol.value == 'macroexpand') { + ast = macroexpand(args.first, env); + continue; + } else if (symbol.value == 'try*') { + var body = args.first; + var catchClause = args[1] as MalList; + try { + ast = EVAL(body, env); + } catch (e) { + assert((catchClause.first as MalSymbol).value == 'catch*'); + var exceptionSymbol = catchClause[1] as MalSymbol; + var catchBody = catchClause[2]; + MalType exceptionValue; + if (e is MalException) { + exceptionValue = e.value; + } else { + exceptionValue = new MalString(e.toString()); + } + var newEnv = new Env(env, [exceptionSymbol], [exceptionValue]); + ast = EVAL(catchBody, newEnv); + } + continue; + } + } + var newAst = eval_ast(ast, env) as MalList; + var f = newAst.elements.first; + var args = newAst.elements.sublist(1); + if (f is MalBuiltin) { + return f.call(args); + } else if (f is MalClosure) { + ast = f.ast; + env = new Env(f.env, f.params, args); + continue; + } else { + throw 'bad!'; + } + } + } + } +} + +String PRINT(MalType x) => printer.pr_str(x); + +String rep(String x) { + var parsed; + try { + parsed = READ(x); + } on reader.ParseException catch (e) { + return e.message; + } + + var evaledAst; + try { + evaledAst = EVAL(parsed, replEnv); + } on NotFoundException catch (e) { + return "'${e.value}' not found"; + } on MalNativeException catch (e) { + return "${e.error}"; + } + return PRINT(evaledAst); +} + +const prompt = 'user> '; +main(List args) { + setupEnv(args.isEmpty ? const [] : args.sublist(1)); + if (args.isNotEmpty) { + rep("(load-file \"${args.first}\")"); + return; + } + while (true) { + stdout.write(prompt); + var input = stdin.readLineSync(); + if (input == null) return; + var output; + try { + output = rep(input); + } on reader.NoInputException { + continue; + } + stdout.writeln(output); + } +} diff --git a/dart/stepA_mal.dart b/dart/stepA_mal.dart new file mode 100644 index 0000000000..f27ec05b36 --- /dev/null +++ b/dart/stepA_mal.dart @@ -0,0 +1,288 @@ +import 'dart:io'; + +import 'core.dart'; +import 'env.dart'; +import 'printer.dart' as printer; +import 'reader.dart' as reader; +import 'types.dart'; + +final Env replEnv = new Env(); + +void setupEnv(List argv) { + ns.forEach((sym, fun) => replEnv.set(sym, fun)); + + replEnv.set(new MalSymbol('eval'), + new MalBuiltin((List args) => EVAL(args.single, replEnv))); + + replEnv.set(new MalSymbol('*ARGV*'), + new MalList(argv.map((s) => new MalString(s)).toList())); + + replEnv.set(new MalSymbol('*host-language*'), new MalString('dart')); + + rep('(def! not (fn* (a) (if a false true)))'); + rep("(def! load-file " + " (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); + rep("(defmacro! cond " + " (fn* (& xs) (if (> (count xs) 0) " + " (list 'if (first xs) " + " (if (> (count xs) 1) " + " (nth xs 1) " + " (throw \"odd number of forms to cond\")) " + " (cons 'cond (rest (rest xs)))))))"); + rep("(def! *gensym-counter* (atom 0))"); + rep("(def! gensym " + " (fn* [] " + " (symbol (str \"G__\" (swap! *gensym-counter* " + " (fn* [x] (+ 1 x)))))))"); + rep("(defmacro! or " + " (fn* (& xs) " + " (if (empty? xs) " + " nil " + " (if (= 1 (count xs)) " + " (first xs) " + " (let* (condvar (gensym)) " + " `(let* (~condvar ~(first xs)) " + " (if ~condvar ~condvar (or ~@(rest xs)))))))))"); +} + +/// Returns `true` if [ast] is a macro call. +/// +/// This checks that [ast] is a list whose first element is a symbol that refers +/// to a function in the current [env] that is a macro. +bool isMacroCall(MalType ast, Env env) { + if (ast is MalList) { + if (ast.isNotEmpty && ast.first is MalSymbol) { + try { + var value = env.get(ast.first); + if (value is MalCallable) { + return value.isMacro; + } + } on NotFoundException { + return false; + } + } + } + return false; +} + +MalType macroexpand(MalType ast, Env env) { + while (isMacroCall(ast, env)) { + var macroSymbol = (ast as MalList).first; + var macro = env.get(macroSymbol) as MalCallable; + ast = macro((ast as MalList).sublist(1)); + } + return ast; +} + +MalType quasiquote(MalType ast) { + bool isPair(MalType ast) { + return ast is MalIterable && ast.isNotEmpty; + } + + if (!isPair(ast)) { + return new MalList([new MalSymbol("quote"), ast]); + } else { + var list = ast as MalIterable; + if (list.first == new MalSymbol("unquote")) { + return list[1]; + } else if (isPair(list.first) && + (list.first as MalIterable).first == new MalSymbol("splice-unquote")) { + return new MalList([ + new MalSymbol("concat"), + (list.first as MalIterable)[1], + quasiquote(new MalList(list.sublist(1))) + ]); + } else { + return new MalList([ + new MalSymbol("cons"), + quasiquote(list[0]), + quasiquote(new MalList(list.sublist(1))) + ]); + } + } +} + +MalType READ(String x) => reader.read_str(x); + +MalType eval_ast(MalType ast, Env env) { + if (ast is MalSymbol) { + return env.get(ast); + } else if (ast is MalList) { + return new MalList(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalVector) { + return new MalVector(ast.elements.map((x) => EVAL(x, env)).toList()); + } else if (ast is MalHashMap) { + var newMap = new Map.from(ast.value); + for (var key in newMap.keys) { + newMap[key] = EVAL(newMap[key], env); + } + return new MalHashMap(newMap); + } else { + return ast; + } +} + +MalType EVAL(MalType ast, Env env) { + while (true) { + if (ast is! MalList) { + return eval_ast(ast, env); + } else { + if ((ast as MalList).elements.isEmpty) { + return ast; + } else { + ast = macroexpand(ast, env); + if (ast is! MalList) return eval_ast(ast, env); + if ((ast as MalList).isEmpty) return ast; + + var list = ast as MalList; + + if (list.elements.first is MalSymbol) { + var symbol = list.elements.first as MalSymbol; + var args = list.elements.sublist(1); + if (symbol.value == "def!") { + MalSymbol key = args.first; + MalType value = EVAL(args[1], env); + env.set(key, value); + return value; + } else if (symbol.value == "defmacro!") { + MalSymbol key = args.first; + MalClosure macro = EVAL(args[1], env) as MalClosure; + macro.isMacro = true; + env.set(key, macro); + return macro; + } else if (symbol.value == "let*") { + // TODO(het): If elements.length is not even, give helpful error + Iterable> pairs(List elements) sync* { + for (var i = 0; i < elements.length; i += 2) { + yield [elements[i], elements[i + 1]]; + } + } + + var newEnv = new Env(env); + MalIterable bindings = args.first; + for (var pair in pairs(bindings.elements)) { + MalSymbol key = pair[0]; + MalType value = EVAL(pair[1], newEnv); + newEnv.set(key, value); + } + ast = args[1]; + env = newEnv; + continue; + } else if (symbol.value == "do") { + eval_ast(new MalList(args.sublist(0, args.length - 1)), env); + ast = args.last; + continue; + } else if (symbol.value == "if") { + var condition = EVAL(args[0], env); + if (condition is MalNil || + condition is MalBool && condition.value == false) { + // False side of branch + if (args.length < 3) { + return new MalNil(); + } + ast = args[2]; + continue; + } else { + // True side of branch + ast = args[1]; + continue; + } + } else if (symbol.value == "fn*") { + var params = (args[0] as MalIterable) + .elements + .map((e) => e as MalSymbol) + .toList(); + return new MalClosure( + params, + args[1], + env, + (List funcArgs) => + EVAL(args[1], new Env(env, params, funcArgs))); + } else if (symbol.value == "quote") { + return args.single; + } else if (symbol.value == "quasiquote") { + ast = quasiquote(args.first); + continue; + } else if (symbol.value == 'macroexpand') { + ast = macroexpand(args.first, env); + continue; + } else if (symbol.value == 'try*') { + var body = args.first; + var catchClause = args[1] as MalList; + try { + ast = EVAL(body, env); + } catch (e) { + assert((catchClause.first as MalSymbol).value == 'catch*'); + var exceptionSymbol = catchClause[1] as MalSymbol; + var catchBody = catchClause[2]; + MalType exceptionValue; + if (e is MalException) { + exceptionValue = e.value; + } else { + exceptionValue = new MalString(e.toString()); + } + var newEnv = new Env(env, [exceptionSymbol], [exceptionValue]); + ast = EVAL(catchBody, newEnv); + } + continue; + } + } + var newAst = eval_ast(ast, env) as MalList; + var f = newAst.elements.first; + var args = newAst.elements.sublist(1); + if (f is MalBuiltin) { + return f.call(args); + } else if (f is MalClosure) { + ast = f.ast; + env = new Env(f.env, f.params, args); + continue; + } else { + throw 'bad!'; + } + } + } + } +} + +String PRINT(MalType x) => printer.pr_str(x); + +String rep(String x) { + var parsed; + try { + parsed = READ(x); + } on reader.ParseException catch (e) { + return e.message; + } + + var evaledAst; + try { + evaledAst = EVAL(parsed, replEnv); + } on NotFoundException catch (e) { + return "'${e.value}' not found"; + } on MalNativeException catch (e) { + return "${e.error}"; + } + return PRINT(evaledAst); +} + +const prompt = 'user> '; +main(List args) { + setupEnv(args.isEmpty ? const [] : args.sublist(1)); + if (args.isNotEmpty) { + rep("(load-file \"${args.first}\")"); + return; + } + rep("(println (str \"Mal [\" *host-language* \"]\"))"); + while (true) { + stdout.write(prompt); + var input = stdin.readLineSync(); + if (input == null) return; + var output; + try { + output = rep(input); + } on reader.NoInputException { + continue; + } + stdout.writeln(output); + } +} diff --git a/dart/types.dart b/dart/types.dart new file mode 100644 index 0000000000..98022b9755 --- /dev/null +++ b/dart/types.dart @@ -0,0 +1,278 @@ +import 'dart:collection'; +import 'env.dart'; + +abstract class MalType { + bool get isMacro => false; + MalType meta; + + MalType clone(); +} + +abstract class MalIterable extends MalType + with ListMixin + implements List { + final List elements; + + MalIterable(this.elements); + + MalType operator [](int index) => elements[index]; + void operator []=(int index, MalType value) { + elements[index] = value; + } + + int get length => elements.length; + void set length(int newLength) { + elements.length = newLength; + } + + bool operator ==(other) { + if (other is! MalIterable) return false; + + // apparently (= (list) nil) should be false... + if (other is MalNil) return false; + + if (elements.length != other.elements.length) return false; + for (var i = 0; i < elements.length; i++) { + if (elements[i] != other.elements[i]) return false; + } + return true; + } + + @override + MalIterable clone(); +} + +class MalList extends MalIterable { + MalList(List elements) : super(elements); + + @override + MalList clone() { + return new MalList(elements.toList()); + } +} + +class MalVector extends MalIterable { + MalVector(List elements) : super(elements); + + @override + MalVector clone() { + return new MalVector(elements.toList()); + } +} + +class MalHashMap extends MalType { + final Map value; + + MalHashMap(this.value); + + MalHashMap.fromSequence(List elements) + : value = _mapFromSequence(elements); + + static Map _mapFromSequence(List elements) { + var result = {}; + + var readingKey = true; + MalType pendingKey; + for (var malType in elements) { + if (readingKey) { + if (malType is MalString || malType is MalKeyword) { + pendingKey = malType; + } else { + throw new ArgumentError('hash-map keys must be strings or keywords'); + } + } else { + result[pendingKey] = malType; + } + readingKey = !readingKey; + } + + return result; + } + + bool operator ==(other) { + if (other is! MalHashMap) return false; + var otherMap = (other as MalHashMap).value; + if (otherMap.length != value.length) return false; + for (var key in value.keys) { + if (!otherMap.containsKey(key)) return false; + if (value[key] != otherMap[key]) return false; + } + return true; + } + + @override + MalHashMap clone() { + return new MalHashMap(new Map.from(value)); + } +} + +class MalInt extends MalType { + final int value; + + MalInt(this.value); + + bool operator ==(other) { + if (other is! MalInt) return false; + return other.value == value; + } + + @override + MalInt clone() { + return new MalInt(value); + } +} + +class MalSymbol extends MalType { + final String value; + + MalSymbol(this.value); + + int get hashCode => value.hashCode; + + bool operator ==(other) { + if (other is! MalSymbol) return false; + return value == other.value; + } + + @override + MalSymbol clone() { + return new MalSymbol(value); + } +} + +class MalKeyword extends MalType { + final String value; + + MalKeyword(this.value); + + int get hashCode => value.hashCode; + + bool operator ==(other) { + if (other is! MalKeyword) return false; + return value == other.value; + } + + @override + MalKeyword clone() { + return new MalKeyword(value); + } +} + +class MalString extends MalType { + final String value; + + MalString(this.value); + + int get hashCode => value.hashCode; + + bool operator ==(other) { + if (other is! MalString) return false; + return other.value == value; + } + + @override + MalString clone() { + return new MalString(value); + } +} + +class MalBool extends MalType { + final bool value; + + MalBool(this.value); + + bool operator ==(other) { + if (other is! MalBool) return false; + return other.value == value; + } + + @override + MalBool clone() { + return new MalBool(value); + } +} + +class MalNil extends MalIterable { + MalNil() : super(const []); + + bool operator ==(other) => other is MalNil; + + @override + MalNil clone() { + return new MalNil(); + } +} + +class MalAtom extends MalType { + MalType value; + + MalAtom(this.value); + + @override + MalAtom clone() { + return new MalAtom(value); + } +} + +abstract class MalCallable extends MalType { + MalType call(List args); + + bool get isMacro => false; +} + +typedef MalType BuiltinFunc(List args); + +class MalBuiltin extends MalCallable { + final BuiltinFunc func; + + MalBuiltin(this.func); + + MalType call(List args) { + return func(args); + } + + @override + MalBuiltin clone() { + return new MalBuiltin(func); + } +} + +typedef MalType EvalFun(MalType ast, Env env); + +class MalClosure extends MalCallable { + final List params; + final MalType ast; + final Env env; + final Function func; + + @override + bool isMacro = false; + + MalClosure(this.params, this.ast, this.env, this.func); + + MalType call(List args) { + return func(args); + } + + @override + MalClosure clone() { + var closure = + new MalClosure(this.params.toList(), this.ast, this.env, this.func); + closure.isMacro = this.isMacro; + return closure; + } +} + +class MalNativeException implements Exception { + final Error error; + + MalNativeException(this.error); + + String toString() => error.toString(); +} + +class MalException implements Exception { + final MalType value; + + MalException(this.value); +} From 46e89434f58ae5a647fe88055ccfb35c67c3baf9 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Sun, 13 Nov 2016 14:27:03 -0800 Subject: [PATCH 0230/2308] fix makefile --- Makefile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 54560b5901..27e0bb05a7 100644 --- a/Makefile +++ b/Makefile @@ -77,11 +77,12 @@ DOCKERIZE = # Settings # -IMPLS = ada awk bash c d chuck clojure coffee cpp crystal cs dart erlang elisp \ - elixir es6 factor forth fsharp go groovy guile haskell haxe \ - io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ - nim objc objpascal perl perl6 php plpgsql plsql ps python r \ - racket rpython ruby rust scala swift swift3 tcl vb vhdl vimscript +IMPLS = ada awk bash basic c d chuck clojure coffee clisp cpp crystal cs dart \ + erlang elisp elixir es6 factor forth fsharp go groovy guile haskell \ + haxe io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ + nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ + python r racket rpython ruby rust scala swift swift3 tcl vb vhdl \ + vimscript step0 = step0_repl step1 = step1_read_print From 4ceefe3b50373167db47ed90673aebeacca87c8c Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Sun, 13 Nov 2016 14:28:19 -0800 Subject: [PATCH 0231/2308] use tabs for Makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 27e0bb05a7..eb810be468 100644 --- a/Makefile +++ b/Makefile @@ -78,8 +78,8 @@ DOCKERIZE = # IMPLS = ada awk bash basic c d chuck clojure coffee clisp cpp crystal cs dart \ - erlang elisp elixir es6 factor forth fsharp go groovy guile haskell \ - haxe io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ + erlang elisp elixir es6 factor forth fsharp go groovy guile haskell \ + haxe io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ python r racket rpython ruby rust scala swift swift3 tcl vb vhdl \ vimscript From bcf95720f24710661e37897947ee672ccbee9510 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Sun, 13 Nov 2016 14:35:35 -0800 Subject: [PATCH 0232/2308] update readme --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 038f8dc6df..812f96bc93 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Mal is implemented in 61 languages: * CoffeeScript * Crystal * D +* Dart * Elixir * Emacs Lisp * Erlang @@ -293,6 +294,17 @@ make ./stepX_YYY ``` +### Dart + +*The Dart implementation was created by [Harry Terkelsen](https://github.com/hterkelsen)* + +The Dart implementation has been tested with Dart 1.20. + +``` +cd dart +dart ./stepX_YYY +``` + ### Emacs Lisp *The Emacs Lisp implementation was created by [Vasilij Schneidermann](https://github.com/wasamasa)* From b15933d27320f88ee6d77f2616ca2366f573a1a4 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Sun, 13 Nov 2016 14:44:11 -0800 Subject: [PATCH 0233/2308] update number of languages Mal is implemented in --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 812f96bc93..a73cf21763 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 61 languages: +Mal is implemented in 62 languages: * Ada * GNU awk From 04d23e07d91f11b50ff79a19fca2c903e9f3521f Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Sun, 13 Nov 2016 15:24:02 -0800 Subject: [PATCH 0234/2308] add dart Dockerfile --- dart/Dockerfile | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 dart/Dockerfile diff --git a/dart/Dockerfile b/dart/Dockerfile new file mode 100644 index 0000000000..0b3602f377 --- /dev/null +++ b/dart/Dockerfile @@ -0,0 +1,29 @@ +FROM ubuntu:vivid +MAINTAINER Harry Terkelsen + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +RUN apt-get -y install apt-transport-https +RUN curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - +RUN curl https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list +RUN apt-get -y update + +RUN apt-get -y install dart From 7cdce42011f0d588efe2e9c23215e83ce1046ecd Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Sun, 13 Nov 2016 15:24:48 -0800 Subject: [PATCH 0235/2308] update travis file --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 4bd5cc8224..97ee8216bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ matrix: - {env: IMPL=clojure, services: [docker]} - {env: IMPL=crystal, services: [docker]} - {env: IMPL=d, services: [docker]} + - {env: IMPL=dart, services: [docker]} - {env: IMPL=elisp, services: [docker]} - {env: IMPL=elixir, services: [docker]} - {env: IMPL=erlang NO_PERF=1, services: [docker]} # perf runs out of memory From eb8bc504fa47e81d597399e8435e0f0e02f85728 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Mon, 14 Nov 2016 13:28:23 -0800 Subject: [PATCH 0236/2308] add dart/Makefile stub --- dart/Makefile | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 dart/Makefile diff --git a/dart/Makefile b/dart/Makefile new file mode 100644 index 0000000000..25bd0e6d6f --- /dev/null +++ b/dart/Makefile @@ -0,0 +1,16 @@ +all: + @true + +SOURCES_BASE = types.dart reader.dart printer.dart +SOURCES_LISP = env.dart core.dart stepA_mal.dart +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +.PHONY: stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" + From 9d59cdb3849ae1c8832b3ff502ed2b36e6a68099 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 15 Nov 2016 22:38:09 -0600 Subject: [PATCH 0237/2308] Basic: refactor of hashmaps, map loops, remove derefs. - Alternate memory layout of hash-maps: Instead of hash-maps being an alternating sequence of keys and values, combine the key/values into a single entry. In other words, switch from this: 8 -> next Z% index (0 for last) 14 key/value (alternating) To this: 8 -> next Z% index (0 for last) key value This requires refactoring of the sequence reader, EVAL_AST and especially DO_HASHMAP and DO_KEY_VALS. So that leads to the next big refactoring: - Change mapping/lapping constructs to share code: Several pieces of mapping/looping code have a common structure, so this moves that common structure to types.in.bas: MAP_LOOP_START, MAP_LOOP_UPDATE, MAP_LOOP_DONE. Then use this code in: - EVAL_AST - READ_SEQ_* - DO_MAP - DO_HASH_MAP - DO_KEYS_VALS This also fixes the issue that several of these looping constructs were creating new empty sequence entries instead of using the common ones at the beginning of memory. - Remove the use of DEREF_*. This isn't actually needed because we no longer create structure that refer to multiple levels of type 14 references. Replace DEREF_* with VAL_* which gets the value of a particular sequence element i.e. Z%(A+1,1). All together, the above changes save over 300 bytes. Also: - Fix empty nil/false/true entries so they are treated the same as other types of data with regards to reference counting and ALLOC/RELEASE. - Add a new memory summary function in debug.in.bas that just prints out free, value count, and references for the early scalar and empty list elements. Comment out the larger one. This saves about 90 bytes. --- basic/Makefile | 2 +- basic/core.in.bas | 305 +++++++++++++++++----------------- basic/debug.in.bas | 96 +++++++---- basic/env.in.bas | 16 +- basic/printer.in.bas | 9 +- basic/reader.in.bas | 179 +++++++++----------- basic/step0_repl.in.bas | 4 +- basic/step1_read_print.in.bas | 5 +- basic/step2_eval.in.bas | 103 ++++-------- basic/step3_env.in.bas | 127 +++++--------- basic/step4_if_fn_do.in.bas | 124 +++++--------- basic/step5_tco.in.bas | 134 ++++++--------- basic/step6_file.in.bas | 134 ++++++--------- basic/step7_quote.in.bas | 148 +++++++---------- basic/step8_macros.in.bas | 154 +++++++---------- basic/step9_try.in.bas | 154 +++++++---------- basic/stepA_mal.in.bas | 143 +++++++--------- basic/types.in.bas | 176 ++++++++++++-------- basic/variables.txt | 4 +- 19 files changed, 887 insertions(+), 1130 deletions(-) diff --git a/basic/Makefile b/basic/Makefile index 6d0a3c00dc..47fa8c174f 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -23,7 +23,7 @@ tests/%.bas: tests/%.in.bas # CBM/C64 image rules -step%.prg: step%.bas +%.prg: %.bas cat $< | tr "A-Z" "a-z" > $<.tmp #cat $< | sed 's/["]\@["]\@!/\L&/g' > $<.tmp petcat -w2 -nc -o $@ $<.tmp diff --git a/basic/core.in.bas b/basic/core.in.bas index 3f4a2e1d51..596e3a8656 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -40,8 +40,8 @@ SUB DO_TCO_FUNCTION G=Z%(F,1) REM Get argument values - R=AR+1:GOSUB DEREF_R:A=R - R=Z%(AR,1)+1:GOSUB DEREF_R:B=R + A=AR:GOSUB VAL_A + B=Z%(AR,1):GOSUB VAL_B ON G-60 GOTO DO_APPLY,DO_MAP,DO_SWAP_BANG @@ -59,6 +59,8 @@ SUB DO_TCO_FUNCTION REM prepend intermediate args to final args element A=AR:B=0:C=C-1:GOSUB SLICE REM release the terminator of new list (we skip over it) + REM we already checked for an empty list above, so R6 is pointer + REM a real non-empty list AY=Z%(R6,1):GOSUB RELEASE REM attach end of slice to final args element Z%(R6,1)=Z%(A+1,1) @@ -84,70 +86,45 @@ SUB DO_TCO_FUNCTION DO_MAP: F=A - REM first result list element - T=6:L=0:N=0:GOSUB ALLOC - - REM push future return val, prior entry, F and B - GOSUB PUSH_R - Q=0:GOSUB PUSH_Q - Q=F:GOSUB PUSH_Q - Q=B:GOSUB PUSH_Q + REM setup the stack for the loop + T=6:GOSUB MAP_LOOP_START DO_MAP_LOOP: - REM set previous to current if not the first element - GOSUB PEEK_Q_2 - IF Q<>0 THEN Z%(Q,1)=R - REM update previous reference to current - Q=R:GOSUB PUT_Q_2 - IF Z%(B,1)=0 THEN GOTO DO_MAP_DONE - REM create argument list for apply call - Z%(3,0)=Z%(3,0)+32 - REM inc ref cnt of referred argument + REM create argument list for apply T=6:L=3:N=Z%(B+1,1):GOSUB ALLOC - REM push argument list - GOSUB PUSH_R + GOSUB PUSH_R: REM push argument list + Q=F:GOSUB PUSH_Q: REM push F + Q=B:GOSUB PUSH_Q: REM push B AR=R:CALL APPLY - REM pop apply args and release them - GOSUB POP_Q:AY=Q - GOSUB RELEASE - - REM set the result value - GOSUB PEEK_Q_2 - Z%(Q+1,1)=R + GOSUB POP_Q:B=Q: REM pop B + GOSUB POP_Q:F=Q: REM pop F + GOSUB POP_Q: REM pop apply args and release them + AY=Q:GOSUB RELEASE - IF ER<>-2 THEN GOTO DO_MAP_DONE + B=Z%(B,1): REM go to the next element - REM restore F - GOSUB PEEK_Q_1:F=Q + REM if error, release the unattached element + IF ER<>-2 THEN AY=R:GOSUB RELEASE:GOTO DO_MAP_DONE - REM update B to next source element - GOSUB PEEK_Q - Q=Z%(Q,1) - B=Q - GOSUB PUT_Q + REM main value is result of apply + N=R - REM allocate next element - T=6:L=0:N=0:GOSUB ALLOC + REM update the return sequence structure + REM release N since list takes full ownership + C=1:T=6:GOSUB MAP_LOOP_UPDATE GOTO DO_MAP_LOOP DO_MAP_DONE: - Q=3:GOSUB PEEK_Q_Q: REM get return val - REM if no error, set the return val - IF ER=-2 THEN R=Q - REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE - - REM pop everything off stack - GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q + REM cleanup stack and get return value + GOSUB MAP_LOOP_DONE GOTO DO_TCO_FUNCTION_DONE - DO_SWAP_BANG: F=B @@ -181,6 +158,20 @@ SUB DO_TCO_FUNCTION DO_TCO_FUNCTION_DONE: END SUB +REM RETURN_INC_REF(R) -> R +REM - return R with 1 ref cnt increase +REM - called with GOTO as a return RETURN +RETURN_INC_REF: + Z%(R,0)=Z%(R,0)+32 + RETURN + +REM RETURN_TRUE_FALSE(R) -> R +REM - take BASIC true/false R, return mal true/false R with ref cnt +REM - called with GOTO as a return RETURN +RETURN_TRUE_FALSE: + IF R THEN R=2 + IF R=0 THEN R=1 + GOTO RETURN_INC_REF REM DO_FUNCTION(F, AR) DO_FUNCTION: @@ -188,8 +179,8 @@ DO_FUNCTION: G=Z%(F,1) REM Get argument values - R=AR+1:GOSUB DEREF_R:A=R - R=Z%(AR,1)+1:GOSUB DEREF_R:B=R + A=AR:GOSUB VAL_A + B=Z%(AR,1):GOSUB VAL_B REM Switch on the function number IF G>59 THEN ER=-1:E$="unknown function"+STR$(G):RETURN @@ -210,50 +201,45 @@ DO_FUNCTION: DO_EQUAL_Q: GOSUB EQUAL_Q - R=R+1 - RETURN + GOTO RETURN_TRUE_FALSE DO_THROW: ER=A Z%(ER,0)=Z%(ER,0)+32 - R=0 + R=-1 RETURN DO_NIL_Q: - R=1 - IF A=0 THEN R=2 - RETURN + R=A=0 + GOTO RETURN_TRUE_FALSE DO_TRUE_Q: - R=1 - IF A=2 THEN R=2 - RETURN + R=A=2 + GOTO RETURN_TRUE_FALSE DO_FALSE_Q: - R=1 - IF A=1 THEN R=2 - RETURN + R=A=1 + GOTO RETURN_TRUE_FALSE DO_STRING_Q: + R=0 + IF (Z%(A,0)AND 31)<>4 THEN GOTO RETURN_TRUE_FALSE + IF MID$(S$(Z%(A,1)),1,1)=CHR$(127) THEN GOTO RETURN_TRUE_FALSE R=1 - IF (Z%(A,0)AND 31)<>4 THEN RETURN - IF MID$(S$(Z%(A,1)),1,1)=CHR$(127) THEN RETURN - R=2 - RETURN + GOTO RETURN_TRUE_FALSE DO_SYMBOL: B$=S$(Z%(A,1)) T=5:GOSUB STRING RETURN DO_SYMBOL_Q: - R=1 - IF (Z%(A,0)AND 31)=5 THEN R=2 - RETURN + R=(Z%(A,0)AND 31)=5 + GOTO RETURN_TRUE_FALSE DO_KEYWORD: B$=S$(Z%(A,1)) IF MID$(B$,1,1)<>CHR$(127) THEN B$=CHR$(127)+B$ T=4:GOSUB STRING RETURN DO_KEYWORD_Q: + R=0 + IF (Z%(A,0)AND 31)<>4 THEN GOTO RETURN_TRUE_FALSE + IF MID$(S$(Z%(A,1)),1,1)<>CHR$(127) THEN GOTO RETURN_TRUE_FALSE R=1 - IF (Z%(A,0)AND 31)<>4 THEN RETURN - IF MID$(S$(Z%(A,1)),1,1)<>CHR$(127) THEN RETURN - R=2 - RETURN + GOTO RETURN_TRUE_FALSE DO_PR_STR: AZ=AR:B=1:B$=" ":GOSUB PR_STR_SEQ @@ -267,19 +253,19 @@ DO_FUNCTION: AZ=AR:B=1:B$=" ":GOSUB PR_STR_SEQ PRINT R$ R=0 - RETURN + GOTO RETURN_INC_REF DO_PRINTLN: AZ=AR:B=0:B$=" ":GOSUB PR_STR_SEQ PRINT R$ R=0 - RETURN + GOTO RETURN_INC_REF DO_READ_STRING: A$=S$(Z%(A,1)) GOSUB READ_STR RETURN DO_READLINE: A$=S$(Z%(A,1)):GOSUB READLINE - IF EZ=1 THEN EZ=0:R=0:RETURN + IF EZ=1 THEN EZ=0:R=0:GOTO RETURN_INC_REF B$=R$:T=4:GOSUB STRING RETURN DO_SLURP: @@ -304,21 +290,17 @@ DO_FUNCTION: RETURN DO_LT: - R=1 - IF Z%(A,1)Z%(B,1) THEN R=2 - RETURN + R=Z%(A,1)>Z%(B,1) + GOTO RETURN_TRUE_FALSE DO_GTE: - R=1 - IF Z%(A,1)>=Z%(B,1) THEN R=2 - RETURN + R=Z%(A,1)>=Z%(B,1) + GOTO RETURN_TRUE_FALSE DO_ADD: T=2:L=Z%(A,1)+Z%(B,1):GOSUB ALLOC @@ -338,86 +320,100 @@ DO_FUNCTION: DO_LIST: R=AR - Z%(R,0)=Z%(R,0)+32 - RETURN + GOTO RETURN_INC_REF DO_LIST_Q: GOSUB LIST_Q - R=R+1: REM map to mal false/true - RETURN + GOTO RETURN_TRUE_FALSE DO_VECTOR: A=AR:T=7:GOSUB FORCE_SEQ_TYPE RETURN DO_VECTOR_Q: - R=1 - IF (Z%(A,0)AND 31)=7 THEN R=2 - RETURN + R=(Z%(A,0)AND 31)=7 + GOTO RETURN_TRUE_FALSE DO_HASH_MAP: - A=AR:T=8:GOSUB FORCE_SEQ_TYPE - RETURN + REM setup the stack for the loop + T=8:GOSUB MAP_LOOP_START + + A=AR + DO_HASH_MAP_LOOP: + IF Z%(A,1)=0 THEN GOTO DO_HASH_MAP_LOOP_DONE + + M=Z%(A+1,1) + N=Z%(Z%(A,1)+1,1) + + A=Z%(Z%(A,1),1): REM skip two + + REM update the return sequence structure + REM do not release M and N since we are pulling them from the + REM arguments (and not creating them here) + C=0:GOSUB MAP_LOOP_UPDATE + + GOTO DO_HASH_MAP_LOOP + + DO_HASH_MAP_LOOP_DONE: + REM cleanup stack and get return value + GOSUB MAP_LOOP_DONE + RETURN + DO_MAP_Q: - R=1 - IF (Z%(A,0)AND 31)=8 THEN R=2 - RETURN + R=(Z%(A,0)AND 31)=8 + GOTO RETURN_TRUE_FALSE DO_ASSOC: H=A AR=Z%(AR,1) DO_ASSOC_LOOP: - R=AR+1:GOSUB DEREF_R:K=R - R=Z%(AR,1)+1:GOSUB DEREF_R:C=R + R=AR:GOSUB VAL_R:K=R + R=Z%(AR,1):GOSUB VAL_R:C=R Z%(H,0)=Z%(H,0)+32 GOSUB ASSOC1:H=R AR=Z%(Z%(AR,1),1) IF AR=0 OR Z%(AR,1)=0 THEN RETURN GOTO DO_ASSOC_LOOP DO_GET: - IF A=0 THEN R=0:RETURN + IF A=0 THEN R=0:GOTO RETURN_INC_REF H=A:K=B:GOSUB HASHMAP_GET - GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+32 - RETURN + GOTO RETURN_INC_REF DO_CONTAINS: H=A:K=B:GOSUB HASHMAP_CONTAINS - R=R+1 - RETURN + GOTO RETURN_TRUE_FALSE DO_KEYS: + T1=0 GOTO DO_KEYS_VALS DO_VALS: - A=Z%(A,1) + T1=1 DO_KEYS_VALS: - REM first result list element - T=6:L=0:N=0:GOSUB ALLOC:T2=R + REM setup the stack for the loop + T=6:GOSUB MAP_LOOP_START DO_KEYS_VALS_LOOP: - IF A=0 OR Z%(A,1)=0 THEN R=T2:RETURN + IF Z%(A,1)=0 THEN GOTO DO_KEYS_VALS_LOOP_DONE - REM copy the value - T1=Z%(A+1,1) - REM inc ref cnt of referred argument - Z%(T1,0)=Z%(T1,0)+32 - Z%(R+1,1)=T1 + IF T1=0 THEN N=Z%(A+1,0) + IF T1=1 THEN N=Z%(A+1,1) - T1=R: REM save previous - REM allocate next element - T=6:L=0:N=0:GOSUB ALLOC - REM point previous element to this one - Z%(T1,1)=R + A=Z%(A,1): REM next element - IF Z%(Z%(A,1),1)=0 THEN R=T2:RETURN - - A=Z%(Z%(A,1),1) + REM update the return sequence structure + REM do not release N since we are pulling it from the + REM hash-map (and not creating them here) + C=0:GOSUB MAP_LOOP_UPDATE GOTO DO_KEYS_VALS_LOOP + DO_KEYS_VALS_LOOP_DONE: + REM cleanup stack and get return value + GOSUB MAP_LOOP_DONE + RETURN + DO_SEQUENTIAL_Q: - R=1 - IF (Z%(A,0)AND 31)=6 OR (Z%(A,0)AND 31)=7 THEN R=2 - RETURN + R=(Z%(A,0)AND 31)=6 OR (Z%(A,0)AND 31)=7 + GOTO RETURN_TRUE_FALSE DO_CONS: T=6:L=B:N=A:GOSUB ALLOC RETURN DO_CONCAT: REM if empty arguments, return empty list - IF Z%(AR,1)=0 THEN R=3:Z%(R,0)=Z%(R,0)+32:RETURN + IF Z%(AR,1)=0 THEN R=3:GOTO RETURN_INC_REF REM single argument IF Z%(Z%(AR,1),1)<>0 THEN GOTO DO_CONCAT_MULT @@ -431,7 +427,7 @@ DO_FUNCTION: CZ=X: REM save current stack position REM push arguments onto the stack DO_CONCAT_STACK: - R=AR+1:GOSUB DEREF_R + R=AR:GOSUB VAL_R GOSUB PUSH_R: REM push sequence AR=Z%(AR,1) IF Z%(AR,1)<>0 THEN GOTO DO_CONCAT_STACK @@ -449,6 +445,8 @@ DO_FUNCTION: GOSUB POP_Q:B=Q REM release the terminator of new list (we skip over it) + REM we already checked for an empty list above, so R6 is pointer + REM a real non-empty list AY=Z%(R6,1):GOSUB RELEASE REM attach new list element before terminator (last actual REM element to the next sequence @@ -459,7 +457,7 @@ DO_FUNCTION: DO_NTH: GOSUB COUNT B=Z%(B,1) - IF R<=B THEN R=0:ER=-1:E$="nth: index out of range":RETURN + IF R<=B THEN R=-1:ER=-1:E$="nth: index out of range":RETURN DO_NTH_LOOP: IF B=0 THEN GOTO DO_NTH_DONE B=B-1 @@ -467,33 +465,30 @@ DO_FUNCTION: GOTO DO_NTH_LOOP DO_NTH_DONE: R=Z%(A+1,1) - Z%(R,0)=Z%(R,0)+32 - RETURN + GOTO RETURN_INC_REF DO_FIRST: - IF A=0 THEN R=0:RETURN - IF Z%(A,1)=0 THEN R=0 - IF Z%(A,1)<>0 THEN R=A+1:GOSUB DEREF_R - IF R<>0 THEN Z%(R,0)=Z%(R,0)+32 - RETURN + R=0 + IF A=0 THEN GOTO RETURN_INC_REF + IF Z%(A,1)<>0 THEN R=A:GOSUB VAL_R + GOTO RETURN_INC_REF DO_REST: - IF A=0 THEN R=3:Z%(R,0)=Z%(R,0)+32:RETURN - IF Z%(A,1)<>0 THEN A=Z%(A,1) + IF A=0 THEN R=3:GOTO RETURN_INC_REF + IF Z%(A,1)<>0 THEN A=Z%(A,1): REM get the next sequence element T=6:GOSUB FORCE_SEQ_TYPE RETURN DO_EMPTY_Q: - R=1 - IF Z%(A,1)=0 THEN R=2 - RETURN + R=Z%(A,1)=0 + GOTO RETURN_TRUE_FALSE DO_COUNT: GOSUB COUNT T=2:L=R:GOSUB ALLOC RETURN DO_CONJ: R=0 - RETURN + GOTO RETURN_INC_REF DO_SEQ: R=0 - RETURN + GOTO RETURN_INC_REF DO_WITH_META: T=Z%(A,0)AND 31 @@ -502,21 +497,18 @@ DO_FUNCTION: T=T+16:L=A:N=B:GOSUB ALLOC RETURN DO_META: - IF (Z%(A,0)AND 31)<16 THEN R=0:RETURN - R=Z%(A+1,1) - Z%(R,0)=Z%(R,0)+32 - RETURN + R=0 + IF (Z%(A,0)AND 31)>15 THEN R=Z%(A+1,1) + GOTO RETURN_INC_REF DO_ATOM: T=12:L=A:GOSUB ALLOC RETURN DO_ATOM_Q: - R=1 - IF (Z%(A,0)AND 31)=12 THEN R=2 - RETURN + R=(Z%(A,0)AND 31)=12 + GOTO RETURN_TRUE_FALSE DO_DEREF: - R=Z%(A,1):GOSUB DEREF_R - Z%(R,0)=Z%(R,0)+32 - RETURN + R=Z%(A,1) + GOTO RETURN_INC_REF DO_RESET_BANG: R=B REM release current value @@ -531,7 +523,10 @@ DO_FUNCTION: REM P1=ZT:P2=-1:GOSUB PR_MEMORY REM RETURN DO_PR_MEMORY_SUMMARY: - GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY + GOSUB PR_MEMORY_SUMMARY_SMALL + R=0 + GOTO RETURN_INC_REF RETURN DO_EVAL: diff --git a/basic/debug.in.bas b/basic/debug.in.bas index 5d199edbea..0f65f75179 100644 --- a/basic/debug.in.bas +++ b/basic/debug.in.bas @@ -13,31 +13,43 @@ CHECK_FREE_LIST: IF P2=-1 THEN PRINT "corrupt free list at "+STR$(P1) RETURN -REM COUNT_STRINGS() -> P2 -COUNT_STRINGS: - P1=0 - P2=0 - COUNT_STRINGS_LOOP: - IF P1>S-1 THEN RETURN - IF S%(P1)>0 THEN P2=P2+1 - P1=P1+1 - GOTO COUNT_STRINGS_LOOP - -PR_MEMORY_SUMMARY: +PR_MEMORY_SUMMARY_SMALL: #cbm P0=FRE(0) + GOSUB CHECK_FREE_LIST + #cbm PRINT "Free:"+STR$(FRE(0))+", "; + PRINT "Values:"+STR$(ZI-1-P2)+", Emptys:"; + FOR I=0 TO 7 + IF I<>4 AND I<>6 THEN PRINT STR$(INT(Z%(I,0)/32))+","; + NEXT I PRINT - #cbm PRINT "Free (FRE) :"+STR$(P0) - GOSUB CHECK_FREE_LIST: REM get count in P2 - PRINT "Values (Z%) :"+STR$(ZI-1-P2)+" /"+STR$(Z1) - REM PRINT " max:"+STR$(ZI-1); - REM PRINT ", freed:"+STR$(P2)+", after repl_env:"+STR$(ZT) - GOSUB COUNT_STRINGS - PRINT "Strings (S$) :"+STR$(P2)+" /"+STR$(Z2) - #qbasic PRINT "Stack (X%) :"+STR$(X+1)+" /"+STR$(Z3) - #cbm PRINT "Stack :"+STR$(X+2-Z3)+" / 1920" RETURN +REM REM COUNT_STRINGS() -> P2 +REM COUNT_STRINGS: +REM P1=0 +REM P2=0 +REM COUNT_STRINGS_LOOP: +REM IF P1>S-1 THEN RETURN +REM IF S%(P1)>0 THEN P2=P2+1 +REM P1=P1+1 +REM GOTO COUNT_STRINGS_LOOP +REM +REM PR_MEMORY_SUMMARY: +REM #cbm P0=FRE(0) +REM +REM PRINT +REM #cbm PRINT "Free (FRE) :"+STR$(P0) +REM GOSUB CHECK_FREE_LIST: REM get count in P2 +REM PRINT "Values (Z%) :"+STR$(ZI-1-P2)+" /"+STR$(Z1) +REM REM PRINT " max:"+STR$(ZI-1); +REM REM PRINT ", freed:"+STR$(P2)+", after repl_env:"+STR$(ZT) +REM GOSUB COUNT_STRINGS +REM PRINT "Strings (S$) :"+STR$(P2)+" /"+STR$(Z2) +REM #qbasic PRINT "Stack (X%) :"+STR$(X+1)+" /"+STR$(Z3) +REM #cbm PRINT "Stack :"+STR$(X+2-Z3)+" / 1920" +REM RETURN +REM REM #cbm PR_MEMORY_MAP: REM #cbm PRINT REM #cbm P1=PEEK(43)+PEEK(44)*256 @@ -60,7 +72,7 @@ REM #cbm PRINT "Variables :"STR$(P3-P2) REM #cbm PRINT "Arrays :"STR$(P4-P3) REM #cbm PRINT "String Heap :"STR$(P7-P5) REM #cbm RETURN - +REM REM REM PR_MEMORY(P1, P2) -> nil REM PR_MEMORY: REM IF P2 nil REM PR_OBJECT: REM RD=0 REM -REM RD=RD+1:X=X+1:X%(X)=P1 +REM IF P1=-1 THEN PRINT " "+STR$(-1)+": ---":RETURN +REM RD=RD+1 +REM Q=P1:GOSUB PUSH_Q REM REM PR_OBJ_LOOP: REM IF RD=0 THEN RETURN -REM I=X%(X):RD=RD-1:X=X-1 +REM GOSUB POP_Q:I=Q +REM RD=RD-1 REM REM P2=Z%(I,0)AND 31 +REM P3=Z%(I,1) REM PRINT " "+STR$(I); -REM PRINT ": ref cnt: "+STR$((Z%(I,0)AND-32)/32); -REM PRINT ", type: "+STR$(P2)+", value: "+STR$(Z%(I,1)); +REM PRINT ": ref cnt:"+STR$((Z%(I,0)AND-32)/32); +REM PRINT ", type:"+STR$(P2)+", value: "+STR$(Z%(I,1)); +REM IF P2=2 THEN PRINT " "+STR$(Z%(I,1)); REM IF P2=4 THEN PRINT " '"+S$(Z%(I,1))+"'"; -REM IF P2=5 THEN PRINT " "+S$(Z%(I,1))+""; +REM IF P2=5 THEN PRINT " "+S$(Z%(I,1)); +REM IF P2=6 AND P3>0 THEN PRINT " ()"; +REM IF P2=6 AND P3=0 THEN PRINT " )"; +REM IF P2=7 AND P3>0 THEN PRINT " []"; +REM IF P2=7 AND P3=0 THEN PRINT " ]"; +REM IF P2=8 AND P3>0 THEN PRINT " {}"; +REM IF P2=8 AND P3=0 THEN PRINT " }"; +REM IF P2=9 THEN PRINT " #"; REM PRINT +REM IF P2=8 THEN PRINT " "+STR$(I+1)+": key:"+STR$(Z%(I+1,0)); +REM IF P2=8 THEN PRINT ", value:"+STR$(Z%(I+1,1)) +REM REM IF P2<=5 OR P2=9 THEN GOTO PR_OBJ_LOOP -REM IF Z%(I,1)<>0 THEN RD=RD+1:X=X+1:X%(X)=Z%(I,1) -REM IF P2>=6 AND P2<=8 THEN RD=RD+1:X=X+1:X%(X)=I+1 +REM IF Z%(I,1)<>0 THEN RD=RD+1:Q=Z%(I,1):GOSUB PUSH_Q +REM IF P2=8 AND I<>7 THEN RD=RD+2:Q=Z%(I+1,1):GOSUB PUSH_Q:Q=Z%(I+1,0):GOSUB PUSH_Q +REM IF P2>5 AND P2<8 THEN RD=RD+1:Q=I+1:GOSUB PUSH_Q REM GOTO PR_OBJ_LOOP diff --git a/basic/env.in.bas b/basic/env.in.bas index cab37f3c7e..37331665f7 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -20,14 +20,14 @@ ENV_NEW_BINDS: ENV_NEW_BINDS_LOOP: IF Z%(A,1)=0 THEN R=E:RETURN REM get/deref the key from A - R=A+1:GOSUB DEREF_R + R=A:GOSUB VAL_R K=R IF S$(Z%(K,1))="&" THEN GOTO EVAL_NEW_BINDS_VARGS EVAL_NEW_BINDS_1x1: REM get/deref the key from B - R=B+1:GOSUB DEREF_R + R=B:GOSUB VAL_R C=R REM set the binding in the environment data GOSUB ENV_SET @@ -39,7 +39,7 @@ ENV_NEW_BINDS: EVAL_NEW_BINDS_VARGS: REM get/deref the key from next element of A A=Z%(A,1) - R=A+1:GOSUB DEREF_R + R=A:GOSUB VAL_R K=R REM the value is the remaining list in B A=B:T=6:GOSUB FORCE_SEQ_TYPE @@ -76,17 +76,17 @@ SUB ENV_FIND REM More efficient to use GET for value (R) and contains? (R3) GOSUB HASHMAP_GET REM if we found it, save value in R4 for ENV_GET - IF R3=1 THEN R4=R:GOTO ENV_FIND_DONE + IF R3=1 THEN R4=R:R=T:GOTO ENV_FIND_DONE T=Z%(T+1,1): REM get outer environment - IF T<>-1 THEN GOTO ENV_FIND_LOOP + IF T>0 THEN GOTO ENV_FIND_LOOP + R=-1 ENV_FIND_DONE: - R=T END SUB REM ENV_GET(E, K) -> R ENV_GET: CALL ENV_FIND - IF R=-1 THEN R=0:ER=-1:E$="'"+S$(Z%(K,1))+"' not found":GOTO ENV_GET_RETURN - R=R4:GOSUB DEREF_R + IF R=-1 THEN ER=-1:E$="'"+S$(Z%(K,1))+"' not found":GOTO ENV_GET_RETURN + R=R4 Z%(R,0)=Z%(R,0)+32 GOTO ENV_GET_RETURN diff --git a/basic/printer.in.bas b/basic/printer.in.bas index ea83f5a88e..14ec9e0b0d 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -4,7 +4,7 @@ PR_STR: PR_STR_RECUR: T=Z%(AZ,0)AND 31 U=Z%(AZ,1) - REM PRINT "AZ: "+STR$(AZ)+", T: "+STR$(T)+", C: "+STR$(U) + REM PRINT "AZ: "+STR$(AZ)+", T: "+STR$(T)+", U: "+STR$(U) IF T=0 THEN R$="nil":RETURN REM if metadata, then get actual object IF T>=16 THEN AZ=U:GOTO PR_STR_RECUR @@ -50,9 +50,14 @@ PR_STR: S$(S)=R$:S=S+1 PR_SEQ_LOOP: IF Z%(AZ,1)=0 THEN GOTO PR_SEQ_DONE - AZ=AZ+1:GOSUB PR_STR + IF T<>8 THEN AZ=AZ+1:GOSUB PR_STR:GOSUB PEEK_Q_1:T=Q + IF T=8 THEN AZ=Z%(AZ+1,0):GOSUB PR_STR:GOSUB PEEK_Q_1:T=Q REM append what we just rendered it S$(S-1)=S$(S-1)+R$ + + REM if this is a hash-map, print the next element + IF T=8 THEN GOSUB PEEK_Q:AZ=Z%(Q+1,1):GOSUB PR_STR:S$(S-1)=S$(S-1)+" "+R$ + REM restore current seq type GOSUB PEEK_Q_1:T=Q REM Go to next list element diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 8e1e522abc..4e1ae352dc 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -1,9 +1,11 @@ REM READ_TOKEN(A$, RI, RF) -> T$ READ_TOKEN: + GOSUB SKIP_SPACES RJ=RI IF RF=1 THEN GOSUB READ_FILE_CHUNK REM PRINT "READ_TOKEN: "+STR$(RJ)+", "+MID$(A$,RJ,1) T$=MID$(A$,RJ,1) + IF T$=";" THEN GOSUB SKIP_TO_EOL:GOTO READ_TOKEN IF T$="(" OR T$=")" OR T$="[" OR T$="]" OR T$="{" OR T$="}" THEN RETURN IF T$="'" OR T$="`" OR T$="@" THEN RETURN IF T$="~" AND NOT MID$(A$,RJ+1,1)="@" THEN RETURN @@ -56,18 +58,14 @@ SKIP_TO_EOL: GOTO SKIP_TO_EOL -READ_ATOM: - R=0 - RETURN - REM READ_FORM(A$, RI, RF) -> R -READ_FORM: - IF ER<>-2 THEN RETURN - GOSUB SKIP_SPACES +SUB READ_FORM + Q=T:GOSUB PUSH_Q: REM save current value of T + READ_FORM_RECUR: + IF ER<>-2 THEN GOTO READ_FORM_RETURN GOSUB READ_TOKEN - IF T$="" AND SD>0 THEN E$="unexpected EOF":GOTO READ_FORM_ABORT REM PRINT "READ_FORM T$: ["+T$+"]" - IF T$="" THEN R=0:GOTO READ_FORM_DONE + IF T$="" THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO READ_FORM_RETURN IF T$="nil" THEN T=0:GOTO READ_NIL_BOOL IF T$="false" THEN T=1:GOTO READ_NIL_BOOL IF T$="true" THEN T=2:GOTO READ_NIL_BOOL @@ -79,45 +77,43 @@ READ_FORM: IF T$="@" THEN B$="deref":GOTO READ_MACRO C$=MID$(T$,1,1) REM PRINT "C$: ["+C$+"]("+STR$(ASC(C$))+")" - IF (C$=";") THEN R=0:GOSUB SKIP_TO_EOL:GOTO READ_FORM IF C$>="0" AND C$<="9" THEN GOTO READ_NUMBER IF C$="-" THEN GOTO READ_SYMBOL_MAYBE IF C$=CHR$(34) THEN GOTO READ_STRING IF C$=":" THEN GOTO READ_KEYWORD - IF C$="(" THEN T=6:GOTO READ_SEQ - IF C$=")" THEN T=6:GOTO READ_SEQ_END - IF C$="[" THEN T=7:GOTO READ_SEQ - IF C$="]" THEN T=7:GOTO READ_SEQ_END - IF C$="{" THEN T=8:GOTO READ_SEQ - IF C$="}" THEN T=8:GOTO READ_SEQ_END + REM set end character in Q and read the sequence + IF C$="(" THEN T=6:Q=ASC(")"):GOTO READ_SEQ_START + IF C$="[" THEN T=7:Q=ASC("]"):GOTO READ_SEQ_START + IF C$="{" THEN T=8:Q=ASC("}"):GOTO READ_SEQ_START + IF C$=")" OR C$="]" OR C$="}" THEN R=-1:ER=-1:E$="unexpected "+C$:GOTO READ_FORM_RETURN GOTO READ_SYMBOL READ_NIL_BOOL: REM PRINT "READ_NIL_BOOL" R=T Z%(R,0)=Z%(R,0)+32 - GOTO READ_FORM_DONE + GOTO READ_FORM_RETURN READ_NUMBER: REM PRINT "READ_NUMBER" T=2:L=VAL(T$):GOSUB ALLOC - GOTO READ_FORM_DONE + GOTO READ_FORM_RETURN READ_MACRO: RI=RI+LEN(T$) - REM to call READ_FORM recursively, SD needs to be saved, set to - REM 0 for the call and then restored afterwards. - REM push macro type and SD + REM push macro type Q=-1*(T$="^"):GOSUB PUSH_Q - Q=SD:GOSUB PUSH_Q REM B$ is set above T=5:GOSUB STRING + REM push string GOSUB PUSH_R - SD=0:GOSUB READ_FORM + CALL READ_FORM + REM push first form GOSUB PUSH_R + IF ER>-2 THEN GOTO READ_MACRO_DONE - Q=3:GOSUB PEEK_Q_Q + GOSUB PEEK_Q_2 IF Q THEN GOTO READ_MACRO_3 READ_MACRO_2: @@ -127,7 +123,7 @@ READ_FORM: GOTO READ_MACRO_DONE READ_MACRO_3: - SD=0:GOSUB READ_FORM + CALL READ_FORM GOSUB PEEK_Q_1:C=Q B=R GOSUB PEEK_Q:A=Q @@ -139,110 +135,86 @@ READ_FORM: AY=B:GOSUB RELEASE AY=A:GOSUB RELEASE - REM get SD and pop the stack - GOSUB POP_Q - GOSUB POP_Q - GOSUB POP_Q:SD=Q - GOSUB POP_Q + REM pop the stack + GOSUB POP_Q: REM pop first form + GOSUB POP_Q: REM pop string + GOSUB POP_Q: REM pop macro type T$="": REM necessary to prevent unexpected EOF errors - GOTO READ_FORM_DONE + GOTO READ_FORM_RETURN + READ_STRING: REM PRINT "READ_STRING" C=ASC(MID$(T$,LEN(T$),1)) - IF C<>34 THEN E$="expected '"+CHR$(34)+"'":GOTO READ_FORM_ABORT + IF C<>34 THEN R=-1:ER=-1:E$="expected '"+CHR$(34)+"'":GOTO READ_FORM_RETURN R$=MID$(T$,2,LEN(T$)-2) S1$=CHR$(92)+CHR$(34):S2$=CHR$(34):GOSUB REPLACE: REM unescape quotes S1$=CHR$(92)+"n":S2$=CHR$(13):GOSUB REPLACE: REM unescape newlines S1$=CHR$(92)+CHR$(92):S2$=CHR$(92):GOSUB REPLACE: REM unescape backslashes REM intern string value B$=R$:T=4:GOSUB STRING - GOTO READ_FORM_DONE + GOTO READ_FORM_RETURN READ_KEYWORD: R$=CHR$(127)+MID$(T$,2,LEN(T$)-1) B$=R$:T=4:GOSUB STRING - GOTO READ_FORM_DONE + GOTO READ_FORM_RETURN READ_SYMBOL_MAYBE: C$=MID$(T$,2,1) IF C$>="0" AND C$<="9" THEN GOTO READ_NUMBER READ_SYMBOL: REM PRINT "READ_SYMBOL" B$=T$:T=5:GOSUB STRING - GOTO READ_FORM_DONE + GOTO READ_FORM_RETURN - READ_SEQ: - REM PRINT "READ_SEQ" - SD=SD+1: REM increase read sequence depth + READ_SEQ_START: + RI=RI+LEN(T$) + SD=SD+1 - REM point to empty sequence to start off - R=(T-5)*2+1: REM calculate location of empty seq - Z%(R,0)=Z%(R,0)+32 + GOSUB PUSH_Q: REM push return character - REM push start ptr on the stack - GOSUB PUSH_R - REM push current sequence type - Q=T:GOSUB PUSH_Q - REM push previous ptr on the stack - GOSUB PUSH_R + REM setup the stack for the loop + GOSUB MAP_LOOP_START - RI=RI+LEN(T$) - GOTO READ_FORM + READ_SEQ_LOOP: + GOSUB READ_TOKEN: REM peek at token + IF T$="" THEN ER=-1:E$="unexpected EOF" + Q=3:GOSUB PEEK_Q_Q + IF ER<>-2 OR T$=CHR$(Q) THEN GOTO READ_SEQ_DONE + + CALL READ_FORM + + REM if error, release the unattached element + IF ER<>-2 THEN AY=R:GOSUB RELEASE:GOTO READ_SEQ_DONE + + REM if this is a hash-map, READ_FORM again + IF T=8 THEN GOSUB PUSH_R:CALL READ_FORM + IF T=8 THEN GOSUB POP_Q:M=Q: REM key value + + REM main value + REM for list/vector this is result of the first READ_FORM + N=R + + + REM update the return sequence structure + REM release N since list takes full ownership + C=1:GOSUB MAP_LOOP_UPDATE + + GOTO READ_SEQ_LOOP - READ_SEQ_END: - REM PRINT "READ_SEQ_END" - IF SD=0 THEN E$="unexpected '"+C$+"'":GOTO READ_FORM_ABORT - GOSUB PEEK_Q_1 - IF Q<>T THEN E$="sequence mismatch":GOTO READ_FORM_ABORT - SD=SD-1: REM decrease read sequence depth - GOSUB POP_Q: REM pop previous - GOSUB POP_Q:T=Q: REM type prior to recur - GOSUB POP_R: REM ptr to start of sequence to return - GOTO READ_FORM_DONE + READ_SEQ_DONE: + SD=SD-1 + REM cleanup stack and get return value + GOSUB MAP_LOOP_DONE + GOSUB POP_Q: REM pop end character ptr +REM P1=R:PRINT "READ_SEQ R:":GOSUB PR_OBJECT + GOTO READ_FORM_RETURN - READ_FORM_DONE: + READ_FORM_RETURN: +REM IF ER<>-2 THEN R=0:Z%(R,0)=Z%(R,0)+32 RI=RI+LEN(T$) + GOSUB POP_Q:T=Q: REM restore current value of T - REM check read sequence depth - IF SD=0 THEN RETURN - - GOSUB PEEK_Q: REM previous element - - REM allocate new sequence entry, set type to previous type, set - REM next to previous next or previous (if first) - L=Z%(Q,1) - IF Q<9 THEN L=Q - AY=R: REM save previous value for release - GOSUB PEEK_Q_1:T=Q - N=R:GOSUB ALLOC - REM list takes ownership - GOSUB RELEASE - IF L<9 THEN AY=L:GOSUB RELEASE - - REM if previous element is the first element then set - REM the first to the new element - GOSUB PEEK_Q: REM previous element - IF Q<9 THEN Q=R:GOSUB PUT_Q_2:GOTO READ_FORM_SKIP_FIRST - REM set previous list element to point to new element - Z%(Q,1)=R - - READ_FORM_SKIP_FIRST: - - REM update previous pointer to current element - Q=R:GOSUB PUT_Q - GOTO READ_FORM - - READ_FORM_ABORT: - ER=-1 - R=0 - READ_FORM_ABORT_UNWIND: - IF SD=0 THEN RETURN - SD=SD-1: REM decrease read sequence depth - REM pop previous, type, and start off the stack - GOSUB POP_Q - GOSUB POP_Q - GOSUB POP_Q:AY=Q - IF SD=0 THEN GOSUB RELEASE - GOTO READ_FORM_ABORT_UNWIND +END SUB REM READ_STR(A$) -> R @@ -250,7 +222,7 @@ READ_STR: RI=1: REM index into A$ RF=0: REM not reading from file SD=0: REM sequence read depth - GOSUB READ_FORM + CALL READ_FORM RETURN REM READ_FILE(A$) -> R @@ -264,7 +236,8 @@ READ_FILE: #qbasic IF NOT _FILEEXISTS(A$) THEN ER=-1:E$="File not found":RETURN #qbasic OPEN A$ FOR INPUT AS #2 REM READ_FILE_CHUNK adds terminating ")" - A$="(do ":GOSUB READ_FORM + A$="(do " + CALL READ_FORM CLOSE 2 EZ=0 RETURN diff --git a/basic/step0_repl.in.bas b/basic/step0_repl.in.bas index f722c91518..3d05ae2069 100755 --- a/basic/step0_repl.in.bas +++ b/basic/step0_repl.in.bas @@ -2,6 +2,8 @@ GOTO MAIN REM $INCLUDE: 'readline.in.bas' +REM $INCLUDE: 'debug.in.bas' + REM READ(A$) -> R$ MAL_READ: R$=A$ @@ -36,6 +38,6 @@ MAIN: GOTO REPL_LOOP QUIT: - REM PRINT "Free: "+STR$(FRE(0)) + REM GOSUB PR_MEMORY_SUMMARY_SMALL END diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index 7c63b341cc..90e8d9e6fb 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -31,12 +31,10 @@ SUB REP IF ER<>-2 THEN GOTO REP_DONE A=R:GOSUB MAL_PRINT - RT$=R$ REP_DONE: REM Release memory from EVAL AY=R:GOSUB RELEASE - R$=RT$ END SUB REM MAIN program @@ -48,6 +46,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser IF EZ=1 THEN GOTO QUIT + IF R$="" THEN GOTO REPL_LOOP A$=R$:CALL REP: REM call REP @@ -56,7 +55,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY_SMALL END PRINT_ERROR: diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index eeba70c428..424d5fa07f 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -22,89 +22,60 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - GOSUB DEREF_A - T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R=A:GOSUB DEREF_R + R=A Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: H=E:K=A:GOSUB HASHMAP_GET - GOSUB DEREF_R - IF R3=0 THEN ER=-1:E$="'"+S$(Z%(A,1))+"' not found":GOTO EVAL_AST_RETURN + IF R3=0 THEN R=-1:ER=-1:E$="'"+S$(Z%(A,1))+"' not found":GOTO EVAL_AST_RETURN Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry (T already set above) - L=0:N=0:GOSUB ALLOC - - REM push type of sequence - Q=T:GOSUB PUSH_Q - REM push sequence index - Q=0:GOSUB PUSH_Q - REM push future return value (new sequence) - GOSUB PUSH_R - REM push previous new sequence entry - GOSUB PUSH_R + REM setup the stack for the loop + GOSUB MAP_LOOP_START EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE - REM if hashmap, skip eval of even entries (keys) - Q=3:GOSUB PEEK_Q_Q:T=Q - REM get and update index - GOSUB PEEK_Q_2 - Q=Q+1:GOSUB PUT_Q_2 - IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF - GOTO EVAL_AST_DO_EVAL - - EVAL_AST_DO_REF: - R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value - GOTO EVAL_AST_ADD_VALUE - - EVAL_AST_DO_EVAL: - REM call EVAL for each entry - A=A+1:CALL EVAL - A=A-1 - GOSUB DEREF_R: REM deref to target of evaluated entry + REM call EVAL for each entry + GOSUB PUSH_A + IF T<>8 THEN GOSUB VAL_A + IF T=8 THEN A=Z%(A+1,1) + Q=T:GOSUB PUSH_Q: REM push/save type + CALL EVAL + GOSUB POP_Q:T=Q: REM pop/restore type + GOSUB POP_A - EVAL_AST_ADD_VALUE: + REM if error, release the unattached element + REM TODO: is R=0 correct? + IF ER<>-2 THEN AY=R:GOSUB RELEASE:R=0:GOTO EVAL_AST_SEQ_LOOP_DONE - REM update previous value pointer to evaluated entry - GOSUB PEEK_Q - Z%(Q+1,1)=R + REM for hash-maps, copy the key (inc ref since we are going to + REM release it below) + IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 - IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM value evaluated above + N=R - REM allocate the next entry - REM same new sequence entry type - Q=3:GOSUB PEEK_Q_Q:T=Q - L=0:N=0:GOSUB ALLOC - - REM update previous sequence entry value to point to new entry - GOSUB PEEK_Q - Z%(Q,1)=R - REM update previous ptr to current entry - Q=R:GOSUB PUT_Q + REM update the return sequence structure + REM release N (and M if T=8) since seq takes full ownership + C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - REM get return value (new seq), index, and seq type - GOSUB PEEK_Q_1 - R=Q - REM pop previous, return, index and type - GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q + REM cleanup stack and get return value + GOSUB MAP_LOOP_DONE GOTO EVAL_AST_RETURN EVAL_AST_RETURN: @@ -128,8 +99,6 @@ SUB EVAL REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" - GOSUB DEREF_A - GOSUB LIST_Q IF R THEN GOTO APPLY_LIST REM ELSE @@ -146,12 +115,13 @@ SUB EVAL REM if error, return f/args for release by caller IF ER<>-2 THEN GOTO EVAL_RETURN - F=R+1 AR=Z%(R,1): REM rest - R=F:GOSUB DEREF_R:F=R - IF (Z%(F,0)AND 31)<>9 THEN ER=-1:E$="apply of non-function":GOTO EVAL_RETURN + GOSUB VAL_R:F=R + + IF (Z%(F,0)AND 31)<>9 THEN R=-1:ER=-1:E$="apply of non-function":GOTO EVAL_INVOKE_DONE GOSUB DO_FUNCTION + EVAL_INVOKE_DONE: AY=W:GOSUB RELEASE GOTO EVAL_RETURN @@ -180,8 +150,8 @@ DO_FUNCTION: G=Z%(F,1) REM Get argument values - R=AR+1:GOSUB DEREF_R:A=Z%(R,1) - R=Z%(AR,1)+1:GOSUB DEREF_R:B=Z%(R,1) + R=AR:GOSUB VAL_R:A=Z%(R,1) + R=Z%(AR,1):GOSUB VAL_R:B=Z%(R,1) REM Switch on the function number IF G=1 THEN GOTO DO_ADD @@ -214,7 +184,7 @@ MAL_PRINT: REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=0:R2=0 + R1=-1:R2=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE @@ -224,13 +194,11 @@ SUB REP IF ER<>-2 THEN GOTO REP_DONE A=R:GOSUB MAL_PRINT - RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2<>0 THEN AY=R2:GOSUB RELEASE - IF R1<>0 THEN AY=R1:GOSUB RELEASE - R$=RT$ + AY=R2:GOSUB RELEASE + AY=R1:GOSUB RELEASE END SUB REM MAIN program @@ -263,6 +231,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser IF EZ=1 THEN GOTO QUIT + IF R$="" THEN GOTO REPL_LOOP A$=R$:CALL REP: REM call REP @@ -271,7 +240,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY_SMALL END PRINT_ERROR: diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index b23fc0e292..f2329baae7 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -23,14 +23,12 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - GOSUB DEREF_A - T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R=A:GOSUB DEREF_R + R=A Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN @@ -40,73 +38,44 @@ SUB EVAL_AST GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry (T already set above) - L=0:N=0:GOSUB ALLOC - - REM push type of sequence - Q=T:GOSUB PUSH_Q - REM push sequence index - Q=0:GOSUB PUSH_Q - REM push future return value (new sequence) - GOSUB PUSH_R - REM push previous new sequence entry - GOSUB PUSH_R + REM setup the stack for the loop + GOSUB MAP_LOOP_START EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE - REM if hashmap, skip eval of even entries (keys) - Q=3:GOSUB PEEK_Q_Q:T=Q - REM get and update index - GOSUB PEEK_Q_2 - Q=Q+1:GOSUB PUT_Q_2 - IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF - GOTO EVAL_AST_DO_EVAL - - EVAL_AST_DO_REF: - R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value - GOTO EVAL_AST_ADD_VALUE - - EVAL_AST_DO_EVAL: - REM call EVAL for each entry - A=A+1:CALL EVAL - A=A-1 - GOSUB DEREF_R: REM deref to target of evaluated entry - - EVAL_AST_ADD_VALUE: + REM call EVAL for each entry + GOSUB PUSH_A + IF T<>8 THEN GOSUB VAL_A + IF T=8 THEN A=Z%(A+1,1) + Q=T:GOSUB PUSH_Q: REM push/save type + CALL EVAL + GOSUB POP_Q:T=Q: REM pop/restore type + GOSUB POP_A - REM update previous value pointer to evaluated entry - GOSUB PEEK_Q - Z%(Q+1,1)=R + REM if error, release the unattached element + REM TODO: is R=0 correct? + IF ER<>-2 THEN AY=R:GOSUB RELEASE:R=0:GOTO EVAL_AST_SEQ_LOOP_DONE - IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM for hash-maps, copy the key (inc ref since we are going to + REM release it below) + IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 - REM allocate the next entry - REM same new sequence entry type - Q=3:GOSUB PEEK_Q_Q:T=Q - L=0:N=0:GOSUB ALLOC + REM value evaluated above + N=R - REM update previous sequence entry value to point to new entry - GOSUB PEEK_Q - Z%(Q,1)=R - REM update previous ptr to current entry - Q=R:GOSUB PUT_Q + REM update the return sequence structure + REM release N (and M if T=8) since seq takes full ownership + C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - GOSUB PEEK_Q_1 - REM if no error, get return value (new seq) - IF ER=-2 THEN R=Q - REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE - - REM pop previous, return, index and type - GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q + REM cleanup stack and get return value + GOSUB MAP_LOOP_DONE GOTO EVAL_AST_RETURN EVAL_AST_RETURN: @@ -130,8 +99,6 @@ SUB EVAL REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" - GOSUB DEREF_A - GOSUB LIST_Q IF R THEN GOTO APPLY_LIST REM ELSE @@ -142,8 +109,7 @@ SUB EVAL GOSUB EMPTY_Q IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0=A+1 - R=A0:GOSUB DEREF_R:A0=R + A0=Z%(A+1,1) REM get symbol in A$ IF (Z%(A0,0)AND 31)<>5 THEN A$="" @@ -154,14 +120,14 @@ SUB EVAL GOTO EVAL_INVOKE EVAL_GET_A3: - A3=Z%(Z%(Z%(A,1),1),1)+1 - R=A3:GOSUB DEREF_R:A3=R + R=Z%(Z%(Z%(A,1),1),1) + GOSUB VAL_R:A3=R EVAL_GET_A2: - A2=Z%(Z%(A,1),1)+1 - R=A2:GOSUB DEREF_R:A2=R + R=Z%(Z%(A,1),1) + GOSUB VAL_R:A2=R EVAL_GET_A1: - A1=Z%(A,1)+1 - R=A1:GOSUB DEREF_R:A1=R + R=Z%(A,1) + GOSUB VAL_R:A1=R RETURN EVAL_DEF: @@ -191,13 +157,13 @@ SUB EVAL Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:CALL EVAL + A=Z%(A1,1):GOSUB VAL_A:CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:C=R:GOSUB ENV_SET + K=Z%(A1+1,1):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -214,19 +180,17 @@ SUB EVAL REM if error, return f/args for release by caller IF ER<>-2 THEN GOTO EVAL_RETURN - F=R+1 AR=Z%(R,1): REM rest - R=F:GOSUB DEREF_R:F=R - IF (Z%(F,0)AND 31)<>9 THEN ER=-1:E$="apply of non-function":GOTO EVAL_RETURN + GOSUB VAL_R:F=R + + IF (Z%(F,0)AND 31)<>9 THEN R=-1:ER=-1:E$="apply of non-function":GOTO EVAL_INVOKE_DONE GOSUB DO_FUNCTION + EVAL_INVOKE_DONE: AY=W:GOSUB RELEASE GOTO EVAL_RETURN EVAL_RETURN: - REM AZ=R: B=1: GOSUB PR_STR - REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER) - REM release environment if not the top one on the stack GOSUB PEEK_Q_1 IF E<>Q THEN AY=E:GOSUB RELEASE @@ -254,8 +218,8 @@ DO_FUNCTION: G=Z%(F,1) REM Get argument values - R=AR+1:GOSUB DEREF_R:A=Z%(R,1) - R=Z%(AR,1)+1:GOSUB DEREF_R:B=Z%(R,1) + R=AR:GOSUB VAL_R:A=Z%(R,1) + R=Z%(AR,1):GOSUB VAL_R:B=Z%(R,1) REM Switch on the function number IF G=1 THEN GOTO DO_ADD @@ -288,7 +252,7 @@ MAL_PRINT: REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=0:R2=0 + R1=-1:R2=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE @@ -298,13 +262,11 @@ SUB REP IF ER<>-2 THEN GOTO REP_DONE A=R:GOSUB MAL_PRINT - RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2<>0 THEN AY=R2:GOSUB RELEASE - IF R1<>0 THEN AY=R1:GOSUB RELEASE - R$=RT$ + AY=R2:GOSUB RELEASE + AY=R1:GOSUB RELEASE END SUB REM MAIN program @@ -314,7 +276,7 @@ MAIN: LV=0 REM create repl_env - C=-1:GOSUB ENV_NEW:D=R + C=0:GOSUB ENV_NEW:D=R E=D REM + function @@ -338,6 +300,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser IF EZ=1 THEN GOTO QUIT + IF R$="" THEN GOTO REPL_LOOP A$=R$:CALL REP: REM call REP @@ -346,7 +309,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY_SMALL END PRINT_ERROR: diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index 08dfb6f71d..b79a583ff3 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -22,14 +22,12 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - GOSUB DEREF_A - T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R=A:GOSUB DEREF_R + R=A Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN @@ -39,73 +37,44 @@ SUB EVAL_AST GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry (T already set above) - L=0:N=0:GOSUB ALLOC - - REM push type of sequence - Q=T:GOSUB PUSH_Q - REM push sequence index - Q=0:GOSUB PUSH_Q - REM push future return value (new sequence) - GOSUB PUSH_R - REM push previous new sequence entry - GOSUB PUSH_R + REM setup the stack for the loop + GOSUB MAP_LOOP_START EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE - REM if hashmap, skip eval of even entries (keys) - Q=3:GOSUB PEEK_Q_Q:T=Q - REM get and update index - GOSUB PEEK_Q_2 - Q=Q+1:GOSUB PUT_Q_2 - IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF - GOTO EVAL_AST_DO_EVAL - - EVAL_AST_DO_REF: - R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value - GOTO EVAL_AST_ADD_VALUE - - EVAL_AST_DO_EVAL: - REM call EVAL for each entry - A=A+1:CALL EVAL - A=A-1 - GOSUB DEREF_R: REM deref to target of evaluated entry - - EVAL_AST_ADD_VALUE: + REM call EVAL for each entry + GOSUB PUSH_A + IF T<>8 THEN GOSUB VAL_A + IF T=8 THEN A=Z%(A+1,1) + Q=T:GOSUB PUSH_Q: REM push/save type + CALL EVAL + GOSUB POP_Q:T=Q: REM pop/restore type + GOSUB POP_A - REM update previous value pointer to evaluated entry - GOSUB PEEK_Q - Z%(Q+1,1)=R + REM if error, release the unattached element + REM TODO: is R=0 correct? + IF ER<>-2 THEN AY=R:GOSUB RELEASE:R=0:GOTO EVAL_AST_SEQ_LOOP_DONE - IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM for hash-maps, copy the key (inc ref since we are going to + REM release it below) + IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 - REM allocate the next entry - REM same new sequence entry type - Q=3:GOSUB PEEK_Q_Q:T=Q - L=0:N=0:GOSUB ALLOC + REM value evaluated above + N=R - REM update previous sequence entry value to point to new entry - GOSUB PEEK_Q - Z%(Q,1)=R - REM update previous ptr to current entry - Q=R:GOSUB PUT_Q + REM update the return sequence structure + REM release N (and M if T=8) since seq takes full ownership + C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - GOSUB PEEK_Q_1 - REM if no error, get return value (new seq) - IF ER=-2 THEN R=Q - REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE - - REM pop previous, return, index and type - GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q + REM cleanup stack and get return value + GOSUB MAP_LOOP_DONE GOTO EVAL_AST_RETURN EVAL_AST_RETURN: @@ -131,8 +100,6 @@ SUB EVAL REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" - GOSUB DEREF_A - GOSUB LIST_Q IF R THEN GOTO APPLY_LIST REM ELSE @@ -143,8 +110,7 @@ SUB EVAL GOSUB EMPTY_Q IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0=A+1 - R=A0:GOSUB DEREF_R:A0=R + A0=Z%(A+1,1) REM get symbol in A$ IF (Z%(A0,0)AND 31)<>5 THEN A$="" @@ -158,14 +124,14 @@ SUB EVAL GOTO EVAL_INVOKE EVAL_GET_A3: - A3=Z%(Z%(Z%(A,1),1),1)+1 - R=A3:GOSUB DEREF_R:A3=R + R=Z%(Z%(Z%(A,1),1),1) + GOSUB VAL_R:A3=R EVAL_GET_A2: - A2=Z%(Z%(A,1),1)+1 - R=A2:GOSUB DEREF_R:A2=R + R=Z%(Z%(A,1),1) + GOSUB VAL_R:A2=R EVAL_GET_A1: - A1=Z%(A,1)+1 - R=A1:GOSUB DEREF_R:A1=R + R=Z%(A,1) + GOSUB VAL_R:A1=R RETURN EVAL_DEF: @@ -195,13 +161,13 @@ SUB EVAL Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:CALL EVAL + A=Z%(A1,1):GOSUB VAL_A:CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:C=R:GOSUB ENV_SET + K=Z%(A1+1,1):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -238,7 +204,7 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:GOTO EVAL_RETURN + IF R<4 THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -256,10 +222,8 @@ SUB EVAL REM push f/args for release after call GOSUB PUSH_R - F=R+1 - AR=Z%(R,1): REM rest - R=F:GOSUB DEREF_R:F=R + GOSUB VAL_R:F=R REM if metadata, get the actual object IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) @@ -284,7 +248,7 @@ SUB EVAL GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4=E: REM save the current environment for release + Q=E:GOSUB PUSH_Q: REM save the current environment for release REM create new environ using env stored with function C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS @@ -292,8 +256,9 @@ SUB EVAL REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) + GOSUB POP_Q:AY=Q GOSUB PEEK_Q_2 - IF E4<>Q THEN AY=E4:GOSUB RELEASE + IF AY<>Q THEN GOSUB RELEASE REM claim the AST before releasing the list containing it A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 @@ -355,7 +320,7 @@ RE: REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=0:R2=0 + R1=-1:R2=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE @@ -365,13 +330,11 @@ SUB REP IF ER<>-2 THEN GOTO REP_DONE A=R:GOSUB MAL_PRINT - RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2<>0 THEN AY=R2:GOSUB RELEASE - IF R1<>0 THEN AY=R1:GOSUB RELEASE - R$=RT$ + AY=R2:GOSUB RELEASE + AY=R1:GOSUB RELEASE END SUB REM MAIN program @@ -381,7 +344,7 @@ MAIN: LV=0 REM create repl_env - C=-1:GOSUB ENV_NEW:D=R + C=0:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -395,6 +358,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser IF EZ=1 THEN GOTO QUIT + IF R$="" THEN GOTO REPL_LOOP A$=R$:CALL REP: REM call REP @@ -403,7 +367,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY_SMALL END PRINT_ERROR: diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index b2b6ad44dd..61d94cb5b5 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -22,14 +22,12 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - GOSUB DEREF_A - T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R=A:GOSUB DEREF_R + R=A Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN @@ -39,77 +37,50 @@ SUB EVAL_AST GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry (T already set above) - L=0:N=0:GOSUB ALLOC - - REM push type of sequence - Q=T:GOSUB PUSH_Q - REM push sequence index - Q=0:GOSUB PUSH_Q - REM push future return value (new sequence) - GOSUB PUSH_R - REM push previous new sequence entry - GOSUB PUSH_R + REM setup the stack for the loop + GOSUB MAP_LOOP_START EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element - Q=6:GOSUB PEEK_Q_Q + REM The EVAL_DO call to EVAL_AST must be call #2 for EVAL_AST to + REM return early and for TCO to work + Q=5:GOSUB PEEK_Q_Q IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE - REM if hashmap, skip eval of even entries (keys) - Q=3:GOSUB PEEK_Q_Q:T=Q - REM get and update index - GOSUB PEEK_Q_2 - Q=Q+1:GOSUB PUT_Q_2 - IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF - GOTO EVAL_AST_DO_EVAL - - EVAL_AST_DO_REF: - R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value - GOTO EVAL_AST_ADD_VALUE - - EVAL_AST_DO_EVAL: - REM call EVAL for each entry - A=A+1:CALL EVAL - A=A-1 - GOSUB DEREF_R: REM deref to target of evaluated entry - - EVAL_AST_ADD_VALUE: + REM call EVAL for each entry + GOSUB PUSH_A + IF T<>8 THEN GOSUB VAL_A + IF T=8 THEN A=Z%(A+1,1) + Q=T:GOSUB PUSH_Q: REM push/save type + CALL EVAL + GOSUB POP_Q:T=Q: REM pop/restore type + GOSUB POP_A - REM update previous value pointer to evaluated entry - GOSUB PEEK_Q - Z%(Q+1,1)=R + REM if error, release the unattached element + REM TODO: is R=0 correct? + IF ER<>-2 THEN AY=R:GOSUB RELEASE:R=0:GOTO EVAL_AST_SEQ_LOOP_DONE - IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM for hash-maps, copy the key (inc ref since we are going to + REM release it below) + IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 - REM allocate the next entry - REM same new sequence entry type - Q=3:GOSUB PEEK_Q_Q:T=Q - L=0:N=0:GOSUB ALLOC + REM value evaluated above + N=R - REM update previous sequence entry value to point to new entry - GOSUB PEEK_Q - Z%(Q,1)=R - REM update previous ptr to current entry - Q=R:GOSUB PUT_Q + REM update the return sequence structure + REM release N (and M if T=8) since seq takes full ownership + C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - GOSUB PEEK_Q_1 - REM if no error, get return value (new seq) - IF ER=-2 THEN R=Q - REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE - - REM pop previous, return, index and type - GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q + REM cleanup stack and get return value + GOSUB MAP_LOOP_DONE GOTO EVAL_AST_RETURN EVAL_AST_RETURN: @@ -135,8 +106,6 @@ SUB EVAL REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" - GOSUB DEREF_A - GOSUB LIST_Q IF R THEN GOTO APPLY_LIST REM ELSE @@ -147,8 +116,7 @@ SUB EVAL GOSUB EMPTY_Q IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0=A+1 - R=A0:GOSUB DEREF_R:A0=R + A0=Z%(A+1,1) REM get symbol in A$ IF (Z%(A0,0)AND 31)<>5 THEN A$="" @@ -162,14 +130,14 @@ SUB EVAL GOTO EVAL_INVOKE EVAL_GET_A3: - A3=Z%(Z%(Z%(A,1),1),1)+1 - R=A3:GOSUB DEREF_R:A3=R + R=Z%(Z%(Z%(A,1),1),1) + GOSUB VAL_R:A3=R EVAL_GET_A2: - A2=Z%(Z%(A,1),1)+1 - R=A2:GOSUB DEREF_R:A2=R + R=Z%(Z%(A,1),1) + GOSUB VAL_R:A2=R EVAL_GET_A1: - A1=Z%(A,1)+1 - R=A1:GOSUB DEREF_R:A1=R + R=Z%(A,1) + GOSUB VAL_R:A1=R RETURN EVAL_DEF: @@ -201,13 +169,13 @@ SUB EVAL Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:CALL EVAL + A=Z%(A1,1):GOSUB VAL_A:CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:C=R:GOSUB ENV_SET + K=Z%(A1+1,1):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -215,11 +183,11 @@ SUB EVAL GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - GOSUB POP_Q:E4=Q: REM pop previous env + GOSUB POP_Q:AY=Q: REM pop previous env REM release previous environment if not the current EVAL env GOSUB PEEK_Q_2 - IF E4<>Q THEN AY=E4:GOSUB RELEASE + IF AY<>Q THEN GOSUB RELEASE GOSUB POP_Q:A2=Q: REM pop A2 A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -228,6 +196,8 @@ SUB EVAL A=Z%(A,1): REM rest GOSUB PUSH_A: REM push/save A + REM this must be EVAL_AST call #2 for EVAL_AST to return early + REM and for TCO to work CALL EVAL_AST REM cleanup @@ -258,7 +228,7 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:GOTO EVAL_RETURN + IF R<4 THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -276,10 +246,8 @@ SUB EVAL REM push f/args for release after call GOSUB PUSH_R - F=R+1 - AR=Z%(R,1): REM rest - R=F:GOSUB DEREF_R:F=R + GOSUB VAL_R:F=R REM if metadata, get the actual object IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) @@ -304,7 +272,7 @@ SUB EVAL GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4=E: REM save the current environment for release + Q=E:GOSUB PUSH_Q: REM save the current environment for release REM create new environ using env stored with function C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS @@ -312,8 +280,9 @@ SUB EVAL REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) + GOSUB POP_Q:AY=Q GOSUB PEEK_Q_2 - IF E4<>Q THEN AY=E4:GOSUB RELEASE + IF AY<>Q THEN GOSUB RELEASE REM claim the AST before releasing the list containing it A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 @@ -375,7 +344,7 @@ RE: REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=0:R2=0 + R1=-1:R2=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE @@ -385,13 +354,11 @@ SUB REP IF ER<>-2 THEN GOTO REP_DONE A=R:GOSUB MAL_PRINT - RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2<>0 THEN AY=R2:GOSUB RELEASE - IF R1<>0 THEN AY=R1:GOSUB RELEASE - R$=RT$ + AY=R2:GOSUB RELEASE + AY=R1:GOSUB RELEASE END SUB REM MAIN program @@ -401,7 +368,7 @@ MAIN: LV=0 REM create repl_env - C=-1:GOSUB ENV_NEW:D=R + C=0:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -415,6 +382,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser IF EZ=1 THEN GOTO QUIT + IF R$="" THEN GOTO REPL_LOOP A$=R$:CALL REP: REM call REP @@ -423,7 +391,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY_SMALL END PRINT_ERROR: diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index f30c6d96f9..8c76518435 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -22,14 +22,12 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - GOSUB DEREF_A - T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R=A:GOSUB DEREF_R + R=A Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN @@ -39,77 +37,50 @@ SUB EVAL_AST GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry (T already set above) - L=0:N=0:GOSUB ALLOC - - REM push type of sequence - Q=T:GOSUB PUSH_Q - REM push sequence index - Q=0:GOSUB PUSH_Q - REM push future return value (new sequence) - GOSUB PUSH_R - REM push previous new sequence entry - GOSUB PUSH_R + REM setup the stack for the loop + GOSUB MAP_LOOP_START EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element - Q=6:GOSUB PEEK_Q_Q + REM The EVAL_DO call to EVAL_AST must be call #2 for EVAL_AST to + REM return early and for TCO to work + Q=5:GOSUB PEEK_Q_Q IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE - REM if hashmap, skip eval of even entries (keys) - Q=3:GOSUB PEEK_Q_Q:T=Q - REM get and update index - GOSUB PEEK_Q_2 - Q=Q+1:GOSUB PUT_Q_2 - IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF - GOTO EVAL_AST_DO_EVAL - - EVAL_AST_DO_REF: - R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value - GOTO EVAL_AST_ADD_VALUE - - EVAL_AST_DO_EVAL: - REM call EVAL for each entry - A=A+1:CALL EVAL - A=A-1 - GOSUB DEREF_R: REM deref to target of evaluated entry - - EVAL_AST_ADD_VALUE: + REM call EVAL for each entry + GOSUB PUSH_A + IF T<>8 THEN GOSUB VAL_A + IF T=8 THEN A=Z%(A+1,1) + Q=T:GOSUB PUSH_Q: REM push/save type + CALL EVAL + GOSUB POP_Q:T=Q: REM pop/restore type + GOSUB POP_A - REM update previous value pointer to evaluated entry - GOSUB PEEK_Q - Z%(Q+1,1)=R + REM if error, release the unattached element + REM TODO: is R=0 correct? + IF ER<>-2 THEN AY=R:GOSUB RELEASE:R=0:GOTO EVAL_AST_SEQ_LOOP_DONE - IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM for hash-maps, copy the key (inc ref since we are going to + REM release it below) + IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 - REM allocate the next entry - REM same new sequence entry type - Q=3:GOSUB PEEK_Q_Q:T=Q - L=0:N=0:GOSUB ALLOC + REM value evaluated above + N=R - REM update previous sequence entry value to point to new entry - GOSUB PEEK_Q - Z%(Q,1)=R - REM update previous ptr to current entry - Q=R:GOSUB PUT_Q + REM update the return sequence structure + REM release N (and M if T=8) since seq takes full ownership + C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - GOSUB PEEK_Q_1 - REM if no error, get return value (new seq) - IF ER=-2 THEN R=Q - REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE - - REM pop previous, return, index and type - GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q + REM cleanup stack and get return value + GOSUB MAP_LOOP_DONE GOTO EVAL_AST_RETURN EVAL_AST_RETURN: @@ -135,8 +106,6 @@ SUB EVAL REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" - GOSUB DEREF_A - GOSUB LIST_Q IF R THEN GOTO APPLY_LIST REM ELSE @@ -147,8 +116,7 @@ SUB EVAL GOSUB EMPTY_Q IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0=A+1 - R=A0:GOSUB DEREF_R:A0=R + A0=Z%(A+1,1) REM get symbol in A$ IF (Z%(A0,0)AND 31)<>5 THEN A$="" @@ -162,14 +130,14 @@ SUB EVAL GOTO EVAL_INVOKE EVAL_GET_A3: - A3=Z%(Z%(Z%(A,1),1),1)+1 - R=A3:GOSUB DEREF_R:A3=R + R=Z%(Z%(Z%(A,1),1),1) + GOSUB VAL_R:A3=R EVAL_GET_A2: - A2=Z%(Z%(A,1),1)+1 - R=A2:GOSUB DEREF_R:A2=R + R=Z%(Z%(A,1),1) + GOSUB VAL_R:A2=R EVAL_GET_A1: - A1=Z%(A,1)+1 - R=A1:GOSUB DEREF_R:A1=R + R=Z%(A,1) + GOSUB VAL_R:A1=R RETURN EVAL_DEF: @@ -201,13 +169,13 @@ SUB EVAL Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:CALL EVAL + A=Z%(A1,1):GOSUB VAL_A:CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:C=R:GOSUB ENV_SET + K=Z%(A1+1,1):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -215,11 +183,11 @@ SUB EVAL GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - GOSUB POP_Q:E4=Q: REM pop previous env + GOSUB POP_Q:AY=Q: REM pop previous env REM release previous environment if not the current EVAL env GOSUB PEEK_Q_2 - IF E4<>Q THEN AY=E4:GOSUB RELEASE + IF AY<>Q THEN GOSUB RELEASE GOSUB POP_Q:A2=Q: REM pop A2 A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -228,6 +196,8 @@ SUB EVAL A=Z%(A,1): REM rest GOSUB PUSH_A: REM push/save A + REM this must be EVAL_AST call #2 for EVAL_AST to return early + REM and for TCO to work CALL EVAL_AST REM cleanup @@ -258,7 +228,7 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:GOTO EVAL_RETURN + IF R<4 THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -276,10 +246,8 @@ SUB EVAL REM push f/args for release after call GOSUB PUSH_R - F=R+1 - AR=Z%(R,1): REM rest - R=F:GOSUB DEREF_R:F=R + GOSUB VAL_R:F=R REM if metadata, get the actual object IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) @@ -304,7 +272,7 @@ SUB EVAL GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4=E: REM save the current environment for release + Q=E:GOSUB PUSH_Q: REM save the current environment for release REM create new environ using env stored with function C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS @@ -312,8 +280,9 @@ SUB EVAL REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) + GOSUB POP_Q:AY=Q GOSUB PEEK_Q_2 - IF E4<>Q THEN AY=E4:GOSUB RELEASE + IF AY<>Q THEN GOSUB RELEASE REM claim the AST before releasing the list containing it A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 @@ -375,7 +344,7 @@ RE: REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=0:R2=0 + R1=-1:R2=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE @@ -385,13 +354,11 @@ SUB REP IF ER<>-2 THEN GOTO REP_DONE A=R:GOSUB MAL_PRINT - RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2<>0 THEN AY=R2:GOSUB RELEASE - IF R1<>0 THEN AY=R1:GOSUB RELEASE - R$=RT$ + AY=R2:GOSUB RELEASE + AY=R1:GOSUB RELEASE END SUB REM MAIN program @@ -401,7 +368,7 @@ MAIN: LV=0 REM create repl_env - C=-1:GOSUB ENV_NEW:D=R + C=0:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -442,6 +409,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser IF EZ=1 THEN GOTO QUIT + IF R$="" THEN GOTO REPL_LOOP A$=R$:CALL REP: REM call REP @@ -450,7 +418,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY_SMALL END PRINT_ERROR: diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index 477ee6e60d..002b40e205 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -30,11 +30,11 @@ SUB QUASIQUOTE GOTO QQ_DONE QQ_UNQUOTE: - R=A+1:GOSUB DEREF_R + R=A:GOSUB VAL_R IF (Z%(R,0)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] - R=Z%(A,1)+1:GOSUB DEREF_R + R=Z%(A,1):GOSUB VAL_R Z%(R,0)=Z%(R,0)+32 GOTO QQ_DONE @@ -47,18 +47,18 @@ SUB QUASIQUOTE GOSUB POP_A REM set A to ast[0] for last two cases - A=A+1:GOSUB DEREF_A + GOSUB VAL_A REM pair? IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_DEFAULT IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT - B=A+1:GOSUB DEREF_B + B=A:GOSUB VAL_B IF (Z%(B,0)AND 31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1)+1:GOSUB DEREF_B:B=B + B=Z%(A,1):GOSUB VAL_B B$="concat":T=5:GOSUB STRING:C=R A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership @@ -93,14 +93,12 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - GOSUB DEREF_A - T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R=A:GOSUB DEREF_R + R=A Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN @@ -110,77 +108,50 @@ SUB EVAL_AST GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry (T already set above) - L=0:N=0:GOSUB ALLOC - - REM push type of sequence - Q=T:GOSUB PUSH_Q - REM push sequence index - Q=0:GOSUB PUSH_Q - REM push future return value (new sequence) - GOSUB PUSH_R - REM push previous new sequence entry - GOSUB PUSH_R + REM setup the stack for the loop + GOSUB MAP_LOOP_START EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element - Q=6:GOSUB PEEK_Q_Q + REM The EVAL_DO call to EVAL_AST must be call #2 for EVAL_AST to + REM return early and for TCO to work + Q=5:GOSUB PEEK_Q_Q IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE - REM if hashmap, skip eval of even entries (keys) - Q=3:GOSUB PEEK_Q_Q:T=Q - REM get and update index - GOSUB PEEK_Q_2 - Q=Q+1:GOSUB PUT_Q_2 - IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF - GOTO EVAL_AST_DO_EVAL - - EVAL_AST_DO_REF: - R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value - GOTO EVAL_AST_ADD_VALUE - - EVAL_AST_DO_EVAL: - REM call EVAL for each entry - A=A+1:CALL EVAL - A=A-1 - GOSUB DEREF_R: REM deref to target of evaluated entry - - EVAL_AST_ADD_VALUE: + REM call EVAL for each entry + GOSUB PUSH_A + IF T<>8 THEN GOSUB VAL_A + IF T=8 THEN A=Z%(A+1,1) + Q=T:GOSUB PUSH_Q: REM push/save type + CALL EVAL + GOSUB POP_Q:T=Q: REM pop/restore type + GOSUB POP_A - REM update previous value pointer to evaluated entry - GOSUB PEEK_Q - Z%(Q+1,1)=R + REM if error, release the unattached element + REM TODO: is R=0 correct? + IF ER<>-2 THEN AY=R:GOSUB RELEASE:R=0:GOTO EVAL_AST_SEQ_LOOP_DONE - IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM for hash-maps, copy the key (inc ref since we are going to + REM release it below) + IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 - REM allocate the next entry - REM same new sequence entry type - Q=3:GOSUB PEEK_Q_Q:T=Q - L=0:N=0:GOSUB ALLOC + REM value evaluated above + N=R - REM update previous sequence entry value to point to new entry - GOSUB PEEK_Q - Z%(Q,1)=R - REM update previous ptr to current entry - Q=R:GOSUB PUT_Q + REM update the return sequence structure + REM release N (and M if T=8) since seq takes full ownership + C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - GOSUB PEEK_Q_1 - REM if no error, get return value (new seq) - IF ER=-2 THEN R=Q - REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE - - REM pop previous, return, index and type - GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q + REM cleanup stack and get return value + GOSUB MAP_LOOP_DONE GOTO EVAL_AST_RETURN EVAL_AST_RETURN: @@ -206,8 +177,6 @@ SUB EVAL REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" - GOSUB DEREF_A - GOSUB LIST_Q IF R THEN GOTO APPLY_LIST REM ELSE @@ -218,8 +187,7 @@ SUB EVAL GOSUB EMPTY_Q IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0=A+1 - R=A0:GOSUB DEREF_R:A0=R + A0=Z%(A+1,1) REM get symbol in A$ IF (Z%(A0,0)AND 31)<>5 THEN A$="" @@ -235,14 +203,14 @@ SUB EVAL GOTO EVAL_INVOKE EVAL_GET_A3: - A3=Z%(Z%(Z%(A,1),1),1)+1 - R=A3:GOSUB DEREF_R:A3=R + R=Z%(Z%(Z%(A,1),1),1) + GOSUB VAL_R:A3=R EVAL_GET_A2: - A2=Z%(Z%(A,1),1)+1 - R=A2:GOSUB DEREF_R:A2=R + R=Z%(Z%(A,1),1) + GOSUB VAL_R:A2=R EVAL_GET_A1: - A1=Z%(A,1)+1 - R=A1:GOSUB DEREF_R:A1=R + R=Z%(A,1) + GOSUB VAL_R:A1=R RETURN EVAL_DEF: @@ -274,13 +242,13 @@ SUB EVAL Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:CALL EVAL + A=Z%(A1,1):GOSUB VAL_A:CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:C=R:GOSUB ENV_SET + K=Z%(A1+1,1):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -288,11 +256,11 @@ SUB EVAL GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - GOSUB POP_Q:E4=Q: REM pop previous env + GOSUB POP_Q:AY=Q: REM pop previous env REM release previous environment if not the current EVAL env GOSUB PEEK_Q_2 - IF E4<>Q THEN AY=E4:GOSUB RELEASE + IF AY<>Q THEN GOSUB RELEASE GOSUB POP_Q:A2=Q: REM pop A2 A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -301,6 +269,8 @@ SUB EVAL A=Z%(A,1): REM rest GOSUB PUSH_A: REM push/save A + REM this must be EVAL_AST call #2 for EVAL_AST to return early + REM and for TCO to work CALL EVAL_AST REM cleanup @@ -317,12 +287,12 @@ SUB EVAL GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_QUOTE: - R=Z%(A,1)+1:GOSUB DEREF_R + R=Z%(A,1):GOSUB VAL_R Z%(R,0)=Z%(R,0)+32 GOTO EVAL_RETURN EVAL_QUASIQUOTE: - R=Z%(A,1)+1:GOSUB DEREF_R + R=Z%(A,1):GOSUB VAL_R A=R:CALL QUASIQUOTE A=R REM add quasiquote result to pending release queue to free when @@ -346,7 +316,7 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:GOTO EVAL_RETURN + IF R<4 THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -364,10 +334,8 @@ SUB EVAL REM push f/args for release after call GOSUB PUSH_R - F=R+1 - AR=Z%(R,1): REM rest - R=F:GOSUB DEREF_R:F=R + GOSUB VAL_R:F=R REM if metadata, get the actual object IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) @@ -392,7 +360,7 @@ SUB EVAL GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4=E: REM save the current environment for release + Q=E:GOSUB PUSH_Q: REM save the current environment for release REM create new environ using env stored with function C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS @@ -400,8 +368,9 @@ SUB EVAL REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) + GOSUB POP_Q:AY=Q GOSUB PEEK_Q_2 - IF E4<>Q THEN AY=E4:GOSUB RELEASE + IF AY<>Q THEN GOSUB RELEASE REM claim the AST before releasing the list containing it A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 @@ -463,7 +432,7 @@ RE: REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=0:R2=0 + R1=-1:R2=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE @@ -473,13 +442,11 @@ SUB REP IF ER<>-2 THEN GOTO REP_DONE A=R:GOSUB MAL_PRINT - RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2<>0 THEN AY=R2:GOSUB RELEASE - IF R1<>0 THEN AY=R1:GOSUB RELEASE - R$=RT$ + AY=R2:GOSUB RELEASE + AY=R1:GOSUB RELEASE END SUB REM MAIN program @@ -489,7 +456,7 @@ MAIN: LV=0 REM create repl_env - C=-1:GOSUB ENV_NEW:D=R + C=0:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -530,6 +497,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser IF EZ=1 THEN GOTO QUIT + IF R$="" THEN GOTO REPL_LOOP A$=R$:CALL REP: REM call REP @@ -538,7 +506,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY_SMALL END PRINT_ERROR: diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index cbfe05f44f..5857913eb5 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -30,11 +30,11 @@ SUB QUASIQUOTE GOTO QQ_DONE QQ_UNQUOTE: - R=A+1:GOSUB DEREF_R + R=A:GOSUB VAL_R IF (Z%(R,0)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] - R=Z%(A,1)+1:GOSUB DEREF_R + R=Z%(A,1):GOSUB VAL_R Z%(R,0)=Z%(R,0)+32 GOTO QQ_DONE @@ -47,18 +47,18 @@ SUB QUASIQUOTE GOSUB POP_A REM set A to ast[0] for last two cases - A=A+1:GOSUB DEREF_A + GOSUB VAL_A REM pair? IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_DEFAULT IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT - B=A+1:GOSUB DEREF_B + B=A:GOSUB VAL_B IF (Z%(B,0)AND 31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1)+1:GOSUB DEREF_B:B=B + B=Z%(A,1):GOSUB VAL_B B$="concat":T=5:GOSUB STRING:C=R A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership @@ -93,13 +93,13 @@ SUB MACROEXPAND IF (Z%(A,0)AND 31)<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? IF Z%(A,1)=0 THEN GOTO MACROEXPAND_DONE - B=A+1:GOSUB DEREF_B + B=A:GOSUB VAL_B REM symbol? in first position IF (Z%(B,0)AND 31)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? K=B:CALL ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE - B=R4:GOSUB DEREF_B + B=R4 REM macro? IF (Z%(B,0)AND 31)<>11 THEN GOTO MACROEXPAND_DONE @@ -126,14 +126,12 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - GOSUB DEREF_A - T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R=A:GOSUB DEREF_R + R=A Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN @@ -143,77 +141,50 @@ SUB EVAL_AST GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry (T already set above) - L=0:N=0:GOSUB ALLOC - - REM push type of sequence - Q=T:GOSUB PUSH_Q - REM push sequence index - Q=0:GOSUB PUSH_Q - REM push future return value (new sequence) - GOSUB PUSH_R - REM push previous new sequence entry - GOSUB PUSH_R + REM setup the stack for the loop + GOSUB MAP_LOOP_START EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element - Q=6:GOSUB PEEK_Q_Q + REM The EVAL_DO call to EVAL_AST must be call #2 for EVAL_AST to + REM return early and for TCO to work + Q=5:GOSUB PEEK_Q_Q IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE - REM if hashmap, skip eval of even entries (keys) - Q=3:GOSUB PEEK_Q_Q:T=Q - REM get and update index - GOSUB PEEK_Q_2 - Q=Q+1:GOSUB PUT_Q_2 - IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF - GOTO EVAL_AST_DO_EVAL - - EVAL_AST_DO_REF: - R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value - GOTO EVAL_AST_ADD_VALUE - - EVAL_AST_DO_EVAL: - REM call EVAL for each entry - A=A+1:CALL EVAL - A=A-1 - GOSUB DEREF_R: REM deref to target of evaluated entry - - EVAL_AST_ADD_VALUE: + REM call EVAL for each entry + GOSUB PUSH_A + IF T<>8 THEN GOSUB VAL_A + IF T=8 THEN A=Z%(A+1,1) + Q=T:GOSUB PUSH_Q: REM push/save type + CALL EVAL + GOSUB POP_Q:T=Q: REM pop/restore type + GOSUB POP_A - REM update previous value pointer to evaluated entry - GOSUB PEEK_Q - Z%(Q+1,1)=R + REM if error, release the unattached element + REM TODO: is R=0 correct? + IF ER<>-2 THEN AY=R:GOSUB RELEASE:R=0:GOTO EVAL_AST_SEQ_LOOP_DONE - IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM for hash-maps, copy the key (inc ref since we are going to + REM release it below) + IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 - REM allocate the next entry - REM same new sequence entry type - Q=3:GOSUB PEEK_Q_Q:T=Q - L=0:N=0:GOSUB ALLOC + REM value evaluated above + N=R - REM update previous sequence entry value to point to new entry - GOSUB PEEK_Q - Z%(Q,1)=R - REM update previous ptr to current entry - Q=R:GOSUB PUT_Q + REM update the return sequence structure + REM release N (and M if T=8) since seq takes full ownership + C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - GOSUB PEEK_Q_1 - REM if no error, get return value (new seq) - IF ER=-2 THEN R=Q - REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE - - REM pop previous, return, index and type - GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q + REM cleanup stack and get return value + GOSUB MAP_LOOP_DONE GOTO EVAL_AST_RETURN EVAL_AST_RETURN: @@ -239,8 +210,6 @@ SUB EVAL REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" - GOSUB DEREF_A - GOSUB LIST_Q IF R THEN GOTO APPLY_LIST EVAL_NOT_LIST: @@ -257,8 +226,7 @@ SUB EVAL GOSUB EMPTY_Q IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0=A+1 - R=A0:GOSUB DEREF_R:A0=R + A0=Z%(A+1,1) REM get symbol in A$ IF (Z%(A0,0)AND 31)<>5 THEN A$="" @@ -276,14 +244,14 @@ SUB EVAL GOTO EVAL_INVOKE EVAL_GET_A3: - A3=Z%(Z%(Z%(A,1),1),1)+1 - R=A3:GOSUB DEREF_R:A3=R + R=Z%(Z%(Z%(A,1),1),1) + GOSUB VAL_R:A3=R EVAL_GET_A2: - A2=Z%(Z%(A,1),1)+1 - R=A2:GOSUB DEREF_R:A2=R + R=Z%(Z%(A,1),1) + GOSUB VAL_R:A2=R EVAL_GET_A1: - A1=Z%(A,1)+1 - R=A1:GOSUB DEREF_R:A1=R + R=Z%(A,1) + GOSUB VAL_R:A1=R RETURN EVAL_DEF: @@ -315,13 +283,13 @@ SUB EVAL Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:CALL EVAL + A=Z%(A1,1):GOSUB VAL_A:CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:C=R:GOSUB ENV_SET + K=Z%(A1+1,1):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -329,11 +297,11 @@ SUB EVAL GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - GOSUB POP_Q:E4=Q: REM pop previous env + GOSUB POP_Q:AY=Q: REM pop previous env REM release previous environment if not the current EVAL env GOSUB PEEK_Q_2 - IF E4<>Q THEN AY=E4:GOSUB RELEASE + IF AY<>Q THEN GOSUB RELEASE GOSUB POP_Q:A2=Q: REM pop A2 A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -342,6 +310,8 @@ SUB EVAL A=Z%(A,1): REM rest GOSUB PUSH_A: REM push/save A + REM this must be EVAL_AST call #2 for EVAL_AST to return early + REM and for TCO to work CALL EVAL_AST REM cleanup @@ -358,12 +328,12 @@ SUB EVAL GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_QUOTE: - R=Z%(A,1)+1:GOSUB DEREF_R + R=Z%(A,1):GOSUB VAL_R Z%(R,0)=Z%(R,0)+32 GOTO EVAL_RETURN EVAL_QUASIQUOTE: - R=Z%(A,1)+1:GOSUB DEREF_R + R=Z%(A,1):GOSUB VAL_R A=R:CALL QUASIQUOTE A=R REM add quasiquote result to pending release queue to free when @@ -389,7 +359,7 @@ SUB EVAL EVAL_MACROEXPAND: REM PRINT "macroexpand" - R=Z%(A,1)+1:GOSUB DEREF_R + R=Z%(A,1):GOSUB VAL_R A=R:CALL MACROEXPAND R=A @@ -412,7 +382,7 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:GOTO EVAL_RETURN + IF R<4 THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -430,10 +400,8 @@ SUB EVAL REM push f/args for release after call GOSUB PUSH_R - F=R+1 - AR=Z%(R,1): REM rest - R=F:GOSUB DEREF_R:F=R + GOSUB VAL_R:F=R REM if metadata, get the actual object IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) @@ -458,7 +426,7 @@ SUB EVAL GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4=E: REM save the current environment for release + Q=E:GOSUB PUSH_Q: REM save the current environment for release REM create new environ using env stored with function C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS @@ -466,8 +434,9 @@ SUB EVAL REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) + GOSUB POP_Q:AY=Q GOSUB PEEK_Q_2 - IF E4<>Q THEN AY=E4:GOSUB RELEASE + IF AY<>Q THEN GOSUB RELEASE REM claim the AST before releasing the list containing it A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 @@ -529,7 +498,7 @@ RE: REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=0:R2=0 + R1=-1:R2=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE @@ -539,13 +508,11 @@ SUB REP IF ER<>-2 THEN GOTO REP_DONE A=R:GOSUB MAL_PRINT - RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2<>0 THEN AY=R2:GOSUB RELEASE - IF R1<>0 THEN AY=R1:GOSUB RELEASE - R$=RT$ + AY=R2:GOSUB RELEASE + AY=R1:GOSUB RELEASE END SUB REM MAIN program @@ -555,7 +522,7 @@ MAIN: LV=0 REM create repl_env - C=-1:GOSUB ENV_NEW:D=R + C=0:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -605,6 +572,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser IF EZ=1 THEN GOTO QUIT + IF R$="" THEN GOTO REPL_LOOP A$=R$:CALL REP: REM call REP @@ -613,7 +581,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY_SMALL END PRINT_ERROR: diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index 233c8c0341..ea12f3f797 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -30,11 +30,11 @@ SUB QUASIQUOTE GOTO QQ_DONE QQ_UNQUOTE: - R=A+1:GOSUB DEREF_R + R=A:GOSUB VAL_R IF (Z%(R,0)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] - R=Z%(A,1)+1:GOSUB DEREF_R + R=Z%(A,1):GOSUB VAL_R Z%(R,0)=Z%(R,0)+32 GOTO QQ_DONE @@ -47,18 +47,18 @@ SUB QUASIQUOTE GOSUB POP_A REM set A to ast[0] for last two cases - A=A+1:GOSUB DEREF_A + GOSUB VAL_A REM pair? IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_DEFAULT IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT - B=A+1:GOSUB DEREF_B + B=A:GOSUB VAL_B IF (Z%(B,0)AND 31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1)+1:GOSUB DEREF_B:B=B + B=Z%(A,1):GOSUB VAL_B B$="concat":T=5:GOSUB STRING:C=R A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership @@ -93,13 +93,13 @@ SUB MACROEXPAND IF (Z%(A,0)AND 31)<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? IF Z%(A,1)=0 THEN GOTO MACROEXPAND_DONE - B=A+1:GOSUB DEREF_B + B=A:GOSUB VAL_B REM symbol? in first position IF (Z%(B,0)AND 31)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? K=B:CALL ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE - B=R4:GOSUB DEREF_B + B=R4 REM macro? IF (Z%(B,0)AND 31)<>11 THEN GOTO MACROEXPAND_DONE @@ -126,14 +126,12 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - GOSUB DEREF_A - T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R=A:GOSUB DEREF_R + R=A Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN @@ -143,77 +141,50 @@ SUB EVAL_AST GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry (T already set above) - L=0:N=0:GOSUB ALLOC - - REM push type of sequence - Q=T:GOSUB PUSH_Q - REM push sequence index - Q=0:GOSUB PUSH_Q - REM push future return value (new sequence) - GOSUB PUSH_R - REM push previous new sequence entry - GOSUB PUSH_R + REM setup the stack for the loop + GOSUB MAP_LOOP_START EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element - Q=6:GOSUB PEEK_Q_Q + REM The EVAL_DO call to EVAL_AST must be call #2 for EVAL_AST to + REM return early and for TCO to work + Q=5:GOSUB PEEK_Q_Q IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE - REM if hashmap, skip eval of even entries (keys) - Q=3:GOSUB PEEK_Q_Q:T=Q - REM get and update index - GOSUB PEEK_Q_2 - Q=Q+1:GOSUB PUT_Q_2 - IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF - GOTO EVAL_AST_DO_EVAL - - EVAL_AST_DO_REF: - R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value - GOTO EVAL_AST_ADD_VALUE - - EVAL_AST_DO_EVAL: - REM call EVAL for each entry - A=A+1:CALL EVAL - A=A-1 - GOSUB DEREF_R: REM deref to target of evaluated entry - - EVAL_AST_ADD_VALUE: + REM call EVAL for each entry + GOSUB PUSH_A + IF T<>8 THEN GOSUB VAL_A + IF T=8 THEN A=Z%(A+1,1) + Q=T:GOSUB PUSH_Q: REM push/save type + CALL EVAL + GOSUB POP_Q:T=Q: REM pop/restore type + GOSUB POP_A - REM update previous value pointer to evaluated entry - GOSUB PEEK_Q - Z%(Q+1,1)=R + REM if error, release the unattached element + REM TODO: is R=0 correct? + IF ER<>-2 THEN AY=R:GOSUB RELEASE:R=0:GOTO EVAL_AST_SEQ_LOOP_DONE - IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM for hash-maps, copy the key (inc ref since we are going to + REM release it below) + IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 - REM allocate the next entry - REM same new sequence entry type - Q=3:GOSUB PEEK_Q_Q:T=Q - L=0:N=0:GOSUB ALLOC + REM value evaluated above + N=R - REM update previous sequence entry value to point to new entry - GOSUB PEEK_Q - Z%(Q,1)=R - REM update previous ptr to current entry - Q=R:GOSUB PUT_Q + REM update the return sequence structure + REM release N (and M if T=8) since seq takes full ownership + C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - GOSUB PEEK_Q_1 - REM if no error, get return value (new seq) - IF ER=-2 THEN R=Q - REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE - - REM pop previous, return, index and type - GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q + REM cleanup stack and get return value + GOSUB MAP_LOOP_DONE GOTO EVAL_AST_RETURN EVAL_AST_RETURN: @@ -239,8 +210,6 @@ SUB EVAL REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" - GOSUB DEREF_A - GOSUB LIST_Q IF R THEN GOTO APPLY_LIST EVAL_NOT_LIST: @@ -257,8 +226,7 @@ SUB EVAL GOSUB EMPTY_Q IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0=A+1 - R=A0:GOSUB DEREF_R:A0=R + A0=Z%(A+1,1) REM get symbol in A$ IF (Z%(A0,0)AND 31)<>5 THEN A$="" @@ -277,14 +245,14 @@ SUB EVAL GOTO EVAL_INVOKE EVAL_GET_A3: - A3=Z%(Z%(Z%(A,1),1),1)+1 - R=A3:GOSUB DEREF_R:A3=R + R=Z%(Z%(Z%(A,1),1),1) + GOSUB VAL_R:A3=R EVAL_GET_A2: - A2=Z%(Z%(A,1),1)+1 - R=A2:GOSUB DEREF_R:A2=R + R=Z%(Z%(A,1),1) + GOSUB VAL_R:A2=R EVAL_GET_A1: - A1=Z%(A,1)+1 - R=A1:GOSUB DEREF_R:A1=R + R=Z%(A,1) + GOSUB VAL_R:A1=R RETURN EVAL_DEF: @@ -316,13 +284,13 @@ SUB EVAL Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:CALL EVAL + A=Z%(A1,1):GOSUB VAL_A:CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:C=R:GOSUB ENV_SET + K=Z%(A1+1,1):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -330,11 +298,11 @@ SUB EVAL GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: - GOSUB POP_Q:E4=Q: REM pop previous env + GOSUB POP_Q:AY=Q: REM pop previous env REM release previous environment if not the current EVAL env GOSUB PEEK_Q_2 - IF E4<>Q THEN AY=E4:GOSUB RELEASE + IF AY<>Q THEN GOSUB RELEASE GOSUB POP_Q:A2=Q: REM pop A2 A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -343,6 +311,8 @@ SUB EVAL A=Z%(A,1): REM rest GOSUB PUSH_A: REM push/save A + REM this must be EVAL_AST call #2 for EVAL_AST to return early + REM and for TCO to work CALL EVAL_AST REM cleanup @@ -359,12 +329,12 @@ SUB EVAL GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_QUOTE: - R=Z%(A,1)+1:GOSUB DEREF_R + R=Z%(A,1):GOSUB VAL_R Z%(R,0)=Z%(R,0)+32 GOTO EVAL_RETURN EVAL_QUASIQUOTE: - R=Z%(A,1)+1:GOSUB DEREF_R + R=Z%(A,1):GOSUB VAL_R A=R:CALL QUASIQUOTE A=R REM add quasiquote result to pending release queue to free when @@ -390,7 +360,7 @@ SUB EVAL EVAL_MACROEXPAND: REM PRINT "macroexpand" - R=Z%(A,1)+1:GOSUB DEREF_R + R=Z%(A,1):GOSUB VAL_R A=R:CALL MACROEXPAND R=A @@ -444,7 +414,7 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:GOTO EVAL_RETURN + IF R<4 THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -462,10 +432,8 @@ SUB EVAL REM push f/args for release after call GOSUB PUSH_R - F=R+1 - AR=Z%(R,1): REM rest - R=F:GOSUB DEREF_R:F=R + GOSUB VAL_R:F=R REM if metadata, get the actual object IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) @@ -490,7 +458,7 @@ SUB EVAL GOTO EVAL_RETURN EVAL_DO_MAL_FUNCTION: - E4=E: REM save the current environment for release + Q=E:GOSUB PUSH_Q: REM save the current environment for release REM create new environ using env stored with function C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS @@ -498,8 +466,9 @@ SUB EVAL REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and REM we no longer need to track it (since we are TCO recurring) + GOSUB POP_Q:AY=Q GOSUB PEEK_Q_2 - IF E4<>Q THEN AY=E4:GOSUB RELEASE + IF AY<>Q THEN GOSUB RELEASE REM claim the AST before releasing the list containing it A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 @@ -561,7 +530,7 @@ RE: REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=0:R2=0 + R1=-1:R2=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE @@ -571,13 +540,11 @@ SUB REP IF ER<>-2 THEN GOTO REP_DONE A=R:GOSUB MAL_PRINT - RT$=R$ REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2<>0 THEN AY=R2:GOSUB RELEASE - IF R1<>0 THEN AY=R1:GOSUB RELEASE - R$=RT$ + AY=R2:GOSUB RELEASE + AY=R1:GOSUB RELEASE END SUB REM MAIN program @@ -587,7 +554,7 @@ MAIN: LV=0 REM create repl_env - C=-1:GOSUB ENV_NEW:D=R + C=0:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -637,6 +604,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser IF EZ=1 THEN GOTO QUIT + IF R$="" THEN GOTO REPL_LOOP A$=R$:CALL REP: REM call REP @@ -645,7 +613,7 @@ MAIN: GOTO REPL_LOOP QUIT: - REM GOSUB PR_MEMORY_SUMMARY + REM GOSUB PR_MEMORY_SUMMARY_SMALL END PRINT_ERROR: diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index 7e0d0d2fc8..7df9f3b0d9 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -30,11 +30,11 @@ SUB QUASIQUOTE GOTO QQ_DONE QQ_UNQUOTE: - R=A+1:GOSUB DEREF_R + R=A:GOSUB VAL_R IF (Z%(R,0)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] - R=Z%(A,1)+1:GOSUB DEREF_R + R=Z%(A,1):GOSUB VAL_R Z%(R,0)=Z%(R,0)+32 GOTO QQ_DONE @@ -47,18 +47,18 @@ SUB QUASIQUOTE GOSUB POP_A REM set A to ast[0] for last two cases - A=A+1:GOSUB DEREF_A + GOSUB VAL_A REM pair? IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_DEFAULT IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT - B=A+1:GOSUB DEREF_B + B=A:GOSUB VAL_B IF (Z%(B,0)AND 31)<>5 THEN GOTO QQ_DEFAULT IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1)+1:GOSUB DEREF_B:B=B + B=Z%(A,1):GOSUB VAL_B B$="concat":T=5:GOSUB STRING:C=R A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership @@ -93,13 +93,13 @@ SUB MACROEXPAND IF (Z%(A,0)AND 31)<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? IF Z%(A,1)=0 THEN GOTO MACROEXPAND_DONE - B=A+1:GOSUB DEREF_B + B=A:GOSUB VAL_B REM symbol? in first position IF (Z%(B,0)AND 31)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? K=B:CALL ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE - B=R4:GOSUB DEREF_B + B=R4 REM macro? IF (Z%(B,0)AND 31)<>11 THEN GOTO MACROEXPAND_DONE @@ -126,14 +126,12 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - GOSUB DEREF_A - T=Z%(A,0)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt - R=A:GOSUB DEREF_R + R=A Z%(R,0)=Z%(R,0)+32 GOTO EVAL_AST_RETURN @@ -143,77 +141,50 @@ SUB EVAL_AST GOTO EVAL_AST_RETURN EVAL_AST_SEQ: - REM allocate the first entry (T already set above) - L=0:N=0:GOSUB ALLOC - - REM push type of sequence - Q=T:GOSUB PUSH_Q - REM push sequence index - Q=0:GOSUB PUSH_Q - REM push future return value (new sequence) - GOSUB PUSH_R - REM push previous new sequence entry - GOSUB PUSH_R + REM setup the stack for the loop + GOSUB MAP_LOOP_START EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element - Q=6:GOSUB PEEK_Q_Q + REM The EVAL_DO call to EVAL_AST must be call #2 for EVAL_AST to + REM return early and for TCO to work + Q=5:GOSUB PEEK_Q_Q IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE - REM if hashmap, skip eval of even entries (keys) - Q=3:GOSUB PEEK_Q_Q:T=Q - REM get and update index - GOSUB PEEK_Q_2 - Q=Q+1:GOSUB PUT_Q_2 - IF T=8 AND ((Q-1)AND 1)=0 THEN GOTO EVAL_AST_DO_REF - GOTO EVAL_AST_DO_EVAL - - EVAL_AST_DO_REF: - R=A+1:GOSUB DEREF_R: REM deref to target of referred entry - Z%(R,0)=Z%(R,0)+32: REM inc ref cnt of referred value - GOTO EVAL_AST_ADD_VALUE - - EVAL_AST_DO_EVAL: - REM call EVAL for each entry - A=A+1:CALL EVAL - A=A-1 - GOSUB DEREF_R: REM deref to target of evaluated entry - - EVAL_AST_ADD_VALUE: + REM call EVAL for each entry + GOSUB PUSH_A + IF T<>8 THEN GOSUB VAL_A + IF T=8 THEN A=Z%(A+1,1) + Q=T:GOSUB PUSH_Q: REM push/save type + CALL EVAL + GOSUB POP_Q:T=Q: REM pop/restore type + GOSUB POP_A - REM update previous value pointer to evaluated entry - GOSUB PEEK_Q - Z%(Q+1,1)=R + REM if error, release the unattached element + REM TODO: is R=0 correct? + IF ER<>-2 THEN AY=R:GOSUB RELEASE:R=0:GOTO EVAL_AST_SEQ_LOOP_DONE - IF ER<>-2 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + REM for hash-maps, copy the key (inc ref since we are going to + REM release it below) + IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 - REM allocate the next entry - REM same new sequence entry type - Q=3:GOSUB PEEK_Q_Q:T=Q - L=0:N=0:GOSUB ALLOC + REM value evaluated above + N=R - REM update previous sequence entry value to point to new entry - GOSUB PEEK_Q - Z%(Q,1)=R - REM update previous ptr to current entry - Q=R:GOSUB PUT_Q + REM update the return sequence structure + REM release N (and M if T=8) since seq takes full ownership + C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list A=Z%(A,1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: - GOSUB PEEK_Q_1 - REM if no error, get return value (new seq) - IF ER=-2 THEN R=Q - REM otherwise, free the return value and return nil - IF ER<>-2 THEN R=0:AY=Q:GOSUB RELEASE - - REM pop previous, return, index and type - GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q:GOSUB POP_Q + REM cleanup stack and get return value + GOSUB MAP_LOOP_DONE GOTO EVAL_AST_RETURN EVAL_AST_RETURN: @@ -239,8 +210,6 @@ SUB EVAL REM AZ=A:B=1:GOSUB PR_STR REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]" - GOSUB DEREF_A - GOSUB LIST_Q IF R THEN GOTO APPLY_LIST EVAL_NOT_LIST: @@ -257,8 +226,7 @@ SUB EVAL GOSUB EMPTY_Q IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN - A0=A+1 - R=A0:GOSUB DEREF_R:A0=R + A0=Z%(A+1,1) REM get symbol in A$ IF (Z%(A0,0)AND 31)<>5 THEN A$="" @@ -277,14 +245,14 @@ SUB EVAL GOTO EVAL_INVOKE EVAL_GET_A3: - A3=Z%(Z%(Z%(A,1),1),1)+1 - R=A3:GOSUB DEREF_R:A3=R + R=Z%(Z%(Z%(A,1),1),1) + GOSUB VAL_R:A3=R EVAL_GET_A2: - A2=Z%(Z%(A,1),1)+1 - R=A2:GOSUB DEREF_R:A2=R + R=Z%(Z%(A,1),1) + GOSUB VAL_R:A2=R EVAL_GET_A1: - A1=Z%(A,1)+1 - R=A1:GOSUB DEREF_R:A1=R + R=Z%(A,1) + GOSUB VAL_R:A1=R RETURN EVAL_DEF: @@ -316,13 +284,13 @@ SUB EVAL Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1)+1:CALL EVAL + A=Z%(A1,1):GOSUB VAL_A:CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE REM set environment: even A1 key to odd A1 eval'd above - K=A1+1:C=R:GOSUB ENV_SET + K=Z%(A1+1,1):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements @@ -343,6 +311,8 @@ SUB EVAL A=Z%(A,1): REM rest GOSUB PUSH_A: REM push/save A + REM this must be EVAL_AST call #2 for EVAL_AST to return early + REM and for TCO to work CALL EVAL_AST REM cleanup @@ -359,12 +329,12 @@ SUB EVAL GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_QUOTE: - R=Z%(A,1)+1:GOSUB DEREF_R + R=Z%(A,1):GOSUB VAL_R Z%(R,0)=Z%(R,0)+32 GOTO EVAL_RETURN EVAL_QUASIQUOTE: - R=Z%(A,1)+1:GOSUB DEREF_R + R=Z%(A,1):GOSUB VAL_R A=R:CALL QUASIQUOTE A=R REM add quasiquote result to pending release queue to free when @@ -390,7 +360,7 @@ SUB EVAL EVAL_MACROEXPAND: REM PRINT "macroexpand" - R=Z%(A,1)+1:GOSUB DEREF_R + R=Z%(A,1):GOSUB VAL_R A=R:CALL MACROEXPAND R=A @@ -444,7 +414,7 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:GOTO EVAL_RETURN + IF R<4 THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -462,10 +432,8 @@ SUB EVAL REM push f/args for release after call GOSUB PUSH_R - F=R+1 - AR=Z%(R,1): REM rest - R=F:GOSUB DEREF_R:F=R + GOSUB VAL_R:F=R REM if metadata, get the actual object IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) @@ -562,7 +530,7 @@ RE: REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=0:R2=0 + R1=-1:R2=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO REP_DONE @@ -575,8 +543,8 @@ SUB REP REP_DONE: REM Release memory from MAL_READ and EVAL - IF R2<>0 THEN AY=R2:GOSUB RELEASE - IF R1<>0 THEN AY=R1:GOSUB RELEASE + AY=R2:GOSUB RELEASE + AY=R1:GOSUB RELEASE END SUB REM MAIN program @@ -586,7 +554,7 @@ MAIN: LV=0 REM create repl_env - C=-1:GOSUB ENV_NEW:D=R + C=0:GOSUB ENV_NEW:D=R REM core.EXT: defined in Basic E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env @@ -654,6 +622,7 @@ MAIN: REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser IF EZ=1 THEN GOTO QUIT + IF R$="" THEN GOTO REPL_LOOP A$=R$:CALL REP: REM call REP @@ -662,7 +631,7 @@ MAIN: GOTO REPL_LOOP QUIT: - GOSUB PR_MEMORY_SUMMARY + GOSUB PR_MEMORY_SUMMARY_SMALL END PRINT_ERROR: diff --git a/basic/types.in.bas b/basic/types.in.bas index b055c2bdcf..76e1b4ab66 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -10,7 +10,7 @@ REM 14 value (unless empty) REM vector next/val 7 -> next Z% index (0 for last) REM 14 value (unless empty) REM hashmap next/val 8 -> next Z% index (0 for last) -REM 14 key/value (alternating) +REM key value REM function 9 -> function index REM mal function 10 -> body AST Z% index REM param env Z% index @@ -50,9 +50,9 @@ INIT_MEMORY: DIM Z%(Z1,1): REM TYPE ARRAY REM Predefine nil, false, true, and an empty list - FOR I=0 TO 8:Z%(I,0)=0:Z%(I,1)=0:NEXT I - Z%(1,0)=1 - Z%(2,0)=1:Z%(2,1)=1 + FOR I=0 TO 8:Z%(I,0)=32:Z%(I,1)=0:NEXT I + Z%(1,0)=1+32 + Z%(2,0)=1+32:Z%(2,1)=1 Z%(3,0)=6+32:Z%(3,1)=0 Z%(5,0)=7+32:Z%(5,1)=0 Z%(7,0)=8+32:Z%(7,1)=0 @@ -180,6 +180,7 @@ ALLOC: GOTO ALLOC_DONE ALLOC_UNUSED: REM PRINT "ALLOC_UNUSED ZI: "+STR$(ZI)+", U: "+STR$(U)+", R: "+STR$(R) + IF R+SZ>Z1 THEN PRINT "Out of mal memory!":END ZI=ZI+SZ IF U=R THEN ZK=ZI REM set previous free to new memory top @@ -188,17 +189,17 @@ ALLOC: ALLOC_DONE: Z%(R,0)=T+32 REM set Z%(R,1) to default L - IF T>=6 AND T<>9 AND L>0 THEN Z%(L,0)=Z%(L,0)+32 + IF T>=6 AND T<>9 THEN Z%(L,0)=Z%(L,0)+32 Z%(R,1)=L IF SZ=1 THEN RETURN Z%(R+1,0)=14: REM default for 6-8, and 13, and >=16 (metadata) - REM function/macro sets Z%(R+1,0) to default M - IF T=10 OR T=11 THEN Z%(M,0)=Z%(M,0)+32:Z%(R+1,0)=M + REM function/macro/hash-map sets Z%(R+1,0) to default M + IF T=8 OR T=10 OR T=11 THEN Z%(M,0)=Z%(M,0)+32:Z%(R+1,0)=M REM seq, function/macro, environment sets Z%(R+1,1) to default N - IF N>0 THEN Z%(N,0)=Z%(N,0)+32 + Z%(N,0)=Z%(N,0)+32 Z%(R+1,1)=N RETURN @@ -229,9 +230,7 @@ RELEASE: RC=RC-1 RELEASE_ONE: - - REM nil, false, true - IF AY<3 THEN GOTO RELEASE_TOP + IF AY=-1 THEN RETURN U=Z%(AY,0)AND 31: REM type V=Z%(AY,1): REM main value/reference @@ -240,12 +239,16 @@ RELEASE: REM PRINT "RELEASE AY:"+STR$(AY)+"["+R$+"] (byte0:"+STR$(Z%(AY,0))+")" REM sanity check not already freed - IF (U)=15 THEN ER=-1:E$="RELEASE of free: "+STR$(AY):RETURN - IF Z%(AY,0)<15 THEN ER=-1:E$="Unowned: "+STR$(AY):RETURN + IF (U)=15 THEN PRINT "RELEASE of free:"+STR$(AY):END + IF Z%(AY,0)<15 THEN PRINT "RELEASE of unowned:"+STR$(AY):END REM decrease reference count by one Z%(AY,0)=Z%(AY,0)-32 + REM nil, false, true, empty sequences + IF AY<9 AND Z%(AY,0)<32 THEN PRINT "RELEASE of empty:"+STR$(AY):END + IF AY<9 THEN GOTO RELEASE_TOP + REM our reference count is not 0, so don't release IF Z%(AY,0)>=32 GOTO RELEASE_TOP @@ -261,7 +264,7 @@ REM IF U>=16 THEN GOSUB RELEASE_METADATA REM IF U=12 THEN GOSUB RELEASE_ATOM REM IF U=13 THEN GOSUB RELEASE_ENV - ON U+1 GOSUB RELEASE_SIMPLE,RELEASE_SIMPLE,RELEASE_SIMPLE,RELEASE_SIMPLE,RELEASE_STRING,RELEASE_STRING,RELEASE_SEQ,RELEASE_SEQ,RELEASE_SEQ,RELEASE_SIMPLE,RELEASE_MAL_FUNCTION,RELEASE_MAL_FUNCTION,RELEASE_ATOM,RELEASE_ENV + ON U+1 GOSUB RELEASE_SIMPLE,RELEASE_SIMPLE,RELEASE_SIMPLE,RELEASE_SIMPLE,RELEASE_STRING,RELEASE_STRING,RELEASE_SEQ,RELEASE_SEQ,RELEASE_HASH_MAP,RELEASE_SIMPLE,RELEASE_MAL_FUNCTION,RELEASE_MAL_FUNCTION,RELEASE_ATOM,RELEASE_ENV REM free the current element and continue, SZ already set GOSUB FREE @@ -277,13 +280,23 @@ REM IF U=13 THEN GOSUB RELEASE_ENV REM free the atom itself RETURN RELEASE_SEQ: - IF V=0 THEN SZ=2:RETURN + SZ=2 + IF V=0 THEN RETURN IF Z%(AY+1,0)<>14 THEN ER=-1:E$="invalid list value"+STR$(AY+1):RETURN REM add value and next element to stack RC=RC+2 Q=Z%(AY+1,1):GOSUB PUSH_Q Q=V:GOSUB PUSH_Q - SZ=2:RETURN + RETURN + RELEASE_HASH_MAP: + SZ=2 + IF V=0 THEN RETURN + REM add key, value and next element to stack + RC=RC+3 + Q=Z%(AY+1,0):GOSUB PUSH_Q + Q=Z%(AY+1,1):GOSUB PUSH_Q + Q=V:GOSUB PUSH_Q + RETURN RELEASE_ATOM: REM add contained/referred value RC=RC+1 @@ -309,7 +322,7 @@ REM IF U=13 THEN GOSUB RELEASE_ENV RC=RC+1 Q=V:GOSUB PUSH_Q REM if outer set, add outer env to stack - IF Z%(AY+1,1)<>-1 THEN RC=RC+1:Q=Z%(AY+1,1):GOSUB PUSH_Q + IF Z%(AY+1,1)<>0 THEN RC=RC+1:Q=Z%(AY+1,1):GOSUB PUSH_Q REM add outer environment to the stack SZ=2:RETURN @@ -341,19 +354,19 @@ REM release stack functions #cbm Y=Y-4 #cbm GOTO RELEASE_PEND -REM DEREF_R(R) -> R -DEREF_R: - IF (Z%(R,0)AND 31)=14 THEN R=Z%(R,1):GOTO DEREF_R +REM VAL_R(R) -> R +VAL_R: + R=Z%(R+1,1) RETURN -REM DEREF_A(A) -> A -DEREF_A: - IF (Z%(A,0)AND 31)=14 THEN A=Z%(A,1):GOTO DEREF_A +REM VAL_A(A) -> A +VAL_A: + A=Z%(A+1,1) RETURN -REM DEREF_B(B) -> B -DEREF_B: - IF (Z%(B,0)AND 31)=14 THEN B=Z%(B,1):GOTO DEREF_B +REM VAL_B(B) -> B +VAL_B: + B=Z%(B+1,1) RETURN @@ -366,9 +379,6 @@ EQUAL_Q: EQUAL_Q_RECUR: - GOSUB DEREF_A - GOSUB DEREF_B - REM push A and B GOSUB PUSH_A Q=B:GOSUB PUSH_Q @@ -443,13 +453,11 @@ STRING: REM fallthrough STRING_SET: -REM IF I>85 THEN PRINT "STRING:"+STR$(I)+" "+B$ S$(I)=B$ REM fallthrough STRING_DONE: S%(I)=S%(I)+1 -REM PRINT "STRING ref: "+S$(I)+" (idx:"+STR$(I)+", ref "+STR$(S%(I))+")" L=I:GOSUB ALLOC RETURN @@ -473,12 +481,58 @@ REM FORCE_SEQ_TYPE(A,T) -> R FORCE_SEQ_TYPE: REM if it's already the right type, inc ref cnt and return it IF (Z%(A,0)AND 31)=T THEN R=A:Z%(R,0)=Z%(R,0)+32:RETURN + REM if it's empty, return the empty sequence match T + IF A<9 THEN R=(T-5)*2+1:Z%(R,0)=Z%(R,0)+32:RETURN REM otherwise, copy first element to turn it into correct type - B=A+1:GOSUB DEREF_B: REM value to copy + B=A:GOSUB VAL_B: REM value to copy L=Z%(A,1):N=B:GOSUB ALLOC: REM T already set IF Z%(A,1)=0 THEN RETURN RETURN +REM MAP_LOOP_START(T): +REM - setup stack for map loop +MAP_LOOP_START: + REM point to empty sequence to start off + R=(T-5)*2+1: REM calculate location of empty seq + Z%(R,0)=Z%(R,0)+32 + + GOSUB PUSH_R: REM push return ptr + GOSUB PUSH_R: REM push empty ptr + GOSUB PUSH_R: REM push current ptr + RETURN + +REM MAP_LOOP_UPDATE(C,N): +REM MAP_LOOP_UPDATE(C,M,N): +REM - called after N (and M if T=8) are set +REM - C indicates whether to free N (and M if T=8) +REM - update the structure of the return sequence +MAP_LOOP_UPDATE: + GOSUB PEEK_Q_1:L=Q: REM empty ptr + + GOSUB ALLOC: REM allocate new sequence element + + REM sequence took ownership + AY=L:GOSUB RELEASE + IF C THEN AY=N:GOSUB RELEASE + IF C AND T=8 THEN AY=M:GOSUB RELEASE + + REM if not first element, set current next to point to new element + GOSUB PEEK_Q + IF Q>8 THEN Z%(Q,1)=R + REM if first element, set return to new element + IF Q<9 THEN Q=R:GOSUB PUT_Q_2 + Q=R:GOSUB PUT_Q: REM update current ptr to new element + + RETURN + +REM MAP_LOOP_DONE() -> R +REM - cleanup stack and set return value +MAP_LOOP_DONE: + GOSUB POP_Q: REM pop current ptr + GOSUB POP_Q: REM pop empty ptr + GOSUB POP_R: REM pop return ptr + RETURN + REM LIST_Q(A) -> R LIST_Q: @@ -514,34 +568,37 @@ LAST: A=Z%(A,1): REM next entry GOTO LAST_LOOP LAST_DONE: - R=W+1:GOSUB DEREF_R + R=W:GOSUB VAL_R Z%(R,0)=Z%(R,0)+32 RETURN REM SLICE(A,B,C) -> R REM make copy of sequence A from index B to C -REM returns R6 as reference to last element of slice +REM returns R6 as reference to last element of slice before empty REM returns A as next element following slice (of original) SLICE: I=0 - W=-1: REM temporary for return as R - R6=0: REM previous list element + R=3: REM always a list + Z%(R,0)=Z%(R,0)+32 + R6=-1: REM last list element before empty + W=R: REM temporary for return as R + REM advance A to position B + SLICE_FIND_B: + IF I0 THEN A=Z%(A,1):I=I+1:GOTO SLICE_FIND_B SLICE_LOOP: - REM always allocate at least one list element - T=6:L=0:N=0:GOSUB ALLOC - IF W=-1 THEN W=R - IF W<>-1 THEN Z%(R6,1)=R - REM advance A to position B - SLICE_FIND_B: - IF I0 THEN A=Z%(A,1):I=I+1:GOTO SLICE_FIND_B REM if current position is C, then return IF C<>-1 AND I>=C THEN R=W:RETURN REM if we reached end of A, then return IF Z%(A,1)=0 THEN R=W:RETURN - R6=R: REM save previous list element - REM copy value and inc ref cnt - Z%(R6+1,1)=Z%(A+1,1) - R=A+1:GOSUB DEREF_R:Z%(R,0)=Z%(R,0)+32 + REM allocate new list element with copied value + T=6:L=3:N=Z%(A+1,1):GOSUB ALLOC + REM sequence took ownership + AY=L:GOSUB RELEASE + REM if not first element, set last to point to new element + IF R6>-1 THEN Z%(R6,1)=R + REM if first element, set return value to new element + IF R6=-1 THEN W=R + R6=R: REM update last list element REM advance to next element of A A=Z%(A,1) I=I+1 @@ -580,15 +637,8 @@ HASHMAP: REM ASSOC1(H, K, C) -> R ASSOC1: - REM deref K and C - R=C:GOSUB DEREF_R:C=R - R=K:GOSUB DEREF_R:K=R - - REM value ptr - T=8:L=H:N=C:GOSUB ALLOC - AY=L:GOSUB RELEASE: REM we took ownership of previous hashmap - REM key ptr - T=8:L=R:N=K:GOSUB ALLOC + REM create key/value entry + T=8:L=H:M=K:N=C:GOSUB ALLOC AY=L:GOSUB RELEASE: REM we took ownership of previous hashmap RETURN @@ -601,6 +651,7 @@ ASSOC1_S: RETURN REM HASHMAP_GET(H, K) -> R +REM - returns R3 with whether we found it or not HASHMAP_GET: B$=S$(Z%(K,1)): REM search key string R3=0: REM whether found or not (for HASHMAP_CONTAINS) @@ -608,15 +659,10 @@ HASHMAP_GET: HASHMAP_GET_LOOP: REM no matching key found IF Z%(H,1)=0 THEN R=0:RETURN - REM follow value ptrs - T2=H+1 - HASHMAP_GET_DEREF: - IF Z%(T2,0)=14 THEN T2=Z%(T2,1):GOTO HASHMAP_GET_DEREF - REM get key string - REM if they are equal, we found it - IF B$=S$(Z%(T2,1)) THEN R3=1:R=Z%(H,1)+1:RETURN - REM skip to next key - H=Z%(Z%(H,1),1) + REM get search string is equal to key string we found it + IF B$=S$(Z%(Z%(H+1,0),1)) THEN R3=1:R=Z%(H+1,1):RETURN + REM skip to next key/value + H=Z%(H,1) GOTO HASHMAP_GET_LOOP REM HASHMAP_CONTAINS(H, K) -> R diff --git a/basic/variables.txt b/basic/variables.txt index c4f84ee56c..bcaaee1359 100644 --- a/basic/variables.txt +++ b/basic/variables.txt @@ -58,7 +58,7 @@ P1 : PR_MEMORY, CHECK_FREE_LIST start P2 : PR_MEMORY, CHECK_FREE_LIST end R1 : REP, RE - MAL_READ result temp R2 : REP, RE - EVAL result temp -R3 : HASHMAP_GET temp and return value +R3 : HASHMAP_GET, DO_HASH_MAP, DO_KEYS_VALS temp and return value R4 : ENV_FIND temp and return value R6 : SLICE return value (last element) SZ : size argument to ALLOC @@ -84,7 +84,7 @@ I : STRING, REPLACE, SLICE, PR_MEMORY, PR_OBJECT J : REPLACE U : ALLOC, RELEASE, PR_STR temp V : RELEASE, PR_STR_SEQ temp -W : SLICE, LAST, QUASIQUOTE, step2-3 EVAL temp +W : SLICE, LAST, QUASIQUOTE, DO_HASH_MAP, DO_KEYS_VALS, step2-3 EVAL temp RC : RELEASE remaining number of elements to release RF : reader reading from file flag S1 : READ_TOKEN in a string? From 6aaee33ea88f2e05350e0d129337875733e3ca63 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 18 Nov 2016 00:46:07 -0600 Subject: [PATCH 0238/2308] Basic: use RE from REP. --- basic/stepA_mal.in.bas | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index 7df9f3b0d9..cdbeeab7ae 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -515,7 +515,7 @@ REM RE(A$) -> R REM Assume D has repl_env REM caller must release result RE: - R1=0 + R1=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO RE_DONE @@ -524,18 +524,15 @@ RE: RE_DONE: REM Release memory from MAL_READ - IF R1<>0 THEN AY=R1:GOSUB RELEASE + AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=-1:R2=-1 - GOSUB MAL_READ - R1=R - IF ER<>-2 THEN GOTO REP_DONE + R2=-1 - A=R:E=D:CALL EVAL + GOSUB RE R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -544,7 +541,6 @@ SUB REP REP_DONE: REM Release memory from MAL_READ and EVAL AY=R2:GOSUB RELEASE - AY=R1:GOSUB RELEASE END SUB REM MAIN program From d7a6c2d6c9c39c726a130c0599432a89d8cb7825 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 18 Nov 2016 00:54:04 -0600 Subject: [PATCH 0239/2308] Basic: refactor memory layout. Use a one dimensional array for the Z% value array. This enables lists, vectors, environments and metadata pointers to all save off 1 word (2 bytes) of space. Split the memory init and functions into mem.in.bas. In addition, change type 14 to be metdata rather than any type 16-31. This change saves about 560 bytes (no second array dimension subscripts) and reduces Z% value usage by 10%-15%. Bump the number of Z% words by 200 (to 8591). This enables self-hosting up to step7 (without step8-stepA functions in core.mal). --- basic/Makefile | 2 +- basic/core.in.bas | 188 +++++++------- basic/debug.in.bas | 192 +++++++++----- basic/env.in.bas | 37 ++- basic/mem.in.bas | 343 +++++++++++++++++++++++++ basic/printer.in.bas | 31 ++- basic/reader.in.bas | 16 +- basic/step1_read_print.in.bas | 1 + basic/step2_eval.in.bas | 36 +-- basic/step3_env.in.bas | 62 ++--- basic/step4_if_fn_do.in.bas | 71 +++--- basic/step5_tco.in.bas | 73 +++--- basic/step6_file.in.bas | 73 +++--- basic/step7_quote.in.bas | 109 ++++---- basic/step8_macros.in.bas | 127 +++++----- basic/step9_try.in.bas | 143 +++++------ basic/stepA_mal.in.bas | 138 +++++----- basic/types.in.bas | 462 ++++------------------------------ basic/variables.txt | 11 +- 19 files changed, 1055 insertions(+), 1060 deletions(-) create mode 100644 basic/mem.in.bas diff --git a/basic/Makefile b/basic/Makefile index 47fa8c174f..3373ea52b1 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -13,7 +13,7 @@ step%.bas: step%.in.bas ./basicpp.py $(BASICPP_OPTS) $< > $@ $(STEPS0_A): readline.in.bas -$(STEPS1_A): debug.in.bas types.in.bas reader.in.bas printer.in.bas +$(STEPS1_A): debug.in.bas mem.in.bas types.in.bas reader.in.bas printer.in.bas $(STEPS3_A): env.in.bas $(STEPS4_A): core.in.bas diff --git a/basic/core.in.bas b/basic/core.in.bas index 596e3a8656..7853ee0a6e 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -7,15 +7,15 @@ REM - restores E REM - call using GOTO and with return label/address on the stack SUB APPLY REM if metadata, get the actual object - IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) + IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) - ON (Z%(F,0)AND 31)-8 GOTO APPLY_FUNCTION,APPLY_MAL_FUNCTION,APPLY_MAL_FUNCTION + ON (Z%(F)AND 31)-8 GOTO APPLY_FUNCTION,APPLY_MAL_FUNCTION,APPLY_MAL_FUNCTION APPLY_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO APPLY_DONE + IF Z%(F+1)<60 THEN GOSUB DO_FUNCTION:GOTO APPLY_DONE REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION + IF Z%(F+1)>60 THEN CALL DO_TCO_FUNCTION GOTO APPLY_DONE APPLY_MAL_FUNCTION: @@ -23,9 +23,9 @@ SUB APPLY REM create new environ using env and params stored in the REM function and bind the params to the apply arguments - C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS + C=Z%(F+3):A=Z%(F+2):B=AR:GOSUB ENV_NEW_BINDS - A=Z%(F,1):E=R:CALL EVAL + A=Z%(F+1):E=R:CALL EVAL AY=E:GOSUB RELEASE: REM release the new environment @@ -37,22 +37,22 @@ END SUB REM DO_TCO_FUNCTION(F, AR) SUB DO_TCO_FUNCTION - G=Z%(F,1) + G=Z%(F+1) REM Get argument values - A=AR:GOSUB VAL_A - B=Z%(AR,1):GOSUB VAL_B + A=Z%(AR+2) + B=Z%(Z%(AR+1)+2) ON G-60 GOTO DO_APPLY,DO_MAP,DO_SWAP_BANG DO_APPLY: F=A - AR=Z%(AR,1) + AR=Z%(AR+1) A=AR:GOSUB COUNT:C=R - A=Z%(AR+1,1) + A=Z%(AR+2) REM no intermediate args, but not a list, so convert it first - IF C<=1 AND (Z%(A,0)AND 31)<>6 THEN T=6:GOSUB FORCE_SEQ_TYPE:GOTO DO_APPLY_2 + IF C<=1 AND (Z%(A)AND 31)<>6 THEN T=6:GOSUB FORCE_SEQ_TYPE:GOTO DO_APPLY_2 REM no intermediate args, just call APPLY directly IF C<=1 THEN GOTO DO_APPLY_1 @@ -61,10 +61,10 @@ SUB DO_TCO_FUNCTION REM release the terminator of new list (we skip over it) REM we already checked for an empty list above, so R6 is pointer REM a real non-empty list - AY=Z%(R6,1):GOSUB RELEASE + AY=Z%(R6+1):GOSUB RELEASE REM attach end of slice to final args element - Z%(R6,1)=Z%(A+1,1) - Z%(Z%(A+1,1),0)=Z%(Z%(A+1,1),0)+32 + Z%(R6+1)=Z%(A+2) + Z%(Z%(A+2))=Z%(Z%(A+2))+32 GOTO DO_APPLY_2 @@ -90,10 +90,10 @@ SUB DO_TCO_FUNCTION T=6:GOSUB MAP_LOOP_START DO_MAP_LOOP: - IF Z%(B,1)=0 THEN GOTO DO_MAP_DONE + IF Z%(B+1)=0 THEN GOTO DO_MAP_DONE REM create argument list for apply - T=6:L=3:N=Z%(B+1,1):GOSUB ALLOC + T=6:L=6:M=Z%(B+2):GOSUB ALLOC GOSUB PUSH_R: REM push argument list Q=F:GOSUB PUSH_Q: REM push F @@ -106,14 +106,14 @@ SUB DO_TCO_FUNCTION GOSUB POP_Q: REM pop apply args and release them AY=Q:GOSUB RELEASE - B=Z%(B,1): REM go to the next element + REM main value is result of apply + M=R + + B=Z%(B+1): REM go to the next element REM if error, release the unattached element IF ER<>-2 THEN AY=R:GOSUB RELEASE:GOTO DO_MAP_DONE - REM main value is result of apply - N=R - REM update the return sequence structure REM release N since list takes full ownership C=1:T=6:GOSUB MAP_LOOP_UPDATE @@ -129,7 +129,7 @@ SUB DO_TCO_FUNCTION F=B REM add atom to front of the args list - T=6:L=Z%(Z%(AR,1),1):N=Z%(A,1):GOSUB ALLOC: REM cons + T=6:L=Z%(Z%(AR+1)+1):M=Z%(A+1):GOSUB ALLOC: REM cons AR=R REM push args for release after @@ -162,25 +162,25 @@ REM RETURN_INC_REF(R) -> R REM - return R with 1 ref cnt increase REM - called with GOTO as a return RETURN RETURN_INC_REF: - Z%(R,0)=Z%(R,0)+32 + Z%(R)=Z%(R)+32 RETURN REM RETURN_TRUE_FALSE(R) -> R REM - take BASIC true/false R, return mal true/false R with ref cnt REM - called with GOTO as a return RETURN RETURN_TRUE_FALSE: - IF R THEN R=2 - IF R=0 THEN R=1 + IF R THEN R=4 + IF R=0 THEN R=2 GOTO RETURN_INC_REF REM DO_FUNCTION(F, AR) DO_FUNCTION: REM Get the function number - G=Z%(F,1) + G=Z%(F+1) REM Get argument values - A=AR:GOSUB VAL_A - B=Z%(AR,1):GOSUB VAL_B + A=Z%(AR+2) + B=Z%(Z%(AR+1)+2) REM Switch on the function number IF G>59 THEN ER=-1:E$="unknown function"+STR$(G):RETURN @@ -204,40 +204,40 @@ DO_FUNCTION: GOTO RETURN_TRUE_FALSE DO_THROW: ER=A - Z%(ER,0)=Z%(ER,0)+32 + Z%(ER)=Z%(ER)+32 R=-1 RETURN DO_NIL_Q: R=A=0 GOTO RETURN_TRUE_FALSE DO_TRUE_Q: - R=A=2 + R=A=4 GOTO RETURN_TRUE_FALSE DO_FALSE_Q: - R=A=1 + R=A=2 GOTO RETURN_TRUE_FALSE DO_STRING_Q: R=0 - IF (Z%(A,0)AND 31)<>4 THEN GOTO RETURN_TRUE_FALSE - IF MID$(S$(Z%(A,1)),1,1)=CHR$(127) THEN GOTO RETURN_TRUE_FALSE + IF (Z%(A)AND 31)<>4 THEN GOTO RETURN_TRUE_FALSE + IF MID$(S$(Z%(A+1)),1,1)=CHR$(127) THEN GOTO RETURN_TRUE_FALSE R=1 GOTO RETURN_TRUE_FALSE DO_SYMBOL: - B$=S$(Z%(A,1)) + B$=S$(Z%(A+1)) T=5:GOSUB STRING RETURN DO_SYMBOL_Q: - R=(Z%(A,0)AND 31)=5 + R=(Z%(A)AND 31)=5 GOTO RETURN_TRUE_FALSE DO_KEYWORD: - B$=S$(Z%(A,1)) + B$=S$(Z%(A+1)) IF MID$(B$,1,1)<>CHR$(127) THEN B$=CHR$(127)+B$ T=4:GOSUB STRING RETURN DO_KEYWORD_Q: R=0 - IF (Z%(A,0)AND 31)<>4 THEN GOTO RETURN_TRUE_FALSE - IF MID$(S$(Z%(A,1)),1,1)<>CHR$(127) THEN GOTO RETURN_TRUE_FALSE + IF (Z%(A)AND 31)<>4 THEN GOTO RETURN_TRUE_FALSE + IF MID$(S$(Z%(A+1)),1,1)<>CHR$(127) THEN GOTO RETURN_TRUE_FALSE R=1 GOTO RETURN_TRUE_FALSE @@ -260,18 +260,18 @@ DO_FUNCTION: R=0 GOTO RETURN_INC_REF DO_READ_STRING: - A$=S$(Z%(A,1)) + A$=S$(Z%(A+1)) GOSUB READ_STR RETURN DO_READLINE: - A$=S$(Z%(A,1)):GOSUB READLINE + A$=S$(Z%(A+1)):GOSUB READLINE IF EZ=1 THEN EZ=0:R=0:GOTO RETURN_INC_REF B$=R$:T=4:GOSUB STRING RETURN DO_SLURP: R$="" - #cbm OPEN 1,8,0,S$(Z%(A,1)) - #qbasic A$=S$(Z%(A,1)) + #cbm OPEN 1,8,0,S$(Z%(A+1)) + #qbasic A$=S$(Z%(A+1)) #qbasic IF NOT _FILEEXISTS(A$) THEN ER=-1:E$="File not found":RETURN #qbasic OPEN A$ FOR INPUT AS #1 DO_SLURP_LOOP: @@ -290,29 +290,29 @@ DO_FUNCTION: RETURN DO_LT: - R=Z%(A,1)Z%(B,1) + R=Z%(A+1)>Z%(B+1) GOTO RETURN_TRUE_FALSE DO_GTE: - R=Z%(A,1)>=Z%(B,1) + R=Z%(A+1)>=Z%(B+1) GOTO RETURN_TRUE_FALSE DO_ADD: - T=2:L=Z%(A,1)+Z%(B,1):GOSUB ALLOC + T=2:L=Z%(A+1)+Z%(B+1):GOSUB ALLOC RETURN DO_SUB: - T=2:L=Z%(A,1)-Z%(B,1):GOSUB ALLOC + T=2:L=Z%(A+1)-Z%(B+1):GOSUB ALLOC RETURN DO_MULT: - T=2:L=Z%(A,1)*Z%(B,1):GOSUB ALLOC + T=2:L=Z%(A+1)*Z%(B+1):GOSUB ALLOC RETURN DO_DIV: - T=2:L=Z%(A,1)/Z%(B,1):GOSUB ALLOC + T=2:L=Z%(A+1)/Z%(B+1):GOSUB ALLOC RETURN DO_TIME_MS: T=2:L=INT((TI-BT)*16.667):GOSUB ALLOC @@ -328,7 +328,7 @@ DO_FUNCTION: A=AR:T=7:GOSUB FORCE_SEQ_TYPE RETURN DO_VECTOR_Q: - R=(Z%(A,0)AND 31)=7 + R=(Z%(A)AND 31)=7 GOTO RETURN_TRUE_FALSE DO_HASH_MAP: REM setup the stack for the loop @@ -336,12 +336,12 @@ DO_FUNCTION: A=AR DO_HASH_MAP_LOOP: - IF Z%(A,1)=0 THEN GOTO DO_HASH_MAP_LOOP_DONE + IF Z%(A+1)=0 THEN GOTO DO_HASH_MAP_LOOP_DONE - M=Z%(A+1,1) - N=Z%(Z%(A,1)+1,1) + M=Z%(A+2) + N=Z%(Z%(A+1)+2) - A=Z%(Z%(A,1),1): REM skip two + A=Z%(Z%(A+1)+1): REM skip two REM update the return sequence structure REM do not release M and N since we are pulling them from the @@ -356,18 +356,18 @@ DO_FUNCTION: RETURN DO_MAP_Q: - R=(Z%(A,0)AND 31)=8 + R=(Z%(A)AND 31)=8 GOTO RETURN_TRUE_FALSE DO_ASSOC: H=A - AR=Z%(AR,1) + AR=Z%(AR+1) DO_ASSOC_LOOP: - R=AR:GOSUB VAL_R:K=R - R=Z%(AR,1):GOSUB VAL_R:C=R - Z%(H,0)=Z%(H,0)+32 + K=Z%(AR+2) + C=Z%(Z%(AR+1)+2) + Z%(H)=Z%(H)+32 GOSUB ASSOC1:H=R - AR=Z%(Z%(AR,1),1) - IF AR=0 OR Z%(AR,1)=0 THEN RETURN + AR=Z%(Z%(AR+1)+1) + IF AR=0 OR Z%(AR+1)=0 THEN RETURN GOTO DO_ASSOC_LOOP DO_GET: IF A=0 THEN R=0:GOTO RETURN_INC_REF @@ -386,12 +386,12 @@ DO_FUNCTION: T=6:GOSUB MAP_LOOP_START DO_KEYS_VALS_LOOP: - IF Z%(A,1)=0 THEN GOTO DO_KEYS_VALS_LOOP_DONE + IF Z%(A+1)=0 THEN GOTO DO_KEYS_VALS_LOOP_DONE - IF T1=0 THEN N=Z%(A+1,0) - IF T1=1 THEN N=Z%(A+1,1) + IF T1=0 THEN M=Z%(A+2) + IF T1=1 THEN M=Z%(A+3) - A=Z%(A,1): REM next element + A=Z%(A+1): REM next element REM update the return sequence structure REM do not release N since we are pulling it from the @@ -406,17 +406,17 @@ DO_FUNCTION: RETURN DO_SEQUENTIAL_Q: - R=(Z%(A,0)AND 31)=6 OR (Z%(A,0)AND 31)=7 + R=(Z%(A)AND 31)=6 OR (Z%(A)AND 31)=7 GOTO RETURN_TRUE_FALSE DO_CONS: - T=6:L=B:N=A:GOSUB ALLOC + T=6:L=B:M=A:GOSUB ALLOC RETURN DO_CONCAT: REM if empty arguments, return empty list - IF Z%(AR,1)=0 THEN R=3:GOTO RETURN_INC_REF + IF Z%(AR+1)=0 THEN R=6:GOTO RETURN_INC_REF REM single argument - IF Z%(Z%(AR,1),1)<>0 THEN GOTO DO_CONCAT_MULT + IF Z%(Z%(AR+1)+1)<>0 THEN GOTO DO_CONCAT_MULT REM force to list type T=6:GOSUB FORCE_SEQ_TYPE RETURN @@ -427,19 +427,19 @@ DO_FUNCTION: CZ=X: REM save current stack position REM push arguments onto the stack DO_CONCAT_STACK: - R=AR:GOSUB VAL_R + R=Z%(AR+2) GOSUB PUSH_R: REM push sequence - AR=Z%(AR,1) - IF Z%(AR,1)<>0 THEN GOTO DO_CONCAT_STACK + AR=Z%(AR+1) + IF Z%(AR+1)<>0 THEN GOTO DO_CONCAT_STACK REM pop last argument as our seq to prepend to GOSUB POP_Q:B=Q REM last arg/seq is not copied so we need to inc ref to it - Z%(B,0)=Z%(B,0)+32 + Z%(B)=Z%(B)+32 DO_CONCAT_LOOP: IF X=CZ THEN R=B:RETURN GOSUB POP_A: REM pop off next seq to prepend - IF Z%(A,1)=0 THEN GOTO DO_CONCAT_LOOP: REM skip empty seqs + IF Z%(A+1)=0 THEN GOTO DO_CONCAT_LOOP: REM skip empty seqs Q=B:GOSUB PUSH_Q B=0:C=-1:GOSUB SLICE GOSUB POP_Q:B=Q @@ -447,37 +447,37 @@ DO_FUNCTION: REM release the terminator of new list (we skip over it) REM we already checked for an empty list above, so R6 is pointer REM a real non-empty list - AY=Z%(R6,1):GOSUB RELEASE + AY=Z%(R6+1):GOSUB RELEASE REM attach new list element before terminator (last actual REM element to the next sequence - Z%(R6,1)=B + Z%(R6+1)=B B=R GOTO DO_CONCAT_LOOP DO_NTH: GOSUB COUNT - B=Z%(B,1) + B=Z%(B+1) IF R<=B THEN R=-1:ER=-1:E$="nth: index out of range":RETURN DO_NTH_LOOP: IF B=0 THEN GOTO DO_NTH_DONE B=B-1 - A=Z%(A,1) + A=Z%(A+1) GOTO DO_NTH_LOOP DO_NTH_DONE: - R=Z%(A+1,1) + R=Z%(A+2) GOTO RETURN_INC_REF DO_FIRST: R=0 IF A=0 THEN GOTO RETURN_INC_REF - IF Z%(A,1)<>0 THEN R=A:GOSUB VAL_R + IF Z%(A+1)<>0 THEN R=Z%(A+2) GOTO RETURN_INC_REF DO_REST: - IF A=0 THEN R=3:GOTO RETURN_INC_REF - IF Z%(A,1)<>0 THEN A=Z%(A,1): REM get the next sequence element + IF A=0 THEN R=6:GOTO RETURN_INC_REF + IF Z%(A+1)<>0 THEN A=Z%(A+1): REM get the next sequence element T=6:GOSUB FORCE_SEQ_TYPE RETURN DO_EMPTY_Q: - R=Z%(A,1)=0 + R=Z%(A+1)=0 GOTO RETURN_TRUE_FALSE DO_COUNT: GOSUB COUNT @@ -491,32 +491,32 @@ DO_FUNCTION: GOTO RETURN_INC_REF DO_WITH_META: - T=Z%(A,0)AND 31 + T=Z%(A)AND 31 REM remove existing metadata first - IF T>=16 THEN A=Z%(A,1):GOTO DO_WITH_META - T=T+16:L=A:N=B:GOSUB ALLOC + IF T=14 THEN A=Z%(A+1):GOTO DO_WITH_META + T=14:L=A:M=B:GOSUB ALLOC RETURN DO_META: R=0 - IF (Z%(A,0)AND 31)>15 THEN R=Z%(A+1,1) + IF (Z%(A)AND 31)=14 THEN R=Z%(A+2) GOTO RETURN_INC_REF DO_ATOM: T=12:L=A:GOSUB ALLOC RETURN DO_ATOM_Q: - R=(Z%(A,0)AND 31)=12 + R=(Z%(A)AND 31)=12 GOTO RETURN_TRUE_FALSE DO_DEREF: - R=Z%(A,1) + R=Z%(A+1) GOTO RETURN_INC_REF DO_RESET_BANG: R=B REM release current value - AY=Z%(A,1):GOSUB RELEASE + AY=Z%(A+1):GOSUB RELEASE REM inc ref by 2 for atom ownership and since we are returning it - Z%(R,0)=Z%(R,0)+64 + Z%(R)=Z%(R)+64 REM update value - Z%(A,1)=R + Z%(A+1)=R RETURN REM DO_PR_MEMORY: @@ -536,7 +536,7 @@ DO_FUNCTION: RETURN DO_READ_FILE: - A$=S$(Z%(A,1)) + A$=S$(Z%(A+1)) GOSUB READ_FILE RETURN diff --git a/basic/debug.in.bas b/basic/debug.in.bas index 0f65f75179..5c5b6b1ccd 100644 --- a/basic/debug.in.bas +++ b/basic/debug.in.bas @@ -5,9 +5,9 @@ CHECK_FREE_LIST: P2=0 CHECK_FREE_LIST_LOOP: IF P1>=ZI THEN GOTO CHECK_FREE_LIST_DONE - IF (Z%(P1,0)AND 31)<>15 THEN P2=-1:GOTO CHECK_FREE_LIST_DONE - P2=P2+(Z%(P1,0)AND-32)/32 - P1=Z%(P1,1) + IF (Z%(P1)AND 31)<>15 THEN P2=-1:GOTO CHECK_FREE_LIST_DONE + P2=P2+(Z%(P1)AND-32)/32 + P1=Z%(P1+1) GOTO CHECK_FREE_LIST_LOOP CHECK_FREE_LIST_DONE: IF P2=-1 THEN PRINT "corrupt free list at "+STR$(P1) @@ -19,11 +19,13 @@ PR_MEMORY_SUMMARY_SMALL: GOSUB CHECK_FREE_LIST #cbm PRINT "Free:"+STR$(FRE(0))+", "; PRINT "Values:"+STR$(ZI-1-P2)+", Emptys:"; - FOR I=0 TO 7 - IF I<>4 AND I<>6 THEN PRINT STR$(INT(Z%(I,0)/32))+","; - NEXT I + FOR I=0 TO 4 STEP 2:GOSUB PR_MEMORY_SUMMARY_SMALL_1:NEXT I + FOR I=6 TO 12 STEP 3:GOSUB PR_MEMORY_SUMMARY_SMALL_1:NEXT I PRINT RETURN + PR_MEMORY_SUMMARY_SMALL_1: + PRINT STR$(INT(Z%(I)/32))+","; + RETURN REM REM COUNT_STRINGS() -> P2 REM COUNT_STRINGS: @@ -73,36 +75,132 @@ REM #cbm PRINT "Arrays :"STR$(P4-P3) REM #cbm PRINT "String Heap :"STR$(P7-P5) REM #cbm RETURN REM +REM REM PR_MEMORY_VALUE(I) -> J: +REM REM - I is memory value to print +REM REM - I is returned as last byte of value printed +REM REM - J is returned as type +REM PR_MEMORY_VALUE: +REM J=Z%(I)AND 31 +REM P3=Z%(I+1) +REM PRINT " "+STR$(I)+": type:"+STR$(J); +REM IF J<>15 THEN PRINT ", refs:"+STR$((Z%(I)-J)/32); +REM IF J=15 THEN PRINT ", size:"+STR$((Z%(I)AND-32)/32); +REM PRINT ", ["+STR$(Z%(I));+" |"+STR$(P3); +REM IF J<6 OR J=9 OR J=12 OR J=15 THEN PRINT " | --- | --- ]";:GOTO PR_MEM_SKIP +REM PRINT " |"+STR$(Z%(I+2)); +REM IF J=6 OR J=7 OR J=13 OR J=14 THEN PRINT " | --- ]";:GOTO PR_MEM_SKIP +REM PRINT " |"+STR$(Z%(I+3))+" ]"; +REM PR_MEM_SKIP: +REM PRINT " >> "; +REM ON J+1 GOTO PR_ENTRY_NIL,PR_ENTRY_BOOL,PR_ENTRY_INT,PR_ENTRY_FLOAT,PR_ENTRY_STR,PR_ENTRY_SYM,PR_ENTRY_LIST,PR_ENTRY_VECTOR,PR_ENTRY_HASH_MAP,PR_ENTRY_FN,PR_ENTRY_MALFN,PR_ENTRY_MAC,PR_ENTRY_ATOM,PR_ENTRY_ENV,PR_ENTRY_META,PR_ENTRY_FREE +REM PRINT "Unknown type:"+STR$(J):END +REM +REM PR_ENTRY_NIL: +REM PRINT "nil" +REM I=I+1 +REM RETURN +REM PR_ENTRY_BOOL: +REM IF P3=0 THEN PRINT "false" +REM IF P3=1 THEN PRINT "true" +REM I=I+1 +REM RETURN +REM PR_ENTRY_INT: +REM PR_ENTRY_FLOAT: +REM PRINT STR$(P3) +REM I=I+1 +REM RETURN +REM PR_ENTRY_STR: +REM PRINT "'"+S$(P3)+"'" +REM I=I+1 +REM RETURN +REM PR_ENTRY_SYM: +REM PRINT S$(P3) +REM I=I+1 +REM RETURN +REM PR_ENTRY_LIST: +REM I=I+2 +REM IF I<16 THEN PRINT "()":RETURN +REM PRINT "(..."+STR$(Z%(I))+" ...)" +REM RETURN +REM PR_ENTRY_VECTOR: +REM I=I+2 +REM IF I<16 THEN PRINT "[]":RETURN +REM PRINT "[..."+STR$(Z%(I))+" ...]" +REM RETURN +REM PR_ENTRY_HASH_MAP: +REM I=I+3 +REM IF I<16 THEN PRINT "{}":RETURN +REM IF J=8 THEN PRINT "{... key:"+STR$(Z%(I-1))+", val:"+STR$(Z%(I))+" ...}" +REM RETURN +REM PR_ENTRY_FN: +REM PRINT "#" +REM I=I+1 +REM RETURN +REM PR_ENTRY_MALFN: +REM PR_ENTRY_MAC: +REM IF I=11 THEN PRINT "MACRO "; +REM PRINT "(fn* param:"+STR$(Z%(I))+", env:"+STR$(Z%(I+1))+")" +REM I=I+3 +REM RETURN +REM PR_ENTRY_ATOM: +REM PRINT "(atom val:"+STR$(P3)+")" +REM I=I+1 +REM RETURN +REM PR_ENTRY_ENV: +REM PRINT "#" +REM I=I+2 +REM RETURN +REM PR_ENTRY_META: +REM PRINT "#" +REM I=I+2 +REM RETURN +REM PR_ENTRY_FREE: +REM PRINT "FREE next:"+STR$(P3); +REM IF I=ZK THEN PRINT " (free list start)"; +REM PRINT +REM I=I-1+(Z%(I)AND-32)/32 +REM RETURN +REM +REM REM PR_OBJECT(P1) -> nil +REM PR_OBJECT: +REM RD=0 +REM +REM IF P1=-1 THEN PRINT " "+STR$(-1)+": ---":RETURN +REM RD=RD+1 +REM Q=P1:GOSUB PUSH_Q +REM +REM PR_OBJ_LOOP: +REM IF RD=0 THEN RETURN +REM RD=RD-1 +REM +REM GOSUB PEEK_Q:I=Q +REM REM IF I<15 THEN GOSUB POP_Q:GOTO PR_OBJ_LOOP +REM GOSUB PR_MEMORY_VALUE +REM REM J holds type now +REM GOSUB POP_Q:I=Q +REM +REM IF J<6 OR J=9 THEN GOTO PR_OBJ_LOOP: REM no contained references +REM REM reference in first position +REM IF Z%(I+1)<>0 THEN RD=RD+1:Q=Z%(I+1):GOSUB PUSH_Q +REM IF J=12 OR J=15 THEN PR_OBJ_LOOP: REM no more reference +REM REM reference in second position +REM IF Z%(I+2)<>0 THEN RD=RD+1:Q=Z%(I+2):GOSUB PUSH_Q +REM IF J=6 OR J=7 OR J=13 OR J=14 THEN PR_OBJ_LOOP: REM no more references +REM IF Z%(I+3)<>0 THEN RD=RD+1:Q=Z%(I+3):GOSUB PUSH_Q +REM GOTO PR_OBJ_LOOP +REM REM REM PR_MEMORY(P1, P2) -> nil REM PR_MEMORY: REM IF P2"+STR$(P2); +REM PRINT "Values (Z%)"+STR$(P1)+" ->"+STR$(P2); REM PRINT " (ZI: "+STR$(ZI)+", ZK: "+STR$(ZK)+"):" REM IF P2P2 THEN GOTO PR_MEMORY_AFTER_VALUES -REM PRINT " "+STR$(I); -REM IF (Z%(I,0)AND 31)=15 THEN GOTO PR_MEMORY_FREE -REM PRINT ": ref cnt: "+STR$((Z%(I,0)AND-32)/32); -REM PRINT ", type: "+STR$(Z%(I,0)AND 31)+", value: "+STR$(Z%(I,1)); -REM IF (Z%(I,0)AND 31)=4 THEN PRINT " '"+S$(Z%(I,1))+"'"; -REM IF (Z%(I,0)AND 31)=5 THEN PRINT " "+S$(Z%(I,1))+""; -REM PRINT -REM I=I+1 -REM IF (Z%(I-1,0)AND 31)<>10 THEN GOTO PR_MEMORY_VALUE_LOOP -REM PRINT " "+STR$(I)+": "; -REM PRINT "params: "+STR$(Z%(I+1,0))+", env:"+STR$(Z%(I+1,1)) -REM I=I+1 -REM GOTO PR_MEMORY_VALUE_LOOP -REM PR_MEMORY_FREE: -REM PRINT ": FREE size: "+STR$((Z%(I,0)AND-32)/32)+", next: "+STR$(Z%(I,1)); -REM IF I=ZK THEN PRINT " (free list start)"; -REM PRINT -REM IF (Z%(I,0)AND-32)=64 THEN I=I+1:PRINT " "+STR$(I)+": ---" -REM I=I+1 -REM GOTO PR_MEMORY_VALUE_LOOP +REM GOSUB PR_MEMORY_VALUE +REM I=I+1 +REM GOTO PR_MEMORY_VALUE_LOOP REM PR_MEMORY_AFTER_VALUES: REM PRINT "S$ String Memory (S: "+STR$(S)+"):" REM IF S<=0 THEN PRINT " ---":GOTO PR_MEMORY_SKIP_STRINGS @@ -120,43 +218,5 @@ REM #qbasic FOR I=0 TO X REM #qbasic #qbasic PRINT " "+STR$(I)+": "+STR$(X%(I)) REM #qbasic NEXT I REM PR_MEMORY_SKIP_STACK: -REM PRINT "^^^^^^" REM RETURN REM -REM REM PR_OBJECT(P1) -> nil -REM PR_OBJECT: -REM RD=0 -REM -REM IF P1=-1 THEN PRINT " "+STR$(-1)+": ---":RETURN -REM RD=RD+1 -REM Q=P1:GOSUB PUSH_Q -REM -REM PR_OBJ_LOOP: -REM IF RD=0 THEN RETURN -REM GOSUB POP_Q:I=Q -REM RD=RD-1 -REM -REM P2=Z%(I,0)AND 31 -REM P3=Z%(I,1) -REM PRINT " "+STR$(I); -REM PRINT ": ref cnt:"+STR$((Z%(I,0)AND-32)/32); -REM PRINT ", type:"+STR$(P2)+", value: "+STR$(Z%(I,1)); -REM IF P2=2 THEN PRINT " "+STR$(Z%(I,1)); -REM IF P2=4 THEN PRINT " '"+S$(Z%(I,1))+"'"; -REM IF P2=5 THEN PRINT " "+S$(Z%(I,1)); -REM IF P2=6 AND P3>0 THEN PRINT " ()"; -REM IF P2=6 AND P3=0 THEN PRINT " )"; -REM IF P2=7 AND P3>0 THEN PRINT " []"; -REM IF P2=7 AND P3=0 THEN PRINT " ]"; -REM IF P2=8 AND P3>0 THEN PRINT " {}"; -REM IF P2=8 AND P3=0 THEN PRINT " }"; -REM IF P2=9 THEN PRINT " #"; -REM PRINT -REM IF P2=8 THEN PRINT " "+STR$(I+1)+": key:"+STR$(Z%(I+1,0)); -REM IF P2=8 THEN PRINT ", value:"+STR$(Z%(I+1,1)) -REM -REM IF P2<=5 OR P2=9 THEN GOTO PR_OBJ_LOOP -REM IF Z%(I,1)<>0 THEN RD=RD+1:Q=Z%(I,1):GOSUB PUSH_Q -REM IF P2=8 AND I<>7 THEN RD=RD+2:Q=Z%(I+1,1):GOSUB PUSH_Q:Q=Z%(I+1,0):GOSUB PUSH_Q -REM IF P2>5 AND P2<8 THEN RD=RD+1:Q=I+1:GOSUB PUSH_Q -REM GOTO PR_OBJ_LOOP diff --git a/basic/env.in.bas b/basic/env.in.bas index 37331665f7..bb8cfeb30e 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -6,7 +6,7 @@ ENV_NEW: AY=R REM set the outer and data pointer - T=13:L=R:N=C:GOSUB ALLOC + T=13:L=R:M=C:GOSUB ALLOC GOSUB RELEASE: REM environment takes ownership RETURN @@ -18,29 +18,26 @@ ENV_NEW_BINDS: E=R REM process bindings ENV_NEW_BINDS_LOOP: - IF Z%(A,1)=0 THEN R=E:RETURN + IF Z%(A+1)=0 THEN R=E:RETURN REM get/deref the key from A - R=A:GOSUB VAL_R - K=R + K=Z%(A+2) - IF S$(Z%(K,1))="&" THEN GOTO EVAL_NEW_BINDS_VARGS + IF S$(Z%(K+1))="&" THEN GOTO EVAL_NEW_BINDS_VARGS EVAL_NEW_BINDS_1x1: REM get/deref the key from B - R=B:GOSUB VAL_R - C=R + C=Z%(B+2) REM set the binding in the environment data GOSUB ENV_SET REM go to next element of A and B - A=Z%(A,1) - B=Z%(B,1) + A=Z%(A+1) + B=Z%(B+1) GOTO ENV_NEW_BINDS_LOOP EVAL_NEW_BINDS_VARGS: REM get/deref the key from next element of A - A=Z%(A,1) - R=A:GOSUB VAL_R - K=R + A=Z%(A+1) + K=Z%(A+2) REM the value is the remaining list in B A=B:T=6:GOSUB FORCE_SEQ_TYPE C=R @@ -52,17 +49,17 @@ ENV_NEW_BINDS: REM ENV_SET(E, K, C) -> R ENV_SET: - H=Z%(E,1) + H=Z%(E+1) GOSUB ASSOC1 - Z%(E,1)=R + Z%(E+1)=R R=C RETURN REM ENV_SET_S(E, B$, C) -> R ENV_SET_S: - H=Z%(E,1) + H=Z%(E+1) GOSUB ASSOC1_S - Z%(E,1)=R + Z%(E+1)=R R=C RETURN @@ -72,12 +69,12 @@ REM in R4 SUB ENV_FIND T=E ENV_FIND_LOOP: - H=Z%(T,1) + H=Z%(T+1) REM More efficient to use GET for value (R) and contains? (R3) GOSUB HASHMAP_GET REM if we found it, save value in R4 for ENV_GET IF R3=1 THEN R4=R:R=T:GOTO ENV_FIND_DONE - T=Z%(T+1,1): REM get outer environment + T=Z%(T+2): REM get outer environment IF T>0 THEN GOTO ENV_FIND_LOOP R=-1 ENV_FIND_DONE: @@ -86,7 +83,7 @@ END SUB REM ENV_GET(E, K) -> R ENV_GET: CALL ENV_FIND - IF R=-1 THEN ER=-1:E$="'"+S$(Z%(K,1))+"' not found":GOTO ENV_GET_RETURN + IF R=-1 THEN ER=-1:E$="'"+S$(Z%(K+1))+"' not found":GOTO ENV_GET_RETURN R=R4 - Z%(R,0)=Z%(R,0)+32 + Z%(R)=Z%(R)+32 GOTO ENV_GET_RETURN diff --git a/basic/mem.in.bas b/basic/mem.in.bas new file mode 100644 index 0000000000..900447d294 --- /dev/null +++ b/basic/mem.in.bas @@ -0,0 +1,343 @@ +REM Memory layout: +REM +REM type bytes +REM ---------- ---------- +REM nil ref/ 0 | 0 | | +REM false ref/ 1 | 0 | | +REM true ref/ 1 | 1 | | +REM integer ref/ 2 | int | | +REM float ref/ 3 | ??? | | +REM string/kw ref/ 4 | S$ idx | | +REM symbol ref/ 5 | S$ idx | | +REM list ref/ 6 | next Z% idx | val Z% idx | +REM vector ref/ 7 | next Z% idx | val Z% idx | +REM hashmap ref/ 8 | next Z% idx | key Z% idx | val Z% idx +REM function ref/ 9 | fn idx | | +REM mal function ref/10 | body Z% idx | param Z% idx | env Z% idx +REM macro fn ref/11 | body Z% idx | param Z% idx | env Z% idx +REM atom ref/12 | val Z% idx | | +REM environment ref/13 | hmap Z% idx | outer Z% idx | +REM metadata ref/14 | obj Z% idx | meta Z% idx | +REM FREE sz/15 | next Z% idx | | +REM +REM The first 15 locations are constant/persistent values: +REM 0: nil +REM 2: false +REM 4: true +REM 6: empty list +REM 9: empty vector +REM 12: empty hash-map + +REM Note: The INIT_MEMORY function is at end of this file for +REM efficiency. The most commonly used function should be at the top +REM since BASIC scans line numbers for every GOTO/GOSUB + + +REM stack functions + +#qbasic PUSH_A: +#qbasic X=X+1:X%(X)=A:RETURN +#qbasic POP_A: +#qbasic A=X%(X):X=X-1:RETURN +#qbasic +#qbasic PUSH_R: +#qbasic X=X+1:X%(X)=R:RETURN +#qbasic POP_R: +#qbasic R=X%(X):X=X-1:RETURN +#qbasic +#qbasic PUSH_Q: +#qbasic X=X+1:X%(X)=Q:RETURN +#qbasic POP_Q: +#qbasic Q=X%(X):X=X-1:RETURN +#qbasic PEEK_Q: +#qbasic Q=X%(X):RETURN +#qbasic PEEK_Q_1: +#qbasic Q=X%(X-1):RETURN +#qbasic PEEK_Q_2: +#qbasic Q=X%(X-2):RETURN +#qbasic PEEK_Q_Q: +#qbasic Q=X%(X-Q):RETURN +#qbasic PUT_Q: +#qbasic X%(X)=Q:RETURN +#qbasic PUT_Q_1: +#qbasic X%(X-1)=Q:RETURN +#qbasic PUT_Q_2: +#qbasic X%(X-2)=Q:RETURN + +#cbm PUSH_A: +#cbm X=X+2:POKE X,A AND255:POKE X+1,A/256:RETURN +#cbm POP_A: +#cbm A=PEEK(X)+PEEK(X+1)*256:X=X-2:RETURN +#cbm +#cbm PUSH_R: +#cbm X=X+2:POKE X,R AND255:POKE X+1,R/256:RETURN +#cbm POP_R: +#cbm R=PEEK(X)+PEEK(X+1)*256:X=X-2:RETURN +#cbm +#cbm PUSH_Q: +#cbm X=X+2:POKE X,Q AND255:POKE X+1,Q/256:RETURN +#cbm POP_Q: +#cbm Q=PEEK(X)+PEEK(X+1)*256:X=X-2:RETURN +#cbm PEEK_Q: +#cbm Q=PEEK(X)+PEEK(X+1)*256:RETURN +#cbm PEEK_Q_1: +#cbm Q=PEEK(X-2)+PEEK(X-1)*256:RETURN +#cbm PEEK_Q_2: +#cbm Q=PEEK(X-4)+PEEK(X-3)*256:RETURN +#cbm PEEK_Q_Q: +#cbm Q=PEEK(X-Q*2)+PEEK(X-Q*2+1)*256:RETURN +#cbm PUT_Q: +#cbm POKE X,Q AND255:POKE X+1,Q/256:RETURN +#cbm PUT_Q_1: +#cbm POKE X-2,Q AND255:POKE X-1,Q/256:RETURN +#cbm PUT_Q_2: +#cbm POKE X-4,Q AND255:POKE X-3,Q/256:RETURN + +REM memory functions + +REM ALLOC(T,L) -> R +REM ALLOC(T,L,M) -> R +REM ALLOC(T,L,M,N) -> R +REM L is value for Z%(R+1) +REM M is value for Z%(R+2), if SZ>2 +REM N is value for Z%(R+3), if SZ>3 +ALLOC: + SZ=3 + IF T<6 OR T=9 OR T=12 THEN SZ=2 + IF T=8 OR T=10 OR T=11 THEN SZ=4 + REM PRINT "ALLOC T: "+STR$(T)+", SZ: "+STR$(SZ)+", ZK: "+STR$(ZK) + U=ZK + R=ZK + ALLOC_LOOP: + IF R=ZI THEN GOTO ALLOC_UNUSED + REM TODO sanity check that type is 15 + IF ((Z%(R)AND-32)/32)=SZ THEN GOTO ALLOC_MIDDLE + REM PRINT "ALLOC search: U: "+STR$(U)+", R: "+STR$(R) + U=R: REM previous set to current + R=Z%(R+1): REM current set to next + GOTO ALLOC_LOOP + ALLOC_MIDDLE: + REM PRINT "ALLOC_MIDDLE: U: "+STR$(U)+", R: "+STR$(R) + REM set free pointer (ZK) to next free + IF R=ZK THEN ZK=Z%(R+1) + REM set previous free to next free + IF R<>ZK THEN Z%(U+1)=Z%(R+1) + GOTO ALLOC_DONE + ALLOC_UNUSED: + REM PRINT "ALLOC_UNUSED ZI: "+STR$(ZI)+", U: "+STR$(U)+", R: "+STR$(R) + IF R+SZ>Z1 THEN PRINT "Out of mal memory!":END + ZI=ZI+SZ + IF U=R THEN ZK=ZI + REM set previous free to new memory top + IF U<>R THEN Z%(U+1)=ZI + GOTO ALLOC_DONE + ALLOC_DONE: + Z%(R)=T+32 + REM set Z%(R+1) to default L + Z%(R+1)=L + IF T>5 AND T<>9 THEN Z%(L)=Z%(L)+32: REM value is a Z% idx + IF SZ>2 THEN Z%(M)=Z%(M)+32:Z%(R+2)=M + IF SZ>3 THEN Z%(N)=Z%(N)+32:Z%(R+3)=N + + RETURN + +REM FREE(AY, SZ) -> nil +FREE: + REM assumes reference count cleanup already (see RELEASE) + Z%(AY)=(SZ*32)+15: REM set type(15) and size + Z%(AY+1)=ZK + ZK=AY + IF SZ>=3 THEN Z%(AY+2)=0 + IF SZ=4 THEN Z%(AY+3)=0 + REM TODO: fail if SZ>4 + RETURN + + +REM RELEASE(AY) -> nil +REM R should not be affected by this call +RELEASE: + RC=0 + + GOTO RELEASE_ONE + + RELEASE_TOP: + + IF RC=0 THEN RETURN + + REM pop next object to release, decrease remaining count + GOSUB POP_Q:AY=Q + RC=RC-1 + + RELEASE_ONE: + IF AY=-1 THEN RETURN + + U=Z%(AY)AND 31: REM type + V=Z%(AY+1): REM main value/reference + + REM set the size + REM TODO: share with ALLOC calculation + SZ=3 + IF U<6 OR U=9 OR U=12 THEN SZ=2 + IF U=8 OR U=10 OR U=11 THEN SZ=4 + + REM AZ=AY: B=1: GOSUB PR_STR + REM PRINT "RELEASE AY:"+STR$(AY)+" ["+R$+"] (byte0:"+STR$(Z%(AY))+", SZ:"+STR$(SZ)+")" + + REM sanity check not already freed + IF (U)=15 THEN PRINT "RELEASE of free:"+STR$(AY):END + IF Z%(AY)<15 THEN PRINT "RELEASE of unowned:"+STR$(AY):END + + REM decrease reference count by one + Z%(AY)=Z%(AY)-32 + + REM nil, false, true, empty sequences + IF AY<16 AND Z%(AY)<32 THEN PRINT "RELEASE of empty:"+STR$(AY):END + IF AY<16 THEN GOTO RELEASE_TOP + + REM our reference count is not 0, so don't release + IF Z%(AY)>=32 GOTO RELEASE_TOP + + REM switch on type + ON U+1 GOSUB RELEASE_SIMPLE,RELEASE_SIMPLE,RELEASE_SIMPLE,RELEASE_SIMPLE,RELEASE_STRING,RELEASE_STRING,RELEASE_SEQ,RELEASE_SEQ,RELEASE_HASH_MAP,RELEASE_SIMPLE,RELEASE_MAL_FUNCTION,RELEASE_MAL_FUNCTION,RELEASE_ATOM,RELEASE_ENV,RELEASE_METADATA + + REM free the current element and continue, SZ already set + GOSUB FREE + GOTO RELEASE_TOP + + RELEASE_SIMPLE: + RETURN + RELEASE_STRING: + REM string type, release interned string, then FREE reference + IF S%(V)=0 THEN ER=-1:E$="RELEASE of free string:"+STR$(S%(V)):RETURN + S%(V)=S%(V)-1 + IF S%(V)=0 THEN S$(V)="": REM free BASIC string + REM free the atom itself + RETURN + RELEASE_SEQ: + IF V=0 THEN RETURN + REM add value and next element to stack + RC=RC+2 + Q=Z%(AY+2):GOSUB PUSH_Q + Q=V:GOSUB PUSH_Q + RETURN + RELEASE_HASH_MAP: + IF V=0 THEN RETURN + REM add key, value and next element to stack + RC=RC+3 + Q=Z%(AY+2):GOSUB PUSH_Q + Q=Z%(AY+3):GOSUB PUSH_Q + Q=V:GOSUB PUSH_Q + RETURN + RELEASE_ATOM: + REM add contained/referred value + RC=RC+1 + Q=V:GOSUB PUSH_Q + REM free the atom itself + RETURN + RELEASE_MAL_FUNCTION: + REM add ast, params and environment to stack + RC=RC+3 + Q=V:GOSUB PUSH_Q + Q=Z%(AY+2):GOSUB PUSH_Q + Q=Z%(AY+3):GOSUB PUSH_Q + REM free the current 3 element mal_function + RETURN + RELEASE_ENV: + REM add the hashmap data to the stack + RC=RC+1 + Q=V:GOSUB PUSH_Q + REM if outer set, add outer env to stack + IF Z%(AY+2)<>0 THEN RC=RC+1:Q=Z%(AY+2):GOSUB PUSH_Q + RETURN + RELEASE_METADATA: + REM add object and metadata object + RC=RC+2 + Q=V:GOSUB PUSH_Q + Q=Z%(AY+2):GOSUB PUSH_Q + RETURN + + +REM release stack functions + +#qbasic PEND_A_LV: +#qbasic Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV:RETURN +#qbasic +#qbasic REM RELEASE_PEND(LV) -> nil +#qbasic RELEASE_PEND: +#qbasic IF Y<0 THEN RETURN +#qbasic IF Y%(Y,1)<=LV THEN RETURN +#qbasic REM PRINT "RELEASE_PEND releasing:"+STR$(Y%(Y,0)) +#qbasic AY=Y%(Y,0):GOSUB RELEASE +#qbasic Y=Y-1 +#qbasic GOTO RELEASE_PEND + +#cbm PEND_A_LV: +#cbm Y=Y+4:POKE Y,A AND255:POKE Y+1,A/256 +#cbm POKE Y+2,LV AND255:POKE Y+3,LV/256:RETURN +#cbm +#cbm REM RELEASE_PEND(LV) -> nil +#cbm RELEASE_PEND: +#cbm IF Y=0 : pointer to error object + ER=-2 + E$="" + + REM TODO: for performance, define all/most non-array variables here + REM so that the array area doesn't have to be shifted down everytime + REM a new non-array variable is defined + + REM boxed element memory + DIM Z%(Z1): REM TYPE ARRAY + + REM Predefine nil, false, true, and an empty sequences + FOR I=0 TO 15:Z%(I)=0:NEXT I + Z%(0)=32: REM nil + Z%(2)=1+32: REM false + Z%(4)=1+32:Z%(5)=1: REM true + Z%(6)=6+32: REM emtpy list + Z%(9)=7+32: REM empty vector + Z%(12)=8+32: REM empty hash-map + + REM start of unused memory + ZI=16 + + REM start of free list + ZK=16 + + REM string memory storage + S=0:DIM S$(Z2):DIM S%(Z2) + + REM call/logic stack + #qbasic X=-1:DIM X%(Z3): REM stack of Z% indexes + #cbm X=Z3-2: REM stack of 1920 Z% indexes at $C000 + + REM pending release stack + #qbasic Y=-1:DIM Y%(Z4,1): REM stack of Z% indexes and level/LV values + #cbm Y=Z4-4: REM stack of 64 Y% indexes/levels at $CF00 + + BT=TI + + RETURN + + diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 14ec9e0b0d..067d617e1b 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -2,12 +2,12 @@ REM PR_STR(AZ, B) -> R$ PR_STR: R$="" PR_STR_RECUR: - T=Z%(AZ,0)AND 31 - U=Z%(AZ,1) + T=Z%(AZ)AND 31 + U=Z%(AZ+1) REM PRINT "AZ: "+STR$(AZ)+", T: "+STR$(T)+", U: "+STR$(U) IF T=0 THEN R$="nil":RETURN REM if metadata, then get actual object - IF T>=16 THEN AZ=U:GOTO PR_STR_RECUR + IF T>=14 THEN AZ=U:GOTO PR_STR_RECUR ON T GOTO PR_BOOLEAN,PR_INTEGER,PR_UNKNOWN,PR_STRING_MAYBE,PR_SYMBOL,PR_SEQ,PR_SEQ,PR_SEQ,PR_FUNCTION,PR_MAL_FUNCTION,PR_MAL_FUNCTION,PR_ATOM,PR_ENV,PR_RECUR,PR_FREE PR_UNKNOWN: @@ -49,22 +49,21 @@ PR_STR: REM save the current rendered string S$(S)=R$:S=S+1 PR_SEQ_LOOP: - IF Z%(AZ,1)=0 THEN GOTO PR_SEQ_DONE - IF T<>8 THEN AZ=AZ+1:GOSUB PR_STR:GOSUB PEEK_Q_1:T=Q - IF T=8 THEN AZ=Z%(AZ+1,0):GOSUB PR_STR:GOSUB PEEK_Q_1:T=Q + IF Z%(AZ+1)=0 THEN GOTO PR_SEQ_DONE + AZ=Z%(AZ+2):GOSUB PR_STR:GOSUB PEEK_Q_1:T=Q REM append what we just rendered it S$(S-1)=S$(S-1)+R$ REM if this is a hash-map, print the next element - IF T=8 THEN GOSUB PEEK_Q:AZ=Z%(Q+1,1):GOSUB PR_STR:S$(S-1)=S$(S-1)+" "+R$ + IF T=8 THEN GOSUB PEEK_Q:AZ=Z%(Q+3):GOSUB PR_STR:S$(S-1)=S$(S-1)+" "+R$ REM restore current seq type GOSUB PEEK_Q_1:T=Q REM Go to next list element GOSUB PEEK_Q - AZ=Z%(Q,1) + AZ=Z%(Q+1) Q=AZ:GOSUB PUT_Q - IF Z%(AZ,1)<>0 THEN S$(S-1)=S$(S-1)+" " + IF Z%(AZ+1)<>0 THEN S$(S-1)=S$(S-1)+" " GOTO PR_SEQ_LOOP PR_SEQ_DONE: REM restore the current string @@ -82,10 +81,10 @@ PR_STR: RETURN PR_MAL_FUNCTION: T1=AZ - AZ=Z%(T1+1,0):GOSUB PR_STR + AZ=Z%(T1+2):GOSUB PR_STR REM append what we just rendered it S$(S)="(fn* "+R$:S=S+1 - AZ=Z%(T1,1):GOSUB PR_STR + AZ=Z%(T1+1):GOSUB PR_STR S=S-1 R$=S$(S)+" "+R$+")" RETURN @@ -107,10 +106,10 @@ PR_STR_SEQ: V=AZ S$(S)="":S=S+1 PR_STR_SEQ_LOOP: - IF Z%(V,1)=0 THEN S=S-1:R$=S$(S):RETURN - AZ=V+1:GOSUB PR_STR + IF Z%(V+1)=0 THEN S=S-1:R$=S$(S):RETURN + AZ=Z%(V+2):GOSUB PR_STR REM goto the next sequence element - V=Z%(V,1) - IF Z%(V,1)=0 THEN S$(S-1)=S$(S-1)+R$ - IF Z%(V,1)<>0 THEN S$(S-1)=S$(S-1)+R$+B$ + V=Z%(V+1) + IF Z%(V+1)=0 THEN S$(S-1)=S$(S-1)+R$ + IF Z%(V+1)<>0 THEN S$(S-1)=S$(S-1)+R$+B$ GOTO PR_STR_SEQ_LOOP diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 4e1ae352dc..de915c4b9a 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -65,7 +65,7 @@ SUB READ_FORM IF ER<>-2 THEN GOTO READ_FORM_RETURN GOSUB READ_TOKEN REM PRINT "READ_FORM T$: ["+T$+"]" - IF T$="" THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO READ_FORM_RETURN + IF T$="" THEN R=0:Z%(R)=Z%(R)+32:GOTO READ_FORM_RETURN IF T$="nil" THEN T=0:GOTO READ_NIL_BOOL IF T$="false" THEN T=1:GOTO READ_NIL_BOOL IF T$="true" THEN T=2:GOTO READ_NIL_BOOL @@ -91,8 +91,8 @@ SUB READ_FORM READ_NIL_BOOL: REM PRINT "READ_NIL_BOOL" - R=T - Z%(R,0)=Z%(R,0)+32 + R=T*2 + Z%(R)=Z%(R)+32 GOTO READ_FORM_RETURN READ_NUMBER: REM PRINT "READ_NUMBER" @@ -181,18 +181,14 @@ SUB READ_FORM IF ER<>-2 OR T$=CHR$(Q) THEN GOTO READ_SEQ_DONE CALL READ_FORM + M=R: REM value (or key for hash-maps) REM if error, release the unattached element IF ER<>-2 THEN AY=R:GOSUB RELEASE:GOTO READ_SEQ_DONE REM if this is a hash-map, READ_FORM again IF T=8 THEN GOSUB PUSH_R:CALL READ_FORM - IF T=8 THEN GOSUB POP_Q:M=Q: REM key value - - REM main value - REM for list/vector this is result of the first READ_FORM - N=R - + IF T=8 THEN N=R:GOSUB POP_Q:M=Q: REM set key and value REM update the return sequence structure REM release N since list takes full ownership @@ -206,11 +202,9 @@ SUB READ_FORM GOSUB MAP_LOOP_DONE GOSUB POP_Q: REM pop end character ptr -REM P1=R:PRINT "READ_SEQ R:":GOSUB PR_OBJECT GOTO READ_FORM_RETURN READ_FORM_RETURN: -REM IF ER<>-2 THEN R=0:Z%(R,0)=Z%(R,0)+32 RI=RI+LEN(T$) GOSUB POP_Q:T=Q: REM restore current value of T diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index 90e8d9e6fb..2e5d045a64 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -1,5 +1,6 @@ GOTO MAIN +REM $INCLUDE: 'mem.in.bas' REM $INCLUDE: 'types.in.bas' REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index 424d5fa07f..98fba99c83 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -1,5 +1,6 @@ GOTO MAIN +REM $INCLUDE: 'mem.in.bas' REM $INCLUDE: 'types.in.bas' REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' @@ -22,19 +23,19 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A,0)AND 31 + T=Z%(A)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R,0)=Z%(R,0)+32 + Z%(R)=Z%(R)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: H=E:K=A:GOSUB HASHMAP_GET - IF R3=0 THEN R=-1:ER=-1:E$="'"+S$(Z%(A,1))+"' not found":GOTO EVAL_AST_RETURN - Z%(R,0)=Z%(R,0)+32 + IF R3=0 THEN R=-1:ER=-1:E$="'"+S$(Z%(A+1))+"' not found":GOTO EVAL_AST_RETURN + Z%(R)=Z%(R)+32 GOTO EVAL_AST_RETURN EVAL_AST_SEQ: @@ -43,16 +44,17 @@ SUB EVAL_AST EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence - IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM call EVAL for each entry GOSUB PUSH_A - IF T<>8 THEN GOSUB VAL_A - IF T=8 THEN A=Z%(A+1,1) + IF T<>8 THEN A=Z%(A+2) + IF T=8 THEN A=Z%(A+3) Q=T:GOSUB PUSH_Q: REM push/save type CALL EVAL GOSUB POP_Q:T=Q: REM pop/restore type GOSUB POP_A + M=R REM if error, release the unattached element REM TODO: is R=0 correct? @@ -60,17 +62,15 @@ SUB EVAL_AST REM for hash-maps, copy the key (inc ref since we are going to REM release it below) - IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 + IF T=8 THEN N=M:M=Z%(A+2):Z%(M)=Z%(M)+32 - REM value evaluated above - N=R REM update the return sequence structure REM release N (and M if T=8) since seq takes full ownership C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list - A=Z%(A,1) + A=Z%(A+1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: @@ -107,7 +107,7 @@ SUB EVAL APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN EVAL_INVOKE: CALL EVAL_AST @@ -116,10 +116,10 @@ SUB EVAL REM if error, return f/args for release by caller IF ER<>-2 THEN GOTO EVAL_RETURN - AR=Z%(R,1): REM rest - GOSUB VAL_R:F=R + AR=Z%(R+1): REM rest + F=Z%(R+2) - IF (Z%(F,0)AND 31)<>9 THEN R=-1:ER=-1:E$="apply of non-function":GOTO EVAL_INVOKE_DONE + IF (Z%(F)AND 31)<>9 THEN R=-1:ER=-1:E$="apply of non-function":GOTO EVAL_INVOKE_DONE GOSUB DO_FUNCTION EVAL_INVOKE_DONE: AY=W:GOSUB RELEASE @@ -147,11 +147,11 @@ DO_FUNCTION: AR$=R$ REM Get the function number - G=Z%(F,1) + G=Z%(F+1) REM Get argument values - R=AR:GOSUB VAL_R:A=Z%(R,1) - R=Z%(AR,1):GOSUB VAL_R:B=Z%(R,1) + A=Z%(Z%(AR+2)+1) + B=Z%(Z%(Z%(AR+1)+2)+1) REM Switch on the function number IF G=1 THEN GOTO DO_ADD diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index f2329baae7..2336c4286f 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -1,5 +1,6 @@ GOTO MAIN +REM $INCLUDE: 'mem.in.bas' REM $INCLUDE: 'types.in.bas' REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' @@ -23,13 +24,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A,0)AND 31 + T=Z%(A)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R,0)=Z%(R,0)+32 + Z%(R)=Z%(R)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -43,16 +44,17 @@ SUB EVAL_AST EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence - IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM call EVAL for each entry GOSUB PUSH_A - IF T<>8 THEN GOSUB VAL_A - IF T=8 THEN A=Z%(A+1,1) + IF T<>8 THEN A=Z%(A+2) + IF T=8 THEN A=Z%(A+3) Q=T:GOSUB PUSH_Q: REM push/save type CALL EVAL GOSUB POP_Q:T=Q: REM pop/restore type GOSUB POP_A + M=R REM if error, release the unattached element REM TODO: is R=0 correct? @@ -60,17 +62,15 @@ SUB EVAL_AST REM for hash-maps, copy the key (inc ref since we are going to REM release it below) - IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 + IF T=8 THEN N=M:M=Z%(A+2):Z%(M)=Z%(M)+32 - REM value evaluated above - N=R REM update the return sequence structure REM release N (and M if T=8) since seq takes full ownership C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list - A=Z%(A,1) + A=Z%(A+1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: @@ -107,27 +107,24 @@ SUB EVAL APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN - A0=Z%(A+1,1) + A0=Z%(A+2) REM get symbol in A$ - IF (Z%(A0,0)AND 31)<>5 THEN A$="" - IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0)AND 31)<>5 THEN A$="" + IF (Z%(A0)AND 31)=5 THEN A$=S$(Z%(A0+1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET GOTO EVAL_INVOKE EVAL_GET_A3: - R=Z%(Z%(Z%(A,1),1),1) - GOSUB VAL_R:A3=R + A3=Z%(Z%(Z%(Z%(A+1)+1)+1)+2) EVAL_GET_A2: - R=Z%(Z%(A,1),1) - GOSUB VAL_R:A2=R + A2=Z%(Z%(Z%(A+1)+1)+2) EVAL_GET_A1: - R=Z%(A,1) - GOSUB VAL_R:A1=R + A1=Z%(Z%(A+1)+2) RETURN EVAL_DEF: @@ -153,21 +150,21 @@ SUB EVAL C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1+1)=0 THEN GOTO EVAL_LET_LOOP_DONE Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1):GOSUB VAL_A:CALL EVAL + A=Z%(Z%(A1+1)+2):CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE - REM set environment: even A1 key to odd A1 eval'd above - K=Z%(A1+1,1):C=R:GOSUB ENV_SET + REM set key/value in the environment + K=Z%(A1+2):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements - A1=Z%(Z%(A1,1),1) + A1=Z%(Z%(A1+1)+1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: @@ -181,10 +178,10 @@ SUB EVAL REM if error, return f/args for release by caller IF ER<>-2 THEN GOTO EVAL_RETURN - AR=Z%(R,1): REM rest - GOSUB VAL_R:F=R + AR=Z%(R+1): REM rest + F=Z%(R+2) - IF (Z%(F,0)AND 31)<>9 THEN R=-1:ER=-1:E$="apply of non-function":GOTO EVAL_INVOKE_DONE + IF (Z%(F)AND 31)<>9 THEN R=-1:ER=-1:E$="apply of non-function":GOTO EVAL_INVOKE_DONE GOSUB DO_FUNCTION EVAL_INVOKE_DONE: AY=W:GOSUB RELEASE @@ -209,17 +206,12 @@ END SUB REM DO_FUNCTION(F, AR) DO_FUNCTION: - AZ=F:GOSUB PR_STR - F$=R$ - AZ=AR:GOSUB PR_STR - AR$=R$ - REM Get the function number - G=Z%(F,1) + G=Z%(F+1) REM Get argument values - R=AR:GOSUB VAL_R:A=Z%(R,1) - R=Z%(AR,1):GOSUB VAL_R:B=Z%(R,1) + A=Z%(Z%(AR+2)+1) + B=Z%(Z%(Z%(AR+1)+2)+1) REM Switch on the function number IF G=1 THEN GOTO DO_ADD diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index b79a583ff3..e6f1368d47 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -1,5 +1,6 @@ GOTO MAIN +REM $INCLUDE: 'mem.in.bas' REM $INCLUDE: 'types.in.bas' REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' @@ -22,13 +23,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A,0)AND 31 + T=Z%(A)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R,0)=Z%(R,0)+32 + Z%(R)=Z%(R)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -42,16 +43,17 @@ SUB EVAL_AST EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence - IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM call EVAL for each entry GOSUB PUSH_A - IF T<>8 THEN GOSUB VAL_A - IF T=8 THEN A=Z%(A+1,1) + IF T<>8 THEN A=Z%(A+2) + IF T=8 THEN A=Z%(A+3) Q=T:GOSUB PUSH_Q: REM push/save type CALL EVAL GOSUB POP_Q:T=Q: REM pop/restore type GOSUB POP_A + M=R REM if error, release the unattached element REM TODO: is R=0 correct? @@ -59,17 +61,15 @@ SUB EVAL_AST REM for hash-maps, copy the key (inc ref since we are going to REM release it below) - IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 + IF T=8 THEN N=M:M=Z%(A+2):Z%(M)=Z%(M)+32 - REM value evaluated above - N=R REM update the return sequence structure REM release N (and M if T=8) since seq takes full ownership C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list - A=Z%(A,1) + A=Z%(A+1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: @@ -108,13 +108,13 @@ SUB EVAL APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN - A0=Z%(A+1,1) + A0=Z%(A+2) REM get symbol in A$ - IF (Z%(A0,0)AND 31)<>5 THEN A$="" - IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0)AND 31)<>5 THEN A$="" + IF (Z%(A0)AND 31)=5 THEN A$=S$(Z%(A0+1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -124,14 +124,11 @@ SUB EVAL GOTO EVAL_INVOKE EVAL_GET_A3: - R=Z%(Z%(Z%(A,1),1),1) - GOSUB VAL_R:A3=R + A3=Z%(Z%(Z%(Z%(A+1)+1)+1)+2) EVAL_GET_A2: - R=Z%(Z%(A,1),1) - GOSUB VAL_R:A2=R + A2=Z%(Z%(Z%(A+1)+1)+2) EVAL_GET_A1: - R=Z%(A,1) - GOSUB VAL_R:A1=R + A1=Z%(Z%(A+1)+2) RETURN EVAL_DEF: @@ -157,21 +154,21 @@ SUB EVAL C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1+1)=0 THEN GOTO EVAL_LET_LOOP_DONE Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1):GOSUB VAL_A:CALL EVAL + A=Z%(Z%(A1+1)+2):CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE - REM set environment: even A1 key to odd A1 eval'd above - K=Z%(A1+1,1):C=R:GOSUB ENV_SET + REM set key/value in the environment + K=Z%(A1+2):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements - A1=Z%(Z%(A1,1),1) + A1=Z%(Z%(A1+1)+1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: @@ -179,7 +176,7 @@ SUB EVAL A=A2:CALL EVAL: REM eval A2 using let_env GOTO EVAL_RETURN EVAL_DO: - A=Z%(A,1): REM rest + A=Z%(A+1): REM rest CALL EVAL_AST @@ -194,7 +191,7 @@ SUB EVAL GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL GOSUB POP_A: REM pop/restore A - IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE + IF (R=0) OR (R=2) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: AY=R:GOSUB RELEASE @@ -204,7 +201,7 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R<4 THEN R=0:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -222,14 +219,14 @@ SUB EVAL REM push f/args for release after call GOSUB PUSH_R - AR=Z%(R,1): REM rest - GOSUB VAL_R:F=R + AR=Z%(R+1): REM rest + F=Z%(R+2) REM if metadata, get the actual object - IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) + IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) - IF (Z%(F,0)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller GOSUB POP_R @@ -237,9 +234,9 @@ SUB EVAL EVAL_DO_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP + IF Z%(F+1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION + IF Z%(F+1)>60 THEN CALL DO_TCO_FUNCTION EVAL_DO_FUNCTION_SKIP: REM pop and release f/args @@ -250,8 +247,8 @@ SUB EVAL EVAL_DO_MAL_FUNCTION: Q=E:GOSUB PUSH_Q: REM save the current environment for release - REM create new environ using env stored with function - C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS + REM create new environ using env and params stored in function + C=Z%(F+3):A=Z%(F+2):B=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and @@ -261,7 +258,7 @@ SUB EVAL IF AY<>Q THEN GOSUB RELEASE REM claim the AST before releasing the list containing it - A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 + A=Z%(F+1):Z%(A)=Z%(A)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) LV=LV+1:GOSUB PEND_A_LV:LV=LV-1 diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index 61d94cb5b5..3b88d73b7c 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -1,5 +1,6 @@ GOTO MAIN +REM $INCLUDE: 'mem.in.bas' REM $INCLUDE: 'types.in.bas' REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' @@ -22,13 +23,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A,0)AND 31 + T=Z%(A)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R,0)=Z%(R,0)+32 + Z%(R)=Z%(R)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -42,22 +43,23 @@ SUB EVAL_AST EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence - IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element REM The EVAL_DO call to EVAL_AST must be call #2 for EVAL_AST to REM return early and for TCO to work Q=5:GOSUB PEEK_Q_Q - IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Q=2 AND Z%(Z%(A+1)+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM call EVAL for each entry GOSUB PUSH_A - IF T<>8 THEN GOSUB VAL_A - IF T=8 THEN A=Z%(A+1,1) + IF T<>8 THEN A=Z%(A+2) + IF T=8 THEN A=Z%(A+3) Q=T:GOSUB PUSH_Q: REM push/save type CALL EVAL GOSUB POP_Q:T=Q: REM pop/restore type GOSUB POP_A + M=R REM if error, release the unattached element REM TODO: is R=0 correct? @@ -65,17 +67,15 @@ SUB EVAL_AST REM for hash-maps, copy the key (inc ref since we are going to REM release it below) - IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 + IF T=8 THEN N=M:M=Z%(A+2):Z%(M)=Z%(M)+32 - REM value evaluated above - N=R REM update the return sequence structure REM release N (and M if T=8) since seq takes full ownership C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list - A=Z%(A,1) + A=Z%(A+1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: @@ -114,13 +114,13 @@ SUB EVAL APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN - A0=Z%(A+1,1) + A0=Z%(A+2) REM get symbol in A$ - IF (Z%(A0,0)AND 31)<>5 THEN A$="" - IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0)AND 31)<>5 THEN A$="" + IF (Z%(A0)AND 31)=5 THEN A$=S$(Z%(A0+1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -130,14 +130,11 @@ SUB EVAL GOTO EVAL_INVOKE EVAL_GET_A3: - R=Z%(Z%(Z%(A,1),1),1) - GOSUB VAL_R:A3=R + A3=Z%(Z%(Z%(Z%(A+1)+1)+1)+2) EVAL_GET_A2: - R=Z%(Z%(A,1),1) - GOSUB VAL_R:A2=R + A2=Z%(Z%(Z%(A+1)+1)+2) EVAL_GET_A1: - R=Z%(A,1) - GOSUB VAL_R:A1=R + A1=Z%(Z%(A+1)+2) RETURN EVAL_DEF: @@ -165,21 +162,21 @@ SUB EVAL C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1+1)=0 THEN GOTO EVAL_LET_LOOP_DONE Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1):GOSUB VAL_A:CALL EVAL + A=Z%(Z%(A1+1)+2):CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE - REM set environment: even A1 key to odd A1 eval'd above - K=Z%(A1+1,1):C=R:GOSUB ENV_SET + REM set key/value in the environment + K=Z%(A1+2):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements - A1=Z%(Z%(A1,1),1) + A1=Z%(Z%(A1+1)+1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: @@ -193,7 +190,7 @@ SUB EVAL A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: - A=Z%(A,1): REM rest + A=Z%(A+1): REM rest GOSUB PUSH_A: REM push/save A REM this must be EVAL_AST call #2 for EVAL_AST to return early @@ -218,7 +215,7 @@ SUB EVAL GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL GOSUB POP_A: REM pop/restore A - IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE + IF (R=0) OR (R=2) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: AY=R:GOSUB RELEASE @@ -228,7 +225,7 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R<4 THEN R=0:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -246,14 +243,14 @@ SUB EVAL REM push f/args for release after call GOSUB PUSH_R - AR=Z%(R,1): REM rest - GOSUB VAL_R:F=R + AR=Z%(R+1): REM rest + F=Z%(R+2) REM if metadata, get the actual object - IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) + IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) - IF (Z%(F,0)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller GOSUB POP_R @@ -261,9 +258,9 @@ SUB EVAL EVAL_DO_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP + IF Z%(F+1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION + IF Z%(F+1)>60 THEN CALL DO_TCO_FUNCTION EVAL_DO_FUNCTION_SKIP: REM pop and release f/args @@ -274,8 +271,8 @@ SUB EVAL EVAL_DO_MAL_FUNCTION: Q=E:GOSUB PUSH_Q: REM save the current environment for release - REM create new environ using env stored with function - C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS + REM create new environ using env and params stored in function + C=Z%(F+3):A=Z%(F+2):B=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and @@ -285,7 +282,7 @@ SUB EVAL IF AY<>Q THEN GOSUB RELEASE REM claim the AST before releasing the list containing it - A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 + A=Z%(F+1):Z%(A)=Z%(A)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) LV=LV+1:GOSUB PEND_A_LV:LV=LV-1 diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index 8c76518435..def1880753 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -1,5 +1,6 @@ GOTO MAIN +REM $INCLUDE: 'mem.in.bas' REM $INCLUDE: 'types.in.bas' REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' @@ -22,13 +23,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A,0)AND 31 + T=Z%(A)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R,0)=Z%(R,0)+32 + Z%(R)=Z%(R)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -42,22 +43,23 @@ SUB EVAL_AST EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence - IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element REM The EVAL_DO call to EVAL_AST must be call #2 for EVAL_AST to REM return early and for TCO to work Q=5:GOSUB PEEK_Q_Q - IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Q=2 AND Z%(Z%(A+1)+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM call EVAL for each entry GOSUB PUSH_A - IF T<>8 THEN GOSUB VAL_A - IF T=8 THEN A=Z%(A+1,1) + IF T<>8 THEN A=Z%(A+2) + IF T=8 THEN A=Z%(A+3) Q=T:GOSUB PUSH_Q: REM push/save type CALL EVAL GOSUB POP_Q:T=Q: REM pop/restore type GOSUB POP_A + M=R REM if error, release the unattached element REM TODO: is R=0 correct? @@ -65,17 +67,15 @@ SUB EVAL_AST REM for hash-maps, copy the key (inc ref since we are going to REM release it below) - IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 + IF T=8 THEN N=M:M=Z%(A+2):Z%(M)=Z%(M)+32 - REM value evaluated above - N=R REM update the return sequence structure REM release N (and M if T=8) since seq takes full ownership C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list - A=Z%(A,1) + A=Z%(A+1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: @@ -114,13 +114,13 @@ SUB EVAL APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN - A0=Z%(A+1,1) + A0=Z%(A+2) REM get symbol in A$ - IF (Z%(A0,0)AND 31)<>5 THEN A$="" - IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0)AND 31)<>5 THEN A$="" + IF (Z%(A0)AND 31)=5 THEN A$=S$(Z%(A0+1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -130,14 +130,11 @@ SUB EVAL GOTO EVAL_INVOKE EVAL_GET_A3: - R=Z%(Z%(Z%(A,1),1),1) - GOSUB VAL_R:A3=R + A3=Z%(Z%(Z%(Z%(A+1)+1)+1)+2) EVAL_GET_A2: - R=Z%(Z%(A,1),1) - GOSUB VAL_R:A2=R + A2=Z%(Z%(Z%(A+1)+1)+2) EVAL_GET_A1: - R=Z%(A,1) - GOSUB VAL_R:A1=R + A1=Z%(Z%(A+1)+2) RETURN EVAL_DEF: @@ -165,21 +162,21 @@ SUB EVAL C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1+1)=0 THEN GOTO EVAL_LET_LOOP_DONE Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1):GOSUB VAL_A:CALL EVAL + A=Z%(Z%(A1+1)+2):CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE - REM set environment: even A1 key to odd A1 eval'd above - K=Z%(A1+1,1):C=R:GOSUB ENV_SET + REM set key/value in the environment + K=Z%(A1+2):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements - A1=Z%(Z%(A1,1),1) + A1=Z%(Z%(A1+1)+1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: @@ -193,7 +190,7 @@ SUB EVAL A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: - A=Z%(A,1): REM rest + A=Z%(A+1): REM rest GOSUB PUSH_A: REM push/save A REM this must be EVAL_AST call #2 for EVAL_AST to return early @@ -218,7 +215,7 @@ SUB EVAL GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL GOSUB POP_A: REM pop/restore A - IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE + IF (R=0) OR (R=2) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: AY=R:GOSUB RELEASE @@ -228,7 +225,7 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R<4 THEN R=0:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -246,14 +243,14 @@ SUB EVAL REM push f/args for release after call GOSUB PUSH_R - AR=Z%(R,1): REM rest - GOSUB VAL_R:F=R + AR=Z%(R+1): REM rest + F=Z%(R+2) REM if metadata, get the actual object - IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) + IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) - IF (Z%(F,0)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller GOSUB POP_R @@ -261,9 +258,9 @@ SUB EVAL EVAL_DO_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP + IF Z%(F+1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION + IF Z%(F+1)>60 THEN CALL DO_TCO_FUNCTION EVAL_DO_FUNCTION_SKIP: REM pop and release f/args @@ -274,8 +271,8 @@ SUB EVAL EVAL_DO_MAL_FUNCTION: Q=E:GOSUB PUSH_Q: REM save the current environment for release - REM create new environ using env stored with function - C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS + REM create new environ using env and params stored in function + C=Z%(F+3):A=Z%(F+2):B=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and @@ -285,7 +282,7 @@ SUB EVAL IF AY<>Q THEN GOSUB RELEASE REM claim the AST before releasing the list containing it - A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 + A=Z%(F+1):Z%(A)=Z%(A)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) LV=LV+1:GOSUB PEND_A_LV:LV=LV-1 diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index 002b40e205..80f64d6ce9 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -1,5 +1,6 @@ GOTO MAIN +REM $INCLUDE: 'mem.in.bas' REM $INCLUDE: 'types.in.bas' REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' @@ -17,8 +18,8 @@ MAL_READ: REM QUASIQUOTE(A) -> R SUB QUASIQUOTE REM pair? - IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_QUOTE - IF (Z%(A,1)=0) THEN GOTO QQ_QUOTE + IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_QUOTE + IF (Z%(A+1)=0) THEN GOTO QQ_QUOTE GOTO QQ_UNQUOTE QQ_QUOTE: @@ -30,35 +31,35 @@ SUB QUASIQUOTE GOTO QQ_DONE QQ_UNQUOTE: - R=A:GOSUB VAL_R - IF (Z%(R,0)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE - IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE + R=Z%(A+2) + IF (Z%(R)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF S$(Z%(R+1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] - R=Z%(A,1):GOSUB VAL_R - Z%(R,0)=Z%(R,0)+32 + R=Z%(Z%(A+1)+2) + Z%(R)=Z%(R)+32 GOTO QQ_DONE QQ_SPLICE_UNQUOTE: GOSUB PUSH_A REM rest of cases call quasiquote on ast[1..] - A=Z%(A,1):CALL QUASIQUOTE + A=Z%(A+1):CALL QUASIQUOTE W=R GOSUB POP_A REM set A to ast[0] for last two cases - GOSUB VAL_A + A=Z%(A+2) REM pair? - IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_DEFAULT - IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT + IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_DEFAULT + IF (Z%(A+1)=0) THEN GOTO QQ_DEFAULT - B=A:GOSUB VAL_B - IF (Z%(B,0)AND 31)<>5 THEN GOTO QQ_DEFAULT - IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT + B=Z%(A+2) + IF (Z%(B)AND 31)<>5 THEN GOTO QQ_DEFAULT + IF S$(Z%(B+1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1):GOSUB VAL_B + B=Z%(Z%(A+1)+2) B$="concat":T=5:GOSUB STRING:C=R A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership @@ -93,13 +94,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A,0)AND 31 + T=Z%(A)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R,0)=Z%(R,0)+32 + Z%(R)=Z%(R)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -113,22 +114,23 @@ SUB EVAL_AST EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence - IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element REM The EVAL_DO call to EVAL_AST must be call #2 for EVAL_AST to REM return early and for TCO to work Q=5:GOSUB PEEK_Q_Q - IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Q=2 AND Z%(Z%(A+1)+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM call EVAL for each entry GOSUB PUSH_A - IF T<>8 THEN GOSUB VAL_A - IF T=8 THEN A=Z%(A+1,1) + IF T<>8 THEN A=Z%(A+2) + IF T=8 THEN A=Z%(A+3) Q=T:GOSUB PUSH_Q: REM push/save type CALL EVAL GOSUB POP_Q:T=Q: REM pop/restore type GOSUB POP_A + M=R REM if error, release the unattached element REM TODO: is R=0 correct? @@ -136,17 +138,15 @@ SUB EVAL_AST REM for hash-maps, copy the key (inc ref since we are going to REM release it below) - IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 + IF T=8 THEN N=M:M=Z%(A+2):Z%(M)=Z%(M)+32 - REM value evaluated above - N=R REM update the return sequence structure REM release N (and M if T=8) since seq takes full ownership C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list - A=Z%(A,1) + A=Z%(A+1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: @@ -185,13 +185,13 @@ SUB EVAL APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN - A0=Z%(A+1,1) + A0=Z%(A+2) REM get symbol in A$ - IF (Z%(A0,0)AND 31)<>5 THEN A$="" - IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0)AND 31)<>5 THEN A$="" + IF (Z%(A0)AND 31)=5 THEN A$=S$(Z%(A0+1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -203,14 +203,11 @@ SUB EVAL GOTO EVAL_INVOKE EVAL_GET_A3: - R=Z%(Z%(Z%(A,1),1),1) - GOSUB VAL_R:A3=R + A3=Z%(Z%(Z%(Z%(A+1)+1)+1)+2) EVAL_GET_A2: - R=Z%(Z%(A,1),1) - GOSUB VAL_R:A2=R + A2=Z%(Z%(Z%(A+1)+1)+2) EVAL_GET_A1: - R=Z%(A,1) - GOSUB VAL_R:A1=R + A1=Z%(Z%(A+1)+2) RETURN EVAL_DEF: @@ -238,21 +235,21 @@ SUB EVAL C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1+1)=0 THEN GOTO EVAL_LET_LOOP_DONE Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1):GOSUB VAL_A:CALL EVAL + A=Z%(Z%(A1+1)+2):CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE - REM set environment: even A1 key to odd A1 eval'd above - K=Z%(A1+1,1):C=R:GOSUB ENV_SET + REM set key/value in the environment + K=Z%(A1+2):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements - A1=Z%(Z%(A1,1),1) + A1=Z%(Z%(A1+1)+1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: @@ -266,7 +263,7 @@ SUB EVAL A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: - A=Z%(A,1): REM rest + A=Z%(A+1): REM rest GOSUB PUSH_A: REM push/save A REM this must be EVAL_AST call #2 for EVAL_AST to return early @@ -287,12 +284,12 @@ SUB EVAL GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_QUOTE: - R=Z%(A,1):GOSUB VAL_R - Z%(R,0)=Z%(R,0)+32 + R=Z%(Z%(A+1)+2) + Z%(R)=Z%(R)+32 GOTO EVAL_RETURN EVAL_QUASIQUOTE: - R=Z%(A,1):GOSUB VAL_R + R=Z%(Z%(A+1)+2) A=R:CALL QUASIQUOTE A=R REM add quasiquote result to pending release queue to free when @@ -306,7 +303,7 @@ SUB EVAL GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL GOSUB POP_A: REM pop/restore A - IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE + IF (R=0) OR (R=2) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: AY=R:GOSUB RELEASE @@ -316,7 +313,7 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R<4 THEN R=0:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -334,14 +331,14 @@ SUB EVAL REM push f/args for release after call GOSUB PUSH_R - AR=Z%(R,1): REM rest - GOSUB VAL_R:F=R + AR=Z%(R+1): REM rest + F=Z%(R+2) REM if metadata, get the actual object - IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) + IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) - IF (Z%(F,0)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller GOSUB POP_R @@ -349,9 +346,9 @@ SUB EVAL EVAL_DO_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP + IF Z%(F+1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION + IF Z%(F+1)>60 THEN CALL DO_TCO_FUNCTION EVAL_DO_FUNCTION_SKIP: REM pop and release f/args @@ -362,8 +359,8 @@ SUB EVAL EVAL_DO_MAL_FUNCTION: Q=E:GOSUB PUSH_Q: REM save the current environment for release - REM create new environ using env stored with function - C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS + REM create new environ using env and params stored in function + C=Z%(F+3):A=Z%(F+2):B=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and @@ -373,7 +370,7 @@ SUB EVAL IF AY<>Q THEN GOSUB RELEASE REM claim the AST before releasing the list containing it - A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 + A=Z%(F+1):Z%(A)=Z%(A)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) LV=LV+1:GOSUB PEND_A_LV:LV=LV-1 diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index 5857913eb5..c81f662797 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -1,5 +1,6 @@ GOTO MAIN +REM $INCLUDE: 'mem.in.bas' REM $INCLUDE: 'types.in.bas' REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' @@ -17,8 +18,8 @@ MAL_READ: REM QUASIQUOTE(A) -> R SUB QUASIQUOTE REM pair? - IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_QUOTE - IF (Z%(A,1)=0) THEN GOTO QQ_QUOTE + IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_QUOTE + IF (Z%(A+1)=0) THEN GOTO QQ_QUOTE GOTO QQ_UNQUOTE QQ_QUOTE: @@ -30,35 +31,35 @@ SUB QUASIQUOTE GOTO QQ_DONE QQ_UNQUOTE: - R=A:GOSUB VAL_R - IF (Z%(R,0)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE - IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE + R=Z%(A+2) + IF (Z%(R)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF S$(Z%(R+1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] - R=Z%(A,1):GOSUB VAL_R - Z%(R,0)=Z%(R,0)+32 + R=Z%(Z%(A+1)+2) + Z%(R)=Z%(R)+32 GOTO QQ_DONE QQ_SPLICE_UNQUOTE: GOSUB PUSH_A REM rest of cases call quasiquote on ast[1..] - A=Z%(A,1):CALL QUASIQUOTE + A=Z%(A+1):CALL QUASIQUOTE W=R GOSUB POP_A REM set A to ast[0] for last two cases - GOSUB VAL_A + A=Z%(A+2) REM pair? - IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_DEFAULT - IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT + IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_DEFAULT + IF (Z%(A+1)=0) THEN GOTO QQ_DEFAULT - B=A:GOSUB VAL_B - IF (Z%(B,0)AND 31)<>5 THEN GOTO QQ_DEFAULT - IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT + B=Z%(A+2) + IF (Z%(B)AND 31)<>5 THEN GOTO QQ_DEFAULT + IF S$(Z%(B+1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1):GOSUB VAL_B + B=Z%(Z%(A+1)+2) B$="concat":T=5:GOSUB STRING:C=R A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership @@ -90,20 +91,20 @@ SUB MACROEXPAND MACROEXPAND_LOOP: REM list? - IF (Z%(A,0)AND 31)<>6 THEN GOTO MACROEXPAND_DONE + IF (Z%(A)AND 31)<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? - IF Z%(A,1)=0 THEN GOTO MACROEXPAND_DONE - B=A:GOSUB VAL_B + IF Z%(A+1)=0 THEN GOTO MACROEXPAND_DONE + B=Z%(A+2) REM symbol? in first position - IF (Z%(B,0)AND 31)<>5 THEN GOTO MACROEXPAND_DONE + IF (Z%(B)AND 31)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? K=B:CALL ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE B=R4 REM macro? - IF (Z%(B,0)AND 31)<>11 THEN GOTO MACROEXPAND_DONE + IF (Z%(B)AND 31)<>11 THEN GOTO MACROEXPAND_DONE - F=B:AR=Z%(A,1):CALL APPLY + F=B:AR=Z%(A+1):CALL APPLY A=R GOSUB PEEK_Q:AY=Q @@ -126,13 +127,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A,0)AND 31 + T=Z%(A)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R,0)=Z%(R,0)+32 + Z%(R)=Z%(R)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -146,22 +147,23 @@ SUB EVAL_AST EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence - IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element REM The EVAL_DO call to EVAL_AST must be call #2 for EVAL_AST to REM return early and for TCO to work Q=5:GOSUB PEEK_Q_Q - IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Q=2 AND Z%(Z%(A+1)+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM call EVAL for each entry GOSUB PUSH_A - IF T<>8 THEN GOSUB VAL_A - IF T=8 THEN A=Z%(A+1,1) + IF T<>8 THEN A=Z%(A+2) + IF T=8 THEN A=Z%(A+3) Q=T:GOSUB PUSH_Q: REM push/save type CALL EVAL GOSUB POP_Q:T=Q: REM pop/restore type GOSUB POP_A + M=R REM if error, release the unattached element REM TODO: is R=0 correct? @@ -169,17 +171,15 @@ SUB EVAL_AST REM for hash-maps, copy the key (inc ref since we are going to REM release it below) - IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 + IF T=8 THEN N=M:M=Z%(A+2):Z%(M)=Z%(M)+32 - REM value evaluated above - N=R REM update the return sequence structure REM release N (and M if T=8) since seq takes full ownership C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list - A=Z%(A,1) + A=Z%(A+1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: @@ -224,13 +224,13 @@ SUB EVAL IF R<>1 THEN GOTO EVAL_NOT_LIST GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN - A0=Z%(A+1,1) + A0=Z%(A+2) REM get symbol in A$ - IF (Z%(A0,0)AND 31)<>5 THEN A$="" - IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0)AND 31)<>5 THEN A$="" + IF (Z%(A0)AND 31)=5 THEN A$=S$(Z%(A0+1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -244,14 +244,11 @@ SUB EVAL GOTO EVAL_INVOKE EVAL_GET_A3: - R=Z%(Z%(Z%(A,1),1),1) - GOSUB VAL_R:A3=R + A3=Z%(Z%(Z%(Z%(A+1)+1)+1)+2) EVAL_GET_A2: - R=Z%(Z%(A,1),1) - GOSUB VAL_R:A2=R + A2=Z%(Z%(Z%(A+1)+1)+2) EVAL_GET_A1: - R=Z%(A,1) - GOSUB VAL_R:A1=R + A1=Z%(Z%(A+1)+2) RETURN EVAL_DEF: @@ -279,21 +276,21 @@ SUB EVAL C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1+1)=0 THEN GOTO EVAL_LET_LOOP_DONE Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1):GOSUB VAL_A:CALL EVAL + A=Z%(Z%(A1+1)+2):CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE - REM set environment: even A1 key to odd A1 eval'd above - K=Z%(A1+1,1):C=R:GOSUB ENV_SET + REM set key/value in the environment + K=Z%(A1+2):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements - A1=Z%(Z%(A1,1),1) + A1=Z%(Z%(A1+1)+1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: @@ -307,7 +304,7 @@ SUB EVAL A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: - A=Z%(A,1): REM rest + A=Z%(A+1): REM rest GOSUB PUSH_A: REM push/save A REM this must be EVAL_AST call #2 for EVAL_AST to return early @@ -328,12 +325,12 @@ SUB EVAL GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_QUOTE: - R=Z%(A,1):GOSUB VAL_R - Z%(R,0)=Z%(R,0)+32 + R=Z%(Z%(A+1)+2) + Z%(R)=Z%(R)+32 GOTO EVAL_RETURN EVAL_QUASIQUOTE: - R=Z%(A,1):GOSUB VAL_R + R=Z%(Z%(A+1)+2) A=R:CALL QUASIQUOTE A=R REM add quasiquote result to pending release queue to free when @@ -351,7 +348,7 @@ SUB EVAL GOSUB POP_Q:A1=Q: REM pop A1 REM change function to macro - Z%(R,0)=Z%(R,0)+1 + Z%(R)=Z%(R)+1 REM set A1 in env to A2 K=A1:C=R:GOSUB ENV_SET @@ -359,12 +356,12 @@ SUB EVAL EVAL_MACROEXPAND: REM PRINT "macroexpand" - R=Z%(A,1):GOSUB VAL_R + R=Z%(Z%(A+1)+2) A=R:CALL MACROEXPAND R=A REM since we are returning it unevaluated, inc the ref cnt - Z%(R,0)=Z%(R,0)+32 + Z%(R)=Z%(R)+32 GOTO EVAL_RETURN EVAL_IF: @@ -372,7 +369,7 @@ SUB EVAL GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL GOSUB POP_A: REM pop/restore A - IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE + IF (R=0) OR (R=2) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: AY=R:GOSUB RELEASE @@ -382,7 +379,7 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R<4 THEN R=0:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -400,14 +397,14 @@ SUB EVAL REM push f/args for release after call GOSUB PUSH_R - AR=Z%(R,1): REM rest - GOSUB VAL_R:F=R + AR=Z%(R+1): REM rest + F=Z%(R+2) REM if metadata, get the actual object - IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) + IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) - IF (Z%(F,0)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller GOSUB POP_R @@ -415,9 +412,9 @@ SUB EVAL EVAL_DO_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP + IF Z%(F+1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION + IF Z%(F+1)>60 THEN CALL DO_TCO_FUNCTION EVAL_DO_FUNCTION_SKIP: REM pop and release f/args @@ -428,8 +425,8 @@ SUB EVAL EVAL_DO_MAL_FUNCTION: Q=E:GOSUB PUSH_Q: REM save the current environment for release - REM create new environ using env stored with function - C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS + REM create new environ using env and params stored in function + C=Z%(F+3):A=Z%(F+2):B=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and @@ -439,7 +436,7 @@ SUB EVAL IF AY<>Q THEN GOSUB RELEASE REM claim the AST before releasing the list containing it - A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 + A=Z%(F+1):Z%(A)=Z%(A)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) LV=LV+1:GOSUB PEND_A_LV:LV=LV-1 diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index ea12f3f797..d2970d29eb 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -1,5 +1,6 @@ GOTO MAIN +REM $INCLUDE: 'mem.in.bas' REM $INCLUDE: 'types.in.bas' REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' @@ -17,8 +18,8 @@ MAL_READ: REM QUASIQUOTE(A) -> R SUB QUASIQUOTE REM pair? - IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_QUOTE - IF (Z%(A,1)=0) THEN GOTO QQ_QUOTE + IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_QUOTE + IF (Z%(A+1)=0) THEN GOTO QQ_QUOTE GOTO QQ_UNQUOTE QQ_QUOTE: @@ -30,35 +31,35 @@ SUB QUASIQUOTE GOTO QQ_DONE QQ_UNQUOTE: - R=A:GOSUB VAL_R - IF (Z%(R,0)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE - IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE + R=Z%(A+2) + IF (Z%(R)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF S$(Z%(R+1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] - R=Z%(A,1):GOSUB VAL_R - Z%(R,0)=Z%(R,0)+32 + R=Z%(Z%(A+1)+2) + Z%(R)=Z%(R)+32 GOTO QQ_DONE QQ_SPLICE_UNQUOTE: GOSUB PUSH_A REM rest of cases call quasiquote on ast[1..] - A=Z%(A,1):CALL QUASIQUOTE + A=Z%(A+1):CALL QUASIQUOTE W=R GOSUB POP_A REM set A to ast[0] for last two cases - GOSUB VAL_A + A=Z%(A+2) REM pair? - IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_DEFAULT - IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT + IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_DEFAULT + IF (Z%(A+1)=0) THEN GOTO QQ_DEFAULT - B=A:GOSUB VAL_B - IF (Z%(B,0)AND 31)<>5 THEN GOTO QQ_DEFAULT - IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT + B=Z%(A+2) + IF (Z%(B)AND 31)<>5 THEN GOTO QQ_DEFAULT + IF S$(Z%(B+1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1):GOSUB VAL_B + B=Z%(Z%(A+1)+2) B$="concat":T=5:GOSUB STRING:C=R A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership @@ -90,20 +91,20 @@ SUB MACROEXPAND MACROEXPAND_LOOP: REM list? - IF (Z%(A,0)AND 31)<>6 THEN GOTO MACROEXPAND_DONE + IF (Z%(A)AND 31)<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? - IF Z%(A,1)=0 THEN GOTO MACROEXPAND_DONE - B=A:GOSUB VAL_B + IF Z%(A+1)=0 THEN GOTO MACROEXPAND_DONE + B=Z%(A+2) REM symbol? in first position - IF (Z%(B,0)AND 31)<>5 THEN GOTO MACROEXPAND_DONE + IF (Z%(B)AND 31)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? K=B:CALL ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE B=R4 REM macro? - IF (Z%(B,0)AND 31)<>11 THEN GOTO MACROEXPAND_DONE + IF (Z%(B)AND 31)<>11 THEN GOTO MACROEXPAND_DONE - F=B:AR=Z%(A,1):CALL APPLY + F=B:AR=Z%(A+1):CALL APPLY A=R GOSUB PEEK_Q:AY=Q @@ -126,13 +127,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A,0)AND 31 + T=Z%(A)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R,0)=Z%(R,0)+32 + Z%(R)=Z%(R)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -146,22 +147,23 @@ SUB EVAL_AST EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence - IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element REM The EVAL_DO call to EVAL_AST must be call #2 for EVAL_AST to REM return early and for TCO to work Q=5:GOSUB PEEK_Q_Q - IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Q=2 AND Z%(Z%(A+1)+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM call EVAL for each entry GOSUB PUSH_A - IF T<>8 THEN GOSUB VAL_A - IF T=8 THEN A=Z%(A+1,1) + IF T<>8 THEN A=Z%(A+2) + IF T=8 THEN A=Z%(A+3) Q=T:GOSUB PUSH_Q: REM push/save type CALL EVAL GOSUB POP_Q:T=Q: REM pop/restore type GOSUB POP_A + M=R REM if error, release the unattached element REM TODO: is R=0 correct? @@ -169,17 +171,15 @@ SUB EVAL_AST REM for hash-maps, copy the key (inc ref since we are going to REM release it below) - IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 + IF T=8 THEN N=M:M=Z%(A+2):Z%(M)=Z%(M)+32 - REM value evaluated above - N=R REM update the return sequence structure REM release N (and M if T=8) since seq takes full ownership C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list - A=Z%(A,1) + A=Z%(A+1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: @@ -224,13 +224,13 @@ SUB EVAL IF R<>1 THEN GOTO EVAL_NOT_LIST GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN - A0=Z%(A+1,1) + A0=Z%(A+2) REM get symbol in A$ - IF (Z%(A0,0)AND 31)<>5 THEN A$="" - IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0)AND 31)<>5 THEN A$="" + IF (Z%(A0)AND 31)=5 THEN A$=S$(Z%(A0+1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -245,14 +245,11 @@ SUB EVAL GOTO EVAL_INVOKE EVAL_GET_A3: - R=Z%(Z%(Z%(A,1),1),1) - GOSUB VAL_R:A3=R + A3=Z%(Z%(Z%(Z%(A+1)+1)+1)+2) EVAL_GET_A2: - R=Z%(Z%(A,1),1) - GOSUB VAL_R:A2=R + A2=Z%(Z%(Z%(A+1)+1)+2) EVAL_GET_A1: - R=Z%(A,1) - GOSUB VAL_R:A1=R + A1=Z%(Z%(A+1)+2) RETURN EVAL_DEF: @@ -280,21 +277,21 @@ SUB EVAL C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1+1)=0 THEN GOTO EVAL_LET_LOOP_DONE Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1):GOSUB VAL_A:CALL EVAL + A=Z%(Z%(A1+1)+2):CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE - REM set environment: even A1 key to odd A1 eval'd above - K=Z%(A1+1,1):C=R:GOSUB ENV_SET + REM set key/value in the environment + K=Z%(A1+2):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements - A1=Z%(Z%(A1,1),1) + A1=Z%(Z%(A1+1)+1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: @@ -308,7 +305,7 @@ SUB EVAL A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: - A=Z%(A,1): REM rest + A=Z%(A+1): REM rest GOSUB PUSH_A: REM push/save A REM this must be EVAL_AST call #2 for EVAL_AST to return early @@ -329,12 +326,12 @@ SUB EVAL GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_QUOTE: - R=Z%(A,1):GOSUB VAL_R - Z%(R,0)=Z%(R,0)+32 + R=Z%(Z%(A+1)+2) + Z%(R)=Z%(R)+32 GOTO EVAL_RETURN EVAL_QUASIQUOTE: - R=Z%(A,1):GOSUB VAL_R + R=Z%(Z%(A+1)+2) A=R:CALL QUASIQUOTE A=R REM add quasiquote result to pending release queue to free when @@ -352,7 +349,7 @@ SUB EVAL GOSUB POP_Q:A1=Q: REM pop A1 REM change function to macro - Z%(R,0)=Z%(R,0)+1 + Z%(R)=Z%(R)+1 REM set A1 in env to A2 K=A1:C=R:GOSUB ENV_SET @@ -360,12 +357,12 @@ SUB EVAL EVAL_MACROEXPAND: REM PRINT "macroexpand" - R=Z%(A,1):GOSUB VAL_R + R=Z%(Z%(A+1)+2) A=R:CALL MACROEXPAND R=A REM since we are returning it unevaluated, inc the ref cnt - Z%(R,0)=Z%(R,0)+32 + Z%(R)=Z%(R)+32 GOTO EVAL_RETURN EVAL_TRY: @@ -377,7 +374,7 @@ SUB EVAL GOSUB POP_A: REM pop/restore A REM if there is not error or catch block then return - IF ER=-2 OR Z%(A,1)=0 THEN GOTO EVAL_RETURN + IF ER=-2 OR Z%(A+1)=0 THEN GOTO EVAL_RETURN REM create environment for the catch block eval C=E:GOSUB ENV_NEW:E=R @@ -386,7 +383,7 @@ SUB EVAL A=A2:GOSUB EVAL_GET_A2: REM set A1 and A2 from catch block REM create object for ER=-1 type raw string errors - IF ER=-1 THEN B$=E$:T=4:GOSUB STRING:ER=R:Z%(R,0)=Z%(R,0)+32 + IF ER=-1 THEN B$=E$:T=4:GOSUB STRING:ER=R:Z%(R)=Z%(R)+32 REM bind the catch symbol to the error object K=A1:C=ER:GOSUB ENV_SET @@ -404,7 +401,7 @@ SUB EVAL GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL GOSUB POP_A: REM pop/restore A - IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE + IF (R=0) OR (R=2) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: AY=R:GOSUB RELEASE @@ -414,7 +411,7 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R<4 THEN R=0:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -432,14 +429,14 @@ SUB EVAL REM push f/args for release after call GOSUB PUSH_R - AR=Z%(R,1): REM rest - GOSUB VAL_R:F=R + AR=Z%(R+1): REM rest + F=Z%(R+2) REM if metadata, get the actual object - IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) + IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) - IF (Z%(F,0)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller GOSUB POP_R @@ -447,9 +444,9 @@ SUB EVAL EVAL_DO_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP + IF Z%(F+1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION + IF Z%(F+1)>60 THEN CALL DO_TCO_FUNCTION EVAL_DO_FUNCTION_SKIP: REM pop and release f/args @@ -460,8 +457,8 @@ SUB EVAL EVAL_DO_MAL_FUNCTION: Q=E:GOSUB PUSH_Q: REM save the current environment for release - REM create new environ using env stored with function - C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS + REM create new environ using env and params stored in function + C=Z%(F+3):A=Z%(F+2):B=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and @@ -471,7 +468,7 @@ SUB EVAL IF AY<>Q THEN GOSUB RELEASE REM claim the AST before releasing the list containing it - A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 + A=Z%(F+1):Z%(A)=Z%(A)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) LV=LV+1:GOSUB PEND_A_LV:LV=LV-1 @@ -515,7 +512,7 @@ REM RE(A$) -> R REM Assume D has repl_env REM caller must release result RE: - R1=0 + R1=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO RE_DONE @@ -524,18 +521,15 @@ RE: RE_DONE: REM Release memory from MAL_READ - IF R1<>0 THEN AY=R1:GOSUB RELEASE + AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=-1:R2=-1 - GOSUB MAL_READ - R1=R - IF ER<>-2 THEN GOTO REP_DONE + R2=-1 - A=R:E=D:CALL EVAL + GOSUB RE R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -544,7 +538,6 @@ SUB REP REP_DONE: REM Release memory from MAL_READ and EVAL AY=R2:GOSUB RELEASE - AY=R1:GOSUB RELEASE END SUB REM MAIN program diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index cdbeeab7ae..bbc0169efd 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -1,5 +1,6 @@ GOTO MAIN +REM $INCLUDE: 'mem.in.bas' REM $INCLUDE: 'types.in.bas' REM $INCLUDE: 'readline.in.bas' REM $INCLUDE: 'reader.in.bas' @@ -17,8 +18,8 @@ MAL_READ: REM QUASIQUOTE(A) -> R SUB QUASIQUOTE REM pair? - IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_QUOTE - IF (Z%(A,1)=0) THEN GOTO QQ_QUOTE + IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_QUOTE + IF (Z%(A+1)=0) THEN GOTO QQ_QUOTE GOTO QQ_UNQUOTE QQ_QUOTE: @@ -30,35 +31,35 @@ SUB QUASIQUOTE GOTO QQ_DONE QQ_UNQUOTE: - R=A:GOSUB VAL_R - IF (Z%(R,0)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE - IF S$(Z%(R,1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE + R=Z%(A+2) + IF (Z%(R)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE + IF S$(Z%(R+1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] - R=Z%(A,1):GOSUB VAL_R - Z%(R,0)=Z%(R,0)+32 + R=Z%(Z%(A+1)+2) + Z%(R)=Z%(R)+32 GOTO QQ_DONE QQ_SPLICE_UNQUOTE: GOSUB PUSH_A REM rest of cases call quasiquote on ast[1..] - A=Z%(A,1):CALL QUASIQUOTE + A=Z%(A+1):CALL QUASIQUOTE W=R GOSUB POP_A REM set A to ast[0] for last two cases - GOSUB VAL_A + A=Z%(A+2) REM pair? - IF (Z%(A,0)AND 31)<6 OR (Z%(A,0)AND 31)>7 THEN GOTO QQ_DEFAULT - IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT + IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_DEFAULT + IF (Z%(A+1)=0) THEN GOTO QQ_DEFAULT - B=A:GOSUB VAL_B - IF (Z%(B,0)AND 31)<>5 THEN GOTO QQ_DEFAULT - IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT + B=Z%(A+2) + IF (Z%(B)AND 31)<>5 THEN GOTO QQ_DEFAULT + IF S$(Z%(B+1))<>"splice-unquote" THEN QQ_DEFAULT REM ['concat, ast[0][1], quasiquote(ast[1..])] - B=Z%(A,1):GOSUB VAL_B + B=Z%(Z%(A+1)+2) B$="concat":T=5:GOSUB STRING:C=R A=W:GOSUB LIST3 REM release inner quasiquoted since outer list takes ownership @@ -90,20 +91,20 @@ SUB MACROEXPAND MACROEXPAND_LOOP: REM list? - IF (Z%(A,0)AND 31)<>6 THEN GOTO MACROEXPAND_DONE + IF (Z%(A)AND 31)<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? - IF Z%(A,1)=0 THEN GOTO MACROEXPAND_DONE - B=A:GOSUB VAL_B + IF Z%(A+1)=0 THEN GOTO MACROEXPAND_DONE + B=Z%(A+2) REM symbol? in first position - IF (Z%(B,0)AND 31)<>5 THEN GOTO MACROEXPAND_DONE + IF (Z%(B)AND 31)<>5 THEN GOTO MACROEXPAND_DONE REM defined in environment? K=B:CALL ENV_FIND IF R=-1 THEN GOTO MACROEXPAND_DONE B=R4 REM macro? - IF (Z%(B,0)AND 31)<>11 THEN GOTO MACROEXPAND_DONE + IF (Z%(B)AND 31)<>11 THEN GOTO MACROEXPAND_DONE - F=B:AR=Z%(A,1):CALL APPLY + F=B:AR=Z%(A+1):CALL APPLY A=R GOSUB PEEK_Q:AY=Q @@ -126,13 +127,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A,0)AND 31 + T=Z%(A)AND 31 IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R,0)=Z%(R,0)+32 + Z%(R)=Z%(R)+32 GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -146,22 +147,23 @@ SUB EVAL_AST EVAL_AST_SEQ_LOOP: REM check if we are done evaluating the source sequence - IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Z%(A+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM if we are returning to DO, then skip last element REM The EVAL_DO call to EVAL_AST must be call #2 for EVAL_AST to REM return early and for TCO to work Q=5:GOSUB PEEK_Q_Q - IF Q=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE + IF Q=2 AND Z%(Z%(A+1)+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE REM call EVAL for each entry GOSUB PUSH_A - IF T<>8 THEN GOSUB VAL_A - IF T=8 THEN A=Z%(A+1,1) + IF T<>8 THEN A=Z%(A+2) + IF T=8 THEN A=Z%(A+3) Q=T:GOSUB PUSH_Q: REM push/save type CALL EVAL GOSUB POP_Q:T=Q: REM pop/restore type GOSUB POP_A + M=R REM if error, release the unattached element REM TODO: is R=0 correct? @@ -169,17 +171,15 @@ SUB EVAL_AST REM for hash-maps, copy the key (inc ref since we are going to REM release it below) - IF T=8 THEN M=Z%(A+1,0):Z%(M,0)=Z%(M,0)+32 + IF T=8 THEN N=M:M=Z%(A+2):Z%(M)=Z%(M)+32 - REM value evaluated above - N=R REM update the return sequence structure REM release N (and M if T=8) since seq takes full ownership C=1:GOSUB MAP_LOOP_UPDATE REM process the next sequence entry from source list - A=Z%(A,1) + A=Z%(A+1) GOTO EVAL_AST_SEQ_LOOP EVAL_AST_SEQ_LOOP_DONE: @@ -224,13 +224,13 @@ SUB EVAL IF R<>1 THEN GOTO EVAL_NOT_LIST GOSUB EMPTY_Q - IF R THEN R=A:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN - A0=Z%(A+1,1) + A0=Z%(A+2) REM get symbol in A$ - IF (Z%(A0,0)AND 31)<>5 THEN A$="" - IF (Z%(A0,0)AND 31)=5 THEN A$=S$(Z%(A0,1)) + IF (Z%(A0)AND 31)<>5 THEN A$="" + IF (Z%(A0)AND 31)=5 THEN A$=S$(Z%(A0+1)) IF A$="def!" THEN GOTO EVAL_DEF IF A$="let*" THEN GOTO EVAL_LET @@ -245,14 +245,11 @@ SUB EVAL GOTO EVAL_INVOKE EVAL_GET_A3: - R=Z%(Z%(Z%(A,1),1),1) - GOSUB VAL_R:A3=R + A3=Z%(Z%(Z%(Z%(A+1)+1)+1)+2) EVAL_GET_A2: - R=Z%(Z%(A,1),1) - GOSUB VAL_R:A2=R + A2=Z%(Z%(Z%(A+1)+1)+2) EVAL_GET_A1: - R=Z%(A,1) - GOSUB VAL_R:A1=R + A1=Z%(Z%(A+1)+2) RETURN EVAL_DEF: @@ -280,21 +277,21 @@ SUB EVAL C=E:GOSUB ENV_NEW E=R EVAL_LET_LOOP: - IF Z%(A1,1)=0 THEN GOTO EVAL_LET_LOOP_DONE + IF Z%(A1+1)=0 THEN GOTO EVAL_LET_LOOP_DONE Q=A1:GOSUB PUSH_Q: REM push A1 REM eval current A1 odd element - A=Z%(A1,1):GOSUB VAL_A:CALL EVAL + A=Z%(Z%(A1+1)+2):CALL EVAL GOSUB POP_Q:A1=Q: REM pop A1 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE - REM set environment: even A1 key to odd A1 eval'd above - K=Z%(A1+1,1):C=R:GOSUB ENV_SET + REM set key/value in the environment + K=Z%(A1+2):C=R:GOSUB ENV_SET AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership REM skip to the next pair of A1 elements - A1=Z%(Z%(A1,1),1) + A1=Z%(Z%(A1+1)+1) GOTO EVAL_LET_LOOP EVAL_LET_LOOP_DONE: @@ -308,7 +305,7 @@ SUB EVAL A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_DO: - A=Z%(A,1): REM rest + A=Z%(A+1): REM rest GOSUB PUSH_A: REM push/save A REM this must be EVAL_AST call #2 for EVAL_AST to return early @@ -329,12 +326,12 @@ SUB EVAL GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_QUOTE: - R=Z%(A,1):GOSUB VAL_R - Z%(R,0)=Z%(R,0)+32 + R=Z%(Z%(A+1)+2) + Z%(R)=Z%(R)+32 GOTO EVAL_RETURN EVAL_QUASIQUOTE: - R=Z%(A,1):GOSUB VAL_R + R=Z%(Z%(A+1)+2) A=R:CALL QUASIQUOTE A=R REM add quasiquote result to pending release queue to free when @@ -352,7 +349,7 @@ SUB EVAL GOSUB POP_Q:A1=Q: REM pop A1 REM change function to macro - Z%(R,0)=Z%(R,0)+1 + Z%(R)=Z%(R)+1 REM set A1 in env to A2 K=A1:C=R:GOSUB ENV_SET @@ -360,12 +357,12 @@ SUB EVAL EVAL_MACROEXPAND: REM PRINT "macroexpand" - R=Z%(A,1):GOSUB VAL_R + R=Z%(Z%(A+1)+2) A=R:CALL MACROEXPAND R=A REM since we are returning it unevaluated, inc the ref cnt - Z%(R,0)=Z%(R,0)+32 + Z%(R)=Z%(R)+32 GOTO EVAL_RETURN EVAL_TRY: @@ -377,7 +374,7 @@ SUB EVAL GOSUB POP_A: REM pop/restore A REM if there is not error or catch block then return - IF ER=-2 OR Z%(A,1)=0 THEN GOTO EVAL_RETURN + IF ER=-2 OR Z%(A+1)=0 THEN GOTO EVAL_RETURN REM create environment for the catch block eval C=E:GOSUB ENV_NEW:E=R @@ -386,7 +383,7 @@ SUB EVAL A=A2:GOSUB EVAL_GET_A2: REM set A1 and A2 from catch block REM create object for ER=-1 type raw string errors - IF ER=-1 THEN B$=E$:T=4:GOSUB STRING:ER=R:Z%(R,0)=Z%(R,0)+32 + IF ER=-1 THEN B$=E$:T=4:GOSUB STRING:ER=R:Z%(R)=Z%(R)+32 REM bind the catch symbol to the error object K=A1:C=ER:GOSUB ENV_SET @@ -404,7 +401,7 @@ SUB EVAL GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL GOSUB POP_A: REM pop/restore A - IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE + IF (R=0) OR (R=2) THEN GOTO EVAL_IF_FALSE EVAL_IF_TRUE: AY=R:GOSUB RELEASE @@ -414,7 +411,7 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:Z%(R,0)=Z%(R,0)+32:GOTO EVAL_RETURN + IF R<4 THEN R=0:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop @@ -432,14 +429,14 @@ SUB EVAL REM push f/args for release after call GOSUB PUSH_R - AR=Z%(R,1): REM rest - GOSUB VAL_R:F=R + AR=Z%(R+1): REM rest + F=Z%(R+2) REM if metadata, get the actual object - IF (Z%(F,0)AND 31)>=16 THEN F=Z%(F,1) + IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) - IF (Z%(F,0)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F,0)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + IF (Z%(F)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION + IF (Z%(F)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller GOSUB POP_R @@ -447,9 +444,9 @@ SUB EVAL EVAL_DO_FUNCTION: REM regular function - IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP + IF Z%(F+1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION + IF Z%(F+1)>60 THEN CALL DO_TCO_FUNCTION EVAL_DO_FUNCTION_SKIP: REM pop and release f/args @@ -460,8 +457,8 @@ SUB EVAL EVAL_DO_MAL_FUNCTION: Q=E:GOSUB PUSH_Q: REM save the current environment for release - REM create new environ using env stored with function - C=Z%(F+1,1):A=Z%(F+1,0):B=AR:GOSUB ENV_NEW_BINDS + REM create new environ using env and params stored in function + C=Z%(F+3):A=Z%(F+2):B=AR:GOSUB ENV_NEW_BINDS REM release previous env if it is not the top one on the REM stack (X%(X-2)) because our new env refers to it and @@ -471,7 +468,7 @@ SUB EVAL IF AY<>Q THEN GOSUB RELEASE REM claim the AST before releasing the list containing it - A=Z%(F,1):Z%(A,0)=Z%(A,0)+32 + A=Z%(F+1):Z%(A)=Z%(A)+32 REM add AST to pending release queue to free as soon as EVAL REM actually returns (LV+1) LV=LV+1:GOSUB PEND_A_LV:LV=LV-1 @@ -627,7 +624,12 @@ MAIN: GOTO REPL_LOOP QUIT: - GOSUB PR_MEMORY_SUMMARY_SMALL + REM GOSUB PR_MEMORY_SUMMARY_SMALL + PRINT:GOSUB PR_MEMORY_SUMMARY_SMALL + REM GOSUB PR_MEMORY_MAP + REM P1=0:P2=ZI:GOSUB PR_MEMORY + REM P1=D:GOSUB PR_OBJECT + REM P1=ZK:GOSUB PR_OBJECT END PRINT_ERROR: diff --git a/basic/types.in.bas b/basic/types.in.bas index 76e1b4ab66..45fcd3e87f 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -1,375 +1,3 @@ -REM Z 0 -> 1 -REM nil 0 -> (unused) -REM boolean 1 -> 0: false, 1: true -REM integer 2 -> int value -REM float 3 -> ??? -REM string/kw 4 -> S$ index -REM symbol 5 -> S$ index -REM list next/val 6 -> next Z% index (0 for last) -REM 14 value (unless empty) -REM vector next/val 7 -> next Z% index (0 for last) -REM 14 value (unless empty) -REM hashmap next/val 8 -> next Z% index (0 for last) -REM key value -REM function 9 -> function index -REM mal function 10 -> body AST Z% index -REM param env Z% index -REM macro (same as 10) 11 -> body AST Z% index -REM param env Z% index -REM atom 12 -> Z% index -REM environment 13 -> data/hashmap Z% index -REM 14 outer Z% index (-1 for none) -REM reference/ptr 14 -> Z% index / or 0 -REM next free ptr 15 -> Z% index / or 0 -REM metadata 16-31 -> Z% index of object with this metadata -REM 14 -> Z% index of metdata object - -INIT_MEMORY: - #cbm T=FRE(0) - #qbasic T=0 - - Z1=4195: REM Z% (boxed memory) size (4 bytes each) - Z2=199: REM S$/S% (string memory) size (3+2 bytes each) - #qbasic Z3=200: REM X% (call stack) size (2 bytes each) - #cbm Z3=49152: REM X starting point at $C000 (2 bytes each) - #qbasic Z4=64: REM Y% (release stack) size (4 bytes each) - #cbm Z4=52992: REM Y starting point at $CF00 (4 bytes each) - - REM global error state - REM -2 : no error - REM -1 : string error in E$ - REM >=0 : pointer to error object - ER=-2 - E$="" - - REM TODO: for performance, define all/most non-array variables here - REM so that the array area doesn't have to be shifted down everytime - REM a new non-array variable is defined - - REM boxed element memory - DIM Z%(Z1,1): REM TYPE ARRAY - - REM Predefine nil, false, true, and an empty list - FOR I=0 TO 8:Z%(I,0)=32:Z%(I,1)=0:NEXT I - Z%(1,0)=1+32 - Z%(2,0)=1+32:Z%(2,1)=1 - Z%(3,0)=6+32:Z%(3,1)=0 - Z%(5,0)=7+32:Z%(5,1)=0 - Z%(7,0)=8+32:Z%(7,1)=0 - -REM Z%(0,0)=0:Z%(0,1)=0 -REM Z%(1,0)=1:Z%(1,1)=0 -REM Z%(2,0)=1:Z%(2,1)=1 -REM Z%(3,0)=6+32:Z%(3,1)=0 -REM Z%(4,0)=0:Z%(4,1)=0 -REM Z%(5,0)=7+32:Z%(5,1)=0 -REM Z%(6,0)=0:Z%(6,1)=0 -REM Z%(7,0)=8+32:Z%(7,1)=0 -REM Z%(8,0)=0:Z%(8,1)=0 - - REM start of unused memory - ZI=9 - - REM start of free list - ZK=9 - - REM string memory storage - S=0:DIM S$(Z2):DIM S%(Z2) - - REM call/logic stack - #qbasic X=-1:DIM X%(Z3): REM stack of Z% indexes - #cbm X=Z3-2: REM stack of 1920 Z% indexes at $C000 - - REM pending release stack - #qbasic Y=-1:DIM Y%(Z4,1): REM stack of Z% indexes and level/LV values - #cbm Y=Z4-4: REM stack of 64 Y% indexes/levels at $CF00 - - BT=TI - - RETURN - - -REM stack functions - -#qbasic PUSH_A: -#qbasic X=X+1:X%(X)=A:RETURN -#qbasic POP_A: -#qbasic A=X%(X):X=X-1:RETURN -#qbasic -#qbasic PUSH_R: -#qbasic X=X+1:X%(X)=R:RETURN -#qbasic POP_R: -#qbasic R=X%(X):X=X-1:RETURN -#qbasic -#qbasic PUSH_Q: -#qbasic X=X+1:X%(X)=Q:RETURN -#qbasic POP_Q: -#qbasic Q=X%(X):X=X-1:RETURN -#qbasic PEEK_Q: -#qbasic Q=X%(X):RETURN -#qbasic PEEK_Q_1: -#qbasic Q=X%(X-1):RETURN -#qbasic PEEK_Q_2: -#qbasic Q=X%(X-2):RETURN -#qbasic PEEK_Q_Q: -#qbasic Q=X%(X-Q):RETURN -#qbasic PUT_Q: -#qbasic X%(X)=Q:RETURN -#qbasic PUT_Q_1: -#qbasic X%(X-1)=Q:RETURN -#qbasic PUT_Q_2: -#qbasic X%(X-2)=Q:RETURN - -#cbm PUSH_A: -#cbm X=X+2:POKE X,A AND255:POKE X+1,A/256:RETURN -#cbm POP_A: -#cbm A=PEEK(X)+PEEK(X+1)*256:X=X-2:RETURN -#cbm -#cbm PUSH_R: -#cbm X=X+2:POKE X,R AND255:POKE X+1,R/256:RETURN -#cbm POP_R: -#cbm R=PEEK(X)+PEEK(X+1)*256:X=X-2:RETURN -#cbm -#cbm PUSH_Q: -#cbm X=X+2:POKE X,Q AND255:POKE X+1,Q/256:RETURN -#cbm POP_Q: -#cbm Q=PEEK(X)+PEEK(X+1)*256:X=X-2:RETURN -#cbm PEEK_Q: -#cbm Q=PEEK(X)+PEEK(X+1)*256:RETURN -#cbm PEEK_Q_1: -#cbm Q=PEEK(X-2)+PEEK(X-1)*256:RETURN -#cbm PEEK_Q_2: -#cbm Q=PEEK(X-4)+PEEK(X-3)*256:RETURN -#cbm PEEK_Q_Q: -#cbm Q=PEEK(X-Q*2)+PEEK(X-Q*2+1)*256:RETURN -#cbm PUT_Q: -#cbm POKE X,Q AND255:POKE X+1,Q/256:RETURN -#cbm PUT_Q_1: -#cbm POKE X-2,Q AND255:POKE X-1,Q/256:RETURN -#cbm PUT_Q_2: -#cbm POKE X-4,Q AND255:POKE X-3,Q/256:RETURN - -REM memory functions - -REM ALLOC(T,L) -> R -REM ALLOC(T,L,N) -> R -REM ALLOC(T,L,M,N) -> R -REM L is default for Z%(R,1) -REM M is default for Z%(R+1,0), if relevant for T -REM N is default for Z%(R+1,1), if relevant for T -ALLOC: - SZ=2 - IF T<6 OR T=9 OR T=12 OR T=14 THEN SZ=1 - REM PRINT "ALLOC T: "+STR$(T)+", SZ: "+STR$(SZ)+", ZK: "+STR$(ZK) - U=ZK - R=ZK - ALLOC_LOOP: - IF R=ZI THEN GOTO ALLOC_UNUSED - REM TODO sanity check that type is 15 - IF ((Z%(R,0)AND-32)/32)=SZ THEN GOTO ALLOC_MIDDLE - REM PRINT "ALLOC search: U: "+STR$(U)+", R: "+STR$(R) - U=R: REM previous set to current - R=Z%(R,1): REM current set to next - GOTO ALLOC_LOOP - ALLOC_MIDDLE: - REM PRINT "ALLOC_MIDDLE: U: "+STR$(U)+", R: "+STR$(R) - REM set free pointer (ZK) to next free - IF R=ZK THEN ZK=Z%(R,1) - REM set previous free to next free - IF R<>ZK THEN Z%(U,1)=Z%(R,1) - GOTO ALLOC_DONE - ALLOC_UNUSED: - REM PRINT "ALLOC_UNUSED ZI: "+STR$(ZI)+", U: "+STR$(U)+", R: "+STR$(R) - IF R+SZ>Z1 THEN PRINT "Out of mal memory!":END - ZI=ZI+SZ - IF U=R THEN ZK=ZI - REM set previous free to new memory top - IF U<>R THEN Z%(U,1)=ZI - GOTO ALLOC_DONE - ALLOC_DONE: - Z%(R,0)=T+32 - REM set Z%(R,1) to default L - IF T>=6 AND T<>9 THEN Z%(L,0)=Z%(L,0)+32 - Z%(R,1)=L - - IF SZ=1 THEN RETURN - Z%(R+1,0)=14: REM default for 6-8, and 13, and >=16 (metadata) - - REM function/macro/hash-map sets Z%(R+1,0) to default M - IF T=8 OR T=10 OR T=11 THEN Z%(M,0)=Z%(M,0)+32:Z%(R+1,0)=M - - REM seq, function/macro, environment sets Z%(R+1,1) to default N - Z%(N,0)=Z%(N,0)+32 - Z%(R+1,1)=N - RETURN - -REM FREE(AY, SZ) -> nil -FREE: - REM assumes reference count cleanup already (see RELEASE) - Z%(AY,0)=(SZ*32)+15: REM set type(15) and size - Z%(AY,1)=ZK - ZK=AY - IF SZ>=2 THEN Z%(AY+1,0)=0:Z%(AY+1,1)=0 - IF SZ>=3 THEN Z%(AY+2,0)=0:Z%(AY+2,1)=0 - RETURN - - -REM RELEASE(AY) -> nil -REM R should not be affected by this call -RELEASE: - RC=0 - - GOTO RELEASE_ONE - - RELEASE_TOP: - - IF RC=0 THEN RETURN - - REM pop next object to release, decrease remaining count - GOSUB POP_Q:AY=Q - RC=RC-1 - - RELEASE_ONE: - IF AY=-1 THEN RETURN - - U=Z%(AY,0)AND 31: REM type - V=Z%(AY,1): REM main value/reference - - REM AZ=AY: B=1: GOSUB PR_STR - REM PRINT "RELEASE AY:"+STR$(AY)+"["+R$+"] (byte0:"+STR$(Z%(AY,0))+")" - - REM sanity check not already freed - IF (U)=15 THEN PRINT "RELEASE of free:"+STR$(AY):END - IF Z%(AY,0)<15 THEN PRINT "RELEASE of unowned:"+STR$(AY):END - - REM decrease reference count by one - Z%(AY,0)=Z%(AY,0)-32 - - REM nil, false, true, empty sequences - IF AY<9 AND Z%(AY,0)<32 THEN PRINT "RELEASE of empty:"+STR$(AY):END - IF AY<9 THEN GOTO RELEASE_TOP - - REM our reference count is not 0, so don't release - IF Z%(AY,0)>=32 GOTO RELEASE_TOP - - REM switch on type - SZ=1: REM default FREE size, adjusted by RELEASE_* - IF U>=16 THEN GOSUB RELEASE_METADATA - -REM IF U<=3 OR U=9 THEN GOSUB RELEASE_SIMPLE -REM IF U=4 OR U=5 THEN GOSUB RELEASE_STRING -REM IF U>=6 AND U<=8 THEN GOSUB RELEASE_SEQ -REM IF U=10 OR U=11 THEN GOSUB RELEASE_MAL_FUNCTION -REM IF U>=16 THEN GOSUB RELEASE_METADATA -REM IF U=12 THEN GOSUB RELEASE_ATOM -REM IF U=13 THEN GOSUB RELEASE_ENV - - ON U+1 GOSUB RELEASE_SIMPLE,RELEASE_SIMPLE,RELEASE_SIMPLE,RELEASE_SIMPLE,RELEASE_STRING,RELEASE_STRING,RELEASE_SEQ,RELEASE_SEQ,RELEASE_HASH_MAP,RELEASE_SIMPLE,RELEASE_MAL_FUNCTION,RELEASE_MAL_FUNCTION,RELEASE_ATOM,RELEASE_ENV - - REM free the current element and continue, SZ already set - GOSUB FREE - GOTO RELEASE_TOP - - RELEASE_SIMPLE: - RETURN - RELEASE_STRING: - REM string type, release interned string, then FREE reference - IF S%(V)=0 THEN ER=-1:E$="RELEASE of free string:"+STR$(S%(V)):RETURN - S%(V)=S%(V)-1 - IF S%(V)=0 THEN S$(V)="": REM free BASIC string - REM free the atom itself - RETURN - RELEASE_SEQ: - SZ=2 - IF V=0 THEN RETURN - IF Z%(AY+1,0)<>14 THEN ER=-1:E$="invalid list value"+STR$(AY+1):RETURN - REM add value and next element to stack - RC=RC+2 - Q=Z%(AY+1,1):GOSUB PUSH_Q - Q=V:GOSUB PUSH_Q - RETURN - RELEASE_HASH_MAP: - SZ=2 - IF V=0 THEN RETURN - REM add key, value and next element to stack - RC=RC+3 - Q=Z%(AY+1,0):GOSUB PUSH_Q - Q=Z%(AY+1,1):GOSUB PUSH_Q - Q=V:GOSUB PUSH_Q - RETURN - RELEASE_ATOM: - REM add contained/referred value - RC=RC+1 - Q=V:GOSUB PUSH_Q - REM free the atom itself - RETURN - RELEASE_MAL_FUNCTION: - REM add ast, params and environment to stack - RC=RC+3 - Q=V:GOSUB PUSH_Q - Q=Z%(AY+1,0):GOSUB PUSH_Q - Q=Z%(AY+1,1):GOSUB PUSH_Q - REM free the current 2 element mal_function and continue - SZ=2:RETURN - RELEASE_METADATA: - REM add object and metadata object - RC=RC+2 - Q=V:GOSUB PUSH_Q - Q=Z%(AY+1,1):GOSUB PUSH_Q - SZ=2:RETURN - RELEASE_ENV: - REM add the hashmap data to the stack - RC=RC+1 - Q=V:GOSUB PUSH_Q - REM if outer set, add outer env to stack - IF Z%(AY+1,1)<>0 THEN RC=RC+1:Q=Z%(AY+1,1):GOSUB PUSH_Q - REM add outer environment to the stack - SZ=2:RETURN - - -REM release stack functions - -#qbasic PEND_A_LV: -#qbasic Y=Y+1:Y%(Y,0)=A:Y%(Y,1)=LV:RETURN -#qbasic -#qbasic REM RELEASE_PEND(LV) -> nil -#qbasic RELEASE_PEND: -#qbasic IF Y<0 THEN RETURN -#qbasic IF Y%(Y,1)<=LV THEN RETURN -#qbasic REM PRINT "RELEASE_PEND releasing:"+STR$(Y%(Y,0)) -#qbasic AY=Y%(Y,0):GOSUB RELEASE -#qbasic Y=Y-1 -#qbasic GOTO RELEASE_PEND - -#cbm PEND_A_LV: -#cbm Y=Y+4:POKE Y,A AND255:POKE Y+1,A/256 -#cbm POKE Y+2,LV AND255:POKE Y+3,LV/256:RETURN -#cbm -#cbm REM RELEASE_PEND(LV) -> nil -#cbm RELEASE_PEND: -#cbm IF Y R -VAL_R: - R=Z%(R+1,1) - RETURN - -REM VAL_A(A) -> A -VAL_A: - A=Z%(A+1,1) - RETURN - -REM VAL_B(B) -> B -VAL_B: - B=Z%(B+1,1) - RETURN - - REM general functions REM EQUAL_Q(A, B) -> R @@ -384,27 +12,27 @@ EQUAL_Q: Q=B:GOSUB PUSH_Q ED=ED+1 - T1=Z%(A,0)AND 31 - T2=Z%(B,0)AND 31 + T1=Z%(A)AND 31 + T2=Z%(B)AND 31 IF T1>5 AND T1<8 AND T2>5 AND T2<8 THEN GOTO EQUAL_Q_SEQ IF T1=8 AND T2=8 THEN GOTO EQUAL_Q_HM - IF T1<>T2 OR Z%(A,1)<>Z%(B,1) THEN R=0 + IF T1<>T2 OR Z%(A+1)<>Z%(B+1) THEN R=0 GOTO EQUAL_Q_DONE EQUAL_Q_SEQ: - IF Z%(A,1)=0 AND Z%(B,1)=0 THEN GOTO EQUAL_Q_DONE - IF Z%(A,1)=0 OR Z%(B,1)=0 THEN R=0:GOTO EQUAL_Q_DONE + IF Z%(A+1)=0 AND Z%(B+1)=0 THEN GOTO EQUAL_Q_DONE + IF Z%(A+1)=0 OR Z%(B+1)=0 THEN R=0:GOTO EQUAL_Q_DONE REM compare the elements - A=Z%(A+1,1):B=Z%(B+1,1) + A=Z%(A+2):B=Z%(B+2) GOTO EQUAL_Q_RECUR EQUAL_Q_SEQ_CONTINUE: REM next elements of the sequences GOSUB PEEK_Q_1:A=Q GOSUB PEEK_Q:B=Q - A=Z%(A,1):B=Z%(B,1) + A=Z%(A+1):B=Z%(B+1) Q=A:GOSUB PUT_Q_1 Q=B:GOSUB PUT_Q GOTO EQUAL_Q_SEQ @@ -480,31 +108,31 @@ REM sequence functions REM FORCE_SEQ_TYPE(A,T) -> R FORCE_SEQ_TYPE: REM if it's already the right type, inc ref cnt and return it - IF (Z%(A,0)AND 31)=T THEN R=A:Z%(R,0)=Z%(R,0)+32:RETURN + IF (Z%(A)AND 31)=T THEN R=A:Z%(R)=Z%(R)+32:RETURN REM if it's empty, return the empty sequence match T - IF A<9 THEN R=(T-5)*2+1:Z%(R,0)=Z%(R,0)+32:RETURN + IF A<16 THEN R=(T-4)*3:Z%(R)=Z%(R)+32:RETURN REM otherwise, copy first element to turn it into correct type - B=A:GOSUB VAL_B: REM value to copy - L=Z%(A,1):N=B:GOSUB ALLOC: REM T already set - IF Z%(A,1)=0 THEN RETURN + B=Z%(A+2): REM value to copy + L=Z%(A+1):M=B:GOSUB ALLOC: REM T already set + IF Z%(A+1)=0 THEN RETURN RETURN REM MAP_LOOP_START(T): REM - setup stack for map loop MAP_LOOP_START: REM point to empty sequence to start off - R=(T-5)*2+1: REM calculate location of empty seq - Z%(R,0)=Z%(R,0)+32 + R=(T-4)*3: REM calculate location of empty seq + Z%(R)=Z%(R)+32 GOSUB PUSH_R: REM push return ptr GOSUB PUSH_R: REM push empty ptr GOSUB PUSH_R: REM push current ptr RETURN -REM MAP_LOOP_UPDATE(C,N): +REM MAP_LOOP_UPDATE(C,M): REM MAP_LOOP_UPDATE(C,M,N): -REM - called after N (and M if T=8) are set -REM - C indicates whether to free N (and M if T=8) +REM - called after M (and N if T=8) are set +REM - C indicates whether to free M (and N if T=8) REM - update the structure of the return sequence MAP_LOOP_UPDATE: GOSUB PEEK_Q_1:L=Q: REM empty ptr @@ -513,14 +141,14 @@ MAP_LOOP_UPDATE: REM sequence took ownership AY=L:GOSUB RELEASE - IF C THEN AY=N:GOSUB RELEASE - IF C AND T=8 THEN AY=M:GOSUB RELEASE + IF C THEN AY=M:GOSUB RELEASE + IF C AND T=8 THEN AY=N:GOSUB RELEASE REM if not first element, set current next to point to new element GOSUB PEEK_Q - IF Q>8 THEN Z%(Q,1)=R + IF Q>14 THEN Z%(Q+1)=R REM if first element, set return to new element - IF Q<9 THEN Q=R:GOSUB PUT_Q_2 + IF Q<15 THEN Q=R:GOSUB PUT_Q_2 Q=R:GOSUB PUT_Q: REM update current ptr to new element RETURN @@ -537,13 +165,13 @@ MAP_LOOP_DONE: REM LIST_Q(A) -> R LIST_Q: R=0 - IF (Z%(A,0)AND 31)=6 THEN R=1 + IF (Z%(A)AND 31)=6 THEN R=1 RETURN REM EMPTY_Q(A) -> R EMPTY_Q: R=0 - IF Z%(A,1)=0 THEN R=1 + IF Z%(A+1)=0 THEN R=1 RETURN REM COUNT(A) -> R @@ -553,23 +181,23 @@ COUNT: R=-1 DO_COUNT_LOOP: R=R+1 - IF Z%(A,1)<>0 THEN A=Z%(A,1):GOTO DO_COUNT_LOOP + IF Z%(A+1)<>0 THEN A=Z%(A+1):GOTO DO_COUNT_LOOP GOSUB POP_A RETURN REM LAST(A) -> R LAST: REM TODO check that actually a list/vector - IF Z%(A,1)=0 THEN R=0:RETURN: REM empty seq, return nil + IF Z%(A+1)=0 THEN R=0:RETURN: REM empty seq, return nil W=0 LAST_LOOP: - IF Z%(A,1)=0 THEN GOTO LAST_DONE: REM end, return previous value + IF Z%(A+1)=0 THEN GOTO LAST_DONE: REM end, return previous value W=A: REM current becomes previous entry - A=Z%(A,1): REM next entry + A=Z%(A+1): REM next entry GOTO LAST_LOOP LAST_DONE: - R=W:GOSUB VAL_R - Z%(R,0)=Z%(R,0)+32 + R=Z%(W+2) + Z%(R)=Z%(R)+32 RETURN REM SLICE(A,B,C) -> R @@ -578,39 +206,39 @@ REM returns R6 as reference to last element of slice before empty REM returns A as next element following slice (of original) SLICE: I=0 - R=3: REM always a list - Z%(R,0)=Z%(R,0)+32 + R=6: REM always a list + Z%(R)=Z%(R)+32 R6=-1: REM last list element before empty W=R: REM temporary for return as R REM advance A to position B SLICE_FIND_B: - IF I0 THEN A=Z%(A,1):I=I+1:GOTO SLICE_FIND_B + IF I0 THEN A=Z%(A,1):I=I+1:GOTO SLICE_FIND_B SLICE_LOOP: REM if current position is C, then return IF C<>-1 AND I>=C THEN R=W:RETURN REM if we reached end of A, then return - IF Z%(A,1)=0 THEN R=W:RETURN + IF Z%(A+1)=0 THEN R=W:RETURN REM allocate new list element with copied value - T=6:L=3:N=Z%(A+1,1):GOSUB ALLOC + T=6:L=6:M=Z%(A+2):GOSUB ALLOC REM sequence took ownership AY=L:GOSUB RELEASE REM if not first element, set last to point to new element - IF R6>-1 THEN Z%(R6,1)=R + IF R6>-1 THEN Z%(R6+1)=R REM if first element, set return value to new element IF R6=-1 THEN W=R R6=R: REM update last list element REM advance to next element of A - A=Z%(A,1) + A=Z%(A+1) I=I+1 GOTO SLICE_LOOP REM LIST2(B,A) -> R LIST2: REM last element is 3 (empty list), second element is A - T=6:L=3:N=A:GOSUB ALLOC + T=6:L=6:M=A:GOSUB ALLOC REM first element is B - T=6:L=R:N=B:GOSUB ALLOC + T=6:L=R:M=B:GOSUB ALLOC AY=L:GOSUB RELEASE: REM new list takes ownership of previous RETURN @@ -620,7 +248,7 @@ LIST3: GOSUB LIST2 REM first element is C - T=6:L=R:N=C:GOSUB ALLOC + T=6:L=R:M=C:GOSUB ALLOC AY=L:GOSUB RELEASE: REM new list takes ownership of previous RETURN @@ -631,8 +259,8 @@ REM hashmap functions REM HASHMAP() -> R HASHMAP: REM just point to static empty hash-map - R=7 - Z%(R,0)=Z%(R,0)+32 + R=12 + Z%(R)=Z%(R)+32 RETURN REM ASSOC1(H, K, C) -> R @@ -653,16 +281,16 @@ ASSOC1_S: REM HASHMAP_GET(H, K) -> R REM - returns R3 with whether we found it or not HASHMAP_GET: - B$=S$(Z%(K,1)): REM search key string + B$=S$(Z%(K+1)): REM search key string R3=0: REM whether found or not (for HASHMAP_CONTAINS) R=0 HASHMAP_GET_LOOP: REM no matching key found - IF Z%(H,1)=0 THEN R=0:RETURN + IF Z%(H+1)=0 THEN R=0:RETURN REM get search string is equal to key string we found it - IF B$=S$(Z%(Z%(H+1,0),1)) THEN R3=1:R=Z%(H+1,1):RETURN + IF B$=S$(Z%(Z%(H+2)+1)) THEN R3=1:R=Z%(H+3):RETURN REM skip to next key/value - H=Z%(H,1) + H=Z%(H+1) GOTO HASHMAP_GET_LOOP REM HASHMAP_CONTAINS(H, K) -> R diff --git a/basic/variables.txt b/basic/variables.txt index bcaaee1359..98d3f8560d 100644 --- a/basic/variables.txt +++ b/basic/variables.txt @@ -54,8 +54,9 @@ Q : PUSH*, POP*, PEEK* return value (and PEEK_Q_Q call arg) AR : APPLY, DO_*_FUNCTION arg list AY : RELEASE/FREE arg AZ : PR_STR arg -P1 : PR_MEMORY, CHECK_FREE_LIST start -P2 : PR_MEMORY, CHECK_FREE_LIST end +P1 : PR_MEMORY, PR_OBJECT, CHECK_FREE_LIST start +P2 : PR_MEMORY, PR_OBJECT, CHECK_FREE_LIST end +P3 : PR_OBJECT, PR_MEMORY_VALUE R1 : REP, RE - MAL_READ result temp R2 : REP, RE - EVAL result temp R3 : HASHMAP_GET, DO_HASH_MAP, DO_KEYS_VALS temp and return value @@ -79,9 +80,9 @@ RD : PR_OBJECT recursion depth SD : READ_STR sequence read recursion depth C$ : READ_TOKEN, SKIP_SPACES, SKIP_TO_EOL current character -G : function value ON GOTO switch flag -I : STRING, REPLACE, SLICE, PR_MEMORY, PR_OBJECT -J : REPLACE +G : function value ON GOTO switch flag, EVAL_AST changed flag +I : STRING, REPLACE, SLICE, PR_MEMORY, PR_OBJECT, PR_MEMORY_VALUE +J : REPLACE, PR_MEMORY_VALUE U : ALLOC, RELEASE, PR_STR temp V : RELEASE, PR_STR_SEQ temp W : SLICE, LAST, QUASIQUOTE, DO_HASH_MAP, DO_KEYS_VALS, step2-3 EVAL temp From 983d9f3b20e5fb30cb8e530f33ded90971bbac5f Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Thu, 25 Aug 2016 22:26:26 +0530 Subject: [PATCH 0240/2308] Common Lisp: Implement step 0 --- .gitignore | 1 + Makefile | 3 ++- common-lisp/Makefile | 4 ++++ common-lisp/run | 2 ++ common-lisp/step0_repl.asd | 22 ++++++++++++++++++ common-lisp/step0_repl.lisp | 45 +++++++++++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 common-lisp/Makefile create mode 100755 common-lisp/run create mode 100644 common-lisp/step0_repl.asd create mode 100644 common-lisp/step0_repl.lisp diff --git a/.gitignore b/.gitignore index 54c0158517..5d30679a21 100644 --- a/.gitignore +++ b/.gitignore @@ -119,3 +119,4 @@ basic/step8_macros.bas basic/step9_try.bas basic/stepA_mal.bas basic/*.prg +common-lisp/*.image diff --git a/Makefile b/Makefile index eb810be468..250b25cd07 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ DOCKERIZE = # Settings # -IMPLS = ada awk bash basic c d chuck clojure coffee clisp cpp crystal cs dart \ +IMPLS = ada awk bash basic c d chuck clojure coffee clisp common-lisp cpp crystal cs dart \ erlang elisp elixir es6 factor forth fsharp go groovy guile haskell \ haxe io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ @@ -154,6 +154,7 @@ chuck_STEP_TO_PROG = chuck/$($(1)).ck clojure_STEP_TO_PROG = clojure/target/$($(1)).jar coffee_STEP_TO_PROG = coffee/$($(1)).coffee clisp_STEP_TO_PROG = clisp/$($(1)).fas +common-lisp_STEP_TO_PROG = common-lisp/$($(1)) cpp_STEP_TO_PROG = cpp/$($(1)) crystal_STEP_TO_PROG = crystal/$($(1)) cs_STEP_TO_PROG = cs/$($(1)).exe diff --git a/common-lisp/Makefile b/common-lisp/Makefile new file mode 100644 index 0000000000..a98c1f7c5a --- /dev/null +++ b/common-lisp/Makefile @@ -0,0 +1,4 @@ +ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) + +step% : step%.lisp + cl-launch -v -l sbcl +Q -S $(ROOT_DIR) -s $@ -d $@.image -o $@ -E 'mal:main' diff --git a/common-lisp/run b/common-lisp/run new file mode 100755 index 0000000000..8ba68a5484 --- /dev/null +++ b/common-lisp/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/common-lisp/step0_repl.asd b/common-lisp/step0_repl.asd new file mode 100644 index 0000000000..3acb26598c --- /dev/null +++ b/common-lisp/step0_repl.asd @@ -0,0 +1,22 @@ +#-quicklisp +(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" + (user-homedir-pathname)))) + (when (probe-file quicklisp-init) + (load quicklisp-init))) + +(ql:quickload :cl-readline) +(ql:quickload :uiop) + +(defpackage #:mal-asd + (:use :cl :asdf)) + +(in-package :mal-asd) + +(defsystem "step0_repl" + :name "MAL" + :version "1.0" + :author "Iqbal Ansari" + :description "Implementation of step 0 of MAL in Common Lisp" + :serial t + :components ((:file "step0_repl")) + :depends-on (:uiop :cl-readline)) diff --git a/common-lisp/step0_repl.lisp b/common-lisp/step0_repl.lisp new file mode 100644 index 0000000000..e891ee6016 --- /dev/null +++ b/common-lisp/step0_repl.lisp @@ -0,0 +1,45 @@ +(defpackage :mal + (:use :common-lisp + :uiop) + (:export :main)) + +(in-package :mal) + +(defun mal-read (string) + string) + +(defun mal-eval (ast) + ast) + +(defun mal-print (expression) + expression) + +(defun rep (string) + (mal-print (mal-eval (mal-read string)))) + +(defvar *use-readline-p* nil) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (if *use-readline-p* + (cl-readline:readline :prompt prompt + :add-history t + :novelty-check (lambda (old new) + (not (string= old new)))) + (raw-input prompt))) + +(defun mal-writeline (string) + (when string + (write-line string) + (force-output *standard-output*))) + +(defun main (&optional (argv nil argv-provided-p)) + (declare (ignorable argv argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") + (string= (uiop:getenv "TERM") "dumb")))) + (loop do (let ((line (mal-readline "user> "))) + (if line (mal-writeline (rep line)) (return))))) From 8164982fd49c2a759666c74be981c69f5628e841 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Fri, 26 Aug 2016 23:45:29 +0530 Subject: [PATCH 0241/2308] Common Lisp: Implement step 1 --- common-lisp/Makefile | 2 +- common-lisp/printer.lisp | 58 +++++++++ common-lisp/reader.lisp | 196 ++++++++++++++++++++++++++++++ common-lisp/step1_read_print.asd | 28 +++++ common-lisp/step1_read_print.lisp | 53 ++++++++ common-lisp/types.lisp | 120 ++++++++++++++++++ common-lisp/utils.lisp | 22 ++++ 7 files changed, 478 insertions(+), 1 deletion(-) create mode 100644 common-lisp/printer.lisp create mode 100644 common-lisp/reader.lisp create mode 100644 common-lisp/step1_read_print.asd create mode 100644 common-lisp/step1_read_print.lisp create mode 100644 common-lisp/types.lisp create mode 100644 common-lisp/utils.lisp diff --git a/common-lisp/Makefile b/common-lisp/Makefile index a98c1f7c5a..5b90fb8c82 100644 --- a/common-lisp/Makefile +++ b/common-lisp/Makefile @@ -1,4 +1,4 @@ ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) -step% : step%.lisp +step% : step%.lisp utils.lisp types.lisp printer.lisp reader.lisp cl-launch -v -l sbcl +Q -S $(ROOT_DIR) -s $@ -d $@.image -o $@ -E 'mal:main' diff --git a/common-lisp/printer.lisp b/common-lisp/printer.lisp new file mode 100644 index 0000000000..a6e25661ba --- /dev/null +++ b/common-lisp/printer.lisp @@ -0,0 +1,58 @@ +(defpackage :printer + (:use :common-lisp :types :genhash) + (:import-from :cl-ppcre + :regex-replace) + (:import-from :utils + :replace-all) + (:export :pr-str)) + +(in-package :printer) + +(defun pr-mal-sequence (start-delimiter sequence end-delimiter &optional (print-readably t)) + (concatenate 'string + start-delimiter + (format nil + "~{~a~^ ~}" + (map 'list (lambda (value) + (pr-str value print-readably)) + (types:mal-data-value sequence))) + end-delimiter)) + +(defun pr-mal-hash-map (hash-map &optional (print-readably t)) + (let ((hash-map-value (types:mal-data-value hash-map))) + (concatenate 'string + "{" + (format nil + "~{~a~^ ~}" + (let (repr) + (genhash:hashmap (lambda (key value) + (push (format nil + "~a ~a" + (pr-str key print-readably) + (pr-str value print-readably)) + repr)) + hash-map-value) + repr)) + "}"))) + +(defun pr-string (ast &optional (print-readably t)) + (if print-readably + (utils:replace-all (prin1-to-string (types:mal-data-value ast)) + " +" + "\\n") + (types:mal-data-value ast))) + +(defun pr-str (ast &optional (print-readably t)) + (when ast + (switch-mal-type ast + (types:number (format nil "~d" (types:mal-data-value ast))) + (types:boolean (if (types:mal-data-value ast) "true" "false")) + (types:nil "nil") + (types:string (pr-string ast print-readably)) + (types:symbol (format nil "~a" (types:mal-data-value ast))) + (types:keyword (format nil "~a" (types:mal-data-value ast))) + (types:list (pr-mal-sequence "(" ast ")" print-readably)) + (types:vector (pr-mal-sequence "[" ast "]" print-readably)) + (types:hash-map (pr-mal-hash-map ast print-readably)) + (types:atom (format nil "(atom ~a)" (pr-str (types:mal-data-value ast))))))) diff --git a/common-lisp/reader.lisp b/common-lisp/reader.lisp new file mode 100644 index 0000000000..068e54bec7 --- /dev/null +++ b/common-lisp/reader.lisp @@ -0,0 +1,196 @@ +(defpackage :reader + (:use :common-lisp :types :genhash) + (:import-from :cl-ppcre + :create-scanner + :do-matches-as-strings + :scan) + (:import-from :utils + :replace-all) + (:export :read-str + :eof + :unexpected-token)) + +(in-package :reader) + +;; Possible errors that can be raised while reading a string +(define-condition unexpected-token (error) + ((expected :initarg :expected :reader expected-token) + (actual :initarg :actual :reader actual-token)) + (:report (lambda (condition stream) + (format stream + "Unexpected token (~a) encountered while reading, expected ~a" + (actual-token condition) + (expected-token condition)))) + (:documentation "Error raised when an unexpected token is encountered while reading.")) + +(define-condition eof (error) + ((context :initarg :context :reader context)) + (:report (lambda (condition stream) + (format stream + "EOF encountered while reading ~a" + (context condition)))) + (:documentation "Error raised when EOF is encountered while reading.")) + +(defvar *tokenizer-re* (create-scanner "[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}('\"`,;)]*)") + "Regular expression to tokenize Lisp code") + +(defvar *number-re* (create-scanner "^(-|\\+)?[\\d]+$") + "Regular expression to match a number") + +(defvar *string-re* (create-scanner "^\"(?:\\\\.|[^\\\\\"])*\"$") + "Regular expression to match a string") + +(defvar *whitespace-chars* + '(#\Space #\Newline #\Backspace #\Tab + #\Linefeed #\Page #\Return #\Rubout #\,) + "Characters to treat as whitespace, these are trimmed in `tokenize'") + +(defun tokenize (string) + "Tokenize given string. + +This function extracts all tokens from the string using *tokenizer-re* +comments are ignored. + +Implementation notes: The regex scan generates some empty tokens, not really +sure why." + (let (tokens) + (do-matches-as-strings (match *tokenizer-re* string) + (let ((token (string-trim *whitespace-chars* match))) + (unless (or (zerop (length token)) + (char= (char token 0) #\;)) + (push token tokens)))) + (nreverse tokens))) + +;; Reader +(defstruct (token-reader) + (tokens nil)) + +(defun peek (reader) + "Returns the next token in the reader without advancing the token stream." + (car (token-reader-tokens reader))) + +(defun next (reader) + "Returns the next token and advances the token stream." + (pop (token-reader-tokens reader))) + +(defun consume (reader &optional (token nil token-provided-p)) + "Consume the next token and advance the token stream. + +If the optional argument token is provided the token stream is advanced only +if token being consumes matches it otherwise and unexpected token error is +raised" + (let ((actual-token (pop (token-reader-tokens reader)))) + (when (and token-provided-p + (not (equal actual-token token))) + (error 'unexpected-token + :expected token + :actual actual-token))) + reader) + +(defun parse-string (token) + (if (and (> (length token) 1) + (scan *string-re* token)) + (read-from-string (utils:replace-all token + "\\n" + " +")) + (error 'eof + :context "string"))) + +(defun read-form-with-meta (reader) + (consume reader) + (let ((meta (read-form reader)) + (value (read-form reader))) + + (when (or (null meta) + (null value)) + (error 'eof + :context "object metadata")) + + (make-mal-list (list (make-mal-symbol '|with-meta|) value meta)))) + +(defun expand-quote (reader) + (let ((quote (next reader))) + (make-mal-list (list (make-mal-symbol (cond + ((string= quote "'") "quote") + ((string= quote "`") "quasiquote") + ((string= quote "~") "unquote") + ((string= quote "~@") "splice-unquote") + ((string= quote "@") "deref"))) + (read-form reader))))) + +(defun read-mal-sequence (reader &optional (delimiter ")") (constructor 'list)) + ;; Consume the opening brace + (consume reader) + (let (forms) + (loop + for token = (peek reader) + while (cond + ((null token) (error 'eof + :context (if (string= delimiter ")") + "list" + "vector"))) + ((string= token delimiter) (return)) + (t (push (read-form reader) forms)))) + ;; Consume the closing brace + (consume reader) + (apply constructor (nreverse forms)))) + +(defun read-hash-map (reader) + ;; Consume the open brace + (consume reader) + (let (forms + (hash-map (types:make-mal-value-hash-table))) + (loop + for token = (peek reader) + while (cond + ((null token) (error 'eof + :context "hash-map")) + ((string= token "}") (return)) + (t (let ((key (read-form reader)) + (value (read-form reader))) + (if (null value) + (error 'eof + :context "hash-map") + (push (cons key value) forms)))))) + ;; Consume the closing brace + (consume reader) + ;; Construct the hash table + (dolist (key-value (nreverse forms)) + (setf (genhash:hashref (car key-value) hash-map) (cdr key-value))) + hash-map)) + +(defun read-atom (reader) + (let ((token (next reader))) + (cond + ((string= token "false") + (make-mal-boolean nil)) + ((string= token "true") + (make-mal-boolean t)) + ((string= token "nil") + (make-mal-nil nil)) + ((char= (char token 0) #\") + (make-mal-string (parse-string token))) + ((char= (char token 0) #\:) + (make-mal-keyword token)) + ((scan *number-re* token) + (make-mal-number (read-from-string token))) + (t (make-mal-symbol token))))) + +(defun read-form (reader) + (let ((token (peek reader))) + (cond + ((null token) nil) + ((string= token "(") (make-mal-list (read-mal-sequence reader + ")" + 'list))) + ((string= token "[") (make-mal-vector (read-mal-sequence reader + "]" + 'vector))) + ((string= token "{") (make-mal-hash-map (read-hash-map reader))) + ((string= token "^") (read-form-with-meta reader)) + ((member token '("'" "`" "~" "~@" "@") :test #'string=) (expand-quote reader)) + (t (read-atom reader))))) + +(defun read-str (string) + (read-form (make-token-reader :tokens (tokenize string)))) diff --git a/common-lisp/step1_read_print.asd b/common-lisp/step1_read_print.asd new file mode 100644 index 0000000000..33c6d448c7 --- /dev/null +++ b/common-lisp/step1_read_print.asd @@ -0,0 +1,28 @@ +#-quicklisp +(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" + (user-homedir-pathname)))) + (when (probe-file quicklisp-init) + (load quicklisp-init))) + +(ql:quickload :uiop) +(ql:quickload :cl-readline) +(ql:quickload :cl-ppcre) +(ql:quickload :genhash) + +(defpackage #:mal-asd + (:use :cl :asdf)) + +(in-package :mal-asd) + +(defsystem "step1_read_print" + :name "MAL" + :version "1.0" + :author "Iqbal Ansari" + :description "Implementation of step 1 of MAL in Common Lisp" + :serial t + :components ((:file "utils") + (:file "types") + (:file "reader") + (:file "printer") + (:file "step1_read_print")) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) diff --git a/common-lisp/step1_read_print.lisp b/common-lisp/step1_read_print.lisp new file mode 100644 index 0000000000..b6b3e195fd --- /dev/null +++ b/common-lisp/step1_read_print.lisp @@ -0,0 +1,53 @@ +(defpackage :mal + (:use :common-lisp + :reader + :printer + :utils) + (:export :main)) + +(in-package :mal) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + ast) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + (make-hash-table :test #'equal))) + (reader:eof (condition) + (format nil + "~a" + condition)))) + +(defvar *use-readline-p* nil) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (if *use-readline-p* + (cl-readline:readline :prompt prompt + :add-history t + :novelty-check (lambda (old new) + (not (string= old new)))) + (raw-input prompt))) + +(defun mal-writeline (string) + (when string + (write-line string) + (force-output *standard-output*))) + +(defun main (&optional (argv nil argv-provided-p)) + (declare (ignorable argv argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") + (string= (uiop:getenv "TERM") "dumb")))) + (loop do (let ((line (mal-readline "user> "))) + (if line (mal-writeline (rep line)) (return))))) diff --git a/common-lisp/types.lisp b/common-lisp/types.lisp new file mode 100644 index 0000000000..420838b9b5 --- /dev/null +++ b/common-lisp/types.lisp @@ -0,0 +1,120 @@ +(defpackage :types + (:use :common-lisp :genhash) + (:export ;; Accessors + :mal-data-value + :mal-data-type + :mal-data-meta + :mal-data-attrs + ;; Mal values + :number + :make-mal-number + :mal-number-p + + :boolean + :make-mal-boolean + :mal-boolean-p + + :nil + :make-mal-nil + :mal-nil-p + + :string + :make-mal-string + :mal-string-p + + :symbol + :make-mal-symbol + :mal-symbol-p + + :keyword + :make-mal-keyword + :mal-keyword-p + + :list + :make-mal-list + :mal-list-p + + :vector + :make-mal-vector + :mal-vector-p + + :hash-map + :make-mal-hash-map + :mal-hash-map-p + + :atom + :make-mal-atom + :mal-atom-p + + :any + + :switch-mal-type + + ;; Hashing mal values + :make-mal-value-hash-table)) + +(in-package :types) + +(defstruct mal-data + (value nil :read-only t) + (type nil :read-only t) + meta + attrs) + +;; Create a constructor and predicate for given type +(defmacro define-mal-type (type) + (let ((constructor (intern (string-upcase (concatenate 'string + "make-mal-" + (symbol-name type))))) + (predicate (intern (string-upcase (concatenate 'string + "mal-" + (symbol-name type) + "-p"))))) + `(progn (defun ,constructor (value &key meta attrs) + (make-mal-data :type ',type + :value value + :meta meta + :attrs attrs)) + + (defun ,predicate (value) + (when (typep value 'mal-data) + (eq (mal-data-type value) ',type)))))) + +(define-mal-type number) +(define-mal-type symbol) +(define-mal-type keyword) +(define-mal-type string) +;; TODO true, false and nil should ideally be singleton +(define-mal-type boolean) +(define-mal-type nil) + +(define-mal-type list) +(define-mal-type vector) +(define-mal-type hash-map) + +(define-mal-type atom) + +;; Generic type +(defvar any) + +(defun mal-data-value= (value1 value2) + (equal (mal-data-value value1) + (mal-data-value value2))) + +(defun make-mal-value-hash-table () + (unless (gethash 'mal-data-value-hash genhash::*hash-test-designator-map*) + (genhash:register-test-designator 'mal-data-value-hash + #'sxhash + #'mal-data-value=)) + (genhash:make-generic-hash-table :test 'mal-data-value-hash)) + +(defmacro switch-mal-type (ast &body forms) + `(let ((type (mal-data-type ,ast))) + (cond + ,@(mapcar (lambda (form) + (list (if (or (equal (car form) t) + (equal (car form) 'any)) + t + (list 'equal (list 'quote (car form)) 'type)) + (cadr form))) + forms)))) diff --git a/common-lisp/utils.lisp b/common-lisp/utils.lisp new file mode 100644 index 0000000000..3c4a58f0d6 --- /dev/null +++ b/common-lisp/utils.lisp @@ -0,0 +1,22 @@ +(defpackage :utils + (:use :common-lisp + :uiop) + (:export :replace-all + :getenv)) + +(in-package :utils) + +(defun replace-all (string part replacement &key (test #'char=)) + "Returns a new string in which all the occurences of the part +is replaced with replacement." + (with-output-to-string (out) + (loop with part-length = (length part) + for old-pos = 0 then (+ pos part-length) + for pos = (search part string + :start2 old-pos + :test test) + do (write-string string out + :start old-pos + :end (or pos (length string))) + when pos do (write-string replacement out) + while pos))) From d045c9cff428ae8179c89a6a49af6bd83b6c9c8b Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 27 Aug 2016 19:00:14 +0530 Subject: [PATCH 0242/2308] Common Lisp: Implement step 2 --- common-lisp/Makefile | 2 +- common-lisp/env.lisp | 12 ++++ common-lisp/printer.lisp | 7 ++- common-lisp/reader.lisp | 6 +- common-lisp/step2_eval.asd | 29 +++++++++ common-lisp/step2_eval.lisp | 116 ++++++++++++++++++++++++++++++++++++ common-lisp/types.lisp | 56 ++++++++++++++++- 7 files changed, 221 insertions(+), 7 deletions(-) create mode 100644 common-lisp/env.lisp create mode 100644 common-lisp/step2_eval.asd create mode 100644 common-lisp/step2_eval.lisp diff --git a/common-lisp/Makefile b/common-lisp/Makefile index 5b90fb8c82..fdc660fbb4 100644 --- a/common-lisp/Makefile +++ b/common-lisp/Makefile @@ -1,4 +1,4 @@ ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) -step% : step%.lisp utils.lisp types.lisp printer.lisp reader.lisp +step% : step%.lisp utils.lisp types.lisp env.lisp printer.lisp reader.lisp cl-launch -v -l sbcl +Q -S $(ROOT_DIR) -s $@ -d $@.image -o $@ -E 'mal:main' diff --git a/common-lisp/env.lisp b/common-lisp/env.lisp new file mode 100644 index 0000000000..88c6d74bb7 --- /dev/null +++ b/common-lisp/env.lisp @@ -0,0 +1,12 @@ +(defpackage :env + (:use :common-lisp) + (:export :undefined-symbol)) + +(in-package :env) + +(define-condition undefined-symbol (error) + ((symbol :initarg :symbol :reader env-symbol)) + (:report (lambda (condition stream) + (format stream + "'~a' not found" + (env-symbol condition))))) diff --git a/common-lisp/printer.lisp b/common-lisp/printer.lisp index a6e25661ba..b3525c1678 100644 --- a/common-lisp/printer.lisp +++ b/common-lisp/printer.lisp @@ -1,5 +1,7 @@ (defpackage :printer - (:use :common-lisp :types :genhash) + (:use :common-lisp + :types + :genhash) (:import-from :cl-ppcre :regex-replace) (:import-from :utils @@ -55,4 +57,5 @@ (types:list (pr-mal-sequence "(" ast ")" print-readably)) (types:vector (pr-mal-sequence "[" ast "]" print-readably)) (types:hash-map (pr-mal-hash-map ast print-readably)) - (types:atom (format nil "(atom ~a)" (pr-str (types:mal-data-value ast))))))) + (types:atom (format nil "(atom ~a)" (pr-str (types:mal-data-value ast)))) + (types:builtin-fn "# "))) + (if line (mal-writeline (rep line)) (return))))) diff --git a/common-lisp/types.lisp b/common-lisp/types.lisp index 420838b9b5..43c90f60b5 100644 --- a/common-lisp/types.lisp +++ b/common-lisp/types.lisp @@ -1,5 +1,6 @@ (defpackage :types - (:use :common-lisp :genhash) + (:use :common-lisp + :genhash) (:export ;; Accessors :mal-data-value :mal-data-type @@ -46,12 +47,19 @@ :make-mal-atom :mal-atom-p + :builtin-fn + :make-mal-builtin-fn + :mal-builtin-fn-p + :any :switch-mal-type ;; Hashing mal values - :make-mal-value-hash-table)) + :make-mal-value-hash-table + + ;; Utilities + :apply-unwrapped-values)) (in-package :types) @@ -94,6 +102,8 @@ (define-mal-type atom) +(define-mal-type builtin-fn) + ;; Generic type (defvar any) @@ -118,3 +128,45 @@ (list 'equal (list 'quote (car form)) 'type)) (cadr form))) forms)))) + +(defun wrap-value (value &key booleanp listp) + (typecase value + (number (make-mal-number value)) + ;; This needs to be before symbol since nil is a symbol + (null (funcall (cond + (booleanp #'make-mal-boolean) + (listp #'make-mal-list) + (t #'make-mal-nil)) + value)) + ;; This needs to before symbol since t, nil are symbols + (boolean (make-mal-boolean value)) + (keyword (make-mal-keyword value)) + (symbol (make-mal-symbol (symbol-name value))) + (string (make-mal-string value)) + (list (make-mal-list (map 'list #'wrap-value value))) + (vector (make-mal-vector (map 'vector #'wrap-value value))) + (hash-table (make-mal-hash-map (let ((new-hash-table (make-mal-value-hash-table))) + (loop + for key being the hash-keys of value + do (setf (gethash (wrap-value key) new-hash-table) + (wrap-value (gethash key value)))) + new-hash-table))))) + +(defun unwrap-value (value) + (switch-mal-type value + (list (mapcar #'unwrap-value (mal-data-value value))) + (vector (map 'vector #'unwrap-value (mal-data-value value))) + (hash-map (let ((hash-table (make-hash-table)) + (hash-map-value (mal-data-value value))) + (loop + for key being the hash-keys of hash-map-value + do (setf (gethash (mal-data-value key) hash-table) + (mal-data-value (gethash key hash-map-value)))) + hash-table)) + (any (mal-data-value value)))) + +(defun apply-unwrapped-values (op &rest values) + (wrap-value (apply op (mapcar #'unwrap-value values)))) + +(defun apply-unwrapped-values-prefer-bool (op &rest values) + (wrap-value (apply op (mapcar #'unwrap-value values)) :booleanp t)) From 526eda57416dd4912d6d36eeb97adacaf8939f56 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 29 Oct 2016 17:14:51 +0530 Subject: [PATCH 0243/2308] Common Lisp: Implement step 3 --- common-lisp/env.lisp | 43 +++++++++-- common-lisp/step3_env.asd | 29 ++++++++ common-lisp/step3_env.lisp | 145 +++++++++++++++++++++++++++++++++++++ common-lisp/types.lisp | 28 ++++++- 4 files changed, 238 insertions(+), 7 deletions(-) create mode 100644 common-lisp/step3_env.asd create mode 100644 common-lisp/step3_env.lisp diff --git a/common-lisp/env.lisp b/common-lisp/env.lisp index 88c6d74bb7..fe67afdcf4 100644 --- a/common-lisp/env.lisp +++ b/common-lisp/env.lisp @@ -1,12 +1,45 @@ (defpackage :env - (:use :common-lisp) - (:export :undefined-symbol)) + (:use :common-lisp :types) + (:shadow :symbol) + (:export :undefined-symbol + :create-mal-env + :get-env + :find-env + :set-env)) (in-package :env) -(define-condition undefined-symbol (error) - ((symbol :initarg :symbol :reader env-symbol)) +(define-condition undefined-symbol (types:mal-runtime-exception) + ((symbol :initarg :symbol :reader symbol)) (:report (lambda (condition stream) (format stream "'~a' not found" - (env-symbol condition))))) + (symbol condition))))) + +(defstruct mal-env + (bindings (make-hash-table :test 'equal) :read-only t) + (parent nil :read-only t)) + +(defun find-env (env symbol) + (let ((value (gethash (types:mal-data-value symbol) + (mal-env-bindings env))) + (parent (mal-env-parent env))) + (cond + (value value) + (parent (find-env parent symbol)) + (t nil)))) + +(defun get-env (env symbol) + (let ((value (find-env env symbol))) + (if value + value + (error 'undefined-symbol + :symbol (format nil "~a" (types:mal-data-value symbol)))))) + +(defun set-env (env symbol value) + (setf (gethash (types:mal-data-value symbol) + (mal-env-bindings env)) + value)) + +(defun create-mal-env (&key (parent nil)) + (make-mal-env :parent parent)) diff --git a/common-lisp/step3_env.asd b/common-lisp/step3_env.asd new file mode 100644 index 0000000000..a0bbc36a9f --- /dev/null +++ b/common-lisp/step3_env.asd @@ -0,0 +1,29 @@ +#-quicklisp +(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" + (user-homedir-pathname)))) + (when (probe-file quicklisp-init) + (load quicklisp-init))) + +(ql:quickload :uiop) +(ql:quickload :cl-readline) +(ql:quickload :cl-ppcre) +(ql:quickload :genhash) + +(defpackage #:mal-asd + (:use :cl :asdf)) + +(in-package :mal-asd) + +(defsystem "step3_env" + :name "MAL" + :version "1.0" + :author "Iqbal Ansari" + :description "Implementation of step 3 of MAL in Common Lisp" + :serial t + :components ((:file "utils") + (:file "types") + (:file "env") + (:file "reader") + (:file "printer") + (:file "step3_env")) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) diff --git a/common-lisp/step3_env.lisp b/common-lisp/step3_env.lisp new file mode 100644 index 0000000000..d82db99cf4 --- /dev/null +++ b/common-lisp/step3_env.lisp @@ -0,0 +1,145 @@ +(defpackage :mal + (:use :common-lisp + :types + :env + :reader + :printer + :genhash) + (:export :main)) + +(in-package :mal) + +(defvar *repl-env* (env:create-mal-env)) + +(env:set-env *repl-env* + (types:make-mal-symbol "+") + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:apply-unwrapped-values '+ + value1 + value2)))) + +(env:set-env *repl-env* + (types:make-mal-symbol "-") + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:apply-unwrapped-values '- + value1 + value2)))) + +(env:set-env *repl-env* + (types:make-mal-symbol "*") + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:apply-unwrapped-values '* + value1 + value2)))) + +(env:set-env *repl-env* + (types:make-mal-symbol "/") + (types:make-mal-builtin-fn (lambda (value1 value2) + (types:apply-unwrapped-values '/ + value1 + value2)))) + +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-let* (make-mal-symbol "let*")) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (types:mal-data-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (types:mal-data-value hash-map)) + (new-hash-table (types:make-mal-value-hash-table))) + (genhash:hashmap (lambda (key value) + (setf (genhash:hashref (mal-eval key env) new-hash-table) + (mal-eval value env))) + hash-map-value) + (types:make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env )) + (types:any ast))) + +(defun eval-let* (forms env) + (let ((new-env (env:create-mal-env :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (types:mal-data-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + types:mal-nil) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + + (mal-eval (third forms) new-env))) + +(defun eval-list (ast env) + (let ((forms (mal-data-value ast))) + (cond + ((mal-data-value= mal-def! (first forms)) + (env:set-env env (second forms) (mal-eval (third forms) env))) + ((mal-data-value= mal-let* (first forms)) + (eval-let* forms env)) + (t (let ((evaluated-list (eval-ast ast env))) + (apply (types:mal-data-value (car evaluated-list)) + (cdr evaluated-list))))))) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + (cond + ((null ast) types:mal-nil) + ((not (types:mal-list-p ast)) (eval-ast ast env)) + ((zerop (length (mal-data-value ast))) ast) + (t (eval-list ast env)))) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (error (condition) + (format nil + "~a" + condition)))) + +(defvar *use-readline-p* nil) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (if *use-readline-p* + (cl-readline:readline :prompt prompt + :add-history t + :novelty-check (lambda (old new) + (not (string= old new)))) + (raw-input prompt))) + +(defun mal-writeline (string) + (when string + (write-line string) + (force-output *standard-output*))) + +(defun main (&optional (argv nil argv-provided-p)) + (declare (ignorable argv argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") + (string= (uiop:getenv "TERM") "dumb")))) + (loop do (let ((line (mal-readline "user> "))) + (if line (mal-writeline (rep line)) (return))))) diff --git a/common-lisp/types.lisp b/common-lisp/types.lisp index 43c90f60b5..7f6122bfd1 100644 --- a/common-lisp/types.lisp +++ b/common-lisp/types.lisp @@ -1,7 +1,8 @@ (defpackage :types (:use :common-lisp :genhash) - (:export ;; Accessors + (:export :mal-data-value= + ;; Accessors :mal-data-value :mal-data-type :mal-data-meta @@ -52,17 +53,36 @@ :mal-builtin-fn-p :any - :switch-mal-type + ;; Singleton values + :mal-nil + :mal-true + :mal-false + ;; Hashing mal values :make-mal-value-hash-table + ;; Error types + :mal-exception + ;; Exceptions raised by the runtime + :mal-runtime-exception + ;; Error + :mal-error ;; Utilities :apply-unwrapped-values)) (in-package :types) +(define-condition mal-error (error) + nil) + +(define-condition mal-exception (error) + nil) + +(define-condition mal-runtime-exception (mal-exception) + nil) + (defstruct mal-data (value nil :read-only t) (type nil :read-only t) @@ -104,6 +124,10 @@ (define-mal-type builtin-fn) +(defvar mal-nil (make-mal-nil nil)) +(defvar mal-true (make-mal-boolean t)) +(defvar mal-false (make-mal-boolean nil)) + ;; Generic type (defvar any) From 82b73d0b2975f1da3bdb71a4dc52b77d5fc5bf2c Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 29 Oct 2016 19:03:46 +0530 Subject: [PATCH 0244/2308] Common Lisp: Implement step 4 --- common-lisp/Makefile | 2 +- common-lisp/core.lisp | 111 ++++++++++++++++++++++++ common-lisp/env.lisp | 26 +++++- common-lisp/printer.lisp | 3 +- common-lisp/step4_if_fn_do.asd | 30 +++++++ common-lisp/step4_if_fn_do.lisp | 148 ++++++++++++++++++++++++++++++++ common-lisp/types.lisp | 63 +++++++++++--- 7 files changed, 367 insertions(+), 16 deletions(-) create mode 100644 common-lisp/core.lisp create mode 100644 common-lisp/step4_if_fn_do.asd create mode 100644 common-lisp/step4_if_fn_do.lisp diff --git a/common-lisp/Makefile b/common-lisp/Makefile index fdc660fbb4..b585d82210 100644 --- a/common-lisp/Makefile +++ b/common-lisp/Makefile @@ -1,4 +1,4 @@ ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) -step% : step%.lisp utils.lisp types.lisp env.lisp printer.lisp reader.lisp +step% : step%.lisp utils.lisp types.lisp env.lisp printer.lisp reader.lisp core.lisp cl-launch -v -l sbcl +Q -S $(ROOT_DIR) -s $@ -d $@.image -o $@ -E 'mal:main' diff --git a/common-lisp/core.lisp b/common-lisp/core.lisp new file mode 100644 index 0000000000..2410d44064 --- /dev/null +++ b/common-lisp/core.lisp @@ -0,0 +1,111 @@ +(defpackage :core + (:use :common-lisp + :types + :printer) + (:export :ns)) + +(in-package :core) + +(defmacro wrap-boolean (form) + `(if ,form + types:mal-true + types:mal-false)) + +(defun mal-add (value1 value2) + (types:apply-unwrapped-values '+ value1 value2)) + +(defun mal-sub (value1 value2) + (types:apply-unwrapped-values '- value1 value2)) + +(defun mal-mul (value1 value2) + (types:apply-unwrapped-values '* value1 value2)) + +(defun mal-div (value1 value2) + (types:make-mal-number (round (/ (types:mal-data-value value1) + (types:mal-data-value value2))))) + +(defun mal-prn (&rest strings) + (format t + "~{~a~^ ~}" + (mapcar (lambda (string) (printer:pr-str string t)) + strings)) + (terpri) + (force-output *standard-output*) + (types:make-mal-nil nil)) + +(defun mal-println (&rest strings) + (format t + "~{~a~^ ~}" + (mapcar (lambda (string) (printer:pr-str string nil)) + strings)) + (terpri) + (force-output *standard-output*) + (types:make-mal-nil nil)) + +(defun mal-pr-str (&rest strings) + (types:make-mal-string (format nil + "~{~a~^ ~}" + (mapcar (lambda (string) (printer:pr-str string t)) + strings)))) + +(defun mal-str (&rest strings) + (types:make-mal-string (format nil + "~{~a~}" + (mapcar (lambda (string) (printer:pr-str string nil)) + strings)))) + +(defun mal-list (&rest values) + (make-mal-list values)) + +(defun mal-list? (value) + (wrap-boolean (or (types:mal-nil-p value) + (types:mal-list-p value)))) + +(defun mal-empty? (value) + (wrap-boolean (zerop (length (types:mal-data-value value))))) + +(defun mal-length (value) + (types:apply-unwrapped-values 'length value)) + +(defun mal-= (value1 value2) + (wrap-boolean (types:mal-data-value= value1 value2))) + +(defun mal-< (value1 value2) + (types:apply-unwrapped-values-prefer-bool '< + value1 + value2)) + +(defun mal-> (value1 value2) + (types:apply-unwrapped-values-prefer-bool '> + value1 + value2)) + +(defun mal-<= (value1 value2) + (types:apply-unwrapped-values-prefer-bool '<= + value1 + value2)) + +(defun mal->= (value1 value2) + (types:apply-unwrapped-values-prefer-bool '>= + value1 + value2)) + +(defvar ns + (list + (cons (types:make-mal-symbol "+") (types:make-mal-builtin-fn #'mal-add)) + (cons (types:make-mal-symbol "-") (types:make-mal-builtin-fn #'mal-sub)) + (cons (types:make-mal-symbol "*") (types:make-mal-builtin-fn #'mal-mul)) + (cons (types:make-mal-symbol "/") (types:make-mal-builtin-fn #'mal-div)) + (cons (types:make-mal-symbol "prn") (types:make-mal-builtin-fn #'mal-prn)) + (cons (types:make-mal-symbol "println") (types:make-mal-builtin-fn #'mal-println)) + (cons (types:make-mal-symbol "pr-str") (types:make-mal-builtin-fn #'mal-pr-str)) + (cons (types:make-mal-symbol "str") (types:make-mal-builtin-fn #'mal-str)) + (cons (types:make-mal-symbol "list") (types:make-mal-builtin-fn #'mal-list)) + (cons (types:make-mal-symbol "list?") (types:make-mal-builtin-fn #'mal-list?)) + (cons (types:make-mal-symbol "empty?") (types:make-mal-builtin-fn #'mal-empty?)) + (cons (types:make-mal-symbol "count") (types:make-mal-builtin-fn #'mal-length)) + (cons (types:make-mal-symbol "=") (types:make-mal-builtin-fn #'mal-=)) + (cons (types:make-mal-symbol "<") (types:make-mal-builtin-fn #'mal-<)) + (cons (types:make-mal-symbol ">") (types:make-mal-builtin-fn #'mal->)) + (cons (types:make-mal-symbol "<=") (types:make-mal-builtin-fn #'mal-<=)) + (cons (types:make-mal-symbol ">=") (types:make-mal-builtin-fn #'mal->=)))) diff --git a/common-lisp/env.lisp b/common-lisp/env.lisp index fe67afdcf4..f63364d8d5 100644 --- a/common-lisp/env.lisp +++ b/common-lisp/env.lisp @@ -16,6 +16,15 @@ "'~a' not found" (symbol condition))))) +(define-condition arity-mismatch (types:mal-runtime-exception) + ((required :initarg :required :reader required) + (provided :initarg :provided :reader provided)) + (:report (lambda (condition stream) + (format stream + "Unexpected number of arguments provided, expected ~a, got ~a" + (required condition) + (provided condition))))) + (defstruct mal-env (bindings (make-hash-table :test 'equal) :read-only t) (parent nil :read-only t)) @@ -41,5 +50,18 @@ (mal-env-bindings env)) value)) -(defun create-mal-env (&key (parent nil)) - (make-mal-env :parent parent)) +(defun create-mal-env (&key (parent nil) (binds nil) (exprs nil)) + (let ((env (make-mal-env :parent parent))) + (loop + while binds + do (let ((key (pop binds))) + (if (string= (types:mal-data-value key) "&") + (let ((key (pop binds))) + (unless key + (error 'arity-mismatch + :required (length binds) + :provided (length exprs))) + (set-env env key (types:make-mal-list exprs)) + (setq binds nil)) + (set-env env key (pop exprs))))) + env)) diff --git a/common-lisp/printer.lisp b/common-lisp/printer.lisp index b3525c1678..de227a32dd 100644 --- a/common-lisp/printer.lisp +++ b/common-lisp/printer.lisp @@ -58,4 +58,5 @@ (types:vector (pr-mal-sequence "[" ast "]" print-readably)) (types:hash-map (pr-mal-hash-map ast print-readably)) (types:atom (format nil "(atom ~a)" (pr-str (types:mal-data-value ast)))) - (types:builtin-fn "#") + (types:builtin-fn "#")))) diff --git a/common-lisp/step4_if_fn_do.asd b/common-lisp/step4_if_fn_do.asd new file mode 100644 index 0000000000..3a01d64853 --- /dev/null +++ b/common-lisp/step4_if_fn_do.asd @@ -0,0 +1,30 @@ +#-quicklisp +(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" + (user-homedir-pathname)))) + (when (probe-file quicklisp-init) + (load quicklisp-init))) + +(ql:quickload :uiop) +(ql:quickload :cl-readline) +(ql:quickload :cl-ppcre) +(ql:quickload :genhash) + +(defpackage #:mal-asd + (:use :cl :asdf)) + +(in-package :mal-asd) + +(defsystem "step4_if_fn_do" + :name "MAL" + :version "1.0" + :author "Iqbal Ansari" + :description "Implementation of step 4 of MAL in Common Lisp" + :serial t + :components ((:file "utils") + (:file "types") + (:file "env") + (:file "reader") + (:file "printer") + (:file "core") + (:file "step4_if_fn_do")) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) diff --git a/common-lisp/step4_if_fn_do.lisp b/common-lisp/step4_if_fn_do.lisp new file mode 100644 index 0000000000..3e9f37ba95 --- /dev/null +++ b/common-lisp/step4_if_fn_do.lisp @@ -0,0 +1,148 @@ +(defpackage :mal + (:use :common-lisp + :types + :env + :reader + :printer + :core) + (:export :main)) + +(in-package :mal) + +(defvar *repl-env* (env:create-mal-env)) + +(dolist (binding core:ns) + (env:set-env *repl-env* + (car binding) + (cdr binding))) + +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-let* (make-mal-symbol "let*")) +(defvar mal-do (make-mal-symbol "do")) +(defvar mal-if (make-mal-symbol "if")) +(defvar mal-fn* (make-mal-symbol "fn*")) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (mal-data-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (types:mal-data-value hash-map)) + (new-hash-table (types:make-mal-value-hash-table))) + (genhash:hashmap (lambda (key value) + (setf (genhash:hashref (mal-eval key env) new-hash-table) + (mal-eval value env))) + hash-map-value) + (types:make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env)) + (types:any ast))) + +(defun eval-let* (forms env) + (let ((new-env (env:create-mal-env :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (mal-data-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + types:mal-nil) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + + (mal-eval (third forms) new-env))) + +(defun eval-list (ast env) + (let ((forms (mal-data-value ast))) + (cond + ((mal-data-value= mal-def! (first forms)) + (env:set-env env (second forms) (mal-eval (third forms) env))) + ((mal-data-value= mal-let* (first forms)) + (eval-let* forms env)) + ((mal-data-value= mal-do (first forms)) + (car (last (mapcar (lambda (form) (mal-eval form env)) + (cdr forms))))) + ((mal-data-value= mal-if (first forms)) + (let ((predicate (mal-eval (second forms) env))) + (mal-eval (if (or (mal-data-value= predicate types:mal-nil) + (mal-data-value= predicate types:mal-false)) + (fourth forms) + (third forms)) + env))) + ((mal-data-value= mal-fn* (first forms)) + (types:make-mal-fn (let ((arglist (second forms)) + (body (third forms))) + (lambda (&rest args) + (mal-eval body (env:create-mal-env :parent env + :binds (map 'list + #'identity + (mal-data-value arglist)) + :exprs args)))))) + (t (let* ((evaluated-list (eval-ast ast env)) + (function (car evaluated-list))) + ;; If first element is a mal function unwrap it + (apply (mal-data-value function) + (cdr evaluated-list))))))) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + (cond + ((null ast) types:mal-nil) + ((not (types:mal-list-p ast)) (eval-ast ast env)) + ((zerop (length (mal-data-value ast))) ast) + (t (eval-list ast env)))) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (error (condition) + (format nil + "~a" + condition)))) + +(rep "(def! not (fn* (a) (if a false true)))") + +(defvar *use-readline-p* nil) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (if *use-readline-p* + (cl-readline:readline :prompt prompt + :add-history t + :novelty-check (lambda (old new) + (not (string= old new)))) + (raw-input prompt))) + +(defun mal-writeline (string) + (when string + (write-line string) + (force-output *standard-output*))) + +(defun main (&optional (argv nil argv-provided-p)) + (declare (ignorable argv argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") + (string= (uiop:getenv "TERM") "dumb")))) + (loop do (let ((line (mal-readline "user> "))) + (if line (mal-writeline (rep line)) (return))))) diff --git a/common-lisp/types.lisp b/common-lisp/types.lisp index 7f6122bfd1..137900db48 100644 --- a/common-lisp/types.lisp +++ b/common-lisp/types.lisp @@ -52,6 +52,10 @@ :make-mal-builtin-fn :mal-builtin-fn-p + :fn + :make-mal-fn + :mal-fn-p + :any :switch-mal-type @@ -70,7 +74,8 @@ :mal-error ;; Utilities - :apply-unwrapped-values)) + :apply-unwrapped-values + :apply-unwrapped-values-prefer-bool)) (in-package :types) @@ -122,6 +127,7 @@ (define-mal-type atom) +(define-mal-type fn) (define-mal-type builtin-fn) (defvar mal-nil (make-mal-nil nil)) @@ -131,17 +137,6 @@ ;; Generic type (defvar any) -(defun mal-data-value= (value1 value2) - (equal (mal-data-value value1) - (mal-data-value value2))) - -(defun make-mal-value-hash-table () - (unless (gethash 'mal-data-value-hash genhash::*hash-test-designator-map*) - (genhash:register-test-designator 'mal-data-value-hash - #'sxhash - #'mal-data-value=)) - (genhash:make-generic-hash-table :test 'mal-data-value-hash)) - (defmacro switch-mal-type (ast &body forms) `(let ((type (mal-data-type ,ast))) (cond @@ -153,6 +148,50 @@ (cadr form))) forms)))) +(defun mal-sequence= (value1 value2) + (let ((sequence1 (map 'list #'identity (mal-data-value value1))) + (sequence2 (map 'list #'identity (mal-data-value value2)))) + (when (= (length sequence1) (length sequence2)) + (every #'identity + (loop + for x in sequence1 + for y in sequence2 + collect (mal-data-value= x y)))))) + +(defun mal-hash-map= (value1 value2) + (let ((map1 (mal-data-value value1)) + (map2 (mal-data-value value2)) + (identical t)) + (when (= (genhash:generic-hash-table-count map1) + (genhash:generic-hash-table-count map2)) + (genhash:hashmap (lambda (key value) + (declare (ignorable value)) + (setf identical + (and identical (mal-data-value= (genhash:hashref key map1) + (genhash:hashref key map2))))) + map1) + identical))) + +(defun mal-data-value= (value1 value2) + (when (and (typep value1 'mal-data) + (typep value2 'mal-data)) + (if (equal (mal-data-type value1) (mal-data-type value2)) + (switch-mal-type value1 + (list (mal-sequence= value1 value2)) + (vector (mal-sequence= value1 value2)) + (hash-map (mal-hash-map= value1 value2)) + (any (equal (mal-data-value value1) (mal-data-value value2)))) + (when (or (and (mal-list-p value1) (mal-vector-p value2)) + (and (mal-list-p value2) (mal-vector-p value1))) + (mal-sequence= value1 value2))))) + +(defun make-mal-value-hash-table () + (unless (gethash 'mal-data-value-hash genhash::*hash-test-designator-map*) + (genhash:register-test-designator 'mal-data-value-hash + #'sxhash + #'mal-data-value=)) + (genhash:make-generic-hash-table :test 'mal-data-value-hash)) + (defun wrap-value (value &key booleanp listp) (typecase value (number (make-mal-number value)) From 626e3a1f7e27577852458119dfb206e8a7ee0142 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 29 Oct 2016 19:03:59 +0530 Subject: [PATCH 0245/2308] Common Lisp: Implement step 5 --- common-lisp/step5_tco.asd | 30 +++++++ common-lisp/step5_tco.lisp | 159 +++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 common-lisp/step5_tco.asd create mode 100644 common-lisp/step5_tco.lisp diff --git a/common-lisp/step5_tco.asd b/common-lisp/step5_tco.asd new file mode 100644 index 0000000000..09df9dc95f --- /dev/null +++ b/common-lisp/step5_tco.asd @@ -0,0 +1,30 @@ +#-quicklisp +(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" + (user-homedir-pathname)))) + (when (probe-file quicklisp-init) + (load quicklisp-init))) + +(ql:quickload :uiop) +(ql:quickload :cl-readline) +(ql:quickload :cl-ppcre) +(ql:quickload :genhash) + +(defpackage #:mal-asd + (:use :cl :asdf)) + +(in-package :mal-asd) + +(defsystem "step5_tco" + :name "MAL" + :version "1.0" + :author "Iqbal Ansari" + :description "Implementation of step 5 of MAL in Common Lisp" + :serial t + :components ((:file "utils") + (:file "types") + (:file "env") + (:file "reader") + (:file "printer") + (:file "core") + (:file "step5_tco")) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) diff --git a/common-lisp/step5_tco.lisp b/common-lisp/step5_tco.lisp new file mode 100644 index 0000000000..c2db76f291 --- /dev/null +++ b/common-lisp/step5_tco.lisp @@ -0,0 +1,159 @@ +(defpackage :mal + (:use :common-lisp + :types + :env + :reader + :printer + :core) + (:export :main)) + +(in-package :mal) + +(defvar *repl-env* (env:create-mal-env)) + +(dolist (binding core:ns) + (env:set-env *repl-env* + (car binding) + (cdr binding))) + +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-let* (make-mal-symbol "let*")) +(defvar mal-do (make-mal-symbol "do")) +(defvar mal-if (make-mal-symbol "if")) +(defvar mal-fn* (make-mal-symbol "fn*")) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (mal-data-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (types:mal-data-value hash-map)) + (new-hash-table (types:make-mal-value-hash-table))) + (genhash:hashmap (lambda (key value) + (setf (genhash:hashref (mal-eval key env) new-hash-table) + (mal-eval value env))) + hash-map-value) + (types:make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env)) + (types:any ast))) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + (loop + do (cond + ((null ast) (return types:mal-nil)) + ((not (types:mal-list-p ast)) (return (eval-ast ast env))) + ((zerop (length (mal-data-value ast))) (return ast)) + (t (let ((forms (mal-data-value ast))) + (cond + ((mal-data-value= mal-def! (first forms)) + (return (env:set-env env (second forms) (mal-eval (third forms) env)))) + + ((mal-data-value= mal-let* (first forms)) + (let ((new-env (env:create-mal-env :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (mal-data-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + types:mal-nil) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + (setf ast (third forms) + env new-env))) + + ((mal-data-value= mal-do (first forms)) + (mapc (lambda (form) (mal-eval form env)) + (butlast (cdr forms))) + (setf ast (car (last forms)))) + + ((mal-data-value= mal-if (first forms)) + (let ((predicate (mal-eval (second forms) env))) + (setf ast (if (or (mal-data-value= predicate types:mal-nil) + (mal-data-value= predicate types:mal-false)) + (fourth forms) + (third forms))))) + + ((mal-data-value= mal-fn* (first forms)) + (return (let ((arglist (second forms)) + (body (third forms))) + (types:make-mal-fn (lambda (&rest args) + (mal-eval body (env:create-mal-env :parent env + :binds (map 'list + #'identity + (mal-data-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env)))))) + + (t (let* ((evaluated-list (eval-ast ast env)) + (function (car evaluated-list))) + ;; If first element is a mal function unwrap it + (if (not (types:mal-fn-p function)) + (return (apply (mal-data-value function) + (cdr evaluated-list))) + (let* ((attrs (types:mal-data-attrs function))) + (setf ast (cdr (assoc 'ast attrs)) + env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-data-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))))))))))) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (error (condition) + (format nil + "~a" + condition)))) + +(rep "(def! not (fn* (a) (if a false true)))") + +(defvar *use-readline-p* nil) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (if *use-readline-p* + (cl-readline:readline :prompt prompt + :add-history t + :novelty-check (lambda (old new) + (not (string= old new)))) + (raw-input prompt))) + +(defun mal-writeline (string) + (when string + (write-line string) + (force-output *standard-output*))) + +(defun main (&optional (argv nil argv-provided-p)) + (declare (ignorable argv argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") + (string= (uiop:getenv "TERM") "dumb")))) + (loop do (let ((line (mal-readline "user> "))) + (if line (mal-writeline (rep line)) (return))))) From 3dc177e40854798b256ad14786a586a25de268aa Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 2 Nov 2016 12:51:14 +0530 Subject: [PATCH 0246/2308] Common Lisp: Implement step 6 --- common-lisp/core.lisp | 35 ++++++- common-lisp/step6_file.asd | 30 ++++++ common-lisp/step6_file.lisp | 182 ++++++++++++++++++++++++++++++++++++ common-lisp/types.lisp | 10 +- common-lisp/utils.lisp | 4 +- 5 files changed, 255 insertions(+), 6 deletions(-) create mode 100644 common-lisp/step6_file.asd create mode 100644 common-lisp/step6_file.lisp diff --git a/common-lisp/core.lisp b/common-lisp/core.lisp index 2410d44064..2708d0a0ed 100644 --- a/common-lisp/core.lisp +++ b/common-lisp/core.lisp @@ -1,6 +1,8 @@ (defpackage :core (:use :common-lisp + :utils :types + :reader :printer) (:export :ns)) @@ -90,6 +92,30 @@ value1 value2)) +(defun mal-read-string (value) + (reader:read-str (types:mal-data-value value))) + +(defun mal-slurp (filename) + (types:apply-unwrapped-values 'read-file-string filename)) + +(defun mal-atom (value) + (types:make-mal-atom value)) + +(defun mal-atom? (value) + (wrap-boolean (types:mal-atom-p value))) + +(defun mal-deref (atom) + (types:mal-data-value atom)) + +(defun mal-reset! (atom value) + (setf (types:mal-data-value atom) value)) + +(defun mal-swap! (atom fn &rest args) + (setf (types:mal-data-value atom) + (apply (types:mal-data-value fn) + (append (list (types:mal-data-value atom)) + args)))) + (defvar ns (list (cons (types:make-mal-symbol "+") (types:make-mal-builtin-fn #'mal-add)) @@ -108,4 +134,11 @@ (cons (types:make-mal-symbol "<") (types:make-mal-builtin-fn #'mal-<)) (cons (types:make-mal-symbol ">") (types:make-mal-builtin-fn #'mal->)) (cons (types:make-mal-symbol "<=") (types:make-mal-builtin-fn #'mal-<=)) - (cons (types:make-mal-symbol ">=") (types:make-mal-builtin-fn #'mal->=)))) + (cons (types:make-mal-symbol ">=") (types:make-mal-builtin-fn #'mal->=)) + (cons (types:make-mal-symbol "read-string") (types:make-mal-builtin-fn #'mal-read-string)) + (cons (types:make-mal-symbol "slurp") (types:make-mal-builtin-fn #'mal-slurp)) + (cons (types:make-mal-symbol "atom") (types:make-mal-builtin-fn #'mal-atom)) + (cons (types:make-mal-symbol "atom?") (types:make-mal-builtin-fn #'mal-atom?)) + (cons (types:make-mal-symbol "deref") (types:make-mal-builtin-fn #'mal-deref)) + (cons (types:make-mal-symbol "reset!") (types:make-mal-builtin-fn #'mal-reset!)) + (cons (types:make-mal-symbol "swap!") (types:make-mal-builtin-fn #'mal-swap!)))) diff --git a/common-lisp/step6_file.asd b/common-lisp/step6_file.asd new file mode 100644 index 0000000000..60babadadc --- /dev/null +++ b/common-lisp/step6_file.asd @@ -0,0 +1,30 @@ +#-quicklisp +(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" + (user-homedir-pathname)))) + (when (probe-file quicklisp-init) + (load quicklisp-init))) + +(ql:quickload :uiop) +(ql:quickload :cl-readline) +(ql:quickload :cl-ppcre) +(ql:quickload :genhash) + +(defpackage #:mal-asd + (:use :cl :asdf)) + +(in-package :mal-asd) + +(defsystem "step6_file" + :name "MAL" + :version "1.0" + :author "Iqbal Ansari" + :description "Implementation of step 6 of MAL in Common Lisp" + :serial t + :components ((:file "utils") + (:file "types") + (:file "env") + (:file "reader") + (:file "printer") + (:file "core") + (:file "step6_file")) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) diff --git a/common-lisp/step6_file.lisp b/common-lisp/step6_file.lisp new file mode 100644 index 0000000000..81e42b3d18 --- /dev/null +++ b/common-lisp/step6_file.lisp @@ -0,0 +1,182 @@ +(defpackage :mal + (:use :common-lisp + :types + :env + :reader + :printer + :core + :utils) + (:export :main)) + +(in-package :mal) + +(defvar *repl-env* (env:create-mal-env)) + +(dolist (binding core:ns) + (env:set-env *repl-env* + (car binding) + (cdr binding))) + +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-let* (make-mal-symbol "let*")) +(defvar mal-do (make-mal-symbol "do")) +(defvar mal-if (make-mal-symbol "if")) +(defvar mal-fn* (make-mal-symbol "fn*")) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (mal-data-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (types:mal-data-value hash-map)) + (new-hash-table (types:make-mal-value-hash-table))) + (genhash:hashmap (lambda (key value) + (setf (genhash:hashref (mal-eval key env) new-hash-table) + (mal-eval value env))) + hash-map-value) + (types:make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env)) + (types:any ast))) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + (loop + do (cond + ((null ast) (return types:mal-nil)) + ((not (types:mal-list-p ast)) (return (eval-ast ast env))) + ((zerop (length (mal-data-value ast))) (return ast)) + (t (let ((forms (mal-data-value ast))) + (cond + ((mal-data-value= mal-def! (first forms)) + (return (env:set-env env (second forms) (mal-eval (third forms) env)))) + + ((mal-data-value= mal-let* (first forms)) + (let ((new-env (env:create-mal-env :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (mal-data-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + types:mal-nil) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + (setf ast (third forms) + env new-env))) + + ((mal-data-value= mal-do (first forms)) + (mapc (lambda (form) (mal-eval form env)) + (butlast (cdr forms))) + (setf ast (car (last forms)))) + + ((mal-data-value= mal-if (first forms)) + (let ((predicate (mal-eval (second forms) env))) + (setf ast (if (or (mal-data-value= predicate types:mal-nil) + (mal-data-value= predicate types:mal-false)) + (fourth forms) + (third forms))))) + + ((mal-data-value= mal-fn* (first forms)) + (return (let ((arglist (second forms)) + (body (third forms))) + (types:make-mal-fn (lambda (&rest args) + (mal-eval body (env:create-mal-env :parent env + :binds (map 'list + #'identity + (mal-data-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env)))))) + + (t (let* ((evaluated-list (eval-ast ast env)) + (function (car evaluated-list))) + ;; If first element is a mal function unwrap it + (if (not (types:mal-fn-p function)) + (return (apply (mal-data-value function) + (cdr evaluated-list))) + (let* ((attrs (types:mal-data-attrs function))) + (setf ast (cdr (assoc 'ast attrs)) + env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-data-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))))))))))) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (error (condition) + (format nil + "~a" + condition)))) + +(env:set-env *repl-env* + (types:make-mal-symbol "eval") + (types:make-mal-builtin-fn (lambda (ast) + (mal-eval ast *repl-env*)))) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + +(defvar *use-readline-p* nil) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (if *use-readline-p* + (cl-readline:readline :prompt prompt + :add-history t + :novelty-check (lambda (old new) + (not (string= old new)))) + (raw-input prompt))) + +(defun mal-writeline (string) + (when string + (write-line string) + (force-output *standard-output*))) + +(defun repl () + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return))))) + +(defun run-file (file) + (rep (format nil "(load-file \"~a\")" file))) + +(defun main (&optional (argv nil argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") + (string= (uiop:getenv "TERM") "dumb")))) + + (let ((args (if argv-provided-p + argv + (cdr (utils:raw-command-line-arguments))))) + (env:set-env *repl-env* + (types:make-mal-symbol "*ARGV*") + (types:wrap-value (cdr args) :listp t)) + (if (null args) + (repl) + (run-file (car args))))) diff --git a/common-lisp/types.lisp b/common-lisp/types.lisp index 137900db48..25f959ccca 100644 --- a/common-lisp/types.lisp +++ b/common-lisp/types.lisp @@ -74,6 +74,8 @@ :mal-error ;; Utilities + :wrap-value + :unwrap-value :apply-unwrapped-values :apply-unwrapped-values-prefer-bool)) @@ -89,7 +91,7 @@ nil) (defstruct mal-data - (value nil :read-only t) + (value nil) (type nil :read-only t) meta attrs) @@ -210,9 +212,9 @@ (vector (make-mal-vector (map 'vector #'wrap-value value))) (hash-table (make-mal-hash-map (let ((new-hash-table (make-mal-value-hash-table))) (loop - for key being the hash-keys of value - do (setf (gethash (wrap-value key) new-hash-table) - (wrap-value (gethash key value)))) + for key being the hash-keys of value + do (setf (gethash (wrap-value key) new-hash-table) + (wrap-value (gethash key value)))) new-hash-table))))) (defun unwrap-value (value) diff --git a/common-lisp/utils.lisp b/common-lisp/utils.lisp index 3c4a58f0d6..f90bfd5943 100644 --- a/common-lisp/utils.lisp +++ b/common-lisp/utils.lisp @@ -2,7 +2,9 @@ (:use :common-lisp :uiop) (:export :replace-all - :getenv)) + :getenv + :read-file-string + :raw-command-line-arguments)) (in-package :utils) From 3e9628d8d73e1169b28d900aae2648e2c70f376a Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 29 Oct 2016 23:05:19 +0530 Subject: [PATCH 0247/2308] Common Lisp: Implement step 7 --- common-lisp/core.lisp | 15 ++- common-lisp/step7_quote.asd | 30 +++++ common-lisp/step7_quote.lisp | 218 +++++++++++++++++++++++++++++++++++ 3 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 common-lisp/step7_quote.asd create mode 100644 common-lisp/step7_quote.lisp diff --git a/common-lisp/core.lisp b/common-lisp/core.lisp index 2708d0a0ed..bd332306f2 100644 --- a/common-lisp/core.lisp +++ b/common-lisp/core.lisp @@ -116,6 +116,17 @@ (append (list (types:mal-data-value atom)) args)))) +(defun mal-cons (element list) + (types:make-mal-list (cons element + (map 'list + #'identity + (types:mal-data-value list))))) + +(defun mal-concat (&rest lists) + (types:make-mal-list (apply #'concatenate + 'list + (mapcar #'types:mal-data-value lists)))) + (defvar ns (list (cons (types:make-mal-symbol "+") (types:make-mal-builtin-fn #'mal-add)) @@ -141,4 +152,6 @@ (cons (types:make-mal-symbol "atom?") (types:make-mal-builtin-fn #'mal-atom?)) (cons (types:make-mal-symbol "deref") (types:make-mal-builtin-fn #'mal-deref)) (cons (types:make-mal-symbol "reset!") (types:make-mal-builtin-fn #'mal-reset!)) - (cons (types:make-mal-symbol "swap!") (types:make-mal-builtin-fn #'mal-swap!)))) + (cons (types:make-mal-symbol "swap!") (types:make-mal-builtin-fn #'mal-swap!)) + (cons (types:make-mal-symbol "cons") (types:make-mal-builtin-fn #'mal-cons)) + (cons (types:make-mal-symbol "concat") (types:make-mal-builtin-fn #'mal-concat)))) diff --git a/common-lisp/step7_quote.asd b/common-lisp/step7_quote.asd new file mode 100644 index 0000000000..1d0406e5e3 --- /dev/null +++ b/common-lisp/step7_quote.asd @@ -0,0 +1,30 @@ +#-quicklisp +(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" + (user-homedir-pathname)))) + (when (probe-file quicklisp-init) + (load quicklisp-init))) + +(ql:quickload :uiop) +(ql:quickload :cl-readline) +(ql:quickload :cl-ppcre) +(ql:quickload :genhash) + +(defpackage #:mal-asd + (:use :cl :asdf)) + +(in-package :mal-asd) + +(defsystem "step7_quote" + :name "MAL" + :version "1.0" + :author "Iqbal Ansari" + :description "Implementation of step 7 of MAL in Common Lisp" + :serial t + :components ((:file "utils") + (:file "types") + (:file "env") + (:file "reader") + (:file "printer") + (:file "core") + (:file "step7_quote")) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) diff --git a/common-lisp/step7_quote.lisp b/common-lisp/step7_quote.lisp new file mode 100644 index 0000000000..b60fe3609a --- /dev/null +++ b/common-lisp/step7_quote.lisp @@ -0,0 +1,218 @@ +(defpackage :mal + (:use :common-lisp + :types + :env + :reader + :printer + :core + :utils) + (:export :main)) + +(in-package :mal) + +(defvar *repl-env* (env:create-mal-env)) + +(dolist (binding core:ns) + (env:set-env *repl-env* + (car binding) + (cdr binding))) + +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-let* (make-mal-symbol "let*")) +(defvar mal-do (make-mal-symbol "do")) +(defvar mal-if (make-mal-symbol "if")) +(defvar mal-fn* (make-mal-symbol "fn*")) +(defvar mal-quote (make-mal-symbol "quote")) +(defvar mal-quasiquote (make-mal-symbol "quasiquote")) +(defvar mal-unquote (make-mal-symbol "unquote")) +(defvar mal-splice-unquote (make-mal-symbol "splice-unquote")) +(defvar mal-cons (make-mal-symbol "cons")) +(defvar mal-concat (make-mal-symbol "concat")) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (mal-data-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (types:mal-data-value hash-map)) + (new-hash-table (types:make-mal-value-hash-table))) + (genhash:hashmap (lambda (key value) + (setf (genhash:hashref (mal-eval key env) new-hash-table) + (mal-eval value env))) + hash-map-value) + (types:make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env)) + (types:any ast))) + +(defun is-pair (value) + (and (or (mal-list-p value) + (mal-vector-p value)) + (< 0 (length (types:mal-data-value value))))) + +(defun quasiquote (ast) + (if (not (is-pair ast)) + (types:make-mal-list (list mal-quote ast)) + (let ((forms (map 'list #'identity (mal-data-value ast)))) + (cond + ((mal-data-value= mal-unquote (first forms)) + (second forms)) + + ((and (is-pair (first forms)) + (mal-data-value= mal-splice-unquote + (first (mal-data-value (first forms))))) + (types:make-mal-list (list mal-concat + (second (mal-data-value (first forms))) + (quasiquote (make-mal-list (cdr forms)))))) + + (t (types:make-mal-list (list mal-cons + (quasiquote (first forms)) + (quasiquote (make-mal-list (cdr forms)))))))))) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + (loop + do (cond + ((null ast) (return types:mal-nil)) + ((not (types:mal-list-p ast)) (return (eval-ast ast env))) + ((zerop (length (mal-data-value ast))) (return ast)) + (t (let ((forms (mal-data-value ast))) + (cond + ((mal-data-value= mal-quote (first forms)) + (return (second forms))) + + ((mal-data-value= mal-quasiquote (first forms)) + (setf ast (quasiquote (second forms)))) + + ((mal-data-value= mal-def! (first forms)) + (return (env:set-env env (second forms) (mal-eval (third forms) env)))) + + ((mal-data-value= mal-let* (first forms)) + (let ((new-env (env:create-mal-env :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (mal-data-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + types:mal-nil) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + (setf ast (third forms) + env new-env))) + + ((mal-data-value= mal-do (first forms)) + (mapc (lambda (form) (mal-eval form env)) + (butlast (cdr forms))) + (setf ast (car (last forms)))) + + ((mal-data-value= mal-if (first forms)) + (let ((predicate (mal-eval (second forms) env))) + (setf ast (if (or (mal-data-value= predicate types:mal-nil) + (mal-data-value= predicate types:mal-false)) + (fourth forms) + (third forms))))) + + ((mal-data-value= mal-fn* (first forms)) + (return (let ((arglist (second forms)) + (body (third forms))) + (types:make-mal-fn (lambda (&rest args) + (mal-eval body (env:create-mal-env :parent env + :binds (map 'list + #'identity + (mal-data-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env)))))) + + (t (let* ((evaluated-list (eval-ast ast env)) + (function (car evaluated-list))) + ;; If first element is a mal function unwrap it + (if (not (types:mal-fn-p function)) + (return (apply (mal-data-value function) + (cdr evaluated-list))) + (let* ((attrs (types:mal-data-attrs function))) + (setf ast (cdr (assoc 'ast attrs)) + env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (mal-data-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))))))))))) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (error (condition) + (format nil + "~a" + condition)))) + +(env:set-env *repl-env* + (types:make-mal-symbol "eval") + (types:make-mal-builtin-fn (lambda (ast) + (mal-eval ast *repl-env*)))) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + +(defvar *use-readline-p* nil) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (if *use-readline-p* + (cl-readline:readline :prompt prompt + :add-history t + :novelty-check (lambda (old new) + (not (string= old new)))) + (raw-input prompt))) + +(defun mal-writeline (string) + (when string + (write-line string) + (force-output *standard-output*))) + +(defun repl () + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return))))) + +(defun run-file (file) + (rep (format nil "(load-file \"~a\")" file))) + +(defun main (&optional (argv nil argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") + (string= (uiop:getenv "TERM") "dumb")))) + + (let ((args (if argv-provided-p + argv + (cdr (utils:raw-command-line-arguments))))) + (env:set-env *repl-env* + (types:make-mal-symbol "*ARGV*") + (types:wrap-value (cdr args) :listp t)) + (if (null args) + (repl) + (run-file (car args))))) From c8ac1eda7d4771baa3a8324be6da5bb61d6756f7 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 29 Oct 2016 23:20:05 +0530 Subject: [PATCH 0248/2308] Common Lisp: Implement step 8 --- common-lisp/core.lisp | 33 +++- common-lisp/step8_macros.asd | 30 ++++ common-lisp/step8_macros.lisp | 277 ++++++++++++++++++++++++++++++++++ 3 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 common-lisp/step8_macros.asd create mode 100644 common-lisp/step8_macros.lisp diff --git a/common-lisp/core.lisp b/common-lisp/core.lisp index bd332306f2..335fd6c176 100644 --- a/common-lisp/core.lisp +++ b/common-lisp/core.lisp @@ -13,6 +13,17 @@ types:mal-true types:mal-false)) +(define-condition index-error (types:mal-error) + ((size :initarg :size :reader index-error-size) + (index :initarg :index :reader index-error-index) + (sequence :initarg :sequence :reader index-error-sequence)) + (:report (lambda (condition stream) + (format stream + "Index out of range (~a), length is ~a but index given was ~a" + (printer:pr-str (index-error-sequence condition)) + (index-error-size condition) + (index-error-index condition))))) + (defun mal-add (value1 value2) (types:apply-unwrapped-values '+ value1 value2)) @@ -127,6 +138,23 @@ 'list (mapcar #'types:mal-data-value lists)))) +(defun mal-nth (sequence index) + (or (nth (types:mal-data-value index) + (map 'list #'identity (types:mal-data-value sequence))) + (error 'index-error + :size (length (mal-value sequence)) + :index (mal-value index) + :sequence sequence))) + +(defun mal-first (sequence) + (or (first (map 'list #'identity (types:mal-data-value sequence))) + (types:make-mal-nil nil))) + +(defun mal-rest (sequence) + (types:make-mal-list (rest (map 'list + #'identity + (types:mal-data-value sequence))))) + (defvar ns (list (cons (types:make-mal-symbol "+") (types:make-mal-builtin-fn #'mal-add)) @@ -154,4 +182,7 @@ (cons (types:make-mal-symbol "reset!") (types:make-mal-builtin-fn #'mal-reset!)) (cons (types:make-mal-symbol "swap!") (types:make-mal-builtin-fn #'mal-swap!)) (cons (types:make-mal-symbol "cons") (types:make-mal-builtin-fn #'mal-cons)) - (cons (types:make-mal-symbol "concat") (types:make-mal-builtin-fn #'mal-concat)))) + (cons (types:make-mal-symbol "concat") (types:make-mal-builtin-fn #'mal-concat)) + (cons (types:make-mal-symbol "nth") (types:make-mal-builtin-fn #'mal-nth)) + (cons (types:make-mal-symbol "first") (types:make-mal-builtin-fn #'mal-first)) + (cons (types:make-mal-symbol "rest") (types:make-mal-builtin-fn #'mal-rest)))) diff --git a/common-lisp/step8_macros.asd b/common-lisp/step8_macros.asd new file mode 100644 index 0000000000..cd262a31fa --- /dev/null +++ b/common-lisp/step8_macros.asd @@ -0,0 +1,30 @@ +#-quicklisp +(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" + (user-homedir-pathname)))) + (when (probe-file quicklisp-init) + (load quicklisp-init))) + +(ql:quickload :uiop) +(ql:quickload :cl-readline) +(ql:quickload :cl-ppcre) +(ql:quickload :genhash) + +(defpackage #:mal-asd + (:use :cl :asdf)) + +(in-package :mal-asd) + +(defsystem "step8_macros" + :name "MAL" + :version "1.0" + :author "Iqbal Ansari" + :description "Implementation of step 8 of MAL in Common Lisp" + :serial t + :components ((:file "utils") + (:file "types") + (:file "env") + (:file "reader") + (:file "printer") + (:file "core") + (:file "step8_macros")) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) diff --git a/common-lisp/step8_macros.lisp b/common-lisp/step8_macros.lisp new file mode 100644 index 0000000000..e52076c275 --- /dev/null +++ b/common-lisp/step8_macros.lisp @@ -0,0 +1,277 @@ +(defpackage :mal + (:use :common-lisp + :types + :env + :reader + :printer + :core + :utils) + (:export :main)) + +(in-package :mal) + +(define-condition invalid-function (types:mal-runtime-exception) + ((form :initarg :form :reader form) + (context :initarg :context :reader context)) + (:report (lambda (condition stream) + (format stream + "Invalid function '~a' provided while ~a" + (printer:pr-str (form condition)) + (if (string= (context condition) "apply") + "applying" + "defining macro"))))) + + +(defvar *repl-env* (env:create-mal-env)) + +(dolist (binding core:ns) + (env:set-env *repl-env* + (car binding) + (cdr binding))) + +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-let* (make-mal-symbol "let*")) +(defvar mal-do (make-mal-symbol "do")) +(defvar mal-if (make-mal-symbol "if")) +(defvar mal-fn* (make-mal-symbol "fn*")) +(defvar mal-quote (make-mal-symbol "quote")) +(defvar mal-quasiquote (make-mal-symbol "quasiquote")) +(defvar mal-unquote (make-mal-symbol "unquote")) +(defvar mal-splice-unquote (make-mal-symbol "splice-unquote")) +(defvar mal-cons (make-mal-symbol "cons")) +(defvar mal-concat (make-mal-symbol "concat")) +(defvar mal-defmacro! (make-mal-symbol "defmacro!")) +(defvar mal-macroexpand (make-mal-symbol "macroexpand")) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (types:mal-data-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (types:mal-data-value hash-map)) + (new-hash-table (types:make-mal-value-hash-table))) + (genhash:hashmap (lambda (key value) + (setf (genhash:hashref (mal-eval key env) new-hash-table) + (mal-eval value env))) + hash-map-value) + (types:make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env)) + (types:any ast))) + +(defun is-pair (value) + (and (or (mal-list-p value) + (mal-vector-p value)) + (< 0 (length (types:mal-data-value value))))) + +(defun quasiquote (ast) + (if (not (is-pair ast)) + (types:make-mal-list (list mal-quote ast)) + (let ((forms (map 'list #'identity (types:mal-data-value ast)))) + (cond + ((types:mal-data-value= mal-unquote (first forms)) + (second forms)) + + ((and (is-pair (first forms)) + (types:mal-data-value= mal-splice-unquote + (first (types:mal-data-value (first forms))))) + (types:make-mal-list (list mal-concat + (second (types:mal-data-value (first forms))) + (quasiquote (make-mal-list (cdr forms)))))) + + (t (types:make-mal-list (list mal-cons + (quasiquote (first forms)) + (quasiquote (make-mal-list (cdr forms)))))))))) + +(defun is-macro-call (ast env) + (when (types:mal-list-p ast) + (let* ((func-symbol (first (types:mal-data-value ast))) + (func (when (types:mal-symbol-p func-symbol) + (env:find-env env func-symbol)))) + (and func + (types:mal-fn-p func) + (cdr (assoc 'is-macro (types:mal-data-attrs func))))))) + +(defun mal-macroexpand (ast env) + (loop + while (is-macro-call ast env) + do (let* ((forms (types:mal-data-value ast)) + (func (env:get-env env (first forms)))) + (setf ast (apply (types:mal-data-value func) + (cdr forms))))) + ast) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + (loop + do (setf ast (mal-macroexpand ast env)) + do (cond + ((null ast) (return types:mal-nil)) + ((not (types:mal-list-p ast)) (return (eval-ast ast env))) + ((zerop (length (types:mal-data-value ast))) (return ast)) + (t (let ((forms (types:mal-data-value ast))) + (cond + ((types:mal-data-value= mal-quote (first forms)) + (return (second forms))) + + ((types:mal-data-value= mal-quasiquote (first forms)) + (setf ast (quasiquote (second forms)))) + + ((types:mal-data-value= mal-macroexpand (first forms)) + (return (mal-macroexpand (second forms) env))) + + ((types:mal-data-value= mal-def! (first forms)) + (return (env:set-env env (second forms) (mal-eval (third forms) env)))) + + ((types:mal-data-value= mal-defmacro! (first forms)) + (let ((value (mal-eval (third forms) env))) + (return (if (types:mal-fn-p value) + (env:set-env env + (second forms) + (progn + (setf (cdr (assoc 'is-macro (types:mal-data-attrs value))) t) + value)) + (error 'invalid-function + :form value + :context "macro"))))) + + ((types:mal-data-value= mal-let* (first forms)) + (let ((new-env (env:create-mal-env :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (types:mal-data-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + types:mal-nil) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + (setf ast (third forms) + env new-env))) + + ((types:mal-data-value= mal-do (first forms)) + (mapc (lambda (form) (mal-eval form env)) + (butlast (cdr forms))) + (setf ast (car (last forms)))) + + ((types:mal-data-value= mal-if (first forms)) + (let ((predicate (mal-eval (second forms) env))) + (setf ast (if (or (types:mal-data-value= predicate types:mal-nil) + (types:mal-data-value= predicate types:mal-false)) + (fourth forms) + (third forms))))) + + ((types:mal-data-value= mal-fn* (first forms)) + (return (let ((arglist (second forms)) + (body (third forms))) + (types:make-mal-fn (lambda (&rest args) + (mal-eval body (env:create-mal-env :parent env + :binds (map 'list + #'identity + (types:mal-data-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env) + (cons 'is-macro nil)))))) + + (t (let* ((evaluated-list (eval-ast ast env)) + (function (car evaluated-list))) + ;; If first element is a mal function unwrap it + (cond ((types:mal-fn-p function) + (let* ((attrs (types:mal-data-attrs function))) + (setf ast (cdr (assoc 'ast attrs)) + env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (types:mal-data-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))) + ((types:mal-builtin-fn-p function) + (return (apply (types:mal-data-value function) + (cdr evaluated-list)))) + (t (error 'invalid-function + :form function + :context "apply"))))))))))) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (types:mal-error (condition) + (format nil + "~a" + condition)) + (error (condition) + (format nil + "Internal error: ~a" + condition)))) + +(env:set-env *repl-env* + (types:make-mal-symbol "eval") + (types:make-mal-builtin-fn (lambda (ast) + (mal-eval ast *repl-env*)))) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") + +(defvar *use-readline-p* nil) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (if *use-readline-p* + (cl-readline:readline :prompt prompt + :add-history t + :novelty-check (lambda (old new) + (not (string= old new)))) + (raw-input prompt))) + +(defun mal-writeline (string) + (when string + (write-line string) + (force-output *standard-output*))) + +(defun repl () + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return))))) + +(defun run-file (file) + (rep (format nil "(load-file \"~a\")" file))) + +(defun main (&optional (argv nil argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") + (string= (uiop:getenv "TERM") "dumb")))) + + (let ((args (if argv-provided-p + argv + (cdr (utils:raw-command-line-arguments))))) + (env:set-env *repl-env* + (types:make-mal-symbol "*ARGV*") + (types:wrap-value (cdr args) :listp t)) + (if (null args) + (repl) + (run-file (car args))))) From 27a79f9e5f30ccc5878ba65a5c49dad8a9744262 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 29 Oct 2016 23:56:44 +0530 Subject: [PATCH 0249/2308] Common Lisp: Implement step 9 --- common-lisp/core.lisp | 150 +++++++++++++++++- common-lisp/step9_try.asd | 30 ++++ common-lisp/step9_try.lisp | 305 +++++++++++++++++++++++++++++++++++++ common-lisp/types.lisp | 5 + 4 files changed, 488 insertions(+), 2 deletions(-) create mode 100644 common-lisp/step9_try.asd create mode 100644 common-lisp/step9_try.lisp diff --git a/common-lisp/core.lisp b/common-lisp/core.lisp index 335fd6c176..125e1c2e22 100644 --- a/common-lisp/core.lisp +++ b/common-lisp/core.lisp @@ -3,7 +3,8 @@ :utils :types :reader - :printer) + :printer + :genhash) (:export :ns)) (in-package :core) @@ -155,6 +156,130 @@ #'identity (types:mal-data-value sequence))))) +(defun mal-throw (value) + (error 'types:mal-user-exception + :data value)) + +(defun mal-apply (fn &rest values) + (let ((final-arg (map 'list + #'identity + (types:mal-data-value (car (last values))))) + (butlast-args (butlast values))) + (apply (types:mal-data-value fn) + (append butlast-args final-arg)))) + +(defun mal-map (fn sequence) + (let ((applicants (map 'list + #'identity + (types:mal-data-value sequence)))) + (types:make-mal-list (mapcar (types:mal-data-value fn) + applicants)))) + +(defun mal-nil? (value) + (wrap-boolean (types:mal-nil-p value))) + +(defun mal-true? (value) + (wrap-boolean (and (types:mal-boolean-p value) + (types:mal-data-value value)))) + +(defun mal-false? (value) + (wrap-boolean (and (types:mal-boolean-p value) + (not (types:mal-data-value value))))) + +(defun mal-symbol (string) + (types:make-mal-symbol (types:mal-data-value string))) + +(defun mal-symbol? (value) + (wrap-boolean (types:mal-symbol-p value))) + +(defun mal-keyword (keyword) + (if (types:mal-keyword-p keyword) + keyword + (types:make-mal-keyword (format nil ":~a" (types:mal-data-value keyword))))) + +(defun mal-keyword? (value) + (wrap-boolean (types:mal-keyword-p value))) + +(defun mal-vector (&rest elements) + (types:make-mal-vector (map 'vector #'identity elements))) + +(defun mal-vector? (value) + (wrap-boolean (types:mal-vector-p value))) + +(defun mal-hash-map (&rest elements) + (let ((hash-map (types:make-mal-value-hash-table))) + (loop + for (key value) on elements + by #'cddr + do (setf (genhash:hashref key hash-map) value)) + (types:make-mal-hash-map hash-map))) + +(defun mal-map? (value) + (wrap-boolean (types:mal-hash-map-p value))) + +(defun mal-assoc (hash-map &rest elements) + (let ((hash-map-value (types:mal-data-value hash-map)) + (new-hash-map (types:make-mal-value-hash-table))) + + (genhash:hashmap (lambda (key value) + (declare (ignorable value)) + (setf (genhash:hashref key new-hash-map) + (genhash:hashref key hash-map-value))) + hash-map-value) + + (loop + for (key value) on elements + by #'cddr + do (setf (genhash:hashref key new-hash-map) value)) + + (types:make-mal-hash-map new-hash-map))) + +(defun mal-dissoc (hash-map &rest elements) + (let ((hash-map-value (types:mal-data-value hash-map)) + (new-hash-map (types:make-mal-value-hash-table))) + + (genhash:hashmap (lambda (key value) + (declare (ignorable value)) + (when (not (member key + elements + :test #'types:mal-data-value=)) + (setf (genhash:hashref key new-hash-map) + (genhash:hashref key hash-map-value)))) + hash-map-value) + (types:make-mal-hash-map new-hash-map))) + +(defun mal-get (hash-map key) + (or (and (types:mal-hash-map-p hash-map) + (genhash:hashref key (types:mal-data-value hash-map))) + types:mal-nil)) + +(defun mal-contains? (hash-map key) + (if (genhash:hashref key (types:mal-data-value hash-map)) + types:mal-true + types:mal-false)) + +(defun mal-keys (hash-map) + (let ((hash-map-value (types:mal-data-value hash-map)) + keys) + (genhash:hashmap (lambda (key value) + (declare (ignorable value)) + (push key keys)) + hash-map-value) + (types:make-mal-list (nreverse keys)))) + +(defun mal-vals (hash-map) + (let ((hash-map-value (types:mal-data-value hash-map)) + values) + (genhash:hashmap (lambda (key value) + (declare (ignorable key)) + (push value values)) + hash-map-value) + (types:make-mal-list (nreverse values)))) + +(defun mal-sequential? (value) + (wrap-boolean (or (types:mal-vector-p value) + (types:mal-list-p value)))) + (defvar ns (list (cons (types:make-mal-symbol "+") (types:make-mal-builtin-fn #'mal-add)) @@ -185,4 +310,25 @@ (cons (types:make-mal-symbol "concat") (types:make-mal-builtin-fn #'mal-concat)) (cons (types:make-mal-symbol "nth") (types:make-mal-builtin-fn #'mal-nth)) (cons (types:make-mal-symbol "first") (types:make-mal-builtin-fn #'mal-first)) - (cons (types:make-mal-symbol "rest") (types:make-mal-builtin-fn #'mal-rest)))) + (cons (types:make-mal-symbol "rest") (types:make-mal-builtin-fn #'mal-rest)) + (cons (types:make-mal-symbol "throw") (types:make-mal-builtin-fn #'mal-throw)) + (cons (types:make-mal-symbol "apply") (types:make-mal-builtin-fn #'mal-apply)) + (cons (types:make-mal-symbol "map") (types:make-mal-builtin-fn #'mal-map)) + (cons (types:make-mal-symbol "nil?") (types:make-mal-builtin-fn #'mal-nil?)) + (cons (types:make-mal-symbol "true?") (types:make-mal-builtin-fn #'mal-true?)) + (cons (types:make-mal-symbol "false?") (types:make-mal-builtin-fn #'mal-false?)) + (cons (types:make-mal-symbol "symbol") (types:make-mal-builtin-fn #'mal-symbol)) + (cons (types:make-mal-symbol "symbol?") (types:make-mal-builtin-fn #'mal-symbol?)) + (cons (types:make-mal-symbol "keyword") (types:make-mal-builtin-fn #'mal-keyword)) + (cons (types:make-mal-symbol "keyword?") (types:make-mal-builtin-fn #'mal-keyword?)) + (cons (types:make-mal-symbol "vector") (types:make-mal-builtin-fn #'mal-vector)) + (cons (types:make-mal-symbol "vector?") (types:make-mal-builtin-fn #'mal-vector?)) + (cons (types:make-mal-symbol "hash-map") (types:make-mal-builtin-fn #'mal-hash-map)) + (cons (types:make-mal-symbol "map?") (types:make-mal-builtin-fn #'mal-map?)) + (cons (types:make-mal-symbol "assoc") (types:make-mal-builtin-fn #'mal-assoc)) + (cons (types:make-mal-symbol "dissoc") (types:make-mal-builtin-fn #'mal-dissoc)) + (cons (types:make-mal-symbol "get") (types:make-mal-builtin-fn #'mal-get)) + (cons (types:make-mal-symbol "contains?") (types:make-mal-builtin-fn #'mal-contains?)) + (cons (types:make-mal-symbol "keys") (types:make-mal-builtin-fn #'mal-keys)) + (cons (types:make-mal-symbol "vals") (types:make-mal-builtin-fn #'mal-vals)) + (cons (types:make-mal-symbol "sequential?") (types:make-mal-builtin-fn #'mal-sequential?)))) diff --git a/common-lisp/step9_try.asd b/common-lisp/step9_try.asd new file mode 100644 index 0000000000..a6aa5e9278 --- /dev/null +++ b/common-lisp/step9_try.asd @@ -0,0 +1,30 @@ +#-quicklisp +(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" + (user-homedir-pathname)))) + (when (probe-file quicklisp-init) + (load quicklisp-init))) + +(ql:quickload :uiop) +(ql:quickload :cl-readline) +(ql:quickload :cl-ppcre) +(ql:quickload :genhash) + +(defpackage #:mal-asd + (:use :cl :asdf)) + +(in-package :mal-asd) + +(defsystem "step9_try" + :name "MAL" + :version "1.0" + :author "Iqbal Ansari" + :description "Implementation of step 9 of MAL in Common Lisp" + :serial t + :components ((:file "utils") + (:file "types") + (:file "env") + (:file "reader") + (:file "printer") + (:file "core") + (:file "step9_try")) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) diff --git a/common-lisp/step9_try.lisp b/common-lisp/step9_try.lisp new file mode 100644 index 0000000000..8228127a95 --- /dev/null +++ b/common-lisp/step9_try.lisp @@ -0,0 +1,305 @@ +(defpackage :mal + (:use :common-lisp + :types + :env + :reader + :printer + :core + :utils) + (:export :main)) + +(in-package :mal) + +(define-condition invalid-function (types:mal-runtime-exception) + ((form :initarg :form :reader form) + (context :initarg :context :reader context)) + (:report (lambda (condition stream) + (format stream + "Invalid function '~a' provided while ~a" + (printer:pr-str (form condition)) + (if (string= (context condition) "apply") + "applying" + "defining macro"))))) + + +(defvar *repl-env* (env:create-mal-env)) + +(dolist (binding core:ns) + (env:set-env *repl-env* + (car binding) + (cdr binding))) + +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-let* (make-mal-symbol "let*")) +(defvar mal-do (make-mal-symbol "do")) +(defvar mal-if (make-mal-symbol "if")) +(defvar mal-fn* (make-mal-symbol "fn*")) +(defvar mal-quote (make-mal-symbol "quote")) +(defvar mal-quasiquote (make-mal-symbol "quasiquote")) +(defvar mal-unquote (make-mal-symbol "unquote")) +(defvar mal-splice-unquote (make-mal-symbol "splice-unquote")) +(defvar mal-cons (make-mal-symbol "cons")) +(defvar mal-concat (make-mal-symbol "concat")) +(defvar mal-defmacro! (make-mal-symbol "defmacro!")) +(defvar mal-macroexpand (make-mal-symbol "macroexpand")) +(defvar mal-try* (make-mal-symbol "try*")) +(defvar mal-catch* (make-mal-symbol "catch*")) +(defvar mal-throw (make-mal-symbol "throw")) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (types:mal-data-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (types:mal-data-value hash-map)) + (new-hash-table (types:make-mal-value-hash-table))) + (genhash:hashmap (lambda (key value) + (setf (genhash:hashref (mal-eval key env) new-hash-table) + (mal-eval value env))) + hash-map-value) + (types:make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env)) + (types:any ast))) + +(defun is-pair (value) + (and (or (mal-list-p value) + (mal-vector-p value)) + (< 0 (length (types:mal-data-value value))))) + +(defun quasiquote (ast) + (if (not (is-pair ast)) + (types:make-mal-list (list mal-quote ast)) + (let ((forms (map 'list #'identity (types:mal-data-value ast)))) + (cond + ((types:mal-data-value= mal-unquote (first forms)) + (second forms)) + + ((and (is-pair (first forms)) + (types:mal-data-value= mal-splice-unquote + (first (types:mal-data-value (first forms))))) + (types:make-mal-list (list mal-concat + (second (types:mal-data-value (first forms))) + (quasiquote (make-mal-list (cdr forms)))))) + + (t (types:make-mal-list (list mal-cons + (quasiquote (first forms)) + (quasiquote (make-mal-list (cdr forms)))))))))) + +(defun is-macro-call (ast env) + (when (types:mal-list-p ast) + (let* ((func-symbol (first (types:mal-data-value ast))) + (func (when (types:mal-symbol-p func-symbol) + (env:find-env env func-symbol)))) + (and func + (types:mal-fn-p func) + (cdr (assoc 'is-macro (types:mal-data-attrs func))))))) + +(defun mal-macroexpand (ast env) + (loop + while (is-macro-call ast env) + do (let* ((forms (types:mal-data-value ast)) + (func (env:get-env env (first forms)))) + (setf ast (apply (types:mal-data-value func) + (cdr forms))))) + ast) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + (loop + do (setf ast (mal-macroexpand ast env)) + do (cond + ((null ast) (return types:mal-nil)) + ((not (types:mal-list-p ast)) (return (eval-ast ast env))) + ((zerop (length (types:mal-data-value ast))) (return ast)) + (t (let ((forms (types:mal-data-value ast))) + (cond + ((types:mal-data-value= mal-quote (first forms)) + (return (second forms))) + + ((types:mal-data-value= mal-quasiquote (first forms)) + (setf ast (quasiquote (second forms)))) + + ((types:mal-data-value= mal-macroexpand (first forms)) + (return (mal-macroexpand (second forms) env))) + + ((types:mal-data-value= mal-def! (first forms)) + (return (env:set-env env (second forms) (mal-eval (third forms) env)))) + + ((types:mal-data-value= mal-defmacro! (first forms)) + (let ((value (mal-eval (third forms) env))) + (return (if (types:mal-fn-p value) + (env:set-env env + (second forms) + (progn + (setf (cdr (assoc 'is-macro (types:mal-data-attrs value))) t) + value)) + (error 'invalid-function + :form value + :context "macro"))))) + + ((types:mal-data-value= mal-let* (first forms)) + (let ((new-env (env:create-mal-env :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (types:mal-data-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + types:mal-nil) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + (setf ast (third forms) + env new-env))) + + ((types:mal-data-value= mal-do (first forms)) + (mapc (lambda (form) (mal-eval form env)) + (butlast (cdr forms))) + (setf ast (car (last forms)))) + + ((types:mal-data-value= mal-if (first forms)) + (let ((predicate (mal-eval (second forms) env))) + (setf ast (if (or (types:mal-data-value= predicate types:mal-nil) + (types:mal-data-value= predicate types:mal-false)) + (fourth forms) + (third forms))))) + + ((types:mal-data-value= mal-fn* (first forms)) + (return (let ((arglist (second forms)) + (body (third forms))) + (types:make-mal-fn (lambda (&rest args) + (mal-eval body (env:create-mal-env :parent env + :binds (map 'list + #'identity + (types:mal-data-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env) + (cons 'is-macro nil)))))) + + ((types:mal-data-value= mal-try* (first forms)) + (handler-case + (return (mal-eval (second forms) env)) + ((or types:mal-exception types:mal-error) (condition) + (when (third forms) + (let ((catch-forms (types:mal-data-value (third forms)))) + (when (types:mal-data-value= mal-catch* + (first catch-forms)) + (return (mal-eval (third catch-forms) + (env:create-mal-env :parent env + :binds (list (second catch-forms)) + :exprs (list (if (or (typep condition 'types:mal-runtime-exception) + (typep condition 'types:mal-error)) + (types:make-mal-string (format nil "~a" condition)) + (types::mal-exception-data condition))))))))) + (error condition)))) + + (t (let* ((evaluated-list (eval-ast ast env)) + (function (car evaluated-list))) + ;; If first element is a mal function unwrap it + (cond ((types:mal-fn-p function) + (let* ((attrs (types:mal-data-attrs function))) + (setf ast (cdr (assoc 'ast attrs)) + env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (types:mal-data-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))) + ((types:mal-builtin-fn-p function) + (return (apply (types:mal-data-value function) + (cdr evaluated-list)))) + (t (error 'invalid-function + :form function + :context "apply"))))))))))) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (types:mal-error (condition) + (format nil + "Error: ~a" + condition)) + (types:mal-runtime-exception (condition) + (format nil + "Exception: ~a" + condition)) + (types:mal-user-exception (condition) + (format nil + "Exception: ~a" + (pr-str (types::mal-exception-data condition)))) + (error (condition) + (format nil + "Internal error: ~a" + condition)))) + +(env:set-env *repl-env* + (types:make-mal-symbol "eval") + (types:make-mal-builtin-fn (lambda (ast) + (mal-eval ast *repl-env*)))) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") + +(defvar *use-readline-p* nil) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (if *use-readline-p* + (cl-readline:readline :prompt prompt + :add-history t + :novelty-check (lambda (old new) + (not (string= old new)))) + (raw-input prompt))) + +(defun mal-writeline (string) + (when string + (write-line string) + (force-output *standard-output*))) + +(defun repl () + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return))))) + +(defun run-file (file) + (rep (format nil "(load-file \"~a\")" file))) + +(defun main (&optional (argv nil argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") + (string= (uiop:getenv "TERM") "dumb")))) + + (let ((args (if argv-provided-p + argv + (cdr (utils:raw-command-line-arguments))))) + (env:set-env *repl-env* + (types:make-mal-symbol "*ARGV*") + (types:wrap-value (cdr args) :listp t)) + (if (null args) + (repl) + (run-file (car args))))) diff --git a/common-lisp/types.lisp b/common-lisp/types.lisp index 25f959ccca..5a5e6f00d9 100644 --- a/common-lisp/types.lisp +++ b/common-lisp/types.lisp @@ -70,6 +70,8 @@ :mal-exception ;; Exceptions raised by the runtime :mal-runtime-exception + ;; Exception raised by user code + :mal-user-exception ;; Error :mal-error @@ -90,6 +92,9 @@ (define-condition mal-runtime-exception (mal-exception) nil) +(define-condition mal-user-exception (mal-exception) + ((data :accessor mal-exception-data :initarg :data))) + (defstruct mal-data (value nil) (type nil :read-only t) From 4434be1d0a740975ccc498540ead2dd028e19163 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 30 Oct 2016 00:07:40 +0530 Subject: [PATCH 0250/2308] Common Lisp: Implement step A --- common-lisp/core.lisp | 74 +++++++- common-lisp/stepA_mal.asd | 30 +++ common-lisp/stepA_mal.lisp | 316 ++++++++++++++++++++++++++++++++ common-lisp/tests/stepA_mal.mal | 61 ++++++ 4 files changed, 480 insertions(+), 1 deletion(-) create mode 100644 common-lisp/stepA_mal.asd create mode 100644 common-lisp/stepA_mal.lisp create mode 100644 common-lisp/tests/stepA_mal.mal diff --git a/common-lisp/core.lisp b/common-lisp/core.lisp index 125e1c2e22..cba2c80894 100644 --- a/common-lisp/core.lisp +++ b/common-lisp/core.lisp @@ -280,6 +280,70 @@ (wrap-boolean (or (types:mal-vector-p value) (types:mal-list-p value)))) +(defun mal-readline (prompt) + (format *standard-output* (types:mal-data-value prompt)) + (force-output *standard-output*) + (types:wrap-value (read-line *standard-input* nil))) + +(defun mal-string? (value) + (wrap-boolean (types:mal-string-p value))) + +(defun mal-time-ms () + (types:make-mal-number (round (/ (get-internal-real-time) + (/ internal-time-units-per-second + 1000))))) + +(defun mal-conj (value &rest elements) + (cond ((types:mal-list-p value) + (types:make-mal-list (append (nreverse elements) + (types:mal-data-value value)))) + ((types:mal-vector-p value) + (types:make-mal-vector (concatenate 'vector + (types:mal-data-value value) + elements))) + (t (error 'types:mal-user-exception)))) + +(defun mal-seq (value) + (if (zerop (length (types:mal-data-value value))) + types:mal-nil + (cond ((types:mal-list-p value) + value) + ((types:mal-vector-p value) + (types:make-mal-list (map 'list + #'identity + (types:mal-data-value value)))) + ((types:mal-string-p value) + (types:make-mal-list (map 'list + (lambda (char) + (types:make-mal-string (make-string 1 :initial-element char))) + (types:mal-data-value value)))) + (t (error 'types:mal-user-exception))))) + +(defun mal-with-meta (value meta) + (funcall (switch-mal-type value + (types:string #'types:make-mal-string) + (types:symbol #'types:make-mal-symbol) + (types:list #'types:make-mal-list) + (types:vector #'types:make-mal-vector) + (types:hash-map #'types:make-mal-hash-map) + (types:fn #'types:make-mal-fn) + (types:builtin-fn #'types:make-mal-builtin-fn)) + (types:mal-data-value value) + :meta meta + :attrs (types:mal-data-attrs value))) + +(defun mal-meta (value) + (or (types:mal-data-meta value) + types:mal-nil)) + +;; Since a nil in Common LISP may mean an empty list or boolean false or +;; simply nil, the caller can specify the preferred type while evaluating an +;; expression +(defun mal-cl-eval (code &optional booleanp listp) + (types:wrap-value (eval (read-from-string (types:mal-data-value code))) + :booleanp (and booleanp (types:mal-data-value booleanp)) + :listp (and listp (types:mal-data-value listp)))) + (defvar ns (list (cons (types:make-mal-symbol "+") (types:make-mal-builtin-fn #'mal-add)) @@ -331,4 +395,12 @@ (cons (types:make-mal-symbol "contains?") (types:make-mal-builtin-fn #'mal-contains?)) (cons (types:make-mal-symbol "keys") (types:make-mal-builtin-fn #'mal-keys)) (cons (types:make-mal-symbol "vals") (types:make-mal-builtin-fn #'mal-vals)) - (cons (types:make-mal-symbol "sequential?") (types:make-mal-builtin-fn #'mal-sequential?)))) + (cons (types:make-mal-symbol "sequential?") (types:make-mal-builtin-fn #'mal-sequential?)) + (cons (types:make-mal-symbol "readline") (types:make-mal-builtin-fn #'mal-readline)) + (cons (types:make-mal-symbol "string?") (types:make-mal-builtin-fn #'mal-string?)) + (cons (types:make-mal-symbol "time-ms") (types:make-mal-builtin-fn #'mal-time-ms)) + (cons (types:make-mal-symbol "conj") (types:make-mal-builtin-fn #'mal-conj)) + (cons (types:make-mal-symbol "seq") (types:make-mal-builtin-fn #'mal-seq)) + (cons (types:make-mal-symbol "with-meta") (types:make-mal-builtin-fn #'mal-with-meta)) + (cons (types:make-mal-symbol "meta") (types:make-mal-builtin-fn #'mal-meta)) + (cons (types:make-mal-symbol "cl-eval") (types:make-mal-builtin-fn #'mal-cl-eval)))) diff --git a/common-lisp/stepA_mal.asd b/common-lisp/stepA_mal.asd new file mode 100644 index 0000000000..cc143ec102 --- /dev/null +++ b/common-lisp/stepA_mal.asd @@ -0,0 +1,30 @@ +#-quicklisp +(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" + (user-homedir-pathname)))) + (when (probe-file quicklisp-init) + (load quicklisp-init))) + +(ql:quickload :uiop) +(ql:quickload :cl-readline) +(ql:quickload :cl-ppcre) +(ql:quickload :genhash) + +(defpackage #:mal-asd + (:use :cl :asdf)) + +(in-package :mal-asd) + +(defsystem "stepA_mal" + :name "MAL" + :version "1.0" + :author "Iqbal Ansari" + :description "Implementation of MAL in Common Lisp" + :serial t + :components ((:file "utils") + (:file "types") + (:file "env") + (:file "reader") + (:file "printer") + (:file "core") + (:file "stepA_mal")) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) diff --git a/common-lisp/stepA_mal.lisp b/common-lisp/stepA_mal.lisp new file mode 100644 index 0000000000..2709a82fe2 --- /dev/null +++ b/common-lisp/stepA_mal.lisp @@ -0,0 +1,316 @@ +(defpackage :mal + (:use :common-lisp + :types + :env + :reader + :printer + :core + :utils) + (:export :main)) + +(in-package :mal) + +(define-condition invalid-function (types:mal-runtime-exception) + ((form :initarg :form :reader form) + (context :initarg :context :reader context)) + (:report (lambda (condition stream) + (format stream + "Invalid function '~a' provided while ~a" + (printer:pr-str (form condition)) + (if (string= (context condition) "apply") + "applying" + "defining macro"))))) + + +(defvar *repl-env* (env:create-mal-env)) + +(dolist (binding core:ns) + (env:set-env *repl-env* + (car binding) + (cdr binding))) + +(defvar mal-def! (make-mal-symbol "def!")) +(defvar mal-let* (make-mal-symbol "let*")) +(defvar mal-do (make-mal-symbol "do")) +(defvar mal-if (make-mal-symbol "if")) +(defvar mal-fn* (make-mal-symbol "fn*")) +(defvar mal-quote (make-mal-symbol "quote")) +(defvar mal-quasiquote (make-mal-symbol "quasiquote")) +(defvar mal-unquote (make-mal-symbol "unquote")) +(defvar mal-splice-unquote (make-mal-symbol "splice-unquote")) +(defvar mal-cons (make-mal-symbol "cons")) +(defvar mal-concat (make-mal-symbol "concat")) +(defvar mal-defmacro! (make-mal-symbol "defmacro!")) +(defvar mal-macroexpand (make-mal-symbol "macroexpand")) +(defvar mal-try* (make-mal-symbol "try*")) +(defvar mal-catch* (make-mal-symbol "catch*")) +(defvar mal-throw (make-mal-symbol "throw")) + +(defun eval-sequence (sequence env) + (map 'list + (lambda (ast) (mal-eval ast env)) + (types:mal-data-value sequence))) + +(defun eval-hash-map (hash-map env) + (let ((hash-map-value (types:mal-data-value hash-map)) + (new-hash-table (types:make-mal-value-hash-table))) + (genhash:hashmap (lambda (key value) + (setf (genhash:hashref (mal-eval key env) new-hash-table) + (mal-eval value env))) + hash-map-value) + (types:make-mal-hash-map new-hash-table))) + +(defun eval-ast (ast env) + (switch-mal-type ast + (types:symbol (env:get-env env ast)) + (types:list (eval-sequence ast env)) + (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) + (types:hash-map (eval-hash-map ast env)) + (types:any ast))) + +(defun is-pair (value) + (and (or (mal-list-p value) + (mal-vector-p value)) + (< 0 (length (types:mal-data-value value))))) + +(defun quasiquote (ast) + (if (not (is-pair ast)) + (types:make-mal-list (list mal-quote ast)) + (let ((forms (map 'list #'identity (types:mal-data-value ast)))) + (cond + ((types:mal-data-value= mal-unquote (first forms)) + (second forms)) + + ((and (is-pair (first forms)) + (types:mal-data-value= mal-splice-unquote + (first (types:mal-data-value (first forms))))) + (types:make-mal-list (list mal-concat + (second (types:mal-data-value (first forms))) + (quasiquote (make-mal-list (cdr forms)))))) + + (t (types:make-mal-list (list mal-cons + (quasiquote (first forms)) + (quasiquote (make-mal-list (cdr forms)))))))))) + +(defun is-macro-call (ast env) + (when (types:mal-list-p ast) + (let* ((func-symbol (first (types:mal-data-value ast))) + (func (when (types:mal-symbol-p func-symbol) + (env:find-env env func-symbol)))) + (and func + (types:mal-fn-p func) + (cdr (assoc 'is-macro (types:mal-data-attrs func))))))) + +(defun mal-macroexpand (ast env) + (loop + while (is-macro-call ast env) + do (let* ((forms (types:mal-data-value ast)) + (func (env:get-env env (first forms)))) + (setf ast (apply (types:mal-data-value func) + (cdr forms))))) + ast) + +(defun mal-read (string) + (reader:read-str string)) + +(defun mal-eval (ast env) + (loop + do (setf ast (mal-macroexpand ast env)) + do (cond + ((null ast) (return types:mal-nil)) + ((not (types:mal-list-p ast)) (return (eval-ast ast env))) + ((zerop (length (types:mal-data-value ast))) (return ast)) + (t (let ((forms (types:mal-data-value ast))) + (cond + ((types:mal-data-value= mal-quote (first forms)) + (return (second forms))) + + ((types:mal-data-value= mal-quasiquote (first forms)) + (setf ast (quasiquote (second forms)))) + + ((types:mal-data-value= mal-macroexpand (first forms)) + (return (mal-macroexpand (second forms) env))) + + ((types:mal-data-value= mal-def! (first forms)) + (return (env:set-env env (second forms) (mal-eval (third forms) env)))) + + ((types:mal-data-value= mal-defmacro! (first forms)) + (let ((value (mal-eval (third forms) env))) + (return (if (types:mal-fn-p value) + (env:set-env env + (second forms) + (progn + (setf (cdr (assoc 'is-macro (types:mal-data-attrs value))) t) + value)) + (error 'invalid-function + :form value + :context "macro"))))) + + ((types:mal-data-value= mal-let* (first forms)) + (let ((new-env (env:create-mal-env :parent env)) + ;; Convert a potential vector to a list + (bindings (map 'list + #'identity + (types:mal-data-value (second forms))))) + + (mapcar (lambda (binding) + (env:set-env new-env + (car binding) + (mal-eval (or (cdr binding) + types:mal-nil) + new-env))) + (loop + for (symbol value) on bindings + by #'cddr + collect (cons symbol value))) + (setf ast (third forms) + env new-env))) + + ((types:mal-data-value= mal-do (first forms)) + (mapc (lambda (form) (mal-eval form env)) + (butlast (cdr forms))) + (setf ast (car (last forms)))) + + ((types:mal-data-value= mal-if (first forms)) + (let ((predicate (mal-eval (second forms) env))) + (setf ast (if (or (types:mal-data-value= predicate types:mal-nil) + (types:mal-data-value= predicate types:mal-false)) + (fourth forms) + (third forms))))) + + ((types:mal-data-value= mal-fn* (first forms)) + (return (let ((arglist (second forms)) + (body (third forms))) + (types:make-mal-fn (lambda (&rest args) + (mal-eval body (env:create-mal-env :parent env + :binds (map 'list + #'identity + (types:mal-data-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env) + (cons 'is-macro nil)))))) + + ((types:mal-data-value= mal-try* (first forms)) + (handler-case + (return (mal-eval (second forms) env)) + ((or types:mal-exception types:mal-error) (condition) + (when (third forms) + (let ((catch-forms (types:mal-data-value (third forms)))) + (when (types:mal-data-value= mal-catch* + (first catch-forms)) + (return (mal-eval (third catch-forms) + (env:create-mal-env :parent env + :binds (list (second catch-forms)) + :exprs (list (if (or (typep condition 'types:mal-runtime-exception) + (typep condition 'types:mal-error)) + (types:make-mal-string (format nil "~a" condition)) + (types::mal-exception-data condition))))))))) + (error condition)))) + + (t (let* ((evaluated-list (eval-ast ast env)) + (function (car evaluated-list))) + ;; If first element is a mal function unwrap it + (cond ((types:mal-fn-p function) + (let* ((attrs (types:mal-data-attrs function))) + (setf ast (cdr (assoc 'ast attrs)) + env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + :binds (map 'list + #'identity + (types:mal-data-value (cdr (assoc 'params attrs)))) + :exprs (cdr evaluated-list))))) + ((types:mal-builtin-fn-p function) + (return (apply (types:mal-data-value function) + (cdr evaluated-list)))) + (t (error 'invalid-function + :form function + :context "apply"))))))))))) + +(defun mal-print (expression) + (printer:pr-str expression)) + +(defun rep (string) + (handler-case + (mal-print (mal-eval (mal-read string) + *repl-env*)) + (types:mal-error (condition) + (format nil + "Error: ~a" + condition)) + (types:mal-runtime-exception (condition) + (format nil + "Exception: ~a" + condition)) + (types:mal-user-exception (condition) + (format nil + "Exception: ~a" + (pr-str (types::mal-exception-data condition)))) + (error (condition) + (format nil + "Internal error: ~a" + condition)))) + +(env:set-env *repl-env* + (types:make-mal-symbol "eval") + (types:make-mal-builtin-fn (lambda (ast) + (mal-eval ast *repl-env*)))) + +(env:set-env *repl-env* + (types:make-mal-symbol "*cl-implementation*") + (types:wrap-value (lisp-implementation-type))) + +(env:set-env *repl-env* + (types:make-mal-symbol "*cl-version*") + (types:wrap-value (lisp-implementation-version))) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +(rep "(def! *host-language* \"common-lisp\")") +(rep "(def! *gensym-counter* (atom 0))") +(rep "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))") +(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))") + +(defvar *use-readline-p* nil) + +(defun raw-input (prompt) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun mal-readline (prompt) + (if *use-readline-p* + (cl-readline:readline :prompt prompt + :add-history t + :novelty-check (lambda (old new) + (not (string= old new)))) + (raw-input prompt))) + +(defun mal-writeline (string) + (when string + (write-line string) + (force-output *standard-output*))) + +(defun repl () + (rep "(println (str \"Mal [\" *host-language* \"]\"))") + (loop do (let ((line (mal-readline "user> "))) + (if line + (mal-writeline (rep line)) + (return))))) + +(defun run-file (file) + (rep (format nil "(load-file \"~a\")" file))) + +(defun main (&optional (argv nil argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") + (string= (uiop:getenv "TERM") "dumb")))) + (let ((args (if argv-provided-p + argv + (cdr (utils:raw-command-line-arguments))))) + (env:set-env *repl-env* + (types:make-mal-symbol "*ARGV*") + (types:wrap-value (cdr args) :listp t)) + (if (null args) + (repl) + (run-file (car args))))) diff --git a/common-lisp/tests/stepA_mal.mal b/common-lisp/tests/stepA_mal.mal new file mode 100644 index 0000000000..69fe1351a9 --- /dev/null +++ b/common-lisp/tests/stepA_mal.mal @@ -0,0 +1,61 @@ +;; Testing clisp interop + +(cl-eval "42") +;=>42 + +(cl-eval "(+ 1 1)") +;=>2 + +(cl-eval "(setq foo 1 bar 2 baz 3)") + +(cl-eval "(list foo bar baz)") +;=>(1 2 3) + +(cl-eval "7") +;=>7 + +;; +;; Testing boolean flag +(cl-eval "(= 123 123)" true) +;=>true + +(cl-eval "(= 123 456)") +;=>nil + +(cl-eval "(= 123 456)" true) +;=>false + +;; +;; Testing list flag +(cl-eval "(last nil)" false true) +;=>() + +(cl-eval "nil" false true) +;=>() + +(cl-eval "nil") +;=>nil + +;; +;; Testing creation of Common Lisp Objects +(cl-eval "#(1 2)") +;=>[1 2] + +;;; Not testing with elements since order in hashtable cannot be guaranteed +(cl-eval "(make-hash-table)") +;=>{} + +(cl-eval "(defun redundant-identity (x) x)")) +;=>REDUNDANT-IDENTITY + +(cl-eval "(redundant-identity 2)")) +;=>2 + +(cl-eval "(defun range (max &key (min 0) (step 1)) (loop for n from min below max by step collect n))") +;=>RANGE + +(cl-eval "(range 10 :min 0 :step 1)") +;=>(0 1 2 3 4 5 6 7 8 9) + +(cl-eval "(mapcar #'1+ (range 10 :min 0 :step 1))") +;=>(1 2 3 4 5 6 7 8 9 10) From 3cf68bd8ff2d32d1ed7c161e5f926a7127dee684 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 2 Nov 2016 15:40:01 +0530 Subject: [PATCH 0251/2308] Common Lisp: Add tasks for stats and stats-lisp for Common Lisp implementation --- common-lisp/Makefile | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/common-lisp/Makefile b/common-lisp/Makefile index b585d82210..1ff729febb 100644 --- a/common-lisp/Makefile +++ b/common-lisp/Makefile @@ -1,4 +1,23 @@ ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) +SOURCES_LISP = env.lisp core.lisp stepA_mal.lisp +SOURCES = utils.lisp types.lisp reader.lisp printer.lisp $(SOURCES_LISP) + +all : stepA_mal + +.PHONY: stats + step% : step%.lisp utils.lisp types.lisp env.lisp printer.lisp reader.lisp core.lisp cl-launch -v -l sbcl +Q -S $(ROOT_DIR) -s $@ -d $@.image -o $@ -E 'mal:main' + +clean: + find . -name 'step*' -executable -exec git check-ignore \{\} \; -delete + rm -f *.lib *.fas[l] + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" + +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" From 542e99704899f6d7a5ec57105b33396f2ea72805 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 30 Oct 2016 00:16:46 +0530 Subject: [PATCH 0252/2308] Common Lisp: Add Dockerfile for Common Lisp implementation --- common-lisp/Dockerfile | 51 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 common-lisp/Dockerfile diff --git a/common-lisp/Dockerfile b/common-lisp/Dockerfile new file mode 100644 index 0000000000..447f413bbb --- /dev/null +++ b/common-lisp/Dockerfile @@ -0,0 +1,51 @@ +FROM ubuntu:vivid +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# Install git, make +RUN apt-get -y install git make + +# Install sbcl +RUN apt-get -y install sbcl + +# Install cl-asdf (CLISP does not seem to come with it) +RUN apt-get -y install cl-launch cl-asdf + +RUN cd /tmp && \ + git clone https://gitlab.common-lisp.net/xcvb/cl-launch.git && \ + cd cl-launch && \ + make install + +# Install wget needed to install quicklisp +RUN apt-get -y install wget + +# Install quicklisp +RUN HOME=/ && \ + cd /tmp && \ + wget https://beta.quicklisp.org/quicklisp.lisp && \ + sbcl --load quicklisp.lisp --quit --eval '(quicklisp-quickstart:install)' --eval '(ql-util:without-prompting (ql:add-to-init-file))' + +RUN chmod -R a+rwx /quicklisp +RUN chmod a+rwx /.sbclrc + +RUN mkdir -p /.cache +RUN chmod -R a+rwx /.cache From b5e99959ddf41aede0095a3d65e180413cae1a61 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 1 Nov 2016 12:52:47 +0530 Subject: [PATCH 0253/2308] Common Lisp: Enable travis for Common Lisp --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 97ee8216bb..94590b220d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ matrix: - {env: IMPL=chuck, services: [docker]} - {env: IMPL=clisp, services: [docker]} - {env: IMPL=clojure, services: [docker]} + - {env: IMPL=common-lisp, services: [docker]} - {env: IMPL=crystal, services: [docker]} - {env: IMPL=d, services: [docker]} - {env: IMPL=dart, services: [docker]} From 79011c53930800b5ae35d62af46af3cae1ee7ec2 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 2 Nov 2016 09:19:24 +0530 Subject: [PATCH 0254/2308] Common Lisp: Ignore fasl and lib files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5d30679a21..a640383263 100644 --- a/.gitignore +++ b/.gitignore @@ -120,3 +120,5 @@ basic/step9_try.bas basic/stepA_mal.bas basic/*.prg common-lisp/*.image +common-lisp/*.fasl +common-lisp/*.lib From 035d28ed439cfaffeb6344684058d055fc8953b7 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 15 Nov 2016 19:21:00 +0530 Subject: [PATCH 0255/2308] Common Lisp: Allow user to select the implementation to use for building MAL --- common-lisp/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common-lisp/Makefile b/common-lisp/Makefile index 1ff729febb..7701f17e08 100644 --- a/common-lisp/Makefile +++ b/common-lisp/Makefile @@ -1,14 +1,14 @@ -ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) +ROOT_DIR = $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) SOURCES_LISP = env.lisp core.lisp stepA_mal.lisp SOURCES = utils.lisp types.lisp reader.lisp printer.lisp $(SOURCES_LISP) +LISP ?= sbcl all : stepA_mal .PHONY: stats - step% : step%.lisp utils.lisp types.lisp env.lisp printer.lisp reader.lisp core.lisp - cl-launch -v -l sbcl +Q -S $(ROOT_DIR) -s $@ -d $@.image -o $@ -E 'mal:main' + cl-launch -v -l $(LISP) +Q -S $(ROOT_DIR) -s $@ -d $@.image -o $@ -E 'mal:main' -e '(load "~/quicklisp/setup.lisp")' clean: find . -name 'step*' -executable -exec git check-ignore \{\} \; -delete From e7e61f3274c48935ef6d12637c1af5472a6ad6be Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 16 Nov 2016 00:24:49 +0530 Subject: [PATCH 0256/2308] Common Lisp: Keep track of the implementation used to build a step Rebuild the step if the implementation changes --- .gitignore | 3 ++- common-lisp/Makefile | 35 ++++++++++++++++++++++++++++------- common-lisp/hist/.keepdir | 0 3 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 common-lisp/hist/.keepdir diff --git a/.gitignore b/.gitignore index a640383263..9620846078 100644 --- a/.gitignore +++ b/.gitignore @@ -119,6 +119,7 @@ basic/step8_macros.bas basic/step9_try.bas basic/stepA_mal.bas basic/*.prg -common-lisp/*.image common-lisp/*.fasl common-lisp/*.lib +common-lisp/images/* +common-lisp/hist/* diff --git a/common-lisp/Makefile b/common-lisp/Makefile index 7701f17e08..c3cd49e55b 100644 --- a/common-lisp/Makefile +++ b/common-lisp/Makefile @@ -1,18 +1,39 @@ -ROOT_DIR = $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) -SOURCES_LISP = env.lisp core.lisp stepA_mal.lisp -SOURCES = utils.lisp types.lisp reader.lisp printer.lisp $(SOURCES_LISP) +# Helper functions +define record_lisp + $(shell (test -f "hist/$(1)_impl" && grep -q $(2) "hist/$(1)_impl") || echo $(2) > "hist/$(1)_impl") +endef + +define steps + $(if $(MAKECMDGOALS),\ + $(if $(findstring all,$(MAKECMDGOALS)),\ + stepA_mal,\ + $(filter step%, $(MAKECMDGOALS))),\ + stepA_mal) +endef + +ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) +SOURCES_LISP := env.lisp core.lisp stepA_mal.lisp +SOURCES := utils.lisp types.lisp reader.lisp printer.lisp $(SOURCES_LISP) LISP ?= sbcl -all : stepA_mal +# Record the Common Lisp implementation used for all steps built in this +# invocation This is used in the targets to rebuild the step if the +# implementation changes +$(foreach step, $(call steps), $(call record_lisp,$(patsubst step%,%,$(step)),$(LISP))) +.PRECIOUS: hist/%_impl .PHONY: stats -step% : step%.lisp utils.lisp types.lisp env.lisp printer.lisp reader.lisp core.lisp - cl-launch -v -l $(LISP) +Q -S $(ROOT_DIR) -s $@ -d $@.image -o $@ -E 'mal:main' -e '(load "~/quicklisp/setup.lisp")' +all : stepA_mal + +hist/%_impl: ; + +step% : step%.lisp utils.lisp types.lisp env.lisp printer.lisp reader.lisp core.lisp hist/%_impl + cl-launch --verbose --lisp $(LISP) --source-registry $(ROOT_DIR) --system $@ --dump images/$@.$(LISP).image -o $@ --entry 'mal:main' clean: find . -name 'step*' -executable -exec git check-ignore \{\} \; -delete - rm -f *.lib *.fas[l] + rm -f *.lib *.fas[l] images/* hist/*_impl stats: $(SOURCES) @wc $^ diff --git a/common-lisp/hist/.keepdir b/common-lisp/hist/.keepdir new file mode 100644 index 0000000000..e69de29bb2 From 89676a9fba4d113f83bfb3f93c73c3fe863f7aca Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Thu, 17 Nov 2016 17:14:33 +0530 Subject: [PATCH 0257/2308] Common Lisp: Initialize standard streams in GNU CLISP on startup Without the initialization CLISP seems to be interacting wierdly with PERL_RL flag --- common-lisp/step0_repl.lisp | 13 +++++++++++++ common-lisp/step1_read_print.lisp | 13 +++++++++++++ common-lisp/step2_eval.lisp | 13 +++++++++++++ common-lisp/step3_env.lisp | 13 +++++++++++++ common-lisp/step4_if_fn_do.lisp | 13 +++++++++++++ common-lisp/step5_tco.lisp | 13 +++++++++++++ common-lisp/step6_file.lisp | 12 ++++++++++++ common-lisp/step7_quote.lisp | 12 ++++++++++++ common-lisp/step8_macros.lisp | 12 ++++++++++++ common-lisp/step9_try.lisp | 12 ++++++++++++ common-lisp/stepA_mal.lisp | 13 +++++++++++++ 11 files changed, 139 insertions(+) diff --git a/common-lisp/step0_repl.lisp b/common-lisp/step0_repl.lisp index e891ee6016..dc5814af7e 100644 --- a/common-lisp/step0_repl.lisp +++ b/common-lisp/step0_repl.lisp @@ -39,7 +39,20 @@ (defun main (&optional (argv nil argv-provided-p)) (declare (ignorable argv argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") (string= (uiop:getenv "TERM") "dumb")))) + + ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort + ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment + ;; variable which the test runner sets causing `read-line' on *standard-input* + ;; to fail with an empty stream error. The following reinitializes the + ;; standard streams + ;; + ;; See http://www.gnu.org/software/clisp/impnotes/streams-interactive.html + #+clisp (setf *standard-input* (ext:make-stream :input) + *standard-output* (ext:make-stream :output :buffered t) + *error-output* (ext:make-stream :error :buffered t)) + (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) diff --git a/common-lisp/step1_read_print.lisp b/common-lisp/step1_read_print.lisp index b6b3e195fd..e72aa294aa 100644 --- a/common-lisp/step1_read_print.lisp +++ b/common-lisp/step1_read_print.lisp @@ -47,7 +47,20 @@ (defun main (&optional (argv nil argv-provided-p)) (declare (ignorable argv argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") (string= (uiop:getenv "TERM") "dumb")))) + + ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort + ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment + ;; variable which the test runner sets causing `read-line' on *standard-input* + ;; to fail with an empty stream error. The following reinitializes the + ;; standard streams + ;; + ;; See http://www.gnu.org/software/clisp/impnotes/streams-interactive.html + #+clisp (setf *standard-input* (ext:make-stream :input) + *standard-output* (ext:make-stream :output :buffered t) + *error-output* (ext:make-stream :error :buffered t)) + (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) diff --git a/common-lisp/step2_eval.lisp b/common-lisp/step2_eval.lisp index 367e39566f..c360a85d08 100644 --- a/common-lisp/step2_eval.lisp +++ b/common-lisp/step2_eval.lisp @@ -110,7 +110,20 @@ (defun main (&optional (argv nil argv-provided-p)) (declare (ignorable argv argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") (string= (uiop:getenv "TERM") "dumb")))) + + ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort + ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment + ;; variable which the test runner sets causing `read-line' on *standard-input* + ;; to fail with an empty stream error. The following reinitializes the + ;; standard streams + ;; + ;; See http://www.gnu.org/software/clisp/impnotes/streams-interactive.html + #+clisp (setf *standard-input* (ext:make-stream :input) + *standard-output* (ext:make-stream :output :buffered t) + *error-output* (ext:make-stream :error :buffered t)) + (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) diff --git a/common-lisp/step3_env.lisp b/common-lisp/step3_env.lisp index d82db99cf4..bdb3422a89 100644 --- a/common-lisp/step3_env.lisp +++ b/common-lisp/step3_env.lisp @@ -139,7 +139,20 @@ (defun main (&optional (argv nil argv-provided-p)) (declare (ignorable argv argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") (string= (uiop:getenv "TERM") "dumb")))) + + ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort + ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment + ;; variable which the test runner sets causing `read-line' on *standard-input* + ;; to fail with an empty stream error. The following reinitializes the + ;; standard streams + ;; + ;; See http://www.gnu.org/software/clisp/impnotes/streams-interactive.html + #+clisp (setf *standard-input* (ext:make-stream :input) + *standard-output* (ext:make-stream :output :buffered t) + *error-output* (ext:make-stream :error :buffered t)) + (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) diff --git a/common-lisp/step4_if_fn_do.lisp b/common-lisp/step4_if_fn_do.lisp index 3e9f37ba95..7ee011b3c7 100644 --- a/common-lisp/step4_if_fn_do.lisp +++ b/common-lisp/step4_if_fn_do.lisp @@ -142,7 +142,20 @@ (defun main (&optional (argv nil argv-provided-p)) (declare (ignorable argv argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") (string= (uiop:getenv "TERM") "dumb")))) + + ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort + ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment + ;; variable which the test runner sets causing `read-line' on *standard-input* + ;; to fail with an empty stream error. The following reinitializes the + ;; standard streams + ;; + ;; See http://www.gnu.org/software/clisp/impnotes/streams-interactive.html + #+clisp (setf *standard-input* (ext:make-stream :input) + *standard-output* (ext:make-stream :output :buffered t) + *error-output* (ext:make-stream :error :buffered t)) + (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) diff --git a/common-lisp/step5_tco.lisp b/common-lisp/step5_tco.lisp index c2db76f291..7dae7ba2a5 100644 --- a/common-lisp/step5_tco.lisp +++ b/common-lisp/step5_tco.lisp @@ -153,7 +153,20 @@ (defun main (&optional (argv nil argv-provided-p)) (declare (ignorable argv argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") (string= (uiop:getenv "TERM") "dumb")))) + + ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort + ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment + ;; variable which the test runner sets causing `read-line' on *standard-input* + ;; to fail with an empty stream error. The following reinitializes the + ;; standard streams + ;; + ;; See http://www.gnu.org/software/clisp/impnotes/streams-interactive.html + #+clisp (setf *standard-input* (ext:make-stream :input) + *standard-output* (ext:make-stream :output :buffered t) + *error-output* (ext:make-stream :error :buffered t)) + (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) diff --git a/common-lisp/step6_file.lisp b/common-lisp/step6_file.lisp index 81e42b3d18..157dd5a083 100644 --- a/common-lisp/step6_file.lisp +++ b/common-lisp/step6_file.lisp @@ -168,9 +168,21 @@ (rep (format nil "(load-file \"~a\")" file))) (defun main (&optional (argv nil argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") (string= (uiop:getenv "TERM") "dumb")))) + ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort + ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment + ;; variable which the test runner sets causing `read-line' on *standard-input* + ;; to fail with an empty stream error. The following reinitializes the + ;; standard streams + ;; + ;; See http://www.gnu.org/software/clisp/impnotes/streams-interactive.html + #+clisp (setf *standard-input* (ext:make-stream :input) + *standard-output* (ext:make-stream :output :buffered t) + *error-output* (ext:make-stream :error :buffered t)) + (let ((args (if argv-provided-p argv (cdr (utils:raw-command-line-arguments))))) diff --git a/common-lisp/step7_quote.lisp b/common-lisp/step7_quote.lisp index b60fe3609a..fdac605b0f 100644 --- a/common-lisp/step7_quote.lisp +++ b/common-lisp/step7_quote.lisp @@ -204,9 +204,21 @@ (rep (format nil "(load-file \"~a\")" file))) (defun main (&optional (argv nil argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") (string= (uiop:getenv "TERM") "dumb")))) + ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort + ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment + ;; variable which the test runner sets causing `read-line' on *standard-input* + ;; to fail with an empty stream error. The following reinitializes the + ;; standard streams + ;; + ;; See http://www.gnu.org/software/clisp/impnotes/streams-interactive.html + #+clisp (setf *standard-input* (ext:make-stream :input) + *standard-output* (ext:make-stream :output :buffered t) + *error-output* (ext:make-stream :error :buffered t)) + (let ((args (if argv-provided-p argv (cdr (utils:raw-command-line-arguments))))) diff --git a/common-lisp/step8_macros.lisp b/common-lisp/step8_macros.lisp index e52076c275..cd376375ef 100644 --- a/common-lisp/step8_macros.lisp +++ b/common-lisp/step8_macros.lisp @@ -263,9 +263,21 @@ (rep (format nil "(load-file \"~a\")" file))) (defun main (&optional (argv nil argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") (string= (uiop:getenv "TERM") "dumb")))) + ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort + ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment + ;; variable which the test runner sets causing `read-line' on *standard-input* + ;; to fail with an empty stream error. The following reinitializes the + ;; standard streams + ;; + ;; See http://www.gnu.org/software/clisp/impnotes/streams-interactive.html + #+clisp (setf *standard-input* (ext:make-stream :input) + *standard-output* (ext:make-stream :output :buffered t) + *error-output* (ext:make-stream :error :buffered t)) + (let ((args (if argv-provided-p argv (cdr (utils:raw-command-line-arguments))))) diff --git a/common-lisp/step9_try.lisp b/common-lisp/step9_try.lisp index 8228127a95..cb3c5848b6 100644 --- a/common-lisp/step9_try.lisp +++ b/common-lisp/step9_try.lisp @@ -291,9 +291,21 @@ (rep (format nil "(load-file \"~a\")" file))) (defun main (&optional (argv nil argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") (string= (uiop:getenv "TERM") "dumb")))) + ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort + ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment + ;; variable which the test runner sets causing `read-line' on *standard-input* + ;; to fail with an empty stream error. The following reinitializes the + ;; standard streams + ;; + ;; See http://www.gnu.org/software/clisp/impnotes/streams-interactive.html + #+clisp (setf *standard-input* (ext:make-stream :input) + *standard-output* (ext:make-stream :output :buffered t) + *error-output* (ext:make-stream :error :buffered t)) + (let ((args (if argv-provided-p argv (cdr (utils:raw-command-line-arguments))))) diff --git a/common-lisp/stepA_mal.lisp b/common-lisp/stepA_mal.lisp index 2709a82fe2..ec42b995bc 100644 --- a/common-lisp/stepA_mal.lisp +++ b/common-lisp/stepA_mal.lisp @@ -303,8 +303,21 @@ (rep (format nil "(load-file \"~a\")" file))) (defun main (&optional (argv nil argv-provided-p)) + (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") (string= (uiop:getenv "TERM") "dumb")))) + + ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort + ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment + ;; variable which the test runner sets causing `read-line' on *standard-input* + ;; to fail with an empty stream error. The following reinitializes the + ;; standard streams + ;; + ;; See http://www.gnu.org/software/clisp/impnotes/streams-interactive.html + #+clisp (setf *standard-input* (ext:make-stream :input) + *standard-output* (ext:make-stream :output :buffered t) + *error-output* (ext:make-stream :error :buffered t)) + (let ((args (if argv-provided-p argv (cdr (utils:raw-command-line-arguments))))) From d7534a7b687b39177424749ebead666bd18a5b02 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Fri, 18 Nov 2016 09:23:24 +0530 Subject: [PATCH 0258/2308] Common Lisp: Fix `println` on GNU CLISP Use `write-line` instead of `(format *standard-output* ... )` since the former seems to be printing an extra newline at the start --- common-lisp/core.lisp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/common-lisp/core.lisp b/common-lisp/core.lisp index cba2c80894..c87779eef6 100644 --- a/common-lisp/core.lisp +++ b/common-lisp/core.lisp @@ -39,21 +39,23 @@ (types:mal-data-value value2))))) (defun mal-prn (&rest strings) - (format t - "~{~a~^ ~}" - (mapcar (lambda (string) (printer:pr-str string t)) - strings)) - (terpri) - (force-output *standard-output*) + ;; Using write-line instead of (format *standard-output* ... ) since the later prints + ;; and extra newline at start in GNU CLISP, if environment variable PERL_RL is true + ;; or terminal is dumb + (write-line (format nil + "~{~a~^ ~}" + (mapcar (lambda (string) (printer:pr-str string t)) + strings))) (types:make-mal-nil nil)) (defun mal-println (&rest strings) - (format t - "~{~a~^ ~}" - (mapcar (lambda (string) (printer:pr-str string nil)) - strings)) - (terpri) - (force-output *standard-output*) + ;; Using write-line instead of (format *standard-output* ... ) since the later prints + ;; and extra newline at start in GNU CLISP, if environment variable PERL_RL is true + ;; or terminal is dumb + (write-line (format nil + "~{~a~^ ~}" + (mapcar (lambda (string) (printer:pr-str string nil)) + strings))) (types:make-mal-nil nil)) (defun mal-pr-str (&rest strings) From 033f64c44f23d32b0b1ecd4f4f4588d1c1875031 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Fri, 18 Nov 2016 12:16:31 +0530 Subject: [PATCH 0259/2308] Common Lisp: Suppress messages from CMUCL while restoring image The 'Reloaded library ... ' messages were causing some tests to fail --- common-lisp/step0_repl.lisp | 18 ++++++++++++++++++ common-lisp/step1_read_print.lisp | 18 ++++++++++++++++++ common-lisp/step2_eval.lisp | 18 ++++++++++++++++++ common-lisp/step3_env.lisp | 18 ++++++++++++++++++ common-lisp/step4_if_fn_do.lisp | 18 ++++++++++++++++++ common-lisp/step5_tco.lisp | 18 ++++++++++++++++++ common-lisp/step6_file.lisp | 18 ++++++++++++++++++ common-lisp/step7_quote.lisp | 18 ++++++++++++++++++ common-lisp/step8_macros.lisp | 18 ++++++++++++++++++ common-lisp/step9_try.lisp | 18 ++++++++++++++++++ common-lisp/stepA_mal.lisp | 18 ++++++++++++++++++ 11 files changed, 198 insertions(+) diff --git a/common-lisp/step0_repl.lisp b/common-lisp/step0_repl.lisp index dc5814af7e..4f43b58884 100644 --- a/common-lisp/step0_repl.lisp +++ b/common-lisp/step0_repl.lisp @@ -56,3 +56,21 @@ (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) + +;;; Workaround for CMUCL's printing of "Reloaded library ... " messages when an +;;; image containing foreign libraries is restored. The extra messages cause the +;;; MAL testcases to fail + +#+cmucl (progn + (defvar *old-standard-output* *standard-output* + "Keep track of current value standard output, this is restored after image restore completes") + + (defun muffle-output () + (setf *standard-output* (make-broadcast-stream))) + + (defun restore-output () + (setf *standard-output* *old-standard-output*)) + + (pushnew #'muffle-output ext:*after-save-initializations*) + (setf ext:*after-save-initializations* + (append ext:*after-save-initializations* (list #'restore-output)))) diff --git a/common-lisp/step1_read_print.lisp b/common-lisp/step1_read_print.lisp index e72aa294aa..f0032b62d0 100644 --- a/common-lisp/step1_read_print.lisp +++ b/common-lisp/step1_read_print.lisp @@ -64,3 +64,21 @@ (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) + +;;; Workaround for CMUCL's printing of "Reloaded library ... " messages when an +;;; image containing foreign libraries is restored. The extra messages cause the +;;; MAL testcases to fail + +#+cmucl (progn + (defvar *old-standard-output* *standard-output* + "Keep track of current value standard output, this is restored after image restore completes") + + (defun muffle-output () + (setf *standard-output* (make-broadcast-stream))) + + (defun restore-output () + (setf *standard-output* *old-standard-output*)) + + (pushnew #'muffle-output ext:*after-save-initializations*) + (setf ext:*after-save-initializations* + (append ext:*after-save-initializations* (list #'restore-output)))) diff --git a/common-lisp/step2_eval.lisp b/common-lisp/step2_eval.lisp index c360a85d08..b19c5fdf2d 100644 --- a/common-lisp/step2_eval.lisp +++ b/common-lisp/step2_eval.lisp @@ -127,3 +127,21 @@ (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) + +;;; Workaround for CMUCL's printing of "Reloaded library ... " messages when an +;;; image containing foreign libraries is restored. The extra messages cause the +;;; MAL testcases to fail + +#+cmucl (progn + (defvar *old-standard-output* *standard-output* + "Keep track of current value standard output, this is restored after image restore completes") + + (defun muffle-output () + (setf *standard-output* (make-broadcast-stream))) + + (defun restore-output () + (setf *standard-output* *old-standard-output*)) + + (pushnew #'muffle-output ext:*after-save-initializations*) + (setf ext:*after-save-initializations* + (append ext:*after-save-initializations* (list #'restore-output)))) diff --git a/common-lisp/step3_env.lisp b/common-lisp/step3_env.lisp index bdb3422a89..7bb2057b15 100644 --- a/common-lisp/step3_env.lisp +++ b/common-lisp/step3_env.lisp @@ -156,3 +156,21 @@ (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) + +;;; Workaround for CMUCL's printing of "Reloaded library ... " messages when an +;;; image containing foreign libraries is restored. The extra messages cause the +;;; MAL testcases to fail + +#+cmucl (progn + (defvar *old-standard-output* *standard-output* + "Keep track of current value standard output, this is restored after image restore completes") + + (defun muffle-output () + (setf *standard-output* (make-broadcast-stream))) + + (defun restore-output () + (setf *standard-output* *old-standard-output*)) + + (pushnew #'muffle-output ext:*after-save-initializations*) + (setf ext:*after-save-initializations* + (append ext:*after-save-initializations* (list #'restore-output)))) diff --git a/common-lisp/step4_if_fn_do.lisp b/common-lisp/step4_if_fn_do.lisp index 7ee011b3c7..1f7bfa3878 100644 --- a/common-lisp/step4_if_fn_do.lisp +++ b/common-lisp/step4_if_fn_do.lisp @@ -159,3 +159,21 @@ (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) + +;;; Workaround for CMUCL's printing of "Reloaded library ... " messages when an +;;; image containing foreign libraries is restored. The extra messages cause the +;;; MAL testcases to fail + +#+cmucl (progn + (defvar *old-standard-output* *standard-output* + "Keep track of current value standard output, this is restored after image restore completes") + + (defun muffle-output () + (setf *standard-output* (make-broadcast-stream))) + + (defun restore-output () + (setf *standard-output* *old-standard-output*)) + + (pushnew #'muffle-output ext:*after-save-initializations*) + (setf ext:*after-save-initializations* + (append ext:*after-save-initializations* (list #'restore-output)))) diff --git a/common-lisp/step5_tco.lisp b/common-lisp/step5_tco.lisp index 7dae7ba2a5..aa6244f97a 100644 --- a/common-lisp/step5_tco.lisp +++ b/common-lisp/step5_tco.lisp @@ -170,3 +170,21 @@ (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) + +;;; Workaround for CMUCL's printing of "Reloaded library ... " messages when an +;;; image containing foreign libraries is restored. The extra messages cause the +;;; MAL testcases to fail + +#+cmucl (progn + (defvar *old-standard-output* *standard-output* + "Keep track of current value standard output, this is restored after image restore completes") + + (defun muffle-output () + (setf *standard-output* (make-broadcast-stream))) + + (defun restore-output () + (setf *standard-output* *old-standard-output*)) + + (pushnew #'muffle-output ext:*after-save-initializations*) + (setf ext:*after-save-initializations* + (append ext:*after-save-initializations* (list #'restore-output)))) diff --git a/common-lisp/step6_file.lisp b/common-lisp/step6_file.lisp index 157dd5a083..71a4f7972e 100644 --- a/common-lisp/step6_file.lisp +++ b/common-lisp/step6_file.lisp @@ -192,3 +192,21 @@ (if (null args) (repl) (run-file (car args))))) + +;;; Workaround for CMUCL's printing of "Reloaded library ... " messages when an +;;; image containing foreign libraries is restored. The extra messages cause the +;;; MAL testcases to fail + +#+cmucl (progn + (defvar *old-standard-output* *standard-output* + "Keep track of current value standard output, this is restored after image restore completes") + + (defun muffle-output () + (setf *standard-output* (make-broadcast-stream))) + + (defun restore-output () + (setf *standard-output* *old-standard-output*)) + + (pushnew #'muffle-output ext:*after-save-initializations*) + (setf ext:*after-save-initializations* + (append ext:*after-save-initializations* (list #'restore-output)))) diff --git a/common-lisp/step7_quote.lisp b/common-lisp/step7_quote.lisp index fdac605b0f..3250f80d55 100644 --- a/common-lisp/step7_quote.lisp +++ b/common-lisp/step7_quote.lisp @@ -228,3 +228,21 @@ (if (null args) (repl) (run-file (car args))))) + +;;; Workaround for CMUCL's printing of "Reloaded library ... " messages when an +;;; image containing foreign libraries is restored. The extra messages cause the +;;; MAL testcases to fail + +#+cmucl (progn + (defvar *old-standard-output* *standard-output* + "Keep track of current value standard output, this is restored after image restore completes") + + (defun muffle-output () + (setf *standard-output* (make-broadcast-stream))) + + (defun restore-output () + (setf *standard-output* *old-standard-output*)) + + (pushnew #'muffle-output ext:*after-save-initializations*) + (setf ext:*after-save-initializations* + (append ext:*after-save-initializations* (list #'restore-output)))) diff --git a/common-lisp/step8_macros.lisp b/common-lisp/step8_macros.lisp index cd376375ef..a8f370b966 100644 --- a/common-lisp/step8_macros.lisp +++ b/common-lisp/step8_macros.lisp @@ -287,3 +287,21 @@ (if (null args) (repl) (run-file (car args))))) + +;;; Workaround for CMUCL's printing of "Reloaded library ... " messages when an +;;; image containing foreign libraries is restored. The extra messages cause the +;;; MAL testcases to fail + +#+cmucl (progn + (defvar *old-standard-output* *standard-output* + "Keep track of current value standard output, this is restored after image restore completes") + + (defun muffle-output () + (setf *standard-output* (make-broadcast-stream))) + + (defun restore-output () + (setf *standard-output* *old-standard-output*)) + + (pushnew #'muffle-output ext:*after-save-initializations*) + (setf ext:*after-save-initializations* + (append ext:*after-save-initializations* (list #'restore-output)))) diff --git a/common-lisp/step9_try.lisp b/common-lisp/step9_try.lisp index cb3c5848b6..cbf759c0af 100644 --- a/common-lisp/step9_try.lisp +++ b/common-lisp/step9_try.lisp @@ -315,3 +315,21 @@ (if (null args) (repl) (run-file (car args))))) + +;;; Workaround for CMUCL's printing of "Reloaded library ... " messages when an +;;; image containing foreign libraries is restored. The extra messages cause the +;;; MAL testcases to fail. + +#+cmucl (progn + (defvar *old-standard-output* *standard-output* + "Keep track of current value standard output, this is restored after image restore completes") + + (defun muffle-output () + (setf *standard-output* (make-broadcast-stream))) + + (defun restore-output () + (setf *standard-output* *old-standard-output*)) + + (pushnew #'muffle-output ext:*after-save-initializations*) + (setf ext:*after-save-initializations* + (append ext:*after-save-initializations* (list #'restore-output)))) diff --git a/common-lisp/stepA_mal.lisp b/common-lisp/stepA_mal.lisp index ec42b995bc..84b7690a7f 100644 --- a/common-lisp/stepA_mal.lisp +++ b/common-lisp/stepA_mal.lisp @@ -327,3 +327,21 @@ (if (null args) (repl) (run-file (car args))))) + +;;; Workaround for CMUCL's printing of "Reloaded library ... " messages when an +;;; image containing foreign libraries is restored. The extra messages cause the +;;; MAL testcases to fail + +#+cmucl (progn + (defvar *old-standard-output* *standard-output* + "Keep track of current value standard output, this is restored after image restore completes") + + (defun muffle-output () + (setf *standard-output* (make-broadcast-stream))) + + (defun restore-output () + (setf *standard-output* *old-standard-output*)) + + (pushnew #'muffle-output ext:*after-save-initializations*) + (setf ext:*after-save-initializations* + (append ext:*after-save-initializations* (list #'restore-output)))) From 250163cd068d27b0992bada4276b5c2c3fd9020b Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Fri, 18 Nov 2016 14:56:42 +0530 Subject: [PATCH 0260/2308] Common Lisp: Use a custom hash-function when running on ECL ECL's sxhash function does not seem to be consistent for compound data types --- common-lisp/types.lisp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/common-lisp/types.lisp b/common-lisp/types.lisp index 5a5e6f00d9..1e56db3855 100644 --- a/common-lisp/types.lisp +++ b/common-lisp/types.lisp @@ -192,11 +192,18 @@ (and (mal-list-p value2) (mal-vector-p value1))) (mal-sequence= value1 value2))))) +(defun mal-sxhash (value) + (sxhash (mal-data-value value))) + (defun make-mal-value-hash-table () (unless (gethash 'mal-data-value-hash genhash::*hash-test-designator-map*) - (genhash:register-test-designator 'mal-data-value-hash - #'sxhash - #'mal-data-value=)) + ;; ECL's implementation of sxhash does not work well with compound types + ;; so using a custom hash function which hashes the underlying value + (let ((hash-function #+ecl #'mal-sxhash + #-ecl #'sxhash)) + (genhash:register-test-designator 'mal-data-value-hash + hash-function + #'mal-data-value=))) (genhash:make-generic-hash-table :test 'mal-data-value-hash)) (defun wrap-value (value &key booleanp listp) From a8499ff9e74e84ee4da5ce63384a0fd6d364ae15 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Fri, 18 Nov 2016 16:46:15 +0530 Subject: [PATCH 0261/2308] Common Lisp: Add wrapper shell code to change to script's directory when cl-launch scripts are run --- common-lisp/Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common-lisp/Makefile b/common-lisp/Makefile index c3cd49e55b..6afbd1b6ce 100644 --- a/common-lisp/Makefile +++ b/common-lisp/Makefile @@ -28,8 +28,12 @@ all : stepA_mal hist/%_impl: ; +# CL_LAUNCH_VERSION is only defined while building it. We change to the +# directory of the CL_LAUNCH_FILE in --wrap script so that the script can find the dumped +# image even if invoked from some directory different from where it +# currently resides step% : step%.lisp utils.lisp types.lisp env.lisp printer.lisp reader.lisp core.lisp hist/%_impl - cl-launch --verbose --lisp $(LISP) --source-registry $(ROOT_DIR) --system $@ --dump images/$@.$(LISP).image -o $@ --entry 'mal:main' + cl-launch --wrap 'if [ -z "$$CL_LAUNCH_VERSION" ] ; then cd "$$(dirname $$CL_LAUNCH_FILE)" ; fi' --verbose --lisp $(LISP) --source-registry $(ROOT_DIR) --system $@ --dump images/$@.$(LISP).image -o $@ --entry 'mal:main' clean: find . -name 'step*' -executable -exec git check-ignore \{\} \; -delete From e7f85ce18c14c275101a8f26e33258b480a62457 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Fri, 18 Nov 2016 17:30:54 +0530 Subject: [PATCH 0262/2308] Common Lisp: Add documentation --- README.md | 13 ++++--- common-lisp/README.org | 88 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 common-lisp/README.org diff --git a/README.md b/README.md index a73cf21763..53b9ddf5fe 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Mal is implemented in 62 languages: * C++ * C# * ChucK -* GNU CLISP +* Common Lisp * Clojure * CoffeeScript * Crystal @@ -237,14 +237,17 @@ cd chuck ./run ``` -### GNU CLISP +### Common Lisp -*The GNU CLISP implementation was created by [Iqbal Ansari](https://github.com/iqbalansari)* +*The Common Lisp implementation was created by [Iqbal Ansari](https://github.com/iqbalansari)* -The implementation has been tested with GNU CLISP v2.49 on Ubuntu 16.04, 14.04 and 12.04 +The implementation has been tested with SBCL, CCL, CMUCL, GNU CLISP, ECL and +Allegro CL on Ubuntu 16.04 and Ubuntu 12.04, see +the [README][common-lisp/README.org] for more details. Provided you have the +dependencies mentioned installed, do the following to run the implementation ``` -cd clisp +cd common-lisp make ./run ``` diff --git a/common-lisp/README.org b/common-lisp/README.org new file mode 100644 index 0000000000..db3795ae9b --- /dev/null +++ b/common-lisp/README.org @@ -0,0 +1,88 @@ +* Implementation of MAL in Common Lisp + +** Introduction + +This is a reasonably portable implementation of MAL in Common Lisp. It has been +tested to work with following Common Lisp implementations + +- Steel Bank Common Lisp ([[http://sbcl.org/]]) +- Clozure Common Lisp ([[http://ccl.clozure.com/]]) +- CMU Common Lisp ([[https://www.cons.org/cmucl/]]) +- GNU CLISP ([[http://www.clisp.org/]]) +- Embeddable Common Lisp ([[https://common-lisp.net/project/ecl/]]) +- Allegro CL ([[http://franz.com/products/allegro-common-lisp/]]) + +[[http://www.cliki.net/cl-launch][cl-launch]] to build command line runnable scripts/images for the above +implementations. + +** Dependencies + +*** [[http://www.cliki.net/cl-launch][cl-launch]] + For building command line executable scripts + +*** [[https://www.quicklisp.org/beta/][quicklisp]] + For installing dependencies + +*** ~readline~ (~libreadline-dev~ on Ubuntu) + For readline integration. If you wish to run the implementation using Allegro + CL, you will also have to install the 32 bit version of readline + (~lib32readline-dev~ on Ubuntu) +*** (Optional) ~asdf~ (~cl-asdf~ on Ubuntu) + This is needed if you want to run the implementation using GNU CLISP, since + GNU CLISP does not ship with ~asdf~ and ~cl-launch~ depends on it. + +** Running using different implementations + +By default the MAL is built using ~sbcl~, you can control this using ~LISP~ +environment variable. The variable should be set to the cl-launch "nickname" for +implementation. The nicknames that work currently are + +|------------------------+----------| +| Implementation | Nickname | +|------------------------+----------| +| Steel Bank Common Lisp | sbcl | +| Clozure Common Lisp | ccl | +| CMU Common Lisp | cmucl | +| GNU CLISP | clisp | +| Embeddable Common Lisp | ecl | +| Allegro CL | allegro | +|------------------------+----------| + +For example to build with GNU CLISP, you need to do the following + +#+BEGIN_SRC sh + cd common-lisp ; LISP=clisp make +#+END_SRC + +You can control the implementation binary used for the build using environment +variables. For a given implementation nickname, the environment variable will +be the capitalization of the given nickname. + +|------------------------+-------------| +| Implementation | Binary Path | +|------------------------+-------------| +| Steel Bank Common Lisp | SBCL | +| Clozure Common Lisp | CCL | +| CMU Common Lisp | CMUCL | +| GNU CLISP | CLISP | +| Embeddable Common Lisp | ECL | +| Allegro CL | ALLEGRO | +|------------------------+-------------| + +For example to build MAL with Clozure CL installed in +~\~/.roswell/impls/x86-64/linux/ccl-bin/1.11/lx86cl64~, you need to do the +following + +#+BEGIN_SRC sh + cd common-lisp ; LISP=ccl CCL=~/.roswell/impls/x86-64/linux/ccl-bin/1.11/lx86cl64 make +#+END_SRC + +You can use the variables ~*cl-implementation*~ and ~*cl-version*~ can be used +to in MAL REPL to check the Common Lisp implementation and the version used for +building it. + +** Interop + +There is some basically interop in the form ~cl-eval~ which takes a string and +evaluates it as Common Lisp code, the result is returned in form of a MAL value, +you are limited to code that produces values that have MAL counterparts. From f48e7d5a7ad43a689c566507804ecce35c7e5b53 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Fri, 18 Nov 2016 17:36:44 +0530 Subject: [PATCH 0263/2308] Common Lisp: Remove GNU CLISP specific implementation --- .travis.yml | 1 - Makefile | 3 +- clisp/.dir-locals.el | 2 - clisp/Dockerfile | 25 --- clisp/Makefile | 21 -- clisp/README.md | 3 - clisp/core.lisp | 402 ------------------------------------ clisp/dependencies.lisp | 6 - clisp/env.lisp | 92 --------- clisp/printer.lisp | 56 ----- clisp/reader.lisp | 172 --------------- clisp/run | 2 - clisp/step0_repl.lisp | 67 ------ clisp/step1_read_print.lisp | 76 ------- clisp/step2_eval.lisp | 145 ------------- clisp/step3_env.lisp | 164 --------------- clisp/step4_if_fn_do.lisp | 180 ---------------- clisp/step5_tco.lisp | 191 ----------------- clisp/step6_file.lisp | 210 ------------------- clisp/step7_quote.lisp | 248 ---------------------- clisp/step8_macros.lisp | 298 -------------------------- clisp/step9_try.lisp | 324 ----------------------------- clisp/stepA_mal.lisp | 329 ----------------------------- clisp/tests/stepA_mal.mal | 75 ------- clisp/types.lisp | 258 ----------------------- clisp/utils.lisp | 20 -- 26 files changed, 1 insertion(+), 3369 deletions(-) delete mode 100644 clisp/.dir-locals.el delete mode 100644 clisp/Dockerfile delete mode 100644 clisp/Makefile delete mode 100644 clisp/README.md delete mode 100644 clisp/core.lisp delete mode 100644 clisp/dependencies.lisp delete mode 100644 clisp/env.lisp delete mode 100644 clisp/printer.lisp delete mode 100644 clisp/reader.lisp delete mode 100755 clisp/run delete mode 100644 clisp/step0_repl.lisp delete mode 100644 clisp/step1_read_print.lisp delete mode 100644 clisp/step2_eval.lisp delete mode 100644 clisp/step3_env.lisp delete mode 100644 clisp/step4_if_fn_do.lisp delete mode 100644 clisp/step5_tco.lisp delete mode 100644 clisp/step6_file.lisp delete mode 100644 clisp/step7_quote.lisp delete mode 100644 clisp/step8_macros.lisp delete mode 100644 clisp/step9_try.lisp delete mode 100644 clisp/stepA_mal.lisp delete mode 100644 clisp/tests/stepA_mal.mal delete mode 100644 clisp/types.lisp delete mode 100644 clisp/utils.lisp diff --git a/.travis.yml b/.travis.yml index 94590b220d..ad303b7454 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,6 @@ matrix: - {env: IMPL=coffee, services: [docker]} - {env: IMPL=cs, services: [docker]} - {env: IMPL=chuck, services: [docker]} - - {env: IMPL=clisp, services: [docker]} - {env: IMPL=clojure, services: [docker]} - {env: IMPL=common-lisp, services: [docker]} - {env: IMPL=crystal, services: [docker]} diff --git a/Makefile b/Makefile index 250b25cd07..a3a735a4ce 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ DOCKERIZE = # Settings # -IMPLS = ada awk bash basic c d chuck clojure coffee clisp common-lisp cpp crystal cs dart \ +IMPLS = ada awk bash basic c d chuck clojure coffee common-lisp cpp crystal cs dart \ erlang elisp elixir es6 factor forth fsharp go groovy guile haskell \ haxe io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ @@ -153,7 +153,6 @@ d_STEP_TO_PROG = d/$($(1)) chuck_STEP_TO_PROG = chuck/$($(1)).ck clojure_STEP_TO_PROG = clojure/target/$($(1)).jar coffee_STEP_TO_PROG = coffee/$($(1)).coffee -clisp_STEP_TO_PROG = clisp/$($(1)).fas common-lisp_STEP_TO_PROG = common-lisp/$($(1)) cpp_STEP_TO_PROG = cpp/$($(1)) crystal_STEP_TO_PROG = crystal/$($(1)) diff --git a/clisp/.dir-locals.el b/clisp/.dir-locals.el deleted file mode 100644 index 96c665ed30..0000000000 --- a/clisp/.dir-locals.el +++ /dev/null @@ -1,2 +0,0 @@ -((lisp-mode - (inferior-lisp-program . "clisp"))) \ No newline at end of file diff --git a/clisp/Dockerfile b/clisp/Dockerfile deleted file mode 100644 index 711244bd65..0000000000 --- a/clisp/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM ubuntu:vivid -MAINTAINER Joel Martin - -########################################################## -# General requirements for testing or common across many -# implementations -########################################################## - -RUN apt-get -y update - -# Required for running tests -RUN apt-get -y install make python - -# Some typical implementation and test requirements -RUN apt-get -y install curl libreadline-dev libedit-dev - -RUN mkdir -p /mal -WORKDIR /mal - -########################################################## -# Specific implementation requirements -########################################################## - -# Install clisp -RUN apt-get -y install clisp diff --git a/clisp/Makefile b/clisp/Makefile deleted file mode 100644 index 1f35911729..0000000000 --- a/clisp/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -SOURCES_BASE = utils.lisp types.lisp reader.lisp printer.lisp -SOURCES_LISP = env.lisp core.lisp stepA_mal.lisp -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - -all : stepA_mal.fas - -.PHONY: stats - -step%.fas : step%.lisp dependencies.lisp utils.lisp types.lisp env.lisp printer.lisp reader.lisp core.lisp - clisp -q -c $< - -clean: - rm *.fas *.lib - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/clisp/README.md b/clisp/README.md deleted file mode 100644 index 120e3d6f3c..0000000000 --- a/clisp/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Implementation of MAL in Common Lisp - -- This implementation is not portable and works only with CLISP diff --git a/clisp/core.lisp b/clisp/core.lisp deleted file mode 100644 index 5ac7cfdf1c..0000000000 --- a/clisp/core.lisp +++ /dev/null @@ -1,402 +0,0 @@ -(defpackage :core - (:use :common-lisp :types :reader :printer) - (:export :ns)) - -(in-package :core) - -(define-condition index-error (types:mal-runtime-exception) - ((size :initarg :size :reader size) - (index :initarg :index :reader index) - (sequence :initarg :sequence :reader sequence)) - (:report (lambda (condition stream) - (format stream - "Index out of range (~a), length is ~a but index given was ~a" - (printer:pr-str (sequence condition)) - (size condition) - (index condition))))) - -(defun get-file-contents (filename) - (with-open-file (stream filename) - (let ((data (make-string (file-length stream)))) - (read-sequence data stream) - data))) - -(defmacro wrap-boolean (form) - `(if ,form - types:mal-true - types:mal-false)) - -(defvar ns - (list - (cons (types:make-mal-symbol "+") - (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values '+ value1 value2)))) - - (cons (types:make-mal-symbol "-") - (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values '- value1 value2)))) - - (cons (types:make-mal-symbol "*") - (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values '* value1 value2)))) - - (cons (types:make-mal-symbol "/") - (types:make-mal-builtin-fn (lambda (value1 value2) - (types:make-mal-number (floor (/ (types:mal-data-value value1) - (types:mal-data-value value2))))))) - - (cons (types:make-mal-symbol "prn") - (types:make-mal-builtin-fn (lambda (&rest strings) - (write-line (format nil - "~{~a~^ ~}" - (mapcar (lambda (string) (printer:pr-str string t)) - strings))) - types:mal-nil))) - - (cons (types:make-mal-symbol "println") - (types:make-mal-builtin-fn (lambda (&rest strings) - (write-line (format nil - "~{~a~^ ~}" - (mapcar (lambda (string) (printer:pr-str string nil)) - strings))) - types:mal-nil))) - - (cons (types:make-mal-symbol "pr-str") - (types:make-mal-builtin-fn (lambda (&rest strings) - (types:make-mal-string (format nil - "~{~a~^ ~}" - (mapcar (lambda (string) (printer:pr-str string t)) - strings)))))) - - (cons (types:make-mal-symbol "str") - (types:make-mal-builtin-fn (lambda (&rest strings) - (types:make-mal-string (format nil - "~{~a~}" - (mapcar (lambda (string) (printer:pr-str string nil)) - strings)))))) - - (cons (types:make-mal-symbol "list") - (types:make-mal-builtin-fn (lambda (&rest values) - (make-mal-list values)))) - - (cons (types:make-mal-symbol "list?") - (types:make-mal-builtin-fn (lambda (value) - (wrap-boolean (or (types:mal-nil-p value) - (types:mal-list-p value)))))) - - (cons (types:make-mal-symbol "empty?") - (types:make-mal-builtin-fn (lambda (value) - (wrap-boolean (zerop (length (mal-data-value value))))))) - - (cons (types:make-mal-symbol "count") - (types:make-mal-builtin-fn (lambda (value) - (types:apply-unwrapped-values 'length value)))) - - (cons (types:make-mal-symbol "=") - (types:make-mal-builtin-fn (lambda (value1 value2) - (wrap-boolean (types:mal-value= value1 value2))))) - - (cons (types:make-mal-symbol "<") - (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values-prefer-bool '< - value1 - value2)))) - - (cons (types:make-mal-symbol ">") - (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values-prefer-bool '> - value1 - value2)))) - - (cons (types:make-mal-symbol "<=") - (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values-prefer-bool '<= - value1 - value2)))) - - (cons (types:make-mal-symbol ">=") - (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values-prefer-bool '>= - value1 - value2)))) - - (cons (types:make-mal-symbol "read-string") - (types:make-mal-builtin-fn (lambda (value) - (reader:read-str (types:mal-data-value value))))) - - (cons (types:make-mal-symbol "slurp") - (types:make-mal-builtin-fn (lambda (filename) - (types:apply-unwrapped-values 'get-file-contents filename)))) - - (cons (types:make-mal-symbol "atom") - (types:make-mal-builtin-fn (lambda (value) - (types:make-mal-atom value)))) - - (cons (types:make-mal-symbol "atom?") - (types:make-mal-builtin-fn (lambda (value) - (wrap-boolean (types:mal-atom-p value))))) - - (cons (types:make-mal-symbol "deref") - (types:make-mal-builtin-fn (lambda (atom) - (types:mal-data-value atom)))) - - (cons (types:make-mal-symbol "reset!") - (types:make-mal-builtin-fn (lambda (atom value) - (setf (types:mal-data-value atom) value)))) - - (cons (types:make-mal-symbol "swap!") - (types:make-mal-builtin-fn (lambda (atom fn &rest args) - (setf (types:mal-data-value atom) - (apply (mal-data-value fn) - (append (list (types:mal-data-value atom)) - args)))))) - - (cons (types:make-mal-symbol "cons") - (types:make-mal-builtin-fn (lambda (element list) - (types:make-mal-list (cons element - (map 'list - #'identity - (mal-data-value list))))))) - - (cons (types:make-mal-symbol "concat") - (types:make-mal-builtin-fn (lambda (&rest lists) - (types:make-mal-list (apply #'concatenate - 'list - (mapcar #'types:mal-data-value lists)))))) - - - (cons (types:make-mal-symbol "nth") - (types:make-mal-builtin-fn (lambda (sequence index) - (or (nth (mal-data-value index) - (map 'list #'identity (mal-data-value sequence))) - (error 'index-error - :size (length (mal-data-value sequence)) - :index (mal-data-value index) - :sequence sequence))))) - - (cons (types:make-mal-symbol "first") - (types:make-mal-builtin-fn (lambda (sequence) - (or (first (map 'list #'identity (mal-data-value sequence))) - types:mal-nil)))) - - (cons (types:make-mal-symbol "rest") - (types:make-mal-builtin-fn (lambda (sequence) - (types:make-mal-list (rest (map 'list - #'identity - (mal-data-value sequence))))))) - - (cons (types:make-mal-symbol "throw") - (types:make-mal-builtin-fn (lambda (value) - (error 'types:mal-user-exception - :data value)))) - - (cons (types:make-mal-symbol "apply") - (types:make-mal-builtin-fn (lambda (fn &rest values) - (let ((final-arg (map 'list - #'identity - (types:mal-data-value (car (last values))))) - (butlast-args (butlast values))) - (apply (types:mal-data-value fn) - (append butlast-args final-arg)))))) - - (cons (types:make-mal-symbol "map") - (types:make-mal-builtin-fn (lambda (fn sequence) - (let ((applicants (map 'list - #'identity - (types:mal-data-value sequence)))) - (types:make-mal-list (mapcar (types:mal-data-value fn) - applicants)))))) - - (cons (types:make-mal-symbol "nil?") - (types:make-mal-builtin-fn (lambda (value) - (wrap-boolean (types:mal-nil-p value))))) - - (cons (types:make-mal-symbol "true?") - (types:make-mal-builtin-fn (lambda (value) - (wrap-boolean (and (types:mal-boolean-p value) - (types:mal-data-value value)))))) - - (cons (types:make-mal-symbol "false?") - (types:make-mal-builtin-fn (lambda (value) - (wrap-boolean (and (types:mal-boolean-p value) - (not (types:mal-data-value value))))))) - - (cons (types:make-mal-symbol "symbol?") - (types:make-mal-builtin-fn (lambda (value) - (wrap-boolean (types:mal-symbol-p value))))) - - (cons (types:make-mal-symbol "symbol") - (types:make-mal-builtin-fn (lambda (string) - (types:make-mal-symbol (types:mal-data-value string))))) - - (cons (types:make-mal-symbol "keyword") - (types:make-mal-builtin-fn (lambda (keyword) - (if (types:mal-keyword-p keyword) - keyword - (types:make-mal-keyword (format nil ":~a" (types:mal-data-value keyword))))))) - - (cons (types:make-mal-symbol "keyword?") - (types:make-mal-builtin-fn (lambda (value) - (wrap-boolean (types:mal-keyword-p value))))) - - (cons (types:make-mal-symbol "vector") - (types:make-mal-builtin-fn (lambda (&rest elements) - (types:make-mal-vector (map 'vector #'identity elements))))) - - (cons (types:make-mal-symbol "vector?") - (types:make-mal-builtin-fn (lambda (value) - (wrap-boolean (types:mal-vector-p value))))) - - (cons (types:make-mal-symbol "hash-map") - (types:make-mal-builtin-fn (lambda (&rest elements) - (let ((hash-map (make-hash-table :test 'types:mal-value=))) - (loop - for (key value) on elements - by #'cddr - do (setf (gethash key hash-map) value)) - (types:make-mal-hash-map hash-map))))) - - (cons (types:make-mal-symbol "map?") - (types:make-mal-builtin-fn (lambda (value) - (wrap-boolean (types:mal-hash-map-p value))))) - - (cons (types:make-mal-symbol "assoc") - (types:make-mal-builtin-fn (lambda (hash-map &rest elements) - (let ((hash-map-value (types:mal-data-value hash-map)) - (new-hash-map (make-hash-table :test 'types:mal-value=))) - - (loop - for key being the hash-keys of hash-map-value - do (setf (gethash key new-hash-map) - (gethash key hash-map-value))) - - (loop - for (key value) on elements - by #'cddr - do (setf (gethash key new-hash-map) value)) - - (types:make-mal-hash-map new-hash-map))))) - - (cons (types:make-mal-symbol "dissoc") - (types:make-mal-builtin-fn (lambda (hash-map &rest elements) - (let ((hash-map-value (types:mal-data-value hash-map)) - (new-hash-map (make-hash-table :test 'types:mal-value=))) - - (loop - for key being the hash-keys of hash-map-value - do (when (not (member key elements :test #'types:mal-value=)) - (setf (gethash key new-hash-map) - (gethash key hash-map-value)))) - - (types:make-mal-hash-map new-hash-map))))) - - (cons (types:make-mal-symbol "get") - (types:make-mal-builtin-fn (lambda (hash-map key) - (or (and (types:mal-hash-map-p hash-map) - (gethash key (types:mal-data-value hash-map))) - types:mal-nil)))) - - (cons (types:make-mal-symbol "contains?") - (types:make-mal-builtin-fn (lambda (hash-map key) - (if (gethash key (types:mal-data-value hash-map)) - types:mal-true - types:mal-false)))) - - (cons (types:make-mal-symbol "keys") - (types:make-mal-builtin-fn (lambda (hash-map) - (let ((hash-map-value (types:mal-data-value hash-map))) - (types:make-mal-list (loop - for key being the hash-keys of hash-map-value - collect key)))))) - - (cons (types:make-mal-symbol "vals") - (types:make-mal-builtin-fn (lambda (hash-map) - (let ((hash-map-value (types:mal-data-value hash-map))) - (types:make-mal-list (loop - for key being the hash-keys of hash-map-value - collect (gethash key hash-map-value))))))) - - (cons (types:make-mal-symbol "sequential?") - (types:make-mal-builtin-fn (lambda (value) - (wrap-boolean (or (types:mal-vector-p value) - (types:mal-list-p value)))))) - - (cons (types:make-mal-symbol "readline") - (types:make-mal-builtin-fn (lambda (prompt) - (format *standard-output* (types:mal-data-value prompt)) - (force-output *standard-output*) - (types:wrap-value (read-line *standard-input* nil))))) - - (cons (types:make-mal-symbol "string?") - (types:make-mal-builtin-fn (lambda (value) - (wrap-boolean (types:mal-string-p value))))) - - (cons (types:make-mal-symbol "time-ms") - (types:make-mal-builtin-fn (lambda () - - (types:make-mal-number (floor (/ (get-internal-real-time) - (/ internal-time-units-per-second - 1000))))))) - - (cons (types:make-mal-symbol "conj") - (types:make-mal-builtin-fn (lambda (value &rest elements) - (cond ((types:mal-list-p value) - (types:make-mal-list (append (nreverse elements) - (types:mal-data-value value)))) - ((types:mal-vector-p value) - (types:make-mal-vector (concatenate 'vector - (types:mal-data-value value) - elements))) - (t (error 'types:mal-user-exception)))))) - (cons (types:make-mal-symbol "seq") - (types:make-mal-builtin-fn (lambda (value) - (if (zerop (length (types:mal-data-value value))) - types:mal-nil - (cond ((types:mal-list-p value) - value) - ((types:mal-vector-p value) - (types:make-mal-list (map 'list - #'identity - (types:mal-data-value value)))) - ((types:mal-string-p value) - (types:make-mal-list (map 'list - (lambda (char) - (types:make-mal-string (make-string 1 :initial-element char))) - (types:mal-data-value value)))) - (t (error 'types:mal-user-exception))))))) - - (cons (types:make-mal-symbol "with-meta") - (types:make-mal-builtin-fn (lambda (value meta) - (funcall (switch-mal-type value - (types:string #'types:make-mal-string) - (types:symbol #'types:make-mal-symbol) - (types:list #'types:make-mal-list) - (types:vector #'types:make-mal-vector) - (types:hash-map #'types:make-mal-hash-map) - (types:fn #'types:make-mal-fn) - (types:builtin-fn #'types:make-mal-builtin-fn)) - (types:mal-data-value value) - :meta meta - :attrs (types:mal-data-attrs value))))) - - (cons (types:make-mal-symbol "meta") - (types:make-mal-builtin-fn (lambda (value) - (or (types:mal-data-meta value) - types:mal-nil)))) - - ;; Since a nil in clisp may mean an empty list or boolean false or simply nil, the - ;; caller can specify the preferred type while evaluating an expression - (cons (types:make-mal-symbol "clisp-eval") - (types:make-mal-builtin-fn (lambda (code &optional booleanp listp) - (types:wrap-value (eval (read-from-string (types:mal-data-value code))) - :booleanp (and booleanp (types:mal-data-value booleanp)) - :listp (and listp (types:mal-data-value listp)))))) - - (cons (types:make-mal-symbol "define-builtin") - (types:make-mal-builtin-fn (lambda (arglist &rest body) - (let* ((func-args (types:unwrap-value arglist)) - (func-body (mapcar #'types:unwrap-value body)) - (func (eval `(lambda ,func-args ,@func-body)))) - (types:make-mal-builtin-fn (lambda (&rest args) - (types:wrap-value (apply func - (mapcar #'types:unwrap-value args))))))))))) diff --git a/clisp/dependencies.lisp b/clisp/dependencies.lisp deleted file mode 100644 index 4d822253e4..0000000000 --- a/clisp/dependencies.lisp +++ /dev/null @@ -1,6 +0,0 @@ -(require "utils") -(require "types") -(require "env") -(require "reader") -(require "printer") -(require "core") diff --git a/clisp/env.lisp b/clisp/env.lisp deleted file mode 100644 index 74604d22d8..0000000000 --- a/clisp/env.lisp +++ /dev/null @@ -1,92 +0,0 @@ -(defpackage :env - (:use :common-lisp :types) - (:export :undefined-symbol - :mal-env - :create-mal-env - :get-env - :find-env - :set-env)) - -(in-package :env) - -(define-condition undefined-symbol (types:mal-runtime-exception) - ((symbol :initarg :symbol :reader symbol)) - (:report (lambda (condition stream) - (format stream - "'~a' not found" - (symbol condition))))) - -(define-condition arity-mismatch (types:mal-runtime-exception) - ((required :initarg :required :reader required) - (provided :initarg :provided :reader provided)) - (:report (lambda (condition stream) - (format stream - "Unexpected number of arguments provided, expected ~a, got ~a" - (required condition) - (provided condition))))) - -(defstruct mal-env - (bindings (make-hash-table :test 'equal) :read-only t) - (parent nil :read-only t)) - -(defun find-env (env symbol) - (let ((value (gethash (types:mal-data-value symbol) - (mal-env-bindings env))) - (parent (mal-env-parent env))) - (cond - (value value) - (parent (find-env parent symbol)) - (t nil)))) - -(defun get-env (env symbol) - (let ((value (find-env env symbol))) - (if value - value - (error 'undefined-symbol - :symbol (format nil "~a" (types:mal-data-value symbol)))))) - -(defun set-env (env symbol value) - (setf (gethash (types:mal-data-value symbol) - (mal-env-bindings env)) - value)) - -(defun create-mal-env (&key (parent nil) (binds nil) (exprs nil)) - (let ((varidiac-position (position (types:make-mal-symbol "&") - binds - :test #'mal-value=))) - (when varidiac-position - (setf (subseq binds varidiac-position (length binds)) - (list (nth (1+ varidiac-position) binds))) - (setf binds (subseq binds 0 (1+ varidiac-position))) - - (let* ((no-of-args (length exprs)) - ;; There are enough arguments for variadic operator - ;; to consume - (rest-args (cond ((>= no-of-args (1+ varidiac-position)) - (make-mal-list (subseq exprs - varidiac-position - (length exprs)))) - ;; There are enough parameters to satisfy the - ;; normal arguments, set rest-args to a nil value - ((= no-of-args varidiac-position) - types:mal-nil)))) - (handler-case - (setf exprs (concatenate 'list - (subseq exprs 0 varidiac-position) - (list rest-args))) - (simple-type-error (condition) - (error 'arity-mismatch - :required (length binds) - :provided (length exprs)))))) - - (when (not (= (length binds) (length exprs))) - (error 'arity-mismatch - :required (length binds) - :provided (length exprs))) - - (let ((arg-params (map 'list #'cons binds exprs)) - (bindings (make-hash-table :test 'equal))) - (dolist (arg-param arg-params) - (setf (gethash (types:mal-data-value (car arg-param)) bindings) - (cdr arg-param))) - (make-mal-env :bindings bindings :parent parent)))) diff --git a/clisp/printer.lisp b/clisp/printer.lisp deleted file mode 100644 index afc05dc352..0000000000 --- a/clisp/printer.lisp +++ /dev/null @@ -1,56 +0,0 @@ -(defpackage :printer - (:use :common-lisp :utils :types) - (:export :pr-str)) - -(in-package :printer) - -(defun pr-mal-sequence (start-delimiter sequence end-delimiter &optional (print-readably t)) - (concatenate 'string - start-delimiter - (format nil - "~{~a~^ ~}" - (map 'list (lambda (value) - (pr-str value print-readably)) - (types:mal-data-value sequence))) - end-delimiter)) - -(defun pr-mal-hash-map (hash-map &optional (print-readably t)) - (let ((hash-map-value (types:mal-data-value hash-map))) - (concatenate 'string - "{" - (format nil - "~{~a~^ ~}" - (let (entries) - (maphash (lambda (key value) - (push (format nil - "~a ~a" - (pr-str key print-readably) - (pr-str value print-readably)) - entries)) - hash-map-value) - (nreverse entries))) - "}"))) - -(defun pr-string (ast &optional (print-readably t)) - (if print-readably - (utils:replace-all (prin1-to-string (types:mal-data-value ast)) - " -" - "\\n") - (types:mal-data-value ast))) - -(defun pr-str (ast &optional (print-readably t)) - (when ast - (switch-mal-type ast - (types:number (format nil "~d" (types:mal-data-value ast))) - (types:boolean (if (types:mal-data-value ast) "true" "false")) - (types:nil "nil") - (types:string (pr-string ast print-readably)) - (types:symbol (types:mal-data-value ast)) - (types:keyword (types:mal-data-value ast)) - (types:list (pr-mal-sequence "(" ast ")" print-readably)) - (types:vector (pr-mal-sequence "[" ast "]" print-readably)) - (types:hash-map (pr-mal-hash-map ast print-readably)) - (types:atom (format nil "(atom ~a)" (pr-str (types:mal-data-value ast)))) - (types:fn "#") - (types:builtin-fn "#")))) diff --git a/clisp/reader.lisp b/clisp/reader.lisp deleted file mode 100644 index 5e9e7b66a7..0000000000 --- a/clisp/reader.lisp +++ /dev/null @@ -1,172 +0,0 @@ -(defpackage :reader - (:use :common-lisp :regexp :utils :types) - (:export :read-str - :eof)) - -(in-package :reader) - -(defvar *string-re* (regexp:regexp-compile "^\"\\(\\\\\\(.\\| -\\)\\|[^\"\\]\\)*\"$") - "Regular expression to match string") - -(defvar *digit-re* (regexp:regexp-compile "^\\(-\\|+\\)\\?[[:digit:]]\\+$") - "Regular expression to match digits") - -(defvar *tokenizer-re* (regexp:regexp-compile "[[:space:],]*\\(~@\\|[][{}()~`'^@]\\|\"\\(\\\\\\(.\\| -\\)\\|[^\"\\]\\)*\"\\?\\|;[^ -]*\\|[^][[:space:]~{}()@^`'\";]*\\)") - "Regular expression to match LISP code") - -(define-condition eof (types:mal-error) - ((context :initarg :context :reader context)) - (:report (lambda (condition stream) - (format stream - "EOF encountered while reading ~a" - (context condition))))) - -(defun parse-string (token) - (if (and (> (length token) 1) - (regexp:regexp-exec *string-re* token)) - (progn - (read-from-string (utils:replace-all token - "\\n" - " -"))) - ;; A bit inaccurate - (error 'eof - :context "string"))) - -;; Useful to debug regexps -(defun test-re (re string) - (let ((match (regexp:match re string))) - (when match - (regexp:match-string string match)))) - -(defun test-tokenizer (re string) - (let ((*tokenizer-re* re)) - (tokenize string))) - -(defun tokenize (string) - (let (tokens) - (do* ((start 0) - (end (length string)) - (match t)) - ((not match)) - (setf match (when (< start end) - (nth-value 1 - (regexp:regexp-exec *tokenizer-re* string :start start)))) - (when match - (setf start (regexp:match-end match)) - (let ((token (string-trim "," (regexp:match-string string match)))) - (unless (or (zerop (length token)) - (char= (char token 0) #\;)) - (push token tokens))))) - (nreverse tokens))) - -(defstruct (token-reader) - (tokens nil)) - -(defun peek (reader) - (car (token-reader-tokens reader))) - -(defun next (reader) - (pop (token-reader-tokens reader))) - -(defun consume (reader) - (pop (token-reader-tokens reader)) - reader) - -(defun read-str (string) - (read-form (make-token-reader :tokens (tokenize string)))) - -(defun read-form (reader) - (let ((token (peek reader))) - (cond - ((null token) nil) - ((string= token "(") (make-mal-list (read-mal-sequence reader - ")" - 'list))) - ((string= token "[") (make-mal-vector (read-mal-sequence reader - "]" - 'vector))) - ((string= token "{") (make-mal-hash-map (read-hash-map reader))) - ((string= token "^") (read-form-with-meta reader)) - ((member token '("'" "`" "~" "~@" "@") :test #'string= ) (expand-quote reader)) - (t (read-atom reader))))) - -(defun read-form-with-meta (reader) - (consume reader) - (let ((meta (read-form reader)) - (value (read-form reader))) - - (when (or (null meta) - (null value)) - (error 'eof - :context "object metadata")) - - (make-mal-list (list (make-mal-symbol "with-meta") value meta)))) - -(defun expand-quote (reader) - (let ((quote (next reader))) - (make-mal-list (list (make-mal-symbol (cond - ((string= quote "'") "quote") - ((string= quote "`") "quasiquote") - ((string= quote "~") "unquote") - ((string= quote "~@") "splice-unquote") - ((string= quote "@") "deref"))) - (read-form reader))))) - -(defun read-mal-sequence (reader &optional (delimiter ")") (constructor 'list)) - ;; Consume the opening brace - (consume reader) - (let (forms) - (loop - for token = (peek reader) - while (cond - ((null token) (error 'eof - :context (if (string= delimiter ")") - "list" - "vector"))) - ((string= token delimiter) (return)) - (t (push (read-form reader) forms)))) - ;; Consume the closing brace - (consume reader) - (apply constructor (nreverse forms)))) - -(defun read-hash-map (reader) - ;; Consume the open brace - (consume reader) - (let (forms) - (loop - for token = (peek reader) - while (cond - ((null token) (error 'eof - :context "hash-map")) - ((string= token "}") (return)) - (t (let ((key (read-form reader)) - (value (read-form reader))) - (if (null value) - (error 'eof - :context "hash-map") - (push (cons key value) forms)))))) - ;; Consume the closing brace - (consume reader) - (make-hash-table :test 'types:mal-value= - :initial-contents (nreverse forms)))) - -(defun read-atom (reader) - (let ((token (next reader))) - (cond - ((string= token "false") - types:mal-false) - ((string= token "true") - types:mal-true) - ((string= token "nil") - types:mal-nil) - ((char= (char token 0) #\") - (make-mal-string (parse-string token))) - ((char= (char token 0) #\:) - (make-mal-keyword token)) - ((regexp:regexp-exec *digit-re* token) - (make-mal-number (read-from-string token))) - (t (make-mal-symbol token))))) diff --git a/clisp/run b/clisp/run deleted file mode 100755 index 95220201e3..0000000000 --- a/clisp/run +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -exec clisp $(dirname $0)/${STEP:-stepA_mal}.fas "${@}" diff --git a/clisp/step0_repl.lisp b/clisp/step0_repl.lisp deleted file mode 100644 index 960ed2eef7..0000000000 --- a/clisp/step0_repl.lisp +++ /dev/null @@ -1,67 +0,0 @@ -(defpackage :mal - (:use :common-lisp - :readline)) - -(in-package :mal) - -(defun mal-read (string) - string) - -(defun mal-eval (ast env) - ast) - -(defun mal-print (expression) - expression) - -(defun rep (string) - (mal-print (mal-eval (mal-read string) - (make-hash-table :test #'equal)))) - -;; Readline setup -;;; The test runner sets this environment variable, in which case we do -;;; use readline since tests do not work with the readline interface -(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) - -(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) - ".mal-clisp-history"))) - -(defun load-history () - (readline:read-history *history-file*)) - -(defun save-history () - (readline:write-history *history-file*)) - -;; Setup history -(when use-readline-p - (load-history)) - -(defun raw-input (prompt) - (format *standard-output* prompt) - (force-output *standard-output*) - (read-line *standard-input* nil)) - -(defun mal-readline (prompt) - (let ((input (if use-readline-p - (readline:readline prompt) - (raw-input prompt)))) - (when (and use-readline-p - input - (not (zerop (length input)))) - (readline:add-history input)) - input)) - -(defun mal-writeline (string) - (when string - (write-line string))) - -(defun main () - (loop do (let ((line (mal-readline "user> "))) - (if line - (mal-writeline (rep line)) - (return)))) - (when use-readline-p - (save-history))) - -;; Do not start REPL inside Emacs -(unless (member :swank *features*) - (main)) diff --git a/clisp/step1_read_print.lisp b/clisp/step1_read_print.lisp deleted file mode 100644 index f1d2bb9f6e..0000000000 --- a/clisp/step1_read_print.lisp +++ /dev/null @@ -1,76 +0,0 @@ -(require "dependencies") - -(defpackage :mal - (:use :common-lisp - :readline - :reader - :printer)) - -(in-package :mal) - -(defun mal-read (string) - (reader:read-str string)) - -(defun mal-eval (ast env) - ast) - -(defun mal-print (expression) - (printer:pr-str expression)) - -(defun rep (string) - (handler-case - (mal-print (mal-eval (mal-read string) - nil)) - (reader:eof (condition) - (format nil - "~a" - condition)))) - -;; Readline setup -;;; The test runner sets this environment variable, in which case we do -;;; use readline since tests do not work with the readline interface -(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) - -(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) - ".mal-clisp-history"))) - -(defun load-history () - (readline:read-history *history-file*)) - -(defun save-history () - (readline:write-history *history-file*)) - -;; Setup history -(when use-readline-p - (load-history)) - -(defun raw-input (prompt) - (format *standard-output* prompt) - (force-output *standard-output*) - (read-line *standard-input* nil)) - -(defun mal-readline (prompt) - (let ((input (if use-readline-p - (readline:readline prompt) - (raw-input prompt)))) - (when (and use-readline-p - input - (not (zerop (length input)))) - (readline:add-history input)) - input)) - -(defun mal-writeline (string) - (when string - (write-line string))) - -(defun main () - (loop do (let ((line (mal-readline "user> "))) - (if line - (mal-writeline (rep line)) - (return)))) - (when use-readline-p - (save-history))) - -;; Do not start REPL inside Emacs -(unless (member :swank *features*) - (main)) diff --git a/clisp/step2_eval.lisp b/clisp/step2_eval.lisp deleted file mode 100644 index 148b566398..0000000000 --- a/clisp/step2_eval.lisp +++ /dev/null @@ -1,145 +0,0 @@ -(require "dependencies") - -(defpackage :mal - (:use :common-lisp - :readline - :types - :env - :reader - :printer)) - -(in-package :mal) - -;; Environment - -(defvar *repl-env* (make-hash-table :test 'types:mal-value=)) - -(setf (gethash (types:make-mal-symbol "+") *repl-env*) - (types:make-mal-builtin-fn (lambda (value1 value2) - (apply-unwrapped-values '+ - value1 - value2)))) - -(setf (gethash (types:make-mal-symbol "-") *repl-env*) - (types:make-mal-builtin-fn (lambda (value1 value2) - (apply-unwrapped-values '- - value1 - value2)))) - -(setf (gethash (types:make-mal-symbol "*") *repl-env*) - (types:make-mal-builtin-fn (lambda (value1 value2) - (apply-unwrapped-values '* - value1 - value2)))) - -(setf (gethash (types:make-mal-symbol "/") *repl-env*) - (types:make-mal-builtin-fn (lambda (value1 value2) - (apply-unwrapped-values '/ - value1 - value2)))) - -(defun lookup-env (symbol env) - (let ((value (gethash symbol env))) - (if value - value - (error 'env:undefined-symbol - :symbol (format nil "~a" (types:mal-data-value symbol)))))) - -(defun mal-read (string) - (reader:read-str string)) - -(defun mal-eval (ast env) - (cond - ((not (types:mal-list-p ast)) (eval-ast ast env)) - ((zerop (length (mal-data-value ast))) ast) - (t (progn - (let ((evaluated-list (eval-ast ast env))) - (apply (mal-data-value (car evaluated-list)) - (cdr evaluated-list))))))) - -(defun mal-print (expression) - (printer:pr-str expression)) - -(defun eval-sequence (sequence env) - (map 'list - (lambda (ast) (mal-eval ast env)) - (mal-data-value sequence))) - -(defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-data-value hash-map)) - (new-hash-table (make-hash-table :test 'types:mal-value=))) - (loop - for key being the hash-keys of hash-map-value - do (setf (gethash key new-hash-table) - (mal-eval (gethash key hash-map-value) env))) - (make-mal-hash-map new-hash-table))) - -(defun eval-ast (ast env) - (switch-mal-type ast - (types:symbol (lookup-env ast env)) - (types:list (eval-sequence ast env)) - (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) - (types:hash-map (eval-hash-map ast env )) - (types:any ast))) - -(defun rep (string) - (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) - (reader:eof (condition) - (format nil - "~a" - condition)) - (env:undefined-symbol (condition) - (format nil - "~a" - condition)))) - -;; Readline setup -;;; The test runner sets this environment variable, in which case we do -;;; use readline since tests do not work with the readline interface -(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) - -(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) - ".mal-clisp-history"))) - -(defun load-history () - (readline:read-history *history-file*)) - -(defun save-history () - (readline:write-history *history-file*)) - -;; Setup history -(when use-readline-p - (load-history)) - -(defun raw-input (prompt) - (format *standard-output* prompt) - (force-output *standard-output*) - (read-line *standard-input* nil)) - -(defun mal-readline (prompt) - (let ((input (if use-readline-p - (readline:readline prompt) - (raw-input prompt)))) - (when (and use-readline-p - input - (not (zerop (length input)))) - (readline:add-history input)) - input)) - -(defun mal-writeline (string) - (when string - (write-line string))) - -(defun main () - (loop do (let ((line (mal-readline "user> "))) - (if line - (mal-writeline (rep line)) - (return)))) - (when use-readline-p - (save-history))) - -;; Do not start REPL inside Emacs -(unless (member :swank *features*) - (main)) diff --git a/clisp/step3_env.lisp b/clisp/step3_env.lisp deleted file mode 100644 index a3aed4a7ea..0000000000 --- a/clisp/step3_env.lisp +++ /dev/null @@ -1,164 +0,0 @@ -(require "dependencies") - -(defpackage :mal - (:use :common-lisp - :readline - :types - :env - :reader - :printer)) - -(in-package :mal) - -(defvar *repl-env* (env:create-mal-env)) - -(set-env *repl-env* - (types:make-mal-symbol "+") - (types:make-mal-builtin-fn (lambda (value1 value2) - (apply-unwrapped-values '+ value1 value2)))) - -(set-env *repl-env* - (types:make-mal-symbol "-") - (types:make-mal-builtin-fn (lambda (value1 value2) - (apply-unwrapped-values '- value1 value2)))) - -(set-env *repl-env* - (types:make-mal-symbol "*") - (types:make-mal-builtin-fn (lambda (value1 value2) - (apply-unwrapped-values '* value1 value2)))) - -(set-env *repl-env* - (types:make-mal-symbol "/") - (types:make-mal-builtin-fn (lambda (value1 value2) - (apply-unwrapped-values '/ value1 value2)))) - -(defvar mal-def! (make-mal-symbol "def!")) -(defvar mal-let* (make-mal-symbol "let*")) - -(defun eval-sequence (sequence env) - (map 'list - (lambda (ast) (mal-eval ast env)) - (mal-data-value sequence))) - -(defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-data-value hash-map)) - (new-hash-table (make-hash-table :test 'types:mal-value=))) - (loop - for key being the hash-keys of hash-map-value - do (setf (gethash key new-hash-table) - (mal-eval (gethash key hash-map-value) env))) - (make-mal-hash-map new-hash-table))) - -(defun eval-ast (ast env) - (switch-mal-type ast - (types:symbol (env:get-env env ast)) - (types:list (eval-sequence ast env)) - (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) - (types:hash-map (eval-hash-map ast env )) - (types:any ast))) - -(defun eval-let* (forms env) - (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (types:mal-data-value (second forms))))) - - (mapcar (lambda (binding) - (env:set-env new-env - (car binding) - (mal-eval (or (cdr binding) - types:mal-nil) - new-env))) - (loop - for (symbol value) on bindings - by #'cddr - collect (cons symbol value))) - - (mal-eval (third forms) new-env))) - -(defun eval-list (ast env) - (let ((forms (mal-data-value ast))) - (cond - ((mal-value= mal-def! (first forms)) - (env:set-env env (second forms) (mal-eval (third forms) env))) - ((mal-value= mal-let* (first forms)) - (eval-let* forms env)) - (t (let ((evaluated-list (eval-ast ast env))) - (apply (types:mal-data-value (car evaluated-list)) - (cdr evaluated-list))))))) - -(defun mal-read (string) - (reader:read-str string)) - -(defun mal-eval (ast env) - (cond - ((null ast) types:mal-nil) - ((not (types:mal-list-p ast)) (eval-ast ast env)) - ((zerop (length (mal-data-value ast))) ast) - (t (eval-list ast env)))) - -(defun mal-print (expression) - (printer:pr-str expression)) - -(defun rep (string) - (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) - (reader:eof (condition) - (format nil - "~a" - condition)) - (env:undefined-symbol (condition) - (format nil - "~a" - condition)))) - -;; Readline setup -;;; The test runner sets this environment variable, in which case we do -;;; use readline since tests do not work with the readline interface -(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) - -(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) - ".mal-clisp-history"))) - -(defun load-history () - (readline:read-history *history-file*)) - -(defun save-history () - (readline:write-history *history-file*)) - -;; Setup history -(when use-readline-p - (load-history)) - -(defun raw-input (prompt) - (format *standard-output* prompt) - (force-output *standard-output*) - (read-line *standard-input* nil)) - -(defun mal-readline (prompt) - (let ((input (if use-readline-p - (readline:readline prompt) - (raw-input prompt)))) - (when (and use-readline-p - input - (not (zerop (length input)))) - (readline:add-history input)) - input)) - -(defun mal-writeline (string) - (when string - (write-line string))) - -(defun main () - (loop do (let ((line (mal-readline "user> "))) - (if line - (mal-writeline (rep line)) - (return)))) - (when use-readline-p - (save-history))) - -;; Do not start REPL inside Emacs -(unless (member :swank *features*) - (main)) diff --git a/clisp/step4_if_fn_do.lisp b/clisp/step4_if_fn_do.lisp deleted file mode 100644 index 0d9657191d..0000000000 --- a/clisp/step4_if_fn_do.lisp +++ /dev/null @@ -1,180 +0,0 @@ -(require "dependencies") - -(defpackage :mal - (:use :common-lisp - :readline - :types - :env - :reader - :printer - :core)) - -(in-package :mal) - -(defvar *repl-env* (env:create-mal-env)) - -(dolist (binding core:ns) - (env:set-env *repl-env* - (car binding) - (cdr binding))) - -(defvar mal-def! (make-mal-symbol "def!")) -(defvar mal-let* (make-mal-symbol "let*")) -(defvar mal-do (make-mal-symbol "do")) -(defvar mal-if (make-mal-symbol "if")) -(defvar mal-fn* (make-mal-symbol "fn*")) - -(defun eval-sequence (sequence env) - (map 'list - (lambda (ast) (mal-eval ast env)) - (mal-data-value sequence))) - -(defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-data-value hash-map)) - (new-hash-table (make-hash-table :test 'types:mal-value=))) - (loop - for key being the hash-keys of hash-map-value - do (setf (gethash key new-hash-table) - (mal-eval (gethash key hash-map-value) env))) - (make-mal-hash-map new-hash-table))) - -(defun eval-ast (ast env) - (switch-mal-type ast - (types:symbol (env:get-env env ast)) - (types:list (eval-sequence ast env)) - (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) - (types:hash-map (eval-hash-map ast env)) - (types:any ast))) - -(defun eval-let* (forms env) - (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (mal-data-value (second forms))))) - - (mapcar (lambda (binding) - (env:set-env new-env - (car binding) - (mal-eval (or (cdr binding) - types:mal-nil) - new-env))) - (loop - for (symbol value) on bindings - by #'cddr - collect (cons symbol value))) - - (mal-eval (third forms) new-env))) - -(defun eval-list (ast env) - (let ((forms (mal-data-value ast))) - (cond - ((mal-value= mal-def! (first forms)) - (env:set-env env (second forms) (mal-eval (third forms) env))) - ((mal-value= mal-let* (first forms)) - (eval-let* forms env)) - ((mal-value= mal-do (first forms)) - (car (last (mapcar (lambda (form) (mal-eval form env)) - (cdr forms))))) - ((mal-value= mal-if (first forms)) - (let ((predicate (mal-eval (second forms) env))) - (mal-eval (if (or (mal-value= predicate types:mal-nil) - (mal-value= predicate types:mal-false)) - (fourth forms) - (third forms)) - env))) - ((mal-value= mal-fn* (first forms)) - (types:make-mal-fn (let ((arglist (second forms)) - (body (third forms))) - (lambda (&rest args) - (mal-eval body (env:create-mal-env :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args)))))) - (t (let* ((evaluated-list (eval-ast ast env)) - (function (car evaluated-list))) - ;; If first element is a mal function unwrap it - (apply (mal-data-value function) - (cdr evaluated-list))))))) - -(defun mal-read (string) - (reader:read-str string)) - -(defun mal-eval (ast env) - (cond - ((null ast) types:mal-nil) - ((not (types:mal-list-p ast)) (eval-ast ast env)) - ((zerop (length (mal-data-value ast))) ast) - (t (eval-list ast env)))) - -(defun mal-print (expression) - (printer:pr-str expression)) - -(defun rep (string) - (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) - (reader:eof (condition) - (format nil - "~a" - condition)) - (env:undefined-symbol (condition) - (format nil - "~a" - condition)) - (error (condition) - (format nil - "~a" - condition)))) - -(rep "(def! not (fn* (a) (if a false true)))") - -;; Readline setup -;;; The test runner sets this environment variable, in which case we do -;;; use readline since tests do not work with the readline interface -(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) - -(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) - ".mal-clisp-history"))) - -(defun load-history () - (readline:read-history *history-file*)) - -(defun save-history () - (readline:write-history *history-file*)) - -;; Setup history -(when use-readline-p - (load-history)) - -(defun raw-input (prompt) - (format *standard-output* prompt) - (force-output *standard-output*) - (read-line *standard-input* nil)) - -(defun mal-readline (prompt) - (let ((input (if use-readline-p - (readline:readline prompt) - (raw-input prompt)))) - (when (and use-readline-p - input - (not (zerop (length input)))) - (readline:add-history input)) - input)) - -(defun mal-writeline (string) - (when string - (write-line string))) - -(defun main () - (loop do (let ((line (mal-readline "user> "))) - (if line - (mal-writeline (rep line)) - (return)))) - (when use-readline-p - (save-history))) - -;; Do not start REPL inside Emacs -(unless (member :swank *features*) - (main)) diff --git a/clisp/step5_tco.lisp b/clisp/step5_tco.lisp deleted file mode 100644 index d25e1d0306..0000000000 --- a/clisp/step5_tco.lisp +++ /dev/null @@ -1,191 +0,0 @@ -(require "dependencies") - -(defpackage :mal - (:use :common-lisp - :readline - :types - :env - :reader - :printer - :core)) - -(in-package :mal) - -(defvar *repl-env* (env:create-mal-env)) - -(dolist (binding core:ns) - (env:set-env *repl-env* - (car binding) - (cdr binding))) - -(defvar mal-def! (make-mal-symbol "def!")) -(defvar mal-let* (make-mal-symbol "let*")) -(defvar mal-do (make-mal-symbol "do")) -(defvar mal-if (make-mal-symbol "if")) -(defvar mal-fn* (make-mal-symbol "fn*")) - -(defun eval-sequence (sequence env) - (map 'list - (lambda (ast) (mal-eval ast env)) - (mal-data-value sequence))) - -(defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-data-value hash-map)) - (new-hash-table (make-hash-table :test 'types:mal-value=))) - (loop - for key being the hash-keys of hash-map-value - do (setf (gethash key new-hash-table) - (mal-eval (gethash key hash-map-value) env))) - (make-mal-hash-map new-hash-table))) - -(defun eval-ast (ast env) - (switch-mal-type ast - (types:symbol (env:get-env env ast)) - (types:list (eval-sequence ast env)) - (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) - (types:hash-map (eval-hash-map ast env)) - (types:any ast))) - -(defun mal-read (string) - (reader:read-str string)) - -(defun mal-eval (ast env) - (loop - do (cond - ((null ast) (return types:mal-nil)) - ((not (types:mal-list-p ast)) (return (eval-ast ast env))) - ((zerop (length (mal-data-value ast))) (return ast)) - (t (let ((forms (mal-data-value ast))) - (cond - ((mal-value= mal-def! (first forms)) - (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - - ((mal-value= mal-let* (first forms)) - (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (mal-data-value (second forms))))) - - (mapcar (lambda (binding) - (env:set-env new-env - (car binding) - (mal-eval (or (cdr binding) - types:mal-nil) - new-env))) - (loop - for (symbol value) on bindings - by #'cddr - collect (cons symbol value))) - (setf ast (third forms) - env new-env))) - - ((mal-value= mal-do (first forms)) - (mapc (lambda (form) (mal-eval form env)) - (butlast (cdr forms))) - (setf ast (car (last forms)))) - - ((mal-value= mal-if (first forms)) - (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (mal-value= predicate types:mal-nil) - (mal-value= predicate types:mal-false)) - (fourth forms) - (third forms))))) - - ((mal-value= mal-fn* (first forms)) - (return (let ((arglist (second forms)) - (body (third forms))) - (types:make-mal-fn (lambda (&rest args) - (mal-eval body (env:create-mal-env :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env)))))) - - (t (let* ((evaluated-list (eval-ast ast env)) - (function (car evaluated-list))) - ;; If first element is a mal function unwrap it - (if (not (types:mal-fn-p function)) - (return (apply (mal-data-value function) - (cdr evaluated-list))) - (let* ((attrs (types:mal-data-attrs function))) - (setf ast (cdr (assoc 'ast attrs)) - env (env:create-mal-env :parent (cdr (assoc 'env attrs)) - :binds (map 'list - #'identity - (mal-data-value (cdr (assoc 'params attrs)))) - :exprs (cdr evaluated-list))))))))))))) - -(defun mal-print (expression) - (printer:pr-str expression)) - -(defun rep (string) - (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) - (reader:eof (condition) - (format nil - "~a" - condition)) - (env:undefined-symbol (condition) - (format nil - "~a" - condition)) - (error (condition) - (format nil - "~a" - condition)))) - -(rep "(def! not (fn* (a) (if a false true)))") - -;; Readline setup -;;; The test runner sets this environment variable, in which case we do -;;; use readline since tests do not work with the readline interface -(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) - -(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) - ".mal-clisp-history"))) - -(defun load-history () - (readline:read-history *history-file*)) - -(defun save-history () - (readline:write-history *history-file*)) - -;; Setup history -(when use-readline-p - (load-history)) - -(defun raw-input (prompt) - (format *standard-output* prompt) - (force-output *standard-output*) - (read-line *standard-input* nil)) - -(defun mal-readline (prompt) - (let ((input (if use-readline-p - (readline:readline prompt) - (raw-input prompt)))) - (when (and use-readline-p - input - (not (zerop (length input)))) - (readline:add-history input)) - input)) - -(defun mal-writeline (string) - (when string - (write-line string))) - -(defun main () - (loop do (let ((line (mal-readline "user> "))) - (if line - (mal-writeline (rep line)) - (return)))) - (when use-readline-p - (save-history))) - -;; Do not start REPL inside Emacs -(unless (member :swank *features*) - (main)) diff --git a/clisp/step6_file.lisp b/clisp/step6_file.lisp deleted file mode 100644 index 095f664a5e..0000000000 --- a/clisp/step6_file.lisp +++ /dev/null @@ -1,210 +0,0 @@ -(require "dependencies") - -(defpackage :mal - (:use :common-lisp - :readline - :types - :env - :reader - :printer - :core)) - -(in-package :mal) - -(defvar *repl-env* (env:create-mal-env)) - -(dolist (binding core:ns) - (env:set-env *repl-env* - (car binding) - (cdr binding))) - -(defvar mal-def! (make-mal-symbol "def!")) -(defvar mal-let* (make-mal-symbol "let*")) -(defvar mal-do (make-mal-symbol "do")) -(defvar mal-if (make-mal-symbol "if")) -(defvar mal-fn* (make-mal-symbol "fn*")) - -(env:set-env *repl-env* - (types:make-mal-symbol "eval") - (types:make-mal-builtin-fn (lambda (ast) - (mal-eval ast *repl-env*)))) - -(defun eval-sequence (sequence env) - (map 'list - (lambda (ast) (mal-eval ast env)) - (mal-data-value sequence))) - -(defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-data-value hash-map)) - (new-hash-table (make-hash-table :test 'types:mal-value=))) - (loop - for key being the hash-keys of hash-map-value - do (setf (gethash (mal-eval key env) new-hash-table) - (mal-eval (gethash key hash-map-value) env))) - (make-mal-hash-map new-hash-table))) - -(defun eval-ast (ast env) - (switch-mal-type ast - (types:symbol (env:get-env env ast)) - (types:list (eval-sequence ast env)) - (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) - (types:hash-map (eval-hash-map ast env)) - (types:any ast))) - -(defun mal-read (string) - (reader:read-str string)) - -(defun mal-eval (ast env) - (loop - do (cond - ((null ast) (return types:mal-nil)) - ((not (types:mal-list-p ast)) (return (eval-ast ast env))) - ((zerop (length (mal-data-value ast))) (return ast)) - (t (let ((forms (mal-data-value ast))) - (cond - ((mal-value= mal-def! (first forms)) - (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - - ((mal-value= mal-let* (first forms)) - (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (mal-data-value (second forms))))) - - (mapcar (lambda (binding) - (env:set-env new-env - (car binding) - (mal-eval (or (cdr binding) - types:mal-nil) - new-env))) - (loop - for (symbol value) on bindings - by #'cddr - collect (cons symbol value))) - (setf ast (third forms) - env new-env))) - - ((mal-value= mal-do (first forms)) - (mapc (lambda (form) (mal-eval form env)) - (butlast (cdr forms))) - (setf ast (car (last forms)))) - - ((mal-value= mal-if (first forms)) - (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (mal-value= predicate types:mal-nil) - (mal-value= predicate types:mal-false)) - (fourth forms) - (third forms))))) - - ((mal-value= mal-fn* (first forms)) - (return (let ((arglist (second forms)) - (body (third forms))) - (types:make-mal-fn (lambda (&rest args) - (mal-eval body (env:create-mal-env :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env)))))) - - (t (let* ((evaluated-list (eval-ast ast env)) - (function (car evaluated-list))) - ;; If first element is a mal function unwrap it - (if (not (types:mal-fn-p function)) - (return (apply (mal-data-value function) - (cdr evaluated-list))) - (let* ((attrs (types:mal-data-attrs function))) - (setf ast (cdr (assoc 'ast attrs)) - env (env:create-mal-env :parent (cdr (assoc 'env attrs)) - :binds (map 'list - #'identity - (mal-data-value (cdr (assoc 'params attrs)))) - :exprs (cdr evaluated-list))))))))))))) - -(defun mal-print (expression) - (printer:pr-str expression)) - -(defun rep (string) - (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) - (reader:eof (condition) - (format nil - "~a" - condition)) - (env:undefined-symbol (condition) - (format nil - "~a" - condition)) - (error (condition) - (format nil - "~a" - condition)))) - -(rep "(def! not (fn* (a) (if a false true)))") -(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") -(rep "(def! *ARGV* (list))") - -(env:set-env *repl-env* - (types:make-mal-symbol "*ARGV*") - (types:wrap-value (cdr common-lisp-user::*args*) - :listp t)) - -;; Readline setup -;;; The test runner sets this environment variable, in which case we do -;;; use readline since tests do not work with the readline interface -(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) - -(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) - ".mal-clisp-history"))) - -(defun load-history () - (readline:read-history *history-file*)) - -(defun save-history () - (readline:write-history *history-file*)) - -;; Setup history -(when use-readline-p - (load-history)) - -(defun raw-input (prompt) - (format *standard-output* prompt) - (force-output *standard-output*) - (read-line *standard-input* nil)) - -(defun mal-readline (prompt) - (let ((input (if use-readline-p - (readline:readline prompt) - (raw-input prompt)))) - (when (and use-readline-p - input - (not (zerop (length input)))) - (readline:add-history input)) - input)) - -(defun mal-writeline (string) - (when string - (write-line string))) - -(defun repl () - (loop do (let ((line (mal-readline "user> "))) - (if line - (mal-writeline (rep line)) - (return)))) - (when use-readline-p - (save-history))) - -(defun main () - (if (null common-lisp-user::*args*) - ;; Do not start REPL inside Emacs - (unless (member :swank *features*) - (repl)) - (rep (format nil - "(load-file \"~a\")" - (car common-lisp-user::*args*))))) - -(main) diff --git a/clisp/step7_quote.lisp b/clisp/step7_quote.lisp deleted file mode 100644 index d73e4f0f20..0000000000 --- a/clisp/step7_quote.lisp +++ /dev/null @@ -1,248 +0,0 @@ -(require "dependencies") - -(defpackage :mal - (:use :common-lisp - :readline - :types - :env - :reader - :printer - :core)) - -(in-package :mal) - -(defvar *repl-env* (env:create-mal-env)) - -(dolist (binding core:ns) - (env:set-env *repl-env* - (car binding) - (cdr binding))) - -(defvar mal-quote (make-mal-symbol "quote")) -(defvar mal-quasiquote (make-mal-symbol "quasiquote")) -(defvar mal-unquote (make-mal-symbol "unquote")) -(defvar mal-splice-unquote (make-mal-symbol "splice-unquote")) -(defvar mal-cons (make-mal-symbol "cons")) -(defvar mal-concat (make-mal-symbol "concat")) -(defvar mal-def! (make-mal-symbol "def!")) -(defvar mal-let* (make-mal-symbol "let*")) -(defvar mal-do (make-mal-symbol "do")) -(defvar mal-if (make-mal-symbol "if")) -(defvar mal-fn* (make-mal-symbol "fn*")) - -(env:set-env *repl-env* - (types:make-mal-symbol "eval") - (types:make-mal-builtin-fn (lambda (ast) - (mal-eval ast *repl-env*)))) - -(defun eval-sequence (sequence env) - (map 'list - (lambda (ast) (mal-eval ast env)) - (mal-data-value sequence))) - -(defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-data-value hash-map)) - (new-hash-table (make-hash-table :test 'types:mal-value=))) - (loop - for key being the hash-keys of hash-map-value - do (setf (gethash (mal-eval key env) new-hash-table) - (mal-eval (gethash key hash-map-value) env))) - (make-mal-hash-map new-hash-table))) - -(defun eval-ast (ast env) - (switch-mal-type ast - (types:symbol (env:get-env env ast)) - (types:list (eval-sequence ast env)) - (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) - (types:hash-map (eval-hash-map ast env)) - (types:any ast))) - - -(defun is-pair (value) - (and (or (mal-list-p value) - (mal-vector-p value)) - (not (zerop (length (mal-data-value value)))))) - -(defun quasiquote (ast) - (if (not (is-pair ast)) - (types:make-mal-list (list mal-quote - ast)) - (let ((forms (map 'list #'identity (mal-data-value ast)))) - (cond - ((mal-value= mal-unquote (first forms)) - (second forms)) - - ((and (is-pair (first forms)) - (mal-value= mal-splice-unquote - (first (mal-data-value (first forms))))) - (types:make-mal-list (list mal-concat - (second (mal-data-value (first forms))) - (quasiquote (make-mal-list (cdr forms)))))) - - (t (types:make-mal-list (list mal-cons - (quasiquote (first forms)) - (quasiquote (make-mal-list (cdr forms)))))))))) - -(defun mal-read (string) - (reader:read-str string)) - -(defun mal-eval (ast env) - (loop - do (cond - ((null ast) (return types:mal-nil)) - ((not (types:mal-list-p ast)) (return (eval-ast ast env))) - ((zerop (length (mal-data-value ast))) (return ast)) - (t (let ((forms (mal-data-value ast))) - (cond - ((mal-value= mal-quote (first forms)) - (return (second forms))) - - ((mal-value= mal-quasiquote (first forms)) - (setf ast (quasiquote (second forms)))) - - ((mal-value= mal-def! (first forms)) - (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - - ((mal-value= mal-let* (first forms)) - (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (mal-data-value (second forms))))) - - (mapcar (lambda (binding) - (env:set-env new-env - (car binding) - (mal-eval (or (cdr binding) - types:mal-nil) - new-env))) - (loop - for (symbol value) on bindings - by #'cddr - collect (cons symbol value))) - (setf ast (third forms) - env new-env))) - - ((mal-value= mal-do (first forms)) - (mapc (lambda (form) (mal-eval form env)) - (butlast (cdr forms))) - (setf ast (car (last forms)))) - - ((mal-value= mal-if (first forms)) - (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (mal-value= predicate types:mal-nil) - (mal-value= predicate types:mal-false)) - (fourth forms) - (third forms))))) - - ((mal-value= mal-fn* (first forms)) - (return (let ((arglist (second forms)) - (body (third forms))) - (types:make-mal-fn (lambda (&rest args) - (mal-eval body (env:create-mal-env :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env)))))) - - (t (let* ((evaluated-list (eval-ast ast env)) - (function (car evaluated-list))) - ;; If first element is a mal function unwrap it - (if (not (types:mal-fn-p function)) - (return (apply (mal-data-value function) - (cdr evaluated-list))) - (let* ((attrs (types:mal-data-attrs function))) - (setf ast (cdr (assoc 'ast attrs)) - env (env:create-mal-env :parent (cdr (assoc 'env attrs)) - :binds (map 'list - #'identity - (mal-data-value (cdr (assoc 'params attrs)))) - :exprs (cdr evaluated-list))))))))))))) - -(defun mal-print (expression) - (printer:pr-str expression)) - -(defun rep (string) - (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) - (reader:eof (condition) - (format nil - "~a" - condition)) - (env:undefined-symbol (condition) - (format nil - "~a" - condition)) - (error (condition) - (format nil - "~a" - condition)))) - -(rep "(def! not (fn* (a) (if a false true)))") -(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") -(rep "(def! *ARGV* (list))") - -(env:set-env *repl-env* - (types:make-mal-symbol "*ARGV*") - (types:wrap-value (cdr common-lisp-user::*args*) - :listp t)) - -;; Readline setup -;;; The test runner sets this environment variable, in which case we do -;;; use readline since tests do not work with the readline interface -(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) - -(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) - ".mal-clisp-history"))) - -(defun load-history () - (readline:read-history *history-file*)) - -(defun save-history () - (readline:write-history *history-file*)) - -;; Setup history -(when use-readline-p - (load-history)) - -(defun raw-input (prompt) - (format *standard-output* prompt) - (force-output *standard-output*) - (read-line *standard-input* nil)) - -(defun mal-readline (prompt) - (let ((input (if use-readline-p - (readline:readline prompt) - (raw-input prompt)))) - (when (and use-readline-p - input - (not (zerop (length input)))) - (readline:add-history input)) - input)) - -(defun mal-writeline (string) - (when string - (write-line string))) - -(defun repl () - (loop do (let ((line (mal-readline "user> "))) - (if line - (mal-writeline (rep line)) - (return)))) - (when use-readline-p - (save-history))) - -(defun main () - (if (null common-lisp-user::*args*) - ;; Do not start REPL inside Emacs - (unless (member :swank *features*) - (repl)) - (rep (format nil - "(load-file \"~a\")" - (car common-lisp-user::*args*))))) - -(main) diff --git a/clisp/step8_macros.lisp b/clisp/step8_macros.lisp deleted file mode 100644 index 6264e30769..0000000000 --- a/clisp/step8_macros.lisp +++ /dev/null @@ -1,298 +0,0 @@ -(require "dependencies") - -(defpackage :mal - (:use :common-lisp - :readline - :types - :env - :reader - :printer - :core)) - -(in-package :mal) - -(define-condition invalid-function (types:mal-error) - ((form :initarg :form :reader form) - (context :initarg :context :reader context)) - (:report (lambda (condition stream) - (format stream - "Invalid function '~a' provided while ~a" - (printer:pr-str (form condition)) - (if (string= (context condition) "apply") - "applying" - "defining macro"))))) - -(defvar *repl-env* (env:create-mal-env)) - -(dolist (binding core:ns) - (env:set-env *repl-env* - (car binding) - (cdr binding))) - -(defvar mal-quote (make-mal-symbol "quote")) -(defvar mal-quasiquote (make-mal-symbol "quasiquote")) -(defvar mal-unquote (make-mal-symbol "unquote")) -(defvar mal-splice-unquote (make-mal-symbol "splice-unquote")) -(defvar mal-cons (make-mal-symbol "cons")) -(defvar mal-concat (make-mal-symbol "concat")) -(defvar mal-macroexpand (make-mal-symbol "macroexpand")) -(defvar mal-def! (make-mal-symbol "def!")) -(defvar mal-defmacro! (make-mal-symbol "defmacro!")) -(defvar mal-let* (make-mal-symbol "let*")) -(defvar mal-do (make-mal-symbol "do")) -(defvar mal-if (make-mal-symbol "if")) -(defvar mal-fn* (make-mal-symbol "fn*")) - -(env:set-env *repl-env* - (types:make-mal-symbol "eval") - (types:make-mal-builtin-fn (lambda (ast) - (mal-eval ast *repl-env*)))) - -(defun eval-sequence (sequence env) - (map 'list - (lambda (ast) (mal-eval ast env)) - (mal-data-value sequence))) - -(defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-data-value hash-map)) - (new-hash-table (make-hash-table :test 'types:mal-value=))) - (loop - for key being the hash-keys of hash-map-value - do (setf (gethash (mal-eval key env) new-hash-table) - (mal-eval (gethash key hash-map-value) env))) - (make-mal-hash-map new-hash-table))) - -(defun eval-ast (ast env) - (switch-mal-type ast - (types:symbol (env:get-env env ast)) - (types:list (eval-sequence ast env)) - (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) - (types:hash-map (eval-hash-map ast env)) - (types:any ast))) - -(defun is-pair (value) - (and (or (mal-list-p value) - (mal-vector-p value)) - (not (zerop (length (mal-data-value value)))))) - -(defun quasiquote (ast) - (if (not (is-pair ast)) - (types:make-mal-list (list mal-quote - ast)) - (let ((forms (map 'list #'identity (mal-data-value ast)))) - (cond - ((mal-value= mal-unquote (first forms)) - (second forms)) - - ((and (is-pair (first forms)) - (mal-value= mal-splice-unquote - (first (mal-data-value (first forms))))) - (types:make-mal-list (list mal-concat - (second (mal-data-value (first forms))) - (quasiquote (make-mal-list (cdr forms)))))) - - (t (types:make-mal-list (list mal-cons - (quasiquote (first forms)) - (quasiquote (make-mal-list (cdr forms)))))))))) - -(defun is-macro-call (ast env) - (when (and (types:mal-list-p ast) - (not (zerop (length (mal-data-value ast))))) - (let* ((func-symbol (first (mal-data-value ast))) - (func (when (types:mal-symbol-p func-symbol) - (env:find-env env func-symbol)))) - (and func - (types:mal-fn-p func) - (cdr (assoc 'is-macro (types:mal-data-attrs func))))))) - -(defun mal-macroexpand (ast env) - (loop - while (is-macro-call ast env) - do (let* ((forms (types:mal-data-value ast)) - (func (env:get-env env (first forms)))) - (setf ast (apply (mal-data-value func) - (cdr forms))))) - ast) - -(defun mal-eval (ast env) - (loop - do (setf ast (mal-macroexpand ast env)) - do (cond - ((null ast) (return types:mal-nil)) - ((not (types:mal-list-p ast)) (return (eval-ast ast env))) - ((zerop (length (mal-data-value ast))) (return ast)) - (t (let ((forms (mal-data-value ast))) - (cond - ((mal-value= mal-quote (first forms)) - (return (second forms))) - - ((mal-value= mal-quasiquote (first forms)) - (setf ast (quasiquote (second forms)))) - - ((mal-value= mal-macroexpand (first forms)) - (return (mal-macroexpand (second forms) env))) - - ((mal-value= mal-def! (first forms)) - (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - - ((mal-value= mal-defmacro! (first forms)) - (let ((value (mal-eval (third forms) env))) - (return (if (types:mal-fn-p value) - (env:set-env env - (second forms) - (progn - (setf (cdr (assoc 'is-macro (types:mal-data-attrs value))) t) - value)) - (error 'invalid-function - :form value - :context "macro"))))) - - ((mal-value= mal-let* (first forms)) - (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (mal-data-value (second forms))))) - - (mapcar (lambda (binding) - (env:set-env new-env - (car binding) - (mal-eval (or (cdr binding) - types:mal-nil) - new-env))) - (loop - for (symbol value) on bindings - by #'cddr - collect (cons symbol value))) - (setf ast (third forms) - env new-env))) - - ((mal-value= mal-do (first forms)) - (mapc (lambda (form) (mal-eval form env)) - (butlast (cdr forms))) - (setf ast (car (last forms)))) - - ((mal-value= mal-if (first forms)) - (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (mal-value= predicate types:mal-nil) - (mal-value= predicate types:mal-false)) - (fourth forms) - (third forms))))) - - ((mal-value= mal-fn* (first forms)) - (return (let ((arglist (second forms)) - (body (third forms))) - (types:make-mal-fn (lambda (&rest args) - (mal-eval body (env:create-mal-env :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env) - (cons 'is-macro nil)))))) - - (t (let* ((evaluated-list (eval-ast ast env)) - (function (car evaluated-list))) - ;; If first element is a mal function unwrap it - (cond ((types:mal-fn-p function) - (let* ((attrs (types:mal-data-attrs function))) - (setf ast (cdr (assoc 'ast attrs)) - env (env:create-mal-env :parent (cdr (assoc 'env attrs)) - :binds (map 'list - #'identity - (mal-data-value (cdr (assoc 'params attrs)))) - :exprs (cdr evaluated-list))))) - ((types:mal-builtin-fn-p function) - (return (apply (mal-data-value function) - (cdr evaluated-list)))) - (t (error 'invalid-function - :form function - :context "apply"))))))))))) - -(defun mal-read (string) - (reader:read-str string)) - -(defun mal-print (expression) - (printer:pr-str expression)) - -(defun rep (string) - (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) - (types:mal-error (condition) - (format nil - "~a" - condition)) - (error (condition) - (format nil - "Internal error: ~a" - condition)))) - -(rep "(def! not (fn* (a) (if a false true)))") -(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") -(rep "(def! *ARGV* (list))") -(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") -(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") - -(env:set-env *repl-env* - (types:make-mal-symbol "*ARGV*") - (types:wrap-value (cdr common-lisp-user::*args*) - :listp t)) - -;; Readline setup -;;; The test runner sets this environment variable, in which case we do -;;; use readline since tests do not work with the readline interface -(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) - -(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) - ".mal-clisp-history"))) - -(defun load-history () - (readline:read-history *history-file*)) - -(defun save-history () - (readline:write-history *history-file*)) - -;; Setup history -(when use-readline-p - (load-history)) - -(defun raw-input (prompt) - (format *standard-output* prompt) - (force-output *standard-output*) - (read-line *standard-input* nil)) - -(defun mal-readline (prompt) - (let ((input (if use-readline-p - (readline:readline prompt) - (raw-input prompt)))) - (when (and use-readline-p - input - (not (zerop (length input)))) - (readline:add-history input)) - input)) - -(defun mal-writeline (string) - (when string - (write-line string))) - -(defun repl () - (loop do (let ((line (mal-readline "user> "))) - (if line - (mal-writeline (rep line)) - (return)))) - (when use-readline-p - (save-history))) - -(defun main () - (if (null common-lisp-user::*args*) - ;; Do not start REPL inside Emacs - (unless (member :swank *features*) - (repl)) - (rep (format nil - "(load-file \"~a\")" - (car common-lisp-user::*args*))))) - -(main) diff --git a/clisp/step9_try.lisp b/clisp/step9_try.lisp deleted file mode 100644 index 2066e95b46..0000000000 --- a/clisp/step9_try.lisp +++ /dev/null @@ -1,324 +0,0 @@ -(require "dependencies") - -(defpackage :mal - (:use :common-lisp - :readline - :types - :env - :reader - :printer - :core)) - -(in-package :mal) - -(define-condition invalid-function (types:mal-runtime-exception) - ((form :initarg :form :reader form) - (context :initarg :context :reader context)) - (:report (lambda (condition stream) - (format stream - "Invalid function '~a' provided while ~a" - (printer:pr-str (form condition)) - (if (string= (context condition) "apply") - "applying" - "defining macro"))))) - -(defvar *repl-env* (env:create-mal-env)) - -(dolist (binding core:ns) - (env:set-env *repl-env* - (car binding) - (cdr binding))) - -(env:set-env *repl-env* - (types:make-mal-symbol "eval") - (types:make-mal-builtin-fn (lambda (ast) - (mal-eval ast *repl-env*)))) - -(defvar mal-quote (make-mal-symbol "quote")) -(defvar mal-quasiquote (make-mal-symbol "quasiquote")) -(defvar mal-unquote (make-mal-symbol "unquote")) -(defvar mal-splice-unquote (make-mal-symbol "splice-unquote")) -(defvar mal-cons (make-mal-symbol "cons")) -(defvar mal-concat (make-mal-symbol "concat")) -(defvar mal-macroexpand (make-mal-symbol "macroexpand")) -(defvar mal-def! (make-mal-symbol "def!")) -(defvar mal-defmacro! (make-mal-symbol "defmacro!")) -(defvar mal-let* (make-mal-symbol "let*")) -(defvar mal-do (make-mal-symbol "do")) -(defvar mal-if (make-mal-symbol "if")) -(defvar mal-fn* (make-mal-symbol "fn*")) -(defvar mal-try* (make-mal-symbol "try*")) -(defvar mal-catch* (make-mal-symbol "catch*")) - -(defun eval-sequence (sequence env) - (map 'list - (lambda (ast) (mal-eval ast env)) - (mal-data-value sequence))) - -(defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-data-value hash-map)) - (new-hash-table (make-hash-table :test 'types:mal-value=))) - (loop - for key being the hash-keys of hash-map-value - do (setf (gethash (mal-eval key env) new-hash-table) - (mal-eval (gethash key hash-map-value) env))) - (make-mal-hash-map new-hash-table))) - -(defun eval-ast (ast env) - (switch-mal-type ast - (types:symbol (env:get-env env ast)) - (types:list (eval-sequence ast env)) - (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) - (types:hash-map (eval-hash-map ast env)) - (types:any ast))) - -(defun is-pair (value) - (and (or (mal-list-p value) - (mal-vector-p value)) - (not (zerop (length (mal-data-value value)))))) - -(defun quasiquote (ast) - (if (not (is-pair ast)) - (types:make-mal-list (list mal-quote - ast)) - (let ((forms (map 'list #'identity (mal-data-value ast)))) - (cond - ((mal-value= mal-unquote (first forms)) - (second forms)) - - ((and (is-pair (first forms)) - (mal-value= mal-splice-unquote - (first (mal-data-value (first forms))))) - (types:make-mal-list (list mal-concat - (second (mal-data-value (first forms))) - (quasiquote (make-mal-list (cdr forms)))))) - - (t (types:make-mal-list (list mal-cons - (quasiquote (first forms)) - (quasiquote (make-mal-list (cdr forms)))))))))) - -(defun is-macro-call (ast env) - (when (and (types:mal-list-p ast) - (not (zerop (length (mal-data-value ast))))) - (let* ((func-symbol (first (mal-data-value ast))) - (func (when (types:mal-symbol-p func-symbol) - (env:find-env env func-symbol)))) - (and func - (types:mal-fn-p func) - (cdr (assoc 'is-macro (types:mal-data-attrs func))))))) - -(defun mal-macroexpand (ast env) - (loop - while (is-macro-call ast env) - do (let* ((forms (types:mal-data-value ast)) - (func (env:get-env env (first forms)))) - (setf ast (apply (mal-data-value func) - (cdr forms))))) - ast) - -(defun mal-eval (ast env) - (loop - do (setf ast (mal-macroexpand ast env)) - do (cond - ((null ast) (return types:mal-nil)) - ((not (types:mal-list-p ast)) (return (eval-ast ast env))) - ((zerop (length (mal-data-value ast))) (return ast)) - (t (let ((forms (mal-data-value ast))) - (cond - ((mal-value= mal-quote (first forms)) - (return (second forms))) - - ((mal-value= mal-quasiquote (first forms)) - (setf ast (quasiquote (second forms)))) - - ((mal-value= mal-macroexpand (first forms)) - (return (mal-macroexpand (second forms) env))) - - ((mal-value= mal-def! (first forms)) - (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - - ((mal-value= mal-defmacro! (first forms)) - (let ((value (mal-eval (third forms) env))) - (return (if (types:mal-fn-p value) - (env:set-env env - (second forms) - (progn - (setf (cdr (assoc 'is-macro (types:mal-data-attrs value))) t) - value)) - (error 'invalid-function - :form value - :context "macro"))))) - - ((mal-value= mal-let* (first forms)) - (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (mal-data-value (second forms))))) - - (mapcar (lambda (binding) - (env:set-env new-env - (car binding) - (mal-eval (or (cdr binding) - types:mal-nil) - new-env))) - (loop - for (symbol value) on bindings - by #'cddr - collect (cons symbol value))) - (setf ast (third forms) - env new-env))) - - ((mal-value= mal-do (first forms)) - (mapc (lambda (form) (mal-eval form env)) - (butlast (cdr forms))) - (setf ast (car (last forms)))) - - ((mal-value= mal-if (first forms)) - (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (mal-value= predicate types:mal-nil) - (mal-value= predicate types:mal-false)) - (fourth forms) - (third forms))))) - - ((mal-value= mal-fn* (first forms)) - (return (let ((arglist (second forms)) - (body (third forms))) - (types:make-mal-fn (lambda (&rest args) - (mal-eval body (env:create-mal-env :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env) - (cons 'is-macro nil)))))) - - ((mal-value= mal-try* (first forms)) - (handler-case - (return (mal-eval (second forms) env)) - (types:mal-exception (condition) - (when (third forms) - (let ((catch-forms (types:mal-data-value (third forms)))) - (when (mal-value= mal-catch* - (first catch-forms)) - (return (mal-eval (third catch-forms) - (env:create-mal-env :parent env - :binds (list (second catch-forms)) - :exprs (list (if (typep condition 'types:mal-runtime-exception) - (types:make-mal-string (format nil "~a" condition)) - (types::mal-exception-data condition))))))))) - (error condition)))) - - (t (let* ((evaluated-list (eval-ast ast env)) - (function (car evaluated-list))) - ;; If first element is a mal function unwrap it - (cond ((types:mal-fn-p function) - (let* ((attrs (types:mal-data-attrs function))) - (setf ast (cdr (assoc 'ast attrs)) - env (env:create-mal-env :parent (cdr (assoc 'env attrs)) - :binds (map 'list - #'identity - (mal-data-value (cdr (assoc 'params attrs)))) - :exprs (cdr evaluated-list))))) - ((types:mal-builtin-fn-p function) - (return (apply (mal-data-value function) - (cdr evaluated-list)))) - (t (error 'invalid-function - :form function - :context "apply"))))))))))) - -(defun mal-read (string) - (reader:read-str string)) - -(defun mal-print (expression) - (printer:pr-str expression)) - -(defun rep (string) - (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) - (types:mal-error (condition) - (format nil - "Error: ~a" - condition)) - (types:mal-runtime-exception (condition) - (format nil - "Exception: ~a" - condition)) - (types:mal-user-exception (condition) - (format nil - "Exception: ~a" - (pr-str (types::mal-exception-data condition)))) - (error (condition) - (format nil - "Internal error: ~a" - condition)))) - -(rep "(def! not (fn* (a) (if a false true)))") -(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") -(rep "(def! *ARGV* (list))") -(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") -(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") - -(env:set-env *repl-env* - (types:make-mal-symbol "*ARGV*") - (types:wrap-value (cdr common-lisp-user::*args*) - :listp t)) - -;; Readline setup -;;; The test runner sets this environment variable, in which case we do -;;; use readline since tests do not work with the readline interface -(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) - -(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) - ".mal-clisp-history"))) - -(defun load-history () - (readline:read-history *history-file*)) - -(defun save-history () - (readline:write-history *history-file*)) - -;; Setup history -(when use-readline-p - (load-history)) - -(defun raw-input (prompt) - (format *standard-output* prompt) - (force-output *standard-output*) - (read-line *standard-input* nil)) - -(defun mal-readline (prompt) - (let ((input (if use-readline-p - (readline:readline prompt) - (raw-input prompt)))) - (when (and use-readline-p - input - (not (zerop (length input)))) - (readline:add-history input)) - input)) - -(defun mal-writeline (string) - (when string - (write-line string))) - -(defun repl () - (loop do (let ((line (mal-readline "user> "))) - (if line - (mal-writeline (rep line)) - (return)))) - (when use-readline-p - (save-history))) - -(defun main () - (if (null common-lisp-user::*args*) - ;; Do not start REPL inside Emacs - (unless (member :swank *features*) - (repl)) - (rep (format nil - "(load-file \"~a\")" - (car common-lisp-user::*args*))))) - -(main) diff --git a/clisp/stepA_mal.lisp b/clisp/stepA_mal.lisp deleted file mode 100644 index 3c7135edd5..0000000000 --- a/clisp/stepA_mal.lisp +++ /dev/null @@ -1,329 +0,0 @@ -(require "dependencies") - -(defpackage :mal - (:use :common-lisp - :readline - :types - :env - :reader - :printer - :core)) - -(in-package :mal) - -(define-condition invalid-function (types:mal-runtime-exception) - ((form :initarg :form :reader form) - (context :initarg :context :reader context)) - (:report (lambda (condition stream) - (format stream - "Invalid function '~a' provided while ~a" - (printer:pr-str (form condition)) - (if (string= (context condition) "apply") - "applying" - "defining macro"))))) - -(defvar *repl-env* (env:create-mal-env)) - -(dolist (binding core:ns) - (env:set-env *repl-env* - (car binding) - (cdr binding))) - -(defvar mal-quote (make-mal-symbol "quote")) -(defvar mal-quasiquote (make-mal-symbol "quasiquote")) -(defvar mal-unquote (make-mal-symbol "unquote")) -(defvar mal-splice-unquote (make-mal-symbol "splice-unquote")) -(defvar mal-cons (make-mal-symbol "cons")) -(defvar mal-concat (make-mal-symbol "concat")) -(defvar mal-macroexpand (make-mal-symbol "macroexpand")) -(defvar mal-def! (make-mal-symbol "def!")) -(defvar mal-defmacro! (make-mal-symbol "defmacro!")) -(defvar mal-let* (make-mal-symbol "let*")) -(defvar mal-do (make-mal-symbol "do")) -(defvar mal-if (make-mal-symbol "if")) -(defvar mal-fn* (make-mal-symbol "fn*")) -(defvar mal-try* (make-mal-symbol "try*")) -(defvar mal-catch* (make-mal-symbol "catch*")) - -(env:set-env *repl-env* - (types:make-mal-symbol "eval") - (types:make-mal-builtin-fn (lambda (ast) - (mal-eval ast *repl-env*)))) - -(defun eval-sequence (sequence env) - (map 'list - (lambda (ast) (mal-eval ast env)) - (mal-data-value sequence))) - -(defun eval-hash-map (hash-map env) - (let ((hash-map-value (mal-data-value hash-map)) - (new-hash-table (make-hash-table :test 'types:mal-value=))) - (loop - for key being the hash-keys of hash-map-value - do (setf (gethash (mal-eval key env) new-hash-table) - (mal-eval (gethash key hash-map-value) env))) - (make-mal-hash-map new-hash-table))) - -(defun eval-ast (ast env) - (switch-mal-type ast - (types:symbol (env:get-env env ast)) - (types:list (eval-sequence ast env)) - (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) - (types:hash-map (eval-hash-map ast env)) - (types:any ast))) - -(defun is-pair (value) - (and (or (mal-list-p value) - (mal-vector-p value)) - (not (zerop (length (mal-data-value value)))))) - -(defun quasiquote (ast) - (if (not (is-pair ast)) - (types:make-mal-list (list mal-quote ast)) - (let ((forms (map 'list #'identity (mal-data-value ast)))) - (cond - ((mal-value= mal-unquote (first forms)) - (second forms)) - - ((and (is-pair (first forms)) - (mal-value= mal-splice-unquote - (first (mal-data-value (first forms))))) - (types:make-mal-list (list mal-concat - (second (mal-data-value (first forms))) - (quasiquote (make-mal-list (cdr forms)))))) - - (t (types:make-mal-list (list mal-cons - (quasiquote (first forms)) - (quasiquote (make-mal-list (cdr forms)))))))))) - -(defun is-macro-call (ast env) - (when (and (types:mal-list-p ast) - (not (zerop (length (mal-data-value ast))))) - (let* ((func-symbol (first (mal-data-value ast))) - (func (when (types:mal-symbol-p func-symbol) - (env:find-env env func-symbol)))) - (and func - (types:mal-fn-p func) - (cdr (assoc 'is-macro (types:mal-data-attrs func))))))) - -(defun mal-macroexpand (ast env) - (loop - while (is-macro-call ast env) - do (let* ((forms (types:mal-data-value ast)) - (func (env:get-env env (first forms)))) - (setf ast (apply (mal-data-value func) - (cdr forms))))) - ast) - -(defun mal-eval (ast env) - (loop - do (setf ast (mal-macroexpand ast env)) - do (cond - ((null ast) (return types:mal-nil)) - ((not (types:mal-list-p ast)) (return (eval-ast ast env))) - ((zerop (length (mal-data-value ast))) (return ast)) - (t (let ((forms (mal-data-value ast))) - (cond - ((mal-value= mal-quote (first forms)) - (return (second forms))) - - ((mal-value= mal-quasiquote (first forms)) - (setf ast (quasiquote (second forms)))) - - ((mal-value= mal-macroexpand (first forms)) - (return (mal-macroexpand (second forms) env))) - - ((mal-value= mal-def! (first forms)) - (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - - ((mal-value= mal-defmacro! (first forms)) - (let ((value (mal-eval (third forms) env))) - (return (if (types:mal-fn-p value) - (env:set-env env - (second forms) - (progn - (setf (cdr (assoc 'is-macro (types:mal-data-attrs value))) t) - value)) - (error 'invalid-function - :form value - :context "macro"))))) - - ((mal-value= mal-let* (first forms)) - (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (mal-data-value (second forms))))) - - (mapcar (lambda (binding) - (env:set-env new-env - (car binding) - (mal-eval (or (cdr binding) - types:mal-nil) - new-env))) - (loop - for (symbol value) on bindings - by #'cddr - collect (cons symbol value))) - (setf ast (third forms) - env new-env))) - - ((mal-value= mal-do (first forms)) - (mapc (lambda (form) (mal-eval form env)) - (butlast (cdr forms))) - (setf ast (car (last forms)))) - - ((mal-value= mal-if (first forms)) - (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (mal-value= predicate types:mal-nil) - (mal-value= predicate types:mal-false)) - (fourth forms) - (third forms))))) - - ((mal-value= mal-fn* (first forms)) - (return (let ((arglist (second forms)) - (body (third forms))) - (types:make-mal-fn (lambda (&rest args) - (mal-eval body (env:create-mal-env :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env) - (cons 'is-macro nil)))))) - - ((mal-value= mal-try* (first forms)) - (handler-case - (return (mal-eval (second forms) env)) - ((or types:mal-exception types:mal-error) (condition) - (when (third forms) - (let ((catch-forms (types:mal-data-value (third forms)))) - (when (mal-value= mal-catch* - (first catch-forms)) - (return (mal-eval (third catch-forms) - (env:create-mal-env :parent env - :binds (list (second catch-forms)) - :exprs (list (if (or (typep condition 'types:mal-runtime-exception) - (typep condition 'types:mal-error)) - (types:make-mal-string (format nil "~a" condition)) - (types::mal-exception-data condition))))))))) - (error condition)))) - - (t (let* ((evaluated-list (eval-ast ast env)) - (function (car evaluated-list))) - ;; If first element is a mal function unwrap it - (cond ((types:mal-fn-p function) - (let* ((attrs (types:mal-data-attrs function))) - (setf ast (cdr (assoc 'ast attrs)) - env (env:create-mal-env :parent (cdr (assoc 'env attrs)) - :binds (map 'list - #'identity - (mal-data-value (cdr (assoc 'params attrs)))) - :exprs (cdr evaluated-list))))) - ((types:mal-builtin-fn-p function) - (return (apply (mal-data-value function) - (cdr evaluated-list)))) - (t (error 'invalid-function - :form function - :context "apply"))))))))))) - -(defun mal-read (string) - (reader:read-str string)) - -(defun mal-print (expression) - (printer:pr-str expression)) - -(defun rep (string) - (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) - (types:mal-error (condition) - (format nil - "Error: ~a" - condition)) - (types:mal-runtime-exception (condition) - (format nil - "Exception: ~a" - condition)) - (types:mal-user-exception (condition) - (format nil - "Exception: ~a" - (pr-str (types::mal-exception-data condition)))) - (error (condition) - (format nil - "Internal error: ~a" - condition)))) - -(rep "(def! not (fn* (a) (if a false true)))") -(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") -(rep "(def! *ARGV* (list))") -(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") -(rep "(def! *host-language* \"clisp\")") -(rep "(def! *gensym-counter* (atom 0))") -(rep "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))") -(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))") -(rep "(defmacro! defbuiltin! (fn* (arglist & forms) `(define-builtin '~arglist '~@forms)))") - -(env:set-env *repl-env* - (types:make-mal-symbol "*ARGV*") - (types:wrap-value (cdr common-lisp-user::*args*) - :listp t)) - -;; Readline setup -;;; The test runner sets this environment variable, in which case we do -;;; use readline since tests do not work with the readline interface -(defvar use-readline-p (not (string= (ext:getenv "PERL_RL") "false"))) - -(defvar *history-file* (namestring (merge-pathnames (user-homedir-pathname) - ".mal-clisp-history"))) - -(defun load-history () - (readline:read-history *history-file*)) - -(defun save-history () - (readline:write-history *history-file*)) - -;; Setup history -(when use-readline-p - (load-history)) - -(defun raw-input (prompt) - (format *standard-output* prompt) - (force-output *standard-output*) - (read-line *standard-input* nil)) - -(defun mal-readline (prompt) - (let ((input (if use-readline-p - (readline:readline prompt) - (raw-input prompt)))) - (when (and use-readline-p - input - (not (zerop (length input)))) - (readline:add-history input)) - input)) - -(defun mal-writeline (string) - (when string - (write-line string))) - -(defun repl () - (rep "(println (str \"Mal [\" *host-language* \"]\"))"); - (loop do (let ((line (mal-readline "user> "))) - (if line - (mal-writeline (rep line)) - (return)))) - (when use-readline-p - (save-history))) - -(defun main () - (if (null common-lisp-user::*args*) - ;; Do not start REPL inside Emacs - (unless (member :swank *features*) - (repl)) - (rep (format nil - "(load-file \"~a\")" - (car common-lisp-user::*args*))))) - -(main) diff --git a/clisp/tests/stepA_mal.mal b/clisp/tests/stepA_mal.mal deleted file mode 100644 index 2bb5d54f39..0000000000 --- a/clisp/tests/stepA_mal.mal +++ /dev/null @@ -1,75 +0,0 @@ -;; Testing clisp interop - -(clisp-eval "42") -;=>42 - -(clisp-eval "(+ 1 1)") -;=>2 - -(clisp-eval "(setq foo 1 bar 2 baz 3)") - -(clisp-eval "(list foo bar baz)") -;=>(1 2 3) - -(clisp-eval "7") -;=>7 - -;; -;; Testing boolean flag -(clisp-eval "(= 123 123)" true) -;=>true - -(clisp-eval "(= 123 456)") -;=>nil - -(clisp-eval "(= 123 456)" true) -;=>false - -;; -;; Testing list flag -(clisp-eval "(last nil)" false true) -;=>() - -(clisp-eval "nil" false true) -;=>() - -(clisp-eval "nil") -;=>nil - -;; -;; Testing creation of Common Lisp Objects -(clisp-eval "#(1 2)") -;=>[1 2] - -;;; Not testing with elements since order in hashtable cannot be guaranteed -(clisp-eval "(make-hash-table)") -;=>{} - -(clisp-eval "(defun redundant-identity (x) x)")) -;=>REDUNDANT-IDENTITY - -(clisp-eval "(redundant-identity 2)")) -;=>2 - -(clisp-eval "(defun range (max &key (min 0) (step 1)) (loop for n from min below max by step collect n))") -;=>RANGE - -(clisp-eval "(range 10 :min 0 :step 1)") -;=>(0 1 2 3 4 5 6 7 8 9) - -(clisp-eval "(mapcar #'1+ (range 10 :min 0 :step 1))") -;=>(1 2 3 4 5 6 7 8 9 10) - -;; -;; Testing defbuiltin! -(def! make-native-hash-map (defbuiltin! (&REST args) (MAKE-HASH-TABLE :initial-contents (LOOP FOR (KEY VALUE) ON args BY (FUNCTION CDDR) COLLECT (CONS KEY VALUE))))) -;=># - -(make-native-hash-map 1 2) -;=>{1 2} - -(def! native-range (defbuiltin! (max &KEY (MIN 0) (STEP 1)) (LOOP FOR n FROM MIN BELOW max BY STEP COLLECT n))) -;=># - -(native-range 10 :MIN 2 :STEP 2) -;=>(2 4 6 8) \ No newline at end of file diff --git a/clisp/types.lisp b/clisp/types.lisp deleted file mode 100644 index fa1f0e1261..0000000000 --- a/clisp/types.lisp +++ /dev/null @@ -1,258 +0,0 @@ -;; Dummy package where MAL variables are interned -(defpackage :mal-user - (:use :common-lisp)) - -(defpackage :types - (:use :common-lisp) - (:export :mal-value= - - ;; Accessors - :mal-data-value - :mal-data-type - :mal-data-meta - :mal-data-attrs - - ;; Mal values - :number - :make-mal-number - :mal-number-p - - :boolean - :mal-boolean-p - - :nil - :mal-nil-p - - :string - :make-mal-string - :mal-string-p - - :symbol - :make-mal-symbol - :mal-symbol-p - - :keyword - :make-mal-keyword - :mal-keyword-p - - :list - :make-mal-list - :mal-list-p - - :vector - :make-mal-vector - :mal-vector-p - - :hash-map - :make-mal-hash-map - :mal-hash-map-p - - :atom - :make-mal-atom - :mal-atom-p - - :fn - :make-mal-fn - :mal-fn-p - - :builtin-fn - :make-mal-builtin-fn - :mal-builtin-fn-p - - :any - - ;; Singleton values - :mal-nil - :mal-true - :mal-false - - :mal-exception - - ;; User exceptions - :mal-user-exception - - ;; Exceptions raised by the runtime itself - :mal-runtime-exception - - ;; Error - :mal-error - - ;; Helpers - :wrap-value - :unwrap-value - :apply-unwrapped-values - :apply-unwrapped-values-prefer-bool - :switch-mal-type)) - -(in-package :types) - -(define-condition mal-error (error) - nil) - -(define-condition mal-exception (error) - nil) - -(define-condition mal-runtime-exception (mal-exception) - nil) - -(define-condition mal-user-exception (mal-exception) - ((data :accessor mal-exception-data :initarg :data))) - -(defstruct mal-data - (type nil :read-only t) - (value nil) - meta - attrs) - -(defmacro define-mal-type (type) - ;; Create a class for given type and a convenience constructor and also export - ;; them - (let ((constructor (intern (string-upcase (concatenate 'string - "make-mal-" - (symbol-name type))))) - (predicate (intern (string-upcase (concatenate 'string - "mal-" - (symbol-name type) - "-p"))))) - `(progn (defun ,constructor (value &key meta attrs) - (make-mal-data :type ',type - :value value - :meta meta - :attrs attrs)) - - (defun ,predicate (value) - (when (typep value 'mal-data) - (equal (mal-data-type value) ',type)))))) - -(define-mal-type number) -(define-mal-type symbol) -(define-mal-type keyword) -(define-mal-type string) -;; TODO true, false and nil should ideally be singleton -(define-mal-type boolean) -(define-mal-type nil) - -(define-mal-type list) -(define-mal-type vector) -(define-mal-type hash-map) - -(define-mal-type atom) - -(define-mal-type fn) -(define-mal-type builtin-fn) - -(defvar mal-nil (make-mal-nil nil)) -(defvar mal-true (make-mal-boolean t)) -(defvar mal-false (make-mal-boolean nil)) - -;; Generic type -(defvar any) - -(defmacro switch-mal-type (ast &body forms) - `(let ((type (types:mal-data-type ,ast))) - (cond - ,@(mapcar (lambda (form) - (list (if (or (equal (car form) t) - (equal (car form) 'any)) - t - (list 'equal (list 'quote (car form)) 'type)) - (cadr form))) - forms)))) - -(defun mal-symbol= (value1 value2) - (string= (mal-data-value value1) - (mal-data-value value2))) - -(defun mal-sequence= (value1 value2) - (let ((sequence1 (map 'list #'identity (mal-data-value value1))) - (sequence2 (map 'list #'identity (mal-data-value value2)))) - (when (= (length sequence1) (length sequence2)) - (every #'identity - (loop - for x in sequence1 - for y in sequence2 - collect (mal-value= x y)))))) - -(defun mal-hash-map= (value1 value2) - (let ((map1 (mal-data-value value1)) - (map2 (mal-data-value value2))) - (when (= (hash-table-count map1) (hash-table-count map2)) - (every #'identity - (loop - for key being the hash-keys of map1 - collect (mal-value= (gethash key map1) - (gethash key map2))))))) - -(defun mal-value= (value1 value2) - (when (and (typep value1 'mal-data) - (typep value2 'mal-data)) - (if (equal (mal-data-type value1) (mal-data-type value2)) - (switch-mal-type value1 - (list (mal-sequence= value1 value2)) - (vector (mal-sequence= value1 value2)) - (hash-map (mal-hash-map= value1 value2)) - (any (equal (mal-data-value value1) (mal-data-value value2)))) - (when (or (and (mal-list-p value1) (mal-vector-p value2)) - (and (mal-list-p value2) (mal-vector-p value1))) - (mal-sequence= value1 value2))))) - -(defun hash-mal-value (value) - (sxhash (mal-data-value value))) - -(ext:define-hash-table-test mal-value= mal-value= hash-mal-value) - -(defun wrap-hash-value (value) - (let ((new-hash-table (make-hash-table :test 'mal-value=))) - (loop - for key being the hash-keys of value - do (setf (gethash (wrap-value key) new-hash-table) - (wrap-value (gethash key value)))) - new-hash-table)) - -(defun wrap-value (value &key booleanp listp) - "Convert a Common Lisp value to MAL value" - (typecase value - (number (make-mal-number value)) - ;; This needs to before symbol since nil is a symbol - (null (cond - (booleanp mal-false) - (listp (make-mal-list nil)) - (t mal-nil))) - ;; This needs to before symbol since t, nil are symbols - (boolean (if value mal-true mal-false)) - (symbol (make-mal-symbol (symbol-name value))) - (keyword (make-mal-keyword value)) - (string (make-mal-string value)) - (list (make-mal-list (map 'list #'wrap-value value))) - (vector (make-mal-vector (map 'vector #'wrap-value value))) - (hash-table (make-mal-hash-map (wrap-hash-value value))) - (null mal-nil))) - -(defun unwrap-value (value) - "Convert a MAL value to native Common Lisp value" - (switch-mal-type value - (list (mapcar #'unwrap-value (mal-data-value value))) - (vector (map 'vector #'unwrap-value (mal-data-value value))) - (hash-map (let ((hash-table (make-hash-table)) - (hash-map-value (mal-data-value value))) - (loop - for key being the hash-keys of hash-map-value - do (setf (gethash (mal-data-value key) hash-table) - (mal-data-value (gethash key hash-map-value)))) - hash-table)) - ;; Unfortunately below means even symbols that user indented to use - ;; from the common lisp are interned in lowercase thus runtime - ;; will not find them as such users need to explicitly upcase the - ;; symbols from common lisp - (symbol (intern (mal-data-value value) :mal-user)) - ;; In case of a keyword strip the first colon, and intern the symbol in - ;; keyword package - (keyword (intern (string-upcase (subseq (mal-data-value value) 1)) - :keyword)) - (any (mal-data-value value)))) - -(defun apply-unwrapped-values (op &rest values) - (wrap-value (apply op (mapcar #'unwrap-value values)))) - -(defun apply-unwrapped-values-prefer-bool (op &rest values) - (wrap-value (apply op (mapcar #'unwrap-value values)) :booleanp t)) diff --git a/clisp/utils.lisp b/clisp/utils.lisp deleted file mode 100644 index 0ba81e7064..0000000000 --- a/clisp/utils.lisp +++ /dev/null @@ -1,20 +0,0 @@ -(defpackage :utils - (:use :common-lisp) - (:export :replace-all)) - -(in-package :utils) - -(defun replace-all (string part replacement &key (test #'char=)) - "Returns a new string in which all the occurences of the part -is replaced with replacement." - (with-output-to-string (out) - (loop with part-length = (length part) - for old-pos = 0 then (+ pos part-length) - for pos = (search part string - :start2 old-pos - :test test) - do (write-string string out - :start old-pos - :end (or pos (length string))) - when pos do (write-string replacement out) - while pos))) From 4202ef7bf1acd2d839016085b2834fbc6c41ab91 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 18 Nov 2016 23:51:33 -0600 Subject: [PATCH 0264/2308] Basic: miscellaneous memory savings. - Use variables A1, A2, B2 for Z%(A+1), Z%(A+2), Z%(B+2) respectively. - Replace Z%(R)=Z%(R)+32 with GOSUB INC_REF_R - Add functions TYPE_A and TYPE_F for (Z%(A)AND 31) and (Z%(F)AND 31) respectively. - Inline NATIVE_FUNCTION and MAL_FUNCTION. All together saves over 500 bytes so increase Z% value memory by 250 entries. --- basic/core.in.bas | 144 ++++++++++++++++++------------------ basic/debug.in.bas | 48 ++++++------ basic/env.in.bas | 2 +- basic/mem.in.bas | 19 ++++- basic/reader.in.bas | 4 +- basic/step2_eval.in.bas | 19 ++--- basic/step3_env.in.bas | 17 +++-- basic/step4_if_fn_do.in.bas | 28 +++---- basic/step5_tco.in.bas | 28 +++---- basic/step6_file.in.bas | 28 +++---- basic/step7_quote.in.bas | 38 +++++----- basic/step8_macros.in.bas | 43 ++++++----- basic/step9_try.in.bas | 33 +++++---- basic/stepA_mal.in.bas | 35 +++++---- basic/types.in.bas | 48 ++++++------ basic/variables.txt | 5 +- 16 files changed, 272 insertions(+), 267 deletions(-) diff --git a/basic/core.in.bas b/basic/core.in.bas index 7853ee0a6e..e0ef5f1814 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -7,9 +7,10 @@ REM - restores E REM - call using GOTO and with return label/address on the stack SUB APPLY REM if metadata, get the actual object - IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) + GOSUB TYPE_F + IF T=14 THEN F=Z%(F+1):GOSUB TYPE_F - ON (Z%(F)AND 31)-8 GOTO APPLY_FUNCTION,APPLY_MAL_FUNCTION,APPLY_MAL_FUNCTION + ON T-8 GOTO APPLY_FUNCTION,APPLY_MAL_FUNCTION,APPLY_MAL_FUNCTION APPLY_FUNCTION: REM regular function @@ -43,6 +44,7 @@ SUB DO_TCO_FUNCTION A=Z%(AR+2) B=Z%(Z%(AR+1)+2) +REM PRINT "F:"+STR$(F)+", Z%(F):"+STR$(Z%(F))+", G:"+STR$(G) ON G-60 GOTO DO_APPLY,DO_MAP,DO_SWAP_BANG DO_APPLY: @@ -52,7 +54,8 @@ SUB DO_TCO_FUNCTION A=Z%(AR+2) REM no intermediate args, but not a list, so convert it first - IF C<=1 AND (Z%(A)AND 31)<>6 THEN T=6:GOSUB FORCE_SEQ_TYPE:GOTO DO_APPLY_2 + GOSUB TYPE_A + IF C<=1 AND T<>6 THEN T=6:GOSUB FORCE_SEQ_TYPE:GOTO DO_APPLY_2 REM no intermediate args, just call APPLY directly IF C<=1 THEN GOTO DO_APPLY_1 @@ -63,8 +66,9 @@ SUB DO_TCO_FUNCTION REM a real non-empty list AY=Z%(R6+1):GOSUB RELEASE REM attach end of slice to final args element - Z%(R6+1)=Z%(A+2) - Z%(Z%(A+2))=Z%(Z%(A+2))+32 + A2=Z%(A+2) + Z%(R6+1)=A2 + Z%(A2)=Z%(A2)+32 GOTO DO_APPLY_2 @@ -136,12 +140,12 @@ SUB DO_TCO_FUNCTION Q=AR:GOSUB PUSH_Q REM push atom - Q=A:GOSUB PUSH_Q + GOSUB PUSH_A CALL APPLY REM pop atom - GOSUB POP_Q:A=Q + GOSUB POP_A REM pop and release args GOSUB POP_Q:AY=Q @@ -158,29 +162,14 @@ SUB DO_TCO_FUNCTION DO_TCO_FUNCTION_DONE: END SUB -REM RETURN_INC_REF(R) -> R -REM - return R with 1 ref cnt increase -REM - called with GOTO as a return RETURN -RETURN_INC_REF: - Z%(R)=Z%(R)+32 - RETURN - -REM RETURN_TRUE_FALSE(R) -> R -REM - take BASIC true/false R, return mal true/false R with ref cnt -REM - called with GOTO as a return RETURN -RETURN_TRUE_FALSE: - IF R THEN R=4 - IF R=0 THEN R=2 - GOTO RETURN_INC_REF - REM DO_FUNCTION(F, AR) DO_FUNCTION: REM Get the function number G=Z%(F+1) REM Get argument values - A=Z%(AR+2) - B=Z%(Z%(AR+1)+2) + A=Z%(AR+2):A1=Z%(A+1) + B=Z%(Z%(AR+1)+2):B1=Z%(B+1) REM Switch on the function number IF G>59 THEN ER=-1:E$="unknown function"+STR$(G):RETURN @@ -218,26 +207,29 @@ DO_FUNCTION: GOTO RETURN_TRUE_FALSE DO_STRING_Q: R=0 - IF (Z%(A)AND 31)<>4 THEN GOTO RETURN_TRUE_FALSE - IF MID$(S$(Z%(A+1)),1,1)=CHR$(127) THEN GOTO RETURN_TRUE_FALSE + GOSUB TYPE_A + IF T<>4 THEN GOTO RETURN_TRUE_FALSE + IF MID$(S$(A1),1,1)=CHR$(127) THEN GOTO RETURN_TRUE_FALSE R=1 GOTO RETURN_TRUE_FALSE DO_SYMBOL: - B$=S$(Z%(A+1)) + B$=S$(A1) T=5:GOSUB STRING RETURN DO_SYMBOL_Q: - R=(Z%(A)AND 31)=5 + GOSUB TYPE_A + R=T=5 GOTO RETURN_TRUE_FALSE DO_KEYWORD: - B$=S$(Z%(A+1)) + B$=S$(A1) IF MID$(B$,1,1)<>CHR$(127) THEN B$=CHR$(127)+B$ T=4:GOSUB STRING RETURN DO_KEYWORD_Q: R=0 - IF (Z%(A)AND 31)<>4 THEN GOTO RETURN_TRUE_FALSE - IF MID$(S$(Z%(A+1)),1,1)<>CHR$(127) THEN GOTO RETURN_TRUE_FALSE + GOSUB TYPE_A + IF T<>4 THEN GOTO RETURN_TRUE_FALSE + IF MID$(S$(A1),1,1)<>CHR$(127) THEN GOTO RETURN_TRUE_FALSE R=1 GOTO RETURN_TRUE_FALSE @@ -253,25 +245,25 @@ DO_FUNCTION: AZ=AR:B=1:B$=" ":GOSUB PR_STR_SEQ PRINT R$ R=0 - GOTO RETURN_INC_REF + GOTO INC_REF_R DO_PRINTLN: AZ=AR:B=0:B$=" ":GOSUB PR_STR_SEQ PRINT R$ R=0 - GOTO RETURN_INC_REF + GOTO INC_REF_R DO_READ_STRING: - A$=S$(Z%(A+1)) + A$=S$(A1) GOSUB READ_STR RETURN DO_READLINE: - A$=S$(Z%(A+1)):GOSUB READLINE - IF EZ=1 THEN EZ=0:R=0:GOTO RETURN_INC_REF + A$=S$(A1):GOSUB READLINE + IF EZ=1 THEN EZ=0:R=0:GOTO INC_REF_R B$=R$:T=4:GOSUB STRING RETURN DO_SLURP: R$="" - #cbm OPEN 1,8,0,S$(Z%(A+1)) - #qbasic A$=S$(Z%(A+1)) + #cbm OPEN 1,8,0,S$(A1) + #qbasic A$=S$(A1) #qbasic IF NOT _FILEEXISTS(A$) THEN ER=-1:E$="File not found":RETURN #qbasic OPEN A$ FOR INPUT AS #1 DO_SLURP_LOOP: @@ -290,29 +282,29 @@ DO_FUNCTION: RETURN DO_LT: - R=Z%(A+1)Z%(B+1) + R=A1>B1 GOTO RETURN_TRUE_FALSE DO_GTE: - R=Z%(A+1)>=Z%(B+1) + R=A1>=B1 GOTO RETURN_TRUE_FALSE DO_ADD: - T=2:L=Z%(A+1)+Z%(B+1):GOSUB ALLOC + T=2:L=A1+B1:GOSUB ALLOC RETURN DO_SUB: - T=2:L=Z%(A+1)-Z%(B+1):GOSUB ALLOC + T=2:L=A1-B1:GOSUB ALLOC RETURN DO_MULT: - T=2:L=Z%(A+1)*Z%(B+1):GOSUB ALLOC + T=2:L=A1*B1:GOSUB ALLOC RETURN DO_DIV: - T=2:L=Z%(A+1)/Z%(B+1):GOSUB ALLOC + T=2:L=A1/B1:GOSUB ALLOC RETURN DO_TIME_MS: T=2:L=INT((TI-BT)*16.667):GOSUB ALLOC @@ -320,7 +312,7 @@ DO_FUNCTION: DO_LIST: R=AR - GOTO RETURN_INC_REF + GOTO INC_REF_R DO_LIST_Q: GOSUB LIST_Q GOTO RETURN_TRUE_FALSE @@ -328,7 +320,8 @@ DO_FUNCTION: A=AR:T=7:GOSUB FORCE_SEQ_TYPE RETURN DO_VECTOR_Q: - R=(Z%(A)AND 31)=7 + GOSUB TYPE_A + R=T=7 GOTO RETURN_TRUE_FALSE DO_HASH_MAP: REM setup the stack for the loop @@ -356,7 +349,8 @@ DO_FUNCTION: RETURN DO_MAP_Q: - R=(Z%(A)AND 31)=8 + GOSUB TYPE_A + R=T=8 GOTO RETURN_TRUE_FALSE DO_ASSOC: H=A @@ -370,9 +364,9 @@ DO_FUNCTION: IF AR=0 OR Z%(AR+1)=0 THEN RETURN GOTO DO_ASSOC_LOOP DO_GET: - IF A=0 THEN R=0:GOTO RETURN_INC_REF + IF A=0 THEN R=0:GOTO INC_REF_R H=A:K=B:GOSUB HASHMAP_GET - GOTO RETURN_INC_REF + GOTO INC_REF_R DO_CONTAINS: H=A:K=B:GOSUB HASHMAP_CONTAINS GOTO RETURN_TRUE_FALSE @@ -406,14 +400,15 @@ DO_FUNCTION: RETURN DO_SEQUENTIAL_Q: - R=(Z%(A)AND 31)=6 OR (Z%(A)AND 31)=7 + GOSUB TYPE_A + R=T=6 OR T=7 GOTO RETURN_TRUE_FALSE DO_CONS: T=6:L=B:M=A:GOSUB ALLOC RETURN DO_CONCAT: REM if empty arguments, return empty list - IF Z%(AR+1)=0 THEN R=6:GOTO RETURN_INC_REF + IF Z%(AR+1)=0 THEN R=6:GOTO INC_REF_R REM single argument IF Z%(Z%(AR+1)+1)<>0 THEN GOTO DO_CONCAT_MULT @@ -455,8 +450,8 @@ DO_FUNCTION: B=R GOTO DO_CONCAT_LOOP DO_NTH: + B=B1 GOSUB COUNT - B=Z%(B+1) IF R<=B THEN R=-1:ER=-1:E$="nth: index out of range":RETURN DO_NTH_LOOP: IF B=0 THEN GOTO DO_NTH_DONE @@ -465,19 +460,19 @@ DO_FUNCTION: GOTO DO_NTH_LOOP DO_NTH_DONE: R=Z%(A+2) - GOTO RETURN_INC_REF + GOTO INC_REF_R DO_FIRST: R=0 - IF A=0 THEN GOTO RETURN_INC_REF - IF Z%(A+1)<>0 THEN R=Z%(A+2) - GOTO RETURN_INC_REF + IF A=0 THEN GOTO INC_REF_R + IF A1<>0 THEN R=Z%(A+2) + GOTO INC_REF_R DO_REST: - IF A=0 THEN R=6:GOTO RETURN_INC_REF - IF Z%(A+1)<>0 THEN A=Z%(A+1): REM get the next sequence element + IF A=0 THEN R=6:GOTO INC_REF_R + IF A1<>0 THEN A=A1: REM get the next sequence element T=6:GOSUB FORCE_SEQ_TYPE RETURN DO_EMPTY_Q: - R=Z%(A+1)=0 + R=A1=0 GOTO RETURN_TRUE_FALSE DO_COUNT: GOSUB COUNT @@ -485,33 +480,36 @@ DO_FUNCTION: RETURN DO_CONJ: R=0 - GOTO RETURN_INC_REF + GOTO INC_REF_R DO_SEQ: R=0 - GOTO RETURN_INC_REF + GOTO INC_REF_R DO_WITH_META: - T=Z%(A)AND 31 + GOSUB TYPE_A REM remove existing metadata first - IF T=14 THEN A=Z%(A+1):GOTO DO_WITH_META + IF T=14 THEN A=A1:GOTO DO_WITH_META T=14:L=A:M=B:GOSUB ALLOC RETURN DO_META: R=0 - IF (Z%(A)AND 31)=14 THEN R=Z%(A+2) - GOTO RETURN_INC_REF + GOSUB TYPE_A + IF T=14 THEN R=Z%(A+2) + GOTO INC_REF_R DO_ATOM: T=12:L=A:GOSUB ALLOC RETURN DO_ATOM_Q: - R=(Z%(A)AND 31)=12 + GOSUB TYPE_A + R=T=12 GOTO RETURN_TRUE_FALSE DO_DEREF: - R=Z%(A+1) - GOTO RETURN_INC_REF + R=A1 + GOTO INC_REF_R DO_RESET_BANG: R=B REM release current value + REM can't use A1 here because DO_RESET_BANG is called from swap! AY=Z%(A+1):GOSUB RELEASE REM inc ref by 2 for atom ownership and since we are returning it Z%(R)=Z%(R)+64 @@ -526,7 +524,7 @@ DO_FUNCTION: REM GOSUB PR_MEMORY_SUMMARY GOSUB PR_MEMORY_SUMMARY_SMALL R=0 - GOTO RETURN_INC_REF + GOTO INC_REF_R RETURN DO_EVAL: @@ -536,12 +534,12 @@ DO_FUNCTION: RETURN DO_READ_FILE: - A$=S$(Z%(A+1)) + A$=S$(A1) GOSUB READ_FILE RETURN INIT_CORE_SET_FUNCTION: - GOSUB NATIVE_FUNCTION + T=9:L=A:GOSUB ALLOC: REM native function C=R:GOSUB ENV_SET_S A=A+1 RETURN diff --git a/basic/debug.in.bas b/basic/debug.in.bas index 5c5b6b1ccd..e14452ecb4 100644 --- a/basic/debug.in.bas +++ b/basic/debug.in.bas @@ -51,30 +51,30 @@ REM PRINT "Strings (S$) :"+STR$(P2)+" /"+STR$(Z2) REM #qbasic PRINT "Stack (X%) :"+STR$(X+1)+" /"+STR$(Z3) REM #cbm PRINT "Stack :"+STR$(X+2-Z3)+" / 1920" REM RETURN -REM -REM #cbm PR_MEMORY_MAP: -REM #cbm PRINT -REM #cbm P1=PEEK(43)+PEEK(44)*256 -REM #cbm P2=PEEK(45)+PEEK(46)*256 -REM #cbm P3=PEEK(47)+PEEK(48)*256 -REM #cbm P4=PEEK(49)+PEEK(50)*256 -REM #cbm P5=PEEK(51)+PEEK(52)*256 -REM #cbm P6=PEEK(53)+PEEK(54)*256 -REM #cbm P7=PEEK(55)+PEEK(56)*256 -REM #cbm PRINT "BASIC beg. :"STR$(P1) -REM #cbm PRINT "Variable beg.:"STR$(P2) -REM #cbm PRINT "Array beg. :"STR$(P3) -REM #cbm PRINT "Array end :"STR$(P4) -REM #cbm PRINT "String beg. :"STR$(P5) -REM #cbm PRINT "String cur. :"STR$(P6) -REM #cbm PRINT "BASIC end :"STR$(P7) -REM #cbm PRINT -REM #cbm PRINT "Program Code :"STR$(P2-P1) -REM #cbm PRINT "Variables :"STR$(P3-P2) -REM #cbm PRINT "Arrays :"STR$(P4-P3) -REM #cbm PRINT "String Heap :"STR$(P7-P5) -REM #cbm RETURN -REM + +#cbm PR_MEMORY_MAP: + #cbm PRINT + #cbm P1=PEEK(43)+PEEK(44)*256 + #cbm P2=PEEK(45)+PEEK(46)*256 + #cbm P3=PEEK(47)+PEEK(48)*256 + #cbm P4=PEEK(49)+PEEK(50)*256 + #cbm P5=PEEK(51)+PEEK(52)*256 + #cbm P6=PEEK(53)+PEEK(54)*256 + #cbm P7=PEEK(55)+PEEK(56)*256 + #cbm PRINT "BASIC beg. :"STR$(P1) + #cbm PRINT "Variable beg.:"STR$(P2) + #cbm PRINT "Array beg. :"STR$(P3) + #cbm PRINT "Array end :"STR$(P4) + #cbm PRINT "String beg. :"STR$(P5) + #cbm PRINT "String cur. :"STR$(P6) + #cbm PRINT "BASIC end :"STR$(P7) + #cbm PRINT + #cbm PRINT "Program Code :"STR$(P2-P1) + #cbm PRINT "Variables :"STR$(P3-P2) + #cbm PRINT "Arrays :"STR$(P4-P3) + #cbm PRINT "String Heap :"STR$(P7-P5) + #cbm RETURN + REM REM PR_MEMORY_VALUE(I) -> J: REM REM - I is memory value to print REM REM - I is returned as last byte of value printed diff --git a/basic/env.in.bas b/basic/env.in.bas index bb8cfeb30e..6f1ed97c57 100644 --- a/basic/env.in.bas +++ b/basic/env.in.bas @@ -85,5 +85,5 @@ ENV_GET: CALL ENV_FIND IF R=-1 THEN ER=-1:E$="'"+S$(Z%(K+1))+"' not found":GOTO ENV_GET_RETURN R=R4 - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO ENV_GET_RETURN diff --git a/basic/mem.in.bas b/basic/mem.in.bas index 900447d294..6144c75b1f 100644 --- a/basic/mem.in.bas +++ b/basic/mem.in.bas @@ -257,6 +257,23 @@ RELEASE: RETURN +REM INC_REF_R(R) -> R +REM - return R with 1 ref cnt increase +REM - call with GOTO to return at caller callsite +REM - call with GOSUB to return to caller +INC_REF_R: + Z%(R)=Z%(R)+32 + RETURN + +REM RETURN_TRUE_FALSE(R) -> R +REM - take BASIC true/false R, return mal true/false R with ref cnt +REM - called with GOTO as a return RETURN +RETURN_TRUE_FALSE: + IF R THEN R=4 + IF R=0 THEN R=2 + GOTO INC_REF_R + + REM release stack functions #qbasic PEND_A_LV: @@ -289,7 +306,7 @@ INIT_MEMORY: #cbm T=FRE(0) #qbasic T=0 - Z1=8191+400: REM Z% (boxed memory) size (2 bytes each) + Z1=8191+650: REM Z% (boxed memory) size (2 bytes each) Z2=199: REM S$/S% (string memory) size (3+2 bytes each) #qbasic Z3=200: REM X% (call stack) size (2 bytes each) #cbm Z3=49152: REM X starting point at $C000 (2 bytes each) diff --git a/basic/reader.in.bas b/basic/reader.in.bas index de915c4b9a..85588a9e76 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -65,7 +65,7 @@ SUB READ_FORM IF ER<>-2 THEN GOTO READ_FORM_RETURN GOSUB READ_TOKEN REM PRINT "READ_FORM T$: ["+T$+"]" - IF T$="" THEN R=0:Z%(R)=Z%(R)+32:GOTO READ_FORM_RETURN + IF T$="" THEN R=0:GOSUB INC_REF_R:GOTO READ_FORM_RETURN IF T$="nil" THEN T=0:GOTO READ_NIL_BOOL IF T$="false" THEN T=1:GOTO READ_NIL_BOOL IF T$="true" THEN T=2:GOTO READ_NIL_BOOL @@ -92,7 +92,7 @@ SUB READ_FORM READ_NIL_BOOL: REM PRINT "READ_NIL_BOOL" R=T*2 - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO READ_FORM_RETURN READ_NUMBER: REM PRINT "READ_NUMBER" diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index 98fba99c83..74ecaae76d 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -23,19 +23,19 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A)AND 31 + GOSUB TYPE_A IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: H=E:K=A:GOSUB HASHMAP_GET IF R3=0 THEN R=-1:ER=-1:E$="'"+S$(Z%(A+1))+"' not found":GOTO EVAL_AST_RETURN - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_AST_RETURN EVAL_AST_SEQ: @@ -107,7 +107,7 @@ SUB EVAL APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R THEN R=A:GOSUB INC_REF_R:GOTO EVAL_RETURN EVAL_INVOKE: CALL EVAL_AST @@ -119,7 +119,8 @@ SUB EVAL AR=Z%(R+1): REM rest F=Z%(R+2) - IF (Z%(F)AND 31)<>9 THEN R=-1:ER=-1:E$="apply of non-function":GOTO EVAL_INVOKE_DONE + GOSUB TYPE_F + IF T<>9 THEN R=-1:ER=-1:E$="apply of non-function":GOTO EVAL_INVOKE_DONE GOSUB DO_FUNCTION EVAL_INVOKE_DONE: AY=W:GOSUB RELEASE @@ -211,19 +212,19 @@ MAIN: GOSUB HASHMAP:D=R REM + function - A=1:GOSUB NATIVE_FUNCTION + T=9:L=1:GOSUB ALLOC: REM native function H=D:B$="+":C=R:GOSUB ASSOC1_S:D=R REM - function - A=2:GOSUB NATIVE_FUNCTION + T=9:L=2:GOSUB ALLOC: REM native function H=D:B$="-":C=R:GOSUB ASSOC1_S:D=R REM * function - A=3:GOSUB NATIVE_FUNCTION + T=9:L=3:GOSUB ALLOC: REM native function H=D:B$="*":C=R:GOSUB ASSOC1_S:D=R REM / function - A=4:GOSUB NATIVE_FUNCTION + T=9:L=4:GOSUB ALLOC: REM native function H=D:B$="/":C=R:GOSUB ASSOC1_S:D=R ZT=ZI: REM top of memory after base repl_env diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index 2336c4286f..615fb2c02c 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -24,13 +24,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A)AND 31 + GOSUB TYPE_A IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -107,7 +107,7 @@ SUB EVAL APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R THEN R=A:GOSUB INC_REF_R:GOTO EVAL_RETURN A0=Z%(A+2) @@ -181,7 +181,8 @@ SUB EVAL AR=Z%(R+1): REM rest F=Z%(R+2) - IF (Z%(F)AND 31)<>9 THEN R=-1:ER=-1:E$="apply of non-function":GOTO EVAL_INVOKE_DONE + GOSUB TYPE_F + IF T<>9 THEN R=-1:ER=-1:E$="apply of non-function":GOTO EVAL_INVOKE_DONE GOSUB DO_FUNCTION EVAL_INVOKE_DONE: AY=W:GOSUB RELEASE @@ -272,19 +273,19 @@ MAIN: E=D REM + function - A=1:GOSUB NATIVE_FUNCTION + T=9:L=1:GOSUB ALLOC: REM native function B$="+":C=R:GOSUB ENV_SET_S REM - function - A=2:GOSUB NATIVE_FUNCTION + T=9:L=2:GOSUB ALLOC: REM native function B$="-":C=R:GOSUB ENV_SET_S REM * function - A=3:GOSUB NATIVE_FUNCTION + T=9:L=3:GOSUB ALLOC: REM native function B$="*":C=R:GOSUB ENV_SET_S REM / function - A=4:GOSUB NATIVE_FUNCTION + T=9:L=4:GOSUB ALLOC: REM native function B$="/":C=R:GOSUB ENV_SET_S ZT=ZI: REM top of memory after base repl_env diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index e6f1368d47..3b2a8cf061 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -23,13 +23,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A)AND 31 + GOSUB TYPE_A IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -108,7 +108,7 @@ SUB EVAL APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R THEN R=A:GOSUB INC_REF_R:GOTO EVAL_RETURN A0=Z%(A+2) @@ -201,13 +201,13 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R<4 THEN R=0:GOSUB INC_REF_R:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set A1 and A2 - A=A2:B=A1:GOSUB MAL_FUNCTION + T=10:L=A2:M=A1:N=E:GOSUB ALLOC: REM mal function GOTO EVAL_RETURN EVAL_INVOKE: @@ -223,10 +223,10 @@ SUB EVAL F=Z%(R+2) REM if metadata, get the actual object - IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) + GOSUB TYPE_F + IF T=14 THEN F=Z%(F+1):GOSUB TYPE_F - IF (Z%(F)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + ON T-8 GOTO EVAL_DO_FUNCTION,EVAL_DO_MAL_FUNCTION,EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller GOSUB POP_R @@ -302,7 +302,7 @@ REM RE(A$) -> R REM Assume D has repl_env REM caller must release result RE: - R1=0 + R1=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO RE_DONE @@ -311,18 +311,15 @@ RE: RE_DONE: REM Release memory from MAL_READ - IF R1<>0 THEN AY=R1:GOSUB RELEASE + AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=-1:R2=-1 - GOSUB MAL_READ - R1=R - IF ER<>-2 THEN GOTO REP_DONE + R2=-1 - A=R:E=D:CALL EVAL + GOSUB RE R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -331,7 +328,6 @@ SUB REP REP_DONE: REM Release memory from MAL_READ and EVAL AY=R2:GOSUB RELEASE - AY=R1:GOSUB RELEASE END SUB REM MAIN program diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index 3b88d73b7c..f6f4a1dc83 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -23,13 +23,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A)AND 31 + GOSUB TYPE_A IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -114,7 +114,7 @@ SUB EVAL APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R THEN R=A:GOSUB INC_REF_R:GOTO EVAL_RETURN A0=Z%(A+2) @@ -225,13 +225,13 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R<4 THEN R=0:GOSUB INC_REF_R:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set A1 and A2 - A=A2:B=A1:GOSUB MAL_FUNCTION + T=10:L=A2:M=A1:N=E:GOSUB ALLOC: REM mal function GOTO EVAL_RETURN EVAL_INVOKE: @@ -247,10 +247,10 @@ SUB EVAL F=Z%(R+2) REM if metadata, get the actual object - IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) + GOSUB TYPE_F + IF T=14 THEN F=Z%(F+1):GOSUB TYPE_F - IF (Z%(F)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + ON T-8 GOTO EVAL_DO_FUNCTION,EVAL_DO_MAL_FUNCTION,EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller GOSUB POP_R @@ -326,7 +326,7 @@ REM RE(A$) -> R REM Assume D has repl_env REM caller must release result RE: - R1=0 + R1=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO RE_DONE @@ -335,18 +335,15 @@ RE: RE_DONE: REM Release memory from MAL_READ - IF R1<>0 THEN AY=R1:GOSUB RELEASE + AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=-1:R2=-1 - GOSUB MAL_READ - R1=R - IF ER<>-2 THEN GOTO REP_DONE + R2=-1 - A=R:E=D:CALL EVAL + GOSUB RE R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -355,7 +352,6 @@ SUB REP REP_DONE: REM Release memory from MAL_READ and EVAL AY=R2:GOSUB RELEASE - AY=R1:GOSUB RELEASE END SUB REM MAIN program diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index def1880753..b289c69c7a 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -23,13 +23,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A)AND 31 + GOSUB TYPE_A IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -114,7 +114,7 @@ SUB EVAL APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R THEN R=A:GOSUB INC_REF_R:GOTO EVAL_RETURN A0=Z%(A+2) @@ -225,13 +225,13 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R<4 THEN R=0:GOSUB INC_REF_R:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set A1 and A2 - A=A2:B=A1:GOSUB MAL_FUNCTION + T=10:L=A2:M=A1:N=E:GOSUB ALLOC: REM mal function GOTO EVAL_RETURN EVAL_INVOKE: @@ -247,10 +247,10 @@ SUB EVAL F=Z%(R+2) REM if metadata, get the actual object - IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) + GOSUB TYPE_F + IF T=14 THEN F=Z%(F+1):GOSUB TYPE_F - IF (Z%(F)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + ON T-8 GOTO EVAL_DO_FUNCTION,EVAL_DO_MAL_FUNCTION,EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller GOSUB POP_R @@ -326,7 +326,7 @@ REM RE(A$) -> R REM Assume D has repl_env REM caller must release result RE: - R1=0 + R1=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO RE_DONE @@ -335,18 +335,15 @@ RE: RE_DONE: REM Release memory from MAL_READ - IF R1<>0 THEN AY=R1:GOSUB RELEASE + AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=-1:R2=-1 - GOSUB MAL_READ - R1=R - IF ER<>-2 THEN GOTO REP_DONE + R2=-1 - A=R:E=D:CALL EVAL + GOSUB RE R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -355,7 +352,6 @@ SUB REP REP_DONE: REM Release memory from MAL_READ and EVAL AY=R2:GOSUB RELEASE - AY=R1:GOSUB RELEASE END SUB REM MAIN program diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index 80f64d6ce9..8d0fd318bc 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -18,7 +18,8 @@ MAL_READ: REM QUASIQUOTE(A) -> R SUB QUASIQUOTE REM pair? - IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_QUOTE + GOSUB TYPE_A + IF T<6 OR T>7 THEN GOTO QQ_QUOTE IF (Z%(A+1)=0) THEN GOTO QQ_QUOTE GOTO QQ_UNQUOTE @@ -36,7 +37,7 @@ SUB QUASIQUOTE IF S$(Z%(R+1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] R=Z%(Z%(A+1)+2) - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO QQ_DONE @@ -51,7 +52,8 @@ SUB QUASIQUOTE A=Z%(A+2) REM pair? - IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_DEFAULT + GOSUB TYPE_A + IF T<6 OR T>7 THEN GOTO QQ_DEFAULT IF (Z%(A+1)=0) THEN GOTO QQ_DEFAULT B=Z%(A+2) @@ -94,13 +96,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A)AND 31 + GOSUB TYPE_A IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -185,7 +187,7 @@ SUB EVAL APPLY_LIST: GOSUB EMPTY_Q - IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R THEN R=A:GOSUB INC_REF_R:GOTO EVAL_RETURN A0=Z%(A+2) @@ -285,7 +287,7 @@ SUB EVAL EVAL_QUOTE: R=Z%(Z%(A+1)+2) - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_RETURN EVAL_QUASIQUOTE: @@ -313,13 +315,13 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R<4 THEN R=0:GOSUB INC_REF_R:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set A1 and A2 - A=A2:B=A1:GOSUB MAL_FUNCTION + T=10:L=A2:M=A1:N=E:GOSUB ALLOC: REM mal function GOTO EVAL_RETURN EVAL_INVOKE: @@ -335,10 +337,10 @@ SUB EVAL F=Z%(R+2) REM if metadata, get the actual object - IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) + GOSUB TYPE_F + IF T=14 THEN F=Z%(F+1):GOSUB TYPE_F - IF (Z%(F)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + ON T-8 GOTO EVAL_DO_FUNCTION,EVAL_DO_MAL_FUNCTION,EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller GOSUB POP_R @@ -414,7 +416,7 @@ REM RE(A$) -> R REM Assume D has repl_env REM caller must release result RE: - R1=0 + R1=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO RE_DONE @@ -423,18 +425,15 @@ RE: RE_DONE: REM Release memory from MAL_READ - IF R1<>0 THEN AY=R1:GOSUB RELEASE + AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=-1:R2=-1 - GOSUB MAL_READ - R1=R - IF ER<>-2 THEN GOTO REP_DONE + R2=-1 - A=R:E=D:CALL EVAL + GOSUB RE R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -443,7 +442,6 @@ SUB REP REP_DONE: REM Release memory from MAL_READ and EVAL AY=R2:GOSUB RELEASE - AY=R1:GOSUB RELEASE END SUB REM MAIN program diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index c81f662797..3f782b1e45 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -18,7 +18,8 @@ MAL_READ: REM QUASIQUOTE(A) -> R SUB QUASIQUOTE REM pair? - IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_QUOTE + GOSUB TYPE_A + IF T<6 OR T>7 THEN GOTO QQ_QUOTE IF (Z%(A+1)=0) THEN GOTO QQ_QUOTE GOTO QQ_UNQUOTE @@ -36,7 +37,7 @@ SUB QUASIQUOTE IF S$(Z%(R+1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] R=Z%(Z%(A+1)+2) - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO QQ_DONE @@ -51,7 +52,8 @@ SUB QUASIQUOTE A=Z%(A+2) REM pair? - IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_DEFAULT + GOSUB TYPE_A + IF T<6 OR T>7 THEN GOTO QQ_DEFAULT IF (Z%(A+1)=0) THEN GOTO QQ_DEFAULT B=Z%(A+2) @@ -91,7 +93,8 @@ SUB MACROEXPAND MACROEXPAND_LOOP: REM list? - IF (Z%(A)AND 31)<>6 THEN GOTO MACROEXPAND_DONE + GOSUB TYPE_A + IF T<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? IF Z%(A+1)=0 THEN GOTO MACROEXPAND_DONE B=Z%(A+2) @@ -127,13 +130,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A)AND 31 + GOSUB TYPE_A IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -224,7 +227,7 @@ SUB EVAL IF R<>1 THEN GOTO EVAL_NOT_LIST GOSUB EMPTY_Q - IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R THEN R=A:GOSUB INC_REF_R:GOTO EVAL_RETURN A0=Z%(A+2) @@ -326,7 +329,7 @@ SUB EVAL EVAL_QUOTE: R=Z%(Z%(A+1)+2) - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_RETURN EVAL_QUASIQUOTE: @@ -361,7 +364,7 @@ SUB EVAL R=A REM since we are returning it unevaluated, inc the ref cnt - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_RETURN EVAL_IF: @@ -379,13 +382,13 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R<4 THEN R=0:GOSUB INC_REF_R:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set A1 and A2 - A=A2:B=A1:GOSUB MAL_FUNCTION + T=10:L=A2:M=A1:N=E:GOSUB ALLOC: REM mal function GOTO EVAL_RETURN EVAL_INVOKE: @@ -401,10 +404,10 @@ SUB EVAL F=Z%(R+2) REM if metadata, get the actual object - IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) + GOSUB TYPE_F + IF T=14 THEN F=Z%(F+1):GOSUB TYPE_F - IF (Z%(F)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + ON T-8 GOTO EVAL_DO_FUNCTION,EVAL_DO_MAL_FUNCTION,EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller GOSUB POP_R @@ -480,7 +483,7 @@ REM RE(A$) -> R REM Assume D has repl_env REM caller must release result RE: - R1=0 + R1=-1 GOSUB MAL_READ R1=R IF ER<>-2 THEN GOTO RE_DONE @@ -489,18 +492,15 @@ RE: RE_DONE: REM Release memory from MAL_READ - IF R1<>0 THEN AY=R1:GOSUB RELEASE + AY=R1:GOSUB RELEASE RETURN: REM caller must release result of EVAL REM REP(A$) -> R$ REM Assume D has repl_env SUB REP - R1=-1:R2=-1 - GOSUB MAL_READ - R1=R - IF ER<>-2 THEN GOTO REP_DONE + R2=-1 - A=R:E=D:CALL EVAL + GOSUB RE R2=R IF ER<>-2 THEN GOTO REP_DONE @@ -509,7 +509,6 @@ SUB REP REP_DONE: REM Release memory from MAL_READ and EVAL AY=R2:GOSUB RELEASE - AY=R1:GOSUB RELEASE END SUB REM MAIN program diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index d2970d29eb..bbc4cac89d 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -18,7 +18,8 @@ MAL_READ: REM QUASIQUOTE(A) -> R SUB QUASIQUOTE REM pair? - IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_QUOTE + GOSUB TYPE_A + IF T<6 OR T>7 THEN GOTO QQ_QUOTE IF (Z%(A+1)=0) THEN GOTO QQ_QUOTE GOTO QQ_UNQUOTE @@ -36,7 +37,7 @@ SUB QUASIQUOTE IF S$(Z%(R+1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] R=Z%(Z%(A+1)+2) - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO QQ_DONE @@ -51,7 +52,8 @@ SUB QUASIQUOTE A=Z%(A+2) REM pair? - IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_DEFAULT + GOSUB TYPE_A + IF T<6 OR T>7 THEN GOTO QQ_DEFAULT IF (Z%(A+1)=0) THEN GOTO QQ_DEFAULT B=Z%(A+2) @@ -91,7 +93,8 @@ SUB MACROEXPAND MACROEXPAND_LOOP: REM list? - IF (Z%(A)AND 31)<>6 THEN GOTO MACROEXPAND_DONE + GOSUB TYPE_A + IF T<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? IF Z%(A+1)=0 THEN GOTO MACROEXPAND_DONE B=Z%(A+2) @@ -127,13 +130,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A)AND 31 + GOSUB TYPE_A IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -224,7 +227,7 @@ SUB EVAL IF R<>1 THEN GOTO EVAL_NOT_LIST GOSUB EMPTY_Q - IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R THEN R=A:GOSUB INC_REF_R:GOTO EVAL_RETURN A0=Z%(A+2) @@ -327,7 +330,7 @@ SUB EVAL EVAL_QUOTE: R=Z%(Z%(A+1)+2) - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_RETURN EVAL_QUASIQUOTE: @@ -362,7 +365,7 @@ SUB EVAL R=A REM since we are returning it unevaluated, inc the ref cnt - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_RETURN EVAL_TRY: @@ -383,7 +386,7 @@ SUB EVAL A=A2:GOSUB EVAL_GET_A2: REM set A1 and A2 from catch block REM create object for ER=-1 type raw string errors - IF ER=-1 THEN B$=E$:T=4:GOSUB STRING:ER=R:Z%(R)=Z%(R)+32 + IF ER=-1 THEN B$=E$:T=4:GOSUB STRING:ER=R:GOSUB INC_REF_R REM bind the catch symbol to the error object K=A1:C=ER:GOSUB ENV_SET @@ -411,13 +414,13 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R<4 THEN R=0:GOSUB INC_REF_R:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set A1 and A2 - A=A2:B=A1:GOSUB MAL_FUNCTION + T=10:L=A2:M=A1:N=E:GOSUB ALLOC: REM mal function GOTO EVAL_RETURN EVAL_INVOKE: @@ -433,10 +436,10 @@ SUB EVAL F=Z%(R+2) REM if metadata, get the actual object - IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) + GOSUB TYPE_F + IF T=14 THEN F=Z%(F+1):GOSUB TYPE_F - IF (Z%(F)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + ON T-8 GOTO EVAL_DO_FUNCTION,EVAL_DO_MAL_FUNCTION,EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller GOSUB POP_R diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index bbc0169efd..e12f236e6c 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -18,7 +18,8 @@ MAL_READ: REM QUASIQUOTE(A) -> R SUB QUASIQUOTE REM pair? - IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_QUOTE + GOSUB TYPE_A + IF T<6 OR T>7 THEN GOTO QQ_QUOTE IF (Z%(A+1)=0) THEN GOTO QQ_QUOTE GOTO QQ_UNQUOTE @@ -36,7 +37,7 @@ SUB QUASIQUOTE IF S$(Z%(R+1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE REM [ast[1]] R=Z%(Z%(A+1)+2) - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO QQ_DONE @@ -51,7 +52,8 @@ SUB QUASIQUOTE A=Z%(A+2) REM pair? - IF (Z%(A)AND 31)<6 OR (Z%(A)AND 31)>7 THEN GOTO QQ_DEFAULT + GOSUB TYPE_A + IF T<6 OR T>7 THEN GOTO QQ_DEFAULT IF (Z%(A+1)=0) THEN GOTO QQ_DEFAULT B=Z%(A+2) @@ -91,7 +93,8 @@ SUB MACROEXPAND MACROEXPAND_LOOP: REM list? - IF (Z%(A)AND 31)<>6 THEN GOTO MACROEXPAND_DONE + GOSUB TYPE_A + IF T<>6 THEN GOTO MACROEXPAND_DONE REM non-empty? IF Z%(A+1)=0 THEN GOTO MACROEXPAND_DONE B=Z%(A+2) @@ -127,13 +130,13 @@ SUB EVAL_AST IF ER<>-2 THEN GOTO EVAL_AST_RETURN - T=Z%(A)AND 31 + GOSUB TYPE_A IF T=5 THEN GOTO EVAL_AST_SYMBOL IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_AST_RETURN EVAL_AST_SYMBOL: @@ -224,7 +227,7 @@ SUB EVAL IF R<>1 THEN GOTO EVAL_NOT_LIST GOSUB EMPTY_Q - IF R THEN R=A:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R THEN R=A:GOSUB INC_REF_R:GOTO EVAL_RETURN A0=Z%(A+2) @@ -327,7 +330,7 @@ SUB EVAL EVAL_QUOTE: R=Z%(Z%(A+1)+2) - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_RETURN EVAL_QUASIQUOTE: @@ -362,7 +365,7 @@ SUB EVAL R=A REM since we are returning it unevaluated, inc the ref cnt - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R GOTO EVAL_RETURN EVAL_TRY: @@ -383,7 +386,7 @@ SUB EVAL A=A2:GOSUB EVAL_GET_A2: REM set A1 and A2 from catch block REM create object for ER=-1 type raw string errors - IF ER=-1 THEN B$=E$:T=4:GOSUB STRING:ER=R:Z%(R)=Z%(R)+32 + IF ER=-1 THEN B$=E$:T=4:GOSUB STRING:ER=R:GOSUB INC_REF_R REM bind the catch symbol to the error object K=A1:C=ER:GOSUB ENV_SET @@ -411,13 +414,13 @@ SUB EVAL AY=R:GOSUB RELEASE REM if no false case (A3), return nil GOSUB COUNT - IF R<4 THEN R=0:Z%(R)=Z%(R)+32:GOTO EVAL_RETURN + IF R<4 THEN R=0:GOSUB INC_REF_R:GOTO EVAL_RETURN GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop EVAL_FN: GOSUB EVAL_GET_A2: REM set A1 and A2 - A=A2:B=A1:GOSUB MAL_FUNCTION + T=10:L=A2:M=A1:N=E:GOSUB ALLOC: REM mal function GOTO EVAL_RETURN EVAL_INVOKE: @@ -433,10 +436,10 @@ SUB EVAL F=Z%(R+2) REM if metadata, get the actual object - IF (Z%(F)AND 31)=14 THEN F=Z%(F+1) + GOSUB TYPE_F + IF T=14 THEN F=Z%(F+1):GOSUB TYPE_F - IF (Z%(F)AND 31)=9 THEN GOTO EVAL_DO_FUNCTION - IF (Z%(F)AND 31)=10 THEN GOTO EVAL_DO_MAL_FUNCTION + ON T-8 GOTO EVAL_DO_FUNCTION,EVAL_DO_MAL_FUNCTION,EVAL_DO_MAL_FUNCTION REM if error, pop and return f/args for release by caller GOSUB POP_R @@ -626,7 +629,7 @@ MAIN: QUIT: REM GOSUB PR_MEMORY_SUMMARY_SMALL PRINT:GOSUB PR_MEMORY_SUMMARY_SMALL - REM GOSUB PR_MEMORY_MAP + GOSUB PR_MEMORY_MAP REM P1=0:P2=ZI:GOSUB PR_MEMORY REM P1=D:GOSUB PR_OBJECT REM P1=ZK:GOSUB PR_OBJECT diff --git a/basic/types.in.bas b/basic/types.in.bas index 45fcd3e87f..cf63927490 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -1,5 +1,15 @@ REM general functions +REM TYPE_A(A) -> T +TYPE_A: + T=Z%(A)AND 31 + RETURN + +REM TYPE_F(F) -> T +TYPE_F: + T=Z%(F)AND 31 + RETURN + REM EQUAL_Q(A, B) -> R EQUAL_Q: ED=0: REM recursion depth @@ -12,12 +22,12 @@ EQUAL_Q: Q=B:GOSUB PUSH_Q ED=ED+1 - T1=Z%(A)AND 31 + GOSUB TYPE_A T2=Z%(B)AND 31 - IF T1>5 AND T1<8 AND T2>5 AND T2<8 THEN GOTO EQUAL_Q_SEQ - IF T1=8 AND T2=8 THEN GOTO EQUAL_Q_HM + IF T>5 AND T<8 AND T2>5 AND T2<8 THEN GOTO EQUAL_Q_SEQ + IF T=8 AND T2=8 THEN GOTO EQUAL_Q_HM - IF T1<>T2 OR Z%(A+1)<>Z%(B+1) THEN R=0 + IF T<>T2 OR Z%(A+1)<>Z%(B+1) THEN R=0 GOTO EQUAL_Q_DONE EQUAL_Q_SEQ: @@ -108,9 +118,9 @@ REM sequence functions REM FORCE_SEQ_TYPE(A,T) -> R FORCE_SEQ_TYPE: REM if it's already the right type, inc ref cnt and return it - IF (Z%(A)AND 31)=T THEN R=A:Z%(R)=Z%(R)+32:RETURN + IF (Z%(A)AND 31)=T THEN R=A:GOTO INC_REF_R REM if it's empty, return the empty sequence match T - IF A<16 THEN R=(T-4)*3:Z%(R)=Z%(R)+32:RETURN + IF A<16 THEN R=(T-4)*3:GOTO INC_REF_R REM otherwise, copy first element to turn it into correct type B=Z%(A+2): REM value to copy L=Z%(A+1):M=B:GOSUB ALLOC: REM T already set @@ -122,12 +132,11 @@ REM - setup stack for map loop MAP_LOOP_START: REM point to empty sequence to start off R=(T-4)*3: REM calculate location of empty seq - Z%(R)=Z%(R)+32 GOSUB PUSH_R: REM push return ptr GOSUB PUSH_R: REM push empty ptr GOSUB PUSH_R: REM push current ptr - RETURN + GOTO INC_REF_R REM MAP_LOOP_UPDATE(C,M): REM MAP_LOOP_UPDATE(C,M,N): @@ -165,7 +174,8 @@ MAP_LOOP_DONE: REM LIST_Q(A) -> R LIST_Q: R=0 - IF (Z%(A)AND 31)=6 THEN R=1 + GOSUB TYPE_A + IF T=6 THEN R=1 RETURN REM EMPTY_Q(A) -> R @@ -197,8 +207,7 @@ LAST: GOTO LAST_LOOP LAST_DONE: R=Z%(W+2) - Z%(R)=Z%(R)+32 - RETURN + GOTO INC_REF_R REM SLICE(A,B,C) -> R REM make copy of sequence A from index B to C @@ -207,7 +216,7 @@ REM returns A as next element following slice (of original) SLICE: I=0 R=6: REM always a list - Z%(R)=Z%(R)+32 + GOSUB INC_REF_R R6=-1: REM last list element before empty W=R: REM temporary for return as R REM advance A to position B @@ -260,8 +269,7 @@ REM HASHMAP() -> R HASHMAP: REM just point to static empty hash-map R=12 - Z%(R)=Z%(R)+32 - RETURN + GOTO INC_REF_R REM ASSOC1(H, K, C) -> R ASSOC1: @@ -299,15 +307,3 @@ HASHMAP_CONTAINS: R=R3 RETURN - -REM function functions - -REM NATIVE_FUNCTION(A) -> R -NATIVE_FUNCTION: - T=9:L=A:GOSUB ALLOC - RETURN - -REM MAL_FUNCTION(A, B, E) -> R -MAL_FUNCTION: - T=10:L=A:M=B:N=E:GOSUB ALLOC - RETURN diff --git a/basic/variables.txt b/basic/variables.txt index 98d3f8560d..049be887e0 100644 --- a/basic/variables.txt +++ b/basic/variables.txt @@ -70,9 +70,10 @@ S2$ : REPLACE replacement Other temporaries: A0 : EVAL ast elements -A1 : EVAL ast elements -A2 : EVAL ast elements +A1 : EVAL ast elements, DO_FUNCTION temp +A2 : EVAL ast elements, DO_FUNCTION temp A3 : EVAL ast elements +B1 : DO_FUNCTION temp CZ : DO_CONCAT stack position ED : EQUAL_Q recursion depth counter From 034e82adc5edb5d9298b3a20702232bfd51f1d11 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Tue, 8 Nov 2016 21:40:18 +0000 Subject: [PATCH 0265/2308] Add Skew implementation See http://skew-lang.org/ for details on the Skew language. Currently Mal only compiles to Javascript, as there are some issues with the C# backend for Skew (https://github.com/evanw/skew/issues/19). Tested with Skew 0.7.42. --- .gitignore | 1 + .travis.yml | 1 + Makefile | 3 +- README.md | 16 ++- skew/Dockerfile | 39 ++++++ skew/Makefile | 36 ++++++ skew/core.sk | 98 +++++++++++++++ skew/env.sk | 38 ++++++ skew/printer.sk | 3 + skew/reader.sk | 139 ++++++++++++++++++++++ skew/run | 2 + skew/step0_repl.sk | 24 ++++ skew/step1_read_print.sk | 29 +++++ skew/step2_eval.sk | 64 ++++++++++ skew/step3_env.sk | 72 +++++++++++ skew/step4_if_fn_do.sk | 90 ++++++++++++++ skew/step5_tco.sk | 110 +++++++++++++++++ skew/step6_file.sk | 117 ++++++++++++++++++ skew/step7_quote.sk | 144 ++++++++++++++++++++++ skew/step8_macros.sk | 176 +++++++++++++++++++++++++++ skew/step9_try.sk | 190 +++++++++++++++++++++++++++++ skew/stepA_mal.sk | 194 ++++++++++++++++++++++++++++++ skew/tests/step5_tco.mal | 15 +++ skew/types.sk | 250 +++++++++++++++++++++++++++++++++++++++ skew/util.sk | 55 +++++++++ 25 files changed, 1904 insertions(+), 2 deletions(-) create mode 100644 skew/Dockerfile create mode 100644 skew/Makefile create mode 100644 skew/core.sk create mode 100644 skew/env.sk create mode 100644 skew/printer.sk create mode 100644 skew/reader.sk create mode 100755 skew/run create mode 100644 skew/step0_repl.sk create mode 100644 skew/step1_read_print.sk create mode 100644 skew/step2_eval.sk create mode 100644 skew/step3_env.sk create mode 100644 skew/step4_if_fn_do.sk create mode 100644 skew/step5_tco.sk create mode 100644 skew/step6_file.sk create mode 100644 skew/step7_quote.sk create mode 100644 skew/step8_macros.sk create mode 100644 skew/step9_try.sk create mode 100644 skew/stepA_mal.sk create mode 100644 skew/tests/step5_tco.mal create mode 100644 skew/types.sk create mode 100644 skew/util.sk diff --git a/.gitignore b/.gitignore index 54c0158517..870c195843 100644 --- a/.gitignore +++ b/.gitignore @@ -101,6 +101,7 @@ r/lib scala/mal.jar scala/target scala/project +skew/*.js tcl/mal.tcl vb/*.exe vb/*.dll diff --git a/.travis.yml b/.travis.yml index 97ee8216bb..ae425bbdb3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,6 +62,7 @@ matrix: - {env: IMPL=ruby, services: [docker]} - {env: IMPL=rust, services: [docker]} - {env: IMPL=scala, services: [docker]} + - {env: IMPL=skew, services: [docker]} - {env: IMPL=swift NO_DOCKER=1, os: osx, osx_image: xcode7} - {env: IMPL=swift3, services: [docker]} - {env: IMPL=swift3 NO_DOCKER=1, os: osx, osx_image: xcode8} diff --git a/Makefile b/Makefile index eb810be468..f015de0549 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ IMPLS = ada awk bash basic c d chuck clojure coffee clisp cpp crystal cs dart \ erlang elisp elixir es6 factor forth fsharp go groovy guile haskell \ haxe io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ - python r racket rpython ruby rust scala swift swift3 tcl vb vhdl \ + python r racket rpython ruby rust scala skew swift swift3 tcl vb vhdl \ vimscript step0 = step0_repl @@ -198,6 +198,7 @@ rpython_STEP_TO_PROG = rpython/$($(1)) ruby_STEP_TO_PROG = ruby/$($(1)).rb rust_STEP_TO_PROG = rust/target/release/$($(1)) scala_STEP_TO_PROG = scala/target/scala-2.11/classes/$($(1)).class +skew_STEP_TO_PROG = skew/$($(1)).js swift_STEP_TO_PROG = swift/$($(1)) swift3_STEP_TO_PROG = swift3/$($(1)) tcl_STEP_TO_PROG = tcl/$($(1)).tcl diff --git a/README.md b/README.md index a73cf21763..eefcf9de8d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 62 languages: +Mal is implemented in 63 languages: * Ada * GNU awk @@ -64,6 +64,7 @@ Mal is implemented in 62 languages: * Ruby * Rust * Scala +* Skew * Swift * Swift 3 * Tcl @@ -802,6 +803,19 @@ sbt compile scala -classpath target/scala*/classes stepX_YYY ``` +### Skew ### + +*The Skew implementation was created by [Dov Murik](https://github.com/dubek)* + +The Skew implementation of mal has been tested with Skew 0.7.42. + +``` +cd skew +make +node stepX_YYY.js +``` + + ### Swift *The Swift implementation was created by [Keith Rollin](https://github.com/keith-rollin)* diff --git a/skew/Dockerfile b/skew/Dockerfile new file mode 100644 index 0000000000..8e689fdadb --- /dev/null +++ b/skew/Dockerfile @@ -0,0 +1,39 @@ +FROM ubuntu:vivid +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# For building node modules +RUN apt-get -y install g++ + +# Add nodesource apt repo config for 0.12 stable +RUN curl -sL https://deb.nodesource.com/setup_0.12 | bash - + +# Install nodejs +RUN apt-get -y install nodejs + +# Link common name +RUN ln -sf nodejs /usr/bin/node + +ENV NPM_CONFIG_CACHE /mal/.npm + +# Skew +RUN npm install -g skew diff --git a/skew/Makefile b/skew/Makefile new file mode 100644 index 0000000000..b13d5f5427 --- /dev/null +++ b/skew/Makefile @@ -0,0 +1,36 @@ +STEPS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do \ + step5_tco step6_file step7_quote step8_macros step9_try stepA_mal + +SOURCES_BASE = util.sk types.sk reader.sk printer.sk +SOURCES_LISP = env.sk core.sk stepA_mal.sk +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +STEP3_DEPS = $(SOURCES_BASE) env.sk +STEP4_DEPS = $(STEP3_DEPS) core.sk + +all: $(foreach s,$(STEPS),$(s).js) dist + +dist: mal + +step0_repl.js step1_read_print.js step2_eval.js step3_env.js: $(STEP3_DEPS) +step4_if_fn_do.js step5_tco.js step6_file.js step7_quote.js step8_macros.js step9_try.js stepA_mal.js: $(STEP4_DEPS) + +%.js: %.sk + skewc --target=js --release --output-file=$@ $^ + +mal: stepA_mal.js + echo "#!/usr/bin/env node" > $@ + cat $< >> $@ + chmod +x $@ + +clean: + rm -rf step*.js mal + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" + +.PHONY: all dist clean stats stats-lisp diff --git a/skew/core.sk b/skew/core.sk new file mode 100644 index 0000000000..3390d8e7d7 --- /dev/null +++ b/skew/core.sk @@ -0,0 +1,98 @@ +def _printLn(s string) MalVal { + printLn(s) + return gNil +} + +const ns StringMap) MalVal> = { + "eval": (a List) => EVAL(a[0], repl_env), + "=": (a List) => MalVal.fromBool(a[0].equal(a[1])), + "throw": (a List) => { throw MalUserError.new(a[0]) }, + + "nil?": (a List) => MalVal.fromBool(a[0] is MalNil), + "true?": (a List) => MalVal.fromBool(a[0] is MalTrue), + "false?": (a List) => MalVal.fromBool(a[0] is MalFalse), + "string?": (a List) => MalVal.fromBool(a[0] is MalString), + "symbol": (a List) => MalSymbol.new((a[0] as MalString).val), + "symbol?": (a List) => MalVal.fromBool(a[0] is MalSymbol), + "keyword": (a List) => MalKeyword.new((a[0] as MalString).val), + "keyword?": (a List) => MalVal.fromBool(a[0] is MalKeyword), + + "pr-str": (a List) => MalString.new(" ".join(a.map(e => pr_str(e, true)))), + "str": (a List) => MalString.new("".join(a.map(e => pr_str(e, false)))), + "prn": (a List) => _printLn(" ".join(a.map(e => pr_str(e, true)))), + "println": (a List) => _printLn(" ".join(a.map(e => pr_str(e, false)))), + "read-string": (a List) => read_str((a[0] as MalString).val), + "readline": (a List) => { + const line = readLine((a[0] as MalString).val) + return line == null ? gNil : MalString.new(line) + }, + "slurp": (a List) => MalString.new(readFile((a[0] as MalString).val)), + + "<": (a List) => MalVal.fromBool((a[0] as MalNumber).val < (a[1] as MalNumber).val), + "<=": (a List) => MalVal.fromBool((a[0] as MalNumber).val <= (a[1] as MalNumber).val), + ">": (a List) => MalVal.fromBool((a[0] as MalNumber).val > (a[1] as MalNumber).val), + ">=": (a List) => MalVal.fromBool((a[0] as MalNumber).val >= (a[1] as MalNumber).val), + "+": (a List) => MalNumber.new((a[0] as MalNumber).val + (a[1] as MalNumber).val), + "-": (a List) => MalNumber.new((a[0] as MalNumber).val - (a[1] as MalNumber).val), + "*": (a List) => MalNumber.new((a[0] as MalNumber).val * (a[1] as MalNumber).val), + "/": (a List) => MalNumber.new((a[0] as MalNumber).val / (a[1] as MalNumber).val), + "time-ms": (a List) => MalNumber.new(timeMs), + + "list": (a List) => MalList.new(a), + "list?": (a List) => MalVal.fromBool(a[0] is MalList), + "vector": (a List) => MalVector.new(a), + "vector?": (a List) => MalVal.fromBool(a[0] is MalVector), + "hash-map": (a List) => MalHashMap.fromList(a), + "map?": (a List) => MalVal.fromBool(a[0] is MalHashMap), + "assoc": (a List) => (a[0] as MalHashMap).assoc(a.slice(1)), + "dissoc": (a List) => (a[0] as MalHashMap).dissoc(a.slice(1)), + "get": (a List) => a[0] is MalNil ? gNil : (a[0] as MalHashMap).get(a[1]), + "contains?": (a List) => MalVal.fromBool((a[0] as MalHashMap).contains(a[1])), + "keys": (a List) => MalList.new((a[0] as MalHashMap).keys), + "vals": (a List) => MalList.new((a[0] as MalHashMap).vals), + + "sequential?": (a List) => MalVal.fromBool(a[0] is MalSequential), + "cons": (a List) => { + var list List = (a[1] as MalSequential).val.clone + list.prepend(a[0]) + return MalList.new(list) + }, + "concat": (a List) => { + var list List = [] + a.each(e => list.append((e as MalSequential).val)) + return MalList.new(list) + }, + "nth": (a List) => (a[0] as MalSequential).nth((a[1] as MalNumber).val), + "first": (a List) => a[0] is MalNil ? gNil : (a[0] as MalSequential).first, + "rest": (a List) => a[0] is MalNil ? MalList.new([]) : (a[0] as MalSequential).rest, + "empty?": (a List) => MalVal.fromBool((a[0] as MalSequential).count == 0), + "count": (a List) => a[0] is MalNil ? MalNumber.new(0) : MalNumber.new((a[0] as MalSequential).count), + "apply": (a List) => { + const f = a[0] as MalCallable + var args = a.slice(1, a.count - 1) + args.append((a[a.count - 1] as MalSequential).val) + return f.call(args) + }, + "map": (a List) => { + const f = a[0] as MalCallable + return MalList.new((a[1] as MalSequential).val.map(e => f.call([e]))) + }, + + "conj": (a List) => (a[0] as MalSequential).conj(a.slice(1)), + "seq": (a List) => a[0].seq, + + "meta": (a List) => a[0].meta, + "with-meta": (a List) => a[0].withMeta(a[1]), + "atom": (a List) => MalAtom.new(a[0]), + "atom?": (a List) => MalVal.fromBool(a[0] is MalAtom), + "deref": (a List) => (a[0] as MalAtom).val, + "reset!": (a List) => (a[0] as MalAtom).resetBang(a[1]), + "swap!": (a List) => { + var atom = a[0] as MalAtom + const oldVal = atom.val + var callArgs = a.slice(2) + callArgs.prepend(oldVal) + const newVal = (a[1] as MalCallable).call(callArgs) + return atom.resetBang(newVal) + }, +} diff --git a/skew/env.sk b/skew/env.sk new file mode 100644 index 0000000000..2f4afb9c8e --- /dev/null +++ b/skew/env.sk @@ -0,0 +1,38 @@ +class Env { + const _outer Env + var _data StringMap = {} + + def new(outer Env) { + _outer = outer + } + + def new(outer Env, binds List, exprs List) { + _outer = outer + for i in 0..binds.count { + const name = (binds[i] as MalSymbol).val + if name == "&" { + const restName = (binds[i + 1] as MalSymbol).val + _data[restName] = MalList.new(exprs.slice(i)) + break + } else { + _data[name] = exprs[i] + } + } + } + + def find(key MalSymbol) Env { + if key.val in _data { return self } + return _outer?.find(key) + } + + def get(key MalSymbol) MalVal { + const env = find(key) + if env == null { throw MalError.new("'" + key.val + "' not found") } + return env._data[key.val] + } + + def set(key MalSymbol, value MalVal) MalVal { + _data[key.val] = value + return value + } +} diff --git a/skew/printer.sk b/skew/printer.sk new file mode 100644 index 0000000000..bd767a0135 --- /dev/null +++ b/skew/printer.sk @@ -0,0 +1,3 @@ +def pr_str(obj MalVal, readable bool) string { + return obj.print(readable) +} diff --git a/skew/reader.sk b/skew/reader.sk new file mode 100644 index 0000000000..457e88865f --- /dev/null +++ b/skew/reader.sk @@ -0,0 +1,139 @@ +class Reader { + const tokens List + var position = 0 + + def peek string { + if position >= tokens.count { + return null + } + return tokens[position] + } + + def next string { + const token = peek + position++ + return token + } +} + +def tokenize(str string) List { + var re = RegExp.new("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;.*|[^\\s\\[\\]{}('\"`,;)]*)", "g") + var tokens List = [] + var match string + while (match = re.exec(str)[1]) != "" { + if match[0] == ';' { + continue + } + tokens.append(match) + } + return tokens +} + +def unescape(s string) string { + return s.replaceAll("\\\"", "\"").replaceAll("\\n", "\n").replaceAll("\\\\", "\\") +} + +def read_atom(rdr Reader) MalVal { + const token = rdr.peek + if token == "nil" { + rdr.next + return gNil + } + if token == "true" { + rdr.next + return gTrue + } + if token == "false" { + rdr.next + return gFalse + } + switch token[0] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' { return MalNumber.new(stringToInt(rdr.next)) } + case '-' { + if token.count <= 1 { return MalSymbol.new(rdr.next) } + switch token[1] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' { return MalNumber.new(stringToInt(rdr.next)) } + default { return MalSymbol.new(rdr.next) } + } + } + case '"' { + const s = rdr.next + if s[s.count - 1] == '"' { + return MalString.new(unescape(s.slice(1, s.count - 1))) + } else { + throw MalError.new("expected '\"', got EOF") + } + } + case ':' { return MalKeyword.new(rdr.next.slice(1)) } + default { return MalSymbol.new(rdr.next) } + } +} + +def read_sequence(rdr Reader, open string, close string) List { + if rdr.next != open { + throw MalError.new("expected '" + open + "'") + } + var token string + var items List = [] + while (token = rdr.peek) != close { + if token == null { + throw MalError.new("expected '" + close + "', got EOF") + } + items.append(read_form(rdr)) + } + rdr.next # consume the close paren/bracket/brace + return items +} + +def read_list(rdr Reader) MalList { + return MalList.new(read_sequence(rdr, "(", ")")) +} + +def read_vector(rdr Reader) MalVector { + return MalVector.new(read_sequence(rdr, "[", "]")) +} + +def read_hash_map(rdr Reader) MalHashMap { + return MalHashMap.fromList(read_sequence(rdr, "{", "}")) +} + +def reader_macro(rdr Reader, symbol_name string) MalVal { + rdr.next + return MalList.new([MalSymbol.new(symbol_name), read_form(rdr)]) +} + +def read_form(rdr Reader) MalVal { + switch rdr.peek[0] { + case '\'' { return reader_macro(rdr, "quote") } + case '`' { return reader_macro(rdr, "quasiquote") } + case '~' { + if rdr.peek == "~" { return reader_macro(rdr, "unquote") } + else if rdr.peek == "~@" { return reader_macro(rdr, "splice-unquote") } + else { return read_atom(rdr) } + } + case '^' { + rdr.next + const meta = read_form(rdr) + return MalList.new([MalSymbol.new("with-meta"), read_form(rdr), meta]) + } + case '@' { return reader_macro(rdr, "deref") } + case ')' { throw MalError.new("unexpected ')'") } + case '(' { return read_list(rdr) } + case ']' { throw MalError.new("unexpected ']'") } + case '[' { return read_vector(rdr) } + case '}' { throw MalError.new("unexpected '}'") } + case '{' { return read_hash_map(rdr) } + default { return read_atom(rdr) } + } +} + +def read_str(str string) MalVal { + const tokens = tokenize(str) + if tokens.isEmpty { return null } + var rdr = Reader.new(tokens) + return read_form(rdr) +} + +@import { + const RegExp dynamic +} diff --git a/skew/run b/skew/run new file mode 100755 index 0000000000..6605303a29 --- /dev/null +++ b/skew/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec node $(dirname $0)/${STEP:-stepA_mal}.js "${@}" diff --git a/skew/step0_repl.sk b/skew/step0_repl.sk new file mode 100644 index 0000000000..4afc931603 --- /dev/null +++ b/skew/step0_repl.sk @@ -0,0 +1,24 @@ +def READ(str string) string { + return str +} + +def EVAL(ast string, env StringMap) string { + return ast +} + +def PRINT(exp string) string { + return exp +} + +def REP(str string) string { + return PRINT(EVAL(READ(str), {})) +} + +@entry +def main { + var line string + while (line = readLine("user> ")) != null { + if line == "" { continue } + printLn(REP(line)) + } +} diff --git a/skew/step1_read_print.sk b/skew/step1_read_print.sk new file mode 100644 index 0000000000..30ead7bb91 --- /dev/null +++ b/skew/step1_read_print.sk @@ -0,0 +1,29 @@ +def READ(str string) MalVal { + return read_str(str) +} + +def EVAL(ast MalVal, env StringMap) MalVal { + return ast +} + +def PRINT(exp MalVal) string { + return exp?.print(true) +} + +def REP(str string) string { + return PRINT(EVAL(READ(str), {})) +} + +@entry +def main { + var line string + while (line = readLine("user> ")) != null { + if line == "" { continue } + try { + printLn(REP(line)) + } + catch e MalError { + printLn("Error: \(e.message)") + } + } +} diff --git a/skew/step2_eval.sk b/skew/step2_eval.sk new file mode 100644 index 0000000000..dc7a606e7c --- /dev/null +++ b/skew/step2_eval.sk @@ -0,0 +1,64 @@ +def READ(str string) MalVal { + return read_str(str) +} + +def eval_ast(ast MalVal, env StringMap) MalVal { + if ast is MalSymbol { + const name = (ast as MalSymbol).val + if !(name in env) { + throw MalError.new("'" + name + "' not found") + } + return env[name] + } else if ast is MalList { + return MalList.new((ast as MalList).val.map(e => EVAL(e, env))) + } else if ast is MalVector { + return MalVector.new((ast as MalVector).val.map(e => EVAL(e, env))) + } else if ast is MalHashMap { + var result List = [] + (ast as MalHashMap).val.each((k string, v MalVal) => { + result.append(EVAL(MalVal.fromHashKey(k), env)) + result.append(EVAL(v, env)) + }) + return MalHashMap.fromList(result) + } else { + return ast + } +} + +def EVAL(ast MalVal, env StringMap) MalVal { + if !(ast is MalList) { return eval_ast(ast, env) } + var astList = ast as MalList + if astList.isEmpty { return ast } + var evaledList = eval_ast(ast, env) as MalList + var fn = evaledList[0] as MalNativeFunc + return fn.call(evaledList.val.slice(1)) +} + +def PRINT(exp MalVal) string { + return exp?.print(true) +} + +var repl_env StringMap = { + "+": MalNativeFunc.new((args List) MalVal => MalNumber.new((args[0] as MalNumber).val + (args[1] as MalNumber).val)), + "-": MalNativeFunc.new((args List) MalVal => MalNumber.new((args[0] as MalNumber).val - (args[1] as MalNumber).val)), + "*": MalNativeFunc.new((args List) MalVal => MalNumber.new((args[0] as MalNumber).val * (args[1] as MalNumber).val)), + "/": MalNativeFunc.new((args List) MalVal => MalNumber.new((args[0] as MalNumber).val / (args[1] as MalNumber).val)), +} + +def REP(str string) string { + return PRINT(EVAL(READ(str), repl_env)) +} + +@entry +def main { + var line string + while (line = readLine("user> ")) != null { + if line == "" { continue } + try { + printLn(REP(line)) + } + catch e MalError { + printLn("Error: \(e.message)") + } + } +} diff --git a/skew/step3_env.sk b/skew/step3_env.sk new file mode 100644 index 0000000000..02e45959eb --- /dev/null +++ b/skew/step3_env.sk @@ -0,0 +1,72 @@ +def READ(str string) MalVal { + return read_str(str) +} + +def eval_ast(ast MalVal, env Env) MalVal { + if ast is MalSymbol { + return env.get(ast as MalSymbol) + } else if ast is MalList { + return MalList.new((ast as MalList).val.map(e => EVAL(e, env))) + } else if ast is MalVector { + return MalVector.new((ast as MalVector).val.map(e => EVAL(e, env))) + } else if ast is MalHashMap { + var result List = [] + (ast as MalHashMap).val.each((k string, v MalVal) => { + result.append(EVAL(MalVal.fromHashKey(k), env)) + result.append(EVAL(v, env)) + }) + return MalHashMap.fromList(result) + } else { + return ast + } +} + +def EVAL(ast MalVal, env Env) MalVal { + if !(ast is MalList) { return eval_ast(ast, env) } + const astList = ast as MalList + if astList.isEmpty { return ast } + const a0sym = astList[0] as MalSymbol + if a0sym.val == "def!" { + return env.set(astList[1] as MalSymbol, EVAL(astList[2], env)) + } else if a0sym.val == "let*" { + var letenv = Env.new(env) + const assigns = astList[1] as MalSequential + for i = 0; i < assigns.count; i += 2 { + letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv)) + } + return EVAL(astList[2], letenv) + } else { + const evaledList = eval_ast(ast, env) as MalList + const fn = evaledList[0] as MalNativeFunc + return fn.call(evaledList.val.slice(1)) + } +} + +def PRINT(exp MalVal) string { + return exp?.print(true) +} + +var repl_env = Env.new(null) + +def REP(str string) string { + return PRINT(EVAL(READ(str), repl_env)) +} + +@entry +def main { + repl_env.set(MalSymbol.new("+"), MalNativeFunc.new((args List) MalVal => MalNumber.new((args[0] as MalNumber).val + (args[1] as MalNumber).val))) + repl_env.set(MalSymbol.new("-"), MalNativeFunc.new((args List) MalVal => MalNumber.new((args[0] as MalNumber).val - (args[1] as MalNumber).val))) + repl_env.set(MalSymbol.new("*"), MalNativeFunc.new((args List) MalVal => MalNumber.new((args[0] as MalNumber).val * (args[1] as MalNumber).val))) + repl_env.set(MalSymbol.new("/"), MalNativeFunc.new((args List) MalVal => MalNumber.new((args[0] as MalNumber).val / (args[1] as MalNumber).val))) + + var line string + while (line = readLine("user> ")) != null { + if line == "" { continue } + try { + printLn(REP(line)) + } + catch e MalError { + printLn("Error: \(e.message)") + } + } +} diff --git a/skew/step4_if_fn_do.sk b/skew/step4_if_fn_do.sk new file mode 100644 index 0000000000..58ab70e2c3 --- /dev/null +++ b/skew/step4_if_fn_do.sk @@ -0,0 +1,90 @@ +def READ(str string) MalVal { + return read_str(str) +} + +def eval_ast(ast MalVal, env Env) MalVal { + if ast is MalSymbol { + return env.get(ast as MalSymbol) + } else if ast is MalList { + return MalList.new((ast as MalList).val.map(e => EVAL(e, env))) + } else if ast is MalVector { + return MalVector.new((ast as MalVector).val.map(e => EVAL(e, env))) + } else if ast is MalHashMap { + var result List = [] + (ast as MalHashMap).val.each((k string, v MalVal) => { + result.append(EVAL(MalVal.fromHashKey(k), env)) + result.append(EVAL(v, env)) + }) + return MalHashMap.fromList(result) + } else { + return ast + } +} + +def EVAL(ast MalVal, env Env) MalVal { + if !(ast is MalList) { return eval_ast(ast, env) } + const astList = ast as MalList + if astList.isEmpty { return ast } + const a0sym = astList[0] as MalSymbol + if a0sym.val == "def!" { + return env.set(astList[1] as MalSymbol, EVAL(astList[2], env)) + } else if a0sym.val == "let*" { + var letenv = Env.new(env) + const assigns = astList[1] as MalSequential + for i = 0; i < assigns.count; i += 2 { + letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv)) + } + return EVAL(astList[2], letenv) + } else if a0sym.val == "do" { + const r = eval_ast(MalList.new(astList.val.slice(1)), env) as MalList + return r[r.count - 1] + } else if a0sym.val == "if" { + const condRes = EVAL(astList[1], env) + if condRes is MalNil || condRes is MalFalse { + return astList.count > 3 ? EVAL(astList[3], env) : gNil + } else { + return EVAL(astList[2], env) + } + } else if a0sym.val == "fn*" { + const argsNames = (astList[1] as MalSequential).val + return MalNativeFunc.new((args List) => EVAL(astList[2], Env.new(env, argsNames, args))) + } else { + const evaledList = eval_ast(ast, env) as MalList + const fn = evaledList[0] as MalNativeFunc + return fn.call(evaledList.val.slice(1)) + } +} + +def PRINT(exp MalVal) string { + return exp?.print(true) +} + +var repl_env = Env.new(null) + +def RE(str string) MalVal { + return EVAL(READ(str), repl_env) +} + +def REP(str string) string { + return PRINT(RE(str)) +} + +@entry +def main { + # core.sk: defined using Skew + ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func))) + + # core.mal: defined using the language itself + RE("(def! not (fn* (a) (if a false true)))") + + var line string + while (line = readLine("user> ")) != null { + if line == "" { continue } + try { + printLn(REP(line)) + } + catch e MalError { + printLn("Error: \(e.message)") + } + } +} diff --git a/skew/step5_tco.sk b/skew/step5_tco.sk new file mode 100644 index 0000000000..40985c540d --- /dev/null +++ b/skew/step5_tco.sk @@ -0,0 +1,110 @@ +def READ(str string) MalVal { + return read_str(str) +} + +def eval_ast(ast MalVal, env Env) MalVal { + if ast is MalSymbol { + return env.get(ast as MalSymbol) + } else if ast is MalList { + return MalList.new((ast as MalList).val.map(e => EVAL(e, env))) + } else if ast is MalVector { + return MalVector.new((ast as MalVector).val.map(e => EVAL(e, env))) + } else if ast is MalHashMap { + var result List = [] + (ast as MalHashMap).val.each((k string, v MalVal) => { + result.append(EVAL(MalVal.fromHashKey(k), env)) + result.append(EVAL(v, env)) + }) + return MalHashMap.fromList(result) + } else { + return ast + } +} + +def EVAL(ast MalVal, env Env) MalVal { + while true { + if !(ast is MalList) { return eval_ast(ast, env) } + const astList = ast as MalList + if astList.isEmpty { return ast } + const a0sym = astList[0] as MalSymbol + if a0sym.val == "def!" { + return env.set(astList[1] as MalSymbol, EVAL(astList[2], env)) + } else if a0sym.val == "let*" { + var letenv = Env.new(env) + const assigns = astList[1] as MalSequential + for i = 0; i < assigns.count; i += 2 { + letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv)) + } + ast = astList[2] + env = letenv + continue # TCO + } else if a0sym.val == "do" { + const parts = astList.val.slice(1) + eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env) + ast = parts[parts.count - 1] + continue # TCO + } else if a0sym.val == "if" { + const condRes = EVAL(astList[1], env) + if condRes is MalNil || condRes is MalFalse { + ast = astList.count > 3 ? astList[3] : gNil + } else { + ast = astList[2] + } + continue # TCO + } else if a0sym.val == "fn*" { + const argsNames = astList[1] as MalSequential + return MalFunc.new(astList[2], argsNames, env, (args List) => EVAL(astList[2], Env.new(env, argsNames.val, args))) + } else { + const evaledList = eval_ast(ast, env) as MalList + const fn = evaledList[0] + const callArgs = evaledList.val.slice(1) + if fn is MalNativeFunc { + return (fn as MalNativeFunc).call(callArgs) + } else if fn is MalFunc { + const f = fn as MalFunc + ast = f.ast + env = Env.new(f.env, f.params.val, callArgs) + continue # TCO + } else { + throw MalError.new("Expected function as head of list") + } + } + } +} + +def PRINT(exp MalVal) string { + return exp?.print(true) +} + +var repl_env = Env.new(null) + +def RE(str string) MalVal { + return EVAL(READ(str), repl_env) +} + +def REP(str string) string { + return PRINT(RE(str)) +} + +@entry +def main { + # core.sk: defined using Skew + ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func))) + + # core.mal: defined using the language itself + RE("(def! not (fn* (a) (if a false true)))") + + var line string + while (line = readLine("user> ")) != null { + if line == "" { continue } + try { + printLn(REP(line)) + } + catch e MalError { + printLn("Error: \(e.message)") + } + catch e Error { + printLn("Error: \(e.message)") + } + } +} diff --git a/skew/step6_file.sk b/skew/step6_file.sk new file mode 100644 index 0000000000..702fb2f2d5 --- /dev/null +++ b/skew/step6_file.sk @@ -0,0 +1,117 @@ +def READ(str string) MalVal { + return read_str(str) +} + +def eval_ast(ast MalVal, env Env) MalVal { + if ast is MalSymbol { + return env.get(ast as MalSymbol) + } else if ast is MalList { + return MalList.new((ast as MalList).val.map(e => EVAL(e, env))) + } else if ast is MalVector { + return MalVector.new((ast as MalVector).val.map(e => EVAL(e, env))) + } else if ast is MalHashMap { + var result List = [] + (ast as MalHashMap).val.each((k string, v MalVal) => { + result.append(EVAL(MalVal.fromHashKey(k), env)) + result.append(EVAL(v, env)) + }) + return MalHashMap.fromList(result) + } else { + return ast + } +} + +def EVAL(ast MalVal, env Env) MalVal { + while true { + if !(ast is MalList) { return eval_ast(ast, env) } + const astList = ast as MalList + if astList.isEmpty { return ast } + const a0sym = astList[0] as MalSymbol + if a0sym.val == "def!" { + return env.set(astList[1] as MalSymbol, EVAL(astList[2], env)) + } else if a0sym.val == "let*" { + var letenv = Env.new(env) + const assigns = astList[1] as MalSequential + for i = 0; i < assigns.count; i += 2 { + letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv)) + } + ast = astList[2] + env = letenv + continue # TCO + } else if a0sym.val == "do" { + const parts = astList.val.slice(1) + eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env) + ast = parts[parts.count - 1] + continue # TCO + } else if a0sym.val == "if" { + const condRes = EVAL(astList[1], env) + if condRes is MalNil || condRes is MalFalse { + ast = astList.count > 3 ? astList[3] : gNil + } else { + ast = astList[2] + } + continue # TCO + } else if a0sym.val == "fn*" { + const argsNames = astList[1] as MalSequential + return MalFunc.new(astList[2], argsNames, env, (args List) => EVAL(astList[2], Env.new(env, argsNames.val, args))) + } else { + const evaledList = eval_ast(ast, env) as MalList + const fn = evaledList[0] + const callArgs = evaledList.val.slice(1) + if fn is MalNativeFunc { + return (fn as MalNativeFunc).call(callArgs) + } else if fn is MalFunc { + const f = fn as MalFunc + ast = f.ast + env = Env.new(f.env, f.params.val, callArgs) + continue # TCO + } else { + throw MalError.new("Expected function as head of list") + } + } + } +} + +def PRINT(exp MalVal) string { + return exp?.print(true) +} + +var repl_env = Env.new(null) + +def RE(str string) MalVal { + return EVAL(READ(str), repl_env) +} + +def REP(str string) string { + return PRINT(RE(str)) +} + +@entry +def main { + # core.sk: defined using Skew + ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func))) + repl_env.set(MalSymbol.new("*ARGV*"), MalList.new(argv.isEmpty ? [] : argv.slice(1).map(e => MalString.new(e)))) + + # core.mal: defined using the language itself + RE("(def! not (fn* (a) (if a false true)))") + RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + + if argv.count > 0 { + RE("(load-file \"" + argv[0] + "\")") + return + } + + var line string + while (line = readLine("user> ")) != null { + if line == "" { continue } + try { + printLn(REP(line)) + } + catch e MalError { + printLn("Error: \(e.message)") + } + catch e Error { + printLn("Error: \(e.message)") + } + } +} diff --git a/skew/step7_quote.sk b/skew/step7_quote.sk new file mode 100644 index 0000000000..8594e61a67 --- /dev/null +++ b/skew/step7_quote.sk @@ -0,0 +1,144 @@ +def READ(str string) MalVal { + return read_str(str) +} + +def isPair(a MalVal) bool { + return a is MalSequential && !(a as MalSequential).isEmpty +} + +def quasiquote(ast MalVal) MalVal { + if !isPair(ast) { + return MalList.new([MalSymbol.new("quote"), ast]) + } + const astSeq = ast as MalSequential + const a0 = astSeq[0] + if a0.isSymbol("unquote") { + return astSeq[1] + } + if isPair(a0) { + const a0Seq = a0 as MalSequential + if a0Seq[0].isSymbol("splice-unquote") { + return MalList.new([MalSymbol.new("concat"), a0Seq[1], quasiquote(astSeq.rest)]) + } + } + return MalList.new([MalSymbol.new("cons"), quasiquote(a0), quasiquote(astSeq.rest)]) +} + +def eval_ast(ast MalVal, env Env) MalVal { + if ast is MalSymbol { + return env.get(ast as MalSymbol) + } else if ast is MalList { + return MalList.new((ast as MalList).val.map(e => EVAL(e, env))) + } else if ast is MalVector { + return MalVector.new((ast as MalVector).val.map(e => EVAL(e, env))) + } else if ast is MalHashMap { + var result List = [] + (ast as MalHashMap).val.each((k string, v MalVal) => { + result.append(EVAL(MalVal.fromHashKey(k), env)) + result.append(EVAL(v, env)) + }) + return MalHashMap.fromList(result) + } else { + return ast + } +} + +def EVAL(ast MalVal, env Env) MalVal { + while true { + if !(ast is MalList) { return eval_ast(ast, env) } + const astList = ast as MalList + if astList.isEmpty { return ast } + const a0sym = astList[0] as MalSymbol + if a0sym.val == "def!" { + return env.set(astList[1] as MalSymbol, EVAL(astList[2], env)) + } else if a0sym.val == "let*" { + var letenv = Env.new(env) + const assigns = astList[1] as MalSequential + for i = 0; i < assigns.count; i += 2 { + letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv)) + } + ast = astList[2] + env = letenv + continue # TCO + } else if a0sym.val == "quote" { + return astList[1] + } else if a0sym.val == "quasiquote" { + ast = quasiquote(astList[1]) + continue # TCO + } else if a0sym.val == "do" { + const parts = astList.val.slice(1) + eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env) + ast = parts[parts.count - 1] + continue # TCO + } else if a0sym.val == "if" { + const condRes = EVAL(astList[1], env) + if condRes is MalNil || condRes is MalFalse { + ast = astList.count > 3 ? astList[3] : gNil + } else { + ast = astList[2] + } + continue # TCO + } else if a0sym.val == "fn*" { + const argsNames = astList[1] as MalSequential + return MalFunc.new(astList[2], argsNames, env, (args List) => EVAL(astList[2], Env.new(env, argsNames.val, args))) + } else { + const evaledList = eval_ast(ast, env) as MalList + const fn = evaledList[0] + const callArgs = evaledList.val.slice(1) + if fn is MalNativeFunc { + return (fn as MalNativeFunc).call(callArgs) + } else if fn is MalFunc { + const f = fn as MalFunc + ast = f.ast + env = Env.new(f.env, f.params.val, callArgs) + continue # TCO + } else { + throw MalError.new("Expected function as head of list") + } + } + } +} + +def PRINT(exp MalVal) string { + return exp?.print(true) +} + +var repl_env = Env.new(null) + +def RE(str string) MalVal { + return EVAL(READ(str), repl_env) +} + +def REP(str string) string { + return PRINT(RE(str)) +} + +@entry +def main { + # core.sk: defined using Skew + ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func))) + repl_env.set(MalSymbol.new("*ARGV*"), MalList.new(argv.isEmpty ? [] : argv.slice(1).map(e => MalString.new(e)))) + + # core.mal: defined using the language itself + RE("(def! not (fn* (a) (if a false true)))") + RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + + if argv.count > 0 { + RE("(load-file \"" + argv[0] + "\")") + return + } + + var line string + while (line = readLine("user> ")) != null { + if line == "" { continue } + try { + printLn(REP(line)) + } + catch e MalError { + printLn("Error: \(e.message)") + } + catch e Error { + printLn("Error: \(e.message)") + } + } +} diff --git a/skew/step8_macros.sk b/skew/step8_macros.sk new file mode 100644 index 0000000000..44565979e0 --- /dev/null +++ b/skew/step8_macros.sk @@ -0,0 +1,176 @@ +def READ(str string) MalVal { + return read_str(str) +} + +def isPair(a MalVal) bool { + return a is MalSequential && !(a as MalSequential).isEmpty +} + +def quasiquote(ast MalVal) MalVal { + if !isPair(ast) { + return MalList.new([MalSymbol.new("quote"), ast]) + } + const astSeq = ast as MalSequential + const a0 = astSeq[0] + if a0.isSymbol("unquote") { + return astSeq[1] + } + if isPair(a0) { + const a0Seq = a0 as MalSequential + if a0Seq[0].isSymbol("splice-unquote") { + return MalList.new([MalSymbol.new("concat"), a0Seq[1], quasiquote(astSeq.rest)]) + } + } + return MalList.new([MalSymbol.new("cons"), quasiquote(a0), quasiquote(astSeq.rest)]) +} + +def isMacro(ast MalVal, env Env) bool { + if !(ast is MalList) { return false } + const astList = ast as MalList + if astList.isEmpty { return false } + const a0 = astList[0] + if !(a0 is MalSymbol) { return false } + const a0Sym = a0 as MalSymbol + if env.find(a0Sym) == null { return false } + const f = env.get(a0Sym) + if !(f is MalFunc) { return false } + return (f as MalFunc).isMacro +} + +def macroexpand(ast MalVal, env Env) MalVal { + while isMacro(ast, env) { + const astList = ast as MalList + const mac = env.get(astList[0] as MalSymbol) as MalFunc + ast = mac.call((astList.rest as MalSequential).val) + } + return ast +} + +def eval_ast(ast MalVal, env Env) MalVal { + if ast is MalSymbol { + return env.get(ast as MalSymbol) + } else if ast is MalList { + return MalList.new((ast as MalList).val.map(e => EVAL(e, env))) + } else if ast is MalVector { + return MalVector.new((ast as MalVector).val.map(e => EVAL(e, env))) + } else if ast is MalHashMap { + var result List = [] + (ast as MalHashMap).val.each((k string, v MalVal) => { + result.append(EVAL(MalVal.fromHashKey(k), env)) + result.append(EVAL(v, env)) + }) + return MalHashMap.fromList(result) + } else { + return ast + } +} + +def EVAL(ast MalVal, env Env) MalVal { + while true { + if !(ast is MalList) { return eval_ast(ast, env) } + ast = macroexpand(ast, env) + if !(ast is MalList) { return eval_ast(ast, env) } + const astList = ast as MalList + if astList.isEmpty { return ast } + const a0sym = astList[0] as MalSymbol + if a0sym.val == "def!" { + return env.set(astList[1] as MalSymbol, EVAL(astList[2], env)) + } else if a0sym.val == "let*" { + var letenv = Env.new(env) + const assigns = astList[1] as MalSequential + for i = 0; i < assigns.count; i += 2 { + letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv)) + } + ast = astList[2] + env = letenv + continue # TCO + } else if a0sym.val == "quote" { + return astList[1] + } else if a0sym.val == "quasiquote" { + ast = quasiquote(astList[1]) + continue # TCO + } else if a0sym.val == "defmacro!" { + var macro = EVAL(astList[2], env) as MalFunc + macro.setAsMacro + return env.set(astList[1] as MalSymbol, macro) + } else if a0sym.val == "macroexpand" { + return macroexpand(astList[1], env) + } else if a0sym.val == "do" { + const parts = astList.val.slice(1) + eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env) + ast = parts[parts.count - 1] + continue # TCO + } else if a0sym.val == "if" { + const condRes = EVAL(astList[1], env) + if condRes is MalNil || condRes is MalFalse { + ast = astList.count > 3 ? astList[3] : gNil + } else { + ast = astList[2] + } + continue # TCO + } else if a0sym.val == "fn*" { + const argsNames = astList[1] as MalSequential + return MalFunc.new(astList[2], argsNames, env, (args List) => EVAL(astList[2], Env.new(env, argsNames.val, args))) + } else { + const evaledList = eval_ast(ast, env) as MalList + const fn = evaledList[0] + const callArgs = evaledList.val.slice(1) + if fn is MalNativeFunc { + return (fn as MalNativeFunc).call(callArgs) + } else if fn is MalFunc { + const f = fn as MalFunc + ast = f.ast + env = Env.new(f.env, f.params.val, callArgs) + continue # TCO + } else { + throw MalError.new("Expected function as head of list") + } + } + } +} + +def PRINT(exp MalVal) string { + return exp?.print(true) +} + +var repl_env = Env.new(null) + +def RE(str string) MalVal { + return EVAL(READ(str), repl_env) +} + +def REP(str string) string { + return PRINT(RE(str)) +} + +@entry +def main { + # core.sk: defined using Skew + ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func))) + repl_env.set(MalSymbol.new("*ARGV*"), MalList.new(argv.isEmpty ? [] : argv.slice(1).map(e => MalString.new(e)))) + + # core.mal: defined using the language itself + RE("(def! not (fn* (a) (if a false true)))") + RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + RE("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") + RE("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") + + if argv.count > 0 { + RE("(load-file \"" + argv[0] + "\")") + return + } + + var line string + while (line = readLine("user> ")) != null { + if line == "" { continue } + try { + printLn(REP(line)) + } + catch e MalError { + printLn("Error: \(e.message)") + } + catch e Error { + printLn("Error: \(e.message)") + } + } +} diff --git a/skew/step9_try.sk b/skew/step9_try.sk new file mode 100644 index 0000000000..179575e27c --- /dev/null +++ b/skew/step9_try.sk @@ -0,0 +1,190 @@ +def READ(str string) MalVal { + return read_str(str) +} + +def isPair(a MalVal) bool { + return a is MalSequential && !(a as MalSequential).isEmpty +} + +def quasiquote(ast MalVal) MalVal { + if !isPair(ast) { + return MalList.new([MalSymbol.new("quote"), ast]) + } + const astSeq = ast as MalSequential + const a0 = astSeq[0] + if a0.isSymbol("unquote") { + return astSeq[1] + } + if isPair(a0) { + const a0Seq = a0 as MalSequential + if a0Seq[0].isSymbol("splice-unquote") { + return MalList.new([MalSymbol.new("concat"), a0Seq[1], quasiquote(astSeq.rest)]) + } + } + return MalList.new([MalSymbol.new("cons"), quasiquote(a0), quasiquote(astSeq.rest)]) +} + +def isMacro(ast MalVal, env Env) bool { + if !(ast is MalList) { return false } + const astList = ast as MalList + if astList.isEmpty { return false } + const a0 = astList[0] + if !(a0 is MalSymbol) { return false } + const a0Sym = a0 as MalSymbol + if env.find(a0Sym) == null { return false } + const f = env.get(a0Sym) + if !(f is MalFunc) { return false } + return (f as MalFunc).isMacro +} + +def macroexpand(ast MalVal, env Env) MalVal { + while isMacro(ast, env) { + const astList = ast as MalList + const mac = env.get(astList[0] as MalSymbol) as MalFunc + ast = mac.call((astList.rest as MalSequential).val) + } + return ast +} + +def eval_ast(ast MalVal, env Env) MalVal { + if ast is MalSymbol { + return env.get(ast as MalSymbol) + } else if ast is MalList { + return MalList.new((ast as MalList).val.map(e => EVAL(e, env))) + } else if ast is MalVector { + return MalVector.new((ast as MalVector).val.map(e => EVAL(e, env))) + } else if ast is MalHashMap { + var result List = [] + (ast as MalHashMap).val.each((k string, v MalVal) => { + result.append(EVAL(MalVal.fromHashKey(k), env)) + result.append(EVAL(v, env)) + }) + return MalHashMap.fromList(result) + } else { + return ast + } +} + +def EVAL(ast MalVal, env Env) MalVal { + while true { + if !(ast is MalList) { return eval_ast(ast, env) } + ast = macroexpand(ast, env) + if !(ast is MalList) { return eval_ast(ast, env) } + const astList = ast as MalList + if astList.isEmpty { return ast } + const a0sym = astList[0] as MalSymbol + if a0sym.val == "def!" { + return env.set(astList[1] as MalSymbol, EVAL(astList[2], env)) + } else if a0sym.val == "let*" { + var letenv = Env.new(env) + const assigns = astList[1] as MalSequential + for i = 0; i < assigns.count; i += 2 { + letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv)) + } + ast = astList[2] + env = letenv + continue # TCO + } else if a0sym.val == "quote" { + return astList[1] + } else if a0sym.val == "quasiquote" { + ast = quasiquote(astList[1]) + continue # TCO + } else if a0sym.val == "defmacro!" { + var macro = EVAL(astList[2], env) as MalFunc + macro.setAsMacro + return env.set(astList[1] as MalSymbol, macro) + } else if a0sym.val == "macroexpand" { + return macroexpand(astList[1], env) + } else if a0sym.val == "try*" { + var exc MalVal + try { + return EVAL(astList[1], env) + } + catch e MalUserError { exc = e.data } + catch e MalError { exc = MalString.new(e.message) } + catch e Error { exc = MalString.new(e.message) } + const catchClause = astList[2] as MalList + var catchEnv = Env.new(env, [catchClause[1] as MalSymbol], [exc]) + return EVAL(catchClause[2], catchEnv) + } else if a0sym.val == "do" { + const parts = astList.val.slice(1) + eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env) + ast = parts[parts.count - 1] + continue # TCO + } else if a0sym.val == "if" { + const condRes = EVAL(astList[1], env) + if condRes is MalNil || condRes is MalFalse { + ast = astList.count > 3 ? astList[3] : gNil + } else { + ast = astList[2] + } + continue # TCO + } else if a0sym.val == "fn*" { + const argsNames = astList[1] as MalSequential + return MalFunc.new(astList[2], argsNames, env, (args List) => EVAL(astList[2], Env.new(env, argsNames.val, args))) + } else { + const evaledList = eval_ast(ast, env) as MalList + const fn = evaledList[0] + const callArgs = evaledList.val.slice(1) + if fn is MalNativeFunc { + return (fn as MalNativeFunc).call(callArgs) + } else if fn is MalFunc { + const f = fn as MalFunc + ast = f.ast + env = Env.new(f.env, f.params.val, callArgs) + continue # TCO + } else { + throw MalError.new("Expected function as head of list") + } + } + } +} + +def PRINT(exp MalVal) string { + return exp?.print(true) +} + +var repl_env = Env.new(null) + +def RE(str string) MalVal { + return EVAL(READ(str), repl_env) +} + +def REP(str string) string { + return PRINT(RE(str)) +} + +@entry +def main { + # core.sk: defined using Skew + ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func))) + repl_env.set(MalSymbol.new("*ARGV*"), MalList.new(argv.isEmpty ? [] : argv.slice(1).map(e => MalString.new(e)))) + + # core.mal: defined using the language itself + RE("(def! not (fn* (a) (if a false true)))") + RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + RE("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") + RE("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") + + if argv.count > 0 { + RE("(load-file \"" + argv[0] + "\")") + return + } + + var line string + while (line = readLine("user> ")) != null { + if line == "" { continue } + try { + printLn(REP(line)) + } + catch e MalUserError { + printLn("Error: \(e.data.print(false))") + } + catch e MalError { + printLn("Error: \(e.message)") + } + catch e Error { + printLn("Error: \(e.message)") + } + } +} diff --git a/skew/stepA_mal.sk b/skew/stepA_mal.sk new file mode 100644 index 0000000000..99d8a39e55 --- /dev/null +++ b/skew/stepA_mal.sk @@ -0,0 +1,194 @@ +def READ(str string) MalVal { + return read_str(str) +} + +def isPair(a MalVal) bool { + return a is MalSequential && !(a as MalSequential).isEmpty +} + +def quasiquote(ast MalVal) MalVal { + if !isPair(ast) { + return MalList.new([MalSymbol.new("quote"), ast]) + } + const astSeq = ast as MalSequential + const a0 = astSeq[0] + if a0.isSymbol("unquote") { + return astSeq[1] + } + if isPair(a0) { + const a0Seq = a0 as MalSequential + if a0Seq[0].isSymbol("splice-unquote") { + return MalList.new([MalSymbol.new("concat"), a0Seq[1], quasiquote(astSeq.rest)]) + } + } + return MalList.new([MalSymbol.new("cons"), quasiquote(a0), quasiquote(astSeq.rest)]) +} + +def isMacro(ast MalVal, env Env) bool { + if !(ast is MalList) { return false } + const astList = ast as MalList + if astList.isEmpty { return false } + const a0 = astList[0] + if !(a0 is MalSymbol) { return false } + const a0Sym = a0 as MalSymbol + if env.find(a0Sym) == null { return false } + const f = env.get(a0Sym) + if !(f is MalFunc) { return false } + return (f as MalFunc).isMacro +} + +def macroexpand(ast MalVal, env Env) MalVal { + while isMacro(ast, env) { + const astList = ast as MalList + const mac = env.get(astList[0] as MalSymbol) as MalFunc + ast = mac.call((astList.rest as MalSequential).val) + } + return ast +} + +def eval_ast(ast MalVal, env Env) MalVal { + if ast is MalSymbol { + return env.get(ast as MalSymbol) + } else if ast is MalList { + return MalList.new((ast as MalList).val.map(e => EVAL(e, env))) + } else if ast is MalVector { + return MalVector.new((ast as MalVector).val.map(e => EVAL(e, env))) + } else if ast is MalHashMap { + var result List = [] + (ast as MalHashMap).val.each((k string, v MalVal) => { + result.append(EVAL(MalVal.fromHashKey(k), env)) + result.append(EVAL(v, env)) + }) + return MalHashMap.fromList(result) + } else { + return ast + } +} + +def EVAL(ast MalVal, env Env) MalVal { + while true { + if !(ast is MalList) { return eval_ast(ast, env) } + ast = macroexpand(ast, env) + if !(ast is MalList) { return eval_ast(ast, env) } + const astList = ast as MalList + if astList.isEmpty { return ast } + const a0sym = astList[0] as MalSymbol + if a0sym.val == "def!" { + return env.set(astList[1] as MalSymbol, EVAL(astList[2], env)) + } else if a0sym.val == "let*" { + var letenv = Env.new(env) + const assigns = astList[1] as MalSequential + for i = 0; i < assigns.count; i += 2 { + letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv)) + } + ast = astList[2] + env = letenv + continue # TCO + } else if a0sym.val == "quote" { + return astList[1] + } else if a0sym.val == "quasiquote" { + ast = quasiquote(astList[1]) + continue # TCO + } else if a0sym.val == "defmacro!" { + var macro = EVAL(astList[2], env) as MalFunc + macro.setAsMacro + return env.set(astList[1] as MalSymbol, macro) + } else if a0sym.val == "macroexpand" { + return macroexpand(astList[1], env) + } else if a0sym.val == "try*" { + var exc MalVal + try { + return EVAL(astList[1], env) + } + catch e MalUserError { exc = e.data } + catch e MalError { exc = MalString.new(e.message) } + catch e Error { exc = MalString.new(e.message) } + const catchClause = astList[2] as MalList + var catchEnv = Env.new(env, [catchClause[1] as MalSymbol], [exc]) + return EVAL(catchClause[2], catchEnv) + } else if a0sym.val == "do" { + const parts = astList.val.slice(1) + eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env) + ast = parts[parts.count - 1] + continue # TCO + } else if a0sym.val == "if" { + const condRes = EVAL(astList[1], env) + if condRes is MalNil || condRes is MalFalse { + ast = astList.count > 3 ? astList[3] : gNil + } else { + ast = astList[2] + } + continue # TCO + } else if a0sym.val == "fn*" { + const argsNames = astList[1] as MalSequential + return MalFunc.new(astList[2], argsNames, env, (args List) => EVAL(astList[2], Env.new(env, argsNames.val, args))) + } else { + const evaledList = eval_ast(ast, env) as MalList + const fn = evaledList[0] + const callArgs = evaledList.val.slice(1) + if fn is MalNativeFunc { + return (fn as MalNativeFunc).call(callArgs) + } else if fn is MalFunc { + const f = fn as MalFunc + ast = f.ast + env = Env.new(f.env, f.params.val, callArgs) + continue # TCO + } else { + throw MalError.new("Expected function as head of list") + } + } + } +} + +def PRINT(exp MalVal) string { + return exp?.print(true) +} + +var repl_env = Env.new(null) + +def RE(str string) MalVal { + return EVAL(READ(str), repl_env) +} + +def REP(str string) string { + return PRINT(RE(str)) +} + +@entry +def main { + # core.sk: defined using Skew + ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func))) + repl_env.set(MalSymbol.new("*ARGV*"), MalList.new(argv.isEmpty ? [] : argv.slice(1).map(e => MalString.new(e)))) + + # core.mal: defined using the language itself + RE("(def! *host-language* \"skew\")") + RE("(def! not (fn* (a) (if a false true)))") + RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + RE("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") + RE("(def! *gensym-counter* (atom 0))") + RE("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))") + RE("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))") + + if argv.count > 0 { + RE("(load-file \"" + argv[0] + "\")") + return + } + + RE("(println (str \"Mal [\" *host-language* \"]\"))") + var line string + while (line = readLine("user> ")) != null { + if line == "" { continue } + try { + printLn(REP(line)) + } + catch e MalUserError { + printLn("Error: \(e.data.print(false))") + } + catch e MalError { + printLn("Error: \(e.message)") + } + catch e Error { + printLn("Error: \(e.message)") + } + } +} diff --git a/skew/tests/step5_tco.mal b/skew/tests/step5_tco.mal new file mode 100644 index 0000000000..d20df25db7 --- /dev/null +++ b/skew/tests/step5_tco.mal @@ -0,0 +1,15 @@ +;; Test recursive non-tail call function + +(def! sum-to (fn* (n) (if (= n 0) 0 (+ n (sum-to (- n 1)))))) + +(sum-to 10) +;=>55 + +;;; no try* yet, so test completion of side-effects +(def! res1 nil) +;=>nil +;;; For implementations without their own TCO this should fail and +;;; leave res1 unchanged +(def! res1 (sum-to 10000)) +res1 +;=>nil diff --git a/skew/types.sk b/skew/types.sk new file mode 100644 index 0000000000..f12ef88611 --- /dev/null +++ b/skew/types.sk @@ -0,0 +1,250 @@ +class MalError { + const message string +} + +class MalUserError { + const data MalVal +} + +class MalVal { + var _meta MalVal = gNil + def toHashKey string { throw MalError.new("Not allowed as hash map key") } + def print(readable bool) string + def equal(o MalVal) bool + def isSymbol(name string) bool { return false } + def seq MalVal { throw MalError.new("seq: called on non-sequence") } + def meta MalVal { return _meta } + def _setMeta(newMeta MalVal) { _meta = newMeta } + def withMeta(newMeta MalVal) MalVal { + var res = self.clone + res._setMeta(newMeta) + return res + } + def clone MalVal +} + +namespace MalVal { + def fromHashKey(key string) MalVal { + if key.startsWith("S_") { return MalString.new(key.slice(2)) } + else if key.startsWith("K_") { return MalKeyword.new(key.slice(2)) } + else { throw "Illegal hash key string" } + } + def fromBool(b bool) MalVal { return b ? gTrue : gFalse } +} + +class MalNil : MalVal { + over print(readable bool) string { return "nil" } + over equal(o MalVal) bool { return o is MalNil } + over seq MalVal { return gNil } + over clone MalVal { return self } +} +const gNil = MalNil.new + +class MalTrue : MalVal { + over print(readable bool) string { return "true" } + over equal(o MalVal) bool { return o is MalTrue } + over clone MalVal { return self } +} +const gTrue = MalTrue.new + +class MalFalse : MalVal { + over print(readable bool) string { return "false" } + over equal(o MalVal) bool { return o is MalFalse } + over clone MalVal { return self } +} +const gFalse = MalFalse.new + +class MalNumber : MalVal { + const _data int + over print(readable bool) string { return _data.toString } + def val int { return _data } + over equal(o MalVal) bool { return o is MalNumber && (o as MalNumber).val == val } + over clone MalVal { return self } +} + +class MalSymbol : MalVal { + const _data string + over print(readable bool) string { return _data } + def val string { return _data } + over equal(o MalVal) bool { return o is MalSymbol && (o as MalSymbol).val == val } + over isSymbol(name string) bool { return _data == name } + over clone MalVal { return MalSymbol.new(_data) } +} + +class MalString : MalVal { + const _data string + over print(readable bool) string { return readable ? "\"\(escaped_data)\"" : _data } + over toHashKey string { return "S_\(_data)" } + def val string { return _data } + over equal(o MalVal) bool { return o is MalString && (o as MalString).val == val } + def escaped_data string { + return _data.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\n", "\\n") + } + over seq MalVal { return _data.count == 0 ? gNil : MalList.new(_data.split("").map(e => MalString.new(e))) } + over clone MalVal { return MalString.new(_data) } +} + +class MalKeyword : MalVal { + const _data string + over print(readable bool) string { return ":\(_data)" } + over toHashKey string { return "K_\(_data)" } + def val string { return _data } + over equal(o MalVal) bool { return o is MalKeyword && (o as MalKeyword).val == val } + over clone MalVal { return MalKeyword.new(_data) } +} + +class MalSequential : MalVal { + const _data List + def val List { return _data } + def isEmpty bool { return _data.isEmpty } + def asOneString(readable bool) string { + return " ".join(_data.map(v => v.print(readable))) + } + def count int { return _data.count } + def [](index int) MalVal { return _data[index] } + over equal(o MalVal) bool { + if !(o is MalSequential) { return false } + const oval = (o as MalSequential).val + if val.count != oval.count { return false } + for i in 0..val.count { + if !val[i].equal(oval[i]) { return false } + } + return true + } + def nth(position int) MalVal { + if position >= count { throw MalError.new("nth: index out of range") } + return val[position] + } + def first MalVal { + if isEmpty { return gNil } + return val[0] + } + def rest MalVal { + if isEmpty { return MalList.new([]) } + return MalList.new(val.slice(1)) + } + def conj(args List) MalVal +} + +class MalList : MalSequential { + over print(readable bool) string { return "(" + asOneString(readable) + ")" } + over seq MalVal { return isEmpty ? gNil : self } + over conj(args List) MalVal { + var res = args.clone + res.reverse + res.append(_data) + return MalList.new(res) + } + over clone MalVal { return MalList.new(_data) } +} + +class MalVector : MalSequential { + over print(readable bool) string { return "[" + asOneString(readable) + "]" } + over seq MalVal { return isEmpty ? gNil : MalList.new(_data) } + over conj(args List) MalVal { + var res = _data.clone + res.append(args) + return MalVector.new(res) + } + over clone MalVal { return MalVector.new(_data) } +} + +class MalHashMap : MalVal { + const _data StringMap + over print(readable bool) string { + var pairs List = [] + _data.each((k string, v MalVal) => pairs.append("\(MalVal.fromHashKey(k).print(readable)) \(v.print(readable))")) + return "{" + " ".join(pairs) + "}" + } + def val StringMap { return _data } + over equal(o MalVal) bool { + if !(o is MalHashMap) { return false } + const oh = o as MalHashMap + if oh.val.count != val.count { return false } + var allEqual = true + _data.each((k string, v MalVal) => { + if !(k in oh.val) || !(v.equal(oh.val[k])) { + allEqual = false + } + }) + return allEqual + } + def assoc(kv_list List) MalVal { + var new_data = _data.clone + for i = 0; i < kv_list.count; i += 2 { + new_data[kv_list[i].toHashKey] = kv_list[i + 1] + } + return MalHashMap.new(new_data) + } + def dissoc(keys List) MalVal { + var new_data = _data.clone + for key in keys { + new_data.remove(key.toHashKey) + } + return MalHashMap.new(new_data) + } + def get(key MalVal) MalVal { return _data.get(key.toHashKey, gNil) } + def contains(key MalVal) bool { return key.toHashKey in _data } + def keys List { + return _data.keys.map(k => MalVal.fromHashKey(k)) + } + def vals List { return _data.values } + over clone MalVal { return MalHashMap.new(_data) } +} + +namespace MalHashMap { + def fromList(kv_list List) MalHashMap { + var result StringMap = {} + for i = 0; i < kv_list.count; i += 2 { + result[kv_list[i].toHashKey] = kv_list[i + 1] + } + return MalHashMap.new(result) + } +} + +class MalCallable : MalVal { + const func fn(List) MalVal + def call(args List) MalVal { + return func(args) + } +} + +class MalNativeFunc : MalCallable { + over print(readable bool) string { return "#" } + over equal(o MalVal) bool { return false } + over clone MalVal { return MalNativeFunc.new(func) } +} + +class MalFunc : MalCallable { + const ast MalVal + const params MalSequential + const env Env + var _macro bool = false + def new(aAst MalVal, aParams MalSequential, aEnv Env, aFunc fn(List) MalVal) { + super(aFunc) + ast = aAst + params = aParams + env = aEnv + } + def isMacro bool { return _macro } + def setAsMacro { _macro = true } + over print(readable bool) string { return "#" } + over equal(o MalVal) bool { return false } + over clone MalVal { + var f = MalFunc.new(ast, params, env, func) + if isMacro { f.setAsMacro } + return f + } +} + +class MalAtom : MalVal { + var _data MalVal + over print(readable bool) string { return "(atom \(_data.print(readable)))" } + def val MalVal { return _data } + over equal(o MalVal) bool { return o is MalAtom && val.equal((o as MalAtom).val) } + def resetBang(newData MalVal) MalVal { + _data = newData + return _data + } + over clone MalVal { return MalAtom.new(_data) } +} diff --git a/skew/util.sk b/skew/util.sk new file mode 100644 index 0000000000..04fdfc1fa6 --- /dev/null +++ b/skew/util.sk @@ -0,0 +1,55 @@ +def argv List { + return process.argv.slice(2) +} + +def timeMs int { + return Date.new.getTime() +} + +var fs = require("fs") + +def readFile(filename string) string { + return fs.readFileSync(filename, "utf-8") +} + +def writeString(s string) { + fs.writeSync(1, s) +} + +def printLn(s string) { + writeString(s) + writeString("\n") +} + +def readLine(prompt string) string { + writeString(prompt) + var buffer = Buffer.new(1024) # in newer Node this should be Buffer.alloc + var stdin = fs.openSync("/dev/stdin", "rs") + var bytesread int + var anycharseen = false + var total = 0 + while (bytesread = fs.readSync(stdin, buffer, total, 1)) > 0 { + anycharseen = true + var lastchar = buffer.slice(total, total + bytesread).toString() + if lastchar == "\n" { + break + } + total += bytesread + } + fs.closeSync(stdin) + return anycharseen ? buffer.slice(0, total).toString() : null +} + +def stringToInt(str string) int { + return parseInt(str) +} + +@import { + const process dynamic + const Buffer dynamic + const Date dynamic + const Error dynamic + + def parseInt(str string) int + def require(name string) dynamic +} From 4257071937484adb89d246a28228ac0b78611b3f Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 21 Nov 2016 01:06:05 +0530 Subject: [PATCH 0266/2308] Common Lisp: Load libraries silently --- common-lisp/step0_repl.asd | 4 ++-- common-lisp/step1_read_print.asd | 8 ++++---- common-lisp/step2_eval.asd | 8 ++++---- common-lisp/step3_env.asd | 8 ++++---- common-lisp/step4_if_fn_do.asd | 8 ++++---- common-lisp/step5_tco.asd | 8 ++++---- common-lisp/step6_file.asd | 8 ++++---- common-lisp/step7_quote.asd | 8 ++++---- common-lisp/step8_macros.asd | 8 ++++---- common-lisp/step9_try.asd | 8 ++++---- common-lisp/stepA_mal.asd | 8 ++++---- 11 files changed, 42 insertions(+), 42 deletions(-) diff --git a/common-lisp/step0_repl.asd b/common-lisp/step0_repl.asd index 3acb26598c..aee96e820a 100644 --- a/common-lisp/step0_repl.asd +++ b/common-lisp/step0_repl.asd @@ -4,8 +4,8 @@ (when (probe-file quicklisp-init) (load quicklisp-init))) -(ql:quickload :cl-readline) -(ql:quickload :uiop) +(ql:quickload :uiop :silent t) +(ql:quickload :cl-readline :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step1_read_print.asd b/common-lisp/step1_read_print.asd index 33c6d448c7..993b77d333 100644 --- a/common-lisp/step1_read_print.asd +++ b/common-lisp/step1_read_print.asd @@ -4,10 +4,10 @@ (when (probe-file quicklisp-init) (load quicklisp-init))) -(ql:quickload :uiop) -(ql:quickload :cl-readline) -(ql:quickload :cl-ppcre) -(ql:quickload :genhash) +(ql:quickload :uiop :silent t) +(ql:quickload :cl-readline :silent t) +(ql:quickload :cl-ppcre :silent t) +(ql:quickload :genhash :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step2_eval.asd b/common-lisp/step2_eval.asd index 61814a7220..2b0b3c5907 100644 --- a/common-lisp/step2_eval.asd +++ b/common-lisp/step2_eval.asd @@ -4,10 +4,10 @@ (when (probe-file quicklisp-init) (load quicklisp-init))) -(ql:quickload :uiop) -(ql:quickload :cl-readline) -(ql:quickload :cl-ppcre) -(ql:quickload :genhash) +(ql:quickload :uiop :silent t) +(ql:quickload :cl-readline :silent t) +(ql:quickload :cl-ppcre :silent t) +(ql:quickload :genhash :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step3_env.asd b/common-lisp/step3_env.asd index a0bbc36a9f..3dc014a669 100644 --- a/common-lisp/step3_env.asd +++ b/common-lisp/step3_env.asd @@ -4,10 +4,10 @@ (when (probe-file quicklisp-init) (load quicklisp-init))) -(ql:quickload :uiop) -(ql:quickload :cl-readline) -(ql:quickload :cl-ppcre) -(ql:quickload :genhash) +(ql:quickload :uiop :silent t) +(ql:quickload :cl-readline :silent t) +(ql:quickload :cl-ppcre :silent t) +(ql:quickload :genhash :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step4_if_fn_do.asd b/common-lisp/step4_if_fn_do.asd index 3a01d64853..efddd5e3b7 100644 --- a/common-lisp/step4_if_fn_do.asd +++ b/common-lisp/step4_if_fn_do.asd @@ -4,10 +4,10 @@ (when (probe-file quicklisp-init) (load quicklisp-init))) -(ql:quickload :uiop) -(ql:quickload :cl-readline) -(ql:quickload :cl-ppcre) -(ql:quickload :genhash) +(ql:quickload :uiop :silent t) +(ql:quickload :cl-readline :silent t) +(ql:quickload :cl-ppcre :silent t) +(ql:quickload :genhash :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step5_tco.asd b/common-lisp/step5_tco.asd index 09df9dc95f..bdc42096bc 100644 --- a/common-lisp/step5_tco.asd +++ b/common-lisp/step5_tco.asd @@ -4,10 +4,10 @@ (when (probe-file quicklisp-init) (load quicklisp-init))) -(ql:quickload :uiop) -(ql:quickload :cl-readline) -(ql:quickload :cl-ppcre) -(ql:quickload :genhash) +(ql:quickload :uiop :silent t) +(ql:quickload :cl-readline :silent t) +(ql:quickload :cl-ppcre :silent t) +(ql:quickload :genhash :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step6_file.asd b/common-lisp/step6_file.asd index 60babadadc..35bc77f375 100644 --- a/common-lisp/step6_file.asd +++ b/common-lisp/step6_file.asd @@ -4,10 +4,10 @@ (when (probe-file quicklisp-init) (load quicklisp-init))) -(ql:quickload :uiop) -(ql:quickload :cl-readline) -(ql:quickload :cl-ppcre) -(ql:quickload :genhash) +(ql:quickload :uiop :silent t) +(ql:quickload :cl-readline :silent t) +(ql:quickload :cl-ppcre :silent t) +(ql:quickload :genhash :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step7_quote.asd b/common-lisp/step7_quote.asd index 1d0406e5e3..efa026930f 100644 --- a/common-lisp/step7_quote.asd +++ b/common-lisp/step7_quote.asd @@ -4,10 +4,10 @@ (when (probe-file quicklisp-init) (load quicklisp-init))) -(ql:quickload :uiop) -(ql:quickload :cl-readline) -(ql:quickload :cl-ppcre) -(ql:quickload :genhash) +(ql:quickload :uiop :silent t) +(ql:quickload :cl-readline :silent t) +(ql:quickload :cl-ppcre :silent t) +(ql:quickload :genhash :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step8_macros.asd b/common-lisp/step8_macros.asd index cd262a31fa..64d2f97636 100644 --- a/common-lisp/step8_macros.asd +++ b/common-lisp/step8_macros.asd @@ -4,10 +4,10 @@ (when (probe-file quicklisp-init) (load quicklisp-init))) -(ql:quickload :uiop) -(ql:quickload :cl-readline) -(ql:quickload :cl-ppcre) -(ql:quickload :genhash) +(ql:quickload :uiop :silent t) +(ql:quickload :cl-readline :silent t) +(ql:quickload :cl-ppcre :silent t) +(ql:quickload :genhash :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step9_try.asd b/common-lisp/step9_try.asd index a6aa5e9278..84e0ef0248 100644 --- a/common-lisp/step9_try.asd +++ b/common-lisp/step9_try.asd @@ -4,10 +4,10 @@ (when (probe-file quicklisp-init) (load quicklisp-init))) -(ql:quickload :uiop) -(ql:quickload :cl-readline) -(ql:quickload :cl-ppcre) -(ql:quickload :genhash) +(ql:quickload :uiop :silent t) +(ql:quickload :cl-readline :silent t) +(ql:quickload :cl-ppcre :silent t) +(ql:quickload :genhash :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/stepA_mal.asd b/common-lisp/stepA_mal.asd index cc143ec102..63efad14ac 100644 --- a/common-lisp/stepA_mal.asd +++ b/common-lisp/stepA_mal.asd @@ -4,10 +4,10 @@ (when (probe-file quicklisp-init) (load quicklisp-init))) -(ql:quickload :uiop) -(ql:quickload :cl-readline) -(ql:quickload :cl-ppcre) -(ql:quickload :genhash) +(ql:quickload :uiop :silent t :verbose nil) +(ql:quickload :cl-readline :silent t) +(ql:quickload :cl-ppcre :silent t) +(ql:quickload :genhash :silent t) (defpackage #:mal-asd (:use :cl :asdf)) From a1586c57d5a654b1656467eda5b558eda0a8f9f1 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 21 Nov 2016 01:07:08 +0530 Subject: [PATCH 0267/2308] Common Lisp: Use a custom hash-function when running on ABCL --- common-lisp/types.lisp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/common-lisp/types.lisp b/common-lisp/types.lisp index 1e56db3855..9b1cdf28fe 100644 --- a/common-lisp/types.lisp +++ b/common-lisp/types.lisp @@ -197,10 +197,11 @@ (defun make-mal-value-hash-table () (unless (gethash 'mal-data-value-hash genhash::*hash-test-designator-map*) - ;; ECL's implementation of sxhash does not work well with compound types - ;; so using a custom hash function which hashes the underlying value - (let ((hash-function #+ecl #'mal-sxhash - #-ecl #'sxhash)) + ;; ECL and ABCL's implementations of sxhash do not work well with compound + ;; types, use a custom hash function which hashes the underlying value + ;; instead + (let ((hash-function #+(or ecl abcl) #'mal-sxhash + #-(or ecl abcl) #'sxhash)) (genhash:register-test-designator 'mal-data-value-hash hash-function #'mal-data-value=))) From 96b93a9769aa9bb6afe73d358ed25c2658641f35 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Mon, 21 Nov 2016 01:11:55 +0530 Subject: [PATCH 0268/2308] Common Lisp: Add support for running on ABCL --- common-lisp/Makefile | 6 +++++ common-lisp/README.org | 48 ++++++++++++++++++++++----------------- common-lisp/run-abcl.lisp | 10 ++++++++ 3 files changed, 43 insertions(+), 21 deletions(-) create mode 100644 common-lisp/run-abcl.lisp diff --git a/common-lisp/Makefile b/common-lisp/Makefile index 6afbd1b6ce..9d620701dd 100644 --- a/common-lisp/Makefile +++ b/common-lisp/Makefile @@ -15,6 +15,7 @@ ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) SOURCES_LISP := env.lisp core.lisp stepA_mal.lisp SOURCES := utils.lisp types.lisp reader.lisp printer.lisp $(SOURCES_LISP) LISP ?= sbcl +ABCL ?= abcl # Record the Common Lisp implementation used for all steps built in this # invocation This is used in the targets to rebuild the step if the @@ -33,7 +34,12 @@ hist/%_impl: ; # image even if invoked from some directory different from where it # currently resides step% : step%.lisp utils.lisp types.lisp env.lisp printer.lisp reader.lisp core.lisp hist/%_impl +ifeq ($(LISP),abcl) + echo -n '#!/bin/sh\ncd `dirname $$0` ; $(ABCL) --noinform --noinit --nosystem --load run-abcl.lisp -- $@ $$@' > $@ + chmod +x $@ +else cl-launch --wrap 'if [ -z "$$CL_LAUNCH_VERSION" ] ; then cd "$$(dirname $$CL_LAUNCH_FILE)" ; fi' --verbose --lisp $(LISP) --source-registry $(ROOT_DIR) --system $@ --dump images/$@.$(LISP).image -o $@ --entry 'mal:main' +endif clean: find . -name 'step*' -executable -exec git check-ignore \{\} \; -delete diff --git a/common-lisp/README.org b/common-lisp/README.org index db3795ae9b..97a494d4eb 100644 --- a/common-lisp/README.org +++ b/common-lisp/README.org @@ -5,31 +5,35 @@ This is a reasonably portable implementation of MAL in Common Lisp. It has been tested to work with following Common Lisp implementations -- Steel Bank Common Lisp ([[http://sbcl.org/]]) -- Clozure Common Lisp ([[http://ccl.clozure.com/]]) -- CMU Common Lisp ([[https://www.cons.org/cmucl/]]) -- GNU CLISP ([[http://www.clisp.org/]]) -- Embeddable Common Lisp ([[https://common-lisp.net/project/ecl/]]) -- Allegro CL ([[http://franz.com/products/allegro-common-lisp/]]) - -[[http://www.cliki.net/cl-launch][cl-launch]] to build command line runnable scripts/images for the above +- Steel Bank Common Lisp [[http://sbcl.org/]] +- Clozure Common Lisp [[http://ccl.clozure.com/]] +- CMU Common Lisp [[https://www.cons.org/cmucl/]] +- GNU CLISP [[http://www.clisp.org/]] +- Embeddable Common Lisp [[https://common-lisp.net/project/ecl/]] +- Allegro CL [[http://franz.com/products/allegro-common-lisp/]] +- Armed Bear Common Lisp [[http://abcl.org/]] + +[[http://www.cliki.net/cl-launch][cl-launch]] to build command line runnable scripts/images for most of the above implementations. ** Dependencies -*** [[http://www.cliki.net/cl-launch][cl-launch]] - For building command line executable scripts +- cl-launch + For building command line executable scripts. See [[http://www.cliki.net/cl-launch][cl-launch]] -*** [[https://www.quicklisp.org/beta/][quicklisp]] - For installing dependencies +- quicklisp + For installing dependencies. See [[https://www.quicklisp.org/beta/][quicklisp]] -*** ~readline~ (~libreadline-dev~ on Ubuntu) - For readline integration. If you wish to run the implementation using Allegro - CL, you will also have to install the 32 bit version of readline - (~lib32readline-dev~ on Ubuntu) -*** (Optional) ~asdf~ (~cl-asdf~ on Ubuntu) - This is needed if you want to run the implementation using GNU CLISP, since - GNU CLISP does not ship with ~asdf~ and ~cl-launch~ depends on it. +- readline + For readline integration. You can install it on Ubuntu using apt the package + is ~libreadline-dev~. If you wish to run the implementation using Allegro CL, + you will also have to install the 32 bit version of readline + (~lib32readline-dev~ on Ubuntu) + +- (Optional) asdf + This is needed if you want to run the implementation using GNU CLISP, since + GNU CLISP does not ship with ~asdf~ and ~cl-launch~ depends on it. You can + install it on Ubuntu using apt the package is ~cl-asdf~ ** Running using different implementations @@ -46,6 +50,7 @@ implementation. The nicknames that work currently are | GNU CLISP | clisp | | Embeddable Common Lisp | ecl | | Allegro CL | allegro | +| Armed Bear Common Lisp | abcl | |------------------------+----------| For example to build with GNU CLISP, you need to do the following @@ -67,6 +72,7 @@ be the capitalization of the given nickname. | GNU CLISP | CLISP | | Embeddable Common Lisp | ECL | | Allegro CL | ALLEGRO | +| Armed Bear Common Lisp | ABCL | |------------------------+-------------| For example to build MAL with Clozure CL installed in @@ -83,6 +89,6 @@ building it. ** Interop -There is some basically interop in the form ~cl-eval~ which takes a string and +There is some basic interop in the form ~cl-eval~ which takes a string and evaluates it as Common Lisp code, the result is returned in form of a MAL value, -you are limited to code that produces values that have MAL counterparts. +as such you are limited to code that produces values that have MAL counterparts. diff --git a/common-lisp/run-abcl.lisp b/common-lisp/run-abcl.lisp new file mode 100644 index 0000000000..73b869a420 --- /dev/null +++ b/common-lisp/run-abcl.lisp @@ -0,0 +1,10 @@ +(require 'asdf) +(push *default-pathname-defaults* asdf:*central-registry*) + +;; Suppress compilation output +(let ((*error-output* (make-broadcast-stream)) + (*standard-output* (make-broadcast-stream))) + (asdf:load-system (car ext:*command-line-argument-list*) :verbose nil)) + +(mal:main (cdr ext:*command-line-argument-list*)) +(cl-user::quit) From e0bcd3fb42659ff93c84bc7ac319bac7c5a1d7bc Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 10 Dec 2016 09:53:10 -0500 Subject: [PATCH 0269/2308] Basic: more efficient/correct file reader. - read one character at a time from the file instead of chunking it into the A$ string. - fix an overflow that was happening during reads of long forms. --- basic/core.in.bas | 23 ++++--- basic/debug.in.bas | 6 +- basic/reader.in.bas | 124 ++++++++++++++++++++++---------------- basic/step6_file.in.bas | 10 ++- basic/step7_quote.in.bas | 10 ++- basic/step8_macros.in.bas | 10 ++- basic/step9_try.in.bas | 10 ++- basic/stepA_mal.in.bas | 10 ++- basic/variables.txt | 6 +- 9 files changed, 126 insertions(+), 83 deletions(-) diff --git a/basic/core.in.bas b/basic/core.in.bas index e0ef5f1814..dcef1b84cd 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -257,27 +257,26 @@ DO_FUNCTION: RETURN DO_READLINE: A$=S$(A1):GOSUB READLINE - IF EZ=1 THEN EZ=0:R=0:GOTO INC_REF_R + IF EZ>0 THEN EZ=0:R=0:GOTO INC_REF_R B$=R$:T=4:GOSUB STRING RETURN DO_SLURP: R$="" - #cbm OPEN 1,8,0,S$(A1) + EZ=0 + #cbm OPEN 2,8,0,S$(A1) #qbasic A$=S$(A1) #qbasic IF NOT _FILEEXISTS(A$) THEN ER=-1:E$="File not found":RETURN - #qbasic OPEN A$ FOR INPUT AS #1 + #qbasic OPEN A$ FOR INPUT AS #2 DO_SLURP_LOOP: - A$="" - #cbm GET#1,A$ - #qbasic A$=INPUT$(1,1) - #qbasic IF EOF(1) THEN EZ=1:A$=A$+CHR$(10)+")":GOTO DO_SLURP_DONE - IF ASC(A$)=10 THEN R$=R$+CHR$(13) - IF (ASC(A$)<>10) AND (A$<>"") THEN R$=R$+A$ - #cbm IF (ST AND 64) THEN GOTO DO_SLURP_DONE - #cbm IF (ST AND 255) THEN ER=-1:E$="File read error "+STR$(ST):RETURN + C$="" + RJ=1:GOSUB READ_FILE_CHAR + IF ASC(C$)=10 THEN R$=R$+CHR$(13) + IF (ASC(C$)<>10) AND (C$<>"") THEN R$=R$+C$ + IF EZ>0 THEN GOTO DO_SLURP_DONE GOTO DO_SLURP_LOOP DO_SLURP_DONE: - CLOSE 1 + CLOSE 2 + IF ER>-2 THEN RETURN B$=R$:T=4:GOSUB STRING RETURN diff --git a/basic/debug.in.bas b/basic/debug.in.bas index e14452ecb4..2f3fd73498 100644 --- a/basic/debug.in.bas +++ b/basic/debug.in.bas @@ -19,12 +19,12 @@ PR_MEMORY_SUMMARY_SMALL: GOSUB CHECK_FREE_LIST #cbm PRINT "Free:"+STR$(FRE(0))+", "; PRINT "Values:"+STR$(ZI-1-P2)+", Emptys:"; - FOR I=0 TO 4 STEP 2:GOSUB PR_MEMORY_SUMMARY_SMALL_1:NEXT I - FOR I=6 TO 12 STEP 3:GOSUB PR_MEMORY_SUMMARY_SMALL_1:NEXT I + FOR P=0 TO 4 STEP 2:GOSUB PR_MEMORY_SUMMARY_SMALL_1:NEXT P + FOR P=6 TO 12 STEP 3:GOSUB PR_MEMORY_SUMMARY_SMALL_1:NEXT P PRINT RETURN PR_MEMORY_SUMMARY_SMALL_1: - PRINT STR$(INT(Z%(I)/32))+","; + PRINT STR$(INT(Z%(P)/32))+","; RETURN REM REM COUNT_STRINGS() -> P2 diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 85588a9e76..c982935313 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -1,59 +1,82 @@ -REM READ_TOKEN(A$, RI, RF) -> T$ +REM READ_TOKEN(RF=0, A$, RI) -> T$ +REM READ_TOKEN(RF=1) -> T$ READ_TOKEN: + IF RF=1 THEN RF=2:T$="(":RETURN + IF RF=2 THEN RF=3:T$="do":RETURN GOSUB SKIP_SPACES - RJ=RI - IF RF=1 THEN GOSUB READ_FILE_CHUNK - REM PRINT "READ_TOKEN: "+STR$(RJ)+", "+MID$(A$,RJ,1) - T$=MID$(A$,RJ,1) - IF T$=";" THEN GOSUB SKIP_TO_EOL:GOTO READ_TOKEN + REM PRINT "READ_TOKEN: "+STR$(RI)+", "+MID$(A$,RI,1) + GOSUB READ_CHAR + IF C$=";" THEN GOSUB SKIP_TO_EOL:GOTO READ_TOKEN + T$=C$ IF T$="(" OR T$=")" OR T$="[" OR T$="]" OR T$="{" OR T$="}" THEN RETURN IF T$="'" OR T$="`" OR T$="@" THEN RETURN - IF T$="~" AND NOT MID$(A$,RJ+1,1)="@" THEN RETURN + GOSUB PEEK_CHAR: REM peek at next character + IF T$="~" AND C$<>"@" THEN RETURN S1=0:S2=0: REM S1: INSTRING?, S2: ESCAPED? IF T$=CHR$(34) THEN S1=1 - RJ=RJ+1 READ_TOKEN_LOOP: - IF RF=1 THEN GOSUB READ_FILE_CHUNK - IF RJ>LEN(A$) THEN RETURN - C$=MID$(A$,RJ,1) - IF S2 THEN GOTO READ_TOKEN_CONT + GOSUB PEEK_CHAR: REM peek at next character + IF C$="" THEN RETURN IF S1 THEN GOTO READ_TOKEN_CONT - IF C$=" " OR C$="," THEN RETURN IF C$=" " OR C$="," OR C$=CHR$(13) OR C$=CHR$(10) THEN RETURN IF C$="(" OR C$=")" OR C$="[" OR C$="]" OR C$="{" OR C$="}" THEN RETURN READ_TOKEN_CONT: + GOSUB READ_CHAR T$=T$+C$ IF T$="~@" THEN RETURN - RJ=RJ+1 - IF S1 AND S2 THEN S2=0:GOTO READ_TOKEN_LOOP - IF S1 AND S2=0 AND C$=CHR$(92) THEN S2=1:GOTO READ_TOKEN_LOOP - IF S1 AND S2=0 AND C$=CHR$(34) THEN RETURN + IF S1=0 OR S2=1 THEN S2=0:GOTO READ_TOKEN_LOOP + REM S1=1 (INSTRING?) and S2=0 (not ESCAPED?) + IF C$=CHR$(92) THEN S2=1 + IF C$=CHR$(34) THEN RETURN GOTO READ_TOKEN_LOOP -READ_FILE_CHUNK: - IF EZ=1 THEN RETURN - IF RI>1 THEN A$=MID$(A$,RI,LEN(A$)-RI+1):RI=1:RJ=RJ-RI+1 - READ_FILE_CHUNK_LOOP: - IF LEN(A$)>RJ+9 THEN RETURN - #cbm GET#2,C$ - #qbasic C$=INPUT$(1,2) - #qbasic IF EOF(2) THEN EZ=1:A$=A$+CHR$(10)+")":RETURN - A$=A$+C$ - #cbm IF (ST AND 64) THEN EZ=1:A$=A$+CHR$(10)+")":RETURN - #cbm IF (ST AND 255) THEN EZ=1:ER=-1:E$="File read error "+STR$(ST):RETURN - GOTO READ_FILE_CHUNK_LOOP + +REM READ_CHAR(A$, RI) -> C$ +READ_CHAR: + RJ=1:GOSUB DO_READ_CHAR + RETURN + +REM PEEK_CHAR(A$, RI) -> C$ +PEEK_CHAR: + RJ=0:GOSUB DO_READ_CHAR + RETURN + +REM DO_READ_CHAR(RJ, A$, RI): +REM - RI is position in A$ +REM - RJ=1 is read, RJ=0 is peek +DO_READ_CHAR: + C$="" + IF RF>0 THEN GOTO READ_FILE_CHAR + IF RI<=LEN(A$) THEN C$=MID$(A$,RI,1):RI=RI+RJ + RETURN + +REM READ_FILE_CHAR(RJ) -> C$ +REM - RJ=1 is read, RJ=0 is peek +REM - D$ is global used for already read pending character +REM - EZ is global used for end of file state +READ_FILE_CHAR: + IF D$<>"" THEN C$=D$:IF RJ=0 THEN RETURN + IF D$<>"" AND RJ=1 THEN D$="":RETURN + D$="" + IF EZ>2 THEN C$="" + IF EZ=2 THEN C$=")" + IF EZ=1 THEN C$=CHR$(10) + IF EZ>0 THEN EZ=EZ+RJ:RETURN + #cbm GET#2,C$ + #qbasic C$=INPUT$(1,2) + #qbasic IF EOF(2) THEN EZ=1:RETURN + IF RJ=0 THEN D$=C$ + #cbm IF (ST AND 64) THEN EZ=1:RETURN + #cbm IF (ST AND 255) THEN EZ=1:ER=-1:E$="File read error"+STR$(ST) + RETURN SKIP_SPACES: - IF RF=1 THEN GOSUB READ_FILE_CHUNK - C$=MID$(A$,RI,1) - IF C$<>" " AND C$<>"," AND C$<>CHR$(13) AND C$<>CHR$(10) THEN RETURN - RI=RI+1 - GOTO SKIP_SPACES + GOSUB PEEK_CHAR: REM peek at next character + IF C$=" " OR C$="," OR C$=CHR$(13) OR C$=CHR$(10) THEN GOSUB READ_CHAR:GOTO SKIP_SPACES + RETURN SKIP_TO_EOL: - IF RF=1 THEN GOSUB READ_FILE_CHUNK - C$=MID$(A$,RI+1,1) - RI=RI+1 + GOSUB READ_CHAR IF C$="" OR C$=CHR$(13) OR C$=CHR$(10) THEN RETURN GOTO SKIP_TO_EOL @@ -83,9 +106,9 @@ SUB READ_FORM IF C$=CHR$(34) THEN GOTO READ_STRING IF C$=":" THEN GOTO READ_KEYWORD REM set end character in Q and read the sequence - IF C$="(" THEN T=6:Q=ASC(")"):GOTO READ_SEQ_START - IF C$="[" THEN T=7:Q=ASC("]"):GOTO READ_SEQ_START - IF C$="{" THEN T=8:Q=ASC("}"):GOTO READ_SEQ_START + IF C$="(" THEN T=6:Q=41:GOTO READ_SEQ_START: REM ")" + IF C$="[" THEN T=7:Q=93:GOTO READ_SEQ_START: REM "]" + IF C$="{" THEN T=8:Q=125:GOTO READ_SEQ_START: REM "}" IF C$=")" OR C$="]" OR C$="}" THEN R=-1:ER=-1:E$="unexpected "+C$:GOTO READ_FORM_RETURN GOTO READ_SYMBOL @@ -99,7 +122,6 @@ SUB READ_FORM T=2:L=VAL(T$):GOSUB ALLOC GOTO READ_FORM_RETURN READ_MACRO: - RI=RI+LEN(T$) REM push macro type Q=-1*(T$="^"):GOSUB PUSH_Q @@ -166,19 +188,22 @@ SUB READ_FORM GOTO READ_FORM_RETURN READ_SEQ_START: - RI=RI+LEN(T$) SD=SD+1 GOSUB PUSH_Q: REM push return character - REM setup the stack for the loop + REM setup the stack for the loop, T has type GOSUB MAP_LOOP_START READ_SEQ_LOOP: - GOSUB READ_TOKEN: REM peek at token - IF T$="" THEN ER=-1:E$="unexpected EOF" + + REM TODO: reduce redundancy with READ_TOKEN + GOSUB SKIP_SPACES + GOSUB PEEK_CHAR: REM peek at next character + IF C$="" THEN ER=-1:E$="unexpected EOF":GOTO READ_SEQ_DONE + IF C$=";" THEN GOSUB SKIP_TO_EOL:GOTO READ_SEQ_LOOP Q=3:GOSUB PEEK_Q_Q - IF ER<>-2 OR T$=CHR$(Q) THEN GOTO READ_SEQ_DONE + IF C$=CHR$(Q) THEN GOSUB READ_CHAR:GOTO READ_SEQ_DONE CALL READ_FORM M=R: REM value (or key for hash-maps) @@ -205,7 +230,6 @@ SUB READ_FORM GOTO READ_FORM_RETURN READ_FORM_RETURN: - RI=RI+LEN(T$) GOSUB POP_Q:T=Q: REM restore current value of T END SUB @@ -221,16 +245,14 @@ READ_STR: REM READ_FILE(A$) -> R READ_FILE: - RI=1: REM index into A$ - RJ=1: REM READ_TOKEN sub-index RF=1: REM reading from file EZ=0: REM file read state (1: EOF) SD=0: REM sequence read depth + D$="": REM pending read/peek character #cbm OPEN 2,8,0,A$ #qbasic IF NOT _FILEEXISTS(A$) THEN ER=-1:E$="File not found":RETURN #qbasic OPEN A$ FOR INPUT AS #2 - REM READ_FILE_CHUNK adds terminating ")" - A$="(do " + REM READ_TOKEN adds "(do ... )" CALL READ_FORM CLOSE 2 EZ=0 diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index b289c69c7a..86e664a85d 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -379,6 +379,8 @@ MAIN: A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" GOSUB RE:AY=R:GOSUB RELEASE + IF ER>-2 THEN GOSUB PRINT_ERROR:END + REM set the argument list A$="(def! *ARGV* (rest -*ARGS*-))" GOSUB RE:AY=R:GOSUB RELEASE @@ -387,12 +389,14 @@ MAIN: A$="(first -*ARGS*-)" GOSUB RE - REM if there is an argument, then run it as a program - IF R<>0 THEN AY=R:GOSUB RELEASE:GOTO RUN_PROG REM no arguments, start REPL loop - IF R=0 THEN GOTO REPL_LOOP + IF R<16 THEN GOTO REPL_LOOP + + REM if there is an argument, then run it as a program RUN_PROG: + REM free up first arg because we get it again + AY=R:GOSUB RELEASE REM run a single mal program and exit A$="(load-file (first -*ARGS*-))" GOSUB RE diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index 8d0fd318bc..909757db51 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -469,6 +469,8 @@ MAIN: A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" GOSUB RE:AY=R:GOSUB RELEASE + IF ER>-2 THEN GOSUB PRINT_ERROR:END + REM set the argument list A$="(def! *ARGV* (rest -*ARGS*-))" GOSUB RE:AY=R:GOSUB RELEASE @@ -477,12 +479,14 @@ MAIN: A$="(first -*ARGS*-)" GOSUB RE - REM if there is an argument, then run it as a program - IF R<>0 THEN AY=R:GOSUB RELEASE:GOTO RUN_PROG REM no arguments, start REPL loop - IF R=0 THEN GOTO REPL_LOOP + IF R<16 THEN GOTO REPL_LOOP + + REM if there is an argument, then run it as a program RUN_PROG: + REM free up first arg because we get it again + AY=R:GOSUB RELEASE REM run a single mal program and exit A$="(load-file (first -*ARGS*-))" GOSUB RE diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index 3f782b1e45..6ded89414e 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -545,6 +545,8 @@ MAIN: A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" GOSUB RE:AY=R:GOSUB RELEASE + IF ER>-2 THEN GOSUB PRINT_ERROR:END + REM set the argument list A$="(def! *ARGV* (rest -*ARGS*-))" GOSUB RE:AY=R:GOSUB RELEASE @@ -553,12 +555,14 @@ MAIN: A$="(first -*ARGS*-)" GOSUB RE - REM if there is an argument, then run it as a program - IF R<>0 THEN AY=R:GOSUB RELEASE:GOTO RUN_PROG REM no arguments, start REPL loop - IF R=0 THEN GOTO REPL_LOOP + IF R<16 THEN GOTO REPL_LOOP + + REM if there is an argument, then run it as a program RUN_PROG: + REM free up first arg because we get it again + AY=R:GOSUB RELEASE REM run a single mal program and exit A$="(load-file (first -*ARGS*-))" GOSUB RE diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index bbc4cac89d..e9b35adb0e 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -577,6 +577,8 @@ MAIN: A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" GOSUB RE:AY=R:GOSUB RELEASE + IF ER>-2 THEN GOSUB PRINT_ERROR:END + REM set the argument list A$="(def! *ARGV* (rest -*ARGS*-))" GOSUB RE:AY=R:GOSUB RELEASE @@ -585,12 +587,14 @@ MAIN: A$="(first -*ARGS*-)" GOSUB RE - REM if there is an argument, then run it as a program - IF R<>0 THEN AY=R:GOSUB RELEASE:GOTO RUN_PROG REM no arguments, start REPL loop - IF R=0 THEN GOTO REPL_LOOP + IF R<16 THEN GOTO REPL_LOOP + + REM if there is an argument, then run it as a program RUN_PROG: + REM free up first arg because we get it again + AY=R:GOSUB RELEASE REM run a single mal program and exit A$="(load-file (first -*ARGS*-))" GOSUB RE diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index e12f236e6c..c75c2e9403 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -589,6 +589,8 @@ MAIN: A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))" GOSUB RE:AY=R:GOSUB RELEASE + IF ER>-2 THEN GOSUB PRINT_ERROR:END + REM set the argument list A$="(def! *ARGV* (rest -*ARGS*-))" GOSUB RE:AY=R:GOSUB RELEASE @@ -597,12 +599,14 @@ MAIN: A$="(first -*ARGS*-)" GOSUB RE - REM if there is an argument, then run it as a program - IF R<>0 THEN AY=R:GOSUB RELEASE:GOTO RUN_PROG REM no arguments, start REPL loop - IF R=0 THEN GOTO REPL + IF R<16 THEN GOTO REPL + + REM if there is an argument, then run it as a program RUN_PROG: + REM free up first arg because we get it again + AY=R:GOSUB RELEASE REM run a single mal program and exit A$="(load-file (first -*ARGS*-))" GOSUB RE diff --git a/basic/variables.txt b/basic/variables.txt index 049be887e0..dd6cbf8a4f 100644 --- a/basic/variables.txt +++ b/basic/variables.txt @@ -54,8 +54,8 @@ Q : PUSH*, POP*, PEEK* return value (and PEEK_Q_Q call arg) AR : APPLY, DO_*_FUNCTION arg list AY : RELEASE/FREE arg AZ : PR_STR arg -P1 : PR_MEMORY, PR_OBJECT, CHECK_FREE_LIST start -P2 : PR_MEMORY, PR_OBJECT, CHECK_FREE_LIST end +P1 : PR_MEMORY*, PR_OBJECT, CHECK_FREE_LIST start +P2 : PR_MEMORY*, PR_OBJECT, CHECK_FREE_LIST end P3 : PR_OBJECT, PR_MEMORY_VALUE R1 : REP, RE - MAL_READ result temp R2 : REP, RE - EVAL result temp @@ -81,12 +81,14 @@ RD : PR_OBJECT recursion depth SD : READ_STR sequence read recursion depth C$ : READ_TOKEN, SKIP_SPACES, SKIP_TO_EOL current character +D$ : READ_TOKEN/READ_FILE_CHAR temp G : function value ON GOTO switch flag, EVAL_AST changed flag I : STRING, REPLACE, SLICE, PR_MEMORY, PR_OBJECT, PR_MEMORY_VALUE J : REPLACE, PR_MEMORY_VALUE U : ALLOC, RELEASE, PR_STR temp V : RELEASE, PR_STR_SEQ temp W : SLICE, LAST, QUASIQUOTE, DO_HASH_MAP, DO_KEYS_VALS, step2-3 EVAL temp +P : PR_MEMORY_SUMMARY_SMALL RC : RELEASE remaining number of elements to release RF : reader reading from file flag S1 : READ_TOKEN in a string? From 7895453b77eefa944013ab1a3ce0a03ed5407b16 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 10 Dec 2016 11:50:40 -0500 Subject: [PATCH 0270/2308] Basic: various memory savings. - simplify DO_CONCAT. - inline MAL_READ/PRINT. - comment out memory debug/sanity checks. - more aggressive space removal. Saves over 900 bytes. Increase Z% value memory by 374 to 9216 (8192+1024). --- basic/basicpp.py | 8 +++-- basic/core.in.bas | 72 +++++++++++++++++++----------------------- basic/debug.in.bas | 56 ++++++++++++++++---------------- basic/mem.in.bas | 27 +++++++++------- basic/printer.in.bas | 6 ++-- basic/reader.in.bas | 3 +- basic/stepA_mal.in.bas | 19 ++++------- basic/types.in.bas | 2 +- 8 files changed, 93 insertions(+), 100 deletions(-) diff --git a/basic/basicpp.py b/basic/basicpp.py index dac2e0ed2b..fbe1c41f27 100755 --- a/basic/basicpp.py +++ b/basic/basicpp.py @@ -105,14 +105,18 @@ def misc_fixups(orig_lines): text = re.sub(r"\OPEN ", "OPEN", text) text = re.sub(r"\bGET ", "GET", text) text = re.sub(r"\bPOKE ", "POKE", text) + text = re.sub(r"\bCLOSE ", "CLOSE", text) + text = re.sub(r"\bFOR ", "FOR", text) + text = re.sub(r" TO ", "TO", text) + text = re.sub(r"\bNEXT ", "NEXT", text) # Remove spaces around GOTO/GOSUB/THEN text = re.sub(r" *GOTO *", "GOTO", text) text = re.sub(r" *GOSUB *", "GOSUB", text) text = re.sub(r" *THEN *", r"THEN", text) - # Remove spaces around AND/OR except after variables - text = re.sub(r"([^A-Z]) *AND *", r"\g<1>AND", text) + # Remove spaces around AND/OR except after ST + text = re.sub(r"(?OR", text) return text.split("\n") diff --git a/basic/core.in.bas b/basic/core.in.bas index dcef1b84cd..4cce38a68f 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -172,7 +172,8 @@ DO_FUNCTION: B=Z%(Z%(AR+1)+2):B1=Z%(B+1) REM Switch on the function number - IF G>59 THEN ER=-1:E$="unknown function"+STR$(G):RETURN + REM MEMORY DEBUGGING: + REM IF G>59 THEN ER=-1:E$="unknown function"+STR$(G):RETURN ON INT(G/10)+1 GOTO DO_1_9,DO_10_19,DO_20_29,DO_30_39,DO_40_49,DO_50_59 DO_1_9: @@ -406,48 +407,41 @@ DO_FUNCTION: T=6:L=B:M=A:GOSUB ALLOC RETURN DO_CONCAT: - REM if empty arguments, return empty list - IF Z%(AR+1)=0 THEN R=6:GOTO INC_REF_R + REM always a list + R=6:GOSUB INC_REF_R + GOSUB PUSH_R: REM current value + GOSUB PUSH_R: REM return value - REM single argument - IF Z%(Z%(AR+1)+1)<>0 THEN GOTO DO_CONCAT_MULT - REM force to list type - T=6:GOSUB FORCE_SEQ_TYPE - RETURN + DO_CONCAT_LOOP: + IF AR<16 THEN GOTO DO_CONCAT_DONE: REM no more elements + + REM slice/copy current element to a list + A=Z%(AR+2) + IF A<16 THEN GOTO DO_CONCAT_LOOP_NEXT: REM skip empty elements + B=0:C=-1:GOSUB SLICE - REM multiple arguments - DO_CONCAT_MULT: - REM TODO: something other than direct X access? - CZ=X: REM save current stack position - REM push arguments onto the stack - DO_CONCAT_STACK: - R=Z%(AR+2) - GOSUB PUSH_R: REM push sequence + GOSUB PEEK_Q: REM return value + REM if this is the first element, set return element + IF Q=6 THEN Q=R:GOSUB PUT_Q:GOTO DO_CONCAT_LOOP_AGAIN + REM otherwise Q<>6, so attach current to sliced + GOSUB PEEK_Q_1 + Z%(Q+1)=R + + DO_CONCAT_LOOP_AGAIN: + REM update current to end of sliced list + Q=R6:GOSUB PUT_Q_1 + REM dec empty since no longer part of slice + AY=6:GOSUB RELEASE + DO_CONCAT_LOOP_NEXT: + REM next list element AR=Z%(AR+1) - IF Z%(AR+1)<>0 THEN GOTO DO_CONCAT_STACK + GOTO DO_CONCAT_LOOP + + DO_CONCAT_DONE: + GOSUB POP_R: REM pop return value + GOSUB POP_Q: REM pop current + RETURN - REM pop last argument as our seq to prepend to - GOSUB POP_Q:B=Q - REM last arg/seq is not copied so we need to inc ref to it - Z%(B)=Z%(B)+32 - DO_CONCAT_LOOP: - IF X=CZ THEN R=B:RETURN - GOSUB POP_A: REM pop off next seq to prepend - IF Z%(A+1)=0 THEN GOTO DO_CONCAT_LOOP: REM skip empty seqs - Q=B:GOSUB PUSH_Q - B=0:C=-1:GOSUB SLICE - GOSUB POP_Q:B=Q - - REM release the terminator of new list (we skip over it) - REM we already checked for an empty list above, so R6 is pointer - REM a real non-empty list - AY=Z%(R6+1):GOSUB RELEASE - REM attach new list element before terminator (last actual - REM element to the next sequence - Z%(R6+1)=B - - B=R - GOTO DO_CONCAT_LOOP DO_NTH: B=B1 GOSUB COUNT diff --git a/basic/debug.in.bas b/basic/debug.in.bas index 2f3fd73498..4d8b1ff297 100644 --- a/basic/debug.in.bas +++ b/basic/debug.in.bas @@ -4,14 +4,12 @@ CHECK_FREE_LIST: P1=ZK P2=0 CHECK_FREE_LIST_LOOP: - IF P1>=ZI THEN GOTO CHECK_FREE_LIST_DONE - IF (Z%(P1)AND 31)<>15 THEN P2=-1:GOTO CHECK_FREE_LIST_DONE + IF P1>=ZI THEN RETURN + REM MEMORY DEBUGGING: + REM IF (Z%(P1)AND 31)<>15 THEN PRINT "corrupt free:"+STR$(P1):END P2=P2+(Z%(P1)AND-32)/32 P1=Z%(P1+1) GOTO CHECK_FREE_LIST_LOOP - CHECK_FREE_LIST_DONE: - IF P2=-1 THEN PRINT "corrupt free list at "+STR$(P1) - RETURN PR_MEMORY_SUMMARY_SMALL: #cbm P0=FRE(0) @@ -51,30 +49,30 @@ REM PRINT "Strings (S$) :"+STR$(P2)+" /"+STR$(Z2) REM #qbasic PRINT "Stack (X%) :"+STR$(X+1)+" /"+STR$(Z3) REM #cbm PRINT "Stack :"+STR$(X+2-Z3)+" / 1920" REM RETURN - -#cbm PR_MEMORY_MAP: - #cbm PRINT - #cbm P1=PEEK(43)+PEEK(44)*256 - #cbm P2=PEEK(45)+PEEK(46)*256 - #cbm P3=PEEK(47)+PEEK(48)*256 - #cbm P4=PEEK(49)+PEEK(50)*256 - #cbm P5=PEEK(51)+PEEK(52)*256 - #cbm P6=PEEK(53)+PEEK(54)*256 - #cbm P7=PEEK(55)+PEEK(56)*256 - #cbm PRINT "BASIC beg. :"STR$(P1) - #cbm PRINT "Variable beg.:"STR$(P2) - #cbm PRINT "Array beg. :"STR$(P3) - #cbm PRINT "Array end :"STR$(P4) - #cbm PRINT "String beg. :"STR$(P5) - #cbm PRINT "String cur. :"STR$(P6) - #cbm PRINT "BASIC end :"STR$(P7) - #cbm PRINT - #cbm PRINT "Program Code :"STR$(P2-P1) - #cbm PRINT "Variables :"STR$(P3-P2) - #cbm PRINT "Arrays :"STR$(P4-P3) - #cbm PRINT "String Heap :"STR$(P7-P5) - #cbm RETURN - +REM +REM #cbm PR_MEMORY_MAP: +REM #cbm PRINT +REM #cbm P1=PEEK(43)+PEEK(44)*256 +REM #cbm P2=PEEK(45)+PEEK(46)*256 +REM #cbm P3=PEEK(47)+PEEK(48)*256 +REM #cbm P4=PEEK(49)+PEEK(50)*256 +REM #cbm P5=PEEK(51)+PEEK(52)*256 +REM #cbm P6=PEEK(53)+PEEK(54)*256 +REM #cbm P7=PEEK(55)+PEEK(56)*256 +REM #cbm PRINT "BASIC beg. :"STR$(P1) +REM #cbm PRINT "Variable beg.:"STR$(P2) +REM #cbm PRINT "Array beg. :"STR$(P3) +REM #cbm PRINT "Array end :"STR$(P4) +REM #cbm PRINT "String beg. :"STR$(P5) +REM #cbm PRINT "String cur. :"STR$(P6) +REM #cbm PRINT "BASIC end :"STR$(P7) +REM #cbm PRINT +REM #cbm PRINT "Program Code :"STR$(P2-P1) +REM #cbm PRINT "Variables :"STR$(P3-P2) +REM #cbm PRINT "Arrays :"STR$(P4-P3) +REM #cbm PRINT "String Heap :"STR$(P7-P5) +REM #cbm RETURN +REM REM REM PR_MEMORY_VALUE(I) -> J: REM REM - I is memory value to print REM REM - I is returned as last byte of value printed diff --git a/basic/mem.in.bas b/basic/mem.in.bas index 6144c75b1f..b40ec809bd 100644 --- a/basic/mem.in.bas +++ b/basic/mem.in.bas @@ -1,5 +1,5 @@ REM Memory layout: -REM +REM REM type bytes REM ---------- ---------- REM nil ref/ 0 | 0 | | @@ -20,7 +20,7 @@ REM environment ref/13 | hmap Z% idx | outer Z% idx | REM metadata ref/14 | obj Z% idx | meta Z% idx | REM FREE sz/15 | next Z% idx | | REM -REM The first 15 locations are constant/persistent values: +REM Locations 0-15 are for constant/persistent values: REM 0: nil REM 2: false REM 4: true @@ -39,12 +39,12 @@ REM stack functions #qbasic X=X+1:X%(X)=A:RETURN #qbasic POP_A: #qbasic A=X%(X):X=X-1:RETURN -#qbasic +#qbasic #qbasic PUSH_R: #qbasic X=X+1:X%(X)=R:RETURN #qbasic POP_R: #qbasic R=X%(X):X=X-1:RETURN -#qbasic +#qbasic #qbasic PUSH_Q: #qbasic X=X+1:X%(X)=Q:RETURN #qbasic POP_Q: @@ -68,12 +68,12 @@ REM stack functions #cbm X=X+2:POKE X,A AND255:POKE X+1,A/256:RETURN #cbm POP_A: #cbm A=PEEK(X)+PEEK(X+1)*256:X=X-2:RETURN -#cbm +#cbm #cbm PUSH_R: #cbm X=X+2:POKE X,R AND255:POKE X+1,R/256:RETURN #cbm POP_R: #cbm R=PEEK(X)+PEEK(X+1)*256:X=X-2:RETURN -#cbm +#cbm #cbm PUSH_Q: #cbm X=X+2:POKE X,Q AND255:POKE X+1,Q/256:RETURN #cbm POP_Q: @@ -125,7 +125,7 @@ ALLOC: GOTO ALLOC_DONE ALLOC_UNUSED: REM PRINT "ALLOC_UNUSED ZI: "+STR$(ZI)+", U: "+STR$(U)+", R: "+STR$(R) - IF R+SZ>Z1 THEN PRINT "Out of mal memory!":END + IF R+SZ>Z1 THEN GOSUB PR_MEMORY_SUMMARY_SMALL:PRINT "Out of mal memory!":END ZI=ZI+SZ IF U=R THEN ZK=ZI REM set previous free to new memory top @@ -184,14 +184,16 @@ RELEASE: REM PRINT "RELEASE AY:"+STR$(AY)+" ["+R$+"] (byte0:"+STR$(Z%(AY))+", SZ:"+STR$(SZ)+")" REM sanity check not already freed - IF (U)=15 THEN PRINT "RELEASE of free:"+STR$(AY):END - IF Z%(AY)<15 THEN PRINT "RELEASE of unowned:"+STR$(AY):END + REM MEMORY DEBUGGING: + REM IF U=15 THEN PRINT "RELEASE of free:"+STR$(AY):END + REM IF Z%(AY)<15 THEN PRINT "RELEASE of unowned:"+STR$(AY):END REM decrease reference count by one Z%(AY)=Z%(AY)-32 REM nil, false, true, empty sequences - IF AY<16 AND Z%(AY)<32 THEN PRINT "RELEASE of empty:"+STR$(AY):END + REM MEMORY DEBUGGING: + REM IF AY<16 AND Z%(AY)<32 THEN PRINT "RELEASE of empty:"+STR$(AY):END IF AY<16 THEN GOTO RELEASE_TOP REM our reference count is not 0, so don't release @@ -208,7 +210,8 @@ RELEASE: RETURN RELEASE_STRING: REM string type, release interned string, then FREE reference - IF S%(V)=0 THEN ER=-1:E$="RELEASE of free string:"+STR$(S%(V)):RETURN + REM MEMORY DEBUGGING: + REM IF S%(V)=0 THEN PRINT "RELEASE of free string:"+STR$(S%(V)):END S%(V)=S%(V)-1 IF S%(V)=0 THEN S$(V)="": REM free BASIC string REM free the atom itself @@ -306,7 +309,7 @@ INIT_MEMORY: #cbm T=FRE(0) #qbasic T=0 - Z1=8191+650: REM Z% (boxed memory) size (2 bytes each) + Z1=8191+1024: REM Z% (boxed memory) size (2 bytes each) Z2=199: REM S$/S% (string memory) size (3+2 bytes each) #qbasic Z3=200: REM X% (call stack) size (2 bytes each) #cbm Z3=49152: REM X starting point at $C000 (2 bytes each) diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 067d617e1b..41e75822f1 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -11,7 +11,8 @@ PR_STR: ON T GOTO PR_BOOLEAN,PR_INTEGER,PR_UNKNOWN,PR_STRING_MAYBE,PR_SYMBOL,PR_SEQ,PR_SEQ,PR_SEQ,PR_FUNCTION,PR_MAL_FUNCTION,PR_MAL_FUNCTION,PR_ATOM,PR_ENV,PR_RECUR,PR_FREE PR_UNKNOWN: - R$="#" + REM MEMORY DEBUGGING: + REM R$="#" RETURN PR_RECUR: AZ=U @@ -76,8 +77,7 @@ PR_STR: IF T=8 THEN R$="{"+R$+"}" RETURN PR_FUNCTION: - T1=U - R$="#" + R$="#" RETURN PR_MAL_FUNCTION: T1=AZ diff --git a/basic/reader.in.bas b/basic/reader.in.bas index c982935313..955cabc4e2 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -8,8 +8,7 @@ READ_TOKEN: GOSUB READ_CHAR IF C$=";" THEN GOSUB SKIP_TO_EOL:GOTO READ_TOKEN T$=C$ - IF T$="(" OR T$=")" OR T$="[" OR T$="]" OR T$="{" OR T$="}" THEN RETURN - IF T$="'" OR T$="`" OR T$="@" THEN RETURN + IF T$="(" OR T$=")" OR T$="[" OR T$="]" OR T$="{" OR T$="}" OR T$="'" OR T$="`" OR T$="@" THEN RETURN GOSUB PEEK_CHAR: REM peek at next character IF T$="~" AND C$<>"@" THEN RETURN S1=0:S2=0: REM S1: INSTRING?, S2: ESCAPED? diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index c75c2e9403..9579bed7c4 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -10,10 +10,7 @@ REM $INCLUDE: 'core.in.bas' REM $INCLUDE: 'debug.in.bas' -REM READ(A$) -> R -MAL_READ: - GOSUB READ_STR - RETURN +REM READ is inlined in RE REM QUASIQUOTE(A) -> R SUB QUASIQUOTE @@ -132,7 +129,7 @@ SUB EVAL_AST GOSUB TYPE_A IF T=5 THEN GOTO EVAL_AST_SYMBOL - IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ + IF T>5 AND T<9 THEN GOTO EVAL_AST_SEQ REM scalar: deref to actual value and inc ref cnt R=A @@ -506,17 +503,15 @@ SUB EVAL END SUB -REM PRINT(A) -> R$ -MAL_PRINT: - AZ=A:B=1:GOSUB PR_STR - RETURN +REM PRINT is inlined in REP + REM RE(A$) -> R REM Assume D has repl_env REM caller must release result RE: R1=-1 - GOSUB MAL_READ + GOSUB READ_STR: REM inlined MAL_READ R1=R IF ER<>-2 THEN GOTO RE_DONE @@ -536,7 +531,7 @@ SUB REP R2=R IF ER<>-2 THEN GOTO REP_DONE - A=R:GOSUB MAL_PRINT + AZ=R:B=1:GOSUB PR_STR: REM MAL_PRINT REP_DONE: REM Release memory from MAL_READ and EVAL @@ -633,7 +628,7 @@ MAIN: QUIT: REM GOSUB PR_MEMORY_SUMMARY_SMALL PRINT:GOSUB PR_MEMORY_SUMMARY_SMALL - GOSUB PR_MEMORY_MAP + REM GOSUB PR_MEMORY_MAP REM P1=0:P2=ZI:GOSUB PR_MEMORY REM P1=D:GOSUB PR_OBJECT REM P1=ZK:GOSUB PR_OBJECT diff --git a/basic/types.in.bas b/basic/types.in.bas index cf63927490..a5853e1e8d 100644 --- a/basic/types.in.bas +++ b/basic/types.in.bas @@ -221,7 +221,7 @@ SLICE: W=R: REM temporary for return as R REM advance A to position B SLICE_FIND_B: - IF I0 THEN A=Z%(A,1):I=I+1:GOTO SLICE_FIND_B + IF I0 THEN A=Z%(A+1):I=I+1:GOTO SLICE_FIND_B SLICE_LOOP: REM if current position is C, then return IF C<>-1 AND I>=C THEN R=W:RETURN From 4a445e84932d9f402b71019b5c095e4b06475ffe Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 13 Dec 2016 21:26:57 -0600 Subject: [PATCH 0271/2308] Basic: QBasic fixes. - restructure memory dim/initialization to support QBasic which requires all DIMs to be earlier in the code than references to the DIM'd variables (unlike C64 which just requires the DIMs to be called first logically). - Fix printed header ("C64 QBasic" -> "QBasic") --- basic/mem.in.bas | 89 +++++++++++++++++++++++++++--------------- basic/stepA_mal.in.bas | 2 +- 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/basic/mem.in.bas b/basic/mem.in.bas index b40ec809bd..a0065ec232 100644 --- a/basic/mem.in.bas +++ b/basic/mem.in.bas @@ -28,10 +28,35 @@ REM 6: empty list REM 9: empty vector REM 12: empty hash-map -REM Note: The INIT_MEMORY function is at end of this file for -REM efficiency. The most commonly used function should be at the top -REM since BASIC scans line numbers for every GOTO/GOSUB - +REM Note: DIM_MEMORY for C64 BASIC and the INIT_MEMORY function are at +REM end of this file for efficiency on C64. The most commonly used +REM function should be at the top since C64 BASIC scans line numbers +REM for every GOTO/GOSUB. On the other hand, QBasic requires that +REM arrays are dimensioned at the top of the file, not just as the +REM first operation on that array so DIM_MEMORY for QBasic is here at +REM the top. + +#qbasic DIM_MEMORY: +#qbasic T=0 +#qbasic +#qbasic Z1=8191+1424: REM Z% (boxed memory) size (2 bytes each) +#qbasic Z2=199: REM S$/S% (string memory) size (3+2 bytes each) +#qbasic Z3=200: REM X% (call stack) size (2 bytes each) +#qbasic Z4=64: REM Y% (release stack) size (4 bytes each) +#qbasic +#qbasic REM boxed element memory +#qbasic DIM Z%(Z1): REM TYPE ARRAY +#qbasic +#qbasic REM string memory storage +#qbasic S=0:DIM S$(Z2):DIM S%(Z2) +#qbasic +#qbasic REM call/logic stack +#qbasic X=-1:DIM X%(Z3): REM stack of Z% indexes +#qbasic +#qbasic REM pending release stack +#qbasic Y=-1:DIM Y%(Z4,1): REM stack of Z% indexes and level/LV values +#qbasic +#qbasic RETURN REM stack functions @@ -305,16 +330,35 @@ REM release stack functions #cbm GOTO RELEASE_PEND -INIT_MEMORY: - #cbm T=FRE(0) - #qbasic T=0 - Z1=8191+1024: REM Z% (boxed memory) size (2 bytes each) - Z2=199: REM S$/S% (string memory) size (3+2 bytes each) - #qbasic Z3=200: REM X% (call stack) size (2 bytes each) - #cbm Z3=49152: REM X starting point at $C000 (2 bytes each) - #qbasic Z4=64: REM Y% (release stack) size (4 bytes each) - #cbm Z4=52992: REM Y starting point at $CF00 (4 bytes each) +#cbm DIM_MEMORY: +#cbm T=FRE(0) +#cbm +#cbm Z1=8191+1424: REM Z% (boxed memory) size (2 bytes each) +#cbm Z2=199: REM S$/S% (string memory) size (3+2 bytes each) +#cbm Z3=49152: REM X starting point at $C000 (2 bytes each) +#cbm Z4=52992: REM Y starting point at $CF00 (4 bytes each) +#cbm +#cbm REM TODO: for performance, define all/most non-array variables here +#cbm REM so that the array area doesn't have to be shifted down everytime +#cbm REM a new non-array variable is defined +#cbm +#cbm REM boxed element memory +#cbm DIM Z%(Z1): REM TYPE ARRAY +#cbm +#cbm REM string memory storage +#cbm S=0:DIM S$(Z2):DIM S%(Z2) +#cbm +#cbm REM call/logic stack +#cbm X=Z3-2: REM stack of 1920 Z% indexes at $C000 +#cbm +#cbm REM pending release stack +#cbm Y=Z4-4: REM stack of 64 Y% indexes/levels at $CF00 +#cbm +#cbm RETURN + +INIT_MEMORY: + GOSUB DIM_MEMORY REM global error state REM -2 : no error @@ -323,13 +367,6 @@ INIT_MEMORY: ER=-2 E$="" - REM TODO: for performance, define all/most non-array variables here - REM so that the array area doesn't have to be shifted down everytime - REM a new non-array variable is defined - - REM boxed element memory - DIM Z%(Z1): REM TYPE ARRAY - REM Predefine nil, false, true, and an empty sequences FOR I=0 TO 15:Z%(I)=0:NEXT I Z%(0)=32: REM nil @@ -345,17 +382,7 @@ INIT_MEMORY: REM start of free list ZK=16 - REM string memory storage - S=0:DIM S$(Z2):DIM S%(Z2) - - REM call/logic stack - #qbasic X=-1:DIM X%(Z3): REM stack of Z% indexes - #cbm X=Z3-2: REM stack of 1920 Z% indexes at $C000 - - REM pending release stack - #qbasic Y=-1:DIM Y%(Z4,1): REM stack of Z% indexes and level/LV values - #cbm Y=Z4-4: REM stack of 64 Y% indexes/levels at $CF00 - + REM start of time clock BT=TI RETURN diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index 9579bed7c4..69b6485714 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -612,7 +612,7 @@ MAIN: REM print the REPL startup header REM save memory by printing this directly #cbm PRINT "Mal [C64 BASIC]" - #qbasic PRINT "Mal [C64 QBasic]" + #qbasic PRINT "Mal [QBasic]" REPL_LOOP: A$="user> ":GOSUB READLINE: REM call input parser From df8c925a24f6809fa37a28bb067dd34faa9a4dfe Mon Sep 17 00:00:00 2001 From: Peter Stephens Date: Mon, 26 Dec 2016 17:05:32 -0600 Subject: [PATCH 0272/2308] fsharp: added some top level exception catching so that the repl wouldn't quit to CLI on errors. --- fsharp/step2_eval.fs | 12 +++++++++--- fsharp/step3_env.fs | 12 +++++++++--- fsharp/step4_if_fn_do.fs | 12 +++++++++--- fsharp/step5_tco.fs | 12 +++++++++--- fsharp/step6_file.fs | 12 +++++++++--- fsharp/step7_quote.fs | 11 +++++++++-- fsharp/step8_macros.fs | 11 +++++++++-- fsharp/step9_try.fs | 11 +++++++++-- fsharp/stepA_mal.fs | 11 +++++++++-- 9 files changed, 81 insertions(+), 23 deletions(-) diff --git a/fsharp/step2_eval.fs b/fsharp/step2_eval.fs index 2db015f837..d14615f128 100644 --- a/fsharp/step2_eval.fs +++ b/fsharp/step2_eval.fs @@ -31,9 +31,15 @@ module REPL try Some(eval env ast) with - | Error.EvalError(msg) - | Error.ReaderError(msg) -> - printfn "%s" msg + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "%s" str + None + | Error.MalError(node) -> + printfn "%s" (Printer.pr_str [node]) + None + | ex -> + printfn "%s" (ex.Message) None let PRINT v = diff --git a/fsharp/step3_env.fs b/fsharp/step3_env.fs index 68226af319..4c7954ac37 100644 --- a/fsharp/step3_env.fs +++ b/fsharp/step3_env.fs @@ -67,9 +67,15 @@ module REPL try Some(eval env ast) with - | Error.EvalError(msg) - | Error.ReaderError(msg) -> - printfn "%s" msg + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "%s" str + None + | Error.MalError(node) -> + printfn "%s" (Printer.pr_str [node]) + None + | ex -> + printfn "%s" (ex.Message) None let PRINT v = diff --git a/fsharp/step4_if_fn_do.fs b/fsharp/step4_if_fn_do.fs index d6e6e5a266..5ffedda78a 100644 --- a/fsharp/step4_if_fn_do.fs +++ b/fsharp/step4_if_fn_do.fs @@ -103,9 +103,15 @@ module REPL try Some(eval env ast) with - | Error.EvalError(msg) - | Error.ReaderError(msg) -> - printfn "%s" msg + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "%s" str + None + | Error.MalError(node) -> + printfn "%s" (Printer.pr_str [node]) + None + | ex -> + printfn "%s" (ex.Message) None let PRINT v = diff --git a/fsharp/step5_tco.fs b/fsharp/step5_tco.fs index 7c0a7d30b0..ab96f441d4 100644 --- a/fsharp/step5_tco.fs +++ b/fsharp/step5_tco.fs @@ -105,9 +105,15 @@ module REPL try Some(eval env ast) with - | Error.EvalError(msg) - | Error.ReaderError(msg) -> - printfn "%s" msg + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "%s" str + None + | Error.MalError(node) -> + printfn "%s" (Printer.pr_str [node]) + None + | ex -> + printfn "%s" (ex.Message) None let PRINT v = diff --git a/fsharp/step6_file.fs b/fsharp/step6_file.fs index 584bf1b426..0d1af99744 100644 --- a/fsharp/step6_file.fs +++ b/fsharp/step6_file.fs @@ -105,9 +105,15 @@ module REPL try Some(eval env ast) with - | Error.EvalError(msg) - | Error.ReaderError(msg) -> - printfn "%s" msg + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "%s" str + None + | Error.MalError(node) -> + printfn "%s" (Printer.pr_str [node]) + None + | ex -> + printfn "%s" (ex.Message) None let PRINT v = diff --git a/fsharp/step7_quote.fs b/fsharp/step7_quote.fs index 8c2746398d..7fbe9d4d69 100644 --- a/fsharp/step7_quote.fs +++ b/fsharp/step7_quote.fs @@ -125,8 +125,15 @@ module REPL try Some(eval env ast) with - | Error.EvalError(msg) -> - printfn "%s" msg + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "%s" str + None + | Error.MalError(node) -> + printfn "%s" (Printer.pr_str [node]) + None + | ex -> + printfn "%s" (ex.Message) None let PRINT v = diff --git a/fsharp/step8_macros.fs b/fsharp/step8_macros.fs index ea0468152d..8a80b56d36 100644 --- a/fsharp/step8_macros.fs +++ b/fsharp/step8_macros.fs @@ -153,8 +153,15 @@ module REPL try Some(eval env ast) with - | Error.EvalError(msg) -> - printfn "%s" msg + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "%s" str + None + | Error.MalError(node) -> + printfn "%s" (Printer.pr_str [node]) + None + | ex -> + printfn "%s" (ex.Message) None let PRINT v = diff --git a/fsharp/step9_try.fs b/fsharp/step9_try.fs index 602a9e380c..babb2f1f4c 100644 --- a/fsharp/step9_try.fs +++ b/fsharp/step9_try.fs @@ -170,8 +170,15 @@ module REPL try Some(eval env ast) with - | Error.EvalError(msg) -> - printfn "%s" msg + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "%s" str + None + | Error.MalError(node) -> + printfn "%s" (Printer.pr_str [node]) + None + | ex -> + printfn "%s" (ex.Message) None let PRINT v = diff --git a/fsharp/stepA_mal.fs b/fsharp/stepA_mal.fs index 04ab54d8f7..8cdaa6836c 100644 --- a/fsharp/stepA_mal.fs +++ b/fsharp/stepA_mal.fs @@ -171,8 +171,15 @@ module REPL try Some(eval env ast) with - | Error.EvalError(msg) -> - printfn "%s" msg + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "%s" str + None + | Error.MalError(node) -> + printfn "%s" (Printer.pr_str [node]) + None + | ex -> + printfn "%s" (ex.Message) None let PRINT v = From e1b080b4019e42b064ff2f3c0f5b9027a5de1648 Mon Sep 17 00:00:00 2001 From: Jonas Lundberg Date: Fri, 3 Feb 2017 09:27:42 +0100 Subject: [PATCH 0273/2308] Update README with hint on test permission failing Ran into some problems with the /run file not having the executable flag set. Setting +x on the run file helps this. The error-message the python runner throws is: Traceback (most recent call last): File "../runtest.py", line 227, in r = Runner(args.mal_cmd, no_pty=args.no_pty) File "../runtest.py", line 105, in __init__ env=env) File "/usr/lib/python3.6/subprocess.py", line 707, in __init__ restore_signals, start_new_session) File "/usr/lib/python3.6/subprocess.py", line 1326, in _execute_child raise child_exception_type(errno_num, err_msg) PermissionError: [Errno 13] Permission denied make: *** [Makefile:310: test^mymal^step0] Error 1 --- process/guide.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/process/guide.md b/process/guide.md index 17447a433c..fa0dec29f2 100644 --- a/process/guide.md +++ b/process/guide.md @@ -109,7 +109,9 @@ quux_STEP_TO_PROG = mylang/$($(1)).qx * Add a "run" script to you implementation directory that listens to the "STEP" environment variable for the implementation step to run - and defaults to "stepA_mal". The following are examples of "run" + and defaults to "stepA_mal". Make sure the run script has the + executable file permission set (or else the test runner might fail with a + permission denied error message). The following are examples of "run" scripts for a compiled language and an interpreted language (where the interpreter is named "quux"): From fe3d4d3bafddd1a4e9954350da6f8356b718e54f Mon Sep 17 00:00:00 2001 From: Massimiliano Ghilardi Date: Mon, 6 Feb 2017 21:58:59 +0100 Subject: [PATCH 0274/2308] Go: Use type assertions where possible instead of reflection --- go/src/types/types.go | 83 ++++++++++++------------------------------- 1 file changed, 23 insertions(+), 60 deletions(-) diff --git a/go/src/types/types.go b/go/src/types/types.go index 98aa654efd..d5bff952ac 100644 --- a/go/src/types/types.go +++ b/go/src/types/types.go @@ -28,29 +28,17 @@ type EnvType interface { // Scalars func Nil_Q(obj MalType) bool { - if obj == nil { - return true - } else { - return false - } + return obj == nil } func True_Q(obj MalType) bool { - switch tobj := obj.(type) { - case bool: - return tobj == true - default: - return false - } + b, ok := obj.(bool) + return ok && b == true } func False_Q(obj MalType) bool { - switch tobj := obj.(type) { - case bool: - return tobj == false - default: - return false - } + b, ok := obj.(bool) + return ok && b == false } // Symbols @@ -59,10 +47,8 @@ type Symbol struct { } func Symbol_Q(obj MalType) bool { - if obj == nil { - return false - } - return reflect.TypeOf(obj).Name() == "Symbol" + _, ok := obj.(Symbol) + return ok } // Keywords @@ -71,23 +57,14 @@ func NewKeyword(s string) (MalType, error) { } func Keyword_Q(obj MalType) bool { - if obj == nil { - return false - } - switch s := obj.(type) { - case string: - return strings.HasPrefix(s, "\u029e") - default: - return false - } + s, ok := obj.(string) + return ok && strings.HasPrefix(s, "\u029e") } // Strings func String_Q(obj MalType) bool { - if obj == nil { - return false - } - return reflect.TypeOf(obj).Name() == "string" + _, ok := obj.(string) + return ok } // Functions @@ -97,10 +74,8 @@ type Func struct { } func Func_Q(obj MalType) bool { - if obj == nil { - return false - } - return reflect.TypeOf(obj).Name() == "Func" + _, ok := obj.(Func) + return ok } type MalFunc struct { @@ -114,10 +89,8 @@ type MalFunc struct { } func MalFunc_Q(obj MalType) bool { - if obj == nil { - return false - } - return reflect.TypeOf(obj).Name() == "MalFunc" + _, ok := obj.(MalFunc) + return ok } func (f MalFunc) SetMacro() MalType { @@ -159,10 +132,8 @@ func NewList(a ...MalType) MalType { } func List_Q(obj MalType) bool { - if obj == nil { - return false - } - return reflect.TypeOf(obj).Name() == "List" + _, ok := obj.(List) + return ok } // Vectors @@ -172,10 +143,8 @@ type Vector struct { } func Vector_Q(obj MalType) bool { - if obj == nil { - return false - } - return reflect.TypeOf(obj).Name() == "Vector" + _, ok := obj.(Vector) + return ok } func GetSlice(seq MalType) ([]MalType, error) { @@ -215,10 +184,8 @@ func NewHashMap(seq MalType) (MalType, error) { } func HashMap_Q(obj MalType) bool { - if obj == nil { - return false - } - return reflect.TypeOf(obj).Name() == "HashMap" + _, ok := obj.(HashMap) + return ok } // Atoms @@ -233,12 +200,8 @@ func (a *Atom) Set(val MalType) MalType { } func Atom_Q(obj MalType) bool { - switch obj.(type) { - case *Atom: - return true - default: - return false - } + _, ok := obj.(*Atom) + return ok } // General functions From 7954b71a81e08f16ea501e2b778de7f2bd56d9c4 Mon Sep 17 00:00:00 2001 From: Chris McCormick Date: Sat, 11 Feb 2017 10:14:24 +0800 Subject: [PATCH 0275/2308] Fixed erroneous fn* -> native PHP function conversion. Added test for callbacks during interop which is where this failure showed up. --- php/interop.php | 2 -- php/tests/stepA_mal.mal | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/php/interop.php b/php/interop.php index 897905fa82..e83c0ef3a5 100644 --- a/php/interop.php +++ b/php/interop.php @@ -19,8 +19,6 @@ function _to_php($obj) { return ${$obj->value}; } elseif (_atom_Q($obj)) { return $obj->value; - } elseif (_function_Q($obj)) { - return $obj->func; } else { return $obj; } diff --git a/php/tests/stepA_mal.mal b/php/tests/stepA_mal.mal index 1dd58ff23a..f86faeef3b 100644 --- a/php/tests/stepA_mal.mal +++ b/php/tests/stepA_mal.mal @@ -29,6 +29,11 @@ (! date "Y-m-d" 0) ;=>"1970-01-01" +;; testing native function with mal callback + +(! array_map (fn* [t] (if (> t 3) t)) [1 2 3 4 5 6]) +;=>(nil nil nil 4 5 6) + ;; testing superglobal variable access (get ($ "_SERVER") "PHP_SELF") From 577e643bfa5e3ec79642fea4c336c70265b7e2a0 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 10 Feb 2017 22:18:49 -0600 Subject: [PATCH 0276/2308] Rust: update rust to 1.14 - fixup new warnings Note: performance on the perf microbenchmark appears much better than before. --- rust/Dockerfile | 18 ++++++++++++------ rust/Makefile | 2 +- rust/src/env.rs | 2 +- rust/src/readline.rs | 6 +++--- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/rust/Dockerfile b/rust/Dockerfile index 2fc5bb72c9..dfd365d877 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -24,12 +24,18 @@ WORKDIR /mal # Install g++ for any C/C++ based implementations RUN apt-get -y install g++ -RUN apt-get -y install pkg-config +# Based on https://github.com/Scorpil/docker-rust/blob/master/stable/Dockerfile -# rust install script requirements -RUN apt-get -y install git sudo +ENV RUST_ARCHIVE=rust-1.14.0-x86_64-unknown-linux-gnu.tar.gz +ENV RUST_DOWNLOAD_URL=https://static.rust-lang.org/dist/$RUST_ARCHIVE -ENV MULTIRUST_HOME /opt/multirust -RUN curl -sf https://raw.githubusercontent.com/brson/multirust/master/blastoff.sh > /tmp/blastoff.sh && chmod +x /tmp/blastoff.sh && /tmp/blastoff.sh --yes && rm /tmp/blastoff.sh +RUN mkdir /rust +WORKDIR /rust -ENV CARGO_HOME /tmp/.cargo +RUN curl -fsOSL $RUST_DOWNLOAD_URL \ + && curl -s $RUST_DOWNLOAD_URL.sha256 | sha256sum -c - \ + && tar -C /rust -xzf $RUST_ARCHIVE --strip-components=1 \ + && rm $RUST_ARCHIVE \ + && ./install.sh + +ENV CARGO_HOME /mal/.cargo diff --git a/rust/Makefile b/rust/Makefile index cb2d4326b5..03a1a6936e 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -8,7 +8,7 @@ SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) ##################### -SRCS = step1_read_print.rs step2_eval.rs step3_env.rs \ +SRCS = step0_repl.rs step1_read_print.rs step2_eval.rs step3_env.rs \ step4_if_fn_do.rs step5_tco.rs step6_file.rs step7_quote.rs \ step8_macros.rs step9_try.rs stepA_mal.rs BINS = $(SRCS:%.rs=target/release/%) diff --git a/rust/src/env.rs b/rust/src/env.rs index c27e499ec4..a1588065ae 100644 --- a/rust/src/env.rs +++ b/rust/src/env.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use types::{MalVal, MalRet, _nil, list, err_string}; use types::MalType::{Sym, List, Vector}; -struct EnvType { +pub struct EnvType { data: HashMap, outer: Option, } diff --git a/rust/src/readline.rs b/rust/src/readline.rs index 37bf6745a3..53caf8f9eb 100644 --- a/rust/src/readline.rs +++ b/rust/src/readline.rs @@ -40,13 +40,13 @@ pub fn readline(prompt: &str) -> Option { // -------------------------------------------- -static mut history_loaded : bool = false; +static mut HISTORY_LOADED : bool = false; static HISTORY_FILE: &'static str = "/home/joelm/.mal-history"; fn load_history() { unsafe { - if history_loaded { return; } - history_loaded = true; + if HISTORY_LOADED { return; } + HISTORY_LOADED = true; } let file = match File::open(HISTORY_FILE) { From a05c086f05ad88bdb65ca81615d8f4a5e1d5a4ae Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 10 Feb 2017 22:06:09 -0600 Subject: [PATCH 0277/2308] ES6: more use of ES6, simplifications, newer babel. - Use Vector class derived from Array - Use Array/Vector.from for initializing/cloning of Array/Vector - Remove most semi-colon line endings - More use of arrow functions - Use Object.assign to copy properties in _malfunc and function cloning. - Remove or inline a bunch of types.js functions that don't really need to be separate functions: _obj_type, _sequential_Q, _symbol, _symbol_Q, _vector, _vector_Q, _hash_map, _hash_map_Q - Simplify dependency list in Makefile - Remove some separate core.js functions by moving them into the core_ns declaration: _nth, keys, vals, with_meta. With node 7, babel is mostly just used for translating imports into CommonJS requires for node. --- es6/Dockerfile | 8 +- es6/Makefile | 35 +++++--- es6/core.js | 188 ++++++++++++++++------------------------ es6/env.js | 10 +-- es6/package.json | 4 + es6/printer.js | 18 ++-- es6/reader.js | 110 +++++++++++------------ es6/step0_repl.js | 8 +- es6/step1_read_print.js | 14 +-- es6/step2_eval.js | 39 ++++----- es6/step3_env.js | 44 ++++------ es6/step4_if_fn_do.js | 38 ++++---- es6/step5_tco.js | 48 +++++----- es6/step6_file.js | 56 ++++++------ es6/step7_quote.js | 74 ++++++++-------- es6/step8_macros.js | 88 +++++++++---------- es6/step9_try.js | 92 +++++++++----------- es6/stepA_mal.js | 92 +++++++++----------- es6/types.js | 97 ++++----------------- 19 files changed, 459 insertions(+), 604 deletions(-) diff --git a/es6/Dockerfile b/es6/Dockerfile index b2033c1e6a..208e7f660e 100644 --- a/es6/Dockerfile +++ b/es6/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:xenial MAINTAINER Joel Martin ########################################################## @@ -24,8 +24,8 @@ WORKDIR /mal # For building node modules RUN apt-get -y install g++ -# Add nodesource apt repo config for 0.12 stable -RUN curl -sL https://deb.nodesource.com/setup_0.12 | bash - +# Add nodesource apt repo config for 7.X +RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - # Install nodejs RUN apt-get -y install nodejs @@ -36,4 +36,4 @@ RUN ln -sf nodejs /usr/bin/node ENV NPM_CONFIG_CACHE /mal/.npm # ES6 -RUN npm install -g babel +RUN npm install -g babel-cli babel-plugin-transform-es2015-modules-commonjs diff --git a/es6/Makefile b/es6/Makefile index d576e5b51e..775b814f26 100644 --- a/es6/Makefile +++ b/es6/Makefile @@ -1,3 +1,7 @@ +export PATH := $(PATH):node_modules/.bin/ + +BABEL_OPTS = --source-maps true \ + --plugins transform-es2015-modules-commonjs SOURCES_BASE = node_readline.js types.js reader.js printer.js SOURCES_LISP = env.js core.js stepA_mal.js @@ -11,9 +15,9 @@ all: node_modules $(foreach s,$(STEPS),build/$(s).js) dist: mal.js mal -build/%.js: %.js +build/%.js: %.js node_modules @mkdir -p $(dir $@) - babel --source-maps true $< --out-file $@ + babel $(BABEL_OPTS) $< --out-file $@ @echo >> $@ # workaround node-uglifier bug mal.js: $(foreach s,$(SOURCES),build/$(s)) @@ -24,17 +28,22 @@ mal: mal.js cat $< >> $@ chmod +x $@ -build/step0_repl.js: step0_repl.js build/node_readline.js -build/step1_read_print.js: step1_read_print.js build/node_readline.js build/types.js build/reader.js build/printer.js -build/step2_eval.js: step2_eval.js build/node_readline.js build/types.js build/reader.js build/printer.js -build/step3_env.js: step3_env.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js -build/step4_if_fn_do.js: step4_if_fn_do.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js build/core.js -build/step5_tco.js: step5_tco.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js build/core.js -build/step6_file.js: step6_file.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js build/core.js -build/step7_quote.js: step7_quote.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js build/core.js -build/step8_macros.js: step8_macros.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js build/core.js -build/step9_try.js: step9_try.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js build/core.js -build/stepA_mal.js: stepA_mal.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js build/core.js +STEP0_DEPS = build/node_readline.js +STEP1_DEPS = $(STEP0_DEPS) build/types.js build/reader.js build/printer.js +STEP3_DEPS = $(STEP1_DEPS) build/env.js +STEP4_DEPS = $(STEP3_DEPS) build/core.js + +build/step0_repl.js: $(STEP0_DEPS) +build/step1_read_print.js: $(STEP1_DEPS) +build/step2_eval.js: $(STEP1_DEPS) +build/step3_env.js: $(STEP3_DEPS) +build/step4_if_fn_do.js: $(STEP4_DEPS) +build/step5_tco.js: $(STEP4_DEPS) +build/step6_file.js: $(STEP4_DEPS) +build/step7_quote.js: $(STEP4_DEPS) +build/step8_macros.js: $(STEP4_DEPS) +build/step9_try.js: $(STEP4_DEPS) +build/stepA_mal.js: $(STEP4_DEPS) node_modules: diff --git a/es6/core.js b/es6/core.js index 621b7c62ec..cd11a8372a 100644 --- a/es6/core.js +++ b/es6/core.js @@ -1,13 +1,10 @@ -import { _equal_Q, _clone, _list_Q, _sequential_Q, - _keyword, _keyword_Q, _vector, _vector_Q, - _hash_map, _hash_map_Q, _assoc_BANG, _dissoc_BANG, - _symbol, _symbol_Q, Atom } from './types' +import { _equal_Q, _clone, _keyword, _keyword_Q, + _list_Q, Vector, _assoc_BANG, _dissoc_BANG, Atom } from './types' import { pr_str } from './printer' import { readline } from './node_readline' import { read_str } from './reader' -// Errors/Exceptions -function mal_throw(exc) { throw exc; } +function _error(e) { throw new Error(e) } // String functions function slurp(f) { @@ -17,129 +14,96 @@ function slurp(f) { var req = new XMLHttpRequest() req.open('GET', f, false) req.send() - if (req.status == 200) { - return req.responseText - } else { - throw new Error(`Failed to slurp file: ${f}`) + if (req.status !== 200) { + _error(`Failed to slurp file: ${f}`) } + return req.responseText } } // Sequence functions -function nth(lst, idx) { - if (idx < lst.length) { return lst[idx]; } - else { throw new Error('nth: index out of range'); } -} - -function conj(lst, ...args) { - if (_list_Q(lst)) { - return args.reverse().concat(lst) - } else { - return _vector(...lst.concat(args)) - } +function conj(o, ...a) { + return _list_Q(o) ? a.reverse().concat(o) : Vector.from(o.concat(a)) } function seq(obj) { if (_list_Q(obj)) { return obj.length > 0 ? obj : null - } else if (_vector_Q(obj)) { - return obj.length > 0 ? obj.slice(0) : null + } else if (obj instanceof Vector) { + return obj.length > 0 ? Array.from(obj.slice(0)) : null } else if (typeof obj === "string" && obj[0] !== '\u029e') { return obj.length > 0 ? obj.split('') : null } else if (obj === null) { return null } else { - throw new Error('seq: called on non-sequence') + _error('seq: called on non-sequence') } } -// hash-map functions - -function keys(hm) { - // TODO: Array.from(hm.keys()) when supported - let ks = [] - for (let k of hm.keys()) { ks.push(k) } - return ks -} - -function vals(hm) { - // TODO: Array.from(hm.keys()) when supported - let vs = [] - for (let v of hm.values()) { vs.push(v) } - return vs -} - -// Metadata functions -function with_meta(obj, m) { - let new_obj = _clone(obj) - new_obj.meta = m - return new_obj -} - // core_ns is namespace of type functions export const core_ns = new Map([ - ['=', _equal_Q], - ['throw', mal_throw], - - ['nil?', a => a === null], - ['true?', a => a === true], - ['false?', a => a === false], - ['string?', a => typeof a === "string" && a[0] !== '\u029e'], - ['symbol', a => _symbol(a)], - ['symbol?', a => _symbol_Q(a)], - ['keyword', a => _keyword(a)], - ['keyword?', a => _keyword_Q(a)], - - ['pr-str', (...a) => a.map(e => pr_str(e,1)).join(' ')], - ['str', (...a) => a.map(e => pr_str(e,0)).join('')], - ['prn', (...a) => console.log(...a.map(e => pr_str(e,1))) || null], - ['println', (...a) => console.log(...a.map(e => pr_str(e,0))) || null], - ['read-string', read_str], - ['readline', readline], - ['slurp', slurp], - - ['<' , (a,b) => a' , (a,b) => a>b], - ['>=', (a,b) => a>=b], - ['+' , (a,b) => a+b], - ['-' , (a,b) => a-b], - ['*' , (a,b) => a*b], - ['/' , (a,b) => a/b], - ["time-ms", () => new Date().getTime()], - - ['list', (...a) => a], - ['list?', _list_Q], - ['vector', _vector], - ['vector?', _vector_Q], - ['hash-map', _hash_map], - ['map?', _hash_map_Q], - ['assoc', (m,...a) => _assoc_BANG(_clone(m), ...a)], - ['dissoc', (m,...a) => _dissoc_BANG(_clone(m), ...a)], - ['get', (m,a) => m === null ? null : m.has(a) ? m.get(a) : null], - ['contains?', (m,a) => m.has(a)], - ['keys', keys], - ['vals', vals], - - ['sequential?', _sequential_Q], - ['cons', (a,b) => [a].concat(b)], - ['concat', (...a) => a.reduce((x,y) => x.concat(y), [])], - ['nth', nth], - ['first', a => a !== null && a.length > 0 ? a[0] : null], - ['rest', a => a === null ? [] : a.slice(1)], - ['empty?', a => a.length === 0], - ['count', a => a === null ? 0 : a.length], - ['apply', (f,...a) => f(...a.slice(0, -1).concat(a[a.length-1]))], - ['map', (f,a) => a.map(x => f(x))], - - ['conj', conj], - ['seq', seq], - - ['meta', a => 'meta' in a ? a['meta'] : null], - ['with-meta', with_meta], - ['atom', a => new Atom(a)], - ['atom?', a => a instanceof Atom], - ['deref', atm => atm.val], - ['reset!', (atm,a) => atm.val = a], - ['swap!', (atm,f,...args) => atm.val = f(...[atm.val].concat(args))] - ]) + ['=', _equal_Q], + ['throw', a => { throw a }], + + ['nil?', a => a === null], + ['true?', a => a === true], + ['false?', a => a === false], + ['string?', a => typeof a === "string" && a[0] !== '\u029e'], + ['symbol', a => Symbol.for(a)], + ['symbol?', a => typeof a === 'symbol'], + ['keyword', _keyword], + ['keyword?', _keyword_Q], + + ['pr-str', (...a) => a.map(e => pr_str(e,1)).join(' ')], + ['str', (...a) => a.map(e => pr_str(e,0)).join('')], + ['prn', (...a) => console.log(...a.map(e => pr_str(e,1))) || null], + ['println', (...a) => console.log(...a.map(e => pr_str(e,0))) || null], + ['read-string', read_str], + ['readline', readline], + ['slurp', slurp], + + ['<' , (a,b) => a' , (a,b) => a>b], + ['>=', (a,b) => a>=b], + ['+' , (a,b) => a+b], + ['-' , (a,b) => a-b], + ['*' , (a,b) => a*b], + ['/' , (a,b) => a/b], + ["time-ms", () => new Date().getTime()], + + ['list', (...a) => a], + ['list?', _list_Q], + ['vector', (...a) => Vector.from(a)], + ['vector?', a => a instanceof Vector], + ['hash-map', (...a) => _assoc_BANG(new Map(), ...a)], + ['map?', a => a instanceof Map], + ['assoc', (m,...a) => _assoc_BANG(_clone(m), ...a)], + ['dissoc', (m,...a) => _dissoc_BANG(_clone(m), ...a)], + ['get', (m,a) => m === null ? null : m.has(a) ? m.get(a) : null], + ['contains?', (m,a) => m.has(a)], + ['keys', a => Array.from(a.keys())], + ['vals', a => Array.from(a.values())], + + ['sequential?', a => Array.isArray(a)], + ['cons', (a,b) => [a].concat(b)], + ['concat', (...a) => a.reduce((x,y) => x.concat(y), [])], + ['nth', (a,b) => b < a.length ? a[b] : _error('nth: index out of range')], + ['first', a => a !== null && a.length > 0 ? a[0] : null], + ['rest', a => a === null ? [] : Array.from(a.slice(1))], + ['empty?', a => a.length === 0], + ['count', a => a === null ? 0 : a.length], + ['apply', (f,...a) => f(...a.slice(0, -1).concat(a[a.length-1]))], + ['map', (f,a) => Array.from(a.map(x => f(x)))], + + ['conj', conj], + ['seq', seq], + + ['meta', a => 'meta' in a ? a['meta'] : null], + ['with-meta', (a,b) => { let c = _clone(a); c.meta = b; return c }], + ['atom', a => new Atom(a)], + ['atom?', a => a instanceof Atom], + ['deref', atm => atm.val], + ['reset!', (atm,a) => atm.val = a], + ['swap!', (atm,f,...args) => atm.val = f(...[atm.val].concat(args))] + ]) diff --git a/es6/env.js b/es6/env.js index b2a9429c40..f0321286be 100644 --- a/es6/env.js +++ b/es6/env.js @@ -5,17 +5,13 @@ export function new_env(outer={}, binds=[], exprs=[]) { if (Symbol.keyFor(binds[i]) === "&") { e[binds[i+1]] = exprs.slice(i) // variable length arguments break - } else { - e[binds[i]] = exprs[i] } + e[binds[i]] = exprs[i] } return e } export const env_get = (env, sym) => { - if (sym in env) { - return env[sym] - } else { - throw Error(`'${Symbol.keyFor(sym)}' not found`) - } + if (sym in env) { return env[sym] } + throw Error(`'${Symbol.keyFor(sym)}' not found`) } export const env_set = (env, sym, val) => env[sym] = val diff --git a/es6/package.json b/es6/package.json index fb844da087..d836e6d663 100644 --- a/es6/package.json +++ b/es6/package.json @@ -5,5 +5,9 @@ "dependencies": { "ffi": "2.0.x", "node-uglifier": "0.4.3" + }, + "devDependencies": { + "babel-cli": "^6.0.0", + "babel-plugin-transform-es2015-modules-commonjs": "*" } } diff --git a/es6/printer.js b/es6/printer.js index c3951f8d66..18e5d350cb 100644 --- a/es6/printer.js +++ b/es6/printer.js @@ -1,15 +1,13 @@ -import { _symbol, _symbol_Q, _list_Q, _vector_Q, _hash_map_Q, Atom } from './types' +import { _symbol, _list_Q, Vector, Atom } from './types' export function pr_str(obj, print_readably) { if (typeof print_readably === 'undefined') { print_readably = true } var _r = print_readably if (_list_Q(obj)) { - var ret = obj.map(function(e) { return pr_str(e,_r) }) - return "(" + ret.join(' ') + ")" - } else if (_vector_Q(obj)) { - var ret = obj.map(function(e) { return pr_str(e,_r) }) - return "[" + ret.join(' ') + "]" - } else if (_hash_map_Q(obj)) { + return "(" + obj.map(e => pr_str(e,_r)).join(' ') + ")" + } else if (obj instanceof Vector) { + return "[" + obj.map(e => pr_str(e,_r)).join(' ') + "]" + } else if (obj instanceof Map) { var ret = [] for (let [k,v] of obj) { ret.push(pr_str(k,_r), pr_str(v,_r)) @@ -20,12 +18,12 @@ export function pr_str(obj, print_readably) { return ':' + obj.slice(1) } else if (_r) { return '"' + obj.replace(/\\/g, "\\\\") - .replace(/"/g, '\\"') - .replace(/\n/g, "\\n") + '"' // string + .replace(/"/g, '\\"') + .replace(/\n/g, "\\n") + '"' } else { return obj } - } else if (_symbol_Q(obj)) { + } else if (typeof obj === 'symbol') { return Symbol.keyFor(obj) } else if (obj === null) { return "nil" diff --git a/es6/reader.js b/es6/reader.js index 98b9861a1d..e7afe00318 100644 --- a/es6/reader.js +++ b/es6/reader.js @@ -1,120 +1,120 @@ -import { _symbol, _keyword, _vector, _hash_map } from './types'; +import { _keyword, Vector, _assoc_BANG } from './types' export class BlankException extends Error {} class Reader { constructor(tokens) { - this.tokens = tokens; - this.position = 0; + this.tokens = tokens + this.position = 0 } - next() { return this.tokens[this.position++]; } - peek() { return this.tokens[this.position]; } + next() { return this.tokens[this.position++] } + peek() { return this.tokens[this.position] } } function tokenize(str) { - const re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g; - let match = null; - let results = []; + const re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g + let match = null + let results = [] while ((match = re.exec(str)[1]) != '') { - if (match[0] === ';') { continue; } - results.push(match); + if (match[0] === ';') { continue } + results.push(match) } - return results; + return results } function read_atom (reader) { - const token = reader.next(); - //console.log("read_atom:", token); + const token = reader.next() + //console.log("read_atom:", token) if (token.match(/^-?[0-9]+$/)) { return parseInt(token,10) // integer } else if (token.match(/^-?[0-9][0-9.]*$/)) { - return parseFloat(token,10); // float + return parseFloat(token,10) // float } else if (token[0] === "\"") { return token.slice(1,token.length-1) .replace(/\\"/g, '"') .replace(/\\n/g, "\n") - .replace(/\\\\/g, "\\"); // string + .replace(/\\\\/g, "\\") // string } else if (token[0] === ":") { - return _keyword(token.slice(1)); + return _keyword(token.slice(1)) } else if (token === "nil") { - return null; + return null } else if (token === "true") { - return true; + return true } else if (token === "false") { - return false; + return false } else { - return _symbol(token); // symbol + return Symbol.for(token) // symbol } } // read list of tokens function read_list(reader, start, end) { - start = start || '('; - end = end || ')'; - var ast = []; - var token = reader.next(); + start = start || '(' + end = end || ')' + var ast = [] + var token = reader.next() if (token !== start) { - throw new Error("expected '" + start + "'"); + throw new Error("expected '" + start + "'") } while ((token = reader.peek()) !== end) { if (!token) { - throw new Error("expected '" + end + "', got EOF"); + throw new Error("expected '" + end + "', got EOF") } - ast.push(read_form(reader)); + ast.push(read_form(reader)) } - reader.next(); - return ast; + reader.next() + return ast } // read vector of tokens function read_vector(reader) { - return _vector(...read_list(reader, '[', ']')); + return Vector.from(read_list(reader, '[', ']')) } // read hash-map key/value pairs function read_hash_map(reader) { - return _hash_map(...read_list(reader, '{', '}')); + return _assoc_BANG(new Map(), ...read_list(reader, '{', '}')) } function read_form(reader) { - var token = reader.peek(); + var token = reader.peek() switch (token) { // reader macros/transforms - case ';': return null; // Ignore comments - case '\'': reader.next(); - return [_symbol('quote'), read_form(reader)]; - case '`': reader.next(); - return [_symbol('quasiquote'), read_form(reader)]; - case '~': reader.next(); - return [_symbol('unquote'), read_form(reader)]; - case '~@': reader.next(); - return [_symbol('splice-unquote'), read_form(reader)]; - case '^': reader.next(); - var meta = read_form(reader); - return [_symbol('with-meta'), read_form(reader), meta]; - case '@': reader.next(); - return [_symbol('deref'), read_form(reader)]; + case ';': return null // Ignore comments + case '\'': reader.next() + return [Symbol.for('quote'), read_form(reader)] + case '`': reader.next() + return [Symbol.for('quasiquote'), read_form(reader)] + case '~': reader.next() + return [Symbol.for('unquote'), read_form(reader)] + case '~@': reader.next() + return [Symbol.for('splice-unquote'), read_form(reader)] + case '^': reader.next() + var meta = read_form(reader) + return [Symbol.for('with-meta'), read_form(reader), meta] + case '@': reader.next() + return [Symbol.for('deref'), read_form(reader)] // list - case ')': throw new Error("unexpected ')'"); - case '(': return read_list(reader); + case ')': throw new Error("unexpected ')'") + case '(': return read_list(reader) // vector - case ']': throw new Error("unexpected ']'"); - case '[': return read_vector(reader); + case ']': throw new Error("unexpected ']'") + case '[': return read_vector(reader) // hash-map - case '}': throw new Error("unexpected '}'"); - case '{': return read_hash_map(reader); + case '}': throw new Error("unexpected '}'") + case '{': return read_hash_map(reader) // atom - default: return read_atom(reader); + default: return read_atom(reader) } } export function read_str(str) { - var tokens = tokenize(str); - if (tokens.length === 0) { throw new BlankException(); } + var tokens = tokenize(str) + if (tokens.length === 0) { throw new BlankException() } return read_form(new Reader(tokens)) } diff --git a/es6/step0_repl.js b/es6/step0_repl.js index 74585f4c38..6679a09262 100644 --- a/es6/step0_repl.js +++ b/es6/step0_repl.js @@ -1,19 +1,19 @@ import { readline } from './node_readline' // read -const READ = (str) => str +const READ = str => str // eval const EVAL = (ast, env) => ast // print -const PRINT = (exp) => exp +const PRINT = exp => exp // repl -const REP = (str) => PRINT(EVAL(READ(str), {})) +const REP = str => PRINT(EVAL(READ(str), {})) while (true) { let line = readline('user> ') if (line == null) break - if (line) { console.log(REP(line)); } + if (line) { console.log(REP(line)) } } diff --git a/es6/step1_read_print.js b/es6/step1_read_print.js index 6b1d1c8033..6ff1cee7d5 100644 --- a/es6/step1_read_print.js +++ b/es6/step1_read_print.js @@ -3,25 +3,25 @@ import { BlankException, read_str } from './reader' import { pr_str } from './printer' // read -const READ = (str) => read_str(str) +const READ = str => read_str(str) // eval const EVAL = (ast, env) => ast // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl -const REP = (str) => PRINT(EVAL(READ(str), {})) +const REP = str => PRINT(EVAL(READ(str), {})) while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + if (line) { console.log(REP(line)) } } catch (exc) { - if (exc instanceof BlankException) { continue; } - if (exc.stack) { console.log(exc.stack); } - else { console.log(`Error: ${exc}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/step2_eval.js b/es6/step2_eval.js index 3622ab8d28..40abe093e8 100644 --- a/es6/step2_eval.js +++ b/es6/step2_eval.js @@ -1,29 +1,24 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q } from './types' +import { _list_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' // read -const READ = (str) => read_str(str) +const READ = str => read_str(str) // eval const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { if (ast in env) { return env[ast] } else { throw Error(`'${Symbol.keyFor(ast)}' not found`) } - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -39,23 +34,23 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl -var repl_env = {[_symbol('+')]: (a,b) => a+b, - [_symbol('-')]: (a,b) => a-b, - [_symbol('*')]: (a,b) => a*b, - [_symbol('/')]: (a,b) => a/b} -const REP = (str) => PRINT(EVAL(READ(str), repl_env)) +var repl_env = {[Symbol.for('+')]: (a,b) => a+b, + [Symbol.for('-')]: (a,b) => a-b, + [Symbol.for('*')]: (a,b) => a*b, + [Symbol.for('/')]: (a,b) => a/b} +const REP = str => PRINT(EVAL(READ(str), repl_env)) while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + if (line) { console.log(REP(line)) } } catch (exc) { - if (exc instanceof BlankException) { continue; } - if (exc.stack) { console.log(exc.stack); } - else { console.log(`Error: ${exc}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/step3_env.js b/es6/step3_env.js index 10de2c2f53..0026b3472b 100644 --- a/es6/step3_env.js +++ b/es6/step3_env.js @@ -1,26 +1,21 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q } from './types' +import { _list_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' import { new_env, env_set, env_get } from './env' // read -const READ = (str) => read_str(str) +const READ = str => read_str(str) // eval const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -33,9 +28,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -50,24 +44,24 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -env_set(repl_env, _symbol('+'), (a,b) => a+b) -env_set(repl_env, _symbol('-'), (a,b) => a-b) -env_set(repl_env, _symbol('*'), (a,b) => a*b) -env_set(repl_env, _symbol('/'), (a,b) => a/b) -const REP = (str) => PRINT(EVAL(READ(str), repl_env)) +env_set(repl_env, Symbol.for('+'), (a,b) => a+b) +env_set(repl_env, Symbol.for('-'), (a,b) => a-b) +env_set(repl_env, Symbol.for('*'), (a,b) => a*b) +env_set(repl_env, Symbol.for('/'), (a,b) => a/b) +const REP = str => PRINT(EVAL(READ(str), repl_env)) while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + if (line) { console.log(REP(line)) } } catch (exc) { - if (exc instanceof BlankException) { continue; } - if (exc.stack) { console.log(exc.stack); } - else { console.log(`Error: ${exc}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/step4_if_fn_do.js b/es6/step4_if_fn_do.js index dca40df45f..4fe1f90e29 100644 --- a/es6/step4_if_fn_do.js +++ b/es6/step4_if_fn_do.js @@ -1,27 +1,22 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q } from './types' +import { _list_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' import { new_env, env_set, env_get } from './env' import { core_ns } from './core' // read -const READ = (str) => read_str(str) +const READ = str => read_str(str) // eval const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -34,9 +29,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -62,14 +56,14 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -const REP = (str) => PRINT(EVAL(READ(str), repl_env)) +const REP = str => PRINT(EVAL(READ(str), repl_env)) // core.EXT: defined using ES6 -for (let [k, v] of core_ns) { env_set(repl_env, _symbol(k), v) } +for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) } // core.mal: defined using language itself REP('(def! not (fn* (a) (if a false true)))') @@ -78,10 +72,10 @@ while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + if (line) { console.log(REP(line)) } } catch (exc) { - if (exc instanceof BlankException) { continue; } - if (exc.stack) { console.log(exc.stack); } - else { console.log(`Error: ${exc}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/step5_tco.js b/es6/step5_tco.js index 37c7fc0d30..7bcb778980 100644 --- a/es6/step5_tco.js +++ b/es6/step5_tco.js @@ -1,27 +1,22 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q, _malfunc, _malfunc_Q } from './types' +import { _list_Q, _malfunc, _malfunc_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' import { new_env, env_set, env_get } from './env' import { core_ns } from './core' // read -const READ = (str) => read_str(str) +const READ = str => read_str(str) // eval const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -35,9 +30,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -46,11 +40,11 @@ const EVAL = (ast, env) => { } env = let_env ast = a2 - break; // continue TCO loop + break // continue TCO loop case 'do': eval_ast(ast.slice(1,-1), env) ast = ast[ast.length-1] - break; // continue TCO loop + break // continue TCO loop case 'if': let cond = EVAL(a1, env) if (cond === null || cond === false) { @@ -58,16 +52,16 @@ const EVAL = (ast, env) => { } else { ast = a2 } - break; // continue TCO loop + break // continue TCO loop case 'fn*': return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), - a2, env, a1) + a2, env, a1) default: let [f, ...args] = eval_ast(ast, env) if (_malfunc_Q(f)) { env = new_env(f.env, f.params, args) ast = f.ast - break; // continue TCO loop + break // continue TCO loop } else { return f(...args) } @@ -76,14 +70,14 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -const REP = (str) => PRINT(EVAL(READ(str), repl_env)) +const REP = str => PRINT(EVAL(READ(str), repl_env)) // core.EXT: defined using ES6 -for (let [k, v] of core_ns) { env_set(repl_env, _symbol(k), v) } +for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) } // core.mal: defined using language itself REP('(def! not (fn* (a) (if a false true)))') @@ -92,10 +86,10 @@ while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + if (line) { console.log(REP(line)) } } catch (exc) { - if (exc instanceof BlankException) { continue; } - if (exc.stack) { console.log(exc.stack); } - else { console.log(`Error: ${exc}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/step6_file.js b/es6/step6_file.js index 00314bf6c2..7396197d57 100644 --- a/es6/step6_file.js +++ b/es6/step6_file.js @@ -1,27 +1,22 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q, _malfunc, _malfunc_Q } from './types' +import { _list_Q, _malfunc, _malfunc_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' import { new_env, env_set, env_get } from './env' import { core_ns } from './core' // read -const READ = (str) => read_str(str) +const READ = str => read_str(str) // eval const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -35,9 +30,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -46,11 +40,11 @@ const EVAL = (ast, env) => { } env = let_env ast = a2 - break; // continue TCO loop + break // continue TCO loop case 'do': eval_ast(ast.slice(1,-1), env) ast = ast[ast.length-1] - break; // continue TCO loop + break // continue TCO loop case 'if': let cond = EVAL(a1, env) if (cond === null || cond === false) { @@ -58,16 +52,16 @@ const EVAL = (ast, env) => { } else { ast = a2 } - break; // continue TCO loop + break // continue TCO loop case 'fn*': return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), - a2, env, a1) + a2, env, a1) default: let [f, ...args] = eval_ast(ast, env) if (_malfunc_Q(f)) { env = new_env(f.env, f.params, args) ast = f.ast - break; // continue TCO loop + break // continue TCO loop } else { return f(...args) } @@ -76,23 +70,23 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -const REP = (str) => PRINT(EVAL(READ(str), repl_env)) +const REP = str => PRINT(EVAL(READ(str), repl_env)) // core.EXT: defined using ES6 -for (let [k, v] of core_ns) { env_set(repl_env, _symbol(k), v) } -env_set(repl_env, _symbol('eval'), a => EVAL(a, repl_env)) -env_set(repl_env, _symbol('*ARGV*'), []) +for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) } +env_set(repl_env, Symbol.for('eval'), a => EVAL(a, repl_env)) +env_set(repl_env, Symbol.for('*ARGV*'), []) // core.mal: defined using language itself REP('(def! not (fn* (a) (if a false true)))') REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))') -if (process.argv.length > 2) { - env_set(repl_env, _symbol('*ARGV*'), process.argv.slice(3)) +if (process.argv.length > 2) { + env_set(repl_env, Symbol.for('*ARGV*'), process.argv.slice(3)) REP(`(load-file "${process.argv[2]}")`) process.exit(0) } @@ -102,10 +96,10 @@ while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + if (line) { console.log(REP(line)) } } catch (exc) { - if (exc instanceof BlankException) { continue; } - if (exc.stack) { console.log(exc.stack); } - else { console.log(`Error: ${exc}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/step7_quote.js b/es6/step7_quote.js index f3094394f3..947c3a2194 100644 --- a/es6/step7_quote.js +++ b/es6/step7_quote.js @@ -1,41 +1,40 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q, _sequential_Q, _malfunc, _malfunc_Q } from './types' +import { _list_Q, _malfunc, _malfunc_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' import { new_env, env_set, env_get } from './env' import { core_ns } from './core' // read -const READ = (str) => read_str(str) +const READ = str => read_str(str) // eval -const is_pair = x => _sequential_Q(x) && x.length > 0 +const is_pair = x => Array.isArray(x) && x.length > 0 const quasiquote = ast => { if (!is_pair(ast)) { - return [_symbol('quote'), ast] - } else if (ast[0] === _symbol('unquote')) { + return [Symbol.for('quote'), ast] + } else if (ast[0] === Symbol.for('unquote')) { return ast[1] - } else if (is_pair(ast[0]) && ast[0][0] === _symbol('splice-unquote')) { - return [_symbol('concat'), ast[0][1], quasiquote(ast.slice(1))] + } else if (is_pair(ast[0]) && ast[0][0] === Symbol.for('splice-unquote')) { + return [Symbol.for('concat'), + ast[0][1], + quasiquote(ast.slice(1))] } else { - return [_symbol('cons'), quasiquote(ast[0]), quasiquote(ast.slice(1))] + return [Symbol.for('cons'), + quasiquote(ast[0]), + quasiquote(ast.slice(1))] } } const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -49,9 +48,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -60,16 +58,16 @@ const EVAL = (ast, env) => { } env = let_env ast = a2 - break; // continue TCO loop + break // continue TCO loop case 'quote': return a1 case 'quasiquote': ast = quasiquote(a1) - break; // continue TCO loop + break // continue TCO loop case 'do': eval_ast(ast.slice(1,-1), env) ast = ast[ast.length-1] - break; // continue TCO loop + break // continue TCO loop case 'if': let cond = EVAL(a1, env) if (cond === null || cond === false) { @@ -77,16 +75,16 @@ const EVAL = (ast, env) => { } else { ast = a2 } - break; // continue TCO loop + break // continue TCO loop case 'fn*': return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), - a2, env, a1) + a2, env, a1) default: let [f, ...args] = eval_ast(ast, env) if (_malfunc_Q(f)) { env = new_env(f.env, f.params, args) ast = f.ast - break; // continue TCO loop + break // continue TCO loop } else { return f(...args) } @@ -95,23 +93,23 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -const REP = (str) => PRINT(EVAL(READ(str), repl_env)) +const REP = str => PRINT(EVAL(READ(str), repl_env)) // core.EXT: defined using ES6 -for (let [k, v] of core_ns) { env_set(repl_env, _symbol(k), v) } -env_set(repl_env, _symbol('eval'), a => EVAL(a, repl_env)) -env_set(repl_env, _symbol('*ARGV*'), []) +for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) } +env_set(repl_env, Symbol.for('eval'), a => EVAL(a, repl_env)) +env_set(repl_env, Symbol.for('*ARGV*'), []) // core.mal: defined using language itself REP('(def! not (fn* (a) (if a false true)))') REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))') -if (process.argv.length > 2) { - env_set(repl_env, _symbol('*ARGV*'), process.argv.slice(3)) +if (process.argv.length > 2) { + env_set(repl_env, Symbol.for('*ARGV*'), process.argv.slice(3)) REP(`(load-file "${process.argv[2]}")`) process.exit(0) } @@ -121,10 +119,10 @@ while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + if (line) { console.log(REP(line)) } } catch (exc) { - if (exc instanceof BlankException) { continue; } - if (exc.stack) { console.log(exc.stack); } - else { console.log(`Error: ${exc}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/step8_macros.js b/es6/step8_macros.js index 277a5ef580..9aa6e1cc9c 100644 --- a/es6/step8_macros.js +++ b/es6/step8_macros.js @@ -1,57 +1,50 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q, _sequential_Q, _malfunc, _malfunc_Q } from './types' +import { _list_Q, _malfunc, _malfunc_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' import { new_env, env_set, env_get } from './env' import { core_ns } from './core' // read -const READ = (str) => read_str(str) +const READ = str => read_str(str) // eval -const is_pair = x => _sequential_Q(x) && x.length > 0 +const is_pair = x => Array.isArray(x) && x.length > 0 const quasiquote = ast => { if (!is_pair(ast)) { - return [_symbol('quote'), ast] - } else if (ast[0] === _symbol('unquote')) { + return [Symbol.for('quote'), ast] + } else if (ast[0] === Symbol.for('unquote')) { return ast[1] - } else if (is_pair(ast[0]) && ast[0][0] === _symbol('splice-unquote')) { - return [_symbol('concat'), ast[0][1], quasiquote(ast.slice(1))] + } else if (is_pair(ast[0]) && ast[0][0] === Symbol.for('splice-unquote')) { + return [Symbol.for('concat'), + ast[0][1], + quasiquote(ast.slice(1))] } else { - return [_symbol('cons'), quasiquote(ast[0]), quasiquote(ast.slice(1))] + return [Symbol.for('cons'), + quasiquote(ast[0]), + quasiquote(ast.slice(1))] } } -function is_macro_call(ast, env) { - return _list_Q(ast) && - _symbol_Q(ast[0]) && - ast[0] in env && - env_get(env, ast[0]).ismacro -} - function macroexpand(ast, env) { - while (is_macro_call(ast, env)) { - let mac = env_get(env, ast[0]) - ast = mac(...ast.slice(1)) + while (_list_Q(ast) && typeof ast[0] === 'symbol' && ast[0] in env) { + let f = env_get(env, ast[0]) + if (!f.ismacro) { break } + ast = f(...ast.slice(1)) } return ast } const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -68,9 +61,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -79,12 +71,12 @@ const EVAL = (ast, env) => { } env = let_env ast = a2 - break; // continue TCO loop + break // continue TCO loop case 'quote': return a1 case 'quasiquote': ast = quasiquote(a1) - break; // continue TCO loop + break // continue TCO loop case 'defmacro!': let func = EVAL(a2, env) func.ismacro = true @@ -94,7 +86,7 @@ const EVAL = (ast, env) => { case 'do': eval_ast(ast.slice(1,-1), env) ast = ast[ast.length-1] - break; // continue TCO loop + break // continue TCO loop case 'if': let cond = EVAL(a1, env) if (cond === null || cond === false) { @@ -102,16 +94,16 @@ const EVAL = (ast, env) => { } else { ast = a2 } - break; // continue TCO loop + break // continue TCO loop case 'fn*': return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), - a2, env, a1) + a2, env, a1) default: let [f, ...args] = eval_ast(ast, env) if (_malfunc_Q(f)) { env = new_env(f.env, f.params, args) ast = f.ast - break; // continue TCO loop + break // continue TCO loop } else { return f(...args) } @@ -120,16 +112,16 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -const REP = (str) => PRINT(EVAL(READ(str), repl_env)) +const REP = str => PRINT(EVAL(READ(str), repl_env)) // core.EXT: defined using ES6 -for (let [k, v] of core_ns) { env_set(repl_env, _symbol(k), v) } -env_set(repl_env, _symbol('eval'), a => EVAL(a, repl_env)) -env_set(repl_env, _symbol('*ARGV*'), []) +for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) } +env_set(repl_env, Symbol.for('eval'), a => EVAL(a, repl_env)) +env_set(repl_env, Symbol.for('*ARGV*'), []) // core.mal: defined using language itself REP('(def! not (fn* (a) (if a false true)))') @@ -137,8 +129,8 @@ REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))' REP('(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list \'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons \'cond (rest (rest xs)))))))') REP('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))') -if (process.argv.length > 2) { - env_set(repl_env, _symbol('*ARGV*'), process.argv.slice(3)) +if (process.argv.length > 2) { + env_set(repl_env, Symbol.for('*ARGV*'), process.argv.slice(3)) REP(`(load-file "${process.argv[2]}")`) process.exit(0) } @@ -148,10 +140,10 @@ while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + if (line) { console.log(REP(line)) } } catch (exc) { - if (exc instanceof BlankException) { continue; } - if (exc.stack) { console.log(exc.stack); } - else { console.log(`Error: ${exc}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/step9_try.js b/es6/step9_try.js index a50cc08ed1..ede914d984 100644 --- a/es6/step9_try.js +++ b/es6/step9_try.js @@ -1,57 +1,50 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q, _sequential_Q, _malfunc, _malfunc_Q } from './types' +import { _list_Q, _malfunc, _malfunc_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' import { new_env, env_set, env_get } from './env' import { core_ns } from './core' // read -const READ = (str) => read_str(str) +const READ = str => read_str(str) // eval -const is_pair = x => _sequential_Q(x) && x.length > 0 +const is_pair = x => Array.isArray(x) && x.length > 0 const quasiquote = ast => { if (!is_pair(ast)) { - return [_symbol('quote'), ast] - } else if (ast[0] === _symbol('unquote')) { + return [Symbol.for('quote'), ast] + } else if (ast[0] === Symbol.for('unquote')) { return ast[1] - } else if (is_pair(ast[0]) && ast[0][0] === _symbol('splice-unquote')) { - return [_symbol('concat'), ast[0][1], quasiquote(ast.slice(1))] + } else if (is_pair(ast[0]) && ast[0][0] === Symbol.for('splice-unquote')) { + return [Symbol.for('concat'), + ast[0][1], + quasiquote(ast.slice(1))] } else { - return [_symbol('cons'), quasiquote(ast[0]), quasiquote(ast.slice(1))] + return [Symbol.for('cons'), + quasiquote(ast[0]), + quasiquote(ast.slice(1))] } } -function is_macro_call(ast, env) { - return _list_Q(ast) && - _symbol_Q(ast[0]) && - ast[0] in env && - env_get(env, ast[0]).ismacro -} - function macroexpand(ast, env) { - while (is_macro_call(ast, env)) { - let mac = env_get(env, ast[0]) - ast = mac(...ast.slice(1)) + while (_list_Q(ast) && typeof ast[0] === 'symbol' && ast[0] in env) { + let f = env_get(env, ast[0]) + if (!f.ismacro) { break } + ast = f(...ast.slice(1)) } return ast } const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -68,9 +61,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -79,12 +71,12 @@ const EVAL = (ast, env) => { } env = let_env ast = a2 - break; // continue TCO loop + break // continue TCO loop case 'quote': return a1 case 'quasiquote': ast = quasiquote(a1) - break; // continue TCO loop + break // continue TCO loop case 'defmacro!': let func = EVAL(a2, env) func.ismacro = true @@ -95,8 +87,8 @@ const EVAL = (ast, env) => { try { return EVAL(a1, env) } catch (exc) { - if (a2 && a2[0] === _symbol('catch*')) { - if (exc instanceof Error) { exc = exc.message; } + if (a2 && a2[0] === Symbol.for('catch*')) { + if (exc instanceof Error) { exc = exc.message } return EVAL(a2[2], new_env(env, [a2[1]], [exc])) } else { throw exc @@ -105,7 +97,7 @@ const EVAL = (ast, env) => { case 'do': eval_ast(ast.slice(1,-1), env) ast = ast[ast.length-1] - break; // continue TCO loop + break // continue TCO loop case 'if': let cond = EVAL(a1, env) if (cond === null || cond === false) { @@ -113,16 +105,16 @@ const EVAL = (ast, env) => { } else { ast = a2 } - break; // continue TCO loop + break // continue TCO loop case 'fn*': return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), - a2, env, a1) + a2, env, a1) default: let [f, ...args] = eval_ast(ast, env) if (_malfunc_Q(f)) { env = new_env(f.env, f.params, args) ast = f.ast - break; // continue TCO loop + break // continue TCO loop } else { return f(...args) } @@ -131,16 +123,16 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -const REP = (str) => PRINT(EVAL(READ(str), repl_env)) +const REP = str => PRINT(EVAL(READ(str), repl_env)) // core.EXT: defined using ES6 -for (let [k, v] of core_ns) { env_set(repl_env, _symbol(k), v) } -env_set(repl_env, _symbol('eval'), a => EVAL(a, repl_env)) -env_set(repl_env, _symbol('*ARGV*'), []) +for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) } +env_set(repl_env, Symbol.for('eval'), a => EVAL(a, repl_env)) +env_set(repl_env, Symbol.for('*ARGV*'), []) // core.mal: defined using language itself REP('(def! not (fn* (a) (if a false true)))') @@ -148,8 +140,8 @@ REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))' REP('(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list \'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons \'cond (rest (rest xs)))))))') REP('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))') -if (process.argv.length > 2) { - env_set(repl_env, _symbol('*ARGV*'), process.argv.slice(3)) +if (process.argv.length > 2) { + env_set(repl_env, Symbol.for('*ARGV*'), process.argv.slice(3)) REP(`(load-file "${process.argv[2]}")`) process.exit(0) } @@ -159,10 +151,10 @@ while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + if (line) { console.log(REP(line)) } } catch (exc) { - if (exc instanceof BlankException) { continue; } - if (exc.stack) { console.log(exc.stack); } - else { console.log(`Error: ${exc}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/stepA_mal.js b/es6/stepA_mal.js index c6fda41488..c883980ebe 100644 --- a/es6/stepA_mal.js +++ b/es6/stepA_mal.js @@ -1,57 +1,50 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q, _sequential_Q, _malfunc, _malfunc_Q } from './types' +import { _list_Q, _malfunc, _malfunc_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' import { new_env, env_set, env_get } from './env' import { core_ns } from './core' // read -const READ = (str) => read_str(str) +const READ = str => read_str(str) // eval -const is_pair = x => _sequential_Q(x) && x.length > 0 +const is_pair = x => Array.isArray(x) && x.length > 0 const quasiquote = ast => { if (!is_pair(ast)) { - return [_symbol('quote'), ast] - } else if (ast[0] === _symbol('unquote')) { + return [Symbol.for('quote'), ast] + } else if (ast[0] === Symbol.for('unquote')) { return ast[1] - } else if (is_pair(ast[0]) && ast[0][0] === _symbol('splice-unquote')) { - return [_symbol('concat'), ast[0][1], quasiquote(ast.slice(1))] + } else if (is_pair(ast[0]) && ast[0][0] === Symbol.for('splice-unquote')) { + return [Symbol.for('concat'), + ast[0][1], + quasiquote(ast.slice(1))] } else { - return [_symbol('cons'), quasiquote(ast[0]), quasiquote(ast.slice(1))] + return [Symbol.for('cons'), + quasiquote(ast[0]), + quasiquote(ast.slice(1))] } } -function is_macro_call(ast, env) { - return _list_Q(ast) && - _symbol_Q(ast[0]) && - ast[0] in env && - env_get(env, ast[0]).ismacro -} - function macroexpand(ast, env) { - while (is_macro_call(ast, env)) { - let mac = env_get(env, ast[0]) - ast = mac(...ast.slice(1)) + while (_list_Q(ast) && typeof ast[0] === 'symbol' && ast[0] in env) { + let f = env_get(env, ast[0]) + if (!f.ismacro) { break } + ast = f(...ast.slice(1)) } return ast } const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -68,9 +61,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -79,12 +71,12 @@ const EVAL = (ast, env) => { } env = let_env ast = a2 - break; // continue TCO loop + break // continue TCO loop case 'quote': return a1 case 'quasiquote': ast = quasiquote(a1) - break; // continue TCO loop + break // continue TCO loop case 'defmacro!': let func = EVAL(a2, env) func.ismacro = true @@ -95,8 +87,8 @@ const EVAL = (ast, env) => { try { return EVAL(a1, env) } catch (exc) { - if (a2 && a2[0] === _symbol('catch*')) { - if (exc instanceof Error) { exc = exc.message; } + if (a2 && a2[0] === Symbol.for('catch*')) { + if (exc instanceof Error) { exc = exc.message } return EVAL(a2[2], new_env(env, [a2[1]], [exc])) } else { throw exc @@ -105,7 +97,7 @@ const EVAL = (ast, env) => { case 'do': eval_ast(ast.slice(1,-1), env) ast = ast[ast.length-1] - break; // continue TCO loop + break // continue TCO loop case 'if': let cond = EVAL(a1, env) if (cond === null || cond === false) { @@ -113,16 +105,16 @@ const EVAL = (ast, env) => { } else { ast = a2 } - break; // continue TCO loop + break // continue TCO loop case 'fn*': return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), - a2, env, a1) + a2, env, a1) default: let [f, ...args] = eval_ast(ast, env) if (_malfunc_Q(f)) { env = new_env(f.env, f.params, args) ast = f.ast - break; // continue TCO loop + break // continue TCO loop } else { return f(...args) } @@ -131,16 +123,16 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -const REP = (str) => PRINT(EVAL(READ(str), repl_env)) +const REP = str => PRINT(EVAL(READ(str), repl_env)) // core.EXT: defined using ES6 -for (let [k, v] of core_ns) { env_set(repl_env, _symbol(k), v) } -env_set(repl_env, _symbol('eval'), a => EVAL(a, repl_env)) -env_set(repl_env, _symbol('*ARGV*'), []) +for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) } +env_set(repl_env, Symbol.for('eval'), a => EVAL(a, repl_env)) +env_set(repl_env, Symbol.for('*ARGV*'), []) // core.mal: defined using language itself REP('(def! *host-language* "ecmascript6")') @@ -151,8 +143,8 @@ REP('(def! *gensym-counter* (atom 0))') REP('(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))') REP('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))') -if (process.argv.length > 2) { - env_set(repl_env, _symbol('*ARGV*'), process.argv.slice(3)) +if (process.argv.length > 2) { + env_set(repl_env, Symbol.for('*ARGV*'), process.argv.slice(3)) REP(`(load-file "${process.argv[2]}")`) process.exit(0) } @@ -162,10 +154,10 @@ while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + if (line) { console.log(REP(line)) } } catch (exc) { - if (exc instanceof BlankException) { continue; } - if (exc.stack) { console.log(exc.stack); } - else { console.log(`Error: ${exc}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/types.js b/es6/types.js index fb579b3cc3..458538ff55 100644 --- a/es6/types.js +++ b/es6/types.js @@ -1,120 +1,59 @@ // General functions - -export const _sequential_Q = lst => _list_Q(lst) || _vector_Q(lst) - -export function _obj_type(obj) { - if (_symbol_Q(obj)) { return 'symbol' } - else if (_list_Q(obj)) { return 'list' } - else if (_vector_Q(obj)) { return 'vector' } - else if (_hash_map_Q(obj)) { return 'hash-map' } - else if (obj === null) { return 'nil' } - else if (obj === true) { return 'true' } - else if (obj === false) { return 'false' } - else { - switch (typeof(obj)) { - case 'number': return 'number' - case 'function': return 'function' - case 'string': return obj[0] == '\u029e' ? 'keyword' : 'string' - default: throw new Error(`Unknown type '${typeof(obj)}'`) - } - } -} - export function _equal_Q (a, b) { - let ota = _obj_type(a), otb = _obj_type(b) - if (!(ota === otb || (_sequential_Q(a) && _sequential_Q(b)))) { - return false - } - switch (ota) { - case 'list': - case 'vector': + if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) { return false } for (let i=0; i f.ast ? true : false Function.prototype.clone = function() { - let that = this - // New function instance - let f = function (...args) { return that.apply(this, args) } - // Copy properties - for (let k of Object.keys(this)) { f[k] = this[k] } - return f + let f = (...a) => this.apply(f, a) // new function instance + return Object.assign(f, this) // copy original properties } -// Symbols -export const _symbol = name => Symbol.for(name) -export const _symbol_Q = obj => typeof obj === 'symbol' - // Keywords export const _keyword = obj => _keyword_Q(obj) ? obj : '\u029e' + obj export const _keyword_Q = obj => typeof obj === 'string' && obj[0] === '\u029e' -// Lists -export const _list_Q = obj => Array.isArray(obj) && !obj.__isvector__ +// Sequence collections +export const _list_Q = obj => Array.isArray(obj) && !(obj instanceof Vector) -// Vectors -// TODO: Extend Array when supported -export function _vector(...args) { - let v = args.slice(0) - v.__isvector__ = true - return v -} -export const _vector_Q = obj => Array.isArray(obj) && !!obj.__isvector__ +export class Vector extends Array {} -// Hash Maps -export const _hash_map = (...args) => _assoc_BANG(new Map(), ...args) -export const _hash_map_Q = hm => hm instanceof Map export function _assoc_BANG(hm, ...args) { - if (args % 2 === 1) { + if (args.length % 2 === 1) { throw new Error('Odd number of assoc arguments') } // Use iterator/Array.from when it works - for (let i=0; i Date: Fri, 10 Feb 2017 22:19:55 -0600 Subject: [PATCH 0278/2308] miniMAL: update to using miniMAL 1.0.2 Also update miniMAL Dockerfile to xenial. --- miniMAL/Dockerfile | 7 ++- miniMAL/core.json | 8 +-- miniMAL/miniMAL-core.json | 102 +++++++++++++++++++++------------- miniMAL/package.json | 2 +- miniMAL/step0_repl.json | 3 +- miniMAL/step1_read_print.json | 8 +-- miniMAL/step2_eval.json | 8 +-- miniMAL/step3_env.json | 10 ++-- miniMAL/step4_if_fn_do.json | 12 ++-- miniMAL/step5_tco.json | 12 ++-- miniMAL/step6_file.json | 18 +++--- miniMAL/step7_quote.json | 18 +++--- miniMAL/step8_macros.json | 18 +++--- miniMAL/step9_try.json | 18 +++--- miniMAL/stepA_mal.json | 18 +++--- 15 files changed, 143 insertions(+), 119 deletions(-) diff --git a/miniMAL/Dockerfile b/miniMAL/Dockerfile index 6843896fa9..152c82a201 100644 --- a/miniMAL/Dockerfile +++ b/miniMAL/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:xenial MAINTAINER Joel Martin ########################################################## @@ -24,8 +24,8 @@ WORKDIR /mal # For building node modules RUN apt-get -y install g++ -# Add nodesource apt repo config for 0.12 stable -RUN curl -sL https://deb.nodesource.com/setup_0.12 | bash - +# Add nodesource apt repo config for 7.X +RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - # Install nodejs RUN apt-get -y install nodejs @@ -35,4 +35,5 @@ RUN ln -sf nodejs /usr/bin/node ENV NPM_CONFIG_CACHE /mal/.npm +# install miniMAL RUN npm install -g minimal-lisp diff --git a/miniMAL/core.json b/miniMAL/core.json index 6ddb7434a6..53d599c724 100644 --- a/miniMAL/core.json +++ b/miniMAL/core.json @@ -27,7 +27,7 @@ "hm"]]]], ["def", "_get", ["fn", ["obj", "key"], - ["if", ["nil?", "obj"], + ["if", ["null?", "obj"], null, ["if", ["contains?", "obj", "key"], ["get", "obj", "key"], @@ -77,7 +77,7 @@ ["if", [">", ["count", "obj"], 0], [".", "obj", ["`", "split"], ["`", ""]], null], - ["if", ["nil?", "obj"], + ["if", ["null?", "obj"], null, ["throw", "seq: called on non-sequence"] ]]]]]], @@ -98,7 +98,7 @@ null]]], ["def", "reset!", ["fn", ["atm", "val"], - ["set", "atm", ["`", "val"], "val"]]], + ["do", ["set", "atm", ["`", "val"], "val"], "val"]]], ["def", "swap!", ["fn", ["atm", "f", "&", "args"], ["let", ["fn", ["if", ["malfunc?", "f"], ["get", "f", ["`", "fn"]], "f"], @@ -113,7 +113,7 @@ ["`", "="], "equal?", ["`", "throw"], "throw", - ["`", "nil?"], "nil?", + ["`", "nil?"], "null?", ["`", "true?"], "true?", ["`", "false?"], "false?", ["`", "string?"], "_string?", diff --git a/miniMAL/miniMAL-core.json b/miniMAL/miniMAL-core.json index 6717968969..632cd2629d 100644 --- a/miniMAL/miniMAL-core.json +++ b/miniMAL/miniMAL-core.json @@ -1,9 +1,28 @@ ["do", -["def", "map", ["fn", ["a", "b"], [".", "b", ["`", "map"], "a"]]], +["def", "new", ["fn", ["a", "&", "b"], + [".", "Reflect", ["`", "construct"], "a", "b"]]], +["def", "del", ["fn", ["a", "b"], + [".", "Reflect", ["`", "deleteProperty"], "a", "b"]]], +["def", "map", ["fn", ["a", "b"], + [".", "b", ["`", "map"], ["fn", ["x"], ["a", "x"]]]]], +["def", "list", ["fn", ["&", "a"], "a"]], +["def", ">=", ["fn", ["a", "b"], + ["if", ["<", "a", "b"], false, true]]], +["def", ">", ["fn", ["a", "b"], + ["if", [">=", "a", "b"], + ["if", ["=", "a", "b"], false, true], + false]]], +["def", "<=", ["fn", ["a", "b"], + ["if", [">", "a", "b"], false, true]]], + +["def", "classOf", ["fn", ["a"], + [".", [".-", [".-", "Object", ["`", "prototype"]], ["`", "toString"]], + ["`", "call"], "a"]]], + ["def", "not", ["fn", ["a"], ["if", "a", false, true]]], -["def", "nil?", ["fn", ["a"], ["=", null, "a"]]], +["def", "null?", ["fn", ["a"], ["=", null, "a"]]], ["def", "true?", ["fn", ["a"], ["=", true, "a"]]], ["def", "false?", ["fn", ["a"], ["=", false, "a"]]], ["def", "string?", ["fn", ["a"], @@ -13,40 +32,38 @@ [".-", [".-", "a", ["`", "constructor"]], ["`", "name"]]]]]], -["def", "pr-list*", ["fn", ["a", "pr", "sep"], +["def", "pr-list*", ["fn", ["a", "b", "c"], [".", ["map", ["fn", ["x"], - ["if", "pr", + ["if", "c", [".", "JSON", ["`", "stringify"], "x"], ["if", ["string?", "x"], "x", [".", "JSON", ["`", "stringify"], "x"]]]], "a"], - ["`", "join"], "sep"]]], + ["`", "join"], "b"]]], ["def", "pr-str", ["fn", ["&", "a"], - ["pr-list*", "a", true, ["`", " "]]]], + ["pr-list*", "a", ["`", " "], true]]], ["def", "str", ["fn", ["&", "a"], - ["pr-list*", "a", false, ["`", ""]]]], + ["pr-list*", "a", ["`", ""], false]]], ["def", "prn", ["fn", ["&", "a"], - [".", "console", ["`", "log"], - ["pr-list*", "a", true, ["`", " "]]]]], + ["do", [".", "console", ["`", "log"], + ["pr-list*", "a", ["`", " "], true]], null]]], ["def", "println", ["fn", ["&", "a"], - [".", "console", ["`", "log"], - ["pr-list*", "a", false, ["`", " "]]]]], + ["do", [".", "console", ["`", "log"], + ["pr-list*", "a", ["`", " "], false]], null]]], -["def", ">=", ["fn", ["a", "b"], - ["if", ["<", "a", "b"], false, true]]], -["def", ">", ["fn", ["a", "b"], - ["if", [">=", "a", "b"], ["if", ["=", "a", "b"], false, true], false]]], -["def", "<=", ["fn", ["a", "b"], - ["if", [">", "a", "b"], false, true]]], - -["def", "list", ["fn", ["&", "a"], "a"]], -["def", "list?", ["fn", ["a"], [".", "Array", ["`", "isArray"], "a"]]], -["def", "get", ["fn", ["a", "b"], [".-", "a", "b"]]], -["def", "set", ["fn", ["a", "b", "c"], [".-", "a", "b", "c"]]], -["def", "contains?", ["fn", ["a", "b"], [".", "a", ["`", "hasOwnProperty"], "b"]]], -["def", "keys", ["fn", ["a"], [".", "Object", ["`", "keys"], "a"]]], -["def", "vals", ["fn", ["a"], ["map",["fn",["k"],["get","a","k"]],["keys", "a"]]]], +["def", "list?", ["fn", ["a"], + [".", "Array", ["`", "isArray"], "a"]]], +["def", "contains?", ["fn", ["a", "b"], + [".", "a", ["`", "hasOwnProperty"], "b"]]], +["def", "get", ["fn", ["a", "b"], + ["if", ["contains?", "a", "b"], [".-", "a", "b"], null]]], +["def", "set", ["fn", ["a", "b", "c"], + ["do", [".-", "a", "b", "c"], "a"]]], +["def", "keys", ["fn", ["a"], + [".", "Object", ["`", "keys"], "a"]]], +["def", "vals", ["fn", ["a"], + ["map",["fn", ["k"], ["get", "a", "k"]], ["keys", "a"]]]], ["def", "cons", ["fn", ["a", "b"], [".", ["`", []], @@ -55,18 +72,28 @@ [".", [".-", ["list"], ["`", "concat"]], ["`", "apply"], ["list"], "a"]]], ["def", "nth", "get"], -["def", "first", ["fn", ["a"], ["nth", "a", 0]]], -["def", "rest", ["fn", ["a"], [".", "a", ["`", "slice"], 1]]], -["def", "empty?", ["fn", ["a"], ["if", ["list?", "a"], ["if", ["=", 0, [".-", "a", ["`", "length"]]], true, false], ["=", "a", null]]]], +["def", "first", ["fn", ["a"], + ["if", [">", [".-", "a", ["`", "length"]], 0], + ["nth", "a", 0], + null]]], +["def", "last", ["fn", ["a"], + ["nth", "a", ["-", [".-", "a", ["`", "length"]], 1]]]], ["def", "count", ["fn", ["a"], [".-", "a", ["`", "length"]]]], -["def", "slice", ["fn", ["a", "start", "&", "endl"], - ["let", ["end", ["if", ["count", "endl"], - ["get", "endl", 0], - [".-", "a", ["`", "length"]]]], - [".", "a", ["`", "slice"], "start", "end"]]]], +["def", "empty?", ["fn", ["a"], + ["if", ["list?", "a"], + ["if", ["=", 0, [".-", "a", ["`", "length"]]], true, false], + ["=", "a", null]]]], +["def", "slice", ["fn", ["a", "b", "&", "end"], + [".", "a", ["`", "slice"], "b", + ["if", [">", ["count", "end"], 0], + ["get", "end", 0], + [".-", "a", ["`", "length"]]]]]], +["def", "rest", ["fn", ["a"], ["slice", "a", 1]]], -["def", "apply", ["fn", ["a", "b"], [".", "a", ["`", "apply"], "a", "b"]]], +["def", "apply", ["fn", ["f", "&", "b"], + [".", "f", ["`", "apply"], "f", + ["concat", ["slice", "b", 0, -1], ["last", "b"]]]]], ["def", "and", ["~", ["fn", ["&", "xs"], ["if", ["empty?", "xs"], @@ -88,14 +115,9 @@ ["`", "or_FIXME"], ["concat", ["`", ["or"]], ["rest", "xs"]]]]]]]]], -["def", "classOf", ["fn", ["a"], - [".", [".-", [".-", "Object", ["`", "prototype"]], ["`", "toString"]], - ["`", "call"], "a"]]], - - ["def", "repl", ["fn",["prompt", "rep"], ["let", ["readline", ["require", ["`", "readline"]], - "opts", ["new", ["fn", [], null]], + "opts", ["new", "Object"], "_", ["set", "opts", ["`", "input"], [".-", "process", ["`", "stdin"]]], "_", ["set", "opts", ["`", "output"], [".-", "process", ["`", "stdout"]]], "_", ["set", "opts", ["`", "terminal"], false], diff --git a/miniMAL/package.json b/miniMAL/package.json index 3f403d0594..0c87452b7e 100644 --- a/miniMAL/package.json +++ b/miniMAL/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Make a Lisp (mal) language implemented in miniMAL", "dependencies": { - "minimal-lisp": "0.0.6", + "minimal-lisp": "1.0.2", "ffi": "2.0.x" } } diff --git a/miniMAL/step0_repl.json b/miniMAL/step0_repl.json index 50f8846431..ca4cdf1bbf 100644 --- a/miniMAL/step0_repl.json +++ b/miniMAL/step0_repl.json @@ -1,6 +1,7 @@ ["do", -["load-file", ["`", "miniMAL-core.json"]], + +["load", ["`", "miniMAL-core.json"]], ["def", "READ", ["fn", ["strng"], "strng"]], diff --git a/miniMAL/step1_read_print.json b/miniMAL/step1_read_print.json index dc1f2695f6..c8e3d6129a 100644 --- a/miniMAL/step1_read_print.json +++ b/miniMAL/step1_read_print.json @@ -1,9 +1,9 @@ ["do", -["load-file", ["`", "miniMAL-core.json"]], -["load-file", ["`", "types.json"]], -["load-file", ["`", "reader.json"]], -["load-file", ["`", "printer.json"]], +["load", ["`", "miniMAL-core.json"]], +["load", ["`", "types.json"]], +["load", ["`", "reader.json"]], +["load", ["`", "printer.json"]], ["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]], diff --git a/miniMAL/step2_eval.json b/miniMAL/step2_eval.json index 40517a59d4..cc16f6384a 100644 --- a/miniMAL/step2_eval.json +++ b/miniMAL/step2_eval.json @@ -1,9 +1,9 @@ ["do", -["load-file", ["`", "miniMAL-core.json"]], -["load-file", ["`", "types.json"]], -["load-file", ["`", "reader.json"]], -["load-file", ["`", "printer.json"]], +["load", ["`", "miniMAL-core.json"]], +["load", ["`", "types.json"]], +["load", ["`", "reader.json"]], +["load", ["`", "printer.json"]], ["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]], diff --git a/miniMAL/step3_env.json b/miniMAL/step3_env.json index c423084599..3aadc8fbf1 100644 --- a/miniMAL/step3_env.json +++ b/miniMAL/step3_env.json @@ -1,10 +1,10 @@ ["do", -["load-file", ["`", "miniMAL-core.json"]], -["load-file", ["`", "types.json"]], -["load-file", ["`", "reader.json"]], -["load-file", ["`", "printer.json"]], -["load-file", ["`", "env.json"]], +["load", ["`", "miniMAL-core.json"]], +["load", ["`", "types.json"]], +["load", ["`", "reader.json"]], +["load", ["`", "printer.json"]], +["load", ["`", "env.json"]], ["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]], diff --git a/miniMAL/step4_if_fn_do.json b/miniMAL/step4_if_fn_do.json index 9b69a8f61b..699aa6d6ae 100644 --- a/miniMAL/step4_if_fn_do.json +++ b/miniMAL/step4_if_fn_do.json @@ -1,11 +1,11 @@ ["do", -["load-file", ["`", "miniMAL-core.json"]], -["load-file", ["`", "types.json"]], -["load-file", ["`", "reader.json"]], -["load-file", ["`", "printer.json"]], -["load-file", ["`", "env.json"]], -["load-file", ["`", "core.json"]], +["load", ["`", "miniMAL-core.json"]], +["load", ["`", "types.json"]], +["load", ["`", "reader.json"]], +["load", ["`", "printer.json"]], +["load", ["`", "env.json"]], +["load", ["`", "core.json"]], ["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]], diff --git a/miniMAL/step5_tco.json b/miniMAL/step5_tco.json index 43c73bf4fa..bdc12a0b4d 100644 --- a/miniMAL/step5_tco.json +++ b/miniMAL/step5_tco.json @@ -1,11 +1,11 @@ ["do", -["load-file", ["`", "miniMAL-core.json"]], -["load-file", ["`", "types.json"]], -["load-file", ["`", "reader.json"]], -["load-file", ["`", "printer.json"]], -["load-file", ["`", "env.json"]], -["load-file", ["`", "core.json"]], +["load", ["`", "miniMAL-core.json"]], +["load", ["`", "types.json"]], +["load", ["`", "reader.json"]], +["load", ["`", "printer.json"]], +["load", ["`", "env.json"]], +["load", ["`", "core.json"]], ["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]], diff --git a/miniMAL/step6_file.json b/miniMAL/step6_file.json index 768cca7411..67c608832f 100644 --- a/miniMAL/step6_file.json +++ b/miniMAL/step6_file.json @@ -1,11 +1,11 @@ ["do", -["load-file", ["`", "miniMAL-core.json"]], -["load-file", ["`", "types.json"]], -["load-file", ["`", "reader.json"]], -["load-file", ["`", "printer.json"]], -["load-file", ["`", "env.json"]], -["load-file", ["`", "core.json"]], +["load", ["`", "miniMAL-core.json"]], +["load", ["`", "types.json"]], +["load", ["`", "reader.json"]], +["load", ["`", "printer.json"]], +["load", ["`", "env.json"]], +["load", ["`", "core.json"]], ["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]], @@ -94,14 +94,14 @@ ["env-set", "repl-env", ["symbol", ["`", "eval"]], ["fn", ["ast"], ["EVAL", "ast", "repl-env"]]], ["env-set", "repl-env", ["symbol", ["`", "*ARGV*"]], - ["slice", "*ARGV*", 1]], + ["slice", "ARGS", 1]], ["`", "core.mal: defined using mal itself"], ["rep", ["`", "(def! not (fn* (a) (if a false true)))"]], ["rep", ["`", "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"]], -["if", ["not", ["empty?", "*ARGV*"]], - ["rep", ["str", ["`", "(load-file \""], ["get", "*ARGV*", 0], ["`", "\")"]]], +["if", ["not", ["empty?", "ARGS"]], + ["rep", ["str", ["`", "(load-file \""], ["get", "ARGS", 0], ["`", "\")"]]], ["repl", ["`", "user> "], "rep"]], null diff --git a/miniMAL/step7_quote.json b/miniMAL/step7_quote.json index f764ffd74c..28b062c7ca 100644 --- a/miniMAL/step7_quote.json +++ b/miniMAL/step7_quote.json @@ -1,11 +1,11 @@ ["do", -["load-file", ["`", "miniMAL-core.json"]], -["load-file", ["`", "types.json"]], -["load-file", ["`", "reader.json"]], -["load-file", ["`", "printer.json"]], -["load-file", ["`", "env.json"]], -["load-file", ["`", "core.json"]], +["load", ["`", "miniMAL-core.json"]], +["load", ["`", "types.json"]], +["load", ["`", "reader.json"]], +["load", ["`", "printer.json"]], +["load", ["`", "env.json"]], +["load", ["`", "core.json"]], ["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]], @@ -119,14 +119,14 @@ ["env-set", "repl-env", ["symbol", ["`", "eval"]], ["fn", ["ast"], ["EVAL", "ast", "repl-env"]]], ["env-set", "repl-env", ["symbol", ["`", "*ARGV*"]], - ["slice", "*ARGV*", 1]], + ["slice", "ARGS", 1]], ["`", "core.mal: defined using mal itself"], ["rep", ["`", "(def! not (fn* (a) (if a false true)))"]], ["rep", ["`", "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"]], -["if", ["not", ["empty?", "*ARGV*"]], - ["rep", ["str", ["`", "(load-file \""], ["get", "*ARGV*", 0], ["`", "\")"]]], +["if", ["not", ["empty?", "ARGS"]], + ["rep", ["str", ["`", "(load-file \""], ["get", "ARGS", 0], ["`", "\")"]]], ["repl", ["`", "user> "], "rep"]], null diff --git a/miniMAL/step8_macros.json b/miniMAL/step8_macros.json index c3a05fca37..2383e4107e 100644 --- a/miniMAL/step8_macros.json +++ b/miniMAL/step8_macros.json @@ -1,11 +1,11 @@ ["do", -["load-file", ["`", "miniMAL-core.json"]], -["load-file", ["`", "types.json"]], -["load-file", ["`", "reader.json"]], -["load-file", ["`", "printer.json"]], -["load-file", ["`", "env.json"]], -["load-file", ["`", "core.json"]], +["load", ["`", "miniMAL-core.json"]], +["load", ["`", "types.json"]], +["load", ["`", "reader.json"]], +["load", ["`", "printer.json"]], +["load", ["`", "env.json"]], +["load", ["`", "core.json"]], ["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]], @@ -143,7 +143,7 @@ ["env-set", "repl-env", ["symbol", ["`", "eval"]], ["fn", ["ast"], ["EVAL", "ast", "repl-env"]]], ["env-set", "repl-env", ["symbol", ["`", "*ARGV*"]], - ["slice", "*ARGV*", 1]], + ["slice", "ARGS", 1]], ["`", "core.mal: defined using mal itself"], ["rep", ["`", "(def! not (fn* (a) (if a false true)))"]], @@ -151,8 +151,8 @@ ["rep", ["`", "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))"]], ["rep", ["`", "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"]], -["if", ["not", ["empty?", "*ARGV*"]], - ["rep", ["str", ["`", "(load-file \""], ["get", "*ARGV*", 0], ["`", "\")"]]], +["if", ["not", ["empty?", "ARGS"]], + ["rep", ["str", ["`", "(load-file \""], ["get", "ARGS", 0], ["`", "\")"]]], ["repl", ["`", "user> "], "rep"]], null diff --git a/miniMAL/step9_try.json b/miniMAL/step9_try.json index e7fbdab63a..8dfc0d2752 100644 --- a/miniMAL/step9_try.json +++ b/miniMAL/step9_try.json @@ -1,11 +1,11 @@ ["do", -["load-file", ["`", "miniMAL-core.json"]], -["load-file", ["`", "types.json"]], -["load-file", ["`", "reader.json"]], -["load-file", ["`", "printer.json"]], -["load-file", ["`", "env.json"]], -["load-file", ["`", "core.json"]], +["load", ["`", "miniMAL-core.json"]], +["load", ["`", "types.json"]], +["load", ["`", "reader.json"]], +["load", ["`", "printer.json"]], +["load", ["`", "env.json"]], +["load", ["`", "core.json"]], ["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]], @@ -154,7 +154,7 @@ ["env-set", "repl-env", ["symbol", ["`", "eval"]], ["fn", ["ast"], ["EVAL", "ast", "repl-env"]]], ["env-set", "repl-env", ["symbol", ["`", "*ARGV*"]], - ["slice", "*ARGV*", 1]], + ["slice", "ARGS", 1]], ["`", "core.mal: defined using mal itself"], ["rep", ["`", "(def! not (fn* (a) (if a false true)))"]], @@ -162,8 +162,8 @@ ["rep", ["`", "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))"]], ["rep", ["`", "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"]], -["if", ["not", ["empty?", "*ARGV*"]], - ["rep", ["str", ["`", "(load-file \""], ["get", "*ARGV*", 0], ["`", "\")"]]], +["if", ["not", ["empty?", "ARGS"]], + ["rep", ["str", ["`", "(load-file \""], ["get", "ARGS", 0], ["`", "\")"]]], ["repl", ["`", "user> "], "rep"]], null diff --git a/miniMAL/stepA_mal.json b/miniMAL/stepA_mal.json index 789949dfdd..789f88a429 100644 --- a/miniMAL/stepA_mal.json +++ b/miniMAL/stepA_mal.json @@ -1,11 +1,11 @@ ["do", -["load-file", ["`", "miniMAL-core.json"]], -["load-file", ["`", "types.json"]], -["load-file", ["`", "reader.json"]], -["load-file", ["`", "printer.json"]], -["load-file", ["`", "env.json"]], -["load-file", ["`", "core.json"]], +["load", ["`", "miniMAL-core.json"]], +["load", ["`", "types.json"]], +["load", ["`", "reader.json"]], +["load", ["`", "printer.json"]], +["load", ["`", "env.json"]], +["load", ["`", "core.json"]], ["def", "READ", ["fn", ["strng"], ["read-str", "strng"]]], @@ -154,7 +154,7 @@ ["env-set", "repl-env", ["symbol", ["`", "eval"]], ["fn", ["ast"], ["EVAL", "ast", "repl-env"]]], ["env-set", "repl-env", ["symbol", ["`", "*ARGV*"]], - ["slice", "*ARGV*", 1]], + ["slice", "ARGS", 1]], ["`", "core.mal: defined using mal itself"], ["rep", ["`", "(def! *host-language* \"miniMAL\")"]], @@ -165,8 +165,8 @@ ["rep", ["`", "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))"]], ["rep", ["`", "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))"]], -["if", ["not", ["empty?", "*ARGV*"]], - ["println", ["rep", ["str", ["`", "(load-file \""], ["get", "*ARGV*", 0], ["`", "\")"]]]], +["if", ["not", ["empty?", "ARGS"]], + ["println", ["rep", ["str", ["`", "(load-file \""], ["get", "ARGS", 0], ["`", "\")"]]]], ["do", ["rep", ["`", "(println (str \"Mal [\" *host-language* \"]\"))"]], ["repl", ["`", "user> "], "rep"]]], From 2b933f429c622e7d920e2f954a57297c6cd8ff5d Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 10 Feb 2017 23:58:37 -0600 Subject: [PATCH 0279/2308] Clojure: support ClojureScript using CLJ_MODE=cljs - Requires conditional in stepA tests. --- Makefile | 10 ++- clojure/Dockerfile | 23 +++++-- clojure/Makefile | 15 +++-- clojure/package.json | 9 +++ clojure/project.clj | 44 ++++++------- clojure/run | 7 ++- clojure/src/{core.clj => mal/core.cljc} | 22 +++++-- clojure/src/{env.clj => mal/env.cljc} | 5 +- clojure/src/mal/node_readline.js | 46 ++++++++++++++ clojure/src/mal/printer.cljc | 63 +++++++++++++++++++ clojure/src/{reader.clj => mal/reader.cljc} | 11 ++-- clojure/src/{ => mal}/readline.clj | 2 +- clojure/src/mal/readline.cljs | 3 + .../{step0_repl.clj => mal/step0_repl.cljc} | 10 +-- .../step1_read_print.cljc} | 19 +++--- .../{step2_eval.clj => mal/step2_eval.cljc} | 22 +++---- .../src/{step3_env.clj => mal/step3_env.cljc} | 21 +++---- .../step4_if_fn_do.cljc} | 23 ++++--- .../src/{step5_tco.clj => mal/step5_tco.cljc} | 23 ++++--- .../{step6_file.clj => mal/step6_file.cljc} | 23 ++++--- .../{step7_quote.clj => mal/step7_quote.cljc} | 23 ++++--- .../step8_macros.cljc} | 25 ++++---- .../src/{step9_try.clj => mal/step9_try.cljc} | 33 +++++----- .../src/{stepA_mal.clj => mal/stepA_mal.cljc} | 43 +++++++------ clojure/src/printer.clj | 36 ----------- clojure/tests/stepA_mal.mal | 17 +++-- 26 files changed, 356 insertions(+), 222 deletions(-) create mode 100644 clojure/package.json rename clojure/src/{core.clj => mal/core.cljc} (74%) rename clojure/src/{env.clj => mal/env.cljc} (86%) create mode 100644 clojure/src/mal/node_readline.js create mode 100644 clojure/src/mal/printer.cljc rename clojure/src/{reader.clj => mal/reader.cljc} (77%) rename clojure/src/{ => mal}/readline.clj (98%) create mode 100644 clojure/src/mal/readline.cljs rename clojure/src/{step0_repl.clj => mal/step0_repl.cljc} (63%) rename clojure/src/{step1_read_print.clj => mal/step1_read_print.cljc} (54%) rename clojure/src/{step2_eval.clj => mal/step2_eval.cljc} (72%) rename clojure/src/{step3_env.clj => mal/step3_env.cljc} (81%) rename clojure/src/{step4_if_fn_do.clj => mal/step4_if_fn_do.cljc} (83%) rename clojure/src/{step5_tco.clj => mal/step5_tco.cljc} (86%) rename clojure/src/{step6_file.clj => mal/step6_file.cljc} (87%) rename clojure/src/{step7_quote.clj => mal/step7_quote.cljc} (88%) rename clojure/src/{step8_macros.clj => mal/step8_macros.cljc} (90%) rename clojure/src/{step9_try.clj => mal/step9_try.cljc} (85%) rename clojure/src/{stepA_mal.clj => mal/stepA_mal.cljc} (81%) delete mode 100644 clojure/src/printer.clj diff --git a/Makefile b/Makefile index c458e2ecb5..9648c66988 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,8 @@ PYTHON = python USE_MATLAB = # python, js, cpp, or neko are currently supported HAXE_MODE = neko +# clj or cljs are currently supported (Clojure vs ClojureScript/lumo) +CLJ_MODE = clj # Extra options to pass to runtest.py TEST_OPTS = @@ -68,8 +70,6 @@ plpgsql_TEST_OPTS = --start-timeout 60 --test-timeout 180 plsql_TEST_OPTS = --start-timeout 120 --test-timeout 120 perl6_TEST_OPTS = --test-timeout=60 -DOCKERIZE= - # Run target/rule within docker image for the implementation DOCKERIZE = @@ -133,6 +133,9 @@ haxe_STEP_TO_PROG_python = haxe/$($(1)).py haxe_STEP_TO_PROG_cpp = haxe/cpp/$($(1)) haxe_STEP_TO_PROG_js = haxe/$($(1)).js +clojure_STEP_TO_PROG_clj = clojure/target/$($(1)).jar +clojure_STEP_TO_PROG_cljs = clojure/src/mal/$($(1)).cljc + opt_DEFERRABLE = $(if $(strip $(DEFERRABLE)),$(if $(filter t true T True TRUE 1 y yes Yes YES,$(DEFERRABLE)),--deferrable,--no-deferrable),--no-deferrable) opt_OPTIONAL = $(if $(strip $(OPTIONAL)),$(if $(filter t true T True TRUE 1 y yes Yes YES,$(OPTIONAL)),--optional,--no-optional),--no-optional) @@ -151,7 +154,7 @@ basic_STEP_TO_PROG = basic/$($(1)).bas c_STEP_TO_PROG = c/$($(1)) d_STEP_TO_PROG = d/$($(1)) chuck_STEP_TO_PROG = chuck/$($(1)).ck -clojure_STEP_TO_PROG = clojure/target/$($(1)).jar +clojure_STEP_TO_PROG = $(clojure_STEP_TO_PROG_$(CLJ_MODE)) coffee_STEP_TO_PROG = coffee/$($(1)).coffee common-lisp_STEP_TO_PROG = common-lisp/$($(1)) cpp_STEP_TO_PROG = cpp/$($(1)) @@ -234,6 +237,7 @@ get_run_prefix = $(strip $(if $(strip $(DOCKERIZE)),\ -it --rm -u $(shell id -u) \ -v $(dir $(abspath $(lastword $(MAKEFILE_LIST)))):/mal \ -w /mal/$(call actual_impl,$(1)) \ + $(if $(filter clojure,$(1)),-e CLJ_MODE=$(CLJ_MODE),) \ $(if $(filter haxe,$(1)),-e HAXE_MODE=$(HAXE_MODE),) \ $(if $(filter factor,$(1)),-e FACTOR_ROOTS=$(FACTOR_ROOTS),) \ $(foreach env,$(3),-e $(env)) \ diff --git a/clojure/Dockerfile b/clojure/Dockerfile index 1701219c7b..1784e6e189 100644 --- a/clojure/Dockerfile +++ b/clojure/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:xenial MAINTAINER Joel Martin ########################################################## @@ -21,10 +21,11 @@ WORKDIR /mal # Specific implementation requirements ########################################################## -# Java and maven -RUN apt-get -y install openjdk-7-jdk -#maven2 -#ENV MAVEN_OPTS -Duser.home=/mal +# +# Clojure (Java and lein) +# + +RUN apt-get -y install openjdk-8-jdk ADD https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein \ /usr/local/bin/lein @@ -32,3 +33,15 @@ RUN chmod 0755 /usr/local/bin/lein ENV LEIN_HOME /mal/.lein ENV LEIN_JVM_OPTS -Duser.home=/mal +# +# ClojureScript (Node and Lumo) +# + +# For building node modules +RUN apt-get -y install g++ + +# Add nodesource apt repo config for 7.X +RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - + +# Install nodejs. Install ffi and lumo-cljs modules globally +RUN apt-get -y install nodejs && npm install -g ffi lumo-cljs diff --git a/clojure/Makefile b/clojure/Makefile index 17fcc593e6..5fda6dbb82 100644 --- a/clojure/Makefile +++ b/clojure/Makefile @@ -1,9 +1,11 @@ -SOURCES_BASE = src/readline.clj src/reader.clj src/printer.clj -SOURCES_LISP = src/env.clj src/core.clj src/stepA_mal.clj -SRCS = $(SOURCES_BASE) src/env.clj src/core.clj +CLJ_MODE ?= clj +SOURCES_UTIL = src/mal/readline.$(CLJ_MODE) +SOURCES_BASE = $(SOURCES_UTIL) src/mal/printer.cljc +SOURCES_LISP = src/mal/env.cljc src/mal/core.cljc src/mal/stepA_mal.cljc +SRCS = $(SOURCES_BASE) src/mal/env.cljc src/mal/core.cljc SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -all: deps +all: deps node_modules dist: mal.jar mal @@ -19,9 +21,12 @@ mal: mal.jar cat <(echo -e '#!/bin/sh\nexec java -jar "$$0" "$$@"') mal.jar > $@ chmod +x mal -target/%.jar: src/%.clj $(SRCS) +target/%.jar: src/mal/%.cljc $(SRCS) lein with-profile $(word 1,$(subst _, ,$*)) uberjar +node_modules: + npm install + clean: rm -rf target/ mal.jar mal diff --git a/clojure/package.json b/clojure/package.json new file mode 100644 index 0000000000..dfaab9cd82 --- /dev/null +++ b/clojure/package.json @@ -0,0 +1,9 @@ +{ + "name": "mal", + "version": "0.0.1", + "description": "Make a Lisp (mal) language implemented in ClojureScript", + "dependencies": { + "ffi": "2.2.x", + "lumo-cljs": "1.0.0" + } +} diff --git a/clojure/project.clj b/clojure/project.clj index acfe822661..f2eea933b3 100644 --- a/clojure/project.clj +++ b/clojure/project.clj @@ -9,37 +9,37 @@ ;; lein trampoline with-profile stepX run ;; To generate a executable uberjar (in target/) for a step: ;; lein with-profile stepX repl - :profiles {:step0 {:main step0-repl + :profiles {:step0 {:main mal.step0-repl :uberjar-name "step0_repl.jar" - :aot [step0-repl]} - :step1 {:main step1-read-print + :aot [mal.step0-repl]} + :step1 {:main mal.step1-read-print :uberjar-name "step1_read_print.jar" - :aot [step1-read-print]} - :step2 {:main step2-eval + :aot [mal.step1-read-print]} + :step2 {:main mal.step2-eval :uberjar-name "step2_eval.jar" - :aot [step2-eval]} - :step3 {:main step3-env + :aot [mal.step2-eval]} + :step3 {:main mal.step3-env :uberjar-name "step3_env.jar" - :aot [step3-env]} - :step4 {:main step4-if-fn-do + :aot [mal.step3-env]} + :step4 {:main mal.step4-if-fn-do :uberjar-name "step4_if_fn_do.jar" - :aot [step4-if-fn-do]} - :step5 {:main step5-tco + :aot [mal.step4-if-fn-do]} + :step5 {:main mal.step5-tco :uberjar-name "step5_tco.jar" - :aot [step5-tco]} - :step6 {:main step6-file + :aot [mal.step5-tco]} + :step6 {:main mal.step6-file :uberjar-name "step6_file.jar" - :aot [step6-file]} - :step7 {:main step7-quote + :aot [mal.step6-file]} + :step7 {:main mal.step7-quote :uberjar-name "step7_quote.jar" - :aot [step7-quote]} - :step8 {:main step8-macros + :aot [mal.step7-quote]} + :step8 {:main mal.step8-macros :uberjar-name "step8_macros.jar" - :aot [step8-macros]} - :step9 {:main step9-try + :aot [mal.step8-macros]} + :step9 {:main mal.step9-try :uberjar-name "step9_try.jar" - :aot [step9-try]} - :stepA {:main stepA-mal + :aot [mal.step9-try]} + :stepA {:main mal.stepA-mal :uberjar-name "stepA_mal.jar" - :aot [stepA-mal]}}) + :aot [mal.stepA-mal]}}) diff --git a/clojure/run b/clojure/run index 94325d438c..a00d501ad4 100755 --- a/clojure/run +++ b/clojure/run @@ -1,2 +1,7 @@ #!/bin/bash -exec java -jar $(dirname $0)/target/${STEP:-stepA_mal}.jar "${@}" +STEP=${STEP:-stepA_mal} +if [ "${CLJ_MODE}" = "cljs" ]; then + exec lumo -c $(dirname $0)/src -m mal.${STEP//_/-} "${@}" +else + exec java -jar $(dirname $0)/target/${STEP}.jar "${@}" +fi diff --git a/clojure/src/core.clj b/clojure/src/mal/core.cljc similarity index 74% rename from clojure/src/core.clj rename to clojure/src/mal/core.cljc index 763ae8644b..103348929d 100644 --- a/clojure/src/core.clj +++ b/clojure/src/mal/core.cljc @@ -1,11 +1,23 @@ -(ns core - (:require [readline] - [printer])) +(ns mal.core + (:require [mal.readline :as readline] + [mal.reader :as reader] + [mal.printer :as printer])) ;; Errors/exceptions (defn mal_throw [obj] (throw (ex-info "mal exception" {:data obj}))) +;; String functions +#?(:cljs (defn slurp [f] (.readFileSync (js/require "fs") f "utf-8"))) + +;; Numeric functions +#?(:clj (defn time-ms [] (System/currentTimeMillis)) + :cljs (defn time-ms [] (.getTime (js/Date.)))) + +;; Atom functions +#?(:clj (defn atom? [atm] (= (type atm) clojure.lang.Atom)) + :cljs (defn atom? [atm] (satisfies? IAtom atm))) + ;; Metadata functions ;; - store metadata at :meta key of the real metadata (defn mal_with_meta [obj m] @@ -43,7 +55,7 @@ ['- -] ['* *] ['/ /] - ['time-ms (fn time-ms [] (System/currentTimeMillis))] + ['time-ms time-ms] ['list list] ['list? seq?] @@ -75,7 +87,7 @@ ['with-meta mal_with_meta] ['meta mal_meta] ['atom atom] - ['atom? (fn atom? [atm] (= (type atm) clojure.lang.Atom))] + ['atom? atom?] ['deref deref] ['reset! reset!] ['swap! swap!]]) diff --git a/clojure/src/env.clj b/clojure/src/mal/env.cljc similarity index 86% rename from clojure/src/env.clj rename to clojure/src/mal/env.cljc index b430be3498..9595a1c560 100644 --- a/clojure/src/env.clj +++ b/clojure/src/mal/env.cljc @@ -1,4 +1,4 @@ -(ns env) +(ns mal.env) (defn env [& [outer binds exprs]] ;;(prn "env" binds exprs) @@ -27,7 +27,8 @@ (defn env-get [env k] (let [e (env-find env k)] (when-not e - (throw (Exception. (str "'" k "' not found")))) + (throw (#?(:clj Exception. + :cljs js/Error.) (str "'" k "' not found")))) (get @e k))) (defn env-set [env k v] diff --git a/clojure/src/mal/node_readline.js b/clojure/src/mal/node_readline.js new file mode 100644 index 0000000000..dc64e3f642 --- /dev/null +++ b/clojure/src/mal/node_readline.js @@ -0,0 +1,46 @@ +// IMPORTANT: choose one +var RL_LIB = "libreadline"; // NOTE: libreadline is GPL +//var RL_LIB = "libedit"; + +var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history'); + +var rlwrap = {}; // namespace for this module in web context + +var ffi = require('ffi'), + fs = require('fs'); + +var rllib = ffi.Library(RL_LIB, { + 'readline': [ 'string', [ 'string' ] ], + 'add_history': [ 'int', [ 'string' ] ]}); + +var rl_history_loaded = false; + +exports.readline = rlwrap.readline = function(prompt) { + prompt = typeof prompt !== 'undefined' ? prompt : "user> "; + + if (!rl_history_loaded) { + rl_history_loaded = true; + var lines = []; + if (fs.existsSync(HISTORY_FILE)) { + lines = fs.readFileSync(HISTORY_FILE).toString().split("\n"); + } + // Max of 2000 lines + lines = lines.slice(Math.max(lines.length - 2000, 0)); + for (var i=0; i ")] (when line - (println (rep line)) + (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment + (println (rep line))) (recur)))) (defn -main [& args] diff --git a/clojure/src/step1_read_print.clj b/clojure/src/mal/step1_read_print.cljc similarity index 54% rename from clojure/src/step1_read_print.clj rename to clojure/src/mal/step1_read_print.cljc index 9fe1ae978c..d942174286 100644 --- a/clojure/src/step1_read_print.clj +++ b/clojure/src/mal/step1_read_print.cljc @@ -1,14 +1,13 @@ -(ns step1-read-print - (:require [clojure.repl] - [readline] - [reader] - [printer]) - (:gen-class)) +(ns mal.step1-read-print + (:require [mal.readline :as readline] + #?(:clj [clojure.repl]) + [mal.reader :as reader] + [mal.printer :as printer]) + #?(:clj (:gen-class))) ;; read (defn READ [& [strng]] - (let [line (if strng strng (read-line))] - (reader/read-string strng))) + (reader/read-string strng)) ;; eval (defn EVAL [ast env] @@ -29,8 +28,8 @@ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment (try (println (rep line)) - (catch Throwable e - (clojure.repl/pst e)))) + #?(:clj (catch Throwable e (clojure.repl/pst e)) + :cljs (catch js/Error e (println (.-stack e)))))) (recur)))) (defn -main [& args] diff --git a/clojure/src/step2_eval.clj b/clojure/src/mal/step2_eval.cljc similarity index 72% rename from clojure/src/step2_eval.clj rename to clojure/src/mal/step2_eval.cljc index e1c02e948a..51c0042239 100644 --- a/clojure/src/step2_eval.clj +++ b/clojure/src/mal/step2_eval.cljc @@ -1,21 +1,21 @@ -(ns step2-eval - (:require [clojure.repl] - [readline] - [reader] - [printer]) - (:gen-class)) +(ns mal.step2-eval + (:require [mal.readline :as readline] + #?(:clj [clojure.repl]) + [mal.reader :as reader] + [mal.printer :as printer]) + #?(:clj (:gen-class))) ;; read (defn READ [& [strng]] - (let [line (if strng strng (read-line))] - (reader/read-string strng))) + (reader/read-string strng)) ;; eval (declare EVAL) (defn eval-ast [ast env] (cond (symbol? ast) (or (get env ast) - (throw (Error. (str ast " not found")))) + (throw (#?(:clj Error. + :cljs js/Error.) (str ast " not found")))) (seq? ast) (doall (map #(EVAL % env) ast)) @@ -60,8 +60,8 @@ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment (try (println (rep line)) - (catch Throwable e - (clojure.repl/pst e)))) + #?(:clj (catch Throwable e (clojure.repl/pst e)) + :cljs (catch js/Error e (println (.-stack e)))))) (recur)))) (defn -main [& args] diff --git a/clojure/src/step3_env.clj b/clojure/src/mal/step3_env.cljc similarity index 81% rename from clojure/src/step3_env.clj rename to clojure/src/mal/step3_env.cljc index 64f9b09dee..2779574ac9 100644 --- a/clojure/src/step3_env.clj +++ b/clojure/src/mal/step3_env.cljc @@ -1,15 +1,14 @@ -(ns step3-env - (:require [clojure.repl] - [readline] - [reader] - [printer] - [env]) - (:gen-class)) +(ns mal.step3-env + (:require [mal.readline :as readline] + #?(:clj [clojure.repl]) + [mal.reader :as reader] + [mal.printer :as printer] + [mal.env :as env]) + #?(:clj (:gen-class))) ;; read (defn READ [& [strng]] - (let [line (if strng strng (read-line))] - (reader/read-string strng))) + (reader/read-string strng)) ;; eval (declare EVAL) @@ -75,8 +74,8 @@ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment (try (println (rep line)) - (catch Throwable e - (clojure.repl/pst e)))) + #?(:clj (catch Throwable e (clojure.repl/pst e)) + :cljs (catch js/Error e (println (.-stack e)))))) (recur)))) (defn -main [& args] diff --git a/clojure/src/step4_if_fn_do.clj b/clojure/src/mal/step4_if_fn_do.cljc similarity index 83% rename from clojure/src/step4_if_fn_do.clj rename to clojure/src/mal/step4_if_fn_do.cljc index 71a5718fbc..4cca3a3b2c 100644 --- a/clojure/src/step4_if_fn_do.clj +++ b/clojure/src/mal/step4_if_fn_do.cljc @@ -1,16 +1,15 @@ -(ns step4-if-fn-do - (:require [clojure.repl] - [readline] - [reader] - [printer] - [env] - [core]) - (:gen-class)) +(ns mal.step4-if-fn-do + (:require [mal.readline :as readline] + #?(:clj [clojure.repl]) + [mal.reader :as reader] + [mal.printer :as printer] + [mal.env :as env] + [mal.core :as core]) + #?(:clj (:gen-class))) ;; read (defn READ [& [strng]] - (let [line (if strng strng (read-line))] - (reader/read-string strng))) + (reader/read-string strng)) ;; eval (declare EVAL) @@ -92,8 +91,8 @@ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment (try (println (rep line)) - (catch Throwable e - (clojure.repl/pst e)))) + #?(:clj (catch Throwable e (clojure.repl/pst e)) + :cljs (catch js/Error e (println (.-stack e)))))) (recur)))) (defn -main [& args] diff --git a/clojure/src/step5_tco.clj b/clojure/src/mal/step5_tco.cljc similarity index 86% rename from clojure/src/step5_tco.clj rename to clojure/src/mal/step5_tco.cljc index 3de1fbd12e..886c4911a0 100644 --- a/clojure/src/step5_tco.clj +++ b/clojure/src/mal/step5_tco.cljc @@ -1,16 +1,15 @@ -(ns step5-tco - (:require [clojure.repl] - [readline] - [reader] - [printer] - [env] - [core]) - (:gen-class)) +(ns mal.step5-tco + (:require [mal.readline :as readline] + #?(:clj [clojure.repl]) + [mal.reader :as reader] + [mal.printer :as printer] + [mal.env :as env] + [mal.core :as core]) + #?(:clj (:gen-class))) ;; read (defn READ [& [strng]] - (let [line (if strng strng (read-line))] - (reader/read-string strng))) + (reader/read-string strng)) ;; eval (declare EVAL) @@ -101,8 +100,8 @@ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment (try (println (rep line)) - (catch Throwable e - (clojure.repl/pst e)))) + #?(:clj (catch Throwable e (clojure.repl/pst e)) + :cljs (catch js/Error e (println (.-stack e)))))) (recur)))) (defn -main [& args] diff --git a/clojure/src/step6_file.clj b/clojure/src/mal/step6_file.cljc similarity index 87% rename from clojure/src/step6_file.clj rename to clojure/src/mal/step6_file.cljc index 7add5ce8b3..e7b884dcc0 100644 --- a/clojure/src/step6_file.clj +++ b/clojure/src/mal/step6_file.cljc @@ -1,16 +1,15 @@ -(ns step6-file - (:require [clojure.repl] - [readline] - [reader] - [printer] - [env] - [core]) - (:gen-class)) +(ns mal.step6-file + (:require [mal.readline :as readline] + #?(:clj [clojure.repl]) + [mal.reader :as reader] + [mal.printer :as printer] + [mal.env :as env] + [mal.core :as core]) + #?(:clj (:gen-class))) ;; read (defn READ [& [strng]] - (let [line (if strng strng (read-line))] - (reader/read-string strng))) + (reader/read-string strng)) ;; eval (declare EVAL) @@ -104,8 +103,8 @@ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment (try (println (rep line)) - (catch Throwable e - (clojure.repl/pst e)))) + #?(:clj (catch Throwable e (clojure.repl/pst e)) + :cljs (catch js/Error e (println (.-stack e)))))) (recur)))) (defn -main [& args] diff --git a/clojure/src/step7_quote.clj b/clojure/src/mal/step7_quote.cljc similarity index 88% rename from clojure/src/step7_quote.clj rename to clojure/src/mal/step7_quote.cljc index 79b4588dfe..a22645b019 100644 --- a/clojure/src/step7_quote.clj +++ b/clojure/src/mal/step7_quote.cljc @@ -1,16 +1,15 @@ -(ns step7-quote - (:require [clojure.repl] - [readline] - [reader] - [printer] - [env] - [core]) - (:gen-class)) +(ns mal.step7-quote + (:require [mal.readline :as readline] + #?(:clj [clojure.repl]) + [mal.reader :as reader] + [mal.printer :as printer] + [mal.env :as env] + [mal.core :as core]) + #?(:clj (:gen-class))) ;; read (defn READ [& [strng]] - (let [line (if strng strng (read-line))] - (reader/read-string strng))) + (reader/read-string strng)) ;; eval (declare EVAL) @@ -127,8 +126,8 @@ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment (try (println (rep line)) - (catch Throwable e - (clojure.repl/pst e)))) + #?(:clj (catch Throwable e (clojure.repl/pst e)) + :cljs (catch js/Error e (println (.-stack e)))))) (recur)))) (defn -main [& args] diff --git a/clojure/src/step8_macros.clj b/clojure/src/mal/step8_macros.cljc similarity index 90% rename from clojure/src/step8_macros.clj rename to clojure/src/mal/step8_macros.cljc index 86b6dac43f..f5a552084a 100644 --- a/clojure/src/step8_macros.clj +++ b/clojure/src/mal/step8_macros.cljc @@ -1,17 +1,16 @@ -(ns step8-macros - (:refer-clojure :exclude [macroexpand]) - (:require [clojure.repl] - [readline] - [reader] - [printer] - [env] - [core]) - (:gen-class)) +(ns mal.step8-macros + (:refer-clojure :exclude [macroexpand]) + (:require [mal.readline :as readline] + #?(:clj [clojure.repl]) + [mal.reader :as reader] + [mal.printer :as printer] + [mal.env :as env] + [mal.core :as core]) + #?(:clj (:gen-class))) ;; read (defn READ [& [strng]] - (let [line (if strng strng (read-line))] - (reader/read-string strng))) + (reader/read-string strng)) ;; eval (declare EVAL) @@ -154,8 +153,8 @@ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment (try (println (rep line)) - (catch Throwable e - (clojure.repl/pst e)))) + #?(:clj (catch Throwable e (clojure.repl/pst e)) + :cljs (catch js/Error e (println (.-stack e)))))) (recur)))) (defn -main [& args] diff --git a/clojure/src/step9_try.clj b/clojure/src/mal/step9_try.cljc similarity index 85% rename from clojure/src/step9_try.clj rename to clojure/src/mal/step9_try.cljc index cf3c227ac5..529c41c2ab 100644 --- a/clojure/src/step9_try.clj +++ b/clojure/src/mal/step9_try.cljc @@ -1,17 +1,16 @@ -(ns step9-try - (:refer-clojure :exclude [macroexpand]) - (:require [clojure.repl] - [readline] - [reader] - [printer] - [env] - [core]) - (:gen-class)) +(ns mal.step9-try + (:refer-clojure :exclude [macroexpand]) + (:require [mal.readline :as readline] + #?(:clj [clojure.repl]) + [mal.reader :as reader] + [mal.printer :as printer] + [mal.env :as env] + [mal.core :as core]) + #?(:clj (:gen-class))) ;; read (defn READ [& [strng]] - (let [line (if strng strng (read-line))] - (reader/read-string strng))) + (reader/read-string strng)) ;; eval (declare EVAL) @@ -102,14 +101,16 @@ (if (= 'catch* (nth a2 0)) (try (EVAL a1 env) - (catch clojure.lang.ExceptionInfo ei + (catch #?(:clj clojure.lang.ExceptionInfo + :cljs ExceptionInfo) ei (EVAL (nth a2 2) (env/env env [(nth a2 1)] [(:data (ex-data ei))]))) - (catch Throwable t + (catch #?(:clj Throwable :cljs :default) t (EVAL (nth a2 2) (env/env env [(nth a2 1)] - [(.getMessage t)])))) + [#?(:clj (.getMessage t) + :cljs (.-message t))])))) (EVAL a1 env)) 'do @@ -168,8 +169,8 @@ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment (try (println (rep line)) - (catch Throwable e - (clojure.repl/pst e)))) + #?(:clj (catch Throwable e (clojure.repl/pst e)) + :cljs (catch js/Error e (println (.-stack e)))))) (recur)))) (defn -main [& args] diff --git a/clojure/src/stepA_mal.clj b/clojure/src/mal/stepA_mal.cljc similarity index 81% rename from clojure/src/stepA_mal.clj rename to clojure/src/mal/stepA_mal.cljc index 8212ee8401..5dd7508fcc 100644 --- a/clojure/src/stepA_mal.clj +++ b/clojure/src/mal/stepA_mal.cljc @@ -1,17 +1,16 @@ -(ns stepA-mal - (:refer-clojure :exclude [macroexpand]) - (:require [clojure.repl] - [readline] - [reader] - [printer] - [env] - [core]) - (:gen-class)) +(ns mal.stepA-mal + (:refer-clojure :exclude [macroexpand]) + (:require [mal.readline :as readline] + #?(:clj [clojure.repl]) + [mal.reader :as reader] + [mal.printer :as printer] + [mal.env :as env] + [mal.core :as core]) + #?(:clj (:gen-class))) ;; read (defn READ [& [strng]] - (let [line (if strng strng (read-line))] - (reader/read-string strng))) + (reader/read-string strng)) ;; eval (declare EVAL) @@ -99,20 +98,27 @@ (macroexpand a1 env) 'clj* - (eval (reader/read-string a1)) + #?(:clj (eval (reader/read-string a1)) + :cljs (throw (ex-info "clj* unsupported in ClojureScript mode" {}))) + + 'js* + #?(:clj (throw (ex-info "js* unsupported in Clojure mode" {})) + :cljs (js->clj (js/eval a1))) 'try* (if (= 'catch* (nth a2 0)) (try (EVAL a1 env) - (catch clojure.lang.ExceptionInfo ei + (catch #?(:clj clojure.lang.ExceptionInfo + :cljs ExceptionInfo) ei (EVAL (nth a2 2) (env/env env [(nth a2 1)] [(:data (ex-data ei))]))) - (catch Throwable t + (catch #?(:clj Throwable :cljs :default) t (EVAL (nth a2 2) (env/env env [(nth a2 1)] - [(.getMessage t)])))) + [#?(:clj (.getMessage t) + :cljs (.-message t))])))) (EVAL a1 env)) 'do @@ -159,7 +165,8 @@ (env/env-set repl-env '*ARGV* ()) ;; core.mal: defined using the language itself -(rep "(def! *host-language* \"clojure\")") +#?(:clj (rep "(def! *host-language* \"clojure\")") + :cljs (rep "(def! *host-language* \"clojurescript\")")) (rep "(def! not (fn* [a] (if a false true)))") (rep "(def! load-file (fn* [f] (eval (read-string (str \"(do \" (slurp f) \")\")))))") (rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") @@ -174,8 +181,8 @@ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment (try (println (rep line)) - (catch Throwable e - (clojure.repl/pst e)))) + #?(:clj (catch Throwable e (clojure.repl/pst e)) + :cljs (catch js/Error e (println (.-stack e)))))) (recur)))) (defn -main [& args] diff --git a/clojure/src/printer.clj b/clojure/src/printer.clj deleted file mode 100644 index d030164408..0000000000 --- a/clojure/src/printer.clj +++ /dev/null @@ -1,36 +0,0 @@ -(ns printer) - -(import '(java.io Writer)) - -;; Override atom printer -(defmethod clojure.core/print-method clojure.lang.Atom [a writer] - (.write writer "(atom ") - (.write writer (pr-str @a)) - (.write writer ")")) - - -;; Override hash-map printer to remove comma separators -(defmethod print-method clojure.lang.IPersistentMap [hm, ^Writer w] - (.write w "{") - (when-let [xs (seq hm)] - (loop [[[k v] & xs] xs] - (print-method k w) (.append w \space) (print-method v w) - (when xs (.write w " ") (recur xs)))) - (.write w "}")) - - -;; Add a version of str that is the same all the way down (no -;; print-readably and nil printing all the way down) -(defn- pr- - ([] nil) - ([x] - (print-method x *out*)) - ([x & more] - (pr- x) - (if-let [nmore (next more)] - (recur (first more) nmore) - (apply pr- more)))) - -(defn _str [& xs] - (binding [*print-readably* nil] - (with-out-str (apply pr- xs)))) diff --git a/clojure/tests/stepA_mal.mal b/clojure/tests/stepA_mal.mal index b3232224fd..4c7ec359f8 100644 --- a/clojure/tests/stepA_mal.mal +++ b/clojure/tests/stepA_mal.mal @@ -1,17 +1,22 @@ -;; Testing basic clojure interop +;; Testing basic clojure/clojurescript interop -(clj* "7") +(def! clj (= *host-language* "clojure")) +(def! cljs (= *host-language* "clojurescript")) + +(if clj (clj* "7") (js* "7")) ;=>7 -(clj* "\"abc\"") +(if clj (clj* "\"abc\"") (js* "\"abc\"")) ;=>"abc" -(clj* "{\"abc\" 123}") +(if clj (clj* "{\"abc\" 123}") {"abc" 123}) ;=>{"abc" 123} -(clj* "(prn \"foo\")") +(if clj (clj* "(prn \"foo\")") (js* "console.log('\"foo\"')")) ; "foo" ;=>nil -(clj* "(for [x [1 2 3]] (+ 1 x))") +(if clj (clj* "(for [x [1 2 3]] (+ 1 x))") '(2 3 4)) ;=>(2 3 4) +(if cljs (js* "[1,2,3].map(function(x) {return 1+x})") [2 3 4]) +;=>[2 3 4] From 2c0c033bfc39b66cc19700e6960aeac3217da795 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 11 Feb 2017 00:12:37 -0600 Subject: [PATCH 0280/2308] Go, Kotlin, Nim, ObjC, RPython: update Dockerfiles. - Fix Nim IOError handling issue introduced by update. --- go/Dockerfile | 2 +- kotlin/Dockerfile | 4 ++-- nim/Dockerfile | 6 +++--- nim/stepA_mal.nim | 1 + objc/Makefile | 4 ++-- rpython/Dockerfile | 4 ++-- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/go/Dockerfile b/go/Dockerfile index 608574edf0..2be2be3f8a 100644 --- a/go/Dockerfile +++ b/go/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:xenial MAINTAINER Joel Martin ########################################################## diff --git a/kotlin/Dockerfile b/kotlin/Dockerfile index 2d3ae15aa7..dfb83bb887 100644 --- a/kotlin/Dockerfile +++ b/kotlin/Dockerfile @@ -25,10 +25,10 @@ WORKDIR /mal RUN apt-get -y install openjdk-7-jdk RUN apt-get -y install unzip -RUN curl -O -J -L https://github.com/JetBrains/kotlin/releases/download/build-1.0.0/kotlin-compiler-1.0.0.zip +RUN curl -O -J -L https://github.com/JetBrains/kotlin/releases/download/v1.0.6/kotlin-compiler-1.0.6.zip RUN mkdir -p /kotlin-compiler -RUN unzip kotlin-compiler-1.0.0.zip -d /kotlin-compiler +RUN unzip kotlin-compiler-1.0.6.zip -d /kotlin-compiler ENV KOTLIN_HOME /kotlin-compiler/kotlinc ENV PATH $KOTLIN_HOME/bin:$PATH diff --git a/nim/Dockerfile b/nim/Dockerfile index 261776d1d5..20c3d79439 100644 --- a/nim/Dockerfile +++ b/nim/Dockerfile @@ -26,10 +26,10 @@ RUN apt-get -y install g++ # Nim RUN apt-get -y install xz-utils -RUN cd /tmp && curl -O http://nim-lang.org/download/nim-0.15.2.tar.xz \ - && tar xvJf /tmp/nim-0.15.2.tar.xz && cd nim-0.15.2 \ +RUN cd /tmp && curl -O http://nim-lang.org/download/nim-0.16.0.tar.xz \ + && tar xvJf /tmp/nim-0.16.0.tar.xz && cd nim-0.16.0 \ && make && sh install.sh /usr/local/bin \ && cp bin/nim /usr/local/bin/ \ - && rm -r /tmp/nim-0.15.2 + && rm -r /tmp/nim-0.16.0 ENV HOME /mal diff --git a/nim/stepA_mal.nim b/nim/stepA_mal.nim index f3295b1474..7f5f9b6eed 100644 --- a/nim/stepA_mal.nim +++ b/nim/stepA_mal.nim @@ -188,6 +188,7 @@ while true: let line = readLineFromStdin("user> ") echo line.rep except Blank: discard + except IOError: quit() except: echo getCurrentExceptionMsg() echo getCurrentException().getStackTrace() diff --git a/objc/Makefile b/objc/Makefile index e069db7830..4afffc62ae 100644 --- a/objc/Makefile +++ b/objc/Makefile @@ -24,8 +24,8 @@ OBJC_LIBS := -lobjc -lreadline else #CC = clang -fblocks -fobjc-nonfragile-abi -fobjc-arc CC = clang -fblocks -fobjc-nonfragile-abi -OBJC_FLAGS := $(shell gnustep-config --objc-flags | egrep -v "Entering|Leaving") -OBJC_LIBS := $(filter-out -shared-libgcc,$(shell gnustep-config --base-libs | egrep -v "Entering|Leaving")) -ldispatch -lreadline +OBJC_FLAGS := $(shell gnustep-config --objc-flags 2>/dev/null | egrep -v "Entering|Leaving") +OBJC_LIBS := $(filter-out -shared-libgcc,$(shell gnustep-config --base-libs 2>/dev/null | egrep -v "Entering|Leaving")) -ldispatch -lreadline endif all: $(STEPS) diff --git a/rpython/Dockerfile b/rpython/Dockerfile index f7b2015577..29f97d58e0 100644 --- a/rpython/Dockerfile +++ b/rpython/Dockerfile @@ -35,9 +35,9 @@ RUN apt-get -y install mercurial libffi-dev pkg-config libz-dev libbz2-dev \ libsqlite3-dev libncurses-dev libexpat1-dev libssl-dev libgdbm-dev tcl-dev RUN mkdir -p /opt/pypy && \ - curl -L https://bitbucket.org/pypy/pypy/downloads/pypy-4.0.1-src.tar.bz2 | tar -xjf - -C /opt/pypy/ --strip-components=1 + curl -L https://bitbucket.org/pypy/pypy/downloads/pypy2-v5.6.0-src.tar.bz2 | tar -xjf - -C /opt/pypy/ --strip-components=1 #curl https://bitbucket.org/pypy/pypy/get/tip.tar.gz | tar -xzf - -C /opt/pypy/ --strip-components=1 -RUN cd /opt/pypy && make +RUN cd /opt/pypy && make && rm -rf /tmp/usession* RUN ln -sf /opt/pypy/rpython/bin/rpython /usr/local/bin/rpython RUN ln -sf /opt/pypy/pypy-c /usr/local/bin/pypy From a0e89ae42a5b139e703f2e229a7bfa5e90264dc5 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 11 Feb 2017 00:15:34 -0600 Subject: [PATCH 0281/2308] Rust, miniMAL, VHDL: misc fixes. - Fix rust Dockerfile working dir. - Make top-level Makefile more generic. This makes it easier to use the Makefile with fewer changes in miniMAL. - Simplify vhdl build when case is fixed in stepA_mal - Remove BUILD_IMPL=js from miniMAL travis test. Just use a docker image specifically made for miniMAL. - Update TODO --- .travis.yml | 2 +- Makefile | 9 +++++++-- docs/TODO | 5 +++++ rust/Dockerfile | 6 ++---- vhdl/Makefile | 7 ++----- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 11e28a8212..73acf2543c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,7 +41,7 @@ matrix: - {env: IMPL=make, services: [docker]} - {env: IMPL=mal BUILD_IMPL=js NO_PERF=1, services: [docker]} - {env: IMPL=matlab, services: [docker]} # Uses Octave - - {env: IMPL=miniMAL BUILD_IMPL=js, services: [docker]} + - {env: IMPL=miniMAL, services: [docker]} - {env: IMPL=nim, services: [docker]} - {env: IMPL=objpascal, services: [docker]} - {env: IMPL=objc NO_DOCKER=1, os: osx, osx_image: xcode7} diff --git a/Makefile b/Makefile index 9648c66988..1dbff81425 100644 --- a/Makefile +++ b/Makefile @@ -84,6 +84,8 @@ IMPLS = ada awk bash basic c d chuck clojure coffee common-lisp cpp crystal cs d python r racket rpython ruby rust scala skew swift swift3 tcl vb vhdl \ vimscript +EXTENSION = .mal + step0 = step0_repl step1 = step1_read_print step2 = step2_eval @@ -96,6 +98,9 @@ step8 = step8_macros step9 = step9_try stepA = stepA_mal +argv_STEP = step6_file + + regress_step0 = step0 regress_step1 = step1 regress_step2 = step2 @@ -144,7 +149,7 @@ opt_OPTIONAL = $(if $(strip $(OPTIONAL)),$(if $(filter t true T True TRUE # being tested. STEP_TEST_FILES = $(strip $(wildcard \ $(foreach s,$(if $(strip $(REGRESS)),$(regress_$(2)),$(2)),\ - $(1)/tests/$($(s)).mal tests/$($(s)).mal))) + $(1)/tests/$($(s))$(EXTENSION) tests/$($(s))$(EXTENSION)))) # Map of step (e.g. "step8") to executable file for that step ada_STEP_TO_PROG = ada/$($(1)) @@ -319,7 +324,7 @@ $(ALL_TESTS): $$(call $$(word 2,$$(subst ^, ,$$(@)))_STEP_TO_PROG,$$(word 3,$$(s echo 'Testing $@; step file: $+, test file: $(test)' && \ echo 'Running: $(call get_runtest_cmd,$(impl),$(step)) ../$(test) -- ../$(impl)/run' && \ $(call get_runtest_cmd,$(impl),$(step)) ../$(test) -- ../$(impl)/run && \ - $(if $(filter tests/step6_file.mal,$(test)),\ + $(if $(filter tests/$(argv_STEP)$(EXTENSION),$(test)),\ echo '----------------------------------------------' && \ echo 'Testing ARGV of $@; step file: $+' && \ echo 'Running: $(call get_argvtest_cmd,$(impl),$(step)) ../$(impl)/run ' && \ diff --git a/docs/TODO b/docs/TODO index 63df3d1fa5..ef3b531da1 100644 --- a/docs/TODO +++ b/docs/TODO @@ -7,6 +7,11 @@ General: ./core.mal to ./lib directory - Finish guide.md + - Refactor ./run: + - java: build step, don't use mvn to run + - plpgsql: maybe combine wrap.sh and run + - vhdl: combine run_vhdl.sh and run + - vimscript: combine run_vimscript.sh and run All Implementations: - regular expression matching in runtest diff --git a/rust/Dockerfile b/rust/Dockerfile index dfd365d877..ba373a258f 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -29,10 +29,8 @@ RUN apt-get -y install g++ ENV RUST_ARCHIVE=rust-1.14.0-x86_64-unknown-linux-gnu.tar.gz ENV RUST_DOWNLOAD_URL=https://static.rust-lang.org/dist/$RUST_ARCHIVE -RUN mkdir /rust -WORKDIR /rust - -RUN curl -fsOSL $RUST_DOWNLOAD_URL \ +RUN mkdir -p /rust && cd /rust \ + && curl -fsOSL $RUST_DOWNLOAD_URL \ && curl -s $RUST_DOWNLOAD_URL.sha256 | sha256sum -c - \ && tar -C /rust -xzf $RUST_ARCHIVE --strip-components=1 \ && rm $RUST_ARCHIVE \ diff --git a/vhdl/Makefile b/vhdl/Makefile index 0649e7863c..dcca4a11f8 100644 --- a/vhdl/Makefile +++ b/vhdl/Makefile @@ -36,11 +36,8 @@ $(OBJS): %.o: %.vhdl $(OTHER_OBJS) $(patsubst %.o,%,$(filter step%,$(OBJS))): $(OTHER_OBJS) $(BINS): %: %.o ghdl -e -g $@ - -# The ghdl linker creates a lowercase executable file; rename it to stepA_mal -stepA_mal: stepA_mal.o - ghdl -e -g $@ - mv stepa_mal stepA_mal + # ghdl linker creates a lowercase executable; rename it to stepA_mal + if [ "$@" = "stepA_mal" ]; then mv stepa_mal $@; fi clean: rm -f $(OBJS) $(BINS) $(OTHER_OBJS) work-obj93.cf mal From 576d01e1b27ed78e06324c18b01088028e9eba5c Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 11 Feb 2017 12:41:06 -0600 Subject: [PATCH 0282/2308] Clojure: node_modules or lein deps but not both. --- clojure/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clojure/Makefile b/clojure/Makefile index 5fda6dbb82..5931cf94de 100644 --- a/clojure/Makefile +++ b/clojure/Makefile @@ -5,7 +5,7 @@ SOURCES_LISP = src/mal/env.cljc src/mal/core.cljc src/mal/stepA_mal.cljc SRCS = $(SOURCES_BASE) src/mal/env.cljc src/mal/core.cljc SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -all: deps node_modules +all: $(if $(filter cljs,$(CLJ_MODE)),node_modules,deps) dist: mal.jar mal From 16d5b0c3059a2e18758b3fbeed17e5fe0d84b5a0 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 11 Feb 2017 12:54:34 -0600 Subject: [PATCH 0283/2308] runtest: print errors before first prompt. - Also, remove extraneous ffi module dep in miniMAL. --- miniMAL/package.json | 3 +-- runtest.py | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/miniMAL/package.json b/miniMAL/package.json index 0c87452b7e..0e14e11f3d 100644 --- a/miniMAL/package.json +++ b/miniMAL/package.json @@ -3,7 +3,6 @@ "version": "0.0.1", "description": "Make a Lisp (mal) language implemented in miniMAL", "dependencies": { - "minimal-lisp": "1.0.2", - "ffi": "2.0.x" + "minimal-lisp": "1.0.2" } } diff --git a/runtest.py b/runtest.py index eee1fcc728..c3735adee4 100755 --- a/runtest.py +++ b/runtest.py @@ -29,8 +29,12 @@ def log(data, end='\n'): sys.stdout.flush() # TODO: do we need to support '\n' too -sep = "\r\n" -#sep = "\n" +import platform +if platform.system().find("CYGWIN_NT") >= 0: + # TODO: this is weird, is this really right on Cygwin? + sep = "\n\r\n" +else: + sep = "\r\n" rundir = None parser = argparse.ArgumentParser( @@ -241,7 +245,13 @@ def assert_prompt(runner, prompts, timeout): # Wait for the initial prompt -assert_prompt(r, ['user> ', 'mal-user> '], args.start_timeout) +try: + assert_prompt(r, ['user> ', 'mal-user> '], args.start_timeout) +except: + _, exc, _ = sys.exc_info() + log("\nException: %s" % repr(exc)) + log("Output before exception:\n%s" % r.buf) + sys.exit(1) # Send the pre-eval code if any if args.pre_eval: From e5faf623bdc1b7a0aed84aa17c358b099eed9a47 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 11 Feb 2017 13:07:58 -0600 Subject: [PATCH 0284/2308] miniMAL: restore ffi dep and build instructions. --- miniMAL/Makefile | 4 ++++ miniMAL/package.json | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/miniMAL/Makefile b/miniMAL/Makefile index 82e7f6e32f..71c717e956 100644 --- a/miniMAL/Makefile +++ b/miniMAL/Makefile @@ -3,12 +3,16 @@ SOURCES_BASE = node_readline.js miniMAL-core.json \ types.json reader.json printer.json SOURCES_LISP = env.json core.json stepA_mal.json SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) +FFI_STEPS = step4_if_fn_do step5_tco step6_file \ + step7_quote step8_macros step9_try stepA_mal all: node_modules node_modules: npm install +$(foreach S,$(FFI_STEPS),$(S).json): node_modules + dist: mal.json mal mal.json: $(filter-out %.js,$(SOURCES)) diff --git a/miniMAL/package.json b/miniMAL/package.json index 0e14e11f3d..0c87452b7e 100644 --- a/miniMAL/package.json +++ b/miniMAL/package.json @@ -3,6 +3,7 @@ "version": "0.0.1", "description": "Make a Lisp (mal) language implemented in miniMAL", "dependencies": { - "minimal-lisp": "1.0.2" + "minimal-lisp": "1.0.2", + "ffi": "2.0.x" } } From 186471a32c4bb4bc6300cff4d7fb2e9998cbbe2f Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 11 Feb 2017 13:38:38 -0600 Subject: [PATCH 0285/2308] miniMAL: bring over node_readline.js to fix build - Node tries to find node_modules subdirectory (to load ffi from) in the target of the symlink. I.e. ../js/node_modules --- miniMAL/node_readline.js | 47 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) mode change 120000 => 100644 miniMAL/node_readline.js diff --git a/miniMAL/node_readline.js b/miniMAL/node_readline.js deleted file mode 120000 index 7771772e4c..0000000000 --- a/miniMAL/node_readline.js +++ /dev/null @@ -1 +0,0 @@ -../js/node_readline.js \ No newline at end of file diff --git a/miniMAL/node_readline.js b/miniMAL/node_readline.js new file mode 100644 index 0000000000..dc64e3f642 --- /dev/null +++ b/miniMAL/node_readline.js @@ -0,0 +1,46 @@ +// IMPORTANT: choose one +var RL_LIB = "libreadline"; // NOTE: libreadline is GPL +//var RL_LIB = "libedit"; + +var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history'); + +var rlwrap = {}; // namespace for this module in web context + +var ffi = require('ffi'), + fs = require('fs'); + +var rllib = ffi.Library(RL_LIB, { + 'readline': [ 'string', [ 'string' ] ], + 'add_history': [ 'int', [ 'string' ] ]}); + +var rl_history_loaded = false; + +exports.readline = rlwrap.readline = function(prompt) { + prompt = typeof prompt !== 'undefined' ? prompt : "user> "; + + if (!rl_history_loaded) { + rl_history_loaded = true; + var lines = []; + if (fs.existsSync(HISTORY_FILE)) { + lines = fs.readFileSync(HISTORY_FILE).toString().split("\n"); + } + // Max of 2000 lines + lines = lines.slice(Math.max(lines.length - 2000, 0)); + for (var i=0; i Date: Thu, 23 Feb 2017 00:03:21 +0900 Subject: [PATCH 0286/2308] TypeScript: setup initial environment --- Makefile | 3 ++- ts/.gitignore | 5 +++++ ts/Makefile | 34 ++++++++++++++++++++++++++++++++++ ts/core.ts | 0 ts/env.ts | 0 ts/mal.ts | 0 ts/node_readline.ts | 45 +++++++++++++++++++++++++++++++++++++++++++++ ts/package.json | 19 +++++++++++++++++++ ts/printer.ts | 0 ts/reader.ts | 0 ts/run | 2 ++ ts/step0_repl.ts | 0 ts/tsconfig.json | 15 +++++++++++++++ ts/types.ts | 0 14 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 ts/.gitignore create mode 100644 ts/Makefile create mode 100644 ts/core.ts create mode 100644 ts/env.ts create mode 100644 ts/mal.ts create mode 100644 ts/node_readline.ts create mode 100644 ts/package.json create mode 100644 ts/printer.ts create mode 100644 ts/reader.ts create mode 100755 ts/run create mode 100644 ts/step0_repl.ts create mode 100644 ts/tsconfig.json create mode 100644 ts/types.ts diff --git a/Makefile b/Makefile index 1dbff81425..ab2b4fd058 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ IMPLS = ada awk bash basic c d chuck clojure coffee common-lisp cpp crystal cs d erlang elisp elixir es6 factor forth fsharp go groovy guile haskell \ haxe io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ - python r racket rpython ruby rust scala skew swift swift3 tcl vb vhdl \ + python r racket rpython ruby rust scala skew swift swift3 tcl ts vb vhdl \ vimscript EXTENSION = .mal @@ -210,6 +210,7 @@ skew_STEP_TO_PROG = skew/$($(1)).js swift_STEP_TO_PROG = swift/$($(1)) swift3_STEP_TO_PROG = swift3/$($(1)) tcl_STEP_TO_PROG = tcl/$($(1)).tcl +ts_STEP_TO_PROG = ts/$($(1)).js vb_STEP_TO_PROG = vb/$($(1)).exe vhdl_STEP_TO_PROG = vhdl/$($(1)) vimscript_STEP_TO_PROG = vimscript/$($(1)).vim diff --git a/ts/.gitignore b/ts/.gitignore new file mode 100644 index 0000000000..0aa7778c55 --- /dev/null +++ b/ts/.gitignore @@ -0,0 +1,5 @@ +node_modules/ + +npm-debug.log + +*.js diff --git a/ts/Makefile b/ts/Makefile new file mode 100644 index 0000000000..3af357f01d --- /dev/null +++ b/ts/Makefile @@ -0,0 +1,34 @@ +SOURCES_BASE = types.ts reader.ts printer.ts +SOURCES_LISP = env.ts core.ts stepA_mal.ts +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +all: node_modules dist + +node_modules: + npm install + +dist: mal.js mal + +%.js: %.js + ./node_modules/.bin/tsc -p ./ + +mal.js: $(SOURCES) + ./node_modules/.bin/tsc -p ./ + +mal: mal.js + echo "#!/usr/bin/env node" > $@ + cat $< >> $@ + chmod +x $@ + +clean: + rm -f *.js mal + + +.PHONY: stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `egrep "^\w*#|^\w*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `egrep "^\w*#|^\w*$$" $^ | wc` "[comments/blanks]" diff --git a/ts/core.ts b/ts/core.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ts/env.ts b/ts/env.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ts/mal.ts b/ts/mal.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ts/node_readline.ts b/ts/node_readline.ts new file mode 100644 index 0000000000..2b90aa6235 --- /dev/null +++ b/ts/node_readline.ts @@ -0,0 +1,45 @@ +import * as path from "path"; +import * as ffi from "ffi"; +import * as fs from "fs"; + +// IMPORTANT: choose one +const RL_LIB = "libreadline"; // NOTE: libreadline is GPL +//var RL_LIB = "libedit"; + +const HISTORY_FILE = path.join(process.env.HOME, ".mal-history"); + +const rllib = ffi.Library(RL_LIB, { + "readline": ["string", ["string"]], + "add_history": ["int", ["string"]], +}); + +let rlHistoryLoaded = false; + +export function readline(prompt?: string): string | null { + prompt = prompt || "user> "; + + if (!rlHistoryLoaded) { + rlHistoryLoaded = true; + let lines: string[] = []; + if (fs.existsSync(HISTORY_FILE)) { + lines = fs.readFileSync(HISTORY_FILE).toString().split("\n"); + } + // Max of 2000 lines + lines = lines.slice(Math.max(lines.length - 2000, 0)); + for (let i = 0; i < lines.length; i++) { + if (lines[i]) { rllib.add_history(lines[i]); } + } + } + + const line = rllib.readline(prompt); + if (line) { + rllib.add_history(line); + try { + fs.appendFileSync(HISTORY_FILE, line + "\n"); + } catch (exc) { + // ignored + } + } + + return line; +}; diff --git a/ts/package.json b/ts/package.json new file mode 100644 index 0000000000..c34f9f63c3 --- /dev/null +++ b/ts/package.json @@ -0,0 +1,19 @@ +{ + "name": "mal", + "private": true, + "version": "1.0.0", + "description": "Make a Lisp (mal) language implemented in TypeScript", + "scripts": { + "test": "npm run test:step0", + "test:step0": "cd .. && make 'test^ts^step0'" + }, + "dependencies": { + "ffi": "^2.2.0" + }, + "devDependencies": { + "@types/ffi": "0.0.19", + "@types/node": "^7.0.5", + "typescript": "^2.1.6", + "typescript-formatter": "^4.1.1" + } +} diff --git a/ts/printer.ts b/ts/printer.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ts/reader.ts b/ts/reader.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ts/run b/ts/run new file mode 100755 index 0000000000..6605303a29 --- /dev/null +++ b/ts/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec node $(dirname $0)/${STEP:-stepA_mal}.js "${@}" diff --git a/ts/step0_repl.ts b/ts/step0_repl.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ts/tsconfig.json b/ts/tsconfig.json new file mode 100644 index 0000000000..47439fb626 --- /dev/null +++ b/ts/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2015", + "noImplicitAny": true, + "noEmitOnError": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "newLine": "LF", + "strictNullChecks": true, + "sourceMap": false + } +} diff --git a/ts/types.ts b/ts/types.ts new file mode 100644 index 0000000000..e69de29bb2 From 9abceb8681273e626050f2a955671424052b41f3 Mon Sep 17 00:00:00 2001 From: vvakame Date: Thu, 23 Feb 2017 00:34:44 +0900 Subject: [PATCH 0287/2308] TypeScript: step 0 --- ts/step0_repl.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ts/step0_repl.ts b/ts/step0_repl.ts index e69de29bb2..03c0814120 100644 --- a/ts/step0_repl.ts +++ b/ts/step0_repl.ts @@ -0,0 +1,32 @@ +import { readline } from "./node_readline"; + +function read(v: string): any { + // TODO + return v; +} + +function evalAST(v: any): any { + // TODO + return v; +} + +function print(v: any): string { + // TODO + return v; +} + +function rep(v: string): string { + // TODO + return print(evalAST(read(v))); +} + +while (true) { + const line = readline("user> "); + if (line == null) { + break; + } + if (line === "") { + continue; + } + console.log(rep(line)); +} From ef918f11bed000460e06b943cac06533a744f207 Mon Sep 17 00:00:00 2001 From: vvakame Date: Thu, 23 Feb 2017 03:04:46 +0900 Subject: [PATCH 0288/2308] update misc --- ts/package.json | 1 + ts/tsconfig.json | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ts/package.json b/ts/package.json index c34f9f63c3..52d091e271 100644 --- a/ts/package.json +++ b/ts/package.json @@ -6,6 +6,7 @@ "scripts": { "test": "npm run test:step0", "test:step0": "cd .. && make 'test^ts^step0'" + "build": "tsfmt -r && tsc -p ./", }, "dependencies": { "ffi": "^2.2.0" diff --git a/ts/tsconfig.json b/ts/tsconfig.json index 47439fb626..5fd9128919 100644 --- a/ts/tsconfig.json +++ b/ts/tsconfig.json @@ -11,5 +11,8 @@ "newLine": "LF", "strictNullChecks": true, "sourceMap": false - } + }, + "exclude": [ + "node_modules" + ] } From f406f88b1e93f6e6427eae87395c227445a37239 Mon Sep 17 00:00:00 2001 From: vvakame Date: Thu, 23 Feb 2017 03:05:01 +0900 Subject: [PATCH 0289/2308] TypeScript: step 1 --- ts/package.json | 5 +- ts/printer.ts | 38 +++++++++++ ts/reader.ts | 145 +++++++++++++++++++++++++++++++++++++++++ ts/step1_read_print.ts | 38 +++++++++++ ts/types.ts | 78 ++++++++++++++++++++++ 5 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 ts/step1_read_print.ts diff --git a/ts/package.json b/ts/package.json index 52d091e271..89d56fa7ec 100644 --- a/ts/package.json +++ b/ts/package.json @@ -4,9 +4,10 @@ "version": "1.0.0", "description": "Make a Lisp (mal) language implemented in TypeScript", "scripts": { - "test": "npm run test:step0", - "test:step0": "cd .. && make 'test^ts^step0'" "build": "tsfmt -r && tsc -p ./", + "test": "npm run build && npm run test:step0 && npm run test:step1", + "test:step0": "cd .. && make 'test^ts^step0'", + "test:step1": "cd .. && make 'test^ts^step1'" }, "dependencies": { "ffi": "^2.2.0" diff --git a/ts/printer.ts b/ts/printer.ts index e69de29bb2..e890180228 100644 --- a/ts/printer.ts +++ b/ts/printer.ts @@ -0,0 +1,38 @@ +import { MalType } from "./types"; + +export function prStr(v: MalType, printReadably = true): string { + switch (v.type) { + case "list": + return `(${v.list.map(v => prStr(v)).join(" ")})`; + case "vector": + return `[${v.list.map(v => prStr(v)).join(" ")}]`; + case "hash-map": + let result = "{"; + for (const [key, value] of v.map) { + if (result !== "{") { + result += " "; + } + result += `${prStr(key)} ${prStr(value)}`; + } + result += "}"; + return result; + case "number": + case "symbol": + case "boolean": + return `${v.v}`; + case "string": + if (printReadably) { + const str = v.v + .replace(/\\/g, "\\\\") + .replace(/"/g, '\\"') + .replace(/\n/g, "\\n"); + return `"${str}"`; + } else { + return v.v; + } + case "null": + return "nil"; + case "keyword": + return `:${v.v.substr(1)}`; + } +} diff --git a/ts/reader.ts b/ts/reader.ts index e69de29bb2..89db0f42a7 100644 --- a/ts/reader.ts +++ b/ts/reader.ts @@ -0,0 +1,145 @@ +import { MalType, MalList, MalString, MalNumber, MalBoolean, MalNull, MalKeyword, MalSymbol, MalVector, MalHashMap } from "./types"; + +class Reader { + position = 0; + + constructor(private tokens: string[]) { } + + next(): string { + const ret = this.peek(); + this.position += 1; + return ret; + } + + peek(): string { + return this.tokens[this.position]; + } +} + +export function readStr(input: string): MalType { + const tokens = tokenizer(input); + const reader = new Reader(tokens); + return readFrom(reader); +} + +function tokenizer(input: string): string[] { + const regexp = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g; + const tokens: string[] = []; + while (true) { + const matches = regexp.exec(input); + if (!matches) { + break; + } + const match = matches[1]; + if (match === "") { + break; + } + if (match[0] !== ";") { + tokens.push(match); + } + } + + return tokens; +} + +function readFrom(reader: Reader): MalType { + const token = reader.peek(); + switch (token) { + case "(": + return readList(reader); + case "[": + return readVector(reader); + case "{": + return readHashMap(reader); + case "'": + return readSymbol("quote"); + case "`": + return readSymbol("quasiquote"); + case "~": + return readSymbol("unquote"); + case "~@": + return readSymbol("splice-unquote"); + case "@": + return readSymbol("deref"); + case "^": + { + reader.next(); + const sym = MalSymbol.get("with-meta"); + const target = readFrom(reader); + return new MalList([sym, readFrom(reader), target]); + } + default: + return readAtom(reader); + } + + function readSymbol(name: string) { + reader.next(); + const sym = MalSymbol.get(name); + const target = readFrom(reader); + return new MalList([sym, target]); + } +} + +function readList(reader: Reader): MalType { + return readParen(reader, MalList, "(", ")"); +} + +function readVector(reader: Reader): MalType { + return readParen(reader, MalVector, "[", "]"); +} + +function readHashMap(reader: Reader): MalType { + return readParen(reader, MalHashMap, "{", "}"); +} + +function readParen(reader: Reader, ctor: { new (list: MalType[]): MalType; }, open: string, close: string): MalType { + const token = reader.next(); // drop open paren + if (token !== open) { + throw new Error(`unexpected token ${token}, expected ${open}`); + } + const list: MalType[] = []; + while (true) { + const next = reader.peek(); + if (next === close) { + break; + } else if (!next) { + throw new Error("unexpected EOF"); + } + list.push(readFrom(reader)); + } + reader.next(); // drop close paren + + return new ctor(list); +} + +function readAtom(reader: Reader): MalType { + const token = reader.next(); + if (token.match(/^-?[0-9]+$/)) { + const v = parseInt(token, 10); + return new MalNumber(v); + } + if (token.match(/^-?[0-9]\.[0-9]+$/)) { + const v = parseFloat(token); + return new MalNumber(v); + } + if (token[0] === '"') { + const v = token.slice(1, token.length - 1) + .replace(/\\"/g, '"') + .replace(/\\n/g, "\n") + .replace(/\\\\/g, "\\"); + return new MalString(v); + } + if (token[0] === ":") { + return new MalKeyword(token.substr(1)); + } + switch (token) { + case "nil": + return new MalNull(); + case "true": + return new MalBoolean(true); + case "false": + return new MalBoolean(false); + } + + return MalSymbol.get(token); +} diff --git a/ts/step1_read_print.ts b/ts/step1_read_print.ts new file mode 100644 index 0000000000..05494a1605 --- /dev/null +++ b/ts/step1_read_print.ts @@ -0,0 +1,38 @@ +import { readline } from "./node_readline"; + +import { MalType } from "./types"; +import { readStr } from "./reader"; +import { prStr } from "./printer"; + +function read(v: string): MalType { + return readStr(v); +} + +function evalAST(v: any): any { + // TODO + return v; +} + +function print(v: MalType): string { + return prStr(v); +} + +function rep(v: string): string { + return print(evalAST(read(v))); +} + +while (true) { + const line = readline("user> "); + if (line == null) { + break; + } + if (line === "") { + continue; + } + try { + console.log(rep(line)); + } catch (e) { + const err: Error = e; + console.error(err.message); + } +} diff --git a/ts/types.ts b/ts/types.ts index e69de29bb2..50283d8f11 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -0,0 +1,78 @@ +export type MalType = MalList | MalNumber | MalString | MalNull | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap; + +export class MalList { + type: "list" = "list"; + + constructor(public list: MalType[]) { + } +} + +export class MalNumber { + type: "number" = "number"; + constructor(public v: number) { + } +} + +export class MalString { + type: "string" = "string"; + constructor(public v: string) { + } +} + +export class MalNull { + type: "null" = "null"; +} + +export class MalBoolean { + type: "boolean" = "boolean"; + constructor(public v: boolean) { + } +} + +export class MalSymbol { + static map = new Map(); + + static get(name: string): MalSymbol { + const sym = Symbol.for(name); + let token = this.map.get(sym); + if (token) { + return token; + } + token = new MalSymbol(name); + this.map.set(sym, token); + return token; + } + + type: "symbol" = "symbol"; + + private constructor(public v: string) { + } +} + +export class MalKeyword { + type: "keyword" = "keyword"; + constructor(public v: string) { + this.v = String.fromCodePoint(0x29E) + this.v; + } +} + +export class MalVector { + type: "vector" = "vector"; + constructor(public list: MalType[]) { + } +} + +export class MalHashMap { + type: "hash-map" = "hash-map"; + map = new Map(); + constructor(list: MalType[]) { + while (list.length !== 0) { + const key = list.shift() !; + const value = list.shift(); + if (value == null) { + throw new Error("unexpected hash length"); + } + this.map.set(key, value); + } + } +} From 83aaf848e17ac998d7fd7eb0982f6a8ced512a26 Mon Sep 17 00:00:00 2001 From: vvakame Date: Thu, 23 Feb 2017 05:18:41 +0900 Subject: [PATCH 0290/2308] TypeScript: step 2 --- ts/package.json | 5 +-- ts/printer.ts | 2 ++ ts/step2_eval.ts | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ ts/types.ts | 12 ++++++- 4 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 ts/step2_eval.ts diff --git a/ts/package.json b/ts/package.json index 89d56fa7ec..15bdd9df2d 100644 --- a/ts/package.json +++ b/ts/package.json @@ -5,9 +5,10 @@ "description": "Make a Lisp (mal) language implemented in TypeScript", "scripts": { "build": "tsfmt -r && tsc -p ./", - "test": "npm run build && npm run test:step0 && npm run test:step1", + "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2", "test:step0": "cd .. && make 'test^ts^step0'", - "test:step1": "cd .. && make 'test^ts^step1'" + "test:step1": "cd .. && make 'test^ts^step1'", + "test:step2": "cd .. && make 'test^ts^step2'" }, "dependencies": { "ffi": "^2.2.0" diff --git a/ts/printer.ts b/ts/printer.ts index e890180228..c3bafd9b4a 100644 --- a/ts/printer.ts +++ b/ts/printer.ts @@ -34,5 +34,7 @@ export function prStr(v: MalType, printReadably = true): string { return "nil"; case "keyword": return `:${v.v.substr(1)}`; + case "function": + throw new Error(`invalid state`); } } diff --git a/ts/step2_eval.ts b/ts/step2_eval.ts new file mode 100644 index 0000000000..86cfd89ef5 --- /dev/null +++ b/ts/step2_eval.ts @@ -0,0 +1,82 @@ +import { readline } from "./node_readline"; + +import { MalType, MalNumber, MalList, MalVector, MalHashMap, MalFunction } from "./types"; +import { readStr } from "./reader"; +import { prStr } from "./printer"; + +function read(str: string): MalType { + return readStr(str); +} + +interface MalEnvironment { + [key: string]: MalFunction; +} + +function evalAST(ast: MalType, env: MalEnvironment): MalType { + switch (ast.type) { + case "symbol": + const f = env[ast.v]; + if (!f) { + throw new Error(`unknown symbol: ${ast.v}`); + } + return f; + case "list": + return new MalList(ast.list.map(ast => evalSexp(ast, env))); + case "vector": + return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + case "hash-map": + const list: MalType[] = []; + for (const [key, value] of ast.map) { + list.push(key); + list.push(evalSexp(value, env)); + } + return new MalHashMap(list); + default: + return ast; + } +} + +function evalSexp(ast: MalType, env: MalEnvironment): MalType { + if (ast.type !== "list") { + return evalAST(ast, env); + } + if (ast.list.length === 0) { + return ast; + } + const result = evalAST(ast, env) as MalList; + const [f, ...rest] = result.list; + if (!MalFunction.instanceOf(f)) { + throw new Error(`unexpected token: ${f.type}, expected: function`); + } + return f.func(...rest); +} + +function print(exp: MalType): string { + return prStr(exp); +} + +const replEnv: MalEnvironment = { + "+": new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v + b!.v)), + "-": new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v - b!.v)), + "*": new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v * b!.v)), + "/": new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v / b!.v)), +}; +function rep(str: string): string { + return print(evalSexp(read(str), replEnv)); +} + +while (true) { + const line = readline("user> "); + if (line == null) { + break; + } + if (line === "") { + continue; + } + try { + console.log(rep(line)); + } catch (e) { + const err: Error = e; + console.error(err.message); + } +} diff --git a/ts/types.ts b/ts/types.ts index 50283d8f11..0e07b86bbe 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -1,4 +1,4 @@ -export type MalType = MalList | MalNumber | MalString | MalNull | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap; +export type MalType = MalList | MalNumber | MalString | MalNull | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap | MalFunction; export class MalList { type: "list" = "list"; @@ -76,3 +76,13 @@ export class MalHashMap { } } } + +export class MalFunction { + static instanceOf(f: MalType): f is MalFunction { + return f instanceof MalFunction; + } + + type: "function" = "function"; + constructor(public func: (...args: (MalType | undefined)[]) => MalType) { + } +} From 76e06b96f4f44ab3b51c064c2b4ed33c7d8f5a19 Mon Sep 17 00:00:00 2001 From: vvakame Date: Fri, 24 Feb 2017 07:41:49 +0900 Subject: [PATCH 0291/2308] update dependencies --- ts/package.json | 4 +- ts/types.ts | 2 +- ts/yarn.lock | 185 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 ts/yarn.lock diff --git a/ts/package.json b/ts/package.json index 15bdd9df2d..866626a590 100644 --- a/ts/package.json +++ b/ts/package.json @@ -16,7 +16,7 @@ "devDependencies": { "@types/ffi": "0.0.19", "@types/node": "^7.0.5", - "typescript": "^2.1.6", - "typescript-formatter": "^4.1.1" + "typescript": "^2.2.1", + "typescript-formatter": "^4.1.2" } } diff --git a/ts/types.ts b/ts/types.ts index 0e07b86bbe..aad9e7772b 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -67,7 +67,7 @@ export class MalHashMap { map = new Map(); constructor(list: MalType[]) { while (list.length !== 0) { - const key = list.shift() !; + const key = list.shift()!; const value = list.shift(); if (value == null) { throw new Error("unexpected hash length"); diff --git a/ts/yarn.lock b/ts/yarn.lock new file mode 100644 index 0000000000..043c10500b --- /dev/null +++ b/ts/yarn.lock @@ -0,0 +1,185 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/ffi@0.0.19": + version "0.0.19" + resolved "https://registry.yarnpkg.com/@types/ffi/-/ffi-0.0.19.tgz#bda12de999e1a6e4532b844497318dbaeb984f82" + dependencies: + "@types/node" "*" + "@types/ref" "*" + "@types/ref-struct" "*" + +"@types/node@*", "@types/node@^7.0.5": + version "7.0.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.5.tgz#96a0f0a618b7b606f1ec547403c00650210bfbb7" + +"@types/ref-struct@*": + version "0.0.28" + resolved "https://registry.yarnpkg.com/@types/ref-struct/-/ref-struct-0.0.28.tgz#b840a8ac495411515dcae209010d5ac661550e84" + dependencies: + "@types/ref" "*" + +"@types/ref@*": + version "0.0.28" + resolved "https://registry.yarnpkg.com/@types/ref/-/ref-0.0.28.tgz#15a61253ed1259038b47499de1c9b0cbca57f55c" + dependencies: + "@types/node" "*" + +balanced-match@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" + +bindings@1, bindings@~1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11" + +bluebird@^3.0.5: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + +brace-expansion@^1.0.0: + version "1.1.6" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" + dependencies: + balanced-match "^0.4.1" + concat-map "0.0.1" + +commander@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" + dependencies: + graceful-readlink ">= 1.0.0" + +commandpost@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commandpost/-/commandpost-1.0.1.tgz#7d7e3e69ae8fe7d6949341e91596ede9b2d631fd" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +debug@2: + version "2.6.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351" + dependencies: + ms "0.7.2" + +editorconfig@^0.13.2: + version "0.13.2" + resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.13.2.tgz#8e57926d9ee69ab6cb999f027c2171467acceb35" + dependencies: + bluebird "^3.0.5" + commander "^2.9.0" + lru-cache "^3.2.0" + sigmund "^1.0.1" + +ffi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ffi/-/ffi-2.2.0.tgz#bf18b04666a29f71227ed56895d5430af47042fa" + dependencies: + bindings "~1.2.0" + debug "2" + nan "2" + ref "1" + ref-struct "1" + +glob-expand@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/glob-expand/-/glob-expand-0.2.1.tgz#1b088ac272b57158870b76816111da4618a66a0f" + dependencies: + glob "~4.5.x" + lodash "~4.13.x" + +glob@~4.5.x: + version "4.5.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "^2.0.1" + once "^1.3.0" + +"graceful-readlink@>= 1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +lodash@~4.13.x: + version "4.13.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.13.1.tgz#83e4b10913f48496d4d16fec4a560af2ee744b68" + +lru-cache@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-3.2.0.tgz#71789b3b7f5399bec8565dda38aa30d2a097efee" + dependencies: + pseudomap "^1.0.1" + +minimatch@^2.0.1: + version "2.0.10" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7" + dependencies: + brace-expansion "^1.0.0" + +ms@0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" + +nan@2: + version "2.5.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +pseudomap@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + +ref-struct@1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ref-struct/-/ref-struct-1.1.0.tgz#5d5ee65ad41cefc3a5c5feb40587261e479edc13" + dependencies: + debug "2" + ref "1" + +ref@1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/ref/-/ref-1.3.4.tgz#724d2bf8ac85f8c8db194d3d85be6efe416bc1e5" + dependencies: + bindings "1" + debug "2" + nan "2" + +sigmund@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" + +typescript-formatter@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/typescript-formatter/-/typescript-formatter-4.1.2.tgz#dfc05b10f17722f905632dc07c11c37663c3fe59" + dependencies: + commandpost "^1.0.0" + editorconfig "^0.13.2" + glob-expand "^0.2.1" + +typescript@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.2.1.tgz#4862b662b988a4c8ff691cc7969622d24db76ae9" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" From 86fa880669922c9052922e7aa840001f3d834cf3 Mon Sep 17 00:00:00 2001 From: vvakame Date: Fri, 24 Feb 2017 08:37:25 +0900 Subject: [PATCH 0292/2308] TypeScript: step 3 --- ts/env.ts | 39 +++++++++++++++++ ts/package.json | 5 ++- ts/step3_env.ts | 113 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 ts/step3_env.ts diff --git a/ts/env.ts b/ts/env.ts index e69de29bb2..126715ac62 100644 --- a/ts/env.ts +++ b/ts/env.ts @@ -0,0 +1,39 @@ +import { MalType, MalSymbol } from "./types"; + +export class Env { + data: Map; + + constructor(public outer?: Env) { + this.data = new Map(); + } + + set(key: MalSymbol, value: MalType): MalType { + this.data.set(key, value); + return value; + } + + find(key: MalSymbol): Env | undefined { + if (this.data.has(key)) { + return this; + } + if (this.outer) { + return this.outer.find(key); + } + + return void 0; + } + + get(key: MalSymbol): MalType { + const env = this.find(key); + if (!env) { + throw new Error(`${key.v} not found`); + } + + const v = env.data.get(key); + if (!v) { + throw new Error(`${key.v} is not exists`); + } + + return v; + } +} diff --git a/ts/package.json b/ts/package.json index 866626a590..b3913c0ae0 100644 --- a/ts/package.json +++ b/ts/package.json @@ -5,10 +5,11 @@ "description": "Make a Lisp (mal) language implemented in TypeScript", "scripts": { "build": "tsfmt -r && tsc -p ./", - "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2", + "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3", "test:step0": "cd .. && make 'test^ts^step0'", "test:step1": "cd .. && make 'test^ts^step1'", - "test:step2": "cd .. && make 'test^ts^step2'" + "test:step2": "cd .. && make 'test^ts^step2'", + "test:step3": "cd .. && make 'test^ts^step3'" }, "dependencies": { "ffi": "^2.2.0" diff --git a/ts/step3_env.ts b/ts/step3_env.ts new file mode 100644 index 0000000000..c61c1967e2 --- /dev/null +++ b/ts/step3_env.ts @@ -0,0 +1,113 @@ +import { readline } from "./node_readline"; + +import { MalType, MalNumber, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { Env } from "./env"; +import { readStr } from "./reader"; +import { prStr } from "./printer"; + +function read(str: string): MalType { + return readStr(str); +} + +function evalAST(ast: MalType, env: Env): MalType { + switch (ast.type) { + case "symbol": + const f = env.get(ast); + if (!f) { + throw new Error(`unknown symbol: ${ast.v}`); + } + return f; + case "list": + return new MalList(ast.list.map(ast => evalSexp(ast, env))); + case "vector": + return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + case "hash-map": + const list: MalType[] = []; + for (const [key, value] of ast.map) { + list.push(key); + list.push(evalSexp(value, env)); + } + return new MalHashMap(list); + default: + return ast; + } +} + +function evalSexp(ast: MalType, env: Env): MalType { + if (ast.type !== "list") { + return evalAST(ast, env); + } + if (ast.list.length === 0) { + return ast; + } + const first = ast.list[0]; + switch (first.type) { + case "symbol": + switch (first.v) { + case "def!": { + const [, key, value] = ast.list; + if (key instanceof MalSymbol === false) { + throw new Error(`unexpected toke type: ${key.type}, expected: symbol`); + } + if (!value) { + throw new Error(`unexpected syntax`); + } + return env.set(key as MalSymbol, evalSexp(value, env)) + } + case "let*": { + let letEnv = new Env(env); + const pairs = ast.list[1]; + if (pairs instanceof MalList === false && pairs instanceof MalVector === false) { + throw new Error(`unexpected toke type: ${pairs.type}, expected: list or vector`); + } + const list = (pairs as (MalList | MalVector)).list; + for (let i = 0; i < list.length; i += 2) { + const key = list[i]; + const value = list[i + 1]; + if (!key || !value) { + throw new Error(`unexpected syntax`); + } + + letEnv.set(key as MalSymbol, evalSexp(value, letEnv)); + } + return evalSexp(ast.list[2], letEnv); + } + } + } + const result = evalAST(ast, env) as MalList; + const [f, ...rest] = result.list; + if (!MalFunction.instanceOf(f)) { + throw new Error(`unexpected token: ${f.type}, expected: function`); + } + return f.func(...rest); +} + +function print(exp: MalType): string { + return prStr(exp); +} + +const replEnv = new Env(); +replEnv.set(MalSymbol.get("+"), new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v + b!.v))); +replEnv.set(MalSymbol.get("-"), new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v - b!.v))); +replEnv.set(MalSymbol.get("*"), new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v * b!.v))); +replEnv.set(MalSymbol.get("/"), new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v / b!.v))); + +function rep(str: string): string { + return print(evalSexp(read(str), replEnv)); +} + +while (true) { + const line = readline("user> "); + if (line == null) { + break; + } + if (line === "") { + continue; + } + try { + console.log(rep(line)); + } catch (e) { + const err: Error = e; + console.error(err.message); + } +} From dfe70453b4d351fdc0964d0e2d20d8a4737b3cdc Mon Sep 17 00:00:00 2001 From: vvakame Date: Fri, 24 Feb 2017 13:21:11 +0900 Subject: [PATCH 0293/2308] TypeScript: step 4 --- ts/core.ts | 131 +++++++++++++++++++++++++++++++++++ ts/env.ts | 13 +++- ts/package.json | 5 +- ts/printer.ts | 8 +-- ts/reader.ts | 2 +- ts/step2_eval.ts | 2 +- ts/step3_env.ts | 2 +- ts/step4_if_fn_do.ts | 161 +++++++++++++++++++++++++++++++++++++++++++ ts/types.ts | 85 ++++++++++++++++++++++- 9 files changed, 397 insertions(+), 12 deletions(-) create mode 100644 ts/step4_if_fn_do.ts diff --git a/ts/core.ts b/ts/core.ts index e69de29bb2..035786357c 100644 --- a/ts/core.ts +++ b/ts/core.ts @@ -0,0 +1,131 @@ +import { MalType, MalSymbol, MalFunction, MalNull, MalList, MalVector, MalBoolean, MalNumber, MalString, equals } from "./types"; +import { prStr } from "./printer"; + +export const ns: Map = (() => { + const ns: { [symbol: string]: typeof MalFunction.prototype.func; } = { + "pr-str"(...args: MalType[]): MalString { + return new MalString(args.map(v => prStr(v, true)).join(" ")); + }, + "str"(...args: MalType[]): MalString { + return new MalString(args.map(v => prStr(v, false)).join("")); + }, + prn(...args: MalType[]): MalNull { + const str = args.map(v => prStr(v, true)).join(" "); + console.log(str); + return MalNull.instance; + }, + println(...args: MalType[]): MalNull { + const str = args.map(v => prStr(v, false)).join(" "); + console.log(str); + return MalNull.instance; + }, + list(...args: MalType[]): MalList { + return new MalList(args); + }, + "list?"(v: MalType): MalBoolean { + return new MalBoolean(v instanceof MalList); + }, + "empty?"(v: MalType): MalBoolean { + if (!MalList.is(v) && !MalVector.is(v)) { + return new MalBoolean(false); + } + return new MalBoolean(v.list.length === 0); + }, + count(v: MalType): MalNumber { + if (MalList.is(v) || MalVector.is(v)) { + return new MalNumber(v.list.length); + } + if (MalNull.is(v)) { + return new MalNumber(0); + } + throw new Error(`unexpected symbol: ${v.type}`); + }, + "+"(a: MalType, b: MalType): MalNumber { + if (!MalNumber.is(a)) { + throw new Error(`unexpected symbol: ${a.type}, expected: number`); + } + if (!MalNumber.is(b)) { + throw new Error(`unexpected symbol: ${b.type}, expected: number`); + } + + return new MalNumber(a.v + b.v); + }, + "-"(a: MalType, b: MalType): MalNumber { + if (!MalNumber.is(a)) { + throw new Error(`unexpected symbol: ${a.type}, expected: number`); + } + if (!MalNumber.is(b)) { + throw new Error(`unexpected symbol: ${b.type}, expected: number`); + } + + return new MalNumber(a.v - b.v); + }, + "*"(a: MalType, b: MalType): MalNumber { + if (!MalNumber.is(a)) { + throw new Error(`unexpected symbol: ${a.type}, expected: number`); + } + if (!MalNumber.is(b)) { + throw new Error(`unexpected symbol: ${b.type}, expected: number`); + } + + return new MalNumber(a.v * b.v); + }, + "/"(a: MalType, b: MalType): MalNumber { + if (!MalNumber.is(a)) { + throw new Error(`unexpected symbol: ${a.type}, expected: number`); + } + if (!MalNumber.is(b)) { + throw new Error(`unexpected symbol: ${b.type}, expected: number`); + } + + return new MalNumber(a.v / b.v); + }, + "="(a: MalType, b: MalType): MalBoolean { + return new MalBoolean(equals(a, b)); + }, + "<"(a: MalType, b: MalType): MalBoolean { + if (!MalNumber.is(a)) { + throw new Error(`unexpected symbol: ${a.type}, expected: number`); + } + if (!MalNumber.is(b)) { + throw new Error(`unexpected symbol: ${b.type}, expected: number`); + } + + return new MalBoolean(a.v < b.v); + }, + "<="(a: MalType, b: MalType): MalBoolean { + if (!MalNumber.is(a)) { + throw new Error(`unexpected symbol: ${a.type}, expected: number`); + } + if (!MalNumber.is(b)) { + throw new Error(`unexpected symbol: ${b.type}, expected: number`); + } + + return new MalBoolean(a.v <= b.v); + }, + ">"(a: MalType, b: MalType): MalBoolean { + if (!MalNumber.is(a)) { + throw new Error(`unexpected symbol: ${a.type}, expected: number`); + } + if (!MalNumber.is(b)) { + throw new Error(`unexpected symbol: ${b.type}, expected: number`); + } + + return new MalBoolean(a.v > b.v); + }, + ">="(a: MalType, b: MalType): MalBoolean { + if (!MalNumber.is(a)) { + throw new Error(`unexpected symbol: ${a.type}, expected: number`); + } + if (!MalNumber.is(b)) { + throw new Error(`unexpected symbol: ${b.type}, expected: number`); + } + + return new MalBoolean(a.v >= b.v); + }, + }; + + const map = new Map(); + Object.keys(ns).forEach(key => map.set(MalSymbol.get(key), new MalFunction(ns[key]))); + return map; +})(); diff --git a/ts/env.ts b/ts/env.ts index 126715ac62..97bbccc963 100644 --- a/ts/env.ts +++ b/ts/env.ts @@ -1,10 +1,19 @@ -import { MalType, MalSymbol } from "./types"; +import { MalType, MalSymbol, MalList } from "./types"; export class Env { data: Map; - constructor(public outer?: Env) { + constructor(public outer?: Env, binds: MalSymbol[] = [], exprts: MalType[] = []) { this.data = new Map(); + + for (let i = 0; i < binds.length; i++) { + const bind = binds[i]; + if (bind.v === "&") { + this.set(binds[i + 1], new MalList(exprts.slice(i))); + break; + } + this.set(bind, exprts[i]); + } } set(key: MalSymbol, value: MalType): MalType { diff --git a/ts/package.json b/ts/package.json index b3913c0ae0..ddf63c3015 100644 --- a/ts/package.json +++ b/ts/package.json @@ -5,11 +5,12 @@ "description": "Make a Lisp (mal) language implemented in TypeScript", "scripts": { "build": "tsfmt -r && tsc -p ./", - "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3", + "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3 && npm run test:step4", "test:step0": "cd .. && make 'test^ts^step0'", "test:step1": "cd .. && make 'test^ts^step1'", "test:step2": "cd .. && make 'test^ts^step2'", - "test:step3": "cd .. && make 'test^ts^step3'" + "test:step3": "cd .. && make 'test^ts^step3'", + "test:step4": "cd .. && make 'test^ts^step4'" }, "dependencies": { "ffi": "^2.2.0" diff --git a/ts/printer.ts b/ts/printer.ts index c3bafd9b4a..edd88656cf 100644 --- a/ts/printer.ts +++ b/ts/printer.ts @@ -3,16 +3,16 @@ import { MalType } from "./types"; export function prStr(v: MalType, printReadably = true): string { switch (v.type) { case "list": - return `(${v.list.map(v => prStr(v)).join(" ")})`; + return `(${v.list.map(v => prStr(v, printReadably)).join(" ")})`; case "vector": - return `[${v.list.map(v => prStr(v)).join(" ")}]`; + return `[${v.list.map(v => prStr(v, printReadably)).join(" ")}]`; case "hash-map": let result = "{"; for (const [key, value] of v.map) { if (result !== "{") { result += " "; } - result += `${prStr(key)} ${prStr(value)}`; + result += `${prStr(key, printReadably)} ${prStr(value, printReadably)}`; } result += "}"; return result; @@ -35,6 +35,6 @@ export function prStr(v: MalType, printReadably = true): string { case "keyword": return `:${v.v.substr(1)}`; case "function": - throw new Error(`invalid state`); + return "#"; } } diff --git a/ts/reader.ts b/ts/reader.ts index 89db0f42a7..b1b5f99835 100644 --- a/ts/reader.ts +++ b/ts/reader.ts @@ -134,7 +134,7 @@ function readAtom(reader: Reader): MalType { } switch (token) { case "nil": - return new MalNull(); + return MalNull.instance; case "true": return new MalBoolean(true); case "false": diff --git a/ts/step2_eval.ts b/ts/step2_eval.ts index 86cfd89ef5..a22315e311 100644 --- a/ts/step2_eval.ts +++ b/ts/step2_eval.ts @@ -45,7 +45,7 @@ function evalSexp(ast: MalType, env: MalEnvironment): MalType { } const result = evalAST(ast, env) as MalList; const [f, ...rest] = result.list; - if (!MalFunction.instanceOf(f)) { + if (!MalFunction.is(f)) { throw new Error(`unexpected token: ${f.type}, expected: function`); } return f.func(...rest); diff --git a/ts/step3_env.ts b/ts/step3_env.ts index c61c1967e2..16a39ea0fd 100644 --- a/ts/step3_env.ts +++ b/ts/step3_env.ts @@ -76,7 +76,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } const result = evalAST(ast, env) as MalList; const [f, ...rest] = result.list; - if (!MalFunction.instanceOf(f)) { + if (!MalFunction.is(f)) { throw new Error(`unexpected token: ${f.type}, expected: function`); } return f.func(...rest); diff --git a/ts/step4_if_fn_do.ts b/ts/step4_if_fn_do.ts new file mode 100644 index 0000000000..d07884e304 --- /dev/null +++ b/ts/step4_if_fn_do.ts @@ -0,0 +1,161 @@ +import { readline } from "./node_readline"; + +import { MalType, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { Env } from "./env"; +import * as core from "./core"; +import { readStr } from "./reader"; +import { prStr } from "./printer"; + +function read(str: string): MalType { + return readStr(str); +} + +function evalAST(ast: MalType, env: Env): MalType { + switch (ast.type) { + case "symbol": + const f = env.get(ast); + if (!f) { + throw new Error(`unknown symbol: ${ast.v}`); + } + return f; + case "list": + return new MalList(ast.list.map(ast => evalSexp(ast, env))); + case "vector": + return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + case "hash-map": + const list: MalType[] = []; + for (const [key, value] of ast.map) { + list.push(key); + list.push(evalSexp(value, env)); + } + return new MalHashMap(list); + default: + return ast; + } +} + +function evalSexp(ast: MalType, env: Env): MalType { + if (ast.type !== "list") { + return evalAST(ast, env); + } + if (ast.list.length === 0) { + return ast; + } + const first = ast.list[0]; + switch (first.type) { + case "symbol": + switch (first.v) { + case "def!": { + const [, key, value] = ast.list; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!value) { + throw new Error(`unexpected syntax`); + } + return env.set(key, evalSexp(value, env)) + } + case "let*": { + let letEnv = new Env(env); + const pairs = ast.list[1]; + if (!MalList.is(pairs) && !MalVector.is(pairs)) { + throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); + } + for (let i = 0; i < pairs.list.length; i += 2) { + const key = pairs.list[i]; + const value = pairs.list[i + 1]; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!key || !value) { + throw new Error(`unexpected syntax`); + } + + letEnv.set(key, evalSexp(value, letEnv)); + } + return evalSexp(ast.list[2], letEnv); + } + case "do": { + const [, ...list] = ast.list; + const ret = evalAST(new MalList(list), env); + if (!MalList.is(ret) && !MalVector.is(ret)) { + throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); + } + return ret.list[ret.list.length - 1]; + } + case "if": { + const [, cond, thenExpr, elseExrp] = ast.list; + const ret = evalSexp(cond, env); + let b = true; + if (MalBoolean.is(ret) && !ret.v) { + b = false; + } else if (MalNull.is(ret)) { + b = false; + } + if (b) { + return evalSexp(thenExpr, env); + } else if (elseExrp) { + return evalSexp(elseExrp, env); + } else { + return MalNull.instance; + } + } + case "fn*": { + const [, args, binds] = ast.list; + if (!MalList.is(args) && !MalVector.is(args)) { + throw new Error(`unexpected return type: ${args.type}, expected: list or vector`); + } + const symbols = args.list.map(arg => { + if (!MalSymbol.is(arg)) { + throw new Error(`unexpected return type: ${arg.type}, expected: symbol`); + } + return arg; + }); + return new MalFunction((...fnArgs: MalType[]) => { + return evalSexp(binds, new Env(env, symbols, fnArgs)); + }); + } + } + } + const result = evalAST(ast, env); + if (!MalList.is(result) && !MalVector.is(result)) { + throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); + } + const [f, ...rest] = result.list; + if (!MalFunction.is(f)) { + throw new Error(`unexpected token: ${f.type}, expected: function`); + } + return f.func(...rest); +} + +function print(exp: MalType): string { + return prStr(exp); +} + +const replEnv = new Env(); +for (const [key, value] of core.ns) { + replEnv.set(key, value); +} + +// core.mal: defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); + +function rep(str: string): string { + return print(evalSexp(read(str), replEnv)); +} + +while (true) { + const line = readline("user> "); + if (line == null) { + break; + } + if (line === "") { + continue; + } + try { + console.log(rep(line)); + } catch (e) { + const err: Error = e; + console.error(err.message); + } +} diff --git a/ts/types.ts b/ts/types.ts index aad9e7772b..e707be702a 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -1,6 +1,54 @@ export type MalType = MalList | MalNumber | MalString | MalNull | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap | MalFunction; +export function equals(a: MalType, b: MalType, strict?: boolean): boolean { + if (strict && a.constructor !== b.constructor) { + return false; + } else if ( + (MalList.is(a) || MalVector.is(a)) + && (MalList.is(b) || MalVector.is(b)) + ) { + return listEquals(a.list, b.list); + } + + if (MalNull.is(a) && MalNull.is(b)) { + return true; + } + if ( + (MalList.is(a) && MalList.is(b)) + || (MalVector.is(a) && MalVector.is(b)) + ) { + return listEquals(a.list, b.list); + } + if ( + (MalNumber.is(a) && MalNumber.is(b)) + || (MalString.is(a) && MalString.is(b)) + || (MalBoolean.is(a) && MalBoolean.is(b)) + || (MalSymbol.is(a) && MalSymbol.is(b)) + || (MalKeyword.is(a) && MalKeyword.is(b)) + ) { + return a.v === b.v; + } + + return false; + + function listEquals(a: MalType[], b: MalType[]): boolean { + if (a.length !== b.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (!equals(a[i], b[i], strict)) { + return false; + } + } + return true; + } +} + export class MalList { + static is(f: MalType): f is MalList { + return f instanceof MalList; + } + type: "list" = "list"; constructor(public list: MalType[]) { @@ -8,28 +56,51 @@ export class MalList { } export class MalNumber { + static is(f: MalType): f is MalNumber { + return f instanceof MalNumber; + } + type: "number" = "number"; constructor(public v: number) { } } export class MalString { + static is(f: MalType): f is MalString { + return f instanceof MalString; + } + type: "string" = "string"; constructor(public v: string) { } } export class MalNull { + static is(f: MalType): f is MalNull { + return f instanceof MalNull; + } + + static instance = new MalNull(); type: "null" = "null"; + + private constructor() { } } export class MalBoolean { + static is(f: MalType): f is MalBoolean { + return f instanceof MalBoolean; + } + type: "boolean" = "boolean"; constructor(public v: boolean) { } } export class MalSymbol { + static is(f: MalType): f is MalSymbol { + return f instanceof MalSymbol; + } + static map = new Map(); static get(name: string): MalSymbol { @@ -50,6 +121,10 @@ export class MalSymbol { } export class MalKeyword { + static is(f: MalType): f is MalKeyword { + return f instanceof MalKeyword; + } + type: "keyword" = "keyword"; constructor(public v: string) { this.v = String.fromCodePoint(0x29E) + this.v; @@ -57,12 +132,20 @@ export class MalKeyword { } export class MalVector { + static is(f: MalType): f is MalVector { + return f instanceof MalVector; + } + type: "vector" = "vector"; constructor(public list: MalType[]) { } } export class MalHashMap { + static is(f: MalType): f is MalHashMap { + return f instanceof MalHashMap; + } + type: "hash-map" = "hash-map"; map = new Map(); constructor(list: MalType[]) { @@ -78,7 +161,7 @@ export class MalHashMap { } export class MalFunction { - static instanceOf(f: MalType): f is MalFunction { + static is(f: MalType): f is MalFunction { return f instanceof MalFunction; } From 79a10a6e131ab81d98542d42896375d23405555f Mon Sep 17 00:00:00 2001 From: vvakame Date: Fri, 24 Feb 2017 16:30:25 +0900 Subject: [PATCH 0294/2308] TypeScript: step 5 --- ts/core.ts | 2 +- ts/package.json | 5 +- ts/step2_eval.ts | 12 +-- ts/step3_env.ts | 12 +-- ts/step4_if_fn_do.ts | 6 +- ts/step5_tco.ts | 170 +++++++++++++++++++++++++++++++++++++++++++ ts/types.ts | 43 ++++++++++- 7 files changed, 231 insertions(+), 19 deletions(-) create mode 100644 ts/step5_tco.ts diff --git a/ts/core.ts b/ts/core.ts index 035786357c..73c9aa7250 100644 --- a/ts/core.ts +++ b/ts/core.ts @@ -126,6 +126,6 @@ export const ns: Map = (() => { }; const map = new Map(); - Object.keys(ns).forEach(key => map.set(MalSymbol.get(key), new MalFunction(ns[key]))); + Object.keys(ns).forEach(key => map.set(MalSymbol.get(key), MalFunction.fromBootstrap(ns[key]))); return map; })(); diff --git a/ts/package.json b/ts/package.json index ddf63c3015..40ca02917a 100644 --- a/ts/package.json +++ b/ts/package.json @@ -5,12 +5,13 @@ "description": "Make a Lisp (mal) language implemented in TypeScript", "scripts": { "build": "tsfmt -r && tsc -p ./", - "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3 && npm run test:step4", + "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3 && npm run test:step4 && npm run test:step5", "test:step0": "cd .. && make 'test^ts^step0'", "test:step1": "cd .. && make 'test^ts^step1'", "test:step2": "cd .. && make 'test^ts^step2'", "test:step3": "cd .. && make 'test^ts^step3'", - "test:step4": "cd .. && make 'test^ts^step4'" + "test:step4": "cd .. && make 'test^ts^step4'", + "test:step5": "cd .. && make 'test^ts^step5'" }, "dependencies": { "ffi": "^2.2.0" diff --git a/ts/step2_eval.ts b/ts/step2_eval.ts index a22315e311..31c6b699bb 100644 --- a/ts/step2_eval.ts +++ b/ts/step2_eval.ts @@ -44,11 +44,11 @@ function evalSexp(ast: MalType, env: MalEnvironment): MalType { return ast; } const result = evalAST(ast, env) as MalList; - const [f, ...rest] = result.list; + const [f, ...args] = result.list; if (!MalFunction.is(f)) { throw new Error(`unexpected token: ${f.type}, expected: function`); } - return f.func(...rest); + return f.func(...args); } function print(exp: MalType): string { @@ -56,10 +56,10 @@ function print(exp: MalType): string { } const replEnv: MalEnvironment = { - "+": new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v + b!.v)), - "-": new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v - b!.v)), - "*": new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v * b!.v)), - "/": new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v / b!.v)), + "+": MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v + b!.v)), + "-": MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v - b!.v)), + "*": MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v * b!.v)), + "/": MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v / b!.v)), }; function rep(str: string): string { return print(evalSexp(read(str), replEnv)); diff --git a/ts/step3_env.ts b/ts/step3_env.ts index 16a39ea0fd..b129ecb1eb 100644 --- a/ts/step3_env.ts +++ b/ts/step3_env.ts @@ -75,11 +75,11 @@ function evalSexp(ast: MalType, env: Env): MalType { } } const result = evalAST(ast, env) as MalList; - const [f, ...rest] = result.list; + const [f, ...args] = result.list; if (!MalFunction.is(f)) { throw new Error(`unexpected token: ${f.type}, expected: function`); } - return f.func(...rest); + return f.func(...args); } function print(exp: MalType): string { @@ -87,10 +87,10 @@ function print(exp: MalType): string { } const replEnv = new Env(); -replEnv.set(MalSymbol.get("+"), new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v + b!.v))); -replEnv.set(MalSymbol.get("-"), new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v - b!.v))); -replEnv.set(MalSymbol.get("*"), new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v * b!.v))); -replEnv.set(MalSymbol.get("/"), new MalFunction((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v / b!.v))); +replEnv.set(MalSymbol.get("+"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v + b!.v))); +replEnv.set(MalSymbol.get("-"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v - b!.v))); +replEnv.set(MalSymbol.get("*"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v * b!.v))); +replEnv.set(MalSymbol.get("/"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v / b!.v))); function rep(str: string): string { return print(evalSexp(read(str), replEnv)); diff --git a/ts/step4_if_fn_do.ts b/ts/step4_if_fn_do.ts index d07884e304..86f2fca3af 100644 --- a/ts/step4_if_fn_do.ts +++ b/ts/step4_if_fn_do.ts @@ -111,7 +111,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } return arg; }); - return new MalFunction((...fnArgs: MalType[]) => { + return MalFunction.fromBootstrap((...fnArgs: MalType[]) => { return evalSexp(binds, new Env(env, symbols, fnArgs)); }); } @@ -121,11 +121,11 @@ function evalSexp(ast: MalType, env: Env): MalType { if (!MalList.is(result) && !MalVector.is(result)) { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } - const [f, ...rest] = result.list; + const [f, ...args] = result.list; if (!MalFunction.is(f)) { throw new Error(`unexpected token: ${f.type}, expected: function`); } - return f.func(...rest); + return f.func(...args); } function print(exp: MalType): string { diff --git a/ts/step5_tco.ts b/ts/step5_tco.ts new file mode 100644 index 0000000000..c240d91391 --- /dev/null +++ b/ts/step5_tco.ts @@ -0,0 +1,170 @@ +import { readline } from "./node_readline"; + +import { MalType, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { Env } from "./env"; +import * as core from "./core"; +import { readStr } from "./reader"; +import { prStr } from "./printer"; + +function read(str: string): MalType { + return readStr(str); +} + +function evalAST(ast: MalType, env: Env): MalType { + switch (ast.type) { + case "symbol": + const f = env.get(ast); + if (!f) { + throw new Error(`unknown symbol: ${ast.v}`); + } + return f; + case "list": + return new MalList(ast.list.map(ast => evalSexp(ast, env))); + case "vector": + return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + case "hash-map": + const list: MalType[] = []; + for (const [key, value] of ast.map) { + list.push(key); + list.push(evalSexp(value, env)); + } + return new MalHashMap(list); + default: + return ast; + } +} + +function evalSexp(ast: MalType, env: Env): MalType { + loop: while (true) { + if (ast.type !== "list") { + return evalAST(ast, env); + } + if (ast.list.length === 0) { + return ast; + } + const first = ast.list[0]; + switch (first.type) { + case "symbol": + switch (first.v) { + case "def!": { + const [, key, value] = ast.list; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!value) { + throw new Error(`unexpected syntax`); + } + return env.set(key, evalSexp(value, env)) + } + case "let*": { + env = new Env(env); + const pairs = ast.list[1]; + if (!MalList.is(pairs) && !MalVector.is(pairs)) { + throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); + } + for (let i = 0; i < pairs.list.length; i += 2) { + const key = pairs.list[i]; + const value = pairs.list[i + 1]; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!key || !value) { + throw new Error(`unexpected syntax`); + } + + env.set(key, evalSexp(value, env)); + } + ast = ast.list[2]; + continue loop; + } + case "do": { + const [, ...list] = ast.list; + const ret = evalAST(new MalList(list), env); + if (!MalList.is(ret) && !MalVector.is(ret)) { + throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); + } + ast = ret.list[ret.list.length - 1]; + continue loop; + } + case "if": { + const [, cond, thenExpr, elseExrp] = ast.list; + const ret = evalSexp(cond, env); + let b = true; + if (MalBoolean.is(ret) && !ret.v) { + b = false; + } else if (MalNull.is(ret)) { + b = false; + } + if (b) { + ast = thenExpr; + } else if (elseExrp) { + ast = elseExrp; + } else { + ast = MalNull.instance; + } + continue loop; + } + case "fn*": { + const [, params, bodyAst] = ast.list; + if (!MalList.is(params) && !MalVector.is(params)) { + throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); + } + const symbols = params.list.map(param => { + if (!MalSymbol.is(param)) { + throw new Error(`unexpected return type: ${param.type}, expected: symbol`); + } + return param; + }); + return MalFunction.fromLisp(evalSexp, env, symbols, bodyAst); + } + } + } + const result = evalAST(ast, env); + if (!MalList.is(result) && !MalVector.is(result)) { + throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); + } + const [f, ...args] = result.list; + if (!MalFunction.is(f)) { + throw new Error(`unexpected token: ${f.type}, expected: function`); + } + if (f.ast) { + ast = f.ast; + env = f.newEnv(args); + continue loop; + } + + return f.func(...args); + } +} + +function print(exp: MalType): string { + return prStr(exp); +} + +const replEnv = new Env(); +for (const [key, value] of core.ns) { + replEnv.set(key, value); +} + +// core.mal: defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); + +function rep(str: string): string { + return print(evalSexp(read(str), replEnv)); +} + +while (true) { + const line = readline("user> "); + if (line == null) { + break; + } + if (line === "") { + continue; + } + try { + console.log(rep(line)); + } catch (e) { + const err: Error = e; + console.error(err.message); + } +} diff --git a/ts/types.ts b/ts/types.ts index e707be702a..99358a3e76 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -1,3 +1,5 @@ +import { Env } from "./env"; + export type MalType = MalList | MalNumber | MalString | MalNull | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap | MalFunction; export function equals(a: MalType, b: MalType, strict?: boolean): boolean { @@ -160,12 +162,51 @@ export class MalHashMap { } } +type MalF = (...args: (MalType | undefined)[]) => MalType; + export class MalFunction { static is(f: MalType): f is MalFunction { return f instanceof MalFunction; } + static fromLisp(evalSexpr: (ast: MalType, env: Env) => MalType, env: Env, params: MalSymbol[], bodyAst: MalType): MalFunction { + const f = new MalFunction(); + f.func = (...args) => evalSexpr(bodyAst, new Env(env, params, malTypes2malSymbols(args))); + f.env = env; + f.params = params; + f.ast = bodyAst; + + return f; + + function malTypes2malSymbols(args: (MalType | undefined)[]): MalSymbol[] { + return args.map(arg => { + if (!arg) { + throw new Error(`undefined argument`); + } + if (!MalSymbol.is(arg)) { + throw new Error(`unexpected token type: ${arg.type}, expected: symbol`); + } + return arg; + }); + } + } + + static fromBootstrap(func: MalF): MalFunction { + const f = new MalFunction(); + f.func = func; + return f; + } + type: "function" = "function"; - constructor(public func: (...args: (MalType | undefined)[]) => MalType) { + + func: MalF; + ast: MalType; + env: Env; + params: MalSymbol[]; + + private constructor() { } + + newEnv(args: MalType[]) { + return new Env(this.env, this.params, args); } } From 555f7fc73e08ae5007016a95eae4294df3d844b7 Mon Sep 17 00:00:00 2001 From: vvakame Date: Fri, 24 Feb 2017 18:28:26 +0900 Subject: [PATCH 0295/2308] TypeScript: step 6 --- ts/core.ts | 47 +++++++++++- ts/package.json | 5 +- ts/printer.ts | 2 + ts/step6_file.ts | 185 +++++++++++++++++++++++++++++++++++++++++++++++ ts/types.ts | 20 +++-- 5 files changed, 250 insertions(+), 9 deletions(-) create mode 100644 ts/step6_file.ts diff --git a/ts/core.ts b/ts/core.ts index 73c9aa7250..b55a162e70 100644 --- a/ts/core.ts +++ b/ts/core.ts @@ -1,4 +1,7 @@ -import { MalType, MalSymbol, MalFunction, MalNull, MalList, MalVector, MalBoolean, MalNumber, MalString, equals } from "./types"; +import * as fs from "fs"; + +import { MalType, MalSymbol, MalFunction, MalNull, MalList, MalVector, MalBoolean, MalNumber, MalString, MalAtom, equals } from "./types"; +import { readStr } from "./reader"; import { prStr } from "./printer"; export const ns: Map = (() => { @@ -19,6 +22,19 @@ export const ns: Map = (() => { console.log(str); return MalNull.instance; }, + "read-string"(v: MalType) { + if (!MalString.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: string`); + } + return readStr(v.v); + }, + slurp(v: MalType) { + if (!MalString.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: string`); + } + const content = fs.readFileSync(v.v, "UTF-8"); + return new MalString(content); + }, list(...args: MalType[]): MalList { return new MalList(args); }, @@ -40,6 +56,35 @@ export const ns: Map = (() => { } throw new Error(`unexpected symbol: ${v.type}`); }, + atom(v: MalType): MalAtom { + return new MalAtom(v); + }, + "atom?"(v: MalType): MalBoolean { + return new MalBoolean(MalAtom.is(v)); + }, + deref(v: MalType): MalType { + if (!MalAtom.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: atom`); + } + return v.v; + }, + "reset!"(atom: MalType, v: MalType): MalType { + if (!MalAtom.is(atom)) { + throw new Error(`unexpected symbol: ${atom.type}, expected: atom`); + } + atom.v = v; + return v; + }, + "swap!"(atom: MalType, f: MalType, ...args: MalType[]): MalType { + if (!MalAtom.is(atom)) { + throw new Error(`unexpected symbol: ${atom.type}, expected: atom`); + } + if (!MalFunction.is(f)) { + throw new Error(`unexpected symbol: ${f.type}, expected: function`); + } + atom.v = f.func(...[atom.v].concat(args)); + return atom.v; + }, "+"(a: MalType, b: MalType): MalNumber { if (!MalNumber.is(a)) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); diff --git a/ts/package.json b/ts/package.json index 40ca02917a..be4e7753f1 100644 --- a/ts/package.json +++ b/ts/package.json @@ -5,13 +5,14 @@ "description": "Make a Lisp (mal) language implemented in TypeScript", "scripts": { "build": "tsfmt -r && tsc -p ./", - "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3 && npm run test:step4 && npm run test:step5", + "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3 && npm run test:step4 && npm run test:step5 && npm run test:step6", "test:step0": "cd .. && make 'test^ts^step0'", "test:step1": "cd .. && make 'test^ts^step1'", "test:step2": "cd .. && make 'test^ts^step2'", "test:step3": "cd .. && make 'test^ts^step3'", "test:step4": "cd .. && make 'test^ts^step4'", - "test:step5": "cd .. && make 'test^ts^step5'" + "test:step5": "cd .. && make 'test^ts^step5'", + "test:step6": "cd .. && make 'test^ts^step6'" }, "dependencies": { "ffi": "^2.2.0" diff --git a/ts/printer.ts b/ts/printer.ts index edd88656cf..81d9c403c8 100644 --- a/ts/printer.ts +++ b/ts/printer.ts @@ -36,5 +36,7 @@ export function prStr(v: MalType, printReadably = true): string { return `:${v.v.substr(1)}`; case "function": return "#"; + case "atom": + return `(atom ${prStr(v.v, printReadably)})`; } } diff --git a/ts/step6_file.ts b/ts/step6_file.ts new file mode 100644 index 0000000000..a29ec10044 --- /dev/null +++ b/ts/step6_file.ts @@ -0,0 +1,185 @@ +import { readline } from "./node_readline"; + +import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { Env } from "./env"; +import * as core from "./core"; +import { readStr } from "./reader"; +import { prStr } from "./printer"; + +function read(str: string): MalType { + return readStr(str); +} + +function evalAST(ast: MalType, env: Env): MalType { + switch (ast.type) { + case "symbol": + const f = env.get(ast); + if (!f) { + throw new Error(`unknown symbol: ${ast.v}`); + } + return f; + case "list": + return new MalList(ast.list.map(ast => evalSexp(ast, env))); + case "vector": + return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + case "hash-map": + const list: MalType[] = []; + for (const [key, value] of ast.map) { + list.push(key); + list.push(evalSexp(value, env)); + } + return new MalHashMap(list); + default: + return ast; + } +} + +function evalSexp(ast: MalType, env: Env): MalType { + loop: while (true) { + if (ast.type !== "list") { + return evalAST(ast, env); + } + if (ast.list.length === 0) { + return ast; + } + const first = ast.list[0]; + switch (first.type) { + case "symbol": + switch (first.v) { + case "def!": { + const [, key, value] = ast.list; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!value) { + throw new Error(`unexpected syntax`); + } + return env.set(key, evalSexp(value, env)) + } + case "let*": { + env = new Env(env); + const pairs = ast.list[1]; + if (!MalList.is(pairs) && !MalVector.is(pairs)) { + throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); + } + for (let i = 0; i < pairs.list.length; i += 2) { + const key = pairs.list[i]; + const value = pairs.list[i + 1]; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!key || !value) { + throw new Error(`unexpected syntax`); + } + + env.set(key, evalSexp(value, env)); + } + ast = ast.list[2]; + continue loop; + } + case "do": { + const [, ...list] = ast.list; + const ret = evalAST(new MalList(list), env); + if (!MalList.is(ret) && !MalVector.is(ret)) { + throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); + } + ast = ret.list[ret.list.length - 1]; + continue loop; + } + case "if": { + const [, cond, thenExpr, elseExrp] = ast.list; + const ret = evalSexp(cond, env); + let b = true; + if (MalBoolean.is(ret) && !ret.v) { + b = false; + } else if (MalNull.is(ret)) { + b = false; + } + if (b) { + ast = thenExpr; + } else if (elseExrp) { + ast = elseExrp; + } else { + ast = MalNull.instance; + } + continue loop; + } + case "fn*": { + const [, params, bodyAst] = ast.list; + if (!MalList.is(params) && !MalVector.is(params)) { + throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); + } + const symbols = params.list.map(param => { + if (!MalSymbol.is(param)) { + throw new Error(`unexpected return type: ${param.type}, expected: symbol`); + } + return param; + }); + return MalFunction.fromLisp(evalSexp, env, symbols, bodyAst); + } + } + } + const result = evalAST(ast, env); + if (!MalList.is(result) && !MalVector.is(result)) { + throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); + } + const [f, ...args] = result.list; + if (!MalFunction.is(f)) { + throw new Error(`unexpected token: ${f.type}, expected: function`); + } + if (f.ast) { + ast = f.ast; + env = f.newEnv(args); + continue loop; + } + + return f.func(...args); + } +} + +function print(exp: MalType): string { + return prStr(exp); +} + +const replEnv = new Env(); +for (const [key, value] of core.ns) { + replEnv.set(key, value); +} +replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { + if (!ast) { + throw new Error(`undefined argument`); + } + return evalSexp(ast, replEnv); +})); + +replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); + +// core.mal: defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))`); + +if (typeof process !== "undefined" && 2 < process.argv.length) { + replEnv.set(MalSymbol.get("*ARGV*"), new MalList(process.argv.slice(3).map(s => new MalString(s)))); + rep(`(load-file "${process.argv[2]}")`); + process.exit(0); +} + +function rep(str: string): string { + return print(evalSexp(read(str), replEnv)); +} + +while (true) { + const line = readline("user> "); + if (line == null) { + break; + } + if (line === "") { + continue; + } + try { + console.log(rep(line)); + } catch (e) { + const err: Error = e; + console.error(err.message); + } +} diff --git a/ts/types.ts b/ts/types.ts index 99358a3e76..3838db69b0 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -1,6 +1,6 @@ import { Env } from "./env"; -export type MalType = MalList | MalNumber | MalString | MalNull | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap | MalFunction; +export type MalType = MalList | MalNumber | MalString | MalNull | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap | MalFunction | MalAtom; export function equals(a: MalType, b: MalType, strict?: boolean): boolean { if (strict && a.constructor !== b.constructor) { @@ -171,21 +171,18 @@ export class MalFunction { static fromLisp(evalSexpr: (ast: MalType, env: Env) => MalType, env: Env, params: MalSymbol[], bodyAst: MalType): MalFunction { const f = new MalFunction(); - f.func = (...args) => evalSexpr(bodyAst, new Env(env, params, malTypes2malSymbols(args))); + f.func = (...args) => evalSexpr(bodyAst, new Env(env, params, checkUndefined(args))); f.env = env; f.params = params; f.ast = bodyAst; return f; - function malTypes2malSymbols(args: (MalType | undefined)[]): MalSymbol[] { + function checkUndefined(args: (MalType | undefined)[]): MalType[] { return args.map(arg => { if (!arg) { throw new Error(`undefined argument`); } - if (!MalSymbol.is(arg)) { - throw new Error(`unexpected token type: ${arg.type}, expected: symbol`); - } return arg; }); } @@ -210,3 +207,14 @@ export class MalFunction { return new Env(this.env, this.params, args); } } + +export class MalAtom { + static is(f: MalType): f is MalAtom { + return f instanceof MalAtom; + } + + type: "atom" = "atom"; + + constructor(public v: MalType) { + } +} \ No newline at end of file From 8d6bad0702594fc9551422440aaca3d3a00529b5 Mon Sep 17 00:00:00 2001 From: vvakame Date: Fri, 24 Feb 2017 20:16:23 +0900 Subject: [PATCH 0296/2308] TypeScript: step 7 --- ts/core.ts | 19 ++++ ts/package.json | 5 +- ts/step7_quote.ts | 232 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 ts/step7_quote.ts diff --git a/ts/core.ts b/ts/core.ts index b55a162e70..8ecda3f029 100644 --- a/ts/core.ts +++ b/ts/core.ts @@ -35,6 +35,25 @@ export const ns: Map = (() => { const content = fs.readFileSync(v.v, "UTF-8"); return new MalString(content); }, + cons(a: MalType, b: MalType) { + if (!MalList.is(b) && !MalVector.is(b)) { + throw new Error(`unexpected symbol: ${b.type}, expected: list or vector`); + } + + return new MalList([a].concat(b.list)); + }, + concat(...args: MalType[]) { + const list = args + .map(arg => { + if (!MalList.is(arg) && !MalVector.is(arg)) { + throw new Error(`unexpected symbol: ${arg.type}, expected: list or vector`); + } + return arg; + }) + .reduce((p, c) => p.concat(c.list), [] as MalType[]); + + return new MalList(list); + }, list(...args: MalType[]): MalList { return new MalList(args); }, diff --git a/ts/package.json b/ts/package.json index be4e7753f1..50c9fe79cc 100644 --- a/ts/package.json +++ b/ts/package.json @@ -5,14 +5,15 @@ "description": "Make a Lisp (mal) language implemented in TypeScript", "scripts": { "build": "tsfmt -r && tsc -p ./", - "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3 && npm run test:step4 && npm run test:step5 && npm run test:step6", + "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3 && npm run test:step4 && npm run test:step5 && npm run test:step6 && npm run test:step7", "test:step0": "cd .. && make 'test^ts^step0'", "test:step1": "cd .. && make 'test^ts^step1'", "test:step2": "cd .. && make 'test^ts^step2'", "test:step3": "cd .. && make 'test^ts^step3'", "test:step4": "cd .. && make 'test^ts^step4'", "test:step5": "cd .. && make 'test^ts^step5'", - "test:step6": "cd .. && make 'test^ts^step6'" + "test:step6": "cd .. && make 'test^ts^step6'", + "test:step7": "cd .. && make 'test^ts^step7'" }, "dependencies": { "ffi": "^2.2.0" diff --git a/ts/step7_quote.ts b/ts/step7_quote.ts new file mode 100644 index 0000000000..2f9749e3b4 --- /dev/null +++ b/ts/step7_quote.ts @@ -0,0 +1,232 @@ +import { readline } from "./node_readline"; + +import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { Env } from "./env"; +import * as core from "./core"; +import { readStr } from "./reader"; +import { prStr } from "./printer"; + +function read(str: string): MalType { + return readStr(str); +} + +function quasiquote(ast: MalType): MalType { + if (!isPair(ast)) { + return new MalList([MalSymbol.get("quote"), ast]); + } + if (!MalList.is(ast) && !MalVector.is(ast)) { + throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); + } + const [arg1, arg2] = ast.list; + if (MalSymbol.is(arg1) && arg1.v === "unquote") { + return arg2; + } + if (isPair(arg1)) { + if (!MalList.is(arg1) && !MalVector.is(arg1)) { + throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); + } + const [arg11, arg12] = arg1.list; + if (MalSymbol.is(arg11) && arg11.v === "splice-unquote") { + return new MalList([ + MalSymbol.get("concat"), + arg12, + quasiquote(new MalList(ast.list.slice(1))), + ]); + } + } + + return new MalList([ + MalSymbol.get("cons"), + quasiquote(arg1), + quasiquote(new MalList(ast.list.slice(1))), + ]); + + function isPair(ast: MalType) { + if (!MalList.is(ast) && !MalVector.is(ast)) { + return false; + } + + return 0 < ast.list.length; + } +} + +function evalAST(ast: MalType, env: Env): MalType { + switch (ast.type) { + case "symbol": + const f = env.get(ast); + if (!f) { + throw new Error(`unknown symbol: ${ast.v}`); + } + return f; + case "list": + return new MalList(ast.list.map(ast => evalSexp(ast, env))); + case "vector": + return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + case "hash-map": + const list: MalType[] = []; + for (const [key, value] of ast.map) { + list.push(key); + list.push(evalSexp(value, env)); + } + return new MalHashMap(list); + default: + return ast; + } +} + +function evalSexp(ast: MalType, env: Env): MalType { + loop: while (true) { + if (ast.type !== "list") { + return evalAST(ast, env); + } + if (ast.list.length === 0) { + return ast; + } + const first = ast.list[0]; + switch (first.type) { + case "symbol": + switch (first.v) { + case "def!": { + const [, key, value] = ast.list; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!value) { + throw new Error(`unexpected syntax`); + } + return env.set(key, evalSexp(value, env)) + } + case "let*": { + env = new Env(env); + const pairs = ast.list[1]; + if (!MalList.is(pairs) && !MalVector.is(pairs)) { + throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); + } + for (let i = 0; i < pairs.list.length; i += 2) { + const key = pairs.list[i]; + const value = pairs.list[i + 1]; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!key || !value) { + throw new Error(`unexpected syntax`); + } + + env.set(key, evalSexp(value, env)); + } + ast = ast.list[2]; + continue loop; + } + case "quote": { + return ast.list[1]; + } + case "quasiquote": { + ast = quasiquote(ast.list[1]); + continue loop; + } + case "do": { + const [, ...list] = ast.list; + const ret = evalAST(new MalList(list), env); + if (!MalList.is(ret) && !MalVector.is(ret)) { + throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); + } + ast = ret.list[ret.list.length - 1]; + continue loop; + } + case "if": { + const [, cond, thenExpr, elseExrp] = ast.list; + const ret = evalSexp(cond, env); + let b = true; + if (MalBoolean.is(ret) && !ret.v) { + b = false; + } else if (MalNull.is(ret)) { + b = false; + } + if (b) { + ast = thenExpr; + } else if (elseExrp) { + ast = elseExrp; + } else { + ast = MalNull.instance; + } + continue loop; + } + case "fn*": { + const [, params, bodyAst] = ast.list; + if (!MalList.is(params) && !MalVector.is(params)) { + throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); + } + const symbols = params.list.map(param => { + if (!MalSymbol.is(param)) { + throw new Error(`unexpected return type: ${param.type}, expected: symbol`); + } + return param; + }); + return MalFunction.fromLisp(evalSexp, env, symbols, bodyAst); + } + } + } + const result = evalAST(ast, env); + if (!MalList.is(result) && !MalVector.is(result)) { + throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); + } + const [f, ...args] = result.list; + if (!MalFunction.is(f)) { + throw new Error(`unexpected token: ${f.type}, expected: function`); + } + if (f.ast) { + ast = f.ast; + env = f.newEnv(args); + continue loop; + } + + return f.func(...args); + } +} + +function print(exp: MalType): string { + return prStr(exp); +} + +const replEnv = new Env(); +for (const [key, value] of core.ns) { + replEnv.set(key, value); +} +replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { + if (!ast) { + throw new Error(`undefined argument`); + } + return evalSexp(ast, replEnv); +})); + +replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); + +// core.mal: defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))`); + +if (typeof process !== "undefined" && 2 < process.argv.length) { + replEnv.set(MalSymbol.get("*ARGV*"), new MalList(process.argv.slice(3).map(s => new MalString(s)))); + rep(`(load-file "${process.argv[2]}")`); + process.exit(0); +} + +function rep(str: string): string { + return print(evalSexp(read(str), replEnv)); +} + +while (true) { + const line = readline("user> "); + if (line == null) { + break; + } + if (line === "") { + continue; + } + try { + console.log(rep(line)); + } catch (e) { + const err: Error = e; + console.error(err.message); + } +} From e21a85a3ef242a05feceeb60121102a93eb631df Mon Sep 17 00:00:00 2001 From: vvakame Date: Fri, 24 Feb 2017 20:57:23 +0900 Subject: [PATCH 0297/2308] TypeScript: step 8 --- ts/core.ts | 35 ++++++ ts/package.json | 5 +- ts/step8_macros.ts | 298 +++++++++++++++++++++++++++++++++++++++++++++ ts/types.ts | 4 + 4 files changed, 340 insertions(+), 2 deletions(-) create mode 100644 ts/step8_macros.ts diff --git a/ts/core.ts b/ts/core.ts index 8ecda3f029..072477e3ac 100644 --- a/ts/core.ts +++ b/ts/core.ts @@ -75,6 +75,41 @@ export const ns: Map = (() => { } throw new Error(`unexpected symbol: ${v.type}`); }, + nth(list: MalType, idx: MalType) { + if (!MalList.is(list) && !MalVector.is(list)) { + throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`); + } + if (!MalNumber.is(idx)) { + throw new Error(`unexpected symbol: ${idx.type}, expected: number`); + } + + const v = list.list[idx.v]; + if (!v) { + throw new Error("nth: index out of range"); + } + + return v; + }, + first(v: MalType) { + if (MalNull.is(v)) { + return MalNull.instance; + } + if (!MalList.is(v) && !MalVector.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: list or vector`); + } + + return v.list[0] || MalNull.instance; + }, + rest(v: MalType) { + if (MalNull.is(v)) { + return new MalList([]); + } + if (!MalList.is(v) && !MalVector.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: list or vector`); + } + + return new MalList(v.list.slice(1)); + }, atom(v: MalType): MalAtom { return new MalAtom(v); }, diff --git a/ts/package.json b/ts/package.json index 50c9fe79cc..00fd8f848a 100644 --- a/ts/package.json +++ b/ts/package.json @@ -5,7 +5,7 @@ "description": "Make a Lisp (mal) language implemented in TypeScript", "scripts": { "build": "tsfmt -r && tsc -p ./", - "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3 && npm run test:step4 && npm run test:step5 && npm run test:step6 && npm run test:step7", + "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3 && npm run test:step4 && npm run test:step5 && npm run test:step6 && npm run test:step7 && npm run test:step8", "test:step0": "cd .. && make 'test^ts^step0'", "test:step1": "cd .. && make 'test^ts^step1'", "test:step2": "cd .. && make 'test^ts^step2'", @@ -13,7 +13,8 @@ "test:step4": "cd .. && make 'test^ts^step4'", "test:step5": "cd .. && make 'test^ts^step5'", "test:step6": "cd .. && make 'test^ts^step6'", - "test:step7": "cd .. && make 'test^ts^step7'" + "test:step7": "cd .. && make 'test^ts^step7'", + "test:step8": "cd .. && make 'test^ts^step8'" }, "dependencies": { "ffi": "^2.2.0" diff --git a/ts/step8_macros.ts b/ts/step8_macros.ts new file mode 100644 index 0000000000..8d09e1a583 --- /dev/null +++ b/ts/step8_macros.ts @@ -0,0 +1,298 @@ +import { readline } from "./node_readline"; + +import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { Env } from "./env"; +import * as core from "./core"; +import { readStr } from "./reader"; +import { prStr } from "./printer"; + +function read(str: string): MalType { + return readStr(str); +} + +function quasiquote(ast: MalType): MalType { + if (!isPair(ast)) { + return new MalList([MalSymbol.get("quote"), ast]); + } + if (!MalList.is(ast) && !MalVector.is(ast)) { + throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); + } + const [arg1, arg2] = ast.list; + if (MalSymbol.is(arg1) && arg1.v === "unquote") { + return arg2; + } + if (isPair(arg1)) { + if (!MalList.is(arg1) && !MalVector.is(arg1)) { + throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); + } + const [arg11, arg12] = arg1.list; + if (MalSymbol.is(arg11) && arg11.v === "splice-unquote") { + return new MalList([ + MalSymbol.get("concat"), + arg12, + quasiquote(new MalList(ast.list.slice(1))), + ]); + } + } + + return new MalList([ + MalSymbol.get("cons"), + quasiquote(arg1), + quasiquote(new MalList(ast.list.slice(1))), + ]); + + function isPair(ast: MalType) { + if (!MalList.is(ast) && !MalVector.is(ast)) { + return false; + } + + return 0 < ast.list.length; + } +} + +function isMacroCall(ast: MalType, env: Env): boolean { + if (!MalList.is(ast) && !MalVector.is(ast)) { + return false; + } + const s = ast.list[0]; + if (!MalSymbol.is(s)) { + return false; + } + const foundEnv = env.find(s); + if (!foundEnv) { + return false; + } + + const f = foundEnv.get(s); + if (!MalFunction.is(f)) { + return false; + } + + return f.isMacro; +} + +function macroexpand(ast: MalType, env: Env): MalType { + while (isMacroCall(ast, env)) { + if (!MalList.is(ast) && !MalVector.is(ast)) { + throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); + } + const s = ast.list[0]; + if (!MalSymbol.is(s)) { + throw new Error(`unexpected token type: ${s.type}, expected: symbol`); + } + const f = env.get(s); + if (!MalFunction.is(f)) { + throw new Error(`unexpected token type: ${f.type}, expected: function`); + } + ast = f.func(...ast.list.slice(1)); + } + + return ast; +} + +function evalAST(ast: MalType, env: Env): MalType { + switch (ast.type) { + case "symbol": + const f = env.get(ast); + if (!f) { + throw new Error(`unknown symbol: ${ast.v}`); + } + return f; + case "list": + return new MalList(ast.list.map(ast => evalSexp(ast, env))); + case "vector": + return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + case "hash-map": + const list: MalType[] = []; + for (const [key, value] of ast.map) { + list.push(key); + list.push(evalSexp(value, env)); + } + return new MalHashMap(list); + default: + return ast; + } +} + +function evalSexp(ast: MalType, env: Env): MalType { + loop: while (true) { + if (ast.type !== "list") { + return evalAST(ast, env); + } + + ast = macroexpand(ast, env); + if (ast.type !== "list" && ast.type !== "vector") { + return evalAST(ast, env); + } + + if (ast.list.length === 0) { + return ast; + } + const first = ast.list[0]; + switch (first.type) { + case "symbol": + switch (first.v) { + case "def!": { + const [, key, value] = ast.list; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!value) { + throw new Error(`unexpected syntax`); + } + return env.set(key, evalSexp(value, env)) + } + case "let*": { + env = new Env(env); + const pairs = ast.list[1]; + if (!MalList.is(pairs) && !MalVector.is(pairs)) { + throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); + } + for (let i = 0; i < pairs.list.length; i += 2) { + const key = pairs.list[i]; + const value = pairs.list[i + 1]; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!key || !value) { + throw new Error(`unexpected syntax`); + } + + env.set(key, evalSexp(value, env)); + } + ast = ast.list[2]; + continue loop; + } + case "quote": { + return ast.list[1]; + } + case "quasiquote": { + ast = quasiquote(ast.list[1]); + continue loop; + } + case "defmacro!": { + const [, key, value] = ast.list; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!value) { + throw new Error(`unexpected syntax`); + } + const f = evalSexp(value, env); + if (!MalFunction.is(f)) { + throw new Error(`unexpected token type: ${f.type}, expected: function`); + } + f.isMacro = true; + return env.set(key, f); + } + case "macroexpand": { + return macroexpand(ast.list[1], env); + } + case "do": { + const [, ...list] = ast.list; + const ret = evalAST(new MalList(list), env); + if (!MalList.is(ret) && !MalVector.is(ret)) { + throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); + } + ast = ret.list[ret.list.length - 1]; + continue loop; + } + case "if": { + const [, cond, thenExpr, elseExrp] = ast.list; + const ret = evalSexp(cond, env); + let b = true; + if (MalBoolean.is(ret) && !ret.v) { + b = false; + } else if (MalNull.is(ret)) { + b = false; + } + if (b) { + ast = thenExpr; + } else if (elseExrp) { + ast = elseExrp; + } else { + ast = MalNull.instance; + } + continue loop; + } + case "fn*": { + const [, params, bodyAst] = ast.list; + if (!MalList.is(params) && !MalVector.is(params)) { + throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); + } + const symbols = params.list.map(param => { + if (!MalSymbol.is(param)) { + throw new Error(`unexpected return type: ${param.type}, expected: symbol`); + } + return param; + }); + return MalFunction.fromLisp(evalSexp, env, symbols, bodyAst); + } + } + } + const result = evalAST(ast, env); + if (!MalList.is(result) && !MalVector.is(result)) { + throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); + } + const [f, ...args] = result.list; + if (!MalFunction.is(f)) { + throw new Error(`unexpected token: ${f.type}, expected: function`); + } + if (f.ast) { + ast = f.ast; + env = f.newEnv(args); + continue loop; + } + + return f.func(...args); + } +} + +function print(exp: MalType): string { + return prStr(exp); +} + +const replEnv = new Env(); +for (const [key, value] of core.ns) { + replEnv.set(key, value); +} +replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { + if (!ast) { + throw new Error(`undefined argument`); + } + return evalSexp(ast, replEnv); +})); + +replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); + +// core.mal: defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))`); +rep(`(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))`); +rep('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))'); + +if (typeof process !== "undefined" && 2 < process.argv.length) { + replEnv.set(MalSymbol.get("*ARGV*"), new MalList(process.argv.slice(3).map(s => new MalString(s)))); + rep(`(load-file "${process.argv[2]}")`); + process.exit(0); +} + +function rep(str: string): string { + return print(evalSexp(read(str), replEnv)); +} + +while (true) { + const line = readline("user> "); + if (line == null) { + break; + } + if (line === "") { + continue; + } + try { + console.log(rep(line)); + } catch (e) { + const err: Error = e; + console.error(err.message); + } +} diff --git a/ts/types.ts b/ts/types.ts index 3838db69b0..e3d5143a6a 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -175,6 +175,7 @@ export class MalFunction { f.env = env; f.params = params; f.ast = bodyAst; + f.isMacro = false; return f; @@ -191,6 +192,8 @@ export class MalFunction { static fromBootstrap(func: MalF): MalFunction { const f = new MalFunction(); f.func = func; + f.isMacro = false; + return f; } @@ -200,6 +203,7 @@ export class MalFunction { ast: MalType; env: Env; params: MalSymbol[]; + isMacro: boolean; private constructor() { } From 10f8aa846cbcacf6b23c2499bff4cbfa8caa64cf Mon Sep 17 00:00:00 2001 From: vvakame Date: Sat, 25 Feb 2017 00:21:30 +0900 Subject: [PATCH 0298/2308] TypeScript: step 9 --- ts/core.ts | 121 +++++++++++++++- ts/env.ts | 4 +- ts/package.json | 5 +- ts/printer.ts | 4 +- ts/reader.ts | 2 +- ts/step2_eval.ts | 2 +- ts/step3_env.ts | 2 +- ts/step4_if_fn_do.ts | 2 +- ts/step5_tco.ts | 2 +- ts/step6_file.ts | 2 +- ts/step7_quote.ts | 2 +- ts/step8_macros.ts | 2 +- ts/step9_try.ts | 320 +++++++++++++++++++++++++++++++++++++++++++ ts/types.ts | 127 ++++++++++++++++- 14 files changed, 578 insertions(+), 19 deletions(-) create mode 100644 ts/step9_try.ts diff --git a/ts/core.ts b/ts/core.ts index 072477e3ac..64b7745efd 100644 --- a/ts/core.ts +++ b/ts/core.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; -import { MalType, MalSymbol, MalFunction, MalNull, MalList, MalVector, MalBoolean, MalNumber, MalString, MalAtom, equals } from "./types"; +import { MalType, MalSymbol, MalFunction, MalNull, MalList, MalVector, MalBoolean, MalNumber, MalString, MalKeyword, MalHashMap, MalAtom, equals } from "./types"; import { readStr } from "./reader"; import { prStr } from "./printer"; @@ -139,6 +139,31 @@ export const ns: Map = (() => { atom.v = f.func(...[atom.v].concat(args)); return atom.v; }, + throw(v: MalType): MalType { + throw v; + }, + apply(f: MalType, ...list: MalType[]) { + if (!MalFunction.is(f)) { + throw new Error(`unexpected symbol: ${f.type}, expected: function`); + } + + const tail = list[list.length - 1]; + if (!MalList.is(tail) && !MalVector.is(tail)) { + throw new Error(`unexpected symbol: ${tail.type}, expected: list or vector`); + } + const args = list.slice(0, -1).concat(tail.list); + return f.func(...args); + }, + map(f: MalType, list: MalType) { + if (!MalFunction.is(f)) { + throw new Error(`unexpected symbol: ${f.type}, expected: function`); + } + if (!MalList.is(list) && !MalVector.is(list)) { + throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`); + } + + return new MalList(list.list.map(v => f.func(v))); + }, "+"(a: MalType, b: MalType): MalNumber { if (!MalNumber.is(a)) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); @@ -222,6 +247,100 @@ export const ns: Map = (() => { return new MalBoolean(a.v >= b.v); }, + "nil?"(v: MalType) { + return new MalBoolean(MalNull.is(v)); + }, + "true?"(v: MalType) { + return new MalBoolean(MalBoolean.is(v) && v.v); + }, + "false?"(v: MalType) { + return new MalBoolean(MalBoolean.is(v) && !v.v); + }, + "symbol?"(v: MalType) { + return new MalBoolean(MalSymbol.is(v)); + }, + symbol(v: MalType) { + if (!MalString.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: string`); + } + return MalSymbol.get(v.v); + }, + keyword(v: MalType) { + if (!MalString.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: string`); + } + return MalKeyword.get(v.v); + }, + "keyword?"(v: MalType) { + return new MalBoolean(MalKeyword.is(v)); + }, + vector(...args: MalType[]): MalVector { + return new MalVector(args); + }, + "vector?"(v: MalType): MalBoolean { + return new MalBoolean(MalVector.is(v)); + }, + "hash-map"(...args: MalType[]) { + return new MalHashMap(args); + }, + "map?"(v: MalType): MalBoolean { + return new MalBoolean(MalHashMap.is(v)); + }, + assoc(v: MalType, ...args: MalType[]) { + if (!MalHashMap.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`); + } + return v.assoc(args); + }, + dissoc(v: MalType, ...args: MalType[]) { + if (!MalHashMap.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`); + } + return v.dissoc(args); + }, + get(v: MalType, key: MalType) { + if (MalNull.is(v)) { + return MalNull.instance; + } + if (!MalHashMap.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`); + } + if (!MalString.is(key) && !MalKeyword.is(key)) { + throw new Error(`unexpected symbol: ${key.type}, expected: string or keyword`); + } + + return v.get(key) || MalNull.instance; + }, + "contains?"(v: MalType, key: MalType) { + if (MalNull.is(v)) { + return MalNull.instance; + } + if (!MalHashMap.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`); + } + if (!MalString.is(key) && !MalKeyword.is(key)) { + throw new Error(`unexpected symbol: ${key.type}, expected: string or keyword`); + } + + return new MalBoolean(v.has(key)); + }, + keys(v: MalType) { + if (!MalHashMap.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`); + } + + return new MalList([...v.keys()]); + }, + vals(v: MalType) { + if (!MalHashMap.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`); + } + + return new MalList([...v.vals()]); + }, + "sequential?"(v: MalType) { + return new MalBoolean(MalList.is(v) || MalVector.is(v)); + }, }; const map = new Map(); diff --git a/ts/env.ts b/ts/env.ts index 97bbccc963..91f87838aa 100644 --- a/ts/env.ts +++ b/ts/env.ts @@ -35,12 +35,12 @@ export class Env { get(key: MalSymbol): MalType { const env = this.find(key); if (!env) { - throw new Error(`${key.v} not found`); + throw new Error(`'${key.v}' not found`); } const v = env.data.get(key); if (!v) { - throw new Error(`${key.v} is not exists`); + throw new Error(`'${key.v}' not found`); } return v; diff --git a/ts/package.json b/ts/package.json index 00fd8f848a..b52d49c34c 100644 --- a/ts/package.json +++ b/ts/package.json @@ -5,7 +5,7 @@ "description": "Make a Lisp (mal) language implemented in TypeScript", "scripts": { "build": "tsfmt -r && tsc -p ./", - "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3 && npm run test:step4 && npm run test:step5 && npm run test:step6 && npm run test:step7 && npm run test:step8", + "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3 && npm run test:step4 && npm run test:step5 && npm run test:step6 && npm run test:step7 && npm run test:step8 && npm run test:step9", "test:step0": "cd .. && make 'test^ts^step0'", "test:step1": "cd .. && make 'test^ts^step1'", "test:step2": "cd .. && make 'test^ts^step2'", @@ -14,7 +14,8 @@ "test:step5": "cd .. && make 'test^ts^step5'", "test:step6": "cd .. && make 'test^ts^step6'", "test:step7": "cd .. && make 'test^ts^step7'", - "test:step8": "cd .. && make 'test^ts^step8'" + "test:step8": "cd .. && make 'test^ts^step8'", + "test:step9": "cd .. && make 'test^ts^step9'" }, "dependencies": { "ffi": "^2.2.0" diff --git a/ts/printer.ts b/ts/printer.ts index 81d9c403c8..b97f379bff 100644 --- a/ts/printer.ts +++ b/ts/printer.ts @@ -8,7 +8,7 @@ export function prStr(v: MalType, printReadably = true): string { return `[${v.list.map(v => prStr(v, printReadably)).join(" ")}]`; case "hash-map": let result = "{"; - for (const [key, value] of v.map) { + for (const [key, value] of v.entries()) { if (result !== "{") { result += " "; } @@ -33,7 +33,7 @@ export function prStr(v: MalType, printReadably = true): string { case "null": return "nil"; case "keyword": - return `:${v.v.substr(1)}`; + return `:${v.v}`; case "function": return "#"; case "atom": diff --git a/ts/reader.ts b/ts/reader.ts index b1b5f99835..c86648677a 100644 --- a/ts/reader.ts +++ b/ts/reader.ts @@ -130,7 +130,7 @@ function readAtom(reader: Reader): MalType { return new MalString(v); } if (token[0] === ":") { - return new MalKeyword(token.substr(1)); + return MalKeyword.get(token.substr(1)); } switch (token) { case "nil": diff --git a/ts/step2_eval.ts b/ts/step2_eval.ts index 31c6b699bb..26d43b37e3 100644 --- a/ts/step2_eval.ts +++ b/ts/step2_eval.ts @@ -26,7 +26,7 @@ function evalAST(ast: MalType, env: MalEnvironment): MalType { return new MalVector(ast.list.map(ast => evalSexp(ast, env))); case "hash-map": const list: MalType[] = []; - for (const [key, value] of ast.map) { + for (const [key, value] of ast.entries()) { list.push(key); list.push(evalSexp(value, env)); } diff --git a/ts/step3_env.ts b/ts/step3_env.ts index b129ecb1eb..cb7bab33bc 100644 --- a/ts/step3_env.ts +++ b/ts/step3_env.ts @@ -23,7 +23,7 @@ function evalAST(ast: MalType, env: Env): MalType { return new MalVector(ast.list.map(ast => evalSexp(ast, env))); case "hash-map": const list: MalType[] = []; - for (const [key, value] of ast.map) { + for (const [key, value] of ast.entries()) { list.push(key); list.push(evalSexp(value, env)); } diff --git a/ts/step4_if_fn_do.ts b/ts/step4_if_fn_do.ts index 86f2fca3af..5863e71719 100644 --- a/ts/step4_if_fn_do.ts +++ b/ts/step4_if_fn_do.ts @@ -24,7 +24,7 @@ function evalAST(ast: MalType, env: Env): MalType { return new MalVector(ast.list.map(ast => evalSexp(ast, env))); case "hash-map": const list: MalType[] = []; - for (const [key, value] of ast.map) { + for (const [key, value] of ast.entries()) { list.push(key); list.push(evalSexp(value, env)); } diff --git a/ts/step5_tco.ts b/ts/step5_tco.ts index c240d91391..934d63d7a2 100644 --- a/ts/step5_tco.ts +++ b/ts/step5_tco.ts @@ -24,7 +24,7 @@ function evalAST(ast: MalType, env: Env): MalType { return new MalVector(ast.list.map(ast => evalSexp(ast, env))); case "hash-map": const list: MalType[] = []; - for (const [key, value] of ast.map) { + for (const [key, value] of ast.entries()) { list.push(key); list.push(evalSexp(value, env)); } diff --git a/ts/step6_file.ts b/ts/step6_file.ts index a29ec10044..fce86f984f 100644 --- a/ts/step6_file.ts +++ b/ts/step6_file.ts @@ -24,7 +24,7 @@ function evalAST(ast: MalType, env: Env): MalType { return new MalVector(ast.list.map(ast => evalSexp(ast, env))); case "hash-map": const list: MalType[] = []; - for (const [key, value] of ast.map) { + for (const [key, value] of ast.entries()) { list.push(key); list.push(evalSexp(value, env)); } diff --git a/ts/step7_quote.ts b/ts/step7_quote.ts index 2f9749e3b4..1b586fafdd 100644 --- a/ts/step7_quote.ts +++ b/ts/step7_quote.ts @@ -64,7 +64,7 @@ function evalAST(ast: MalType, env: Env): MalType { return new MalVector(ast.list.map(ast => evalSexp(ast, env))); case "hash-map": const list: MalType[] = []; - for (const [key, value] of ast.map) { + for (const [key, value] of ast.entries()) { list.push(key); list.push(evalSexp(value, env)); } diff --git a/ts/step8_macros.ts b/ts/step8_macros.ts index 8d09e1a583..0f58a12ffe 100644 --- a/ts/step8_macros.ts +++ b/ts/step8_macros.ts @@ -104,7 +104,7 @@ function evalAST(ast: MalType, env: Env): MalType { return new MalVector(ast.list.map(ast => evalSexp(ast, env))); case "hash-map": const list: MalType[] = []; - for (const [key, value] of ast.map) { + for (const [key, value] of ast.entries()) { list.push(key); list.push(evalSexp(value, env)); } diff --git a/ts/step9_try.ts b/ts/step9_try.ts new file mode 100644 index 0000000000..527713323e --- /dev/null +++ b/ts/step9_try.ts @@ -0,0 +1,320 @@ +import { readline } from "./node_readline"; + +import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST } from "./types"; +import { Env } from "./env"; +import * as core from "./core"; +import { readStr } from "./reader"; +import { prStr } from "./printer"; + +function read(str: string): MalType { + return readStr(str); +} + +function quasiquote(ast: MalType): MalType { + if (!isPair(ast)) { + return new MalList([MalSymbol.get("quote"), ast]); + } + if (!MalList.is(ast) && !MalVector.is(ast)) { + throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); + } + const [arg1, arg2] = ast.list; + if (MalSymbol.is(arg1) && arg1.v === "unquote") { + return arg2; + } + if (isPair(arg1)) { + if (!MalList.is(arg1) && !MalVector.is(arg1)) { + throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); + } + const [arg11, arg12] = arg1.list; + if (MalSymbol.is(arg11) && arg11.v === "splice-unquote") { + return new MalList([ + MalSymbol.get("concat"), + arg12, + quasiquote(new MalList(ast.list.slice(1))), + ]); + } + } + + return new MalList([ + MalSymbol.get("cons"), + quasiquote(arg1), + quasiquote(new MalList(ast.list.slice(1))), + ]); + + function isPair(ast: MalType) { + if (!MalList.is(ast) && !MalVector.is(ast)) { + return false; + } + + return 0 < ast.list.length; + } +} + +function isMacroCall(ast: MalType, env: Env): boolean { + if (!MalList.is(ast) && !MalVector.is(ast)) { + return false; + } + const s = ast.list[0]; + if (!MalSymbol.is(s)) { + return false; + } + const foundEnv = env.find(s); + if (!foundEnv) { + return false; + } + + const f = foundEnv.get(s); + if (!MalFunction.is(f)) { + return false; + } + + return f.isMacro; +} + +function macroexpand(ast: MalType, env: Env): MalType { + while (isMacroCall(ast, env)) { + if (!MalList.is(ast) && !MalVector.is(ast)) { + throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); + } + const s = ast.list[0]; + if (!MalSymbol.is(s)) { + throw new Error(`unexpected token type: ${s.type}, expected: symbol`); + } + const f = env.get(s); + if (!MalFunction.is(f)) { + throw new Error(`unexpected token type: ${f.type}, expected: function`); + } + ast = f.func(...ast.list.slice(1)); + } + + return ast; +} + +function evalAST(ast: MalType, env: Env): MalType { + switch (ast.type) { + case "symbol": + const f = env.get(ast); + if (!f) { + throw new Error(`unknown symbol: ${ast.v}`); + } + return f; + case "list": + return new MalList(ast.list.map(ast => evalSexp(ast, env))); + case "vector": + return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + case "hash-map": + const list: MalType[] = []; + for (const [key, value] of ast.entries()) { + list.push(key); + list.push(evalSexp(value, env)); + } + return new MalHashMap(list); + default: + return ast; + } +} + +function evalSexp(ast: MalType, env: Env): MalType { + loop: while (true) { + if (ast.type !== "list") { + return evalAST(ast, env); + } + + ast = macroexpand(ast, env); + if (ast.type !== "list" && ast.type !== "vector") { + return evalAST(ast, env); + } + + if (ast.list.length === 0) { + return ast; + } + const first = ast.list[0]; + switch (first.type) { + case "symbol": + switch (first.v) { + case "def!": { + const [, key, value] = ast.list; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!value) { + throw new Error(`unexpected syntax`); + } + return env.set(key, evalSexp(value, env)) + } + case "let*": { + env = new Env(env); + const pairs = ast.list[1]; + if (!MalList.is(pairs) && !MalVector.is(pairs)) { + throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); + } + for (let i = 0; i < pairs.list.length; i += 2) { + const key = pairs.list[i]; + const value = pairs.list[i + 1]; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!key || !value) { + throw new Error(`unexpected syntax`); + } + + env.set(key, evalSexp(value, env)); + } + ast = ast.list[2]; + continue loop; + } + case "quote": { + return ast.list[1]; + } + case "quasiquote": { + ast = quasiquote(ast.list[1]); + continue loop; + } + case "defmacro!": { + const [, key, value] = ast.list; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!value) { + throw new Error(`unexpected syntax`); + } + const f = evalSexp(value, env); + if (!MalFunction.is(f)) { + throw new Error(`unexpected token type: ${f.type}, expected: function`); + } + f.isMacro = true; + return env.set(key, f); + } + case "macroexpand": { + return macroexpand(ast.list[1], env); + } + case "try*": { + try { + return evalSexp(ast.list[1], env); + } catch (e) { + const catchBody = ast.list[2]; + if (!MalList.is(catchBody) && !MalVector.is(catchBody)) { + throw new Error(`unexpected return type: ${catchBody.type}, expected: list or vector`); + } + const catchSymbol = catchBody.list[0]; + if (MalSymbol.is(catchSymbol) && catchSymbol.v === "catch*") { + const errorSymbol = catchBody.list[1]; + if (!MalSymbol.is(errorSymbol)) { + throw new Error(`unexpected return type: ${errorSymbol.type}, expected: symbol`); + } + if (!isAST(e)) { + e = new MalString((e as Error).message); + } + return evalSexp(catchBody.list[2], new Env(env, [errorSymbol], [e])); + } + throw e; + } + } + case "do": { + const [, ...list] = ast.list; + const ret = evalAST(new MalList(list), env); + if (!MalList.is(ret) && !MalVector.is(ret)) { + throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); + } + ast = ret.list[ret.list.length - 1]; + continue loop; + } + case "if": { + const [, cond, thenExpr, elseExrp] = ast.list; + const ret = evalSexp(cond, env); + let b = true; + if (MalBoolean.is(ret) && !ret.v) { + b = false; + } else if (MalNull.is(ret)) { + b = false; + } + if (b) { + ast = thenExpr; + } else if (elseExrp) { + ast = elseExrp; + } else { + ast = MalNull.instance; + } + continue loop; + } + case "fn*": { + const [, params, bodyAst] = ast.list; + if (!MalList.is(params) && !MalVector.is(params)) { + throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); + } + const symbols = params.list.map(param => { + if (!MalSymbol.is(param)) { + throw new Error(`unexpected return type: ${param.type}, expected: symbol`); + } + return param; + }); + return MalFunction.fromLisp(evalSexp, env, symbols, bodyAst); + } + } + } + const result = evalAST(ast, env); + if (!MalList.is(result) && !MalVector.is(result)) { + throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); + } + const [f, ...args] = result.list; + if (!MalFunction.is(f)) { + throw new Error(`unexpected token: ${f.type}, expected: function`); + } + if (f.ast) { + ast = f.ast; + env = f.newEnv(args); + continue loop; + } + + return f.func(...args); + } +} + +function print(exp: MalType): string { + return prStr(exp); +} + +const replEnv = new Env(); +for (const [key, value] of core.ns) { + replEnv.set(key, value); +} +replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { + if (!ast) { + throw new Error(`undefined argument`); + } + return evalSexp(ast, replEnv); +})); + +replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); + +// core.mal: defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))`); +rep(`(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))`); +rep('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))'); + +if (typeof process !== "undefined" && 2 < process.argv.length) { + replEnv.set(MalSymbol.get("*ARGV*"), new MalList(process.argv.slice(3).map(s => new MalString(s)))); + rep(`(load-file "${process.argv[2]}")`); + process.exit(0); +} + +function rep(str: string): string { + return print(evalSexp(read(str), replEnv)); +} + +while (true) { + const line = readline("user> "); + if (line == null) { + break; + } + if (line === "") { + continue; + } + try { + console.log(rep(line)); + } catch (e) { + const err: Error = e; + console.error(err.message); + } +} diff --git a/ts/types.ts b/ts/types.ts index e3d5143a6a..e3df149d9e 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -21,6 +21,28 @@ export function equals(a: MalType, b: MalType, strict?: boolean): boolean { ) { return listEquals(a.list, b.list); } + if (MalHashMap.is(a) && MalHashMap.is(b)) { + if (a.keywordMap.size !== b.keywordMap.size) { + return false; + } + if (Object.keys(a.stringMap).length !== Object.keys(b.stringMap).length) { + return false; + } + for (const [aK, aV] of a.entries()) { + if (!MalString.is(aK) && !MalKeyword.is(aK)) { + throw new Error(`unexpected symbol: ${aK.type}, expected: string or keyword`); + } + const bV = b.get(aK); + if (MalNull.is(aV) && MalNull.is(bV)) { + continue; + } + if (!equals(aV, bV)) { + return false; + } + } + + return true; + } if ( (MalNumber.is(a) && MalNumber.is(b)) || (MalString.is(a) && MalString.is(b)) @@ -46,6 +68,10 @@ export function equals(a: MalType, b: MalType, strict?: boolean): boolean { } } +export function isAST(v: MalType): v is MalType { + return !!v.type; +} + export class MalList { static is(f: MalType): f is MalList { return f instanceof MalList; @@ -127,9 +153,22 @@ export class MalKeyword { return f instanceof MalKeyword; } + static map = new Map(); + + static get(name: string): MalKeyword { + const sym = Symbol.for(name); + let token = this.map.get(sym); + if (token) { + return token; + } + token = new MalKeyword(name); + this.map.set(sym, token); + return token; + } + type: "keyword" = "keyword"; - constructor(public v: string) { - this.v = String.fromCodePoint(0x29E) + this.v; + + private constructor(public v: string) { } } @@ -149,7 +188,9 @@ export class MalHashMap { } type: "hash-map" = "hash-map"; - map = new Map(); + stringMap: { [key: string]: MalType } = {}; + keywordMap = new Map(); + constructor(list: MalType[]) { while (list.length !== 0) { const key = list.shift()!; @@ -157,8 +198,86 @@ export class MalHashMap { if (value == null) { throw new Error("unexpected hash length"); } - this.map.set(key, value); + if (MalKeyword.is(key)) { + this.keywordMap.set(key, value); + } else if (MalString.is(key)) { + this.stringMap[key.v] = value; + } else { + throw new Error(`unexpected key symbol: ${key.type}, expected: keyword or string`); + } + } + } + + has(key: MalKeyword | MalString) { + if (MalKeyword.is(key)) { + return !!this.keywordMap.get(key); + } + return !!this.stringMap[key.v]; + } + + get(key: MalKeyword | MalString) { + if (MalKeyword.is(key)) { + return this.keywordMap.get(key) || MalNull.instance; + } + return this.stringMap[key.v] || MalNull.instance; + } + + entries(): [MalType, MalType][] { + const list: [MalType, MalType][] = []; + + for (const [k, v] of this.keywordMap) { + list.push([k, v]); } + Object.keys(this.stringMap).forEach(v => list.push([new MalString(v), this.stringMap[v]])); + + return list; + } + + keys(): MalType[] { + const list: MalType[] = []; + for (const v of this.keywordMap.keys()) { + list.push(v); + } + Object.keys(this.stringMap).forEach(v => list.push(new MalString(v))); + return list; + } + + vals(): MalType[] { + const list: MalType[] = []; + for (const v of this.keywordMap.values()) { + list.push(v); + } + Object.keys(this.stringMap).forEach(v => list.push(this.stringMap[v])); + return list; + } + + assoc(args: MalType[]): MalHashMap { + const list: MalType[] = []; + this.keywordMap.forEach((value, key) => { + list.push(key); + list.push(value); + }); + Object.keys(this.stringMap).forEach(keyStr => { + list.push(new MalString(keyStr)); + list.push(this.stringMap[keyStr]); + }); + + return new MalHashMap(list.concat(args)); + } + + dissoc(args: MalType[]): MalHashMap { + const newHashMap = this.assoc([]); + + args.forEach(arg => { + if (MalString.is(arg)) { + delete newHashMap.stringMap[arg.v]; + } else if (MalKeyword.is(arg)) { + newHashMap.keywordMap.delete(arg); + } else { + throw new Error(`unexpected symbol: ${arg.type}, expected: keyword or string`); + } + }); + return newHashMap; } } From bbddf168352541402da402d752f96ee6c4bb2541 Mon Sep 17 00:00:00 2001 From: vvakame Date: Sat, 25 Feb 2017 04:31:15 +0900 Subject: [PATCH 0299/2308] TypeScript: step A --- ts/core.ts | 63 +++++++++ ts/package.json | 5 +- ts/step5_tco.ts | 9 +- ts/step6_file.ts | 9 +- ts/step7_quote.ts | 9 +- ts/step8_macros.ts | 9 +- ts/step9_try.ts | 9 +- ts/stepA_mal.ts | 321 +++++++++++++++++++++++++++++++++++++++++++++ ts/types.ts | 82 ++++++++++++ 9 files changed, 484 insertions(+), 32 deletions(-) create mode 100644 ts/stepA_mal.ts diff --git a/ts/core.ts b/ts/core.ts index 64b7745efd..0c692efb70 100644 --- a/ts/core.ts +++ b/ts/core.ts @@ -1,11 +1,25 @@ import * as fs from "fs"; +import { readline } from "./node_readline"; + import { MalType, MalSymbol, MalFunction, MalNull, MalList, MalVector, MalBoolean, MalNumber, MalString, MalKeyword, MalHashMap, MalAtom, equals } from "./types"; import { readStr } from "./reader"; import { prStr } from "./printer"; export const ns: Map = (() => { const ns: { [symbol: string]: typeof MalFunction.prototype.func; } = { + readline(v: MalType) { + if (!MalString.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: string`); + } + + const ret = readline(v.v); + if (ret == null) { + return MalNull.instance; + } + + return new MalString(ret); + }, "pr-str"(...args: MalType[]): MalString { return new MalString(args.map(v => prStr(v, true)).join(" ")); }, @@ -247,6 +261,9 @@ export const ns: Map = (() => { return new MalBoolean(a.v >= b.v); }, + "time-ms"() { + return new MalNumber(Date.now()); + }, "nil?"(v: MalType) { return new MalBoolean(MalNull.is(v)); }, @@ -256,6 +273,9 @@ export const ns: Map = (() => { "false?"(v: MalType) { return new MalBoolean(MalBoolean.is(v) && !v.v); }, + "string?"(v: MalType) { + return new MalBoolean(MalString.is(v)); + }, "symbol?"(v: MalType) { return new MalBoolean(MalSymbol.is(v)); }, @@ -341,6 +361,49 @@ export const ns: Map = (() => { "sequential?"(v: MalType) { return new MalBoolean(MalList.is(v) || MalVector.is(v)); }, + conj(list: MalType, ...args: MalType[]) { + switch (list.type) { + case "list": + const newList = new MalList(list.list); + args.forEach(arg => newList.list.unshift(arg)); + return newList; + case "vector": + return new MalVector([...list.list, ...args]); + } + + throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`); + }, + seq(v: MalType) { + if (MalList.is(v)) { + if (v.list.length === 0) { + return MalNull.instance; + } + return v; + } + if (MalVector.is(v)) { + if (v.list.length === 0) { + return MalNull.instance; + } + return new MalList(v.list); + } + if (MalString.is(v)) { + if (v.v.length === 0) { + return MalNull.instance; + } + return new MalList(v.v.split("").map(s => new MalString(s))); + } + if (MalNull.is(v)) { + return MalNull.instance; + } + + throw new Error(`unexpected symbol: ${v.type}, expected: list or vector or string`); + }, + "with-meta"(v: MalType, m: MalType) { + return v.withMeta(m); + }, + meta(v: MalType) { + return v.meta || MalNull.instance; + }, }; const map = new Map(); diff --git a/ts/package.json b/ts/package.json index b52d49c34c..162c75250c 100644 --- a/ts/package.json +++ b/ts/package.json @@ -5,7 +5,7 @@ "description": "Make a Lisp (mal) language implemented in TypeScript", "scripts": { "build": "tsfmt -r && tsc -p ./", - "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3 && npm run test:step4 && npm run test:step5 && npm run test:step6 && npm run test:step7 && npm run test:step8 && npm run test:step9", + "test": "npm run build && npm run test:step0 && npm run test:step1 && npm run test:step2 && npm run test:step3 && npm run test:step4 && npm run test:step5 && npm run test:step6 && npm run test:step7 && npm run test:step8 && npm run test:step9 && npm run test:stepA", "test:step0": "cd .. && make 'test^ts^step0'", "test:step1": "cd .. && make 'test^ts^step1'", "test:step2": "cd .. && make 'test^ts^step2'", @@ -15,7 +15,8 @@ "test:step6": "cd .. && make 'test^ts^step6'", "test:step7": "cd .. && make 'test^ts^step7'", "test:step8": "cd .. && make 'test^ts^step8'", - "test:step9": "cd .. && make 'test^ts^step9'" + "test:step9": "cd .. && make 'test^ts^step9'", + "test:stepA": "cd .. && make 'test^ts^stepA'" }, "dependencies": { "ffi": "^2.2.0" diff --git a/ts/step5_tco.ts b/ts/step5_tco.ts index 934d63d7a2..b3f12efff4 100644 --- a/ts/step5_tco.ts +++ b/ts/step5_tco.ts @@ -78,12 +78,9 @@ function evalSexp(ast: MalType, env: Env): MalType { continue loop; } case "do": { - const [, ...list] = ast.list; - const ret = evalAST(new MalList(list), env); - if (!MalList.is(ret) && !MalVector.is(ret)) { - throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); - } - ast = ret.list[ret.list.length - 1]; + const list = ast.list.slice(1, -1); + evalAST(new MalList(list), env); + ast = ast.list[ast.list.length - 1]; continue loop; } case "if": { diff --git a/ts/step6_file.ts b/ts/step6_file.ts index fce86f984f..43d827285f 100644 --- a/ts/step6_file.ts +++ b/ts/step6_file.ts @@ -78,12 +78,9 @@ function evalSexp(ast: MalType, env: Env): MalType { continue loop; } case "do": { - const [, ...list] = ast.list; - const ret = evalAST(new MalList(list), env); - if (!MalList.is(ret) && !MalVector.is(ret)) { - throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); - } - ast = ret.list[ret.list.length - 1]; + const list = ast.list.slice(1, -1); + evalAST(new MalList(list), env); + ast = ast.list[ast.list.length - 1]; continue loop; } case "if": { diff --git a/ts/step7_quote.ts b/ts/step7_quote.ts index 1b586fafdd..ec67684a81 100644 --- a/ts/step7_quote.ts +++ b/ts/step7_quote.ts @@ -125,12 +125,9 @@ function evalSexp(ast: MalType, env: Env): MalType { continue loop; } case "do": { - const [, ...list] = ast.list; - const ret = evalAST(new MalList(list), env); - if (!MalList.is(ret) && !MalVector.is(ret)) { - throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); - } - ast = ret.list[ret.list.length - 1]; + const list = ast.list.slice(1, -1); + evalAST(new MalList(list), env); + ast = ast.list[ast.list.length - 1]; continue loop; } case "if": { diff --git a/ts/step8_macros.ts b/ts/step8_macros.ts index 0f58a12ffe..0690ea9bf3 100644 --- a/ts/step8_macros.ts +++ b/ts/step8_macros.ts @@ -189,12 +189,9 @@ function evalSexp(ast: MalType, env: Env): MalType { return macroexpand(ast.list[1], env); } case "do": { - const [, ...list] = ast.list; - const ret = evalAST(new MalList(list), env); - if (!MalList.is(ret) && !MalVector.is(ret)) { - throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); - } - ast = ret.list[ret.list.length - 1]; + const list = ast.list.slice(1, -1); + evalAST(new MalList(list), env); + ast = ast.list[ast.list.length - 1]; continue loop; } case "if": { diff --git a/ts/step9_try.ts b/ts/step9_try.ts index 527713323e..3ed673ce05 100644 --- a/ts/step9_try.ts +++ b/ts/step9_try.ts @@ -211,12 +211,9 @@ function evalSexp(ast: MalType, env: Env): MalType { } } case "do": { - const [, ...list] = ast.list; - const ret = evalAST(new MalList(list), env); - if (!MalList.is(ret) && !MalVector.is(ret)) { - throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); - } - ast = ret.list[ret.list.length - 1]; + const list = ast.list.slice(1, -1); + evalAST(new MalList(list), env); + ast = ast.list[ast.list.length - 1]; continue loop; } case "if": { diff --git a/ts/stepA_mal.ts b/ts/stepA_mal.ts new file mode 100644 index 0000000000..e3494fbd20 --- /dev/null +++ b/ts/stepA_mal.ts @@ -0,0 +1,321 @@ +import { readline } from "./node_readline"; + +import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST } from "./types"; +import { Env } from "./env"; +import * as core from "./core"; +import { readStr } from "./reader"; +import { prStr } from "./printer"; + +function read(str: string): MalType { + return readStr(str); +} + +function quasiquote(ast: MalType): MalType { + if (!isPair(ast)) { + return new MalList([MalSymbol.get("quote"), ast]); + } + if (!MalList.is(ast) && !MalVector.is(ast)) { + throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); + } + const [arg1, arg2] = ast.list; + if (MalSymbol.is(arg1) && arg1.v === "unquote") { + return arg2; + } + if (isPair(arg1)) { + if (!MalList.is(arg1) && !MalVector.is(arg1)) { + throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); + } + const [arg11, arg12] = arg1.list; + if (MalSymbol.is(arg11) && arg11.v === "splice-unquote") { + return new MalList([ + MalSymbol.get("concat"), + arg12, + quasiquote(new MalList(ast.list.slice(1))), + ]); + } + } + + return new MalList([ + MalSymbol.get("cons"), + quasiquote(arg1), + quasiquote(new MalList(ast.list.slice(1))), + ]); + + function isPair(ast: MalType) { + if (!MalList.is(ast) && !MalVector.is(ast)) { + return false; + } + + return 0 < ast.list.length; + } +} + +function isMacroCall(ast: MalType, env: Env): boolean { + if (!MalList.is(ast) && !MalVector.is(ast)) { + return false; + } + const s = ast.list[0]; + if (!MalSymbol.is(s)) { + return false; + } + const foundEnv = env.find(s); + if (!foundEnv) { + return false; + } + + const f = foundEnv.get(s); + if (!MalFunction.is(f)) { + return false; + } + + return f.isMacro; +} + +function macroexpand(ast: MalType, env: Env): MalType { + while (isMacroCall(ast, env)) { + if (!MalList.is(ast) && !MalVector.is(ast)) { + throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); + } + const s = ast.list[0]; + if (!MalSymbol.is(s)) { + throw new Error(`unexpected token type: ${s.type}, expected: symbol`); + } + const f = env.get(s); + if (!MalFunction.is(f)) { + throw new Error(`unexpected token type: ${f.type}, expected: function`); + } + ast = f.func(...ast.list.slice(1)); + } + + return ast; +} + +function evalAST(ast: MalType, env: Env): MalType { + switch (ast.type) { + case "symbol": + const f = env.get(ast); + if (!f) { + throw new Error(`unknown symbol: ${ast.v}`); + } + return f; + case "list": + return new MalList(ast.list.map(ast => evalSexp(ast, env))); + case "vector": + return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + case "hash-map": + const list: MalType[] = []; + for (const [key, value] of ast.entries()) { + list.push(key); + list.push(evalSexp(value, env)); + } + return new MalHashMap(list); + default: + return ast; + } +} + +function evalSexp(ast: MalType, env: Env): MalType { + loop: while (true) { + if (ast.type !== "list") { + return evalAST(ast, env); + } + + ast = macroexpand(ast, env); + if (ast.type !== "list" && ast.type !== "vector") { + return evalAST(ast, env); + } + + if (ast.list.length === 0) { + return ast; + } + const first = ast.list[0]; + switch (first.type) { + case "symbol": + switch (first.v) { + case "def!": { + const [, key, value] = ast.list; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!value) { + throw new Error(`unexpected syntax`); + } + return env.set(key, evalSexp(value, env)) + } + case "let*": { + env = new Env(env); + const pairs = ast.list[1]; + if (!MalList.is(pairs) && !MalVector.is(pairs)) { + throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); + } + for (let i = 0; i < pairs.list.length; i += 2) { + const key = pairs.list[i]; + const value = pairs.list[i + 1]; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!key || !value) { + throw new Error(`unexpected syntax`); + } + + env.set(key, evalSexp(value, env)); + } + ast = ast.list[2]; + continue loop; + } + case "quote": { + return ast.list[1]; + } + case "quasiquote": { + ast = quasiquote(ast.list[1]); + continue loop; + } + case "defmacro!": { + const [, key, value] = ast.list; + if (!MalSymbol.is(key)) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } + if (!value) { + throw new Error(`unexpected syntax`); + } + const f = evalSexp(value, env); + if (!MalFunction.is(f)) { + throw new Error(`unexpected token type: ${f.type}, expected: function`); + } + f.isMacro = true; + return env.set(key, f); + } + case "macroexpand": { + return macroexpand(ast.list[1], env); + } + case "try*": { + try { + return evalSexp(ast.list[1], env); + } catch (e) { + const catchBody = ast.list[2]; + if (!MalList.is(catchBody) && !MalVector.is(catchBody)) { + throw new Error(`unexpected return type: ${catchBody.type}, expected: list or vector`); + } + const catchSymbol = catchBody.list[0]; + if (MalSymbol.is(catchSymbol) && catchSymbol.v === "catch*") { + const errorSymbol = catchBody.list[1]; + if (!MalSymbol.is(errorSymbol)) { + throw new Error(`unexpected return type: ${errorSymbol.type}, expected: symbol`); + } + if (!isAST(e)) { + e = new MalString((e as Error).message); + } + return evalSexp(catchBody.list[2], new Env(env, [errorSymbol], [e])); + } + throw e; + } + } + case "do": { + const list = ast.list.slice(1, -1); + evalAST(new MalList(list), env); + ast = ast.list[ast.list.length - 1]; + continue loop; + } + case "if": { + const [, cond, thenExpr, elseExrp] = ast.list; + const ret = evalSexp(cond, env); + let b = true; + if (MalBoolean.is(ret) && !ret.v) { + b = false; + } else if (MalNull.is(ret)) { + b = false; + } + if (b) { + ast = thenExpr; + } else if (elseExrp) { + ast = elseExrp; + } else { + ast = MalNull.instance; + } + continue loop; + } + case "fn*": { + const [, params, bodyAst] = ast.list; + if (!MalList.is(params) && !MalVector.is(params)) { + throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); + } + const symbols = params.list.map(param => { + if (!MalSymbol.is(param)) { + throw new Error(`unexpected return type: ${param.type}, expected: symbol`); + } + return param; + }); + return MalFunction.fromLisp(evalSexp, env, symbols, bodyAst); + } + } + } + const result = evalAST(ast, env); + if (!MalList.is(result) && !MalVector.is(result)) { + throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); + } + const [f, ...args] = result.list; + if (!MalFunction.is(f)) { + throw new Error(`unexpected token: ${f.type}, expected: function`); + } + if (f.ast) { + ast = f.ast; + env = f.newEnv(args); + continue loop; + } + + return f.func(...args); + } +} + +function print(exp: MalType): string { + return prStr(exp); +} + +const replEnv = new Env(); +for (const [key, value] of core.ns) { + replEnv.set(key, value); +} +replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { + if (!ast) { + throw new Error(`undefined argument`); + } + return evalSexp(ast, replEnv); +})); + +replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); + +// core.mal: defined using the language itself +rep(`(def! *host-language* "TypeScript")`); +rep("(def! not (fn* (a) (if a false true)))"); +rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))`); +rep(`(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))`); +rep("(def! *gensym-counter* (atom 0))"); +rep("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))"); +rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))"); + +if (typeof process !== "undefined" && 2 < process.argv.length) { + replEnv.set(MalSymbol.get("*ARGV*"), new MalList(process.argv.slice(3).map(s => new MalString(s)))); + rep(`(load-file "${process.argv[2]}")`); + process.exit(0); +} + +function rep(str: string): string { + return print(evalSexp(read(str), replEnv)); +} + +rep(`(println (str "Mal [" *host-language* "]"))`); +while (true) { + const line = readline("user> "); + if (line == null) { + break; + } + if (line === "") { + continue; + } + try { + console.log(rep(line)); + } catch (e) { + const err: Error = e; + console.error(err.message); + } +} diff --git a/ts/types.ts b/ts/types.ts index e3df149d9e..39e49c676f 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -78,9 +78,16 @@ export class MalList { } type: "list" = "list"; + meta?: MalType; constructor(public list: MalType[]) { } + + withMeta(meta: MalType) { + const v = new MalList(this.list); + v.meta = meta; + return v; + } } export class MalNumber { @@ -89,8 +96,16 @@ export class MalNumber { } type: "number" = "number"; + meta?: MalType; + constructor(public v: number) { } + + withMeta(meta: MalType) { + const v = new MalNumber(this.v); + v.meta = meta; + return v; + } } export class MalString { @@ -99,8 +114,16 @@ export class MalString { } type: "string" = "string"; + meta?: MalType; + constructor(public v: string) { } + + withMeta(meta: MalType) { + const v = new MalString(this.v); + v.meta = meta; + return v; + } } export class MalNull { @@ -109,9 +132,15 @@ export class MalNull { } static instance = new MalNull(); + type: "null" = "null"; + meta?: MalType; private constructor() { } + + withMeta(_meta: MalType): MalNull { + throw new Error(`not supported`); + } } export class MalBoolean { @@ -120,8 +149,16 @@ export class MalBoolean { } type: "boolean" = "boolean"; + meta?: MalType; + constructor(public v: boolean) { } + + withMeta(meta: MalType) { + const v = new MalBoolean(this.v); + v.meta = meta; + return v; + } } export class MalSymbol { @@ -143,9 +180,14 @@ export class MalSymbol { } type: "symbol" = "symbol"; + meta?: MalType; private constructor(public v: string) { } + + withMeta(_meta: MalType): MalSymbol { + throw new Error(`not supported`); + } } export class MalKeyword { @@ -167,9 +209,14 @@ export class MalKeyword { } type: "keyword" = "keyword"; + meta?: MalType; private constructor(public v: string) { } + + withMeta(_meta: MalType): MalKeyword { + throw new Error(`not supported`); + } } export class MalVector { @@ -178,8 +225,16 @@ export class MalVector { } type: "vector" = "vector"; + meta?: MalType; + constructor(public list: MalType[]) { } + + withMeta(meta: MalType) { + const v = new MalVector(this.list); + v.meta = meta; + return v; + } } export class MalHashMap { @@ -190,6 +245,7 @@ export class MalHashMap { type: "hash-map" = "hash-map"; stringMap: { [key: string]: MalType } = {}; keywordMap = new Map(); + meta?: MalType; constructor(list: MalType[]) { while (list.length !== 0) { @@ -208,6 +264,12 @@ export class MalHashMap { } } + withMeta(meta: MalType) { + const v = this.assoc([]); + v.meta = meta; + return v; + } + has(key: MalKeyword | MalString) { if (MalKeyword.is(key)) { return !!this.keywordMap.get(key); @@ -323,9 +385,22 @@ export class MalFunction { env: Env; params: MalSymbol[]; isMacro: boolean; + meta?: MalType; private constructor() { } + withMeta(meta: MalType) { + const f = new MalFunction(); + f.func = this.func; + f.ast = this.ast; + f.env = this.env; + f.params = this.params; + f.isMacro = this.isMacro; + f.meta = meta; + + return f; + } + newEnv(args: MalType[]) { return new Env(this.env, this.params, args); } @@ -337,7 +412,14 @@ export class MalAtom { } type: "atom" = "atom"; + meta?: MalType; constructor(public v: MalType) { } + + withMeta(meta: MalType) { + const v = new MalAtom(this.v); + v.meta = meta; + return v; + } } \ No newline at end of file From eb7a2bbd83ad3c306474e8371528761280bd9731 Mon Sep 17 00:00:00 2001 From: vvakame Date: Sat, 25 Feb 2017 10:34:37 +0900 Subject: [PATCH 0300/2308] support --target es5 --- ts/step4_if_fn_do.ts | 4 ++-- ts/step5_tco.ts | 4 ++-- ts/step6_file.ts | 4 ++-- ts/step7_quote.ts | 4 ++-- ts/step8_macros.ts | 4 ++-- ts/step9_try.ts | 4 ++-- ts/stepA_mal.ts | 4 ++-- ts/tsconfig.json | 5 ++++- ts/types.ts | 14 +++++++------- 9 files changed, 25 insertions(+), 22 deletions(-) diff --git a/ts/step4_if_fn_do.ts b/ts/step4_if_fn_do.ts index 5863e71719..23166998d4 100644 --- a/ts/step4_if_fn_do.ts +++ b/ts/step4_if_fn_do.ts @@ -133,9 +133,9 @@ function print(exp: MalType): string { } const replEnv = new Env(); -for (const [key, value] of core.ns) { +core.ns.forEach((value, key) => { replEnv.set(key, value); -} +}); // core.mal: defined using the language itself rep("(def! not (fn* (a) (if a false true)))"); diff --git a/ts/step5_tco.ts b/ts/step5_tco.ts index b3f12efff4..3a702b37c1 100644 --- a/ts/step5_tco.ts +++ b/ts/step5_tco.ts @@ -139,9 +139,9 @@ function print(exp: MalType): string { } const replEnv = new Env(); -for (const [key, value] of core.ns) { +core.ns.forEach((value, key) => { replEnv.set(key, value); -} +}); // core.mal: defined using the language itself rep("(def! not (fn* (a) (if a false true)))"); diff --git a/ts/step6_file.ts b/ts/step6_file.ts index 43d827285f..c244dd7ce3 100644 --- a/ts/step6_file.ts +++ b/ts/step6_file.ts @@ -139,9 +139,9 @@ function print(exp: MalType): string { } const replEnv = new Env(); -for (const [key, value] of core.ns) { +core.ns.forEach((value, key) => { replEnv.set(key, value); -} +}); replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { if (!ast) { throw new Error(`undefined argument`); diff --git a/ts/step7_quote.ts b/ts/step7_quote.ts index ec67684a81..1f485a1a86 100644 --- a/ts/step7_quote.ts +++ b/ts/step7_quote.ts @@ -186,9 +186,9 @@ function print(exp: MalType): string { } const replEnv = new Env(); -for (const [key, value] of core.ns) { +core.ns.forEach((value, key) => { replEnv.set(key, value); -} +}); replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { if (!ast) { throw new Error(`undefined argument`); diff --git a/ts/step8_macros.ts b/ts/step8_macros.ts index 0690ea9bf3..8abacf6b97 100644 --- a/ts/step8_macros.ts +++ b/ts/step8_macros.ts @@ -250,9 +250,9 @@ function print(exp: MalType): string { } const replEnv = new Env(); -for (const [key, value] of core.ns) { +core.ns.forEach((value, key) => { replEnv.set(key, value); -} +}); replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { if (!ast) { throw new Error(`undefined argument`); diff --git a/ts/step9_try.ts b/ts/step9_try.ts index 3ed673ce05..c49e3be48f 100644 --- a/ts/step9_try.ts +++ b/ts/step9_try.ts @@ -272,9 +272,9 @@ function print(exp: MalType): string { } const replEnv = new Env(); -for (const [key, value] of core.ns) { +core.ns.forEach((value, key) => { replEnv.set(key, value); -} +}); replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { if (!ast) { throw new Error(`undefined argument`); diff --git a/ts/stepA_mal.ts b/ts/stepA_mal.ts index e3494fbd20..df21906805 100644 --- a/ts/stepA_mal.ts +++ b/ts/stepA_mal.ts @@ -272,9 +272,9 @@ function print(exp: MalType): string { } const replEnv = new Env(); -for (const [key, value] of core.ns) { +core.ns.forEach((value, key) => { replEnv.set(key, value); -} +}); replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { if (!ast) { throw new Error(`undefined argument`); diff --git a/ts/tsconfig.json b/ts/tsconfig.json index 5fd9128919..94a5d7816d 100644 --- a/ts/tsconfig.json +++ b/ts/tsconfig.json @@ -1,7 +1,10 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2015", + "target": "es5", + "lib": [ + "es2015" + ], "noImplicitAny": true, "noEmitOnError": true, "noImplicitReturns": true, diff --git a/ts/types.ts b/ts/types.ts index 39e49c676f..10f72adf00 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -287,9 +287,9 @@ export class MalHashMap { entries(): [MalType, MalType][] { const list: [MalType, MalType][] = []; - for (const [k, v] of this.keywordMap) { + this.keywordMap.forEach((v, k) => { list.push([k, v]); - } + }); Object.keys(this.stringMap).forEach(v => list.push([new MalString(v), this.stringMap[v]])); return list; @@ -297,18 +297,18 @@ export class MalHashMap { keys(): MalType[] { const list: MalType[] = []; - for (const v of this.keywordMap.keys()) { - list.push(v); - } + this.keywordMap.forEach((_v, k) => { + list.push(k); + }); Object.keys(this.stringMap).forEach(v => list.push(new MalString(v))); return list; } vals(): MalType[] { const list: MalType[] = []; - for (const v of this.keywordMap.values()) { + this.keywordMap.forEach(v => { list.push(v); - } + }); Object.keys(this.stringMap).forEach(v => list.push(this.stringMap[v])); return list; } From c493c4c0abea33e249cca07e205d663978a2812f Mon Sep 17 00:00:00 2001 From: vvakame Date: Sat, 25 Feb 2017 11:11:28 +0900 Subject: [PATCH 0301/2308] add Dockerfile --- ts/Dockerfile | 29 +++++++++++++++++++++++++++++ ts/Makefile | 16 +++------------- ts/mal.ts | 0 3 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 ts/Dockerfile delete mode 100644 ts/mal.ts diff --git a/ts/Dockerfile b/ts/Dockerfile new file mode 100644 index 0000000000..8926b363f5 --- /dev/null +++ b/ts/Dockerfile @@ -0,0 +1,29 @@ +FROM ubuntu:xenial +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +RUN apt-get -y install build-essential +RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - +RUN apt-get -y install nodejs +RUN ln -sf nodejs /usr/bin/node + +ENV NPM_CONFIG_CACHE /mal/.npm diff --git a/ts/Makefile b/ts/Makefile index 3af357f01d..e0ea8bb646 100644 --- a/ts/Makefile +++ b/ts/Makefile @@ -2,23 +2,13 @@ SOURCES_BASE = types.ts reader.ts printer.ts SOURCES_LISP = env.ts core.ts stepA_mal.ts SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -all: node_modules dist +all: node_modules ts node_modules: npm install -dist: mal.js mal - -%.js: %.js - ./node_modules/.bin/tsc -p ./ - -mal.js: $(SOURCES) - ./node_modules/.bin/tsc -p ./ - -mal: mal.js - echo "#!/usr/bin/env node" > $@ - cat $< >> $@ - chmod +x $@ +ts: node_modules + $(npm bin)/tsc -p ./ clean: rm -f *.js mal diff --git a/ts/mal.ts b/ts/mal.ts deleted file mode 100644 index e69de29bb2..0000000000 From 9c92462f39160940d91ea0c2679755eba59d2eb2 Mon Sep 17 00:00:00 2001 From: vvakame Date: Sat, 25 Feb 2017 11:32:06 +0900 Subject: [PATCH 0302/2308] more fitting to process's txt --- ts/step0_repl.ts | 19 +++++++++++-------- ts/step1_read_print.ts | 19 +++++++++++-------- ts/step2_eval.ts | 13 ++++++++----- ts/step3_env.ts | 25 ++++++++++++++----------- ts/step4_if_fn_do.ts | 34 +++++++++++++++++++--------------- ts/step5_tco.ts | 28 ++++++++++++++++------------ ts/step6_file.ts | 30 +++++++++++++++++------------- ts/step7_quote.ts | 31 +++++++++++++++++-------------- ts/step8_macros.ts | 37 ++++++++++++++++++++----------------- ts/step9_try.ts | 41 ++++++++++++++++++++++------------------- ts/stepA_mal.ts | 41 ++++++++++++++++++++++------------------- ts/types.ts | 4 ++-- 12 files changed, 179 insertions(+), 143 deletions(-) diff --git a/ts/step0_repl.ts b/ts/step0_repl.ts index 03c0814120..5ed8da9e18 100644 --- a/ts/step0_repl.ts +++ b/ts/step0_repl.ts @@ -1,23 +1,26 @@ import { readline } from "./node_readline"; -function read(v: string): any { +// READ +function read(str: string): any { // TODO - return v; + return str; } -function evalAST(v: any): any { +// EVAL +function evalMal(ast: any, _env?: any): any { // TODO - return v; + return ast; } -function print(v: any): string { +// PRINT +function print(exp: any): string { // TODO - return v; + return exp; } -function rep(v: string): string { +function rep(str: string): string { // TODO - return print(evalAST(read(v))); + return print(evalMal(read(str))); } while (true) { diff --git a/ts/step1_read_print.ts b/ts/step1_read_print.ts index 05494a1605..47e4e9a977 100644 --- a/ts/step1_read_print.ts +++ b/ts/step1_read_print.ts @@ -4,21 +4,24 @@ import { MalType } from "./types"; import { readStr } from "./reader"; import { prStr } from "./printer"; -function read(v: string): MalType { - return readStr(v); +// READ +function read(str: string): MalType { + return readStr(str); } -function evalAST(v: any): any { +// EVAL +function evalMal(ast: any, _env?: any): any { // TODO - return v; + return ast; } -function print(v: MalType): string { - return prStr(v); +// PRINT +function print(exp: MalType): string { + return prStr(exp); } -function rep(v: string): string { - return print(evalAST(read(v))); +function rep(str: string): string { + return print(evalMal(read(str))); } while (true) { diff --git a/ts/step2_eval.ts b/ts/step2_eval.ts index 26d43b37e3..7449f01a0f 100644 --- a/ts/step2_eval.ts +++ b/ts/step2_eval.ts @@ -4,6 +4,7 @@ import { MalType, MalNumber, MalList, MalVector, MalHashMap, MalFunction } from import { readStr } from "./reader"; import { prStr } from "./printer"; +// READ function read(str: string): MalType { return readStr(str); } @@ -21,14 +22,14 @@ function evalAST(ast: MalType, env: MalEnvironment): MalType { } return f; case "list": - return new MalList(ast.list.map(ast => evalSexp(ast, env))); + return new MalList(ast.list.map(ast => evalMal(ast, env))); case "vector": - return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + return new MalVector(ast.list.map(ast => evalMal(ast, env))); case "hash-map": const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); - list.push(evalSexp(value, env)); + list.push(evalMal(value, env)); } return new MalHashMap(list); default: @@ -36,7 +37,8 @@ function evalAST(ast: MalType, env: MalEnvironment): MalType { } } -function evalSexp(ast: MalType, env: MalEnvironment): MalType { +// EVAL +function evalMal(ast: MalType, env: MalEnvironment): MalType { if (ast.type !== "list") { return evalAST(ast, env); } @@ -51,6 +53,7 @@ function evalSexp(ast: MalType, env: MalEnvironment): MalType { return f.func(...args); } +// PRINT function print(exp: MalType): string { return prStr(exp); } @@ -62,7 +65,7 @@ const replEnv: MalEnvironment = { "/": MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v / b!.v)), }; function rep(str: string): string { - return print(evalSexp(read(str), replEnv)); + return print(evalMal(read(str), replEnv)); } while (true) { diff --git a/ts/step3_env.ts b/ts/step3_env.ts index cb7bab33bc..da4622adae 100644 --- a/ts/step3_env.ts +++ b/ts/step3_env.ts @@ -5,6 +5,7 @@ import { Env } from "./env"; import { readStr } from "./reader"; import { prStr } from "./printer"; +// READ function read(str: string): MalType { return readStr(str); } @@ -18,14 +19,14 @@ function evalAST(ast: MalType, env: Env): MalType { } return f; case "list": - return new MalList(ast.list.map(ast => evalSexp(ast, env))); + return new MalList(ast.list.map(ast => evalMal(ast, env))); case "vector": - return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + return new MalVector(ast.list.map(ast => evalMal(ast, env))); case "hash-map": const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); - list.push(evalSexp(value, env)); + list.push(evalMal(value, env)); } return new MalHashMap(list); default: @@ -33,7 +34,8 @@ function evalAST(ast: MalType, env: Env): MalType { } } -function evalSexp(ast: MalType, env: Env): MalType { +// EVAL +function evalMal(ast: MalType, env: Env): MalType { if (ast.type !== "list") { return evalAST(ast, env); } @@ -52,7 +54,7 @@ function evalSexp(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key as MalSymbol, evalSexp(value, env)) + return env.set(key as MalSymbol, evalMal(value, env)) } case "let*": { let letEnv = new Env(env); @@ -68,9 +70,9 @@ function evalSexp(ast: MalType, env: Env): MalType { throw new Error(`unexpected syntax`); } - letEnv.set(key as MalSymbol, evalSexp(value, letEnv)); + letEnv.set(key as MalSymbol, evalMal(value, letEnv)); } - return evalSexp(ast.list[2], letEnv); + return evalMal(ast.list[2], letEnv); } } } @@ -82,20 +84,21 @@ function evalSexp(ast: MalType, env: Env): MalType { return f.func(...args); } +// PRINT function print(exp: MalType): string { return prStr(exp); } const replEnv = new Env(); +function rep(str: string): string { + return print(evalMal(read(str), replEnv)); +} + replEnv.set(MalSymbol.get("+"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v + b!.v))); replEnv.set(MalSymbol.get("-"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v - b!.v))); replEnv.set(MalSymbol.get("*"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v * b!.v))); replEnv.set(MalSymbol.get("/"), MalFunction.fromBootstrap((a?: MalNumber, b?: MalNumber) => new MalNumber(a!.v / b!.v))); -function rep(str: string): string { - return print(evalSexp(read(str), replEnv)); -} - while (true) { const line = readline("user> "); if (line == null) { diff --git a/ts/step4_if_fn_do.ts b/ts/step4_if_fn_do.ts index 23166998d4..6ec0f9f5b7 100644 --- a/ts/step4_if_fn_do.ts +++ b/ts/step4_if_fn_do.ts @@ -6,6 +6,7 @@ import * as core from "./core"; import { readStr } from "./reader"; import { prStr } from "./printer"; +// READ function read(str: string): MalType { return readStr(str); } @@ -19,14 +20,14 @@ function evalAST(ast: MalType, env: Env): MalType { } return f; case "list": - return new MalList(ast.list.map(ast => evalSexp(ast, env))); + return new MalList(ast.list.map(ast => evalMal(ast, env))); case "vector": - return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + return new MalVector(ast.list.map(ast => evalMal(ast, env))); case "hash-map": const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); - list.push(evalSexp(value, env)); + list.push(evalMal(value, env)); } return new MalHashMap(list); default: @@ -34,7 +35,8 @@ function evalAST(ast: MalType, env: Env): MalType { } } -function evalSexp(ast: MalType, env: Env): MalType { +// EVAL +function evalMal(ast: MalType, env: Env): MalType { if (ast.type !== "list") { return evalAST(ast, env); } @@ -53,7 +55,7 @@ function evalSexp(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key, evalSexp(value, env)) + return env.set(key, evalMal(value, env)) } case "let*": { let letEnv = new Env(env); @@ -71,9 +73,9 @@ function evalSexp(ast: MalType, env: Env): MalType { throw new Error(`unexpected syntax`); } - letEnv.set(key, evalSexp(value, letEnv)); + letEnv.set(key, evalMal(value, letEnv)); } - return evalSexp(ast.list[2], letEnv); + return evalMal(ast.list[2], letEnv); } case "do": { const [, ...list] = ast.list; @@ -85,7 +87,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } case "if": { const [, cond, thenExpr, elseExrp] = ast.list; - const ret = evalSexp(cond, env); + const ret = evalMal(cond, env); let b = true; if (MalBoolean.is(ret) && !ret.v) { b = false; @@ -93,9 +95,9 @@ function evalSexp(ast: MalType, env: Env): MalType { b = false; } if (b) { - return evalSexp(thenExpr, env); + return evalMal(thenExpr, env); } else if (elseExrp) { - return evalSexp(elseExrp, env); + return evalMal(elseExrp, env); } else { return MalNull.instance; } @@ -112,7 +114,7 @@ function evalSexp(ast: MalType, env: Env): MalType { return arg; }); return MalFunction.fromBootstrap((...fnArgs: MalType[]) => { - return evalSexp(binds, new Env(env, symbols, fnArgs)); + return evalMal(binds, new Env(env, symbols, fnArgs)); }); } } @@ -128,11 +130,17 @@ function evalSexp(ast: MalType, env: Env): MalType { return f.func(...args); } +// PRINT function print(exp: MalType): string { return prStr(exp); } const replEnv = new Env(); +function rep(str: string): string { + return print(evalMal(read(str), replEnv)); +} + +// core.EXT: defined using Racket core.ns.forEach((value, key) => { replEnv.set(key, value); }); @@ -140,10 +148,6 @@ core.ns.forEach((value, key) => { // core.mal: defined using the language itself rep("(def! not (fn* (a) (if a false true)))"); -function rep(str: string): string { - return print(evalSexp(read(str), replEnv)); -} - while (true) { const line = readline("user> "); if (line == null) { diff --git a/ts/step5_tco.ts b/ts/step5_tco.ts index 3a702b37c1..4626444a7d 100644 --- a/ts/step5_tco.ts +++ b/ts/step5_tco.ts @@ -6,6 +6,7 @@ import * as core from "./core"; import { readStr } from "./reader"; import { prStr } from "./printer"; +// READ function read(str: string): MalType { return readStr(str); } @@ -19,14 +20,14 @@ function evalAST(ast: MalType, env: Env): MalType { } return f; case "list": - return new MalList(ast.list.map(ast => evalSexp(ast, env))); + return new MalList(ast.list.map(ast => evalMal(ast, env))); case "vector": - return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + return new MalVector(ast.list.map(ast => evalMal(ast, env))); case "hash-map": const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); - list.push(evalSexp(value, env)); + list.push(evalMal(value, env)); } return new MalHashMap(list); default: @@ -34,7 +35,8 @@ function evalAST(ast: MalType, env: Env): MalType { } } -function evalSexp(ast: MalType, env: Env): MalType { +// EVAL +function evalMal(ast: MalType, env: Env): MalType { loop: while (true) { if (ast.type !== "list") { return evalAST(ast, env); @@ -54,7 +56,7 @@ function evalSexp(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key, evalSexp(value, env)) + return env.set(key, evalMal(value, env)) } case "let*": { env = new Env(env); @@ -72,7 +74,7 @@ function evalSexp(ast: MalType, env: Env): MalType { throw new Error(`unexpected syntax`); } - env.set(key, evalSexp(value, env)); + env.set(key, evalMal(value, env)); } ast = ast.list[2]; continue loop; @@ -85,7 +87,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } case "if": { const [, cond, thenExpr, elseExrp] = ast.list; - const ret = evalSexp(cond, env); + const ret = evalMal(cond, env); let b = true; if (MalBoolean.is(ret) && !ret.v) { b = false; @@ -112,7 +114,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } return param; }); - return MalFunction.fromLisp(evalSexp, env, symbols, bodyAst); + return MalFunction.fromLisp(evalMal, env, symbols, bodyAst); } } } @@ -134,11 +136,17 @@ function evalSexp(ast: MalType, env: Env): MalType { } } +// PRINT function print(exp: MalType): string { return prStr(exp); } const replEnv = new Env(); +function rep(str: string): string { + return print(evalMal(read(str), replEnv)); +} + +// core.EXT: defined using Racket core.ns.forEach((value, key) => { replEnv.set(key, value); }); @@ -146,10 +154,6 @@ core.ns.forEach((value, key) => { // core.mal: defined using the language itself rep("(def! not (fn* (a) (if a false true)))"); -function rep(str: string): string { - return print(evalSexp(read(str), replEnv)); -} - while (true) { const line = readline("user> "); if (line == null) { diff --git a/ts/step6_file.ts b/ts/step6_file.ts index c244dd7ce3..39f2e9adf8 100644 --- a/ts/step6_file.ts +++ b/ts/step6_file.ts @@ -6,6 +6,7 @@ import * as core from "./core"; import { readStr } from "./reader"; import { prStr } from "./printer"; +// READ function read(str: string): MalType { return readStr(str); } @@ -19,14 +20,14 @@ function evalAST(ast: MalType, env: Env): MalType { } return f; case "list": - return new MalList(ast.list.map(ast => evalSexp(ast, env))); + return new MalList(ast.list.map(ast => evalMal(ast, env))); case "vector": - return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + return new MalVector(ast.list.map(ast => evalMal(ast, env))); case "hash-map": const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); - list.push(evalSexp(value, env)); + list.push(evalMal(value, env)); } return new MalHashMap(list); default: @@ -34,7 +35,8 @@ function evalAST(ast: MalType, env: Env): MalType { } } -function evalSexp(ast: MalType, env: Env): MalType { +// EVAL +function evalMal(ast: MalType, env: Env): MalType { loop: while (true) { if (ast.type !== "list") { return evalAST(ast, env); @@ -54,7 +56,7 @@ function evalSexp(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key, evalSexp(value, env)) + return env.set(key, evalMal(value, env)) } case "let*": { env = new Env(env); @@ -72,7 +74,7 @@ function evalSexp(ast: MalType, env: Env): MalType { throw new Error(`unexpected syntax`); } - env.set(key, evalSexp(value, env)); + env.set(key, evalMal(value, env)); } ast = ast.list[2]; continue loop; @@ -85,7 +87,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } case "if": { const [, cond, thenExpr, elseExrp] = ast.list; - const ret = evalSexp(cond, env); + const ret = evalMal(cond, env); let b = true; if (MalBoolean.is(ret) && !ret.v) { b = false; @@ -112,7 +114,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } return param; }); - return MalFunction.fromLisp(evalSexp, env, symbols, bodyAst); + return MalFunction.fromLisp(evalMal, env, symbols, bodyAst); } } } @@ -134,11 +136,17 @@ function evalSexp(ast: MalType, env: Env): MalType { } } +// PRINT function print(exp: MalType): string { return prStr(exp); } const replEnv = new Env(); +function rep(str: string): string { + return print(evalMal(read(str), replEnv)); +} + +// core.EXT: defined using Racket core.ns.forEach((value, key) => { replEnv.set(key, value); }); @@ -146,7 +154,7 @@ replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { if (!ast) { throw new Error(`undefined argument`); } - return evalSexp(ast, replEnv); + return evalMal(ast, replEnv); })); replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); @@ -161,10 +169,6 @@ if (typeof process !== "undefined" && 2 < process.argv.length) { process.exit(0); } -function rep(str: string): string { - return print(evalSexp(read(str), replEnv)); -} - while (true) { const line = readline("user> "); if (line == null) { diff --git a/ts/step7_quote.ts b/ts/step7_quote.ts index 1f485a1a86..e069936a0a 100644 --- a/ts/step7_quote.ts +++ b/ts/step7_quote.ts @@ -6,6 +6,7 @@ import * as core from "./core"; import { readStr } from "./reader"; import { prStr } from "./printer"; +// READ function read(str: string): MalType { return readStr(str); } @@ -59,14 +60,14 @@ function evalAST(ast: MalType, env: Env): MalType { } return f; case "list": - return new MalList(ast.list.map(ast => evalSexp(ast, env))); + return new MalList(ast.list.map(ast => evalMal(ast, env))); case "vector": - return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + return new MalVector(ast.list.map(ast => evalMal(ast, env))); case "hash-map": const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); - list.push(evalSexp(value, env)); + list.push(evalMal(value, env)); } return new MalHashMap(list); default: @@ -74,7 +75,8 @@ function evalAST(ast: MalType, env: Env): MalType { } } -function evalSexp(ast: MalType, env: Env): MalType { +// EVAL +function evalMal(ast: MalType, env: Env): MalType { loop: while (true) { if (ast.type !== "list") { return evalAST(ast, env); @@ -94,7 +96,7 @@ function evalSexp(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key, evalSexp(value, env)) + return env.set(key, evalMal(value, env)) } case "let*": { env = new Env(env); @@ -112,7 +114,7 @@ function evalSexp(ast: MalType, env: Env): MalType { throw new Error(`unexpected syntax`); } - env.set(key, evalSexp(value, env)); + env.set(key, evalMal(value, env)); } ast = ast.list[2]; continue loop; @@ -132,7 +134,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } case "if": { const [, cond, thenExpr, elseExrp] = ast.list; - const ret = evalSexp(cond, env); + const ret = evalMal(cond, env); let b = true; if (MalBoolean.is(ret) && !ret.v) { b = false; @@ -159,7 +161,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } return param; }); - return MalFunction.fromLisp(evalSexp, env, symbols, bodyAst); + return MalFunction.fromLisp(evalMal, env, symbols, bodyAst); } } } @@ -181,11 +183,17 @@ function evalSexp(ast: MalType, env: Env): MalType { } } +// PRINT function print(exp: MalType): string { return prStr(exp); } const replEnv = new Env(); +function rep(str: string): string { + return print(evalMal(read(str), replEnv)); +} + +// core.EXT: defined using Racket core.ns.forEach((value, key) => { replEnv.set(key, value); }); @@ -193,9 +201,8 @@ replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { if (!ast) { throw new Error(`undefined argument`); } - return evalSexp(ast, replEnv); + return evalMal(ast, replEnv); })); - replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); // core.mal: defined using the language itself @@ -208,10 +215,6 @@ if (typeof process !== "undefined" && 2 < process.argv.length) { process.exit(0); } -function rep(str: string): string { - return print(evalSexp(read(str), replEnv)); -} - while (true) { const line = readline("user> "); if (line == null) { diff --git a/ts/step8_macros.ts b/ts/step8_macros.ts index 8abacf6b97..432b1fb00b 100644 --- a/ts/step8_macros.ts +++ b/ts/step8_macros.ts @@ -6,6 +6,7 @@ import * as core from "./core"; import { readStr } from "./reader"; import { prStr } from "./printer"; +// READ function read(str: string): MalType { return readStr(str); } @@ -50,7 +51,7 @@ function quasiquote(ast: MalType): MalType { } } -function isMacroCall(ast: MalType, env: Env): boolean { +function isMacro(ast: MalType, env: Env): boolean { if (!MalList.is(ast) && !MalVector.is(ast)) { return false; } @@ -72,7 +73,7 @@ function isMacroCall(ast: MalType, env: Env): boolean { } function macroexpand(ast: MalType, env: Env): MalType { - while (isMacroCall(ast, env)) { + while (isMacro(ast, env)) { if (!MalList.is(ast) && !MalVector.is(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } @@ -99,14 +100,14 @@ function evalAST(ast: MalType, env: Env): MalType { } return f; case "list": - return new MalList(ast.list.map(ast => evalSexp(ast, env))); + return new MalList(ast.list.map(ast => evalMal(ast, env))); case "vector": - return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + return new MalVector(ast.list.map(ast => evalMal(ast, env))); case "hash-map": const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); - list.push(evalSexp(value, env)); + list.push(evalMal(value, env)); } return new MalHashMap(list); default: @@ -114,7 +115,8 @@ function evalAST(ast: MalType, env: Env): MalType { } } -function evalSexp(ast: MalType, env: Env): MalType { +// EVAL +function evalMal(ast: MalType, env: Env): MalType { loop: while (true) { if (ast.type !== "list") { return evalAST(ast, env); @@ -140,7 +142,7 @@ function evalSexp(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key, evalSexp(value, env)) + return env.set(key, evalMal(value, env)) } case "let*": { env = new Env(env); @@ -158,7 +160,7 @@ function evalSexp(ast: MalType, env: Env): MalType { throw new Error(`unexpected syntax`); } - env.set(key, evalSexp(value, env)); + env.set(key, evalMal(value, env)); } ast = ast.list[2]; continue loop; @@ -178,7 +180,7 @@ function evalSexp(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - const f = evalSexp(value, env); + const f = evalMal(value, env); if (!MalFunction.is(f)) { throw new Error(`unexpected token type: ${f.type}, expected: function`); } @@ -196,7 +198,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } case "if": { const [, cond, thenExpr, elseExrp] = ast.list; - const ret = evalSexp(cond, env); + const ret = evalMal(cond, env); let b = true; if (MalBoolean.is(ret) && !ret.v) { b = false; @@ -223,7 +225,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } return param; }); - return MalFunction.fromLisp(evalSexp, env, symbols, bodyAst); + return MalFunction.fromLisp(evalMal, env, symbols, bodyAst); } } } @@ -245,11 +247,17 @@ function evalSexp(ast: MalType, env: Env): MalType { } } +// PRINT function print(exp: MalType): string { return prStr(exp); } const replEnv = new Env(); +function rep(str: string): string { + return print(evalMal(read(str), replEnv)); +} + +// core.EXT: defined using Racket core.ns.forEach((value, key) => { replEnv.set(key, value); }); @@ -257,9 +265,8 @@ replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { if (!ast) { throw new Error(`undefined argument`); } - return evalSexp(ast, replEnv); + return evalMal(ast, replEnv); })); - replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); // core.mal: defined using the language itself @@ -274,10 +281,6 @@ if (typeof process !== "undefined" && 2 < process.argv.length) { process.exit(0); } -function rep(str: string): string { - return print(evalSexp(read(str), replEnv)); -} - while (true) { const line = readline("user> "); if (line == null) { diff --git a/ts/step9_try.ts b/ts/step9_try.ts index c49e3be48f..32dfc16224 100644 --- a/ts/step9_try.ts +++ b/ts/step9_try.ts @@ -6,6 +6,7 @@ import * as core from "./core"; import { readStr } from "./reader"; import { prStr } from "./printer"; +// READ function read(str: string): MalType { return readStr(str); } @@ -50,7 +51,7 @@ function quasiquote(ast: MalType): MalType { } } -function isMacroCall(ast: MalType, env: Env): boolean { +function isMacro(ast: MalType, env: Env): boolean { if (!MalList.is(ast) && !MalVector.is(ast)) { return false; } @@ -72,7 +73,7 @@ function isMacroCall(ast: MalType, env: Env): boolean { } function macroexpand(ast: MalType, env: Env): MalType { - while (isMacroCall(ast, env)) { + while (isMacro(ast, env)) { if (!MalList.is(ast) && !MalVector.is(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } @@ -99,14 +100,14 @@ function evalAST(ast: MalType, env: Env): MalType { } return f; case "list": - return new MalList(ast.list.map(ast => evalSexp(ast, env))); + return new MalList(ast.list.map(ast => evalMal(ast, env))); case "vector": - return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + return new MalVector(ast.list.map(ast => evalMal(ast, env))); case "hash-map": const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); - list.push(evalSexp(value, env)); + list.push(evalMal(value, env)); } return new MalHashMap(list); default: @@ -114,7 +115,8 @@ function evalAST(ast: MalType, env: Env): MalType { } } -function evalSexp(ast: MalType, env: Env): MalType { +// EVAL +function evalMal(ast: MalType, env: Env): MalType { loop: while (true) { if (ast.type !== "list") { return evalAST(ast, env); @@ -140,7 +142,7 @@ function evalSexp(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key, evalSexp(value, env)) + return env.set(key, evalMal(value, env)) } case "let*": { env = new Env(env); @@ -158,7 +160,7 @@ function evalSexp(ast: MalType, env: Env): MalType { throw new Error(`unexpected syntax`); } - env.set(key, evalSexp(value, env)); + env.set(key, evalMal(value, env)); } ast = ast.list[2]; continue loop; @@ -178,7 +180,7 @@ function evalSexp(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - const f = evalSexp(value, env); + const f = evalMal(value, env); if (!MalFunction.is(f)) { throw new Error(`unexpected token type: ${f.type}, expected: function`); } @@ -190,7 +192,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } case "try*": { try { - return evalSexp(ast.list[1], env); + return evalMal(ast.list[1], env); } catch (e) { const catchBody = ast.list[2]; if (!MalList.is(catchBody) && !MalVector.is(catchBody)) { @@ -205,7 +207,7 @@ function evalSexp(ast: MalType, env: Env): MalType { if (!isAST(e)) { e = new MalString((e as Error).message); } - return evalSexp(catchBody.list[2], new Env(env, [errorSymbol], [e])); + return evalMal(catchBody.list[2], new Env(env, [errorSymbol], [e])); } throw e; } @@ -218,7 +220,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } case "if": { const [, cond, thenExpr, elseExrp] = ast.list; - const ret = evalSexp(cond, env); + const ret = evalMal(cond, env); let b = true; if (MalBoolean.is(ret) && !ret.v) { b = false; @@ -245,7 +247,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } return param; }); - return MalFunction.fromLisp(evalSexp, env, symbols, bodyAst); + return MalFunction.fromLisp(evalMal, env, symbols, bodyAst); } } } @@ -267,11 +269,17 @@ function evalSexp(ast: MalType, env: Env): MalType { } } +// PRINT function print(exp: MalType): string { return prStr(exp); } const replEnv = new Env(); +function rep(str: string): string { + return print(evalMal(read(str), replEnv)); +} + +// core.EXT: defined using Racket core.ns.forEach((value, key) => { replEnv.set(key, value); }); @@ -279,9 +287,8 @@ replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { if (!ast) { throw new Error(`undefined argument`); } - return evalSexp(ast, replEnv); + return evalMal(ast, replEnv); })); - replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); // core.mal: defined using the language itself @@ -296,10 +303,6 @@ if (typeof process !== "undefined" && 2 < process.argv.length) { process.exit(0); } -function rep(str: string): string { - return print(evalSexp(read(str), replEnv)); -} - while (true) { const line = readline("user> "); if (line == null) { diff --git a/ts/stepA_mal.ts b/ts/stepA_mal.ts index df21906805..13b55f43cc 100644 --- a/ts/stepA_mal.ts +++ b/ts/stepA_mal.ts @@ -6,6 +6,7 @@ import * as core from "./core"; import { readStr } from "./reader"; import { prStr } from "./printer"; +// READ function read(str: string): MalType { return readStr(str); } @@ -50,7 +51,7 @@ function quasiquote(ast: MalType): MalType { } } -function isMacroCall(ast: MalType, env: Env): boolean { +function isMacro(ast: MalType, env: Env): boolean { if (!MalList.is(ast) && !MalVector.is(ast)) { return false; } @@ -72,7 +73,7 @@ function isMacroCall(ast: MalType, env: Env): boolean { } function macroexpand(ast: MalType, env: Env): MalType { - while (isMacroCall(ast, env)) { + while (isMacro(ast, env)) { if (!MalList.is(ast) && !MalVector.is(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } @@ -99,14 +100,14 @@ function evalAST(ast: MalType, env: Env): MalType { } return f; case "list": - return new MalList(ast.list.map(ast => evalSexp(ast, env))); + return new MalList(ast.list.map(ast => evalMal(ast, env))); case "vector": - return new MalVector(ast.list.map(ast => evalSexp(ast, env))); + return new MalVector(ast.list.map(ast => evalMal(ast, env))); case "hash-map": const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); - list.push(evalSexp(value, env)); + list.push(evalMal(value, env)); } return new MalHashMap(list); default: @@ -114,7 +115,8 @@ function evalAST(ast: MalType, env: Env): MalType { } } -function evalSexp(ast: MalType, env: Env): MalType { +// EVAL +function evalMal(ast: MalType, env: Env): MalType { loop: while (true) { if (ast.type !== "list") { return evalAST(ast, env); @@ -140,7 +142,7 @@ function evalSexp(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key, evalSexp(value, env)) + return env.set(key, evalMal(value, env)) } case "let*": { env = new Env(env); @@ -158,7 +160,7 @@ function evalSexp(ast: MalType, env: Env): MalType { throw new Error(`unexpected syntax`); } - env.set(key, evalSexp(value, env)); + env.set(key, evalMal(value, env)); } ast = ast.list[2]; continue loop; @@ -178,7 +180,7 @@ function evalSexp(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - const f = evalSexp(value, env); + const f = evalMal(value, env); if (!MalFunction.is(f)) { throw new Error(`unexpected token type: ${f.type}, expected: function`); } @@ -190,7 +192,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } case "try*": { try { - return evalSexp(ast.list[1], env); + return evalMal(ast.list[1], env); } catch (e) { const catchBody = ast.list[2]; if (!MalList.is(catchBody) && !MalVector.is(catchBody)) { @@ -205,7 +207,7 @@ function evalSexp(ast: MalType, env: Env): MalType { if (!isAST(e)) { e = new MalString((e as Error).message); } - return evalSexp(catchBody.list[2], new Env(env, [errorSymbol], [e])); + return evalMal(catchBody.list[2], new Env(env, [errorSymbol], [e])); } throw e; } @@ -218,7 +220,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } case "if": { const [, cond, thenExpr, elseExrp] = ast.list; - const ret = evalSexp(cond, env); + const ret = evalMal(cond, env); let b = true; if (MalBoolean.is(ret) && !ret.v) { b = false; @@ -245,7 +247,7 @@ function evalSexp(ast: MalType, env: Env): MalType { } return param; }); - return MalFunction.fromLisp(evalSexp, env, symbols, bodyAst); + return MalFunction.fromLisp(evalMal, env, symbols, bodyAst); } } } @@ -267,11 +269,17 @@ function evalSexp(ast: MalType, env: Env): MalType { } } +// PRINT function print(exp: MalType): string { return prStr(exp); } const replEnv = new Env(); +function rep(str: string): string { + return print(evalMal(read(str), replEnv)); +} + +// core.EXT: defined using Racket core.ns.forEach((value, key) => { replEnv.set(key, value); }); @@ -279,9 +287,8 @@ replEnv.set(MalSymbol.get("eval"), MalFunction.fromBootstrap(ast => { if (!ast) { throw new Error(`undefined argument`); } - return evalSexp(ast, replEnv); + return evalMal(ast, replEnv); })); - replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); // core.mal: defined using the language itself @@ -299,10 +306,6 @@ if (typeof process !== "undefined" && 2 < process.argv.length) { process.exit(0); } -function rep(str: string): string { - return print(evalSexp(read(str), replEnv)); -} - rep(`(println (str "Mal [" *host-language* "]"))`); while (true) { const line = readline("user> "); diff --git a/ts/types.ts b/ts/types.ts index 10f72adf00..db9bb94504 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -350,9 +350,9 @@ export class MalFunction { return f instanceof MalFunction; } - static fromLisp(evalSexpr: (ast: MalType, env: Env) => MalType, env: Env, params: MalSymbol[], bodyAst: MalType): MalFunction { + static fromLisp(evalMal: (ast: MalType, env: Env) => MalType, env: Env, params: MalSymbol[], bodyAst: MalType): MalFunction { const f = new MalFunction(); - f.func = (...args) => evalSexpr(bodyAst, new Env(env, params, checkUndefined(args))); + f.func = (...args) => evalMal(bodyAst, new Env(env, params, checkUndefined(args))); f.env = env; f.params = params; f.ast = bodyAst; From 12c0c9a3a83e28ad89139fd0e3678490bca54019 Mon Sep 17 00:00:00 2001 From: vvakame Date: Sat, 25 Feb 2017 13:03:04 +0900 Subject: [PATCH 0303/2308] reordering core.ns --- ts/core.ts | 369 +++++++++++++++++++++++++++-------------------------- 1 file changed, 188 insertions(+), 181 deletions(-) diff --git a/ts/core.ts b/ts/core.ts index 0c692efb70..353b35a0b7 100644 --- a/ts/core.ts +++ b/ts/core.ts @@ -8,18 +8,44 @@ import { prStr } from "./printer"; export const ns: Map = (() => { const ns: { [symbol: string]: typeof MalFunction.prototype.func; } = { - readline(v: MalType) { + "="(a: MalType, b: MalType): MalBoolean { + return new MalBoolean(equals(a, b)); + }, + throw(v: MalType): MalType { + throw v; + }, + + "nil?"(v: MalType) { + return new MalBoolean(MalNull.is(v)); + }, + "true?"(v: MalType) { + return new MalBoolean(MalBoolean.is(v) && v.v); + }, + "false?"(v: MalType) { + return new MalBoolean(MalBoolean.is(v) && !v.v); + }, + "string?"(v: MalType) { + return new MalBoolean(MalString.is(v)); + }, + symbol(v: MalType) { if (!MalString.is(v)) { throw new Error(`unexpected symbol: ${v.type}, expected: string`); } - - const ret = readline(v.v); - if (ret == null) { - return MalNull.instance; + return MalSymbol.get(v.v); + }, + "symbol?"(v: MalType) { + return new MalBoolean(MalSymbol.is(v)); + }, + keyword(v: MalType) { + if (!MalString.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: string`); } - - return new MalString(ret); + return MalKeyword.get(v.v); }, + "keyword?"(v: MalType) { + return new MalBoolean(MalKeyword.is(v)); + }, + "pr-str"(...args: MalType[]): MalString { return new MalString(args.map(v => prStr(v, true)).join(" ")); }, @@ -42,143 +68,27 @@ export const ns: Map = (() => { } return readStr(v.v); }, - slurp(v: MalType) { + readline(v: MalType) { if (!MalString.is(v)) { throw new Error(`unexpected symbol: ${v.type}, expected: string`); } - const content = fs.readFileSync(v.v, "UTF-8"); - return new MalString(content); - }, - cons(a: MalType, b: MalType) { - if (!MalList.is(b) && !MalVector.is(b)) { - throw new Error(`unexpected symbol: ${b.type}, expected: list or vector`); - } - - return new MalList([a].concat(b.list)); - }, - concat(...args: MalType[]) { - const list = args - .map(arg => { - if (!MalList.is(arg) && !MalVector.is(arg)) { - throw new Error(`unexpected symbol: ${arg.type}, expected: list or vector`); - } - return arg; - }) - .reduce((p, c) => p.concat(c.list), [] as MalType[]); - - return new MalList(list); - }, - list(...args: MalType[]): MalList { - return new MalList(args); - }, - "list?"(v: MalType): MalBoolean { - return new MalBoolean(v instanceof MalList); - }, - "empty?"(v: MalType): MalBoolean { - if (!MalList.is(v) && !MalVector.is(v)) { - return new MalBoolean(false); - } - return new MalBoolean(v.list.length === 0); - }, - count(v: MalType): MalNumber { - if (MalList.is(v) || MalVector.is(v)) { - return new MalNumber(v.list.length); - } - if (MalNull.is(v)) { - return new MalNumber(0); - } - throw new Error(`unexpected symbol: ${v.type}`); - }, - nth(list: MalType, idx: MalType) { - if (!MalList.is(list) && !MalVector.is(list)) { - throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`); - } - if (!MalNumber.is(idx)) { - throw new Error(`unexpected symbol: ${idx.type}, expected: number`); - } - const v = list.list[idx.v]; - if (!v) { - throw new Error("nth: index out of range"); - } - - return v; - }, - first(v: MalType) { - if (MalNull.is(v)) { + const ret = readline(v.v); + if (ret == null) { return MalNull.instance; } - if (!MalList.is(v) && !MalVector.is(v)) { - throw new Error(`unexpected symbol: ${v.type}, expected: list or vector`); - } - - return v.list[0] || MalNull.instance; - }, - rest(v: MalType) { - if (MalNull.is(v)) { - return new MalList([]); - } - if (!MalList.is(v) && !MalVector.is(v)) { - throw new Error(`unexpected symbol: ${v.type}, expected: list or vector`); - } - return new MalList(v.list.slice(1)); - }, - atom(v: MalType): MalAtom { - return new MalAtom(v); - }, - "atom?"(v: MalType): MalBoolean { - return new MalBoolean(MalAtom.is(v)); - }, - deref(v: MalType): MalType { - if (!MalAtom.is(v)) { - throw new Error(`unexpected symbol: ${v.type}, expected: atom`); - } - return v.v; - }, - "reset!"(atom: MalType, v: MalType): MalType { - if (!MalAtom.is(atom)) { - throw new Error(`unexpected symbol: ${atom.type}, expected: atom`); - } - atom.v = v; - return v; - }, - "swap!"(atom: MalType, f: MalType, ...args: MalType[]): MalType { - if (!MalAtom.is(atom)) { - throw new Error(`unexpected symbol: ${atom.type}, expected: atom`); - } - if (!MalFunction.is(f)) { - throw new Error(`unexpected symbol: ${f.type}, expected: function`); - } - atom.v = f.func(...[atom.v].concat(args)); - return atom.v; - }, - throw(v: MalType): MalType { - throw v; + return new MalString(ret); }, - apply(f: MalType, ...list: MalType[]) { - if (!MalFunction.is(f)) { - throw new Error(`unexpected symbol: ${f.type}, expected: function`); - } - - const tail = list[list.length - 1]; - if (!MalList.is(tail) && !MalVector.is(tail)) { - throw new Error(`unexpected symbol: ${tail.type}, expected: list or vector`); + slurp(v: MalType) { + if (!MalString.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: string`); } - const args = list.slice(0, -1).concat(tail.list); - return f.func(...args); + const content = fs.readFileSync(v.v, "UTF-8"); + return new MalString(content); }, - map(f: MalType, list: MalType) { - if (!MalFunction.is(f)) { - throw new Error(`unexpected symbol: ${f.type}, expected: function`); - } - if (!MalList.is(list) && !MalVector.is(list)) { - throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`); - } - return new MalList(list.list.map(v => f.func(v))); - }, - "+"(a: MalType, b: MalType): MalNumber { + "<"(a: MalType, b: MalType): MalBoolean { if (!MalNumber.is(a)) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } @@ -186,9 +96,9 @@ export const ns: Map = (() => { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } - return new MalNumber(a.v + b.v); + return new MalBoolean(a.v < b.v); }, - "-"(a: MalType, b: MalType): MalNumber { + "<="(a: MalType, b: MalType): MalBoolean { if (!MalNumber.is(a)) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } @@ -196,9 +106,9 @@ export const ns: Map = (() => { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } - return new MalNumber(a.v - b.v); + return new MalBoolean(a.v <= b.v); }, - "*"(a: MalType, b: MalType): MalNumber { + ">"(a: MalType, b: MalType): MalBoolean { if (!MalNumber.is(a)) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } @@ -206,9 +116,9 @@ export const ns: Map = (() => { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } - return new MalNumber(a.v * b.v); + return new MalBoolean(a.v > b.v); }, - "/"(a: MalType, b: MalType): MalNumber { + ">="(a: MalType, b: MalType): MalBoolean { if (!MalNumber.is(a)) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } @@ -216,12 +126,9 @@ export const ns: Map = (() => { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } - return new MalNumber(a.v / b.v); - }, - "="(a: MalType, b: MalType): MalBoolean { - return new MalBoolean(equals(a, b)); + return new MalBoolean(a.v >= b.v); }, - "<"(a: MalType, b: MalType): MalBoolean { + "+"(a: MalType, b: MalType): MalNumber { if (!MalNumber.is(a)) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } @@ -229,9 +136,9 @@ export const ns: Map = (() => { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } - return new MalBoolean(a.v < b.v); + return new MalNumber(a.v + b.v); }, - "<="(a: MalType, b: MalType): MalBoolean { + "-"(a: MalType, b: MalType): MalNumber { if (!MalNumber.is(a)) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } @@ -239,9 +146,9 @@ export const ns: Map = (() => { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } - return new MalBoolean(a.v <= b.v); + return new MalNumber(a.v - b.v); }, - ">"(a: MalType, b: MalType): MalBoolean { + "*"(a: MalType, b: MalType): MalNumber { if (!MalNumber.is(a)) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } @@ -249,9 +156,9 @@ export const ns: Map = (() => { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } - return new MalBoolean(a.v > b.v); + return new MalNumber(a.v * b.v); }, - ">="(a: MalType, b: MalType): MalBoolean { + "/"(a: MalType, b: MalType): MalNumber { if (!MalNumber.is(a)) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } @@ -259,40 +166,17 @@ export const ns: Map = (() => { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } - return new MalBoolean(a.v >= b.v); + return new MalNumber(a.v / b.v); }, "time-ms"() { return new MalNumber(Date.now()); }, - "nil?"(v: MalType) { - return new MalBoolean(MalNull.is(v)); - }, - "true?"(v: MalType) { - return new MalBoolean(MalBoolean.is(v) && v.v); - }, - "false?"(v: MalType) { - return new MalBoolean(MalBoolean.is(v) && !v.v); - }, - "string?"(v: MalType) { - return new MalBoolean(MalString.is(v)); - }, - "symbol?"(v: MalType) { - return new MalBoolean(MalSymbol.is(v)); - }, - symbol(v: MalType) { - if (!MalString.is(v)) { - throw new Error(`unexpected symbol: ${v.type}, expected: string`); - } - return MalSymbol.get(v.v); - }, - keyword(v: MalType) { - if (!MalString.is(v)) { - throw new Error(`unexpected symbol: ${v.type}, expected: string`); - } - return MalKeyword.get(v.v); + + list(...args: MalType[]): MalList { + return new MalList(args); }, - "keyword?"(v: MalType) { - return new MalBoolean(MalKeyword.is(v)); + "list?"(v: MalType): MalBoolean { + return new MalBoolean(v instanceof MalList); }, vector(...args: MalType[]): MalVector { return new MalVector(args); @@ -358,9 +242,102 @@ export const ns: Map = (() => { return new MalList([...v.vals()]); }, + "sequential?"(v: MalType) { return new MalBoolean(MalList.is(v) || MalVector.is(v)); }, + cons(a: MalType, b: MalType) { + if (!MalList.is(b) && !MalVector.is(b)) { + throw new Error(`unexpected symbol: ${b.type}, expected: list or vector`); + } + + return new MalList([a].concat(b.list)); + }, + concat(...args: MalType[]) { + const list = args + .map(arg => { + if (!MalList.is(arg) && !MalVector.is(arg)) { + throw new Error(`unexpected symbol: ${arg.type}, expected: list or vector`); + } + return arg; + }) + .reduce((p, c) => p.concat(c.list), [] as MalType[]); + + return new MalList(list); + }, + nth(list: MalType, idx: MalType) { + if (!MalList.is(list) && !MalVector.is(list)) { + throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`); + } + if (!MalNumber.is(idx)) { + throw new Error(`unexpected symbol: ${idx.type}, expected: number`); + } + + const v = list.list[idx.v]; + if (!v) { + throw new Error("nth: index out of range"); + } + + return v; + }, + first(v: MalType) { + if (MalNull.is(v)) { + return MalNull.instance; + } + if (!MalList.is(v) && !MalVector.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: list or vector`); + } + + return v.list[0] || MalNull.instance; + }, + rest(v: MalType) { + if (MalNull.is(v)) { + return new MalList([]); + } + if (!MalList.is(v) && !MalVector.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: list or vector`); + } + + return new MalList(v.list.slice(1)); + }, + "empty?"(v: MalType): MalBoolean { + if (!MalList.is(v) && !MalVector.is(v)) { + return new MalBoolean(false); + } + return new MalBoolean(v.list.length === 0); + }, + count(v: MalType): MalNumber { + if (MalList.is(v) || MalVector.is(v)) { + return new MalNumber(v.list.length); + } + if (MalNull.is(v)) { + return new MalNumber(0); + } + throw new Error(`unexpected symbol: ${v.type}`); + }, + apply(f: MalType, ...list: MalType[]) { + if (!MalFunction.is(f)) { + throw new Error(`unexpected symbol: ${f.type}, expected: function`); + } + + const tail = list[list.length - 1]; + if (!MalList.is(tail) && !MalVector.is(tail)) { + throw new Error(`unexpected symbol: ${tail.type}, expected: list or vector`); + } + const args = list.slice(0, -1).concat(tail.list); + return f.func(...args); + }, + map(f: MalType, list: MalType) { + if (!MalFunction.is(f)) { + throw new Error(`unexpected symbol: ${f.type}, expected: function`); + } + if (!MalList.is(list) && !MalVector.is(list)) { + throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`); + } + + return new MalList(list.list.map(v => f.func(v))); + }, + conj(list: MalType, ...args: MalType[]) { switch (list.type) { case "list": @@ -398,11 +375,41 @@ export const ns: Map = (() => { throw new Error(`unexpected symbol: ${v.type}, expected: list or vector or string`); }, + + meta(v: MalType) { + return v.meta || MalNull.instance; + }, "with-meta"(v: MalType, m: MalType) { return v.withMeta(m); }, - meta(v: MalType) { - return v.meta || MalNull.instance; + atom(v: MalType): MalAtom { + return new MalAtom(v); + }, + "atom?"(v: MalType): MalBoolean { + return new MalBoolean(MalAtom.is(v)); + }, + deref(v: MalType): MalType { + if (!MalAtom.is(v)) { + throw new Error(`unexpected symbol: ${v.type}, expected: atom`); + } + return v.v; + }, + "reset!"(atom: MalType, v: MalType): MalType { + if (!MalAtom.is(atom)) { + throw new Error(`unexpected symbol: ${atom.type}, expected: atom`); + } + atom.v = v; + return v; + }, + "swap!"(atom: MalType, f: MalType, ...args: MalType[]): MalType { + if (!MalAtom.is(atom)) { + throw new Error(`unexpected symbol: ${atom.type}, expected: atom`); + } + if (!MalFunction.is(f)) { + throw new Error(`unexpected symbol: ${f.type}, expected: function`); + } + atom.v = f.func(...[atom.v].concat(args)); + return atom.v; }, }; From 92bf05308edc98d482aaa1a584fec2e760c375dc Mon Sep 17 00:00:00 2001 From: vvakame Date: Sat, 25 Feb 2017 13:28:38 +0900 Subject: [PATCH 0304/2308] add isSeq function to types --- ts/core.ts | 22 +++++++++++----------- ts/step4_if_fn_do.ts | 10 +++++----- ts/step5_tco.ts | 8 ++++---- ts/step6_file.ts | 8 ++++---- ts/step7_quote.ts | 14 +++++++------- ts/step8_macros.ts | 18 +++++++++--------- ts/step9_try.ts | 20 ++++++++++---------- ts/stepA_mal.ts | 20 ++++++++++---------- ts/types.ts | 16 ++++++---------- 9 files changed, 66 insertions(+), 70 deletions(-) diff --git a/ts/core.ts b/ts/core.ts index 353b35a0b7..5d8ed93f69 100644 --- a/ts/core.ts +++ b/ts/core.ts @@ -2,7 +2,7 @@ import * as fs from "fs"; import { readline } from "./node_readline"; -import { MalType, MalSymbol, MalFunction, MalNull, MalList, MalVector, MalBoolean, MalNumber, MalString, MalKeyword, MalHashMap, MalAtom, equals } from "./types"; +import { MalType, MalSymbol, MalFunction, MalNull, MalList, MalVector, MalBoolean, MalNumber, MalString, MalKeyword, MalHashMap, MalAtom, equals, isSeq } from "./types"; import { readStr } from "./reader"; import { prStr } from "./printer"; @@ -244,10 +244,10 @@ export const ns: Map = (() => { }, "sequential?"(v: MalType) { - return new MalBoolean(MalList.is(v) || MalVector.is(v)); + return new MalBoolean(isSeq(v)); }, cons(a: MalType, b: MalType) { - if (!MalList.is(b) && !MalVector.is(b)) { + if (!isSeq(b)) { throw new Error(`unexpected symbol: ${b.type}, expected: list or vector`); } @@ -256,7 +256,7 @@ export const ns: Map = (() => { concat(...args: MalType[]) { const list = args .map(arg => { - if (!MalList.is(arg) && !MalVector.is(arg)) { + if (!isSeq(arg)) { throw new Error(`unexpected symbol: ${arg.type}, expected: list or vector`); } return arg; @@ -266,7 +266,7 @@ export const ns: Map = (() => { return new MalList(list); }, nth(list: MalType, idx: MalType) { - if (!MalList.is(list) && !MalVector.is(list)) { + if (!isSeq(list)) { throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`); } if (!MalNumber.is(idx)) { @@ -284,7 +284,7 @@ export const ns: Map = (() => { if (MalNull.is(v)) { return MalNull.instance; } - if (!MalList.is(v) && !MalVector.is(v)) { + if (!isSeq(v)) { throw new Error(`unexpected symbol: ${v.type}, expected: list or vector`); } @@ -294,20 +294,20 @@ export const ns: Map = (() => { if (MalNull.is(v)) { return new MalList([]); } - if (!MalList.is(v) && !MalVector.is(v)) { + if (!isSeq(v)) { throw new Error(`unexpected symbol: ${v.type}, expected: list or vector`); } return new MalList(v.list.slice(1)); }, "empty?"(v: MalType): MalBoolean { - if (!MalList.is(v) && !MalVector.is(v)) { + if (!isSeq(v)) { return new MalBoolean(false); } return new MalBoolean(v.list.length === 0); }, count(v: MalType): MalNumber { - if (MalList.is(v) || MalVector.is(v)) { + if (isSeq(v)) { return new MalNumber(v.list.length); } if (MalNull.is(v)) { @@ -321,7 +321,7 @@ export const ns: Map = (() => { } const tail = list[list.length - 1]; - if (!MalList.is(tail) && !MalVector.is(tail)) { + if (!isSeq(tail)) { throw new Error(`unexpected symbol: ${tail.type}, expected: list or vector`); } const args = list.slice(0, -1).concat(tail.list); @@ -331,7 +331,7 @@ export const ns: Map = (() => { if (!MalFunction.is(f)) { throw new Error(`unexpected symbol: ${f.type}, expected: function`); } - if (!MalList.is(list) && !MalVector.is(list)) { + if (!isSeq(list)) { throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`); } diff --git a/ts/step4_if_fn_do.ts b/ts/step4_if_fn_do.ts index 6ec0f9f5b7..65d8192816 100644 --- a/ts/step4_if_fn_do.ts +++ b/ts/step4_if_fn_do.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { MalType, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -60,7 +60,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "let*": { let letEnv = new Env(env); const pairs = ast.list[1]; - if (!MalList.is(pairs) && !MalVector.is(pairs)) { + if (!isSeq(pairs)) { throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); } for (let i = 0; i < pairs.list.length; i += 2) { @@ -80,7 +80,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "do": { const [, ...list] = ast.list; const ret = evalAST(new MalList(list), env); - if (!MalList.is(ret) && !MalVector.is(ret)) { + if (!isSeq(ret)) { throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); } return ret.list[ret.list.length - 1]; @@ -104,7 +104,7 @@ function evalMal(ast: MalType, env: Env): MalType { } case "fn*": { const [, args, binds] = ast.list; - if (!MalList.is(args) && !MalVector.is(args)) { + if (!isSeq(args)) { throw new Error(`unexpected return type: ${args.type}, expected: list or vector`); } const symbols = args.list.map(arg => { @@ -120,7 +120,7 @@ function evalMal(ast: MalType, env: Env): MalType { } } const result = evalAST(ast, env); - if (!MalList.is(result) && !MalVector.is(result)) { + if (!isSeq(result)) { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; diff --git a/ts/step5_tco.ts b/ts/step5_tco.ts index 4626444a7d..714c406ba1 100644 --- a/ts/step5_tco.ts +++ b/ts/step5_tco.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { MalType, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -61,7 +61,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "let*": { env = new Env(env); const pairs = ast.list[1]; - if (!MalList.is(pairs) && !MalVector.is(pairs)) { + if (!isSeq(pairs)) { throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); } for (let i = 0; i < pairs.list.length; i += 2) { @@ -105,7 +105,7 @@ function evalMal(ast: MalType, env: Env): MalType { } case "fn*": { const [, params, bodyAst] = ast.list; - if (!MalList.is(params) && !MalVector.is(params)) { + if (!isSeq(params)) { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { @@ -119,7 +119,7 @@ function evalMal(ast: MalType, env: Env): MalType { } } const result = evalAST(ast, env); - if (!MalList.is(result) && !MalVector.is(result)) { + if (!isSeq(result)) { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; diff --git a/ts/step6_file.ts b/ts/step6_file.ts index 39f2e9adf8..c11fd5e9f6 100644 --- a/ts/step6_file.ts +++ b/ts/step6_file.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -61,7 +61,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "let*": { env = new Env(env); const pairs = ast.list[1]; - if (!MalList.is(pairs) && !MalVector.is(pairs)) { + if (!isSeq(pairs)) { throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); } for (let i = 0; i < pairs.list.length; i += 2) { @@ -105,7 +105,7 @@ function evalMal(ast: MalType, env: Env): MalType { } case "fn*": { const [, params, bodyAst] = ast.list; - if (!MalList.is(params) && !MalVector.is(params)) { + if (!isSeq(params)) { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { @@ -119,7 +119,7 @@ function evalMal(ast: MalType, env: Env): MalType { } } const result = evalAST(ast, env); - if (!MalList.is(result) && !MalVector.is(result)) { + if (!isSeq(result)) { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; diff --git a/ts/step7_quote.ts b/ts/step7_quote.ts index e069936a0a..dfac5e8b9b 100644 --- a/ts/step7_quote.ts +++ b/ts/step7_quote.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -15,7 +15,7 @@ function quasiquote(ast: MalType): MalType { if (!isPair(ast)) { return new MalList([MalSymbol.get("quote"), ast]); } - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const [arg1, arg2] = ast.list; @@ -23,7 +23,7 @@ function quasiquote(ast: MalType): MalType { return arg2; } if (isPair(arg1)) { - if (!MalList.is(arg1) && !MalVector.is(arg1)) { + if (!isSeq(arg1)) { throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); } const [arg11, arg12] = arg1.list; @@ -43,7 +43,7 @@ function quasiquote(ast: MalType): MalType { ]); function isPair(ast: MalType) { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { return false; } @@ -101,7 +101,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "let*": { env = new Env(env); const pairs = ast.list[1]; - if (!MalList.is(pairs) && !MalVector.is(pairs)) { + if (!isSeq(pairs)) { throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); } for (let i = 0; i < pairs.list.length; i += 2) { @@ -152,7 +152,7 @@ function evalMal(ast: MalType, env: Env): MalType { } case "fn*": { const [, params, bodyAst] = ast.list; - if (!MalList.is(params) && !MalVector.is(params)) { + if (!isSeq(params)) { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { @@ -166,7 +166,7 @@ function evalMal(ast: MalType, env: Env): MalType { } } const result = evalAST(ast, env); - if (!MalList.is(result) && !MalVector.is(result)) { + if (!isSeq(result)) { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; diff --git a/ts/step8_macros.ts b/ts/step8_macros.ts index 432b1fb00b..e599dfe8c0 100644 --- a/ts/step8_macros.ts +++ b/ts/step8_macros.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -15,7 +15,7 @@ function quasiquote(ast: MalType): MalType { if (!isPair(ast)) { return new MalList([MalSymbol.get("quote"), ast]); } - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const [arg1, arg2] = ast.list; @@ -23,7 +23,7 @@ function quasiquote(ast: MalType): MalType { return arg2; } if (isPair(arg1)) { - if (!MalList.is(arg1) && !MalVector.is(arg1)) { + if (!isSeq(arg1)) { throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); } const [arg11, arg12] = arg1.list; @@ -43,7 +43,7 @@ function quasiquote(ast: MalType): MalType { ]); function isPair(ast: MalType) { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { return false; } @@ -52,7 +52,7 @@ function quasiquote(ast: MalType): MalType { } function isMacro(ast: MalType, env: Env): boolean { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { return false; } const s = ast.list[0]; @@ -74,7 +74,7 @@ function isMacro(ast: MalType, env: Env): boolean { function macroexpand(ast: MalType, env: Env): MalType { while (isMacro(ast, env)) { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const s = ast.list[0]; @@ -147,7 +147,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "let*": { env = new Env(env); const pairs = ast.list[1]; - if (!MalList.is(pairs) && !MalVector.is(pairs)) { + if (!isSeq(pairs)) { throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); } for (let i = 0; i < pairs.list.length; i += 2) { @@ -216,7 +216,7 @@ function evalMal(ast: MalType, env: Env): MalType { } case "fn*": { const [, params, bodyAst] = ast.list; - if (!MalList.is(params) && !MalVector.is(params)) { + if (!isSeq(params)) { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { @@ -230,7 +230,7 @@ function evalMal(ast: MalType, env: Env): MalType { } } const result = evalAST(ast, env); - if (!MalList.is(result) && !MalVector.is(result)) { + if (!isSeq(result)) { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; diff --git a/ts/step9_try.ts b/ts/step9_try.ts index 32dfc16224..0f281877c8 100644 --- a/ts/step9_try.ts +++ b/ts/step9_try.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST } from "./types"; +import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -15,7 +15,7 @@ function quasiquote(ast: MalType): MalType { if (!isPair(ast)) { return new MalList([MalSymbol.get("quote"), ast]); } - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const [arg1, arg2] = ast.list; @@ -23,7 +23,7 @@ function quasiquote(ast: MalType): MalType { return arg2; } if (isPair(arg1)) { - if (!MalList.is(arg1) && !MalVector.is(arg1)) { + if (!isSeq(arg1)) { throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); } const [arg11, arg12] = arg1.list; @@ -43,7 +43,7 @@ function quasiquote(ast: MalType): MalType { ]); function isPair(ast: MalType) { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { return false; } @@ -52,7 +52,7 @@ function quasiquote(ast: MalType): MalType { } function isMacro(ast: MalType, env: Env): boolean { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { return false; } const s = ast.list[0]; @@ -74,7 +74,7 @@ function isMacro(ast: MalType, env: Env): boolean { function macroexpand(ast: MalType, env: Env): MalType { while (isMacro(ast, env)) { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const s = ast.list[0]; @@ -147,7 +147,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "let*": { env = new Env(env); const pairs = ast.list[1]; - if (!MalList.is(pairs) && !MalVector.is(pairs)) { + if (!isSeq(pairs)) { throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); } for (let i = 0; i < pairs.list.length; i += 2) { @@ -195,7 +195,7 @@ function evalMal(ast: MalType, env: Env): MalType { return evalMal(ast.list[1], env); } catch (e) { const catchBody = ast.list[2]; - if (!MalList.is(catchBody) && !MalVector.is(catchBody)) { + if (!isSeq(catchBody)) { throw new Error(`unexpected return type: ${catchBody.type}, expected: list or vector`); } const catchSymbol = catchBody.list[0]; @@ -238,7 +238,7 @@ function evalMal(ast: MalType, env: Env): MalType { } case "fn*": { const [, params, bodyAst] = ast.list; - if (!MalList.is(params) && !MalVector.is(params)) { + if (!isSeq(params)) { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { @@ -252,7 +252,7 @@ function evalMal(ast: MalType, env: Env): MalType { } } const result = evalAST(ast, env); - if (!MalList.is(result) && !MalVector.is(result)) { + if (!isSeq(result)) { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; diff --git a/ts/stepA_mal.ts b/ts/stepA_mal.ts index 13b55f43cc..3f311a3eab 100644 --- a/ts/stepA_mal.ts +++ b/ts/stepA_mal.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST } from "./types"; +import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -15,7 +15,7 @@ function quasiquote(ast: MalType): MalType { if (!isPair(ast)) { return new MalList([MalSymbol.get("quote"), ast]); } - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const [arg1, arg2] = ast.list; @@ -23,7 +23,7 @@ function quasiquote(ast: MalType): MalType { return arg2; } if (isPair(arg1)) { - if (!MalList.is(arg1) && !MalVector.is(arg1)) { + if (!isSeq(arg1)) { throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); } const [arg11, arg12] = arg1.list; @@ -43,7 +43,7 @@ function quasiquote(ast: MalType): MalType { ]); function isPair(ast: MalType) { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { return false; } @@ -52,7 +52,7 @@ function quasiquote(ast: MalType): MalType { } function isMacro(ast: MalType, env: Env): boolean { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { return false; } const s = ast.list[0]; @@ -74,7 +74,7 @@ function isMacro(ast: MalType, env: Env): boolean { function macroexpand(ast: MalType, env: Env): MalType { while (isMacro(ast, env)) { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const s = ast.list[0]; @@ -147,7 +147,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "let*": { env = new Env(env); const pairs = ast.list[1]; - if (!MalList.is(pairs) && !MalVector.is(pairs)) { + if (!isSeq(pairs)) { throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); } for (let i = 0; i < pairs.list.length; i += 2) { @@ -195,7 +195,7 @@ function evalMal(ast: MalType, env: Env): MalType { return evalMal(ast.list[1], env); } catch (e) { const catchBody = ast.list[2]; - if (!MalList.is(catchBody) && !MalVector.is(catchBody)) { + if (!isSeq(catchBody)) { throw new Error(`unexpected return type: ${catchBody.type}, expected: list or vector`); } const catchSymbol = catchBody.list[0]; @@ -238,7 +238,7 @@ function evalMal(ast: MalType, env: Env): MalType { } case "fn*": { const [, params, bodyAst] = ast.list; - if (!MalList.is(params) && !MalVector.is(params)) { + if (!isSeq(params)) { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { @@ -252,7 +252,7 @@ function evalMal(ast: MalType, env: Env): MalType { } } const result = evalAST(ast, env); - if (!MalList.is(result) && !MalVector.is(result)) { + if (!isSeq(result)) { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; diff --git a/ts/types.ts b/ts/types.ts index db9bb94504..6b23b3d8f0 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -3,22 +3,14 @@ import { Env } from "./env"; export type MalType = MalList | MalNumber | MalString | MalNull | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap | MalFunction | MalAtom; export function equals(a: MalType, b: MalType, strict?: boolean): boolean { - if (strict && a.constructor !== b.constructor) { + if (strict && a.type !== b.type) { return false; - } else if ( - (MalList.is(a) || MalVector.is(a)) - && (MalList.is(b) || MalVector.is(b)) - ) { - return listEquals(a.list, b.list); } if (MalNull.is(a) && MalNull.is(b)) { return true; } - if ( - (MalList.is(a) && MalList.is(b)) - || (MalVector.is(a) && MalVector.is(b)) - ) { + if (isSeq(a) && isSeq(b)) { return listEquals(a.list, b.list); } if (MalHashMap.is(a) && MalHashMap.is(b)) { @@ -68,6 +60,10 @@ export function equals(a: MalType, b: MalType, strict?: boolean): boolean { } } +export function isSeq(ast: MalType): ast is MalList | MalVector { + return MalList.is(ast) || MalVector.is(ast); +} + export function isAST(v: MalType): v is MalType { return !!v.type; } From 5bb7479da576a5439997ecf7f73e67f577478965 Mon Sep 17 00:00:00 2001 From: vvakame Date: Sat, 25 Feb 2017 14:12:15 +0900 Subject: [PATCH 0305/2308] refactor to using const enum --- ts/core.ts | 114 +++++++++++++++++++++---------------------- ts/printer.ts | 24 ++++----- ts/step2_eval.ts | 14 +++--- ts/step3_env.ts | 16 +++--- ts/step4_if_fn_do.ts | 32 ++++++------ ts/step5_tco.ts | 26 +++++----- ts/step6_file.ts | 26 +++++----- ts/step7_quote.ts | 30 ++++++------ ts/step8_macros.ts | 44 ++++++++--------- ts/step9_try.ts | 48 +++++++++--------- ts/stepA_mal.ts | 48 +++++++++--------- ts/types.ts | 112 ++++++++++++++++-------------------------- 12 files changed, 252 insertions(+), 282 deletions(-) diff --git a/ts/core.ts b/ts/core.ts index 5d8ed93f69..d33cd9e647 100644 --- a/ts/core.ts +++ b/ts/core.ts @@ -2,7 +2,7 @@ import * as fs from "fs"; import { readline } from "./node_readline"; -import { MalType, MalSymbol, MalFunction, MalNull, MalList, MalVector, MalBoolean, MalNumber, MalString, MalKeyword, MalHashMap, MalAtom, equals, isSeq } from "./types"; +import { Node, MalType, MalSymbol, MalFunction, MalNull, MalList, MalVector, MalBoolean, MalNumber, MalString, MalKeyword, MalHashMap, MalAtom, equals, isSeq } from "./types"; import { readStr } from "./reader"; import { prStr } from "./printer"; @@ -16,34 +16,34 @@ export const ns: Map = (() => { }, "nil?"(v: MalType) { - return new MalBoolean(MalNull.is(v)); + return new MalBoolean(v.type === Node.Null); }, "true?"(v: MalType) { - return new MalBoolean(MalBoolean.is(v) && v.v); + return new MalBoolean(v.type === Node.Boolean && v.v); }, "false?"(v: MalType) { - return new MalBoolean(MalBoolean.is(v) && !v.v); + return new MalBoolean(v.type === Node.Boolean && !v.v); }, "string?"(v: MalType) { - return new MalBoolean(MalString.is(v)); + return new MalBoolean(v.type === Node.String); }, symbol(v: MalType) { - if (!MalString.is(v)) { + if (v.type !== Node.String) { throw new Error(`unexpected symbol: ${v.type}, expected: string`); } return MalSymbol.get(v.v); }, "symbol?"(v: MalType) { - return new MalBoolean(MalSymbol.is(v)); + return new MalBoolean(v.type === Node.Symbol); }, keyword(v: MalType) { - if (!MalString.is(v)) { + if (v.type !== Node.String) { throw new Error(`unexpected symbol: ${v.type}, expected: string`); } return MalKeyword.get(v.v); }, "keyword?"(v: MalType) { - return new MalBoolean(MalKeyword.is(v)); + return new MalBoolean(v.type === Node.Keyword); }, "pr-str"(...args: MalType[]): MalString { @@ -63,13 +63,13 @@ export const ns: Map = (() => { return MalNull.instance; }, "read-string"(v: MalType) { - if (!MalString.is(v)) { + if (v.type !== Node.String) { throw new Error(`unexpected symbol: ${v.type}, expected: string`); } return readStr(v.v); }, readline(v: MalType) { - if (!MalString.is(v)) { + if (v.type !== Node.String) { throw new Error(`unexpected symbol: ${v.type}, expected: string`); } @@ -81,7 +81,7 @@ export const ns: Map = (() => { return new MalString(ret); }, slurp(v: MalType) { - if (!MalString.is(v)) { + if (v.type !== Node.String) { throw new Error(`unexpected symbol: ${v.type}, expected: string`); } const content = fs.readFileSync(v.v, "UTF-8"); @@ -89,80 +89,80 @@ export const ns: Map = (() => { }, "<"(a: MalType, b: MalType): MalBoolean { - if (!MalNumber.is(a)) { + if (a.type !== Node.Number) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } - if (!MalNumber.is(b)) { + if (b.type !== Node.Number) { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } return new MalBoolean(a.v < b.v); }, "<="(a: MalType, b: MalType): MalBoolean { - if (!MalNumber.is(a)) { + if (a.type !== Node.Number) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } - if (!MalNumber.is(b)) { + if (b.type !== Node.Number) { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } return new MalBoolean(a.v <= b.v); }, ">"(a: MalType, b: MalType): MalBoolean { - if (!MalNumber.is(a)) { + if (a.type !== Node.Number) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } - if (!MalNumber.is(b)) { + if (b.type !== Node.Number) { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } return new MalBoolean(a.v > b.v); }, ">="(a: MalType, b: MalType): MalBoolean { - if (!MalNumber.is(a)) { + if (a.type !== Node.Number) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } - if (!MalNumber.is(b)) { + if (b.type !== Node.Number) { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } return new MalBoolean(a.v >= b.v); }, "+"(a: MalType, b: MalType): MalNumber { - if (!MalNumber.is(a)) { + if (a.type !== Node.Number) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } - if (!MalNumber.is(b)) { + if (b.type !== Node.Number) { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } return new MalNumber(a.v + b.v); }, "-"(a: MalType, b: MalType): MalNumber { - if (!MalNumber.is(a)) { + if (a.type !== Node.Number) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } - if (!MalNumber.is(b)) { + if (b.type !== Node.Number) { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } return new MalNumber(a.v - b.v); }, "*"(a: MalType, b: MalType): MalNumber { - if (!MalNumber.is(a)) { + if (a.type !== Node.Number) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } - if (!MalNumber.is(b)) { + if (b.type !== Node.Number) { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } return new MalNumber(a.v * b.v); }, "/"(a: MalType, b: MalType): MalNumber { - if (!MalNumber.is(a)) { + if (a.type !== Node.Number) { throw new Error(`unexpected symbol: ${a.type}, expected: number`); } - if (!MalNumber.is(b)) { + if (b.type !== Node.Number) { throw new Error(`unexpected symbol: ${b.type}, expected: number`); } @@ -182,61 +182,61 @@ export const ns: Map = (() => { return new MalVector(args); }, "vector?"(v: MalType): MalBoolean { - return new MalBoolean(MalVector.is(v)); + return new MalBoolean(v.type === Node.Vector); }, "hash-map"(...args: MalType[]) { return new MalHashMap(args); }, "map?"(v: MalType): MalBoolean { - return new MalBoolean(MalHashMap.is(v)); + return new MalBoolean(v.type === Node.HashMap); }, assoc(v: MalType, ...args: MalType[]) { - if (!MalHashMap.is(v)) { + if (v.type !== Node.HashMap) { throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`); } return v.assoc(args); }, dissoc(v: MalType, ...args: MalType[]) { - if (!MalHashMap.is(v)) { + if (v.type !== Node.HashMap) { throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`); } return v.dissoc(args); }, get(v: MalType, key: MalType) { - if (MalNull.is(v)) { + if (v.type === Node.Null) { return MalNull.instance; } - if (!MalHashMap.is(v)) { + if (v.type !== Node.HashMap) { throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`); } - if (!MalString.is(key) && !MalKeyword.is(key)) { + if (key.type !== Node.String && key.type !== Node.Keyword) { throw new Error(`unexpected symbol: ${key.type}, expected: string or keyword`); } return v.get(key) || MalNull.instance; }, "contains?"(v: MalType, key: MalType) { - if (MalNull.is(v)) { + if (v.type === Node.Null) { return MalNull.instance; } - if (!MalHashMap.is(v)) { + if (v.type !== Node.HashMap) { throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`); } - if (!MalString.is(key) && !MalKeyword.is(key)) { + if (key.type !== Node.String && key.type !== Node.Keyword) { throw new Error(`unexpected symbol: ${key.type}, expected: string or keyword`); } return new MalBoolean(v.has(key)); }, keys(v: MalType) { - if (!MalHashMap.is(v)) { + if (v.type !== Node.HashMap) { throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`); } return new MalList([...v.keys()]); }, vals(v: MalType) { - if (!MalHashMap.is(v)) { + if (v.type !== Node.HashMap) { throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`); } @@ -269,7 +269,7 @@ export const ns: Map = (() => { if (!isSeq(list)) { throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`); } - if (!MalNumber.is(idx)) { + if (idx.type !== Node.Number) { throw new Error(`unexpected symbol: ${idx.type}, expected: number`); } @@ -281,7 +281,7 @@ export const ns: Map = (() => { return v; }, first(v: MalType) { - if (MalNull.is(v)) { + if (v.type === Node.Null) { return MalNull.instance; } if (!isSeq(v)) { @@ -291,7 +291,7 @@ export const ns: Map = (() => { return v.list[0] || MalNull.instance; }, rest(v: MalType) { - if (MalNull.is(v)) { + if (v.type === Node.Null) { return new MalList([]); } if (!isSeq(v)) { @@ -310,13 +310,13 @@ export const ns: Map = (() => { if (isSeq(v)) { return new MalNumber(v.list.length); } - if (MalNull.is(v)) { + if (v.type === Node.Null) { return new MalNumber(0); } throw new Error(`unexpected symbol: ${v.type}`); }, apply(f: MalType, ...list: MalType[]) { - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected symbol: ${f.type}, expected: function`); } @@ -328,7 +328,7 @@ export const ns: Map = (() => { return f.func(...args); }, map(f: MalType, list: MalType) { - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected symbol: ${f.type}, expected: function`); } if (!isSeq(list)) { @@ -340,36 +340,36 @@ export const ns: Map = (() => { conj(list: MalType, ...args: MalType[]) { switch (list.type) { - case "list": + case Node.List: const newList = new MalList(list.list); args.forEach(arg => newList.list.unshift(arg)); return newList; - case "vector": + case Node.Vector: return new MalVector([...list.list, ...args]); } throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`); }, seq(v: MalType) { - if (MalList.is(v)) { + if (v.type === Node.List) { if (v.list.length === 0) { return MalNull.instance; } return v; } - if (MalVector.is(v)) { + if (v.type === Node.Vector) { if (v.list.length === 0) { return MalNull.instance; } return new MalList(v.list); } - if (MalString.is(v)) { + if (v.type === Node.String) { if (v.v.length === 0) { return MalNull.instance; } return new MalList(v.v.split("").map(s => new MalString(s))); } - if (MalNull.is(v)) { + if (v.type === Node.Null) { return MalNull.instance; } @@ -386,26 +386,26 @@ export const ns: Map = (() => { return new MalAtom(v); }, "atom?"(v: MalType): MalBoolean { - return new MalBoolean(MalAtom.is(v)); + return new MalBoolean(v.type === Node.Atom); }, deref(v: MalType): MalType { - if (!MalAtom.is(v)) { + if (v.type !== Node.Atom) { throw new Error(`unexpected symbol: ${v.type}, expected: atom`); } return v.v; }, "reset!"(atom: MalType, v: MalType): MalType { - if (!MalAtom.is(atom)) { + if (atom.type !== Node.Atom) { throw new Error(`unexpected symbol: ${atom.type}, expected: atom`); } atom.v = v; return v; }, "swap!"(atom: MalType, f: MalType, ...args: MalType[]): MalType { - if (!MalAtom.is(atom)) { + if (atom.type !== Node.Atom) { throw new Error(`unexpected symbol: ${atom.type}, expected: atom`); } - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected symbol: ${f.type}, expected: function`); } atom.v = f.func(...[atom.v].concat(args)); diff --git a/ts/printer.ts b/ts/printer.ts index b97f379bff..24c48dd18b 100644 --- a/ts/printer.ts +++ b/ts/printer.ts @@ -1,12 +1,12 @@ -import { MalType } from "./types"; +import { Node, MalType } from "./types"; export function prStr(v: MalType, printReadably = true): string { switch (v.type) { - case "list": + case Node.List: return `(${v.list.map(v => prStr(v, printReadably)).join(" ")})`; - case "vector": + case Node.Vector: return `[${v.list.map(v => prStr(v, printReadably)).join(" ")}]`; - case "hash-map": + case Node.HashMap: let result = "{"; for (const [key, value] of v.entries()) { if (result !== "{") { @@ -16,11 +16,11 @@ export function prStr(v: MalType, printReadably = true): string { } result += "}"; return result; - case "number": - case "symbol": - case "boolean": + case Node.Number: + case Node.Symbol: + case Node.Boolean: return `${v.v}`; - case "string": + case Node.String: if (printReadably) { const str = v.v .replace(/\\/g, "\\\\") @@ -30,13 +30,13 @@ export function prStr(v: MalType, printReadably = true): string { } else { return v.v; } - case "null": + case Node.Null: return "nil"; - case "keyword": + case Node.Keyword: return `:${v.v}`; - case "function": + case Node.Function: return "#"; - case "atom": + case Node.Atom: return `(atom ${prStr(v.v, printReadably)})`; } } diff --git a/ts/step2_eval.ts b/ts/step2_eval.ts index 7449f01a0f..bacbd109df 100644 --- a/ts/step2_eval.ts +++ b/ts/step2_eval.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalNumber, MalList, MalVector, MalHashMap, MalFunction } from "./types"; +import { Node, MalType, MalNumber, MalList, MalVector, MalHashMap, MalFunction } from "./types"; import { readStr } from "./reader"; import { prStr } from "./printer"; @@ -15,17 +15,17 @@ interface MalEnvironment { function evalAST(ast: MalType, env: MalEnvironment): MalType { switch (ast.type) { - case "symbol": + case Node.Symbol: const f = env[ast.v]; if (!f) { throw new Error(`unknown symbol: ${ast.v}`); } return f; - case "list": + case Node.List: return new MalList(ast.list.map(ast => evalMal(ast, env))); - case "vector": + case Node.Vector: return new MalVector(ast.list.map(ast => evalMal(ast, env))); - case "hash-map": + case Node.HashMap: const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); @@ -39,7 +39,7 @@ function evalAST(ast: MalType, env: MalEnvironment): MalType { // EVAL function evalMal(ast: MalType, env: MalEnvironment): MalType { - if (ast.type !== "list") { + if (ast.type !== Node.List) { return evalAST(ast, env); } if (ast.list.length === 0) { @@ -47,7 +47,7 @@ function evalMal(ast: MalType, env: MalEnvironment): MalType { } const result = evalAST(ast, env) as MalList; const [f, ...args] = result.list; - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected token: ${f.type}, expected: function`); } return f.func(...args); diff --git a/ts/step3_env.ts b/ts/step3_env.ts index da4622adae..971b9cca60 100644 --- a/ts/step3_env.ts +++ b/ts/step3_env.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalNumber, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { Node, MalType, MalNumber, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; import { Env } from "./env"; import { readStr } from "./reader"; import { prStr } from "./printer"; @@ -12,17 +12,17 @@ function read(str: string): MalType { function evalAST(ast: MalType, env: Env): MalType { switch (ast.type) { - case "symbol": + case Node.Symbol: const f = env.get(ast); if (!f) { throw new Error(`unknown symbol: ${ast.v}`); } return f; - case "list": + case Node.List: return new MalList(ast.list.map(ast => evalMal(ast, env))); - case "vector": + case Node.Vector: return new MalVector(ast.list.map(ast => evalMal(ast, env))); - case "hash-map": + case Node.HashMap: const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); @@ -36,7 +36,7 @@ function evalAST(ast: MalType, env: Env): MalType { // EVAL function evalMal(ast: MalType, env: Env): MalType { - if (ast.type !== "list") { + if (ast.type !== Node.List) { return evalAST(ast, env); } if (ast.list.length === 0) { @@ -44,7 +44,7 @@ function evalMal(ast: MalType, env: Env): MalType { } const first = ast.list[0]; switch (first.type) { - case "symbol": + case Node.Symbol: switch (first.v) { case "def!": { const [, key, value] = ast.list; @@ -78,7 +78,7 @@ function evalMal(ast: MalType, env: Env): MalType { } const result = evalAST(ast, env) as MalList; const [f, ...args] = result.list; - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected token: ${f.type}, expected: function`); } return f.func(...args); diff --git a/ts/step4_if_fn_do.ts b/ts/step4_if_fn_do.ts index 65d8192816..0733fd91fd 100644 --- a/ts/step4_if_fn_do.ts +++ b/ts/step4_if_fn_do.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; +import { Node, MalType, MalNull, MalList, MalVector, MalHashMap, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -13,17 +13,17 @@ function read(str: string): MalType { function evalAST(ast: MalType, env: Env): MalType { switch (ast.type) { - case "symbol": + case Node.Symbol: const f = env.get(ast); if (!f) { throw new Error(`unknown symbol: ${ast.v}`); } return f; - case "list": + case Node.List: return new MalList(ast.list.map(ast => evalMal(ast, env))); - case "vector": + case Node.Vector: return new MalVector(ast.list.map(ast => evalMal(ast, env))); - case "hash-map": + case Node.HashMap: const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); @@ -37,7 +37,7 @@ function evalAST(ast: MalType, env: Env): MalType { // EVAL function evalMal(ast: MalType, env: Env): MalType { - if (ast.type !== "list") { + if (ast.type !== Node.List) { return evalAST(ast, env); } if (ast.list.length === 0) { @@ -45,11 +45,11 @@ function evalMal(ast: MalType, env: Env): MalType { } const first = ast.list[0]; switch (first.type) { - case "symbol": + case Node.Symbol: switch (first.v) { case "def!": { const [, key, value] = ast.list; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!value) { @@ -66,7 +66,7 @@ function evalMal(ast: MalType, env: Env): MalType { for (let i = 0; i < pairs.list.length; i += 2) { const key = pairs.list[i]; const value = pairs.list[i + 1]; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!key || !value) { @@ -89,9 +89,9 @@ function evalMal(ast: MalType, env: Env): MalType { const [, cond, thenExpr, elseExrp] = ast.list; const ret = evalMal(cond, env); let b = true; - if (MalBoolean.is(ret) && !ret.v) { + if (ret.type === Node.Boolean && !ret.v) { b = false; - } else if (MalNull.is(ret)) { + } else if (ret.type === Node.Null) { b = false; } if (b) { @@ -107,11 +107,11 @@ function evalMal(ast: MalType, env: Env): MalType { if (!isSeq(args)) { throw new Error(`unexpected return type: ${args.type}, expected: list or vector`); } - const symbols = args.list.map(arg => { - if (!MalSymbol.is(arg)) { - throw new Error(`unexpected return type: ${arg.type}, expected: symbol`); + const symbols = args.list.map(param => { + if (param.type !== Node.Symbol) { + throw new Error(`unexpected return type: ${param.type}, expected: symbol`); } - return arg; + return param; }); return MalFunction.fromBootstrap((...fnArgs: MalType[]) => { return evalMal(binds, new Env(env, symbols, fnArgs)); @@ -124,7 +124,7 @@ function evalMal(ast: MalType, env: Env): MalType { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected token: ${f.type}, expected: function`); } return f.func(...args); diff --git a/ts/step5_tco.ts b/ts/step5_tco.ts index 714c406ba1..1efd6ed00c 100644 --- a/ts/step5_tco.ts +++ b/ts/step5_tco.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; +import { Node, MalType, MalNull, MalList, MalVector, MalHashMap, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -13,17 +13,17 @@ function read(str: string): MalType { function evalAST(ast: MalType, env: Env): MalType { switch (ast.type) { - case "symbol": + case Node.Symbol: const f = env.get(ast); if (!f) { throw new Error(`unknown symbol: ${ast.v}`); } return f; - case "list": + case Node.List: return new MalList(ast.list.map(ast => evalMal(ast, env))); - case "vector": + case Node.Vector: return new MalVector(ast.list.map(ast => evalMal(ast, env))); - case "hash-map": + case Node.HashMap: const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); @@ -38,7 +38,7 @@ function evalAST(ast: MalType, env: Env): MalType { // EVAL function evalMal(ast: MalType, env: Env): MalType { loop: while (true) { - if (ast.type !== "list") { + if (ast.type !== Node.List) { return evalAST(ast, env); } if (ast.list.length === 0) { @@ -46,11 +46,11 @@ function evalMal(ast: MalType, env: Env): MalType { } const first = ast.list[0]; switch (first.type) { - case "symbol": + case Node.Symbol: switch (first.v) { case "def!": { const [, key, value] = ast.list; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!value) { @@ -67,7 +67,7 @@ function evalMal(ast: MalType, env: Env): MalType { for (let i = 0; i < pairs.list.length; i += 2) { const key = pairs.list[i]; const value = pairs.list[i + 1]; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!key || !value) { @@ -89,9 +89,9 @@ function evalMal(ast: MalType, env: Env): MalType { const [, cond, thenExpr, elseExrp] = ast.list; const ret = evalMal(cond, env); let b = true; - if (MalBoolean.is(ret) && !ret.v) { + if (ret.type === Node.Boolean && !ret.v) { b = false; - } else if (MalNull.is(ret)) { + } else if (ret.type === Node.Null) { b = false; } if (b) { @@ -109,7 +109,7 @@ function evalMal(ast: MalType, env: Env): MalType { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { - if (!MalSymbol.is(param)) { + if (param.type !== Node.Symbol) { throw new Error(`unexpected return type: ${param.type}, expected: symbol`); } return param; @@ -123,7 +123,7 @@ function evalMal(ast: MalType, env: Env): MalType { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected token: ${f.type}, expected: function`); } if (f.ast) { diff --git a/ts/step6_file.ts b/ts/step6_file.ts index c11fd5e9f6..7e9b11e1d8 100644 --- a/ts/step6_file.ts +++ b/ts/step6_file.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; +import { Node, MalType, MalString, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -13,17 +13,17 @@ function read(str: string): MalType { function evalAST(ast: MalType, env: Env): MalType { switch (ast.type) { - case "symbol": + case Node.Symbol: const f = env.get(ast); if (!f) { throw new Error(`unknown symbol: ${ast.v}`); } return f; - case "list": + case Node.List: return new MalList(ast.list.map(ast => evalMal(ast, env))); - case "vector": + case Node.Vector: return new MalVector(ast.list.map(ast => evalMal(ast, env))); - case "hash-map": + case Node.HashMap: const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); @@ -38,7 +38,7 @@ function evalAST(ast: MalType, env: Env): MalType { // EVAL function evalMal(ast: MalType, env: Env): MalType { loop: while (true) { - if (ast.type !== "list") { + if (ast.type !== Node.List) { return evalAST(ast, env); } if (ast.list.length === 0) { @@ -46,11 +46,11 @@ function evalMal(ast: MalType, env: Env): MalType { } const first = ast.list[0]; switch (first.type) { - case "symbol": + case Node.Symbol: switch (first.v) { case "def!": { const [, key, value] = ast.list; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!value) { @@ -67,7 +67,7 @@ function evalMal(ast: MalType, env: Env): MalType { for (let i = 0; i < pairs.list.length; i += 2) { const key = pairs.list[i]; const value = pairs.list[i + 1]; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!key || !value) { @@ -89,9 +89,9 @@ function evalMal(ast: MalType, env: Env): MalType { const [, cond, thenExpr, elseExrp] = ast.list; const ret = evalMal(cond, env); let b = true; - if (MalBoolean.is(ret) && !ret.v) { + if (ret.type === Node.Boolean && !ret.v) { b = false; - } else if (MalNull.is(ret)) { + } else if (ret.type === Node.Null) { b = false; } if (b) { @@ -109,7 +109,7 @@ function evalMal(ast: MalType, env: Env): MalType { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { - if (!MalSymbol.is(param)) { + if (param.type !== Node.Symbol) { throw new Error(`unexpected return type: ${param.type}, expected: symbol`); } return param; @@ -123,7 +123,7 @@ function evalMal(ast: MalType, env: Env): MalType { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected token: ${f.type}, expected: function`); } if (f.ast) { diff --git a/ts/step7_quote.ts b/ts/step7_quote.ts index dfac5e8b9b..e25075b6fc 100644 --- a/ts/step7_quote.ts +++ b/ts/step7_quote.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; +import { Node, MalType, MalString, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -19,7 +19,7 @@ function quasiquote(ast: MalType): MalType { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const [arg1, arg2] = ast.list; - if (MalSymbol.is(arg1) && arg1.v === "unquote") { + if (arg1.type === Node.Symbol && arg1.v === "unquote") { return arg2; } if (isPair(arg1)) { @@ -27,7 +27,7 @@ function quasiquote(ast: MalType): MalType { throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); } const [arg11, arg12] = arg1.list; - if (MalSymbol.is(arg11) && arg11.v === "splice-unquote") { + if (arg11.type === Node.Symbol && arg11.v === "splice-unquote") { return new MalList([ MalSymbol.get("concat"), arg12, @@ -53,17 +53,17 @@ function quasiquote(ast: MalType): MalType { function evalAST(ast: MalType, env: Env): MalType { switch (ast.type) { - case "symbol": + case Node.Symbol: const f = env.get(ast); if (!f) { throw new Error(`unknown symbol: ${ast.v}`); } return f; - case "list": + case Node.List: return new MalList(ast.list.map(ast => evalMal(ast, env))); - case "vector": + case Node.Vector: return new MalVector(ast.list.map(ast => evalMal(ast, env))); - case "hash-map": + case Node.HashMap: const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); @@ -78,7 +78,7 @@ function evalAST(ast: MalType, env: Env): MalType { // EVAL function evalMal(ast: MalType, env: Env): MalType { loop: while (true) { - if (ast.type !== "list") { + if (ast.type !== Node.List) { return evalAST(ast, env); } if (ast.list.length === 0) { @@ -86,11 +86,11 @@ function evalMal(ast: MalType, env: Env): MalType { } const first = ast.list[0]; switch (first.type) { - case "symbol": + case Node.Symbol: switch (first.v) { case "def!": { const [, key, value] = ast.list; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!value) { @@ -107,7 +107,7 @@ function evalMal(ast: MalType, env: Env): MalType { for (let i = 0; i < pairs.list.length; i += 2) { const key = pairs.list[i]; const value = pairs.list[i + 1]; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!key || !value) { @@ -136,9 +136,9 @@ function evalMal(ast: MalType, env: Env): MalType { const [, cond, thenExpr, elseExrp] = ast.list; const ret = evalMal(cond, env); let b = true; - if (MalBoolean.is(ret) && !ret.v) { + if (ret.type === Node.Boolean && !ret.v) { b = false; - } else if (MalNull.is(ret)) { + } else if (ret.type === Node.Null) { b = false; } if (b) { @@ -156,7 +156,7 @@ function evalMal(ast: MalType, env: Env): MalType { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { - if (!MalSymbol.is(param)) { + if (param.type !== Node.Symbol) { throw new Error(`unexpected return type: ${param.type}, expected: symbol`); } return param; @@ -170,7 +170,7 @@ function evalMal(ast: MalType, env: Env): MalType { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected token: ${f.type}, expected: function`); } if (f.ast) { diff --git a/ts/step8_macros.ts b/ts/step8_macros.ts index e599dfe8c0..b13944e532 100644 --- a/ts/step8_macros.ts +++ b/ts/step8_macros.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; +import { Node, MalType, MalString, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -19,7 +19,7 @@ function quasiquote(ast: MalType): MalType { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const [arg1, arg2] = ast.list; - if (MalSymbol.is(arg1) && arg1.v === "unquote") { + if (arg1.type === Node.Symbol && arg1.v === "unquote") { return arg2; } if (isPair(arg1)) { @@ -27,7 +27,7 @@ function quasiquote(ast: MalType): MalType { throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); } const [arg11, arg12] = arg1.list; - if (MalSymbol.is(arg11) && arg11.v === "splice-unquote") { + if (arg11.type === Node.Symbol && arg11.v === "splice-unquote") { return new MalList([ MalSymbol.get("concat"), arg12, @@ -56,7 +56,7 @@ function isMacro(ast: MalType, env: Env): boolean { return false; } const s = ast.list[0]; - if (!MalSymbol.is(s)) { + if (s.type !== Node.Symbol) { return false; } const foundEnv = env.find(s); @@ -65,7 +65,7 @@ function isMacro(ast: MalType, env: Env): boolean { } const f = foundEnv.get(s); - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { return false; } @@ -78,11 +78,11 @@ function macroexpand(ast: MalType, env: Env): MalType { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const s = ast.list[0]; - if (!MalSymbol.is(s)) { + if (s.type !== Node.Symbol) { throw new Error(`unexpected token type: ${s.type}, expected: symbol`); } const f = env.get(s); - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected token type: ${f.type}, expected: function`); } ast = f.func(...ast.list.slice(1)); @@ -93,17 +93,17 @@ function macroexpand(ast: MalType, env: Env): MalType { function evalAST(ast: MalType, env: Env): MalType { switch (ast.type) { - case "symbol": + case Node.Symbol: const f = env.get(ast); if (!f) { throw new Error(`unknown symbol: ${ast.v}`); } return f; - case "list": + case Node.List: return new MalList(ast.list.map(ast => evalMal(ast, env))); - case "vector": + case Node.Vector: return new MalVector(ast.list.map(ast => evalMal(ast, env))); - case "hash-map": + case Node.HashMap: const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); @@ -118,12 +118,12 @@ function evalAST(ast: MalType, env: Env): MalType { // EVAL function evalMal(ast: MalType, env: Env): MalType { loop: while (true) { - if (ast.type !== "list") { + if (ast.type !== Node.List) { return evalAST(ast, env); } ast = macroexpand(ast, env); - if (ast.type !== "list" && ast.type !== "vector") { + if (!isSeq(ast)) { return evalAST(ast, env); } @@ -132,11 +132,11 @@ function evalMal(ast: MalType, env: Env): MalType { } const first = ast.list[0]; switch (first.type) { - case "symbol": + case Node.Symbol: switch (first.v) { case "def!": { const [, key, value] = ast.list; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!value) { @@ -153,7 +153,7 @@ function evalMal(ast: MalType, env: Env): MalType { for (let i = 0; i < pairs.list.length; i += 2) { const key = pairs.list[i]; const value = pairs.list[i + 1]; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!key || !value) { @@ -174,14 +174,14 @@ function evalMal(ast: MalType, env: Env): MalType { } case "defmacro!": { const [, key, value] = ast.list; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!value) { throw new Error(`unexpected syntax`); } const f = evalMal(value, env); - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected token type: ${f.type}, expected: function`); } f.isMacro = true; @@ -200,9 +200,9 @@ function evalMal(ast: MalType, env: Env): MalType { const [, cond, thenExpr, elseExrp] = ast.list; const ret = evalMal(cond, env); let b = true; - if (MalBoolean.is(ret) && !ret.v) { + if (ret.type === Node.Boolean && !ret.v) { b = false; - } else if (MalNull.is(ret)) { + } else if (ret.type === Node.Null) { b = false; } if (b) { @@ -220,7 +220,7 @@ function evalMal(ast: MalType, env: Env): MalType { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { - if (!MalSymbol.is(param)) { + if (param.type !== Node.Symbol) { throw new Error(`unexpected return type: ${param.type}, expected: symbol`); } return param; @@ -234,7 +234,7 @@ function evalMal(ast: MalType, env: Env): MalType { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected token: ${f.type}, expected: function`); } if (f.ast) { diff --git a/ts/step9_try.ts b/ts/step9_try.ts index 0f281877c8..162123055f 100644 --- a/ts/step9_try.ts +++ b/ts/step9_try.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; +import { Node, MalType, MalString, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -19,7 +19,7 @@ function quasiquote(ast: MalType): MalType { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const [arg1, arg2] = ast.list; - if (MalSymbol.is(arg1) && arg1.v === "unquote") { + if (arg1.type === Node.Symbol && arg1.v === "unquote") { return arg2; } if (isPair(arg1)) { @@ -27,7 +27,7 @@ function quasiquote(ast: MalType): MalType { throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); } const [arg11, arg12] = arg1.list; - if (MalSymbol.is(arg11) && arg11.v === "splice-unquote") { + if (arg11.type === Node.Symbol && arg11.v === "splice-unquote") { return new MalList([ MalSymbol.get("concat"), arg12, @@ -56,7 +56,7 @@ function isMacro(ast: MalType, env: Env): boolean { return false; } const s = ast.list[0]; - if (!MalSymbol.is(s)) { + if (s.type !== Node.Symbol) { return false; } const foundEnv = env.find(s); @@ -65,7 +65,7 @@ function isMacro(ast: MalType, env: Env): boolean { } const f = foundEnv.get(s); - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { return false; } @@ -78,11 +78,11 @@ function macroexpand(ast: MalType, env: Env): MalType { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const s = ast.list[0]; - if (!MalSymbol.is(s)) { + if (s.type !== Node.Symbol) { throw new Error(`unexpected token type: ${s.type}, expected: symbol`); } const f = env.get(s); - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected token type: ${f.type}, expected: function`); } ast = f.func(...ast.list.slice(1)); @@ -93,17 +93,17 @@ function macroexpand(ast: MalType, env: Env): MalType { function evalAST(ast: MalType, env: Env): MalType { switch (ast.type) { - case "symbol": + case Node.Symbol: const f = env.get(ast); if (!f) { throw new Error(`unknown symbol: ${ast.v}`); } return f; - case "list": + case Node.List: return new MalList(ast.list.map(ast => evalMal(ast, env))); - case "vector": + case Node.Vector: return new MalVector(ast.list.map(ast => evalMal(ast, env))); - case "hash-map": + case Node.HashMap: const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); @@ -118,12 +118,12 @@ function evalAST(ast: MalType, env: Env): MalType { // EVAL function evalMal(ast: MalType, env: Env): MalType { loop: while (true) { - if (ast.type !== "list") { + if (ast.type !== Node.List) { return evalAST(ast, env); } ast = macroexpand(ast, env); - if (ast.type !== "list" && ast.type !== "vector") { + if (!isSeq(ast)) { return evalAST(ast, env); } @@ -132,11 +132,11 @@ function evalMal(ast: MalType, env: Env): MalType { } const first = ast.list[0]; switch (first.type) { - case "symbol": + case Node.Symbol: switch (first.v) { case "def!": { const [, key, value] = ast.list; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!value) { @@ -153,7 +153,7 @@ function evalMal(ast: MalType, env: Env): MalType { for (let i = 0; i < pairs.list.length; i += 2) { const key = pairs.list[i]; const value = pairs.list[i + 1]; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!key || !value) { @@ -174,14 +174,14 @@ function evalMal(ast: MalType, env: Env): MalType { } case "defmacro!": { const [, key, value] = ast.list; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!value) { throw new Error(`unexpected syntax`); } const f = evalMal(value, env); - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected token type: ${f.type}, expected: function`); } f.isMacro = true; @@ -199,9 +199,9 @@ function evalMal(ast: MalType, env: Env): MalType { throw new Error(`unexpected return type: ${catchBody.type}, expected: list or vector`); } const catchSymbol = catchBody.list[0]; - if (MalSymbol.is(catchSymbol) && catchSymbol.v === "catch*") { + if (catchSymbol.type === Node.Symbol && catchSymbol.v === "catch*") { const errorSymbol = catchBody.list[1]; - if (!MalSymbol.is(errorSymbol)) { + if (errorSymbol.type !== Node.Symbol) { throw new Error(`unexpected return type: ${errorSymbol.type}, expected: symbol`); } if (!isAST(e)) { @@ -222,9 +222,9 @@ function evalMal(ast: MalType, env: Env): MalType { const [, cond, thenExpr, elseExrp] = ast.list; const ret = evalMal(cond, env); let b = true; - if (MalBoolean.is(ret) && !ret.v) { + if (ret.type === Node.Boolean && !ret.v) { b = false; - } else if (MalNull.is(ret)) { + } else if (ret.type === Node.Null) { b = false; } if (b) { @@ -242,7 +242,7 @@ function evalMal(ast: MalType, env: Env): MalType { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { - if (!MalSymbol.is(param)) { + if (param.type !== Node.Symbol) { throw new Error(`unexpected return type: ${param.type}, expected: symbol`); } return param; @@ -256,7 +256,7 @@ function evalMal(ast: MalType, env: Env): MalType { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected token: ${f.type}, expected: function`); } if (f.ast) { diff --git a/ts/stepA_mal.ts b/ts/stepA_mal.ts index 3f311a3eab..72b18765a2 100644 --- a/ts/stepA_mal.ts +++ b/ts/stepA_mal.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; +import { Node, MalType, MalString, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -19,7 +19,7 @@ function quasiquote(ast: MalType): MalType { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const [arg1, arg2] = ast.list; - if (MalSymbol.is(arg1) && arg1.v === "unquote") { + if (arg1.type === Node.Symbol && arg1.v === "unquote") { return arg2; } if (isPair(arg1)) { @@ -27,7 +27,7 @@ function quasiquote(ast: MalType): MalType { throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); } const [arg11, arg12] = arg1.list; - if (MalSymbol.is(arg11) && arg11.v === "splice-unquote") { + if (arg11.type === Node.Symbol && arg11.v === "splice-unquote") { return new MalList([ MalSymbol.get("concat"), arg12, @@ -56,7 +56,7 @@ function isMacro(ast: MalType, env: Env): boolean { return false; } const s = ast.list[0]; - if (!MalSymbol.is(s)) { + if (s.type !== Node.Symbol) { return false; } const foundEnv = env.find(s); @@ -65,7 +65,7 @@ function isMacro(ast: MalType, env: Env): boolean { } const f = foundEnv.get(s); - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { return false; } @@ -78,11 +78,11 @@ function macroexpand(ast: MalType, env: Env): MalType { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const s = ast.list[0]; - if (!MalSymbol.is(s)) { + if (s.type !== Node.Symbol) { throw new Error(`unexpected token type: ${s.type}, expected: symbol`); } const f = env.get(s); - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected token type: ${f.type}, expected: function`); } ast = f.func(...ast.list.slice(1)); @@ -93,17 +93,17 @@ function macroexpand(ast: MalType, env: Env): MalType { function evalAST(ast: MalType, env: Env): MalType { switch (ast.type) { - case "symbol": + case Node.Symbol: const f = env.get(ast); if (!f) { throw new Error(`unknown symbol: ${ast.v}`); } return f; - case "list": + case Node.List: return new MalList(ast.list.map(ast => evalMal(ast, env))); - case "vector": + case Node.Vector: return new MalVector(ast.list.map(ast => evalMal(ast, env))); - case "hash-map": + case Node.HashMap: const list: MalType[] = []; for (const [key, value] of ast.entries()) { list.push(key); @@ -118,12 +118,12 @@ function evalAST(ast: MalType, env: Env): MalType { // EVAL function evalMal(ast: MalType, env: Env): MalType { loop: while (true) { - if (ast.type !== "list") { + if (ast.type !== Node.List) { return evalAST(ast, env); } ast = macroexpand(ast, env); - if (ast.type !== "list" && ast.type !== "vector") { + if (!isSeq(ast)) { return evalAST(ast, env); } @@ -132,11 +132,11 @@ function evalMal(ast: MalType, env: Env): MalType { } const first = ast.list[0]; switch (first.type) { - case "symbol": + case Node.Symbol: switch (first.v) { case "def!": { const [, key, value] = ast.list; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!value) { @@ -153,7 +153,7 @@ function evalMal(ast: MalType, env: Env): MalType { for (let i = 0; i < pairs.list.length; i += 2) { const key = pairs.list[i]; const value = pairs.list[i + 1]; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!key || !value) { @@ -174,14 +174,14 @@ function evalMal(ast: MalType, env: Env): MalType { } case "defmacro!": { const [, key, value] = ast.list; - if (!MalSymbol.is(key)) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected token type: ${key.type}, expected: symbol`); } if (!value) { throw new Error(`unexpected syntax`); } const f = evalMal(value, env); - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected token type: ${f.type}, expected: function`); } f.isMacro = true; @@ -199,9 +199,9 @@ function evalMal(ast: MalType, env: Env): MalType { throw new Error(`unexpected return type: ${catchBody.type}, expected: list or vector`); } const catchSymbol = catchBody.list[0]; - if (MalSymbol.is(catchSymbol) && catchSymbol.v === "catch*") { + if (catchSymbol.type === Node.Symbol && catchSymbol.v === "catch*") { const errorSymbol = catchBody.list[1]; - if (!MalSymbol.is(errorSymbol)) { + if (errorSymbol.type !== Node.Symbol) { throw new Error(`unexpected return type: ${errorSymbol.type}, expected: symbol`); } if (!isAST(e)) { @@ -222,9 +222,9 @@ function evalMal(ast: MalType, env: Env): MalType { const [, cond, thenExpr, elseExrp] = ast.list; const ret = evalMal(cond, env); let b = true; - if (MalBoolean.is(ret) && !ret.v) { + if (ret.type === Node.Boolean && !ret.v) { b = false; - } else if (MalNull.is(ret)) { + } else if (ret.type === Node.Null) { b = false; } if (b) { @@ -242,7 +242,7 @@ function evalMal(ast: MalType, env: Env): MalType { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { - if (!MalSymbol.is(param)) { + if (param.type !== Node.Symbol) { throw new Error(`unexpected return type: ${param.type}, expected: symbol`); } return param; @@ -256,7 +256,7 @@ function evalMal(ast: MalType, env: Env): MalType { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; - if (!MalFunction.is(f)) { + if (f.type !== Node.Function) { throw new Error(`unexpected token: ${f.type}, expected: function`); } if (f.ast) { diff --git a/ts/types.ts b/ts/types.ts index 6b23b3d8f0..8368e2c5a3 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -2,18 +2,32 @@ import { Env } from "./env"; export type MalType = MalList | MalNumber | MalString | MalNull | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap | MalFunction | MalAtom; +export const enum Node { + List = 1, + Number, + String, + Null, + Boolean, + Symbol, + Keyword, + Vector, + HashMap, + Function, + Atom, +} + export function equals(a: MalType, b: MalType, strict?: boolean): boolean { if (strict && a.type !== b.type) { return false; } - if (MalNull.is(a) && MalNull.is(b)) { + if (a.type === Node.Null && b.type === Node.Null) { return true; } if (isSeq(a) && isSeq(b)) { return listEquals(a.list, b.list); } - if (MalHashMap.is(a) && MalHashMap.is(b)) { + if (a.type === Node.HashMap && b.type === Node.HashMap) { if (a.keywordMap.size !== b.keywordMap.size) { return false; } @@ -21,11 +35,11 @@ export function equals(a: MalType, b: MalType, strict?: boolean): boolean { return false; } for (const [aK, aV] of a.entries()) { - if (!MalString.is(aK) && !MalKeyword.is(aK)) { + if (aK.type !== Node.String && aK.type !== Node.Keyword) { throw new Error(`unexpected symbol: ${aK.type}, expected: string or keyword`); } const bV = b.get(aK); - if (MalNull.is(aV) && MalNull.is(bV)) { + if (aV.type === Node.Null && bV.type === Node.Null) { continue; } if (!equals(aV, bV)) { @@ -36,11 +50,11 @@ export function equals(a: MalType, b: MalType, strict?: boolean): boolean { return true; } if ( - (MalNumber.is(a) && MalNumber.is(b)) - || (MalString.is(a) && MalString.is(b)) - || (MalBoolean.is(a) && MalBoolean.is(b)) - || (MalSymbol.is(a) && MalSymbol.is(b)) - || (MalKeyword.is(a) && MalKeyword.is(b)) + (a.type === Node.Number && b.type === Node.Number) + || (a.type === Node.String && b.type === Node.String) + || (a.type === Node.Boolean && b.type === Node.Boolean) + || (a.type === Node.Symbol && b.type === Node.Symbol) + || (a.type === Node.Keyword && b.type === Node.Keyword) ) { return a.v === b.v; } @@ -61,7 +75,7 @@ export function equals(a: MalType, b: MalType, strict?: boolean): boolean { } export function isSeq(ast: MalType): ast is MalList | MalVector { - return MalList.is(ast) || MalVector.is(ast); + return ast.type === Node.List || ast.type === Node.Vector; } export function isAST(v: MalType): v is MalType { @@ -69,11 +83,7 @@ export function isAST(v: MalType): v is MalType { } export class MalList { - static is(f: MalType): f is MalList { - return f instanceof MalList; - } - - type: "list" = "list"; + type: Node.List = Node.List; meta?: MalType; constructor(public list: MalType[]) { @@ -87,11 +97,7 @@ export class MalList { } export class MalNumber { - static is(f: MalType): f is MalNumber { - return f instanceof MalNumber; - } - - type: "number" = "number"; + type: Node.Number = Node.Number; meta?: MalType; constructor(public v: number) { @@ -105,11 +111,7 @@ export class MalNumber { } export class MalString { - static is(f: MalType): f is MalString { - return f instanceof MalString; - } - - type: "string" = "string"; + type: Node.String = Node.String; meta?: MalType; constructor(public v: string) { @@ -123,13 +125,10 @@ export class MalString { } export class MalNull { - static is(f: MalType): f is MalNull { - return f instanceof MalNull; - } static instance = new MalNull(); - type: "null" = "null"; + type: Node.Null = Node.Null; meta?: MalType; private constructor() { } @@ -140,11 +139,7 @@ export class MalNull { } export class MalBoolean { - static is(f: MalType): f is MalBoolean { - return f instanceof MalBoolean; - } - - type: "boolean" = "boolean"; + type: Node.Boolean = Node.Boolean; meta?: MalType; constructor(public v: boolean) { @@ -158,10 +153,6 @@ export class MalBoolean { } export class MalSymbol { - static is(f: MalType): f is MalSymbol { - return f instanceof MalSymbol; - } - static map = new Map(); static get(name: string): MalSymbol { @@ -175,7 +166,7 @@ export class MalSymbol { return token; } - type: "symbol" = "symbol"; + type: Node.Symbol = Node.Symbol; meta?: MalType; private constructor(public v: string) { @@ -187,10 +178,6 @@ export class MalSymbol { } export class MalKeyword { - static is(f: MalType): f is MalKeyword { - return f instanceof MalKeyword; - } - static map = new Map(); static get(name: string): MalKeyword { @@ -204,7 +191,7 @@ export class MalKeyword { return token; } - type: "keyword" = "keyword"; + type: Node.Keyword = Node.Keyword; meta?: MalType; private constructor(public v: string) { @@ -216,11 +203,7 @@ export class MalKeyword { } export class MalVector { - static is(f: MalType): f is MalVector { - return f instanceof MalVector; - } - - type: "vector" = "vector"; + type: Node.Vector = Node.Vector; meta?: MalType; constructor(public list: MalType[]) { @@ -234,11 +217,7 @@ export class MalVector { } export class MalHashMap { - static is(f: MalType): f is MalHashMap { - return f instanceof MalHashMap; - } - - type: "hash-map" = "hash-map"; + type: Node.HashMap = Node.HashMap; stringMap: { [key: string]: MalType } = {}; keywordMap = new Map(); meta?: MalType; @@ -250,9 +229,9 @@ export class MalHashMap { if (value == null) { throw new Error("unexpected hash length"); } - if (MalKeyword.is(key)) { + if (key.type === Node.Keyword) { this.keywordMap.set(key, value); - } else if (MalString.is(key)) { + } else if (key.type === Node.String) { this.stringMap[key.v] = value; } else { throw new Error(`unexpected key symbol: ${key.type}, expected: keyword or string`); @@ -267,14 +246,14 @@ export class MalHashMap { } has(key: MalKeyword | MalString) { - if (MalKeyword.is(key)) { + if (key.type === Node.Keyword) { return !!this.keywordMap.get(key); } return !!this.stringMap[key.v]; } get(key: MalKeyword | MalString) { - if (MalKeyword.is(key)) { + if (key.type === Node.Keyword) { return this.keywordMap.get(key) || MalNull.instance; } return this.stringMap[key.v] || MalNull.instance; @@ -327,9 +306,9 @@ export class MalHashMap { const newHashMap = this.assoc([]); args.forEach(arg => { - if (MalString.is(arg)) { + if (arg.type === Node.String) { delete newHashMap.stringMap[arg.v]; - } else if (MalKeyword.is(arg)) { + } else if (arg.type === Node.Keyword) { newHashMap.keywordMap.delete(arg); } else { throw new Error(`unexpected symbol: ${arg.type}, expected: keyword or string`); @@ -342,10 +321,6 @@ export class MalHashMap { type MalF = (...args: (MalType | undefined)[]) => MalType; export class MalFunction { - static is(f: MalType): f is MalFunction { - return f instanceof MalFunction; - } - static fromLisp(evalMal: (ast: MalType, env: Env) => MalType, env: Env, params: MalSymbol[], bodyAst: MalType): MalFunction { const f = new MalFunction(); f.func = (...args) => evalMal(bodyAst, new Env(env, params, checkUndefined(args))); @@ -374,8 +349,7 @@ export class MalFunction { return f; } - type: "function" = "function"; - + type: Node.Function = Node.Function; func: MalF; ast: MalType; env: Env; @@ -403,11 +377,7 @@ export class MalFunction { } export class MalAtom { - static is(f: MalType): f is MalAtom { - return f instanceof MalAtom; - } - - type: "atom" = "atom"; + type: Node.Atom = Node.Atom; meta?: MalType; constructor(public v: MalType) { From 6071876ffeb59458c2550061eaf7fca642d96da6 Mon Sep 17 00:00:00 2001 From: vvakame Date: Sat, 25 Feb 2017 14:16:51 +0900 Subject: [PATCH 0306/2308] rename MalNull to MalNil --- ts/core.ts | 46 ++++++++++++++++++++++---------------------- ts/printer.ts | 2 +- ts/reader.ts | 4 ++-- ts/step4_if_fn_do.ts | 6 +++--- ts/step5_tco.ts | 6 +++--- ts/step6_file.ts | 6 +++--- ts/step7_quote.ts | 6 +++--- ts/step8_macros.ts | 6 +++--- ts/step9_try.ts | 6 +++--- ts/stepA_mal.ts | 6 +++--- ts/types.ts | 28 +++++++++++++++++---------- 11 files changed, 65 insertions(+), 57 deletions(-) diff --git a/ts/core.ts b/ts/core.ts index d33cd9e647..cb882f518b 100644 --- a/ts/core.ts +++ b/ts/core.ts @@ -2,7 +2,7 @@ import * as fs from "fs"; import { readline } from "./node_readline"; -import { Node, MalType, MalSymbol, MalFunction, MalNull, MalList, MalVector, MalBoolean, MalNumber, MalString, MalKeyword, MalHashMap, MalAtom, equals, isSeq } from "./types"; +import { Node, MalType, MalSymbol, MalFunction, MalNil, MalList, MalVector, MalBoolean, MalNumber, MalString, MalKeyword, MalHashMap, MalAtom, equals, isSeq } from "./types"; import { readStr } from "./reader"; import { prStr } from "./printer"; @@ -16,7 +16,7 @@ export const ns: Map = (() => { }, "nil?"(v: MalType) { - return new MalBoolean(v.type === Node.Null); + return new MalBoolean(v.type === Node.Nil); }, "true?"(v: MalType) { return new MalBoolean(v.type === Node.Boolean && v.v); @@ -52,15 +52,15 @@ export const ns: Map = (() => { "str"(...args: MalType[]): MalString { return new MalString(args.map(v => prStr(v, false)).join("")); }, - prn(...args: MalType[]): MalNull { + prn(...args: MalType[]): MalNil { const str = args.map(v => prStr(v, true)).join(" "); console.log(str); - return MalNull.instance; + return MalNil.instance; }, - println(...args: MalType[]): MalNull { + println(...args: MalType[]): MalNil { const str = args.map(v => prStr(v, false)).join(" "); console.log(str); - return MalNull.instance; + return MalNil.instance; }, "read-string"(v: MalType) { if (v.type !== Node.String) { @@ -75,7 +75,7 @@ export const ns: Map = (() => { const ret = readline(v.v); if (ret == null) { - return MalNull.instance; + return MalNil.instance; } return new MalString(ret); @@ -203,8 +203,8 @@ export const ns: Map = (() => { return v.dissoc(args); }, get(v: MalType, key: MalType) { - if (v.type === Node.Null) { - return MalNull.instance; + if (v.type === Node.Nil) { + return MalNil.instance; } if (v.type !== Node.HashMap) { throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`); @@ -213,11 +213,11 @@ export const ns: Map = (() => { throw new Error(`unexpected symbol: ${key.type}, expected: string or keyword`); } - return v.get(key) || MalNull.instance; + return v.get(key) || MalNil.instance; }, "contains?"(v: MalType, key: MalType) { - if (v.type === Node.Null) { - return MalNull.instance; + if (v.type === Node.Nil) { + return MalNil.instance; } if (v.type !== Node.HashMap) { throw new Error(`unexpected symbol: ${v.type}, expected: hash-map`); @@ -281,17 +281,17 @@ export const ns: Map = (() => { return v; }, first(v: MalType) { - if (v.type === Node.Null) { - return MalNull.instance; + if (v.type === Node.Nil) { + return MalNil.instance; } if (!isSeq(v)) { throw new Error(`unexpected symbol: ${v.type}, expected: list or vector`); } - return v.list[0] || MalNull.instance; + return v.list[0] || MalNil.instance; }, rest(v: MalType) { - if (v.type === Node.Null) { + if (v.type === Node.Nil) { return new MalList([]); } if (!isSeq(v)) { @@ -310,7 +310,7 @@ export const ns: Map = (() => { if (isSeq(v)) { return new MalNumber(v.list.length); } - if (v.type === Node.Null) { + if (v.type === Node.Nil) { return new MalNumber(0); } throw new Error(`unexpected symbol: ${v.type}`); @@ -353,31 +353,31 @@ export const ns: Map = (() => { seq(v: MalType) { if (v.type === Node.List) { if (v.list.length === 0) { - return MalNull.instance; + return MalNil.instance; } return v; } if (v.type === Node.Vector) { if (v.list.length === 0) { - return MalNull.instance; + return MalNil.instance; } return new MalList(v.list); } if (v.type === Node.String) { if (v.v.length === 0) { - return MalNull.instance; + return MalNil.instance; } return new MalList(v.v.split("").map(s => new MalString(s))); } - if (v.type === Node.Null) { - return MalNull.instance; + if (v.type === Node.Nil) { + return MalNil.instance; } throw new Error(`unexpected symbol: ${v.type}, expected: list or vector or string`); }, meta(v: MalType) { - return v.meta || MalNull.instance; + return v.meta || MalNil.instance; }, "with-meta"(v: MalType, m: MalType) { return v.withMeta(m); diff --git a/ts/printer.ts b/ts/printer.ts index 24c48dd18b..f1806c3272 100644 --- a/ts/printer.ts +++ b/ts/printer.ts @@ -30,7 +30,7 @@ export function prStr(v: MalType, printReadably = true): string { } else { return v.v; } - case Node.Null: + case Node.Nil: return "nil"; case Node.Keyword: return `:${v.v}`; diff --git a/ts/reader.ts b/ts/reader.ts index c86648677a..74396cb9ac 100644 --- a/ts/reader.ts +++ b/ts/reader.ts @@ -1,4 +1,4 @@ -import { MalType, MalList, MalString, MalNumber, MalBoolean, MalNull, MalKeyword, MalSymbol, MalVector, MalHashMap } from "./types"; +import { MalType, MalList, MalString, MalNumber, MalBoolean, MalNil, MalKeyword, MalSymbol, MalVector, MalHashMap } from "./types"; class Reader { position = 0; @@ -134,7 +134,7 @@ function readAtom(reader: Reader): MalType { } switch (token) { case "nil": - return MalNull.instance; + return MalNil.instance; case "true": return new MalBoolean(true); case "false": diff --git a/ts/step4_if_fn_do.ts b/ts/step4_if_fn_do.ts index 0733fd91fd..2239f2bb2b 100644 --- a/ts/step4_if_fn_do.ts +++ b/ts/step4_if_fn_do.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { Node, MalType, MalNull, MalList, MalVector, MalHashMap, MalFunction, isSeq } from "./types"; +import { Node, MalType, MalNil, MalList, MalVector, MalHashMap, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -91,7 +91,7 @@ function evalMal(ast: MalType, env: Env): MalType { let b = true; if (ret.type === Node.Boolean && !ret.v) { b = false; - } else if (ret.type === Node.Null) { + } else if (ret.type === Node.Nil) { b = false; } if (b) { @@ -99,7 +99,7 @@ function evalMal(ast: MalType, env: Env): MalType { } else if (elseExrp) { return evalMal(elseExrp, env); } else { - return MalNull.instance; + return MalNil.instance; } } case "fn*": { diff --git a/ts/step5_tco.ts b/ts/step5_tco.ts index 1efd6ed00c..e1f6029857 100644 --- a/ts/step5_tco.ts +++ b/ts/step5_tco.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { Node, MalType, MalNull, MalList, MalVector, MalHashMap, MalFunction, isSeq } from "./types"; +import { Node, MalType, MalNil, MalList, MalVector, MalHashMap, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -91,7 +91,7 @@ function evalMal(ast: MalType, env: Env): MalType { let b = true; if (ret.type === Node.Boolean && !ret.v) { b = false; - } else if (ret.type === Node.Null) { + } else if (ret.type === Node.Nil) { b = false; } if (b) { @@ -99,7 +99,7 @@ function evalMal(ast: MalType, env: Env): MalType { } else if (elseExrp) { ast = elseExrp; } else { - ast = MalNull.instance; + ast = MalNil.instance; } continue loop; } diff --git a/ts/step6_file.ts b/ts/step6_file.ts index 7e9b11e1d8..f37fb57a24 100644 --- a/ts/step6_file.ts +++ b/ts/step6_file.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { Node, MalType, MalString, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; +import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -91,7 +91,7 @@ function evalMal(ast: MalType, env: Env): MalType { let b = true; if (ret.type === Node.Boolean && !ret.v) { b = false; - } else if (ret.type === Node.Null) { + } else if (ret.type === Node.Nil) { b = false; } if (b) { @@ -99,7 +99,7 @@ function evalMal(ast: MalType, env: Env): MalType { } else if (elseExrp) { ast = elseExrp; } else { - ast = MalNull.instance; + ast = MalNil.instance; } continue loop; } diff --git a/ts/step7_quote.ts b/ts/step7_quote.ts index e25075b6fc..5a21b384d9 100644 --- a/ts/step7_quote.ts +++ b/ts/step7_quote.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { Node, MalType, MalString, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; +import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -138,7 +138,7 @@ function evalMal(ast: MalType, env: Env): MalType { let b = true; if (ret.type === Node.Boolean && !ret.v) { b = false; - } else if (ret.type === Node.Null) { + } else if (ret.type === Node.Nil) { b = false; } if (b) { @@ -146,7 +146,7 @@ function evalMal(ast: MalType, env: Env): MalType { } else if (elseExrp) { ast = elseExrp; } else { - ast = MalNull.instance; + ast = MalNil.instance; } continue loop; } diff --git a/ts/step8_macros.ts b/ts/step8_macros.ts index b13944e532..fdb8555f1b 100644 --- a/ts/step8_macros.ts +++ b/ts/step8_macros.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { Node, MalType, MalString, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; +import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -202,7 +202,7 @@ function evalMal(ast: MalType, env: Env): MalType { let b = true; if (ret.type === Node.Boolean && !ret.v) { b = false; - } else if (ret.type === Node.Null) { + } else if (ret.type === Node.Nil) { b = false; } if (b) { @@ -210,7 +210,7 @@ function evalMal(ast: MalType, env: Env): MalType { } else if (elseExrp) { ast = elseExrp; } else { - ast = MalNull.instance; + ast = MalNil.instance; } continue loop; } diff --git a/ts/step9_try.ts b/ts/step9_try.ts index 162123055f..2a27f6f8c0 100644 --- a/ts/step9_try.ts +++ b/ts/step9_try.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { Node, MalType, MalString, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; +import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -224,7 +224,7 @@ function evalMal(ast: MalType, env: Env): MalType { let b = true; if (ret.type === Node.Boolean && !ret.v) { b = false; - } else if (ret.type === Node.Null) { + } else if (ret.type === Node.Nil) { b = false; } if (b) { @@ -232,7 +232,7 @@ function evalMal(ast: MalType, env: Env): MalType { } else if (elseExrp) { ast = elseExrp; } else { - ast = MalNull.instance; + ast = MalNil.instance; } continue loop; } diff --git a/ts/stepA_mal.ts b/ts/stepA_mal.ts index 72b18765a2..0ce6c353cb 100644 --- a/ts/stepA_mal.ts +++ b/ts/stepA_mal.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { Node, MalType, MalString, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; +import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -224,7 +224,7 @@ function evalMal(ast: MalType, env: Env): MalType { let b = true; if (ret.type === Node.Boolean && !ret.v) { b = false; - } else if (ret.type === Node.Null) { + } else if (ret.type === Node.Nil) { b = false; } if (b) { @@ -232,7 +232,7 @@ function evalMal(ast: MalType, env: Env): MalType { } else if (elseExrp) { ast = elseExrp; } else { - ast = MalNull.instance; + ast = MalNil.instance; } continue loop; } diff --git a/ts/types.ts b/ts/types.ts index 8368e2c5a3..5ac88d78c1 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -1,12 +1,12 @@ import { Env } from "./env"; -export type MalType = MalList | MalNumber | MalString | MalNull | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap | MalFunction | MalAtom; +export type MalType = MalList | MalNumber | MalString | MalNil | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap | MalFunction | MalAtom; export const enum Node { List = 1, Number, String, - Null, + Nil, Boolean, Symbol, Keyword, @@ -21,7 +21,7 @@ export function equals(a: MalType, b: MalType, strict?: boolean): boolean { return false; } - if (a.type === Node.Null && b.type === Node.Null) { + if (a.type === Node.Nil && b.type === Node.Nil) { return true; } if (isSeq(a) && isSeq(b)) { @@ -39,7 +39,7 @@ export function equals(a: MalType, b: MalType, strict?: boolean): boolean { throw new Error(`unexpected symbol: ${aK.type}, expected: string or keyword`); } const bV = b.get(aK); - if (aV.type === Node.Null && bV.type === Node.Null) { + if (aV.type === Node.Nil && bV.type === Node.Nil) { continue; } if (!equals(aV, bV)) { @@ -124,16 +124,24 @@ export class MalString { } } -export class MalNull { +export class MalNil { - static instance = new MalNull(); + private static _instance?: MalNil; - type: Node.Null = Node.Null; + static get instance(): MalNil { + if (this._instance) { + return this._instance; + } + this._instance = new MalNil(); + return this._instance; + } + + type: Node.Nil = Node.Nil; meta?: MalType; private constructor() { } - withMeta(_meta: MalType): MalNull { + withMeta(_meta: MalType): MalNil { throw new Error(`not supported`); } } @@ -254,9 +262,9 @@ export class MalHashMap { get(key: MalKeyword | MalString) { if (key.type === Node.Keyword) { - return this.keywordMap.get(key) || MalNull.instance; + return this.keywordMap.get(key) || MalNil.instance; } - return this.stringMap[key.v] || MalNull.instance; + return this.stringMap[key.v] || MalNil.instance; } entries(): [MalType, MalType][] { From c527d5655850a81792d286a2716431531a6b6572 Mon Sep 17 00:00:00 2001 From: vvakame Date: Sat, 25 Feb 2017 14:32:18 +0900 Subject: [PATCH 0307/2308] add TypeScript section README.md --- README.md | 11 +++++++++++ ts/Makefile | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 47b3b1d199..b649291e38 100644 --- a/README.md +++ b/README.md @@ -856,6 +856,17 @@ cd tcl tclsh ./stepX_YYY.tcl ``` +### TypeScript + +The TypeScript implementation of mal requires the TypeScript 2.2 compiler. +It has been tested with Node.js v7.6.0. + +``` +cd ts +make +node ./stepX_YYY.js +``` + ### VHDL *The VHDL implementation was created by [Dov Murik](https://github.com/dubek)* diff --git a/ts/Makefile b/ts/Makefile index e0ea8bb646..90ee12a7f6 100644 --- a/ts/Makefile +++ b/ts/Makefile @@ -8,7 +8,7 @@ node_modules: npm install ts: node_modules - $(npm bin)/tsc -p ./ + ./node_modules/.bin/tsc -p ./ clean: rm -f *.js mal From 677a1c9db1cbede6413b90aae015e0df505ab301 Mon Sep 17 00:00:00 2001 From: vvakame Date: Sat, 25 Feb 2017 14:44:11 +0900 Subject: [PATCH 0308/2308] small fix --- ts/core.ts | 2 +- ts/node_readline.ts | 2 +- ts/step2_eval.ts | 7 +++++-- ts/step3_env.ts | 20 +++++++++++++------- ts/step4_if_fn_do.ts | 2 +- ts/step5_tco.ts | 2 +- ts/step6_file.ts | 2 +- ts/step7_quote.ts | 2 +- ts/step8_macros.ts | 4 ++-- ts/step9_try.ts | 4 ++-- ts/stepA_mal.ts | 2 +- 11 files changed, 29 insertions(+), 20 deletions(-) diff --git a/ts/core.ts b/ts/core.ts index cb882f518b..c004e0bc6a 100644 --- a/ts/core.ts +++ b/ts/core.ts @@ -176,7 +176,7 @@ export const ns: Map = (() => { return new MalList(args); }, "list?"(v: MalType): MalBoolean { - return new MalBoolean(v instanceof MalList); + return new MalBoolean(v.type === Node.List); }, vector(...args: MalType[]): MalVector { return new MalVector(args); diff --git a/ts/node_readline.ts b/ts/node_readline.ts index 2b90aa6235..dca3ac5610 100644 --- a/ts/node_readline.ts +++ b/ts/node_readline.ts @@ -4,7 +4,7 @@ import * as fs from "fs"; // IMPORTANT: choose one const RL_LIB = "libreadline"; // NOTE: libreadline is GPL -//var RL_LIB = "libedit"; +// var RL_LIB = "libedit"; const HISTORY_FILE = path.join(process.env.HOME, ".mal-history"); diff --git a/ts/step2_eval.ts b/ts/step2_eval.ts index bacbd109df..a3b95ccd04 100644 --- a/ts/step2_eval.ts +++ b/ts/step2_eval.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { Node, MalType, MalNumber, MalList, MalVector, MalHashMap, MalFunction } from "./types"; +import { Node, MalType, MalNumber, MalList, MalVector, MalHashMap, MalFunction, isSeq } from "./types"; import { readStr } from "./reader"; import { prStr } from "./printer"; @@ -45,7 +45,10 @@ function evalMal(ast: MalType, env: MalEnvironment): MalType { if (ast.list.length === 0) { return ast; } - const result = evalAST(ast, env) as MalList; + const result = evalAST(ast, env); + if (!isSeq(result)) { + throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); + } const [f, ...args] = result.list; if (f.type !== Node.Function) { throw new Error(`unexpected token: ${f.type}, expected: function`); diff --git a/ts/step3_env.ts b/ts/step3_env.ts index 971b9cca60..4be842383c 100644 --- a/ts/step3_env.ts +++ b/ts/step3_env.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { Node, MalType, MalNumber, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { Node, MalType, MalNumber, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import { readStr } from "./reader"; import { prStr } from "./printer"; @@ -48,35 +48,41 @@ function evalMal(ast: MalType, env: Env): MalType { switch (first.v) { case "def!": { const [, key, value] = ast.list; - if (key instanceof MalSymbol === false) { + if (key.type !== Node.Symbol) { throw new Error(`unexpected toke type: ${key.type}, expected: symbol`); } if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key as MalSymbol, evalMal(value, env)) + return env.set(key, evalMal(value, env)); } case "let*": { let letEnv = new Env(env); const pairs = ast.list[1]; - if (pairs instanceof MalList === false && pairs instanceof MalVector === false) { + if (!isSeq(pairs)) { throw new Error(`unexpected toke type: ${pairs.type}, expected: list or vector`); } - const list = (pairs as (MalList | MalVector)).list; + const list = pairs.list; for (let i = 0; i < list.length; i += 2) { const key = list[i]; const value = list[i + 1]; + if (key.type !== Node.Symbol) { + throw new Error(`unexpected token type: ${key.type}, expected: symbol`); + } if (!key || !value) { throw new Error(`unexpected syntax`); } - letEnv.set(key as MalSymbol, evalMal(value, letEnv)); + letEnv.set(key, evalMal(value, letEnv)); } return evalMal(ast.list[2], letEnv); } } } - const result = evalAST(ast, env) as MalList; + const result = evalAST(ast, env); + if (!isSeq(result)) { + throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); + } const [f, ...args] = result.list; if (f.type !== Node.Function) { throw new Error(`unexpected token: ${f.type}, expected: function`); diff --git a/ts/step4_if_fn_do.ts b/ts/step4_if_fn_do.ts index 2239f2bb2b..e98b4beae6 100644 --- a/ts/step4_if_fn_do.ts +++ b/ts/step4_if_fn_do.ts @@ -55,7 +55,7 @@ function evalMal(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key, evalMal(value, env)) + return env.set(key, evalMal(value, env)); } case "let*": { let letEnv = new Env(env); diff --git a/ts/step5_tco.ts b/ts/step5_tco.ts index e1f6029857..02e881bbb6 100644 --- a/ts/step5_tco.ts +++ b/ts/step5_tco.ts @@ -56,7 +56,7 @@ function evalMal(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key, evalMal(value, env)) + return env.set(key, evalMal(value, env)); } case "let*": { env = new Env(env); diff --git a/ts/step6_file.ts b/ts/step6_file.ts index f37fb57a24..225385da19 100644 --- a/ts/step6_file.ts +++ b/ts/step6_file.ts @@ -56,7 +56,7 @@ function evalMal(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key, evalMal(value, env)) + return env.set(key, evalMal(value, env)); } case "let*": { env = new Env(env); diff --git a/ts/step7_quote.ts b/ts/step7_quote.ts index 5a21b384d9..f360bfee61 100644 --- a/ts/step7_quote.ts +++ b/ts/step7_quote.ts @@ -96,7 +96,7 @@ function evalMal(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key, evalMal(value, env)) + return env.set(key, evalMal(value, env)); } case "let*": { env = new Env(env); diff --git a/ts/step8_macros.ts b/ts/step8_macros.ts index fdb8555f1b..d3152fb298 100644 --- a/ts/step8_macros.ts +++ b/ts/step8_macros.ts @@ -142,7 +142,7 @@ function evalMal(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key, evalMal(value, env)) + return env.set(key, evalMal(value, env)); } case "let*": { env = new Env(env); @@ -273,7 +273,7 @@ replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); rep("(def! not (fn* (a) (if a false true)))"); rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))`); rep(`(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))`); -rep('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))'); +rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); if (typeof process !== "undefined" && 2 < process.argv.length) { replEnv.set(MalSymbol.get("*ARGV*"), new MalList(process.argv.slice(3).map(s => new MalString(s)))); diff --git a/ts/step9_try.ts b/ts/step9_try.ts index 2a27f6f8c0..cae3d1dc9b 100644 --- a/ts/step9_try.ts +++ b/ts/step9_try.ts @@ -142,7 +142,7 @@ function evalMal(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key, evalMal(value, env)) + return env.set(key, evalMal(value, env)); } case "let*": { env = new Env(env); @@ -295,7 +295,7 @@ replEnv.set(MalSymbol.get("*ARGV*"), new MalList([])); rep("(def! not (fn* (a) (if a false true)))"); rep(`(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))`); rep(`(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))`); -rep('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))'); +rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); if (typeof process !== "undefined" && 2 < process.argv.length) { replEnv.set(MalSymbol.get("*ARGV*"), new MalList(process.argv.slice(3).map(s => new MalString(s)))); diff --git a/ts/stepA_mal.ts b/ts/stepA_mal.ts index 0ce6c353cb..6943ce6c6d 100644 --- a/ts/stepA_mal.ts +++ b/ts/stepA_mal.ts @@ -142,7 +142,7 @@ function evalMal(ast: MalType, env: Env): MalType { if (!value) { throw new Error(`unexpected syntax`); } - return env.set(key, evalMal(value, env)) + return env.set(key, evalMal(value, env)); } case "let*": { env = new Env(env); From 29db6f4ec29c5d957c172c1e36c8d98df2d70752 Mon Sep 17 00:00:00 2001 From: vvakame Date: Sun, 26 Feb 2017 10:57:19 +0900 Subject: [PATCH 0309/2308] fix typo --- ts/reader.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ts/reader.ts b/ts/reader.ts index 74396cb9ac..7bd409dbd1 100644 --- a/ts/reader.ts +++ b/ts/reader.ts @@ -19,7 +19,7 @@ class Reader { export function readStr(input: string): MalType { const tokens = tokenizer(input); const reader = new Reader(tokens); - return readFrom(reader); + return readForm(reader); } function tokenizer(input: string): string[] { @@ -42,7 +42,7 @@ function tokenizer(input: string): string[] { return tokens; } -function readFrom(reader: Reader): MalType { +function readForm(reader: Reader): MalType { const token = reader.peek(); switch (token) { case "(": @@ -65,8 +65,8 @@ function readFrom(reader: Reader): MalType { { reader.next(); const sym = MalSymbol.get("with-meta"); - const target = readFrom(reader); - return new MalList([sym, readFrom(reader), target]); + const target = readForm(reader); + return new MalList([sym, readForm(reader), target]); } default: return readAtom(reader); @@ -75,7 +75,7 @@ function readFrom(reader: Reader): MalType { function readSymbol(name: string) { reader.next(); const sym = MalSymbol.get(name); - const target = readFrom(reader); + const target = readForm(reader); return new MalList([sym, target]); } } @@ -105,7 +105,7 @@ function readParen(reader: Reader, ctor: { new (list: MalType[]): MalType; }, op } else if (!next) { throw new Error("unexpected EOF"); } - list.push(readFrom(reader)); + list.push(readForm(reader)); } reader.next(); // drop close paren From db885df9a05dcdee9cc5ebc250ef1b18b65216b1 Mon Sep 17 00:00:00 2001 From: vvakame Date: Sun, 26 Feb 2017 11:02:46 +0900 Subject: [PATCH 0310/2308] address comment --- .travis.yml | 1 + README.md | 7 +++++-- ts/Makefile | 20 +++++++++++++------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 73acf2543c..010ac7cea7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,6 +67,7 @@ matrix: - {env: IMPL=swift3, services: [docker]} - {env: IMPL=swift3 NO_DOCKER=1, os: osx, osx_image: xcode8} - {env: IMPL=tcl, services: [docker]} + - {env: IMPL=ts, services: [docker]} - {env: IMPL=vb, services: [docker]} - {env: IMPL=vhdl, services: [docker]} - {env: IMPL=vimscript, services: [docker]} diff --git a/README.md b/README.md index b649291e38..94ffc2072e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 63 languages: +Mal is implemented in 64 languages: * Ada * GNU awk @@ -68,6 +68,7 @@ Mal is implemented in 63 languages: * Swift * Swift 3 * Tcl +* TypeScript * VHDL * Vimscript * Visual Basic.NET @@ -858,8 +859,10 @@ tclsh ./stepX_YYY.tcl ### TypeScript +*The TypeScript implementation was created by [vvakame](https://github.com/vvakame)* + The TypeScript implementation of mal requires the TypeScript 2.2 compiler. -It has been tested with Node.js v7.6.0. +It has been tested with Node.js v6. ``` cd ts diff --git a/ts/Makefile b/ts/Makefile index 90ee12a7f6..0404a90f92 100644 --- a/ts/Makefile +++ b/ts/Makefile @@ -2,23 +2,29 @@ SOURCES_BASE = types.ts reader.ts printer.ts SOURCES_LISP = env.ts core.ts stepA_mal.ts SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -all: node_modules ts +STEPS = step0_repl step1_read_print step2_eval step3_env \ + step4_if_fn_do step5_tco step6_file step7_quote \ + step8_macros step9_try stepA_mal + +all: ts node_modules: npm install -ts: node_modules +step%.js: node_modules ./node_modules/.bin/tsc -p ./ -clean: - rm -f *.js mal +.PHONY: ts clean stats tests $(TESTS) -.PHONY: stats tests $(TESTS) +ts: $(foreach s,$(STEPS),$(s).js) + +clean: + rm -f *.js stats: $(SOURCES) @wc $^ - @printf "%5s %5s %5s %s\n" `egrep "^\w*#|^\w*$$" $^ | wc` "[comments/blanks]" + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" stats-lisp: $(SOURCES_LISP) @wc $^ - @printf "%5s %5s %5s %s\n" `egrep "^\w*#|^\w*$$" $^ | wc` "[comments/blanks]" + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" From 9ed75afdf41423dcb9b6e7369ad3f04c13ffe82d Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 31 Mar 2017 10:25:24 -0500 Subject: [PATCH 0311/2308] Update README with LambdaConf presentation. --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 94ffc2072e..07dd123645 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,12 @@ conference (yes the presentation is a mal program). At Midwest.io 2015, Joel Martin gave a presentation on Mal titled "Achievement Unlocked: A Better Path to Language Learning". [Video](https://www.youtube.com/watch?v=lgyOAiRtZGw), -[Slides](http://kanaka.github.io/midwest.io.mal/). +[Slides](http://kanaka.github.io/midwest.io.mal/). More recently +Joel gave a presentation on "Make Your Own Lisp Interpreter in +10 Incremental Steps" at LambdaConf 2016: [Part +1](https://www.youtube.com/watch?v=jVhupfthTEk), [Part +2](https://www.youtube.com/watch?v=X5OQBMGpaTU), +[Slides](http://kanaka.github.io/lambdaconf/). If you are interesting in creating a mal implementation (or just interested in using mal for something), please drop by the #mal From dcccf1b26386e207a8de253c9ae348524ea52cc8 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Wed, 5 Apr 2017 22:01:41 +0200 Subject: [PATCH 0312/2308] Added livescript --- .gitignore | 1 + Makefile | 3 ++- livescript/Makefile | 16 ++++++++++++++++ livescript/package.json | 15 +++++++++++++++ livescript/run | 2 ++ livescript/step0_repl.ls | 23 +++++++++++++++++++++++ 6 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 livescript/Makefile create mode 100644 livescript/package.json create mode 100755 livescript/run create mode 100644 livescript/step0_repl.ls diff --git a/.gitignore b/.gitignore index c7d90d9a16..a7f1a11bfd 100644 --- a/.gitignore +++ b/.gitignore @@ -124,3 +124,4 @@ common-lisp/*.fasl common-lisp/*.lib common-lisp/images/* common-lisp/hist/* +livescript/*.js diff --git a/Makefile b/Makefile index ab2b4fd058..1ad8c84f7c 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ IMPLS = ada awk bash basic c d chuck clojure coffee common-lisp cpp crystal cs d haxe io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ python r racket rpython ruby rust scala skew swift swift3 tcl ts vb vhdl \ - vimscript + vimscript livescript EXTENSION = .mal @@ -215,6 +215,7 @@ vb_STEP_TO_PROG = vb/$($(1)).exe vhdl_STEP_TO_PROG = vhdl/$($(1)) vimscript_STEP_TO_PROG = vimscript/$($(1)).vim guile_STEP_TO_PROG = guile/$($(1)).scm +livescript_STEP_TO_PROG = livescript/$($(1)).js # Needed some argument munging diff --git a/livescript/Makefile b/livescript/Makefile new file mode 100644 index 0000000000..cc5e7a8052 --- /dev/null +++ b/livescript/Makefile @@ -0,0 +1,16 @@ +TESTS = + +SOURCES_BASE = +SOURCES_LISP = stepA_mal.ls +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +all: node_modules + +node_modules: + npm install + +%.js: %.ls + lsc -d -c $(@:%.js=%.ls) + +clean: + rm -f *.js diff --git a/livescript/package.json b/livescript/package.json new file mode 100644 index 0000000000..e4325a1b5b --- /dev/null +++ b/livescript/package.json @@ -0,0 +1,15 @@ +{ + "name": "livescript", + "version": "1.0.0", + "description": "", + "main": "index.js", + "dependencies": { + "prelude-ls": "^1.1.2" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/livescript/run b/livescript/run new file mode 100755 index 0000000000..6605303a29 --- /dev/null +++ b/livescript/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec node $(dirname $0)/${STEP:-stepA_mal}.js "${@}" diff --git a/livescript/step0_repl.ls b/livescript/step0_repl.ls new file mode 100644 index 0000000000..b57aa61ffa --- /dev/null +++ b/livescript/step0_repl.ls @@ -0,0 +1,23 @@ +readline = require 'readline' +{id} = require 'prelude-ls' + + +READ = id +EVAL = -> +PRINT = id +rep = (line) -> + + +rl = readline.createInterface do + input : process.stdin + output : process.stdout + prompt: 'user> ' + +rl.on 'line', (line) -> + console.log rep line + rl.prompt! + +rl.on 'close', -> + process.exit 0 + +rl.prompt! From 185052f14d0a730ba67f0187cb453c370fc7e3a7 Mon Sep 17 00:00:00 2001 From: boyned//Kampfkarren Date: Wed, 12 Apr 2017 08:22:19 -0700 Subject: [PATCH 0313/2308] Fix guide's explanation of Regex it didn't incude the parantheses --- process/guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/process/guide.md b/process/guide.md index fa0dec29f2..cd17969bf0 100644 --- a/process/guide.md +++ b/process/guide.md @@ -329,7 +329,7 @@ expression support. * `~@`: Captures the special two-characters `~@` (tokenized). * ```[\[\]{}()'`~^@]```: Captures any special single character, one of - ```[]{}'`~^@``` (tokenized). + ```[]{}()'`~^@``` (tokenized). * `"(?:\\.|[^\\"])*"`: Starts capturing at a double-quote and stops at the next double-quote unless it was proceeded by a backslash in which case it From 18ed1641768adafc3269d49b6c381a08a94468ec Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Fri, 5 May 2017 22:19:54 +0200 Subject: [PATCH 0314/2308] Step 0 & 1 --- livescript/Makefile | 8 +-- livescript/env | 2 + livescript/package.json | 3 +- livescript/printer.ls | 11 ++++ livescript/reader.ls | 91 ++++++++++++++++++++++++++++++++++ livescript/step0_repl.ls | 31 +++++++----- livescript/step1_read_print.ls | 17 +++++++ 7 files changed, 145 insertions(+), 18 deletions(-) create mode 100644 livescript/env create mode 100644 livescript/printer.ls create mode 100644 livescript/reader.ls create mode 100644 livescript/step1_read_print.ls diff --git a/livescript/Makefile b/livescript/Makefile index cc5e7a8052..a8da36b488 100644 --- a/livescript/Makefile +++ b/livescript/Makefile @@ -1,15 +1,15 @@ TESTS = -SOURCES_BASE = -SOURCES_LISP = stepA_mal.ls +SOURCES_BASE = reader.js printer.js +SOURCES_LISP = SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -all: node_modules +all: $(SOURCES) node_modules node_modules: npm install -%.js: %.ls +%.js: %.ls $(SOURCES) lsc -d -c $(@:%.js=%.ls) clean: diff --git a/livescript/env b/livescript/env new file mode 100644 index 0000000000..6c312492f1 --- /dev/null +++ b/livescript/env @@ -0,0 +1,2 @@ +test + STEP=step0_repl ./run diff --git a/livescript/package.json b/livescript/package.json index e4325a1b5b..8cb53e1afa 100644 --- a/livescript/package.json +++ b/livescript/package.json @@ -4,7 +4,8 @@ "description": "", "main": "index.js", "dependencies": { - "prelude-ls": "^1.1.2" + "prelude-ls": "^1.1.2", + "ffi": "2.0.x" }, "devDependencies": {}, "scripts": { diff --git a/livescript/printer.ls b/livescript/printer.ls new file mode 100644 index 0000000000..7328c90b3d --- /dev/null +++ b/livescript/printer.ls @@ -0,0 +1,11 @@ +{is-type, map, join} = require 'prelude-ls' + +export pr_str = (ast) -> + if is-type \Array ast + '(' + (ast |> map pr_str |> join ' ') + ')' + else + {type, value} = ast + switch type + | \int => value + | \string => value + | \symbol => value diff --git a/livescript/reader.ls b/livescript/reader.ls new file mode 100644 index 0000000000..1fd4734997 --- /dev/null +++ b/livescript/reader.ls @@ -0,0 +1,91 @@ +readline = require 'readline' +{id} = require 'prelude-ls' + +class Reader + (tokens) -> + @tokens = tokens + @pos = 0 + + # returns the token at the current position + # and increments position. + next: -> + result = @peek! + if result? then @pos += 1 + result + + # just returns the token at the current position. + peek: -> + if @pos < @tokens.length + @tokens[@pos] + + +export read_str = (str) -> + str + |> tokenizer + |> (tokens) -> new Reader tokens + |> read_form + +# This function will take a single string and return an array/list +# of all the tokens (strings) in it. +tokenizer = (str) -> + re = // + [\s,]* # whitespace or commas + ( ~@ # special two-char ~@ + | [\[\]{}()'`~^@] # special single char one of []{}'`~^@ + | "(?:\\.| [^\\"])*" # double-quoted string + | ;.* # any seq of chars starting ; + | [^\s\[\]{}('"`,;)]+ # seq of non-special chars: symbols, numbers, + ) # "true", "false" and "nil". + //y + + tokens = [] + while re.lastIndex < str.length + idx = re.lastIndex + m = re.exec str + # console.log 'at ', idx, 'matched', m + if not m + # Allow whitespace or commas at the end of the input. + break if /[\s,]+/.exec str.substring idx + throw new Error 'parse error at character ' + idx + + # Ignore comments. + tok = m[1] + if tok[1] != ';' then tokens.push tok + + tokens + +read_form = (reader) -> + if reader.peek! == '(' + read_list reader + else if reader.peek!? + read_atom reader + else + throw new Error 'parse error: expected a form' + +read_list = (reader) -> + list = [] + reader.next! # accept '(' + loop + token = reader.peek! + if not token? + throw new Error 'expected \')\', got EOF' + else if token == ')' + reader.next! + break + + list.push read_form reader + + return list + +special_chars = '[]{}\'`~^@' + +read_atom = (reader) -> + token = reader.peek! + if token[0] == '"' + {type: \string, value: reader.next!} + else if token.match /^-?\d+$/ + {type: \int, value: reader.next!} + else if token != '~@' and token not in special_chars + {type: \symbol, value: reader.next!} + else + throw new Error 'parse error: expected an atom, got ' + token diff --git a/livescript/step0_repl.ls b/livescript/step0_repl.ls index b57aa61ffa..0395fc9a23 100644 --- a/livescript/step0_repl.ls +++ b/livescript/step0_repl.ls @@ -1,23 +1,28 @@ -readline = require 'readline' +readline = require './node_readline' {id} = require 'prelude-ls' READ = id -EVAL = -> +EVAL = id PRINT = id -rep = (line) -> +rep = (line) -> PRINT EVAL READ line -rl = readline.createInterface do - input : process.stdin - output : process.stdout - prompt: 'user> ' +loop + line = readline.readline 'user> ' + break if not line? or line == '' + console.log rep line -rl.on 'line', (line) -> - console.log rep line - rl.prompt! +# rl = readline.createInterface do +# input : process.stdin +# output : process.stdout +# prompt: 'user> ' -rl.on 'close', -> - process.exit 0 +# rl.prompt! -rl.prompt! +# rl.on 'line', (line) -> +# console.log rep line +# rl.prompt! + +# rl.on 'close', -> +# process.exit 0 diff --git a/livescript/step1_read_print.ls b/livescript/step1_read_print.ls new file mode 100644 index 0000000000..cd1ca0225f --- /dev/null +++ b/livescript/step1_read_print.ls @@ -0,0 +1,17 @@ +readline = require './node_readline' +{id} = require 'prelude-ls' +{read_str} = require './reader' +{pr_str} = require './printer' + + +EVAL = id + +rep = (line) -> pr_str EVAL read_str line + +loop + line = readline.readline 'user> ' + break if not line? or line == '' + try + console.log rep line + catch {message} + console.log message From cb86911fac1ecbc589d83a87a6413e7bfd7fba9f Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Fri, 5 May 2017 23:02:54 +0200 Subject: [PATCH 0315/2308] Step 2 --- livescript/Makefile | 2 +- livescript/printer.ls | 18 +++++++------- livescript/reader.ls | 23 +++++++++++------- livescript/step2_eval.ls | 51 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 18 deletions(-) create mode 100644 livescript/step2_eval.ls diff --git a/livescript/Makefile b/livescript/Makefile index a8da36b488..0b5cacda3c 100644 --- a/livescript/Makefile +++ b/livescript/Makefile @@ -1,7 +1,7 @@ TESTS = SOURCES_BASE = reader.js printer.js -SOURCES_LISP = +SOURCES_LISP = SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) all: $(SOURCES) node_modules diff --git a/livescript/printer.ls b/livescript/printer.ls index 7328c90b3d..0286c39397 100644 --- a/livescript/printer.ls +++ b/livescript/printer.ls @@ -1,11 +1,11 @@ {is-type, map, join} = require 'prelude-ls' -export pr_str = (ast) -> - if is-type \Array ast - '(' + (ast |> map pr_str |> join ' ') + ')' - else - {type, value} = ast - switch type - | \int => value - | \string => value - | \symbol => value +pr_list = (list) -> list |> map pr_str |> join ' ' + +export pr_str = ({type, value}: ast) -> + switch type + | \int => value + | \string => value + | \symbol => value + | \list => '(' + (pr_list value) + ')' + | \vector => '[' + (pr_list value) + ']' diff --git a/livescript/reader.ls b/livescript/reader.ls index 1fd4734997..8ee4e08f0b 100644 --- a/livescript/reader.ls +++ b/livescript/reader.ls @@ -23,7 +23,12 @@ export read_str = (str) -> str |> tokenizer |> (tokens) -> new Reader tokens - |> read_form + |> (reader) -> + result = read_form reader + if reader.peek! + throw new Error "expected EOF, got #{reader.peek!}" + result + # This function will take a single string and return an array/list # of all the tokens (strings) in it. @@ -56,26 +61,28 @@ tokenizer = (str) -> read_form = (reader) -> if reader.peek! == '(' - read_list reader + read_list reader, ')' + else if reader.peek! == '[' + read_list reader, ']' else if reader.peek!? read_atom reader else throw new Error 'parse error: expected a form' -read_list = (reader) -> +read_list = (reader, end) -> list = [] - reader.next! # accept '(' + reader.next! # accept '(' or '[' loop token = reader.peek! if not token? - throw new Error 'expected \')\', got EOF' - else if token == ')' + throw new Error "expected '#{end}', got EOF" + else if token == end reader.next! break list.push read_form reader - return list + return {type: if end == ')' then \list else \vector, value: list} special_chars = '[]{}\'`~^@' @@ -84,7 +91,7 @@ read_atom = (reader) -> if token[0] == '"' {type: \string, value: reader.next!} else if token.match /^-?\d+$/ - {type: \int, value: reader.next!} + {type: \int, value: parseInt reader.next!} else if token != '~@' and token not in special_chars {type: \symbol, value: reader.next!} else diff --git a/livescript/step2_eval.ls b/livescript/step2_eval.ls new file mode 100644 index 0000000000..d74fad639c --- /dev/null +++ b/livescript/step2_eval.ls @@ -0,0 +1,51 @@ +readline = require './node_readline' +{id, map} = require 'prelude-ls' +{read_str} = require './reader' +{pr_str} = require './printer' + +repl_env = do + '+': + type: \function + value: (a, b) -> {type: \int, value: a.value + b.value} + '-': + type: \function + value: (a, b) -> {type: \int, value: a.value - b.value} + '*': + type: \function + value: (a, b) -> {type: \int, value: a.value * b.value} + '/': + type: \function + value: (a, b) -> {type: \int, value: parseInt(a.value / b.value)} + +eval_ast = (repl_env, {type, value}: ast) --> + switch type + | \symbol => + result = repl_env[value] + if not result? then throw new Error 'symbol not found: ', value + result + | \list, \vector => + result = value |> map eval_ast repl_env + if type == \list and result.length != 0 + fn = result[0] + if fn.type != \function + throw new Error fn.value, ' is not a function' + fn.value.apply repl_env, result.slice 1 + else + {type: type, value: result} + | otherwise => + ast + + +rep = (line) -> + line + |> read_str + |> eval_ast repl_env + |> pr_str + +loop + line = readline.readline 'user> ' + break if not line? or line == '' + try + console.log rep line + catch {message} + console.error message From a9c0e8ba8de4ea13fd92d6634e936d129f626a49 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sat, 6 May 2017 15:48:44 +0200 Subject: [PATCH 0316/2308] Step 3 --- livescript/Makefile | 2 +- livescript/env | 2 - livescript/env.ls | 16 ++++++ livescript/step3_env.ls | 115 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 3 deletions(-) delete mode 100644 livescript/env create mode 100644 livescript/env.ls create mode 100644 livescript/step3_env.ls diff --git a/livescript/Makefile b/livescript/Makefile index 0b5cacda3c..064a52daa3 100644 --- a/livescript/Makefile +++ b/livescript/Makefile @@ -1,6 +1,6 @@ TESTS = -SOURCES_BASE = reader.js printer.js +SOURCES_BASE = reader.js printer.js env.js SOURCES_LISP = SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) diff --git a/livescript/env b/livescript/env deleted file mode 100644 index 6c312492f1..0000000000 --- a/livescript/env +++ /dev/null @@ -1,2 +0,0 @@ -test - STEP=step0_repl ./run diff --git a/livescript/env.ls b/livescript/env.ls new file mode 100644 index 0000000000..46adbe2ab7 --- /dev/null +++ b/livescript/env.ls @@ -0,0 +1,16 @@ +export class Env + (outer = null, data = {}) -> + @outer = outer + @data = data + + set: (symbol, ast) -> + @data[symbol] = ast + + find: (symbol) -> + if symbol of @data then @ + else if @outer? then @outer.find symbol + + get: (symbol) -> + env = @find symbol + if env then env.data[symbol] + else throw new Error "symbol not found: #{symbol}" diff --git a/livescript/step3_env.ls b/livescript/step3_env.ls new file mode 100644 index 0000000000..9a493ef2fb --- /dev/null +++ b/livescript/step3_env.ls @@ -0,0 +1,115 @@ +readline = require './node_readline' +{id, map, each} = require 'prelude-ls' +{read_str} = require './reader' +{pr_str} = require './printer' +{Env} = require './env' + +repl_env = new Env null, do + '+': + type: \function + value: (a, b) -> {type: \int, value: a.value + b.value} + '-': + type: \function + value: (a, b) -> {type: \int, value: a.value - b.value} + '*': + type: \function + value: (a, b) -> {type: \int, value: a.value * b.value} + '/': + type: \function + value: (a, b) -> {type: \int, value: parseInt(a.value / b.value)} + + +is-symbol = ({type, value}: ast, name) -> + type == \symbol and value == name + + +list-to-pairs = (list) -> + [0 to (list.length - 2) by 2] \ + |> map (idx) -> [list[idx], list[idx+1]] + + +eval_ast = (env, {type, value}: ast) --> + switch type + | \symbol => env.get value + | \vector => do + type: \vector + value: value |> map eval_ast env + + | \list => + # Empty list, return empty list. + if value.length == 0 + ast + + # Symbol definition. + else if is-symbol value[0], 'def!' + if value.length != 3 + throw new Error "def! expected 2 parameters, + got #{value.length - 1}" + + # Name is in the first parameter, and is not evaluated. + name = value[1] + if name.type != \symbol + throw new Error "expected a symbol + for the first parameter of def!, + got a #{name.type}" + + # Evaluate the second parameter and store + # it under name in the env. + env.set name.value, (eval_ast env, value[2]) + + # Create a new environment. + else if is-symbol value[0], 'let*' + if value.length != 3 + throw new Error "let* expected 2 parameters, + got #{value.length - 1}" + + binding_list = value[1] + if binding_list.type not in [\list \vector] + throw new Error "expected 1st parameter of let* to + be a binding list (or vector), + got a #{binding_list.type}" + else if binding_list.value.length % 2 != 0 + throw new Error "binding list of let* must have an even + number of parameters" + + # Make a new environment with the + # current environment as outer. + let_env = new Env env + + # Evaluate all binding values in the + # new environment. + binding_list.value + |> list-to-pairs + |> each ([binding_name, binding_value]) -> + if binding_name.type != \symbol + throw new Error "expected a symbol as binding name, + got a #{binding_name.type}" + + let_env.set binding_name.value, (eval_ast let_env, binding_value) + + # Evaluate the 'body' of let* with the new environment. + eval_ast let_env, value[2] + else + [fn, ...args] = value |> map eval_ast env + if fn.type != \function + throw new Error fn.value, ' is not a function' + fn.value.apply env, args + + | otherwise => + ast + + +rep = (line) -> + line + |> read_str + |> eval_ast repl_env + |> pr_str + + +loop + line = readline.readline 'user> ' + break if not line? or line == '' + try + console.log rep line + catch {message} + console.error message From 3181c695486bb83b7e559b68cde5ac48a6bf3c9d Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Mon, 8 May 2017 21:31:26 +0200 Subject: [PATCH 0317/2308] Step 4 --- livescript/Makefile | 2 +- livescript/core.ls | 69 ++++++++++++++ livescript/printer.ls | 4 +- livescript/reader.ls | 6 +- livescript/step3_env.ls | 139 ++++++++++++++------------- livescript/step4_if_fn_do.ls | 177 +++++++++++++++++++++++++++++++++++ 6 files changed, 329 insertions(+), 68 deletions(-) create mode 100644 livescript/core.ls create mode 100644 livescript/step4_if_fn_do.ls diff --git a/livescript/Makefile b/livescript/Makefile index 064a52daa3..b80d940136 100644 --- a/livescript/Makefile +++ b/livescript/Makefile @@ -1,6 +1,6 @@ TESTS = -SOURCES_BASE = reader.js printer.js env.js +SOURCES_BASE = reader.js printer.js env.js core.js SOURCES_LISP = SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) diff --git a/livescript/core.ls b/livescript/core.ls new file mode 100644 index 0000000000..2f952f9b83 --- /dev/null +++ b/livescript/core.ls @@ -0,0 +1,69 @@ +{zip, map, apply, and-list, join} = require 'prelude-ls' +{pr_str} = require './printer' + + +export runtime-error = (msg) -> throw new Error msg + + +fn = (body) -> {type: \function, value: body} +const-nil = {type: \const, value: \nil} +const-int = (int) -> {type: \int, value: int} +const-bool = (bool) -> {type: \const, value: if bool then \true else \false} +const-str = (str) -> {type: \string, value: str} + +list-or-vector = ({type}) -> type in [\list \vector] + +deep-equals = (a, b) -> + if not list-or-vector a then + if a.type != b.type then false + else a.value == b.value + else if list-or-vector b then + if a.value.length != b.value.length then false + else + # Compare all elements of a and b with deep-equals. + zip a.value, b.value + |> map (apply deep-equals) + |> and-list # all must be true (equals) + else false + + +export ns = do + '+': fn (a, b) -> const-int a.value + b.value + '-': fn (a, b) -> const-int a.value - b.value + '*': fn (a, b) -> const-int a.value * b.value + '/': fn (a, b) -> const-int parseInt (a.value / b.value) + + 'list': fn (...list) -> {type: \list, value: list} + 'list?': fn (param) -> const-bool param.type == \list + + 'empty?': fn (param) -> + if not list-or-vector param + runtime-error "'empty?' expected first parameter + to be of type list or vector, + got a #{param.type}." + + const-bool param.value.length == 0 + + 'count': fn (param) -> + if not list-or-vector param + runtime-error "'count' expected first parameter + to be of type list or vector, + got a #{param.type}." + + const-int param.value.length + + 'prn': fn (param) -> + if param + console.log pr_str param + + const-nil + + '=': fn (a, b) -> const-bool (deep-equals a, b) + '<': fn (a, b) -> const-bool a.value < b.value + '>': fn (a, b) -> const-bool a.value > b.value + '<=': fn (a, b) -> const-bool a.value <= b.value + '>=': fn (a, b) -> const-bool a.value >= b.value + + 'not': fn (a) -> const-bool (a.type == \const and a.value == \false) + + 'str': fn (...params) -> const-str (params |> map pr_str |> join '') \ No newline at end of file diff --git a/livescript/printer.ls b/livescript/printer.ls index 0286c39397..f4c8d5138a 100644 --- a/livescript/printer.ls +++ b/livescript/printer.ls @@ -4,8 +4,10 @@ pr_list = (list) -> list |> map pr_str |> join ' ' export pr_str = ({type, value}: ast) -> switch type + | \const => value | \int => value - | \string => value + | \string => value # TODO encode string | \symbol => value | \list => '(' + (pr_list value) + ')' | \vector => '[' + (pr_list value) + ']' + | \function => '#' diff --git a/livescript/reader.ls b/livescript/reader.ls index 8ee4e08f0b..4b01a4b3f8 100644 --- a/livescript/reader.ls +++ b/livescript/reader.ls @@ -85,10 +85,14 @@ read_list = (reader, end) -> return {type: if end == ')' then \list else \vector, value: list} special_chars = '[]{}\'`~^@' +constants = [\true \false \nil] read_atom = (reader) -> token = reader.peek! - if token[0] == '"' + if token in constants + {type: \const, value: reader.next!} + else if token[0] == '"' + # TODO decode string {type: \string, value: reader.next!} else if token.match /^-?\d+$/ {type: \int, value: parseInt reader.next!} diff --git a/livescript/step3_env.ls b/livescript/step3_env.ls index 9a493ef2fb..3c7dfc5cb8 100644 --- a/livescript/step3_env.ls +++ b/livescript/step3_env.ls @@ -28,75 +28,84 @@ list-to-pairs = (list) -> |> map (idx) -> [list[idx], list[idx+1]] -eval_ast = (env, {type, value}: ast) --> +eval_simple = (env, {type, value}: ast) -> switch type | \symbol => env.get value - | \vector => do - type: \vector + | \list, \vector => do + type: type value: value |> map eval_ast env + | otherwise => ast + - | \list => - # Empty list, return empty list. - if value.length == 0 - ast - - # Symbol definition. - else if is-symbol value[0], 'def!' - if value.length != 3 - throw new Error "def! expected 2 parameters, - got #{value.length - 1}" - - # Name is in the first parameter, and is not evaluated. - name = value[1] - if name.type != \symbol - throw new Error "expected a symbol - for the first parameter of def!, - got a #{name.type}" - - # Evaluate the second parameter and store - # it under name in the env. - env.set name.value, (eval_ast env, value[2]) - - # Create a new environment. - else if is-symbol value[0], 'let*' - if value.length != 3 - throw new Error "let* expected 2 parameters, - got #{value.length - 1}" - - binding_list = value[1] - if binding_list.type not in [\list \vector] - throw new Error "expected 1st parameter of let* to - be a binding list (or vector), - got a #{binding_list.type}" - else if binding_list.value.length % 2 != 0 - throw new Error "binding list of let* must have an even - number of parameters" - - # Make a new environment with the - # current environment as outer. - let_env = new Env env - - # Evaluate all binding values in the - # new environment. - binding_list.value - |> list-to-pairs - |> each ([binding_name, binding_value]) -> - if binding_name.type != \symbol - throw new Error "expected a symbol as binding name, - got a #{binding_name.type}" - - let_env.set binding_name.value, (eval_ast let_env, binding_value) - - # Evaluate the 'body' of let* with the new environment. - eval_ast let_env, value[2] - else - [fn, ...args] = value |> map eval_ast env - if fn.type != \function - throw new Error fn.value, ' is not a function' - fn.value.apply env, args - - | otherwise => - ast +eval_ast = (env, {type, value}: ast) --> + if type != \list then eval_simple env, ast + else if value.length == 0 then ast + else if value[0].type == \symbol + params = value[1 to] + switch value[0].value + | 'def!' => eval_def env, params + | 'let*' => eval_let env, params + | otherwise => eval_apply env, value + else + eval_apply env, value + + +check_params = (name, params, expected) -> + if params.length != expected + throw new Error "#{name} expected #{expected} parameters, + got #{params.length}" + + +eval_def = (env, params) -> + check_params 'def!', params, 2 + + # Name is in the first parameter, and is not evaluated. + name = params[0] + if name.type != \symbol + throw new Error "expected a symbol for the first parameter + of def!, got a #{name.type}" + + # Evaluate the second parameter and store + # it under name in the env. + env.set name.value, (eval_ast env, params[1]) + + +eval_let = (env, params) -> + check_params 'let*', params, 2 + + binding_list = params[0] + if binding_list.type not in [\list \vector] + throw new Error "expected 1st parameter of let* to + be a binding list (or vector), + got a #{binding_list.type}" + else if binding_list.value.length % 2 != 0 + throw new Error "binding list of let* must have an even + number of parameters" + + # Make a new environment with the + # current environment as outer. + let_env = new Env env + + # Evaluate all binding values in the + # new environment. + binding_list.value + |> list-to-pairs + |> each ([binding_name, binding_value]) -> + if binding_name.type != \symbol + throw new Error "expected a symbol as binding name, + got a #{binding_name.type}" + + let_env.set binding_name.value, (eval_ast let_env, binding_value) + + # Evaluate the 'body' of let* with the new environment. + eval_ast let_env, params[1] + + +eval_apply = (env, list) -> + [fn, ...args] = list |> map eval_ast env + if fn.type != \function + throw new Error fn.value, ' is not a function' + fn.value.apply env, args rep = (line) -> diff --git a/livescript/step4_if_fn_do.ls b/livescript/step4_if_fn_do.ls new file mode 100644 index 0000000000..52488564e3 --- /dev/null +++ b/livescript/step4_if_fn_do.ls @@ -0,0 +1,177 @@ +readline = require './node_readline' +{id, map, each, last, all, unique, zip} = require 'prelude-ls' +{read_str} = require './reader' +{pr_str} = require './printer' +{Env} = require './env' +{runtime-error, ns} = require './core' + + +is-thruthy = ({type, value}) -> + type != \const or value not in [\nil \false] + + +list-to-pairs = (list) -> + [0 to (list.length - 2) by 2] \ + |> map (idx) -> [list[idx], list[idx+1]] + + +eval_simple = (env, {type, value}: ast) -> + switch type + | \symbol => env.get value + | \list, \vector => do + type: type + value: value |> map eval_ast env + | otherwise => ast + + +eval_ast = (env, {type, value}: ast) --> + if type != \list then eval_simple env, ast + else if value.length == 0 then ast + else if value[0].type == \symbol + params = value[1 to] + switch value[0].value + | 'def!' => eval_def env, params + | 'let*' => eval_let env, params + | 'do' => eval_do env, params + | 'if' => eval_if env, params + | 'fn*' => eval_fn env, params + | otherwise => eval_apply env, value + else + eval_apply env, value + + +check_params = (name, params, expected) -> + if params.length != expected + runtime-error "'#{name}' expected #{expected} parameters, + got #{params.length}" + + +eval_def = (env, params) -> + check_params 'def!', params, 2 + + # Name is in the first parameter, and is not evaluated. + name = params[0] + if name.type != \symbol + runtime-error "expected a symbol for the first parameter + of def!, got a #{name.type}" + + # Evaluate the second parameter and store + # it under name in the env. + env.set name.value, (eval_ast env, params[1]) + + +eval_let = (env, params) -> + check_params 'let*', params, 2 + + binding_list = params[0] + if binding_list.type not in [\list \vector] + runtime-error "expected 1st parameter of 'let*' to + be a binding list (or vector), + got a #{binding_list.type}" + else if binding_list.value.length % 2 != 0 + runtime-error "binding list of 'let*' must have an even + number of parameters" + + # Make a new environment with the + # current environment as outer. + let_env = new Env env + + # Evaluate all binding values in the + # new environment. + binding_list.value + |> list-to-pairs + |> each ([binding_name, binding_value]) -> + if binding_name.type != \symbol + runtime-error "expected a symbol as binding name, + got a #{binding_name.type}" + + let_env.set binding_name.value, (eval_ast let_env, binding_value) + + # Evaluate the 'body' of let* with the new environment. + eval_ast let_env, params[1] + + +eval_do = (env, params) -> + if params.length == 0 + runtime-error "'do' expected at least one parameter" + + params |> map eval_ast env |> last + + +eval_if = (env, params) -> + if params.length < 2 + runtime-error "'if' expected at least 2 parameters" + else if params.length > 3 + runtime-error "'if' expected at most 3 parameters" + + cond = eval_ast env, params[0] + if is-thruthy cond + eval_ast env, params[1] + else if params.length > 2 + eval_ast env, params[2] + else + {type: \const, value: \nil} + + +eval_fn = (env, params) -> + check_params 'fn*', params, 2 + + if params[0].type not in [\list \vector] + runtime-error "'fn*' expected first parameter to be a list or vector." + + # TODO also support (& args) + # and (a & args) + + if not all (.type == \symbol), params[0].value + runtime-error "'fn*' expected only symbols in the parameters list." + + binds = params[0].value |> map (.value) + + if (unique binds).length != binds.length + runtime-error "'fn*' duplicate symbols in parameters list." + + body = params[1] + + fn_instance = (...values) -> + if values.length != binds.length + runtime-error "function expected #{binds.length} parameters, + got #{values.length}" + + # Set binds to values in the new env. + fn_env = new Env env + for [name, value] in (zip binds, values) + fn_env.set name, value + + # Evaluate the function body with the new environment. + eval_ast fn_env, body + + {type: \function, value: fn_instance} + + +eval_apply = (env, list) -> + [fn, ...args] = list |> map eval_ast env + if fn.type != \function + runtime-error "#{fn.value} is not a function" + + fn.value.apply env, args + + +repl_env = new Env +for symbol, value of ns + repl_env.set symbol, value + + +rep = (line) -> + line + |> read_str + |> eval_ast repl_env + |> pr_str + + +loop + line = readline.readline 'user> ' + break if not line? or line == '' + try + console.log rep line + catch {message} + console.error message From 2ff2d84b40526c21f1f58f95d6bc4ddc5142f78d Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Wed, 10 May 2017 17:57:31 +0200 Subject: [PATCH 0318/2308] Deferrables until step 4 --- livescript/Makefile | 2 +- livescript/core.ls | 77 +++++++++++++------- livescript/printer.ls | 54 +++++++++++--- livescript/reader.ls | 129 +++++++++++++++++++++++++++------ livescript/step1_read_print.ls | 7 +- livescript/step2_eval.ls | 4 +- livescript/step4_if_fn_do.ls | 41 ++++++++--- livescript/utils.ls | 6 ++ 8 files changed, 245 insertions(+), 75 deletions(-) create mode 100644 livescript/utils.ls diff --git a/livescript/Makefile b/livescript/Makefile index b80d940136..62b91c1bb2 100644 --- a/livescript/Makefile +++ b/livescript/Makefile @@ -1,6 +1,6 @@ TESTS = -SOURCES_BASE = reader.js printer.js env.js core.js +SOURCES_BASE = reader.js printer.js env.js core.js utils.js SOURCES_LISP = SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) diff --git a/livescript/core.ls b/livescript/core.ls index 2f952f9b83..433b6311e2 100644 --- a/livescript/core.ls +++ b/livescript/core.ls @@ -1,4 +1,4 @@ -{zip, map, apply, and-list, join} = require 'prelude-ls' +{zip, map, apply, and-list, join, Obj} = require 'prelude-ls' {pr_str} = require './printer' @@ -6,7 +6,7 @@ export runtime-error = (msg) -> throw new Error msg fn = (body) -> {type: \function, value: body} -const-nil = {type: \const, value: \nil} +const-nil = -> {type: \const, value: \nil} const-int = (int) -> {type: \int, value: int} const-bool = (bool) -> {type: \const, value: if bool then \true else \false} const-str = (str) -> {type: \string, value: str} @@ -36,27 +36,31 @@ export ns = do 'list': fn (...list) -> {type: \list, value: list} 'list?': fn (param) -> const-bool param.type == \list - 'empty?': fn (param) -> - if not list-or-vector param - runtime-error "'empty?' expected first parameter - to be of type list or vector, - got a #{param.type}." - - const-bool param.value.length == 0 - - 'count': fn (param) -> - if not list-or-vector param - runtime-error "'count' expected first parameter - to be of type list or vector, - got a #{param.type}." - - const-int param.value.length - - 'prn': fn (param) -> - if param - console.log pr_str param - - const-nil + 'empty?': fn ({type, value}) -> + switch type + | \const => + if value == \nil + then const-bool true + else runtime-error "'empty?' is not supported on #{value}" + | \list, \vector => + const-bool value.length == 0 + | \map => + const-bool Obj.empty value + | otherwise => + runtime-error "'empty?' is not supported on type #{type}" + + 'count': fn ({type, value}) -> + switch type + | \const => + if value == \nil + then const-int 0 + else runtime-error "'count' is not supported on #{value}" + | \list, \vector => + const-int value.length + | \map => + value |> Obj.keys |> (.length) |> const-int + | otherwise => + runtime-error "'count' is not supported on type #{type}" '=': fn (a, b) -> const-bool (deep-equals a, b) '<': fn (a, b) -> const-bool a.value < b.value @@ -64,6 +68,27 @@ export ns = do '<=': fn (a, b) -> const-bool a.value <= b.value '>=': fn (a, b) -> const-bool a.value >= b.value - 'not': fn (a) -> const-bool (a.type == \const and a.value == \false) - - 'str': fn (...params) -> const-str (params |> map pr_str |> join '') \ No newline at end of file + 'not': fn ({type, value}) -> + const-bool (type == \const and value == \false) + + 'pr-str': fn (...params) -> + params |> map (p) -> pr_str p, print_readably=true + |> join ' ' + |> const-str + + 'str': fn (...params) -> + params |> map (p) -> pr_str p, print_readably=false + |> join '' + |> const-str + + 'prn': fn (...params) -> + params |> map (p) -> pr_str p, print_readably=true + |> join ' ' + |> console.log + |> const-nil + + 'println': fn (...params) -> + params |> map (p) -> pr_str p, print_readbly=false + |> join ' ' + |> console.log + |> const-nil diff --git a/livescript/printer.ls b/livescript/printer.ls index f4c8d5138a..05277af4dc 100644 --- a/livescript/printer.ls +++ b/livescript/printer.ls @@ -1,13 +1,49 @@ -{is-type, map, join} = require 'prelude-ls' +{is-type, map, join, obj-to-pairs} = require 'prelude-ls' +{keyword-prefix} = require './reader' -pr_list = (list) -> list |> map pr_str |> join ' ' -export pr_str = ({type, value}: ast) -> +export pr_str = ({type, value}: ast, print_readably=true) -> switch type - | \const => value - | \int => value - | \string => value # TODO encode string - | \symbol => value - | \list => '(' + (pr_list value) + ')' - | \vector => '[' + (pr_list value) + ']' + | \const => value + | \int => value + | \string => + if print_readably + then encode-string value + else value + | \symbol => value + | \keyword => value + | \list => '(' + (pr_list value, print_readably) + ')' + | \vector => '[' + (pr_list value, print_readably) + ']' + | \map => '{' + (pr_map value, print_readably) + '}' | \function => '#' + + +encode-string = (str) -> + str |> (.replace /[\n\"\\]/g, + (ch) -> switch ch + | '\n' => '\\n' + | '"' => '\\"' + | '\\' => '\\\\') + |> (enc) -> "\"#{enc}\"" + + +pr_list = (list, print_readably) -> + list |> map (ast) -> pr_str ast, print_readably + |> join ' ' + + +pr_map_key = (key, print_readably) -> + if key.startsWith keyword-prefix + key.substring 1 + else if print_readably + encode-string key + else + key + +pr_map = (obj, print_readably) -> + obj |> obj-to-pairs + |> map ([key, value]) -> + key_str = pr_map_key key, print_readably + value_str = pr_str value, print_readably + key_str + ' ' + value_str + |> join ' ' diff --git a/livescript/reader.ls b/livescript/reader.ls index 4b01a4b3f8..dfde5762c0 100644 --- a/livescript/reader.ls +++ b/livescript/reader.ls @@ -1,5 +1,10 @@ readline = require 'readline' -{id} = require 'prelude-ls' +{id, map, pairs-to-obj} = require 'prelude-ls' +{list-to-pairs} = require './utils' + +export class OnlyComment + +parse-error = (msg) -> throw new Error msg class Reader (tokens) -> @@ -19,14 +24,26 @@ class Reader @tokens[@pos] +accept-comment = (reader) -> + token = reader.peek! + if token? and token.startsWith ';' + throw new OnlyComment + + +eof-or-comment = (reader) -> + token = reader.peek! + if token? and not token.startsWith ';' + then parse-error "expected EOF, got '#{token}'" + + export read_str = (str) -> str |> tokenizer |> (tokens) -> new Reader tokens |> (reader) -> + accept-comment reader result = read_form reader - if reader.peek! - throw new Error "expected EOF, got #{reader.peek!}" + eof-or-comment reader result @@ -51,52 +68,118 @@ tokenizer = (str) -> if not m # Allow whitespace or commas at the end of the input. break if /[\s,]+/.exec str.substring idx - throw new Error 'parse error at character ' + idx + parse-error "parse error at character #{idx}" - # Ignore comments. - tok = m[1] - if tok[1] != ';' then tokens.push tok + tokens.push m[1] tokens read_form = (reader) -> - if reader.peek! == '(' - read_list reader, ')' - else if reader.peek! == '[' - read_list reader, ']' - else if reader.peek!? - read_atom reader - else - throw new Error 'parse error: expected a form' + switch reader.peek! + | '(' => read_list reader, ')' + | '[' => read_list reader, ']' + | '{' => read_list reader, '}' + | '\'' => read-macro 'quote', reader + | '\`' => read-macro 'quasiquote', reader + | '~' => read-macro 'unquote', reader + | '~@' => read-macro 'splice-unquote', reader + | '@' => read-macro 'deref', reader # todo only symbol? + | '^' => read-with-meta reader + | otherwise => + if that? then read_atom reader + else parse-error 'expected a form, got EOF' + read_list = (reader, end) -> list = [] - reader.next! # accept '(' or '[' + reader.next! # accept '(', '[' or '{' loop token = reader.peek! if not token? - throw new Error "expected '#{end}', got EOF" + parse-error "expected '#{end}', got EOF" else if token == end reader.next! break list.push read_form reader - - return {type: if end == ')' then \list else \vector, value: list} + + switch end + | ')' => {type: \list, value: list} + | ']' => {type: \vector, value: list} + | '}' => list-to-map list + special_chars = '[]{}\'`~^@' constants = [\true \false \nil] + read_atom = (reader) -> token = reader.peek! if token in constants {type: \const, value: reader.next!} else if token[0] == '"' - # TODO decode string - {type: \string, value: reader.next!} + {type: \string, value: decode-string reader.next!} else if token.match /^-?\d+$/ {type: \int, value: parseInt reader.next!} else if token != '~@' and token not in special_chars - {type: \symbol, value: reader.next!} + if token.startsWith ':' + {type: \keyword, value: reader.next!} + else + {type: \symbol, value: reader.next!} else - throw new Error 'parse error: expected an atom, got ' + token + parse-error "expected an atom, got #{token}" + + +decode-string = (str) -> + str |> (.slice 1, -1) + |> (.replace /\\[\"\\n]/g, + (esc) -> switch esc + | '\\n' => '\n' + | '\\"' => '"' + | '\\\\' => '\\') + + +export keyword-prefix = '\u029e' + + +list-to-map = (list) -> + if list.length % 2 != 0 + parse-error "map should have an even number + of elements, got #{list.length}" + + list-to-pairs list + |> map ([key, value]) -> + switch key.type + | \string => [key.value, value] + | \keyword => [keyword-prefix + key.value, value] + | otherwise => + parse-error "map can only have strings or keywords as keys, + got a #{key.type}" + |> pairs-to-obj + |> (obj) -> {type: \map, value: obj} + + +read-macro = (symbol, reader) -> + reader.next! # accept macro start token + + do + type: \list + value: + * {type: \symbol, value: symbol} + * read_form reader + + +read-with-meta = (reader) -> + reader.next! # accept ^ + if reader.peek! != '{' + parse-error "expected a map after with-meta reader macro '^'" + + meta = read_list reader, '}' + form = read_form reader + + do + type: \list + value: + * {type: \symbol, value: 'with-meta'} + * form + * meta diff --git a/livescript/step1_read_print.ls b/livescript/step1_read_print.ls index cd1ca0225f..1c15955a16 100644 --- a/livescript/step1_read_print.ls +++ b/livescript/step1_read_print.ls @@ -1,6 +1,6 @@ readline = require './node_readline' {id} = require 'prelude-ls' -{read_str} = require './reader' +{read_str, OnlyComment} = require './reader' {pr_str} = require './printer' @@ -13,5 +13,6 @@ loop break if not line? or line == '' try console.log rep line - catch {message} - console.log message + catch {message}: ex + if ex not instanceof OnlyComment + console.log message diff --git a/livescript/step2_eval.ls b/livescript/step2_eval.ls index d74fad639c..aa4124da97 100644 --- a/livescript/step2_eval.ls +++ b/livescript/step2_eval.ls @@ -1,5 +1,5 @@ readline = require './node_readline' -{id, map} = require 'prelude-ls' +{id, map, Obj} = require 'prelude-ls' {read_str} = require './reader' {pr_str} = require './printer' @@ -32,6 +32,8 @@ eval_ast = (repl_env, {type, value}: ast) --> fn.value.apply repl_env, result.slice 1 else {type: type, value: result} + | \map => + {type: \map, value: value |> Obj.map eval_ast repl_env} | otherwise => ast diff --git a/livescript/step4_if_fn_do.ls b/livescript/step4_if_fn_do.ls index 52488564e3..e368ff2e42 100644 --- a/livescript/step4_if_fn_do.ls +++ b/livescript/step4_if_fn_do.ls @@ -1,26 +1,25 @@ readline = require './node_readline' -{id, map, each, last, all, unique, zip} = require 'prelude-ls' +{id, map, each, last, all, unique, zip, Obj, elem-index} = require 'prelude-ls' {read_str} = require './reader' {pr_str} = require './printer' {Env} = require './env' {runtime-error, ns} = require './core' +{list-to-pairs} = require './utils' is-thruthy = ({type, value}) -> type != \const or value not in [\nil \false] -list-to-pairs = (list) -> - [0 to (list.length - 2) by 2] \ - |> map (idx) -> [list[idx], list[idx+1]] +fmap-ast = (fn, {type, value}: ast) --> + {type: type, value: fn value} eval_simple = (env, {type, value}: ast) -> switch type | \symbol => env.get value - | \list, \vector => do - type: type - value: value |> map eval_ast env + | \list, \vector => ast |> fmap-ast map eval_ast env + | \map => ast |> fmap-ast Obj.map eval_ast env | otherwise => ast @@ -119,13 +118,21 @@ eval_fn = (env, params) -> if params[0].type not in [\list \vector] runtime-error "'fn*' expected first parameter to be a list or vector." - # TODO also support (& args) - # and (a & args) - if not all (.type == \symbol), params[0].value runtime-error "'fn*' expected only symbols in the parameters list." binds = params[0].value |> map (.value) + vargs = null + + # Parse variadic bind. + if binds.length >= 2 + [...rest, amper, name] = binds + if amper == '&' and name != '&' + binds = rest + vargs = name + + if elem-index '&', binds + runtime-error "'fn*' invalid usage of variadic parameters." if (unique binds).length != binds.length runtime-error "'fn*' duplicate symbols in parameters list." @@ -133,15 +140,25 @@ eval_fn = (env, params) -> body = params[1] fn_instance = (...values) -> - if values.length != binds.length + if not vargs and values.length != binds.length runtime-error "function expected #{binds.length} parameters, got #{values.length}" + else if vargs and values.length < binds.length + runtime-error "function expected at least + #{binds.length} parameters, + got #{values.length}" # Set binds to values in the new env. fn_env = new Env env + for [name, value] in (zip binds, values) fn_env.set name, value + if vargs + fn_env.set vargs, do + type: \list + value: values.slice binds.length + # Evaluate the function body with the new environment. eval_ast fn_env, body @@ -165,7 +182,7 @@ rep = (line) -> line |> read_str |> eval_ast repl_env - |> pr_str + |> (ast) -> pr_str ast, print_readably=true loop diff --git a/livescript/utils.ls b/livescript/utils.ls new file mode 100644 index 0000000000..21d1ac3c22 --- /dev/null +++ b/livescript/utils.ls @@ -0,0 +1,6 @@ +{map} = require 'prelude-ls' + + +export list-to-pairs = (list) -> + [0 to (list.length - 2) by 2] \ + |> map (idx) -> [list[idx], list[idx+1]] From 3b88488d82459d0dffd5a96486d2e67aae0b64b8 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Wed, 10 May 2017 21:27:23 +0200 Subject: [PATCH 0319/2308] Step 5 --- livescript/step5_tco.ls | 212 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 livescript/step5_tco.ls diff --git a/livescript/step5_tco.ls b/livescript/step5_tco.ls new file mode 100644 index 0000000000..04be185498 --- /dev/null +++ b/livescript/step5_tco.ls @@ -0,0 +1,212 @@ +readline = require './node_readline' +{id, map, each, last, all, unique, zip, Obj, elem-index} = require 'prelude-ls' +{read_str} = require './reader' +{pr_str} = require './printer' +{Env} = require './env' +{runtime-error, ns} = require './core' +{list-to-pairs} = require './utils' + + +defer-tco = (env, ast) -> + type: \tco + env: env + ast: ast + + +is-thruthy = ({type, value}) -> + type != \const or value not in [\nil \false] + + +fmap-ast = (fn, {type, value}: ast) --> + {type: type, value: fn value} + + +eval_simple = (env, {type, value}: ast) -> + switch type + | \symbol => env.get value + | \list, \vector => ast |> fmap-ast map eval_ast env + | \map => ast |> fmap-ast Obj.map eval_ast env + | otherwise => ast + + +eval_ast = (env, {type, value}: ast) --> + loop + if type != \list + return eval_simple env, ast + else if value.length == 0 + return ast + + result = if value[0].type == \symbol + params = value[1 to] + switch value[0].value + | 'def!' => eval_def env, params + | 'let*' => eval_let env, params + | 'do' => eval_do env, params + | 'if' => eval_if env, params + | 'fn*' => eval_fn env, params + | otherwise => eval_apply env, value + else + eval_apply env, value + + if result.type == \tco + env = result.env + {type, value}: ast = result.ast + else + return result + + +check_params = (name, params, expected) -> + if params.length != expected + runtime-error "'#{name}' expected #{expected} parameters, + got #{params.length}" + + +eval_def = (env, params) -> + check_params 'def!', params, 2 + + # Name is in the first parameter, and is not evaluated. + name = params[0] + if name.type != \symbol + runtime-error "expected a symbol for the first parameter + of def!, got a #{name.type}" + + # Evaluate the second parameter and store + # it under name in the env. + env.set name.value, (eval_ast env, params[1]) + + +eval_let = (env, params) -> + check_params 'let*', params, 2 + + binding_list = params[0] + if binding_list.type not in [\list \vector] + runtime-error "expected 1st parameter of 'let*' to + be a binding list (or vector), + got a #{binding_list.type}" + else if binding_list.value.length % 2 != 0 + runtime-error "binding list of 'let*' must have an even + number of parameters" + + # Make a new environment with the + # current environment as outer. + let_env = new Env env + + # Evaluate all binding values in the + # new environment. + binding_list.value + |> list-to-pairs + |> each ([binding_name, binding_value]) -> + if binding_name.type != \symbol + runtime-error "expected a symbol as binding name, + got a #{binding_name.type}" + + let_env.set binding_name.value, (eval_ast let_env, binding_value) + + # Defer evaluation of let* body with TCO. + defer-tco let_env, params[1] + + +eval_do = (env, params) -> + if params.length == 0 + runtime-error "'do' expected at least one parameter" + + [...rest, last-param] = params + rest |> each eval_ast env + tco env, last-param + + +eval_if = (env, params) -> + if params.length < 2 + runtime-error "'if' expected at least 2 parameters" + else if params.length > 3 + runtime-error "'if' expected at most 3 parameters" + + cond = eval_ast env, params[0] + if is-thruthy cond + defer-tco env, params[1] + else if params.length > 2 + defer-tco env, params[2] + else + {type: \const, value: \nil} + + +eval_fn = (env, params) -> + check_params 'fn*', params, 2 + + if params[0].type not in [\list \vector] + runtime-error "'fn*' expected first parameter to be a list or vector." + + if not all (.type == \symbol), params[0].value + runtime-error "'fn*' expected only symbols in the parameters list." + + binds = params[0].value |> map (.value) + vargs = null + + # Parse variadic bind. + if binds.length >= 2 + [...rest, amper, name] = binds + if amper == '&' and name != '&' + binds = rest + vargs = name + + if elem-index '&', binds + runtime-error "'fn*' invalid usage of variadic parameters." + + if (unique binds).length != binds.length + runtime-error "'fn*' duplicate symbols in parameters list." + + body = params[1] + + fn_instance = (...values) -> + if not vargs and values.length != binds.length + runtime-error "function expected #{binds.length} parameters, + got #{values.length}" + else if vargs and values.length < binds.length + runtime-error "function expected at least + #{binds.length} parameters, + got #{values.length}" + + # Set binds to values in the new env. + fn_env = new Env env + + for [name, value] in (zip binds, values) + fn_env.set name, value + + if vargs + fn_env.set vargs, do + type: \list + value: values.slice binds.length + + # Defer evaluation of the function body to TCO. + defer-tco fn_env, body + + {type: \function, value: fn_instance} + + +eval_apply = (env, list) -> + [fn, ...args] = list |> map eval_ast env + if fn.type != \function + runtime-error "#{fn.value} is not a function" + + fn.value.apply env, args + + +repl_env = new Env +for symbol, value of ns + repl_env.set symbol, value + + +rep = (line) -> + line + |> read_str + |> eval_ast repl_env + |> (ast) -> pr_str ast, print_readably=true + + +loop + line = readline.readline 'user> ' + break if not line? or line == '' + try + console.log rep line + catch {message} + console.error message From 25bb14c94ab0ac8393303cac51bd7448338728ae Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Thu, 11 May 2017 21:37:12 +0200 Subject: [PATCH 0320/2308] Step 6 --- livescript/core.ls | 40 +++++++ livescript/error.ls | 0 livescript/printer.ls | 1 + livescript/reader.ls | 14 +-- livescript/step6_file.ls | 241 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 286 insertions(+), 10 deletions(-) create mode 100644 livescript/error.ls create mode 100644 livescript/step6_file.ls diff --git a/livescript/core.ls b/livescript/core.ls index 433b6311e2..d02b1f2bb3 100644 --- a/livescript/core.ls +++ b/livescript/core.ls @@ -1,5 +1,7 @@ {zip, map, apply, and-list, join, Obj} = require 'prelude-ls' {pr_str} = require './printer' +{read_str} = require './reader' +fs = require 'fs' export runtime-error = (msg) -> throw new Error msg @@ -27,6 +29,11 @@ deep-equals = (a, b) -> else false +check-type = (name, required-type, given-type) -> + if required-type != given-type + runtime-error "'#{name}' is not supported on #{given-type}" + + export ns = do '+': fn (a, b) -> const-int a.value + b.value '-': fn (a, b) -> const-int a.value - b.value @@ -92,3 +99,36 @@ export ns = do |> join ' ' |> console.log |> const-nil + + 'read-string': fn ({type, value}) -> + check-type 'read-string', \string, type + read_str value + + 'slurp': fn (filename) -> + if filename.type != \string + runtime-error "'slurp' expected the first parameter + to be a string, got a #{filename.type}" + + const-str <| fs.readFileSync filename.value, 'utf8' + + 'atom': fn (value) -> {type: \atom, value: value} + 'atom?': fn (atom) -> const-bool atom.type == \atom + 'deref': fn (atom) -> + check-type 'deref', \atom, atom.type + atom.value + + 'reset!': fn (atom, value) -> + check-type 'reset!', \atom, atom.type + atom.value = value + + 'swap!': fn (atom, fn, ...args) -> + check-type 'swap!', \atom, atom.type + if fn.type != \function + runtime-error "'swap!' expected the second parameter + to be a function, got a #{fn.type}" + + atom.value = fn.value.apply @, [atom.value] ++ args + if atom.value.type == \tco # TODO make this a method. + atom.value = atom.value.eval! + + atom.value diff --git a/livescript/error.ls b/livescript/error.ls new file mode 100644 index 0000000000..e69de29bb2 diff --git a/livescript/printer.ls b/livescript/printer.ls index 05277af4dc..f02fc429a9 100644 --- a/livescript/printer.ls +++ b/livescript/printer.ls @@ -16,6 +16,7 @@ export pr_str = ({type, value}: ast, print_readably=true) -> | \vector => '[' + (pr_list value, print_readably) + ']' | \map => '{' + (pr_map value, print_readably) + '}' | \function => '#' + | \atom => '(atom ' + (pr_str value) + ')' encode-string = (str) -> diff --git a/livescript/reader.ls b/livescript/reader.ls index dfde5762c0..1397283356 100644 --- a/livescript/reader.ls +++ b/livescript/reader.ls @@ -24,12 +24,6 @@ class Reader @tokens[@pos] -accept-comment = (reader) -> - token = reader.peek! - if token? and token.startsWith ';' - throw new OnlyComment - - eof-or-comment = (reader) -> token = reader.peek! if token? and not token.startsWith ';' @@ -41,9 +35,8 @@ export read_str = (str) -> |> tokenizer |> (tokens) -> new Reader tokens |> (reader) -> - accept-comment reader result = read_form reader - eof-or-comment reader + if token? then parse-error "expected EOF, got '#{token}'" result @@ -64,13 +57,14 @@ tokenizer = (str) -> while re.lastIndex < str.length idx = re.lastIndex m = re.exec str - # console.log 'at ', idx, 'matched', m if not m # Allow whitespace or commas at the end of the input. break if /[\s,]+/.exec str.substring idx parse-error "parse error at character #{idx}" - tokens.push m[1] + tok = m[1] + # Ignore comments. + if tok[0] != ';' then tokens.push m[1] tokens diff --git a/livescript/step6_file.ls b/livescript/step6_file.ls new file mode 100644 index 0000000000..1844ebe80f --- /dev/null +++ b/livescript/step6_file.ls @@ -0,0 +1,241 @@ +readline = require './node_readline' +{id, map, each, last, all, unique, zip, Obj, elem-index} = require 'prelude-ls' +{read_str} = require './reader' +{pr_str} = require './printer' +{Env} = require './env' +{runtime-error, ns} = require './core' +{list-to-pairs} = require './utils' + + +defer-tco = (env, ast) -> + type: \tco + env: env + ast: ast + eval: -> eval_ast env, ast + + +is-thruthy = ({type, value}) -> + type != \const or value not in [\nil \false] + + +fmap-ast = (fn, {type, value}: ast) --> + {type: type, value: fn value} + + +eval_simple = (env, {type, value}: ast) -> + switch type + | \symbol => env.get value + | \list, \vector => ast |> fmap-ast map eval_ast env + | \map => ast |> fmap-ast Obj.map eval_ast env + | otherwise => ast + + +eval_ast = (env, {type, value}: ast) --> + loop + if type != \list + return eval_simple env, ast + else if value.length == 0 + return ast + + result = if value[0].type == \symbol + params = value[1 to] + switch value[0].value + | 'def!' => eval_def env, params + | 'let*' => eval_let env, params + | 'do' => eval_do env, params + | 'if' => eval_if env, params + | 'fn*' => eval_fn env, params + | otherwise => eval_apply env, value + else + eval_apply env, value + + if result.type == \tco + env = result.env + {type, value}: ast = result.ast + else + return result + + +check_params = (name, params, expected) -> + if params.length != expected + runtime-error "'#{name}' expected #{expected} parameters, + got #{params.length}" + + +eval_def = (env, params) -> + check_params 'def!', params, 2 + + # Name is in the first parameter, and is not evaluated. + name = params[0] + if name.type != \symbol + runtime-error "expected a symbol for the first parameter + of def!, got a #{name.type}" + + # Evaluate the second parameter and store + # it under name in the env. + env.set name.value, (eval_ast env, params[1]) + + +eval_let = (env, params) -> + check_params 'let*', params, 2 + + binding_list = params[0] + if binding_list.type not in [\list \vector] + runtime-error "expected 1st parameter of 'let*' to + be a binding list (or vector), + got a #{binding_list.type}" + else if binding_list.value.length % 2 != 0 + runtime-error "binding list of 'let*' must have an even + number of parameters" + + # Make a new environment with the + # current environment as outer. + let_env = new Env env + + # Evaluate all binding values in the + # new environment. + binding_list.value + |> list-to-pairs + |> each ([binding_name, binding_value]) -> + if binding_name.type != \symbol + runtime-error "expected a symbol as binding name, + got a #{binding_name.type}" + + let_env.set binding_name.value, (eval_ast let_env, binding_value) + + # Defer evaluation of let* body with TCO. + defer-tco let_env, params[1] + + +eval_do = (env, params) -> + if params.length == 0 + runtime-error "'do' expected at least one parameter" + + [...rest, last-param] = params + rest |> each eval_ast env + defer-tco env, last-param + + +eval_if = (env, params) -> + if params.length < 2 + runtime-error "'if' expected at least 2 parameters" + else if params.length > 3 + runtime-error "'if' expected at most 3 parameters" + + cond = eval_ast env, params[0] + if is-thruthy cond + defer-tco env, params[1] + else if params.length > 2 + defer-tco env, params[2] + else + {type: \const, value: \nil} + + +eval_fn = (env, params) -> + check_params 'fn*', params, 2 + + if params[0].type not in [\list \vector] + runtime-error "'fn*' expected first parameter to be a list or vector." + + if not all (.type == \symbol), params[0].value + runtime-error "'fn*' expected only symbols in the parameters list." + + binds = params[0].value |> map (.value) + vargs = null + + # Parse variadic bind. + if binds.length >= 2 + [...rest, amper, name] = binds + if amper == '&' and name != '&' + binds = rest + vargs = name + + if elem-index '&', binds + runtime-error "'fn*' invalid usage of variadic parameters." + + if (unique binds).length != binds.length + runtime-error "'fn*' duplicate symbols in parameters list." + + body = params[1] + + fn_instance = (...values) -> + if not vargs and values.length != binds.length + runtime-error "function expected #{binds.length} parameters, + got #{values.length}" + else if vargs and values.length < binds.length + runtime-error "function expected at least + #{binds.length} parameters, + got #{values.length}" + + # Set binds to values in the new env. + fn_env = new Env env + + for [name, value] in (zip binds, values) + fn_env.set name, value + + if vargs + fn_env.set vargs, do + type: \list + value: values.slice binds.length + + # Defer evaluation of the function body to TCO. + defer-tco fn_env, body + + {type: \function, value: fn_instance} + + +eval_apply = (env, list) -> + [fn, ...args] = list |> map eval_ast env + if fn.type != \function + runtime-error "#{fn.value} is not a function" + + fn.value.apply env, args + + +repl_env = new Env +for symbol, value of ns + repl_env.set symbol, value + +# Evil eval. +repl_env.set 'eval', do + type: \function + value: (ast) -> eval_ast repl_env, ast # or use current env? (@ = this). + + +rep = (line) -> + line + |> read_str + |> eval_ast repl_env + |> (ast) -> pr_str ast, print_readably=true + +# Define load-file. +rep ' +(def! load-file + (fn* (f) + (eval + (read-string + (str "(do " (slurp f) ")")))))' + +# Parse program arguments. +# The first two (exe and core-file) are, respectively, +# the interpreter executable (nodejs or lsc) and the +# source file being executed (stepX_*.(ls|js)). +[exe, core-file, mal-file, ...argv] = process.argv + +repl_env.set '*ARGV*', do + type: \list + value: argv |> map (arg) -> + type: \string + value: arg + +if mal-file + rep "(load-file \"#{mal-file}\")" +else + # REPL. + loop + line = readline.readline 'user> ' + break if not line? or line == '' + try + console.log rep line + catch {message} + console.error message From a650ae5b9c9176ec320e3b3d51c3e2612777329d Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Thu, 11 May 2017 22:46:47 +0200 Subject: [PATCH 0321/2308] Step 7 --- livescript/core.ls | 33 +++-- livescript/step7_quote.ls | 282 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 307 insertions(+), 8 deletions(-) create mode 100644 livescript/step7_quote.ls diff --git a/livescript/core.ls b/livescript/core.ls index d02b1f2bb3..18b9c9e4b5 100644 --- a/livescript/core.ls +++ b/livescript/core.ls @@ -1,4 +1,4 @@ -{zip, map, apply, and-list, join, Obj} = require 'prelude-ls' +{zip, map, apply, and-list, join, Obj, concat, all} = require 'prelude-ls' {pr_str} = require './printer' {read_str} = require './reader' fs = require 'fs' @@ -29,9 +29,14 @@ deep-equals = (a, b) -> else false -check-type = (name, required-type, given-type) -> - if required-type != given-type - runtime-error "'#{name}' is not supported on #{given-type}" +check-param = (name, idx, test, expected, actual) -> + if not test + runtime-error "'#{name}' expected parameter #{idx} + to be #{expected}, got #{actual}" + + +check-type = (name, idx, expected, actual) -> + check-param name, idx, expected == actual, expected, actual export ns = do @@ -101,7 +106,7 @@ export ns = do |> const-nil 'read-string': fn ({type, value}) -> - check-type 'read-string', \string, type + check-type 'read-string', 0, \string, type read_str value 'slurp': fn (filename) -> @@ -114,15 +119,15 @@ export ns = do 'atom': fn (value) -> {type: \atom, value: value} 'atom?': fn (atom) -> const-bool atom.type == \atom 'deref': fn (atom) -> - check-type 'deref', \atom, atom.type + check-type 'deref', 0, \atom, atom.type atom.value 'reset!': fn (atom, value) -> - check-type 'reset!', \atom, atom.type + check-type 'reset!', 0, \atom, atom.type atom.value = value 'swap!': fn (atom, fn, ...args) -> - check-type 'swap!', \atom, atom.type + check-type 'swap!', 0, \atom, atom.type if fn.type != \function runtime-error "'swap!' expected the second parameter to be a function, got a #{fn.type}" @@ -132,3 +137,15 @@ export ns = do atom.value = atom.value.eval! atom.value + + 'cons': fn (value, list) -> + check-param 'cons', 1, (list-or-vector list), + 'list or vector', list.type + + {type: \list, value: [value] ++ list.value} + + 'concat': fn (...params) -> + if not all list-or-vector, params + runtime-error "'concat' expected all parameters to be a list or vector" + + {type: \list, value: params |> map (.value) |> concat} diff --git a/livescript/step7_quote.ls b/livescript/step7_quote.ls new file mode 100644 index 0000000000..16e1283614 --- /dev/null +++ b/livescript/step7_quote.ls @@ -0,0 +1,282 @@ +readline = require './node_readline' +{id, map, each, last, all, unique, zip, Obj, elem-index} = require 'prelude-ls' +{read_str} = require './reader' +{pr_str} = require './printer' +{Env} = require './env' +{runtime-error, ns} = require './core' +{list-to-pairs} = require './utils' + + +defer-tco = (env, ast) -> + type: \tco + env: env + ast: ast + eval: -> eval_ast env, ast + + +is-thruthy = ({type, value}) -> + type != \const or value not in [\nil \false] + + +fmap-ast = (fn, {type, value}: ast) --> + {type: type, value: fn value} + + +make-symbol = (name) -> {type: \symbol, value: name} +make-list = (value) -> {type: \list, value: value} +make-call = (name, params) -> make-list [make-symbol name] ++ params +is-symbol = (ast, name) -> ast.type == \symbol and ast.value == name + + +eval_simple = (env, {type, value}: ast) -> + switch type + | \symbol => env.get value + | \list, \vector => ast |> fmap-ast map eval_ast env + | \map => ast |> fmap-ast Obj.map eval_ast env + | otherwise => ast + + +eval_ast = (env, {type, value}: ast) --> + loop + if type != \list + return eval_simple env, ast + else if value.length == 0 + return ast + + result = if value[0].type == \symbol + params = value[1 to] + switch value[0].value + | 'def!' => eval_def env, params + | 'let*' => eval_let env, params + | 'do' => eval_do env, params + | 'if' => eval_if env, params + | 'fn*' => eval_fn env, params + | 'quote' => eval_quote env, params + | 'quasiquote' => eval_quasiquote env, params + | otherwise => eval_apply env, value + else + eval_apply env, value + + if result.type == \tco + env = result.env + {type, value}: ast = result.ast + else + return result + + +check_params = (name, params, expected) -> + if params.length != expected + runtime-error "'#{name}' expected #{expected} parameters, + got #{params.length}" + + +eval_def = (env, params) -> + check_params 'def!', params, 2 + + # Name is in the first parameter, and is not evaluated. + name = params[0] + if name.type != \symbol + runtime-error "expected a symbol for the first parameter + of def!, got a #{name.type}" + + # Evaluate the second parameter and store + # it under name in the env. + env.set name.value, (eval_ast env, params[1]) + + +eval_let = (env, params) -> + check_params 'let*', params, 2 + + binding_list = params[0] + if binding_list.type not in [\list \vector] + runtime-error "expected 1st parameter of 'let*' to + be a binding list (or vector), + got a #{binding_list.type}" + else if binding_list.value.length % 2 != 0 + runtime-error "binding list of 'let*' must have an even + number of parameters" + + # Make a new environment with the + # current environment as outer. + let_env = new Env env + + # Evaluate all binding values in the + # new environment. + binding_list.value + |> list-to-pairs + |> each ([binding_name, binding_value]) -> + if binding_name.type != \symbol + runtime-error "expected a symbol as binding name, + got a #{binding_name.type}" + + let_env.set binding_name.value, (eval_ast let_env, binding_value) + + # Defer evaluation of let* body with TCO. + defer-tco let_env, params[1] + + +eval_do = (env, params) -> + if params.length == 0 + runtime-error "'do' expected at least one parameter" + + [...rest, last-param] = params + rest |> each eval_ast env + defer-tco env, last-param + + +eval_if = (env, params) -> + if params.length < 2 + runtime-error "'if' expected at least 2 parameters" + else if params.length > 3 + runtime-error "'if' expected at most 3 parameters" + + cond = eval_ast env, params[0] + if is-thruthy cond + defer-tco env, params[1] + else if params.length > 2 + defer-tco env, params[2] + else + {type: \const, value: \nil} + + +eval_fn = (env, params) -> + check_params 'fn*', params, 2 + + if params[0].type not in [\list \vector] + runtime-error "'fn*' expected first parameter to be a list or vector." + + if not all (.type == \symbol), params[0].value + runtime-error "'fn*' expected only symbols in the parameters list." + + binds = params[0].value |> map (.value) + vargs = null + + # Parse variadic bind. + if binds.length >= 2 + [...rest, amper, name] = binds + if amper == '&' and name != '&' + binds = rest + vargs = name + + if elem-index '&', binds + runtime-error "'fn*' invalid usage of variadic parameters." + + if (unique binds).length != binds.length + runtime-error "'fn*' duplicate symbols in parameters list." + + body = params[1] + + fn_instance = (...values) -> + if not vargs and values.length != binds.length + runtime-error "function expected #{binds.length} parameters, + got #{values.length}" + else if vargs and values.length < binds.length + runtime-error "function expected at least + #{binds.length} parameters, + got #{values.length}" + + # Set binds to values in the new env. + fn_env = new Env env + + for [name, value] in (zip binds, values) + fn_env.set name, value + + if vargs + fn_env.set vargs, + make-list values.slice binds.length + + # Defer evaluation of the function body to TCO. + defer-tco fn_env, body + + {type: \function, value: fn_instance} + + +eval_apply = (env, list) -> + [fn, ...args] = list |> map eval_ast env + if fn.type != \function + runtime-error "#{fn.value} is not a function" + + fn.value.apply env, args + + +eval_quote = (env, params) -> + if params.length != 1 + runtime-error "quote expected 1 parameter, got #{params.length}" + + params[0] + + +is-pair = (ast) -> ast.type in [\list \vector] and ast.value.length != 0 + + +eval_quasiquote = (env, params) -> + # if params.length != 1 + # runtime-error "quasiquote expected 1 parameter, got #{params.length}" + + ast = params[0] + new-ast = if not is-pair ast + make-call 'quote', [ast] + else if is-symbol ast.value[0], 'unquote' + ast.value[1] + else if is-pair ast.value[0] and \ + is-symbol ast.value[0].value[0], 'splice-unquote' + make-call 'concat', [ + ast.value[0].value[1] + make-call 'quasiquote', [make-list ast.value[1 to]] + ] + else + make-call 'cons', [ + make-call 'quasiquote', [ast.value[0]] + make-call 'quasiquote', [make-list ast.value[1 to]] + ] + + defer-tco env, new-ast + + +repl_env = new Env +for symbol, value of ns + repl_env.set symbol, value + +# Evil eval. +repl_env.set 'eval', do + type: \function + value: (ast) -> eval_ast repl_env, ast # or use current env? (@ = this). + + +rep = (line) -> + line + |> read_str + |> eval_ast repl_env + |> (ast) -> pr_str ast, print_readably=true + +# Define load-file. +rep ' +(def! load-file + (fn* (f) + (eval + (read-string + (str "(do " (slurp f) ")")))))' + +# Parse program arguments. +# The first two (exe and core-file) are, respectively, +# the interpreter executable (nodejs or lsc) and the +# source file being executed (stepX_*.(ls|js)). +[exe, core-file, mal-file, ...argv] = process.argv + +repl_env.set '*ARGV*', do + type: \list + value: argv |> map (arg) -> + type: \string + value: arg + +if mal-file + rep "(load-file \"#{mal-file}\")" +else + # REPL. + loop + line = readline.readline 'user> ' + break if not line? or line == '' + try + console.log rep line + catch {message} + console.error message From 86e32f4d5adc214b4d8c1e46779e4b25df09358d Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Fri, 12 May 2017 22:04:31 +0200 Subject: [PATCH 0322/2308] Step 8 --- livescript/core.ls | 11 +- livescript/env.ls | 7 +- livescript/step8_macros.ls | 334 +++++++++++++++++++++++++++++++++++++ 3 files changed, 346 insertions(+), 6 deletions(-) create mode 100644 livescript/step8_macros.ls diff --git a/livescript/core.ls b/livescript/core.ls index 18b9c9e4b5..a803e1dbc5 100644 --- a/livescript/core.ls +++ b/livescript/core.ls @@ -6,6 +6,11 @@ fs = require 'fs' export runtime-error = (msg) -> throw new Error msg +export unpack-tco = (ast) -> + if ast.type == \tco + then ast.eval! + else ast + fn = (body) -> {type: \function, value: body} const-nil = -> {type: \const, value: \nil} @@ -132,11 +137,7 @@ export ns = do runtime-error "'swap!' expected the second parameter to be a function, got a #{fn.type}" - atom.value = fn.value.apply @, [atom.value] ++ args - if atom.value.type == \tco # TODO make this a method. - atom.value = atom.value.eval! - - atom.value + atom.value = unpack-tco (fn.value.apply @, [atom.value] ++ args) 'cons': fn (value, list) -> check-param 'cons', 1, (list-or-vector list), diff --git a/livescript/env.ls b/livescript/env.ls index 46adbe2ab7..1abdfa1af3 100644 --- a/livescript/env.ls +++ b/livescript/env.ls @@ -11,6 +11,11 @@ export class Env else if @outer? then @outer.find symbol get: (symbol) -> + result = @try-get symbol + if not result + then throw new Error "symbol not found: #{symbol}" + else result + + try-get: (symbol) -> env = @find symbol if env then env.data[symbol] - else throw new Error "symbol not found: #{symbol}" diff --git a/livescript/step8_macros.ls b/livescript/step8_macros.ls new file mode 100644 index 0000000000..d0cb83d988 --- /dev/null +++ b/livescript/step8_macros.ls @@ -0,0 +1,334 @@ +readline = require './node_readline' +{id, map, each, last, all, unique, zip, Obj, elem-index} = require 'prelude-ls' +{read_str} = require './reader' +{pr_str} = require './printer' +{Env} = require './env' +{runtime-error, ns, unpack-tco} = require './core' +{list-to-pairs} = require './utils' + + +defer-tco = (env, ast) -> + type: \tco + env: env + ast: ast + eval: -> eval_ast env, ast + + +is-thruthy = ({type, value}) -> + type != \const or value not in [\nil \false] + + +fmap-ast = (fn, {type, value}: ast) --> + {type: type, value: fn value} + + +make-symbol = (name) -> {type: \symbol, value: name} +make-list = (value) -> {type: \list, value: value} +make-call = (name, params) -> make-list [make-symbol name] ++ params +is-symbol = (ast, name) -> ast.type == \symbol and ast.value == name + + +eval_simple = (env, {type, value}: ast) -> + switch type + | \symbol => env.get value + | \list, \vector => ast |> fmap-ast map eval_ast env + | \map => ast |> fmap-ast Obj.map eval_ast env + | otherwise => ast + + +eval_ast = (env, ast) --> + loop + if ast.type != \list + return eval_simple env, ast + + ast = macroexpand env, ast + if ast.type != \list + return eval_simple env, ast + else if ast.value.length == 0 + return ast + + result = if ast.value[0].type == \symbol + params = ast.value[1 to] + switch ast.value[0].value + | 'def!' => eval_def env, params + | 'let*' => eval_let env, params + | 'do' => eval_do env, params + | 'if' => eval_if env, params + | 'fn*' => eval_fn env, params + | 'quote' => eval_quote env, params + | 'quasiquote' => eval_quasiquote env, params + | 'defmacro!' => eval_defmacro env, params + | 'macroexpand' => eval_macroexpand env, params + | otherwise => eval_apply env, ast.value + else + eval_apply env, ast.value + + if result.type == \tco + {env, ast} = result + else + return result + + +check_params = (name, params, expected) -> + if params.length != expected + runtime-error "'#{name}' expected #{expected} parameters, + got #{params.length}" + + +eval_def = (env, params) -> + check_params 'def!', params, 2 + + # Name is in the first parameter, and is not evaluated. + name = params[0] + if name.type != \symbol + runtime-error "expected a symbol for the first parameter + of def!, got a #{name.type}" + + # Evaluate the second parameter and store + # it under name in the env. + env.set name.value, (eval_ast env, params[1]) + + +eval_let = (env, params) -> + check_params 'let*', params, 2 + + binding_list = params[0] + if binding_list.type not in [\list \vector] + runtime-error "expected 1st parameter of 'let*' to + be a binding list (or vector), + got a #{binding_list.type}" + else if binding_list.value.length % 2 != 0 + runtime-error "binding list of 'let*' must have an even + number of parameters" + + # Make a new environment with the + # current environment as outer. + let_env = new Env env + + # Evaluate all binding values in the + # new environment. + binding_list.value + |> list-to-pairs + |> each ([binding_name, binding_value]) -> + if binding_name.type != \symbol + runtime-error "expected a symbol as binding name, + got a #{binding_name.type}" + + let_env.set binding_name.value, (eval_ast let_env, binding_value) + + # Defer evaluation of let* body with TCO. + defer-tco let_env, params[1] + + +eval_do = (env, params) -> + if params.length == 0 + runtime-error "'do' expected at least one parameter" + + [...rest, last-param] = params + rest |> each eval_ast env + defer-tco env, last-param + + +eval_if = (env, params) -> + if params.length < 2 + runtime-error "'if' expected at least 2 parameters" + else if params.length > 3 + runtime-error "'if' expected at most 3 parameters" + + cond = eval_ast env, params[0] + if is-thruthy cond + defer-tco env, params[1] + else if params.length > 2 + defer-tco env, params[2] + else + {type: \const, value: \nil} + + +eval_fn = (env, params) -> + check_params 'fn*', params, 2 + + if params[0].type not in [\list \vector] + runtime-error "'fn*' expected first parameter to be a list or vector." + + if not all (.type == \symbol), params[0].value + runtime-error "'fn*' expected only symbols in the parameters list." + + binds = params[0].value |> map (.value) + vargs = null + + # Parse variadic bind. + if binds.length >= 2 + [...rest, amper, name] = binds + if amper == '&' and name != '&' + binds = rest + vargs = name + + if elem-index '&', binds + runtime-error "'fn*' invalid usage of variadic parameters." + + if (unique binds).length != binds.length + runtime-error "'fn*' duplicate symbols in parameters list." + + body = params[1] + + fn_instance = (...values) -> + if not vargs and values.length != binds.length + runtime-error "function expected #{binds.length} parameters, + got #{values.length}" + else if vargs and values.length < binds.length + runtime-error "function expected at least + #{binds.length} parameters, + got #{values.length}" + + # Set binds to values in the new env. + fn_env = new Env env + + for [name, value] in (zip binds, values) + fn_env.set name, value + + if vargs + fn_env.set vargs, + make-list values.slice binds.length + + # Defer evaluation of the function body to TCO. + defer-tco fn_env, body + + {type: \function, value: fn_instance, is_macro: false} + + +eval_apply = (env, list) -> + [fn, ...args] = list |> map eval_ast env + if fn.type != \function + runtime-error "#{fn.value} is not a function" + + fn.value.apply env, args + + +eval_quote = (env, params) -> + if params.length != 1 + runtime-error "quote expected 1 parameter, got #{params.length}" + + params[0] + + +is-pair = (ast) -> ast.type in [\list \vector] and ast.value.length != 0 + + +eval_quasiquote = (env, params) -> + if params.length != 1 + runtime-error "quasiquote expected 1 parameter, got #{params.length}" + + ast = params[0] + new-ast = if not is-pair ast + make-call 'quote', [ast] + else if is-symbol ast.value[0], 'unquote' + ast.value[1] + else if is-pair ast.value[0] and \ + is-symbol ast.value[0].value[0], 'splice-unquote' + make-call 'concat', [ + ast.value[0].value[1] + make-call 'quasiquote', [make-list ast.value[1 to]] + ] + else + make-call 'cons', [ + make-call 'quasiquote', [ast.value[0]] + make-call 'quasiquote', [make-list ast.value[1 to]] + ] + + defer-tco env, new-ast + + +eval_defmacro = (env, params) -> + check_params 'def!', params, 2 + + # Name is in the first parameter, and is not evaluated. + name = params[0] + if name.type != \symbol + runtime-error "expected a symbol for the first parameter + of defmacro!, got a #{name.type}" + + # Evaluate the second parameter. + fn = eval_ast env, params[1] + if fn.type != \function + runtime-error "expected a function for the second parameter + of defmacro!, got a #{fn.type}" + + # Copy fn and mark the function as a macro. + macro_fn = fn with is_macro: true + env.set name.value, macro_fn + + +get-macro-fn = (env, ast) -> + if ast.type == \list and + ast.value.length != 0 and + ast.value[0].type == \symbol + fn = env.try-get ast.value[0].value + if fn and fn.type == \function and fn.is_macro + then fn + + +macroexpand = (env, ast) -> + loop # until ast is not a macro function call. + macro_fn = get-macro-fn env, ast + if not macro_fn then return ast + ast = unpack-tco <| macro_fn.value.apply env, ast.value[1 to] + + +eval_macroexpand = (env, params) -> + if params.length != 1 + runtime-error "'macroexpand' expected 1 parameter, + got #{params.length}" + + macroexpand env, params[0] + + +repl_env = new Env +for symbol, value of ns + repl_env.set symbol, value + +# Evil eval. +repl_env.set 'eval', do + type: \function + value: (ast) -> eval_ast repl_env, ast # or use current env? (@ = this). + +# Read, Evaluate, Print +rep = (line) -> + line + |> read_str + |> eval_ast repl_env + |> (ast) -> pr_str ast, print_readably=true + + +# Define load-file. +rep ' +(def! load-file + (fn* (f) + (eval + (read-string + (str "(do " (slurp f) ")")))))' + + +# Parse program arguments. +# The first two (exe and core-file) are, respectively, +# the interpreter executable (nodejs or lsc) and the +# source file being executed (stepX_*.(ls|js)). +[exe, core-file, mal-file, ...argv] = process.argv + +repl_env.set '*ARGV*', do + type: \list + value: argv |> map (arg) -> + type: \string + value: arg + + +if mal-file + rep "(load-file \"#{mal-file}\")" +else + # REPL. + loop + line = readline.readline 'user> ' + break if not line? or line == '' + try + console.log rep line + catch {message} + console.error message From 19677091f17c0d0cab1e681086979f6b0339ea21 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Fri, 12 May 2017 22:15:14 +0200 Subject: [PATCH 0323/2308] Added first, rest and nth --- livescript/core.ls | 25 +++++++++++++++++++++++++ livescript/step7_quote.ls | 4 ++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/livescript/core.ls b/livescript/core.ls index a803e1dbc5..0c55bd8c70 100644 --- a/livescript/core.ls +++ b/livescript/core.ls @@ -150,3 +150,28 @@ export ns = do runtime-error "'concat' expected all parameters to be a list or vector" {type: \list, value: params |> map (.value) |> concat} + + 'nth': fn (list, index) -> + check-param 'nth', 0, (list-or-vector list), + 'list or vector', list.type + check-param 'nth', 1, index.type == \int, + 'int', index.type + + if index.value < 0 or index.value >= list.value.length + runtime-error 'list index out of bounds' + + list.value[index.value] + + 'first': fn (list) -> + check-param 'first', 0, (list-or-vector list), + 'list or vector', list.type + + if list.value.length == 0 + then const-nil! + else list.value[0] + + 'rest': fn (list) -> + check-param 'rest', 0, (list-or-vector list), + 'list or vector', list.type + + {type: \list, value: list.value.slice 1} diff --git a/livescript/step7_quote.ls b/livescript/step7_quote.ls index 16e1283614..0deea8647f 100644 --- a/livescript/step7_quote.ls +++ b/livescript/step7_quote.ls @@ -210,8 +210,8 @@ is-pair = (ast) -> ast.type in [\list \vector] and ast.value.length != 0 eval_quasiquote = (env, params) -> - # if params.length != 1 - # runtime-error "quasiquote expected 1 parameter, got #{params.length}" + if params.length != 1 + runtime-error "quasiquote expected 1 parameter, got #{params.length}" ast = params[0] new-ast = if not is-pair ast From b145558e39e6d88b22442d94c3b7692452cdeaf0 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sat, 13 May 2017 21:13:10 +0200 Subject: [PATCH 0324/2308] Step 8 complete --- livescript/core.ls | 6 ++++++ livescript/step8_macros.ls | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/livescript/core.ls b/livescript/core.ls index 0c55bd8c70..a070658c3e 100644 --- a/livescript/core.ls +++ b/livescript/core.ls @@ -163,6 +163,9 @@ export ns = do list.value[index.value] 'first': fn (list) -> + if list.type == \const and list.value == \nil + return const-nil! + check-param 'first', 0, (list-or-vector list), 'list or vector', list.type @@ -171,6 +174,9 @@ export ns = do else list.value[0] 'rest': fn (list) -> + if list.type == \const and list.value == \nil + return {type: \list, value: []} + check-param 'rest', 0, (list-or-vector list), 'list or vector', list.type diff --git a/livescript/step8_macros.ls b/livescript/step8_macros.ls index d0cb83d988..d256ca9a47 100644 --- a/livescript/step8_macros.ls +++ b/livescript/step8_macros.ls @@ -307,6 +307,27 @@ rep ' (read-string (str "(do " (slurp f) ")")))))' +# Define cond. +rep ' +(defmacro! cond + (fn* (& xs) + (if (> (count xs) 0) + (list \'if (first xs) + (if (> (count xs) 1) + (nth xs 1) + (throw "odd number of forms to cond")) + (cons \'cond (rest (rest xs)))))))' + +# Define or. +rep ' +(defmacro! or + (fn* (& xs) + (if (empty? xs) + nil + (if (= 1 (count xs)) + (first xs) + `(let* (or_FIXME ~(first xs)) + (if or_FIXME or_FIXME (or ~@(rest xs))))))))' # Parse program arguments. # The first two (exe and core-file) are, respectively, From 65164fe20de03f6dae8cae9be0d229713c1de0da Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sat, 13 May 2017 22:05:34 +0200 Subject: [PATCH 0325/2308] More of step 8 --- livescript/core.ls | 35 ++++ livescript/env.ls | 2 +- livescript/step9_try.ls | 384 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 livescript/step9_try.ls diff --git a/livescript/core.ls b/livescript/core.ls index a070658c3e..9407d0569d 100644 --- a/livescript/core.ls +++ b/livescript/core.ls @@ -181,3 +181,38 @@ export ns = do 'list or vector', list.type {type: \list, value: list.value.slice 1} + + 'throw': fn (value) -> throw value + + 'apply': fn (fn, ...params, list) -> + check-type 'apply', 0, \function, fn.type + if not list then runtime-error "apply expected at least two parameters" + check-param 'apply', params.length+1, (list-or-vector list), + 'list or vector', list.type + + unpack-tco fn.value.apply @, params ++ list.value + + 'map': fn (fn, list) -> + check-type 'map', 0, \function, fn.type + check-param 'map', 1, (list-or-vector list), + 'list or vector', list.type + + mapped-list = list.value |> map (value) -> + unpack-tco fn.value.apply @, [value] + + {type: \list, value: mapped-list} + + 'nil?': fn (ast) -> const-bool (ast.type == \const and ast.value == \nil) + 'true?': fn (ast) -> const-bool (ast.type == \const and ast.value == \true) + 'false?': fn (ast) -> const-bool (ast.type == \const and ast.value == \false) + 'symbol?': fn (ast) -> const-bool ast.type == \symbol + + 'symbol': fn (str) -> + check-type 'symbol', 0, \string, str.type + {type: \symbol, value: str.value} + + 'keyword': fn (str) -> + check-type 'keyword', 0, \string, str.type + {type: \keyword, value: str.value} + + 'keyword?': fn (ast) -> const-bool ast.type == \keyword diff --git a/livescript/env.ls b/livescript/env.ls index 1abdfa1af3..594048012d 100644 --- a/livescript/env.ls +++ b/livescript/env.ls @@ -13,7 +13,7 @@ export class Env get: (symbol) -> result = @try-get symbol if not result - then throw new Error "symbol not found: #{symbol}" + then throw new Error "'#{symbol}' not found" else result try-get: (symbol) -> diff --git a/livescript/step9_try.ls b/livescript/step9_try.ls new file mode 100644 index 0000000000..1b6fd3b2af --- /dev/null +++ b/livescript/step9_try.ls @@ -0,0 +1,384 @@ +readline = require './node_readline' +{id, map, each, last, all, unique, zip, Obj, elem-index} = require 'prelude-ls' +{read_str} = require './reader' +{pr_str} = require './printer' +{Env} = require './env' +{runtime-error, ns, unpack-tco} = require './core' +{list-to-pairs} = require './utils' + + +defer-tco = (env, ast) -> + type: \tco + env: env + ast: ast + eval: -> eval_ast env, ast + + +is-thruthy = ({type, value}) -> + type != \const or value not in [\nil \false] + + +fmap-ast = (fn, {type, value}: ast) --> + {type: type, value: fn value} + + +make-symbol = (name) -> {type: \symbol, value: name} +make-list = (value) -> {type: \list, value: value} +make-call = (name, params) -> make-list [make-symbol name] ++ params +is-symbol = (ast, name) -> ast.type == \symbol and ast.value == name + + +eval_simple = (env, {type, value}: ast) -> + switch type + | \symbol => env.get value + | \list, \vector => ast |> fmap-ast map eval_ast env + | \map => ast |> fmap-ast Obj.map eval_ast env + | otherwise => ast + + +eval_ast = (env, ast) --> + loop + if ast.type != \list + return eval_simple env, ast + + ast = macroexpand env, ast + if ast.type != \list + return eval_simple env, ast + else if ast.value.length == 0 + return ast + + result = if ast.value[0].type == \symbol + params = ast.value[1 to] + switch ast.value[0].value + | 'def!' => eval_def env, params + | 'let*' => eval_let env, params + | 'do' => eval_do env, params + | 'if' => eval_if env, params + | 'fn*' => eval_fn env, params + | 'quote' => eval_quote env, params + | 'quasiquote' => eval_quasiquote env, params + | 'defmacro!' => eval_defmacro env, params + | 'macroexpand' => eval_macroexpand env, params + | 'try*' => eval_try env, params + | otherwise => eval_apply env, ast.value + else + eval_apply env, ast.value + + if result.type == \tco + {env, ast} = result + else + return result + + +check_params = (name, params, expected) -> + if params.length != expected + runtime-error "'#{name}' expected #{expected} parameters, + got #{params.length}" + + +eval_def = (env, params) -> + check_params 'def!', params, 2 + + # Name is in the first parameter, and is not evaluated. + name = params[0] + if name.type != \symbol + runtime-error "expected a symbol for the first parameter + of def!, got a #{name.type}" + + # Evaluate the second parameter and store + # it under name in the env. + env.set name.value, (eval_ast env, params[1]) + + +eval_let = (env, params) -> + check_params 'let*', params, 2 + + binding_list = params[0] + if binding_list.type not in [\list \vector] + runtime-error "expected 1st parameter of 'let*' to + be a binding list (or vector), + got a #{binding_list.type}" + else if binding_list.value.length % 2 != 0 + runtime-error "binding list of 'let*' must have an even + number of parameters" + + # Make a new environment with the + # current environment as outer. + let_env = new Env env + + # Evaluate all binding values in the + # new environment. + binding_list.value + |> list-to-pairs + |> each ([binding_name, binding_value]) -> + if binding_name.type != \symbol + runtime-error "expected a symbol as binding name, + got a #{binding_name.type}" + + let_env.set binding_name.value, (eval_ast let_env, binding_value) + + # Defer evaluation of let* body with TCO. + defer-tco let_env, params[1] + + +eval_do = (env, params) -> + if params.length == 0 + runtime-error "'do' expected at least one parameter" + + [...rest, last-param] = params + rest |> each eval_ast env + defer-tco env, last-param + + +eval_if = (env, params) -> + if params.length < 2 + runtime-error "'if' expected at least 2 parameters" + else if params.length > 3 + runtime-error "'if' expected at most 3 parameters" + + cond = eval_ast env, params[0] + if is-thruthy cond + defer-tco env, params[1] + else if params.length > 2 + defer-tco env, params[2] + else + {type: \const, value: \nil} + + +eval_fn = (env, params) -> + check_params 'fn*', params, 2 + + if params[0].type not in [\list \vector] + runtime-error "'fn*' expected first parameter to be a list or vector." + + if not all (.type == \symbol), params[0].value + runtime-error "'fn*' expected only symbols in the parameters list." + + binds = params[0].value |> map (.value) + vargs = null + + # Parse variadic bind. + if binds.length >= 2 + [...rest, amper, name] = binds + if amper == '&' and name != '&' + binds = rest + vargs = name + + if elem-index '&', binds + runtime-error "'fn*' invalid usage of variadic parameters." + + if (unique binds).length != binds.length + runtime-error "'fn*' duplicate symbols in parameters list." + + body = params[1] + + fn_instance = (...values) -> + if not vargs and values.length != binds.length + runtime-error "function expected #{binds.length} parameters, + got #{values.length}" + else if vargs and values.length < binds.length + runtime-error "function expected at least + #{binds.length} parameters, + got #{values.length}" + + # Set binds to values in the new env. + fn_env = new Env env + + for [name, value] in (zip binds, values) + fn_env.set name, value + + if vargs + fn_env.set vargs, + make-list values.slice binds.length + + # Defer evaluation of the function body to TCO. + defer-tco fn_env, body + + {type: \function, value: fn_instance, is_macro: false} + + +eval_apply = (env, list) -> + [fn, ...args] = list |> map eval_ast env + if fn.type != \function + runtime-error "#{fn.value} is not a function" + + fn.value.apply env, args + + +eval_quote = (env, params) -> + if params.length != 1 + runtime-error "quote expected 1 parameter, got #{params.length}" + + params[0] + + +is-pair = (ast) -> ast.type in [\list \vector] and ast.value.length != 0 + + +eval_quasiquote = (env, params) -> + if params.length != 1 + runtime-error "quasiquote expected 1 parameter, got #{params.length}" + + ast = params[0] + new-ast = if not is-pair ast + make-call 'quote', [ast] + else if is-symbol ast.value[0], 'unquote' + ast.value[1] + else if is-pair ast.value[0] and \ + is-symbol ast.value[0].value[0], 'splice-unquote' + make-call 'concat', [ + ast.value[0].value[1] + make-call 'quasiquote', [make-list ast.value[1 to]] + ] + else + make-call 'cons', [ + make-call 'quasiquote', [ast.value[0]] + make-call 'quasiquote', [make-list ast.value[1 to]] + ] + + defer-tco env, new-ast + + +eval_defmacro = (env, params) -> + check_params 'def!', params, 2 + + # Name is in the first parameter, and is not evaluated. + name = params[0] + if name.type != \symbol + runtime-error "expected a symbol for the first parameter + of defmacro!, got a #{name.type}" + + # Evaluate the second parameter. + fn = eval_ast env, params[1] + if fn.type != \function + runtime-error "expected a function for the second parameter + of defmacro!, got a #{fn.type}" + + # Copy fn and mark the function as a macro. + macro_fn = fn with is_macro: true + env.set name.value, macro_fn + + +get-macro-fn = (env, ast) -> + if ast.type == \list and + ast.value.length != 0 and + ast.value[0].type == \symbol + fn = env.try-get ast.value[0].value + if fn and fn.type == \function and fn.is_macro + then fn + + +macroexpand = (env, ast) -> + loop # until ast is not a macro function call. + macro_fn = get-macro-fn env, ast + if not macro_fn then return ast + ast = unpack-tco <| macro_fn.value.apply env, ast.value[1 to] + + +eval_macroexpand = (env, params) -> + if params.length != 1 + runtime-error "'macroexpand' expected 1 parameter, + got #{params.length}" + + macroexpand env, params[0] + + +eval_try = (env, params) -> + if params.length != 2 + runtime-error "'try*' expected 2 parameters, + got #{params.length}" + + try-form = params[0] + catch-clause = params[1] + if catch-clause.type != \list or + catch-clause.value.length != 3 or + not (is-symbol catch-clause.value[0], 'catch*') or + catch-clause.value[1].type != \symbol + runtime-error "'try*' expected the second parameter to be + of the form (catch* A B)" + + try + eval_ast env, try-form + catch error + error-symbol = catch-clause.value[1].value + error-value = \ + if error.message + then {type: \string, value: error.message} + else error + + catch-env = new Env env + catch-env.set error-symbol, error-value + eval_ast catch-env, catch-clause.value[2] + + +repl_env = new Env +for symbol, value of ns + repl_env.set symbol, value + +# Evil eval. +repl_env.set 'eval', do + type: \function + value: (ast) -> eval_ast repl_env, ast # or use current env? (@ = this). + +# Read, Evaluate, Print +rep = (line) -> + line + |> read_str + |> eval_ast repl_env + |> (ast) -> pr_str ast, print_readably=true + + +# Define load-file. +rep ' +(def! load-file + (fn* (f) + (eval + (read-string + (str "(do " (slurp f) ")")))))' + +# Define cond. +rep ' +(defmacro! cond + (fn* (& xs) + (if (> (count xs) 0) + (list \'if (first xs) + (if (> (count xs) 1) + (nth xs 1) + (throw "odd number of forms to cond")) + (cons \'cond (rest (rest xs)))))))' + +# Define or. +rep ' +(defmacro! or + (fn* (& xs) + (if (empty? xs) + nil + (if (= 1 (count xs)) + (first xs) + `(let* (or_FIXME ~(first xs)) + (if or_FIXME or_FIXME (or ~@(rest xs))))))))' + +# Parse program arguments. +# The first two (exe and core-file) are, respectively, +# the interpreter executable (nodejs or lsc) and the +# source file being executed (stepX_*.(ls|js)). +[exe, core-file, mal-file, ...argv] = process.argv + +repl_env.set '*ARGV*', do + type: \list + value: argv |> map (arg) -> + type: \string + value: arg + + +if mal-file + rep "(load-file \"#{mal-file}\")" +else + # REPL. + loop + line = readline.readline 'user> ' + break if not line? or line == '' + try + console.log rep line + catch {message} + console.error message From 894f5ce82c4b6a615ba35b73a5740471b6e397d8 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sun, 14 May 2017 22:05:49 +0200 Subject: [PATCH 0326/2308] Completed step 9 --- livescript/core.ls | 101 +++++++++++++++++++++++++++++++++++++------ livescript/reader.ls | 16 +++---- 2 files changed, 95 insertions(+), 22 deletions(-) diff --git a/livescript/core.ls b/livescript/core.ls index 9407d0569d..2c480bf9f7 100644 --- a/livescript/core.ls +++ b/livescript/core.ls @@ -1,6 +1,10 @@ -{zip, map, apply, and-list, join, Obj, concat, all} = require 'prelude-ls' +{ + zip, map, apply, and-list, join, Obj, concat, all, + pairs-to-obj, obj-to-pairs, reject, keys, values, + difference, empty +} = require 'prelude-ls' {pr_str} = require './printer' -{read_str} = require './reader' +{read_str, list-to-map, map-keyword, keyword-prefix} = require './reader' fs = require 'fs' @@ -20,18 +24,25 @@ const-str = (str) -> {type: \string, value: str} list-or-vector = ({type}) -> type in [\list \vector] +are-lists-equal = (equals-fn, a, b) -> + if a.length != b.length then false + else zip a, b |> map (apply equals-fn) |> and-list + deep-equals = (a, b) -> - if not list-or-vector a then - if a.type != b.type then false - else a.value == b.value - else if list-or-vector b then - if a.value.length != b.value.length then false - else - # Compare all elements of a and b with deep-equals. - zip a.value, b.value - |> map (apply deep-equals) - |> and-list # all must be true (equals) - else false + if (list-or-vector a) and (list-or-vector b) then + are-lists-equal deep-equals, a.value, b.value + else if a.type == \map and b.type == \map then + a-keys = keys a.value + b-keys = keys b.value + if a-keys.length == b-keys.length and \ + empty (difference a-keys, b-keys) + #if are-lists-equal (==), a-keys, b-keys + a-keys |> map (key) -> [a.value[key], b.value[key]] + |> map (apply deep-equals) + |> and-list + else false + else if a.type != b.type then false + else a.value == b.value check-param = (name, idx, test, expected, actual) -> @@ -213,6 +224,68 @@ export ns = do 'keyword': fn (str) -> check-type 'keyword', 0, \string, str.type - {type: \keyword, value: str.value} + {type: \keyword, value: ':' + str.value} 'keyword?': fn (ast) -> const-bool ast.type == \keyword + + 'vector': fn (...params) -> {type: \vector, value: params} + 'vector?': fn (ast) -> const-bool ast.type == \vector + + 'hash-map': fn (...params) -> list-to-map params + + 'map?': fn (ast) -> const-bool ast.type == \map + + 'assoc': fn (m, ...params) -> + check-type 'assoc', 0, \map, m.type + + # Turn the params into a map, this is kind of hacky. + params-map = list-to-map params + + # Copy the map by cloning (prototyping). + new-map = ^^m.value + + for k, v of params-map.value + new-map[k] = v + + {type: \map, value: new-map} + + 'dissoc': fn (m, ...keys) -> + check-type 'dissoc', 0, \map, m.type + + # Convert keyword to map key strings. + str-keys = keys |> map map-keyword + + new-map = m.value + |> obj-to-pairs + |> reject ([key, value]) -> key in str-keys + |> pairs-to-obj + + {type: \map, value: new-map} + + 'get': fn (m, key) -> + if m.type == \const and m.value == \nil + then return const-nil! + + check-type 'get', 0, \map, m.type + str-key = map-keyword key + value = m.value[str-key] + if value then value else const-nil! + + 'contains?': fn (m, key) -> + check-type 'contains?', 0, \map, m.type + str-key = map-keyword key + const-bool (str-key of m.value) + + 'keys': fn (m) -> + check-type 'keys', 0, \map, m.type + result = keys m.value |> map (key) -> + if key.startsWith keyword-prefix + then {type: \keyword, value: key.substring 1} + else {type: \string, value: key} + {type: \list, value: result} + + 'vals': fn (m) -> + check-type 'vals', 0, \map, m.type + {type: \list, value: values m.value} + + 'sequential?': fn (ast) -> const-bool list-or-vector ast diff --git a/livescript/reader.ls b/livescript/reader.ls index 1397283356..96aad575ac 100644 --- a/livescript/reader.ls +++ b/livescript/reader.ls @@ -135,20 +135,20 @@ decode-string = (str) -> export keyword-prefix = '\u029e' +export map-keyword = (key) -> + switch key.type + | \string => key.value + | \keyword => keyword-prefix + key.value + | otherwise => + parse-error "#{key.type} can't be a map key" -list-to-map = (list) -> +export list-to-map = (list) -> if list.length % 2 != 0 parse-error "map should have an even number of elements, got #{list.length}" list-to-pairs list - |> map ([key, value]) -> - switch key.type - | \string => [key.value, value] - | \keyword => [keyword-prefix + key.value, value] - | otherwise => - parse-error "map can only have strings or keywords as keys, - got a #{key.type}" + |> map ([key, value]) -> [(map-keyword key), value] |> pairs-to-obj |> (obj) -> {type: \map, value: obj} From 681ce637b6ce51fb8bc44bac1e21075607997da2 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 23 May 2017 11:04:32 -0500 Subject: [PATCH 0327/2308] Deprecate tests/docker/Dockerfile --- tests/docker/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/docker/Dockerfile b/tests/docker/Dockerfile index 3286498781..0357c61f78 100644 --- a/tests/docker/Dockerfile +++ b/tests/docker/Dockerfile @@ -1,3 +1,6 @@ +# WARNING: This file is deprecated. Each implementation now has its +# own Dockerfile. + FROM ubuntu:utopic MAINTAINER Joel Martin From 4603f2a931e8305afae3428012b522e87b518b63 Mon Sep 17 00:00:00 2001 From: Dennis Felsing Date: Tue, 23 May 2017 14:46:37 +0200 Subject: [PATCH 0328/2308] Update to Nim 0.17.0 --- README.md | 4 ++-- nim/Dockerfile | 6 +++--- tests/docker/Dockerfile | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 07dd123645..0a0ace4564 100644 --- a/README.md +++ b/README.md @@ -566,11 +566,11 @@ cd make make -f stepX_YYY.mk ``` -### Nim 0.15.2 +### Nim 0.17.0 *The Nim implementation was created by [Dennis Felsing (def-)](https://github.com/def-)* -The Nim implementation of mal has been tested with Nim 0.15.2. +The Nim implementation of mal has been tested with Nim 0.17.0. ``` cd nim diff --git a/nim/Dockerfile b/nim/Dockerfile index 20c3d79439..9744753e93 100644 --- a/nim/Dockerfile +++ b/nim/Dockerfile @@ -26,10 +26,10 @@ RUN apt-get -y install g++ # Nim RUN apt-get -y install xz-utils -RUN cd /tmp && curl -O http://nim-lang.org/download/nim-0.16.0.tar.xz \ - && tar xvJf /tmp/nim-0.16.0.tar.xz && cd nim-0.16.0 \ +RUN cd /tmp && curl -O http://nim-lang.org/download/nim-0.17.0.tar.xz \ + && tar xvJf /tmp/nim-0.17.0.tar.xz && cd nim-0.17.0 \ && make && sh install.sh /usr/local/bin \ && cp bin/nim /usr/local/bin/ \ - && rm -r /tmp/nim-0.16.0 + && rm -r /tmp/nim-0.17.0 ENV HOME /mal diff --git a/tests/docker/Dockerfile b/tests/docker/Dockerfile index 3286498781..6172429efe 100644 --- a/tests/docker/Dockerfile +++ b/tests/docker/Dockerfile @@ -116,10 +116,10 @@ RUN luarocks install linenoise RUN npm install -g minimal-lisp # Nim -RUN cd /tmp && wget http://nim-lang.org/download/nim-0.11.0.tar.xz \ - && tar xvJf /tmp/nim-0.11.0.tar.xz && cd nim-0.11.0 \ +RUN cd /tmp && wget http://nim-lang.org/download/nim-0.17.0.tar.xz \ + && tar xvJf /tmp/nim-0.17.0.tar.xz && cd nim-0.17.0 \ && make && sh install.sh /usr/local/bin \ - && rm -r /tmp/nim-0.11.0 + && rm -r /tmp/nim-0.17.0 # OCaml RUN apt-get -y install ocaml-batteries-included From 5182fdad56aba07adc09e4d85fabd1bdc54658ff Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 23 May 2017 23:28:48 +0200 Subject: [PATCH 0329/2308] Apply hack to Emacs 25 instead of Emacs 25.1 --- elisp/reader.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elisp/reader.el b/elisp/reader.el index e63b1aaced..af68fd1fad 100644 --- a/elisp/reader.el +++ b/elisp/reader.el @@ -1,6 +1,6 @@ ;; HACK: `text-quoting-style' prettifies quotes in error messages on -;; Emacs 25.1, but no longer does from 25.2 upwards... -(when (and (= emacs-major-version 25) (= emacs-minor-version 1)) +;; Emacs 25, but no longer does from 26 upwards... +(when (= emacs-major-version 25) (setq text-quoting-style 'grave)) (defvar tokens nil) From 4d8cfe7ec7b9bd228d284168f13d0c2decb18e1c Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Wed, 24 May 2017 14:48:55 +0200 Subject: [PATCH 0330/2308] Self hosting! --- livescript/core.ls | 57 +++++- livescript/stepA_mal.ls | 397 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 451 insertions(+), 3 deletions(-) create mode 100644 livescript/stepA_mal.ls diff --git a/livescript/core.ls b/livescript/core.ls index 2c480bf9f7..c1bc034838 100644 --- a/livescript/core.ls +++ b/livescript/core.ls @@ -1,11 +1,13 @@ + { zip, map, apply, and-list, join, Obj, concat, all, pairs-to-obj, obj-to-pairs, reject, keys, values, - difference, empty + difference, empty, reverse, chars } = require 'prelude-ls' {pr_str} = require './printer' {read_str, list-to-map, map-keyword, keyword-prefix} = require './reader' fs = require 'fs' +{readline} = require './node_readline' export runtime-error = (msg) -> throw new Error msg @@ -15,7 +17,6 @@ export unpack-tco = (ast) -> then ast.eval! else ast - fn = (body) -> {type: \function, value: body} const-nil = -> {type: \const, value: \nil} const-int = (int) -> {type: \int, value: int} @@ -97,7 +98,7 @@ export ns = do '>=': fn (a, b) -> const-bool a.value >= b.value 'not': fn ({type, value}) -> - const-bool (type == \const and value == \false) + const-bool (type == \const and value in [\false \nil]) 'pr-str': fn (...params) -> params |> map (p) -> pr_str p, print_readably=true @@ -289,3 +290,53 @@ export ns = do {type: \list, value: values m.value} 'sequential?': fn (ast) -> const-bool list-or-vector ast + + 'with-meta': fn (ast, m) -> + ast with {meta: m} + + 'meta': fn (ast) -> + if ast.meta + then ast.meta + else const-nil! + + 'readline': fn (prompt) -> + check-type 'readline', 0, \string, prompt.type + result = readline prompt.value + if result? + then const-str result + else const-nil! + + 'time-ms': fn -> + const-int (new Date).getTime! + + 'conj': fn (list, ...params) -> + check-param 'conj', 0, (list-or-vector list), + 'list or vector', list.type + + if list.type == \list + type: \list + value: (reverse params) ++ list.value + else + type: \vector + value: list.value ++ params + + 'string?': fn (ast) -> const-bool ast.type == \string + + 'seq': fn (seq) -> + switch seq.type + | \list => + if seq.value.length + then seq + else const-nil! + | \vector => + if seq.value.length + then {type: \list, value: seq.value} + else const-nil! + | \string => + if seq.value.length + then {type: \list, value: chars seq.value |> map const-str} + else const-nil! + | otherwise => + if seq.type == \const and seq.value == \nil + then const-nil! + else runtime-error "unsupported type for 'seq': #{seq.type}" diff --git a/livescript/stepA_mal.ls b/livescript/stepA_mal.ls new file mode 100644 index 0000000000..eb39d8f9bb --- /dev/null +++ b/livescript/stepA_mal.ls @@ -0,0 +1,397 @@ +readline = require './node_readline' +{id, map, each, last, all, unique, zip, Obj, elem-index} = require 'prelude-ls' +{read_str} = require './reader' +{pr_str} = require './printer' +{Env} = require './env' +{runtime-error, ns, unpack-tco} = require './core' +{list-to-pairs} = require './utils' + + +defer-tco = (env, ast) -> + type: \tco + env: env + ast: ast + eval: -> eval_ast env, ast + + +is-thruthy = ({type, value}) -> + type != \const or value not in [\nil \false] + + +fmap-ast = (fn, {type, value}: ast) --> + {type: type, value: fn value} + + +make-symbol = (name) -> {type: \symbol, value: name} +make-list = (value) -> {type: \list, value: value} +make-call = (name, params) -> make-list [make-symbol name] ++ params +is-symbol = (ast, name) -> ast.type == \symbol and ast.value == name + + +eval_simple = (env, {type, value}: ast) -> + switch type + | \symbol => env.get value + | \list, \vector => ast |> fmap-ast map eval_ast env + | \map => ast |> fmap-ast Obj.map eval_ast env + | otherwise => ast + + +eval_ast = (env, ast) --> + loop + if ast.type != \list + return eval_simple env, ast + + ast = macroexpand env, ast + if ast.type != \list + return eval_simple env, ast + else if ast.value.length == 0 + return ast + + result = if ast.value[0].type == \symbol + params = ast.value[1 to] + switch ast.value[0].value + | 'def!' => eval_def env, params + | 'let*' => eval_let env, params + | 'do' => eval_do env, params + | 'if' => eval_if env, params + | 'fn*' => eval_fn env, params + | 'quote' => eval_quote env, params + | 'quasiquote' => eval_quasiquote env, params + | 'defmacro!' => eval_defmacro env, params + | 'macroexpand' => eval_macroexpand env, params + | 'try*' => eval_try env, params + | otherwise => eval_apply env, ast.value + else + eval_apply env, ast.value + + if result.type == \tco + {env, ast} = result + else + return result + + +check_params = (name, params, expected) -> + if params.length != expected + runtime-error "'#{name}' expected #{expected} parameters, + got #{params.length}" + + +eval_def = (env, params) -> + check_params 'def!', params, 2 + + # Name is in the first parameter, and is not evaluated. + name = params[0] + if name.type != \symbol + runtime-error "expected a symbol for the first parameter + of def!, got a #{name.type}" + + # Evaluate the second parameter and store + # it under name in the env. + env.set name.value, (eval_ast env, params[1]) + + +eval_let = (env, params) -> + check_params 'let*', params, 2 + + binding_list = params[0] + if binding_list.type not in [\list \vector] + runtime-error "expected 1st parameter of 'let*' to + be a binding list (or vector), + got a #{binding_list.type}" + else if binding_list.value.length % 2 != 0 + runtime-error "binding list of 'let*' must have an even + number of parameters" + + # Make a new environment with the + # current environment as outer. + let_env = new Env env + + # Evaluate all binding values in the + # new environment. + binding_list.value + |> list-to-pairs + |> each ([binding_name, binding_value]) -> + if binding_name.type != \symbol + runtime-error "expected a symbol as binding name, + got a #{binding_name.type}" + + let_env.set binding_name.value, (eval_ast let_env, binding_value) + + # Defer evaluation of let* body with TCO. + defer-tco let_env, params[1] + + +eval_do = (env, params) -> + if params.length == 0 + runtime-error "'do' expected at least one parameter" + + [...rest, last-param] = params + rest |> each eval_ast env + defer-tco env, last-param + + +eval_if = (env, params) -> + if params.length < 2 + runtime-error "'if' expected at least 2 parameters" + else if params.length > 3 + runtime-error "'if' expected at most 3 parameters" + + cond = eval_ast env, params[0] + if is-thruthy cond + defer-tco env, params[1] + else if params.length > 2 + defer-tco env, params[2] + else + {type: \const, value: \nil} + + +eval_fn = (env, params) -> + check_params 'fn*', params, 2 + + if params[0].type not in [\list \vector] + runtime-error "'fn*' expected first parameter to be a list or vector." + + if not all (.type == \symbol), params[0].value + runtime-error "'fn*' expected only symbols in the parameters list." + + binds = params[0].value |> map (.value) + vargs = null + + # Parse variadic bind. + if binds.length >= 2 + [...rest, amper, name] = binds + if amper == '&' and name != '&' + binds = rest + vargs = name + + if elem-index '&', binds + runtime-error "'fn*' invalid usage of variadic parameters." + + if (unique binds).length != binds.length + runtime-error "'fn*' duplicate symbols in parameters list." + + body = params[1] + + fn_instance = (...values) -> + if not vargs and values.length != binds.length + runtime-error "function expected #{binds.length} parameters, + got #{values.length}" + else if vargs and values.length < binds.length + runtime-error "function expected at least + #{binds.length} parameters, + got #{values.length}" + + # Set binds to values in the new env. + fn_env = new Env env + + for [name, value] in (zip binds, values) + fn_env.set name, value + + if vargs + fn_env.set vargs, + make-list values.slice binds.length + + # Defer evaluation of the function body to TCO. + defer-tco fn_env, body + + {type: \function, value: fn_instance, is_macro: false} + + +eval_apply = (env, list) -> + [fn, ...args] = list |> map eval_ast env + if fn.type != \function + runtime-error "#{fn.value} is not a function, got a #{fn.type}" + + fn.value.apply env, args + + +eval_quote = (env, params) -> + if params.length != 1 + runtime-error "quote expected 1 parameter, got #{params.length}" + + params[0] + + +is-pair = (ast) -> ast.type in [\list \vector] and ast.value.length != 0 + + +eval_quasiquote = (env, params) -> + if params.length != 1 + runtime-error "quasiquote expected 1 parameter, got #{params.length}" + + ast = params[0] + new-ast = if not is-pair ast + make-call 'quote', [ast] + else if is-symbol ast.value[0], 'unquote' + ast.value[1] + else if is-pair ast.value[0] and \ + is-symbol ast.value[0].value[0], 'splice-unquote' + make-call 'concat', [ + ast.value[0].value[1] + make-call 'quasiquote', [make-list ast.value[1 to]] + ] + else + make-call 'cons', [ + make-call 'quasiquote', [ast.value[0]] + make-call 'quasiquote', [make-list ast.value[1 to]] + ] + + defer-tco env, new-ast + + +eval_defmacro = (env, params) -> + check_params 'def!', params, 2 + + # Name is in the first parameter, and is not evaluated. + name = params[0] + if name.type != \symbol + runtime-error "expected a symbol for the first parameter + of defmacro!, got a #{name.type}" + + # Evaluate the second parameter. + fn = eval_ast env, params[1] + if fn.type != \function + runtime-error "expected a function for the second parameter + of defmacro!, got a #{fn.type}" + + # Copy fn and mark the function as a macro. + macro_fn = fn with is_macro: true + env.set name.value, macro_fn + + +get-macro-fn = (env, ast) -> + if ast.type == \list and + ast.value.length != 0 and + ast.value[0].type == \symbol + fn = env.try-get ast.value[0].value + if fn and fn.type == \function and fn.is_macro + then fn + + +macroexpand = (env, ast) -> + loop # until ast is not a macro function call. + macro_fn = get-macro-fn env, ast + if not macro_fn then return ast + ast = unpack-tco <| macro_fn.value.apply env, ast.value[1 to] + + +eval_macroexpand = (env, params) -> + if params.length != 1 + runtime-error "'macroexpand' expected 1 parameter, + got #{params.length}" + + macroexpand env, params[0] + + +eval_try = (env, params) -> + if params.length != 2 + runtime-error "'try*' expected 2 parameters, + got #{params.length}" + + try-form = params[0] + catch-clause = params[1] + if catch-clause.type != \list or + catch-clause.value.length != 3 or + not (is-symbol catch-clause.value[0], 'catch*') or + catch-clause.value[1].type != \symbol + runtime-error "'try*' expected the second parameter to be + of the form (catch* A B)" + + try + eval_ast env, try-form + catch error + error-symbol = catch-clause.value[1].value + error-value = \ + if error.message + then {type: \string, value: error.message} + else error + + catch-env = new Env env + catch-env.set error-symbol, error-value + eval_ast catch-env, catch-clause.value[2] + + +repl_env = new Env +for symbol, value of ns + repl_env.set symbol, value + +# Evil eval. +repl_env.set 'eval', do + type: \function + value: (ast) -> eval_ast repl_env, ast # or use current env? (@ = this). + +# Read, Evaluate, Print +rep = (line) -> + line + |> read_str + |> eval_ast repl_env + |> (ast) -> pr_str ast, print_readably=true + + +# Define load-file. +rep ' +(def! load-file + (fn* (f) + (eval + (read-string + (str "(do " (slurp f) ")")))))' + +# Define cond. +rep ' +(defmacro! cond + (fn* (& xs) + (if (> (count xs) 0) + (list \'if (first xs) + (if (> (count xs) 1) + (nth xs 1) + (throw "odd number of forms to cond")) + (cons \'cond (rest (rest xs)))))))' + +rep '(def! *gensym-counter* (atom 0))' + +rep ' +(def! gensym + (fn* [] + (symbol + (str "G__" + (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))' + +rep ' +(defmacro! or + (fn* (& xs) + (if (empty? xs) + nil + (if (= 1 (count xs)) + (first xs) + (let* (condvar (gensym)) + `(let* (~condvar ~(first xs)) + (if ~condvar ~condvar (or ~@(rest xs)))))))))' + +# Parse program arguments. +# The first two (exe and core-file) are, respectively, +# the interpreter executable (nodejs or lsc) and the +# source file being executed (stepX_*.(ls|js)). +[exe, core-file, mal-file, ...argv] = process.argv + +repl_env.set '*ARGV*', do + type: \list + value: argv |> map (arg) -> + type: \string + value: arg + +repl_env.set '*host-language*', + {type: \string, value: 'livescript'} + + +if mal-file + rep "(load-file \"#{mal-file}\")" +else + # REPL. + rep '(println (str "Mal [" *host-language* "]"))' + loop + line = readline.readline 'user> ' + break if not line? or line == '' + try + console.log rep line + catch {message} + console.error message From a873632f191afcc606bd445102df4ce118c3d18f Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Wed, 24 May 2017 15:03:48 +0200 Subject: [PATCH 0331/2308] Fixed recursion in Makefile --- livescript/Makefile | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/livescript/Makefile b/livescript/Makefile index 62b91c1bb2..a18354c4e1 100644 --- a/livescript/Makefile +++ b/livescript/Makefile @@ -1,16 +1,18 @@ -TESTS = +SOURCES_BASE = reader.ls printer.ls env.ls core.ls utils.ls +SOURCES_STEPS = step0_repl.ls step1_read_print.ls step2_eval.ls \ + step4_if_fn_do.ls step5_tco.ls step6_file.ls step7_quote.ls \ + step8_macros.ls step9_try.ls stepA_mal.ls +SOURCES = $(SOURCES_BASE) $(SOURCES_STEPS) -SOURCES_BASE = reader.js printer.js env.js core.js utils.js -SOURCES_LISP = -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) +BINS = $(SOURCES:%.ls=%.js) -all: $(SOURCES) node_modules +all: $(BINS) node_modules node_modules: npm install -%.js: %.ls $(SOURCES) +%.js: %.ls lsc -d -c $(@:%.js=%.ls) clean: - rm -f *.js + rm -f $(BINS) From 361dffd818245f32e997fc2eb9f144385a2e68ff Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Wed, 24 May 2017 15:07:09 +0200 Subject: [PATCH 0332/2308] Fixed wrong ignore of livescript/node_readline.js --- .gitignore | 2 ++ livescript/node_readline.js | 47 +++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 livescript/node_readline.js diff --git a/.gitignore b/.gitignore index a7f1a11bfd..354278e904 100644 --- a/.gitignore +++ b/.gitignore @@ -125,3 +125,5 @@ common-lisp/*.lib common-lisp/images/* common-lisp/hist/* livescript/*.js +!livescript/node_readline.js +livescript/node_modules diff --git a/livescript/node_readline.js b/livescript/node_readline.js new file mode 100644 index 0000000000..e59a62bd1e --- /dev/null +++ b/livescript/node_readline.js @@ -0,0 +1,47 @@ +// IMPORTANT: choose one +var RL_LIB = "libreadline"; // NOTE: libreadline is GPL +//var RL_LIB = "libedit"; + +var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history'); + +var rlwrap = {}; // namespace for this module in web context + +var ffi = require('ffi'), + fs = require('fs'); + +var rllib = ffi.Library(RL_LIB, { + 'readline': [ 'string', [ 'string' ] ], + 'add_history': [ 'int', [ 'string' ] ]}); + +var rl_history_loaded = false; + +exports.readline = rlwrap.readline = function(prompt) { + prompt = typeof prompt !== 'undefined' ? prompt : "user> "; + + if (!rl_history_loaded) { + rl_history_loaded = true; + var lines = []; + if (fs.existsSync(HISTORY_FILE)) { + lines = fs.readFileSync(HISTORY_FILE).toString().split("\n"); + } + // Max of 2000 lines + lines = lines.slice(Math.max(lines.length - 2000, 0)); + for (var i=0; i Date: Wed, 24 May 2017 15:10:53 +0200 Subject: [PATCH 0333/2308] Added livescript to dependencies + use local livescript compiler --- livescript/Makefile | 6 ++++-- livescript/package.json | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/livescript/Makefile b/livescript/Makefile index a18354c4e1..080d572732 100644 --- a/livescript/Makefile +++ b/livescript/Makefile @@ -6,13 +6,15 @@ SOURCES = $(SOURCES_BASE) $(SOURCES_STEPS) BINS = $(SOURCES:%.ls=%.js) -all: $(BINS) node_modules +LSC = node_modules/.bin/lsc + +all: node_modules $(BINS) node_modules: npm install %.js: %.ls - lsc -d -c $(@:%.js=%.ls) + $(LSC) -d -c $(@:%.js=%.ls) clean: rm -f $(BINS) diff --git a/livescript/package.json b/livescript/package.json index 8cb53e1afa..af94704c1e 100644 --- a/livescript/package.json +++ b/livescript/package.json @@ -4,10 +4,12 @@ "description": "", "main": "index.js", "dependencies": { - "prelude-ls": "^1.1.2", - "ffi": "2.0.x" + "ffi": "2.0.x", + "prelude-ls": "^1.1.2" + }, + "devDependencies": { + "livescript": "^1.5.0" }, - "devDependencies": {}, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, From 0af8fd20ce0342ea5ae45b9082c3c5bd520ddd30 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Wed, 24 May 2017 19:12:00 +0200 Subject: [PATCH 0334/2308] Fixed Makefile, added Dockerfile and updated README. --- .travis.yml | 1 + README.md | 15 ++++++++++++++- livescript/Dockerfile | 31 +++++++++++++++++++++++++++++++ livescript/Makefile | 22 +++++++++++++++++++++- 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 livescript/Dockerfile diff --git a/.travis.yml b/.travis.yml index 010ac7cea7..711212ab7d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,6 +36,7 @@ matrix: - {env: IMPL=js, services: [docker]} - {env: IMPL=julia, services: [docker]} - {env: IMPL=kotlin, services: [docker]} + - {env: IMPL=livescript, services: [docker]} - {env: IMPL=logo, services: [docker]} - {env: IMPL=lua, services: [docker]} - {env: IMPL=make, services: [docker]} diff --git a/README.md b/README.md index 07dd123645..97aa11167a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 64 languages: +Mal is implemented in 65 languages: * Ada * GNU awk @@ -39,6 +39,7 @@ Mal is implemented in 64 languages: * JavaScript ([Online Demo](http://kanaka.github.io/mal)) * Julia * Kotlin +* LiveScript * Logo * Lua * GNU Make @@ -525,6 +526,18 @@ make java -jar stepX_YYY.jar ``` +### LiveScript + +*The LiveScript implementation was created by [Jos van Bakel](https://github.com/c0deaddict)* + +The LiveScript implementation of mal has been tested with LiveScript 1.5. + +``` +cd livescript +make +node_modules/.bin/lsc stepX_YYY.ls +``` + ### Logo *The Logo implementation was created by [Dov Murik](https://github.com/dubek)* diff --git a/livescript/Dockerfile b/livescript/Dockerfile new file mode 100644 index 0000000000..fbb4b5572e --- /dev/null +++ b/livescript/Dockerfile @@ -0,0 +1,31 @@ +FROM ubuntu:xenial +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# For building node modules +RUN apt-get -y install g++ + +# Add nodesource apt repo config for 7.X +RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - + +# Install nodejs +RUN apt-get -y install nodejs \ No newline at end of file diff --git a/livescript/Makefile b/livescript/Makefile index 080d572732..faec18c5a6 100644 --- a/livescript/Makefile +++ b/livescript/Makefile @@ -2,6 +2,7 @@ SOURCES_BASE = reader.ls printer.ls env.ls core.ls utils.ls SOURCES_STEPS = step0_repl.ls step1_read_print.ls step2_eval.ls \ step4_if_fn_do.ls step5_tco.ls step6_file.ls step7_quote.ls \ step8_macros.ls step9_try.ls stepA_mal.ls +SOURCES_LISP = env.ls core.ls stepA_mal.ls SOURCES = $(SOURCES_BASE) $(SOURCES_STEPS) BINS = $(SOURCES:%.ls=%.js) @@ -13,8 +14,27 @@ all: node_modules $(BINS) node_modules: npm install -%.js: %.ls +%.js: %.ls node_modules $(LSC) -d -c $(@:%.js=%.ls) +step1_read_print.js: utils.js reader.js printer.js +step2_eval.js: utils.js reader.js printer.js +step3_env.js: utils.js reader.js printer.js env.js +step4_if_fn_do.js: utils.js reader.js printer.js env.js core.js +step5_tco.js: utils.js reader.js printer.js env.js core.js +step6_file.js: utils.js reader.js printer.js env.js core.js +step7_quote.js: utils.js reader.js printer.js env.js core.js +step8_macros.js: utils.js reader.js printer.js env.js core.js +step9_try.js: utils.js reader.js printer.js env.js core.js +stepA_mal.js: utils.js reader.js printer.js env.js core.js + clean: rm -f $(BINS) + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `egrep "^\w*#|^\w*$$" $^ | wc` "[comments/blanks]" + +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `egrep "^\w*#|^\w*$$" $^ | wc` "[comments/blanks]" From f5df79b639c6875531ce1a843aac08ca62af13d0 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 24 May 2017 12:32:00 -0500 Subject: [PATCH 0335/2308] Livescript: update Dockerfile to support Travis. --- livescript/Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/livescript/Dockerfile b/livescript/Dockerfile index fbb4b5572e..edfa6948c6 100644 --- a/livescript/Dockerfile +++ b/livescript/Dockerfile @@ -28,4 +28,7 @@ RUN apt-get -y install g++ RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - # Install nodejs -RUN apt-get -y install nodejs \ No newline at end of file +RUN apt-get -y install nodejs + +ENV NPM_CONFIG_CACHE /mal/.npm + From bff7ae8bc81a4712385bbbb2cabc8a4a30afe9bc Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sun, 4 Jun 2017 11:54:24 +0200 Subject: [PATCH 0336/2308] Elm: step 0 --- .gitignore | 5 +++ Makefile | 3 +- elm/Dockerfile | 31 +++++++++++++++++ elm/Makefile | 40 ++++++++++++++++++++++ elm/bootstrap.js | 20 +++++++++++ elm/elm-package.json | 14 ++++++++ elm/node_readline.js | 47 +++++++++++++++++++++++++ elm/package.json | 17 ++++++++++ elm/run | 2 ++ elm/step0_repl.elm | 81 ++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 elm/Dockerfile create mode 100644 elm/Makefile create mode 100644 elm/bootstrap.js create mode 100644 elm/elm-package.json create mode 100644 elm/node_readline.js create mode 100644 elm/package.json create mode 100755 elm/run create mode 100644 elm/step0_repl.elm diff --git a/.gitignore b/.gitignore index 354278e904..b5bfa49851 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,8 @@ common-lisp/hist/* livescript/*.js !livescript/node_readline.js livescript/node_modules +elm/node_modules +elm/elm-stuff +elm/*.js +!elm/node_readline.js +!elm/bootstrap.js diff --git a/Makefile b/Makefile index 1ad8c84f7c..7473c11cc0 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ IMPLS = ada awk bash basic c d chuck clojure coffee common-lisp cpp crystal cs d haxe io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ python r racket rpython ruby rust scala skew swift swift3 tcl ts vb vhdl \ - vimscript livescript + vimscript livescript elm EXTENSION = .mal @@ -216,6 +216,7 @@ vhdl_STEP_TO_PROG = vhdl/$($(1)) vimscript_STEP_TO_PROG = vimscript/$($(1)).vim guile_STEP_TO_PROG = guile/$($(1)).scm livescript_STEP_TO_PROG = livescript/$($(1)).js +elm_STEP_TO_PROG = elm/$($(1)).elm # Needed some argument munging diff --git a/elm/Dockerfile b/elm/Dockerfile new file mode 100644 index 0000000000..fbb4b5572e --- /dev/null +++ b/elm/Dockerfile @@ -0,0 +1,31 @@ +FROM ubuntu:xenial +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# For building node modules +RUN apt-get -y install g++ + +# Add nodesource apt repo config for 7.X +RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - + +# Install nodejs +RUN apt-get -y install nodejs \ No newline at end of file diff --git a/elm/Makefile b/elm/Makefile new file mode 100644 index 0000000000..e5daf40fa0 --- /dev/null +++ b/elm/Makefile @@ -0,0 +1,40 @@ +SOURCES_BASE = #reader.ls printer.ls env.ls core.ls utils.ls +SOURCES_STEPS = step0_repl.elm #step1_read_print.ls step2_eval.ls \ + step4_if_fn_do.ls step5_tco.ls step6_file.ls step7_quote.ls \ + step8_macros.ls step9_try.ls stepA_mal.ls +SOURCES_LISP = #env.ls core.ls stepA_mal.ls +SOURCES = $(SOURCES_BASE) $(SOURCES_STEPS) + +BINS = $(SOURCES:%.elm=%.js) + +ELM_MAKE = node_modules/.bin/elm-make + +all: node_modules $(BINS) + +node_modules: + npm install + +%.js: %.elm node_modules + $(ELM_MAKE) $(@:%.js=%.elm) --output $@ + +# step1_read_print.js: utils.js reader.js printer.js +# step2_eval.js: utils.js reader.js printer.js +# step3_env.js: utils.js reader.js printer.js env.js +# step4_if_fn_do.js: utils.js reader.js printer.js env.js core.js +# step5_tco.js: utils.js reader.js printer.js env.js core.js +# step6_file.js: utils.js reader.js printer.js env.js core.js +# step7_quote.js: utils.js reader.js printer.js env.js core.js +# step8_macros.js: utils.js reader.js printer.js env.js core.js +# step9_try.js: utils.js reader.js printer.js env.js core.js +# stepA_mal.js: utils.js reader.js printer.js env.js core.js + +clean: + rm -f $(BINS) + +# stats: $(SOURCES) +# @wc $^ +# @printf "%5s %5s %5s %s\n" `egrep "^\w*#|^\w*$$" $^ | wc` "[comments/blanks]" + +# stats-lisp: $(SOURCES_LISP) +# @wc $^ +# @printf "%5s %5s %5s %s\n" `egrep "^\w*#|^\w*$$" $^ | wc` "[comments/blanks]" diff --git a/elm/bootstrap.js b/elm/bootstrap.js new file mode 100644 index 0000000000..6ec5f3048c --- /dev/null +++ b/elm/bootstrap.js @@ -0,0 +1,20 @@ +var readline = require('./node_readline'); + +// The first two arguments are: 'node' and 'bootstrap.js' +// The third argument is the name of the Elm module to load. +var args = process.argv.slice(2); +var mod = require('./' + args[0]); + +var app = mod.Main.worker({ + args: args.slice(1) +}); + +// Hook up the output and readLine ports of the app. +app.ports.output.subscribe(function(line) { + console.log(line); +}); + +app.ports.readLine.subscribe(function(prompt) { + var line = readline.readline(prompt); + app.ports.input.send(line); +}); diff --git a/elm/elm-package.json b/elm/elm-package.json new file mode 100644 index 0000000000..bec8632f38 --- /dev/null +++ b/elm/elm-package.json @@ -0,0 +1,14 @@ +{ + "version": "1.0.0", + "summary": "Make-A-Lisp implementation in Elm", + "repository": "https://github.com/kanaka/mal.git", + "license": "BSD3", + "source-directories": [ + "." + ], + "exposed-modules": [], + "dependencies": { + "elm-lang/core": "5.1.1 <= v < 6.0.0" + }, + "elm-version": "0.18.0 <= v < 0.19.0" +} diff --git a/elm/node_readline.js b/elm/node_readline.js new file mode 100644 index 0000000000..e59a62bd1e --- /dev/null +++ b/elm/node_readline.js @@ -0,0 +1,47 @@ +// IMPORTANT: choose one +var RL_LIB = "libreadline"; // NOTE: libreadline is GPL +//var RL_LIB = "libedit"; + +var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history'); + +var rlwrap = {}; // namespace for this module in web context + +var ffi = require('ffi'), + fs = require('fs'); + +var rllib = ffi.Library(RL_LIB, { + 'readline': [ 'string', [ 'string' ] ], + 'add_history': [ 'int', [ 'string' ] ]}); + +var rl_history_loaded = false; + +exports.readline = rlwrap.readline = function(prompt) { + prompt = typeof prompt !== 'undefined' ? prompt : "user> "; + + if (!rl_history_loaded) { + rl_history_loaded = true; + var lines = []; + if (fs.existsSync(HISTORY_FILE)) { + lines = fs.readFileSync(HISTORY_FILE).toString().split("\n"); + } + // Max of 2000 lines + lines = lines.slice(Math.max(lines.length - 2000, 0)); + for (var i=0; i Cmd msg + + +port readLine : String -> Cmd msg + + +port input : (Maybe String -> msg) -> Sub msg + + +main : Program Flags Model Msg +main = + programWithFlags + { init = init + , update = update + , subscriptions = \model -> input Input + } + + +type alias Flags = + { args : List String + } + + +type alias Model = + { args : List String + } + + +type Msg + = Input (Maybe String) + + +init : Flags -> ( Model, Cmd Msg ) +init flags = + ( flags, readLine prompt ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + Input (Just line) -> + ( model + , Cmd.batch + [ output (rep line) + , readLine prompt + ] + ) + + Input Nothing -> + ( model, Cmd.none ) + + +prompt : String +prompt = + "user> " + + +read : String -> String +read ast = + ast + + +eval : String -> String +eval ast = + ast + + +print : String -> String +print ast = + ast + + +rep : String -> String +rep = + read >> eval >> print From 0d8a8d94fd972e964946cbefa52f20a1add89768 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sun, 4 Jun 2017 23:41:21 +0200 Subject: [PATCH 0337/2308] Elm: step 1 using parser combinator --- elm/Makefile | 11 ++- elm/Printer.elm | 87 +++++++++++++++++ elm/Reader.elm | 203 +++++++++++++++++++++++++++++++++++++++ elm/Types.elm | 27 ++++++ elm/Utils.elm | 73 ++++++++++++++ elm/elm-package.json | 1 + elm/step1_read_print.elm | 111 +++++++++++++++++++++ 7 files changed, 509 insertions(+), 4 deletions(-) create mode 100644 elm/Printer.elm create mode 100644 elm/Reader.elm create mode 100644 elm/Types.elm create mode 100644 elm/Utils.elm create mode 100644 elm/step1_read_print.elm diff --git a/elm/Makefile b/elm/Makefile index e5daf40fa0..9dba7758bc 100644 --- a/elm/Makefile +++ b/elm/Makefile @@ -1,5 +1,5 @@ SOURCES_BASE = #reader.ls printer.ls env.ls core.ls utils.ls -SOURCES_STEPS = step0_repl.elm #step1_read_print.ls step2_eval.ls \ +SOURCES_STEPS = step0_repl.elm step1_read_print.elm #step2_eval.ls \ step4_if_fn_do.ls step5_tco.ls step6_file.ls step7_quote.ls \ step8_macros.ls step9_try.ls stepA_mal.ls SOURCES_LISP = #env.ls core.ls stepA_mal.ls @@ -7,17 +7,20 @@ SOURCES = $(SOURCES_BASE) $(SOURCES_STEPS) BINS = $(SOURCES:%.elm=%.js) -ELM_MAKE = node_modules/.bin/elm-make +ELM = node_modules/.bin/elm all: node_modules $(BINS) node_modules: npm install +elm_packages: + $(ELM) package install + %.js: %.elm node_modules - $(ELM_MAKE) $(@:%.js=%.elm) --output $@ + $(ELM) make $(@:%.js=%.elm) --output $@ -# step1_read_print.js: utils.js reader.js printer.js +step1_read_print.js: Reader.elm Printer.elm Utils.elm Types.elm # step2_eval.js: utils.js reader.js printer.js # step3_env.js: utils.js reader.js printer.js env.js # step4_if_fn_do.js: utils.js reader.js printer.js env.js core.js diff --git a/elm/Printer.elm b/elm/Printer.elm new file mode 100644 index 0000000000..5667d53a2a --- /dev/null +++ b/elm/Printer.elm @@ -0,0 +1,87 @@ +module Printer exposing (..) + +import Array exposing (Array) +import Dict exposing (Dict) +import Types exposing (MalExpr(..), keywordPrefix) +import Utils exposing (encodeString, wrap) + + +printString : Bool -> MalExpr -> String +printString readably ast = + case ast of + MalNil -> + "nil" + + MalBool True -> + "true" + + MalBool False -> + "false" + + MalInt int -> + toString int + + MalString str -> + printRawString readably str + + MalSymbol sym -> + sym + + MalKeyword kw -> + kw + + MalList list -> + printList readably list + + MalVector vec -> + printVector readably vec + + MalMap map -> + printMap readably map + + +printRawString : Bool -> String -> String +printRawString readably str = + if readably then + encodeString str + else + str + + +printList : Bool -> List MalExpr -> String +printList readably = + List.map (printString readably) + >> String.join " " + >> wrap "(" ")" + + +printVector : Bool -> Array MalExpr -> String +printVector readably = + Array.map (printString readably) + >> Array.toList + >> String.join " " + >> wrap "[" "]" + + +printMap : Bool -> Dict String MalExpr -> String +printMap readably = + let + -- Strip off the keyword prefix if it is there. + printKey k = + case String.uncons k of + Just ( prefix, rest ) -> + if prefix == keywordPrefix then + rest + else + printRawString readably k + + _ -> + printRawString readably k + + printEntry ( k, v ) = + (printKey k) ++ " " ++ (printString readably v) + in + Dict.toList + >> List.map printEntry + >> String.join " " + >> wrap "{" "}" diff --git a/elm/Reader.elm b/elm/Reader.elm new file mode 100644 index 0000000000..bcd934486f --- /dev/null +++ b/elm/Reader.elm @@ -0,0 +1,203 @@ +module Reader exposing (..) + +import Array +import Dict +import Combine exposing (..) +import Combine.Num +import Types exposing (MalExpr(..), keywordPrefix) +import Utils exposing (decodeString, makeCall) + + +comment : Parser s String +comment = + regex ";.*" + + +ws : Parser s (List String) +ws = + many (comment <|> string "," <|> whitespace) + + +nil : Parser s MalExpr +nil = + MalNil <$ string "nil" "nil" + + +int : Parser s MalExpr +int = + MalInt <$> Combine.Num.int "int" + + +bool : Parser s MalExpr +bool = + MalBool + <$> choice + [ True <$ string "true" + , False <$ string "false" + ] + "bool" + + +symbolString : Parser s String +symbolString = + regex "[^\\s\\[\\]{}('\"`,;)]+" + + +symbol : Parser s MalExpr +symbol = + MalSymbol + <$> symbolString + "symbol" + + +keywordString : Parser s String +keywordString = + (++) + <$> string ":" + <*> symbolString + + +keyword : Parser s MalExpr +keyword = + MalKeyword <$> keywordString + + +list : Parser s MalExpr +list = + MalList + <$> parens (many form <* ws) + "list" + + +vector : Parser s MalExpr +vector = + MalVector + << Array.fromList + <$> (string "[" + *> many form + <* ws + <* string "]" + ) + "vector" + + +mapKey : Parser s String +mapKey = + choice + [ String.cons keywordPrefix <$> keywordString + , decodeString <$> strString + ] + + +mapEntry : Parser s ( String, MalExpr ) +mapEntry = + (,) <$> mapKey <*> form "map entry" + + +map : Parser s MalExpr +map = + lazy <| + \() -> + MalMap + << Dict.fromList + <$> (string "{" + *> many (ws *> mapEntry) + <* ws + <* string "}" + ) + "map" + + +atom : Parser s MalExpr +atom = + choice + [ int + , bool + , str + , nil + , keyword + , symbol + ] + "atom" + + +form : Parser s MalExpr +form = + lazy <| + \() -> + let + parsers = + [ list + , vector + , map + , simpleMacro "'" "quote" + , simpleMacro "`" "quasiquote" + , simpleMacro "~@" "splice-unquote" + , simpleMacro "~" "unquote" + , simpleMacro "@" "deref" + , withMeta + , atom + ] + in + ws *> choice parsers "form" + + +simpleMacro : String -> String -> Parser s MalExpr +simpleMacro token symbol = + makeCall symbol + << List.singleton + <$> (string token *> form) + symbol + + +withMeta : Parser s MalExpr +withMeta = + lazy <| + \() -> + let + make meta expr = + makeCall "with-meta" [ expr, meta ] + in + make + <$> (string "^" *> map) + <*> form + "with-meta" + + +readString : String -> Result String (Maybe MalExpr) +readString str = + case parse ((maybe form) <* ws <* end) str of + Ok ( _, _, ast ) -> + Ok ast + + Err ( _, stream, ms ) -> + Err <| formatError ms stream + + +formatError : List String -> InputStream -> String +formatError ms stream = + let + location = + currentLocation stream + in + "Parse error: expected a: " + ++ String.join ", " ms + ++ " " + ++ "(at " + ++ toString location.line + ++ ":" + ++ toString location.column + ++ ")" + + +str : Parser s MalExpr +str = + MalString << decodeString <$> strString + + +{-| Syntax highlighter in VS code is messed up by this regex, +that's why it's down below. :) +-} +strString : Parser s String +strString = + regex "\"(\\\\\"|[^\"])*\"" "string" diff --git a/elm/Types.elm b/elm/Types.elm new file mode 100644 index 0000000000..766457e756 --- /dev/null +++ b/elm/Types.elm @@ -0,0 +1,27 @@ +module Types exposing (MalExpr(..), keywordPrefix) + +import Array exposing (Array) +import Dict exposing (Dict) + + +type MalExpr + = MalNil + | MalBool Bool + | MalInt Int + | MalString String + | MalKeyword String + | MalSymbol String + | MalList (List MalExpr) + | MalVector (Array MalExpr) + | MalMap (Dict String MalExpr) + + +{-| Keywords are prefixed by this char for usage in a MalMap. +Elm doesn't support user defined types as keys in a Dict. + +The unicode char is: '\x029e' + +-} +keywordPrefix : Char +keywordPrefix = + 'Êž' diff --git a/elm/Utils.elm b/elm/Utils.elm new file mode 100644 index 0000000000..809954a1a7 --- /dev/null +++ b/elm/Utils.elm @@ -0,0 +1,73 @@ +module Utils + exposing + ( decodeString + , encodeString + , makeCall + , wrap + , maybeToList + ) + +import Regex exposing (replace, regex, HowMany(All)) +import Types exposing (MalExpr(..)) + + +decodeString : String -> String +decodeString = + let + unescape { match } = + case match of + "\\n" -> + "\n" + + "\\\"" -> + "\"" + + "\\\\" -> + "\\" + + other -> + other + in + String.slice 1 -1 + >> replace All (regex "\\\\[\\\"\\\\n]") unescape + + +encodeString : String -> String +encodeString = + let + escape { match } = + case match of + "\n" -> + "\\n" + + "\"" -> + "\\\"" + + "\\" -> + "\\\\" + + other -> + other + in + wrap "\"" "\"" + << replace All (regex "[\\n\\\"\\\\]") escape + + +makeCall : String -> List MalExpr -> MalExpr +makeCall symbol args = + MalList <| (MalSymbol symbol) :: args + + +wrap : String -> String -> String -> String +wrap prefix suffix str = + prefix ++ str ++ suffix + + +maybeToList : Maybe a -> List a +maybeToList m = + case m of + Just x -> + [ x ] + + Nothing -> + [] diff --git a/elm/elm-package.json b/elm/elm-package.json index bec8632f38..5ba0b8cd82 100644 --- a/elm/elm-package.json +++ b/elm/elm-package.json @@ -8,6 +8,7 @@ ], "exposed-modules": [], "dependencies": { + "Bogdanp/elm-combine": "3.1.1 <= v < 4.0.0", "elm-lang/core": "5.1.1 <= v < 6.0.0" }, "elm-version": "0.18.0 <= v < 0.19.0" diff --git a/elm/step1_read_print.elm b/elm/step1_read_print.elm new file mode 100644 index 0000000000..0acd76fcdd --- /dev/null +++ b/elm/step1_read_print.elm @@ -0,0 +1,111 @@ +port module Main exposing (..) + +import Platform exposing (programWithFlags) +import Json.Decode +import Types exposing (MalExpr(..)) +import Reader exposing (readString) +import Printer exposing (printString) +import Utils exposing (maybeToList) + + +-- Output a string to stdout + + +port output : String -> Cmd msg + + + +-- Read a line from the stdin + + +port readLine : String -> Cmd msg + + + +-- Received a line from the stdin (in response to readLine). + + +port input : (Maybe String -> msg) -> Sub msg + + +main : Program Flags Model Msg +main = + programWithFlags + { init = init + , update = update + , subscriptions = \model -> input Input + } + + +type alias Flags = + { args : List String + } + + +type alias Model = + { args : List String + } + + +type Msg + = Input (Maybe String) + + +init : Flags -> ( Model, Cmd Msg ) +init flags = + ( flags, readLine prompt ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + Input (Just line) -> + let + outputCmd = + rep line |> Maybe.map output + + cmds = + maybeToList outputCmd ++ [ readLine prompt ] + in + ( model + , Cmd.batch cmds + ) + + Input Nothing -> + ( model, Cmd.none ) + + +prompt : String +prompt = + "user> " + + +read : String -> Result String (Maybe MalExpr) +read = + readString + + +eval : MalExpr -> MalExpr +eval ast = + ast + + +print : MalExpr -> String +print = + printString True + + +rep : String -> Maybe String +rep = + let + formatResult result = + case result of + Ok optStr -> + optStr + + Err msg -> + Just msg + in + readString + >> Result.map (Maybe.map (eval >> print)) + >> formatResult From 4e84165f907428541f90e40d7061e987ac23a809 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Mon, 5 Jun 2017 12:23:33 +0200 Subject: [PATCH 0338/2308] Elm: step 2 --- elm/Makefile | 6 +- elm/Printer.elm | 3 + elm/Reader.elm | 2 +- elm/Types.elm | 1 + elm/Utils.elm | 14 +++ elm/step1_read_print.elm | 33 +++-- elm/step2_eval.elm | 260 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 303 insertions(+), 16 deletions(-) create mode 100644 elm/step2_eval.elm diff --git a/elm/Makefile b/elm/Makefile index 9dba7758bc..ef0bc9249d 100644 --- a/elm/Makefile +++ b/elm/Makefile @@ -1,6 +1,6 @@ SOURCES_BASE = #reader.ls printer.ls env.ls core.ls utils.ls -SOURCES_STEPS = step0_repl.elm step1_read_print.elm #step2_eval.ls \ - step4_if_fn_do.ls step5_tco.ls step6_file.ls step7_quote.ls \ +SOURCES_STEPS = step0_repl.elm step1_read_print.elm step2_eval.elm \ + #step4_if_fn_do.ls step5_tco.ls step6_file.ls step7_quote.ls \ step8_macros.ls step9_try.ls stepA_mal.ls SOURCES_LISP = #env.ls core.ls stepA_mal.ls SOURCES = $(SOURCES_BASE) $(SOURCES_STEPS) @@ -21,7 +21,7 @@ elm_packages: $(ELM) make $(@:%.js=%.elm) --output $@ step1_read_print.js: Reader.elm Printer.elm Utils.elm Types.elm -# step2_eval.js: utils.js reader.js printer.js +step2_eval.js: Reader.elm Printer.elm Utils.elm Types.elm # step3_env.js: utils.js reader.js printer.js env.js # step4_if_fn_do.js: utils.js reader.js printer.js env.js core.js # step5_tco.js: utils.js reader.js printer.js env.js core.js diff --git a/elm/Printer.elm b/elm/Printer.elm index 5667d53a2a..4d7f8fe01b 100644 --- a/elm/Printer.elm +++ b/elm/Printer.elm @@ -39,6 +39,9 @@ printString readably ast = MalMap map -> printMap readably map + MalFunction _ -> + "#" + printRawString : Bool -> String -> String printRawString readably str = diff --git a/elm/Reader.elm b/elm/Reader.elm index bcd934486f..11c4e5cf4e 100644 --- a/elm/Reader.elm +++ b/elm/Reader.elm @@ -180,7 +180,7 @@ formatError ms stream = location = currentLocation stream in - "Parse error: expected a: " + "Parse error: " ++ String.join ", " ms ++ " " ++ "(at " diff --git a/elm/Types.elm b/elm/Types.elm index 766457e756..32c002ccbd 100644 --- a/elm/Types.elm +++ b/elm/Types.elm @@ -14,6 +14,7 @@ type MalExpr | MalList (List MalExpr) | MalVector (Array MalExpr) | MalMap (Dict String MalExpr) + | MalFunction (List MalExpr -> Result String MalExpr) {-| Keywords are prefixed by this char for usage in a MalMap. diff --git a/elm/Utils.elm b/elm/Utils.elm index 809954a1a7..4097becff0 100644 --- a/elm/Utils.elm +++ b/elm/Utils.elm @@ -5,6 +5,7 @@ module Utils , makeCall , wrap , maybeToList + , zip ) import Regex exposing (replace, regex, HowMany(All)) @@ -71,3 +72,16 @@ maybeToList m = Nothing -> [] + + +zip : List a -> List b -> List ( a, b ) +zip a b = + case ( a, b ) of + ( [], _ ) -> + [] + + ( _, [] ) -> + [] + + ( x :: xs, y :: ys ) -> + ( x, y ) :: zip xs ys diff --git a/elm/step1_read_print.elm b/elm/step1_read_print.elm index 0acd76fcdd..285d447525 100644 --- a/elm/step1_read_print.elm +++ b/elm/step1_read_print.elm @@ -1,30 +1,29 @@ port module Main exposing (..) -import Platform exposing (programWithFlags) +{-| Your IDE might complain that the Json.Decode import +is not used, but it is. Without it you'll get a runtime exception. +-} + import Json.Decode +import Platform exposing (programWithFlags) import Types exposing (MalExpr(..)) import Reader exposing (readString) import Printer exposing (printString) import Utils exposing (maybeToList) --- Output a string to stdout - - +{-| Output a string to stdout +-} port output : String -> Cmd msg - --- Read a line from the stdin - - +{-| Read a line from the stdin +-} port readLine : String -> Cmd msg - --- Received a line from the stdin (in response to readLine). - - +{-| Received a line from the stdin (in response to readLine). +-} port input : (Maybe String -> msg) -> Sub msg @@ -64,6 +63,7 @@ update msg model = outputCmd = rep line |> Maybe.map output + -- Don't print output when 'rep' returns Nothing. cmds = maybeToList outputCmd ++ [ readLine prompt ] in @@ -80,6 +80,13 @@ prompt = "user> " +{-| read can return three things: + +Ok (Just expr) -> parsed okay +Ok Nothing -> empty string (only whitespace and/or comments) +Err msg -> parse error + +-} read : String -> Result String (Maybe MalExpr) read = readString @@ -95,6 +102,8 @@ print = printString True +{-| Read-Eval-Print +-} rep : String -> Maybe String rep = let diff --git a/elm/step2_eval.elm b/elm/step2_eval.elm new file mode 100644 index 0000000000..b12a93f81f --- /dev/null +++ b/elm/step2_eval.elm @@ -0,0 +1,260 @@ +port module Main exposing (..) + +{-| Your IDE might complain that the Json.Decode import +is not used, but it is. Without it you'll get a runtime exception. +-} + +import Json.Decode +import Platform exposing (programWithFlags) +import Types exposing (MalExpr(..)) +import Reader exposing (readString) +import Printer exposing (printString) +import Utils exposing (maybeToList, zip) +import Dict exposing (Dict) +import Tuple exposing (mapFirst) +import Array + + +{-| Output a string to stdout +-} +port output : String -> Cmd msg + + +{-| Read a line from the stdin +-} +port readLine : String -> Cmd msg + + +{-| Received a line from the stdin (in response to readLine). +-} +port input : (Maybe String -> msg) -> Sub msg + + +main : Program Flags Model Msg +main = + programWithFlags + { init = init + , update = update + , subscriptions = \model -> input Input + } + + +type alias Flags = + { args : List String + } + + +type alias ReplEnv = + Dict String MalExpr + + +type alias Model = + { args : List String + , env : ReplEnv + } + + +type Msg + = Input (Maybe String) + + +init : Flags -> ( Model, Cmd Msg ) +init { args } = + ( { args = args, env = initReplEnv }, readLine prompt ) + + +initReplEnv : ReplEnv +initReplEnv = + let + binaryOp fn args = + case args of + [ MalInt x, MalInt y ] -> + Ok <| MalInt (fn x y) + + _ -> + Err "unsupported arguments" + in + Dict.fromList + [ ( "+", MalFunction <| binaryOp (+) ) + , ( "-", MalFunction <| binaryOp (-) ) + , ( "*", MalFunction <| binaryOp (*) ) + , ( "/", MalFunction <| binaryOp (//) ) + ] + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + Input (Just line) -> + case rep model.env line of + Nothing -> + ( model, readLine prompt ) + + Just ( result, newEnv ) -> + ( { model | env = newEnv } + , Cmd.batch + [ makeOutput result + , readLine prompt + ] + ) + + Input Nothing -> + ( model, Cmd.none ) + + +makeOutput : Result String String -> Cmd msg +makeOutput result = + output <| + case result of + Ok str -> + str + + Err msg -> + "ERR:" ++ msg + + +prompt : String +prompt = + "user> " + + +{-| read can return three things: + +Ok (Just expr) -> parsed okay +Ok Nothing -> empty string (only whitespace and/or comments) +Err msg -> parse error + +-} +read : String -> Result String (Maybe MalExpr) +read = + readString + + +eval : ReplEnv -> MalExpr -> ( Result String MalExpr, ReplEnv ) +eval env ast = + case ast of + MalList [] -> + ( Ok ast, env ) + + MalList list -> + case evalList env list [] of + ( Ok newList, newEnv ) -> + case newList of + [] -> + ( Err "can't happen", newEnv ) + + (MalFunction fn) :: args -> + ( fn args, newEnv ) + + fn :: _ -> + ( Err ((printString True fn) ++ " is not a function"), newEnv ) + + ( Err msg, newEnv ) -> + ( Err msg, newEnv ) + + _ -> + evalAst env ast + + +evalAst : ReplEnv -> MalExpr -> ( Result String MalExpr, ReplEnv ) +evalAst env ast = + case ast of + MalSymbol sym -> + -- Lookup symbol in env and return value or raise error if not found. + case Dict.get sym env of + Just val -> + ( Ok val, env ) + + Nothing -> + ( Err "symbol not found", env ) + + MalList list -> + -- Return new list that is result of calling eval on each element of list. + evalList env list [] + |> mapFirst (Result.map MalList) + + MalVector vec -> + evalList env (Array.toList vec) [] + |> mapFirst (Result.map (Array.fromList >> MalVector)) + + MalMap map -> + evalList env (Dict.values map) [] + |> mapFirst + (Result.map + (zip (Dict.keys map) + >> Dict.fromList + >> MalMap + ) + ) + + _ -> + ( Ok ast, env ) + + +evalList : ReplEnv -> List MalExpr -> List MalExpr -> ( Result String (List MalExpr), ReplEnv ) +evalList env list acc = + case list of + [] -> + ( Ok (List.reverse acc), env ) + + x :: rest -> + case eval env x of + ( Ok val, newEnv ) -> + evalList newEnv rest (val :: acc) + + ( Err msg, newEnv ) -> + ( Err msg, newEnv ) + + +{-| Try to map a list with a fn that can return a Err. + +Maps the list from left to right. As soon as a error +occurs it will not process any more elements and return +the error. + +-} +tryMapList : (a -> Result e b) -> List a -> Result e (List b) +tryMapList fn list = + let + go x = + Result.andThen + (\acc -> + case fn x of + Ok val -> + Ok (val :: acc) + + Err msg -> + Err msg + ) + in + List.foldl go (Ok []) list + |> Result.map List.reverse + + +print : MalExpr -> String +print = + printString True + + +{-| Read-Eval-Print. rep returns: + +Nothing -> if an empty string is read (ws/comments) +Just ((Ok out), newEnv) -> input has been evaluated. +Just ((Err msg), env) -> error parsing or evaluating. + +-} +rep : ReplEnv -> String -> Maybe ( Result String String, ReplEnv ) +rep env input = + let + evalPrint = + eval env >> mapFirst (Result.map print) + in + case readString input of + Ok Nothing -> + Nothing + + Err msg -> + Just ( Err msg, env ) + + Ok (Just ast) -> + Just (evalPrint ast) From 4cb2c1e49d234f1333d9a70ecc0150ef10c5119b Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Mon, 5 Jun 2017 13:30:12 +0200 Subject: [PATCH 0339/2308] Elm: step 3 --- elm/Env.elm | 33 +++++ elm/Makefile | 5 +- elm/step3_env.elm | 319 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 elm/Env.elm create mode 100644 elm/step3_env.elm diff --git a/elm/Env.elm b/elm/Env.elm new file mode 100644 index 0000000000..31f1d71b58 --- /dev/null +++ b/elm/Env.elm @@ -0,0 +1,33 @@ +module Env exposing (Env, make, set, get) + +import Types exposing (MalExpr(..)) +import Dict exposing (Dict) + + +type Env + = Env + { outer : Maybe Env + , data : Dict String MalExpr + } + + +make : Maybe Env -> Env +make outer = + Env { outer = outer, data = Dict.empty } + + +set : String -> MalExpr -> Env -> Env +set name expr (Env env) = + Env { env | data = Dict.insert name expr env.data } + + +get : String -> Env -> Result String MalExpr +get name ((Env { outer, data }) as env) = + case Dict.get name data of + Just val -> + Ok val + + Nothing -> + outer + |> Maybe.map (get name) + |> Maybe.withDefault (Err "symbol not found") diff --git a/elm/Makefile b/elm/Makefile index ef0bc9249d..05b4f13786 100644 --- a/elm/Makefile +++ b/elm/Makefile @@ -1,6 +1,7 @@ SOURCES_BASE = #reader.ls printer.ls env.ls core.ls utils.ls SOURCES_STEPS = step0_repl.elm step1_read_print.elm step2_eval.elm \ - #step4_if_fn_do.ls step5_tco.ls step6_file.ls step7_quote.ls \ + step3_env.elm # \ + step4_if_fn_do.ls step5_tco.ls step6_file.ls step7_quote.ls \ step8_macros.ls step9_try.ls stepA_mal.ls SOURCES_LISP = #env.ls core.ls stepA_mal.ls SOURCES = $(SOURCES_BASE) $(SOURCES_STEPS) @@ -22,7 +23,7 @@ elm_packages: step1_read_print.js: Reader.elm Printer.elm Utils.elm Types.elm step2_eval.js: Reader.elm Printer.elm Utils.elm Types.elm -# step3_env.js: utils.js reader.js printer.js env.js +step3_env.js: Reader.elm Printer.elm Utils.elm Types.elm Env.elm # step4_if_fn_do.js: utils.js reader.js printer.js env.js core.js # step5_tco.js: utils.js reader.js printer.js env.js core.js # step6_file.js: utils.js reader.js printer.js env.js core.js diff --git a/elm/step3_env.elm b/elm/step3_env.elm new file mode 100644 index 0000000000..99958dc097 --- /dev/null +++ b/elm/step3_env.elm @@ -0,0 +1,319 @@ +port module Main exposing (..) + +{-| Your IDE might complain that the Json.Decode import +is not used, but it is. Without it you'll get a runtime exception. +-} + +import Json.Decode +import Platform exposing (programWithFlags) +import Types exposing (MalExpr(..)) +import Reader exposing (readString) +import Printer exposing (printString) +import Utils exposing (maybeToList, zip) +import Dict exposing (Dict) +import Tuple exposing (mapFirst, mapSecond) +import Array +import Env exposing (Env) + + +{-| Output a string to stdout +-} +port output : String -> Cmd msg + + +{-| Read a line from the stdin +-} +port readLine : String -> Cmd msg + + +{-| Received a line from the stdin (in response to readLine). +-} +port input : (Maybe String -> msg) -> Sub msg + + +main : Program Flags Model Msg +main = + programWithFlags + { init = init + , update = update + , subscriptions = \model -> input Input + } + + +type alias Flags = + { args : List String + } + + +type alias Model = + { args : List String + , env : Env + } + + +type Msg + = Input (Maybe String) + + +init : Flags -> ( Model, Cmd Msg ) +init { args } = + ( { args = args, env = initReplEnv }, readLine prompt ) + + +initReplEnv : Env +initReplEnv = + let + binaryOp fn args = + case args of + [ MalInt x, MalInt y ] -> + Ok <| MalInt (fn x y) + + _ -> + Err "unsupported arguments" + in + Env.make Nothing + |> Env.set "+" (MalFunction <| binaryOp (+)) + |> Env.set "-" (MalFunction <| binaryOp (-)) + |> Env.set "*" (MalFunction <| binaryOp (*)) + |> Env.set "/" (MalFunction <| binaryOp (//)) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + Input (Just line) -> + case rep model.env line of + Nothing -> + ( model, readLine prompt ) + + Just ( result, newEnv ) -> + ( { model | env = newEnv } + , Cmd.batch + [ makeOutput result + , readLine prompt + ] + ) + + Input Nothing -> + ( model, Cmd.none ) + + +makeOutput : Result String String -> Cmd msg +makeOutput result = + output <| + case result of + Ok str -> + str + + Err msg -> + "ERR:" ++ msg + + +prompt : String +prompt = + "user> " + + +{-| read can return three things: + +Ok (Just expr) -> parsed okay +Ok Nothing -> empty string (only whitespace and/or comments) +Err msg -> parse error + +-} +read : String -> Result String (Maybe MalExpr) +read = + readString + + +eval : Env -> MalExpr -> ( Result String MalExpr, Env ) +eval env ast = + case ast of + MalList [] -> + ( Ok ast, env ) + + MalList ((MalSymbol "def!") :: args) -> + evalDef env args + + MalList ((MalSymbol "let*") :: args) -> + evalLet env args + + MalList list -> + case evalList env list [] of + ( Ok newList, newEnv ) -> + case newList of + [] -> + ( Err "can't happen", newEnv ) + + (MalFunction fn) :: args -> + ( fn args, newEnv ) + + fn :: _ -> + ( Err ((printString True fn) ++ " is not a function"), newEnv ) + + ( Err msg, newEnv ) -> + ( Err msg, newEnv ) + + _ -> + evalAst env ast + + +evalAst : Env -> MalExpr -> ( Result String MalExpr, Env ) +evalAst env ast = + case ast of + MalSymbol sym -> + -- Lookup symbol in env and return value or raise error if not found. + case Env.get sym env of + Ok val -> + ( Ok val, env ) + + Err msg -> + ( Err msg, env ) + + MalList list -> + -- Return new list that is result of calling eval on each element of list. + evalList env list [] + |> mapFirst (Result.map MalList) + + MalVector vec -> + evalList env (Array.toList vec) [] + |> mapFirst (Result.map (Array.fromList >> MalVector)) + + MalMap map -> + evalList env (Dict.values map) [] + |> mapFirst + (Result.map + (zip (Dict.keys map) + >> Dict.fromList + >> MalMap + ) + ) + + _ -> + ( Ok ast, env ) + + +evalList : Env -> List MalExpr -> List MalExpr -> ( Result String (List MalExpr), Env ) +evalList env list acc = + case list of + [] -> + ( Ok (List.reverse acc), env ) + + x :: rest -> + case eval env x of + ( Ok val, newEnv ) -> + evalList newEnv rest (val :: acc) + + ( Err msg, newEnv ) -> + ( Err msg, newEnv ) + + +evalDef : Env -> List MalExpr -> ( Result String MalExpr, Env ) +evalDef env args = + case args of + [ MalSymbol name, uneValue ] -> + case eval env uneValue of + ( Ok value, newEnv ) -> + ( Ok value, Env.set name value newEnv ) + + err -> + err + + _ -> + ( Err "def! expected two args: name and value", env ) + + +evalLet : Env -> List MalExpr -> ( Result String MalExpr, Env ) +evalLet env args = + let + evalBinds env binds = + case binds of + (MalSymbol name) :: expr :: rest -> + case eval env expr of + ( Ok value, newEnv ) -> + let + newEnv = + Env.set name value env + in + if List.isEmpty rest then + Ok newEnv + else + evalBinds newEnv rest + + ( Err msg, _ ) -> + Err msg + + _ -> + Err "let* expected an even number of binds (symbol expr ..)" + + go binds body = + case evalBinds (Env.make (Just env)) binds of + Ok newEnv -> + mapSecond (\_ -> env) (eval newEnv body) + + Err msg -> + ( Err msg, env ) + in + case args of + [ MalList binds, body ] -> + go binds body + + [ MalVector bindsVec, body ] -> + go (Array.toList bindsVec) body + + _ -> + ( Err "let* expected two args: binds and a body", env ) + + +{-| Try to map a list with a fn that can return a Err. + +Maps the list from left to right. As soon as a error +occurs it will not process any more elements and return +the error. + +-} +tryMapList : (a -> Result e b) -> List a -> Result e (List b) +tryMapList fn list = + let + go x = + Result.andThen + (\acc -> + case fn x of + Ok val -> + Ok (val :: acc) + + Err msg -> + Err msg + ) + in + List.foldl go (Ok []) list + |> Result.map List.reverse + + +print : MalExpr -> String +print = + printString True + + +{-| Read-Eval-Print. rep returns: + +Nothing -> if an empty string is read (ws/comments) +Just ((Ok out), newEnv) -> input has been evaluated. +Just ((Err msg), env) -> error parsing or evaluating. + +-} +rep : Env -> String -> Maybe ( Result String String, Env ) +rep env input = + let + evalPrint = + eval env >> mapFirst (Result.map print) + in + case readString input of + Ok Nothing -> + Nothing + + Err msg -> + Just ( Err msg, env ) + + Ok (Just ast) -> + Just (evalPrint ast) From c792f15ef800868dc0abe1f89a11dc453d3e4420 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Thu, 8 Jun 2017 19:19:27 +0200 Subject: [PATCH 0340/2308] Elm: part 4 halfway finished. Hello Monads. --- elm/Core.elm | 121 ++++++++++++ elm/Env.elm | 11 +- elm/Eval.elm | 90 +++++++++ elm/IO.elm | 53 ++++++ elm/Makefile | 18 +- elm/Types.elm | 39 +++- elm/Utils.elm | 14 ++ elm/bootstrap.js | 7 +- elm/step0_repl.elm | 36 ++-- elm/step1_read_print.elm | 56 ++---- elm/step2_eval.elm | 57 ++---- elm/step3_env.elm | 56 ++---- elm/step4_if_fn_do.elm | 395 +++++++++++++++++++++++++++++++++++++++ 13 files changed, 799 insertions(+), 154 deletions(-) create mode 100644 elm/Core.elm create mode 100644 elm/Eval.elm create mode 100644 elm/IO.elm create mode 100644 elm/step4_if_fn_do.elm diff --git a/elm/Core.elm b/elm/Core.elm new file mode 100644 index 0000000000..b24c3549b1 --- /dev/null +++ b/elm/Core.elm @@ -0,0 +1,121 @@ +module Core exposing (..) + +import Types exposing (MalExpr(..), Eval, Env) +import Env +import Eval +import Printer exposing (printString) +import Array +import IO exposing (IO(..)) + + +ns : Env +ns = + let + binaryOp fn retType args = + case args of + [ MalInt x, MalInt y ] -> + Eval.succeed (retType (fn x y)) + + _ -> + Eval.fail "unsupported arguments" + + {- list -} + list = + Eval.succeed << MalList + + {- list? -} + isList args = + case args of + [ MalList _ ] -> + Eval.succeed (MalBool True) + + _ -> + Eval.succeed (MalBool False) + + {- empty? -} + isEmpty args = + case args of + [ MalList list ] -> + Eval.succeed <| MalBool (List.isEmpty list) + + [ MalVector vec ] -> + Eval.succeed <| MalBool (Array.isEmpty vec) + + _ -> + Eval.fail "unsupported arguments" + + {- count -} + count args = + case args of + [ MalList list ] -> + Eval.succeed <| MalInt (List.length list) + + [ MalVector vec ] -> + Eval.succeed <| MalInt (Array.length vec) + + _ -> + Eval.fail "unsupported arguments" + + {- = -} + equals args = + case args of + [ a, b ] -> + Eval.succeed <| MalBool (a == b) + + _ -> + Eval.fail "unsupported arguments" + + {- pr-str -} + prStr = + List.map (printString True) + >> String.join " " + >> MalString + >> Eval.succeed + + {- str -} + str = + List.map (printString False) + >> String.join "" + >> MalString + >> Eval.succeed + + writeLine str = + Eval.io (IO.writeLine str) + (\msg -> + case msg of + LineWritten -> + -- TODO need caller continuation here... + Eval.succeed MalNil + + _ -> + Eval.fail "wrong IO, expected LineWritten" + ) + + prn = + List.map (printString True) + >> String.join " " + >> writeLine + + println = + List.map (printString False) + >> String.join " " + >> writeLine + in + Env.make Nothing + |> Env.set "+" (MalFunction <| binaryOp (+) MalInt) + |> Env.set "-" (MalFunction <| binaryOp (-) MalInt) + |> Env.set "*" (MalFunction <| binaryOp (*) MalInt) + |> Env.set "/" (MalFunction <| binaryOp (//) MalInt) + |> Env.set "<" (MalFunction <| binaryOp (<) MalBool) + |> Env.set ">" (MalFunction <| binaryOp (>) MalBool) + |> Env.set "<=" (MalFunction <| binaryOp (<=) MalBool) + |> Env.set ">=" (MalFunction <| binaryOp (>=) MalBool) + |> Env.set "list" (MalFunction list) + |> Env.set "list?" (MalFunction isList) + |> Env.set "empty?" (MalFunction isEmpty) + |> Env.set "count" (MalFunction count) + |> Env.set "=" (MalFunction equals) + |> Env.set "pr-str" (MalFunction prStr) + |> Env.set "str" (MalFunction str) + |> Env.set "prn" (MalFunction prn) + |> Env.set "println" (MalFunction println) diff --git a/elm/Env.elm b/elm/Env.elm index 31f1d71b58..6240c837dc 100644 --- a/elm/Env.elm +++ b/elm/Env.elm @@ -1,16 +1,9 @@ -module Env exposing (Env, make, set, get) +module Env exposing (make, set, get) -import Types exposing (MalExpr(..)) +import Types exposing (MalExpr(..), Env(..)) import Dict exposing (Dict) -type Env - = Env - { outer : Maybe Env - , data : Dict String MalExpr - } - - make : Maybe Env -> Env make outer = Env { outer = outer, data = Dict.empty } diff --git a/elm/Eval.elm b/elm/Eval.elm new file mode 100644 index 0000000000..8ade773a1f --- /dev/null +++ b/elm/Eval.elm @@ -0,0 +1,90 @@ +module Eval exposing (..) + +import Types exposing (..) +import IO exposing (IO) + + +apply : Eval a -> EvalState -> EvalContext a +apply (Eval f) state = + f state + + +run : EvalState -> Eval a -> EvalContext a +run state e = + apply e state + + +withState : (EvalState -> Eval a) -> Eval a +withState f = + Eval <| + \state -> + apply (f state) state + + +putState : EvalState -> Eval () +putState state = + Eval <| + \_ -> + apply (succeed ()) state + + +modifyState : (EvalState -> EvalState) -> Eval () +modifyState f = + Eval <| + \state -> + apply (succeed ()) (f state) + + +succeed : a -> Eval a +succeed res = + Eval <| + \state -> + ( state, EvalOk res ) + + +io : Cmd Msg -> (IO -> Eval a) -> Eval a +io cmd cont = + Eval <| + \state -> + ( state, EvalIO cmd cont ) + + +map : (a -> b) -> Eval a -> Eval b +map f e = + Eval <| + \state -> + case apply e state of + ( state, EvalOk res ) -> + ( state, EvalOk (f res) ) + + ( state, EvalErr msg ) -> + ( state, EvalErr msg ) + + ( state, EvalIO cmd cont ) -> + ( state, EvalIO cmd (cont >> map f) ) + + +andThen : (a -> Eval b) -> Eval a -> Eval b +andThen f e = + Eval <| + \state -> + case apply e state of + ( state, EvalOk res ) -> + apply (f res) state + + ( state, EvalErr msg ) -> + ( state, EvalErr msg ) + + ( state, EvalIO cmd cont ) -> + ( state, EvalIO cmd (cont >> andThen f) ) + + + +-- Debug.log "wrapping EvalIO" ( state, EvalIO cmd cont ) + + +fail : String -> Eval a +fail msg = + Eval <| + \state -> + ( state, EvalErr msg ) diff --git a/elm/IO.elm b/elm/IO.elm new file mode 100644 index 0000000000..5afd3d7d27 --- /dev/null +++ b/elm/IO.elm @@ -0,0 +1,53 @@ +port module IO + exposing + ( IO(..) + , writeLine + , readLine + , input + , decodeIO + ) + +import Json.Decode exposing (..) + + +{-| Output a string to stdout +-} +port writeLine : String -> Cmd msg + + +{-| Read a line from the stdin +-} +port readLine : String -> Cmd msg + + +{-| Received a response for a command. +-} +port input : (Value -> msg) -> Sub msg + + +type IO + = LineRead (Maybe String) + | LineWritten + + +decodeIO : Decoder IO +decodeIO = + field "tag" string + |> andThen decodeTag + + +decodeTag : String -> Decoder IO +decodeTag tag = + case tag of + "lineRead" -> + field "line" (nullable string) + |> map LineRead + + "lineWritten" -> + succeed LineWritten + + _ -> + fail <| + "Trying to decode IO, but tag " + ++ tag + ++ " is not supported." diff --git a/elm/Makefile b/elm/Makefile index 05b4f13786..cfee1eec08 100644 --- a/elm/Makefile +++ b/elm/Makefile @@ -1,10 +1,10 @@ -SOURCES_BASE = #reader.ls printer.ls env.ls core.ls utils.ls -SOURCES_STEPS = step0_repl.elm step1_read_print.elm step2_eval.elm \ - step3_env.elm # \ - step4_if_fn_do.ls step5_tco.ls step6_file.ls step7_quote.ls \ - step8_macros.ls step9_try.ls stepA_mal.ls +SOURCES_BASE = Reader.elm Printer.elm Utils.elm Types.elm Env.elm \ + Core.elm Eval.elm IO.elm +SOURCES_STEPS = step0_repl.elm step4_if_fn_do.elm #step1_read_print.elm step2_eval.elm \ + step3_env.elm step4_if_fn_do.elm #step5_tco.ls step6_file.ls \ + step7_quote.ls step8_macros.ls step9_try.ls stepA_mal.ls SOURCES_LISP = #env.ls core.ls stepA_mal.ls -SOURCES = $(SOURCES_BASE) $(SOURCES_STEPS) +SOURCES = $(SOURCES_STEPS) BINS = $(SOURCES:%.elm=%.js) @@ -22,9 +22,9 @@ elm_packages: $(ELM) make $(@:%.js=%.elm) --output $@ step1_read_print.js: Reader.elm Printer.elm Utils.elm Types.elm -step2_eval.js: Reader.elm Printer.elm Utils.elm Types.elm -step3_env.js: Reader.elm Printer.elm Utils.elm Types.elm Env.elm -# step4_if_fn_do.js: utils.js reader.js printer.js env.js core.js +#step2_eval.js: Reader.elm Printer.elm Utils.elm Types.elm +#step3_env.js: Reader.elm Printer.elm Utils.elm Types.elm Env.elm +step4_if_fn_do.js: $(SOURCES_BASE) # step5_tco.js: utils.js reader.js printer.js env.js core.js # step6_file.js: utils.js reader.js printer.js env.js core.js # step7_quote.js: utils.js reader.js printer.js env.js core.js diff --git a/elm/Types.elm b/elm/Types.elm index 32c002ccbd..5687ff22d4 100644 --- a/elm/Types.elm +++ b/elm/Types.elm @@ -1,7 +1,42 @@ -module Types exposing (MalExpr(..), keywordPrefix) +module Types exposing (..) import Array exposing (Array) import Dict exposing (Dict) +import IO exposing (IO) + + +type Msg + = Input (Result String IO) + + +type Env + = Env + { outer : Maybe Env + , data : Dict String MalExpr + } + + +type alias EvalState = + { env : Env + } + + +type EvalResult res + = EvalErr String + | EvalOk res + | EvalIO (Cmd Msg) (IO -> Eval res) + + +type alias EvalContext res = + ( EvalState, EvalResult res ) + + +type alias EvalFn res = + EvalState -> EvalContext res + + +type Eval res + = Eval (EvalFn res) type MalExpr @@ -14,7 +49,7 @@ type MalExpr | MalList (List MalExpr) | MalVector (Array MalExpr) | MalMap (Dict String MalExpr) - | MalFunction (List MalExpr -> Result String MalExpr) + | MalFunction (List MalExpr -> Eval MalExpr) {-| Keywords are prefixed by this char for usage in a MalMap. diff --git a/elm/Utils.elm b/elm/Utils.elm index 4097becff0..571e4bbcb8 100644 --- a/elm/Utils.elm +++ b/elm/Utils.elm @@ -6,6 +6,7 @@ module Utils , wrap , maybeToList , zip + , last ) import Regex exposing (replace, regex, HowMany(All)) @@ -85,3 +86,16 @@ zip a b = ( x :: xs, y :: ys ) -> ( x, y ) :: zip xs ys + + +last : List a -> Maybe a +last list = + case list of + [] -> + Nothing + + [ x ] -> + Just x + + x :: xs -> + last xs diff --git a/elm/bootstrap.js b/elm/bootstrap.js index 6ec5f3048c..e91fc5c06f 100644 --- a/elm/bootstrap.js +++ b/elm/bootstrap.js @@ -9,12 +9,13 @@ var app = mod.Main.worker({ args: args.slice(1) }); -// Hook up the output and readLine ports of the app. -app.ports.output.subscribe(function(line) { +// Hook up the writeLine and readLine ports of the app. +app.ports.writeLine.subscribe(function(line) { console.log(line); + app.ports.input.send({"tag": "lineWritten"}); }); app.ports.readLine.subscribe(function(prompt) { var line = readline.readline(prompt); - app.ports.input.send(line); + app.ports.input.send({"tag": "lineRead", "line": line}); }); diff --git a/elm/step0_repl.elm b/elm/step0_repl.elm index b7960c8dda..41596b429a 100644 --- a/elm/step0_repl.elm +++ b/elm/step0_repl.elm @@ -1,16 +1,8 @@ port module Main exposing (..) +import IO exposing (..) +import Json.Decode exposing (decodeValue) import Platform exposing (programWithFlags) -import Json.Decode - - -port output : String -> Cmd msg - - -port readLine : String -> Cmd msg - - -port input : (Maybe String -> msg) -> Sub msg main : Program Flags Model Msg @@ -18,7 +10,8 @@ main = programWithFlags { init = init , update = update - , subscriptions = \model -> input Input + , subscriptions = + \model -> input (decodeValue decodeIO >> Input) } @@ -33,7 +26,7 @@ type alias Model = type Msg - = Input (Maybe String) + = Input (Result String IO) init : Flags -> ( Model, Cmd Msg ) @@ -44,17 +37,18 @@ init flags = update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of - Input (Just line) -> - ( model - , Cmd.batch - [ output (rep line) - , readLine prompt - ] - ) - - Input Nothing -> + Input (Ok (LineRead (Just line))) -> + ( model, writeLine (rep line) ) + + Input (Ok LineWritten) -> + ( model, readLine prompt ) + + Input (Ok (LineRead Nothing)) -> ( model, Cmd.none ) + Input (Err msg) -> + Debug.crash msg ( model, Cmd.none ) + prompt : String prompt = diff --git a/elm/step1_read_print.elm b/elm/step1_read_print.elm index 285d447525..ff7c09a6a8 100644 --- a/elm/step1_read_print.elm +++ b/elm/step1_read_print.elm @@ -1,10 +1,7 @@ port module Main exposing (..) -{-| Your IDE might complain that the Json.Decode import -is not used, but it is. Without it you'll get a runtime exception. --} - -import Json.Decode +import IO exposing (..) +import Json.Decode exposing (decodeValue) import Platform exposing (programWithFlags) import Types exposing (MalExpr(..)) import Reader exposing (readString) @@ -12,27 +9,13 @@ import Printer exposing (printString) import Utils exposing (maybeToList) -{-| Output a string to stdout --} -port output : String -> Cmd msg - - -{-| Read a line from the stdin --} -port readLine : String -> Cmd msg - - -{-| Received a line from the stdin (in response to readLine). --} -port input : (Maybe String -> msg) -> Sub msg - - main : Program Flags Model Msg main = programWithFlags { init = init , update = update - , subscriptions = \model -> input Input + , subscriptions = + \model -> input (decodeValue decodeIO >> Input) } @@ -47,7 +30,7 @@ type alias Model = type Msg - = Input (Maybe String) + = Input (Result String IO) init : Flags -> ( Model, Cmd Msg ) @@ -58,22 +41,23 @@ init flags = update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of - Input (Just line) -> - let - outputCmd = - rep line |> Maybe.map output - - -- Don't print output when 'rep' returns Nothing. - cmds = - maybeToList outputCmd ++ [ readLine prompt ] - in - ( model - , Cmd.batch cmds - ) - - Input Nothing -> + Input (Ok (LineRead (Just line))) -> + case rep line of + Just out -> + ( model, writeLine out ) + + Nothing -> + ( model, readLine prompt ) + + Input (Ok LineWritten) -> + ( model, readLine prompt ) + + Input (Ok (LineRead Nothing)) -> ( model, Cmd.none ) + Input (Err msg) -> + Debug.crash msg ( model, Cmd.none ) + prompt : String prompt = diff --git a/elm/step2_eval.elm b/elm/step2_eval.elm index b12a93f81f..25d83fd7c5 100644 --- a/elm/step2_eval.elm +++ b/elm/step2_eval.elm @@ -1,10 +1,7 @@ port module Main exposing (..) -{-| Your IDE might complain that the Json.Decode import -is not used, but it is. Without it you'll get a runtime exception. --} - -import Json.Decode +import IO exposing (..) +import Json.Decode exposing (decodeValue) import Platform exposing (programWithFlags) import Types exposing (MalExpr(..)) import Reader exposing (readString) @@ -15,27 +12,13 @@ import Tuple exposing (mapFirst) import Array -{-| Output a string to stdout --} -port output : String -> Cmd msg - - -{-| Read a line from the stdin --} -port readLine : String -> Cmd msg - - -{-| Received a line from the stdin (in response to readLine). --} -port input : (Maybe String -> msg) -> Sub msg - - main : Program Flags Model Msg main = programWithFlags { init = init , update = update - , subscriptions = \model -> input Input + , subscriptions = + \model -> input (decodeValue decodeIO >> Input) } @@ -55,7 +38,7 @@ type alias Model = type Msg - = Input (Maybe String) + = Input (Result String IO) init : Flags -> ( Model, Cmd Msg ) @@ -85,32 +68,32 @@ initReplEnv = update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of - Input (Just line) -> + Input (Ok (LineRead (Just line))) -> case rep model.env line of Nothing -> ( model, readLine prompt ) Just ( result, newEnv ) -> - ( { model | env = newEnv } - , Cmd.batch - [ makeOutput result - , readLine prompt - ] - ) + ( { model | env = newEnv }, writeLine (makeOutput result) ) - Input Nothing -> + Input (Ok LineWritten) -> + ( model, readLine prompt ) + + Input (Ok (LineRead Nothing)) -> ( model, Cmd.none ) + Input (Err msg) -> + Debug.crash msg ( model, Cmd.none ) + -makeOutput : Result String String -> Cmd msg +makeOutput : Result String String -> String makeOutput result = - output <| - case result of - Ok str -> - str + case result of + Ok str -> + str - Err msg -> - "ERR:" ++ msg + Err msg -> + "ERR:" ++ msg prompt : String diff --git a/elm/step3_env.elm b/elm/step3_env.elm index 99958dc097..1f5c44ba5f 100644 --- a/elm/step3_env.elm +++ b/elm/step3_env.elm @@ -1,10 +1,7 @@ port module Main exposing (..) -{-| Your IDE might complain that the Json.Decode import -is not used, but it is. Without it you'll get a runtime exception. --} - -import Json.Decode +import IO exposing (..) +import Json.Decode exposing (decodeValue) import Platform exposing (programWithFlags) import Types exposing (MalExpr(..)) import Reader exposing (readString) @@ -16,27 +13,12 @@ import Array import Env exposing (Env) -{-| Output a string to stdout --} -port output : String -> Cmd msg - - -{-| Read a line from the stdin --} -port readLine : String -> Cmd msg - - -{-| Received a line from the stdin (in response to readLine). --} -port input : (Maybe String -> msg) -> Sub msg - - main : Program Flags Model Msg main = programWithFlags { init = init , update = update - , subscriptions = \model -> input Input + , subscriptions = \model -> input (decodeValue decodeIO >> Input) } @@ -52,7 +34,7 @@ type alias Model = type Msg - = Input (Maybe String) + = Input (Result String IO) init : Flags -> ( Model, Cmd Msg ) @@ -81,32 +63,32 @@ initReplEnv = update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of - Input (Just line) -> + Input (Ok (LineRead (Just line))) -> case rep model.env line of Nothing -> ( model, readLine prompt ) Just ( result, newEnv ) -> - ( { model | env = newEnv } - , Cmd.batch - [ makeOutput result - , readLine prompt - ] - ) + ( { model | env = newEnv }, writeLine (makeOutput result) ) - Input Nothing -> + Input (Ok LineWritten) -> + ( model, readLine prompt ) + + Input (Ok (LineRead Nothing)) -> ( model, Cmd.none ) + Input (Err msg) -> + Debug.crash msg ( model, Cmd.none ) + -makeOutput : Result String String -> Cmd msg +makeOutput : Result String String -> String makeOutput result = - output <| - case result of - Ok str -> - str + case result of + Ok str -> + str - Err msg -> - "ERR:" ++ msg + Err msg -> + "ERR:" ++ msg prompt : String diff --git a/elm/step4_if_fn_do.elm b/elm/step4_if_fn_do.elm new file mode 100644 index 0000000000..db67ae16c0 --- /dev/null +++ b/elm/step4_if_fn_do.elm @@ -0,0 +1,395 @@ +port module Main exposing (..) + +import Array +import Dict exposing (Dict) +import IO exposing (..) +import Json.Decode exposing (decodeValue) +import Platform exposing (programWithFlags) +import Types exposing (..) +import Reader exposing (readString) +import Printer exposing (printString) +import Utils exposing (maybeToList, zip, last) +import Env +import Core +import Eval + + +main : Program Flags Model Msg +main = + programWithFlags + { init = init + , update = update + , subscriptions = + \model -> input (decodeValue decodeIO >> Input) + } + + +type alias Flags = + { args : List String + } + + +type alias Model = + { args : List String + , env : Env + , cont : Maybe (IO -> Eval MalExpr) + } + + +init : Flags -> ( Model, Cmd Msg ) +init { args } = + ( { args = args + , env = Core.ns + , cont = Nothing + } + , readLine prompt + ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case model.cont of + Nothing -> + normalUpdate msg model + + Just cont -> + case msg of + Input (Ok io) -> + run { model | cont = Nothing } (cont io) + + Input (Err msg) -> + Debug.crash msg ( model, Cmd.none ) + + +normalUpdate : Msg -> Model -> ( Model, Cmd Msg ) +normalUpdate msg model = + case msg of + Input (Ok (LineRead (Just line))) -> + rep line + |> Maybe.map (run model) + |> Maybe.withDefault (( model, readLine prompt )) + + Input (Ok LineWritten) -> + ( model, readLine prompt ) + + Input (Ok (LineRead Nothing)) -> + ( model, Cmd.none ) + + Input (Err msg) -> + Debug.crash msg ( model, Cmd.none ) + + +run : Model -> Eval MalExpr -> ( Model, Cmd Msg ) +run model e = + case Eval.run { env = model.env } e of + ( { env }, EvalOk expr ) -> + ( { model | env = env }, writeLine (print expr) ) + + ( { env }, EvalErr msg ) -> + ( { model | env = env }, writeLine ("ERR:" ++ msg) ) + + ( { env }, EvalIO cmd cont ) -> + ( { model | cont = Just cont }, cmd ) + + +makeOutput : Result String String -> String +makeOutput result = + case result of + Ok str -> + str + + Err msg -> + "ERR:" ++ msg + + +prompt : String +prompt = + "user> " + + +{-| read can return three things: + +Ok (Just expr) -> parsed okay +Ok Nothing -> empty string (only whitespace and/or comments) +Err msg -> parse error + +-} +read : String -> Result String (Maybe MalExpr) +read = + readString + + +eval : MalExpr -> Eval MalExpr +eval ast = + case ast of + MalList [] -> + Eval.succeed ast + + MalList ((MalSymbol "def!") :: args) -> + evalDef args + + MalList ((MalSymbol "let*") :: args) -> + evalLet args + + MalList ((MalSymbol "do") :: args) -> + evalDo args + + MalList ((MalSymbol "if") :: args) -> + evalIf args + + MalList ((MalSymbol "fn*") :: args) -> + evalFn args + + MalList list -> + evalList list + |> Eval.andThen + (\newList -> + case newList of + [] -> + Eval.fail "can't happen" + + (MalFunction fn) :: args -> + fn args + + fn :: _ -> + Eval.fail ((printString True fn) ++ " is not a function") + ) + + _ -> + evalAst ast + + +evalAst : MalExpr -> Eval MalExpr +evalAst ast = + case ast of + MalSymbol sym -> + -- Lookup symbol in env and return value or raise error if not found. + Eval.withState + (\state -> + case Env.get sym state.env of + Ok val -> + Eval.succeed val + + Err msg -> + Eval.fail msg + ) + + MalList list -> + -- Return new list that is result of calling eval on each element of list. + evalList list + |> Eval.map MalList + + MalVector vec -> + evalList (Array.toList vec) + |> Eval.map (Array.fromList >> MalVector) + + MalMap map -> + evalList (Dict.values map) + |> Eval.map + (zip (Dict.keys map) + >> Dict.fromList + >> MalMap + ) + + _ -> + Eval.succeed ast + + +evalList : List MalExpr -> Eval (List MalExpr) +evalList list = + let + go list acc = + case list of + [] -> + Eval.succeed (List.reverse acc) + + x :: rest -> + eval x + |> Eval.andThen + (\val -> + go rest (val :: acc) + ) + in + go list [] + + +evalDef : List MalExpr -> Eval MalExpr +evalDef args = + case args of + [ MalSymbol name, uneValue ] -> + eval uneValue + |> Eval.andThen + (\value -> + Eval.modifyState + (\state -> + { state | env = Env.set name value state.env } + ) + |> Eval.andThen (\_ -> Eval.succeed value) + ) + + _ -> + Eval.fail "def! expected two args: name and value" + + +evalLet : List MalExpr -> Eval MalExpr +evalLet args = + let + evalBinds binds = + case binds of + (MalSymbol name) :: expr :: rest -> + eval expr + |> Eval.andThen + (\value -> + Eval.modifyState (\state -> { state | env = Env.set name value state.env }) + |> Eval.andThen + (\_ -> + if List.isEmpty rest then + Eval.succeed () + else + evalBinds rest + ) + ) + + _ -> + Eval.fail "let* expected an even number of binds (symbol expr ..)" + + go binds body = + Eval.modifyState (\state -> { state | env = Env.make (Just state.env) }) + |> Eval.andThen (\_ -> evalBinds binds) + |> Eval.andThen (\_ -> eval body) + in + case args of + [ MalList binds, body ] -> + go binds body + + [ MalVector bindsVec, body ] -> + go (Array.toList bindsVec) body + + _ -> + Eval.fail "let* expected two args: binds and a body" + + +evalDo : List MalExpr -> Eval MalExpr +evalDo args = + let + returnLast list = + case last list of + Just value -> + Eval.succeed value + + Nothing -> + Eval.fail "do expected at least one arg" + in + evalList args + |> Eval.andThen returnLast + + +evalIf : List MalExpr -> Eval MalExpr +evalIf args = + let + isThruthy expr = + expr /= MalNil && expr /= (MalBool False) + + go condition trueExpr falseExpr = + eval condition + |> Eval.map isThruthy + |> Eval.andThen + (\cond -> + eval + (if cond then + trueExpr + else + falseExpr + ) + ) + in + case args of + [ condition, trueExpr ] -> + go condition trueExpr MalNil + + [ condition, trueExpr, falseExpr ] -> + go condition trueExpr falseExpr + + _ -> + Eval.fail "if expected at least two args" + + +evalFn : List MalExpr -> Eval MalExpr +evalFn args = + let + extractSymbols list acc = + case list of + [] -> + Ok (List.reverse acc) + + (MalSymbol name) :: rest -> + extractSymbols rest (name :: acc) + + _ -> + Err "all binds in fn* must be a symbol" + + bindArgs env pairs = + case pairs of + [] -> + env + + ( bind, arg ) :: rest -> + bindArgs (Env.set bind arg env) rest + + makeEnv binds args env = + zip binds args + |> bindArgs (Env.make (Just env)) + in + case args of + [ MalList bindsList, body ] -> + case extractSymbols bindsList [] of + Ok binds -> + let + fn args = + if List.length args /= List.length binds then + Eval.fail <| + "function expected " + ++ (toString (List.length binds)) + ++ " arguments, got " + ++ (toString (List.length binds)) + else + -- TODO: push state and pop afterwards! + -- TODO or temporary change state? + Eval.withState + (\state -> + Eval.putState ({ state | env = makeEnv binds args state.env }) + |> Eval.andThen (\_ -> eval body) + |> Eval.andThen (\res -> Eval.putState state |> Eval.map (\_ -> res)) + ) + in + Eval.succeed (MalFunction fn) + + -- TODO explicitly pass current env + Err msg -> + Eval.fail msg + + _ -> + Eval.fail "fn* expected two args: binds list and body" + + +print : MalExpr -> String +print = + printString True + + +{-| Read-Eval-Print. + +Doesn't actually run the Eval but returns the monad. + +-} +rep : String -> Maybe (Eval MalExpr) +rep input = + case readString input of + Ok Nothing -> + Nothing + + Err msg -> + Just (Eval.fail msg) + + Ok (Just ast) -> + eval ast |> Just From 86fcd61dfaebb95d93a5c5396cc57004103b411b Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sun, 11 Jun 2017 22:44:49 +0200 Subject: [PATCH 0341/2308] Elm: step 4 - fns defined in mal + var args --- elm/Core.elm | 13 +- elm/Env.elm | 184 +++++++++++++++++++++++++--- elm/Eval.elm | 20 ++-- elm/Types.elm | 20 ++-- elm/Utils.elm | 14 +++ elm/step4_if_fn_do.elm | 263 +++++++++++++++++++++++++++-------------- 6 files changed, 386 insertions(+), 128 deletions(-) diff --git a/elm/Core.elm b/elm/Core.elm index b24c3549b1..491b6b48a2 100644 --- a/elm/Core.elm +++ b/elm/Core.elm @@ -47,6 +47,9 @@ ns = {- count -} count args = case args of + [ MalNil ] -> + Eval.succeed (MalInt 0) + [ MalList list ] -> Eval.succeed <| MalInt (List.length list) @@ -101,7 +104,7 @@ ns = >> String.join " " >> writeLine in - Env.make Nothing + Env.global |> Env.set "+" (MalFunction <| binaryOp (+) MalInt) |> Env.set "-" (MalFunction <| binaryOp (-) MalInt) |> Env.set "*" (MalFunction <| binaryOp (*) MalInt) @@ -119,3 +122,11 @@ ns = |> Env.set "str" (MalFunction str) |> Env.set "prn" (MalFunction prn) |> Env.set "println" (MalFunction println) + + +malInit : List String +malInit = + [ """(def! not + (fn* (a) + (if a false true)))""" + ] diff --git a/elm/Env.elm b/elm/Env.elm index 6240c837dc..c67c2a7bed 100644 --- a/elm/Env.elm +++ b/elm/Env.elm @@ -1,26 +1,178 @@ -module Env exposing (make, set, get) +module Env exposing (global, push, pop, enter, leave, ref, get, set) -import Types exposing (MalExpr(..), Env(..)) +import Types exposing (MalExpr, Frame, Env) import Dict exposing (Dict) -make : Maybe Env -> Env -make outer = - Env { outer = outer, data = Dict.empty } +global : Env +global = + { frames = Dict.singleton 0 (emptyFrame Nothing) + , nextFrameId = 1 + , currentFrameId = 0 + } + + +push : Env -> Env +push env = + let + frameId = + env.nextFrameId + + newFrame = + emptyFrame (Just env.currentFrameId) + in + { currentFrameId = frameId + , frames = Dict.insert frameId newFrame env.frames + , nextFrameId = env.nextFrameId + 1 + } + + + +-- TODO Dont' return result, Debug.crash instead. + + +pop : Env -> Result String Env +pop env = + let + frameId = + env.currentFrameId + in + case Dict.get frameId env.frames of + Just currentFrame -> + case currentFrame.outerId of + Just outerId -> + Ok + { env + | currentFrameId = outerId + , frames = Dict.update frameId deref env.frames + } + + Nothing -> + Err "tried to pop global frame" + + Nothing -> + Err ("current frame " ++ (toString frameId) ++ " doesn't exist") + + +setBinds : List ( String, MalExpr ) -> Frame -> Frame +setBinds binds frame = + case binds of + [] -> + frame + + ( name, expr ) :: rest -> + setBinds rest + { frame | data = Dict.insert name expr frame.data } + + +enter : Int -> List ( String, MalExpr ) -> Env -> Env +enter parentFrameId binds env = + let + frameId = + env.nextFrameId + + newFrame = + setBinds binds (emptyFrame (Just parentFrameId)) + in + { currentFrameId = frameId + , frames = Dict.insert frameId newFrame env.frames + , nextFrameId = env.nextFrameId + 1 + } + + +leave : Int -> Env -> Env +leave orgFrameId env = + let + frameId = + env.currentFrameId + in + { env + | currentFrameId = orgFrameId + , frames = Dict.update frameId deref env.frames + } + + +{-| Increase refCnt for the current frame +-} +ref : Env -> Env +ref env = + let + incRef = + Maybe.map + (\frame -> + { frame | refCnt = frame.refCnt + 1 } + ) + + newFrames = + Dict.update env.currentFrameId incRef env.frames + in + { env | frames = newFrames } + + + +-- TODO: when disposing, deref all function's frames? +-- TODO: is that enough instead of a GC? + + +deref : Maybe Frame -> Maybe Frame +deref = + Maybe.andThen + (\frame -> + if frame.refCnt == 1 then + Nothing + else + Just { frame | refCnt = frame.refCnt - 1 } + ) + + + +-- TODO need a GC. +-- given a Env, see which frames are not reachable. +-- in MalFunction need to refer to the frameId. + + +emptyFrame : Maybe Int -> Frame +emptyFrame outerId = + { outerId = outerId + , data = Dict.empty + , refCnt = 1 + } set : String -> MalExpr -> Env -> Env -set name expr (Env env) = - Env { env | data = Dict.insert name expr env.data } +set name expr env = + let + updateFrame = + Maybe.map + (\frame -> + { frame | data = Dict.insert name expr frame.data } + ) + + frameId = + env.currentFrameId + + newFrames = + Dict.update frameId updateFrame env.frames + in + { env | frames = newFrames } get : String -> Env -> Result String MalExpr -get name ((Env { outer, data }) as env) = - case Dict.get name data of - Just val -> - Ok val - - Nothing -> - outer - |> Maybe.map (get name) - |> Maybe.withDefault (Err "symbol not found") +get name env = + let + go frameId = + case Dict.get frameId env.frames of + Nothing -> + Err "frame not found" + + Just frame -> + case Dict.get name frame.data of + Just value -> + Ok value + + Nothing -> + frame.outerId + |> Maybe.map go + |> Maybe.withDefault (Err "symbol not found") + in + go env.currentFrameId diff --git a/elm/Eval.elm b/elm/Eval.elm index 8ade773a1f..c4e043700b 100644 --- a/elm/Eval.elm +++ b/elm/Eval.elm @@ -4,32 +4,32 @@ import Types exposing (..) import IO exposing (IO) -apply : Eval a -> EvalState -> EvalContext a +apply : Eval a -> Env -> EvalContext a apply (Eval f) state = f state -run : EvalState -> Eval a -> EvalContext a +run : Env -> Eval a -> EvalContext a run state e = apply e state -withState : (EvalState -> Eval a) -> Eval a -withState f = +withEnv : (Env -> Eval a) -> Eval a +withEnv f = Eval <| \state -> apply (f state) state -putState : EvalState -> Eval () -putState state = +setEnv : Env -> Eval () +setEnv state = Eval <| \_ -> apply (succeed ()) state -modifyState : (EvalState -> EvalState) -> Eval () -modifyState f = +modifyEnv : (Env -> Env) -> Eval () +modifyEnv f = Eval <| \state -> apply (succeed ()) (f state) @@ -79,10 +79,6 @@ andThen f e = ( state, EvalIO cmd (cont >> andThen f) ) - --- Debug.log "wrapping EvalIO" ( state, EvalIO cmd cont ) - - fail : String -> Eval a fail msg = Eval <| diff --git a/elm/Types.elm b/elm/Types.elm index 5687ff22d4..04b78e5166 100644 --- a/elm/Types.elm +++ b/elm/Types.elm @@ -9,15 +9,17 @@ type Msg = Input (Result String IO) -type Env - = Env - { outer : Maybe Env - , data : Dict String MalExpr - } +type alias Frame = + { outerId : Maybe Int + , data : Dict String MalExpr + , refCnt : Int + } -type alias EvalState = - { env : Env +type alias Env = + { frames : Dict Int Frame + , nextFrameId : Int + , currentFrameId : Int } @@ -28,11 +30,11 @@ type EvalResult res type alias EvalContext res = - ( EvalState, EvalResult res ) + ( Env, EvalResult res ) type alias EvalFn res = - EvalState -> EvalContext res + Env -> EvalContext res type Eval res diff --git a/elm/Utils.elm b/elm/Utils.elm index 571e4bbcb8..bd226d4dc6 100644 --- a/elm/Utils.elm +++ b/elm/Utils.elm @@ -7,6 +7,7 @@ module Utils , maybeToList , zip , last + , justValues ) import Regex exposing (replace, regex, HowMany(All)) @@ -99,3 +100,16 @@ last list = x :: xs -> last xs + + +justValues : List (Maybe a) -> List a +justValues list = + case list of + [] -> + [] + + (Just x) :: rest -> + x :: (justValues rest) + + Nothing :: rest -> + justValues rest diff --git a/elm/step4_if_fn_do.elm b/elm/step4_if_fn_do.elm index db67ae16c0..35de9ca653 100644 --- a/elm/step4_if_fn_do.elm +++ b/elm/step4_if_fn_do.elm @@ -8,7 +8,7 @@ import Platform exposing (programWithFlags) import Types exposing (..) import Reader exposing (readString) import Printer exposing (printString) -import Utils exposing (maybeToList, zip, last) +import Utils exposing (maybeToList, zip, last, justValues) import Env import Core import Eval @@ -29,77 +29,101 @@ type alias Flags = } -type alias Model = - { args : List String - , env : Env - , cont : Maybe (IO -> Eval MalExpr) - } +type Model + = InitIO Env (IO -> Eval MalExpr) + | InitError String + | ReplActive Env + | ReplIO Env (IO -> Eval MalExpr) init : Flags -> ( Model, Cmd Msg ) init { args } = - ( { args = args - , env = Core.ns - , cont = Nothing - } - , readLine prompt - ) + let + initEnv = + Core.ns + + evalMalInit = + Core.malInit + |> List.map rep + |> justValues + |> List.foldl + (\b a -> a |> Eval.andThen (\_ -> b)) + (Eval.succeed MalNil) + in + runInit initEnv evalMalInit update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = - case model.cont of - Nothing -> - normalUpdate msg model + case model of + InitError _ -> + -- ignore all + ( model, Cmd.none ) - Just cont -> + InitIO env cont -> case msg of Input (Ok io) -> - run { model | cont = Nothing } (cont io) + runInit env (cont io) Input (Err msg) -> Debug.crash msg ( model, Cmd.none ) + ReplActive env -> + case msg of + Input (Ok (LineRead (Just line))) -> + case rep line of + Just expr -> + run env expr -normalUpdate : Msg -> Model -> ( Model, Cmd Msg ) -normalUpdate msg model = - case msg of - Input (Ok (LineRead (Just line))) -> - rep line - |> Maybe.map (run model) - |> Maybe.withDefault (( model, readLine prompt )) + Nothing -> + ( model, readLine prompt ) - Input (Ok LineWritten) -> - ( model, readLine prompt ) + Input (Ok LineWritten) -> + ( model, readLine prompt ) - Input (Ok (LineRead Nothing)) -> - ( model, Cmd.none ) + Input (Ok (LineRead Nothing)) -> + -- Ctrl+D = The End. + ( model, Cmd.none ) - Input (Err msg) -> - Debug.crash msg ( model, Cmd.none ) + Input (Err msg) -> + Debug.crash msg ( model, Cmd.none ) + ReplIO env cont -> + case msg of + Input (Ok io) -> + run env (cont io) -run : Model -> Eval MalExpr -> ( Model, Cmd Msg ) -run model e = - case Eval.run { env = model.env } e of - ( { env }, EvalOk expr ) -> - ( { model | env = env }, writeLine (print expr) ) + Input (Err msg) -> + Debug.crash msg ( model, Cmd.none ) - ( { env }, EvalErr msg ) -> - ( { model | env = env }, writeLine ("ERR:" ++ msg) ) - ( { env }, EvalIO cmd cont ) -> - ( { model | cont = Just cont }, cmd ) +runInit : Env -> Eval MalExpr -> ( Model, Cmd Msg ) +runInit env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + -- Init went okay, start REPL. + ( ReplActive env, readLine prompt ) + ( env, EvalErr msg ) -> + -- Init failed, don't start REPL. + ( InitError msg, writeLine ("ERR:" ++ msg) ) -makeOutput : Result String String -> String -makeOutput result = - case result of - Ok str -> - str + ( env, EvalIO cmd cont ) -> + -- IO in init. + ( InitIO env cont, cmd ) - Err msg -> - "ERR:" ++ msg + +run : Env -> Eval MalExpr -> ( Model, Cmd Msg ) +run env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + ( ReplActive env, writeLine (print expr) ) + + ( env, EvalErr msg ) -> + ( ReplActive env, writeLine ("ERR:" ++ msg) ) + + ( env, EvalIO cmd cont ) -> + ( ReplIO env cont, cmd ) prompt : String @@ -164,9 +188,9 @@ evalAst ast = case ast of MalSymbol sym -> -- Lookup symbol in env and return value or raise error if not found. - Eval.withState - (\state -> - case Env.get sym state.env of + Eval.withEnv + (\env -> + case Env.get sym env of Ok val -> Eval.succeed val @@ -220,10 +244,7 @@ evalDef args = eval uneValue |> Eval.andThen (\value -> - Eval.modifyState - (\state -> - { state | env = Env.set name value state.env } - ) + Eval.modifyEnv (Env.set name value) |> Eval.andThen (\_ -> Eval.succeed value) ) @@ -240,7 +261,7 @@ evalLet args = eval expr |> Eval.andThen (\value -> - Eval.modifyState (\state -> { state | env = Env.set name value state.env }) + Eval.modifyEnv (Env.set name value) |> Eval.andThen (\_ -> if List.isEmpty rest then @@ -254,9 +275,21 @@ evalLet args = Eval.fail "let* expected an even number of binds (symbol expr ..)" go binds body = - Eval.modifyState (\state -> { state | env = Env.make (Just state.env) }) + Eval.modifyEnv Env.push |> Eval.andThen (\_ -> evalBinds binds) |> Eval.andThen (\_ -> eval body) + |> Eval.andThen + (\res -> + Eval.withEnv + (\env -> + case Env.pop env of + Ok env -> + Eval.setEnv env |> Eval.map (\_ -> res) + + Err msg -> + Eval.fail msg + ) + ) in case args of [ MalList binds, body ] -> @@ -317,56 +350,106 @@ evalIf args = evalFn : List MalExpr -> Eval MalExpr evalFn args = let - extractSymbols list acc = + {- Extract symbols from the binds list and verify their uniqueness -} + extractSymbols acc list = case list of [] -> Ok (List.reverse acc) (MalSymbol name) :: rest -> - extractSymbols rest (name :: acc) + if List.member name acc then + Err "all binds must have unique names" + else + extractSymbols (name :: acc) rest _ -> Err "all binds in fn* must be a symbol" - bindArgs env pairs = - case pairs of - [] -> - env + parseBinds list = + case List.reverse list of + var :: "&" :: rest -> + Ok <| bindVarArgs (List.reverse rest) var - ( bind, arg ) :: rest -> - bindArgs (Env.set bind arg env) rest + _ -> + if List.member "&" list then + Err "varargs separator '&' is used incorrectly" + else + Ok <| bindArgs list + + extractAndParse = + extractSymbols [] >> Result.andThen parseBinds + + bindArgs binds args = + let + numBinds = + List.length binds + in + if List.length args /= numBinds then + Err <| + "function expected " + ++ (toString numBinds) + ++ " arguments" + else + Ok <| zip binds args + + bindVarArgs binds var args = + let + minArgs = + List.length binds + + varArgs = + MalList (List.drop minArgs args) + in + if List.length args < minArgs then + Err <| + "function expected at least " + ++ (toString minArgs) + ++ " arguments" + else + Ok <| zip binds args ++ [ ( var, varArgs ) ] + + makeFn frameId binder body args = + case binder args of + Ok bound -> + Eval.withEnv + (\env -> + Eval.modifyEnv (Env.enter frameId bound) + |> Eval.andThen (\_ -> eval body) + |> Eval.andThen + (\res -> + Eval.modifyEnv (Env.leave env.currentFrameId) + |> Eval.map (\_ -> res) + ) + ) + + Err msg -> + Eval.fail msg + + go bindsList body = + case extractAndParse bindsList of + Ok binder -> + Eval.modifyEnv Env.ref + -- reference the current frame. + |> Eval.andThen + (\_ -> + Eval.withEnv + (\env -> + Eval.succeed + (MalFunction + (makeFn env.currentFrameId binder body) + ) + ) + ) - makeEnv binds args env = - zip binds args - |> bindArgs (Env.make (Just env)) + Err msg -> + Eval.fail msg in case args of [ MalList bindsList, body ] -> - case extractSymbols bindsList [] of - Ok binds -> - let - fn args = - if List.length args /= List.length binds then - Eval.fail <| - "function expected " - ++ (toString (List.length binds)) - ++ " arguments, got " - ++ (toString (List.length binds)) - else - -- TODO: push state and pop afterwards! - -- TODO or temporary change state? - Eval.withState - (\state -> - Eval.putState ({ state | env = makeEnv binds args state.env }) - |> Eval.andThen (\_ -> eval body) - |> Eval.andThen (\res -> Eval.putState state |> Eval.map (\_ -> res)) - ) - in - Eval.succeed (MalFunction fn) + go bindsList body - -- TODO explicitly pass current env - Err msg -> - Eval.fail msg + [ MalVector bindsVec, body ] -> + go (Array.toList bindsVec) body _ -> Eval.fail "fn* expected two args: binds list and body" From 0bac0757af4f15c8bc07f8ef04b0875fdd206478 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Wed, 14 Jun 2017 15:49:27 +0200 Subject: [PATCH 0342/2308] Elm: step5 - TCO the theory --- elm/Core.elm | 165 ++++++++++++-- elm/Env.elm | 172 +++++++++++--- elm/Eval.elm | 16 ++ elm/IO.elm | 16 ++ elm/Makefile | 6 +- elm/Printer.elm | 41 +++- elm/Types.elm | 27 ++- elm/bootstrap.js | 11 + elm/step4_if_fn_do.elm | 109 +++++---- elm/step5_tco.elm | 505 +++++++++++++++++++++++++++++++++++++++++ 10 files changed, 959 insertions(+), 109 deletions(-) create mode 100644 elm/step5_tco.elm diff --git a/elm/Core.elm b/elm/Core.elm index 491b6b48a2..06362e6deb 100644 --- a/elm/Core.elm +++ b/elm/Core.elm @@ -1,16 +1,20 @@ module Core exposing (..) -import Types exposing (MalExpr(..), Eval, Env) +import Types exposing (MalExpr(..), MalFunction(..), Eval, Env) import Env import Eval import Printer exposing (printString) import Array import IO exposing (IO(..)) +import Reader ns : Env ns = let + makeFn = + CoreFunc >> MalFunction + binaryOp fn retType args = case args of [ MalInt x, MalInt y ] -> @@ -82,12 +86,12 @@ ns = >> MalString >> Eval.succeed + {- helper function to write a string to stdout -} writeLine str = Eval.io (IO.writeLine str) (\msg -> case msg of LineWritten -> - -- TODO need caller continuation here... Eval.succeed MalNil _ -> @@ -103,25 +107,148 @@ ns = List.map (printString False) >> String.join " " >> writeLine + + printEnv args = + case args of + [] -> + Eval.withEnv (Printer.printEnv >> writeLine) + + _ -> + Eval.fail "unsupported arguments" + + readString args = + case args of + [ MalString str ] -> + case Reader.readString str of + Ok Nothing -> + Eval.succeed MalNil + + Ok (Just ast) -> + Eval.succeed ast + + Err msg -> + Eval.fail msg + + _ -> + Eval.fail "unsupported arguments" + + slurp args = + case args of + [ MalString filename ] -> + Eval.io (IO.readFile filename) + (\msg -> + case msg of + FileRead contents -> + Eval.succeed <| MalString contents + + Exception msg -> + Eval.fail msg + + _ -> + Eval.fail "wrong IO, expected FileRead" + ) + + _ -> + Eval.fail "unsupported arguments" + + atom args = + case args of + [ value ] -> + Eval.withEnv + (\env -> + case Env.newAtom value env of + ( newEnv, atomId ) -> + Eval.setEnv newEnv + |> Eval.map (\_ -> MalAtom atomId) + ) + + _ -> + Eval.fail "unsupported arguments" + + isAtom args = + case args of + [ MalAtom _ ] -> + Eval.succeed <| MalBool True + + _ -> + Eval.succeed <| MalBool False + + deref args = + case args of + [ MalAtom atomId ] -> + Eval.withEnv (Env.getAtom atomId >> Eval.succeed) + + _ -> + Eval.fail "unsupported arguments" + + reset args = + case args of + [ MalAtom atomId, value ] -> + Eval.modifyEnv (Env.setAtom atomId value) + |> Eval.map (always value) + + _ -> + Eval.fail "unsupported arguments" + + {- helper function for calling a core or user function -} + callFn func args = + case func of + CoreFunc fn -> + fn args + + UserFunc { fn } -> + fn args + + swap args = + case args of + (MalAtom atomId) :: (MalFunction func) :: args -> + Eval.withEnv + (\env -> + let + value = + Env.getAtom atomId env + in + callFn func (value :: args) + ) + |> Eval.andThen + (\res -> + Eval.modifyEnv (Env.setAtom atomId res) + |> Eval.map (always res) + ) + + _ -> + Eval.fail "unsupported arguments" + + gc args = + Eval.withEnv (Env.gc >> Printer.printEnv >> writeLine) in Env.global - |> Env.set "+" (MalFunction <| binaryOp (+) MalInt) - |> Env.set "-" (MalFunction <| binaryOp (-) MalInt) - |> Env.set "*" (MalFunction <| binaryOp (*) MalInt) - |> Env.set "/" (MalFunction <| binaryOp (//) MalInt) - |> Env.set "<" (MalFunction <| binaryOp (<) MalBool) - |> Env.set ">" (MalFunction <| binaryOp (>) MalBool) - |> Env.set "<=" (MalFunction <| binaryOp (<=) MalBool) - |> Env.set ">=" (MalFunction <| binaryOp (>=) MalBool) - |> Env.set "list" (MalFunction list) - |> Env.set "list?" (MalFunction isList) - |> Env.set "empty?" (MalFunction isEmpty) - |> Env.set "count" (MalFunction count) - |> Env.set "=" (MalFunction equals) - |> Env.set "pr-str" (MalFunction prStr) - |> Env.set "str" (MalFunction str) - |> Env.set "prn" (MalFunction prn) - |> Env.set "println" (MalFunction println) + |> Env.set "+" (makeFn <| binaryOp (+) MalInt) + |> Env.set "-" (makeFn <| binaryOp (-) MalInt) + |> Env.set "*" (makeFn <| binaryOp (*) MalInt) + |> Env.set "/" (makeFn <| binaryOp (//) MalInt) + |> Env.set "<" (makeFn <| binaryOp (<) MalBool) + |> Env.set ">" (makeFn <| binaryOp (>) MalBool) + |> Env.set "<=" (makeFn <| binaryOp (<=) MalBool) + |> Env.set ">=" (makeFn <| binaryOp (>=) MalBool) + |> Env.set "list" (makeFn list) + |> Env.set "list?" (makeFn isList) + |> Env.set "empty?" (makeFn isEmpty) + |> Env.set "count" (makeFn count) + |> Env.set "=" (makeFn equals) + |> Env.set "pr-str" (makeFn prStr) + |> Env.set "str" (makeFn str) + |> Env.set "prn" (makeFn prn) + |> Env.set "println" (makeFn println) + |> Env.set "pr-env" (makeFn printEnv) + |> Env.set "read-string" (makeFn readString) + |> Env.set "slurp" (makeFn slurp) + |> Env.set "atom" (makeFn atom) + |> Env.set "atom?" (makeFn isAtom) + |> Env.set "deref" (makeFn deref) + |> Env.set "reset!" (makeFn reset) + |> Env.set "swap!" (makeFn swap) + |> Env.set "gc" (makeFn gc) malInit : List String diff --git a/elm/Env.elm b/elm/Env.elm index c67c2a7bed..19158e8c35 100644 --- a/elm/Env.elm +++ b/elm/Env.elm @@ -1,14 +1,37 @@ -module Env exposing (global, push, pop, enter, leave, ref, get, set) +module Env + exposing + ( global + , push + , pop + , enter + , leave + , ref + , get + , set + , newAtom + , getAtom + , setAtom + , gc + ) + +import Types exposing (MalExpr(..), MalFunction(..), Frame, Env) +import Dict +import Array +import Set + -import Types exposing (MalExpr, Frame, Env) -import Dict exposing (Dict) +globalFrameId : Int +globalFrameId = + 0 global : Env global = - { frames = Dict.singleton 0 (emptyFrame Nothing) - , nextFrameId = 1 - , currentFrameId = 0 + { frames = Dict.singleton globalFrameId (emptyFrame Nothing) + , nextFrameId = globalFrameId + 1 + , currentFrameId = globalFrameId + , atoms = Dict.empty + , nextAtomId = 0 } @@ -21,17 +44,14 @@ push env = newFrame = emptyFrame (Just env.currentFrameId) in - { currentFrameId = frameId - , frames = Dict.insert frameId newFrame env.frames - , nextFrameId = env.nextFrameId + 1 + { env + | currentFrameId = frameId + , frames = Dict.insert frameId newFrame env.frames + , nextFrameId = env.nextFrameId + 1 } - --- TODO Dont' return result, Debug.crash instead. - - -pop : Env -> Result String Env +pop : Env -> Env pop env = let frameId = @@ -41,17 +61,19 @@ pop env = Just currentFrame -> case currentFrame.outerId of Just outerId -> - Ok - { env - | currentFrameId = outerId - , frames = Dict.update frameId deref env.frames - } + { env + | currentFrameId = outerId + , frames = Dict.update frameId deref env.frames + } - Nothing -> - Err "tried to pop global frame" + _ -> + Debug.crash "tried to pop global frame" Nothing -> - Err ("current frame " ++ (toString frameId) ++ " doesn't exist") + Debug.crash <| + "current frame " + ++ (toString frameId) + ++ " doesn't exist" setBinds : List ( String, MalExpr ) -> Frame -> Frame @@ -69,14 +91,16 @@ enter : Int -> List ( String, MalExpr ) -> Env -> Env enter parentFrameId binds env = let frameId = - env.nextFrameId + Debug.log "enter #" + env.nextFrameId newFrame = setBinds binds (emptyFrame (Just parentFrameId)) in - { currentFrameId = frameId - , frames = Dict.insert frameId newFrame env.frames - , nextFrameId = env.nextFrameId + 1 + { env + | currentFrameId = frameId + , frames = Dict.insert frameId newFrame env.frames + , nextFrameId = env.nextFrameId + 1 } @@ -84,7 +108,8 @@ leave : Int -> Env -> Env leave orgFrameId env = let frameId = - env.currentFrameId + Debug.log "leave #" + env.currentFrameId in { env | currentFrameId = orgFrameId @@ -111,7 +136,8 @@ ref env = -- TODO: when disposing, deref all function's frames? --- TODO: is that enough instead of a GC? +-- TODO: is that enough instead of a GC? no: don't know how often the function is referenced. +-- TODO: consideration: keep refCnt for MalFunction, or implement a light GC. deref : Maybe Frame -> Maybe Frame @@ -131,6 +157,62 @@ deref = -- in MalFunction need to refer to the frameId. +{-| Given an Env see which frames are not reachable from the +global frame. Return a new Env without the unreachable frames. +-} +gc : Env -> Env +gc env = + let + countList acc = + List.foldl countRefs acc + + countFrame acc { data } = + data |> Dict.values |> countList acc + + countRefs expr acc = + Debug.log (toString expr) <| + case expr of + MalFunction (UserFunc { frameId }) -> + if not (Set.member frameId acc) then + Debug.log "counting" <| + case Dict.get frameId env.frames of + Just frame -> + countFrame (Set.insert frameId acc) frame + + Nothing -> + Debug.crash ("frame " ++ (toString frameId) ++ " not found in GC") + else + acc + + MalList list -> + countList acc list + + MalVector vec -> + countList acc (Array.toList vec) + + MalMap map -> + countList acc (Dict.values map) + + _ -> + acc + + initSet = + Set.fromList [ globalFrameId, env.currentFrameId ] + in + case Dict.get globalFrameId env.frames of + Nothing -> + Debug.crash "global frame not found" + + Just globalFrame -> + countFrame initSet globalFrame + |> Set.toList + |> Debug.log "used frames" + |> List.map (\frameId -> ( frameId, emptyFrame Nothing )) + |> Dict.fromList + |> Dict.intersect (Debug.log "cur frames" env.frames) + |> (\frames -> { env | frames = frames }) + + emptyFrame : Maybe Int -> Frame emptyFrame outerId = { outerId = outerId @@ -163,7 +245,7 @@ get name env = go frameId = case Dict.get frameId env.frames of Nothing -> - Err "frame not found" + Err <| "frame " ++ (toString frameId) ++ " not found" Just frame -> case Dict.get name frame.data of @@ -176,3 +258,35 @@ get name env = |> Maybe.withDefault (Err "symbol not found") in go env.currentFrameId + + +newAtom : MalExpr -> Env -> ( Env, Int ) +newAtom value env = + let + atomId = + env.nextAtomId + + newEnv = + { env + | atoms = Dict.insert atomId value env.atoms + , nextAtomId = atomId + 1 + } + in + ( newEnv, atomId ) + + +getAtom : Int -> Env -> MalExpr +getAtom atomId env = + case Dict.get atomId env.atoms of + Just value -> + value + + Nothing -> + Debug.crash <| "atom " ++ (toString atomId) ++ " not found" + + +setAtom : Int -> MalExpr -> Env -> Env +setAtom atomId value env = + { env + | atoms = Dict.insert atomId value env.atoms + } diff --git a/elm/Eval.elm b/elm/Eval.elm index c4e043700b..1756a40afe 100644 --- a/elm/Eval.elm +++ b/elm/Eval.elm @@ -2,6 +2,8 @@ module Eval exposing (..) import Types exposing (..) import IO exposing (IO) +import Env +import Printer apply : Eval a -> Env -> EvalContext a @@ -84,3 +86,17 @@ fail msg = Eval <| \state -> ( state, EvalErr msg ) + + +enter : Int -> List ( String, MalExpr ) -> Eval a -> Eval a +enter frameId bound body = + withEnv + (\env -> + modifyEnv (Env.enter frameId bound) + |> andThen (\_ -> body) + |> andThen + (\res -> + modifyEnv (Env.leave env.currentFrameId) + |> map (\_ -> res) + ) + ) diff --git a/elm/IO.elm b/elm/IO.elm index 5afd3d7d27..48ff9c439c 100644 --- a/elm/IO.elm +++ b/elm/IO.elm @@ -3,6 +3,7 @@ port module IO ( IO(..) , writeLine , readLine + , readFile , input , decodeIO ) @@ -20,6 +21,11 @@ port writeLine : String -> Cmd msg port readLine : String -> Cmd msg +{-| Read the contents of a file +-} +port readFile : String -> Cmd msg + + {-| Received a response for a command. -} port input : (Value -> msg) -> Sub msg @@ -28,6 +34,8 @@ port input : (Value -> msg) -> Sub msg type IO = LineRead (Maybe String) | LineWritten + | FileRead String + | Exception String decodeIO : Decoder IO @@ -46,6 +54,14 @@ decodeTag tag = "lineWritten" -> succeed LineWritten + "fileRead" -> + field "contents" string + |> map FileRead + + "exception" -> + field "message" string + |> map Exception + _ -> fail <| "Trying to decode IO, but tag " diff --git a/elm/Makefile b/elm/Makefile index cfee1eec08..8e0a7a1945 100644 --- a/elm/Makefile +++ b/elm/Makefile @@ -1,7 +1,7 @@ SOURCES_BASE = Reader.elm Printer.elm Utils.elm Types.elm Env.elm \ Core.elm Eval.elm IO.elm -SOURCES_STEPS = step0_repl.elm step4_if_fn_do.elm #step1_read_print.elm step2_eval.elm \ - step3_env.elm step4_if_fn_do.elm #step5_tco.ls step6_file.ls \ +SOURCES_STEPS = step0_repl.elm step4_if_fn_do.elm step5_tco.elm #step1_read_print.elm step2_eval.elm \ + step3_env.elm #step6_file.ls \ step7_quote.ls step8_macros.ls step9_try.ls stepA_mal.ls SOURCES_LISP = #env.ls core.ls stepA_mal.ls SOURCES = $(SOURCES_STEPS) @@ -25,7 +25,7 @@ step1_read_print.js: Reader.elm Printer.elm Utils.elm Types.elm #step2_eval.js: Reader.elm Printer.elm Utils.elm Types.elm #step3_env.js: Reader.elm Printer.elm Utils.elm Types.elm Env.elm step4_if_fn_do.js: $(SOURCES_BASE) -# step5_tco.js: utils.js reader.js printer.js env.js core.js +step5_tco.js: $(SOURCES_BASE) # step6_file.js: utils.js reader.js printer.js env.js core.js # step7_quote.js: utils.js reader.js printer.js env.js core.js # step8_macros.js: utils.js reader.js printer.js env.js core.js diff --git a/elm/Printer.elm b/elm/Printer.elm index 4d7f8fe01b..e23d0fd992 100644 --- a/elm/Printer.elm +++ b/elm/Printer.elm @@ -2,7 +2,7 @@ module Printer exposing (..) import Array exposing (Array) import Dict exposing (Dict) -import Types exposing (MalExpr(..), keywordPrefix) +import Types exposing (Env, MalExpr(..), keywordPrefix) import Utils exposing (encodeString, wrap) @@ -42,6 +42,12 @@ printString readably ast = MalFunction _ -> "#" + MalAtom atomId -> + "#" + + MalApply _ -> + "#" + printRawString : Bool -> String -> String printRawString readably str = @@ -88,3 +94,36 @@ printMap readably = >> List.map printEntry >> String.join " " >> wrap "{" "}" + + +printEnv : Env -> String +printEnv env = + let + printOuterId = + Maybe.map toString >> Maybe.withDefault "nil" + + printHeader frameId { outerId, refCnt } = + "#" + ++ (toString frameId) + ++ " outer=" + ++ printOuterId outerId + ++ " refCnt=" + ++ (toString refCnt) + + printFrame frameId frame = + String.join "\n" + ((printHeader frameId frame) + :: (Dict.foldr printDatum [] frame.data) + ) + + printFrameAcc k v acc = + printFrame k v :: acc + + printDatum k v acc = + (k ++ " = " ++ (printString True v)) :: acc + in + "--- Environment ---\n" + ++ "Current frame: #" + ++ (toString env.currentFrameId) + ++ "\n\n" + ++ String.join "\n\n" (Dict.foldr printFrameAcc [] env.frames) diff --git a/elm/Types.elm b/elm/Types.elm index 04b78e5166..fbd0beb75a 100644 --- a/elm/Types.elm +++ b/elm/Types.elm @@ -20,6 +20,8 @@ type alias Env = { frames : Dict Int Frame , nextFrameId : Int , currentFrameId : Int + , atoms : Dict Int MalExpr + , nextAtomId : Int } @@ -29,6 +31,10 @@ type EvalResult res | EvalIO (Cmd Msg) (IO -> Eval res) + +-- TODO EvalTCO Env -> Eval MalExpr (?) + + type alias EvalContext res = ( Env, EvalResult res ) @@ -41,6 +47,23 @@ type Eval res = Eval (EvalFn res) +type alias MalFn = + List MalExpr -> Eval MalExpr + + +type MalFunction + = CoreFunc MalFn + | UserFunc { frameId : Int, fn : MalFn } + + +type alias TcoFn = + () -> Eval MalExpr + + +type alias Bound = + List ( String, MalExpr ) + + type MalExpr = MalNil | MalBool Bool @@ -51,7 +74,9 @@ type MalExpr | MalList (List MalExpr) | MalVector (Array MalExpr) | MalMap (Dict String MalExpr) - | MalFunction (List MalExpr -> Eval MalExpr) + | MalFunction MalFunction + | MalApply { frameId : Int, bound : Bound, body : MalExpr } + | MalAtom Int {-| Keywords are prefixed by this char for usage in a MalMap. diff --git a/elm/bootstrap.js b/elm/bootstrap.js index e91fc5c06f..59d3230105 100644 --- a/elm/bootstrap.js +++ b/elm/bootstrap.js @@ -1,4 +1,5 @@ var readline = require('./node_readline'); +var fs = require('fs'); // The first two arguments are: 'node' and 'bootstrap.js' // The third argument is the name of the Elm module to load. @@ -19,3 +20,13 @@ app.ports.readLine.subscribe(function(prompt) { var line = readline.readline(prompt); app.ports.input.send({"tag": "lineRead", "line": line}); }); + +// Read the contents of a file. +app.ports.readFile.subscribe(function(filename) { + try { + var contents = fs.readFileSync(filename, 'utf8'); + app.ports.input.send({"tag": "fileRead", "contents": contents}); + } catch (e) { + app.ports.input.send({"tag": "exception", "message": e.message}); + } +}); \ No newline at end of file diff --git a/elm/step4_if_fn_do.elm b/elm/step4_if_fn_do.elm index 35de9ca653..466ac1865b 100644 --- a/elm/step4_if_fn_do.elm +++ b/elm/step4_if_fn_do.elm @@ -66,7 +66,7 @@ update msg model = runInit env (cont io) Input (Err msg) -> - Debug.crash msg ( model, Cmd.none ) + Debug.crash msg ReplActive env -> case msg of @@ -85,8 +85,11 @@ update msg model = -- Ctrl+D = The End. ( model, Cmd.none ) + Input (Ok io) -> + Debug.crash "unexpected IO received: " io + Input (Err msg) -> - Debug.crash msg ( model, Cmd.none ) + Debug.crash msg ReplIO env cont -> case msg of @@ -145,42 +148,48 @@ read = eval : MalExpr -> Eval MalExpr eval ast = - case ast of - MalList [] -> - Eval.succeed ast + Debug.log "eval " (printString True ast) + |> (\_ -> + case ast of + MalList [] -> + Eval.succeed ast - MalList ((MalSymbol "def!") :: args) -> - evalDef args + MalList ((MalSymbol "def!") :: args) -> + evalDef args - MalList ((MalSymbol "let*") :: args) -> - evalLet args + MalList ((MalSymbol "let*") :: args) -> + evalLet args - MalList ((MalSymbol "do") :: args) -> - evalDo args + MalList ((MalSymbol "do") :: args) -> + evalDo args - MalList ((MalSymbol "if") :: args) -> - evalIf args + MalList ((MalSymbol "if") :: args) -> + evalIf args - MalList ((MalSymbol "fn*") :: args) -> - evalFn args + MalList ((MalSymbol "fn*") :: args) -> + evalFn args - MalList list -> - evalList list - |> Eval.andThen - (\newList -> - case newList of - [] -> - Eval.fail "can't happen" + MalList list -> + evalList list + |> Eval.andThen + (\newList -> + case newList of + [] -> + Eval.fail "can't happen" - (MalFunction fn) :: args -> - fn args + (MalFunction (CoreFunc fn)) :: args -> + fn args - fn :: _ -> - Eval.fail ((printString True fn) ++ " is not a function") - ) + (MalFunction (UserFunc { fn })) :: args -> + fn args - _ -> - evalAst ast + fn :: _ -> + Eval.fail ((printString True fn) ++ " is not a function") + ) + + _ -> + evalAst ast + ) evalAst : MalExpr -> Eval MalExpr @@ -280,15 +289,8 @@ evalLet args = |> Eval.andThen (\_ -> eval body) |> Eval.andThen (\res -> - Eval.withEnv - (\env -> - case Env.pop env of - Ok env -> - Eval.setEnv env |> Eval.map (\_ -> res) - - Err msg -> - Eval.fail msg - ) + Eval.modifyEnv Env.pop + |> Eval.map (\_ -> res) ) in case args of @@ -408,22 +410,19 @@ evalFn args = else Ok <| zip binds args ++ [ ( var, varArgs ) ] - makeFn frameId binder body args = - case binder args of - Ok bound -> - Eval.withEnv - (\env -> - Eval.modifyEnv (Env.enter frameId bound) - |> Eval.andThen (\_ -> eval body) - |> Eval.andThen - (\res -> - Eval.modifyEnv (Env.leave env.currentFrameId) - |> Eval.map (\_ -> res) - ) - ) + makeFn frameId binder body = + MalFunction <| + UserFunc + { frameId = frameId + , fn = + \args -> + case binder args of + Ok bound -> + Eval.enter frameId bound (eval body) - Err msg -> - Eval.fail msg + Err msg -> + Eval.fail msg + } go bindsList body = case extractAndParse bindsList of @@ -435,9 +434,7 @@ evalFn args = Eval.withEnv (\env -> Eval.succeed - (MalFunction - (makeFn env.currentFrameId binder body) - ) + (makeFn env.currentFrameId binder body) ) ) diff --git a/elm/step5_tco.elm b/elm/step5_tco.elm new file mode 100644 index 0000000000..d48c50e52c --- /dev/null +++ b/elm/step5_tco.elm @@ -0,0 +1,505 @@ +port module Main exposing (..) + +import Array +import Dict exposing (Dict) +import IO exposing (..) +import Json.Decode exposing (decodeValue) +import Platform exposing (programWithFlags) +import Types exposing (..) +import Reader exposing (readString) +import Printer exposing (printString) +import Utils exposing (maybeToList, zip, last, justValues) +import Env +import Core +import Eval + + +main : Program Flags Model Msg +main = + programWithFlags + { init = init + , update = update + , subscriptions = + \model -> input (decodeValue decodeIO >> Input) + } + + +type alias Flags = + { args : List String + } + + +type Model + = InitIO Env (IO -> Eval MalExpr) + | InitError String + | ReplActive Env + | ReplIO Env (IO -> Eval MalExpr) + + +init : Flags -> ( Model, Cmd Msg ) +init { args } = + let + initEnv = + Core.ns + + evalMalInit = + Core.malInit + |> List.map rep + |> justValues + |> List.foldl + (\b a -> a |> Eval.andThen (\_ -> b)) + (Eval.succeed MalNil) + in + runInit initEnv evalMalInit + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case model of + InitError _ -> + -- ignore all + ( model, Cmd.none ) + + InitIO env cont -> + case msg of + Input (Ok io) -> + runInit env (cont io) + + Input (Err msg) -> + Debug.crash msg + + ReplActive env -> + case msg of + Input (Ok (LineRead (Just line))) -> + case rep line of + Just expr -> + run env expr + + Nothing -> + ( model, readLine prompt ) + + Input (Ok LineWritten) -> + ( model, readLine prompt ) + + Input (Ok (LineRead Nothing)) -> + -- Ctrl+D = The End. + ( model, Cmd.none ) + + Input (Ok io) -> + Debug.crash "unexpected IO received: " io + + Input (Err msg) -> + Debug.crash msg + + ReplIO env cont -> + case msg of + Input (Ok io) -> + run env (cont io) + + Input (Err msg) -> + Debug.crash msg ( model, Cmd.none ) + + +runInit : Env -> Eval MalExpr -> ( Model, Cmd Msg ) +runInit env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + -- Init went okay, start REPL. + ( ReplActive env, readLine prompt ) + + ( env, EvalErr msg ) -> + -- Init failed, don't start REPL. + ( InitError msg, writeLine ("ERR:" ++ msg) ) + + ( env, EvalIO cmd cont ) -> + -- IO in init. + ( InitIO env cont, cmd ) + + +run : Env -> Eval MalExpr -> ( Model, Cmd Msg ) +run env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + ( ReplActive env, writeLine (print expr) ) + + ( env, EvalErr msg ) -> + ( ReplActive env, writeLine ("ERR:" ++ msg) ) + + ( env, EvalIO cmd cont ) -> + ( ReplIO env cont, cmd ) + + +prompt : String +prompt = + "user> " + + +{-| read can return three things: + +Ok (Just expr) -> parsed okay +Ok Nothing -> empty string (only whitespace and/or comments) +Err msg -> parse error + +-} +read : String -> Result String (Maybe MalExpr) +read = + readString + + +eval : MalExpr -> Eval MalExpr +eval ast = + Debug.log "eval " (printString True ast) + |> (\_ -> + evalNoApply ast + |> Eval.andThen + (\ast -> + case ast of + MalApply { frameId, bound, body } -> + Eval.withEnv + (\env -> + Eval.modifyEnv (Env.enter frameId bound) + |> Eval.andThen (\_ -> evalNoApply body) + |> Eval.andThen + (\res -> + Eval.modifyEnv (Env.leave env.currentFrameId) + |> Eval.map (\_ -> res) + ) + ) + |> Eval.andThen eval + + _ -> + Eval.succeed ast + ) + ) + + +evalNoApply : MalExpr -> Eval MalExpr +evalNoApply ast = + Debug.log "evalNoApply " (printString True ast) + |> (\_ -> + case ast of + MalList [] -> + Eval.succeed ast + + MalList ((MalSymbol "def!") :: args) -> + evalDef args + + MalList ((MalSymbol "let*") :: args) -> + evalLet args + + MalList ((MalSymbol "do") :: args) -> + evalDo args + + MalList ((MalSymbol "if") :: args) -> + evalIf args + + MalList ((MalSymbol "fn*") :: args) -> + evalFn args + + MalList list -> + evalList list + |> Eval.andThen + (\newList -> + case newList of + [] -> + Eval.fail "can't happen" + + (MalFunction (CoreFunc fn)) :: args -> + fn args + + (MalFunction (UserFunc { fn })) :: args -> + fn args + + fn :: _ -> + Eval.fail ((printString True fn) ++ " is not a function") + ) + + _ -> + evalAst ast + ) + + +evalAst : MalExpr -> Eval MalExpr +evalAst ast = + case ast of + MalSymbol sym -> + -- Lookup symbol in env and return value or raise error if not found. + Eval.withEnv + (\env -> + case Env.get sym env of + Ok val -> + Eval.succeed val + + Err msg -> + Eval.fail msg + ) + + MalList list -> + -- Return new list that is result of calling eval on each element of list. + evalList list + |> Eval.map MalList + + MalVector vec -> + evalList (Array.toList vec) + |> Eval.map (Array.fromList >> MalVector) + + MalMap map -> + evalList (Dict.values map) + |> Eval.map + (zip (Dict.keys map) + >> Dict.fromList + >> MalMap + ) + + _ -> + Eval.succeed ast + + +evalList : List MalExpr -> Eval (List MalExpr) +evalList list = + let + go list acc = + case list of + [] -> + Eval.succeed (List.reverse acc) + + x :: rest -> + eval x + |> Eval.andThen + (\val -> + go rest (val :: acc) + ) + in + go list [] + + +evalDef : List MalExpr -> Eval MalExpr +evalDef args = + case args of + [ MalSymbol name, uneValue ] -> + eval uneValue + |> Eval.andThen + (\value -> + Eval.modifyEnv (Env.set name value) + |> Eval.andThen (\_ -> Eval.succeed value) + ) + + _ -> + Eval.fail "def! expected two args: name and value" + + +evalLet : List MalExpr -> Eval MalExpr +evalLet args = + let + evalBinds binds = + case binds of + (MalSymbol name) :: expr :: rest -> + eval expr + |> Eval.andThen + (\value -> + Eval.modifyEnv (Env.set name value) + |> Eval.andThen + (\_ -> + if List.isEmpty rest then + Eval.succeed () + else + evalBinds rest + ) + ) + + _ -> + Eval.fail "let* expected an even number of binds (symbol expr ..)" + + go binds body = + Eval.modifyEnv Env.push + |> Eval.andThen (\_ -> evalBinds binds) + |> Eval.andThen (\_ -> evalNoApply body) + |> Eval.andThen + (\res -> + Eval.modifyEnv Env.pop + |> Eval.map (\_ -> res) + ) + in + case args of + [ MalList binds, body ] -> + go binds body + + [ MalVector bindsVec, body ] -> + go (Array.toList bindsVec) body + + _ -> + Eval.fail "let* expected two args: binds and a body" + + +evalDo : List MalExpr -> Eval MalExpr +evalDo args = + case List.reverse args of + last :: rest -> + evalList (List.reverse rest) + |> Eval.andThen (\_ -> evalNoApply last) + + [] -> + Eval.fail "do expected at least one arg" + + +evalIf : List MalExpr -> Eval MalExpr +evalIf args = + let + isThruthy expr = + expr /= MalNil && expr /= (MalBool False) + + go condition trueExpr falseExpr = + eval condition + |> Eval.map isThruthy + |> Eval.andThen + (\cond -> + evalNoApply + (if cond then + trueExpr + else + falseExpr + ) + ) + in + case args of + [ condition, trueExpr ] -> + go condition trueExpr MalNil + + [ condition, trueExpr, falseExpr ] -> + go condition trueExpr falseExpr + + _ -> + Eval.fail "if expected at least two args" + + +evalFn : List MalExpr -> Eval MalExpr +evalFn args = + let + {- Extract symbols from the binds list and verify their uniqueness -} + extractSymbols acc list = + case list of + [] -> + Ok (List.reverse acc) + + (MalSymbol name) :: rest -> + if List.member name acc then + Err "all binds must have unique names" + else + extractSymbols (name :: acc) rest + + _ -> + Err "all binds in fn* must be a symbol" + + parseBinds list = + case List.reverse list of + var :: "&" :: rest -> + Ok <| bindVarArgs (List.reverse rest) var + + _ -> + if List.member "&" list then + Err "varargs separator '&' is used incorrectly" + else + Ok <| bindArgs list + + extractAndParse = + extractSymbols [] >> Result.andThen parseBinds + + bindArgs binds args = + let + numBinds = + List.length binds + in + if List.length args /= numBinds then + Err <| + "function expected " + ++ (toString numBinds) + ++ " arguments" + else + Ok <| zip binds args + + bindVarArgs binds var args = + let + minArgs = + List.length binds + + varArgs = + MalList (List.drop minArgs args) + in + if List.length args < minArgs then + Err <| + "function expected at least " + ++ (toString minArgs) + ++ " arguments" + else + Ok <| zip binds args ++ [ ( var, varArgs ) ] + + makeFn frameId binder body = + MalFunction <| + UserFunc + { frameId = frameId + , fn = + \args -> + case binder args of + Ok bound -> + Eval.succeed <| + -- TODO : choice Env.enter prematurely? + -- I think it is needed by the garbage collect.. + MalApply + { frameId = frameId + , bound = bound + , body = body + } + + Err msg -> + Eval.fail msg + } + + go bindsList body = + case extractAndParse bindsList of + Ok binder -> + Eval.modifyEnv Env.ref + -- reference the current frame. + |> Eval.andThen + (\_ -> + Eval.withEnv + (\env -> + Eval.succeed + (makeFn env.currentFrameId binder body) + ) + ) + + Err msg -> + Eval.fail msg + in + case args of + [ MalList bindsList, body ] -> + go bindsList body + + [ MalVector bindsVec, body ] -> + go (Array.toList bindsVec) body + + _ -> + Eval.fail "fn* expected two args: binds list and body" + + +print : MalExpr -> String +print = + printString True + + +{-| Read-Eval-Print. + +Doesn't actually run the Eval but returns the monad. + +-} +rep : String -> Maybe (Eval MalExpr) +rep input = + case readString input of + Ok Nothing -> + Nothing + + Err msg -> + Just (Eval.fail msg) + + Ok (Just ast) -> + eval ast |> Just From 16586ba573e3739ca69c759bb9f1a5a3ba367f75 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Thu, 15 Jun 2017 21:06:49 +0200 Subject: [PATCH 0343/2308] Elm step 5: cleaning up a bit, add debug mode. --- elm/Core.elm | 16 ++++++ elm/Env.elm | 44 +++++++++-------- elm/Eval.elm | 110 ++++++++++++++++++++++------------------- elm/Types.elm | 22 +++++---- elm/step5_tco.elm | 123 ++++++++++++++++++++++++++-------------------- 5 files changed, 179 insertions(+), 136 deletions(-) diff --git a/elm/Core.elm b/elm/Core.elm index 06362e6deb..559f0ecf12 100644 --- a/elm/Core.elm +++ b/elm/Core.elm @@ -221,6 +221,21 @@ ns = gc args = Eval.withEnv (Env.gc >> Printer.printEnv >> writeLine) + + setDebug enabled = + Eval.modifyEnv + (\env -> + { env | debug = enabled } + ) + |> Eval.andThen (\_ -> Eval.succeed MalNil) + + debug args = + case args of + [ MalBool True ] -> + setDebug True + + _ -> + setDebug False in Env.global |> Env.set "+" (makeFn <| binaryOp (+) MalInt) @@ -249,6 +264,7 @@ ns = |> Env.set "reset!" (makeFn reset) |> Env.set "swap!" (makeFn swap) |> Env.set "gc" (makeFn gc) + |> Env.set "debug!" (makeFn debug) malInit : List String diff --git a/elm/Env.elm b/elm/Env.elm index 19158e8c35..5efb7ad2e6 100644 --- a/elm/Env.elm +++ b/elm/Env.elm @@ -1,6 +1,7 @@ module Env exposing - ( global + ( debug + , global , push , pop , enter @@ -20,6 +21,14 @@ import Array import Set +debug : Env -> String -> a -> a +debug env msg value = + if env.debug then + Debug.log msg value + else + value + + globalFrameId : Int globalFrameId = 0 @@ -32,6 +41,7 @@ global = , currentFrameId = globalFrameId , atoms = Dict.empty , nextAtomId = 0 + , debug = True } @@ -91,8 +101,7 @@ enter : Int -> List ( String, MalExpr ) -> Env -> Env enter parentFrameId binds env = let frameId = - Debug.log "enter #" - env.nextFrameId + debug env "enter #" env.nextFrameId newFrame = setBinds binds (emptyFrame (Just parentFrameId)) @@ -108,8 +117,7 @@ leave : Int -> Env -> Env leave orgFrameId env = let frameId = - Debug.log "leave #" - env.currentFrameId + debug env "leave #" env.currentFrameId in { env | currentFrameId = orgFrameId @@ -134,12 +142,6 @@ ref env = { env | frames = newFrames } - --- TODO: when disposing, deref all function's frames? --- TODO: is that enough instead of a GC? no: don't know how often the function is referenced. --- TODO: consideration: keep refCnt for MalFunction, or implement a light GC. - - deref : Maybe Frame -> Maybe Frame deref = Maybe.andThen @@ -151,12 +153,6 @@ deref = ) - --- TODO need a GC. --- given a Env, see which frames are not reachable. --- in MalFunction need to refer to the frameId. - - {-| Given an Env see which frames are not reachable from the global frame. Return a new Env without the unreachable frames. -} @@ -170,11 +166,11 @@ gc env = data |> Dict.values |> countList acc countRefs expr acc = - Debug.log (toString expr) <| + debug env ("gc-visit " ++ (toString expr)) <| case expr of MalFunction (UserFunc { frameId }) -> if not (Set.member frameId acc) then - Debug.log "counting" <| + debug env "gc-counting" <| case Dict.get frameId env.frames of Just frame -> countFrame (Set.insert frameId acc) frame @@ -198,6 +194,11 @@ gc env = initSet = Set.fromList [ globalFrameId, env.currentFrameId ] + + reportUnused frames used = + Dict.diff frames used + |> debug env "unused frames" + |> (\_ -> frames) in case Dict.get globalFrameId env.frames of Nothing -> @@ -206,10 +207,11 @@ gc env = Just globalFrame -> countFrame initSet globalFrame |> Set.toList - |> Debug.log "used frames" + |> debug env "used frames" |> List.map (\frameId -> ( frameId, emptyFrame Nothing )) |> Dict.fromList - |> Dict.intersect (Debug.log "cur frames" env.frames) + |> reportUnused env.frames + |> Dict.intersect env.frames |> (\frames -> { env | frames = frames }) diff --git a/elm/Eval.elm b/elm/Eval.elm index 1756a40afe..daa1472506 100644 --- a/elm/Eval.elm +++ b/elm/Eval.elm @@ -3,89 +3,72 @@ module Eval exposing (..) import Types exposing (..) import IO exposing (IO) import Env -import Printer apply : Eval a -> Env -> EvalContext a -apply (Eval f) state = - f state +apply f env = + f env run : Env -> Eval a -> EvalContext a -run state e = - apply e state +run env e = + apply e env withEnv : (Env -> Eval a) -> Eval a -withEnv f = - Eval <| - \state -> - apply (f state) state +withEnv f env = + apply (f env) env setEnv : Env -> Eval () -setEnv state = - Eval <| - \_ -> - apply (succeed ()) state +setEnv env _ = + apply (succeed ()) env modifyEnv : (Env -> Env) -> Eval () -modifyEnv f = - Eval <| - \state -> - apply (succeed ()) (f state) +modifyEnv f env = + apply (succeed ()) (f env) succeed : a -> Eval a -succeed res = - Eval <| - \state -> - ( state, EvalOk res ) +succeed res env = + ( env, EvalOk res ) io : Cmd Msg -> (IO -> Eval a) -> Eval a -io cmd cont = - Eval <| - \state -> - ( state, EvalIO cmd cont ) +io cmd cont env = + ( env, EvalIO cmd cont ) map : (a -> b) -> Eval a -> Eval b -map f e = - Eval <| - \state -> - case apply e state of - ( state, EvalOk res ) -> - ( state, EvalOk (f res) ) +map f e env = + case apply e env of + ( env, EvalOk res ) -> + ( env, EvalOk (f res) ) - ( state, EvalErr msg ) -> - ( state, EvalErr msg ) + ( env, EvalErr msg ) -> + ( env, EvalErr msg ) - ( state, EvalIO cmd cont ) -> - ( state, EvalIO cmd (cont >> map f) ) + ( env, EvalIO cmd cont ) -> + ( env, EvalIO cmd (cont >> map f) ) andThen : (a -> Eval b) -> Eval a -> Eval b -andThen f e = - Eval <| - \state -> - case apply e state of - ( state, EvalOk res ) -> - apply (f res) state +andThen f e env = + case apply e env of + ( env, EvalOk res ) -> + apply (f res) env - ( state, EvalErr msg ) -> - ( state, EvalErr msg ) + ( env, EvalErr msg ) -> + ( env, EvalErr msg ) - ( state, EvalIO cmd cont ) -> - ( state, EvalIO cmd (cont >> andThen f) ) + ( env, EvalIO cmd cont ) -> + ( env, EvalIO cmd (cont >> andThen f) ) fail : String -> Eval a -fail msg = - Eval <| - \state -> - ( state, EvalErr msg ) +fail msg env = + ( env, EvalErr msg ) enter : Int -> List ( String, MalExpr ) -> Eval a -> Eval a @@ -93,10 +76,35 @@ enter frameId bound body = withEnv (\env -> modifyEnv (Env.enter frameId bound) - |> andThen (\_ -> body) + |> andThen (always body) |> andThen (\res -> modifyEnv (Env.leave env.currentFrameId) - |> map (\_ -> res) + |> map (always res) ) ) + + +{-| Apply f to expr repeatedly. +Continues iterating if f returns (Left eval). +Stops if f returns (Right expr). + +Tail call optimized. + +-} +runLoop : (MalExpr -> Either (Eval MalExpr) MalExpr) -> MalExpr -> Eval MalExpr +runLoop f expr env = + case f expr of + Left e -> + case apply e env of + ( env, EvalOk expr ) -> + runLoop f expr env + + ( env, EvalErr msg ) -> + ( env, EvalErr msg ) + + ( env, EvalIO cmd cont ) -> + ( env, EvalIO cmd (cont >> andThen (runLoop f)) ) + + Right expr -> + ( env, EvalOk expr ) diff --git a/elm/Types.elm b/elm/Types.elm index fbd0beb75a..a3ef598f9c 100644 --- a/elm/Types.elm +++ b/elm/Types.elm @@ -5,6 +5,11 @@ import Dict exposing (Dict) import IO exposing (IO) +type Either a b + = Left a + | Right b + + type Msg = Input (Result String IO) @@ -22,6 +27,7 @@ type alias Env = , currentFrameId : Int , atoms : Dict Int MalExpr , nextAtomId : Int + , debug : Bool } @@ -31,22 +37,14 @@ type EvalResult res | EvalIO (Cmd Msg) (IO -> Eval res) - --- TODO EvalTCO Env -> Eval MalExpr (?) - - type alias EvalContext res = ( Env, EvalResult res ) -type alias EvalFn res = +type alias Eval res = Env -> EvalContext res -type Eval res - = Eval (EvalFn res) - - type alias MalFn = List MalExpr -> Eval MalExpr @@ -56,6 +54,10 @@ type MalFunction | UserFunc { frameId : Int, fn : MalFn } +type alias ApplyRec = + { frameId : Int, bound : Bound, body : MalExpr } + + type alias TcoFn = () -> Eval MalExpr @@ -75,7 +77,7 @@ type MalExpr | MalVector (Array MalExpr) | MalMap (Dict String MalExpr) | MalFunction MalFunction - | MalApply { frameId : Int, bound : Bound, body : MalExpr } + | MalApply ApplyRec | MalAtom Int diff --git a/elm/step5_tco.elm b/elm/step5_tco.elm index d48c50e52c..6591c3a22a 100644 --- a/elm/step5_tco.elm +++ b/elm/step5_tco.elm @@ -146,77 +146,92 @@ read = readString +debug : String -> a -> Eval b -> Eval b +debug msg value e = + Eval.withEnv + (\env -> + Env.debug env msg value + |> always e + ) + + eval : MalExpr -> Eval MalExpr eval ast = - Debug.log "eval " (printString True ast) - |> (\_ -> - evalNoApply ast - |> Eval.andThen - (\ast -> - case ast of - MalApply { frameId, bound, body } -> - Eval.withEnv - (\env -> - Eval.modifyEnv (Env.enter frameId bound) - |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.andThen - (\res -> - Eval.modifyEnv (Env.leave env.currentFrameId) - |> Eval.map (\_ -> res) - ) - ) - |> Eval.andThen eval - - _ -> - Eval.succeed ast + let + apply expr = + case expr of + MalApply app -> + Left + (debug "evalApply" + (printString True expr) + (evalApply app) ) - ) + + _ -> + Right expr + in + evalNoApply ast + |> Eval.andThen (Eval.runLoop apply) + + +evalApply : ApplyRec -> Eval MalExpr +evalApply { frameId, bound, body } = + Eval.withEnv + (\env -> + Eval.modifyEnv (Env.enter frameId bound) + |> Eval.andThen (\_ -> evalNoApply body) + |> Eval.andThen + (\res -> + Eval.modifyEnv (Env.leave env.currentFrameId) + |> Eval.map (\_ -> res) + ) + ) evalNoApply : MalExpr -> Eval MalExpr evalNoApply ast = - Debug.log "evalNoApply " (printString True ast) - |> (\_ -> - case ast of - MalList [] -> - Eval.succeed ast + debug "evalNoApply" + (printString True ast) + (case ast of + MalList [] -> + Eval.succeed ast - MalList ((MalSymbol "def!") :: args) -> - evalDef args + MalList ((MalSymbol "def!") :: args) -> + evalDef args - MalList ((MalSymbol "let*") :: args) -> - evalLet args + MalList ((MalSymbol "let*") :: args) -> + evalLet args - MalList ((MalSymbol "do") :: args) -> - evalDo args + MalList ((MalSymbol "do") :: args) -> + evalDo args - MalList ((MalSymbol "if") :: args) -> - evalIf args + MalList ((MalSymbol "if") :: args) -> + evalIf args - MalList ((MalSymbol "fn*") :: args) -> - evalFn args + MalList ((MalSymbol "fn*") :: args) -> + evalFn args - MalList list -> - evalList list - |> Eval.andThen - (\newList -> - case newList of - [] -> - Eval.fail "can't happen" + MalList list -> + evalList list + |> Eval.andThen + (\newList -> + case newList of + [] -> + Eval.fail "can't happen" - (MalFunction (CoreFunc fn)) :: args -> - fn args + (MalFunction (CoreFunc fn)) :: args -> + fn args - (MalFunction (UserFunc { fn })) :: args -> - fn args + (MalFunction (UserFunc { fn })) :: args -> + fn args - fn :: _ -> - Eval.fail ((printString True fn) ++ " is not a function") - ) + fn :: _ -> + Eval.fail ((printString True fn) ++ " is not a function") + ) - _ -> - evalAst ast - ) + _ -> + evalAst ast + ) evalAst : MalExpr -> Eval MalExpr From c03ae85fcde448971c2acd8e444e9981a40d59a6 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Thu, 15 Jun 2017 21:07:37 +0200 Subject: [PATCH 0344/2308] Elm step 5: tests passed. --- elm/Env.elm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm/Env.elm b/elm/Env.elm index 5efb7ad2e6..c26fb58a21 100644 --- a/elm/Env.elm +++ b/elm/Env.elm @@ -41,7 +41,7 @@ global = , currentFrameId = globalFrameId , atoms = Dict.empty , nextAtomId = 0 - , debug = True + , debug = False } From a83904ea9273a5e94b65b676ba23abee92b1e80f Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Thu, 15 Jun 2017 21:23:37 +0200 Subject: [PATCH 0345/2308] Elm step 6: fix bug: def! always writes in global frame. --- elm/Core.elm | 4 + elm/Env.elm | 18 +- elm/Makefile | 6 +- elm/step1_read_print.elm | 3 + elm/step2_eval.elm | 13 +- elm/step4_if_fn_do.elm | 63 +++-- elm/step5_tco.elm | 2 +- elm/step6_file.elm | 534 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 596 insertions(+), 47 deletions(-) create mode 100644 elm/step6_file.elm diff --git a/elm/Core.elm b/elm/Core.elm index 559f0ecf12..315f8d07ed 100644 --- a/elm/Core.elm +++ b/elm/Core.elm @@ -272,4 +272,8 @@ malInit = [ """(def! not (fn* (a) (if a false true)))""" + , """(def! load-file + (fn* (f) + (eval (read-string + (str "(do " (slurp f) ")")))))""" ] diff --git a/elm/Env.elm b/elm/Env.elm index c26fb58a21..e6a9bf7b91 100644 --- a/elm/Env.elm +++ b/elm/Env.elm @@ -9,6 +9,7 @@ module Env , ref , get , set + , def , newAtom , getAtom , setAtom @@ -223,8 +224,8 @@ emptyFrame outerId = } -set : String -> MalExpr -> Env -> Env -set name expr env = +setInFrame : Int -> String -> MalExpr -> Env -> Env +setInFrame frameId name expr env = let updateFrame = Maybe.map @@ -232,15 +233,22 @@ set name expr env = { frame | data = Dict.insert name expr frame.data } ) - frameId = - env.currentFrameId - newFrames = Dict.update frameId updateFrame env.frames in { env | frames = newFrames } +set : String -> MalExpr -> Env -> Env +set name expr env = + setInFrame env.currentFrameId name expr env + + +def : String -> MalExpr -> Env -> Env +def = + setInFrame globalFrameId + + get : String -> Env -> Result String MalExpr get name env = let diff --git a/elm/Makefile b/elm/Makefile index 8e0a7a1945..37c90f10ff 100644 --- a/elm/Makefile +++ b/elm/Makefile @@ -1,7 +1,7 @@ SOURCES_BASE = Reader.elm Printer.elm Utils.elm Types.elm Env.elm \ Core.elm Eval.elm IO.elm -SOURCES_STEPS = step0_repl.elm step4_if_fn_do.elm step5_tco.elm #step1_read_print.elm step2_eval.elm \ - step3_env.elm #step6_file.ls \ +SOURCES_STEPS = step0_repl.elm step4_if_fn_do.elm step5_tco.elm step6_file.elm step1_read_print.elm #step2_eval.elm \ + #step3_env.elm \ step7_quote.ls step8_macros.ls step9_try.ls stepA_mal.ls SOURCES_LISP = #env.ls core.ls stepA_mal.ls SOURCES = $(SOURCES_STEPS) @@ -26,7 +26,7 @@ step1_read_print.js: Reader.elm Printer.elm Utils.elm Types.elm #step3_env.js: Reader.elm Printer.elm Utils.elm Types.elm Env.elm step4_if_fn_do.js: $(SOURCES_BASE) step5_tco.js: $(SOURCES_BASE) -# step6_file.js: utils.js reader.js printer.js env.js core.js +step6_file.js: $(SOURCES_BASE) # step7_quote.js: utils.js reader.js printer.js env.js core.js # step8_macros.js: utils.js reader.js printer.js env.js core.js # step9_try.js: utils.js reader.js printer.js env.js core.js diff --git a/elm/step1_read_print.elm b/elm/step1_read_print.elm index ff7c09a6a8..cff0e9a6bc 100644 --- a/elm/step1_read_print.elm +++ b/elm/step1_read_print.elm @@ -55,6 +55,9 @@ update msg model = Input (Ok (LineRead Nothing)) -> ( model, Cmd.none ) + Input (Ok io) -> + Debug.crash "unexpected IO received: " io + Input (Err msg) -> Debug.crash msg ( model, Cmd.none ) diff --git a/elm/step2_eval.elm b/elm/step2_eval.elm index 25d83fd7c5..7f7b85c8f5 100644 --- a/elm/step2_eval.elm +++ b/elm/step2_eval.elm @@ -3,7 +3,7 @@ port module Main exposing (..) import IO exposing (..) import Json.Decode exposing (decodeValue) import Platform exposing (programWithFlags) -import Types exposing (MalExpr(..)) +import Types exposing (MalExpr(..), MalFunction(..)) import Reader exposing (readString) import Printer exposing (printString) import Utils exposing (maybeToList, zip) @@ -49,6 +49,9 @@ init { args } = initReplEnv : ReplEnv initReplEnv = let + makeFn = + CoreFunc >> MalFunction + binaryOp fn args = case args of [ MalInt x, MalInt y ] -> @@ -58,10 +61,10 @@ initReplEnv = Err "unsupported arguments" in Dict.fromList - [ ( "+", MalFunction <| binaryOp (+) ) - , ( "-", MalFunction <| binaryOp (-) ) - , ( "*", MalFunction <| binaryOp (*) ) - , ( "/", MalFunction <| binaryOp (//) ) + [ ( "+", makeFn <| binaryOp (+) ) + , ( "-", makeFn <| binaryOp (-) ) + , ( "*", makeFn <| binaryOp (*) ) + , ( "/", makeFn <| binaryOp (//) ) ] diff --git a/elm/step4_if_fn_do.elm b/elm/step4_if_fn_do.elm index 466ac1865b..e6c31954da 100644 --- a/elm/step4_if_fn_do.elm +++ b/elm/step4_if_fn_do.elm @@ -148,48 +148,45 @@ read = eval : MalExpr -> Eval MalExpr eval ast = - Debug.log "eval " (printString True ast) - |> (\_ -> - case ast of - MalList [] -> - Eval.succeed ast + case ast of + MalList [] -> + Eval.succeed ast - MalList ((MalSymbol "def!") :: args) -> - evalDef args + MalList ((MalSymbol "def!") :: args) -> + evalDef args - MalList ((MalSymbol "let*") :: args) -> - evalLet args + MalList ((MalSymbol "let*") :: args) -> + evalLet args - MalList ((MalSymbol "do") :: args) -> - evalDo args + MalList ((MalSymbol "do") :: args) -> + evalDo args - MalList ((MalSymbol "if") :: args) -> - evalIf args + MalList ((MalSymbol "if") :: args) -> + evalIf args - MalList ((MalSymbol "fn*") :: args) -> - evalFn args + MalList ((MalSymbol "fn*") :: args) -> + evalFn args - MalList list -> - evalList list - |> Eval.andThen - (\newList -> - case newList of - [] -> - Eval.fail "can't happen" + MalList list -> + evalList list + |> Eval.andThen + (\newList -> + case newList of + [] -> + Eval.fail "can't happen" - (MalFunction (CoreFunc fn)) :: args -> - fn args + (MalFunction (CoreFunc fn)) :: args -> + fn args - (MalFunction (UserFunc { fn })) :: args -> - fn args + (MalFunction (UserFunc { fn })) :: args -> + fn args - fn :: _ -> - Eval.fail ((printString True fn) ++ " is not a function") - ) + fn :: _ -> + Eval.fail ((printString True fn) ++ " is not a function") + ) - _ -> - evalAst ast - ) + _ -> + evalAst ast evalAst : MalExpr -> Eval MalExpr @@ -253,7 +250,7 @@ evalDef args = eval uneValue |> Eval.andThen (\value -> - Eval.modifyEnv (Env.set name value) + Eval.modifyEnv (Env.def name value) |> Eval.andThen (\_ -> Eval.succeed value) ) diff --git a/elm/step5_tco.elm b/elm/step5_tco.elm index 6591c3a22a..1ae5dcf574 100644 --- a/elm/step5_tco.elm +++ b/elm/step5_tco.elm @@ -295,7 +295,7 @@ evalDef args = eval uneValue |> Eval.andThen (\value -> - Eval.modifyEnv (Env.set name value) + Eval.modifyEnv (Env.def name value) |> Eval.andThen (\_ -> Eval.succeed value) ) diff --git a/elm/step6_file.elm b/elm/step6_file.elm new file mode 100644 index 0000000000..789f7ee7d9 --- /dev/null +++ b/elm/step6_file.elm @@ -0,0 +1,534 @@ +port module Main exposing (..) + +import Array +import Dict exposing (Dict) +import IO exposing (..) +import Json.Decode exposing (decodeValue) +import Platform exposing (programWithFlags) +import Types exposing (..) +import Reader exposing (readString) +import Printer exposing (printString) +import Utils exposing (maybeToList, zip, last, justValues) +import Env +import Core +import Eval + + +main : Program Flags Model Msg +main = + programWithFlags + { init = init + , update = update + , subscriptions = + \model -> input (decodeValue decodeIO >> Input) + } + + +type alias Flags = + { args : List String + } + + +type Model + = InitIO Env (IO -> Eval MalExpr) + | InitError String + | ReplActive Env + | ReplIO Env (IO -> Eval MalExpr) + + +init : Flags -> ( Model, Cmd Msg ) +init { args } = + let + makeFn = + CoreFunc >> MalFunction + + initEnv = + Core.ns + |> Env.set "eval" (makeFn malEval) + + evalMalInit = + Core.malInit + |> List.map rep + |> justValues + |> List.foldl + (\b a -> a |> Eval.andThen (\_ -> b)) + (Eval.succeed MalNil) + in + runInit initEnv evalMalInit + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case model of + InitError _ -> + -- ignore all + ( model, Cmd.none ) + + InitIO env cont -> + case msg of + Input (Ok io) -> + runInit env (cont io) + + Input (Err msg) -> + Debug.crash msg + + ReplActive env -> + case msg of + Input (Ok (LineRead (Just line))) -> + case rep line of + Just expr -> + run env expr + + Nothing -> + ( model, readLine prompt ) + + Input (Ok LineWritten) -> + ( model, readLine prompt ) + + Input (Ok (LineRead Nothing)) -> + -- Ctrl+D = The End. + ( model, Cmd.none ) + + Input (Ok io) -> + Debug.crash "unexpected IO received: " io + + Input (Err msg) -> + Debug.crash msg + + ReplIO env cont -> + case msg of + Input (Ok io) -> + run env (cont io) + + Input (Err msg) -> + Debug.crash msg ( model, Cmd.none ) + + +runInit : Env -> Eval MalExpr -> ( Model, Cmd Msg ) +runInit env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + -- Init went okay, start REPL. + ( ReplActive env, readLine prompt ) + + ( env, EvalErr msg ) -> + -- Init failed, don't start REPL. + ( InitError msg, writeLine ("ERR:" ++ msg) ) + + ( env, EvalIO cmd cont ) -> + -- IO in init. + ( InitIO env cont, cmd ) + + +run : Env -> Eval MalExpr -> ( Model, Cmd Msg ) +run env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + ( ReplActive env, writeLine (print expr) ) + + ( env, EvalErr msg ) -> + ( ReplActive env, writeLine ("ERR:" ++ msg) ) + + ( env, EvalIO cmd cont ) -> + ( ReplIO env cont, cmd ) + + +prompt : String +prompt = + "user> " + + +{-| read can return three things: + +Ok (Just expr) -> parsed okay +Ok Nothing -> empty string (only whitespace and/or comments) +Err msg -> parse error + +-} +read : String -> Result String (Maybe MalExpr) +read = + readString + + +debug : String -> a -> Eval b -> Eval b +debug msg value e = + Eval.withEnv + (\env -> + Env.debug env msg value + |> always e + ) + + +eval : MalExpr -> Eval MalExpr +eval ast = + let + apply expr = + case expr of + MalApply app -> + Left + (debug "evalApply" + (printString True expr) + (evalApply app) + ) + + _ -> + Right expr + in + evalNoApply ast + |> Eval.andThen (Eval.runLoop apply) + + +malEval : List MalExpr -> Eval MalExpr +malEval args = + case args of + [ expr ] -> + eval expr + + _ -> + Eval.fail "unsupported arguments" + + +evalApply : ApplyRec -> Eval MalExpr +evalApply { frameId, bound, body } = + Eval.withEnv + (\env -> + Eval.modifyEnv (Env.enter frameId bound) + |> Eval.andThen (\_ -> evalNoApply body) + |> Eval.andThen + (\res -> + Eval.modifyEnv (Env.leave env.currentFrameId) + |> Eval.map (\_ -> res) + ) + ) + + +evalNoApply : MalExpr -> Eval MalExpr +evalNoApply ast = + debug "evalNoApply" + (printString True ast) + (case ast of + MalList [] -> + Eval.succeed ast + + MalList ((MalSymbol "def!") :: args) -> + evalDef args + + MalList ((MalSymbol "let*") :: args) -> + evalLet args + + MalList ((MalSymbol "do") :: args) -> + evalDo args + + MalList ((MalSymbol "if") :: args) -> + evalIf args + + MalList ((MalSymbol "fn*") :: args) -> + evalFn args + + MalList list -> + evalList list + |> Eval.andThen + (\newList -> + case newList of + [] -> + Eval.fail "can't happen" + + (MalFunction (CoreFunc fn)) :: args -> + fn args + + (MalFunction (UserFunc { fn })) :: args -> + fn args + + fn :: _ -> + Eval.fail ((printString True fn) ++ " is not a function") + ) + + _ -> + evalAst ast + ) + + +evalAst : MalExpr -> Eval MalExpr +evalAst ast = + case ast of + MalSymbol sym -> + -- Lookup symbol in env and return value or raise error if not found. + Eval.withEnv + (\env -> + case Env.get sym env of + Ok val -> + Eval.succeed val + + Err msg -> + Eval.fail msg + ) + + MalList list -> + -- Return new list that is result of calling eval on each element of list. + evalList list + |> Eval.map MalList + + MalVector vec -> + evalList (Array.toList vec) + |> Eval.map (Array.fromList >> MalVector) + + MalMap map -> + evalList (Dict.values map) + |> Eval.map + (zip (Dict.keys map) + >> Dict.fromList + >> MalMap + ) + + _ -> + Eval.succeed ast + + +evalList : List MalExpr -> Eval (List MalExpr) +evalList list = + let + go list acc = + case list of + [] -> + Eval.succeed (List.reverse acc) + + x :: rest -> + eval x + |> Eval.andThen + (\val -> + go rest (val :: acc) + ) + in + go list [] + + +evalDef : List MalExpr -> Eval MalExpr +evalDef args = + case args of + [ MalSymbol name, uneValue ] -> + eval uneValue + |> Eval.andThen + (\value -> + Eval.modifyEnv (Env.set name value) + |> Eval.andThen (\_ -> Eval.succeed value) + ) + + _ -> + Eval.fail "def! expected two args: name and value" + + +evalLet : List MalExpr -> Eval MalExpr +evalLet args = + let + evalBinds binds = + case binds of + (MalSymbol name) :: expr :: rest -> + eval expr + |> Eval.andThen + (\value -> + Eval.modifyEnv (Env.set name value) + |> Eval.andThen + (\_ -> + if List.isEmpty rest then + Eval.succeed () + else + evalBinds rest + ) + ) + + _ -> + Eval.fail "let* expected an even number of binds (symbol expr ..)" + + go binds body = + Eval.modifyEnv Env.push + |> Eval.andThen (\_ -> evalBinds binds) + |> Eval.andThen (\_ -> evalNoApply body) + |> Eval.andThen + (\res -> + Eval.modifyEnv Env.pop + |> Eval.map (\_ -> res) + ) + in + case args of + [ MalList binds, body ] -> + go binds body + + [ MalVector bindsVec, body ] -> + go (Array.toList bindsVec) body + + _ -> + Eval.fail "let* expected two args: binds and a body" + + +evalDo : List MalExpr -> Eval MalExpr +evalDo args = + case List.reverse args of + last :: rest -> + evalList (List.reverse rest) + |> Eval.andThen (\_ -> evalNoApply last) + + [] -> + Eval.fail "do expected at least one arg" + + +evalIf : List MalExpr -> Eval MalExpr +evalIf args = + let + isThruthy expr = + expr /= MalNil && expr /= (MalBool False) + + go condition trueExpr falseExpr = + eval condition + |> Eval.map isThruthy + |> Eval.andThen + (\cond -> + evalNoApply + (if cond then + trueExpr + else + falseExpr + ) + ) + in + case args of + [ condition, trueExpr ] -> + go condition trueExpr MalNil + + [ condition, trueExpr, falseExpr ] -> + go condition trueExpr falseExpr + + _ -> + Eval.fail "if expected at least two args" + + +evalFn : List MalExpr -> Eval MalExpr +evalFn args = + let + {- Extract symbols from the binds list and verify their uniqueness -} + extractSymbols acc list = + case list of + [] -> + Ok (List.reverse acc) + + (MalSymbol name) :: rest -> + if List.member name acc then + Err "all binds must have unique names" + else + extractSymbols (name :: acc) rest + + _ -> + Err "all binds in fn* must be a symbol" + + parseBinds list = + case List.reverse list of + var :: "&" :: rest -> + Ok <| bindVarArgs (List.reverse rest) var + + _ -> + if List.member "&" list then + Err "varargs separator '&' is used incorrectly" + else + Ok <| bindArgs list + + extractAndParse = + extractSymbols [] >> Result.andThen parseBinds + + bindArgs binds args = + let + numBinds = + List.length binds + in + if List.length args /= numBinds then + Err <| + "function expected " + ++ (toString numBinds) + ++ " arguments" + else + Ok <| zip binds args + + bindVarArgs binds var args = + let + minArgs = + List.length binds + + varArgs = + MalList (List.drop minArgs args) + in + if List.length args < minArgs then + Err <| + "function expected at least " + ++ (toString minArgs) + ++ " arguments" + else + Ok <| zip binds args ++ [ ( var, varArgs ) ] + + makeFn frameId binder body = + MalFunction <| + UserFunc + { frameId = frameId + , fn = + \args -> + case binder args of + Ok bound -> + Eval.succeed <| + -- TODO : choice Env.enter prematurely? + -- I think it is needed by the garbage collect.. + MalApply + { frameId = frameId + , bound = bound + , body = body + } + + Err msg -> + Eval.fail msg + } + + go bindsList body = + case extractAndParse bindsList of + Ok binder -> + Eval.modifyEnv Env.ref + -- reference the current frame. + |> Eval.andThen + (\_ -> + Eval.withEnv + (\env -> + Eval.succeed + (makeFn env.currentFrameId binder body) + ) + ) + + Err msg -> + Eval.fail msg + in + case args of + [ MalList bindsList, body ] -> + go bindsList body + + [ MalVector bindsVec, body ] -> + go (Array.toList bindsVec) body + + _ -> + Eval.fail "fn* expected two args: binds list and body" + + +print : MalExpr -> String +print = + printString True + + +{-| Read-Eval-Print. + +Doesn't actually run the Eval but returns the monad. + +-} +rep : String -> Maybe (Eval MalExpr) +rep input = + case readString input of + Ok Nothing -> + Nothing + + Err msg -> + Just (Eval.fail msg) + + Ok (Just ast) -> + eval ast |> Just From fe50dd7a4950e80968b1d03e0a819ef8092b2db1 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sun, 18 Jun 2017 21:38:48 +0200 Subject: [PATCH 0346/2308] Elm: Step 4-6 tests succeed --- elm/Core.elm | 165 +++++++++-- elm/Eval.elm | 10 + elm/Printer.elm | 47 ++-- elm/Types.elm | 7 +- elm/step1_read_print.elm | 4 +- elm/step4_if_fn_do.elm | 44 +-- elm/step5_tco.elm | 66 +++-- elm/step6_file.elm | 148 +++++++--- elm/step7_quote.elm | 583 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 934 insertions(+), 140 deletions(-) create mode 100644 elm/step7_quote.elm diff --git a/elm/Core.elm b/elm/Core.elm index 315f8d07ed..a4038e4682 100644 --- a/elm/Core.elm +++ b/elm/Core.elm @@ -5,8 +5,10 @@ import Env import Eval import Printer exposing (printString) import Array +import Dict import IO exposing (IO(..)) import Reader +import Utils exposing (zip) ns : Env @@ -63,28 +65,101 @@ ns = _ -> Eval.fail "unsupported arguments" + equalLists a b = + case ( a, b ) of + ( [], [] ) -> + True + + ( x :: xs, y :: ys ) -> + if deepEquals x y then + equalLists xs ys + else + False + + _ -> + False + + compareListTo list other = + case other of + MalList otherList -> + equalLists list otherList + + MalVector vec -> + equalLists list (Array.toList vec) + + _ -> + False + + equalMaps a b = + if Dict.keys a /= Dict.keys b then + False + else + zip (Dict.values a) (Dict.values b) + |> List.map (uncurry deepEquals) + |> List.all identity + + deepEquals a b = + case ( a, b ) of + ( MalList list, MalList otherList ) -> + equalLists list otherList + + ( MalList list, MalVector vec ) -> + equalLists list (Array.toList vec) + + ( MalList _, _ ) -> + False + + ( MalVector vec, MalList list ) -> + equalLists (Array.toList vec) list + + ( MalVector vec, MalVector otherVec ) -> + equalLists (Array.toList vec) (Array.toList otherVec) + + ( MalVector _, _ ) -> + False + + ( MalMap map, MalMap otherMap ) -> + equalMaps map otherMap + + ( MalMap _, _ ) -> + False + + ( _, MalMap _ ) -> + False + + _ -> + a == b + {- = -} equals args = case args of [ a, b ] -> - Eval.succeed <| MalBool (a == b) + Eval.succeed <| MalBool (deepEquals a b) _ -> Eval.fail "unsupported arguments" {- pr-str -} - prStr = - List.map (printString True) - >> String.join " " - >> MalString - >> Eval.succeed + prStr args = + Eval.withEnv + (\env -> + args + |> List.map (printString env True) + |> String.join " " + |> MalString + |> Eval.succeed + ) {- str -} - str = - List.map (printString False) - >> String.join "" - >> MalString - >> Eval.succeed + str args = + Eval.withEnv + (\env -> + args + |> List.map (printString env False) + |> String.join "" + |> MalString + |> Eval.succeed + ) {- helper function to write a string to stdout -} writeLine str = @@ -98,15 +173,23 @@ ns = Eval.fail "wrong IO, expected LineWritten" ) - prn = - List.map (printString True) - >> String.join " " - >> writeLine + prn args = + Eval.withEnv + (\env -> + args + |> List.map (printString env True) + |> String.join " " + |> writeLine + ) - println = - List.map (printString False) - >> String.join " " - >> writeLine + println args = + Eval.withEnv + (\env -> + args + |> List.map (printString env False) + |> String.join " " + |> writeLine + ) printEnv args = case args of @@ -196,12 +279,13 @@ ns = CoreFunc fn -> fn args - UserFunc { fn } -> - fn args + UserFunc { eagerFn } -> + eagerFn args swap args = case args of (MalAtom atomId) :: (MalFunction func) :: args -> + -- TODO eval apply here! Eval.withEnv (\env -> let @@ -236,6 +320,44 @@ ns = _ -> setDebug False + + typeof args = + case args of + [ MalInt _ ] -> + Eval.succeed <| MalSymbol "int" + + [ MalBool _ ] -> + Eval.succeed <| MalSymbol "bool" + + [ MalString _ ] -> + Eval.succeed <| MalSymbol "string" + + [ MalKeyword _ ] -> + Eval.succeed <| MalSymbol "keyword" + + [ MalSymbol _ ] -> + Eval.succeed <| MalSymbol "symbol" + + [ MalNil ] -> + Eval.succeed <| MalSymbol "nil" + + [ MalList _ ] -> + Eval.succeed <| MalSymbol "vector" + + [ MalVector _ ] -> + Eval.succeed <| MalSymbol "vector" + + [ MalMap _ ] -> + Eval.succeed <| MalSymbol "vector" + + [ MalFunction _ ] -> + Eval.succeed <| MalSymbol "function" + + [ MalAtom _ ] -> + Eval.succeed <| MalSymbol "atom" + + _ -> + Eval.fail "unsupported arguments" in Env.global |> Env.set "+" (makeFn <| binaryOp (+) MalInt) @@ -265,6 +387,7 @@ ns = |> Env.set "swap!" (makeFn swap) |> Env.set "gc" (makeFn gc) |> Env.set "debug!" (makeFn debug) + |> Env.set "typeof" (makeFn typeof) malInit : List String diff --git a/elm/Eval.elm b/elm/Eval.elm index daa1472506..4c581084ac 100644 --- a/elm/Eval.elm +++ b/elm/Eval.elm @@ -108,3 +108,13 @@ runLoop f expr env = Right expr -> ( env, EvalOk expr ) + + +fromResult : Result String a -> Eval a +fromResult res = + case res of + Ok val -> + succeed val + + Err msg -> + fail msg diff --git a/elm/Printer.elm b/elm/Printer.elm index e23d0fd992..0da4e2dda7 100644 --- a/elm/Printer.elm +++ b/elm/Printer.elm @@ -4,10 +4,11 @@ import Array exposing (Array) import Dict exposing (Dict) import Types exposing (Env, MalExpr(..), keywordPrefix) import Utils exposing (encodeString, wrap) +import Env -printString : Bool -> MalExpr -> String -printString readably ast = +printString : Env -> Bool -> MalExpr -> String +printString env readably ast = case ast of MalNil -> "nil" @@ -22,7 +23,7 @@ printString readably ast = toString int MalString str -> - printRawString readably str + printRawString env readably str MalSymbol sym -> sym @@ -31,49 +32,53 @@ printString readably ast = kw MalList list -> - printList readably list + printList env readably list MalVector vec -> - printVector readably vec + printVector env readably vec MalMap map -> - printMap readably map + printMap env readably map MalFunction _ -> "#" MalAtom atomId -> - "#" + let + value = + Env.getAtom atomId env + in + "(atom " ++ (printString env True value) ++ ")" MalApply _ -> "#" -printRawString : Bool -> String -> String -printRawString readably str = +printRawString : Env -> Bool -> String -> String +printRawString env readably str = if readably then encodeString str else str -printList : Bool -> List MalExpr -> String -printList readably = - List.map (printString readably) +printList : Env -> Bool -> List MalExpr -> String +printList env readably = + List.map (printString env readably) >> String.join " " >> wrap "(" ")" -printVector : Bool -> Array MalExpr -> String -printVector readably = - Array.map (printString readably) +printVector : Env -> Bool -> Array MalExpr -> String +printVector env readably = + Array.map (printString env readably) >> Array.toList >> String.join " " >> wrap "[" "]" -printMap : Bool -> Dict String MalExpr -> String -printMap readably = +printMap : Env -> Bool -> Dict String MalExpr -> String +printMap env readably = let -- Strip off the keyword prefix if it is there. printKey k = @@ -82,13 +87,13 @@ printMap readably = if prefix == keywordPrefix then rest else - printRawString readably k + printRawString env readably k _ -> - printRawString readably k + printRawString env readably k printEntry ( k, v ) = - (printKey k) ++ " " ++ (printString readably v) + (printKey k) ++ " " ++ (printString env readably v) in Dict.toList >> List.map printEntry @@ -120,7 +125,7 @@ printEnv env = printFrame k v :: acc printDatum k v acc = - (k ++ " = " ++ (printString True v)) :: acc + (k ++ " = " ++ (printString env True v)) :: acc in "--- Environment ---\n" ++ "Current frame: #" diff --git a/elm/Types.elm b/elm/Types.elm index a3ef598f9c..d7393f814c 100644 --- a/elm/Types.elm +++ b/elm/Types.elm @@ -31,10 +31,13 @@ type alias Env = } +type alias EvalCont a = IO -> Eval a + + type EvalResult res = EvalErr String | EvalOk res - | EvalIO (Cmd Msg) (IO -> Eval res) + | EvalIO (Cmd Msg) (EvalCont res) type alias EvalContext res = @@ -51,7 +54,7 @@ type alias MalFn = type MalFunction = CoreFunc MalFn - | UserFunc { frameId : Int, fn : MalFn } + | UserFunc { frameId : Int, lazyFn : MalFn, eagerFn : MalFn } type alias ApplyRec = diff --git a/elm/step1_read_print.elm b/elm/step1_read_print.elm index cff0e9a6bc..c0653c81b2 100644 --- a/elm/step1_read_print.elm +++ b/elm/step1_read_print.elm @@ -6,7 +6,7 @@ import Platform exposing (programWithFlags) import Types exposing (MalExpr(..)) import Reader exposing (readString) import Printer exposing (printString) -import Utils exposing (maybeToList) +import Env main : Program Flags Model Msg @@ -86,7 +86,7 @@ eval ast = print : MalExpr -> String print = - printString True + printString Env.global True {-| Read-Eval-Print diff --git a/elm/step4_if_fn_do.elm b/elm/step4_if_fn_do.elm index e6c31954da..be30d08aa5 100644 --- a/elm/step4_if_fn_do.elm +++ b/elm/step4_if_fn_do.elm @@ -120,7 +120,7 @@ run : Env -> Eval MalExpr -> ( Model, Cmd Msg ) run env expr = case Eval.run env expr of ( env, EvalOk expr ) -> - ( ReplActive env, writeLine (print expr) ) + ( ReplActive env, writeLine (print env expr) ) ( env, EvalErr msg ) -> ( ReplActive env, writeLine ("ERR:" ++ msg) ) @@ -178,11 +178,14 @@ eval ast = (MalFunction (CoreFunc fn)) :: args -> fn args - (MalFunction (UserFunc { fn })) :: args -> - fn args + (MalFunction (UserFunc { eagerFn })) :: args -> + eagerFn args fn :: _ -> - Eval.fail ((printString True fn) ++ " is not a function") + Eval.withEnv + (\env -> + Eval.fail ((printString env True fn) ++ " is not a function") + ) ) _ -> @@ -408,18 +411,21 @@ evalFn args = Ok <| zip binds args ++ [ ( var, varArgs ) ] makeFn frameId binder body = - MalFunction <| - UserFunc - { frameId = frameId - , fn = - \args -> - case binder args of - Ok bound -> - Eval.enter frameId bound (eval body) - - Err msg -> - Eval.fail msg - } + let + fn args = + case binder args of + Ok bound -> + Eval.enter frameId bound (eval body) + + Err msg -> + Eval.fail msg + in + MalFunction <| + UserFunc + { frameId = frameId + , lazyFn = fn + , eagerFn = fn + } go bindsList body = case extractAndParse bindsList of @@ -449,9 +455,9 @@ evalFn args = Eval.fail "fn* expected two args: binds list and body" -print : MalExpr -> String -print = - printString True +print : Env -> MalExpr -> String +print env = + printString env True {-| Read-Eval-Print. diff --git a/elm/step5_tco.elm b/elm/step5_tco.elm index 1ae5dcf574..da7b9c437f 100644 --- a/elm/step5_tco.elm +++ b/elm/step5_tco.elm @@ -120,7 +120,7 @@ run : Env -> Eval MalExpr -> ( Model, Cmd Msg ) run env expr = case Eval.run env expr of ( env, EvalOk expr ) -> - ( ReplActive env, writeLine (print expr) ) + ( ReplActive env, writeLine (print env expr) ) ( env, EvalErr msg ) -> ( ReplActive env, writeLine ("ERR:" ++ msg) ) @@ -146,11 +146,11 @@ read = readString -debug : String -> a -> Eval b -> Eval b -debug msg value e = +debug : String -> (Env -> a) -> Eval b -> Eval b +debug msg f e = Eval.withEnv (\env -> - Env.debug env msg value + Env.debug env msg (f env) |> always e ) @@ -163,7 +163,7 @@ eval ast = MalApply app -> Left (debug "evalApply" - (printString True expr) + (\env -> printString env True expr) (evalApply app) ) @@ -191,7 +191,7 @@ evalApply { frameId, bound, body } = evalNoApply : MalExpr -> Eval MalExpr evalNoApply ast = debug "evalNoApply" - (printString True ast) + (\env -> printString env True ast) (case ast of MalList [] -> Eval.succeed ast @@ -222,11 +222,14 @@ evalNoApply ast = (MalFunction (CoreFunc fn)) :: args -> fn args - (MalFunction (UserFunc { fn })) :: args -> - fn args + (MalFunction (UserFunc { lazyFn })) :: args -> + lazyFn args fn :: _ -> - Eval.fail ((printString True fn) ++ " is not a function") + Eval.withEnv + (\env -> + Eval.fail ((printString env True fn) ++ " is not a function") + ) ) _ -> @@ -450,24 +453,27 @@ evalFn args = makeFn frameId binder body = MalFunction <| - UserFunc - { frameId = frameId - , fn = - \args -> - case binder args of - Ok bound -> - Eval.succeed <| - -- TODO : choice Env.enter prematurely? - -- I think it is needed by the garbage collect.. - MalApply - { frameId = frameId - , bound = bound - , body = body - } - - Err msg -> - Eval.fail msg - } + let + lazyFn args = + case binder args of + Ok bound -> + Eval.succeed <| + -- TODO : choice Env.enter prematurely? + -- I think it is needed by the garbage collect.. + MalApply + { frameId = frameId + , bound = bound + , body = body + } + + Err msg -> + Eval.fail msg + in + UserFunc + { frameId = frameId + , lazyFn = lazyFn + , eagerFn = lazyFn >> Eval.andThen eval + } go bindsList body = case extractAndParse bindsList of @@ -497,9 +503,9 @@ evalFn args = Eval.fail "fn* expected two args: binds list and body" -print : MalExpr -> String -print = - printString True +print : Env -> MalExpr -> String +print env = + printString env True {-| Read-Eval-Print. diff --git a/elm/step6_file.elm b/elm/step6_file.elm index 789f7ee7d9..c5f4edd3d0 100644 --- a/elm/step6_file.elm +++ b/elm/step6_file.elm @@ -24,16 +24,21 @@ main = } +type alias Args = + List String + + type alias Flags = - { args : List String + { args : Args } type Model - = InitIO Env (IO -> Eval MalExpr) - | InitError String + = InitIO Args Env (IO -> Eval MalExpr) + | ScriptIO Env (IO -> Eval MalExpr) | ReplActive Env | ReplIO Env (IO -> Eval MalExpr) + | Stopped init : Flags -> ( Model, Cmd Msg ) @@ -45,6 +50,7 @@ init { args } = initEnv = Core.ns |> Env.set "eval" (makeFn malEval) + |> Env.set "*ARGV*" (MalList (args |> List.map MalString)) evalMalInit = Core.malInit @@ -54,20 +60,27 @@ init { args } = (\b a -> a |> Eval.andThen (\_ -> b)) (Eval.succeed MalNil) in - runInit initEnv evalMalInit + runInit args initEnv evalMalInit update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case model of - InitError _ -> - -- ignore all + Stopped -> ( model, Cmd.none ) - InitIO env cont -> + InitIO args env cont -> case msg of Input (Ok io) -> - runInit env (cont io) + runInit args env (cont io) + + Input (Err msg) -> + Debug.crash msg + + ScriptIO env cont -> + case msg of + Input (Ok io) -> + runScriptLoop env (cont io) Input (Err msg) -> Debug.crash msg @@ -104,27 +117,66 @@ update msg model = Debug.crash msg ( model, Cmd.none ) -runInit : Env -> Eval MalExpr -> ( Model, Cmd Msg ) -runInit env expr = +runInit : Args -> Env -> Eval MalExpr -> ( Model, Cmd Msg ) +runInit args env expr = case Eval.run env expr of ( env, EvalOk expr ) -> - -- Init went okay, start REPL. - ( ReplActive env, readLine prompt ) + -- Init went okay. + case args of + -- If we got no args: start REPL. + [] -> + ( ReplActive env, readLine prompt ) + + -- Run the script in the first argument. + -- Put the rest of the arguments as *ARGV*. + filename :: argv -> + runScript filename argv env ( env, EvalErr msg ) -> -- Init failed, don't start REPL. - ( InitError msg, writeLine ("ERR:" ++ msg) ) + ( Stopped, writeLine ("ERR:" ++ msg) ) ( env, EvalIO cmd cont ) -> -- IO in init. - ( InitIO env cont, cmd ) + ( InitIO args env cont, cmd ) + + +runScript : String -> List String -> Env -> ( Model, Cmd Msg ) +runScript filename argv env = + let + malArgv = + MalList (List.map MalString argv) + + newEnv = + env |> Env.set "*ARGV*" malArgv + + program = + MalList + [ MalSymbol "load-file" + , MalString filename + ] + in + runScriptLoop newEnv (eval program) + + +runScriptLoop : Env -> Eval MalExpr -> ( Model, Cmd Msg ) +runScriptLoop env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + ( Stopped, Cmd.none ) + + ( env, EvalErr msg ) -> + ( Stopped, writeLine ("ERR:" ++ msg) ) + + ( env, EvalIO cmd cont ) -> + ( ScriptIO env cont, cmd ) run : Env -> Eval MalExpr -> ( Model, Cmd Msg ) run env expr = case Eval.run env expr of ( env, EvalOk expr ) -> - ( ReplActive env, writeLine (print expr) ) + ( ReplActive env, writeLine (print env expr) ) ( env, EvalErr msg ) -> ( ReplActive env, writeLine ("ERR:" ++ msg) ) @@ -150,11 +202,11 @@ read = readString -debug : String -> a -> Eval b -> Eval b -debug msg value e = +debug : String -> (Env -> a) -> Eval b -> Eval b +debug msg f e = Eval.withEnv (\env -> - Env.debug env msg value + Env.debug env msg (f env) |> always e ) @@ -167,7 +219,7 @@ eval ast = MalApply app -> Left (debug "evalApply" - (printString True expr) + (\env -> printString env True expr) (evalApply app) ) @@ -205,7 +257,7 @@ evalApply { frameId, bound, body } = evalNoApply : MalExpr -> Eval MalExpr evalNoApply ast = debug "evalNoApply" - (printString True ast) + (\env -> printString env True ast) (case ast of MalList [] -> Eval.succeed ast @@ -236,11 +288,14 @@ evalNoApply ast = (MalFunction (CoreFunc fn)) :: args -> fn args - (MalFunction (UserFunc { fn })) :: args -> - fn args + (MalFunction (UserFunc { lazyFn })) :: args -> + lazyFn args fn :: _ -> - Eval.fail ((printString True fn) ++ " is not a function") + Eval.withEnv + (\env -> + Eval.fail ((printString env True fn) ++ " is not a function") + ) ) _ -> @@ -309,7 +364,7 @@ evalDef args = eval uneValue |> Eval.andThen (\value -> - Eval.modifyEnv (Env.set name value) + Eval.modifyEnv (Env.def name value) |> Eval.andThen (\_ -> Eval.succeed value) ) @@ -464,24 +519,27 @@ evalFn args = makeFn frameId binder body = MalFunction <| - UserFunc - { frameId = frameId - , fn = - \args -> - case binder args of - Ok bound -> - Eval.succeed <| - -- TODO : choice Env.enter prematurely? - -- I think it is needed by the garbage collect.. - MalApply - { frameId = frameId - , bound = bound - , body = body - } - - Err msg -> - Eval.fail msg - } + let + lazyFn args = + case binder args of + Ok bound -> + Eval.succeed <| + -- TODO : choice Env.enter prematurely? + -- I think it is needed by the garbage collect.. + MalApply + { frameId = frameId + , bound = bound + , body = body + } + + Err msg -> + Eval.fail msg + in + UserFunc + { frameId = frameId + , lazyFn = lazyFn + , eagerFn = lazyFn >> Eval.andThen eval + } go bindsList body = case extractAndParse bindsList of @@ -511,9 +569,9 @@ evalFn args = Eval.fail "fn* expected two args: binds list and body" -print : MalExpr -> String -print = - printString True +print : Env -> MalExpr -> String +print env = + printString env True {-| Read-Eval-Print. diff --git a/elm/step7_quote.elm b/elm/step7_quote.elm new file mode 100644 index 0000000000..71be893dca --- /dev/null +++ b/elm/step7_quote.elm @@ -0,0 +1,583 @@ +port module Main exposing (..) + +import Array +import Dict exposing (Dict) +import IO exposing (..) +import Json.Decode exposing (decodeValue) +import Platform exposing (programWithFlags) +import Types exposing (..) +import Reader exposing (readString) +import Printer exposing (printString) +import Utils exposing (maybeToList, zip, last, justValues) +import Env +import Core +import Eval + + +main : Program Flags Model Msg +main = + programWithFlags + { init = init + , update = update + , subscriptions = + \model -> input (decodeValue decodeIO >> Input) + } + + +type alias Args = + List String + + +type alias Flags = + { args : Args + } + + +type Model + = InitIO Args Env (IO -> Eval MalExpr) + | ScriptIO Env (IO -> Eval MalExpr) + | ReplActive Env + | ReplIO Env (IO -> Eval MalExpr) + | Stopped + + +init : Flags -> ( Model, Cmd Msg ) +init { args } = + let + makeFn = + CoreFunc >> MalFunction + + initEnv = + Core.ns + |> Env.set "eval" (makeFn malEval) + |> Env.set "*ARGV*" (MalList (args |> List.map MalString)) + + evalMalInit = + Core.malInit + |> List.map rep + |> justValues + |> List.foldl + (\b a -> a |> Eval.andThen (\_ -> b)) + (Eval.succeed MalNil) + in + runInit args initEnv evalMalInit + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case model of + Stopped -> + ( model, Cmd.none ) + + InitIO args env cont -> + case msg of + Input (Ok io) -> + runInit args env (cont io) + + Input (Err msg) -> + Debug.crash msg + + ScriptIO env cont -> + case msg of + Input (Ok io) -> + runScriptLoop env (cont io) + + Input (Err msg) -> + Debug.crash msg + + ReplActive env -> + case msg of + Input (Ok (LineRead (Just line))) -> + case rep line of + Just expr -> + run env expr + + Nothing -> + ( model, readLine prompt ) + + Input (Ok LineWritten) -> + ( model, readLine prompt ) + + Input (Ok (LineRead Nothing)) -> + -- Ctrl+D = The End. + ( model, Cmd.none ) + + Input (Ok io) -> + Debug.crash "unexpected IO received: " io + + Input (Err msg) -> + Debug.crash msg + + ReplIO env cont -> + case msg of + Input (Ok io) -> + run env (cont io) + + Input (Err msg) -> + Debug.crash msg ( model, Cmd.none ) + + +runInit : Args -> Env -> Eval MalExpr -> ( Model, Cmd Msg ) +runInit args env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + -- Init went okay. + case args of + -- If we got no args: start REPL. + [] -> + ( ReplActive env, readLine prompt ) + + -- Run the script in the first argument. + -- Put the rest of the arguments as *ARGV*. + filename :: argv -> + runScript filename argv env + + ( env, EvalErr msg ) -> + -- Init failed, don't start REPL. + ( Stopped, writeLine ("ERR:" ++ msg) ) + + ( env, EvalIO cmd cont ) -> + -- IO in init. + ( InitIO args env cont, cmd ) + + +runScript : String -> List String -> Env -> ( Model, Cmd Msg ) +runScript filename argv env = + let + malArgv = + MalList (List.map MalString argv) + + newEnv = + env |> Env.set "*ARGV*" malArgv + + program = + MalList + [ MalSymbol "load-file" + , MalString filename + ] + in + runScriptLoop newEnv (eval program) + + +runScriptLoop : Env -> Eval MalExpr -> ( Model, Cmd Msg ) +runScriptLoop env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + ( Stopped, Cmd.none ) + + ( env, EvalErr msg ) -> + ( Stopped, writeLine ("ERR:" ++ msg) ) + + ( env, EvalIO cmd cont ) -> + ( ScriptIO env cont, cmd ) + + +run : Env -> Eval MalExpr -> ( Model, Cmd Msg ) +run env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + ( ReplActive env, writeLine (print env expr) ) + + ( env, EvalErr msg ) -> + ( ReplActive env, writeLine ("ERR:" ++ msg) ) + + ( env, EvalIO cmd cont ) -> + ( ReplIO env cont, cmd ) + + +prompt : String +prompt = + "user> " + + +{-| read can return three things: + +Ok (Just expr) -> parsed okay +Ok Nothing -> empty string (only whitespace and/or comments) +Err msg -> parse error + +-} +read : String -> Result String (Maybe MalExpr) +read = + readString + + +debug : String -> (Env -> a) -> Eval b -> Eval b +debug msg f e = + Eval.withEnv + (\env -> + Env.debug env msg (f env) + |> always e + ) + + +eval : MalExpr -> Eval MalExpr +eval ast = + let + apply expr = + case expr of + MalApply app -> + Left + (debug "evalApply" + (\env -> printString env True expr) + (evalApply app) + ) + + _ -> + Right expr + in + evalNoApply ast + |> Eval.andThen (Eval.runLoop apply) + + +malEval : List MalExpr -> Eval MalExpr +malEval args = + case args of + [ expr ] -> + eval expr + + _ -> + Eval.fail "unsupported arguments" + + +evalApply : ApplyRec -> Eval MalExpr +evalApply { frameId, bound, body } = + Eval.withEnv + (\env -> + Eval.modifyEnv (Env.enter frameId bound) + |> Eval.andThen (\_ -> evalNoApply body) + |> Eval.andThen + (\res -> + Eval.modifyEnv (Env.leave env.currentFrameId) + |> Eval.map (\_ -> res) + ) + ) + + +evalNoApply : MalExpr -> Eval MalExpr +evalNoApply ast = + debug "evalNoApply" + (\env -> printString env True ast) + (case ast of + MalList [] -> + Eval.succeed ast + + MalList ((MalSymbol "def!") :: args) -> + evalDef args + + MalList ((MalSymbol "let*") :: args) -> + evalLet args + + MalList ((MalSymbol "do") :: args) -> + evalDo args + + MalList ((MalSymbol "if") :: args) -> + evalIf args + + MalList ((MalSymbol "fn*") :: args) -> + evalFn args + + MalList list -> + evalList list + |> Eval.andThen + (\newList -> + case newList of + [] -> + Eval.fail "can't happen" + + (MalFunction (CoreFunc fn)) :: args -> + fn args + + (MalFunction (UserFunc { lazyFn })) :: args -> + lazyFn args + + fn :: _ -> + Eval.withEnv + (\env -> + Eval.fail ((printString env True fn) ++ " is not a function") + ) + ) + + _ -> + evalAst ast + ) + + +evalAst : MalExpr -> Eval MalExpr +evalAst ast = + case ast of + MalSymbol sym -> + -- Lookup symbol in env and return value or raise error if not found. + Eval.withEnv (Env.get sym >> Eval.fromResult) + + MalList list -> + -- Return new list that is result of calling eval on each element of list. + evalList list + |> Eval.map MalList + + MalVector vec -> + evalList (Array.toList vec) + |> Eval.map (Array.fromList >> MalVector) + + MalMap map -> + evalList (Dict.values map) + |> Eval.map + (zip (Dict.keys map) + >> Dict.fromList + >> MalMap + ) + + _ -> + Eval.succeed ast + + +evalList : List MalExpr -> Eval (List MalExpr) +evalList list = + let + go list acc = + case list of + [] -> + Eval.succeed (List.reverse acc) + + x :: rest -> + eval x + |> Eval.andThen + (\val -> + go rest (val :: acc) + ) + in + go list [] + + +evalDef : List MalExpr -> Eval MalExpr +evalDef args = + case args of + [ MalSymbol name, uneValue ] -> + eval uneValue + |> Eval.andThen + (\value -> + Eval.modifyEnv (Env.def name value) + |> Eval.andThen (\_ -> Eval.succeed value) + ) + + _ -> + Eval.fail "def! expected two args: name and value" + + +evalLet : List MalExpr -> Eval MalExpr +evalLet args = + let + evalBinds binds = + case binds of + (MalSymbol name) :: expr :: rest -> + eval expr + |> Eval.andThen + (\value -> + Eval.modifyEnv (Env.set name value) + |> Eval.andThen + (\_ -> + if List.isEmpty rest then + Eval.succeed () + else + evalBinds rest + ) + ) + + _ -> + Eval.fail "let* expected an even number of binds (symbol expr ..)" + + go binds body = + Eval.modifyEnv Env.push + |> Eval.andThen (\_ -> evalBinds binds) + |> Eval.andThen (\_ -> evalNoApply body) + |> Eval.andThen + (\res -> + Eval.modifyEnv Env.pop + |> Eval.map (\_ -> res) + ) + in + case args of + [ MalList binds, body ] -> + go binds body + + [ MalVector bindsVec, body ] -> + go (Array.toList bindsVec) body + + _ -> + Eval.fail "let* expected two args: binds and a body" + + +evalDo : List MalExpr -> Eval MalExpr +evalDo args = + case List.reverse args of + last :: rest -> + evalList (List.reverse rest) + |> Eval.andThen (\_ -> evalNoApply last) + + [] -> + Eval.fail "do expected at least one arg" + + +evalIf : List MalExpr -> Eval MalExpr +evalIf args = + let + isThruthy expr = + expr /= MalNil && expr /= (MalBool False) + + go condition trueExpr falseExpr = + eval condition + |> Eval.map isThruthy + |> Eval.andThen + (\cond -> + evalNoApply + (if cond then + trueExpr + else + falseExpr + ) + ) + in + case args of + [ condition, trueExpr ] -> + go condition trueExpr MalNil + + [ condition, trueExpr, falseExpr ] -> + go condition trueExpr falseExpr + + _ -> + Eval.fail "if expected at least two args" + + +evalFn : List MalExpr -> Eval MalExpr +evalFn args = + let + {- Extract symbols from the binds list and verify their uniqueness -} + extractSymbols acc list = + case list of + [] -> + Ok (List.reverse acc) + + (MalSymbol name) :: rest -> + if List.member name acc then + Err "all binds must have unique names" + else + extractSymbols (name :: acc) rest + + _ -> + Err "all binds in fn* must be a symbol" + + parseBinds list = + case List.reverse list of + var :: "&" :: rest -> + Ok <| bindVarArgs (List.reverse rest) var + + _ -> + if List.member "&" list then + Err "varargs separator '&' is used incorrectly" + else + Ok <| bindArgs list + + extractAndParse = + extractSymbols [] >> Result.andThen parseBinds + + bindArgs binds args = + let + numBinds = + List.length binds + in + if List.length args /= numBinds then + Err <| + "function expected " + ++ (toString numBinds) + ++ " arguments" + else + Ok <| zip binds args + + bindVarArgs binds var args = + let + minArgs = + List.length binds + + varArgs = + MalList (List.drop minArgs args) + in + if List.length args < minArgs then + Err <| + "function expected at least " + ++ (toString minArgs) + ++ " arguments" + else + Ok <| zip binds args ++ [ ( var, varArgs ) ] + + makeFn frameId binder body = + MalFunction <| + let + lazyFn = + binder + >> Eval.fromResult + >> Eval.map + (\bound -> + -- TODO : choice Env.enter prematurely? + -- I think it is needed by the garbage collect.. + MalApply + { frameId = frameId + , bound = bound + , body = body + } + ) + in + UserFunc + { frameId = frameId + , lazyFn = lazyFn + , eagerFn = lazyFn >> Eval.andThen eval + } + + go bindsList body = + extractAndParse bindsList + |> Eval.fromResult + |> Eval.andThen + (\binder -> + Eval.modifyEnv Env.ref + -- reference the current frame. + |> Eval.andThen + (\_ -> + Eval.withEnv + (\env -> + Eval.succeed + (makeFn env.currentFrameId binder body) + ) + ) + ) + in + case args of + [ MalList bindsList, body ] -> + go bindsList body + + [ MalVector bindsVec, body ] -> + go (Array.toList bindsVec) body + + _ -> + Eval.fail "fn* expected two args: binds list and body" + + +print : Env -> MalExpr -> String +print env = + printString env True + + +{-| Read-Eval-Print. + +Doesn't actually run the Eval but returns the monad. + +-} +rep : String -> Maybe (Eval MalExpr) +rep input = + case readString input of + Ok Nothing -> + Nothing + + Err msg -> + Just (Eval.fail msg) + + Ok (Just ast) -> + eval ast |> Just From 071ce8a8ed0c71204bbe6cd07a46d2aedae27ac5 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sun, 18 Jun 2017 21:48:31 +0200 Subject: [PATCH 0347/2308] Elm: step 6 fixed eval/def --- elm/Env.elm | 13 +++++++------ elm/step4_if_fn_do.elm | 2 +- elm/step5_tco.elm | 2 +- elm/step6_file.elm | 13 +++++++++++-- elm/step7_quote.elm | 2 +- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/elm/Env.elm b/elm/Env.elm index e6a9bf7b91..f9ccb9306b 100644 --- a/elm/Env.elm +++ b/elm/Env.elm @@ -1,15 +1,16 @@ module Env exposing ( debug + , globalFrameId , global , push , pop + , jump , enter , leave , ref , get , set - , def , newAtom , getAtom , setAtom @@ -46,6 +47,11 @@ global = } +jump : Int -> Env -> Env +jump frameId env = + { env | currentFrameId = frameId } + + push : Env -> Env push env = let @@ -244,11 +250,6 @@ set name expr env = setInFrame env.currentFrameId name expr env -def : String -> MalExpr -> Env -> Env -def = - setInFrame globalFrameId - - get : String -> Env -> Result String MalExpr get name env = let diff --git a/elm/step4_if_fn_do.elm b/elm/step4_if_fn_do.elm index be30d08aa5..97b650f7db 100644 --- a/elm/step4_if_fn_do.elm +++ b/elm/step4_if_fn_do.elm @@ -253,7 +253,7 @@ evalDef args = eval uneValue |> Eval.andThen (\value -> - Eval.modifyEnv (Env.def name value) + Eval.modifyEnv (Env.set name value) |> Eval.andThen (\_ -> Eval.succeed value) ) diff --git a/elm/step5_tco.elm b/elm/step5_tco.elm index da7b9c437f..b8f205bdca 100644 --- a/elm/step5_tco.elm +++ b/elm/step5_tco.elm @@ -298,7 +298,7 @@ evalDef args = eval uneValue |> Eval.andThen (\value -> - Eval.modifyEnv (Env.def name value) + Eval.modifyEnv (Env.set name value) |> Eval.andThen (\_ -> Eval.succeed value) ) diff --git a/elm/step6_file.elm b/elm/step6_file.elm index c5f4edd3d0..6cf7b6db5e 100644 --- a/elm/step6_file.elm +++ b/elm/step6_file.elm @@ -234,7 +234,16 @@ malEval : List MalExpr -> Eval MalExpr malEval args = case args of [ expr ] -> - eval expr + Eval.withEnv + (\env -> + Eval.modifyEnv (Env.jump Env.globalFrameId) + |> Eval.andThen (\_ -> eval expr) + |> Eval.andThen + (\res -> + Eval.modifyEnv (Env.jump env.currentFrameId) + |> Eval.andThen (\_ -> Eval.succeed res) + ) + ) _ -> Eval.fail "unsupported arguments" @@ -364,7 +373,7 @@ evalDef args = eval uneValue |> Eval.andThen (\value -> - Eval.modifyEnv (Env.def name value) + Eval.modifyEnv (Env.set name value) |> Eval.andThen (\_ -> Eval.succeed value) ) diff --git a/elm/step7_quote.elm b/elm/step7_quote.elm index 71be893dca..5c5fd136cb 100644 --- a/elm/step7_quote.elm +++ b/elm/step7_quote.elm @@ -356,7 +356,7 @@ evalDef args = eval uneValue |> Eval.andThen (\value -> - Eval.modifyEnv (Env.def name value) + Eval.modifyEnv (Env.set name value) |> Eval.andThen (\_ -> Eval.succeed value) ) From 496726894d8812048c9a0a3dbd9f1e2cacc64775 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sun, 18 Jun 2017 22:21:25 +0200 Subject: [PATCH 0348/2308] Elm step 7: cons and concat --- elm/Core.elm | 26 ++++++++++++++++++++++++++ elm/Eval.elm | 12 ++++++++++++ elm/Makefile | 8 ++++---- elm/step7_quote.elm | 29 +++++++++-------------------- 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/elm/Core.elm b/elm/Core.elm index a4038e4682..d7f2cbc952 100644 --- a/elm/Core.elm +++ b/elm/Core.elm @@ -358,6 +358,30 @@ ns = _ -> Eval.fail "unsupported arguments" + + cons args = + case args of + [ e, MalList list ] -> + Eval.succeed <| MalList (e :: list) + + _ -> + Eval.fail "unsupported arguments" + + concat args = + let + go arg acc = + case arg of + MalList list -> + Eval.succeed (acc ++ list) + + MalVector vec -> + Eval.succeed (acc ++ Array.toList vec) + + _ -> + Eval.fail "unsupported arguments" + in + List.foldl (go >> Eval.andThen) (Eval.succeed []) args + |> Eval.map MalList in Env.global |> Env.set "+" (makeFn <| binaryOp (+) MalInt) @@ -388,6 +412,8 @@ ns = |> Env.set "gc" (makeFn gc) |> Env.set "debug!" (makeFn debug) |> Env.set "typeof" (makeFn typeof) + |> Env.set "cons" (makeFn cons) + |> Env.set "concat" (makeFn concat) malInit : List String diff --git a/elm/Eval.elm b/elm/Eval.elm index 4c581084ac..fec2d7fbd6 100644 --- a/elm/Eval.elm +++ b/elm/Eval.elm @@ -118,3 +118,15 @@ fromResult res = Err msg -> fail msg + + +{-| Chain the left and right Eval but ignore the right's result. +-} +ignore : Eval b -> Eval a -> Eval a +ignore right left = + left + |> andThen + (\res -> + right + |> andThen (\_ -> succeed res) + ) diff --git a/elm/Makefile b/elm/Makefile index 37c90f10ff..17e52a2c18 100644 --- a/elm/Makefile +++ b/elm/Makefile @@ -1,8 +1,8 @@ SOURCES_BASE = Reader.elm Printer.elm Utils.elm Types.elm Env.elm \ Core.elm Eval.elm IO.elm -SOURCES_STEPS = step0_repl.elm step4_if_fn_do.elm step5_tco.elm step6_file.elm step1_read_print.elm #step2_eval.elm \ - #step3_env.elm \ - step7_quote.ls step8_macros.ls step9_try.ls stepA_mal.ls +SOURCES_STEPS = step0_repl.elm step4_if_fn_do.elm step5_tco.elm step6_file.elm step1_read_print.elm step7_quote.elm + #step2_eval.elm step3_env.elm \ + step8_macros.ls step9_try.ls stepA_mal.ls SOURCES_LISP = #env.ls core.ls stepA_mal.ls SOURCES = $(SOURCES_STEPS) @@ -27,7 +27,7 @@ step1_read_print.js: Reader.elm Printer.elm Utils.elm Types.elm step4_if_fn_do.js: $(SOURCES_BASE) step5_tco.js: $(SOURCES_BASE) step6_file.js: $(SOURCES_BASE) -# step7_quote.js: utils.js reader.js printer.js env.js core.js +step7_quote.js: $(SOURCES_BASE) # step8_macros.js: utils.js reader.js printer.js env.js core.js # step9_try.js: utils.js reader.js printer.js env.js core.js # stepA_mal.js: utils.js reader.js printer.js env.js core.js diff --git a/elm/step7_quote.elm b/elm/step7_quote.elm index 5c5fd136cb..72bff9a8cb 100644 --- a/elm/step7_quote.elm +++ b/elm/step7_quote.elm @@ -246,11 +246,7 @@ evalApply { frameId, bound, body } = (\env -> Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.andThen - (\res -> - Eval.modifyEnv (Env.leave env.currentFrameId) - |> Eval.map (\_ -> res) - ) + |> Eval.ignore (Eval.modifyEnv (Env.leave env.currentFrameId)) ) @@ -390,11 +386,7 @@ evalLet args = Eval.modifyEnv Env.push |> Eval.andThen (\_ -> evalBinds binds) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.andThen - (\res -> - Eval.modifyEnv Env.pop - |> Eval.map (\_ -> res) - ) + |> Eval.ignore (Eval.modifyEnv Env.pop) in case args of [ MalList binds, body ] -> @@ -535,18 +527,15 @@ evalFn args = go bindsList body = extractAndParse bindsList |> Eval.fromResult + -- reference the current frame. + |> Eval.ignore (Eval.modifyEnv Env.ref) |> Eval.andThen (\binder -> - Eval.modifyEnv Env.ref - -- reference the current frame. - |> Eval.andThen - (\_ -> - Eval.withEnv - (\env -> - Eval.succeed - (makeFn env.currentFrameId binder body) - ) - ) + Eval.withEnv + (\env -> + Eval.succeed + (makeFn env.currentFrameId binder body) + ) ) in case args of From 16fbc20a27d08e3e946304015efbc44567c10f42 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Tue, 20 Jun 2017 17:23:00 +0200 Subject: [PATCH 0349/2308] Elm step 7-9 --- elm/Core.elm | 360 +++++++++++++++++++- elm/Eval.elm | 17 +- elm/Makefile | 31 +- elm/Types.elm | 10 +- elm/step0_repl.elm | 3 + elm/step4_if_fn_do.elm | 11 +- elm/step5_tco.elm | 13 +- elm/step6_file.elm | 17 +- elm/step7_quote.elm | 86 ++++- elm/step8_macros.elm | 727 +++++++++++++++++++++++++++++++++++++++ elm/step9_try.elm | 749 +++++++++++++++++++++++++++++++++++++++++ 11 files changed, 1983 insertions(+), 41 deletions(-) create mode 100644 elm/step8_macros.elm create mode 100644 elm/step9_try.elm diff --git a/elm/Core.elm b/elm/Core.elm index d7f2cbc952..751077c6e8 100644 --- a/elm/Core.elm +++ b/elm/Core.elm @@ -1,6 +1,6 @@ module Core exposing (..) -import Types exposing (MalExpr(..), MalFunction(..), Eval, Env) +import Types exposing (MalExpr(..), MalFunction(..), Eval, Env, keywordPrefix) import Env import Eval import Printer exposing (printString) @@ -285,7 +285,6 @@ ns = swap args = case args of (MalAtom atomId) :: (MalFunction func) :: args -> - -- TODO eval apply here! Eval.withEnv (\env -> let @@ -364,6 +363,9 @@ ns = [ e, MalList list ] -> Eval.succeed <| MalList (e :: list) + [ e, MalVector vec ] -> + Eval.succeed <| MalList (e :: (Array.toList vec)) + _ -> Eval.fail "unsupported arguments" @@ -382,6 +384,327 @@ ns = in List.foldl (go >> Eval.andThen) (Eval.succeed []) args |> Eval.map MalList + + nth args = + let + get list index = + if index < 0 then + Nothing + else if index == 0 then + List.head list + else + case list of + [] -> + Nothing + + _ :: rest -> + get rest (index - 1) + + make res = + case res of + Just value -> + Eval.succeed value + + Nothing -> + Eval.fail "index out of bounds" + in + case args of + [ MalList list, MalInt index ] -> + make <| get list index + + [ MalVector vec, MalInt index ] -> + make <| Array.get index vec + + _ -> + Eval.fail "unsupported arguments" + + first args = + let + make = + Eval.succeed << Maybe.withDefault MalNil + in + case args of + [ MalNil ] -> + Eval.succeed MalNil + + [ MalList list ] -> + make <| List.head list + + [ MalVector vec ] -> + make <| Array.get 0 vec + + _ -> + Eval.fail "unsupported arguments" + + rest args = + case args of + [ MalNil ] -> + Eval.succeed <| MalList [] + + [ MalList [] ] -> + Eval.succeed <| MalList [] + + [ MalList (head :: tail) ] -> + Eval.succeed <| MalList tail + + [ MalVector vec ] -> + Array.toList vec + |> List.tail + |> Maybe.withDefault [] + |> MalList + |> Eval.succeed + + _ -> + Eval.fail "unsupported arguments" + + throw args = + case args of + [ MalString msg ] -> + Eval.fail msg + + _ -> + Eval.fail "undefined exception" + + apply args = + case args of + (MalFunction func) :: rest -> + callFn func rest + + _ -> + Eval.fail "unsupported arguments" + + map args = + let + go func list acc = + case list of + [] -> + Eval.succeed <| MalList <| List.reverse acc + + inv :: rest -> + callFn func [ inv ] + |> Eval.andThen + (\outv -> + go func rest (outv :: acc) + ) + in + case args of + [ MalFunction func, MalList list ] -> + go func list [] + + [ MalFunction func, MalVector vec ] -> + go func (Array.toList vec) [] + + _ -> + Eval.fail "unsupported arguments" + + isNil args = + Eval.succeed <| + MalBool <| + case args of + MalNil :: _ -> + True + + _ -> + False + + isTrue args = + Eval.succeed <| + MalBool <| + case args of + (MalBool True) :: _ -> + True + + _ -> + False + + isFalse args = + Eval.succeed <| + MalBool <| + case args of + (MalBool False) :: _ -> + True + + _ -> + False + + isSymbol args = + Eval.succeed <| + MalBool <| + case args of + (MalSymbol _) :: _ -> + True + + _ -> + False + + isKeyword args = + Eval.succeed <| + MalBool <| + case args of + (MalKeyword _) :: _ -> + True + + _ -> + False + + isVector args = + Eval.succeed <| + MalBool <| + case args of + (MalVector _) :: _ -> + True + + _ -> + False + + isMap args = + Eval.succeed <| + MalBool <| + case args of + (MalMap _) :: _ -> + True + + _ -> + False + + symbol args = + case args of + [ MalString str ] -> + Eval.succeed <| MalSymbol str + + _ -> + Eval.fail "unsupported arguments" + + keyword args = + case args of + [ MalString str ] -> + Eval.succeed <| MalKeyword (String.cons ':' str) + + _ -> + Eval.fail "unsupported arguments" + + vector args = + Eval.succeed <| MalVector <| Array.fromList args + + parseKey key = + case key of + MalString str -> + Ok str + + MalKeyword keyword -> + Ok <| String.cons keywordPrefix keyword + + _ -> + Err "map key must be a symbol or keyword" + + buildMap list acc = + case list of + [] -> + Eval.succeed <| MalMap acc + + key :: value :: rest -> + parseKey key + |> Eval.fromResult + |> Eval.andThen + (\key -> + buildMap rest (Dict.insert key value acc) + ) + + _ -> + Eval.fail "expected an even number of key-value pairs" + + hashMap args = + buildMap args Dict.empty + + assoc args = + case args of + (MalMap dict) :: rest -> + buildMap rest dict + + _ -> + Eval.fail "unsupported arguments" + + dissoc args = + let + go keys acc = + case keys of + [] -> + Eval.succeed <| MalMap acc + + key :: rest -> + parseKey key + |> Eval.fromResult + |> Eval.andThen + (\key -> + go rest (Dict.remove key acc) + ) + in + case args of + (MalMap dict) :: keys -> + go keys dict + + _ -> + Eval.fail "unsupported arguments" + + get args = + case args of + [ MalNil, key ] -> + Eval.succeed MalNil + + [ MalMap dict, key ] -> + parseKey key + |> Eval.fromResult + |> Eval.map + (\key -> + Dict.get key dict + |> Maybe.withDefault MalNil + ) + + _ -> + Eval.fail "unsupported arguments" + + contains args = + case args of + [ MalMap dict, key ] -> + parseKey key + |> Eval.fromResult + |> Eval.map (\key -> Dict.member key dict) + |> Eval.map MalBool + + _ -> + Eval.fail "unsupported arguments" + + unparseKey key = + case String.uncons key of + Just ( prefix, rest ) -> + if prefix == keywordPrefix then + MalKeyword rest + else + MalString key + + _ -> + MalString key + + keys args = + case args of + [ MalMap dict ] -> + Dict.keys dict + |> List.map unparseKey + |> MalList + |> Eval.succeed + + _ -> + Eval.fail "unsupported arguments" + + vals args = + case args of + [ MalMap dict ] -> + Dict.values dict + |> MalList + |> Eval.succeed + + _ -> + Eval.fail "unsupported arguments" in Env.global |> Env.set "+" (makeFn <| binaryOp (+) MalInt) @@ -414,15 +737,24 @@ ns = |> Env.set "typeof" (makeFn typeof) |> Env.set "cons" (makeFn cons) |> Env.set "concat" (makeFn concat) - - -malInit : List String -malInit = - [ """(def! not - (fn* (a) - (if a false true)))""" - , """(def! load-file - (fn* (f) - (eval (read-string - (str "(do " (slurp f) ")")))))""" - ] + |> Env.set "nth" (makeFn nth) + |> Env.set "first" (makeFn first) + |> Env.set "rest" (makeFn rest) + |> Env.set "throw" (makeFn throw) + |> Env.set "nil?" (makeFn isNil) + |> Env.set "true?" (makeFn isTrue) + |> Env.set "false?" (makeFn isFalse) + |> Env.set "symbol?" (makeFn isSymbol) + |> Env.set "keyword?" (makeFn isKeyword) + |> Env.set "vector?" (makeFn isVector) + |> Env.set "map?" (makeFn isMap) + |> Env.set "symbol" (makeFn symbol) + |> Env.set "keyword" (makeFn keyword) + |> Env.set "vector" (makeFn vector) + |> Env.set "hash-map" (makeFn hashMap) + |> Env.set "assoc" (makeFn assoc) + |> Env.set "dissoc" (makeFn dissoc) + |> Env.set "get" (makeFn get) + |> Env.set "contains?" (makeFn contains) + |> Env.set "keys" (makeFn keys) + |> Env.set "vals" (makeFn vals) diff --git a/elm/Eval.elm b/elm/Eval.elm index fec2d7fbd6..3fc4cf7942 100644 --- a/elm/Eval.elm +++ b/elm/Eval.elm @@ -66,6 +66,19 @@ andThen f e env = ( env, EvalIO cmd (cont >> andThen f) ) +catchError : (String -> Eval a) -> Eval a -> Eval a +catchError f e env = + case apply e env of + ( env, EvalOk res ) -> + ( env, EvalOk res ) + + ( env, EvalErr msg ) -> + apply (f msg) env + + ( env, EvalIO cmd cont ) -> + ( env, EvalIO cmd (cont >> catchError f) ) + + fail : String -> Eval a fail msg env = ( env, EvalErr msg ) @@ -92,9 +105,9 @@ Stops if f returns (Right expr). Tail call optimized. -} -runLoop : (MalExpr -> Either (Eval MalExpr) MalExpr) -> MalExpr -> Eval MalExpr +runLoop : (MalExpr -> Env -> Either (Eval MalExpr) MalExpr) -> MalExpr -> Eval MalExpr runLoop f expr env = - case f expr of + case f expr env of Left e -> case apply e env of ( env, EvalOk expr ) -> diff --git a/elm/Makefile b/elm/Makefile index 17e52a2c18..83de99987b 100644 --- a/elm/Makefile +++ b/elm/Makefile @@ -1,10 +1,7 @@ -SOURCES_BASE = Reader.elm Printer.elm Utils.elm Types.elm Env.elm \ - Core.elm Eval.elm IO.elm -SOURCES_STEPS = step0_repl.elm step4_if_fn_do.elm step5_tco.elm step6_file.elm step1_read_print.elm step7_quote.elm - #step2_eval.elm step3_env.elm \ - step8_macros.ls step9_try.ls stepA_mal.ls -SOURCES_LISP = #env.ls core.ls stepA_mal.ls -SOURCES = $(SOURCES_STEPS) +SOURCES = step0_repl.elm step1_read_print.elm \ + step4_if_fn_do.elm step5_tco.elm step6_file.elm step7_quote.elm \ + step8_macros.elm step9_try.elm # stepA_mal.ls \ + #step2_eval.elm step3_env.elm BINS = $(SOURCES:%.elm=%.js) @@ -21,15 +18,21 @@ elm_packages: %.js: %.elm node_modules $(ELM) make $(@:%.js=%.elm) --output $@ -step1_read_print.js: Reader.elm Printer.elm Utils.elm Types.elm +STEP0_SOURCES = IO.elm +STEP1_SOURCES = $(STEP0_SOURCES) Reader.elm Printer.elm Utils.elm Types.elm Env.elm +STEP2_SOURCES = $(STEP1_SOURCES) +STEP3_SOURCES = $(STEP2_SOURCES) +STEP4_SOURCES = $(STEP3_SOURCES) Core.elm Eval.elm + +step1_read_print.js: $(STEP1_SOURCES) #step2_eval.js: Reader.elm Printer.elm Utils.elm Types.elm #step3_env.js: Reader.elm Printer.elm Utils.elm Types.elm Env.elm -step4_if_fn_do.js: $(SOURCES_BASE) -step5_tco.js: $(SOURCES_BASE) -step6_file.js: $(SOURCES_BASE) -step7_quote.js: $(SOURCES_BASE) -# step8_macros.js: utils.js reader.js printer.js env.js core.js -# step9_try.js: utils.js reader.js printer.js env.js core.js +step4_if_fn_do.js: $(STEP4_SOURCES) +step5_tco.js: $(STEP4_SOURCES) +step6_file.js: $(STEP4_SOURCES) +step7_quote.js: $(STEP4_SOURCES) +step8_macros.js: $(STEP4_SOURCES) +step9_try.js: $(STEP4_SOURCES) # stepA_mal.js: utils.js reader.js printer.js env.js core.js clean: diff --git a/elm/Types.elm b/elm/Types.elm index d7393f814c..81e62d02b3 100644 --- a/elm/Types.elm +++ b/elm/Types.elm @@ -31,7 +31,8 @@ type alias Env = } -type alias EvalCont a = IO -> Eval a +type alias EvalCont a = + IO -> Eval a type EvalResult res @@ -54,7 +55,12 @@ type alias MalFn = type MalFunction = CoreFunc MalFn - | UserFunc { frameId : Int, lazyFn : MalFn, eagerFn : MalFn } + | UserFunc + { frameId : Int + , lazyFn : MalFn + , eagerFn : MalFn + , isMacro : Bool + } type alias ApplyRec = diff --git a/elm/step0_repl.elm b/elm/step0_repl.elm index 41596b429a..972d8d6843 100644 --- a/elm/step0_repl.elm +++ b/elm/step0_repl.elm @@ -46,6 +46,9 @@ update msg model = Input (Ok (LineRead Nothing)) -> ( model, Cmd.none ) + Input (Ok _) -> + ( model, Cmd.none ) + Input (Err msg) -> Debug.crash msg ( model, Cmd.none ) diff --git a/elm/step4_if_fn_do.elm b/elm/step4_if_fn_do.elm index 97b650f7db..c8e349b637 100644 --- a/elm/step4_if_fn_do.elm +++ b/elm/step4_if_fn_do.elm @@ -43,7 +43,7 @@ init { args } = Core.ns evalMalInit = - Core.malInit + malInit |> List.map rep |> justValues |> List.foldl @@ -53,6 +53,14 @@ init { args } = runInit initEnv evalMalInit +malInit : List String +malInit = + [ """(def! not + (fn* (a) + (if a false true)))""" + ] + + update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case model of @@ -425,6 +433,7 @@ evalFn args = { frameId = frameId , lazyFn = fn , eagerFn = fn + , isMacro = False } go bindsList body = diff --git a/elm/step5_tco.elm b/elm/step5_tco.elm index b8f205bdca..34409fb266 100644 --- a/elm/step5_tco.elm +++ b/elm/step5_tco.elm @@ -43,7 +43,7 @@ init { args } = Core.ns evalMalInit = - Core.malInit + malInit |> List.map rep |> justValues |> List.foldl @@ -53,6 +53,14 @@ init { args } = runInit initEnv evalMalInit +malInit : List String +malInit = + [ """(def! not + (fn* (a) + (if a false true)))""" + ] + + update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case model of @@ -158,7 +166,7 @@ debug msg f e = eval : MalExpr -> Eval MalExpr eval ast = let - apply expr = + apply expr env = case expr of MalApply app -> Left @@ -473,6 +481,7 @@ evalFn args = { frameId = frameId , lazyFn = lazyFn , eagerFn = lazyFn >> Eval.andThen eval + , isMacro = False } go bindsList body = diff --git a/elm/step6_file.elm b/elm/step6_file.elm index 6cf7b6db5e..46dc256276 100644 --- a/elm/step6_file.elm +++ b/elm/step6_file.elm @@ -53,7 +53,7 @@ init { args } = |> Env.set "*ARGV*" (MalList (args |> List.map MalString)) evalMalInit = - Core.malInit + malInit |> List.map rep |> justValues |> List.foldl @@ -63,6 +63,18 @@ init { args } = runInit args initEnv evalMalInit +malInit : List String +malInit = + [ """(def! not + (fn* (a) + (if a false true)))""" + , """(def! load-file + (fn* (f) + (eval (read-string + (str "(do " (slurp f) ")")))))""" + ] + + update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case model of @@ -214,7 +226,7 @@ debug msg f e = eval : MalExpr -> Eval MalExpr eval ast = let - apply expr = + apply expr env = case expr of MalApply app -> Left @@ -548,6 +560,7 @@ evalFn args = { frameId = frameId , lazyFn = lazyFn , eagerFn = lazyFn >> Eval.andThen eval + , isMacro = False } go bindsList body = diff --git a/elm/step7_quote.elm b/elm/step7_quote.elm index 72bff9a8cb..776741fa19 100644 --- a/elm/step7_quote.elm +++ b/elm/step7_quote.elm @@ -8,7 +8,7 @@ import Platform exposing (programWithFlags) import Types exposing (..) import Reader exposing (readString) import Printer exposing (printString) -import Utils exposing (maybeToList, zip, last, justValues) +import Utils exposing (maybeToList, zip, last, justValues, makeCall) import Env import Core import Eval @@ -53,7 +53,7 @@ init { args } = |> Env.set "*ARGV*" (MalList (args |> List.map MalString)) evalMalInit = - Core.malInit + malInit |> List.map rep |> justValues |> List.foldl @@ -63,6 +63,18 @@ init { args } = runInit args initEnv evalMalInit +malInit : List String +malInit = + [ """(def! not + (fn* (a) + (if a false true)))""" + , """(def! load-file + (fn* (f) + (eval (read-string + (str "(do " (slurp f) ")")))))""" + ] + + update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case model of @@ -214,7 +226,7 @@ debug msg f e = eval : MalExpr -> Eval MalExpr eval ast = let - apply expr = + apply expr env = case expr of MalApply app -> Left @@ -234,7 +246,16 @@ malEval : List MalExpr -> Eval MalExpr malEval args = case args of [ expr ] -> - eval expr + Eval.withEnv + (\env -> + Eval.modifyEnv (Env.jump Env.globalFrameId) + |> Eval.andThen (\_ -> eval expr) + |> Eval.andThen + (\res -> + Eval.modifyEnv (Env.jump env.currentFrameId) + |> Eval.andThen (\_ -> Eval.succeed res) + ) + ) _ -> Eval.fail "unsupported arguments" @@ -273,6 +294,18 @@ evalNoApply ast = MalList ((MalSymbol "fn*") :: args) -> evalFn args + MalList ((MalSymbol "quote") :: args) -> + evalQuote args + + MalList ((MalSymbol "quasiquote") :: args) -> + case args of + [ expr ] -> + -- TCO. + evalNoApply (evalQuasiQuote expr) + + _ -> + Eval.fail "unsupported arguments" + MalList list -> evalList list |> Eval.andThen @@ -522,6 +555,7 @@ evalFn args = { frameId = frameId , lazyFn = lazyFn , eagerFn = lazyFn >> Eval.andThen eval + , isMacro = False } go bindsList body = @@ -549,6 +583,50 @@ evalFn args = Eval.fail "fn* expected two args: binds list and body" +evalQuote : List MalExpr -> Eval MalExpr +evalQuote args = + case args of + [ expr ] -> + Eval.succeed expr + + _ -> + Eval.fail "unsupported arguments" + + +evalQuasiQuote : MalExpr -> MalExpr +evalQuasiQuote expr = + let + apply list empty = + case list of + [ MalSymbol "unquote", ast ] -> + ast + + (MalList [ MalSymbol "splice-unquote", ast ]) :: rest -> + makeCall "concat" + [ ast + , evalQuasiQuote (MalList rest) + ] + + ast :: rest -> + makeCall "cons" + [ evalQuasiQuote ast + , evalQuasiQuote (MalList rest) + ] + + _ -> + makeCall "quote" [ empty ] + in + case expr of + MalList list -> + apply list (MalList []) + + MalVector vec -> + apply (Array.toList vec) (MalVector Array.empty) + + ast -> + makeCall "quote" [ ast ] + + print : Env -> MalExpr -> String print env = printString env True diff --git a/elm/step8_macros.elm b/elm/step8_macros.elm new file mode 100644 index 0000000000..70b48cbe1b --- /dev/null +++ b/elm/step8_macros.elm @@ -0,0 +1,727 @@ +port module Main exposing (..) + +import Array +import Dict exposing (Dict) +import IO exposing (..) +import Json.Decode exposing (decodeValue) +import Platform exposing (programWithFlags) +import Types exposing (..) +import Reader exposing (readString) +import Printer exposing (printString) +import Utils exposing (maybeToList, zip, last, justValues, makeCall) +import Env +import Core +import Eval + + +main : Program Flags Model Msg +main = + programWithFlags + { init = init + , update = update + , subscriptions = + \model -> input (decodeValue decodeIO >> Input) + } + + +type alias Args = + List String + + +type alias Flags = + { args : Args + } + + +type Model + = InitIO Args Env (IO -> Eval MalExpr) + | ScriptIO Env (IO -> Eval MalExpr) + | ReplActive Env + | ReplIO Env (IO -> Eval MalExpr) + | Stopped + + +init : Flags -> ( Model, Cmd Msg ) +init { args } = + let + makeFn = + CoreFunc >> MalFunction + + initEnv = + Core.ns + |> Env.set "eval" (makeFn malEval) + |> Env.set "*ARGV*" (MalList (args |> List.map MalString)) + + evalMalInit = + malInit + |> List.map rep + |> justValues + |> List.foldl + (\b a -> a |> Eval.andThen (\_ -> b)) + (Eval.succeed MalNil) + in + runInit args initEnv evalMalInit + + +malInit : List String +malInit = + [ """(def! not + (fn* (a) + (if a false true)))""" + , """(def! load-file + (fn* (f) + (eval (read-string + (str "(do " (slurp f) ")")))))""" + , """(defmacro! cond + (fn* (& xs) + (if (> (count xs) 0) + (list 'if (first xs) + (if (> (count xs) 1) + (nth xs 1) + (throw "odd number of forms to cond")) + (cons 'cond (rest (rest xs)))))))""" + , """(defmacro! or + (fn* (& xs) + (if (empty? xs) + nil + (if (= 1 (count xs)) + (first xs) + `(let* (or_FIXME ~(first xs)) + (if or_FIXME or_FIXME (or ~@(rest xs))))))))""" + ] + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case model of + Stopped -> + ( model, Cmd.none ) + + InitIO args env cont -> + case msg of + Input (Ok io) -> + runInit args env (cont io) + + Input (Err msg) -> + Debug.crash msg + + ScriptIO env cont -> + case msg of + Input (Ok io) -> + runScriptLoop env (cont io) + + Input (Err msg) -> + Debug.crash msg + + ReplActive env -> + case msg of + Input (Ok (LineRead (Just line))) -> + case rep line of + Just expr -> + run env expr + + Nothing -> + ( model, readLine prompt ) + + Input (Ok LineWritten) -> + ( model, readLine prompt ) + + Input (Ok (LineRead Nothing)) -> + -- Ctrl+D = The End. + ( model, Cmd.none ) + + Input (Ok io) -> + Debug.crash "unexpected IO received: " io + + Input (Err msg) -> + Debug.crash msg + + ReplIO env cont -> + case msg of + Input (Ok io) -> + run env (cont io) + + Input (Err msg) -> + Debug.crash msg ( model, Cmd.none ) + + +runInit : Args -> Env -> Eval MalExpr -> ( Model, Cmd Msg ) +runInit args env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + -- Init went okay. + case args of + -- If we got no args: start REPL. + [] -> + ( ReplActive env, readLine prompt ) + + -- Run the script in the first argument. + -- Put the rest of the arguments as *ARGV*. + filename :: argv -> + runScript filename argv env + + ( env, EvalErr msg ) -> + -- Init failed, don't start REPL. + ( Stopped, writeLine ("ERR:" ++ msg) ) + + ( env, EvalIO cmd cont ) -> + -- IO in init. + ( InitIO args env cont, cmd ) + + +runScript : String -> List String -> Env -> ( Model, Cmd Msg ) +runScript filename argv env = + let + malArgv = + MalList (List.map MalString argv) + + newEnv = + env |> Env.set "*ARGV*" malArgv + + program = + MalList + [ MalSymbol "load-file" + , MalString filename + ] + in + runScriptLoop newEnv (eval program) + + +runScriptLoop : Env -> Eval MalExpr -> ( Model, Cmd Msg ) +runScriptLoop env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + ( Stopped, Cmd.none ) + + ( env, EvalErr msg ) -> + ( Stopped, writeLine ("ERR:" ++ msg) ) + + ( env, EvalIO cmd cont ) -> + ( ScriptIO env cont, cmd ) + + +run : Env -> Eval MalExpr -> ( Model, Cmd Msg ) +run env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + ( ReplActive env, writeLine (print env expr) ) + + ( env, EvalErr msg ) -> + ( ReplActive env, writeLine ("ERR:" ++ msg) ) + + ( env, EvalIO cmd cont ) -> + ( ReplIO env cont, cmd ) + + +prompt : String +prompt = + "user> " + + +{-| read can return three things: + +Ok (Just expr) -> parsed okay +Ok Nothing -> empty string (only whitespace and/or comments) +Err msg -> parse error + +-} +read : String -> Result String (Maybe MalExpr) +read = + readString + + +debug : String -> (Env -> a) -> Eval b -> Eval b +debug msg f e = + Eval.withEnv + (\env -> + Env.debug env msg (f env) + |> always e + ) + + +eval : MalExpr -> Eval MalExpr +eval ast = + let + apply expr env = + case expr of + MalApply app -> + Left + (debug "evalApply" + (\env -> printString env True expr) + (evalApply app) + ) + + _ -> + Right expr + in + evalNoApply ast + |> Eval.andThen (Eval.runLoop apply) + + +malEval : List MalExpr -> Eval MalExpr +malEval args = + case args of + [ expr ] -> + Eval.withEnv + (\env -> + Eval.modifyEnv (Env.jump Env.globalFrameId) + |> Eval.andThen (\_ -> eval expr) + |> Eval.andThen + (\res -> + Eval.modifyEnv (Env.jump env.currentFrameId) + |> Eval.andThen (\_ -> Eval.succeed res) + ) + ) + + _ -> + Eval.fail "unsupported arguments" + + +evalApply : ApplyRec -> Eval MalExpr +evalApply { frameId, bound, body } = + Eval.withEnv + (\env -> + Eval.modifyEnv (Env.enter frameId bound) + |> Eval.andThen (\_ -> evalNoApply body) + |> Eval.ignore (Eval.modifyEnv (Env.leave env.currentFrameId)) + ) + + +evalNoApply : MalExpr -> Eval MalExpr +evalNoApply ast = + debug "evalNoApply" + (\env -> printString env True ast) + (macroexpand ast + |> Eval.andThen + (\ast -> + case ast of + MalList [] -> + Eval.succeed ast + + MalList ((MalSymbol "def!") :: args) -> + evalDef args + + MalList ((MalSymbol "let*") :: args) -> + evalLet args + + MalList ((MalSymbol "do") :: args) -> + evalDo args + + MalList ((MalSymbol "if") :: args) -> + evalIf args + + MalList ((MalSymbol "fn*") :: args) -> + evalFn args + + MalList ((MalSymbol "quote") :: args) -> + evalQuote args + + MalList ((MalSymbol "quasiquote") :: args) -> + case args of + [ expr ] -> + -- TCO. + evalNoApply (evalQuasiQuote expr) + + _ -> + Eval.fail "unsupported arguments" + + MalList ((MalSymbol "defmacro!") :: args) -> + evalDefMacro args + + MalList ((MalSymbol "macroexpand") :: args) -> + case args of + [ expr ] -> + macroexpand expr + + _ -> + Eval.fail "unsupported arguments" + + MalList list -> + evalList list + |> Eval.andThen + (\newList -> + case newList of + [] -> + Eval.fail "can't happen" + + (MalFunction (CoreFunc fn)) :: args -> + fn args + + (MalFunction (UserFunc { lazyFn })) :: args -> + lazyFn args + + fn :: _ -> + Eval.withEnv + (\env -> + Eval.fail ((printString env True fn) ++ " is not a function") + ) + ) + + _ -> + evalAst ast + ) + ) + + +evalAst : MalExpr -> Eval MalExpr +evalAst ast = + case ast of + MalSymbol sym -> + -- Lookup symbol in env and return value or raise error if not found. + Eval.withEnv (Env.get sym >> Eval.fromResult) + + MalList list -> + -- Return new list that is result of calling eval on each element of list. + evalList list + |> Eval.map MalList + + MalVector vec -> + evalList (Array.toList vec) + |> Eval.map (Array.fromList >> MalVector) + + MalMap map -> + evalList (Dict.values map) + |> Eval.map + (zip (Dict.keys map) + >> Dict.fromList + >> MalMap + ) + + _ -> + Eval.succeed ast + + +evalList : List MalExpr -> Eval (List MalExpr) +evalList list = + let + go list acc = + case list of + [] -> + Eval.succeed (List.reverse acc) + + x :: rest -> + eval x + |> Eval.andThen + (\val -> + go rest (val :: acc) + ) + in + go list [] + + +evalDef : List MalExpr -> Eval MalExpr +evalDef args = + case args of + [ MalSymbol name, uneValue ] -> + eval uneValue + |> Eval.andThen + (\value -> + Eval.modifyEnv (Env.set name value) + |> Eval.andThen (\_ -> Eval.succeed value) + ) + + _ -> + Eval.fail "def! expected two args: name and value" + + +evalDefMacro : List MalExpr -> Eval MalExpr +evalDefMacro args = + case args of + [ MalSymbol name, uneValue ] -> + eval uneValue + |> Eval.andThen + (\value -> + case value of + MalFunction (UserFunc fn) -> + let + macroFn = + MalFunction (UserFunc { fn | isMacro = True }) + in + Eval.modifyEnv (Env.set name macroFn) + |> Eval.andThen (\_ -> Eval.succeed macroFn) + + _ -> + Eval.fail "defmacro! is only supported on a user function" + ) + + _ -> + Eval.fail "defmacro! expected two args: name and value" + + +evalLet : List MalExpr -> Eval MalExpr +evalLet args = + let + evalBinds binds = + case binds of + (MalSymbol name) :: expr :: rest -> + eval expr + |> Eval.andThen + (\value -> + Eval.modifyEnv (Env.set name value) + |> Eval.andThen + (\_ -> + if List.isEmpty rest then + Eval.succeed () + else + evalBinds rest + ) + ) + + _ -> + Eval.fail "let* expected an even number of binds (symbol expr ..)" + + go binds body = + Eval.modifyEnv Env.push + |> Eval.andThen (\_ -> evalBinds binds) + |> Eval.andThen (\_ -> evalNoApply body) + |> Eval.ignore (Eval.modifyEnv Env.pop) + in + case args of + [ MalList binds, body ] -> + go binds body + + [ MalVector bindsVec, body ] -> + go (Array.toList bindsVec) body + + _ -> + Eval.fail "let* expected two args: binds and a body" + + +evalDo : List MalExpr -> Eval MalExpr +evalDo args = + case List.reverse args of + last :: rest -> + evalList (List.reverse rest) + |> Eval.andThen (\_ -> evalNoApply last) + + [] -> + Eval.fail "do expected at least one arg" + + +evalIf : List MalExpr -> Eval MalExpr +evalIf args = + let + isThruthy expr = + expr /= MalNil && expr /= (MalBool False) + + go condition trueExpr falseExpr = + eval condition + |> Eval.map isThruthy + |> Eval.andThen + (\cond -> + evalNoApply + (if cond then + trueExpr + else + falseExpr + ) + ) + in + case args of + [ condition, trueExpr ] -> + go condition trueExpr MalNil + + [ condition, trueExpr, falseExpr ] -> + go condition trueExpr falseExpr + + _ -> + Eval.fail "if expected at least two args" + + +evalFn : List MalExpr -> Eval MalExpr +evalFn args = + let + {- Extract symbols from the binds list and verify their uniqueness -} + extractSymbols acc list = + case list of + [] -> + Ok (List.reverse acc) + + (MalSymbol name) :: rest -> + if List.member name acc then + Err "all binds must have unique names" + else + extractSymbols (name :: acc) rest + + _ -> + Err "all binds in fn* must be a symbol" + + parseBinds list = + case List.reverse list of + var :: "&" :: rest -> + Ok <| bindVarArgs (List.reverse rest) var + + _ -> + if List.member "&" list then + Err "varargs separator '&' is used incorrectly" + else + Ok <| bindArgs list + + extractAndParse = + extractSymbols [] >> Result.andThen parseBinds + + bindArgs binds args = + let + numBinds = + List.length binds + in + if List.length args /= numBinds then + Err <| + "function expected " + ++ (toString numBinds) + ++ " arguments" + else + Ok <| zip binds args + + bindVarArgs binds var args = + let + minArgs = + List.length binds + + varArgs = + MalList (List.drop minArgs args) + in + if List.length args < minArgs then + Err <| + "function expected at least " + ++ (toString minArgs) + ++ " arguments" + else + Ok <| zip binds args ++ [ ( var, varArgs ) ] + + makeFn frameId binder body = + MalFunction <| + let + lazyFn = + binder + >> Eval.fromResult + >> Eval.map + (\bound -> + -- TODO : choice Env.enter prematurely? + -- I think it is needed by the garbage collect.. + MalApply + { frameId = frameId + , bound = bound + , body = body + } + ) + in + UserFunc + { frameId = frameId + , lazyFn = lazyFn + , eagerFn = lazyFn >> Eval.andThen eval + , isMacro = False + } + + go bindsList body = + extractAndParse bindsList + |> Eval.fromResult + -- reference the current frame. + |> Eval.ignore (Eval.modifyEnv Env.ref) + |> Eval.andThen + (\binder -> + Eval.withEnv + (\env -> + Eval.succeed + (makeFn env.currentFrameId binder body) + ) + ) + in + case args of + [ MalList bindsList, body ] -> + go bindsList body + + [ MalVector bindsVec, body ] -> + go (Array.toList bindsVec) body + + _ -> + Eval.fail "fn* expected two args: binds list and body" + + +evalQuote : List MalExpr -> Eval MalExpr +evalQuote args = + case args of + [ expr ] -> + Eval.succeed expr + + _ -> + Eval.fail "unsupported arguments" + + +evalQuasiQuote : MalExpr -> MalExpr +evalQuasiQuote expr = + let + apply list empty = + case list of + [ MalSymbol "unquote", ast ] -> + ast + + (MalList [ MalSymbol "splice-unquote", ast ]) :: rest -> + makeCall "concat" + [ ast + , evalQuasiQuote (MalList rest) + ] + + ast :: rest -> + makeCall "cons" + [ evalQuasiQuote ast + , evalQuasiQuote (MalList rest) + ] + + _ -> + makeCall "quote" [ empty ] + in + case expr of + MalList list -> + apply list (MalList []) + + MalVector vec -> + apply (Array.toList vec) (MalVector Array.empty) + + ast -> + makeCall "quote" [ ast ] + + +macroexpand : MalExpr -> Eval MalExpr +macroexpand expr = + let + expand expr env = + case expr of + MalList ((MalSymbol name) :: args) -> + case Env.get name env of + Ok (MalFunction (UserFunc fn)) -> + if fn.isMacro then + Left <| fn.eagerFn args + else + Right expr + + _ -> + Right expr + + _ -> + Right expr + in + Eval.runLoop expand expr + + +print : Env -> MalExpr -> String +print env = + printString env True + + +{-| Read-Eval-Print. + +Doesn't actually run the Eval but returns the monad. + +-} +rep : String -> Maybe (Eval MalExpr) +rep input = + case readString input of + Ok Nothing -> + Nothing + + Err msg -> + Just (Eval.fail msg) + + Ok (Just ast) -> + eval ast |> Just diff --git a/elm/step9_try.elm b/elm/step9_try.elm new file mode 100644 index 0000000000..7d1bc30aa9 --- /dev/null +++ b/elm/step9_try.elm @@ -0,0 +1,749 @@ +port module Main exposing (..) + +import Array +import Dict exposing (Dict) +import IO exposing (..) +import Json.Decode exposing (decodeValue) +import Platform exposing (programWithFlags) +import Types exposing (..) +import Reader exposing (readString) +import Printer exposing (printString) +import Utils exposing (maybeToList, zip, last, justValues, makeCall) +import Env +import Core +import Eval + + +main : Program Flags Model Msg +main = + programWithFlags + { init = init + , update = update + , subscriptions = + \model -> input (decodeValue decodeIO >> Input) + } + + +type alias Args = + List String + + +type alias Flags = + { args : Args + } + + +type Model + = InitIO Args Env (IO -> Eval MalExpr) + | ScriptIO Env (IO -> Eval MalExpr) + | ReplActive Env + | ReplIO Env (IO -> Eval MalExpr) + | Stopped + + +init : Flags -> ( Model, Cmd Msg ) +init { args } = + let + makeFn = + CoreFunc >> MalFunction + + initEnv = + Core.ns + |> Env.set "eval" (makeFn malEval) + |> Env.set "*ARGV*" (MalList (args |> List.map MalString)) + + evalMalInit = + malInit + |> List.map rep + |> justValues + |> List.foldl + (\b a -> a |> Eval.andThen (\_ -> b)) + (Eval.succeed MalNil) + in + runInit args initEnv evalMalInit + + +malInit : List String +malInit = + [ """(def! not + (fn* (a) + (if a false true)))""" + , """(def! load-file + (fn* (f) + (eval (read-string + (str "(do " (slurp f) ")")))))""" + , """(defmacro! cond + (fn* (& xs) + (if (> (count xs) 0) + (list 'if (first xs) + (if (> (count xs) 1) + (nth xs 1) + (throw "odd number of forms to cond")) + (cons 'cond (rest (rest xs)))))))""" + , """(defmacro! or + (fn* (& xs) + (if (empty? xs) + nil + (if (= 1 (count xs)) + (first xs) + `(let* (or_FIXME ~(first xs)) + (if or_FIXME or_FIXME (or ~@(rest xs))))))))""" + ] + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case model of + Stopped -> + ( model, Cmd.none ) + + InitIO args env cont -> + case msg of + Input (Ok io) -> + runInit args env (cont io) + + Input (Err msg) -> + Debug.crash msg + + ScriptIO env cont -> + case msg of + Input (Ok io) -> + runScriptLoop env (cont io) + + Input (Err msg) -> + Debug.crash msg + + ReplActive env -> + case msg of + Input (Ok (LineRead (Just line))) -> + case rep line of + Just expr -> + run env expr + + Nothing -> + ( model, readLine prompt ) + + Input (Ok LineWritten) -> + ( model, readLine prompt ) + + Input (Ok (LineRead Nothing)) -> + -- Ctrl+D = The End. + ( model, Cmd.none ) + + Input (Ok io) -> + Debug.crash "unexpected IO received: " io + + Input (Err msg) -> + Debug.crash msg + + ReplIO env cont -> + case msg of + Input (Ok io) -> + run env (cont io) + + Input (Err msg) -> + Debug.crash msg ( model, Cmd.none ) + + +runInit : Args -> Env -> Eval MalExpr -> ( Model, Cmd Msg ) +runInit args env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + -- Init went okay. + case args of + -- If we got no args: start REPL. + [] -> + ( ReplActive env, readLine prompt ) + + -- Run the script in the first argument. + -- Put the rest of the arguments as *ARGV*. + filename :: argv -> + runScript filename argv env + + ( env, EvalErr msg ) -> + -- Init failed, don't start REPL. + ( Stopped, writeLine ("ERR:" ++ msg) ) + + ( env, EvalIO cmd cont ) -> + -- IO in init. + ( InitIO args env cont, cmd ) + + +runScript : String -> List String -> Env -> ( Model, Cmd Msg ) +runScript filename argv env = + let + malArgv = + MalList (List.map MalString argv) + + newEnv = + env |> Env.set "*ARGV*" malArgv + + program = + MalList + [ MalSymbol "load-file" + , MalString filename + ] + in + runScriptLoop newEnv (eval program) + + +runScriptLoop : Env -> Eval MalExpr -> ( Model, Cmd Msg ) +runScriptLoop env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + ( Stopped, Cmd.none ) + + ( env, EvalErr msg ) -> + ( Stopped, writeLine ("ERR:" ++ msg) ) + + ( env, EvalIO cmd cont ) -> + ( ScriptIO env cont, cmd ) + + +run : Env -> Eval MalExpr -> ( Model, Cmd Msg ) +run env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + ( ReplActive env, writeLine (print env expr) ) + + ( env, EvalErr msg ) -> + ( ReplActive env, writeLine ("ERR:" ++ msg) ) + + ( env, EvalIO cmd cont ) -> + ( ReplIO env cont, cmd ) + + +prompt : String +prompt = + "user> " + + +{-| read can return three things: + +Ok (Just expr) -> parsed okay +Ok Nothing -> empty string (only whitespace and/or comments) +Err msg -> parse error + +-} +read : String -> Result String (Maybe MalExpr) +read = + readString + + +debug : String -> (Env -> a) -> Eval b -> Eval b +debug msg f e = + Eval.withEnv + (\env -> + Env.debug env msg (f env) + |> always e + ) + + +eval : MalExpr -> Eval MalExpr +eval ast = + let + apply expr env = + case expr of + MalApply app -> + Left + (debug "evalApply" + (\env -> printString env True expr) + (evalApply app) + ) + + _ -> + Right expr + in + evalNoApply ast + |> Eval.andThen (Eval.runLoop apply) + + +malEval : List MalExpr -> Eval MalExpr +malEval args = + case args of + [ expr ] -> + Eval.withEnv + (\env -> + Eval.modifyEnv (Env.jump Env.globalFrameId) + |> Eval.andThen (\_ -> eval expr) + |> Eval.andThen + (\res -> + Eval.modifyEnv (Env.jump env.currentFrameId) + |> Eval.andThen (\_ -> Eval.succeed res) + ) + ) + + _ -> + Eval.fail "unsupported arguments" + + +evalApply : ApplyRec -> Eval MalExpr +evalApply { frameId, bound, body } = + Eval.withEnv + (\env -> + Eval.modifyEnv (Env.enter frameId bound) + |> Eval.andThen (\_ -> evalNoApply body) + |> Eval.ignore (Eval.modifyEnv (Env.leave env.currentFrameId)) + ) + + +evalNoApply : MalExpr -> Eval MalExpr +evalNoApply ast = + let + go ast = + case ast of + MalList [] -> + Eval.succeed ast + + MalList ((MalSymbol "def!") :: args) -> + evalDef args + + MalList ((MalSymbol "let*") :: args) -> + evalLet args + + MalList ((MalSymbol "do") :: args) -> + evalDo args + + MalList ((MalSymbol "if") :: args) -> + evalIf args + + MalList ((MalSymbol "fn*") :: args) -> + evalFn args + + MalList ((MalSymbol "quote") :: args) -> + evalQuote args + + MalList ((MalSymbol "quasiquote") :: args) -> + case args of + [ expr ] -> + -- TCO. + evalNoApply (evalQuasiQuote expr) + + _ -> + Eval.fail "unsupported arguments" + + MalList ((MalSymbol "defmacro!") :: args) -> + evalDefMacro args + + MalList ((MalSymbol "macroexpand") :: args) -> + case args of + [ expr ] -> + macroexpand expr + + _ -> + Eval.fail "unsupported arguments" + + MalList ((MalSymbol "try*") :: args) -> + evalTry args + + MalList list -> + evalList list + |> Eval.andThen + (\newList -> + case newList of + [] -> + Eval.fail "can't happen" + + (MalFunction (CoreFunc fn)) :: args -> + fn args + + (MalFunction (UserFunc { lazyFn })) :: args -> + lazyFn args + + fn :: _ -> + Eval.withEnv + (\env -> + Eval.fail ((printString env True fn) ++ " is not a function") + ) + ) + + _ -> + evalAst ast + in + debug "evalNoApply" + (\env -> printString env True ast) + (macroexpand ast |> Eval.andThen go) + + +evalAst : MalExpr -> Eval MalExpr +evalAst ast = + case ast of + MalSymbol sym -> + -- Lookup symbol in env and return value or raise error if not found. + Eval.withEnv (Env.get sym >> Eval.fromResult) + + MalList list -> + -- Return new list that is result of calling eval on each element of list. + evalList list + |> Eval.map MalList + + MalVector vec -> + evalList (Array.toList vec) + |> Eval.map (Array.fromList >> MalVector) + + MalMap map -> + evalList (Dict.values map) + |> Eval.map + (zip (Dict.keys map) + >> Dict.fromList + >> MalMap + ) + + _ -> + Eval.succeed ast + + +evalList : List MalExpr -> Eval (List MalExpr) +evalList list = + let + go list acc = + case list of + [] -> + Eval.succeed (List.reverse acc) + + x :: rest -> + eval x + |> Eval.andThen + (\val -> + go rest (val :: acc) + ) + in + go list [] + + +evalDef : List MalExpr -> Eval MalExpr +evalDef args = + case args of + [ MalSymbol name, uneValue ] -> + eval uneValue + |> Eval.andThen + (\value -> + Eval.modifyEnv (Env.set name value) + |> Eval.andThen (\_ -> Eval.succeed value) + ) + + _ -> + Eval.fail "def! expected two args: name and value" + + +evalDefMacro : List MalExpr -> Eval MalExpr +evalDefMacro args = + case args of + [ MalSymbol name, uneValue ] -> + eval uneValue + |> Eval.andThen + (\value -> + case value of + MalFunction (UserFunc fn) -> + let + macroFn = + MalFunction (UserFunc { fn | isMacro = True }) + in + Eval.modifyEnv (Env.set name macroFn) + |> Eval.andThen (\_ -> Eval.succeed macroFn) + + _ -> + Eval.fail "defmacro! is only supported on a user function" + ) + + _ -> + Eval.fail "defmacro! expected two args: name and value" + + +evalLet : List MalExpr -> Eval MalExpr +evalLet args = + let + evalBinds binds = + case binds of + (MalSymbol name) :: expr :: rest -> + eval expr + |> Eval.andThen + (\value -> + Eval.modifyEnv (Env.set name value) + |> Eval.andThen + (\_ -> + if List.isEmpty rest then + Eval.succeed () + else + evalBinds rest + ) + ) + + _ -> + Eval.fail "let* expected an even number of binds (symbol expr ..)" + + go binds body = + Eval.modifyEnv Env.push + |> Eval.andThen (\_ -> evalBinds binds) + |> Eval.andThen (\_ -> evalNoApply body) + |> Eval.ignore (Eval.modifyEnv Env.pop) + in + case args of + [ MalList binds, body ] -> + go binds body + + [ MalVector bindsVec, body ] -> + go (Array.toList bindsVec) body + + _ -> + Eval.fail "let* expected two args: binds and a body" + + +evalDo : List MalExpr -> Eval MalExpr +evalDo args = + case List.reverse args of + last :: rest -> + evalList (List.reverse rest) + |> Eval.andThen (\_ -> evalNoApply last) + + [] -> + Eval.fail "do expected at least one arg" + + +evalIf : List MalExpr -> Eval MalExpr +evalIf args = + let + isThruthy expr = + expr /= MalNil && expr /= (MalBool False) + + go condition trueExpr falseExpr = + eval condition + |> Eval.map isThruthy + |> Eval.andThen + (\cond -> + evalNoApply + (if cond then + trueExpr + else + falseExpr + ) + ) + in + case args of + [ condition, trueExpr ] -> + go condition trueExpr MalNil + + [ condition, trueExpr, falseExpr ] -> + go condition trueExpr falseExpr + + _ -> + Eval.fail "if expected at least two args" + + +evalFn : List MalExpr -> Eval MalExpr +evalFn args = + let + {- Extract symbols from the binds list and verify their uniqueness -} + extractSymbols acc list = + case list of + [] -> + Ok (List.reverse acc) + + (MalSymbol name) :: rest -> + if List.member name acc then + Err "all binds must have unique names" + else + extractSymbols (name :: acc) rest + + _ -> + Err "all binds in fn* must be a symbol" + + parseBinds list = + case List.reverse list of + var :: "&" :: rest -> + Ok <| bindVarArgs (List.reverse rest) var + + _ -> + if List.member "&" list then + Err "varargs separator '&' is used incorrectly" + else + Ok <| bindArgs list + + extractAndParse = + extractSymbols [] >> Result.andThen parseBinds + + bindArgs binds args = + let + numBinds = + List.length binds + in + if List.length args /= numBinds then + Err <| + "function expected " + ++ (toString numBinds) + ++ " arguments" + else + Ok <| zip binds args + + bindVarArgs binds var args = + let + minArgs = + List.length binds + + varArgs = + MalList (List.drop minArgs args) + in + if List.length args < minArgs then + Err <| + "function expected at least " + ++ (toString minArgs) + ++ " arguments" + else + Ok <| zip binds args ++ [ ( var, varArgs ) ] + + makeFn frameId binder body = + MalFunction <| + let + lazyFn = + binder + >> Eval.fromResult + >> Eval.map + (\bound -> + -- TODO : choice Env.enter prematurely? + -- I think it is needed by the garbage collect.. + MalApply + { frameId = frameId + , bound = bound + , body = body + } + ) + in + UserFunc + { frameId = frameId + , lazyFn = lazyFn + , eagerFn = lazyFn >> Eval.andThen eval + , isMacro = False + } + + go bindsList body = + extractAndParse bindsList + |> Eval.fromResult + -- reference the current frame. + |> Eval.ignore (Eval.modifyEnv Env.ref) + |> Eval.andThen + (\binder -> + Eval.withEnv + (\env -> + Eval.succeed + (makeFn env.currentFrameId binder body) + ) + ) + in + case args of + [ MalList bindsList, body ] -> + go bindsList body + + [ MalVector bindsVec, body ] -> + go (Array.toList bindsVec) body + + _ -> + Eval.fail "fn* expected two args: binds list and body" + + +evalQuote : List MalExpr -> Eval MalExpr +evalQuote args = + case args of + [ expr ] -> + Eval.succeed expr + + _ -> + Eval.fail "unsupported arguments" + + +evalQuasiQuote : MalExpr -> MalExpr +evalQuasiQuote expr = + let + apply list empty = + case list of + [ MalSymbol "unquote", ast ] -> + ast + + (MalList [ MalSymbol "splice-unquote", ast ]) :: rest -> + makeCall "concat" + [ ast + , evalQuasiQuote (MalList rest) + ] + + ast :: rest -> + makeCall "cons" + [ evalQuasiQuote ast + , evalQuasiQuote (MalList rest) + ] + + _ -> + makeCall "quote" [ empty ] + in + case expr of + MalList list -> + apply list (MalList []) + + MalVector vec -> + apply (Array.toList vec) (MalVector Array.empty) + + ast -> + makeCall "quote" [ ast ] + + +macroexpand : MalExpr -> Eval MalExpr +macroexpand expr = + let + expand expr env = + case expr of + MalList ((MalSymbol name) :: args) -> + case Env.get name env of + Ok (MalFunction (UserFunc fn)) -> + if fn.isMacro then + Left <| fn.eagerFn args + else + Right expr + + _ -> + Right expr + + _ -> + Right expr + in + Eval.runLoop expand expr + + +evalTry : List MalExpr -> Eval MalExpr +evalTry args = + case args of + [ body, MalList [ MalSymbol "catch*", MalSymbol sym, handler ] ] -> + eval body + |> Eval.catchError + (\msg -> + Eval.modifyEnv Env.push + |> Eval.andThen + (\_ -> + Eval.modifyEnv (Env.set sym (MalString msg)) + ) + |> Eval.andThen (\_ -> evalNoApply handler) + |> Eval.ignore (Eval.modifyEnv Env.pop) + ) + + _ -> + Eval.fail "try* expected a body a catch block" + + +print : Env -> MalExpr -> String +print env = + printString env True + + +{-| Read-Eval-Print. + +Doesn't actually run the Eval but returns the monad. + +-} +rep : String -> Maybe (Eval MalExpr) +rep input = + case readString input of + Ok Nothing -> + Nothing + + Err msg -> + Just (Eval.fail msg) + + Ok (Just ast) -> + eval ast |> Just From c9c948de84395bffa5ae7177a57ce80230b7890e Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Fri, 23 Jun 2017 16:56:04 +0200 Subject: [PATCH 0350/2308] Elm step A --- elm/Core.elm | 161 ++++++++- elm/Env.elm | 2 +- elm/Eval.elm | 9 +- elm/IO.elm | 2 + elm/Makefile | 4 +- elm/Reader.elm | 48 ++- elm/Types.elm | 3 +- elm/step4_if_fn_do.elm | 14 +- elm/step5_tco.elm | 14 +- elm/step6_file.elm | 12 +- elm/step7_quote.elm | 12 +- elm/step8_macros.elm | 12 +- elm/step9_try.elm | 16 +- elm/stepA_mal.elm | 765 +++++++++++++++++++++++++++++++++++++++++ 14 files changed, 1017 insertions(+), 57 deletions(-) create mode 100644 elm/stepA_mal.elm diff --git a/elm/Core.elm b/elm/Core.elm index 751077c6e8..ffda42b575 100644 --- a/elm/Core.elm +++ b/elm/Core.elm @@ -1,6 +1,6 @@ module Core exposing (..) -import Types exposing (MalExpr(..), MalFunction(..), Eval, Env, keywordPrefix) +import Types exposing (..) import Env import Eval import Printer exposing (printString) @@ -9,6 +9,8 @@ import Dict import IO exposing (IO(..)) import Reader import Utils exposing (zip) +import Time +import Task ns : Env @@ -459,8 +461,8 @@ ns = throw args = case args of - [ MalString msg ] -> - Eval.fail msg + ex :: _ -> + Eval.throw ex _ -> Eval.fail "undefined exception" @@ -468,7 +470,18 @@ ns = apply args = case args of (MalFunction func) :: rest -> - callFn func rest + case List.reverse rest of + (MalList last) :: middle -> + callFn func ((List.reverse middle) ++ last) + + (MalVector last) :: middle -> + callFn func + ((List.reverse middle) + ++ (Array.toList last) + ) + + _ -> + Eval.fail "apply expected the last argument to be a list or vector" _ -> Eval.fail "unsupported arguments" @@ -567,6 +580,29 @@ ns = _ -> False + isString args = + Eval.succeed <| + MalBool <| + case args of + (MalString _) :: _ -> + True + + _ -> + False + + isSequential args = + Eval.succeed <| + MalBool <| + case args of + (MalList _) :: _ -> + True + + (MalVector _) :: _ -> + True + + _ -> + False + symbol args = case args of [ MalString str ] -> @@ -705,6 +741,113 @@ ns = _ -> Eval.fail "unsupported arguments" + + readLine args = + case args of + [ MalString prompt ] -> + Eval.io (IO.readLine prompt) + (\msg -> + case msg of + LineRead (Just line) -> + Eval.succeed (MalString line) + + LineRead Nothing -> + Eval.succeed MalNil + + _ -> + Eval.fail "wrong IO, expected LineRead" + ) + + _ -> + Eval.fail "unsupported arguments" + + withMeta args = + case args of + [ MalFunction (UserFunc func), meta ] -> + Eval.succeed <| MalFunction <| UserFunc { func | meta = Just meta } + + _ -> + Eval.fail "with-meta expected a user function and a map" + + meta args = + case args of + [ MalFunction (UserFunc { meta }) ] -> + Eval.succeed (Maybe.withDefault MalNil meta) + + _ -> + Eval.succeed MalNil + + conj args = + case args of + (MalList list) :: rest -> + Eval.succeed <| + MalList <| + (List.reverse rest) + ++ list + + (MalVector vec) :: rest -> + Eval.succeed <| + MalVector <| + Array.append + vec + (Array.fromList rest) + + _ -> + Eval.fail "unsupported arguments" + + seq args = + case args of + [ MalNil ] -> + Eval.succeed MalNil + + [ MalList [] ] -> + Eval.succeed MalNil + + [ MalString "" ] -> + Eval.succeed MalNil + + [ (MalList _) as list ] -> + Eval.succeed list + + [ MalVector vec ] -> + Eval.succeed <| + if Array.isEmpty vec then + MalNil + else + MalList <| Array.toList vec + + [ MalString str ] -> + Eval.succeed <| + MalList <| + (String.toList str + |> List.map String.fromChar + |> List.map MalString + ) + + _ -> + Eval.fail "unsupported arguments" + + requestTime = + Task.perform (GotTime >> Ok >> Input) Time.now + + timeMs args = + case args of + [] -> + Eval.io requestTime + (\msg -> + case msg of + GotTime time -> + Time.inMilliseconds time + |> truncate + |> MalInt + |> Eval.succeed + + _ -> + Eval.fail "wrong IO, expected GotTime" + ) + + _ -> + Eval.fail "time-ms takes no arguments" in Env.global |> Env.set "+" (makeFn <| binaryOp (+) MalInt) @@ -741,6 +884,8 @@ ns = |> Env.set "first" (makeFn first) |> Env.set "rest" (makeFn rest) |> Env.set "throw" (makeFn throw) + |> Env.set "apply" (makeFn apply) + |> Env.set "map" (makeFn map) |> Env.set "nil?" (makeFn isNil) |> Env.set "true?" (makeFn isTrue) |> Env.set "false?" (makeFn isFalse) @@ -748,6 +893,8 @@ ns = |> Env.set "keyword?" (makeFn isKeyword) |> Env.set "vector?" (makeFn isVector) |> Env.set "map?" (makeFn isMap) + |> Env.set "string?" (makeFn isString) + |> Env.set "sequential?" (makeFn isSequential) |> Env.set "symbol" (makeFn symbol) |> Env.set "keyword" (makeFn keyword) |> Env.set "vector" (makeFn vector) @@ -758,3 +905,9 @@ ns = |> Env.set "contains?" (makeFn contains) |> Env.set "keys" (makeFn keys) |> Env.set "vals" (makeFn vals) + |> Env.set "readline" (makeFn readLine) + |> Env.set "with-meta" (makeFn withMeta) + |> Env.set "meta" (makeFn meta) + |> Env.set "conj" (makeFn conj) + |> Env.set "seq" (makeFn seq) + |> Env.set "time-ms" (makeFn timeMs) diff --git a/elm/Env.elm b/elm/Env.elm index f9ccb9306b..658ee1a553 100644 --- a/elm/Env.elm +++ b/elm/Env.elm @@ -266,7 +266,7 @@ get name env = Nothing -> frame.outerId |> Maybe.map go - |> Maybe.withDefault (Err "symbol not found") + |> Maybe.withDefault (Err <| "'" ++ name ++ "' not found") in go env.currentFrameId diff --git a/elm/Eval.elm b/elm/Eval.elm index 3fc4cf7942..ddead03182 100644 --- a/elm/Eval.elm +++ b/elm/Eval.elm @@ -66,7 +66,7 @@ andThen f e env = ( env, EvalIO cmd (cont >> andThen f) ) -catchError : (String -> Eval a) -> Eval a -> Eval a +catchError : (MalExpr -> Eval a) -> Eval a -> Eval a catchError f e env = case apply e env of ( env, EvalOk res ) -> @@ -81,7 +81,12 @@ catchError f e env = fail : String -> Eval a fail msg env = - ( env, EvalErr msg ) + ( env, EvalErr <| MalString msg ) + + +throw : MalExpr -> Eval a +throw ex env = + ( env, EvalErr ex ) enter : Int -> List ( String, MalExpr ) -> Eval a -> Eval a diff --git a/elm/IO.elm b/elm/IO.elm index 48ff9c439c..a67f151984 100644 --- a/elm/IO.elm +++ b/elm/IO.elm @@ -9,6 +9,7 @@ port module IO ) import Json.Decode exposing (..) +import Time exposing (Time) {-| Output a string to stdout @@ -36,6 +37,7 @@ type IO | LineWritten | FileRead String | Exception String + | GotTime Time decodeIO : Decoder IO diff --git a/elm/Makefile b/elm/Makefile index 83de99987b..40ac7eff71 100644 --- a/elm/Makefile +++ b/elm/Makefile @@ -1,6 +1,6 @@ SOURCES = step0_repl.elm step1_read_print.elm \ step4_if_fn_do.elm step5_tco.elm step6_file.elm step7_quote.elm \ - step8_macros.elm step9_try.elm # stepA_mal.ls \ + step8_macros.elm step9_try.elm stepA_mal.elm #step2_eval.elm step3_env.elm BINS = $(SOURCES:%.elm=%.js) @@ -33,7 +33,7 @@ step6_file.js: $(STEP4_SOURCES) step7_quote.js: $(STEP4_SOURCES) step8_macros.js: $(STEP4_SOURCES) step9_try.js: $(STEP4_SOURCES) -# stepA_mal.js: utils.js reader.js printer.js env.js core.js +stepA_mal.js: $(STEP4_SOURCES) clean: rm -f $(BINS) diff --git a/elm/Reader.elm b/elm/Reader.elm index 11c4e5cf4e..c770460171 100644 --- a/elm/Reader.elm +++ b/elm/Reader.elm @@ -18,36 +18,36 @@ ws = many (comment <|> string "," <|> whitespace) -nil : Parser s MalExpr -nil = - MalNil <$ string "nil" "nil" - - int : Parser s MalExpr int = MalInt <$> Combine.Num.int "int" -bool : Parser s MalExpr -bool = - MalBool - <$> choice - [ True <$ string "true" - , False <$ string "false" - ] - "bool" - - symbolString : Parser s String symbolString = regex "[^\\s\\[\\]{}('\"`,;)]+" -symbol : Parser s MalExpr -symbol = - MalSymbol - <$> symbolString - "symbol" +symbolOrConst : Parser s MalExpr +symbolOrConst = + let + make sym = + case sym of + "nil" -> + MalNil + + "true" -> + MalBool True + + "false" -> + MalBool False + + _ -> + MalSymbol sym + in + make + <$> symbolString + "symbol" keywordString : Parser s String @@ -112,11 +112,9 @@ atom : Parser s MalExpr atom = choice [ int - , bool - , str - , nil , keyword - , symbol + , symbolOrConst + , str ] "atom" @@ -159,7 +157,7 @@ withMeta = makeCall "with-meta" [ expr, meta ] in make - <$> (string "^" *> map) + <$> (string "^" *> form) <*> form "with-meta" diff --git a/elm/Types.elm b/elm/Types.elm index 81e62d02b3..2fb55ec910 100644 --- a/elm/Types.elm +++ b/elm/Types.elm @@ -36,7 +36,7 @@ type alias EvalCont a = type EvalResult res - = EvalErr String + = EvalErr MalExpr | EvalOk res | EvalIO (Cmd Msg) (EvalCont res) @@ -60,6 +60,7 @@ type MalFunction , lazyFn : MalFn , eagerFn : MalFn , isMacro : Bool + , meta : Maybe MalExpr } diff --git a/elm/step4_if_fn_do.elm b/elm/step4_if_fn_do.elm index c8e349b637..e524426b44 100644 --- a/elm/step4_if_fn_do.elm +++ b/elm/step4_if_fn_do.elm @@ -31,7 +31,7 @@ type alias Flags = type Model = InitIO Env (IO -> Eval MalExpr) - | InitError String + | InitError | ReplActive Env | ReplIO Env (IO -> Eval MalExpr) @@ -64,7 +64,7 @@ malInit = update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case model of - InitError _ -> + InitError -> -- ignore all ( model, Cmd.none ) @@ -117,7 +117,7 @@ runInit env expr = ( env, EvalErr msg ) -> -- Init failed, don't start REPL. - ( InitError msg, writeLine ("ERR:" ++ msg) ) + ( InitError, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> -- IO in init. @@ -131,7 +131,7 @@ run env expr = ( ReplActive env, writeLine (print env expr) ) ( env, EvalErr msg ) -> - ( ReplActive env, writeLine ("ERR:" ++ msg) ) + ( ReplActive env, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> ( ReplIO env cont, cmd ) @@ -434,6 +434,7 @@ evalFn args = , lazyFn = fn , eagerFn = fn , isMacro = False + , meta = Nothing } go bindsList body = @@ -469,6 +470,11 @@ print env = printString env True +printError : Env -> MalExpr -> String +printError env expr = + "ERR:" ++ (printString env False expr) + + {-| Read-Eval-Print. Doesn't actually run the Eval but returns the monad. diff --git a/elm/step5_tco.elm b/elm/step5_tco.elm index 34409fb266..7938325eae 100644 --- a/elm/step5_tco.elm +++ b/elm/step5_tco.elm @@ -31,7 +31,7 @@ type alias Flags = type Model = InitIO Env (IO -> Eval MalExpr) - | InitError String + | InitError | ReplActive Env | ReplIO Env (IO -> Eval MalExpr) @@ -64,7 +64,7 @@ malInit = update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case model of - InitError _ -> + InitError -> -- ignore all ( model, Cmd.none ) @@ -117,7 +117,7 @@ runInit env expr = ( env, EvalErr msg ) -> -- Init failed, don't start REPL. - ( InitError msg, writeLine ("ERR:" ++ msg) ) + ( InitError, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> -- IO in init. @@ -131,7 +131,7 @@ run env expr = ( ReplActive env, writeLine (print env expr) ) ( env, EvalErr msg ) -> - ( ReplActive env, writeLine ("ERR:" ++ msg) ) + ( ReplActive env, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> ( ReplIO env cont, cmd ) @@ -482,6 +482,7 @@ evalFn args = , lazyFn = lazyFn , eagerFn = lazyFn >> Eval.andThen eval , isMacro = False + , meta = Nothing } go bindsList body = @@ -517,6 +518,11 @@ print env = printString env True +printError : Env -> MalExpr -> String +printError env expr = + "ERR:" ++ (printString env False expr) + + {-| Read-Eval-Print. Doesn't actually run the Eval but returns the monad. diff --git a/elm/step6_file.elm b/elm/step6_file.elm index 46dc256276..5c7013d357 100644 --- a/elm/step6_file.elm +++ b/elm/step6_file.elm @@ -146,7 +146,7 @@ runInit args env expr = ( env, EvalErr msg ) -> -- Init failed, don't start REPL. - ( Stopped, writeLine ("ERR:" ++ msg) ) + ( Stopped, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> -- IO in init. @@ -178,7 +178,7 @@ runScriptLoop env expr = ( Stopped, Cmd.none ) ( env, EvalErr msg ) -> - ( Stopped, writeLine ("ERR:" ++ msg) ) + ( Stopped, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> ( ScriptIO env cont, cmd ) @@ -191,7 +191,7 @@ run env expr = ( ReplActive env, writeLine (print env expr) ) ( env, EvalErr msg ) -> - ( ReplActive env, writeLine ("ERR:" ++ msg) ) + ( ReplActive env, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> ( ReplIO env cont, cmd ) @@ -561,6 +561,7 @@ evalFn args = , lazyFn = lazyFn , eagerFn = lazyFn >> Eval.andThen eval , isMacro = False + , meta = Nothing } go bindsList body = @@ -596,6 +597,11 @@ print env = printString env True +printError : Env -> MalExpr -> String +printError env expr = + "ERR:" ++ (printString env False expr) + + {-| Read-Eval-Print. Doesn't actually run the Eval but returns the monad. diff --git a/elm/step7_quote.elm b/elm/step7_quote.elm index 776741fa19..330c2ffdfe 100644 --- a/elm/step7_quote.elm +++ b/elm/step7_quote.elm @@ -146,7 +146,7 @@ runInit args env expr = ( env, EvalErr msg ) -> -- Init failed, don't start REPL. - ( Stopped, writeLine ("ERR:" ++ msg) ) + ( Stopped, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> -- IO in init. @@ -178,7 +178,7 @@ runScriptLoop env expr = ( Stopped, Cmd.none ) ( env, EvalErr msg ) -> - ( Stopped, writeLine ("ERR:" ++ msg) ) + ( Stopped, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> ( ScriptIO env cont, cmd ) @@ -191,7 +191,7 @@ run env expr = ( ReplActive env, writeLine (print env expr) ) ( env, EvalErr msg ) -> - ( ReplActive env, writeLine ("ERR:" ++ msg) ) + ( ReplActive env, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> ( ReplIO env cont, cmd ) @@ -556,6 +556,7 @@ evalFn args = , lazyFn = lazyFn , eagerFn = lazyFn >> Eval.andThen eval , isMacro = False + , meta = Nothing } go bindsList body = @@ -632,6 +633,11 @@ print env = printString env True +printError : Env -> MalExpr -> String +printError env expr = + "ERR:" ++ (printString env False expr) + + {-| Read-Eval-Print. Doesn't actually run the Eval but returns the monad. diff --git a/elm/step8_macros.elm b/elm/step8_macros.elm index 70b48cbe1b..9ca040b6ad 100644 --- a/elm/step8_macros.elm +++ b/elm/step8_macros.elm @@ -162,7 +162,7 @@ runInit args env expr = ( env, EvalErr msg ) -> -- Init failed, don't start REPL. - ( Stopped, writeLine ("ERR:" ++ msg) ) + ( Stopped, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> -- IO in init. @@ -194,7 +194,7 @@ runScriptLoop env expr = ( Stopped, Cmd.none ) ( env, EvalErr msg ) -> - ( Stopped, writeLine ("ERR:" ++ msg) ) + ( Stopped, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> ( ScriptIO env cont, cmd ) @@ -207,7 +207,7 @@ run env expr = ( ReplActive env, writeLine (print env expr) ) ( env, EvalErr msg ) -> - ( ReplActive env, writeLine ("ERR:" ++ msg) ) + ( ReplActive env, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> ( ReplIO env cont, cmd ) @@ -611,6 +611,7 @@ evalFn args = , lazyFn = lazyFn , eagerFn = lazyFn >> Eval.andThen eval , isMacro = False + , meta = Nothing } go bindsList body = @@ -709,6 +710,11 @@ print env = printString env True +printError : Env -> MalExpr -> String +printError env expr = + "ERR:" ++ (printString env False expr) + + {-| Read-Eval-Print. Doesn't actually run the Eval but returns the monad. diff --git a/elm/step9_try.elm b/elm/step9_try.elm index 7d1bc30aa9..699843757b 100644 --- a/elm/step9_try.elm +++ b/elm/step9_try.elm @@ -162,7 +162,7 @@ runInit args env expr = ( env, EvalErr msg ) -> -- Init failed, don't start REPL. - ( Stopped, writeLine ("ERR:" ++ msg) ) + ( Stopped, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> -- IO in init. @@ -194,7 +194,7 @@ runScriptLoop env expr = ( Stopped, Cmd.none ) ( env, EvalErr msg ) -> - ( Stopped, writeLine ("ERR:" ++ msg) ) + ( Stopped, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> ( ScriptIO env cont, cmd ) @@ -207,7 +207,7 @@ run env expr = ( ReplActive env, writeLine (print env expr) ) ( env, EvalErr msg ) -> - ( ReplActive env, writeLine ("ERR:" ++ msg) ) + ( ReplActive env, writeLine (printError env msg) ) ( env, EvalIO cmd cont ) -> ( ReplIO env cont, cmd ) @@ -613,6 +613,7 @@ evalFn args = , lazyFn = lazyFn , eagerFn = lazyFn >> Eval.andThen eval , isMacro = False + , meta = Nothing } go bindsList body = @@ -712,11 +713,11 @@ evalTry args = [ body, MalList [ MalSymbol "catch*", MalSymbol sym, handler ] ] -> eval body |> Eval.catchError - (\msg -> + (\ex -> Eval.modifyEnv Env.push |> Eval.andThen (\_ -> - Eval.modifyEnv (Env.set sym (MalString msg)) + Eval.modifyEnv (Env.set sym ex) ) |> Eval.andThen (\_ -> evalNoApply handler) |> Eval.ignore (Eval.modifyEnv Env.pop) @@ -731,6 +732,11 @@ print env = printString env True +printError : Env -> MalExpr -> String +printError env expr = + "ERR:" ++ (printString env False expr) + + {-| Read-Eval-Print. Doesn't actually run the Eval but returns the monad. diff --git a/elm/stepA_mal.elm b/elm/stepA_mal.elm new file mode 100644 index 0000000000..4186852057 --- /dev/null +++ b/elm/stepA_mal.elm @@ -0,0 +1,765 @@ +port module Main exposing (..) + +import Array +import Dict exposing (Dict) +import IO exposing (..) +import Json.Decode exposing (decodeValue) +import Platform exposing (programWithFlags) +import Types exposing (..) +import Reader exposing (readString) +import Printer exposing (printString) +import Utils exposing (maybeToList, zip, last, justValues, makeCall) +import Env +import Core +import Eval + + +main : Program Flags Model Msg +main = + programWithFlags + { init = init + , update = update + , subscriptions = + \model -> input (decodeValue decodeIO >> Input) + } + + +type alias Args = + List String + + +type alias Flags = + { args : Args + } + + +type Model + = InitIO Args Env (IO -> Eval MalExpr) + | ScriptIO Env (IO -> Eval MalExpr) + | ReplActive Env + | ReplIO Env (IO -> Eval MalExpr) + | Stopped + + +init : Flags -> ( Model, Cmd Msg ) +init { args } = + let + makeFn = + CoreFunc >> MalFunction + + initEnv = + Core.ns + |> Env.set "eval" (makeFn malEval) + |> Env.set "*ARGV*" (MalList (args |> List.map MalString)) + |> Env.set "*host-language*" (MalString "elm") + + evalMalInit = + malInit + |> List.map rep + |> justValues + |> List.foldl + (\b a -> a |> Eval.andThen (\_ -> b)) + (Eval.succeed MalNil) + in + runInit args initEnv evalMalInit + + +malInit : List String +malInit = + [ """(def! not + (fn* (a) + (if a false true)))""" + , """(def! load-file + (fn* (f) + (eval (read-string + (str "(do " (slurp f) ")")))))""" + , """(defmacro! cond + (fn* (& xs) + (if (> (count xs) 0) + (list 'if (first xs) + (if (> (count xs) 1) + (nth xs 1) + (throw "odd number of forms to cond")) + (cons 'cond (rest (rest xs)))))))""" + , """(def! *gensym-counter* (atom 0))""" + , """(def! gensym + (fn* [] (symbol + (str "G__" + (swap! *gensym-counter* + (fn* [x] (+ 1 x)))))))""" + , """(defmacro! or + (fn* (& xs) + (if (empty? xs) + nil + (if (= 1 (count xs)) + (first xs) + (let* (condvar (gensym)) + `(let* (~condvar ~(first xs)) + (if ~condvar + ~condvar + (or ~@(rest xs)))))))))""" + ] + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case model of + Stopped -> + ( model, Cmd.none ) + + InitIO args env cont -> + case msg of + Input (Ok io) -> + runInit args env (cont io) + + Input (Err msg) -> + Debug.crash msg + + ScriptIO env cont -> + case msg of + Input (Ok io) -> + runScriptLoop env (cont io) + + Input (Err msg) -> + Debug.crash msg + + ReplActive env -> + case msg of + Input (Ok (LineRead (Just line))) -> + case rep line of + Just expr -> + run env expr + + Nothing -> + ( model, readLine prompt ) + + Input (Ok LineWritten) -> + ( model, readLine prompt ) + + Input (Ok (LineRead Nothing)) -> + -- Ctrl+D = The End. + ( model, Cmd.none ) + + Input (Ok io) -> + Debug.crash "unexpected IO received: " io + + Input (Err msg) -> + Debug.crash msg + + ReplIO env cont -> + case msg of + Input (Ok io) -> + run env (cont io) + + Input (Err msg) -> + Debug.crash msg ( model, Cmd.none ) + + +runInit : Args -> Env -> Eval MalExpr -> ( Model, Cmd Msg ) +runInit args env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + -- Init went okay. + case args of + -- If we got no args: start REPL. + [] -> + ( ReplActive env, readLine prompt ) + + -- Run the script in the first argument. + -- Put the rest of the arguments as *ARGV*. + filename :: argv -> + runScript filename argv env + + ( env, EvalErr msg ) -> + -- Init failed, don't start REPL. + ( Stopped, writeLine (printError env msg) ) + + ( env, EvalIO cmd cont ) -> + -- IO in init. + ( InitIO args env cont, cmd ) + + +runScript : String -> List String -> Env -> ( Model, Cmd Msg ) +runScript filename argv env = + let + malArgv = + MalList (List.map MalString argv) + + newEnv = + env |> Env.set "*ARGV*" malArgv + + program = + MalList + [ MalSymbol "load-file" + , MalString filename + ] + in + runScriptLoop newEnv (eval program) + + +runScriptLoop : Env -> Eval MalExpr -> ( Model, Cmd Msg ) +runScriptLoop env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + ( Stopped, Cmd.none ) + + ( env, EvalErr msg ) -> + ( Stopped, writeLine (printError env msg) ) + + ( env, EvalIO cmd cont ) -> + ( ScriptIO env cont, cmd ) + + +run : Env -> Eval MalExpr -> ( Model, Cmd Msg ) +run env expr = + case Eval.run env expr of + ( env, EvalOk expr ) -> + ( ReplActive env, writeLine (print env expr) ) + + ( env, EvalErr msg ) -> + ( ReplActive env, writeLine (printError env msg) ) + + ( env, EvalIO cmd cont ) -> + ( ReplIO env cont, cmd ) + + +prompt : String +prompt = + "user> " + + +{-| read can return three things: + +Ok (Just expr) -> parsed okay +Ok Nothing -> empty string (only whitespace and/or comments) +Err msg -> parse error + +-} +read : String -> Result String (Maybe MalExpr) +read = + readString + + +debug : String -> (Env -> a) -> Eval b -> Eval b +debug msg f e = + Eval.withEnv + (\env -> + Env.debug env msg (f env) + |> always e + ) + + +eval : MalExpr -> Eval MalExpr +eval ast = + let + apply expr env = + case expr of + MalApply app -> + Left + (debug "evalApply" + (\env -> printString env True expr) + (evalApply app) + ) + + _ -> + Right expr + in + evalNoApply ast + |> Eval.andThen (Eval.runLoop apply) + + +malEval : List MalExpr -> Eval MalExpr +malEval args = + case args of + [ expr ] -> + Eval.withEnv + (\env -> + Eval.modifyEnv (Env.jump Env.globalFrameId) + |> Eval.andThen (\_ -> eval expr) + |> Eval.andThen + (\res -> + Eval.modifyEnv (Env.jump env.currentFrameId) + |> Eval.andThen (\_ -> Eval.succeed res) + ) + ) + + _ -> + Eval.fail "unsupported arguments" + + +evalApply : ApplyRec -> Eval MalExpr +evalApply { frameId, bound, body } = + Eval.withEnv + (\env -> + Eval.modifyEnv (Env.enter frameId bound) + |> Eval.andThen (\_ -> evalNoApply body) + |> Eval.ignore (Eval.modifyEnv (Env.leave env.currentFrameId)) + ) + + +evalNoApply : MalExpr -> Eval MalExpr +evalNoApply ast = + let + go ast = + case ast of + MalList [] -> + Eval.succeed ast + + MalList ((MalSymbol "def!") :: args) -> + evalDef args + + MalList ((MalSymbol "let*") :: args) -> + evalLet args + + MalList ((MalSymbol "do") :: args) -> + evalDo args + + MalList ((MalSymbol "if") :: args) -> + evalIf args + + MalList ((MalSymbol "fn*") :: args) -> + evalFn args + + MalList ((MalSymbol "quote") :: args) -> + evalQuote args + + MalList ((MalSymbol "quasiquote") :: args) -> + case args of + [ expr ] -> + -- TCO. + evalNoApply (evalQuasiQuote expr) + + _ -> + Eval.fail "unsupported arguments" + + MalList ((MalSymbol "defmacro!") :: args) -> + evalDefMacro args + + MalList ((MalSymbol "macroexpand") :: args) -> + case args of + [ expr ] -> + macroexpand expr + + _ -> + Eval.fail "unsupported arguments" + + MalList ((MalSymbol "try*") :: args) -> + evalTry args + + MalList list -> + evalList list + |> Eval.andThen + (\newList -> + case newList of + [] -> + Eval.fail "can't happen" + + (MalFunction (CoreFunc fn)) :: args -> + fn args + + (MalFunction (UserFunc { lazyFn })) :: args -> + lazyFn args + + fn :: _ -> + Eval.withEnv + (\env -> + Eval.fail ((printString env True fn) ++ " is not a function") + ) + ) + + _ -> + evalAst ast + in + debug "evalNoApply" + (\env -> printString env True ast) + (macroexpand ast |> Eval.andThen go) + + +evalAst : MalExpr -> Eval MalExpr +evalAst ast = + case ast of + MalSymbol sym -> + -- Lookup symbol in env and return value or raise error if not found. + Eval.withEnv (Env.get sym >> Eval.fromResult) + + MalList list -> + -- Return new list that is result of calling eval on each element of list. + evalList list + |> Eval.map MalList + + MalVector vec -> + evalList (Array.toList vec) + |> Eval.map (Array.fromList >> MalVector) + + MalMap map -> + evalList (Dict.values map) + |> Eval.map + (zip (Dict.keys map) + >> Dict.fromList + >> MalMap + ) + + _ -> + Eval.succeed ast + + +evalList : List MalExpr -> Eval (List MalExpr) +evalList list = + let + go list acc = + case list of + [] -> + Eval.succeed (List.reverse acc) + + x :: rest -> + eval x + |> Eval.andThen + (\val -> + go rest (val :: acc) + ) + in + go list [] + + +evalDef : List MalExpr -> Eval MalExpr +evalDef args = + case args of + [ MalSymbol name, uneValue ] -> + eval uneValue + |> Eval.andThen + (\value -> + Eval.modifyEnv (Env.set name value) + |> Eval.andThen (\_ -> Eval.succeed value) + ) + + _ -> + Eval.fail "def! expected two args: name and value" + + +evalDefMacro : List MalExpr -> Eval MalExpr +evalDefMacro args = + case args of + [ MalSymbol name, uneValue ] -> + eval uneValue + |> Eval.andThen + (\value -> + case value of + MalFunction (UserFunc fn) -> + let + macroFn = + MalFunction (UserFunc { fn | isMacro = True }) + in + Eval.modifyEnv (Env.set name macroFn) + |> Eval.andThen (\_ -> Eval.succeed macroFn) + + _ -> + Eval.fail "defmacro! is only supported on a user function" + ) + + _ -> + Eval.fail "defmacro! expected two args: name and value" + + +evalLet : List MalExpr -> Eval MalExpr +evalLet args = + let + evalBinds binds = + case binds of + (MalSymbol name) :: expr :: rest -> + eval expr + |> Eval.andThen + (\value -> + Eval.modifyEnv (Env.set name value) + |> Eval.andThen + (\_ -> + if List.isEmpty rest then + Eval.succeed () + else + evalBinds rest + ) + ) + + _ -> + Eval.fail "let* expected an even number of binds (symbol expr ..)" + + go binds body = + Eval.modifyEnv Env.push + |> Eval.andThen (\_ -> evalBinds binds) + |> Eval.andThen (\_ -> evalNoApply body) + |> Eval.ignore (Eval.modifyEnv Env.pop) + in + case args of + [ MalList binds, body ] -> + go binds body + + [ MalVector bindsVec, body ] -> + go (Array.toList bindsVec) body + + _ -> + Eval.fail "let* expected two args: binds and a body" + + +evalDo : List MalExpr -> Eval MalExpr +evalDo args = + case List.reverse args of + last :: rest -> + evalList (List.reverse rest) + |> Eval.andThen (\_ -> evalNoApply last) + + [] -> + Eval.fail "do expected at least one arg" + + +evalIf : List MalExpr -> Eval MalExpr +evalIf args = + let + isThruthy expr = + expr /= MalNil && expr /= (MalBool False) + + go condition trueExpr falseExpr = + eval condition + |> Eval.map isThruthy + |> Eval.andThen + (\cond -> + evalNoApply + (if cond then + trueExpr + else + falseExpr + ) + ) + in + case args of + [ condition, trueExpr ] -> + go condition trueExpr MalNil + + [ condition, trueExpr, falseExpr ] -> + go condition trueExpr falseExpr + + _ -> + Eval.fail "if expected at least two args" + + +evalFn : List MalExpr -> Eval MalExpr +evalFn args = + let + {- Extract symbols from the binds list and verify their uniqueness -} + extractSymbols acc list = + case list of + [] -> + Ok (List.reverse acc) + + (MalSymbol name) :: rest -> + if List.member name acc then + Err "all binds must have unique names" + else + extractSymbols (name :: acc) rest + + _ -> + Err "all binds in fn* must be a symbol" + + parseBinds list = + case List.reverse list of + var :: "&" :: rest -> + Ok <| bindVarArgs (List.reverse rest) var + + _ -> + if List.member "&" list then + Err "varargs separator '&' is used incorrectly" + else + Ok <| bindArgs list + + extractAndParse = + extractSymbols [] >> Result.andThen parseBinds + + bindArgs binds args = + let + numBinds = + List.length binds + in + if List.length args /= numBinds then + Err <| + "function expected " + ++ (toString numBinds) + ++ " arguments" + else + Ok <| zip binds args + + bindVarArgs binds var args = + let + minArgs = + List.length binds + + varArgs = + MalList (List.drop minArgs args) + in + if List.length args < minArgs then + Err <| + "function expected at least " + ++ (toString minArgs) + ++ " arguments" + else + Ok <| zip binds args ++ [ ( var, varArgs ) ] + + makeFn frameId binder body = + MalFunction <| + let + lazyFn = + binder + >> Eval.fromResult + >> Eval.map + (\bound -> + -- TODO : choice Env.enter prematurely? + -- I think it is needed by the garbage collect.. + MalApply + { frameId = frameId + , bound = bound + , body = body + } + ) + in + UserFunc + { frameId = frameId + , lazyFn = lazyFn + , eagerFn = lazyFn >> Eval.andThen eval + , isMacro = False + , meta = Nothing + } + + go bindsList body = + extractAndParse bindsList + |> Eval.fromResult + -- reference the current frame. + |> Eval.ignore (Eval.modifyEnv Env.ref) + |> Eval.andThen + (\binder -> + Eval.withEnv + (\env -> + Eval.succeed + (makeFn env.currentFrameId binder body) + ) + ) + in + case args of + [ MalList bindsList, body ] -> + go bindsList body + + [ MalVector bindsVec, body ] -> + go (Array.toList bindsVec) body + + _ -> + Eval.fail "fn* expected two args: binds list and body" + + +evalQuote : List MalExpr -> Eval MalExpr +evalQuote args = + case args of + [ expr ] -> + Eval.succeed expr + + _ -> + Eval.fail "unsupported arguments" + + +evalQuasiQuote : MalExpr -> MalExpr +evalQuasiQuote expr = + let + apply list empty = + case list of + [ MalSymbol "unquote", ast ] -> + ast + + (MalList [ MalSymbol "splice-unquote", ast ]) :: rest -> + makeCall "concat" + [ ast + , evalQuasiQuote (MalList rest) + ] + + ast :: rest -> + makeCall "cons" + [ evalQuasiQuote ast + , evalQuasiQuote (MalList rest) + ] + + _ -> + makeCall "quote" [ empty ] + in + case expr of + MalList list -> + apply list (MalList []) + + MalVector vec -> + apply (Array.toList vec) (MalVector Array.empty) + + ast -> + makeCall "quote" [ ast ] + + +macroexpand : MalExpr -> Eval MalExpr +macroexpand expr = + let + expand expr env = + case expr of + MalList ((MalSymbol name) :: args) -> + case Env.get name env of + Ok (MalFunction (UserFunc fn)) -> + if fn.isMacro then + Left <| fn.eagerFn args + else + Right expr + + _ -> + Right expr + + _ -> + Right expr + in + Eval.runLoop expand expr + + +evalTry : List MalExpr -> Eval MalExpr +evalTry args = + case args of + [ body, MalList [ MalSymbol "catch*", MalSymbol sym, handler ] ] -> + eval body + |> Eval.catchError + (\ex -> + Eval.modifyEnv Env.push + |> Eval.andThen + (\_ -> + Eval.modifyEnv (Env.set sym ex) + ) + |> Eval.andThen (\_ -> evalNoApply handler) + |> Eval.ignore (Eval.modifyEnv Env.pop) + ) + + _ -> + Eval.fail "try* expected a body a catch block" + + +print : Env -> MalExpr -> String +print env = + printString env True + + +printError : Env -> MalExpr -> String +printError env expr = + "ERR:" ++ (printString env False expr) + + +{-| Read-Eval-Print. + +Doesn't actually run the Eval but returns the monad. + +-} +rep : String -> Maybe (Eval MalExpr) +rep input = + case readString input of + Ok Nothing -> + Nothing + + Err msg -> + Just (Eval.fail msg) + + Ok (Just ast) -> + eval ast |> Just From ac9c71d69ec19ab5aa502a197e71f0c34a0ac6f1 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Fri, 23 Jun 2017 17:05:53 +0200 Subject: [PATCH 0351/2308] Elm step A: fix time-ms --- elm/Core.elm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm/Core.elm b/elm/Core.elm index ffda42b575..02dc946853 100644 --- a/elm/Core.elm +++ b/elm/Core.elm @@ -838,7 +838,7 @@ ns = case msg of GotTime time -> Time.inMilliseconds time - |> truncate + |> floor |> MalInt |> Eval.succeed From 74547df6e4e2feab74fe1cd9a5665e71ca1ee9f2 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sun, 25 Jun 2017 10:59:09 +0200 Subject: [PATCH 0352/2308] Elm: fix step 2 and 3 --- elm/Makefile | 12 ++++++------ elm/step2_eval.elm | 29 ++++++++++++++++++++-------- elm/step3_env.elm | 48 ++++++++++++++++++++++++++++++---------------- 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/elm/Makefile b/elm/Makefile index 40ac7eff71..d9724a99f6 100644 --- a/elm/Makefile +++ b/elm/Makefile @@ -1,7 +1,6 @@ -SOURCES = step0_repl.elm step1_read_print.elm \ - step4_if_fn_do.elm step5_tco.elm step6_file.elm step7_quote.elm \ - step8_macros.elm step9_try.elm stepA_mal.elm - #step2_eval.elm step3_env.elm +SOURCES = step0_repl.elm step1_read_print.elm step2_eval.elm \ + step3_env.elm step4_if_fn_do.elm step5_tco.elm step6_file.elm \ + step7_quote.elm step8_macros.elm step9_try.elm stepA_mal.elm BINS = $(SOURCES:%.elm=%.js) @@ -24,9 +23,10 @@ STEP2_SOURCES = $(STEP1_SOURCES) STEP3_SOURCES = $(STEP2_SOURCES) STEP4_SOURCES = $(STEP3_SOURCES) Core.elm Eval.elm +step0_repl.js: $(STEP0_SOURCES) step1_read_print.js: $(STEP1_SOURCES) -#step2_eval.js: Reader.elm Printer.elm Utils.elm Types.elm -#step3_env.js: Reader.elm Printer.elm Utils.elm Types.elm Env.elm +step2_eval.js: $(STEP2_SOURCES) +step3_env.js: $(STEP3_SOURCES) step4_if_fn_do.js: $(STEP4_SOURCES) step5_tco.js: $(STEP4_SOURCES) step6_file.js: $(STEP4_SOURCES) diff --git a/elm/step2_eval.elm b/elm/step2_eval.elm index 7f7b85c8f5..39ebf8a751 100644 --- a/elm/step2_eval.elm +++ b/elm/step2_eval.elm @@ -3,13 +3,15 @@ port module Main exposing (..) import IO exposing (..) import Json.Decode exposing (decodeValue) import Platform exposing (programWithFlags) -import Types exposing (MalExpr(..), MalFunction(..)) +import Types exposing (..) import Reader exposing (readString) import Printer exposing (printString) import Utils exposing (maybeToList, zip) import Dict exposing (Dict) -import Tuple exposing (mapFirst) +import Tuple exposing (mapFirst, second) import Array +import Eval +import Env main : Program Flags Model Msg @@ -55,10 +57,10 @@ initReplEnv = binaryOp fn args = case args of [ MalInt x, MalInt y ] -> - Ok <| MalInt (fn x y) + Eval.succeed <| MalInt (fn x y) _ -> - Err "unsupported arguments" + Eval.fail "unsupported arguments" in Dict.fromList [ ( "+", makeFn <| binaryOp (+) ) @@ -85,6 +87,9 @@ update msg model = Input (Ok (LineRead Nothing)) -> ( model, Cmd.none ) + Input (Ok io) -> + Debug.crash "unexpected IO received: " io + Input (Err msg) -> Debug.crash msg ( model, Cmd.none ) @@ -129,11 +134,19 @@ eval env ast = [] -> ( Err "can't happen", newEnv ) - (MalFunction fn) :: args -> - ( fn args, newEnv ) + (MalFunction (CoreFunc fn)) :: args -> + case second <| Eval.run Env.global (fn args) of + EvalOk res -> + ( Ok res, newEnv ) + + EvalErr msg -> + ( Err (print msg), newEnv ) + + _ -> + Debug.crash "can't happen" fn :: _ -> - ( Err ((printString True fn) ++ " is not a function"), newEnv ) + ( Err ((print fn) ++ " is not a function"), newEnv ) ( Err msg, newEnv ) -> ( Err msg, newEnv ) @@ -219,7 +232,7 @@ tryMapList fn list = print : MalExpr -> String print = - printString True + printString Env.global True {-| Read-Eval-Print. rep returns: diff --git a/elm/step3_env.elm b/elm/step3_env.elm index 1f5c44ba5f..ddcb858f55 100644 --- a/elm/step3_env.elm +++ b/elm/step3_env.elm @@ -3,14 +3,15 @@ port module Main exposing (..) import IO exposing (..) import Json.Decode exposing (decodeValue) import Platform exposing (programWithFlags) -import Types exposing (MalExpr(..)) +import Types exposing (..) import Reader exposing (readString) import Printer exposing (printString) import Utils exposing (maybeToList, zip) import Dict exposing (Dict) -import Tuple exposing (mapFirst, mapSecond) +import Tuple exposing (mapFirst, mapSecond, second) import Array -import Env exposing (Env) +import Env +import Eval main : Program Flags Model Msg @@ -45,19 +46,22 @@ init { args } = initReplEnv : Env initReplEnv = let + makeFn = + CoreFunc >> MalFunction + binaryOp fn args = case args of [ MalInt x, MalInt y ] -> - Ok <| MalInt (fn x y) + Eval.succeed <| MalInt (fn x y) _ -> - Err "unsupported arguments" + Eval.fail "unsupported arguments" in - Env.make Nothing - |> Env.set "+" (MalFunction <| binaryOp (+)) - |> Env.set "-" (MalFunction <| binaryOp (-)) - |> Env.set "*" (MalFunction <| binaryOp (*)) - |> Env.set "/" (MalFunction <| binaryOp (//)) + Env.global + |> Env.set "+" (makeFn <| binaryOp (+)) + |> Env.set "-" (makeFn <| binaryOp (-)) + |> Env.set "*" (makeFn <| binaryOp (*)) + |> Env.set "/" (makeFn <| binaryOp (//)) update : Msg -> Model -> ( Model, Cmd Msg ) @@ -77,6 +81,9 @@ update msg model = Input (Ok (LineRead Nothing)) -> ( model, Cmd.none ) + Input (Ok io) -> + Debug.crash "unexpected IO received: " io + Input (Err msg) -> Debug.crash msg ( model, Cmd.none ) @@ -127,11 +134,19 @@ eval env ast = [] -> ( Err "can't happen", newEnv ) - (MalFunction fn) :: args -> - ( fn args, newEnv ) + (MalFunction (CoreFunc fn)) :: args -> + case second <| Eval.run Env.global (fn args) of + EvalOk res -> + ( Ok res, newEnv ) + + EvalErr msg -> + ( Err (print msg), newEnv ) + + _ -> + Debug.crash "can't happen" fn :: _ -> - ( Err ((printString True fn) ++ " is not a function"), newEnv ) + ( Err ((print fn) ++ " is not a function"), newEnv ) ( Err msg, newEnv ) -> ( Err msg, newEnv ) @@ -229,9 +244,10 @@ evalLet env args = Err "let* expected an even number of binds (symbol expr ..)" go binds body = - case evalBinds (Env.make (Just env)) binds of + case evalBinds (Env.push env) binds of Ok newEnv -> - mapSecond (\_ -> env) (eval newEnv body) + eval newEnv body + |> mapSecond (\_ -> Env.pop newEnv) Err msg -> ( Err msg, env ) @@ -274,7 +290,7 @@ tryMapList fn list = print : MalExpr -> String print = - printString True + printString Env.global True {-| Read-Eval-Print. rep returns: From 908bb61f763938a9a23b0657ae58e64effa294b0 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Thu, 29 Jun 2017 00:59:18 +0200 Subject: [PATCH 0353/2308] Implement step 0 --- Makefile | 3 ++- gst/Makefile | 17 +++++++++++++++++ gst/readline.st | 20 ++++++++++++++++++++ gst/run | 2 ++ gst/step0_repl.st | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 gst/Makefile create mode 100644 gst/readline.st create mode 100755 gst/run create mode 100644 gst/step0_repl.st diff --git a/Makefile b/Makefile index ab2b4fd058..cc8e49eb98 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ DOCKERIZE = # IMPLS = ada awk bash basic c d chuck clojure coffee common-lisp cpp crystal cs dart \ - erlang elisp elixir es6 factor forth fsharp go groovy guile haskell \ + erlang elisp elixir es6 factor forth fsharp go groovy gst guile haskell \ haxe io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ python r racket rpython ruby rust scala skew swift swift3 tcl ts vb vhdl \ @@ -175,6 +175,7 @@ forth_STEP_TO_PROG = forth/$($(1)).fs fsharp_STEP_TO_PROG = fsharp/$($(1)).exe go_STEP_TO_PROG = go/$($(1)) groovy_STEP_TO_PROG = groovy/$($(1)).groovy +gst_STEP_TO_PROG = gst/$($(1)).st java_STEP_TO_PROG = java/target/classes/mal/$($(1)).class haskell_STEP_TO_PROG = haskell/$($(1)) haxe_STEP_TO_PROG = $(haxe_STEP_TO_PROG_$(HAXE_MODE)) diff --git a/gst/Makefile b/gst/Makefile new file mode 100644 index 0000000000..f6c19c8d68 --- /dev/null +++ b/gst/Makefile @@ -0,0 +1,17 @@ +SOURCES_BASE = readline.st reader.st printer.st types.st +SOURCES_LISP = env.st func.st core.st stepA_mal.st +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +all: + +clean: + +.PHONY: stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" + diff --git a/gst/readline.st b/gst/readline.st new file mode 100644 index 0000000000..2dca73c7b6 --- /dev/null +++ b/gst/readline.st @@ -0,0 +1,20 @@ +DLD addLibrary: 'libreadline'. +DLD addLibrary: 'libhistory'. + +Object subclass: ReadLine [ + ReadLine class >> readLine: prompt [ + + ] + + ReadLine class >> addHistory: item [ + + ] + + ReadLine class >> readHistory: filePath [ + + ] + + ReadLine class >> writeHistory: filePath [ + + ] +] diff --git a/gst/run b/gst/run new file mode 100755 index 0000000000..4d413ea72d --- /dev/null +++ b/gst/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec gst -f $(dirname $0)/${STEP:-stepA_mal}.st "${@}" diff --git a/gst/step0_repl.st b/gst/step0_repl.st new file mode 100644 index 0000000000..982d89db02 --- /dev/null +++ b/gst/step0_repl.st @@ -0,0 +1,34 @@ +FileStream fileIn: 'readline.st'. + +Object subclass: MAL [ + MAL class >> READ: input [ + ^input + ] + + MAL class >> EVAL: sexp [ + ^sexp + ] + + MAL class >> PRINT: sexp [ + ^sexp + ] + + MAL class >> rep: input [ + ^self PRINT: (self EVAL: (self READ: input)) + ] +] + +| input historyFile | + +historyFile := '.mal_history'. +ReadLine readHistory: historyFile. + +[ input := ReadLine readLine: 'user> '. input isNil ] whileFalse: [ + input isEmpty ifFalse: [ + ReadLine addHistory: input. + ReadLine writeHistory: historyFile. + (MAL rep: input) displayNl. + ] +] + +'' displayNl. From 34aa5ced023ae3b6dc58de82e6c84fba6b8b2b8f Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 2 Jul 2017 14:41:36 +0200 Subject: [PATCH 0354/2308] Implement step 1 --- gst/Makefile | 2 +- gst/printer.st | 52 ++++++++++++ gst/reader.st | 173 ++++++++++++++++++++++++++++++++++++++++ gst/step1_read_print.st | 39 +++++++++ gst/types.st | 146 +++++++++++++++++++++++++++++++++ gst/util.st | 30 +++++++ 6 files changed, 441 insertions(+), 1 deletion(-) create mode 100644 gst/printer.st create mode 100644 gst/reader.st create mode 100644 gst/step1_read_print.st create mode 100644 gst/types.st create mode 100644 gst/util.st diff --git a/gst/Makefile b/gst/Makefile index f6c19c8d68..c876270e97 100644 --- a/gst/Makefile +++ b/gst/Makefile @@ -1,4 +1,4 @@ -SOURCES_BASE = readline.st reader.st printer.st types.st +SOURCES_BASE = readline.st reader.st printer.st types.st util.st SOURCES_LISP = env.st func.st core.st stepA_mal.st SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) diff --git a/gst/printer.st b/gst/printer.st new file mode 100644 index 0000000000..cfcf58ccc5 --- /dev/null +++ b/gst/printer.st @@ -0,0 +1,52 @@ +FileStream fileIn: 'types.st'. + +Object subclass: Printer [ + Printer class >> prStr: sexp printReadably: printReadably [ + sexp type = #true ifTrue: [ ^'true' ]. + sexp type = #false ifTrue: [ ^'false' ]. + sexp type = #nil ifTrue: [ ^'nil' ]. + + sexp type = #number ifTrue: [ ^sexp value asString ]. + sexp type = #symbol ifTrue: [ ^sexp value asString ]. + sexp type = #keyword ifTrue: [ ^':', sexp value ]. + + sexp type = #string ifTrue: [ + printReadably ifTrue: [ + ^sexp value repr + ] ifFalse: [ + ^sexp value + ] + ]. + + sexp type = #list ifTrue: [ + ^self prList: sexp printReadably: printReadably + starter: '(' ender: ')' + ]. + sexp type = #vector ifTrue: [ + ^self prList: sexp printReadably: printReadably + starter: '[' ender: ']' + ]. + sexp type = #map ifTrue: [ + ^self prMap: sexp printReadably: printReadably + ]. + + Error halt: 'unimplemented type' + ] + + Printer class >> prList: sexp printReadably: printReadably + starter: starter ender: ender [ + | items | + items := sexp value collect: + [ :item | self prStr: item printReadably: printReadably ]. + ^starter, (items join: ' ') , ender + ] + + Printer class >> prMap: sexp printReadably: printReadably [ + | items | + items := sexp value associations collect: + [ :item | + (self prStr: item key printReadably: printReadably), ' ', + (self prStr: item value printReadably: printReadably) ]. + ^'{', (items join: ', '), '}' + ] +] diff --git a/gst/reader.st b/gst/reader.st new file mode 100644 index 0000000000..4938b735d2 --- /dev/null +++ b/gst/reader.st @@ -0,0 +1,173 @@ +FileStream fileIn: 'types.st'. +FileStream fileIn: 'util.st'. + +Object subclass: Reader [ + | storage index | + + TokenRegex := '[\s,]*(~@|[\[\]{}()''`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}(''"`,;)]*)'. + CommentRegex := ';.*'. + NumberRegex := '-?[0-9]+(?:\.[0-9]+)?'. + + Reader class >> tokenizer: input [ + | tokens token hit pos done | + tokens := OrderedCollection new. + pos := 1. + done := false. + + [done] whileFalse: [ + hit := input searchRegex: TokenRegex startingAt: pos. + token := hit at: 1. + token size = 0 ifTrue: [ + tokens add: (input copyFrom: pos to: input size) trimSeparators. + done := true. + ]. + (token size = 0 or: [token matchRegex: CommentRegex]) ifFalse: [ + tokens add: token + ]. + pos := pos + (hit match size). + pos > input size ifTrue: [ + done := true. + ]. + ]. + ^tokens + ] + + Reader class >> readStr: input [ + | tokens reader form | + tokens := self tokenizer: input. + reader := self new: tokens. + tokens isEmpty ifTrue: [ + ^MALEmptyInput new signal + ]. + ^self readForm: reader. + ] + + Reader class >> readForm: reader [ + | token | + token := reader peek. + token = '(' ifTrue: [ + ^self readList: reader class: MALList ender: ')' + ]. + token = '[' ifTrue: [ + ^self readList: reader class: MALVector ender: ']' + ]. + token = '{' ifTrue: [ + ^self readList: reader class: MALMap ender: '}' + ]. + + (token matchRegex: '[])}]') ifTrue: [ + ^MALUnexpectedToken new signal: token + ]. + + token = '''' ifTrue: [ + ^self readSimpleMacro: reader name: #quote + ]. + token = '`' ifTrue: [ + ^self readSimpleMacro: reader name: #quasiquote + ]. + token = '~' ifTrue: [ + ^self readSimpleMacro: reader name: #unquote + ]. + token = '~@' ifTrue: [ + ^self readSimpleMacro: reader name: #'splice-unquote' + ]. + token = '@' ifTrue: [ + ^self readSimpleMacro: reader name: #deref + ]. + + token = '^' ifTrue: [ + ^self readWithMetaMacro: reader + ]. + + ^self readAtom: reader + ] + + Reader class >> readList: reader class: aClass ender: ender [ + | storage token | + storage := OrderedCollection new. + "pop opening token" + reader next. + [ token := reader peek. token isNil ] whileFalse: [ + token = ender ifTrue: [ + ender = '}' ifTrue: [ + storage := storage asDictionary. + ]. + "pop closing token" + reader next. + ^aClass new: storage + ]. + storage add: (self readForm: reader). + ]. + ^MALUnterminatedSequence new signal: ender + ] + + Reader class >> readAtom: reader [ + | token | + token := reader next. + + token = 'true' ifTrue: [ ^MALObject True ]. + token = 'false' ifTrue: [ ^MALObject False ]. + token = 'nil' ifTrue: [ ^MALObject Nil ]. + + (token first = $") ifTrue: [ + (token last = $") ifTrue: [ + ^MALString new: token parse + ] ifFalse: [ + ^MALUnterminatedSequence new signal: '"' + ] + ]. + + (token matchRegex: NumberRegex) ifTrue: [ + ^MALNumber new: token asNumber + ]. + + (token first = $:) ifTrue: [ + ^MALKeyword new: token allButFirst asSymbol + ]. + + ^MALSymbol new: token asSymbol + ] + + Reader class >> readSimpleMacro: reader name: name [ + | form list | + "pop reader macro token" + reader next. + form := self readForm: reader. + list := OrderedCollection from: { MALSymbol new: name. form }. + ^MALList new: list + ] + + Reader class >> readWithMetaMacro: reader [ + | form meta list | + "pop reader macro token" + reader next. + meta := self readForm: reader. + form := self readForm: reader. + list := OrderedCollection from: + { MALSymbol new: #'with-meta'. form. meta }. + ^MALList new: list + ] + + Reader class >> new: tokens [ + | reader | + reader := super new. + reader init: tokens. + ^reader + ] + + init: tokens [ + storage := tokens. + index := 1. + ] + + peek [ + ^storage at: index ifAbsent: [ ^nil ] + ] + + next [ + | token | + token := self peek. + index := index + 1. + ^token + ] +] diff --git a/gst/step1_read_print.st b/gst/step1_read_print.st new file mode 100644 index 0000000000..6b33705a97 --- /dev/null +++ b/gst/step1_read_print.st @@ -0,0 +1,39 @@ +FileStream fileIn: 'readline.st'. +FileStream fileIn: 'reader.st'. +FileStream fileIn: 'printer.st'. + +Object subclass: MAL [ + MAL class >> READ: input [ + ^Reader readStr: input + ] + + MAL class >> EVAL: sexp [ + ^sexp + ] + + MAL class >> PRINT: sexp [ + ^Printer prStr: sexp printReadably: true + ] + + MAL class >> rep: input [ + ^self PRINT: (self EVAL: (self READ: input)) + ] +] + +| input historyFile | + +historyFile := '.mal_history'. +ReadLine readHistory: historyFile. + +[ input := ReadLine readLine: 'user> '. input isNil ] whileFalse: [ + input isEmpty ifFalse: [ + ReadLine addHistory: input. + ReadLine writeHistory: historyFile. + [ (MAL rep: input) displayNl ] + on: MALEmptyInput do: [ #return ] + on: MALError do: + [ :err | ('error: ', err messageText) displayNl. #return ]. + ] +] + +'' displayNl. diff --git a/gst/types.st b/gst/types.st new file mode 100644 index 0000000000..f03cb74863 --- /dev/null +++ b/gst/types.st @@ -0,0 +1,146 @@ +Object subclass: MALObject [ + | type value meta | + + type [ ^type ] + value [ ^value ] + meta [ ^meta ] + + setValue: aValue [ + value := aValue. + ] + + setMeta: aMeta [ + meta := aMeta. + ] + + MALObject class >> new: type value: value meta: meta [ + | object | + object := super new. + object init: type value: value meta: meta. + ^object + ] + + init: aType value: aValue meta: aMeta [ + type := aType. + value := aValue. + meta := aMeta. + ] + + withMeta: meta [ + | object | + object := self deepCopy. + object setMeta: meta. + ^object + ] + + printOn: stream [ + stream nextPutAll: '<'; + nextPutAll: self class printString; + nextPutAll: ': '; + nextPutAll: value printString. + meta notNil ifTrue: [ + stream nextPutAll: ' | ' + nextPutAll: meta printString. + ]. + stream nextPutAll: '>'. + ] +] + +MALObject subclass: MALTrue [ + MALTrue class >> new [ + ^super new: #true value: true meta: nil. + ] +] + +MALObject subclass: MALFalse [ + MALFalse class >> new [ + ^super new: #false value: false meta: nil. + ] +] + +MALObject subclass: MALNil [ + MALNil class >> new [ + ^super new: #nil value: nil meta: nil. + ] +] + +MALObject class extend [ + True := MALTrue new. + False := MALFalse new. + Nil := MALNil new. + + True [ ^True ] + False [ ^False ] + Nil [ ^Nil ] +] + +MALObject subclass: MALNumber [ + MALNumber class >> new: value [ + ^super new: #number value: value meta: nil. + ] +] + +MALObject subclass: MALString [ + MALString class >> new: value [ + ^super new: #string value: value meta: nil. + ] +] + +MALObject subclass: MALSymbol [ + MALSymbol class >> new: value [ + ^super new: #symbol value: value meta: nil. + ] +] + +MALObject subclass: MALKeyword [ + MALKeyword class >> new: value [ + ^super new: #keyword value: value meta: nil. + ] +] + +MALObject subclass: MALList [ + MALList class >> new: value [ + ^super new: #list value: value meta: nil. + ] +] + +MALObject subclass: MALVector [ + MALVector class >> new: value [ + ^super new: #vector value: value meta: nil. + ] +] + +MALObject subclass: MALMap [ + MALMap class >> new: value [ + ^super new: #map value: value meta: nil. + ] +] + +MALObject subclass: MALAtom [ + MALAtom class >> new: value [ + ^super new: #atom value: value meta: nil. + ] +] + +Error subclass: MALError [ + description [ ^'A MAL-related error' ] + isResumable [ ^true ] +] + +MALError subclass: MALUnterminatedSequence [ + MALUnterminatedSequence class >> new [ ^super new ] + + messageText [ ^'expected ''', self basicMessageText, ''', got EOF' ] +] + +MALError subclass: MALUnexpectedToken [ + MALUnexpectedToken class >> new [ ^super new ] + + messageText [ ^'unexpected token: ''', self basicMessageText, ''''] +] + +MALError subclass: MALEmptyInput [ + MALEmptyInput class >> new [ ^super new ] + + messageText [ ^'Empty input' ] +] diff --git a/gst/util.st b/gst/util.st new file mode 100644 index 0000000000..82e64ec63f --- /dev/null +++ b/gst/util.st @@ -0,0 +1,30 @@ +OrderedCollection extend [ + asDictionary [ + | dict assoc | + dict := Dictionary new. + 1 to: self size by: 2 do: + [ :i | dict add: (self at: i) -> (self at: i + 1) ]. + ^dict + ] +] + +String extend [ + parse [ + |text| + text := self copyFrom: 2 to: self size - 1. + text := text copyReplaceAll: '\"' with: '"'. + text := text copyReplaceAll: '\n' with: ' +'. + text := text copyReplaceAll: '\\' with: '\'. + ^text + ] + + repr [ + |text| + text := self copyReplaceAll: '\' with: '\\'. + text := text copyReplaceAll: ' +' with: '\n'. + text := text copyReplaceAll: '"' with: '\"'. + ^'"', text, '"' + ] +] From 1b18f3590abddd904ca2a189c5c91c7c3a0b606e Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 2 Jul 2017 22:13:53 +0200 Subject: [PATCH 0355/2308] Implement step 2 --- gst/printer.st | 1 + gst/step2_eval.st | 82 +++++++++++++++++++++++++++++++++++++++++++++++ gst/types.st | 6 ++++ 3 files changed, 89 insertions(+) create mode 100644 gst/step2_eval.st diff --git a/gst/printer.st b/gst/printer.st index cfcf58ccc5..8cf6d6d985 100644 --- a/gst/printer.st +++ b/gst/printer.st @@ -2,6 +2,7 @@ FileStream fileIn: 'types.st'. Object subclass: Printer [ Printer class >> prStr: sexp printReadably: printReadably [ + sexp class = BlockClosure ifTrue: [ ^'#' ]. sexp type = #true ifTrue: [ ^'true' ]. sexp type = #false ifTrue: [ ^'false' ]. sexp type = #nil ifTrue: [ ^'nil' ]. diff --git a/gst/step2_eval.st b/gst/step2_eval.st new file mode 100644 index 0000000000..c105f9497b --- /dev/null +++ b/gst/step2_eval.st @@ -0,0 +1,82 @@ +FileStream fileIn: 'readline.st'. +FileStream fileIn: 'reader.st'. +FileStream fileIn: 'printer.st'. + +Object subclass: MAL [ + MAL class >> READ: input [ + ^Reader readStr: input + ] + + MAL class >> evalAst: sexp env: env [ + sexp type = #symbol ifTrue: [ + ^env at: sexp value ifAbsent: [ + ^MALUnknownSymbol new signal: sexp value + ]. + ]. + + sexp type = #list ifTrue: [ + ^self evalList: sexp env: env class: MALList + ]. + sexp type = #vector ifTrue: [ + ^self evalList: sexp env: env class: MALVector + ]. + sexp type = #map ifTrue: [ + ^self evalList: sexp env: env class: MALMap + ]. + + ^sexp + ] + + MAL class >> evalList: sexp env: env class: aClass [ + | items | + items := sexp value collect: + [ :item | self EVAL: item env: env ]. + ^aClass new: items + ] + + MAL class >> EVAL: sexp env: env [ + | forms function args | + sexp type ~= #list ifTrue: [ + ^self evalAst: sexp env: env + ]. + sexp value isEmpty ifTrue: [ + ^sexp + ]. + + forms := (self evalAst: sexp env: env) value. + function := forms first. + args := forms allButFirst asArray. + ^function valueWithArguments: args + ] + + MAL class >> PRINT: sexp [ + ^Printer prStr: sexp printReadably: true + ] + + MAL class >> rep: input env: env [ + ^self PRINT: (self EVAL: (self READ: input) env: env) + ] +] + +| input historyFile replEnv | + +historyFile := '.mal_history'. +ReadLine readHistory: historyFile. +replEnv := Dictionary from: + { #+ -> [ :a :b | MALNumber new: a value + b value ]. + #- -> [ :a :b | MALNumber new: a value - b value ]. + #* -> [ :a :b | MALNumber new: a value * b value ]. + #/ -> [ :a :b | MALNumber new: a value // b value ] }. + +[ input := ReadLine readLine: 'user> '. input isNil ] whileFalse: [ + input isEmpty ifFalse: [ + ReadLine addHistory: input. + ReadLine writeHistory: historyFile. + [ (MAL rep: input env: replEnv) displayNl ] + on: MALEmptyInput do: [ #return ] + on: MALError do: + [ :err | ('error: ', err messageText) displayNl. #return ]. + ] +] + +'' displayNl. diff --git a/gst/types.st b/gst/types.st index f03cb74863..6bf88f48d2 100644 --- a/gst/types.st +++ b/gst/types.st @@ -144,3 +144,9 @@ MALError subclass: MALEmptyInput [ messageText [ ^'Empty input' ] ] + +MALError subclass: MALUnknownSymbol [ + MALUnknownSymbol class >> new [ ^super new ] + + messageText [ ^'''', self basicMessageText, ''' not found'] +] From 4ade8121f77b50e34c9026729fcf7604eaaed490 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 3 Jul 2017 09:29:21 +0200 Subject: [PATCH 0356/2308] Implement step 3 --- gst/env.st | 42 +++++++++++++++++++ gst/step3_env.st | 104 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 gst/env.st create mode 100644 gst/step3_env.st diff --git a/gst/env.st b/gst/env.st new file mode 100644 index 0000000000..c73a828228 --- /dev/null +++ b/gst/env.st @@ -0,0 +1,42 @@ +FileStream fileIn: 'types.st'. + +Object subclass: Env [ + | data outer | + + Env class >> new: outerEnv [ + | env | + env := super new. + env init: outerEnv. + ^env + ] + + init: env [ + data := Dictionary new. + outer := env. + ] + + set: key value: value [ + data at: key put: value. + ] + + find: key [ + ^data at: key ifAbsent: [ + outer notNil ifTrue: [ + outer find: key + ] ifFalse: [ + nil + ] + ] + ] + + get: key [ + | value | + value := self find: key. + + value notNil ifTrue: [ + ^value + ] ifFalse: [ + ^MALUnknownSymbol new signal: key + ] + ] +] diff --git a/gst/step3_env.st b/gst/step3_env.st new file mode 100644 index 0000000000..bb7e8a14a7 --- /dev/null +++ b/gst/step3_env.st @@ -0,0 +1,104 @@ +FileStream fileIn: 'readline.st'. +FileStream fileIn: 'reader.st'. +FileStream fileIn: 'printer.st'. +FileStream fileIn: 'env.st'. + +Object subclass: MAL [ + MAL class >> READ: input [ + ^Reader readStr: input + ] + + MAL class >> evalAst: sexp env: env [ + sexp type = #symbol ifTrue: [ + ^env get: sexp value + ]. + + sexp type = #list ifTrue: [ + ^self evalList: sexp env: env class: MALList + ]. + sexp type = #vector ifTrue: [ + ^self evalList: sexp env: env class: MALVector + ]. + sexp type = #map ifTrue: [ + ^self evalList: sexp env: env class: MALMap + ]. + + ^sexp + ] + + MAL class >> evalList: sexp env: env class: aClass [ + | items | + items := sexp value collect: + [ :item | self EVAL: item env: env ]. + ^aClass new: items + ] + + MAL class >> EVAL: sexp env: env [ + | ast a0_ a1_ a2 forms function args | + sexp type ~= #list ifTrue: [ + ^self evalAst: sexp env: env + ]. + sexp value isEmpty ifTrue: [ + ^sexp + ]. + + ast := sexp value. + a0_ := ast first value. + a0_ = #'def!' ifTrue: [ + | result | + a1_ := ast second value. + a2 := ast third. + result := self EVAL: a2 env: env. + env set: a1_ value: result. + ^result + ]. + + a0_ = #'let*' ifTrue: [ + | env_ | + env_ := Env new: env. + a1_ := ast second value. + a2 := ast third. + 1 to: a1_ size by: 2 do: + [ :i | env_ set: (a1_ at: i) value + value: (self EVAL: (a1_ at: i + 1) env: env_) ]. + ^self EVAL: a2 env: env_ + ]. + + forms := (self evalAst: sexp env: env) value. + function := forms first. + args := forms allButFirst asArray. + ^function valueWithArguments: args + ] + + MAL class >> PRINT: sexp [ + ^Printer prStr: sexp printReadably: true + ] + + MAL class >> rep: input env: env [ + ^self PRINT: (self EVAL: (self READ: input) env: env) + ] +] + +| input historyFile replEnv | + +historyFile := '.mal_history'. +ReadLine readHistory: historyFile. +replEnv := Env new: nil. + +replEnv set: #+ value: [ :a :b | MALNumber new: a value + b value ]. +replEnv set: #- value: [ :a :b | MALNumber new: a value - b value ]. +replEnv set: #* value: [ :a :b | MALNumber new: a value * b value ]. +replEnv set: #/ value: [ :a :b | MALNumber new: a value // b value ]. + +[ input := ReadLine readLine: 'user> '. input isNil ] whileFalse: [ + input isEmpty ifFalse: [ + ReadLine addHistory: input. + ReadLine writeHistory: historyFile. + [ (MAL rep: input env: replEnv) displayNl ] + on: MALEmptyInput do: [ #return ] + on: MALError do: + [ :err | ('error: ', err messageText) displayNl. #return ]. + ] +] + +'' displayNl. From adb2ac7840fc004687a114462c5a647fecc2ce86 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Wed, 5 Jul 2017 09:15:41 +0200 Subject: [PATCH 0357/2308] Implement step 4 --- gst/core.st | 59 +++++++++++++++++++ gst/env.st | 17 +++++- gst/step4_if_fn_do.st | 132 ++++++++++++++++++++++++++++++++++++++++++ gst/types.st | 19 ++++++ 4 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 gst/core.st create mode 100644 gst/step4_if_fn_do.st diff --git a/gst/core.st b/gst/core.st new file mode 100644 index 0000000000..f4f397f6ef --- /dev/null +++ b/gst/core.st @@ -0,0 +1,59 @@ +FileStream fileIn: 'types.st'. +FileStream fileIn: 'printer.st'. + +Object subclass: Core [ + Ns := Dictionary new. + Core class >> Ns [ ^Ns ] + + Core class >> coerce: block [ + block value ifTrue: [ ^MALObject True ] ifFalse: [ ^MALObject False ] + ] + + Core class >> printedArgs: args readable: readable sep: sep [ + | items | + items := args collect: + [ :arg | Printer prStr: arg printReadably: readable ]. + "NOTE: {} join returns the unchanged array" + items isEmpty ifTrue: [ ^'' ] ifFalse: [ ^items join: sep ] + ] +] + +Core Ns at: #+ put: + [ :args | MALNumber new: args first value + args second value ]. +Core Ns at: #- put: + [ :args | MALNumber new: args first value - args second value ]. +Core Ns at: #* put: + [ :args | MALNumber new: args first value * args second value ]. +Core Ns at: #/ put: + [ :args | MALNumber new: args first value // args second value ]. + +Core Ns at: #'pr-str' put: + [ :args | MALString new: (Core printedArgs: args readable: true sep: ' ') ]. +Core Ns at: #str put: + [ :args | MALString new: (Core printedArgs: args readable: false sep: '') ]. +Core Ns at: #prn put: + [ :args | (Core printedArgs: args readable: true sep: ' ') displayNl. + MALObject Nil ]. +Core Ns at: #println put: + [ :args | (Core printedArgs: args readable: false sep: ' ') displayNl. + MALObject Nil ]. + +Core Ns at: #list put: [ :args | MALList new: (OrderedCollection from: args) ]. +Core Ns at: #'list?' put: + [ :args | Core coerce: [ args first type = #list ] ]. +Core Ns at: #'empty?' put: + [ :args | Core coerce: [ args first value isEmpty ] ]. +Core Ns at: #count put: + [ :args | MALNumber new: args first value size ]. + +Core Ns at: #= put: + [ :args | Core coerce: [ args first = args second ] ]. + +Core Ns at: #< put: + [ :args | Core coerce: [ args first value < args second value ] ]. +Core Ns at: #<= put: + [ :args | Core coerce: [ args first value <= args second value ] ]. +Core Ns at: #> put: + [ :args | Core coerce: [ args first value > args second value ] ]. +Core Ns at: #>= put: + [ :args | Core coerce: [ args first value >= args second value ] ]. diff --git a/gst/env.st b/gst/env.st index c73a828228..ee9111cd07 100644 --- a/gst/env.st +++ b/gst/env.st @@ -4,15 +4,28 @@ Object subclass: Env [ | data outer | Env class >> new: outerEnv [ + ^self new: outerEnv binds: {} exprs: {} + ] + + Env class >> new: outerEnv binds: binds exprs: exprs [ | env | env := super new. - env init: outerEnv. + env init: outerEnv binds: binds exprs: exprs. ^env ] - init: env [ + init: env binds: binds exprs: exprs [ data := Dictionary new. outer := env. + 1 to: binds size do: + [ :i | (binds at: i) = #& ifTrue: [ + | rest | + rest := MALList new: (exprs copyFrom: i). + self set: (binds at: i + 1) value: rest. + ^nil + ] ifFalse: [ + self set: (binds at: i) value: (exprs at: i) + ] ] ] set: key value: value [ diff --git a/gst/step4_if_fn_do.st b/gst/step4_if_fn_do.st new file mode 100644 index 0000000000..138b967838 --- /dev/null +++ b/gst/step4_if_fn_do.st @@ -0,0 +1,132 @@ +FileStream fileIn: 'readline.st'. +FileStream fileIn: 'reader.st'. +FileStream fileIn: 'printer.st'. +FileStream fileIn: 'env.st'. +FileStream fileIn: 'core.st'. + +Object subclass: MAL [ + MAL class >> READ: input [ + ^Reader readStr: input + ] + + MAL class >> evalAst: sexp env: env [ + sexp type = #symbol ifTrue: [ + ^env get: sexp value + ]. + + sexp type = #list ifTrue: [ + ^self evalList: sexp env: env class: MALList + ]. + sexp type = #vector ifTrue: [ + ^self evalList: sexp env: env class: MALVector + ]. + sexp type = #map ifTrue: [ + ^self evalList: sexp env: env class: MALMap + ]. + + ^sexp + ] + + MAL class >> evalList: sexp env: env class: aClass [ + | items | + items := sexp value collect: + [ :item | self EVAL: item env: env ]. + ^aClass new: items + ] + + MAL class >> EVAL: sexp env: env [ + | ast a0_ a1 a1_ a1_n a2 a3 forms function args | + sexp type ~= #list ifTrue: [ + ^self evalAst: sexp env: env + ]. + sexp value isEmpty ifTrue: [ + ^sexp + ]. + + ast := sexp value. + a0_ := ast first value. + a0_ = #'def!' ifTrue: [ + | result | + a1_ := ast second value. + a2 := ast third. + result := self EVAL: a2 env: env. + env set: a1_ value: result. + ^result + ]. + + a0_ = #'let*' ifTrue: [ + | env_ | + env_ := Env new: env. + a1_ := ast second value. + a2 := ast third. + 1 to: a1_ size by: 2 do: + [ :i | env_ set: (a1_ at: i) value + value: (self EVAL: (a1_ at: i + 1) env: env_) ]. + ^self EVAL: a2 env: env_ + ]. + + a0_ = #do ifTrue: [ + a1_n := ast allButFirst. + ^(a1_n collect: [ :item | self EVAL: item env: env]) last + ]. + + a0_ = #if ifTrue: [ + | condition | + a1 := ast second. + a2 := ast third. + a3 := ast at: 4 ifAbsent: [ MALObject Nil ]. + condition := self EVAL: a1 env: env. + + (condition type = #false or: [ condition type = #nil ]) ifTrue: [ + ^self EVAL: a3 env: env + ] ifFalse: [ + ^self EVAL: a2 env: env + ] + ]. + + a0_ = #'fn*' ifTrue: [ + | binds | + a1_ := ast second value. + binds := a1_ collect: [ :item | item value ]. + a2 := ast third. + ^[ :args | self EVAL: a2 env: + (Env new: env binds: binds exprs: args) ] + ]. + + forms := (self evalAst: sexp env: env) value. + function := forms first. + args := forms allButFirst asArray. + ^function value: args + ] + + MAL class >> PRINT: sexp [ + ^Printer prStr: sexp printReadably: true + ] + + MAL class >> rep: input env: env [ + ^self PRINT: (self EVAL: (self READ: input) env: env) + ] +] + +| input historyFile replEnv | + +historyFile := '.mal_history'. +ReadLine readHistory: historyFile. +replEnv := Env new: nil. + +Core Ns keysAndValuesDo: [ :op :block | replEnv set: op value: block ]. + +MAL rep: '(def! not (fn* (a) (if a false true)))' env: replEnv. + +[ input := ReadLine readLine: 'user> '. input isNil ] whileFalse: [ + input isEmpty ifFalse: [ + ReadLine addHistory: input. + ReadLine writeHistory: historyFile. + [ (MAL rep: input env: replEnv) displayNl ] + on: MALEmptyInput do: [ #return ] + on: MALError do: + [ :err | ('error: ', err messageText) displayNl. #return ]. + ] +] + +'' displayNl. diff --git a/gst/types.st b/gst/types.st index 6bf88f48d2..d9015e8106 100644 --- a/gst/types.st +++ b/gst/types.st @@ -44,6 +44,15 @@ Object subclass: MALObject [ ]. stream nextPutAll: '>'. ] + + = x [ + self type ~= x type ifTrue: [ ^false ]. + ^self value = x value + ] + + hash [ + ^self value hash + ] ] MALObject subclass: MALTrue [ @@ -102,12 +111,22 @@ MALObject subclass: MALList [ MALList class >> new: value [ ^super new: #list value: value meta: nil. ] + + = x [ + (x type ~= #list and: [ x type ~= #vector ]) ifTrue: [ ^false ]. + ^self value = x value + ] ] MALObject subclass: MALVector [ MALVector class >> new: value [ ^super new: #vector value: value meta: nil. ] + + = x [ + (x type ~= #vector and: [ x type ~= #list ]) ifTrue: [ ^false ]. + ^self value = x value + ] ] MALObject subclass: MALMap [ From 6da164bde8cb5e64b56e19ccc4e809e58a7020ae Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Thu, 6 Jul 2017 22:54:34 +0200 Subject: [PATCH 0358/2308] Implement step 5 --- gst/func.st | 22 ++++++ gst/printer.st | 1 + gst/step5_tco.st | 169 +++++++++++++++++++++++++++++++++++++++++++++++ gst/util.st | 6 ++ 4 files changed, 198 insertions(+) create mode 100644 gst/func.st create mode 100644 gst/step5_tco.st diff --git a/gst/func.st b/gst/func.st new file mode 100644 index 0000000000..90ecd62063 --- /dev/null +++ b/gst/func.st @@ -0,0 +1,22 @@ +Object subclass: Func [ + | ast params env fn | + + ast [ ^ast ] + params [ ^params ] + env [ ^env ] + fn [ ^fn ] + + Func class >> new: ast params: params env: env fn: fn [ + | func | + func := super new. + func init: ast params: params env: env fn: fn. + ^func + ] + + init: anAst params: someParams env: anEnv fn: aFn [ + ast := anAst. + params := someParams. + env := anEnv. + fn := aFn. + ] +] diff --git a/gst/printer.st b/gst/printer.st index 8cf6d6d985..5543892263 100644 --- a/gst/printer.st +++ b/gst/printer.st @@ -3,6 +3,7 @@ FileStream fileIn: 'types.st'. Object subclass: Printer [ Printer class >> prStr: sexp printReadably: printReadably [ sexp class = BlockClosure ifTrue: [ ^'#' ]. + sexp class = Func ifTrue: [ ^'#' ]. sexp type = #true ifTrue: [ ^'true' ]. sexp type = #false ifTrue: [ ^'false' ]. sexp type = #nil ifTrue: [ ^'nil' ]. diff --git a/gst/step5_tco.st b/gst/step5_tco.st new file mode 100644 index 0000000000..1515f211da --- /dev/null +++ b/gst/step5_tco.st @@ -0,0 +1,169 @@ +FileStream fileIn: 'readline.st'. +FileStream fileIn: 'reader.st'. +FileStream fileIn: 'printer.st'. +FileStream fileIn: 'env.st'. +FileStream fileIn: 'func.st'. +FileStream fileIn: 'core.st'. + +Object subclass: MAL [ + MAL class >> READ: input [ + ^Reader readStr: input + ] + + MAL class >> evalAst: sexp env: env [ + sexp type = #symbol ifTrue: [ + ^env get: sexp value + ]. + + sexp type = #list ifTrue: [ + ^self evalList: sexp env: env class: MALList + ]. + sexp type = #vector ifTrue: [ + ^self evalList: sexp env: env class: MALVector + ]. + sexp type = #map ifTrue: [ + ^self evalList: sexp env: env class: MALMap + ]. + + ^sexp + ] + + MAL class >> evalList: sexp env: env class: aClass [ + | items | + items := sexp value collect: + [ :item | self EVAL: item env: env ]. + ^aClass new: items + ] + + MAL class >> EVAL: aSexp env: anEnv [ + | sexp env ast a0_ a1 a1_ a2 a3 an forms function args | + + "NOTE: redefinition of method arguments is not allowed" + sexp := aSexp. + env := anEnv. + + [ + [ :continue | + sexp type ~= #list ifTrue: [ + ^self evalAst: sexp env: env + ]. + sexp value isEmpty ifTrue: [ + ^sexp + ]. + + ast := sexp value. + a0_ := ast first value. + + a0_ = #'def!' ifTrue: [ + | result | + a1_ := ast second value. + a2 := ast third. + result := self EVAL: a2 env: env. + env set: a1_ value: result. + ^result + ]. + + a0_ = #'let*' ifTrue: [ + | env_ | + env_ := Env new: env. + a1_ := ast second value. + a2 := ast third. + 1 to: a1_ size by: 2 do: + [ :i | env_ set: (a1_ at: i) value + value: (self EVAL: (a1_ at: i + 1) + env: env_) ]. + env := env_. + sexp := a2. + continue value "TCO" + ]. + + a0_ = #do ifTrue: [ + | forms last | + ast size < 2 ifTrue: [ + forms := {}. + last := MALObject Nil. + ] ifFalse: [ + forms := ast copyFrom: 2 to: ast size - 1. + last := ast last. + ]. + + forms do: [ :form | self EVAL: form env: env ]. + sexp := last. + continue value "TCO" + ]. + + a0_ = #if ifTrue: [ + | condition | + a1 := ast second. + a2 := ast third. + a3 := ast at: 4 ifAbsent: [ MALObject Nil ]. + condition := self EVAL: a1 env: env. + + (condition type = #false or: + [ condition type = #nil ]) ifTrue: [ + sexp := a3 + ] ifFalse: [ + sexp := a2 + ]. + continue value "TCO" + ]. + + a0_ = #'fn*' ifTrue: [ + | binds env_ fn | + a1_ := ast second value. + binds := a1_ collect: [ :item | item value ]. + a2 := ast third. + fn := [ :args | + self EVAL: a2 env: + (Env new: env binds: binds exprs: args) ]. + ^Func new: a2 params: binds env: env fn: fn + ]. + + forms := (self evalAst: sexp env: env) value. + function := forms first. + args := forms allButFirst asArray. + + function class = BlockClosure ifTrue: [ ^function value: args ]. + function class = Func ifTrue: [ + | env_ | + sexp := function ast. + env_ := Env new: function env binds: function params + exprs: args. + env := env_. + continue value "TCO" + ] + ] valueWithExit + ] repeat. + ] + + MAL class >> PRINT: sexp [ + ^Printer prStr: sexp printReadably: true + ] + + MAL class >> rep: input env: env [ + ^self PRINT: (self EVAL: (self READ: input) env: env) + ] +] + +| input historyFile replEnv | + +historyFile := '.mal_history'. +ReadLine readHistory: historyFile. +replEnv := Env new: nil. + +Core Ns keysAndValuesDo: [ :op :block | replEnv set: op value: block ]. + +MAL rep: '(def! not (fn* (a) (if a false true)))' env: replEnv. + +[ input := ReadLine readLine: 'user> '. input isNil ] whileFalse: [ + input isEmpty ifFalse: [ + ReadLine addHistory: input. + ReadLine writeHistory: historyFile. + [ (MAL rep: input env: replEnv) displayNl ] + on: MALEmptyInput do: [ #return ] + on: MALError do: + [ :err | ('error: ', err messageText) displayNl. #return ]. + ] +] + +'' displayNl. diff --git a/gst/util.st b/gst/util.st index 82e64ec63f..4a93866f23 100644 --- a/gst/util.st +++ b/gst/util.st @@ -28,3 +28,9 @@ String extend [ ^'"', text, '"' ] ] + +BlockClosure extend [ + valueWithExit [ + ^self value: [ ^nil ] + ] +] From e3ce370c5f91f3f196bb207f40037519faf90292 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Fri, 7 Jul 2017 18:36:48 +0200 Subject: [PATCH 0359/2308] Implement step 6 --- gst/core.st | 27 +++++++ gst/func.st | 14 +++- gst/printer.st | 4 + gst/step6_file.st | 185 ++++++++++++++++++++++++++++++++++++++++++++++ gst/types.st | 6 +- 5 files changed, 232 insertions(+), 4 deletions(-) create mode 100644 gst/step6_file.st diff --git a/gst/core.st b/gst/core.st index f4f397f6ef..c8b699d6bf 100644 --- a/gst/core.st +++ b/gst/core.st @@ -1,5 +1,6 @@ FileStream fileIn: 'types.st'. FileStream fileIn: 'printer.st'. +FileStream fileIn: 'reader.st'. Object subclass: Core [ Ns := Dictionary new. @@ -57,3 +58,29 @@ Core Ns at: #> put: [ :args | Core coerce: [ args first value > args second value ] ]. Core Ns at: #>= put: [ :args | Core coerce: [ args first value >= args second value ] ]. + +Core Ns at: #'read-string' put: + [ :args | Reader readStr: args first value ]. +Core Ns at: #slurp put: + [ :args | MALString new: (File path: args first value) contents ]. + +Core Ns at: #atom put: + [ :args | MALAtom new: args first ]. +Core Ns at: #'atom?' put: + [ :args | Core coerce: [ args first type = #atom ] ]. +Core Ns at: #deref put: + [ :args | args first value ]. +Core Ns at: #'reset!' put: + [ :args | args first value: args second. args second ]. +Core Ns at: #'swap!' put: + [ :args | + | a f x xs result | + a := args first. + f := args second. + f class = Func ifTrue: [ f := f fn ]. + x := a value. + xs := args allButFirst: 2. + result := f value: (xs copyWithFirst: x). + a value: result. + result + ]. diff --git a/gst/func.st b/gst/func.st index 90ecd62063..ccff241e90 100644 --- a/gst/func.st +++ b/gst/func.st @@ -1,10 +1,15 @@ Object subclass: Func [ - | ast params env fn | + | ast params env fn meta | ast [ ^ast ] params [ ^params ] env [ ^env ] fn [ ^fn ] + meta [ ^meta ] + + meta: aMeta [ + meta := aMeta + ] Func class >> new: ast params: params env: env fn: fn [ | func | @@ -19,4 +24,11 @@ Object subclass: Func [ env := anEnv. fn := aFn. ] + + withMeta: meta [ + | func | + func := self deepCopy. + func meta: meta. + ^func + ] ] diff --git a/gst/printer.st b/gst/printer.st index 5543892263..7079a49a28 100644 --- a/gst/printer.st +++ b/gst/printer.st @@ -32,6 +32,10 @@ Object subclass: Printer [ ^self prMap: sexp printReadably: printReadably ]. + sexp type = #atom ifTrue: [ + ^'(atom ', (self prStr: sexp value printReadably: printReadably), ')' + ]. + Error halt: 'unimplemented type' ] diff --git a/gst/step6_file.st b/gst/step6_file.st new file mode 100644 index 0000000000..f7d6edb289 --- /dev/null +++ b/gst/step6_file.st @@ -0,0 +1,185 @@ +FileStream fileIn: 'readline.st'. +FileStream fileIn: 'reader.st'. +FileStream fileIn: 'printer.st'. +FileStream fileIn: 'env.st'. +FileStream fileIn: 'func.st'. +FileStream fileIn: 'core.st'. + +Object subclass: MAL [ + MAL class >> READ: input [ + ^Reader readStr: input + ] + + MAL class >> evalAst: sexp env: env [ + sexp class = BlockClosure ifTrue: [^sexp ]. + + sexp type = #symbol ifTrue: [ + ^env get: sexp value + ]. + + sexp type = #list ifTrue: [ + ^self evalList: sexp env: env class: MALList + ]. + sexp type = #vector ifTrue: [ + ^self evalList: sexp env: env class: MALVector + ]. + sexp type = #map ifTrue: [ + ^self evalList: sexp env: env class: MALMap + ]. + + ^sexp + ] + + MAL class >> evalList: sexp env: env class: aClass [ + | items | + items := sexp value collect: + [ :item | self EVAL: item env: env ]. + ^aClass new: items + ] + + MAL class >> EVAL: aSexp env: anEnv [ + | sexp env ast a0 a0_ a1 a1_ a2 a3 an forms function args | + + "NOTE: redefinition of method arguments is not allowed" + sexp := aSexp. + env := anEnv. + + [ + [ :continue | + (sexp class = BlockClosure or: [ sexp type ~= #list ]) ifTrue: [ + ^self evalAst: sexp env: env + ]. + sexp value isEmpty ifTrue: [ + ^sexp + ]. + + ast := sexp value. + a0 := ast first. + + a0 class ~= BlockClosure ifTrue: [ + a0_ := ast first value. + a0_ = #'def!' ifTrue: [ + | result | + a1_ := ast second value. + a2 := ast third. + result := self EVAL: a2 env: env. + env set: a1_ value: result. + ^result + ]. + + a0_ = #'let*' ifTrue: [ + | env_ | + env_ := Env new: env. + a1_ := ast second value. + a2 := ast third. + 1 to: a1_ size by: 2 do: + [ :i | env_ set: (a1_ at: i) value + value: (self EVAL: (a1_ at: i + 1) + env: env_) ]. + env := env_. + sexp := a2. + continue value "TCO" + ]. + + a0_ = #do ifTrue: [ + | forms last | + ast size < 2 ifTrue: [ + forms := {}. + last := MALObject Nil. + ] ifFalse: [ + forms := ast copyFrom: 2 to: ast size - 1. + last := ast last. + ]. + + forms do: [ :form | self EVAL: form env: env ]. + sexp := last. + continue value "TCO" + ]. + + a0_ = #if ifTrue: [ + | condition | + a1 := ast second. + a2 := ast third. + a3 := ast at: 4 ifAbsent: [ MALObject Nil ]. + condition := self EVAL: a1 env: env. + + (condition type = #false or: + [ condition type = #nil ]) ifTrue: [ + sexp := a3 + ] ifFalse: [ + sexp := a2 + ]. + continue value "TCO" + ]. + + a0_ = #'fn*' ifTrue: [ + | binds env_ fn | + a1_ := ast second value. + binds := a1_ collect: [ :item | item value ]. + a2 := ast third. + fn := [ :args | + self EVAL: a2 env: + (Env new: env binds: binds exprs: args) ]. + ^Func new: a2 params: binds env: env fn: fn + ] + ]. + + forms := (self evalAst: sexp env: env) value. + function := forms first. + args := forms allButFirst asArray. + + function class = BlockClosure ifTrue: [ ^function value: args ]. + function class = Func ifTrue: [ + | env_ | + sexp := function ast. + env_ := Env new: function env binds: function params + exprs: args. + env := env_. + continue value "TCO" + ] + ] valueWithExit + ] repeat. + ] + + MAL class >> PRINT: sexp [ + ^Printer prStr: sexp printReadably: true + ] + + MAL class >> rep: input env: env [ + ^self PRINT: (self EVAL: (self READ: input) env: env) + ] +] + +| input historyFile replEnv argv | + +historyFile := '.mal_history'. +ReadLine readHistory: historyFile. +replEnv := Env new: nil. + +argv := Smalltalk arguments. +argv notEmpty ifTrue: [ argv := argv allButFirst ]. +argv := OrderedCollection from: (argv collect: [ :arg | MALString new: arg ]). + +Core Ns keysAndValuesDo: [ :op :block | replEnv set: op value: block ]. +replEnv set: #eval value: [ :args | MAL EVAL: args first env: replEnv ]. +replEnv set: #'*ARGV*' value: (MALList new: argv). + +MAL rep: '(def! not (fn* (a) (if a false true)))' env: replEnv. +MAL rep: '(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))' env: replEnv. + +Smalltalk arguments notEmpty ifTrue: [ + MAL rep: '(load-file "', Smalltalk arguments first, '")' env: replEnv +] ifFalse: [ + [ input := ReadLine readLine: 'user> '. input isNil ] whileFalse: [ + input isEmpty ifFalse: [ + ReadLine addHistory: input. + ReadLine writeHistory: historyFile. + [ (MAL rep: input env: replEnv) displayNl ] + on: MALEmptyInput do: [ #return ] + on: MALError do: + [ :err | ('error: ', err messageText) displayNl. #return ]. + ] + ]. + + '' displayNl. +] diff --git a/gst/types.st b/gst/types.st index d9015e8106..4938b1b60f 100644 --- a/gst/types.st +++ b/gst/types.st @@ -5,11 +5,11 @@ Object subclass: MALObject [ value [ ^value ] meta [ ^meta ] - setValue: aValue [ + value: aValue [ value := aValue. ] - setMeta: aMeta [ + meta: aMeta [ meta := aMeta. ] @@ -29,7 +29,7 @@ Object subclass: MALObject [ withMeta: meta [ | object | object := self deepCopy. - object setMeta: meta. + object meta: meta. ^object ] From 243c27c3b8c792e20ad5e8908d20164db020c29d Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Fri, 7 Jul 2017 20:36:14 +0200 Subject: [PATCH 0360/2308] Stylistic fixes --- gst/reader.st | 2 +- gst/step5_tco.st | 2 +- gst/step6_file.st | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gst/reader.st b/gst/reader.st index 4938b735d2..2cbeea0353 100644 --- a/gst/reader.st +++ b/gst/reader.st @@ -161,7 +161,7 @@ Object subclass: Reader [ ] peek [ - ^storage at: index ifAbsent: [ ^nil ] + ^storage at: index ifAbsent: [ nil ] ] next [ diff --git a/gst/step5_tco.st b/gst/step5_tco.st index 1515f211da..7ac7f1bbca 100644 --- a/gst/step5_tco.st +++ b/gst/step5_tco.st @@ -36,7 +36,7 @@ Object subclass: MAL [ ] MAL class >> EVAL: aSexp env: anEnv [ - | sexp env ast a0_ a1 a1_ a2 a3 an forms function args | + | sexp env ast a0_ a1 a1_ a2 a3 forms function args | "NOTE: redefinition of method arguments is not allowed" sexp := aSexp. diff --git a/gst/step6_file.st b/gst/step6_file.st index f7d6edb289..8040d66f80 100644 --- a/gst/step6_file.st +++ b/gst/step6_file.st @@ -38,7 +38,7 @@ Object subclass: MAL [ ] MAL class >> EVAL: aSexp env: anEnv [ - | sexp env ast a0 a0_ a1 a1_ a2 a3 an forms function args | + | sexp env ast a0 a0_ a1 a1_ a2 a3 forms function args | "NOTE: redefinition of method arguments is not allowed" sexp := aSexp. From d586e01498e228273de6591c8d6ba0d12a447528 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Fri, 7 Jul 2017 20:36:27 +0200 Subject: [PATCH 0361/2308] Implement step 7 --- gst/core.st | 6 ++ gst/step7_quote.st | 229 +++++++++++++++++++++++++++++++++++++++++++++ gst/types.st | 5 + 3 files changed, 240 insertions(+) create mode 100644 gst/step7_quote.st diff --git a/gst/core.st b/gst/core.st index c8b699d6bf..a59f774d26 100644 --- a/gst/core.st +++ b/gst/core.st @@ -84,3 +84,9 @@ Core Ns at: #'swap!' put: a value: result. result ]. + +Core Ns at: #cons put: + [ :args | MALList new: (args second value copyWithFirst: args first) ]. +Core Ns at: #concat put: + [ :args | MALList new: (OrderedCollection join: + (args collect: [ :arg | arg value ])) ]. diff --git a/gst/step7_quote.st b/gst/step7_quote.st new file mode 100644 index 0000000000..6ca5aabac0 --- /dev/null +++ b/gst/step7_quote.st @@ -0,0 +1,229 @@ +FileStream fileIn: 'readline.st'. +FileStream fileIn: 'reader.st'. +FileStream fileIn: 'printer.st'. +FileStream fileIn: 'env.st'. +FileStream fileIn: 'func.st'. +FileStream fileIn: 'core.st'. + +Object subclass: MAL [ + MAL class >> READ: input [ + ^Reader readStr: input + ] + + MAL class >> evalAst: sexp env: env [ + sexp class = BlockClosure ifTrue: [^sexp ]. + + sexp type = #symbol ifTrue: [ + ^env get: sexp value + ]. + + sexp type = #list ifTrue: [ + ^self evalList: sexp env: env class: MALList + ]. + sexp type = #vector ifTrue: [ + ^self evalList: sexp env: env class: MALVector + ]. + sexp type = #map ifTrue: [ + ^self evalList: sexp env: env class: MALMap + ]. + + ^sexp + ] + + MAL class >> evalList: sexp env: env class: aClass [ + | items | + items := sexp value collect: + [ :item | self EVAL: item env: env ]. + ^aClass new: items + ] + + MAL class >> quasiquote: ast [ + | result a a0 a0_ a0_0 a0_1 rest | + ast isPair ifFalse: [ + result := {MALSymbol new: #quote. ast}. + ^MALList new: (OrderedCollection from: result) + ]. + + a := ast value. + a0 := a first. + + (a0 type = #symbol and: [ a0 value = #unquote ]) ifTrue: [ ^a second ]. + + a0 isPair ifTrue: [ + a0_ := a0 value. + a0_0 := a0_ first. + a0_1 := a0_ second. + + (a0_0 type = #symbol and: + [ a0_0 value = #'splice-unquote' ]) ifTrue: [ + rest := MALList new: a allButFirst. + result := {MALSymbol new: #concat. a0_1. + self quasiquote: rest}. + ^MALList new: (OrderedCollection from: result) + ] + ]. + + rest := MALList new: a allButFirst. + result := {MALSymbol new: #cons. self quasiquote: a0. + self quasiquote: rest}. + ^MALList new: (OrderedCollection from: result) + ] + + MAL class >> EVAL: aSexp env: anEnv [ + | sexp env ast a0 a0_ a1 a1_ a2 a3 forms function args | + + "NOTE: redefinition of method arguments is not allowed" + sexp := aSexp. + env := anEnv. + + [ + [ :continue | + (sexp class = BlockClosure or: [ sexp type ~= #list ]) ifTrue: [ + ^self evalAst: sexp env: env + ]. + sexp value isEmpty ifTrue: [ + ^sexp + ]. + + ast := sexp value. + a0 := ast first. + + a0 class ~= BlockClosure ifTrue: [ + a0_ := ast first value. + a0_ = #'def!' ifTrue: [ + | result | + a1_ := ast second value. + a2 := ast third. + result := self EVAL: a2 env: env. + env set: a1_ value: result. + ^result + ]. + + a0_ = #'let*' ifTrue: [ + | env_ | + env_ := Env new: env. + a1_ := ast second value. + a2 := ast third. + 1 to: a1_ size by: 2 do: + [ :i | env_ set: (a1_ at: i) value + value: (self EVAL: (a1_ at: i + 1) + env: env_) ]. + env := env_. + sexp := a2. + continue value "TCO" + ]. + + a0_ = #do ifTrue: [ + | forms last | + ast size < 2 ifTrue: [ + forms := {}. + last := MALObject Nil. + ] ifFalse: [ + forms := ast copyFrom: 2 to: ast size - 1. + last := ast last. + ]. + + forms do: [ :form | self EVAL: form env: env ]. + sexp := last. + continue value "TCO" + ]. + + a0_ = #if ifTrue: [ + | condition | + a1 := ast second. + a2 := ast third. + a3 := ast at: 4 ifAbsent: [ MALObject Nil ]. + condition := self EVAL: a1 env: env. + + (condition type = #false or: + [ condition type = #nil ]) ifTrue: [ + sexp := a3 + ] ifFalse: [ + sexp := a2 + ]. + continue value "TCO" + ]. + + a0_ = #quote ifTrue: [ + a1 := ast second. + ^a1 + ]. + + a0_ = #quasiquote ifTrue: [ + | result | + a1 := ast second. + sexp := self quasiquote: a1. + continue value "TCO" + ]. + + a0_ = #'fn*' ifTrue: [ + | binds env_ fn | + a1_ := ast second value. + binds := a1_ collect: [ :item | item value ]. + a2 := ast third. + fn := [ :args | + self EVAL: a2 env: + (Env new: env binds: binds exprs: args) ]. + ^Func new: a2 params: binds env: env fn: fn + ] + ]. + + forms := (self evalAst: sexp env: env) value. + function := forms first. + args := forms allButFirst asArray. + + function class = BlockClosure ifTrue: [ ^function value: args ]. + function class = Func ifTrue: [ + | env_ | + sexp := function ast. + env_ := Env new: function env binds: function params + exprs: args. + env := env_. + continue value "TCO" + ] + ] valueWithExit + ] repeat. + ] + + MAL class >> PRINT: sexp [ + ^Printer prStr: sexp printReadably: true + ] + + MAL class >> rep: input env: env [ + ^self PRINT: (self EVAL: (self READ: input) env: env) + ] +] + +| input historyFile replEnv argv | + +historyFile := '.mal_history'. +ReadLine readHistory: historyFile. +replEnv := Env new: nil. + +argv := Smalltalk arguments. +argv notEmpty ifTrue: [ argv := argv allButFirst ]. +argv := OrderedCollection from: (argv collect: [ :arg | MALString new: arg ]). + +Core Ns keysAndValuesDo: [ :op :block | replEnv set: op value: block ]. +replEnv set: #eval value: [ :args | MAL EVAL: args first env: replEnv ]. +replEnv set: #'*ARGV*' value: (MALList new: argv). + +MAL rep: '(def! not (fn* (a) (if a false true)))' env: replEnv. +MAL rep: '(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))' env: replEnv. + +Smalltalk arguments notEmpty ifTrue: [ + MAL rep: '(load-file "', Smalltalk arguments first, '")' env: replEnv +] ifFalse: [ + [ input := ReadLine readLine: 'user> '. input isNil ] whileFalse: [ + input isEmpty ifFalse: [ + ReadLine addHistory: input. + ReadLine writeHistory: historyFile. + [ (MAL rep: input env: replEnv) displayNl ] + on: MALEmptyInput do: [ #return ] + on: MALError do: + [ :err | ('error: ', err messageText) displayNl. #return ]. + ] + ]. + + '' displayNl. +] diff --git a/gst/types.st b/gst/types.st index 4938b1b60f..e40638d8e2 100644 --- a/gst/types.st +++ b/gst/types.st @@ -33,6 +33,11 @@ Object subclass: MALObject [ ^object ] + isPair [ + ^(self type = #list or: [ self type = #vector ]) and: + [ self value notEmpty ] + ] + printOn: stream [ stream nextPutAll: '<'; nextPutAll: self class printString; From cfb42291cf51742c51fc1af1ea1df43c87f26fee Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sat, 8 Jul 2017 18:41:39 +0200 Subject: [PATCH 0362/2308] Elm step A: fixed Env ref counting, fixed unwinding of Env on error. --- elm/Env.elm | 109 ++++++++++++++++++++++++------------------- elm/Eval.elm | 18 +++++++ elm/step6_file.elm | 6 +-- elm/step7_quote.elm | 10 ++-- elm/step8_macros.elm | 10 ++-- elm/step9_try.elm | 14 ++---- elm/stepA_mal.elm | 14 ++---- 7 files changed, 97 insertions(+), 84 deletions(-) diff --git a/elm/Env.elm b/elm/Env.elm index 658ee1a553..5f083a8742 100644 --- a/elm/Env.elm +++ b/elm/Env.elm @@ -47,6 +47,16 @@ global = } +getFrame : Int -> Env -> Frame +getFrame frameId env = + case Dict.get frameId env.frames of + Just frame -> + frame + + Nothing -> + Debug.crash <| "frame #" ++ (toString frameId) ++ " not found" + + jump : Int -> Env -> Env jump frameId env = { env | currentFrameId = frameId } @@ -73,24 +83,19 @@ pop env = let frameId = env.currentFrameId - in - case Dict.get frameId env.frames of - Just currentFrame -> - case currentFrame.outerId of - Just outerId -> - { env - | currentFrameId = outerId - , frames = Dict.update frameId deref env.frames - } - _ -> - Debug.crash "tried to pop global frame" + frame = + getFrame frameId env + in + case frame.outerId of + Just outerId -> + { env + | currentFrameId = outerId + , frames = Dict.update frameId free env.frames + } - Nothing -> - Debug.crash <| - "current frame " - ++ (toString frameId) - ++ " doesn't exist" + _ -> + Debug.crash "tried to pop global frame" setBinds : List ( String, MalExpr ) -> Frame -> Frame @@ -128,29 +133,39 @@ leave orgFrameId env = in { env | currentFrameId = orgFrameId - , frames = Dict.update frameId deref env.frames + , frames = Dict.update frameId free env.frames } -{-| Increase refCnt for the current frame +{-| Increase refCnt for the current frame, +and all it's parent frames. -} ref : Env -> Env ref env = let - incRef = - Maybe.map - (\frame -> + go frameId env = + let + frame = + getFrame frameId env + + newFrame = { frame | refCnt = frame.refCnt + 1 } - ) - newFrames = - Dict.update env.currentFrameId incRef env.frames + newEnv = + { env | frames = Dict.insert frameId newFrame env.frames } + in + case frame.outerId of + Just outerId -> + go outerId newEnv + + Nothing -> + newEnv in - { env | frames = newFrames } + go env.currentFrameId env -deref : Maybe Frame -> Maybe Frame -deref = +free : Maybe Frame -> Maybe Frame +free = Maybe.andThen (\frame -> if frame.refCnt == 1 then @@ -162,6 +177,9 @@ deref = {-| Given an Env see which frames are not reachable from the global frame. Return a new Env without the unreachable frames. + +TODO include current expression. + -} gc : Env -> Env gc env = @@ -230,9 +248,12 @@ emptyFrame outerId = } -setInFrame : Int -> String -> MalExpr -> Env -> Env -setInFrame frameId name expr env = +set : String -> MalExpr -> Env -> Env +set name expr env = let + frameId = + env.currentFrameId + updateFrame = Maybe.map (\frame -> @@ -245,28 +266,22 @@ setInFrame frameId name expr env = { env | frames = newFrames } -set : String -> MalExpr -> Env -> Env -set name expr env = - setInFrame env.currentFrameId name expr env - - get : String -> Env -> Result String MalExpr get name env = let go frameId = - case Dict.get frameId env.frames of - Nothing -> - Err <| "frame " ++ (toString frameId) ++ " not found" - - Just frame -> - case Dict.get name frame.data of - Just value -> - Ok value - - Nothing -> - frame.outerId - |> Maybe.map go - |> Maybe.withDefault (Err <| "'" ++ name ++ "' not found") + let + frame = + getFrame frameId env + in + case Dict.get name frame.data of + Just value -> + Ok value + + Nothing -> + frame.outerId + |> Maybe.map go + |> Maybe.withDefault (Err <| "'" ++ name ++ "' not found") in go env.currentFrameId diff --git a/elm/Eval.elm b/elm/Eval.elm index ddead03182..cba0b41cde 100644 --- a/elm/Eval.elm +++ b/elm/Eval.elm @@ -53,6 +53,9 @@ map f e env = ( env, EvalIO cmd (cont >> map f) ) +{-| Chain two Eval's together. The function f takes the result from +the left eval and generates a new Eval. +-} andThen : (a -> Eval b) -> Eval a -> Eval b andThen f e env = case apply e env of @@ -66,6 +69,21 @@ andThen f e env = ( env, EvalIO cmd (cont >> andThen f) ) +{-| Apply a transformation to the Env, for a Ok and a Err. +-} +finally : (Env -> Env) -> Eval a -> Eval a +finally f e env = + case apply e env of + ( env, EvalOk res ) -> + ( f env, EvalOk res ) + + ( env, EvalErr msg ) -> + ( f env, EvalErr msg ) + + ( env, EvalIO cmd cont ) -> + ( env, EvalIO cmd (cont >> finally f) ) + + catchError : (MalExpr -> Eval a) -> Eval a -> Eval a catchError f e env = case apply e env of diff --git a/elm/step6_file.elm b/elm/step6_file.elm index 5c7013d357..1577b90e33 100644 --- a/elm/step6_file.elm +++ b/elm/step6_file.elm @@ -250,11 +250,7 @@ malEval args = (\env -> Eval.modifyEnv (Env.jump Env.globalFrameId) |> Eval.andThen (\_ -> eval expr) - |> Eval.andThen - (\res -> - Eval.modifyEnv (Env.jump env.currentFrameId) - |> Eval.andThen (\_ -> Eval.succeed res) - ) + |> Eval.finally (Env.jump env.currentFrameId) ) _ -> diff --git a/elm/step7_quote.elm b/elm/step7_quote.elm index 330c2ffdfe..c10724df3a 100644 --- a/elm/step7_quote.elm +++ b/elm/step7_quote.elm @@ -250,11 +250,7 @@ malEval args = (\env -> Eval.modifyEnv (Env.jump Env.globalFrameId) |> Eval.andThen (\_ -> eval expr) - |> Eval.andThen - (\res -> - Eval.modifyEnv (Env.jump env.currentFrameId) - |> Eval.andThen (\_ -> Eval.succeed res) - ) + |> Eval.finally (Env.jump env.currentFrameId) ) _ -> @@ -267,7 +263,7 @@ evalApply { frameId, bound, body } = (\env -> Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.ignore (Eval.modifyEnv (Env.leave env.currentFrameId)) + |> Eval.finally (Env.leave env.currentFrameId) ) @@ -419,7 +415,7 @@ evalLet args = Eval.modifyEnv Env.push |> Eval.andThen (\_ -> evalBinds binds) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.ignore (Eval.modifyEnv Env.pop) + |> Eval.finally Env.pop in case args of [ MalList binds, body ] -> diff --git a/elm/step8_macros.elm b/elm/step8_macros.elm index 9ca040b6ad..f3c8cb8e80 100644 --- a/elm/step8_macros.elm +++ b/elm/step8_macros.elm @@ -266,11 +266,7 @@ malEval args = (\env -> Eval.modifyEnv (Env.jump Env.globalFrameId) |> Eval.andThen (\_ -> eval expr) - |> Eval.andThen - (\res -> - Eval.modifyEnv (Env.jump env.currentFrameId) - |> Eval.andThen (\_ -> Eval.succeed res) - ) + |> Eval.finally (Env.jump env.currentFrameId) ) _ -> @@ -283,7 +279,7 @@ evalApply { frameId, bound, body } = (\env -> Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.ignore (Eval.modifyEnv (Env.leave env.currentFrameId)) + |> Eval.finally (Env.leave env.currentFrameId) ) @@ -474,7 +470,7 @@ evalLet args = Eval.modifyEnv Env.push |> Eval.andThen (\_ -> evalBinds binds) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.ignore (Eval.modifyEnv Env.pop) + |> Eval.finally Env.pop in case args of [ MalList binds, body ] -> diff --git a/elm/step9_try.elm b/elm/step9_try.elm index 699843757b..b67d35429b 100644 --- a/elm/step9_try.elm +++ b/elm/step9_try.elm @@ -266,11 +266,7 @@ malEval args = (\env -> Eval.modifyEnv (Env.jump Env.globalFrameId) |> Eval.andThen (\_ -> eval expr) - |> Eval.andThen - (\res -> - Eval.modifyEnv (Env.jump env.currentFrameId) - |> Eval.andThen (\_ -> Eval.succeed res) - ) + |> Eval.finally (Env.jump env.currentFrameId) ) _ -> @@ -283,7 +279,7 @@ evalApply { frameId, bound, body } = (\env -> Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.ignore (Eval.modifyEnv (Env.leave env.currentFrameId)) + |> Eval.finally (Env.leave env.currentFrameId) ) @@ -476,7 +472,7 @@ evalLet args = Eval.modifyEnv Env.push |> Eval.andThen (\_ -> evalBinds binds) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.ignore (Eval.modifyEnv Env.pop) + |> Eval.finally Env.pop in case args of [ MalList binds, body ] -> @@ -719,8 +715,8 @@ evalTry args = (\_ -> Eval.modifyEnv (Env.set sym ex) ) - |> Eval.andThen (\_ -> evalNoApply handler) - |> Eval.ignore (Eval.modifyEnv Env.pop) + |> Eval.andThen (\_ -> eval handler) + |> Eval.finally Env.pop ) _ -> diff --git a/elm/stepA_mal.elm b/elm/stepA_mal.elm index 4186852057..5f8675f3b4 100644 --- a/elm/stepA_mal.elm +++ b/elm/stepA_mal.elm @@ -276,11 +276,7 @@ malEval args = (\env -> Eval.modifyEnv (Env.jump Env.globalFrameId) |> Eval.andThen (\_ -> eval expr) - |> Eval.andThen - (\res -> - Eval.modifyEnv (Env.jump env.currentFrameId) - |> Eval.andThen (\_ -> Eval.succeed res) - ) + |> Eval.finally (Env.jump env.currentFrameId) ) _ -> @@ -293,7 +289,7 @@ evalApply { frameId, bound, body } = (\env -> Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.ignore (Eval.modifyEnv (Env.leave env.currentFrameId)) + |> Eval.finally (Env.leave env.currentFrameId) ) @@ -486,7 +482,7 @@ evalLet args = Eval.modifyEnv Env.push |> Eval.andThen (\_ -> evalBinds binds) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.ignore (Eval.modifyEnv Env.pop) + |> Eval.finally Env.pop in case args of [ MalList binds, body ] -> @@ -729,8 +725,8 @@ evalTry args = (\_ -> Eval.modifyEnv (Env.set sym ex) ) - |> Eval.andThen (\_ -> evalNoApply handler) - |> Eval.ignore (Eval.modifyEnv Env.pop) + |> Eval.andThen (\_ -> eval handler) + |> Eval.finally Env.pop ) _ -> From ae4600c7a5a0d0317dfdf3f74915547c14eede70 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 8 Jul 2017 19:49:09 +0200 Subject: [PATCH 0363/2308] Implement step 8 --- gst/core.st | 26 ++++ gst/func.st | 8 +- gst/step8_macros.st | 285 ++++++++++++++++++++++++++++++++++++++++++++ gst/types.st | 6 + 4 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 gst/step8_macros.st diff --git a/gst/core.st b/gst/core.st index a59f774d26..13708bde3b 100644 --- a/gst/core.st +++ b/gst/core.st @@ -90,3 +90,29 @@ Core Ns at: #cons put: Core Ns at: #concat put: [ :args | MALList new: (OrderedCollection join: (args collect: [ :arg | arg value ])) ]. +Core Ns at: #nth put: + [ :args | + | items index | + items := args first value. + index := args second value + 1. + items at: index ifAbsent: [ MALOutOfBounds new signal ] + ]. +Core Ns at: #first put: + [ :args | + args first type = #nil ifTrue: [ + MALObject Nil + ] ifFalse: [ + args first value at: 1 ifAbsent: [ MALObject Nil ]. + ] + ]. +Core Ns at: #rest put: + [ :args | + | items rest | + items := args first value. + (args first type = #nil or: [ items isEmpty ]) ifTrue: [ + rest := {} + ] ifFalse: [ + rest := items allButFirst + ]. + MALList new: (OrderedCollection from: rest) + ]. diff --git a/gst/func.st b/gst/func.st index ccff241e90..76979d258a 100644 --- a/gst/func.st +++ b/gst/func.st @@ -1,12 +1,17 @@ Object subclass: Func [ - | ast params env fn meta | + | ast params env fn isMacro meta | ast [ ^ast ] params [ ^params ] env [ ^env ] fn [ ^fn ] + isMacro [ ^isMacro ] meta [ ^meta ] + isMacro: bool [ + isMacro := bool + ] + meta: aMeta [ meta := aMeta ] @@ -23,6 +28,7 @@ Object subclass: Func [ params := someParams. env := anEnv. fn := aFn. + isMacro := false ] withMeta: meta [ diff --git a/gst/step8_macros.st b/gst/step8_macros.st new file mode 100644 index 0000000000..74b9aa45a6 --- /dev/null +++ b/gst/step8_macros.st @@ -0,0 +1,285 @@ +FileStream fileIn: 'readline.st'. +FileStream fileIn: 'reader.st'. +FileStream fileIn: 'printer.st'. +FileStream fileIn: 'env.st'. +FileStream fileIn: 'func.st'. +FileStream fileIn: 'core.st'. + +Object subclass: MAL [ + MAL class >> READ: input [ + ^Reader readStr: input + ] + + MAL class >> evalAst: sexp env: env [ + sexp class = BlockClosure ifTrue: [^sexp ]. + + sexp type = #symbol ifTrue: [ + ^env get: sexp value + ]. + + sexp type = #list ifTrue: [ + ^self evalList: sexp env: env class: MALList + ]. + sexp type = #vector ifTrue: [ + ^self evalList: sexp env: env class: MALVector + ]. + sexp type = #map ifTrue: [ + ^self evalList: sexp env: env class: MALMap + ]. + + ^sexp + ] + + MAL class >> evalList: sexp env: env class: aClass [ + | items | + items := sexp value collect: + [ :item | self EVAL: item env: env ]. + ^aClass new: items + ] + + MAL class >> quasiquote: ast [ + | result a a0 a0_ a0_0 a0_1 rest | + ast isPair ifFalse: [ + result := {MALSymbol new: #quote. ast}. + ^MALList new: (OrderedCollection from: result) + ]. + + a := ast value. + a0 := a first. + + (a0 type = #symbol and: [ a0 value = #unquote ]) ifTrue: [ ^a second ]. + + a0 isPair ifTrue: [ + a0_ := a0 value. + a0_0 := a0_ first. + a0_1 := a0_ second. + + (a0_0 type = #symbol and: + [ a0_0 value = #'splice-unquote' ]) ifTrue: [ + rest := MALList new: a allButFirst. + result := {MALSymbol new: #concat. a0_1. + self quasiquote: rest}. + ^MALList new: (OrderedCollection from: result) + ] + ]. + + rest := MALList new: a allButFirst. + result := {MALSymbol new: #cons. self quasiquote: a0. + self quasiquote: rest}. + ^MALList new: (OrderedCollection from: result) + ] + + MAL class >> isMacroCall: ast env: env [ + | a0 a0_ f | + ast type = #list ifTrue: [ + a0 := ast value first. + a0_ := a0 value. + a0 type = #symbol ifTrue: [ + f := env find: a0_. + (f notNil and: [ f class ~= BlockClosure ]) ifTrue: [ + ^f isMacro + ] + ] + ]. + ^false + ] + + MAL class >> macroexpand: aSexp env: env [ + | sexp | + + "NOTE: redefinition of method arguments is not allowed" + sexp := aSexp. + + [ self isMacroCall: sexp env: env ] whileTrue: [ + | ast a0_ macro rest | + ast := sexp value. + a0_ := ast first value. + macro := env find: a0_. + rest := ast allButFirst. + sexp := macro fn value: rest. + ]. + + ^sexp + ] + + MAL class >> EVAL: aSexp env: anEnv [ + | sexp env ast a0 a0_ a1 a1_ a2 a3 forms function args | + + "NOTE: redefinition of method arguments is not allowed" + sexp := aSexp. + env := anEnv. + + [ + [ :continue | + (sexp class = BlockClosure or: [ sexp type ~= #list ]) ifTrue: [ + ^self evalAst: sexp env: env + ]. + sexp value isEmpty ifTrue: [ + ^sexp + ]. + + sexp := self macroexpand: sexp env: env. + sexp type ~= #list ifTrue: [ + ^self evalAst: sexp env: env + ]. + + ast := sexp value. + a0 := ast first. + + a0 class ~= BlockClosure ifTrue: [ + a0_ := ast first value. + a0_ = #'def!' ifTrue: [ + | result | + a1_ := ast second value. + a2 := ast third. + result := self EVAL: a2 env: env. + env set: a1_ value: result. + ^result + ]. + + a0_ = #'defmacro!' ifTrue: [ + | result | + a1_ := ast second value. + a2 := ast third. + result := self EVAL: a2 env: env. + result isMacro: true. + env set: a1_ value: result. + ^result + ]. + + a0_ = #'macroexpand' ifTrue: [ + a1 := ast second. + ^self macroexpand: a1 env: env + ]. + + a0_ = #'let*' ifTrue: [ + | env_ | + env_ := Env new: env. + a1_ := ast second value. + a2 := ast third. + 1 to: a1_ size by: 2 do: + [ :i | env_ set: (a1_ at: i) value + value: (self EVAL: (a1_ at: i + 1) + env: env_) ]. + env := env_. + sexp := a2. + continue value "TCO" + ]. + + a0_ = #do ifTrue: [ + | forms last | + ast size < 2 ifTrue: [ + forms := {}. + last := MALObject Nil. + ] ifFalse: [ + forms := ast copyFrom: 2 to: ast size - 1. + last := ast last. + ]. + + forms do: [ :form | self EVAL: form env: env ]. + sexp := last. + continue value "TCO" + ]. + + a0_ = #if ifTrue: [ + | condition | + a1 := ast second. + a2 := ast third. + a3 := ast at: 4 ifAbsent: [ MALObject Nil ]. + condition := self EVAL: a1 env: env. + + (condition type = #false or: + [ condition type = #nil ]) ifTrue: [ + sexp := a3 + ] ifFalse: [ + sexp := a2 + ]. + continue value "TCO" + ]. + + a0_ = #quote ifTrue: [ + a1 := ast second. + ^a1 + ]. + + a0_ = #quasiquote ifTrue: [ + | result | + a1 := ast second. + sexp := self quasiquote: a1. + continue value "TCO" + ]. + + a0_ = #'fn*' ifTrue: [ + | binds env_ fn | + a1_ := ast second value. + binds := a1_ collect: [ :item | item value ]. + a2 := ast third. + fn := [ :args | + self EVAL: a2 env: + (Env new: env binds: binds exprs: args) ]. + ^Func new: a2 params: binds env: env fn: fn + ] + ]. + + forms := (self evalAst: sexp env: env) value. + function := forms first. + args := forms allButFirst asArray. + + function class = BlockClosure ifTrue: [ ^function value: args ]. + function class = Func ifTrue: [ + | env_ | + sexp := function ast. + env_ := Env new: function env binds: function params + exprs: args. + env := env_. + continue value "TCO" + ] + ] valueWithExit + ] repeat. + ] + + MAL class >> PRINT: sexp [ + ^Printer prStr: sexp printReadably: true + ] + + MAL class >> rep: input env: env [ + ^self PRINT: (self EVAL: (self READ: input) env: env) + ] +] + +| input historyFile replEnv argv | + +historyFile := '.mal_history'. +ReadLine readHistory: historyFile. +replEnv := Env new: nil. + +argv := Smalltalk arguments. +argv notEmpty ifTrue: [ argv := argv allButFirst ]. +argv := OrderedCollection from: (argv collect: [ :arg | MALString new: arg ]). + +Core Ns keysAndValuesDo: [ :op :block | replEnv set: op value: block ]. +replEnv set: #eval value: [ :args | MAL EVAL: args first env: replEnv ]. +replEnv set: #'*ARGV*' value: (MALList new: argv). + +MAL rep: '(def! not (fn* (a) (if a false true)))' env: replEnv. +MAL rep: '(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))' env: replEnv. + +MAL rep: '(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list ''if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons ''cond (rest (rest xs)))))))' env: replEnv. +MAL rep: '(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))' env: replEnv. + +Smalltalk arguments notEmpty ifTrue: [ + MAL rep: '(load-file "', Smalltalk arguments first, '")' env: replEnv +] ifFalse: [ + [ input := ReadLine readLine: 'user> '. input isNil ] whileFalse: [ + input isEmpty ifFalse: [ + ReadLine addHistory: input. + ReadLine writeHistory: historyFile. + [ (MAL rep: input env: replEnv) displayNl ] + on: MALEmptyInput do: [ #return ] + on: MALError do: + [ :err | ('error: ', err messageText) displayNl. #return ]. + ] + ]. + + '' displayNl. +] diff --git a/gst/types.st b/gst/types.st index e40638d8e2..b986887594 100644 --- a/gst/types.st +++ b/gst/types.st @@ -174,3 +174,9 @@ MALError subclass: MALUnknownSymbol [ messageText [ ^'''', self basicMessageText, ''' not found'] ] + +MALError subclass: MALOutOfBounds [ + MALOutOfBounds class >> new [ ^super new ] + + messageText [ ^'Out of bounds' ] +] From 58e44bbb33fd210071656a44488db01dc5c3e135 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 8 Jul 2017 22:55:08 +0200 Subject: [PATCH 0364/2308] Monkey-patch BlockClosure for simpler code --- gst/step6_file.st | 124 ++++++++++++++++---------------- gst/step7_quote.st | 144 ++++++++++++++++++------------------- gst/step8_macros.st | 172 ++++++++++++++++++++++---------------------- gst/util.st | 4 ++ 4 files changed, 218 insertions(+), 226 deletions(-) diff --git a/gst/step6_file.st b/gst/step6_file.st index 8040d66f80..8315042c9b 100644 --- a/gst/step6_file.st +++ b/gst/step6_file.st @@ -11,8 +11,6 @@ Object subclass: MAL [ ] MAL class >> evalAst: sexp env: env [ - sexp class = BlockClosure ifTrue: [^sexp ]. - sexp type = #symbol ifTrue: [ ^env get: sexp value ]. @@ -46,7 +44,7 @@ Object subclass: MAL [ [ [ :continue | - (sexp class = BlockClosure or: [ sexp type ~= #list ]) ifTrue: [ + sexp type ~= #list ifTrue: [ ^self evalAst: sexp env: env ]. sexp value isEmpty ifTrue: [ @@ -56,72 +54,70 @@ Object subclass: MAL [ ast := sexp value. a0 := ast first. - a0 class ~= BlockClosure ifTrue: [ - a0_ := ast first value. - a0_ = #'def!' ifTrue: [ - | result | - a1_ := ast second value. - a2 := ast third. - result := self EVAL: a2 env: env. - env set: a1_ value: result. - ^result - ]. + a0_ := ast first value. + a0_ = #'def!' ifTrue: [ + | result | + a1_ := ast second value. + a2 := ast third. + result := self EVAL: a2 env: env. + env set: a1_ value: result. + ^result + ]. - a0_ = #'let*' ifTrue: [ - | env_ | - env_ := Env new: env. - a1_ := ast second value. - a2 := ast third. - 1 to: a1_ size by: 2 do: - [ :i | env_ set: (a1_ at: i) value - value: (self EVAL: (a1_ at: i + 1) - env: env_) ]. - env := env_. - sexp := a2. - continue value "TCO" - ]. + a0_ = #'let*' ifTrue: [ + | env_ | + env_ := Env new: env. + a1_ := ast second value. + a2 := ast third. + 1 to: a1_ size by: 2 do: + [ :i | env_ set: (a1_ at: i) value + value: (self EVAL: (a1_ at: i + 1) + env: env_) ]. + env := env_. + sexp := a2. + continue value "TCO" + ]. - a0_ = #do ifTrue: [ - | forms last | - ast size < 2 ifTrue: [ - forms := {}. - last := MALObject Nil. - ] ifFalse: [ - forms := ast copyFrom: 2 to: ast size - 1. - last := ast last. - ]. - - forms do: [ :form | self EVAL: form env: env ]. - sexp := last. - continue value "TCO" + a0_ = #do ifTrue: [ + | forms last | + ast size < 2 ifTrue: [ + forms := {}. + last := MALObject Nil. + ] ifFalse: [ + forms := ast copyFrom: 2 to: ast size - 1. + last := ast last. ]. - a0_ = #if ifTrue: [ - | condition | - a1 := ast second. - a2 := ast third. - a3 := ast at: 4 ifAbsent: [ MALObject Nil ]. - condition := self EVAL: a1 env: env. - - (condition type = #false or: - [ condition type = #nil ]) ifTrue: [ - sexp := a3 - ] ifFalse: [ - sexp := a2 - ]. - continue value "TCO" - ]. + forms do: [ :form | self EVAL: form env: env ]. + sexp := last. + continue value "TCO" + ]. + + a0_ = #if ifTrue: [ + | condition | + a1 := ast second. + a2 := ast third. + a3 := ast at: 4 ifAbsent: [ MALObject Nil ]. + condition := self EVAL: a1 env: env. + + (condition type = #false or: + [ condition type = #nil ]) ifTrue: [ + sexp := a3 + ] ifFalse: [ + sexp := a2 + ]. + continue value "TCO" + ]. - a0_ = #'fn*' ifTrue: [ - | binds env_ fn | - a1_ := ast second value. - binds := a1_ collect: [ :item | item value ]. - a2 := ast third. - fn := [ :args | - self EVAL: a2 env: - (Env new: env binds: binds exprs: args) ]. - ^Func new: a2 params: binds env: env fn: fn - ] + a0_ = #'fn*' ifTrue: [ + | binds env_ fn | + a1_ := ast second value. + binds := a1_ collect: [ :item | item value ]. + a2 := ast third. + fn := [ :args | + self EVAL: a2 env: + (Env new: env binds: binds exprs: args) ]. + ^Func new: a2 params: binds env: env fn: fn ]. forms := (self evalAst: sexp env: env) value. diff --git a/gst/step7_quote.st b/gst/step7_quote.st index 6ca5aabac0..b1aa1f8db1 100644 --- a/gst/step7_quote.st +++ b/gst/step7_quote.st @@ -11,8 +11,6 @@ Object subclass: MAL [ ] MAL class >> evalAst: sexp env: env [ - sexp class = BlockClosure ifTrue: [^sexp ]. - sexp type = #symbol ifTrue: [ ^env get: sexp value ]. @@ -78,7 +76,7 @@ Object subclass: MAL [ [ [ :continue | - (sexp class = BlockClosure or: [ sexp type ~= #list ]) ifTrue: [ + sexp type ~= #list ifTrue: [ ^self evalAst: sexp env: env ]. sexp value isEmpty ifTrue: [ @@ -88,84 +86,82 @@ Object subclass: MAL [ ast := sexp value. a0 := ast first. - a0 class ~= BlockClosure ifTrue: [ - a0_ := ast first value. - a0_ = #'def!' ifTrue: [ - | result | - a1_ := ast second value. - a2 := ast third. - result := self EVAL: a2 env: env. - env set: a1_ value: result. - ^result - ]. + a0_ := ast first value. + a0_ = #'def!' ifTrue: [ + | result | + a1_ := ast second value. + a2 := ast third. + result := self EVAL: a2 env: env. + env set: a1_ value: result. + ^result + ]. - a0_ = #'let*' ifTrue: [ - | env_ | - env_ := Env new: env. - a1_ := ast second value. - a2 := ast third. - 1 to: a1_ size by: 2 do: - [ :i | env_ set: (a1_ at: i) value - value: (self EVAL: (a1_ at: i + 1) - env: env_) ]. - env := env_. - sexp := a2. - continue value "TCO" - ]. + a0_ = #'let*' ifTrue: [ + | env_ | + env_ := Env new: env. + a1_ := ast second value. + a2 := ast third. + 1 to: a1_ size by: 2 do: + [ :i | env_ set: (a1_ at: i) value + value: (self EVAL: (a1_ at: i + 1) + env: env_) ]. + env := env_. + sexp := a2. + continue value "TCO" + ]. - a0_ = #do ifTrue: [ - | forms last | - ast size < 2 ifTrue: [ - forms := {}. - last := MALObject Nil. - ] ifFalse: [ - forms := ast copyFrom: 2 to: ast size - 1. - last := ast last. - ]. - - forms do: [ :form | self EVAL: form env: env ]. - sexp := last. - continue value "TCO" + a0_ = #do ifTrue: [ + | forms last | + ast size < 2 ifTrue: [ + forms := {}. + last := MALObject Nil. + ] ifFalse: [ + forms := ast copyFrom: 2 to: ast size - 1. + last := ast last. ]. - a0_ = #if ifTrue: [ - | condition | - a1 := ast second. - a2 := ast third. - a3 := ast at: 4 ifAbsent: [ MALObject Nil ]. - condition := self EVAL: a1 env: env. - - (condition type = #false or: - [ condition type = #nil ]) ifTrue: [ - sexp := a3 - ] ifFalse: [ - sexp := a2 - ]. - continue value "TCO" - ]. + forms do: [ :form | self EVAL: form env: env ]. + sexp := last. + continue value "TCO" + ]. - a0_ = #quote ifTrue: [ - a1 := ast second. - ^a1 - ]. + a0_ = #if ifTrue: [ + | condition | + a1 := ast second. + a2 := ast third. + a3 := ast at: 4 ifAbsent: [ MALObject Nil ]. + condition := self EVAL: a1 env: env. + + (condition type = #false or: + [ condition type = #nil ]) ifTrue: [ + sexp := a3 + ] ifFalse: [ + sexp := a2 + ]. + continue value "TCO" + ]. - a0_ = #quasiquote ifTrue: [ - | result | - a1 := ast second. - sexp := self quasiquote: a1. - continue value "TCO" - ]. + a0_ = #quote ifTrue: [ + a1 := ast second. + ^a1 + ]. - a0_ = #'fn*' ifTrue: [ - | binds env_ fn | - a1_ := ast second value. - binds := a1_ collect: [ :item | item value ]. - a2 := ast third. - fn := [ :args | - self EVAL: a2 env: - (Env new: env binds: binds exprs: args) ]. - ^Func new: a2 params: binds env: env fn: fn - ] + a0_ = #quasiquote ifTrue: [ + | result | + a1 := ast second. + sexp := self quasiquote: a1. + continue value "TCO" + ]. + + a0_ = #'fn*' ifTrue: [ + | binds env_ fn | + a1_ := ast second value. + binds := a1_ collect: [ :item | item value ]. + a2 := ast third. + fn := [ :args | + self EVAL: a2 env: + (Env new: env binds: binds exprs: args) ]. + ^Func new: a2 params: binds env: env fn: fn ]. forms := (self evalAst: sexp env: env) value. diff --git a/gst/step8_macros.st b/gst/step8_macros.st index 74b9aa45a6..76f612f26f 100644 --- a/gst/step8_macros.st +++ b/gst/step8_macros.st @@ -11,8 +11,6 @@ Object subclass: MAL [ ] MAL class >> evalAst: sexp env: env [ - sexp class = BlockClosure ifTrue: [^sexp ]. - sexp type = #symbol ifTrue: [ ^env get: sexp value ]. @@ -76,7 +74,7 @@ Object subclass: MAL [ a0_ := a0 value. a0 type = #symbol ifTrue: [ f := env find: a0_. - (f notNil and: [ f class ~= BlockClosure ]) ifTrue: [ + (f notNil and: [ f class = Func ]) ifTrue: [ ^f isMacro ] ] @@ -111,7 +109,7 @@ Object subclass: MAL [ [ [ :continue | - (sexp class = BlockClosure or: [ sexp type ~= #list ]) ifTrue: [ + sexp type ~= #list ifTrue: [ ^self evalAst: sexp env: env ]. sexp value isEmpty ifTrue: [ @@ -126,99 +124,97 @@ Object subclass: MAL [ ast := sexp value. a0 := ast first. - a0 class ~= BlockClosure ifTrue: [ - a0_ := ast first value. - a0_ = #'def!' ifTrue: [ - | result | - a1_ := ast second value. - a2 := ast third. - result := self EVAL: a2 env: env. - env set: a1_ value: result. - ^result - ]. + a0_ := ast first value. + a0_ = #'def!' ifTrue: [ + | result | + a1_ := ast second value. + a2 := ast third. + result := self EVAL: a2 env: env. + env set: a1_ value: result. + ^result + ]. - a0_ = #'defmacro!' ifTrue: [ - | result | - a1_ := ast second value. - a2 := ast third. - result := self EVAL: a2 env: env. - result isMacro: true. - env set: a1_ value: result. - ^result - ]. + a0_ = #'defmacro!' ifTrue: [ + | result | + a1_ := ast second value. + a2 := ast third. + result := self EVAL: a2 env: env. + result isMacro: true. + env set: a1_ value: result. + ^result + ]. - a0_ = #'macroexpand' ifTrue: [ - a1 := ast second. - ^self macroexpand: a1 env: env - ]. + a0_ = #'macroexpand' ifTrue: [ + a1 := ast second. + ^self macroexpand: a1 env: env + ]. - a0_ = #'let*' ifTrue: [ - | env_ | - env_ := Env new: env. - a1_ := ast second value. - a2 := ast third. - 1 to: a1_ size by: 2 do: - [ :i | env_ set: (a1_ at: i) value - value: (self EVAL: (a1_ at: i + 1) - env: env_) ]. - env := env_. - sexp := a2. - continue value "TCO" - ]. + a0_ = #'let*' ifTrue: [ + | env_ | + env_ := Env new: env. + a1_ := ast second value. + a2 := ast third. + 1 to: a1_ size by: 2 do: + [ :i | env_ set: (a1_ at: i) value + value: (self EVAL: (a1_ at: i + 1) + env: env_) ]. + env := env_. + sexp := a2. + continue value "TCO" + ]. - a0_ = #do ifTrue: [ - | forms last | - ast size < 2 ifTrue: [ - forms := {}. - last := MALObject Nil. - ] ifFalse: [ - forms := ast copyFrom: 2 to: ast size - 1. - last := ast last. - ]. - - forms do: [ :form | self EVAL: form env: env ]. - sexp := last. - continue value "TCO" + a0_ = #do ifTrue: [ + | forms last | + ast size < 2 ifTrue: [ + forms := {}. + last := MALObject Nil. + ] ifFalse: [ + forms := ast copyFrom: 2 to: ast size - 1. + last := ast last. ]. - a0_ = #if ifTrue: [ - | condition | - a1 := ast second. - a2 := ast third. - a3 := ast at: 4 ifAbsent: [ MALObject Nil ]. - condition := self EVAL: a1 env: env. - - (condition type = #false or: - [ condition type = #nil ]) ifTrue: [ - sexp := a3 - ] ifFalse: [ - sexp := a2 - ]. - continue value "TCO" - ]. + forms do: [ :form | self EVAL: form env: env ]. + sexp := last. + continue value "TCO" + ]. - a0_ = #quote ifTrue: [ - a1 := ast second. - ^a1 - ]. + a0_ = #if ifTrue: [ + | condition | + a1 := ast second. + a2 := ast third. + a3 := ast at: 4 ifAbsent: [ MALObject Nil ]. + condition := self EVAL: a1 env: env. + + (condition type = #false or: + [ condition type = #nil ]) ifTrue: [ + sexp := a3 + ] ifFalse: [ + sexp := a2 + ]. + continue value "TCO" + ]. - a0_ = #quasiquote ifTrue: [ - | result | - a1 := ast second. - sexp := self quasiquote: a1. - continue value "TCO" - ]. + a0_ = #quote ifTrue: [ + a1 := ast second. + ^a1 + ]. - a0_ = #'fn*' ifTrue: [ - | binds env_ fn | - a1_ := ast second value. - binds := a1_ collect: [ :item | item value ]. - a2 := ast third. - fn := [ :args | - self EVAL: a2 env: - (Env new: env binds: binds exprs: args) ]. - ^Func new: a2 params: binds env: env fn: fn - ] + a0_ = #quasiquote ifTrue: [ + | result | + a1 := ast second. + sexp := self quasiquote: a1. + continue value "TCO" + ]. + + a0_ = #'fn*' ifTrue: [ + | binds env_ fn | + a1_ := ast second value. + binds := a1_ collect: [ :item | item value ]. + a2 := ast third. + fn := [ :args | + self EVAL: a2 env: + (Env new: env binds: binds exprs: args) ]. + ^Func new: a2 params: binds env: env fn: fn ]. forms := (self evalAst: sexp env: env) value. diff --git a/gst/util.st b/gst/util.st index 4a93866f23..021caedf9b 100644 --- a/gst/util.st +++ b/gst/util.st @@ -33,4 +33,8 @@ BlockClosure extend [ valueWithExit [ ^self value: [ ^nil ] ] + + "HACK" + type [ ^#closure ] + value [ ^nil ] ] From d38ab263fe3884146e53f928a626868d9f6e8351 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 9 Jul 2017 10:51:56 +0200 Subject: [PATCH 0365/2308] Implement step 9 --- gst/core.st | 98 ++++++++++++++-- gst/printer.st | 2 +- gst/step9_try.st | 299 +++++++++++++++++++++++++++++++++++++++++++++++ gst/types.st | 9 ++ gst/util.st | 2 +- 5 files changed, 400 insertions(+), 10 deletions(-) create mode 100644 gst/step9_try.st diff --git a/gst/core.st b/gst/core.st index 13708bde3b..62db39cd5a 100644 --- a/gst/core.st +++ b/gst/core.st @@ -10,6 +10,14 @@ Object subclass: Core [ block value ifTrue: [ ^MALObject True ] ifFalse: [ ^MALObject False ] ] + Core class >> nilable: args else: block [ + args first type = #nil ifTrue: [ + ^MALObject Nil + ] ifFalse: [ + ^block value + ] + ] + Core class >> printedArgs: args readable: readable sep: sep [ | items | items := args collect: @@ -63,6 +71,8 @@ Core Ns at: #'read-string' put: [ :args | Reader readStr: args first value ]. Core Ns at: #slurp put: [ :args | MALString new: (File path: args first value) contents ]. +Core Ns at: #throw put: + [ :args | MALCustomError new signal: args first ]. Core Ns at: #atom put: [ :args | MALAtom new: args first ]. @@ -98,21 +108,93 @@ Core Ns at: #nth put: items at: index ifAbsent: [ MALOutOfBounds new signal ] ]. Core Ns at: #first put: - [ :args | - args first type = #nil ifTrue: [ - MALObject Nil - ] ifFalse: [ - args first value at: 1 ifAbsent: [ MALObject Nil ]. - ] - ]. + [ :args | Core nilable: args else: [ + args first value at: 1 ifAbsent: [ MALObject Nil ] ] ]. Core Ns at: #rest put: [ :args | | items rest | items := args first value. - (args first type = #nil or: [ items isEmpty ]) ifTrue: [ + (args first type = #nil or: [ items isEmpty ]) ifTrue: [ rest := {} ] ifFalse: [ rest := items allButFirst ]. MALList new: (OrderedCollection from: rest) ]. + +Core Ns at: #apply put: + [ :args | + | f rest result | + f := args first. + f class = Func ifTrue: [ f := f fn ]. + args size < 3 ifTrue: [ + rest := {} + ] ifFalse: [ + rest := args copyFrom: 2 to: args size - 1 + ]. + rest := rest, args last value. + f value: rest + ]. +Core Ns at: #map put: + [ :args | + | items f result | + f := args first. + f class = Func ifTrue: [ f := f fn ]. + items := args second value. + result := items collect: [ :item | f value: {item} ]. + MALList new: (OrderedCollection from: result) + ]. + +Core Ns at: #'nil?' put: + [ :args | Core coerce: [ args first type = #nil ] ]. +Core Ns at: #'true?' put: + [ :args | Core coerce: [ args first type = #true ] ]. +Core Ns at: #'false?' put: + [ :args | Core coerce: [ args first type = #false ] ]. +Core Ns at: #'symbol?' put: + [ :args | Core coerce: [ args first type = #symbol ] ]. +Core Ns at: #'keyword?' put: + [ :args | Core coerce: [ args first type = #keyword ] ]. +Core Ns at: #'vector?' put: + [ :args | Core coerce: [ args first type = #vector ] ]. +Core Ns at: #'map?' put: + [ :args | Core coerce: [ args first type = #map ] ]. +Core Ns at: #'sequential?' put: + [ :args | Core coerce: [ args first type = #list or: + [ args first type = #vector ] ] ]. + +Core Ns at: #symbol put: + [ :args | MALSymbol new: args first value asSymbol ]. +Core Ns at: #keyword put: + [ :args | MALKeyword new: args first value asSymbol ]. +Core Ns at: #vector put: + [ :args | MALVector new: (OrderedCollection from: args) ]. +Core Ns at: #'hash-map' put: + [ :args | MALMap new: args asDictionary ]. + +Core Ns at: #assoc put: + [ :args | + | result keyVals | + result := Dictionary from: args first value associations. + keyVals := args allButFirst. + 1 to: keyVals size by: 2 do: + [ :i | result add: (keyVals at: i) -> (keyVals at: i + 1) ]. + MALMap new: result + ]. +Core Ns at: #dissoc put: + [ :args | + | result keys | + result := Dictionary from: args first value associations. + keys := args allButFirst. + keys do: [ :key | result removeKey: key ifAbsent: [ nil ] ]. + MALMap new: result + ]. +Core Ns at: #get put: + [ :args | Core nilable: args else: + [ args first value at: args second ifAbsent: [ MALObject Nil ] ] ]. +Core Ns at: #'contains?' put: + [ :args | Core coerce: [ args first value includesKey: args second ] ]. +Core Ns at: #keys put: + [ :args | MALList new: (OrderedCollection from: args first value keys) ]. +Core Ns at: #vals put: + [ :args | MALList new: (OrderedCollection from: args first value values) ]. diff --git a/gst/printer.st b/gst/printer.st index 7079a49a28..4bf44d41ea 100644 --- a/gst/printer.st +++ b/gst/printer.st @@ -53,6 +53,6 @@ Object subclass: Printer [ [ :item | (self prStr: item key printReadably: printReadably), ' ', (self prStr: item value printReadably: printReadably) ]. - ^'{', (items join: ', '), '}' + ^'{', (items join: ' '), '}' ] ] diff --git a/gst/step9_try.st b/gst/step9_try.st new file mode 100644 index 0000000000..7b5f65552b --- /dev/null +++ b/gst/step9_try.st @@ -0,0 +1,299 @@ +FileStream fileIn: 'readline.st'. +FileStream fileIn: 'reader.st'. +FileStream fileIn: 'printer.st'. +FileStream fileIn: 'env.st'. +FileStream fileIn: 'func.st'. +FileStream fileIn: 'core.st'. + +Object subclass: MAL [ + MAL class >> READ: input [ + ^Reader readStr: input + ] + + MAL class >> evalAst: sexp env: env [ + sexp type = #symbol ifTrue: [ + ^env get: sexp value + ]. + + sexp type = #list ifTrue: [ + ^self evalList: sexp env: env class: MALList + ]. + sexp type = #vector ifTrue: [ + ^self evalList: sexp env: env class: MALVector + ]. + sexp type = #map ifTrue: [ + ^self evalList: sexp env: env class: MALMap + ]. + + ^sexp + ] + + MAL class >> evalList: sexp env: env class: aClass [ + | items | + items := sexp value collect: + [ :item | self EVAL: item env: env ]. + ^aClass new: items + ] + + MAL class >> quasiquote: ast [ + | result a a0 a0_ a0_0 a0_1 rest | + ast isPair ifFalse: [ + result := {MALSymbol new: #quote. ast}. + ^MALList new: (OrderedCollection from: result) + ]. + + a := ast value. + a0 := a first. + + (a0 type = #symbol and: [ a0 value = #unquote ]) ifTrue: [ ^a second ]. + + a0 isPair ifTrue: [ + a0_ := a0 value. + a0_0 := a0_ first. + a0_1 := a0_ second. + + (a0_0 type = #symbol and: + [ a0_0 value = #'splice-unquote' ]) ifTrue: [ + rest := MALList new: a allButFirst. + result := {MALSymbol new: #concat. a0_1. + self quasiquote: rest}. + ^MALList new: (OrderedCollection from: result) + ] + ]. + + rest := MALList new: a allButFirst. + result := {MALSymbol new: #cons. self quasiquote: a0. + self quasiquote: rest}. + ^MALList new: (OrderedCollection from: result) + ] + + MAL class >> isMacroCall: ast env: env [ + | a0 a0_ f | + ast type = #list ifTrue: [ + a0 := ast value first. + a0_ := a0 value. + a0 type = #symbol ifTrue: [ + f := env find: a0_. + (f notNil and: [ f class = Func ]) ifTrue: [ + ^f isMacro + ] + ] + ]. + ^false + ] + + MAL class >> macroexpand: aSexp env: env [ + | sexp | + + "NOTE: redefinition of method arguments is not allowed" + sexp := aSexp. + + [ self isMacroCall: sexp env: env ] whileTrue: [ + | ast a0_ macro rest | + ast := sexp value. + a0_ := ast first value. + macro := env find: a0_. + rest := ast allButFirst. + sexp := macro fn value: rest. + ]. + + ^sexp + ] + + MAL class >> EVAL: aSexp env: anEnv [ + | sexp env ast a0 a0_ a1 a1_ a2 a2_ a3 forms function args | + + "NOTE: redefinition of method arguments is not allowed" + sexp := aSexp. + env := anEnv. + + [ + [ :continue | + sexp type ~= #list ifTrue: [ + ^self evalAst: sexp env: env + ]. + sexp value isEmpty ifTrue: [ + ^sexp + ]. + + sexp := self macroexpand: sexp env: env. + sexp type ~= #list ifTrue: [ + ^self evalAst: sexp env: env + ]. + + ast := sexp value. + a0 := ast first. + + a0_ := ast first value. + a0_ = #'def!' ifTrue: [ + | result | + a1_ := ast second value. + a2 := ast third. + result := self EVAL: a2 env: env. + env set: a1_ value: result. + ^result + ]. + + a0_ = #'defmacro!' ifTrue: [ + | result | + a1_ := ast second value. + a2 := ast third. + result := self EVAL: a2 env: env. + result isMacro: true. + env set: a1_ value: result. + ^result + ]. + + a0_ = #'macroexpand' ifTrue: [ + a1 := ast second. + ^self macroexpand: a1 env: env + ]. + + a0_ = #'let*' ifTrue: [ + | env_ | + env_ := Env new: env. + a1_ := ast second value. + a2 := ast third. + 1 to: a1_ size by: 2 do: + [ :i | env_ set: (a1_ at: i) value + value: (self EVAL: (a1_ at: i + 1) + env: env_) ]. + env := env_. + sexp := a2. + continue value "TCO" + ]. + + a0_ = #do ifTrue: [ + | forms last | + ast size < 2 ifTrue: [ + forms := {}. + last := MALObject Nil. + ] ifFalse: [ + forms := ast copyFrom: 2 to: ast size - 1. + last := ast last. + ]. + + forms do: [ :form | self EVAL: form env: env ]. + sexp := last. + continue value "TCO" + ]. + + a0_ = #if ifTrue: [ + | condition | + a1 := ast second. + a2 := ast third. + a3 := ast at: 4 ifAbsent: [ MALObject Nil ]. + condition := self EVAL: a1 env: env. + + (condition type = #false or: + [ condition type = #nil ]) ifTrue: [ + sexp := a3 + ] ifFalse: [ + sexp := a2 + ]. + continue value "TCO" + ]. + + a0_ = #quote ifTrue: [ + a1 := ast second. + ^a1 + ]. + + a0_ = #quasiquote ifTrue: [ + | result | + a1 := ast second. + sexp := self quasiquote: a1. + continue value "TCO" + ]. + + a0_ = #'try*' ifTrue: [ + | A B C | + A := ast second. + a2_ := ast third value. + B := a2_ second value. + C := a2_ third. + ^[ self EVAL: A env: env ] on: MALError do: + [ :err | + | data env_ result | + data := err data. + data isString ifTrue: [ + data := MALString new: data + ]. + env_ := Env new: env binds: {B} exprs: {data}. + err return: (self EVAL: C env: env_) + ] + ]. + + a0_ = #'fn*' ifTrue: [ + | binds env_ fn | + a1_ := ast second value. + binds := a1_ collect: [ :item | item value ]. + a2 := ast third. + fn := [ :args | + self EVAL: a2 env: + (Env new: env binds: binds exprs: args) ]. + ^Func new: a2 params: binds env: env fn: fn + ]. + + forms := (self evalAst: sexp env: env) value. + function := forms first. + args := forms allButFirst asArray. + + function class = BlockClosure ifTrue: [ ^function value: args ]. + function class = Func ifTrue: [ + | env_ | + sexp := function ast. + env_ := Env new: function env binds: function params + exprs: args. + env := env_. + continue value "TCO" + ] + ] valueWithExit + ] repeat. + ] + + MAL class >> PRINT: sexp [ + ^Printer prStr: sexp printReadably: true + ] + + MAL class >> rep: input env: env [ + ^self PRINT: (self EVAL: (self READ: input) env: env) + ] +] + +| input historyFile replEnv argv | + +historyFile := '.mal_history'. +ReadLine readHistory: historyFile. +replEnv := Env new: nil. + +argv := Smalltalk arguments. +argv notEmpty ifTrue: [ argv := argv allButFirst ]. +argv := OrderedCollection from: (argv collect: [ :arg | MALString new: arg ]). + +Core Ns keysAndValuesDo: [ :op :block | replEnv set: op value: block ]. +replEnv set: #eval value: [ :args | MAL EVAL: args first env: replEnv ]. +replEnv set: #'*ARGV*' value: (MALList new: argv). + +MAL rep: '(def! not (fn* (a) (if a false true)))' env: replEnv. +MAL rep: '(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))' env: replEnv. + +MAL rep: '(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list ''if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons ''cond (rest (rest xs)))))))' env: replEnv. +MAL rep: '(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))' env: replEnv. + +Smalltalk arguments notEmpty ifTrue: [ + MAL rep: '(load-file "', Smalltalk arguments first, '")' env: replEnv +] ifFalse: [ + [ input := ReadLine readLine: 'user> '. input isNil ] whileFalse: [ + input isEmpty ifFalse: [ + ReadLine addHistory: input. + ReadLine writeHistory: historyFile. + [ (MAL rep: input env: replEnv) displayNl ] + on: MALEmptyInput do: [ #return ] + on: MALError do: + [ :err | ('error: ', err messageText) displayNl. #return ]. + ] + ]. + + '' displayNl. +] diff --git a/gst/types.st b/gst/types.st index b986887594..9f3b032a1c 100644 --- a/gst/types.st +++ b/gst/types.st @@ -149,6 +149,8 @@ MALObject subclass: MALAtom [ Error subclass: MALError [ description [ ^'A MAL-related error' ] isResumable [ ^true ] + + data [ ^self messageText ] ] MALError subclass: MALUnterminatedSequence [ @@ -180,3 +182,10 @@ MALError subclass: MALOutOfBounds [ messageText [ ^'Out of bounds' ] ] + +MALError subclass: MALCustomError [ + MALCustomError class >> new [ ^super new ] + + messageText [ ^Printer prStr: self basicMessageText printReadably: true ] + data [ ^self basicMessageText ] +] diff --git a/gst/util.st b/gst/util.st index 021caedf9b..03a226d09e 100644 --- a/gst/util.st +++ b/gst/util.st @@ -1,4 +1,4 @@ -OrderedCollection extend [ +SequenceableCollection extend [ asDictionary [ | dict assoc | dict := Dictionary new. From 43d8bb4e2ea28a59fb4bb546199dd2fe3b947fc9 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sun, 9 Jul 2017 12:34:58 +0200 Subject: [PATCH 0366/2308] Elm step A: implemented GC. MAL tests are failing.. --- elm/Core.elm | 11 +- elm/Env.elm | 262 ++++++++++++++++++++++++----------------- elm/Eval.elm | 34 +++--- elm/Types.elm | 2 + elm/step4_if_fn_do.elm | 7 +- elm/step5_tco.elm | 9 +- elm/step6_file.elm | 9 +- elm/step7_quote.elm | 3 +- elm/step8_macros.elm | 3 +- elm/step9_try.elm | 3 +- elm/stepA_mal.elm | 4 +- 11 files changed, 197 insertions(+), 150 deletions(-) diff --git a/elm/Core.elm b/elm/Core.elm index 02dc946853..95b9adf9c3 100644 --- a/elm/Core.elm +++ b/elm/Core.elm @@ -305,7 +305,7 @@ ns = Eval.fail "unsupported arguments" gc args = - Eval.withEnv (Env.gc >> Printer.printEnv >> writeLine) + Eval.withEnv (Env.gc MalNil >> Printer.printEnv >> writeLine) setDebug enabled = Eval.modifyEnv @@ -316,11 +316,14 @@ ns = debug args = case args of - [ MalBool True ] -> - setDebug True + [ MalBool value ] -> + setDebug value _ -> - setDebug False + Eval.withEnv + (\env -> + Eval.succeed (MalBool env.debug) + ) typeof args = case args of diff --git a/elm/Env.elm b/elm/Env.elm index 5f083a8742..867df02eae 100644 --- a/elm/Env.elm +++ b/elm/Env.elm @@ -36,6 +36,11 @@ globalFrameId = 0 +defaultGcInterval : Int +defaultGcInterval = + 3 + + global : Env global = { frames = Dict.singleton globalFrameId (emptyFrame Nothing) @@ -44,6 +49,8 @@ global = , atoms = Dict.empty , nextAtomId = 0 , debug = False + , gcInterval = defaultGcInterval + , gcCounter = 0 } @@ -57,6 +64,84 @@ getFrame frameId env = Debug.crash <| "frame #" ++ (toString frameId) ++ " not found" +emptyFrame : Maybe Int -> Frame +emptyFrame outerId = + { outerId = outerId + , data = Dict.empty + , refCnt = 1 + } + + +set : String -> MalExpr -> Env -> Env +set name expr env = + let + frameId = + env.currentFrameId + + updateFrame = + Maybe.map + (\frame -> + { frame | data = Dict.insert name expr frame.data } + ) + + newFrames = + Dict.update frameId updateFrame env.frames + in + { env | frames = newFrames } + + +get : String -> Env -> Result String MalExpr +get name env = + let + go frameId = + let + frame = + getFrame frameId env + in + case Dict.get name frame.data of + Just value -> + Ok value + + Nothing -> + frame.outerId + |> Maybe.map go + |> Maybe.withDefault (Err <| "'" ++ name ++ "' not found") + in + go env.currentFrameId + + +newAtom : MalExpr -> Env -> ( Env, Int ) +newAtom value env = + let + atomId = + env.nextAtomId + + newEnv = + { env + | atoms = Dict.insert atomId value env.atoms + , nextAtomId = atomId + 1 + } + in + ( newEnv, atomId ) + + +getAtom : Int -> Env -> MalExpr +getAtom atomId env = + case Dict.get atomId env.atoms of + Just value -> + value + + Nothing -> + Debug.crash <| "atom " ++ (toString atomId) ++ " not found" + + +setAtom : Int -> MalExpr -> Env -> Env +setAtom atomId value env = + { env + | atoms = Dict.insert atomId value env.atoms + } + + jump : Int -> Env -> Env jump frameId env = { env | currentFrameId = frameId } @@ -160,8 +245,11 @@ ref env = Nothing -> newEnv + + newEnv = + go env.currentFrameId env in - go env.currentFrameId env + { newEnv | gcCounter = newEnv.gcCounter + 1 } free : Maybe Frame -> Maybe Frame @@ -176,143 +264,99 @@ free = {-| Given an Env see which frames are not reachable from the -global frame. Return a new Env without the unreachable frames. +global frame, or from the current expression. -TODO include current expression. +Return a new Env with the unreachable frames removed. -} -gc : Env -> Env -gc env = +gc : MalExpr -> Env -> Env +gc currentExpr env = let countList acc = List.foldl countRefs acc - countFrame acc { data } = + countFrame { data } acc = data |> Dict.values |> countList acc - countRefs expr acc = - debug env ("gc-visit " ++ (toString expr)) <| - case expr of - MalFunction (UserFunc { frameId }) -> - if not (Set.member frameId acc) then - debug env "gc-counting" <| - case Dict.get frameId env.frames of - Just frame -> - countFrame (Set.insert frameId acc) frame - - Nothing -> - Debug.crash ("frame " ++ (toString frameId) ++ " not found in GC") - else - acc - - MalList list -> - countList acc list - - MalVector vec -> - countList acc (Array.toList vec) - - MalMap map -> - countList acc (Dict.values map) - - _ -> - acc - - initSet = - Set.fromList [ globalFrameId, env.currentFrameId ] + recur frameId acc = + if not (Set.member frameId acc) then + let + frame = + getFrame frameId env - reportUnused frames used = - Dict.diff frames used - |> debug env "unused frames" - |> (\_ -> frames) - in - case Dict.get globalFrameId env.frames of - Nothing -> - Debug.crash "global frame not found" + newAcc = + (Set.insert frameId acc) + in + countFrame frame newAcc + else + acc - Just globalFrame -> - countFrame initSet globalFrame - |> Set.toList - |> debug env "used frames" - |> List.map (\frameId -> ( frameId, emptyFrame Nothing )) - |> Dict.fromList - |> reportUnused env.frames - |> Dict.intersect env.frames - |> (\frames -> { env | frames = frames }) + countRefs expr acc = + case expr of + MalFunction (UserFunc { frameId }) -> + recur frameId acc + MalApply { frameId } -> + recur frameId acc -emptyFrame : Maybe Int -> Frame -emptyFrame outerId = - { outerId = outerId - , data = Dict.empty - , refCnt = 1 - } + MalList list -> + countList acc list + MalVector vec -> + countList acc (Array.toList vec) -set : String -> MalExpr -> Env -> Env -set name expr env = - let - frameId = - env.currentFrameId + MalMap map -> + countList acc (Dict.values map) - updateFrame = - Maybe.map - (\frame -> - { frame | data = Dict.insert name expr frame.data } - ) + MalAtom atomId -> + let + value = + getAtom atomId env + in + countRefs value acc - newFrames = - Dict.update frameId updateFrame env.frames - in - { env | frames = newFrames } + _ -> + acc + initSet = + Set.fromList [ globalFrameId, env.currentFrameId ] -get : String -> Env -> Result String MalExpr -get name env = - let - go frameId = + expandParents frameId acc = let frame = getFrame frameId env in - case Dict.get name frame.data of - Just value -> - Ok value + case frame.outerId of + Just parentId -> + Set.insert parentId acc Nothing -> - frame.outerId - |> Maybe.map go - |> Maybe.withDefault (Err <| "'" ++ name ++ "' not found") - in - go env.currentFrameId + acc + expandAllFrames frames = + Set.foldl expandParents frames frames -newAtom : MalExpr -> Env -> ( Env, Int ) -newAtom value env = - let - atomId = - env.nextAtomId + makeEmptyFrame frameId = + ( frameId, emptyFrame Nothing ) - newEnv = + globalFrame = + getFrame globalFrameId env + + makeNewEnv newFrames = { env - | atoms = Dict.insert atomId value env.atoms - , nextAtomId = atomId + 1 + | frames = newFrames + , gcCounter = 0 } - in - ( newEnv, atomId ) + keepFilter keep frameId _ = + Set.member frameId keep -getAtom : Int -> Env -> MalExpr -getAtom atomId env = - case Dict.get atomId env.atoms of - Just value -> - value - - Nothing -> - Debug.crash <| "atom " ++ (toString atomId) ++ " not found" - - -setAtom : Int -> MalExpr -> Env -> Env -setAtom atomId value env = - { env - | atoms = Dict.insert atomId value env.atoms - } + filterFrames frames keep = + Dict.filter (keepFilter keep) frames + in + initSet + |> countRefs currentExpr + |> countFrame globalFrame + |> expandAllFrames + |> filterFrames env.frames + |> makeNewEnv diff --git a/elm/Eval.elm b/elm/Eval.elm index cba0b41cde..4bd38a19ed 100644 --- a/elm/Eval.elm +++ b/elm/Eval.elm @@ -84,6 +84,26 @@ finally f e env = ( env, EvalIO cmd (cont >> finally f) ) +gcPass : Eval MalExpr -> Eval MalExpr +gcPass e env = + let + go env t expr = + if env.gcCounter >= env.gcInterval then + ( Env.gc expr env, t expr ) + else + ( env, t expr ) + in + case apply e env of + ( env, EvalOk res ) -> + go env EvalOk res + + ( env, EvalErr msg ) -> + go env EvalErr msg + + ( env, EvalIO cmd cont ) -> + ( env, EvalIO cmd (cont >> gcPass) ) + + catchError : (MalExpr -> Eval a) -> Eval a -> Eval a catchError f e env = case apply e env of @@ -107,20 +127,6 @@ throw ex env = ( env, EvalErr ex ) -enter : Int -> List ( String, MalExpr ) -> Eval a -> Eval a -enter frameId bound body = - withEnv - (\env -> - modifyEnv (Env.enter frameId bound) - |> andThen (always body) - |> andThen - (\res -> - modifyEnv (Env.leave env.currentFrameId) - |> map (always res) - ) - ) - - {-| Apply f to expr repeatedly. Continues iterating if f returns (Left eval). Stops if f returns (Right expr). diff --git a/elm/Types.elm b/elm/Types.elm index 2fb55ec910..e978f638c4 100644 --- a/elm/Types.elm +++ b/elm/Types.elm @@ -28,6 +28,8 @@ type alias Env = , atoms : Dict Int MalExpr , nextAtomId : Int , debug : Bool + , gcInterval : Int + , gcCounter : Int } diff --git a/elm/step4_if_fn_do.elm b/elm/step4_if_fn_do.elm index e524426b44..ed1af853b4 100644 --- a/elm/step4_if_fn_do.elm +++ b/elm/step4_if_fn_do.elm @@ -423,7 +423,12 @@ evalFn args = fn args = case binder args of Ok bound -> - Eval.enter frameId bound (eval body) + Eval.withEnv + (\env -> + Eval.modifyEnv (Env.enter frameId bound) + |> Eval.andThen (always (eval body)) + |> Eval.finally (Env.leave env.currentFrameId) + ) Err msg -> Eval.fail msg diff --git a/elm/step5_tco.elm b/elm/step5_tco.elm index 7938325eae..d00791ef4f 100644 --- a/elm/step5_tco.elm +++ b/elm/step5_tco.elm @@ -188,11 +188,8 @@ evalApply { frameId, bound, body } = (\env -> Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.andThen - (\res -> - Eval.modifyEnv (Env.leave env.currentFrameId) - |> Eval.map (\_ -> res) - ) + |> Eval.finally (Env.leave env.currentFrameId) + |> Eval.gcPass ) @@ -466,8 +463,6 @@ evalFn args = case binder args of Ok bound -> Eval.succeed <| - -- TODO : choice Env.enter prematurely? - -- I think it is needed by the garbage collect.. MalApply { frameId = frameId , bound = bound diff --git a/elm/step6_file.elm b/elm/step6_file.elm index 1577b90e33..93d392888c 100644 --- a/elm/step6_file.elm +++ b/elm/step6_file.elm @@ -263,11 +263,8 @@ evalApply { frameId, bound, body } = (\env -> Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.andThen - (\res -> - Eval.modifyEnv (Env.leave env.currentFrameId) - |> Eval.map (\_ -> res) - ) + |> Eval.finally (Env.leave env.currentFrameId) + |> Eval.gcPass ) @@ -541,8 +538,6 @@ evalFn args = case binder args of Ok bound -> Eval.succeed <| - -- TODO : choice Env.enter prematurely? - -- I think it is needed by the garbage collect.. MalApply { frameId = frameId , bound = bound diff --git a/elm/step7_quote.elm b/elm/step7_quote.elm index c10724df3a..5cc67c1f53 100644 --- a/elm/step7_quote.elm +++ b/elm/step7_quote.elm @@ -264,6 +264,7 @@ evalApply { frameId, bound, body } = Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) |> Eval.finally (Env.leave env.currentFrameId) + |> Eval.gcPass ) @@ -538,8 +539,6 @@ evalFn args = >> Eval.fromResult >> Eval.map (\bound -> - -- TODO : choice Env.enter prematurely? - -- I think it is needed by the garbage collect.. MalApply { frameId = frameId , bound = bound diff --git a/elm/step8_macros.elm b/elm/step8_macros.elm index f3c8cb8e80..476b5da2db 100644 --- a/elm/step8_macros.elm +++ b/elm/step8_macros.elm @@ -280,6 +280,7 @@ evalApply { frameId, bound, body } = Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) |> Eval.finally (Env.leave env.currentFrameId) + |> Eval.gcPass ) @@ -593,8 +594,6 @@ evalFn args = >> Eval.fromResult >> Eval.map (\bound -> - -- TODO : choice Env.enter prematurely? - -- I think it is needed by the garbage collect.. MalApply { frameId = frameId , bound = bound diff --git a/elm/step9_try.elm b/elm/step9_try.elm index b67d35429b..20963eb2b6 100644 --- a/elm/step9_try.elm +++ b/elm/step9_try.elm @@ -280,6 +280,7 @@ evalApply { frameId, bound, body } = Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) |> Eval.finally (Env.leave env.currentFrameId) + |> Eval.gcPass ) @@ -595,8 +596,6 @@ evalFn args = >> Eval.fromResult >> Eval.map (\bound -> - -- TODO : choice Env.enter prematurely? - -- I think it is needed by the garbage collect.. MalApply { frameId = frameId , bound = bound diff --git a/elm/stepA_mal.elm b/elm/stepA_mal.elm index 5f8675f3b4..b6f60bd579 100644 --- a/elm/stepA_mal.elm +++ b/elm/stepA_mal.elm @@ -266,6 +266,7 @@ eval ast = in evalNoApply ast |> Eval.andThen (Eval.runLoop apply) + |> Eval.gcPass malEval : List MalExpr -> Eval MalExpr @@ -290,6 +291,7 @@ evalApply { frameId, bound, body } = Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) |> Eval.finally (Env.leave env.currentFrameId) + |> Eval.gcPass ) @@ -605,8 +607,6 @@ evalFn args = >> Eval.fromResult >> Eval.map (\bound -> - -- TODO : choice Env.enter prematurely? - -- I think it is needed by the garbage collect.. MalApply { frameId = frameId , bound = bound From aee373f32ec26c654896c401d5cab401761ed8d7 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 9 Jul 2017 20:05:59 +0200 Subject: [PATCH 0367/2308] Wrap BlockClosure into Fn to work around a bug Extending a BlockClosure by a meta field gives a segfault for whatever reason. Yes, seriously. --- gst/core.st | 147 ++++++++++++++++++++---------------------- gst/func.st | 18 +----- gst/printer.st | 4 +- gst/step4_if_fn_do.st | 4 +- gst/step5_tco.st | 4 +- gst/step6_file.st | 6 +- gst/step7_quote.st | 6 +- gst/step8_macros.st | 8 +-- gst/step9_try.st | 8 +-- gst/types.st | 17 +++++ 10 files changed, 111 insertions(+), 111 deletions(-) diff --git a/gst/core.st b/gst/core.st index 62db39cd5a..a5a87e6397 100644 --- a/gst/core.st +++ b/gst/core.st @@ -28,90 +28,92 @@ Object subclass: Core [ ] Core Ns at: #+ put: - [ :args | MALNumber new: args first value + args second value ]. + (Fn new: [ :args | MALNumber new: args first value + args second value ]). Core Ns at: #- put: - [ :args | MALNumber new: args first value - args second value ]. + (Fn new: [ :args | MALNumber new: args first value - args second value ]). Core Ns at: #* put: - [ :args | MALNumber new: args first value * args second value ]. + (Fn new: [ :args | MALNumber new: args first value * args second value ]). Core Ns at: #/ put: - [ :args | MALNumber new: args first value // args second value ]. + (Fn new: [ :args | MALNumber new: args first value // args second value ]). Core Ns at: #'pr-str' put: - [ :args | MALString new: (Core printedArgs: args readable: true sep: ' ') ]. + (Fn new: [ :args | MALString new: (Core printedArgs: args readable: true + sep: ' ') ]). Core Ns at: #str put: - [ :args | MALString new: (Core printedArgs: args readable: false sep: '') ]. + (Fn new: [ :args | MALString new: (Core printedArgs: args readable: false + sep: '') ]). Core Ns at: #prn put: - [ :args | (Core printedArgs: args readable: true sep: ' ') displayNl. - MALObject Nil ]. + (Fn new: [ :args | + (Core printedArgs: args readable: true sep: ' ') displayNl. + MALObject Nil ]). Core Ns at: #println put: - [ :args | (Core printedArgs: args readable: false sep: ' ') displayNl. - MALObject Nil ]. + (Fn new: [ :args | + (Core printedArgs: args readable: false sep: ' ') displayNl. + MALObject Nil ]). -Core Ns at: #list put: [ :args | MALList new: (OrderedCollection from: args) ]. +Core Ns at: #list put: + (Fn new: [ :args | MALList new: (OrderedCollection from: args) ]). Core Ns at: #'list?' put: - [ :args | Core coerce: [ args first type = #list ] ]. + (Fn new: [ :args | Core coerce: [ args first type = #list ] ]). Core Ns at: #'empty?' put: - [ :args | Core coerce: [ args first value isEmpty ] ]. + (Fn new: [ :args | Core coerce: [ args first value isEmpty ] ]). Core Ns at: #count put: - [ :args | MALNumber new: args first value size ]. + (Fn new: [ :args | MALNumber new: args first value size ]). Core Ns at: #= put: - [ :args | Core coerce: [ args first = args second ] ]. + (Fn new: [ :args | Core coerce: [ args first = args second ] ]). Core Ns at: #< put: - [ :args | Core coerce: [ args first value < args second value ] ]. + (Fn new: [ :args | Core coerce: [ args first value < args second value ] ]). Core Ns at: #<= put: - [ :args | Core coerce: [ args first value <= args second value ] ]. + (Fn new: [ :args | Core coerce: [ args first value <= args second value ] ]). Core Ns at: #> put: - [ :args | Core coerce: [ args first value > args second value ] ]. + (Fn new: [ :args | Core coerce: [ args first value > args second value ] ]). Core Ns at: #>= put: - [ :args | Core coerce: [ args first value >= args second value ] ]. + (Fn new: [ :args | Core coerce: [ args first value >= args second value ] ]). Core Ns at: #'read-string' put: - [ :args | Reader readStr: args first value ]. + (Fn new: [ :args | Reader readStr: args first value ]). Core Ns at: #slurp put: - [ :args | MALString new: (File path: args first value) contents ]. + (Fn new: [ :args | MALString new: (File path: args first value) contents ]). Core Ns at: #throw put: - [ :args | MALCustomError new signal: args first ]. + (Fn new: [ :args | MALCustomError new signal: args first ]). Core Ns at: #atom put: - [ :args | MALAtom new: args first ]. + (Fn new: [ :args | MALAtom new: args first ]). Core Ns at: #'atom?' put: - [ :args | Core coerce: [ args first type = #atom ] ]. + (Fn new: [ :args | Core coerce: [ args first type = #atom ] ]). Core Ns at: #deref put: - [ :args | args first value ]. + (Fn new: [ :args | args first value ]). Core Ns at: #'reset!' put: - [ :args | args first value: args second. args second ]. + (Fn new: [ :args | args first value: args second. args second ]). Core Ns at: #'swap!' put: - [ :args | + (Fn new: [ :args | | a f x xs result | a := args first. - f := args second. - f class = Func ifTrue: [ f := f fn ]. + f := args second fn. x := a value. xs := args allButFirst: 2. result := f value: (xs copyWithFirst: x). a value: result. - result - ]. + result ]). Core Ns at: #cons put: - [ :args | MALList new: (args second value copyWithFirst: args first) ]. + (Fn new: [ :args | MALList new: (args second value copyWithFirst: args first) ]). Core Ns at: #concat put: - [ :args | MALList new: (OrderedCollection join: - (args collect: [ :arg | arg value ])) ]. + (Fn new: [ :args | MALList new: (OrderedCollection join: + (args collect: [ :arg | arg value ])) ]). Core Ns at: #nth put: - [ :args | + (Fn new: [ :args | | items index | items := args first value. index := args second value + 1. - items at: index ifAbsent: [ MALOutOfBounds new signal ] - ]. + items at: index ifAbsent: [ MALOutOfBounds new signal ] ]). Core Ns at: #first put: - [ :args | Core nilable: args else: [ - args first value at: 1 ifAbsent: [ MALObject Nil ] ] ]. + (Fn new: [ :args | Core nilable: args else: [ + args first value at: 1 ifAbsent: [ MALObject Nil ] ] ]). Core Ns at: #rest put: - [ :args | + (Fn new: [ :args | | items rest | items := args first value. (args first type = #nil or: [ items isEmpty ]) ifTrue: [ @@ -119,82 +121,75 @@ Core Ns at: #rest put: ] ifFalse: [ rest := items allButFirst ]. - MALList new: (OrderedCollection from: rest) - ]. + MALList new: (OrderedCollection from: rest) ]). Core Ns at: #apply put: - [ :args | + (Fn new: [ :args | | f rest result | - f := args first. - f class = Func ifTrue: [ f := f fn ]. + f := args first fn. args size < 3 ifTrue: [ rest := {} ] ifFalse: [ rest := args copyFrom: 2 to: args size - 1 ]. rest := rest, args last value. - f value: rest - ]. + f value: rest ]). Core Ns at: #map put: - [ :args | + (Fn new: [ :args | | items f result | - f := args first. - f class = Func ifTrue: [ f := f fn ]. + f := args first fn. items := args second value. result := items collect: [ :item | f value: {item} ]. - MALList new: (OrderedCollection from: result) - ]. + MALList new: (OrderedCollection from: result) ]). Core Ns at: #'nil?' put: - [ :args | Core coerce: [ args first type = #nil ] ]. + (Fn new: [ :args | Core coerce: [ args first type = #nil ] ]). Core Ns at: #'true?' put: - [ :args | Core coerce: [ args first type = #true ] ]. + (Fn new: [ :args | Core coerce: [ args first type = #true ] ]). Core Ns at: #'false?' put: - [ :args | Core coerce: [ args first type = #false ] ]. + (Fn new: [ :args | Core coerce: [ args first type = #false ] ]). Core Ns at: #'symbol?' put: - [ :args | Core coerce: [ args first type = #symbol ] ]. + (Fn new: [ :args | Core coerce: [ args first type = #symbol ] ]). Core Ns at: #'keyword?' put: - [ :args | Core coerce: [ args first type = #keyword ] ]. + (Fn new: [ :args | Core coerce: [ args first type = #keyword ] ]). Core Ns at: #'vector?' put: - [ :args | Core coerce: [ args first type = #vector ] ]. + (Fn new: [ :args | Core coerce: [ args first type = #vector ] ]). Core Ns at: #'map?' put: - [ :args | Core coerce: [ args first type = #map ] ]. + (Fn new: [ :args | Core coerce: [ args first type = #map ] ]). Core Ns at: #'sequential?' put: - [ :args | Core coerce: [ args first type = #list or: - [ args first type = #vector ] ] ]. + (Fn new: [ :args | Core coerce: [ args first type = #list or: + [ args first type = #vector ] ] ]). Core Ns at: #symbol put: - [ :args | MALSymbol new: args first value asSymbol ]. + (Fn new: [ :args | MALSymbol new: args first value asSymbol ]). Core Ns at: #keyword put: - [ :args | MALKeyword new: args first value asSymbol ]. + (Fn new: [ :args | MALKeyword new: args first value asSymbol ]). Core Ns at: #vector put: - [ :args | MALVector new: (OrderedCollection from: args) ]. + (Fn new: [ :args | MALVector new: (OrderedCollection from: args) ]). Core Ns at: #'hash-map' put: - [ :args | MALMap new: args asDictionary ]. + (Fn new: [ :args | MALMap new: args asDictionary ]). Core Ns at: #assoc put: - [ :args | + (Fn new: [ :args | | result keyVals | result := Dictionary from: args first value associations. keyVals := args allButFirst. 1 to: keyVals size by: 2 do: [ :i | result add: (keyVals at: i) -> (keyVals at: i + 1) ]. - MALMap new: result - ]. + MALMap new: result ]). Core Ns at: #dissoc put: - [ :args | + (Fn new: [ :args | | result keys | result := Dictionary from: args first value associations. keys := args allButFirst. keys do: [ :key | result removeKey: key ifAbsent: [ nil ] ]. - MALMap new: result - ]. + MALMap new: result ]). Core Ns at: #get put: - [ :args | Core nilable: args else: - [ args first value at: args second ifAbsent: [ MALObject Nil ] ] ]. + (Fn new: [ :args | Core nilable: args else: + [ args first value at: args second ifAbsent: [ MALObject Nil ] ] ]). Core Ns at: #'contains?' put: - [ :args | Core coerce: [ args first value includesKey: args second ] ]. + (Fn new: [ :args | Core coerce: [ args first value includesKey: args second ] ]). Core Ns at: #keys put: - [ :args | MALList new: (OrderedCollection from: args first value keys) ]. + (Fn new: [ :args | MALList new: (OrderedCollection from: args first value keys) ]). Core Ns at: #vals put: - [ :args | MALList new: (OrderedCollection from: args first value values) ]. + (Fn new: [ :args | MALList new: (OrderedCollection from: args first value values) ]). diff --git a/gst/func.st b/gst/func.st index 76979d258a..dc5e97fe65 100644 --- a/gst/func.st +++ b/gst/func.st @@ -1,24 +1,19 @@ -Object subclass: Func [ - | ast params env fn isMacro meta | +MALObject subclass: Func [ + | ast params env fn isMacro | ast [ ^ast ] params [ ^params ] env [ ^env ] fn [ ^fn ] isMacro [ ^isMacro ] - meta [ ^meta ] isMacro: bool [ isMacro := bool ] - meta: aMeta [ - meta := aMeta - ] - Func class >> new: ast params: params env: env fn: fn [ | func | - func := super new. + func := super new: #func value: fn meta: nil. func init: ast params: params env: env fn: fn. ^func ] @@ -30,11 +25,4 @@ Object subclass: Func [ fn := aFn. isMacro := false ] - - withMeta: meta [ - | func | - func := self deepCopy. - func meta: meta. - ^func - ] ] diff --git a/gst/printer.st b/gst/printer.st index 4bf44d41ea..d84994656f 100644 --- a/gst/printer.st +++ b/gst/printer.st @@ -2,8 +2,8 @@ FileStream fileIn: 'types.st'. Object subclass: Printer [ Printer class >> prStr: sexp printReadably: printReadably [ - sexp class = BlockClosure ifTrue: [ ^'#' ]. - sexp class = Func ifTrue: [ ^'#' ]. + sexp type = #fn ifTrue: [ ^'#' ]. + sexp type = #func ifTrue: [ ^'#' ]. sexp type = #true ifTrue: [ ^'true' ]. sexp type = #false ifTrue: [ ^'false' ]. sexp type = #nil ifTrue: [ ^'nil' ]. diff --git a/gst/step4_if_fn_do.st b/gst/step4_if_fn_do.st index 138b967838..8bb7adb58a 100644 --- a/gst/step4_if_fn_do.st +++ b/gst/step4_if_fn_do.st @@ -89,12 +89,12 @@ Object subclass: MAL [ a1_ := ast second value. binds := a1_ collect: [ :item | item value ]. a2 := ast third. - ^[ :args | self EVAL: a2 env: + ^Fn new: [ :args | self EVAL: a2 env: (Env new: env binds: binds exprs: args) ] ]. forms := (self evalAst: sexp env: env) value. - function := forms first. + function := forms first fn. args := forms allButFirst asArray. ^function value: args ] diff --git a/gst/step5_tco.st b/gst/step5_tco.st index 7ac7f1bbca..9341b86c4e 100644 --- a/gst/step5_tco.st +++ b/gst/step5_tco.st @@ -123,8 +123,8 @@ Object subclass: MAL [ function := forms first. args := forms allButFirst asArray. - function class = BlockClosure ifTrue: [ ^function value: args ]. - function class = Func ifTrue: [ + function type = #fn ifTrue: [ ^function fn value: args ]. + function type = #func ifTrue: [ | env_ | sexp := function ast. env_ := Env new: function env binds: function params diff --git a/gst/step6_file.st b/gst/step6_file.st index 8315042c9b..758cc68c52 100644 --- a/gst/step6_file.st +++ b/gst/step6_file.st @@ -124,8 +124,8 @@ Object subclass: MAL [ function := forms first. args := forms allButFirst asArray. - function class = BlockClosure ifTrue: [ ^function value: args ]. - function class = Func ifTrue: [ + function type = #fn ifTrue: [ ^function fn value: args ]. + function type = #func ifTrue: [ | env_ | sexp := function ast. env_ := Env new: function env binds: function params @@ -157,7 +157,7 @@ argv notEmpty ifTrue: [ argv := argv allButFirst ]. argv := OrderedCollection from: (argv collect: [ :arg | MALString new: arg ]). Core Ns keysAndValuesDo: [ :op :block | replEnv set: op value: block ]. -replEnv set: #eval value: [ :args | MAL EVAL: args first env: replEnv ]. +replEnv set: #eval value: (Fn new: [ :args | MAL EVAL: args first env: replEnv ]). replEnv set: #'*ARGV*' value: (MALList new: argv). MAL rep: '(def! not (fn* (a) (if a false true)))' env: replEnv. diff --git a/gst/step7_quote.st b/gst/step7_quote.st index b1aa1f8db1..faeaf645f9 100644 --- a/gst/step7_quote.st +++ b/gst/step7_quote.st @@ -168,8 +168,8 @@ Object subclass: MAL [ function := forms first. args := forms allButFirst asArray. - function class = BlockClosure ifTrue: [ ^function value: args ]. - function class = Func ifTrue: [ + function type = #fn ifTrue: [ ^function fn value: args ]. + function type = #func ifTrue: [ | env_ | sexp := function ast. env_ := Env new: function env binds: function params @@ -201,7 +201,7 @@ argv notEmpty ifTrue: [ argv := argv allButFirst ]. argv := OrderedCollection from: (argv collect: [ :arg | MALString new: arg ]). Core Ns keysAndValuesDo: [ :op :block | replEnv set: op value: block ]. -replEnv set: #eval value: [ :args | MAL EVAL: args first env: replEnv ]. +replEnv set: #eval value: (Fn new: [ :args | MAL EVAL: args first env: replEnv ]). replEnv set: #'*ARGV*' value: (MALList new: argv). MAL rep: '(def! not (fn* (a) (if a false true)))' env: replEnv. diff --git a/gst/step8_macros.st b/gst/step8_macros.st index 76f612f26f..69c7721ac3 100644 --- a/gst/step8_macros.st +++ b/gst/step8_macros.st @@ -74,7 +74,7 @@ Object subclass: MAL [ a0_ := a0 value. a0 type = #symbol ifTrue: [ f := env find: a0_. - (f notNil and: [ f class = Func ]) ifTrue: [ + (f notNil and: [ f type = #func ]) ifTrue: [ ^f isMacro ] ] @@ -221,8 +221,8 @@ Object subclass: MAL [ function := forms first. args := forms allButFirst asArray. - function class = BlockClosure ifTrue: [ ^function value: args ]. - function class = Func ifTrue: [ + function type = #fn ifTrue: [ ^function fn value: args ]. + function type = #func ifTrue: [ | env_ | sexp := function ast. env_ := Env new: function env binds: function params @@ -254,7 +254,7 @@ argv notEmpty ifTrue: [ argv := argv allButFirst ]. argv := OrderedCollection from: (argv collect: [ :arg | MALString new: arg ]). Core Ns keysAndValuesDo: [ :op :block | replEnv set: op value: block ]. -replEnv set: #eval value: [ :args | MAL EVAL: args first env: replEnv ]. +replEnv set: #eval value: (Fn new: [ :args | MAL EVAL: args first env: replEnv ]). replEnv set: #'*ARGV*' value: (MALList new: argv). MAL rep: '(def! not (fn* (a) (if a false true)))' env: replEnv. diff --git a/gst/step9_try.st b/gst/step9_try.st index 7b5f65552b..bd0e434247 100644 --- a/gst/step9_try.st +++ b/gst/step9_try.st @@ -74,7 +74,7 @@ Object subclass: MAL [ a0_ := a0 value. a0 type = #symbol ifTrue: [ f := env find: a0_. - (f notNil and: [ f class = Func ]) ifTrue: [ + (f notNil and: [ f type = #func ]) ifTrue: [ ^f isMacro ] ] @@ -239,8 +239,8 @@ Object subclass: MAL [ function := forms first. args := forms allButFirst asArray. - function class = BlockClosure ifTrue: [ ^function value: args ]. - function class = Func ifTrue: [ + function type = #fn ifTrue: [ ^function fn value: args ]. + function type = #func ifTrue: [ | env_ | sexp := function ast. env_ := Env new: function env binds: function params @@ -272,7 +272,7 @@ argv notEmpty ifTrue: [ argv := argv allButFirst ]. argv := OrderedCollection from: (argv collect: [ :arg | MALString new: arg ]). Core Ns keysAndValuesDo: [ :op :block | replEnv set: op value: block ]. -replEnv set: #eval value: [ :args | MAL EVAL: args first env: replEnv ]. +replEnv set: #eval value: (Fn new: [ :args | MAL EVAL: args first env: replEnv ]). replEnv set: #'*ARGV*' value: (MALList new: argv). MAL rep: '(def! not (fn* (a) (if a false true)))' env: replEnv. diff --git a/gst/types.st b/gst/types.st index 9f3b032a1c..6e9a199176 100644 --- a/gst/types.st +++ b/gst/types.st @@ -146,6 +146,23 @@ MALObject subclass: MALAtom [ ] ] +MALObject subclass: Fn [ + | fn | + + fn [ ^fn ] + + Fn class >> new: fn [ + | f | + f := super new: #fn value: fn meta: nil. + f init: fn. + ^f + ] + + init: f [ + fn := f. + ] +] + Error subclass: MALError [ description [ ^'A MAL-related error' ] isResumable [ ^true ] From 968e1a199ef48ca596229fedbd508c65c4f9d1d6 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 9 Jul 2017 20:10:16 +0200 Subject: [PATCH 0368/2308] Implement step A --- gst/core.st | 52 ++++++++ gst/stepA_mal.st | 303 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 355 insertions(+) create mode 100644 gst/stepA_mal.st diff --git a/gst/core.st b/gst/core.st index a5a87e6397..651ab63713 100644 --- a/gst/core.st +++ b/gst/core.st @@ -1,6 +1,7 @@ FileStream fileIn: 'types.st'. FileStream fileIn: 'printer.st'. FileStream fileIn: 'reader.st'. +FileStream fileIn: 'readline.st'. Object subclass: Core [ Ns := Dictionary new. @@ -78,6 +79,17 @@ Core Ns at: #slurp put: (Fn new: [ :args | MALString new: (File path: args first value) contents ]). Core Ns at: #throw put: (Fn new: [ :args | MALCustomError new signal: args first ]). +Core Ns at: #readline put: + (Fn new: [ :args | + | result | + result := ReadLine readLine: args first value. + result isString ifTrue: [ + MALString new: result + ] ifFalse: [ + MALObject Nil + ] ]). +Core Ns at: #'time-ms' put: + (Fn new: [ :args | MALNumber new: Time millisecondClock ]). Core Ns at: #atom put: (Fn new: [ :args | MALAtom new: args first ]). @@ -122,6 +134,36 @@ Core Ns at: #rest put: rest := items allButFirst ]. MALList new: (OrderedCollection from: rest) ]). +Core Ns at: #conj put: + (Fn new: [ :args | + | kind result items | + kind := args first type. + result := args first value. + items := args allButFirst. + + kind = #list ifTrue: [ + MALList new: (OrderedCollection from: items reverse, result) + ] ifFalse: [ + MALVector new: (OrderedCollection from: result, items) + ] ]). +Core Ns at: #seq put: + (Fn new: [ :args | + | kind storage result | + kind := args first type. + storage := args first value. + Core nilable: args else: [ + storage isEmpty ifTrue: [ + MALObject Nil + ] ifFalse: [ + kind = #string ifTrue: [ + result := (OrderedCollection from: storage) collect: + [ :char | MALString new: char asString ]. + MALList new: result + ] ifFalse: [ + MALList new: (OrderedCollection from: storage) + ] + ] + ] ]). Core Ns at: #apply put: (Fn new: [ :args | @@ -142,6 +184,14 @@ Core Ns at: #map put: result := items collect: [ :item | f value: {item} ]. MALList new: (OrderedCollection from: result) ]). +Core Ns at: #meta put: + (Fn new: [ :args | + | meta | + meta := args first meta. + meta isNil ifTrue: [ MALObject Nil ] ifFalse: [ meta ] ]). +Core Ns at: #'with-meta' put: + (Fn new: [ :args | args first withMeta: args second ]). + Core Ns at: #'nil?' put: (Fn new: [ :args | Core coerce: [ args first type = #nil ] ]). Core Ns at: #'true?' put: @@ -152,6 +202,8 @@ Core Ns at: #'symbol?' put: (Fn new: [ :args | Core coerce: [ args first type = #symbol ] ]). Core Ns at: #'keyword?' put: (Fn new: [ :args | Core coerce: [ args first type = #keyword ] ]). +Core Ns at: #'string?' put: + (Fn new: [ :args | Core coerce: [ args first type = #string ] ]). Core Ns at: #'vector?' put: (Fn new: [ :args | Core coerce: [ args first type = #vector ] ]). Core Ns at: #'map?' put: diff --git a/gst/stepA_mal.st b/gst/stepA_mal.st new file mode 100644 index 0000000000..2bd378d668 --- /dev/null +++ b/gst/stepA_mal.st @@ -0,0 +1,303 @@ +FileStream fileIn: 'readline.st'. +FileStream fileIn: 'reader.st'. +FileStream fileIn: 'printer.st'. +FileStream fileIn: 'env.st'. +FileStream fileIn: 'func.st'. +FileStream fileIn: 'core.st'. + +Object subclass: MAL [ + MAL class >> READ: input [ + ^Reader readStr: input + ] + + MAL class >> evalAst: sexp env: env [ + sexp type = #symbol ifTrue: [ + ^env get: sexp value + ]. + + sexp type = #list ifTrue: [ + ^self evalList: sexp env: env class: MALList + ]. + sexp type = #vector ifTrue: [ + ^self evalList: sexp env: env class: MALVector + ]. + sexp type = #map ifTrue: [ + ^self evalList: sexp env: env class: MALMap + ]. + + ^sexp + ] + + MAL class >> evalList: sexp env: env class: aClass [ + | items | + items := sexp value collect: + [ :item | self EVAL: item env: env ]. + ^aClass new: items + ] + + MAL class >> quasiquote: ast [ + | result a a0 a0_ a0_0 a0_1 rest | + ast isPair ifFalse: [ + result := {MALSymbol new: #quote. ast}. + ^MALList new: (OrderedCollection from: result) + ]. + + a := ast value. + a0 := a first. + + (a0 type = #symbol and: [ a0 value = #unquote ]) ifTrue: [ ^a second ]. + + a0 isPair ifTrue: [ + a0_ := a0 value. + a0_0 := a0_ first. + a0_1 := a0_ second. + + (a0_0 type = #symbol and: + [ a0_0 value = #'splice-unquote' ]) ifTrue: [ + rest := MALList new: a allButFirst. + result := {MALSymbol new: #concat. a0_1. + self quasiquote: rest}. + ^MALList new: (OrderedCollection from: result) + ] + ]. + + rest := MALList new: a allButFirst. + result := {MALSymbol new: #cons. self quasiquote: a0. + self quasiquote: rest}. + ^MALList new: (OrderedCollection from: result) + ] + + MAL class >> isMacroCall: ast env: env [ + | a0 a0_ f | + ast type = #list ifTrue: [ + a0 := ast value first. + a0_ := a0 value. + a0 type = #symbol ifTrue: [ + f := env find: a0_. + (f notNil and: [ f type = #func ]) ifTrue: [ + ^f isMacro + ] + ] + ]. + ^false + ] + + MAL class >> macroexpand: aSexp env: env [ + | sexp | + + "NOTE: redefinition of method arguments is not allowed" + sexp := aSexp. + + [ self isMacroCall: sexp env: env ] whileTrue: [ + | ast a0_ macro rest | + ast := sexp value. + a0_ := ast first value. + macro := env find: a0_. + rest := ast allButFirst. + sexp := macro fn value: rest. + ]. + + ^sexp + ] + + MAL class >> EVAL: aSexp env: anEnv [ + | sexp env ast a0 a0_ a1 a1_ a2 a2_ a3 forms function args | + + "NOTE: redefinition of method arguments is not allowed" + sexp := aSexp. + env := anEnv. + + [ + [ :continue | + sexp type ~= #list ifTrue: [ + ^self evalAst: sexp env: env + ]. + sexp value isEmpty ifTrue: [ + ^sexp + ]. + + sexp := self macroexpand: sexp env: env. + sexp type ~= #list ifTrue: [ + ^self evalAst: sexp env: env + ]. + + ast := sexp value. + a0 := ast first. + + a0_ := ast first value. + a0_ = #'def!' ifTrue: [ + | result | + a1_ := ast second value. + a2 := ast third. + result := self EVAL: a2 env: env. + env set: a1_ value: result. + ^result + ]. + + a0_ = #'defmacro!' ifTrue: [ + | result | + a1_ := ast second value. + a2 := ast third. + result := self EVAL: a2 env: env. + result isMacro: true. + env set: a1_ value: result. + ^result + ]. + + a0_ = #'macroexpand' ifTrue: [ + a1 := ast second. + ^self macroexpand: a1 env: env + ]. + + a0_ = #'let*' ifTrue: [ + | env_ | + env_ := Env new: env. + a1_ := ast second value. + a2 := ast third. + 1 to: a1_ size by: 2 do: + [ :i | env_ set: (a1_ at: i) value + value: (self EVAL: (a1_ at: i + 1) + env: env_) ]. + env := env_. + sexp := a2. + continue value "TCO" + ]. + + a0_ = #do ifTrue: [ + | forms last | + ast size < 2 ifTrue: [ + forms := {}. + last := MALObject Nil. + ] ifFalse: [ + forms := ast copyFrom: 2 to: ast size - 1. + last := ast last. + ]. + + forms do: [ :form | self EVAL: form env: env ]. + sexp := last. + continue value "TCO" + ]. + + a0_ = #if ifTrue: [ + | condition | + a1 := ast second. + a2 := ast third. + a3 := ast at: 4 ifAbsent: [ MALObject Nil ]. + condition := self EVAL: a1 env: env. + + (condition type = #false or: + [ condition type = #nil ]) ifTrue: [ + sexp := a3 + ] ifFalse: [ + sexp := a2 + ]. + continue value "TCO" + ]. + + a0_ = #quote ifTrue: [ + a1 := ast second. + ^a1 + ]. + + a0_ = #quasiquote ifTrue: [ + | result | + a1 := ast second. + sexp := self quasiquote: a1. + continue value "TCO" + ]. + + a0_ = #'try*' ifTrue: [ + | A B C | + A := ast second. + a2_ := ast third value. + B := a2_ second value. + C := a2_ third. + ^[ self EVAL: A env: env ] on: MALError do: + [ :err | + | data env_ result | + data := err data. + data isString ifTrue: [ + data := MALString new: data + ]. + env_ := Env new: env binds: {B} exprs: {data}. + err return: (self EVAL: C env: env_) + ] + ]. + + a0_ = #'fn*' ifTrue: [ + | binds env_ fn | + a1_ := ast second value. + binds := a1_ collect: [ :item | item value ]. + a2 := ast third. + fn := [ :args | + self EVAL: a2 env: + (Env new: env binds: binds exprs: args) ]. + ^Func new: a2 params: binds env: env fn: fn + ]. + + forms := (self evalAst: sexp env: env) value. + function := forms first. + args := forms allButFirst asArray. + + function type = #fn ifTrue: [ ^function fn value: args ]. + function type = #func ifTrue: [ + | env_ | + sexp := function ast. + env_ := Env new: function env binds: function params + exprs: args. + env := env_. + continue value "TCO" + ] + ] valueWithExit + ] repeat. + ] + + MAL class >> PRINT: sexp [ + ^Printer prStr: sexp printReadably: true + ] + + MAL class >> rep: input env: env [ + ^self PRINT: (self EVAL: (self READ: input) env: env) + ] +] + +| input historyFile replEnv argv | + +historyFile := '.mal_history'. +ReadLine readHistory: historyFile. +replEnv := Env new: nil. + +argv := Smalltalk arguments. +argv notEmpty ifTrue: [ argv := argv allButFirst ]. +argv := OrderedCollection from: (argv collect: [ :arg | MALString new: arg ]). + +Core Ns keysAndValuesDo: [ :op :block | replEnv set: op value: block ]. +replEnv set: #eval value: (Fn new: [ :args | MAL EVAL: args first env: replEnv ]). +replEnv set: #'*ARGV*' value: (MALList new: argv). +replEnv set: #'*host-language*' value: (MALString new: 'smalltalk'). + +MAL rep: '(def! not (fn* (a) (if a false true)))' env: replEnv. +MAL rep: '(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))' env: replEnv. + +MAL rep: '(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list ''if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons ''cond (rest (rest xs)))))))' env: replEnv. +MAL rep: '(def! *gensym-counter* (atom 0))' env: replEnv. +MAL rep: '(def! gensym (fn* [] (symbol (str "G__" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))' env: replEnv. +MAL rep: '(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))' env: replEnv. + +Smalltalk arguments notEmpty ifTrue: [ + MAL rep: '(load-file "', Smalltalk arguments first, '")' env: replEnv +] ifFalse: [ + MAL rep: '(println (str "Mal [" *host-language* "]"))' env: replEnv. + [ input := ReadLine readLine: 'user> '. input isNil ] whileFalse: [ + input isEmpty ifFalse: [ + ReadLine addHistory: input. + ReadLine writeHistory: historyFile. + [ (MAL rep: input env: replEnv) displayNl ] + on: MALEmptyInput do: [ #return ] + on: MALError do: + [ :err | ('error: ', err messageText) displayNl. #return ]. + ] + ]. + + '' displayNl. +] From fd216c1911b08503b0ac47704f2ac203ff647ac9 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 9 Jul 2017 22:51:15 +0200 Subject: [PATCH 0369/2308] Self-hosting fix --- gst/env.st | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gst/env.st b/gst/env.st index ee9111cd07..a6658aba57 100644 --- a/gst/env.st +++ b/gst/env.st @@ -20,8 +20,8 @@ Object subclass: Env [ 1 to: binds size do: [ :i | (binds at: i) = #& ifTrue: [ | rest | - rest := MALList new: (exprs copyFrom: i). - self set: (binds at: i + 1) value: rest. + rest := OrderedCollection from: (exprs copyFrom: i). + self set: (binds at: i + 1) value: (MALList new: rest). ^nil ] ifFalse: [ self set: (binds at: i) value: (exprs at: i) From 2aa83563229537338d238e98df0485e1ff7ea7f8 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 9 Jul 2017 23:57:14 +0200 Subject: [PATCH 0370/2308] Write function for relative loading --- gst/core.st | 5 ----- gst/env.st | 2 -- gst/printer.st | 2 -- gst/reader.st | 3 --- gst/step0_repl.st | 11 ++++++++++- gst/step1_read_print.st | 17 ++++++++++++++--- gst/step2_eval.st | 17 ++++++++++++++--- gst/step3_env.st | 19 +++++++++++++++---- gst/step4_if_fn_do.st | 21 ++++++++++++++++----- gst/step5_tco.st | 23 +++++++++++++++++------ gst/step6_file.st | 23 +++++++++++++++++------ gst/step7_quote.st | 23 +++++++++++++++++------ gst/step8_macros.st | 23 +++++++++++++++++------ gst/step9_try.st | 23 +++++++++++++++++------ gst/stepA_mal.st | 23 +++++++++++++++++------ 15 files changed, 171 insertions(+), 64 deletions(-) diff --git a/gst/core.st b/gst/core.st index 651ab63713..2ceae1a886 100644 --- a/gst/core.st +++ b/gst/core.st @@ -1,8 +1,3 @@ -FileStream fileIn: 'types.st'. -FileStream fileIn: 'printer.st'. -FileStream fileIn: 'reader.st'. -FileStream fileIn: 'readline.st'. - Object subclass: Core [ Ns := Dictionary new. Core class >> Ns [ ^Ns ] diff --git a/gst/env.st b/gst/env.st index a6658aba57..c62f871434 100644 --- a/gst/env.st +++ b/gst/env.st @@ -1,5 +1,3 @@ -FileStream fileIn: 'types.st'. - Object subclass: Env [ | data outer | diff --git a/gst/printer.st b/gst/printer.st index d84994656f..c86fc7ebf3 100644 --- a/gst/printer.st +++ b/gst/printer.st @@ -1,5 +1,3 @@ -FileStream fileIn: 'types.st'. - Object subclass: Printer [ Printer class >> prStr: sexp printReadably: printReadably [ sexp type = #fn ifTrue: [ ^'#' ]. diff --git a/gst/reader.st b/gst/reader.st index 2cbeea0353..1515d277aa 100644 --- a/gst/reader.st +++ b/gst/reader.st @@ -1,6 +1,3 @@ -FileStream fileIn: 'types.st'. -FileStream fileIn: 'util.st'. - Object subclass: Reader [ | storage index | diff --git a/gst/step0_repl.st b/gst/step0_repl.st index 982d89db02..622347dfc7 100644 --- a/gst/step0_repl.st +++ b/gst/step0_repl.st @@ -1,4 +1,13 @@ -FileStream fileIn: 'readline.st'. +String extend [ + String >> loadRelative [ + | scriptPath scriptDirectory | + scriptPath := ContextPart thisContext currentFileName. + scriptDirectory := FilePath stripFileNameFor: scriptPath. + FileStream fileIn: (FilePath append: self to: scriptDirectory) + ] +] + +'readline.st' loadRelative. Object subclass: MAL [ MAL class >> READ: input [ diff --git a/gst/step1_read_print.st b/gst/step1_read_print.st index 6b33705a97..4957a4ef48 100644 --- a/gst/step1_read_print.st +++ b/gst/step1_read_print.st @@ -1,6 +1,17 @@ -FileStream fileIn: 'readline.st'. -FileStream fileIn: 'reader.st'. -FileStream fileIn: 'printer.st'. +String extend [ + String >> loadRelative [ + | scriptPath scriptDirectory | + scriptPath := ContextPart thisContext currentFileName. + scriptDirectory := FilePath stripFileNameFor: scriptPath. + FileStream fileIn: (FilePath append: self to: scriptDirectory) + ] +] + +'readline.st' loadRelative. +'util.st' loadRelative. +'types.st' loadRelative. +'reader.st' loadRelative. +'printer.st' loadRelative. Object subclass: MAL [ MAL class >> READ: input [ diff --git a/gst/step2_eval.st b/gst/step2_eval.st index c105f9497b..030dbcb6d8 100644 --- a/gst/step2_eval.st +++ b/gst/step2_eval.st @@ -1,6 +1,17 @@ -FileStream fileIn: 'readline.st'. -FileStream fileIn: 'reader.st'. -FileStream fileIn: 'printer.st'. +String extend [ + String >> loadRelative [ + | scriptPath scriptDirectory | + scriptPath := ContextPart thisContext currentFileName. + scriptDirectory := FilePath stripFileNameFor: scriptPath. + FileStream fileIn: (FilePath append: self to: scriptDirectory) + ] +] + +'readline.st' loadRelative. +'util.st' loadRelative. +'types.st' loadRelative. +'reader.st' loadRelative. +'printer.st' loadRelative. Object subclass: MAL [ MAL class >> READ: input [ diff --git a/gst/step3_env.st b/gst/step3_env.st index bb7e8a14a7..baf0289293 100644 --- a/gst/step3_env.st +++ b/gst/step3_env.st @@ -1,7 +1,18 @@ -FileStream fileIn: 'readline.st'. -FileStream fileIn: 'reader.st'. -FileStream fileIn: 'printer.st'. -FileStream fileIn: 'env.st'. +String extend [ + String >> loadRelative [ + | scriptPath scriptDirectory | + scriptPath := ContextPart thisContext currentFileName. + scriptDirectory := FilePath stripFileNameFor: scriptPath. + FileStream fileIn: (FilePath append: self to: scriptDirectory) + ] +] + +'readline.st' loadRelative. +'util.st' loadRelative. +'types.st' loadRelative. +'reader.st' loadRelative. +'printer.st' loadRelative. +'env.st' loadRelative. Object subclass: MAL [ MAL class >> READ: input [ diff --git a/gst/step4_if_fn_do.st b/gst/step4_if_fn_do.st index 8bb7adb58a..a61e0b21c1 100644 --- a/gst/step4_if_fn_do.st +++ b/gst/step4_if_fn_do.st @@ -1,8 +1,19 @@ -FileStream fileIn: 'readline.st'. -FileStream fileIn: 'reader.st'. -FileStream fileIn: 'printer.st'. -FileStream fileIn: 'env.st'. -FileStream fileIn: 'core.st'. +String extend [ + String >> loadRelative [ + | scriptPath scriptDirectory | + scriptPath := ContextPart thisContext currentFileName. + scriptDirectory := FilePath stripFileNameFor: scriptPath. + FileStream fileIn: (FilePath append: self to: scriptDirectory) + ] +] + +'readline.st' loadRelative. +'util.st' loadRelative. +'types.st' loadRelative. +'reader.st' loadRelative. +'printer.st' loadRelative. +'env.st' loadRelative. +'core.st' loadRelative. Object subclass: MAL [ MAL class >> READ: input [ diff --git a/gst/step5_tco.st b/gst/step5_tco.st index 9341b86c4e..d0289b8cef 100644 --- a/gst/step5_tco.st +++ b/gst/step5_tco.st @@ -1,9 +1,20 @@ -FileStream fileIn: 'readline.st'. -FileStream fileIn: 'reader.st'. -FileStream fileIn: 'printer.st'. -FileStream fileIn: 'env.st'. -FileStream fileIn: 'func.st'. -FileStream fileIn: 'core.st'. +String extend [ + String >> loadRelative [ + | scriptPath scriptDirectory | + scriptPath := ContextPart thisContext currentFileName. + scriptDirectory := FilePath stripFileNameFor: scriptPath. + FileStream fileIn: (FilePath append: self to: scriptDirectory) + ] +] + +'readline.st' loadRelative. +'util.st' loadRelative. +'types.st' loadRelative. +'reader.st' loadRelative. +'printer.st' loadRelative. +'env.st' loadRelative. +'func.st' loadRelative. +'core.st' loadRelative. Object subclass: MAL [ MAL class >> READ: input [ diff --git a/gst/step6_file.st b/gst/step6_file.st index 758cc68c52..93b475ee57 100644 --- a/gst/step6_file.st +++ b/gst/step6_file.st @@ -1,9 +1,20 @@ -FileStream fileIn: 'readline.st'. -FileStream fileIn: 'reader.st'. -FileStream fileIn: 'printer.st'. -FileStream fileIn: 'env.st'. -FileStream fileIn: 'func.st'. -FileStream fileIn: 'core.st'. +String extend [ + String >> loadRelative [ + | scriptPath scriptDirectory | + scriptPath := ContextPart thisContext currentFileName. + scriptDirectory := FilePath stripFileNameFor: scriptPath. + FileStream fileIn: (FilePath append: self to: scriptDirectory) + ] +] + +'readline.st' loadRelative. +'util.st' loadRelative. +'types.st' loadRelative. +'reader.st' loadRelative. +'printer.st' loadRelative. +'env.st' loadRelative. +'func.st' loadRelative. +'core.st' loadRelative. Object subclass: MAL [ MAL class >> READ: input [ diff --git a/gst/step7_quote.st b/gst/step7_quote.st index faeaf645f9..0e28848631 100644 --- a/gst/step7_quote.st +++ b/gst/step7_quote.st @@ -1,9 +1,20 @@ -FileStream fileIn: 'readline.st'. -FileStream fileIn: 'reader.st'. -FileStream fileIn: 'printer.st'. -FileStream fileIn: 'env.st'. -FileStream fileIn: 'func.st'. -FileStream fileIn: 'core.st'. +String extend [ + String >> loadRelative [ + | scriptPath scriptDirectory | + scriptPath := ContextPart thisContext currentFileName. + scriptDirectory := FilePath stripFileNameFor: scriptPath. + FileStream fileIn: (FilePath append: self to: scriptDirectory) + ] +] + +'readline.st' loadRelative. +'util.st' loadRelative. +'types.st' loadRelative. +'reader.st' loadRelative. +'printer.st' loadRelative. +'env.st' loadRelative. +'func.st' loadRelative. +'core.st' loadRelative. Object subclass: MAL [ MAL class >> READ: input [ diff --git a/gst/step8_macros.st b/gst/step8_macros.st index 69c7721ac3..df76eafd3e 100644 --- a/gst/step8_macros.st +++ b/gst/step8_macros.st @@ -1,9 +1,20 @@ -FileStream fileIn: 'readline.st'. -FileStream fileIn: 'reader.st'. -FileStream fileIn: 'printer.st'. -FileStream fileIn: 'env.st'. -FileStream fileIn: 'func.st'. -FileStream fileIn: 'core.st'. +String extend [ + String >> loadRelative [ + | scriptPath scriptDirectory | + scriptPath := ContextPart thisContext currentFileName. + scriptDirectory := FilePath stripFileNameFor: scriptPath. + FileStream fileIn: (FilePath append: self to: scriptDirectory) + ] +] + +'readline.st' loadRelative. +'util.st' loadRelative. +'types.st' loadRelative. +'reader.st' loadRelative. +'printer.st' loadRelative. +'env.st' loadRelative. +'func.st' loadRelative. +'core.st' loadRelative. Object subclass: MAL [ MAL class >> READ: input [ diff --git a/gst/step9_try.st b/gst/step9_try.st index bd0e434247..1e98b38832 100644 --- a/gst/step9_try.st +++ b/gst/step9_try.st @@ -1,9 +1,20 @@ -FileStream fileIn: 'readline.st'. -FileStream fileIn: 'reader.st'. -FileStream fileIn: 'printer.st'. -FileStream fileIn: 'env.st'. -FileStream fileIn: 'func.st'. -FileStream fileIn: 'core.st'. +String extend [ + String >> loadRelative [ + | scriptPath scriptDirectory | + scriptPath := ContextPart thisContext currentFileName. + scriptDirectory := FilePath stripFileNameFor: scriptPath. + FileStream fileIn: (FilePath append: self to: scriptDirectory) + ] +] + +'readline.st' loadRelative. +'util.st' loadRelative. +'types.st' loadRelative. +'reader.st' loadRelative. +'printer.st' loadRelative. +'env.st' loadRelative. +'func.st' loadRelative. +'core.st' loadRelative. Object subclass: MAL [ MAL class >> READ: input [ diff --git a/gst/stepA_mal.st b/gst/stepA_mal.st index 2bd378d668..299ded9af7 100644 --- a/gst/stepA_mal.st +++ b/gst/stepA_mal.st @@ -1,9 +1,20 @@ -FileStream fileIn: 'readline.st'. -FileStream fileIn: 'reader.st'. -FileStream fileIn: 'printer.st'. -FileStream fileIn: 'env.st'. -FileStream fileIn: 'func.st'. -FileStream fileIn: 'core.st'. +String extend [ + String >> loadRelative [ + | scriptPath scriptDirectory | + scriptPath := ContextPart thisContext currentFileName. + scriptDirectory := FilePath stripFileNameFor: scriptPath. + FileStream fileIn: (FilePath append: self to: scriptDirectory) + ] +] + +'readline.st' loadRelative. +'util.st' loadRelative. +'types.st' loadRelative. +'reader.st' loadRelative. +'printer.st' loadRelative. +'env.st' loadRelative. +'func.st' loadRelative. +'core.st' loadRelative. Object subclass: MAL [ MAL class >> READ: input [ From 06ecfc6a61172f1851ac9d03729e46684dcc2419 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 10 Jul 2017 09:04:39 +0200 Subject: [PATCH 0371/2308] Fix backquote error for perf tests --- gst/step7_quote.st | 6 +++--- gst/step8_macros.st | 6 +++--- gst/step9_try.st | 6 +++--- gst/stepA_mal.st | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/gst/step7_quote.st b/gst/step7_quote.st index 0e28848631..7d6c70095a 100644 --- a/gst/step7_quote.st +++ b/gst/step7_quote.st @@ -47,7 +47,7 @@ Object subclass: MAL [ ] MAL class >> quasiquote: ast [ - | result a a0 a0_ a0_0 a0_1 rest | + | result a a0 a0_ a0_0 rest | ast isPair ifFalse: [ result := {MALSymbol new: #quote. ast}. ^MALList new: (OrderedCollection from: result) @@ -61,12 +61,12 @@ Object subclass: MAL [ a0 isPair ifTrue: [ a0_ := a0 value. a0_0 := a0_ first. - a0_1 := a0_ second. (a0_0 type = #symbol and: [ a0_0 value = #'splice-unquote' ]) ifTrue: [ rest := MALList new: a allButFirst. - result := {MALSymbol new: #concat. a0_1. + result := {MALSymbol new: #concat. + a0_ second. self quasiquote: rest}. ^MALList new: (OrderedCollection from: result) ] diff --git a/gst/step8_macros.st b/gst/step8_macros.st index df76eafd3e..d51ea1c048 100644 --- a/gst/step8_macros.st +++ b/gst/step8_macros.st @@ -47,7 +47,7 @@ Object subclass: MAL [ ] MAL class >> quasiquote: ast [ - | result a a0 a0_ a0_0 a0_1 rest | + | result a a0 a0_ a0_0 rest | ast isPair ifFalse: [ result := {MALSymbol new: #quote. ast}. ^MALList new: (OrderedCollection from: result) @@ -61,12 +61,12 @@ Object subclass: MAL [ a0 isPair ifTrue: [ a0_ := a0 value. a0_0 := a0_ first. - a0_1 := a0_ second. (a0_0 type = #symbol and: [ a0_0 value = #'splice-unquote' ]) ifTrue: [ rest := MALList new: a allButFirst. - result := {MALSymbol new: #concat. a0_1. + result := {MALSymbol new: #concat. + a0_ second. self quasiquote: rest}. ^MALList new: (OrderedCollection from: result) ] diff --git a/gst/step9_try.st b/gst/step9_try.st index 1e98b38832..ae1db7fd5d 100644 --- a/gst/step9_try.st +++ b/gst/step9_try.st @@ -47,7 +47,7 @@ Object subclass: MAL [ ] MAL class >> quasiquote: ast [ - | result a a0 a0_ a0_0 a0_1 rest | + | result a a0 a0_ a0_0 rest | ast isPair ifFalse: [ result := {MALSymbol new: #quote. ast}. ^MALList new: (OrderedCollection from: result) @@ -61,12 +61,12 @@ Object subclass: MAL [ a0 isPair ifTrue: [ a0_ := a0 value. a0_0 := a0_ first. - a0_1 := a0_ second. (a0_0 type = #symbol and: [ a0_0 value = #'splice-unquote' ]) ifTrue: [ rest := MALList new: a allButFirst. - result := {MALSymbol new: #concat. a0_1. + result := {MALSymbol new: #concat. + a0_ second. self quasiquote: rest}. ^MALList new: (OrderedCollection from: result) ] diff --git a/gst/stepA_mal.st b/gst/stepA_mal.st index 299ded9af7..29f15d2efc 100644 --- a/gst/stepA_mal.st +++ b/gst/stepA_mal.st @@ -47,7 +47,7 @@ Object subclass: MAL [ ] MAL class >> quasiquote: ast [ - | result a a0 a0_ a0_0 a0_1 rest | + | result a a0 a0_ a0_0 rest | ast isPair ifFalse: [ result := {MALSymbol new: #quote. ast}. ^MALList new: (OrderedCollection from: result) @@ -61,12 +61,12 @@ Object subclass: MAL [ a0 isPair ifTrue: [ a0_ := a0 value. a0_0 := a0_ first. - a0_1 := a0_ second. (a0_0 type = #symbol and: [ a0_0 value = #'splice-unquote' ]) ifTrue: [ rest := MALList new: a allButFirst. - result := {MALSymbol new: #concat. a0_1. + result := {MALSymbol new: #concat. + a0_ second. self quasiquote: rest}. ^MALList new: (OrderedCollection from: result) ] From 9592d833f21dbb74d56bdf7d1840353961a7b662 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 10 Jul 2017 09:12:48 +0200 Subject: [PATCH 0372/2308] Mention in README --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bcaa3ca76a..dfcb2004de 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 65 languages: +Mal is implemented in 66 languages: * Ada * GNU awk @@ -32,6 +32,7 @@ Mal is implemented in 65 languages: * Go * Groovy * GNU Guile +* GNU Smalltalk * Haskell * Haxe * Io @@ -441,6 +442,17 @@ cd guile guile -L ./ stepX_YYY.scm ``` +### GNU Smalltalk + +*The Smalltalk implementation was created by [Vasilij Schneidermann](https://github.com/wasamasa)* + +The Smalltalk implementation of mal has been tested with GNU Smalltalk 3.2.91. + +``` +cd gst +./run +``` + ### Haskell The Haskell implementation requires the ghc compiler version 7.10.1 or From ae63cd371bf6e6039ae1088b28dd1331297095e3 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 10 Jul 2017 21:39:05 +0200 Subject: [PATCH 0373/2308] Add dockerfile --- gst/Dockerfile | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 gst/Dockerfile diff --git a/gst/Dockerfile b/gst/Dockerfile new file mode 100644 index 0000000000..81b7cebc03 --- /dev/null +++ b/gst/Dockerfile @@ -0,0 +1,26 @@ +FROM ubuntu:wily +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# picolisp +RUN apt-get -y install gnu-smalltalk + From 1c72f6ba960435f58076206593d92be968bc8fbd Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 10 Jul 2017 21:47:05 +0200 Subject: [PATCH 0374/2308] Add to test matrix --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 711212ab7d..3a53dd5b6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ matrix: - {env: IMPL=fsharp, services: [docker]} - {env: IMPL=go, services: [docker]} - {env: IMPL=groovy, services: [docker]} + - {env: IMPL=gst, services: [docker]} - {env: IMPL=guile, services: [docker]} - {env: IMPL=haskell, services: [docker]} - {env: IMPL=haxe, services: [docker]} From 65055aa22268020407107a51c5bb3e56d8efee51 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 10 Jul 2017 23:28:26 +0200 Subject: [PATCH 0375/2308] Backport a regex bugfix from 3.2.91 to 3.2.4 --- gst/util.st | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/gst/util.st b/gst/util.st index 03a226d09e..6e0e429d53 100644 --- a/gst/util.st +++ b/gst/util.st @@ -33,8 +33,29 @@ BlockClosure extend [ valueWithExit [ ^self value: [ ^nil ] ] +] + +"NOTE: bugfix version from 3.2.91 for 3.2.4" +Namespace current: Kernel [ + +MatchingRegexResults extend [ + at: anIndex [ + + | reg text | + anIndex = 0 ifTrue: [^self match]. + cache isNil ifTrue: [cache := Array new: registers size]. + (cache at: anIndex) isNil + ifTrue: + [reg := registers at: anIndex. + text := reg isNil + ifTrue: [nil] + ifFalse: [ + reg isEmpty + ifTrue: [''] + ifFalse: [self subject copyFrom: reg first to: reg last]]. + cache at: anIndex put: text]. + ^cache at: anIndex + ] +] - "HACK" - type [ ^#closure ] - value [ ^nil ] ] From c4a26e54717d6aeaf6dfb908966db929f64718dc Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 11 Jul 2017 23:38:06 +0200 Subject: [PATCH 0376/2308] Implement gst-eval --- gst/core.st | 2 ++ gst/tests/stepA_mal.mal | 11 +++++++++++ gst/util.st | 27 +++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 gst/tests/stepA_mal.mal diff --git a/gst/core.st b/gst/core.st index 2ceae1a886..d1c52de0ce 100644 --- a/gst/core.st +++ b/gst/core.st @@ -85,6 +85,8 @@ Core Ns at: #readline put: ] ]). Core Ns at: #'time-ms' put: (Fn new: [ :args | MALNumber new: Time millisecondClock ]). +Core Ns at: #'gst-eval' put: + (Fn new: [ :args | (Behavior evaluate: args first value) toMALValue ]). Core Ns at: #atom put: (Fn new: [ :args | MALAtom new: args first ]). diff --git a/gst/tests/stepA_mal.mal b/gst/tests/stepA_mal.mal new file mode 100644 index 0000000000..32221a4580 --- /dev/null +++ b/gst/tests/stepA_mal.mal @@ -0,0 +1,11 @@ +(gst-eval "1 + 1") +;=>2 + +(gst-eval "{1. 2. 3}") +;=>[1 2 3] + +(gst-eval "#('a' 'b' 'c') join: ' '") +;=>"a b c" + +(gst-eval "'Hello World!' displayNl") +; Hello World! diff --git a/gst/util.st b/gst/util.st index 6e0e429d53..5a73576e27 100644 --- a/gst/util.st +++ b/gst/util.st @@ -35,6 +35,33 @@ BlockClosure extend [ ] ] +Object extend [ + toMALValue [ + self = true ifTrue: [ ^MALObject True ]. + self = false ifTrue: [ ^MALObject False ]. + self = nil ifTrue: [ ^MALObject Nil ]. + self isNumber ifTrue: [ ^MALNumber new: self ]. + self isString ifTrue: [ ^MALString new: self ]. + self isSymbol ifTrue: [ ^MALSymbol new: self ]. + self isArray ifTrue: [ + ^MALVector new: (self asOrderedCollection collect: + [ :item | item toMALValue ]) + ]. + self isSequenceable ifTrue: [ + ^MALList new: (self asOrderedCollection collect: + [ :item | item toMALValue ]) + ]. + self class = Dictionary ifTrue: [ + | result | + result := Dictionary new. + self keysAndValuesDo: [ :key :value | + result at: key toMALValue put: value toMALValue + ]. + ^MALMap new: result + ] + ] +] + "NOTE: bugfix version from 3.2.91 for 3.2.4" Namespace current: Kernel [ From 4c696bfb54c98b161070490b3e423b6bb0e9fbdc Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Tue, 18 Jul 2017 21:34:36 +0200 Subject: [PATCH 0377/2308] Elm: GC almost working. --- elm/Core.elm | 4 +- elm/Env.elm | 194 ++++++++++++++++++++++++++++++----------- elm/Eval.elm | 25 ++++++ elm/Makefile | 2 +- elm/Printer.elm | 35 ++++++-- elm/Types.elm | 2 + elm/step4_if_fn_do.elm | 6 +- elm/step5_tco.elm | 6 +- elm/step6_file.elm | 8 +- elm/step7_quote.elm | 8 +- elm/step8_macros.elm | 10 +-- elm/step9_try.elm | 10 +-- elm/stepA_mal.elm | 19 ++-- mal/core.mal | 4 +- 14 files changed, 241 insertions(+), 92 deletions(-) diff --git a/elm/Core.elm b/elm/Core.elm index 95b9adf9c3..1b31ae9e4e 100644 --- a/elm/Core.elm +++ b/elm/Core.elm @@ -500,12 +500,12 @@ ns = callFn func [ inv ] |> Eval.andThen (\outv -> - go func rest (outv :: acc) + Eval.pushRef outv (go func rest (outv :: acc)) ) in case args of [ MalFunction func, MalList list ] -> - go func list [] + Eval.withStack (go func list []) [ MalFunction func, MalVector vec ] -> go func (Array.toList vec) [] diff --git a/elm/Env.elm b/elm/Env.elm index 867df02eae..ae64056437 100644 --- a/elm/Env.elm +++ b/elm/Env.elm @@ -3,17 +3,19 @@ module Env ( debug , globalFrameId , global - , push - , pop - , jump - , enter - , leave - , ref , get , set , newAtom , getAtom , setAtom + , push + , pop + , enter + , jump + , leave + , ref + , pushRef + , restoreRefs , gc ) @@ -38,12 +40,12 @@ globalFrameId = defaultGcInterval : Int defaultGcInterval = - 3 + 10 global : Env global = - { frames = Dict.singleton globalFrameId (emptyFrame Nothing) + { frames = Dict.singleton globalFrameId (emptyFrame Nothing Nothing) , nextFrameId = globalFrameId + 1 , currentFrameId = globalFrameId , atoms = Dict.empty @@ -51,11 +53,12 @@ global = , debug = False , gcInterval = defaultGcInterval , gcCounter = 0 + , stack = [] } -getFrame : Int -> Env -> Frame -getFrame frameId env = +getFrame : Env -> Int -> Frame +getFrame env frameId = case Dict.get frameId env.frames of Just frame -> frame @@ -64,9 +67,10 @@ getFrame frameId env = Debug.crash <| "frame #" ++ (toString frameId) ++ " not found" -emptyFrame : Maybe Int -> Frame -emptyFrame outerId = +emptyFrame : Maybe Int -> Maybe Int -> Frame +emptyFrame outerId exitId = { outerId = outerId + , exitId = exitId , data = Dict.empty , refCnt = 1 } @@ -96,7 +100,7 @@ get name env = go frameId = let frame = - getFrame frameId env + getFrame env frameId in case Dict.get name frame.data of Just value -> @@ -142,11 +146,6 @@ setAtom atomId value env = } -jump : Int -> Env -> Env -jump frameId env = - { env | currentFrameId = frameId } - - push : Env -> Env push env = let @@ -154,7 +153,10 @@ push env = env.nextFrameId newFrame = - emptyFrame (Just env.currentFrameId) + emptyFrame (Just env.currentFrameId) Nothing + + bogus = + debug env "push" frameId in { env | currentFrameId = frameId @@ -170,7 +172,10 @@ pop env = env.currentFrameId frame = - getFrame frameId env + getFrame env frameId + + bogus = + debug env "pop" frameId in case frame.outerId of Just outerId -> @@ -194,14 +199,19 @@ setBinds binds frame = { frame | data = Dict.insert name expr frame.data } +{-| Enter a new frame with a set of binds +-} enter : Int -> List ( String, MalExpr ) -> Env -> Env -enter parentFrameId binds env = +enter outerId binds env = let frameId = debug env "enter #" env.nextFrameId + exitId = + env.currentFrameId + newFrame = - setBinds binds (emptyFrame (Just parentFrameId)) + setBinds binds (emptyFrame (Just outerId) (Just exitId)) in { env | currentFrameId = frameId @@ -210,15 +220,55 @@ enter parentFrameId binds env = } -leave : Int -> Env -> Env -leave orgFrameId env = +{-| Jump into a frame +-} +jump : Int -> Env -> Env +jump frameId env = + let + setExitId = + Maybe.map + (\frame -> + { frame + | exitId = Just env.currentFrameId + , refCnt = frame.refCnt + 1 + } + ) + + bogus = + debug env "jump #" frameId + in + { env + | currentFrameId = frameId + , frames = Dict.update frameId setExitId env.frames + } + + +leave : Env -> Env +leave env = let frameId = debug env "leave #" env.currentFrameId + + frame = + getFrame env frameId + + exitId = + case frame.exitId of + Just exitId -> + exitId + + Nothing -> + Debug.crash <| + "frame #" + ++ (toString frameId) + ++ " doesn't have an exitId" in { env - | currentFrameId = orgFrameId - , frames = Dict.update frameId free env.frames + | currentFrameId = exitId + , frames = + env.frames + |> Dict.insert frameId { frame | exitId = Nothing } + |> Dict.update frameId free } @@ -231,7 +281,7 @@ ref env = go frameId env = let frame = - getFrame frameId env + getFrame env frameId newFrame = { frame | refCnt = frame.refCnt + 1 } @@ -263,6 +313,16 @@ free = ) +pushRef : MalExpr -> Env -> Env +pushRef ref env = + { env | stack = ref :: env.stack } + + +restoreRefs : List MalExpr -> Env -> Env +restoreRefs refs env = + { env | stack = refs } + + {-| Given an Env see which frames are not reachable from the global frame, or from the current expression. @@ -270,10 +330,12 @@ Return a new Env with the unreachable frames removed. -} gc : MalExpr -> Env -> Env -gc currentExpr env = +gc expr env = let + -- bogus = + -- Debug.log "GC stack = " env.stack countList acc = - List.foldl countRefs acc + List.foldl countExpr acc countFrame { data } acc = data |> Dict.values |> countList acc @@ -282,22 +344,28 @@ gc currentExpr env = if not (Set.member frameId acc) then let frame = - getFrame frameId env + getFrame env frameId newAcc = - (Set.insert frameId acc) + Set.insert frameId acc in countFrame frame newAcc else acc - countRefs expr acc = + countBound bound acc = + bound + |> List.map Tuple.second + |> countList acc + + countExpr expr acc = case expr of MalFunction (UserFunc { frameId }) -> recur frameId acc - MalApply { frameId } -> + MalApply { frameId, bound } -> recur frameId acc + |> countBound bound MalList list -> countList acc list @@ -313,7 +381,7 @@ gc currentExpr env = value = getAtom atomId env in - countRefs value acc + countExpr value acc _ -> acc @@ -321,26 +389,42 @@ gc currentExpr env = initSet = Set.fromList [ globalFrameId, env.currentFrameId ] - expandParents frameId acc = + countFrames frames acc = + Set.toList frames + |> List.map (getFrame env) + |> List.foldl countFrame acc + + expand frameId frame fn acc = + case fn frame of + Nothing -> + acc + + Just parentId -> + Set.insert parentId acc + + expandBoth frameId = let frame = - getFrame frameId env + getFrame env frameId in - case frame.outerId of - Just parentId -> - Set.insert parentId acc - - Nothing -> - acc + expand frameId frame .outerId + >> expand frameId frame .exitId - expandAllFrames frames = - Set.foldl expandParents frames frames + expandParents frames = + Set.foldl expandBoth frames frames - makeEmptyFrame frameId = - ( frameId, emptyFrame Nothing ) + loop acc = + let + newAcc = + expandParents acc - globalFrame = - getFrame globalFrameId env + newParents = + Set.diff newAcc acc + in + if Set.isEmpty newParents then + newAcc + else + loop <| countFrames newParents newAcc makeNewEnv newFrames = { env @@ -353,10 +437,16 @@ gc currentExpr env = filterFrames frames keep = Dict.filter (keepFilter keep) frames + + reportUnused frames keep = + Set.diff (Set.fromList (Dict.keys frames)) keep + |> Debug.log "\n\nUNUSED FRAMES\n\n" + |> always keep in - initSet - |> countRefs currentExpr - |> countFrame globalFrame - |> expandAllFrames + countFrames initSet initSet + |> countExpr expr + |> (flip countList) env.stack + |> loop + -- |> reportUnused env.frames |> filterFrames env.frames |> makeNewEnv diff --git a/elm/Eval.elm b/elm/Eval.elm index 4bd38a19ed..1d37df3eb6 100644 --- a/elm/Eval.elm +++ b/elm/Eval.elm @@ -3,6 +3,7 @@ module Eval exposing (..) import Types exposing (..) import IO exposing (IO) import Env +import Printer exposing (printEnv) apply : Eval a -> Env -> EvalContext a @@ -89,6 +90,12 @@ gcPass e env = let go env t expr = if env.gcCounter >= env.gcInterval then + --Debug.log + -- ("before GC: " + -- ++ (printEnv env) + -- ) + -- "" + -- |> always ( Env.gc env, t expr ) ( Env.gc expr env, t expr ) else ( env, t expr ) @@ -172,3 +179,21 @@ ignore right left = right |> andThen (\_ -> succeed res) ) + + +withStack : Eval a -> Eval a +withStack e = + withEnv + (\env -> + e + |> ignore + (modifyEnv + (Env.restoreRefs env.stack) + ) + ) + + +pushRef : MalExpr -> Eval a -> Eval a +pushRef ref e = + modifyEnv (Env.pushRef ref) + |> andThen (always e) diff --git a/elm/Makefile b/elm/Makefile index d9724a99f6..e7ad0989a0 100644 --- a/elm/Makefile +++ b/elm/Makefile @@ -1,4 +1,4 @@ -SOURCES = step0_repl.elm step1_read_print.elm step2_eval.elm \ +SOURCES = stepA_mal.elm #step0_repl.elm step1_read_print.elm step2_eval.elm \ step3_env.elm step4_if_fn_do.elm step5_tco.elm step6_file.elm \ step7_quote.elm step8_macros.elm step9_try.elm stepA_mal.elm diff --git a/elm/Printer.elm b/elm/Printer.elm index 0da4e2dda7..6359a23925 100644 --- a/elm/Printer.elm +++ b/elm/Printer.elm @@ -2,7 +2,7 @@ module Printer exposing (..) import Array exposing (Array) import Dict exposing (Dict) -import Types exposing (Env, MalExpr(..), keywordPrefix) +import Types exposing (Env, MalExpr(..), keywordPrefix, MalFunction(..)) import Utils exposing (encodeString, wrap) import Env @@ -40,6 +40,18 @@ printString env readably ast = MalMap map -> printMap env readably map + MalFunction (UserFunc { frameId, meta }) -> + "# + "" + + Just meta -> + " meta=" ++ printString env True meta + ) + ++ ">" + MalFunction _ -> "#" @@ -50,8 +62,19 @@ printString env readably ast = in "(atom " ++ (printString env True value) ++ ")" - MalApply _ -> - "#" + MalApply { frameId, bound } -> + "#" + + +printBound : Env -> Bool -> List ( String, MalExpr ) -> String +printBound env readably = + let + printEntry name value = + name ++ "=" ++ (printString env readably value) + in + List.map (uncurry printEntry) + >> String.join " " + >> wrap "(" ")" printRawString : Env -> Bool -> String -> String @@ -107,11 +130,13 @@ printEnv env = printOuterId = Maybe.map toString >> Maybe.withDefault "nil" - printHeader frameId { outerId, refCnt } = + printHeader frameId { outerId, exitId, refCnt } = "#" ++ (toString frameId) ++ " outer=" ++ printOuterId outerId + ++ " exit=" + ++ printOuterId exitId ++ " refCnt=" ++ (toString refCnt) @@ -125,7 +150,7 @@ printEnv env = printFrame k v :: acc printDatum k v acc = - (k ++ " = " ++ (printString env True v)) :: acc + (k ++ " = " ++ (printString env False v)) :: acc in "--- Environment ---\n" ++ "Current frame: #" diff --git a/elm/Types.elm b/elm/Types.elm index e978f638c4..1cdcb110cc 100644 --- a/elm/Types.elm +++ b/elm/Types.elm @@ -16,6 +16,7 @@ type Msg type alias Frame = { outerId : Maybe Int + , exitId : Maybe Int , data : Dict String MalExpr , refCnt : Int } @@ -30,6 +31,7 @@ type alias Env = , debug : Bool , gcInterval : Int , gcCounter : Int + , stack : List MalExpr } diff --git a/elm/step4_if_fn_do.elm b/elm/step4_if_fn_do.elm index ed1af853b4..c769ffaaae 100644 --- a/elm/step4_if_fn_do.elm +++ b/elm/step4_if_fn_do.elm @@ -187,7 +187,7 @@ eval ast = fn args (MalFunction (UserFunc { eagerFn })) :: args -> - eagerFn args + eagerFn [] args fn :: _ -> Eval.withEnv @@ -427,7 +427,7 @@ evalFn args = (\env -> Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (always (eval body)) - |> Eval.finally (Env.leave env.currentFrameId) + |> Eval.finally Env.leave ) Err msg -> @@ -437,7 +437,7 @@ evalFn args = UserFunc { frameId = frameId , lazyFn = fn - , eagerFn = fn + , eagerFn = always fn , isMacro = False , meta = Nothing } diff --git a/elm/step5_tco.elm b/elm/step5_tco.elm index d00791ef4f..96a87b46cc 100644 --- a/elm/step5_tco.elm +++ b/elm/step5_tco.elm @@ -188,8 +188,8 @@ evalApply { frameId, bound, body } = (\env -> Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.finally (Env.leave env.currentFrameId) - |> Eval.gcPass + |> Eval.finally Env.leave + |> Eval.gcPass [] ) @@ -475,7 +475,7 @@ evalFn args = UserFunc { frameId = frameId , lazyFn = lazyFn - , eagerFn = lazyFn >> Eval.andThen eval + , eagerFn = \_ -> lazyFn >> Eval.andThen eval , isMacro = False , meta = Nothing } diff --git a/elm/step6_file.elm b/elm/step6_file.elm index 93d392888c..87643819ce 100644 --- a/elm/step6_file.elm +++ b/elm/step6_file.elm @@ -250,7 +250,7 @@ malEval args = (\env -> Eval.modifyEnv (Env.jump Env.globalFrameId) |> Eval.andThen (\_ -> eval expr) - |> Eval.finally (Env.jump env.currentFrameId) + |> Eval.finally Env.leave ) _ -> @@ -263,8 +263,8 @@ evalApply { frameId, bound, body } = (\env -> Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.finally (Env.leave env.currentFrameId) - |> Eval.gcPass + |> Eval.finally Env.leave + |> Eval.gcPass [] ) @@ -550,7 +550,7 @@ evalFn args = UserFunc { frameId = frameId , lazyFn = lazyFn - , eagerFn = lazyFn >> Eval.andThen eval + , eagerFn = \_ -> lazyFn >> Eval.andThen eval , isMacro = False , meta = Nothing } diff --git a/elm/step7_quote.elm b/elm/step7_quote.elm index 5cc67c1f53..8a09aaa50e 100644 --- a/elm/step7_quote.elm +++ b/elm/step7_quote.elm @@ -250,7 +250,7 @@ malEval args = (\env -> Eval.modifyEnv (Env.jump Env.globalFrameId) |> Eval.andThen (\_ -> eval expr) - |> Eval.finally (Env.jump env.currentFrameId) + |> Eval.finally Env.leave ) _ -> @@ -263,8 +263,8 @@ evalApply { frameId, bound, body } = (\env -> Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.finally (Env.leave env.currentFrameId) - |> Eval.gcPass + |> Eval.finally Env.leave + |> Eval.gcPass [] ) @@ -549,7 +549,7 @@ evalFn args = UserFunc { frameId = frameId , lazyFn = lazyFn - , eagerFn = lazyFn >> Eval.andThen eval + , eagerFn = \_ -> lazyFn >> Eval.andThen eval , isMacro = False , meta = Nothing } diff --git a/elm/step8_macros.elm b/elm/step8_macros.elm index 476b5da2db..adf9caa2f0 100644 --- a/elm/step8_macros.elm +++ b/elm/step8_macros.elm @@ -266,7 +266,7 @@ malEval args = (\env -> Eval.modifyEnv (Env.jump Env.globalFrameId) |> Eval.andThen (\_ -> eval expr) - |> Eval.finally (Env.jump env.currentFrameId) + |> Eval.finally Env.leave ) _ -> @@ -279,8 +279,8 @@ evalApply { frameId, bound, body } = (\env -> Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.finally (Env.leave env.currentFrameId) - |> Eval.gcPass + |> Eval.finally Env.leave + |> Eval.gcPass [] ) @@ -604,7 +604,7 @@ evalFn args = UserFunc { frameId = frameId , lazyFn = lazyFn - , eagerFn = lazyFn >> Eval.andThen eval + , eagerFn = \_ -> lazyFn >> Eval.andThen eval , isMacro = False , meta = Nothing } @@ -687,7 +687,7 @@ macroexpand expr = case Env.get name env of Ok (MalFunction (UserFunc fn)) -> if fn.isMacro then - Left <| fn.eagerFn args + Left <| fn.eagerFn [] args else Right expr diff --git a/elm/step9_try.elm b/elm/step9_try.elm index 20963eb2b6..ba886aae6d 100644 --- a/elm/step9_try.elm +++ b/elm/step9_try.elm @@ -266,7 +266,7 @@ malEval args = (\env -> Eval.modifyEnv (Env.jump Env.globalFrameId) |> Eval.andThen (\_ -> eval expr) - |> Eval.finally (Env.jump env.currentFrameId) + |> Eval.finally Env.leave ) _ -> @@ -279,8 +279,8 @@ evalApply { frameId, bound, body } = (\env -> Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.finally (Env.leave env.currentFrameId) - |> Eval.gcPass + |> Eval.finally Env.leave + |> Eval.gcPass [] ) @@ -606,7 +606,7 @@ evalFn args = UserFunc { frameId = frameId , lazyFn = lazyFn - , eagerFn = lazyFn >> Eval.andThen eval + , eagerFn = \_ -> lazyFn >> Eval.andThen eval , isMacro = False , meta = Nothing } @@ -689,7 +689,7 @@ macroexpand expr = case Env.get name env of Ok (MalFunction (UserFunc fn)) -> if fn.isMacro then - Left <| fn.eagerFn args + Left <| fn.eagerFn [] args else Right expr diff --git a/elm/stepA_mal.elm b/elm/stepA_mal.elm index b6f60bd579..478b7c0c40 100644 --- a/elm/stepA_mal.elm +++ b/elm/stepA_mal.elm @@ -277,7 +277,7 @@ malEval args = (\env -> Eval.modifyEnv (Env.jump Env.globalFrameId) |> Eval.andThen (\_ -> eval expr) - |> Eval.finally (Env.jump env.currentFrameId) + |> Eval.finally Env.leave ) _ -> @@ -290,7 +290,7 @@ evalApply { frameId, bound, body } = (\env -> Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) - |> Eval.finally (Env.leave env.currentFrameId) + |> Eval.finally Env.leave |> Eval.gcPass ) @@ -368,9 +368,14 @@ evalNoApply ast = _ -> evalAst ast in - debug "evalNoApply" - (\env -> printString env True ast) - (macroexpand ast |> Eval.andThen go) + macroexpand ast + |> Eval.andThen go + |> Eval.andThen + (\res -> + debug "evalNoApply" + (\env -> (printString env True ast) ++ " = " ++ (printString env True res)) + (Eval.succeed res) + ) evalAst : MalExpr -> Eval MalExpr @@ -413,10 +418,10 @@ evalList list = eval x |> Eval.andThen (\val -> - go rest (val :: acc) + Eval.pushRef val <| go rest (val :: acc) ) in - go list [] + Eval.withStack <| go list [] evalDef : List MalExpr -> Eval MalExpr diff --git a/mal/core.mal b/mal/core.mal index a766b80125..dbf7944c6d 100644 --- a/mal/core.mal +++ b/mal/core.mal @@ -60,4 +60,6 @@ ["atom?" atom?] ["deref" deref] ["reset!" reset!] - ["swap!" swap!]]) + ["swap!" swap!] + ["gc" gc] + ["pr-env" pr-env]]) From 55c252420126adc9c1b7bd5c4c2b270aa3303f1a Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Thu, 20 Jul 2017 11:36:36 +0000 Subject: [PATCH 0378/2308] tests: Add test cases in step4 and step9 --- tests/step4_if_fn_do.mal | 6 ++++++ tests/step9_try.mal | 2 ++ 2 files changed, 8 insertions(+) diff --git a/tests/step4_if_fn_do.mal b/tests/step4_if_fn_do.mal index 7b0b9a015a..0b69031ac1 100644 --- a/tests/step4_if_fn_do.mal +++ b/tests/step4_if_fn_do.mal @@ -120,6 +120,12 @@ ;=>false (= "abc" "ABC") ;=>false +(= true true) +;=>true +(= false false) +;=>true +(= nil nil) +;=>true (= (list) (list)) ;=>true diff --git a/tests/step9_try.mal b/tests/step9_try.mal index 2d82121e86..17cfae240e 100644 --- a/tests/step9_try.mal +++ b/tests/step9_try.mal @@ -55,6 +55,8 @@ ;=>nil (apply list (list)) ;=>() +(apply symbol? (list (quote two))) +;=>true ;; Testing apply function with user functions (apply (fn* (a b) (+ a b)) (list 2 3)) From 33a372916d6495fa9b111b9e13071ba5810d65b1 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Sun, 2 Apr 2017 10:46:45 +0000 Subject: [PATCH 0379/2308] Add rexx implementation --- Makefile | 3 +- rexx/.gitignore | 1 + rexx/Dockerfile | 26 ++ rexx/Makefile | 43 ++++ rexx/core.rexx | 498 +++++++++++++++++++++++++++++++++++++ rexx/env.rexx | 59 +++++ rexx/printer.rexx | 54 ++++ rexx/reader.rexx | 194 +++++++++++++++ rexx/readline.rexx | 8 + rexx/run | 2 + rexx/step0_repl.rexx | 23 ++ rexx/step1_read_print.rexx | 32 +++ rexx/step2_eval.rexx | 127 ++++++++++ rexx/step3_env.rexx | 151 +++++++++++ rexx/step4_if_fn_do.rexx | 176 +++++++++++++ rexx/step5_tco.rexx | 183 ++++++++++++++ rexx/step6_file.rexx | 214 ++++++++++++++++ rexx/step7_quote.rexx | 234 +++++++++++++++++ rexx/step8_macros.rexx | 265 ++++++++++++++++++++ rexx/step9_try.rexx | 286 +++++++++++++++++++++ rexx/stepA_mal.rexx | 291 ++++++++++++++++++++++ rexx/tests/step5_tco.mal | 2 + rexx/tests/stepA_mal.mal | 23 ++ rexx/types.rexx | 248 ++++++++++++++++++ 24 files changed, 3142 insertions(+), 1 deletion(-) create mode 100644 rexx/.gitignore create mode 100644 rexx/Dockerfile create mode 100644 rexx/Makefile create mode 100644 rexx/core.rexx create mode 100644 rexx/env.rexx create mode 100644 rexx/printer.rexx create mode 100644 rexx/reader.rexx create mode 100644 rexx/readline.rexx create mode 100755 rexx/run create mode 100644 rexx/step0_repl.rexx create mode 100644 rexx/step1_read_print.rexx create mode 100644 rexx/step2_eval.rexx create mode 100644 rexx/step3_env.rexx create mode 100644 rexx/step4_if_fn_do.rexx create mode 100644 rexx/step5_tco.rexx create mode 100644 rexx/step6_file.rexx create mode 100644 rexx/step7_quote.rexx create mode 100644 rexx/step8_macros.rexx create mode 100644 rexx/step9_try.rexx create mode 100644 rexx/stepA_mal.rexx create mode 100644 rexx/tests/step5_tco.mal create mode 100644 rexx/tests/stepA_mal.mal create mode 100644 rexx/types.rexx diff --git a/Makefile b/Makefile index 8bafcfee2f..805e3c4ca1 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ IMPLS = ada awk bash basic c d chuck clojure coffee common-lisp cpp crystal cs d erlang elisp elixir es6 factor forth fsharp go groovy gst guile haskell \ haxe io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ - python r racket rpython ruby rust scala skew swift swift3 tcl ts vb vhdl \ + python r racket rexx rpython ruby rust scala skew swift swift3 tcl ts vb vhdl \ vimscript livescript EXTENSION = .mal @@ -203,6 +203,7 @@ ps_STEP_TO_PROG = ps/$($(1)).ps python_STEP_TO_PROG = python/$($(1)).py r_STEP_TO_PROG = r/$($(1)).r racket_STEP_TO_PROG = racket/$($(1)).rkt +rexx_STEP_TO_PROG = rexx/$($(1)).rexx rpython_STEP_TO_PROG = rpython/$($(1)) ruby_STEP_TO_PROG = ruby/$($(1)).rb rust_STEP_TO_PROG = rust/target/release/$($(1)) diff --git a/rexx/.gitignore b/rexx/.gitignore new file mode 100644 index 0000000000..8b0a0636a6 --- /dev/null +++ b/rexx/.gitignore @@ -0,0 +1 @@ +*.rexxpp diff --git a/rexx/Dockerfile b/rexx/Dockerfile new file mode 100644 index 0000000000..83b666f13f --- /dev/null +++ b/rexx/Dockerfile @@ -0,0 +1,26 @@ +FROM ubuntu:xenial +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +RUN apt-get -y install cpp regina-rexx + +ENV HOME /mal diff --git a/rexx/Makefile b/rexx/Makefile new file mode 100644 index 0000000000..151ff038d6 --- /dev/null +++ b/rexx/Makefile @@ -0,0 +1,43 @@ +TESTS = + +SOURCES_BASE = readline.rexx types.rexx reader.rexx printer.rexx +SOURCES_LISP = env.rexx core.rexx stepA_mal.rexx +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +SRCS = step0_repl.rexx step1_read_print.rexx step2_eval.rexx step3_env.rexx \ + step4_if_fn_do.rexx step5_tco.rexx step6_file.rexx step7_quote.rexx \ + step8_macros.rexx step9_try.rexx stepA_mal.rexx +PREPROCESSED = $(SRCS:%.rexx=%.rexxpp) + +all: $(PREPROCESSED) dist + +dist: mal + +mal: mal.rexxpp + echo "#!/usr/bin/rexx -a" > $@ + cat $< >> $@ + chmod +x $@ + +mal.rexxpp: stepA_mal.rexxpp + cp -a $+ $@ + +$(PREPROCESSED): %.rexxpp: %.rexx readline.rexx types.rexx reader.rexx printer.rexx env.rexx core.rexx + cpp -CC -P -nostdinc $< > $@ + +clean: + rm -f mal.rexx mal *.rexxpp + +.PHONY: all dist clean stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" + +tests: $(TESTS) + +$(TESTS): + @echo "Running $@"; \ + rexx $@ || exit 1; \ diff --git a/rexx/core.rexx b/rexx/core.rexx new file mode 100644 index 0000000000..d458fcf03c --- /dev/null +++ b/rexx/core.rexx @@ -0,0 +1,498 @@ +#ifndef __core__ +#define __core__ + +#include "types.rexx" + +mal_equal?: procedure expose values. /* mal_equal?(a, b) */ + return new_boolean(equal?(arg(1), arg(2))) + +mal_throw: procedure expose values. err /* mal_throw(a) */ + err = "__MAL_EXCEPTION__" arg(1) + return "ERR" + +mal_nil?: procedure expose values. /* mal_nil?(a) */ + return new_boolean(nil?(arg(1))) + +mal_true?: procedure expose values. /* mal_true?(a) */ + return new_boolean(true?(arg(1))) + +mal_false?: procedure expose values. /* mal_false?(a) */ + return new_boolean(false?(arg(1))) + +mal_string?: procedure expose values. /* mal_string?(a) */ + return new_boolean(string?(arg(1))) + +mal_symbol: procedure expose values. /* mal_symbol(a) */ + return new_symbol(obj_val(arg(1))) + +mal_symbol?: procedure expose values. /* mal_symbol?(a) */ + return new_boolean(symbol?(arg(1))) + +mal_keyword: procedure expose values. /* mal_keyword(a) */ + return new_keyword(obj_val(arg(1))) + +mal_keyword?: procedure expose values. /* mal_keyword?(a) */ + return new_boolean(keyword?(arg(1))) + +mal_pr_str: procedure expose values. /* mal_pr_str(...) */ + res = "" + do i=1 to arg() + element = pr_str(arg(i), 1) + if i == 1 then + res = element + else + res = res || " " || element + end + return new_string(res) + +mal_str: procedure expose values. /* mal_str(...) */ + res = "" + do i=1 to arg() + element = pr_str(arg(i), 0) + if i == 1 then + res = element + else + res = res || element + end + return new_string(res) + +mal_prn: procedure expose values. /* mal_prn(...) */ + res = "" + do i=1 to arg() + element = pr_str(arg(i), 1) + if i == 1 then + res = element + else + res = res || " " || element + end + say res + return new_nil() + +mal_println: procedure expose values. /* mal_println(...) */ + res = "" + do i=1 to arg() + element = pr_str(arg(i), 0) + if i == 1 then + res = element + else + res = res || " " || element + end + say res + return new_nil() + +mal_read_string: procedure expose values. /* mal_read_string(str) */ + return read_str(obj_val(arg(1))) + +mal_readline: procedure expose values. /* mal_readline(prompt) */ + line = readline(obj_val(arg(1))) + if length(line) > 0 then return new_string(line) + if lines() > 0 then return new_string("") + return new_nil() + +mal_slurp: procedure expose values. /* mal_read_string(filename) */ + file_content = charin(obj_val(arg(1)), , 100000) + return new_string(file_content) + +mal_lt: procedure expose values. /* mal_lt(a, b) */ + return new_boolean(obj_val(arg(1)) < obj_val(arg(2))) + +mal_lte: procedure expose values. /* mal_lte(a, b) */ + return new_boolean(obj_val(arg(1)) <= obj_val(arg(2))) + +mal_gt: procedure expose values. /* mal_gt(a, b) */ + return new_boolean(obj_val(arg(1)) > obj_val(arg(2))) + +mal_gte: procedure expose values. /* mal_gte(a, b) */ + return new_boolean(obj_val(arg(1)) >= obj_val(arg(2))) + +mal_add: procedure expose values. /* mal_add(a, b) */ + return new_number(obj_val(arg(1)) + obj_val(arg(2))) + +mal_sub: procedure expose values. /* mal_sub(a, b) */ + return new_number(obj_val(arg(1)) - obj_val(arg(2))) + +mal_mul: procedure expose values. /* mal_mul(a, b) */ + return new_number(obj_val(arg(1)) * obj_val(arg(2))) + +mal_div: procedure expose values. /* mal_div(a, b) */ + return new_number(obj_val(arg(1)) / obj_val(arg(2))) + +mal_time_ms: procedure expose values. /* mal_time_ms() */ + return new_number(trunc(time('E') * 1000)) + +mal_list: procedure expose values. /* mal_list(...) */ + res = "" + do i=1 to arg() + if i == 1 then + res = arg(i) + else + res = res || " " || arg(i) + end + return new_list(res) + +mal_list?: procedure expose values. /* mal_list?(a) */ + return new_boolean(list?(arg(1))) + +mal_vector: procedure expose values. /* mal_vector(...) */ + res = "" + do i=1 to arg() + if i == 1 then + res = arg(i) + else + res = res || " " || arg(i) + end + return new_vector(res) + +mal_vector?: procedure expose values. /* mal_vector?(a) */ + return new_boolean(vector?(arg(1))) + +mal_hash_map: procedure expose values. /* mal_hash_map(...) */ + res = "" + do i=1 to arg() + if i == 1 then + res = arg(i) + else + res = res || " " || arg(i) + end + return new_hashmap(res) + +mal_map?: procedure expose values. /* mal_map?(a) */ + return new_boolean(hashmap?(arg(1))) + +mal_assoc: procedure expose values. /* mal_assoc(a, ...) */ + hm = arg(1) + res = "" + do i=2 to arg() by 2 + key_val = arg(i) || " " || arg(i + 1) + if res == 2 then + res = key_val + else + res = res || " " || key_val + end + hm_val = obj_val(hm) + do i=1 to words(hm_val) by 2 + if \contains?(res, word(hm_val, i)) then + res = res || " " || word(hm_val, i) || " " || word(hm_val, i + 1) + end + return new_hashmap(res) + +mal_dissoc: procedure expose values. /* mal_dissoc(a, ...) */ + hm = arg(1) + res = "" + hm_val = obj_val(hm) + do i=1 to words(hm_val) by 2 + key = word(hm_val, i) + found = 0 + do j=2 to arg() + if equal?(key, arg(j)) then do + found = 1 + leave + end + end + if \found then do + if length(res) > 0 then res = res || " " + res = res || key || " " || word(hm_val, i + 1) + end + end + return new_hashmap(res) + +mal_get: procedure expose values. /* mal_get(a, b) */ + res = hashmap_get(obj_val(arg(1)), arg(2)) + if res == "" then + return new_nil() + else + return res + +mal_contains?: procedure expose values. /* mal_contains?(a, b) */ + return new_boolean(contains?(obj_val(arg(1)), arg(2))) + +mal_keys: procedure expose values. /* mal_keys(a) */ + hm_val = obj_val(arg(1)) + seq = "" + do i=1 to words(hm_val) by 2 + if i == 1 then + seq = word(hm_val, i) + else + seq = seq || " " || word(hm_val, i) + end + return new_list(seq) + +mal_vals: procedure expose values. /* mal_vals(a) */ + hm_val = obj_val(arg(1)) + seq = "" + do i=2 to words(hm_val) by 2 + if i == 1 then + seq = word(hm_val, i) + else + seq = seq || " " || word(hm_val, i) + end + return new_list(seq) + +mal_sequential?: procedure expose values. /* mal_sequential?(a) */ + return new_boolean(sequential?(arg(1))) + +mal_cons: procedure expose values. /* mal_cons(a, b) */ + return new_list(arg(1) || " " || obj_val(arg(2))) + +mal_concat: procedure expose values. /* mal_concat(...) */ + seq = "" + do i=1 to arg() + if i == 1 then + seq = obj_val(arg(i)) + else + seq = seq || " " || obj_val(arg(i)) + end + return new_list(seq) + +mal_nth: procedure expose values. err /* mal_nth(list, index) */ + list_val = obj_val(arg(1)) + i = obj_val(arg(2)) + if i >= words(list_val) then do + err = "nth: index out of range" + return "ERR" + end + return word(list_val, i + 1) + +mal_first: procedure expose values. /* mal_first(a) */ + if nil?(arg(1)) then return new_nil() + list_val = obj_val(arg(1)) + if words(list_val) == 0 then return new_nil() + return word(list_val, 1) + +mal_rest: procedure expose values. /* mal_rest(a) */ + return new_list(subword(obj_val(arg(1)), 2)) + +mal_empty?: procedure expose values. /* mal_empty?(a) */ + if nil?(arg(1)) then return new_true() + return new_boolean(count_elements(arg(1)) == 0) + +mal_count: procedure expose values. /* mal_count(a) */ + if nil?(arg(1)) then return new_number(0) + return new_number(count_elements(arg(1))) + +apply_function: procedure expose values. env. err /* apply_function(fn, lst) */ + f = arg(1) + call_args = arg(2) + select + when nativefn?(f) then do + call_args_val = obj_val(call_args) + call_list = "" + do i=1 to words(call_args_val) + element = '"' || word(call_args_val, i) || '"' + if i > 1 then + call_list = call_list || ', ' || element + else + call_list = element + end + res = "" + interpret "res = " || obj_val(f) || "(" || call_list || ")" + return res + end + when func?(f) then do + apply_env_idx = new_env(func_env_idx(f), func_binds(f), call_args) + return eval(func_body_ast(f), apply_env_idx) + end + otherwise + err = "Unsupported function object type: " || obj_type(f) + return "ERR" + end + +mal_apply: procedure expose values. env. err /* mal_apply(fn, ..., lst) */ + fn = arg(1) + seq = "" + do i=2 to (arg() - 1) + if i == 2 then + seq = arg(i) + else + seq = seq || " " || arg(i) + end + if arg() > 1 then do + seq = seq || " " || obj_val(arg(arg())) + end + return apply_function(fn, new_list(seq)) + +mal_map: procedure expose values. env. err /* mal_map(f, lst) */ + fn = arg(1) + lst_val = obj_val(arg(2)) + res = "" + do i=1 to words(lst_val) + element = word(lst_val, i) + mapped_element = apply_function(fn, new_list(element)) + if mapped_element == "ERR" then return "ERR" + if i == 1 then + res = mapped_element + else + res = res || " " || mapped_element + end + return new_list(res) + +mal_conj: procedure expose values. env. err /* mal_conj(a, ...) */ + a = arg(1) + select + when list?(a) then do + do i=2 to arg() + a = mal_cons(arg(i), a) + end + return a + end + when vector?(a) then do + seq = obj_val(a) + do i=2 to arg() + if length(seq) > 0 then seq = seq || " " + seq = seq || arg(i) + end + return new_vector(seq) + end + otherwise + err = "conj requires list or vector" + return "ERR" + end + +mal_seq: procedure expose values. env. err /* mal_conj(a) */ + a = arg(1) + select + when string?(a) then do + str = obj_val(a) + if length(str) == 0 then return new_nil() + seq = "" + do i=1 to length(str) + element = new_string(substr(str, i, 1)) + if i == 1 then + seq = element + else + seq = seq || " " || element + end + return new_list(seq) + end + when list?(a) then do + if count_elements(a) == 0 then return new_nil() + return a + end + when vector?(a) then do + if count_elements(a) == 0 then return new_nil() + return new_list(obj_val(a)) + end + when nil?(a) then return new_nil() + otherwise + err = "seq requires string or list or vector or nil" + return "ERR" + end + +mal_with_meta: procedure expose values. /* mal_with_meta(a, b) */ + new_obj = obj_clone_and_set_meta(arg(1), arg(2)) + if new_obj == "" then return arg(1) + return new_obj + +mal_meta: procedure expose values. /* mal_meta(a) */ + meta = obj_meta(arg(1)) + if meta == "" then return new_nil() + return meta + +mal_atom: procedure expose values. /* mal_atom(a) */ + return new_atom(arg(1)) + +mal_atom?: procedure expose values. /* mal_atom?(a) */ + return new_boolean(atom?(arg(1))) + +mal_deref: procedure expose values. /* mal_deref(a) */ + return obj_val(arg(1)) + +mal_reset!: procedure expose values. /* mal_reset!(a, new_val) */ + return atom_set(arg(1), arg(2)) + +mal_swap!: procedure expose values. env. err /* mal_swap!(a, fn, ...) */ + atom = arg(1) + fn = arg(2) + atom_val = obj_val(atom) + seq = atom_val + do i=3 to arg() + seq = seq || " " || arg(i) + end + new_val = apply_function(fn, new_list(seq)) + if new_val == "ERR" then return "ERR" + return atom_set(atom, new_val) + +mal_rexx_eval: procedure expose values. /* mal_rexx_eval(..., a) */ + do i=1 to (arg() - 1) + interpret obj_val(arg(i)) + end + last_arg = arg(arg()) + if nil?(last_arg) then return new_nil() + last_arg_str = obj_val(last_arg) + if length(last_arg_str) == 0 then return new_nil() + rexx_eval_res = "" + interpret "rexx_eval_res = " || last_arg_str + if datatype(rexx_eval_res) == "NUM" then + return new_number(rexx_eval_res) + else + return new_string(rexx_eval_res) + +get_core_ns: procedure /* get_core_ns() */ + return "= mal_equal?" , + "throw mal_throw" , + , + "nil? mal_nil?" , + "true? mal_true?" , + "false? mal_false?" , + "string? mal_string?" , + "symbol mal_symbol" , + "symbol? mal_symbol?" , + "keyword mal_keyword" , + "keyword? mal_keyword?" , + , + "pr-str mal_pr_str" , + "str mal_str" , + "prn mal_prn" , + "println mal_println" , + "read-string mal_read_string" , + "readline mal_readline" , + "slurp mal_slurp" , + , + "< mal_lt" , + "<= mal_lte" , + "> mal_gt" , + ">= mal_gte" , + "+ mal_add" , + "- mal_sub" , + "* mal_mul" , + "/ mal_div" , + "time-ms mal_time_ms" , + , + "list mal_list" , + "list? mal_list?" , + "vector mal_vector" , + "vector? mal_vector?" , + "hash-map mal_hash_map" , + "map? mal_map?" , + "assoc mal_assoc" , + "dissoc mal_dissoc" , + "get mal_get" , + "contains? mal_contains?" , + "keys mal_keys" , + "vals mal_vals" , + , + "sequential? mal_sequential?" , + "cons mal_cons" , + "concat mal_concat" , + "nth mal_nth" , + "first mal_first" , + "rest mal_rest" , + "empty? mal_empty?" , + "count mal_count" , + "apply mal_apply" , + "map mal_map" , + , + "conj mal_conj" , + "seq mal_seq" , + , + "meta mal_meta" , + "with-meta mal_with_meta" , + "atom mal_atom" , + "atom? mal_atom?" , + "deref mal_deref" , + "reset! mal_reset!" , + "swap! mal_swap!" , + , + "rexx-eval mal_rexx_eval" + +#endif diff --git a/rexx/env.rexx b/rexx/env.rexx new file mode 100644 index 0000000000..4dec7e62bf --- /dev/null +++ b/rexx/env.rexx @@ -0,0 +1,59 @@ +#ifndef __env__ +#define __env__ + +env. = "" +env.0 = 0 + +new_env_index: procedure expose env. /* new_env_index() */ + env.0 = env.0 + 1 + return env.0 + +new_env: procedure expose env. values. /* new_env(outer_env_idx [, binds, exprs]) */ + outer_env_idx = arg(1) + binds = arg(2) + exprs = arg(3) + idx = new_env_index() + env.idx.outer = outer_env_idx + env.idx.data. = "" + if binds \= "" then do + binds_val = obj_val(binds) + exprs_val = obj_val(exprs) + do i=1 to words(binds_val) + varname = obj_val(word(binds_val, i)) + if varname == "&" then do + rest_args_list = new_list(subword(exprs_val, i)) + varname = obj_val(word(binds_val, i + 1)) + x = env_set(idx, varname, rest_args_list) + leave + end + else + x = env_set(idx, varname, word(exprs_val, i)) + end + end + return idx + +env_set: procedure expose env. /* env_set(env_idx, key, val) */ + env_idx = arg(1) + key = arg(2) + val = arg(3) + env.env_idx.data.key = val + return val + +env_find: procedure expose env. /* env_find(env_idx, key) */ + env_idx = arg(1) + key = arg(2) + if env.env_idx.data.key \= "" then return env_idx + if env.env_idx.outer > 0 then return env_find(env.env_idx.outer, key) + return 0 + +env_get: procedure expose env. err /* env_get(env_idx, key) */ + env_idx = arg(1) + key = arg(2) + found_env_idx = env_find(env_idx, key) + if found_env_idx == 0 then do + err = "'" || key || "' not found" + return "ERR" + end + return env.found_env_idx.data.key + +#endif diff --git a/rexx/printer.rexx b/rexx/printer.rexx new file mode 100644 index 0000000000..e7922ef0ab --- /dev/null +++ b/rexx/printer.rexx @@ -0,0 +1,54 @@ +#ifndef __printer__ +#define __printer__ + +#include "types.rexx" + +format_string: procedure /* format_string(str, readable) */ + str = arg(1) + readable = arg(2) + if readable then do + res = changestr('5C'x, str, "\\") + res = changestr('"', res, '\"') + res = changestr('0A'x, res, "\n") + return '"' || res || '"' + end + else + return str + +format_sequence: procedure expose values. /* format_sequence(val, open_char, close_char, readable) */ + val = arg(1) + open_char = arg(2) + close_char = arg(3) + readable = arg(4) + res = "" + do i=1 to words(val) + element = word(val, i) + if i > 1 then res = res || " " + res = res || pr_str(element, readable) + end + return open_char || res || close_char + +pr_str: procedure expose values. /* pr_str(ast, readable) */ + ast = arg(1) + readable = arg(2) + type = obj_type(ast) + val = obj_val(ast) + select + when type == "nill" then return "nil" + when type == "true" then return "true" + when type == "fals" then return "false" + when type == "numb" then return val + when type == "symb" then return val + when type == "stri" then return format_string(val, readable) + when type == "keyw" then return ":" || val + when type == "list" then return format_sequence(val, "(", ")", readable) + when type == "vect" then return format_sequence(val, "[", "]", readable) + when type == "hash" then return format_sequence(val, "{", "}", readable) + when type == "nafn" then return "#" + when type == "func" then return "#" + when type == "atom" then return "(atom " || pr_str(val, readable) || ")" + otherwise + return "#" + end + +#endif diff --git a/rexx/reader.rexx b/rexx/reader.rexx new file mode 100644 index 0000000000..3afc8620d0 --- /dev/null +++ b/rexx/reader.rexx @@ -0,0 +1,194 @@ +#ifndef __reader__ +#define __reader__ + +#include "types.rexx" + +next_token: procedure expose pos /* next_token(str) */ + TAB = '09'x + LF = '0A'x + CR = '0D'x + SEPARATOR_CHARS = TAB || LF || CR || " []{}()'`,;" || '"' + WHITESPACE_CHARS = TAB || LF || CR || " ," + str = arg(1) + token = "" + ch = substr(str, pos, 1) + select + when pos(ch, WHITESPACE_CHARS) > 0 then + pos = pos + 1 + when pos(ch, "[]{}()'`^@") > 0 then do + pos = pos + 1 + token = ch + end + when ch == '~' then do + if substr(str, pos + 1, 1) == '@' then do + pos = pos + 2 + token = "~@" + end + else do + pos = pos + 1 + token = "~" + end + end + when ch == ";" then do + do while pos <= length(str) + ch = substr(str, pos, 1) + if (ch == LF) | (ch == CR) then + leave + else + pos = pos + 1 + end + end + when ch == '"' then do + tmppos = pos + 1 + do while tmppos < length(str) + ch = substr(str, tmppos, 1) + select + when ch == '"' then + leave + when ch == '5C'x then /* backslash */ + tmppos = tmppos + 2 + otherwise + tmppos = tmppos + 1 + end + end + token = substr(str, pos, tmppos - pos + 1) + pos = tmppos + 1 + end + otherwise + tmppos = pos + do while tmppos <= length(str) + ch = substr(str, tmppos, 1) + if pos(ch, SEPARATOR_CHARS) > 0 then + leave + else + token = token || ch + tmppos = tmppos + 1 + end + pos = tmppos + end + return token + +tokenize: procedure expose tokens. /* tokenize(str) */ + str = arg(1) + tokens. = "" + num_of_tokens = 0 + str_to_tokenize = str + pos = 1 + do while pos <= length(str) + token = next_token(str_to_tokenize) + if length(token) > 0 then do + num_of_tokens = num_of_tokens + 1 + tokens.num_of_tokens = token + end + end + tokens.0 = num_of_tokens + return num_of_tokens + +is_number: procedure /* is_number(token) */ + token = arg(1) + ch = substr(token, 1, 1) + DIGITS = "0123456789" + if pos(ch, DIGITS) > 0 then return 1 + if (ch == '-') & (pos(substr(token, 2, 1), DIGITS) > 0) then return 1 + return 0 + +parse_string: procedure /* parse_string(token) */ + token = arg(1) + res = substr(token, 2, length(token) - 2) /* Remove quotes */ + res = changestr("\n", res, '0A'x) + res = changestr('\"', res, '"') + res = changestr("\\", res, '5C'x) + return res + +parse_keyword: procedure /* parse_keyword(token) */ + token = arg(1) + return substr(token, 2) /* Remove initial ":" */ + +read_atom: procedure expose values. tokens. pos /* read_atom() */ + token = tokens.pos + pos = pos + 1 + select + when is_number(token) then return new_number(token) + when token == "nil" then return new_nil() + when token == "true" then return new_true() + when token == "false" then return new_false() + when substr(token, 1, 1) == ':' then return new_keyword(parse_keyword(token)) + when substr(token, 1, 1) == '"' then return new_string(parse_string(token)) + otherwise + return new_symbol(token) + end + +read_sequence: procedure expose values. tokens. pos /* read_sequence(type, end_char) */ + type = arg(1) + end_char = arg(2) + pos = pos + 1 /* Consume the open paren */ + token = tokens.pos + seq = "" + do while (pos <= tokens.0) & (token \== end_char) + element = read_form() + if element == "ERR" then return "ERR" + if seq == "" then + seq = element + else + seq = seq || " " || element + token = tokens.pos + end + pos = pos + 1 /* Consume the close paren */ + return new_seq(type, seq) + +reader_macro: procedure expose values. tokens. pos /* reader_macro(symbol) */ + symbol = arg(1) + pos = pos + 1 /* Consume the macro token */ + element = read_form() + if element == "ERR" then return "ERR" + seq = new_symbol(symbol) || " " || element + return new_list(seq) + +reader_with_meta_macro: procedure expose values. tokens. pos /* reader_with_meta_macro() */ + pos = pos + 1 /* Consume the macro token */ + meta = read_form() + if meta == "ERR" then return "ERR" + element = read_form() + if element == "ERR" then return "ERR" + seq = new_symbol("with-meta") || " " || element || " " || meta + return new_list(seq) + +read_form: procedure expose values. tokens. pos err /* read_form() */ + token = tokens.pos + select + when token == "'" then return reader_macro("quote") + when token == '`' then return reader_macro("quasiquote") + when token == '~' then return reader_macro("unquote") + when token == '~@' then return reader_macro("splice-unquote") + when token == '@' then return reader_macro("deref") + when token == '^' then return reader_with_meta_macro() + when token == '(' then return read_sequence("list", ")") + when token == ')' then do + err = "unexpected ')'" + return "ERR" + end + when token == '[' then return read_sequence("vect", "]") + when token == ']' then do + err = "unexpected ']'" + return "ERR" + end + when token == '{' then return read_sequence("hash", "}") + when token == '}' then do + err = "unexpected '}'" + return "ERR" + end + otherwise + return read_atom() + end + +read_str: procedure expose values. err /* read_str(line) */ + line = arg(1) + tokens. = "" + num_of_tokens = tokenize(line) + if num_of_tokens == 0 then + return "" + ast. = "" + pos = 1 + return read_form() + +#endif diff --git a/rexx/readline.rexx b/rexx/readline.rexx new file mode 100644 index 0000000000..4482bd7e5b --- /dev/null +++ b/rexx/readline.rexx @@ -0,0 +1,8 @@ +#ifndef __readline__ +#define __readline__ + +readline: procedure /* readline(prompt) */ + call charout , arg(1) + return linein() + +#endif diff --git a/rexx/run b/rexx/run new file mode 100755 index 0000000000..f792d597f3 --- /dev/null +++ b/rexx/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec rexx -a $(dirname $0)/${STEP:-stepA_mal}.rexxpp "${@}" diff --git a/rexx/step0_repl.rexx b/rexx/step0_repl.rexx new file mode 100644 index 0000000000..7ae0168c1c --- /dev/null +++ b/rexx/step0_repl.rexx @@ -0,0 +1,23 @@ +call main +exit + +#include "readline.rexx" + +read: procedure /* read(str) */ + return arg(1) + +eval: procedure /* eval(exp, env) */ + return arg(1) + +print: procedure /* print(exp) */ + return arg(1) + +rep: procedure /* rep(str) */ + return print(eval(read(arg(1), ""))) + +main: + do while lines() > 0 /* 1 == 1 */ + input_line = readline('user> ') + if length(input_line) > 0 then + call lineout , rep(input_line) + end diff --git a/rexx/step1_read_print.rexx b/rexx/step1_read_print.rexx new file mode 100644 index 0000000000..eb697ff075 --- /dev/null +++ b/rexx/step1_read_print.rexx @@ -0,0 +1,32 @@ +call main +exit + +#include "readline.rexx" +#include "reader.rexx" +#include "printer.rexx" + +read: procedure expose values. /* read(str) */ + return read_str(arg(1)) + +eval: procedure expose values. /* eval(exp, env) */ + return arg(1) + +print: procedure expose values. /* print(exp) */ + return pr_str(arg(1), 1) + +rep: procedure expose values. /* rep(str) */ + return print(eval(read(arg(1), ""))) + +main: + values. = "" + values.0 = 0 + do while lines() > 0 /* 1 == 1 */ + input_line = readline('user> ') + if length(input_line) > 0 then do + res = rep(input_line) + if res == "ERR" then + call lineout , "Error: " || err + else + call lineout , res + end + end diff --git a/rexx/step2_eval.rexx b/rexx/step2_eval.rexx new file mode 100644 index 0000000000..6fffd80acd --- /dev/null +++ b/rexx/step2_eval.rexx @@ -0,0 +1,127 @@ +call main +exit + +#include "readline.rexx" +#include "reader.rexx" +#include "printer.rexx" +#include "types.rexx" + +read: procedure expose values. err /* read(str) */ + return read_str(arg(1)) + +eval_ast: procedure expose values. env. err /* eval_ast(ast) */ + ast = arg(1) + type = obj_type(ast) + val = obj_val(ast) + select + when type == "symb" then do + varname = val + if env.varname == "" then do + err = "'" || varname || "' not found" + return "ERR" + end + return env.varname + end + when type == "list" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i)) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_list(res) + end + when type == "vect" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i)) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_vector(res) + end + when type == "hash" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i)) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_hashmap(res) + end + otherwise + return ast + end + +eval: procedure expose values. env. err /* eval(ast) */ + ast = arg(1) + if \list?(ast) then return eval_ast(ast) + astval = obj_val(ast) + if words(astval) == 0 then return ast + lst_obj = eval_ast(ast) + if lst_obj == "ERR" then return "ERR" + lst = obj_val(lst_obj) + f = word(lst, 1) + call_args = subword(lst, 2) + call_list = "" + do i=1 to words(call_args) + element = '"' || word(call_args, i) || '"' + if i > 1 then + call_list = call_list || ', ' || element + else + call_list = element + end + res = "" + interpret "res = " || f || "(" || call_list || ")" + return res + +print: procedure expose values. /* print(ast) */ + return pr_str(arg(1), 1) + +rep: procedure expose values. env. err /* rep(str) */ + ast = read(arg(1)) + if ast == "ERR" then return "ERR" + exp = eval(ast) + if exp == "ERR" then return "ERR" + return print(exp) + +mal_add: procedure expose values. /* mal_add(a, b) */ + return new_number(obj_val(arg(1)) + obj_val(arg(2))) + +mal_sub: procedure expose values. /* mal_sub(a, b) */ + return new_number(obj_val(arg(1)) - obj_val(arg(2))) + +mal_mul: procedure expose values. /* mal_mul(a, b) */ + return new_number(obj_val(arg(1)) * obj_val(arg(2))) + +mal_div: procedure expose values. /* mal_div(a, b) */ + return new_number(obj_val(arg(1)) / obj_val(arg(2))) + +main: + values. = "" + values.0 = 0 + env. = "" + key = "+" ; env.key = "mal_add" + key = "-" ; env.key = "mal_sub" + key = "*" ; env.key = "mal_mul" + key = "/" ; env.key = "mal_div" + err = "" + do while lines() > 0 /* 1 == 1 */ + input_line = readline('user> ') + if length(input_line) > 0 then do + res = rep(input_line) + if res == "ERR" then + call lineout , "Error: " || err + else + call lineout , res + end + end diff --git a/rexx/step3_env.rexx b/rexx/step3_env.rexx new file mode 100644 index 0000000000..839e2fe68c --- /dev/null +++ b/rexx/step3_env.rexx @@ -0,0 +1,151 @@ +call main +exit + +#include "readline.rexx" +#include "reader.rexx" +#include "printer.rexx" +#include "types.rexx" +#include "env.rexx" + +read: procedure expose values. err /* read(str) */ + return read_str(arg(1)) + +eval_ast: procedure expose values. env. err /* eval_ast(ast, env_idx) */ + ast = arg(1) + env_idx = arg(2) + type = obj_type(ast) + val = obj_val(ast) + select + when type == "symb" then return env_get(env_idx, val) + when type == "list" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_list(res) + end + when type == "vect" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_vector(res) + end + when type == "hash" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_hashmap(res) + end + otherwise + return ast + end + +eval: procedure expose values. env. err /* eval(ast) */ + ast = arg(1) + env_idx = arg(2) + if \list?(ast) then return eval_ast(ast, env_idx) + astval = obj_val(ast) + if words(astval) == 0 then return ast + a0sym = obj_val(word(astval, 1)) + select + when a0sym == "def!" then do + a1sym = obj_val(word(astval, 2)) + a2 = eval(word(astval, 3), env_idx) + if a2 == "ERR" then return "ERR" + return env_set(env_idx, a1sym, a2) + end + when a0sym == "let*" then do + a1lst = obj_val(word(astval, 2)) + letenv_idx = new_env(env_idx) + do i=1 to words(a1lst) by 2 + k = obj_val(word(a1lst, i)) + v = eval(word(a1lst, i + 1), letenv_idx) + if v == "ERR" then return "ERR" + unused = env_set(letenv_idx, k, v) + end + return eval(word(astval, 3), letenv_idx) + end + otherwise + lst_obj = eval_ast(ast, env_idx) + if lst_obj == "ERR" then return "ERR" + lst = obj_val(lst_obj) + f = word(lst, 1) + call_args = subword(lst, 2) + call_list = "" + do i=1 to words(call_args) + element = '"' || word(call_args, i) || '"' + if i > 1 then + call_list = call_list || ', ' || element + else + call_list = element + end + res = "" + interpret "res = " || f || "(" || call_list || ")" + return res + end + +print: procedure expose values. /* print(ast) */ + return pr_str(arg(1), 1) + +re: procedure expose values. env. err repl_env_idx /* re(str) */ + str = arg(1) + ast = read(str) + if ast == "ERR" then return "ERR" + return eval(ast, repl_env_idx) + +rep: procedure expose values. env. err repl_env_idx /* rep(str) */ + str = arg(1) + exp = re(str) + if exp == "ERR" then return "ERR" + return print(exp) + +mal_add: procedure expose values. /* mal_add(a, b) */ + return new_number(obj_val(arg(1)) + obj_val(arg(2))) + +mal_sub: procedure expose values. /* mal_sub(a, b) */ + return new_number(obj_val(arg(1)) - obj_val(arg(2))) + +mal_mul: procedure expose values. /* mal_mul(a, b) */ + return new_number(obj_val(arg(1)) * obj_val(arg(2))) + +mal_div: procedure expose values. /* mal_div(a, b) */ + return new_number(obj_val(arg(1)) / obj_val(arg(2))) + +main: + values. = "" + values.0 = 0 + env. = "" + env.0 = 0 + repl_env_idx = new_env(0) + x = env_set(repl_env_idx, "+", "mal_add") + x = env_set(repl_env_idx, "-", "mal_sub") + x = env_set(repl_env_idx, "*", "mal_mul") + x = env_set(repl_env_idx, "/", "mal_div") + err = "" + do while lines() > 0 /* 1 == 1 */ + input_line = readline('user> ') + if length(input_line) > 0 then do + res = rep(input_line) + if res == "ERR" then + call lineout , "Error: " || err + else + call lineout , res + end + end diff --git a/rexx/step4_if_fn_do.rexx b/rexx/step4_if_fn_do.rexx new file mode 100644 index 0000000000..564fbed84e --- /dev/null +++ b/rexx/step4_if_fn_do.rexx @@ -0,0 +1,176 @@ +call main +exit + +#include "readline.rexx" +#include "reader.rexx" +#include "printer.rexx" +#include "types.rexx" +#include "env.rexx" +#include "core.rexx" + +read: procedure expose values. err /* read(str) */ + return read_str(arg(1)) + +eval_ast: procedure expose values. env. err /* eval_ast(ast, env_idx) */ + ast = arg(1) + env_idx = arg(2) + type = obj_type(ast) + val = obj_val(ast) + select + when type == "symb" then return env_get(env_idx, val) + when type == "list" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_list(res) + end + when type == "vect" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_vector(res) + end + when type == "hash" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_hashmap(res) + end + otherwise + return ast + end + +eval: procedure expose values. env. err /* eval(ast) */ + ast = arg(1) + env_idx = arg(2) + if \list?(ast) then return eval_ast(ast, env_idx) + astval = obj_val(ast) + if words(astval) == 0 then return ast + a0sym = obj_val(word(astval, 1)) + select + when a0sym == "def!" then do + a1sym = obj_val(word(astval, 2)) + a2 = eval(word(astval, 3), env_idx) + if a2 == "ERR" then return "ERR" + return env_set(env_idx, a1sym, a2) + end + when a0sym == "let*" then do + a1lst = obj_val(word(astval, 2)) + letenv_idx = new_env(env_idx) + do i=1 to words(a1lst) by 2 + k = obj_val(word(a1lst, i)) + v = eval(word(a1lst, i + 1), letenv_idx) + if v == "ERR" then return "ERR" + unused = env_set(letenv_idx, k, v) + end + return eval(word(astval, 3), letenv_idx) + end + when a0sym == "do" then do + res = "ERR" + do i=2 to words(astval) + res = eval(word(astval, i), env_idx) + if res == "ERR" then return "ERR" + end + return res + end + when a0sym == "if" then do + condval = eval(word(astval, 2), env_idx) + if false?(condval) | nil?(condval) then + if words(astval) >= 4 then + return eval(word(astval, 4), env_idx) + else + return new_nil() + else + return eval(word(astval, 3), env_idx) + end + when a0sym == "fn*" then return new_func(word(astval, 3), env_idx, word(astval, 2)) + otherwise + lst_obj = eval_ast(ast, env_idx) + if lst_obj == "ERR" then return "ERR" + lst = obj_val(lst_obj) + f = word(lst, 1) + select + when nativefn?(f) then do + call_args = subword(lst, 2) + call_list = "" + do i=1 to words(call_args) + element = '"' || word(call_args, i) || '"' + if i > 1 then + call_list = call_list || ', ' || element + else + call_list = element + end + res = "" + interpret "res = " || obj_val(f) || "(" || call_list || ")" + return res + end + when func?(f) then do + call_args = new_list(subword(lst, 2)) + return eval(func_body_ast(f), new_env(func_env_idx(f), func_binds(f), call_args)) + end + otherwise + err = "Unsupported function object type: " || obj_type(f) + return "ERR" + end + end + +print: procedure expose values. /* print(ast) */ + return pr_str(arg(1), 1) + +re: procedure expose values. env. err repl_env_idx /* re(str) */ + str = arg(1) + ast = read(str) + if ast == "ERR" then return "ERR" + return eval(ast, repl_env_idx) + +rep: procedure expose values. env. err repl_env_idx /* rep(str) */ + str = arg(1) + exp = re(str) + if exp == "ERR" then return "ERR" + return print(exp) + +main: + values. = "" + values.0 = 0 + env. = "" + env.0 = 0 + repl_env_idx = new_env(0) + + /* core.rexx: defined using Rexx */ + core_ns = get_core_ns() + do i=1 to words(core_ns) by 2 + x = env_set(repl_env_idx, word(core_ns, i), new_nativefn(word(core_ns, i + 1))) + end + + /* core.mal: defined using the language itself */ + x = re("(def! not (fn* (a) (if a false true)))") + + err = "" + do while lines() > 0 /* 1 == 1 */ + input_line = readline('user> ') + if length(input_line) > 0 then do + res = rep(input_line) + if res == "ERR" then + call lineout , "Error: " || err + else + call lineout , res + end + end diff --git a/rexx/step5_tco.rexx b/rexx/step5_tco.rexx new file mode 100644 index 0000000000..6799b761ed --- /dev/null +++ b/rexx/step5_tco.rexx @@ -0,0 +1,183 @@ +call main +exit + +#include "readline.rexx" +#include "reader.rexx" +#include "printer.rexx" +#include "types.rexx" +#include "env.rexx" +#include "core.rexx" + +read: procedure expose values. err /* read(str) */ + return read_str(arg(1)) + +eval_ast: procedure expose values. env. err /* eval_ast(ast, env_idx) */ + ast = arg(1) + env_idx = arg(2) + type = obj_type(ast) + val = obj_val(ast) + select + when type == "symb" then return env_get(env_idx, val) + when type == "list" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_list(res) + end + when type == "vect" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_vector(res) + end + when type == "hash" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_hashmap(res) + end + otherwise + return ast + end + +eval: procedure expose values. env. err /* eval(ast) */ + ast = arg(1) + env_idx = arg(2) + do forever + if \list?(ast) then return eval_ast(ast, env_idx) + astval = obj_val(ast) + if words(astval) == 0 then return ast + a0sym = obj_val(word(astval, 1)) + select + when a0sym == "def!" then do + a1sym = obj_val(word(astval, 2)) + a2 = eval(word(astval, 3), env_idx) + if a2 == "ERR" then return "ERR" + return env_set(env_idx, a1sym, a2) + end + when a0sym == "let*" then do + a1lst = obj_val(word(astval, 2)) + letenv_idx = new_env(env_idx) + do i=1 to words(a1lst) by 2 + k = obj_val(word(a1lst, i)) + v = eval(word(a1lst, i + 1), letenv_idx) + if v == "ERR" then return "ERR" + unused = env_set(letenv_idx, k, v) + end + env_idx = letenv_idx + ast = word(astval, 3) + /* TCO */ + end + when a0sym == "do" then do + do i=2 to (words(astval) - 1) + res = eval(word(astval, i), env_idx) + if res == "ERR" then return "ERR" + end + ast = word(astval, words(astval)) + /* TCO */ + end + when a0sym == "if" then do + condval = eval(word(astval, 2), env_idx) + if false?(condval) | nil?(condval) then + if words(astval) >= 4 then + ast = word(astval, 4) + else + return new_nil() + else + ast = word(astval, 3) + /* TCO */ + end + when a0sym == "fn*" then return new_func(word(astval, 3), env_idx, word(astval, 2)) + otherwise + lst_obj = eval_ast(ast, env_idx) + if lst_obj == "ERR" then return "ERR" + lst = obj_val(lst_obj) + f = word(lst, 1) + select + when nativefn?(f) then do + call_args = subword(lst, 2) + call_list = "" + do i=1 to words(call_args) + element = '"' || word(call_args, i) || '"' + if i > 1 then + call_list = call_list || ', ' || element + else + call_list = element + end + res = "" + interpret "res = " || obj_val(f) || "(" || call_list || ")" + return res + end + when func?(f) then do + call_args = new_list(subword(lst, 2)) + env_idx = new_env(func_env_idx(f), func_binds(f), call_args) + ast = func_body_ast(f) + /* TCO */ + end + otherwise + err = "Unsupported function object type: " || obj_type(f) + return "ERR" + end + end + end + +print: procedure expose values. /* print(ast) */ + return pr_str(arg(1), 1) + +re: procedure expose values. env. err repl_env_idx /* re(str) */ + str = arg(1) + ast = read(str) + if ast == "ERR" then return "ERR" + return eval(ast, repl_env_idx) + +rep: procedure expose values. env. err repl_env_idx /* rep(str) */ + str = arg(1) + exp = re(str) + if exp == "ERR" then return "ERR" + return print(exp) + +main: + values. = "" + values.0 = 0 + env. = "" + env.0 = 0 + repl_env_idx = new_env(0) + + /* core.rexx: defined using Rexx */ + core_ns = get_core_ns() + do i=1 to words(core_ns) by 2 + x = env_set(repl_env_idx, word(core_ns, i), new_nativefn(word(core_ns, i + 1))) + end + + /* core.mal: defined using the language itself */ + x = rep("(def! not (fn* (a) (if a false true)))") + + err = "" + do while lines() > 0 /* 1 == 1 */ + input_line = readline('user> ') + if length(input_line) > 0 then do + res = rep(input_line) + if res == "ERR" then + call lineout , "Error: " || err + else + call lineout , res + end + end diff --git a/rexx/step6_file.rexx b/rexx/step6_file.rexx new file mode 100644 index 0000000000..fa1223177c --- /dev/null +++ b/rexx/step6_file.rexx @@ -0,0 +1,214 @@ +/* Save command-line arguments from the top-level program before entering a procedure */ +command_line_args. = "" +command_line_args.0 = arg() +do i=1 to command_line_args.0 + command_line_args.i = arg(i) +end + +call main +exit + +#include "readline.rexx" +#include "reader.rexx" +#include "printer.rexx" +#include "types.rexx" +#include "env.rexx" +#include "core.rexx" + +read: procedure expose values. err /* read(str) */ + return read_str(arg(1)) + +eval_ast: procedure expose values. env. err /* eval_ast(ast, env_idx) */ + ast = arg(1) + env_idx = arg(2) + type = obj_type(ast) + val = obj_val(ast) + select + when type == "symb" then return env_get(env_idx, val) + when type == "list" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_list(res) + end + when type == "vect" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_vector(res) + end + when type == "hash" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_hashmap(res) + end + otherwise + return ast + end + +eval: procedure expose values. env. err /* eval(ast) */ + ast = arg(1) + env_idx = arg(2) + do forever + if \list?(ast) then return eval_ast(ast, env_idx) + astval = obj_val(ast) + if words(astval) == 0 then return ast + a0sym = obj_val(word(astval, 1)) + select + when a0sym == "def!" then do + a1sym = obj_val(word(astval, 2)) + a2 = eval(word(astval, 3), env_idx) + if a2 == "ERR" then return "ERR" + return env_set(env_idx, a1sym, a2) + end + when a0sym == "let*" then do + a1lst = obj_val(word(astval, 2)) + letenv_idx = new_env(env_idx) + do i=1 to words(a1lst) by 2 + k = obj_val(word(a1lst, i)) + v = eval(word(a1lst, i + 1), letenv_idx) + if v == "ERR" then return "ERR" + unused = env_set(letenv_idx, k, v) + end + env_idx = letenv_idx + ast = word(astval, 3) + /* TCO */ + end + when a0sym == "do" then do + do i=2 to (words(astval) - 1) + res = eval(word(astval, i), env_idx) + if res == "ERR" then return "ERR" + end + ast = word(astval, words(astval)) + /* TCO */ + end + when a0sym == "if" then do + condval = eval(word(astval, 2), env_idx) + if false?(condval) | nil?(condval) then + if words(astval) >= 4 then + ast = word(astval, 4) + else + return new_nil() + else + ast = word(astval, 3) + /* TCO */ + end + when a0sym == "fn*" then return new_func(word(astval, 3), env_idx, word(astval, 2)) + otherwise + lst_obj = eval_ast(ast, env_idx) + if lst_obj == "ERR" then return "ERR" + lst = obj_val(lst_obj) + f = word(lst, 1) + select + when nativefn?(f) then do + call_args = subword(lst, 2) + call_list = "" + do i=1 to words(call_args) + element = '"' || word(call_args, i) || '"' + if i > 1 then + call_list = call_list || ', ' || element + else + call_list = element + end + res = "" + interpret "res = " || obj_val(f) || "(" || call_list || ")" + return res + end + when func?(f) then do + call_args = new_list(subword(lst, 2)) + env_idx = new_env(func_env_idx(f), func_binds(f), call_args) + ast = func_body_ast(f) + /* TCO */ + end + otherwise + err = "Unsupported function object type: " || obj_type(f) + return "ERR" + end + end + end + +print: procedure expose values. /* print(ast) */ + return pr_str(arg(1), 1) + +re: procedure expose values. env. err repl_env_idx /* re(str) */ + str = arg(1) + ast = read(str) + if ast == "ERR" then return "ERR" + return eval(ast, repl_env_idx) + +rep: procedure expose values. env. err repl_env_idx /* rep(str) */ + str = arg(1) + exp = re(str) + if exp == "ERR" then return "ERR" + return print(exp) + +mal_eval: procedure expose values. env. err /* mal_eval(ast) */ + ast = arg(1) + if ast == "ERR" then return "ERR" + return eval(arg(1), 1) /* repl_env_idx is always 1 because it's the first env */ + +build_args_list: procedure expose values. command_line_args. /* build_args_list() */ + seq = "" + do i=2 to command_line_args.0 + s = new_string(command_line_args.i) + if i == 1 then + seq = s + else + seq = seq || " " || s + end + return new_list(seq) + +main: + values. = "" + values.0 = 0 + env. = "" + env.0 = 0 + repl_env_idx = new_env(0) + + /* core.rexx: defined using Rexx */ + core_ns = get_core_ns() + do i=1 to words(core_ns) by 2 + x = env_set(repl_env_idx, word(core_ns, i), new_nativefn(word(core_ns, i + 1))) + end + x = env_set(repl_env_idx, "eval", new_nativefn("mal_eval")) + x = env_set(repl_env_idx, "*ARGV*", build_args_list()) + + /* core.mal: defined using the language itself */ + x = re("(def! not (fn* (a) (if a false true)))") + x = re('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))') + + err = "" + if command_line_args.0 > 0 then do + x = re('(load-file "' || command_line_args.1 || '")') + return + end + + do while lines() > 0 /* 1 == 1 */ + input_line = readline('user> ') + if length(input_line) > 0 then do + res = rep(input_line) + if res == "ERR" then + call lineout , "Error: " || err + else + call lineout , res + end + end diff --git a/rexx/step7_quote.rexx b/rexx/step7_quote.rexx new file mode 100644 index 0000000000..648317c52d --- /dev/null +++ b/rexx/step7_quote.rexx @@ -0,0 +1,234 @@ +/* Save command-line arguments from the top-level program before entering a procedure */ +command_line_args. = "" +command_line_args.0 = arg() +do i=1 to command_line_args.0 + command_line_args.i = arg(i) +end + +call main +exit + +#include "readline.rexx" +#include "reader.rexx" +#include "printer.rexx" +#include "types.rexx" +#include "env.rexx" +#include "core.rexx" + +read: procedure expose values. err /* read(str) */ + return read_str(arg(1)) + +pair?: procedure expose values. /* pair?(ast) */ + ast = arg(1) + return sequential?(ast) & words(obj_val(ast)) > 0 + +quasiquote: procedure expose values. env. err /* quasiquote(ast) */ + ast = arg(1) + if \pair?(ast) then return new_list(new_symbol("quote") || " " || ast) + ast0 = word(obj_val(ast), 1) + if symbol?(ast0) & obj_val(ast0) == "unquote" then return word(obj_val(ast), 2) + ast00 = word(obj_val(ast0), 1) + if pair?(ast0) & symbol?(ast00) & obj_val(ast00) == "splice-unquote" then + return new_list(new_symbol("concat") || " " || word(obj_val(ast0), 2) || " " || quasiquote(mal_rest(ast))) + else + return new_list(new_symbol("cons") || " " || quasiquote(ast0) || " " || quasiquote(mal_rest(ast))) + +eval_ast: procedure expose values. env. err /* eval_ast(ast, env_idx) */ + ast = arg(1) + env_idx = arg(2) + type = obj_type(ast) + val = obj_val(ast) + select + when type == "symb" then return env_get(env_idx, val) + when type == "list" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_list(res) + end + when type == "vect" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_vector(res) + end + when type == "hash" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_hashmap(res) + end + otherwise + return ast + end + +eval: procedure expose values. env. err /* eval(ast) */ + ast = arg(1) + env_idx = arg(2) + do forever + if \list?(ast) then return eval_ast(ast, env_idx) + astval = obj_val(ast) + if words(astval) == 0 then return ast + a0sym = obj_val(word(astval, 1)) + select + when a0sym == "def!" then do + a1sym = obj_val(word(astval, 2)) + a2 = eval(word(astval, 3), env_idx) + if a2 == "ERR" then return "ERR" + return env_set(env_idx, a1sym, a2) + end + when a0sym == "let*" then do + a1lst = obj_val(word(astval, 2)) + letenv_idx = new_env(env_idx) + do i=1 to words(a1lst) by 2 + k = obj_val(word(a1lst, i)) + v = eval(word(a1lst, i + 1), letenv_idx) + if v == "ERR" then return "ERR" + unused = env_set(letenv_idx, k, v) + end + env_idx = letenv_idx + ast = word(astval, 3) + /* TCO */ + end + when a0sym == "quote" then return word(astval, 2) + when a0sym == "quasiquote" then do + ast = quasiquote(word(astval, 2)) + /* TCO */ + end + when a0sym == "do" then do + do i=2 to (words(astval) - 1) + res = eval(word(astval, i), env_idx) + if res == "ERR" then return "ERR" + end + ast = word(astval, words(astval)) + /* TCO */ + end + when a0sym == "if" then do + condval = eval(word(astval, 2), env_idx) + if false?(condval) | nil?(condval) then + if words(astval) >= 4 then + ast = word(astval, 4) + else + return new_nil() + else + ast = word(astval, 3) + /* TCO */ + end + when a0sym == "fn*" then return new_func(word(astval, 3), env_idx, word(astval, 2)) + otherwise + lst_obj = eval_ast(ast, env_idx) + if lst_obj == "ERR" then return "ERR" + lst = obj_val(lst_obj) + f = word(lst, 1) + select + when nativefn?(f) then do + call_args = subword(lst, 2) + call_list = "" + do i=1 to words(call_args) + element = '"' || word(call_args, i) || '"' + if i > 1 then + call_list = call_list || ', ' || element + else + call_list = element + end + res = "" + interpret "res = " || obj_val(f) || "(" || call_list || ")" + return res + end + when func?(f) then do + call_args = new_list(subword(lst, 2)) + env_idx = new_env(func_env_idx(f), func_binds(f), call_args) + ast = func_body_ast(f) + /* TCO */ + end + otherwise + err = "Unsupported function object type: " || obj_type(f) + return "ERR" + end + end + end + +print: procedure expose values. /* print(ast) */ + return pr_str(arg(1), 1) + +re: procedure expose values. env. err repl_env_idx /* re(str) */ + str = arg(1) + ast = read(str) + if ast == "ERR" then return "ERR" + return eval(ast, repl_env_idx) + +rep: procedure expose values. env. err repl_env_idx /* rep(str) */ + str = arg(1) + exp = re(str) + if exp == "ERR" then return "ERR" + return print(exp) + +mal_eval: procedure expose values. env. err /* mal_eval(ast) */ + ast = arg(1) + if ast == "ERR" then return "ERR" + return eval(arg(1), 1) /* repl_env_idx is always 1 because it's the first env */ + +build_args_list: procedure expose values. command_line_args. /* build_args_list() */ + seq = "" + do i=2 to command_line_args.0 + s = new_string(command_line_args.i) + if i == 1 then + seq = s + else + seq = seq || " " || s + end + return new_list(seq) + +main: + values. = "" + values.0 = 0 + env. = "" + env.0 = 0 + repl_env_idx = new_env(0) + + /* core.rexx: defined using Rexx */ + core_ns = get_core_ns() + do i=1 to words(core_ns) by 2 + x = env_set(repl_env_idx, word(core_ns, i), new_nativefn(word(core_ns, i + 1))) + end + x = env_set(repl_env_idx, "eval", new_nativefn("mal_eval")) + x = env_set(repl_env_idx, "*ARGV*", build_args_list()) + + /* core.mal: defined using the language itself */ + x = re("(def! not (fn* (a) (if a false true)))") + x = re('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))') + + err = "" + if command_line_args.0 > 0 then do + x = re('(load-file "' || command_line_args.1 || '")') + return + end + + do while lines() > 0 /* 1 == 1 */ + input_line = readline('user> ') + if length(input_line) > 0 then do + res = rep(input_line) + if res == "ERR" then + call lineout , "Error: " || err + else + call lineout , res + end + end diff --git a/rexx/step8_macros.rexx b/rexx/step8_macros.rexx new file mode 100644 index 0000000000..7430a0b0e7 --- /dev/null +++ b/rexx/step8_macros.rexx @@ -0,0 +1,265 @@ +/* Save command-line arguments from the top-level program before entering a procedure */ +command_line_args. = "" +command_line_args.0 = arg() +do i=1 to command_line_args.0 + command_line_args.i = arg(i) +end + +call main +exit + +#include "readline.rexx" +#include "reader.rexx" +#include "printer.rexx" +#include "types.rexx" +#include "env.rexx" +#include "core.rexx" + +read: procedure expose values. err /* read(str) */ + return read_str(arg(1)) + +pair?: procedure expose values. /* pair?(ast) */ + ast = arg(1) + return sequential?(ast) & words(obj_val(ast)) > 0 + +quasiquote: procedure expose values. env. err /* quasiquote(ast) */ + ast = arg(1) + if \pair?(ast) then return new_list(new_symbol("quote") || " " || ast) + ast0 = word(obj_val(ast), 1) + if symbol?(ast0) & obj_val(ast0) == "unquote" then return word(obj_val(ast), 2) + ast00 = word(obj_val(ast0), 1) + if pair?(ast0) & symbol?(ast00) & obj_val(ast00) == "splice-unquote" then + return new_list(new_symbol("concat") || " " || word(obj_val(ast0), 2) || " " || quasiquote(mal_rest(ast))) + else + return new_list(new_symbol("cons") || " " || quasiquote(ast0) || " " || quasiquote(mal_rest(ast))) + +macro?: procedure expose values. env. /* macro?(ast, env_idx) */ + ast = arg(1) + env_idx = arg(2) + if \list?(ast) then return 0 + ast0 = mal_first(ast) + if \symbol?(ast0) then return 0 + if env_find(env_idx, obj_val(ast0)) == 0 then return 0 + return func_macro?(env_get(env_idx, obj_val(ast0))) + +macroexpand: procedure expose values. env. err /* macroexpand(ast, env_idx) */ + ast = arg(1) + env_idx = arg(2) + do while macro?(ast, env_idx) + mac = env_get(env_idx, obj_val(mal_first(ast))) + call_args = mal_rest(ast) + mac_env_idx = new_env(func_env_idx(mac), func_binds(mac), call_args) + ast = eval(func_body_ast(mac), mac_env_idx) + end + return ast + +eval_ast: procedure expose values. env. err /* eval_ast(ast, env_idx) */ + ast = arg(1) + env_idx = arg(2) + type = obj_type(ast) + val = obj_val(ast) + select + when type == "symb" then return env_get(env_idx, val) + when type == "list" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_list(res) + end + when type == "vect" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_vector(res) + end + when type == "hash" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_hashmap(res) + end + otherwise + return ast + end + +eval: procedure expose values. env. err /* eval(ast) */ + ast = arg(1) + env_idx = arg(2) + do forever + if \list?(ast) then return eval_ast(ast, env_idx) + ast = macroexpand(ast, env_idx) + if \list?(ast) then return eval_ast(ast, env_idx) + astval = obj_val(ast) + if words(astval) == 0 then return ast + a0sym = obj_val(word(astval, 1)) + select + when a0sym == "def!" then do + a1sym = obj_val(word(astval, 2)) + a2 = eval(word(astval, 3), env_idx) + if a2 == "ERR" then return "ERR" + return env_set(env_idx, a1sym, a2) + end + when a0sym == "let*" then do + a1lst = obj_val(word(astval, 2)) + letenv_idx = new_env(env_idx) + do i=1 to words(a1lst) by 2 + k = obj_val(word(a1lst, i)) + v = eval(word(a1lst, i + 1), letenv_idx) + if v == "ERR" then return "ERR" + unused = env_set(letenv_idx, k, v) + end + env_idx = letenv_idx + ast = word(astval, 3) + /* TCO */ + end + when a0sym == "quote" then return word(astval, 2) + when a0sym == "quasiquote" then do + ast = quasiquote(word(astval, 2)) + /* TCO */ + end + when a0sym == "defmacro!" then do + a1sym = obj_val(word(astval, 2)) + a2 = eval(word(astval, 3), env_idx) + if a2 == "ERR" then return "ERR" + return env_set(env_idx, a1sym, func_mark_as_macro(a2)) + end + when a0sym == "macroexpand" then return macroexpand(word(astval, 2), env_idx) + when a0sym == "do" then do + do i=2 to (words(astval) - 1) + res = eval(word(astval, i), env_idx) + if res == "ERR" then return "ERR" + end + ast = word(astval, words(astval)) + /* TCO */ + end + when a0sym == "if" then do + condval = eval(word(astval, 2), env_idx) + if false?(condval) | nil?(condval) then + if words(astval) >= 4 then + ast = word(astval, 4) + else + return new_nil() + else + ast = word(astval, 3) + /* TCO */ + end + when a0sym == "fn*" then return new_func(word(astval, 3), env_idx, word(astval, 2)) + otherwise + lst_obj = eval_ast(ast, env_idx) + if lst_obj == "ERR" then return "ERR" + lst = obj_val(lst_obj) + f = word(lst, 1) + select + when nativefn?(f) then do + call_args = subword(lst, 2) + call_list = "" + do i=1 to words(call_args) + element = '"' || word(call_args, i) || '"' + if i > 1 then + call_list = call_list || ', ' || element + else + call_list = element + end + res = "" + interpret "res = " || obj_val(f) || "(" || call_list || ")" + return res + end + when func?(f) then do + call_args = new_list(subword(lst, 2)) + env_idx = new_env(func_env_idx(f), func_binds(f), call_args) + ast = func_body_ast(f) + /* TCO */ + end + otherwise + err = "Unsupported function object type: " || obj_type(f) + return "ERR" + end + end + end + +print: procedure expose values. /* print(ast) */ + return pr_str(arg(1), 1) + +re: procedure expose values. env. err repl_env_idx /* re(str) */ + str = arg(1) + ast = read(str) + if ast == "ERR" then return "ERR" + return eval(ast, repl_env_idx) + +rep: procedure expose values. env. err repl_env_idx /* rep(str) */ + str = arg(1) + exp = re(str) + if exp == "ERR" then return "ERR" + return print(exp) + +mal_eval: procedure expose values. env. err /* mal_eval(ast) */ + ast = arg(1) + if ast == "ERR" then return "ERR" + return eval(arg(1), 1) /* repl_env_idx is always 1 because it's the first env */ + +build_args_list: procedure expose values. command_line_args. /* build_args_list() */ + seq = "" + do i=2 to command_line_args.0 + s = new_string(command_line_args.i) + if i == 1 then + seq = s + else + seq = seq || " " || s + end + return new_list(seq) + +main: + values. = "" + values.0 = 0 + env. = "" + env.0 = 0 + repl_env_idx = new_env(0) + + /* core.rexx: defined using Rexx */ + core_ns = get_core_ns() + do i=1 to words(core_ns) by 2 + x = env_set(repl_env_idx, word(core_ns, i), new_nativefn(word(core_ns, i + 1))) + end + x = env_set(repl_env_idx, "eval", new_nativefn("mal_eval")) + x = env_set(repl_env_idx, "*ARGV*", build_args_list()) + + /* core.mal: defined using the language itself */ + x = re("(def! not (fn* (a) (if a false true)))") + x = re('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))') + x = re("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw " || '"' || "odd number of forms to cond" || '"' || ")) (cons 'cond (rest (rest xs)))))))"); + x = re("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); + + err = "" + if command_line_args.0 > 0 then do + x = re('(load-file "' || command_line_args.1 || '")') + return + end + + do while lines() > 0 /* 1 == 1 */ + input_line = readline('user> ') + if length(input_line) > 0 then do + res = rep(input_line) + if res == "ERR" then + call lineout , "Error: " || err + else + call lineout , res + end + end diff --git a/rexx/step9_try.rexx b/rexx/step9_try.rexx new file mode 100644 index 0000000000..36a1cc3c10 --- /dev/null +++ b/rexx/step9_try.rexx @@ -0,0 +1,286 @@ +/* Save command-line arguments from the top-level program before entering a procedure */ +command_line_args. = "" +command_line_args.0 = arg() +do i=1 to command_line_args.0 + command_line_args.i = arg(i) +end + +call main +exit + +#include "readline.rexx" +#include "reader.rexx" +#include "printer.rexx" +#include "types.rexx" +#include "env.rexx" +#include "core.rexx" + +read: procedure expose values. err /* read(str) */ + return read_str(arg(1)) + +pair?: procedure expose values. /* pair?(ast) */ + ast = arg(1) + return sequential?(ast) & words(obj_val(ast)) > 0 + +quasiquote: procedure expose values. env. err /* quasiquote(ast) */ + ast = arg(1) + if \pair?(ast) then return new_list(new_symbol("quote") || " " || ast) + ast0 = word(obj_val(ast), 1) + if symbol?(ast0) & obj_val(ast0) == "unquote" then return word(obj_val(ast), 2) + ast00 = word(obj_val(ast0), 1) + if pair?(ast0) & symbol?(ast00) & obj_val(ast00) == "splice-unquote" then + return new_list(new_symbol("concat") || " " || word(obj_val(ast0), 2) || " " || quasiquote(mal_rest(ast))) + else + return new_list(new_symbol("cons") || " " || quasiquote(ast0) || " " || quasiquote(mal_rest(ast))) + +macro?: procedure expose values. env. /* macro?(ast, env_idx) */ + ast = arg(1) + env_idx = arg(2) + if \list?(ast) then return 0 + ast0 = mal_first(ast) + if \symbol?(ast0) then return 0 + if env_find(env_idx, obj_val(ast0)) == 0 then return 0 + return func_macro?(env_get(env_idx, obj_val(ast0))) + +macroexpand: procedure expose values. env. err /* macroexpand(ast, env_idx) */ + ast = arg(1) + env_idx = arg(2) + do while macro?(ast, env_idx) + mac = env_get(env_idx, obj_val(mal_first(ast))) + call_args = mal_rest(ast) + mac_env_idx = new_env(func_env_idx(mac), func_binds(mac), call_args) + ast = eval(func_body_ast(mac), mac_env_idx) + end + return ast + +eval_ast: procedure expose values. env. err /* eval_ast(ast, env_idx) */ + ast = arg(1) + env_idx = arg(2) + type = obj_type(ast) + val = obj_val(ast) + select + when type == "symb" then return env_get(env_idx, val) + when type == "list" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_list(res) + end + when type == "vect" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_vector(res) + end + when type == "hash" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_hashmap(res) + end + otherwise + return ast + end + +eval: procedure expose values. env. err /* eval(ast) */ + ast = arg(1) + env_idx = arg(2) + do forever + if \list?(ast) then return eval_ast(ast, env_idx) + ast = macroexpand(ast, env_idx) + if \list?(ast) then return eval_ast(ast, env_idx) + astval = obj_val(ast) + if words(astval) == 0 then return ast + a0sym = obj_val(word(astval, 1)) + select + when a0sym == "def!" then do + a1sym = obj_val(word(astval, 2)) + a2 = eval(word(astval, 3), env_idx) + if a2 == "ERR" then return "ERR" + return env_set(env_idx, a1sym, a2) + end + when a0sym == "let*" then do + a1lst = obj_val(word(astval, 2)) + letenv_idx = new_env(env_idx) + do i=1 to words(a1lst) by 2 + k = obj_val(word(a1lst, i)) + v = eval(word(a1lst, i + 1), letenv_idx) + if v == "ERR" then return "ERR" + unused = env_set(letenv_idx, k, v) + end + env_idx = letenv_idx + ast = word(astval, 3) + /* TCO */ + end + when a0sym == "quote" then return word(astval, 2) + when a0sym == "quasiquote" then do + ast = quasiquote(word(astval, 2)) + /* TCO */ + end + when a0sym == "defmacro!" then do + a1sym = obj_val(word(astval, 2)) + a2 = eval(word(astval, 3), env_idx) + if a2 == "ERR" then return "ERR" + return env_set(env_idx, a1sym, func_mark_as_macro(a2)) + end + when a0sym == "macroexpand" then return macroexpand(word(astval, 2), env_idx) + when a0sym == "try*" then do + res = eval(word(astval, 2), env_idx) + if res == "ERR" then do + if word(err, 1) == "__MAL_EXCEPTION__" then + errobj = word(err, 2) + else + errobj = new_string(err) + catchlst = obj_val(word(astval, 3)) + catch_env_idx = new_env(env_idx, new_list(word(catchlst, 2)), new_list(errobj)) + err = "" + return eval(word(catchlst, 3), catch_env_idx) + end + else + return res + end + when a0sym == "do" then do + do i=2 to (words(astval) - 1) + res = eval(word(astval, i), env_idx) + if res == "ERR" then return "ERR" + end + ast = word(astval, words(astval)) + /* TCO */ + end + when a0sym == "if" then do + condval = eval(word(astval, 2), env_idx) + if false?(condval) | nil?(condval) then + if words(astval) >= 4 then + ast = word(astval, 4) + else + return new_nil() + else + ast = word(astval, 3) + /* TCO */ + end + when a0sym == "fn*" then return new_func(word(astval, 3), env_idx, word(astval, 2)) + otherwise + lst_obj = eval_ast(ast, env_idx) + if lst_obj == "ERR" then return "ERR" + lst = obj_val(lst_obj) + f = word(lst, 1) + select + when nativefn?(f) then do + call_args = subword(lst, 2) + call_list = "" + do i=1 to words(call_args) + element = '"' || word(call_args, i) || '"' + if i > 1 then + call_list = call_list || ', ' || element + else + call_list = element + end + res = "" + interpret "res = " || obj_val(f) || "(" || call_list || ")" + return res + end + when func?(f) then do + call_args = new_list(subword(lst, 2)) + env_idx = new_env(func_env_idx(f), func_binds(f), call_args) + ast = func_body_ast(f) + /* TCO */ + end + otherwise + err = "Unsupported function object type: " || obj_type(f) + return "ERR" + end + end + end + +print: procedure expose values. /* print(ast) */ + return pr_str(arg(1), 1) + +re: procedure expose values. env. err repl_env_idx /* re(str) */ + str = arg(1) + ast = read(str) + if ast == "ERR" then return "ERR" + return eval(ast, repl_env_idx) + +rep: procedure expose values. env. err repl_env_idx /* rep(str) */ + str = arg(1) + exp = re(str) + if exp == "ERR" then return "ERR" + return print(exp) + +mal_eval: procedure expose values. env. err /* mal_eval(ast) */ + ast = arg(1) + if ast == "ERR" then return "ERR" + return eval(arg(1), 1) /* repl_env_idx is always 1 because it's the first env */ + +build_args_list: procedure expose values. command_line_args. /* build_args_list() */ + seq = "" + do i=2 to command_line_args.0 + s = new_string(command_line_args.i) + if i == 1 then + seq = s + else + seq = seq || " " || s + end + return new_list(seq) + +main: + values. = "" + values.0 = 0 + env. = "" + env.0 = 0 + repl_env_idx = new_env(0) + + /* core.rexx: defined using Rexx */ + core_ns = get_core_ns() + do i=1 to words(core_ns) by 2 + x = env_set(repl_env_idx, word(core_ns, i), new_nativefn(word(core_ns, i + 1))) + end + x = env_set(repl_env_idx, "eval", new_nativefn("mal_eval")) + x = env_set(repl_env_idx, "*ARGV*", build_args_list()) + + /* core.mal: defined using the language itself */ + x = re("(def! not (fn* (a) (if a false true)))") + x = re('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))') + x = re("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw " || '"' || "odd number of forms to cond" || '"' || ")) (cons 'cond (rest (rest xs)))))))"); + x = re("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); + + err = "" + if command_line_args.0 > 0 then do + x = re('(load-file "' || command_line_args.1 || '")') + return + end + + do while lines() > 0 /* 1 == 1 */ + input_line = readline('user> ') + if length(input_line) > 0 then do + res = rep(input_line) + if res == "ERR" then do + if word(err, 1) == "__MAL_EXCEPTION__" then + errstr = pr_str(word(err, 2), 0) + else + errstr = err + call lineout , "Error: " || errstr + err = "" + end + else + call lineout , res + end + end diff --git a/rexx/stepA_mal.rexx b/rexx/stepA_mal.rexx new file mode 100644 index 0000000000..fb01015d8f --- /dev/null +++ b/rexx/stepA_mal.rexx @@ -0,0 +1,291 @@ +/* Save command-line arguments from the top-level program before entering a procedure */ +command_line_args. = "" +command_line_args.0 = arg() +do i=1 to command_line_args.0 + command_line_args.i = arg(i) +end + +call main +exit + +#include "readline.rexx" +#include "reader.rexx" +#include "printer.rexx" +#include "types.rexx" +#include "env.rexx" +#include "core.rexx" + +read: procedure expose values. err /* read(str) */ + return read_str(arg(1)) + +pair?: procedure expose values. /* pair?(ast) */ + ast = arg(1) + return sequential?(ast) & words(obj_val(ast)) > 0 + +quasiquote: procedure expose values. env. err /* quasiquote(ast) */ + ast = arg(1) + if \pair?(ast) then return new_list(new_symbol("quote") || " " || ast) + ast0 = word(obj_val(ast), 1) + if symbol?(ast0) & obj_val(ast0) == "unquote" then return word(obj_val(ast), 2) + ast00 = word(obj_val(ast0), 1) + if pair?(ast0) & symbol?(ast00) & obj_val(ast00) == "splice-unquote" then + return new_list(new_symbol("concat") || " " || word(obj_val(ast0), 2) || " " || quasiquote(mal_rest(ast))) + else + return new_list(new_symbol("cons") || " " || quasiquote(ast0) || " " || quasiquote(mal_rest(ast))) + +macro?: procedure expose values. env. /* macro?(ast, env_idx) */ + ast = arg(1) + env_idx = arg(2) + if \list?(ast) then return 0 + ast0 = mal_first(ast) + if \symbol?(ast0) then return 0 + if env_find(env_idx, obj_val(ast0)) == 0 then return 0 + return func_macro?(env_get(env_idx, obj_val(ast0))) + +macroexpand: procedure expose values. env. err /* macroexpand(ast, env_idx) */ + ast = arg(1) + env_idx = arg(2) + do while macro?(ast, env_idx) + mac = env_get(env_idx, obj_val(mal_first(ast))) + call_args = mal_rest(ast) + mac_env_idx = new_env(func_env_idx(mac), func_binds(mac), call_args) + ast = eval(func_body_ast(mac), mac_env_idx) + end + return ast + +eval_ast: procedure expose values. env. err /* eval_ast(ast, env_idx) */ + ast = arg(1) + env_idx = arg(2) + type = obj_type(ast) + val = obj_val(ast) + select + when type == "symb" then return env_get(env_idx, val) + when type == "list" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_list(res) + end + when type == "vect" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_vector(res) + end + when type == "hash" then do + res = "" + do i=1 to words(val) + element = eval(word(val, i), env_idx) + if element == "ERR" then return "ERR" + if i > 1 then + res = res || " " || element + else + res = element + end + return new_hashmap(res) + end + otherwise + return ast + end + +eval: procedure expose values. env. err /* eval(ast) */ + ast = arg(1) + env_idx = arg(2) + do forever + if \list?(ast) then return eval_ast(ast, env_idx) + ast = macroexpand(ast, env_idx) + if \list?(ast) then return eval_ast(ast, env_idx) + astval = obj_val(ast) + if words(astval) == 0 then return ast + a0sym = obj_val(word(astval, 1)) + select + when a0sym == "def!" then do + a1sym = obj_val(word(astval, 2)) + a2 = eval(word(astval, 3), env_idx) + if a2 == "ERR" then return "ERR" + return env_set(env_idx, a1sym, a2) + end + when a0sym == "let*" then do + a1lst = obj_val(word(astval, 2)) + letenv_idx = new_env(env_idx) + do i=1 to words(a1lst) by 2 + k = obj_val(word(a1lst, i)) + v = eval(word(a1lst, i + 1), letenv_idx) + if v == "ERR" then return "ERR" + unused = env_set(letenv_idx, k, v) + end + env_idx = letenv_idx + ast = word(astval, 3) + /* TCO */ + end + when a0sym == "quote" then return word(astval, 2) + when a0sym == "quasiquote" then do + ast = quasiquote(word(astval, 2)) + /* TCO */ + end + when a0sym == "defmacro!" then do + a1sym = obj_val(word(astval, 2)) + a2 = eval(word(astval, 3), env_idx) + if a2 == "ERR" then return "ERR" + return env_set(env_idx, a1sym, func_mark_as_macro(a2)) + end + when a0sym == "macroexpand" then return macroexpand(word(astval, 2), env_idx) + when a0sym == "try*" then do + res = eval(word(astval, 2), env_idx) + if res == "ERR" then do + if word(err, 1) == "__MAL_EXCEPTION__" then + errobj = word(err, 2) + else + errobj = new_string(err) + catchlst = obj_val(word(astval, 3)) + catch_env_idx = new_env(env_idx, new_list(word(catchlst, 2)), new_list(errobj)) + err = "" + return eval(word(catchlst, 3), catch_env_idx) + end + else + return res + end + when a0sym == "do" then do + do i=2 to (words(astval) - 1) + res = eval(word(astval, i), env_idx) + if res == "ERR" then return "ERR" + end + ast = word(astval, words(astval)) + /* TCO */ + end + when a0sym == "if" then do + condval = eval(word(astval, 2), env_idx) + if false?(condval) | nil?(condval) then + if words(astval) >= 4 then + ast = word(astval, 4) + else + return new_nil() + else + ast = word(astval, 3) + /* TCO */ + end + when a0sym == "fn*" then return new_func(word(astval, 3), env_idx, word(astval, 2)) + otherwise + lst_obj = eval_ast(ast, env_idx) + if lst_obj == "ERR" then return "ERR" + lst = obj_val(lst_obj) + f = word(lst, 1) + select + when nativefn?(f) then do + call_args = subword(lst, 2) + call_list = "" + do i=1 to words(call_args) + element = '"' || word(call_args, i) || '"' + if i > 1 then + call_list = call_list || ', ' || element + else + call_list = element + end + res = "" + interpret "res = " || obj_val(f) || "(" || call_list || ")" + return res + end + when func?(f) then do + call_args = new_list(subword(lst, 2)) + env_idx = new_env(func_env_idx(f), func_binds(f), call_args) + ast = func_body_ast(f) + /* TCO */ + end + otherwise + err = "Unsupported function object type: " || obj_type(f) + return "ERR" + end + end + end + +print: procedure expose values. /* print(ast) */ + return pr_str(arg(1), 1) + +re: procedure expose values. env. err repl_env_idx /* re(str) */ + str = arg(1) + ast = read(str) + if ast == "ERR" then return "ERR" + return eval(ast, repl_env_idx) + +rep: procedure expose values. env. err repl_env_idx /* rep(str) */ + str = arg(1) + exp = re(str) + if exp == "ERR" then return "ERR" + return print(exp) + +mal_eval: procedure expose values. env. err /* mal_eval(ast) */ + ast = arg(1) + if ast == "ERR" then return "ERR" + return eval(arg(1), 1) /* repl_env_idx is always 1 because it's the first env */ + +build_args_list: procedure expose values. command_line_args. /* build_args_list() */ + seq = "" + do i=2 to command_line_args.0 + s = new_string(command_line_args.i) + if i == 1 then + seq = s + else + seq = seq || " " || s + end + return new_list(seq) + +main: + x = time('R') /* Reset the internal stopwatch; used by `time-ms` */ + values. = "" + values.0 = 0 + env. = "" + env.0 = 0 + repl_env_idx = new_env(0) + + /* core.rexx: defined using Rexx */ + core_ns = get_core_ns() + do i=1 to words(core_ns) by 2 + x = env_set(repl_env_idx, word(core_ns, i), new_nativefn(word(core_ns, i + 1))) + end + x = env_set(repl_env_idx, "eval", new_nativefn("mal_eval")) + x = env_set(repl_env_idx, "*ARGV*", build_args_list()) + + /* core.mal: defined using the language itself */ + x = re('(def! *host-language* "rexx")') + x = re("(def! not (fn* (a) (if a false true)))") + x = re('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))') + x = re("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw " || '"' || "odd number of forms to cond" || '"' || ")) (cons 'cond (rest (rest xs)))))))"); + x = re("(def! *gensym-counter* (atom 0))") + x = re('(def! gensym (fn* [] (symbol (str "G__" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))') + x = re("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))") + + err = "" + if command_line_args.0 > 0 then do + x = re('(load-file "' || command_line_args.1 || '")') + return + end + + x = re('(println (str "Mal [" *host-language* "]"))') + do while lines() > 0 /* 1 == 1 */ + input_line = readline('user> ') + if length(input_line) > 0 then do + res = rep(input_line) + if res == "ERR" then do + if word(err, 1) == "__MAL_EXCEPTION__" then + errstr = pr_str(word(err, 2), 0) + else + errstr = err + call lineout , "Error: " || errstr + err = "" + end + else + call lineout , res + end + end diff --git a/rexx/tests/step5_tco.mal b/rexx/tests/step5_tco.mal new file mode 100644 index 0000000000..51604d627e --- /dev/null +++ b/rexx/tests/step5_tco.mal @@ -0,0 +1,2 @@ +;; REXX: skipping non-TCO recursion +;; Reason: regina rexx interpreter segfaults (unrecoverable) diff --git a/rexx/tests/stepA_mal.mal b/rexx/tests/stepA_mal.mal new file mode 100644 index 0000000000..cc05df486b --- /dev/null +++ b/rexx/tests/stepA_mal.mal @@ -0,0 +1,23 @@ +;; Testing basic Rexx interop +;; +;; Note that in Rexx "everything is a string". Numeric outputs are converted to +;; Mal numbers. + +(rexx-eval "3 ** 4") +;=>81 + +(rexx-eval "words('a bb ' || 'ccc dddd')") +;=>4 + +(rexx-eval "d2x(254)") +;=>"FE" + +(rexx-eval "say 'hello' 12.34 upper('rexx')" nil) +; hello 12.34 REXX +;=>nil + +(rexx-eval "foo = 8" "foo + 3") +;=>11 + +(rexx-eval "parse version s1 s2 s3 s4 s5" "'rexx_version=' || s2") +;=>"rexx_version=5.00" diff --git a/rexx/types.rexx b/rexx/types.rexx new file mode 100644 index 0000000000..c9b1ad3e9a --- /dev/null +++ b/rexx/types.rexx @@ -0,0 +1,248 @@ +#ifndef __types__ +#define __types__ + +values. = "" +values.0 = 0 + +new_value_index: procedure expose values. /* new_value_index() */ + values.0 = values.0 + 1 + return values.0 + +obj_type: procedure /* obj_type(obj) */ + obj = arg(1) + return left(obj, 4) + +obj_val: procedure expose values. /* obj_val(obj) */ + obj = arg(1) + type = obj_type(obj) + val = substr(obj, 6) + select + when type == "numb" | type == "nill" | type == "true" | type == "fals" then return val + otherwise + return values.val + end + +obj_meta: procedure expose values. /* obj_meta(obj) */ + obj = arg(1) + type = obj_type(obj) + if type == "numb" | type == "nill" | type == "true" | type == "fals" then return "" + ind = substr(obj, 6) + return values.meta.ind + +obj_clone_and_set_meta: procedure expose values. /* obj_clone_and_set_meta(obj, new_meta) */ + obj = arg(1) + new_meta = arg(2) + type = obj_type(obj) + if type == "numb" | type == "nill" | type == "true" | type == "fals" then return "" + orig_ind = substr(obj, 6) + new_idx = new_value_index() + values.new_idx = values.orig_ind + values.meta.new_idx = new_meta + return type || "_" || new_idx + +new_number: procedure /* new_number(n) */ + n = arg(1) + return "numb_" || n + +new_nil: procedure /* new_nil() */ + return "nill_0" + +nil?: procedure /* nil?(obj) */ + return obj_type(arg(1)) == "nill" + +new_true: procedure /* new_true() */ + return "true_0" + +true?: procedure /* true?(obj) */ + return obj_type(arg(1)) == "true" + +new_false: procedure /* new_false() */ + return "fals_0" + +false?: procedure /* false?(obj) */ + return obj_type(arg(1)) == "fals" + +new_boolean: procedure /* new_boolean(cond) */ + if arg(1) then + return new_true() + else + return new_false() + +new_symbol: procedure expose values. /* new_symbol(str) */ + str = arg(1) + idx = new_value_index() + values.idx = str + return "symb_" || idx + +symbol?: procedure /* symbol?(obj) */ + return obj_type(arg(1)) == "symb" + +new_string: procedure expose values. /* new_string(str) */ + str = arg(1) + idx = new_value_index() + values.idx = str + return "stri_" || idx + +string?: procedure /* string?(obj) */ + return obj_type(arg(1)) == "stri" + +new_keyword: procedure expose values. /* new_keyword(str) */ + str = arg(1) + idx = new_value_index() + values.idx = str + return "keyw_" || idx + +keyword?: procedure /* keyword?(obj) */ + return obj_type(arg(1)) == "keyw" + +new_seq: procedure expose values. /* new_seq(type, seq) */ + type = arg(1) + seq = arg(2) + idx = new_value_index() + values.idx = seq + return type || "_" || idx + +new_list: procedure expose values. /* new_list(seq) */ + seq = arg(1) + return new_seq("list", seq) + +list?: procedure /* list?(obj) */ + return obj_type(arg(1)) == "list" + +new_vector: procedure expose values. /* new_vector(seq) */ + seq = arg(1) + return new_seq("vect", seq) + +vector?: procedure /* vector?(obj) */ + return obj_type(arg(1)) == "vect" + +sequential?: procedure /* sequential?(obj) */ + return (list?(arg(1)) | vector?(arg(1))) + +count_elements: procedure expose values. /* count_elements(lst) */ + return words(obj_val(arg(1))) + +new_hashmap: procedure expose values. /* new_hashmap(seq) */ + seq = arg(1) + return new_seq("hash", seq) + +hashmap?: procedure /* hashmap?(obj) */ + return obj_type(arg(1)) == "hash" + +contains?: procedure expose values. /* contains?(hm_val, key) */ + hm_val = arg(1) + key = arg(2) + do i=1 to words(hm_val) by 2 + if equal?(key, word(hm_val, i)) then return 1 + end + return 0 + +hashmap_get: procedure expose values. /* hashmap_get(hm_val, key) */ + hm_val = arg(1) + key = arg(2) + do i=1 to words(hm_val) by 2 + if equal?(key, word(hm_val, i)) then return word(hm_val, i + 1) + end + return "" + +new_nativefn: procedure expose values. /* new_hashmap(native_func_name) */ + native_func_name = arg(1) + idx = new_value_index() + values.idx = native_func_name + return "nafn_" || idx + +nativefn?: procedure /* nativefn?(obj) */ + return obj_type(arg(1)) == "nafn" + +new_func: procedure expose values. /* new_func(body_ast, env_idx, binds) */ + body_ast = arg(1) + env_idx = arg(2) + binds = arg(3) + is_macro = 0 + idx = new_value_index() + values.idx = body_ast env_idx binds is_macro + return "func_" || idx + +func?: procedure /* func?(obj) */ + return obj_type(arg(1)) == "func" + +func_macro?: procedure expose values. /* func_macro?(obj) */ + return func?(arg(1)) & (func_is_macro(arg(1)) == 1) + +func_body_ast: procedure expose values. /* func_body_ast(func_obj) */ + return word(obj_val(arg(1)), 1) + +func_env_idx: procedure expose values. /* func_env_idx(func_obj) */ + return word(obj_val(arg(1)), 2) + +func_binds: procedure expose values. /* func_binds(func_obj) */ + return word(obj_val(arg(1)), 3) + +func_is_macro: procedure expose values. /* func_is_macro(func_obj) */ + return word(obj_val(arg(1)), 4) + +func_mark_as_macro: procedure expose values. /* func_mark_as_macro(func_obj) */ + idx = substr(arg(1), 6) + values.idx = subword(values.idx, 1, 3) 1 + return arg(1) + +new_atom: procedure expose values. /* new_atom(obj) */ + obj = arg(1) + idx = new_value_index() + values.idx = obj + return "atom_" || idx + +atom?: procedure /* atom?(obj) */ + return obj_type(arg(1)) == "atom" + +atom_set: procedure expose values. /* atom_set(atom, new_value) */ + atom = arg(1) + new_value = arg(2) + idx = substr(atom, 6) + values.idx = new_value + return new_value + +equal_hashmap?: procedure expose values. /* equal_hashmap?(a, b) */ + hma_val = obj_val(arg(1)) + hmb_val = obj_val(arg(2)) + if words(hma_val) \= words(hmb_val) then return 0 + do i=1 to words(hma_val) by 2 + a_key = word(hma_val, i) + a_val = word(hma_val, i + 1) + b_val = hashmap_get(hmb_val, a_key) + if b_val == "" then return 0 + if \equal?(a_val, b_val) then return 0 + end + return 1 + +equal_sequential?: procedure expose values. /* equal_sequential?(a, b) */ + a_val = obj_val(arg(1)) + b_val = obj_val(arg(2)) + if words(a_val) \= words(b_val) then return 0 + do i=1 to words(a_val) + if \equal?(word(a_val, i), word(b_val, i)) then return 0 + end + return 1 + +equal?: procedure expose values. /* equal?(a, b) */ + a = arg(1) + b = arg(2) + a_type = obj_type(a) + b_type = obj_type(b) + a_val = obj_val(a) + b_val = obj_val(b) + select + when nil?(a) then return nil?(b) + when true?(a) then return true?(b) + when false?(a) then return false?(b) + when (a_type == "numb" & b_type = "numb") | , + (a_type == "symb" & b_type = "symb") | , + (a_type == "stri" & b_type = "stri") | , + (a_type == "keyw" & b_type = "keyw") then return (obj_val(a) == obj_val(b)) + when (sequential?(a) & sequential?(b)) then return equal_sequential?(a, b) + when (hashmap?(a) & hashmap?(b)) then return equal_hashmap?(a, b) + otherwise + return 0 + end + +#endif From 44a3e2ef704458c407c446b90aef345de13f9cc0 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Thu, 20 Jul 2017 12:07:01 +0000 Subject: [PATCH 0380/2308] rexx: Add Rexx to main README --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dfcb2004de..baac56710e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 66 languages: +Mal is implemented in 67 languages: * Ada * GNU awk @@ -63,6 +63,7 @@ Mal is implemented in 66 languages: * RPython * R * Racket +* Rexx * Ruby * Rust * Scala @@ -808,6 +809,18 @@ cd racket ./stepX_YYY.rkt ``` +### Rexx + +*The Rexx implementation was created by [Dov Murik](https://github.com/dubek)* + +The Rexx implementation of mal has been tested with Regina Rexx 3.6. + +``` +cd rexx +make +rexx -a ./stepX_YYY.rexxpp +``` + ### Ruby (1.9+) ``` From a66c64008abcb0156d821da34ebd68719cf8f039 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Thu, 20 Jul 2017 12:07:45 +0000 Subject: [PATCH 0381/2308] rexx: Add `rexx` to Travis CI matrix --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3a53dd5b6b..f482183a95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,6 +60,7 @@ matrix: - {env: IMPL=python, services: [docker]} - {env: IMPL=r, services: [docker]} - {env: IMPL=racket, services: [docker]} + - {env: IMPL=rexx, services: [docker]} - {env: IMPL=rpython, services: [docker]} - {env: IMPL=ruby, services: [docker]} - {env: IMPL=rust, services: [docker]} From 999efc9f4ba2f360c674b9fba535d236b6525708 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Thu, 20 Jul 2017 17:37:08 +0200 Subject: [PATCH 0382/2308] Fix init warning I mistakenly assumed that the way to retrieve the current context is by calling a documented static method. Apparently thisContext is the missing sixth keyword in Smalltalk, so that's not needed. --- gst/step0_repl.st | 2 +- gst/step1_read_print.st | 2 +- gst/step2_eval.st | 2 +- gst/step3_env.st | 2 +- gst/step4_if_fn_do.st | 2 +- gst/step5_tco.st | 2 +- gst/step6_file.st | 2 +- gst/step7_quote.st | 2 +- gst/step8_macros.st | 2 +- gst/step9_try.st | 2 +- gst/stepA_mal.st | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/gst/step0_repl.st b/gst/step0_repl.st index 622347dfc7..5549a89fcd 100644 --- a/gst/step0_repl.st +++ b/gst/step0_repl.st @@ -1,7 +1,7 @@ String extend [ String >> loadRelative [ | scriptPath scriptDirectory | - scriptPath := ContextPart thisContext currentFileName. + scriptPath := thisContext currentFileName. scriptDirectory := FilePath stripFileNameFor: scriptPath. FileStream fileIn: (FilePath append: self to: scriptDirectory) ] diff --git a/gst/step1_read_print.st b/gst/step1_read_print.st index 4957a4ef48..53384b4c05 100644 --- a/gst/step1_read_print.st +++ b/gst/step1_read_print.st @@ -1,7 +1,7 @@ String extend [ String >> loadRelative [ | scriptPath scriptDirectory | - scriptPath := ContextPart thisContext currentFileName. + scriptPath := thisContext currentFileName. scriptDirectory := FilePath stripFileNameFor: scriptPath. FileStream fileIn: (FilePath append: self to: scriptDirectory) ] diff --git a/gst/step2_eval.st b/gst/step2_eval.st index 030dbcb6d8..7682ccd9db 100644 --- a/gst/step2_eval.st +++ b/gst/step2_eval.st @@ -1,7 +1,7 @@ String extend [ String >> loadRelative [ | scriptPath scriptDirectory | - scriptPath := ContextPart thisContext currentFileName. + scriptPath := thisContext currentFileName. scriptDirectory := FilePath stripFileNameFor: scriptPath. FileStream fileIn: (FilePath append: self to: scriptDirectory) ] diff --git a/gst/step3_env.st b/gst/step3_env.st index baf0289293..9c51b7b238 100644 --- a/gst/step3_env.st +++ b/gst/step3_env.st @@ -1,7 +1,7 @@ String extend [ String >> loadRelative [ | scriptPath scriptDirectory | - scriptPath := ContextPart thisContext currentFileName. + scriptPath := thisContext currentFileName. scriptDirectory := FilePath stripFileNameFor: scriptPath. FileStream fileIn: (FilePath append: self to: scriptDirectory) ] diff --git a/gst/step4_if_fn_do.st b/gst/step4_if_fn_do.st index a61e0b21c1..b3cc590d42 100644 --- a/gst/step4_if_fn_do.st +++ b/gst/step4_if_fn_do.st @@ -1,7 +1,7 @@ String extend [ String >> loadRelative [ | scriptPath scriptDirectory | - scriptPath := ContextPart thisContext currentFileName. + scriptPath := thisContext currentFileName. scriptDirectory := FilePath stripFileNameFor: scriptPath. FileStream fileIn: (FilePath append: self to: scriptDirectory) ] diff --git a/gst/step5_tco.st b/gst/step5_tco.st index d0289b8cef..43c7381ca3 100644 --- a/gst/step5_tco.st +++ b/gst/step5_tco.st @@ -1,7 +1,7 @@ String extend [ String >> loadRelative [ | scriptPath scriptDirectory | - scriptPath := ContextPart thisContext currentFileName. + scriptPath := thisContext currentFileName. scriptDirectory := FilePath stripFileNameFor: scriptPath. FileStream fileIn: (FilePath append: self to: scriptDirectory) ] diff --git a/gst/step6_file.st b/gst/step6_file.st index 93b475ee57..9e79435f5e 100644 --- a/gst/step6_file.st +++ b/gst/step6_file.st @@ -1,7 +1,7 @@ String extend [ String >> loadRelative [ | scriptPath scriptDirectory | - scriptPath := ContextPart thisContext currentFileName. + scriptPath := thisContext currentFileName. scriptDirectory := FilePath stripFileNameFor: scriptPath. FileStream fileIn: (FilePath append: self to: scriptDirectory) ] diff --git a/gst/step7_quote.st b/gst/step7_quote.st index 7d6c70095a..bdb8d88b59 100644 --- a/gst/step7_quote.st +++ b/gst/step7_quote.st @@ -1,7 +1,7 @@ String extend [ String >> loadRelative [ | scriptPath scriptDirectory | - scriptPath := ContextPart thisContext currentFileName. + scriptPath := thisContext currentFileName. scriptDirectory := FilePath stripFileNameFor: scriptPath. FileStream fileIn: (FilePath append: self to: scriptDirectory) ] diff --git a/gst/step8_macros.st b/gst/step8_macros.st index d51ea1c048..602e44415d 100644 --- a/gst/step8_macros.st +++ b/gst/step8_macros.st @@ -1,7 +1,7 @@ String extend [ String >> loadRelative [ | scriptPath scriptDirectory | - scriptPath := ContextPart thisContext currentFileName. + scriptPath := thisContext currentFileName. scriptDirectory := FilePath stripFileNameFor: scriptPath. FileStream fileIn: (FilePath append: self to: scriptDirectory) ] diff --git a/gst/step9_try.st b/gst/step9_try.st index ae1db7fd5d..08bb9768de 100644 --- a/gst/step9_try.st +++ b/gst/step9_try.st @@ -1,7 +1,7 @@ String extend [ String >> loadRelative [ | scriptPath scriptDirectory | - scriptPath := ContextPart thisContext currentFileName. + scriptPath := thisContext currentFileName. scriptDirectory := FilePath stripFileNameFor: scriptPath. FileStream fileIn: (FilePath append: self to: scriptDirectory) ] diff --git a/gst/stepA_mal.st b/gst/stepA_mal.st index 29f15d2efc..dea76c217c 100644 --- a/gst/stepA_mal.st +++ b/gst/stepA_mal.st @@ -1,7 +1,7 @@ String extend [ String >> loadRelative [ | scriptPath scriptDirectory | - scriptPath := ContextPart thisContext currentFileName. + scriptPath := thisContext currentFileName. scriptDirectory := FilePath stripFileNameFor: scriptPath. FileStream fileIn: (FilePath append: self to: scriptDirectory) ] From 4c77d21607778fb6680549430c4d3750e353a679 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Thu, 20 Jul 2017 20:12:14 +0000 Subject: [PATCH 0383/2308] bash: Fix `string?` result on keyword arguments Fixed wrong `string?` value on keyword arguments in bash implementation (2 soft test fails). Before the fix: ``` TEST: (string? :abc) -> ['',false] -> SOFT FAIL (line 115): Expected : '(string? :abc)\r\nfalse' Got : '(string? :abc)\r\ntrue' TEST: (string? (keyword "abc")) -> ['',false] -> SOFT FAIL (line 117): Expected : '(string? (keyword "abc"))\r\nfalse' Got : '(string? (keyword "abc"))\r\ntrue' ``` With the fix: ``` TEST: (string? :abc) -> ['',false] -> SUCCESS TEST: (string? (keyword "abc")) -> ['',false] -> SUCCESS ``` --- bash/core.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash/core.sh b/bash/core.sh index 0a4d07c97a..99cc98c8ff 100644 --- a/bash/core.sh +++ b/bash/core.sh @@ -74,7 +74,7 @@ time_ms () { # String functions -string? () { _string? "${1}" && r="${__true}" || r="${__false}"; } +string? () { _string? "${1}" && ( ! _keyword? "${1}" ) && r="${__true}" || r="${__false}"; } pr_str () { local res="" From 50ed0aa9a14318b551e547a24ddaf4c5efd9e8ad Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sat, 22 Jul 2017 21:44:25 +0200 Subject: [PATCH 0384/2308] Elm step A: GC working --- elm/Env.elm | 38 +++++--------------------------------- elm/Eval.elm | 28 +++++++++++++++++++++++++++- elm/Types.elm | 1 + elm/stepA_mal.elm | 7 +------ 4 files changed, 34 insertions(+), 40 deletions(-) diff --git a/elm/Env.elm b/elm/Env.elm index ae64056437..7aaff29da8 100644 --- a/elm/Env.elm +++ b/elm/Env.elm @@ -11,7 +11,6 @@ module Env , push , pop , enter - , jump , leave , ref , pushRef @@ -54,6 +53,7 @@ global = , gcInterval = defaultGcInterval , gcCounter = 0 , stack = [] + , keepFrames = [] } @@ -220,29 +220,6 @@ enter outerId binds env = } -{-| Jump into a frame --} -jump : Int -> Env -> Env -jump frameId env = - let - setExitId = - Maybe.map - (\frame -> - { frame - | exitId = Just env.currentFrameId - , refCnt = frame.refCnt + 1 - } - ) - - bogus = - debug env "jump #" frameId - in - { env - | currentFrameId = frameId - , frames = Dict.update frameId setExitId env.frames - } - - leave : Env -> Env leave env = let @@ -332,8 +309,6 @@ Return a new Env with the unreachable frames removed. gc : MalExpr -> Env -> Env gc expr env = let - -- bogus = - -- Debug.log "GC stack = " env.stack countList acc = List.foldl countExpr acc @@ -387,7 +362,10 @@ gc expr env = acc initSet = - Set.fromList [ globalFrameId, env.currentFrameId ] + Set.fromList + ([ globalFrameId, env.currentFrameId ] + ++ env.keepFrames + ) countFrames frames acc = Set.toList frames @@ -437,16 +415,10 @@ gc expr env = filterFrames frames keep = Dict.filter (keepFilter keep) frames - - reportUnused frames keep = - Set.diff (Set.fromList (Dict.keys frames)) keep - |> Debug.log "\n\nUNUSED FRAMES\n\n" - |> always keep in countFrames initSet initSet |> countExpr expr |> (flip countList) env.stack |> loop - -- |> reportUnused env.frames |> filterFrames env.frames |> makeNewEnv diff --git a/elm/Eval.elm b/elm/Eval.elm index 1d37df3eb6..b32946e661 100644 --- a/elm/Eval.elm +++ b/elm/Eval.elm @@ -3,7 +3,6 @@ module Eval exposing (..) import Types exposing (..) import IO exposing (IO) import Env -import Printer exposing (printEnv) apply : Eval a -> Env -> EvalContext a @@ -197,3 +196,30 @@ pushRef : MalExpr -> Eval a -> Eval a pushRef ref e = modifyEnv (Env.pushRef ref) |> andThen (always e) + + +inGlobal : Eval a -> Eval a +inGlobal body = + let + enter env = + setEnv + { env + | keepFrames = env.currentFrameId :: env.keepFrames + , currentFrameId = Env.globalFrameId + } + + leave oldEnv newEnv = + { newEnv + | keepFrames = oldEnv.keepFrames + , currentFrameId = oldEnv.currentFrameId + } + in + withEnv + (\env -> + if env.currentFrameId /= Env.globalFrameId then + enter env + |> andThen (always body) + |> finally (leave env) + else + body + ) diff --git a/elm/Types.elm b/elm/Types.elm index 1cdcb110cc..9960c55362 100644 --- a/elm/Types.elm +++ b/elm/Types.elm @@ -32,6 +32,7 @@ type alias Env = , gcInterval : Int , gcCounter : Int , stack : List MalExpr + , keepFrames : List Int } diff --git a/elm/stepA_mal.elm b/elm/stepA_mal.elm index 478b7c0c40..71bdac5613 100644 --- a/elm/stepA_mal.elm +++ b/elm/stepA_mal.elm @@ -273,12 +273,7 @@ malEval : List MalExpr -> Eval MalExpr malEval args = case args of [ expr ] -> - Eval.withEnv - (\env -> - Eval.modifyEnv (Env.jump Env.globalFrameId) - |> Eval.andThen (\_ -> eval expr) - |> Eval.finally Env.leave - ) + Eval.inGlobal (eval expr) _ -> Eval.fail "unsupported arguments" From b346116e53af3138bb890c6564099be3598806eb Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sat, 22 Jul 2017 21:59:59 +0200 Subject: [PATCH 0385/2308] Elm: re-enabled steps 1-9 --- elm/Makefile | 17 ++++++++++------- elm/step4_if_fn_do.elm | 4 ++-- elm/step5_tco.elm | 4 ++-- elm/step6_file.elm | 11 +++-------- elm/step7_quote.elm | 11 +++-------- elm/step8_macros.elm | 13 ++++--------- elm/step9_try.elm | 13 ++++--------- 7 files changed, 28 insertions(+), 45 deletions(-) diff --git a/elm/Makefile b/elm/Makefile index e7ad0989a0..99fcffddf6 100644 --- a/elm/Makefile +++ b/elm/Makefile @@ -1,7 +1,10 @@ -SOURCES = stepA_mal.elm #step0_repl.elm step1_read_print.elm step2_eval.elm \ +SOURCES = step0_repl.elm step1_read_print.elm step2_eval.elm \ step3_env.elm step4_if_fn_do.elm step5_tco.elm step6_file.elm \ step7_quote.elm step8_macros.elm step9_try.elm stepA_mal.elm +SOURCES_LISP = Env.elm Core.elm Eval.elm stepA_mal.elm + + BINS = $(SOURCES:%.elm=%.js) ELM = node_modules/.bin/elm @@ -38,10 +41,10 @@ stepA_mal.js: $(STEP4_SOURCES) clean: rm -f $(BINS) -# stats: $(SOURCES) -# @wc $^ -# @printf "%5s %5s %5s %s\n" `egrep "^\w*#|^\w*$$" $^ | wc` "[comments/blanks]" +stats: $(STEP4_SOURCES) stepA_mal.elm + @wc $^ + @printf "%5s %5s %5s %s\n" `egrep "^\w*(--|\{-|-\})|^\w*$$" $^ | wc` "[comments/blanks]" -# stats-lisp: $(SOURCES_LISP) -# @wc $^ -# @printf "%5s %5s %5s %s\n" `egrep "^\w*#|^\w*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `egrep "^\w*(--|\{-|-\})|^\w*$$" $^ | wc` "[comments/blanks]" diff --git a/elm/step4_if_fn_do.elm b/elm/step4_if_fn_do.elm index c769ffaaae..113e2253c7 100644 --- a/elm/step4_if_fn_do.elm +++ b/elm/step4_if_fn_do.elm @@ -187,7 +187,7 @@ eval ast = fn args (MalFunction (UserFunc { eagerFn })) :: args -> - eagerFn [] args + eagerFn args fn :: _ -> Eval.withEnv @@ -437,7 +437,7 @@ evalFn args = UserFunc { frameId = frameId , lazyFn = fn - , eagerFn = always fn + , eagerFn = fn , isMacro = False , meta = Nothing } diff --git a/elm/step5_tco.elm b/elm/step5_tco.elm index 96a87b46cc..c460102c03 100644 --- a/elm/step5_tco.elm +++ b/elm/step5_tco.elm @@ -189,7 +189,7 @@ evalApply { frameId, bound, body } = Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) |> Eval.finally Env.leave - |> Eval.gcPass [] + |> Eval.gcPass ) @@ -475,7 +475,7 @@ evalFn args = UserFunc { frameId = frameId , lazyFn = lazyFn - , eagerFn = \_ -> lazyFn >> Eval.andThen eval + , eagerFn = lazyFn >> Eval.andThen eval , isMacro = False , meta = Nothing } diff --git a/elm/step6_file.elm b/elm/step6_file.elm index 87643819ce..670cdadaac 100644 --- a/elm/step6_file.elm +++ b/elm/step6_file.elm @@ -246,12 +246,7 @@ malEval : List MalExpr -> Eval MalExpr malEval args = case args of [ expr ] -> - Eval.withEnv - (\env -> - Eval.modifyEnv (Env.jump Env.globalFrameId) - |> Eval.andThen (\_ -> eval expr) - |> Eval.finally Env.leave - ) + Eval.inGlobal (eval expr) _ -> Eval.fail "unsupported arguments" @@ -264,7 +259,7 @@ evalApply { frameId, bound, body } = Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) |> Eval.finally Env.leave - |> Eval.gcPass [] + |> Eval.gcPass ) @@ -550,7 +545,7 @@ evalFn args = UserFunc { frameId = frameId , lazyFn = lazyFn - , eagerFn = \_ -> lazyFn >> Eval.andThen eval + , eagerFn = lazyFn >> Eval.andThen eval , isMacro = False , meta = Nothing } diff --git a/elm/step7_quote.elm b/elm/step7_quote.elm index 8a09aaa50e..1e396dce5b 100644 --- a/elm/step7_quote.elm +++ b/elm/step7_quote.elm @@ -246,12 +246,7 @@ malEval : List MalExpr -> Eval MalExpr malEval args = case args of [ expr ] -> - Eval.withEnv - (\env -> - Eval.modifyEnv (Env.jump Env.globalFrameId) - |> Eval.andThen (\_ -> eval expr) - |> Eval.finally Env.leave - ) + Eval.inGlobal (eval expr) _ -> Eval.fail "unsupported arguments" @@ -264,7 +259,7 @@ evalApply { frameId, bound, body } = Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) |> Eval.finally Env.leave - |> Eval.gcPass [] + |> Eval.gcPass ) @@ -549,7 +544,7 @@ evalFn args = UserFunc { frameId = frameId , lazyFn = lazyFn - , eagerFn = \_ -> lazyFn >> Eval.andThen eval + , eagerFn = lazyFn >> Eval.andThen eval , isMacro = False , meta = Nothing } diff --git a/elm/step8_macros.elm b/elm/step8_macros.elm index adf9caa2f0..c089a90f08 100644 --- a/elm/step8_macros.elm +++ b/elm/step8_macros.elm @@ -262,12 +262,7 @@ malEval : List MalExpr -> Eval MalExpr malEval args = case args of [ expr ] -> - Eval.withEnv - (\env -> - Eval.modifyEnv (Env.jump Env.globalFrameId) - |> Eval.andThen (\_ -> eval expr) - |> Eval.finally Env.leave - ) + Eval.inGlobal (eval expr) _ -> Eval.fail "unsupported arguments" @@ -280,7 +275,7 @@ evalApply { frameId, bound, body } = Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) |> Eval.finally Env.leave - |> Eval.gcPass [] + |> Eval.gcPass ) @@ -604,7 +599,7 @@ evalFn args = UserFunc { frameId = frameId , lazyFn = lazyFn - , eagerFn = \_ -> lazyFn >> Eval.andThen eval + , eagerFn = lazyFn >> Eval.andThen eval , isMacro = False , meta = Nothing } @@ -687,7 +682,7 @@ macroexpand expr = case Env.get name env of Ok (MalFunction (UserFunc fn)) -> if fn.isMacro then - Left <| fn.eagerFn [] args + Left <| fn.eagerFn args else Right expr diff --git a/elm/step9_try.elm b/elm/step9_try.elm index ba886aae6d..356584ec5f 100644 --- a/elm/step9_try.elm +++ b/elm/step9_try.elm @@ -262,12 +262,7 @@ malEval : List MalExpr -> Eval MalExpr malEval args = case args of [ expr ] -> - Eval.withEnv - (\env -> - Eval.modifyEnv (Env.jump Env.globalFrameId) - |> Eval.andThen (\_ -> eval expr) - |> Eval.finally Env.leave - ) + Eval.inGlobal (eval expr) _ -> Eval.fail "unsupported arguments" @@ -280,7 +275,7 @@ evalApply { frameId, bound, body } = Eval.modifyEnv (Env.enter frameId bound) |> Eval.andThen (\_ -> evalNoApply body) |> Eval.finally Env.leave - |> Eval.gcPass [] + |> Eval.gcPass ) @@ -606,7 +601,7 @@ evalFn args = UserFunc { frameId = frameId , lazyFn = lazyFn - , eagerFn = \_ -> lazyFn >> Eval.andThen eval + , eagerFn = lazyFn >> Eval.andThen eval , isMacro = False , meta = Nothing } @@ -689,7 +684,7 @@ macroexpand expr = case Env.get name env of Ok (MalFunction (UserFunc fn)) -> if fn.isMacro then - Left <| fn.eagerFn [] args + Left <| fn.eagerFn args else Right expr From f38c7ffad12aea5b3c4b8bbccf7c0f4c14662721 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sat, 22 Jul 2017 22:18:35 +0200 Subject: [PATCH 0386/2308] Elm: fix makefile, added to readme and enable travis --- .travis.yml | 1 + Makefile | 2 +- README.md | 13 +++++++++++++ elm/Makefile | 7 +++---- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 711212ab7d..c9aa9b5c68 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,7 @@ matrix: - {env: IMPL=dart, services: [docker]} - {env: IMPL=elisp, services: [docker]} - {env: IMPL=elixir, services: [docker]} + - {env: IMPL=elm, services: [docker]} - {env: IMPL=erlang NO_PERF=1, services: [docker]} # perf runs out of memory - {env: IMPL=es6, services: [docker]} - {env: IMPL=factor, services: [docker]} diff --git a/Makefile b/Makefile index 7473c11cc0..cd6872971e 100644 --- a/Makefile +++ b/Makefile @@ -216,7 +216,7 @@ vhdl_STEP_TO_PROG = vhdl/$($(1)) vimscript_STEP_TO_PROG = vimscript/$($(1)).vim guile_STEP_TO_PROG = guile/$($(1)).scm livescript_STEP_TO_PROG = livescript/$($(1)).js -elm_STEP_TO_PROG = elm/$($(1)).elm +elm_STEP_TO_PROG = elm/$($(1)).js # Needed some argument munging diff --git a/README.md b/README.md index 97aa11167a..94e60a029d 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Mal is implemented in 65 languages: * D * Dart * Elixir +* Elm * Emacs Lisp * Erlang * ES6 (ECMAScript 6 / ECMAScript 2015) @@ -345,6 +346,18 @@ mix stepX_YYY iex -S mix stepX_YYY ``` +### Elm + +*The Elm implementation was created by [Jos van Bakel](https://github.com/c0deaddict)* + +The Elm implementation of mal has been tested with Elm 0.18.0 + +``` +cd elm +make stepX_YYY.js +STEP=stepX_YYY ./run +``` + ### Erlang *The Erlang implementation was created by [Nathan Fiedler (nlfiedler)](https://github.com/nlfiedler)* diff --git a/elm/Makefile b/elm/Makefile index 99fcffddf6..b20685f406 100644 --- a/elm/Makefile +++ b/elm/Makefile @@ -4,20 +4,19 @@ SOURCES = step0_repl.elm step1_read_print.elm step2_eval.elm \ SOURCES_LISP = Env.elm Core.elm Eval.elm stepA_mal.elm - BINS = $(SOURCES:%.elm=%.js) ELM = node_modules/.bin/elm -all: node_modules $(BINS) +all: node_modules elm_packages $(BINS) node_modules: npm install elm_packages: - $(ELM) package install + $(ELM) package install -y -%.js: %.elm node_modules +%.js: %.elm node_modules elm_packages $(ELM) make $(@:%.js=%.elm) --output $@ STEP0_SOURCES = IO.elm From 1a9bc42c5a37049bd3de331bfb77882799cdf457 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Sat, 22 Jul 2017 22:21:45 +0200 Subject: [PATCH 0387/2308] Elm: revert change in core.mal --- mal/core.mal | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mal/core.mal b/mal/core.mal index dbf7944c6d..a766b80125 100644 --- a/mal/core.mal +++ b/mal/core.mal @@ -60,6 +60,4 @@ ["atom?" atom?] ["deref" deref] ["reset!" reset!] - ["swap!" swap!] - ["gc" gc] - ["pr-env" pr-env]]) + ["swap!" swap!]]) From 9c47ff44dac16eec9a6d2dd15e9c4d13c89e97ba Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Tue, 25 Jul 2017 09:55:52 +0200 Subject: [PATCH 0388/2308] Elm: use direct paths in Makefile --- elm/Makefile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/elm/Makefile b/elm/Makefile index b20685f406..1ad56ff655 100644 --- a/elm/Makefile +++ b/elm/Makefile @@ -6,7 +6,8 @@ SOURCES_LISP = Env.elm Core.elm Eval.elm stepA_mal.elm BINS = $(SOURCES:%.elm=%.js) -ELM = node_modules/.bin/elm +ELM_MAKE = node_modules/.bin/elm-make +ELM_PACKAGE = node_modules/.bin/elm-package all: node_modules elm_packages $(BINS) @@ -14,10 +15,10 @@ node_modules: npm install elm_packages: - $(ELM) package install -y + $(ELM_PACKAGE) install -y %.js: %.elm node_modules elm_packages - $(ELM) make $(@:%.js=%.elm) --output $@ + $(ELM_MAKE) $(@:%.js=%.elm) --output $@ STEP0_SOURCES = IO.elm STEP1_SOURCES = $(STEP0_SOURCES) Reader.elm Printer.elm Utils.elm Types.elm Env.elm From 224f4c78eab3d0a11e603ea9966fc9f0679039b8 Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Tue, 25 Jul 2017 16:42:53 +0200 Subject: [PATCH 0389/2308] Elm: fixed Dockerfile --- elm/.dockerignore | 1 + elm/Dockerfile | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 elm/.dockerignore diff --git a/elm/.dockerignore b/elm/.dockerignore new file mode 100644 index 0000000000..3c3629e647 --- /dev/null +++ b/elm/.dockerignore @@ -0,0 +1 @@ +node_modules diff --git a/elm/Dockerfile b/elm/Dockerfile index fbb4b5572e..ef06b82a21 100644 --- a/elm/Dockerfile +++ b/elm/Dockerfile @@ -12,7 +12,7 @@ RUN apt-get -y update RUN apt-get -y install make python # Some typical implementation and test requirements -RUN apt-get -y install curl libreadline-dev libedit-dev +RUN apt-get -y install curl libreadline-dev libedit-dev netbase RUN mkdir -p /mal WORKDIR /mal @@ -28,4 +28,7 @@ RUN apt-get -y install g++ RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - # Install nodejs -RUN apt-get -y install nodejs \ No newline at end of file +RUN apt-get -y install nodejs + +ENV HOME /mal +ENV NPM_CONFIG_CACHE /mal/.npm From 6351d14c6c84a72b8feccd24de6a90f64624e1c1 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 25 Jul 2017 11:12:46 -0500 Subject: [PATCH 0390/2308] elm: move more specific deps lower in the file. --- elm/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/elm/Dockerfile b/elm/Dockerfile index ef06b82a21..5b3bc3e271 100644 --- a/elm/Dockerfile +++ b/elm/Dockerfile @@ -12,7 +12,7 @@ RUN apt-get -y update RUN apt-get -y install make python # Some typical implementation and test requirements -RUN apt-get -y install curl libreadline-dev libedit-dev netbase +RUN apt-get -y install curl libreadline-dev libedit-dev RUN mkdir -p /mal WORKDIR /mal @@ -21,8 +21,8 @@ WORKDIR /mal # Specific implementation requirements ########################################################## -# For building node modules -RUN apt-get -y install g++ +# For building node modules and pulling elm packages +RUN apt-get -y install g++ netbase # Add nodesource apt repo config for 7.X RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - From 74771ea2a9a58e07f71c3213957fb60d27dc91c8 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 25 Jul 2017 11:13:13 -0500 Subject: [PATCH 0391/2308] plpgsql: support Travis run with alt. user Seems that Travis sometimes runs with a user other than 1001 so add user ID 2000 as well. --- plpgsql/Dockerfile | 4 +++- plpgsql/entrypoint.sh | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/plpgsql/Dockerfile b/plpgsql/Dockerfile index bde9fcdd23..eb64c1ae34 100644 --- a/plpgsql/Dockerfile +++ b/plpgsql/Dockerfile @@ -19,8 +19,9 @@ ENV HOME=/var/run/postgresql WORKDIR /mal -# Travis runs as user ID 1001 so add that user +# Travis runs as a couple of different users so add them RUN useradd -ou 1001 -m -s /bin/bash -G sudo,postgres travis +RUN useradd -ou 2000 -m -s /bin/bash -G sudo,postgres travis2 # Enable postgres and travis users to sudo for postgres startup RUN echo "%sudo ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers @@ -29,6 +30,7 @@ RUN echo "%sudo ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers RUN sed -i 's/peer$/peer map=mal/' /etc/postgresql/9.4/main/pg_hba.conf RUN echo "mal postgres postgres" >> /etc/postgresql/9.4/main/pg_ident.conf RUN echo "mal travis postgres" >> /etc/postgresql/9.4/main/pg_ident.conf +RUN echo "mal travis2 postgres" >> /etc/postgresql/9.4/main/pg_ident.conf # Add entrypoint.sh which starts postgres then run bash/command ADD entrypoint.sh /entrypoint.sh diff --git a/plpgsql/entrypoint.sh b/plpgsql/entrypoint.sh index 76b614af31..6eaf5164b7 100755 --- a/plpgsql/entrypoint.sh +++ b/plpgsql/entrypoint.sh @@ -9,9 +9,9 @@ while [[ ${1:0:1} = '-' ]]; do done sudo --user=${POSTGRES_SUDO_USER} \ - /usr/lib/postgresql/9.4/bin/postgres \ + bash -c "/usr/lib/postgresql/9.4/bin/postgres \ -c config_file=/etc/postgresql/9.4/main/postgresql.conf \ - ${POPTS} >/var/log/postgresql/output.log 2>&1 & disown -h + ${POPTS} >/var/log/postgresql/output.log 2>&1" & disown -h while ! ( echo "" > /dev/tcp/localhost/5432) 2>/dev/null; do echo "Waiting for postgres to start" From b5775743e151a7f00bd5da744678166326b65a38 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 25 Jul 2017 11:18:33 -0500 Subject: [PATCH 0392/2308] README: update to 68 languages. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7debacefea..4cc1e729e6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 67 languages: +Mal is implemented in 68 languages: * Ada * GNU awk From 2110814e43e464afceea62ae9a997944339d5a1c Mon Sep 17 00:00:00 2001 From: Jos van Bakel Date: Tue, 25 Jul 2017 22:19:56 +0200 Subject: [PATCH 0393/2308] Elm: Removed usage of Env from step1 and step2 --- elm/Eval.elm | 13 +++++++++++++ elm/Printer.elm | 21 +++++++-------------- elm/step1_read_print.elm | 5 ++--- elm/step2_eval.elm | 14 +++++--------- elm/step3_env.elm | 9 +++------ 5 files changed, 30 insertions(+), 32 deletions(-) diff --git a/elm/Eval.elm b/elm/Eval.elm index b32946e661..d05888f947 100644 --- a/elm/Eval.elm +++ b/elm/Eval.elm @@ -223,3 +223,16 @@ inGlobal body = else body ) + + +runSimple : Eval a -> Result MalExpr a +runSimple e = + case run Env.global e of + ( _, EvalOk res ) -> + Ok res + + ( _, EvalErr msg ) -> + Err msg + + _ -> + Debug.crash "can't happen" diff --git a/elm/Printer.elm b/elm/Printer.elm index 6359a23925..2c4402a027 100644 --- a/elm/Printer.elm +++ b/elm/Printer.elm @@ -7,6 +7,11 @@ import Utils exposing (encodeString, wrap) import Env +printStr : Bool -> MalExpr -> String +printStr = + printString Env.global + + printString : Env -> Bool -> MalExpr -> String printString env readably ast = case ast of @@ -40,18 +45,6 @@ printString env readably ast = MalMap map -> printMap env readably map - MalFunction (UserFunc { frameId, meta }) -> - "# - "" - - Just meta -> - " meta=" ++ printString env True meta - ) - ++ ">" - MalFunction _ -> "#" @@ -62,8 +55,8 @@ printString env readably ast = in "(atom " ++ (printString env True value) ++ ")" - MalApply { frameId, bound } -> - "#" + MalApply _ -> + "#" printBound : Env -> Bool -> List ( String, MalExpr ) -> String diff --git a/elm/step1_read_print.elm b/elm/step1_read_print.elm index c0653c81b2..46476bf583 100644 --- a/elm/step1_read_print.elm +++ b/elm/step1_read_print.elm @@ -5,8 +5,7 @@ import Json.Decode exposing (decodeValue) import Platform exposing (programWithFlags) import Types exposing (MalExpr(..)) import Reader exposing (readString) -import Printer exposing (printString) -import Env +import Printer exposing (printStr) main : Program Flags Model Msg @@ -86,7 +85,7 @@ eval ast = print : MalExpr -> String print = - printString Env.global True + printStr True {-| Read-Eval-Print diff --git a/elm/step2_eval.elm b/elm/step2_eval.elm index 39ebf8a751..d23e1d8cea 100644 --- a/elm/step2_eval.elm +++ b/elm/step2_eval.elm @@ -5,13 +5,12 @@ import Json.Decode exposing (decodeValue) import Platform exposing (programWithFlags) import Types exposing (..) import Reader exposing (readString) -import Printer exposing (printString) +import Printer exposing (printStr) import Utils exposing (maybeToList, zip) import Dict exposing (Dict) import Tuple exposing (mapFirst, second) import Array import Eval -import Env main : Program Flags Model Msg @@ -135,16 +134,13 @@ eval env ast = ( Err "can't happen", newEnv ) (MalFunction (CoreFunc fn)) :: args -> - case second <| Eval.run Env.global (fn args) of - EvalOk res -> + case Eval.runSimple (fn args) of + Ok res -> ( Ok res, newEnv ) - EvalErr msg -> + Err msg -> ( Err (print msg), newEnv ) - _ -> - Debug.crash "can't happen" - fn :: _ -> ( Err ((print fn) ++ " is not a function"), newEnv ) @@ -232,7 +228,7 @@ tryMapList fn list = print : MalExpr -> String print = - printString Env.global True + printStr True {-| Read-Eval-Print. rep returns: diff --git a/elm/step3_env.elm b/elm/step3_env.elm index ddcb858f55..59a592c874 100644 --- a/elm/step3_env.elm +++ b/elm/step3_env.elm @@ -135,16 +135,13 @@ eval env ast = ( Err "can't happen", newEnv ) (MalFunction (CoreFunc fn)) :: args -> - case second <| Eval.run Env.global (fn args) of - EvalOk res -> + case Eval.runSimple (fn args) of + Ok res -> ( Ok res, newEnv ) - EvalErr msg -> + Err msg -> ( Err (print msg), newEnv ) - _ -> - Debug.crash "can't happen" - fn :: _ -> ( Err ((print fn) ++ " is not a function"), newEnv ) From 62fc786e1b7c0a1a6448500b691c93baa3e6d04f Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 26 Jul 2017 23:53:39 -0500 Subject: [PATCH 0394/2308] [clojure] Fixes to support lumo 1.6.0. - Pass down the CLJ_MODE value for DOCKERIZED builds in addition to run/tests. - Correct deps so that node_modules is installed for CLJ_MODE=cljs for all steps. - Lumo 1.6.0 is currently not able to install globally as root (https://github.com/anmonteiro/lumo/issues/206) so add the local node_modules/.bin to the PATH in the run script. - Reader conditionals needed for clojure.reader/cljs.reader until this is fixed: https://github.com/anmonteiro/lumo/issues/209 --- .gitignore | 3 +++ Makefile | 12 ++++++++++-- clojure/Dockerfile | 14 ++++++++++---- clojure/Makefile | 7 ++++++- clojure/package.json | 2 +- clojure/run | 1 + clojure/src/mal/reader.cljc | 6 ++++-- 7 files changed, 35 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index b5bfa49851..279cf5c950 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,14 @@ .bash_history .cache +.config .mal-history .crystal .lein .m2 .ivy2 .sbt +.npm +.node-gyp */experiments */node_modules *.o diff --git a/Makefile b/Makefile index 0cdecb0eba..20a663629d 100644 --- a/Makefile +++ b/Makefile @@ -237,7 +237,15 @@ actual_impl = $(if $(filter mal,$(1)),$(MAL_IMPL),$(1)) # Returns nothing if DOCKERIZE is not set, otherwise returns the # docker prefix necessary to run make within the docker environment # for this impl -get_build_prefix = $(if $(strip $(DOCKERIZE)),docker run -it --rm -u $(shell id -u) -v $(dir $(abspath $(lastword $(MAKEFILE_LIST)))):/mal -w /mal/$(1) $(if $(filter factor,$(1)),-e FACTOR_ROOTS=$(FACTOR_ROOTS),) $(call impl_to_image,$(1)) ,) +get_build_prefix = $(strip $(if $(strip $(DOCKERIZE)),\ + docker run \ + -it --rm -u $(shell id -u) \ + -v $(dir $(abspath $(lastword $(MAKEFILE_LIST)))):/mal \ + -w /mal/$(1) \ + $(if $(filter clojure,$(1)),-e CLJ_MODE=$(CLJ_MODE),) \ + $(if $(filter factor,$(1)),-e FACTOR_ROOTS=$(FACTOR_ROOTS),) \ + $(call impl_to_image,$(1)) \ + ,)) # Takes impl and step arguments # Returns a command prefix (docker command and environment variables) @@ -304,7 +312,7 @@ ALL_REPL = $(strip $(sort \ $(foreach i,$(DO_IMPLS),$(foreach s,$(STEPS),$(call $(i)_STEP_TO_PROG,$(s)))): $(foreach impl,$(word 1,$(subst /, ,$(@))),\ $(if $(DOCKERIZE), \ - $(call get_build_prefix,$(impl))$(MAKE) $(patsubst $(impl)/%,%,$(@)), \ + $(call get_build_prefix,$(impl)) $(MAKE) $(patsubst $(impl)/%,%,$(@)), \ $(MAKE) -C $(impl) $(subst $(impl)/,,$(@)))) # Allow IMPL, and IMPL^STEP diff --git a/clojure/Dockerfile b/clojure/Dockerfile index 1784e6e189..2a8eb37114 100644 --- a/clojure/Dockerfile +++ b/clojure/Dockerfile @@ -40,8 +40,14 @@ ENV LEIN_JVM_OPTS -Duser.home=/mal # For building node modules RUN apt-get -y install g++ -# Add nodesource apt repo config for 7.X -RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - +# Add nodesource apt repo config for 8.X +RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - + +# Install nodejs. +RUN apt-get -y install nodejs + +## Install ffi and lumo-cljs modules globally +#RUN npm install -g ffi lumo-cljs + +ENV HOME=/mal -# Install nodejs. Install ffi and lumo-cljs modules globally -RUN apt-get -y install nodejs && npm install -g ffi lumo-cljs diff --git a/clojure/Makefile b/clojure/Makefile index 5931cf94de..f1b4039848 100644 --- a/clojure/Makefile +++ b/clojure/Makefile @@ -5,7 +5,7 @@ SOURCES_LISP = src/mal/env.cljc src/mal/core.cljc src/mal/stepA_mal.cljc SRCS = $(SOURCES_BASE) src/mal/env.cljc src/mal/core.cljc SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -all: $(if $(filter cljs,$(CLJ_MODE)),node_modules,deps) +DEPS = $(if $(filter cljs,$(CLJ_MODE)),node_modules,deps) dist: mal.jar mal @@ -21,6 +21,11 @@ mal: mal.jar cat <(echo -e '#!/bin/sh\nexec java -jar "$$0" "$$@"') mal.jar > $@ chmod +x mal +src/mal/%.cljc: $(DEPS) + @true + +#src/mal/stepA_mal.cljc: $(DEPS) + target/%.jar: src/mal/%.cljc $(SRCS) lein with-profile $(word 1,$(subst _, ,$*)) uberjar diff --git a/clojure/package.json b/clojure/package.json index dfaab9cd82..ea4479f733 100644 --- a/clojure/package.json +++ b/clojure/package.json @@ -4,6 +4,6 @@ "description": "Make a Lisp (mal) language implemented in ClojureScript", "dependencies": { "ffi": "2.2.x", - "lumo-cljs": "1.0.0" + "lumo-cljs": "^1.6.0" } } diff --git a/clojure/run b/clojure/run index a00d501ad4..54c14f8014 100755 --- a/clojure/run +++ b/clojure/run @@ -1,4 +1,5 @@ #!/bin/bash +export PATH=$PATH:$(dirname $0)/node_modules/.bin STEP=${STEP:-stepA_mal} if [ "${CLJ_MODE}" = "cljs" ]; then exec lumo -c $(dirname $0)/src -m mal.${STEP//_/-} "${@}" diff --git a/clojure/src/mal/reader.cljc b/clojure/src/mal/reader.cljc index 1e7c7e6e99..fbe36d5788 100644 --- a/clojure/src/mal/reader.cljc +++ b/clojure/src/mal/reader.cljc @@ -1,7 +1,9 @@ (ns mal.reader (:refer-clojure :exclude [read-string]) - (:require [clojure.tools.reader :as r] - [clojure.tools.reader.reader-types :as rt])) + #?(:clj (:require [clojure.tools.reader :as r] + [clojure.tools.reader.reader-types :as rt])) + #?(:cljs (:require [cljs.tools.reader :as r] + [cljs.tools.reader.reader-types :as rt]))) ;; change tools.reader syntax-quote to quasiquote (defn- wrap [sym] From 259b5c9420f6aadebf091d7783d726d6249c7468 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 27 Jul 2017 00:19:00 -0500 Subject: [PATCH 0395/2308] [clojure] fix uberjar file name. The project.clj sets the uberjar path so the build needs to copy from that location. --- clojure/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clojure/Makefile b/clojure/Makefile index f1b4039848..4c468efd29 100644 --- a/clojure/Makefile +++ b/clojure/Makefile @@ -14,7 +14,7 @@ deps: mal.jar: $(SOURCES) lein with-profile stepA uberjar - cp target/mal-0.0.1-SNAPSHOT-standalone.jar $@ + cp target/stepA_mal.jar $@ SHELL := bash mal: mal.jar From e4d2540897258c90c29f0081ac742cede8915175 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 27 Jul 2017 00:35:46 -0500 Subject: [PATCH 0396/2308] [clojure] add ClojureScript mode to travis list. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e211b438ac..fc6c6a7ffe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,8 @@ matrix: - {env: IMPL=coffee, services: [docker]} - {env: IMPL=cs, services: [docker]} - {env: IMPL=chuck, services: [docker]} - - {env: IMPL=clojure, services: [docker]} + - {env: IMPL=clojure CLJ_MODE=clj, services: [docker]} + - {env: IMPL=clojure CLJ_MODE=cljs, services: [docker]} - {env: IMPL=common-lisp, services: [docker]} - {env: IMPL=crystal, services: [docker]} - {env: IMPL=d, services: [docker]} From 7a6fdd1b4c959843c622e1223c4f9a6652cae242 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 27 Jul 2017 00:52:31 -0500 Subject: [PATCH 0397/2308] [travis] pass down CLJ_MODE --- .travis_build.sh | 6 ++++-- .travis_test.sh | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.travis_build.sh b/.travis_build.sh index 308965ec24..5adf810cee 100755 --- a/.travis_build.sh +++ b/.travis_build.sh @@ -21,7 +21,9 @@ if [ -z "${NO_DOCKER}" ]; then make -C ${BUILD_IMPL} step9_try || true fi docker run -it -u $(id -u) -v `pwd`:/mal kanaka/mal-test-${img_impl} \ - make -C ${BUILD_IMPL} + make CLJ_MODE=${CLJ_MODE} \ + -C ${BUILD_IMPL} else - make -C ${BUILD_IMPL} + make CLJ_MODE=${CLJ_MODE} \ + -C ${BUILD_IMPL} fi diff --git a/.travis_test.sh b/.travis_test.sh index af5d16c61b..0a3d95062d 100755 --- a/.travis_test.sh +++ b/.travis_test.sh @@ -22,7 +22,9 @@ else fi ${MAKE} TEST_OPTS="--debug-file ../${ACTION}.err" \ - MAL_IMPL=${MAL_IMPL} ${ACTION}^${IMPL} + MAL_IMPL=${MAL_IMPL} \ + CLJ_MODE=${CLJ_MODE} \ + ${ACTION}^${IMPL} # no failure so remove error log rm -f ${ACTION}.err || true From f88f1ca1e1ffe79aaf5258a5581166c9fa4e4be2 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 22 Nov 2016 13:42:27 +0530 Subject: [PATCH 0398/2308] Common Lisp: Fix warning while compiling core.lisp --- common-lisp/core.lisp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common-lisp/core.lisp b/common-lisp/core.lisp index c87779eef6..e333c8cdd4 100644 --- a/common-lisp/core.lisp +++ b/common-lisp/core.lisp @@ -145,8 +145,8 @@ (or (nth (types:mal-data-value index) (map 'list #'identity (types:mal-data-value sequence))) (error 'index-error - :size (length (mal-value sequence)) - :index (mal-value index) + :size (length (types:mal-data-value sequence)) + :index (types:mal-data-value index) :sequence sequence))) (defun mal-first (sequence) From efe3f67629f96339b1b672e542d2964729149148 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 22 Nov 2016 13:44:20 +0530 Subject: [PATCH 0399/2308] Common Lisp: Do not create boolean and nil while reading --- common-lisp/reader.lisp | 6 +++--- common-lisp/types.lisp | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/common-lisp/reader.lisp b/common-lisp/reader.lisp index 539949629e..bd609ff216 100644 --- a/common-lisp/reader.lisp +++ b/common-lisp/reader.lisp @@ -166,11 +166,11 @@ raised" (let ((token (next reader))) (cond ((string= token "false") - (make-mal-boolean nil)) + mal-false) ((string= token "true") - (make-mal-boolean t)) + mal-true) ((string= token "nil") - (make-mal-nil nil)) + mal-nil) ((char= (char token 0) #\") (make-mal-string (parse-string token))) ((char= (char token 0) #\:) diff --git a/common-lisp/types.lisp b/common-lisp/types.lisp index 9b1cdf28fe..9b29799efd 100644 --- a/common-lisp/types.lisp +++ b/common-lisp/types.lisp @@ -124,7 +124,6 @@ (define-mal-type symbol) (define-mal-type keyword) (define-mal-type string) -;; TODO true, false and nil should ideally be singleton (define-mal-type boolean) (define-mal-type nil) From 1a33a0b66e97018fee1c9db9b0fbe83423be07f2 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 22 Nov 2016 13:45:40 +0530 Subject: [PATCH 0400/2308] Common Lisp: Use genhash APIs instead of native API in (un)wrap-value --- common-lisp/types.lisp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/common-lisp/types.lisp b/common-lisp/types.lisp index 9b29799efd..2d97b554ff 100644 --- a/common-lisp/types.lisp +++ b/common-lisp/types.lisp @@ -223,10 +223,10 @@ (list (make-mal-list (map 'list #'wrap-value value))) (vector (make-mal-vector (map 'vector #'wrap-value value))) (hash-table (make-mal-hash-map (let ((new-hash-table (make-mal-value-hash-table))) - (loop - for key being the hash-keys of value - do (setf (gethash (wrap-value key) new-hash-table) - (wrap-value (gethash key value)))) + (genhash:hashmap (lambda (key value) + (setf (genhash:hashref (wrap-value key) new-hash-table) + (wrap-value value))) + value) new-hash-table))))) (defun unwrap-value (value) @@ -235,10 +235,10 @@ (vector (map 'vector #'unwrap-value (mal-data-value value))) (hash-map (let ((hash-table (make-hash-table)) (hash-map-value (mal-data-value value))) - (loop - for key being the hash-keys of hash-map-value - do (setf (gethash (mal-data-value key) hash-table) - (mal-data-value (gethash key hash-map-value)))) + (genhash:hashmap (lambda (key value) + (setf (genhash:hashref (mal-data-value key) hash-table) + (mal-data-value value))) + hash-map-value) hash-table)) (any (mal-data-value value)))) From 0795349bc4b1b8193cf8c4402a9a0460e8da09b9 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 22 Nov 2016 13:47:28 +0530 Subject: [PATCH 0401/2308] Common Lisp: DRY conversion of vectors to list --- common-lisp/reader.lisp | 5 +++-- common-lisp/step0_repl.lisp | 5 +++-- common-lisp/step1_read_print.lisp | 10 ++++++---- common-lisp/step2_eval.lisp | 12 ++++++++---- common-lisp/step3_env.lisp | 14 ++++++++------ common-lisp/step4_if_fn_do.lisp | 15 +++++++++------ common-lisp/step5_tco.lisp | 15 +++++++++------ common-lisp/step6_file.lisp | 18 ++++++++++-------- common-lisp/step7_quote.lisp | 18 ++++++++++-------- common-lisp/step8_macros.lisp | 18 ++++++++++-------- common-lisp/step9_try.lisp | 14 ++++++++------ common-lisp/stepA_mal.lisp | 18 ++++++++++-------- common-lisp/types.lisp | 13 ++++++++----- common-lisp/utils.lisp | 7 ++++++- 14 files changed, 108 insertions(+), 74 deletions(-) diff --git a/common-lisp/reader.lisp b/common-lisp/reader.lisp index bd609ff216..ba4f44c828 100644 --- a/common-lisp/reader.lisp +++ b/common-lisp/reader.lisp @@ -1,7 +1,8 @@ (defpackage :reader (:use :common-lisp - :types - :genhash) + :types) + (:import-from :genhash + :hashref) (:import-from :cl-ppcre :create-scanner :do-matches-as-strings diff --git a/common-lisp/step0_repl.lisp b/common-lisp/step0_repl.lisp index 4f43b58884..c881dd5a75 100644 --- a/common-lisp/step0_repl.lisp +++ b/common-lisp/step0_repl.lisp @@ -1,6 +1,7 @@ (defpackage :mal - (:use :common-lisp - :uiop) + (:use :common-lisp) + (:import-from :uiop + :getenv) (:export :main)) (in-package :mal) diff --git a/common-lisp/step1_read_print.lisp b/common-lisp/step1_read_print.lisp index f0032b62d0..b16578b629 100644 --- a/common-lisp/step1_read_print.lisp +++ b/common-lisp/step1_read_print.lisp @@ -1,8 +1,9 @@ (defpackage :mal (:use :common-lisp :reader - :printer - :utils) + :printer) + (:import-from :utils + :getenv) (:export :main)) (in-package :mal) @@ -11,6 +12,7 @@ (reader:read-str string)) (defun mal-eval (ast env) + (declare (ignorable env)) ast) (defun mal-print (expression) @@ -48,8 +50,8 @@ (defun main (&optional (argv nil argv-provided-p)) (declare (ignorable argv argv-provided-p)) - (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") - (string= (uiop:getenv "TERM") "dumb")))) + (setf *use-readline-p* (not (or (string= (utils:getenv "PERL_RL") "false") + (string= (utils:getenv "TERM") "dumb")))) ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment diff --git a/common-lisp/step2_eval.lisp b/common-lisp/step2_eval.lisp index b19c5fdf2d..e3d33b5a31 100644 --- a/common-lisp/step2_eval.lisp +++ b/common-lisp/step2_eval.lisp @@ -3,8 +3,12 @@ :types :env :reader - :printer - :genhash) + :printer) + (:import-from :genhash + :hashref + :hashmap) + (:import-from :utils + :getenv) (:export :main)) (in-package :mal) @@ -111,8 +115,8 @@ (defun main (&optional (argv nil argv-provided-p)) (declare (ignorable argv argv-provided-p)) - (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") - (string= (uiop:getenv "TERM") "dumb")))) + (setf *use-readline-p* (not (or (string= (utils:getenv "PERL_RL") "false") + (string= (utils:getenv "TERM") "dumb")))) ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment diff --git a/common-lisp/step3_env.lisp b/common-lisp/step3_env.lisp index 7bb2057b15..3365a72f65 100644 --- a/common-lisp/step3_env.lisp +++ b/common-lisp/step3_env.lisp @@ -5,6 +5,11 @@ :reader :printer :genhash) + (:import-from :genhash + :hashref + :hashmap) + (:import-from :utils + :getenv) (:export :main)) (in-package :mal) @@ -66,10 +71,7 @@ (defun eval-let* (forms env) (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (types:mal-data-value (second forms))))) + (bindings (utils:listify (types:mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env @@ -140,8 +142,8 @@ (defun main (&optional (argv nil argv-provided-p)) (declare (ignorable argv argv-provided-p)) - (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") - (string= (uiop:getenv "TERM") "dumb")))) + (setf *use-readline-p* (not (or (string= (utils:getenv "PERL_RL") "false") + (string= (utils:getenv "TERM") "dumb")))) ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment diff --git a/common-lisp/step4_if_fn_do.lisp b/common-lisp/step4_if_fn_do.lisp index 1f7bfa3878..52b3a934bf 100644 --- a/common-lisp/step4_if_fn_do.lisp +++ b/common-lisp/step4_if_fn_do.lisp @@ -5,6 +5,12 @@ :reader :printer :core) + (:import-from :genhash + :hashref + :hashmap) + (:import-from :utils + :listify + :getenv) (:export :main)) (in-package :mal) @@ -46,10 +52,7 @@ (defun eval-let* (forms env) (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (mal-data-value (second forms))))) + (bindings (utils:listify (types:mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env @@ -143,8 +146,8 @@ (defun main (&optional (argv nil argv-provided-p)) (declare (ignorable argv argv-provided-p)) - (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") - (string= (uiop:getenv "TERM") "dumb")))) + (setf *use-readline-p* (not (or (string= (utils:getenv "PERL_RL") "false") + (string= (utils:getenv "TERM") "dumb")))) ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment diff --git a/common-lisp/step5_tco.lisp b/common-lisp/step5_tco.lisp index aa6244f97a..181bfab623 100644 --- a/common-lisp/step5_tco.lisp +++ b/common-lisp/step5_tco.lisp @@ -5,6 +5,12 @@ :reader :printer :core) + (:import-from :genhash + :hashref + :hashmap) + (:import-from :utils + :listify + :getenv) (:export :main)) (in-package :mal) @@ -60,10 +66,7 @@ ((mal-data-value= mal-let* (first forms)) (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (mal-data-value (second forms))))) + (bindings (utils:listify (types:mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env @@ -154,8 +157,8 @@ (defun main (&optional (argv nil argv-provided-p)) (declare (ignorable argv argv-provided-p)) - (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") - (string= (uiop:getenv "TERM") "dumb")))) + (setf *use-readline-p* (not (or (string= (utils:getenv "PERL_RL") "false") + (string= (utils:getenv "TERM") "dumb")))) ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment diff --git a/common-lisp/step6_file.lisp b/common-lisp/step6_file.lisp index 71a4f7972e..b474aac53c 100644 --- a/common-lisp/step6_file.lisp +++ b/common-lisp/step6_file.lisp @@ -4,8 +4,13 @@ :env :reader :printer - :core - :utils) + :core) + (:import-from :genhash + :hashref + :hashmap) + (:import-from :utils + :listify + :getenv) (:export :main)) (in-package :mal) @@ -61,10 +66,7 @@ ((mal-data-value= mal-let* (first forms)) (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (mal-data-value (second forms))))) + (bindings (utils:listify (types:mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env @@ -169,8 +171,8 @@ (defun main (&optional (argv nil argv-provided-p)) - (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") - (string= (uiop:getenv "TERM") "dumb")))) + (setf *use-readline-p* (not (or (string= (utils:getenv "PERL_RL") "false") + (string= (utils:getenv "TERM") "dumb")))) ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment diff --git a/common-lisp/step7_quote.lisp b/common-lisp/step7_quote.lisp index 3250f80d55..26d4a57ba6 100644 --- a/common-lisp/step7_quote.lisp +++ b/common-lisp/step7_quote.lisp @@ -4,8 +4,13 @@ :env :reader :printer - :core - :utils) + :core) + (:import-from :genhash + :hashref + :hashmap) + (:import-from :utils + :listify + :getenv) (:export :main)) (in-package :mal) @@ -97,10 +102,7 @@ ((mal-data-value= mal-let* (first forms)) (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (mal-data-value (second forms))))) + (bindings (utils:listify (types:mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env @@ -205,8 +207,8 @@ (defun main (&optional (argv nil argv-provided-p)) - (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") - (string= (uiop:getenv "TERM") "dumb")))) + (setf *use-readline-p* (not (or (string= (utils:getenv "PERL_RL") "false") + (string= (utils:getenv "TERM") "dumb")))) ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment diff --git a/common-lisp/step8_macros.lisp b/common-lisp/step8_macros.lisp index a8f370b966..4ae36f87b2 100644 --- a/common-lisp/step8_macros.lisp +++ b/common-lisp/step8_macros.lisp @@ -4,8 +4,13 @@ :env :reader :printer - :core - :utils) + :core) + (:import-from :genhash + :hashref + :hashmap) + (:import-from :utils + :listify + :getenv) (:export :main)) (in-package :mal) @@ -145,10 +150,7 @@ ((types:mal-data-value= mal-let* (first forms)) (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (types:mal-data-value (second forms))))) + (bindings (utils:listify (types:mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env @@ -264,8 +266,8 @@ (defun main (&optional (argv nil argv-provided-p)) - (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") - (string= (uiop:getenv "TERM") "dumb")))) + (setf *use-readline-p* (not (or (string= (utils:getenv "PERL_RL") "false") + (string= (utils:getenv "TERM") "dumb")))) ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment diff --git a/common-lisp/step9_try.lisp b/common-lisp/step9_try.lisp index cbf759c0af..c03be8f2af 100644 --- a/common-lisp/step9_try.lisp +++ b/common-lisp/step9_try.lisp @@ -4,8 +4,13 @@ :env :reader :printer - :core - :utils) + :core) + (:import-from :genhash + :hashref + :hashmap) + (:import-from :utils + :listify + :getenv) (:export :main)) (in-package :mal) @@ -148,10 +153,7 @@ ((types:mal-data-value= mal-let* (first forms)) (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (types:mal-data-value (second forms))))) + (bindings (utils:listify (types:mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env diff --git a/common-lisp/stepA_mal.lisp b/common-lisp/stepA_mal.lisp index 84b7690a7f..fa629aa7ac 100644 --- a/common-lisp/stepA_mal.lisp +++ b/common-lisp/stepA_mal.lisp @@ -4,8 +4,13 @@ :env :reader :printer - :core - :utils) + :core) + (:import-from :genhash + :hashref + :hashmap) + (:import-from :utils + :listify + :getenv) (:export :main)) (in-package :mal) @@ -148,10 +153,7 @@ ((types:mal-data-value= mal-let* (first forms)) (let ((new-env (env:create-mal-env :parent env)) - ;; Convert a potential vector to a list - (bindings (map 'list - #'identity - (types:mal-data-value (second forms))))) + (bindings (utils:listify (types:mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env @@ -304,8 +306,8 @@ (defun main (&optional (argv nil argv-provided-p)) - (setf *use-readline-p* (not (or (string= (uiop:getenv "PERL_RL") "false") - (string= (uiop:getenv "TERM") "dumb")))) + (setf *use-readline-p* (not (or (string= (utils:getenv "PERL_RL") "false") + (string= (utils:getenv "TERM") "dumb")))) ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment diff --git a/common-lisp/types.lisp b/common-lisp/types.lisp index 2d97b554ff..5e9ac37599 100644 --- a/common-lisp/types.lisp +++ b/common-lisp/types.lisp @@ -1,6 +1,8 @@ (defpackage :types (:use :common-lisp :genhash) + (:import-from :utils + :listify) (:export :mal-data-value= ;; Accessors :mal-data-value @@ -155,8 +157,8 @@ forms)))) (defun mal-sequence= (value1 value2) - (let ((sequence1 (map 'list #'identity (mal-data-value value1))) - (sequence2 (map 'list #'identity (mal-data-value value2)))) + (let ((sequence1 (utils:listify (mal-data-value value1))) + (sequence2 (utils:listify (mal-data-value value2)))) (when (= (length sequence1) (length sequence2)) (every #'identity (loop @@ -181,6 +183,7 @@ (defun mal-data-value= (value1 value2) (when (and (typep value1 'mal-data) (typep value2 'mal-data)) + (if (equal (mal-data-type value1) (mal-data-type value2)) (switch-mal-type value1 (list (mal-sequence= value1 value2)) @@ -211,9 +214,9 @@ (number (make-mal-number value)) ;; This needs to be before symbol since nil is a symbol (null (funcall (cond - (booleanp #'make-mal-boolean) - (listp #'make-mal-list) - (t #'make-mal-nil)) + (booleanp #'make-mal-boolean) + (listp #'make-mal-list) + (t #'make-mal-nil)) value)) ;; This needs to before symbol since t, nil are symbols (boolean (make-mal-boolean value)) diff --git a/common-lisp/utils.lisp b/common-lisp/utils.lisp index f90bfd5943..8845f2048b 100644 --- a/common-lisp/utils.lisp +++ b/common-lisp/utils.lisp @@ -4,7 +4,8 @@ (:export :replace-all :getenv :read-file-string - :raw-command-line-arguments)) + :raw-command-line-arguments + :listify)) (in-package :utils) @@ -22,3 +23,7 @@ is replaced with replacement." :end (or pos (length string))) when pos do (write-string replacement out) while pos))) + +(defun listify (sequence) + "Convert a sequence to a list" + (map 'list #'identity sequence)) From a0e32831fd595caa55ec866ea1f24e164350524c Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 22 Nov 2016 13:49:00 +0530 Subject: [PATCH 0402/2308] Common Lisp: Cleanup printing of hashmap and sequences --- common-lisp/printer.lisp | 45 ++++++++++++++++------------------------ common-lisp/types.lisp | 9 ++------ 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/common-lisp/printer.lisp b/common-lisp/printer.lisp index de227a32dd..a6833d0ef8 100644 --- a/common-lisp/printer.lisp +++ b/common-lisp/printer.lisp @@ -1,41 +1,32 @@ (defpackage :printer (:use :common-lisp - :types - :genhash) + :types) + (:import-from :genhash + :hashmap) (:import-from :cl-ppcre :regex-replace) (:import-from :utils - :replace-all) + :replace-all + :listify) (:export :pr-str)) (in-package :printer) (defun pr-mal-sequence (start-delimiter sequence end-delimiter &optional (print-readably t)) - (concatenate 'string - start-delimiter - (format nil - "~{~a~^ ~}" - (map 'list (lambda (value) - (pr-str value print-readably)) - (types:mal-data-value sequence))) - end-delimiter)) + (format nil + "~a~{~a~^ ~}~a" + start-delimiter + (mapcar (lambda (value) + (pr-str value print-readably)) + (utils:listify (types:mal-data-value sequence))) + end-delimiter)) -(defun pr-mal-hash-map (hash-map &optional (print-readably t)) - (let ((hash-map-value (types:mal-data-value hash-map))) - (concatenate 'string - "{" - (format nil - "~{~a~^ ~}" - (let (repr) - (genhash:hashmap (lambda (key value) - (push (format nil - "~a ~a" - (pr-str key print-readably) - (pr-str value print-readably)) - repr)) - hash-map-value) - repr)) - "}"))) +(defun pr-mal-hash-map (hash-map &optional (print-readably t) &aux repr) + (genhash:hashmap (lambda (key value) + (push (pr-str value print-readably) repr) + (push (pr-str key print-readably) repr)) + (types:mal-data-value hash-map)) + (format nil "{~{~a ~a~^ ~}}" repr)) (defun pr-string (ast &optional (print-readably t)) (if print-readably diff --git a/common-lisp/types.lisp b/common-lisp/types.lisp index 5e9ac37599..2447da518b 100644 --- a/common-lisp/types.lisp +++ b/common-lisp/types.lisp @@ -105,13 +105,8 @@ ;; Create a constructor and predicate for given type (defmacro define-mal-type (type) - (let ((constructor (intern (string-upcase (concatenate 'string - "make-mal-" - (symbol-name type))))) - (predicate (intern (string-upcase (concatenate 'string - "mal-" - (symbol-name type) - "-p"))))) + (let ((constructor (intern (format nil "MAKE-MAL-~a" (symbol-name type)))) + (predicate (intern (format nil "MAL-~a-P" (symbol-name type))))) `(progn (defun ,constructor (value &key meta attrs) (make-mal-data :type ',type :value value From 371ebd53a3292ec65a25338592f217e0fb29bf27 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 22 Nov 2016 17:25:16 +0530 Subject: [PATCH 0403/2308] Common Lisp: Move source code to src/ folder --- common-lisp/Makefile | 6 +++--- common-lisp/{ => src}/core.lisp | 0 common-lisp/{ => src}/env.lisp | 0 common-lisp/{ => src}/printer.lisp | 0 common-lisp/{ => src}/reader.lisp | 0 common-lisp/{ => src}/step0_repl.lisp | 0 common-lisp/{ => src}/step1_read_print.lisp | 0 common-lisp/{ => src}/step2_eval.lisp | 0 common-lisp/{ => src}/step3_env.lisp | 0 common-lisp/{ => src}/step4_if_fn_do.lisp | 0 common-lisp/{ => src}/step5_tco.lisp | 0 common-lisp/{ => src}/step6_file.lisp | 0 common-lisp/{ => src}/step7_quote.lisp | 0 common-lisp/{ => src}/step8_macros.lisp | 0 common-lisp/{ => src}/step9_try.lisp | 0 common-lisp/{ => src}/stepA_mal.lisp | 0 common-lisp/{ => src}/types.lisp | 0 common-lisp/{ => src}/utils.lisp | 0 common-lisp/step0_repl.asd | 3 ++- common-lisp/step1_read_print.asd | 3 ++- common-lisp/step2_eval.asd | 3 ++- common-lisp/step3_env.asd | 3 ++- common-lisp/step4_if_fn_do.asd | 3 ++- common-lisp/step5_tco.asd | 3 ++- common-lisp/step6_file.asd | 3 ++- common-lisp/step7_quote.asd | 3 ++- common-lisp/step8_macros.asd | 3 ++- common-lisp/step9_try.asd | 3 ++- common-lisp/stepA_mal.asd | 3 ++- 29 files changed, 25 insertions(+), 14 deletions(-) rename common-lisp/{ => src}/core.lisp (100%) rename common-lisp/{ => src}/env.lisp (100%) rename common-lisp/{ => src}/printer.lisp (100%) rename common-lisp/{ => src}/reader.lisp (100%) rename common-lisp/{ => src}/step0_repl.lisp (100%) rename common-lisp/{ => src}/step1_read_print.lisp (100%) rename common-lisp/{ => src}/step2_eval.lisp (100%) rename common-lisp/{ => src}/step3_env.lisp (100%) rename common-lisp/{ => src}/step4_if_fn_do.lisp (100%) rename common-lisp/{ => src}/step5_tco.lisp (100%) rename common-lisp/{ => src}/step6_file.lisp (100%) rename common-lisp/{ => src}/step7_quote.lisp (100%) rename common-lisp/{ => src}/step8_macros.lisp (100%) rename common-lisp/{ => src}/step9_try.lisp (100%) rename common-lisp/{ => src}/stepA_mal.lisp (100%) rename common-lisp/{ => src}/types.lisp (100%) rename common-lisp/{ => src}/utils.lisp (100%) diff --git a/common-lisp/Makefile b/common-lisp/Makefile index 9d620701dd..d6d8437d0f 100644 --- a/common-lisp/Makefile +++ b/common-lisp/Makefile @@ -12,8 +12,8 @@ define steps endef ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) -SOURCES_LISP := env.lisp core.lisp stepA_mal.lisp -SOURCES := utils.lisp types.lisp reader.lisp printer.lisp $(SOURCES_LISP) +SOURCES_LISP := src/env.lisp src/core.lisp src/stepA_mal.lisp +SOURCES := src/utils.lisp src/types.lisp src/reader.lisp src/printer.lisp $(SOURCES_LISP) LISP ?= sbcl ABCL ?= abcl @@ -33,7 +33,7 @@ hist/%_impl: ; # directory of the CL_LAUNCH_FILE in --wrap script so that the script can find the dumped # image even if invoked from some directory different from where it # currently resides -step% : step%.lisp utils.lisp types.lisp env.lisp printer.lisp reader.lisp core.lisp hist/%_impl +step% : src/step%.lisp src/utils.lisp src/types.lisp src/env.lisp src/printer.lisp src/reader.lisp src/core.lisp hist/%_impl ifeq ($(LISP),abcl) echo -n '#!/bin/sh\ncd `dirname $$0` ; $(ABCL) --noinform --noinit --nosystem --load run-abcl.lisp -- $@ $$@' > $@ chmod +x $@ diff --git a/common-lisp/core.lisp b/common-lisp/src/core.lisp similarity index 100% rename from common-lisp/core.lisp rename to common-lisp/src/core.lisp diff --git a/common-lisp/env.lisp b/common-lisp/src/env.lisp similarity index 100% rename from common-lisp/env.lisp rename to common-lisp/src/env.lisp diff --git a/common-lisp/printer.lisp b/common-lisp/src/printer.lisp similarity index 100% rename from common-lisp/printer.lisp rename to common-lisp/src/printer.lisp diff --git a/common-lisp/reader.lisp b/common-lisp/src/reader.lisp similarity index 100% rename from common-lisp/reader.lisp rename to common-lisp/src/reader.lisp diff --git a/common-lisp/step0_repl.lisp b/common-lisp/src/step0_repl.lisp similarity index 100% rename from common-lisp/step0_repl.lisp rename to common-lisp/src/step0_repl.lisp diff --git a/common-lisp/step1_read_print.lisp b/common-lisp/src/step1_read_print.lisp similarity index 100% rename from common-lisp/step1_read_print.lisp rename to common-lisp/src/step1_read_print.lisp diff --git a/common-lisp/step2_eval.lisp b/common-lisp/src/step2_eval.lisp similarity index 100% rename from common-lisp/step2_eval.lisp rename to common-lisp/src/step2_eval.lisp diff --git a/common-lisp/step3_env.lisp b/common-lisp/src/step3_env.lisp similarity index 100% rename from common-lisp/step3_env.lisp rename to common-lisp/src/step3_env.lisp diff --git a/common-lisp/step4_if_fn_do.lisp b/common-lisp/src/step4_if_fn_do.lisp similarity index 100% rename from common-lisp/step4_if_fn_do.lisp rename to common-lisp/src/step4_if_fn_do.lisp diff --git a/common-lisp/step5_tco.lisp b/common-lisp/src/step5_tco.lisp similarity index 100% rename from common-lisp/step5_tco.lisp rename to common-lisp/src/step5_tco.lisp diff --git a/common-lisp/step6_file.lisp b/common-lisp/src/step6_file.lisp similarity index 100% rename from common-lisp/step6_file.lisp rename to common-lisp/src/step6_file.lisp diff --git a/common-lisp/step7_quote.lisp b/common-lisp/src/step7_quote.lisp similarity index 100% rename from common-lisp/step7_quote.lisp rename to common-lisp/src/step7_quote.lisp diff --git a/common-lisp/step8_macros.lisp b/common-lisp/src/step8_macros.lisp similarity index 100% rename from common-lisp/step8_macros.lisp rename to common-lisp/src/step8_macros.lisp diff --git a/common-lisp/step9_try.lisp b/common-lisp/src/step9_try.lisp similarity index 100% rename from common-lisp/step9_try.lisp rename to common-lisp/src/step9_try.lisp diff --git a/common-lisp/stepA_mal.lisp b/common-lisp/src/stepA_mal.lisp similarity index 100% rename from common-lisp/stepA_mal.lisp rename to common-lisp/src/stepA_mal.lisp diff --git a/common-lisp/types.lisp b/common-lisp/src/types.lisp similarity index 100% rename from common-lisp/types.lisp rename to common-lisp/src/types.lisp diff --git a/common-lisp/utils.lisp b/common-lisp/src/utils.lisp similarity index 100% rename from common-lisp/utils.lisp rename to common-lisp/src/utils.lisp diff --git a/common-lisp/step0_repl.asd b/common-lisp/step0_repl.asd index aee96e820a..fbad7d602c 100644 --- a/common-lisp/step0_repl.asd +++ b/common-lisp/step0_repl.asd @@ -19,4 +19,5 @@ :description "Implementation of step 0 of MAL in Common Lisp" :serial t :components ((:file "step0_repl")) - :depends-on (:uiop :cl-readline)) + :depends-on (:uiop :cl-readline) + :pathname "src/") diff --git a/common-lisp/step1_read_print.asd b/common-lisp/step1_read_print.asd index 993b77d333..ea7134fbe3 100644 --- a/common-lisp/step1_read_print.asd +++ b/common-lisp/step1_read_print.asd @@ -25,4 +25,5 @@ (:file "reader") (:file "printer") (:file "step1_read_print")) - :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash) + :pathname "src/") diff --git a/common-lisp/step2_eval.asd b/common-lisp/step2_eval.asd index 2b0b3c5907..06b1b8074d 100644 --- a/common-lisp/step2_eval.asd +++ b/common-lisp/step2_eval.asd @@ -26,4 +26,5 @@ (:file "reader") (:file "printer") (:file "step2_eval")) - :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash) + :pathname "src/") diff --git a/common-lisp/step3_env.asd b/common-lisp/step3_env.asd index 3dc014a669..5120a3c54b 100644 --- a/common-lisp/step3_env.asd +++ b/common-lisp/step3_env.asd @@ -26,4 +26,5 @@ (:file "reader") (:file "printer") (:file "step3_env")) - :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash) + :pathname "src/") diff --git a/common-lisp/step4_if_fn_do.asd b/common-lisp/step4_if_fn_do.asd index efddd5e3b7..902f99a242 100644 --- a/common-lisp/step4_if_fn_do.asd +++ b/common-lisp/step4_if_fn_do.asd @@ -27,4 +27,5 @@ (:file "printer") (:file "core") (:file "step4_if_fn_do")) - :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash) + :pathname "src/") diff --git a/common-lisp/step5_tco.asd b/common-lisp/step5_tco.asd index bdc42096bc..08ebfaddb6 100644 --- a/common-lisp/step5_tco.asd +++ b/common-lisp/step5_tco.asd @@ -27,4 +27,5 @@ (:file "printer") (:file "core") (:file "step5_tco")) - :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash) + :pathname "src/") diff --git a/common-lisp/step6_file.asd b/common-lisp/step6_file.asd index 35bc77f375..f8937b2109 100644 --- a/common-lisp/step6_file.asd +++ b/common-lisp/step6_file.asd @@ -27,4 +27,5 @@ (:file "printer") (:file "core") (:file "step6_file")) - :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash) + :pathname "src/") diff --git a/common-lisp/step7_quote.asd b/common-lisp/step7_quote.asd index efa026930f..73df642a2c 100644 --- a/common-lisp/step7_quote.asd +++ b/common-lisp/step7_quote.asd @@ -27,4 +27,5 @@ (:file "printer") (:file "core") (:file "step7_quote")) - :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash) + :pathname "src/") diff --git a/common-lisp/step8_macros.asd b/common-lisp/step8_macros.asd index 64d2f97636..e0b4c41a32 100644 --- a/common-lisp/step8_macros.asd +++ b/common-lisp/step8_macros.asd @@ -27,4 +27,5 @@ (:file "printer") (:file "core") (:file "step8_macros")) - :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash) + :pathname "src/") diff --git a/common-lisp/step9_try.asd b/common-lisp/step9_try.asd index 84e0ef0248..b1ea4a08fd 100644 --- a/common-lisp/step9_try.asd +++ b/common-lisp/step9_try.asd @@ -27,4 +27,5 @@ (:file "printer") (:file "core") (:file "step9_try")) - :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash) + :pathname "src/") diff --git a/common-lisp/stepA_mal.asd b/common-lisp/stepA_mal.asd index 63efad14ac..b9aca81c7e 100644 --- a/common-lisp/stepA_mal.asd +++ b/common-lisp/stepA_mal.asd @@ -27,4 +27,5 @@ (:file "printer") (:file "core") (:file "stepA_mal")) - :depends-on (:uiop :cl-readline :cl-ppcre :genhash)) + :depends-on (:uiop :cl-readline :cl-ppcre :genhash) + :pathname "src/") From 2ce883223985bb66a408a934549b4ec2fc2e0bb7 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sun, 4 Dec 2016 13:50:37 +0530 Subject: [PATCH 0404/2308] Common Lisp: Miscellaneous cleanups Refactor env a bit --- common-lisp/src/env.lisp | 54 +++++++++++++------------- common-lisp/src/reader.lisp | 66 ++++++++++++++------------------ common-lisp/step1_read_print.asd | 1 + common-lisp/step2_eval.asd | 1 + common-lisp/step3_env.asd | 1 + common-lisp/step4_if_fn_do.asd | 1 + common-lisp/step5_tco.asd | 1 + common-lisp/step6_file.asd | 1 + common-lisp/step7_quote.asd | 1 + common-lisp/step8_macros.asd | 1 + common-lisp/step9_try.asd | 1 + common-lisp/stepA_mal.asd | 1 + 12 files changed, 66 insertions(+), 64 deletions(-) diff --git a/common-lisp/src/env.lisp b/common-lisp/src/env.lisp index f63364d8d5..b28d984189 100644 --- a/common-lisp/src/env.lisp +++ b/common-lisp/src/env.lisp @@ -30,38 +30,38 @@ (parent nil :read-only t)) (defun find-env (env symbol) - (let ((value (gethash (types:mal-data-value symbol) - (mal-env-bindings env))) - (parent (mal-env-parent env))) - (cond - (value value) - (parent (find-env parent symbol)) - (t nil)))) + (when env + (or (gethash (mal-data-value symbol) + (mal-env-bindings env)) + (find-env (mal-env-parent env) symbol)))) (defun get-env (env symbol) - (let ((value (find-env env symbol))) - (if value - value - (error 'undefined-symbol - :symbol (format nil "~a" (types:mal-data-value symbol)))))) + (or (find-env env symbol) + (error 'undefined-symbol + :symbol (format nil "~a" (mal-data-value symbol))))) (defun set-env (env symbol value) (setf (gethash (types:mal-data-value symbol) (mal-env-bindings env)) value)) -(defun create-mal-env (&key (parent nil) (binds nil) (exprs nil)) - (let ((env (make-mal-env :parent parent))) - (loop - while binds - do (let ((key (pop binds))) - (if (string= (types:mal-data-value key) "&") - (let ((key (pop binds))) - (unless key - (error 'arity-mismatch - :required (length binds) - :provided (length exprs))) - (set-env env key (types:make-mal-list exprs)) - (setq binds nil)) - (set-env env key (pop exprs))))) - env)) +(defun create-mal-env (&key parent binds exprs) + (let ((env (make-mal-env :parent parent)) + (params-length (length binds)) + (arg-length (length exprs))) + + (flet ((arity-mismatch () + (error 'arity-mismatch + :required params-length + :provided arg-length))) + (loop + for key = (pop binds) + while key + do (if (string/= (mal-data-value key) "&") + (set-env env key (or (pop exprs) + (arity-mismatch))) + (progn (set-env env + (or (pop binds) (arity-mismatch)) + (make-mal-list exprs)) + (setq binds nil)))) + env))) diff --git a/common-lisp/src/reader.lisp b/common-lisp/src/reader.lisp index ba4f44c828..d088453476 100644 --- a/common-lisp/src/reader.lisp +++ b/common-lisp/src/reader.lisp @@ -1,6 +1,7 @@ (defpackage :reader (:use :common-lisp - :types) + :types + :alexandria) (:import-from :genhash :hashref) (:import-from :cl-ppcre @@ -85,9 +86,7 @@ raised" (let ((actual-token (pop (token-reader-tokens reader)))) (when (and token-provided-p (not (equal actual-token token))) - (error 'unexpected-token - :expected token - :actual actual-token))) + (error 'unexpected-token :expected token :actual actual-token))) reader) (defun parse-string (token) @@ -97,8 +96,7 @@ raised" "\\n" " ")) - (error 'eof - :context "string"))) + (error 'eof :context "string"))) (defun read-form-with-meta (reader) (consume reader) @@ -107,20 +105,18 @@ raised" (when (or (null meta) (null value)) - (error 'eof - :context "object metadata")) + (error 'eof :context "object metadata")) (make-mal-list (list (make-mal-symbol "with-meta") value meta)))) (defun expand-quote (reader) - (let ((quote (next reader))) - (make-mal-list (list (make-mal-symbol (cond - ((string= quote "'") "quote") - ((string= quote "`") "quasiquote") - ((string= quote "~") "unquote") - ((string= quote "~@") "splice-unquote") - ((string= quote "@") "deref"))) - (read-form reader))))) + (let ((quote-sym (make-mal-symbol (switch ((next reader) :test #'string=) + ("'" "quote") + ("`" "quasiquote") + ("~" "unquote") + ("~@" "splice-unquote") + ("@" "deref"))))) + (make-mal-list (list quote-sym (read-form reader))))) (defun read-mal-sequence (reader &optional (delimiter ")") (constructor 'list)) ;; Consume the opening brace @@ -129,10 +125,9 @@ raised" (loop for token = (peek reader) while (cond - ((null token) (error 'eof - :context (if (string= delimiter ")") - "list" - "vector"))) + ((null token) (error 'eof :context (if (string= delimiter ")") + "list" + "vector"))) ((string= token delimiter) (return)) (t (push (read-form reader) forms)))) ;; Consume the closing brace @@ -147,14 +142,12 @@ raised" (loop for token = (peek reader) while (cond - ((null token) (error 'eof - :context "hash-map")) + ((null token) (error 'eof :context "hash-map")) ((string= token "}") (return)) (t (let ((key (read-form reader)) (value (read-form reader))) (if (null value) - (error 'eof - :context "hash-map") + (error 'eof :context "hash-map") (push (cons key value) forms)))))) ;; Consume the closing brace (consume reader) @@ -181,19 +174,18 @@ raised" (t (make-mal-symbol token))))) (defun read-form (reader) - (let ((token (peek reader))) - (cond - ((null token) nil) - ((string= token "(") (make-mal-list (read-mal-sequence reader - ")" - 'list))) - ((string= token "[") (make-mal-vector (read-mal-sequence reader - "]" - 'vector))) - ((string= token "{") (make-mal-hash-map (read-hash-map reader))) - ((string= token "^") (read-form-with-meta reader)) - ((member token '("'" "`" "~" "~@" "@") :test #'string=) (expand-quote reader)) - (t (read-atom reader))))) + (switch ((peek reader) :test #'equal) + (nil nil) + ("(" (make-mal-list (read-mal-sequence reader ")" 'list))) + ("[" (make-mal-vector (read-mal-sequence reader "]" 'vector))) + ("{" (make-mal-hash-map (read-hash-map reader))) + ("^" (read-form-with-meta reader)) + ("'" (expand-quote reader)) + ("`" (expand-quote reader)) + ("~" (expand-quote reader)) + ("~@" (expand-quote reader)) + ("@" (expand-quote reader)) + (t (read-atom reader)))) (defun read-str (string) (read-form (make-token-reader :tokens (tokenize string)))) diff --git a/common-lisp/step1_read_print.asd b/common-lisp/step1_read_print.asd index ea7134fbe3..c3719f155e 100644 --- a/common-lisp/step1_read_print.asd +++ b/common-lisp/step1_read_print.asd @@ -8,6 +8,7 @@ (ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) +(ql:quickload :alexandria :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step2_eval.asd b/common-lisp/step2_eval.asd index 06b1b8074d..319157d3cf 100644 --- a/common-lisp/step2_eval.asd +++ b/common-lisp/step2_eval.asd @@ -8,6 +8,7 @@ (ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) +(ql:quickload :alexandria :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step3_env.asd b/common-lisp/step3_env.asd index 5120a3c54b..025644622d 100644 --- a/common-lisp/step3_env.asd +++ b/common-lisp/step3_env.asd @@ -8,6 +8,7 @@ (ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) +(ql:quickload :alexandria :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step4_if_fn_do.asd b/common-lisp/step4_if_fn_do.asd index 902f99a242..8af7349f4b 100644 --- a/common-lisp/step4_if_fn_do.asd +++ b/common-lisp/step4_if_fn_do.asd @@ -8,6 +8,7 @@ (ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) +(ql:quickload :alexandria :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step5_tco.asd b/common-lisp/step5_tco.asd index 08ebfaddb6..0fb40bce43 100644 --- a/common-lisp/step5_tco.asd +++ b/common-lisp/step5_tco.asd @@ -8,6 +8,7 @@ (ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) +(ql:quickload :alexandria :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step6_file.asd b/common-lisp/step6_file.asd index f8937b2109..671641c924 100644 --- a/common-lisp/step6_file.asd +++ b/common-lisp/step6_file.asd @@ -8,6 +8,7 @@ (ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) +(ql:quickload :alexandria :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step7_quote.asd b/common-lisp/step7_quote.asd index 73df642a2c..96ab0191a2 100644 --- a/common-lisp/step7_quote.asd +++ b/common-lisp/step7_quote.asd @@ -8,6 +8,7 @@ (ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) +(ql:quickload :alexandria :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step8_macros.asd b/common-lisp/step8_macros.asd index e0b4c41a32..4cab290143 100644 --- a/common-lisp/step8_macros.asd +++ b/common-lisp/step8_macros.asd @@ -8,6 +8,7 @@ (ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) +(ql:quickload :alexandria :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step9_try.asd b/common-lisp/step9_try.asd index b1ea4a08fd..4788e6298d 100644 --- a/common-lisp/step9_try.asd +++ b/common-lisp/step9_try.asd @@ -8,6 +8,7 @@ (ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) +(ql:quickload :alexandria :silent t) (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/stepA_mal.asd b/common-lisp/stepA_mal.asd index b9aca81c7e..22d5e6f165 100644 --- a/common-lisp/stepA_mal.asd +++ b/common-lisp/stepA_mal.asd @@ -8,6 +8,7 @@ (ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) +(ql:quickload :alexandria :silent t) (defpackage #:mal-asd (:use :cl :asdf)) From 448f74e072041bd016a27475806c3c082d28b2c9 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 22 Aug 2017 12:54:23 +0530 Subject: [PATCH 0405/2308] Common Lisp: Refactor reader to reduce redundant error checks --- common-lisp/src/reader.lisp | 104 ++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 58 deletions(-) diff --git a/common-lisp/src/reader.lisp b/common-lisp/src/reader.lisp index d088453476..5391b4dd89 100644 --- a/common-lisp/src/reader.lisp +++ b/common-lisp/src/reader.lisp @@ -31,7 +31,7 @@ ((context :initarg :context :reader context)) (:report (lambda (condition stream) (format stream - "EOF encountered while reading ~a" + "EOF encountered while reading '~a'" (context condition)))) (:documentation "Error raised when EOF is encountered while reading.")) @@ -98,17 +98,6 @@ raised" ")) (error 'eof :context "string"))) -(defun read-form-with-meta (reader) - (consume reader) - (let ((meta (read-form reader)) - (value (read-form reader))) - - (when (or (null meta) - (null value)) - (error 'eof :context "object metadata")) - - (make-mal-list (list (make-mal-symbol "with-meta") value meta)))) - (defun expand-quote (reader) (let ((quote-sym (make-mal-symbol (switch ((next reader) :test #'string=) ("'" "quote") @@ -118,66 +107,61 @@ raised" ("@" "deref"))))) (make-mal-list (list quote-sym (read-form reader))))) -(defun read-mal-sequence (reader &optional (delimiter ")") (constructor 'list)) - ;; Consume the opening brace - (consume reader) - (let (forms) - (loop - for token = (peek reader) - while (cond - ((null token) (error 'eof :context (if (string= delimiter ")") - "list" - "vector"))) - ((string= token delimiter) (return)) - (t (push (read-form reader) forms)))) +(defun read-mal-sequence (reader &optional (type 'list) &aux forms) + (let ((context (string-downcase (symbol-name type))) + (delimiter (if (equal type 'list) ")" "]"))) + + ;; Consume the opening brace + (consume reader) + + (setf forms (loop + until (string= (peek reader) delimiter) + collect (read-form-or-eof reader context))) + ;; Consume the closing brace (consume reader) - (apply constructor (nreverse forms)))) + + (apply type forms))) (defun read-hash-map (reader) - ;; Consume the open brace - (consume reader) - (let (forms - (hash-map (types:make-mal-value-hash-table))) + (let ((map (make-mal-value-hash-table)) + (context "hash-map")) + + ;; Consume the open brace + (consume reader) + (loop - for token = (peek reader) - while (cond - ((null token) (error 'eof :context "hash-map")) - ((string= token "}") (return)) - (t (let ((key (read-form reader)) - (value (read-form reader))) - (if (null value) - (error 'eof :context "hash-map") - (push (cons key value) forms)))))) + until (string= (peek reader) "}") + do (setf (hashref (read-form-or-eof reader context) map) + (read-form-or-eof reader context))) + ;; Consume the closing brace (consume reader) - ;; Construct the hash table - (dolist (key-value (nreverse forms)) - (setf (genhash:hashref (car key-value) hash-map) (cdr key-value))) - hash-map)) + + map)) (defun read-atom (reader) (let ((token (next reader))) - (cond - ((string= token "false") - mal-false) - ((string= token "true") - mal-true) - ((string= token "nil") - mal-nil) - ((char= (char token 0) #\") - (make-mal-string (parse-string token))) - ((char= (char token 0) #\:) - (make-mal-keyword token)) - ((scan *number-re* token) - (make-mal-number (read-from-string token))) - (t (make-mal-symbol token))))) + (cond ((string= token "false") mal-false) + ((string= token "true") mal-true) + ((string= token "nil") mal-nil) + ((char= (char token 0) #\") (make-mal-string (parse-string token))) + ((char= (char token 0) #\:) (make-mal-keyword token)) + ((scan *number-re* token) (make-mal-number (read-from-string token))) + (t (make-mal-symbol token))))) + +(defun read-form-with-meta (reader) + (consume reader) + + (let ((meta (read-form-or-eof reader "object meta")) + (value (read-form-or-eof reader "object meta"))) + (make-mal-list (list (make-mal-symbol "with-meta") value meta)))) (defun read-form (reader) (switch ((peek reader) :test #'equal) (nil nil) - ("(" (make-mal-list (read-mal-sequence reader ")" 'list))) - ("[" (make-mal-vector (read-mal-sequence reader "]" 'vector))) + ("(" (make-mal-list (read-mal-sequence reader 'list))) + ("[" (make-mal-vector (read-mal-sequence reader 'vector))) ("{" (make-mal-hash-map (read-hash-map reader))) ("^" (read-form-with-meta reader)) ("'" (expand-quote reader)) @@ -187,5 +171,9 @@ raised" ("@" (expand-quote reader)) (t (read-atom reader)))) +(defun read-form-or-eof (reader context) + (or (read-form reader) + (error 'eof :context context))) + (defun read-str (string) (read-form (make-token-reader :tokens (tokenize string)))) From a658bfa6d970ff401043db97855b4e85d8ccf3fe Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 22 Aug 2017 13:15:00 +0530 Subject: [PATCH 0406/2308] Common Lisp: Use mal-nil and listify wherever applicable --- common-lisp/src/core.lisp | 38 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/common-lisp/src/core.lisp b/common-lisp/src/core.lisp index e333c8cdd4..03933d33ad 100644 --- a/common-lisp/src/core.lisp +++ b/common-lisp/src/core.lisp @@ -10,9 +10,7 @@ (in-package :core) (defmacro wrap-boolean (form) - `(if ,form - types:mal-true - types:mal-false)) + `(if ,form mal-true mal-false)) (define-condition index-error (types:mal-error) ((size :initarg :size :reader index-error-size) @@ -46,7 +44,7 @@ "~{~a~^ ~}" (mapcar (lambda (string) (printer:pr-str string t)) strings))) - (types:make-mal-nil nil)) + mal-nil) (defun mal-println (&rest strings) ;; Using write-line instead of (format *standard-output* ... ) since the later prints @@ -56,7 +54,7 @@ "~{~a~^ ~}" (mapcar (lambda (string) (printer:pr-str string nil)) strings))) - (types:make-mal-nil nil)) + mal-nil) (defun mal-pr-str (&rest strings) (types:make-mal-string (format nil @@ -131,10 +129,7 @@ args)))) (defun mal-cons (element list) - (types:make-mal-list (cons element - (map 'list - #'identity - (types:mal-data-value list))))) + (types:make-mal-list (cons element (listify (types:mal-data-value list))))) (defun mal-concat (&rest lists) (types:make-mal-list (apply #'concatenate @@ -143,37 +138,30 @@ (defun mal-nth (sequence index) (or (nth (types:mal-data-value index) - (map 'list #'identity (types:mal-data-value sequence))) + (listify (types:mal-data-value sequence))) (error 'index-error :size (length (types:mal-data-value sequence)) :index (types:mal-data-value index) :sequence sequence))) (defun mal-first (sequence) - (or (first (map 'list #'identity (types:mal-data-value sequence))) - (types:make-mal-nil nil))) + (or (first (listify (types:mal-data-value sequence))) mal-nil)) (defun mal-rest (sequence) - (types:make-mal-list (rest (map 'list - #'identity - (types:mal-data-value sequence))))) + (types:make-mal-list (rest (listify (types:mal-data-value sequence))))) (defun mal-throw (value) (error 'types:mal-user-exception :data value)) (defun mal-apply (fn &rest values) - (let ((final-arg (map 'list - #'identity - (types:mal-data-value (car (last values))))) - (butlast-args (butlast values))) + (let ((last (listify (types:mal-data-value (car (last values))))) + (butlast (butlast values))) (apply (types:mal-data-value fn) - (append butlast-args final-arg)))) + (append butlast last)))) (defun mal-map (fn sequence) - (let ((applicants (map 'list - #'identity - (types:mal-data-value sequence)))) + (let ((applicants (listify (types:mal-data-value sequence)))) (types:make-mal-list (mapcar (types:mal-data-value fn) applicants)))) @@ -311,9 +299,7 @@ (cond ((types:mal-list-p value) value) ((types:mal-vector-p value) - (types:make-mal-list (map 'list - #'identity - (types:mal-data-value value)))) + (types:make-mal-list (listify (types:mal-data-value value)))) ((types:mal-string-p value) (types:make-mal-list (map 'list (lambda (char) From 5de0a3ee95fb84dfdc5597a26413e9c295d9f6f6 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 22 Aug 2017 13:15:48 +0530 Subject: [PATCH 0407/2308] Common Lisp: Do not export make-mal-boolean and make-mal-nil --- common-lisp/src/types.lisp | 2 -- 1 file changed, 2 deletions(-) diff --git a/common-lisp/src/types.lisp b/common-lisp/src/types.lisp index 2447da518b..700258171a 100644 --- a/common-lisp/src/types.lisp +++ b/common-lisp/src/types.lisp @@ -15,11 +15,9 @@ :mal-number-p :boolean - :make-mal-boolean :mal-boolean-p :nil - :make-mal-nil :mal-nil-p :string From f989ef2747425f63ce1c24601ca4bcbcc82f642c Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 22 Aug 2017 13:49:24 +0530 Subject: [PATCH 0408/2308] Common Lisp: Abstract definition of mal level functions in defmal macro --- common-lisp/src/core.lisp | 375 +++++++++++++++++--------------------- 1 file changed, 163 insertions(+), 212 deletions(-) diff --git a/common-lisp/src/core.lisp b/common-lisp/src/core.lisp index 03933d33ad..a66929f128 100644 --- a/common-lisp/src/core.lisp +++ b/common-lisp/src/core.lisp @@ -4,14 +4,12 @@ :types :reader :printer - :genhash) + :genhash + :alexandria) (:export :ns)) (in-package :core) -(defmacro wrap-boolean (form) - `(if ,form mal-true mal-false)) - (define-condition index-error (types:mal-error) ((size :initarg :size :reader index-error-size) (index :initarg :index :reader index-error-index) @@ -23,20 +21,37 @@ (index-error-size condition) (index-error-index condition))))) -(defun mal-add (value1 value2) - (types:apply-unwrapped-values '+ value1 value2)) +(defmacro wrap-boolean (form) + `(if ,form mal-true mal-false)) + +(defvar ns nil) + +(defmacro defmal (name arglist &rest body) + (let* ((symbol-name (if (stringp name) + name + ;; Since common lisp intern all the symbols in + ;; uppercase (by default) we need to convert the + ;; symbol to lowercase while introducing it in MAL + ;; environment + (string-downcase (symbol-name name)))) + (internal-name (format nil "MAL-~a" (string-upcase symbol-name)))) + `(push (cons (make-mal-symbol ,symbol-name) + (make-mal-builtin-fn (defun ,(intern internal-name) ,arglist ,@body))) + ns))) -(defun mal-sub (value1 value2) - (types:apply-unwrapped-values '- value1 value2)) +(defmal + (value1 value2) + (apply-unwrapped-values '+ value1 value2)) -(defun mal-mul (value1 value2) - (types:apply-unwrapped-values '* value1 value2)) +(defmal - (value1 value2) + (apply-unwrapped-values '- value1 value2)) -(defun mal-div (value1 value2) - (types:make-mal-number (round (/ (types:mal-data-value value1) - (types:mal-data-value value2))))) +(defmal * (value1 value2) + (apply-unwrapped-values '* value1 value2)) -(defun mal-prn (&rest strings) +(defmal / (value1 value2) + (make-mal-number (round (/ (mal-data-value value1) (mal-data-value value2))))) + +(defmal prn (&rest strings) ;; Using write-line instead of (format *standard-output* ... ) since the later prints ;; and extra newline at start in GNU CLISP, if environment variable PERL_RL is true ;; or terminal is dumb @@ -46,7 +61,7 @@ strings))) mal-nil) -(defun mal-println (&rest strings) +(defmal println (&rest strings) ;; Using write-line instead of (format *standard-output* ... ) since the later prints ;; and extra newline at start in GNU CLISP, if environment variable PERL_RL is true ;; or terminal is dumb @@ -56,87 +71,75 @@ strings))) mal-nil) -(defun mal-pr-str (&rest strings) - (types:make-mal-string (format nil - "~{~a~^ ~}" - (mapcar (lambda (string) (printer:pr-str string t)) - strings)))) +(defmal pr-str (&rest strings) + (make-mal-string (format nil + "~{~a~^ ~}" + (mapcar (lambda (string) (printer:pr-str string t)) + strings)))) -(defun mal-str (&rest strings) - (types:make-mal-string (format nil - "~{~a~}" - (mapcar (lambda (string) (printer:pr-str string nil)) - strings)))) +(defmal str (&rest strings) + (make-mal-string (format nil + "~{~a~}" + (mapcar (lambda (string) (printer:pr-str string nil)) + strings)))) -(defun mal-list (&rest values) +(defmal list (&rest values) (make-mal-list values)) -(defun mal-list? (value) - (wrap-boolean (or (types:mal-nil-p value) - (types:mal-list-p value)))) +(defmal list? (value) + (wrap-boolean (or (types:mal-nil-p value) (types:mal-list-p value)))) -(defun mal-empty? (value) +(defmal empty? (value) (wrap-boolean (zerop (length (types:mal-data-value value))))) -(defun mal-length (value) +(defmal count (value) (types:apply-unwrapped-values 'length value)) -(defun mal-= (value1 value2) +(defmal = (value1 value2) (wrap-boolean (types:mal-data-value= value1 value2))) -(defun mal-< (value1 value2) - (types:apply-unwrapped-values-prefer-bool '< - value1 - value2)) +(defmal < (value1 value2) + (wrap-boolean (< (mal-data-value value1) (mal-data-value value2)))) -(defun mal-> (value1 value2) - (types:apply-unwrapped-values-prefer-bool '> - value1 - value2)) +(defmal > (value1 value2) + (wrap-boolean (> (mal-data-value value1) (mal-data-value value2)))) -(defun mal-<= (value1 value2) - (types:apply-unwrapped-values-prefer-bool '<= - value1 - value2)) +(defmal <= (value1 value2) + (wrap-boolean (<= (mal-data-value value1) (mal-data-value value2)))) -(defun mal->= (value1 value2) - (types:apply-unwrapped-values-prefer-bool '>= - value1 - value2)) +(defmal >= (value1 value2) + (wrap-boolean (>= (mal-data-value value1) (mal-data-value value2)))) -(defun mal-read-string (value) +(defmal read-string (value) (reader:read-str (types:mal-data-value value))) -(defun mal-slurp (filename) +(defmal slurp (filename) (types:apply-unwrapped-values 'read-file-string filename)) -(defun mal-atom (value) +(defmal atom (value) (types:make-mal-atom value)) -(defun mal-atom? (value) +(defmal atom? (value) (wrap-boolean (types:mal-atom-p value))) -(defun mal-deref (atom) +(defmal deref (atom) (types:mal-data-value atom)) -(defun mal-reset! (atom value) +(defmal reset! (atom value) (setf (types:mal-data-value atom) value)) -(defun mal-swap! (atom fn &rest args) +(defmal swap! (atom fn &rest args) (setf (types:mal-data-value atom) (apply (types:mal-data-value fn) - (append (list (types:mal-data-value atom)) - args)))) + (append (list (types:mal-data-value atom)) args)))) -(defun mal-cons (element list) +(defmal cons (element list) (types:make-mal-list (cons element (listify (types:mal-data-value list))))) -(defun mal-concat (&rest lists) - (types:make-mal-list (apply #'concatenate - 'list - (mapcar #'types:mal-data-value lists)))) +(defmal concat (&rest lists) + (types:make-mal-list (apply #'concatenate 'list (mapcar #'types:mal-data-value lists)))) -(defun mal-nth (sequence index) +(defmal nth (sequence index) (or (nth (types:mal-data-value index) (listify (types:mal-data-value sequence))) (error 'index-error @@ -144,70 +147,64 @@ :index (types:mal-data-value index) :sequence sequence))) -(defun mal-first (sequence) +(defmal first (sequence) (or (first (listify (types:mal-data-value sequence))) mal-nil)) -(defun mal-rest (sequence) +(defmal rest (sequence) (types:make-mal-list (rest (listify (types:mal-data-value sequence))))) -(defun mal-throw (value) - (error 'types:mal-user-exception - :data value)) +(defmal throw (value) + (error 'types:mal-user-exception :data value)) -(defun mal-apply (fn &rest values) +(defmal apply (fn &rest values) (let ((last (listify (types:mal-data-value (car (last values))))) (butlast (butlast values))) - (apply (types:mal-data-value fn) - (append butlast last)))) + (apply (types:mal-data-value fn) (append butlast last)))) -(defun mal-map (fn sequence) +(defmal map (fn sequence) (let ((applicants (listify (types:mal-data-value sequence)))) - (types:make-mal-list (mapcar (types:mal-data-value fn) - applicants)))) + (types:make-mal-list (mapcar (types:mal-data-value fn) applicants)))) -(defun mal-nil? (value) +(defmal nil? (value) (wrap-boolean (types:mal-nil-p value))) -(defun mal-true? (value) - (wrap-boolean (and (types:mal-boolean-p value) - (types:mal-data-value value)))) +(defmal true? (value) + (wrap-boolean (and (types:mal-boolean-p value) (types:mal-data-value value)))) -(defun mal-false? (value) - (wrap-boolean (and (types:mal-boolean-p value) - (not (types:mal-data-value value))))) +(defmal false? (value) + (wrap-boolean (and (types:mal-boolean-p value) (not (types:mal-data-value value))))) -(defun mal-symbol (string) +(defmal symbol (string) (types:make-mal-symbol (types:mal-data-value string))) -(defun mal-symbol? (value) +(defmal symbol? (value) (wrap-boolean (types:mal-symbol-p value))) -(defun mal-keyword (keyword) +(defmal keyword (keyword) (if (types:mal-keyword-p keyword) keyword (types:make-mal-keyword (format nil ":~a" (types:mal-data-value keyword))))) -(defun mal-keyword? (value) +(defmal keyword? (value) (wrap-boolean (types:mal-keyword-p value))) -(defun mal-vector (&rest elements) +(defmal vector (&rest elements) (types:make-mal-vector (map 'vector #'identity elements))) -(defun mal-vector? (value) +(defmal vector? (value) (wrap-boolean (types:mal-vector-p value))) -(defun mal-hash-map (&rest elements) +(defmal hash-map (&rest elements) (let ((hash-map (types:make-mal-value-hash-table))) - (loop - for (key value) on elements + (loop for (key value) on elements by #'cddr do (setf (genhash:hashref key hash-map) value)) (types:make-mal-hash-map hash-map))) -(defun mal-map? (value) +(defmal map? (value) (wrap-boolean (types:mal-hash-map-p value))) -(defun mal-assoc (hash-map &rest elements) +(defmal assoc (hash-map &rest elements) (let ((hash-map-value (types:mal-data-value hash-map)) (new-hash-map (types:make-mal-value-hash-table))) @@ -217,73 +214,71 @@ (genhash:hashref key hash-map-value))) hash-map-value) - (loop - for (key value) on elements + (loop for (key value) on elements by #'cddr do (setf (genhash:hashref key new-hash-map) value)) (types:make-mal-hash-map new-hash-map))) -(defun mal-dissoc (hash-map &rest elements) - (let ((hash-map-value (types:mal-data-value hash-map)) - (new-hash-map (types:make-mal-value-hash-table))) +(defmal dissoc (hash-map &rest elements) + (let ((hash-map-value (mal-data-value hash-map)) + (new-hash-map (make-mal-value-hash-table))) - (genhash:hashmap (lambda (key value) - (declare (ignorable value)) - (when (not (member key - elements - :test #'types:mal-data-value=)) - (setf (genhash:hashref key new-hash-map) - (genhash:hashref key hash-map-value)))) - hash-map-value) - (types:make-mal-hash-map new-hash-map))) + (hashmap (lambda (key value) + (declare (ignorable value)) + (when (not (member key elements :test #'mal-data-value=)) + (setf (hashref key new-hash-map) + (hashref key hash-map-value)))) + hash-map-value) + + (make-mal-hash-map new-hash-map))) -(defun mal-get (hash-map key) - (or (and (types:mal-hash-map-p hash-map) - (genhash:hashref key (types:mal-data-value hash-map))) +(defmal get (hash-map key) + (or (and (types:mal-hash-map-p hash-map) (genhash:hashref key (types:mal-data-value hash-map))) types:mal-nil)) -(defun mal-contains? (hash-map key) - (if (genhash:hashref key (types:mal-data-value hash-map)) - types:mal-true - types:mal-false)) +(defmal contains? (hash-map key) + (if (genhash:hashref key (types:mal-data-value hash-map)) types:mal-true types:mal-false)) -(defun mal-keys (hash-map) +(defmal keys (hash-map) (let ((hash-map-value (types:mal-data-value hash-map)) keys) - (genhash:hashmap (lambda (key value) - (declare (ignorable value)) - (push key keys)) - hash-map-value) - (types:make-mal-list (nreverse keys)))) -(defun mal-vals (hash-map) + (hashmap (lambda (key value) + (declare (ignorable value)) + (push key keys)) + hash-map-value) + + (make-mal-list (nreverse keys)))) + +(defmal vals (hash-map) (let ((hash-map-value (types:mal-data-value hash-map)) values) - (genhash:hashmap (lambda (key value) - (declare (ignorable key)) - (push value values)) - hash-map-value) - (types:make-mal-list (nreverse values)))) -(defun mal-sequential? (value) - (wrap-boolean (or (types:mal-vector-p value) - (types:mal-list-p value)))) + (hashmap (lambda (key value) + (declare (ignorable key)) + (push value values)) + hash-map-value) + + (make-mal-list (nreverse values)))) -(defun mal-readline (prompt) +(defmal sequential? (value) + (wrap-boolean (or (types:mal-vector-p value) (types:mal-list-p value)))) + +(defmal readline (prompt) (format *standard-output* (types:mal-data-value prompt)) (force-output *standard-output*) (types:wrap-value (read-line *standard-input* nil))) -(defun mal-string? (value) +(defmal string? (value) (wrap-boolean (types:mal-string-p value))) -(defun mal-time-ms () +(defmal time-ms () (types:make-mal-number (round (/ (get-internal-real-time) (/ internal-time-units-per-second 1000))))) -(defun mal-conj (value &rest elements) +(defmal conj (value &rest elements) (cond ((types:mal-list-p value) (types:make-mal-list (append (nreverse elements) (types:mal-data-value value)))) @@ -293,21 +288,18 @@ elements))) (t (error 'types:mal-user-exception)))) -(defun mal-seq (value) - (if (zerop (length (types:mal-data-value value))) - types:mal-nil - (cond ((types:mal-list-p value) - value) - ((types:mal-vector-p value) - (types:make-mal-list (listify (types:mal-data-value value)))) - ((types:mal-string-p value) - (types:make-mal-list (map 'list - (lambda (char) - (types:make-mal-string (make-string 1 :initial-element char))) - (types:mal-data-value value)))) - (t (error 'types:mal-user-exception))))) - -(defun mal-with-meta (value meta) +(defmal seq (value) + (if (zerop (length (mal-data-value value))) + mal-nil + (cond ((mal-list-p value) value) + ((mal-vector-p value) + (make-mal-list (listify (mal-data-value value)))) + ((mal-string-p value) + (make-mal-list (mapcar (alexandria:compose #'make-mal-string #'string) + (coerce (mal-data-value value) 'list)))) + (t (error 'mal-user-exception))))) + +(defmal with-meta (value meta) (funcall (switch-mal-type value (types:string #'types:make-mal-string) (types:symbol #'types:make-mal-symbol) @@ -320,75 +312,34 @@ :meta meta :attrs (types:mal-data-attrs value))) -(defun mal-meta (value) - (or (types:mal-data-meta value) - types:mal-nil)) +(defmal meta (value) + (or (types:mal-data-meta value) types:mal-nil)) + +(defun wrap-value (value &optional booleanp listp) + (typecase value + (number (make-mal-number value)) + ;; This needs to be before symbol since nil is a symbol + (null (cond (booleanp mal-false) + (listp (make-mal-list value)) + (t mal-nil))) + ;; This needs to before symbol since t, nil are symbols + (boolean (if value mal-true mal-nil)) + (keyword (make-mal-keyword value)) + (symbol (make-mal-symbol (symbol-name value))) + (string (make-mal-string value)) + (list (make-mal-list (map 'list #'wrap-value value))) + (vector (make-mal-vector (map 'vector #'wrap-value value))) + (hash-table (make-mal-hash-map (let ((new-hash-table (make-mal-value-hash-table))) + (hashmap (lambda (key value) + (setf (hashref (wrap-value key) new-hash-table) + (wrap-value value))) + value) + new-hash-table))))) ;; Since a nil in Common LISP may mean an empty list or boolean false or ;; simply nil, the caller can specify the preferred type while evaluating an ;; expression -(defun mal-cl-eval (code &optional booleanp listp) +(defmal cl-eval (code &optional booleanp listp) (types:wrap-value (eval (read-from-string (types:mal-data-value code))) :booleanp (and booleanp (types:mal-data-value booleanp)) :listp (and listp (types:mal-data-value listp)))) - -(defvar ns - (list - (cons (types:make-mal-symbol "+") (types:make-mal-builtin-fn #'mal-add)) - (cons (types:make-mal-symbol "-") (types:make-mal-builtin-fn #'mal-sub)) - (cons (types:make-mal-symbol "*") (types:make-mal-builtin-fn #'mal-mul)) - (cons (types:make-mal-symbol "/") (types:make-mal-builtin-fn #'mal-div)) - (cons (types:make-mal-symbol "prn") (types:make-mal-builtin-fn #'mal-prn)) - (cons (types:make-mal-symbol "println") (types:make-mal-builtin-fn #'mal-println)) - (cons (types:make-mal-symbol "pr-str") (types:make-mal-builtin-fn #'mal-pr-str)) - (cons (types:make-mal-symbol "str") (types:make-mal-builtin-fn #'mal-str)) - (cons (types:make-mal-symbol "list") (types:make-mal-builtin-fn #'mal-list)) - (cons (types:make-mal-symbol "list?") (types:make-mal-builtin-fn #'mal-list?)) - (cons (types:make-mal-symbol "empty?") (types:make-mal-builtin-fn #'mal-empty?)) - (cons (types:make-mal-symbol "count") (types:make-mal-builtin-fn #'mal-length)) - (cons (types:make-mal-symbol "=") (types:make-mal-builtin-fn #'mal-=)) - (cons (types:make-mal-symbol "<") (types:make-mal-builtin-fn #'mal-<)) - (cons (types:make-mal-symbol ">") (types:make-mal-builtin-fn #'mal->)) - (cons (types:make-mal-symbol "<=") (types:make-mal-builtin-fn #'mal-<=)) - (cons (types:make-mal-symbol ">=") (types:make-mal-builtin-fn #'mal->=)) - (cons (types:make-mal-symbol "read-string") (types:make-mal-builtin-fn #'mal-read-string)) - (cons (types:make-mal-symbol "slurp") (types:make-mal-builtin-fn #'mal-slurp)) - (cons (types:make-mal-symbol "atom") (types:make-mal-builtin-fn #'mal-atom)) - (cons (types:make-mal-symbol "atom?") (types:make-mal-builtin-fn #'mal-atom?)) - (cons (types:make-mal-symbol "deref") (types:make-mal-builtin-fn #'mal-deref)) - (cons (types:make-mal-symbol "reset!") (types:make-mal-builtin-fn #'mal-reset!)) - (cons (types:make-mal-symbol "swap!") (types:make-mal-builtin-fn #'mal-swap!)) - (cons (types:make-mal-symbol "cons") (types:make-mal-builtin-fn #'mal-cons)) - (cons (types:make-mal-symbol "concat") (types:make-mal-builtin-fn #'mal-concat)) - (cons (types:make-mal-symbol "nth") (types:make-mal-builtin-fn #'mal-nth)) - (cons (types:make-mal-symbol "first") (types:make-mal-builtin-fn #'mal-first)) - (cons (types:make-mal-symbol "rest") (types:make-mal-builtin-fn #'mal-rest)) - (cons (types:make-mal-symbol "throw") (types:make-mal-builtin-fn #'mal-throw)) - (cons (types:make-mal-symbol "apply") (types:make-mal-builtin-fn #'mal-apply)) - (cons (types:make-mal-symbol "map") (types:make-mal-builtin-fn #'mal-map)) - (cons (types:make-mal-symbol "nil?") (types:make-mal-builtin-fn #'mal-nil?)) - (cons (types:make-mal-symbol "true?") (types:make-mal-builtin-fn #'mal-true?)) - (cons (types:make-mal-symbol "false?") (types:make-mal-builtin-fn #'mal-false?)) - (cons (types:make-mal-symbol "symbol") (types:make-mal-builtin-fn #'mal-symbol)) - (cons (types:make-mal-symbol "symbol?") (types:make-mal-builtin-fn #'mal-symbol?)) - (cons (types:make-mal-symbol "keyword") (types:make-mal-builtin-fn #'mal-keyword)) - (cons (types:make-mal-symbol "keyword?") (types:make-mal-builtin-fn #'mal-keyword?)) - (cons (types:make-mal-symbol "vector") (types:make-mal-builtin-fn #'mal-vector)) - (cons (types:make-mal-symbol "vector?") (types:make-mal-builtin-fn #'mal-vector?)) - (cons (types:make-mal-symbol "hash-map") (types:make-mal-builtin-fn #'mal-hash-map)) - (cons (types:make-mal-symbol "map?") (types:make-mal-builtin-fn #'mal-map?)) - (cons (types:make-mal-symbol "assoc") (types:make-mal-builtin-fn #'mal-assoc)) - (cons (types:make-mal-symbol "dissoc") (types:make-mal-builtin-fn #'mal-dissoc)) - (cons (types:make-mal-symbol "get") (types:make-mal-builtin-fn #'mal-get)) - (cons (types:make-mal-symbol "contains?") (types:make-mal-builtin-fn #'mal-contains?)) - (cons (types:make-mal-symbol "keys") (types:make-mal-builtin-fn #'mal-keys)) - (cons (types:make-mal-symbol "vals") (types:make-mal-builtin-fn #'mal-vals)) - (cons (types:make-mal-symbol "sequential?") (types:make-mal-builtin-fn #'mal-sequential?)) - (cons (types:make-mal-symbol "readline") (types:make-mal-builtin-fn #'mal-readline)) - (cons (types:make-mal-symbol "string?") (types:make-mal-builtin-fn #'mal-string?)) - (cons (types:make-mal-symbol "time-ms") (types:make-mal-builtin-fn #'mal-time-ms)) - (cons (types:make-mal-symbol "conj") (types:make-mal-builtin-fn #'mal-conj)) - (cons (types:make-mal-symbol "seq") (types:make-mal-builtin-fn #'mal-seq)) - (cons (types:make-mal-symbol "with-meta") (types:make-mal-builtin-fn #'mal-with-meta)) - (cons (types:make-mal-symbol "meta") (types:make-mal-builtin-fn #'mal-meta)) - (cons (types:make-mal-symbol "cl-eval") (types:make-mal-builtin-fn #'mal-cl-eval)))) From 8202dbf815defba66d4af9c7a0b84bd6cb15dde6 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 22 Aug 2017 16:01:11 +0530 Subject: [PATCH 0409/2308] Common Lisp: Get rid of apply-uwrapped-values and friends --- common-lisp/src/core.lisp | 18 +++++------ common-lisp/src/step2_eval.lisp | 20 +++++-------- common-lisp/src/step3_env.lisp | 20 +++++-------- common-lisp/src/step6_file.lisp | 2 +- common-lisp/src/step7_quote.lisp | 2 +- common-lisp/src/step8_macros.lisp | 2 +- common-lisp/src/step9_try.lisp | 2 +- common-lisp/src/stepA_mal.lisp | 6 ++-- common-lisp/src/types.lisp | 50 +------------------------------ 9 files changed, 33 insertions(+), 89 deletions(-) diff --git a/common-lisp/src/core.lisp b/common-lisp/src/core.lisp index a66929f128..e8a4efcf01 100644 --- a/common-lisp/src/core.lisp +++ b/common-lisp/src/core.lisp @@ -40,13 +40,13 @@ ns))) (defmal + (value1 value2) - (apply-unwrapped-values '+ value1 value2)) + (make-mal-number (+ (mal-data-value value1) (mal-data-value value2)))) (defmal - (value1 value2) - (apply-unwrapped-values '- value1 value2)) + (make-mal-number (- (mal-data-value value1) (mal-data-value value2)))) (defmal * (value1 value2) - (apply-unwrapped-values '* value1 value2)) + (make-mal-number (* (mal-data-value value1) (mal-data-value value2)))) (defmal / (value1 value2) (make-mal-number (round (/ (mal-data-value value1) (mal-data-value value2))))) @@ -93,7 +93,7 @@ (wrap-boolean (zerop (length (types:mal-data-value value))))) (defmal count (value) - (types:apply-unwrapped-values 'length value)) + (make-mal-number (length (mal-data-value value)))) (defmal = (value1 value2) (wrap-boolean (types:mal-data-value= value1 value2))) @@ -114,7 +114,7 @@ (reader:read-str (types:mal-data-value value))) (defmal slurp (filename) - (types:apply-unwrapped-values 'read-file-string filename)) + (make-mal-string (read-file-string (mal-data-value filename)))) (defmal atom (value) (types:make-mal-atom value)) @@ -268,7 +268,7 @@ (defmal readline (prompt) (format *standard-output* (types:mal-data-value prompt)) (force-output *standard-output*) - (types:wrap-value (read-line *standard-input* nil))) + (make-mal-string (read-line *standard-input* nil))) (defmal string? (value) (wrap-boolean (types:mal-string-p value))) @@ -340,6 +340,6 @@ ;; simply nil, the caller can specify the preferred type while evaluating an ;; expression (defmal cl-eval (code &optional booleanp listp) - (types:wrap-value (eval (read-from-string (types:mal-data-value code))) - :booleanp (and booleanp (types:mal-data-value booleanp)) - :listp (and listp (types:mal-data-value listp)))) + (wrap-value (eval (read-from-string (mal-data-value code))) + (and booleanp (mal-data-value booleanp)) + (and listp (mal-data-value listp)))) diff --git a/common-lisp/src/step2_eval.lisp b/common-lisp/src/step2_eval.lisp index e3d33b5a31..70fbf4d5d8 100644 --- a/common-lisp/src/step2_eval.lisp +++ b/common-lisp/src/step2_eval.lisp @@ -17,27 +17,23 @@ (setf (genhash:hashref (types:make-mal-symbol "+") *repl-env*) (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values '+ - value1 - value2)))) + (make-mal-number (+ (mal-data-value value1) + (mal-data-value value2)))))) (setf (genhash:hashref (types:make-mal-symbol "-") *repl-env*) (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values '- - value1 - value2)))) + (make-mal-number (- (mal-data-value value1) + (mal-data-value value2)))))) (setf (genhash:hashref (types:make-mal-symbol "*") *repl-env*) (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values '* - value1 - value2)))) + (make-mal-number (* (mal-data-value value1) + (mal-data-value value2)))))) (setf (genhash:hashref (types:make-mal-symbol "/") *repl-env*) (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values '/ - value1 - value2)))) + (make-mal-number (/ (mal-data-value value1) + (mal-data-value value2)))))) (defun lookup-env (symbol env) (let ((value (genhash:hashref symbol env))) diff --git a/common-lisp/src/step3_env.lisp b/common-lisp/src/step3_env.lisp index 3365a72f65..d988f12c41 100644 --- a/common-lisp/src/step3_env.lisp +++ b/common-lisp/src/step3_env.lisp @@ -19,30 +19,26 @@ (env:set-env *repl-env* (types:make-mal-symbol "+") (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values '+ - value1 - value2)))) + (make-mal-number (+ (mal-data-value value1) + (mal-data-value value2)))))) (env:set-env *repl-env* (types:make-mal-symbol "-") (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values '- - value1 - value2)))) + (make-mal-number (- (mal-data-value value1) + (mal-data-value value2)))))) (env:set-env *repl-env* (types:make-mal-symbol "*") (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values '* - value1 - value2)))) + (make-mal-number (* (mal-data-value value1) + (mal-data-value value2)))))) (env:set-env *repl-env* (types:make-mal-symbol "/") (types:make-mal-builtin-fn (lambda (value1 value2) - (types:apply-unwrapped-values '/ - value1 - value2)))) + (make-mal-number (/ (mal-data-value value1) + (mal-data-value value2)))))) (defvar mal-def! (make-mal-symbol "def!")) (defvar mal-let* (make-mal-symbol "let*")) diff --git a/common-lisp/src/step6_file.lisp b/common-lisp/src/step6_file.lisp index b474aac53c..e9585c63b2 100644 --- a/common-lisp/src/step6_file.lisp +++ b/common-lisp/src/step6_file.lisp @@ -190,7 +190,7 @@ (cdr (utils:raw-command-line-arguments))))) (env:set-env *repl-env* (types:make-mal-symbol "*ARGV*") - (types:wrap-value (cdr args) :listp t)) + (make-mal-list (mapcar #'make-mal-string (cdr args)))) (if (null args) (repl) (run-file (car args))))) diff --git a/common-lisp/src/step7_quote.lisp b/common-lisp/src/step7_quote.lisp index 26d4a57ba6..46ab36c369 100644 --- a/common-lisp/src/step7_quote.lisp +++ b/common-lisp/src/step7_quote.lisp @@ -226,7 +226,7 @@ (cdr (utils:raw-command-line-arguments))))) (env:set-env *repl-env* (types:make-mal-symbol "*ARGV*") - (types:wrap-value (cdr args) :listp t)) + (make-mal-list (mapcar #'make-mal-string (cdr args)))) (if (null args) (repl) (run-file (car args))))) diff --git a/common-lisp/src/step8_macros.lisp b/common-lisp/src/step8_macros.lisp index 4ae36f87b2..805de19516 100644 --- a/common-lisp/src/step8_macros.lisp +++ b/common-lisp/src/step8_macros.lisp @@ -285,7 +285,7 @@ (cdr (utils:raw-command-line-arguments))))) (env:set-env *repl-env* (types:make-mal-symbol "*ARGV*") - (types:wrap-value (cdr args) :listp t)) + (make-mal-list (mapcar #'make-mal-string (cdr args)))) (if (null args) (repl) (run-file (car args))))) diff --git a/common-lisp/src/step9_try.lisp b/common-lisp/src/step9_try.lisp index c03be8f2af..ecccf8ebbc 100644 --- a/common-lisp/src/step9_try.lisp +++ b/common-lisp/src/step9_try.lisp @@ -313,7 +313,7 @@ (cdr (utils:raw-command-line-arguments))))) (env:set-env *repl-env* (types:make-mal-symbol "*ARGV*") - (types:wrap-value (cdr args) :listp t)) + (make-mal-list (mapcar #'make-mal-string (cdr args)))) (if (null args) (repl) (run-file (car args))))) diff --git a/common-lisp/src/stepA_mal.lisp b/common-lisp/src/stepA_mal.lisp index fa629aa7ac..628e675a31 100644 --- a/common-lisp/src/stepA_mal.lisp +++ b/common-lisp/src/stepA_mal.lisp @@ -260,11 +260,11 @@ (env:set-env *repl-env* (types:make-mal-symbol "*cl-implementation*") - (types:wrap-value (lisp-implementation-type))) + (make-mal-string (lisp-implementation-type))) (env:set-env *repl-env* (types:make-mal-symbol "*cl-version*") - (types:wrap-value (lisp-implementation-version))) + (make-mal-string (lisp-implementation-version))) (rep "(def! not (fn* (a) (if a false true)))") (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") @@ -325,7 +325,7 @@ (cdr (utils:raw-command-line-arguments))))) (env:set-env *repl-env* (types:make-mal-symbol "*ARGV*") - (types:wrap-value (cdr args) :listp t)) + (make-mal-list (mapcar #'make-mal-string (cdr args)))) (if (null args) (repl) (run-file (car args))))) diff --git a/common-lisp/src/types.lisp b/common-lisp/src/types.lisp index 700258171a..a6c3830230 100644 --- a/common-lisp/src/types.lisp +++ b/common-lisp/src/types.lisp @@ -73,13 +73,7 @@ ;; Exception raised by user code :mal-user-exception ;; Error - :mal-error - - ;; Utilities - :wrap-value - :unwrap-value - :apply-unwrapped-values - :apply-unwrapped-values-prefer-bool)) + :mal-error)) (in-package :types) @@ -201,45 +195,3 @@ hash-function #'mal-data-value=))) (genhash:make-generic-hash-table :test 'mal-data-value-hash)) - -(defun wrap-value (value &key booleanp listp) - (typecase value - (number (make-mal-number value)) - ;; This needs to be before symbol since nil is a symbol - (null (funcall (cond - (booleanp #'make-mal-boolean) - (listp #'make-mal-list) - (t #'make-mal-nil)) - value)) - ;; This needs to before symbol since t, nil are symbols - (boolean (make-mal-boolean value)) - (keyword (make-mal-keyword value)) - (symbol (make-mal-symbol (symbol-name value))) - (string (make-mal-string value)) - (list (make-mal-list (map 'list #'wrap-value value))) - (vector (make-mal-vector (map 'vector #'wrap-value value))) - (hash-table (make-mal-hash-map (let ((new-hash-table (make-mal-value-hash-table))) - (genhash:hashmap (lambda (key value) - (setf (genhash:hashref (wrap-value key) new-hash-table) - (wrap-value value))) - value) - new-hash-table))))) - -(defun unwrap-value (value) - (switch-mal-type value - (list (mapcar #'unwrap-value (mal-data-value value))) - (vector (map 'vector #'unwrap-value (mal-data-value value))) - (hash-map (let ((hash-table (make-hash-table)) - (hash-map-value (mal-data-value value))) - (genhash:hashmap (lambda (key value) - (setf (genhash:hashref (mal-data-value key) hash-table) - (mal-data-value value))) - hash-map-value) - hash-table)) - (any (mal-data-value value)))) - -(defun apply-unwrapped-values (op &rest values) - (wrap-value (apply op (mapcar #'unwrap-value values)))) - -(defun apply-unwrapped-values-prefer-bool (op &rest values) - (wrap-value (apply op (mapcar #'unwrap-value values)) :booleanp t)) From baa3c3af1a0176993ac22fabdad3a8805563a89f Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 22 Aug 2017 16:18:31 +0530 Subject: [PATCH 0410/2308] Common Lisp: Do not prefix symbols with package name unnecessarily --- common-lisp/src/core.lisp | 148 ++++++++++----------- common-lisp/src/env.lisp | 8 +- common-lisp/src/printer.lisp | 30 ++--- common-lisp/src/step0_repl.lisp | 2 +- common-lisp/src/step1_read_print.lisp | 9 +- common-lisp/src/step2_eval.lisp | 43 +++---- common-lisp/src/step3_env.lisp | 59 ++++----- common-lisp/src/step4_if_fn_do.lisp | 37 +++--- common-lisp/src/step5_tco.lisp | 51 ++++---- common-lisp/src/step6_file.lisp | 47 +++---- common-lisp/src/step7_quote.lisp | 77 +++++------ common-lisp/src/step8_macros.lisp | 141 ++++++++++---------- common-lisp/src/step9_try.lisp | 173 ++++++++++++------------- common-lisp/src/stepA_mal.lisp | 178 ++++++++++++-------------- common-lisp/src/types.lisp | 52 ++++---- 15 files changed, 488 insertions(+), 567 deletions(-) diff --git a/common-lisp/src/core.lisp b/common-lisp/src/core.lisp index e8a4efcf01..06c7a03596 100644 --- a/common-lisp/src/core.lisp +++ b/common-lisp/src/core.lisp @@ -10,7 +10,7 @@ (in-package :core) -(define-condition index-error (types:mal-error) +(define-condition index-error (mal-error) ((size :initarg :size :reader index-error-size) (index :initarg :index :reader index-error-index) (sequence :initarg :sequence :reader index-error-sequence)) @@ -87,16 +87,16 @@ (make-mal-list values)) (defmal list? (value) - (wrap-boolean (or (types:mal-nil-p value) (types:mal-list-p value)))) + (wrap-boolean (or (mal-nil-p value) (mal-list-p value)))) (defmal empty? (value) - (wrap-boolean (zerop (length (types:mal-data-value value))))) + (wrap-boolean (zerop (length (mal-data-value value))))) (defmal count (value) (make-mal-number (length (mal-data-value value)))) (defmal = (value1 value2) - (wrap-boolean (types:mal-data-value= value1 value2))) + (wrap-boolean (mal-data-value= value1 value2))) (defmal < (value1 value2) (wrap-boolean (< (mal-data-value value1) (mal-data-value value2)))) @@ -111,114 +111,114 @@ (wrap-boolean (>= (mal-data-value value1) (mal-data-value value2)))) (defmal read-string (value) - (reader:read-str (types:mal-data-value value))) + (reader:read-str (mal-data-value value))) (defmal slurp (filename) (make-mal-string (read-file-string (mal-data-value filename)))) (defmal atom (value) - (types:make-mal-atom value)) + (make-mal-atom value)) (defmal atom? (value) - (wrap-boolean (types:mal-atom-p value))) + (wrap-boolean (mal-atom-p value))) (defmal deref (atom) - (types:mal-data-value atom)) + (mal-data-value atom)) (defmal reset! (atom value) - (setf (types:mal-data-value atom) value)) + (setf (mal-data-value atom) value)) (defmal swap! (atom fn &rest args) - (setf (types:mal-data-value atom) - (apply (types:mal-data-value fn) - (append (list (types:mal-data-value atom)) args)))) + (setf (mal-data-value atom) + (apply (mal-data-value fn) + (append (list (mal-data-value atom)) args)))) (defmal cons (element list) - (types:make-mal-list (cons element (listify (types:mal-data-value list))))) + (make-mal-list (cons element (listify (mal-data-value list))))) (defmal concat (&rest lists) - (types:make-mal-list (apply #'concatenate 'list (mapcar #'types:mal-data-value lists)))) + (make-mal-list (apply #'concatenate 'list (mapcar #'mal-data-value lists)))) (defmal nth (sequence index) - (or (nth (types:mal-data-value index) - (listify (types:mal-data-value sequence))) + (or (nth (mal-data-value index) + (listify (mal-data-value sequence))) (error 'index-error - :size (length (types:mal-data-value sequence)) - :index (types:mal-data-value index) + :size (length (mal-data-value sequence)) + :index (mal-data-value index) :sequence sequence))) (defmal first (sequence) - (or (first (listify (types:mal-data-value sequence))) mal-nil)) + (or (first (listify (mal-data-value sequence))) mal-nil)) (defmal rest (sequence) - (types:make-mal-list (rest (listify (types:mal-data-value sequence))))) + (make-mal-list (rest (listify (mal-data-value sequence))))) (defmal throw (value) - (error 'types:mal-user-exception :data value)) + (error 'mal-user-exception :data value)) (defmal apply (fn &rest values) - (let ((last (listify (types:mal-data-value (car (last values))))) + (let ((last (listify (mal-data-value (car (last values))))) (butlast (butlast values))) - (apply (types:mal-data-value fn) (append butlast last)))) + (apply (mal-data-value fn) (append butlast last)))) (defmal map (fn sequence) - (let ((applicants (listify (types:mal-data-value sequence)))) - (types:make-mal-list (mapcar (types:mal-data-value fn) applicants)))) + (let ((applicants (listify (mal-data-value sequence)))) + (make-mal-list (mapcar (mal-data-value fn) applicants)))) (defmal nil? (value) - (wrap-boolean (types:mal-nil-p value))) + (wrap-boolean (mal-nil-p value))) (defmal true? (value) - (wrap-boolean (and (types:mal-boolean-p value) (types:mal-data-value value)))) + (wrap-boolean (and (mal-boolean-p value) (mal-data-value value)))) (defmal false? (value) - (wrap-boolean (and (types:mal-boolean-p value) (not (types:mal-data-value value))))) + (wrap-boolean (and (mal-boolean-p value) (not (mal-data-value value))))) (defmal symbol (string) - (types:make-mal-symbol (types:mal-data-value string))) + (make-mal-symbol (mal-data-value string))) (defmal symbol? (value) - (wrap-boolean (types:mal-symbol-p value))) + (wrap-boolean (mal-symbol-p value))) (defmal keyword (keyword) - (if (types:mal-keyword-p keyword) + (if (mal-keyword-p keyword) keyword - (types:make-mal-keyword (format nil ":~a" (types:mal-data-value keyword))))) + (make-mal-keyword (format nil ":~a" (mal-data-value keyword))))) (defmal keyword? (value) - (wrap-boolean (types:mal-keyword-p value))) + (wrap-boolean (mal-keyword-p value))) (defmal vector (&rest elements) - (types:make-mal-vector (map 'vector #'identity elements))) + (make-mal-vector (map 'vector #'identity elements))) (defmal vector? (value) - (wrap-boolean (types:mal-vector-p value))) + (wrap-boolean (mal-vector-p value))) (defmal hash-map (&rest elements) - (let ((hash-map (types:make-mal-value-hash-table))) + (let ((hash-map (make-mal-value-hash-table))) (loop for (key value) on elements by #'cddr - do (setf (genhash:hashref key hash-map) value)) - (types:make-mal-hash-map hash-map))) + do (setf (hashref key hash-map) value)) + (make-mal-hash-map hash-map))) (defmal map? (value) - (wrap-boolean (types:mal-hash-map-p value))) + (wrap-boolean (mal-hash-map-p value))) (defmal assoc (hash-map &rest elements) - (let ((hash-map-value (types:mal-data-value hash-map)) - (new-hash-map (types:make-mal-value-hash-table))) + (let ((hash-map-value (mal-data-value hash-map)) + (new-hash-map (make-mal-value-hash-table))) - (genhash:hashmap (lambda (key value) - (declare (ignorable value)) - (setf (genhash:hashref key new-hash-map) - (genhash:hashref key hash-map-value))) - hash-map-value) + (hashmap (lambda (key value) + (declare (ignorable value)) + (setf (hashref key new-hash-map) + (hashref key hash-map-value))) + hash-map-value) (loop for (key value) on elements by #'cddr - do (setf (genhash:hashref key new-hash-map) value)) + do (setf (hashref key new-hash-map) value)) - (types:make-mal-hash-map new-hash-map))) + (make-mal-hash-map new-hash-map))) (defmal dissoc (hash-map &rest elements) (let ((hash-map-value (mal-data-value hash-map)) @@ -234,14 +234,14 @@ (make-mal-hash-map new-hash-map))) (defmal get (hash-map key) - (or (and (types:mal-hash-map-p hash-map) (genhash:hashref key (types:mal-data-value hash-map))) + (or (and (mal-hash-map-p hash-map) (hashref key (mal-data-value hash-map))) types:mal-nil)) (defmal contains? (hash-map key) (if (genhash:hashref key (types:mal-data-value hash-map)) types:mal-true types:mal-false)) (defmal keys (hash-map) - (let ((hash-map-value (types:mal-data-value hash-map)) + (let ((hash-map-value (mal-data-value hash-map)) keys) (hashmap (lambda (key value) @@ -252,7 +252,7 @@ (make-mal-list (nreverse keys)))) (defmal vals (hash-map) - (let ((hash-map-value (types:mal-data-value hash-map)) + (let ((hash-map-value (mal-data-value hash-map)) values) (hashmap (lambda (key value) @@ -263,30 +263,30 @@ (make-mal-list (nreverse values)))) (defmal sequential? (value) - (wrap-boolean (or (types:mal-vector-p value) (types:mal-list-p value)))) + (wrap-boolean (or (mal-vector-p value) (mal-list-p value)))) (defmal readline (prompt) - (format *standard-output* (types:mal-data-value prompt)) + (format *standard-output* (mal-data-value prompt)) (force-output *standard-output*) (make-mal-string (read-line *standard-input* nil))) (defmal string? (value) - (wrap-boolean (types:mal-string-p value))) + (wrap-boolean (mal-string-p value))) (defmal time-ms () - (types:make-mal-number (round (/ (get-internal-real-time) - (/ internal-time-units-per-second - 1000))))) + (make-mal-number (round (/ (get-internal-real-time) + (/ internal-time-units-per-second + 1000))))) (defmal conj (value &rest elements) - (cond ((types:mal-list-p value) - (types:make-mal-list (append (nreverse elements) - (types:mal-data-value value)))) - ((types:mal-vector-p value) - (types:make-mal-vector (concatenate 'vector - (types:mal-data-value value) - elements))) - (t (error 'types:mal-user-exception)))) + (cond ((mal-list-p value) + (make-mal-list (append (nreverse elements) + (mal-data-value value)))) + ((mal-vector-p value) + (make-mal-vector (concatenate 'vector + (mal-data-value value) + elements))) + (t (error 'mal-user-exception)))) (defmal seq (value) (if (zerop (length (mal-data-value value))) @@ -301,16 +301,16 @@ (defmal with-meta (value meta) (funcall (switch-mal-type value - (types:string #'types:make-mal-string) - (types:symbol #'types:make-mal-symbol) - (types:list #'types:make-mal-list) - (types:vector #'types:make-mal-vector) - (types:hash-map #'types:make-mal-hash-map) - (types:fn #'types:make-mal-fn) - (types:builtin-fn #'types:make-mal-builtin-fn)) - (types:mal-data-value value) + (types:string #'make-mal-string) + (types:symbol #'make-mal-symbol) + (types:list #'make-mal-list) + (types:vector #'make-mal-vector) + (types:hash-map #'make-mal-hash-map) + (types:fn #'make-mal-fn) + (types:builtin-fn #'make-mal-builtin-fn)) + (mal-data-value value) :meta meta - :attrs (types:mal-data-attrs value))) + :attrs (mal-data-attrs value))) (defmal meta (value) (or (types:mal-data-meta value) types:mal-nil)) diff --git a/common-lisp/src/env.lisp b/common-lisp/src/env.lisp index b28d984189..0a2ff05cae 100644 --- a/common-lisp/src/env.lisp +++ b/common-lisp/src/env.lisp @@ -9,14 +9,14 @@ (in-package :env) -(define-condition undefined-symbol (types:mal-runtime-exception) +(define-condition undefined-symbol (mal-runtime-exception) ((symbol :initarg :symbol :reader symbol)) (:report (lambda (condition stream) (format stream "'~a' not found" (symbol condition))))) -(define-condition arity-mismatch (types:mal-runtime-exception) +(define-condition arity-mismatch (mal-runtime-exception) ((required :initarg :required :reader required) (provided :initarg :provided :reader provided)) (:report (lambda (condition stream) @@ -41,9 +41,7 @@ :symbol (format nil "~a" (mal-data-value symbol))))) (defun set-env (env symbol value) - (setf (gethash (types:mal-data-value symbol) - (mal-env-bindings env)) - value)) + (setf (gethash (mal-data-value symbol) (mal-env-bindings env)) value)) (defun create-mal-env (&key parent binds exprs) (let ((env (make-mal-env :parent parent)) diff --git a/common-lisp/src/printer.lisp b/common-lisp/src/printer.lisp index a6833d0ef8..e6c8de6651 100644 --- a/common-lisp/src/printer.lisp +++ b/common-lisp/src/printer.lisp @@ -18,36 +18,36 @@ start-delimiter (mapcar (lambda (value) (pr-str value print-readably)) - (utils:listify (types:mal-data-value sequence))) + (listify (mal-data-value sequence))) end-delimiter)) (defun pr-mal-hash-map (hash-map &optional (print-readably t) &aux repr) - (genhash:hashmap (lambda (key value) - (push (pr-str value print-readably) repr) - (push (pr-str key print-readably) repr)) - (types:mal-data-value hash-map)) + (hashmap (lambda (key value) + (push (pr-str value print-readably) repr) + (push (pr-str key print-readably) repr)) + (mal-data-value hash-map)) (format nil "{~{~a ~a~^ ~}}" repr)) (defun pr-string (ast &optional (print-readably t)) - (if print-readably - (utils:replace-all (prin1-to-string (types:mal-data-value ast)) - " + (if print-readably + (replace-all (prin1-to-string (mal-data-value ast)) + " " - "\\n") - (types:mal-data-value ast))) + "\\n") + (mal-data-value ast))) (defun pr-str (ast &optional (print-readably t)) (when ast (switch-mal-type ast - (types:number (format nil "~d" (types:mal-data-value ast))) - (types:boolean (if (types:mal-data-value ast) "true" "false")) + (types:number (format nil "~d" (mal-data-value ast))) + (types:boolean (if (mal-data-value ast) "true" "false")) (types:nil "nil") (types:string (pr-string ast print-readably)) - (types:symbol (format nil "~a" (types:mal-data-value ast))) - (types:keyword (format nil "~a" (types:mal-data-value ast))) + (types:symbol (format nil "~a" (mal-data-value ast))) + (types:keyword (format nil "~a" (mal-data-value ast))) (types:list (pr-mal-sequence "(" ast ")" print-readably)) (types:vector (pr-mal-sequence "[" ast "]" print-readably)) (types:hash-map (pr-mal-hash-map ast print-readably)) - (types:atom (format nil "(atom ~a)" (pr-str (types:mal-data-value ast)))) + (types:atom (format nil "(atom ~a)" (pr-str (mal-data-value ast)))) (types:builtin-fn "#") (types:builtin-fn "#")))) diff --git a/common-lisp/src/step0_repl.lisp b/common-lisp/src/step0_repl.lisp index c881dd5a75..79d180056d 100644 --- a/common-lisp/src/step0_repl.lisp +++ b/common-lisp/src/step0_repl.lisp @@ -27,7 +27,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (cl-readline:readline :prompt prompt + (rl:readline :prompt prompt :add-history t :novelty-check (lambda (old new) (not (string= old new)))) diff --git a/common-lisp/src/step1_read_print.lisp b/common-lisp/src/step1_read_print.lisp index b16578b629..1c0bd248a2 100644 --- a/common-lisp/src/step1_read_print.lisp +++ b/common-lisp/src/step1_read_print.lisp @@ -20,12 +20,9 @@ (defun rep (string) (handler-case - (mal-print (mal-eval (mal-read string) - (make-hash-table :test #'equal))) + (mal-print (mal-eval (mal-read string) (make-hash-table :test #'equal))) (reader:eof (condition) - (format nil - "~a" - condition)))) + (format nil "~a" condition)))) (defvar *use-readline-p* nil) @@ -36,7 +33,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (cl-readline:readline :prompt prompt + (rl:readline :prompt prompt :add-history t :novelty-check (lambda (old new) (not (string= old new)))) diff --git a/common-lisp/src/step2_eval.lisp b/common-lisp/src/step2_eval.lisp index 70fbf4d5d8..ecfaedb4b4 100644 --- a/common-lisp/src/step2_eval.lisp +++ b/common-lisp/src/step2_eval.lisp @@ -13,25 +13,25 @@ (in-package :mal) -(defvar *repl-env* (types:make-mal-value-hash-table)) +(defvar *repl-env* (make-mal-value-hash-table)) -(setf (genhash:hashref (types:make-mal-symbol "+") *repl-env*) - (types:make-mal-builtin-fn (lambda (value1 value2) +(setf (genhash:hashref (make-mal-symbol "+") *repl-env*) + (make-mal-builtin-fn (lambda (value1 value2) (make-mal-number (+ (mal-data-value value1) (mal-data-value value2)))))) -(setf (genhash:hashref (types:make-mal-symbol "-") *repl-env*) - (types:make-mal-builtin-fn (lambda (value1 value2) +(setf (genhash:hashref (make-mal-symbol "-") *repl-env*) + (make-mal-builtin-fn (lambda (value1 value2) (make-mal-number (- (mal-data-value value1) (mal-data-value value2)))))) -(setf (genhash:hashref (types:make-mal-symbol "*") *repl-env*) - (types:make-mal-builtin-fn (lambda (value1 value2) +(setf (genhash:hashref (make-mal-symbol "*") *repl-env*) + (make-mal-builtin-fn (lambda (value1 value2) (make-mal-number (* (mal-data-value value1) (mal-data-value value2)))))) -(setf (genhash:hashref (types:make-mal-symbol "/") *repl-env*) - (types:make-mal-builtin-fn (lambda (value1 value2) +(setf (genhash:hashref (make-mal-symbol "/") *repl-env*) + (make-mal-builtin-fn (lambda (value1 value2) (make-mal-number (/ (mal-data-value value1) (mal-data-value value2)))))) @@ -40,21 +40,21 @@ (if value value (error 'env:undefined-symbol - :symbol (format nil "~a" (types:mal-data-value symbol)))))) + :symbol (format nil "~a" (mal-data-value symbol)))))) (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) - (types:mal-data-value sequence))) + (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (types:mal-data-value hash-map)) - (new-hash-table (types:make-mal-value-hash-table))) + (let ((hash-map-value (mal-data-value hash-map)) + (new-hash-table (make-mal-value-hash-table))) (genhash:hashmap (lambda (key value) (setf (genhash:hashref (mal-eval key env) new-hash-table) (mal-eval value env))) hash-map-value) - (types:make-mal-hash-map new-hash-table))) + (make-mal-hash-map new-hash-table))) (defun eval-ast (ast env) (switch-mal-type ast @@ -69,11 +69,11 @@ (defun mal-eval (ast env) (cond - ((not (types:mal-list-p ast)) (eval-ast ast env)) - ((zerop (length (types:mal-data-value ast))) ast) + ((not (mal-list-p ast)) (eval-ast ast env)) + ((zerop (length (mal-data-value ast))) ast) (t (progn (let ((evaluated-list (eval-ast ast env))) - (apply (types:mal-data-value (car evaluated-list)) + (apply (mal-data-value (car evaluated-list)) (cdr evaluated-list))))))) (defun mal-print (expression) @@ -81,12 +81,9 @@ (defun rep (string) (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) + (mal-print (mal-eval (mal-read string) *repl-env*)) (error (condition) - (format nil - "~a" - condition)))) + (format nil "~a" condition)))) (defvar *use-readline-p* nil) @@ -97,7 +94,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (cl-readline:readline :prompt prompt + (rl:readline :prompt prompt :add-history t :novelty-check (lambda (old new) (not (string= old new)))) diff --git a/common-lisp/src/step3_env.lisp b/common-lisp/src/step3_env.lisp index d988f12c41..ff34662223 100644 --- a/common-lisp/src/step3_env.lisp +++ b/common-lisp/src/step3_env.lisp @@ -17,28 +17,28 @@ (defvar *repl-env* (env:create-mal-env)) (env:set-env *repl-env* - (types:make-mal-symbol "+") - (types:make-mal-builtin-fn (lambda (value1 value2) - (make-mal-number (+ (mal-data-value value1) - (mal-data-value value2)))))) + (make-mal-symbol "+") + (make-mal-builtin-fn (lambda (value1 value2) + (make-mal-number (+ (mal-data-value value1) + (mal-data-value value2)))))) (env:set-env *repl-env* - (types:make-mal-symbol "-") - (types:make-mal-builtin-fn (lambda (value1 value2) - (make-mal-number (- (mal-data-value value1) - (mal-data-value value2)))))) + (make-mal-symbol "-") + (make-mal-builtin-fn (lambda (value1 value2) + (make-mal-number (- (mal-data-value value1) + (mal-data-value value2)))))) (env:set-env *repl-env* - (types:make-mal-symbol "*") - (types:make-mal-builtin-fn (lambda (value1 value2) - (make-mal-number (* (mal-data-value value1) - (mal-data-value value2)))))) + (make-mal-symbol "*") + (make-mal-builtin-fn (lambda (value1 value2) + (make-mal-number (* (mal-data-value value1) + (mal-data-value value2)))))) (env:set-env *repl-env* - (types:make-mal-symbol "/") - (types:make-mal-builtin-fn (lambda (value1 value2) - (make-mal-number (/ (mal-data-value value1) - (mal-data-value value2)))))) + (make-mal-symbol "/") + (make-mal-builtin-fn (lambda (value1 value2) + (make-mal-number (/ (mal-data-value value1) + (mal-data-value value2)))))) (defvar mal-def! (make-mal-symbol "def!")) (defvar mal-let* (make-mal-symbol "let*")) @@ -46,16 +46,16 @@ (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) - (types:mal-data-value sequence))) + (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (types:mal-data-value hash-map)) - (new-hash-table (types:make-mal-value-hash-table))) + (let ((hash-map-value (mal-data-value hash-map)) + (new-hash-table (make-mal-value-hash-table))) (genhash:hashmap (lambda (key value) (setf (genhash:hashref (mal-eval key env) new-hash-table) (mal-eval value env))) hash-map-value) - (types:make-mal-hash-map new-hash-table))) + (make-mal-hash-map new-hash-table))) (defun eval-ast (ast env) (switch-mal-type ast @@ -67,13 +67,13 @@ (defun eval-let* (forms env) (let ((new-env (env:create-mal-env :parent env)) - (bindings (utils:listify (types:mal-data-value (second forms))))) + (bindings (utils:listify (mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - types:mal-nil) + mal-nil) new-env))) (loop for (symbol value) on bindings @@ -90,7 +90,7 @@ ((mal-data-value= mal-let* (first forms)) (eval-let* forms env)) (t (let ((evaluated-list (eval-ast ast env))) - (apply (types:mal-data-value (car evaluated-list)) + (apply (mal-data-value (car evaluated-list)) (cdr evaluated-list))))))) (defun mal-read (string) @@ -98,8 +98,8 @@ (defun mal-eval (ast env) (cond - ((null ast) types:mal-nil) - ((not (types:mal-list-p ast)) (eval-ast ast env)) + ((null ast) mal-nil) + ((not (mal-list-p ast)) (eval-ast ast env)) ((zerop (length (mal-data-value ast))) ast) (t (eval-list ast env)))) @@ -108,12 +108,9 @@ (defun rep (string) (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) + (mal-print (mal-eval (mal-read string) *repl-env*)) (error (condition) - (format nil - "~a" - condition)))) + (format nil "~a" condition)))) (defvar *use-readline-p* nil) @@ -124,7 +121,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (cl-readline:readline :prompt prompt + (rl:readline :prompt prompt :add-history t :novelty-check (lambda (old new) (not (string= old new)))) diff --git a/common-lisp/src/step4_if_fn_do.lisp b/common-lisp/src/step4_if_fn_do.lisp index 52b3a934bf..99fca65be4 100644 --- a/common-lisp/src/step4_if_fn_do.lisp +++ b/common-lisp/src/step4_if_fn_do.lisp @@ -18,9 +18,7 @@ (defvar *repl-env* (env:create-mal-env)) (dolist (binding core:ns) - (env:set-env *repl-env* - (car binding) - (cdr binding))) + (env:set-env *repl-env* (car binding) (cdr binding))) (defvar mal-def! (make-mal-symbol "def!")) (defvar mal-let* (make-mal-symbol "let*")) @@ -34,13 +32,13 @@ (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (types:mal-data-value hash-map)) - (new-hash-table (types:make-mal-value-hash-table))) + (let ((hash-map-value (mal-data-value hash-map)) + (new-hash-table (make-mal-value-hash-table))) (genhash:hashmap (lambda (key value) (setf (genhash:hashref (mal-eval key env) new-hash-table) (mal-eval value env))) hash-map-value) - (types:make-mal-hash-map new-hash-table))) + (make-mal-hash-map new-hash-table))) (defun eval-ast (ast env) (switch-mal-type ast @@ -52,13 +50,13 @@ (defun eval-let* (forms env) (let ((new-env (env:create-mal-env :parent env)) - (bindings (utils:listify (types:mal-data-value (second forms))))) + (bindings (utils:listify (mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - types:mal-nil) + mal-nil) new-env))) (loop for (symbol value) on bindings @@ -79,19 +77,17 @@ (cdr forms))))) ((mal-data-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) - (mal-eval (if (or (mal-data-value= predicate types:mal-nil) - (mal-data-value= predicate types:mal-false)) + (mal-eval (if (or (mal-data-value= predicate mal-nil) + (mal-data-value= predicate mal-false)) (fourth forms) (third forms)) env))) ((mal-data-value= mal-fn* (first forms)) - (types:make-mal-fn (let ((arglist (second forms)) + (make-mal-fn (let ((arglist (second forms)) (body (third forms))) (lambda (&rest args) (mal-eval body (env:create-mal-env :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) + :binds (listify (mal-data-value arglist)) :exprs args)))))) (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) @@ -104,8 +100,8 @@ (defun mal-eval (ast env) (cond - ((null ast) types:mal-nil) - ((not (types:mal-list-p ast)) (eval-ast ast env)) + ((null ast) mal-nil) + ((not (mal-list-p ast)) (eval-ast ast env)) ((zerop (length (mal-data-value ast))) ast) (t (eval-list ast env)))) @@ -114,12 +110,9 @@ (defun rep (string) (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) + (mal-print (mal-eval (mal-read string) *repl-env*)) (error (condition) - (format nil - "~a" - condition)))) + (format nil "~a" condition)))) (rep "(def! not (fn* (a) (if a false true)))") @@ -132,7 +125,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (cl-readline:readline :prompt prompt + (rl:readline :prompt prompt :add-history t :novelty-check (lambda (old new) (not (string= old new)))) diff --git a/common-lisp/src/step5_tco.lisp b/common-lisp/src/step5_tco.lisp index 181bfab623..18150d6df2 100644 --- a/common-lisp/src/step5_tco.lisp +++ b/common-lisp/src/step5_tco.lisp @@ -18,9 +18,7 @@ (defvar *repl-env* (env:create-mal-env)) (dolist (binding core:ns) - (env:set-env *repl-env* - (car binding) - (cdr binding))) + (env:set-env *repl-env* (car binding) (cdr binding))) (defvar mal-def! (make-mal-symbol "def!")) (defvar mal-let* (make-mal-symbol "let*")) @@ -34,13 +32,13 @@ (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (types:mal-data-value hash-map)) - (new-hash-table (types:make-mal-value-hash-table))) + (let ((hash-map-value (mal-data-value hash-map)) + (new-hash-table (make-mal-value-hash-table))) (genhash:hashmap (lambda (key value) (setf (genhash:hashref (mal-eval key env) new-hash-table) (mal-eval value env))) hash-map-value) - (types:make-mal-hash-map new-hash-table))) + (make-mal-hash-map new-hash-table))) (defun eval-ast (ast env) (switch-mal-type ast @@ -56,8 +54,8 @@ (defun mal-eval (ast env) (loop do (cond - ((null ast) (return types:mal-nil)) - ((not (types:mal-list-p ast)) (return (eval-ast ast env))) + ((null ast) (return mal-nil)) + ((not (mal-list-p ast)) (return (eval-ast ast env))) ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) (cond @@ -66,13 +64,13 @@ ((mal-data-value= mal-let* (first forms)) (let ((new-env (env:create-mal-env :parent env)) - (bindings (utils:listify (types:mal-data-value (second forms))))) + (bindings (utils:listify (mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - types:mal-nil) + mal-nil) new-env))) (loop for (symbol value) on bindings @@ -88,31 +86,29 @@ ((mal-data-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (mal-data-value= predicate types:mal-nil) - (mal-data-value= predicate types:mal-false)) + (setf ast (if (or (mal-data-value= predicate mal-nil) + (mal-data-value= predicate mal-false)) (fourth forms) (third forms))))) ((mal-data-value= mal-fn* (first forms)) (return (let ((arglist (second forms)) (body (third forms))) - (types:make-mal-fn (lambda (&rest args) - (mal-eval body (env:create-mal-env :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env)))))) + (make-mal-fn (lambda (&rest args) + (mal-eval body (env:create-mal-env :parent env + :binds (listify (mal-data-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env)))))) (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) ;; If first element is a mal function unwrap it - (if (not (types:mal-fn-p function)) + (if (not (mal-fn-p function)) (return (apply (mal-data-value function) (cdr evaluated-list))) - (let* ((attrs (types:mal-data-attrs function))) + (let* ((attrs (mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) env (env:create-mal-env :parent (cdr (assoc 'env attrs)) :binds (map 'list @@ -125,12 +121,9 @@ (defun rep (string) (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) + (mal-print (mal-eval (mal-read string) *repl-env*)) (error (condition) - (format nil - "~a" - condition)))) + (format nil "~a" condition)))) (rep "(def! not (fn* (a) (if a false true)))") @@ -143,7 +136,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (cl-readline:readline :prompt prompt + (rl:readline :prompt prompt :add-history t :novelty-check (lambda (old new) (not (string= old new)))) diff --git a/common-lisp/src/step6_file.lisp b/common-lisp/src/step6_file.lisp index e9585c63b2..152a9af6f8 100644 --- a/common-lisp/src/step6_file.lisp +++ b/common-lisp/src/step6_file.lisp @@ -18,9 +18,7 @@ (defvar *repl-env* (env:create-mal-env)) (dolist (binding core:ns) - (env:set-env *repl-env* - (car binding) - (cdr binding))) + (env:set-env *repl-env* (car binding) (cdr binding))) (defvar mal-def! (make-mal-symbol "def!")) (defvar mal-let* (make-mal-symbol "let*")) @@ -34,13 +32,13 @@ (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (types:mal-data-value hash-map)) - (new-hash-table (types:make-mal-value-hash-table))) + (let ((hash-map-value (mal-data-value hash-map)) + (new-hash-table (make-mal-value-hash-table))) (genhash:hashmap (lambda (key value) (setf (genhash:hashref (mal-eval key env) new-hash-table) (mal-eval value env))) hash-map-value) - (types:make-mal-hash-map new-hash-table))) + (make-mal-hash-map new-hash-table))) (defun eval-ast (ast env) (switch-mal-type ast @@ -56,8 +54,8 @@ (defun mal-eval (ast env) (loop do (cond - ((null ast) (return types:mal-nil)) - ((not (types:mal-list-p ast)) (return (eval-ast ast env))) + ((null ast) (return mal-nil)) + ((not (mal-list-p ast)) (return (eval-ast ast env))) ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) (cond @@ -66,13 +64,13 @@ ((mal-data-value= mal-let* (first forms)) (let ((new-env (env:create-mal-env :parent env)) - (bindings (utils:listify (types:mal-data-value (second forms))))) + (bindings (utils:listify (mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - types:mal-nil) + mal-nil) new-env))) (loop for (symbol value) on bindings @@ -88,19 +86,17 @@ ((mal-data-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (mal-data-value= predicate types:mal-nil) - (mal-data-value= predicate types:mal-false)) + (setf ast (if (or (mal-data-value= predicate mal-nil) + (mal-data-value= predicate mal-false)) (fourth forms) (third forms))))) ((mal-data-value= mal-fn* (first forms)) (return (let ((arglist (second forms)) (body (third forms))) - (types:make-mal-fn (lambda (&rest args) + (make-mal-fn (lambda (&rest args) (mal-eval body (env:create-mal-env :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) + :binds (listify (mal-data-value arglist)) :exprs args))) :attrs (list (cons 'params arglist) (cons 'ast body) @@ -109,10 +105,10 @@ (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) ;; If first element is a mal function unwrap it - (if (not (types:mal-fn-p function)) + (if (not (mal-fn-p function)) (return (apply (mal-data-value function) (cdr evaluated-list))) - (let* ((attrs (types:mal-data-attrs function))) + (let* ((attrs (mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) env (env:create-mal-env :parent (cdr (assoc 'env attrs)) :binds (map 'list @@ -125,16 +121,13 @@ (defun rep (string) (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) + (mal-print (mal-eval (mal-read string) *repl-env*)) (error (condition) - (format nil - "~a" - condition)))) + (format nil "~a" condition)))) (env:set-env *repl-env* - (types:make-mal-symbol "eval") - (types:make-mal-builtin-fn (lambda (ast) + (make-mal-symbol "eval") + (make-mal-builtin-fn (lambda (ast) (mal-eval ast *repl-env*)))) (rep "(def! not (fn* (a) (if a false true)))") @@ -149,7 +142,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (cl-readline:readline :prompt prompt + (rl:readline :prompt prompt :add-history t :novelty-check (lambda (old new) (not (string= old new)))) @@ -189,7 +182,7 @@ argv (cdr (utils:raw-command-line-arguments))))) (env:set-env *repl-env* - (types:make-mal-symbol "*ARGV*") + (make-mal-symbol "*ARGV*") (make-mal-list (mapcar #'make-mal-string (cdr args)))) (if (null args) (repl) diff --git a/common-lisp/src/step7_quote.lisp b/common-lisp/src/step7_quote.lisp index 46ab36c369..956b0bcf01 100644 --- a/common-lisp/src/step7_quote.lisp +++ b/common-lisp/src/step7_quote.lisp @@ -18,9 +18,7 @@ (defvar *repl-env* (env:create-mal-env)) (dolist (binding core:ns) - (env:set-env *repl-env* - (car binding) - (cdr binding))) + (env:set-env *repl-env* (car binding) (cdr binding))) (defvar mal-def! (make-mal-symbol "def!")) (defvar mal-let* (make-mal-symbol "let*")) @@ -40,13 +38,13 @@ (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (types:mal-data-value hash-map)) - (new-hash-table (types:make-mal-value-hash-table))) + (let ((hash-map-value (mal-data-value hash-map)) + (new-hash-table (make-mal-value-hash-table))) (genhash:hashmap (lambda (key value) (setf (genhash:hashref (mal-eval key env) new-hash-table) (mal-eval value env))) hash-map-value) - (types:make-mal-hash-map new-hash-table))) + (make-mal-hash-map new-hash-table))) (defun eval-ast (ast env) (switch-mal-type ast @@ -59,11 +57,11 @@ (defun is-pair (value) (and (or (mal-list-p value) (mal-vector-p value)) - (< 0 (length (types:mal-data-value value))))) + (< 0 (length (mal-data-value value))))) (defun quasiquote (ast) (if (not (is-pair ast)) - (types:make-mal-list (list mal-quote ast)) + (make-mal-list (list mal-quote ast)) (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond ((mal-data-value= mal-unquote (first forms)) @@ -71,14 +69,14 @@ ((and (is-pair (first forms)) (mal-data-value= mal-splice-unquote - (first (mal-data-value (first forms))))) - (types:make-mal-list (list mal-concat - (second (mal-data-value (first forms))) - (quasiquote (make-mal-list (cdr forms)))))) + (first (mal-data-value (first forms))))) + (make-mal-list (list mal-concat + (second (mal-data-value (first forms))) + (quasiquote (make-mal-list (cdr forms)))))) - (t (types:make-mal-list (list mal-cons - (quasiquote (first forms)) - (quasiquote (make-mal-list (cdr forms)))))))))) + (t (make-mal-list (list mal-cons + (quasiquote (first forms)) + (quasiquote (make-mal-list (cdr forms)))))))))) (defun mal-read (string) (reader:read-str string)) @@ -86,8 +84,8 @@ (defun mal-eval (ast env) (loop do (cond - ((null ast) (return types:mal-nil)) - ((not (types:mal-list-p ast)) (return (eval-ast ast env))) + ((null ast) (return mal-nil)) + ((not (mal-list-p ast)) (return (eval-ast ast env))) ((zerop (length (mal-data-value ast))) (return ast)) (t (let ((forms (mal-data-value ast))) (cond @@ -102,13 +100,13 @@ ((mal-data-value= mal-let* (first forms)) (let ((new-env (env:create-mal-env :parent env)) - (bindings (utils:listify (types:mal-data-value (second forms))))) + (bindings (utils:listify (mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - types:mal-nil) + mal-nil) new-env))) (loop for (symbol value) on bindings @@ -124,31 +122,29 @@ ((mal-data-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (mal-data-value= predicate types:mal-nil) - (mal-data-value= predicate types:mal-false)) + (setf ast (if (or (mal-data-value= predicate mal-nil) + (mal-data-value= predicate mal-false)) (fourth forms) (third forms))))) ((mal-data-value= mal-fn* (first forms)) (return (let ((arglist (second forms)) (body (third forms))) - (types:make-mal-fn (lambda (&rest args) - (mal-eval body (env:create-mal-env :parent env - :binds (map 'list - #'identity - (mal-data-value arglist)) - :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env)))))) + (make-mal-fn (lambda (&rest args) + (mal-eval body (env:create-mal-env :parent env + :binds (listify (mal-data-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env)))))) (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) ;; If first element is a mal function unwrap it - (if (not (types:mal-fn-p function)) + (if (not (mal-fn-p function)) (return (apply (mal-data-value function) (cdr evaluated-list))) - (let* ((attrs (types:mal-data-attrs function))) + (let* ((attrs (mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) env (env:create-mal-env :parent (cdr (assoc 'env attrs)) :binds (map 'list @@ -161,17 +157,14 @@ (defun rep (string) (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) + (mal-print (mal-eval (mal-read string) *repl-env*)) (error (condition) - (format nil - "~a" - condition)))) + (format nil "~a" condition)))) (env:set-env *repl-env* - (types:make-mal-symbol "eval") - (types:make-mal-builtin-fn (lambda (ast) - (mal-eval ast *repl-env*)))) + (make-mal-symbol "eval") + (make-mal-builtin-fn (lambda (ast) + (mal-eval ast *repl-env*)))) (rep "(def! not (fn* (a) (if a false true)))") (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") @@ -185,7 +178,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (cl-readline:readline :prompt prompt + (rl:readline :prompt prompt :add-history t :novelty-check (lambda (old new) (not (string= old new)))) @@ -225,7 +218,7 @@ argv (cdr (utils:raw-command-line-arguments))))) (env:set-env *repl-env* - (types:make-mal-symbol "*ARGV*") + (make-mal-symbol "*ARGV*") (make-mal-list (mapcar #'make-mal-string (cdr args)))) (if (null args) (repl) diff --git a/common-lisp/src/step8_macros.lisp b/common-lisp/src/step8_macros.lisp index 805de19516..0c2c214e41 100644 --- a/common-lisp/src/step8_macros.lisp +++ b/common-lisp/src/step8_macros.lisp @@ -15,7 +15,7 @@ (in-package :mal) -(define-condition invalid-function (types:mal-runtime-exception) +(define-condition invalid-function (mal-runtime-exception) ((form :initarg :form :reader form) (context :initarg :context :reader context)) (:report (lambda (condition stream) @@ -30,9 +30,7 @@ (defvar *repl-env* (env:create-mal-env)) (dolist (binding core:ns) - (env:set-env *repl-env* - (car binding) - (cdr binding))) + (env:set-env *repl-env* (car binding) (cdr binding))) (defvar mal-def! (make-mal-symbol "def!")) (defvar mal-let* (make-mal-symbol "let*")) @@ -51,16 +49,16 @@ (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) - (types:mal-data-value sequence))) + (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (types:mal-data-value hash-map)) - (new-hash-table (types:make-mal-value-hash-table))) + (let ((hash-map-value (mal-data-value hash-map)) + (new-hash-table (make-mal-value-hash-table))) (genhash:hashmap (lambda (key value) (setf (genhash:hashref (mal-eval key env) new-hash-table) (mal-eval value env))) hash-map-value) - (types:make-mal-hash-map new-hash-table))) + (make-mal-hash-map new-hash-table))) (defun eval-ast (ast env) (switch-mal-type ast @@ -73,42 +71,42 @@ (defun is-pair (value) (and (or (mal-list-p value) (mal-vector-p value)) - (< 0 (length (types:mal-data-value value))))) + (< 0 (length (mal-data-value value))))) (defun quasiquote (ast) (if (not (is-pair ast)) - (types:make-mal-list (list mal-quote ast)) - (let ((forms (map 'list #'identity (types:mal-data-value ast)))) + (make-mal-list (list mal-quote ast)) + (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond - ((types:mal-data-value= mal-unquote (first forms)) + ((mal-data-value= mal-unquote (first forms)) (second forms)) ((and (is-pair (first forms)) - (types:mal-data-value= mal-splice-unquote - (first (types:mal-data-value (first forms))))) - (types:make-mal-list (list mal-concat - (second (types:mal-data-value (first forms))) - (quasiquote (make-mal-list (cdr forms)))))) + (mal-data-value= mal-splice-unquote + (first (mal-data-value (first forms))))) + (make-mal-list (list mal-concat + (second (mal-data-value (first forms))) + (quasiquote (make-mal-list (cdr forms)))))) - (t (types:make-mal-list (list mal-cons - (quasiquote (first forms)) - (quasiquote (make-mal-list (cdr forms)))))))))) + (t (make-mal-list (list mal-cons + (quasiquote (first forms)) + (quasiquote (make-mal-list (cdr forms)))))))))) (defun is-macro-call (ast env) - (when (types:mal-list-p ast) - (let* ((func-symbol (first (types:mal-data-value ast))) - (func (when (types:mal-symbol-p func-symbol) + (when (mal-list-p ast) + (let* ((func-symbol (first (mal-data-value ast))) + (func (when (mal-symbol-p func-symbol) (env:find-env env func-symbol)))) (and func - (types:mal-fn-p func) - (cdr (assoc 'is-macro (types:mal-data-attrs func))))))) + (mal-fn-p func) + (cdr (assoc 'is-macro (mal-data-attrs func))))))) (defun mal-macroexpand (ast env) (loop while (is-macro-call ast env) - do (let* ((forms (types:mal-data-value ast)) + do (let* ((forms (mal-data-value ast)) (func (env:get-env env (first forms)))) - (setf ast (apply (types:mal-data-value func) + (setf ast (apply (mal-data-value func) (cdr forms))))) ast) @@ -119,44 +117,44 @@ (loop do (setf ast (mal-macroexpand ast env)) do (cond - ((null ast) (return types:mal-nil)) - ((not (types:mal-list-p ast)) (return (eval-ast ast env))) - ((zerop (length (types:mal-data-value ast))) (return ast)) - (t (let ((forms (types:mal-data-value ast))) + ((null ast) (return mal-nil)) + ((not (mal-list-p ast)) (return (eval-ast ast env))) + ((zerop (length (mal-data-value ast))) (return ast)) + (t (let ((forms (mal-data-value ast))) (cond - ((types:mal-data-value= mal-quote (first forms)) + ((mal-data-value= mal-quote (first forms)) (return (second forms))) - ((types:mal-data-value= mal-quasiquote (first forms)) + ((mal-data-value= mal-quasiquote (first forms)) (setf ast (quasiquote (second forms)))) - ((types:mal-data-value= mal-macroexpand (first forms)) + ((mal-data-value= mal-macroexpand (first forms)) (return (mal-macroexpand (second forms) env))) - ((types:mal-data-value= mal-def! (first forms)) + ((mal-data-value= mal-def! (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - ((types:mal-data-value= mal-defmacro! (first forms)) + ((mal-data-value= mal-defmacro! (first forms)) (let ((value (mal-eval (third forms) env))) - (return (if (types:mal-fn-p value) + (return (if (mal-fn-p value) (env:set-env env (second forms) (progn - (setf (cdr (assoc 'is-macro (types:mal-data-attrs value))) t) + (setf (cdr (assoc 'is-macro (mal-data-attrs value))) t) value)) (error 'invalid-function :form value :context "macro"))))) - ((types:mal-data-value= mal-let* (first forms)) + ((mal-data-value= mal-let* (first forms)) (let ((new-env (env:create-mal-env :parent env)) - (bindings (utils:listify (types:mal-data-value (second forms))))) + (bindings (utils:listify (mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - types:mal-nil) + mal-nil) new-env))) (loop for (symbol value) on bindings @@ -165,45 +163,43 @@ (setf ast (third forms) env new-env))) - ((types:mal-data-value= mal-do (first forms)) + ((mal-data-value= mal-do (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) - ((types:mal-data-value= mal-if (first forms)) + ((mal-data-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (types:mal-data-value= predicate types:mal-nil) - (types:mal-data-value= predicate types:mal-false)) + (setf ast (if (or (mal-data-value= predicate mal-nil) + (mal-data-value= predicate mal-false)) (fourth forms) (third forms))))) - ((types:mal-data-value= mal-fn* (first forms)) + ((mal-data-value= mal-fn* (first forms)) (return (let ((arglist (second forms)) (body (third forms))) - (types:make-mal-fn (lambda (&rest args) - (mal-eval body (env:create-mal-env :parent env - :binds (map 'list - #'identity - (types:mal-data-value arglist)) - :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env) - (cons 'is-macro nil)))))) + (make-mal-fn (lambda (&rest args) + (mal-eval body (env:create-mal-env :parent env + :binds (listify (mal-data-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env) + (cons 'is-macro nil)))))) (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) ;; If first element is a mal function unwrap it - (cond ((types:mal-fn-p function) - (let* ((attrs (types:mal-data-attrs function))) + (cond ((mal-fn-p function) + (let* ((attrs (mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) env (env:create-mal-env :parent (cdr (assoc 'env attrs)) :binds (map 'list #'identity - (types:mal-data-value (cdr (assoc 'params attrs)))) + (mal-data-value (cdr (assoc 'params attrs)))) :exprs (cdr evaluated-list))))) - ((types:mal-builtin-fn-p function) - (return (apply (types:mal-data-value function) + ((mal-builtin-fn-p function) + (return (apply (mal-data-value function) (cdr evaluated-list)))) (t (error 'invalid-function :form function @@ -214,21 +210,16 @@ (defun rep (string) (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) - (types:mal-error (condition) - (format nil - "~a" - condition)) + (mal-print (mal-eval (mal-read string) *repl-env*)) + (mal-error (condition) + (format nil "~a" condition)) (error (condition) - (format nil - "Internal error: ~a" - condition)))) + (format nil "Internal error: ~a" condition)))) (env:set-env *repl-env* - (types:make-mal-symbol "eval") - (types:make-mal-builtin-fn (lambda (ast) - (mal-eval ast *repl-env*)))) + (make-mal-symbol "eval") + (make-mal-builtin-fn (lambda (ast) + (mal-eval ast *repl-env*)))) (rep "(def! not (fn* (a) (if a false true)))") (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") @@ -244,7 +235,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (cl-readline:readline :prompt prompt + (rl:readline :prompt prompt :add-history t :novelty-check (lambda (old new) (not (string= old new)))) @@ -284,7 +275,7 @@ argv (cdr (utils:raw-command-line-arguments))))) (env:set-env *repl-env* - (types:make-mal-symbol "*ARGV*") + (make-mal-symbol "*ARGV*") (make-mal-list (mapcar #'make-mal-string (cdr args)))) (if (null args) (repl) diff --git a/common-lisp/src/step9_try.lisp b/common-lisp/src/step9_try.lisp index ecccf8ebbc..bc3600150a 100644 --- a/common-lisp/src/step9_try.lisp +++ b/common-lisp/src/step9_try.lisp @@ -15,7 +15,7 @@ (in-package :mal) -(define-condition invalid-function (types:mal-runtime-exception) +(define-condition invalid-function (mal-runtime-exception) ((form :initarg :form :reader form) (context :initarg :context :reader context)) (:report (lambda (condition stream) @@ -30,9 +30,7 @@ (defvar *repl-env* (env:create-mal-env)) (dolist (binding core:ns) - (env:set-env *repl-env* - (car binding) - (cdr binding))) + (env:set-env *repl-env* (car binding) (cdr binding))) (defvar mal-def! (make-mal-symbol "def!")) (defvar mal-let* (make-mal-symbol "let*")) @@ -54,16 +52,16 @@ (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) - (types:mal-data-value sequence))) + (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (types:mal-data-value hash-map)) - (new-hash-table (types:make-mal-value-hash-table))) + (let ((hash-map-value (mal-data-value hash-map)) + (new-hash-table (make-mal-value-hash-table))) (genhash:hashmap (lambda (key value) (setf (genhash:hashref (mal-eval key env) new-hash-table) (mal-eval value env))) hash-map-value) - (types:make-mal-hash-map new-hash-table))) + (make-mal-hash-map new-hash-table))) (defun eval-ast (ast env) (switch-mal-type ast @@ -76,42 +74,42 @@ (defun is-pair (value) (and (or (mal-list-p value) (mal-vector-p value)) - (< 0 (length (types:mal-data-value value))))) + (< 0 (length (mal-data-value value))))) (defun quasiquote (ast) (if (not (is-pair ast)) - (types:make-mal-list (list mal-quote ast)) - (let ((forms (map 'list #'identity (types:mal-data-value ast)))) + (make-mal-list (list mal-quote ast)) + (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond - ((types:mal-data-value= mal-unquote (first forms)) + ((mal-data-value= mal-unquote (first forms)) (second forms)) ((and (is-pair (first forms)) - (types:mal-data-value= mal-splice-unquote - (first (types:mal-data-value (first forms))))) - (types:make-mal-list (list mal-concat - (second (types:mal-data-value (first forms))) - (quasiquote (make-mal-list (cdr forms)))))) + (mal-data-value= mal-splice-unquote + (first (mal-data-value (first forms))))) + (make-mal-list (list mal-concat + (second (mal-data-value (first forms))) + (quasiquote (make-mal-list (cdr forms)))))) - (t (types:make-mal-list (list mal-cons - (quasiquote (first forms)) - (quasiquote (make-mal-list (cdr forms)))))))))) + (t (make-mal-list (list mal-cons + (quasiquote (first forms)) + (quasiquote (make-mal-list (cdr forms)))))))))) (defun is-macro-call (ast env) - (when (types:mal-list-p ast) - (let* ((func-symbol (first (types:mal-data-value ast))) - (func (when (types:mal-symbol-p func-symbol) + (when (mal-list-p ast) + (let* ((func-symbol (first (mal-data-value ast))) + (func (when (mal-symbol-p func-symbol) (env:find-env env func-symbol)))) (and func - (types:mal-fn-p func) - (cdr (assoc 'is-macro (types:mal-data-attrs func))))))) + (mal-fn-p func) + (cdr (assoc 'is-macro (mal-data-attrs func))))))) (defun mal-macroexpand (ast env) (loop while (is-macro-call ast env) - do (let* ((forms (types:mal-data-value ast)) + do (let* ((forms (mal-data-value ast)) (func (env:get-env env (first forms)))) - (setf ast (apply (types:mal-data-value func) + (setf ast (apply (mal-data-value func) (cdr forms))))) ast) @@ -122,44 +120,44 @@ (loop do (setf ast (mal-macroexpand ast env)) do (cond - ((null ast) (return types:mal-nil)) - ((not (types:mal-list-p ast)) (return (eval-ast ast env))) - ((zerop (length (types:mal-data-value ast))) (return ast)) - (t (let ((forms (types:mal-data-value ast))) + ((null ast) (return mal-nil)) + ((not (mal-list-p ast)) (return (eval-ast ast env))) + ((zerop (length (mal-data-value ast))) (return ast)) + (t (let ((forms (mal-data-value ast))) (cond - ((types:mal-data-value= mal-quote (first forms)) + ((mal-data-value= mal-quote (first forms)) (return (second forms))) - ((types:mal-data-value= mal-quasiquote (first forms)) + ((mal-data-value= mal-quasiquote (first forms)) (setf ast (quasiquote (second forms)))) - ((types:mal-data-value= mal-macroexpand (first forms)) + ((mal-data-value= mal-macroexpand (first forms)) (return (mal-macroexpand (second forms) env))) - ((types:mal-data-value= mal-def! (first forms)) + ((mal-data-value= mal-def! (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - ((types:mal-data-value= mal-defmacro! (first forms)) + ((mal-data-value= mal-defmacro! (first forms)) (let ((value (mal-eval (third forms) env))) - (return (if (types:mal-fn-p value) + (return (if (mal-fn-p value) (env:set-env env (second forms) (progn - (setf (cdr (assoc 'is-macro (types:mal-data-attrs value))) t) + (setf (cdr (assoc 'is-macro (mal-data-attrs value))) t) value)) (error 'invalid-function :form value :context "macro"))))) - ((types:mal-data-value= mal-let* (first forms)) + ((mal-data-value= mal-let* (first forms)) (let ((new-env (env:create-mal-env :parent env)) - (bindings (utils:listify (types:mal-data-value (second forms))))) + (bindings (utils:listify (mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - types:mal-nil) + mal-nil) new-env))) (loop for (symbol value) on bindings @@ -168,62 +166,60 @@ (setf ast (third forms) env new-env))) - ((types:mal-data-value= mal-do (first forms)) + ((mal-data-value= mal-do (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) - ((types:mal-data-value= mal-if (first forms)) + ((mal-data-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (types:mal-data-value= predicate types:mal-nil) - (types:mal-data-value= predicate types:mal-false)) + (setf ast (if (or (mal-data-value= predicate mal-nil) + (mal-data-value= predicate mal-false)) (fourth forms) (third forms))))) - ((types:mal-data-value= mal-fn* (first forms)) + ((mal-data-value= mal-fn* (first forms)) (return (let ((arglist (second forms)) (body (third forms))) - (types:make-mal-fn (lambda (&rest args) - (mal-eval body (env:create-mal-env :parent env - :binds (map 'list - #'identity - (types:mal-data-value arglist)) - :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env) - (cons 'is-macro nil)))))) - - ((types:mal-data-value= mal-try* (first forms)) + (make-mal-fn (lambda (&rest args) + (mal-eval body (env:create-mal-env :parent env + :binds (listify (mal-data-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env) + (cons 'is-macro nil)))))) + + ((mal-data-value= mal-try* (first forms)) (handler-case (return (mal-eval (second forms) env)) - ((or types:mal-exception types:mal-error) (condition) + ((or mal-exception mal-error) (condition) (when (third forms) - (let ((catch-forms (types:mal-data-value (third forms)))) - (when (types:mal-data-value= mal-catch* - (first catch-forms)) + (let ((catch-forms (mal-data-value (third forms)))) + (when (mal-data-value= mal-catch* + (first catch-forms)) (return (mal-eval (third catch-forms) (env:create-mal-env :parent env :binds (list (second catch-forms)) - :exprs (list (if (or (typep condition 'types:mal-runtime-exception) - (typep condition 'types:mal-error)) - (types:make-mal-string (format nil "~a" condition)) - (types::mal-exception-data condition))))))))) + :exprs (list (if (or (typep condition 'mal-runtime-exception) + (typep condition 'mal-error)) + (make-mal-string (format nil "~a" condition)) + (mal-exception-data condition))))))))) (error condition)))) (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) ;; If first element is a mal function unwrap it - (cond ((types:mal-fn-p function) - (let* ((attrs (types:mal-data-attrs function))) + (cond ((mal-fn-p function) + (let* ((attrs (mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) env (env:create-mal-env :parent (cdr (assoc 'env attrs)) :binds (map 'list #'identity - (types:mal-data-value (cdr (assoc 'params attrs)))) + (mal-data-value (cdr (assoc 'params attrs)))) :exprs (cdr evaluated-list))))) - ((types:mal-builtin-fn-p function) - (return (apply (types:mal-data-value function) + ((mal-builtin-fn-p function) + (return (apply (mal-data-value function) (cdr evaluated-list)))) (t (error 'invalid-function :form function @@ -234,29 +230,20 @@ (defun rep (string) (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) - (types:mal-error (condition) - (format nil - "Error: ~a" - condition)) - (types:mal-runtime-exception (condition) - (format nil - "Exception: ~a" - condition)) - (types:mal-user-exception (condition) - (format nil - "Exception: ~a" - (pr-str (types::mal-exception-data condition)))) + (mal-print (mal-eval (mal-read string) *repl-env*)) + (mal-error (condition) + (format nil "Error: ~a" condition)) + (mal-runtime-exception (condition) + (format nil "Exception: ~a" condition)) + (mal-user-exception (condition) + (format nil "Exception: ~a" (pr-str (mal-exception-data condition)))) (error (condition) - (format nil - "Internal error: ~a" - condition)))) + (format nil "Internal error: ~a" condition)))) (env:set-env *repl-env* - (types:make-mal-symbol "eval") - (types:make-mal-builtin-fn (lambda (ast) - (mal-eval ast *repl-env*)))) + (make-mal-symbol "eval") + (make-mal-builtin-fn (lambda (ast) + (mal-eval ast *repl-env*)))) (rep "(def! not (fn* (a) (if a false true)))") (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") @@ -272,7 +259,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (cl-readline:readline :prompt prompt + (rl:readline :prompt prompt :add-history t :novelty-check (lambda (old new) (not (string= old new)))) @@ -312,7 +299,7 @@ argv (cdr (utils:raw-command-line-arguments))))) (env:set-env *repl-env* - (types:make-mal-symbol "*ARGV*") + (make-mal-symbol "*ARGV*") (make-mal-list (mapcar #'make-mal-string (cdr args)))) (if (null args) (repl) diff --git a/common-lisp/src/stepA_mal.lisp b/common-lisp/src/stepA_mal.lisp index 628e675a31..82c6c7fca4 100644 --- a/common-lisp/src/stepA_mal.lisp +++ b/common-lisp/src/stepA_mal.lisp @@ -15,7 +15,7 @@ (in-package :mal) -(define-condition invalid-function (types:mal-runtime-exception) +(define-condition invalid-function (mal-runtime-exception) ((form :initarg :form :reader form) (context :initarg :context :reader context)) (:report (lambda (condition stream) @@ -26,13 +26,10 @@ "applying" "defining macro"))))) - (defvar *repl-env* (env:create-mal-env)) (dolist (binding core:ns) - (env:set-env *repl-env* - (car binding) - (cdr binding))) + (env:set-env *repl-env* (car binding) (cdr binding))) (defvar mal-def! (make-mal-symbol "def!")) (defvar mal-let* (make-mal-symbol "let*")) @@ -54,16 +51,16 @@ (defun eval-sequence (sequence env) (map 'list (lambda (ast) (mal-eval ast env)) - (types:mal-data-value sequence))) + (mal-data-value sequence))) (defun eval-hash-map (hash-map env) - (let ((hash-map-value (types:mal-data-value hash-map)) - (new-hash-table (types:make-mal-value-hash-table))) + (let ((hash-map-value (mal-data-value hash-map)) + (new-hash-table (make-mal-value-hash-table))) (genhash:hashmap (lambda (key value) (setf (genhash:hashref (mal-eval key env) new-hash-table) (mal-eval value env))) hash-map-value) - (types:make-mal-hash-map new-hash-table))) + (make-mal-hash-map new-hash-table))) (defun eval-ast (ast env) (switch-mal-type ast @@ -76,42 +73,42 @@ (defun is-pair (value) (and (or (mal-list-p value) (mal-vector-p value)) - (< 0 (length (types:mal-data-value value))))) + (< 0 (length (mal-data-value value))))) (defun quasiquote (ast) (if (not (is-pair ast)) - (types:make-mal-list (list mal-quote ast)) - (let ((forms (map 'list #'identity (types:mal-data-value ast)))) + (make-mal-list (list mal-quote ast)) + (let ((forms (map 'list #'identity (mal-data-value ast)))) (cond - ((types:mal-data-value= mal-unquote (first forms)) + ((mal-data-value= mal-unquote (first forms)) (second forms)) ((and (is-pair (first forms)) - (types:mal-data-value= mal-splice-unquote - (first (types:mal-data-value (first forms))))) - (types:make-mal-list (list mal-concat - (second (types:mal-data-value (first forms))) - (quasiquote (make-mal-list (cdr forms)))))) + (mal-data-value= mal-splice-unquote + (first (mal-data-value (first forms))))) + (make-mal-list (list mal-concat + (second (mal-data-value (first forms))) + (quasiquote (make-mal-list (cdr forms)))))) - (t (types:make-mal-list (list mal-cons - (quasiquote (first forms)) - (quasiquote (make-mal-list (cdr forms)))))))))) + (t (make-mal-list (list mal-cons + (quasiquote (first forms)) + (quasiquote (make-mal-list (cdr forms)))))))))) (defun is-macro-call (ast env) - (when (types:mal-list-p ast) - (let* ((func-symbol (first (types:mal-data-value ast))) - (func (when (types:mal-symbol-p func-symbol) + (when (mal-list-p ast) + (let* ((func-symbol (first (mal-data-value ast))) + (func (when (mal-symbol-p func-symbol) (env:find-env env func-symbol)))) (and func - (types:mal-fn-p func) - (cdr (assoc 'is-macro (types:mal-data-attrs func))))))) + (mal-fn-p func) + (cdr (assoc 'is-macro (mal-data-attrs func))))))) (defun mal-macroexpand (ast env) (loop while (is-macro-call ast env) - do (let* ((forms (types:mal-data-value ast)) + do (let* ((forms (mal-data-value ast)) (func (env:get-env env (first forms)))) - (setf ast (apply (types:mal-data-value func) + (setf ast (apply (mal-data-value func) (cdr forms))))) ast) @@ -122,44 +119,44 @@ (loop do (setf ast (mal-macroexpand ast env)) do (cond - ((null ast) (return types:mal-nil)) - ((not (types:mal-list-p ast)) (return (eval-ast ast env))) - ((zerop (length (types:mal-data-value ast))) (return ast)) - (t (let ((forms (types:mal-data-value ast))) + ((null ast) (return mal-nil)) + ((not (mal-list-p ast)) (return (eval-ast ast env))) + ((zerop (length (mal-data-value ast))) (return ast)) + (t (let ((forms (mal-data-value ast))) (cond - ((types:mal-data-value= mal-quote (first forms)) + ((mal-data-value= mal-quote (first forms)) (return (second forms))) - ((types:mal-data-value= mal-quasiquote (first forms)) + ((mal-data-value= mal-quasiquote (first forms)) (setf ast (quasiquote (second forms)))) - ((types:mal-data-value= mal-macroexpand (first forms)) + ((mal-data-value= mal-macroexpand (first forms)) (return (mal-macroexpand (second forms) env))) - ((types:mal-data-value= mal-def! (first forms)) + ((mal-data-value= mal-def! (first forms)) (return (env:set-env env (second forms) (mal-eval (third forms) env)))) - ((types:mal-data-value= mal-defmacro! (first forms)) + ((mal-data-value= mal-defmacro! (first forms)) (let ((value (mal-eval (third forms) env))) - (return (if (types:mal-fn-p value) + (return (if (mal-fn-p value) (env:set-env env (second forms) (progn - (setf (cdr (assoc 'is-macro (types:mal-data-attrs value))) t) + (setf (cdr (assoc 'is-macro (mal-data-attrs value))) t) value)) (error 'invalid-function :form value :context "macro"))))) - ((types:mal-data-value= mal-let* (first forms)) + ((mal-data-value= mal-let* (first forms)) (let ((new-env (env:create-mal-env :parent env)) - (bindings (utils:listify (types:mal-data-value (second forms))))) + (bindings (utils:listify (mal-data-value (second forms))))) (mapcar (lambda (binding) (env:set-env new-env (car binding) (mal-eval (or (cdr binding) - types:mal-nil) + mal-nil) new-env))) (loop for (symbol value) on bindings @@ -168,62 +165,60 @@ (setf ast (third forms) env new-env))) - ((types:mal-data-value= mal-do (first forms)) + ((mal-data-value= mal-do (first forms)) (mapc (lambda (form) (mal-eval form env)) (butlast (cdr forms))) (setf ast (car (last forms)))) - ((types:mal-data-value= mal-if (first forms)) + ((mal-data-value= mal-if (first forms)) (let ((predicate (mal-eval (second forms) env))) - (setf ast (if (or (types:mal-data-value= predicate types:mal-nil) - (types:mal-data-value= predicate types:mal-false)) + (setf ast (if (or (mal-data-value= predicate mal-nil) + (mal-data-value= predicate mal-false)) (fourth forms) (third forms))))) - ((types:mal-data-value= mal-fn* (first forms)) + ((mal-data-value= mal-fn* (first forms)) (return (let ((arglist (second forms)) (body (third forms))) - (types:make-mal-fn (lambda (&rest args) - (mal-eval body (env:create-mal-env :parent env - :binds (map 'list - #'identity - (types:mal-data-value arglist)) - :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env) - (cons 'is-macro nil)))))) - - ((types:mal-data-value= mal-try* (first forms)) + (make-mal-fn (lambda (&rest args) + (mal-eval body (env:create-mal-env :parent env + :binds (listify (mal-data-value arglist)) + :exprs args))) + :attrs (list (cons 'params arglist) + (cons 'ast body) + (cons 'env env) + (cons 'is-macro nil)))))) + + ((mal-data-value= mal-try* (first forms)) (handler-case (return (mal-eval (second forms) env)) - ((or types:mal-exception types:mal-error) (condition) + ((or mal-exception mal-error) (condition) (when (third forms) - (let ((catch-forms (types:mal-data-value (third forms)))) - (when (types:mal-data-value= mal-catch* - (first catch-forms)) + (let ((catch-forms (mal-data-value (third forms)))) + (when (mal-data-value= mal-catch* + (first catch-forms)) (return (mal-eval (third catch-forms) (env:create-mal-env :parent env :binds (list (second catch-forms)) - :exprs (list (if (or (typep condition 'types:mal-runtime-exception) - (typep condition 'types:mal-error)) - (types:make-mal-string (format nil "~a" condition)) - (types::mal-exception-data condition))))))))) + :exprs (list (if (or (typep condition 'mal-runtime-exception) + (typep condition 'mal-error)) + (make-mal-string (format nil "~a" condition)) + (mal-exception-data condition))))))))) (error condition)))) (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) ;; If first element is a mal function unwrap it - (cond ((types:mal-fn-p function) - (let* ((attrs (types:mal-data-attrs function))) + (cond ((mal-fn-p function) + (let* ((attrs (mal-data-attrs function))) (setf ast (cdr (assoc 'ast attrs)) env (env:create-mal-env :parent (cdr (assoc 'env attrs)) :binds (map 'list #'identity - (types:mal-data-value (cdr (assoc 'params attrs)))) + (mal-data-value (cdr (assoc 'params attrs)))) :exprs (cdr evaluated-list))))) - ((types:mal-builtin-fn-p function) - (return (apply (types:mal-data-value function) + ((mal-builtin-fn-p function) + (return (apply (mal-data-value function) (cdr evaluated-list)))) (t (error 'invalid-function :form function @@ -234,36 +229,27 @@ (defun rep (string) (handler-case - (mal-print (mal-eval (mal-read string) - *repl-env*)) - (types:mal-error (condition) - (format nil - "Error: ~a" - condition)) - (types:mal-runtime-exception (condition) - (format nil - "Exception: ~a" - condition)) - (types:mal-user-exception (condition) - (format nil - "Exception: ~a" - (pr-str (types::mal-exception-data condition)))) + (mal-print (mal-eval (mal-read string) *repl-env*)) + (mal-error (condition) + (format nil "Error: ~a" condition)) + (mal-runtime-exception (condition) + (format nil "Exception: ~a" condition)) + (mal-user-exception (condition) + (format nil "Exception: ~a" (pr-str (mal-exception-data condition)))) (error (condition) - (format nil - "Internal error: ~a" - condition)))) + (format nil "Internal error: ~a" condition)))) (env:set-env *repl-env* - (types:make-mal-symbol "eval") - (types:make-mal-builtin-fn (lambda (ast) - (mal-eval ast *repl-env*)))) + (make-mal-symbol "eval") + (make-mal-builtin-fn (lambda (ast) + (mal-eval ast *repl-env*)))) (env:set-env *repl-env* - (types:make-mal-symbol "*cl-implementation*") + (make-mal-symbol "*cl-implementation*") (make-mal-string (lisp-implementation-type))) (env:set-env *repl-env* - (types:make-mal-symbol "*cl-version*") + (make-mal-symbol "*cl-version*") (make-mal-string (lisp-implementation-version))) (rep "(def! not (fn* (a) (if a false true)))") @@ -283,7 +269,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (cl-readline:readline :prompt prompt + (rl:readline :prompt prompt :add-history t :novelty-check (lambda (old new) (not (string= old new)))) @@ -324,7 +310,7 @@ argv (cdr (utils:raw-command-line-arguments))))) (env:set-env *repl-env* - (types:make-mal-symbol "*ARGV*") + (make-mal-symbol "*ARGV*") (make-mal-list (mapcar #'make-mal-string (cdr args)))) (if (null args) (repl) diff --git a/common-lisp/src/types.lisp b/common-lisp/src/types.lisp index a6c3830230..57e5e6bca6 100644 --- a/common-lisp/src/types.lisp +++ b/common-lisp/src/types.lisp @@ -68,6 +68,7 @@ :make-mal-value-hash-table ;; Error types :mal-exception + :mal-exception-data ;; Exceptions raised by the runtime :mal-runtime-exception ;; Exception raised by user code @@ -77,14 +78,11 @@ (in-package :types) -(define-condition mal-error (error) - nil) +(define-condition mal-error (error) nil) -(define-condition mal-exception (error) - nil) +(define-condition mal-exception (error) nil) -(define-condition mal-runtime-exception (mal-exception) - nil) +(define-condition mal-runtime-exception (mal-exception) nil) (define-condition mal-user-exception (mal-exception) ((data :accessor mal-exception-data :initarg :data))) @@ -136,35 +134,33 @@ `(let ((type (mal-data-type ,ast))) (cond ,@(mapcar (lambda (form) - (list (if (or (equal (car form) t) - (equal (car form) 'any)) - t + (list (or (equal (car form) t) + (equal (car form) 'any) (list 'equal (list 'quote (car form)) 'type)) (cadr form))) forms)))) (defun mal-sequence= (value1 value2) - (let ((sequence1 (utils:listify (mal-data-value value1))) - (sequence2 (utils:listify (mal-data-value value2)))) + (let ((sequence1 (listify (mal-data-value value1))) + (sequence2 (listify (mal-data-value value2)))) + (when (= (length sequence1) (length sequence2)) - (every #'identity - (loop - for x in sequence1 - for y in sequence2 - collect (mal-data-value= x y)))))) + (every #'identity (loop for x in sequence1 + for y in sequence2 + collect (mal-data-value= x y)))))) (defun mal-hash-map= (value1 value2) (let ((map1 (mal-data-value value1)) (map2 (mal-data-value value2)) (identical t)) - (when (= (genhash:generic-hash-table-count map1) - (genhash:generic-hash-table-count map2)) - (genhash:hashmap (lambda (key value) - (declare (ignorable value)) - (setf identical - (and identical (mal-data-value= (genhash:hashref key map1) - (genhash:hashref key map2))))) - map1) + (when (= (generic-hash-table-count map1) + (generic-hash-table-count map2)) + (hashmap (lambda (key value) + (declare (ignorable value)) + (setf identical + (and identical (mal-data-value= (hashref key map1) + (hashref key map2))))) + map1) identical))) (defun mal-data-value= (value1 value2) @@ -191,7 +187,7 @@ ;; instead (let ((hash-function #+(or ecl abcl) #'mal-sxhash #-(or ecl abcl) #'sxhash)) - (genhash:register-test-designator 'mal-data-value-hash - hash-function - #'mal-data-value=))) - (genhash:make-generic-hash-table :test 'mal-data-value-hash)) + (register-test-designator 'mal-data-value-hash + hash-function + #'mal-data-value=))) + (make-generic-hash-table :test 'mal-data-value-hash)) From 3da6a0cb09a6fb85d323a0d16e04d4da3990235e Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 22 Aug 2017 16:53:03 +0530 Subject: [PATCH 0411/2308] Common Lisp: Fix printing of user defined functions --- common-lisp/src/printer.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-lisp/src/printer.lisp b/common-lisp/src/printer.lisp index e6c8de6651..2187bca7a0 100644 --- a/common-lisp/src/printer.lisp +++ b/common-lisp/src/printer.lisp @@ -49,5 +49,5 @@ (types:vector (pr-mal-sequence "[" ast "]" print-readably)) (types:hash-map (pr-mal-hash-map ast print-readably)) (types:atom (format nil "(atom ~a)" (pr-str (mal-data-value ast)))) - (types:builtin-fn "#") + (types:fn "#") (types:builtin-fn "#")))) From 774d5cf8b879bb52f9c7661a32c8055d9c011853 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Tue, 22 Aug 2017 18:28:50 +0530 Subject: [PATCH 0412/2308] Common Lisp: Add basic completion for toplevel symbols to the REPL --- common-lisp/src/env.lisp | 3 ++- common-lisp/src/step0_repl.lisp | 7 +++---- common-lisp/src/step1_read_print.lisp | 7 +++---- common-lisp/src/step2_eval.lisp | 27 +++++++++++++++++++++----- common-lisp/src/step3_env.lisp | 27 +++++++++++++++++++++----- common-lisp/src/step4_if_fn_do.lisp | 27 +++++++++++++++++++++----- common-lisp/src/step5_tco.lisp | 27 +++++++++++++++++++++----- common-lisp/src/step6_file.lisp | 27 +++++++++++++++++++++----- common-lisp/src/step7_quote.lisp | 27 +++++++++++++++++++++----- common-lisp/src/step8_macros.lisp | 27 +++++++++++++++++++++----- common-lisp/src/step9_try.lisp | 28 ++++++++++++++++++++++----- common-lisp/src/stepA_mal.lisp | 27 +++++++++++++++++++++----- common-lisp/src/utils.lisp | 15 +++++++++++++- 13 files changed, 221 insertions(+), 55 deletions(-) diff --git a/common-lisp/src/env.lisp b/common-lisp/src/env.lisp index 0a2ff05cae..771b72cf53 100644 --- a/common-lisp/src/env.lisp +++ b/common-lisp/src/env.lisp @@ -5,7 +5,8 @@ :create-mal-env :get-env :find-env - :set-env)) + :set-env + :mal-env-bindings)) (in-package :env) diff --git a/common-lisp/src/step0_repl.lisp b/common-lisp/src/step0_repl.lisp index 79d180056d..17ba1619c7 100644 --- a/common-lisp/src/step0_repl.lisp +++ b/common-lisp/src/step0_repl.lisp @@ -2,6 +2,8 @@ (:use :common-lisp) (:import-from :uiop :getenv) + (:import-from :cl-readline + :readline) (:export :main)) (in-package :mal) @@ -27,10 +29,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (rl:readline :prompt prompt - :add-history t - :novelty-check (lambda (old new) - (not (string= old new)))) + (rl:readline :prompt prompt :add-history t :novelty-check #'string/=) (raw-input prompt))) (defun mal-writeline (string) diff --git a/common-lisp/src/step1_read_print.lisp b/common-lisp/src/step1_read_print.lisp index 1c0bd248a2..871ffbdb02 100644 --- a/common-lisp/src/step1_read_print.lisp +++ b/common-lisp/src/step1_read_print.lisp @@ -4,6 +4,8 @@ :printer) (:import-from :utils :getenv) + (:import-from :cl-readline + :readline) (:export :main)) (in-package :mal) @@ -33,10 +35,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (rl:readline :prompt prompt - :add-history t - :novelty-check (lambda (old new) - (not (string= old new)))) + (rl:readline :prompt prompt :add-history t :novelty-check #'string/=) (raw-input prompt))) (defun mal-writeline (string) diff --git a/common-lisp/src/step2_eval.lisp b/common-lisp/src/step2_eval.lisp index ecfaedb4b4..13723d5823 100644 --- a/common-lisp/src/step2_eval.lisp +++ b/common-lisp/src/step2_eval.lisp @@ -4,11 +4,15 @@ :env :reader :printer) + (:import-from :cl-readline + :readline + :register-function) (:import-from :genhash :hashref :hashmap) (:import-from :utils - :getenv) + :getenv + :common-prefix) (:export :main)) (in-package :mal) @@ -87,6 +91,18 @@ (defvar *use-readline-p* nil) +(defun complete-toplevel-symbols (input &rest ignored) + (declare (ignorable ignored)) + + (let (candidates) + (loop for key being the hash-keys of *repl-env* + when (let ((pos (search input key))) (and pos (zerop pos))) + do (push key candidates)) + + (if (= 1 (length candidates)) + (cons (car candidates) candidates) + (cons (apply #'utils:common-prefix candidates) candidates)))) + (defun raw-input (prompt) (format *standard-output* prompt) (force-output *standard-output*) @@ -94,10 +110,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (rl:readline :prompt prompt - :add-history t - :novelty-check (lambda (old new) - (not (string= old new)))) + (rl:readline :prompt prompt :add-history t :novelty-check #'string/=) (raw-input prompt))) (defun mal-writeline (string) @@ -122,6 +135,10 @@ *standard-output* (ext:make-stream :output :buffered t) *error-output* (ext:make-stream :error :buffered t)) + ;; CCL fails with a error while registering completion function + ;; See also https://github.com/mrkkrp/cl-readline/issues/5 + #-ccl (rl:register-function :complete #'complete-toplevel-symbols) + (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) diff --git a/common-lisp/src/step3_env.lisp b/common-lisp/src/step3_env.lisp index ff34662223..2aa091312b 100644 --- a/common-lisp/src/step3_env.lisp +++ b/common-lisp/src/step3_env.lisp @@ -5,11 +5,15 @@ :reader :printer :genhash) + (:import-from :cl-readline + :readline + :register-function) (:import-from :genhash :hashref :hashmap) (:import-from :utils - :getenv) + :getenv + :common-prefix) (:export :main)) (in-package :mal) @@ -114,6 +118,18 @@ (defvar *use-readline-p* nil) +(defun complete-toplevel-symbols (input &rest ignored) + (declare (ignorable ignored)) + + (let (candidates) + (loop for key being the hash-keys of (env:mal-env-bindings *repl-env*) + when (let ((pos (search input key))) (and pos (zerop pos))) + do (push key candidates)) + + (if (= 1 (length candidates)) + (cons (car candidates) candidates) + (cons (apply #'utils:common-prefix candidates) candidates)))) + (defun raw-input (prompt) (format *standard-output* prompt) (force-output *standard-output*) @@ -121,10 +137,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (rl:readline :prompt prompt - :add-history t - :novelty-check (lambda (old new) - (not (string= old new)))) + (rl:readline :prompt prompt :add-history t :novelty-check #'string/=) (raw-input prompt))) (defun mal-writeline (string) @@ -149,6 +162,10 @@ *standard-output* (ext:make-stream :output :buffered t) *error-output* (ext:make-stream :error :buffered t)) + ;; CCL fails with a error while registering completion function + ;; See also https://github.com/mrkkrp/cl-readline/issues/5 + #-ccl (rl:register-function :complete #'complete-toplevel-symbols) + (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) diff --git a/common-lisp/src/step4_if_fn_do.lisp b/common-lisp/src/step4_if_fn_do.lisp index 99fca65be4..0d865a8326 100644 --- a/common-lisp/src/step4_if_fn_do.lisp +++ b/common-lisp/src/step4_if_fn_do.lisp @@ -5,12 +5,16 @@ :reader :printer :core) + (:import-from :cl-readline + :readline + :register-function) (:import-from :genhash :hashref :hashmap) (:import-from :utils :listify - :getenv) + :getenv + :common-prefix) (:export :main)) (in-package :mal) @@ -118,6 +122,18 @@ (defvar *use-readline-p* nil) +(defun complete-toplevel-symbols (input &rest ignored) + (declare (ignorable ignored)) + + (let (candidates) + (loop for key being the hash-keys of (env:mal-env-bindings *repl-env*) + when (let ((pos (search input key))) (and pos (zerop pos))) + do (push key candidates)) + + (if (= 1 (length candidates)) + (cons (car candidates) candidates) + (cons (apply #'utils:common-prefix candidates) candidates)))) + (defun raw-input (prompt) (format *standard-output* prompt) (force-output *standard-output*) @@ -125,10 +141,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (rl:readline :prompt prompt - :add-history t - :novelty-check (lambda (old new) - (not (string= old new)))) + (rl:readline :prompt prompt :add-history t :novelty-check #'string/=) (raw-input prompt))) (defun mal-writeline (string) @@ -153,6 +166,10 @@ *standard-output* (ext:make-stream :output :buffered t) *error-output* (ext:make-stream :error :buffered t)) + ;; CCL fails with a error while registering completion function + ;; See also https://github.com/mrkkrp/cl-readline/issues/5 + #-ccl (rl:register-function :complete #'complete-toplevel-symbols) + (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) diff --git a/common-lisp/src/step5_tco.lisp b/common-lisp/src/step5_tco.lisp index 18150d6df2..b0d9ece2d5 100644 --- a/common-lisp/src/step5_tco.lisp +++ b/common-lisp/src/step5_tco.lisp @@ -5,12 +5,16 @@ :reader :printer :core) + (:import-from :cl-readline + :readline + :register-function) (:import-from :genhash :hashref :hashmap) (:import-from :utils :listify - :getenv) + :getenv + :common-prefix) (:export :main)) (in-package :mal) @@ -129,6 +133,18 @@ (defvar *use-readline-p* nil) +(defun complete-toplevel-symbols (input &rest ignored) + (declare (ignorable ignored)) + + (let (candidates) + (loop for key being the hash-keys of (env:mal-env-bindings *repl-env*) + when (let ((pos (search input key))) (and pos (zerop pos))) + do (push key candidates)) + + (if (= 1 (length candidates)) + (cons (car candidates) candidates) + (cons (apply #'utils:common-prefix candidates) candidates)))) + (defun raw-input (prompt) (format *standard-output* prompt) (force-output *standard-output*) @@ -136,10 +152,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (rl:readline :prompt prompt - :add-history t - :novelty-check (lambda (old new) - (not (string= old new)))) + (rl:readline :prompt prompt :add-history t :novelty-check #'string/=) (raw-input prompt))) (defun mal-writeline (string) @@ -164,6 +177,10 @@ *standard-output* (ext:make-stream :output :buffered t) *error-output* (ext:make-stream :error :buffered t)) + ;; CCL fails with a error while registering completion function + ;; See also https://github.com/mrkkrp/cl-readline/issues/5 + #-ccl (rl:register-function :complete #'complete-toplevel-symbols) + (loop do (let ((line (mal-readline "user> "))) (if line (mal-writeline (rep line)) (return))))) diff --git a/common-lisp/src/step6_file.lisp b/common-lisp/src/step6_file.lisp index 152a9af6f8..361305ccdd 100644 --- a/common-lisp/src/step6_file.lisp +++ b/common-lisp/src/step6_file.lisp @@ -5,12 +5,16 @@ :reader :printer :core) + (:import-from :cl-readline + :readline + :register-function) (:import-from :genhash :hashref :hashmap) (:import-from :utils :listify - :getenv) + :getenv + :common-prefix) (:export :main)) (in-package :mal) @@ -135,6 +139,18 @@ (defvar *use-readline-p* nil) +(defun complete-toplevel-symbols (input &rest ignored) + (declare (ignorable ignored)) + + (let (candidates) + (loop for key being the hash-keys of (env:mal-env-bindings *repl-env*) + when (let ((pos (search input key))) (and pos (zerop pos))) + do (push key candidates)) + + (if (= 1 (length candidates)) + (cons (car candidates) candidates) + (cons (apply #'utils:common-prefix candidates) candidates)))) + (defun raw-input (prompt) (format *standard-output* prompt) (force-output *standard-output*) @@ -142,10 +158,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (rl:readline :prompt prompt - :add-history t - :novelty-check (lambda (old new) - (not (string= old new)))) + (rl:readline :prompt prompt :add-history t :novelty-check #'string/=) (raw-input prompt))) (defun mal-writeline (string) @@ -178,6 +191,10 @@ *standard-output* (ext:make-stream :output :buffered t) *error-output* (ext:make-stream :error :buffered t)) + ;; CCL fails with a error while registering completion function + ;; See also https://github.com/mrkkrp/cl-readline/issues/5 + #-ccl (rl:register-function :complete #'complete-toplevel-symbols) + (let ((args (if argv-provided-p argv (cdr (utils:raw-command-line-arguments))))) diff --git a/common-lisp/src/step7_quote.lisp b/common-lisp/src/step7_quote.lisp index 956b0bcf01..ea7d53d28e 100644 --- a/common-lisp/src/step7_quote.lisp +++ b/common-lisp/src/step7_quote.lisp @@ -5,12 +5,16 @@ :reader :printer :core) + (:import-from :cl-readline + :readline + :register-function) (:import-from :genhash :hashref :hashmap) (:import-from :utils :listify - :getenv) + :getenv + :common-prefix) (:export :main)) (in-package :mal) @@ -171,6 +175,18 @@ (defvar *use-readline-p* nil) +(defun complete-toplevel-symbols (input &rest ignored) + (declare (ignorable ignored)) + + (let (candidates) + (loop for key being the hash-keys of (env:mal-env-bindings *repl-env*) + when (let ((pos (search input key))) (and pos (zerop pos))) + do (push key candidates)) + + (if (= 1 (length candidates)) + (cons (car candidates) candidates) + (cons (apply #'utils:common-prefix candidates) candidates)))) + (defun raw-input (prompt) (format *standard-output* prompt) (force-output *standard-output*) @@ -178,10 +194,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (rl:readline :prompt prompt - :add-history t - :novelty-check (lambda (old new) - (not (string= old new)))) + (rl:readline :prompt prompt :add-history t :novelty-check #'string/=) (raw-input prompt))) (defun mal-writeline (string) @@ -214,6 +227,10 @@ *standard-output* (ext:make-stream :output :buffered t) *error-output* (ext:make-stream :error :buffered t)) + ;; CCL fails with a error while registering completion function + ;; See also https://github.com/mrkkrp/cl-readline/issues/5 + #-ccl (rl:register-function :complete #'complete-toplevel-symbols) + (let ((args (if argv-provided-p argv (cdr (utils:raw-command-line-arguments))))) diff --git a/common-lisp/src/step8_macros.lisp b/common-lisp/src/step8_macros.lisp index 0c2c214e41..cbf47eb78b 100644 --- a/common-lisp/src/step8_macros.lisp +++ b/common-lisp/src/step8_macros.lisp @@ -5,12 +5,16 @@ :reader :printer :core) + (:import-from :cl-readline + :readline + :register-function) (:import-from :genhash :hashref :hashmap) (:import-from :utils :listify - :getenv) + :getenv + :common-prefix) (:export :main)) (in-package :mal) @@ -228,6 +232,18 @@ (defvar *use-readline-p* nil) +(defun complete-toplevel-symbols (input &rest ignored) + (declare (ignorable ignored)) + + (let (candidates) + (loop for key being the hash-keys of (env:mal-env-bindings *repl-env*) + when (let ((pos (search input key))) (and pos (zerop pos))) + do (push key candidates)) + + (if (= 1 (length candidates)) + (cons (car candidates) candidates) + (cons (apply #'utils:common-prefix candidates) candidates)))) + (defun raw-input (prompt) (format *standard-output* prompt) (force-output *standard-output*) @@ -235,10 +251,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (rl:readline :prompt prompt - :add-history t - :novelty-check (lambda (old new) - (not (string= old new)))) + (rl:readline :prompt prompt :add-history t :novelty-check #'string/=) (raw-input prompt))) (defun mal-writeline (string) @@ -271,6 +284,10 @@ *standard-output* (ext:make-stream :output :buffered t) *error-output* (ext:make-stream :error :buffered t)) + ;; CCL fails with a error while registering completion function + ;; See also https://github.com/mrkkrp/cl-readline/issues/5 + #-ccl (rl:register-function :complete #'complete-toplevel-symbols) + (let ((args (if argv-provided-p argv (cdr (utils:raw-command-line-arguments))))) diff --git a/common-lisp/src/step9_try.lisp b/common-lisp/src/step9_try.lisp index bc3600150a..18e0060047 100644 --- a/common-lisp/src/step9_try.lisp +++ b/common-lisp/src/step9_try.lisp @@ -5,12 +5,16 @@ :reader :printer :core) + (:import-from :cl-readline + :readline + :register-function) (:import-from :genhash :hashref :hashmap) (:import-from :utils :listify - :getenv) + :getenv + :common-prefix) (:export :main)) (in-package :mal) @@ -252,6 +256,18 @@ (defvar *use-readline-p* nil) +(defun complete-toplevel-symbols (input &rest ignored) + (declare (ignorable ignored)) + + (let (candidates) + (loop for key being the hash-keys of (env:mal-env-bindings *repl-env*) + when (let ((pos (search input key))) (and pos (zerop pos))) + do (push key candidates)) + + (if (= 1 (length candidates)) + (cons (car candidates) candidates) + (cons (apply #'utils:common-prefix candidates) candidates)))) + (defun raw-input (prompt) (format *standard-output* prompt) (force-output *standard-output*) @@ -259,10 +275,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (rl:readline :prompt prompt - :add-history t - :novelty-check (lambda (old new) - (not (string= old new)))) + (rl:readline :prompt prompt :add-history t :novelty-check #'string/=) (raw-input prompt))) (defun mal-writeline (string) @@ -295,6 +308,11 @@ *standard-output* (ext:make-stream :output :buffered t) *error-output* (ext:make-stream :error :buffered t)) + ;; CCL fails with a error while registering completion function + ;; See also https://github.com/mrkkrp/cl-readline/issues/5 + #-ccl (rl:register-function :complete #'complete-toplevel-symbols) + + (let ((args (if argv-provided-p argv (cdr (utils:raw-command-line-arguments))))) diff --git a/common-lisp/src/stepA_mal.lisp b/common-lisp/src/stepA_mal.lisp index 82c6c7fca4..046dd9f43d 100644 --- a/common-lisp/src/stepA_mal.lisp +++ b/common-lisp/src/stepA_mal.lisp @@ -5,12 +5,16 @@ :reader :printer :core) + (:import-from :cl-readline + :readline + :register-function) (:import-from :genhash :hashref :hashmap) (:import-from :utils :listify - :getenv) + :getenv + :common-prefix) (:export :main)) (in-package :mal) @@ -262,6 +266,18 @@ (defvar *use-readline-p* nil) +(defun complete-toplevel-symbols (input &rest ignored) + (declare (ignorable ignored)) + + (let (candidates) + (loop for key being the hash-keys of (env:mal-env-bindings *repl-env*) + when (let ((pos (search input key))) (and pos (zerop pos))) + do (push key candidates)) + + (if (= 1 (length candidates)) + (cons (car candidates) candidates) + (cons (apply #'utils:common-prefix candidates) candidates)))) + (defun raw-input (prompt) (format *standard-output* prompt) (force-output *standard-output*) @@ -269,10 +285,7 @@ (defun mal-readline (prompt) (if *use-readline-p* - (rl:readline :prompt prompt - :add-history t - :novelty-check (lambda (old new) - (not (string= old new)))) + (rl:readline :prompt prompt :add-history t :novelty-check #'string/=) (raw-input prompt))) (defun mal-writeline (string) @@ -306,6 +319,10 @@ *standard-output* (ext:make-stream :output :buffered t) *error-output* (ext:make-stream :error :buffered t)) + ;; CCL fails with a error while registering completion function + ;; See also https://github.com/mrkkrp/cl-readline/issues/5 + #-ccl (rl:register-function :complete #'complete-toplevel-symbols) + (let ((args (if argv-provided-p argv (cdr (utils:raw-command-line-arguments))))) diff --git a/common-lisp/src/utils.lisp b/common-lisp/src/utils.lisp index 8845f2048b..95eadf5dab 100644 --- a/common-lisp/src/utils.lisp +++ b/common-lisp/src/utils.lisp @@ -5,7 +5,8 @@ :getenv :read-file-string :raw-command-line-arguments - :listify)) + :listify + :common-prefix)) (in-package :utils) @@ -27,3 +28,15 @@ is replaced with replacement." (defun listify (sequence) "Convert a sequence to a list" (map 'list #'identity sequence)) + +(defun common-prefix (&rest strings) + (if (not strings) + "" + (let* ((char-lists (mapcar (lambda (string) (coerce string 'list)) strings)) + (char-tuples (apply #'mapcar #'list char-lists)) + (count 0)) + (loop for char-tuple in char-tuples + while (every (lambda (char) (equal char (car char-tuple))) char-tuple) + do (incf count)) + + (subseq (car strings) 0 count)))) From 31a77c4c14701b9c1985490b0e3d863efa028619 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 23 Aug 2017 23:45:05 +0530 Subject: [PATCH 0413/2308] Common Lisp: Create standalone executables for supported Lisps --- common-lisp/Makefile | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/common-lisp/Makefile b/common-lisp/Makefile index d6d8437d0f..9c614c4d20 100644 --- a/common-lisp/Makefile +++ b/common-lisp/Makefile @@ -11,11 +11,14 @@ define steps stepA_mal) endef +LISP ?= sbcl +ABCL ?= abcl + +STANDALONE_EXE = sbcl clisp ccl ecl cmucl + ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) SOURCES_LISP := src/env.lisp src/core.lisp src/stepA_mal.lisp SOURCES := src/utils.lisp src/types.lisp src/reader.lisp src/printer.lisp $(SOURCES_LISP) -LISP ?= sbcl -ABCL ?= abcl # Record the Common Lisp implementation used for all steps built in this # invocation This is used in the targets to rebuild the step if the @@ -34,7 +37,18 @@ hist/%_impl: ; # image even if invoked from some directory different from where it # currently resides step% : src/step%.lisp src/utils.lisp src/types.lisp src/env.lisp src/printer.lisp src/reader.lisp src/core.lisp hist/%_impl -ifeq ($(LISP),abcl) + +ifeq ($(LISP),clisp) + @echo "==============================================================" + @echo "WARNING: This build might fail since GNU Clisp does not have bundled version of asdf (yet)" + @echo "Please do something like below to make it work" + @echo "(mkdir -p ~/common-lisp/ && cd ~/common-lisp && git clone -b release https://gitlab.common-lisp.net/asdf/asdf.git && cd asdf && make)" + @echo "==============================================================" +endif + +ifneq ($(filter $(LISP),$(STANDALONE_EXE)),) + cl-launch --wrap 'if [ -z "$$CL_LAUNCH_VERSION" ] ; then cd "$$(dirname $$CL_LAUNCH_FILE)" ; fi' --verbose --lisp $(LISP) --source-registry $(ROOT_DIR) --system $@ --dump '!' -o $@ --entry 'mal:main' +else ifeq ($(LISP),abcl) echo -n '#!/bin/sh\ncd `dirname $$0` ; $(ABCL) --noinform --noinit --nosystem --load run-abcl.lisp -- $@ $$@' > $@ chmod +x $@ else From df020af215b2163f3e1153947fb1bbf08e62383f Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 23 Aug 2017 23:49:52 +0530 Subject: [PATCH 0414/2308] Common Lisp: Simplify the clean task --- common-lisp/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-lisp/Makefile b/common-lisp/Makefile index 9c614c4d20..77b1530e30 100644 --- a/common-lisp/Makefile +++ b/common-lisp/Makefile @@ -56,7 +56,7 @@ else endif clean: - find . -name 'step*' -executable -exec git check-ignore \{\} \; -delete + find . -maxdepth 1 -name 'step*' -executable -delete rm -f *.lib *.fas[l] images/* hist/*_impl stats: $(SOURCES) From 269ce7ffb715d568a481d313621009cbd3d65e64 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Thu, 24 Aug 2017 18:58:17 +0530 Subject: [PATCH 0415/2308] Common Lisp: Add support for running MAL using MKCL --- common-lisp/Makefile | 12 +++++++++++- common-lisp/README.org | 14 ++++++++++---- common-lisp/fake-readline.lisp | 18 ++++++++++++++++++ common-lisp/run-mkcl.lisp | 21 +++++++++++++++++++++ common-lisp/src/types.lisp | 10 +++++----- common-lisp/step0_repl.asd | 3 ++- common-lisp/step1_read_print.asd | 4 +++- common-lisp/step2_eval.asd | 4 +++- common-lisp/step3_env.asd | 4 +++- common-lisp/step4_if_fn_do.asd | 4 +++- common-lisp/step5_tco.asd | 4 +++- common-lisp/step6_file.asd | 4 +++- common-lisp/step7_quote.asd | 4 +++- common-lisp/step8_macros.asd | 4 +++- common-lisp/step9_try.asd | 4 +++- common-lisp/stepA_mal.asd | 4 +++- 16 files changed, 97 insertions(+), 21 deletions(-) create mode 100644 common-lisp/fake-readline.lisp create mode 100644 common-lisp/run-mkcl.lisp diff --git a/common-lisp/Makefile b/common-lisp/Makefile index 77b1530e30..a80f34f58c 100644 --- a/common-lisp/Makefile +++ b/common-lisp/Makefile @@ -13,7 +13,10 @@ endef LISP ?= sbcl ABCL ?= abcl +MKCL ?= mkcl +# TODO: In theory cl-launch should be able to build standalone executable using +# MKCL unfortunately the executable crashes on startup STANDALONE_EXE = sbcl clisp ccl ecl cmucl ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) @@ -51,8 +54,15 @@ ifneq ($(filter $(LISP),$(STANDALONE_EXE)),) else ifeq ($(LISP),abcl) echo -n '#!/bin/sh\ncd `dirname $$0` ; $(ABCL) --noinform --noinit --nosystem --load run-abcl.lisp -- $@ $$@' > $@ chmod +x $@ -else +else ifeq ($(LISP),mkcl) + $(MKCL) -eval '(progn (require "asdf") (push *default-pathname-defaults* asdf:*central-registry*) (asdf:load-system "$@") (quit))' + echo -n '#!/bin/sh\ncd `dirname $$0` ; $(MKCL) -q -load run-mkcl.lisp -- $@ $$@' > $@ + chmod +x $@ +else ifeq ($(LISP),allegro) cl-launch --wrap 'if [ -z "$$CL_LAUNCH_VERSION" ] ; then cd "$$(dirname $$CL_LAUNCH_FILE)" ; fi' --verbose --lisp $(LISP) --source-registry $(ROOT_DIR) --system $@ --dump images/$@.$(LISP).image -o $@ --entry 'mal:main' +else + @echo "Unsupported Lisp implementation $(LISP)" + @exit 1 endif clean: diff --git a/common-lisp/README.org b/common-lisp/README.org index 97a494d4eb..b29f73a7fa 100644 --- a/common-lisp/README.org +++ b/common-lisp/README.org @@ -2,19 +2,19 @@ ** Introduction -This is a reasonably portable implementation of MAL in Common Lisp. It has been -tested to work with following Common Lisp implementations +This is a portable implementation of MAL in Common Lisp. It has been tested to +work with following Common Lisp implementations - Steel Bank Common Lisp [[http://sbcl.org/]] - Clozure Common Lisp [[http://ccl.clozure.com/]] - CMU Common Lisp [[https://www.cons.org/cmucl/]] - GNU CLISP [[http://www.clisp.org/]] - Embeddable Common Lisp [[https://common-lisp.net/project/ecl/]] +- ManKai Common Lisp https://common-lisp.net/project/mkcl/ - Allegro CL [[http://franz.com/products/allegro-common-lisp/]] - Armed Bear Common Lisp [[http://abcl.org/]] -[[http://www.cliki.net/cl-launch][cl-launch]] to build command line runnable scripts/images for most of the above -implementations. +[[http://www.cliki.net/cl-launch][cl-launch]] to build executable/wrapper scripts for most of the above implementations. ** Dependencies @@ -49,6 +49,7 @@ implementation. The nicknames that work currently are | CMU Common Lisp | cmucl | | GNU CLISP | clisp | | Embeddable Common Lisp | ecl | +| ManKai Common Lisp | mkcl | | Allegro CL | allegro | | Armed Bear Common Lisp | abcl | |------------------------+----------| @@ -71,6 +72,7 @@ be the capitalization of the given nickname. | CMU Common Lisp | CMUCL | | GNU CLISP | CLISP | | Embeddable Common Lisp | ECL | +| ManKai Common Lisp | MKCL | | Allegro CL | ALLEGRO | | Armed Bear Common Lisp | ABCL | |------------------------+-------------| @@ -92,3 +94,7 @@ building it. There is some basic interop in the form ~cl-eval~ which takes a string and evaluates it as Common Lisp code, the result is returned in form of a MAL value, as such you are limited to code that produces values that have MAL counterparts. + +** Known Issues + ABCL takes a long to boot as such it needs to be run with ~TEST_OPTS~ set to + ~--start-timeout 120~ diff --git a/common-lisp/fake-readline.lisp b/common-lisp/fake-readline.lisp new file mode 100644 index 0000000000..9895c6ed5b --- /dev/null +++ b/common-lisp/fake-readline.lisp @@ -0,0 +1,18 @@ +;; For some reason MKCL fails to find libreadline.so as a result cl-readline +;; fails. To avoid conditionals in the code we fake the cl-readline interface +;; and use it in asdf definitions when running under MKCL +(defpackage :cl-readline + (:nicknames :rl) + (:use :common-lisp)) + +(in-package :cl-readline) + +(defun readline (&keys prompt already-prompted num-chars + erase-empty-line add-history novelty-check) + (declare (ignorable ignored)) + (format *standard-output* prompt) + (force-output *standard-output*) + (read-line *standard-input* nil)) + +(defun register-function (&rest ignored) + (declare (ignorable ignored))) diff --git a/common-lisp/run-mkcl.lisp b/common-lisp/run-mkcl.lisp new file mode 100644 index 0000000000..8d751a0814 --- /dev/null +++ b/common-lisp/run-mkcl.lisp @@ -0,0 +1,21 @@ +(require 'asdf) +(push *default-pathname-defaults* asdf:*central-registry*) + +(defvar *raw-command-line-args* (loop + :for index + :from 1 + :below (mkcl:argc) + :collect (mkcl:argv index))) + +(defvar *command-line-args* (subseq *raw-command-line-args* + (min (1+ (position "--" *raw-command-line-args* :test #'string=)) + (length *raw-command-line-args*)))) + +;; Suppress compilation output +(let ((*error-output* (make-broadcast-stream)) + (*standard-output* (make-broadcast-stream))) + (format *standard-output* "~a" *command-line-args*) + (asdf:load-system (car *command-line-args*) :verbose nil)) + +(mal:main (cdr *command-line-args*)) +(quit) diff --git a/common-lisp/src/types.lisp b/common-lisp/src/types.lisp index 57e5e6bca6..f9f05a232d 100644 --- a/common-lisp/src/types.lisp +++ b/common-lisp/src/types.lisp @@ -182,11 +182,11 @@ (defun make-mal-value-hash-table () (unless (gethash 'mal-data-value-hash genhash::*hash-test-designator-map*) - ;; ECL and ABCL's implementations of sxhash do not work well with compound - ;; types, use a custom hash function which hashes the underlying value - ;; instead - (let ((hash-function #+(or ecl abcl) #'mal-sxhash - #-(or ecl abcl) #'sxhash)) + ;; ECL, ABCL and MKCL's implementations of sxhash do not work well with + ;; compound types, use a custom hash function which hashes the underlying + ;; value instead + (let ((hash-function #+(or ecl abcl mkcl) #'mal-sxhash + #-(or ecl abcl mkcl) #'sxhash)) (register-test-designator 'mal-data-value-hash hash-function #'mal-data-value=))) diff --git a/common-lisp/step0_repl.asd b/common-lisp/step0_repl.asd index fbad7d602c..57f978b66a 100644 --- a/common-lisp/step0_repl.asd +++ b/common-lisp/step0_repl.asd @@ -5,7 +5,8 @@ (load quicklisp-init))) (ql:quickload :uiop :silent t) -(ql:quickload :cl-readline :silent t) +#-mkcl (ql:quickload :cl-readline :silent t) +#+mkcl (load "fake-readline.lisp") (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step1_read_print.asd b/common-lisp/step1_read_print.asd index c3719f155e..814538c6db 100644 --- a/common-lisp/step1_read_print.asd +++ b/common-lisp/step1_read_print.asd @@ -5,11 +5,13 @@ (load quicklisp-init))) (ql:quickload :uiop :silent t) -(ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) (ql:quickload :alexandria :silent t) +#-mkcl (ql:quickload :cl-readline :silent t) +#+mkcl (load "fake-readline.lisp") + (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step2_eval.asd b/common-lisp/step2_eval.asd index 319157d3cf..f83e21e5fe 100644 --- a/common-lisp/step2_eval.asd +++ b/common-lisp/step2_eval.asd @@ -5,11 +5,13 @@ (load quicklisp-init))) (ql:quickload :uiop :silent t) -(ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) (ql:quickload :alexandria :silent t) +#-mkcl (ql:quickload :cl-readline :silent t) +#+mkcl (load "fake-readline.lisp") + (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step3_env.asd b/common-lisp/step3_env.asd index 025644622d..804921a499 100644 --- a/common-lisp/step3_env.asd +++ b/common-lisp/step3_env.asd @@ -5,11 +5,13 @@ (load quicklisp-init))) (ql:quickload :uiop :silent t) -(ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) (ql:quickload :alexandria :silent t) +#-mkcl (ql:quickload :cl-readline :silent t) +#+mkcl (load "fake-readline.lisp") + (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step4_if_fn_do.asd b/common-lisp/step4_if_fn_do.asd index 8af7349f4b..a2a7f44c08 100644 --- a/common-lisp/step4_if_fn_do.asd +++ b/common-lisp/step4_if_fn_do.asd @@ -5,11 +5,13 @@ (load quicklisp-init))) (ql:quickload :uiop :silent t) -(ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) (ql:quickload :alexandria :silent t) +#-mkcl (ql:quickload :cl-readline :silent t) +#+mkcl (load "fake-readline.lisp") + (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step5_tco.asd b/common-lisp/step5_tco.asd index 0fb40bce43..aa684ab52f 100644 --- a/common-lisp/step5_tco.asd +++ b/common-lisp/step5_tco.asd @@ -5,11 +5,13 @@ (load quicklisp-init))) (ql:quickload :uiop :silent t) -(ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) (ql:quickload :alexandria :silent t) +#-mkcl (ql:quickload :cl-readline :silent t) +#+mkcl (load "fake-readline.lisp") + (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step6_file.asd b/common-lisp/step6_file.asd index 671641c924..594ad8969b 100644 --- a/common-lisp/step6_file.asd +++ b/common-lisp/step6_file.asd @@ -5,11 +5,13 @@ (load quicklisp-init))) (ql:quickload :uiop :silent t) -(ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) (ql:quickload :alexandria :silent t) +#-mkcl (ql:quickload :cl-readline :silent t) +#+mkcl (load "fake-readline.lisp") + (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step7_quote.asd b/common-lisp/step7_quote.asd index 96ab0191a2..cf0ca7bff5 100644 --- a/common-lisp/step7_quote.asd +++ b/common-lisp/step7_quote.asd @@ -5,11 +5,13 @@ (load quicklisp-init))) (ql:quickload :uiop :silent t) -(ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) (ql:quickload :alexandria :silent t) +#-mkcl (ql:quickload :cl-readline :silent t) +#+mkcl (load "fake-readline.lisp") + (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step8_macros.asd b/common-lisp/step8_macros.asd index 4cab290143..5d6fdc7912 100644 --- a/common-lisp/step8_macros.asd +++ b/common-lisp/step8_macros.asd @@ -5,11 +5,13 @@ (load quicklisp-init))) (ql:quickload :uiop :silent t) -(ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) (ql:quickload :alexandria :silent t) +#-mkcl (ql:quickload :cl-readline :silent t) +#+mkcl (load "fake-readline.lisp") + (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/step9_try.asd b/common-lisp/step9_try.asd index 4788e6298d..2a07db6cf4 100644 --- a/common-lisp/step9_try.asd +++ b/common-lisp/step9_try.asd @@ -5,11 +5,13 @@ (load quicklisp-init))) (ql:quickload :uiop :silent t) -(ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) (ql:quickload :alexandria :silent t) +#-mkcl (ql:quickload :cl-readline :silent t) +#+mkcl (load "fake-readline.lisp") + (defpackage #:mal-asd (:use :cl :asdf)) diff --git a/common-lisp/stepA_mal.asd b/common-lisp/stepA_mal.asd index 22d5e6f165..d8dc2774b4 100644 --- a/common-lisp/stepA_mal.asd +++ b/common-lisp/stepA_mal.asd @@ -5,11 +5,13 @@ (load quicklisp-init))) (ql:quickload :uiop :silent t :verbose nil) -(ql:quickload :cl-readline :silent t) (ql:quickload :cl-ppcre :silent t) (ql:quickload :genhash :silent t) (ql:quickload :alexandria :silent t) +#-mkcl (ql:quickload :cl-readline :silent t) +#+mkcl (load "fake-readline.lisp") + (defpackage #:mal-asd (:use :cl :asdf)) From 95648aadb3bdcbaad5df4692856fb04a14e9e04a Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Sat, 26 Aug 2017 13:03:26 +0530 Subject: [PATCH 0416/2308] Common Lisp: Fix try* to catch all errors --- common-lisp/src/step9_try.lisp | 10 ++++------ common-lisp/src/stepA_mal.lisp | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/common-lisp/src/step9_try.lisp b/common-lisp/src/step9_try.lisp index 18e0060047..268e50dab6 100644 --- a/common-lisp/src/step9_try.lisp +++ b/common-lisp/src/step9_try.lisp @@ -197,7 +197,7 @@ ((mal-data-value= mal-try* (first forms)) (handler-case (return (mal-eval (second forms) env)) - ((or mal-exception mal-error) (condition) + (error (condition) (when (third forms) (let ((catch-forms (mal-data-value (third forms)))) (when (mal-data-value= mal-catch* @@ -205,11 +205,9 @@ (return (mal-eval (third catch-forms) (env:create-mal-env :parent env :binds (list (second catch-forms)) - :exprs (list (if (or (typep condition 'mal-runtime-exception) - (typep condition 'mal-error)) - (make-mal-string (format nil "~a" condition)) - (mal-exception-data condition))))))))) - (error condition)))) + :exprs (list (if (typep condition 'mal-user-exception) + (mal-exception-data condition) + (make-mal-string (format nil "~a" condition))))))))))))) (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) diff --git a/common-lisp/src/stepA_mal.lisp b/common-lisp/src/stepA_mal.lisp index 046dd9f43d..ec189dd30a 100644 --- a/common-lisp/src/stepA_mal.lisp +++ b/common-lisp/src/stepA_mal.lisp @@ -196,7 +196,7 @@ ((mal-data-value= mal-try* (first forms)) (handler-case (return (mal-eval (second forms) env)) - ((or mal-exception mal-error) (condition) + (error (condition) (when (third forms) (let ((catch-forms (mal-data-value (third forms)))) (when (mal-data-value= mal-catch* @@ -204,11 +204,9 @@ (return (mal-eval (third catch-forms) (env:create-mal-env :parent env :binds (list (second catch-forms)) - :exprs (list (if (or (typep condition 'mal-runtime-exception) - (typep condition 'mal-error)) - (make-mal-string (format nil "~a" condition)) - (mal-exception-data condition))))))))) - (error condition)))) + :exprs (list (if (typep condition 'mal-user-exception) + (mal-exception-data condition) + (make-mal-string (format nil "~a" condition))))))))))))) (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) From 244ac2d6d2b424a928116a4663408ae20b0d73ed Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 2 Sep 2017 00:46:52 +0200 Subject: [PATCH 0417/2308] Implement step 0 --- Makefile | 5 +++-- scm/Makefile | 17 +++++++++++++++++ scm/run | 12 ++++++++++++ scm/step0_repl.scm | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 scm/Makefile create mode 100755 scm/run create mode 100644 scm/step0_repl.scm diff --git a/Makefile b/Makefile index 20a663629d..ee6d07ab72 100644 --- a/Makefile +++ b/Makefile @@ -81,8 +81,8 @@ IMPLS = ada awk bash basic c d chuck clojure coffee common-lisp cpp crystal cs d erlang elisp elixir es6 factor forth fsharp go groovy gst guile haskell \ haxe io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ - python r racket rexx rpython ruby rust scala skew swift swift3 tcl ts vb vhdl \ - vimscript livescript elm + python r racket rexx rpython ruby rust scala scm skew swift swift3 tcl \ + ts vb vhdl vimscript livescript elm EXTENSION = .mal @@ -208,6 +208,7 @@ rpython_STEP_TO_PROG = rpython/$($(1)) ruby_STEP_TO_PROG = ruby/$($(1)).rb rust_STEP_TO_PROG = rust/target/release/$($(1)) scala_STEP_TO_PROG = scala/target/scala-2.11/classes/$($(1)).class +scm_STEP_TO_PROG = scm/$($(1)).scm skew_STEP_TO_PROG = skew/$($(1)).js swift_STEP_TO_PROG = swift/$($(1)) swift3_STEP_TO_PROG = swift3/$($(1)) diff --git a/scm/Makefile b/scm/Makefile new file mode 100644 index 0000000000..02e2630df3 --- /dev/null +++ b/scm/Makefile @@ -0,0 +1,17 @@ +SOURCES_BASE = reader.scm printer.scm types.scm +SOURCES_LISP = env.scm func.scm core.scm stepA_mal.scm +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +all: + +clean: + +.PHONY: stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" + diff --git a/scm/run b/scm/run new file mode 100755 index 0000000000..258c023b67 --- /dev/null +++ b/scm/run @@ -0,0 +1,12 @@ +#!/bin/bash +case ${SCM_MODE:-chibi} in + chibi) exec chibi-scheme $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; + kawa) exec kawa --r7rs $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; + chicken) exec csi -R r7rs -s $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; + gauche) exec gosh $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; + picrin) exec picrin $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; + sagittarius) exec sagittarius -n $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; + cyclone) exec icyc -s $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; + foment) exec foment $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; + *) echo "Invalid SCM_MODE: ${SCM_MODE}"; exit 2 ;; +esac diff --git a/scm/step0_repl.scm b/scm/step0_repl.scm new file mode 100644 index 0000000000..c02c11ea9d --- /dev/null +++ b/scm/step0_repl.scm @@ -0,0 +1,33 @@ +(import (scheme base)) +(import (scheme write)) + +(define (READ input) + input) + +(define (EVAL input) + input) + +(define (PRINT input) + input) + +(define (rep input) + (PRINT (EVAL (READ input)))) + +(define (readline prompt) + (display prompt) + (flush-output-port) + (let ((input (read-line))) + (if (eof-object? input) + #f + input))) + +(define (main) + (let loop () + (let ((input (readline "user> "))) + (when input + (display (rep input)) + (newline) + (loop)))) + (newline)) + +(main) From 4fc912a2fb8b51ca5fa7534b8a3edff2b4149579 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 2 Sep 2017 21:50:26 +0200 Subject: [PATCH 0418/2308] Add notes --- scm/notes.rst | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 scm/notes.rst diff --git a/scm/notes.rst b/scm/notes.rst new file mode 100644 index 0000000000..fd1bce6049 --- /dev/null +++ b/scm/notes.rst @@ -0,0 +1,106 @@ +Key +=== + +- Chibi: c +- Kawa: k +- CHICKEN: C +- Gauche: g +- Picrin: p +- Sagitarrius: s +- Cyclone: § +- Foment: f +- Guile: G +- Racket: r +- Larceny: l + +- Works: y +- Doesn't: n +- Sort of: x +- Unknown: ? + +Matrix +====== + +======================== === === === === === === === === === === === + Scheme implementations c k C g p s § f G r l +======================== === === === === === === === === === === === + R7RS support y y y y y y y y n ? x +------------------------ --- --- --- --- --- --- --- --- --- --- --- + Console I/O y y y y y y x n ? ? ? +------------------------ --- --- --- --- --- --- --- --- --- --- --- + Step #0 y y y y y y y n ? ? ? +------------------------ --- --- --- --- --- --- --- --- --- --- --- + Modules y y y y n y y y ? ? ? +------------------------ --- --- --- --- --- --- --- --- --- --- --- + Automatic library load y y x y n y x y ? ? ? +------------------------ --- --- --- --- --- --- --- --- --- --- --- + (scheme char) y y y y n y y y ? ? ? +======================== === === === === === === === === === === === + +Notes +===== + +R7RS Support +------------ + +This is about whether I can write a script in R7RS Scheme and +successfully execute it with the implementation. Guile didn't pass +this test and the manual merely mentions it implements a few +R7RS-features (which is far from sufficient), Racket supposedly has +inofficial support for it, Larceny refuses loading up anything else +than a R7RS library. + +Console I/O +----------- + +In step 0 a REPL is implemented that echoes back user input and quits +on EOF (signalled by ``C-d``). Cyclone is weird here because its +``read-line`` procedure includes the trailing newline (fixed +upstream), Foment doesn't recognize EOF from console, but does so fine +with input redirection (as in ``(with-input-from-file "..." +read-line)``), a bug report for that is still pending. + +Step #0 +------- + +This is about whether the tests for step #0 have been passed +successfully. Foment fails this as it detects it's wired up to a tty +and probes for its cursor position before initializing its readline +implementation. This makes the test rig fail detecting a prompt. A +bug has been submitted upstream to rectify this. + +Modules +------- + +MAL requires a certain amount of modularization by splitting the +interpreter up into multiple files, for this R7RS libraries are a +natural fit. This is purely about whether the implementation allows +using code from a library file inside a script file. The only one not +passing this requirement is Picrin as it neither allows loading up +multiple files nor automatically loads up extra files. This leaves me +with just ``load`` as primitive, but this is not sufficient because I +need a relative load facility and the details on how its argument is +treated are implementation-specific. + +Automatic library load +---------------------- + +R7RS libraries are specified as a list of identifiers, commonly +translated to a nested path (for example ``(foo bar)`` translates to +``foo/bar.sld``) that is looked up in the include locations and loaded +automatically. CHICKEN translates them to ``foo.bar.scm`` and doesn't +load up source files automatically, instead you'll have to compile +libraries to importable shared libraries. Similarly, Cyclone only +loads up libraries after they've been compiled. Picrin doesn't do +anything in this regard, so only something like concatenating source +files (hello JS!) might work out. + +(scheme char) +------------- + +R7RS is split up into many base libraries, including one for char +procedures. This is necessary for tokenization of user input, +unfortunately Picrin doesn't implement this namespace at all and +throws parts of it into ``(scheme base)`` instead, without the +mandatory unicode support. This and the preceding failures are reason +enough for me to exclude it from this comparison. From 6ca835670b4cb89ac1b27823a159208285bd6282 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 3 Sep 2017 23:30:37 +0200 Subject: [PATCH 0419/2308] Implement step 1 --- scm/Makefile | 47 +++++++++-- scm/lib/printer.sld | 43 ++++++++++ scm/lib/reader.sld | 178 +++++++++++++++++++++++++++++++++++++++ scm/lib/types.sld | 49 +++++++++++ scm/lib/util.sld | 85 +++++++++++++++++++ scm/notes.rst | 38 +++++++++ scm/run | 15 ++-- scm/step1_read_print.scm | 44 ++++++++++ 8 files changed, 485 insertions(+), 14 deletions(-) create mode 100644 scm/lib/printer.sld create mode 100644 scm/lib/reader.sld create mode 100644 scm/lib/types.sld create mode 100644 scm/lib/util.sld create mode 100644 scm/step1_read_print.scm diff --git a/scm/Makefile b/scm/Makefile index 02e2630df3..9959074cd3 100644 --- a/scm/Makefile +++ b/scm/Makefile @@ -1,12 +1,48 @@ -SOURCES_BASE = reader.scm printer.scm types.scm -SOURCES_LISP = env.scm func.scm core.scm stepA_mal.scm +SOURCES_BASE = lib/reader.sld lib/printer.sld lib/types.sld +SOURCES_LISP = lib/env.sld lib/func.sld lib/core.sld stepA_mal.scm SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) +BINS = step0_repl step1_read_print -all: +SYMLINK = ln -sfr +RM = rm -f -clean: +CSC = csc -O2 -R r7rs +CSCSO = csc -O2 -R r7rs -sJ +CYCLONE = cyclone -O2 + +all: symlinks + +.PHONY: symlinks chicken cyclone clean stats stats-lisp + +symlinks: + $(SYMLINK) lib/util.sld lib/util.scm + $(SYMLINK) lib/util.sld lib.util.scm + $(SYMLINK) lib/types.sld lib/types.scm + $(SYMLINK) lib/types.sld lib.types.scm + $(SYMLINK) lib/reader.sld lib/reader.scm + $(SYMLINK) lib/reader.sld lib.reader.scm + $(SYMLINK) lib/printer.sld lib/printer.scm + $(SYMLINK) lib/printer.sld lib.printer.scm -.PHONY: stats tests $(TESTS) +chicken: + $(CSCSO) lib.util.scm + $(CSCSO) lib.types.scm + $(CSCSO) lib.reader.scm + $(CSCSO) lib.printer.scm + $(CSC) step0_repl.scm + $(CSC) step1_read_print.scm + +cyclone: + $(CYCLONE) lib/util.sld + $(CYCLONE) lib/types.sld + $(CYCLONE) lib/reader.sld + $(CYCLONE) lib/printer.sld + $(CYCLONE) step0_repl.scm + $(CYCLONE) step1_read_print.scm + +clean: + $(RM) lib/*.scm lib/*.so lib/*.c lib/*.o lib/*.meta + $(RM) lib.*.scm *.so *.c *.o $(BINS) stats: $(SOURCES) @wc $^ @@ -14,4 +50,3 @@ stats: $(SOURCES) stats-lisp: $(SOURCES_LISP) @wc $^ @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - diff --git a/scm/lib/printer.sld b/scm/lib/printer.sld new file mode 100644 index 0000000000..f7badab876 --- /dev/null +++ b/scm/lib/printer.sld @@ -0,0 +1,43 @@ +(define-library (lib printer) + +(export pr-str) + +(import (scheme base)) +(import (scheme write)) + +(import (lib util)) +(import (lib types)) + +(begin + +(define (pr-str ast print-readably) + (let* ((type (and (mal-object? ast) (mal-type ast))) + (value (and type (mal-value ast)))) + (case type + ((true) "true") + ((false) "false") + ((nil) "nil") + ((number) (number->string value)) + ((string) (call-with-output-string + (lambda (port) + (if print-readably + (write value port) + (display value port))))) + ((keyword) (string-append ":" (symbol->string value))) + ((symbol) (symbol->string value)) + ((list) (pr-list value "(" ")" print-readably)) + ((vector) (pr-list (vector->list value) "[" "]" print-readably)) + ((map) (pr-list (alist->list value) "{" "}" print-readably)) + (else (error "unknown type"))))) + +(define (pr-list items starter ender print-readably) + (call-with-output-string + (lambda (port) + (display starter port) + (let ((reprs (map (lambda (item) (pr-str item print-readably)) items))) + (display (string-intersperse reprs " ") port)) + (display ender port)))) + +) + +) diff --git a/scm/lib/reader.sld b/scm/lib/reader.sld new file mode 100644 index 0000000000..c697f1da41 --- /dev/null +++ b/scm/lib/reader.sld @@ -0,0 +1,178 @@ +(define-library (lib reader) + +(export read-str) + +(import (scheme base)) +(import (scheme char)) +(import (scheme read)) +(import (scheme write)) + +(import (lib util)) +(import (lib types)) + +(begin + +(define-record-type reader + (%make-reader tokens position) + reader? + (tokens %reader-tokens) + (position %reader-position %reader-position-set!)) + +(define (make-reader tokens) + (%make-reader (list->vector tokens) 0)) + +(define (peek reader) + (let ((tokens (%reader-tokens reader)) + (position (%reader-position reader))) + (if (>= position (vector-length tokens)) + #f + (vector-ref tokens position)))) + +(define (next reader) + (let ((token (peek reader))) + (when token + (%reader-position-set! reader (+ (%reader-position reader) 1))) + token)) + +(define (read-str input) + (let* ((tokens (tokenizer input)) + (reader (make-reader tokens))) + (read-form reader))) + +(define (whitespace-char? char) + (or (char-whitespace? char) (char=? char #\,))) + +(define (special-char? char) + (memv char '(#\[ #\] #\{ #\} #\( #\) #\' #\` #\~ #\^ #\@))) + +(define (non-word-char? char) + (or (whitespace-char? char) + (memv char '(#\[ #\] #\{ #\} #\( #\) #\' #\" #\` #\;)))) + +(define (tokenizer input) + (call-with-input-string input + (lambda (port) + (let loop ((tokens '())) + (if (eof-object? (peek-char port)) + (reverse tokens) + (let ((char (read-char port))) + (cond + ((whitespace-char? char) + (loop tokens)) + ((and (char=? char #\~) + (char=? (peek-char port) #\@)) + (read-char port) ; remove @ token + (loop (cons "~@" tokens))) + ((char=? char #\") + (loop (cons (tokenize-string port) tokens))) + ((char=? char #\;) + (skip-comment port) + (loop tokens)) + ((special-char? char) + (loop (cons (list->string (list char)) tokens))) + (else + (loop (cons (tokenize-word port char) tokens)))))))))) + +(define (tokenize-string port) + (let loop ((chars '(#\"))) + (let ((char (read-char port))) + (cond + ((eof-object? char) + (list->string (reverse chars))) + ((char=? char #\\) + (let ((char (read-char port))) + (when (not (eof-object? char)) + (loop (cons char (cons #\\ chars)))))) + ((not (char=? char #\")) + (loop (cons char chars))) + ((char=? char #\") + (list->string (reverse (cons #\" chars)))))))) + +(define (skip-comment port) + (let loop () + (let ((char (peek-char port))) + (when (not (or (eof-object? char) + (char=? char #\newline))) + (read-char port) + (loop))))) + +(define (tokenize-word port char) + (let loop ((chars (list char))) + (let ((char (peek-char port))) + (if (or (eof-object? char) + (non-word-char? char)) + (list->string (reverse chars)) + (loop (cons (read-char port) chars)))))) + +(define (read-form reader) + (let ((token (peek reader))) + (cond + ((equal? token "'") + (read-macro reader 'quote)) + ((equal? token "`") + (read-macro reader 'quasiquote)) + ((equal? token "~") + (read-macro reader 'unquote)) + ((equal? token "~@") + (read-macro reader 'splice-unquote)) + ((equal? token "@") + (read-macro reader 'deref)) + ((equal? token "^") + (read-meta reader)) + ((equal? token "(") + (read-list reader ")" mal-list)) + ((equal? token "[") + (read-list reader "]" (lambda (items) (mal-vector (list->vector items))))) + ((equal? token "{") + (read-list reader "}" (lambda (items) (mal-map (list->alist items))))) + (else + (read-atom reader))))) + +(define (read-macro reader symbol) + (next reader) ; pop macro token + (mal-list (list (mal-symbol symbol) (read-form reader)))) + +(define (read-meta reader) + (next reader) ; pop macro token + (let ((form (read-form reader))) + (mal-list (list (mal-symbol 'with-meta) (read-form reader) form)))) + +(define (read-list reader ender proc) + (next reader) ; pop list start + (let loop ((items '())) + (let ((token (peek reader))) + (cond + ((equal? token ender) + (next reader) + (proc (reverse items))) + ((not token) + (error (str "expected '" ender "', got EOF"))) + (else + (loop (cons (read-form reader) items))))))) + +(define (read-atom reader) + (let ((token (next reader))) + (cond + ((not token) + (error "end of token stream" 'empty-input)) + ((equal? token "true") + mal-true) + ((equal? token "false") + mal-false) + ((equal? token "nil") + mal-nil) + ((string->number token) + => mal-number) + ((char=? (string-ref token 0) #\") + (let ((last (- (string-length token) 1))) + (if (char=? (string-ref token last) #\") + (mal-string (call-with-input-string token read)) + (error (str "expected '" #\" "', got EOF"))))) + ((char=? (string-ref token 0) #\:) + (mal-keyword (string->symbol (string-copy token 1)))) + (else + (mal-symbol (string->symbol token)))))) + +) + +) diff --git a/scm/lib/types.sld b/scm/lib/types.sld new file mode 100644 index 0000000000..e4702ce510 --- /dev/null +++ b/scm/lib/types.sld @@ -0,0 +1,49 @@ +(define-library (lib types) + +(export mal-object? mal-type mal-value mal-value-set! + mal-true mal-false mal-nil + mal-number mal-string mal-symbol mal-keyword + mal-list mal-vector mal-map mal-atom) + +(import (scheme base)) + +(begin + +(define-record-type mal-object + (make-mal-object type value meta) + mal-object? + (type mal-type) + (value mal-value mal-value-set!) + (meta mal-meta mal-meta-set!)) + +(define mal-true (make-mal-object 'true #t #f)) +(define mal-false (make-mal-object 'false #f #f)) +(define mal-nil (make-mal-object 'nil #f #f)) + +(define (mal-number n) + (make-mal-object 'number n #f)) + +(define (mal-string string) + (make-mal-object 'string string #f)) + +(define (mal-symbol name) + (make-mal-object 'symbol name #f)) + +(define (mal-keyword name) + (make-mal-object 'keyword name #f)) + +(define (mal-list items) + (make-mal-object 'list items #f)) + +(define (mal-vector items) + (make-mal-object 'vector items #f)) + +(define (mal-map items) + (make-mal-object 'map items #f)) + +(define (mal-atom item) + (make-mal-object 'atom item #f)) + +) + +) diff --git a/scm/lib/util.sld b/scm/lib/util.sld new file mode 100644 index 0000000000..1ed063cfcf --- /dev/null +++ b/scm/lib/util.sld @@ -0,0 +1,85 @@ +(define-library (lib util) + +(export call-with-input-string call-with-output-string + str prn debug + + ;; HACK: cyclone doesn't have those + error-object? error-object-message error-object-irritants) + +(import (scheme base)) +(import (scheme write)) + +(begin + +;; HACK: cyclone currently implements error the SICP way +(cond-expand + (cyclone + (define error-object? pair?) + (define error-object-message car) + (define error-object-irritants cdr)) + (else)) + +(define (call-with-input-string string proc) + (let ((port (open-input-string string))) + (dynamic-wind + (lambda () #t) + (lambda () (proc port)) + (lambda () (close-input-port port))))) + +(define (call-with-output-string proc) + (let ((port (open-output-string))) + (dynamic-wind + (lambda () #t) + (lambda () (proc port) (get-output-string port)) + (lambda () (close-output-port port))))) + +(define (str . items) + (call-with-output-string + (lambda (port) + (for-each (lambda (item) (display item port)) items)))) + +(define (prn . items) + (for-each write items) + (newline)) + +(define (debug . items) + (parameterize ((current-output-port (current-error-port))) + (apply prn items))) + +(define (intersperse items sep) + (let loop ((items items) + (acc '())) + (if (null? items) + (reverse acc) + (let ((tail (cdr items))) + (if (null? tail) + (loop (cdr items) (cons (car items) acc)) + (loop (cdr items) (cons sep (cons (car items) acc)))))))) + +(define (string-intersperse items sep) + (apply string-append (intersperse items sep))) + +(define (list->alist items) + (let loop ((items items) + (acc '())) + (if (null? items) + (reverse acc) + (let ((key (car items))) + (when (null? (cdr items)) + (error "unbalanced list")) + (let ((value (cadr items))) + (loop (cddr items) + (cons (cons key value) acc))))))) + +(define (alist->list items) + (let loop ((items items) + (acc '())) + (if (null? items) + (reverse acc) + (let ((kv (car items))) + (loop (cdr items) + (cons (cdr kv) (cons (car kv) acc))))))) + +) + +) diff --git a/scm/notes.rst b/scm/notes.rst index fd1bce6049..31ca602e5b 100644 --- a/scm/notes.rst +++ b/scm/notes.rst @@ -35,6 +35,10 @@ Matrix Automatic library load y y x y n y x y ? ? ? ------------------------ --- --- --- --- --- --- --- --- --- --- --- (scheme char) y y y y n y y y ? ? ? +------------------------ --- --- --- --- --- --- --- --- --- --- --- + Error objects y y y y ? y n y ? ? ? +------------------------ --- --- --- --- --- --- --- --- --- --- --- + Step #1 y y y y ? y n n ? ? ? ======================== === === === === === === === === === === === Notes @@ -104,3 +108,37 @@ unfortunately Picrin doesn't implement this namespace at all and throws parts of it into ``(scheme base)`` instead, without the mandatory unicode support. This and the preceding failures are reason enough for me to exclude it from this comparison. + +Error objects +------------- + +While there is an exception system, there is no need to use ``raise`` +when the more convenient ``error`` is available. Cyclone doesn't yet +support its helper procedures though, so I've written my own +replacements for them for its current internal representation (a list +of the message and the arguments). This will most certainly break +once it actually starts supporting them... + +Step #1 +------- + +This is about whether the tests for step #1 have been passed +successfully. Foment fails here as it sends ANSI escapes to the test +rig, but works again after a recent bugfix. Cyclone had a +show-stopping bug where the last symbol token had one garbage byte too +many, I've fixed this and another bug about the write representation +locally for now. + +Bug reports +=========== + +- https://github.com/justinethier/cyclone/issues/216 +- https://github.com/justinethier/cyclone/issues/217 +- https://github.com/justinethier/cyclone/issues/219 +- https://github.com/justinethier/cyclone/issues/220 +- https://github.com/justinethier/cyclone/issues/221 +- https://github.com/leftmike/foment/issues/14 +- https://github.com/leftmike/foment/issues/15 +- https://github.com/leftmike/foment/issues/16 +- https://github.com/leftmike/foment/issues/17 +- https://github.com/leftmike/foment/issues/18 diff --git a/scm/run b/scm/run index 258c023b67..4537671a1f 100755 --- a/scm/run +++ b/scm/run @@ -1,12 +1,11 @@ #!/bin/bash case ${SCM_MODE:-chibi} in - chibi) exec chibi-scheme $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; - kawa) exec kawa --r7rs $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; - chicken) exec csi -R r7rs -s $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; - gauche) exec gosh $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; - picrin) exec picrin $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; - sagittarius) exec sagittarius -n $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; - cyclone) exec icyc -s $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; - foment) exec foment $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; + chibi) exec chibi-scheme $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; + kawa) exec kawa --r7rs $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; + chicken) exec $(dirname $0)/${STEP:-stepA_mal} "${@}" ;; + gauche) exec gosh -I. $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; + sagittarius) exec sagittarius -n -L. $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; + cyclone) exec $(dirname $0)/${STEP:-stepA_mal} "${@}" ;; + foment) exec foment $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; *) echo "Invalid SCM_MODE: ${SCM_MODE}"; exit 2 ;; esac diff --git a/scm/step1_read_print.scm b/scm/step1_read_print.scm new file mode 100644 index 0000000000..8c2026f06a --- /dev/null +++ b/scm/step1_read_print.scm @@ -0,0 +1,44 @@ +(import (scheme base)) +(import (scheme write)) + +(import (lib util)) +(import (lib reader)) +(import (lib printer)) +(import (lib types)) + +(define (READ input) + (read-str input)) + +(define (EVAL ast) + ast) + +(define (PRINT ast) + (pr-str ast #t)) + +(define (rep input) + (PRINT (EVAL (READ input)))) + +(define (readline prompt) + (display prompt) + (flush-output-port) + (let ((input (read-line))) + (if (eof-object? input) + #f + input))) + +(define (main) + (let loop () + (let ((input (readline "user> "))) + (when input + (guard + (ex ((error-object? ex) + (when (not (memv 'empty-input (error-object-irritants ex))) + (display "[error] ") + (display (error-object-message ex)) + (newline)))) + (display (rep input)) + (newline)) + (loop)))) + (newline)) + +(main) From 99b66d704f1d1391ac68db91aa8f7089e967f6c5 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 4 Sep 2017 17:51:38 +0200 Subject: [PATCH 0420/2308] Implement step 2 --- scm/Makefile | 4 ++- scm/lib/util.sld | 14 ++++++++++ scm/step2_eval.scm | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 scm/step2_eval.scm diff --git a/scm/Makefile b/scm/Makefile index 9959074cd3..fd4249d2f6 100644 --- a/scm/Makefile +++ b/scm/Makefile @@ -1,7 +1,7 @@ SOURCES_BASE = lib/reader.sld lib/printer.sld lib/types.sld SOURCES_LISP = lib/env.sld lib/func.sld lib/core.sld stepA_mal.scm SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -BINS = step0_repl step1_read_print +BINS = step0_repl step1_read_print step2_eval SYMLINK = ln -sfr RM = rm -f @@ -31,6 +31,7 @@ chicken: $(CSCSO) lib.printer.scm $(CSC) step0_repl.scm $(CSC) step1_read_print.scm + $(CSC) step2_eval.scm cyclone: $(CYCLONE) lib/util.sld @@ -39,6 +40,7 @@ cyclone: $(CYCLONE) lib/printer.sld $(CYCLONE) step0_repl.scm $(CYCLONE) step1_read_print.scm + $(CYCLONE) step2_eval.scm clean: $(RM) lib/*.scm lib/*.so lib/*.c lib/*.o lib/*.meta diff --git a/scm/lib/util.sld b/scm/lib/util.sld index 1ed063cfcf..ea671f1079 100644 --- a/scm/lib/util.sld +++ b/scm/lib/util.sld @@ -2,6 +2,8 @@ (export call-with-input-string call-with-output-string str prn debug + string-intersperse + list->alist alist->list alist-ref alist-map ;; HACK: cyclone doesn't have those error-object? error-object-message error-object-irritants) @@ -80,6 +82,18 @@ (loop (cdr items) (cons (cdr kv) (cons (car kv) acc))))))) +(define (alist-ref key alist) + (let loop ((items alist)) + (if (pair? items) + (let ((item (car items))) + (if (eqv? (car item) key) + (cdr item) + (loop (cdr items)))) + #f))) + +(define (alist-map proc items) + (map (lambda (item) (proc (car item) (cdr item))) items)) + ) ) diff --git a/scm/step2_eval.scm b/scm/step2_eval.scm new file mode 100644 index 0000000000..26f9038779 --- /dev/null +++ b/scm/step2_eval.scm @@ -0,0 +1,70 @@ +(import (scheme base)) +(import (scheme write)) + +(import (lib util)) +(import (lib reader)) +(import (lib printer)) +(import (lib types)) + +(define (READ input) + (read-str input)) + +(define (eval-ast ast env) + (let ((type (and (mal-object? ast) (mal-type ast))) + (value (and (mal-object? ast) (mal-value ast)))) + (case type + ((symbol) (or (alist-ref value env) + (error (str "'" value "' not found")))) + ((list) (mal-list (map (lambda (item) (EVAL item env)) value))) + ((vector) (mal-vector (vector-map (lambda (item) (EVAL item env)) value))) + ((map) (mal-map (alist-map (lambda (key value) (cons key (EVAL value env))) value))) + (else ast)))) + +(define (EVAL ast env) + (let ((type (and (mal-object? ast) (mal-type ast)))) + (if (not (eq? type 'list)) + (eval-ast ast env) + (let ((items (mal-value ast))) + (if (null? items) + ast + (let* ((items (mal-value (eval-ast ast env))) + (op (car items)) + (ops (cdr items))) + (apply op ops))))))) + +(define (PRINT ast) + (pr-str ast #t)) + +(define repl-env + `((+ . ,(lambda (a b) (mal-number (+ (mal-value a) (mal-value b))))) + (- . ,(lambda (a b) (mal-number (- (mal-value a) (mal-value b))))) + (* . ,(lambda (a b) (mal-number (* (mal-value a) (mal-value b))))) + (/ . ,(lambda (a b) (mal-number (/ (mal-value a) (mal-value b))))))) + +(define (rep input) + (PRINT (EVAL (READ input) repl-env))) + +(define (readline prompt) + (display prompt) + (flush-output-port) + (let ((input (read-line))) + (if (eof-object? input) + #f + input))) + +(define (main) + (let loop () + (let ((input (readline "user> "))) + (when input + (guard + (ex ((error-object? ex) + (when (not (memv 'empty-input (error-object-irritants ex))) + (display "[error] ") + (display (error-object-message ex)) + (newline)))) + (display (rep input)) + (newline)) + (loop)))) + (newline)) + +(main) From f409e200b6a7805c3cb4d292109991778df46215 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 4 Sep 2017 19:49:02 +0200 Subject: [PATCH 0421/2308] Implement step 3 --- scm/Makefile | 8 +++- scm/lib/env.sld | 44 ++++++++++++++++++++++ scm/lib/printer.sld | 38 ++++++++++--------- scm/step3_env.scm | 91 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 162 insertions(+), 19 deletions(-) create mode 100644 scm/lib/env.sld create mode 100644 scm/step3_env.scm diff --git a/scm/Makefile b/scm/Makefile index fd4249d2f6..f0f62bdcfe 100644 --- a/scm/Makefile +++ b/scm/Makefile @@ -1,7 +1,7 @@ SOURCES_BASE = lib/reader.sld lib/printer.sld lib/types.sld SOURCES_LISP = lib/env.sld lib/func.sld lib/core.sld stepA_mal.scm SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -BINS = step0_repl step1_read_print step2_eval +BINS = step0_repl step1_read_print step2_eval step3_env SYMLINK = ln -sfr RM = rm -f @@ -23,24 +23,30 @@ symlinks: $(SYMLINK) lib/reader.sld lib.reader.scm $(SYMLINK) lib/printer.sld lib/printer.scm $(SYMLINK) lib/printer.sld lib.printer.scm + $(SYMLINK) lib/env.sld lib/env.scm + $(SYMLINK) lib/env.sld lib.env.scm chicken: $(CSCSO) lib.util.scm $(CSCSO) lib.types.scm $(CSCSO) lib.reader.scm $(CSCSO) lib.printer.scm + $(CSCSO) lib.env.scm $(CSC) step0_repl.scm $(CSC) step1_read_print.scm $(CSC) step2_eval.scm + $(CSC) step3_env.scm cyclone: $(CYCLONE) lib/util.sld $(CYCLONE) lib/types.sld $(CYCLONE) lib/reader.sld $(CYCLONE) lib/printer.sld + $(CYCLONE) lib/env.sld $(CYCLONE) step0_repl.scm $(CYCLONE) step1_read_print.scm $(CYCLONE) step2_eval.scm + $(CYCLONE) step3_env.scm clean: $(RM) lib/*.scm lib/*.so lib/*.c lib/*.o lib/*.meta diff --git a/scm/lib/env.sld b/scm/lib/env.sld new file mode 100644 index 0000000000..cbbd1b5b8d --- /dev/null +++ b/scm/lib/env.sld @@ -0,0 +1,44 @@ +(define-library (lib env) + +(export make-env env-set env-find env-get) + +(import (scheme base)) + +(import (lib util)) + +(begin + +(define-record-type env + (%make-env outer data) + env? + (outer env-outer) + (data env-data env-data-set!)) + +(define (make-env outer . rest) + (let ((env (%make-env outer '()))) + (when (pair? rest) + (let ((binds (car rest)) + (exprs (cadr rest))) + (for-each (lambda (bind expr) (env-set env bind expr)) + binds + exprs))) + env)) + +(define (env-set env key value) + (env-data-set! env (cons (cons key value) (env-data env)))) + +(define (env-find env key) + (cond + ((alist-ref key (env-data env)) env) + ((env-outer env) => (lambda (outer) (env-find outer key))) + (else #f))) + +(define (env-get env key) + (let ((env (env-find env key))) + (if env + (alist-ref key (env-data env)) + (error (str "'" key "' not found"))))) + +) + +) diff --git a/scm/lib/printer.sld b/scm/lib/printer.sld index f7badab876..8391c24ab6 100644 --- a/scm/lib/printer.sld +++ b/scm/lib/printer.sld @@ -11,24 +11,26 @@ (begin (define (pr-str ast print-readably) - (let* ((type (and (mal-object? ast) (mal-type ast))) - (value (and type (mal-value ast)))) - (case type - ((true) "true") - ((false) "false") - ((nil) "nil") - ((number) (number->string value)) - ((string) (call-with-output-string - (lambda (port) - (if print-readably - (write value port) - (display value port))))) - ((keyword) (string-append ":" (symbol->string value))) - ((symbol) (symbol->string value)) - ((list) (pr-list value "(" ")" print-readably)) - ((vector) (pr-list (vector->list value) "[" "]" print-readably)) - ((map) (pr-list (alist->list value) "{" "}" print-readably)) - (else (error "unknown type"))))) + (if (procedure? ast) + "#" + (let* ((type (and (mal-object? ast) (mal-type ast))) + (value (and type (mal-value ast)))) + (case type + ((true) "true") + ((false) "false") + ((nil) "nil") + ((number) (number->string value)) + ((string) (call-with-output-string + (lambda (port) + (if print-readably + (write value port) + (display value port))))) + ((keyword) (string-append ":" (symbol->string value))) + ((symbol) (symbol->string value)) + ((list) (pr-list value "(" ")" print-readably)) + ((vector) (pr-list (vector->list value) "[" "]" print-readably)) + ((map) (pr-list (alist->list value) "{" "}" print-readably)) + (else (error "unknown type")))))) (define (pr-list items starter ender print-readably) (call-with-output-string diff --git a/scm/step3_env.scm b/scm/step3_env.scm new file mode 100644 index 0000000000..9046380d64 --- /dev/null +++ b/scm/step3_env.scm @@ -0,0 +1,91 @@ +(import (scheme base)) +(import (scheme write)) + +(import (lib util)) +(import (lib reader)) +(import (lib printer)) +(import (lib types)) +(import (lib env)) + +(define (READ input) + (read-str input)) + +(define (eval-ast ast env) + (let ((type (and (mal-object? ast) (mal-type ast))) + (value (and (mal-object? ast) (mal-value ast)))) + (case type + ((symbol) (env-get env value)) + ((list) (mal-list (map (lambda (item) (EVAL item env)) value))) + ((vector) (mal-vector (vector-map (lambda (item) (EVAL item env)) value))) + ((map) (mal-map (alist-map (lambda (key value) (cons key (EVAL value env))) value))) + (else ast)))) + +(define (EVAL ast env) + (let ((type (and (mal-object? ast) (mal-type ast)))) + (if (not (eq? type 'list)) + (eval-ast ast env) + (let ((items (mal-value ast))) + (if (null? items) + ast + (case (mal-value (car items)) + ((def!) + (let ((symbol (mal-value (cadr items))) + (value (EVAL (list-ref items 2) env))) + (env-set env symbol value) + value)) + ((let*) + (let* ((env* (make-env env)) + (binds (mal-value (cadr items))) + (binds (if (vector? binds) (vector->list binds) binds)) + (form (list-ref items 2))) + (let loop ((binds binds)) + (when (pair? binds) + (let ((key (mal-value (car binds)))) + (when (null? (cdr binds)) + (error "unbalanced list")) + (let ((value (EVAL (cadr binds) env*))) + (env-set env* key value) + (loop (cddr binds)))))) + (EVAL form env*))) + (else + (let* ((items (mal-value (eval-ast ast env))) + (op (car items)) + (ops (cdr items))) + (apply op ops))))))))) + +(define (PRINT ast) + (pr-str ast #t)) + +(define repl-env (make-env #f)) +(env-set repl-env '+ (lambda (a b) (mal-number (+ (mal-value a) (mal-value b))))) +(env-set repl-env '- (lambda (a b) (mal-number (- (mal-value a) (mal-value b))))) +(env-set repl-env '* (lambda (a b) (mal-number (* (mal-value a) (mal-value b))))) +(env-set repl-env '/ (lambda (a b) (mal-number (/ (mal-value a) (mal-value b))))) + +(define (rep input) + (PRINT (EVAL (READ input) repl-env))) + +(define (readline prompt) + (display prompt) + (flush-output-port) + (let ((input (read-line))) + (if (eof-object? input) + #f + input))) + +(define (main) + (let loop () + (let ((input (readline "user> "))) + (when input + (guard + (ex ((error-object? ex) + (when (not (memv 'empty-input (error-object-irritants ex))) + (display "[error] ") + (display (error-object-message ex)) + (newline)))) + (display (rep input)) + (newline)) + (loop)))) + (newline)) + +(main) From fd7da503ae11d2aaae2050a9cda53f5d51e7d59e Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 5 Sep 2017 08:33:36 +0200 Subject: [PATCH 0422/2308] Add gitignore --- scm/.gitignore | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 scm/.gitignore diff --git a/scm/.gitignore b/scm/.gitignore new file mode 100644 index 0000000000..844f88703e --- /dev/null +++ b/scm/.gitignore @@ -0,0 +1,9 @@ +lib/*.scm +lib/*.so +lib/*.c +lib/*.o +lib/*.meta +lib.*.scm +*.so +*.c +*.o From 1d20dc6b7723803ab2919e180486c651b25f72bb Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Wed, 6 Sep 2017 01:24:34 +0200 Subject: [PATCH 0423/2308] Implement step 4 --- scm/Makefile | 8 ++- scm/lib/core.sld | 84 +++++++++++++++++++++++++++++ scm/lib/env.sld | 15 ++++-- scm/lib/printer.sld | 12 ++++- scm/lib/util.sld | 7 ++- scm/notes.rst | 27 ++++++++++ scm/step4_if_fn_do.scm | 119 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 264 insertions(+), 8 deletions(-) create mode 100644 scm/lib/core.sld create mode 100644 scm/step4_if_fn_do.scm diff --git a/scm/Makefile b/scm/Makefile index f0f62bdcfe..1b3bce49c3 100644 --- a/scm/Makefile +++ b/scm/Makefile @@ -1,7 +1,7 @@ SOURCES_BASE = lib/reader.sld lib/printer.sld lib/types.sld SOURCES_LISP = lib/env.sld lib/func.sld lib/core.sld stepA_mal.scm SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -BINS = step0_repl step1_read_print step2_eval step3_env +BINS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do SYMLINK = ln -sfr RM = rm -f @@ -25,6 +25,8 @@ symlinks: $(SYMLINK) lib/printer.sld lib.printer.scm $(SYMLINK) lib/env.sld lib/env.scm $(SYMLINK) lib/env.sld lib.env.scm + $(SYMLINK) lib/core.sld lib/core.scm + $(SYMLINK) lib/core.sld lib.core.scm chicken: $(CSCSO) lib.util.scm @@ -32,10 +34,12 @@ chicken: $(CSCSO) lib.reader.scm $(CSCSO) lib.printer.scm $(CSCSO) lib.env.scm + $(CSCSO) lib.core.scm $(CSC) step0_repl.scm $(CSC) step1_read_print.scm $(CSC) step2_eval.scm $(CSC) step3_env.scm + $(CSC) step4_if_fn_do.scm cyclone: $(CYCLONE) lib/util.sld @@ -43,10 +47,12 @@ cyclone: $(CYCLONE) lib/reader.sld $(CYCLONE) lib/printer.sld $(CYCLONE) lib/env.sld + $(CYCLONE) lib/core.sld $(CYCLONE) step0_repl.scm $(CYCLONE) step1_read_print.scm $(CYCLONE) step2_eval.scm $(CYCLONE) step3_env.scm + $(CYCLONE) step4_if_fn_do.scm clean: $(RM) lib/*.scm lib/*.so lib/*.c lib/*.o lib/*.meta diff --git a/scm/lib/core.sld b/scm/lib/core.sld new file mode 100644 index 0000000000..876bd0726d --- /dev/null +++ b/scm/lib/core.sld @@ -0,0 +1,84 @@ +(define-library (lib core) + +(export ns) + +(import (scheme base)) +(import (scheme write)) + +(import (lib types)) +(import (lib util)) +(import (lib printer)) + +(begin + +(define (coerce x) + (if x mal-true mal-false)) + +(define (->printed-string args print-readably sep) + (let ((items (map (lambda (arg) (pr-str arg print-readably)) args))) + (string-intersperse items sep))) + +(define (mal-equal? a b) + (let ((a-type (and (mal-object? a) (mal-type a))) + (a-value (and (mal-object? a) (mal-value a))) + (b-type (and (mal-object? b) (mal-type b))) + (b-value (and (mal-object? b) (mal-value b)))) + (cond + ((or (not a-type) (not b-type)) + mal-false) + ((and (memq a-type '(list vector)) + (memq b-type '(list vector))) + (mal-list-equal? (->list a-value) (->list b-value))) + ((and (eq? a-type 'map) (eq? b-type 'map)) + (error "TODO")) + (else + (and (eq? a-type b-type) + (equal? a-value b-value)))))) + +(define (mal-list-equal? as bs) + (let loop ((as as) + (bs bs)) + (cond + ((and (null? as) (null? bs)) #t) + ((or (null? as) (null? bs)) #f) + (else + (if (mal-equal? (car as) (car bs)) + (loop (cdr as) (cdr bs)) + #f))))) + +(define ns + `((+ . ,(lambda (a b) (mal-number (+ (mal-value a) (mal-value b))))) + (- . ,(lambda (a b) (mal-number (- (mal-value a) (mal-value b))))) + (* . ,(lambda (a b) (mal-number (* (mal-value a) (mal-value b))))) + (/ . ,(lambda (a b) (mal-number (/ (mal-value a) (mal-value b))))) + + (list . ,(lambda args (mal-list args))) + (list? . ,(lambda (x) (coerce (and (mal-object? x) (eq? (mal-type x) 'list))))) + (empty? . ,(lambda (lis) (coerce (null? (->list (mal-value lis)))))) + (count . ,(lambda (lis) (mal-number + (if (eq? lis mal-nil) + 0 + (length (->list (mal-value lis))))))) + + (< . ,(lambda (a b) (coerce (< (mal-value a) (mal-value b))))) + (<= . ,(lambda (a b) (coerce (<= (mal-value a) (mal-value b))))) + (> . ,(lambda (a b) (coerce (> (mal-value a) (mal-value b))))) + (>= . ,(lambda (a b) (coerce (>= (mal-value a) (mal-value b))))) + (= . ,(lambda (a b) (coerce (mal-equal? a b)))) + + (pr-str . ,(lambda args (mal-string (->printed-string args #t " ")))) + (str . ,(lambda args (mal-string (->printed-string args #f "")))) + (prn . ,(lambda args + (display (->printed-string args #t " ")) + (newline) + mal-nil)) + (println . ,(lambda args + (display (->printed-string args #f " ")) + (newline) + mal-nil)) + + )) + +) + +) diff --git a/scm/lib/env.sld b/scm/lib/env.sld index cbbd1b5b8d..1804d78128 100644 --- a/scm/lib/env.sld +++ b/scm/lib/env.sld @@ -5,6 +5,7 @@ (import (scheme base)) (import (lib util)) +(import (lib types)) (begin @@ -17,11 +18,15 @@ (define (make-env outer . rest) (let ((env (%make-env outer '()))) (when (pair? rest) - (let ((binds (car rest)) - (exprs (cadr rest))) - (for-each (lambda (bind expr) (env-set env bind expr)) - binds - exprs))) + (let loop ((binds (car rest)) + (exprs (cadr rest))) + (when (pair? binds) + (let ((bind (car binds))) + (if (eq? bind '&) + (env-set env (cadr binds) (mal-list exprs)) + (begin + (env-set env bind (car exprs)) + (loop (cdr binds) (cdr exprs)))))))) env)) (define (env-set env key value) diff --git a/scm/lib/printer.sld b/scm/lib/printer.sld index 8391c24ab6..247350eb2f 100644 --- a/scm/lib/printer.sld +++ b/scm/lib/printer.sld @@ -23,7 +23,17 @@ ((string) (call-with-output-string (lambda (port) (if print-readably - (write value port) + (begin + (display #\" port) + (string-for-each + (lambda (char) + (case char + ((#\\) (display "\\\\" port)) + ((#\") (display "\\\"" port)) + ((#\newline) (display "\\n" port)) + (else (display char port)))) + value) + (display #\" port)) (display value port))))) ((keyword) (string-append ":" (symbol->string value))) ((symbol) (symbol->string value)) diff --git a/scm/lib/util.sld b/scm/lib/util.sld index ea671f1079..8fc0cf54c2 100644 --- a/scm/lib/util.sld +++ b/scm/lib/util.sld @@ -3,7 +3,7 @@ (export call-with-input-string call-with-output-string str prn debug string-intersperse - list->alist alist->list alist-ref alist-map + list->alist alist->list alist-ref alist-map ->list ;; HACK: cyclone doesn't have those error-object? error-object-message error-object-irritants) @@ -94,6 +94,11 @@ (define (alist-map proc items) (map (lambda (item) (proc (car item) (cdr item))) items)) +(define (->list items) + (if (vector? items) + (vector->list items) + items)) + ) ) diff --git a/scm/notes.rst b/scm/notes.rst index 31ca602e5b..af9aaab3bf 100644 --- a/scm/notes.rst +++ b/scm/notes.rst @@ -39,6 +39,10 @@ Matrix Error objects y y y y ? y n y ? ? ? ------------------------ --- --- --- --- --- --- --- --- --- --- --- Step #1 y y y y ? y n n ? ? ? +------------------------ --- --- --- --- --- --- --- --- --- --- --- + (srfi 1) y y y y y y y n ? ? ? +------------------------ --- --- --- --- --- --- --- --- --- --- --- + Step #4 y y y y ? y n n ? ? ? ======================== === === === === === === === === === === === Notes @@ -129,6 +133,29 @@ show-stopping bug where the last symbol token had one garbage byte too many, I've fixed this and another bug about the write representation locally for now. +(srfi 1) +-------- + +The infamous list processing SRFI. It contains many goodies you'd +taken for granted in other programming languages, such as a procedure +for retrieving the last element of a list. All implementation except +Foment have it, so I just write my own list helpers as needed. No big +deal. + +Step #4 +------- + +Step #2 and #3 worked without any hitch, step #4 however exposes some +shortcuts I've taken. R7RS states for certain procedures that +evaluation order is unspecified to allow for optimizations for pure +functions, Cyclone makes use of this for ``map``. ``begin`` is +guaranteed to go from left to right, an explicit loop also works. My +clever trick of repurposing ``read`` and ``write`` for parsing and +serializing machine-readable strings backfired as R7RS only specifies +that backslashes and double-quotes need to be quoted, newlines may be +quoted but don't have to. For this reason I rolled my own serializer +that takes care of all of those characters. + Bug reports =========== diff --git a/scm/step4_if_fn_do.scm b/scm/step4_if_fn_do.scm new file mode 100644 index 0000000000..40b7a932eb --- /dev/null +++ b/scm/step4_if_fn_do.scm @@ -0,0 +1,119 @@ +(import (scheme base)) +(import (scheme write)) + +(import (lib util)) +(import (lib reader)) +(import (lib printer)) +(import (lib types)) +(import (lib env)) +(import (lib core)) + +(define (READ input) + (read-str input)) + +(define (eval-ast ast env) + (let ((type (and (mal-object? ast) (mal-type ast))) + (value (and (mal-object? ast) (mal-value ast)))) + (case type + ((symbol) (env-get env value)) + ((list) (mal-list (map (lambda (item) (EVAL item env)) value))) + ((vector) (mal-vector (vector-map (lambda (item) (EVAL item env)) value))) + ((map) (mal-map (alist-map (lambda (key value) (cons key (EVAL value env))) value))) + (else ast)))) + +(define (EVAL ast env) + (let ((type (and (mal-object? ast) (mal-type ast)))) + (if (not (eq? type 'list)) + (eval-ast ast env) + (let ((items (mal-value ast))) + (if (null? items) + ast + (case (mal-value (car items)) + ((def!) + (let ((symbol (mal-value (cadr items))) + (value (EVAL (list-ref items 2) env))) + (env-set env symbol value) + value)) + ((let*) + (let ((env* (make-env env)) + (binds (->list (mal-value (cadr items)))) + (form (list-ref items 2))) + (let loop ((binds binds)) + (when (pair? binds) + (let ((key (mal-value (car binds)))) + (when (null? (cdr binds)) + (error "unbalanced list")) + (let ((value (EVAL (cadr binds) env*))) + (env-set env* key value) + (loop (cddr binds)))))) + (EVAL form env*))) + ((do) + (let ((forms (cdr items))) + (if (null? forms) + mal-nil + ;; the evaluation order of map is unspecified + (let loop ((forms forms)) + (let ((form (car forms)) + (tail (cdr forms))) + (if (null? tail) + (EVAL form env) + (begin + (EVAL form env) + (loop tail)))))))) + ((if) + (let* ((condition (EVAL (cadr items) env)) + (type (and (mal-object? condition) + (mal-type condition)))) + (if (memq type '(false nil)) + (if (< (length items) 4) + mal-nil + (EVAL (list-ref items 3) env)) + (EVAL (list-ref items 2) env)))) + ((fn*) + (let* ((binds (->list (mal-value (cadr items)))) + (binds (map mal-value binds)) + (body (list-ref items 2))) + (lambda args + (let ((env* (make-env env binds args))) + (EVAL body env*))))) + (else + (let* ((items (mal-value (eval-ast ast env))) + (op (car items)) + (ops (cdr items))) + (apply op ops))))))))) + +(define (PRINT ast) + (pr-str ast #t)) + +(define repl-env (make-env #f)) +(for-each (lambda (kv) (env-set repl-env (car kv) (cdr kv))) ns) + +(define (rep input) + (PRINT (EVAL (READ input) repl-env))) + +(rep "(def! not (fn* (a) (if a false true)))") + +(define (readline prompt) + (display prompt) + (flush-output-port) + (let ((input (read-line))) + (if (eof-object? input) + #f + input))) + +(define (main) + (let loop () + (let ((input (readline "user> "))) + (when input + (guard + (ex ((error-object? ex) + (when (not (memv 'empty-input (error-object-irritants ex))) + (display "[error] ") + (display (error-object-message ex)) + (newline)))) + (display (rep input)) + (newline)) + (loop)))) + (newline)) + +(main) From a9385e9777468eed8e72a0556f621fb60c37f55c Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Wed, 6 Sep 2017 17:40:38 +0200 Subject: [PATCH 0424/2308] Implement step 5 --- scm/Makefile | 6 ++- scm/lib/printer.sld | 66 ++++++++++++----------- scm/lib/types.sld | 12 ++++- scm/lib/util.sld | 2 +- scm/step5_tco.scm | 125 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 177 insertions(+), 34 deletions(-) create mode 100644 scm/step5_tco.scm diff --git a/scm/Makefile b/scm/Makefile index 1b3bce49c3..d96e3a0e58 100644 --- a/scm/Makefile +++ b/scm/Makefile @@ -1,7 +1,7 @@ SOURCES_BASE = lib/reader.sld lib/printer.sld lib/types.sld -SOURCES_LISP = lib/env.sld lib/func.sld lib/core.sld stepA_mal.scm +SOURCES_LISP = lib/env.sld lib/core.sld stepA_mal.scm SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -BINS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do +BINS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do step5_tco SYMLINK = ln -sfr RM = rm -f @@ -40,6 +40,7 @@ chicken: $(CSC) step2_eval.scm $(CSC) step3_env.scm $(CSC) step4_if_fn_do.scm + $(CSC) step5_tco.scm cyclone: $(CYCLONE) lib/util.sld @@ -53,6 +54,7 @@ cyclone: $(CYCLONE) step2_eval.scm $(CYCLONE) step3_env.scm $(CYCLONE) step4_if_fn_do.scm + $(CYCLONE) step5_tco.scm clean: $(RM) lib/*.scm lib/*.so lib/*.c lib/*.o lib/*.meta diff --git a/scm/lib/printer.sld b/scm/lib/printer.sld index 247350eb2f..f921fefc05 100644 --- a/scm/lib/printer.sld +++ b/scm/lib/printer.sld @@ -11,36 +11,42 @@ (begin (define (pr-str ast print-readably) - (if (procedure? ast) - "#" - (let* ((type (and (mal-object? ast) (mal-type ast))) - (value (and type (mal-value ast)))) - (case type - ((true) "true") - ((false) "false") - ((nil) "nil") - ((number) (number->string value)) - ((string) (call-with-output-string - (lambda (port) - (if print-readably - (begin - (display #\" port) - (string-for-each - (lambda (char) - (case char - ((#\\) (display "\\\\" port)) - ((#\") (display "\\\"" port)) - ((#\newline) (display "\\n" port)) - (else (display char port)))) - value) - (display #\" port)) - (display value port))))) - ((keyword) (string-append ":" (symbol->string value))) - ((symbol) (symbol->string value)) - ((list) (pr-list value "(" ")" print-readably)) - ((vector) (pr-list (vector->list value) "[" "]" print-readably)) - ((map) (pr-list (alist->list value) "{" "}" print-readably)) - (else (error "unknown type")))))) + (cond + ((procedure? ast) + "#") + ((func? ast) + "#") + (else + (if (procedure? ast) + "#" + (let* ((type (and (mal-object? ast) (mal-type ast))) + (value (and (mal-object? ast) (mal-value ast)))) + (case type + ((true) "true") + ((false) "false") + ((nil) "nil") + ((number) (number->string value)) + ((string) (call-with-output-string + (lambda (port) + (if print-readably + (begin + (display #\" port) + (string-for-each + (lambda (char) + (case char + ((#\\) (display "\\\\" port)) + ((#\") (display "\\\"" port)) + ((#\newline) (display "\\n" port)) + (else (display char port)))) + value) + (display #\" port)) + (display value port))))) + ((keyword) (string-append ":" (symbol->string value))) + ((symbol) (symbol->string value)) + ((list) (pr-list value "(" ")" print-readably)) + ((vector) (pr-list (vector->list value) "[" "]" print-readably)) + ((map) (pr-list (alist->list value) "{" "}" print-readably)) + (else (error "unknown type")))))))) (define (pr-list items starter ender print-readably) (call-with-output-string diff --git a/scm/lib/types.sld b/scm/lib/types.sld index e4702ce510..ca54566475 100644 --- a/scm/lib/types.sld +++ b/scm/lib/types.sld @@ -3,7 +3,9 @@ (export mal-object? mal-type mal-value mal-value-set! mal-true mal-false mal-nil mal-number mal-string mal-symbol mal-keyword - mal-list mal-vector mal-map mal-atom) + mal-list mal-vector mal-map mal-atom + + make-func func? func-ast func-params func-env func-fn) (import (scheme base)) @@ -44,6 +46,14 @@ (define (mal-atom item) (make-mal-object 'atom item #f)) +(define-record-type func + (make-func ast params env fn) + func? + (ast func-ast) + (params func-params) + (env func-env) + (fn func-fn)) + ) ) diff --git a/scm/lib/util.sld b/scm/lib/util.sld index 8fc0cf54c2..4a6744bce5 100644 --- a/scm/lib/util.sld +++ b/scm/lib/util.sld @@ -41,7 +41,7 @@ (for-each (lambda (item) (display item port)) items)))) (define (prn . items) - (for-each write items) + (for-each (lambda (item) (write item) (display " ")) items) (newline)) (define (debug . items) diff --git a/scm/step5_tco.scm b/scm/step5_tco.scm new file mode 100644 index 0000000000..62ab631fb5 --- /dev/null +++ b/scm/step5_tco.scm @@ -0,0 +1,125 @@ +(import (scheme base)) +(import (scheme write)) + +(import (lib util)) +(import (lib reader)) +(import (lib printer)) +(import (lib types)) +(import (lib env)) +(import (lib core)) + +(define (READ input) + (read-str input)) + +(define (eval-ast ast env) + (let ((type (and (mal-object? ast) (mal-type ast))) + (value (and (mal-object? ast) (mal-value ast)))) + (case type + ((symbol) (env-get env value)) + ((list) (mal-list (map (lambda (item) (EVAL item env)) value))) + ((vector) (mal-vector (vector-map (lambda (item) (EVAL item env)) value))) + ((map) (mal-map (alist-map (lambda (key value) (cons key (EVAL value env))) value))) + (else ast)))) + +(define (EVAL ast env) + (let ((type (and (mal-object? ast) (mal-type ast)))) + (if (not (eq? type 'list)) + (eval-ast ast env) + (let ((items (mal-value ast))) + (if (null? items) + ast + (case (mal-value (car items)) + ((def!) + (let ((symbol (mal-value (cadr items))) + (value (EVAL (list-ref items 2) env))) + (env-set env symbol value) + value)) + ((let*) + (let ((env* (make-env env)) + (binds (->list (mal-value (cadr items)))) + (form (list-ref items 2))) + (let loop ((binds binds)) + (when (pair? binds) + (let ((key (mal-value (car binds)))) + (when (null? (cdr binds)) + (error "unbalanced list")) + (let ((value (EVAL (cadr binds) env*))) + (env-set env* key value) + (loop (cddr binds)))))) + (EVAL form env*))) ; TCO + ((do) + (let ((forms (cdr items))) + (if (null? forms) + mal-nil + ;; the evaluation order of map is unspecified + (let loop ((forms forms)) + (let ((form (car forms)) + (tail (cdr forms))) + (if (null? tail) + (EVAL form env) ; TCO + (begin + (EVAL form env) + (loop tail)))))))) + ((if) + (let* ((condition (EVAL (cadr items) env)) + (type (and (mal-object? condition) + (mal-type condition)))) + (if (memq type '(false nil)) + (if (< (length items) 4) + mal-nil + (EVAL (list-ref items 3) env)) ; TCO + (EVAL (list-ref items 2) env)))) ; TCO + ((fn*) + (let* ((binds (->list (mal-value (cadr items)))) + (binds (map mal-value binds)) + (body (list-ref items 2)) + (fn (lambda args + (let ((env* (make-env env binds args))) + (EVAL body env*))))) + (make-func body binds env fn))) + (else + (let* ((items (mal-value (eval-ast ast env))) + (op (car items)) + (ops (cdr items))) + (if (func? op) + (let* ((outer (func-env op)) + (binds (func-params op)) + (env* (make-env outer binds ops))) + (EVAL (func-ast op) env*)) ; TCO + (apply op ops)))))))))) + +(define (PRINT ast) + (pr-str ast #t)) + +(define repl-env (make-env #f)) +(for-each (lambda (kv) (env-set repl-env (car kv) (cdr kv))) ns) + +(define (rep input) + (PRINT (EVAL (READ input) repl-env))) + +(rep "(def! not (fn* (a) (if a false true)))") + +(define (readline prompt) + (display prompt) + (flush-output-port) + (let ((input (read-line))) + (if (eof-object? input) + #f + input))) + +(define (main) + (let loop () + (let ((input (readline "user> "))) + (when input + (guard + (ex ((error-object? ex) + (when (not (memv 'empty-input (error-object-irritants ex))) + (display "[error] ") + (display (error-object-message ex)) + (newline)))) + (display (rep input)) + (newline)) + (loop)))) + (newline)) + +(main) From 663059ad9beb9d6b1221318409b7cf4c18ee429e Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Thu, 7 Sep 2017 19:56:21 +0200 Subject: [PATCH 0425/2308] Implement step 6 --- scm/Makefile | 3 + scm/lib/core.sld | 31 +++++++++- scm/lib/printer.sld | 1 + scm/lib/util.sld | 8 ++- scm/notes.rst | 13 +++++ scm/step6_file.scm | 135 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 scm/step6_file.scm diff --git a/scm/Makefile b/scm/Makefile index d96e3a0e58..52df73b33b 100644 --- a/scm/Makefile +++ b/scm/Makefile @@ -2,6 +2,7 @@ SOURCES_BASE = lib/reader.sld lib/printer.sld lib/types.sld SOURCES_LISP = lib/env.sld lib/core.sld stepA_mal.scm SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) BINS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do step5_tco +BINS += step6_file SYMLINK = ln -sfr RM = rm -f @@ -41,6 +42,7 @@ chicken: $(CSC) step3_env.scm $(CSC) step4_if_fn_do.scm $(CSC) step5_tco.scm + $(CSC) step6_file.scm cyclone: $(CYCLONE) lib/util.sld @@ -55,6 +57,7 @@ cyclone: $(CYCLONE) step3_env.scm $(CYCLONE) step4_if_fn_do.scm $(CYCLONE) step5_tco.scm + $(CYCLONE) step6_file.scm clean: $(RM) lib/*.scm lib/*.so lib/*.c lib/*.o lib/*.meta diff --git a/scm/lib/core.sld b/scm/lib/core.sld index 876bd0726d..e76a58dfaa 100644 --- a/scm/lib/core.sld +++ b/scm/lib/core.sld @@ -4,16 +4,21 @@ (import (scheme base)) (import (scheme write)) +(import (scheme file)) (import (lib types)) (import (lib util)) (import (lib printer)) +(import (lib reader)) (begin (define (coerce x) (if x mal-true mal-false)) +(define (mal-instance-of? x type) + (and (mal-object? x) (eq? (mal-type x) type))) + (define (->printed-string args print-readably sep) (let ((items (map (lambda (arg) (pr-str arg print-readably)) args))) (string-intersperse items sep))) @@ -46,6 +51,17 @@ (loop (cdr as) (cdr bs)) #f))))) +(define (slurp path) + (call-with-output-string + (lambda (out) + (call-with-input-file path + (lambda (in) + (let loop () + (let ((chunk (read-string 1024 in))) + (when (not (eof-object? chunk)) + (display chunk out) + (loop))))))))) + (define ns `((+ . ,(lambda (a b) (mal-number (+ (mal-value a) (mal-value b))))) (- . ,(lambda (a b) (mal-number (- (mal-value a) (mal-value b))))) @@ -53,7 +69,7 @@ (/ . ,(lambda (a b) (mal-number (/ (mal-value a) (mal-value b))))) (list . ,(lambda args (mal-list args))) - (list? . ,(lambda (x) (coerce (and (mal-object? x) (eq? (mal-type x) 'list))))) + (list? . ,(lambda (x) (coerce (mal-instance-of? x 'list)))) (empty? . ,(lambda (lis) (coerce (null? (->list (mal-value lis)))))) (count . ,(lambda (lis) (mal-number (if (eq? lis mal-nil) @@ -77,6 +93,19 @@ (newline) mal-nil)) + (read-string . ,(lambda (string) (read-str (mal-value string)))) + (slurp . ,(lambda (path) (mal-string (slurp (mal-value path))))) + + (atom . ,(lambda (x) (mal-atom x))) + (atom? . ,(lambda (x) (coerce (mal-instance-of? x 'atom)))) + (deref . ,(lambda (atom) (mal-value atom))) + (reset! . ,(lambda (atom x) (mal-value-set! atom x) x)) + (swap! . ,(lambda (atom fn . args) + (let* ((fn (if (func? fn) (func-fn fn) fn)) + (value (apply fn (cons (mal-value atom) args)))) + (mal-value-set! atom value) + value))) + )) ) diff --git a/scm/lib/printer.sld b/scm/lib/printer.sld index f921fefc05..18fbfae74e 100644 --- a/scm/lib/printer.sld +++ b/scm/lib/printer.sld @@ -46,6 +46,7 @@ ((list) (pr-list value "(" ")" print-readably)) ((vector) (pr-list (vector->list value) "[" "]" print-readably)) ((map) (pr-list (alist->list value) "{" "}" print-readably)) + ((atom) (string-append "(atom " (pr-str value print-readably) ")")) (else (error "unknown type")))))))) (define (pr-list items starter ender print-readably) diff --git a/scm/lib/util.sld b/scm/lib/util.sld index 4a6744bce5..f9a1ba08cf 100644 --- a/scm/lib/util.sld +++ b/scm/lib/util.sld @@ -3,7 +3,8 @@ (export call-with-input-string call-with-output-string str prn debug string-intersperse - list->alist alist->list alist-ref alist-map ->list + list->alist alist->list alist-ref alist-map + ->list cdr-safe ;; HACK: cyclone doesn't have those error-object? error-object-message error-object-irritants) @@ -99,6 +100,11 @@ (vector->list items) items)) +(define (cdr-safe x) + (if (pair? x) + (cdr x) + '())) + ) ) diff --git a/scm/notes.rst b/scm/notes.rst index af9aaab3bf..ec5c3401d4 100644 --- a/scm/notes.rst +++ b/scm/notes.rst @@ -43,6 +43,8 @@ Matrix (srfi 1) y y y y y y y n ? ? ? ------------------------ --- --- --- --- --- --- --- --- --- --- --- Step #4 y y y y ? y n n ? ? ? +------------------------ --- --- --- --- --- --- --- --- --- --- --- + Step #6 y y y y ? y n y ? ? ? ======================== === === === === === === === === === === === Notes @@ -156,6 +158,16 @@ that backslashes and double-quotes need to be quoted, newlines may be quoted but don't have to. For this reason I rolled my own serializer that takes care of all of those characters. +Step #6 +------- + +Step #5 wasn't a problem either, however this step introduced basic +file I/O. To read a complete file into a string I read a fixed size +string from the file port until EOF is returned and stuff each chunk +into the string port. This strategy yields an infinite loop with +Cyclone as it doesn't ever return EOF. I've handed in a PR to fix +this. + Bug reports =========== @@ -164,6 +176,7 @@ Bug reports - https://github.com/justinethier/cyclone/issues/219 - https://github.com/justinethier/cyclone/issues/220 - https://github.com/justinethier/cyclone/issues/221 +- https://github.com/justinethier/cyclone/pull/222 - https://github.com/leftmike/foment/issues/14 - https://github.com/leftmike/foment/issues/15 - https://github.com/leftmike/foment/issues/16 diff --git a/scm/step6_file.scm b/scm/step6_file.scm new file mode 100644 index 0000000000..c90eaebb81 --- /dev/null +++ b/scm/step6_file.scm @@ -0,0 +1,135 @@ +(import (scheme base)) +(import (scheme write)) +(import (scheme process-context)) + +(import (lib util)) +(import (lib reader)) +(import (lib printer)) +(import (lib types)) +(import (lib env)) +(import (lib core)) + +(define (READ input) + (read-str input)) + +(define (eval-ast ast env) + (let ((type (and (mal-object? ast) (mal-type ast))) + (value (and (mal-object? ast) (mal-value ast)))) + (case type + ((symbol) (env-get env value)) + ((list) (mal-list (map (lambda (item) (EVAL item env)) value))) + ((vector) (mal-vector (vector-map (lambda (item) (EVAL item env)) value))) + ((map) (mal-map (alist-map (lambda (key value) (cons key (EVAL value env))) value))) + (else ast)))) + +(define (EVAL ast env) + (let ((type (and (mal-object? ast) (mal-type ast)))) + (if (not (eq? type 'list)) + (eval-ast ast env) + (let ((items (mal-value ast))) + (if (null? items) + ast + (let ((a0 (car items))) + (case (and (mal-object? a0) (mal-value a0)) + ((def!) + (let ((symbol (mal-value (cadr items))) + (value (EVAL (list-ref items 2) env))) + (env-set env symbol value) + value)) + ((let*) + (let ((env* (make-env env)) + (binds (->list (mal-value (cadr items)))) + (form (list-ref items 2))) + (let loop ((binds binds)) + (when (pair? binds) + (let ((key (mal-value (car binds)))) + (when (null? (cdr binds)) + (error "unbalanced list")) + (let ((value (EVAL (cadr binds) env*))) + (env-set env* key value) + (loop (cddr binds)))))) + (EVAL form env*))) ; TCO + ((do) + (let ((forms (cdr items))) + (if (null? forms) + mal-nil + ;; the evaluation order of map is unspecified + (let loop ((forms forms)) + (let ((form (car forms)) + (tail (cdr forms))) + (if (null? tail) + (EVAL form env) ; TCO + (begin + (EVAL form env) + (loop tail)))))))) + ((if) + (let* ((condition (EVAL (cadr items) env)) + (type (and (mal-object? condition) + (mal-type condition)))) + (if (memq type '(false nil)) + (if (< (length items) 4) + mal-nil + (EVAL (list-ref items 3) env)) ; TCO + (EVAL (list-ref items 2) env)))) ; TCO + ((fn*) + (let* ((binds (->list (mal-value (cadr items)))) + (binds (map mal-value binds)) + (body (list-ref items 2)) + (fn (lambda args + (let ((env* (make-env env binds args))) + (EVAL body env*))))) + (make-func body binds env fn))) + (else + (let* ((items (mal-value (eval-ast ast env))) + (op (car items)) + (ops (cdr items))) + (if (func? op) + (let* ((outer (func-env op)) + (binds (func-params op)) + (env* (make-env outer binds ops))) + (EVAL (func-ast op) env*)) ; TCO + (apply op ops))))))))))) + +(define (PRINT ast) + (pr-str ast #t)) + +(define repl-env (make-env #f)) +(for-each (lambda (kv) (env-set repl-env (car kv) (cdr kv))) ns) + +(define (rep input) + (PRINT (EVAL (READ input) repl-env))) + +(define argv (cdr (command-line))) + +(env-set repl-env 'eval (lambda (ast) (EVAL ast repl-env))) +(env-set repl-env '*ARGV* (mal-list (map mal-string (cdr-safe argv)))) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + +(define (readline prompt) + (display prompt) + (flush-output-port) + (let ((input (read-line))) + (if (eof-object? input) + #f + input))) + +(define (main) + (let loop () + (let ((input (readline "user> "))) + (when input + (guard + (ex ((error-object? ex) + (when (not (memv 'empty-input (error-object-irritants ex))) + (display "[error] ") + (display (error-object-message ex)) + (newline)))) + (display (rep input)) + (newline)) + (loop)))) + (newline)) + +(if (null? argv) + (main) + (rep (string-append "(load-file \"" (car argv) "\")"))) From 7f0ce0f00952f14a072b8f8750e39329925b3b5a Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Thu, 7 Sep 2017 19:57:04 +0200 Subject: [PATCH 0426/2308] Implement step 7 --- scm/Makefile | 4 +- scm/lib/core.sld | 3 + scm/step7_quote.scm | 163 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 scm/step7_quote.scm diff --git a/scm/Makefile b/scm/Makefile index 52df73b33b..01bf7a5e2c 100644 --- a/scm/Makefile +++ b/scm/Makefile @@ -2,7 +2,7 @@ SOURCES_BASE = lib/reader.sld lib/printer.sld lib/types.sld SOURCES_LISP = lib/env.sld lib/core.sld stepA_mal.scm SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) BINS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do step5_tco -BINS += step6_file +BINS += step6_file step7_quote SYMLINK = ln -sfr RM = rm -f @@ -43,6 +43,7 @@ chicken: $(CSC) step4_if_fn_do.scm $(CSC) step5_tco.scm $(CSC) step6_file.scm + $(CSC) step7_quote.scm cyclone: $(CYCLONE) lib/util.sld @@ -58,6 +59,7 @@ cyclone: $(CYCLONE) step4_if_fn_do.scm $(CYCLONE) step5_tco.scm $(CYCLONE) step6_file.scm + $(CYCLONE) step7_quote.scm clean: $(RM) lib/*.scm lib/*.so lib/*.c lib/*.o lib/*.meta diff --git a/scm/lib/core.sld b/scm/lib/core.sld index e76a58dfaa..bd8a0cef38 100644 --- a/scm/lib/core.sld +++ b/scm/lib/core.sld @@ -106,6 +106,9 @@ (mal-value-set! atom value) value))) + (cons . ,(lambda (x xs) (mal-list (cons x (->list (mal-value xs)))))) + (concat . ,(lambda args (mal-list (apply append (map (lambda (arg) (->list (mal-value arg))) args))))) + )) ) diff --git a/scm/step7_quote.scm b/scm/step7_quote.scm new file mode 100644 index 0000000000..ea081bccba --- /dev/null +++ b/scm/step7_quote.scm @@ -0,0 +1,163 @@ +(import (scheme base)) +(import (scheme write)) +(import (scheme process-context)) + +(import (lib util)) +(import (lib reader)) +(import (lib printer)) +(import (lib types)) +(import (lib env)) +(import (lib core)) + +(define (READ input) + (read-str input)) + +(define (eval-ast ast env) + (let ((type (and (mal-object? ast) (mal-type ast))) + (value (and (mal-object? ast) (mal-value ast)))) + (case type + ((symbol) (env-get env value)) + ((list) (mal-list (map (lambda (item) (EVAL item env)) value))) + ((vector) (mal-vector (vector-map (lambda (item) (EVAL item env)) value))) + ((map) (mal-map (alist-map (lambda (key value) (cons key (EVAL value env))) value))) + (else ast)))) + +(define (is-pair? ast) + (let ((type (and (mal-object? ast) (mal-type ast)))) + (if (memq type '(list vector)) + (pair? (->list (mal-value ast))) + #f))) + +(define (QUASIQUOTE ast) + (if (not (is-pair? ast)) + (mal-list (list (mal-symbol 'quote) ast)) + (let* ((items (->list (mal-value ast))) + (a0 (car items))) + (if (and (mal-object? a0) + (eq? (mal-type a0) 'symbol) + (eq? (mal-value a0) 'unquote)) + (cadr items) + (if (and (is-pair? a0) + (mal-object? (car (mal-value a0))) + (eq? (mal-type (car (mal-value a0))) 'symbol) + (eq? (mal-value (car (mal-value a0))) 'splice-unquote)) + (mal-list (list (mal-symbol 'concat) + (cadr (mal-value a0)) + (QUASIQUOTE (mal-list (cdr items))))) + (mal-list (list (mal-symbol 'cons) + (QUASIQUOTE a0) + (QUASIQUOTE (mal-list (cdr items)))))))))) + +(define (EVAL ast env) + (let ((type (and (mal-object? ast) (mal-type ast)))) + (if (not (eq? type 'list)) + (eval-ast ast env) + (let ((items (mal-value ast))) + (if (null? items) + ast + (let ((a0 (car items))) + (case (and (mal-object? a0) (mal-value a0)) + ((def!) + (let ((symbol (mal-value (cadr items))) + (value (EVAL (list-ref items 2) env))) + (env-set env symbol value) + value)) + ((let*) + (let ((env* (make-env env)) + (binds (->list (mal-value (cadr items)))) + (form (list-ref items 2))) + (let loop ((binds binds)) + (when (pair? binds) + (let ((key (mal-value (car binds)))) + (when (null? (cdr binds)) + (error "unbalanced list")) + (let ((value (EVAL (cadr binds) env*))) + (env-set env* key value) + (loop (cddr binds)))))) + (EVAL form env*))) ; TCO + ((do) + (let ((forms (cdr items))) + (if (null? forms) + mal-nil + ;; the evaluation order of map is unspecified + (let loop ((forms forms)) + (let ((form (car forms)) + (tail (cdr forms))) + (if (null? tail) + (EVAL form env) ; TCO + (begin + (EVAL form env) + (loop tail)))))))) + ((if) + (let* ((condition (EVAL (cadr items) env)) + (type (and (mal-object? condition) + (mal-type condition)))) + (if (memq type '(false nil)) + (if (< (length items) 4) + mal-nil + (EVAL (list-ref items 3) env)) ; TCO + (EVAL (list-ref items 2) env)))) ; TCO + ((quote) (cadr items)) + ((quasiquote) (EVAL (QUASIQUOTE (cadr items)) env)) ; TCO + ((fn*) + (let* ((binds (->list (mal-value (cadr items)))) + (binds (map mal-value binds)) + (body (list-ref items 2)) + (fn (lambda args + (let ((env* (make-env env binds args))) + (EVAL body env*))))) + (make-func body binds env fn))) + (else + (let* ((items (mal-value (eval-ast ast env))) + (op (car items)) + (ops (cdr items))) + (if (func? op) + (let* ((outer (func-env op)) + (binds (func-params op)) + (env* (make-env outer binds ops))) + (EVAL (func-ast op) env*)) ; TCO + (apply op ops))))))))))) + +(define (PRINT ast) + (pr-str ast #t)) + +(define repl-env (make-env #f)) +(for-each (lambda (kv) (env-set repl-env (car kv) (cdr kv))) ns) + +(define (rep input) + (PRINT (EVAL (READ input) repl-env))) + +(define argv (cdr (command-line))) + +(env-set repl-env 'eval (lambda (ast) (EVAL ast repl-env))) +(env-set repl-env '*ARGV* (mal-list (map mal-string (cdr-safe argv)))) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + +(define (readline prompt) + (display prompt) + (flush-output-port) + (let ((input (read-line))) + (if (eof-object? input) + #f + input))) + +(define (main) + (let loop () + (let ((input (readline "user> "))) + (when input + (guard + (ex ((error-object? ex) + (when (not (memv 'empty-input (error-object-irritants ex))) + (display "[error] ") + (display (error-object-message ex)) + (newline)))) + (display (rep input)) + (newline)) + (loop)))) + (newline)) + +(if (null? argv) + (main) + (rep (string-append "(load-file \"" (car argv) "\")"))) From 0d6f869650d48f999c5b7c70d6e9f3b7ad7ca657 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Thu, 7 Sep 2017 22:41:37 +0200 Subject: [PATCH 0427/2308] Implement step 8 --- scm/Makefile | 4 +- scm/lib/core.sld | 20 ++++- scm/lib/types.sld | 16 +++- scm/lib/util.sld | 7 +- scm/step8_macros.scm | 206 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 245 insertions(+), 8 deletions(-) create mode 100644 scm/step8_macros.scm diff --git a/scm/Makefile b/scm/Makefile index 01bf7a5e2c..b3f4542ae2 100644 --- a/scm/Makefile +++ b/scm/Makefile @@ -2,7 +2,7 @@ SOURCES_BASE = lib/reader.sld lib/printer.sld lib/types.sld SOURCES_LISP = lib/env.sld lib/core.sld stepA_mal.scm SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) BINS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do step5_tco -BINS += step6_file step7_quote +BINS += step6_file step7_quote step8_macros SYMLINK = ln -sfr RM = rm -f @@ -44,6 +44,7 @@ chicken: $(CSC) step5_tco.scm $(CSC) step6_file.scm $(CSC) step7_quote.scm + $(CSC) step8_macros.scm cyclone: $(CYCLONE) lib/util.sld @@ -60,6 +61,7 @@ cyclone: $(CYCLONE) step5_tco.scm $(CYCLONE) step6_file.scm $(CYCLONE) step7_quote.scm + $(CYCLONE) step8_macros.scm clean: $(RM) lib/*.scm lib/*.so lib/*.c lib/*.o lib/*.meta diff --git a/scm/lib/core.sld b/scm/lib/core.sld index bd8a0cef38..d969578b84 100644 --- a/scm/lib/core.sld +++ b/scm/lib/core.sld @@ -16,9 +16,6 @@ (define (coerce x) (if x mal-true mal-false)) -(define (mal-instance-of? x type) - (and (mal-object? x) (eq? (mal-type x) type))) - (define (->printed-string args print-readably sep) (let ((items (map (lambda (arg) (pr-str arg print-readably)) args))) (string-intersperse items sep))) @@ -108,6 +105,23 @@ (cons . ,(lambda (x xs) (mal-list (cons x (->list (mal-value xs)))))) (concat . ,(lambda args (mal-list (apply append (map (lambda (arg) (->list (mal-value arg))) args))))) + (nth . ,(lambda (x n) (let ((items (->list (mal-value x))) + (index (mal-value n))) + (if (< index (length items)) + (list-ref items index) + (error (str "Out of range: " index)))))) + (first . ,(lambda (x) (if (eq? x mal-nil) + mal-nil + (let ((items (->list (mal-value x)))) + (if (null? items) + mal-nil + (car items)))))) + (rest . ,(lambda (x) (if (eq? x mal-nil) + (mal-list '()) + (let ((items (->list (mal-value x)))) + (if (null? items) + (mal-list '()) + (mal-list (cdr items))))))) )) diff --git a/scm/lib/types.sld b/scm/lib/types.sld index ca54566475..b5a8581d83 100644 --- a/scm/lib/types.sld +++ b/scm/lib/types.sld @@ -5,7 +5,10 @@ mal-number mal-string mal-symbol mal-keyword mal-list mal-vector mal-map mal-atom - make-func func? func-ast func-params func-env func-fn) + make-func func? func-ast func-params func-env + func-fn func-macro? func-macro?-set! + + mal-instance-of?) (import (scheme base)) @@ -47,12 +50,19 @@ (make-mal-object 'atom item #f)) (define-record-type func - (make-func ast params env fn) + (%make-func ast params env fn macro?) func? (ast func-ast) (params func-params) (env func-env) - (fn func-fn)) + (fn func-fn) + (macro? func-macro? func-macro?-set!)) + +(define (make-func ast params env fn) + (%make-func ast params env fn #f)) + +(define (mal-instance-of? x type) + (and (mal-object? x) (eq? (mal-type x) type))) ) diff --git a/scm/lib/util.sld b/scm/lib/util.sld index f9a1ba08cf..44615ccb4f 100644 --- a/scm/lib/util.sld +++ b/scm/lib/util.sld @@ -4,7 +4,7 @@ str prn debug string-intersperse list->alist alist->list alist-ref alist-map - ->list cdr-safe + ->list car-safe cdr-safe ;; HACK: cyclone doesn't have those error-object? error-object-message error-object-irritants) @@ -100,6 +100,11 @@ (vector->list items) items)) +(define (car-safe x) + (if (pair? x) + (car x) + '())) + (define (cdr-safe x) (if (pair? x) (cdr x) diff --git a/scm/step8_macros.scm b/scm/step8_macros.scm new file mode 100644 index 0000000000..006ed82b69 --- /dev/null +++ b/scm/step8_macros.scm @@ -0,0 +1,206 @@ +(import (scheme base)) +(import (scheme write)) +(import (scheme process-context)) + +(import (lib util)) +(import (lib reader)) +(import (lib printer)) +(import (lib types)) +(import (lib env)) +(import (lib core)) + +(define (READ input) + (read-str input)) + +(define (eval-ast ast env) + (let ((type (and (mal-object? ast) (mal-type ast))) + (value (and (mal-object? ast) (mal-value ast)))) + (case type + ((symbol) (env-get env value)) + ((list) (mal-list (map (lambda (item) (EVAL item env)) value))) + ((vector) (mal-vector (vector-map (lambda (item) (EVAL item env)) value))) + ((map) (mal-map (alist-map (lambda (key value) (cons key (EVAL value env))) value))) + (else ast)))) + +(define (is-pair? ast) + (let ((type (and (mal-object? ast) (mal-type ast)))) + (if (memq type '(list vector)) + (pair? (->list (mal-value ast))) + #f))) + +(define (QUASIQUOTE ast) + (if (not (is-pair? ast)) + (mal-list (list (mal-symbol 'quote) ast)) + (let* ((items (->list (mal-value ast))) + (a0 (car items))) + (if (and (mal-object? a0) + (eq? (mal-type a0) 'symbol) + (eq? (mal-value a0) 'unquote)) + (cadr items) + (if (and (is-pair? a0) + (mal-object? (car (mal-value a0))) + (eq? (mal-type (car (mal-value a0))) 'symbol) + (eq? (mal-value (car (mal-value a0))) 'splice-unquote)) + (mal-list (list (mal-symbol 'concat) + (cadr (mal-value a0)) + (QUASIQUOTE (mal-list (cdr items))))) + (mal-list (list (mal-symbol 'cons) + (QUASIQUOTE a0) + (QUASIQUOTE (mal-list (cdr items)))))))))) + +(define (is-macro-call? ast env) + (if (mal-instance-of? ast 'list) + (let ((op (car-safe (mal-value ast)))) + (if (mal-instance-of? op 'symbol) + (let* ((symbol (mal-value op)) + (env (env-find env symbol))) + (if env + (let ((x (env-get env symbol))) + (if (and (func? x) (func-macro? x)) + #t + #f)) + #f)) + #f)) + #f)) + +(define (macroexpand ast env) + (let loop ((ast ast)) + (if (is-macro-call? ast env) + (let* ((items (mal-value ast)) + (op (car items)) + (ops (cdr items)) + (fn (func-fn (env-get env (mal-value op))))) + (loop (apply fn ops))) + ast))) + +(define (EVAL ast env) + (let ((type (and (mal-object? ast) (mal-type ast)))) + (if (not (eq? type 'list)) + (eval-ast ast env) + (if (null? (mal-value ast)) + ast + (let* ((ast (macroexpand ast env)) + (items (mal-value ast))) + (if (not (mal-instance-of? ast 'list)) + (eval-ast ast env) + (let ((a0 (car items))) + (case (and (mal-object? a0) (mal-value a0)) + ((def!) + (let ((symbol (mal-value (cadr items))) + (value (EVAL (list-ref items 2) env))) + (env-set env symbol value) + value)) + ((defmacro!) + (let ((symbol (mal-value (cadr items))) + (value (EVAL (list-ref items 2) env))) + (when (func? value) + (func-macro?-set! value #t)) + (env-set env symbol value) + value)) + ((macroexpand) + (macroexpand (cadr items) env)) + ((let*) + (let ((env* (make-env env)) + (binds (->list (mal-value (cadr items)))) + (form (list-ref items 2))) + (let loop ((binds binds)) + (when (pair? binds) + (let ((key (mal-value (car binds)))) + (when (null? (cdr binds)) + (error "unbalanced list")) + (let ((value (EVAL (cadr binds) env*))) + (env-set env* key value) + (loop (cddr binds)))))) + (EVAL form env*))) ; TCO + ((do) + (let ((forms (cdr items))) + (if (null? forms) + mal-nil + ;; the evaluation order of map is unspecified + (let loop ((forms forms)) + (let ((form (car forms)) + (tail (cdr forms))) + (if (null? tail) + (EVAL form env) ; TCO + (begin + (EVAL form env) + (loop tail)))))))) + ((if) + (let* ((condition (EVAL (cadr items) env)) + (type (and (mal-object? condition) + (mal-type condition)))) + (if (memq type '(false nil)) + (if (< (length items) 4) + mal-nil + (EVAL (list-ref items 3) env)) ; TCO + (EVAL (list-ref items 2) env)))) ; TCO + ((quote) + (cadr items)) + ((quasiquote) + (EVAL (QUASIQUOTE (cadr items)) env)) ; TCO + ((fn*) + (let* ((binds (->list (mal-value (cadr items)))) + (binds (map mal-value binds)) + (body (list-ref items 2)) + (fn (lambda args + (let ((env* (make-env env binds args))) + (EVAL body env*))))) + (make-func body binds env fn))) + (else + (let* ((items (mal-value (eval-ast ast env))) + (op (car items)) + (ops (cdr items))) + (if (func? op) + (let* ((outer (func-env op)) + (binds (func-params op)) + (env* (make-env outer binds ops))) + (EVAL (func-ast op) env*)) ; TCO + (apply op ops)))))))))))) + +(define (PRINT ast) + (pr-str ast #t)) + +(define repl-env (make-env #f)) +(for-each (lambda (kv) (env-set repl-env (car kv) (cdr kv))) ns) + +(define (rep input) + (PRINT (EVAL (READ input) repl-env))) + +(define argv (cdr (command-line))) + +(env-set repl-env 'eval (lambda (ast) (EVAL ast repl-env))) +(env-set repl-env '*ARGV* (mal-list (map mal-string (cdr-safe argv)))) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + +(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") + + +(define (readline prompt) + (display prompt) + (flush-output-port) + (let ((input (read-line))) + (if (eof-object? input) + #f + input))) + +(define (main) + (let loop () + (let ((input (readline "user> "))) + (when input + (guard + (ex ((error-object? ex) + (when (not (memv 'empty-input (error-object-irritants ex))) + (display "[error] ") + (display (error-object-message ex)) + (newline)))) + (display (rep input)) + (newline)) + (loop)))) + (newline)) + +(if (null? argv) + (main) + (rep (string-append "(load-file \"" (car argv) "\")"))) From 1d117aafee2042d286294ec4018701e96c84fd38 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Fri, 8 Sep 2017 16:34:44 +0200 Subject: [PATCH 0428/2308] Implement step 9 --- scm/.gitignore | 1 + scm/Makefile | 31 ++++++- scm/lib/core.sld | 66 +++++++++++++- scm/lib/util.sld | 49 +++++++--- scm/notes.rst | 2 + scm/run | 4 +- scm/step9_try.scm | 222 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 358 insertions(+), 17 deletions(-) create mode 100644 scm/step9_try.scm diff --git a/scm/.gitignore b/scm/.gitignore index 844f88703e..02a300ce14 100644 --- a/scm/.gitignore +++ b/scm/.gitignore @@ -7,3 +7,4 @@ lib.*.scm *.so *.c *.o +out/ diff --git a/scm/Makefile b/scm/Makefile index b3f4542ae2..018444dc14 100644 --- a/scm/Makefile +++ b/scm/Makefile @@ -2,18 +2,20 @@ SOURCES_BASE = lib/reader.sld lib/printer.sld lib/types.sld SOURCES_LISP = lib/env.sld lib/core.sld stepA_mal.scm SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) BINS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do step5_tco -BINS += step6_file step7_quote step8_macros +BINS += step6_file step7_quote step8_macros step9_try SYMLINK = ln -sfr RM = rm -f +RMR = rm -rf -CSC = csc -O2 -R r7rs -CSCSO = csc -O2 -R r7rs -sJ +KAWA = kawa --r7rs --no-warn-unused -d out -C +CSC = csc -O3 -R r7rs +CSCSO = $(CSC) -sJ CYCLONE = cyclone -O2 all: symlinks -.PHONY: symlinks chicken cyclone clean stats stats-lisp +.PHONY: symlinks kawa chicken cyclone clean stats stats-lisp symlinks: $(SYMLINK) lib/util.sld lib/util.scm @@ -29,6 +31,24 @@ symlinks: $(SYMLINK) lib/core.sld lib/core.scm $(SYMLINK) lib/core.sld lib.core.scm +kawa: + $(KAWA) lib/util.scm + $(KAWA) lib/types.scm + $(KAWA) lib/reader.scm + $(KAWA) lib/printer.scm + $(KAWA) lib/env.scm + $(KAWA) lib/core.scm + $(KAWA) step0_repl.scm + $(KAWA) step1_read_print.scm + $(KAWA) step2_eval.scm + $(KAWA) step3_env.scm + $(KAWA) step4_if_fn_do.scm + $(KAWA) step5_tco.scm + $(KAWA) step6_file.scm + $(KAWA) step7_quote.scm + $(KAWA) step8_macros.scm + $(KAWA) step9_try.scm + chicken: $(CSCSO) lib.util.scm $(CSCSO) lib.types.scm @@ -45,6 +65,7 @@ chicken: $(CSC) step6_file.scm $(CSC) step7_quote.scm $(CSC) step8_macros.scm + $(CSC) step9_try.scm cyclone: $(CYCLONE) lib/util.sld @@ -62,10 +83,12 @@ cyclone: $(CYCLONE) step6_file.scm $(CYCLONE) step7_quote.scm $(CYCLONE) step8_macros.scm + $(CYCLONE) step9_try.scm clean: $(RM) lib/*.scm lib/*.so lib/*.c lib/*.o lib/*.meta $(RM) lib.*.scm *.so *.c *.o $(BINS) + $(RMR) out stats: $(SOURCES) @wc $^ diff --git a/scm/lib/core.sld b/scm/lib/core.sld index d969578b84..7b757daeed 100644 --- a/scm/lib/core.sld +++ b/scm/lib/core.sld @@ -32,7 +32,7 @@ (memq b-type '(list vector))) (mal-list-equal? (->list a-value) (->list b-value))) ((and (eq? a-type 'map) (eq? b-type 'map)) - (error "TODO")) + (mal-map-equal? a-value b-value)) (else (and (eq? a-type b-type) (equal? a-value b-value)))))) @@ -48,6 +48,39 @@ (loop (cdr as) (cdr bs)) #f))))) +(define (mal-map-ref key m . default) + (if (pair? default) + (alist-ref key m mal-equal? (car default)) + (alist-ref key m mal-equal?))) + +(define (mal-map-equal? as bs) + (if (not (= (length as) (length bs))) + #f + (let loop ((as as)) + (if (pair? as) + (let* ((item (car as)) + (key (car item)) + (value (cdr item))) + (if (mal-equal? (mal-map-ref key bs) value) + (loop (cdr as)) + #f)) + #t)))) + +(define (mal-map-dissoc m keys) + (let loop ((items m) + (acc '())) + (if (pair? items) + (let* ((item (car items)) + (key (car item))) + (if (contains? keys (lambda (x) (mal-equal? key x))) + (loop (cdr items) acc) + (loop (cdr items) (cons item acc)))) + (reverse acc)))) + +(define (mal-map-assoc m kvs) + (let ((kvs (list->alist kvs))) + (append kvs (mal-map-dissoc m (map car kvs))))) + (define (slurp path) (call-with-output-string (lambda (out) @@ -92,6 +125,7 @@ (read-string . ,(lambda (string) (read-str (mal-value string)))) (slurp . ,(lambda (path) (mal-string (slurp (mal-value path))))) + (throw . ,(lambda (x) (raise (cons 'user-error x)))) (atom . ,(lambda (x) (mal-atom x))) (atom? . ,(lambda (x) (coerce (mal-instance-of? x 'atom)))) @@ -123,6 +157,36 @@ (mal-list '()) (mal-list (cdr items))))))) + (apply . ,(lambda (f . args) (apply (if (func? f) (func-fn f) f) + (if (pair? (cdr args)) + (append (butlast args) + (->list (mal-value (last args)))) + (->list (mal-value (car args))))))) + (map . ,(lambda (f items) (mal-list (map (if (func? f) (func-fn f) f) + (->list (mal-value items)))))) + + (nil? . ,(lambda (x) (coerce (eq? x mal-nil)))) + (true? . ,(lambda (x) (coerce (eq? x mal-true)))) + (false? . ,(lambda (x) (coerce (eq? x mal-false)))) + (symbol? . ,(lambda (x) (coerce (mal-instance-of? x 'symbol)))) + (symbol . ,(lambda (x) (mal-symbol (string->symbol (mal-value x))))) + (keyword? . ,(lambda (x) (coerce (mal-instance-of? x 'keyword)))) + (keyword . ,(lambda (x) (mal-keyword (string->symbol (mal-value x))))) + (vector? . ,(lambda (x) (coerce (mal-instance-of? x 'vector)))) + (vector . ,(lambda args (mal-vector (list->vector args)))) + (map? . ,(lambda (x) (coerce (mal-instance-of? x 'map)))) + (hash-map . ,(lambda args (mal-map (list->alist args)))) + (sequential? . ,(lambda (x) (coerce (and (mal-object? x) + (memq (mal-type x) + '(list vector)))))) + + (assoc . ,(lambda (m . kvs) (mal-map (mal-map-assoc (mal-value m) kvs)))) + (dissoc . ,(lambda (m . keys) (mal-map (mal-map-dissoc (mal-value m) keys)))) + (get . ,(lambda (m key) (mal-map-ref key (mal-value m) mal-nil))) + (contains? . ,(lambda (m key) (coerce (mal-map-ref key (mal-value m))))) + (keys . ,(lambda (m) (mal-list (map car (mal-value m))))) + (vals . ,(lambda (m) (mal-list (map cdr (mal-value m))))) + )) ) diff --git a/scm/lib/util.sld b/scm/lib/util.sld index 44615ccb4f..0b3e8cb21d 100644 --- a/scm/lib/util.sld +++ b/scm/lib/util.sld @@ -4,7 +4,7 @@ str prn debug string-intersperse list->alist alist->list alist-ref alist-map - ->list car-safe cdr-safe + ->list car-safe cdr-safe contains? last butlast ;; HACK: cyclone doesn't have those error-object? error-object-message error-object-irritants) @@ -17,7 +17,7 @@ ;; HACK: cyclone currently implements error the SICP way (cond-expand (cyclone - (define error-object? pair?) + (define (error-object? x) (and (pair? x) (string? (car x)))) (define error-object-message car) (define error-object-irritants cdr)) (else)) @@ -83,14 +83,16 @@ (loop (cdr items) (cons (cdr kv) (cons (car kv) acc))))))) -(define (alist-ref key alist) - (let loop ((items alist)) - (if (pair? items) - (let ((item (car items))) - (if (eqv? (car item) key) - (cdr item) - (loop (cdr items)))) - #f))) +(define (alist-ref key alist . args) + (let ((test (if (pair? args) (car args) eqv?)) + (default (if (> (length args) 1) (cadr args) #f))) + (let loop ((items alist)) + (if (pair? items) + (let ((item (car items))) + (if (test (car item) key) + (cdr item) + (loop (cdr items)))) + default)))) (define (alist-map proc items) (map (lambda (item) (proc (car item) (cdr item))) items)) @@ -110,6 +112,33 @@ (cdr x) '())) +(define (contains? items test) + (let loop ((items items)) + (if (pair? items) + (if (test (car items)) + #t + (loop (cdr items))) + #f))) + +(define (last items) + (when (null? items) + (error "empty argument")) + (let loop ((items items)) + (let ((tail (cdr items))) + (if (pair? tail) + (loop tail) + (car items))))) + +(define (butlast items) + (when (null? items) + (error "empty argument")) + (let loop ((items items) + (acc '())) + (let ((tail (cdr items))) + (if (pair? tail) + (loop tail (cons (car items) acc)) + (reverse acc))))) + ) ) diff --git a/scm/notes.rst b/scm/notes.rst index ec5c3401d4..4a45cc5762 100644 --- a/scm/notes.rst +++ b/scm/notes.rst @@ -177,6 +177,8 @@ Bug reports - https://github.com/justinethier/cyclone/issues/220 - https://github.com/justinethier/cyclone/issues/221 - https://github.com/justinethier/cyclone/pull/222 +- https://github.com/justinethier/cyclone/issues/224 +- https://github.com/justinethier/cyclone/issues/225 - https://github.com/leftmike/foment/issues/14 - https://github.com/leftmike/foment/issues/15 - https://github.com/leftmike/foment/issues/16 diff --git a/scm/run b/scm/run index 4537671a1f..b517126cb1 100755 --- a/scm/run +++ b/scm/run @@ -1,9 +1,9 @@ #!/bin/bash case ${SCM_MODE:-chibi} in chibi) exec chibi-scheme $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; - kawa) exec kawa --r7rs $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; - chicken) exec $(dirname $0)/${STEP:-stepA_mal} "${@}" ;; + kawa) cd $(dirname $0)/out && exec kawa -f ${STEP:-stepA_mal} "${@}" ;; gauche) exec gosh -I. $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; + chicken) exec $(dirname $0)/${STEP:-stepA_mal} "${@}" ;; sagittarius) exec sagittarius -n -L. $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; cyclone) exec $(dirname $0)/${STEP:-stepA_mal} "${@}" ;; foment) exec foment $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; diff --git a/scm/step9_try.scm b/scm/step9_try.scm new file mode 100644 index 0000000000..ad4647337c --- /dev/null +++ b/scm/step9_try.scm @@ -0,0 +1,222 @@ +(import (scheme base)) +(import (scheme write)) +(import (scheme process-context)) + +(import (lib util)) +(import (lib reader)) +(import (lib printer)) +(import (lib types)) +(import (lib env)) +(import (lib core)) + +(define (READ input) + (read-str input)) + +(define (eval-ast ast env) + (let ((type (and (mal-object? ast) (mal-type ast))) + (value (and (mal-object? ast) (mal-value ast)))) + (case type + ((symbol) (env-get env value)) + ((list) (mal-list (map (lambda (item) (EVAL item env)) value))) + ((vector) (mal-vector (vector-map (lambda (item) (EVAL item env)) value))) + ((map) (mal-map (alist-map (lambda (key value) (cons key (EVAL value env))) value))) + (else ast)))) + +(define (is-pair? ast) + (let ((type (and (mal-object? ast) (mal-type ast)))) + (if (memq type '(list vector)) + (pair? (->list (mal-value ast))) + #f))) + +(define (QUASIQUOTE ast) + (if (not (is-pair? ast)) + (mal-list (list (mal-symbol 'quote) ast)) + (let* ((items (->list (mal-value ast))) + (a0 (car items))) + (if (and (mal-object? a0) + (eq? (mal-type a0) 'symbol) + (eq? (mal-value a0) 'unquote)) + (cadr items) + (if (and (is-pair? a0) + (mal-object? (car (mal-value a0))) + (eq? (mal-type (car (mal-value a0))) 'symbol) + (eq? (mal-value (car (mal-value a0))) 'splice-unquote)) + (mal-list (list (mal-symbol 'concat) + (cadr (mal-value a0)) + (QUASIQUOTE (mal-list (cdr items))))) + (mal-list (list (mal-symbol 'cons) + (QUASIQUOTE a0) + (QUASIQUOTE (mal-list (cdr items)))))))))) + +(define (is-macro-call? ast env) + (if (mal-instance-of? ast 'list) + (let ((op (car-safe (mal-value ast)))) + (if (mal-instance-of? op 'symbol) + (let* ((symbol (mal-value op)) + (env (env-find env symbol))) + (if env + (let ((x (env-get env symbol))) + (if (and (func? x) (func-macro? x)) + #t + #f)) + #f)) + #f)) + #f)) + +(define (macroexpand ast env) + (let loop ((ast ast)) + (if (is-macro-call? ast env) + (let* ((items (mal-value ast)) + (op (car items)) + (ops (cdr items)) + (fn (func-fn (env-get env (mal-value op))))) + (loop (apply fn ops))) + ast))) + +(define (EVAL ast env) + (define (handle-catch value handler) + (let* ((symbol (mal-value (cadr handler))) + (form (list-ref handler 2)) + (env* (make-env env (list symbol) (list value)))) + (EVAL form env*))) + (let ((type (and (mal-object? ast) (mal-type ast)))) + (if (not (eq? type 'list)) + (eval-ast ast env) + (if (null? (mal-value ast)) + ast + (let* ((ast (macroexpand ast env)) + (items (mal-value ast))) + (if (not (mal-instance-of? ast 'list)) + (eval-ast ast env) + (let ((a0 (car items))) + (case (and (mal-object? a0) (mal-value a0)) + ((def!) + (let ((symbol (mal-value (cadr items))) + (value (EVAL (list-ref items 2) env))) + (env-set env symbol value) + value)) + ((defmacro!) + (let ((symbol (mal-value (cadr items))) + (value (EVAL (list-ref items 2) env))) + (when (func? value) + (func-macro?-set! value #t)) + (env-set env symbol value) + value)) + ((macroexpand) + (macroexpand (cadr items) env)) + ((try*) + (let* ((form (cadr items)) + (handler (mal-value (list-ref items 2)))) + (guard + (ex ((error-object? ex) + (handle-catch + (mal-string (error-object-message ex)) + handler)) + ((and (pair? ex) (eq? (car ex) 'user-error)) + (handle-catch (cdr ex) handler))) + (EVAL form env)))) + ((let*) + (let ((env* (make-env env)) + (binds (->list (mal-value (cadr items)))) + (form (list-ref items 2))) + (let loop ((binds binds)) + (when (pair? binds) + (let ((key (mal-value (car binds)))) + (when (null? (cdr binds)) + (error "unbalanced list")) + (let ((value (EVAL (cadr binds) env*))) + (env-set env* key value) + (loop (cddr binds)))))) + (EVAL form env*))) ; TCO + ((do) + (let ((forms (cdr items))) + (if (null? forms) + mal-nil + ;; the evaluation order of map is unspecified + (let loop ((forms forms)) + (let ((form (car forms)) + (tail (cdr forms))) + (if (null? tail) + (EVAL form env) ; TCO + (begin + (EVAL form env) + (loop tail)))))))) + ((if) + (let* ((condition (EVAL (cadr items) env)) + (type (and (mal-object? condition) + (mal-type condition)))) + (if (memq type '(false nil)) + (if (< (length items) 4) + mal-nil + (EVAL (list-ref items 3) env)) ; TCO + (EVAL (list-ref items 2) env)))) ; TCO + ((quote) + (cadr items)) + ((quasiquote) + (EVAL (QUASIQUOTE (cadr items)) env)) ; TCO + ((fn*) + (let* ((binds (->list (mal-value (cadr items)))) + (binds (map mal-value binds)) + (body (list-ref items 2)) + (fn (lambda args + (let ((env* (make-env env binds args))) + (EVAL body env*))))) + (make-func body binds env fn))) + (else + (let* ((items (mal-value (eval-ast ast env))) + (op (car items)) + (ops (cdr items))) + (if (func? op) + (let* ((outer (func-env op)) + (binds (func-params op)) + (env* (make-env outer binds ops))) + (EVAL (func-ast op) env*)) ; TCO + (apply op ops)))))))))))) + +(define (PRINT ast) + (pr-str ast #t)) + +(define repl-env (make-env #f)) +(for-each (lambda (kv) (env-set repl-env (car kv) (cdr kv))) ns) + +(define (rep input) + (PRINT (EVAL (READ input) repl-env))) + +(define argv (cdr (command-line))) + +(env-set repl-env 'eval (lambda (ast) (EVAL ast repl-env))) +(env-set repl-env '*ARGV* (mal-list (map mal-string (cdr-safe argv)))) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + +(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") + + +(define (readline prompt) + (display prompt) + (flush-output-port) + (let ((input (read-line))) + (if (eof-object? input) + #f + input))) + +(define (main) + (let loop () + (let ((input (readline "user> "))) + (when input + (guard + (ex ((error-object? ex) + (when (not (memv 'empty-input (error-object-irritants ex))) + (display "[error] ") + (display (error-object-message ex)) + (newline)))) + (display (rep input)) + (newline)) + (loop)))) + (newline)) + +(if (null? argv) + (main) + (rep (string-append "(load-file \"" (car argv) "\")"))) From e0704a2b8bfdb9f09687d27988ea69b82f4d4115 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Fri, 8 Sep 2017 16:35:07 +0200 Subject: [PATCH 0429/2308] Fix warning for argv with CHICKEN --- scm/step6_file.scm | 8 ++++---- scm/step7_quote.scm | 8 ++++---- scm/step8_macros.scm | 8 ++++---- scm/step9_try.scm | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/scm/step6_file.scm b/scm/step6_file.scm index c90eaebb81..4e39963f31 100644 --- a/scm/step6_file.scm +++ b/scm/step6_file.scm @@ -99,10 +99,10 @@ (define (rep input) (PRINT (EVAL (READ input) repl-env))) -(define argv (cdr (command-line))) +(define args (cdr (command-line))) (env-set repl-env 'eval (lambda (ast) (EVAL ast repl-env))) -(env-set repl-env '*ARGV* (mal-list (map mal-string (cdr-safe argv)))) +(env-set repl-env '*ARGV* (mal-list (map mal-string (cdr-safe args)))) (rep "(def! not (fn* (a) (if a false true)))") (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") @@ -130,6 +130,6 @@ (loop)))) (newline)) -(if (null? argv) +(if (null? args) (main) - (rep (string-append "(load-file \"" (car argv) "\")"))) + (rep (string-append "(load-file \"" (car args) "\")"))) diff --git a/scm/step7_quote.scm b/scm/step7_quote.scm index ea081bccba..1fbc5c596c 100644 --- a/scm/step7_quote.scm +++ b/scm/step7_quote.scm @@ -127,10 +127,10 @@ (define (rep input) (PRINT (EVAL (READ input) repl-env))) -(define argv (cdr (command-line))) +(define args (cdr (command-line))) (env-set repl-env 'eval (lambda (ast) (EVAL ast repl-env))) -(env-set repl-env '*ARGV* (mal-list (map mal-string (cdr-safe argv)))) +(env-set repl-env '*ARGV* (mal-list (map mal-string (cdr-safe args)))) (rep "(def! not (fn* (a) (if a false true)))") (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") @@ -158,6 +158,6 @@ (loop)))) (newline)) -(if (null? argv) +(if (null? args) (main) - (rep (string-append "(load-file \"" (car argv) "\")"))) + (rep (string-append "(load-file \"" (car args) "\")"))) diff --git a/scm/step8_macros.scm b/scm/step8_macros.scm index 006ed82b69..c4c22919b5 100644 --- a/scm/step8_macros.scm +++ b/scm/step8_macros.scm @@ -166,10 +166,10 @@ (define (rep input) (PRINT (EVAL (READ input) repl-env))) -(define argv (cdr (command-line))) +(define args (cdr (command-line))) (env-set repl-env 'eval (lambda (ast) (EVAL ast repl-env))) -(env-set repl-env '*ARGV* (mal-list (map mal-string (cdr-safe argv)))) +(env-set repl-env '*ARGV* (mal-list (map mal-string (cdr-safe args)))) (rep "(def! not (fn* (a) (if a false true)))") (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") @@ -201,6 +201,6 @@ (loop)))) (newline)) -(if (null? argv) +(if (null? args) (main) - (rep (string-append "(load-file \"" (car argv) "\")"))) + (rep (string-append "(load-file \"" (car args) "\")"))) diff --git a/scm/step9_try.scm b/scm/step9_try.scm index ad4647337c..76c41c6e5d 100644 --- a/scm/step9_try.scm +++ b/scm/step9_try.scm @@ -182,10 +182,10 @@ (define (rep input) (PRINT (EVAL (READ input) repl-env))) -(define argv (cdr (command-line))) +(define args (cdr (command-line))) (env-set repl-env 'eval (lambda (ast) (EVAL ast repl-env))) -(env-set repl-env '*ARGV* (mal-list (map mal-string (cdr-safe argv)))) +(env-set repl-env '*ARGV* (mal-list (map mal-string (cdr-safe args)))) (rep "(def! not (fn* (a) (if a false true)))") (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") @@ -217,6 +217,6 @@ (loop)))) (newline)) -(if (null? argv) +(if (null? args) (main) - (rep (string-append "(load-file \"" (car argv) "\")"))) + (rep (string-append "(load-file \"" (car args) "\")"))) From b2c53ccdb7dfbcae588fe73b08f9ee1b3cd4e798 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Fri, 8 Sep 2017 16:50:34 +0200 Subject: [PATCH 0430/2308] Move readline to util library --- scm/lib/util.sld | 9 +++++++++ scm/step1_read_print.scm | 8 -------- scm/step2_eval.scm | 8 -------- scm/step3_env.scm | 8 -------- scm/step4_if_fn_do.scm | 8 -------- scm/step5_tco.scm | 8 -------- scm/step6_file.scm | 8 -------- scm/step7_quote.scm | 8 -------- scm/step8_macros.scm | 8 -------- scm/step9_try.scm | 8 -------- 10 files changed, 9 insertions(+), 72 deletions(-) diff --git a/scm/lib/util.sld b/scm/lib/util.sld index 0b3e8cb21d..c63c9ef9d1 100644 --- a/scm/lib/util.sld +++ b/scm/lib/util.sld @@ -5,6 +5,7 @@ string-intersperse list->alist alist->list alist-ref alist-map ->list car-safe cdr-safe contains? last butlast + readline ;; HACK: cyclone doesn't have those error-object? error-object-message error-object-irritants) @@ -139,6 +140,14 @@ (loop tail (cons (car items) acc)) (reverse acc))))) +(define (readline prompt) + (display prompt) + (flush-output-port) + (let ((input (read-line))) + (if (eof-object? input) + #f + input))) + ) ) diff --git a/scm/step1_read_print.scm b/scm/step1_read_print.scm index 8c2026f06a..6cb64d0d64 100644 --- a/scm/step1_read_print.scm +++ b/scm/step1_read_print.scm @@ -18,14 +18,6 @@ (define (rep input) (PRINT (EVAL (READ input)))) -(define (readline prompt) - (display prompt) - (flush-output-port) - (let ((input (read-line))) - (if (eof-object? input) - #f - input))) - (define (main) (let loop () (let ((input (readline "user> "))) diff --git a/scm/step2_eval.scm b/scm/step2_eval.scm index 26f9038779..87db7ee741 100644 --- a/scm/step2_eval.scm +++ b/scm/step2_eval.scm @@ -44,14 +44,6 @@ (define (rep input) (PRINT (EVAL (READ input) repl-env))) -(define (readline prompt) - (display prompt) - (flush-output-port) - (let ((input (read-line))) - (if (eof-object? input) - #f - input))) - (define (main) (let loop () (let ((input (readline "user> "))) diff --git a/scm/step3_env.scm b/scm/step3_env.scm index 9046380d64..ab21a126e9 100644 --- a/scm/step3_env.scm +++ b/scm/step3_env.scm @@ -65,14 +65,6 @@ (define (rep input) (PRINT (EVAL (READ input) repl-env))) -(define (readline prompt) - (display prompt) - (flush-output-port) - (let ((input (read-line))) - (if (eof-object? input) - #f - input))) - (define (main) (let loop () (let ((input (readline "user> "))) diff --git a/scm/step4_if_fn_do.scm b/scm/step4_if_fn_do.scm index 40b7a932eb..3a112d028e 100644 --- a/scm/step4_if_fn_do.scm +++ b/scm/step4_if_fn_do.scm @@ -93,14 +93,6 @@ (rep "(def! not (fn* (a) (if a false true)))") -(define (readline prompt) - (display prompt) - (flush-output-port) - (let ((input (read-line))) - (if (eof-object? input) - #f - input))) - (define (main) (let loop () (let ((input (readline "user> "))) diff --git a/scm/step5_tco.scm b/scm/step5_tco.scm index 62ab631fb5..51506e72bd 100644 --- a/scm/step5_tco.scm +++ b/scm/step5_tco.scm @@ -99,14 +99,6 @@ (rep "(def! not (fn* (a) (if a false true)))") -(define (readline prompt) - (display prompt) - (flush-output-port) - (let ((input (read-line))) - (if (eof-object? input) - #f - input))) - (define (main) (let loop () (let ((input (readline "user> "))) diff --git a/scm/step6_file.scm b/scm/step6_file.scm index 4e39963f31..79c34e51e7 100644 --- a/scm/step6_file.scm +++ b/scm/step6_file.scm @@ -107,14 +107,6 @@ (rep "(def! not (fn* (a) (if a false true)))") (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") -(define (readline prompt) - (display prompt) - (flush-output-port) - (let ((input (read-line))) - (if (eof-object? input) - #f - input))) - (define (main) (let loop () (let ((input (readline "user> "))) diff --git a/scm/step7_quote.scm b/scm/step7_quote.scm index 1fbc5c596c..487a61037e 100644 --- a/scm/step7_quote.scm +++ b/scm/step7_quote.scm @@ -135,14 +135,6 @@ (rep "(def! not (fn* (a) (if a false true)))") (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") -(define (readline prompt) - (display prompt) - (flush-output-port) - (let ((input (read-line))) - (if (eof-object? input) - #f - input))) - (define (main) (let loop () (let ((input (readline "user> "))) diff --git a/scm/step8_macros.scm b/scm/step8_macros.scm index c4c22919b5..dc974f0fcf 100644 --- a/scm/step8_macros.scm +++ b/scm/step8_macros.scm @@ -178,14 +178,6 @@ (rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") -(define (readline prompt) - (display prompt) - (flush-output-port) - (let ((input (read-line))) - (if (eof-object? input) - #f - input))) - (define (main) (let loop () (let ((input (readline "user> "))) diff --git a/scm/step9_try.scm b/scm/step9_try.scm index 76c41c6e5d..f828ccd175 100644 --- a/scm/step9_try.scm +++ b/scm/step9_try.scm @@ -194,14 +194,6 @@ (rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") -(define (readline prompt) - (display prompt) - (flush-output-port) - (let ((input (read-line))) - (if (eof-object? input) - #f - input))) - (define (main) (let loop () (let ((input (readline "user> "))) From 8e53f705b1737a24513ff8417aa5a186a67bc7e4 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Fri, 8 Sep 2017 21:06:17 +0200 Subject: [PATCH 0431/2308] Implement step A --- scm/Makefile | 26 +++--- scm/lib/core.sld | 55 ++++++++++++ scm/lib/reader.sld | 2 +- scm/lib/types.sld | 11 +-- scm/lib/util.sld | 9 +- scm/run | 2 +- scm/stepA_mal.scm | 219 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 305 insertions(+), 19 deletions(-) create mode 100644 scm/stepA_mal.scm diff --git a/scm/Makefile b/scm/Makefile index 018444dc14..a431e812cf 100644 --- a/scm/Makefile +++ b/scm/Makefile @@ -2,13 +2,14 @@ SOURCES_BASE = lib/reader.sld lib/printer.sld lib/types.sld SOURCES_LISP = lib/env.sld lib/core.sld stepA_mal.scm SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) BINS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do step5_tco -BINS += step6_file step7_quote step8_macros step9_try +BINS += step6_file step7_quote step8_macros step9_try stepA_mal SYMLINK = ln -sfr RM = rm -f RMR = rm -rf KAWA = kawa --r7rs --no-warn-unused -d out -C +KAWAM = kawa --r7rs --no-warn-unused -d out --main -C CSC = csc -O3 -R r7rs CSCSO = $(CSC) -sJ CYCLONE = cyclone -O2 @@ -38,16 +39,17 @@ kawa: $(KAWA) lib/printer.scm $(KAWA) lib/env.scm $(KAWA) lib/core.scm - $(KAWA) step0_repl.scm - $(KAWA) step1_read_print.scm - $(KAWA) step2_eval.scm - $(KAWA) step3_env.scm - $(KAWA) step4_if_fn_do.scm - $(KAWA) step5_tco.scm - $(KAWA) step6_file.scm - $(KAWA) step7_quote.scm - $(KAWA) step8_macros.scm - $(KAWA) step9_try.scm + $(KAWAM) step0_repl.scm + $(KAWAM) step1_read_print.scm + $(KAWAM) step2_eval.scm + $(KAWAM) step3_env.scm + $(KAWAM) step4_if_fn_do.scm + $(KAWAM) step5_tco.scm + $(KAWAM) step6_file.scm + $(KAWAM) step7_quote.scm + $(KAWAM) step8_macros.scm + $(KAWAM) step9_try.scm + $(KAWAM) stepA_mal.scm chicken: $(CSCSO) lib.util.scm @@ -66,6 +68,7 @@ chicken: $(CSC) step7_quote.scm $(CSC) step8_macros.scm $(CSC) step9_try.scm + $(CSC) stepA_mal.scm cyclone: $(CYCLONE) lib/util.sld @@ -84,6 +87,7 @@ cyclone: $(CYCLONE) step7_quote.scm $(CYCLONE) step8_macros.scm $(CYCLONE) step9_try.scm + $(CYCLONE) stepA_mal.scm clean: $(RM) lib/*.scm lib/*.so lib/*.c lib/*.o lib/*.meta diff --git a/scm/lib/core.sld b/scm/lib/core.sld index 7b757daeed..37cb3820de 100644 --- a/scm/lib/core.sld +++ b/scm/lib/core.sld @@ -5,6 +5,7 @@ (import (scheme base)) (import (scheme write)) (import (scheme file)) +(import (scheme time)) (import (lib types)) (import (lib util)) @@ -92,6 +93,9 @@ (display chunk out) (loop))))))))) +(define (time-ms) + (* (/ (current-jiffy) (jiffies-per-second)) 1000.0)) + (define ns `((+ . ,(lambda (a b) (mal-number (+ (mal-value a) (mal-value b))))) (- . ,(lambda (a b) (mal-number (- (mal-value a) (mal-value b))))) @@ -126,6 +130,9 @@ (read-string . ,(lambda (string) (read-str (mal-value string)))) (slurp . ,(lambda (path) (mal-string (slurp (mal-value path))))) (throw . ,(lambda (x) (raise (cons 'user-error x)))) + (readline . ,(lambda (prompt) (let ((output (readline (mal-value prompt)))) + (if output (mal-string output) mal-nil)))) + (time-ms . ,(lambda () (mal-number (time-ms)))) (atom . ,(lambda (x) (mal-atom x))) (atom? . ,(lambda (x) (coerce (mal-instance-of? x 'atom)))) @@ -156,6 +163,33 @@ (if (null? items) (mal-list '()) (mal-list (cdr items))))))) + (conj . ,(lambda (coll . args) + (let ((items (mal-value coll))) + (cond + ((vector? items) + (mal-vector (vector-append items (list->vector args)))) + ((list? items) + (mal-list (append (reverse args) items))) + (else + (error "invalid collection type")))))) + (seq . ,(lambda (x) (if (eq? x mal-nil) + mal-nil + (let ((value (mal-value x))) + (case (mal-type x) + ((list) + (if (null? value) + mal-nil + x)) + ((vector) + (if (zero? (vector-length value)) + mal-nil + (mal-list (vector->list value)))) + ((string) + (if (zero? (string-length value)) + mal-nil + (mal-list (map mal-string (explode value))))) + (else + (error "invalid collection type"))))))) (apply . ,(lambda (f . args) (apply (if (func? f) (func-fn f) f) (if (pair? (cdr args)) @@ -168,6 +202,7 @@ (nil? . ,(lambda (x) (coerce (eq? x mal-nil)))) (true? . ,(lambda (x) (coerce (eq? x mal-true)))) (false? . ,(lambda (x) (coerce (eq? x mal-false)))) + (string? . ,(lambda (x) (coerce (mal-instance-of? x 'string)))) (symbol? . ,(lambda (x) (coerce (mal-instance-of? x 'symbol)))) (symbol . ,(lambda (x) (mal-symbol (string->symbol (mal-value x))))) (keyword? . ,(lambda (x) (coerce (mal-instance-of? x 'keyword)))) @@ -187,6 +222,26 @@ (keys . ,(lambda (m) (mal-list (map car (mal-value m))))) (vals . ,(lambda (m) (mal-list (map cdr (mal-value m))))) + (with-meta . ,(lambda (x meta) + (cond + ((mal-object? x) + (make-mal-object (mal-type x) (mal-value x) meta)) + ((func? x) + (let ((func (make-func (func-ast x) (func-params x) + (func-env x) (func-fn x)))) + (func-macro?-set! func (func-macro? x)) + (func-meta-set! func meta) + func)) + (else + (error "unsupported type"))))) + (meta . ,(lambda (x) (cond + ((mal-object? x) + (or (mal-meta x) mal-nil)) + ((func? x) + (or (func-meta x) mal-nil)) + (else + mal-nil)))) + )) ) diff --git a/scm/lib/reader.sld b/scm/lib/reader.sld index c697f1da41..6e63e9f20d 100644 --- a/scm/lib/reader.sld +++ b/scm/lib/reader.sld @@ -69,7 +69,7 @@ (skip-comment port) (loop tokens)) ((special-char? char) - (loop (cons (list->string (list char)) tokens))) + (loop (cons (char->string char) tokens))) (else (loop (cons (tokenize-word port char) tokens)))))))))) diff --git a/scm/lib/types.sld b/scm/lib/types.sld index b5a8581d83..8eebb6f854 100644 --- a/scm/lib/types.sld +++ b/scm/lib/types.sld @@ -1,12 +1,12 @@ (define-library (lib types) -(export mal-object? mal-type mal-value mal-value-set! +(export make-mal-object mal-object? mal-type mal-value mal-value-set! mal-meta mal-true mal-false mal-nil mal-number mal-string mal-symbol mal-keyword mal-list mal-vector mal-map mal-atom make-func func? func-ast func-params func-env - func-fn func-macro? func-macro?-set! + func-fn func-macro? func-macro?-set! func-meta func-meta-set! mal-instance-of?) @@ -50,16 +50,17 @@ (make-mal-object 'atom item #f)) (define-record-type func - (%make-func ast params env fn macro?) + (%make-func ast params env fn macro? meta) func? (ast func-ast) (params func-params) (env func-env) (fn func-fn) - (macro? func-macro? func-macro?-set!)) + (macro? func-macro? func-macro?-set!) + (meta func-meta func-meta-set!)) (define (make-func ast params env fn) - (%make-func ast params env fn #f)) + (%make-func ast params env fn #f #f)) (define (mal-instance-of? x type) (and (mal-object? x) (eq? (mal-type x) type))) diff --git a/scm/lib/util.sld b/scm/lib/util.sld index c63c9ef9d1..36ee354bd5 100644 --- a/scm/lib/util.sld +++ b/scm/lib/util.sld @@ -2,7 +2,8 @@ (export call-with-input-string call-with-output-string str prn debug - string-intersperse + string-intersperse explode + char->string list->alist alist->list alist-ref alist-map ->list car-safe cdr-safe contains? last butlast readline @@ -63,6 +64,12 @@ (define (string-intersperse items sep) (apply string-append (intersperse items sep))) +(define (char->string char) + (list->string (list char))) + +(define (explode string) + (map char->string (string->list string))) + (define (list->alist items) (let loop ((items items) (acc '())) diff --git a/scm/run b/scm/run index b517126cb1..5dd755c7c1 100755 --- a/scm/run +++ b/scm/run @@ -1,7 +1,7 @@ #!/bin/bash case ${SCM_MODE:-chibi} in chibi) exec chibi-scheme $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; - kawa) cd $(dirname $0)/out && exec kawa -f ${STEP:-stepA_mal} "${@}" ;; + kawa) java -cp /usr/share/kawa/lib/kawa.jar:out ${STEP:-stepA_mal} "${@}" ;; gauche) exec gosh -I. $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; chicken) exec $(dirname $0)/${STEP:-stepA_mal} "${@}" ;; sagittarius) exec sagittarius -n -L. $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; diff --git a/scm/stepA_mal.scm b/scm/stepA_mal.scm new file mode 100644 index 0000000000..fbfc6792db --- /dev/null +++ b/scm/stepA_mal.scm @@ -0,0 +1,219 @@ +(import (scheme base)) +(import (scheme write)) +(import (scheme process-context)) + +(import (lib util)) +(import (lib reader)) +(import (lib printer)) +(import (lib types)) +(import (lib env)) +(import (lib core)) + +(define (READ input) + (read-str input)) + +(define (eval-ast ast env) + (let ((type (and (mal-object? ast) (mal-type ast))) + (value (and (mal-object? ast) (mal-value ast)))) + (case type + ((symbol) (env-get env value)) + ((list) (mal-list (map (lambda (item) (EVAL item env)) value))) + ((vector) (mal-vector (vector-map (lambda (item) (EVAL item env)) value))) + ((map) (mal-map (alist-map (lambda (key value) (cons key (EVAL value env))) value))) + (else ast)))) + +(define (is-pair? ast) + (let ((type (and (mal-object? ast) (mal-type ast)))) + (if (memq type '(list vector)) + (pair? (->list (mal-value ast))) + #f))) + +(define (QUASIQUOTE ast) + (if (not (is-pair? ast)) + (mal-list (list (mal-symbol 'quote) ast)) + (let* ((items (->list (mal-value ast))) + (a0 (car items))) + (if (and (mal-object? a0) + (eq? (mal-type a0) 'symbol) + (eq? (mal-value a0) 'unquote)) + (cadr items) + (if (and (is-pair? a0) + (mal-object? (car (mal-value a0))) + (eq? (mal-type (car (mal-value a0))) 'symbol) + (eq? (mal-value (car (mal-value a0))) 'splice-unquote)) + (mal-list (list (mal-symbol 'concat) + (cadr (mal-value a0)) + (QUASIQUOTE (mal-list (cdr items))))) + (mal-list (list (mal-symbol 'cons) + (QUASIQUOTE a0) + (QUASIQUOTE (mal-list (cdr items)))))))))) + +(define (is-macro-call? ast env) + (if (mal-instance-of? ast 'list) + (let ((op (car-safe (mal-value ast)))) + (if (mal-instance-of? op 'symbol) + (let* ((symbol (mal-value op)) + (env (env-find env symbol))) + (if env + (let ((x (env-get env symbol))) + (if (and (func? x) (func-macro? x)) + #t + #f)) + #f)) + #f)) + #f)) + +(define (macroexpand ast env) + (let loop ((ast ast)) + (if (is-macro-call? ast env) + (let* ((items (mal-value ast)) + (op (car items)) + (ops (cdr items)) + (fn (func-fn (env-get env (mal-value op))))) + (loop (apply fn ops))) + ast))) + +(define (EVAL ast env) + (define (handle-catch value handler) + (let* ((symbol (mal-value (cadr handler))) + (form (list-ref handler 2)) + (env* (make-env env (list symbol) (list value)))) + (EVAL form env*))) + (let ((type (and (mal-object? ast) (mal-type ast)))) + (if (not (eq? type 'list)) + (eval-ast ast env) + (if (null? (mal-value ast)) + ast + (let* ((ast (macroexpand ast env)) + (items (mal-value ast))) + (if (not (mal-instance-of? ast 'list)) + (eval-ast ast env) + (let ((a0 (car items))) + (case (and (mal-object? a0) (mal-value a0)) + ((def!) + (let ((symbol (mal-value (cadr items))) + (value (EVAL (list-ref items 2) env))) + (env-set env symbol value) + value)) + ((defmacro!) + (let ((symbol (mal-value (cadr items))) + (value (EVAL (list-ref items 2) env))) + (when (func? value) + (func-macro?-set! value #t)) + (env-set env symbol value) + value)) + ((macroexpand) + (macroexpand (cadr items) env)) + ((try*) + (let* ((form (cadr items)) + (handler (mal-value (list-ref items 2)))) + (guard + (ex ((error-object? ex) + (handle-catch + (mal-string (error-object-message ex)) + handler)) + ((and (pair? ex) (eq? (car ex) 'user-error)) + (handle-catch (cdr ex) handler))) + (EVAL form env)))) + ((let*) + (let ((env* (make-env env)) + (binds (->list (mal-value (cadr items)))) + (form (list-ref items 2))) + (let loop ((binds binds)) + (when (pair? binds) + (let ((key (mal-value (car binds)))) + (when (null? (cdr binds)) + (error "unbalanced list")) + (let ((value (EVAL (cadr binds) env*))) + (env-set env* key value) + (loop (cddr binds)))))) + (EVAL form env*))) ; TCO + ((do) + (let ((forms (cdr items))) + (if (null? forms) + mal-nil + ;; the evaluation order of map is unspecified + (let loop ((forms forms)) + (let ((form (car forms)) + (tail (cdr forms))) + (if (null? tail) + (EVAL form env) ; TCO + (begin + (EVAL form env) + (loop tail)))))))) + ((if) + (let* ((condition (EVAL (cadr items) env)) + (type (and (mal-object? condition) + (mal-type condition)))) + (if (memq type '(false nil)) + (if (< (length items) 4) + mal-nil + (EVAL (list-ref items 3) env)) ; TCO + (EVAL (list-ref items 2) env)))) ; TCO + ((quote) + (cadr items)) + ((quasiquote) + (EVAL (QUASIQUOTE (cadr items)) env)) ; TCO + ((fn*) + (let* ((binds (->list (mal-value (cadr items)))) + (binds (map mal-value binds)) + (body (list-ref items 2)) + (fn (lambda args + (let ((env* (make-env env binds args))) + (EVAL body env*))))) + (make-func body binds env fn))) + (else + (let* ((items (mal-value (eval-ast ast env))) + (op (car items)) + (ops (cdr items))) + (if (func? op) + (let* ((outer (func-env op)) + (binds (func-params op)) + (env* (make-env outer binds ops))) + (EVAL (func-ast op) env*)) ; TCO + (apply op ops)))))))))))) + +(define (PRINT ast) + (pr-str ast #t)) + +(define repl-env (make-env #f)) +(for-each (lambda (kv) (env-set repl-env (car kv) (cdr kv))) ns) + +(define (rep input) + (PRINT (EVAL (READ input) repl-env))) + +(define args (cdr (command-line))) + +(env-set repl-env 'eval (lambda (ast) (EVAL ast repl-env))) +(env-set repl-env '*ARGV* (mal-list (map mal-string (cdr-safe args)))) +(let ((scheme (or (get-environment-variable "SCM_MODE") "chibi"))) + (env-set repl-env '*host-language* (mal-string (str "scheme (" scheme ")")))) + +(rep "(def! not (fn* (a) (if a false true)))") +(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + +(rep "(def! *gensym-counter* (atom 0))") +(rep "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))") + +(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))") +(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") + +(define (main) + (rep "(println (str \"Mal [\" *host-language* \"]\"))") + (let loop () + (let ((input (readline "user> "))) + (when input + (guard + (ex ((error-object? ex) + (when (not (memv 'empty-input (error-object-irritants ex))) + (display "[error] ") + (display (error-object-message ex)) + (newline)))) + (display (rep input)) + (newline)) + (loop)))) + (newline)) + +(if (null? args) + (main) + (rep (string-append "(load-file \"" (car args) "\")"))) From dda1fe77c437be7687894e53a9ac000f4e3e77a4 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 9 Sep 2017 12:29:35 +0200 Subject: [PATCH 0432/2308] Make perf tests run --- scm/.gitignore | 1 + scm/Makefile | 31 ++++++++++++++++++------------- scm/run | 18 +++++++++++------- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/scm/.gitignore b/scm/.gitignore index 02a300ce14..2cc78f64d7 100644 --- a/scm/.gitignore +++ b/scm/.gitignore @@ -8,3 +8,4 @@ lib.*.scm *.c *.o out/ +eggs/ diff --git a/scm/Makefile b/scm/Makefile index a431e812cf..09e507fe69 100644 --- a/scm/Makefile +++ b/scm/Makefile @@ -4,13 +4,14 @@ SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) BINS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do step5_tco BINS += step6_file step7_quote step8_macros step9_try stepA_mal +MKDIR = mkdir -p SYMLINK = ln -sfr RM = rm -f RMR = rm -rf KAWA = kawa --r7rs --no-warn-unused -d out -C KAWAM = kawa --r7rs --no-warn-unused -d out --main -C -CSC = csc -O3 -R r7rs +CSC = CHICKEN_REPOSITORY=$(CURDIR)/eggs csc -O3 -R r7rs CSCSO = $(CSC) -sJ CYCLONE = cyclone -O2 @@ -19,18 +20,19 @@ all: symlinks .PHONY: symlinks kawa chicken cyclone clean stats stats-lisp symlinks: + $(MKDIR) eggs $(SYMLINK) lib/util.sld lib/util.scm - $(SYMLINK) lib/util.sld lib.util.scm + $(SYMLINK) lib/util.sld eggs/lib.util.scm $(SYMLINK) lib/types.sld lib/types.scm - $(SYMLINK) lib/types.sld lib.types.scm + $(SYMLINK) lib/types.sld eggs/lib.types.scm $(SYMLINK) lib/reader.sld lib/reader.scm - $(SYMLINK) lib/reader.sld lib.reader.scm + $(SYMLINK) lib/reader.sld eggs/lib.reader.scm $(SYMLINK) lib/printer.sld lib/printer.scm - $(SYMLINK) lib/printer.sld lib.printer.scm + $(SYMLINK) lib/printer.sld eggs/lib.printer.scm $(SYMLINK) lib/env.sld lib/env.scm - $(SYMLINK) lib/env.sld lib.env.scm + $(SYMLINK) lib/env.sld eggs/lib.env.scm $(SYMLINK) lib/core.sld lib/core.scm - $(SYMLINK) lib/core.sld lib.core.scm + $(SYMLINK) lib/core.sld eggs/lib.core.scm kawa: $(KAWA) lib/util.scm @@ -52,12 +54,14 @@ kawa: $(KAWAM) stepA_mal.scm chicken: - $(CSCSO) lib.util.scm - $(CSCSO) lib.types.scm - $(CSCSO) lib.reader.scm - $(CSCSO) lib.printer.scm - $(CSCSO) lib.env.scm - $(CSCSO) lib.core.scm + chicken-install -init eggs + CHICKEN_REPOSITORY=$(CURDIR)/eggs chicken-install r7rs + $(CSCSO) eggs/lib.util.scm + $(CSCSO) eggs/lib.types.scm + $(CSCSO) eggs/lib.reader.scm + $(CSCSO) eggs/lib.printer.scm + $(CSCSO) eggs/lib.env.scm + $(CSCSO) eggs/lib.core.scm $(CSC) step0_repl.scm $(CSC) step1_read_print.scm $(CSC) step2_eval.scm @@ -93,6 +97,7 @@ clean: $(RM) lib/*.scm lib/*.so lib/*.c lib/*.o lib/*.meta $(RM) lib.*.scm *.so *.c *.o $(BINS) $(RMR) out + $(RMR) eggs stats: $(SOURCES) @wc $^ diff --git a/scm/run b/scm/run index 5dd755c7c1..3130ec0c81 100755 --- a/scm/run +++ b/scm/run @@ -1,11 +1,15 @@ #!/bin/bash +basedir=$(dirname $0) +kawa=${KAWA_JAR:-/usr/share/kawa/lib/kawa.jar} +step=${STEP:-stepA_mal} + case ${SCM_MODE:-chibi} in - chibi) exec chibi-scheme $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; - kawa) java -cp /usr/share/kawa/lib/kawa.jar:out ${STEP:-stepA_mal} "${@}" ;; - gauche) exec gosh -I. $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; - chicken) exec $(dirname $0)/${STEP:-stepA_mal} "${@}" ;; - sagittarius) exec sagittarius -n -L. $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; - cyclone) exec $(dirname $0)/${STEP:-stepA_mal} "${@}" ;; - foment) exec foment $(dirname $0)/${STEP:-stepA_mal}.scm "${@}" ;; + chibi) exec chibi-scheme -I$basedir $basedir/$step.scm "${@}" ;; + kawa) exec java -cp $kawa:$basedir/out $step "${@}" ;; + gauche) exec gosh -I$basedir $basedir/$step.scm "${@}" ;; + chicken) CHICKEN_REPOSITORY=$basedir/eggs exec $basedir/$step "${@}" ;; + sagittarius) exec sagittarius -n -L$basedir $basedir/$step.scm "${@}" ;; + cyclone) exec $basedir/$step "${@}" ;; + foment) exec foment $basedir/$step.scm "${@}" ;; *) echo "Invalid SCM_MODE: ${SCM_MODE}"; exit 2 ;; esac From 0541442183ddb9a83ff149e6cf8e2b4c823b0a93 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 9 Sep 2017 12:34:18 +0200 Subject: [PATCH 0433/2308] Self-hosting fix --- scm/lib/core.sld | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/scm/lib/core.sld b/scm/lib/core.sld index 37cb3820de..8299a992a9 100644 --- a/scm/lib/core.sld +++ b/scm/lib/core.sld @@ -82,6 +82,13 @@ (let ((kvs (list->alist kvs))) (append kvs (mal-map-dissoc m (map car kvs))))) +(define (map-in-order proc items) + (let loop ((items items) + (acc '())) + (if (null? items) + (reverse acc) + (loop (cdr items) (cons (proc (car items)) acc))))) + (define (slurp path) (call-with-output-string (lambda (out) @@ -196,8 +203,9 @@ (append (butlast args) (->list (mal-value (last args)))) (->list (mal-value (car args))))))) - (map . ,(lambda (f items) (mal-list (map (if (func? f) (func-fn f) f) - (->list (mal-value items)))))) + (map . ,(lambda (f items) (mal-list (map-in-order + (if (func? f) (func-fn f) f) + (->list (mal-value items)))))) (nil? . ,(lambda (x) (coerce (eq? x mal-nil)))) (true? . ,(lambda (x) (coerce (eq? x mal-true)))) From a43528b9dc405c38a9e1df0da726e666385c9ca2 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 9 Sep 2017 16:09:47 +0200 Subject: [PATCH 0434/2308] Implement scm-eval --- scm/lib/core.sld | 24 ++++++++++++++++++++++++ scm/tests/stepA_mal.mal | 17 +++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 scm/tests/stepA_mal.mal diff --git a/scm/lib/core.sld b/scm/lib/core.sld index 8299a992a9..b3d815376c 100644 --- a/scm/lib/core.sld +++ b/scm/lib/core.sld @@ -6,6 +6,9 @@ (import (scheme write)) (import (scheme file)) (import (scheme time)) +(import (scheme read)) +(import (scheme eval)) +(import (scheme repl)) (import (lib types)) (import (lib util)) @@ -103,6 +106,26 @@ (define (time-ms) (* (/ (current-jiffy) (jiffies-per-second)) 1000.0)) +(define (->mal-object x) + (cond + ((boolean? x) (if x mal-true mal-false)) + ((char? x) (mal-string (char->string x))) + ((procedure? x) x) + ((symbol? x) (mal-symbol x)) + ((number? x) (mal-number x)) + ((string? x) (mal-string x)) + ((or (null? x) (pair? x)) + (mal-list (map ->mal-object x))) + ((vector? x) + (mal-vector (vector-map ->mal-object x))) + (else + (error "unknown type")))) + +(define (scm-eval input) + (call-with-input-string input + (lambda (port) + (->mal-object (eval (read port) (interaction-environment)))))) + (define ns `((+ . ,(lambda (a b) (mal-number (+ (mal-value a) (mal-value b))))) (- . ,(lambda (a b) (mal-number (- (mal-value a) (mal-value b))))) @@ -140,6 +163,7 @@ (readline . ,(lambda (prompt) (let ((output (readline (mal-value prompt)))) (if output (mal-string output) mal-nil)))) (time-ms . ,(lambda () (mal-number (time-ms)))) + (scm-eval . ,(lambda (input) (scm-eval (mal-value input)))) (atom . ,(lambda (x) (mal-atom x))) (atom? . ,(lambda (x) (coerce (mal-instance-of? x 'atom)))) diff --git a/scm/tests/stepA_mal.mal b/scm/tests/stepA_mal.mal new file mode 100644 index 0000000000..da3f7fe3bc --- /dev/null +++ b/scm/tests/stepA_mal.mal @@ -0,0 +1,17 @@ +;; Testing basic Scheme interop + +(scm-eval "(+ 1 1)") +;=>2 + +(scm-eval "(begin (display \"Hello World!\") (newline))") +; "Hello World!" + +(scm-eval "(string->list \"MAL\")") +;=>("M" "A" "L") + +(scm-eval "(map + '(1 2 3) '(4 5 6))") +;=>(5 7 9) + +(scm-eval "(define (rot13 c) (integer->char (+ 65 (modulo (+ (- (char->integer c) 65) 13) 26))))") +(scm-eval "(string-map rot13 \"ZNY\")") +;=>"MAL" From ff3a8bf8c1aa6678a71afa7a4a69a1a9bd280ae7 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 10 Sep 2017 12:24:55 +0200 Subject: [PATCH 0435/2308] Mention Scheme in README, add instructions --- README.md | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4cc1e729e6..913ecdec2c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 68 languages: +Mal is implemented in 69 languages: * Ada * GNU awk @@ -68,6 +68,7 @@ Mal is implemented in 68 languages: * Ruby * Rust * Scala +* Scheme (R7RS) * Skew * Swift * Swift 3 @@ -863,6 +864,39 @@ sbt compile scala -classpath target/scala*/classes stepX_YYY ``` +### Scheme ### + +*The Scheme implementation was created by [Vasilij Schneidermann](https://github.com/wasamasa)* + +The Scheme implementation of mal has been tested with Chibi-Scheme +0.7.3, Kawa 2.4, Gauche 0.9.5, CHICKEN 4.11.0, Sagittarius 0.8.3, +Cyclone 0.6.3 (Git version) and Foment 0.4 (Git version). You should +be able to get it running on other conforming R7RS implementations +after figuring out how libraries are loaded and adjusting the +`Makefile` and `run` script accordingly. + +``` +cd scm +make symlinks +# chibi +SCM_MODE=chibi ./run +# kawa +make kawa +SCM_MODE=kawa ./run +# gauche +SCM_MODE=gauche ./run +# chicken +make chicken +SCM_MODE=chicken ./run +# sagittarius +SCM_MODE=sagittarius ./run +# cyclone +make cyclone +SCM_MODE=cyclone ./run +# foment +SCM_MODE=foment ./run +``` + ### Skew ### *The Skew implementation was created by [Dov Murik](https://github.com/dubek)* From 84dee477f382c55e5df190baab5be736e11afaa7 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 10 Sep 2017 13:21:45 +0200 Subject: [PATCH 0436/2308] Improve env lookup performance considerably --- scm/lib/env.sld | 8 ++++---- scm/lib/util.sld | 4 +++- scm/step8_macros.scm | 12 +++++------- scm/step9_try.scm | 12 +++++------- scm/stepA_mal.scm | 12 +++++------- 5 files changed, 22 insertions(+), 26 deletions(-) diff --git a/scm/lib/env.sld b/scm/lib/env.sld index 1804d78128..00e4f2a2c1 100644 --- a/scm/lib/env.sld +++ b/scm/lib/env.sld @@ -34,14 +34,14 @@ (define (env-find env key) (cond - ((alist-ref key (env-data env)) env) + ((alist-ref key (env-data env)) => identity) ((env-outer env) => (lambda (outer) (env-find outer key))) (else #f))) (define (env-get env key) - (let ((env (env-find env key))) - (if env - (alist-ref key (env-data env)) + (let ((value (env-find env key))) + (if value + value (error (str "'" key "' not found"))))) ) diff --git a/scm/lib/util.sld b/scm/lib/util.sld index 36ee354bd5..05c0bdb4d6 100644 --- a/scm/lib/util.sld +++ b/scm/lib/util.sld @@ -6,7 +6,7 @@ char->string list->alist alist->list alist-ref alist-map ->list car-safe cdr-safe contains? last butlast - readline + identity readline ;; HACK: cyclone doesn't have those error-object? error-object-message error-object-irritants) @@ -147,6 +147,8 @@ (loop tail (cons (car items) acc)) (reverse acc))))) +(define (identity x) x) + (define (readline prompt) (display prompt) (flush-output-port) diff --git a/scm/step8_macros.scm b/scm/step8_macros.scm index dc974f0fcf..8df319a3d2 100644 --- a/scm/step8_macros.scm +++ b/scm/step8_macros.scm @@ -52,13 +52,11 @@ (if (mal-instance-of? ast 'list) (let ((op (car-safe (mal-value ast)))) (if (mal-instance-of? op 'symbol) - (let* ((symbol (mal-value op)) - (env (env-find env symbol))) - (if env - (let ((x (env-get env symbol))) - (if (and (func? x) (func-macro? x)) - #t - #f)) + (let ((x (env-find env (mal-value op)))) + (if x + (if (and (func? x) (func-macro? x)) + #t + #f) #f)) #f)) #f)) diff --git a/scm/step9_try.scm b/scm/step9_try.scm index f828ccd175..e9c0d3975d 100644 --- a/scm/step9_try.scm +++ b/scm/step9_try.scm @@ -52,13 +52,11 @@ (if (mal-instance-of? ast 'list) (let ((op (car-safe (mal-value ast)))) (if (mal-instance-of? op 'symbol) - (let* ((symbol (mal-value op)) - (env (env-find env symbol))) - (if env - (let ((x (env-get env symbol))) - (if (and (func? x) (func-macro? x)) - #t - #f)) + (let ((x (env-find env (mal-value op)))) + (if x + (if (and (func? x) (func-macro? x)) + #t + #f) #f)) #f)) #f)) diff --git a/scm/stepA_mal.scm b/scm/stepA_mal.scm index fbfc6792db..d5cd0da879 100644 --- a/scm/stepA_mal.scm +++ b/scm/stepA_mal.scm @@ -52,13 +52,11 @@ (if (mal-instance-of? ast 'list) (let ((op (car-safe (mal-value ast)))) (if (mal-instance-of? op 'symbol) - (let* ((symbol (mal-value op)) - (env (env-find env symbol))) - (if env - (let ((x (env-get env symbol))) - (if (and (func? x) (func-macro? x)) - #t - #f)) + (let ((x (env-find env (mal-value op)))) + (if x + (if (and (func? x) (func-macro? x)) + #t + #f) #f)) #f)) #f)) From 49352c897b326a8c2b0864b388711d47c68cd658 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 10 Sep 2017 13:28:27 +0200 Subject: [PATCH 0437/2308] Fix build with cyclone --- scm/lib/core.sld | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scm/lib/core.sld b/scm/lib/core.sld index b3d815376c..7e62cfc1ff 100644 --- a/scm/lib/core.sld +++ b/scm/lib/core.sld @@ -8,7 +8,12 @@ (import (scheme time)) (import (scheme read)) (import (scheme eval)) -(import (scheme repl)) +;; HACK: cyclone doesn't implement environments yet, but its eval +;; behaves as if you were using the repl environment +(cond-expand + (cyclone) + (else + (import (scheme repl)))) (import (lib types)) (import (lib util)) @@ -124,7 +129,11 @@ (define (scm-eval input) (call-with-input-string input (lambda (port) - (->mal-object (eval (read port) (interaction-environment)))))) + (cond-expand + (cyclone + (->mal-object (eval (read port)))) + (else + (->mal-object (eval (read port) (interaction-environment)))))))) (define ns `((+ . ,(lambda (a b) (mal-number (+ (mal-value a) (mal-value b))))) From 4bb2ab37be6031fccd3a170e65004fad54a7e510 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 11 Sep 2017 16:45:53 -0500 Subject: [PATCH 0438/2308] Matlab: update to Octave 4.2.1 Make Octave the primary in the README description and add instructions for direct Octave execution. --- README.md | 13 +++++++------ matlab/Dockerfile | 7 ++++--- matlab/run | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 4cc1e729e6..4ea9ab1b82 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Mal is implemented in 68 languages: * Lua * GNU Make * mal itself -* MATLAB +* Matlab (GNU Octave and MATLAB) * [miniMAL](https://github.com/kanaka/miniMAL) * Nim * Object Pascal @@ -652,18 +652,19 @@ make ./stepX_YYY ``` -### MATLAB +### MatLab (GNU Octave and MATLAB) -The MATLAB implementation of mal has been tested with MATLAB version -R2014a on Linux. Note that MATLAB is a commercial product. It should -be fairly simple to support GNU Octave once it support classdef object -syntax. +The MatLab implementation has been tested with GNU Octave 4.2.1. +It has also been tested with MATLAB version R2014a on Linux. Note that +MATLAB is a commercial product. ``` cd matlab ./stepX_YYY +octave -q --no-gui --no-history --eval "stepX_YYY();quit;" matlab -nodisplay -nosplash -nodesktop -nojvm -r "stepX_YYY();quit;" # OR with command line arguments +octave -q --no-gui --no-history --eval "stepX_YYY('arg1','arg2');quit;" matlab -nodisplay -nosplash -nodesktop -nojvm -r "stepX_YYY('arg1','arg2');quit;" ``` diff --git a/matlab/Dockerfile b/matlab/Dockerfile index ea9afaa046..4ac3468975 100644 --- a/matlab/Dockerfile +++ b/matlab/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:xenial MAINTAINER Joel Martin ########################################################## @@ -22,8 +22,8 @@ WORKDIR /mal ########################################################## # Java and maven deps -RUN apt-get -y install openjdk-7-jdk -RUN apt-get -y install maven2 +RUN apt-get -y install openjdk-8-jdk +RUN apt-get -y install maven ENV MAVEN_OPTS -Duser.home=/mal # GNU Octave @@ -32,3 +32,4 @@ RUN apt-get -y install software-properties-common && \ apt-get -y update && \ apt-get -y install octave +ENV HOME /mal diff --git a/matlab/run b/matlab/run index 1cb6ecbbea..3d4f957efb 100755 --- a/matlab/run +++ b/matlab/run @@ -9,5 +9,5 @@ fi if [ -n "$USE_MATLAB" ] ; then exec matlab -nodisplay -nosplash -nodesktop -nojvm -r "${STEP:-stepA_mal}($args);quit;" else - exec octave --no-gui --no-history -q --traditional --eval "${STEP:-stepA_mal}($args);quit;" + exec octave -q --no-gui --no-history --eval "${STEP:-stepA_mal}($args);quit;" fi From a7b8df67156b4743bcad8d7a95b80164b56ae530 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 11 Sep 2017 23:54:50 +0200 Subject: [PATCH 0439/2308] Rename to scheme --- Makefile | 4 +- README.md | 2 +- {scm => scheme}/.gitignore | 0 {scm => scheme}/Makefile | 0 {scm => scheme}/lib/core.sld | 0 {scm => scheme}/lib/env.sld | 0 {scm => scheme}/lib/printer.sld | 0 {scm => scheme}/lib/reader.sld | 0 {scm => scheme}/lib/types.sld | 0 {scm => scheme}/lib/util.sld | 0 {scm => scheme}/run | 0 {scm => scheme}/step0_repl.scm | 0 {scm => scheme}/step1_read_print.scm | 0 {scm => scheme}/step2_eval.scm | 0 {scm => scheme}/step3_env.scm | 0 {scm => scheme}/step4_if_fn_do.scm | 0 {scm => scheme}/step5_tco.scm | 0 {scm => scheme}/step6_file.scm | 0 {scm => scheme}/step7_quote.scm | 0 {scm => scheme}/step8_macros.scm | 0 {scm => scheme}/step9_try.scm | 0 {scm => scheme}/stepA_mal.scm | 0 {scm => scheme}/tests/stepA_mal.mal | 0 scm/notes.rst | 186 --------------------------- 24 files changed, 3 insertions(+), 189 deletions(-) rename {scm => scheme}/.gitignore (100%) rename {scm => scheme}/Makefile (100%) rename {scm => scheme}/lib/core.sld (100%) rename {scm => scheme}/lib/env.sld (100%) rename {scm => scheme}/lib/printer.sld (100%) rename {scm => scheme}/lib/reader.sld (100%) rename {scm => scheme}/lib/types.sld (100%) rename {scm => scheme}/lib/util.sld (100%) rename {scm => scheme}/run (100%) rename {scm => scheme}/step0_repl.scm (100%) rename {scm => scheme}/step1_read_print.scm (100%) rename {scm => scheme}/step2_eval.scm (100%) rename {scm => scheme}/step3_env.scm (100%) rename {scm => scheme}/step4_if_fn_do.scm (100%) rename {scm => scheme}/step5_tco.scm (100%) rename {scm => scheme}/step6_file.scm (100%) rename {scm => scheme}/step7_quote.scm (100%) rename {scm => scheme}/step8_macros.scm (100%) rename {scm => scheme}/step9_try.scm (100%) rename {scm => scheme}/stepA_mal.scm (100%) rename {scm => scheme}/tests/stepA_mal.mal (100%) delete mode 100644 scm/notes.rst diff --git a/Makefile b/Makefile index ee6d07ab72..4b3ddc94b4 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ IMPLS = ada awk bash basic c d chuck clojure coffee common-lisp cpp crystal cs d erlang elisp elixir es6 factor forth fsharp go groovy gst guile haskell \ haxe io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ - python r racket rexx rpython ruby rust scala scm skew swift swift3 tcl \ + python r racket rexx rpython ruby rust scala scheme skew swift swift3 tcl \ ts vb vhdl vimscript livescript elm EXTENSION = .mal @@ -208,7 +208,7 @@ rpython_STEP_TO_PROG = rpython/$($(1)) ruby_STEP_TO_PROG = ruby/$($(1)).rb rust_STEP_TO_PROG = rust/target/release/$($(1)) scala_STEP_TO_PROG = scala/target/scala-2.11/classes/$($(1)).class -scm_STEP_TO_PROG = scm/$($(1)).scm +scheme_STEP_TO_PROG = scheme/$($(1)).scm skew_STEP_TO_PROG = skew/$($(1)).js swift_STEP_TO_PROG = swift/$($(1)) swift3_STEP_TO_PROG = swift3/$($(1)) diff --git a/README.md b/README.md index 913ecdec2c..ac9aa7d291 100644 --- a/README.md +++ b/README.md @@ -876,7 +876,7 @@ after figuring out how libraries are loaded and adjusting the `Makefile` and `run` script accordingly. ``` -cd scm +cd scheme make symlinks # chibi SCM_MODE=chibi ./run diff --git a/scm/.gitignore b/scheme/.gitignore similarity index 100% rename from scm/.gitignore rename to scheme/.gitignore diff --git a/scm/Makefile b/scheme/Makefile similarity index 100% rename from scm/Makefile rename to scheme/Makefile diff --git a/scm/lib/core.sld b/scheme/lib/core.sld similarity index 100% rename from scm/lib/core.sld rename to scheme/lib/core.sld diff --git a/scm/lib/env.sld b/scheme/lib/env.sld similarity index 100% rename from scm/lib/env.sld rename to scheme/lib/env.sld diff --git a/scm/lib/printer.sld b/scheme/lib/printer.sld similarity index 100% rename from scm/lib/printer.sld rename to scheme/lib/printer.sld diff --git a/scm/lib/reader.sld b/scheme/lib/reader.sld similarity index 100% rename from scm/lib/reader.sld rename to scheme/lib/reader.sld diff --git a/scm/lib/types.sld b/scheme/lib/types.sld similarity index 100% rename from scm/lib/types.sld rename to scheme/lib/types.sld diff --git a/scm/lib/util.sld b/scheme/lib/util.sld similarity index 100% rename from scm/lib/util.sld rename to scheme/lib/util.sld diff --git a/scm/run b/scheme/run similarity index 100% rename from scm/run rename to scheme/run diff --git a/scm/step0_repl.scm b/scheme/step0_repl.scm similarity index 100% rename from scm/step0_repl.scm rename to scheme/step0_repl.scm diff --git a/scm/step1_read_print.scm b/scheme/step1_read_print.scm similarity index 100% rename from scm/step1_read_print.scm rename to scheme/step1_read_print.scm diff --git a/scm/step2_eval.scm b/scheme/step2_eval.scm similarity index 100% rename from scm/step2_eval.scm rename to scheme/step2_eval.scm diff --git a/scm/step3_env.scm b/scheme/step3_env.scm similarity index 100% rename from scm/step3_env.scm rename to scheme/step3_env.scm diff --git a/scm/step4_if_fn_do.scm b/scheme/step4_if_fn_do.scm similarity index 100% rename from scm/step4_if_fn_do.scm rename to scheme/step4_if_fn_do.scm diff --git a/scm/step5_tco.scm b/scheme/step5_tco.scm similarity index 100% rename from scm/step5_tco.scm rename to scheme/step5_tco.scm diff --git a/scm/step6_file.scm b/scheme/step6_file.scm similarity index 100% rename from scm/step6_file.scm rename to scheme/step6_file.scm diff --git a/scm/step7_quote.scm b/scheme/step7_quote.scm similarity index 100% rename from scm/step7_quote.scm rename to scheme/step7_quote.scm diff --git a/scm/step8_macros.scm b/scheme/step8_macros.scm similarity index 100% rename from scm/step8_macros.scm rename to scheme/step8_macros.scm diff --git a/scm/step9_try.scm b/scheme/step9_try.scm similarity index 100% rename from scm/step9_try.scm rename to scheme/step9_try.scm diff --git a/scm/stepA_mal.scm b/scheme/stepA_mal.scm similarity index 100% rename from scm/stepA_mal.scm rename to scheme/stepA_mal.scm diff --git a/scm/tests/stepA_mal.mal b/scheme/tests/stepA_mal.mal similarity index 100% rename from scm/tests/stepA_mal.mal rename to scheme/tests/stepA_mal.mal diff --git a/scm/notes.rst b/scm/notes.rst deleted file mode 100644 index 4a45cc5762..0000000000 --- a/scm/notes.rst +++ /dev/null @@ -1,186 +0,0 @@ -Key -=== - -- Chibi: c -- Kawa: k -- CHICKEN: C -- Gauche: g -- Picrin: p -- Sagitarrius: s -- Cyclone: § -- Foment: f -- Guile: G -- Racket: r -- Larceny: l - -- Works: y -- Doesn't: n -- Sort of: x -- Unknown: ? - -Matrix -====== - -======================== === === === === === === === === === === === - Scheme implementations c k C g p s § f G r l -======================== === === === === === === === === === === === - R7RS support y y y y y y y y n ? x ------------------------- --- --- --- --- --- --- --- --- --- --- --- - Console I/O y y y y y y x n ? ? ? ------------------------- --- --- --- --- --- --- --- --- --- --- --- - Step #0 y y y y y y y n ? ? ? ------------------------- --- --- --- --- --- --- --- --- --- --- --- - Modules y y y y n y y y ? ? ? ------------------------- --- --- --- --- --- --- --- --- --- --- --- - Automatic library load y y x y n y x y ? ? ? ------------------------- --- --- --- --- --- --- --- --- --- --- --- - (scheme char) y y y y n y y y ? ? ? ------------------------- --- --- --- --- --- --- --- --- --- --- --- - Error objects y y y y ? y n y ? ? ? ------------------------- --- --- --- --- --- --- --- --- --- --- --- - Step #1 y y y y ? y n n ? ? ? ------------------------- --- --- --- --- --- --- --- --- --- --- --- - (srfi 1) y y y y y y y n ? ? ? ------------------------- --- --- --- --- --- --- --- --- --- --- --- - Step #4 y y y y ? y n n ? ? ? ------------------------- --- --- --- --- --- --- --- --- --- --- --- - Step #6 y y y y ? y n y ? ? ? -======================== === === === === === === === === === === === - -Notes -===== - -R7RS Support ------------- - -This is about whether I can write a script in R7RS Scheme and -successfully execute it with the implementation. Guile didn't pass -this test and the manual merely mentions it implements a few -R7RS-features (which is far from sufficient), Racket supposedly has -inofficial support for it, Larceny refuses loading up anything else -than a R7RS library. - -Console I/O ------------ - -In step 0 a REPL is implemented that echoes back user input and quits -on EOF (signalled by ``C-d``). Cyclone is weird here because its -``read-line`` procedure includes the trailing newline (fixed -upstream), Foment doesn't recognize EOF from console, but does so fine -with input redirection (as in ``(with-input-from-file "..." -read-line)``), a bug report for that is still pending. - -Step #0 -------- - -This is about whether the tests for step #0 have been passed -successfully. Foment fails this as it detects it's wired up to a tty -and probes for its cursor position before initializing its readline -implementation. This makes the test rig fail detecting a prompt. A -bug has been submitted upstream to rectify this. - -Modules -------- - -MAL requires a certain amount of modularization by splitting the -interpreter up into multiple files, for this R7RS libraries are a -natural fit. This is purely about whether the implementation allows -using code from a library file inside a script file. The only one not -passing this requirement is Picrin as it neither allows loading up -multiple files nor automatically loads up extra files. This leaves me -with just ``load`` as primitive, but this is not sufficient because I -need a relative load facility and the details on how its argument is -treated are implementation-specific. - -Automatic library load ----------------------- - -R7RS libraries are specified as a list of identifiers, commonly -translated to a nested path (for example ``(foo bar)`` translates to -``foo/bar.sld``) that is looked up in the include locations and loaded -automatically. CHICKEN translates them to ``foo.bar.scm`` and doesn't -load up source files automatically, instead you'll have to compile -libraries to importable shared libraries. Similarly, Cyclone only -loads up libraries after they've been compiled. Picrin doesn't do -anything in this regard, so only something like concatenating source -files (hello JS!) might work out. - -(scheme char) -------------- - -R7RS is split up into many base libraries, including one for char -procedures. This is necessary for tokenization of user input, -unfortunately Picrin doesn't implement this namespace at all and -throws parts of it into ``(scheme base)`` instead, without the -mandatory unicode support. This and the preceding failures are reason -enough for me to exclude it from this comparison. - -Error objects -------------- - -While there is an exception system, there is no need to use ``raise`` -when the more convenient ``error`` is available. Cyclone doesn't yet -support its helper procedures though, so I've written my own -replacements for them for its current internal representation (a list -of the message and the arguments). This will most certainly break -once it actually starts supporting them... - -Step #1 -------- - -This is about whether the tests for step #1 have been passed -successfully. Foment fails here as it sends ANSI escapes to the test -rig, but works again after a recent bugfix. Cyclone had a -show-stopping bug where the last symbol token had one garbage byte too -many, I've fixed this and another bug about the write representation -locally for now. - -(srfi 1) --------- - -The infamous list processing SRFI. It contains many goodies you'd -taken for granted in other programming languages, such as a procedure -for retrieving the last element of a list. All implementation except -Foment have it, so I just write my own list helpers as needed. No big -deal. - -Step #4 -------- - -Step #2 and #3 worked without any hitch, step #4 however exposes some -shortcuts I've taken. R7RS states for certain procedures that -evaluation order is unspecified to allow for optimizations for pure -functions, Cyclone makes use of this for ``map``. ``begin`` is -guaranteed to go from left to right, an explicit loop also works. My -clever trick of repurposing ``read`` and ``write`` for parsing and -serializing machine-readable strings backfired as R7RS only specifies -that backslashes and double-quotes need to be quoted, newlines may be -quoted but don't have to. For this reason I rolled my own serializer -that takes care of all of those characters. - -Step #6 -------- - -Step #5 wasn't a problem either, however this step introduced basic -file I/O. To read a complete file into a string I read a fixed size -string from the file port until EOF is returned and stuff each chunk -into the string port. This strategy yields an infinite loop with -Cyclone as it doesn't ever return EOF. I've handed in a PR to fix -this. - -Bug reports -=========== - -- https://github.com/justinethier/cyclone/issues/216 -- https://github.com/justinethier/cyclone/issues/217 -- https://github.com/justinethier/cyclone/issues/219 -- https://github.com/justinethier/cyclone/issues/220 -- https://github.com/justinethier/cyclone/issues/221 -- https://github.com/justinethier/cyclone/pull/222 -- https://github.com/justinethier/cyclone/issues/224 -- https://github.com/justinethier/cyclone/issues/225 -- https://github.com/leftmike/foment/issues/14 -- https://github.com/leftmike/foment/issues/15 -- https://github.com/leftmike/foment/issues/16 -- https://github.com/leftmike/foment/issues/17 -- https://github.com/leftmike/foment/issues/18 From 2d76e8776c189aaaf73120d71d24d4072c1c372f Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 11 Sep 2017 17:49:26 -0500 Subject: [PATCH 0440/2308] Generic _MODE setting. Add travis modes. For implementations that support different compile/run modes, the convention is not _MODE=. This is passed through to make/docker commands. Fix Makefiles and run scripts to listen to the respective *_MODE variables. Enable travis builds/tests for 4 Haxe modes and 2 python modes. --- .travis.yml | 11 ++++++++--- .travis_build.sh | 7 +++++-- .travis_test.sh | 5 ++++- Makefile | 25 +++++++++++++------------ README.md | 10 +++++----- clojure/Makefile | 6 +++--- clojure/run | 2 +- haxe/Makefile | 14 +++++++------- haxe/run | 4 ++-- matlab/run | 2 +- python/run | 2 +- 11 files changed, 50 insertions(+), 38 deletions(-) diff --git a/.travis.yml b/.travis.yml index fc6c6a7ffe..7d1b821db5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,8 @@ matrix: - {env: IMPL=coffee, services: [docker]} - {env: IMPL=cs, services: [docker]} - {env: IMPL=chuck, services: [docker]} - - {env: IMPL=clojure CLJ_MODE=clj, services: [docker]} - - {env: IMPL=clojure CLJ_MODE=cljs, services: [docker]} + - {env: IMPL=clojure clojure_MODE=clj, services: [docker]} + - {env: IMPL=clojure clojure_MODE=cljs, services: [docker]} - {env: IMPL=common-lisp, services: [docker]} - {env: IMPL=crystal, services: [docker]} - {env: IMPL=d, services: [docker]} @@ -33,6 +33,10 @@ matrix: - {env: IMPL=gst, services: [docker]} - {env: IMPL=guile, services: [docker]} - {env: IMPL=haskell, services: [docker]} + - {env: IMPL=haxe haxe_MODE=neko, services: [docker]} + - {env: IMPL=haxe haxe_MODE=python, services: [docker]} + - {env: IMPL=haxe haxe_MODE=cpp, services: [docker]} + - {env: IMPL=haxe haxe_MODE=js, services: [docker]} - {env: IMPL=haxe, services: [docker]} - {env: IMPL=io, services: [docker]} - {env: IMPL=java, services: [docker]} @@ -59,7 +63,8 @@ matrix: # - {env: IMPL=plsql, services: [docker]} - {env: IMPL=ps, services: [docker]} - {env: IMPL=powershell, services: [docker]} - - {env: IMPL=python, services: [docker]} + - {env: IMPL=python python_MODE=python2, services: [docker]} + - {env: IMPL=python python_MODE=python3, services: [docker]} - {env: IMPL=r, services: [docker]} - {env: IMPL=racket, services: [docker]} - {env: IMPL=rexx, services: [docker]} diff --git a/.travis_build.sh b/.travis_build.sh index 5adf810cee..60537127f6 100755 --- a/.travis_build.sh +++ b/.travis_build.sh @@ -4,6 +4,9 @@ set -ex BUILD_IMPL=${BUILD_IMPL:-${IMPL}} +mode_var=${MAL_IMPL}_MODE +mode_val=${!mode_var} + # If NO_DOCKER is blank then launch use a docker image, otherwise # use the Travis image/tools directly. if [ -z "${NO_DOCKER}" ]; then @@ -21,9 +24,9 @@ if [ -z "${NO_DOCKER}" ]; then make -C ${BUILD_IMPL} step9_try || true fi docker run -it -u $(id -u) -v `pwd`:/mal kanaka/mal-test-${img_impl} \ - make CLJ_MODE=${CLJ_MODE} \ + make ${mode_val:+${mode_var}=${mode_val}} \ -C ${BUILD_IMPL} else - make CLJ_MODE=${CLJ_MODE} \ + make ${mode_val:+${mode_var}=${mode_val}} \ -C ${BUILD_IMPL} fi diff --git a/.travis_test.sh b/.travis_test.sh index 0a3d95062d..c452324502 100755 --- a/.travis_test.sh +++ b/.travis_test.sh @@ -6,6 +6,9 @@ ACTION=${1} IMPL=${2} MAL_IMPL=${3:-js} +mode_var=${MAL_IMPL}_MODE +mode_val=${!mode_var} + echo "ACTION: ${ACTION}" echo "IMPL: ${IMPL}" echo "MAL_IMPL: ${MAL_IMPL}" @@ -23,7 +26,7 @@ fi ${MAKE} TEST_OPTS="--debug-file ../${ACTION}.err" \ MAL_IMPL=${MAL_IMPL} \ - CLJ_MODE=${CLJ_MODE} \ + ${mode_val:+${mode_var}=${mode_val}} \ ${ACTION}^${IMPL} # no failure so remove error log diff --git a/Makefile b/Makefile index 20a663629d..6f0ff1a894 100644 --- a/Makefile +++ b/Makefile @@ -44,12 +44,14 @@ all help: MAL_IMPL = js -PYTHON = python -USE_MATLAB = -# python, js, cpp, or neko are currently supported -HAXE_MODE = neko -# clj or cljs are currently supported (Clojure vs ClojureScript/lumo) -CLJ_MODE = clj +# clj or cljs (Clojure vs ClojureScript/lumo) +clojure_MODE = clj +# python, js, cpp, or neko +haxe_MODE = neko +# octave or matlab +matlab_MODE = octave +# python, python2 or python3 +python_MODE = python # Extra options to pass to runtest.py TEST_OPTS = @@ -159,7 +161,7 @@ basic_STEP_TO_PROG = basic/$($(1)).bas c_STEP_TO_PROG = c/$($(1)) d_STEP_TO_PROG = d/$($(1)) chuck_STEP_TO_PROG = chuck/$($(1)).ck -clojure_STEP_TO_PROG = $(clojure_STEP_TO_PROG_$(CLJ_MODE)) +clojure_STEP_TO_PROG = $(clojure_STEP_TO_PROG_$(clojure_MODE)) coffee_STEP_TO_PROG = coffee/$($(1)).coffee common-lisp_STEP_TO_PROG = common-lisp/$($(1)) cpp_STEP_TO_PROG = cpp/$($(1)) @@ -178,7 +180,7 @@ groovy_STEP_TO_PROG = groovy/$($(1)).groovy gst_STEP_TO_PROG = gst/$($(1)).st java_STEP_TO_PROG = java/target/classes/mal/$($(1)).class haskell_STEP_TO_PROG = haskell/$($(1)) -haxe_STEP_TO_PROG = $(haxe_STEP_TO_PROG_$(HAXE_MODE)) +haxe_STEP_TO_PROG = $(haxe_STEP_TO_PROG_$(haxe_MODE)) io_STEP_TO_PROG = io/$($(1)).io julia_STEP_TO_PROG = julia/$($(1)).jl js_STEP_TO_PROG = js/$($(1)).js @@ -242,7 +244,7 @@ get_build_prefix = $(strip $(if $(strip $(DOCKERIZE)),\ -it --rm -u $(shell id -u) \ -v $(dir $(abspath $(lastword $(MAKEFILE_LIST)))):/mal \ -w /mal/$(1) \ - $(if $(filter clojure,$(1)),-e CLJ_MODE=$(CLJ_MODE),) \ + $(if $(strip $($(1)_MODE)),-e $(1)_MODE=$($(1)_MODE),) \ $(if $(filter factor,$(1)),-e FACTOR_ROOTS=$(FACTOR_ROOTS),) \ $(call impl_to_image,$(1)) \ ,)) @@ -255,14 +257,13 @@ get_run_prefix = $(strip $(if $(strip $(DOCKERIZE)),\ -it --rm -u $(shell id -u) \ -v $(dir $(abspath $(lastword $(MAKEFILE_LIST)))):/mal \ -w /mal/$(call actual_impl,$(1)) \ - $(if $(filter clojure,$(1)),-e CLJ_MODE=$(CLJ_MODE),) \ - $(if $(filter haxe,$(1)),-e HAXE_MODE=$(HAXE_MODE),) \ + $(if $(strip $($(1)_MODE)),-e $(1)_MODE=$($(1)_MODE),) \ $(if $(filter factor,$(1)),-e FACTOR_ROOTS=$(FACTOR_ROOTS),) \ $(foreach env,$(3),-e $(env)) \ $(call impl_to_image,$(call actual_impl,$(1))) \ ,\ env STEP=$($2) MAL_IMPL=$(MAL_IMPL) \ - $(if $(filter haxe,$(1)),HAXE_MODE=$(HAXE_MODE),) \ + $(if $(strip $($(1)_MODE)),$(1)_MODE=$($(1)_MODE),) \ $(if $(filter factor,$(1)),FACTOR_ROOTS=$(FACTOR_ROOTS),) \ $(3))) diff --git a/README.md b/README.md index 4ea9ab1b82..1b10efd04b 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Mal is implemented in 68 languages: * C# * ChucK * Common Lisp -* Clojure +* Clojure (Clojure and ClojureScript) * CoffeeScript * Crystal * D @@ -35,7 +35,7 @@ Mal is implemented in 68 languages: * GNU Guile * GNU Smalltalk * Haskell -* Haxe +* Haxe (Neko, Python, C++ and JavaScript) * Io * Java * JavaScript ([Online Demo](http://kanaka.github.io/mal)) @@ -60,7 +60,7 @@ Mal is implemented in 68 languages: * PL/SQL (Oracle) * Postscript * PowerShell -* Python +* Python (2.X and 3.X) * RPython * R * Racket @@ -478,7 +478,7 @@ make ./stepX_YYY ``` -### Haxe +### Haxe (Neko, Python, C++ and JavaScript) The Haxe implementation of mal requires Haxe version 3.2 to compile. Four different Haxe targets are supported: Neko, Python, C++, and @@ -785,7 +785,7 @@ cd powershell powershell ./stepX_YYY.ps1 ``` -### Python (2.X or 3.X) +### Python (2.X and 3.X) ``` cd python diff --git a/clojure/Makefile b/clojure/Makefile index 4c468efd29..c77d1c2f27 100644 --- a/clojure/Makefile +++ b/clojure/Makefile @@ -1,11 +1,11 @@ -CLJ_MODE ?= clj -SOURCES_UTIL = src/mal/readline.$(CLJ_MODE) +clojure_MODE ?= clj +SOURCES_UTIL = src/mal/readline.$(clojure_MODE) SOURCES_BASE = $(SOURCES_UTIL) src/mal/printer.cljc SOURCES_LISP = src/mal/env.cljc src/mal/core.cljc src/mal/stepA_mal.cljc SRCS = $(SOURCES_BASE) src/mal/env.cljc src/mal/core.cljc SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -DEPS = $(if $(filter cljs,$(CLJ_MODE)),node_modules,deps) +DEPS = $(if $(filter cljs,$(clojure_MODE)),node_modules,deps) dist: mal.jar mal diff --git a/clojure/run b/clojure/run index 54c14f8014..f74eba6f25 100755 --- a/clojure/run +++ b/clojure/run @@ -1,7 +1,7 @@ #!/bin/bash export PATH=$PATH:$(dirname $0)/node_modules/.bin STEP=${STEP:-stepA_mal} -if [ "${CLJ_MODE}" = "cljs" ]; then +if [ "${clojure_MODE}" = "cljs" ]; then exec lumo -c $(dirname $0)/src -m mal.${STEP//_/-} "${@}" else exec java -jar $(dirname $0)/target/${STEP}.jar "${@}" diff --git a/haxe/Makefile b/haxe/Makefile index 30ad30088e..6af11e251f 100644 --- a/haxe/Makefile +++ b/haxe/Makefile @@ -8,7 +8,7 @@ STEPS = step0_repl step1_read_print step2_eval step3_env \ step4_if_fn_do step5_tco step6_file step7_quote \ step8_macros step9_try stepA_mal -HAXE_DIST_MODE = neko +haxe_MODE = neko dist_neko = mal.n dist_python = mal.py dist_cpp = cpp/mal @@ -38,18 +38,18 @@ mal.js: stepA_mal.js cp $< $@ -mal: $(dist_$(HAXE_DIST_MODE)) - $(if $(filter cpp,$(HAXE_DIST_MODE)),\ +mal: $(dist_$(haxe_MODE)) + $(if $(filter cpp,$(haxe_MODE)),\ cp $< $@;,\ - $(if $(filter neko,$(HAXE_DIST_MODE)),\ + $(if $(filter neko,$(haxe_MODE)),\ nekotools boot $<;,\ - $(if $(filter js,$(HAXE_DIST_MODE)),\ + $(if $(filter js,$(haxe_MODE)),\ echo "#!/usr/bin/env node" > $@;\ cat $< >> $@;,\ - $(if $(filter python,$(HAXE_DIST_MODE)),\ + $(if $(filter python,$(haxe_MODE)),\ echo "#!/usr/bin/env python3" > $@;\ cat $< >> $@;,\ - $(error Invalid HAXE_DIST_MODE: $(HAXE_DIST_MODE)))))) + $(error Invalid haxe_MODE: $(haxe_MODE)))))) chmod +x $@ diff --git a/haxe/run b/haxe/run index 4adaf760e9..6011b6c1e1 100755 --- a/haxe/run +++ b/haxe/run @@ -1,8 +1,8 @@ #!/bin/bash -case ${HAXE_MODE:-neko} in +case ${haxe_MODE:-neko} in neko) exec neko $(dirname $0)/${STEP:-stepA_mal}.n "${@}" ;; python) exec python3 $(dirname $0)/${STEP:-stepA_mal}.py "${@}" ;; js) exec node $(dirname $0)/${STEP:-stepA_mal}.js "${@}" ;; cpp) exec $(dirname $0)/cpp/${STEP:-stepA_mal} "${@}" ;; - *) echo "Invalid HAXE_MODE: ${HAXE_MODE}"; exit 2 ;; + *) echo "Invalid haxe_MODE: ${haxe_MODE}"; exit 2 ;; esac diff --git a/matlab/run b/matlab/run index 3d4f957efb..e3f209dea1 100755 --- a/matlab/run +++ b/matlab/run @@ -6,7 +6,7 @@ if [ "$#" -gt 0 ]; then args="$args,'$a'" done fi -if [ -n "$USE_MATLAB" ] ; then +if [ "$matlab_MODE" = "matlab" ] ; then exec matlab -nodisplay -nosplash -nodesktop -nojvm -r "${STEP:-stepA_mal}($args);quit;" else exec octave -q --no-gui --no-history --eval "${STEP:-stepA_mal}($args);quit;" diff --git a/python/run b/python/run index 09220ec06a..7549617761 100755 --- a/python/run +++ b/python/run @@ -1,2 +1,2 @@ #!/bin/bash -exec python $(dirname $0)/${STEP:-stepA_mal}.py "${@}" +exec ${python_MODE:-python} $(dirname $0)/${STEP:-stepA_mal}.py "${@}" From 49aa3e96ea63bec7688746f857291c02fd6c9e61 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 11 Sep 2017 18:10:24 -0500 Subject: [PATCH 0441/2308] Remove redundant Haxe travis test. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7d1b821db5..a107abdfae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,6 @@ matrix: - {env: IMPL=haxe haxe_MODE=python, services: [docker]} - {env: IMPL=haxe haxe_MODE=cpp, services: [docker]} - {env: IMPL=haxe haxe_MODE=js, services: [docker]} - - {env: IMPL=haxe, services: [docker]} - {env: IMPL=io, services: [docker]} - {env: IMPL=java, services: [docker]} - {env: IMPL=js, services: [docker]} From 85bb01d0bb0bff8afb5e5686f238dc880aef1ea4 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 11 Sep 2017 18:23:32 -0500 Subject: [PATCH 0442/2308] Fix conveyance of *_MODE variables to build rules. --- Makefile | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 6f0ff1a894..623a8f6795 100644 --- a/Makefile +++ b/Makefile @@ -239,7 +239,7 @@ actual_impl = $(if $(filter mal,$(1)),$(MAL_IMPL),$(1)) # Returns nothing if DOCKERIZE is not set, otherwise returns the # docker prefix necessary to run make within the docker environment # for this impl -get_build_prefix = $(strip $(if $(strip $(DOCKERIZE)),\ +get_build_command = $(strip $(if $(strip $(DOCKERIZE)),\ docker run \ -it --rm -u $(shell id -u) \ -v $(dir $(abspath $(lastword $(MAKEFILE_LIST)))):/mal \ @@ -247,7 +247,9 @@ get_build_prefix = $(strip $(if $(strip $(DOCKERIZE)),\ $(if $(strip $($(1)_MODE)),-e $(1)_MODE=$($(1)_MODE),) \ $(if $(filter factor,$(1)),-e FACTOR_ROOTS=$(FACTOR_ROOTS),) \ $(call impl_to_image,$(1)) \ - ,)) + $(MAKE) $(if $(strip $($(1)_MODE)),$(1)_MODE=$($(1)_MODE),) \ + ,\ + $(MAKE) $(if $(strip $($(1)_MODE)),$(1)_MODE=$($(1)_MODE),))) # Takes impl and step arguments # Returns a command prefix (docker command and environment variables) @@ -313,8 +315,8 @@ ALL_REPL = $(strip $(sort \ $(foreach i,$(DO_IMPLS),$(foreach s,$(STEPS),$(call $(i)_STEP_TO_PROG,$(s)))): $(foreach impl,$(word 1,$(subst /, ,$(@))),\ $(if $(DOCKERIZE), \ - $(call get_build_prefix,$(impl)) $(MAKE) $(patsubst $(impl)/%,%,$(@)), \ - $(MAKE) -C $(impl) $(subst $(impl)/,,$(@)))) + $(call get_build_command,$(impl)) $(patsubst $(impl)/%,%,$(@)), \ + $(call get_build_command,$(impl)) -C $(impl) $(subst $(impl)/,,$(@)))) # Allow IMPL, and IMPL^STEP .SECONDEXPANSION: @@ -441,10 +443,10 @@ $(2): @echo "----------------------------------------------"; \ $$(foreach impl,$$(word 2,$$(subst ^, ,$$(@))),\ $$(if $$(DOCKERIZE), \ - echo "Running: $$(call get_build_prefix,$$(impl))$$(MAKE) --no-print-directory $(1)"; \ - $$(call get_build_prefix,$$(impl))$$(MAKE) --no-print-directory $(1), \ - echo "Running: $$(MAKE) --no-print-directory -C $$(impl) $(1)"; \ - $$(MAKE) --no-print-directory -C $$(impl) $(1))) + echo "Running: $$(call get_build_command,$$(impl)) --no-print-directory $(1)"; \ + $$(call get_build_command,$$(impl)) --no-print-directory $(1), \ + echo "Running: $$(call get_build_command,$$(impl)) --no-print-directory -C $$(impl) $(1)"; \ + $$(call get_build_command,$$(impl)) --no-print-directory -C $$(impl) $(1))) endef recur_impls_ = $(filter-out $(foreach impl,$($(1)_EXCLUDES),$(1)^$(impl)),$(foreach impl,$(IMPLS),$(1)^$(impl))) From 6e8730c4eb88ada19ba28473a59519d0052647e6 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 11 Sep 2017 18:30:29 -0500 Subject: [PATCH 0443/2308] Fix IMPL variables in travis scripts. --- .travis_build.sh | 2 +- .travis_test.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis_build.sh b/.travis_build.sh index 60537127f6..d47a2939f5 100755 --- a/.travis_build.sh +++ b/.travis_build.sh @@ -4,7 +4,7 @@ set -ex BUILD_IMPL=${BUILD_IMPL:-${IMPL}} -mode_var=${MAL_IMPL}_MODE +mode_var=${IMPL}_MODE mode_val=${!mode_var} # If NO_DOCKER is blank then launch use a docker image, otherwise diff --git a/.travis_test.sh b/.travis_test.sh index c452324502..cafb005faa 100755 --- a/.travis_test.sh +++ b/.travis_test.sh @@ -6,7 +6,7 @@ ACTION=${1} IMPL=${2} MAL_IMPL=${3:-js} -mode_var=${MAL_IMPL}_MODE +mode_var=${IMPL}_MODE mode_val=${!mode_var} echo "ACTION: ${ACTION}" From 458852b23a5e7fd08ea1bd0e588a93cd21e3ba98 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 11 Sep 2017 23:28:00 -0500 Subject: [PATCH 0444/2308] Clojure, Haxe: all target based on MODE. --- clojure/Makefile | 2 +- haxe/Makefile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clojure/Makefile b/clojure/Makefile index c77d1c2f27..f994b5ea0a 100644 --- a/clojure/Makefile +++ b/clojure/Makefile @@ -7,7 +7,7 @@ SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) DEPS = $(if $(filter cljs,$(clojure_MODE)),node_modules,deps) -dist: mal.jar mal +dist: $(if $(filter cljs,$(clojure_MODE)),,mal.jar mal) deps: lein deps diff --git a/haxe/Makefile b/haxe/Makefile index 6af11e251f..0602c9edf7 100644 --- a/haxe/Makefile +++ b/haxe/Makefile @@ -8,12 +8,12 @@ STEPS = step0_repl step1_read_print step2_eval step3_env \ step4_if_fn_do step5_tco step6_file step7_quote \ step8_macros step9_try stepA_mal -haxe_MODE = neko +haxe_MODE ?= neko dist_neko = mal.n dist_python = mal.py dist_cpp = cpp/mal -all: all-neko all-python all-cpp all-js +all: all-$(haxe_MODE) all-neko: $(foreach x,$(STEPS),$(x).n) From df2cc0c51cfe09e5c17b29111cbdf3aec4000850 Mon Sep 17 00:00:00 2001 From: Poeticode Date: Tue, 12 Sep 2017 00:36:27 -0400 Subject: [PATCH 0445/2308] Rename mal's Error class to _Error --- php/core.php | 2 +- php/step9_try.php | 2 +- php/stepA_mal.php | 2 +- php/types.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/php/core.php b/php/core.php index 78f107e3a0..49c63246fa 100644 --- a/php/core.php +++ b/php/core.php @@ -6,7 +6,7 @@ require_once 'printer.php'; // Error/Exception functions -function mal_throw($obj) { throw new Error($obj); } +function mal_throw($obj) { throw new _Error($obj); } // String functions diff --git a/php/step9_try.php b/php/step9_try.php index 1f6e8b0612..7b03d4ecfe 100644 --- a/php/step9_try.php +++ b/php/step9_try.php @@ -119,7 +119,7 @@ function MAL_EVAL($ast, $env) { if ($a2[0]->value === "catch*") { try { return MAL_EVAL($a1, $env); - } catch (Error $e) { + } catch (_Error $e) { $catch_env = new Env($env, array($a2[1]), array($e->obj)); return MAL_EVAL($a2[2], $catch_env); diff --git a/php/stepA_mal.php b/php/stepA_mal.php index 2ac4c301a3..65652ef3e0 100644 --- a/php/stepA_mal.php +++ b/php/stepA_mal.php @@ -124,7 +124,7 @@ function MAL_EVAL($ast, $env) { if ($a2[0]->value === "catch*") { try { return MAL_EVAL($a1, $env); - } catch (Error $e) { + } catch (_Error $e) { $catch_env = new Env($env, array($a2[1]), array($e->obj)); return MAL_EVAL($a2[2], $catch_env); diff --git a/php/types.php b/php/types.php index d7dbdacf82..1279f88f8e 100644 --- a/php/types.php +++ b/php/types.php @@ -2,7 +2,7 @@ // Errors/Exceptions -class Error extends Exception { +class _Error extends Exception { public $obj = null; public function __construct($obj) { parent::__construct("Mal Error", 0, null); From 6c0968a6a4517b2db733eaa73968821d48372371 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 12 Sep 2017 09:44:51 +0200 Subject: [PATCH 0446/2308] Make use of scheme_MODE --- README.md | 16 ++++++++-------- scheme/Makefile | 11 +++++++++-- scheme/run | 4 ++-- scheme/stepA_mal.scm | 2 +- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ac9aa7d291..a192c23329 100644 --- a/README.md +++ b/README.md @@ -864,7 +864,7 @@ sbt compile scala -classpath target/scala*/classes stepX_YYY ``` -### Scheme ### +### Scheme (R7RS) ### *The Scheme implementation was created by [Vasilij Schneidermann](https://github.com/wasamasa)* @@ -879,22 +879,22 @@ after figuring out how libraries are loaded and adjusting the cd scheme make symlinks # chibi -SCM_MODE=chibi ./run +scheme_MODE=chibi ./run # kawa make kawa -SCM_MODE=kawa ./run +scheme_MODE=kawa ./run # gauche -SCM_MODE=gauche ./run +scheme_MODE=gauche ./run # chicken make chicken -SCM_MODE=chicken ./run +scheme_MODE=chicken ./run # sagittarius -SCM_MODE=sagittarius ./run +scheme_MODE=sagittarius ./run # cyclone make cyclone -SCM_MODE=cyclone ./run +scheme_MODE=cyclone ./run # foment -SCM_MODE=foment ./run +scheme_MODE=foment ./run ``` ### Skew ### diff --git a/scheme/Makefile b/scheme/Makefile index 09e507fe69..260c713d53 100644 --- a/scheme/Makefile +++ b/scheme/Makefile @@ -3,6 +3,7 @@ SOURCES_LISP = lib/env.sld lib/core.sld stepA_mal.scm SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) BINS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do step5_tco BINS += step6_file step7_quote step8_macros step9_try stepA_mal +scheme_MODE ?= chibi MKDIR = mkdir -p SYMLINK = ln -sfr @@ -15,9 +16,15 @@ CSC = CHICKEN_REPOSITORY=$(CURDIR)/eggs csc -O3 -R r7rs CSCSO = $(CSC) -sJ CYCLONE = cyclone -O2 -all: symlinks +DEPS = $(if $(filter kawa,$(scheme_MODE)),kawa,\ + $(if $(filter chicken,$(scheme_MODE)),chicken,\ + $(if $(filter cyclone,$(scheme_MODE)),cyclone))) -.PHONY: symlinks kawa chicken cyclone clean stats stats-lisp +all: symlinks build + +.PHONY: symlinks build kawa chicken cyclone clean stats stats-lisp + +build: $(DEPS) symlinks: $(MKDIR) eggs diff --git a/scheme/run b/scheme/run index 3130ec0c81..80498c456e 100755 --- a/scheme/run +++ b/scheme/run @@ -3,7 +3,7 @@ basedir=$(dirname $0) kawa=${KAWA_JAR:-/usr/share/kawa/lib/kawa.jar} step=${STEP:-stepA_mal} -case ${SCM_MODE:-chibi} in +case ${scheme_MODE:-chibi} in chibi) exec chibi-scheme -I$basedir $basedir/$step.scm "${@}" ;; kawa) exec java -cp $kawa:$basedir/out $step "${@}" ;; gauche) exec gosh -I$basedir $basedir/$step.scm "${@}" ;; @@ -11,5 +11,5 @@ case ${SCM_MODE:-chibi} in sagittarius) exec sagittarius -n -L$basedir $basedir/$step.scm "${@}" ;; cyclone) exec $basedir/$step "${@}" ;; foment) exec foment $basedir/$step.scm "${@}" ;; - *) echo "Invalid SCM_MODE: ${SCM_MODE}"; exit 2 ;; + *) echo "Invalid scheme_MODE: ${scheme_MODE}"; exit 2 ;; esac diff --git a/scheme/stepA_mal.scm b/scheme/stepA_mal.scm index d5cd0da879..f2da4e98f8 100644 --- a/scheme/stepA_mal.scm +++ b/scheme/stepA_mal.scm @@ -184,7 +184,7 @@ (env-set repl-env 'eval (lambda (ast) (EVAL ast repl-env))) (env-set repl-env '*ARGV* (mal-list (map mal-string (cdr-safe args)))) -(let ((scheme (or (get-environment-variable "SCM_MODE") "chibi"))) +(let ((scheme (or (get-environment-variable "scheme_MODE") "chibi"))) (env-set repl-env '*host-language* (mal-string (str "scheme (" scheme ")")))) (rep "(def! not (fn* (a) (if a false true)))") From edd7e9c2599d70f301c17f1daa19eb87760d459c Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 12 Sep 2017 08:57:01 -0500 Subject: [PATCH 0447/2308] Haxe: skip step5 when cpp MODE. --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 623a8f6795..125fc39e5c 100644 --- a/Makefile +++ b/Makefile @@ -124,6 +124,7 @@ test_EXCLUDES += test^matlab^step5 # never completes at 10,000 test_EXCLUDES += test^plpgsql^step5 # too slow for 10,000 test_EXCLUDES += test^plsql^step5 # too slow for 10,000 test_EXCLUDES += test^powershell^step5 # too slow for 10,000 +test_EXCLUDES += $(if $(filter cpp,$(haxe_MODE)),test^haxe^step5,) # cpp finishes 10,000, segfaults at 100,000 perf_EXCLUDES = mal # TODO: fix this From e68e138f27f7310cce8ff525fa013626f720dd51 Mon Sep 17 00:00:00 2001 From: Michael Pope Date: Tue, 12 Sep 2017 11:34:43 -0700 Subject: [PATCH 0448/2308] Update Elixir syntax & version to work with v1.5.x --- elixir/lib/mal/core.ex | 6 +++--- elixir/lib/mix/tasks/step0_repl.ex | 4 ++-- elixir/lib/mix/tasks/step1_read_print.ex | 4 ++-- elixir/lib/mix/tasks/step2_eval.ex | 6 +++--- elixir/lib/mix/tasks/step3_env.ex | 4 ++-- elixir/lib/mix/tasks/step4_if_fn_do.ex | 4 ++-- elixir/lib/mix/tasks/step5_tco.ex | 4 ++-- elixir/lib/mix/tasks/step6_file.ex | 4 ++-- elixir/lib/mix/tasks/step7_quote.ex | 4 ++-- elixir/lib/mix/tasks/step8_macros.ex | 4 ++-- elixir/lib/mix/tasks/step9_try.ex | 4 ++-- elixir/lib/mix/tasks/stepA_mal.ex | 4 ++-- elixir/mix.exs | 6 +++--- 13 files changed, 29 insertions(+), 29 deletions(-) diff --git a/elixir/lib/mal/core.ex b/elixir/lib/mal/core.ex index 1af6581d32..6bb67d202f 100644 --- a/elixir/lib/mal/core.ex +++ b/elixir/lib/mal/core.ex @@ -75,7 +75,7 @@ defmodule Mal.Core do def readline(prompt) do IO.write(:stdio, prompt) IO.read(:stdio, :line) - |> String.strip(?\n) + |> String.trim("\n") end defp convert_vector({type, ast, meta}) when type == :map do @@ -223,9 +223,9 @@ defmodule Mal.Core do end defp seq([nil]), do: nil - defp seq([{:list, [], meta}]), do: nil + defp seq([{:list, [], _meta}]), do: nil defp seq([{:list, ast, meta}]), do: {:list, ast, meta} - defp seq([{:vector, [], meta}]), do: nil + defp seq([{:vector, [], _meta}]), do: nil defp seq([{:vector, ast, meta}]), do: {:list, ast, meta} defp seq([""]), do: nil defp seq([s]), do: {:list, String.split(s, "", trim: true), nil} diff --git a/elixir/lib/mix/tasks/step0_repl.ex b/elixir/lib/mix/tasks/step0_repl.ex index 437e787021..4cd3efec2a 100644 --- a/elixir/lib/mix/tasks/step0_repl.ex +++ b/elixir/lib/mix/tasks/step0_repl.ex @@ -1,12 +1,12 @@ defmodule Mix.Tasks.Step0Repl do - def run(_), do: loop + def run(_), do: loop() defp loop do Mal.Core.readline("user> ") |> read_eval_print |> IO.puts - loop + loop() end defp read(input) do diff --git a/elixir/lib/mix/tasks/step1_read_print.ex b/elixir/lib/mix/tasks/step1_read_print.ex index 25399b08df..9569e68fb6 100644 --- a/elixir/lib/mix/tasks/step1_read_print.ex +++ b/elixir/lib/mix/tasks/step1_read_print.ex @@ -1,12 +1,12 @@ defmodule Mix.Tasks.Step1ReadPrint do - def run(_), do: loop + def run(_), do: loop() defp loop do Mal.Core.readline("user> ") |> read_eval_print |> IO.puts - loop + loop() end defp read(input) do diff --git a/elixir/lib/mix/tasks/step2_eval.ex b/elixir/lib/mix/tasks/step2_eval.ex index 0d130afce9..9d8bf555ad 100644 --- a/elixir/lib/mix/tasks/step2_eval.ex +++ b/elixir/lib/mix/tasks/step2_eval.ex @@ -6,7 +6,7 @@ defmodule Mix.Tasks.Step2Eval do "/" => &div/2 } - def run(_), do: loop + def run(_), do: loop() defp loop do IO.write(:stdio, "user> ") @@ -14,7 +14,7 @@ defmodule Mix.Tasks.Step2Eval do |> read_eval_print |> IO.puts - loop + loop() end defp eval_ast({:list, ast, meta}, env) when is_list(ast) do @@ -46,7 +46,7 @@ defmodule Mix.Tasks.Step2Eval do Mal.Reader.read_str(input) end - defp eval({:list, [], _} = empty_ast, env), do: empty_ast + defp eval({:list, [], _} = empty_ast, _env), do: empty_ast defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta) defp eval(ast, env), do: eval_ast(ast, env) diff --git a/elixir/lib/mix/tasks/step3_env.ex b/elixir/lib/mix/tasks/step3_env.ex index 8c49e50972..34f1903180 100644 --- a/elixir/lib/mix/tasks/step3_env.ex +++ b/elixir/lib/mix/tasks/step3_env.ex @@ -50,7 +50,7 @@ defmodule Mix.Tasks.Step3Env do Mal.Reader.read_str(input) end - defp eval_bindings([], _env), do: _env + defp eval_bindings([], env), do: env defp eval_bindings([{:symbol, key}, binding | tail], env) do evaluated = eval(binding, env) Mal.Env.set(env, key, evaluated) @@ -58,7 +58,7 @@ defmodule Mix.Tasks.Step3Env do end defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"}) - defp eval({:list, [], _} = empty_ast, env), do: empty_ast + defp eval({:list, [], _} = empty_ast, _env), do: empty_ast defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta) defp eval(ast, env), do: eval_ast(ast, env) diff --git a/elixir/lib/mix/tasks/step4_if_fn_do.ex b/elixir/lib/mix/tasks/step4_if_fn_do.ex index 25ef00ef81..77ee53a816 100644 --- a/elixir/lib/mix/tasks/step4_if_fn_do.ex +++ b/elixir/lib/mix/tasks/step4_if_fn_do.ex @@ -59,7 +59,7 @@ defmodule Mix.Tasks.Step4IfFnDo do Mal.Reader.read_str(input) end - defp eval_bindings([], _env), do: _env + defp eval_bindings([], env), do: env defp eval_bindings([{:symbol, key}, binding | tail], env) do evaluated = eval(binding, env) Mal.Env.set(env, key, evaluated) @@ -67,7 +67,7 @@ defmodule Mix.Tasks.Step4IfFnDo do end defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"}) - defp eval({:list, [], _} = empty_ast, env), do: empty_ast + defp eval({:list, [], _} = empty_ast, _env), do: empty_ast defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta) defp eval(ast, env), do: eval_ast(ast, env) diff --git a/elixir/lib/mix/tasks/step5_tco.ex b/elixir/lib/mix/tasks/step5_tco.ex index f5ca80fa14..68d1343ddc 100644 --- a/elixir/lib/mix/tasks/step5_tco.ex +++ b/elixir/lib/mix/tasks/step5_tco.ex @@ -59,7 +59,7 @@ defmodule Mix.Tasks.Step5Tco do Mal.Reader.read_str(input) end - defp eval_bindings([], _env), do: _env + defp eval_bindings([], env), do: env defp eval_bindings([{:symbol, key}, binding | tail], env) do evaluated = eval(binding, env) Mal.Env.set(env, key, evaluated) @@ -67,7 +67,7 @@ defmodule Mix.Tasks.Step5Tco do end defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"}) - defp eval({:list, [], _} = empty_ast, env), do: empty_ast + defp eval({:list, [], _} = empty_ast, _env), do: empty_ast defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta) defp eval(ast, env), do: eval_ast(ast, env) diff --git a/elixir/lib/mix/tasks/step6_file.ex b/elixir/lib/mix/tasks/step6_file.ex index 7ddcefae5f..da486b0f33 100644 --- a/elixir/lib/mix/tasks/step6_file.ex +++ b/elixir/lib/mix/tasks/step6_file.ex @@ -82,7 +82,7 @@ defmodule Mix.Tasks.Step6File do Mal.Reader.read_str(input) end - defp eval_bindings([], _env), do: _env + defp eval_bindings([], env), do: env defp eval_bindings([{:symbol, key}, binding | tail], env) do evaluated = eval(binding, env) Mal.Env.set(env, key, evaluated) @@ -90,7 +90,7 @@ defmodule Mix.Tasks.Step6File do end defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"}) - defp eval({:list, [], _} = empty_ast, env), do: empty_ast + defp eval({:list, [], _} = empty_ast, _env), do: empty_ast defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta) defp eval(ast, env), do: eval_ast(ast, env) diff --git a/elixir/lib/mix/tasks/step7_quote.ex b/elixir/lib/mix/tasks/step7_quote.ex index 566f6c54ba..bd615e8d5d 100644 --- a/elixir/lib/mix/tasks/step7_quote.ex +++ b/elixir/lib/mix/tasks/step7_quote.ex @@ -82,7 +82,7 @@ defmodule Mix.Tasks.Step7Quote do Mal.Reader.read_str(input) end - defp eval_bindings([], _env), do: _env + defp eval_bindings([], env), do: env defp eval_bindings([{:symbol, key}, binding | tail], env) do evaluated = eval(binding, env) Mal.Env.set(env, key, evaluated) @@ -114,7 +114,7 @@ defmodule Mix.Tasks.Step7Quote do end defp quasiquote(ast, _env), do: list([{:symbol, "quote"}, ast]) - defp eval({:list, [], _} = empty_ast, env), do: empty_ast + defp eval({:list, [], _} = empty_ast, _env), do: empty_ast defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta) defp eval(ast, env), do: eval_ast(ast, env) diff --git a/elixir/lib/mix/tasks/step8_macros.ex b/elixir/lib/mix/tasks/step8_macros.ex index 11cc5777b8..abb6e6df3b 100644 --- a/elixir/lib/mix/tasks/step8_macros.ex +++ b/elixir/lib/mix/tasks/step8_macros.ex @@ -105,7 +105,7 @@ defmodule Mix.Tasks.Step8Macros do Mal.Reader.read_str(input) end - defp eval_bindings([], _env), do: _env + defp eval_bindings([], env), do: env defp eval_bindings([{:symbol, key}, binding | tail], env) do evaluated = eval(binding, env) Mal.Env.set(env, key, evaluated) @@ -159,7 +159,7 @@ defmodule Mix.Tasks.Step8Macros do end end - defp eval({:list, [], _} = empty_ast, env), do: empty_ast + defp eval({:list, [], _} = empty_ast, _env), do: empty_ast defp eval({:list, _list, _meta} = ast, env) do case macroexpand(ast, env) do {:list, list, meta} -> eval_list(list, env, meta) diff --git a/elixir/lib/mix/tasks/step9_try.ex b/elixir/lib/mix/tasks/step9_try.ex index aa8f75ef38..036952a647 100644 --- a/elixir/lib/mix/tasks/step9_try.ex +++ b/elixir/lib/mix/tasks/step9_try.ex @@ -105,7 +105,7 @@ defmodule Mix.Tasks.Step9Try do Mal.Reader.read_str(input) end - defp eval_bindings([], _env), do: _env + defp eval_bindings([], env), do: env defp eval_bindings([{:symbol, key}, binding | tail], env) do evaluated = eval(binding, env) Mal.Env.set(env, key, evaluated) @@ -159,7 +159,7 @@ defmodule Mix.Tasks.Step9Try do end end - defp eval({:list, [], _} = empty_ast, env), do: empty_ast + defp eval({:list, [], _} = empty_ast, _env), do: empty_ast defp eval({:list, _list, _meta} = ast, env) do case macroexpand(ast, env) do {:list, list, meta} -> eval_list(list, env, meta) diff --git a/elixir/lib/mix/tasks/stepA_mal.ex b/elixir/lib/mix/tasks/stepA_mal.ex index 7ac36b8370..af7586eb13 100644 --- a/elixir/lib/mix/tasks/stepA_mal.ex +++ b/elixir/lib/mix/tasks/stepA_mal.ex @@ -124,7 +124,7 @@ defmodule Mix.Tasks.StepAMal do Mal.Reader.read_str(input) end - defp eval_bindings([], _env), do: _env + defp eval_bindings([], env), do: env defp eval_bindings([{:symbol, key}, binding | tail], env) do evaluated = eval(binding, env) Mal.Env.set(env, key, evaluated) @@ -178,7 +178,7 @@ defmodule Mix.Tasks.StepAMal do end end - defp eval({:list, [], _} = empty_ast, env), do: empty_ast + defp eval({:list, [], _} = empty_ast, _env), do: empty_ast defp eval({:list, _list, _meta} = ast, env) do case macroexpand(ast, env) do {:list, list, meta} -> eval_list(list, env, meta) diff --git a/elixir/mix.exs b/elixir/mix.exs index aba14e7b0c..5d768f6e0a 100644 --- a/elixir/mix.exs +++ b/elixir/mix.exs @@ -4,12 +4,12 @@ defmodule Mal.Mixfile do def project do [app: :mal, version: "0.0.1", - elixir: "~> 1.0", + elixir: "~> 1.5", build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, - deps: deps, + deps: deps(), default_task: "stepA_mal", - escript: escript] + escript: escript()] end def escript do From 50af689531733546019982ed76c4104dfd66e5b8 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 13 Sep 2017 05:15:21 +1000 Subject: [PATCH 0449/2308] Avoid premature sort --- python/mal_types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/mal_types.py b/python/mal_types.py index 57cbde2c7d..7c5e30c183 100644 --- a/python/mal_types.py +++ b/python/mal_types.py @@ -31,10 +31,10 @@ def _equal_Q(a, b): return True elif _hash_map_Q(a): akeys = a.keys() - akeys.sort() bkeys = b.keys() - bkeys.sort() if len(akeys) != len(bkeys): return False + akeys.sort() + bkeys.sort() for i in range(len(akeys)): if akeys[i] != bkeys[i]: return False if not _equal_Q(a[akeys[i]], b[bkeys[i]]): return False From 953dc52ae8789b838470bdd86712b4c109bd71ee Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 13 Sep 2017 12:00:34 +1000 Subject: [PATCH 0450/2308] Remove unnecessary lookup in 'dissoc' --- python/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/core.py b/python/core.py index d87f1e8da0..b52768dc43 100644 --- a/python/core.py +++ b/python/core.py @@ -36,7 +36,7 @@ def assoc(src_hm, *key_vals): def dissoc(src_hm, *keys): hm = copy.copy(src_hm) for key in keys: - if key in hm: del hm[key] + hm.pop(key, None) return hm def get(hm, key): From a77b8357de7f4ecb4d0902c575ed102355316d1c Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 13 Sep 2017 12:03:07 +1000 Subject: [PATCH 0451/2308] Remove unnecessary attr access in 'meta' --- python/core.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/core.py b/python/core.py index d87f1e8da0..66cafce56a 100644 --- a/python/core.py +++ b/python/core.py @@ -109,8 +109,7 @@ def with_meta(obj, meta): return new_obj def meta(obj): - if hasattr(obj, "__meta__"): return obj.__meta__ - else: return None + return getattr(obj, "__meta__", None) # Atoms functions From ebaca6bbe921701be67df71a901d911f2b7c1b5b Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 13 Sep 2017 12:05:04 +1000 Subject: [PATCH 0452/2308] Remove unnecessary lookup in 'get' --- python/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/core.py b/python/core.py index d87f1e8da0..ec8d180272 100644 --- a/python/core.py +++ b/python/core.py @@ -40,8 +40,8 @@ def dissoc(src_hm, *keys): return hm def get(hm, key): - if hm and key in hm: - return hm[key] + if hm is not None: + return hm.get(key) else: return None From d7d982e8b2f31ccc329ec5d780b91a17eaef895c Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Wed, 13 Sep 2017 09:57:43 +0200 Subject: [PATCH 0453/2308] Add Dockerfile and try finding sash --- scheme/Dockerfile | 59 +++++++++++++++++++++++++++++++++++++++++++++++ scheme/run | 8 ++++++- 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 scheme/Dockerfile diff --git a/scheme/Dockerfile b/scheme/Dockerfile new file mode 100644 index 0000000000..846f8e303d --- /dev/null +++ b/scheme/Dockerfile @@ -0,0 +1,59 @@ +FROM ubuntu:xenial +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# Prepackaged Scheme implementations +RUN apt-get -y install gauche chicken-bin + +# Chibi +RUN apt-get -y install bison gcc g++ flex +RUN cd /tmp && curl -Lo chibi-0.7.3.tar.gz https://github.com/ashinn/chibi-scheme/archive/0.7.3.tar.gz \ + && tar xvzf chibi-0.7.3.tar.gz && cd chibi-scheme-0.7.3 \ + && make && make install && rm -rf /tmp/chibi-* + +# Kawa +RUN apt-get -y install openjdk-8-jdk-headless groff +RUN cd /tmp && curl -O http://ftp.gnu.org/pub/gnu/kawa/kawa-2.4.tar.gz \ + && tar xvzf kawa-2.4.tar.gz && cd kawa-2.4 \ + && ./configure && make && make install && rm -rf /tmp/kawa-2.4* + +# Sagittarius +RUN apt-get -y install cmake libgc-dev zlib1g-dev libffi-dev +RUN cd /tmp && curl -LO https://bitbucket.org/ktakashi/sagittarius-scheme/downloads/sagittarius-0.8.3.tar.gz \ + && tar xvzf sagittarius-0.8.3.tar.gz && cd sagittarius-0.8.3 \ + && cmake . && make && make install && rm -rf /tmp/sagittarius-0.8.3* + +# Cyclone +RUN apt-get -y install git libtommath-dev +RUN cd /tmp && curl -O http://concurrencykit.org/releases/ck-0.6.0.tar.gz \ + && tar xvzf ck-0.6.0.tar.gz && cd ck-0.6.0 && ./configure PREFIX=/usr \ + && make all && make install && ldconfig && rm -rf /tmp/ck-0.6.0* +RUN cd /tmp && git clone https://github.com/justinethier/cyclone-bootstrap \ + && cd cyclone-bootstrap && make CFLAGS="-O2 -fPIC -rdynamic -Wall -Iinclude -L." \ + && make install && rm -rf /tmp/cyclone-bootstrap + +# Foment +RUN cd /tmp && git clone https://github.com/leftmike/foment \ + && cd foment/unix && make && cp release/foment /usr/bin/foment \ + && rm -rf /tmp/foment + +ENV HOME /mal diff --git a/scheme/run b/scheme/run index 80498c456e..c4fa69e7a3 100755 --- a/scheme/run +++ b/scheme/run @@ -3,12 +3,18 @@ basedir=$(dirname $0) kawa=${KAWA_JAR:-/usr/share/kawa/lib/kawa.jar} step=${STEP:-stepA_mal} +if [[ $(which sash 2>/dev/null) ]]; then + sagittarius=sash +elif [[ $(which sagittarius 2>/dev/null) ]]; then + sagittarius=sagittarius +fi + case ${scheme_MODE:-chibi} in chibi) exec chibi-scheme -I$basedir $basedir/$step.scm "${@}" ;; kawa) exec java -cp $kawa:$basedir/out $step "${@}" ;; gauche) exec gosh -I$basedir $basedir/$step.scm "${@}" ;; chicken) CHICKEN_REPOSITORY=$basedir/eggs exec $basedir/$step "${@}" ;; - sagittarius) exec sagittarius -n -L$basedir $basedir/$step.scm "${@}" ;; + sagittarius) exec $sagittarius -n -L$basedir $basedir/$step.scm "${@}" ;; cyclone) exec $basedir/$step "${@}" ;; foment) exec foment $basedir/$step.scm "${@}" ;; *) echo "Invalid scheme_MODE: ${scheme_MODE}"; exit 2 ;; From b58ce6c42f97c9b2d8b0cc173f3dc5245a8c05a7 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Wed, 13 Sep 2017 09:58:18 +0200 Subject: [PATCH 0454/2308] Add seven Scheme implementations to Dockerfile --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index a107abdfae..a62674a0a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -71,6 +71,13 @@ matrix: - {env: IMPL=ruby, services: [docker]} - {env: IMPL=rust, services: [docker]} - {env: IMPL=scala, services: [docker]} + - {env: IMPL=scheme scheme_MODE=chibi, services: [docker]} + - {env: IMPL=scheme scheme_MODE=kawa, services: [docker]} + - {env: IMPL=scheme scheme_MODE=gauche, services: [docker]} + - {env: IMPL=scheme scheme_MODE=chicken, services: [docker]} + - {env: IMPL=scheme scheme_MODE=sagittarius, services: [docker]} + - {env: IMPL=scheme scheme_MODE=cyclone, services: [docker]} + - {env: IMPL=scheme scheme_MODE=foment, services: [docker]} - {env: IMPL=skew, services: [docker]} - {env: IMPL=swift NO_DOCKER=1, os: osx, osx_image: xcode7} - {env: IMPL=swift3, services: [docker]} From fea8cfff34ed8a9da569cfba0a69cd82384b173e Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Wed, 13 Sep 2017 16:49:15 +0200 Subject: [PATCH 0455/2308] Fix Makefile --- Makefile | 12 +++- scheme/.gitignore | 2 +- scheme/Makefile | 171 ++++++++++++++++++++++------------------------ scheme/eggs/.keep | 0 4 files changed, 94 insertions(+), 91 deletions(-) create mode 100644 scheme/eggs/.keep diff --git a/Makefile b/Makefile index 236438e3f3..52768a6f6a 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,8 @@ haxe_MODE = neko matlab_MODE = octave # python, python2 or python3 python_MODE = python +# scheme (chibi, kawa, gauche, chicken, sagittarius, cyclone, foment) +scheme_MODE = scheme # Extra options to pass to runtest.py TEST_OPTS = @@ -143,6 +145,14 @@ haxe_STEP_TO_PROG_js = haxe/$($(1)).js clojure_STEP_TO_PROG_clj = clojure/target/$($(1)).jar clojure_STEP_TO_PROG_cljs = clojure/src/mal/$($(1)).cljc +scheme_STEP_TO_PROG_chibi = scheme/$($(1)).scm +scheme_STEP_TO_PROG_kawa = scheme/out/$($(1)).class +scheme_STEP_TO_PROG_gauche = scheme/$($(1)).scm +scheme_STEP_TO_PROG_chicken = scheme/$($(1)) +scheme_STEP_TO_PROG_sagittarius = scheme/$($(1)).scm +scheme_STEP_TO_PROG_cyclone = scheme/$($(1)) +scheme_STEP_TO_PROG_foment = scheme/$($(1)).scm + opt_DEFERRABLE = $(if $(strip $(DEFERRABLE)),$(if $(filter t true T True TRUE 1 y yes Yes YES,$(DEFERRABLE)),--deferrable,--no-deferrable),--no-deferrable) opt_OPTIONAL = $(if $(strip $(OPTIONAL)),$(if $(filter t true T True TRUE 1 y yes Yes YES,$(OPTIONAL)),--optional,--no-optional),--no-optional) @@ -210,7 +220,7 @@ rpython_STEP_TO_PROG = rpython/$($(1)) ruby_STEP_TO_PROG = ruby/$($(1)).rb rust_STEP_TO_PROG = rust/target/release/$($(1)) scala_STEP_TO_PROG = scala/target/scala-2.11/classes/$($(1)).class -scheme_STEP_TO_PROG = scheme/$($(1)).scm +scheme_STEP_TO_PROG = $(scheme_STEP_TO_PROG_$(scheme_MODE)) skew_STEP_TO_PROG = skew/$($(1)).js swift_STEP_TO_PROG = swift/$($(1)) swift3_STEP_TO_PROG = swift3/$($(1)) diff --git a/scheme/.gitignore b/scheme/.gitignore index 2cc78f64d7..31fc0e8966 100644 --- a/scheme/.gitignore +++ b/scheme/.gitignore @@ -8,4 +8,4 @@ lib.*.scm *.c *.o out/ -eggs/ +eggs/* \ No newline at end of file diff --git a/scheme/Makefile b/scheme/Makefile index 260c713d53..5e33a00ae4 100644 --- a/scheme/Makefile +++ b/scheme/Makefile @@ -1,110 +1,103 @@ -SOURCES_BASE = lib/reader.sld lib/printer.sld lib/types.sld +SOURCES_BASE = lib/util.sld lib/reader.sld lib/printer.sld lib/types.sld SOURCES_LISP = lib/env.sld lib/core.sld stepA_mal.scm SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) BINS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do step5_tco BINS += step6_file step7_quote step8_macros step9_try stepA_mal scheme_MODE ?= chibi +CLASSSTEPS = out/step0_repl.class out/step1_read_print.class \ + out/step3_env.class out/step4_if_fn_do.class out/step5_tco.class \ + out/step6_file.class out/step7_quote.class out/step8_macros.class \ + out/step9_try.class out/stepA_mal.class +STEPS = $(if $(filter kawa,$(scheme_MODE)),$(CLASSSTEPS),\ + $(if $(filter chicken,$(scheme_MODE)),$(BINS),\ + $(if $(filter cyclone,$(scheme_MODE)),$(BINS)))) + +KAWA_STEP1_DEPS = out/lib/util.class out/lib/reader.class \ + out/lib/printer.class out/lib/types.class +KAWA_STEP3_DEPS = $(KAWA_STEP1_DEPS) out/lib/env.class +KAWA_STEP4_DEPS = $(KAWA_STEP3_DEPS) out/lib/core.class +CHICKEN_STEP1_DEPS = eggs/lib.util.so eggs/lib.types.so \ + eggs/lib.reader.so eggs/lib.printer.so +CHICKEN_STEP3_DEPS = $(CHICKEN_STEP1_DEPS) eggs/lib.env.so +CHICKEN_STEP4_DEPS = $(CHICKEN_STEP3_DEPS) eggs/lib.core.so +CYCLONE_STEP1_DEPS = lib/util.so lib/reader.so lib/printer.so lib/types.so +CYCLONE_STEP3_DEPS = $(CYCLONE_STEP1_DEPS) lib/env.so +CYCLONE_STEP4_DEPS = $(CYCLONE_STEP3_DEPS) lib/core.so + +STEP1_DEPS = $(if $(filter kawa,$(scheme_MODE)),$(KAWA_STEP1_DEPS),\ + $(if $(filter chicken,$(scheme_MODE)),$(CHICKEN_STEP1_DEPS),\ + $(if $(filter cyclone,$(scheme_MODE)),$(CYCLONE_STEP1_DEPS)))) +STEP3_DEPS = $(if $(filter kawa,$(scheme_MODE)),$(KAWA_STEP3_DEPS),\ + $(if $(filter chicken,$(scheme_MODE)),$(CHICKEN_STEP3_DEPS),\ + $(if $(filter cyclone,$(scheme_MODE)),$(CYCLONE_STEP3_DEPS)))) +STEP4_DEPS = $(if $(filter kawa,$(scheme_MODE)),$(KAWA_STEP4_DEPS),\ + $(if $(filter chicken,$(scheme_MODE)),$(CHICKEN_STEP4_DEPS),\ + $(if $(filter cyclone,$(scheme_MODE)),$(CYCLONE_STEP4_DEPS)))) + +KAWALIB = kawa --r7rs --no-warn-unused -d out -C +KAWA = kawa --r7rs --no-warn-unused -d out --main -C +CHICKEN = CHICKEN_REPOSITORY=$(CURDIR)/eggs csc -O3 -R r7rs +CHICKENLIB = $(CHICKEN) -sJ +CYCLONELIB = cyclone -O2 +CYCLONE = $(CYCLONELIB) + +SCMLIB = $(if $(filter kawa,$(scheme_MODE)),$(KAWALIB),\ + $(if $(filter chicken,$(scheme_MODE)),$(CHICKENLIB),\ + $(if $(filter cyclone,$(scheme_MODE)),$(CYCLONELIB)))) +SCM = $(if $(filter kawa,$(scheme_MODE)),$(KAWA),\ + $(if $(filter chicken,$(scheme_MODE)),$(CHICKEN),\ + $(if $(filter cyclone,$(scheme_MODE)),$(CYCLONE)))) + MKDIR = mkdir -p SYMLINK = ln -sfr RM = rm -f RMR = rm -rf -KAWA = kawa --r7rs --no-warn-unused -d out -C -KAWAM = kawa --r7rs --no-warn-unused -d out --main -C -CSC = CHICKEN_REPOSITORY=$(CURDIR)/eggs csc -O3 -R r7rs -CSCSO = $(CSC) -sJ -CYCLONE = cyclone -O2 - -DEPS = $(if $(filter kawa,$(scheme_MODE)),kawa,\ - $(if $(filter chicken,$(scheme_MODE)),chicken,\ - $(if $(filter cyclone,$(scheme_MODE)),cyclone))) - -all: symlinks build - -.PHONY: symlinks build kawa chicken cyclone clean stats stats-lisp - -build: $(DEPS) - -symlinks: - $(MKDIR) eggs - $(SYMLINK) lib/util.sld lib/util.scm - $(SYMLINK) lib/util.sld eggs/lib.util.scm - $(SYMLINK) lib/types.sld lib/types.scm - $(SYMLINK) lib/types.sld eggs/lib.types.scm - $(SYMLINK) lib/reader.sld lib/reader.scm - $(SYMLINK) lib/reader.sld eggs/lib.reader.scm - $(SYMLINK) lib/printer.sld lib/printer.scm - $(SYMLINK) lib/printer.sld eggs/lib.printer.scm - $(SYMLINK) lib/env.sld lib/env.scm - $(SYMLINK) lib/env.sld eggs/lib.env.scm - $(SYMLINK) lib/core.sld lib/core.scm - $(SYMLINK) lib/core.sld eggs/lib.core.scm - -kawa: - $(KAWA) lib/util.scm - $(KAWA) lib/types.scm - $(KAWA) lib/reader.scm - $(KAWA) lib/printer.scm - $(KAWA) lib/env.scm - $(KAWA) lib/core.scm - $(KAWAM) step0_repl.scm - $(KAWAM) step1_read_print.scm - $(KAWAM) step2_eval.scm - $(KAWAM) step3_env.scm - $(KAWAM) step4_if_fn_do.scm - $(KAWAM) step5_tco.scm - $(KAWAM) step6_file.scm - $(KAWAM) step7_quote.scm - $(KAWAM) step8_macros.scm - $(KAWAM) step9_try.scm - $(KAWAM) stepA_mal.scm - -chicken: +all: $(STEPS) + +.PHONY: clean stats stats-lisp +.PRECIOUS: lib/%.scm eggs/lib.%.scm + +eggs/r7rs.so: chicken-install -init eggs CHICKEN_REPOSITORY=$(CURDIR)/eggs chicken-install r7rs - $(CSCSO) eggs/lib.util.scm - $(CSCSO) eggs/lib.types.scm - $(CSCSO) eggs/lib.reader.scm - $(CSCSO) eggs/lib.printer.scm - $(CSCSO) eggs/lib.env.scm - $(CSCSO) eggs/lib.core.scm - $(CSC) step0_repl.scm - $(CSC) step1_read_print.scm - $(CSC) step2_eval.scm - $(CSC) step3_env.scm - $(CSC) step4_if_fn_do.scm - $(CSC) step5_tco.scm - $(CSC) step6_file.scm - $(CSC) step7_quote.scm - $(CSC) step8_macros.scm - $(CSC) step9_try.scm - $(CSC) stepA_mal.scm - -cyclone: - $(CYCLONE) lib/util.sld - $(CYCLONE) lib/types.sld - $(CYCLONE) lib/reader.sld - $(CYCLONE) lib/printer.sld - $(CYCLONE) lib/env.sld - $(CYCLONE) lib/core.sld - $(CYCLONE) step0_repl.scm - $(CYCLONE) step1_read_print.scm - $(CYCLONE) step2_eval.scm - $(CYCLONE) step3_env.scm - $(CYCLONE) step4_if_fn_do.scm - $(CYCLONE) step5_tco.scm - $(CYCLONE) step6_file.scm - $(CYCLONE) step7_quote.scm - $(CYCLONE) step8_macros.scm - $(CYCLONE) step9_try.scm - $(CYCLONE) stepA_mal.scm + +lib/%.scm: lib/%.sld + $(SYMLINK) $< $@ + +eggs/lib.%.scm: lib/%.sld + $(SYMLINK) $< $@ + +out/lib/%.class: lib/%.scm + $(SCMLIB) $< + +out/%.class: %.scm + $(SCM) $< + +eggs/lib.%.so: eggs/lib.%.scm + $(SCMLIB) $< + +lib/%.so: lib/%.sld + $(SCMLIB) $< + +%: %.scm + $(SCM) $< + +out/step1_read_print.class out/step2_eval.class: $(STEP1_DEPS) +out/step3_env.class: $(STEP3_DEPS) +out/step4_if_fn_do.class out/step5_tco.class out/step6_file.class out/step7_quote.class out/step8_macros.class out/step9_try.class out/stepA_mal.class: $(STEP4_DEPS) + +step0_repl: $(if $(filter chicken,$(scheme_MODE)),eggs/r7rs.so,) +step1_read_print.scm step2_eval.scm: $(STEP1_DEPS) +step3_env.scm: $(STEP3_DEPS) +step4_if_fn_do.scm step5_tco.scm step6_file.scm step7_quote.scm step8_macros.scm step9_try.scm stepA_mal.scm: $(STEP4_DEPS) clean: $(RM) lib/*.scm lib/*.so lib/*.c lib/*.o lib/*.meta $(RM) lib.*.scm *.so *.c *.o $(BINS) + $(RM) eggs/* $(RMR) out - $(RMR) eggs stats: $(SOURCES) @wc $^ diff --git a/scheme/eggs/.keep b/scheme/eggs/.keep new file mode 100644 index 0000000000..e69de29bb2 From 2b9b1104579135d2281dbef51f1d5336f29584e5 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Wed, 13 Sep 2017 16:49:26 +0200 Subject: [PATCH 0456/2308] Fix eval test --- scheme/lib/core.sld | 3 ++- scheme/tests/stepA_mal.mal | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scheme/lib/core.sld b/scheme/lib/core.sld index 7e62cfc1ff..b6cfbe33fc 100644 --- a/scheme/lib/core.sld +++ b/scheme/lib/core.sld @@ -133,7 +133,8 @@ (cyclone (->mal-object (eval (read port)))) (else - (->mal-object (eval (read port) (interaction-environment)))))))) + (->mal-object (eval (read port) (environment '(scheme base) + '(scheme write))))))))) (define ns `((+ . ,(lambda (a b) (mal-number (+ (mal-value a) (mal-value b))))) diff --git a/scheme/tests/stepA_mal.mal b/scheme/tests/stepA_mal.mal index da3f7fe3bc..85eb6f06ad 100644 --- a/scheme/tests/stepA_mal.mal +++ b/scheme/tests/stepA_mal.mal @@ -12,6 +12,5 @@ (scm-eval "(map + '(1 2 3) '(4 5 6))") ;=>(5 7 9) -(scm-eval "(define (rot13 c) (integer->char (+ 65 (modulo (+ (- (char->integer c) 65) 13) 26))))") -(scm-eval "(string-map rot13 \"ZNY\")") +(scm-eval "(string-map (lambda (c) (integer->char (+ 65 (modulo (+ (- (char->integer c) 65) 13) 26)))) \"ZNY\")") ;=>"MAL" From f301fa6ce71cd62042c8a3c29cdb88e8bcb3c82e Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Wed, 13 Sep 2017 17:51:45 +0200 Subject: [PATCH 0457/2308] Fix execution of Kawa and Gauche in Docker --- scheme/Makefile | 13 ++++++++++--- scheme/run | 7 ++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/scheme/Makefile b/scheme/Makefile index 5e33a00ae4..2239360349 100644 --- a/scheme/Makefile +++ b/scheme/Makefile @@ -17,6 +17,9 @@ KAWA_STEP1_DEPS = out/lib/util.class out/lib/reader.class \ out/lib/printer.class out/lib/types.class KAWA_STEP3_DEPS = $(KAWA_STEP1_DEPS) out/lib/env.class KAWA_STEP4_DEPS = $(KAWA_STEP3_DEPS) out/lib/core.class +GAUCHE_STEP1_DEPS = lib/util.scm lib/reader.scm lib/printer.scm lib/types.scm +GAUCHE_STEP3_DEPS = $(GAUCHE_STEP1_DEPS) lib/env.scm +GAUCHE_STEP4_DEPS = $(GAUCHE_STEP3_DEPS) lib/core.scm CHICKEN_STEP1_DEPS = eggs/lib.util.so eggs/lib.types.so \ eggs/lib.reader.so eggs/lib.printer.so CHICKEN_STEP3_DEPS = $(CHICKEN_STEP1_DEPS) eggs/lib.env.so @@ -26,14 +29,17 @@ CYCLONE_STEP3_DEPS = $(CYCLONE_STEP1_DEPS) lib/env.so CYCLONE_STEP4_DEPS = $(CYCLONE_STEP3_DEPS) lib/core.so STEP1_DEPS = $(if $(filter kawa,$(scheme_MODE)),$(KAWA_STEP1_DEPS),\ + $(if $(filter gauche,$(scheme_MODE)),$(GAUCHE_STEP1_DEPS),\ $(if $(filter chicken,$(scheme_MODE)),$(CHICKEN_STEP1_DEPS),\ - $(if $(filter cyclone,$(scheme_MODE)),$(CYCLONE_STEP1_DEPS)))) + $(if $(filter cyclone,$(scheme_MODE)),$(CYCLONE_STEP1_DEPS))))) STEP3_DEPS = $(if $(filter kawa,$(scheme_MODE)),$(KAWA_STEP3_DEPS),\ + $(if $(filter gauche,$(scheme_MODE)),$(GAUCHE_STEP3_DEPS),\ $(if $(filter chicken,$(scheme_MODE)),$(CHICKEN_STEP3_DEPS),\ - $(if $(filter cyclone,$(scheme_MODE)),$(CYCLONE_STEP3_DEPS)))) + $(if $(filter cyclone,$(scheme_MODE)),$(CYCLONE_STEP3_DEPS))))) STEP4_DEPS = $(if $(filter kawa,$(scheme_MODE)),$(KAWA_STEP4_DEPS),\ + $(if $(filter gauche,$(scheme_MODE)),$(GAUCHE_STEP4_DEPS),\ $(if $(filter chicken,$(scheme_MODE)),$(CHICKEN_STEP4_DEPS),\ - $(if $(filter cyclone,$(scheme_MODE)),$(CYCLONE_STEP4_DEPS)))) + $(if $(filter cyclone,$(scheme_MODE)),$(CYCLONE_STEP4_DEPS))))) KAWALIB = kawa --r7rs --no-warn-unused -d out -C KAWA = kawa --r7rs --no-warn-unused -d out --main -C @@ -89,6 +95,7 @@ out/step3_env.class: $(STEP3_DEPS) out/step4_if_fn_do.class out/step5_tco.class out/step6_file.class out/step7_quote.class out/step8_macros.class out/step9_try.class out/stepA_mal.class: $(STEP4_DEPS) step0_repl: $(if $(filter chicken,$(scheme_MODE)),eggs/r7rs.so,) + step1_read_print.scm step2_eval.scm: $(STEP1_DEPS) step3_env.scm: $(STEP3_DEPS) step4_if_fn_do.scm step5_tco.scm step6_file.scm step7_quote.scm step8_macros.scm step9_try.scm stepA_mal.scm: $(STEP4_DEPS) diff --git a/scheme/run b/scheme/run index c4fa69e7a3..9614f8baea 100755 --- a/scheme/run +++ b/scheme/run @@ -1,8 +1,13 @@ #!/bin/bash basedir=$(dirname $0) -kawa=${KAWA_JAR:-/usr/share/kawa/lib/kawa.jar} step=${STEP:-stepA_mal} +if [[ -e /usr/share/kawa/lib/kawa.jar ]]; then + kawa=/usr/share/kawa/lib/kawa.jar +elif [[ -e /usr/local/share/kawa/lib/kawa.jar ]]; then + kawa=/usr/local/share/kawa/lib/kawa.jar +fi + if [[ $(which sash 2>/dev/null) ]]; then sagittarius=sash elif [[ $(which sagittarius 2>/dev/null) ]]; then From d09216a03c2e65dce5a3d4df1184d87ec81fd5e9 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Wed, 13 Sep 2017 20:14:50 +0200 Subject: [PATCH 0458/2308] Set default Scheme implementation for tests --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 52768a6f6a..70ea11732e 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ matlab_MODE = octave # python, python2 or python3 python_MODE = python # scheme (chibi, kawa, gauche, chicken, sagittarius, cyclone, foment) -scheme_MODE = scheme +scheme_MODE = chibi # Extra options to pass to runtest.py TEST_OPTS = From bcf3234d906fd9403ebc4f52fe4f850ff3a8fd19 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Wed, 13 Sep 2017 21:25:05 +0200 Subject: [PATCH 0459/2308] Disable Foment due to inexplicable hangs --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a62674a0a4..bceeab73d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -77,7 +77,7 @@ matrix: - {env: IMPL=scheme scheme_MODE=chicken, services: [docker]} - {env: IMPL=scheme scheme_MODE=sagittarius, services: [docker]} - {env: IMPL=scheme scheme_MODE=cyclone, services: [docker]} - - {env: IMPL=scheme scheme_MODE=foment, services: [docker]} +# - {env: IMPL=scheme scheme_MODE=foment, services: [docker]} - {env: IMPL=skew, services: [docker]} - {env: IMPL=swift NO_DOCKER=1, os: osx, osx_image: xcode7} - {env: IMPL=swift3, services: [docker]} From 007e4bf842640898c7a63ae4e4484a54d511e30f Mon Sep 17 00:00:00 2001 From: Michael Pope Date: Wed, 13 Sep 2017 11:45:12 -0700 Subject: [PATCH 0460/2308] Update Dockerfile for Elixir --- elixir/Dockerfile | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/elixir/Dockerfile b/elixir/Dockerfile index 7f013a5928..758c9036a6 100644 --- a/elixir/Dockerfile +++ b/elixir/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:xenial MAINTAINER Joel Martin ########################################################## @@ -22,8 +22,9 @@ WORKDIR /mal ########################################################## # Elixir -RUN curl -O https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb \ - && dpkg -i erlang-solutions_1.0_all.deb -RUN apt-get -y update -RUN apt-get -y install elixir - +RUN apt-get install -y wget +RUN wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb +RUN dpkg -i erlang-solutions_1.0_all.deb +RUN apt-get update -y +RUN apt-get install -y esl-erlang +RUN apt-get install -y elixir From 5a5357b17ec00caeeb136edf23f549c990d231af Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 14 Sep 2017 23:36:25 -0500 Subject: [PATCH 0461/2308] Makefile: re-ordering. Drop extra SECONDEXPANSIONs --- Makefile | 80 +++++++++++++++++++++++++++----------------------------- 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/Makefile b/Makefile index 5049bf5f1d..38a189a180 100644 --- a/Makefile +++ b/Makefile @@ -66,19 +66,12 @@ REGRESS = DEFERRABLE=1 OPTIONAL=1 -# Extra implementation specific options to pass to runtest.py -logo_TEST_OPTS = --start-timeout 60 --test-timeout 120 -mal_TEST_OPTS = --start-timeout 60 --test-timeout 120 -miniMAL_TEST_OPTS = --start-timeout 60 --test-timeout 120 -plpgsql_TEST_OPTS = --start-timeout 60 --test-timeout 180 -plsql_TEST_OPTS = --start-timeout 120 --test-timeout 120 -perl6_TEST_OPTS = --test-timeout=60 - # Run target/rule within docker image for the implementation DOCKERIZE = + # -# Settings +# Implementation specific settings # IMPLS = ada awk bash basic c d chuck clojure coffee common-lisp cpp crystal cs dart \ @@ -134,8 +127,24 @@ dist_EXCLUDES += mal # TODO: still need to implement dist dist_EXCLUDES += guile io julia matlab swift + +# Extra options to pass to runtest.py +logo_TEST_OPTS = --start-timeout 60 --test-timeout 120 +mal_TEST_OPTS = --start-timeout 60 --test-timeout 120 +miniMAL_TEST_OPTS = --start-timeout 60 --test-timeout 120 +plpgsql_TEST_OPTS = --start-timeout 60 --test-timeout 180 +plsql_TEST_OPTS = --start-timeout 120 --test-timeout 120 +perl6_TEST_OPTS = --test-timeout=60 +vimscript_TEST_OPTS = --test-timeout 30 +ifeq ($(MAL_IMPL),vimscript) +mal_TEST_OPTS = --start-timeout 60 --test-timeout 180 +else ifeq ($(MAL_IMPL),powershell) +mal_TEST_OPTS = --start-timeout 60 --test-timeout 180 +endif + + # -# Utility functions +# Implementation specific utility functions # haxe_STEP_TO_PROG_neko = haxe/$($(1)).n @@ -154,16 +163,6 @@ scheme_STEP_TO_PROG_sagittarius = scheme/$($(1)).scm scheme_STEP_TO_PROG_cyclone = scheme/$($(1)) scheme_STEP_TO_PROG_foment = scheme/$($(1)).scm -opt_DEFERRABLE = $(if $(strip $(DEFERRABLE)),$(if $(filter t true T True TRUE 1 y yes Yes YES,$(DEFERRABLE)),--deferrable,--no-deferrable),--no-deferrable) -opt_OPTIONAL = $(if $(strip $(OPTIONAL)),$(if $(filter t true T True TRUE 1 y yes Yes YES,$(OPTIONAL)),--optional,--no-optional),--no-optional) - -# Return list of test files for a given step. If REGRESS is set then -# test files will include step 2 tests through tests for the step -# being tested. -STEP_TEST_FILES = $(strip $(wildcard \ - $(foreach s,$(if $(strip $(REGRESS)),$(regress_$(2)),$(2)),\ - $(1)/tests/$($(s))$(EXTENSION) tests/$($(s))$(EXTENSION)))) - # Map of step (e.g. "step8") to executable file for that step ada_STEP_TO_PROG = ada/$($(1)) awk_STEP_TO_PROG = awk/$($(1)).awk @@ -235,12 +234,26 @@ livescript_STEP_TO_PROG = livescript/$($(1)).js elm_STEP_TO_PROG = elm/$($(1)).js +# +# General settings and utility functions +# + # Needed some argument munging COMMA = , noop = SPACE = $(noop) $(noop) export FACTOR_ROOTS := . +opt_DEFERRABLE = $(if $(strip $(DEFERRABLE)),$(if $(filter t true T True TRUE 1 y yes Yes YES,$(DEFERRABLE)),--deferrable,--no-deferrable),--no-deferrable) +opt_OPTIONAL = $(if $(strip $(OPTIONAL)),$(if $(filter t true T True TRUE 1 y yes Yes YES,$(OPTIONAL)),--optional,--no-optional),--no-optional) + +# Return list of test files for a given step. If REGRESS is set then +# test files will include step 2 tests through tests for the step +# being tested. +STEP_TEST_FILES = $(strip $(wildcard \ + $(foreach s,$(if $(strip $(REGRESS)),$(regress_$(2)),$(2)),\ + $(1)/tests/$($(s))$(EXTENSION) tests/$($(s))$(EXTENSION)))) + # DOCKERIZE utility functions lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1)))))))))))))))))))))))))) impl_to_image = kanaka/mal-test-$(call lc,$(1)) @@ -290,13 +303,6 @@ get_runtest_cmd = $(call get_run_prefix,$(1),$(2),$(if $(filter cs fsharp tcl vb # Returns the runtest command prefix (with runtest options) for testing the given step get_argvtest_cmd = $(call get_run_prefix,$(1),$(2)) ../run_argv_test.sh -vimscript_TEST_OPTS = --test-timeout 30 -ifeq ($(MAL_IMPL),vimscript) -mal_TEST_OPTS = --start-timeout 60 --test-timeout 180 -else ifeq ($(MAL_IMPL),powershell) -mal_TEST_OPTS = --start-timeout 60 --test-timeout 180 -endif - # Derived lists STEPS = $(sort $(filter step%,$(.VARIABLES))) DO_IMPLS = $(filter-out $(SKIP_IMPLS),$(IMPLS)) @@ -316,10 +322,14 @@ ALL_REPL = $(strip $(sort \ $(foreach impl,$(DO_IMPLS),\ $(foreach step,$(STEPS),repl^$(impl)^$(step))))) + # # Build rules # +# Enable secondary expansion for all rules +.SECONDEXPANSION: + # Build a program in an implementation directory # Make sure we always try and build first because the dependencies are # encoded in the implementation Makefile not here @@ -331,10 +341,8 @@ $(foreach i,$(DO_IMPLS),$(foreach s,$(STEPS),$(call $(i)_STEP_TO_PROG,$(s)))): $(call get_build_command,$(impl)) -C $(impl) $(subst $(impl)/,,$(@)))) # Allow IMPL, and IMPL^STEP -.SECONDEXPANSION: $(DO_IMPLS): $$(foreach s,$$(STEPS),$$(call $$(@)_STEP_TO_PROG,$$(s))) -.SECONDEXPANSION: $(foreach i,$(DO_IMPLS),$(foreach s,$(STEPS),$(i)^$(s))): $$(call $$(word 1,$$(subst ^, ,$$(@)))_STEP_TO_PROG,$$(word 2,$$(subst ^, ,$$(@)))) @@ -342,7 +350,6 @@ $(foreach i,$(DO_IMPLS),$(foreach s,$(STEPS),$(i)^$(s))): $$(call $$(word 1,$$(s # Test rules # -.SECONDEXPANSION: $(ALL_TESTS): $$(call $$(word 2,$$(subst ^, ,$$(@)))_STEP_TO_PROG,$$(word 3,$$(subst ^, ,$$(@)))) @$(foreach impl,$(word 2,$(subst ^, ,$(@))),\ $(foreach step,$(word 3,$(subst ^, ,$(@))),\ @@ -364,10 +371,8 @@ $(ALL_TESTS): $$(call $$(word 2,$$(subst ^, ,$$(@)))_STEP_TO_PROG,$$(word 3,$$(s test: $(ALL_TESTS) tests: $(ALL_TESTS) -.SECONDEXPANSION: $(IMPL_TESTS): $$(filter $$@^%,$$(ALL_TESTS)) -.SECONDEXPANSION: $(STEP_TESTS): $$(foreach step,$$(subst test^,,$$@),$$(filter %^$$(step),$$(ALL_TESTS))) @@ -377,7 +382,6 @@ $(STEP_TESTS): $$(foreach step,$$(subst test^,,$$@),$$(filter %^$$(step),$$(ALL_ dist: $(IMPL_DIST) -.SECONDEXPANSION: $(IMPL_DIST): @echo "----------------------------------------------"; \ $(foreach impl,$(word 2,$(subst ^, ,$(@))),\ @@ -391,7 +395,6 @@ $(IMPL_DIST): docker-build: $(DOCKER_BUILD) -.SECONDEXPANSION: $(DOCKER_BUILD): echo "----------------------------------------------"; \ $(foreach impl,$(word 2,$(subst ^, ,$(@))),\ @@ -405,7 +408,6 @@ $(DOCKER_BUILD): perf: $(IMPL_PERF) -.SECONDEXPANSION: $(IMPL_PERF): @echo "----------------------------------------------"; \ $(foreach impl,$(word 2,$(subst ^, ,$(@))),\ @@ -423,23 +425,20 @@ $(IMPL_PERF): # REPL invocation rules # -.SECONDEXPANSION: $(ALL_REPL): $$(call $$(word 2,$$(subst ^, ,$$(@)))_STEP_TO_PROG,$$(word 3,$$(subst ^, ,$$(@)))) @$(foreach impl,$(word 2,$(subst ^, ,$(@))),\ $(foreach step,$(word 3,$(subst ^, ,$(@))),\ cd $(if $(filter mal,$(impl)),$(MAL_IMPL),$(impl)); \ echo 'REPL implementation $(impl), step file: $+'; \ - echo 'Running: $(call get_run_prefix,$(impl),$(step)) ../$(impl)/run'; \ - $(call get_run_prefix,$(impl),$(step)) ../$(impl)/run;)) + echo 'Running: $(call get_run_prefix,$(impl),$(step)) ../$(impl)/run $(RUN_ARGS)'; \ + $(call get_run_prefix,$(impl),$(step)) ../$(impl)/run $(RUN_ARGS);)) # Allow repl^IMPL^STEP and repl^IMPL (which starts REPL of stepA) -.SECONDEXPANSION: $(IMPL_REPL): $$@^stepA # # Utility functions # -.SECONDEXPANSION: print-%: @echo "$($(*))" @@ -450,7 +449,6 @@ print-%: define recur_template .PHONY: $(1) $(1): $(2) -.SECONDEXPANSION: $(2): @echo "----------------------------------------------"; \ $$(foreach impl,$$(word 2,$$(subst ^, ,$$(@))),\ From 48bd82ba6ca545fa42f5b983d965ba2d96f55dac Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 14 Sep 2017 23:37:26 -0500 Subject: [PATCH 0462/2308] Makefile: add docker-shell target(s). --- Makefile | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 38a189a180..c446e19e44 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,8 @@ all help: @echo @echo 'make "docker-build^IMPL" # build docker image for IMPL' @echo + @echo 'make "docker-shell^IMPL" # start bash shell in docker image for IMPL' + @echo # # Command line settings @@ -276,10 +278,10 @@ get_build_command = $(strip $(if $(strip $(DOCKERIZE)),\ ,\ $(MAKE) $(if $(strip $($(1)_MODE)),$(1)_MODE=$($(1)_MODE),))) -# Takes impl and step arguments +# Takes impl and step args. Optional env vars and dockerize args # Returns a command prefix (docker command and environment variables) # necessary to launch the given impl and step -get_run_prefix = $(strip $(if $(strip $(DOCKERIZE)),\ +get_run_prefix = $(strip $(if $(strip $(DOCKERIZE) $(4)),\ docker run -e STEP=$($2) -e MAL_IMPL=$(MAL_IMPL) \ -it --rm -u $(shell id -u) \ -v $(dir $(abspath $(lastword $(MAKEFILE_LIST)))):/mal \ @@ -315,6 +317,8 @@ ALL_TESTS = $(filter-out $(test_EXCLUDES),\ DOCKER_BUILD = $(foreach impl,$(DO_IMPLS),docker-build^$(impl)) +DOCKER_SHELL = $(foreach impl,$(DO_IMPLS),docker-shell^$(impl)) + IMPL_PERF = $(foreach impl,$(filter-out $(perf_EXCLUDES),$(DO_IMPLS)),perf^$(impl)) IMPL_REPL = $(foreach impl,$(DO_IMPLS),repl^$(impl)) @@ -377,29 +381,26 @@ $(STEP_TESTS): $$(foreach step,$$(subst test^,,$$@),$$(filter %^$$(step),$$(ALL_ # -# Dist rules +# Docker build rules # -dist: $(IMPL_DIST) +docker-build: $(DOCKER_BUILD) -$(IMPL_DIST): +$(DOCKER_BUILD): @echo "----------------------------------------------"; \ $(foreach impl,$(word 2,$(subst ^, ,$(@))),\ - echo "Running: make -C $(impl) dist"; \ - $(MAKE) --no-print-directory -C $(impl) dist) - + echo "Running: docker build -t $(call impl_to_image,$(impl)) .:"; \ + cd $(impl) && docker build -t $(call impl_to_image,$(impl)) .) # -# Docker build rules +# Docker shell rules # -docker-build: $(DOCKER_BUILD) - -$(DOCKER_BUILD): - echo "----------------------------------------------"; \ +$(DOCKER_SHELL): + @echo "----------------------------------------------"; \ $(foreach impl,$(word 2,$(subst ^, ,$(@))),\ - echo "Running: docker build -t $(call impl_to_image,$(impl)) .:"; \ - cd $(impl) && docker build -t $(call impl_to_image,$(impl)) .) + echo "Running: $(call get_run_prefix,$(impl),stepA,,dockerize) bash"; \ + $(call get_run_prefix,$(impl),stepA,,dockerize) bash) # From 2a368ba6df62190298243b1d39d7bb11eef3f415 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 14 Sep 2017 23:39:00 -0500 Subject: [PATCH 0463/2308] JS, Rexx: fix step build deps. --- Makefile | 2 +- js/Makefile | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c446e19e44..b2c179d0f4 100644 --- a/Makefile +++ b/Makefile @@ -217,7 +217,7 @@ ps_STEP_TO_PROG = ps/$($(1)).ps python_STEP_TO_PROG = python/$($(1)).py r_STEP_TO_PROG = r/$($(1)).r racket_STEP_TO_PROG = racket/$($(1)).rkt -rexx_STEP_TO_PROG = rexx/$($(1)).rexx +rexx_STEP_TO_PROG = rexx/$($(1)).rexxpp rpython_STEP_TO_PROG = rpython/$($(1)) ruby_STEP_TO_PROG = ruby/$($(1)).rb rust_STEP_TO_PROG = rust/target/release/$($(1)) diff --git a/js/Makefile b/js/Makefile index 72e71348e4..8e4ad35bf1 100644 --- a/js/Makefile +++ b/js/Makefile @@ -6,6 +6,10 @@ SOURCES_LISP = env.js core.js stepA_mal.js SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) WEB_SOURCES = $(SOURCES:node_readline.js=jq_readline.js) +STEPS = step0_repl.js step1_read_print.js step2_eval.js step3_env.js \ + step4_if_fn_do.js step5_tco.js step6_file.js \ + step7_quote.js step8_macros.js step9_try.js stepA_mal.js + all: node_modules dist: mal.js mal web/mal.js @@ -13,6 +17,8 @@ dist: mal.js mal web/mal.js node_modules: npm install +$(STEPS): node_modules + mal.js: $(SOURCES) cat $+ | grep -v "= *require('./" >> $@ @@ -26,6 +32,7 @@ web/mal.js: $(WEB_SOURCES) clean: rm -f mal.js web/mal.js + rm -rf node_modules .PHONY: stats tests $(TESTS) From 115e430d02950571153b77e9774c777150d8806e Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 14 Sep 2017 23:50:15 -0500 Subject: [PATCH 0464/2308] Basic: QBasic fixes/enabling. Recursive includes. Enable QBasic console mode usage and fix bugs in newline handling differences with C64 basic (cbm). To enable console mode for QBasic programs, have basicpp.py prefix the output with the QB64 specific console activation variables/functions. One change to basicpp.py to make this change more straightfowards is recursive includes so that includes can appear in more than just the top level step files. This allows us to conditionally include the right readline implementation. For QBasic in the special console mode (instead of the default full-screen UI mode) we need to use the LINE INPUT command in order to read input. --- .travis.yml | 3 +- Makefile | 6 ++++ basic/Dockerfile | 12 ++++++++ basic/Makefile | 26 ++++++++++------ basic/basicpp.py | 56 ++++++++++++++++++++++------------- basic/core.in.bas | 3 +- basic/printer.in.bas | 3 +- basic/reader.in.bas | 3 +- basic/readline.in.bas | 33 ++------------------- basic/readline_char.in.bas | 31 +++++++++++++++++++ basic/readline_line.in.bas | 6 ++++ basic/run | 6 +++- basic/step0_repl.in.bas | 3 +- basic/step1_read_print.in.bas | 3 +- basic/step2_eval.in.bas | 3 +- basic/step3_env.in.bas | 3 +- basic/step4_if_fn_do.in.bas | 3 +- basic/step5_tco.in.bas | 3 +- basic/step6_file.in.bas | 3 +- basic/step7_quote.in.bas | 3 +- basic/step8_macros.in.bas | 3 +- basic/step9_try.in.bas | 3 +- basic/stepA_mal.in.bas | 4 +-- 23 files changed, 146 insertions(+), 76 deletions(-) create mode 100644 basic/readline_char.in.bas create mode 100644 basic/readline_line.in.bas diff --git a/.travis.yml b/.travis.yml index bceeab73d8..45fc93f27a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,8 @@ matrix: - {env: IMPL=ada, services: [docker]} - {env: IMPL=awk, services: [docker]} - {env: IMPL=bash, services: [docker]} - - {env: IMPL=basic, services: [docker]} + - {env: IMPL=basic basic_MODE=cbm, services: [docker]} + - {env: IMPL=basic basic_MODE=qbasic, services: [docker]} - {env: IMPL=c, services: [docker]} - {env: IMPL=cpp, services: [docker]} - {env: IMPL=coffee, services: [docker]} diff --git a/Makefile b/Makefile index b2c179d0f4..d89507d95d 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,8 @@ all help: MAL_IMPL = js +# cbm or qbasic +basic_MODE = cbm # clj or cljs (Clojure vs ClojureScript/lumo) clojure_MODE = clj # python, js, cpp, or neko @@ -149,6 +151,9 @@ endif # Implementation specific utility functions # +basic_STEP_TO_PROG_cbm = basic/$($(1)).bas +basic_STEP_TO_PROG_qbasic = basic/$($(1)) + haxe_STEP_TO_PROG_neko = haxe/$($(1)).n haxe_STEP_TO_PROG_python = haxe/$($(1)).py haxe_STEP_TO_PROG_cpp = haxe/cpp/$($(1)) @@ -170,6 +175,7 @@ ada_STEP_TO_PROG = ada/$($(1)) awk_STEP_TO_PROG = awk/$($(1)).awk bash_STEP_TO_PROG = bash/$($(1)).sh basic_STEP_TO_PROG = basic/$($(1)).bas +basic_STEP_TO_PROG = $(basic_STEP_TO_PROG_$(basic_MODE)) c_STEP_TO_PROG = c/$($(1)) d_STEP_TO_PROG = d/$($(1)) chuck_STEP_TO_PROG = chuck/$($(1)).ck diff --git a/basic/Dockerfile b/basic/Dockerfile index 95200cfb5d..928b1b1c62 100644 --- a/basic/Dockerfile +++ b/basic/Dockerfile @@ -32,3 +32,15 @@ RUN cd /tmp && \ cd .. && \ rm -r cbmbasic* +RUN apt-get install -y g++ mesa-common-dev libglu1-mesa-dev libasound2-dev wget +RUN cd /tmp && \ + curl -L http://www.qb64.net/release/official/2017_02_09__02_14_38-1.1-20170120.51/linux/qb64-1.1-20170120.51-lnx.tar.gz | tar xzf - && \ + cd qb64 && \ + find . -name '*.sh' -exec sed -i "s/\r//g" {} \; && \ + env EUID=1 ./setup_lnx.sh && \ + mkdir -p /usr/share/qb64 && \ + cp -a qb64 internal LICENSE programs source /usr/share/qb64/ && \ + echo '#!/bin/sh\ncd /usr/share/qb64\n./qb64 "${@}"' > /usr/bin/qb64 && \ + chmod +x /usr/bin/qb64 + + diff --git a/basic/Makefile b/basic/Makefile index 3373ea52b1..e2cd4bb3f5 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -1,5 +1,7 @@ -MODE = cbm -BASICPP_OPTS = --mode $(MODE) +basic_MODE = cbm +BASICPP_OPTS = --mode $(basic_MODE) + +QB64 = qb64 STEPS4_A = step4_if_fn_do.bas step5_tco.bas step6_file.bas \ step7_quote.bas step8_macros.bas step9_try.bas stepA_mal.bas @@ -7,19 +9,23 @@ STEPS3_A = step3_env.bas $(STEPS4_A) STEPS1_A = step1_read_print.bas step2_eval.bas $(STEPS3_A) STEPS0_A = step0_repl.bas $(STEPS1_A) +$(STEPS0_A): readline.in.bas readline_line.in.bas readline_char.in.bas +$(STEPS1_A): debug.in.bas mem.in.bas types.in.bas reader.in.bas printer.in.bas +$(STEPS3_A): env.in.bas +$(STEPS4_A): core.in.bas + + all: $(STEPS0_A) step%.bas: step%.in.bas ./basicpp.py $(BASICPP_OPTS) $< > $@ -$(STEPS0_A): readline.in.bas -$(STEPS1_A): debug.in.bas mem.in.bas types.in.bas reader.in.bas printer.in.bas -$(STEPS3_A): env.in.bas -$(STEPS4_A): core.in.bas - tests/%.bas: tests/%.in.bas ./basicpp.py $(BASICPP_OPTS) $< > $@ +# QBasic specific compilation rule +step%: step%.bas + $(QB64) -x $(abspath $<) -o $(abspath $@) # CBM/C64 image rules @@ -51,11 +57,13 @@ mal.d64: mal.prg .args.mal.prg core.mal.prg .PHONY: clean stats clean: - rm -f $(STEPS0_A) *.d64 *.prg + rm -f $(STEPS0_A) $(subst .bas,,$(STEPS0_A)) *.d64 *.prg qb64 + rm -rf ./internal SOURCES_LISP = env.in.bas core.in.bas stepA_mal.in.bas -SOURCES = readline.in.bas types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP) +SOURCES = readline.in.bas readline_line.in.bas readline_char.in.bas \ + types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP) stats: $(SOURCES) @wc $^ diff --git a/basic/basicpp.py b/basic/basicpp.py index fbe1c41f27..cb2f6223f8 100755 --- a/basic/basicpp.py +++ b/basic/basicpp.py @@ -13,6 +13,7 @@ def parse_args(): parser.add_argument('infiles', type=str, nargs='+', help='the Basic files to preprocess') parser.add_argument('--mode', choices=["cbm", "qbasic"], default="cbm") + parser.add_argument('--sub-mode', choices=["noui", "ui"], default="noui") parser.add_argument('--keep-rems', action='store_true', default=False, help='The type of REMs to keep (0 (none) -> 4 (all)') parser.add_argument('--keep-blank-lines', action='store_true', default=False, @@ -25,6 +26,7 @@ def parse_args(): help='Do not combine lines using the ":" separator') args = parser.parse_args() + args.full_mode = "%s-%s" % (args.mode, args.sub_mode) if args.keep_rems and not args.skip_combine_lines: debug("Option --keep-rems implies --skip-combine-lines ") args.skip_combine_lines = True @@ -36,30 +38,37 @@ def parse_args(): return args # pull in include files -def resolve_includes(orig_lines, keep_rems=0): +def resolve_includes(orig_lines, args): included = {} - lines = [] - for line in orig_lines: - m = re.match(r"^ *REM \$INCLUDE: '([^'\n]*)' *$", line) - if m and m.group(1) not in included: - f = m.group(1) - if f not in included: + lines = orig_lines[:] + position = 0 + while position < len(lines): + line = lines[position] + m = re.match(r"^(?:#([^ ]*) )? *REM \$INCLUDE: '([^'\n]*)' *$", line) + if m: + mode = m.group(1) + f = m.group(2) + if mode and mode != args.mode and mode != args.full_mode: + position += 1 + elif f not in included: ilines = [l.rstrip() for l in open(f).readlines()] - if keep_rems: lines.append("REM vvv BEGIN '%s' vvv" % f) - lines.extend(ilines) - if keep_rems: lines.append("REM ^^^ END '%s' ^^^" % f) + if args.keep_rems: lines.append("REM vvv BEGIN '%s' vvv" % f) + lines[position:position+1] = ilines + if args.keep_rems: lines.append("REM ^^^ END '%s' ^^^" % f) else: debug("Ignoring already included file: %s" % f) else: - lines.append(line) + position += 1 return lines -def resolve_mode(orig_lines, mode): +def resolve_mode(orig_lines, args): lines = [] for line in orig_lines: m = re.match(r"^ *#([^ \n]*) *([^\n]*)$", line) if m: - if m.group(1) == mode: + if m.group(1) == args.mode: + lines.append(m.group(2)) + elif m.group(1) == args.full_mode: lines.append(m.group(2)) continue lines.append(line) @@ -121,7 +130,7 @@ def misc_fixups(orig_lines): return text.split("\n") -def finalize(lines, args, mode): +def finalize(lines, args): labels_lines = {} lines_labels = {} call_index = {} @@ -158,7 +167,7 @@ def finalize(lines, args, mode): label = sub+"_"+str(call_index[sub]) # Replace the CALL with stack based GOTO - if mode == "cbm": + if args.mode == "cbm": lines.append("%s %sQ=%s:GOSUBPUSH_Q:GOTO%s" % ( lnum, prefix, call_index[sub], sub)) else: @@ -199,7 +208,7 @@ def finalize(lines, args, mode): index = call_index[cur_sub] ret_labels = [cur_sub+"_"+str(i) for i in range(1, index+1)] - if mode == "cbm": + if args.mode == "cbm": line = "%s GOSUBPOP_Q:ONQGOTO%s" % (lnum, ",".join(ret_labels)) else: line = "%s X=X-1:ON X%%(X+1) GOTO %s" % (lnum, ",".join(ret_labels)) @@ -213,7 +222,7 @@ def update_labels_lines(text, a, b): stext = text text = re.sub(r"(THEN *)%s\b" % a, r"\g<1>%s" % b, stext) #text = re.sub(r"(THEN)%s\b" % a, r"THEN%s" % b, stext) - if mode == "cbm": + if args.mode == "cbm": text = re.sub(r"ON *([^:\n]*) *GOTO *([^:\n]*)\b%s\b" % a, r"ON\g<1>GOTO\g<2>%s" % b, text) text = re.sub(r"ON *([^:\n]*) *GOSUB *([^:\n]*)\b%s\b" % a, r"ON\g<1>GOSUB\g<2>%s" % b, text) else: @@ -286,6 +295,13 @@ def renum(line): text = update_labels_lines(text, a, b) lines = text.split("\n") + # Force non-UI QBasic to use text console. LINE INPUT also needs + # to be used instead in character-by-character READLINE + if args.full_mode == "qbasic-noui": + # Add console program prefix for qb64/qbasic + lines = ["$CONSOLE", + "$SCREENHIDE", + "_DEST _CONSOLE"] + lines return lines @@ -300,10 +316,10 @@ def renum(line): debug("Original lines: %s" % len(lines)) # pull in include files - lines = resolve_includes(lines, keep_rems=args.keep_rems) + lines = resolve_includes(lines, args) debug("Lines after includes: %s" % len(lines)) - lines = resolve_mode(lines, mode=args.mode) + lines = resolve_mode(lines, args) debug("Lines after resolving mode specific lines: %s" % len(lines)) # drop blank lines @@ -325,7 +341,7 @@ def renum(line): lines = misc_fixups(lines) # number lines, drop/keep labels, combine lines - lines = finalize(lines, args, mode=args.mode) + lines = finalize(lines, args) debug("Lines after finalizing: %s" % len(lines)) print("\n".join(lines)) diff --git a/basic/core.in.bas b/basic/core.in.bas index 4cce38a68f..1d22fe2863 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -271,7 +271,8 @@ DO_FUNCTION: DO_SLURP_LOOP: C$="" RJ=1:GOSUB READ_FILE_CHAR - IF ASC(C$)=10 THEN R$=R$+CHR$(13) + #cbm IF ASC(C$)=10 THEN R$=R$+CHR$(13) + #qbasic IF ASC(C$)=10 THEN R$=R$+CHR$(10) IF (ASC(C$)<>10) AND (C$<>"") THEN R$=R$+C$ IF EZ>0 THEN GOTO DO_SLURP_DONE GOTO DO_SLURP_LOOP diff --git a/basic/printer.in.bas b/basic/printer.in.bas index 41e75822f1..55c6360b47 100644 --- a/basic/printer.in.bas +++ b/basic/printer.in.bas @@ -37,7 +37,8 @@ PR_STR: PR_STRING_READABLY: S1$="\":S2$="\\":GOSUB REPLACE: REM escape backslash " S1$=CHR$(34):S2$="\"+CHR$(34):GOSUB REPLACE: REM escape quotes " - S1$=CHR$(13):S2$="\n":GOSUB REPLACE: REM escape newlines + #cbm S1$=CHR$(13):S2$="\n":GOSUB REPLACE: REM escape newlines + #qbasic S1$=CHR$(10):S2$="\n":GOSUB REPLACE: REM escape newlines R$=CHR$(34)+R$+CHR$(34) RETURN PR_SYMBOL: diff --git a/basic/reader.in.bas b/basic/reader.in.bas index 955cabc4e2..daafb43ca9 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -169,7 +169,8 @@ SUB READ_FORM IF C<>34 THEN R=-1:ER=-1:E$="expected '"+CHR$(34)+"'":GOTO READ_FORM_RETURN R$=MID$(T$,2,LEN(T$)-2) S1$=CHR$(92)+CHR$(34):S2$=CHR$(34):GOSUB REPLACE: REM unescape quotes - S1$=CHR$(92)+"n":S2$=CHR$(13):GOSUB REPLACE: REM unescape newlines + #cbm S1$=CHR$(92)+"n":S2$=CHR$(13):GOSUB REPLACE: REM unescape newlines + #qbasic S1$=CHR$(92)+"n":S2$=CHR$(10):GOSUB REPLACE: REM unescape newlines S1$=CHR$(92)+CHR$(92):S2$=CHR$(92):GOSUB REPLACE: REM unescape backslashes REM intern string value B$=R$:T=4:GOSUB STRING diff --git a/basic/readline.in.bas b/basic/readline.in.bas index 75b7996343..67cbaa76ba 100644 --- a/basic/readline.in.bas +++ b/basic/readline.in.bas @@ -1,31 +1,4 @@ -REM READLINE(A$) -> R$ -READLINE: - EZ=0 - PRINT A$; - C$="":R$="":C=0 - READCH: - #cbm GET C$ - #qbasic C$=INKEY$ - IF C$="" THEN GOTO READCH - C=ASC(C$) - REM PRINT C - #qbasic IF ASC(C$)=8 THEN C=20:C$=CHR$(20) - IF C=4 OR C=0 THEN EZ=1:GOTO RL_DONE: REM EOF - IF C=127 OR C=20 THEN GOSUB RL_BACKSPACE - IF C=127 OR C=20 THEN GOTO READCH - IF (C<32 OR C>127) AND C<>13 THEN GOTO READCH - PRINT C$; - IF LEN(R$)<255 AND C$<>CHR$(13) THEN R$=R$+C$ - IF LEN(R$)<255 AND C$<>CHR$(13) THEN GOTO READCH - RL_DONE: - RETURN - REM Assumes R$ has input buffer - RL_BACKSPACE: - IF LEN(R$)=0 THEN RETURN - R$=LEFT$(R$,LEN(R$)-1) - #cbm PRINT CHR$(157)+" "+CHR$(157); - #qbasic LOCATE ,POS(0)-1 - #qbasic PRINT " "; - #qbasic LOCATE ,POS(0)-1 - RETURN +#cbm REM $INCLUDE: 'readline_char.in.bas' +#qbasic-ui REM $INCLUDE: 'readline_char.in.bas' +#qbasic-noui REM $INCLUDE: 'readline_line.in.bas' diff --git a/basic/readline_char.in.bas b/basic/readline_char.in.bas new file mode 100644 index 0000000000..75b7996343 --- /dev/null +++ b/basic/readline_char.in.bas @@ -0,0 +1,31 @@ +REM READLINE(A$) -> R$ +READLINE: + EZ=0 + PRINT A$; + C$="":R$="":C=0 + READCH: + #cbm GET C$ + #qbasic C$=INKEY$ + IF C$="" THEN GOTO READCH + C=ASC(C$) + REM PRINT C + #qbasic IF ASC(C$)=8 THEN C=20:C$=CHR$(20) + IF C=4 OR C=0 THEN EZ=1:GOTO RL_DONE: REM EOF + IF C=127 OR C=20 THEN GOSUB RL_BACKSPACE + IF C=127 OR C=20 THEN GOTO READCH + IF (C<32 OR C>127) AND C<>13 THEN GOTO READCH + PRINT C$; + IF LEN(R$)<255 AND C$<>CHR$(13) THEN R$=R$+C$ + IF LEN(R$)<255 AND C$<>CHR$(13) THEN GOTO READCH + RL_DONE: + RETURN + + REM Assumes R$ has input buffer + RL_BACKSPACE: + IF LEN(R$)=0 THEN RETURN + R$=LEFT$(R$,LEN(R$)-1) + #cbm PRINT CHR$(157)+" "+CHR$(157); + #qbasic LOCATE ,POS(0)-1 + #qbasic PRINT " "; + #qbasic LOCATE ,POS(0)-1 + RETURN diff --git a/basic/readline_line.in.bas b/basic/readline_line.in.bas new file mode 100644 index 0000000000..3d65f4172f --- /dev/null +++ b/basic/readline_line.in.bas @@ -0,0 +1,6 @@ +REM READLINE(A$) -> R$ +READLINE: + EZ=0 + PRINT A$ ; + LINE INPUT ; R$ + RETURN diff --git a/basic/run b/basic/run index 7fe8318cc2..df853cbc5d 100755 --- a/basic/run +++ b/basic/run @@ -1,4 +1,8 @@ #!/bin/bash cd $(dirname $0) (echo "(list $(for a in "${@}"; do echo -n "\"${a}\""; done))") > .args.mal -exec cbmbasic ${STEP:-stepA_mal}.bas "${@}" +case ${basic_MODE:-cbm} in + cbm) exec cbmbasic ${STEP:-stepA_mal}.bas "${@}" ;; + qbasic) exec ./${STEP:-stepA_mal} "${@}" ;; + *) echo "Invalid basic_MODE: ${basic_MODE}"; exit 2 ;; +esac diff --git a/basic/step0_repl.in.bas b/basic/step0_repl.in.bas index 3d05ae2069..a7c3e74b2b 100755 --- a/basic/step0_repl.in.bas +++ b/basic/step0_repl.in.bas @@ -39,5 +39,6 @@ MAIN: QUIT: REM GOSUB PR_MEMORY_SUMMARY_SMALL - END + #cbm END + #qbasic SYSTEM diff --git a/basic/step1_read_print.in.bas b/basic/step1_read_print.in.bas index 2e5d045a64..aa0c45f310 100755 --- a/basic/step1_read_print.in.bas +++ b/basic/step1_read_print.in.bas @@ -57,7 +57,8 @@ MAIN: QUIT: REM GOSUB PR_MEMORY_SUMMARY_SMALL - END + #cbm END + #qbasic SYSTEM PRINT_ERROR: PRINT "Error: "+E$ diff --git a/basic/step2_eval.in.bas b/basic/step2_eval.in.bas index 74ecaae76d..062f83cd3f 100755 --- a/basic/step2_eval.in.bas +++ b/basic/step2_eval.in.bas @@ -242,7 +242,8 @@ MAIN: QUIT: REM GOSUB PR_MEMORY_SUMMARY_SMALL - END + #cbm END + #qbasic SYSTEM PRINT_ERROR: PRINT "Error: "+E$ diff --git a/basic/step3_env.in.bas b/basic/step3_env.in.bas index 615fb2c02c..8340fd2732 100755 --- a/basic/step3_env.in.bas +++ b/basic/step3_env.in.bas @@ -303,7 +303,8 @@ MAIN: QUIT: REM GOSUB PR_MEMORY_SUMMARY_SMALL - END + #cbm END + #qbasic SYSTEM PRINT_ERROR: PRINT "Error: "+E$ diff --git a/basic/step4_if_fn_do.in.bas b/basic/step4_if_fn_do.in.bas index 3b2a8cf061..c3ca6eedcf 100755 --- a/basic/step4_if_fn_do.in.bas +++ b/basic/step4_if_fn_do.in.bas @@ -361,7 +361,8 @@ MAIN: QUIT: REM GOSUB PR_MEMORY_SUMMARY_SMALL - END + #cbm END + #qbasic SYSTEM PRINT_ERROR: PRINT "Error: "+E$ diff --git a/basic/step5_tco.in.bas b/basic/step5_tco.in.bas index f6f4a1dc83..184235c09c 100755 --- a/basic/step5_tco.in.bas +++ b/basic/step5_tco.in.bas @@ -385,7 +385,8 @@ MAIN: QUIT: REM GOSUB PR_MEMORY_SUMMARY_SMALL - END + #cbm END + #qbasic SYSTEM PRINT_ERROR: PRINT "Error: "+E$ diff --git a/basic/step6_file.in.bas b/basic/step6_file.in.bas index 86e664a85d..5d894d3c58 100755 --- a/basic/step6_file.in.bas +++ b/basic/step6_file.in.bas @@ -416,7 +416,8 @@ MAIN: QUIT: REM GOSUB PR_MEMORY_SUMMARY_SMALL - END + #cbm END + #qbasic SYSTEM PRINT_ERROR: PRINT "Error: "+E$ diff --git a/basic/step7_quote.in.bas b/basic/step7_quote.in.bas index 909757db51..75b91cc313 100755 --- a/basic/step7_quote.in.bas +++ b/basic/step7_quote.in.bas @@ -506,7 +506,8 @@ MAIN: QUIT: REM GOSUB PR_MEMORY_SUMMARY_SMALL - END + #cbm END + #qbasic SYSTEM PRINT_ERROR: PRINT "Error: "+E$ diff --git a/basic/step8_macros.in.bas b/basic/step8_macros.in.bas index 6ded89414e..c2c6f8edff 100755 --- a/basic/step8_macros.in.bas +++ b/basic/step8_macros.in.bas @@ -582,7 +582,8 @@ MAIN: QUIT: REM GOSUB PR_MEMORY_SUMMARY_SMALL - END + #cbm END + #qbasic SYSTEM PRINT_ERROR: PRINT "Error: "+E$ diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index e9b35adb0e..43ced642e2 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -614,7 +614,8 @@ MAIN: QUIT: REM GOSUB PR_MEMORY_SUMMARY_SMALL - END + #cbm END + #qbasic SYSTEM PRINT_ERROR: REM if the error is an object, then print and free it diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index 69b6485714..e4facc5a55 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -627,12 +627,12 @@ MAIN: QUIT: REM GOSUB PR_MEMORY_SUMMARY_SMALL - PRINT:GOSUB PR_MEMORY_SUMMARY_SMALL REM GOSUB PR_MEMORY_MAP REM P1=0:P2=ZI:GOSUB PR_MEMORY REM P1=D:GOSUB PR_OBJECT REM P1=ZK:GOSUB PR_OBJECT - END + #cbm END + #qbasic SYSTEM PRINT_ERROR: REM if the error is an object, then print and free it From e17aef048ef18f917d3294064c4b04be237ae648 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 15 Sep 2017 00:00:23 -0500 Subject: [PATCH 0465/2308] Misc fixes and TODO updates. - Truncate ruby and python stacktraces since they can get very long in certain cases causing problems. - Fix Clojure West example with proper escaping. --- .gitignore | 1 + docs/TODO | 39 +++++++++++++++++++----------------- examples/clojurewest2014.mal | 8 ++++---- process/guide.md | 1 + python/step5_tco.py | 2 +- ruby/step5_tco.rb | 2 +- runtest.py | 1 + 7 files changed, 30 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 279cf5c950..887b03c3f6 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ .sbt .npm .node-gyp +.elm */experiments */node_modules *.o diff --git a/docs/TODO b/docs/TODO index ef3b531da1..a273008f76 100644 --- a/docs/TODO +++ b/docs/TODO @@ -7,17 +7,13 @@ General: ./core.mal to ./lib directory - Finish guide.md - - Refactor ./run: - - java: build step, don't use mvn to run - - plpgsql: maybe combine wrap.sh and run - - vhdl: combine run_vhdl.sh and run - - vimscript: combine run_vimscript.sh and run + - mention that identifier names are suggested. some have run + into collisions with READ,EVAL,PRINT in case insensitive + languages All Implementations: - regular expression matching in runtest - add re (use in rep) everywhere and use that (to avoid printing) - - per impl tests for step5_tco, or at least a better way of - enabling/disabling/tweaking per implementation - fix stepA soft failures: lua matlab miniMAL perl racket Other ideas for All: @@ -55,15 +51,11 @@ Bash: C: - come up with better way to do 20 vararg code - - GC: use http://www.hboehm.info/gc/ C#: - accumulates line breaks with mal/clojurewest2014.mal - interop: http://www.ckode.dk/programming/eval-in-c-yes-its-possible/ -Clojure: - - fix "\/" exception in mal/clojurewest2014.mal - CoffeeScript: - make target to compile to JS @@ -77,9 +69,9 @@ Haskell: - immediately exits mal/clojurewest2014.mal ("\/" exception) Java: + - build step, don't use mvn in run script - Use gradle instead of mvn http://blog.paralleluniverse.co/2014/05/01/modern-java/ - - MAL formatting is a bit off with mal/clojurewest2014.mal Javascript: - interop: adopt techniques from miniMAL @@ -87,9 +79,6 @@ Javascript: Make: - allow '_' in make variable names - hash-map with space in key string - - Fix: make -f stepA_mal.mk ../mal/step6_file.mal - (slurp "../tests/incA.mal") - (read-string "(+ 2 3)") - errors should propagate up from within load-file - GC: expore using "undefined" directive in Make 3.82 @@ -97,9 +86,6 @@ Mal: - line numbers in errors - step5_tco -MATLAB: - - Port to support both GNU Octave and MATLAB - miniMAL: - figure out why {} literals are "static"/persistent @@ -107,6 +93,9 @@ ObjPascal: - verify that GC/reference counting works - fix comment by itself error at REPL +plpgsql: + - maybe combine wrap.sh and run + Perl: - fix metadata on native functions - fix extra line breaks at REPL @@ -116,6 +105,14 @@ Postscript: - fix blank line after comments - fix command line arg processing (doesn't run file specified) +Powershell: + - convert function with "abc_def" to "abc-def" + - remove extraneous return statements at end of functions + - remove unnecessary semi-colons + - use ArrayList instead of Array for performance + - new test to test Keys/keys as hash-map key + - test *? predicates with nil + R: - tracebacks in errors - fix running from different directory @@ -129,3 +126,9 @@ Rust: Scala - readline - fix exception when finished running something on command line + +VHDL: + - combine run_vhdl.sh and run + +vimscript: + - combine run_vimscript.sh and run diff --git a/examples/clojurewest2014.mal b/examples/clojurewest2014.mal index 2b5be9c665..98fcf17fef 100755 --- a/examples/clojurewest2014.mal +++ b/examples/clojurewest2014.mal @@ -25,10 +25,10 @@ (list (list (title2 " __ __ _ _") - (title2 "| \/ | / \ | |") - (title2 "| |\/| | / _ \ | | ") - (title2 "| | | |/ ___ \| |___ ") - (title2 "|_| |_/_/ \_\_____|")) + (title2 "| \\/ | / \\ | |") + (title2 "| |\\/| | / _ \\ | | ") + (title2 "| | | |/ ___ \\| |___ ") + (title2 "|_| |_/_/ \\_\\_____|")) (list (title "gherkin") "- a lisp1 written in bash4") diff --git a/process/guide.md b/process/guide.md index cd17969bf0..2da87827e5 100644 --- a/process/guide.md +++ b/process/guide.md @@ -1329,6 +1329,7 @@ implementation. Let us continue! * In the main program, use the `rep` function to define two new control structures macros. Here are the string arguments for `rep` to define these macros: + * TODO: note throw is not present until step9 * `cond`: "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" * `or`: "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" diff --git a/python/step5_tco.py b/python/step5_tco.py index da338d413f..8b714cef10 100644 --- a/python/step5_tco.py +++ b/python/step5_tco.py @@ -97,4 +97,4 @@ def REP(str): print(REP(line)) except reader.Blank: continue except Exception as e: - print("".join(traceback.format_exception(*sys.exc_info()))) + print("".join(traceback.format_exception(*sys.exc_info())[0:100])) diff --git a/ruby/step5_tco.rb b/ruby/step5_tco.rb index a6a82d46da..059bb55a6b 100644 --- a/ruby/step5_tco.rb +++ b/ruby/step5_tco.rb @@ -103,6 +103,6 @@ def PRINT(exp) puts REP[line] rescue Exception => e puts "Error: #{e}" - puts "\t#{e.backtrace.join("\n\t")}" + puts "\t#{e.backtrace[0..100].join("\n\t")}" end end diff --git a/runtest.py b/runtest.py index c3735adee4..1930b62c1d 100755 --- a/runtest.py +++ b/runtest.py @@ -127,6 +127,7 @@ def read_to_prompt(self, prompts, timeout): new_data = new_data.decode("utf-8") if IS_PY_3 else new_data #print("new_data: '%s'" % new_data) debug(new_data) + # Perform newline cleanup if self.no_pty: self.buf += new_data.replace("\n", "\r\n") else: From 47bcc4c0fac0fe6fea6cbb1dbf196a6881e8af53 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 15 Sep 2017 09:29:38 -0500 Subject: [PATCH 0466/2308] Basic: fix time-ms for QBasic. --- basic/core.in.bas | 3 ++- basic/mem.in.bas | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/basic/core.in.bas b/basic/core.in.bas index 1d22fe2863..d8b7a5025a 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -308,7 +308,8 @@ DO_FUNCTION: T=2:L=A1/B1:GOSUB ALLOC RETURN DO_TIME_MS: - T=2:L=INT((TI-BT)*16.667):GOSUB ALLOC + #cbm T=2:L=INT((TI-BT)*16.667):GOSUB ALLOC + #qbasic T=2:L=INT((TIMER(0.001)-BT#)*1000):GOSUB ALLOC RETURN DO_LIST: diff --git a/basic/mem.in.bas b/basic/mem.in.bas index a0065ec232..d4a6d54570 100644 --- a/basic/mem.in.bas +++ b/basic/mem.in.bas @@ -383,7 +383,8 @@ INIT_MEMORY: ZK=16 REM start of time clock - BT=TI + #cbm BT=TI + #qbasic BT#=TIMER(0.001) RETURN From 0198b7a230e48a6e06fed419c406833904244941 Mon Sep 17 00:00:00 2001 From: Sebastian Rasmussen Date: Fri, 15 Sep 2017 19:57:10 +0200 Subject: [PATCH 0467/2308] Fix a number of typos in documentation/comments. --- LICENSE | 2 +- basic/variables.txt | 2 +- chuck/notes.md | 4 ++-- cpp/README.md | 2 +- docs/FAQ.md | 2 +- docs/Hints.md | 4 ++-- docs/TODO | 2 +- docs/step_notes.txt | 2 +- examples/clojurewest2014.mal | 2 +- examples/memoize.mal | 4 ++-- examples/presentation.mal | 2 +- process/guide.md | 4 ++-- 12 files changed, 16 insertions(+), 16 deletions(-) diff --git a/LICENSE b/LICENSE index 3bfdf5f61e..88b2867725 100644 --- a/LICENSE +++ b/LICENSE @@ -4,7 +4,7 @@ Mal (make-a-lisp) is licensed under the MPL 2.0 (Mozilla Public License 2.0). The text of the MPL 2.0 license is included below and can be found at https://www.mozilla.org/MPL/2.0/ -Many of the implemenations run or compile using a line editing +Many of the implementations run or compile using a line editing library. In some cases, the implementations provide an option in the code to switch between the GNU GPL licensed GNU readline library and the BSD licensed editline (libedit) library. diff --git a/basic/variables.txt b/basic/variables.txt index dd6cbf8a4f..3e7afc7623 100644 --- a/basic/variables.txt +++ b/basic/variables.txt @@ -36,7 +36,7 @@ Calling arguments/temporaries: A : common call argument (especially EVAL, EVAL_AST) A$ : common call argument (READLINE, reader, string temp, key value) B : common call argument -B$ : STRING arg (HASHMAP_GET temp), PR_STR_SEQ seperator +B$ : STRING arg (HASHMAP_GET temp), PR_STR_SEQ separator : INIT_CORE_SET_FUNCTION, ENV_SET_S, ASSOC1_S C : common call argument, DO_TCO_FUNCTION temp in DO_APPLY E : environment (EVAL, EVAL_AST) diff --git a/chuck/notes.md b/chuck/notes.md index e897610d2a..c467d15bc9 100644 --- a/chuck/notes.md +++ b/chuck/notes.md @@ -1,7 +1,7 @@ # Step 1 - What if I don't have an OOP language? -- types.qx could be more promently mentioned... +- types.qx could be more prominently mentioned... - A table with all types and suggested object names would be hugely useful - Same for a list of all errors and their messages @@ -146,7 +146,7 @@ this even related? - It would be worth to mention that `with-meta` shall clone its argument to avoid one of the more sneaky test failure reasons -- "The value of this entry should be a mal string containing thename +- "The value of this entry should be a mal string containing the name of the current implementation." - "When the REPL starts up (as opposed to when it is called with a script and/or arguments), call the rep function with this string to diff --git a/cpp/README.md b/cpp/README.md index d62db64742..6bae4435ec 100644 --- a/cpp/README.md +++ b/cpp/README.md @@ -30,7 +30,7 @@ can be used to make and run this implementation. ./docker make - * run one of the implemenations: + * run one of the implementations: ./docker run ./stepA_mal diff --git a/docs/FAQ.md b/docs/FAQ.md index f292b1bf2c..d4536c0000 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -101,7 +101,7 @@ functionality goes into which step: a scientific fact that many small rewards are more motivating than a single large reward (citation intentionally omitted, get a small reward by googling it yourself). Each step in mal adds new - functionality that can actually be exercised by the implementor and, + functionality that can actually be exercised by the implementer and, just as importantly, easily tested. Also, the step structure of mal/make-a-lisp is not perfect. It never diff --git a/docs/Hints.md b/docs/Hints.md index d9d44c3519..3686cc833d 100644 --- a/docs/Hints.md +++ b/docs/Hints.md @@ -1,4 +1,4 @@ -# Mal/Make-a-Lisp Implmentation Hints +# Mal/Make-a-Lisp Implementation Hints @@ -81,7 +81,7 @@ it is considered legitimate for upstream inclusion. ### How do I read the command-line arguments if my language runtime doesn't support access to them? Most languages give access to the command-line arguments that were passed to -the program, either as an arguement to the `main` function (like `argc` and +the program, either as an argument to the `main` function (like `argc` and `argv` in C) or as a global variable (like `sys.argv` in Python). If your target language doesn't have such mechanisms, consider adding a wrapper script that will read the command-line arguments that were passed to the script and diff --git a/docs/TODO b/docs/TODO index a273008f76..074277f6c9 100644 --- a/docs/TODO +++ b/docs/TODO @@ -80,7 +80,7 @@ Make: - allow '_' in make variable names - hash-map with space in key string - errors should propagate up from within load-file - - GC: expore using "undefined" directive in Make 3.82 + - GC: explore using "undefined" directive in Make 3.82 Mal: - line numbers in errors diff --git a/docs/step_notes.txt b/docs/step_notes.txt index e28761a3c0..df6a74e6ad 100644 --- a/docs/step_notes.txt +++ b/docs/step_notes.txt @@ -379,7 +379,7 @@ Step Notes: - Now self-hosting! -- Extra defintions needed for self-hosting +- Extra definitions needed for self-hosting - core module: - symbol?, sequential? (if not already) - vector, vector? diff --git a/examples/clojurewest2014.mal b/examples/clojurewest2014.mal index 98fcf17fef..2d26336bed 100755 --- a/examples/clojurewest2014.mal +++ b/examples/clojurewest2014.mal @@ -84,7 +84,7 @@ "- performance" "- namespaces" "- keywords" - "- GC (in bash, make, C implmentations)" + "- GC (in bash, make, C implementations)" "- lots of other things") (list (title "why?") diff --git a/examples/memoize.mal b/examples/memoize.mal index 500666c2de..0f9a882744 100644 --- a/examples/memoize.mal +++ b/examples/memoize.mal @@ -1,13 +1,13 @@ ;; ;; memoize.mal ;; -;; Impelement `memoize` using an atom (`mem`) which holds the memoized results +;; Implement `memoize` using an atom (`mem`) which holds the memoized results ;; (hash-map from the arguments to the result). When the function is called, ;; the hash-map is checked to see if the result for the given argument was already ;; calculated and stored. If this is the case, it is returned immediately; ;; otherwise, it is calculated and stored in `mem`. ;; -;; Adapated from http://clojure.org/atoms +;; Adapted from http://clojure.org/atoms ;; ;; Memoize any function diff --git a/examples/presentation.mal b/examples/presentation.mal index 80dadac426..093f9fafce 100755 --- a/examples/presentation.mal +++ b/examples/presentation.mal @@ -83,7 +83,7 @@ (title "things it does not have") "- performance" "- namespaces" - "- GC (in bash, make, C implmentations)" + "- GC (in bash, make, C implementations)" "- protocols :-(" "- lots of other things") (list diff --git a/process/guide.md b/process/guide.md index 2da87827e5..464897a185 100644 --- a/process/guide.md +++ b/process/guide.md @@ -523,7 +523,7 @@ repl_env = {'+': lambda a,b: a+b, `eval_ast` switches on the type of `ast` as follows: * symbol: lookup the symbol in the environment structure and return - the value or raise an error no value is found + the value or raise an error if no value is found * list: return a new list that is the result of calling `EVAL` on each of the members of the list * otherwise just return the original `ast` value @@ -1518,7 +1518,7 @@ diff -urp ../process/step9_try.txt ../process/stepA_mal.txt * Add a new "\*host-language\*" (symbol) entry to your REPL environment. The value of this entry should be a mal string - containing thename of the current implementation. + containing the name of the current implementation. * When the REPL starts up (as opposed to when it is called with a script and/or arguments), call the `rep` function with this string From 012e4179afb22ad6e1e9467bdd3dfaa78ab8c6a9 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 19 Sep 2017 21:45:20 -0500 Subject: [PATCH 0468/2308] FAQ: expand main repo merge expectations section. --- docs/FAQ.md | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index d4536c0000..6fb2c6750f 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -124,16 +124,6 @@ programming language. Here are a few guidelines for getting your implementation accepted into the main repository: -* Your implementation needs to be complete enough to self-host. This - means that all the mandatory tests should pass in both direct and - self-hosted modes: - ```bash - make "test^[IMPL_NAME]" - make MAL_IMPL=[IMPL_NAME] "test^mal" - ``` - You do not need to pass the final optional tests for stepA that are - marked as optional and not needed for self-hosting. - * Your implementation should follow the existing mal steps and structure: Lisp-centric code (eval, eval_ast, quasiquote, macroexpand) in the step files, other code in reader, printer, env, @@ -152,7 +142,35 @@ into the main repository: welcome). However, if it is clear to me that your implementation is not idiomatic in a given language then I will probably ask you to improve it first. - + +* Your implementation needs to be complete enough to self-host. This + means that all the mandatory tests should pass in both direct and + self-hosted modes: + ```bash + make "test^[IMPL_NAME]" + make MAL_IMPL=[IMPL_NAME] "test^mal" + ``` + You do not need to pass the final optional tests for stepA that are + marked as optional and not needed for self-hosting (except for the + `time-ms` function which is needed to run the micro-benchmark tests). + +* Create a `Dockerfile` in your directory that installs all the + packages necessary to build and run your implementation. Refer to other + implementations for examples of what the Dockerfile should contain. + Build your docker image and tag it `kanaka/mal-test-[IMPL_NAME]`. + The top-level Makefile has support for building/testing within + docker with the `DOCKERIZE` flag: + ```bash + make DOCKERIZE=1 "test^[IMPL_NAME]" + make DOCKERIZE=1 MAL_IMPL=[IMPL_NAME] "test^mal" + ``` + +* Make sure the Travis build and test scripts pass locally: + ```bash + IMPL=[IMPL_NAME] ./.travis_build.sh + ./.travis_test.sh test [IMPL_NAME] + ``` + * If you are creating a new implementation for an existing implementation (or somebody beats you to the punch while you are working on it), there is still a chance I will merge your From dd4020b944136680c70777a558161b41f9777527 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 19 Sep 2017 21:45:57 -0500 Subject: [PATCH 0469/2308] Guide: fix TODOs. Add next steps section. Also move a few general TODOs to docs/TODO. --- docs/TODO | 5 ++++ process/guide.md | 74 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/docs/TODO b/docs/TODO index 074277f6c9..a2ef21b955 100644 --- a/docs/TODO +++ b/docs/TODO @@ -10,6 +10,11 @@ General: - mention that identifier names are suggested. some have run into collisions with READ,EVAL,PRINT in case insensitive languages + - simplify: "X argument (list element Y)" -> ast[Y] + - more clarity about when to peek and poke in read_list and + read_form + - tokenizer: use first group rather than whole match (to + eliminate whitespace/commas) All Implementations: - regular expression matching in runtest diff --git a/process/guide.md b/process/guide.md index 464897a185..aa996c743d 100644 --- a/process/guide.md +++ b/process/guide.md @@ -32,9 +32,9 @@ So jump right in (er ... start the climb)! You might already have a language in mind that you want to use. Technically speaking, mal can be implemented in any sufficiently -complete programming language (i.e. Turing complete), however, there are a few -language features that can make the task MUCH easier. Here are some of -them in rough order of importance: +complete programming language (i.e. Turing complete), however, there +are a few language features that can make the task MUCH easier. Here +are some of them in rough order of importance: * A sequential compound data structure (e.g. arrays, lists, vectors, etc) @@ -1329,8 +1329,11 @@ implementation. Let us continue! * In the main program, use the `rep` function to define two new control structures macros. Here are the string arguments for `rep` to define these macros: - * TODO: note throw is not present until step9 * `cond`: "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" + * Note that `cond` calls the `throw` function when `cond` is + called with an odd number of args. The `throw` function is + implemented in the next step, but it will still serve it's + purpose here by causing an undefined symbol error. * `or`: "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" @@ -1513,8 +1516,17 @@ diff -urp ../process/step9_try.txt ../process/stepA_mal.txt entered by the user is returned as a string. If the user sends an end-of-file (usually Ctrl-D), then nil is returned. -* Add meta-data support to mal functions. TODO. Should be separate - from the function macro flag. +* Add meta-data support to mal functions by adding a new metadata + attribute on mal functions that refers to another mal value/type + (nil by default). Add the following metadata related core functions: + * `meta`: this takes a single mal function argument and returns the + value of the metadata attribute. + * `with-meta`: this function takes two arguments. The first argument + is a mal function and the second argument is another mal + value/type to set as metadata. A copy of the mal function is + returned that has its `meta` attribute set to the second argument. + Note that it is important that the environment and macro attribute + of mal function are retained when it is copied. * Add a new "\*host-language\*" (symbol) entry to your REPL environment. The value of this entry should be a mal string @@ -1606,15 +1618,15 @@ For extra information read [Peter Seibel's thorough discussion about #### Optional additions -* Add metadata support to mal functions, other composite data - types, and native functions. +* Add metadata support to other composite data types (lists, vectors + and hash-maps), and to native functions. * Add the following new core functions: * `time-ms`: takes no arguments and returns the number of milliseconds since epoch (00:00:00 UTC January 1, 1970), or, if not possible, since another point in time (`time-ms` is usually used relatively to measure time durations). After `time-ms` is - implemented, you can run the mal implementation performance - benchmarks by running `make perf^quux`. + implemented, you can run the performance micro-benchmarks by + running `make perf^quux`. * `conj`: takes a collection and one or more elements as arguments and returns a new collection which includes the original collection and the new elements. If the collection is a list, a @@ -1640,13 +1652,35 @@ For extra information read [Peter Seibel's thorough discussion about function should be added in `quux/tests/stepA_mal.mal` (see the [tests for `lua-eval`](../lua/tests/stepA_mal.mal) as an example). - -## TODO: - -* simplify: "X argument (list element Y)" -> ast[Y] -* list of types with metadata: mal functions (required for - self-hosting), list, vector, hash-map, native functions (optional - for self-hosting). -* more clarity about when to peek and poke in read_list and read_form -* tokenizer: use first group rather than whole match (to eliminate - whitespace/commas) +### Next Steps + +* Join the #mal IRC channel. It's fairly quiet but there are bursts of + interesting conversation related to mal, Lisps, esoteric programming + languages, etc. +* If you have created an implementation for a new target language (or + a unique and interesting variant of an existing implementation), + consider sending a pull request to add it into the main mal + repository. The [FAQ](FAQ.md#add_implementation) describes general + requirements for getting an implementation merged into the main + repository. +* Take your interpreter implementation and have it emit source code in + the target language rather than immediately evaluating it. In other + words, create a compiler. +* Pick a new target language and implement mal in it. Pick a language + that is very different from any that you already know. +* Use your mal implementation to implement a real world project. Many + of these will force you to address interop. Some ideas: + * Web server (with mal as CGI language for extra points) + * An IRC/Slack chat bot + * An editor (GUI or curses) with mal as a scripting/extension + language. + * An AI player for a game like Chess or Go. +* Implement a feature in your mal implementation that is not covered + by this guide. Some ideas: + * Namespaces + * Multi-threading support + * Errors with line numbers and/or stack traces. + * Lazy sequences + * Clojure-style protocols + * Full call/cc (call-with-current-continuation) support + * Explicit TCO (i.e. `recur`) with tail-position error checking From 0a9a4685371e02598a7bc4a30123c471b13f1bb8 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 19 Sep 2017 22:05:03 -0500 Subject: [PATCH 0470/2308] python: remove extraneous macroexpand call. --- python/step8_macros.py | 2 +- python/step9_try.py | 2 +- python/stepA_mal.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/step8_macros.py b/python/step8_macros.py index 016aae8ed3..cdf7969306 100644 --- a/python/step8_macros.py +++ b/python/step8_macros.py @@ -37,7 +37,7 @@ def is_macro_call(ast, env): def macroexpand(ast, env): while is_macro_call(ast, env): mac = env.get(ast[0]) - ast = macroexpand(mac(*ast[1:]), env) + ast = mac(*ast[1:]) return ast def eval_ast(ast, env): diff --git a/python/step9_try.py b/python/step9_try.py index 9cebe9c662..80b7f5d2f5 100644 --- a/python/step9_try.py +++ b/python/step9_try.py @@ -37,7 +37,7 @@ def is_macro_call(ast, env): def macroexpand(ast, env): while is_macro_call(ast, env): mac = env.get(ast[0]) - ast = macroexpand(mac(*ast[1:]), env) + ast = mac(*ast[1:]) return ast def eval_ast(ast, env): diff --git a/python/stepA_mal.py b/python/stepA_mal.py index 6f2f6dabf2..2da3f7190f 100644 --- a/python/stepA_mal.py +++ b/python/stepA_mal.py @@ -37,7 +37,7 @@ def is_macro_call(ast, env): def macroexpand(ast, env): while is_macro_call(ast, env): mac = env.get(ast[0]) - ast = macroexpand(mac(*ast[1:]), env) + ast = mac(*ast[1:]) return ast def eval_ast(ast, env): From cf01dac14531c9df661bba199c81cbd53bea247c Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 19 Sep 2017 22:31:02 -0500 Subject: [PATCH 0471/2308] Add package.lock to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 887b03c3f6..a0cf920095 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ .sbt .npm .node-gyp +package-lock.json .elm */experiments */node_modules From 67365c05460ffc24f2244bab9db243543ff93751 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 19 Sep 2017 22:35:59 -0500 Subject: [PATCH 0472/2308] Hy: step0 --- Makefile | 3 ++- hy/run | 2 ++ hy/step0_repl.hy | 20 ++++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100755 hy/run create mode 100755 hy/step0_repl.hy diff --git a/Makefile b/Makefile index d89507d95d..100e2f9086 100644 --- a/Makefile +++ b/Makefile @@ -80,7 +80,7 @@ DOCKERIZE = IMPLS = ada awk bash basic c d chuck clojure coffee common-lisp cpp crystal cs dart \ erlang elisp elixir es6 factor forth fsharp go groovy gst guile haskell \ - haxe io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ + haxe hy io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ python r racket rexx rpython ruby rust scala scheme skew swift swift3 tcl \ ts vb vhdl vimscript livescript elm @@ -199,6 +199,7 @@ gst_STEP_TO_PROG = gst/$($(1)).st java_STEP_TO_PROG = java/target/classes/mal/$($(1)).class haskell_STEP_TO_PROG = haskell/$($(1)) haxe_STEP_TO_PROG = $(haxe_STEP_TO_PROG_$(haxe_MODE)) +hy_STEP_TO_PROG = hy/$($(1)).hy io_STEP_TO_PROG = io/$($(1)).io julia_STEP_TO_PROG = julia/$($(1)).jl js_STEP_TO_PROG = js/$($(1)).js diff --git a/hy/run b/hy/run new file mode 100755 index 0000000000..97f856223b --- /dev/null +++ b/hy/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec $(dirname $0)/${STEP:-stepA_mal}.hy "${@}" diff --git a/hy/step0_repl.hy b/hy/step0_repl.hy new file mode 100755 index 0000000000..9596f056f6 --- /dev/null +++ b/hy/step0_repl.hy @@ -0,0 +1,20 @@ +#!/usr/bin/env hy + +(defn READ [str] + str) + +(defn EVAL [ast env] + ast) + +(defn PRINT [exp] + exp) + +(defn REP [str] + (PRINT (EVAL (READ str) {}))) + +(while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)))) From 58ff81103f4aacccd12ce0845adfeed5a321e065 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 19 Sep 2017 22:44:41 -0500 Subject: [PATCH 0473/2308] Hy: step1 --- hy/printer.hy | 22 ++++++++++ hy/reader.hy | 92 ++++++++++++++++++++++++++++++++++++++++++ hy/step1_read_print.hy | 27 +++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 hy/printer.hy create mode 100644 hy/reader.hy create mode 100755 hy/step1_read_print.hy diff --git a/hy/printer.hy b/hy/printer.hy new file mode 100644 index 0000000000..14c8c07ca7 --- /dev/null +++ b/hy/printer.hy @@ -0,0 +1,22 @@ +(import [hy.models [HyInteger :as Int HyKeyword :as Keyword + HyString :as Str HySymbol :as Sym HyDict :as Map]]) + + +(defn escape [s] + (-> (str s) (.replace "\\" "\\\\") + (.replace "\"" "\\\"") + (.replace "\n" "\\n"))) + +(defn pr-str [obj &optional [print-readably True]] + (setv _r print-readably + t (type obj)) + (Str + (if + (none? obj) "nil" + (= t bool) (if obj "true" "false") + (= t Keyword) (+ ":" (name obj)) + (= t Str) (if _r (+ "\"" (escape obj) "\"") obj) + (= t tuple) (+ "(" (.join " " (map (fn [x] (pr-str x _r)) obj)) ")") + (= t list) (+ "[" (.join " " (map (fn [x] (pr-str x _r)) obj)) "]") + (= t Map) (+ "{" (.join " " (map (fn [x] (pr-str x _r)) obj)) "}") + True (str obj)))) diff --git a/hy/reader.hy b/hy/reader.hy new file mode 100644 index 0000000000..55010dc5b5 --- /dev/null +++ b/hy/reader.hy @@ -0,0 +1,92 @@ +(import [hy.models [HyInteger :as Int HyKeyword :as Keyword + HyString :as Str HySymbol :as Sym HyDict :as Map]] + [re]) + +(defclass Blank [Exception]) + +(defclass Reader [] + (defn --init-- [self tokens &optional [position 0]] + (setv self.tokens tokens self.position position)) + (defn next [self] + (setv self.position (+ 1 self.position)) + (get self.tokens (- self.position 1))) + (defn peek [self] + (if (> (len self.tokens) self.position) + (get self.tokens self.position) + None))) + +(def tok-re (.compile re "[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:[\\\\].|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}()'\"`@,;]+)")) +(def int-re (.compile re "-?[0-9]+$")) + +(defn tokenize [str] + (list-comp + t + (t (.findall re tok-re str)) + (!= (get t 0) ";"))) + +(defn unescape [s] + (-> s (.replace "\\\"" "\"") + (.replace "\\n" "\n") + (.replace "\\\\" "\\"))) + +(defn read-atom [rdr] + (setv token (.next rdr)) + (if + (.match re int-re token) (int token) + (= "\"" (get token 0)) (Str (unescape (cut token 1 -1))) + (= ":" (get token 0)) (Keyword token) + (= "nil" token) None + (= "true" token) True + (= "false" token) False + True (Sym token))) + +(defn read-seq [rdr &optional [start "("] [end ")"]] + (setv ast (list) + token (.next rdr)) + (if (!= token start) + (raise (Exception (+ "expected '" start "'"))) + (do + (setv token (.peek rdr)) + (while (!= token end) + (if (not token) (raise (Exception (+ "expected '" end + ", got EOF")))) + (.append ast (read-form rdr)) + (setv token (.peek rdr))) + (.next rdr) + ast))) + +(defn read-form [rdr] + (setv token (.peek rdr)) + (if + (= ";" (get token 0)) (.next rdr) + + (= "'" token) (do (.next rdr) + (tuple [(Sym "quote") (read-form rdr)])) + (= "`" token) (do (.next rdr) + (tuple [(Sym "quasiquote") (read-form rdr)])) + (= "~" token) (do (.next rdr) + (tuple [(Sym "unquote") (read-form rdr)])) + (= "~@" token) (do (.next rdr) + (tuple [(Sym "splice-unquote") + (read-form rdr)])) + (= "^" token) (do (.next rdr) + (setv meta (read-form rdr)) + (tuple [(Sym "with-meta") (read-form rdr) meta])) + (= "@" token) (do (.next rdr) + (tuple [(Sym "deref") (read-form rdr)])) + + (= ")" token) (raise (Exception "unexpected ')'")) + (= "(" token) (tuple (read-seq rdr "(" ")")) + + (= "]" token) (raise (Exception "unexpected ')'")) + (= "[" token) (read-seq rdr "[" "]") + + (= "}" token) (raise (Exception "unexpected '}'")) + (= "{" token) (Map (read-seq rdr "{" "}")) + + True (read-atom rdr))) + +(defn read-str [str] + (setv tokens (tokenize str)) + (if (= 0 (len tokens)) (raise (Blank "blank line"))) + (read-form (Reader tokens))) diff --git a/hy/step1_read_print.hy b/hy/step1_read_print.hy new file mode 100755 index 0000000000..dcd384214c --- /dev/null +++ b/hy/step1_read_print.hy @@ -0,0 +1,27 @@ +#!/usr/bin/env hy + +(import sys traceback) +(import [reader [read-str Blank]]) +(import [printer [pr-str]]) + +(defn READ [str] + (read-str str)) + +(defn EVAL [ast env] + ast) + +(defn PRINT [exp] + (pr-str exp True)) + +(defn REP [str] + (PRINT (EVAL (READ str) {}))) + +(while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) From cadde2db981b3a691c74554c4d63b46b7e909de6 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 20 Sep 2017 20:43:00 -0500 Subject: [PATCH 0474/2308] Hy: step2 --- hy/step2_eval.hy | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100755 hy/step2_eval.hy diff --git a/hy/step2_eval.hy b/hy/step2_eval.hy new file mode 100755 index 0000000000..2b7fd9a431 --- /dev/null +++ b/hy/step2_eval.hy @@ -0,0 +1,56 @@ +#!/usr/bin/env hy + +(import [hy.models [HyDict :as Map]]) +(import sys traceback) +(import [reader [read-str Blank]]) +(import [printer [pr-str]]) + +;; read +(defn READ [str] + (read-str str)) + +;; eval +(defn eval-ast [ast env] + (if + (symbol? ast) (if (.has_key env ast) (get env ast) + (raise (Exception (+ ast " not found")))) + (instance? Map ast) (Map (map (fn [x] (EVAL x env)) ast)) + (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) + (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) + True ast)) + +(defn EVAL [ast env] + (if (not (instance? tuple ast)) + (eval-ast ast env) + + (if + (empty? ast) ast + (do + (setv el (eval-ast ast env) + f (first el) + args (rest el)) + (apply f args))))) + +;; print +(defn PRINT [exp] + (pr-str exp True)) + +;; repl + +(def repl-env {'+ + + '- - + '* * + '/ (fn [a b] (int (/ a b)))}) + +(defn REP [str] + (PRINT (EVAL (READ str) repl-env))) + +(while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) From 5d2813209a63aebecd4c71c4cf3818f0fb9c594b Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 20 Sep 2017 21:21:41 -0500 Subject: [PATCH 0475/2308] Hy: step3 --- hy/env.hy | 19 +++++++++++++ hy/step3_env.hy | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 hy/env.hy create mode 100755 hy/step3_env.hy diff --git a/hy/env.hy b/hy/env.hy new file mode 100644 index 0000000000..1893aa440c --- /dev/null +++ b/hy/env.hy @@ -0,0 +1,19 @@ +(defn env-new [&optional [outer None] [binds None] [exprs None]] + {:outer outer}) + +(defn env-find [env k] + (if + (.has_key env k) env + (.has_key env ':outer) (env-find (get env ':outer) k) + True None)) + +(defn env-get [env k] + (setv e (env-find env k)) + (if-not e + (raise (Exception (+ "'" k "' not found")))) + (get e k)) + +(defn env-set [env k v] + (assoc env k v) + v) + diff --git a/hy/step3_env.hy b/hy/step3_env.hy new file mode 100755 index 0000000000..31393cef53 --- /dev/null +++ b/hy/step3_env.hy @@ -0,0 +1,74 @@ +#!/usr/bin/env hy + +(import [hy.models [HyDict :as Map HySymbol :as Sym]]) +(import sys traceback) +(import [reader [read-str Blank]]) +(import [printer [pr-str]]) +(import [env [env-new env-get env-set]]) + +;; read +(defn READ [str] + (read-str str)) + +;; eval +(defn eval-ast [ast env] + ;;(print "eval-ast:" ast (type ast)) + (if + (symbol? ast) (env-get env ast) + (instance? Map ast) (Map (map (fn [x] (EVAL x env)) ast)) + (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) + (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) + True ast)) + +(defn EVAL [ast env] + ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) + (if (not (instance? tuple ast)) + (eval-ast ast env) + + ;; apply list + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + ast + + (= (Sym "def!") a0) + (env-set env a1 (EVAL a2 env)) + + (= (Sym "let*") a0) + (do + (setv let-env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set let-env b (EVAL e let-env))) + (EVAL a2 let-env)) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (rest el)) + (apply f args)))))) + +;; print +(defn PRINT [exp] + (pr-str exp True)) + +;; repl + +(def repl-env {'+ + + '- - + '* * + '/ (fn [a b] (int (/ a b)))}) + +(defn REP [str] + (PRINT (EVAL (READ str) repl-env))) + +(while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) From 379c7998eac55e36be10a519628b69d0a540ffc2 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 20 Sep 2017 22:24:58 -0500 Subject: [PATCH 0476/2308] Hy: step4 --- hy/core.hy | 31 +++++++++++++++ hy/env.hy | 16 +++++++- hy/step4_if_fn_do.hy | 95 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 hy/core.hy create mode 100755 hy/step4_if_fn_do.hy diff --git a/hy/core.hy b/hy/core.hy new file mode 100644 index 0000000000..7a98352fed --- /dev/null +++ b/hy/core.hy @@ -0,0 +1,31 @@ +(import [hy.models [HyString :as Str]]) +(import [printer [pr-str]]) + +(defn equal [a b] + (if (and (coll? a) (coll? b) (= (len a) (len b))) + (every? (fn [[a b]] (equal a b)) (zip a b)) + (= a b))) + +(def ns + {"=" equal + + "pr-str" (fn [&rest a] (Str (.join " " (map (fn [e] (pr-str e True)) a)))) + "str" (fn [&rest a] (Str (.join "" (map (fn [e] (pr-str e False)) a)))) + "prn" (fn [&rest a] (print (.join " " (map (fn [e] (pr-str e True)) a)))) + "println" (fn [&rest a] (print (.join " " (map (fn [e] (pr-str e False)) a)))) + + "<" < + "<=" <= + ">" > + ">=" >= + "+" + + "-" - + "*" * + "/" (fn [a b] (int (/ a b))) + + "list" (fn [&rest args] (tuple args)) + "list?" (fn [a] (instance? tuple a)) + + "empty?" empty? + "count" (fn [a] (if (none? a) 0 (len a))) + }) diff --git a/hy/env.hy b/hy/env.hy index 1893aa440c..eb6235146b 100644 --- a/hy/env.hy +++ b/hy/env.hy @@ -1,5 +1,17 @@ -(defn env-new [&optional [outer None] [binds None] [exprs None]] - {:outer outer}) +(import [hy.models [HySymbol :as Sym]]) + +(defn env-new [&optional [outer None] [binds []] [exprs []]] + (setv env {:outer outer}) + (while binds + (if + (= (Sym "&") (first binds)) + (do (assoc env (nth binds 1) (tuple exprs)) (break)) + + True + (do (assoc env (first binds) (first exprs)) + (setv binds (list (rest binds)) + exprs (list (rest exprs)))))) + env) (defn env-find [env k] (if diff --git a/hy/step4_if_fn_do.hy b/hy/step4_if_fn_do.hy new file mode 100755 index 0000000000..2c8db51871 --- /dev/null +++ b/hy/step4_if_fn_do.hy @@ -0,0 +1,95 @@ +#!/usr/bin/env hy + +(import [hy.models [HyDict :as Map HySymbol :as Sym]]) +(import sys traceback) +(import [reader [read-str Blank]]) +(import [printer [pr-str]]) +(import [env [env-new env-get env-set]]) +(import core) + +;; read +(defn READ [str] + (read-str str)) + +;; eval +(defn eval-ast [ast env] + ;;(print "eval-ast:" ast (type ast)) + (if + (symbol? ast) (env-get env ast) + (instance? Map ast) (Map (map (fn [x] (EVAL x env)) ast)) + (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) + (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) + True ast)) + +(defn EVAL [ast env] + ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) + (if (not (instance? tuple ast)) + (eval-ast ast env) + + ;; apply list + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + ast + + (= (Sym "def!") a0) + (env-set env a1 (EVAL a2 env)) + + (= (Sym "let*") a0) + (do + (setv let-env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set let-env b (EVAL e let-env))) + (EVAL a2 let-env)) + + (= (Sym "do") a0) + (last (eval-ast (list (rest ast)) env)) + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (EVAL (nth ast 3) env) + None) + (EVAL a2 env))) + + (= (Sym "fn*") a0) + (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + ;;(print "f:" f "args:" args) + (apply f args)))))) + +;; print +(defn PRINT [exp] + (pr-str exp True)) + +;; repl +(def repl-env (env-new)) +(defn REP [str] + (PRINT (EVAL (READ str) repl-env))) + +;; core.hy: defined using Hy +(for [k core.ns] + (env-set repl-env (Sym k) (get core.ns k))) + +;; core.mal: defined using the language itself +(REP "(def! not (fn* [a] (if a false true)))") + +(while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) From 45f4d834a87d4b9b5c04f73211b6a6cfa82c4c41 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 20 Sep 2017 22:52:52 -0500 Subject: [PATCH 0477/2308] Hy: step5 --- hy/step5_tco.hy | 109 +++++++++++++++++++++++++++++++++++++++++ hy/tests/step5_tco.mal | 15 ++++++ 2 files changed, 124 insertions(+) create mode 100755 hy/step5_tco.hy create mode 100644 hy/tests/step5_tco.mal diff --git a/hy/step5_tco.hy b/hy/step5_tco.hy new file mode 100755 index 0000000000..ebc58fa6b6 --- /dev/null +++ b/hy/step5_tco.hy @@ -0,0 +1,109 @@ +#!/usr/bin/env hy + +(import [hy.models [HyDict :as Map HySymbol :as Sym]]) +(import sys traceback) +(import [reader [read-str Blank]]) +(import [printer [pr-str]]) +(import [env [env-new env-get env-set]]) +(import core) + +;; read +(defn READ [str] + (read-str str)) + +;; eval +(defn eval-ast [ast env] + ;;(print "eval-ast:" ast (type ast)) + (if + (symbol? ast) (env-get env ast) + (instance? Map ast) (Map (map (fn [x] (EVAL x env)) ast)) + (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) + (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) + True ast)) + +(defn EVAL [ast env] + ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) + (setv res None) + (while True + (if (not (instance? tuple ast)) + (setv res (eval-ast ast env)) + + ;; apply list + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + (setv res ast) + + (= (Sym "def!") a0) + (setv res (env-set env a1 (EVAL a2 env))) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (setv ast a2) + (continue)) ;; TCO + + (= (Sym "do") a0) + (do (eval-ast (list (butlast (rest ast))) env) + (setv ast (last ast)) + (continue)) ;; TCO + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (do (setv ast (nth ast 3)) (continue)) ;; TCO + (setv res None)) + (do (setv ast a2) (continue)))) ;; TCO + + (= (Sym "fn*") a0) + (setv func (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + func.ast a2 + func.env env + func.params a1 + res func) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (if (hasattr f "ast") + (do (setv ast f.ast + env (env-new f.env f.params args)) + (continue)) ;; TCO + (setv res (apply f args))))))) + (break)) + res) + +;; print +(defn PRINT [exp] + (pr-str exp True)) + +;; repl +(def repl-env (env-new)) +(defn REP [str] + (PRINT (EVAL (READ str) repl-env))) + +;; core.hy: defined using Hy +(for [k core.ns] + (env-set repl-env (Sym k) (get core.ns k))) + +;; core.mal: defined using the language itself +(REP "(def! not (fn* [a] (if a false true)))") + +(while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) diff --git a/hy/tests/step5_tco.mal b/hy/tests/step5_tco.mal new file mode 100644 index 0000000000..d20df25db7 --- /dev/null +++ b/hy/tests/step5_tco.mal @@ -0,0 +1,15 @@ +;; Test recursive non-tail call function + +(def! sum-to (fn* (n) (if (= n 0) 0 (+ n (sum-to (- n 1)))))) + +(sum-to 10) +;=>55 + +;;; no try* yet, so test completion of side-effects +(def! res1 nil) +;=>nil +;;; For implementations without their own TCO this should fail and +;;; leave res1 unchanged +(def! res1 (sum-to 10000)) +res1 +;=>nil From 5b86f08c037a729918a1e69b52e583ebded1033b Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 20 Sep 2017 23:27:30 -0500 Subject: [PATCH 0478/2308] Hy: step6 --- hy/core.hy | 15 +++++- hy/env.hy | 6 +-- hy/mal_types.hy | 3 ++ hy/printer.hy | 3 +- hy/step6_file.hy | 116 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 hy/mal_types.hy create mode 100755 hy/step6_file.hy diff --git a/hy/core.hy b/hy/core.hy index 7a98352fed..5215add203 100644 --- a/hy/core.hy +++ b/hy/core.hy @@ -1,4 +1,6 @@ (import [hy.models [HyString :as Str]]) +(import [mal_types [Atom]]) +(import [reader [read-str]]) (import [printer [pr-str]]) (defn equal [a b] @@ -13,6 +15,8 @@ "str" (fn [&rest a] (Str (.join "" (map (fn [e] (pr-str e False)) a)))) "prn" (fn [&rest a] (print (.join " " (map (fn [e] (pr-str e True)) a)))) "println" (fn [&rest a] (print (.join " " (map (fn [e] (pr-str e False)) a)))) + "read-string" read-str + "slurp" (fn [a] (Str (-> a open .read))) "<" < "<=" <= @@ -23,9 +27,16 @@ "*" * "/" (fn [a b] (int (/ a b))) - "list" (fn [&rest args] (tuple args)) - "list?" (fn [a] (instance? tuple a)) + "list" (fn [&rest args] (tuple args)) + "list?" (fn [a] (instance? tuple a)) "empty?" empty? "count" (fn [a] (if (none? a) 0 (len a))) + + "atom" (fn [a] (Atom a)) + "atom?" (fn [a] (instance? Atom a)) + "deref" (fn [a] a.val) + "reset!" (fn [a b] (do (setv a.val b) b)) + "swap!" (fn [a f &rest args] (do (setv a.val (apply f (+ (, a.val) args))) + a.val)) }) diff --git a/hy/env.hy b/hy/env.hy index eb6235146b..02161704e4 100644 --- a/hy/env.hy +++ b/hy/env.hy @@ -15,9 +15,9 @@ (defn env-find [env k] (if - (.has_key env k) env - (.has_key env ':outer) (env-find (get env ':outer) k) - True None)) + (.has_key env k) env + (get env ':outer) (env-find (get env ':outer) k) + True None)) (defn env-get [env k] (setv e (env-find env k)) diff --git a/hy/mal_types.hy b/hy/mal_types.hy new file mode 100644 index 0000000000..b227e4321e --- /dev/null +++ b/hy/mal_types.hy @@ -0,0 +1,3 @@ +(defclass Atom [] + (defn --init-- [self val] (setv self.val val))) + diff --git a/hy/printer.hy b/hy/printer.hy index 14c8c07ca7..1e63484810 100644 --- a/hy/printer.hy +++ b/hy/printer.hy @@ -1,6 +1,6 @@ (import [hy.models [HyInteger :as Int HyKeyword :as Keyword HyString :as Str HySymbol :as Sym HyDict :as Map]]) - +(import [mal_types [Atom]]) (defn escape [s] (-> (str s) (.replace "\\" "\\\\") @@ -19,4 +19,5 @@ (= t tuple) (+ "(" (.join " " (map (fn [x] (pr-str x _r)) obj)) ")") (= t list) (+ "[" (.join " " (map (fn [x] (pr-str x _r)) obj)) "]") (= t Map) (+ "{" (.join " " (map (fn [x] (pr-str x _r)) obj)) "}") + (instance? Atom obj) (+ "(atom " (pr-str obj.val _r) ")") True (str obj)))) diff --git a/hy/step6_file.hy b/hy/step6_file.hy new file mode 100755 index 0000000000..735842cc5b --- /dev/null +++ b/hy/step6_file.hy @@ -0,0 +1,116 @@ +#!/usr/bin/env hy + +(import [hy.models [HyDict :as Map HyString :as Str HySymbol :as Sym]]) +(import sys traceback) +(import [reader [read-str Blank]]) +(import [printer [pr-str]]) +(import [env [env-new env-get env-set]]) +(import core) + +;; read +(defn READ [str] + (read-str str)) + +;; eval +(defn eval-ast [ast env] + ;;(print "eval-ast:" ast (type ast)) + (if + (symbol? ast) (env-get env ast) + (instance? Map ast) (Map (map (fn [x] (EVAL x env)) ast)) + (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) + (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) + True ast)) + +(defn EVAL [ast env] + ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) + (setv res None) + (while True + (if (not (instance? tuple ast)) + (setv res (eval-ast ast env)) + + ;; apply list + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + (setv res ast) + + (= (Sym "def!") a0) + (setv res (env-set env a1 (EVAL a2 env))) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (setv ast a2) + (continue)) ;; TCO + + (= (Sym "do") a0) + (do (eval-ast (list (butlast (rest ast))) env) + (setv ast (last ast)) + (continue)) ;; TCO + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (do (setv ast (nth ast 3)) (continue)) ;; TCO + (setv res None)) + (do (setv ast a2) (continue)))) ;; TCO + + (= (Sym "fn*") a0) + (setv func (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + func.ast a2 + func.env env + func.params a1 + res func) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (if (hasattr f "ast") + (do (setv ast f.ast + env (env-new f.env f.params args)) + (continue)) ;; TCO + (setv res (apply f args))))))) + (break)) + res) + +;; print +(defn PRINT [exp] + (pr-str exp True)) + +;; repl +(def repl-env (env-new)) +(defn REP [str] + (PRINT (EVAL (READ str) repl-env))) + +;; core.hy: defined using Hy +(for [k core.ns] + (env-set repl-env (Sym k) (get core.ns k))) +(env-set repl-env (Sym "eval") (fn [ast] (EVAL ast repl-env))) +(env-set repl-env (Sym "*ARGV*") (tuple (map Str (rest (rest sys.argv))))) + +;; core.mal: defined using the language itself +(REP "(def! not (fn* [a] (if a false true)))") +(REP "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + +(when (>= (len sys.argv) 2) + (REP (+ "(load-file \"" (get sys.argv 1) "\")")) + (sys.exit 0)) + +(while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) From 4abefe454aee3276ef7775832370fd6d9d3bcc5b Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 20 Sep 2017 23:49:32 -0500 Subject: [PATCH 0479/2308] Hy: step7 --- hy/core.hy | 12 +++- hy/step7_quote.hy | 140 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 3 deletions(-) create mode 100755 hy/step7_quote.hy diff --git a/hy/core.hy b/hy/core.hy index 5215add203..599b8e7a50 100644 --- a/hy/core.hy +++ b/hy/core.hy @@ -5,8 +5,12 @@ (defn equal [a b] (if (and (coll? a) (coll? b) (= (len a) (len b))) - (every? (fn [[a b]] (equal a b)) (zip a b)) - (= a b))) + (every? (fn [[a b]] (equal a b)) (zip a b)) + + (= (type a) (type b)) + (= a b) + + False)) (def ns {"=" equal @@ -17,7 +21,7 @@ "println" (fn [&rest a] (print (.join " " (map (fn [e] (pr-str e False)) a)))) "read-string" read-str "slurp" (fn [a] (Str (-> a open .read))) - + "<" < "<=" <= ">" > @@ -30,6 +34,8 @@ "list" (fn [&rest args] (tuple args)) "list?" (fn [a] (instance? tuple a)) + "cons" (fn [a b] (tuple (chain [a] b))) + "concat" (fn [&rest a] (tuple (apply chain a))) "empty?" empty? "count" (fn [a] (if (none? a) 0 (len a))) diff --git a/hy/step7_quote.hy b/hy/step7_quote.hy new file mode 100755 index 0000000000..74150467f8 --- /dev/null +++ b/hy/step7_quote.hy @@ -0,0 +1,140 @@ +#!/usr/bin/env hy + +(import [hy.models [HyDict :as Map HyString :as Str HySymbol :as Sym]]) +(import sys traceback) +(import [reader [read-str Blank]]) +(import [printer [pr-str]]) +(import [env [env-new env-get env-set]]) +(import core) + +;; read +(defn READ [str] + (read-str str)) + +;; eval +(defn pair? [x] + (and (coll? x) (> (len x) 0))) + +(defn QUASIQUOTE [ast] + (if + (not (pair? ast)) + (tuple [(Sym "quote") ast]) + + (= (Sym "unquote") (first ast)) + (nth ast 1) + + (and (pair? (first ast)) + (= (Sym "splice-unquote") (first (first ast)))) + (tuple [(Sym "concat") (nth (first ast) 1) (QUASIQUOTE (tuple (rest ast)))]) + + True + (tuple [(Sym "cons") (QUASIQUOTE (first ast)) (QUASIQUOTE (tuple (rest ast)))]))) + +(defn eval-ast [ast env] + ;;(print "eval-ast:" ast (type ast)) + (if + (symbol? ast) (env-get env ast) + (instance? Map ast) (Map (map (fn [x] (EVAL x env)) ast)) + (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) + (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) + True ast)) + +(defn EVAL [ast env] + ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) + (setv res None) + (while True + (if (not (instance? tuple ast)) + (setv res (eval-ast ast env)) + + ;; apply list + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + (setv res ast) + + (= (Sym "def!") a0) + (setv res (env-set env a1 (EVAL a2 env))) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (setv ast a2) + (continue)) ;; TCO + + (= (Sym "quote") a0) + (setv res a1) + + (= (Sym "quasiquote") a0) + (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO + + (= (Sym "do") a0) + (do (eval-ast (list (butlast (rest ast))) env) + (setv ast (last ast)) + (continue)) ;; TCO + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (do (setv ast (nth ast 3)) (continue)) ;; TCO + (setv res None)) + (do (setv ast a2) (continue)))) ;; TCO + + (= (Sym "fn*") a0) + (setv func (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + func.ast a2 + func.env env + func.params a1 + res func) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (if (hasattr f "ast") + (do (setv ast f.ast + env (env-new f.env f.params args)) + (continue)) ;; TCO + (setv res (apply f args))))))) + (break)) + res) + +;; print +(defn PRINT [exp] + (pr-str exp True)) + +;; repl +(def repl-env (env-new)) +(defn REP [str] + (PRINT (EVAL (READ str) repl-env))) + +;; core.hy: defined using Hy +(for [k core.ns] + (env-set repl-env (Sym k) (get core.ns k))) +(env-set repl-env (Sym "eval") (fn [ast] (EVAL ast repl-env))) +(env-set repl-env (Sym "*ARGV*") (tuple (map Str (rest (rest sys.argv))))) + +;; core.mal: defined using the language itself +(REP "(def! not (fn* [a] (if a false true)))") +(REP "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") + +(when (>= (len sys.argv) 2) + (REP (+ "(load-file \"" (get sys.argv 1) "\")")) + (sys.exit 0)) + +(while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) From 3cc7b877befb89f23fd3588c69cf989566c6b534 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 21 Sep 2017 21:22:55 -0500 Subject: [PATCH 0480/2308] Hy: step8 --- hy/core.hy | 3 + hy/step8_macros.hy | 172 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100755 hy/step8_macros.hy diff --git a/hy/core.hy b/hy/core.hy index 599b8e7a50..0406b8e03e 100644 --- a/hy/core.hy +++ b/hy/core.hy @@ -36,6 +36,9 @@ "cons" (fn [a b] (tuple (chain [a] b))) "concat" (fn [&rest a] (tuple (apply chain a))) + "nth" (fn [a b] (get a b)) + "first" (fn [a] (if (none? a) None (first a))) + "rest" (fn [a] (if (none? a) (,) (tuple (rest a)))) "empty?" empty? "count" (fn [a] (if (none? a) 0 (len a))) diff --git a/hy/step8_macros.hy b/hy/step8_macros.hy new file mode 100755 index 0000000000..5de0cc6254 --- /dev/null +++ b/hy/step8_macros.hy @@ -0,0 +1,172 @@ +#!/usr/bin/env hy + +(import [hy.models [HyDict :as Map HyString :as Str HySymbol :as Sym]]) +(import sys traceback) +(import [reader [read-str Blank]]) +(import [printer [pr-str]]) +(import [env [env-new env-get env-set env-find]]) +(import core) + +;; read +(defn READ [str] + (read-str str)) + +;; eval +(defn pair? [x] + (and (coll? x) (> (len x) 0))) + +(defn QUASIQUOTE [ast] + (if + (not (pair? ast)) + (tuple [(Sym "quote") ast]) + + (= (Sym "unquote") (first ast)) + (nth ast 1) + + (and (pair? (first ast)) + (= (Sym "splice-unquote") (first (first ast)))) + (tuple [(Sym "concat") (nth (first ast) 1) (QUASIQUOTE (tuple (rest ast)))]) + + True + (tuple [(Sym "cons") (QUASIQUOTE (first ast)) (QUASIQUOTE (tuple (rest ast)))]))) + +(defn macro? [ast env] + (when (and (coll? ast) + (symbol? (first ast)) + (env-find env (first ast))) + (setv mac (env-get env (first ast))) + (and (hasattr mac "macro") + mac.macro))) + +(defn macroexpand [ast env] + (while (macro? ast env) + (setv mac (env-get env (first ast)) + ast (apply mac (tuple (rest ast))))) + ast) + + + +(defn eval-ast [ast env] + ;;(print "eval-ast:" ast (type ast)) + (if + (symbol? ast) (env-get env ast) + (instance? Map ast) (Map (map (fn [x] (EVAL x env)) ast)) + (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) + (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) + True ast)) + +(defn EVAL [ast env] + ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) + (setv res None) + (while True + (if (not (instance? tuple ast)) + (setv res (eval-ast ast env)) + + ;; apply list + (do + (setv ast (macroexpand ast env)) + (if (not (instance? tuple ast)) + (setv res (eval-ast ast env)) + + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + (setv res ast) + + (= (Sym "def!") a0) + (setv res (env-set env a1 (EVAL a2 env))) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (setv ast a2) + (continue)) ;; TCO + + (= (Sym "quote") a0) + (setv res a1) + + (= (Sym "quasiquote") a0) + (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO + + (= (Sym "defmacro!") a0) + (do (setv func (EVAL a2 env) + func.macro True) + (setv res (env-set env a1 func))) + + (= (Sym "macroexpand") a0) + (setv res (macroexpand a1 env)) + + (= (Sym "do") a0) + (do (eval-ast (list (butlast (rest ast))) env) + (setv ast (last ast)) + (continue)) ;; TCO + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (do (setv ast (nth ast 3)) (continue)) ;; TCO + (setv res None)) + (do (setv ast a2) (continue)))) ;; TCO + + (= (Sym "fn*") a0) + (setv func (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + func.ast a2 + func.env env + func.params a1 + res func) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (if (hasattr f "ast") + (do (setv ast f.ast + env (env-new f.env f.params args)) + (continue)) ;; TCO + (setv res (apply f args))))))))) + (break)) + res) + +;; print +(defn PRINT [exp] + (pr-str exp True)) + +;; repl +(def repl-env (env-new)) +(defn REP [str] + (PRINT (EVAL (READ str) repl-env))) + +;; core.hy: defined using Hy +(for [k core.ns] + (env-set repl-env (Sym k) (get core.ns k))) +(env-set repl-env (Sym "eval") (fn [ast] (EVAL ast repl-env))) +(env-set repl-env (Sym "*ARGV*") (tuple (map Str (rest (rest sys.argv))))) + +;; core.mal: defined using the language itself +(REP "(def! not (fn* [a] (if a false true)))") +(REP "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +(REP "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +(REP "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") + + +(when (>= (len sys.argv) 2) + (REP (+ "(load-file \"" (get sys.argv 1) "\")")) + (sys.exit 0)) + +(while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) From 081c322323f64e40335dda7aa9983f4dd8bfdd0d Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 21 Sep 2017 22:35:00 -0500 Subject: [PATCH 0481/2308] Hy: use dict instead of HyDict --- hy/core.hy | 13 ++++++++++--- hy/printer.hy | 6 ++++-- hy/reader.hy | 4 ++-- hy/step2_eval.hy | 5 +++-- hy/step3_env.hy | 6 ++++-- hy/step4_if_fn_do.hy | 6 ++++-- hy/step5_tco.hy | 6 ++++-- hy/step6_file.hy | 6 ++++-- hy/step7_quote.hy | 8 +++++--- hy/step8_macros.hy | 8 +++++--- 10 files changed, 45 insertions(+), 23 deletions(-) diff --git a/hy/core.hy b/hy/core.hy index 0406b8e03e..1b6e5327c9 100644 --- a/hy/core.hy +++ b/hy/core.hy @@ -3,10 +3,16 @@ (import [reader [read-str]]) (import [printer [pr-str]]) +(defn sequential? [a] + (or (instance? tuple a) (instance? list a))) + (defn equal [a b] - (if (and (coll? a) (coll? b) (= (len a) (len b))) + (if (and (sequential? a) (sequential? b) (= (len a) (len b))) (every? (fn [[a b]] (equal a b)) (zip a b)) + (and (instance? dict a) (instance? dict b) (= (.keys a) (.keys b))) + (every? (fn [k] (and (equal (get a k) (get b k)))) a) + (= (type a) (type b)) (= a b) @@ -41,11 +47,12 @@ "rest" (fn [a] (if (none? a) (,) (tuple (rest a)))) "empty?" empty? "count" (fn [a] (if (none? a) 0 (len a))) + "apply" (fn [f &rest a] (apply f (+ (list (butlast a)) (list (last a))))) + "map" (fn [f a] (tuple (map f a))) "atom" (fn [a] (Atom a)) "atom?" (fn [a] (instance? Atom a)) "deref" (fn [a] a.val) "reset!" (fn [a b] (do (setv a.val b) b)) - "swap!" (fn [a f &rest args] (do (setv a.val (apply f (+ (, a.val) args))) - a.val)) + "swap!" (fn [a f &rest xs] (do (setv a.val (apply f (+ (, a.val) xs))) a.val)) }) diff --git a/hy/printer.hy b/hy/printer.hy index 1e63484810..55809fc195 100644 --- a/hy/printer.hy +++ b/hy/printer.hy @@ -1,5 +1,5 @@ (import [hy.models [HyInteger :as Int HyKeyword :as Keyword - HyString :as Str HySymbol :as Sym HyDict :as Map]]) + HyString :as Str HySymbol :as Sym]]) (import [mal_types [Atom]]) (defn escape [s] @@ -18,6 +18,8 @@ (= t Str) (if _r (+ "\"" (escape obj) "\"") obj) (= t tuple) (+ "(" (.join " " (map (fn [x] (pr-str x _r)) obj)) ")") (= t list) (+ "[" (.join " " (map (fn [x] (pr-str x _r)) obj)) "]") - (= t Map) (+ "{" (.join " " (map (fn [x] (pr-str x _r)) obj)) "}") + (= t dict) (+ "{" (.join " " (map (fn [k] (+ (pr-str k _r) " " + (pr-str (get obj k) _r))) + obj)) "}") (instance? Atom obj) (+ "(atom " (pr-str obj.val _r) ")") True (str obj)))) diff --git a/hy/reader.hy b/hy/reader.hy index 55010dc5b5..41abec29d3 100644 --- a/hy/reader.hy +++ b/hy/reader.hy @@ -1,5 +1,5 @@ (import [hy.models [HyInteger :as Int HyKeyword :as Keyword - HyString :as Str HySymbol :as Sym HyDict :as Map]] + HyString :as Str HySymbol :as Sym]] [re]) (defclass Blank [Exception]) @@ -82,7 +82,7 @@ (= "[" token) (read-seq rdr "[" "]") (= "}" token) (raise (Exception "unexpected '}'")) - (= "{" token) (Map (read-seq rdr "{" "}")) + (= "{" token) (dict (partition (read-seq rdr "{" "}") 2)) True (read-atom rdr))) diff --git a/hy/step2_eval.hy b/hy/step2_eval.hy index 2b7fd9a431..d6b407b52c 100755 --- a/hy/step2_eval.hy +++ b/hy/step2_eval.hy @@ -1,6 +1,5 @@ #!/usr/bin/env hy -(import [hy.models [HyDict :as Map]]) (import sys traceback) (import [reader [read-str Blank]]) (import [printer [pr-str]]) @@ -14,7 +13,9 @@ (if (symbol? ast) (if (.has_key env ast) (get env ast) (raise (Exception (+ ast " not found")))) - (instance? Map ast) (Map (map (fn [x] (EVAL x env)) ast)) + (instance? dict ast) (dict (map (fn [k] + [(EVAL k env) (EVAL (get ast k) env)]) + ast)) (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) True ast)) diff --git a/hy/step3_env.hy b/hy/step3_env.hy index 31393cef53..95dc3fbbb2 100755 --- a/hy/step3_env.hy +++ b/hy/step3_env.hy @@ -1,6 +1,6 @@ #!/usr/bin/env hy -(import [hy.models [HyDict :as Map HySymbol :as Sym]]) +(import [hy.models [HySymbol :as Sym]]) (import sys traceback) (import [reader [read-str Blank]]) (import [printer [pr-str]]) @@ -15,7 +15,9 @@ ;;(print "eval-ast:" ast (type ast)) (if (symbol? ast) (env-get env ast) - (instance? Map ast) (Map (map (fn [x] (EVAL x env)) ast)) + (instance? dict ast) (dict (map (fn [k] + [(EVAL k env) (EVAL (get ast k) env)]) + ast)) (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) True ast)) diff --git a/hy/step4_if_fn_do.hy b/hy/step4_if_fn_do.hy index 2c8db51871..bf9c6ba737 100755 --- a/hy/step4_if_fn_do.hy +++ b/hy/step4_if_fn_do.hy @@ -1,6 +1,6 @@ #!/usr/bin/env hy -(import [hy.models [HyDict :as Map HySymbol :as Sym]]) +(import [hy.models [HySymbol :as Sym]]) (import sys traceback) (import [reader [read-str Blank]]) (import [printer [pr-str]]) @@ -16,7 +16,9 @@ ;;(print "eval-ast:" ast (type ast)) (if (symbol? ast) (env-get env ast) - (instance? Map ast) (Map (map (fn [x] (EVAL x env)) ast)) + (instance? dict ast) (dict (map (fn [k] + [(EVAL k env) (EVAL (get ast k) env)]) + ast)) (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) True ast)) diff --git a/hy/step5_tco.hy b/hy/step5_tco.hy index ebc58fa6b6..6b4ee8fafa 100755 --- a/hy/step5_tco.hy +++ b/hy/step5_tco.hy @@ -1,6 +1,6 @@ #!/usr/bin/env hy -(import [hy.models [HyDict :as Map HySymbol :as Sym]]) +(import [hy.models [HySymbol :as Sym]]) (import sys traceback) (import [reader [read-str Blank]]) (import [printer [pr-str]]) @@ -16,7 +16,9 @@ ;;(print "eval-ast:" ast (type ast)) (if (symbol? ast) (env-get env ast) - (instance? Map ast) (Map (map (fn [x] (EVAL x env)) ast)) + (instance? dict ast) (dict (map (fn [k] + [(EVAL k env) (EVAL (get ast k) env)]) + ast)) (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) True ast)) diff --git a/hy/step6_file.hy b/hy/step6_file.hy index 735842cc5b..680a347928 100755 --- a/hy/step6_file.hy +++ b/hy/step6_file.hy @@ -1,6 +1,6 @@ #!/usr/bin/env hy -(import [hy.models [HyDict :as Map HyString :as Str HySymbol :as Sym]]) +(import [hy.models [HyString :as Str HySymbol :as Sym]]) (import sys traceback) (import [reader [read-str Blank]]) (import [printer [pr-str]]) @@ -16,7 +16,9 @@ ;;(print "eval-ast:" ast (type ast)) (if (symbol? ast) (env-get env ast) - (instance? Map ast) (Map (map (fn [x] (EVAL x env)) ast)) + (instance? dict ast) (dict (map (fn [k] + [(EVAL k env) (EVAL (get ast k) env)]) + ast)) (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) True ast)) diff --git a/hy/step7_quote.hy b/hy/step7_quote.hy index 74150467f8..17e6db7f1d 100755 --- a/hy/step7_quote.hy +++ b/hy/step7_quote.hy @@ -1,6 +1,6 @@ #!/usr/bin/env hy -(import [hy.models [HyDict :as Map HyString :as Str HySymbol :as Sym]]) +(import [hy.models [HyString :as Str HySymbol :as Sym]]) (import sys traceback) (import [reader [read-str Blank]]) (import [printer [pr-str]]) @@ -13,7 +13,7 @@ ;; eval (defn pair? [x] - (and (coll? x) (> (len x) 0))) + (and (core.sequential? x) (> (len x) 0))) (defn QUASIQUOTE [ast] (if @@ -34,7 +34,9 @@ ;;(print "eval-ast:" ast (type ast)) (if (symbol? ast) (env-get env ast) - (instance? Map ast) (Map (map (fn [x] (EVAL x env)) ast)) + (instance? dict ast) (dict (map (fn [k] + [(EVAL k env) (EVAL (get ast k) env)]) + ast)) (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) True ast)) diff --git a/hy/step8_macros.hy b/hy/step8_macros.hy index 5de0cc6254..740c751b80 100755 --- a/hy/step8_macros.hy +++ b/hy/step8_macros.hy @@ -1,6 +1,6 @@ #!/usr/bin/env hy -(import [hy.models [HyDict :as Map HyString :as Str HySymbol :as Sym]]) +(import [hy.models [HyString :as Str HySymbol :as Sym]]) (import sys traceback) (import [reader [read-str Blank]]) (import [printer [pr-str]]) @@ -13,7 +13,7 @@ ;; eval (defn pair? [x] - (and (coll? x) (> (len x) 0))) + (and (core.sequential? x) (> (len x) 0))) (defn QUASIQUOTE [ast] (if @@ -50,7 +50,9 @@ ;;(print "eval-ast:" ast (type ast)) (if (symbol? ast) (env-get env ast) - (instance? Map ast) (Map (map (fn [x] (EVAL x env)) ast)) + (instance? dict ast) (dict (map (fn [k] + [(EVAL k env) (EVAL (get ast k) env)]) + ast)) (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) True ast)) From ef47406a9e47fe0a69f78083bc4d253963e434a0 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 21 Sep 2017 22:35:30 -0500 Subject: [PATCH 0482/2308] Hy: step9 --- hy/core.hy | 33 +++++++-- hy/mal_types.hy | 3 + hy/step9_try.hy | 188 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 219 insertions(+), 5 deletions(-) create mode 100755 hy/step9_try.hy diff --git a/hy/core.hy b/hy/core.hy index 1b6e5327c9..960f620240 100644 --- a/hy/core.hy +++ b/hy/core.hy @@ -1,5 +1,6 @@ -(import [hy.models [HyString :as Str]]) -(import [mal_types [Atom]]) +(import [hy.models [HyKeyword :as Keyword HyString :as Str HySymbol :as Sym]]) +(import [copy [copy]]) +(import [mal_types [MalException Atom]]) (import [reader [read-str]]) (import [printer [pr-str]]) @@ -19,7 +20,16 @@ False)) (def ns - {"=" equal + {"=" equal + "throw" (fn [a] (raise (MalException a))) + + "nil?" none? + "true?" (fn [a] (and (instance? bool a) (= a True))) + "false?" (fn [a] (and (instance? bool a) (= a False))) + "symbol" (fn [a] (Sym a)) + "symbol?" (fn [a] (instance? Sym a)) + "keyword" (fn [a] (Keyword (if (keyword? a) a (+ ":" a)))) + "keyword?" (fn [a] (keyword? a)) "pr-str" (fn [&rest a] (Str (.join " " (map (fn [e] (pr-str e True)) a)))) "str" (fn [&rest a] (Str (.join "" (map (fn [e] (pr-str e False)) a)))) @@ -37,9 +47,22 @@ "*" * "/" (fn [a b] (int (/ a b))) - "list" (fn [&rest args] (tuple args)) - "list?" (fn [a] (instance? tuple a)) + "list" (fn [&rest args] (tuple args)) + "list?" (fn [a] (instance? tuple a)) + "vector" (fn [&rest a] (list a)) + "vector?" (fn [a] (instance? list a)) + "hash-map" (fn [&rest a] (dict (partition a 2))) + "map?" (fn [a] (instance? dict a)) + "assoc" (fn [m &rest a] (setv m (copy m)) + (for [[k v] (partition a 2)] (assoc m k v)) m) + "dissoc" (fn [m &rest a] (setv m (copy m)) + (for [k a] (if (.has_key m k) (.pop m k))) m) + "get" (fn [m a] (if (and m (.has_key m a)) (get m a))) + "contains?" (fn [m a] (if (none? m) None (.has_key m a))) + "keys" (fn [m] (tuple (.keys m))) + "vals" (fn [m] (tuple (.values m))) + "sequential?" sequential? "cons" (fn [a b] (tuple (chain [a] b))) "concat" (fn [&rest a] (tuple (apply chain a))) "nth" (fn [a b] (get a b)) diff --git a/hy/mal_types.hy b/hy/mal_types.hy index b227e4321e..eb0515cef1 100644 --- a/hy/mal_types.hy +++ b/hy/mal_types.hy @@ -1,3 +1,6 @@ +(defclass MalException [Exception] + (defn --init-- [self val] (setv self.val val))) + (defclass Atom [] (defn --init-- [self val] (setv self.val val))) diff --git a/hy/step9_try.hy b/hy/step9_try.hy new file mode 100755 index 0000000000..2e2f4edd87 --- /dev/null +++ b/hy/step9_try.hy @@ -0,0 +1,188 @@ +#!/usr/bin/env hy + +(import [hy.models [HyString :as Str HySymbol :as Sym]]) +(import sys traceback) +(import [mal_types [MalException]]) +(import [reader [read-str Blank]]) +(import [printer [pr-str]]) +(import [env [env-new env-get env-set env-find]]) +(import core) + +;; read +(defn READ [str] + (read-str str)) + +;; eval +(defn pair? [x] + (and (core.sequential? x) (> (len x) 0))) + +(defn QUASIQUOTE [ast] + (if + (not (pair? ast)) + (tuple [(Sym "quote") ast]) + + (= (Sym "unquote") (first ast)) + (nth ast 1) + + (and (pair? (first ast)) + (= (Sym "splice-unquote") (first (first ast)))) + (tuple [(Sym "concat") (nth (first ast) 1) (QUASIQUOTE (tuple (rest ast)))]) + + True + (tuple [(Sym "cons") (QUASIQUOTE (first ast)) (QUASIQUOTE (tuple (rest ast)))]))) + +(defn macro? [ast env] + (when (and (coll? ast) + (symbol? (first ast)) + (env-find env (first ast))) + (setv mac (env-get env (first ast))) + (and (hasattr mac "macro") + mac.macro))) + +(defn macroexpand [ast env] + (while (macro? ast env) + (setv mac (env-get env (first ast)) + ast (apply mac (tuple (rest ast))))) + ast) + + + +(defn eval-ast [ast env] + ;;(print "eval-ast:" ast (type ast)) + (if + (symbol? ast) (env-get env ast) + (instance? dict ast) (dict (map (fn [k] + [(EVAL k env) (EVAL (get ast k) env)]) + ast)) + (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) + (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) + True ast)) + +(defn EVAL [ast env] + ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) + (setv res None) + (while True + (if (not (instance? tuple ast)) + (setv res (eval-ast ast env)) + + ;; apply list + (do + (setv ast (macroexpand ast env)) + (if (not (instance? tuple ast)) + (setv res (eval-ast ast env)) + + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + (setv res ast) + + (= (Sym "def!") a0) + (setv res (env-set env a1 (EVAL a2 env))) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (setv ast a2) + (continue)) ;; TCO + + (= (Sym "quote") a0) + (setv res a1) + + (= (Sym "quasiquote") a0) + (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO + + (= (Sym "defmacro!") a0) + (do (setv func (EVAL a2 env) + func.macro True) + (setv res (env-set env a1 func))) + + (= (Sym "macroexpand") a0) + (setv res (macroexpand a1 env)) + + (= (Sym "try*") a0) + (setv res + (if (= (Sym "catch*") (nth a2 0)) + (try + (EVAL a1 env) + (except [e Exception] + (if (instance? MalException e) + (setv exc e.val) + (setv exc (Str (get e.args 0)))) + (EVAL (nth a2 2) (env-new env [(nth a2 1)] + [exc])))) + (EVAL a1 env))) + + (= (Sym "do") a0) + (do (eval-ast (list (butlast (rest ast))) env) + (setv ast (last ast)) + (continue)) ;; TCO + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (do (setv ast (nth ast 3)) (continue)) ;; TCO + (setv res None)) + (do (setv ast a2) (continue)))) ;; TCO + + (= (Sym "fn*") a0) + (setv func (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + func.ast a2 + func.env env + func.params a1 + res func) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (if (hasattr f "ast") + (do (setv ast f.ast + env (env-new f.env f.params args)) + (continue)) ;; TCO + (setv res (apply f args))))))))) + (break)) + res) + +;; print +(defn PRINT [exp] + (pr-str exp True)) + +;; repl +(def repl-env (env-new)) +(defn REP [str] + (PRINT (EVAL (READ str) repl-env))) + +;; core.hy: defined using Hy +(for [k core.ns] + (env-set repl-env (Sym k) (get core.ns k))) +(env-set repl-env (Sym "eval") (fn [ast] (EVAL ast repl-env))) +(env-set repl-env (Sym "*ARGV*") (tuple (map Str (rest (rest sys.argv))))) + +;; core.mal: defined using the language itself +(REP "(def! not (fn* [a] (if a false true)))") +(REP "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +(REP "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +(REP "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") + + +(when (>= (len sys.argv) 2) + (REP (+ "(load-file \"" (get sys.argv 1) "\")")) + (sys.exit 0)) + +(while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) From f382a6122b5089db793638915f7402fc4102f8dc Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 21 Sep 2017 23:15:51 -0500 Subject: [PATCH 0483/2308] Hy: stepA (except meta on collections) --- hy/core.hy | 14 +++- hy/mal_types.hy | 9 +++ hy/stepA_mal.hy | 191 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+), 1 deletion(-) create mode 100755 hy/stepA_mal.hy diff --git a/hy/core.hy b/hy/core.hy index 960f620240..d4984a7e3d 100644 --- a/hy/core.hy +++ b/hy/core.hy @@ -1,6 +1,7 @@ (import [hy.models [HyKeyword :as Keyword HyString :as Str HySymbol :as Sym]]) (import [copy [copy]]) -(import [mal_types [MalException Atom]]) +(import [time [time]]) +(import [mal_types [MalException Atom clone]]) (import [reader [read-str]]) (import [printer [pr-str]]) @@ -26,6 +27,7 @@ "nil?" none? "true?" (fn [a] (and (instance? bool a) (= a True))) "false?" (fn [a] (and (instance? bool a) (= a False))) + "string?" (fn [a] (and (string? a) (not (keyword? a)))) "symbol" (fn [a] (Sym a)) "symbol?" (fn [a] (instance? Sym a)) "keyword" (fn [a] (Keyword (if (keyword? a) a (+ ":" a)))) @@ -36,6 +38,7 @@ "prn" (fn [&rest a] (print (.join " " (map (fn [e] (pr-str e True)) a)))) "println" (fn [&rest a] (print (.join " " (map (fn [e] (pr-str e False)) a)))) "read-string" read-str + "readline" (fn [a] (Str (raw_input a))) "slurp" (fn [a] (Str (-> a open .read))) "<" < @@ -46,6 +49,7 @@ "-" - "*" * "/" (fn [a b] (int (/ a b))) + "time-ms" (fn [] (int (* 1000 (time)))) "list" (fn [&rest args] (tuple args)) "list?" (fn [a] (instance? tuple a)) @@ -73,6 +77,14 @@ "apply" (fn [f &rest a] (apply f (+ (list (butlast a)) (list (last a))))) "map" (fn [f a] (tuple (map f a))) + "conj" (fn [a &rest xs] (if (instance? list a) (+ a (list xs)) + (tuple (+ (tuple (reversed xs)) a)))) + "seq" (fn [a] (if (or (none? a) (empty? a)) None + (string? a) (tuple (map Str a)) + (tuple a))) + + "meta" (fn [a] (if (hasattr a "meta") a.meta)) + "with-meta" (fn [a b] (setv a (clone a)) (setv a.meta b) a) "atom" (fn [a] (Atom a)) "atom?" (fn [a] (instance? Atom a)) "deref" (fn [a] a.val) diff --git a/hy/mal_types.hy b/hy/mal_types.hy index eb0515cef1..4a30e6fdbd 100644 --- a/hy/mal_types.hy +++ b/hy/mal_types.hy @@ -1,6 +1,15 @@ +(import [types :as pytypes]) + (defclass MalException [Exception] (defn --init-- [self val] (setv self.val val))) (defclass Atom [] (defn --init-- [self val] (setv self.val val))) +(defn clone [obj] + (if (= (type obj) pytypes.FunctionType) + (pytypes.FunctionType obj.__code__ obj.__globals__ + :name obj.__name__ + :argdefs obj.__defaults__ + :closure obj.__closure__) + obj)) diff --git a/hy/stepA_mal.hy b/hy/stepA_mal.hy new file mode 100755 index 0000000000..080e6b4f3d --- /dev/null +++ b/hy/stepA_mal.hy @@ -0,0 +1,191 @@ +#!/usr/bin/env hy + +(import [hy.models [HyString :as Str HySymbol :as Sym]]) +(import sys traceback) +(import [mal_types [MalException]]) +(import [reader [read-str Blank]]) +(import [printer [pr-str]]) +(import [env [env-new env-get env-set env-find]]) +(import core) + +;; read +(defn READ [str] + (read-str str)) + +;; eval +(defn pair? [x] + (and (core.sequential? x) (> (len x) 0))) + +(defn QUASIQUOTE [ast] + (if + (not (pair? ast)) + (tuple [(Sym "quote") ast]) + + (= (Sym "unquote") (first ast)) + (nth ast 1) + + (and (pair? (first ast)) + (= (Sym "splice-unquote") (first (first ast)))) + (tuple [(Sym "concat") (nth (first ast) 1) (QUASIQUOTE (tuple (rest ast)))]) + + True + (tuple [(Sym "cons") (QUASIQUOTE (first ast)) (QUASIQUOTE (tuple (rest ast)))]))) + +(defn macro? [ast env] + (when (and (coll? ast) + (symbol? (first ast)) + (env-find env (first ast))) + (setv mac (env-get env (first ast))) + (and (hasattr mac "macro") + mac.macro))) + +(defn macroexpand [ast env] + (while (macro? ast env) + (setv mac (env-get env (first ast)) + ast (apply mac (tuple (rest ast))))) + ast) + + + +(defn eval-ast [ast env] + ;;(print "eval-ast:" ast (type ast)) + (if + (symbol? ast) (env-get env ast) + (instance? dict ast) (dict (map (fn [k] + [(EVAL k env) (EVAL (get ast k) env)]) + ast)) + (instance? tuple ast) (tuple (map (fn [x] (EVAL x env)) ast)) + (instance? list ast) (list (map (fn [x] (EVAL x env)) ast)) + True ast)) + +(defn EVAL [ast env] + ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) + (setv res None) + (while True + (if (not (instance? tuple ast)) + (setv res (eval-ast ast env)) + + ;; apply list + (do + (setv ast (macroexpand ast env)) + (if (not (instance? tuple ast)) + (setv res (eval-ast ast env)) + + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + (setv res ast) + + (= (Sym "def!") a0) + (setv res (env-set env a1 (EVAL a2 env))) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (setv ast a2) + (continue)) ;; TCO + + (= (Sym "quote") a0) + (setv res a1) + + (= (Sym "quasiquote") a0) + (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO + + (= (Sym "defmacro!") a0) + (do (setv func (EVAL a2 env) + func.macro True) + (setv res (env-set env a1 func))) + + (= (Sym "macroexpand") a0) + (setv res (macroexpand a1 env)) + + (= (Sym "try*") a0) + (setv res + (if (= (Sym "catch*") (nth a2 0)) + (try + (EVAL a1 env) + (except [e Exception] + (if (instance? MalException e) + (setv exc e.val) + (setv exc (Str (get e.args 0)))) + (EVAL (nth a2 2) (env-new env [(nth a2 1)] + [exc])))) + (EVAL a1 env))) + + (= (Sym "do") a0) + (do (eval-ast (list (butlast (rest ast))) env) + (setv ast (last ast)) + (continue)) ;; TCO + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (do (setv ast (nth ast 3)) (continue)) ;; TCO + (setv res None)) + (do (setv ast a2) (continue)))) ;; TCO + + (= (Sym "fn*") a0) + (setv func (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + func.ast a2 + func.env env + func.params a1 + res func) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (if (hasattr f "ast") + (do (setv ast f.ast + env (env-new f.env f.params args)) + (continue)) ;; TCO + (setv res (apply f args))))))))) + (break)) + res) + +;; print +(defn PRINT [exp] + (pr-str exp True)) + +;; repl +(def repl-env (env-new)) +(defn REP [str] + (PRINT (EVAL (READ str) repl-env))) + +;; core.hy: defined using Hy +(for [k core.ns] + (env-set repl-env (Sym k) (get core.ns k))) +(env-set repl-env (Sym "eval") (fn [ast] (EVAL ast repl-env))) +(env-set repl-env (Sym "*ARGV*") (tuple (map Str (rest (rest sys.argv))))) + +;; core.mal: defined using the language itself +(REP "(def! *host-language* \"Hy\")") +(REP "(def! not (fn* [a] (if a false true)))") +(REP "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") +(REP "(def! *gensym-counter* (atom 0))") +(REP "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))") +(REP "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))") + + +(when (>= (len sys.argv) 2) + (REP (+ "(load-file \"" (get sys.argv 1) "\")")) + (sys.exit 0)) + +(REP "(println (str \"Mal [\" *host-language* \"]\"))") +(while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) From e8f52c24f49d68a235d0a82f4df31f5562ed1274 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 21 Sep 2017 23:32:15 -0500 Subject: [PATCH 0484/2308] Hy: sync indenting. Define entry with defmain. --- hy/Makefile | 21 ++++++ hy/step0_repl.hy | 14 ++-- hy/step1_read_print.hy | 21 +++--- hy/step2_eval.hy | 43 +++++++------ hy/step3_env.hy | 65 ++++++++++--------- hy/step4_if_fn_do.hy | 107 ++++++++++++++++--------------- hy/step5_tco.hy | 120 +++++++++++++++++----------------- hy/step6_file.hy | 130 +++++++++++++++++++------------------ hy/step7_quote.hy | 142 +++++++++++++++++++++-------------------- hy/step8_macros.hy | 44 +++++++------ hy/step9_try.hy | 46 ++++++------- hy/stepA_mal.hy | 48 +++++++------- 12 files changed, 429 insertions(+), 372 deletions(-) create mode 100644 hy/Makefile diff --git a/hy/Makefile b/hy/Makefile new file mode 100644 index 0000000000..2e6a5d7b64 --- /dev/null +++ b/hy/Makefile @@ -0,0 +1,21 @@ +SOURCES_BASE = mal_types.hy reader.hy printer.hy +SOURCES_LISP = env.hy core.hy stepA_mal.hy +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +all: mal.hy + +mal.hy: stepA_mal.hy + cp $< $@ + +clean: + rm -f mal.hy *.pyc + +#.PHONY: stats tests $(TESTS) +.PHONY: stats + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/hy/step0_repl.hy b/hy/step0_repl.hy index 9596f056f6..d651bbf0ef 100755 --- a/hy/step0_repl.hy +++ b/hy/step0_repl.hy @@ -12,9 +12,11 @@ (defn REP [str] (PRINT (EVAL (READ str) {}))) -(while True - (try - (do (setv line (raw_input "user> ")) - (if (= "" line) (continue)) - (print (REP line))) - (except [EOFError] (break)))) +(defmain [&rest args] + ;; indented to match later steps + (while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break))))) diff --git a/hy/step1_read_print.hy b/hy/step1_read_print.hy index dcd384214c..179b09f884 100755 --- a/hy/step1_read_print.hy +++ b/hy/step1_read_print.hy @@ -16,12 +16,15 @@ (defn REP [str] (PRINT (EVAL (READ str) {}))) -(while True - (try - (do (setv line (raw_input "user> ")) - (if (= "" line) (continue)) - (print (REP line))) - (except [EOFError] (break)) - (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) +(defmain [&rest args] + ;; indented to match later steps + (while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception + (.exc_info sys)))))))) diff --git a/hy/step2_eval.hy b/hy/step2_eval.hy index d6b407b52c..6eb4b0f324 100755 --- a/hy/step2_eval.hy +++ b/hy/step2_eval.hy @@ -22,22 +22,26 @@ (defn EVAL [ast env] (if (not (instance? tuple ast)) - (eval-ast ast env) + (eval-ast ast env) - (if - (empty? ast) ast - (do - (setv el (eval-ast ast env) - f (first el) - args (rest el)) - (apply f args))))) + ;; apply list + ;; indented to match later steps + (if + (empty? ast) + ast + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (rest el)) + (apply f args))))) ;; print (defn PRINT [exp] (pr-str exp True)) ;; repl - (def repl-env {'+ + '- - '* * @@ -46,12 +50,15 @@ (defn REP [str] (PRINT (EVAL (READ str) repl-env))) -(while True - (try - (do (setv line (raw_input "user> ")) - (if (= "" line) (continue)) - (print (REP line))) - (except [EOFError] (break)) - (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) +(defmain [&rest args] + ;; indented to match later steps + (while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception + (.exc_info sys)))))))) diff --git a/hy/step3_env.hy b/hy/step3_env.hy index 95dc3fbbb2..2e46c3cfc3 100755 --- a/hy/step3_env.hy +++ b/hy/step3_env.hy @@ -25,38 +25,38 @@ (defn EVAL [ast env] ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) (if (not (instance? tuple ast)) - (eval-ast ast env) + (eval-ast ast env) - ;; apply list - (do - (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) - (if - (none? a0) - ast + ;; apply list + ;; indented to match later steps + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + ast - (= (Sym "def!") a0) - (env-set env a1 (EVAL a2 env)) + (= (Sym "def!") a0) + (env-set env a1 (EVAL a2 env)) - (= (Sym "let*") a0) - (do - (setv let-env (env-new env)) - (for [[b e] (partition a1 2)] - (env-set let-env b (EVAL e let-env))) - (EVAL a2 let-env)) + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (EVAL a2 env)) - ;; apply - (do - (setv el (eval-ast ast env) - f (first el) - args (rest el)) - (apply f args)))))) + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (rest el)) + (apply f args)))))) ;; print (defn PRINT [exp] (pr-str exp True)) ;; repl - (def repl-env {'+ + '- - '* * @@ -65,12 +65,15 @@ (defn REP [str] (PRINT (EVAL (READ str) repl-env))) -(while True - (try - (do (setv line (raw_input "user> ")) - (if (= "" line) (continue)) - (print (REP line))) - (except [EOFError] (break)) - (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) +(defmain [&rest args] + ;; indented to match later steps + (while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception + (.exc_info sys)))))))) diff --git a/hy/step4_if_fn_do.hy b/hy/step4_if_fn_do.hy index bf9c6ba737..0fd04c442b 100755 --- a/hy/step4_if_fn_do.hy +++ b/hy/step4_if_fn_do.hy @@ -26,49 +26,49 @@ (defn EVAL [ast env] ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) (if (not (instance? tuple ast)) - (eval-ast ast env) - - ;; apply list - (do - (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) - (if - (none? a0) - ast - - (= (Sym "def!") a0) - (env-set env a1 (EVAL a2 env)) - - (= (Sym "let*") a0) - (do - (setv let-env (env-new env)) - (for [[b e] (partition a1 2)] - (env-set let-env b (EVAL e let-env))) - (EVAL a2 let-env)) - - (= (Sym "do") a0) - (last (eval-ast (list (rest ast)) env)) - - (= (Sym "if") a0) - (do - (setv cond (EVAL a1 env)) - (if (or (none? cond) (and (instance? bool cond) - (= cond False))) - (if (> (len ast) 2) - (EVAL (nth ast 3) env) - None) - (EVAL a2 env))) - - (= (Sym "fn*") a0) - (fn [&rest args] - (EVAL a2 (env-new env a1 (or args [])))) - - ;; apply - (do - (setv el (eval-ast ast env) - f (first el) - args (list (rest el))) - ;;(print "f:" f "args:" args) - (apply f args)))))) + (eval-ast ast env) + + ;; apply list + ;; indented to match later steps + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + ast + + (= (Sym "def!") a0) + (env-set env a1 (EVAL a2 env)) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (EVAL a2 env)) + + (= (Sym "do") a0) + (last (eval-ast (list (rest ast)) env)) + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (EVAL (nth ast 3) env) + None) + (EVAL a2 env))) + + (= (Sym "fn*") a0) + (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (apply f args)))))) ;; print (defn PRINT [exp] @@ -86,12 +86,15 @@ ;; core.mal: defined using the language itself (REP "(def! not (fn* [a] (if a false true)))") -(while True - (try - (do (setv line (raw_input "user> ")) - (if (= "" line) (continue)) - (print (REP line))) - (except [EOFError] (break)) - (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) +(defmain [&rest args] + ;; indented to match later steps + (while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception + (.exc_info sys)))))))) diff --git a/hy/step5_tco.hy b/hy/step5_tco.hy index 6b4ee8fafa..8e6ec056c1 100755 --- a/hy/step5_tco.hy +++ b/hy/step5_tco.hy @@ -29,58 +29,59 @@ (while True (if (not (instance? tuple ast)) (setv res (eval-ast ast env)) - + ;; apply list - (do - (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) - (if - (none? a0) - (setv res ast) - - (= (Sym "def!") a0) - (setv res (env-set env a1 (EVAL a2 env))) - - (= (Sym "let*") a0) - (do - (setv env (env-new env)) - (for [[b e] (partition a1 2)] - (env-set env b (EVAL e env))) - (setv ast a2) - (continue)) ;; TCO - - (= (Sym "do") a0) - (do (eval-ast (list (butlast (rest ast))) env) - (setv ast (last ast)) - (continue)) ;; TCO - - (= (Sym "if") a0) - (do - (setv cond (EVAL a1 env)) - (if (or (none? cond) (and (instance? bool cond) - (= cond False))) - (if (> (len ast) 2) - (do (setv ast (nth ast 3)) (continue)) ;; TCO - (setv res None)) - (do (setv ast a2) (continue)))) ;; TCO - - (= (Sym "fn*") a0) - (setv func (fn [&rest args] - (EVAL a2 (env-new env a1 (or args [])))) - func.ast a2 - func.env env - func.params a1 - res func) - - ;; apply + ;; indented to match later steps (do - (setv el (eval-ast ast env) - f (first el) - args (list (rest el))) - (if (hasattr f "ast") - (do (setv ast f.ast - env (env-new f.env f.params args)) + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + (setv res ast) + + (= (Sym "def!") a0) + (setv res (env-set env a1 (EVAL a2 env))) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (setv ast a2) + (continue)) ;; TCO + + (= (Sym "do") a0) + (do (eval-ast (list (butlast (rest ast))) env) + (setv ast (last ast)) (continue)) ;; TCO - (setv res (apply f args))))))) + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (do (setv ast (nth ast 3)) (continue)) ;; TCO + (setv res None)) + (do (setv ast a2) (continue)))) ;; TCO + + (= (Sym "fn*") a0) + (setv func (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + func.ast a2 + func.env env + func.params a1 + res func) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (if (hasattr f "ast") + (do (setv ast f.ast + env (env-new f.env f.params args)) + (continue)) ;; TCO + (setv res (apply f args))))))) (break)) res) @@ -100,12 +101,15 @@ ;; core.mal: defined using the language itself (REP "(def! not (fn* [a] (if a false true)))") -(while True - (try - (do (setv line (raw_input "user> ")) - (if (= "" line) (continue)) - (print (REP line))) - (except [EOFError] (break)) - (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) +(defmain [&rest args] + ;; indented to match later steps + (while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception + (.exc_info sys)))))))) diff --git a/hy/step6_file.hy b/hy/step6_file.hy index 680a347928..8bd4d696d0 100755 --- a/hy/step6_file.hy +++ b/hy/step6_file.hy @@ -29,58 +29,59 @@ (while True (if (not (instance? tuple ast)) (setv res (eval-ast ast env)) - + ;; apply list - (do - (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) - (if - (none? a0) - (setv res ast) - - (= (Sym "def!") a0) - (setv res (env-set env a1 (EVAL a2 env))) - - (= (Sym "let*") a0) - (do - (setv env (env-new env)) - (for [[b e] (partition a1 2)] - (env-set env b (EVAL e env))) - (setv ast a2) - (continue)) ;; TCO - - (= (Sym "do") a0) - (do (eval-ast (list (butlast (rest ast))) env) - (setv ast (last ast)) - (continue)) ;; TCO - - (= (Sym "if") a0) - (do - (setv cond (EVAL a1 env)) - (if (or (none? cond) (and (instance? bool cond) - (= cond False))) - (if (> (len ast) 2) - (do (setv ast (nth ast 3)) (continue)) ;; TCO - (setv res None)) - (do (setv ast a2) (continue)))) ;; TCO - - (= (Sym "fn*") a0) - (setv func (fn [&rest args] - (EVAL a2 (env-new env a1 (or args [])))) - func.ast a2 - func.env env - func.params a1 - res func) - - ;; apply + ;; indented to match later steps (do - (setv el (eval-ast ast env) - f (first el) - args (list (rest el))) - (if (hasattr f "ast") - (do (setv ast f.ast - env (env-new f.env f.params args)) + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + (setv res ast) + + (= (Sym "def!") a0) + (setv res (env-set env a1 (EVAL a2 env))) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (setv ast a2) + (continue)) ;; TCO + + (= (Sym "do") a0) + (do (eval-ast (list (butlast (rest ast))) env) + (setv ast (last ast)) (continue)) ;; TCO - (setv res (apply f args))))))) + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (do (setv ast (nth ast 3)) (continue)) ;; TCO + (setv res None)) + (do (setv ast a2) (continue)))) ;; TCO + + (= (Sym "fn*") a0) + (setv func (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + func.ast a2 + func.env env + func.params a1 + res func) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (if (hasattr f "ast") + (do (setv ast f.ast + env (env-new f.env f.params args)) + (continue)) ;; TCO + (setv res (apply f args))))))) (break)) res) @@ -97,22 +98,25 @@ (for [k core.ns] (env-set repl-env (Sym k) (get core.ns k))) (env-set repl-env (Sym "eval") (fn [ast] (EVAL ast repl-env))) -(env-set repl-env (Sym "*ARGV*") (tuple (map Str (rest (rest sys.argv))))) +(env-set repl-env (Sym "*ARGV*") (, )) ;; core.mal: defined using the language itself (REP "(def! not (fn* [a] (if a false true)))") (REP "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") -(when (>= (len sys.argv) 2) - (REP (+ "(load-file \"" (get sys.argv 1) "\")")) - (sys.exit 0)) - -(while True - (try - (do (setv line (raw_input "user> ")) - (if (= "" line) (continue)) - (print (REP line))) - (except [EOFError] (break)) - (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) +(defmain [&rest args] + (if (>= (len args) 2) + (do + (env-set repl-env (Sym "*ARGV*") (tuple (map Str (rest (rest args))))) + (REP (+ "(load-file \"" (get args 1) "\")"))) + (do + (while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception + (.exc_info sys)))))))))) diff --git a/hy/step7_quote.hy b/hy/step7_quote.hy index 17e6db7f1d..532e970c53 100755 --- a/hy/step7_quote.hy +++ b/hy/step7_quote.hy @@ -47,64 +47,65 @@ (while True (if (not (instance? tuple ast)) (setv res (eval-ast ast env)) - + ;; apply list - (do - (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) - (if - (none? a0) - (setv res ast) - - (= (Sym "def!") a0) - (setv res (env-set env a1 (EVAL a2 env))) - - (= (Sym "let*") a0) - (do - (setv env (env-new env)) - (for [[b e] (partition a1 2)] - (env-set env b (EVAL e env))) - (setv ast a2) - (continue)) ;; TCO - - (= (Sym "quote") a0) - (setv res a1) - - (= (Sym "quasiquote") a0) - (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO - - (= (Sym "do") a0) - (do (eval-ast (list (butlast (rest ast))) env) - (setv ast (last ast)) - (continue)) ;; TCO - - (= (Sym "if") a0) + ;; indented to match later steps (do - (setv cond (EVAL a1 env)) - (if (or (none? cond) (and (instance? bool cond) - (= cond False))) - (if (> (len ast) 2) - (do (setv ast (nth ast 3)) (continue)) ;; TCO - (setv res None)) - (do (setv ast a2) (continue)))) ;; TCO - - (= (Sym "fn*") a0) - (setv func (fn [&rest args] - (EVAL a2 (env-new env a1 (or args [])))) - func.ast a2 - func.env env - func.params a1 - res func) - - ;; apply - (do - (setv el (eval-ast ast env) - f (first el) - args (list (rest el))) - (if (hasattr f "ast") - (do (setv ast f.ast - env (env-new f.env f.params args)) + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + (setv res ast) + + (= (Sym "def!") a0) + (setv res (env-set env a1 (EVAL a2 env))) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (setv ast a2) + (continue)) ;; TCO + + (= (Sym "quote") a0) + (setv res a1) + + (= (Sym "quasiquote") a0) + (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO + + (= (Sym "do") a0) + (do (eval-ast (list (butlast (rest ast))) env) + (setv ast (last ast)) (continue)) ;; TCO - (setv res (apply f args))))))) + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (do (setv ast (nth ast 3)) (continue)) ;; TCO + (setv res None)) + (do (setv ast a2) (continue)))) ;; TCO + + (= (Sym "fn*") a0) + (setv func (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + func.ast a2 + func.env env + func.params a1 + res func) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (if (hasattr f "ast") + (do (setv ast f.ast + env (env-new f.env f.params args)) + (continue)) ;; TCO + (setv res (apply f args))))))) (break)) res) @@ -121,22 +122,25 @@ (for [k core.ns] (env-set repl-env (Sym k) (get core.ns k))) (env-set repl-env (Sym "eval") (fn [ast] (EVAL ast repl-env))) -(env-set repl-env (Sym "*ARGV*") (tuple (map Str (rest (rest sys.argv))))) +(env-set repl-env (Sym "*ARGV*") (, )) ;; core.mal: defined using the language itself (REP "(def! not (fn* [a] (if a false true)))") (REP "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))") -(when (>= (len sys.argv) 2) - (REP (+ "(load-file \"" (get sys.argv 1) "\")")) - (sys.exit 0)) - -(while True - (try - (do (setv line (raw_input "user> ")) - (if (= "" line) (continue)) - (print (REP line))) - (except [EOFError] (break)) - (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) +(defmain [&rest args] + (if (>= (len args) 2) + (do + (env-set repl-env (Sym "*ARGV*") (tuple (map Str (rest (rest args))))) + (REP (+ "(load-file \"" (get args 1) "\")"))) + (do + (while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception + (.exc_info sys)))))))))) diff --git a/hy/step8_macros.hy b/hy/step8_macros.hy index 740c751b80..d0574ccda9 100755 --- a/hy/step8_macros.hy +++ b/hy/step8_macros.hy @@ -63,7 +63,7 @@ (while True (if (not (instance? tuple ast)) (setv res (eval-ast ast env)) - + ;; apply list (do (setv ast (macroexpand ast env)) @@ -75,10 +75,10 @@ (if (none? a0) (setv res ast) - + (= (Sym "def!") a0) (setv res (env-set env a1 (EVAL a2 env))) - + (= (Sym "let*") a0) (do (setv env (env-new env)) @@ -92,7 +92,7 @@ (= (Sym "quasiquote") a0) (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO - + (= (Sym "defmacro!") a0) (do (setv func (EVAL a2 env) func.macro True) @@ -105,7 +105,7 @@ (do (eval-ast (list (butlast (rest ast))) env) (setv ast (last ast)) (continue)) ;; TCO - + (= (Sym "if") a0) (do (setv cond (EVAL a1 env)) @@ -115,7 +115,7 @@ (do (setv ast (nth ast 3)) (continue)) ;; TCO (setv res None)) (do (setv ast a2) (continue)))) ;; TCO - + (= (Sym "fn*") a0) (setv func (fn [&rest args] (EVAL a2 (env-new env a1 (or args [])))) @@ -150,7 +150,7 @@ (for [k core.ns] (env-set repl-env (Sym k) (get core.ns k))) (env-set repl-env (Sym "eval") (fn [ast] (EVAL ast repl-env))) -(env-set repl-env (Sym "*ARGV*") (tuple (map Str (rest (rest sys.argv))))) +(env-set repl-env (Sym "*ARGV*") (, )) ;; core.mal: defined using the language itself (REP "(def! not (fn* [a] (if a false true)))") @@ -158,17 +158,19 @@ (REP "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") (REP "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") - -(when (>= (len sys.argv) 2) - (REP (+ "(load-file \"" (get sys.argv 1) "\")")) - (sys.exit 0)) - -(while True - (try - (do (setv line (raw_input "user> ")) - (if (= "" line) (continue)) - (print (REP line))) - (except [EOFError] (break)) - (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) +(defmain [&rest args] + (if (>= (len args) 2) + (do + (env-set repl-env (Sym "*ARGV*") (tuple (map Str (rest (rest args))))) + (REP (+ "(load-file \"" (get args 1) "\")"))) + (do + (while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception + (.exc_info sys)))))))))) diff --git a/hy/step9_try.hy b/hy/step9_try.hy index 2e2f4edd87..ae5f936f93 100755 --- a/hy/step9_try.hy +++ b/hy/step9_try.hy @@ -64,7 +64,7 @@ (while True (if (not (instance? tuple ast)) (setv res (eval-ast ast env)) - + ;; apply list (do (setv ast (macroexpand ast env)) @@ -76,10 +76,10 @@ (if (none? a0) (setv res ast) - + (= (Sym "def!") a0) (setv res (env-set env a1 (EVAL a2 env))) - + (= (Sym "let*") a0) (do (setv env (env-new env)) @@ -93,7 +93,7 @@ (= (Sym "quasiquote") a0) (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO - + (= (Sym "defmacro!") a0) (do (setv func (EVAL a2 env) func.macro True) @@ -114,12 +114,12 @@ (EVAL (nth a2 2) (env-new env [(nth a2 1)] [exc])))) (EVAL a1 env))) - + (= (Sym "do") a0) (do (eval-ast (list (butlast (rest ast))) env) (setv ast (last ast)) (continue)) ;; TCO - + (= (Sym "if") a0) (do (setv cond (EVAL a1 env)) @@ -129,7 +129,7 @@ (do (setv ast (nth ast 3)) (continue)) ;; TCO (setv res None)) (do (setv ast a2) (continue)))) ;; TCO - + (= (Sym "fn*") a0) (setv func (fn [&rest args] (EVAL a2 (env-new env a1 (or args [])))) @@ -164,7 +164,7 @@ (for [k core.ns] (env-set repl-env (Sym k) (get core.ns k))) (env-set repl-env (Sym "eval") (fn [ast] (EVAL ast repl-env))) -(env-set repl-env (Sym "*ARGV*") (tuple (map Str (rest (rest sys.argv))))) +(env-set repl-env (Sym "*ARGV*") (, )) ;; core.mal: defined using the language itself (REP "(def! not (fn* [a] (if a false true)))") @@ -172,17 +172,19 @@ (REP "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") (REP "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") - -(when (>= (len sys.argv) 2) - (REP (+ "(load-file \"" (get sys.argv 1) "\")")) - (sys.exit 0)) - -(while True - (try - (do (setv line (raw_input "user> ")) - (if (= "" line) (continue)) - (print (REP line))) - (except [EOFError] (break)) - (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) +(defmain [&rest args] + (if (>= (len args) 2) + (do + (env-set repl-env (Sym "*ARGV*") (tuple (map Str (rest (rest args))))) + (REP (+ "(load-file \"" (get args 1) "\")"))) + (do + (while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception + (.exc_info sys)))))))))) diff --git a/hy/stepA_mal.hy b/hy/stepA_mal.hy index 080e6b4f3d..5d9a9fb4fe 100755 --- a/hy/stepA_mal.hy +++ b/hy/stepA_mal.hy @@ -64,7 +64,7 @@ (while True (if (not (instance? tuple ast)) (setv res (eval-ast ast env)) - + ;; apply list (do (setv ast (macroexpand ast env)) @@ -76,10 +76,10 @@ (if (none? a0) (setv res ast) - + (= (Sym "def!") a0) (setv res (env-set env a1 (EVAL a2 env))) - + (= (Sym "let*") a0) (do (setv env (env-new env)) @@ -93,7 +93,7 @@ (= (Sym "quasiquote") a0) (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO - + (= (Sym "defmacro!") a0) (do (setv func (EVAL a2 env) func.macro True) @@ -114,12 +114,12 @@ (EVAL (nth a2 2) (env-new env [(nth a2 1)] [exc])))) (EVAL a1 env))) - + (= (Sym "do") a0) (do (eval-ast (list (butlast (rest ast))) env) (setv ast (last ast)) (continue)) ;; TCO - + (= (Sym "if") a0) (do (setv cond (EVAL a1 env)) @@ -129,7 +129,7 @@ (do (setv ast (nth ast 3)) (continue)) ;; TCO (setv res None)) (do (setv ast a2) (continue)))) ;; TCO - + (= (Sym "fn*") a0) (setv func (fn [&rest args] (EVAL a2 (env-new env a1 (or args [])))) @@ -164,7 +164,7 @@ (for [k core.ns] (env-set repl-env (Sym k) (get core.ns k))) (env-set repl-env (Sym "eval") (fn [ast] (EVAL ast repl-env))) -(env-set repl-env (Sym "*ARGV*") (tuple (map Str (rest (rest sys.argv))))) +(env-set repl-env (Sym "*ARGV*") (, )) ;; core.mal: defined using the language itself (REP "(def! *host-language* \"Hy\")") @@ -174,18 +174,20 @@ (REP "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))") (REP "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))") - -(when (>= (len sys.argv) 2) - (REP (+ "(load-file \"" (get sys.argv 1) "\")")) - (sys.exit 0)) - -(REP "(println (str \"Mal [\" *host-language* \"]\"))") -(while True - (try - (do (setv line (raw_input "user> ")) - (if (= "" line) (continue)) - (print (REP line))) - (except [EOFError] (break)) - (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception (.exc_info sys))))))) +(defmain [&rest args] + (if (>= (len args) 2) + (do + (env-set repl-env (Sym "*ARGV*") (tuple (map Str (rest (rest args))))) + (REP (+ "(load-file \"" (get args 1) "\")"))) + (do + (REP "(println (str \"Mal [\" *host-language* \"]\"))") + (while True + (try + (do (setv line (raw_input "user> ")) + (if (= "" line) (continue)) + (print (REP line))) + (except [EOFError] (break)) + (except [Blank]) + (except [] + (print (.join "" (apply traceback.format_exception + (.exc_info sys)))))))))) From bf794d15b5a3029bfdc75b6fb16a2f2043b19345 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 21 Sep 2017 23:44:33 -0500 Subject: [PATCH 0485/2308] Hy: add Dockerfile --- hy/Dockerfile | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 hy/Dockerfile diff --git a/hy/Dockerfile b/hy/Dockerfile new file mode 100644 index 0000000000..c984900b5f --- /dev/null +++ b/hy/Dockerfile @@ -0,0 +1,28 @@ +FROM ubuntu:xenial +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# Hy +RUN apt-get -y install python-pip +RUN pip install hy && \ + mkdir /.cache && \ + chmod uog+rwx /.cache From 1872f735e440719c6134d16557592ba0f0fe79d5 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 21 Sep 2017 23:58:14 -0500 Subject: [PATCH 0486/2308] Hy: simplify main eval loop. Dockerfile cleanup. --- hy/Dockerfile | 4 +- hy/step2_eval.hy | 26 ++++---- hy/step3_env.hy | 48 +++++++------- hy/step4_if_fn_do.hy | 90 ++++++++++++------------- hy/step5_tco.hy | 110 +++++++++++++++--------------- hy/step6_file.hy | 108 +++++++++++++++--------------- hy/step7_quote.hy | 120 +++++++++++++++++---------------- hy/step8_macros.hy | 144 ++++++++++++++++++++-------------------- hy/step9_try.hy | 155 ++++++++++++++++++++++--------------------- hy/stepA_mal.hy | 155 ++++++++++++++++++++++--------------------- 10 files changed, 485 insertions(+), 475 deletions(-) diff --git a/hy/Dockerfile b/hy/Dockerfile index c984900b5f..4d977ee8b2 100644 --- a/hy/Dockerfile +++ b/hy/Dockerfile @@ -22,7 +22,7 @@ WORKDIR /mal ########################################################## # Hy -RUN apt-get -y install python-pip -RUN pip install hy && \ +RUN apt-get -y install python-pip && \ + pip install hy && \ mkdir /.cache && \ chmod uog+rwx /.cache diff --git a/hy/step2_eval.hy b/hy/step2_eval.hy index 6eb4b0f324..7d8b8fe7a0 100755 --- a/hy/step2_eval.hy +++ b/hy/step2_eval.hy @@ -21,21 +21,21 @@ True ast)) (defn EVAL [ast env] - (if (not (instance? tuple ast)) - (eval-ast ast env) + ;; indented to match later steps + (if (not (instance? tuple ast)) + (eval-ast ast env) - ;; apply list - ;; indented to match later steps - (if - (empty? ast) - ast + ;; apply list + (if + (empty? ast) + ast - ;; apply - (do - (setv el (eval-ast ast env) - f (first el) - args (rest el)) - (apply f args))))) + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (apply f args))))) ;; print (defn PRINT [exp] diff --git a/hy/step3_env.hy b/hy/step3_env.hy index 2e46c3cfc3..94c9fad380 100755 --- a/hy/step3_env.hy +++ b/hy/step3_env.hy @@ -23,34 +23,34 @@ True ast)) (defn EVAL [ast env] - ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) - (if (not (instance? tuple ast)) - (eval-ast ast env) + ;;(print "EVAL:" ast (type ast)) + ;; indented to match later steps + (if (not (instance? tuple ast)) + (eval-ast ast env) - ;; apply list - ;; indented to match later steps - (do - (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) - (if - (none? a0) - ast + ;; apply list + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + ast - (= (Sym "def!") a0) - (env-set env a1 (EVAL a2 env)) + (= (Sym "def!") a0) + (env-set env a1 (EVAL a2 env)) - (= (Sym "let*") a0) - (do - (setv env (env-new env)) - (for [[b e] (partition a1 2)] - (env-set env b (EVAL e env))) - (EVAL a2 env)) + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (EVAL a2 env)) - ;; apply - (do - (setv el (eval-ast ast env) - f (first el) - args (rest el)) - (apply f args)))))) + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (apply f args)))))) ;; print (defn PRINT [exp] diff --git a/hy/step4_if_fn_do.hy b/hy/step4_if_fn_do.hy index 0fd04c442b..b75831d08d 100755 --- a/hy/step4_if_fn_do.hy +++ b/hy/step4_if_fn_do.hy @@ -24,51 +24,51 @@ True ast)) (defn EVAL [ast env] - ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) - (if (not (instance? tuple ast)) - (eval-ast ast env) - - ;; apply list - ;; indented to match later steps - (do - (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) - (if - (none? a0) - ast - - (= (Sym "def!") a0) - (env-set env a1 (EVAL a2 env)) - - (= (Sym "let*") a0) - (do - (setv env (env-new env)) - (for [[b e] (partition a1 2)] - (env-set env b (EVAL e env))) - (EVAL a2 env)) - - (= (Sym "do") a0) - (last (eval-ast (list (rest ast)) env)) - - (= (Sym "if") a0) - (do - (setv cond (EVAL a1 env)) - (if (or (none? cond) (and (instance? bool cond) - (= cond False))) - (if (> (len ast) 2) - (EVAL (nth ast 3) env) - None) - (EVAL a2 env))) - - (= (Sym "fn*") a0) - (fn [&rest args] - (EVAL a2 (env-new env a1 (or args [])))) - - ;; apply - (do - (setv el (eval-ast ast env) - f (first el) - args (list (rest el))) - (apply f args)))))) + ;;(print "EVAL:" ast (type ast)) + ;; indented to match later steps + (if (not (instance? tuple ast)) + (eval-ast ast env) + + ;; apply list + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + ast + + (= (Sym "def!") a0) + (env-set env a1 (EVAL a2 env)) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (EVAL a2 env)) + + (= (Sym "do") a0) + (last (eval-ast (list (rest ast)) env)) + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (EVAL (nth ast 3) env) + None) + (EVAL a2 env))) + + (= (Sym "fn*") a0) + (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (apply f args)))))) ;; print (defn PRINT [exp] diff --git a/hy/step5_tco.hy b/hy/step5_tco.hy index 8e6ec056c1..55e07d67aa 100755 --- a/hy/step5_tco.hy +++ b/hy/step5_tco.hy @@ -24,64 +24,66 @@ True ast)) (defn EVAL [ast env] - ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) + ;;(print "EVAL:" ast (type ast)) + ;; indented to match later steps (setv res None) (while True - (if (not (instance? tuple ast)) - (setv res (eval-ast ast env)) - - ;; apply list - ;; indented to match later steps - (do - (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) - (if - (none? a0) - (setv res ast) - - (= (Sym "def!") a0) - (setv res (env-set env a1 (EVAL a2 env))) - - (= (Sym "let*") a0) - (do - (setv env (env-new env)) - (for [[b e] (partition a1 2)] - (env-set env b (EVAL e env))) - (setv ast a2) - (continue)) ;; TCO - - (= (Sym "do") a0) - (do (eval-ast (list (butlast (rest ast))) env) - (setv ast (last ast)) + (setv res + (if (not (instance? tuple ast)) + (eval-ast ast env) + + ;; apply list + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + ast + + (= (Sym "def!") a0) + (env-set env a1 (EVAL a2 env)) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (setv ast a2) (continue)) ;; TCO - (= (Sym "if") a0) - (do - (setv cond (EVAL a1 env)) - (if (or (none? cond) (and (instance? bool cond) - (= cond False))) - (if (> (len ast) 2) - (do (setv ast (nth ast 3)) (continue)) ;; TCO - (setv res None)) - (do (setv ast a2) (continue)))) ;; TCO - - (= (Sym "fn*") a0) - (setv func (fn [&rest args] - (EVAL a2 (env-new env a1 (or args [])))) - func.ast a2 - func.env env - func.params a1 - res func) - - ;; apply - (do - (setv el (eval-ast ast env) - f (first el) - args (list (rest el))) - (if (hasattr f "ast") - (do (setv ast f.ast - env (env-new f.env f.params args)) - (continue)) ;; TCO - (setv res (apply f args))))))) + (= (Sym "do") a0) + (do (eval-ast (list (butlast (rest ast))) env) + (setv ast (last ast)) + (continue)) ;; TCO + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (do (setv ast (nth ast 3)) (continue)) ;; TCO + None) + (do (setv ast a2) (continue)))) ;; TCO + + (= (Sym "fn*") a0) + (do + (setv func (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + func.ast a2 + func.env env + func.params a1) + func) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (if (hasattr f "ast") + (do (setv ast f.ast + env (env-new f.env f.params args)) + (continue)) ;; TCO + (apply f args))))))) (break)) res) diff --git a/hy/step6_file.hy b/hy/step6_file.hy index 8bd4d696d0..e74ec2dc17 100755 --- a/hy/step6_file.hy +++ b/hy/step6_file.hy @@ -25,63 +25,65 @@ (defn EVAL [ast env] ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) + ;; indented to match later steps (setv res None) (while True - (if (not (instance? tuple ast)) - (setv res (eval-ast ast env)) - - ;; apply list - ;; indented to match later steps - (do - (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) - (if - (none? a0) - (setv res ast) - - (= (Sym "def!") a0) - (setv res (env-set env a1 (EVAL a2 env))) - - (= (Sym "let*") a0) - (do - (setv env (env-new env)) - (for [[b e] (partition a1 2)] - (env-set env b (EVAL e env))) - (setv ast a2) - (continue)) ;; TCO - - (= (Sym "do") a0) - (do (eval-ast (list (butlast (rest ast))) env) - (setv ast (last ast)) + (setv res + (if (not (instance? tuple ast)) + (eval-ast ast env) + + ;; apply list + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + ast + + (= (Sym "def!") a0) + (env-set env a1 (EVAL a2 env)) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (setv ast a2) (continue)) ;; TCO - (= (Sym "if") a0) - (do - (setv cond (EVAL a1 env)) - (if (or (none? cond) (and (instance? bool cond) - (= cond False))) - (if (> (len ast) 2) - (do (setv ast (nth ast 3)) (continue)) ;; TCO - (setv res None)) - (do (setv ast a2) (continue)))) ;; TCO - - (= (Sym "fn*") a0) - (setv func (fn [&rest args] - (EVAL a2 (env-new env a1 (or args [])))) - func.ast a2 - func.env env - func.params a1 - res func) - - ;; apply - (do - (setv el (eval-ast ast env) - f (first el) - args (list (rest el))) - (if (hasattr f "ast") - (do (setv ast f.ast - env (env-new f.env f.params args)) - (continue)) ;; TCO - (setv res (apply f args))))))) + (= (Sym "do") a0) + (do (eval-ast (list (butlast (rest ast))) env) + (setv ast (last ast)) + (continue)) ;; TCO + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (do (setv ast (nth ast 3)) (continue)) ;; TCO + None) + (do (setv ast a2) (continue)))) ;; TCO + + (= (Sym "fn*") a0) + (do + (setv func (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + func.ast a2 + func.env env + func.params a1) + func) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (if (hasattr f "ast") + (do (setv ast f.ast + env (env-new f.env f.params args)) + (continue)) ;; TCO + (apply f args))))))) (break)) res) diff --git a/hy/step7_quote.hy b/hy/step7_quote.hy index 532e970c53..c78259a730 100755 --- a/hy/step7_quote.hy +++ b/hy/step7_quote.hy @@ -43,69 +43,71 @@ (defn EVAL [ast env] ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) + ;; indented to match later steps (setv res None) (while True - (if (not (instance? tuple ast)) - (setv res (eval-ast ast env)) - - ;; apply list - ;; indented to match later steps - (do - (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) - (if - (none? a0) - (setv res ast) - - (= (Sym "def!") a0) - (setv res (env-set env a1 (EVAL a2 env))) - - (= (Sym "let*") a0) - (do - (setv env (env-new env)) - (for [[b e] (partition a1 2)] - (env-set env b (EVAL e env))) - (setv ast a2) - (continue)) ;; TCO - - (= (Sym "quote") a0) - (setv res a1) - - (= (Sym "quasiquote") a0) - (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO - - (= (Sym "do") a0) - (do (eval-ast (list (butlast (rest ast))) env) - (setv ast (last ast)) + (setv res + (if (not (instance? tuple ast)) + (eval-ast ast env) + + ;; apply list + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + ast + + (= (Sym "def!") a0) + (env-set env a1 (EVAL a2 env)) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (setv ast a2) (continue)) ;; TCO - (= (Sym "if") a0) - (do - (setv cond (EVAL a1 env)) - (if (or (none? cond) (and (instance? bool cond) - (= cond False))) - (if (> (len ast) 2) - (do (setv ast (nth ast 3)) (continue)) ;; TCO - (setv res None)) - (do (setv ast a2) (continue)))) ;; TCO - - (= (Sym "fn*") a0) - (setv func (fn [&rest args] - (EVAL a2 (env-new env a1 (or args [])))) - func.ast a2 - func.env env - func.params a1 - res func) - - ;; apply - (do - (setv el (eval-ast ast env) - f (first el) - args (list (rest el))) - (if (hasattr f "ast") - (do (setv ast f.ast - env (env-new f.env f.params args)) - (continue)) ;; TCO - (setv res (apply f args))))))) + (= (Sym "quote") a0) + a1 + + (= (Sym "quasiquote") a0) + (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO + + (= (Sym "do") a0) + (do (eval-ast (list (butlast (rest ast))) env) + (setv ast (last ast)) + (continue)) ;; TCO + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (do (setv ast (nth ast 3)) (continue)) ;; TCO + None) + (do (setv ast a2) (continue)))) ;; TCO + + (= (Sym "fn*") a0) + (do + (setv func (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + func.ast a2 + func.env env + func.params a1) + func) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (if (hasattr f "ast") + (do (setv ast f.ast + env (env-new f.env f.params args)) + (continue)) ;; TCO + (apply f args))))))) (break)) res) diff --git a/hy/step8_macros.hy b/hy/step8_macros.hy index d0574ccda9..68832345d4 100755 --- a/hy/step8_macros.hy +++ b/hy/step8_macros.hy @@ -61,79 +61,81 @@ ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) (setv res None) (while True - (if (not (instance? tuple ast)) - (setv res (eval-ast ast env)) - - ;; apply list - (do - (setv ast (macroexpand ast env)) - (if (not (instance? tuple ast)) - (setv res (eval-ast ast env)) - - (do - (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) - (if - (none? a0) - (setv res ast) - - (= (Sym "def!") a0) - (setv res (env-set env a1 (EVAL a2 env))) - - (= (Sym "let*") a0) - (do - (setv env (env-new env)) - (for [[b e] (partition a1 2)] - (env-set env b (EVAL e env))) - (setv ast a2) - (continue)) ;; TCO - - (= (Sym "quote") a0) - (setv res a1) - - (= (Sym "quasiquote") a0) - (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO - - (= (Sym "defmacro!") a0) - (do (setv func (EVAL a2 env) - func.macro True) - (setv res (env-set env a1 func))) - - (= (Sym "macroexpand") a0) - (setv res (macroexpand a1 env)) - - (= (Sym "do") a0) - (do (eval-ast (list (butlast (rest ast))) env) - (setv ast (last ast)) + (setv res + (if (not (instance? tuple ast)) + (eval-ast ast env) + + ;; apply list + (do + (setv ast (macroexpand ast env)) + (if (not (instance? tuple ast)) + (eval-ast ast env) + + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + ast + + (= (Sym "def!") a0) + (env-set env a1 (EVAL a2 env)) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (setv ast a2) (continue)) ;; TCO - (= (Sym "if") a0) - (do - (setv cond (EVAL a1 env)) - (if (or (none? cond) (and (instance? bool cond) - (= cond False))) - (if (> (len ast) 2) - (do (setv ast (nth ast 3)) (continue)) ;; TCO - (setv res None)) - (do (setv ast a2) (continue)))) ;; TCO - - (= (Sym "fn*") a0) - (setv func (fn [&rest args] - (EVAL a2 (env-new env a1 (or args [])))) - func.ast a2 - func.env env - func.params a1 - res func) - - ;; apply - (do - (setv el (eval-ast ast env) - f (first el) - args (list (rest el))) - (if (hasattr f "ast") - (do (setv ast f.ast - env (env-new f.env f.params args)) - (continue)) ;; TCO - (setv res (apply f args))))))))) + (= (Sym "quote") a0) + a1 + + (= (Sym "quasiquote") a0) + (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO + + (= (Sym "defmacro!") a0) + (do (setv func (EVAL a2 env) + func.macro True) + (env-set env a1 func)) + + (= (Sym "macroexpand") a0) + (macroexpand a1 env) + + (= (Sym "do") a0) + (do (eval-ast (list (butlast (rest ast))) env) + (setv ast (last ast)) + (continue)) ;; TCO + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (do (setv ast (nth ast 3)) (continue)) ;; TCO + None) + (do (setv ast a2) (continue)))) ;; TCO + + (= (Sym "fn*") a0) + (do + (setv func (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + func.ast a2 + func.env env + func.params a1) + func) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (if (hasattr f "ast") + (do (setv ast f.ast + env (env-new f.env f.params args)) + (continue)) ;; TCO + (apply f args))))))))) (break)) res) diff --git a/hy/step9_try.hy b/hy/step9_try.hy index ae5f936f93..b1a7d50ced 100755 --- a/hy/step9_try.hy +++ b/hy/step9_try.hy @@ -62,48 +62,48 @@ ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) (setv res None) (while True - (if (not (instance? tuple ast)) - (setv res (eval-ast ast env)) - - ;; apply list - (do - (setv ast (macroexpand ast env)) - (if (not (instance? tuple ast)) - (setv res (eval-ast ast env)) - - (do - (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) - (if - (none? a0) - (setv res ast) - - (= (Sym "def!") a0) - (setv res (env-set env a1 (EVAL a2 env))) - - (= (Sym "let*") a0) - (do - (setv env (env-new env)) - (for [[b e] (partition a1 2)] - (env-set env b (EVAL e env))) - (setv ast a2) - (continue)) ;; TCO - - (= (Sym "quote") a0) - (setv res a1) - - (= (Sym "quasiquote") a0) - (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO - - (= (Sym "defmacro!") a0) - (do (setv func (EVAL a2 env) - func.macro True) - (setv res (env-set env a1 func))) - - (= (Sym "macroexpand") a0) - (setv res (macroexpand a1 env)) - - (= (Sym "try*") a0) - (setv res + (setv res + (if (not (instance? tuple ast)) + (eval-ast ast env) + + ;; apply list + (do + (setv ast (macroexpand ast env)) + (if (not (instance? tuple ast)) + (eval-ast ast env) + + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + ast + + (= (Sym "def!") a0) + (env-set env a1 (EVAL a2 env)) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (setv ast a2) + (continue)) ;; TCO + + (= (Sym "quote") a0) + a1 + + (= (Sym "quasiquote") a0) + (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO + + (= (Sym "defmacro!") a0) + (do (setv func (EVAL a2 env) + func.macro True) + (env-set env a1 func)) + + (= (Sym "macroexpand") a0) + (macroexpand a1 env) + + (= (Sym "try*") a0) (if (= (Sym "catch*") (nth a2 0)) (try (EVAL a1 env) @@ -113,41 +113,42 @@ (setv exc (Str (get e.args 0)))) (EVAL (nth a2 2) (env-new env [(nth a2 1)] [exc])))) - (EVAL a1 env))) - - (= (Sym "do") a0) - (do (eval-ast (list (butlast (rest ast))) env) - (setv ast (last ast)) - (continue)) ;; TCO - - (= (Sym "if") a0) - (do - (setv cond (EVAL a1 env)) - (if (or (none? cond) (and (instance? bool cond) - (= cond False))) - (if (> (len ast) 2) - (do (setv ast (nth ast 3)) (continue)) ;; TCO - (setv res None)) - (do (setv ast a2) (continue)))) ;; TCO - - (= (Sym "fn*") a0) - (setv func (fn [&rest args] - (EVAL a2 (env-new env a1 (or args [])))) - func.ast a2 - func.env env - func.params a1 - res func) - - ;; apply - (do - (setv el (eval-ast ast env) - f (first el) - args (list (rest el))) - (if (hasattr f "ast") - (do (setv ast f.ast - env (env-new f.env f.params args)) - (continue)) ;; TCO - (setv res (apply f args))))))))) + (EVAL a1 env)) + + (= (Sym "do") a0) + (do (eval-ast (list (butlast (rest ast))) env) + (setv ast (last ast)) + (continue)) ;; TCO + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (do (setv ast (nth ast 3)) (continue)) ;; TCO + None) + (do (setv ast a2) (continue)))) ;; TCO + + (= (Sym "fn*") a0) + (do + (setv func (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + func.ast a2 + func.env env + func.params a1) + func) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (if (hasattr f "ast") + (do (setv ast f.ast + env (env-new f.env f.params args)) + (continue)) ;; TCO + (apply f args))))))))) (break)) res) diff --git a/hy/stepA_mal.hy b/hy/stepA_mal.hy index 5d9a9fb4fe..67c9943417 100755 --- a/hy/stepA_mal.hy +++ b/hy/stepA_mal.hy @@ -62,48 +62,48 @@ ;;(print "EVAL:" ast (type ast) (instance? tuple ast)) (setv res None) (while True - (if (not (instance? tuple ast)) - (setv res (eval-ast ast env)) - - ;; apply list - (do - (setv ast (macroexpand ast env)) - (if (not (instance? tuple ast)) - (setv res (eval-ast ast env)) - - (do - (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) - (if - (none? a0) - (setv res ast) - - (= (Sym "def!") a0) - (setv res (env-set env a1 (EVAL a2 env))) - - (= (Sym "let*") a0) - (do - (setv env (env-new env)) - (for [[b e] (partition a1 2)] - (env-set env b (EVAL e env))) - (setv ast a2) - (continue)) ;; TCO - - (= (Sym "quote") a0) - (setv res a1) - - (= (Sym "quasiquote") a0) - (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO - - (= (Sym "defmacro!") a0) - (do (setv func (EVAL a2 env) - func.macro True) - (setv res (env-set env a1 func))) - - (= (Sym "macroexpand") a0) - (setv res (macroexpand a1 env)) - - (= (Sym "try*") a0) - (setv res + (setv res + (if (not (instance? tuple ast)) + (eval-ast ast env) + + ;; apply list + (do + (setv ast (macroexpand ast env)) + (if (not (instance? tuple ast)) + (eval-ast ast env) + + (do + (setv [a0 a1 a2] [(nth ast 0) (nth ast 1) (nth ast 2)]) + (if + (none? a0) + ast + + (= (Sym "def!") a0) + (env-set env a1 (EVAL a2 env)) + + (= (Sym "let*") a0) + (do + (setv env (env-new env)) + (for [[b e] (partition a1 2)] + (env-set env b (EVAL e env))) + (setv ast a2) + (continue)) ;; TCO + + (= (Sym "quote") a0) + a1 + + (= (Sym "quasiquote") a0) + (do (setv ast (QUASIQUOTE a1)) (continue)) ;; TCO + + (= (Sym "defmacro!") a0) + (do (setv func (EVAL a2 env) + func.macro True) + (env-set env a1 func)) + + (= (Sym "macroexpand") a0) + (macroexpand a1 env) + + (= (Sym "try*") a0) (if (= (Sym "catch*") (nth a2 0)) (try (EVAL a1 env) @@ -113,41 +113,42 @@ (setv exc (Str (get e.args 0)))) (EVAL (nth a2 2) (env-new env [(nth a2 1)] [exc])))) - (EVAL a1 env))) - - (= (Sym "do") a0) - (do (eval-ast (list (butlast (rest ast))) env) - (setv ast (last ast)) - (continue)) ;; TCO - - (= (Sym "if") a0) - (do - (setv cond (EVAL a1 env)) - (if (or (none? cond) (and (instance? bool cond) - (= cond False))) - (if (> (len ast) 2) - (do (setv ast (nth ast 3)) (continue)) ;; TCO - (setv res None)) - (do (setv ast a2) (continue)))) ;; TCO - - (= (Sym "fn*") a0) - (setv func (fn [&rest args] - (EVAL a2 (env-new env a1 (or args [])))) - func.ast a2 - func.env env - func.params a1 - res func) - - ;; apply - (do - (setv el (eval-ast ast env) - f (first el) - args (list (rest el))) - (if (hasattr f "ast") - (do (setv ast f.ast - env (env-new f.env f.params args)) - (continue)) ;; TCO - (setv res (apply f args))))))))) + (EVAL a1 env)) + + (= (Sym "do") a0) + (do (eval-ast (list (butlast (rest ast))) env) + (setv ast (last ast)) + (continue)) ;; TCO + + (= (Sym "if") a0) + (do + (setv cond (EVAL a1 env)) + (if (or (none? cond) (and (instance? bool cond) + (= cond False))) + (if (> (len ast) 2) + (do (setv ast (nth ast 3)) (continue)) ;; TCO + None) + (do (setv ast a2) (continue)))) ;; TCO + + (= (Sym "fn*") a0) + (do + (setv func (fn [&rest args] + (EVAL a2 (env-new env a1 (or args [])))) + func.ast a2 + func.env env + func.params a1) + func) + + ;; apply + (do + (setv el (eval-ast ast env) + f (first el) + args (list (rest el))) + (if (hasattr f "ast") + (do (setv ast f.ast + env (env-new f.env f.params args)) + (continue)) ;; TCO + (apply f args))))))))) (break)) res) From ccab8f5907bd80cf484687f9bd0849643267d00a Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 22 Sep 2017 12:16:54 -0500 Subject: [PATCH 0487/2308] Hy: add to README and travis. --- .travis.yml | 1 + README.md | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 45fc93f27a..b021b3158d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,6 +38,7 @@ matrix: - {env: IMPL=haxe haxe_MODE=python, services: [docker]} - {env: IMPL=haxe haxe_MODE=cpp, services: [docker]} - {env: IMPL=haxe haxe_MODE=js, services: [docker]} + - {env: IMPL=hy, services: [docker]} - {env: IMPL=io, services: [docker]} - {env: IMPL=java, services: [docker]} - {env: IMPL=js, services: [docker]} diff --git a/README.md b/README.md index 48505bbca1..ed48a798a7 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 69 languages: +Mal is implemented in 70 languages: * Ada * GNU awk @@ -36,6 +36,7 @@ Mal is implemented in 69 languages: * GNU Smalltalk * Haskell * Haxe (Neko, Python, C++ and JavaScript) +* Hy * Io * Java * JavaScript ([Online Demo](http://kanaka.github.io/mal)) @@ -501,6 +502,15 @@ make all-js node ./stepX_YYY.js ``` +### Hy + +The Hy implementation of mal has been tested with Hy 0.13.0. + +``` +cd hy +./stepX_YYY.hy +``` + ### Io *The Io implementation was created by [Dov Murik](https://github.com/dubek)* From 21986733a1ae07ec11f74ef6e4b1a4dbbf682de1 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Tue, 12 Sep 2017 20:51:40 +0000 Subject: [PATCH 0488/2308] Yorick implementation --- Makefile | 3 +- yorick/Dockerfile | 26 +++ yorick/Makefile | 24 +++ yorick/core.i | 343 ++++++++++++++++++++++++++++++++++++++ yorick/env.i | 44 +++++ yorick/hash.i | 79 +++++++++ yorick/printer.i | 50 ++++++ yorick/reader.i | 155 +++++++++++++++++ yorick/run | 3 + yorick/step0_repl.i | 33 ++++ yorick/step1_read_print.i | 43 +++++ yorick/step2_eval.i | 96 +++++++++++ yorick/step3_env.i | 113 +++++++++++++ yorick/step4_if_fn_do.i | 155 +++++++++++++++++ yorick/step5_tco.i | 162 ++++++++++++++++++ yorick/step6_file.i | 190 +++++++++++++++++++++ yorick/step7_quote.i | 215 ++++++++++++++++++++++++ yorick/step8_macros.i | 251 ++++++++++++++++++++++++++++ yorick/step9_try.i | 265 +++++++++++++++++++++++++++++ yorick/stepA_mal.i | 270 ++++++++++++++++++++++++++++++ yorick/types.i | 166 ++++++++++++++++++ 21 files changed, 2685 insertions(+), 1 deletion(-) create mode 100644 yorick/Dockerfile create mode 100644 yorick/Makefile create mode 100644 yorick/core.i create mode 100644 yorick/env.i create mode 100644 yorick/hash.i create mode 100644 yorick/printer.i create mode 100644 yorick/reader.i create mode 100755 yorick/run create mode 100644 yorick/step0_repl.i create mode 100644 yorick/step1_read_print.i create mode 100644 yorick/step2_eval.i create mode 100644 yorick/step3_env.i create mode 100644 yorick/step4_if_fn_do.i create mode 100644 yorick/step5_tco.i create mode 100644 yorick/step6_file.i create mode 100644 yorick/step7_quote.i create mode 100644 yorick/step8_macros.i create mode 100644 yorick/step9_try.i create mode 100644 yorick/stepA_mal.i create mode 100644 yorick/types.i diff --git a/Makefile b/Makefile index 100e2f9086..dbd80c7216 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,7 @@ IMPLS = ada awk bash basic c d chuck clojure coffee common-lisp cpp crystal cs d haxe hy io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ python r racket rexx rpython ruby rust scala scheme skew swift swift3 tcl \ - ts vb vhdl vimscript livescript elm + ts vb vhdl vimscript yorick livescript elm EXTENSION = .mal @@ -238,6 +238,7 @@ ts_STEP_TO_PROG = ts/$($(1)).js vb_STEP_TO_PROG = vb/$($(1)).exe vhdl_STEP_TO_PROG = vhdl/$($(1)) vimscript_STEP_TO_PROG = vimscript/$($(1)).vim +yorick_STEP_TO_PROG = yorick/$($(1)).i guile_STEP_TO_PROG = guile/$($(1)).scm livescript_STEP_TO_PROG = livescript/$($(1)).js elm_STEP_TO_PROG = elm/$($(1)).js diff --git a/yorick/Dockerfile b/yorick/Dockerfile new file mode 100644 index 0000000000..8a3f0037f3 --- /dev/null +++ b/yorick/Dockerfile @@ -0,0 +1,26 @@ +FROM ubuntu:xenial +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +RUN apt-get -y install yorick yorick-yeti yorick-yeti-regex + +ENV HOME /mal diff --git a/yorick/Makefile b/yorick/Makefile new file mode 100644 index 0000000000..ccc3d68740 --- /dev/null +++ b/yorick/Makefile @@ -0,0 +1,24 @@ +SOURCES_BASE = hash.i types.i reader.i printer.i +SOURCES_LISP = env.i core.i stepA_mal.i +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +.PHONY: all dist clean stats stats-lisp + +all: dist + +dist: mal + +mal: $(SOURCES) + echo "#!/usr/bin/yorick -batch" > $@ + cat $+ | grep -v "^require," >> $@ + chmod +x $@ + +clean: + rm -f mal + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/yorick/core.i b/yorick/core.i new file mode 100644 index 0000000000..167f388c81 --- /dev/null +++ b/yorick/core.i @@ -0,0 +1,343 @@ +require, "types.i" + +func mal_equal(a) { return new_boolean(equal(*a(1), *a(2))); } +func mal_throw(a) { return MalError(obj=a(1)); } + +func mal_nil_q(a) { return new_boolean(structof(*a(1)) == MalNil); } +func mal_true_q(a) { return new_boolean(structof(*a(1)) == MalTrue); } +func mal_false_q(a) { return new_boolean(structof(*a(1)) == MalFalse); } +func mal_string_q(a) { return new_boolean(structof(*a(1)) == MalString); } +func mal_symbol(a) { return MalSymbol(val=a(1)->val); } +func mal_symbol_q(a) { return new_boolean(structof(*a(1)) == MalSymbol); } +func mal_keyword(a) { return MalKeyword(val=a(1)->val); } +func mal_keyword_q(a) { return new_boolean(structof(*a(1)) == MalKeyword); } + +func string_helper(a, delimiter, readable) +{ + res = "" + for (i = 1; i <= numberof(a); ++i) { + if (i > 1) res += delimiter + res += pr_str(*a(i), readable) + } + return res +} + +func mal_pr_str(a) { return MalString(val=string_helper(a, " ", 1)); } +func mal_str(a) { return MalString(val=string_helper(a, "", 0)); } +func mal_prn(a) { write, format="%s\n", string_helper(a, " ", 1); return MAL_NIL; } +func mal_println(a) { write, format="%s\n", string_helper(a, " ", 0); return MAL_NIL; } +func mal_read_string(a) { return read_str(a(1)->val); } + +func mal_readline(a) +{ + extern stdin_file + write, format="%s", a(1)->val + line = rdline(stdin_file, prompt="") + return line ? MalString(val=line) : MAL_NIL +} + +func mal_slurp(a) +{ + f = open(a(1)->val) + lines = rdfile(f) + close, f + s = "" + for (i = 1; i <= numberof(lines); ++i) { + s += (lines(i) + "\n") + } + return MalString(val=s) +} + +func mal_lt(a) { return new_boolean(a(1)->val < a(2)->val); } +func mal_lte(a) { return new_boolean(a(1)->val <= a(2)->val); } +func mal_gt(a) { return new_boolean(a(1)->val > a(2)->val); } +func mal_gte(a) { return new_boolean(a(1)->val >= a(2)->val); } + +func mal_add(a) { return MalNumber(val=(a(1)->val + a(2)->val)); } +func mal_sub(a) { return MalNumber(val=(a(1)->val - a(2)->val)); } +func mal_mul(a) { return MalNumber(val=(a(1)->val * a(2)->val)); } +func mal_div(a) { return MalNumber(val=(a(1)->val / a(2)->val)); } + +func mal_time_ms(a) +{ + elapsed = array(double, 3) + timer, elapsed + return MalNumber(val=floor(elapsed(3) * 1000)) +} + +func mal_list(a) { return MalList(val=&a); } +func mal_list_q(a) { return new_boolean(structof(*a(1)) == MalList); } +func mal_vector(a) { return MalVector(val=&a); } +func mal_vector_q(a) { return new_boolean(structof(*a(1)) == MalVector); } +func mal_hash_map(a) { return array_to_hashmap(a); } +func mal_map_q(a) { return new_boolean(structof(*a(1)) == MalHashmap); } + +func mal_assoc(a) { + h = *(a(1)->val) + k1 = *h.keys + v1 = *h.vals + new_h = Hash(keys=&k1, vals=&v1) + for (i = 2; i <= numberof(a); i += 2) { + hash_set, new_h, hashmap_obj_to_key(*a(i)), *a(i + 1) + } + return MalHashmap(val=&new_h); +} + +func mal_dissoc(a) { + h = *(a(1)->val) + k1 = *h.keys + v1 = *h.vals + new_h = Hash(keys=&k1, vals=&v1) + for (i = 2; i <= numberof(a); ++i) { + hash_delete, new_h, hashmap_obj_to_key(*a(i)) + } + return MalHashmap(val=&new_h); +} + +func mal_get(a) { + if (structof(*a(1)) == MalNil) return MAL_NIL + h = *(a(1)->val) + key_obj = *a(2) + val = hash_get(h, hashmap_obj_to_key(key_obj)) + return is_void(val) ? MAL_NIL : val +} + +func mal_contains_q(a) { + if (structof(*a(1)) == MalNil) return MAL_FALSE + h = *(a(1)->val) + key_obj = *a(2) + return hash_has_key(h, hashmap_obj_to_key(key_obj)) ? MAL_TRUE : MAL_FALSE +} + +func mal_keys(a) { + keys_strs = *(a(1)->val->keys) + if (numberof(keys_strs) == 0) return MalList(val=&[]) + res = array(pointer, numberof(keys_strs)) + for (i = 1; i <= numberof(keys_strs); ++i) { + res(i) = &hashmap_key_to_obj(keys_strs(i)) + } + return MalList(val=&res); +} + +func mal_vals(a) { return MalList(val=a(1)->val->vals); } + +func mal_sequential_q(a) { return new_boolean(structof(*a(1)) == MalList || structof(*a(1)) == MalVector); } + +func mal_cons(a) +{ + a2_len = count(*a(2)) + seq = array(pointer, a2_len + 1) + seq(1) = a(1) + if (a2_len > 0) { + seq(2:) = *(a(2)->val) + } + return MalList(val=&seq) +} + +func mal_concat(a) +{ + seq = [] + for (i = 1; i <= numberof(a); ++i) { + grow, seq, *(a(i)->val) + } + return MalList(val=&seq) +} + +func mal_nth(a) +{ + index = a(2)->val + if (index >= count(*a(1))) return MalError(message="nth: index out of range") + return *((*(a(1)->val))(index + 1)) +} + +func mal_first(a) +{ + if (structof(*a(1)) == MalNil || count(*a(1)) == 0) return MAL_NIL + return *((*(a(1)->val))(1)) +} + +func mal_rest(a) +{ + if (structof(*a(1)) == MalNil) return MalList(val=&[]) + return rest(*a(1)) +} + +func mal_empty_q(a) { return new_boolean((structof(*a(1)) == MalNil ? 1 : count(*a(1)) == 0)); } +func mal_count(a) { return MalNumber(val=(structof(*a(1)) == MalNil ? 0 : count(*a(1)))); } + +func call_func(fn, args) +{ + if (structof(fn) == MalNativeFunction) { + return call_core_fn(fn.val, args) + } else if (structof(fn) == MalFunction) { + fn_env = env_new(fn.env, binds=*fn.binds, exprs=args) + return EVAL(*fn.ast, fn_env) + } else { + return MalError(message="Unknown function type") + } +} + +func mal_apply(a) { + mid_args = numberof(a) > 2 ? a(2:-1) : [] + return call_func(*a(1), grow(mid_args, *(a(0)->val))) +} + +func mal_map(a) { + fn = *a(1) + seq = *(a(2)->val) + if (numberof(seq) == 0) return MalList(val=&[]) + new_seq = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + new_val = call_func(fn, [seq(i)]) + if (structof(new_val) == MalError) return new_val + new_seq(i) = &new_val + } + return MalList(val=&new_seq) +} + +func mal_conj(a) +{ + obj = *a(1) + type = structof(obj) + if (type == MalList) { + res = obj + for (i = 2; i <= numberof(a); ++i) { + res = mal_cons([a(i), &res]) + } + return res + } else if (type == MalVector) { + seq = *obj.val + grow, seq, a(2:) + return MalVector(val=&seq) + } else { + return MalError(message="conj requires list or vector") + } +} + +func mal_seq(a) +{ + obj = *a(1) + type = structof(obj) + if (type == MalString) { + len = strlen(obj.val) + if (len == 0) return MAL_NIL + seq = array(pointer, len) + for (i = 1; i <= len; ++i) { + seq(i) = &MalString(val=strpart(obj.val, i:i)) + } + return MalList(val=&seq) + } else if (type == MalList) { + return count(obj) == 0 ? MAL_NIL : obj + } else if (type == MalVector) { + return count(obj) == 0 ? MAL_NIL : MalList(val=obj.val) + } else if (type == MalNil) { + return MAL_NIL + } else { + return MalError(message="seq requires string or list or vector or nil") + } +} + +func mal_meta(a) +{ + meta_obj = *(a(1)->meta) + return is_void(meta_obj) ? MAL_NIL : meta_obj +} + +func mal_with_meta(a) +{ + new_obj = *a(1) + new_obj.meta = a(2) + return new_obj +} + +func mal_atom(a) { return MalAtom(val=&MalAtomVal(val=a(1))); } +func mal_atom_q(a) { return new_boolean(structof(*a(1)) == MalAtom); } +func mal_deref(a) { return *(a(1)->val->val); } +func mal_reset_bang(a) { a(1)->val->val = a(2); return *(a(1)->val->val); } +func mal_swap_bang(a) +{ + old_val = mal_deref([a(1)]) + args = array(pointer, numberof(a) - 1) + args(1) = &old_val + if (numberof(a) > 2) args(2:) = a(3:) + new_val = call_func(*a(2), args) + if (structof(new_val) == MalError) return new_val + return mal_reset_bang([a(1), &new_val]) +} + +func mal_eval(a) { return EVAL(*a(1), repl_env); } + +core_ns = h_new() + +h_set, core_ns, "=", mal_equal +h_set, core_ns, "throw", mal_throw + +h_set, core_ns, "nil?", mal_nil_q +h_set, core_ns, "true?", mal_true_q +h_set, core_ns, "false?", mal_false_q +h_set, core_ns, "string?", mal_string_q +h_set, core_ns, "symbol", mal_symbol +h_set, core_ns, "symbol?", mal_symbol_q +h_set, core_ns, "keyword", mal_keyword +h_set, core_ns, "keyword?", mal_keyword_q + +h_set, core_ns, "pr-str", mal_pr_str +h_set, core_ns, "str", mal_str +h_set, core_ns, "prn", mal_prn +h_set, core_ns, "println", mal_println +h_set, core_ns, "read-string", mal_read_string +h_set, core_ns, "readline", mal_readline +h_set, core_ns, "slurp", mal_slurp + +h_set, core_ns, "<", mal_lt +h_set, core_ns, "<=", mal_lte +h_set, core_ns, ">", mal_gt +h_set, core_ns, ">=", mal_gte +h_set, core_ns, "+", mal_add +h_set, core_ns, "-", mal_sub +h_set, core_ns, "*", mal_mul +h_set, core_ns, "/", mal_div +h_set, core_ns, "time-ms", mal_time_ms + +h_set, core_ns, "list", mal_list +h_set, core_ns, "list?", mal_list_q +h_set, core_ns, "vector", mal_vector +h_set, core_ns, "vector?", mal_vector_q +h_set, core_ns, "hash-map", mal_hash_map +h_set, core_ns, "map?", mal_map_q +h_set, core_ns, "assoc", mal_assoc +h_set, core_ns, "dissoc", mal_dissoc +h_set, core_ns, "get", mal_get +h_set, core_ns, "contains?", mal_contains_q +h_set, core_ns, "keys", mal_keys +h_set, core_ns, "vals", mal_vals + +h_set, core_ns, "sequential?", mal_sequential_q +h_set, core_ns, "cons", mal_cons +h_set, core_ns, "concat", mal_concat +h_set, core_ns, "nth", mal_nth +h_set, core_ns, "first", mal_first +h_set, core_ns, "rest", mal_rest +h_set, core_ns, "empty?", mal_empty_q +h_set, core_ns, "count", mal_count +h_set, core_ns, "apply", mal_apply +h_set, core_ns, "map", mal_map + +h_set, core_ns, "conj", mal_conj +h_set, core_ns, "seq", mal_seq + +h_set, core_ns, "meta", mal_meta +h_set, core_ns, "with-meta", mal_with_meta +h_set, core_ns, "atom", mal_atom +h_set, core_ns, "atom?", mal_atom_q +h_set, core_ns, "deref", mal_deref +h_set, core_ns, "reset!", mal_reset_bang +h_set, core_ns, "swap!", mal_swap_bang + +h_set, core_ns, "eval", mal_eval + +func call_core_fn(name, args_list) +{ + f = h_get(core_ns, name) + return f(args_list) +} diff --git a/yorick/env.i b/yorick/env.i new file mode 100644 index 0000000000..d1e8a9cdc0 --- /dev/null +++ b/yorick/env.i @@ -0,0 +1,44 @@ +require, "hash.i" +require, "types.i" + +struct Env { + pointer outer + Hash data +} + +func env_new(outer_ptr, binds=, exprs=) +{ + env = Env(outer=outer_ptr, data=hash_new()) + for (i = 1; i <= numberof(binds); ++i) { + if (binds(i)->val == "&") { + rest_args = numberof(exprs) >= i ? exprs(i:) : [] + env_set, env, binds(i + 1)->val, MalList(val=&rest_args) + break + } else { + env_set, env, binds(i)->val, *exprs(i) + } + } + return env +} + +func env_find(env, key) +{ + if (hash_has_key(env.data, key)) return env + if (is_void(*env.outer)) return nil + return env_find(*env.outer, key) +} + +func env_get(env, key) +{ + found_env = env_find(env, key) + if (is_void(found_env)) return MalError(message=("'" + key + "' not found")) + return hash_get(found_env.data, key) +} + +func env_set(&env, key, val) +{ + d = env.data + hash_set, d, key, val + env.data = d + return val +} diff --git a/yorick/hash.i b/yorick/hash.i new file mode 100644 index 0000000000..250e4c72dc --- /dev/null +++ b/yorick/hash.i @@ -0,0 +1,79 @@ +// Implement our old naive O(n) map because Yeti's hash table (h_new()) cannot +// be used inside arrays and structs (we can't get a pointer to hash table). +// This prevents saving pointer to environment in MalFunction for example. + +struct Hash { + pointer keys + pointer vals +} + +func hash_new(void) +{ + return Hash(keys=&[], vals=&[]) +} + +func hash_get(h, key) +{ + for (i = 1; i <= numberof(*h.keys); ++i) { + if ((*h.keys)(i) == key) return *((*h.vals)(i)) + } + return nil +} + +func hash_has_key(h, key) +{ + for (i = 1; i <= numberof(*h.keys); ++i) { + if ((*h.keys)(i) == key) return 1 + } + return 0 +} + +func hash_set(&h, key, val) +{ + if (is_void(*h.keys)) { + h.keys = &[key] + h.vals = &[&val] + return + } + for (i = 1; i <= numberof(*h.keys); ++i) { + if ((*h.keys)(i) == key) { + (*h.vals)(i) = &val + return + } + } + tmp = *h.keys + grow, tmp, [key] + h.keys = &tmp + tmp = *h.vals + grow, tmp, [&val] + h.vals = &tmp +} + +func hash_delete(&h, key) +{ + if (is_void(*h.keys) || numberof(*h.keys) == 0) return + k = *h.keys + v = *h.vals + if (numberof(k) == 1) { + if (k(1) == key) { + h.keys = &[] + h.vals = &[] + return + } + } + for (i = 1; i <= numberof(k); ++i) { + if (k(i) == key) { + if (i == 1) { + h.keys = &(k(i+1:)) + h.vals = &(v(i+1:)) + } else if (i == numberof(k)) { + h.keys = &(k(1:i-1)) + h.vals = &(v(1:i-1)) + } else { + h.keys = &grow(k(1:i-1), k(i+1:)) + h.vals = &grow(v(1:i-1), v(i+1:)) + } + return + } + } +} diff --git a/yorick/printer.i b/yorick/printer.i new file mode 100644 index 0000000000..acefd17c26 --- /dev/null +++ b/yorick/printer.i @@ -0,0 +1,50 @@ +require, "types.i" + +func format_seq(val, start_char, end_char, readable) +{ + seq = *val + res = "" + for (i = 1; i <= numberof(seq); ++i) { + if (i > 1) res += " " + res += pr_str(*seq(i), readable) + } + return start_char + res + end_char +} + +func format_hashmap(h, readable) +{ + res = "" + for (i = 1; i <= numberof(*h.keys); ++i) { + if (i > 1) res += " " + key = hashmap_key_to_obj((*h.keys)(i)) + res += pr_str(key, readable) + " " + pr_str(*((*h.vals)(i)), readable) + } + return "{" + res + "}" +} + +func escape(s) +{ + s1 = streplaceall(s, "\\", "\\\\") + s2 = streplaceall(s1, "\"", "\\\"") + s3 = streplaceall(s2, "\n", "\\n") + return "\"" + s3 + "\"" +} + +func pr_str(ast, readable) +{ + type = structof(ast) + if (type == MalNil) return "nil" + else if (type == MalTrue) return "true" + else if (type == MalFalse) return "false" + else if (type == MalNumber) return totxt(ast.val) + else if (type == MalSymbol) return ast.val + else if (type == MalString) return readable ? escape(ast.val) : ast.val + else if (type == MalKeyword) return ":" + ast.val + else if (type == MalList) return format_seq(ast.val, "(", ")", readable) + else if (type == MalVector) return format_seq(ast.val, "[", "]", readable) + else if (type == MalHashmap) return format_hashmap(*ast.val, readable) + else if (type == MalAtom) return "(atom " + pr_str(*(ast.val->val), readable) + ")" + else if (type == MalNativeFunction) return "#" + else if (type == MalFunction) return "#" + else MalError(message=("Unknown type " + totxt(type))) +} diff --git a/yorick/reader.i b/yorick/reader.i new file mode 100644 index 0000000000..3b7e90f09b --- /dev/null +++ b/yorick/reader.i @@ -0,0 +1,155 @@ +#include "yeti_regex.i" +require, "types.i" + +TOKENIZER_REGEXP = regcomp("[[:space:],]*(~@|[][{}()'`~@]|\"([\\].|[^\\\"])*\"|;.*|[^][[:space:]{}()'\"`~@,;]*)", newline=1) + +func tokenize(str) +{ + match0 = "" + match1 = "" + pos = 1 + tokens = [] + while (1) { + m = regmatch(TOKENIZER_REGEXP, str, match0, match1, start=pos, indices=1) + if (m == 0) break + b = match1(1) + e = match1(2) - 1 + if (e < b) { + pos = match1(2) + 1 + continue + } + token = strpart(str, b:e) + pos = match1(2) + if (strpart(token, 1:1) == ";") continue + grow, tokens, [token] + } + return tokens +} + +struct Reader { + pointer tokens + int pos +} + +func reader_peek(rdr) +{ + if (rdr.pos > numberof(*rdr.tokens)) return string(0) + return (*rdr.tokens)(rdr.pos) +} + +func reader_next(rdr) +{ + token = reader_peek(rdr) + rdr.pos += 1 + return token +} + +NUMBER_REGEXP = regcomp("^-?[0-9]+$") + +func unescape(s) +{ + s1 = strpart(s, 2:-1) // remove surrounding quotes + s2 = streplaceall(s1, "\\n", "\n") + s3 = streplaceall(s2, "\\\"", "\"") + return streplaceall(s3, "\\\\", "\\") +} + +func read_atom(rdr) +{ + token = reader_next(rdr) + if (token == "nil") return MAL_NIL + else if (token == "true") return MAL_TRUE + else if (token == "false") return MAL_FALSE + else if (regmatch(NUMBER_REGEXP, token)) return MalNumber(val=tonum(token)) + else if (strpart(token, 1:1) == "\"") return MalString(val=unescape(token)) + else if (strpart(token, 1:1) == ":") return MalKeyword(val=strpart(token, 2:)) + else return MalSymbol(val=token) +} + +func read_seq(rdr, start_char, end_char) +{ + token = reader_next(rdr) + if (token != start_char) { + return MalError(message=("expected '" + start_char + "'")) + } + + elements = [] + token = reader_peek(rdr) + while (token != end_char) { + if (token == string(0)) { + return MalError(message=("expected '" + end_char + "'")) + } + e = read_form(rdr) + if (structof(e) == MalError) return e + grow, elements, [&e] + token = reader_peek(rdr) + } + token = reader_next(rdr) + return elements +} + +func read_list(rdr) +{ + seq = read_seq(rdr, "(", ")") + if (structof(seq) == MalError) return seq + return MalList(val=&seq) +} + +func read_vector(rdr) +{ + seq = read_seq(rdr, "[", "]") + if (structof(seq) == MalError) return seq + return MalVector(val=&seq) +} + +func read_hashmap(rdr) +{ + seq = read_seq(rdr, "{", "}") + if (structof(seq) == MalError) return seq + return array_to_hashmap(seq) +} + +func reader_macro(rdr, symbol_name) +{ + shortcut = reader_next(rdr) + form = read_form(rdr) + if (structof(form) == MalError) return form + seq = [&MalSymbol(val=symbol_name), &form] + return MalList(val=&seq) +} + +func reader_with_meta_macro(rdr) +{ + shortcut = reader_next(rdr) + meta = read_form(rdr) + if (structof(meta) == MalError) return meta + form = read_form(rdr) + if (structof(form) == MalError) return form + seq = [&MalSymbol(val="with-meta"), &form, &meta] + return MalList(val=&seq) +} + +func read_form(rdr) +{ + token = reader_peek(rdr) + if (token == "'") return reader_macro(rdr, "quote") + else if (token == "`") return reader_macro(rdr, "quasiquote") + else if (token == "~") return reader_macro(rdr, "unquote") + else if (token == "~@") return reader_macro(rdr, "splice-unquote") + else if (token == "@") return reader_macro(rdr, "deref") + else if (token == "^") return reader_with_meta_macro(rdr) + else if (token == "(") return read_list(rdr) + else if (token == ")") return MalError(message="unexpected ')'") + else if (token == "[") return read_vector(rdr) + else if (token == "]") return MalError(message="unexpected ']'") + else if (token == "{") return read_hashmap(rdr) + else if (token == "}") return MalError(message="unexpected '}'") + else return read_atom(rdr) +} + +func read_str(str) +{ + tokens = tokenize(str) + rdr = Reader(tokens=&tokens, pos=1) + return read_form(rdr) +} diff --git a/yorick/run b/yorick/run new file mode 100755 index 0000000000..c54589bb04 --- /dev/null +++ b/yorick/run @@ -0,0 +1,3 @@ +#!/bin/bash +export YORICK_MAL_PATH="$(dirname $0)" +exec yorick -batch "$YORICK_MAL_PATH/${STEP:-stepA_mal}.i" "${@}" diff --git a/yorick/step0_repl.i b/yorick/step0_repl.i new file mode 100644 index 0000000000..6a7fa25016 --- /dev/null +++ b/yorick/step0_repl.i @@ -0,0 +1,33 @@ +func READ(str) +{ + return str +} + +func EVAL(exp, env) +{ + return exp +} + +func PRINT(exp) +{ + return exp +} + +func REP(str) +{ + return PRINT(EVAL(READ(str), "")) +} + +func main(void) +{ + stdin_file = open("/dev/stdin", "r") + while (1) { + write, format="%s", "user> " + line = rdline(stdin_file, prompt="") + if (!line) break + if (strlen(line) > 0) write, format="%s\n", REP(line) + } + write, "" +} + +main; diff --git a/yorick/step1_read_print.i b/yorick/step1_read_print.i new file mode 100644 index 0000000000..8a97cb8cf1 --- /dev/null +++ b/yorick/step1_read_print.i @@ -0,0 +1,43 @@ +set_path, get_env("YORICK_MAL_PATH") + ":" + get_path() +require, "reader.i" +require, "printer.i" + +func READ(str) +{ + return read_str(str) +} + +func EVAL(exp, env) +{ + if (structof(exp) == MalError) return exp + return exp +} + +func PRINT(exp) +{ + if (structof(exp) == MalError) return exp + return pr_str(exp, 1) +} + +func REP(str) +{ + return PRINT(EVAL(READ(str), "")) +} + +func main(void) +{ + stdin_file = open("/dev/stdin", "r") + while (1) { + write, format="%s", "user> " + line = rdline(stdin_file, prompt="") + if (!line) break + if (strlen(line) > 0) { + result = REP(line) + if (structof(result) == MalError) write, format="Error: %s\n", result.message + else write, format="%s\n", result + } + } + write, "" +} + +main; diff --git a/yorick/step2_eval.i b/yorick/step2_eval.i new file mode 100644 index 0000000000..2e59df84ca --- /dev/null +++ b/yorick/step2_eval.i @@ -0,0 +1,96 @@ +set_path, get_env("YORICK_MAL_PATH") + ":" + get_path() +require, "reader.i" +require, "printer.i" +require, "core.i" + +func READ(str) +{ + return read_str(str) +} + +func eval_ast(ast, env) +{ + type = structof(ast) + if (type == MalSymbol) { + val = h_get(env, ast.val) + if (is_void(val)) return MalError(message=("'" + ast.val + "' not found")) + return val + } else if (type == MalList) { + seq = *(ast.val) + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalList(val=&res) + } else if (type == MalVector) { + seq = *(ast.val) + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalVector(val=&res) + } else if (type == MalHashmap) { + h = *(ast.val) + if (numberof(*h.keys) == 0) return ast + res = hash_new() + for (i = 1; i <= numberof(*h.keys); ++i) { + new_key = EVAL(hashmap_key_to_obj((*h.keys)(i)), env) + if (structof(new_key) == MalError) return new_key + new_val = EVAL(*((*h.vals)(i)), env) + if (structof(new_val) == MalError) return new_val + hash_set, res, hashmap_obj_to_key(new_key), new_val + } + return MalHashmap(val=&res) + } else return ast +} + +func EVAL(ast, env) +{ + if (structof(ast) == MalError) return ast + if (structof(ast) != MalList) return eval_ast(ast, env) + if (numberof(*ast.val) == 0) return ast + el = eval_ast(ast, env) + if (structof(el) == MalError) return el + seq = *el.val + args = (numberof(seq) > 1) ? seq(2:) : [] + return call_core_fn(seq(1)->val, args) +} + +func PRINT(exp) +{ + if (structof(exp) == MalError) return exp + return pr_str(exp, 1) +} + +func REP(str, env) +{ + return PRINT(EVAL(READ(str), env)) +} + +func main(void) +{ + repl_env = h_new() + h_set, repl_env, "+", MalNativeFunction(val="+") + h_set, repl_env, "-", MalNativeFunction(val="-") + h_set, repl_env, "*", MalNativeFunction(val="*") + h_set, repl_env, "/", MalNativeFunction(val="/") + + stdin_file = open("/dev/stdin", "r") + while (1) { + write, format="%s", "user> " + line = rdline(stdin_file, prompt="") + if (!line) break + if (strlen(line) > 0) { + result = REP(line, repl_env) + if (structof(result) == MalError) write, format="Error: %s\n", result.message + else write, format="%s\n", result + } + } + write, "" +} + +main; diff --git a/yorick/step3_env.i b/yorick/step3_env.i new file mode 100644 index 0000000000..4cb21e5ff6 --- /dev/null +++ b/yorick/step3_env.i @@ -0,0 +1,113 @@ +set_path, get_env("YORICK_MAL_PATH") + ":" + get_path() +require, "reader.i" +require, "printer.i" +require, "core.i" +require, "env.i" + +func READ(str) +{ + return read_str(str) +} + +func eval_ast(ast, env) +{ + type = structof(ast) + if (type == MalSymbol) { + return env_get(env, ast.val) + } else if (type == MalList) { + seq = *(ast.val) + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalList(val=&res) + } else if (type == MalVector) { + seq = *(ast.val) + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalVector(val=&res) + } else if (type == MalHashmap) { + h = *(ast.val) + if (numberof(*h.keys) == 0) return ast + res = hash_new() + for (i = 1; i <= numberof(*h.keys); ++i) { + new_key = EVAL(hashmap_key_to_obj((*h.keys)(i)), env) + if (structof(new_key) == MalError) return new_key + new_val = EVAL(*((*h.vals)(i)), env) + if (structof(new_val) == MalError) return new_val + hash_set, res, hashmap_obj_to_key(new_key), new_val + } + return MalHashmap(val=&res) + } else return ast +} + +func EVAL(ast, env) +{ + if (structof(ast) == MalError) return ast + if (structof(ast) != MalList) return eval_ast(ast, env) + lst = *ast.val + if (numberof(lst) == 0) return ast + a1 = lst(1)->val + if (a1 == "def!") { + new_value = EVAL(*lst(3), env) + if (structof(new_value) == MalError) return new_value + return env_set(env, lst(2)->val, new_value) + } else if (a1 == "let*") { + let_env = env_new(&env) + args_lst = *(lst(2)->val) + for (i = 1; i <= numberof(args_lst); i += 2) { + var_name = args_lst(i)->val + var_value = EVAL(*args_lst(i + 1), let_env) + if (structof(var_value) == MalError) return var_value + env_set, let_env, var_name, var_value + } + return EVAL(*lst(3), let_env) + } else { + el = eval_ast(ast, env) + if (structof(el) == MalError) return el + seq = *el.val + args = (numberof(seq) > 1) ? seq(2:) : [] + return call_core_fn(seq(1)->val, args) + } +} + +func PRINT(exp) +{ + if (structof(exp) == MalError) return exp + return pr_str(exp, 1) +} + +func REP(str, env) +{ + return PRINT(EVAL(READ(str), env)) +} + +func main(void) +{ + repl_env = env_new(pointer(0)) + env_set, repl_env, "+", MalNativeFunction(val="+") + env_set, repl_env, "-", MalNativeFunction(val="-") + env_set, repl_env, "*", MalNativeFunction(val="*") + env_set, repl_env, "/", MalNativeFunction(val="/") + + stdin_file = open("/dev/stdin", "r") + while (1) { + write, format="%s", "user> " + line = rdline(stdin_file, prompt="") + if (!line) break + if (strlen(line) > 0) { + result = REP(line, repl_env) + if (structof(result) == MalError) write, format="Error: %s\n", result.message + else write, format="%s\n", result + } + } + write, "" +} + +main; diff --git a/yorick/step4_if_fn_do.i b/yorick/step4_if_fn_do.i new file mode 100644 index 0000000000..34308be913 --- /dev/null +++ b/yorick/step4_if_fn_do.i @@ -0,0 +1,155 @@ +set_path, get_env("YORICK_MAL_PATH") + ":" + get_path() +require, "reader.i" +require, "printer.i" +require, "core.i" +require, "env.i" + +func READ(str) +{ + return read_str(str) +} + +func eval_ast(ast, env) +{ + type = structof(ast) + if (type == MalSymbol) { + return env_get(env, ast.val) + } else if (type == MalList) { + seq = *(ast.val) + if (numberof(seq) == 0) return ast + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalList(val=&res) + } else if (type == MalVector) { + seq = *(ast.val) + if (numberof(seq) == 0) return ast + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalVector(val=&res) + } else if (type == MalHashmap) { + h = *(ast.val) + if (numberof(*h.keys) == 0) return ast + res = hash_new() + for (i = 1; i <= numberof(*h.keys); ++i) { + new_key = EVAL(hashmap_key_to_obj((*h.keys)(i)), env) + if (structof(new_key) == MalError) return new_key + new_val = EVAL(*((*h.vals)(i)), env) + if (structof(new_val) == MalError) return new_val + hash_set, res, hashmap_obj_to_key(new_key), new_val + } + return MalHashmap(val=&res) + } else return ast +} + +func EVAL(ast, env) +{ + if (structof(ast) == MalError) return ast + if (structof(ast) != MalList) return eval_ast(ast, env) + lst = *ast.val + if (numberof(lst) == 0) return ast + a1 = lst(1)->val + if (a1 == "def!") { + new_value = EVAL(*lst(3), env) + if (structof(new_value) == MalError) return new_value + return env_set(env, lst(2)->val, new_value) + } else if (a1 == "let*") { + let_env = env_new(&env) + args_lst = *(lst(2)->val) + for (i = 1; i <= numberof(args_lst); i += 2) { + var_name = args_lst(i)->val + var_value = EVAL(*args_lst(i + 1), let_env) + if (structof(var_value) == MalError) return var_value + env_set, let_env, var_name, var_value + } + return EVAL(*lst(3), let_env) + } else if (a1 == "do") { + ret = nil + for (i = 2; i <= numberof(lst); ++i) { + ret = EVAL(*lst(i), env) + if (structof(ret) == MalError) return ret + } + return ret + } else if (a1 == "if") { + cond_val = EVAL(*lst(2), env) + if (structof(cond_val) == MalError) return cond_val + if ((structof(cond_val) == MalNil) || (structof(cond_val) == MalFalse)) { + if (numberof(lst) > 3) { + return EVAL(*lst(4), env) + } else { + return MAL_NIL + } + } else { + return EVAL(*lst(3), env) + } + } else if (a1 == "fn*") { + return MalFunction(env=&env, binds=lst(2)->val, ast=lst(3)) + } else { + el = eval_ast(ast, env) + if (structof(el) == MalError) return el + seq = *el.val + if (structof(*seq(1)) == MalNativeFunction) { + args = (numberof(seq) > 1) ? seq(2:) : [] + return call_core_fn(seq(1)->val, args) + } else if (structof(*seq(1)) == MalFunction) { + fn = *seq(1) + exprs = numberof(seq) > 1 ? seq(2:) : [] + fn_env = env_new(fn.env, binds=*fn.binds, exprs=exprs) + return EVAL(*fn.ast, fn_env) + } else { + return MalError(message="Unknown function type") + } + } +} + +func PRINT(exp) +{ + if (structof(exp) == MalError) return exp + return pr_str(exp, 1) +} + +func RE(str, env) +{ + return EVAL(READ(str), env) +} + +func REP(str, env) +{ + return PRINT(EVAL(READ(str), env)) +} + +func main(void) +{ + repl_env = env_new(pointer(0)) + + // core.i: defined using Yorick + core_symbols = h_keys(core_ns) + for (i = 1; i <= numberof(core_symbols); ++i) { + env_set, repl_env, core_symbols(i), MalNativeFunction(val=core_symbols(i)) + } + + // core.mal: defined using the language itself + RE, "(def! not (fn* (a) (if a false true)))", repl_env + + stdin_file = open("/dev/stdin", "r") + while (1) { + write, format="%s", "user> " + line = rdline(stdin_file, prompt="") + if (!line) break + if (strlen(line) > 0) { + result = REP(line, repl_env) + if (structof(result) == MalError) write, format="Error: %s\n", result.message + else write, format="%s\n", result + } + } + write, "" +} + +main; diff --git a/yorick/step5_tco.i b/yorick/step5_tco.i new file mode 100644 index 0000000000..4005b03123 --- /dev/null +++ b/yorick/step5_tco.i @@ -0,0 +1,162 @@ +set_path, get_env("YORICK_MAL_PATH") + ":" + get_path() +require, "reader.i" +require, "printer.i" +require, "core.i" +require, "env.i" + +func READ(str) +{ + return read_str(str) +} + +func eval_ast(ast, env) +{ + type = structof(ast) + if (type == MalSymbol) { + return env_get(env, ast.val) + } else if (type == MalList) { + seq = *(ast.val) + if (numberof(seq) == 0) return ast + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalList(val=&res) + } else if (type == MalVector) { + seq = *(ast.val) + if (numberof(seq) == 0) return ast + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalVector(val=&res) + } else if (type == MalHashmap) { + h = *(ast.val) + if (numberof(*h.keys) == 0) return ast + res = hash_new() + for (i = 1; i <= numberof(*h.keys); ++i) { + new_key = EVAL(hashmap_key_to_obj((*h.keys)(i)), env) + if (structof(new_key) == MalError) return new_key + new_val = EVAL(*((*h.vals)(i)), env) + if (structof(new_val) == MalError) return new_val + hash_set, res, hashmap_obj_to_key(new_key), new_val + } + return MalHashmap(val=&res) + } else return ast +} + +func EVAL(ast, env) +{ + while (1) { + if (structof(ast) == MalError) return ast + if (structof(ast) != MalList) return eval_ast(ast, env) + lst = *ast.val + if (numberof(lst) == 0) return ast + a1 = lst(1)->val + if (a1 == "def!") { + new_value = EVAL(*lst(3), env) + if (structof(new_value) == MalError) return new_value + return env_set(env, lst(2)->val, new_value) + } else if (a1 == "let*") { + let_env = env_new(&env) + args_lst = *(lst(2)->val) + for (i = 1; i <= numberof(args_lst); i += 2) { + var_name = args_lst(i)->val + var_value = EVAL(*args_lst(i + 1), let_env) + if (structof(var_value) == MalError) return var_value + env_set, let_env, var_name, var_value + } + ast = *lst(3) + env = let_env + // TCO + } else if (a1 == "do") { + for (i = 2; i < numberof(lst); ++i) { + ret = EVAL(*lst(i), env) + if (structof(ret) == MalError) return ret + } + ast = *lst(numberof(lst)) + // TCO + } else if (a1 == "if") { + cond_val = EVAL(*lst(2), env) + if (structof(cond_val) == MalError) return cond_val + if ((structof(cond_val) == MalNil) || (structof(cond_val) == MalFalse)) { + if (numberof(lst) > 3) { + ast = *lst(4) + } else { + return MAL_NIL + } + } else { + ast = *lst(3) + } + // TCO + } else if (a1 == "fn*") { + return MalFunction(env=&env, binds=lst(2)->val, ast=lst(3)) + } else { + el = eval_ast(ast, env) + if (structof(el) == MalError) return el + seq = *el.val + if (structof(*seq(1)) == MalNativeFunction) { + args = (numberof(seq) > 1) ? seq(2:) : [] + return call_core_fn(seq(1)->val, args) + } else if (structof(*seq(1)) == MalFunction) { + fn = *seq(1) + exprs = numberof(seq) > 1 ? seq(2:) : [] + fn_env = env_new(fn.env, binds=*fn.binds, exprs=exprs) + ast = *fn.ast + env = fn_env + // TCO + } else { + return MalError(message="Unknown function type") + } + } + } +} + +func PRINT(exp) +{ + if (structof(exp) == MalError) return exp + return pr_str(exp, 1) +} + +func RE(str, env) +{ + return EVAL(READ(str), env) +} + +func REP(str, env) +{ + return PRINT(EVAL(READ(str), env)) +} + +func main(void) +{ + repl_env = env_new(pointer(0)) + + // core.i: defined using Yorick + core_symbols = h_keys(core_ns) + for (i = 1; i <= numberof(core_symbols); ++i) { + env_set, repl_env, core_symbols(i), MalNativeFunction(val=core_symbols(i)) + } + + // core.mal: defined using the language itself + RE, "(def! not (fn* (a) (if a false true)))", repl_env + + stdin_file = open("/dev/stdin", "r") + while (1) { + write, format="%s", "user> " + line = rdline(stdin_file, prompt="") + if (!line) break + if (strlen(line) > 0) { + result = REP(line, repl_env) + if (structof(result) == MalError) write, format="Error: %s\n", result.message + else write, format="%s\n", result + } + } + write, "" +} + +main; diff --git a/yorick/step6_file.i b/yorick/step6_file.i new file mode 100644 index 0000000000..70dfe901fc --- /dev/null +++ b/yorick/step6_file.i @@ -0,0 +1,190 @@ +set_path, get_env("YORICK_MAL_PATH") + ":" + get_path() +require, "reader.i" +require, "printer.i" +require, "core.i" +require, "env.i" + +func READ(str) +{ + return read_str(str) +} + +func eval_ast(ast, env) +{ + type = structof(ast) + if (type == MalSymbol) { + return env_get(env, ast.val) + } else if (type == MalList) { + seq = *(ast.val) + if (numberof(seq) == 0) return ast + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalList(val=&res) + } else if (type == MalVector) { + seq = *(ast.val) + if (numberof(seq) == 0) return ast + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalVector(val=&res) + } else if (type == MalHashmap) { + h = *(ast.val) + if (numberof(*h.keys) == 0) return ast + res = hash_new() + for (i = 1; i <= numberof(*h.keys); ++i) { + new_key = EVAL(hashmap_key_to_obj((*h.keys)(i)), env) + if (structof(new_key) == MalError) return new_key + new_val = EVAL(*((*h.vals)(i)), env) + if (structof(new_val) == MalError) return new_val + hash_set, res, hashmap_obj_to_key(new_key), new_val + } + return MalHashmap(val=&res) + } else return ast +} + +func EVAL(ast, env) +{ + while (1) { + if (structof(ast) == MalError) return ast + if (structof(ast) != MalList) return eval_ast(ast, env) + lst = *ast.val + if (numberof(lst) == 0) return ast + a1 = lst(1)->val + if (a1 == "def!") { + new_value = EVAL(*lst(3), env) + if (structof(new_value) == MalError) return new_value + return env_set(env, lst(2)->val, new_value) + } else if (a1 == "let*") { + let_env = env_new(&env) + args_lst = *(lst(2)->val) + for (i = 1; i <= numberof(args_lst); i += 2) { + var_name = args_lst(i)->val + var_value = EVAL(*args_lst(i + 1), let_env) + if (structof(var_value) == MalError) return var_value + env_set, let_env, var_name, var_value + } + ast = *lst(3) + env = let_env + // TCO + } else if (a1 == "do") { + for (i = 2; i < numberof(lst); ++i) { + ret = EVAL(*lst(i), env) + if (structof(ret) == MalError) return ret + } + ast = *lst(numberof(lst)) + // TCO + } else if (a1 == "if") { + cond_val = EVAL(*lst(2), env) + if (structof(cond_val) == MalError) return cond_val + if ((structof(cond_val) == MalNil) || (structof(cond_val) == MalFalse)) { + if (numberof(lst) > 3) { + ast = *lst(4) + } else { + return MAL_NIL + } + } else { + ast = *lst(3) + } + // TCO + } else if (a1 == "fn*") { + return MalFunction(env=&env, binds=lst(2)->val, ast=lst(3)) + } else { + el = eval_ast(ast, env) + if (structof(el) == MalError) return el + seq = *el.val + if (structof(*seq(1)) == MalNativeFunction) { + args = (numberof(seq) > 1) ? seq(2:) : [] + return call_core_fn(seq(1)->val, args) + } else if (structof(*seq(1)) == MalFunction) { + fn = *seq(1) + exprs = numberof(seq) > 1 ? seq(2:) : [] + fn_env = env_new(fn.env, binds=*fn.binds, exprs=exprs) + ast = *fn.ast + env = fn_env + // TCO + } else { + return MalError(message="Unknown function type") + } + } + } +} + +func PRINT(exp) +{ + if (structof(exp) == MalError) return exp + return pr_str(exp, 1) +} + +func RE(str, env) +{ + return EVAL(READ(str), env) +} + +func REP(str, env) +{ + return PRINT(EVAL(READ(str), env)) +} + +func get_command_line(void) +// Force quiet mode (-q) to prevent Yorick from printing its banner +{ + argv = get_argv() + return numberof(argv) > 1 ? grow([argv(1), "-q"], argv(2:)) : [argv(1), "-q"] +} + +func prepare_argv_list(args) +{ + if (numberof(args) <= 1) return MalList(val=&[]) + str_lst = array(pointer, numberof(args) - 1) + for (i = 2; i <= numberof(args); ++i) { + str_lst(i - 1) = &MalString(val=args(i)) + } + return MalList(val=&str_lst) +} + +repl_env = nil + +func main(void) +{ + extern repl_env + repl_env = env_new(pointer(0)) + + // core.i: defined using Yorick + core_symbols = h_keys(core_ns) + for (i = 1; i <= numberof(core_symbols); ++i) { + env_set, repl_env, core_symbols(i), MalNativeFunction(val=core_symbols(i)) + } + command_line_args = process_argv() + env_set, repl_env, "*ARGV*", prepare_argv_list(command_line_args) + + // core.mal: defined using the language itself + RE, "(def! not (fn* (a) (if a false true)))", repl_env + RE, "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env + + if (numberof(command_line_args) > 0) { + RE, "(load-file \"" + command_line_args(1) + "\")", repl_env + return 0 + } + + stdin_file = open("/dev/stdin", "r") + while (1) { + write, format="%s", "user> " + line = rdline(stdin_file, prompt="") + if (!line) break + if (strlen(line) > 0) { + result = REP(line, repl_env) + if (structof(result) == MalError) write, format="Error: %s\n", result.message + else write, format="%s\n", result + } + } + write, "" +} + +main; diff --git a/yorick/step7_quote.i b/yorick/step7_quote.i new file mode 100644 index 0000000000..bf9cbd1aad --- /dev/null +++ b/yorick/step7_quote.i @@ -0,0 +1,215 @@ +set_path, get_env("YORICK_MAL_PATH") + ":" + get_path() +require, "reader.i" +require, "printer.i" +require, "core.i" +require, "env.i" + +func READ(str) +{ + return read_str(str) +} + +func is_pair(ast) +{ + type = structof(ast) + return ((type == MalList) || (type == MalVector)) && count(ast) > 0 +} + +func quasiquote(ast) +{ + if (!is_pair(ast)) return MalList(val=&[&MalSymbol(val="quote"), &ast]) + lst = *ast.val + ast1 = *lst(1) + if (structof(ast1) == MalSymbol && ast1.val == "unquote") return *lst(2) + if (is_pair(ast1)) { + ast11 = *((*ast1.val)(1)) + if (structof(ast11) == MalSymbol && ast11.val == "splice-unquote") { + return MalList(val=&[&MalSymbol(val="concat"), (*ast1.val)(2), &quasiquote(rest(ast))]) + } + } + return MalList(val=&[&MalSymbol(val="cons"), &quasiquote(ast1), &quasiquote(rest(ast))]) +} + +func eval_ast(ast, env) +{ + type = structof(ast) + if (type == MalSymbol) { + return env_get(env, ast.val) + } else if (type == MalList) { + seq = *(ast.val) + if (numberof(seq) == 0) return ast + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalList(val=&res) + } else if (type == MalVector) { + seq = *(ast.val) + if (numberof(seq) == 0) return ast + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalVector(val=&res) + } else if (type == MalHashmap) { + h = *(ast.val) + if (numberof(*h.keys) == 0) return ast + res = hash_new() + for (i = 1; i <= numberof(*h.keys); ++i) { + new_key = EVAL(hashmap_key_to_obj((*h.keys)(i)), env) + if (structof(new_key) == MalError) return new_key + new_val = EVAL(*((*h.vals)(i)), env) + if (structof(new_val) == MalError) return new_val + hash_set, res, hashmap_obj_to_key(new_key), new_val + } + return MalHashmap(val=&res) + } else return ast +} + +func EVAL(ast, env) +{ + while (1) { + if (structof(ast) == MalError) return ast + if (structof(ast) != MalList) return eval_ast(ast, env) + lst = *ast.val + if (numberof(lst) == 0) return ast + a1 = lst(1)->val + if (a1 == "def!") { + new_value = EVAL(*lst(3), env) + if (structof(new_value) == MalError) return new_value + return env_set(env, lst(2)->val, new_value) + } else if (a1 == "let*") { + let_env = env_new(&env) + args_lst = *(lst(2)->val) + for (i = 1; i <= numberof(args_lst); i += 2) { + var_name = args_lst(i)->val + var_value = EVAL(*args_lst(i + 1), let_env) + if (structof(var_value) == MalError) return var_value + env_set, let_env, var_name, var_value + } + ast = *lst(3) + env = let_env + // TCO + } else if (a1 == "quote") { + return *lst(2) + } else if (a1 == "quasiquote") { + ast = quasiquote(*lst(2)) // TCO + } else if (a1 == "do") { + for (i = 2; i < numberof(lst); ++i) { + ret = EVAL(*lst(i), env) + if (structof(ret) == MalError) return ret + } + ast = *lst(numberof(lst)) + // TCO + } else if (a1 == "if") { + cond_val = EVAL(*lst(2), env) + if (structof(cond_val) == MalError) return cond_val + if ((structof(cond_val) == MalNil) || (structof(cond_val) == MalFalse)) { + if (numberof(lst) > 3) { + ast = *lst(4) + } else { + return MAL_NIL + } + } else { + ast = *lst(3) + } + // TCO + } else if (a1 == "fn*") { + return MalFunction(env=&env, binds=lst(2)->val, ast=lst(3)) + } else { + el = eval_ast(ast, env) + if (structof(el) == MalError) return el + seq = *el.val + if (structof(*seq(1)) == MalNativeFunction) { + args = (numberof(seq) > 1) ? seq(2:) : [] + return call_core_fn(seq(1)->val, args) + } else if (structof(*seq(1)) == MalFunction) { + fn = *seq(1) + exprs = numberof(seq) > 1 ? seq(2:) : [] + fn_env = env_new(fn.env, binds=*fn.binds, exprs=exprs) + ast = *fn.ast + env = fn_env + // TCO + } else { + return MalError(message="Unknown function type") + } + } + } +} + +func PRINT(exp) +{ + if (structof(exp) == MalError) return exp + return pr_str(exp, 1) +} + +func RE(str, env) +{ + return EVAL(READ(str), env) +} + +func REP(str, env) +{ + return PRINT(EVAL(READ(str), env)) +} + +func get_command_line(void) +// Force quiet mode (-q) to prevent Yorick from printing its banner +{ + argv = get_argv() + return numberof(argv) > 1 ? grow([argv(1), "-q"], argv(2:)) : [argv(1), "-q"] +} + +func prepare_argv_list(args) +{ + if (numberof(args) <= 1) return MalList(val=&[]) + str_lst = array(pointer, numberof(args) - 1) + for (i = 2; i <= numberof(args); ++i) { + str_lst(i - 1) = &MalString(val=args(i)) + } + return MalList(val=&str_lst) +} + +repl_env = nil + +func main(void) +{ + extern repl_env + repl_env = env_new(pointer(0)) + + // core.i: defined using Yorick + core_symbols = h_keys(core_ns) + for (i = 1; i <= numberof(core_symbols); ++i) { + env_set, repl_env, core_symbols(i), MalNativeFunction(val=core_symbols(i)) + } + command_line_args = process_argv() + env_set, repl_env, "*ARGV*", prepare_argv_list(command_line_args) + + // core.mal: defined using the language itself + RE, "(def! not (fn* (a) (if a false true)))", repl_env + RE, "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env + + if (numberof(command_line_args) > 0) { + RE, "(load-file \"" + command_line_args(1) + "\")", repl_env + return 0 + } + + stdin_file = open("/dev/stdin", "r") + while (1) { + write, format="%s", "user> " + line = rdline(stdin_file, prompt="") + if (!line) break + if (strlen(line) > 0) { + result = REP(line, repl_env) + if (structof(result) == MalError) write, format="Error: %s\n", result.message + else write, format="%s\n", result + } + } + write, "" +} + +main; diff --git a/yorick/step8_macros.i b/yorick/step8_macros.i new file mode 100644 index 0000000000..c5c5fb840f --- /dev/null +++ b/yorick/step8_macros.i @@ -0,0 +1,251 @@ +set_path, get_env("YORICK_MAL_PATH") + ":" + get_path() +require, "reader.i" +require, "printer.i" +require, "core.i" +require, "env.i" + +func READ(str) +{ + return read_str(str) +} + +func is_pair(ast) +{ + type = structof(ast) + return ((type == MalList) || (type == MalVector)) && count(ast) > 0 +} + +func quasiquote(ast) +{ + if (!is_pair(ast)) return MalList(val=&[&MalSymbol(val="quote"), &ast]) + lst = *ast.val + ast1 = *lst(1) + if (structof(ast1) == MalSymbol && ast1.val == "unquote") return *lst(2) + if (is_pair(ast1)) { + ast11 = *((*ast1.val)(1)) + if (structof(ast11) == MalSymbol && ast11.val == "splice-unquote") { + return MalList(val=&[&MalSymbol(val="concat"), (*ast1.val)(2), &quasiquote(rest(ast))]) + } + } + return MalList(val=&[&MalSymbol(val="cons"), &quasiquote(ast1), &quasiquote(rest(ast))]) +} + +func is_macro_call(ast, env) +{ + if (structof(ast) != MalList) return 0 + if (count(ast) == 0) return 0 + a1 = *((*ast.val)(1)) + if (structof(a1) != MalSymbol) return 0 + var_name = a1.val + found_env = env_find(env, var_name) + if (is_void(found_env)) return 0 + obj = env_get(env, var_name) + return is_macro(obj) +} + +func macroexpand(ast, env) +{ + while (is_macro_call(ast, env)) { + macro_name = (*ast.val)(1)->val + macro_obj = env_get(env, macro_name) + macro_args = *rest(ast).val + fn_env = env_new(macro_obj.env, binds=*macro_obj.binds, exprs=macro_args) + ast = EVAL(*macro_obj.ast, fn_env) + } + return ast +} + +func eval_ast(ast, env) +{ + type = structof(ast) + if (type == MalSymbol) { + return env_get(env, ast.val) + } else if (type == MalList) { + seq = *(ast.val) + if (numberof(seq) == 0) return ast + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalList(val=&res) + } else if (type == MalVector) { + seq = *(ast.val) + if (numberof(seq) == 0) return ast + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalVector(val=&res) + } else if (type == MalHashmap) { + h = *(ast.val) + if (numberof(*h.keys) == 0) return ast + res = hash_new() + for (i = 1; i <= numberof(*h.keys); ++i) { + new_key = EVAL(hashmap_key_to_obj((*h.keys)(i)), env) + if (structof(new_key) == MalError) return new_key + new_val = EVAL(*((*h.vals)(i)), env) + if (structof(new_val) == MalError) return new_val + hash_set, res, hashmap_obj_to_key(new_key), new_val + } + return MalHashmap(val=&res) + } else return ast +} + +func EVAL(ast, env) +{ + while (1) { + if (structof(ast) == MalError) return ast + if (structof(ast) != MalList) return eval_ast(ast, env) + ast = macroexpand(ast, env) + if (structof(ast) != MalList) return eval_ast(ast, env) + lst = *ast.val + if (numberof(lst) == 0) return ast + a1 = lst(1)->val + if (a1 == "def!") { + new_value = EVAL(*lst(3), env) + if (structof(new_value) == MalError) return new_value + return env_set(env, lst(2)->val, new_value) + } else if (a1 == "let*") { + let_env = env_new(&env) + args_lst = *(lst(2)->val) + for (i = 1; i <= numberof(args_lst); i += 2) { + var_name = args_lst(i)->val + var_value = EVAL(*args_lst(i + 1), let_env) + if (structof(var_value) == MalError) return var_value + env_set, let_env, var_name, var_value + } + ast = *lst(3) + env = let_env + // TCO + } else if (a1 == "quote") { + return *lst(2) + } else if (a1 == "quasiquote") { + ast = quasiquote(*lst(2)) // TCO + } else if (a1 == "defmacro!") { + new_value = EVAL(*lst(3), env) + if (structof(new_value) == MalError) return new_value + new_value.macro = 1 + return env_set(env, lst(2)->val, new_value) + } else if (a1 == "macroexpand") { + return macroexpand(*lst(2), env) + } else if (a1 == "do") { + for (i = 2; i < numberof(lst); ++i) { + ret = EVAL(*lst(i), env) + if (structof(ret) == MalError) return ret + } + ast = *lst(numberof(lst)) + // TCO + } else if (a1 == "if") { + cond_val = EVAL(*lst(2), env) + if (structof(cond_val) == MalError) return cond_val + if ((structof(cond_val) == MalNil) || (structof(cond_val) == MalFalse)) { + if (numberof(lst) > 3) { + ast = *lst(4) + } else { + return MAL_NIL + } + } else { + ast = *lst(3) + } + // TCO + } else if (a1 == "fn*") { + return MalFunction(env=&env, binds=lst(2)->val, ast=lst(3), macro=0) + } else { + el = eval_ast(ast, env) + if (structof(el) == MalError) return el + seq = *el.val + if (structof(*seq(1)) == MalNativeFunction) { + args = (numberof(seq) > 1) ? seq(2:) : [] + return call_core_fn(seq(1)->val, args) + } else if (structof(*seq(1)) == MalFunction) { + fn = *seq(1) + exprs = numberof(seq) > 1 ? seq(2:) : [] + fn_env = env_new(fn.env, binds=*fn.binds, exprs=exprs) + ast = *fn.ast + env = fn_env + // TCO + } else { + return MalError(message="Unknown function type") + } + } + } +} + +func PRINT(exp) +{ + if (structof(exp) == MalError) return exp + return pr_str(exp, 1) +} + +func RE(str, env) +{ + return EVAL(READ(str), env) +} + +func REP(str, env) +{ + return PRINT(EVAL(READ(str), env)) +} + +func get_command_line(void) +// Force quiet mode (-q) to prevent Yorick from printing its banner +{ + argv = get_argv() + return numberof(argv) > 1 ? grow([argv(1), "-q"], argv(2:)) : [argv(1), "-q"] +} + +func prepare_argv_list(args) +{ + if (numberof(args) <= 1) return MalList(val=&[]) + str_lst = array(pointer, numberof(args) - 1) + for (i = 2; i <= numberof(args); ++i) { + str_lst(i - 1) = &MalString(val=args(i)) + } + return MalList(val=&str_lst) +} + +repl_env = nil + +func main(void) +{ + extern repl_env + repl_env = env_new(pointer(0)) + + // core.i: defined using Yorick + core_symbols = h_keys(core_ns) + for (i = 1; i <= numberof(core_symbols); ++i) { + env_set, repl_env, core_symbols(i), MalNativeFunction(val=core_symbols(i)) + } + command_line_args = process_argv() + env_set, repl_env, "*ARGV*", prepare_argv_list(command_line_args) + + // core.mal: defined using the language itself + RE, "(def! not (fn* (a) (if a false true)))", repl_env + RE, "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env + RE, "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", repl_env + RE, "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", repl_env + + if (numberof(command_line_args) > 0) { + RE, "(load-file \"" + command_line_args(1) + "\")", repl_env + return 0 + } + + stdin_file = open("/dev/stdin", "r") + while (1) { + write, format="%s", "user> " + line = rdline(stdin_file, prompt="") + if (!line) break + if (strlen(line) > 0) { + result = REP(line, repl_env) + if (structof(result) == MalError) write, format="Error: %s\n", result.message + else write, format="%s\n", result + } + } + write, "" +} + +main; diff --git a/yorick/step9_try.i b/yorick/step9_try.i new file mode 100644 index 0000000000..a4434974ea --- /dev/null +++ b/yorick/step9_try.i @@ -0,0 +1,265 @@ +set_path, get_env("YORICK_MAL_PATH") + ":" + get_path() +require, "reader.i" +require, "printer.i" +require, "core.i" +require, "env.i" + +func READ(str) +{ + return read_str(str) +} + +func is_pair(ast) +{ + type = structof(ast) + return ((type == MalList) || (type == MalVector)) && count(ast) > 0 +} + +func quasiquote(ast) +{ + if (!is_pair(ast)) return MalList(val=&[&MalSymbol(val="quote"), &ast]) + lst = *ast.val + ast1 = *lst(1) + if (structof(ast1) == MalSymbol && ast1.val == "unquote") return *lst(2) + if (is_pair(ast1)) { + ast11 = *((*ast1.val)(1)) + if (structof(ast11) == MalSymbol && ast11.val == "splice-unquote") { + return MalList(val=&[&MalSymbol(val="concat"), (*ast1.val)(2), &quasiquote(rest(ast))]) + } + } + return MalList(val=&[&MalSymbol(val="cons"), &quasiquote(ast1), &quasiquote(rest(ast))]) +} + +func is_macro_call(ast, env) +{ + if (structof(ast) != MalList) return 0 + if (count(ast) == 0) return 0 + a1 = *((*ast.val)(1)) + if (structof(a1) != MalSymbol) return 0 + var_name = a1.val + found_env = env_find(env, var_name) + if (is_void(found_env)) return 0 + obj = env_get(env, var_name) + return is_macro(obj) +} + +func macroexpand(ast, env) +{ + while (is_macro_call(ast, env)) { + macro_name = (*ast.val)(1)->val + macro_obj = env_get(env, macro_name) + macro_args = *rest(ast).val + fn_env = env_new(macro_obj.env, binds=*macro_obj.binds, exprs=macro_args) + ast = EVAL(*macro_obj.ast, fn_env) + } + return ast +} + +func eval_ast(ast, env) +{ + type = structof(ast) + if (type == MalSymbol) { + return env_get(env, ast.val) + } else if (type == MalList) { + seq = *(ast.val) + if (numberof(seq) == 0) return ast + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalList(val=&res) + } else if (type == MalVector) { + seq = *(ast.val) + if (numberof(seq) == 0) return ast + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalVector(val=&res) + } else if (type == MalHashmap) { + h = *(ast.val) + if (numberof(*h.keys) == 0) return ast + res = hash_new() + for (i = 1; i <= numberof(*h.keys); ++i) { + new_key = EVAL(hashmap_key_to_obj((*h.keys)(i)), env) + if (structof(new_key) == MalError) return new_key + new_val = EVAL(*((*h.vals)(i)), env) + if (structof(new_val) == MalError) return new_val + hash_set, res, hashmap_obj_to_key(new_key), new_val + } + return MalHashmap(val=&res) + } else return ast +} + +func EVAL(ast, env) +{ + while (1) { + if (structof(ast) == MalError) return ast + if (structof(ast) != MalList) return eval_ast(ast, env) + ast = macroexpand(ast, env) + if (structof(ast) != MalList) return eval_ast(ast, env) + lst = *ast.val + if (numberof(lst) == 0) return ast + a1 = lst(1)->val + if (a1 == "def!") { + new_value = EVAL(*lst(3), env) + if (structof(new_value) == MalError) return new_value + return env_set(env, lst(2)->val, new_value) + } else if (a1 == "let*") { + let_env = env_new(&env) + args_lst = *(lst(2)->val) + for (i = 1; i <= numberof(args_lst); i += 2) { + var_name = args_lst(i)->val + var_value = EVAL(*args_lst(i + 1), let_env) + if (structof(var_value) == MalError) return var_value + env_set, let_env, var_name, var_value + } + ast = *lst(3) + env = let_env + // TCO + } else if (a1 == "quote") { + return *lst(2) + } else if (a1 == "quasiquote") { + ast = quasiquote(*lst(2)) // TCO + } else if (a1 == "defmacro!") { + new_value = EVAL(*lst(3), env) + if (structof(new_value) == MalError) return new_value + new_value.macro = 1 + return env_set(env, lst(2)->val, new_value) + } else if (a1 == "macroexpand") { + return macroexpand(*lst(2), env) + } else if (a1 == "try*") { + ret = EVAL(*lst(2), env) + if (structof(ret) == MalError) { + exc = *ret.obj + if (is_void(exc)) { + exc = MalString(val=ret.message) + } + catch_lst = *(lst(3)->val) + catch_env = env_new(&env) + env_set, catch_env, catch_lst(2)->val, exc + return EVAL(*catch_lst(3), catch_env) + } else { + return ret + } + } else if (a1 == "do") { + for (i = 2; i < numberof(lst); ++i) { + ret = EVAL(*lst(i), env) + if (structof(ret) == MalError) return ret + } + ast = *lst(numberof(lst)) + // TCO + } else if (a1 == "if") { + cond_val = EVAL(*lst(2), env) + if (structof(cond_val) == MalError) return cond_val + if ((structof(cond_val) == MalNil) || (structof(cond_val) == MalFalse)) { + if (numberof(lst) > 3) { + ast = *lst(4) + } else { + return MAL_NIL + } + } else { + ast = *lst(3) + } + // TCO + } else if (a1 == "fn*") { + return MalFunction(env=&env, binds=lst(2)->val, ast=lst(3), macro=0) + } else { + el = eval_ast(ast, env) + if (structof(el) == MalError) return el + seq = *el.val + if (structof(*seq(1)) == MalNativeFunction) { + args = (numberof(seq) > 1) ? seq(2:) : [] + return call_core_fn(seq(1)->val, args) + } else if (structof(*seq(1)) == MalFunction) { + fn = *seq(1) + exprs = numberof(seq) > 1 ? seq(2:) : [] + fn_env = env_new(fn.env, binds=*fn.binds, exprs=exprs) + ast = *fn.ast + env = fn_env + // TCO + } else { + return MalError(message="Unknown function type") + } + } + } +} + +func PRINT(exp) +{ + if (structof(exp) == MalError) return exp + return pr_str(exp, 1) +} + +func RE(str, env) +{ + return EVAL(READ(str), env) +} + +func REP(str, env) +{ + return PRINT(EVAL(READ(str), env)) +} + +func get_command_line(void) +// Force quiet mode (-q) to prevent Yorick from printing its banner +{ + argv = get_argv() + return numberof(argv) > 1 ? grow([argv(1), "-q"], argv(2:)) : [argv(1), "-q"] +} + +func prepare_argv_list(args) +{ + if (numberof(args) <= 1) return MalList(val=&[]) + str_lst = array(pointer, numberof(args) - 1) + for (i = 2; i <= numberof(args); ++i) { + str_lst(i - 1) = &MalString(val=args(i)) + } + return MalList(val=&str_lst) +} + +repl_env = nil + +func main(void) +{ + extern repl_env + repl_env = env_new(pointer(0)) + + // core.i: defined using Yorick + core_symbols = h_keys(core_ns) + for (i = 1; i <= numberof(core_symbols); ++i) { + env_set, repl_env, core_symbols(i), MalNativeFunction(val=core_symbols(i)) + } + command_line_args = process_argv() + env_set, repl_env, "*ARGV*", prepare_argv_list(command_line_args) + + // core.mal: defined using the language itself + RE, "(def! not (fn* (a) (if a false true)))", repl_env + RE, "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env + RE, "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", repl_env + RE, "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", repl_env + + if (numberof(command_line_args) > 0) { + RE, "(load-file \"" + command_line_args(1) + "\")", repl_env + return 0 + } + + stdin_file = open("/dev/stdin", "r") + while (1) { + write, format="%s", "user> " + line = rdline(stdin_file, prompt="") + if (!line) break + if (strlen(line) > 0) { + result = REP(line, repl_env) + if (structof(result) == MalError) write, format="Error: %s\n", result.message + else write, format="%s\n", result + } + } + write, "" +} + +main; diff --git a/yorick/stepA_mal.i b/yorick/stepA_mal.i new file mode 100644 index 0000000000..53829636dc --- /dev/null +++ b/yorick/stepA_mal.i @@ -0,0 +1,270 @@ +set_path, get_env("YORICK_MAL_PATH") + ":" + get_path() +require, "reader.i" +require, "printer.i" +require, "core.i" +require, "env.i" + +func READ(str) +{ + return read_str(str) +} + +func is_pair(ast) +{ + type = structof(ast) + return ((type == MalList) || (type == MalVector)) && count(ast) > 0 +} + +func quasiquote(ast) +{ + if (!is_pair(ast)) return MalList(val=&[&MalSymbol(val="quote"), &ast]) + lst = *ast.val + ast1 = *lst(1) + if (structof(ast1) == MalSymbol && ast1.val == "unquote") return *lst(2) + if (is_pair(ast1)) { + ast11 = *((*ast1.val)(1)) + if (structof(ast11) == MalSymbol && ast11.val == "splice-unquote") { + return MalList(val=&[&MalSymbol(val="concat"), (*ast1.val)(2), &quasiquote(rest(ast))]) + } + } + return MalList(val=&[&MalSymbol(val="cons"), &quasiquote(ast1), &quasiquote(rest(ast))]) +} + +func is_macro_call(ast, env) +{ + if (structof(ast) != MalList) return 0 + if (count(ast) == 0) return 0 + a1 = *((*ast.val)(1)) + if (structof(a1) != MalSymbol) return 0 + var_name = a1.val + found_env = env_find(env, var_name) + if (is_void(found_env)) return 0 + obj = env_get(env, var_name) + return is_macro(obj) +} + +func macroexpand(ast, env) +{ + while (is_macro_call(ast, env)) { + macro_name = (*ast.val)(1)->val + macro_obj = env_get(env, macro_name) + macro_args = *rest(ast).val + fn_env = env_new(macro_obj.env, binds=*macro_obj.binds, exprs=macro_args) + ast = EVAL(*macro_obj.ast, fn_env) + } + return ast +} + +func eval_ast(ast, env) +{ + type = structof(ast) + if (type == MalSymbol) { + return env_get(env, ast.val) + } else if (type == MalList) { + seq = *(ast.val) + if (numberof(seq) == 0) return ast + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalList(val=&res) + } else if (type == MalVector) { + seq = *(ast.val) + if (numberof(seq) == 0) return ast + res = array(pointer, numberof(seq)) + for (i = 1; i <= numberof(seq); ++i) { + e = EVAL(*seq(i), env) + if (structof(e) == MalError) return e + res(i) = &e + } + return MalVector(val=&res) + } else if (type == MalHashmap) { + h = *(ast.val) + if (numberof(*h.keys) == 0) return ast + res = hash_new() + for (i = 1; i <= numberof(*h.keys); ++i) { + new_key = EVAL(hashmap_key_to_obj((*h.keys)(i)), env) + if (structof(new_key) == MalError) return new_key + new_val = EVAL(*((*h.vals)(i)), env) + if (structof(new_val) == MalError) return new_val + hash_set, res, hashmap_obj_to_key(new_key), new_val + } + return MalHashmap(val=&res) + } else return ast +} + +func EVAL(ast, env) +{ + while (1) { + if (structof(ast) == MalError) return ast + if (structof(ast) != MalList) return eval_ast(ast, env) + ast = macroexpand(ast, env) + if (structof(ast) != MalList) return eval_ast(ast, env) + lst = *ast.val + if (numberof(lst) == 0) return ast + a1 = lst(1)->val + if (a1 == "def!") { + new_value = EVAL(*lst(3), env) + if (structof(new_value) == MalError) return new_value + return env_set(env, lst(2)->val, new_value) + } else if (a1 == "let*") { + let_env = env_new(&env) + args_lst = *(lst(2)->val) + for (i = 1; i <= numberof(args_lst); i += 2) { + var_name = args_lst(i)->val + var_value = EVAL(*args_lst(i + 1), let_env) + if (structof(var_value) == MalError) return var_value + env_set, let_env, var_name, var_value + } + ast = *lst(3) + env = let_env + // TCO + } else if (a1 == "quote") { + return *lst(2) + } else if (a1 == "quasiquote") { + ast = quasiquote(*lst(2)) // TCO + } else if (a1 == "defmacro!") { + new_value = EVAL(*lst(3), env) + if (structof(new_value) == MalError) return new_value + new_value.macro = 1 + return env_set(env, lst(2)->val, new_value) + } else if (a1 == "macroexpand") { + return macroexpand(*lst(2), env) + } else if (a1 == "try*") { + ret = EVAL(*lst(2), env) + if (structof(ret) == MalError) { + exc = *ret.obj + if (is_void(exc)) { + exc = MalString(val=ret.message) + } + catch_lst = *(lst(3)->val) + catch_env = env_new(&env) + env_set, catch_env, catch_lst(2)->val, exc + return EVAL(*catch_lst(3), catch_env) + } else { + return ret + } + } else if (a1 == "do") { + for (i = 2; i < numberof(lst); ++i) { + ret = EVAL(*lst(i), env) + if (structof(ret) == MalError) return ret + } + ast = *lst(numberof(lst)) + // TCO + } else if (a1 == "if") { + cond_val = EVAL(*lst(2), env) + if (structof(cond_val) == MalError) return cond_val + if ((structof(cond_val) == MalNil) || (structof(cond_val) == MalFalse)) { + if (numberof(lst) > 3) { + ast = *lst(4) + } else { + return MAL_NIL + } + } else { + ast = *lst(3) + } + // TCO + } else if (a1 == "fn*") { + return MalFunction(env=&env, binds=lst(2)->val, ast=lst(3), macro=0) + } else { + el = eval_ast(ast, env) + if (structof(el) == MalError) return el + seq = *el.val + if (structof(*seq(1)) == MalNativeFunction) { + args = (numberof(seq) > 1) ? seq(2:) : [] + return call_core_fn(seq(1)->val, args) + } else if (structof(*seq(1)) == MalFunction) { + fn = *seq(1) + exprs = numberof(seq) > 1 ? seq(2:) : [] + fn_env = env_new(fn.env, binds=*fn.binds, exprs=exprs) + ast = *fn.ast + env = fn_env + // TCO + } else { + return MalError(message="Unknown function type") + } + } + } +} + +func PRINT(exp) +{ + if (structof(exp) == MalError) return exp + return pr_str(exp, 1) +} + +func RE(str, env) +{ + return EVAL(READ(str), env) +} + +func REP(str, env) +{ + return PRINT(EVAL(READ(str), env)) +} + +func get_command_line(void) +// Force quiet mode (-q) to prevent Yorick from printing its banner +{ + argv = get_argv() + return numberof(argv) > 1 ? grow([argv(1), "-q"], argv(2:)) : [argv(1), "-q"] +} + +func prepare_argv_list(args) +{ + if (numberof(args) <= 1) return MalList(val=&[]) + str_lst = array(pointer, numberof(args) - 1) + for (i = 2; i <= numberof(args); ++i) { + str_lst(i - 1) = &MalString(val=args(i)) + } + return MalList(val=&str_lst) +} + +repl_env = nil +stdin_file = open("/dev/stdin", "r") + +func main(void) +{ + extern repl_env + extern stdin_file + repl_env = env_new(pointer(0)) + + // core.i: defined using Yorick + core_symbols = h_keys(core_ns) + for (i = 1; i <= numberof(core_symbols); ++i) { + env_set, repl_env, core_symbols(i), MalNativeFunction(val=core_symbols(i)) + } + command_line_args = process_argv() + env_set, repl_env, "*ARGV*", prepare_argv_list(command_line_args) + + // core.mal: defined using the language itself + RE, "(def! *host-language* \"yorick\")", repl_env + RE, "(def! not (fn* (a) (if a false true)))", repl_env + RE, "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env + RE, "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", repl_env + RE, "(def! *gensym-counter* (atom 0))", repl_env + RE, "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))", repl_env + RE, "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))", repl_env + + if (numberof(command_line_args) > 0) { + RE, "(load-file \"" + command_line_args(1) + "\")", repl_env + return 0 + } + + RE, "(println (str \"Mal [\" *host-language* \"]\"))", repl_env + while (1) { + write, format="%s", "user> " + line = rdline(stdin_file, prompt="") + if (!line) break + if (strlen(line) > 0) { + result = REP(line, repl_env) + if (structof(result) == MalError) write, format="Error: %s\n", result.message + else write, format="%s\n", result + } + } + write, "" +} + +main; diff --git a/yorick/types.i b/yorick/types.i new file mode 100644 index 0000000000..a171e31dd1 --- /dev/null +++ b/yorick/types.i @@ -0,0 +1,166 @@ +require, "hash.i" + +struct MalError { + string message + pointer obj +} + +struct MalNil { + int val +} + +MAL_NIL = MalNil() + +struct MalTrue { + int val +} + +MAL_TRUE = MalTrue() + +struct MalFalse { + int val +} + +MAL_FALSE = MalFalse() + +struct MalNumber { + int val +} + +func new_number(s) +{ + return MalNumber(val=atoi(s)) +} + +struct MalSymbol { + string val + pointer meta +} + +struct MalString { + string val + pointer meta +} + +struct MalKeyword { + string val + pointer meta +} + +struct MalList { + pointer val + pointer meta +} + +struct MalVector { + pointer val + pointer meta +} + +func count(obj) { return numberof(*obj.val); } + +func rest(obj) { + seq = count(obj) <= 1 ? [] : ((*obj.val)(2:)) + return MalList(val=&seq) +} + +struct MalHashmap { + pointer val + pointer meta +} + +func hashmap_obj_to_key(obj) { + if (structof(obj) == MalString) return "str:" + obj.val + else if (structof(obj) == MalSymbol) return "sym:" + obj.val + else if (structof(obj) == MalKeyword) return "key:" + obj.val + else error, "Unsupported obj type for hash key" +} + +func hashmap_key_to_obj(key) { + type_str = strpart(key, 1:4) + val = strpart(key, 5:) + if (type_str == "str:") return MalString(val=val) + else if (type_str == "sym:") return MalSymbol(val=val) + else if (type_str == "key:") return MalKeyword(val=val) + else error, "Unsupported key type" +} + +func array_to_hashmap(seq) +{ + if (numberof(seq) % 2 != 0) return MalError(message="Odd number of elements in hashmap") + h = hash_new() + for (i = 1; i <= numberof(seq); i += 2) { + hash_set, h, hashmap_obj_to_key(*seq(i)), *seq(i + 1) + } + return MalHashmap(val=&h) +} + +struct MalNativeFunction { + string val + pointer meta +} + +struct MalFunction { + pointer env + pointer binds + pointer ast + int macro + pointer meta +} + +struct MalAtom { + pointer val + pointer meta +} + +func is_macro(obj) { return (structof(obj) == MalFunction && obj.macro); } + +struct MalAtomVal { + pointer val +} + +func new_boolean(b) { + if (b) return MAL_TRUE + return MAL_FALSE +} + +func equal_seq(seq_a, seq_b) { + if (numberof(seq_a) != numberof(seq_b)) return 0 + for (i = 1; i <= numberof(seq_a); ++i) { + if (!equal(*seq_a(i), *seq_b(i))) return 0 + } + return 1 +} + +func equal_hash(hm_a, hm_b) { + if (numberof(*hm_a.keys) != numberof(*hm_b.keys)) return 0 + for (i = 1; i <= numberof(*hm_a.keys); ++i) { + key_a = (*hm_a.keys)(i) + val_a = *((*hm_a.vals)(i)) + val_b = hash_get(hm_b, key_a) + if (is_void(val_b) || !equal(val_a, val_b)) return 0 + } + return 1 +} + +func equal(a, b) { + ta = structof(a) + tb = structof(b) + if (ta == MalNil) return tb == MalNil + else if (ta == MalTrue) return tb == MalTrue + else if (ta == MalFalse) return tb == MalFalse + else if (ta == MalNumber) return tb == MalNumber && a.val == b.val + else if (ta == MalSymbol) return tb == MalSymbol && a.val == b.val + else if (ta == MalString) return tb == MalString && a.val == b.val + else if (ta == MalKeyword) return tb == MalKeyword && a.val == b.val + else if (ta == MalList || ta == MalVector) { + return (tb == MalList || tb == MalVector) && equal_seq(*(a.val), *(b.val)) + } + else if (ta == MalHashmap) return tb == MalHashmap && equal_hash(*a.val, *b.val) + else return 0 +} + +func streplaceall(s, pattern, subst) +{ + return streplace(s, strfind(pattern, s, n=999), subst) +} From a3665311219cc95d2f3ecd55faf86eb2861cd6b5 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Fri, 22 Sep 2017 21:14:35 +0000 Subject: [PATCH 0489/2308] README: Add Yorick (the 71st implementation) --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ed48a798a7..8e9c9433da 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 70 languages: +Mal is implemented in 71 languages: * Ada * GNU awk @@ -78,6 +78,7 @@ Mal is implemented in 70 languages: * VHDL * Vimscript * Visual Basic.NET +* Yorick Mal is a learning tool. See the [make-a-lisp process @@ -1006,6 +1007,17 @@ make mono ./stepX_YYY.exe ``` +### Yorick + +*The Yorick implementation was created by [Dov Murik](https://github.com/dubek)* + +The Yorick implementation of mal was tested on Yorick 2.2.04. + +``` +cd yorick +yorick -batch ./stepX_YYY.i +``` + ## Running tests From d64619ac508fd98ee2d0c8337c5d09e278a4e947 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Fri, 22 Sep 2017 21:15:43 +0000 Subject: [PATCH 0490/2308] travis: Add yorick to build matrix --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index b021b3158d..2f641cb216 100644 --- a/.travis.yml +++ b/.travis.yml @@ -89,6 +89,7 @@ matrix: - {env: IMPL=vb, services: [docker]} - {env: IMPL=vhdl, services: [docker]} - {env: IMPL=vimscript, services: [docker]} + - {env: IMPL=yorick, services: [docker]} script: # Build From c81a869e46aba5e8fb9fb700be5501a57e901492 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Sun, 24 Sep 2017 08:45:59 +0000 Subject: [PATCH 0491/2308] yorick: Add `yorick-eval` interop support --- yorick/core.i | 19 +++++++++++++++++++ yorick/tests/stepA_mal.mal | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 yorick/tests/stepA_mal.mal diff --git a/yorick/core.i b/yorick/core.i index 167f388c81..c4d6ed447a 100644 --- a/yorick/core.i +++ b/yorick/core.i @@ -267,6 +267,24 @@ func mal_swap_bang(a) func mal_eval(a) { return EVAL(*a(1), repl_env); } +func yorick_to_mal(e) +{ + if (is_void(e)) return MAL_NIL + if (is_scalar(e)) { + if (is_numerical(e)) return MalNumber(val=e) + else if (is_string(e)) return MalString(val=e) + else return MalString(val=totxt(e)) + } else { + seq = array(pointer, numberof(e)) + for (i = 1; i <= numberof(e); ++i) { + seq(i) = &yorick_to_mal(e(i)) + } + return MalList(val=&seq) + } +} + +func mal_yorick_eval(a) { return yorick_to_mal(exec(a(1)->val)); } + core_ns = h_new() h_set, core_ns, "=", mal_equal @@ -335,6 +353,7 @@ h_set, core_ns, "reset!", mal_reset_bang h_set, core_ns, "swap!", mal_swap_bang h_set, core_ns, "eval", mal_eval +h_set, core_ns, "yorick-eval", mal_yorick_eval func call_core_fn(name, args_list) { diff --git a/yorick/tests/stepA_mal.mal b/yorick/tests/stepA_mal.mal new file mode 100644 index 0000000000..8c2229a12d --- /dev/null +++ b/yorick/tests/stepA_mal.mal @@ -0,0 +1,33 @@ +;; Testing basic Yorick interop + +(yorick-eval "7") +;=>7 + +(yorick-eval "\"7\" + \"89\"") +;=>"789" + +(yorick-eval "123 == 123") +;=>1 + +(yorick-eval "123 == 456") +;=>0 + +(yorick-eval "[7, 8, 9]") +;=>(7 8 9) + +(yorick-eval "write, format=\"%s-%d\\x0A\", \"hello\", 1234; return nil;") +; hello-1234 +;=>nil + +(yorick-eval "extern my_global_var; my_global_var = 8; return nil;") +(yorick-eval "my_global_var") +;=>8 + +(yorick-eval "a = [7, 8, 9]; return a + 10;") +;=>(17 18 19) + +(yorick-eval "[\"ab\", \"cd\", \"ef\"] + [\"X\", \"Y\", \"Z\"]") +;=>("abX" "cdY" "efZ") + +(yorick-eval "strpart(\"ABCDEFGHIJ\", 4:7)") +;=>"DEFG" From 33f404afd46184ca1163a639530a59608aa48d40 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Mon, 25 Sep 2017 19:53:49 +0000 Subject: [PATCH 0492/2308] yorick: Fix unescaping of "\\n" --- yorick/reader.i | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/yorick/reader.i b/yorick/reader.i index 3b7e90f09b..b6bb15e92f 100644 --- a/yorick/reader.i +++ b/yorick/reader.i @@ -48,10 +48,11 @@ NUMBER_REGEXP = regcomp("^-?[0-9]+$") func unescape(s) { - s1 = strpart(s, 2:-1) // remove surrounding quotes - s2 = streplaceall(s1, "\\n", "\n") - s3 = streplaceall(s2, "\\\"", "\"") - return streplaceall(s3, "\\\\", "\\") + s = strpart(s, 2:-1) // remove surrounding quotes + s = streplaceall(s, "\\\\", "\x01") + s = streplaceall(s, "\\n", "\n") + s = streplaceall(s, "\\\"", "\"") + return streplaceall(s, "\x01", "\\") } func read_atom(rdr) From a9f64691635a7a6ee0952c1aceff3c42d85a5fe0 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 25 Sep 2017 15:25:47 -0500 Subject: [PATCH 0493/2308] Makefile: alphabetize implementations. --- Makefile | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index dbd80c7216..4a7b236a6e 100644 --- a/Makefile +++ b/Makefile @@ -78,12 +78,12 @@ DOCKERIZE = # Implementation specific settings # -IMPLS = ada awk bash basic c d chuck clojure coffee common-lisp cpp crystal cs dart \ - erlang elisp elixir es6 factor forth fsharp go groovy gst guile haskell \ - haxe hy io java julia js kotlin logo lua make mal ocaml matlab miniMAL \ - nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \ +IMPLS = ada awk bash basic c chuck clojure coffee common-lisp cpp crystal cs d dart \ + elisp elixir elm erlang es6 factor forth fsharp go groovy gst guile haskell \ + haxe hy io java js julia kotlin livescript logo lua make mal matlab miniMAL \ + nim objc objpascal ocaml perl perl6 php pil plpgsql plsql powershell ps \ python r racket rexx rpython ruby rust scala scheme skew swift swift3 tcl \ - ts vb vhdl vimscript yorick livescript elm + ts vb vhdl vimscript yorick EXTENSION = .mal @@ -136,9 +136,9 @@ dist_EXCLUDES += guile io julia matlab swift logo_TEST_OPTS = --start-timeout 60 --test-timeout 120 mal_TEST_OPTS = --start-timeout 60 --test-timeout 120 miniMAL_TEST_OPTS = --start-timeout 60 --test-timeout 120 +perl6_TEST_OPTS = --test-timeout=60 plpgsql_TEST_OPTS = --start-timeout 60 --test-timeout 180 plsql_TEST_OPTS = --start-timeout 120 --test-timeout 120 -perl6_TEST_OPTS = --test-timeout=60 vimscript_TEST_OPTS = --test-timeout 30 ifeq ($(MAL_IMPL),vimscript) mal_TEST_OPTS = --start-timeout 60 --test-timeout 180 @@ -154,14 +154,14 @@ endif basic_STEP_TO_PROG_cbm = basic/$($(1)).bas basic_STEP_TO_PROG_qbasic = basic/$($(1)) +clojure_STEP_TO_PROG_clj = clojure/target/$($(1)).jar +clojure_STEP_TO_PROG_cljs = clojure/src/mal/$($(1)).cljc + haxe_STEP_TO_PROG_neko = haxe/$($(1)).n haxe_STEP_TO_PROG_python = haxe/$($(1)).py haxe_STEP_TO_PROG_cpp = haxe/cpp/$($(1)) haxe_STEP_TO_PROG_js = haxe/$($(1)).js -clojure_STEP_TO_PROG_clj = clojure/target/$($(1)).jar -clojure_STEP_TO_PROG_cljs = clojure/src/mal/$($(1)).cljc - scheme_STEP_TO_PROG_chibi = scheme/$($(1)).scm scheme_STEP_TO_PROG_kawa = scheme/out/$($(1)).class scheme_STEP_TO_PROG_gauche = scheme/$($(1)).scm @@ -174,10 +174,8 @@ scheme_STEP_TO_PROG_foment = scheme/$($(1)).scm ada_STEP_TO_PROG = ada/$($(1)) awk_STEP_TO_PROG = awk/$($(1)).awk bash_STEP_TO_PROG = bash/$($(1)).sh -basic_STEP_TO_PROG = basic/$($(1)).bas basic_STEP_TO_PROG = $(basic_STEP_TO_PROG_$(basic_MODE)) c_STEP_TO_PROG = c/$($(1)) -d_STEP_TO_PROG = d/$($(1)) chuck_STEP_TO_PROG = chuck/$($(1)).ck clojure_STEP_TO_PROG = $(clojure_STEP_TO_PROG_$(clojure_MODE)) coffee_STEP_TO_PROG = coffee/$($(1)).coffee @@ -185,9 +183,11 @@ common-lisp_STEP_TO_PROG = common-lisp/$($(1)) cpp_STEP_TO_PROG = cpp/$($(1)) crystal_STEP_TO_PROG = crystal/$($(1)) cs_STEP_TO_PROG = cs/$($(1)).exe +d_STEP_TO_PROG = d/$($(1)) dart_STEP_TO_PROG = dart/$($(1)).dart elisp_STEP_TO_PROG = elisp/$($(1)).el elixir_STEP_TO_PROG = elixir/lib/mix/tasks/$($(1)).ex +elm_STEP_TO_PROG = elm/$($(1)).js erlang_STEP_TO_PROG = erlang/$($(1)) es6_STEP_TO_PROG = es6/build/$($(1)).js factor_STEP_TO_PROG = factor/$($(1))/$($(1)).factor @@ -196,23 +196,26 @@ fsharp_STEP_TO_PROG = fsharp/$($(1)).exe go_STEP_TO_PROG = go/$($(1)) groovy_STEP_TO_PROG = groovy/$($(1)).groovy gst_STEP_TO_PROG = gst/$($(1)).st -java_STEP_TO_PROG = java/target/classes/mal/$($(1)).class +guile_STEP_TO_PROG = guile/$($(1)).scm haskell_STEP_TO_PROG = haskell/$($(1)) haxe_STEP_TO_PROG = $(haxe_STEP_TO_PROG_$(haxe_MODE)) hy_STEP_TO_PROG = hy/$($(1)).hy io_STEP_TO_PROG = io/$($(1)).io -julia_STEP_TO_PROG = julia/$($(1)).jl +java_STEP_TO_PROG = java/target/classes/mal/$($(1)).class js_STEP_TO_PROG = js/$($(1)).js +julia_STEP_TO_PROG = julia/$($(1)).jl kotlin_STEP_TO_PROG = kotlin/$($(1)).jar +livescript_STEP_TO_PROG = livescript/$($(1)).js +logo_STEP_TO_PROG = logo/$($(1)).lg lua_STEP_TO_PROG = lua/$($(1)).lua make_STEP_TO_PROG = make/$($(1)).mk mal_STEP_TO_PROG = mal/$($(1)).mal -ocaml_STEP_TO_PROG = ocaml/$($(1)) matlab_STEP_TO_PROG = matlab/$($(1)).m miniMAL_STEP_TO_PROG = miniMAL/$($(1)).json nim_STEP_TO_PROG = nim/$($(1)) objc_STEP_TO_PROG = objc/$($(1)) objpascal_STEP_TO_PROG = objpascal/$($(1)) +ocaml_STEP_TO_PROG = ocaml/$($(1)) perl_STEP_TO_PROG = perl/$($(1)).pl perl6_STEP_TO_PROG = perl6/$($(1)).pl php_STEP_TO_PROG = php/$($(1)).php @@ -239,9 +242,6 @@ vb_STEP_TO_PROG = vb/$($(1)).exe vhdl_STEP_TO_PROG = vhdl/$($(1)) vimscript_STEP_TO_PROG = vimscript/$($(1)).vim yorick_STEP_TO_PROG = yorick/$($(1)).i -guile_STEP_TO_PROG = guile/$($(1)).scm -livescript_STEP_TO_PROG = livescript/$($(1)).js -elm_STEP_TO_PROG = elm/$($(1)).js # From 8e59b7151b176b829d36fed944a693084cf9b60f Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Mon, 25 Sep 2017 20:24:48 +0000 Subject: [PATCH 0494/2308] tests: Add test for unescaping "\\n" in step4 Several implementations fail when unescaping "\\n" in their reader module because string-wide replacements of the two chars '\' and 'n' by a newline char will replace the last two chars. --- tests/step4_if_fn_do.mal | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/step4_if_fn_do.mal b/tests/step4_if_fn_do.mal index 0b69031ac1..6e503f9748 100644 --- a/tests/step4_if_fn_do.mal +++ b/tests/step4_if_fn_do.mal @@ -273,6 +273,9 @@ a "abc\\def\\ghi" ;=>"abc\\def\\ghi" +"\\n" +;=>"\\n" + ;; Testing pr-str (pr-str) From e73fcefe8fa80f8674710a0d42186a52381fb1df Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Wed, 27 Sep 2017 07:02:07 +0000 Subject: [PATCH 0495/2308] Fix unescaping in rexx, skew, vimscript --- rexx/reader.rexx | 3 ++- skew/reader.sk | 2 +- vimscript/reader.vim | 18 +++++++++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/rexx/reader.rexx b/rexx/reader.rexx index 3afc8620d0..3b992f9679 100644 --- a/rexx/reader.rexx +++ b/rexx/reader.rexx @@ -95,9 +95,10 @@ is_number: procedure /* is_number(token) */ parse_string: procedure /* parse_string(token) */ token = arg(1) res = substr(token, 2, length(token) - 2) /* Remove quotes */ + res = changestr("\\", res, '01'x) res = changestr("\n", res, '0A'x) res = changestr('\"', res, '"') - res = changestr("\\", res, '5C'x) + res = changestr('01'x, res, '5C'x) return res parse_keyword: procedure /* parse_keyword(token) */ diff --git a/skew/reader.sk b/skew/reader.sk index 457e88865f..0aa723ba27 100644 --- a/skew/reader.sk +++ b/skew/reader.sk @@ -30,7 +30,7 @@ def tokenize(str string) List { } def unescape(s string) string { - return s.replaceAll("\\\"", "\"").replaceAll("\\n", "\n").replaceAll("\\\\", "\\") + return s.replaceAll("\\\\", "\x01").replaceAll("\\\"", "\"").replaceAll("\\n", "\n").replaceAll("\x01", "\\") } def read_atom(rdr Reader) MalVal { diff --git a/vimscript/reader.vim b/vimscript/reader.vim index 38510cd45e..f22debe6db 100644 --- a/vimscript/reader.vim +++ b/vimscript/reader.vim @@ -42,12 +42,20 @@ function Tokenize(str) return tokens endfunction +function UnescapeChar(seq) + if a:seq == '\"' + return '"' + elseif a:seq == '\n' + return "\n" + elseif a:seq == '\\' + return '\' + else + return a:seq + endif +endfunction + function ParseString(token) - let str = a:token[1:-2] - let str = substitute(str, '\\"', '"', "g") - let str = substitute(str, '\\n', "\n", "g") - let str = substitute(str, '\\\\', "\\", "g") - return str + return substitute(a:token[1:-2], '\\.', '\=UnescapeChar(submatch(0))', "g") endfunction function ReadAtom(rdr) From 0794206dc5a61b141ffcb9b7096864f79efedca2 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Wed, 27 Sep 2017 07:19:29 +0000 Subject: [PATCH 0496/2308] Fix unescaping in go, perl --- go/src/reader/reader.go | 6 ++++-- perl/reader.pm | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go/src/reader/reader.go b/go/src/reader/reader.go index 6411d72ce2..dc0f54f46f 100644 --- a/go/src/reader/reader.go +++ b/go/src/reader/reader.go @@ -69,9 +69,11 @@ func read_atom(rdr Reader) (MalType, error) { str := (*token)[1 : len(*token)-1] return strings.Replace( strings.Replace( - strings.Replace(str, `\"`, `"`, -1), + strings.Replace( + strings.Replace(str, `\\`, "\u029e", -1), + `\"`, `"`, -1), `\n`, "\n", -1), - `\\`, "\\", -1), nil + "\u029e", "\\", -1), nil } else if (*token)[0] == ':' { return NewKeyword((*token)[1:len(*token)]) } else if *token == "nil" { diff --git a/perl/reader.pm b/perl/reader.pm index 9527231df3..a4196badb0 100644 --- a/perl/reader.pm +++ b/perl/reader.pm @@ -32,10 +32,9 @@ sub read_atom { given ($token) { when(/^-?[0-9]+$/) { return Integer->new($token) } when(/^"/) { + my %escaped_chars = ( "\\\\" => "\\", "\\\"" => "\"", "\\n" => "\n" ); my $str = substr $token, 1, -1; - $str =~ s/\\"/"/g; - $str =~ s/\\n/\n/g; - $str =~ s/\\\\/\\/g; + $str =~ s/\\./$escaped_chars{$&}/ge; return String->new($str) } when(/^:/) { return _keyword(substr($token,1)) } From 1b17329dd5b3f2e78904d1db77bffb9f289522b1 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 27 Sep 2017 12:13:15 +0530 Subject: [PATCH 0497/2308] Fix unescaping strings for racket, elixir and io --- elixir/lib/mal/reader.ex | 6 ++---- io/MalReader.io | 3 ++- racket/reader.rkt | 8 +------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/elixir/lib/mal/reader.ex b/elixir/lib/mal/reader.ex index 59365c7f78..82ecc8d7d6 100644 --- a/elixir/lib/mal/reader.ex +++ b/elixir/lib/mal/reader.ex @@ -85,10 +85,8 @@ defmodule Mal.Reader do cond do String.starts_with?(token, "\"") and String.ends_with?(token, "\"") -> token - |> String.slice(1..-2) - |> String.replace("\\\"", "\"") - |> String.replace("\\n", "\n") - |> String.replace("\\\\", "\\") + |> Code.string_to_quoted + |> elem(1) integer?(token) -> Integer.parse(token) diff --git a/io/MalReader.io b/io/MalReader.io index 6192cb52fc..b753913706 100644 --- a/io/MalReader.io +++ b/io/MalReader.io @@ -28,7 +28,8 @@ MalReader := Object clone do ( numberRegex := Regex with("^-?[0-9]+$") read_string := method(token, - token exSlice(1, -1) replaceSeq("\\\"", "\"") replaceSeq("\\n", "\n") replaceSeq("\\\\", "\\") + placeholder := 127 asCharacter + token exSlice(1, -1) replaceSeq("\\\\", placeholder) replaceSeq("\\\"", "\"") replaceSeq("\\n", "\n") replaceSeq(placeholder, "\\") ) read_atom := method(rdr, diff --git a/racket/reader.rkt b/racket/reader.rkt index 280b9af2a0..6ff34cfb99 100644 --- a/racket/reader.rkt +++ b/racket/reader.rkt @@ -32,13 +32,7 @@ [(regexp-match #px"^-?[0-9][0-9.]*$" token) (string->number token)] [(regexp-match #px"^\".*\"$" token) - (string-replace - (string-replace - (string-replace - (substring token 1 (- (string-length token) 1)) - "\\\"" "\"") - "\\n" "\n") - "\\\\" "\\")] + (with-input-from-string token read)] [(regexp-match #px"^:" token) (_keyword (substring token 1))] [(equal? "nil" token) nil] [(equal? "true" token) #t] From 9ce82e328979f8c14a45aa88a299cf7b760fb2f8 Mon Sep 17 00:00:00 2001 From: Iqbal Ansari Date: Wed, 27 Sep 2017 19:06:43 +0530 Subject: [PATCH 0498/2308] Fix unescaping of strings for factor --- factor/lib/reader/reader.factor | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/factor/lib/reader/reader.factor b/factor/lib/reader/reader.factor index 587ed90ab7..767241ae26 100644 --- a/factor/lib/reader/reader.factor +++ b/factor/lib/reader/reader.factor @@ -1,18 +1,26 @@ ! Copyright (C) 2015 Jordan Lewis. ! See http://factorcode.org/license.txt for BSD license. -USING: arrays combinators grouping hashtables kernel lists -locals make lib.types math.parser regexp sequences splitting ; +USING: arrays combinators grouping hashtables kernel lists locals +make lib.types math.parser regexp sequences splitting strings ; IN: lib.reader CONSTANT: token-regex R/ (~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)~^@]+)/ DEFER: read-form +: (read-string) ( str -- maltype ) + rest but-last R/ \\./ [ + { + { [ dup >string "\\\\" = ] [ drop "\\" ] } + { [ dup >string "\\n" = ] [ drop "\n" ] } + { [ dup >string "\\\"" = ] [ drop "\"" ] } + [ ] + } cond + ] re-replace-with ; + : (read-atom) ( str -- maltype ) { - { [ dup first CHAR: " = ] [ rest but-last "\\\"" "\"" replace - "\\n" "\n" replace - "\\\\" "\\" replace ] } + { [ dup first CHAR: " = ] [ (read-string) ] } { [ dup first CHAR: : = ] [ rest ] } { [ dup "false" = ] [ drop f ] } { [ dup "true" = ] [ drop t ] } From ea02f4644594bb66f7ed80844c8ca28eb748517e Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 26 Sep 2017 09:12:29 -0500 Subject: [PATCH 0499/2308] Fix unescaping in bash, basic, es6, js, ruby, rust. --- bash/reader.sh | 3 ++- basic/reader.in.bas | 3 ++- es6/reader.js | 4 +--- js/reader.js | 4 +--- ruby/reader.rb | 2 +- rust/src/printer.rs | 9 +++++---- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/bash/reader.sh b/bash/reader.sh index 688fc8b442..fa6064ce73 100644 --- a/bash/reader.sh +++ b/bash/reader.sh @@ -14,9 +14,10 @@ READ_ATOM () { [0-9]*) _number "${token}" ;; -[0-9]*) _number "${token}" ;; \"*) token="${token:1:-1}" + token="${token//\\\\/${__keyw}}" token="${token//\\\"/\"}" token="${token//\\n/$'\n'}" - token="${token//\\\\/\\}" + token="${token//${__keyw}/\\}" _string "${token}" ;; :*) _keyword "${token:1}" ;; nil) r="${__nil}" ;; diff --git a/basic/reader.in.bas b/basic/reader.in.bas index daafb43ca9..ee2e864e8d 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -168,10 +168,11 @@ SUB READ_FORM C=ASC(MID$(T$,LEN(T$),1)) IF C<>34 THEN R=-1:ER=-1:E$="expected '"+CHR$(34)+"'":GOTO READ_FORM_RETURN R$=MID$(T$,2,LEN(T$)-2) + S1$=CHR$(92)+CHR$(92):S2$=CHR$(127):GOSUB REPLACE: REM protect backslashes S1$=CHR$(92)+CHR$(34):S2$=CHR$(34):GOSUB REPLACE: REM unescape quotes #cbm S1$=CHR$(92)+"n":S2$=CHR$(13):GOSUB REPLACE: REM unescape newlines #qbasic S1$=CHR$(92)+"n":S2$=CHR$(10):GOSUB REPLACE: REM unescape newlines - S1$=CHR$(92)+CHR$(92):S2$=CHR$(92):GOSUB REPLACE: REM unescape backslashes + S1$=CHR$(127):S2$=CHR$(92):GOSUB REPLACE: REM unescape backslashes REM intern string value B$=R$:T=4:GOSUB STRING GOTO READ_FORM_RETURN diff --git a/es6/reader.js b/es6/reader.js index e7afe00318..fb2c2f2fac 100644 --- a/es6/reader.js +++ b/es6/reader.js @@ -31,9 +31,7 @@ function read_atom (reader) { return parseFloat(token,10) // float } else if (token[0] === "\"") { return token.slice(1,token.length-1) - .replace(/\\"/g, '"') - .replace(/\\n/g, "\n") - .replace(/\\\\/g, "\\") // string + .replace(/\\(.)/g, (_, c) => c === "n" ? "\n" : c) } else if (token[0] === ":") { return _keyword(token.slice(1)) } else if (token === "nil") { diff --git a/js/reader.js b/js/reader.js index d2bcd91bc4..b147c13b8a 100644 --- a/js/reader.js +++ b/js/reader.js @@ -33,9 +33,7 @@ function read_atom (reader) { return parseFloat(token,10); // float } else if (token[0] === "\"") { return token.slice(1,token.length-1) - .replace(/\\"/g, '"') - .replace(/\\n/g, "\n") - .replace(/\\\\/g, "\\"); // string + .replace(/\\(.)/g, function (_, c) { return c === "n" ? "\n" : c}) } else if (token[0] === ":") { return types._keyword(token.slice(1)); } else if (token === "nil") { diff --git a/ruby/reader.rb b/ruby/reader.rb index 1e601744e1..446f7ae5d7 100644 --- a/ruby/reader.rb +++ b/ruby/reader.rb @@ -23,7 +23,7 @@ def tokenize(str) end def parse_str(t) # trim and unescape - return t[1..-2].gsub(/\\"/, '"').gsub(/\\n/, "\n").gsub(/\\\\/, "\\") + return t[1..-2].gsub(/\\./, {"\\\\" => "\\", "\\n" => "\n", "\\\"" => '"'}) end def read_atom(rdr) diff --git a/rust/src/printer.rs b/rust/src/printer.rs index 4150d39d27..1bb11e3c27 100644 --- a/rust/src/printer.rs +++ b/rust/src/printer.rs @@ -1,4 +1,5 @@ use types::MalVal; +use regex::Captures; pub fn escape_str(s: &str) -> String { let mut escaped = String::new(); @@ -22,10 +23,10 @@ pub fn escape_str(s: &str) -> String { } pub fn unescape_str(s: &str) -> String { - let re1 = regex!(r#"\\""#); - let re2 = regex!(r#"\\n"#); - let re3 = regex!(r#"\\\\"#); - re3.replace_all(&re2.replace_all(&re1.replace_all(&s, "\""), "\n"), "\\") + let re = regex!(r#"\\(.)"#); + re.replace_all(&s, |caps: &Captures| { + format!("{}", if &caps[1] == "n" { "\n" } else { &caps[1] }) + }) } pub fn pr_list(lst: &Vec, pr: bool, From 273226aa7465a43ec11b0a2747721f8dfd8f2eac Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 26 Sep 2017 10:34:49 -0500 Subject: [PATCH 0500/2308] Fix unescaping in matlab, miniMAL and rpython. --- matlab/reader.m | 3 ++- miniMAL/reader.json | 12 ++++++------ rpython/reader.py | 3 ++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/matlab/reader.m b/matlab/reader.m index ada1af6f1b..8cea773ac4 100644 --- a/matlab/reader.m +++ b/matlab/reader.m @@ -16,9 +16,10 @@ atm = str2double(token); elseif strcmp(token(1), '"') atm = token(2:length(token)-1); + atm = strrep(atm, '\\', char(255)); atm = strrep(atm, '\"', '"'); atm = strrep(atm, '\n', char(10)); - atm = strrep(atm, '\\', '\'); + atm = strrep(atm, char(255), '\'); elseif strcmp(token(1), ':') s = token(2:end); atm = type_utils.keyword(s); diff --git a/miniMAL/reader.json b/miniMAL/reader.json index d9ceeddb1c..830158e96e 100644 --- a/miniMAL/reader.json +++ b/miniMAL/reader.json @@ -38,12 +38,12 @@ ["parseInt", "token", 10], ["if", ["=", ["`", "\""], ["get", "token", 0]], [".", - [".", - [".", - ["slice", "token", 1, ["-", ["count", "token"], 1]], - ["`", "replace"], ["RegExp", ["`", "\\\\\""], ["`", "g"]], ["`", "\""]], - ["`", "replace"], ["RegExp", ["`", "\\\\n"], ["`", "g"]], ["`", "\n"]], - ["`", "replace"], ["RegExp", ["`", "\\\\\\\\"], ["`", "g"]], ["`", "\\"]], + ["slice", "token", 1, ["-", ["count", "token"], 1]], + ["`", "replace"], ["RegExp", ["`", "\\\\(.)"], ["`", "g"]], + ["fn", ["_", "c"], + ["if", ["=", "c", ["`", "n"]], + ["`", "\n"], + "c"]]], ["if", ["=", ["`", ":"], ["get", "token", 0]], ["keyword", ["slice", "token", 1]], ["if", ["=", ["`", "nil"], "token"], diff --git a/rpython/reader.py b/rpython/reader.py index 1e5acf6cb4..5f8af46b22 100644 --- a/rpython/reader.py +++ b/rpython/reader.py @@ -51,9 +51,10 @@ def read_atom(reader): return MalStr(u"") else: s = unicode(token[1:end]) + s = types._replace(u'\\\\', u"\u029e", s) s = types._replace(u'\\"', u'"', s) s = types._replace(u'\\n', u"\n", s) - s = types._replace(u'\\\\', u"\\", s) + s = types._replace(u"\u029e", u"\\", s) return MalStr(s) elif token[0] == ':': return _keywordu(unicode(token[1:])) elif token == "nil": return types.nil From 42aecee642b36aeb0b0aec15cfbc6b0007ca7b24 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 26 Sep 2017 12:12:15 -0500 Subject: [PATCH 0501/2308] Fix unescaping in cs, hy, nim, objpascal, ps, rpython, vb --- cs/reader.cs | 7 ++++--- hy/reader.hy | 7 ++++--- nim/Dockerfile | 6 +++--- nim/reader.nim | 2 +- objpascal/reader.pas | 7 ++++--- ps/reader.ps | Bin 8867 -> 8895 bytes rpython/reader.py | 6 +++--- vb/reader.vb | 7 ++++--- 8 files changed, 23 insertions(+), 19 deletions(-) diff --git a/cs/reader.cs b/cs/reader.cs index a644309a8a..60798e37bf 100644 --- a/cs/reader.cs +++ b/cs/reader.cs @@ -71,9 +71,10 @@ public static MalVal read_atom(Reader rdr) { } else if (match.Groups[6].Value != String.Empty) { string str = match.Groups[6].Value; str = str.Substring(1, str.Length-2) - .Replace("\\\"", "\"") - .Replace("\\n", "\n") - .Replace("\\\\", "\\"); + .Replace("\\\\", "\u029e") + .Replace("\\\"", "\"") + .Replace("\\n", "\n") + .Replace("\u029e", "\\"); return new Mal.types.MalString(str); } else if (match.Groups[7].Value != String.Empty) { return new Mal.types.MalString("\u029e" + match.Groups[7].Value); diff --git a/hy/reader.hy b/hy/reader.hy index 41abec29d3..4c9bd1d525 100644 --- a/hy/reader.hy +++ b/hy/reader.hy @@ -25,9 +25,10 @@ (!= (get t 0) ";"))) (defn unescape [s] - (-> s (.replace "\\\"" "\"") - (.replace "\\n" "\n") - (.replace "\\\\" "\\"))) + (-> s (.replace "\\\\" "\u029e") + (.replace "\\\"" "\"") + (.replace "\\n" "\n") + (.replace "\u029e" "\\"))) (defn read-atom [rdr] (setv token (.next rdr)) diff --git a/nim/Dockerfile b/nim/Dockerfile index 9744753e93..1ffef105b5 100644 --- a/nim/Dockerfile +++ b/nim/Dockerfile @@ -26,10 +26,10 @@ RUN apt-get -y install g++ # Nim RUN apt-get -y install xz-utils -RUN cd /tmp && curl -O http://nim-lang.org/download/nim-0.17.0.tar.xz \ - && tar xvJf /tmp/nim-0.17.0.tar.xz && cd nim-0.17.0 \ +RUN cd /tmp && curl -O https://nim-lang.org/download/nim-0.17.2.tar.xz \ + && tar xvJf /tmp/nim-0.17.2.tar.xz && cd nim-0.17.2 \ && make && sh install.sh /usr/local/bin \ && cp bin/nim /usr/local/bin/ \ - && rm -r /tmp/nim-0.17.0 + && rm -r /tmp/nim-0.17.2 ENV HOME /mal diff --git a/nim/reader.nim b/nim/reader.nim index 4df8c9c5b6..6bc2997fee 100644 --- a/nim/reader.nim +++ b/nim/reader.nim @@ -61,7 +61,7 @@ proc read_hash_map(r: var Reader): MalType = proc read_atom(r: var Reader): MalType = let t = r.next if t.match(intRE): number t.parseInt - elif t[0] == '"': str t[1 .. '' then begin Str := copy(Token, 2, Length(Token)-2); - Str := StringReplace(Str, '\"', '"', [rfReplaceAll]); - Str := StringReplace(Str, '\n', #10, [rfReplaceAll]); - Str := StringReplace(Str, '\\', '\', [rfReplaceAll]); + Str := StringReplace(Str, '\\', #127, [rfReplaceAll]); + Str := StringReplace(Str, '\"', '"', [rfReplaceAll]); + Str := StringReplace(Str, '\n', #10, [rfReplaceAll]); + Str := StringReplace(Str, #127, '\', [rfReplaceAll]); read_atom := TMalString.Create(Str) end else if RE.Match[7] <> '' then diff --git a/ps/reader.ps b/ps/reader.ps index 3574242f0ecce270e028a6e3c1a03f298f05d28f..c5e19bf15848f0b6fcf9e542c0f241f52ccb5377 100644 GIT binary patch delta 88 zcmZ4Ny5DufL~iex7)=F@7(;V&O@*S=f}F(UR4xS|(1?jq(gadK0!=s%Bpd@IQN@8K KY(BvKSPTGL<`}I2 delta 42 vcmdn*y4ZEYL~bS}&B^Py#l String.Empty Then return New Mal.types.MalString(ChrW(&H029e) & match.Groups(7).Value) Else If match.Groups(8).Value <> String.Empty Then From e91c55c2b65cf7628760433f1fcce84f40e645df Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 26 Sep 2017 22:07:49 +0200 Subject: [PATCH 0502/2308] Fix unescaping in chuck, common-lisp, gst, guile, python --- chuck/types/boxed/String.ck | 3 ++- common-lisp/src/reader.lisp | 16 ++++++++++++---- gst/util.st | 6 ++++-- guile/reader.scm | 15 ++++----------- python/reader.py | 4 ++-- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/chuck/types/boxed/String.ck b/chuck/types/boxed/String.ck index c0ec6f71e2..4c7390a8b3 100644 --- a/chuck/types/boxed/String.ck +++ b/chuck/types/boxed/String.ck @@ -112,9 +112,10 @@ public class String fun static string parse(string input) { slice(input, 1, input.length() - 1) => string output; + replaceAll(output, "\\\\", "\177") => output; replaceAll(output, "\\\"", "\"") => output; replaceAll(output, "\\n", "\n") => output; - replaceAll(output, "\\\\", "\\") => output; + replaceAll(output, "\177", "\\") => output; return output; } diff --git a/common-lisp/src/reader.lisp b/common-lisp/src/reader.lisp index 5391b4dd89..cfcb5ae8dd 100644 --- a/common-lisp/src/reader.lisp +++ b/common-lisp/src/reader.lisp @@ -90,12 +90,20 @@ raised" reader) (defun parse-string (token) + ;; read-from-string doesn't handle \n (if (and (> (length token) 1) (scan *string-re* token)) - (read-from-string (utils:replace-all token - "\\n" - " -")) + (let ((input (subseq token 1 (1- (length token))))) + (with-output-to-string (out) + (with-input-from-string (in input) + (loop while (peek-char nil in nil) + do (let ((char (read-char in))) + (if (eql char #\\ ) + (let ((char (read-char in))) + (if (eql char #\n) + (terpri out) + (princ char out))) + (princ char out))))))) (error 'eof :context "string"))) (defun expand-quote (reader) diff --git a/gst/util.st b/gst/util.st index 5a73576e27..4a0009e6b0 100644 --- a/gst/util.st +++ b/gst/util.st @@ -10,12 +10,14 @@ SequenceableCollection extend [ String extend [ parse [ - |text| + |text canary| + canary := 8r177 asCharacter asString. text := self copyFrom: 2 to: self size - 1. + text := text copyReplaceAll: '\\' with: canary. text := text copyReplaceAll: '\"' with: '"'. text := text copyReplaceAll: '\n' with: ' '. - text := text copyReplaceAll: '\\' with: '\'. + text := text copyReplaceAll: canary with: '\'. ^text ] diff --git a/guile/reader.scm b/guile/reader.scm index c734759e03..38cebbe066 100644 --- a/guile/reader.scm +++ b/guile/reader.scm @@ -78,21 +78,14 @@ (lp (cddr next))))))))) (define (read_atom reader) - (define (->str s) - (string-sub - (string-sub - (string-sub s "\\\\\"" "\"") - "\\\\n" "\n") - "\\\\\\\\" "\\")) (let ((token (reader 'next))) (cond ((string-match "^-?[0-9][0-9.]*$" token) => (lambda (m) (string->number (match:substring m 0)))) - ((string-match "^\"(.*)(.)$" token) - => (lambda (m) - (if (string=? "\"" (match:substring m 2)) - (->str (match:substring m 1)) - (throw 'mal-error "expected '\"'")))) + ((eqv? (string-ref token 0) #\") + (if (eqv? (string-ref token (- (string-length token) 1)) #\") + (with-input-from-string token read) + (throw 'mal-error "expected '\"'"))) ((string-match "^:(.*)" token) => (lambda (m) (string->keyword (match:substring m 1)))) ((string=? "nil" token) nil) diff --git a/python/reader.py b/python/reader.py index 44c9d741cc..84c46c5d92 100644 --- a/python/reader.py +++ b/python/reader.py @@ -1,5 +1,5 @@ import re -from mal_types import (_symbol, _keyword, _list, _vector, _hash_map, _s2u) +from mal_types import (_symbol, _keyword, _list, _vector, _hash_map, _s2u, _u) class Blank(Exception): pass @@ -23,7 +23,7 @@ def tokenize(str): return [t for t in re.findall(tre, str) if t[0] != ';'] def _unescape(s): - return s.replace('\\"', '"').replace('\\n', '\n').replace('\\\\', '\\') + return s.replace('\\\\', _u('\u029e')).replace('\\"', '"').replace('\\n', '\n').replace(_u('\u029e'), '\\') def read_atom(reader): int_re = re.compile(r"-?[0-9]+$") From a821cd720423678f14baea7c9a59ebc08aebfbb4 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 26 Sep 2017 14:04:01 -0500 Subject: [PATCH 0503/2308] Fix unescaping in c, coffee, crystal, haxe, plpgsql and r. --- c/reader.c | 15 +-------------- coffee/reader.coffee | 4 +--- crystal/Makefile | 14 +++++++++++++- crystal/reader.cr | 6 +++--- haxe/reader/Reader.hx | 19 ++++++++++++------- plpgsql/reader.sql | 3 ++- r/reader.r | 5 +++-- 7 files changed, 35 insertions(+), 31 deletions(-) diff --git a/c/reader.c b/c/reader.c index c50e5e97a4..2528b3a1d0 100644 --- a/c/reader.c +++ b/c/reader.c @@ -78,14 +78,6 @@ Reader *tokenize(char *line) { } -char *replace_str(const char *str, const char *old, const char *new) -{ - GRegex *reg = g_regex_new (old, 0, 0, NULL); - char *str_tmp = g_regex_replace_literal(reg, str, -1, 0, new, 0, NULL); - MAL_GC_FREE(reg); - return str_tmp; -} - MalVal *read_atom(Reader *reader) { char *token; GRegex *regex; @@ -117,12 +109,7 @@ MalVal *read_atom(Reader *reader) { atom = &mal_false; } else if (g_match_info_fetch_pos(matchInfo, 6, &pos, NULL) && pos != -1) { //g_print("read_atom string: %s\n", token); - char *str_tmp = replace_str(g_match_info_fetch(matchInfo, 6), "\\\\\"", "\""); - char *str_tmp2 = replace_str(str_tmp, "\\\\n", "\n"); - MAL_GC_FREE(str_tmp); - char *str_tmp3 = replace_str(str_tmp2, "\\\\\\\\", "\\"); - MAL_GC_FREE(str_tmp2); - atom = malval_new_string(str_tmp3); + atom = malval_new_string(g_strcompress(g_match_info_fetch(matchInfo, 6))); } else if (g_match_info_fetch_pos(matchInfo, 7, &pos, NULL) && pos != -1) { //g_print("read_atom keyword\n"); atom = malval_new_keyword(MAL_GC_STRDUP(g_match_info_fetch(matchInfo, 7))); diff --git a/coffee/reader.coffee b/coffee/reader.coffee index 1c9ab3e6d0..c6fb7d9179 100644 --- a/coffee/reader.coffee +++ b/coffee/reader.coffee @@ -24,9 +24,7 @@ read_atom = (rdr) -> else if token.match /^-?[0-9][0-9.]*$/ then parseFloat token,10 else if token[0] == '"' token.slice(1, token.length-1) - .replace(/\\"/g, '"') - .replace(/\\n/g, "\n") - .replace(/\\\\/g, "\\") + .replace(/\\(.)/g, (_, c) -> if c == 'n' then '\n' else c) else if token[0] == ':' then types._keyword(token[1..]) else if token == "nil" then null else if token == "true" then true diff --git a/crystal/Makefile b/crystal/Makefile index e33eadbef6..a26b02d13e 100644 --- a/crystal/Makefile +++ b/crystal/Makefile @@ -2,6 +2,12 @@ STEPS = step0_repl.cr step1_read_print.cr step2_eval.cr step3_env.cr \ step4_if_fn_do.cr step5_tco.cr step6_file.cr step7_quote.cr \ step8_macros.cr step9_try.cr stepA_mal.cr +STEP0_DEPS = readline.cr +STEP1_DEPS = $(STEP0_DEPS) reader.cr printer.cr +STEP2_DEPS = $(STEP1_DEPS) types.cr +STEP3_DEPS = $(STEP2_DEPS) env.cr +STEP4_DEPS = $(STEP3_DEPS) core.cr error.cr + STEP_BINS = $(STEPS:%.cr=%) LAST_STEP_BIN = $(word $(words $(STEP_BINS)),$(STEP_BINS)) @@ -12,9 +18,15 @@ dist: mal mal: $(LAST_STEP_BIN) cp $< $@ -$(STEP_BINS): %: %.cr $(MAL_LIB) +$(STEP_BINS): %: %.cr crystal compile --release $< +step0_repl: $(STEP0_DEPS) +step1_read_print: $(STEP1_DEPS) +step2_eval: $(STEP2_DEPS) +step3_env: $(STEP3_DEPS) +step4_if_fn_do step5_tco step6_file step7_quote step8_macros step9_try stepA_mal: $(STEP4_DEPS) + clean: rm -rf $(STEP_BINS) mal .crystal diff --git a/crystal/reader.cr b/crystal/reader.cr index b02c47a39b..34b469220c 100644 --- a/crystal/reader.cr +++ b/crystal/reader.cr @@ -81,9 +81,9 @@ class Reader when token == "true" then true when token == "false" then false when token == "nil" then nil - when token[0] == '"' then token[1..-2].gsub(/\\"/, "\"") - .gsub(/\\n/, "\n") - .gsub(/\\\\/, "\\") + when token[0] == '"' then token[1..-2].gsub(/\\(.)/, {"\\\"" => "\"", + "\\n" => "\n", + "\\\\" => "\\"}) when token[0] == ':' then "\u029e#{token[1..-1]}" else Mal::Symbol.new token end diff --git a/haxe/reader/Reader.hx b/haxe/reader/Reader.hx index e6d601d49f..3955a3d4d5 100644 --- a/haxe/reader/Reader.hx +++ b/haxe/reader/Reader.hx @@ -58,15 +58,20 @@ class Reader { case _ if (re_int.match(token)): MalInt(Std.parseInt(token)); case _ if (re_str.match(token)): - var re1 = ~/\\"/g, + var re1 = ~/\\\\/g, re2 = ~/\\n/g, - re3 = ~/\\\\/g, + re3 = ~/\\"/g, + re4 = ~/\x7f/g, s = token.substr(1, token.length-2); - MalString(re3.replace( - re2.replace( - re1.replace(s, "\""), - "\n"), - "\\")); + MalString(re4.replace( + re3.replace( + re2.replace( + re1.replace( + s, + "\x7f"), + "\n"), + "\""), + "\\")); case _: MalSymbol(token); } diff --git a/plpgsql/reader.sql b/plpgsql/reader.sql index f0b96bb2a1..2368df23e3 100644 --- a/plpgsql/reader.sql +++ b/plpgsql/reader.sql @@ -39,9 +39,10 @@ BEGIN ELSIF token ~ '^".*"' THEN -- string -- string str := substring(token FROM 2 FOR (char_length(token)-2)); + str := replace(str, '\\', chr(CAST(x'7f' AS integer))); str := replace(str, '\"', '"'); str := replace(str, '\n', E'\n'); - str := replace(str, '\\', E'\\'); + str := replace(str, chr(CAST(x'7f' AS integer)), E'\\'); result := types._stringv(str); ELSIF token ~ '^:.*' THEN -- keyword -- keyword diff --git a/r/reader.r b/r/reader.r index 030581093c..b2729f840e 100644 --- a/r/reader.r +++ b/r/reader.r @@ -43,10 +43,11 @@ read_atom <- function(rdr) { } else if (re_match("^-?[0-9][0-9.]*$", token)) { as.double(token) } else if (substr(token,1,1) == "\"") { - gsub("\\\\\\\\", "\\\\", + gsub("\x7f", "\\\\", gsub("\\\\n", "\n", gsub("\\\\\"", "\"", - substr(token, 2, nchar(token)-1)))) + gsub("\\\\\\\\", "\x7f", + substr(token, 2, nchar(token)-1))))) } else if (substr(token,1,1) == ":") { new.keyword(substring(token,2)) } else if (token == "nil") { From da9aef124aa86998f19afe6c729d1d3105f0506a Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 26 Sep 2017 15:31:50 -0500 Subject: [PATCH 0504/2308] Fix unescaping in lua, objc, php, powershell, swift3 and ts. --- lua/reader.lua | 3 ++- objc/reader.m | 5 +++-- php/reader.php | 7 ++++--- powershell/reader.psm1 | 3 ++- swift3/Sources/reader.swift | 5 +++-- ts/Makefile | 2 +- ts/reader.ts | 4 +--- 7 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lua/reader.lua b/lua/reader.lua index ee0a61e6d7..261f3ecbc3 100644 --- a/lua/reader.lua +++ b/lua/reader.lua @@ -45,9 +45,10 @@ function M.read_atom(rdr) elseif float_re:exec(token) then return tonumber(token) elseif string.sub(token,1,1) == '"' then local sval = string.sub(token,2,string.len(token)-1) + sval = string.gsub(sval, '\\\\', '\177') sval = string.gsub(sval, '\\"', '"') sval = string.gsub(sval, '\\n', '\n') - sval = string.gsub(sval, '\\\\', '\\') + sval = string.gsub(sval, '\177', '\\') return sval elseif string.sub(token,1,1) == ':' then return "\177" .. string.sub(token,2) diff --git a/objc/reader.m b/objc/reader.m index 49320e33cb..2755ffd419 100644 --- a/objc/reader.m +++ b/objc/reader.m @@ -98,10 +98,11 @@ - (NSString *)peek { return [MalFalse alloc]; // TODO: intern } else if ([match rangeAtIndex:6].location < -1ULL/2) { // string NSString * str = [token substringWithRange:[match rangeAtIndex:6]]; - return [[[str + return [[[[str + stringByReplacingOccurrencesOfString:@"\\\\" withString:@"\u029e"] stringByReplacingOccurrencesOfString:@"\\\"" withString:@"\""] stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"] - stringByReplacingOccurrencesOfString:@"\\\\" withString:@"\\"]; + stringByReplacingOccurrencesOfString:@"\u029e" withString:@"\\"]; } else if ([match rangeAtIndex:7].location < -1ULL/2) { // keyword return [NSString stringWithFormat:@"\u029e%@", [token substringWithRange:[match rangeAtIndex:7]]]; diff --git a/php/reader.php b/php/reader.php index 68c21eaad1..54ed8b11c2 100644 --- a/php/reader.php +++ b/php/reader.php @@ -38,9 +38,10 @@ function read_atom($reader) { return intval($token, 10); } elseif ($token[0] === "\"") { $str = substr($token, 1, -1); - $str = preg_replace('/\\\\"/', '"', $str); - $str = preg_replace('/\\\\n/', "\n", $str); - $str = preg_replace('/\\\\\\\\/', "\\", $str); + $str = str_replace('\\\\', chr(0x7f), $str); + $str = str_replace('\\"', '"', $str); + $str = str_replace('\\n', "\n", $str); + $str = str_replace(chr(0x7f), "\\", $str); return $str; } elseif ($token[0] === ":") { return _keyword(substr($token,1)); diff --git a/powershell/reader.psm1 b/powershell/reader.psm1 index 88712e0e25..b957ca2055 100644 --- a/powershell/reader.psm1 +++ b/powershell/reader.psm1 @@ -33,9 +33,10 @@ function read_atom([Reader] $rdr) { return [convert]::ToInt32($token, 10) } elseif ($token -match "^`".*`"") { $s = $token.Substring(1,$token.Length-2) + $s = $s -replace "\\\\", "$([char]0x29e)" $s = $s -replace "\\`"", "`"" $s = $s -replace "\\n", "`n" - $s = $s -replace "\\\\", "\" + $s = $s -replace "$([char]0x29e)", "\" return $s } elseif ($token -match ":.*") { return "$([char]0x29e)$($token.substring(1))" diff --git a/swift3/Sources/reader.swift b/swift3/Sources/reader.swift index c4eacf4272..66cef2d3a2 100644 --- a/swift3/Sources/reader.swift +++ b/swift3/Sources/reader.swift @@ -79,9 +79,10 @@ func read_string(_ rdr: Reader) throws -> MalVal { } let matchStr = rdr.str.substring(with: rdr.str.index(after: start).. c == 'n' ? '\n' : c) return new MalString(v); } if (token[0] === ":") { From 051685d1a206849b25e21ba093105b37934a7cf8 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 26 Sep 2017 16:29:30 -0500 Subject: [PATCH 0505/2308] Fix unescaping in julia and kotlin. --- julia/reader.jl | 9 +++------ kotlin/src/mal/reader.kt | 6 +++++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/julia/reader.jl b/julia/reader.jl index 487dfe8211..29b5dc9548 100644 --- a/julia/reader.jl +++ b/julia/reader.jl @@ -38,12 +38,9 @@ function read_atom(rdr) elseif ismatch(r"^-?[0-9][0-9.]*$", token) float(token) elseif ismatch(r"^\".*\"$", token) - replace( - replace( - replace(token[2:end-1], - "\\\"", "\""), - "\\n", "\n"), - "\\\\", "\\") + replace(token[2:end-1], r"\\.", (r) -> get(Dict("\\n"=>"\n", + "\\\""=>"\"", + "\\\\"=>"\\"), r, r)) elseif token[1] == ':' "\u029e$(token[2:end])" elseif token == "nil" diff --git a/kotlin/src/mal/reader.kt b/kotlin/src/mal/reader.kt index 2adddcbeb0..3b23be5f04 100644 --- a/kotlin/src/mal/reader.kt +++ b/kotlin/src/mal/reader.kt @@ -139,7 +139,11 @@ fun read_atom(reader: Reader): MalType { } else if (groups[4]?.value != null) { FALSE } else if (groups[5]?.value != null) { - MalString((groups[5]?.value as String).replace("\\n", "\n").replace("\\\"", "\"").replace("\\\\", "\\")) + MalString((groups[5]?.value as String).replace(Regex("""\\(.)""")) + { m: MatchResult -> + if (m.groups[1]?.value == "n") "\n" + else m.groups[1]?.value.toString() + }) } else if (groups[6]?.value != null) { MalKeyword(groups[6]?.value as String) } else if (groups[7]?.value != null) { From a7d9ace3bef8586c2307462bc0a2bc6163ed8516 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 26 Sep 2017 17:45:32 -0500 Subject: [PATCH 0506/2308] Fix unescaping in awk, d, dart. --- awk/reader.awk | 3 ++- d/reader.d | 5 ++++- dart/reader.dart | 5 ++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/awk/reader.awk b/awk/reader.awk index 95824ee549..6dcaf804f0 100644 --- a/awk/reader.awk +++ b/awk/reader.awk @@ -1,9 +1,10 @@ function reader_read_string(token, v, r) { token = substr(token, 1, length(token) - 1) - gsub(/\\\\/, "\\", token) + gsub(/\\\\/, "\u029e", token) gsub(/\\"/, "\"", token) gsub(/\\n/, "\n", token) + gsub("\u029e", "\\", token) return token } diff --git a/d/reader.d b/d/reader.d index 9cd39653f1..ac33b84656 100644 --- a/d/reader.d +++ b/d/reader.d @@ -61,11 +61,14 @@ string[] tokenize(string str) MalString parse_string(string token) { + // TODO: this could be done with replaceAll + // https://dlang.org/library/std/regex/replace_all.html string unescaped = token[1..$-1] // Remove surrounding quotes + .replace("\\\\", "\u029e") .replace("\\n", "\n") .replace("\\\"", "\"") - .replace("\\\\", "\\"); + .replace("\u029e", "\\"); return new MalString(unescaped); } diff --git a/dart/reader.dart b/dart/reader.dart index 5734fce0f8..e440ae054b 100644 --- a/dart/reader.dart +++ b/dart/reader.dart @@ -118,9 +118,8 @@ MalType read_atom(Reader reader) { var sanitizedToken = token // remove surrounding quotes .substring(1, token.length - 1) - .replaceAll(r'\"', '"') - .replaceAll(r'\n', '\n') - .replaceAll(r'\\', '\\'); + .replaceAllMapped(new RegExp("\\\\(.)"), + (Match m) => m[1] == 'n' ? '\n' : m[1]); return new MalString(sanitizedToken); } From 150008c9fcbefef690584416a19cbafbe79129b2 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 27 Sep 2017 11:00:15 -0500 Subject: [PATCH 0507/2308] Fix unescaping in scala. --- scala/reader.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scala/reader.scala b/scala/reader.scala index c8d75e4360..891e677c03 100644 --- a/scala/reader.scala +++ b/scala/reader.scala @@ -26,7 +26,11 @@ object reader { } def parse_str(s: String): String = { - s.replace("\\\"", "\"").replace("\\n", "\n").replace("\\\\", "\\") + // TODO: use re.replaceAllIn instead for single pass + s.replace("\\\\", "\u029e") + .replace("\\\"", "\"") + .replace("\\n", "\n") + .replace("\u029e", "\\") } def read_atom(rdr: Reader): Any = { From 130fdf5d189495dff3f455f2c1e29fd28df29725 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 27 Sep 2017 22:08:49 -0500 Subject: [PATCH 0508/2308] Ignore rust/matlab runtime dirs. Typo in TODO. --- .gitignore | 2 ++ docs/TODO | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a0cf920095..a1651f2bf9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .bash_history .cache +.cargo .config .mal-history .crystal @@ -79,6 +80,7 @@ lua/linenoise.so lua/mal.lua make/mal.mk mal/mal.mal +matlab/octave-workspace miniMAL/mal.json nim/nimcache* objc/*.d diff --git a/docs/TODO b/docs/TODO index a2ef21b955..7cea51ed2f 100644 --- a/docs/TODO +++ b/docs/TODO @@ -85,7 +85,7 @@ Make: - allow '_' in make variable names - hash-map with space in key string - errors should propagate up from within load-file - - GC: explore using "undefined" directive in Make 3.82 + - GC: explore using "undefine" directive in Make 3.82 Mal: - line numbers in errors From a11a023558c5a660d4d1b1d7d85ff3129d81d828 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 27 Sep 2017 22:10:24 -0500 Subject: [PATCH 0509/2308] ES6: Use ES6 modules. Update node. --- .gitignore | 2 +- Makefile | 2 +- es6/Dockerfile | 7 +-- es6/Makefile | 52 +++++-------------- es6/{core.js => core.mjs} | 18 +++---- es6/{env.js => env.mjs} | 0 es6/node_readline.js | 5 +- es6/package.json | 9 ++-- es6/{printer.js => printer.mjs} | 4 +- es6/{reader.js => reader.mjs} | 4 +- es6/run | 2 +- es6/{step0_repl.js => step0_repl.mjs} | 0 ...ep1_read_print.js => step1_read_print.mjs} | 0 es6/{step2_eval.js => step2_eval.mjs} | 0 es6/{step3_env.js => step3_env.mjs} | 0 es6/{step4_if_fn_do.js => step4_if_fn_do.mjs} | 0 es6/{step5_tco.js => step5_tco.mjs} | 0 es6/{step6_file.js => step6_file.mjs} | 0 es6/{step7_quote.js => step7_quote.mjs} | 0 es6/{step8_macros.js => step8_macros.mjs} | 0 es6/{step9_try.js => step9_try.mjs} | 0 es6/{stepA_mal.js => stepA_mal.mjs} | 0 es6/{types.js => types.mjs} | 34 ++++++------ 23 files changed, 55 insertions(+), 84 deletions(-) rename es6/{core.js => core.mjs} (86%) rename es6/{env.js => env.mjs} (100%) rename es6/{printer.js => printer.mjs} (92%) rename es6/{reader.js => reader.mjs} (96%) rename es6/{step0_repl.js => step0_repl.mjs} (100%) rename es6/{step1_read_print.js => step1_read_print.mjs} (100%) rename es6/{step2_eval.js => step2_eval.mjs} (100%) rename es6/{step3_env.js => step3_env.mjs} (100%) rename es6/{step4_if_fn_do.js => step4_if_fn_do.mjs} (100%) rename es6/{step5_tco.js => step5_tco.mjs} (100%) rename es6/{step6_file.js => step6_file.mjs} (100%) rename es6/{step7_quote.js => step7_quote.mjs} (100%) rename es6/{step8_macros.js => step8_macros.mjs} (100%) rename es6/{step9_try.js => step9_try.mjs} (100%) rename es6/{stepA_mal.js => stepA_mal.mjs} (100%) rename es6/{types.js => types.mjs} (62%) diff --git a/.gitignore b/.gitignore index a1651f2bf9..5ecac391f2 100644 --- a/.gitignore +++ b/.gitignore @@ -52,7 +52,7 @@ erlang/ebin erlang/.rebar erlang/src/*.beam es6/mal.js -es6/build +es6/.esm-cache factor/mal.factor forth/mal.fs fsharp/*.exe diff --git a/Makefile b/Makefile index 4a7b236a6e..8bf6a66f3f 100644 --- a/Makefile +++ b/Makefile @@ -189,7 +189,7 @@ elisp_STEP_TO_PROG = elisp/$($(1)).el elixir_STEP_TO_PROG = elixir/lib/mix/tasks/$($(1)).ex elm_STEP_TO_PROG = elm/$($(1)).js erlang_STEP_TO_PROG = erlang/$($(1)) -es6_STEP_TO_PROG = es6/build/$($(1)).js +es6_STEP_TO_PROG = es6/$($(1)).mjs factor_STEP_TO_PROG = factor/$($(1))/$($(1)).factor forth_STEP_TO_PROG = forth/$($(1)).fs fsharp_STEP_TO_PROG = fsharp/$($(1)).exe diff --git a/es6/Dockerfile b/es6/Dockerfile index 208e7f660e..ddcadb5b86 100644 --- a/es6/Dockerfile +++ b/es6/Dockerfile @@ -25,15 +25,10 @@ WORKDIR /mal RUN apt-get -y install g++ # Add nodesource apt repo config for 7.X -RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - +RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - # Install nodejs RUN apt-get -y install nodejs # Link common name -RUN ln -sf nodejs /usr/bin/node - ENV NPM_CONFIG_CACHE /mal/.npm - -# ES6 -RUN npm install -g babel-cli babel-plugin-transform-es2015-modules-commonjs diff --git a/es6/Makefile b/es6/Makefile index 775b814f26..b88fd2a0d1 100644 --- a/es6/Makefile +++ b/es6/Makefile @@ -1,56 +1,32 @@ -export PATH := $(PATH):node_modules/.bin/ - -BABEL_OPTS = --source-maps true \ - --plugins transform-es2015-modules-commonjs - -SOURCES_BASE = node_readline.js types.js reader.js printer.js -SOURCES_LISP = env.js core.js stepA_mal.js +SOURCES_BASE = node_readline.js types.mjs reader.mjs printer.mjs +SOURCES_LISP = env.mjs core.mjs stepA_mal.mjs SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -STEPS = step0_repl step1_read_print step2_eval step3_env \ - step4_if_fn_do step5_tco step6_file step7_quote \ - step8_macros step9_try stepA_mal +STEPS = step0_repl.mjs step1_read_print.mjs step2_eval.mjs step3_env.mjs \ + step4_if_fn_do.mjs step5_tco.mjs step6_file.mjs \ + step7_quote.mjs step8_macros.mjs step9_try.mjs stepA_mal.mjs -all: node_modules $(foreach s,$(STEPS),build/$(s).js) +all: node_modules dist: mal.js mal -build/%.js: %.js node_modules - @mkdir -p $(dir $@) - babel $(BABEL_OPTS) $< --out-file $@ - @echo >> $@ # workaround node-uglifier bug +node_modules: + npm install + +$(STEPS): node_modules -mal.js: $(foreach s,$(SOURCES),build/$(s)) - node -e 'nu = new (require("node-uglifier"))("./build/stepA_mal.js"); nu.merge().exportToFile("$@")' +mal.js: $(SOURCES) + cat $+ | sed 's/^export //' | grep -v "^import " >> $@ mal: mal.js echo "#!/usr/bin/env node" > $@ cat $< >> $@ chmod +x $@ -STEP0_DEPS = build/node_readline.js -STEP1_DEPS = $(STEP0_DEPS) build/types.js build/reader.js build/printer.js -STEP3_DEPS = $(STEP1_DEPS) build/env.js -STEP4_DEPS = $(STEP3_DEPS) build/core.js - -build/step0_repl.js: $(STEP0_DEPS) -build/step1_read_print.js: $(STEP1_DEPS) -build/step2_eval.js: $(STEP1_DEPS) -build/step3_env.js: $(STEP3_DEPS) -build/step4_if_fn_do.js: $(STEP4_DEPS) -build/step5_tco.js: $(STEP4_DEPS) -build/step6_file.js: $(STEP4_DEPS) -build/step7_quote.js: $(STEP4_DEPS) -build/step8_macros.js: $(STEP4_DEPS) -build/step9_try.js: $(STEP4_DEPS) -build/stepA_mal.js: $(STEP4_DEPS) - - -node_modules: - npm install clean: - rm -f build/* mal.js mal + rm -f mal.js mal + rm -rf node_modules .PHONY: stats tests $(TESTS) diff --git a/es6/core.js b/es6/core.mjs similarity index 86% rename from es6/core.js rename to es6/core.mjs index cd11a8372a..7b9e1f5219 100644 --- a/es6/core.js +++ b/es6/core.mjs @@ -1,5 +1,5 @@ -import { _equal_Q, _clone, _keyword, _keyword_Q, - _list_Q, Vector, _assoc_BANG, _dissoc_BANG, Atom } from './types' +import { _equal_Q, _clone, _keyword, _keyword_Q } from './types' +import { _list_Q, Vector, _assoc_BANG, Atom } from './types' import { pr_str } from './printer' import { readline } from './node_readline' import { read_str } from './reader' @@ -22,16 +22,12 @@ function slurp(f) { } // Sequence functions -function conj(o, ...a) { - return _list_Q(o) ? a.reverse().concat(o) : Vector.from(o.concat(a)) -} - function seq(obj) { if (_list_Q(obj)) { return obj.length > 0 ? obj : null } else if (obj instanceof Vector) { return obj.length > 0 ? Array.from(obj.slice(0)) : null - } else if (typeof obj === "string" && obj[0] !== '\u029e') { + } else if (typeof obj === "string" && !_keyword_Q(obj)) { return obj.length > 0 ? obj.split('') : null } else if (obj === null) { return null @@ -48,7 +44,7 @@ export const core_ns = new Map([ ['nil?', a => a === null], ['true?', a => a === true], ['false?', a => a === false], - ['string?', a => typeof a === "string" && a[0] !== '\u029e'], + ['string?', a => typeof a === "string" && !_keyword_Q(a)], ['symbol', a => Symbol.for(a)], ['symbol?', a => typeof a === 'symbol'], ['keyword', _keyword], @@ -79,7 +75,8 @@ export const core_ns = new Map([ ['hash-map', (...a) => _assoc_BANG(new Map(), ...a)], ['map?', a => a instanceof Map], ['assoc', (m,...a) => _assoc_BANG(_clone(m), ...a)], - ['dissoc', (m,...a) => _dissoc_BANG(_clone(m), ...a)], + ['dissoc', (m,...a) => { let n = _clone(m); a.forEach(k => n.delete(k)); + return n}], ['get', (m,a) => m === null ? null : m.has(a) ? m.get(a) : null], ['contains?', (m,a) => m.has(a)], ['keys', a => Array.from(a.keys())], @@ -96,7 +93,8 @@ export const core_ns = new Map([ ['apply', (f,...a) => f(...a.slice(0, -1).concat(a[a.length-1]))], ['map', (f,a) => Array.from(a.map(x => f(x)))], - ['conj', conj], + ['conj', (s,...a) => _list_Q(s) ? a.reverse().concat(s) + : Vector.from(s.concat(a))], ['seq', seq], ['meta', a => 'meta' in a ? a['meta'] : null], diff --git a/es6/env.js b/es6/env.mjs similarity index 100% rename from es6/env.js rename to es6/env.mjs diff --git a/es6/node_readline.js b/es6/node_readline.js index dd5db0b68e..9e2fb864ba 100644 --- a/es6/node_readline.js +++ b/es6/node_readline.js @@ -13,7 +13,7 @@ var rllib = ffi.Library(RL_LIB, { var rl_history_loaded = false; -export function readline(prompt) { +function readline(prompt) { prompt = prompt || "user> "; if (!rl_history_loaded) { @@ -41,3 +41,6 @@ export function readline(prompt) { return line; }; + +//exports.readline = readline +module.exports = {readline: readline} diff --git a/es6/package.json b/es6/package.json index d836e6d663..627da97fec 100644 --- a/es6/package.json +++ b/es6/package.json @@ -3,11 +3,10 @@ "version": "0.0.1", "description": "Make a Lisp (mal) language implemented in ES6 (ECMAScript 6 / ECMAScript 2015)", "dependencies": { - "ffi": "2.0.x", - "node-uglifier": "0.4.3" + "@std/esm": "^0.11.0", + "ffi": "2.0.x" }, - "devDependencies": { - "babel-cli": "^6.0.0", - "babel-plugin-transform-es2015-modules-commonjs": "*" + "@std/esm": { + "cjs": true } } diff --git a/es6/printer.js b/es6/printer.mjs similarity index 92% rename from es6/printer.js rename to es6/printer.mjs index 18e5d350cb..82230f5d89 100644 --- a/es6/printer.js +++ b/es6/printer.mjs @@ -1,4 +1,4 @@ -import { _symbol, _list_Q, Vector, Atom } from './types' +import { _list_Q, _keyword_Q, Vector, Atom } from './types' export function pr_str(obj, print_readably) { if (typeof print_readably === 'undefined') { print_readably = true } @@ -14,7 +14,7 @@ export function pr_str(obj, print_readably) { } return "{" + ret.join(' ') + "}" } else if (typeof obj === "string") { - if (obj[0] === '\u029e') { + if (_keyword_Q(obj)) { return ':' + obj.slice(1) } else if (_r) { return '"' + obj.replace(/\\/g, "\\\\") diff --git a/es6/reader.js b/es6/reader.mjs similarity index 96% rename from es6/reader.js rename to es6/reader.mjs index fb2c2f2fac..ae23ddf084 100644 --- a/es6/reader.js +++ b/es6/reader.mjs @@ -1,4 +1,4 @@ -import { _keyword, Vector, _assoc_BANG } from './types' +import { _keyword, _assoc_BANG, Vector } from './types'; export class BlankException extends Error {} @@ -66,7 +66,7 @@ function read_list(reader, start, end) { // read vector of tokens function read_vector(reader) { - return Vector.from(read_list(reader, '[', ']')) + return Vector.from(read_list(reader, '[', ']')); } // read hash-map key/value pairs diff --git a/es6/run b/es6/run index 24d7d2b86b..a6c8bf8387 100755 --- a/es6/run +++ b/es6/run @@ -1,2 +1,2 @@ #!/bin/bash -exec node $(dirname $0)/build/${STEP:-stepA_mal}.js "${@}" +exec node -r @std/esm $(dirname $0)/${STEP:-stepA_mal}.mjs "${@}" diff --git a/es6/step0_repl.js b/es6/step0_repl.mjs similarity index 100% rename from es6/step0_repl.js rename to es6/step0_repl.mjs diff --git a/es6/step1_read_print.js b/es6/step1_read_print.mjs similarity index 100% rename from es6/step1_read_print.js rename to es6/step1_read_print.mjs diff --git a/es6/step2_eval.js b/es6/step2_eval.mjs similarity index 100% rename from es6/step2_eval.js rename to es6/step2_eval.mjs diff --git a/es6/step3_env.js b/es6/step3_env.mjs similarity index 100% rename from es6/step3_env.js rename to es6/step3_env.mjs diff --git a/es6/step4_if_fn_do.js b/es6/step4_if_fn_do.mjs similarity index 100% rename from es6/step4_if_fn_do.js rename to es6/step4_if_fn_do.mjs diff --git a/es6/step5_tco.js b/es6/step5_tco.mjs similarity index 100% rename from es6/step5_tco.js rename to es6/step5_tco.mjs diff --git a/es6/step6_file.js b/es6/step6_file.mjs similarity index 100% rename from es6/step6_file.js rename to es6/step6_file.mjs diff --git a/es6/step7_quote.js b/es6/step7_quote.mjs similarity index 100% rename from es6/step7_quote.js rename to es6/step7_quote.mjs diff --git a/es6/step8_macros.js b/es6/step8_macros.mjs similarity index 100% rename from es6/step8_macros.js rename to es6/step8_macros.mjs diff --git a/es6/step9_try.js b/es6/step9_try.mjs similarity index 100% rename from es6/step9_try.js rename to es6/step9_try.mjs diff --git a/es6/stepA_mal.js b/es6/stepA_mal.mjs similarity index 100% rename from es6/stepA_mal.js rename to es6/stepA_mal.mjs diff --git a/es6/types.js b/es6/types.mjs similarity index 62% rename from es6/types.js rename to es6/types.mjs index 458538ff55..d6198b6dce 100644 --- a/es6/types.js +++ b/es6/types.mjs @@ -19,12 +19,19 @@ export function _equal_Q (a, b) { export function _clone(obj, new_meta) { let new_obj = null - if (_list_Q(obj)) { new_obj = obj.slice(0) } - else if (obj instanceof Vector) { new_obj = Vector.from(obj.slice(0)) } - else if (obj instanceof Map) { new_obj = new Map(obj.entries()) } - else if (obj instanceof Function) { new_obj = obj.clone() } - else { throw Error("Invalid clone") } - if (new_meta !== undefined) { new_obj.meta = new_meta } + if (_list_Q(obj)) { + new_obj = obj.slice(0) + } else if (obj instanceof Vector) { + new_obj = Vector.from(obj) + } else if (obj instanceof Map) { + new_obj = new Map(obj.entries()) + } else if (obj instanceof Function) { + let f = (...a) => obj.apply(f, a) // new function instance + new_obj = Object.assign(f, obj) // copy original properties + } else { + throw Error('Unsupported type for clone') + } + if (typeof new_meta !== 'undefined') { new_obj.meta = new_meta } return new_obj } @@ -33,33 +40,26 @@ export function _malfunc(f, ast, env, params, meta=null, ismacro=false) { return Object.assign(f, {ast, env, params, meta, ismacro}) } export const _malfunc_Q = f => f.ast ? true : false -Function.prototype.clone = function() { - let f = (...a) => this.apply(f, a) // new function instance - return Object.assign(f, this) // copy original properties -} // Keywords export const _keyword = obj => _keyword_Q(obj) ? obj : '\u029e' + obj export const _keyword_Q = obj => typeof obj === 'string' && obj[0] === '\u029e' -// Sequence collections +// Lists export const _list_Q = obj => Array.isArray(obj) && !(obj instanceof Vector) -export class Vector extends Array {} +// Vectors +export class Vector extends Array { } +// Maps export function _assoc_BANG(hm, ...args) { if (args.length % 2 === 1) { throw new Error('Odd number of assoc arguments') } - // Use iterator/Array.from when it works for (let i=0; i Date: Sat, 30 Sep 2017 13:14:06 +0200 Subject: [PATCH 0510/2308] fix typo in guide --- process/guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/process/guide.md b/process/guide.md index aa996c743d..554c145c5b 100644 --- a/process/guide.md +++ b/process/guide.md @@ -351,7 +351,7 @@ expression support. subclass type. For example, if your language is object oriented, then you can define a top level MalType (in `types.qx`) that all your mal data types inherit from. The MalList type (which also - inherits from MalType) will contains a list/array of other MalTypes. + inherits from MalType) will contain a list/array of other MalTypes. If your language is dynamically typed then you can likely just return a plain list/array of other mal types. From 0ad505e829a6ad0877856c7f6c95ee9f844e96a4 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 9 Oct 2017 00:10:03 +0100 Subject: [PATCH 0511/2308] Step0 (input/output) passes tests * String handling simplified, will not handle very long strings * Memory allocation missing, always using the same buffer currently --- Makefile | 3 +- nasm/Makefile | 3 + nasm/run | 3 + nasm/step0_repl.asm | 326 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 nasm/Makefile create mode 100755 nasm/run create mode 100644 nasm/step0_repl.asm diff --git a/Makefile b/Makefile index 8bf6a66f3f..d88cd89741 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ DOCKERIZE = IMPLS = ada awk bash basic c chuck clojure coffee common-lisp cpp crystal cs d dart \ elisp elixir elm erlang es6 factor forth fsharp go groovy gst guile haskell \ haxe hy io java js julia kotlin livescript logo lua make mal matlab miniMAL \ - nim objc objpascal ocaml perl perl6 php pil plpgsql plsql powershell ps \ + nasm nim objc objpascal ocaml perl perl6 php pil plpgsql plsql powershell ps \ python r racket rexx rpython ruby rust scala scheme skew swift swift3 tcl \ ts vb vhdl vimscript yorick @@ -212,6 +212,7 @@ make_STEP_TO_PROG = make/$($(1)).mk mal_STEP_TO_PROG = mal/$($(1)).mal matlab_STEP_TO_PROG = matlab/$($(1)).m miniMAL_STEP_TO_PROG = miniMAL/$($(1)).json +nasm_STEP_TO_PROG = nasm/$($(1)) nim_STEP_TO_PROG = nim/$($(1)) objc_STEP_TO_PROG = objc/$($(1)) objpascal_STEP_TO_PROG = objpascal/$($(1)) diff --git a/nasm/Makefile b/nasm/Makefile new file mode 100644 index 0000000000..511d7c665e --- /dev/null +++ b/nasm/Makefile @@ -0,0 +1,3 @@ +step0_repl: + nasm -felf64 step0_repl.asm + ld -o $@ step0_repl.o diff --git a/nasm/run b/nasm/run new file mode 100755 index 0000000000..0ecd249cae --- /dev/null +++ b/nasm/run @@ -0,0 +1,3 @@ +#!/bin/bash +exec $(dirname $0)/${STEP:-stepA_mal} "${@}" + diff --git a/nasm/step0_repl.asm b/nasm/step0_repl.asm new file mode 100644 index 0000000000..decdf4de2e --- /dev/null +++ b/nasm/step0_repl.asm @@ -0,0 +1,326 @@ +;; nasm -felf64 mal.asm && ld mal.o && ./a.out + +;; Calling convention: Address of input is in RSI +;; Address of return value is in RAX +;; + +;; Data structures +;; Memory management is done by having two fixed-size datatypes, +;; Cons and Array. +;; +;; Both Cons and Array have the following in common: +;; a type field at the start, a reference count, followed by data +;; [ type (8) | (8) | refs (16) | data ] + + +;; +STRUC Cons +.typecar: RESB 1 ; Type information for car (8 bit) +.typecdr: RESB 1 ; Type information for cdr (8 bits) +.refcount: RESW 1 ; Number of references to this Cons (16 bit) +.car: RESQ 1 ; First value (64 bit) +.cdr: RESQ 1 ; Second value (64 bit) +.size: ; Total size of struc +ENDSTRUC + + +%define array_chunk_len 32 ; Number of 64-bit values which can be stored in a single chunk + +STRUC Array +.type: RESB 1 ; Type information (8 bits) +.control: RESB 1 ; Control data (8 bits) +.refcount: RESW 1 ; Number of references to this Array (16 bit) +.length: RESD 1 ; Number of elements in array (32 bit) +.next RESQ 1 ; Pointer to the next chunk (64 bit) +.data: RESQ array_chunk_len ; Data storage +.size: ; Total size of struc +ENDSTRUC + +;; Type information +%define type_char 1 ; Character type +%define type_integer 2 ; Integer type +%define type_float 3 ; Floating point number +%define type_array 128 ; Last bit tests if array or cons + + + + global _start + +section .data + +str: ISTRUC Array +AT Array.type, db type_char + type_array +AT Array.length, dd 6 +AT Array.data, db 'hello',10 +IEND + +prompt_string: db 10,"user> " ; The string to print at the prompt +.len: equ $ - prompt_string + +error_msg_print_string: db "Error in print string",10 +.len: equ $ - error_msg_print_string + +section .bss + +section .text + +;; ------------------------------------------ +;; Array alloc_array() +;; +;; Returns the address of an Array object in RAX +alloc_array: + mov rax, str + ret + +;; ------------------------------------------- +;; Prints a raw string to stdout +;; String address in rsi, string length in rdx +print_rawstring: + push rax + push rdi + + ; write(1, string, length) + mov rax, 1 ; system call 1 is write + mov rdi, 1 ; file handle 1 is stdout + syscall + + pop rdi + pop rax + + ret + +;; ------------------------------------------ +;; void print_string(char array) +;; Address of the char Array should be in RSI +print_string: + ; Push registers we're going to use + push rax + push rdi + push rdx + push rsi + + ; Check that we have a char array + mov al, [rsi] + cmp al, type_char + type_array + jne .error + + ; write(1, string, length) + mov edx, [rsi + Array.length] ; number of bytes + add rsi, Array.data ; address of raw string to output + call print_rawstring + + ; Restore registers + pop rsi + pop rdx + pop rdi + pop rax + + ret +.error: + ; An error occurred + mov rdx, error_msg_print_string.len ; number of bytes + mov rsi, error_msg_print_string ; address of raw string to output + call print_rawstring + ; exit + jmp quit_error + +;; ------------------------------------------ +;; String itostring(Integer number) +;; +;; Converts an integer to a string (array of chars) +;; +;; Input in RAX +;; Return string address in RAX +itostring: + ; Save registers to restore afterwards + push rcx + push rdx + push rsi + push rdi + + mov rcx, 0 ; counter of how many bytes we need to print in the end + +.divideLoop: + inc rcx ; count each byte to print - number of characters + xor rdx, rdx + mov rsi, 10 + idiv rsi ; divide rax by rsi + add rdx, 48 ; convert rdx to it's ascii representation - rdx holds the remainder after a divide instruction + ; Character is now in DL + dec rsp + mov BYTE [rsp], dl ; Put onto stack + + cmp rax, 0 ; can the integer be divided anymore? + jnz .divideLoop ; jump if not zero to the label divideLoop + + ; Get an Array object to put the string into + call alloc_array ; Address in RAX + + ; put length into string + mov [rax + Array.length], ecx + + ; copy data from stack into string + ; Note: Currently this does not handle long strings + mov rdi, rax + add rdi, Array.data ; Address where raw string will go +.copyLoop: + mov BYTE dl, [rsp] ; Copy one byte at a time. Could be more efficient + mov [rdi], BYTE dl + inc rsp + inc rdi + dec rcx + cmp rcx, 0 + jnz .copyLoop + + ; Restore registers + pop rdi + pop rsi + pop rdx + pop rcx + + ret + +;; ---------------------------- +;; int stringtoi(String) +;; +;; Convert a string (char array) to an integer +;; +;; Address of input string is in RSI +;; Output integer in RAX +stringtoi: + + ret + +;------------------------------------------ +; void exit() +; Exit program and restore resources +quit: + mov eax, 60 ; system call 60 is exit + xor rdi, rdi ; exit code 0 + syscall ; invoke operating system to exit + +quit_error: + mov eax, 60 ; system call 60 is exit + mov rdi, 1 ; exit code 1 + syscall + + +;; Takes a string as input and processes it into a form +read: + mov rax, rsi ; Return the input + ret + +;; Evaluates a form +eval: + mov rax, rsi ; Return the input + ret + +;; Prints the result +print: + mov rax, rsi ; Return the input + ret + +;; Read-Eval-Print in sequence +rep_seq: + call read + mov rsi, rax ; Output of read into input of eval + call eval + mov rsi, rax ; Output of eval into input of print + call print + mov rsi, rax ; Return value + ret + +;; Read a line from stdin +;; Gets a new string array, fills it until a newline or EOF is reached +;; Returns pointer to string in RAX +read_line: + ; Get an array to put the string into + ; Address in rax + call alloc_array + ; Mark it as a character array (string) + mov BYTE [rax + Array.type], type_char + type_array + + push rax ; Save pointer to string + + ; Read character by character until either newline or end of input + mov ebx, 0 ; Count how many characters read + mov rsi, rax + add rsi, Array.data ; Point to the data +.readLoop: + mov rax, 0 ; sys_read + mov rdi, 0 ; stdin + mov rdx, 1 ; count + syscall + + ; Characters read in RAX + cmp rax, 0 ; end loop if read <= 0 + jle .readLoopEnd + + mov cl, BYTE [rsi] + + cmp cl, 10 ; End if we read a newline + je .readLoopEnd + + cmp cl, 8 ; Backspace? + je .handleBackspace + + cmp cl, 31 ; Below space + jle .readLoop ; Ignore, keep going + + cmp cl, 127 ; DEL or above + jge .readLoop ; Ignore, keep going + + inc ebx + inc rsi ; Move to next point in the array + jmp .readLoop ; Get another character + +.handleBackspace: + ; Check if we've read any characters + cmp ebx, 0 + je .readLoop ; If not, carry on the loop + ; Characters have been read. Remove one + dec ebx + dec rsi + jmp .readLoop +.readLoopEnd: + pop rax ; Restore pointer to string + mov DWORD [rax + Array.length], ebx ; Set string length + ret + + + +_start: + + ; ----------------------------- + ; Main loop + +.mainLoop: + ; print the prompt + mov rdx, prompt_string.len ; number of bytes + mov rsi, prompt_string ; address of raw string to output + call print_rawstring + + call read_line + + ; Check if we have a zero-length string + cmp DWORD [rax+Array.length], 0 + je .mainLoopEnd + + mov rsi, rax ; Put into input of print_string + call print_string + + jmp .mainLoop +.mainLoopEnd: + + ;mov rdx, 1 + ;mov rsi, + ;call print_rawstring + ;inc rsp + + ;mov rax, 1223 + ;call itostring + ;mov rsi, rax + ;call print_string + + jmp quit + From 424b9aa5884be1bc415a587c9bb212b1c11e9106 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 9 Oct 2017 21:46:50 +0200 Subject: [PATCH 0512/2308] Elisp: Do shallow copies in with-meta --- elisp/core.el | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/elisp/core.el b/elisp/core.el index 28b8e85d12..a684e3f287 100644 --- a/elisp/core.el +++ b/elisp/core.el @@ -223,8 +223,7 @@ (meta . ,(mal-fn (lambda (mal-object) (or (mal-meta mal-object) mal-nil)))) (with-meta . ,(mal-fn (lambda (mal-object meta) - ;; TODO: doesn't work on hashtables - (let ((mal-object* (copy-tree mal-object t))) + (let ((mal-object* (copy-sequence mal-object))) (setf (aref mal-object* 2) meta) mal-object*)))) From 03a374b4705656ec5047770c96786802ef39baa6 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 9 Oct 2017 21:47:11 +0200 Subject: [PATCH 0513/2308] Elisp: Make predicate check stricter --- elisp/types.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elisp/types.el b/elisp/types.el index 8029e5eecf..6abcbd2ad3 100644 --- a/elisp/types.el +++ b/elisp/types.el @@ -18,7 +18,7 @@ (defun ,constructor (&optional value meta) (vector ',name value meta)) (defun ,predicate (arg) - (and (arrayp arg) (eq (aref arg 0) ',name)))))) + (and (vectorp arg) (eq (aref arg 0) ',name)))))) (mal-object nil) (mal-object true) From 59436f1a1208bfafe24809eff4015fbbd54fb683 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Mon, 9 Oct 2017 20:23:39 +0000 Subject: [PATCH 0514/2308] Add number?, fn?, macro? in stepA - tests and process guide Ref #298 --- process/guide.md | 4 ++++ process/stepA_mal.txt | 3 +++ tests/stepA_mal.mal | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/process/guide.md b/process/guide.md index 554c145c5b..455a76e3f1 100644 --- a/process/guide.md +++ b/process/guide.md @@ -1635,6 +1635,10 @@ For extra information read [Peter Seibel's thorough discussion about new vector is returned with the elements added to the end of the given vector. * `string?`: returns true if the parameter is a string. + * `number?`: returns true if the parameter is a number. + * `fn?`: returns true if the parameter is a function (internal or + user-defined). + * `macro?`: returns true if the parameter is a macro. * `seq`: takes a list, vector, string, or nil. If an empty list, empty vector, or empty string ("") is passed in then nil is returned. Otherwise, a list is returned unchanged, a vector is diff --git a/process/stepA_mal.txt b/process/stepA_mal.txt index 431a4b98ee..f16d6e3363 100644 --- a/process/stepA_mal.txt +++ b/process/stepA_mal.txt @@ -87,6 +87,9 @@ ns = {'=: equal?, 'symbol?: symbol?, 'keyword: keyword, 'keyword?: keyword?, + 'number?: number?, + 'fn?: fn?, + 'macro?: macro?, 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")), 'str: (a) -> a.map(|s| pr_str(e,false)).join("")), diff --git a/tests/stepA_mal.mal b/tests/stepA_mal.mal index a349c09b6b..5c458e1e3e 100644 --- a/tests/stepA_mal.mal +++ b/tests/stepA_mal.mal @@ -96,6 +96,47 @@ (get @e "bar") ;=>(1 2 3) +;; ------------------------------------------------------------------ +;; TODO move these to optional functionality after adding them to all +;; implementations +;; +;; Testing string? function +(number? 123) +;=>true +(number? -1) +;=>true +(number? nil) +;=>false +(number? false) +;=>false +(number? "123") +;=>false + +;; Testing fn? function +(fn? +) +;=>true +(fn? not) +;=>true +(fn? cond) +;=>false +(fn? "+") +;=>false +(fn? :+) +;=>false + +;; Testing macro? function +(macro? cond) +;=>true +(macro? +) +;=>false +(macro? not) +;=>false +(macro? "+") +;=>false +(macro? :+) +;=>false + +;; ------------------------------------------------------------------ ;>>> soft=True ;>>> optional=True From 2e13db63248c5c1deb7af5af3460a693e87bdfff Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Mon, 9 Oct 2017 20:25:08 +0000 Subject: [PATCH 0515/2308] ruby: Add number?, fn?, macro? --- ruby/core.rb | 3 +++ ruby/suggest.mal | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 ruby/suggest.mal diff --git a/ruby/core.rb b/ruby/core.rb index 0b68c4a284..0c26c8de55 100644 --- a/ruby/core.rb +++ b/ruby/core.rb @@ -13,6 +13,9 @@ :symbol? => lambda {|a| a.is_a? Symbol}, :keyword => lambda {|a| "\u029e"+a}, :keyword? => lambda {|a| (a.is_a? String) && "\u029e" == a[0]}, + :number? => lambda {|a| a.is_a? Numeric}, + :fn? => lambda {|a| (a.is_a? Proc) && (!(a.is_a? Function) || !a.is_macro)}, + :macro? => lambda {|a| (a.is_a? Function) && a.is_macro}, :"pr-str" => lambda {|*a| a.map {|e| _pr_str(e, true)}.join(" ")}, :str => lambda {|*a| a.map {|e| _pr_str(e, false)}.join("")}, diff --git a/ruby/suggest.mal b/ruby/suggest.mal new file mode 100644 index 0000000000..685a5d1db2 --- /dev/null +++ b/ruby/suggest.mal @@ -0,0 +1,7 @@ +(def! read-args (fn* [args] + (let* [arg (readline "arg> ")] + (if (or (nil? arg) (empty? arg)) + args + (read-args (conj args arg)))))) + +(prn "The args you entered are:" (read-args [])) From 7cecb87a7d3dfd23143ac8556b60cf1cabf562ce Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Mon, 9 Oct 2017 20:52:46 +0000 Subject: [PATCH 0516/2308] go, rexx, vimscript, yorick: Add number?, fn?, macro? --- go/src/core/core.go | 20 ++++++++++++++++++++ go/src/types/types.go | 5 +++++ rexx/core.rexx | 12 ++++++++++++ rexx/t.mal | 1 + rexx/types.rexx | 3 +++ vimscript/core.vim | 3 +++ yorick/core.i | 10 ++++++++++ 7 files changed, 54 insertions(+) create mode 100644 rexx/t.mal diff --git a/go/src/core/core.go b/go/src/core/core.go index 646135f046..abbaae8986 100644 --- a/go/src/core/core.go +++ b/go/src/core/core.go @@ -20,6 +20,19 @@ func throw(a []MalType) (MalType, error) { return nil, MalError{a[0]} } +func fn_q(a []MalType) (MalType, error) { + switch f := a[0].(type) { + case MalFunc: + return !f.GetMacro(), nil + case Func: + return true, nil + case func([]MalType) (MalType, error): + return true, nil + default: + return false, nil + } +} + // String functions func pr_str(a []MalType) (MalType, error) { @@ -464,6 +477,13 @@ var NS = map[string]MalType{ "keyword?": func(a []MalType) (MalType, error) { return Keyword_Q(a[0]), nil }, + "number?": func(a []MalType) (MalType, error) { + return Number_Q(a[0]), nil + }, + "fn?": fn_q, + "macro?": func(a []MalType) (MalType, error) { + return MalFunc_Q(a[0]) && a[0].(MalFunc).GetMacro(), nil + }, "pr-str": func(a []MalType) (MalType, error) { return pr_str(a) }, "str": func(a []MalType) (MalType, error) { return str(a) }, diff --git a/go/src/types/types.go b/go/src/types/types.go index d5bff952ac..9d4cb10b5f 100644 --- a/go/src/types/types.go +++ b/go/src/types/types.go @@ -41,6 +41,11 @@ func False_Q(obj MalType) bool { return ok && b == false } +func Number_Q(obj MalType) bool { + _, ok := obj.(int) + return ok +} + // Symbols type Symbol struct { Val string diff --git a/rexx/core.rexx b/rexx/core.rexx index d458fcf03c..2b9c749b07 100644 --- a/rexx/core.rexx +++ b/rexx/core.rexx @@ -34,6 +34,15 @@ mal_keyword: procedure expose values. /* mal_keyword(a) */ mal_keyword?: procedure expose values. /* mal_keyword?(a) */ return new_boolean(keyword?(arg(1))) +mal_number?: procedure expose values. /* mal_number?(a) */ + return new_boolean(number?(arg(1))) + +mal_fn?: procedure expose values. /* mal_fn?(a) */ + return new_boolean(nativefn?(arg(1)) | (func?(arg(1)) & (func_is_macro(arg(1)) \= 1))) + +mal_macro?: procedure expose values. /* mal_macro?(a) */ + return new_boolean(func_macro?(arg(1))) + mal_pr_str: procedure expose values. /* mal_pr_str(...) */ res = "" do i=1 to arg() @@ -439,6 +448,9 @@ get_core_ns: procedure /* get_core_ns() */ "symbol? mal_symbol?" , "keyword mal_keyword" , "keyword? mal_keyword?" , + "number? mal_number?" , + "fn? mal_fn?" , + "macro? mal_macro?" , , "pr-str mal_pr_str" , "str mal_str" , diff --git a/rexx/t.mal b/rexx/t.mal new file mode 100644 index 0000000000..0c27e43547 --- /dev/null +++ b/rexx/t.mal @@ -0,0 +1 @@ +(prn (+ 4 5)) diff --git a/rexx/types.rexx b/rexx/types.rexx index c9b1ad3e9a..ba60256727 100644 --- a/rexx/types.rexx +++ b/rexx/types.rexx @@ -44,6 +44,9 @@ new_number: procedure /* new_number(n) */ n = arg(1) return "numb_" || n +number?: procedure /* number?(obj) */ + return obj_type(arg(1)) == "numb" + new_nil: procedure /* new_nil() */ return "nill_0" diff --git a/vimscript/core.vim b/vimscript/core.vim index f85cd83139..5dfda3307a 100644 --- a/vimscript/core.vim +++ b/vimscript/core.vim @@ -188,6 +188,9 @@ let CoreNs = { \ "string?": NewNativeFnLambda({a -> BoolNew(StringQ(a[0]))}), \ "keyword": NewNativeFnLambda({a -> KeywordNew(a[0].val)}), \ "keyword?": NewNativeFnLambda({a -> BoolNew(KeywordQ(a[0]))}), + \ "number?": NewNativeFnLambda({a -> BoolNew(IntegerQ(a[0]))}), + \ "fn?": NewNativeFnLambda({a -> BoolNew(NativeFunctionQ(a[0]) || FunctionQ(a[0]))}), + \ "macro?": NewNativeFnLambda({a -> BoolNew(MacroQ(a[0]))}), \ "list": NewNativeFnLambda({a -> ListNew(a)}), \ "list?": NewNativeFnLambda({a -> BoolNew(ListQ(a[0]))}), \ "vector": NewNativeFnLambda({a -> VectorNew(a)}), diff --git a/yorick/core.i b/yorick/core.i index c4d6ed447a..38dc622505 100644 --- a/yorick/core.i +++ b/yorick/core.i @@ -11,6 +11,13 @@ func mal_symbol(a) { return MalSymbol(val=a(1)->val); } func mal_symbol_q(a) { return new_boolean(structof(*a(1)) == MalSymbol); } func mal_keyword(a) { return MalKeyword(val=a(1)->val); } func mal_keyword_q(a) { return new_boolean(structof(*a(1)) == MalKeyword); } +func mal_number_q(a) { return new_boolean(structof(*a(1)) == MalNumber); } +func mal_fn_q(a) +{ + if (structof(*a(1)) == MalNativeFunction) return MAL_TRUE; + return new_boolean(structof(*a(1)) == MalFunction && !a(1)->macro); +} +func mal_macro_q(a) { return new_boolean(structof(*a(1)) == MalFunction && a(1)->macro); } func string_helper(a, delimiter, readable) { @@ -298,6 +305,9 @@ h_set, core_ns, "symbol", mal_symbol h_set, core_ns, "symbol?", mal_symbol_q h_set, core_ns, "keyword", mal_keyword h_set, core_ns, "keyword?", mal_keyword_q +h_set, core_ns, "number?", mal_number_q +h_set, core_ns, "fn?", mal_fn_q +h_set, core_ns, "macro?", mal_macro_q h_set, core_ns, "pr-str", mal_pr_str h_set, core_ns, "str", mal_str From 3eb7df5a61926815bedd7fe4cc098ce9550c90bb Mon Sep 17 00:00:00 2001 From: Stephen Thirlwall Date: Tue, 10 Oct 2017 09:45:31 +1100 Subject: [PATCH 0517/2308] cpp makefile uses latest homebrew readline version /usr/local/opt/$package is a symlink to /usr/local/Cellar/$package/$latest_version --- cpp/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/Makefile b/cpp/Makefile index 5e464d51e8..ad20eb8360 100644 --- a/cpp/Makefile +++ b/cpp/Makefile @@ -3,7 +3,7 @@ uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') ifeq ($(uname_S),Darwin) # Native build on yosemite. Requires: brew install readline CXX=g++ - READLINE=/usr/local/Cellar/readline/6.3.8 + READLINE=/usr/local/opt/readline INCPATHS=-I$(READLINE)/include LIBPATHS=-L$(READLINE)/lib else From d53749f28e076ff44e4312a48e9689be3586c6c6 Mon Sep 17 00:00:00 2001 From: Stephen Thirlwall Date: Tue, 10 Oct 2017 10:02:09 +1100 Subject: [PATCH 0518/2308] cpp: add number?, fn? and macro? --- cpp/Core.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/cpp/Core.cpp b/cpp/Core.cpp index 0e02cf8096..6ae1f5eaa0 100644 --- a/cpp/Core.cpp +++ b/cpp/Core.cpp @@ -64,6 +64,7 @@ BUILTIN_ISA("atom?", malAtom); BUILTIN_ISA("keyword?", malKeyword); BUILTIN_ISA("list?", malList); BUILTIN_ISA("map?", malHash); +BUILTIN_ISA("number?", malInteger); BUILTIN_ISA("sequential?", malSequence); BUILTIN_ISA("string?", malString); BUILTIN_ISA("symbol?", malSymbol); @@ -241,6 +242,19 @@ BUILTIN("first") return seq->first(); } +BUILTIN("fn?") +{ + CHECK_ARGS_IS(1); + malValuePtr arg = *argsBegin++; + + // Lambdas are functions, unless they're macros. + if (const malLambda* lambda = DYNAMIC_CAST(malLambda, arg)) { + return mal::boolean(!lambda->isMacro()); + } + // Builtins are functions. + return mal::boolean(DYNAMIC_CAST(malBuiltIn, arg)); +} + BUILTIN("get") { CHECK_ARGS_IS(2); @@ -270,6 +284,15 @@ BUILTIN("keyword") return mal::keyword(":" + token->value()); } +BUILTIN("macro?") +{ + CHECK_ARGS_IS(1); + + // Macros are implemented as lambdas, with a special flag. + const malLambda* lambda = DYNAMIC_CAST(malLambda, *argsBegin); + return mal::boolean((lambda != NULL) && lambda->isMacro()); +} + BUILTIN("meta") { CHECK_ARGS_IS(1); From e21edd83c3c703c747e7e60a1476098c68e9d2e4 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 10 Oct 2017 00:22:10 +0100 Subject: [PATCH 0519/2308] Adding memory allocation, started reader Simple fixed-size memory manager, which uses reference counting and a list of free blocks. Not properly tested yet, particularly the Cons version (linked list) which is more complicated than the Array type. --- nasm/reader.asm | 24 ++ nasm/step1_read_print.asm | 553 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 577 insertions(+) create mode 100644 nasm/reader.asm create mode 100644 nasm/step1_read_print.asm diff --git a/nasm/reader.asm b/nasm/reader.asm new file mode 100644 index 0000000000..e78226de02 --- /dev/null +++ b/nasm/reader.asm @@ -0,0 +1,24 @@ + +section .bss + +;; State of Reader + + +section .text + +read_str: + ; Convert the input string into a list of tokens + call tokenizer + ; RAX now contains address of list of tokens + ret + +;; ----------------------------- +;; list(tokens) tokenizer (string) +;; +;; Input string address in RSI +;; Creates a list of tokens, returns address in RAX +tokenizer: + + ; Get a new + call alloc_array + ret diff --git a/nasm/step1_read_print.asm b/nasm/step1_read_print.asm new file mode 100644 index 0000000000..2b2d6d14f8 --- /dev/null +++ b/nasm/step1_read_print.asm @@ -0,0 +1,553 @@ +;; +;; nasm -felf64 step1_read_print.asm && ld step1_read_print.o && ./a.out + +;; Calling convention: Address of input is in RSI +;; Address of return value is in RAX +;; + +;; Data structures +;; Memory management is done by having two fixed-size datatypes, +;; Cons and Array. +;; +;; Both Cons and Array have the following in common: +;; a type field at the start, a reference count, followed by data +;; [ type (8) | (8) | refs (16) | data ] + + +;; +STRUC Cons +.typecar: RESB 1 ; Type information for car (8 bit) +.typecdr: RESB 1 ; Type information for cdr (8 bits) +.refcount: RESW 1 ; Number of references to this Cons (16 bit) +.car: RESQ 1 ; First value (64 bit) +.cdr: RESQ 1 ; Second value (64 bit) +.size: ; Total size of struc +ENDSTRUC + + +%define array_chunk_len 32 ; Number of 64-bit values which can be stored in a single chunk + +STRUC Array +.type: RESB 1 ; Type information (8 bits) +.control: RESB 1 ; Control data (8 bits) +.refcount: RESW 1 ; Number of references to this Array (16 bit) +.length: RESD 1 ; Number of elements in array (32 bit) +.next RESQ 1 ; Pointer to the next chunk (64 bit) +.data: RESQ array_chunk_len ; Data storage +.size: ; Total size of struc +ENDSTRUC + +;; Type information +%define type_char 1 ; Character type +%define type_integer 2 ; Integer type +%define type_float 3 ; Floating point number +%define type_atom 64 ; 1 if just an atom, not a list or array +%define type_array 128 ; Last bit tests if array or cons + +%include "reader.asm" + + + global _start + +section .data + +;str: ISTRUC Array +;AT Array.type, db type_char + type_array +;AT Array.length, dd 6 +;AT Array.data, db 'hello',10 +;IEND + +;; ------------------------------------------ +;; Fixed strings for printing + +prompt_string: db 10,"user> " ; The string to print at the prompt +.len: equ $ - prompt_string + +error_msg_print_string: db "Error in print string",10 +.len: equ $ - error_msg_print_string + +error_array_memory_limit: db "Error: Run out of memory for Array objects. Increase heap_array_limit.",10 +.len: equ $ - error_array_memory_limit + +error_cons_memory_limit: db "Error: Run out of memory for Cons objects. Increase heap_cons_limit.",10 +.len: equ $ - error_cons_memory_limit + +;; ------------------------------------------ +;; Memory management +;; +;; For each object (Cons or Array), there is a block of memory (in BSS). +;; When an object is requested it is first taken from the free list +;; If the free list is empty (address 0) then the next object in the block +;; is used, and the heap_x_number counter is incremented. When an object +;; is free'd it is pushed onto the heap_x_free list. + + +%define heap_cons_limit 1 ; Number of cons objects which can be created + +heap_cons_next: dd heap_cons_store ; Address of next cons in memory +heap_cons_free: dq 0 ; Address of start of free list + +%define heap_array_limit 1 ; Number of array objects which can be created + +heap_array_next: dd heap_array_store +heap_array_free: dq 0 + +section .bss + +;; Reserve space to store Cons and Array objects +heap_cons_store: resb heap_cons_limit * Cons.size +.end: ; Address of end of the store + +heap_array_store: resb heap_array_limit * Array.size +.end: + +section .text + +;; ------------------------------------------ +;; Array alloc_array() +;; +;; Returns the address of an Array object in RAX +alloc_array: + + ; Get the address of a free array + mov rax, [heap_array_free] ; Address of the array + + ; Check if it's null + cmp rax, 0 + je .create_array + + mov rbx, [rax + Array.next] ; Get the address of the next array in the linked list + mov [heap_array_free], rbx ; Put this address at the front of the list + jmp .initialise_array + +.create_array: + + ; Get the address of the next Array + mov rax, [heap_array_next] + ; Check if we've reached the end + cmp rax, heap_array_store.end + je .out_of_memory + + mov rbx, rax + add rbx, Array.size ; Address of the next array + mov [heap_array_next], rbx ; for next time + +.initialise_array: + ; Address of Array now in rax + mov BYTE [rax + Array.type], type_array + mov WORD [rax + Array.refcount], 1 ; Only one reference + mov DWORD [rax + Array.length], 0 + mov QWORD [rax + Array.next], 0 ; null next address + + ret + +.out_of_memory: + mov rsi, error_array_memory_limit + mov rdx, error_array_memory_limit.len + call print_rawstring + jmp quit_error + + +;; ------------------------------------------- +;; Decrements the reference count of the array in RSI +;; If the count reaches zero then push the array +;; onto the free list +release_array: + mov ax, WORD [rsi + Array.refcount] + dec ax + mov WORD [rsi + Array.refcount], ax + jz .free ; If the count reaches zero then put on free list + ret + +.free: + ; Get the next field + mov rbx, [rsi + Array.next] + + mov rax, [heap_array_free] ; Get the current head + mov [rsi + Array.next], rax ; Put current head into the "next" field + mov [heap_array_free], rsi ; Push Array onto free list + + cmp rbx, 0 + jne .release_next ; If there is another array, then need to release it + + ret + +.release_next: + ; release the next array + mov rsi, rbx + call release_array + ret + +;; ------------------------------------------ +;; Cons alloc_cons() +;; +;; Returns the address of a Cons object in RAX +alloc_cons: + + ; Get the address of a free cons + mov rax, [heap_cons_free] ; Address of the cons + + ; Check if it's null + cmp rax, 0 + je .create_cons + + mov rbx, [rax + Cons.cdr] ; Get the address of the next cons in the linked list + mov [heap_cons_free], rbx ; Put this address at the front of the list + jmp .initialise_cons + +.create_cons: + + ; Get the address of the next Cons + mov rax, [heap_cons_next] + ; Check if we've reached the end + cmp rax, heap_cons_store.end + je .out_of_memory + + mov rbx, rax + add rbx, Cons.size ; Address of the next cons + mov [heap_cons_next], rbx ; for next time + +.initialise_cons: + ; Address of Cons now in rax + mov BYTE [rax + Cons.typecar], 0 + mov BYTE [rax + Cons.typecdr], 0 + mov WORD [rax + Cons.refcount], 1 ; Only one reference + mov QWORD [rax + Cons.car], 0 + mov QWORD [rax + Cons.cdr], 0 + ret + +.out_of_memory: + mov rsi, error_cons_memory_limit + mov rdx, error_cons_memory_limit.len + call print_rawstring + jmp quit_error + + +;; ------------------------------------------- +;; Decrements the reference count of the cons in RSI +;; If the count reaches zero then push the cons +;; onto the free list +release_cons: + mov ax, WORD [rsi + Cons.refcount] + dec ax + mov WORD [rsi + Cons.refcount], ax + jz .free ; If the count reaches zero then put on free list + ret + +.free: + ; Get and push cdr onto stack + mov rcx, [rsi + Cons.cdr] + push rcx + push rsi + + mov rax, [heap_cons_free] ; Get the current head + mov [rsi + Cons.cdr], rax ; Put current head into the "cdr" field + mov [heap_cons_free], rsi ; Push Cons onto free list + + ; Check if the CAR needs to be released + + mov al, BYTE [rsi+Cons.typecar] + mov bl, type_atom + and bl, al ; bl now zero if a list or array + jnz .free_cdr + + ; Get the address stored in CAR + mov rsi, [rsi + Cons.car] + + ; test if type is array or cons + mov bl, type_array + and bl, al ; bl now zero if cons + jnz .car_array + + ; CAR is a Cons + call release_cons + jmp .free_cdr + +.car_array: + ; CAR is an Array + call release_array + +.free_cdr: + pop rcx ; This was rsi, the original Cons + pop rsi ; This was rcx, the original Cons.cdr + + ; Get the type from the original Cons + mov al, BYTE [rcx+Cons.typecdr] + mov bl, type_atom + and bl, al ; bl now zero if a list or array + jnz .done + + ; test if type is array or cons + mov bl, type_array + and bl, al ; bl now zero if cons + jnz .cdr_array + + ; CAR is a Cons + call release_cons + ret + +.cdr_array: + ; CAR is an Array + call release_array +.done: + ret + + +;; ------------------------------------------- +;; Prints a raw string to stdout +;; String address in rsi, string length in rdx +print_rawstring: + push rax + push rdi + + ; write(1, string, length) + mov rax, 1 ; system call 1 is write + mov rdi, 1 ; file handle 1 is stdout + syscall + + pop rdi + pop rax + + ret + +;; ------------------------------------------ +;; void print_string(char array) +;; Address of the char Array should be in RSI +print_string: + ; Push registers we're going to use + push rax + push rdi + push rdx + push rsi + + ; Check that we have a char array + mov al, [rsi] + cmp al, type_char + type_array + jne .error + + ; write(1, string, length) + mov edx, [rsi + Array.length] ; number of bytes + add rsi, Array.data ; address of raw string to output + call print_rawstring + + ; Restore registers + pop rsi + pop rdx + pop rdi + pop rax + + ret +.error: + ; An error occurred + mov rdx, error_msg_print_string.len ; number of bytes + mov rsi, error_msg_print_string ; address of raw string to output + call print_rawstring + ; exit + jmp quit_error + +;; ------------------------------------------ +;; String itostring(Integer number) +;; +;; Converts an integer to a string (array of chars) +;; +;; Input in RAX +;; Return string address in RAX +itostring: + ; Save registers to restore afterwards + push rcx + push rdx + push rsi + push rdi + + mov rcx, 0 ; counter of how many bytes we need to print in the end + +.divideLoop: + inc rcx ; count each byte to print - number of characters + xor rdx, rdx + mov rsi, 10 + idiv rsi ; divide rax by rsi + add rdx, 48 ; convert rdx to it's ascii representation - rdx holds the remainder after a divide instruction + ; Character is now in DL + dec rsp + mov BYTE [rsp], dl ; Put onto stack + + cmp rax, 0 ; can the integer be divided anymore? + jnz .divideLoop ; jump if not zero to the label divideLoop + + ; Get an Array object to put the string into + call alloc_array ; Address in RAX + + ; put length into string + mov [rax + Array.length], ecx + + ; copy data from stack into string + ; Note: Currently this does not handle long strings + mov rdi, rax + add rdi, Array.data ; Address where raw string will go +.copyLoop: + mov BYTE dl, [rsp] ; Copy one byte at a time. Could be more efficient + mov [rdi], BYTE dl + inc rsp + inc rdi + dec rcx + cmp rcx, 0 + jnz .copyLoop + + ; Restore registers + pop rdi + pop rsi + pop rdx + pop rcx + + ret + +;; ---------------------------- +;; int stringtoi(String) +;; +;; Convert a string (char array) to an integer +;; +;; Address of input string is in RSI +;; Output integer in RAX +stringtoi: + + ret + +;------------------------------------------ +; void exit() +; Exit program and restore resources +quit: + mov eax, 60 ; system call 60 is exit + xor rdi, rdi ; exit code 0 + syscall ; invoke operating system to exit + +quit_error: + mov eax, 60 ; system call 60 is exit + mov rdi, 1 ; exit code 1 + syscall + + +;; Takes a string as input and processes it into a form +read: + mov rax, rsi ; Return the input + ret + +;; Evaluates a form +eval: + mov rax, rsi ; Return the input + ret + +;; Prints the result +print: + mov rax, rsi ; Return the input + ret + +;; Read-Eval-Print in sequence +rep_seq: + call read + mov rsi, rax ; Output of read into input of eval + call eval + mov rsi, rax ; Output of eval into input of print + call print + mov rsi, rax ; Return value + ret + +;; Read a line from stdin +;; Gets a new string array, fills it until a newline or EOF is reached +;; Returns pointer to string in RAX +read_line: + ; Get an array to put the string into + ; Address in rax + call alloc_array + ; Mark it as a character array (string) + mov BYTE [rax + Array.type], type_char + type_array + + push rax ; Save pointer to string + + ; Read character by character until either newline or end of input + mov ebx, 0 ; Count how many characters read + mov rsi, rax + add rsi, Array.data ; Point to the data +.readLoop: + mov rax, 0 ; sys_read + mov rdi, 0 ; stdin + mov rdx, 1 ; count + syscall + + ; Characters read in RAX + cmp rax, 0 ; end loop if read <= 0 + jle .readLoopEnd + + mov cl, BYTE [rsi] + + cmp cl, 10 ; End if we read a newline + je .readLoopEnd + + cmp cl, 8 ; Backspace? + je .handleBackspace + + cmp cl, 31 ; Below space + jle .readLoop ; Ignore, keep going + + cmp cl, 127 ; DEL or above + jge .readLoop ; Ignore, keep going + + inc ebx + inc rsi ; Move to next point in the array + jmp .readLoop ; Get another character + +.handleBackspace: + ; Check if we've read any characters + cmp ebx, 0 + je .readLoop ; If not, carry on the loop + ; Characters have been read. Remove one + dec ebx + dec rsi + jmp .readLoop +.readLoopEnd: + pop rax ; Restore pointer to string + mov DWORD [rax + Array.length], ebx ; Set string length + ret + + + +_start: + + ; ----------------------------- + ; Main loop + +.mainLoop: + ; print the prompt + mov rdx, prompt_string.len ; number of bytes + mov rsi, prompt_string ; address of raw string to output + call print_rawstring + + call read_line + + ; Check if we have a zero-length string + cmp DWORD [rax+Array.length], 0 + je .mainLoopEnd + + push rax ; Save address of the string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release the string + pop rsi + call release_array + + jmp .mainLoop +.mainLoopEnd: + + ;mov rdx, 1 + ;mov rsi, + ;call print_rawstring + ;inc rsp + + ;mov rax, 1223 + ;call itostring + ;mov rsi, rax + ;call print_string + + jmp quit + From 7cabea4f759d0e4c47ff3e7e67050c54d156119e Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 10 Oct 2017 18:29:36 +0200 Subject: [PATCH 0520/2308] elisp, chuck, pil, gst, scheme: Add number?, fn?, macro? --- chuck/core.ck | 7 +++++-- chuck/step2_eval.ck | 1 + chuck/step3_env.ck | 1 + chuck/step4_if_fn_do.ck | 2 +- chuck/step5_tco.ck | 2 +- chuck/step6_file.ck | 2 +- chuck/step7_quote.ck | 2 +- chuck/step8_macros.ck | 2 +- chuck/step9_try.ck | 2 +- chuck/types/subr/MalIsFn.ck | 15 +++++++++++++++ chuck/types/subr/MalIsMacro.ck | 14 ++++++++++++++ chuck/types/subr/MalIsNumber.ck | 14 ++++++++++++++ elisp/core.el | 10 ++++++++++ gst/core.st | 9 +++++++++ pil/core.l | 3 +++ scheme/lib/core.sld | 4 ++++ 16 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 chuck/types/subr/MalIsFn.ck create mode 100644 chuck/types/subr/MalIsMacro.ck create mode 100644 chuck/types/subr/MalIsNumber.ck diff --git a/chuck/core.ck b/chuck/core.ck index 7c1eb63388..7f3f21fb8c 100644 --- a/chuck/core.ck +++ b/chuck/core.ck @@ -14,10 +14,10 @@ public class Core "nth", "first", "rest", "throw", "apply", "map", - "nil?", "true?", "false?", "symbol?", "keyword?", "vector?", "map?", + "nil?", "true?", "false?", "number?", "symbol?", "keyword?", "vector?", "map?", "symbol", "keyword", "vector", "hash-map", "assoc", "dissoc", "get", "contains?", "keys", "vals", - "sequential?", + "sequential?", "fn?", "macro?", "readline", "meta", "with-meta", "time-ms", "conj", "string?", "seq"] @=> Core.names; MalSubr ns[0] @=> Core.ns; @@ -67,6 +67,7 @@ new MalMap @=> Core.ns["map"]; new MalIsNil @=> Core.ns["nil?"]; new MalIsTrue @=> Core.ns["true?"]; new MalIsFalse @=> Core.ns["false?"]; +new MalIsNumber @=> Core.ns["number?"]; new MalIsSymbol @=> Core.ns["symbol?"]; new MalIsKeyword @=> Core.ns["keyword?"]; new MalIsVector @=> Core.ns["vector?"]; @@ -85,6 +86,8 @@ new MalKeys @=> Core.ns["keys"]; new MalVals @=> Core.ns["vals"]; new MalSequential @=> Core.ns["sequential?"]; +new MalIsFn @=> Core.ns["fn?"]; +new MalIsMacro @=> Core.ns["macro?"]; new MalReadline @=> Core.ns["readline"]; new MalMeta @=> Core.ns["meta"]; diff --git a/chuck/step2_eval.ck b/chuck/step2_eval.ck index 6795783153..d3753ae8ce 100644 --- a/chuck/step2_eval.ck +++ b/chuck/step2_eval.ck @@ -17,6 +17,7 @@ // @import reader.ck // @import printer.ck // @import env.ck +// @import func.ck // @import types/MalSubr.ck // @import types/subr/*.ck diff --git a/chuck/step3_env.ck b/chuck/step3_env.ck index 2d2b1bae1f..7c0bb7cd31 100644 --- a/chuck/step3_env.ck +++ b/chuck/step3_env.ck @@ -17,6 +17,7 @@ // @import reader.ck // @import printer.ck // @import env.ck +// @import func.ck // @import types/MalSubr.ck // @import types/subr/*.ck diff --git a/chuck/step4_if_fn_do.ck b/chuck/step4_if_fn_do.ck index 09c0ae5667..16a4cd91e5 100644 --- a/chuck/step4_if_fn_do.ck +++ b/chuck/step4_if_fn_do.ck @@ -17,10 +17,10 @@ // @import reader.ck // @import printer.ck // @import env.ck +// @import func.ck // @import types/MalSubr.ck // @import types/subr/*.ck // @import core.ck -// @import func.ck fun MalObject READ(string input) { diff --git a/chuck/step5_tco.ck b/chuck/step5_tco.ck index da2a23008f..cc07f5f780 100644 --- a/chuck/step5_tco.ck +++ b/chuck/step5_tco.ck @@ -17,10 +17,10 @@ // @import reader.ck // @import printer.ck // @import env.ck +// @import func.ck // @import types/MalSubr.ck // @import types/subr/*.ck // @import core.ck -// @import func.ck fun MalObject READ(string input) { diff --git a/chuck/step6_file.ck b/chuck/step6_file.ck index 0102daad5d..f9c5a4b583 100644 --- a/chuck/step6_file.ck +++ b/chuck/step6_file.ck @@ -17,10 +17,10 @@ // @import reader.ck // @import printer.ck // @import env.ck +// @import func.ck // @import types/MalSubr.ck // @import types/subr/*.ck // @import core.ck -// @import func.ck fun MalObject READ(string input) { diff --git a/chuck/step7_quote.ck b/chuck/step7_quote.ck index c89d91d604..d1aef647e7 100644 --- a/chuck/step7_quote.ck +++ b/chuck/step7_quote.ck @@ -17,10 +17,10 @@ // @import reader.ck // @import printer.ck // @import env.ck +// @import func.ck // @import types/MalSubr.ck // @import types/subr/*.ck // @import core.ck -// @import func.ck fun MalObject READ(string input) { diff --git a/chuck/step8_macros.ck b/chuck/step8_macros.ck index a9020ea681..09b3a54ffe 100644 --- a/chuck/step8_macros.ck +++ b/chuck/step8_macros.ck @@ -17,10 +17,10 @@ // @import reader.ck // @import printer.ck // @import env.ck +// @import func.ck // @import types/MalSubr.ck // @import types/subr/*.ck // @import core.ck -// @import func.ck fun MalObject READ(string input) { diff --git a/chuck/step9_try.ck b/chuck/step9_try.ck index a95cb5a935..c040462929 100644 --- a/chuck/step9_try.ck +++ b/chuck/step9_try.ck @@ -17,10 +17,10 @@ // @import reader.ck // @import printer.ck // @import env.ck +// @import func.ck // @import types/MalSubr.ck // @import types/subr/*.ck // @import core.ck -// @import func.ck fun MalObject READ(string input) { diff --git a/chuck/types/subr/MalIsFn.ck b/chuck/types/subr/MalIsFn.ck new file mode 100644 index 0000000000..a3df6131f7 --- /dev/null +++ b/chuck/types/subr/MalIsFn.ck @@ -0,0 +1,15 @@ +public class MalIsFn extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + if( args[0].type == "subr" || ( args[0].type == "func" && + !(args[0]$Func).isMacro ) ) + { + return Constants.TRUE; + } + else + { + return Constants.FALSE; + } + } +} diff --git a/chuck/types/subr/MalIsMacro.ck b/chuck/types/subr/MalIsMacro.ck new file mode 100644 index 0000000000..1ed2fc73a2 --- /dev/null +++ b/chuck/types/subr/MalIsMacro.ck @@ -0,0 +1,14 @@ +public class MalIsMacro extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + if( args[0].type == "func" && (args[0]$Func).isMacro ) + { + return Constants.TRUE; + } + else + { + return Constants.FALSE; + } + } +} diff --git a/chuck/types/subr/MalIsNumber.ck b/chuck/types/subr/MalIsNumber.ck new file mode 100644 index 0000000000..09231ceff8 --- /dev/null +++ b/chuck/types/subr/MalIsNumber.ck @@ -0,0 +1,14 @@ +public class MalIsNumber extends MalSubr +{ + fun MalObject call(MalObject args[]) + { + if( args[0].type == "int" ) + { + return Constants.TRUE; + } + else + { + return Constants.FALSE; + } + } +} diff --git a/elisp/core.el b/elisp/core.el index 28b8e85d12..a9ecbd4315 100644 --- a/elisp/core.el +++ b/elisp/core.el @@ -177,6 +177,7 @@ (true? . ,(mal-fn (lambda (arg) (if (mal-true-p arg) mal-true mal-false)))) (false? . ,(mal-fn (lambda (arg) (if (mal-false-p arg) mal-true mal-false)))) + (number? . ,(mal-fn (lambda (arg) (if (mal-number-p arg) mal-true mal-false)))) (symbol? . ,(mal-fn (lambda (arg) (if (mal-symbol-p arg) mal-true mal-false)))) (keyword? . ,(mal-fn (lambda (arg) (if (mal-keyword-p arg) mal-true mal-false)))) (string? . ,(mal-fn (lambda (arg) (if (mal-string-p arg) mal-true mal-false)))) @@ -193,6 +194,15 @@ (mal-map map))))) (sequential? . ,(mal-fn 'mal-seq-p)) + (fn? . ,(mal-fn (lambda (arg) (if (or (mal-fn-p arg) + (and (mal-func-p arg) + (not (mal-func-macro-p arg)))) + mal-true + mal-false)))) + (macro? . ,(mal-fn (lambda (arg) (if (and (mal-func-p arg) + (mal-func-macro-p arg)) + mal-true + mal-false)))) (get . ,(mal-fn (lambda (map key) (if (mal-map-p map) (or (gethash key (mal-value map)) mal-nil) mal-nil)))) (contains? . ,(mal-fn (lambda (map key) (if (gethash key (mal-value map)) mal-true mal-false)))) diff --git a/gst/core.st b/gst/core.st index d1c52de0ce..bb85438c80 100644 --- a/gst/core.st +++ b/gst/core.st @@ -195,6 +195,8 @@ Core Ns at: #'true?' put: (Fn new: [ :args | Core coerce: [ args first type = #true ] ]). Core Ns at: #'false?' put: (Fn new: [ :args | Core coerce: [ args first type = #false ] ]). +Core Ns at: #'number?' put: + (Fn new: [ :args | Core coerce: [ args first type = #number ] ]). Core Ns at: #'symbol?' put: (Fn new: [ :args | Core coerce: [ args first type = #symbol ] ]). Core Ns at: #'keyword?' put: @@ -208,6 +210,13 @@ Core Ns at: #'map?' put: Core Ns at: #'sequential?' put: (Fn new: [ :args | Core coerce: [ args first type = #list or: [ args first type = #vector ] ] ]). +Core Ns at: #'fn?' put: + (Fn new: [ :args | Core coerce: [ args first type = #fn or: + [ args first type = #func and: + [ args first isMacro not ] ] ] ]). +Core Ns at: #'macro?' put: + (Fn new: [ :args | Core coerce: [ args first type = #func and: + [ args first isMacro ] ] ]). Core Ns at: #symbol put: (Fn new: [ :args | MALSymbol new: args first value asSymbol ]). diff --git a/pil/core.l b/pil/core.l index 5a22963b2c..0ef572c948 100644 --- a/pil/core.l +++ b/pil/core.l @@ -146,12 +146,15 @@ (nil? . `(MAL-fn '((X) (if (= (MAL-type X) 'nil) *MAL-true *MAL-false)))) (true? . `(MAL-fn '((X) (if (= (MAL-type X) 'true) *MAL-true *MAL-false)))) (false? . `(MAL-fn '((X) (if (= (MAL-type X) 'false) *MAL-true *MAL-false)))) + (number? . `(MAL-fn '((X) (if (= (MAL-type X) 'number) *MAL-true *MAL-false)))) (symbol? . `(MAL-fn '((X) (if (= (MAL-type X) 'symbol) *MAL-true *MAL-false)))) (keyword? . `(MAL-fn '((X) (if (= (MAL-type X) 'keyword) *MAL-true *MAL-false)))) (string? . `(MAL-fn '((X) (if (= (MAL-type X) 'string) *MAL-true *MAL-false)))) (vector? . `(MAL-fn '((X) (if (= (MAL-type X) 'vector) *MAL-true *MAL-false)))) (map? . `(MAL-fn '((X) (if (= (MAL-type X) 'map) *MAL-true *MAL-false)))) (sequential? . `(MAL-fn '((X) (if (MAL-seq? X) *MAL-true *MAL-false)))) + (fn? . `(MAL-fn '((X) (if (or (= (MAL-type X) 'fn) (and (= (MAL-type X) 'func) (not (get X 'is-macro)))) *MAL-true *MAL-false)))) + (macro? . `(MAL-fn '((X) (if (and (= (MAL-type X) 'func) (get X 'is-macro)) *MAL-true *MAL-false)))) (symbol . `(MAL-fn '((Name) (MAL-symbol (MAL-value Name))))) (keyword . `(MAL-fn '((X) (if (= (MAL-type X) 'keyword) X (MAL-keyword (MAL-value X)))))) diff --git a/scheme/lib/core.sld b/scheme/lib/core.sld index b6cfbe33fc..4d615b8d0e 100644 --- a/scheme/lib/core.sld +++ b/scheme/lib/core.sld @@ -244,6 +244,7 @@ (nil? . ,(lambda (x) (coerce (eq? x mal-nil)))) (true? . ,(lambda (x) (coerce (eq? x mal-true)))) (false? . ,(lambda (x) (coerce (eq? x mal-false)))) + (number? . ,(lambda (x) (coerce (mal-instance-of? x 'number)))) (string? . ,(lambda (x) (coerce (mal-instance-of? x 'string)))) (symbol? . ,(lambda (x) (coerce (mal-instance-of? x 'symbol)))) (symbol . ,(lambda (x) (mal-symbol (string->symbol (mal-value x))))) @@ -256,6 +257,9 @@ (sequential? . ,(lambda (x) (coerce (and (mal-object? x) (memq (mal-type x) '(list vector)))))) + (fn? . ,(lambda (x) (coerce (or (procedure? x) + (and (func? x) (not (func-macro? x))))))) + (macro? . ,(lambda (x) (coerce (and (func? x) (func-macro? x))))) (assoc . ,(lambda (m . kvs) (mal-map (mal-map-assoc (mal-value m) kvs)))) (dissoc . ,(lambda (m . keys) (mal-map (mal-map-dissoc (mal-value m) keys)))) From 9e1b17522935b7e58a6762729e6fa1fd4ae3ac00 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Wed, 11 Oct 2017 10:28:04 +0200 Subject: [PATCH 0521/2308] js, lua, perl, python, racket: Add number?, fn?, macro? --- js/core.js | 3 +++ js/types.js | 6 ++++++ lua/core.lua | 3 +++ lua/types.lua | 11 +++++++++++ perl/core.pm | 5 ++++- perl/types.pm | 6 +++++- python/core.py | 5 +++++ python/mal_types.py | 4 +++- racket/core.rkt | 5 +++++ 9 files changed, 45 insertions(+), 3 deletions(-) diff --git a/js/core.js b/js/core.js index 01dad97c50..9eb3ca43ca 100644 --- a/js/core.js +++ b/js/core.js @@ -194,11 +194,14 @@ var ns = {'type': types._obj_type, 'nil?': types._nil_Q, 'true?': types._true_Q, 'false?': types._false_Q, + 'number?': types._number_Q, 'string?': types._string_Q, 'symbol': types._symbol, 'symbol?': types._symbol_Q, 'keyword': types._keyword, 'keyword?': types._keyword_Q, + 'fn?': types._fn_Q, + 'macro?': types._macro_Q, 'pr-str': pr_str, 'str': str, diff --git a/js/types.js b/js/types.js index 6e7f196b05..0fb324e711 100644 --- a/js/types.js +++ b/js/types.js @@ -88,6 +88,7 @@ function _clone (obj) { function _nil_Q(a) { return a === null ? true : false; } function _true_Q(a) { return a === true ? true : false; } function _false_Q(a) { return a === false ? true : false; } +function _number_Q(obj) { return typeof obj === 'number'; } function _string_Q(obj) { return typeof obj === 'string' && obj[0] !== '\u029e'; } @@ -136,6 +137,8 @@ Function.prototype.clone = function() { } return temp; }; +function _fn_Q(obj) { return _function_Q(obj) && !obj._ismacro_; } +function _macro_Q(obj) { return _function_Q(obj) && !!obj._ismacro_; } // Lists @@ -205,6 +208,7 @@ exports._clone = types._clone = _clone; exports._nil_Q = types._nil_Q = _nil_Q; exports._true_Q = types._true_Q = _true_Q; exports._false_Q = types._false_Q = _false_Q; +exports._number_Q = types._number_Q = _number_Q; exports._string_Q = types._string_Q = _string_Q; exports._symbol = types._symbol = _symbol; exports._symbol_Q = types._symbol_Q = _symbol_Q; @@ -212,6 +216,8 @@ exports._keyword = types._keyword = _keyword; exports._keyword_Q = types._keyword_Q = _keyword_Q; exports._function = types._function = _function; exports._function_Q = types._function_Q = _function_Q; +exports._fn_Q = types._fn_Q = _fn_Q; +exports._macro_Q = types._macro_Q = _macro_Q; exports._list = types._list = _list; exports._list_Q = types._list_Q = _list_Q; exports._vector = types._vector = _vector; diff --git a/lua/core.lua b/lua/core.lua index 3376b09dce..8c8d2c54ec 100644 --- a/lua/core.lua +++ b/lua/core.lua @@ -240,11 +240,14 @@ M.ns = { ['nil?'] = function(a) return a==Nil end, ['true?'] = function(a) return a==true end, ['false?'] = function(a) return a==false end, + ['number?'] = function(a) return types._number_Q(a) end, symbol = function(a) return types.Symbol:new(a) end, ['symbol?'] = function(a) return types._symbol_Q(a) end, ['string?'] = function(a) return types._string_Q(a) and "\177" ~= string.sub(a,1,1) end, keyword = function(a) return "\177"..a end, ['keyword?'] = function(a) return types._keyword_Q(a) end, + ['fn?'] = function(a) return types._fn_Q(a) end, + ['macro?'] = function(a) return types._macro_Q(a) end, ['pr-str'] = pr_str, str = str, diff --git a/lua/types.lua b/lua/types.lua index 0155451247..62bc6a2fa9 100644 --- a/lua/types.lua +++ b/lua/types.lua @@ -94,6 +94,11 @@ function M._nil_Q(obj) return obj == Nil end +-- Numbers +function M._number_Q(obj) + return type(obj) == "number" +end + -- Strings function M._string_Q(obj) return type(obj) == "string" @@ -186,6 +191,12 @@ end function M._malfunc_Q(obj) return utils.instanceOf(obj, M.MalFunc) end +function M._fn_Q(obj) + return type(obj) == "function" or (M._malfunc_Q(obj) and not obj.ismacro) +end +function M._macro_Q(obj) + return M._malfunc_Q(obj) and obj.ismacro +end -- Atoms diff --git a/perl/core.pm b/perl/core.pm index 7810ab38ab..8c51ae2342 100644 --- a/perl/core.pm +++ b/perl/core.pm @@ -8,7 +8,7 @@ use Time::HiRes qw(time); use readline; use types qw(_sequential_Q _equal_Q _clone $nil $true $false _nil_Q _true_Q _false_Q - _symbol _symbol_Q _string_Q _keyword _keyword_Q _list_Q _vector_Q + _number_Q _symbol _symbol_Q _string_Q _keyword _keyword_Q _list_Q _vector_Q _sub_Q _function_Q _hash_map _hash_map_Q _assoc_BANG _dissoc_BANG _atom_Q); use reader qw(read_str); use printer qw(_pr_str); @@ -217,11 +217,14 @@ our $core_ns = { 'nil?' => sub { _nil_Q($_[0]->nth(0)) ? $true : $false }, 'true?' => sub { _true_Q($_[0]->nth(0)) ? $true : $false }, 'false?' => sub { _false_Q($_[0]->nth(0)) ? $true : $false }, + 'number?' => sub { _number_Q($_[0]->nth(0)) ? $true : $false }, 'symbol' => sub { Symbol->new(${$_[0]->nth(0)}) }, 'symbol?' => sub { _symbol_Q($_[0]->nth(0)) ? $true : $false }, 'string?' => sub { _string_Q($_[0]->nth(0)) ? $true : $false }, 'keyword' => sub { _keyword(${$_[0]->nth(0)}) }, 'keyword?' => sub { _keyword_Q($_[0]->nth(0)) ? $true : $false }, + 'fn?' => sub { (_sub_Q($_[0]->nth(0)) || (_function_Q($_[0]->nth(0)) && !$_[0]->nth(0)->{ismacro})) ? $true : $false }, + 'macro?' => sub { (_function_Q($_[0]->nth(0)) && $_[0]->nth(0)->{ismacro}) ? $true : $false }, 'pr-str' => sub { pr_str($_[0]) }, 'str' => sub { str($_[0]) }, diff --git a/perl/types.pm b/perl/types.pm index 4e80f3f495..4eba2b3e22 100644 --- a/perl/types.pm +++ b/perl/types.pm @@ -6,7 +6,7 @@ use feature qw(switch); use Exporter 'import'; our @EXPORT_OK = qw(_sequential_Q _equal_Q _clone $nil $true $false _nil_Q _true_Q _false_Q - _symbol _symbol_Q _string_Q _keyword _keyword_Q _list_Q _vector_Q + _number_Q _symbol _symbol_Q _string_Q _keyword _keyword_Q _list_Q _vector_Q _sub_Q _function_Q _hash_map _hash_map_Q _assoc_BANG _dissoc_BANG _atom_Q); use Data::Dumper; @@ -103,6 +103,7 @@ sub _false_Q { return $_[0] eq $false } package Integer; sub new { my $class = shift; bless \do { my $x=$_[0] }, $class } } +sub _number_Q { (ref $_[0]) =~ /^Integer/ } { @@ -213,6 +214,9 @@ sub _hash_map_Q { (ref $_[0]) =~ /^HashMap/ } } } +sub _sub_Q { (ref $_[0]) =~ /^CODE/ } +sub _function_Q { (ref $_[0]) =~ /^Function/ } + # FunctionRef diff --git a/python/core.py b/python/core.py index 5e1e963163..86a7ec8eb0 100644 --- a/python/core.py +++ b/python/core.py @@ -128,11 +128,16 @@ def swap_BANG(atm,f,*args): 'nil?': types._nil_Q, 'true?': types._true_Q, 'false?': types._false_Q, + 'number?': types._number_Q, 'string?': types._string_Q, 'symbol': types._symbol, 'symbol?': types._symbol_Q, 'keyword': types._keyword, 'keyword?': types._keyword_Q, + 'fn?': lambda x: (types._function_Q(x) and not hasattr(x, '_ismacro_')), + 'macro?': lambda x: (types._function_Q(x) and + hasattr(x, '_ismacro_') and + x._ismacro_), 'pr-str': pr_str, 'str': do_str, diff --git a/python/mal_types.py b/python/mal_types.py index 7c5e30c183..32c42e1d69 100644 --- a/python/mal_types.py +++ b/python/mal_types.py @@ -68,6 +68,7 @@ def _string_Q(exp): return len(exp) == 0 or exp[0] != _u("\u029e") else: return False +def _number_Q(exp): return type(exp) == int # Symbols class Symbol(str): pass @@ -93,7 +94,8 @@ def fn(*args): fn.__ast__ = ast fn.__gen_env__ = lambda args: Env(env, params, args) return fn -def _function_Q(f): return type(f) == type(function_Q) +def _function_Q(f): + return callable(f) # lists class List(list): diff --git a/racket/core.rkt b/racket/core.rkt index 2602f98169..e7cc53d5a7 100644 --- a/racket/core.rkt +++ b/racket/core.rkt @@ -57,11 +57,16 @@ 'nil? _nil? 'true? (lambda (x) (eq? x #t)) 'false? (lambda (x) (eq? x #f)) + 'number? number? 'symbol (lambda (s) (if (symbol? s) s (string->symbol s))) 'symbol? symbol? 'string? _string? 'keyword (lambda (s) (if (_keyword? s) s (_keyword s))) 'keyword? _keyword? + 'fn? (lambda (s) (if (malfunc? s) + (not (malfunc-macro? s)) + (procedure? s))) + 'macro? (lambda (s) (and (malfunc? s) (malfunc-macro? s))) 'pr-str (lambda a (pr_lst a #t " ")) 'str (lambda a (pr_lst a #f "")) From 5bbc7a1fb89d03467ab53b3810ca2ba501798da4 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 11 Oct 2017 15:57:07 -0400 Subject: [PATCH 0522/2308] bash, basic: add number?, fn?, and macro? --- bash/core.sh | 10 ++- bash/step2_eval.sh | 3 +- bash/step3_env.sh | 5 +- bash/step4_if_fn_do.sh | 7 +- bash/step5_tco.sh | 7 +- bash/step6_file.sh | 7 +- bash/step7_quote.sh | 7 +- bash/step8_macros.sh | 4 +- bash/step9_try.sh | 6 +- bash/stepA_mal.sh | 8 +- basic/core.in.bas | 171 ++++++++++++++++++++++------------------- 11 files changed, 131 insertions(+), 104 deletions(-) diff --git a/bash/core.sh b/bash/core.sh index 99cc98c8ff..154592ff22 100644 --- a/bash/core.sh +++ b/bash/core.sh @@ -92,14 +92,14 @@ prn () { local res="" for x in "${@}"; do _pr_str "${x}" yes; res="${res} ${r}"; done echo "${res:1}" - r="${__nil}"; + r="${__nil}"; } println () { local res="" for x in "${@}"; do _pr_str "${x}"; res="${res} ${r}"; done echo "${res:1}" - r="${__nil}"; + r="${__nil}"; } readline () { @@ -119,7 +119,8 @@ slurp () { # Function functions -function? () { _function? "${1}" && r="${__true}" || r="${__false}"; } +function? () { _function? "${1}" && [ -z "${ANON["${1}_ismacro_"]}" ] && r="${__true}" || r="${__false}"; } +macro? () { _function? "${1}" && [ "${ANON["${1}_ismacro_"]}" ] && r="${__true}" || r="${__false}"; } # List functions @@ -364,6 +365,9 @@ declare -A core_ns=( [symbol?]=symbol? [keyword]=keyword [keyword?]=keyword? + [number?]=number? + [fn?]=function? + [macro?]=macro? [pr-str]=pr_str [str]=str diff --git a/bash/step2_eval.sh b/bash/step2_eval.sh index de8054fb11..15083b937e 100755 --- a/bash/step2_eval.sh +++ b/bash/step2_eval.sh @@ -48,9 +48,10 @@ EVAL () { EVAL_AST "${ast}" "${env}" return fi - _empty? "${ast}" && r="${ast}" && return # apply list + _empty? "${ast}" && r="${ast}" && return + EVAL_AST "${ast}" "${env}" [[ "${__ERROR}" ]] && return 1 local el="${r}" diff --git a/bash/step3_env.sh b/bash/step3_env.sh index 850ec0354b..2200c8362c 100755 --- a/bash/step3_env.sh +++ b/bash/step3_env.sh @@ -48,9 +48,10 @@ EVAL () { EVAL_AST "${ast}" "${env}" return fi - _empty? "${ast}" && r="${ast}" && return # apply list + _empty? "${ast}" && r="${ast}" && return + _nth "${ast}" 0; local a0="${r}" _nth "${ast}" 1; local a1="${r}" _nth "${ast}" 2; local a2="${r}" @@ -59,7 +60,7 @@ EVAL () { [[ "${__ERROR}" ]] && return 1 ENV_SET "${env}" "${a1}" "${r}" return ;; - let*) ENV "${env}"; local let_env="${r}" + let__STAR__) ENV "${env}"; local let_env="${r}" local let_pairs=(${ANON["${a1}"]}) local idx=0 #echo "let: [${let_pairs[*]}] for ${a2}" diff --git a/bash/step4_if_fn_do.sh b/bash/step4_if_fn_do.sh index 16e745f75a..e701e9fb25 100755 --- a/bash/step4_if_fn_do.sh +++ b/bash/step4_if_fn_do.sh @@ -49,9 +49,10 @@ EVAL () { EVAL_AST "${ast}" "${env}" return fi - _empty? "${ast}" && r="${ast}" && return # apply list + _empty? "${ast}" && r="${ast}" && return + _nth "${ast}" 0; local a0="${r}" _nth "${ast}" 1; local a1="${r}" _nth "${ast}" 2; local a2="${r}" @@ -60,7 +61,7 @@ EVAL () { [[ "${__ERROR}" ]] && return 1 ENV_SET "${env}" "${a1}" "${r}" return ;; - let*) ENV "${env}"; local let_env="${r}" + let__STAR__) ENV "${env}"; local let_env="${r}" local let_pairs=(${ANON["${a1}"]}) local idx=0 #echo "let: [${let_pairs[*]}] for ${a2}" @@ -91,7 +92,7 @@ EVAL () { EVAL "${a2}" "${env}" fi return ;; - fn*) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ + fn__STAR__) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ EVAL \"${a2}\" \"\${r}\"" return ;; *) EVAL_AST "${ast}" "${env}" diff --git a/bash/step5_tco.sh b/bash/step5_tco.sh index fa5c7b8b68..e7eda09a7b 100755 --- a/bash/step5_tco.sh +++ b/bash/step5_tco.sh @@ -50,9 +50,10 @@ EVAL () { EVAL_AST "${ast}" "${env}" return fi - _empty? "${ast}" && r="${ast}" && return # apply list + _empty? "${ast}" && r="${ast}" && return + _nth "${ast}" 0; local a0="${r}" _nth "${ast}" 1; local a1="${r}" _nth "${ast}" 2; local a2="${r}" @@ -61,7 +62,7 @@ EVAL () { [[ "${__ERROR}" ]] && return 1 ENV_SET "${env}" "${a1}" "${r}" return ;; - let*) ENV "${env}"; local let_env="${r}" + let__STAR__) ENV "${env}"; local let_env="${r}" local let_pairs=(${ANON["${a1}"]}) local idx=0 #echo "let: [${let_pairs[*]}] for ${a2}" @@ -99,7 +100,7 @@ EVAL () { fi # Continue loop ;; - fn*) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ + fn__STAR__) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ EVAL \"${a2}\" \"\${r}\"" \ "${a2}" "${env}" "${a1}" return ;; diff --git a/bash/step6_file.sh b/bash/step6_file.sh index 1fe855b326..d1e7f11854 100755 --- a/bash/step6_file.sh +++ b/bash/step6_file.sh @@ -50,9 +50,10 @@ EVAL () { EVAL_AST "${ast}" "${env}" return fi - _empty? "${ast}" && r="${ast}" && return # apply list + _empty? "${ast}" && r="${ast}" && return + _nth "${ast}" 0; local a0="${r}" _nth "${ast}" 1; local a1="${r}" _nth "${ast}" 2; local a2="${r}" @@ -61,7 +62,7 @@ EVAL () { [[ "${__ERROR}" ]] && return 1 ENV_SET "${env}" "${a1}" "${r}" return ;; - let*) ENV "${env}"; local let_env="${r}" + let__STAR__) ENV "${env}"; local let_env="${r}" local let_pairs=(${ANON["${a1}"]}) local idx=0 #echo "let: [${let_pairs[*]}] for ${a2}" @@ -99,7 +100,7 @@ EVAL () { fi # Continue loop ;; - fn*) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ + fn__STAR__) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ EVAL \"${a2}\" \"\${r}\"" \ "${a2}" "${env}" "${a1}" return ;; diff --git a/bash/step7_quote.sh b/bash/step7_quote.sh index dc7401c028..c90074c1c0 100755 --- a/bash/step7_quote.sh +++ b/bash/step7_quote.sh @@ -87,9 +87,10 @@ EVAL () { EVAL_AST "${ast}" "${env}" return fi - _empty? "${ast}" && r="${ast}" && return # apply list + _empty? "${ast}" && r="${ast}" && return + _nth "${ast}" 0; local a0="${r}" _nth "${ast}" 1; local a1="${r}" _nth "${ast}" 2; local a2="${r}" @@ -98,7 +99,7 @@ EVAL () { [[ "${__ERROR}" ]] && return 1 ENV_SET "${env}" "${a1}" "${r}" return ;; - let*) ENV "${env}"; local let_env="${r}" + let__STAR__) ENV "${env}"; local let_env="${r}" local let_pairs=(${ANON["${a1}"]}) local idx=0 #echo "let: [${let_pairs[*]}] for ${a2}" @@ -144,7 +145,7 @@ EVAL () { fi # Continue loop ;; - fn*) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ + fn__STAR__) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ EVAL \"${a2}\" \"\${r}\"" \ "${a2}" "${env}" "${a1}" return ;; diff --git a/bash/step8_macros.sh b/bash/step8_macros.sh index 3675684c25..d86cdd64cf 100755 --- a/bash/step8_macros.sh +++ b/bash/step8_macros.sh @@ -132,7 +132,7 @@ EVAL () { [[ "${__ERROR}" ]] && return 1 ENV_SET "${env}" "${a1}" "${r}" return ;; - let*) ENV "${env}"; local let_env="${r}" + let__STAR__) ENV "${env}"; local let_env="${r}" local let_pairs=(${ANON["${a1}"]}) local idx=0 #echo "let: [${let_pairs[*]}] for ${a2}" @@ -187,7 +187,7 @@ EVAL () { fi # Continue loop ;; - fn*) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ + fn__STAR__) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ EVAL \"${a2}\" \"\${r}\"" \ "${a2}" "${env}" "${a1}" return ;; diff --git a/bash/step9_try.sh b/bash/step9_try.sh index 01efd85801..7567050a01 100755 --- a/bash/step9_try.sh +++ b/bash/step9_try.sh @@ -132,7 +132,7 @@ EVAL () { [[ "${__ERROR}" ]] && return 1 ENV_SET "${env}" "${a1}" "${r}" return ;; - let*) ENV "${env}"; local let_env="${r}" + let__STAR__) ENV "${env}"; local let_env="${r}" local let_pairs=(${ANON["${a1}"]}) local idx=0 #echo "let: [${let_pairs[*]}] for ${a2}" @@ -162,7 +162,7 @@ EVAL () { macroexpand) MACROEXPAND "${a1}" "${env}" return ;; - try*) EVAL "${a1}" "${env}" + try__STAR__) EVAL "${a1}" "${env}" [[ -z "${__ERROR}" ]] && return _nth "${a2}" 0; local a20="${r}" if [ "${ANON["${a20}"]}" == "catch__STAR__" ]; then @@ -200,7 +200,7 @@ EVAL () { fi # Continue loop ;; - fn*) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ + fn__STAR__) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ EVAL \"${a2}\" \"\${r}\"" \ "${a2}" "${env}" "${a1}" return ;; diff --git a/bash/stepA_mal.sh b/bash/stepA_mal.sh index fecaef61c2..f7f3e2f0cc 100755 --- a/bash/stepA_mal.sh +++ b/bash/stepA_mal.sh @@ -132,7 +132,7 @@ EVAL () { [[ "${__ERROR}" ]] && return 1 ENV_SET "${env}" "${a1}" "${r}" return ;; - let*) ENV "${env}"; local let_env="${r}" + let__STAR__) ENV "${env}"; local let_env="${r}" local let_pairs=(${ANON["${a1}"]}) local idx=0 #echo "let: [${let_pairs[*]}] for ${a2}" @@ -162,7 +162,7 @@ EVAL () { macroexpand) MACROEXPAND "${a1}" "${env}" return ;; - sh*) EVAL "${a1}" "${env}" + sh__STAR__) EVAL "${a1}" "${env}" local output="" local line="" while read line; do @@ -170,7 +170,7 @@ EVAL () { done < <(eval ${ANON["${r}"]}) _string "${output%\\n}" return ;; - try*) EVAL "${a1}" "${env}" + try__STAR__) EVAL "${a1}" "${env}" [[ -z "${__ERROR}" ]] && return _nth "${a2}" 0; local a20="${r}" if [ "${ANON["${a20}"]}" == "catch__STAR__" ]; then @@ -208,7 +208,7 @@ EVAL () { fi # Continue loop ;; - fn*) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ + fn__STAR__) _function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ EVAL \"${a2}\" \"\${r}\"" \ "${a2}" "${env}" "${a1}" return ;; diff --git a/basic/core.in.bas b/basic/core.in.bas index d8b7a5025a..009d81ab7f 100644 --- a/basic/core.in.bas +++ b/basic/core.in.bas @@ -14,9 +14,9 @@ SUB APPLY APPLY_FUNCTION: REM regular function - IF Z%(F+1)<60 THEN GOSUB DO_FUNCTION:GOTO APPLY_DONE + IF Z%(F+1)<64 THEN GOSUB DO_FUNCTION:GOTO APPLY_DONE REM for recur functions (apply, map, swap!), use GOTO - IF Z%(F+1)>60 THEN CALL DO_TCO_FUNCTION + IF Z%(F+1)>64 THEN CALL DO_TCO_FUNCTION GOTO APPLY_DONE APPLY_MAL_FUNCTION: @@ -45,7 +45,7 @@ SUB DO_TCO_FUNCTION B=Z%(Z%(AR+1)+2) REM PRINT "F:"+STR$(F)+", Z%(F):"+STR$(Z%(F))+", G:"+STR$(G) - ON G-60 GOTO DO_APPLY,DO_MAP,DO_SWAP_BANG + ON G-64 GOTO DO_APPLY,DO_MAP,DO_SWAP_BANG DO_APPLY: F=A @@ -174,20 +174,22 @@ DO_FUNCTION: REM Switch on the function number REM MEMORY DEBUGGING: REM IF G>59 THEN ER=-1:E$="unknown function"+STR$(G):RETURN - ON INT(G/10)+1 GOTO DO_1_9,DO_10_19,DO_20_29,DO_30_39,DO_40_49,DO_50_59 + ON INT(G/10)+1 GOTO DO_1_9,DO_10_19,DO_20_29,DO_30_39,DO_40_49,DO_50_59,DO_60_69 DO_1_9: ON G GOTO DO_EQUAL_Q,DO_THROW,DO_NIL_Q,DO_TRUE_Q,DO_FALSE_Q,DO_STRING_Q,DO_SYMBOL,DO_SYMBOL_Q,DO_KEYWORD DO_10_19: - ON G-9 GOTO DO_KEYWORD_Q,DO_PR_STR,DO_STR,DO_PRN,DO_PRINTLN,DO_READ_STRING,DO_READLINE,DO_SLURP,DO_LT,DO_LTE + ON G-9 GOTO DO_KEYWORD_Q,DO_NUMBER_Q,DO_FN_Q,DO_MACRO_Q,DO_PR_STR,DO_STR,DO_PRN,DO_PRINTLN,DO_READ_STRING,DO_READLINE DO_20_29: - ON G-19 GOTO DO_GT,DO_GTE,DO_ADD,DO_SUB,DO_MULT,DO_DIV,DO_TIME_MS,DO_LIST,DO_LIST_Q,DO_VECTOR + ON G-19 GOTO DO_SLURP,DO_LT,DO_LTE,DO_GT,DO_GTE,DO_ADD,DO_SUB,DO_MULT,DO_DIV,DO_TIME_MS DO_30_39: - ON G-29 GOTO DO_VECTOR_Q,DO_HASH_MAP,DO_MAP_Q,DO_ASSOC,DO_THROW,DO_GET,DO_CONTAINS,DO_KEYS,DO_VALS,DO_SEQUENTIAL_Q + ON G-29 GOTO DO_LIST,DO_LIST_Q,DO_VECTOR,DO_VECTOR_Q,DO_HASH_MAP,DO_MAP_Q,DO_ASSOC,DO_THROW,DO_GET,DO_CONTAINS DO_40_49: - ON G-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_CONJ,DO_SEQ,DO_WITH_META + ON G-39 GOTO DO_KEYS,DO_VALS,DO_SEQUENTIAL_Q,DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT DO_50_59: - ON G-49 GOTO DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_EVAL,DO_READ_FILE,DO_PR_MEMORY_SUMMARY + ON G-49 GOTO DO_CONJ,DO_SEQ,DO_WITH_META,DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_EVAL,DO_READ_FILE + DO_60_69: + ON G-59 GOTO DO_PR_MEMORY_SUMMARY DO_EQUAL_Q: GOSUB EQUAL_Q @@ -233,6 +235,18 @@ DO_FUNCTION: IF MID$(S$(A1),1,1)<>CHR$(127) THEN GOTO RETURN_TRUE_FALSE R=1 GOTO RETURN_TRUE_FALSE + DO_NUMBER_Q: + GOSUB TYPE_A + R=T=2 + GOTO RETURN_TRUE_FALSE + DO_FN_Q: + GOSUB TYPE_A + R=T=9 OR T=10 + GOTO RETURN_TRUE_FALSE + DO_MACRO_Q: + GOSUB TYPE_A + R=T=11 + GOTO RETURN_TRUE_FALSE DO_PR_STR: AZ=AR:B=1:B$=" ":GOSUB PR_STR_SEQ @@ -512,16 +526,6 @@ DO_FUNCTION: Z%(A+1)=R RETURN - REM DO_PR_MEMORY: - REM P1=ZT:P2=-1:GOSUB PR_MEMORY - REM RETURN - DO_PR_MEMORY_SUMMARY: - REM GOSUB PR_MEMORY_SUMMARY - GOSUB PR_MEMORY_SUMMARY_SMALL - R=0 - GOTO INC_REF_R - RETURN - DO_EVAL: Q=E:GOSUB PUSH_Q: REM push/save environment E=D:CALL EVAL @@ -533,6 +537,16 @@ DO_FUNCTION: GOSUB READ_FILE RETURN + REM DO_PR_MEMORY: + REM P1=ZT:P2=-1:GOSUB PR_MEMORY + REM RETURN + DO_PR_MEMORY_SUMMARY: + REM GOSUB PR_MEMORY_SUMMARY + GOSUB PR_MEMORY_SUMMARY_SMALL + R=0 + GOTO INC_REF_R + RETURN + INIT_CORE_SET_FUNCTION: T=9:L=A:GOSUB ALLOC: REM native function C=R:GOSUB ENV_SET_S @@ -555,65 +569,68 @@ INIT_CORE_NS: B$="symbol?":GOSUB INIT_CORE_SET_FUNCTION: REM A=8 B$="keyword":GOSUB INIT_CORE_SET_FUNCTION: REM A=9 B$="keyword?":GOSUB INIT_CORE_SET_FUNCTION: REM A=10 - - B$="pr-str":GOSUB INIT_CORE_SET_FUNCTION: REM A=11 - B$="str":GOSUB INIT_CORE_SET_FUNCTION: REM A=12 - B$="prn":GOSUB INIT_CORE_SET_FUNCTION: REM A=13 - B$="println":GOSUB INIT_CORE_SET_FUNCTION: REM A=14 - B$="read-string":GOSUB INIT_CORE_SET_FUNCTION: REM A=15 - B$="readline":GOSUB INIT_CORE_SET_FUNCTION: REM A=16 - B$="slurp":GOSUB INIT_CORE_SET_FUNCTION: REM A=17 - - B$="<":GOSUB INIT_CORE_SET_FUNCTION: REM A=18 - B$="<=":GOSUB INIT_CORE_SET_FUNCTION: REM A=19 - B$=">":GOSUB INIT_CORE_SET_FUNCTION: REM A=20 - B$=">=":GOSUB INIT_CORE_SET_FUNCTION: REM A=21 - B$="+":GOSUB INIT_CORE_SET_FUNCTION: REM A=22 - B$="-":GOSUB INIT_CORE_SET_FUNCTION: REM A=23 - B$="*":GOSUB INIT_CORE_SET_FUNCTION: REM A=24 - B$="/":GOSUB INIT_CORE_SET_FUNCTION: REM A=25 - B$="time-ms":GOSUB INIT_CORE_SET_FUNCTION: REM A=26 - - B$="list":GOSUB INIT_CORE_SET_FUNCTION: REM A=27 - B$="list?":GOSUB INIT_CORE_SET_FUNCTION: REM A=28 - B$="vector":GOSUB INIT_CORE_SET_FUNCTION: REM A=29 - B$="vector?":GOSUB INIT_CORE_SET_FUNCTION: REM A=30 - B$="hash-map":GOSUB INIT_CORE_SET_FUNCTION: REM A=31 - B$="map?":GOSUB INIT_CORE_SET_FUNCTION: REM A=32 - B$="assoc":GOSUB INIT_CORE_SET_FUNCTION: REM A=33 - B$="dissoc":GOSUB INIT_CORE_SET_FUNCTION: REM A=34 - B$="get":GOSUB INIT_CORE_SET_FUNCTION: REM A=35 - B$="contains?":GOSUB INIT_CORE_SET_FUNCTION: REM A=36 - B$="keys":GOSUB INIT_CORE_SET_FUNCTION: REM A=37 - B$="vals":GOSUB INIT_CORE_SET_FUNCTION: REM A=38 - - B$="sequential?":GOSUB INIT_CORE_SET_FUNCTION: REM A=39 - B$="cons":GOSUB INIT_CORE_SET_FUNCTION: REM A=40 - B$="concat":GOSUB INIT_CORE_SET_FUNCTION: REM A=41 - B$="nth":GOSUB INIT_CORE_SET_FUNCTION: REM A=42 - B$="first":GOSUB INIT_CORE_SET_FUNCTION: REM A=43 - B$="rest":GOSUB INIT_CORE_SET_FUNCTION: REM A=44 - B$="empty?":GOSUB INIT_CORE_SET_FUNCTION: REM A=45 - B$="count":GOSUB INIT_CORE_SET_FUNCTION: REM A=46 - - B$="conj":GOSUB INIT_CORE_SET_FUNCTION: REM A=47 - B$="seq":GOSUB INIT_CORE_SET_FUNCTION: REM A=48 - - B$="with-meta":GOSUB INIT_CORE_SET_FUNCTION: REM A=49 - B$="meta":GOSUB INIT_CORE_SET_FUNCTION: REM A=50 - B$="atom":GOSUB INIT_CORE_SET_FUNCTION: REM A=51 - B$="atom?":GOSUB INIT_CORE_SET_FUNCTION: REM A=52 - B$="deref":GOSUB INIT_CORE_SET_FUNCTION: REM A=53 - B$="reset!":GOSUB INIT_CORE_SET_FUNCTION: REM A=54 - - B$="eval":GOSUB INIT_CORE_SET_FUNCTION: REM A=55 - B$="read-file":GOSUB INIT_CORE_SET_FUNCTION: REM A=56 - B$="pr-memory-summary":GOSUB INIT_CORE_SET_FUNCTION: REM A=57 + B$="number?":GOSUB INIT_CORE_SET_FUNCTION: REM A=11 + B$="fn?":GOSUB INIT_CORE_SET_FUNCTION: REM A=12 + B$="macro?":GOSUB INIT_CORE_SET_FUNCTION: REM A=13 + + B$="pr-str":GOSUB INIT_CORE_SET_FUNCTION: REM A=14 + B$="str":GOSUB INIT_CORE_SET_FUNCTION: REM A=15 + B$="prn":GOSUB INIT_CORE_SET_FUNCTION: REM A=16 + B$="println":GOSUB INIT_CORE_SET_FUNCTION: REM A=17 + B$="read-string":GOSUB INIT_CORE_SET_FUNCTION: REM A=18 + B$="readline":GOSUB INIT_CORE_SET_FUNCTION: REM A=19 + B$="slurp":GOSUB INIT_CORE_SET_FUNCTION: REM A=20 + + B$="<":GOSUB INIT_CORE_SET_FUNCTION: REM A=21 + B$="<=":GOSUB INIT_CORE_SET_FUNCTION: REM A=22 + B$=">":GOSUB INIT_CORE_SET_FUNCTION: REM A=23 + B$=">=":GOSUB INIT_CORE_SET_FUNCTION: REM A=24 + B$="+":GOSUB INIT_CORE_SET_FUNCTION: REM A=25 + B$="-":GOSUB INIT_CORE_SET_FUNCTION: REM A=26 + B$="*":GOSUB INIT_CORE_SET_FUNCTION: REM A=27 + B$="/":GOSUB INIT_CORE_SET_FUNCTION: REM A=28 + B$="time-ms":GOSUB INIT_CORE_SET_FUNCTION: REM A=29 + + B$="list":GOSUB INIT_CORE_SET_FUNCTION: REM A=30 + B$="list?":GOSUB INIT_CORE_SET_FUNCTION: REM A=31 + B$="vector":GOSUB INIT_CORE_SET_FUNCTION: REM A=32 + B$="vector?":GOSUB INIT_CORE_SET_FUNCTION: REM A=33 + B$="hash-map":GOSUB INIT_CORE_SET_FUNCTION: REM A=34 + B$="map?":GOSUB INIT_CORE_SET_FUNCTION: REM A=35 + B$="assoc":GOSUB INIT_CORE_SET_FUNCTION: REM A=36 + B$="dissoc":GOSUB INIT_CORE_SET_FUNCTION: REM A=37 + B$="get":GOSUB INIT_CORE_SET_FUNCTION: REM A=38 + B$="contains?":GOSUB INIT_CORE_SET_FUNCTION: REM A=39 + B$="keys":GOSUB INIT_CORE_SET_FUNCTION: REM A=40 + B$="vals":GOSUB INIT_CORE_SET_FUNCTION: REM A=41 + + B$="sequential?":GOSUB INIT_CORE_SET_FUNCTION: REM A=42 + B$="cons":GOSUB INIT_CORE_SET_FUNCTION: REM A=43 + B$="concat":GOSUB INIT_CORE_SET_FUNCTION: REM A=44 + B$="nth":GOSUB INIT_CORE_SET_FUNCTION: REM A=45 + B$="first":GOSUB INIT_CORE_SET_FUNCTION: REM A=46 + B$="rest":GOSUB INIT_CORE_SET_FUNCTION: REM A=47 + B$="empty?":GOSUB INIT_CORE_SET_FUNCTION: REM A=48 + B$="count":GOSUB INIT_CORE_SET_FUNCTION: REM A=49 + + B$="conj":GOSUB INIT_CORE_SET_FUNCTION: REM A=50 + B$="seq":GOSUB INIT_CORE_SET_FUNCTION: REM A=51 + + B$="with-meta":GOSUB INIT_CORE_SET_FUNCTION: REM A=52 + B$="meta":GOSUB INIT_CORE_SET_FUNCTION: REM A=53 + B$="atom":GOSUB INIT_CORE_SET_FUNCTION: REM A=54 + B$="atom?":GOSUB INIT_CORE_SET_FUNCTION: REM A=55 + B$="deref":GOSUB INIT_CORE_SET_FUNCTION: REM A=56 + B$="reset!":GOSUB INIT_CORE_SET_FUNCTION: REM A=57 + + B$="eval":GOSUB INIT_CORE_SET_FUNCTION: REM A=58 + B$="read-file":GOSUB INIT_CORE_SET_FUNCTION: REM A=59 + B$="pr-memory-summary":GOSUB INIT_CORE_SET_FUNCTION: REM A=60 REM these are in DO_TCO_FUNCTION - A=61 - B$="apply":GOSUB INIT_CORE_SET_FUNCTION: REM A=61 - B$="map":GOSUB INIT_CORE_SET_FUNCTION: REM A=62 - B$="swap!":GOSUB INIT_CORE_SET_FUNCTION: REM A=63 + A=65 + B$="apply":GOSUB INIT_CORE_SET_FUNCTION: REM A=65 + B$="map":GOSUB INIT_CORE_SET_FUNCTION: REM A=66 + B$="swap!":GOSUB INIT_CORE_SET_FUNCTION: REM A=67 RETURN From 2d3ea9330ef0d308e1c98684d03c25043bd2235c Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 11 Oct 2017 23:29:29 +0100 Subject: [PATCH 0523/2308] Clarifying types, tidying object release code Changed how types of containers and content are marked, and how these map onto MAL types. Simplified object release code, since data type is always now stored with the data. --- nasm/step1_read_print.asm | 156 +++++++++++++++++++++++++++----------- 1 file changed, 111 insertions(+), 45 deletions(-) diff --git a/nasm/step1_read_print.asm b/nasm/step1_read_print.asm index 2b2d6d14f8..9855c53cbb 100644 --- a/nasm/step1_read_print.asm +++ b/nasm/step1_read_print.asm @@ -6,15 +6,65 @@ ;; ;; Data structures +;; =============== +;; ;; Memory management is done by having two fixed-size datatypes, ;; Cons and Array. ;; ;; Both Cons and Array have the following in common: ;; a type field at the start, a reference count, followed by data ;; [ type (8) | (8) | refs (16) | data ] - - +;; +;; +;; Type bit fields +;; --------------- +;; +;; The 8-bit type fields describe the Block, Container and Content type. +;; +;; The Block type is used for memory management, to determine the kind of memory block +;; The Container type indicates the data structure that the Cons or Array block is being used to represent +;; The Content type indicates the raw type of the data in the content +;; +;; Block type [1 bit]: +;; 0 0 - Cons memory block +;; 1 1 - Array memory block ;; +;; Container type [3 bits]: +;; 0 0 - Value (single boxed value for Cons blocks, vector for Array blocks). +;; 2 1 - List (value followed by pointer). Only for Cons blocks +;; 4 2 - Symbol (special char array). Only for Array blocks +;; 6 3 - Keyword +;; 8 4 - Map +;; 10 5 - Function +;; +;; Content type [4 bits]: +;; 0 0 - Nil +;; 16 1 - Bool +;; 32 2 - Char +;; 48 3 - Int +;; 64 4 - Float +;; 80 5 - Pointer (memory address) +;; +;; These represent MAL data types as follows: +;; +;; MAL type Block Container Content +;; --------- | -------- | ---------- | --------- +;; integer Cons Value Int +;; symbol Array Symbol Char +;; list Cons List Any +;; nil Cons Value Nil +;; true Cons Value Bool (1) +;; false Cons Value Bool (0) +;; string Array Value Char +;; keyword Array Keyword Char +;; vector Array Value Int/Float +;; hash-map Array Map Pointer (?TBD) +;; atom Cons Value Pointer +;; + +;; Cons type. +;; Used to store either a single value with type information +;; or a pair of (value, Pointer or Nil) to represent a list STRUC Cons .typecar: RESB 1 ; Type information for car (8 bit) .typecdr: RESB 1 ; Type information for cdr (8 bits) @@ -38,12 +88,38 @@ STRUC Array ENDSTRUC ;; Type information -%define type_char 1 ; Character type -%define type_integer 2 ; Integer type -%define type_float 3 ; Floating point number -%define type_atom 64 ; 1 if just an atom, not a list or array -%define type_array 128 ; Last bit tests if array or cons +%define block_mask 1 ; LSB for block type +%define container_mask 2 + 4 + 8 ; Next three bits for container type +%define content_mask 16 + 32 + 64 + 128 ; Four bits for content type + +;; Block types +%define block_cons 0 +%define block_array 1 + +;; Container types +%define container_value 0 +%define container_list 2 +%define container_symbol 4 +%define container_keyword 6 +%define container_map 8 +%define container_function 10 + +;; Content type +%define content_nil 0 +%define content_bool 16 +%define content_char 32 +%define content_int 48 +%define content_float 64 +%define content_pointer 80 ; Memory pointer (to Cons or Array) +%define content_function 96 ; Function pointer + +;; Common combinations for MAL types +%define maltype_integer (block_cons + container_value + content_int) +%define maltype_string (block_array + container_value + content_char) +%define maltype_symbol (block_array + container_symbol + content_char) + + %include "reader.asm" @@ -52,11 +128,11 @@ ENDSTRUC section .data ;str: ISTRUC Array -;AT Array.type, db type_char + type_array +;AT Array.type, db maltype_string ;AT Array.length, dd 6 ;AT Array.data, db 'hello',10 ;IEND - + ;; ------------------------------------------ ;; Fixed strings for printing @@ -102,7 +178,7 @@ heap_array_store: resb heap_array_limit * Array.size .end: section .text - + ;; ------------------------------------------ ;; Array alloc_array() ;; @@ -134,7 +210,7 @@ alloc_array: .initialise_array: ; Address of Array now in rax - mov BYTE [rax + Array.type], type_array + mov BYTE [rax + Array.type], block_array mov WORD [rax + Array.refcount], 1 ; Only one reference mov DWORD [rax + Array.length], 0 mov QWORD [rax + Array.next], 0 ; null next address @@ -237,8 +313,8 @@ release_cons: .free: ; Get and push cdr onto stack mov rcx, [rsi + Cons.cdr] - push rcx - push rsi + push rcx ; Content of CDR + push rsi ; Original Cons object being released mov rax, [heap_cons_free] ; Get the current head mov [rsi + Cons.cdr], rax ; Put current head into the "cdr" field @@ -247,51 +323,41 @@ release_cons: ; Check if the CAR needs to be released mov al, BYTE [rsi+Cons.typecar] - mov bl, type_atom - and bl, al ; bl now zero if a list or array - jnz .free_cdr + and al, content_mask ; Test content type + cmp al, content_pointer + jne .free_cdr ; Jump if CAR not pointer + ; CAR is a pointer to either a Cons or Array ; Get the address stored in CAR mov rsi, [rsi + Cons.car] - - ; test if type is array or cons - mov bl, type_array - and bl, al ; bl now zero if cons - jnz .car_array - - ; CAR is a Cons - call release_cons - jmp .free_cdr - -.car_array: - ; CAR is an Array - call release_array - + call release_object .free_cdr: pop rcx ; This was rsi, the original Cons pop rsi ; This was rcx, the original Cons.cdr ; Get the type from the original Cons mov al, BYTE [rcx+Cons.typecdr] - mov bl, type_atom - and bl, al ; bl now zero if a list or array - jnz .done + and al, content_mask ; Test content type + cmp al, content_pointer + jne .done + + call release_object +.done: + ret - ; test if type is array or cons - mov bl, type_array - and bl, al ; bl now zero if cons - jnz .cdr_array - ; CAR is a Cons +;; Releases either a Cons or Array +;; Address of object in RSI +release_object: + mov al, BYTE [rsi] ; Get first byte + and al, block_mask ; Test block type + cmp al, block_array ; Test if it's an array + je .array call release_cons ret - -.cdr_array: - ; CAR is an Array +.array: call release_array -.done: ret - ;; ------------------------------------------- ;; Prints a raw string to stdout @@ -322,7 +388,7 @@ print_string: ; Check that we have a char array mov al, [rsi] - cmp al, type_char + type_array + cmp al, maltype_string jne .error ; write(1, string, length) @@ -459,7 +525,7 @@ read_line: ; Address in rax call alloc_array ; Mark it as a character array (string) - mov BYTE [rax + Array.type], type_char + type_array + mov BYTE [rax + Array.type], maltype_string push rax ; Save pointer to string From 2df92e06552b0366bfdad1a6a55b7081ca3be763 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 11 Oct 2017 21:18:50 -0400 Subject: [PATCH 0524/2308] c, rpython, vhdl: add number?, fn?, and macro? --- c/core.c | 20 ++++++++++++++++++-- c/core.h | 2 +- rpython/core.py | 6 ++++++ vhdl/core.vhdl | 21 +++++++++++++++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/c/core.c b/c/core.c index 55e048ee8b..3cfbb2e420 100644 --- a/c/core.c +++ b/c/core.c @@ -26,7 +26,7 @@ MalVal *equal_Q(MalVal *a, MalVal *b) { } -// Scalar functions +// Misc predicates MalVal *nil_Q(MalVal *seq) { return seq->type & MAL_NIL ? &mal_true : &mal_false; } MalVal *true_Q(MalVal *seq) { return seq->type & MAL_TRUE ? &mal_true : &mal_false; } @@ -38,6 +38,19 @@ MalVal *string_Q(MalVal *seq) { return &mal_false; } } +MalVal *number_Q(MalVal *obj) { + return obj->type & MAL_INTEGER || obj->type & MAL_FLOAT + ? &mal_true + : &mal_false; +} +MalVal *fn_Q(MalVal *obj) { + return (obj->type & MAL_FUNCTION_C || obj->type & MAL_FUNCTION_MAL) && + !obj->ismacro + ? &mal_true + : &mal_false; +} +MalVal *macro_Q(MalVal *obj) { return obj->ismacro ? &mal_true : &mal_false; } + // Symbol functions @@ -492,7 +505,7 @@ MalVal *swap_BANG(MalVal *args) { -core_ns_entry core_ns[58] = { +core_ns_entry core_ns[61] = { {"=", (void*(*)(void*))equal_Q, 2}, {"throw", (void*(*)(void*))throw, 1}, {"nil?", (void*(*)(void*))nil_Q, 1}, @@ -503,6 +516,9 @@ core_ns_entry core_ns[58] = { {"symbol?", (void*(*)(void*))symbol_Q, 1}, {"keyword", (void*(*)(void*))keyword, 1}, {"keyword?", (void*(*)(void*))keyword_Q, 1}, + {"number?", (void*(*)(void*))number_Q, 1}, + {"fn?", (void*(*)(void*))fn_Q, 1}, + {"macro?", (void*(*)(void*))macro_Q, 1}, {"pr-str", (void*(*)(void*))pr_str, -1}, {"str", (void*(*)(void*))str, -1}, diff --git a/c/core.h b/c/core.h index 9d612a66f0..4c8909b67f 100644 --- a/c/core.h +++ b/c/core.h @@ -10,6 +10,6 @@ typedef struct { int arg_cnt; } core_ns_entry; -extern core_ns_entry core_ns[58]; +extern core_ns_entry core_ns[61]; #endif diff --git a/rpython/core.py b/rpython/core.py index 5539a89963..5e7a12fb96 100644 --- a/rpython/core.py +++ b/rpython/core.py @@ -38,6 +38,9 @@ def symbol(args): def symbol_Q(args): return wrap_tf(types._symbol_Q(args[0])) def keyword(args): return types._keyword(args[0]) def keyword_Q(args): return wrap_tf(types._keyword_Q(args[0])) +def number_Q(args): return wrap_tf(types._int_Q(args[0])) +def function_Q(args): return wrap_tf(types._function_Q(args[0]) and not args[0].ismacro) +def macro_Q(args): return wrap_tf(types._function_Q(args[0]) and args[0].ismacro) # String functions @@ -375,6 +378,9 @@ def swap_BANG(args): 'symbol?': symbol_Q, 'keyword': keyword, 'keyword?': keyword_Q, + 'number?': number_Q, + 'fn?': function_Q, + 'macro?': macro_Q, 'pr-str': pr_str, 'str': do_str, diff --git a/vhdl/core.vhdl b/vhdl/core.vhdl index d95f6a2bda..00b18e0f1c 100644 --- a/vhdl/core.vhdl +++ b/vhdl/core.vhdl @@ -66,6 +66,21 @@ package body core is new_boolean(args.seq_val(0).val_type = mal_keyword, result); end procedure fn_keyword_q; + procedure fn_number_q(args: inout mal_val_ptr; result: out mal_val_ptr; err: out mal_val_ptr) is + begin + new_boolean(args.seq_val(0).val_type = mal_number, result); + end procedure fn_number_q; + + procedure fn_function_q(args: inout mal_val_ptr; result: out mal_val_ptr; err: out mal_val_ptr) is + begin + new_boolean((args.seq_val(0).val_type = mal_fn and not args.seq_val(0).func_val.f_is_macro) or args.seq_val(0).val_type = mal_nativefn, result); + end procedure fn_function_q; + + procedure fn_macro_q(args: inout mal_val_ptr; result: out mal_val_ptr; err: out mal_val_ptr) is + begin + new_boolean(args.seq_val(0).val_type = mal_fn and args.seq_val(0).func_val.f_is_macro, result); + end procedure fn_macro_q; + procedure fn_pr_str(args: inout mal_val_ptr; result: out mal_val_ptr; err: out mal_val_ptr) is variable s: line; begin @@ -533,6 +548,9 @@ package body core is elsif f.all = "symbol?" then fn_symbol_q(args, result, err); elsif f.all = "keyword" then fn_keyword(args, result, err); elsif f.all = "keyword?" then fn_keyword_q(args, result, err); + elsif f.all = "number?" then fn_number_q(args, result, err); + elsif f.all = "fn?" then fn_function_q(args, result, err); + elsif f.all = "macro?" then fn_macro_q(args, result, err); elsif f.all = "pr-str" then fn_pr_str(args, result, err); elsif f.all = "str" then fn_str(args, result, err); elsif f.all = "prn" then fn_prn(args, result, err); @@ -609,6 +627,9 @@ package body core is define_core_function(e, "symbol?"); define_core_function(e, "keyword"); define_core_function(e, "keyword?"); + define_core_function(e, "number?"); + define_core_function(e, "fn?"); + define_core_function(e, "macro?"); define_core_function(e, "pr-str"); define_core_function(e, "str"); define_core_function(e, "prn"); From f5bb2941f6751213c2f7d4ea4da9d2fb6abe2b17 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 12 Oct 2017 18:14:38 -0400 Subject: [PATCH 0525/2308] coffee, clojure, cs: add number?, fn?, and macro? --- clojure/src/mal/core.cljc | 3 +++ coffee/core.coffee | 3 +++ coffee/types.coffee | 1 + cs/core.cs | 12 ++++++++++++ 4 files changed, 19 insertions(+) diff --git a/clojure/src/mal/core.cljc b/clojure/src/mal/core.cljc index 103348929d..283c9a3907 100644 --- a/clojure/src/mal/core.cljc +++ b/clojure/src/mal/core.cljc @@ -39,6 +39,9 @@ ['symbol? symbol?] ['keyword keyword] ['keyword? keyword?] + ['number? number?] + ['fn? (fn [o] (if (and (fn? o) (not (:ismacro (meta o)))) true false))] + ['macro? (fn [o] (if (and (fn? o) (:ismacro (meta o))) true false))] ['pr-str pr-str] ['str printer/_str] diff --git a/coffee/core.coffee b/coffee/core.coffee index bbe8c38c17..518a4c8259 100644 --- a/coffee/core.coffee +++ b/coffee/core.coffee @@ -47,6 +47,9 @@ exports.ns = { 'symbol?': types._symbol_Q, 'keyword': types._keyword, 'keyword?': types._keyword_Q, + 'number?': (a) -> typeof a == 'number', + 'fn?': (a) -> typeof a == 'function' and not types._macro_Q(a), + 'macro?': types._macro_Q, 'pr-str': (a...) -> a.map((exp) -> _pr_str(exp,true)).join(" "), 'str': (a...) -> a.map((exp) -> _pr_str(exp,false)).join(""), diff --git a/coffee/types.coffee b/coffee/types.coffee index 0f5666c7c0..5252281762 100644 --- a/coffee/types.coffee +++ b/coffee/types.coffee @@ -83,6 +83,7 @@ E._function = (evalfn, ast, env, params) -> fn.__ismacro__ = false fn E._function_Q = _function_Q = (o) -> !!o.__ast__ +E._macro_Q = _macro_Q = (o) -> _function_Q(o) and o.__ismacro__ # Lists E._list_Q = _list_Q = (o) -> Array.isArray(o) && !o.__isvector__ diff --git a/cs/core.cs b/cs/core.cs index 590a0de13b..9bb7a7ab87 100644 --- a/cs/core.cs +++ b/cs/core.cs @@ -65,6 +65,15 @@ public class core { } } ); + static MalFunc number_Q = new MalFunc( + a => a[0] is MalInt ? True : False); + + static MalFunc function_Q = new MalFunc( + a => a[0] is MalFunc && !((MalFunc)a[0]).isMacro() ? True : False); + + static MalFunc macro_Q = new MalFunc( + a => a[0] is MalFunc && ((MalFunc)a[0]).isMacro() ? True : False); + // Number functions static MalFunc time_ms = new MalFunc( @@ -325,6 +334,9 @@ public class core { {"string?", string_Q}, {"keyword", keyword}, {"keyword?", keyword_Q}, + {"number?", number_Q}, + {"fn?", function_Q}, + {"macro?", macro_Q}, {"pr-str", pr_str}, {"str", str}, From 365bc24cffd0d23432ac99caf15b90940cfe7e1f Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 12 Oct 2017 23:59:13 +0100 Subject: [PATCH 0526/2308] Work on tokenizer Not complete yet, but should recognise special characters and strings (mostly). String code not complete yet, since it won't handle long strings correctly (no chaining of Arrays). --- nasm/reader.asm | 223 ++++++++++++++++++++++++++++++++++++-- nasm/step1_read_print.asm | 4 +- 2 files changed, 219 insertions(+), 8 deletions(-) diff --git a/nasm/reader.asm b/nasm/reader.asm index e78226de02..818c680ca8 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -1,15 +1,224 @@ -section .bss - -;; State of Reader - section .text +;; Read a string into memory as a form (nested lists and atoms) +;; Note: In this implementation the tokenizer is not done separately +;; +;; Input: Address of string (char array) in RSI +;; read_str: - ; Convert the input string into a list of tokens - call tokenizer - ; RAX now contains address of list of tokens + + +;; Initialise the tokenizer +tokenizer_init: + ; Put start of data array into rax + mov rax, rsi + add rax, Array.data + ; Put end of data array into rbx + mov ebx, [rsi + Array.length] ; Length of array, zero-extended + add rbx, rax + + ret + +;; Move onto the next chunk of the array +;; This is needed because strings are not stored in one +;; contiguous block of memory, but may use multiple Array +;; objects in a linked list +;; +;; If no chunks are left, then RAX = RBX +tokenizer_next_chunk: + mov rax, [rsi + Array.next] + cmp rax, 0 + je .no_more + ; More chunks left + mov rsi, rax + call tokenizer_init + ret +.no_more: + ; No more chunks left. RAX is zero + mov rbx, rax + ret + +;; Moves the next char into CL +;; If no more, puts 0 into CL +tokenizer_next_char: + ; Check if we have reached the end of this chunk + cmp rax, rbx + jne .chars_remain + + ; Hit the end. See if there is another chunk + call tokenizer_next_chunk + cmp rax, rbx + jne .chars_remain ; Success, got another + + ; No more chunks + mov cl, 0 ; Null char signals end + ret + +.chars_remain: + mov cl, BYTE [rax] + inc rax ; point to next byte + ret + +;; Get the next token +;; Token code is in CL register. Could be: +;; - 0 : Nil, finished +;; - Characters ()[]()'`~^@ +;; - Pair '~@', represented by code 1 +;; - A string: " in CL, and address in R8 +tokenizer_next: + +.next_char: + ; Fetch the next char into CL + call tokenizer_next_char + + cmp cl, 0 + je .found ; End, no more tokens + + ; Here expect to have: + ; - The current character in CL + ; - Address of next data in rax + ; - Address of data end in rbx + + ; Skip whitespace or commas + cmp cl, ' ' ; Space + je .next_char + cmp cl, ',' ; Comma + je .next_char + cmp cl, 9 ; Tab + + ; Special characters. These are returned in CL as-is + cmp cl, '(' + je .found + cmp cl, ')' + je .found + cmp cl, '[' + je .found + cmp cl, ']' + je .found + cmp cl, '{' + je .found + cmp cl, '}' + je .found + cmp cl, 39 ; character ' + je .found + cmp cl, 96 ; character ` + je .found + cmp cl, '^' + je .found + cmp cl, '@' + je .found + cmp cl, '~' ; Could be followed by '@' + je .handle_tilde + + cmp cl, ';' ; Start of a comment + je .tokens_finished + + cmp cl, '"' ; Opening string quotes + jmp .handle_string + + ; Could be number or symbol + + + + + ret + +.handle_string: + ; Get an array to put the string into + + ; save state of tokenizer + push rsi + push rax + push rbx + + call alloc_array + mov r8, rax ; Address of array in r8 + mov [r8], BYTE maltype_string ; mark as a string + + ; restore state + pop rbx + pop rax + pop rsi + + ; Put start of data array into r9 + mov r9, r8 + add r9, Array.data + ; Put end of data array into r10 + mov r10d, [rsi + Array.length] ; Length of array, zero-extended + add r10, r9 + + ; Now read chars from input string and push into output +.string_loop: + call tokenizer_next_char + cmp cl, 0 ; End of characters + je .error + + cmp cl, '"' ; Finished + je .found ; Leave '"' in CL + + cmp cl, 92 ; Escape '\' + jne .end_string_escape + + ; Current character is a '\' + call tokenizer_next_char + cmp cl, 0 ; End of characters + je .error + + cmp cl, 'n' ; \n, newline + je .insert_newline + + ; Whatever is in cl is now put into string + ; including '"' + jmp .end_string_escape + +.insert_newline: + mov cl, 10 + jmp .end_string_escape + +.end_string_escape: + + ; Put CL onto result array + mov [r9], cl + inc r9 + + jmp .string_loop + + ret + +.tokens_finished: + mov cl, 0 ; End of tokens + ret + +.handle_tilde: + ; Could have '~' or '~@'. Need to peek at the next char + + ; Push current state of the tokenizer + push rsi + push rax + push rbx + call tokenizer_next_char ; Next char in CL + cmp cl, '@' + jne .tilde_no_amp ; Just '~', not '~@' + ; Got '~@' + mov cl, 1 ; Signals '~@' + + ; Discard old state by moving stack pointer + add esp, 24 ; 3 * 8 bytes + ret + +.tilde_no_amp: + ; Restore state of the tokenizer + pop rbx + pop rax + pop rsi + ; fall through to finished + +.found: + ret + +.error: ret ;; ----------------------------- diff --git a/nasm/step1_read_print.asm b/nasm/step1_read_print.asm index 9855c53cbb..e1ddd067f1 100644 --- a/nasm/step1_read_print.asm +++ b/nasm/step1_read_print.asm @@ -81,7 +81,7 @@ STRUC Array .type: RESB 1 ; Type information (8 bits) .control: RESB 1 ; Control data (8 bits) .refcount: RESW 1 ; Number of references to this Array (16 bit) -.length: RESD 1 ; Number of elements in array (32 bit) +.length: RESD 1 ; Number of elements in this part of the array (32 bit) .next RESQ 1 ; Pointer to the next chunk (64 bit) .data: RESQ array_chunk_len ; Data storage .size: ; Total size of struc @@ -442,6 +442,8 @@ itostring: ; Get an Array object to put the string into call alloc_array ; Address in RAX + + mov [rax], BYTE maltype_string ; mark as a string ; put length into string mov [rax + Array.length], ecx From 69edbad782754fc59565c14d3afaae76bab4d173 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Sat, 14 Oct 2017 12:18:10 +0000 Subject: [PATCH 0527/2308] tests: Use a real user-defined function when testing fn? and macro? --- tests/stepA_mal.mal | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/stepA_mal.mal b/tests/stepA_mal.mal index 5c458e1e3e..5adb0824fa 100644 --- a/tests/stepA_mal.mal +++ b/tests/stepA_mal.mal @@ -112,10 +112,12 @@ (number? "123") ;=>false +(def! add1 (fn* (x) (+ x 1))) + ;; Testing fn? function (fn? +) ;=>true -(fn? not) +(fn? add1) ;=>true (fn? cond) ;=>false @@ -129,7 +131,7 @@ ;=>true (macro? +) ;=>false -(macro? not) +(macro? add1) ;=>false (macro? "+") ;=>false From c91c8de968b9a10ee54e408bb1e0175d4a2eb113 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Sat, 14 Oct 2017 12:14:03 +0000 Subject: [PATCH 0528/2308] forth, skew, tcl: Add number?, fn?, macro? --- forth/core.fs | 19 +++++++++++++++++++ skew/core.sk | 4 ++++ tcl/core.tcl | 20 ++++++++++++++++++++ tcl/types.tcl | 4 ++++ 4 files changed, 47 insertions(+) diff --git a/forth/core.fs b/forth/core.fs index 79fa6847fa..593229c93f 100644 --- a/forth/core.fs +++ b/forth/core.fs @@ -218,6 +218,25 @@ defcore atom? drop @ mal-type @ Atom = mal-bool ;; defcore true? drop @ mal-true = mal-bool ;; defcore false? drop @ mal-false = mal-bool ;; defcore nil? drop @ mal-nil = mal-bool ;; +defcore number? drop @ mal-type @ MalInt = mal-bool ;; +defcore fn? + drop @ + dup mal-type @ MalUserFn = if + MalUserFn/is-macro? @ if + mal-false + else + mal-true + endif + else + mal-type @ MalNativeFn = if + mal-true + else + mal-false + endif + endif ;; +defcore macro? drop @ dup mal-type @ MalUserFn = + swap MalUserFn/is-macro? @ + and mal-bool ;; defcore sequential? drop @ sequential? ;; diff --git a/skew/core.sk b/skew/core.sk index 3390d8e7d7..bd470a1e30 100644 --- a/skew/core.sk +++ b/skew/core.sk @@ -16,6 +16,10 @@ const ns StringMap) MalVal> = { "symbol?": (a List) => MalVal.fromBool(a[0] is MalSymbol), "keyword": (a List) => MalKeyword.new((a[0] as MalString).val), "keyword?": (a List) => MalVal.fromBool(a[0] is MalKeyword), + "number?": (a List) => MalVal.fromBool(a[0] is MalNumber), + "fn?": (a List) => MalVal.fromBool(a[0] is MalNativeFunc || + (a[0] is MalFunc && !(a[0] as MalFunc).isMacro)), + "macro?": (a List) => MalVal.fromBool(a[0] is MalFunc && (a[0] as MalFunc).isMacro), "pr-str": (a List) => MalString.new(" ".join(a.map(e => pr_str(e, true)))), "str": (a List) => MalString.new("".join(a.map(e => pr_str(e, false)))), diff --git a/tcl/core.tcl b/tcl/core.tcl index a7ab9ea160..b168afb1af 100644 --- a/tcl/core.tcl +++ b/tcl/core.tcl @@ -40,6 +40,23 @@ proc mal_keyword_q {a} { bool_new [keyword_q [lindex $a 0]] } +proc mal_number_q {a} { + bool_new [integer_q [lindex $a 0]] +} + +proc mal_fn_q {a} { + set f [lindex $a 0] + switch [obj_type $f] { + function { return [bool_new [expr {![macro_q $f]}]] } + nativefunction { return $::mal_true } + default { return $::mal_false } + } +} + +proc mal_macro_q {a} { + bool_new [macro_q [lindex $a 0]] +} + proc render_array {arr readable delim} { set res {} foreach e $arr { @@ -383,6 +400,9 @@ set core_ns [dict create \ "string?" [nativefunction_new mal_string_q] \ "keyword" [nativefunction_new mal_keyword] \ "keyword?" [nativefunction_new mal_keyword_q] \ + "number?" [nativefunction_new mal_number_q] \ + "fn?" [nativefunction_new mal_fn_q] \ + "macro?" [nativefunction_new mal_macro_q] \ \ "pr-str" [nativefunction_new mal_pr_str] \ "str" [nativefunction_new mal_str] \ diff --git a/tcl/types.tcl b/tcl/types.tcl index e1edfad3b9..8bf2c705d6 100644 --- a/tcl/types.tcl +++ b/tcl/types.tcl @@ -73,6 +73,10 @@ proc integer_new {num} { obj_new "integer" $num } +proc integer_q {obj} { + expr {[obj_type $obj] == "integer"} +} + proc symbol_new {name} { obj_new "symbol" $name } From d5e01edc36ba05ab9690ac00e6c691c6734f410d Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sun, 15 Oct 2017 00:26:09 +0100 Subject: [PATCH 0529/2308] Reader and printer read/write integers Some progress: Integers read, passed as Cons object through to pr_str, then as Array (string) to print_string. Memory management (reference counting works so far...) --- nasm/printer.asm | 81 +++++++++++++++ nasm/reader.asm | 211 +++++++++++++++++++++++++++++++++++--- nasm/step1_read_print.asm | 101 ++++++++++++++---- 3 files changed, 359 insertions(+), 34 deletions(-) create mode 100644 nasm/printer.asm diff --git a/nasm/printer.asm b/nasm/printer.asm new file mode 100644 index 0000000000..11dd2b4d10 --- /dev/null +++ b/nasm/printer.asm @@ -0,0 +1,81 @@ +;;; Turns forms (lists, values/atoms) into strings +;;; +;;; + +section .data +unknown_type_string: db "#" +.len: equ $ - unknown_type_string + +unknown_value_string: db "#" +.len: equ $ - unknown_value_string + +nil_value_string: db "nil" +.len: equ $ - nil_value_string + +section .text + +;; Input: Address of object in RSI +;; +;; Output: Address of string in RAX +;; +;; Modifies: RCX +;; Calls: raw_to_string, +pr_str: + + ; Get the type + mov cl, BYTE [rsi] + + ; Check if it's already a string + cmp cl, maltype_string + jne .not_string + mov rax, rsi + ret + +.not_string: + ; Now test the container type (value, list) + + mov ch, cl + + and ch, container_mask + jz .value + + cmp ch, 2 + je .list + + cmp ch, 4 + je .symbol + + ; Unknown + mov rsi, unknown_type_string + mov edx, unknown_type_string.len + call raw_to_string ; Puts a String in RAX + ret + +.value: + mov ch, cl + and ch, content_mask + jz .value_nil + + cmp ch, 48 + je .value_int + + mov rsi, unknown_value_string + mov edx, unknown_value_string.len + call raw_to_string ; Puts a String in RAX + ret + +.value_nil: + mov rsi, nil_value_string + mov edx, nil_value_string.len + call raw_to_string + ret + +.value_int: + mov rax, [rsi + Cons.car] + call itostring + ret +.list: + ret +.symbol: + ret + diff --git a/nasm/reader.asm b/nasm/reader.asm index 818c680ca8..8c448a9c3f 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -7,10 +7,139 @@ section .text ;; ;; Input: Address of string (char array) in RSI ;; +;; Output: Address of object in RAX +;; +;; Uses registers: +;; R13 Address of the current list (starts 0) +;; R14 Stack pointer at start. Used for unwinding on error +;; R15 Address of first list. Used for unwinding on error +;; read_str: + ; Initialise tokenizer + call tokenizer_init + + ; Get the next token + call tokenizer_next + + ; Set current list to zero + mov r13, 0 + + ; Save stack pointer for unwinding + mov r14, rsp + + ; check what type of token by testing CL + cmp cl, 0 + jne .got_token + + ; No tokens. Return 'nil' + call alloc_cons + mov [rax], BYTE maltype_nil + ret + +.read_loop: + + call tokenizer_next + cmp cl, 0 + jne .got_token + + ; Unexpected end of tokens + + mov rsp, r14 ; Restore stack + mov rsi, r13 ; Top Cons + call release_cons ; This should delete everything + + call alloc_cons + mov [rax], BYTE maltype_nil + + ret + +.got_token: + + cmp cl, '(' + je .list_start + + cmp cl, ')' + je .list_end + + cmp cl, 'i' + je .append_object ; Cons already in R8 + + ; Unknown + call alloc_cons + mov [rax], BYTE maltype_nil + ret + + ; -------------------------------- +.list_start: + ; Push current list onto stack + push r13 + + ; Push current state of the tokenizer + push rsi + push rax + push rbx + + ; Start new list + call alloc_cons ; Address in rax + + cmp r13, 0 + jne .list_link_last + + ; This is the top-level list + mov r15, rax + +.list_link_last: + ; The new list is nested + mov [r13 + Cons.cdr], rax + mov [r13 + Cons.typecdr], BYTE content_pointer +.list_done: + mov r13, rax ; Switch to new list + + ; Restore state + pop rbx + pop rax + pop rsi + + jmp .read_loop + + ; -------------------------------- +.list_end: + + ; Put the current list into r8 + mov r8, r13 + + ; Pop the previous list + pop r13 + + jmp .append_object ; Add R8 to list in R13 + + ; -------------------------------- +.append_object: + ; Append Cons in R8 to list in R13 + ; If no list in R13 (address is zero) then returns + ; with R8 moved to RAX + + cmp r13, 0 + je .finished + ; Append to list + mov [r13 + Cons.cdr], r8 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r8 + Cons.typecdr], BYTE content_nil + + jmp .read_loop + ; -------------------------------- +.finished: + ; No list to add this object to, so finished + mov rax, r8 + ret ;; Initialise the tokenizer +;; +;; Input: Address of string in RSI +;; +;; NOTE: This uses RSI, RAX and RBX, and expects these to be preserved +;; between calls to tokenizer_next_char tokenizer_init: ; Put start of data array into rax mov rax, rsi @@ -67,6 +196,10 @@ tokenizer_next_char: ;; - Characters ()[]()'`~^@ ;; - Pair '~@', represented by code 1 ;; - A string: " in CL, and address in R8 +;; - An integer: 'i' in CL +;; +;; Address of object in R8 +;; tokenizer_next: .next_char: @@ -115,12 +248,71 @@ tokenizer_next: cmp cl, ';' ; Start of a comment je .tokens_finished - cmp cl, '"' ; Opening string quotes - jmp .handle_string + cmp cl, 34 ; Opening string quotes + je .handle_string ; Could be number or symbol + ; Check for a character 0-9 + cmp cl, '0' + jl .handle_symbol + cmp cl, '9' + jg .handle_symbol + +.handle_integer: + ; Start integer + ; accumulate in EDX + xor edx, edx + + ; Push current state of the tokenizer + push rsi + push rax + push rbx +.integer_loop: + ; Here have a char 0-9 in CL + sub cl, '0' ; Convert to number between 0 and 9 + movzx ebx, cl + add edx, ebx + + ; Peek at next character + push rdx + call tokenizer_next_char ; Next char in CL + pop rdx + + cmp cl, '0' + jl .integer_finished + cmp cl, '9' + jg .integer_finished + + imul edx, 10 + + jmp .integer_loop + +.integer_finished: + ; Next char not an int + + push rdx ; Save the integer + + ; Get a Cons object to put the result into + call alloc_cons + ; Address of Cons now in RAX + mov r8, rax + mov [r8], BYTE maltype_integer + + pop rdx + mov [r8 + Cons.car], rdx + + ; Restore state + pop rbx + pop rax + pop rsi + + mov cl, 'i' ; Mark as an integer + ret + +.handle_symbol: + ret @@ -155,7 +347,7 @@ tokenizer_next: cmp cl, 0 ; End of characters je .error - cmp cl, '"' ; Finished + cmp cl, 34 ; Finishing '"' je .found ; Leave '"' in CL cmp cl, 92 ; Escape '\' @@ -180,6 +372,7 @@ tokenizer_next: .end_string_escape: ; Put CL onto result array + ; NOTE: this doesn't handle long strings (multiple memory blocks) mov [r9], cl inc r9 @@ -205,7 +398,7 @@ tokenizer_next: mov cl, 1 ; Signals '~@' ; Discard old state by moving stack pointer - add esp, 24 ; 3 * 8 bytes + add rsp, 24 ; 3 * 8 bytes ret .tilde_no_amp: @@ -221,13 +414,3 @@ tokenizer_next: .error: ret -;; ----------------------------- -;; list(tokens) tokenizer (string) -;; -;; Input string address in RSI -;; Creates a list of tokens, returns address in RAX -tokenizer: - - ; Get a new - call alloc_array - ret diff --git a/nasm/step1_read_print.asm b/nasm/step1_read_print.asm index e1ddd067f1..3a60c56686 100644 --- a/nasm/step1_read_print.asm +++ b/nasm/step1_read_print.asm @@ -118,15 +118,22 @@ ENDSTRUC %define maltype_integer (block_cons + container_value + content_int) %define maltype_string (block_array + container_value + content_char) %define maltype_symbol (block_array + container_symbol + content_char) - +%define maltype_nil (block_cons + container_value + content_nil) %include "reader.asm" +%include "printer.asm" global _start section .data +test_string1: db 10, "test1", 10 +.len: equ $ - test_string1 + +test_string2: db 10, "test2", 10 +.len: equ $ - test_string2 + ;str: ISTRUC Array ;AT Array.type, db maltype_string ;AT Array.length, dd 6 @@ -158,12 +165,12 @@ error_cons_memory_limit: db "Error: Run out of memory for Cons objects. Increase ;; is free'd it is pushed onto the heap_x_free list. -%define heap_cons_limit 1 ; Number of cons objects which can be created +%define heap_cons_limit 10 ; Number of cons objects which can be created heap_cons_next: dd heap_cons_store ; Address of next cons in memory heap_cons_free: dq 0 ; Address of start of free list -%define heap_array_limit 1 ; Number of array objects which can be created +%define heap_array_limit 4 ; Number of array objects which can be created heap_array_next: dd heap_array_store heap_array_free: dq 0 @@ -230,6 +237,15 @@ alloc_array: ;; onto the free list release_array: mov ax, WORD [rsi + Array.refcount] + + push rsi + push rdx + mov rsi, test_string1 + mov rdx, test_string1.len + call print_rawstring + pop rdx + pop rsi + dec ax mov WORD [rsi + Array.refcount], ax jz .free ; If the count reaches zero then put on free list @@ -358,6 +374,49 @@ release_object: .array: call release_array ret + +;; ------------------------------------------- +;; String type + +;; Create a new string, address in RAX +string_new: + call alloc_array + mov [rax], BYTE maltype_string + ret + +;; Convert a raw string to a String type +;; +;; Input: Address of raw string in RSI, length in EDX +;; Output: Address of string in RAX +;; +;; Modifies registers: R8,R9,RCX +raw_to_string: + push rsi + push rdx + call string_new ; String now in RAX + pop rdx + pop rsi + mov [rax + Array.length], DWORD edx + mov r8, rax + add r8, Array.data ; Address of string data + mov r9, rsi ; Address of raw data + mov ecx, edx ; Count +.copy_loop: + + mov bl, BYTE [r9] + mov [r8], BYTE bl + inc r8 + inc r9 + dec ecx + jnz .copy_loop + ret + + + +;; Appends a character to a string +;; Input: Address of string in RSI, character in CL +string_append_char: + ret ;; ------------------------------------------- ;; Prints a raw string to stdout @@ -468,17 +527,6 @@ itostring: pop rcx ret - -;; ---------------------------- -;; int stringtoi(String) -;; -;; Convert a string (char array) to an integer -;; -;; Address of input string is in RSI -;; Output integer in RAX -stringtoi: - - ret ;------------------------------------------ ; void exit() @@ -493,11 +541,6 @@ quit_error: mov rdi, 1 ; exit code 1 syscall - -;; Takes a string as input and processes it into a form -read: - mov rax, rsi ; Return the input - ret ;; Evaluates a form eval: @@ -511,7 +554,7 @@ print: ;; Read-Eval-Print in sequence rep_seq: - call read + call read_str mov rsi, rax ; Output of read into input of eval call eval mov rsi, rax ; Output of eval into input of print @@ -596,10 +639,28 @@ _start: je .mainLoopEnd push rax ; Save address of the string + + ; Put into read_str + mov rsi, rax + call read_str + push rax + + ; Put into pr_str + mov rsi, rax + call pr_str + push rax mov rsi, rax ; Put into input of print_string call print_string + ; Release string from pr_str + pop rsi + call release_array + + ; Release the Cons from read_str + pop rsi + call release_cons + ; Release the string pop rsi call release_array From d90be1a9b226fd95fdb46f0d818924d33bc705e9 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Sun, 15 Oct 2017 06:04:09 +0000 Subject: [PATCH 0530/2308] awk, ocaml: Add number?, fn?, macro? --- awk/core.awk | 29 +++++++++++++++++++++++++++++ ocaml/core.ml | 15 +++++++++++++++ ocaml/step8_macros.ml | 6 ++---- ocaml/step9_try.ml | 6 ++---- ocaml/stepA_mal.ml | 6 ++---- 5 files changed, 50 insertions(+), 12 deletions(-) diff --git a/awk/core.awk b/awk/core.awk index f7c03de989..ee34f16596 100644 --- a/awk/core.awk +++ b/awk/core.awk @@ -127,6 +127,32 @@ function core_keywordp(idx) return types_heap[idx][1] ~ /^:/ ? "#true" : "#false" } +function core_numberp(idx) +{ + if (types_heap[idx]["len"] != 2) { + return "!\"Invalid argument length for builtin function 'number?'. Expects exactly 1 argument, supplied " (types_heap[idx]["len"] - 1) "." + } + return types_heap[idx][1] ~ /^\+/ ? "#true" : "#false" +} + +function core_fnp(idx) +{ + if (types_heap[idx]["len"] != 2) { + return "!\"Invalid argument length for builtin function 'fn?'. Expects exactly 1 argument, supplied " (types_heap[idx]["len"] - 1) "." + } + f = types_heap[idx][1] + return f ~ /^[$&%]/ && !types_heap[substr(f, 2)]["is_macro"] ? "#true" : "#false" +} + +function core_macrop(idx) +{ + if (types_heap[idx]["len"] != 2) { + return "!\"Invalid argument length for builtin function 'macro?'. Expects exactly 1 argument, supplied " (types_heap[idx]["len"] - 1) "." + } + f = types_heap[idx][1] + return f ~ /^\$/ && types_heap[substr(f, 2)]["is_macro"] ? "#true" : "#false" +} + function core_pr_str(idx, i, len, result) @@ -1028,6 +1054,9 @@ function core_init() core_ns["'symbol?"] = "&core_symbolp" core_ns["'keyword"] = "&core_keyword" core_ns["'keyword?"] = "&core_keywordp" + core_ns["'number?"] = "&core_numberp" + core_ns["'fn?"] = "&core_fnp" + core_ns["'macro?"] = "&core_macrop" core_ns["'pr-str"] = "&core_pr_str" core_ns["'str"] = "&core_str" diff --git a/ocaml/core.ml b/ocaml/core.ml index b1633406fc..5c6f19761f 100644 --- a/ocaml/core.ml +++ b/ocaml/core.ml @@ -1,6 +1,8 @@ module T = Types.Types let ns = Env.make None +let kw_macro = T.Keyword "macro" + let num_fun t f = Types.fn (function | [(T.Int a); (T.Int b)] -> t (f a b) @@ -146,6 +148,19 @@ let init env = begin (Types.fn (function [T.String x] -> T.Keyword x | _ -> T.Nil)); Env.set env (Types.symbol "keyword?") (Types.fn (function [T.Keyword _] -> T.Bool true | _ -> T.Bool false)); + Env.set env (Types.symbol "number?") + (Types.fn (function [T.Int _] -> T.Bool true | _ -> T.Bool false)); + Env.set env (Types.symbol "fn?") + (Types.fn (function + | [T.Fn { T.meta = T.Map { T.value = meta } }] + -> mk_bool (not (Types.MalMap.mem kw_macro meta && Types.to_bool (Types.MalMap.find kw_macro meta))) + | [T.Fn _] -> T.Bool true + | _ -> T.Bool false)); + Env.set env (Types.symbol "macro?") + (Types.fn (function + | [T.Fn { T.meta = T.Map { T.value = meta } }] + -> mk_bool (Types.MalMap.mem kw_macro meta && Types.to_bool (Types.MalMap.find kw_macro meta)) + | _ -> T.Bool false)); Env.set env (Types.symbol "nil?") (Types.fn (function [T.Nil] -> T.Bool true | _ -> T.Bool false)); Env.set env (Types.symbol "true?") diff --git a/ocaml/step8_macros.ml b/ocaml/step8_macros.ml index 93113e5ad5..92ee6308d5 100644 --- a/ocaml/step8_macros.ml +++ b/ocaml/step8_macros.ml @@ -14,14 +14,12 @@ let rec quasiquote ast = Types.list [Types.symbol "cons"; quasiquote head; quasiquote (Types.list tail) ] | ast -> Types.list [Types.symbol "quote"; ast] -let kw_macro = T.Keyword "macro" - let is_macro_call ast env = match ast with | T.List { T.value = s :: args } -> (match (try Env.get env s with _ -> T.Nil) with | T.Fn { T.meta = T.Map { T.value = meta } } - -> Types.MalMap.mem kw_macro meta && Types.to_bool (Types.MalMap.find kw_macro meta) + -> Types.MalMap.mem Core.kw_macro meta && Types.to_bool (Types.MalMap.find Core.kw_macro meta) | _ -> false) | _ -> false @@ -61,7 +59,7 @@ and eval ast env = | T.List { T.value = [(T.Symbol { T.value = "defmacro!" }); key; expr] } -> (match (eval expr env) with | T.Fn { T.value = f; T.meta = meta } -> - let fn = T.Fn { T.value = f; meta = Core.assoc [meta; kw_macro; (T.Bool true)]} + let fn = T.Fn { T.value = f; meta = Core.assoc [meta; Core.kw_macro; (T.Bool true)]} in Env.set env key fn; fn | _ -> raise (Invalid_argument "defmacro! value must be a fn")) | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.Vector { T.value = bindings }); body] } diff --git a/ocaml/step9_try.ml b/ocaml/step9_try.ml index 1885359ffe..61cee2decc 100644 --- a/ocaml/step9_try.ml +++ b/ocaml/step9_try.ml @@ -14,14 +14,12 @@ let rec quasiquote ast = Types.list [Types.symbol "cons"; quasiquote head; quasiquote (Types.list tail) ] | ast -> Types.list [Types.symbol "quote"; ast] -let kw_macro = T.Keyword "macro" - let is_macro_call ast env = match ast with | T.List { T.value = s :: args } -> (match (try Env.get env s with _ -> T.Nil) with | T.Fn { T.meta = T.Map { T.value = meta } } - -> Types.MalMap.mem kw_macro meta && Types.to_bool (Types.MalMap.find kw_macro meta) + -> Types.MalMap.mem Core.kw_macro meta && Types.to_bool (Types.MalMap.find Core.kw_macro meta) | _ -> false) | _ -> false @@ -61,7 +59,7 @@ and eval ast env = | T.List { T.value = [(T.Symbol { T.value = "defmacro!" }); key; expr] } -> (match (eval expr env) with | T.Fn { T.value = f; T.meta = meta } -> - let fn = T.Fn { T.value = f; meta = Core.assoc [meta; kw_macro; (T.Bool true)]} + let fn = T.Fn { T.value = f; meta = Core.assoc [meta; Core.kw_macro; (T.Bool true)]} in Env.set env key fn; fn | _ -> raise (Invalid_argument "devmacro! value must be a fn")) | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.Vector { T.value = bindings }); body] } diff --git a/ocaml/stepA_mal.ml b/ocaml/stepA_mal.ml index c36e6c87e3..a8cbd1c3a3 100644 --- a/ocaml/stepA_mal.ml +++ b/ocaml/stepA_mal.ml @@ -14,14 +14,12 @@ let rec quasiquote ast = Types.list [Types.symbol "cons"; quasiquote head; quasiquote (Types.list tail) ] | ast -> Types.list [Types.symbol "quote"; ast] -let kw_macro = T.Keyword "macro" - let is_macro_call ast env = match ast with | T.List { T.value = s :: args } -> (match (try Env.get env s with _ -> T.Nil) with | T.Fn { T.meta = T.Map { T.value = meta } } - -> Types.MalMap.mem kw_macro meta && Types.to_bool (Types.MalMap.find kw_macro meta) + -> Types.MalMap.mem Core.kw_macro meta && Types.to_bool (Types.MalMap.find Core.kw_macro meta) | _ -> false) | _ -> false @@ -61,7 +59,7 @@ and eval ast env = | T.List { T.value = [(T.Symbol { T.value = "defmacro!" }); key; expr] } -> (match (eval expr env) with | T.Fn { T.value = f; T.meta = meta } -> - let fn = T.Fn { T.value = f; meta = Core.assoc [meta; kw_macro; (T.Bool true)]} + let fn = T.Fn { T.value = f; meta = Core.assoc [meta; Core.kw_macro; (T.Bool true)]} in Env.set env key fn; fn | _ -> raise (Invalid_argument "devmacro! value must be a fn")) | T.List { T.value = [(T.Symbol { T.value = "let*" }); (T.Vector { T.value = bindings }); body] } From 51796fd84f4c4a0193f4c5f7da53cd04d9eec26f Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Sun, 15 Oct 2017 12:07:40 +0000 Subject: [PATCH 0531/2308] crystal, logo: Add number?, fn?, macro? --- crystal/core.cr | 17 +++++++++++++++++ logo/core.lg | 20 ++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/crystal/core.cr b/crystal/core.cr index b7a5c3c931..2694cf8542 100644 --- a/crystal/core.cr +++ b/crystal/core.cr @@ -188,6 +188,20 @@ def self.keyword?(args) head.is_a?(String) && !head.empty? && head[0] == '\u029e' end +def self.number?(args) + args.first.unwrap.is_a?(Int64) +end + +def self.fn?(args) + return false if args.first.macro? + head = args.first.unwrap + head.is_a?(Mal::Func) || head.is_a?(Mal::Closure) +end + +def self.macro?(args) + args.first.macro? +end + def self.vector(args) args.to_mal(Mal::Vector) end @@ -411,6 +425,9 @@ NS = { "string?" => func(:string?), "keyword" => func(:keyword), "keyword?" => func(:keyword?), + "number?" => func(:number?), + "fn?" => func(:fn?), + "macro?" => func(:macro?), "vector" => func(:vector), "vector?" => func(:vector?), "hash-map" => func(:hash_map), diff --git a/logo/core.lg b/logo/core.lg index 2d807fd662..6860cdd91e 100644 --- a/logo/core.lg +++ b/logo/core.lg @@ -49,6 +49,23 @@ to mal_keyword_q :a output bool_to_mal ((obj_type :a) = "keyword) end +to mal_number_q :a +output bool_to_mal ((obj_type :a) = "number) +end + +to mal_fn_q :a +case obj_type :a [ + [[nativefn] output true_new ] + [[fn] output bool_to_mal not fn_is_macro :a] + [else output false_new ] +] +end + +to mal_macro_q :a +if ((obj_type :a) = "fn) [ output bool_to_mal fn_is_macro :a ] +output false_new +end + to mal_pr_str [:args] output obj_new "string pr_seq :args "true " " :space_char end @@ -355,6 +372,9 @@ make "core_ns [ [[symbol symbol?] [nativefn mal_symbol_q]] [[symbol keyword] [nativefn mal_keyword]] [[symbol keyword?] [nativefn mal_keyword_q]] + [[symbol number?] [nativefn mal_number_q]] + [[symbol fn?] [nativefn mal_fn_q]] + [[symbol macro?] [nativefn mal_macro_q]] [[symbol pr-str] [nativefn mal_pr_str]] [[symbol str] [nativefn mal_str]] From f55aec105e9ae002d9db98c58bed8135c0237d93 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Mon, 16 Oct 2017 06:18:44 +0000 Subject: [PATCH 0532/2308] ps, rust: Add number?, fn?, macro? --- ps/core.ps | Bin 8131 -> 8534 bytes rust/src/core.rs | 3 +++ rust/src/types.rs | 27 +++++++++++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/ps/core.ps b/ps/core.ps index f5b6e04989321319fb2d6570564408edb5461c0d..de77ab27d56cfbbed9b88874730d6ff13bf01a2f 100644 GIT binary patch delta 417 zcma)2J8r^25baXz2`FEKWGd59TFMD%qZHO+&yrQ_HSDKAmcT}V$(h*X&jtpMi8jsN1J3>)ONta24h;dK+|m}-S_ zk8wa+%fdDp*5u)BAkY@3@jW=cwY1+coV4MK+spEJJAUyA2`L#?zW? f|KTLMKBI*5okbd>W7N;?a25~RA)E7ka(jCK_y30) delta 16 YcmccSbl85v#LdSUqa-%hN!?%r07R?@QUCw| diff --git a/rust/src/core.rs b/rust/src/core.rs index 00e3532d67..a598f6cf17 100644 --- a/rust/src/core.rs +++ b/rust/src/core.rs @@ -499,6 +499,9 @@ pub fn ns() -> HashMap { ns.insert("symbol?".to_string(), func(types::symbol_q)); ns.insert("keyword".to_string(), func(types::_keyword)); ns.insert("keyword?".to_string(), func(types::keyword_q)); + ns.insert("number?".to_string(), func(types::int_q)); + ns.insert("fn?".to_string(), func(types::fn_q)); + ns.insert("macro?".to_string(), func(types::macro_q)); ns.insert("pr-str".to_string(), func(pr_str)); ns.insert("str".to_string(), func(str)); diff --git a/rust/src/types.rs b/rust/src/types.rs index b85affa338..0fdf8de21e 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -207,6 +207,15 @@ pub fn string_q(a:Vec) -> MalRet { } pub fn _int(i: isize) -> MalVal { Rc::new(Int(i)) } +pub fn int_q(a:Vec) -> MalRet { + if a.len() != 1 { + return err_str("Wrong arity to number? call"); + } + match *a[0] { + Int(_) => Ok(_true()), + _ => Ok(_false()), + } +} // Symbols @@ -371,6 +380,24 @@ pub fn malfunc(eval: fn(MalVal, Env) -> MalRet, pub fn malfuncd(mfd: MalFuncData, meta: MalVal) -> MalVal { Rc::new(MalFunc(mfd,meta)) } +pub fn fn_q(a:Vec) -> MalRet { + if a.len() != 1 { + return err_str("Wrong arity to fn? call"); + } + match *a[0] { + Func(..) | MalFunc(MalFuncData { is_macro: false, .. }, _) => Ok(_true()), + _ => Ok(_false()), + } +} +pub fn macro_q(a:Vec) -> MalRet { + if a.len() != 1 { + return err_str("Wrong arity to macro? call"); + } + match *a[0] { + MalFunc(MalFuncData { is_macro: true, .. }, _) => Ok(_true()), + _ => Ok(_false()), + } +} // Atoms From c1709fad0bbd6bd3b253ba2573898e7f1178cf76 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Mon, 16 Oct 2017 21:56:21 +0000 Subject: [PATCH 0533/2308] d, nim: Add number?, fn?, macro? --- d/mal_core.d | 21 +++++++++++++++++++++ nim/core.nim | 3 +++ nim/step8_macros.nim | 2 +- nim/step9_try.nim | 2 +- nim/stepA_mal.nim | 2 +- nim/types.nim | 11 ++++++++++- 6 files changed, 37 insertions(+), 4 deletions(-) diff --git a/d/mal_core.d b/d/mal_core.d index 561b4571b5..2d14fb5cf3 100644 --- a/d/mal_core.d +++ b/d/mal_core.d @@ -53,6 +53,24 @@ static MalType mal_keyword_q(MalType[] a ...) return bool_to_mal(s.is_keyword()); } +static MalType mal_fn_q(MalType[] a ...) +{ + verify_args_count(a, 1); + auto builtinfn = cast(MalBuiltinFunc) a[0]; + if (builtinfn !is null) return mal_true; + auto malfunc = cast(MalFunc) a[0]; + if (malfunc !is null) return bool_to_mal(!malfunc.is_macro); + return mal_false; +} + +static MalType mal_macro_q(MalType[] a ...) +{ + verify_args_count(a, 1); + auto malfunc = cast(MalFunc) a[0]; + if (malfunc !is null) return bool_to_mal(malfunc.is_macro); + return mal_false; +} + static MalType mal_pr_str(MalType[] a ...) { auto items_strs = a.map!(e => pr_str(e, true)); @@ -341,6 +359,9 @@ static this() "string?": &mal_string_q, "keyword": &mal_keyword, "keyword?": &mal_keyword_q, + "number?": (a ...) => mal_type_q!MalInteger(a), + "fn?": &mal_fn_q, + "macro?": &mal_macro_q, "pr-str": &mal_pr_str, "str": &mal_str, diff --git a/nim/core.nim b/nim/core.nim index 777e243e76..e9921898ef 100644 --- a/nim/core.nim +++ b/nim/core.nim @@ -224,6 +224,9 @@ let ns* = { "symbol?": fun symbol_q, "keyword": fun keyword, "keyword?": fun keyword_q, + "number?": fun number_q, + "fn?": fun fn_q, + "macro?": fun macro_q, "with-meta": fun with_meta, "meta": fun meta, diff --git a/nim/step8_macros.nim b/nim/step8_macros.nim index 0f2d0fb0a7..0cd6776778 100644 --- a/nim/step8_macros.nim +++ b/nim/step8_macros.nim @@ -18,7 +18,7 @@ proc quasiquote(ast: MalType): MalType = proc is_macro_call(ast: MalType, env: Env): bool = ast.kind == List and ast.list[0].kind == Symbol and - env.find(ast.list[0].str) != nil and env.get(ast.list[0].str).macro_q + env.find(ast.list[0].str) != nil and env.get(ast.list[0].str).fun_is_macro proc macroexpand(ast: MalType, env: Env): MalType = result = ast diff --git a/nim/step9_try.nim b/nim/step9_try.nim index 763d67d685..97884ddc16 100644 --- a/nim/step9_try.nim +++ b/nim/step9_try.nim @@ -18,7 +18,7 @@ proc quasiquote(ast: MalType): MalType = proc is_macro_call(ast: MalType, env: Env): bool = ast.kind == List and ast.list[0].kind == Symbol and - env.find(ast.list[0].str) != nil and env.get(ast.list[0].str).macro_q + env.find(ast.list[0].str) != nil and env.get(ast.list[0].str).fun_is_macro proc macroexpand(ast: MalType, env: Env): MalType = result = ast diff --git a/nim/stepA_mal.nim b/nim/stepA_mal.nim index 7f5f9b6eed..abc3fbc1f6 100644 --- a/nim/stepA_mal.nim +++ b/nim/stepA_mal.nim @@ -18,7 +18,7 @@ proc quasiquote(ast: MalType): MalType = proc is_macro_call(ast: MalType, env: Env): bool = ast.kind == List and ast.list.len > 0 and ast.list[0].kind == Symbol and - env.find(ast.list[0].str) != nil and env.get(ast.list[0].str).macro_q + env.find(ast.list[0].str) != nil and env.get(ast.list[0].str).fun_is_macro proc macroexpand(ast: MalType, env: Env): MalType = result = ast diff --git a/nim/types.nim b/nim/types.nim index aeae5ea320..48f442b621 100644 --- a/nim/types.nim +++ b/nim/types.nim @@ -64,7 +64,7 @@ proc hash_map*(xs: varargs[MalType]): MalType {.procvar.} = else: xs[i].str result.hash_map[s] = xs[i+1] -proc macro_q*(x: MalType): bool = +proc fun_is_macro*(x: MalType): bool = if x.kind == Fun: result = x.is_macro elif x.kind == MalFun: result = x.malfun.is_macro else: raise newException(ValueError, "no function") @@ -124,6 +124,15 @@ proc keyword*(xs: varargs[MalType]): MalType {.procvar.} = proc keyword_q*(xs: varargs[MalType]): MalType {.procvar.} = boolObj(xs[0].kind == String and xs[0].str[0] == '\xff') +proc number_q*(xs: varargs[MalType]): MalType {.procvar.} = + boolObj xs[0].kind == Number + +proc fn_q*(xs: varargs[MalType]): MalType {.procvar.} = + boolObj((xs[0].kind == MalFun or xs[0].kind == Fun) and not xs[0].fun_is_macro) + +proc macro_q*(xs: varargs[MalType]): MalType {.procvar.} = + boolObj((xs[0].kind == MalFun or xs[0].kind == Fun) and xs[0].fun_is_macro) + proc atom*(xs: varargs[MalType]): MalType {.procvar.} = atom(xs[0]) From 85657c96323ab5da579a7a0fff7ac82a182e74fc Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 17 Oct 2017 01:29:08 +0200 Subject: [PATCH 0534/2308] common-lisp, es6, guile, java, mal: Add number?, fn?, macro? --- common-lisp/src/core.lisp | 12 ++++++++++++ common-lisp/src/step5_tco.lisp | 12 ++++++------ common-lisp/src/step6_file.lisp | 12 ++++++------ common-lisp/src/step7_quote.lisp | 12 ++++++------ common-lisp/src/step8_macros.lisp | 18 +++++++++--------- common-lisp/src/step9_try.lisp | 18 +++++++++--------- common-lisp/src/stepA_mal.lisp | 18 +++++++++--------- es6/core.mjs | 3 +++ guile/core.scm | 3 +++ guile/step6_file.scm | 2 +- guile/step7_quote.scm | 2 +- guile/step8_macros.scm | 4 ++-- guile/step9_try.scm | 4 ++-- guile/stepA_mal.scm | 4 ++-- guile/types.scm | 8 +++++--- java/src/main/java/mal/core.java | 20 ++++++++++++++++++++ mal/core.mal | 17 +++++++++++++++++ 17 files changed, 113 insertions(+), 56 deletions(-) diff --git a/common-lisp/src/core.lisp b/common-lisp/src/core.lisp index 06c7a03596..74eeb77210 100644 --- a/common-lisp/src/core.lisp +++ b/common-lisp/src/core.lisp @@ -174,6 +174,9 @@ (defmal false? (value) (wrap-boolean (and (mal-boolean-p value) (not (mal-data-value value))))) +(defmal number? (value) + (wrap-boolean (mal-number-p value))) + (defmal symbol (string) (make-mal-symbol (mal-data-value string))) @@ -194,6 +197,15 @@ (defmal vector? (value) (wrap-boolean (mal-vector-p value))) +(defmal fn? (value) + (wrap-boolean (or (mal-builtin-fn-p value) + (and (mal-fn-p value) + (not (cdr (assoc :is-macro (mal-data-attrs value)))))))) + +(defmal macro? (value) + (wrap-boolean (and (mal-fn-p value) + (cdr (assoc :is-macro (mal-data-attrs value)))))) + (defmal hash-map (&rest elements) (let ((hash-map (make-mal-value-hash-table))) (loop for (key value) on elements diff --git a/common-lisp/src/step5_tco.lisp b/common-lisp/src/step5_tco.lisp index b0d9ece2d5..be68074299 100644 --- a/common-lisp/src/step5_tco.lisp +++ b/common-lisp/src/step5_tco.lisp @@ -102,9 +102,9 @@ (mal-eval body (env:create-mal-env :parent env :binds (listify (mal-data-value arglist)) :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env)))))) + :attrs (list (cons :params arglist) + (cons :ast body) + (cons :env env)))))) (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) @@ -113,11 +113,11 @@ (return (apply (mal-data-value function) (cdr evaluated-list))) (let* ((attrs (mal-data-attrs function))) - (setf ast (cdr (assoc 'ast attrs)) - env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + (setf ast (cdr (assoc :ast attrs)) + env (env:create-mal-env :parent (cdr (assoc :env attrs)) :binds (map 'list #'identity - (mal-data-value (cdr (assoc 'params attrs)))) + (mal-data-value (cdr (assoc :params attrs)))) :exprs (cdr evaluated-list))))))))))))) (defun mal-print (expression) diff --git a/common-lisp/src/step6_file.lisp b/common-lisp/src/step6_file.lisp index 361305ccdd..75b3621a0e 100644 --- a/common-lisp/src/step6_file.lisp +++ b/common-lisp/src/step6_file.lisp @@ -102,9 +102,9 @@ (mal-eval body (env:create-mal-env :parent env :binds (listify (mal-data-value arglist)) :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env)))))) + :attrs (list (cons :params arglist) + (cons :ast body) + (cons :env env)))))) (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) @@ -113,11 +113,11 @@ (return (apply (mal-data-value function) (cdr evaluated-list))) (let* ((attrs (mal-data-attrs function))) - (setf ast (cdr (assoc 'ast attrs)) - env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + (setf ast (cdr (assoc :ast attrs)) + env (env:create-mal-env :parent (cdr (assoc :env attrs)) :binds (map 'list #'identity - (mal-data-value (cdr (assoc 'params attrs)))) + (mal-data-value (cdr (assoc :params attrs)))) :exprs (cdr evaluated-list))))))))))))) (defun mal-print (expression) diff --git a/common-lisp/src/step7_quote.lisp b/common-lisp/src/step7_quote.lisp index ea7d53d28e..cf92fc50df 100644 --- a/common-lisp/src/step7_quote.lisp +++ b/common-lisp/src/step7_quote.lisp @@ -138,9 +138,9 @@ (mal-eval body (env:create-mal-env :parent env :binds (listify (mal-data-value arglist)) :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env)))))) + :attrs (list (cons :params arglist) + (cons :ast body) + (cons :env env)))))) (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) @@ -149,11 +149,11 @@ (return (apply (mal-data-value function) (cdr evaluated-list))) (let* ((attrs (mal-data-attrs function))) - (setf ast (cdr (assoc 'ast attrs)) - env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + (setf ast (cdr (assoc :ast attrs)) + env (env:create-mal-env :parent (cdr (assoc :env attrs)) :binds (map 'list #'identity - (mal-data-value (cdr (assoc 'params attrs)))) + (mal-data-value (cdr (assoc :params attrs)))) :exprs (cdr evaluated-list))))))))))))) (defun mal-print (expression) diff --git a/common-lisp/src/step8_macros.lisp b/common-lisp/src/step8_macros.lisp index cbf47eb78b..e27ac6cca8 100644 --- a/common-lisp/src/step8_macros.lisp +++ b/common-lisp/src/step8_macros.lisp @@ -103,7 +103,7 @@ (env:find-env env func-symbol)))) (and func (mal-fn-p func) - (cdr (assoc 'is-macro (mal-data-attrs func))))))) + (cdr (assoc :is-macro (mal-data-attrs func))))))) (defun mal-macroexpand (ast env) (loop @@ -144,7 +144,7 @@ (env:set-env env (second forms) (progn - (setf (cdr (assoc 'is-macro (mal-data-attrs value))) t) + (setf (cdr (assoc :is-macro (mal-data-attrs value))) t) value)) (error 'invalid-function :form value @@ -186,21 +186,21 @@ (mal-eval body (env:create-mal-env :parent env :binds (listify (mal-data-value arglist)) :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env) - (cons 'is-macro nil)))))) + :attrs (list (cons :params arglist) + (cons :ast body) + (cons :env env) + (cons :is-macro nil)))))) (t (let* ((evaluated-list (eval-ast ast env)) (function (car evaluated-list))) ;; If first element is a mal function unwrap it (cond ((mal-fn-p function) (let* ((attrs (mal-data-attrs function))) - (setf ast (cdr (assoc 'ast attrs)) - env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + (setf ast (cdr (assoc :ast attrs)) + env (env:create-mal-env :parent (cdr (assoc :env attrs)) :binds (map 'list #'identity - (mal-data-value (cdr (assoc 'params attrs)))) + (mal-data-value (cdr (assoc :params attrs)))) :exprs (cdr evaluated-list))))) ((mal-builtin-fn-p function) (return (apply (mal-data-value function) diff --git a/common-lisp/src/step9_try.lisp b/common-lisp/src/step9_try.lisp index 268e50dab6..e6257a3900 100644 --- a/common-lisp/src/step9_try.lisp +++ b/common-lisp/src/step9_try.lisp @@ -106,7 +106,7 @@ (env:find-env env func-symbol)))) (and func (mal-fn-p func) - (cdr (assoc 'is-macro (mal-data-attrs func))))))) + (cdr (assoc :is-macro (mal-data-attrs func))))))) (defun mal-macroexpand (ast env) (loop @@ -147,7 +147,7 @@ (env:set-env env (second forms) (progn - (setf (cdr (assoc 'is-macro (mal-data-attrs value))) t) + (setf (cdr (assoc :is-macro (mal-data-attrs value))) t) value)) (error 'invalid-function :form value @@ -189,10 +189,10 @@ (mal-eval body (env:create-mal-env :parent env :binds (listify (mal-data-value arglist)) :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env) - (cons 'is-macro nil)))))) + :attrs (list (cons :params arglist) + (cons :ast body) + (cons :env env) + (cons :is-macro nil)))))) ((mal-data-value= mal-try* (first forms)) (handler-case @@ -214,11 +214,11 @@ ;; If first element is a mal function unwrap it (cond ((mal-fn-p function) (let* ((attrs (mal-data-attrs function))) - (setf ast (cdr (assoc 'ast attrs)) - env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + (setf ast (cdr (assoc :ast attrs)) + env (env:create-mal-env :parent (cdr (assoc :env attrs)) :binds (map 'list #'identity - (mal-data-value (cdr (assoc 'params attrs)))) + (mal-data-value (cdr (assoc :params attrs)))) :exprs (cdr evaluated-list))))) ((mal-builtin-fn-p function) (return (apply (mal-data-value function) diff --git a/common-lisp/src/stepA_mal.lisp b/common-lisp/src/stepA_mal.lisp index ec189dd30a..73dc67cbef 100644 --- a/common-lisp/src/stepA_mal.lisp +++ b/common-lisp/src/stepA_mal.lisp @@ -105,7 +105,7 @@ (env:find-env env func-symbol)))) (and func (mal-fn-p func) - (cdr (assoc 'is-macro (mal-data-attrs func))))))) + (cdr (assoc :is-macro (mal-data-attrs func))))))) (defun mal-macroexpand (ast env) (loop @@ -146,7 +146,7 @@ (env:set-env env (second forms) (progn - (setf (cdr (assoc 'is-macro (mal-data-attrs value))) t) + (setf (cdr (assoc :is-macro (mal-data-attrs value))) t) value)) (error 'invalid-function :form value @@ -188,10 +188,10 @@ (mal-eval body (env:create-mal-env :parent env :binds (listify (mal-data-value arglist)) :exprs args))) - :attrs (list (cons 'params arglist) - (cons 'ast body) - (cons 'env env) - (cons 'is-macro nil)))))) + :attrs (list (cons :params arglist) + (cons :ast body) + (cons :env env) + (cons :is-macro nil)))))) ((mal-data-value= mal-try* (first forms)) (handler-case @@ -213,11 +213,11 @@ ;; If first element is a mal function unwrap it (cond ((mal-fn-p function) (let* ((attrs (mal-data-attrs function))) - (setf ast (cdr (assoc 'ast attrs)) - env (env:create-mal-env :parent (cdr (assoc 'env attrs)) + (setf ast (cdr (assoc :ast attrs)) + env (env:create-mal-env :parent (cdr (assoc :env attrs)) :binds (map 'list #'identity - (mal-data-value (cdr (assoc 'params attrs)))) + (mal-data-value (cdr (assoc :params attrs)))) :exprs (cdr evaluated-list))))) ((mal-builtin-fn-p function) (return (apply (mal-data-value function) diff --git a/es6/core.mjs b/es6/core.mjs index 7b9e1f5219..64a7436aa4 100644 --- a/es6/core.mjs +++ b/es6/core.mjs @@ -44,11 +44,14 @@ export const core_ns = new Map([ ['nil?', a => a === null], ['true?', a => a === true], ['false?', a => a === false], + ['number?', a => typeof a === 'number'], ['string?', a => typeof a === "string" && !_keyword_Q(a)], ['symbol', a => Symbol.for(a)], ['symbol?', a => typeof a === 'symbol'], ['keyword', _keyword], ['keyword?', _keyword_Q], + ['fn?', a => typeof a === 'function' && !a.ismacro ], + ['macro?', a => typeof a === 'function' && !!a.ismacro ], ['pr-str', (...a) => a.map(e => pr_str(e,1)).join(' ')], ['str', (...a) => a.map(e => pr_str(e,0)).join('')], diff --git a/guile/core.scm b/guile/core.scm index f5b485eb13..1ba7c7048e 100644 --- a/guile/core.scm +++ b/guile/core.scm @@ -235,6 +235,7 @@ (nil? ,_nil?) (true? ,_true?) (false? ,_false?) + (number? ,number?) (symbol? ,symbol?) (symbol ,->symbol) (string? ,_string?) @@ -251,6 +252,8 @@ (vals ,_vals) (contains? ,_contains?) (sequential? ,_sequential?) + (fn? ,is-func?) + (macro? ,is-macro?) (readline ,__readline) (meta ,_meta) (with-meta ,_with-meta) diff --git a/guile/step6_file.scm b/guile/step6_file.scm index 3fb7b651be..ccb4a0adb3 100644 --- a/guile/step6_file.scm +++ b/guile/step6_file.scm @@ -37,7 +37,7 @@ (define (eval_func ast env) (define (_eval o) (EVAL o env)) - (define (func? x) (and=> ((env 'get) x) is-func?)) + (define (func? x) (and=> ((env 'get) x) is-func)) (cond ((func? (car ast)) => (lambda (c) diff --git a/guile/step7_quote.scm b/guile/step7_quote.scm index 2cebf4493c..bffd8de22c 100644 --- a/guile/step7_quote.scm +++ b/guile/step7_quote.scm @@ -37,7 +37,7 @@ (define (eval_func ast env) (define (_eval o) (EVAL o env)) - (define (func? x) (and=> ((env 'get) x) is-func?)) + (define (func? x) (and=> ((env 'get) x) is-func)) (cond ((func? (car ast)) => (lambda (c) diff --git a/guile/step8_macros.scm b/guile/step8_macros.scm index af36411f49..aa098c412c 100644 --- a/guile/step8_macros.scm +++ b/guile/step8_macros.scm @@ -37,7 +37,7 @@ (define (eval_func ast env) (define (_eval o) (EVAL o env)) - (define (func? x) (and=> ((env 'get) x) is-func?)) + (define (func? x) (and=> ((env 'get) x) is-func)) (cond ((func? (car ast)) => (lambda (c) @@ -55,7 +55,7 @@ (define (is_macro_call ast env) (and (list? ast) (> (length ast) 0) - (and=> (env-check (car ast) env) is-macro?))) + (and=> (env-check (car ast) env) is-macro))) (define (_macroexpand ast env) (cond diff --git a/guile/step9_try.scm b/guile/step9_try.scm index 01ab6deaa9..33de5622c3 100644 --- a/guile/step9_try.scm +++ b/guile/step9_try.scm @@ -37,7 +37,7 @@ (define (eval_func ast env) (define (_eval o) (EVAL o env)) - (define (func? x) (and=> (env-check x env) is-func?)) + (define (func? x) (and=> (env-check x env) is-func)) ;;(format #t "AAA: ~a~%" (func? (car ast))) (cond ((func? (car ast)) @@ -56,7 +56,7 @@ (define (is_macro_call ast env) (and (list? ast) (> (length ast) 0) - (and=> (env-check (car ast) env) is-macro?))) + (and=> (env-check (car ast) env) is-macro))) (define (_macroexpand ast env) (cond diff --git a/guile/stepA_mal.scm b/guile/stepA_mal.scm index 98127ae252..1f9bf0e3bc 100644 --- a/guile/stepA_mal.scm +++ b/guile/stepA_mal.scm @@ -52,7 +52,7 @@ x))) (if (callable? f) f - (and=> (env-check f env) is-func?)))) + (and=> (env-check f env) is-func)))) (cond ((func? (car ast)) => (lambda (c) @@ -70,7 +70,7 @@ (define (is_macro_call ast env) (and (list? ast) (> (length ast) 0) - (and=> (env-check (car ast) env) is-macro?))) + (and=> (env-check (car ast) env) is-macro))) (define (_macroexpand ast env) (cond diff --git a/guile/types.scm b/guile/types.scm index 90ab01c41c..7329cd5ce4 100644 --- a/guile/types.scm +++ b/guile/types.scm @@ -21,7 +21,7 @@ make-atom atom? atom-val atom-val-set! make-callable callable? callable-is_macro callable-is_macro-set! callable-closure - is-func? is-macro? make-func callable-apply + is-func is-func? is-macro is-macro? make-func callable-apply callable-unbox-set! callable-unbox callable-meta-info hash-table-clone box? box unbox) @@ -77,8 +77,10 @@ (eq? (callable-is_macro c) b) c)) -(define (is-func? c) (callable-check c #f)) -(define (is-macro? c) (callable-check c #t)) +(define (is-func c) (callable-check c #f)) +(define (is-func? c) (and (is-func c) #t)) +(define (is-macro c) (callable-check c #t)) +(define (is-macro? c) (and (is-macro c) #t)) (define (hash-table-clone ht) (list->hash-map (hash-fold (lambda (k v p) (cons k (cons v p))) '() ht))) diff --git a/java/src/main/java/mal/core.java b/java/src/main/java/mal/core.java index 3ac6cce292..23843b8a6e 100644 --- a/java/src/main/java/mal/core.java +++ b/java/src/main/java/mal/core.java @@ -49,6 +49,11 @@ public MalVal apply(MalList args) throws MalThrowable { return args.nth(0) == False ? True : False; } }; + static MalFunction number_Q = new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return args.nth(0) instanceof MalInteger ? True : False; + } + }; static MalFunction string_Q = new MalFunction() { public MalVal apply(MalList args) throws MalThrowable { if (!(args.nth(0) instanceof MalString)) { return False; } @@ -87,6 +92,18 @@ public MalVal apply(MalList args) throws MalThrowable { return True; } }; + static MalFunction fn_Q = new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + if (!(args.nth(0) instanceof MalFunction)) { return False; } + return ((MalFunction)args.nth(0)).isMacro() ? False : True; + } + }; + static MalFunction macro_Q = new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + if (!(args.nth(0) instanceof MalFunction)) { return False; } + return ((MalFunction)args.nth(0)).isMacro() ? True : False; + } + }; // String functions @@ -545,11 +562,14 @@ public MalVal apply(MalList a) throws MalThrowable { .put("nil?", nil_Q) .put("true?", true_Q) .put("false?", false_Q) + .put("number?", number_Q) .put("string?", string_Q) .put("symbol", symbol) .put("symbol?", symbol_Q) .put("keyword", keyword) .put("keyword?", keyword_Q) + .put("fn?", fn_Q) + .put("macro?", macro_Q) .put("pr-str", pr_str) .put("str", str) diff --git a/mal/core.mal b/mal/core.mal index a766b80125..15bcac3ec0 100644 --- a/mal/core.mal +++ b/mal/core.mal @@ -1,14 +1,31 @@ +(def! _fn? (fn* [x] + (if (fn? x) + (if (get (meta x) "ismacro") + false + true) + false))) + +(def! macro? (fn* [x] + (if (fn? x) + (if (get (meta x) "ismacro") + true + false) + false))) + (def! core_ns [["=" =] ["throw" throw] ["nil?" nil?] ["true?" true?] ["false?" false?] + ["number?" number?] ["string?" string?] ["symbol" symbol] ["symbol?" symbol?] ["keyword" keyword] ["keyword?" keyword?] + ["fn?" _fn?] + ["macro?" macro?] ["pr-str" pr-str] ["str" str] From f18417417c17e1023bc7e70d18bf85e5206d3097 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Tue, 17 Oct 2017 05:54:16 +0000 Subject: [PATCH 0535/2308] io, swift3, vb: Add number?, fn?, macro? --- io/MalCore.io | 4 ++++ swift3/Sources/core.swift | 19 +++++++++++++++++++ vb/core.vb | 27 +++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/io/MalCore.io b/io/MalCore.io index 2a99470981..9841996e69 100644 --- a/io/MalCore.io +++ b/io/MalCore.io @@ -83,6 +83,10 @@ MalCore := Object clone do( "symbol?", block(a, a at(0) type == "MalSymbol"), "keyword", block(a, MalKeyword with(a at(0))), "keyword?", block(a, a at(0) type == "MalKeyword"), + "number?", block(a, a at(0) type == "Number"), + "fn?", block(a, (a at(0) type == "Block") or + ((a at(0) type == "MalFunc") and (a at(0) isMacro not))), + "macro?", block(a, (a at(0) type == "MalFunc") and (a at(0) isMacro)), "pr-str", block(a, a map(s, s malPrint(true)) join(" ")), "str", block(a, a map(s, s malPrint(false)) join("")), diff --git a/swift3/Sources/core.swift b/swift3/Sources/core.swift index fc4235b74c..dcf5e300dc 100644 --- a/swift3/Sources/core.swift +++ b/swift3/Sources/core.swift @@ -85,6 +85,25 @@ let core_ns: Dictionary) throws -> MalVal> = [ default: return MV.MalFalse } }, + "number?": { + switch $0[0] { + case MV.MalInt(_): return MV.MalTrue + default: return MV.MalFalse + } + }, + "fn?": { + switch $0[0] { + case MalVal.MalFunc(_, nil, _, _, _, _), + MalVal.MalFunc(_, _, _, _, false, _): return MV.MalTrue + default: return MV.MalFalse + } + }, + "macro?": { + switch $0[0] { + case MalVal.MalFunc(_, _, _, _, true, _): return MV.MalTrue + default: return MV.MalFalse + } + }, "pr-str": { // TODO: if the following two statements are combined into one, we get diff --git a/vb/core.vb b/vb/core.vb index 27c6974c9d..6534de5a69 100644 --- a/vb/core.vb +++ b/vb/core.vb @@ -104,6 +104,30 @@ Namespace Mal End If End Function + Shared Function number_Q(a As MalList) As MalVal + If TypeOf a(0) Is MalInt Then + return MalTrue + Else + return MalFalse + End If + End Function + + Shared Function fn_Q(a As MalList) As MalVal + If TypeOf a(0) Is MalFunc AndAlso Not DirectCast(a(0),MalFunc).isMacro() Then + return MalTrue + Else + return MalFalse + End If + End Function + + Shared Function macro_Q(a As MalList) As MalVal + If TypeOf a(0) Is MalFunc AndAlso DirectCast(a(0),MalFunc).isMacro() Then + return MalTrue + Else + return MalFalse + End If + End Function + ' Number functions Shared Function lt(a As MalList) As MalVal @@ -454,6 +478,9 @@ Namespace Mal ns.Add("string?", New MalFunc(AddressOf string_Q)) ns.Add("keyword", new MalFunc(AddressOf keyword)) ns.Add("keyword?", New MalFunc(AddressOf keyword_Q)) + ns.Add("number?", New MalFunc(AddressOf number_Q)) + ns.Add("fn?", New MalFunc(AddressOf fn_Q)) + ns.Add("macro?", New MalFunc(AddressOf macro_Q)) ns.Add("pr-str",New MalFunc(AddressOf pr_str)) ns.Add("str", New MalFunc(AddressOf str)) From 684d814dd870b524638947d0767ac5018c91cfd3 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 18 Oct 2017 00:14:31 +0100 Subject: [PATCH 0536/2308] String concatenation working Progress on printer, not yet working. string_append_string now appends one string to the end of another, handling long strings with multiple Array blocks. --- nasm/printer.asm | 42 ++++++++++++ nasm/reader.asm | 16 +++++ nasm/step1_read_print.asm | 139 +++++++++++++++++++++++++++++++++++++- 3 files changed, 194 insertions(+), 3 deletions(-) diff --git a/nasm/printer.asm b/nasm/printer.asm index 11dd2b4d10..15e91fb500 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -75,6 +75,48 @@ pr_str: call itostring ret .list: + mov r12, rsi ; Input list + + call string_new ; String in rax + ; Put '(' onto string + mov rsi, rax + mov cl, '(' + call string_append_char + + ; loop through list + push rsi ; Save output string + + ; Extract values and print + ; mov bl, BYTE [r12] + ; xor bl, container_list ; Change from list to value + ; mov BYTE [r12], bl + ; mov rsi, r12 + + mov rsi, r12 + mov BYTE [rsi], maltype_integer + call pr_str ; String in rax + + ; mov bl, BYTE [r12] + ; xor bl, container_list ; Change from value to list + ; mov BYTE [r12], bl + + pop rsi ; Restore output string + ; concatenate strings in rax and rsi + mov rdx, rax ; String to be copied + + push rax + push rbx + push rcx + call string_append_string + pop rcx + pop rbx + pop rax + + ; put ')' at the end of the string + mov cl, ')' + call string_append_char + + mov rax, rsi ret .symbol: ret diff --git a/nasm/reader.asm b/nasm/reader.asm index 8c448a9c3f..bd59de351a 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -82,11 +82,15 @@ read_str: ; Start new list call alloc_cons ; Address in rax + mov [rax], BYTE (block_cons + container_list + content_nil) + + cmp r13, 0 jne .list_link_last ; This is the top-level list mov r15, rax + jmp .list_done .list_link_last: ; The new list is nested @@ -104,6 +108,17 @@ read_str: ; -------------------------------- .list_end: + + ; Check if there is a list + cmp r13, 0 + jne .list_end_ok + + call alloc_cons + mov [rax], BYTE maltype_nil + + ret + +.list_end_ok: ; Put the current list into r8 mov r8, r13 @@ -113,6 +128,7 @@ read_str: jmp .append_object ; Add R8 to list in R13 + ; -------------------------------- .append_object: ; Append Cons in R8 to list in R13 diff --git a/nasm/step1_read_print.asm b/nasm/step1_read_print.asm index 3a60c56686..6a8915a908 100644 --- a/nasm/step1_read_print.asm +++ b/nasm/step1_read_print.asm @@ -190,6 +190,8 @@ section .text ;; Array alloc_array() ;; ;; Returns the address of an Array object in RAX +;; +;; Working registers: rbx alloc_array: ; Get the address of a free array @@ -382,6 +384,7 @@ release_object: string_new: call alloc_array mov [rax], BYTE maltype_string + mov QWORD [rax + Array.next], 0 ret ;; Convert a raw string to a String type @@ -416,6 +419,125 @@ raw_to_string: ;; Appends a character to a string ;; Input: Address of string in RSI, character in CL string_append_char: + mov eax, DWORD [rsi + Array.length] + inc eax + mov DWORD [rsi + Array.length], eax + dec eax + add rax, rsi + add rax, Array.data ; End of data + mov [rax], BYTE cl + ret + +;; Appends a string to the end of a string +;; +;; Input: String to be modified in RSI +;; String to be copied in RDX +;; +;; Output: Modified string in RSI +;; +;; Working registers: +;; rax Array chunk for output (copied to) +;; rbx Array chunk for input (copied from) +;; cl Character being copied +;; r8 Address of destination +;; r9 Destination end address +;; r10 Address of source +;; r11 Source end address +string_append_string: + ; copy source Array address to rbx + mov rbx, rdx + + ; source data address in r10 + mov r10, rbx + add r10, Array.data ; Start of the data + + ; source data end address in r11 + mov r11, r10 + mov r8d, DWORD [rbx + Array.length] + add r11, r8 + + ; Find the end of the string in RSI + ; and put the address of the Array object into rax + mov rax, rsi +.find_string_end: + mov r8, QWORD [rax + Array.next] + cmp r8, 0 ; Next chunk is null + je .got_dest_end ; so reached end + + mov rax, r8 ; Go to next chunk + jmp .find_string_end +.got_dest_end: + + ; destination data address into r8 + mov r8, rax + add r8, Array.data + add r8d, DWORD [rax + Array.length] + + ; destination data end into r9 + mov r9, rax + add r9, Array.size + +.copy_loop: + ; Copy one byte from source to destination + mov cl, BYTE [r10] + mov BYTE [r8], cl + + ; move source to next byte + inc r10 + ; Check if we've reached the end of this Array + cmp r10, r11 + jne .source_ok + + ; have reached the end of the source Array + mov rbx, QWORD [rbx + Array.next] ; Get the next Array address + cmp rbx, 0 ; Test if it's null + je .finished ; No more, so we're done + ; Move on to next Array object + + ; Get source address into r10 + mov r10, rbx + add r10, Array.data ; Start of the data + + ; Source end address + mov r11, rbx + add r11, Array.size + +.source_ok: + + ; Move destination to next byte + inc r8 + ; Check if we've reached end of the Array + cmp r8, r9 + jne .copy_loop ; Next byte + + ; Reached the end of the destination + ; Need to allocate another Array + push rax + push rbx + call alloc_array ; New Array in rax + mov r8, rax ; copy to r8 + pop rbx + pop rax + + ; Previous Array in rax. + ; Add a reference to the new array and set length + mov QWORD [rax + Array.next], r8 + mov DWORD [rax + Array.length], (Array.size - Array.data) + mov rax, r8 ; new array + add r8, Array.data ; Start of data + + mov r9, rax + add r9, Array.size + +.finished: + ; Compare r8 (destination) with data start + ; to get length of string + sub r8, rax + sub r8, Array.data + inc r8 + ; r8 now contains length + mov DWORD [rax + Array.length], r8d + ret ;; ------------------------------------------- @@ -500,9 +622,7 @@ itostring: jnz .divideLoop ; jump if not zero to the label divideLoop ; Get an Array object to put the string into - call alloc_array ; Address in RAX - - mov [rax], BYTE maltype_string ; mark as a string + call string_new ; Address in RAX ; put length into string mov [rax + Array.length], ecx @@ -622,6 +742,19 @@ read_line: _start: + + ; mov rsi, test_string1 + ; mov edx, test_string1.len + ; call raw_to_string ; address in rax + ; push rax + ; mov rsi, test_string2 + ; mov edx, test_string2.len + ; call raw_to_string + ; pop rsi + ; mov rdx, rax + ; call string_append_string + ; call print_string + ; ----------------------------- ; Main loop From 9968eecb8ee92bf6b0409dd5dadc21051e571365 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Thu, 19 Oct 2017 10:06:40 +0000 Subject: [PATCH 0537/2308] fsharp, livescript, ts: Add number?, fn?, macro? --- fsharp/core.fs | 3 +++ fsharp/env.fs | 3 +++ livescript/core.ls | 4 ++++ ts/core.ts | 9 +++++++++ 4 files changed, 19 insertions(+) diff --git a/fsharp/core.fs b/fsharp/core.fs index cbd08206f2..2ab21a745a 100644 --- a/fsharp/core.fs +++ b/fsharp/core.fs @@ -149,6 +149,9 @@ module Core let isSymbol = isPattern (function Symbol(_) -> true | _ -> false) let isKeyword = isPattern (function Keyword(_) -> true | _ -> false) let isString = isPattern (function String(_) -> true | _ -> false) + let isNumber = isPattern (function Number(_) -> true | _ -> false) + let isFn = isPattern (function BuiltInFunc(_, _, _) | Func(_, _, _, _, _, _) -> true | _ -> false) + let isMacro = isPattern (function Macro(_, _, _, _, _, _) -> true | _ -> false) let isSequential = isPattern (function Node.Seq(_) -> true | _ -> false) let isVector = isPattern (function Vector(_, _) -> true | _ -> false) let isMap = isPattern (function Map(_, _) -> true | _ -> false) diff --git a/fsharp/env.fs b/fsharp/env.fs index 73f95afdc5..e82ad92415 100644 --- a/fsharp/env.fs +++ b/fsharp/env.fs @@ -79,6 +79,9 @@ module Env wrap "string?" Core.isString wrap "keyword?" Core.isKeyword wrap "keyword" Core.keyword + wrap "number?" Core.isNumber + wrap "fn?" Core.isFn + wrap "macro?" Core.isMacro wrap "sequential?" Core.isSequential wrap "vector?" Core.isVector wrap "vector" Core.vector diff --git a/livescript/core.ls b/livescript/core.ls index c1bc034838..524d131942 100644 --- a/livescript/core.ls +++ b/livescript/core.ls @@ -229,6 +229,10 @@ export ns = do 'keyword?': fn (ast) -> const-bool ast.type == \keyword + 'number?': fn (ast) -> const-bool ast.type == \int + 'fn?': fn (ast) -> const-bool (ast.type == \function and not ast.is_macro) + 'macro?': fn (ast) -> const-bool (ast.type == \function and ast.is_macro) + 'vector': fn (...params) -> {type: \vector, value: params} 'vector?': fn (ast) -> const-bool ast.type == \vector diff --git a/ts/core.ts b/ts/core.ts index c004e0bc6a..ddf5fcb632 100644 --- a/ts/core.ts +++ b/ts/core.ts @@ -45,6 +45,15 @@ export const ns: Map = (() => { "keyword?"(v: MalType) { return new MalBoolean(v.type === Node.Keyword); }, + "number?"(v: MalType) { + return new MalBoolean(v.type === Node.Number); + }, + "fn?"(v: MalType) { + return new MalBoolean(v.type === Node.Function && !v.isMacro); + }, + "macro?"(v: MalType) { + return new MalBoolean(v.type === Node.Function && v.isMacro); + }, "pr-str"(...args: MalType[]): MalString { return new MalString(args.map(v => prStr(v, true)).join(" ")); From e69ec598954224d97e0cd8a6a0df3f297db631bf Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 19 Oct 2017 23:01:33 +0100 Subject: [PATCH 0538/2308] pr_str prints simple lists, reader improving Can now print (int int) from a list of two Cons objects. Reader code should now read lists, but not yet working. --- nasm/printer.asm | 83 +++++++--- nasm/reader.asm | 338 +++++++++++++++++++++----------------- nasm/step1_read_print.asm | 27 ++- 3 files changed, 268 insertions(+), 180 deletions(-) diff --git a/nasm/printer.asm b/nasm/printer.asm index 15e91fb500..79304ea302 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -18,15 +18,21 @@ section .text ;; ;; Output: Address of string in RAX ;; -;; Modifies: RCX -;; Calls: raw_to_string, +;; Modifies: +;; RCX +;; R12 +;; R13 +;; Calls: raw_to_string, +;; +;; pr_str: ; Get the type mov cl, BYTE [rsi] - + ; Check if it's already a string cmp cl, maltype_string + jne .not_string mov rax, rsi ret @@ -75,45 +81,80 @@ pr_str: call itostring ret .list: + mov r12, rsi ; Input list call string_new ; String in rax + mov r13, rax ; Output string in r13 + ; Put '(' onto string mov rsi, rax mov cl, '(' call string_append_char ; loop through list - push rsi ; Save output string +.list_loop: ; Extract values and print - ; mov bl, BYTE [r12] - ; xor bl, container_list ; Change from list to value - ; mov BYTE [r12], bl - ; mov rsi, r12 - + mov rsi, r12 - mov BYTE [rsi], maltype_integer + mov cl, BYTE [rsi] ; Get type + + ; Check if it's a pointer (address) + mov ch, cl + and ch, content_mask + cmp ch, content_pointer + je .list_loop_pointer + + ; A value (nil, int etc. or function) + xor cl, container_list ; Remove list type -> value + mov BYTE [rsi], cl + + push r13 + push r12 call pr_str ; String in rax + pop r12 + pop r13 - ; mov bl, BYTE [r12] - ; xor bl, container_list ; Change from value to list - ; mov BYTE [r12], bl + mov cl, BYTE [r12] + or cl, container_list ; Restore list type + mov BYTE [r12], cl + jmp .list_loop_got_str +.list_loop_pointer: + mov rsi, [rsi + Cons.car] ; Address of object + push r13 + push r12 + call pr_str ; String in rax + pop r12 + pop r13 - pop rsi ; Restore output string +.list_loop_got_str: ; concatenate strings in rax and rsi + mov rsi, r13 ; Output string mov rdx, rax ; String to be copied - - push rax - push rbx - push rcx + call string_append_string - pop rcx - pop rbx - pop rax + + ; Check if this is the end of the list + mov cl, BYTE [r12 + Cons.typecdr] + cmp cl, content_nil + je .list_finished + + ; More left in the list + + ; Add space between values + mov cl, ' ' + mov rsi, r13 + call string_append_char + + ; Get next Cons + mov r12, [r12 + Cons.cdr] + jmp .list_loop +.list_finished: ; put ')' at the end of the string mov cl, ')' + mov rsi, r13 call string_append_char mov rax, rsi diff --git a/nasm/reader.asm b/nasm/reader.asm index bd59de351a..5507bac7a3 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -10,32 +10,34 @@ section .text ;; Output: Address of object in RAX ;; ;; Uses registers: -;; R13 Address of the current list (starts 0) +;; R12 Address of the start of the current list (starts 0) +;; R13 Address of the current list tail ;; R14 Stack pointer at start. Used for unwinding on error ;; R15 Address of first list. Used for unwinding on error ;; +;; In addition, the tokenizer uses +;; +;; RAX (object return) +;; RBX +;; RCX (character return in CL) +;; RDX +;; R8 ** State must be preserved +;; R9 ** +;; R10 ** +;; read_str: ; Initialise tokenizer call tokenizer_init - ; Get the next token - call tokenizer_next - ; Set current list to zero - mov r13, 0 + mov r12, 0 + + ; Set first list to zero + mov r15, 0 ; Save stack pointer for unwinding mov r14, rsp - ; check what type of token by testing CL - cmp cl, 0 - jne .got_token - - ; No tokens. Return 'nil' - call alloc_cons - mov [rax], BYTE maltype_nil - ret - .read_loop: call tokenizer_next @@ -43,126 +45,170 @@ read_str: jne .got_token ; Unexpected end of tokens - - mov rsp, r14 ; Restore stack - mov rsi, r13 ; Top Cons - call release_cons ; This should delete everything - - call alloc_cons - mov [rax], BYTE maltype_nil - - ret + jmp .unwind .got_token: + + cmp cl, 'i' + je .finished cmp cl, '(' je .list_start cmp cl, ')' - je .list_end - - cmp cl, 'i' - je .append_object ; Cons already in R8 - + je .return_nil ; Note: if reading a list, cl will be tested in the list reader + ; Unknown - call alloc_cons - mov [rax], BYTE maltype_nil - ret + jmp .return_nil ; -------------------------------- + .list_start: ; Push current list onto stack + push r12 push r13 - ; Push current state of the tokenizer - push rsi - push rax - push rbx + ; Get the first value + ; Note that we call rather than jmp because the first + ; value needs to be treated differently. There's nothing + ; to append to yet... + call .read_loop + + ; rax now contains the first object + cmp cl, ')' ; Check if it was end of list + je .list_has_contents + mov cl, 0 ; so ')' doesn't propagate to nested lists + pop r13 + pop r12 + ret ; Returns 'nil' given "()" +.list_has_contents: + ; If this is a Cons then use it + ; If not, then need to allocate a Cons + mov cl, BYTE [rax] + mov ch, cl + and ch, (block_mask + container_mask) ; Tests block and container type + jz .list_is_value + + ; If here then not a simple value, so need to allocate + ; a Cons object ; Start new list + push rax call alloc_cons ; Address in rax - - mov [rax], BYTE (block_cons + container_list + content_nil) - - - cmp r13, 0 - jne .list_link_last + pop rbx + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], rbx + ; Now have Cons in RAX, containing pointer to object as car - ; This is the top-level list - mov r15, rax - jmp .list_done +.list_is_value: + ; Cons in RAX + ; Make sure it's marked as a list + mov cl, BYTE [rax] + or cl, container_list + mov [rax], BYTE cl -.list_link_last: - ; The new list is nested - mov [r13 + Cons.cdr], rax - mov [r13 + Cons.typecdr], BYTE content_pointer -.list_done: - mov r13, rax ; Switch to new list + mov r13, rax ; Set current list + cmp r15, 0 ; Test if first list + jne .list_read_loop + mov r15, rax ; Save the first, for unwinding - ; Restore state - pop rbx - pop rax - pop rsi +.list_read_loop: + ; Repeatedly get the next value in the list + ; (which may be other lists) + ; until we get a ')' token - jmp .read_loop + call .read_loop ; object in rax - ; -------------------------------- -.list_end: + cmp cl, ')' ; Check if it was end of list + je .list_done - ; Check if there is a list - cmp r13, 0 - jne .list_end_ok + ; Test if this is a Cons value + mov cl, BYTE [rax] + mov ch, cl + and ch, (block_mask + container_mask) ; Tests block and container type + jz .list_loop_is_value - call alloc_cons - mov [rax], BYTE maltype_nil + ; If here then not a simple value, so need to allocate + ; a Cons object - ret - -.list_end_ok: + ; Start new list + push rax + call alloc_cons ; Address in rax + pop rbx + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], rbx + ; Now have Cons in RAX, containing pointer to object as car - ; Put the current list into r8 - mov r8, r13 +.list_loop_is_value: + ; Cons in RAX - ; Pop the previous list - pop r13 + ; Make sure it's marked as a list + mov cl, BYTE [rax] + or cl, container_list + mov [rax], BYTE cl - jmp .append_object ; Add R8 to list in R13 - + ; Append to r13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax ; Set current list - ; -------------------------------- -.append_object: - ; Append Cons in R8 to list in R13 - ; If no list in R13 (address is zero) then returns - ; with R8 moved to RAX - - cmp r13, 0 - je .finished + jmp .list_read_loop + +.list_done: + ; Terminate the list + mov [r13 + Cons.typecdr], BYTE content_nil + mov QWORD [r13 + Cons.cdr], QWORD 0 + mov rax, r12 ; Start of current list - ; Append to list - mov [r13 + Cons.cdr], r8 - mov [r13 + Cons.typecdr], BYTE content_pointer - mov [r8 + Cons.typecdr], BYTE content_nil + mov [rax], BYTE (container_list + content_int) + + ; Pop previous list (if any) + pop r13 + pop r12 + ret - jmp .read_loop ; -------------------------------- .finished: - ; No list to add this object to, so finished - mov rax, r8 ret +.unwind: + ; Jump to here cleans up + + mov rsp, r14 ; Rewind stack pointer + cmp r15, 0 ; Check if there is a list + jne .return_nil + mov rsi, r15 + call release_cons ; releases everything recursively + ; fall through to return_nil +.return_nil: + ; Allocates a new Cons object with nil and returns + ; Cleanup should happen before jumping here + call alloc_cons + mov [rax], BYTE maltype_nil + ret + + + ;; Initialise the tokenizer ;; ;; Input: Address of string in RSI ;; ;; NOTE: This uses RSI, RAX and RBX, and expects these to be preserved ;; between calls to tokenizer_next_char +;; +;; R9 Address of string +;; R10 Position in data array +;; R11 End of data array +;; tokenizer_init: - ; Put start of data array into rax - mov rax, rsi - add rax, Array.data - ; Put end of data array into rbx - mov ebx, [rsi + Array.length] ; Length of array, zero-extended - add rbx, rax + ; Save string to r9 + mov r9, rsi + ; Put start of data array into r10 + mov r10, rsi + add r10, Array.data + ; Put end of data array into r11 + mov r11d, [rsi + Array.length] ; Length of array, zero-extended + add r11, r10 ret @@ -171,30 +217,30 @@ tokenizer_init: ;; contiguous block of memory, but may use multiple Array ;; objects in a linked list ;; -;; If no chunks are left, then RAX = RBX +;; If no chunks are left, then R10 = R11 tokenizer_next_chunk: - mov rax, [rsi + Array.next] - cmp rax, 0 + mov r10, [r9 + Array.next] + cmp r10, 0 je .no_more ; More chunks left - mov rsi, rax + mov rsi, r10 call tokenizer_init ret .no_more: - ; No more chunks left. RAX is zero - mov rbx, rax + ; No more chunks left. R10 is zero + mov r11, r10 ret ;; Moves the next char into CL ;; If no more, puts 0 into CL tokenizer_next_char: ; Check if we have reached the end of this chunk - cmp rax, rbx + cmp r10, r11 jne .chars_remain ; Hit the end. See if there is another chunk call tokenizer_next_chunk - cmp rax, rbx + cmp r10, r11 jne .chars_remain ; Success, got another ; No more chunks @@ -202,8 +248,8 @@ tokenizer_next_char: ret .chars_remain: - mov cl, BYTE [rax] - inc rax ; point to next byte + mov cl, BYTE [r10] + inc r10 ; point to next byte ret ;; Get the next token @@ -211,11 +257,16 @@ tokenizer_next_char: ;; - 0 : Nil, finished ;; - Characters ()[]()'`~^@ ;; - Pair '~@', represented by code 1 -;; - A string: " in CL, and address in R8 +;; - A string: " in CL, and address in RAX ;; - An integer: 'i' in CL ;; -;; Address of object in R8 -;; +;; Address of object in RAX +;; +;; May use registers: +;; RBX +;; RCX +;; RDX +;; tokenizer_next: .next_char: @@ -227,8 +278,8 @@ tokenizer_next: ; Here expect to have: ; - The current character in CL - ; - Address of next data in rax - ; - Address of data end in rbx + ; - Address of next data in r10 + ; - Address of data end in r11 ; Skip whitespace or commas cmp cl, ' ' ; Space @@ -279,11 +330,6 @@ tokenizer_next: ; Start integer ; accumulate in EDX xor edx, edx - - ; Push current state of the tokenizer - push rsi - push rax - push rbx .integer_loop: ; Here have a char 0-9 in CL @@ -292,9 +338,7 @@ tokenizer_next: add edx, ebx ; Peek at next character - push rdx call tokenizer_next_char ; Next char in CL - pop rdx cmp cl, '0' jl .integer_finished @@ -308,22 +352,18 @@ tokenizer_next: .integer_finished: ; Next char not an int - push rdx ; Save the integer + push rdx ; Save the integer ; Get a Cons object to put the result into call alloc_cons + + pop rdx ; Restore integer + ; Address of Cons now in RAX - mov r8, rax - mov [r8], BYTE maltype_integer + mov [rax], BYTE maltype_integer - pop rdx - mov [r8 + Cons.car], rdx + mov [rax + Cons.car], rdx - ; Restore state - pop rbx - pop rax - pop rsi - mov cl, 'i' ; Mark as an integer ret @@ -335,36 +375,25 @@ tokenizer_next: .handle_string: ; Get an array to put the string into - - ; save state of tokenizer - push rsi - push rax - push rbx - - call alloc_array - mov r8, rax ; Address of array in r8 - mov [r8], BYTE maltype_string ; mark as a string - ; restore state - pop rbx - pop rax - pop rsi + call string_new ; Array in RAX - ; Put start of data array into r9 - mov r9, r8 - add r9, Array.data - ; Put end of data array into r10 - mov r10d, [rsi + Array.length] ; Length of array, zero-extended - add r10, r9 + ; Put start of data array into rbx + mov rbx, rax + add rbx, Array.data + ; Put end of data array into rdx + mov edx, DWORD [rax + Array.length] ; Length of array, zero-extended + add rdx, rbx ; Now read chars from input string and push into output .string_loop: + call tokenizer_next_char cmp cl, 0 ; End of characters je .error cmp cl, 34 ; Finishing '"' - je .found ; Leave '"' in CL + je .string_done ; Leave '"' in CL cmp cl, 92 ; Escape '\' jne .end_string_escape @@ -389,13 +418,20 @@ tokenizer_next: ; Put CL onto result array ; NOTE: this doesn't handle long strings (multiple memory blocks) - mov [r9], cl - inc r9 + mov [rbx], cl + inc rbx jmp .string_loop - + +.string_done: + ; Calculate the length from rbx + sub rbx, Array.data + sub rbx, rax + mov [rax+Array.length], DWORD ebx ret + ; --------------------------------- + .tokens_finished: mov cl, 0 ; End of tokens ret @@ -404,9 +440,9 @@ tokenizer_next: ; Could have '~' or '~@'. Need to peek at the next char ; Push current state of the tokenizer - push rsi - push rax - push rbx + push r9 + push r10 + push r11 call tokenizer_next_char ; Next char in CL cmp cl, '@' jne .tilde_no_amp ; Just '~', not '~@' @@ -422,7 +458,7 @@ tokenizer_next: pop rbx pop rax pop rsi - ; fall through to finished + ; fall through to found .found: ret diff --git a/nasm/step1_read_print.asm b/nasm/step1_read_print.asm index 6a8915a908..644c9cd7ee 100644 --- a/nasm/step1_read_print.asm +++ b/nasm/step1_read_print.asm @@ -139,6 +139,19 @@ test_string2: db 10, "test2", 10 ;AT Array.length, dd 6 ;AT Array.data, db 'hello',10 ;IEND + +test_cons: ISTRUC Cons +AT Cons.typecar, db ( maltype_integer + 2 ) +AT Cons.typecdr, db 0 +AT Cons.car, dq 123 +IEND + +test_cons2: ISTRUC Cons +AT Cons.typecar, db ( maltype_integer + 2 ) +AT Cons.typecdr, db content_pointer +AT Cons.car, dq 456 +AT Cons.cdr, dq test_cons +IEND ;; ------------------------------------------ ;; Fixed strings for printing @@ -239,14 +252,6 @@ alloc_array: ;; onto the free list release_array: mov ax, WORD [rsi + Array.refcount] - - push rsi - push rdx - mov rsi, test_string1 - mov rdx, test_string1.len - call print_rawstring - pop rdx - pop rsi dec ax mov WORD [rsi + Array.refcount], ax @@ -755,6 +760,12 @@ _start: ; call string_append_string ; call print_string + + ; Test printing short list of integers + ; mov rsi, test_cons2 + ; call pr_str + ; mov rsi, rax + ; call print_string ; ----------------------------- ; Main loop From 49badea29ff778a36c926c49542dbd88919eefe1 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 19 Oct 2017 23:08:35 +0100 Subject: [PATCH 0539/2308] Small bug-fixes to reader Not yet working, returns nil given (10) --- nasm/reader.asm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nasm/reader.asm b/nasm/reader.asm index 5507bac7a3..f0e075954f 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -76,7 +76,7 @@ read_str: ; rax now contains the first object cmp cl, ')' ; Check if it was end of list - je .list_has_contents + jne .list_has_contents mov cl, 0 ; so ')' doesn't propagate to nested lists pop r13 pop r12 @@ -106,7 +106,8 @@ read_str: mov cl, BYTE [rax] or cl, container_list mov [rax], BYTE cl - + + mov r12, rax ; Start of current list mov r13, rax ; Set current list cmp r15, 0 ; Test if first list jne .list_read_loop From f2abb9c15f8642804c02f05d77a1c574d07a6932 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 20 Oct 2017 07:27:03 +0100 Subject: [PATCH 0540/2308] Integer list reading and writing working * Integer tokenizer was not putting the last non-integer character back, so end of tokens was returned rather than ')' * Some typos, jne <-> je --- nasm/reader.asm | 50 +++++++++++++++++++++++++-------------- nasm/step1_read_print.asm | 2 +- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/nasm/reader.asm b/nasm/reader.asm index f0e075954f..d8db831069 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -24,6 +24,10 @@ section .text ;; R8 ** State must be preserved ;; R9 ** ;; R10 ** +;; R12 +;; R13 +;; R14 Original stack pointer on call +;; R15 Top-level list, so all can be released on error ;; read_str: ; Initialise tokenizer @@ -64,22 +68,17 @@ read_str: ; -------------------------------- .list_start: - ; Push current list onto stack - push r12 - push r13 - + ; Get the first value ; Note that we call rather than jmp because the first ; value needs to be treated differently. There's nothing ; to append to yet... call .read_loop - + ; rax now contains the first object cmp cl, ')' ; Check if it was end of list jne .list_has_contents mov cl, 0 ; so ')' doesn't propagate to nested lists - pop r13 - pop r12 ret ; Returns 'nil' given "()" .list_has_contents: ; If this is a Cons then use it @@ -118,8 +117,12 @@ read_str: ; (which may be other lists) ; until we get a ')' token + push r12 + push r13 call .read_loop ; object in rax - + pop r13 + pop r12 + cmp cl, ')' ; Check if it was end of list je .list_done @@ -163,9 +166,6 @@ read_str: mov [rax], BYTE (container_list + content_int) - ; Pop previous list (if any) - pop r13 - pop r12 ret ; -------------------------------- @@ -177,14 +177,16 @@ read_str: mov rsp, r14 ; Rewind stack pointer cmp r15, 0 ; Check if there is a list - jne .return_nil + je .return_nil mov rsi, r15 call release_cons ; releases everything recursively ; fall through to return_nil .return_nil: ; Allocates a new Cons object with nil and returns ; Cleanup should happen before jumping here + push rcx call alloc_cons + pop rcx mov [rax], BYTE maltype_nil ret @@ -337,7 +339,12 @@ tokenizer_next: sub cl, '0' ; Convert to number between 0 and 9 movzx ebx, cl add edx, ebx - + + ; Push current state of the tokenizer + push r9 + push r10 + push r11 + ; Peek at next character call tokenizer_next_char ; Next char in CL @@ -345,6 +352,9 @@ tokenizer_next: jl .integer_finished cmp cl, '9' jg .integer_finished + + ; Discard old state by moving stack pointer + add rsp, 24 ; 3 * 8 bytes imul edx, 10 @@ -352,8 +362,12 @@ tokenizer_next: .integer_finished: ; Next char not an int - - + + ; Restore state of the tokenizer + pop r11 + pop r10 + pop r9 + push rdx ; Save the integer ; Get a Cons object to put the result into call alloc_cons @@ -456,9 +470,9 @@ tokenizer_next: .tilde_no_amp: ; Restore state of the tokenizer - pop rbx - pop rax - pop rsi + pop r11 + pop r10 + pop r9 ; fall through to found .found: diff --git a/nasm/step1_read_print.asm b/nasm/step1_read_print.asm index 644c9cd7ee..4bb9b5bb4b 100644 --- a/nasm/step1_read_print.asm +++ b/nasm/step1_read_print.asm @@ -183,7 +183,7 @@ error_cons_memory_limit: db "Error: Run out of memory for Cons objects. Increase heap_cons_next: dd heap_cons_store ; Address of next cons in memory heap_cons_free: dq 0 ; Address of start of free list -%define heap_array_limit 4 ; Number of array objects which can be created +%define heap_array_limit 10 ; Number of array objects which can be created heap_array_next: dd heap_array_store heap_array_free: dq 0 From 955b354b00d6f5d42078a28b27252053a0b9a6ea Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 21 Oct 2017 08:21:56 +0100 Subject: [PATCH 0541/2308] Handles symbols, quoting and unquoting Test suite passes required and deferrable tests except: 1. () is apparently different from nil, so empty list needs to be handled differently 2. String printing currently just prints the string without quotes or escapes. --- nasm/Makefile | 6 +- nasm/printer.asm | 34 ++++++- nasm/reader.asm | 189 +++++++++++++++++++++++++++++++++++++- nasm/step1_read_print.asm | 4 +- 4 files changed, 226 insertions(+), 7 deletions(-) diff --git a/nasm/Makefile b/nasm/Makefile index 511d7c665e..1e7ea2fc65 100644 --- a/nasm/Makefile +++ b/nasm/Makefile @@ -1,3 +1,7 @@ -step0_repl: +step0_repl: step0_repl.asm nasm -felf64 step0_repl.asm ld -o $@ step0_repl.o + +step1_read_print: step1_read_print.asm + nasm -felf64 step1_read_print.asm + ld -o $@ step1_read_print.o diff --git a/nasm/printer.asm b/nasm/printer.asm index 79304ea302..b16e589eaf 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -132,9 +132,17 @@ pr_str: ; concatenate strings in rax and rsi mov rsi, r13 ; Output string mov rdx, rax ; String to be copied - + + push rsi ; Save output string + push rax ; save temporary string call string_append_string + ; Release the string + pop rsi ; Was in rax, temporary string + call release_array + + pop rsi ; restore output string + ; Check if this is the end of the list mov cl, BYTE [r12 + Cons.typecdr] cmp cl, content_nil @@ -160,5 +168,29 @@ pr_str: mov rax, rsi ret .symbol: + ; Make a copy of the string + call string_new ; in rax + mov ebx, DWORD [rsi + Array.length] + mov [rax + Array.length], ebx + mov rcx, rsi + add rcx, Array.data ; Start of input data + mov rdx, rsi + add rdx, Array.size ; End of input data + mov r12, rax + add r12, Array.data ; Start of output data +.symbol_copy_loop: + ; Copy [rax] -> [r12] + mov rbx, [rcx] + mov [r12], rbx + add rcx, 8 ; Next 64 bits of input + cmp rcx, rdx + je .symbol_finished + + add r12, 8 ; Next 64 bits of output + jmp .symbol_copy_loop +.symbol_finished: + + + ret diff --git a/nasm/reader.asm b/nasm/reader.asm index d8db831069..f10c9d9dd2 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -1,4 +1,16 @@ +section .data +quote_symbol_string: db "quote" +.len: equ $ - quote_symbol_string + +quasiquote_symbol_string: db "quasiquote" +.len: equ $ - quasiquote_symbol_string + +unquote_symbol_string: db "unquote" +.len: equ $ - unquote_symbol_string + +splice_unquote_symbol_string: db "splice-unquote" +.len: equ $ - splice_unquote_symbol_string section .text @@ -53,7 +65,11 @@ read_str: .got_token: - cmp cl, 'i' + cmp cl, 'i' ; An integer. Cons object in RAX + je .finished + cmp cl, '"' ; A string. Array object in RAX + je .finished + cmp cl, 's' je .finished cmp cl, '(' @@ -61,6 +77,15 @@ read_str: cmp cl, ')' je .return_nil ; Note: if reading a list, cl will be tested in the list reader + + cmp cl, 39 ; quote ' + je .handle_quote + cmp cl, '`' + je .handle_quasiquote + cmp cl, '~' + je .handle_unquote + cmp cl, 1 + je .handle_splice_unquote ; Unknown jmp .return_nil @@ -124,7 +149,7 @@ read_str: pop r12 cmp cl, ')' ; Check if it was end of list - je .list_done + je .list_done ; Have nil object in rax ; Test if this is a Cons value mov cl, BYTE [rax] @@ -159,15 +184,104 @@ read_str: jmp .list_read_loop .list_done: + ; Release nil object in rax + mov rsi, rax + call release_cons + ; Terminate the list mov [r13 + Cons.typecdr], BYTE content_nil mov QWORD [r13 + Cons.cdr], QWORD 0 mov rax, r12 ; Start of current list + + ret + + ; -------------------------------- +.handle_quote: + ; Turn 'a into (quote a) + call alloc_cons ; Address in rax + mov r12, rax + + ; Get a symbol "quote" + push r8 + push r9 + mov rsi, quote_symbol_string + mov edx, quote_symbol_string.len + call raw_to_string ; Address in rax + pop r9 + pop r8 + +.wrap_next_object: + mov [rax], BYTE maltype_symbol + mov [r12], BYTE (block_cons + container_list + content_pointer) + mov [r12 + Cons.car], rax + + ; Get the next object + push r12 + call .read_loop ; object in rax + pop r12 + + mov r13, rax ; Put object to be quoted in r13 - mov [rax], BYTE (container_list + content_int) + call alloc_cons ; Address in rax + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], r13 + mov [rax + Cons.typecdr], BYTE content_nil + ; Cons object in rax. Append to object in r12 + mov [r12 + Cons.typecdr], BYTE content_pointer + mov [r12 + Cons.cdr], rax + + mov rax, r12 ret + ; -------------------------------- +.handle_quasiquote: + ; Turn `a into (quasiquote a) + call alloc_cons ; Address in rax + mov r12, rax + + ; Get a symbol "quasiquote" + push r8 + push r9 + mov rsi, quasiquote_symbol_string + mov edx, quasiquote_symbol_string.len + call raw_to_string ; Address in rax + pop r9 + pop r8 + jmp .wrap_next_object ; From there the same as handle_quote + + ; -------------------------------- +.handle_unquote: + ; Turn ~a into (unquote a) + call alloc_cons ; Address in rax + mov r12, rax + + ; Get a symbol "unquote" + push r8 + push r9 + mov rsi, unquote_symbol_string + mov edx, unquote_symbol_string.len + call raw_to_string ; Address in rax + pop r9 + pop r8 + jmp .wrap_next_object ; From there the same as handle_quote + + ; -------------------------------- +.handle_splice_unquote: + ; Turn ~@a into (unquote a) + call alloc_cons ; Address in rax + mov r12, rax + + ; Get a symbol "unquote" + push r8 + push r9 + mov rsi, splice_unquote_symbol_string + mov edx, splice_unquote_symbol_string.len + call raw_to_string ; Address in rax + pop r9 + pop r8 + jmp .wrap_next_object ; From there the same as handle_quote + ; -------------------------------- .finished: ret @@ -188,6 +302,7 @@ read_str: call alloc_cons pop rcx mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil ret @@ -262,6 +377,7 @@ tokenizer_next_char: ;; - Pair '~@', represented by code 1 ;; - A string: " in CL, and address in RAX ;; - An integer: 'i' in CL +;; - A symbol: 's' in CL, address in RAX ;; ;; Address of object in RAX ;; @@ -382,12 +498,78 @@ tokenizer_next: mov cl, 'i' ; Mark as an integer ret + ; ------------------------------------------- .handle_symbol: + ; Read characters until reaching whitespace, special character or end + call string_new + mov rsi, rax ; Output string in rsi + +.symbol_loop: + ; Put the current character into the array + call string_append_char + ; Push current state of the tokenizer + push r9 + push r10 + push r11 + call tokenizer_next_char + cmp cl, 0 ; End of characters + je .symbol_finished + + cmp cl, ' ' ; Space + je .symbol_finished + cmp cl, ',' ; Comma + je .symbol_finished + cmp cl, 9 ; Tab + + cmp cl, '(' + je .symbol_finished + cmp cl, ')' + je .symbol_finished + cmp cl, '[' + je .symbol_finished + cmp cl, ']' + je .symbol_finished + cmp cl, '{' + je .symbol_finished + cmp cl, '}' + je .symbol_finished + cmp cl, 39 ; character ' + je .symbol_finished + cmp cl, 96 ; character ` + je .symbol_finished + cmp cl, '^' + je .symbol_finished + cmp cl, '@' + je .symbol_finished + cmp cl, '~' + je .symbol_finished + cmp cl, ';' ; Start of a comment + je .symbol_finished + cmp cl, 34 ; Opening string quotes + je .symbol_finished + + ; Keeping current character + ; Discard old state by moving stack pointer + add rsp, 24 ; 3 * 8 bytes + + jmp .symbol_loop ; Append to array + +.symbol_finished: + ; Not keeping current character + ; Restore state of the tokenizer + pop r11 + pop r10 + pop r9 + + mov rax, rsi + mov [rax], BYTE maltype_symbol ; Mark as a symbol + mov cl, 's' ; used by read_str ret + ; -------------------------------------------- .handle_string: ; Get an array to put the string into @@ -469,6 +651,7 @@ tokenizer_next: ret .tilde_no_amp: + mov cl, '~' ; Restore state of the tokenizer pop r11 pop r10 diff --git a/nasm/step1_read_print.asm b/nasm/step1_read_print.asm index 4bb9b5bb4b..637ddce95c 100644 --- a/nasm/step1_read_print.asm +++ b/nasm/step1_read_print.asm @@ -801,9 +801,9 @@ _start: pop rsi call release_array - ; Release the Cons from read_str + ; Release the object from read_str pop rsi - call release_cons + call release_object ; Could be Cons or Array ; Release the string pop rsi From 1eb4f83354506319cac8a3070b99d83381a7ed4e Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sun, 22 Oct 2017 00:05:33 +0100 Subject: [PATCH 0542/2308] String printing and negative numbers * Prints strings, escaping " and \n * Handles negative integers, reading and printing. Difference between "- 10" (symbol, number) and "-10" (number) All essential and deferrable tests pass --- nasm/printer.asm | 68 +++++++++++++++++++++++++++++++++++++-- nasm/reader.asm | 52 +++++++++++++++++++++++++++++- nasm/step1_read_print.asm | 42 ++++++++++++++++++++---- 3 files changed, 151 insertions(+), 11 deletions(-) diff --git a/nasm/printer.asm b/nasm/printer.asm index b16e589eaf..584491d9a2 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -34,9 +34,68 @@ pr_str: cmp cl, maltype_string jne .not_string - mov rax, rsi - ret + + ; --------------------------- + ; Handle string + + call string_new ; Output string in rax + + mov r12, rax + add r12, Array.data ; Output data + mov r13, rsi + add r13, Array.data ; Input data + mov r14d, DWORD [rsi + Array.length] + add r14, Array.data + add r14, rsi ; End of input data + + ; Put " at start of output string + mov [r12], BYTE '"' + inc r12 + + ; Loop through the input string, escaping characters + +.string_loop: + cmp r13, r14 + je .string_finished + + mov cl, BYTE [r13] ; Get next character + inc r13 + + cmp cl, '"' ; + je .string_escape_char + + cmp cl, 10 ; Newline + je .string_newline + + ; No special case, just copy the byte + mov [r12], BYTE cl + inc r12 + jmp .string_loop + +.string_newline: + mov cl, 'n' + ;jmp .string_escape_char +.string_escape_char: ; Add a '\' before char in cl + mov [r12], BYTE 92 ; Escape '\' + inc r12 + mov [r12], BYTE cl + inc r12 + jmp .string_loop + +.string_finished: + + mov [r12], BYTE '"' ; At the end + inc r12 + ; Calculate length of string + sub r12, rax + sub r12, Array.data + + mov [rax + Array.length], DWORD r12d + + ret + + ; ---------------------------- .not_string: ; Now test the container type (value, list) @@ -105,6 +164,9 @@ pr_str: and ch, content_mask cmp ch, content_pointer je .list_loop_pointer + + cmp ch, content_empty + je .list_check_end ; A value (nil, int etc. or function) xor cl, container_list ; Remove list type -> value @@ -142,7 +204,7 @@ pr_str: call release_array pop rsi ; restore output string - +.list_check_end: ; Check if this is the end of the list mov cl, BYTE [r12 + Cons.typecdr] cmp cl, content_nil diff --git a/nasm/reader.asm b/nasm/reader.asm index f10c9d9dd2..4048453427 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -104,6 +104,8 @@ read_str: cmp cl, ')' ; Check if it was end of list jne .list_has_contents mov cl, 0 ; so ')' doesn't propagate to nested lists + ; Set list to empty + mov [rax], BYTE maltype_empty_list ret ; Returns 'nil' given "()" .list_has_contents: ; If this is a Cons then use it @@ -439,12 +441,54 @@ tokenizer_next: ; Could be number or symbol + cmp cl, '-' ; Minus sign + je .handle_minus + mov ch, 0 + ; Check for a character 0-9 cmp cl, '0' jl .handle_symbol cmp cl, '9' jg .handle_symbol + ; Here an integer + jmp .handle_integer + +.handle_minus: + + ; Push current state of the tokenizer + push r9 + push r10 + push r11 + + ; Get the next character + call tokenizer_next_char + + ; Check if it is a number + cmp cl, '0' + jl .minus_not_number + cmp cl, '9' + jg .minus_not_number + + ; Here is a number + mov ch, '-' ; Put '-' in ch for later + + ; Discard old state by moving stack pointer + add rsp, 24 ; 3 * 8 bytes + + jmp .handle_integer + +.minus_not_number: + + ; Restore state + pop r11 + pop r10 + pop r9 + + mov cl, '-' ; Put back + + jmp .handle_symbol + .handle_integer: ; Start integer ; accumulate in EDX @@ -489,7 +533,13 @@ tokenizer_next: call alloc_cons pop rdx ; Restore integer - + + ; Check if the number should be negative + cmp ch, '-' + jne .integer_store + neg rdx + +.integer_store: ; Address of Cons now in RAX mov [rax], BYTE maltype_integer diff --git a/nasm/step1_read_print.asm b/nasm/step1_read_print.asm index 637ddce95c..a9ef12b1f2 100644 --- a/nasm/step1_read_print.asm +++ b/nasm/step1_read_print.asm @@ -38,12 +38,14 @@ ;; 10 5 - Function ;; ;; Content type [4 bits]: -;; 0 0 - Nil -;; 16 1 - Bool -;; 32 2 - Char -;; 48 3 - Int -;; 64 4 - Float -;; 80 5 - Pointer (memory address) +;; 0 0 - Nil +;; 16 1 - Bool +;; 32 2 - Char +;; 48 3 - Int +;; 64 4 - Float +;; 80 5 - Pointer (memory address) +;; 96 6 - Function (instruction address) +;; 112 7 - Empty (distinct from Nil) ;; ;; These represent MAL data types as follows: ;; @@ -113,12 +115,14 @@ ENDSTRUC %define content_float 64 %define content_pointer 80 ; Memory pointer (to Cons or Array) %define content_function 96 ; Function pointer - +%define content_empty 112 + ;; Common combinations for MAL types %define maltype_integer (block_cons + container_value + content_int) %define maltype_string (block_array + container_value + content_char) %define maltype_symbol (block_array + container_symbol + content_char) %define maltype_nil (block_cons + container_value + content_nil) +%define maltype_empty_list (block_cons + container_list + content_empty) %include "reader.asm" %include "printer.asm" @@ -606,12 +610,25 @@ print_string: ;; Return string address in RAX itostring: ; Save registers to restore afterwards + push rbx push rcx push rdx push rsi push rdi mov rcx, 0 ; counter of how many bytes we need to print in the end + + mov rbx, rax ; Original input + + ; Check if the number is negative + cmp rax, 0 + jge .divideLoop + + ; a negative number. To get the '-' sign + ; at the front the test is done again at the end + ; using the value stored in rbx + + neg rax ; Make it positive .divideLoop: inc rcx ; count each byte to print - number of characters @@ -626,6 +643,16 @@ itostring: cmp rax, 0 ; can the integer be divided anymore? jnz .divideLoop ; jump if not zero to the label divideLoop + ; Check if the value was negative (in rbx) + cmp rbx, 0 + jge .create_string + + ; a negative number + dec rsp + mov BYTE [rsp], '-' + inc rcx + +.create_string: ; Get an Array object to put the string into call string_new ; Address in RAX @@ -650,6 +677,7 @@ itostring: pop rsi pop rdx pop rcx + pop rbx ret From f0662456bacf394258fbce1d0f451b80e6ee840d Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sun, 22 Oct 2017 00:24:53 +0100 Subject: [PATCH 0543/2308] Move types and system calls into separate files * Memory management, Cons, Arrays and String types in types.asm * Functions containing syscall in system.asm * Added more useful error message in reader, on unexpected end of input --- nasm/reader.asm | 10 + nasm/step1_read_print.asm | 749 +------------------------------------- nasm/system.asm | 93 +++++ nasm/types.asm | 629 ++++++++++++++++++++++++++++++++ 4 files changed, 739 insertions(+), 742 deletions(-) create mode 100644 nasm/system.asm create mode 100644 nasm/types.asm diff --git a/nasm/reader.asm b/nasm/reader.asm index 4048453427..f23bb5df8d 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -11,6 +11,9 @@ unquote_symbol_string: db "unquote" splice_unquote_symbol_string: db "splice-unquote" .len: equ $ - splice_unquote_symbol_string + +error_string_unexpected_end: db "Error: Unexpected end of input. Could be a missing )", 10 +.len: equ $ - error_string_unexpected_end section .text @@ -61,6 +64,13 @@ read_str: jne .got_token ; Unexpected end of tokens + push r14 + push r15 + mov rdx, error_string_unexpected_end.len + mov rsi, error_string_unexpected_end + call print_rawstring + pop r15 + pop r14 jmp .unwind .got_token: diff --git a/nasm/step1_read_print.asm b/nasm/step1_read_print.asm index a9ef12b1f2..a651e86b2d 100644 --- a/nasm/step1_read_print.asm +++ b/nasm/step1_read_print.asm @@ -5,131 +5,13 @@ ;; Address of return value is in RAX ;; -;; Data structures -;; =============== -;; -;; Memory management is done by having two fixed-size datatypes, -;; Cons and Array. -;; -;; Both Cons and Array have the following in common: -;; a type field at the start, a reference count, followed by data -;; [ type (8) | (8) | refs (16) | data ] -;; -;; -;; Type bit fields -;; --------------- -;; -;; The 8-bit type fields describe the Block, Container and Content type. -;; -;; The Block type is used for memory management, to determine the kind of memory block -;; The Container type indicates the data structure that the Cons or Array block is being used to represent -;; The Content type indicates the raw type of the data in the content -;; -;; Block type [1 bit]: -;; 0 0 - Cons memory block -;; 1 1 - Array memory block -;; -;; Container type [3 bits]: -;; 0 0 - Value (single boxed value for Cons blocks, vector for Array blocks). -;; 2 1 - List (value followed by pointer). Only for Cons blocks -;; 4 2 - Symbol (special char array). Only for Array blocks -;; 6 3 - Keyword -;; 8 4 - Map -;; 10 5 - Function -;; -;; Content type [4 bits]: -;; 0 0 - Nil -;; 16 1 - Bool -;; 32 2 - Char -;; 48 3 - Int -;; 64 4 - Float -;; 80 5 - Pointer (memory address) -;; 96 6 - Function (instruction address) -;; 112 7 - Empty (distinct from Nil) -;; -;; These represent MAL data types as follows: -;; -;; MAL type Block Container Content -;; --------- | -------- | ---------- | --------- -;; integer Cons Value Int -;; symbol Array Symbol Char -;; list Cons List Any -;; nil Cons Value Nil -;; true Cons Value Bool (1) -;; false Cons Value Bool (0) -;; string Array Value Char -;; keyword Array Keyword Char -;; vector Array Value Int/Float -;; hash-map Array Map Pointer (?TBD) -;; atom Cons Value Pointer -;; +global _start -;; Cons type. -;; Used to store either a single value with type information -;; or a pair of (value, Pointer or Nil) to represent a list -STRUC Cons -.typecar: RESB 1 ; Type information for car (8 bit) -.typecdr: RESB 1 ; Type information for cdr (8 bits) -.refcount: RESW 1 ; Number of references to this Cons (16 bit) -.car: RESQ 1 ; First value (64 bit) -.cdr: RESQ 1 ; Second value (64 bit) -.size: ; Total size of struc -ENDSTRUC - - -%define array_chunk_len 32 ; Number of 64-bit values which can be stored in a single chunk - -STRUC Array -.type: RESB 1 ; Type information (8 bits) -.control: RESB 1 ; Control data (8 bits) -.refcount: RESW 1 ; Number of references to this Array (16 bit) -.length: RESD 1 ; Number of elements in this part of the array (32 bit) -.next RESQ 1 ; Pointer to the next chunk (64 bit) -.data: RESQ array_chunk_len ; Data storage -.size: ; Total size of struc -ENDSTRUC - -;; Type information - -%define block_mask 1 ; LSB for block type -%define container_mask 2 + 4 + 8 ; Next three bits for container type -%define content_mask 16 + 32 + 64 + 128 ; Four bits for content type - -;; Block types -%define block_cons 0 -%define block_array 1 - -;; Container types -%define container_value 0 -%define container_list 2 -%define container_symbol 4 -%define container_keyword 6 -%define container_map 8 -%define container_function 10 - -;; Content type -%define content_nil 0 -%define content_bool 16 -%define content_char 32 -%define content_int 48 -%define content_float 64 -%define content_pointer 80 ; Memory pointer (to Cons or Array) -%define content_function 96 ; Function pointer -%define content_empty 112 - -;; Common combinations for MAL types -%define maltype_integer (block_cons + container_value + content_int) -%define maltype_string (block_array + container_value + content_char) -%define maltype_symbol (block_array + container_symbol + content_char) -%define maltype_nil (block_cons + container_value + content_nil) -%define maltype_empty_list (block_cons + container_list + content_empty) +%include "types.asm" ; Data types, memory +%include "system.asm" ; System calls +%include "reader.asm" ; String -> Data structures +%include "printer.asm" ; Data structures -> String -%include "reader.asm" -%include "printer.asm" - - - global _start - section .data test_string1: db 10, "test1", 10 @@ -162,538 +44,8 @@ IEND prompt_string: db 10,"user> " ; The string to print at the prompt .len: equ $ - prompt_string - -error_msg_print_string: db "Error in print string",10 -.len: equ $ - error_msg_print_string - -error_array_memory_limit: db "Error: Run out of memory for Array objects. Increase heap_array_limit.",10 -.len: equ $ - error_array_memory_limit - -error_cons_memory_limit: db "Error: Run out of memory for Cons objects. Increase heap_cons_limit.",10 -.len: equ $ - error_cons_memory_limit - -;; ------------------------------------------ -;; Memory management -;; -;; For each object (Cons or Array), there is a block of memory (in BSS). -;; When an object is requested it is first taken from the free list -;; If the free list is empty (address 0) then the next object in the block -;; is used, and the heap_x_number counter is incremented. When an object -;; is free'd it is pushed onto the heap_x_free list. - - -%define heap_cons_limit 10 ; Number of cons objects which can be created - -heap_cons_next: dd heap_cons_store ; Address of next cons in memory -heap_cons_free: dq 0 ; Address of start of free list - -%define heap_array_limit 10 ; Number of array objects which can be created - -heap_array_next: dd heap_array_store -heap_array_free: dq 0 - -section .bss - -;; Reserve space to store Cons and Array objects -heap_cons_store: resb heap_cons_limit * Cons.size -.end: ; Address of end of the store - -heap_array_store: resb heap_array_limit * Array.size -.end: - -section .text - -;; ------------------------------------------ -;; Array alloc_array() -;; -;; Returns the address of an Array object in RAX -;; -;; Working registers: rbx -alloc_array: - - ; Get the address of a free array - mov rax, [heap_array_free] ; Address of the array - - ; Check if it's null - cmp rax, 0 - je .create_array - - mov rbx, [rax + Array.next] ; Get the address of the next array in the linked list - mov [heap_array_free], rbx ; Put this address at the front of the list - jmp .initialise_array - -.create_array: - - ; Get the address of the next Array - mov rax, [heap_array_next] - ; Check if we've reached the end - cmp rax, heap_array_store.end - je .out_of_memory - - mov rbx, rax - add rbx, Array.size ; Address of the next array - mov [heap_array_next], rbx ; for next time - -.initialise_array: - ; Address of Array now in rax - mov BYTE [rax + Array.type], block_array - mov WORD [rax + Array.refcount], 1 ; Only one reference - mov DWORD [rax + Array.length], 0 - mov QWORD [rax + Array.next], 0 ; null next address - - ret - -.out_of_memory: - mov rsi, error_array_memory_limit - mov rdx, error_array_memory_limit.len - call print_rawstring - jmp quit_error - - -;; ------------------------------------------- -;; Decrements the reference count of the array in RSI -;; If the count reaches zero then push the array -;; onto the free list -release_array: - mov ax, WORD [rsi + Array.refcount] - - dec ax - mov WORD [rsi + Array.refcount], ax - jz .free ; If the count reaches zero then put on free list - ret - -.free: - ; Get the next field - mov rbx, [rsi + Array.next] - - mov rax, [heap_array_free] ; Get the current head - mov [rsi + Array.next], rax ; Put current head into the "next" field - mov [heap_array_free], rsi ; Push Array onto free list - - cmp rbx, 0 - jne .release_next ; If there is another array, then need to release it - - ret - -.release_next: - ; release the next array - mov rsi, rbx - call release_array - ret - -;; ------------------------------------------ -;; Cons alloc_cons() -;; -;; Returns the address of a Cons object in RAX -alloc_cons: - - ; Get the address of a free cons - mov rax, [heap_cons_free] ; Address of the cons - - ; Check if it's null - cmp rax, 0 - je .create_cons - - mov rbx, [rax + Cons.cdr] ; Get the address of the next cons in the linked list - mov [heap_cons_free], rbx ; Put this address at the front of the list - jmp .initialise_cons - -.create_cons: - - ; Get the address of the next Cons - mov rax, [heap_cons_next] - ; Check if we've reached the end - cmp rax, heap_cons_store.end - je .out_of_memory - - mov rbx, rax - add rbx, Cons.size ; Address of the next cons - mov [heap_cons_next], rbx ; for next time - -.initialise_cons: - ; Address of Cons now in rax - mov BYTE [rax + Cons.typecar], 0 - mov BYTE [rax + Cons.typecdr], 0 - mov WORD [rax + Cons.refcount], 1 ; Only one reference - mov QWORD [rax + Cons.car], 0 - mov QWORD [rax + Cons.cdr], 0 - ret - -.out_of_memory: - mov rsi, error_cons_memory_limit - mov rdx, error_cons_memory_limit.len - call print_rawstring - jmp quit_error - - -;; ------------------------------------------- -;; Decrements the reference count of the cons in RSI -;; If the count reaches zero then push the cons -;; onto the free list -release_cons: - mov ax, WORD [rsi + Cons.refcount] - dec ax - mov WORD [rsi + Cons.refcount], ax - jz .free ; If the count reaches zero then put on free list - ret - -.free: - ; Get and push cdr onto stack - mov rcx, [rsi + Cons.cdr] - push rcx ; Content of CDR - push rsi ; Original Cons object being released - - mov rax, [heap_cons_free] ; Get the current head - mov [rsi + Cons.cdr], rax ; Put current head into the "cdr" field - mov [heap_cons_free], rsi ; Push Cons onto free list - - ; Check if the CAR needs to be released - - mov al, BYTE [rsi+Cons.typecar] - and al, content_mask ; Test content type - cmp al, content_pointer - jne .free_cdr ; Jump if CAR not pointer - - ; CAR is a pointer to either a Cons or Array - ; Get the address stored in CAR - mov rsi, [rsi + Cons.car] - call release_object -.free_cdr: - pop rcx ; This was rsi, the original Cons - pop rsi ; This was rcx, the original Cons.cdr - - ; Get the type from the original Cons - mov al, BYTE [rcx+Cons.typecdr] - and al, content_mask ; Test content type - cmp al, content_pointer - jne .done - - call release_object -.done: - ret - - -;; Releases either a Cons or Array -;; Address of object in RSI -release_object: - mov al, BYTE [rsi] ; Get first byte - and al, block_mask ; Test block type - cmp al, block_array ; Test if it's an array - je .array - call release_cons - ret -.array: - call release_array - ret - -;; ------------------------------------------- -;; String type - -;; Create a new string, address in RAX -string_new: - call alloc_array - mov [rax], BYTE maltype_string - mov QWORD [rax + Array.next], 0 - ret - -;; Convert a raw string to a String type -;; -;; Input: Address of raw string in RSI, length in EDX -;; Output: Address of string in RAX -;; -;; Modifies registers: R8,R9,RCX -raw_to_string: - push rsi - push rdx - call string_new ; String now in RAX - pop rdx - pop rsi - mov [rax + Array.length], DWORD edx - mov r8, rax - add r8, Array.data ; Address of string data - mov r9, rsi ; Address of raw data - mov ecx, edx ; Count -.copy_loop: - - mov bl, BYTE [r9] - mov [r8], BYTE bl - inc r8 - inc r9 - dec ecx - jnz .copy_loop - ret - - - -;; Appends a character to a string -;; Input: Address of string in RSI, character in CL -string_append_char: - mov eax, DWORD [rsi + Array.length] - inc eax - mov DWORD [rsi + Array.length], eax - dec eax - add rax, rsi - add rax, Array.data ; End of data - mov [rax], BYTE cl - ret - -;; Appends a string to the end of a string -;; -;; Input: String to be modified in RSI -;; String to be copied in RDX -;; -;; Output: Modified string in RSI -;; -;; Working registers: -;; rax Array chunk for output (copied to) -;; rbx Array chunk for input (copied from) -;; cl Character being copied -;; r8 Address of destination -;; r9 Destination end address -;; r10 Address of source -;; r11 Source end address -string_append_string: - ; copy source Array address to rbx - mov rbx, rdx - - ; source data address in r10 - mov r10, rbx - add r10, Array.data ; Start of the data - - ; source data end address in r11 - mov r11, r10 - mov r8d, DWORD [rbx + Array.length] - add r11, r8 - - ; Find the end of the string in RSI - ; and put the address of the Array object into rax - mov rax, rsi -.find_string_end: - mov r8, QWORD [rax + Array.next] - cmp r8, 0 ; Next chunk is null - je .got_dest_end ; so reached end - - mov rax, r8 ; Go to next chunk - jmp .find_string_end -.got_dest_end: - - ; destination data address into r8 - mov r8, rax - add r8, Array.data - add r8d, DWORD [rax + Array.length] - - ; destination data end into r9 - mov r9, rax - add r9, Array.size - -.copy_loop: - ; Copy one byte from source to destination - mov cl, BYTE [r10] - mov BYTE [r8], cl - - ; move source to next byte - inc r10 - ; Check if we've reached the end of this Array - cmp r10, r11 - jne .source_ok - - ; have reached the end of the source Array - mov rbx, QWORD [rbx + Array.next] ; Get the next Array address - cmp rbx, 0 ; Test if it's null - je .finished ; No more, so we're done - ; Move on to next Array object - - ; Get source address into r10 - mov r10, rbx - add r10, Array.data ; Start of the data - - ; Source end address - mov r11, rbx - add r11, Array.size - -.source_ok: - - ; Move destination to next byte - inc r8 - ; Check if we've reached end of the Array - cmp r8, r9 - jne .copy_loop ; Next byte - - ; Reached the end of the destination - ; Need to allocate another Array - push rax - push rbx - call alloc_array ; New Array in rax - mov r8, rax ; copy to r8 - pop rbx - pop rax - - ; Previous Array in rax. - ; Add a reference to the new array and set length - mov QWORD [rax + Array.next], r8 - mov DWORD [rax + Array.length], (Array.size - Array.data) - mov rax, r8 ; new array - add r8, Array.data ; Start of data - - mov r9, rax - add r9, Array.size - -.finished: - ; Compare r8 (destination) with data start - ; to get length of string - sub r8, rax - sub r8, Array.data - inc r8 - ; r8 now contains length - mov DWORD [rax + Array.length], r8d - - ret - -;; ------------------------------------------- -;; Prints a raw string to stdout -;; String address in rsi, string length in rdx -print_rawstring: - push rax - push rdi - - ; write(1, string, length) - mov rax, 1 ; system call 1 is write - mov rdi, 1 ; file handle 1 is stdout - syscall - - pop rdi - pop rax - - ret - -;; ------------------------------------------ -;; void print_string(char array) -;; Address of the char Array should be in RSI -print_string: - ; Push registers we're going to use - push rax - push rdi - push rdx - push rsi - - ; Check that we have a char array - mov al, [rsi] - cmp al, maltype_string - jne .error - - ; write(1, string, length) - mov edx, [rsi + Array.length] ; number of bytes - add rsi, Array.data ; address of raw string to output - call print_rawstring - - ; Restore registers - pop rsi - pop rdx - pop rdi - pop rax - - ret -.error: - ; An error occurred - mov rdx, error_msg_print_string.len ; number of bytes - mov rsi, error_msg_print_string ; address of raw string to output - call print_rawstring - ; exit - jmp quit_error - -;; ------------------------------------------ -;; String itostring(Integer number) -;; -;; Converts an integer to a string (array of chars) -;; -;; Input in RAX -;; Return string address in RAX -itostring: - ; Save registers to restore afterwards - push rbx - push rcx - push rdx - push rsi - push rdi - - mov rcx, 0 ; counter of how many bytes we need to print in the end - - mov rbx, rax ; Original input - - ; Check if the number is negative - cmp rax, 0 - jge .divideLoop - - ; a negative number. To get the '-' sign - ; at the front the test is done again at the end - ; using the value stored in rbx - - neg rax ; Make it positive - -.divideLoop: - inc rcx ; count each byte to print - number of characters - xor rdx, rdx - mov rsi, 10 - idiv rsi ; divide rax by rsi - add rdx, 48 ; convert rdx to it's ascii representation - rdx holds the remainder after a divide instruction - ; Character is now in DL - dec rsp - mov BYTE [rsp], dl ; Put onto stack - - cmp rax, 0 ; can the integer be divided anymore? - jnz .divideLoop ; jump if not zero to the label divideLoop - - ; Check if the value was negative (in rbx) - cmp rbx, 0 - jge .create_string - - ; a negative number - dec rsp - mov BYTE [rsp], '-' - inc rcx - -.create_string: - ; Get an Array object to put the string into - call string_new ; Address in RAX - - ; put length into string - mov [rax + Array.length], ecx - - ; copy data from stack into string - ; Note: Currently this does not handle long strings - mov rdi, rax - add rdi, Array.data ; Address where raw string will go -.copyLoop: - mov BYTE dl, [rsp] ; Copy one byte at a time. Could be more efficient - mov [rdi], BYTE dl - inc rsp - inc rdi - dec rcx - cmp rcx, 0 - jnz .copyLoop - - ; Restore registers - pop rdi - pop rsi - pop rdx - pop rcx - pop rbx - - ret - -;------------------------------------------ -; void exit() -; Exit program and restore resources -quit: - mov eax, 60 ; system call 60 is exit - xor rdi, rdi ; exit code 0 - syscall ; invoke operating system to exit - -quit_error: - mov eax, 60 ; system call 60 is exit - mov rdi, 1 ; exit code 1 - syscall - + +section .text ;; Evaluates a form eval: @@ -715,85 +67,8 @@ rep_seq: mov rsi, rax ; Return value ret -;; Read a line from stdin -;; Gets a new string array, fills it until a newline or EOF is reached -;; Returns pointer to string in RAX -read_line: - ; Get an array to put the string into - ; Address in rax - call alloc_array - ; Mark it as a character array (string) - mov BYTE [rax + Array.type], maltype_string - - push rax ; Save pointer to string - - ; Read character by character until either newline or end of input - mov ebx, 0 ; Count how many characters read - mov rsi, rax - add rsi, Array.data ; Point to the data -.readLoop: - mov rax, 0 ; sys_read - mov rdi, 0 ; stdin - mov rdx, 1 ; count - syscall - - ; Characters read in RAX - cmp rax, 0 ; end loop if read <= 0 - jle .readLoopEnd - - mov cl, BYTE [rsi] - - cmp cl, 10 ; End if we read a newline - je .readLoopEnd - - cmp cl, 8 ; Backspace? - je .handleBackspace - - cmp cl, 31 ; Below space - jle .readLoop ; Ignore, keep going - cmp cl, 127 ; DEL or above - jge .readLoop ; Ignore, keep going - - inc ebx - inc rsi ; Move to next point in the array - jmp .readLoop ; Get another character - -.handleBackspace: - ; Check if we've read any characters - cmp ebx, 0 - je .readLoop ; If not, carry on the loop - ; Characters have been read. Remove one - dec ebx - dec rsi - jmp .readLoop -.readLoopEnd: - pop rax ; Restore pointer to string - mov DWORD [rax + Array.length], ebx ; Set string length - ret - - - _start: - - ; mov rsi, test_string1 - ; mov edx, test_string1.len - ; call raw_to_string ; address in rax - ; push rax - ; mov rsi, test_string2 - ; mov edx, test_string2.len - ; call raw_to_string - ; pop rsi - ; mov rdx, rax - ; call string_append_string - ; call print_string - - - ; Test printing short list of integers - ; mov rsi, test_cons2 - ; call pr_str - ; mov rsi, rax - ; call print_string ; ----------------------------- ; Main loop @@ -840,15 +115,5 @@ _start: jmp .mainLoop .mainLoopEnd: - ;mov rdx, 1 - ;mov rsi, - ;call print_rawstring - ;inc rsp - - ;mov rax, 1223 - ;call itostring - ;mov rsi, rax - ;call print_string - jmp quit diff --git a/nasm/system.asm b/nasm/system.asm new file mode 100644 index 0000000000..6389b2a64f --- /dev/null +++ b/nasm/system.asm @@ -0,0 +1,93 @@ +;;; System call functions +;;; +;;; This file contains system-specific functions, +;;; which use calls to the operating system (Linux) + + +;; ------------------------------------------- +;; Prints a raw string to stdout +;; String address in rsi, string length in rdx +print_rawstring: + push rax + push rdi + + ; write(1, string, length) + mov rax, 1 ; system call 1 is write + mov rdi, 1 ; file handle 1 is stdout + syscall + + pop rdi + pop rax + + ret + +;------------------------------------------ +; void exit() +; Exit program and restore resources +quit: + mov eax, 60 ; system call 60 is exit + xor rdi, rdi ; exit code 0 + syscall ; invoke operating system to exit + +quit_error: + mov eax, 60 ; system call 60 is exit + mov rdi, 1 ; exit code 1 + syscall + + +;; Read a line from stdin +;; Gets a new string array, fills it until a newline or EOF is reached +;; Returns pointer to string in RAX +read_line: + ; Get an array to put the string into + ; Address in rax + call alloc_array + ; Mark it as a character array (string) + mov BYTE [rax + Array.type], maltype_string + + push rax ; Save pointer to string + + ; Read character by character until either newline or end of input + mov ebx, 0 ; Count how many characters read + mov rsi, rax + add rsi, Array.data ; Point to the data +.readLoop: + mov rax, 0 ; sys_read + mov rdi, 0 ; stdin + mov rdx, 1 ; count + syscall + + ; Characters read in RAX + cmp rax, 0 ; end loop if read <= 0 + jle .readLoopEnd + + mov cl, BYTE [rsi] + + cmp cl, 10 ; End if we read a newline + je .readLoopEnd + + cmp cl, 8 ; Backspace? + je .handleBackspace + + cmp cl, 31 ; Below space + jle .readLoop ; Ignore, keep going + + cmp cl, 127 ; DEL or above + jge .readLoop ; Ignore, keep going + + inc ebx + inc rsi ; Move to next point in the array + jmp .readLoop ; Get another character + +.handleBackspace: + ; Check if we've read any characters + cmp ebx, 0 + je .readLoop ; If not, carry on the loop + ; Characters have been read. Remove one + dec ebx + dec rsi + jmp .readLoop +.readLoopEnd: + pop rax ; Restore pointer to string + mov DWORD [rax + Array.length], ebx ; Set string length + ret diff --git a/nasm/types.asm b/nasm/types.asm new file mode 100644 index 0000000000..55bd3b0bb6 --- /dev/null +++ b/nasm/types.asm @@ -0,0 +1,629 @@ +;; Data structures +;; =============== +;; +;; Memory management is done by having two fixed-size datatypes, +;; Cons and Array. +;; +;; Both Cons and Array have the following in common: +;; a type field at the start, a reference count, followed by data +;; [ type (8) | (8) | refs (16) | data ] +;; +;; +;; Type bit fields +;; --------------- +;; +;; The 8-bit type fields describe the Block, Container and Content type. +;; +;; The Block type is used for memory management, to determine the kind of memory block +;; The Container type indicates the data structure that the Cons or Array block is being used to represent +;; The Content type indicates the raw type of the data in the content +;; +;; Block type [1 bit]: +;; 0 0 - Cons memory block +;; 1 1 - Array memory block +;; +;; Container type [3 bits]: +;; 0 0 - Value (single boxed value for Cons blocks, vector for Array blocks). +;; 2 1 - List (value followed by pointer). Only for Cons blocks +;; 4 2 - Symbol (special char array). Only for Array blocks +;; 6 3 - Keyword +;; 8 4 - Map +;; 10 5 - Function +;; +;; Content type [4 bits]: +;; 0 0 - Nil +;; 16 1 - Bool +;; 32 2 - Char +;; 48 3 - Int +;; 64 4 - Float +;; 80 5 - Pointer (memory address) +;; 96 6 - Function (instruction address) +;; 112 7 - Empty (distinct from Nil) +;; +;; These represent MAL data types as follows: +;; +;; MAL type Block Container Content +;; --------- | -------- | ---------- | --------- +;; integer Cons Value Int +;; symbol Array Symbol Char +;; list Cons List Any +;; nil Cons Value Nil +;; true Cons Value Bool (1) +;; false Cons Value Bool (0) +;; string Array Value Char +;; keyword Array Keyword Char +;; vector Array Value Int/Float +;; hash-map Array Map Pointer (?TBD) +;; atom Cons Value Pointer +;; + +;; Cons type. +;; Used to store either a single value with type information +;; or a pair of (value, Pointer or Nil) to represent a list +STRUC Cons +.typecar: RESB 1 ; Type information for car (8 bit) +.typecdr: RESB 1 ; Type information for cdr (8 bits) +.refcount: RESW 1 ; Number of references to this Cons (16 bit) +.car: RESQ 1 ; First value (64 bit) +.cdr: RESQ 1 ; Second value (64 bit) +.size: ; Total size of struc +ENDSTRUC + + +%define array_chunk_len 32 ; Number of 64-bit values which can be stored in a single chunk + +STRUC Array +.type: RESB 1 ; Type information (8 bits) +.control: RESB 1 ; Control data (8 bits) +.refcount: RESW 1 ; Number of references to this Array (16 bit) +.length: RESD 1 ; Number of elements in this part of the array (32 bit) +.next RESQ 1 ; Pointer to the next chunk (64 bit) +.data: RESQ array_chunk_len ; Data storage +.size: ; Total size of struc +ENDSTRUC + +;; Type information + +%define block_mask 1 ; LSB for block type +%define container_mask 2 + 4 + 8 ; Next three bits for container type +%define content_mask 16 + 32 + 64 + 128 ; Four bits for content type + +;; Block types +%define block_cons 0 +%define block_array 1 + +;; Container types +%define container_value 0 +%define container_list 2 +%define container_symbol 4 +%define container_keyword 6 +%define container_map 8 +%define container_function 10 + +;; Content type +%define content_nil 0 +%define content_bool 16 +%define content_char 32 +%define content_int 48 +%define content_float 64 +%define content_pointer 80 ; Memory pointer (to Cons or Array) +%define content_function 96 ; Function pointer +%define content_empty 112 + +;; Common combinations for MAL types +%define maltype_integer (block_cons + container_value + content_int) +%define maltype_string (block_array + container_value + content_char) +%define maltype_symbol (block_array + container_symbol + content_char) +%define maltype_nil (block_cons + container_value + content_nil) +%define maltype_empty_list (block_cons + container_list + content_empty) + + +;; ------------------------------------------ + +section .data + +;; Fixed strings for printing + + +error_msg_print_string: db "Error in print string",10 +.len: equ $ - error_msg_print_string + +error_array_memory_limit: db "Error: Run out of memory for Array objects. Increase heap_array_limit.",10 +.len: equ $ - error_array_memory_limit + +error_cons_memory_limit: db "Error: Run out of memory for Cons objects. Increase heap_cons_limit.",10 +.len: equ $ - error_cons_memory_limit + +;; ------------------------------------------ +;; Memory management +;; +;; For each object (Cons or Array), there is a block of memory (in BSS). +;; When an object is requested it is first taken from the free list +;; If the free list is empty (address 0) then the next object in the block +;; is used, and the heap_x_number counter is incremented. When an object +;; is free'd it is pushed onto the heap_x_free list. + + +%define heap_cons_limit 10 ; Number of cons objects which can be created + +heap_cons_next: dd heap_cons_store ; Address of next cons in memory +heap_cons_free: dq 0 ; Address of start of free list + +%define heap_array_limit 10 ; Number of array objects which can be created + +heap_array_next: dd heap_array_store +heap_array_free: dq 0 + +section .bss + +;; Reserve space to store Cons and Array objects +heap_cons_store: resb heap_cons_limit * Cons.size +.end: ; Address of end of the store + +heap_array_store: resb heap_array_limit * Array.size +.end: + +section .text + +;; ------------------------------------------ +;; Array alloc_array() +;; +;; Returns the address of an Array object in RAX +;; +;; Working registers: rbx +alloc_array: + + ; Get the address of a free array + mov rax, [heap_array_free] ; Address of the array + + ; Check if it's null + cmp rax, 0 + je .create_array + + mov rbx, [rax + Array.next] ; Get the address of the next array in the linked list + mov [heap_array_free], rbx ; Put this address at the front of the list + jmp .initialise_array + +.create_array: + + ; Get the address of the next Array + mov rax, [heap_array_next] + ; Check if we've reached the end + cmp rax, heap_array_store.end + je .out_of_memory + + mov rbx, rax + add rbx, Array.size ; Address of the next array + mov [heap_array_next], rbx ; for next time + +.initialise_array: + ; Address of Array now in rax + mov BYTE [rax + Array.type], block_array + mov WORD [rax + Array.refcount], 1 ; Only one reference + mov DWORD [rax + Array.length], 0 + mov QWORD [rax + Array.next], 0 ; null next address + + ret + +.out_of_memory: + mov rsi, error_array_memory_limit + mov rdx, error_array_memory_limit.len + call print_rawstring + jmp quit_error + + +;; ------------------------------------------- +;; Decrements the reference count of the array in RSI +;; If the count reaches zero then push the array +;; onto the free list +release_array: + mov ax, WORD [rsi + Array.refcount] + + dec ax + mov WORD [rsi + Array.refcount], ax + jz .free ; If the count reaches zero then put on free list + ret + +.free: + ; Get the next field + mov rbx, [rsi + Array.next] + + mov rax, [heap_array_free] ; Get the current head + mov [rsi + Array.next], rax ; Put current head into the "next" field + mov [heap_array_free], rsi ; Push Array onto free list + + cmp rbx, 0 + jne .release_next ; If there is another array, then need to release it + + ret + +.release_next: + ; release the next array + mov rsi, rbx + call release_array + ret + +;; ------------------------------------------ +;; Cons alloc_cons() +;; +;; Returns the address of a Cons object in RAX +alloc_cons: + + ; Get the address of a free cons + mov rax, [heap_cons_free] ; Address of the cons + + ; Check if it's null + cmp rax, 0 + je .create_cons + + mov rbx, [rax + Cons.cdr] ; Get the address of the next cons in the linked list + mov [heap_cons_free], rbx ; Put this address at the front of the list + jmp .initialise_cons + +.create_cons: + + ; Get the address of the next Cons + mov rax, [heap_cons_next] + ; Check if we've reached the end + cmp rax, heap_cons_store.end + je .out_of_memory + + mov rbx, rax + add rbx, Cons.size ; Address of the next cons + mov [heap_cons_next], rbx ; for next time + +.initialise_cons: + ; Address of Cons now in rax + mov BYTE [rax + Cons.typecar], 0 + mov BYTE [rax + Cons.typecdr], 0 + mov WORD [rax + Cons.refcount], 1 ; Only one reference + mov QWORD [rax + Cons.car], 0 + mov QWORD [rax + Cons.cdr], 0 + ret + +.out_of_memory: + mov rsi, error_cons_memory_limit + mov rdx, error_cons_memory_limit.len + call print_rawstring + jmp quit_error + + +;; ------------------------------------------- +;; Decrements the reference count of the cons in RSI +;; If the count reaches zero then push the cons +;; onto the free list +release_cons: + mov ax, WORD [rsi + Cons.refcount] + dec ax + mov WORD [rsi + Cons.refcount], ax + jz .free ; If the count reaches zero then put on free list + ret + +.free: + ; Get and push cdr onto stack + mov rcx, [rsi + Cons.cdr] + push rcx ; Content of CDR + push rsi ; Original Cons object being released + + mov rax, [heap_cons_free] ; Get the current head + mov [rsi + Cons.cdr], rax ; Put current head into the "cdr" field + mov [heap_cons_free], rsi ; Push Cons onto free list + + ; Check if the CAR needs to be released + + mov al, BYTE [rsi+Cons.typecar] + and al, content_mask ; Test content type + cmp al, content_pointer + jne .free_cdr ; Jump if CAR not pointer + + ; CAR is a pointer to either a Cons or Array + ; Get the address stored in CAR + mov rsi, [rsi + Cons.car] + call release_object +.free_cdr: + pop rcx ; This was rsi, the original Cons + pop rsi ; This was rcx, the original Cons.cdr + + ; Get the type from the original Cons + mov al, BYTE [rcx+Cons.typecdr] + and al, content_mask ; Test content type + cmp al, content_pointer + jne .done + + call release_object +.done: + ret + + +;; Releases either a Cons or Array +;; Address of object in RSI +release_object: + mov al, BYTE [rsi] ; Get first byte + and al, block_mask ; Test block type + cmp al, block_array ; Test if it's an array + je .array + call release_cons + ret +.array: + call release_array + ret + +;; ------------------------------------------- +;; String type + +;; Create a new string, address in RAX +string_new: + call alloc_array + mov [rax], BYTE maltype_string + mov QWORD [rax + Array.next], 0 + ret + +;; Convert a raw string to a String type +;; +;; Input: Address of raw string in RSI, length in EDX +;; Output: Address of string in RAX +;; +;; Modifies registers: R8,R9,RCX +raw_to_string: + push rsi + push rdx + call string_new ; String now in RAX + pop rdx + pop rsi + mov [rax + Array.length], DWORD edx + mov r8, rax + add r8, Array.data ; Address of string data + mov r9, rsi ; Address of raw data + mov ecx, edx ; Count +.copy_loop: + + mov bl, BYTE [r9] + mov [r8], BYTE bl + inc r8 + inc r9 + dec ecx + jnz .copy_loop + ret + + + +;; Appends a character to a string +;; Input: Address of string in RSI, character in CL +string_append_char: + mov eax, DWORD [rsi + Array.length] + inc eax + mov DWORD [rsi + Array.length], eax + dec eax + add rax, rsi + add rax, Array.data ; End of data + mov [rax], BYTE cl + ret + +;; Appends a string to the end of a string +;; +;; Input: String to be modified in RSI +;; String to be copied in RDX +;; +;; Output: Modified string in RSI +;; +;; Working registers: +;; rax Array chunk for output (copied to) +;; rbx Array chunk for input (copied from) +;; cl Character being copied +;; r8 Address of destination +;; r9 Destination end address +;; r10 Address of source +;; r11 Source end address +string_append_string: + ; copy source Array address to rbx + mov rbx, rdx + + ; source data address in r10 + mov r10, rbx + add r10, Array.data ; Start of the data + + ; source data end address in r11 + mov r11, r10 + mov r8d, DWORD [rbx + Array.length] + add r11, r8 + + ; Find the end of the string in RSI + ; and put the address of the Array object into rax + mov rax, rsi +.find_string_end: + mov r8, QWORD [rax + Array.next] + cmp r8, 0 ; Next chunk is null + je .got_dest_end ; so reached end + + mov rax, r8 ; Go to next chunk + jmp .find_string_end +.got_dest_end: + + ; destination data address into r8 + mov r8, rax + add r8, Array.data + add r8d, DWORD [rax + Array.length] + + ; destination data end into r9 + mov r9, rax + add r9, Array.size + +.copy_loop: + ; Copy one byte from source to destination + mov cl, BYTE [r10] + mov BYTE [r8], cl + + ; move source to next byte + inc r10 + ; Check if we've reached the end of this Array + cmp r10, r11 + jne .source_ok + + ; have reached the end of the source Array + mov rbx, QWORD [rbx + Array.next] ; Get the next Array address + cmp rbx, 0 ; Test if it's null + je .finished ; No more, so we're done + ; Move on to next Array object + + ; Get source address into r10 + mov r10, rbx + add r10, Array.data ; Start of the data + + ; Source end address + mov r11, rbx + add r11, Array.size + +.source_ok: + + ; Move destination to next byte + inc r8 + ; Check if we've reached end of the Array + cmp r8, r9 + jne .copy_loop ; Next byte + + ; Reached the end of the destination + ; Need to allocate another Array + push rax + push rbx + call alloc_array ; New Array in rax + mov r8, rax ; copy to r8 + pop rbx + pop rax + + ; Previous Array in rax. + ; Add a reference to the new array and set length + mov QWORD [rax + Array.next], r8 + mov DWORD [rax + Array.length], (Array.size - Array.data) + mov rax, r8 ; new array + add r8, Array.data ; Start of data + + mov r9, rax + add r9, Array.size + +.finished: + ; Compare r8 (destination) with data start + ; to get length of string + sub r8, rax + sub r8, Array.data + inc r8 + ; r8 now contains length + mov DWORD [rax + Array.length], r8d + + ret + +;; ------------------------------------------ +;; void print_string(char array) +;; Address of the char Array should be in RSI +print_string: + ; Push registers we're going to use + push rax + push rdi + push rdx + push rsi + + ; Check that we have a char array + mov al, [rsi] + cmp al, maltype_string + jne .error + + ; write(1, string, length) + mov edx, [rsi + Array.length] ; number of bytes + add rsi, Array.data ; address of raw string to output + call print_rawstring + + ; Restore registers + pop rsi + pop rdx + pop rdi + pop rax + + ret +.error: + ; An error occurred + mov rdx, error_msg_print_string.len ; number of bytes + mov rsi, error_msg_print_string ; address of raw string to output + call print_rawstring + ; exit + jmp quit_error + +;; ------------------------------------------ +;; String itostring(Integer number) +;; +;; Converts an integer to a string (array of chars) +;; +;; Input in RAX +;; Return string address in RAX +itostring: + ; Save registers to restore afterwards + push rbx + push rcx + push rdx + push rsi + push rdi + + mov rcx, 0 ; counter of how many bytes we need to print in the end + + mov rbx, rax ; Original input + + ; Check if the number is negative + cmp rax, 0 + jge .divideLoop + + ; a negative number. To get the '-' sign + ; at the front the test is done again at the end + ; using the value stored in rbx + + neg rax ; Make it positive + +.divideLoop: + inc rcx ; count each byte to print - number of characters + xor rdx, rdx + mov rsi, 10 + idiv rsi ; divide rax by rsi + add rdx, 48 ; convert rdx to it's ascii representation - rdx holds the remainder after a divide instruction + ; Character is now in DL + dec rsp + mov BYTE [rsp], dl ; Put onto stack + + cmp rax, 0 ; can the integer be divided anymore? + jnz .divideLoop ; jump if not zero to the label divideLoop + + ; Check if the value was negative (in rbx) + cmp rbx, 0 + jge .create_string + + ; a negative number + dec rsp + mov BYTE [rsp], '-' + inc rcx + +.create_string: + ; Get an Array object to put the string into + call string_new ; Address in RAX + + ; put length into string + mov [rax + Array.length], ecx + + ; copy data from stack into string + ; Note: Currently this does not handle long strings + mov rdi, rax + add rdi, Array.data ; Address where raw string will go +.copyLoop: + mov BYTE dl, [rsp] ; Copy one byte at a time. Could be more efficient + mov [rdi], BYTE dl + inc rsp + inc rdi + dec rcx + cmp rcx, 0 + jnz .copyLoop + + ; Restore registers + pop rdi + pop rsi + pop rdx + pop rcx + pop rbx + + ret + + From c1cd93d09795331b64c1ef8081e6deb177af0163 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sun, 22 Oct 2017 15:09:13 +0100 Subject: [PATCH 0544/2308] Added arithmetic functions to core.asm Starting step 2. * core_add, core_sub, core_mul and core_div are defined in core.asm. These take a list of integers and return an integer. Currently step2_eval.asm is hard-wired to call core_add, so (1 2 3) -> 6 --- nasm/Makefile | 8 ++- nasm/core.asm | 86 ++++++++++++++++++++++++++++++ nasm/step2_eval.asm | 124 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 nasm/core.asm create mode 100644 nasm/step2_eval.asm diff --git a/nasm/Makefile b/nasm/Makefile index 1e7ea2fc65..9dfc396bac 100644 --- a/nasm/Makefile +++ b/nasm/Makefile @@ -2,6 +2,12 @@ step0_repl: step0_repl.asm nasm -felf64 step0_repl.asm ld -o $@ step0_repl.o -step1_read_print: step1_read_print.asm +COMPONENTS=core.asm reader.asm printer.asm types.asm system.asm + +step1_read_print: step1_read_print.asm $(COMPONENTS) nasm -felf64 step1_read_print.asm ld -o $@ step1_read_print.o + +step2_eval: step2_eval.asm $(COMPONENTS) + nasm -felf64 step2_eval.asm + ld -o $@ step2_eval.o diff --git a/nasm/core.asm b/nasm/core.asm new file mode 100644 index 0000000000..79d88cda7d --- /dev/null +++ b/nasm/core.asm @@ -0,0 +1,86 @@ +;; Core functions +;; +;; + +section .text + +;; Adds a list of numbers, address in RSI +;; Returns the sum as a number object with address in RAX +;; Since most of the code is common to all operators, +;; RBX is used to jump to the required instruction +core_add: + mov rbx, core_arithmetic.do_addition + jmp core_arithmetic +core_sub: + mov rbx, core_arithmetic.do_subtraction + jmp core_arithmetic +core_mul: + mov rbx, core_arithmetic.do_multiply + jmp core_arithmetic +core_div: + mov rbx, core_arithmetic.do_division + ; Fall through to core_arithmetic +core_arithmetic: + ; Check that the first object is a number + mov cl, BYTE [rsi] + mov ch, cl + and ch, block_mask + cmp ch, block_cons + jne .error + mov ch, cl + and ch, content_mask + cmp ch, content_int + jne .error + + ; Put the starting value in rax + mov rax, [rsi + Cons.car] + +.add_loop: + ; Fetch the next value + mov cl, [rsi + Cons.typecdr] + cmp cl, content_nil + je .finished ; Nothing let + cmp cl, content_pointer + jne .error + + mov rsi, [rsi + Cons.cdr] ; Get next cons + + ; Check that it is an integer + mov cl, BYTE [rsi] + and cl, content_mask + cmp cl, content_int + jne .error + + ; Jump to the required operation, address in RBX + jmp rbx + +.do_addition: + add rax, [rsi + Cons.car] + jmp .add_loop +.do_subtraction: + sub rax, [rsi + Cons.car] + jmp .add_loop +.do_multiply: + imul rax, [rsi + Cons.car] + jmp .add_loop +.do_division: + xor rdx, rdx ; Zero high bits + mov rcx, [rsi + Cons.car] + idiv rcx + jmp .add_loop + +.finished: + ; Value in rbx + push rax + ; Get a Cons object to put the result into + call alloc_cons + pop rbx + mov [rax], BYTE maltype_integer + mov [rax + Cons.car], rbx + ret +.error: + ; Return nil + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret diff --git a/nasm/step2_eval.asm b/nasm/step2_eval.asm new file mode 100644 index 0000000000..eed5fe19b8 --- /dev/null +++ b/nasm/step2_eval.asm @@ -0,0 +1,124 @@ +;; +;; nasm -felf64 step1_read_print.asm && ld step1_read_print.o && ./a.out + +;; Calling convention: Address of input is in RSI +;; Address of return value is in RAX +;; + +global _start + +%include "types.asm" ; Data types, memory +%include "system.asm" ; System calls +%include "reader.asm" ; String -> Data structures +%include "core.asm" ; Core functions +%include "printer.asm" ; Data structures -> String + +section .data + +test_string1: db 10, "test1", 10 +.len: equ $ - test_string1 + +test_string2: db 10, "test2", 10 +.len: equ $ - test_string2 + +;str: ISTRUC Array +;AT Array.type, db maltype_string +;AT Array.length, dd 6 +;AT Array.data, db 'hello',10 +;IEND + +test_cons: ISTRUC Cons +AT Cons.typecar, db ( maltype_integer + 2 ) +AT Cons.typecdr, db 0 +AT Cons.car, dq 123 +IEND + +test_cons2: ISTRUC Cons +AT Cons.typecar, db ( maltype_integer + 2 ) +AT Cons.typecdr, db content_pointer +AT Cons.car, dq 456 +AT Cons.cdr, dq test_cons +IEND + +;; ------------------------------------------ +;; Fixed strings for printing + +prompt_string: db 10,"user> " ; The string to print at the prompt +.len: equ $ - prompt_string + +section .text + +;; Evaluates a form +eval: + mov rax, rsi ; Return the input + ret + +;; Prints the result +print: + mov rax, rsi ; Return the input + ret + +;; Read-Eval-Print in sequence +rep_seq: + call read_str + mov rsi, rax ; Output of read into input of eval + call eval + mov rsi, rax ; Output of eval into input of print + call print + mov rsi, rax ; Return value + ret + + +_start: + + ; ----------------------------- + ; Main loop + +.mainLoop: + ; print the prompt + mov rdx, prompt_string.len ; number of bytes + mov rsi, prompt_string ; address of raw string to output + call print_rawstring + + call read_line + + ; Check if we have a zero-length string + cmp DWORD [rax+Array.length], 0 + je .mainLoopEnd + + push rax ; Save address of the string + + ; Put into read_str + mov rsi, rax + call read_str + push rax + + ; Add together + mov rsi, rax + call core_add + + ; Put into pr_str + mov rsi, rax + call pr_str + push rax + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from pr_str + pop rsi + call release_array + + ; Release the object from read_str + pop rsi + call release_object ; Could be Cons or Array + + ; Release the string + pop rsi + call release_array + + jmp .mainLoop +.mainLoopEnd: + + jmp quit + From 5c2377fd079a0c73b3d01f88418d06b5de4556dc Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sun, 22 Oct 2017 23:47:18 +0100 Subject: [PATCH 0545/2308] Started adding Map type An associative type, for now being implemented as a list with an even number of elements: key,value,key,value,... Later may replace with something more efficient. Needed for environment, to allow lookup of functions from symbols. --- nasm/types.asm | 134 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 1 deletion(-) diff --git a/nasm/types.asm b/nasm/types.asm index 55bd3b0bb6..0bb89556a9 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -53,7 +53,7 @@ ;; string Array Value Char ;; keyword Array Keyword Char ;; vector Array Value Int/Float -;; hash-map Array Map Pointer (?TBD) +;; hash-map Cons Map Alternate key, values ;; atom Cons Value Pointer ;; @@ -247,6 +247,9 @@ release_array: ;; Cons alloc_cons() ;; ;; Returns the address of a Cons object in RAX +;; +;; Modifies: +;; RBX alloc_cons: ; Get the address of a free cons @@ -348,6 +351,21 @@ release_object: call release_array ret +;; Increment reference count of Cons or Array +;; Address of object in RSI +;; +;; This code makes use of the fact that the reference +;; count is in the same place in Cons and Array types +;; +;; Modifies +;; RAX +incref_object: + mov ax, WORD [rsi + Cons.refcount] ; Same for Array + inc ax + ; Check for overflow? + mov [rsi + Cons.refcount], WORD ax + ret + ;; ------------------------------------------- ;; String type @@ -626,4 +644,118 @@ itostring: ret +;; ------------------------------------------------------------ +;; Map type + +map_new: + call alloc_cons + mov [rax], BYTE (block_cons + container_map + content_empty) + mov [rax + Cons.typecdr], BYTE content_nil + ret + + +;; Add to map. Input is a list with an even number of values +;; as (key, value, key, value, ...) +;; +;; Inputs: +;; RSI - Map to append to. This is not modified +;; RDI - List to add to the map +;; Outputs: +;; RAX - New map +;; +;; Modifies: +;; RCX +map_add: + ; Check type of input + mov cl, BYTE [rsi] + mov cl, ch + and ch, block_mask + container_mask + cmp ch, block_cons + container_map + jne .error + + mov cl, BYTE [rdi] + and cl, block_mask + container_mask + cmp cl, block_cons + container_list + jne .error + + xor r8, r8 ; Zero r8 + +.copy_input: + ; Copy input list, changing container type + call alloc_cons + + mov cl, BYTE [rdi] + and cl, content_mask ; Keep the content + add cl, block_cons + container_map + mov [rax], BYTE cl ; Set type + mov rcx, [rdi+Cons.car] ; Copy data + mov [rax+Cons.car], rcx + + cmp cl, (block_cons + container_map + content_pointer) + jne .copy_not_pointer + + ; Copying a pointer to data + ; so need to increase the reference count + mov bx, WORD [rcx + Cons.refcount] ; Same offset for Array + inc bx + mov [rcx + Cons.refcount], WORD bx + +.copy_not_pointer: + + ; Check if this is the first object + cmp r8, 0 + jnz .copy_not_first + mov r8, rax ; Save start of map to R8 + mov r9, rax ; Last cons in R9 + jmp .copy_next +.copy_not_first: + ; Append to R9 + mov [r9+Cons.cdr], rax + mov [r9+Cons.typecdr], BYTE content_pointer + + ; Put new Cons in R9 as the latest in the list + mov r9, rax + +.copy_next: + ; Check if we've reached the end + mov cl, BYTE [rdi + Cons.typecdr] + cmp cl, content_nil + je .copy_finished + + ; Not yet. Get next Cons and keep going + mov rdi, [rdi + Cons.cdr] + jmp .copy_input + +.copy_finished: + ; Start of map in r8, end in r9 + + ; Check if the original map is empty + mov cl, [rsi] + and cl, content_mask + cmp cl, content_empty + je .return + + ; Put old map on the end of the new map + ; For now this avoids the need to overwrite + ; values in the map, since a search will find + ; the new values first. + + mov [r9 + Cons.cdr], rsi + mov [r9 + Cons.typecdr], BYTE content_pointer + + ; Increment reference count + mov bx, WORD [rsi + Cons.refcount] + inc bx + mov [rsi + Cons.refcount], WORD bx + +.return: + mov rax, r8 + ret + +.error: + ; Return nil + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret From a58f416a0d3875e971291ada75333d468aed013b Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 23 Oct 2017 09:06:26 +0200 Subject: [PATCH 0546/2308] hy, php, powershell, scala: Add number?, fn?, macro? --- hy/core.hy | 4 ++++ php/core.php | 3 +++ php/types.php | 2 ++ powershell/core.psm1 | 4 ++++ powershell/types.psm1 | 4 ++++ scala/core.scala | 22 ++++++++++++++++++++++ 6 files changed, 39 insertions(+) diff --git a/hy/core.hy b/hy/core.hy index d4984a7e3d..37ce46659c 100644 --- a/hy/core.hy +++ b/hy/core.hy @@ -27,11 +27,15 @@ "nil?" none? "true?" (fn [a] (and (instance? bool a) (= a True))) "false?" (fn [a] (and (instance? bool a) (= a False))) + "number?" (fn [a] (instance? int a)) "string?" (fn [a] (and (string? a) (not (keyword? a)))) "symbol" (fn [a] (Sym a)) "symbol?" (fn [a] (instance? Sym a)) "keyword" (fn [a] (Keyword (if (keyword? a) a (+ ":" a)))) "keyword?" (fn [a] (keyword? a)) + "fn?" (fn [a] (and (callable a) (or (not (hasattr a "macro")) + (not a.macro)))) + "macro?" (fn [a] (and (callable a) (and (hasattr a "macro") a.macro))) "pr-str" (fn [&rest a] (Str (.join " " (map (fn [e] (pr-str e True)) a)))) "str" (fn [&rest a] (Str (.join "" (map (fn [e] (pr-str e False)) a)))) diff --git a/php/core.php b/php/core.php index 49c63246fa..245b938ecd 100644 --- a/php/core.php +++ b/php/core.php @@ -209,12 +209,15 @@ function swap_BANG($atm, $f) { 'nil?'=> function ($a) { return _nil_Q($a); }, 'true?'=> function ($a) { return _true_Q($a); }, 'false?'=> function ($a) { return _false_Q($a); }, + 'number?'=> function ($a) { return _number_Q($a); }, 'symbol'=> function () { return call_user_func_array('_symbol', func_get_args()); }, 'symbol?'=> function ($a) { return _symbol_Q($a); }, 'keyword'=> function () { return call_user_func_array('_keyword', func_get_args()); }, 'keyword?'=> function ($a) { return _keyword_Q($a); }, 'string?'=> function ($a) { return _string_Q($a); }, + 'fn?'=> function($a) { return _fn_Q($a) || (_function_Q($a) && !$a->ismacro ); }, + 'macro?'=> function($a) { return _function_Q($a) && $a->ismacro; }, 'pr-str'=> function () { return call_user_func_array('pr_str', func_get_args()); }, 'str'=> function () { return call_user_func_array('str', func_get_args()); }, 'prn'=> function () { return call_user_func_array('prn', func_get_args()); }, diff --git a/php/types.php b/php/types.php index 1279f88f8e..57157b314a 100644 --- a/php/types.php +++ b/php/types.php @@ -50,6 +50,7 @@ function _false_Q($obj) { return $obj === false; } function _string_Q($obj) { return is_string($obj) && strpos($obj, chr(0x7f)) !== 0; } +function _number_Q($obj) { return is_int($obj); } // Symbols @@ -114,6 +115,7 @@ function _function($func, $type='platform', return new FunctionClass($func, $type, $ast, $env, $params, $ismacro); } function _function_Q($obj) { return $obj instanceof FunctionClass; } +function _fn_Q($obj) { return $obj instanceof Closure; } // Parent class of list, vector, hash-map diff --git a/powershell/core.psm1 b/powershell/core.psm1 index f95464cffa..b2e592dee3 100644 --- a/powershell/core.psm1 +++ b/powershell/core.psm1 @@ -103,11 +103,15 @@ $core_ns = @{ "nil?" = { param($a); $a -eq $null }; "true?" = { param($a); $a -eq $true }; "false?" = { param($a); $a -eq $false }; + "number?" = { param($a); $a -is [int32] }; "string?" = { param($a); string? $a }; "symbol" = Get-Command new-symbol; "symbol?" = { param($a); symbol? $a }; "keyword" = Get-Command new-keyword; "keyword?" = { param($a); keyword? $a }; + "fn?" = { param($a); (fn? $a) -or ((malfunc? $a) -and + (-not $a.macro)) }; + "macro?" = { param($a); (malfunc? $a) -and $a.macro }; "pr-str" = { pr_seq $args $true " " }; "str" = { pr_seq $args $false "" }; diff --git a/powershell/types.psm1 b/powershell/types.psm1 index e2fb735ec2..7eeaa37fc1 100644 --- a/powershell/types.psm1 +++ b/powershell/types.psm1 @@ -271,6 +271,10 @@ function new-malfunc($ast, $params, $env, $fn, $macro, $meta) { function malfunc?($obj) { $obj -is [MalFunc] } + +function fn?($obj) { + $obj -is [System.Management.Automation.ScriptBlock] +} # # General functions # diff --git a/scala/core.scala b/scala/core.scala index f92e660da8..c56851beff 100644 --- a/scala/core.scala +++ b/scala/core.scala @@ -31,6 +31,21 @@ object core { } } + def fn_Q(a: List[Any]) = { + a(0) match { + case s: Func => true + case s: MalFunction => !s.asInstanceOf[MalFunction].ismacro + case _ => false + } + } + + def macro_Q(a: List[Any]) = { + a(0) match { + case s: MalFunction => s.asInstanceOf[MalFunction].ismacro + case _ => false + } + } + // number functions def _bool_op(a: List[Any], op: (Long, Long) => Boolean) = { op(a(0).asInstanceOf[Long],a(1).asInstanceOf[Long]) @@ -40,6 +55,10 @@ object core { op(a(0).asInstanceOf[Long],a(1).asInstanceOf[Long]) } + def number_Q(a: List[Any]) = { + a(0).isInstanceOf[Long] || a(0).isInstanceOf[Double] + } + // string functions def read_string(a: List[Any]) = { @@ -232,11 +251,14 @@ object core { "nil?" -> ((a: List[Any]) => a(0) == null), "true?" -> ((a: List[Any]) => a(0) == true), "false?" -> ((a: List[Any]) => a(0) == false), + "number?" -> number_Q _, "string?" -> string_Q _, "symbol" -> ((a: List[Any]) => Symbol(a(0).asInstanceOf[String])), "symbol?" -> ((a: List[Any]) => a(0).isInstanceOf[Symbol]), "keyword" -> keyword _, "keyword?" -> keyword_Q _, + "fn?" -> fn_Q, + "macro?" -> macro_Q, "pr-str" -> ((a: List[Any]) => _pr_list(a, true, " ")), "str" -> ((a: List[Any]) => _pr_list(a, false, "")), From 2ff47a342fa3deb66b324bea227b8a8e81743429 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 23 Oct 2017 17:15:42 +0200 Subject: [PATCH 0547/2308] Fix typo --- tests/stepA_mal.mal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stepA_mal.mal b/tests/stepA_mal.mal index 5adb0824fa..7ba1c30372 100644 --- a/tests/stepA_mal.mal +++ b/tests/stepA_mal.mal @@ -100,7 +100,7 @@ ;; TODO move these to optional functionality after adding them to all ;; implementations ;; -;; Testing string? function +;; Testing number? function (number? 123) ;=>true (number? -1) From 27a793e1d5d17b410d15c02c689b56fc22b338dc Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 23 Oct 2017 17:15:49 +0200 Subject: [PATCH 0548/2308] hy: Add missing cond macro --- hy/stepA_mal.hy | 1 + 1 file changed, 1 insertion(+) diff --git a/hy/stepA_mal.hy b/hy/stepA_mal.hy index 67c9943417..0981895fd9 100755 --- a/hy/stepA_mal.hy +++ b/hy/stepA_mal.hy @@ -174,6 +174,7 @@ (REP "(def! *gensym-counter* (atom 0))") (REP "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))") (REP "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))") +(REP "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") (defmain [&rest args] (if (>= (len args) 2) From f996de18bb6f3f8e0265c144001e00a855e744f3 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 23 Oct 2017 17:16:00 +0200 Subject: [PATCH 0549/2308] hy: Fix number? --- hy/core.hy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/core.hy b/hy/core.hy index 37ce46659c..b78dfd8c96 100644 --- a/hy/core.hy +++ b/hy/core.hy @@ -27,7 +27,7 @@ "nil?" none? "true?" (fn [a] (and (instance? bool a) (= a True))) "false?" (fn [a] (and (instance? bool a) (= a False))) - "number?" (fn [a] (instance? int a)) + "number?" (fn [a] (and (not (instance? bool a)) (instance? int a))) "string?" (fn [a] (and (string? a) (not (keyword? a)))) "symbol" (fn [a] (Sym a)) "symbol?" (fn [a] (instance? Sym a)) From 1a318c5ed8b71f9531e0cc0795db2f14981e6ebe Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 23 Oct 2017 23:14:57 +0100 Subject: [PATCH 0550/2308] Work on object comparison Compare integers and char characters. Needed for associative (map) type on symbols. --- nasm/core.asm | 33 ++++++++++ nasm/step2_eval.asm | 4 +- nasm/types.asm | 157 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+), 2 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 79d88cda7d..13b9436387 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -84,3 +84,36 @@ core_arithmetic: mov [rax], BYTE maltype_nil mov [rax + Cons.typecdr], BYTE content_nil ret + +;; Test objects for equality +core_equal_p: + ; Check that rsi contains a list + mov cl, BYTE [rsi] + and cl, block_mask + container_mask + cmp cl, block_cons + container_list + jne .error + + ; Check that the list has a second pointer + mov cl, BYTE [rsi + Cons.typecdr] + cmp cl, content_pointer + jne .error + + ; move second pointer into rdi + mov rdi, [rsi + Cons.cdr] + + ; Compare rsi and rdi objects + call compare_objects ; result in rax + + ; for now put result into Cons + mov rdi, rax + call alloc_cons + mov [rax], BYTE maltype_integer + mov [rax + Cons.typecdr], BYTE content_nil + mov [rax + Cons.car], rdi + ret +.error: + ; Return nil + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret diff --git a/nasm/step2_eval.asm b/nasm/step2_eval.asm index eed5fe19b8..1bd9524d76 100644 --- a/nasm/step2_eval.asm +++ b/nasm/step2_eval.asm @@ -93,9 +93,9 @@ _start: call read_str push rax - ; Add together + ; Compare objects mov rsi, rax - call core_add + call core_equal_p ; Put into pr_str mov rsi, rax diff --git a/nasm/types.asm b/nasm/types.asm index 0bb89556a9..e40443d687 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -644,6 +644,163 @@ itostring: ret + +;; ------------------------------------------------------------ +;; Object comparison +;; +;; These comparison functions take two objects +;; in RSI and RDI +;; and return a code (not an object) in RAX +;; +;; RAX = 0 Objects are equal +;; 1 RSI object is greater than RDI +;; 2 RSI object is less than RDI +;; -1 Different object types, or no ordering +;; +;; Note that the ordering of objects depends on the type +;; strings - Alphabetical +;; +;; +;; + +;; Given an object in RSI, follows pointers +;; to return the value object in RAX +;; +;; Modifies registers: +;; RCX +compare_get_value: + mov cl, BYTE [rsi] + mov ch, cl + and ch, block_mask + jnz .nop ; Got an Array + + ; Here got Cons + mov ch, cl + and ch, content_mask + cmp ch, content_pointer + jne .nop ; Not a pointer + + ; Got a pointer, so follow and return + mov rax, [rsi + Cons.car] + ret +.nop: + mov rax, rsi + ret + +;; Compare two objects. Note that this does not compare lists +;; but will just compare the first element +;; +;; Modifies registers +;; RCX +;; RBX +compare_objects: + ; Get the value that RSI points to + call compare_get_value + mov rbx, rax ; Save in RBX + ; Get the value that RDI points to + mov rsi, rdi + call compare_get_value + mov rdi, rax + mov rsi, rbx + + ; now get types + mov cl, BYTE [rsi] ; Type of RSI + mov bl, BYTE [rdi] ; Type of RDI + + ; Don't care about container type + and cl, block_mask + content_mask + and bl, block_mask + content_mask + + cmp bl, cl ; compare block and content + jne .different_types + + ; Here the same block, content type + ; May be different container (value/list, string/symbol) + cmp bl, block_cons + content_nil + je .objects_equal ; nil + + cmp bl, block_array + content_char + je compare_char_array ; strings, symbols + + cmp bl, block_cons + content_int + je .integers + + ; Unknown + jmp .different_types + +.integers: + ; two Cons objects, both containing integers + mov rbx, [rsi + Cons.car] + cmp rbx, [rdi + Cons.car] + je .objects_equal + jl .rdi_greater + jmp .rsi_greater + +.objects_equal: + mov rax, 0 + ret + +.rsi_greater: ; rsi > rdi + mov rax, 1 + ret + +.rdi_greater: ; rdi > rsi + mov rax, 2 + ret + +.different_types: + mov rax, -1 + ret + + +;; Char array objects (strings, symbols, keywords) in RSI and RDI +;; Return code in RAX +;; +;; Modifies registers: +;; RBX +;; RCX +;; RDX +compare_char_array: + ; Check length + mov eax, DWORD [rsi + Array.length] + mov ebx, DWORD [rdi + Array.length] + cmp eax, ebx + jne .different + + ; same length + mov rbx, rsi + add rbx, Array.data + mov rcx, rdi + add rcx, Array.data +.compare_loop: + ; get next character + mov dl, BYTE [rbx] + cmp dl, BYTE [rcx] + jl .rdi_greater + jg .rsi_greater + + ; equal + inc rbx + inc rcx + dec eax + jnz .compare_loop + + ; equal + mov rax, 0 + ret + +.rsi_greater: ; rsi > rdi + mov rax, 1 + ret + +.rdi_greater: ; rdi > rsi + mov rax, 2 + ret + +.different: + mov rax, -1 + ret + ;; ------------------------------------------------------------ ;; Map type From 677cfe0c18dabad86c897d1c03f76a23df02bd3c Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Tue, 24 Oct 2017 13:09:47 +0000 Subject: [PATCH 0551/2308] objc, perl6, r: Add number?, fn?, macro? --- objc/core.m | 10 ++++++++++ perl6/core.pm | 3 +++ r/core.r | 3 +++ r/types.r | 5 +++++ 4 files changed, 21 insertions(+) diff --git a/objc/core.m b/objc/core.m index 1397019791..2acceeec8b 100644 --- a/objc/core.m +++ b/objc/core.m @@ -48,6 +48,16 @@ + (NSDictionary *)ns { ![args[0] isKindOfClass:[MalSymbol class]] && !string_Q(args[0])); }, + @"number?": ^(NSArray *args){ + return wrap_tf([args[0] isKindOfClass:[NSNumber class]]); + }, + @"fn?": ^(NSArray *args){ + return wrap_tf(block_Q(args[0]) || + ([args[0] isKindOfClass:[MalFunc class]] && ![(MalFunc *)args[0] isMacro])); + }, + @"macro?": ^(NSArray *args){ + return wrap_tf([args[0] isKindOfClass:[MalFunc class]] && [(MalFunc *)args[0] isMacro]); + }, @"pr-str": ^(NSArray *args){ NSMutableArray * res = [NSMutableArray array]; diff --git a/perl6/core.pm b/perl6/core.pm index a3f20253bd..50baa455bc 100644 --- a/perl6/core.pm +++ b/perl6/core.pm @@ -79,6 +79,9 @@ our %ns = ( symbol => MalCode({ MalSymbol($^a.val) }), keyword => MalCode({ $^a.val ~~ /^\x29E/ ?? $^a !! MalString("\x29E" ~ $^a.val) }), 'keyword?' => MalCode({ $^a.val ~~ /^\x29E/ ?? $TRUE !! $FALSE }), + 'number?' => MalCode({ $^a ~~ MalNumber ?? $TRUE !! $FALSE }), + 'fn?' => MalCode({ ($^a ~~ MalCallable && !$^a.?is_macro) ?? $TRUE !! $FALSE }), + 'macro?' => MalCode({ $^a.?is_macro ?? $TRUE !! $FALSE }), vector => MalCode({ MalVector(@_) }), 'vector?' => MalCode({ $^a ~~ MalVector ?? $TRUE !! $FALSE }), hash-map => MalCode({ MalHashMap(@_.map({ $^a.val => $^b }).Hash) }), diff --git a/r/core.r b/r/core.r index 2a658e4cc1..a14c0740b1 100644 --- a/r/core.r +++ b/r/core.r @@ -145,6 +145,9 @@ core_ns <- list( "symbol?"=.symbol_q, "keyword"=new.keyword, "keyword?"=.keyword_q, + "number?"=.number_q, + "fn?"=.fn_q, + "macro?"=.macro_q, "pr-str"=pr_str, "str"=str, diff --git a/r/types.r b/r/types.r index b9ecfe4225..bfadaa1f7f 100644 --- a/r/types.r +++ b/r/types.r @@ -104,6 +104,8 @@ new.keyword <- function(name) concat("\u029e", name) "" == substring(obj,1,8)) } +.number_q <- function(obj) "numeric" == class(obj) || "integer" == class(obj) + # Functions malfunc <- function(eval, ast, env, params) { @@ -128,6 +130,9 @@ fapply <- function(mf, args) { } } +.fn_q <- function(obj) "function" == class(obj) || (.malfunc_q(obj) && !obj$ismacro) +.macro_q <- function(obj) .malfunc_q(obj) && obj$ismacro + # Lists new.list <- function(...) new.listl(list(...)) new.listl <- function(lst) { class(lst) <- "List"; lst } From 14a5c0d8bc0f676dea73940330f4cb212968a610 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 24 Oct 2017 09:29:51 -0500 Subject: [PATCH 0552/2308] make: move macro attr to fn rather than in env. --- make/step8_macros.mk | 4 ++-- make/step9_try.mk | 4 ++-- make/stepA_mal.mk | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/make/step8_macros.mk b/make/step8_macros.mk index 7ea351edec..7ee0a94404 100644 --- a/make/step8_macros.mk +++ b/make/step8_macros.mk @@ -32,7 +32,7 @@ $(strip \ endef define IS_MACRO_CALL -$(if $(call _list?,$(1)),$(call ENV_FIND,$(2),_macro_$($(call _nth,$(1),0)_value)),) +$(if $(call _list?,$(1)),$(if $(call ENV_FIND,$(2),$($(call _nth,$(1),0)_value)),$(_macro_$(call ENV_GET,$(2),$($(call _nth,$(1),0)_value))),),) endef define MACROEXPAND @@ -96,7 +96,7 @@ $(if $(__ERROR),,\ $(foreach a1,$(call _nth,$(1),1),\ $(foreach a2,$(call _nth,$(1),2),\ $(foreach res,$(call EVAL,$(a2),$(2)),\ - $(if $(call ENV_SET,$(2),_macro_$($(a1)_value),true),,)\ + $(eval _macro_$(res) = true)\ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\ $(if $(call _EQ,macroexpand,$($(a0)_value)),\ $(call MACROEXPAND,$(call _nth,$(1),1),$(2)),\ diff --git a/make/step9_try.mk b/make/step9_try.mk index 47d1fd72f2..2066795625 100644 --- a/make/step9_try.mk +++ b/make/step9_try.mk @@ -32,7 +32,7 @@ $(strip \ endef define IS_MACRO_CALL -$(if $(call _list?,$(1)),$(call ENV_FIND,$(2),_macro_$($(call _nth,$(1),0)_value)),) +$(if $(call _list?,$(1)),$(if $(call ENV_FIND,$(2),$($(call _nth,$(1),0)_value)),$(_macro_$(call ENV_GET,$(2),$($(call _nth,$(1),0)_value))),),) endef define MACROEXPAND @@ -96,7 +96,7 @@ $(if $(__ERROR),,\ $(foreach a1,$(call _nth,$(1),1),\ $(foreach a2,$(call _nth,$(1),2),\ $(foreach res,$(call EVAL,$(a2),$(2)),\ - $(if $(call ENV_SET,$(2),_macro_$($(a1)_value),true),,)\ + $(eval _macro_$(res) = true)\ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\ $(if $(call _EQ,macroexpand,$($(a0)_value)),\ $(call MACROEXPAND,$(call _nth,$(1),1),$(2)),\ diff --git a/make/stepA_mal.mk b/make/stepA_mal.mk index 5e3642076f..475009d598 100644 --- a/make/stepA_mal.mk +++ b/make/stepA_mal.mk @@ -32,7 +32,7 @@ $(strip \ endef define IS_MACRO_CALL -$(if $(call _list?,$(1)),$(call ENV_FIND,$(2),_macro_$($(call _nth,$(1),0)_value)),) +$(if $(call _list?,$(1)),$(if $(call ENV_FIND,$(2),$($(call _nth,$(1),0)_value)),$(_macro_$(call ENV_GET,$(2),$($(call _nth,$(1),0)_value))),),) endef define MACROEXPAND @@ -96,7 +96,7 @@ $(if $(__ERROR),,\ $(foreach a1,$(call _nth,$(1),1),\ $(foreach a2,$(call _nth,$(1),2),\ $(foreach res,$(call EVAL,$(a2),$(2)),\ - $(if $(call ENV_SET,$(2),_macro_$($(a1)_value),true),,)\ + $(eval _macro_$(res) = true)\ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\ $(if $(call _EQ,macroexpand,$($(a0)_value)),\ $(call MACROEXPAND,$(call _nth,$(1),1),$(2)),\ From b156d1f1459870b64d1d7f84913010591e3364ff Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 24 Oct 2017 09:31:16 -0500 Subject: [PATCH 0553/2308] make, miniMAL: add number?, fn? and macro? --- make/core.mk | 8 +++++--- miniMAL/core.json | 8 ++++++++ miniMAL/miniMAL-core.json | 4 ++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/make/core.mk b/make/core.mk index 1497527ec3..4e6da5b0a5 100644 --- a/make/core.mk +++ b/make/core.mk @@ -76,7 +76,8 @@ subs = $(strip \ # Function functions -function? = $(if $(call _function?,$(1)),$(__true),$(__false)) +fn? = $(if $(call _function?,$(1)),$(if $(_macro_$(1)),$(__false),$(__true)),$(__false)) +macro? = $(if $(_macro_$(1)),$(__true),$(__false)) # List functions @@ -239,7 +240,9 @@ core_ns = type obj_type \ symbol? symbol? \ keyword keyword \ keyword? keyword? \ - function? function? \ + number? number? \ + fn? fn? \ + macro? macro? \ \ pr-str pr_str \ str str \ @@ -249,7 +252,6 @@ core_ns = type obj_type \ read-string read_str \ slurp slurp \ subs subs \ - number? number? \ < number_lt \ <= number_lte \ > number_gt \ diff --git a/miniMAL/core.json b/miniMAL/core.json index 53d599c724..edc39aa7e0 100644 --- a/miniMAL/core.json +++ b/miniMAL/core.json @@ -121,6 +121,14 @@ ["`", "symbol?"], "symbol?", ["`", "keyword"], "keyword", ["`", "keyword?"], "keyword?", + ["`", "number?"], "number?", + ["`", "fn?"], ["fn", ["a"], + ["or", ["function?", "a"], + ["and", ["malfunc?", "a"], + ["not", ["get", "a", ["`", "macro?"]]]]]], + ["`", "macro?"], ["fn", ["a"], + ["and", ["malfunc?", "a"], + ["get", "a", ["`", "macro?"]]]], ["`", "pr-str"], ["fn", ["&", "a"], ["pr-list", "a", true, ["`", " "]]], ["`", "str"], ["fn", ["&", "a"], ["pr-list", "a", false, ["`", ""]]], diff --git a/miniMAL/miniMAL-core.json b/miniMAL/miniMAL-core.json index 632cd2629d..cc3379f0e7 100644 --- a/miniMAL/miniMAL-core.json +++ b/miniMAL/miniMAL-core.json @@ -31,6 +31,10 @@ ["=", ["`", "String"], [".-", [".-", "a", ["`", "constructor"]], ["`", "name"]]]]]], +["def", "number?", ["fn", ["a"], + ["=", ["`", "[object Number]"], ["classOf", "a"]]]], +["def", "function?", ["fn", ["a"], + ["isa", "a", "Function"]]], ["def", "pr-list*", ["fn", ["a", "b", "c"], [".", ["map", ["fn", ["x"], From 69e561c0e02f6098c6317e4fb5e3fe6fd9da8a7d Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 24 Oct 2017 23:01:45 +0100 Subject: [PATCH 0554/2308] Hash map read & print working Implementation is as a list, not a hash. The code is therefore essentially the same as the list read/print routines. --- nasm/printer.asm | 105 +++++++++++++++++++++++++++++- nasm/reader.asm | 163 +++++++++++++++++++++++++++++++++++++++++++++-- nasm/types.asm | 2 +- 3 files changed, 259 insertions(+), 11 deletions(-) diff --git a/nasm/printer.asm b/nasm/printer.asm index 584491d9a2..839284322c 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -104,18 +104,22 @@ pr_str: and ch, container_mask jz .value - cmp ch, 2 + cmp ch, container_list je .list - cmp ch, 4 + cmp ch, container_symbol je .symbol + cmp ch, container_map + je .map + ; Unknown mov rsi, unknown_type_string mov edx, unknown_type_string.len call raw_to_string ; Puts a String in RAX ret + ; -------------------------------- .value: mov ch, cl and ch, content_mask @@ -129,16 +133,20 @@ pr_str: call raw_to_string ; Puts a String in RAX ret + ; -------------------------------- .value_nil: mov rsi, nil_value_string mov edx, nil_value_string.len call raw_to_string ret + ; -------------------------------- .value_int: mov rax, [rsi + Cons.car] call itostring ret + + ; -------------------------------- .list: mov r12, rsi ; Input list @@ -229,6 +237,8 @@ pr_str: mov rax, rsi ret + + ; -------------------------------- .symbol: ; Make a copy of the string call string_new ; in rax @@ -251,8 +261,97 @@ pr_str: add r12, 8 ; Next 64 bits of output jmp .symbol_copy_loop .symbol_finished: + ret + + ; -------------------------------- +.map: + mov r12, rsi ; Input map + call string_new ; String in rax + mov r13, rax ; Output string in r13 - ret + ; Put '{' onto string + mov rsi, rax + mov cl, '{' + call string_append_char + + ; loop through map +.map_loop: + + ; Extract values and print + + mov rsi, r12 + mov cl, BYTE [rsi] ; Get type + + ; Check if it's a pointer (address) + mov ch, cl + and ch, content_mask + cmp ch, content_pointer + je .map_loop_pointer + + cmp ch, content_empty + je .map_check_end + + ; A value (nil, int etc. or function) + xor cl, container_map ; Remove map type -> value + mov BYTE [rsi], cl + + push r13 + push r12 + call pr_str ; String in rax + pop r12 + pop r13 + + mov cl, BYTE [r12] + or cl, container_map ; Restore map type + mov BYTE [r12], cl + jmp .map_loop_got_str +.map_loop_pointer: + mov rsi, [rsi + Cons.car] ; Address of object + push r13 + push r12 + call pr_str ; String in rax + pop r12 + pop r13 + +.map_loop_got_str: + ; concatenate strings in rax and rsi + mov rsi, r13 ; Output string + mov rdx, rax ; String to be copied + + push rsi ; Save output string + push rax ; save temporary string + call string_append_string + + ; Release the string + pop rsi ; Was in rax, temporary string + call release_array + + pop rsi ; restore output string +.map_check_end: + ; Check if this is the end of the map + mov cl, BYTE [r12 + Cons.typecdr] + cmp cl, content_nil + je .map_finished + + ; More left in the map + + ; Add space between values + mov cl, ' ' + mov rsi, r13 + call string_append_char + ; Get next Cons + mov r12, [r12 + Cons.cdr] + jmp .map_loop + +.map_finished: + ; put '}' at the end of the string + mov cl, '}' + mov rsi, r13 + call string_append_char + + mov rax, rsi + ret + diff --git a/nasm/reader.asm b/nasm/reader.asm index f23bb5df8d..6058784043 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -1,5 +1,7 @@ section .data +;; Reader macro strings + quote_symbol_string: db "quote" .len: equ $ - quote_symbol_string @@ -12,8 +14,16 @@ unquote_symbol_string: db "unquote" splice_unquote_symbol_string: db "splice-unquote" .len: equ $ - splice_unquote_symbol_string +deref_symbol_string: db "deref" +.len: equ $ - deref_symbol_string + +;; Error message strings + error_string_unexpected_end: db "Error: Unexpected end of input. Could be a missing )", 10 .len: equ $ - error_string_unexpected_end + +error_string_bracket_not_brace: db "Error: Expecting '}' but got ')'" +.len: equ $ - error_string_bracket_not_brace section .text @@ -64,14 +74,9 @@ read_str: jne .got_token ; Unexpected end of tokens - push r14 - push r15 mov rdx, error_string_unexpected_end.len mov rsi, error_string_unexpected_end - call print_rawstring - pop r15 - pop r14 - jmp .unwind + jmp .error .got_token: @@ -84,10 +89,16 @@ read_str: cmp cl, '(' je .list_start - + cmp cl, ')' je .return_nil ; Note: if reading a list, cl will be tested in the list reader + cmp cl, '{' + je .map_start + + cmp cl, '}' ; cl tested in map reader + je .return_nil + cmp cl, 39 ; quote ' je .handle_quote cmp cl, '`' @@ -96,6 +107,8 @@ read_str: je .handle_unquote cmp cl, 1 je .handle_splice_unquote + cmp cl, '@' + je .handle_deref ; Unknown jmp .return_nil @@ -207,6 +220,113 @@ read_str: ret + ; -------------------------------- + +.map_start: + + ; Get the first value + ; Note that we call rather than jmp because the first + ; value needs to be treated differently. There's nothing + ; to append to yet... + call .read_loop + + ; rax now contains the first object + cmp cl, '}' ; Check if it was end of map + jne .map_has_contents + mov cl, 0 ; so '}' doesn't propagate to nested maps + ; Set map to empty + mov [rax], BYTE maltype_empty_map + ret ; Returns 'nil' given "()" +.map_has_contents: + ; If this is a Cons then use it + ; If not, then need to allocate a Cons + mov cl, BYTE [rax] + mov ch, cl + and ch, (block_mask + container_mask) ; Tests block and container type + jz .map_is_value + + ; If here then not a simple value, so need to allocate + ; a Cons object + + ; Start new map + push rax + call alloc_cons ; Address in rax + pop rbx + mov [rax], BYTE (block_cons + container_map + content_pointer) + mov [rax + Cons.car], rbx + ; Now have Cons in RAX, containing pointer to object as car + +.map_is_value: + ; Cons in RAX + ; Make sure it's marked as a map + mov cl, BYTE [rax] + or cl, container_map + mov [rax], BYTE cl + + mov r12, rax ; Start of current map + mov r13, rax ; Set current map + cmp r15, 0 ; Test if first map + jne .map_read_loop + mov r15, rax ; Save the first, for unwinding + +.map_read_loop: + ; Repeatedly get the next value in the map + ; (which may be other maps) + ; until we get a '}' token + + push r12 + push r13 + call .read_loop ; object in rax + pop r13 + pop r12 + + cmp cl, '}' ; Check if it was end of map + je .map_done ; Have nil object in rax + + ; Test if this is a Cons value + mov cl, BYTE [rax] + mov ch, cl + and ch, (block_mask + container_mask) ; Tests block and container type + jz .map_loop_is_value + + ; If here then not a simple value, so need to allocate + ; a Cons object + + ; Start new map + push rax + call alloc_cons ; Address in rax + pop rbx + mov [rax], BYTE (block_cons + container_map + content_pointer) + mov [rax + Cons.car], rbx + ; Now have Cons in RAX, containing pointer to object as car + +.map_loop_is_value: + ; Cons in RAX + + ; Make sure it's marked as a map + mov cl, BYTE [rax] + or cl, container_map + mov [rax], BYTE cl + + ; Append to r13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax ; Set current map + + jmp .map_read_loop + +.map_done: + ; Release nil object in rax + mov rsi, rax + call release_cons + + ; Terminate the map + mov [r13 + Cons.typecdr], BYTE content_nil + mov QWORD [r13 + Cons.cdr], QWORD 0 + mov rax, r12 ; Start of current map + + ret + ; -------------------------------- .handle_quote: ; Turn 'a into (quote a) @@ -293,11 +413,40 @@ read_str: pop r9 pop r8 jmp .wrap_next_object ; From there the same as handle_quote + + ; -------------------------------- + +.handle_deref: + ; Turn @a into (deref a) + + call alloc_cons ; Address in rax + mov r12, rax + + ; Get a symbol "deref" + push r8 + push r9 + mov rsi, deref_symbol_string + mov edx, deref_symbol_string.len + call raw_to_string ; Address in rax + pop r9 + pop r8 + jmp .wrap_next_object ; From there the same as handle_quote + ; -------------------------------- .finished: ret +.error: + ; Jump here on error with raw string in RSI + ; and string length in rdx + push r14 + push r15 + call print_rawstring + pop r15 + pop r14 + + ; fall through to unwind .unwind: ; Jump to here cleans up diff --git a/nasm/types.asm b/nasm/types.asm index e40443d687..ddc6d4952a 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -116,7 +116,7 @@ ENDSTRUC %define maltype_symbol (block_array + container_symbol + content_char) %define maltype_nil (block_cons + container_value + content_nil) %define maltype_empty_list (block_cons + container_list + content_empty) - +%define maltype_empty_map (block_cons + container_map + content_empty) ;; ------------------------------------------ From 0c7d765c12c384c1cbacc56a8678962c788948ef Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 25 Oct 2017 00:16:06 +0100 Subject: [PATCH 0555/2308] map_find finds values in a Map type Works in simple tests, returns address of position in the map, and sets Zero Flag if value is found. This function will be used in both set and get methods --- nasm/types.asm | 95 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/nasm/types.asm b/nasm/types.asm index ddc6d4952a..3deaccc967 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -687,7 +687,8 @@ compare_get_value: mov rax, rsi ret -;; Compare two objects. Note that this does not compare lists +;; Compare two objects in RSI and RDI. +;; Note that this does not compare lists ;; but will just compare the first element ;; ;; Modifies registers @@ -916,3 +917,95 @@ map_add: mov [rax], BYTE maltype_nil mov [rax + Cons.typecdr], BYTE content_nil ret + +;; Find a key in a map +;; +;; Inputs: RSI - map +;; RDI - key +;; +;; Outputs: RAX - Cons object containing value in CAR +;; +;; If value is found then the Zero Flag is set +;; +;; Examples: +;; {a 1 b 2} find a -> {1 b 2} +;; {1 2 3 4} find a -> {4} +map_find: + mov al, BYTE [rsi] + cmp al, maltype_empty_map + je .not_found + +.map_loop: + ; compare RSI and RDI, ignoring differences in container + push rsi + push rdi + call compare_objects + pop rdi + pop rsi + + ; rax is now zero if objects are equal + cmp rax, 0 + je .found + + ; Move along two cons to the next key + mov al, [rsi + Cons.typecdr] + cmp al, content_pointer + jne .error ; Expecting value after key + + mov rsi, [rsi + Cons.cdr] ; Get value + mov al, [rsi + Cons.typecdr] + cmp al, content_pointer + jne .not_found + + mov rsi, [rsi + Cons.cdr] ; Get next key + + jmp .map_loop ; Test next key + +.found: + + lahf ; flags in AH + or ah, 64 ; set zero flag + sahf + + ; key in rsi. Get next value + mov al, [rsi + Cons.typecdr] + cmp al, content_pointer + jne .error ; Expecting value after key + + mov rsi, [rsi + Cons.cdr] + + ; increment reference count + mov ax, WORD [rsi + Cons.refcount] + inc ax + mov [rsi + Cons.refcount], WORD ax + ; Put address in rax + mov rax, rsi + ret + +.not_found: + lahf ; flags in AH + and ah, 255-64 ; remove zero flag + sahf + + ; last cons in rsi + ; increment reference count + mov ax, WORD [rsi + Cons.refcount] + inc ax + mov [rsi + Cons.refcount], WORD ax + ; Put address in rax + mov rax, rsi + + ret + +.error: + + lahf ; flags in AH + or ah, 255-64 ; set zero flag + sahf + + ; return nil + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret + From 7d4d355ecbcd4ce054d83172e3341f82231271cb Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Wed, 25 Oct 2017 20:56:46 +0000 Subject: [PATCH 0556/2308] factor: Add number?, fn?, macro? --- factor/lib/core/core.factor | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/factor/lib/core/core.factor b/factor/lib/core/core.factor index 906be6553b..b51458b8a8 100644 --- a/factor/lib/core/core.factor +++ b/factor/lib/core/core.factor @@ -3,7 +3,7 @@ USING: accessors arrays assocs combinators combinators.short-circuit fry grouping hash-sets hashtables io io.encodings.utf8 io.files kernel lists lib.env lib.printer -lib.reader lib.types math namespaces readline sequences sets +lib.reader lib.types math namespaces quotations readline sequences sets strings system vectors ; IN: lib.core @@ -50,6 +50,9 @@ CONSTANT: ns H{ { "string?" [ first string? ] } { "keyword" [ first ] } { "keyword?" [ first malkeyword? ] } + { "number?" [ first number? ] } + { "fn?" [ first { [ callable? ] [ { [ malfn? ] [ macro?>> not ] } 1&& ] } 1|| ] } + { "macro?" [ first { [ malfn? ] [ macro?>> ] } 1&& ] } { "vector" [ >vector ] } { "vector?" [ first vector? ] } { "hash-map" [ 2 group parse-hashtable ] } From 2992dc7151a5981d1a88e8329d1e4f22ac641850 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 25 Oct 2017 22:48:05 +0100 Subject: [PATCH 0557/2308] Added map_set function Replaces a value if already in the map, or adds the key-value pair. --- nasm/types.asm | 156 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 3 deletions(-) diff --git a/nasm/types.asm b/nasm/types.asm index 3deaccc967..641beb1e4e 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -39,6 +39,7 @@ ;; 80 5 - Pointer (memory address) ;; 96 6 - Function (instruction address) ;; 112 7 - Empty (distinct from Nil) +;; ;; ;; These represent MAL data types as follows: ;; @@ -804,7 +805,10 @@ compare_char_array: ;; ------------------------------------------------------------ ;; Map type - +;; +;; This uses a list (Cons type) to represent key-value pairs in +;; a single chain. The only map which consists of an odd number of Cons +;; objects is the empty map, created by map_new map_new: call alloc_cons mov [rax], BYTE (block_cons + container_map + content_empty) @@ -920,11 +924,16 @@ map_add: ;; Find a key in a map ;; -;; Inputs: RSI - map -;; RDI - key +;; Inputs: RSI - map [ Modified ] +;; RDI - key [ Modified ] ;; ;; Outputs: RAX - Cons object containing value in CAR ;; +;; Modifies registers: +;; RBX [compare_objects, alloc_cons] +;; RCX [compare_objects] +;; +;; ;; If value is found then the Zero Flag is set ;; ;; Examples: @@ -1008,4 +1017,145 @@ map_find: mov [rax], BYTE maltype_nil mov [rax + Cons.typecdr], BYTE content_nil ret + +;; Map set +;; +;; Sets a key-value pair in a map +;; +;; Inputs: RSI - map [modified] +;; RDI - key [modified] +;; RCX - value +;; +map_set: + ; Save inputs in less volatile registers + mov r8, rsi ; map + mov r9, rdi ; key + mov r10, rcx ; value + + ; Find the key, to see if it already exists in the map + call map_find ; Cons object in RAX + je .found_key + + ; Key not in map. RAX should be address of the last + ; value in the map, or empty + mov rbx, r8 + cmp bl, maltype_empty_map + je .set_key + + ; Append key + push rax + call alloc_cons ; New Cons in rax + pop rbx ; Last Cons in map + + ; append rax to rbx + mov [rbx + Cons.typecdr], BYTE content_pointer + mov [rbx + Cons.cdr], rax + jmp .set_key ; Put key into rax + +.found_key: + ; Key already in map, so replace value + ; address in RAX + + ; check type of value already there + mov bl, BYTE [rax] + and bl, content_mask + cmp bl, content_pointer + jne .set_value ; Not a pointer, just overwrite + + ; A pointer, so need to release + mov rsi, [rax + Cons.car] ; Address of object + push rax + call release_object + pop rax + + jmp .set_value ; put value into Cons + +.set_key: + ; Put key (R9) in RAX + + ; Check the type of object + mov bl, BYTE [r9] + mov bh, bl + and bh, block_mask + jnz .set_key_pointer ; Array, so point to it + + ; Here a Cons object + mov bh, bl + and bh, container_mask + cmp bl, container_value + jne .set_key_pointer ; Not a simple value, so point to it + ; A value, so copy + mov rcx, [r9 + Cons.car] + mov [rax + Cons.car], rcx + + ; Set the type + and bl, content_mask + or bl, (block_cons + container_map) + mov [rax], BYTE bl + + jmp .set_key_done + +.set_key_pointer: + ; The key is a pointer + + mov [rax + Cons.car], r9 + mov [rax], BYTE (block_cons + container_map + content_pointer) + ; Increment reference count + mov bx, WORD [r9 + Cons.refcount] + inc bx + mov [r9 + Cons.refcount], bx + ; fall through to .set_key_done + +.set_key_done: + ; Key in RAX. allocate and append a Cons for the value + push rax + call alloc_cons ; value Cons in rax + pop rbx ; key Cons + ; append rax to rbx + mov [rbx + Cons.typecdr], BYTE content_pointer + mov [rbx + Cons.cdr], rax + + ; fall through to .set_value + + ; -------------------------------- +.set_value: + ; Set the value into the Cons at [rax] + + ; Check the type of object + mov bl, BYTE [r10] + mov bh, bl + and bh, block_mask + jnz .set_value_pointer ; Array, so point to it + + ; Here a Cons object + mov bh, bl + and bh, container_mask + cmp bl, container_value + jne .set_value_pointer ; Not a simple value, so point to it + ; A value, so copy + mov rcx, [r10 + Cons.car] + mov [rax + Cons.car], rcx + + ; Set the type + and bl, content_mask + or bl, (block_cons + container_map) + mov [rax], BYTE bl + + jmp .finished + +.set_value_pointer: + mov [rax + Cons.car], r10 ; Put address into CAR + mov [rax], BYTE (block_cons + container_map + content_pointer) ; Mark as a pointer + ; Increment reference count + mov bx, WORD [r10 + Cons.refcount] + inc bx + mov [r10 + Cons.refcount], bx + ; fall through to .finished + +.finished: + ; Put the map into rax + mov rax, r8 + ret + + From 3d1dbb204e9a6d8938736facf8cb3b175b353456 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 25 Oct 2017 22:38:09 -0500 Subject: [PATCH 0558/2308] Dart, Groovy, Haskell: add number?, fn? and macro? --- dart/core.dart | 9 +++++++++ groovy/core.groovy | 5 +++++ haskell/Core.hs | 3 +++ haskell/Types.hs | 15 +++++++++++++-- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/dart/core.dart b/dart/core.dart index 9213a8034b..01462e603d 100644 --- a/dart/core.dart +++ b/dart/core.dart @@ -169,6 +169,15 @@ Map ns = { new MalSymbol('keyword?'): new MalBuiltin((List args) { return new MalBool(args.first is MalKeyword); }), + new MalSymbol('number?'): new MalBuiltin((List args) { + return new MalBool(args.first is MalInt); + }), + new MalSymbol('fn?'): new MalBuiltin((List args) { + return new MalBool(args.first is MalCallable && !(args.first.isMacro)); + }), + new MalSymbol('macro?'): new MalBuiltin((List args) { + return new MalBool(args.first is MalCallable && args.first.isMacro); + }), new MalSymbol('vector'): new MalBuiltin((List args) { return new MalVector(args); }), diff --git a/groovy/core.groovy b/groovy/core.groovy index 712cc08844..7291de8e56 100644 --- a/groovy/core.groovy +++ b/groovy/core.groovy @@ -1,6 +1,7 @@ import types import types.MalException import types.MalSymbol +import types.MalFunc import reader import printer @@ -72,6 +73,10 @@ class core { "symbol?": { a -> a[0] instanceof MalSymbol }, "keyword": { a -> types.keyword(a[0]) }, "keyword?": { a -> types.keyword_Q(a[0]) }, + "number?": { a -> a[0] instanceof Integer }, + "fn?": { a -> (a[0] instanceof MalFunc && !a[0].ismacro) || + a[0] instanceof Closure }, + "macro?": { a -> a[0] instanceof MalFunc && a[0].ismacro }, "pr-str": core.&do_pr_str, "str": core.&do_str, diff --git a/haskell/Core.hs b/haskell/Core.hs index 99a2bfa30f..995e4dc407 100644 --- a/haskell/Core.hs +++ b/haskell/Core.hs @@ -257,6 +257,9 @@ ns = [ ("symbol?", _func $ run_1 $ _symbol_Q), ("keyword", _func $ keyword), ("keyword?", _func $ run_1 $ _keyword_Q), + ("number?", _func $ run_1 $ _number_Q), + ("fn?", _func $ run_1 $ _fn_Q), + ("macro?", _func $ run_1 $ _macro_Q), ("pr-str", _func pr_str), ("str", _func str), diff --git a/haskell/Types.hs b/haskell/Types.hs index fc26ddb32c..8cf413cdf9 100644 --- a/haskell/Types.hs +++ b/haskell/Types.hs @@ -1,8 +1,8 @@ module Types (MalVal (..), MalError (..), IOThrows (..), Fn (..), EnvData (..), Env, throwStr, throwMalVal, _get_call, _to_list, - _func, _malfunc, - _nil_Q, _true_Q, _false_Q, _string_Q, _symbol_Q, _keyword_Q, + _func, _malfunc, _fn_Q, _macro_Q, + _nil_Q, _true_Q, _false_Q, _string_Q, _symbol_Q, _keyword_Q, _number_Q, _list_Q, _vector_Q, _hash_map_Q, _atom_Q) where @@ -98,6 +98,14 @@ _malfunc_meta ast env params fn meta = MalFunc {fn=(Fn fn), ast=ast, env=env, params=params, macro=False, meta=meta} +_fn_Q (MalFunc {macro=False}) = MalTrue +_fn_Q (Func _ _) = MalTrue +_fn_Q _ = MalFalse + +_macro_Q (MalFunc {macro=True}) = MalTrue +_macro_Q _ = MalFalse + + -- Scalars _nil_Q Nil = MalTrue _nil_Q _ = MalFalse @@ -118,6 +126,9 @@ _string_Q _ = MalFalse _keyword_Q (MalString ('\x029e':_)) = MalTrue _keyword_Q _ = MalFalse +_number_Q (MalNumber _) = MalTrue +_number_Q _ = MalFalse + -- Lists _list_Q (MalList _ _) = MalTrue From 0218385f8b380408cced674ea520dd9d363094cb Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 25 Oct 2017 22:38:24 -0500 Subject: [PATCH 0559/2308] Haxe, Matlab: add number?, fn? and macro? --- haxe/core/Core.hx | 3 +++ haxe/types/Types.hx | 15 +++++++++++++++ matlab/core.m | 3 +++ matlab/type_utils.m | 13 +++++++++++++ 4 files changed, 34 insertions(+) diff --git a/haxe/core/Core.hx b/haxe/core/Core.hx index a990af97bd..d7b82d3b57 100644 --- a/haxe/core/Core.hx +++ b/haxe/core/Core.hx @@ -328,6 +328,9 @@ class Core { "symbol?" => function(a) { return BoolFn(symbol_Q(a[0])); }, "keyword" => keyword, "keyword?" => function(a) { return BoolFn(keyword_Q(a[0])); }, + "number?" => function(a) { return BoolFn(number_Q(a[0])); }, + "fn?" => function(a) { return BoolFn(_fn_Q(a[0])); }, + "macro?" => function(a) { return BoolFn(_macro_Q(a[0])); }, "pr-str" => pr_str, "str" => str, diff --git a/haxe/types/Types.hx b/haxe/types/Types.hx index 5c25cce416..669e29b466 100644 --- a/haxe/types/Types.hx +++ b/haxe/types/Types.hx @@ -73,6 +73,13 @@ class Types { } } + public static function _fn_Q(x:MalType) { + return switch (x) { + case MalFunc(_,_,_,_,ismacro,_): !ismacro; + case _: false; + } + } + public static function _macro_Q(x:MalType) { return switch (x) { case MalFunc(_,_,_,_,ismacro,_): ismacro; @@ -124,6 +131,14 @@ class Types { } } + public static function number_Q(x:MalType) { + return switch (x) { + case MalInt(_): true; + case _: false; + } + } + + // Sequence operations public static function list_Q(x:MalType) { return switch (x) { diff --git a/matlab/core.m b/matlab/core.m index 4354f3a218..fe455d9491 100644 --- a/matlab/core.m +++ b/matlab/core.m @@ -230,6 +230,9 @@ n('symbol?') = @(a) isa(a, 'types.Symbol'); n('keyword') = @(a) type_utils.keyword(a); n('keyword?') = @(a) type_utils.keyword_Q(a); + n('number?') = @(a) type_utils.number_Q(a); + n('fn?') = @(a) type_utils.fn_Q(a); + n('macro?') = @(a) type_utils.macro_Q(a); n('pr-str') = @(varargin) core.pr_str(varargin{:}); n('str') = @(varargin) core.do_str(varargin{:}); diff --git a/matlab/type_utils.m b/matlab/type_utils.m index 8a4ce210ad..939df5260e 100644 --- a/matlab/type_utils.m +++ b/matlab/type_utils.m @@ -74,6 +74,19 @@ ret = strcmp(class(obj), 'char') && ~type_utils.keyword_Q(obj); end + function ret = number_Q(obj) + ret = strcmp(class(obj), 'double'); + end + + function ret = fn_Q(obj) + ret = isa(obj,'function_handle') || ... + (isa(obj,'types.Function') && ~obj.is_macro); + end + + function ret = macro_Q(obj) + ret = isa(obj,'types.Function') && obj.is_macro; + end + function print_stack(err) for i=1:numel(err.stack) stack = err.stack(i); From 9d7af55272282bad7e86707eb0dc85f23a0a26ab Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Thu, 26 Oct 2017 08:47:31 +0000 Subject: [PATCH 0560/2308] elixir, erlang, julia, kotlin: Add number?, fn?, macro? --- elixir/lib/mal/core.ex | 9 +++++++++ erlang/src/core.erl | 26 ++++++++++++++++++++++++++ julia/core.jl | 3 +++ kotlin/src/mal/core.kt | 4 ++++ 4 files changed, 42 insertions(+) diff --git a/elixir/lib/mal/core.ex b/elixir/lib/mal/core.ex index 6bb67d202f..ca651a4b79 100644 --- a/elixir/lib/mal/core.ex +++ b/elixir/lib/mal/core.ex @@ -47,6 +47,8 @@ defmodule Mal.Core do "swap!" => &swap!/1, "conj" => &conj/1, "seq" => &seq/1, + "fn?" => &fn?/1, + "macro?" => ¯o?/1, "time-ms" => fn _ -> :erlang.system_time(:milli_seconds) end, "readline" => fn [prompt] -> readline(prompt) end, "sequential?" => fn arg -> vector?(arg) or list?(arg) end, @@ -55,6 +57,7 @@ defmodule Mal.Core do "true?" => fn [type] -> type == true end, "false?" => fn [type] -> type == false end, "string?" => fn [obj] -> String.valid?(obj) end, + "number?" => fn [obj] -> is_number(obj) end, "symbol" => fn [name] -> {:symbol, name} end, "read-string" => fn [input] -> Mal.Reader.read_str(input) end, "throw" => fn [arg] -> throw({:error, arg}) end, @@ -230,4 +233,10 @@ defmodule Mal.Core do defp seq([""]), do: nil defp seq([s]), do: {:list, String.split(s, "", trim: true), nil} defp seq(_), do: nil + + defp fn?([%Function{macro: false}]), do: true + defp fn?(_), do: false + + defp macro?([%Function{macro: true}]), do: true + defp macro?(_), do: false end diff --git a/erlang/src/core.erl b/erlang/src/core.erl index 154ddb1045..9aa2ba7578 100644 --- a/erlang/src/core.erl +++ b/erlang/src/core.erl @@ -20,6 +20,29 @@ false_p([Arg]) -> false_p(_) -> {error, "false? takes a single argument"}. +number_p([{integer, _}]) -> + true; +number_p([_]) -> + false; +number_p(_) -> + {error, "number? takes a single argument"}. + +fn_p([{function, _, _}]) -> + true; +fn_p([{closure, _, _, _, _, _}]) -> + true; +fn_p([_]) -> + false; +fn_p(_) -> + {error, "fn? takes a single argument"}. + +macro_p([{macro, _, _, _}]) -> + true; +macro_p([_]) -> + false; +macro_p(_) -> + {error, "macro? takes a single argument"}. + count([{Type, List, _Meta}]) when Type == list orelse Type == vector -> {integer, length(List)}; count([nil]) -> @@ -324,6 +347,7 @@ ns() -> "empty?" => fun empty_q/1, "false?" => fun false_p/1, "first" => fun first/1, + "fn?" => fun fn_p/1, "get" => fun types:map_get/1, "hash-map" => fun types:hash_map/1, "keys" => fun types:map_keys/1, @@ -331,11 +355,13 @@ ns() -> "keyword?" => fun types:keyword_p/1, "list" => fun types:list/1, "list?" => fun types:list_p/1, + "macro?" => fun macro_p/1, "map" => fun map_f/1, "map?" => fun types:map_p/1, "meta" => fun types:meta/1, "nil?" => fun nil_p/1, "nth" => fun nth/1, + "number?" => fun number_p/1, "pr-str" => fun pr_str/1, "println" => fun println/1, "prn" => fun prn/1, diff --git a/julia/core.jl b/julia/core.jl index 93057bcc47..680d25fd7c 100644 --- a/julia/core.jl +++ b/julia/core.jl @@ -79,6 +79,9 @@ ns = Dict{Any,Any}( symbol("symbol?") => (a) -> typeof(a) === Symbol, symbol("keyword") => (a) -> a[1] == '\u029e' ? a : "\u029e$(a)", symbol("keyword?") => keyword_Q, + symbol("number?") => (a) -> isa(a, AbstractFloat) || isa(a, Int64), + symbol("fn?") => (a) -> isa(a, Function) || (isa(a, types.MalFunc) && !a.ismacro), + symbol("macro?") => (a) -> isa(a, types.MalFunc) && a.ismacro, symbol("pr-str") => (a...) -> join(map((e)->pr_str(e, true),a)," "), :str => (a...) -> join(map((e)->pr_str(e, false),a),""), diff --git a/kotlin/src/mal/core.kt b/kotlin/src/mal/core.kt index addacd8d06..bc312c8b98 100644 --- a/kotlin/src/mal/core.kt +++ b/kotlin/src/mal/core.kt @@ -117,6 +117,10 @@ val ns = hashMapOf( if (param is MalKeyword) param else MalKeyword((a.nth(0) as MalString).value) }), envPair("keyword?", { a: ISeq -> if (a.nth(0) is MalKeyword) TRUE else FALSE }), + envPair("number?", { a: ISeq -> if (a.nth(0) is MalInteger) TRUE else FALSE }), + envPair("fn?", { a: ISeq -> if ((a.nth(0) as? MalFunction)?.is_macro ?: true) FALSE else TRUE }), + envPair("macro?", { a: ISeq -> if ((a.nth(0) as? MalFunction)?.is_macro ?: false) TRUE else FALSE }), + envPair("vector", { a: ISeq -> MalVector(a) }), envPair("vector?", { a: ISeq -> if (a.nth(0) is MalVector) TRUE else FALSE }), From 72311fee8a37838382dbee26f57be29fed5269f1 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 26 Oct 2017 23:36:44 +0100 Subject: [PATCH 0561/2308] Added map_get, and Environment type set/get Environments implemented as a list of maps, with each pointing to its outer environment. core_environment function (in core.asm) creates an Environment, and adds the core +,-,*,/ functions. --- nasm/core.asm | 93 ++++++++++++++++++ nasm/printer.asm | 16 +++- nasm/step2_eval.asm | 13 ++- nasm/types.asm | 225 +++++++++++++++++++++++++++++++++++++++----- 4 files changed, 318 insertions(+), 29 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 13b9436387..404ef93e33 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -2,8 +2,101 @@ ;; ;; +section .data + +core_add_symbol: db "+" +.len: equ $ - core_add_symbol + +core_sub_symbol: db "-" +.len: equ $ - core_sub_symbol + +core_mul_symbol: db "*" +.len: equ $ - core_mul_symbol + +core_div_symbol: db "/" +.len: equ $ - core_div_symbol + section .text +;; Create an Environment with core functions +;; +;; Returns Environment in RAX +;; +;; +core_environment: + ; Create the top-level environment + call env_new ; in RAX + push rax + + ; ----------------- + ; add + mov rsi, core_add_symbol + mov edx, core_add_symbol.len + call raw_to_symbol ; Symbol in RAX + push rax + + mov rsi, core_add + call native_function ; Function in RAX + + mov rcx, rax ; value (function) + pop rdi ; key (symbol) + pop rsi ; environment + call env_set + + ; ----------------- + ; sub + push rsi ; environment + mov rsi, core_sub_symbol + mov edx, core_sub_symbol.len + call raw_to_symbol ; Symbol in RAX + push rax + + mov rsi, core_sub + call native_function ; Function in RAX + + mov rcx, rax ; value (function) + pop rdi ; key (symbol) + pop rsi ; environment + call env_set + + + ; ----------------- + ; mul + push rsi ; environment + mov rsi, core_mul_symbol + mov edx, core_mul_symbol.len + call raw_to_symbol ; Symbol in RAX + push rax + + mov rsi, core_mul + call native_function ; Function in RAX + + mov rcx, rax ; value (function) + pop rdi ; key (symbol) + pop rsi ; environment + call env_set + + ; ----------------- + ; div + push rsi ; environment + mov rsi, core_div_symbol + mov edx, core_div_symbol.len + call raw_to_symbol ; Symbol in RAX + push rax + + mov rsi, core_div + call native_function ; Function in RAX + + mov rcx, rax ; value (function) + pop rdi ; key (symbol) + pop rsi ; environment + call env_set + + ; ----------------- + ; Put the environment in RAX + mov rax, rsi + ret + ;; Adds a list of numbers, address in RSI ;; Returns the sum as a number object with address in RAX ;; Since most of the code is common to all operators, diff --git a/nasm/printer.asm b/nasm/printer.asm index 839284322c..e7348eff25 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -5,10 +5,13 @@ section .data unknown_type_string: db "#" .len: equ $ - unknown_type_string - + unknown_value_string: db "#" .len: equ $ - unknown_value_string +function_type_string: db "#" +.len: equ $ - function_type_string + nil_value_string: db "nil" .len: equ $ - nil_value_string @@ -112,6 +115,9 @@ pr_str: cmp ch, container_map je .map + + cmp ch, container_function + je .function ; Unknown mov rsi, unknown_type_string @@ -354,4 +360,10 @@ pr_str: mov rax, rsi ret - + + ; -------------------------------- +.function: + mov rsi, function_type_string + mov edx, function_type_string.len + call raw_to_string ; Puts a String in RAX + ret diff --git a/nasm/step2_eval.asm b/nasm/step2_eval.asm index 1bd9524d76..6e8f72353f 100644 --- a/nasm/step2_eval.asm +++ b/nasm/step2_eval.asm @@ -70,7 +70,15 @@ rep_seq: _start: + ; Create and print the core environment + call core_environment ; Environment in RAX + + mov rsi, rax + call pr_str + mov rsi, rax ; Put into input of print_string + call print_string + ; ----------------------------- ; Main loop @@ -93,12 +101,9 @@ _start: call read_str push rax - ; Compare objects - mov rsi, rax - call core_equal_p ; Put into pr_str - mov rsi, rax + mov rsi, rax call pr_str push rax diff --git a/nasm/types.asm b/nasm/types.asm index 641beb1e4e..1a2536978e 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -145,7 +145,7 @@ error_cons_memory_limit: db "Error: Run out of memory for Cons objects. Increase ;; is free'd it is pushed onto the heap_x_free list. -%define heap_cons_limit 10 ; Number of cons objects which can be created +%define heap_cons_limit 20 ; Number of cons objects which can be created heap_cons_next: dd heap_cons_store ; Address of next cons in memory heap_cons_free: dq 0 ; Address of start of free list @@ -403,8 +403,18 @@ raw_to_string: dec ecx jnz .copy_loop ret - - + +;; Convert a raw string to a symbol +;; +;; Input: Address of raw string in RSI, length in EDX +;; Output: Address of string in RAX +;; +;; Modifies registers: R8,R9,RCX +raw_to_symbol: + call raw_to_string + ; set the content type + mov [rax], BYTE (block_array + container_symbol + content_char) + ret ;; Appends a character to a string ;; Input: Address of string in RSI, character in CL @@ -983,24 +993,24 @@ map_find: mov rsi, [rsi + Cons.cdr] - ; increment reference count - mov ax, WORD [rsi + Cons.refcount] - inc ax - mov [rsi + Cons.refcount], WORD ax + ; ; increment reference count + ; mov ax, WORD [rsi + Cons.refcount] + ; inc ax + ; mov [rsi + Cons.refcount], WORD ax ; Put address in rax mov rax, rsi ret .not_found: lahf ; flags in AH - and ah, 255-64 ; remove zero flag + and ah, 255-64 ; clear zero flag sahf ; last cons in rsi ; increment reference count - mov ax, WORD [rsi + Cons.refcount] - inc ax - mov [rsi + Cons.refcount], WORD ax + ; mov ax, WORD [rsi + Cons.refcount] + ; inc ax + ; mov [rsi + Cons.refcount], WORD ax ; Put address in rax mov rax, rsi @@ -1009,7 +1019,7 @@ map_find: .error: lahf ; flags in AH - or ah, 255-64 ; set zero flag + and ah, 255-64 ; clear zero flag sahf ; return nil @@ -1022,9 +1032,9 @@ map_find: ;; ;; Sets a key-value pair in a map ;; -;; Inputs: RSI - map [modified] -;; RDI - key [modified] -;; RCX - value +;; Inputs: RSI - map [not modified] +;; RDI - key [not modified] +;; RCX - value [not modified] ;; map_set: ; Save inputs in less volatile registers @@ -1038,10 +1048,10 @@ map_set: ; Key not in map. RAX should be address of the last ; value in the map, or empty - mov rbx, r8 + mov bl, BYTE [rax] cmp bl, maltype_empty_map je .set_key - + ; Append key push rax call alloc_cons ; New Cons in rax @@ -1055,7 +1065,7 @@ map_set: .found_key: ; Key already in map, so replace value ; address in RAX - + ; check type of value already there mov bl, BYTE [rax] and bl, content_mask @@ -1078,7 +1088,7 @@ map_set: mov bh, bl and bh, block_mask jnz .set_key_pointer ; Array, so point to it - + ; Here a Cons object mov bh, bl and bh, container_mask @@ -1097,7 +1107,7 @@ map_set: .set_key_pointer: ; The key is a pointer - + mov [rax + Cons.car], r9 mov [rax], BYTE (block_cons + container_map + content_pointer) ; Increment reference count @@ -1120,7 +1130,7 @@ map_set: ; -------------------------------- .set_value: ; Set the value into the Cons at [rax] - + ; Check the type of object mov bl, BYTE [r10] mov bh, bl @@ -1153,9 +1163,178 @@ map_set: ; fall through to .finished .finished: - ; Put the map into rax - mov rax, r8 + ; Restore inputs + mov rsi, r8 + mov rdi, r9 + mov rcx, r10 + ret + +;; Get a value from a map, incrementing the reference count +;; of the object returned +;; +;; Inputs: RSI - map +;; RDI - key +;; +;; Returns: If found, Zero Flag is set and address in RAX +;; If not found, Zero Flag cleared +;; +;; Modifies registers: +;; RAX +;; RBX +;; RCX +;; R8 +;; R9 +map_get: + ; Save inputs + mov r8, rsi ; map + mov r9, rdi ; key + + call map_find ; Cons object in RAX + je .found_key + + ; Not found + + mov rsi, r8 + mov rdi, r9 + + lahf ; flags in AH + and ah, 255-64 ; clear zero flag + sahf + + ret + ; --------------- +.found_key: + ; Check if the object in RAX is a value or pointer + mov bl, BYTE [rax] + and bl, content_mask + cmp bl, content_pointer + je .got_pointer + + ; A value, so copy + + push rax + push rbx + call alloc_cons ; cons in rax + pop rbx ; content type in bl + pop rcx ; Object to copy + + add bl, block_cons + container_value + mov [rax], BYTE bl ; set type + mov [rax + Cons.typecdr], BYTE content_nil + + ; Copy value + mov rbx, [rcx + Cons.car] + mov [rax + Cons.car], rbx + + jmp .finished_found + +.got_pointer: + ; A pointer, so get the address + mov rax, [rax + Cons.car] + + ; increment reference count + mov bx, WORD [rax + Cons.refcount] + inc bx + mov [rax + Cons.refcount], bx + + ; Fall through to .finished_found +.finished_found: + mov rsi, r8 + mov rdi, r9 + + ret + +;; ------------------------------------------------------------ +;; Environment type +;; +;; These are lists of maps. The head of the list is the +;; current environment, and CDR points to the outer environment +;; +;; ( {} {} ... ) + +;; Return a new Environment type in RAX +;; +;; Modifies registers: +;; RAX +;; RBX +env_new: + call map_new ; map in RAX + push rax + call alloc_cons ; Cons in RAX + pop rbx ; map in RBX + + mov [rax], BYTE (block_cons + container_list + content_pointer) + ; CDR type already set to nil in alloc_cons + mov [rax + Cons.car], rbx + + ret + +;; Environment set +;; +;; Sets a key-value pair in an environment +;; +;; Inputs: RSI - env [not modified] +;; RDI - key [not modified] +;; RCX - value [not modified] +;; +env_set: + push rsi + ; Get the first CAR, which should be a map + mov rsi, [rsi + Cons.car] + call map_set + pop rsi ret +;; Environment get +;; +;; Get a value from an environment, incrementing the reference count +;; of the object returned +;; +;; Inputs: RSI - environment +;; RDI - key +;; +;; Returns: If found, Zero Flag is set and address in RAX +;; If not found, Zero Flag cleared +env_get: + push rsi + ; Get the map in CAR + mov rsi, [rsi + Cons.car] + call map_get + je .found + + ; Not found, so try outer + pop rsi + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .not_found + mov rsi, [rsi + Cons.cdr] ; outer + jmp env_get +.found: + pop rsi + ret + +.not_found: + lahf ; flags in AH + and ah, 255-64 ; clear zero flag + sahf + ret +;; ------------------------------------------------------------ +;; Function type +;; +;; Functions are consist of a list +;; - First car is the function address to call +;; +;; ( addr ) +;; +;; + +;; Address of native function in RSI +;; returns Function object in RAX +native_function: + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_function) + mov [rax + Cons.car], rsi + ret From 7ff3d6f005883a85886da49a8e4931d4bcd66dda Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 28 Oct 2017 16:20:25 +0100 Subject: [PATCH 0562/2308] Adding eval_ast and eval functions Currently evaluates lists and values, looking up symbols in the repl_env map. e.g (+ 1 (* 3 2)) -> (# 1 (# 3 2)) --- nasm/core.asm | 26 ++++++- nasm/step2_eval.asm | 171 +++++++++++++++++++++++++++++++++++--------- nasm/types.asm | 10 ++- 3 files changed, 172 insertions(+), 35 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 404ef93e33..95b7ddc5dc 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -15,6 +15,10 @@ core_mul_symbol: db "*" core_div_symbol: db "/" .len: equ $ - core_div_symbol + +core_equal_symbol: db "=" +.len: equ $ - core_equal_symbol + section .text @@ -87,6 +91,22 @@ core_environment: mov rsi, core_div call native_function ; Function in RAX + mov rcx, rax ; value (function) + pop rdi ; key (symbol) + pop rsi ; environment + call env_set + + ; ----------------- + ; equal (=) + push rsi ; environment + mov rsi, core_equal_symbol + mov edx, core_equal_symbol.len + call raw_to_symbol ; Symbol in RAX + push rax + + mov rsi, core_equal_p + call native_function ; Function in RAX + mov rcx, rax ; value (function) pop rdi ; key (symbol) pop rsi ; environment @@ -96,7 +116,11 @@ core_environment: ; Put the environment in RAX mov rax, rsi ret - + +;; ---------------------------------------------------- + +;; Integer arithmetic operations +;; ;; Adds a list of numbers, address in RSI ;; Returns the sum as a number object with address in RAX ;; Since most of the code is common to all operators, diff --git a/nasm/step2_eval.asm b/nasm/step2_eval.asm index 6e8f72353f..b7247210c6 100644 --- a/nasm/step2_eval.asm +++ b/nasm/step2_eval.asm @@ -13,33 +13,13 @@ global _start %include "core.asm" ; Core functions %include "printer.asm" ; Data structures -> String -section .data - -test_string1: db 10, "test1", 10 -.len: equ $ - test_string1 - -test_string2: db 10, "test2", 10 -.len: equ $ - test_string2 +section .bss -;str: ISTRUC Array -;AT Array.type, db maltype_string -;AT Array.length, dd 6 -;AT Array.data, db 'hello',10 -;IEND - -test_cons: ISTRUC Cons -AT Cons.typecar, db ( maltype_integer + 2 ) -AT Cons.typecdr, db 0 -AT Cons.car, dq 123 -IEND - -test_cons2: ISTRUC Cons -AT Cons.typecar, db ( maltype_integer + 2 ) -AT Cons.typecdr, db content_pointer -AT Cons.car, dq 456 -AT Cons.cdr, dq test_cons -IEND +;; Top-level (REPL) environment +repl_env:resq 1 +section .data + ;; ------------------------------------------ ;; Fixed strings for printing @@ -48,9 +28,126 @@ prompt_string: db 10,"user> " ; The string to print at the prompt section .text -;; Evaluates a form +;; Evaluates a form in RSI +eval_ast: + ; Check the type + mov al, BYTE [rsi] + + ; Check if this is a list + mov ah, al + and ah, container_mask + cmp ah, container_list + je .list + + ; Not a list + cmp ah, container_symbol + je .symbol + + ; Not a symbol or a list + call incref_object ; Increment reference count + + mov rax, rsi + ret + +.symbol: + ; look in environment + mov rdi, rsi ; symbol is the key + mov rsi, [repl_env] ; Environment + call env_get + je .done ; result in RAX + + ; Not found, should raise an error + + ; Return nil + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret + +.list: + ; Evaluate each element of the list + ; + xor r8, r8 ; The list to return + ; r9 contains head of list + +.list_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .list_pointer + + ; A value, so copy + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_list) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .list_append + +.list_pointer: + ; List element is a pointer to something + push rsi + push r8 + push r9 + mov rsi, [rsi + Cons.car] ; Get the address + call eval_ast ; Evaluate it, result in rax + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, container_mask + cmp bh, container_value + je .list_append + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_list + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + ; Fall through to .list_append + +.list_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .list_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .list_next + +.list_first: + mov r8, rax + mov r9, rax + ; fall through to .list_next + +.list_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .list_done ; finished list + mov rsi, [rsi + Cons.cdr] ; next in list + jmp .list_loop + +.list_done: + mov rax, r8 ; Return the list +.done: + ret + +;; Evaluates a form in RSI eval: - mov rax, rsi ; Return the input + call eval_ast ret ;; Prints the result @@ -73,6 +170,8 @@ _start: ; Create and print the core environment call core_environment ; Environment in RAX + mov [repl_env], rax ; store in memory + mov rsi, rax call pr_str @@ -94,18 +193,22 @@ _start: cmp DWORD [rax+Array.length], 0 je .mainLoopEnd - push rax ; Save address of the string - + push rax ; Save address of the input string + ; Put into read_str mov rsi, rax call read_str - push rax + push rax ; Save AST + ; Eval + mov rsi, rax + call eval + push rax ; Save result ; Put into pr_str mov rsi, rax call pr_str - push rax + push rax ; Save output string mov rsi, rax ; Put into input of print_string call print_string @@ -113,12 +216,16 @@ _start: ; Release string from pr_str pop rsi call release_array + + ; Release result of eval + pop rsi + call release_object ; Release the object from read_str pop rsi call release_object ; Could be Cons or Array - ; Release the string + ; Release the input string pop rsi call release_array diff --git a/nasm/types.asm b/nasm/types.asm index 1a2536978e..f2a22cf6c1 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -145,12 +145,12 @@ error_cons_memory_limit: db "Error: Run out of memory for Cons objects. Increase ;; is free'd it is pushed onto the heap_x_free list. -%define heap_cons_limit 20 ; Number of cons objects which can be created +%define heap_cons_limit 50 ; Number of cons objects which can be created heap_cons_next: dd heap_cons_store ; Address of next cons in memory heap_cons_free: dq 0 ; Address of start of free list -%define heap_array_limit 10 ; Number of array objects which can be created +%define heap_array_limit 20 ; Number of array objects which can be created heap_array_next: dd heap_array_store heap_array_free: dq 0 @@ -1204,6 +1204,7 @@ map_get: ret ; --------------- .found_key: + ; Check if the object in RAX is a value or pointer mov bl, BYTE [rax] and bl, content_mask @@ -1242,6 +1243,11 @@ map_get: mov rsi, r8 mov rdi, r9 + mov rbx, rax + lahf ; flags in AH + or ah, 64 ; set zero flag + sahf + mov rax, rbx ret ;; ------------------------------------------------------------ From 6c78c820fa3fa60c1f008003e54cf3ebe1379306 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sun, 29 Oct 2017 15:27:20 +0000 Subject: [PATCH 0563/2308] Step 2: All non-deferrable tests pass Simple arithmetic operations with nested lists work. Map almost working: (keys {a 1 b 2}) -> (a b) but evaluating currently results in segfault so disabled. --- nasm/core.asm | 30 +++++++++- nasm/step2_eval.asm | 135 +++++++++++++++++++++++++++++++++++++++++--- nasm/types.asm | 86 +++++++++++++++++++++++++++- 3 files changed, 240 insertions(+), 11 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 95b7ddc5dc..2bb99c2c1b 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -18,7 +18,9 @@ core_div_symbol: db "/" core_equal_symbol: db "=" .len: equ $ - core_equal_symbol - + +core_keys_symbol: db "keys" +.len: equ $ - core_keys_symbol section .text @@ -107,6 +109,22 @@ core_environment: mov rsi, core_equal_p call native_function ; Function in RAX + mov rcx, rax ; value (function) + pop rdi ; key (symbol) + pop rsi ; environment + call env_set + + ; ----------------- + ; keys + push rsi ; environment + mov rsi, core_keys_symbol + mov edx, core_keys_symbol.len + call raw_to_symbol ; Symbol in RAX + push rax + + mov rsi, core_keys + call native_function ; Function in RAX + mov rcx, rax ; value (function) pop rdi ; key (symbol) pop rsi ; environment @@ -181,7 +199,7 @@ core_arithmetic: imul rax, [rsi + Cons.car] jmp .add_loop .do_division: - xor rdx, rdx ; Zero high bits + cqo ; Sign extend RAX into RDX mov rcx, [rsi + Cons.car] idiv rcx jmp .add_loop @@ -234,3 +252,11 @@ core_equal_p: mov [rax], BYTE maltype_nil mov [rax + Cons.typecdr], BYTE content_nil ret + +;; Given a map, returns a list of keys +;; Input: List in RSI with one Map element +;; Returns: List in RAX +core_keys: + mov rsi, [rsi + Cons.car] + call map_keys + ret diff --git a/nasm/step2_eval.asm b/nasm/step2_eval.asm index b7247210c6..8084eaa8a3 100644 --- a/nasm/step2_eval.asm +++ b/nasm/step2_eval.asm @@ -38,12 +38,15 @@ eval_ast: and ah, container_mask cmp ah, container_list je .list - - ; Not a list + + ;cmp ah, container_map + ;je .map + + ; Not a list or a map cmp ah, container_symbol je .symbol - - ; Not a symbol or a list + + ; Not a symbol, list or map call incref_object ; Increment reference count mov rax, rsi @@ -95,7 +98,7 @@ eval_ast: push r8 push r9 mov rsi, [rsi + Cons.car] ; Get the address - call eval_ast ; Evaluate it, result in rax + call eval ; Evaluate it, result in rax pop r9 pop r8 pop rsi @@ -103,10 +106,10 @@ eval_ast: ; Check the type it's evaluated to mov bl, BYTE [rax] mov bh, bl - and bh, container_mask - cmp bh, container_value + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) je .list_append - + ; Not a value, so need a pointer to it push rax call alloc_cons @@ -142,12 +145,128 @@ eval_ast: .list_done: mov rax, r8 ; Return the list + ret + + ; --------------------- +.map: + ; Create a new map, evaluating all the values + mov r10, rsi ; input in R10 + call map_keys + mov r11, rax ; Get list of keys in R11 + mov r13, rax ; Head of list in R13 + + call map_new + mov r12, rax ; new map in R12 + +.map_loop: + + ; Check the type of the key + mov al, BYTE [r13] + and al, content_mask + cmp al, content_pointer + je .map_key_pointer + + mov [r13], BYTE al ; Remove list container + + ; Get next value + mov rsi, r10 + mov rdi, r13 + call map_get ; Result in RAX + + ; Evaluate + + + mov rsi, r12 + mov rdi, r13 + mov rcx, rax + call map_set + + ; put back list container + mov al, BYTE [r13] + or al, container_list + mov [r13], BYTE al + + jmp .map_next + +.map_key_pointer: + mov rsi, r10 + mov rdi, [r13 + Cons.car] + call map_get ; Result in RAX + + ; Evaluate + + mov rsi, r12 + mov rdi, [r13 + Cons.car] + mov rcx, rax + call map_set + +.map_next: + + mov al, BYTE [r13 + Cons.typecdr] + cmp al, content_pointer + jne .map_done + + mov r13, [r13 + Cons.cdr] ; next key + jmp .map_loop + +.map_done: + ; Release list of keys + mov rsi, r11 + call release_cons + + mov rax, r12 + ret + ; --------------------- .done: ret ;; Evaluates a form in RSI eval: + ; Check type + mov al, BYTE [rsi] + cmp al, maltype_empty_list + je .empty_list ; empty list, return unchanged + + and al, container_mask + cmp al, container_list + je .list + + ; Not a list. Evaluate and return + call eval_ast + ret +.list: + ; A list call eval_ast + + ; Check that the first element of the return is a function + mov bl, BYTE [rax] + and bl, content_mask + cmp bl, content_pointer + jne .list_not_function + + mov rbx, [rax + Cons.car] ; Get the address + mov cl, BYTE [rbx] + cmp cl, maltype_function + jne .list_not_function + + ; Call the function with the rest of the list in RSI + push rax + mov rsi, [rax + Cons.cdr] ; Rest of list + mov rdi, rbx ; Function object in RDI + call [rbx + Cons.car] ; Call function + ; Result in rax + pop rsi ; eval'ed list + push rax + call release_cons + pop rax + ret + +.list_not_function: + ; Not a function. Probably an error + ret + +.empty_list: + mov rax, rsi ret ;; Prints the result diff --git a/nasm/types.asm b/nasm/types.asm index f2a22cf6c1..0741d55615 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -118,6 +118,7 @@ ENDSTRUC %define maltype_nil (block_cons + container_value + content_nil) %define maltype_empty_list (block_cons + container_list + content_empty) %define maltype_empty_map (block_cons + container_map + content_empty) +%define maltype_function (block_cons + container_function + content_function) ;; ------------------------------------------ @@ -1092,8 +1093,9 @@ map_set: ; Here a Cons object mov bh, bl and bh, container_mask - cmp bl, container_value + cmp bh, container_value jne .set_key_pointer ; Not a simple value, so point to it + ; A value, so copy mov rcx, [r9 + Cons.car] mov [rax + Cons.car], rcx @@ -1249,6 +1251,88 @@ map_get: sahf mov rax, rbx ret + +;; Get a list of keys +;; +;; Input: Map in RSI +;; +;; Returns: List in RAX +;; +;; Modifies registers: +;; RAX +;; RBX +;; RCX +;; R8 +;; R9 +map_keys: + ; check type + mov al, BYTE [rsi] + cmp al, maltype_empty_map + je .empty_map + + and al, container_mask + cmp al, container_map + jne .empty_map ; error + + xor r8, r8 ; Return list + + ; Take the current value +.loop: + ; Create a new Cons for this key + call alloc_cons + mov cl, BYTE [rsi] + and cl, content_mask + add cl, block_cons + container_list + mov [rax], BYTE cl ; Set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; Set value + + and cl, content_mask + cmp cl, content_pointer + jne .append + + ; A pointer, so increment reference count + mov cx, WORD [rbx + Cons.refcount] + inc cx + mov [rbx + Cons.refcount], WORD cx + +.append: + cmp r8, 0 + je .first + + ; appending + mov [r9 + Cons.typecdr], BYTE content_pointer + mov [r9 + Cons.cdr], rax + mov r9, rax + jmp .next +.first: + ; First key, so put into r8 + mov r8, rax + mov r9, rax +.next: + ; First get the value + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .done ; error. Should be a value + mov rsi, [rsi + Cons.cdr] + + ; Get the next key + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .done + mov rsi, [rsi + Cons.cdr] + jmp .loop +.done: + ; Finished, return the list + mov rax, r8 + ret + +.empty_map: + ; return empty list + call alloc_cons + mov [rax], BYTE maltype_empty_list + ret + ;; ------------------------------------------------------------ ;; Environment type From e731205ac4ab41191479d90b206643910629d879 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 30 Oct 2017 22:52:34 +0000 Subject: [PATCH 0564/2308] Step 2: Map evaluation works Using different method, now relying on implementation detail of Map. Works for simple tests, and test suite cases. Vector not yet implemented,but all other step 2 tests pass --- nasm/step2_eval.asm | 154 +++++++++++++++++++++++++++++++------------- 1 file changed, 109 insertions(+), 45 deletions(-) diff --git a/nasm/step2_eval.asm b/nasm/step2_eval.asm index 8084eaa8a3..166e075bf3 100644 --- a/nasm/step2_eval.asm +++ b/nasm/step2_eval.asm @@ -39,8 +39,8 @@ eval_ast: cmp ah, container_list je .list - ;cmp ah, container_map - ;je .map + cmp ah, container_map + je .map ; Not a list or a map cmp ah, container_symbol @@ -150,72 +150,136 @@ eval_ast: ; --------------------- .map: ; Create a new map, evaluating all the values + + ; Check if the map is empty + cmp al, maltype_empty_map + jne .map_not_empty + + ; map empty. Just return it + call incref_object + mov rax, rsi + ret + +.map_not_empty: + mov r10, rsi ; input in R10 - call map_keys - mov r11, rax ; Get list of keys in R11 - mov r13, rax ; Head of list in R13 + xor r12, r12 ; New map in r12 - call map_new - mov r12, rax ; new map in R12 + ; Now loop through each key-value pair + ; NOTE: This method relies on the implementation + ; of map as a list .map_loop: + ; Copy the key + call alloc_cons ; New Cons in RAX + + mov bl, [r10 + Cons.typecar] ; Type in BL + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] ; Value in RCX + mov [rax + Cons.car], rcx ; Check the type of the key - mov al, BYTE [r13] - and al, content_mask + and bl, content_mask + cmp bl, content_pointer + jne .map_got_key ; a value + + ; a pointer, so increment reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.map_got_key: + cmp r12,0 + jne .append_key + + ; First key + mov r12, rax + mov r13, rax + jmp .map_value + +.append_key: + ; Appending to previous value in r13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + +.map_value: + ; Check that we have a value + mov al, BYTE [r10 + Cons.typecdr] cmp al, content_pointer - je .map_key_pointer + jne .map_error_missing_value + mov r10, [r10 + Cons.cdr] - mov [r13], BYTE al ; Remove list container - - ; Get next value - mov rsi, r10 - mov rdi, r13 - call map_get ; Result in RAX + ; Now got value in r10 - ; Evaluate - - - mov rsi, r12 - mov rdi, r13 - mov rcx, rax - call map_set + ; Check the type of the value + mov bl, [r10 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .map_value_pointer - ; put back list container - mov al, BYTE [r13] - or al, container_list - mov [r13], BYTE al + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r10 + Cons.typecar] + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .map_got_value +.map_value_pointer: + ; A pointer, so need to evaluate + push r10 ; Input + push r12 ; start of result + push r13 ; Current head of result + mov rsi, [r10 + Cons.car] ; Get the address + call eval ; Evaluate it, result in rax + pop r13 + pop r12 + pop r10 - jmp .map_next + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) -.map_key_pointer: - mov rsi, r10 - mov rdi, [r13 + Cons.car] - call map_get ; Result in RAX + jne .map_eval_pointer - ; Evaluate + ; A value, so just change the type to a map + and bl, content_mask + add bl, (block_cons + container_map) + mov [rax], BYTE bl + jmp .map_got_value - mov rsi, r12 - mov rdi, [r13 + Cons.car] - mov rcx, rax - call map_set +.map_eval_pointer: + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_map + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx -.map_next: +.map_got_value: + ; Append RAX to list in R13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax - mov al, BYTE [r13 + Cons.typecdr] + ; Check if there's another key + mov al, BYTE [r10 + Cons.typecdr] cmp al, content_pointer - jne .map_done - - mov r13, [r13 + Cons.cdr] ; next key + jne .map_done ; finished map + mov r10, [r10 + Cons.cdr] ; next in map jmp .map_loop .map_done: - ; Release list of keys - mov rsi, r11 - call release_cons + mov rax, r12 + ret +.map_error_missing_value: mov rax, r12 ret + ; --------------------- .done: ret From 72893cd1e4f482fe8b26d7263c38904629773b9b Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 30 Oct 2017 23:45:55 +0000 Subject: [PATCH 0565/2308] Stage 3 : def! working Environment still hard-wired to be REPL env. Added special variable *env* which returns the environment object for printing. Helps debugging of environments. --- nasm/Makefile | 4 + nasm/env.asm | 108 +++++++++ nasm/step2_eval.asm | 1 + nasm/step3_env.asm | 548 ++++++++++++++++++++++++++++++++++++++++++++ nasm/types.asm | 77 ------- 5 files changed, 661 insertions(+), 77 deletions(-) create mode 100644 nasm/env.asm create mode 100644 nasm/step3_env.asm diff --git a/nasm/Makefile b/nasm/Makefile index 9dfc396bac..3eddf75ad6 100644 --- a/nasm/Makefile +++ b/nasm/Makefile @@ -11,3 +11,7 @@ step1_read_print: step1_read_print.asm $(COMPONENTS) step2_eval: step2_eval.asm $(COMPONENTS) nasm -felf64 step2_eval.asm ld -o $@ step2_eval.o + +step3_env: step3_env.asm $(COMPONENTS) + nasm -felf64 step3_env.asm + ld -o $@ step3_env.o diff --git a/nasm/env.asm b/nasm/env.asm new file mode 100644 index 0000000000..f3891c07aa --- /dev/null +++ b/nasm/env.asm @@ -0,0 +1,108 @@ + +;; ------------------------------------------------------------ +;; Environment type +;; +;; These are lists of maps. The head of the list is the +;; current environment, and CDR points to the outer environment +;; +;; ( {} {} ... ) + + +section .data + +env_symbol: ISTRUC Array +AT Array.type, db maltype_symbol +AT Array.length, dd 5 +AT Array.data, db '*env*' +IEND + +section .text + +;; Return a new Environment type in RAX +;; +;; Modifies registers: +;; RAX +;; RBX +env_new: + call map_new ; map in RAX + push rax + call alloc_cons ; Cons in RAX + pop rbx ; map in RBX + + mov [rax], BYTE (block_cons + container_list + content_pointer) + ; CDR type already set to nil in alloc_cons + mov [rax + Cons.car], rbx + + ret + +;; Environment set +;; +;; Sets a key-value pair in an environment +;; +;; Inputs: RSI - env [not modified] +;; RDI - key [not modified] +;; RCX - value [not modified] +;; +env_set: + push rsi + ; Get the first CAR, which should be a map + mov rsi, [rsi + Cons.car] + call map_set + pop rsi + ret + +;; Environment get +;; +;; Get a value from an environment, incrementing the reference count +;; of the object returned +;; +;; Inputs: RSI - environment +;; RDI - key +;; +;; Returns: If found, Zero Flag is set and address in RAX +;; If not found, Zero Flag cleared +env_get: + push rsi + + ; Check special variable *env* + mov rsi, env_symbol + call compare_char_array + pop rsi + cmp rax, 0 + jne .not_env_symbol + + ; Env symbol, so return this environment + call incref_object + lahf ; flags in AH + or ah, 64 ; set zero flag + sahf + mov rax, rsi + ret + +.not_env_symbol: + + ; Get the map in CAR + mov rsi, [rsi + Cons.car] + call map_get + je .found + + ; Not found, so try outer + pop rsi + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .not_found + + mov rsi, [rsi + Cons.cdr] ; outer + jmp env_get +.found: + pop rsi + ret + +.not_found: + lahf ; flags in AH + and ah, 255-64 ; clear zero flag + sahf + ret + + diff --git a/nasm/step2_eval.asm b/nasm/step2_eval.asm index 166e075bf3..24364f32d2 100644 --- a/nasm/step2_eval.asm +++ b/nasm/step2_eval.asm @@ -8,6 +8,7 @@ global _start %include "types.asm" ; Data types, memory +%include "env.asm" ; Environment type %include "system.asm" ; System calls %include "reader.asm" ; String -> Data structures %include "core.asm" ; Core functions diff --git a/nasm/step3_env.asm b/nasm/step3_env.asm new file mode 100644 index 0000000000..c5d0db12a9 --- /dev/null +++ b/nasm/step3_env.asm @@ -0,0 +1,548 @@ +;; +;; nasm -felf64 step3_env.asm && ld step3_env.o && ./a.out +;; +;; Calling convention: Address of input is in RSI +;; Address of return value is in RAX +;; + +global _start + +%include "types.asm" ; Data types, memory +%include "env.asm" ; Environment type +%include "system.asm" ; System calls +%include "reader.asm" ; String -> Data structures +%include "core.asm" ; Core functions +%include "printer.asm" ; Data structures -> String + +section .bss + +;; Top-level (REPL) environment +repl_env:resq 1 + +section .data + +;; ------------------------------------------ +;; Fixed strings for printing + +prompt_string: db 10,"user> " ; The string to print at the prompt +.len: equ $ - prompt_string + + +def_symbol: ISTRUC Array +AT Array.type, db maltype_symbol +AT Array.length, dd 4 +AT Array.data, db 'def!' +IEND + +let_symbol: ISTRUC Array +AT Array.type, db maltype_symbol +AT Array.length, dd 4 +AT Array.data, db 'let*' +IEND + +section .text + +;; Evaluates a form in RSI +eval_ast: + ; Check the type + mov al, BYTE [rsi] + + ; Check if this is a list + mov ah, al + and ah, container_mask + cmp ah, container_list + je .list + + cmp ah, container_map + je .map + + ; Not a list or a map + cmp ah, container_symbol + je .symbol + + ; Not a symbol, list or map + call incref_object ; Increment reference count + + mov rax, rsi + ret + +.symbol: + ; look in environment + mov rdi, rsi ; symbol is the key + mov rsi, [repl_env] ; Environment + call env_get + je .done ; result in RAX + + ; Not found, should raise an error + + ; Return nil + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret + +.list: + ; Evaluate each element of the list + ; + xor r8, r8 ; The list to return + ; r9 contains head of list + +.list_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .list_pointer + + ; A value, so copy + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_list) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .list_append + +.list_pointer: + ; List element is a pointer to something + push rsi + push r8 + push r9 + mov rsi, [rsi + Cons.car] ; Get the address + call eval ; Evaluate it, result in rax + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .list_append + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_list + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + ; Fall through to .list_append + +.list_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .list_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .list_next + +.list_first: + mov r8, rax + mov r9, rax + ; fall through to .list_next + +.list_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .list_done ; finished list + mov rsi, [rsi + Cons.cdr] ; next in list + jmp .list_loop + +.list_done: + mov rax, r8 ; Return the list + ret + + ; --------------------- +.map: + ; Create a new map, evaluating all the values + + ; Check if the map is empty + cmp al, maltype_empty_map + jne .map_not_empty + + ; map empty. Just return it + call incref_object + mov rax, rsi + ret + +.map_not_empty: + + mov r10, rsi ; input in R10 + xor r12, r12 ; New map in r12 + + ; Now loop through each key-value pair + ; NOTE: This method relies on the implementation + ; of map as a list + +.map_loop: + ; Copy the key + call alloc_cons ; New Cons in RAX + + mov bl, [r10 + Cons.typecar] ; Type in BL + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] ; Value in RCX + mov [rax + Cons.car], rcx + + ; Check the type of the key + and bl, content_mask + cmp bl, content_pointer + jne .map_got_key ; a value + + ; a pointer, so increment reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.map_got_key: + cmp r12,0 + jne .append_key + + ; First key + mov r12, rax + mov r13, rax + jmp .map_value + +.append_key: + ; Appending to previous value in r13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + +.map_value: + ; Check that we have a value + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_error_missing_value + mov r10, [r10 + Cons.cdr] + + ; Now got value in r10 + + ; Check the type of the value + mov bl, [r10 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .map_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r10 + Cons.typecar] + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .map_got_value +.map_value_pointer: + ; A pointer, so need to evaluate + push r10 ; Input + push r12 ; start of result + push r13 ; Current head of result + mov rsi, [r10 + Cons.car] ; Get the address + call eval ; Evaluate it, result in rax + pop r13 + pop r12 + pop r10 + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + + jne .map_eval_pointer + + ; A value, so just change the type to a map + and bl, content_mask + add bl, (block_cons + container_map) + mov [rax], BYTE bl + jmp .map_got_value + +.map_eval_pointer: + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_map + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + +.map_got_value: + ; Append RAX to list in R13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + + ; Check if there's another key + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_done ; finished map + mov r10, [r10 + Cons.cdr] ; next in map + jmp .map_loop + +.map_done: + mov rax, r12 + ret + +.map_error_missing_value: + mov rax, r12 + ret + + ; --------------------- +.done: + ret + +;; Evaluates a form in RSI +eval: + ; Check type + mov al, BYTE [rsi] + cmp al, maltype_empty_list + je .empty_list ; empty list, return unchanged + + and al, container_mask + cmp al, container_list + je .list + + ; Not a list. Evaluate and return + call eval_ast + ret + + ; -------------------- +.list: + ; A list + + ; Check if the first element is a symbol + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .list_eval + + mov rbx, [rsi + Cons.car] + mov al, BYTE [rbx] + cmp al, maltype_symbol + jne .list_eval + + ; Is a symbol, address in RBX + push rsi + + ; Compare against def! + mov rsi, rbx + mov rdi, def_symbol + call compare_char_array + pop rsi + cmp rax, 0 + je .def_symbol + + push rsi + mov rdi, let_symbol + call compare_char_array + pop rsi + cmp rax, 0 + je .let_symbol + + ; Unrecognised + jmp .list_eval + +.def_symbol: + ; Define a new symbol in current environment + + ; call alloc_cons + ; mov [rax], BYTE maltype_nil + ; mov [rax + Cons.typecdr], BYTE content_nil + ; ret + + ; Next item should be a symbol + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Now should have a symbol + + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + jne .def_error_expecting_symbol + mov r8, [rsi + Cons.car] ; Symbol (?) + + mov al, BYTE [r8] + cmp al, maltype_symbol + jne .def_error_expecting_symbol + + ; R8 now contains a symbol + + ; expecting a value or pointer next + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a pointer + mov al, BYTE [rsi] + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .def_pointer + + ; A value, so copy + push rax + call alloc_cons + pop rbx ; BL now contains type + and bl, content_mask + add bl, (block_cons + container_value) + mov [rax], BYTE bl + mov rcx, [rsi + Cons.car] + mov [rax + Cons.car], rcx + mov rsi, rax + + jmp .def_got_value + +.def_pointer: + ; A pointer, so evaluate + push r8 ; the symbol + mov rsi, [rsi + Cons.car] ; Pointer + call eval + mov rsi, rax + pop r8 + +.def_got_value: + ; Symbol in R8, value in RSI + mov rdi, r8 ; key (symbol) + mov rcx, rsi ; Value + mov rsi, [repl_env] + call env_set + + mov rax, rcx ; Return the value + ret + +.def_error_missing_arg: + +.def_error_expecting_symbol: + + mov rax, rsi + ret + + ; ----------------------------- +.let_symbol: + ; Create a new environment + + jmp .list_not_function +.list_eval: + + call eval_ast + + ; Check that the first element of the return is a function + mov bl, BYTE [rax] + and bl, content_mask + cmp bl, content_pointer + jne .list_not_function + + mov rbx, [rax + Cons.car] ; Get the address + mov cl, BYTE [rbx] + cmp cl, maltype_function + jne .list_not_function + + ; Call the function with the rest of the list in RSI + push rax + mov rsi, [rax + Cons.cdr] ; Rest of list + mov rdi, rbx ; Function object in RDI + call [rbx + Cons.car] ; Call function + ; Result in rax + pop rsi ; eval'ed list + push rax + call release_cons + pop rax + ret + +.list_not_function: + ; Not a function. Probably an error + ret + +.empty_list: + mov rax, rsi + ret + +;; Prints the result +print: + mov rax, rsi ; Return the input + ret + +;; Read-Eval-Print in sequence +rep_seq: + call read_str + mov rsi, rax ; Output of read into input of eval + call eval + mov rsi, rax ; Output of eval into input of print + call print + mov rsi, rax ; Return value + ret + + +_start: + ; Create and print the core environment + call core_environment ; Environment in RAX + + mov [repl_env], rax ; store in memory + + ; ----------------------------- + ; Main loop + +.mainLoop: + ; print the prompt + mov rdx, prompt_string.len ; number of bytes + mov rsi, prompt_string ; address of raw string to output + call print_rawstring + + call read_line + + ; Check if we have a zero-length string + cmp DWORD [rax+Array.length], 0 + je .mainLoopEnd + + push rax ; Save address of the input string + + ; Put into read_str + mov rsi, rax + call read_str + push rax ; Save AST + + ; Eval + mov rsi, rax + call eval + push rax ; Save result + + ; Put into pr_str + mov rsi, rax + call pr_str + push rax ; Save output string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from pr_str + pop rsi + call release_array + + ; Release result of eval + pop rsi + call release_object + + ; Release the object from read_str + pop rsi + call release_object ; Could be Cons or Array + + ; Release the input string + pop rsi + call release_array + + jmp .mainLoop +.mainLoopEnd: + + jmp quit + diff --git a/nasm/types.asm b/nasm/types.asm index 0741d55615..7012a0ab86 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -1334,83 +1334,6 @@ map_keys: ret -;; ------------------------------------------------------------ -;; Environment type -;; -;; These are lists of maps. The head of the list is the -;; current environment, and CDR points to the outer environment -;; -;; ( {} {} ... ) - -;; Return a new Environment type in RAX -;; -;; Modifies registers: -;; RAX -;; RBX -env_new: - call map_new ; map in RAX - push rax - call alloc_cons ; Cons in RAX - pop rbx ; map in RBX - - mov [rax], BYTE (block_cons + container_list + content_pointer) - ; CDR type already set to nil in alloc_cons - mov [rax + Cons.car], rbx - - ret - -;; Environment set -;; -;; Sets a key-value pair in an environment -;; -;; Inputs: RSI - env [not modified] -;; RDI - key [not modified] -;; RCX - value [not modified] -;; -env_set: - push rsi - ; Get the first CAR, which should be a map - mov rsi, [rsi + Cons.car] - call map_set - pop rsi - ret - -;; Environment get -;; -;; Get a value from an environment, incrementing the reference count -;; of the object returned -;; -;; Inputs: RSI - environment -;; RDI - key -;; -;; Returns: If found, Zero Flag is set and address in RAX -;; If not found, Zero Flag cleared -env_get: - push rsi - ; Get the map in CAR - mov rsi, [rsi + Cons.car] - call map_get - je .found - - ; Not found, so try outer - pop rsi - - mov al, BYTE [rsi + Cons.typecdr] - cmp al, content_pointer - jne .not_found - - mov rsi, [rsi + Cons.cdr] ; outer - jmp env_get -.found: - pop rsi - ret - -.not_found: - lahf ; flags in AH - and ah, 255-64 ; clear zero flag - sahf - ret - ;; ------------------------------------------------------------ ;; Function type ;; From dcd798841479fea5e7ddd98cf5ad53cfbd060e38 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 31 Oct 2017 13:41:36 +0100 Subject: [PATCH 0566/2308] elm, objpascal, plpgsql: Add number?, fn?, macro? --- elm/Core.elm | 41 +++++++++++++++++++++++++++++++++++++++++ objpascal/core.pas | 23 +++++++++++++++++++++++ plpgsql/core.sql | 18 ++++++++++++++++++ plpgsql/types.sql | 31 +++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+) diff --git a/elm/Core.elm b/elm/Core.elm index 1b31ae9e4e..07e6eced43 100644 --- a/elm/Core.elm +++ b/elm/Core.elm @@ -543,6 +543,16 @@ ns = _ -> False + isNumber args = + Eval.succeed <| + MalBool <| + case args of + (MalInt _) :: _ -> + True + + _ -> + False + isSymbol args = Eval.succeed <| MalBool <| @@ -606,6 +616,34 @@ ns = _ -> False + isFn args = + Eval.succeed <| + MalBool <| + case args of + (MalFunction (CoreFunc _)) :: _ -> + True + (MalFunction (UserFunc fn)) :: _ -> + if fn.isMacro then + False + else + True + + _ -> + False + + isMacro args = + Eval.succeed <| + MalBool <| + case args of + (MalFunction (UserFunc fn)) :: _ -> + if fn.isMacro then + True + else + False + + _ -> + False + symbol args = case args of [ MalString str ] -> @@ -892,12 +930,15 @@ ns = |> Env.set "nil?" (makeFn isNil) |> Env.set "true?" (makeFn isTrue) |> Env.set "false?" (makeFn isFalse) + |> Env.set "number?" (makeFn isNumber) |> Env.set "symbol?" (makeFn isSymbol) |> Env.set "keyword?" (makeFn isKeyword) |> Env.set "vector?" (makeFn isVector) |> Env.set "map?" (makeFn isMap) |> Env.set "string?" (makeFn isString) |> Env.set "sequential?" (makeFn isSequential) + |> Env.set "fn?" (makeFn isFn) + |> Env.set "macro?" (makeFn isMacro) |> Env.set "symbol" (makeFn symbol) |> Env.set "keyword" (makeFn keyword) |> Env.set "vector" (makeFn vector) diff --git a/objpascal/core.pas b/objpascal/core.pas index dd5fcb6d5c..1822d887e0 100644 --- a/objpascal/core.pas +++ b/objpascal/core.pas @@ -52,6 +52,10 @@ function false_Q(Args: TMalArray) : TMal; begin false_Q := wrap_tf(Args[0] is TMalFalse); end; +function number_Q(Args: TMalArray) : TMal; +begin + number_Q := wrap_tf(Args[0] is TMalInt); +end; function string_Q(Args: TMalArray) : TMal; begin string_Q := wrap_tf(_string_Q(Args[0])); @@ -82,6 +86,22 @@ function keyword_Q(Args: TMalArray) : TMal; begin keyword_Q := wrap_tf((Args[0] is TMalString) and not _string_Q(Args[0])); end; +function fn_Q(Args: TMalArray) : TMal; +begin + if Args[0] is TMalFunc then + fn_Q := wrap_tf(not (Args[0] as TMalFunc).isMacro) + else + fn_Q := TMalFalse.Create; +end; + +function macro_Q(Args: TMalArray) : TMal; +begin + if Args[0] is TMalFunc then + macro_Q := wrap_tf((Args[0] as TMalFunc).isMacro) + else + macro_Q := TMalFalse.Create; +end; + // String functions @@ -541,11 +561,14 @@ initialization NS['nil?'] := @nil_Q; NS['true?'] := @true_Q; NS['false?'] := @false_Q; + NS['number?'] := @number_Q; NS['string?'] := @string_Q; NS['symbol'] := @symbol; NS['symbol?'] := @symbol_Q; NS['keyword'] := @keyword; NS['keyword?'] := @keyword_Q; + NS['fn?'] := @fn_Q; + NS['macro?'] := @macro_Q; NS['pr-str'] := @do_pr_str; NS['str'] := @str; diff --git a/plpgsql/core.sql b/plpgsql/core.sql index 25614e7524..9e69491305 100644 --- a/plpgsql/core.sql +++ b/plpgsql/core.sql @@ -33,6 +33,11 @@ BEGIN RETURN types._wraptf(types._false_Q(args[1])); END; $$ LANGUAGE plpgsql; +CREATE FUNCTION core.number_Q(args integer[]) RETURNS integer AS $$ +BEGIN + RETURN types._wraptf(types._number_Q(args[1])); +END; $$ LANGUAGE plpgsql; + CREATE FUNCTION core.string_Q(args integer[]) RETURNS integer AS $$ BEGIN RETURN types._wraptf(types._string_Q(args[1])); @@ -62,6 +67,16 @@ BEGIN RETURN types._wraptf(types._keyword_Q(args[1])); END; $$ LANGUAGE plpgsql; +CREATE FUNCTION core.fn_Q(args integer[]) RETURNS integer AS $$ +BEGIN + RETURN types._wraptf(types._fn_Q(args[1])); +END; $$ LANGUAGE plpgsql; + +CREATE FUNCTION core.macro_Q(args integer[]) RETURNS integer AS $$ +BEGIN + RETURN types._wraptf(types._macro_Q(args[1])); +END; $$ LANGUAGE plpgsql; + -- string functions @@ -495,11 +510,14 @@ INSERT INTO envs.env (env_id, outer_id, data) 'nil?', types._function('core.nil_Q'), 'true?', types._function('core.true_Q'), 'false?', types._function('core.false_Q'), + 'number?', types._function('core.number_Q'), 'string?', types._function('core.string_Q'), 'symbol', types._function('core.symbol'), 'symbol?', types._function('core.symbol_Q'), 'keyword', types._function('core.keyword'), 'keyword?', types._function('core.keyword_Q'), + 'fn?', types._function('core.fn_Q'), + 'macro?', types._function('core.macro_Q'), 'pr-str', types._function('core.pr_str'), 'str', types._function('core.str'), diff --git a/plpgsql/types.sql b/plpgsql/types.sql index 62a63cc83b..a6cb67d1e6 100644 --- a/plpgsql/types.sql +++ b/plpgsql/types.sql @@ -207,6 +207,15 @@ BEGIN RETURN false; END; $$ LANGUAGE plpgsql; +-- _number_Q: +-- takes a value_id +-- returns the whether value_id is integer or float type +CREATE FUNCTION types._number_Q(id integer) RETURNS boolean AS $$ +BEGIN + RETURN types._tf((SELECT 1 FROM types.value + WHERE (type_id = 3 OR type_id = 4) + AND value_id = id)); +END; $$ LANGUAGE plpgsql; -- _valueToString: -- takes a value_id for a string @@ -304,6 +313,28 @@ BEGIN RETURN result; END; $$ LANGUAGE plpgsql; +-- _fn_Q: +-- takes a value_id +-- returns the whether value_id is a function +CREATE FUNCTION types._fn_Q(id integer) RETURNS boolean AS $$ +BEGIN + RETURN types._tf((SELECT 1 FROM types.value + WHERE (type_id = 11 OR type_id = 12) + AND macro IS NULL + AND value_id = id)); +END; $$ LANGUAGE plpgsql; + +-- _macro_Q: +-- takes a value_id +-- returns the whether value_id is a macro +CREATE FUNCTION types._macro_Q(id integer) RETURNS boolean AS $$ +BEGIN + RETURN types._tf((SELECT 1 FROM types.value + WHERE type_id = 12 + AND macro IS TRUE + AND value_id = id)); +END; $$ LANGUAGE plpgsql; + -- --------------------------------------------------------- -- sequence functions From ccb1f324518e2312db92b0a73e12c174cb3f2fef Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 31 Oct 2017 23:15:20 +0000 Subject: [PATCH 0567/2308] Small bugfix in Env and error messages * Bug in Env lookup could result in segfault * Added some error message printing. Not really handling errors properly yet. --- nasm/core.asm | 1 + nasm/env.asm | 19 ++++++++++---- nasm/step2_eval.asm | 60 ++++++++++++++++++++++++++++++++++++++++++++- nasm/step3_env.asm | 54 ++++++++++++++++++++++++++++++++++++++-- 4 files changed, 126 insertions(+), 8 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 2bb99c2c1b..b3194b28ba 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -31,6 +31,7 @@ section .text ;; core_environment: ; Create the top-level environment + xor rsi, rsi ; Set outer to nil call env_new ; in RAX push rax diff --git a/nasm/env.asm b/nasm/env.asm index f3891c07aa..9bb6117ba3 100644 --- a/nasm/env.asm +++ b/nasm/env.asm @@ -17,7 +17,11 @@ AT Array.data, db '*env*' IEND section .text - + +;; Create a new Environment +;; +;; Input: outer Environment in RSI. If zero, then nil outer. +;; ;; Return a new Environment type in RAX ;; ;; Modifies registers: @@ -32,7 +36,13 @@ env_new: mov [rax], BYTE (block_cons + container_list + content_pointer) ; CDR type already set to nil in alloc_cons mov [rax + Cons.car], rbx - + + cmp rsi, 0 + jne .set_outer + ret ; No outer, just return +.set_outer: + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rsi ret ;; Environment set @@ -80,14 +90,14 @@ env_get: ret .not_env_symbol: - + push rsi ; Get the map in CAR mov rsi, [rsi + Cons.car] call map_get + pop rsi je .found ; Not found, so try outer - pop rsi mov al, BYTE [rsi + Cons.typecdr] cmp al, content_pointer @@ -96,7 +106,6 @@ env_get: mov rsi, [rsi + Cons.cdr] ; outer jmp env_get .found: - pop rsi ret .not_found: diff --git a/nasm/step2_eval.asm b/nasm/step2_eval.asm index 24364f32d2..639169dc16 100644 --- a/nasm/step2_eval.asm +++ b/nasm/step2_eval.asm @@ -26,7 +26,20 @@ section .data prompt_string: db 10,"user> " ; The string to print at the prompt .len: equ $ - prompt_string - + + +def_symbol: ISTRUC Array +AT Array.type, db maltype_symbol +AT Array.length, dd 4 +AT Array.data, db 'def!' +IEND + +let_symbol: ISTRUC Array +AT Array.type, db maltype_symbol +AT Array.length, dd 4 +AT Array.data, db 'let*' +IEND + section .text ;; Evaluates a form in RSI @@ -299,8 +312,53 @@ eval: ; Not a list. Evaluate and return call eval_ast ret + + ; -------------------- .list: ; A list + + ; Check if the first element is a symbol + mov al, BYTE [rsi] + and bl, content_mask + cmp bl, content_pointer + jne .list_eval + + mov rbx, [rsi + Cons.car] + mov al, BYTE [rbx] + cmp al, maltype_symbol + jne .list_eval + + ; Is a symbol, address in RBX + push rsi + + ; Compare against def! + mov rsi, rbx + mov rdi, def_symbol + call compare_char_array + pop rsi + cmp rax, 0 + je .def_symbol + + push rsi + mov rdi, let_symbol + call compare_char_array + pop rsi + cmp rax, 0 + je .let_symbol + + ; Unrecognised + jmp .list_eval + +.def_symbol: + ; Define a new symbol in current environment + + jmp .list_not_function +.let_symbol: + ; Create a new environment + + jmp .list_not_function +.list_eval: + call eval_ast ; Check that the first element of the return is a function diff --git a/nasm/step3_env.asm b/nasm/step3_env.asm index c5d0db12a9..b3ac569995 100644 --- a/nasm/step3_env.asm +++ b/nasm/step3_env.asm @@ -27,7 +27,18 @@ section .data prompt_string: db 10,"user> " ; The string to print at the prompt .len: equ $ - prompt_string +error_string: db 27,'[31m',"Error",27,'[0m',": " +.len: equ $ - error_string +not_found_string: db " not found.",10 +.len: equ $ - not_found_string + +def_missing_arg_string: db "missing argument to def!",10 +.len: equ $ - def_missing_arg_string + +def_expecting_symbol_string: db "expecting symbol as first argument to def!",10 +.len: equ $ - def_expecting_symbol_string + def_symbol: ISTRUC Array AT Array.type, db maltype_symbol AT Array.length, dd 4 @@ -68,12 +79,27 @@ eval_ast: .symbol: ; look in environment + push rsi mov rdi, rsi ; symbol is the key mov rsi, [repl_env] ; Environment call env_get + pop rsi je .done ; result in RAX ; Not found, should raise an error + push rsi + mov rsi, error_string + mov rdx, error_string.len + call print_rawstring ; print 'Error: ' + + pop rsi + mov edx, [rsi + Array.length] + add rsi, Array.data + call print_rawstring ; print symbol + + mov rsi, not_found_string + mov rdx, not_found_string.len + call print_rawstring ; print ' not found' ; Return nil call alloc_cons @@ -422,10 +448,34 @@ eval: ret .def_error_missing_arg: + mov rsi, error_string + mov rdx, error_string.len + call print_rawstring ; print 'Error: ' + + mov rsi, def_missing_arg_string + mov rdx, def_missing_arg_string.len + call print_rawstring -.def_error_expecting_symbol: + ; Return nil + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret - mov rax, rsi + +.def_error_expecting_symbol: + mov rsi, error_string + mov rdx, error_string.len + call print_rawstring ; print 'Error: ' + + mov rsi, def_expecting_symbol_string + mov rdx, def_expecting_symbol_string.len + call print_rawstring + + ; Return nil + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil ret ; ----------------------------- From 3e072a0f047478f7f9a6c0c21ab51d3c3f1de5ba Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 1 Nov 2017 00:27:48 +0000 Subject: [PATCH 0568/2308] let* form bindings nearly working Defines a new environment, which is passed to Eval. Simple forms seem to work, printing environment to test: (let* (a 1 b (* a 2))) -> ({a 1 b 2} {+ # - # * # / # = # keys #}) but nested lists have a problem: user> (let* (a 1 b (* a (+ a 1)))) -> Error: * not found. ({a 1 b nil} #) though standalone nested loops seem ok: user> (+ 1 (* 2 3)) -> 7 --- nasm/env.asm | 4 ++ nasm/step3_env.asm | 176 ++++++++++++++++++++++++++++++++++++++++----- nasm/types.asm | 6 +- 3 files changed, 169 insertions(+), 17 deletions(-) diff --git a/nasm/env.asm b/nasm/env.asm index 9bb6117ba3..a4e73e8d7f 100644 --- a/nasm/env.asm +++ b/nasm/env.asm @@ -53,6 +53,10 @@ env_new: ;; RDI - key [not modified] ;; RCX - value [not modified] ;; +;; Modifies registers: +;; R8 +;; R9 +;; R10 env_set: push rsi ; Get the first CAR, which should be a map diff --git a/nasm/step3_env.asm b/nasm/step3_env.asm index b3ac569995..146c092d2e 100644 --- a/nasm/step3_env.asm +++ b/nasm/step3_env.asm @@ -52,9 +52,16 @@ AT Array.data, db 'let*' IEND section .text - -;; Evaluates a form in RSI + +;; ---------------------------------------------- +;; Evaluates a form +;; +;; Inputs: RSI Form to evaluate +;; RDI Environment +;; eval_ast: + mov r15, rdi ; Save Env in r15 + ; Check the type mov al, BYTE [rsi] @@ -80,8 +87,9 @@ eval_ast: .symbol: ; look in environment push rsi - mov rdi, rsi ; symbol is the key - mov rsi, [repl_env] ; Environment + xchg rsi, rdi + ; symbol is the key in rdi + ; Environment in rsi call env_get pop rsi je .done ; result in RAX @@ -137,8 +145,11 @@ eval_ast: push rsi push r8 push r9 + push r15 ; Env mov rsi, [rsi + Cons.car] ; Get the address + mov rdi, r15 call eval ; Evaluate it, result in rax + pop r15 pop r9 pop r8 pop rsi @@ -271,8 +282,11 @@ eval_ast: push r10 ; Input push r12 ; start of result push r13 ; Current head of result + push r15 ; Env mov rsi, [r10 + Cons.car] ; Get the address + mov rdi, r15 call eval ; Evaluate it, result in rax + pop r15 pop r13 pop r12 pop r10 @@ -324,8 +338,17 @@ eval_ast: .done: ret -;; Evaluates a form in RSI +;; ---------------------------------------------------- +;; Evaluates a form +;; +;; Input: RSI Form to evaluate +;; RDI Environment +;; +;; Returns: Result in RAX +;; eval: + mov r15, rdi ; Env + ; Check type mov al, BYTE [rsi] cmp al, maltype_empty_list @@ -356,18 +379,23 @@ eval: ; Is a symbol, address in RBX push rsi - + push rbx + ; Compare against def! mov rsi, rbx mov rdi, def_symbol call compare_char_array + pop rbx pop rsi cmp rax, 0 je .def_symbol push rsi + push rbx + mov rsi, rbx mov rdi, let_symbol call compare_char_array + pop rbx pop rsi cmp rax, 0 je .let_symbol @@ -377,12 +405,7 @@ eval: .def_symbol: ; Define a new symbol in current environment - - ; call alloc_cons - ; mov [rax], BYTE maltype_nil - ; mov [rax + Cons.typecdr], BYTE content_nil - ; ret - + ; Next item should be a symbol mov al, BYTE [rsi + Cons.typecdr] cmp al, content_pointer @@ -432,16 +455,19 @@ eval: .def_pointer: ; A pointer, so evaluate push r8 ; the symbol + push r15 ; Env mov rsi, [rsi + Cons.car] ; Pointer + mov rdi, r15 call eval mov rsi, rax + pop r15 pop r8 .def_got_value: ; Symbol in R8, value in RSI mov rdi, r8 ; key (symbol) mov rcx, rsi ; Value - mov rsi, [repl_env] + mov rsi, r15 ; Environment call env_set mov rax, rcx ; Return the value @@ -481,10 +507,127 @@ eval: ; ----------------------------- .let_symbol: ; Create a new environment + + mov r11, rsi - jmp .list_not_function -.list_eval: + mov rsi, r15 ; Outer env + call env_new + mov r14, rax ; New environment in r14 + + ; Second element should be the bindings + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_bindings + mov r11, [r11 + Cons.cdr] + + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .let_error_bindings_list + + mov r12, [r11 + Cons.car] ; should be bindings list + mov al, BYTE [r12] + and al, (block_mask + container_mask) + cmp al, block_cons + container_list + jne .let_error_bindings_list + + ; R12 now contains a list with an even number of items + ; The first should be a symbol, then a value to evaluate + +.let_bind_loop: + + ; Get the symbol + mov al, BYTE [r12] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_symbol + + mov r13, [r12 + Cons.car] ; Symbol (?) + mov al, BYTE [r13] + cmp al, maltype_symbol + jne .let_error_bind_symbol + + ; R13 now contains a symbol to bind + ; The next item in the bindings list (R12) + ; should be a value or expression to evaluate + + mov al, BYTE [r12 + Cons.typecdr] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_value + mov r12, [r12 + Cons.cdr] + + ; got value in R12 + + ; Check the type of the value + mov bl, [r12 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .let_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r12 + Cons.typecar] + and bl, content_mask + ;or bl, (block_cons + container_value) ; 0 + mov [rax + Cons.typecar], bl + mov rcx, [r12 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .let_got_value + +.let_value_pointer: + ; A pointer, so need to evaluate + push r11 ; let* form list + push r12 ; Position in bindings list + push r13 ; symbol to bind + push r14 ; new environment + mov rsi, [r12 + Cons.car] ; Get the address + mov rdi, r14 + call eval ; Evaluate it, result in rax + pop r14 + pop r13 + pop r12 + pop r11 + +.let_got_value: + + mov rsi, r14 ; Env + mov rdi, r13 ; key + mov rcx, rax ; value + call env_set + ; Check if there are more bindings + mov al, BYTE [r12 + Cons.typecdr] + cmp al, content_pointer + jne .let_done_binding + mov r12, [r12 + Cons.cdr] ; Next + jmp .let_bind_loop + +.let_done_binding: + ; Done bindings. + ; Evaluate next item in let* form in new environment + + + mov rax, r14 + ret + +.let_error_missing_bindings: +.let_error_bindings_list: ; expected a list, got something else +.let_error_bind_symbol: ; expected a symbol, got something else +.let_error_bind_value: ; Missing value in binding list + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret + + ; ----------------------------- + +.list_eval: + + mov rdi, r15 ; Environment call eval_ast ; Check that the first element of the return is a function @@ -563,7 +706,8 @@ _start: push rax ; Save AST ; Eval - mov rsi, rax + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment call eval push rax ; Save result diff --git a/nasm/types.asm b/nasm/types.asm index 7012a0ab86..bcd8ef2639 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -151,7 +151,7 @@ error_cons_memory_limit: db "Error: Run out of memory for Cons objects. Increase heap_cons_next: dd heap_cons_store ; Address of next cons in memory heap_cons_free: dq 0 ; Address of start of free list -%define heap_array_limit 20 ; Number of array objects which can be created +%define heap_array_limit 50 ; Number of array objects which can be created heap_array_next: dd heap_array_store heap_array_free: dq 0 @@ -1037,6 +1037,10 @@ map_find: ;; RDI - key [not modified] ;; RCX - value [not modified] ;; +;; Modifies registers: +;; R8 +;; R9 +;; R10 map_set: ; Save inputs in less volatile registers mov r8, rsi ; map From 968faaad7c8fce1d7df1b9dc0567a5ed702f2206 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Wed, 1 Nov 2017 14:07:51 +0000 Subject: [PATCH 0569/2308] ada: Add number?, fn?, macro? --- ada/core.adb | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/ada/core.adb b/ada/core.adb index 68f45830f8..3f9e590ca1 100644 --- a/ada/core.adb +++ b/ada/core.adb @@ -587,6 +587,39 @@ package body Core is end Is_Keyword; + function Is_Number (Rest_Handle : Mal_Handle) return Types.Mal_Handle is + First_Param : Mal_Handle; + begin + First_Param := Car (Deref_List (Rest_Handle).all); + return New_Bool_Mal_Type (Deref (First_Param).Sym_Type = Int); + end Is_Number; + + + function Is_Fn (Rest_Handle : Mal_Handle) return Types.Mal_Handle is + First_Param : Mal_Handle; + Res : Boolean; + begin + First_Param := Car (Deref_List (Rest_Handle).all); + case Deref (First_Param).Sym_Type is + when Func => + Res := True; + when Lambda => + Res := not Deref_Lambda (First_Param).Get_Is_Macro; + when others => + Res := False; + end case; + return New_Bool_Mal_Type (Res); + end Is_Fn; + + + function Is_Macro (Rest_Handle : Mal_Handle) return Types.Mal_Handle is + First_Param : Mal_Handle; + begin + First_Param := Car (Deref_List (Rest_Handle).all); + return New_Bool_Mal_Type (Deref (First_Param).Sym_Type = Lambda and then Deref_Lambda (First_Param).Get_Is_Macro); + end Is_Macro; + + function New_List (Rest_Handle : Mal_Handle) return Types.Mal_Handle is Rest_List : Types.List_Mal_Type; @@ -1126,6 +1159,18 @@ package body Core is "keyword?", New_Func_Mal_Type ("keyword?", Is_Keyword'access)); + Envs.Set (Repl_Env, + "number?", + New_Func_Mal_Type ("number?", Is_Number'access)); + + Envs.Set (Repl_Env, + "fn?", + New_Func_Mal_Type ("fn?", Is_Fn'access)); + + Envs.Set (Repl_Env, + "macro?", + New_Func_Mal_Type ("macro?", Is_Macro'access)); + Envs.Set (Repl_Env, "pr-str", New_Func_Mal_Type ("pr-str", Pr_Str'access)); From aecec6d786034db9ad5395458ab4d5ec78d03b25 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 1 Nov 2017 17:37:47 +0000 Subject: [PATCH 0570/2308] Fix bug in let Nested lists in binding now work: (let* (a 1 b (* a (+ a 1)))) --- nasm/step3_env.asm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nasm/step3_env.asm b/nasm/step3_env.asm index 146c092d2e..f15f566af2 100644 --- a/nasm/step3_env.asm +++ b/nasm/step3_env.asm @@ -628,8 +628,10 @@ eval: .list_eval: mov rdi, r15 ; Environment + push r15 call eval_ast - + pop r15 + ; Check that the first element of the return is a function mov bl, BYTE [rax] and bl, content_mask @@ -643,10 +645,12 @@ eval: ; Call the function with the rest of the list in RSI push rax + push r15 mov rsi, [rax + Cons.cdr] ; Rest of list mov rdi, rbx ; Function object in RDI call [rbx + Cons.car] ; Call function ; Result in rax + pop r15 pop rsi ; eval'ed list push rax call release_cons From 145c38ae005126a9ad361084fc79d85430901426 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 1 Nov 2017 22:36:00 +0000 Subject: [PATCH 0571/2308] def! and let* working Tests pass (mostly...), and reference counting seems to be working; can run the same commands repeatedly without running out of memory. One test failing is (def! w (abc)) which is supposed to fail, but then abort the def!. Fixing this probably means adding error handling. --- nasm/env.asm | 14 ++++++- nasm/step3_env.asm | 91 ++++++++++++++++++++++++++++++++++++++++++++-- nasm/types.asm | 7 +++- 3 files changed, 104 insertions(+), 8 deletions(-) diff --git a/nasm/env.asm b/nasm/env.asm index a4e73e8d7f..0b30739e45 100644 --- a/nasm/env.asm +++ b/nasm/env.asm @@ -20,8 +20,10 @@ section .text ;; Create a new Environment ;; -;; Input: outer Environment in RSI. If zero, then nil outer. -;; +;; Input: outer Environment in RSI. +;; - If zero, then nil outer. +;; - If not zero, increments reference count +;; ;; Return a new Environment type in RAX ;; ;; Modifies registers: @@ -43,6 +45,11 @@ env_new: .set_outer: mov [rax + Cons.typecdr], BYTE content_pointer mov [rax + Cons.cdr], rsi + + ; increment reference counter of outer + mov rbx, rax ; because incref_object modifies rax + call incref_object + mov rax, rbx ret ;; Environment set @@ -53,6 +60,9 @@ env_new: ;; RDI - key [not modified] ;; RCX - value [not modified] ;; +;; Increments reference counts of key and value +;; if pointers to them are created +;; ;; Modifies registers: ;; R8 ;; R9 diff --git a/nasm/step3_env.asm b/nasm/step3_env.asm index f15f566af2..4437c94607 100644 --- a/nasm/step3_env.asm +++ b/nasm/step3_env.asm @@ -38,6 +38,22 @@ def_missing_arg_string: db "missing argument to def!",10 def_expecting_symbol_string: db "expecting symbol as first argument to def!",10 .len: equ $ - def_expecting_symbol_string + + +let_missing_bindings_string: db "let* missing bindings",10 +.len: equ $ - let_missing_bindings_string + +let_bindings_list_string: db "let* expected a list of bindings",10 +.len: equ $ - let_bindings_list_string + +let_bind_symbol_string: db "let* expected a symbol in bindings list",10 +.len: equ $ - let_bind_symbol_string + +let_bind_value_string: db "let* missing value in bindings list",10 +.len: equ $ - let_bind_value_string + +let_missing_body_string: db "let* missing body",10 +.len: equ $ - let_missing_body_string def_symbol: ISTRUC Array AT Array.type, db maltype_symbol @@ -508,11 +524,11 @@ eval: .let_symbol: ; Create a new environment - mov r11, rsi + mov r11, rsi ; Let form in R11 - mov rsi, r15 ; Outer env + mov rsi, r15 ; Outer env call env_new - mov r14, rax ; New environment in r14 + mov r14, rax ; New environment in R14 ; Second element should be the bindings @@ -597,6 +613,10 @@ eval: mov rdi, r13 ; key mov rcx, rax ; value call env_set + + ; Release the value + mov rsi, rcx ; The value + call release_object ; Check if there are more bindings mov al, BYTE [r12 + Cons.typecdr] @@ -608,15 +628,78 @@ eval: .let_done_binding: ; Done bindings. ; Evaluate next item in let* form in new environment + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_body + mov r11, [r11 + Cons.cdr] ; Now contains value to evaluate + ; Check type of the value + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + je .body_pointer + + ; Just a value, so copy + call alloc_cons + mov bl, BYTE [r11] + and bl, content_mask + mov [rax], BYTE bl ; set type + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx ; copy value + jmp .let_done +.body_pointer: + ; Evaluate using new environment - mov rax, r14 + mov rsi, r11 + mov rdi, r14 ; New environment + push r14 + call eval + pop r14 + +.let_done: + ; Release the environment + mov rsi, r14 + push rax + call release_object + pop rax ret .let_error_missing_bindings: + mov rsi, let_missing_bindings_string + mov rdx, let_missing_bindings_string.len + jmp .let_handle_error + .let_error_bindings_list: ; expected a list, got something else + mov rsi, let_bindings_list_string + mov rdx, let_bindings_list_string.len + jmp .let_handle_error + .let_error_bind_symbol: ; expected a symbol, got something else + mov rsi, let_bind_symbol_string + mov rdx, let_bind_symbol_string.len + jmp .let_handle_error + .let_error_bind_value: ; Missing value in binding list + mov rsi, let_bind_value_string + mov rdx, let_bind_value_string.len + jmp .let_handle_error + +.let_error_missing_body: ; Missing body to evaluate + mov rsi, let_missing_body_string + mov rdx, let_missing_body_string.len + jmp .let_handle_error + +.let_handle_error: + push rsi + push rdx + mov rsi, error_string + mov rdx, error_string.len + call print_rawstring ; print 'Error: ' + + pop rdx + pop rsi + call print_rawstring ; print message call alloc_cons mov [rax], BYTE maltype_nil diff --git a/nasm/types.asm b/nasm/types.asm index bcd8ef2639..d7eeb9e88b 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -146,12 +146,12 @@ error_cons_memory_limit: db "Error: Run out of memory for Cons objects. Increase ;; is free'd it is pushed onto the heap_x_free list. -%define heap_cons_limit 50 ; Number of cons objects which can be created +%define heap_cons_limit 100 ; Number of cons objects which can be created heap_cons_next: dd heap_cons_store ; Address of next cons in memory heap_cons_free: dq 0 ; Address of start of free list -%define heap_array_limit 50 ; Number of array objects which can be created +%define heap_array_limit 40 ; Number of array objects which can be created heap_array_next: dd heap_array_store heap_array_free: dq 0 @@ -1037,6 +1037,9 @@ map_find: ;; RDI - key [not modified] ;; RCX - value [not modified] ;; +;; If references are added to key or value, +;; then reference counts are incremented. +;; ;; Modifies registers: ;; R8 ;; R9 From 2c457d9bf2f38fa0836d003d38d028844aa923eb Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 1 Nov 2017 23:58:32 +0000 Subject: [PATCH 0572/2308] Added error handling, step 3 test passing * Added simple error handling, which just provides a way to reset the stack and jump to a handler. At the moment no attempt is made to reclaim memory or tidy up. * Some failing tests due to vectors not yet being supported. --- nasm/step3_env.asm | 207 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 175 insertions(+), 32 deletions(-) diff --git a/nasm/step3_env.asm b/nasm/step3_env.asm index 4437c94607..9ad1327d02 100644 --- a/nasm/step3_env.asm +++ b/nasm/step3_env.asm @@ -18,6 +18,9 @@ section .bss ;; Top-level (REPL) environment repl_env:resq 1 + +;; Error handler list +error_handler: resq 1 section .data @@ -69,6 +72,133 @@ IEND section .text +;; ---------------------------------------------- +;; +;; Error handling +;; +;; A handler consists of: +;; - A stack pointer address to reset to +;; - An address to jump to +;; - An optional data structure to pass +;; +;; When jumped to, an error handler will be given: +;; - the object thrown in RSI +;; - the optional data structure in RDI +;; + + +;; Add an error handler to the front of the list +;; +;; Input: RSI - Stack pointer +;; RDI - Address to jump to +;; RCX - Data structure. Set to zero for none. +;; If not zero, reference count incremented +;; +;; Modifies registers: +;; RAX +;; RBX +error_handler_push: + call alloc_cons + ; car will point to a list (stack, addr, data) + ; cdr will point to the previous handler + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov rbx, [error_handler] + cmp rbx, 0 ; Check if previous handler was zero + je .create_handler ; Zero, so leave null + ; Not zero, so create pointer to it + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rbx + + ; note: not incrementing reference count, since + ; we're replacing one reference with another +.create_handler: + mov [error_handler], rax ; new error handler + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.car], rax + ; Store stack pointer + mov [rax], BYTE (block_cons + container_list + content_function) + mov [rax + Cons.car], rsi ; stack pointer + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.typecdr], BYTE content_pointer + mov [rdx + Cons.cdr], rax + ; Store function pointer to jump to + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], rdi + + ; Check if there is an object to pass to handler + cmp rcx, 0 + je .done + + ; Set the final CDR to point to the object + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rcx + + mov rsi, rcx + call incref_object + +.done: + ret + + +;; Removes an error handler from the list +;; +;; Modifies registers: +;; RSI +;; RAX +;; RCX +error_handler_pop: + ; get the address + mov rsi, [error_handler] + cmp rsi, 0 + je .done ; Nothing to remove + + push rsi + mov rsi, [rsi + Cons.cdr] ; next handler + mov [error_handler], rsi + call incref_object ; needed because releasing soon + + pop rsi ; handler being removed + call release_cons + +.done: + ret + + +;; Throw an error +;; Object to pass to handler should be in RSI +error_throw: + ; Get the next error handler + mov rax, [error_handler] + cmp rax, 0 + je .no_handler + + ; Got a handler + mov rax, [rax + Cons.car] ; handler + mov rbx, [rax + Cons.car] ; stack pointer + mov rax, [rax + Cons.cdr] + mov rcx, [rax + Cons.car] ; function + mov rdi, [rax + Cons.cdr] ; data structure + + ; Reset stack + mov rsp, rbx + + ; Jump to the handler + jmp rcx + +.no_handler: + ; Print the object in RSI then quit + cmp rsi, 0 + je .done ; nothing to print + call pr_str + mov rsi, rax + call print_string +.done: + jmp quit_error + ;; ---------------------------------------------- ;; Evaluates a form ;; @@ -110,13 +240,14 @@ eval_ast: pop rsi je .done ; result in RAX - ; Not found, should raise an error + ; Not found, throw an error push rsi mov rsi, error_string mov rdx, error_string.len call print_rawstring ; print 'Error: ' pop rsi + push rsi mov edx, [rsi + Array.length] add rsi, Array.data call print_rawstring ; print symbol @@ -124,12 +255,9 @@ eval_ast: mov rsi, not_found_string mov rdx, not_found_string.len call print_rawstring ; print ' not found' - - ; Return nil - call alloc_cons - mov [rax], BYTE maltype_nil - mov [rax + Cons.typecdr], BYTE content_nil - ret + pop rsi + + jmp error_throw .list: ; Evaluate each element of the list @@ -470,6 +598,10 @@ eval: .def_pointer: ; A pointer, so evaluate + + ; This may throw an error, so define a handler + + push r8 ; the symbol push r15 ; Env mov rsi, [rsi + Cons.car] ; Pointer @@ -490,36 +622,29 @@ eval: ret .def_error_missing_arg: - mov rsi, error_string - mov rdx, error_string.len - call print_rawstring ; print 'Error: ' - mov rsi, def_missing_arg_string mov rdx, def_missing_arg_string.len - call print_rawstring - - ; Return nil - call alloc_cons - mov [rax], BYTE maltype_nil - mov [rax + Cons.typecdr], BYTE content_nil - ret - + jmp .def_handle_error .def_error_expecting_symbol: + mov rsi, def_expecting_symbol_string + mov rdx, def_expecting_symbol_string.len + jmp .def_handle_error + +.def_handle_error: + push rsi + push rdx mov rsi, error_string mov rdx, error_string.len call print_rawstring ; print 'Error: ' - - mov rsi, def_expecting_symbol_string - mov rdx, def_expecting_symbol_string.len - call print_rawstring - ; Return nil - call alloc_cons - mov [rax], BYTE maltype_nil - mov [rax + Cons.typecdr], BYTE content_nil - ret + pop rdx + pop rsi + call print_rawstring ; print message + xor rsi, rsi ; no object to throw + jmp error_throw ; No return + ; ----------------------------- .let_symbol: ; Create a new environment @@ -691,6 +816,8 @@ eval: jmp .let_handle_error .let_handle_error: + push r11 ; For printing later + push rsi push rdx mov rsi, error_string @@ -701,10 +828,8 @@ eval: pop rsi call print_rawstring ; print message - call alloc_cons - mov [rax], BYTE maltype_nil - mov [rax + Cons.typecdr], BYTE content_nil - ret + pop rsi ; let* form + jmp error_throw ; No return ; ----------------------------- @@ -769,6 +894,12 @@ _start: call core_environment ; Environment in RAX mov [repl_env], rax ; store in memory + + ; Set the error handler + mov rsi, rsp ; Stack pointer + mov rdi, .catch ; Address to jump to + xor rcx, rcx ; No data + call error_handler_push ; ----------------------------- ; Main loop @@ -827,3 +958,15 @@ _start: jmp quit +.catch: + ; Jumps here on error + + ; Check if an object was thrown + cmp rsi, 0 + je .catch_done_print ; nothing to print + call pr_str + mov rsi, rax + call print_string +.catch_done_print: + jmp .mainLoop ; Go back to the prompt + From c3c9f34833c66b378313389b1fd23200f40ec0fb Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 1 Nov 2017 20:38:42 -0500 Subject: [PATCH 0573/2308] [swift] add number?, fn?, and macro? --- swift/core.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/swift/core.swift b/swift/core.swift index 7e92b60017..52d662b3e2 100644 --- a/swift/core.swift +++ b/swift/core.swift @@ -61,6 +61,18 @@ private func fn_keywordQ(obj: MalVal) throws -> Bool { return is_keyword(obj) } +private func fn_numberQ(obj: MalVal) throws -> Bool { + return is_integer(obj) || is_float(obj) +} + +private func fn_functionQ(obj: MalVal) throws -> Bool { + return is_function(obj) +} + +private func fn_macroQ(obj: MalVal) throws -> Bool { + return is_macro(obj) +} + private func fn_prstr(args: MalVarArgs) throws -> String { let args_str_array = args.value.map { pr_str($0, true) } return args_str_array.joinWithSeparator(" ") @@ -688,6 +700,9 @@ let ns: [String: MalBuiltin.Signature] = [ "symbol?": { try unwrap_args($0, forFunction: fn_symbolQ) }, "keyword": { try unwrap_args($0, forFunction: fn_keyword) }, "keyword?": { try unwrap_args($0, forFunction: fn_keywordQ) }, + "number?": { try unwrap_args($0, forFunction: fn_numberQ) }, + "fn?": { try unwrap_args($0, forFunction: fn_functionQ) }, + "macro?": { try unwrap_args($0, forFunction: fn_macroQ) }, "pr-str": { try unwrap_args($0, forFunction: fn_prstr) }, "str": { try unwrap_args($0, forFunction: fn_str) }, From aa42fdcea3a41fda1b2b4ae823cac573a5901cb6 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 1 Nov 2017 21:45:28 -0500 Subject: [PATCH 0574/2308] [plsql] add number?, fn? and macro? --- plsql/core.sql | 6 ++++++ plsql/entrypoint.sh | 11 +++++++++-- plsql/types.sql | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/plsql/core.sql b/plsql/core.sql index ceaaaf32c5..1d4ad1f689 100644 --- a/plsql/core.sql +++ b/plsql/core.sql @@ -480,6 +480,9 @@ BEGIN WHEN fname = 'symbol?' THEN RETURN types.tf(M(a(1)).type_id = 7); WHEN fname = 'keyword' THEN RETURN keyword(M, a(1)); WHEN fname = 'keyword?' THEN RETURN types.tf(types.keyword_Q(M, a(1))); + WHEN fname = 'number?' THEN RETURN types.tf(types.number_Q(M, a(1))); + WHEN fname = 'fn?' THEN RETURN types.tf(types.function_Q(M, a(1))); + WHEN fname = 'macro?' THEN RETURN types.tf(types.macro_Q(M, a(1))); WHEN fname = 'pr-str' THEN RETURN pr_str(M, H, a); WHEN fname = 'str' THEN RETURN str(M, H, a); @@ -549,6 +552,9 @@ BEGIN 'symbol?', 'keyword', 'keyword?', + 'number?', + 'fn?', + 'macro?', 'pr-str', 'str', diff --git a/plsql/entrypoint.sh b/plsql/entrypoint.sh index ff4dd8d69b..549e7ce80c 100755 --- a/plsql/entrypoint.sh +++ b/plsql/entrypoint.sh @@ -1,7 +1,14 @@ #!/bin/bash -echo "Starting Oracle XE" -sudo /usr/sbin/startup.sh +case ${1} in +make*) + echo "Skipping Oracle XE startup" + ;; +*) + echo "Starting Oracle XE" + sudo /usr/sbin/startup.sh + ;; +esac if [ "${*}" ]; then exec "${@}" diff --git a/plsql/types.sql b/plsql/types.sql index a6fdc8ec9e..fb142c79c4 100644 --- a/plsql/types.sql +++ b/plsql/types.sql @@ -114,6 +114,9 @@ CREATE OR REPLACE PACKAGE types IS FUNCTION symbol(M IN OUT NOCOPY mal_table, name varchar) RETURN integer; FUNCTION keyword(M IN OUT NOCOPY mal_table, name varchar) RETURN integer; FUNCTION keyword_Q(M IN OUT NOCOPY mal_table, val integer) RETURN boolean; + FUNCTION number_Q(M IN OUT NOCOPY mal_table, val integer) RETURN boolean; + FUNCTION function_Q(M IN OUT NOCOPY mal_table, val integer) RETURN boolean; + FUNCTION macro_Q(M IN OUT NOCOPY mal_table, val integer) RETURN boolean; -- sequence functions FUNCTION seq(M IN OUT NOCOPY mal_table, @@ -384,6 +387,38 @@ BEGIN END IF; END; +FUNCTION number_Q(M IN OUT NOCOPY mal_table, val integer) RETURN boolean IS + str CLOB; +BEGIN + IF M(val).type_id IN (3,4) THEN + RETURN TRUE; + ELSE + RETURN FALSE; + END IF; +END; + +FUNCTION function_Q(M IN OUT NOCOPY mal_table, val integer) RETURN boolean IS + str CLOB; +BEGIN + IF M(val).type_id = 11 THEN + RETURN TRUE; + ELSIF M(val).type_id = 12 THEN + RETURN TREAT(M(val) AS mal_func_T).is_macro = 0; + ELSE + RETURN FALSE; + END IF; +END; + +FUNCTION macro_Q(M IN OUT NOCOPY mal_table, val integer) RETURN boolean IS + str CLOB; +BEGIN + IF M(val).type_id = 12 THEN + RETURN TREAT(M(val) AS mal_func_T).is_macro > 0; + ELSE + RETURN FALSE; + END IF; +END; + -- --------------------------------------------------------- -- sequence functions From 393d1140a3000dd9d7bff185e2439c642e1ad73b Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 1 Nov 2017 21:56:29 -0500 Subject: [PATCH 0575/2308] tests: move number?, fn? and macro? to optional --- tests/stepA_mal.mal | 50 +++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/tests/stepA_mal.mal b/tests/stepA_mal.mal index 7ba1c30372..1d3601f098 100644 --- a/tests/stepA_mal.mal +++ b/tests/stepA_mal.mal @@ -97,9 +97,30 @@ ;=>(1 2 3) ;; ------------------------------------------------------------------ -;; TODO move these to optional functionality after adding them to all -;; implementations + +;>>> soft=True +;>>> optional=True +;; +;; ------- Optional Functionality -------------- +;; ------- (Not needed for self-hosting) ------- + ;; +;; Testing string? function +(string? "") +;=>true +(string? 'abc) +;=>false +(string? "abc") +;=>true +(string? :abc) +;=>false +(string? (keyword "abc")) +;=>false +(string? 234) +;=>false +(string? nil) +;=>false + ;; Testing number? function (number? 123) ;=>true @@ -138,31 +159,6 @@ (macro? :+) ;=>false -;; ------------------------------------------------------------------ - -;>>> soft=True -;>>> optional=True -;; -;; ------- Optional Functionality -------------- -;; ------- (Not needed for self-hosting) ------- - -;; -;; Testing string? function -(string? "") -;=>true -(string? 'abc) -;=>false -(string? "abc") -;=>true -(string? :abc) -;=>false -(string? (keyword "abc")) -;=>false -(string? 234) -;=>false -(string? nil) -;=>false - ;; ;; Testing conj function From f4a8accef878020a68beb1c69ac4e3af8708cb11 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Sat, 14 Oct 2017 11:23:26 +0000 Subject: [PATCH 0576/2308] vhdl: Fix time-ms: Return milliseconds since last midnight UTC to fit in 32-bit integer We used to return milliseconds since 2000 but that number is too large to fit in a signed 32-bit integer. Instead we now return the number of milliseconds since last midnight UTC, which is between 0 and 86399999 (still fits in a 32-bit signed integer). --- vhdl/core.vhdl | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/vhdl/core.vhdl b/vhdl/core.vhdl index 00b18e0f1c..d228e246a8 100644 --- a/vhdl/core.vhdl +++ b/vhdl/core.vhdl @@ -226,17 +226,16 @@ package body core is assert false severity failure; end function gettimeofday; - -- Returns the number of milliseconds since 2000-01-01 00:00:00 UTC because - -- a standard VHDL integer is 32-bit and therefore cannot hold the number of + -- Returns the number of milliseconds since last midnight UTC because a + -- standard VHDL integer is 32-bit and therefore cannot hold the number of -- milliseconds since 1970-01-01. procedure fn_time_ms(args: inout mal_val_ptr; result: out mal_val_ptr; err: out mal_val_ptr) is variable tv: c_timeval; variable dummy: c_timezone; variable rc: integer; - constant utc_2000_01_01: c_seconds64 := 946684800 c_sec; -- UNIX time at 2000-01-01 00:00:00 UTC begin rc := gettimeofday(tv, dummy); - new_number(((tv.tv_sec - utc_2000_01_01) / 1 c_sec) * 1000 + (tv.tv_usec / 1000 c_usec), result); + new_number(((tv.tv_sec / 1 c_sec) mod 86400) * 1000 + (tv.tv_usec / 1000 c_usec), result); end procedure fn_time_ms; procedure fn_list(args: inout mal_val_ptr; result: out mal_val_ptr; err: out mal_val_ptr) is @@ -610,12 +609,6 @@ package body core is end procedure define_core_function; procedure define_core_functions(e: inout env_ptr) is - variable is_eof: boolean; - variable input_line, result, err: line; - variable sym: mal_val_ptr; - variable fn: mal_val_ptr; - variable outer: env_ptr; - variable repl_env: env_ptr; begin define_core_function(e, "="); define_core_function(e, "throw"); From 1d99c373a165cbb1284fa991528d4ac22683e69d Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 2 Nov 2017 23:06:09 +0000 Subject: [PATCH 0577/2308] Using macros to shorten source code * Definition of symbols with repetition of the name in .len replaced by a macro "static" in macros.mac * core_environment now uses a macro to add native functions, since this involved a lot of code repetition. --- nasm/core.asm | 140 +++++++++++------------------------------------ nasm/macros.mac | 17 ++++++ nasm/printer.asm | 17 +++--- nasm/types.asm | 26 ++++++--- 4 files changed, 72 insertions(+), 128 deletions(-) create mode 100644 nasm/macros.mac diff --git a/nasm/core.asm b/nasm/core.asm index b3194b28ba..71fd00f8ab 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -2,134 +2,56 @@ ;; ;; -section .data - -core_add_symbol: db "+" -.len: equ $ - core_add_symbol - -core_sub_symbol: db "-" -.len: equ $ - core_sub_symbol +%include "macros.mac" -core_mul_symbol: db "*" -.len: equ $ - core_mul_symbol - -core_div_symbol: db "/" -.len: equ $ - core_div_symbol - -core_equal_symbol: db "=" -.len: equ $ - core_equal_symbol +section .data -core_keys_symbol: db "keys" -.len: equ $ - core_keys_symbol + static core_add_symbol, db "+" + static core_sub_symbol, db "-" + static core_mul_symbol, db "*" + static core_div_symbol, db "/" + static core_equal_symbol, db "=" + static core_keys_symbol, db "keys" section .text -;; Create an Environment with core functions -;; -;; Returns Environment in RAX -;; -;; -core_environment: - ; Create the top-level environment - xor rsi, rsi ; Set outer to nil - call env_new ; in RAX - push rax - - ; ----------------- - ; add - mov rsi, core_add_symbol - mov edx, core_add_symbol.len - call raw_to_symbol ; Symbol in RAX - push rax - - mov rsi, core_add - call native_function ; Function in RAX - - mov rcx, rax ; value (function) - pop rdi ; key (symbol) - pop rsi ; environment - call env_set - - ; ----------------- - ; sub +;; Add a native function to the core environment +;; This is used in core_environment +%macro core_env_native 2 push rsi ; environment - mov rsi, core_sub_symbol - mov edx, core_sub_symbol.len + mov rsi, %1 + mov edx, %1.len call raw_to_symbol ; Symbol in RAX push rax - mov rsi, core_sub + mov rsi, %2 call native_function ; Function in RAX mov rcx, rax ; value (function) pop rdi ; key (symbol) pop rsi ; environment call env_set +%endmacro - - ; ----------------- - ; mul - push rsi ; environment - mov rsi, core_mul_symbol - mov edx, core_mul_symbol.len - call raw_to_symbol ; Symbol in RAX - push rax - - mov rsi, core_mul - call native_function ; Function in RAX - - mov rcx, rax ; value (function) - pop rdi ; key (symbol) - pop rsi ; environment - call env_set +;; Create an Environment with core functions +;; +;; Returns Environment in RAX +;; +;; +core_environment: + ; Create the top-level environment + xor rsi, rsi ; Set outer to nil + call env_new + mov rsi, rax ; Environment in RSI - ; ----------------- - ; div - push rsi ; environment - mov rsi, core_div_symbol - mov edx, core_div_symbol.len - call raw_to_symbol ; Symbol in RAX - push rax - - mov rsi, core_div - call native_function ; Function in RAX - - mov rcx, rax ; value (function) - pop rdi ; key (symbol) - pop rsi ; environment - call env_set + core_env_native core_add_symbol, core_add + core_env_native core_sub_symbol, core_sub + core_env_native core_mul_symbol, core_mul + core_env_native core_div_symbol, core_div - ; ----------------- - ; equal (=) - push rsi ; environment - mov rsi, core_equal_symbol - mov edx, core_equal_symbol.len - call raw_to_symbol ; Symbol in RAX - push rax - - mov rsi, core_equal_p - call native_function ; Function in RAX - - mov rcx, rax ; value (function) - pop rdi ; key (symbol) - pop rsi ; environment - call env_set + core_env_native core_equal_symbol, core_equal_p - ; ----------------- - ; keys - push rsi ; environment - mov rsi, core_keys_symbol - mov edx, core_keys_symbol.len - call raw_to_symbol ; Symbol in RAX - push rax - - mov rsi, core_keys - call native_function ; Function in RAX - - mov rcx, rax ; value (function) - pop rdi ; key (symbol) - pop rsi ; environment - call env_set + core_env_native core_keys_symbol, core_keys ; ----------------- ; Put the environment in RAX diff --git a/nasm/macros.mac b/nasm/macros.mac new file mode 100644 index 0000000000..2e2d0aeaba --- /dev/null +++ b/nasm/macros.mac @@ -0,0 +1,17 @@ +;; Some useful macros + +%ifndef MACROS_MAC +%define MACROS_MAC + +;; Define a static data value +;; +;; static label value +;; +%macro static 2+ + %1: %2 + %1.len: equ $ - %1 +%endmacro + + +%endif + diff --git a/nasm/printer.asm b/nasm/printer.asm index e7348eff25..647a4d8406 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -2,18 +2,15 @@ ;;; ;;; +%include "macros.mac" + section .data -unknown_type_string: db "#" -.len: equ $ - unknown_type_string - -unknown_value_string: db "#" -.len: equ $ - unknown_value_string -function_type_string: db "#" -.len: equ $ - function_type_string - -nil_value_string: db "nil" -.len: equ $ - nil_value_string + ; Constant strings for printing + static unknown_type_string, db "#" + static unknown_value_string, db "#" + static function_type_string, db "#" + static nil_value_string, db "nil" section .text diff --git a/nasm/types.asm b/nasm/types.asm index d7eeb9e88b..bf777b99bc 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -23,22 +23,25 @@ ;; 1 1 - Array memory block ;; ;; Container type [3 bits]: -;; 0 0 - Value (single boxed value for Cons blocks, vector for Array blocks). +;; 0 0 - Value (single boxed value for Cons blocks, multiple values for Array blocks). ;; 2 1 - List (value followed by pointer). Only for Cons blocks ;; 4 2 - Symbol (special char array). Only for Array blocks -;; 6 3 - Keyword +;; 6 3 - Keyword. Only for Array blocks ;; 8 4 - Map ;; 10 5 - Function +;; 12 6 - Macro +;; 14 7 - Vector ;; ;; Content type [4 bits]: ;; 0 0 - Nil -;; 16 1 - Bool +;; 16 1 - True ;; 32 2 - Char ;; 48 3 - Int ;; 64 4 - Float ;; 80 5 - Pointer (memory address) ;; 96 6 - Function (instruction address) ;; 112 7 - Empty (distinct from Nil) +;; 208 8 - False ;; ;; ;; These represent MAL data types as follows: @@ -48,12 +51,12 @@ ;; integer Cons Value Int ;; symbol Array Symbol Char ;; list Cons List Any +;; vector Cons Vector Any ;; nil Cons Value Nil -;; true Cons Value Bool (1) -;; false Cons Value Bool (0) +;; true Cons Value True +;; false Cons Value False ;; string Array Value Char ;; keyword Array Keyword Char -;; vector Array Value Int/Float ;; hash-map Cons Map Alternate key, values ;; atom Cons Value Pointer ;; @@ -100,16 +103,19 @@ ENDSTRUC %define container_keyword 6 %define container_map 8 %define container_function 10 - +%define container_macro 12 +%define container_vector 14 + ;; Content type %define content_nil 0 -%define content_bool 16 +%define content_true 16 %define content_char 32 %define content_int 48 %define content_float 64 %define content_pointer 80 ; Memory pointer (to Cons or Array) %define content_function 96 ; Function pointer %define content_empty 112 +%define content_false 208 ;; Common combinations for MAL types %define maltype_integer (block_cons + container_value + content_int) @@ -119,7 +125,9 @@ ENDSTRUC %define maltype_empty_list (block_cons + container_list + content_empty) %define maltype_empty_map (block_cons + container_map + content_empty) %define maltype_function (block_cons + container_function + content_function) - +%define maltype_macro (block_cons + container_macro + content_function) +%define maltype_true (block_cons + container_value + content_true) +%define maltype_false (block_cons + container_value + content_false) ;; ------------------------------------------ section .data From 1e0fdbaa3216fc7681f64191775fb9e133997c30 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 3 Nov 2017 00:17:11 +0000 Subject: [PATCH 0578/2308] Adding support for vectors All step2 tests pass --- nasm/printer.asm | 97 +++++++++++++++++++++++++++++- nasm/reader.asm | 142 ++++++++++++++++++++++++++++++++++++++------ nasm/step2_eval.asm | 92 +++++++++++++++++++++++++++- nasm/step3_env.asm | 98 +++++++++++++++++++++++++++++- nasm/types.asm | 17 +++--- 5 files changed, 411 insertions(+), 35 deletions(-) diff --git a/nasm/printer.asm b/nasm/printer.asm index 647a4d8406..2fd2e61de9 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -97,7 +97,7 @@ pr_str: ; ---------------------------- .not_string: - ; Now test the container type (value, list) + ; Now test the container type (value, list, map, vector) mov ch, cl @@ -113,6 +113,9 @@ pr_str: cmp ch, container_map je .map + cmp ch, container_vector + je .vector + cmp ch, container_function je .function @@ -358,6 +361,98 @@ pr_str: mov rax, rsi ret + ; -------------------------------- +.vector: + + mov r12, rsi ; Input vector + + call string_new ; String in rax + mov r13, rax ; Output string in r13 + + ; Put '[' onto string + mov rsi, rax + mov cl, '[' + call string_append_char + + ; loop through vector +.vector_loop: + + ; Extract values and print + + mov rsi, r12 + mov cl, BYTE [rsi] ; Get type + + ; Check if it's a pointer (address) + mov ch, cl + and ch, content_mask + cmp ch, content_pointer + je .vector_loop_pointer + + cmp ch, content_empty + je .vector_check_end + + ; A value (nil, int etc. or function) + xor cl, container_vector ; Remove vector type -> value + mov BYTE [rsi], cl + + push r13 + push r12 + call pr_str ; String in rax + pop r12 + pop r13 + + mov cl, BYTE [r12] + or cl, container_vector ; Restore vector type + mov BYTE [r12], cl + jmp .vector_loop_got_str +.vector_loop_pointer: + mov rsi, [rsi + Cons.car] ; Address of object + push r13 + push r12 + call pr_str ; String in rax + pop r12 + pop r13 + +.vector_loop_got_str: + ; concatenate strings in rax and rsi + mov rsi, r13 ; Output string + mov rdx, rax ; String to be copied + + push rsi ; Save output string + push rax ; save temporary string + call string_append_string + + ; Release the string + pop rsi ; Was in rax, temporary string + call release_array + + pop rsi ; restore output string +.vector_check_end: + ; Check if this is the end of the vector + mov cl, BYTE [r12 + Cons.typecdr] + cmp cl, content_nil + je .vector_finished + + ; More left in the vector + + ; Add space between values + mov cl, ' ' + mov rsi, r13 + call string_append_char + + ; Get next Cons + mov r12, [r12 + Cons.cdr] + jmp .vector_loop + +.vector_finished: + ; put ']' at the end of the string + mov cl, ']' + mov rsi, r13 + call string_append_char + + mov rax, rsi + ret + ; -------------------------------- .function: mov rsi, function_type_string diff --git a/nasm/reader.asm b/nasm/reader.asm index 6058784043..99cb97d8b0 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -1,29 +1,20 @@ +%include "macros.mac" + section .data ;; Reader macro strings - -quote_symbol_string: db "quote" -.len: equ $ - quote_symbol_string - -quasiquote_symbol_string: db "quasiquote" -.len: equ $ - quasiquote_symbol_string -unquote_symbol_string: db "unquote" -.len: equ $ - unquote_symbol_string - -splice_unquote_symbol_string: db "splice-unquote" -.len: equ $ - splice_unquote_symbol_string - -deref_symbol_string: db "deref" -.len: equ $ - deref_symbol_string + static quote_symbol_string, db "quote" + static quasiquote_symbol_string, db "quasiquote" + static unquote_symbol_string, db "unquote" + static splice_unquote_symbol_string, db "splice-unquote" + static deref_symbol_string, db "deref" + ;; Error message strings -error_string_unexpected_end: db "Error: Unexpected end of input. Could be a missing )", 10 -.len: equ $ - error_string_unexpected_end - -error_string_bracket_not_brace: db "Error: Expecting '}' but got ')'" -.len: equ $ - error_string_bracket_not_brace +static error_string_unexpected_end, db "Error: Unexpected end of input. Could be a missing )", 10 +static error_string_bracket_not_brace, db "Error: Expecting '}' but got ')'" section .text @@ -98,6 +89,12 @@ read_str: cmp cl, '}' ; cl tested in map reader je .return_nil + + cmp cl, '[' + je .vector_start + + cmp cl, ']' ; cl tested in vector reader + je .return_nil cmp cl, 39 ; quote ' je .handle_quote @@ -327,6 +324,113 @@ read_str: ret + ; -------------------------------- + +.vector_start: + + ; Get the first value + ; Note that we call rather than jmp because the first + ; value needs to be treated differently. There's nothing + ; to append to yet... + call .read_loop + + ; rax now contains the first object + cmp cl, ']' ; Check if it was end of vector + jne .vector_has_contents + mov cl, 0 ; so ']' doesn't propagate to nested vectors + ; Set vector to empty + mov [rax], BYTE maltype_empty_vector + ret ; Returns 'nil' given "()" +.vector_has_contents: + ; If this is a Cons then use it + ; If not, then need to allocate a Cons + mov cl, BYTE [rax] + mov ch, cl + and ch, (block_mask + container_mask) ; Tests block and container type + jz .vector_is_value + + ; If here then not a simple value, so need to allocate + ; a Cons object + + ; Start new vector + push rax + call alloc_cons ; Address in rax + pop rbx + mov [rax], BYTE (block_cons + container_vector + content_pointer) + mov [rax + Cons.car], rbx + ; Now have Cons in RAX, containing pointer to object as car + +.vector_is_value: + ; Cons in RAX + ; Make sure it's marked as a vector + mov cl, BYTE [rax] + or cl, container_vector + mov [rax], BYTE cl + + mov r12, rax ; Start of current vector + mov r13, rax ; Set current vector + cmp r15, 0 ; Test if first vector + jne .vector_read_loop + mov r15, rax ; Save the first, for unwinding + +.vector_read_loop: + ; Repeatedly get the next value in the vector + ; (which may be other vectors) + ; until we get a ']' token + + push r12 + push r13 + call .read_loop ; object in rax + pop r13 + pop r12 + + cmp cl, ']' ; Check if it was end of vector + je .vector_done ; Have nil object in rax + + ; Test if this is a Cons value + mov cl, BYTE [rax] + mov ch, cl + and ch, (block_mask + container_mask) ; Tests block and container type + jz .vector_loop_is_value + + ; If here then not a simple value, so need to allocate + ; a Cons object + + ; Start new vector + push rax + call alloc_cons ; Address in rax + pop rbx + mov [rax], BYTE (block_cons + container_vector + content_pointer) + mov [rax + Cons.car], rbx + ; Now have Cons in RAX, containing pointer to object as car + +.vector_loop_is_value: + ; Cons in RAX + + ; Make sure it's marked as a vector + mov cl, BYTE [rax] + or cl, container_vector + mov [rax], BYTE cl + + ; Append to r13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax ; Set current vector + + jmp .vector_read_loop + +.vector_done: + ; Release nil object in rax + mov rsi, rax + call release_cons + + ; Terminate the vector + mov [r13 + Cons.typecdr], BYTE content_nil + mov QWORD [r13 + Cons.cdr], QWORD 0 + mov rax, r12 ; Start of current vector + + ret + ; -------------------------------- .handle_quote: ; Turn 'a into (quote a) diff --git a/nasm/step2_eval.asm b/nasm/step2_eval.asm index 639169dc16..98025a0208 100644 --- a/nasm/step2_eval.asm +++ b/nasm/step2_eval.asm @@ -56,11 +56,14 @@ eval_ast: cmp ah, container_map je .map - ; Not a list or a map + cmp ah, container_vector + je .vector + + ; Not a list, map or vector cmp ah, container_symbol je .symbol - ; Not a symbol, list or map + ; Not a symbol, list, map or vector call incref_object ; Increment reference count mov rax, rsi @@ -294,6 +297,91 @@ eval_ast: mov rax, r12 ret + ; ------------------------------ +.vector: + ; Evaluate each element of the vector + ; + xor r8, r8 ; The vector to return + ; r9 contains head of vector + +.vector_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .vector_pointer + + ; A value, so copy + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_vector) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .vector_append + +.vector_pointer: + ; Vector element is a pointer to something + push rsi + push r8 + push r9 + mov rsi, [rsi + Cons.car] ; Get the address + call eval ; Evaluate it, result in rax + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .vector_append_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_vector + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .vector_append + +.vector_append_value: + or bl, container_vector + mov [rax], BYTE bl + +.vector_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .vector_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .vector_next + +.vector_first: + mov r8, rax + mov r9, rax + ; fall through to .vector_next + +.vector_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .vector_done ; finished vector + mov rsi, [rsi + Cons.cdr] ; next in vector + jmp .vector_loop + +.vector_done: + mov rax, r8 ; Return the vector + ret + ; --------------------- .done: ret diff --git a/nasm/step3_env.asm b/nasm/step3_env.asm index 9ad1327d02..f740748138 100644 --- a/nasm/step3_env.asm +++ b/nasm/step3_env.asm @@ -220,11 +220,14 @@ eval_ast: cmp ah, container_map je .map - ; Not a list or a map + cmp ah, container_vector + je .vector + + ; Not a list, map or vector cmp ah, container_symbol je .symbol - ; Not a symbol, list or map + ; Not a symbol, list, map or vector call incref_object ; Increment reference count mov rax, rsi @@ -258,7 +261,8 @@ eval_ast: pop rsi jmp error_throw - + + ; ------------------------------ .list: ; Evaluate each element of the list ; @@ -478,6 +482,94 @@ eval_ast: mov rax, r12 ret + ; ------------------------------ +.vector: + ; Evaluate each element of the vector + ; + xor r8, r8 ; The vector to return + ; r9 contains head of vector + +.vector_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .vector_pointer + + ; A value, so copy + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_vector) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .vector_append + +.vector_pointer: + ; Vector element is a pointer to something + push rsi + push r8 + push r9 + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Get the address + mov rdi, r15 + call eval ; Evaluate it, result in rax + pop r15 + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .vector_append_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_vector + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .vector_append + +.vector_append_value: + or bl, container_vector + mov [rax], BYTE bl + +.vector_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .vector_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .vector_next + +.vector_first: + mov r8, rax + mov r9, rax + ; fall through to .vector_next + +.vector_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .vector_done ; finished vector + mov rsi, [rsi + Cons.cdr] ; next in vector + jmp .vector_loop + +.vector_done: + mov rax, r8 ; Return the vector + ret + ; --------------------- .done: ret diff --git a/nasm/types.asm b/nasm/types.asm index bf777b99bc..785019a8fd 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -60,6 +60,8 @@ ;; hash-map Cons Map Alternate key, values ;; atom Cons Value Pointer ;; + +%include "macros.mac" ;; Cons type. ;; Used to store either a single value with type information @@ -124,6 +126,7 @@ ENDSTRUC %define maltype_nil (block_cons + container_value + content_nil) %define maltype_empty_list (block_cons + container_list + content_empty) %define maltype_empty_map (block_cons + container_map + content_empty) +%define maltype_empty_vector (block_cons + container_vector + content_empty) %define maltype_function (block_cons + container_function + content_function) %define maltype_macro (block_cons + container_macro + content_function) %define maltype_true (block_cons + container_value + content_true) @@ -134,15 +137,9 @@ section .data ;; Fixed strings for printing - -error_msg_print_string: db "Error in print string",10 -.len: equ $ - error_msg_print_string - -error_array_memory_limit: db "Error: Run out of memory for Array objects. Increase heap_array_limit.",10 -.len: equ $ - error_array_memory_limit - -error_cons_memory_limit: db "Error: Run out of memory for Cons objects. Increase heap_cons_limit.",10 -.len: equ $ - error_cons_memory_limit + static error_msg_print_string, db "Error in print string",10 + static error_array_memory_limit, db "Error: Run out of memory for Array objects. Increase heap_array_limit.",10 + static error_cons_memory_limit, db "Error: Run out of memory for Cons objects. Increase heap_cons_limit.",10 ;; ------------------------------------------ ;; Memory management @@ -159,7 +156,7 @@ error_cons_memory_limit: db "Error: Run out of memory for Cons objects. Increase heap_cons_next: dd heap_cons_store ; Address of next cons in memory heap_cons_free: dq 0 ; Address of start of free list -%define heap_array_limit 40 ; Number of array objects which can be created +%define heap_array_limit 50 ; Number of array objects which can be created heap_array_next: dd heap_array_store heap_array_free: dq 0 From c301f1c4ca93e1a4b59b3c7298617655a633731d Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 3 Nov 2017 07:28:55 +0000 Subject: [PATCH 0579/2308] Adding read/print of nil, true and false Previously these were read as symbols, not their own type. Now the reader recognises these as special and converts them to the correct internal type. --- nasm/macros.mac | 17 ++++++++++++ nasm/printer.asm | 22 ++++++++++++++- nasm/reader.asm | 68 +++++++++++++++++++++++++++++++++++++++++++--- nasm/step3_env.asm | 45 ++++++++++-------------------- 4 files changed, 116 insertions(+), 36 deletions(-) diff --git a/nasm/macros.mac b/nasm/macros.mac index 2e2d0aeaba..0122116a32 100644 --- a/nasm/macros.mac +++ b/nasm/macros.mac @@ -12,6 +12,23 @@ %1.len: equ $ - %1 %endmacro +;; Define a symbol which can be compared against +;; +;; static_symbol name, string +;; +;; Example: +;; +;; static_symbol def_symbol, 'def!' +;; +%macro static_symbol 2 + %strlen slen %2 ; length of string + + %1: ISTRUC Array + AT Array.type, db maltype_symbol + AT Array.length, dd slen + AT Array.data, db %2 + IEND +%endmacro %endif diff --git a/nasm/printer.asm b/nasm/printer.asm index 2fd2e61de9..cdcdea7542 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -11,6 +11,8 @@ section .data static unknown_value_string, db "#" static function_type_string, db "#" static nil_value_string, db "nil" + static true_value_string, db "true" + static false_value_string, db "false" section .text @@ -131,9 +133,15 @@ pr_str: and ch, content_mask jz .value_nil - cmp ch, 48 + cmp ch, content_int je .value_int + cmp ch, content_true + je .value_true + + cmp ch, content_false + je .value_false + mov rsi, unknown_value_string mov edx, unknown_value_string.len call raw_to_string ; Puts a String in RAX @@ -145,6 +153,18 @@ pr_str: mov edx, nil_value_string.len call raw_to_string ret + +.value_true: + mov rsi, true_value_string + mov edx, true_value_string.len + call raw_to_string + ret + +.value_false: + mov rsi, false_value_string + mov edx, false_value_string.len + call raw_to_string + ret ; -------------------------------- .value_int: diff --git a/nasm/reader.asm b/nasm/reader.asm index 99cb97d8b0..d4273f4c90 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -13,8 +13,14 @@ section .data ;; Error message strings -static error_string_unexpected_end, db "Error: Unexpected end of input. Could be a missing )", 10 -static error_string_bracket_not_brace, db "Error: Expecting '}' but got ')'" + static error_string_unexpected_end, db "Error: Unexpected end of input. Could be a missing )", 10 + static error_string_bracket_not_brace, db "Error: Expecting '}' but got ')'" + +;; Symbols for comparison + + static_symbol nil_symbol, 'nil' + static_symbol true_symbol, 'true' + static_symbol false_symbol, 'false' section .text @@ -75,8 +81,8 @@ read_str: je .finished cmp cl, '"' ; A string. Array object in RAX je .finished - cmp cl, 's' - je .finished + cmp cl, 's' ; A symbol + je .symbol cmp cl, '(' je .list_start @@ -535,7 +541,61 @@ read_str: pop r9 pop r8 jmp .wrap_next_object ; From there the same as handle_quote + + ; -------------------------------- +.symbol: + ; symbol is in RAX + ; Some symbols are have their own type + ; - nil, true, false + ; + + mov rsi, rax + mov rdi, nil_symbol + push rsi + call compare_char_array + pop rsi + cmp rax, 0 + je .symbol_nil + + mov rdi, true_symbol + push rsi + call compare_char_array + pop rsi + cmp rax, 0 + je .symbol_true + + mov rdi, false_symbol + push rsi + call compare_char_array + pop rsi + cmp rax, 0 + je .symbol_false + + ; not a special symbol, so return + mov rax, rsi + ret + +.symbol_nil: + ; symbol in rsi not needed + call release_array + call alloc_cons + mov [rax], BYTE maltype_nil ; a nil type + ret + +.symbol_true: + call release_array + + call alloc_cons + mov [rax], BYTE maltype_true + ret + +.symbol_false: + call release_array + + call alloc_cons + mov [rax], BYTE maltype_false + ret ; -------------------------------- .finished: diff --git a/nasm/step3_env.asm b/nasm/step3_env.asm index f740748138..ebbd2ad7aa 100644 --- a/nasm/step3_env.asm +++ b/nasm/step3_env.asm @@ -27,48 +27,31 @@ section .data ;; ------------------------------------------ ;; Fixed strings for printing -prompt_string: db 10,"user> " ; The string to print at the prompt -.len: equ $ - prompt_string + static prompt_string, db 10,"user> " ; The string to print at the prompt -error_string: db 27,'[31m',"Error",27,'[0m',": " -.len: equ $ - error_string + static error_string, db 27,'[31m',"Error",27,'[0m',": " -not_found_string: db " not found.",10 -.len: equ $ - not_found_string + static not_found_string, db " not found.",10 -def_missing_arg_string: db "missing argument to def!",10 -.len: equ $ - def_missing_arg_string + static def_missing_arg_string, db "missing argument to def!",10 -def_expecting_symbol_string: db "expecting symbol as first argument to def!",10 -.len: equ $ - def_expecting_symbol_string + static def_expecting_symbol_string, db "expecting symbol as first argument to def!",10 + static let_missing_bindings_string, db "let* missing bindings",10 -let_missing_bindings_string: db "let* missing bindings",10 -.len: equ $ - let_missing_bindings_string + static let_bindings_list_string, db "let* expected a list of bindings",10 -let_bindings_list_string: db "let* expected a list of bindings",10 -.len: equ $ - let_bindings_list_string + static let_bind_symbol_string, db "let* expected a symbol in bindings list",10 -let_bind_symbol_string: db "let* expected a symbol in bindings list",10 -.len: equ $ - let_bind_symbol_string + static let_bind_value_string, db "let* missing value in bindings list",10 -let_bind_value_string: db "let* missing value in bindings list",10 -.len: equ $ - let_bind_value_string + static let_missing_body_string, db "let* missing body",10 -let_missing_body_string: db "let* missing body",10 -.len: equ $ - let_missing_body_string - -def_symbol: ISTRUC Array -AT Array.type, db maltype_symbol -AT Array.length, dd 4 -AT Array.data, db 'def!' -IEND + +;; Symbols used for comparison -let_symbol: ISTRUC Array -AT Array.type, db maltype_symbol -AT Array.length, dd 4 -AT Array.data, db 'let*' -IEND + static_symbol def_symbol, 'def!' + static_symbol let_symbol, 'let*' section .text From ef1d93042808eb06fc85f15a1dab826a53661ee4 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 3 Nov 2017 22:57:19 +0000 Subject: [PATCH 0580/2308] Bug fix in list and vector eval If a value was returned from an expression then it would be modified by being inserted into the list or vector. If this value was the result of a symbol lookup then this would modify the symbol value. let* form now takes either vectors or lists All stage 3 tests now pass --- nasm/step3_env.asm | 71 +++++++++++++++++++++++++++++++++++++--------- nasm/types.asm | 6 ++++ 2 files changed, 63 insertions(+), 14 deletions(-) diff --git a/nasm/step3_env.asm b/nasm/step3_env.asm index ebbd2ad7aa..bdc9e017b4 100644 --- a/nasm/step3_env.asm +++ b/nasm/step3_env.asm @@ -39,7 +39,7 @@ section .data static let_missing_bindings_string, db "let* missing bindings",10 - static let_bindings_list_string, db "let* expected a list of bindings",10 + static let_bindings_list_string, db "let* expected a list or vector of bindings",10 static let_bind_symbol_string, db "let* expected a symbol in bindings list",10 @@ -259,7 +259,8 @@ eval_ast: cmp ah, content_pointer je .list_pointer - ; A value, so copy + ; A value in RSI, so copy + call alloc_cons mov bl, BYTE [rsi] and bl, content_mask @@ -290,7 +291,7 @@ eval_ast: mov bh, bl and bh, (block_mask + container_mask) cmp bh, (block_cons + container_value) - je .list_append + je .list_eval_value ; Not a value, so need a pointer to it push rax @@ -298,8 +299,29 @@ eval_ast: mov [rax], BYTE (block_cons + container_list + content_pointer) pop rbx ; Address to point to mov [rax + Cons.car], rbx - ; Fall through to .list_append + jmp .list_append + +.list_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_list) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi + ; Fall through to .list_append .list_append: ; In RAX @@ -510,7 +532,7 @@ eval_ast: mov bh, bl and bh, (block_mask + container_mask) cmp bh, (block_cons + container_value) - je .vector_append_value + je .vector_eval_value ; Not a value, so need a pointer to it push rax @@ -520,9 +542,25 @@ eval_ast: mov [rax + Cons.car], rbx jmp .vector_append -.vector_append_value: - or bl, container_vector - mov [rax], BYTE bl +.vector_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_vector) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi .vector_append: ; In RAX @@ -745,14 +783,19 @@ eval: mov r12, [r11 + Cons.car] ; should be bindings list mov al, BYTE [r12] and al, (block_mask + container_mask) + ; Can be either a list or vector cmp al, block_cons + container_list - jne .let_error_bindings_list + je .let_bind_loop + cmp al, block_cons + container_vector + je .let_bind_loop + ; Not a list or vector + jmp .let_error_bindings_list + +.let_bind_loop: ; R12 now contains a list with an even number of items ; The first should be a symbol, then a value to evaluate - -.let_bind_loop: - + ; Get the symbol mov al, BYTE [r12] and al, content_mask @@ -851,7 +894,7 @@ eval: .body_pointer: ; Evaluate using new environment - mov rsi, r11 + mov rsi, [r11 + Cons.car] ; Object pointed to mov rdi, r14 ; New environment push r14 call eval @@ -870,7 +913,7 @@ eval: mov rdx, let_missing_bindings_string.len jmp .let_handle_error -.let_error_bindings_list: ; expected a list, got something else +.let_error_bindings_list: ; expected a list or vector, got something else mov rsi, let_bindings_list_string mov rdx, let_bindings_list_string.len jmp .let_handle_error diff --git a/nasm/types.asm b/nasm/types.asm index 785019a8fd..99b7b094af 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -302,6 +302,12 @@ alloc_cons: ;; Decrements the reference count of the cons in RSI ;; If the count reaches zero then push the cons ;; onto the free list +;; +;; Modifies registers: +;; RAX +;; RBX +;; RCX +;; release_cons: mov ax, WORD [rsi + Cons.refcount] dec ax From a721b04c240fb11a677f34d6f1bd43998f167b64 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 3 Nov 2017 23:36:52 +0000 Subject: [PATCH 0581/2308] do form working Calls eval rather than eval_ast, since this allows things like multiple def! forms inside a do form. --- nasm/step4_if_fn_do.asm | 1193 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1193 insertions(+) create mode 100644 nasm/step4_if_fn_do.asm diff --git a/nasm/step4_if_fn_do.asm b/nasm/step4_if_fn_do.asm new file mode 100644 index 0000000000..14d5807e86 --- /dev/null +++ b/nasm/step4_if_fn_do.asm @@ -0,0 +1,1193 @@ +;; +;; nasm -felf64 step4_if_fn_do.asm && ld step4_if_fn_do.o && ./a.out +;; +;; Calling convention: Address of input is in RSI +;; Address of return value is in RAX +;; + +global _start + +%include "types.asm" ; Data types, memory +%include "env.asm" ; Environment type +%include "system.asm" ; System calls +%include "reader.asm" ; String -> Data structures +%include "core.asm" ; Core functions +%include "printer.asm" ; Data structures -> String + +section .bss + +;; Top-level (REPL) environment +repl_env:resq 1 + +;; Error handler list +error_handler: resq 1 + +section .data + +;; ------------------------------------------ +;; Fixed strings for printing + + static prompt_string, db 10,"user> " ; The string to print at the prompt + + static error_string, db 27,'[31m',"Error",27,'[0m',": " + + static not_found_string, db " not found.",10 + + static def_missing_arg_string, db "missing argument to def!",10 + + static def_expecting_symbol_string, db "expecting symbol as first argument to def!",10 + + static let_missing_bindings_string, db "let* missing bindings",10 + + static let_bindings_list_string, db "let* expected a list or vector of bindings",10 + + static let_bind_symbol_string, db "let* expected a symbol in bindings list",10 + + static let_bind_value_string, db "let* missing value in bindings list",10 + + static let_missing_body_string, db "let* missing body",10 + + +;; Symbols used for comparison + + static_symbol def_symbol, 'def!' + static_symbol let_symbol, 'let*' + static_symbol do_symbol, 'do' + static_symbol if_symbol, 'if' + static_symbol fn_symbol, 'fn*' + +section .text + +;; ---------------------------------------------- +;; +;; Error handling +;; +;; A handler consists of: +;; - A stack pointer address to reset to +;; - An address to jump to +;; - An optional data structure to pass +;; +;; When jumped to, an error handler will be given: +;; - the object thrown in RSI +;; - the optional data structure in RDI +;; + + +;; Add an error handler to the front of the list +;; +;; Input: RSI - Stack pointer +;; RDI - Address to jump to +;; RCX - Data structure. Set to zero for none. +;; If not zero, reference count incremented +;; +;; Modifies registers: +;; RAX +;; RBX +error_handler_push: + call alloc_cons + ; car will point to a list (stack, addr, data) + ; cdr will point to the previous handler + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov rbx, [error_handler] + cmp rbx, 0 ; Check if previous handler was zero + je .create_handler ; Zero, so leave null + ; Not zero, so create pointer to it + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rbx + + ; note: not incrementing reference count, since + ; we're replacing one reference with another +.create_handler: + mov [error_handler], rax ; new error handler + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.car], rax + ; Store stack pointer + mov [rax], BYTE (block_cons + container_list + content_function) + mov [rax + Cons.car], rsi ; stack pointer + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.typecdr], BYTE content_pointer + mov [rdx + Cons.cdr], rax + ; Store function pointer to jump to + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], rdi + + ; Check if there is an object to pass to handler + cmp rcx, 0 + je .done + + ; Set the final CDR to point to the object + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rcx + + mov rsi, rcx + call incref_object + +.done: + ret + + +;; Removes an error handler from the list +;; +;; Modifies registers: +;; RSI +;; RAX +;; RCX +error_handler_pop: + ; get the address + mov rsi, [error_handler] + cmp rsi, 0 + je .done ; Nothing to remove + + push rsi + mov rsi, [rsi + Cons.cdr] ; next handler + mov [error_handler], rsi + call incref_object ; needed because releasing soon + + pop rsi ; handler being removed + call release_cons + +.done: + ret + + +;; Throw an error +;; Object to pass to handler should be in RSI +error_throw: + ; Get the next error handler + mov rax, [error_handler] + cmp rax, 0 + je .no_handler + + ; Got a handler + mov rax, [rax + Cons.car] ; handler + mov rbx, [rax + Cons.car] ; stack pointer + mov rax, [rax + Cons.cdr] + mov rcx, [rax + Cons.car] ; function + mov rdi, [rax + Cons.cdr] ; data structure + + ; Reset stack + mov rsp, rbx + + ; Jump to the handler + jmp rcx + +.no_handler: + ; Print the object in RSI then quit + cmp rsi, 0 + je .done ; nothing to print + call pr_str + mov rsi, rax + call print_string +.done: + jmp quit_error + +;; ---------------------------------------------- +;; Evaluates a form +;; +;; Inputs: RSI Form to evaluate +;; RDI Environment +;; +eval_ast: + mov r15, rdi ; Save Env in r15 + + ; Check the type + mov al, BYTE [rsi] + + ; Check if this is a list + mov ah, al + and ah, container_mask + cmp ah, container_list + je .list + + cmp ah, container_map + je .map + + cmp ah, container_vector + je .vector + + ; Not a list, map or vector + cmp ah, container_symbol + je .symbol + + ; Not a symbol, list, map or vector + call incref_object ; Increment reference count + + mov rax, rsi + ret + +.symbol: + ; look in environment + push rsi + xchg rsi, rdi + ; symbol is the key in rdi + ; Environment in rsi + call env_get + pop rsi + je .done ; result in RAX + + ; Not found, throw an error + push rsi + mov rsi, error_string + mov rdx, error_string.len + call print_rawstring ; print 'Error: ' + + pop rsi + push rsi + mov edx, [rsi + Array.length] + add rsi, Array.data + call print_rawstring ; print symbol + + mov rsi, not_found_string + mov rdx, not_found_string.len + call print_rawstring ; print ' not found' + pop rsi + + jmp error_throw + + ; ------------------------------ +.list: + ; Evaluate each element of the list + ; + xor r8, r8 ; The list to return + ; r9 contains head of list + +.list_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .list_pointer + + ; A value in RSI, so copy + + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_list) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .list_append + +.list_pointer: + ; List element is a pointer to something + push rsi + push r8 + push r9 + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Get the address + mov rdi, r15 + call eval ; Evaluate it, result in rax + pop r15 + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .list_eval_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_list + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .list_append + +.list_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_list) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi + + ; Fall through to .list_append +.list_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .list_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .list_next + +.list_first: + mov r8, rax + mov r9, rax + ; fall through to .list_next + +.list_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .list_done ; finished list + mov rsi, [rsi + Cons.cdr] ; next in list + jmp .list_loop + +.list_done: + mov rax, r8 ; Return the list + ret + + ; --------------------- +.map: + ; Create a new map, evaluating all the values + + ; Check if the map is empty + cmp al, maltype_empty_map + jne .map_not_empty + + ; map empty. Just return it + call incref_object + mov rax, rsi + ret + +.map_not_empty: + + mov r10, rsi ; input in R10 + xor r12, r12 ; New map in r12 + + ; Now loop through each key-value pair + ; NOTE: This method relies on the implementation + ; of map as a list + +.map_loop: + ; Copy the key + call alloc_cons ; New Cons in RAX + + mov bl, [r10 + Cons.typecar] ; Type in BL + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] ; Value in RCX + mov [rax + Cons.car], rcx + + ; Check the type of the key + and bl, content_mask + cmp bl, content_pointer + jne .map_got_key ; a value + + ; a pointer, so increment reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.map_got_key: + cmp r12,0 + jne .append_key + + ; First key + mov r12, rax + mov r13, rax + jmp .map_value + +.append_key: + ; Appending to previous value in r13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + +.map_value: + ; Check that we have a value + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_error_missing_value + mov r10, [r10 + Cons.cdr] + + ; Now got value in r10 + + ; Check the type of the value + mov bl, [r10 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .map_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r10 + Cons.typecar] + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .map_got_value +.map_value_pointer: + ; A pointer, so need to evaluate + push r10 ; Input + push r12 ; start of result + push r13 ; Current head of result + push r15 ; Env + mov rsi, [r10 + Cons.car] ; Get the address + mov rdi, r15 + call eval ; Evaluate it, result in rax + pop r15 + pop r13 + pop r12 + pop r10 + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + + jne .map_eval_pointer + + ; A value, so just change the type to a map + and bl, content_mask + add bl, (block_cons + container_map) + mov [rax], BYTE bl + jmp .map_got_value + +.map_eval_pointer: + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_map + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + +.map_got_value: + ; Append RAX to list in R13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + + ; Check if there's another key + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_done ; finished map + mov r10, [r10 + Cons.cdr] ; next in map + jmp .map_loop + +.map_done: + mov rax, r12 + ret + +.map_error_missing_value: + mov rax, r12 + ret + + ; ------------------------------ +.vector: + ; Evaluate each element of the vector + ; + xor r8, r8 ; The vector to return + ; r9 contains head of vector + +.vector_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .vector_pointer + + ; A value, so copy + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_vector) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .vector_append + +.vector_pointer: + ; Vector element is a pointer to something + push rsi + push r8 + push r9 + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Get the address + mov rdi, r15 + call eval ; Evaluate it, result in rax + pop r15 + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .vector_eval_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_vector + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .vector_append + +.vector_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_vector) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi + +.vector_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .vector_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .vector_next + +.vector_first: + mov r8, rax + mov r9, rax + ; fall through to .vector_next + +.vector_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .vector_done ; finished vector + mov rsi, [rsi + Cons.cdr] ; next in vector + jmp .vector_loop + +.vector_done: + mov rax, r8 ; Return the vector + ret + + ; --------------------- +.done: + ret + + + +;; Comparison of symbols for eval function +;; Compares the symbol in RSI with specified symbol +;; Preserves RSI and RBX +;; Modifies RDI +%macro eval_cmp_symbol 1 + push rsi + push rbx + mov rsi, rbx + mov rdi, %1 + call compare_char_array + pop rbx + pop rsi + cmp rax, 0 +%endmacro + +;; ---------------------------------------------------- +;; Evaluates a form +;; +;; Input: RSI Form to evaluate +;; RDI Environment +;; +;; Returns: Result in RAX +;; +eval: + mov r15, rdi ; Env + + ; Check type + mov al, BYTE [rsi] + cmp al, maltype_empty_list + je .empty_list ; empty list, return unchanged + + and al, container_mask + cmp al, container_list + je .list + + ; Not a list. Evaluate and return + call eval_ast + ret + + ; -------------------- +.list: + ; A list + + ; Check if the first element is a symbol + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .list_eval + + mov rbx, [rsi + Cons.car] + mov al, BYTE [rbx] + cmp al, maltype_symbol + jne .list_eval + + ; Is a symbol, address in RBX + + ; Compare against special form symbols + + eval_cmp_symbol def_symbol ; def! + je .def_symbol + + eval_cmp_symbol let_symbol ; let* + je .let_symbol + + eval_cmp_symbol do_symbol ; do + je .do_symbol + + eval_cmp_symbol if_symbol ; if + je .if_symbol + + eval_cmp_symbol fn_symbol ; fn + je .fn_symbol + + ; Unrecognised + jmp .list_eval + +.def_symbol: + ; Define a new symbol in current environment + + ; Next item should be a symbol + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Now should have a symbol + + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + jne .def_error_expecting_symbol + mov r8, [rsi + Cons.car] ; Symbol (?) + + mov al, BYTE [r8] + cmp al, maltype_symbol + jne .def_error_expecting_symbol + + ; R8 now contains a symbol + + ; expecting a value or pointer next + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a pointer + mov al, BYTE [rsi] + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .def_pointer + + ; A value, so copy + push rax + call alloc_cons + pop rbx ; BL now contains type + and bl, content_mask + add bl, (block_cons + container_value) + mov [rax], BYTE bl + mov rcx, [rsi + Cons.car] + mov [rax + Cons.car], rcx + mov rsi, rax + + jmp .def_got_value + +.def_pointer: + ; A pointer, so evaluate + + ; This may throw an error, so define a handler + + + push r8 ; the symbol + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Pointer + mov rdi, r15 + call eval + mov rsi, rax + pop r15 + pop r8 + +.def_got_value: + ; Symbol in R8, value in RSI + mov rdi, r8 ; key (symbol) + mov rcx, rsi ; Value + mov rsi, r15 ; Environment + call env_set + + mov rax, rcx ; Return the value + ret + +.def_error_missing_arg: + mov rsi, def_missing_arg_string + mov rdx, def_missing_arg_string.len + jmp .def_handle_error + +.def_error_expecting_symbol: + mov rsi, def_expecting_symbol_string + mov rdx, def_expecting_symbol_string.len + jmp .def_handle_error + +.def_handle_error: + push rsi + push rdx + mov rsi, error_string + mov rdx, error_string.len + call print_rawstring ; print 'Error: ' + + pop rdx + pop rsi + call print_rawstring ; print message + + xor rsi, rsi ; no object to throw + jmp error_throw ; No return + + ; ----------------------------- +.let_symbol: + ; Create a new environment + + mov r11, rsi ; Let form in R11 + + mov rsi, r15 ; Outer env + call env_new + mov r14, rax ; New environment in R14 + + ; Second element should be the bindings + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_bindings + mov r11, [r11 + Cons.cdr] + + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .let_error_bindings_list + + mov r12, [r11 + Cons.car] ; should be bindings list + mov al, BYTE [r12] + and al, (block_mask + container_mask) + ; Can be either a list or vector + cmp al, block_cons + container_list + je .let_bind_loop + cmp al, block_cons + container_vector + je .let_bind_loop + + ; Not a list or vector + jmp .let_error_bindings_list + +.let_bind_loop: + ; R12 now contains a list with an even number of items + ; The first should be a symbol, then a value to evaluate + + ; Get the symbol + mov al, BYTE [r12] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_symbol + + mov r13, [r12 + Cons.car] ; Symbol (?) + mov al, BYTE [r13] + cmp al, maltype_symbol + jne .let_error_bind_symbol + + ; R13 now contains a symbol to bind + ; The next item in the bindings list (R12) + ; should be a value or expression to evaluate + + mov al, BYTE [r12 + Cons.typecdr] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_value + mov r12, [r12 + Cons.cdr] + + ; got value in R12 + + ; Check the type of the value + mov bl, [r12 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .let_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r12 + Cons.typecar] + and bl, content_mask + ;or bl, (block_cons + container_value) ; 0 + mov [rax + Cons.typecar], bl + mov rcx, [r12 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .let_got_value + +.let_value_pointer: + ; A pointer, so need to evaluate + push r11 ; let* form list + push r12 ; Position in bindings list + push r13 ; symbol to bind + push r14 ; new environment + mov rsi, [r12 + Cons.car] ; Get the address + mov rdi, r14 + call eval ; Evaluate it, result in rax + pop r14 + pop r13 + pop r12 + pop r11 + +.let_got_value: + + mov rsi, r14 ; Env + mov rdi, r13 ; key + mov rcx, rax ; value + call env_set + + ; Release the value + mov rsi, rcx ; The value + call release_object + + ; Check if there are more bindings + mov al, BYTE [r12 + Cons.typecdr] + cmp al, content_pointer + jne .let_done_binding + mov r12, [r12 + Cons.cdr] ; Next + jmp .let_bind_loop + +.let_done_binding: + ; Done bindings. + ; Evaluate next item in let* form in new environment + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_body + mov r11, [r11 + Cons.cdr] ; Now contains value to evaluate + ; Check type of the value + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + je .body_pointer + + ; Just a value, so copy + call alloc_cons + mov bl, BYTE [r11] + and bl, content_mask + mov [rax], BYTE bl ; set type + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx ; copy value + jmp .let_done + +.body_pointer: + ; Evaluate using new environment + + mov rsi, [r11 + Cons.car] ; Object pointed to + mov rdi, r14 ; New environment + push r14 + call eval + pop r14 + +.let_done: + ; Release the environment + mov rsi, r14 + push rax + call release_object + pop rax + ret + +.let_error_missing_bindings: + mov rsi, let_missing_bindings_string + mov rdx, let_missing_bindings_string.len + jmp .let_handle_error + +.let_error_bindings_list: ; expected a list or vector, got something else + mov rsi, let_bindings_list_string + mov rdx, let_bindings_list_string.len + jmp .let_handle_error + +.let_error_bind_symbol: ; expected a symbol, got something else + mov rsi, let_bind_symbol_string + mov rdx, let_bind_symbol_string.len + jmp .let_handle_error + +.let_error_bind_value: ; Missing value in binding list + mov rsi, let_bind_value_string + mov rdx, let_bind_value_string.len + jmp .let_handle_error + +.let_error_missing_body: ; Missing body to evaluate + mov rsi, let_missing_body_string + mov rdx, let_missing_body_string.len + jmp .let_handle_error + +.let_handle_error: + push r11 ; For printing later + + push rsi + push rdx + mov rsi, error_string + mov rdx, error_string.len + call print_rawstring ; print 'Error: ' + + pop rdx + pop rsi + call print_rawstring ; print message + + pop rsi ; let* form + jmp error_throw ; No return + + ; ----------------------------- + +.do_symbol: + mov r11, rsi ; do form in RSI + ; Environment in R15 + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .do_no_body + mov r11, [r11 + Cons.cdr] + +.do_symbol_loop: + ; Check if this is a value or pointer + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + jne .do_body_value + + ; A pointer, so evaluate + push r15 + push r11 + mov rsi, [r11 + Cons.car] ; Form + mov rdi, r15 ; Env + call eval ; Result in RAX + pop r11 + pop r15 + + ; Check if there is another form + mov bl, BYTE [r11 + Cons.typecdr] + cmp bl, content_pointer + jne .do_done ; No more, so finished + + ; Another form. Discard the result of the last eval + mov rsi, rax + call release_object +.do_next: + + mov r11, [r11 + Cons.cdr] ; Next in list + + jmp .do_symbol_loop + +.do_done: + ret ; Return result in RAX + +.do_body_value: + + ; Got a value in R11. + ; If this is the last form then return, + ; but if not then can ignore + + mov bl, BYTE [r11 + Cons.typecdr] + and bl, block_mask + content_mask + cmp bl, content_pointer + jne .do_body_value_return + + ; Not the last, so ignore + jmp .do_next + +.do_body_value_return: + ; Got a value as last form. Copy and return + + push rax + call alloc_cons + pop rbx ; type in BL + mov [rax], BYTE bl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + ret + +.do_no_body: + ; No expressions to evaluate. Return nil + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret + + ; ----------------------------- + +.if_symbol: + + + ; ----------------------------- + +.fn_symbol: + + + ; ----------------------------- + +.list_eval: + + mov rdi, r15 ; Environment + push r15 + call eval_ast + pop r15 + + ; Check that the first element of the return is a function + mov bl, BYTE [rax] + and bl, content_mask + cmp bl, content_pointer + jne .list_not_function + + mov rbx, [rax + Cons.car] ; Get the address + mov cl, BYTE [rbx] + cmp cl, maltype_function + jne .list_not_function + + ; Call the function with the rest of the list in RSI + push rax + push r15 + mov rsi, [rax + Cons.cdr] ; Rest of list + mov rdi, rbx ; Function object in RDI + call [rbx + Cons.car] ; Call function + ; Result in rax + pop r15 + pop rsi ; eval'ed list + push rax + call release_cons + pop rax + ret + +.list_not_function: + ; Not a function. Probably an error + ret + +.empty_list: + mov rax, rsi + ret + +;; Prints the result +print: + mov rax, rsi ; Return the input + ret + +;; Read-Eval-Print in sequence +rep_seq: + call read_str + mov rsi, rax ; Output of read into input of eval + call eval + mov rsi, rax ; Output of eval into input of print + call print + mov rsi, rax ; Return value + ret + + +_start: + ; Create and print the core environment + call core_environment ; Environment in RAX + + mov [repl_env], rax ; store in memory + + ; Set the error handler + mov rsi, rsp ; Stack pointer + mov rdi, .catch ; Address to jump to + xor rcx, rcx ; No data + call error_handler_push + + ; ----------------------------- + ; Main loop + +.mainLoop: + ; print the prompt + mov rdx, prompt_string.len ; number of bytes + mov rsi, prompt_string ; address of raw string to output + call print_rawstring + + call read_line + + ; Check if we have a zero-length string + cmp DWORD [rax+Array.length], 0 + je .mainLoopEnd + + push rax ; Save address of the input string + + ; Put into read_str + mov rsi, rax + call read_str + push rax ; Save AST + + ; Eval + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment + call eval + push rax ; Save result + + ; Put into pr_str + mov rsi, rax + call pr_str + push rax ; Save output string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from pr_str + pop rsi + call release_array + + ; Release result of eval + pop rsi + call release_object + + ; Release the object from read_str + pop rsi + call release_object ; Could be Cons or Array + + ; Release the input string + pop rsi + call release_array + + jmp .mainLoop +.mainLoopEnd: + + jmp quit + +.catch: + ; Jumps here on error + + ; Check if an object was thrown + cmp rsi, 0 + je .catch_done_print ; nothing to print + call pr_str + mov rsi, rax + call print_string +.catch_done_print: + jmp .mainLoop ; Go back to the prompt + From d76bd06b50ae861477ac006e2bbbd0f416dc4b09 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sat, 4 Nov 2017 12:07:17 +0100 Subject: [PATCH 0582/2308] c: Make use of pkg-config for libffi flags --- c/Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/c/Makefile b/c/Makefile index 7aa44449b3..98ceee60b7 100644 --- a/c/Makefile +++ b/c/Makefile @@ -27,6 +27,9 @@ OTHER_HDRS = types.h readline.h reader.h printer.h core.h interop.h GLIB_CFLAGS ?= $(shell pkg-config --cflags glib-2.0) GLIB_LDFLAGS ?= $(shell pkg-config --libs glib-2.0) +FFI_CFLAGS ?= $(shell pkg-config libffi --cflags) +FFI_LDFLAGS ?= $(shell pkg-config libffi --libs) + ifeq ($(shell uname -s),Darwin) CFLAGS +=-DOSX=1 @@ -45,8 +48,8 @@ CFLAGS += -DUSE_GC=1 LDFLAGS += -lgc endif -CFLAGS += $(GLIB_CFLAGS) -LDFLAGS += -l$(RL_LIBRARY) $(GLIB_LDFLAGS) -ldl -lffi +CFLAGS += $(GLIB_CFLAGS) $(FFI_CFLAGS) +LDFLAGS += -l$(RL_LIBRARY) $(GLIB_LDFLAGS) $(FFI_LDFLAGS) -ldl ##################### From 77872467f66036dab7b2927a19296d574ae02269 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 4 Nov 2017 23:45:54 +0000 Subject: [PATCH 0583/2308] if and fn* forms working * Added if and fn* handling in eval * New Env constructor env_new_bind which takes a list of binding symbols and expressions * Macro print_str_mac to shorten raw string printing * Functions implemented as a list, storing an address to call, outer environment, bindings and body * apply_fn handles user-defined functions. Calls env_new_bind, evaluates the body of the function, and returns the result Simple tests seem to work: user> (def! f (fn* (a b) (+ a b))) # user> (f 1 2) 3 --- nasm/env.asm | 129 ++++++++++++++++++-- nasm/macros.mac | 8 ++ nasm/step4_if_fn_do.asm | 256 +++++++++++++++++++++++++++++++++++++--- nasm/types.asm | 9 +- 4 files changed, 374 insertions(+), 28 deletions(-) diff --git a/nasm/env.asm b/nasm/env.asm index 0b30739e45..8a87c0fdb4 100644 --- a/nasm/env.asm +++ b/nasm/env.asm @@ -1,4 +1,6 @@ - + +%include "macros.mac" + ;; ------------------------------------------------------------ ;; Environment type ;; @@ -6,16 +8,17 @@ ;; current environment, and CDR points to the outer environment ;; ;; ( {} {} ... ) + +section .data +;; Symbols used for comparison + static_symbol env_symbol, '*env*' -section .data - -env_symbol: ISTRUC Array -AT Array.type, db maltype_symbol -AT Array.length, dd 5 -AT Array.data, db '*env*' -IEND +;; Error message strings + static env_binds_error_string, db "Expecting symbol in binds list",10 + static env_binds_missing_string, db "Missing expression in bind",10 + section .text ;; Create a new Environment @@ -52,6 +55,116 @@ env_new: mov rax, rbx ret +;; Create a new environment using a binding list +;; +;; Input: RSI - Outer environment +;; RDI - Binds, a list of symbols +;; RCX - Exprs, a list of values to bind each symbol to +;; +;; Modifies registers +;; RBX +;; R8 +;; R9 +;; R10 +;; R11 +;; R12 +;; R13 +env_new_bind: + mov r11, rdi ; binds list in R11 + mov r12, rcx ; expr list in R12 + + call env_new + mov r13, rax ; New environment in R13 + +.bind_loop: + ; Check the type in the bind list + mov bl, BYTE [r11] + and bl, content_mask + cmp bl, content_pointer + jne .bind_not_symbol + + mov rdi, [r11 + Cons.car] ; Symbol object? + mov bl, BYTE [rdi] + cmp bl, maltype_symbol + jne .bind_not_symbol + + ; RDI now contains a symbol + ; Check the type in expr + + mov bl, BYTE [r12] + mov bh, bl + and bh, content_mask + cmp bh, content_pointer + je .value_pointer + + ; A value. Need to remove the container type + xchg bl,bh + mov [r12], BYTE bl + xchg bl,bh + mov rcx, r12 ; Value + mov rsi, r13 ; Env + push rbx + call env_set + pop rbx + ; Restore original type + mov [r12], BYTE bl + jmp .next + +.value_pointer: + ; A pointer to something, so just pass address to env_set + mov rcx, [r12 + Cons.car] + mov rsi, r13 + call env_set + ; Fall through to next +.next: + ; Check if there is a next + mov bl, BYTE [r11 + Cons.typecdr] + cmp bl, content_pointer + jne .done + + ; Got another symbol + mov r11, [r11 + Cons.cdr] ; Next symbol + + ; Check if there's an expression to bind to + mov bl, BYTE [r12 + Cons.typecdr] + cmp bl, content_pointer + jne .bind_missing_expr + + mov r12, [r12 + Cons.cdr] ; Next expression + jmp .bind_loop +.done: + mov rax, r13 ; Env + ret + +.bind_not_symbol: ; Expecting a symbol + push r11 ; Binds list + + ; Release the environment + mov rsi, r13 + call release_object + + print_str_mac error_string ; print 'Error: ' + + print_str_mac env_binds_error_string + + pop rsi ; Throw binds list + jmp error_throw + +.bind_missing_expr: + push r11 ; Binds list + + ; Release the environment + mov rsi, r13 + call release_object + + print_str_mac error_string ; print 'Error: ' + + print_str_mac env_binds_missing_string + + pop rsi ; Throw binds list + jmp error_throw + + ;; Environment set ;; ;; Sets a key-value pair in an environment diff --git a/nasm/macros.mac b/nasm/macros.mac index 0122116a32..5f0d5fda90 100644 --- a/nasm/macros.mac +++ b/nasm/macros.mac @@ -29,6 +29,14 @@ AT Array.data, db %2 IEND %endmacro + +;; Macro for printing raw string +;; +%macro print_str_mac 1 + mov rsi, %1 ; String address + mov rdx, %1.len ; Length of string + call print_rawstring +%endmacro %endif diff --git a/nasm/step4_if_fn_do.asm b/nasm/step4_if_fn_do.asm index 14d5807e86..d6b1651a1e 100644 --- a/nasm/step4_if_fn_do.asm +++ b/nasm/step4_if_fn_do.asm @@ -231,19 +231,15 @@ eval_ast: ; Not found, throw an error push rsi - mov rsi, error_string - mov rdx, error_string.len - call print_rawstring ; print 'Error: ' - + print_str_mac error_string ; print 'Error: ' + pop rsi push rsi mov edx, [rsi + Array.length] add rsi, Array.data call print_rawstring ; print symbol - - mov rsi, not_found_string - mov rdx, not_found_string.len - call print_rawstring ; print ' not found' + + print_str_mac not_found_string ; print ' not found' pop rsi jmp error_throw @@ -763,9 +759,7 @@ eval: .def_handle_error: push rsi push rdx - mov rsi, error_string - mov rdx, error_string.len - call print_rawstring ; print 'Error: ' + print_str_mac error_string ; print 'Error: ' pop rdx pop rsi @@ -954,9 +948,8 @@ eval: push rsi push rdx - mov rsi, error_string - mov rdx, error_string.len - call print_rawstring ; print 'Error: ' + + print_str_mac error_string ; print 'Error: ' pop rdx pop rsi @@ -1045,12 +1038,211 @@ eval: ; ----------------------------- .if_symbol: + mov r11, rsi ; if form in R11 + ; Environment in R15 + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .if_no_condition + + mov r11, [r11 + Cons.cdr] ; Should be a condition + + ; Check if value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .if_cond_value + + ; A pointer, so evaluate + + push r15 + push r11 + mov rsi, [r11 + Cons.car] ; Form + mov rdi, r15 ; Env + call eval ; Result in RAX + pop r11 + pop r15 + + ; Get type + mov bl, BYTE [rax] + + ; release value + push rbx + mov rsi, rax + call release_object + pop rbx + + ; Check type + cmp bl, maltype_nil + je .if_false + cmp bl, maltype_false + je .if_false + + jmp .if_true + +.if_cond_value: + + ; A value + cmp al, content_nil + je .if_false + cmp al, content_false + je .if_false + + jmp .if_true +.if_false: + ; Skip the next item + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .if_nil + + mov r11, [r11 + Cons.cdr] + +.if_true: + ; Get the next item in the list and evaluate it + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .if_nil + + mov r11, [r11 + Cons.cdr] + + ; Check if value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + je .if_got_pointer + +.if_got_value: + ; copy value in r11 + call alloc_cons + mov bl, BYTE [r11] + and bl, content_mask + mov [rax], BYTE bl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + ret + +.if_got_pointer: + mov rsi, [r11 + Cons.car] ; Form + mov rdi, r15 ; Env + call eval + ret + +.if_no_condition: ; just (if) without a condition + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret + +.if_nil: + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret ; ----------------------------- .fn_symbol: + mov r11, rsi ; fn form in R11 + ; Environment in R15 + ; Get the binds and body of the function + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .fn_empty + + mov r11, [r11 + Cons.cdr] + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .fn_binds_not_list + + mov r12, [r11 + Cons.car] ; Should be binds list + mov al, BYTE [r12] + and al, (block_mask + container_mask) + cmp al, (block_cons + container_list) + je .fn_got_binds ; Can be list + cmp al, (block_cons + container_vector) + je .fn_got_binds ; or vector + jmp .fn_binds_not_list + +.fn_got_binds: + + ; Next get the body of the function + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .fn_no_body + + mov r11, [r11 + Cons.cdr] + ; Check value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .fn_got_body ; Body in r11 + mov r11, [r11 + Cons.car] +.fn_got_body: + + ; Now put into function type + ; Addr is "apply_fn", the address to call + ; Env in R15 + ; Binds in R12 + ; Body in R11 + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_function) + mov rbx, apply_fn + mov [rax + Cons.car], rbx ; Address of apply function + mov [rax + Cons.typecdr], BYTE content_pointer + + mov r13, rax ; Return list in R13 + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r15 ; Environment + mov [rax + Cons.typecdr], BYTE content_pointer + + mov [r13 + Cons.cdr], rax ; Append to list + mov r14, rax + + push rax + mov rsi, r15 + call incref_object + pop rax + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r12 ; Binds list + mov [rax + Cons.typecdr], BYTE content_pointer + + mov [r14 + Cons.cdr], rax ; Append to list + mov r14, rax + + push rax + mov rsi, r12 + call incref_object + pop rax + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r11 ; Body of function + + mov [r14 + Cons.cdr], rax + + mov rsi, r11 + call incref_object + + mov rax, r13 + ret + +.fn_empty: +.fn_binds_not_list: +.fn_no_body: + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret + ; ----------------------------- @@ -1093,6 +1285,38 @@ eval: .empty_list: mov rax, rsi ret + +;; Applies a user-defined function +;; +;; Input: RSI - Arguments to bind +;; RDI - Function object +apply_fn: + push rsi + ; Extract values from the list in RDI + mov rax, [rdi + Cons.cdr] + mov rsi, [rax + Cons.car] ; Env + mov rax, [rax + Cons.cdr] + mov rdi, [rax + Cons.car] ; Binds + pop rcx ; Exprs + + push rax + call env_new_bind + mov rdi, rax ; New environment in RDI + pop rax ; Function object + + mov rax, [rax + Cons.cdr] + mov rsi, [rax + Cons.car] ; Body + + push rdi ; Environment + call eval + pop rsi + + ; Release the environment + push rax + call release_object + pop rax + + ret ;; Prints the result print: @@ -1127,9 +1351,7 @@ _start: .mainLoop: ; print the prompt - mov rdx, prompt_string.len ; number of bytes - mov rsi, prompt_string ; address of raw string to output - call print_rawstring + print_str_mac prompt_string call read_line diff --git a/nasm/types.asm b/nasm/types.asm index 99b7b094af..0fd89e7cee 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -1160,7 +1160,7 @@ map_set: ; Here a Cons object mov bh, bl and bh, container_mask - cmp bl, container_value + cmp bh, container_value jne .set_value_pointer ; Not a simple value, so point to it ; A value, so copy mov rcx, [r10 + Cons.car] @@ -1357,8 +1357,11 @@ map_keys: ;; ;; Functions are consist of a list ;; - First car is the function address to call -;; -;; ( addr ) +;; - Second is the environment +;; - Third is the binds list +;; - Fourth is the body of the function +;; +;; ( addr env binds body ) ;; ;; From 2a6439a5a654009d4f9ea89b95602a8631393e67 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sun, 5 Nov 2017 22:48:27 +0000 Subject: [PATCH 0584/2308] Adding core functions * Added list, list?, empty?, count * Working on pr-str and prn. Complete, but get a strange segfault when printing lists. Commenting out a "push" makes a difference... * Function call now passes empty list if no arguments are given. --- nasm/core.asm | 258 +++++++++++++++++++++++++++++++++++++++- nasm/printer.asm | 1 + nasm/step4_if_fn_do.asm | 18 ++- 3 files changed, 272 insertions(+), 5 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 71fd00f8ab..7fca59efb5 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -6,13 +6,29 @@ section .data +;; Symbols for comparison static core_add_symbol, db "+" static core_sub_symbol, db "-" static core_mul_symbol, db "*" static core_div_symbol, db "/" + + static core_listp_symbol, db "list?" + static core_emptyp_symbol, db "empty?" + static core_equal_symbol, db "=" - static core_keys_symbol, db "keys" + + static core_count_symbol, db "count" + static core_keys_symbol, db "keys" + + static core_list_symbol, db "list" + + static core_pr_str_symbol, db "pr-str" + static core_prn_symbol, db "prn" +;; Strings + + static core_emptyp_error_string, db "empty? expects a list, vector or map",10 + static core_count_error_string, db "count expects a list or vector",10 section .text ;; Add a native function to the core environment @@ -48,10 +64,18 @@ core_environment: core_env_native core_sub_symbol, core_sub core_env_native core_mul_symbol, core_mul core_env_native core_div_symbol, core_div - + + core_env_native core_listp_symbol, core_listp + core_env_native core_emptyp_symbol, core_emptyp + core_env_native core_count_symbol, core_count + core_env_native core_equal_symbol, core_equal_p - + core_env_native core_keys_symbol, core_keys + core_env_native core_list_symbol, core_list + + core_env_native core_pr_str_symbol, core_pr_str + core_env_native core_prn_symbol, core_prn ; ----------------- ; Put the environment in RAX @@ -176,6 +200,115 @@ core_equal_p: mov [rax + Cons.typecdr], BYTE content_nil ret +;; Test if a given object is a list +;; Input list in RSI +;; Returns true or false in RAX +core_listp: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .false ; Should be a pointer to a list + + mov rax, [rsi + Cons.car] + mov al, BYTE [rax] + and al, (block_mask + container_mask) + cmp al, (block_cons + container_list) + jne .false + + ; Is a list, return true + call alloc_cons + mov [rax], BYTE maltype_true + ret + +.false: + call alloc_cons + mov [rax], BYTE maltype_false + ret + +;; Test if the given list, vector or map is empty +core_emptyp: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .error ; Expected a container + mov rax, [rsi + Cons.car] + mov al, BYTE [rax] + cmp al, maltype_empty_list + je .true + cmp al, maltype_empty_vector + je .true + cmp al, maltype_empty_map + je .true + + ; false + call alloc_cons + mov [rax], BYTE maltype_false + ret +.true: + call alloc_cons + mov [rax], BYTE maltype_true + ret +.error: + push rsi + print_str_mac error_string + print_str_mac core_emptyp_error_string + pop rsi + jmp error_throw + +;; Count the number of elements in given list or vector +core_count: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .error ; Expected a container + + mov rsi, [rsi + Cons.car] + mov al, BYTE [rsi] + mov ah, al + and ah, (block_mask + container_mask) + cmp ah, (block_cons + container_list) + je .start_count + cmp ah, (block_cons + container_vector) + je .start_count + + jmp .error ; Not a list or vector + +.start_count: + + xor rbx,rbx + mov ah, al + and ah, content_mask + cmp ah, content_empty + je .done ; Empty list or vector + +.loop: + inc rbx + + ; Check if there's another + mov al, [rsi + Cons.typecdr] + cmp al, content_pointer + jne .done + + mov rsi, [rsi + Cons.cdr] + jmp .loop + +.done: ; Count is in RBX + + push rbx + call alloc_cons + pop rbx + mov [rax], BYTE maltype_integer + mov [rax + Cons.car], rbx + ret + +.error: + push rsi + print_str_mac error_string + print_str_mac core_count_error_string + pop rsi + jmp error_throw + + ;; Given a map, returns a list of keys ;; Input: List in RSI with one Map element ;; Returns: List in RAX @@ -183,3 +316,122 @@ core_keys: mov rsi, [rsi + Cons.car] call map_keys ret + +;; Return arguments as a list +;; +core_list: + call incref_object + mov rax, rsi + ret + +;; ------------------------------------------------ +;; String functions + +;; Convert arguments to a readable string, separated by a space +;; +core_pr_str: + + mov al, BYTE [rsi] + mov ah, al + and ah, content_mask + cmp ah, content_empty + je .empty ; Nothing to print + + xor r8, r8 ; Return string in r8 + +.loop: + cmp ah, content_pointer + je .got_pointer + + ; A value. Remove list container + xchg ah, al + mov [rsi], BYTE al + xchg ah, al + push rsi + push rax + call pr_str + pop rbx + pop rsi + mov [rsi], BYTE bl ; restore type + jmp .got_string + +.got_pointer: + push rsi + call pr_str + ret + + ;mov rsi, [rsi + Cons.car] ; Address pointed to + call pr_str + ;call string_new + pop rsi + +.got_string: + ; String now in rax + + cmp r8, 0 + jne .append + + ; first string + mov r8, rax ; Output string + jmp .next + +.append: + push rsi + push rax + + mov rsi, r8 ; Output string + mov rdx, rax ; String to be copied + call string_append_string + mov r8, rax + + pop rsi ; Was in rax, temporary string + call release_array ; Release the string + + pop rsi ; Restore input + +.next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .done + + ; More inputs + mov rsi, [rsi + Cons.cdr] ; pointer + + ; Add separator + push rsi + mov rsi, r8 + mov cl, ' ' + call string_append_char + pop rsi + + ; Get the type in ah for comparison at start of loop + mov al, BYTE [rsi] + mov ah, al + and ah, content_mask + + jmp .loop +.done: + ; No more input, so return + mov rax, r8 + ret + +.empty: + call string_new ; An empty string + ret + +;; Print arguments readably, return nil +core_prn: + ; Convert to string + call core_pr_str + ; print the string + mov rsi, rax + push rsi ; Save the string address + call print_string + pop rsi + call release_array ; Release the string + + ; Return nil + call alloc_cons + mov [rax], BYTE maltype_nil + ret diff --git a/nasm/printer.asm b/nasm/printer.asm index cdcdea7542..3e26d77eba 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -24,6 +24,7 @@ section .text ;; RCX ;; R12 ;; R13 +;; R14 ;; Calls: raw_to_string, ;; ;; diff --git a/nasm/step4_if_fn_do.asm b/nasm/step4_if_fn_do.asm index d6b1651a1e..3fab6c75a9 100644 --- a/nasm/step4_if_fn_do.asm +++ b/nasm/step4_if_fn_do.asm @@ -55,6 +55,11 @@ section .data static_symbol do_symbol, 'do' static_symbol if_symbol, 'if' static_symbol fn_symbol, 'fn*' + +;; Empty list value, passed to functions without args +;; Note this is just a single byte, so the rest of the +;; list must never be accessed. +static_empty_list: db maltype_empty_list section .text @@ -1263,11 +1268,20 @@ eval: mov cl, BYTE [rbx] cmp cl, maltype_function jne .list_not_function - + + ; Check the rest of the args + mov cl, BYTE [rax + Cons.typecdr] + cmp cl, content_pointer + je .list_got_args + ; No arguments + mov rsi, static_empty_list ; Point to an empty list + jmp .list_function_call +.list_got_args: + mov rsi, [rax + Cons.cdr] ; Rest of list +.list_function_call: ; Call the function with the rest of the list in RSI push rax push r15 - mov rsi, [rax + Cons.cdr] ; Rest of list mov rdi, rbx ; Function object in RDI call [rbx + Cons.car] ; Call function ; Result in rax From cbcb37cd76f41721a68d34b06226f2b989b9f45d Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 6 Nov 2017 23:09:10 +0000 Subject: [PATCH 0585/2308] functions =, pr-str and prn working pr-str was failing due to R8 being overwritten. Added push/pop and now works as expected. Test for equality now added compare_objects_rec which recursively compares lists and other structures --- nasm/core.asm | 62 ++++++++++++++++++--------- nasm/printer.asm | 1 + nasm/step4_if_fn_do.asm | 7 +--- nasm/types.asm | 92 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 26 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 7fca59efb5..1398072703 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -69,7 +69,7 @@ core_environment: core_env_native core_emptyp_symbol, core_emptyp core_env_native core_count_symbol, core_count - core_env_native core_equal_symbol, core_equal_p + core_env_native core_equal_symbol, core_equalp core_env_native core_keys_symbol, core_keys core_env_native core_list_symbol, core_list @@ -167,10 +167,13 @@ core_arithmetic: mov [rax + Cons.typecdr], BYTE content_nil ret -;; Test objects for equality -core_equal_p: +;; compare objects for equality +core_equalp: ; Check that rsi contains a list mov cl, BYTE [rsi] + cmp cl, maltype_empty_list + je .error + and cl, block_mask + container_mask cmp cl, block_cons + container_list jne .error @@ -183,23 +186,42 @@ core_equal_p: ; move second pointer into rdi mov rdi, [rsi + Cons.cdr] - ; Compare rsi and rdi objects - call compare_objects ; result in rax + ; Remove next pointers + mov cl, BYTE [rsi + Cons.typecdr] + mov [rsi + Cons.typecdr], BYTE 0 + + mov bl, BYTE [rdi + Cons.typecdr] + mov [rdi + Cons.typecdr], BYTE 0 + + push rbx + push rcx + + ; Compare the objects recursively + call compare_objects_rec + + ; Restore next pointers + pop rcx + pop rbx + mov [rsi + Cons.typecdr], BYTE cl + mov [rdi + Cons.typecdr], BYTE bl - ; for now put result into Cons - mov rdi, rax + je .true + + +.false: call alloc_cons - mov [rax], BYTE maltype_integer - mov [rax + Cons.typecdr], BYTE content_nil - mov [rax + Cons.car], rdi + mov [rax], BYTE maltype_false ret -.error: - ; Return nil +.true: call alloc_cons - mov [rax], BYTE maltype_nil - mov [rax + Cons.typecdr], BYTE content_nil + mov [rax], BYTE maltype_true ret - +.error: + push rsi + print_str_mac error_string ; print 'Error: ' + pop rsi + jmp error_throw + ;; Test if a given object is a list ;; Input list in RSI ;; Returns true or false in RAX @@ -349,7 +371,9 @@ core_pr_str: xchg ah, al push rsi push rax + push r8 call pr_str + pop r8 pop rbx pop rsi mov [rsi], BYTE bl ; restore type @@ -357,12 +381,10 @@ core_pr_str: .got_pointer: push rsi + push r8 + mov rsi, [rsi + Cons.car] ; Address pointed to call pr_str - ret - - ;mov rsi, [rsi + Cons.car] ; Address pointed to - call pr_str - ;call string_new + pop r8 pop rsi .got_string: diff --git a/nasm/printer.asm b/nasm/printer.asm index 3e26d77eba..83d2a1d9d5 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -22,6 +22,7 @@ section .text ;; ;; Modifies: ;; RCX +;; R8 ;; R12 ;; R13 ;; R14 diff --git a/nasm/step4_if_fn_do.asm b/nasm/step4_if_fn_do.asm index 3fab6c75a9..c577583971 100644 --- a/nasm/step4_if_fn_do.asm +++ b/nasm/step4_if_fn_do.asm @@ -1331,11 +1331,6 @@ apply_fn: pop rax ret - -;; Prints the result -print: - mov rax, rsi ; Return the input - ret ;; Read-Eval-Print in sequence rep_seq: @@ -1343,7 +1338,7 @@ rep_seq: mov rsi, rax ; Output of read into input of eval call eval mov rsi, rax ; Output of eval into input of print - call print + call pr_str mov rsi, rax ; Return value ret diff --git a/nasm/types.asm b/nasm/types.asm index 0fd89e7cee..ff78a83a03 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -777,6 +777,98 @@ compare_objects: ret +;; Recursively check objects, including lists +;; +;; Inputs: Objects in RSI and RDI +;; +;; Sets ZF if equal, clears flag otherwise +compare_objects_rec: + ; Compare rsi and rdi objects + + ; Check type + mov al, BYTE [rsi] + mov bl, BYTE [rdi] + cmp al, bl + jne .false + + ; Check the container type + and bl, block_mask + jnz .array + + ; Check if a pointer to something + and al, content_mask + cmp al, content_pointer + je .pointer + + ; Get the values + + mov rbx, [rsi + Cons.car] + mov rcx, [rdi + Cons.car] + cmp rbx, rcx + jne .false + + ; Value is the same, so get next + jmp .next + +.array: + ; Comparing arrays + call compare_char_array + cmp rax, 0 + ret ; Array has no next + +.pointer: + + mov rbx, [rsi + Cons.car] + mov rcx, [rdi + Cons.car] + cmp rbx, rcx + je .next ; Equal pointers + + push rsi + push rdi + ; Put the addresses to compare into RSI and RDI + mov rsi, rbx + mov rdi, rcx + call compare_objects_rec + pop rdi + pop rsi + jne .false + ; fall through to .next + +.next: + ; Check if both have a 'cdr' pointer + mov al, BYTE [rsi + Cons.typecdr] + mov bl, BYTE [rdi + Cons.typecdr] + + cmp al, content_pointer + je .rsi_has_next + + ; No next pointer in RSI + cmp bl, content_pointer + je .false ; RDI has a next pointer + + ; Neither have a next pointer, so done + jmp .true + +.rsi_has_next: + cmp bl, content_pointer + jne .false ; RDI has no next pointer + + ; Both have a next pointer, so keep going + mov rsi, [rsi + Cons.cdr] + mov rdi, [rdi + Cons.cdr] + jmp compare_objects_rec + +.false: + lahf ; flags in AH + and ah, 255-64 ; clear zero flag + sahf + ret +.true: + lahf ; flags in AH + or ah, 64 ; set zero flag + sahf + ret + ;; Char array objects (strings, symbols, keywords) in RSI and RDI ;; Return code in RAX ;; From d692bd1f951aa414f95464510c5f193f633f703c Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 6 Nov 2017 23:37:57 +0000 Subject: [PATCH 0586/2308] Added numeric comparison operators TEST RESULTS (for ../tests/step4_if_fn_do.mal): 0: soft failing tests 60: failing tests 108: passing tests 168: total tests Some to look at next time: * (= "" "") -> false, should be true * (def! DO (fn* (a) 7)) (DO 3) -> (7), should be 7 * Function with empty argument list currently not allowed --- nasm/Makefile | 4 +++ nasm/core.asm | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++ nasm/types.asm | 4 +-- 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/nasm/Makefile b/nasm/Makefile index 3eddf75ad6..6c6b46d030 100644 --- a/nasm/Makefile +++ b/nasm/Makefile @@ -15,3 +15,7 @@ step2_eval: step2_eval.asm $(COMPONENTS) step3_env: step3_env.asm $(COMPONENTS) nasm -felf64 step3_env.asm ld -o $@ step3_env.o + +step4_if_fn_do: step4_if_fn_do.asm $(COMPONENTS) + nasm -felf64 step4_if_fn_do.asm + ld -o $@ step4_if_fn_do.o diff --git a/nasm/core.asm b/nasm/core.asm index 1398072703..ff71cfcb4a 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -16,6 +16,10 @@ section .data static core_emptyp_symbol, db "empty?" static core_equal_symbol, db "=" + static core_gt_symbol, db ">" + static core_lt_symbol, db "<" + static core_ge_symbol, db ">=" + static core_le_symbol, db "<=" static core_count_symbol, db "count" static core_keys_symbol, db "keys" @@ -29,6 +33,7 @@ section .data static core_emptyp_error_string, db "empty? expects a list, vector or map",10 static core_count_error_string, db "count expects a list or vector",10 + static core_numeric_expect_ints, db "comparison operator expected two numbers",10 section .text ;; Add a native function to the core environment @@ -70,6 +75,10 @@ core_environment: core_env_native core_count_symbol, core_count core_env_native core_equal_symbol, core_equalp + core_env_native core_gt_symbol, core_gt + core_env_native core_lt_symbol, core_lt + core_env_native core_ge_symbol, core_ge + core_env_native core_le_symbol, core_le core_env_native core_keys_symbol, core_keys core_env_native core_list_symbol, core_list @@ -221,6 +230,73 @@ core_equalp: print_str_mac error_string ; print 'Error: ' pop rsi jmp error_throw + +;; ----------------------------------------------------------------- +;; Numerical comparisons + + +core_gt: + mov rcx, core_compare_num.gt + jmp core_compare_num +core_lt: + mov rcx, core_compare_num.lt + jmp core_compare_num +core_ge: + mov rcx, core_compare_num.ge + jmp core_compare_num +core_le: + mov rcx, core_compare_num.le + ;jmp core_compare_num +core_compare_num: + ; The first argument should be an int + mov al, BYTE [rsi] + and al, content_mask + cmp al, maltype_integer + jne .error + + ; Check that there's a second argument + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .error + mov rax, [rsi + Cons.car] + mov rdi, [rsi + Cons.cdr] + + ; The second arg should also be an int + mov bl, BYTE [rdi] + and bl, content_mask + cmp bl, maltype_integer + jne .error + + mov rbx, [rdi + Cons.car] + + cmp rax, rbx + jmp rcx ; Address set above +.gt: + jg .true + jmp .false +.lt: + jl .true + jmp .false +.ge: + jge .true + jmp .false +.le: + jle .true + ;jmp .false +.false: + call alloc_cons + mov [rax], BYTE maltype_false + ret +.true: + call alloc_cons + mov [rax], BYTE maltype_true + ret +.error: + push rsi + print_str_mac error_string ; print 'Error: ' + print_str_mac core_numeric_expect_ints + pop rsi + jmp error_throw ;; Test if a given object is a list ;; Input list in RSI diff --git a/nasm/types.asm b/nasm/types.asm index ff78a83a03..9767219f0d 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -151,12 +151,12 @@ section .data ;; is free'd it is pushed onto the heap_x_free list. -%define heap_cons_limit 100 ; Number of cons objects which can be created +%define heap_cons_limit 1000 ; Number of cons objects which can be created heap_cons_next: dd heap_cons_store ; Address of next cons in memory heap_cons_free: dq 0 ; Address of start of free list -%define heap_array_limit 50 ; Number of array objects which can be created +%define heap_array_limit 300 ; Number of array objects which can be created heap_array_next: dd heap_array_store heap_array_free: dq 0 From aa722d73db1c8c87ace527c82bee5c493838e6df Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 7 Nov 2017 23:30:11 +0000 Subject: [PATCH 0587/2308] step 4 non-deferrable tests passing Down to 37 failing tests. Some odd ones, maybe indicating something strange going on with memory for string arrays. --- nasm/core.asm | 18 +++++++++-- nasm/env.asm | 5 ++- nasm/printer.asm | 3 ++ nasm/step4_if_fn_do.asm | 69 ++++++++++++++++++++++++++++++++++++----- nasm/types.asm | 11 +++++-- 5 files changed, 93 insertions(+), 13 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index ff71cfcb4a..613b095640 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -357,11 +357,16 @@ core_emptyp: core_count: mov al, BYTE [rsi] and al, content_mask + + cmp al, content_nil + je .zero + cmp al, content_pointer jne .error ; Expected a container mov rsi, [rsi + Cons.car] mov al, BYTE [rsi] + mov ah, al and ah, (block_mask + container_mask) cmp ah, (block_cons + container_list) @@ -389,7 +394,9 @@ core_count: mov rsi, [rsi + Cons.cdr] jmp .loop - + +.zero: ; Return zero count + mov rbx, 0 .done: ; Count is in RBX push rbx @@ -522,8 +529,15 @@ core_pr_str: core_prn: ; Convert to string call core_pr_str - ; print the string mov rsi, rax + + ; Put newline at the end + push rsi + mov cl, 10 ; newline + call string_append_char + pop rsi + + ; print the string push rsi ; Save the string address call print_string pop rsi diff --git a/nasm/env.asm b/nasm/env.asm index 8a87c0fdb4..dd4bd4a356 100644 --- a/nasm/env.asm +++ b/nasm/env.asm @@ -75,11 +75,14 @@ env_new_bind: call env_new mov r13, rax ; New environment in R13 - + .bind_loop: ; Check the type in the bind list mov bl, BYTE [r11] and bl, content_mask + cmp bl, content_empty + je .done ; No bindings + cmp bl, content_pointer jne .bind_not_symbol diff --git a/nasm/printer.asm b/nasm/printer.asm index 83d2a1d9d5..0322c55ff5 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -68,6 +68,9 @@ pr_str: cmp cl, '"' ; je .string_escape_char + cmp cl, 92 ; Escape '\' + je .string_escape_char + cmp cl, 10 ; Newline je .string_newline diff --git a/nasm/step4_if_fn_do.asm b/nasm/step4_if_fn_do.asm index c577583971..c2b11ee308 100644 --- a/nasm/step4_if_fn_do.asm +++ b/nasm/step4_if_fn_do.asm @@ -55,11 +55,19 @@ section .data static_symbol do_symbol, 'do' static_symbol if_symbol, 'if' static_symbol fn_symbol, 'fn*' - + ;; Empty list value, passed to functions without args ;; Note this is just a single byte, so the rest of the ;; list must never be accessed. -static_empty_list: db maltype_empty_list +static_empty_list: ISTRUC Cons +AT Cons.typecar, db maltype_empty_list +AT Cons.typecdr, db 0 +AT Cons.refcount, dw -1 +IEND + +;; Startup string. This is evaluated on startup + static mal_startup_string, db "(def! not (fn* (a) (if a false true)))" + section .text @@ -1183,8 +1191,14 @@ eval: mov al, BYTE [r11] and al, content_mask cmp al, content_pointer - jne .fn_got_body ; Body in r11 + jne .fn_is_value ; Body in r11 mov r11, [r11 + Cons.car] + jmp .fn_got_body + +.fn_is_value: + ; Body is just a value, no expression + mov [r11], BYTE al ; Mark as value, not list + .fn_got_body: ; Now put into function type @@ -1304,6 +1318,8 @@ eval: ;; ;; Input: RSI - Arguments to bind ;; RDI - Function object +;; +;; Output: Result in RAX apply_fn: push rsi ; Extract values from the list in RDI @@ -1311,16 +1327,29 @@ apply_fn: mov rsi, [rax + Cons.car] ; Env mov rax, [rax + Cons.cdr] mov rdi, [rax + Cons.car] ; Binds + mov rax, [rax + Cons.cdr] + mov rax, [rax + Cons.car] ; Body pop rcx ; Exprs + ; Check the type of the body + mov bl, BYTE [rax] + and bl, block_mask + container_mask + jnz .bind + ; Just a value (in RAX). No eval needed + + push rax + mov rsi, rax + call incref_object + pop rax + ret +.bind: + ; Create a new environment, binding arguments push rax call env_new_bind mov rdi, rax ; New environment in RDI - pop rax ; Function object - - mov rax, [rax + Cons.cdr] - mov rsi, [rax + Cons.car] ; Body + pop rsi ; Body + ; Evaluate the function body push rdi ; Environment call eval pop rsi @@ -1331,6 +1360,7 @@ apply_fn: pop rax ret + ;; Read-Eval-Print in sequence rep_seq: @@ -1354,6 +1384,31 @@ _start: mov rdi, .catch ; Address to jump to xor rcx, rcx ; No data call error_handler_push + + ; Evaluate the startup string + + mov rsi, mal_startup_string + mov edx, mal_startup_string.len + call raw_to_string ; String in RAX + + push rax + mov rsi, rax + call read_str ; AST in RAX + pop rsi ; string + + push rax ; AST + call release_array ; string + pop rsi ; AST + + push rsi + mov rdi, [repl_env] ; Environment + call eval + pop rsi + + push rax + call release_object ; AST + pop rsi + call release_object ; Return from eval ; ----------------------------- ; Main loop diff --git a/nasm/types.asm b/nasm/types.asm index 9767219f0d..60b13cf9c9 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -386,6 +386,7 @@ incref_object: string_new: call alloc_array mov [rax], BYTE maltype_string + mov DWORD [rax + Array.length], 0 mov QWORD [rax + Array.next], 0 ret @@ -884,6 +885,10 @@ compare_char_array: jne .different ; same length + + cmp eax, 0 + je .equal ; Both zero length + mov rbx, rsi add rbx, Array.data mov rcx, rdi @@ -895,13 +900,13 @@ compare_char_array: jl .rdi_greater jg .rsi_greater - ; equal + ; this character is equal inc rbx inc rcx dec eax - jnz .compare_loop + jnz .compare_loop ; Next character - ; equal +.equal: mov rax, 0 ret From 2eba19559b585375cb74c227ac06b354950cde69 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 8 Nov 2017 23:31:09 +0000 Subject: [PATCH 0588/2308] step 4 keywords, []/() comparison, variadic args Finished support for "& more" style args. Now handles case where "more" is empty. Better handling of empty list, removing use of static empty list, since this seemed to lead to hard-to-debug memory issues. Comparison between Cons container types (vector, list, map for now) ignores container type, so (= [] (list)) -> true Step 4 down to 19 failing tests, all due to missing str and println functions. --- nasm/env.asm | 73 +++++++++++++++++++++++++++++++++++++---- nasm/step4_if_fn_do.asm | 59 +++++++++++++++++++++------------ nasm/types.asm | 19 +++++++++-- 3 files changed, 121 insertions(+), 30 deletions(-) diff --git a/nasm/env.asm b/nasm/env.asm index dd4bd4a356..ad35d8d01b 100644 --- a/nasm/env.asm +++ b/nasm/env.asm @@ -13,11 +13,13 @@ section .data ;; Symbols used for comparison static_symbol env_symbol, '*env*' - + static_symbol ampersand_symbol, '&' + ;; Error message strings - static env_binds_error_string, db "Expecting symbol in binds list",10 - static env_binds_missing_string, db "Missing expression in bind",10 + static env_binds_error_string, db "Env expecting symbol in binds list",10 + static env_binds_missing_string, db "Env missing expression in bind",10 + static env_missing_symbol_after_amp_string, db "Env missing symbol after &",10 section .text @@ -92,11 +94,24 @@ env_new_bind: jne .bind_not_symbol ; RDI now contains a symbol + + ; Check if it is '&' + mov rsi, ampersand_symbol + push rdi + call compare_char_array ; Compares RSI and RDI + pop rdi + cmp rax, 0 + je .variadic ; Bind rest of args to following symbol + ; Check the type in expr mov bl, BYTE [r12] mov bh, bl and bh, content_mask + + cmp bh, content_empty + je .bind_missing_expr ; No expression + cmp bh, content_pointer je .value_pointer @@ -120,7 +135,7 @@ env_new_bind: call env_set ; Fall through to next .next: - ; Check if there is a next + ; Check if there is a next symbol mov bl, BYTE [r11 + Cons.typecdr] cmp bl, content_pointer jne .done @@ -131,13 +146,57 @@ env_new_bind: ; Check if there's an expression to bind to mov bl, BYTE [r12 + Cons.typecdr] cmp bl, content_pointer - jne .bind_missing_expr + jne .next_no_expr ; No expr, but symbol could be & mov r12, [r12 + Cons.cdr] ; Next expression jmp .bind_loop + +.next_no_expr: + call alloc_cons + mov [rax], BYTE maltype_empty_list + mov r12, rax + + jmp .bind_loop .done: mov rax, r13 ; Env ret + +.variadic: + ; R11 Cons contains '&' symbol + ; Bind next symbol to the rest of the list in R12 + mov bl, BYTE [r11 + Cons.typecdr] + cmp bl, content_pointer + jne .missing_symbol_after_amp + + mov r11, [r11 + Cons.cdr] + + mov bl, BYTE [r11] + and bl, content_mask + cmp bl, content_pointer + jne .bind_not_symbol + + mov rdi, [r11 + Cons.car] ; Symbol object? + mov bl, BYTE [rdi] + cmp bl, maltype_symbol + jne .bind_not_symbol + + ; Bind symbol in RDI to R12 + mov rcx, r12 ; Value + mov rsi, r13 ; Env + call env_set + jmp .done + +.missing_symbol_after_amp: + push r12 + + ; Release the environment + mov rsi, r13 + call release_object + + print_str_mac error_string ; print 'Error: ' + print_str_mac env_missing_symbol_after_amp_string + pop rsi + jmp error_throw .bind_not_symbol: ; Expecting a symbol push r11 ; Binds list @@ -154,8 +213,10 @@ env_new_bind: jmp error_throw .bind_missing_expr: + ; Have a symbol, but no expression. + push r11 ; Binds list - + ; Release the environment mov rsi, r13 call release_object diff --git a/nasm/step4_if_fn_do.asm b/nasm/step4_if_fn_do.asm index c2b11ee308..9fc58d4dd9 100644 --- a/nasm/step4_if_fn_do.asm +++ b/nasm/step4_if_fn_do.asm @@ -46,7 +46,8 @@ section .data static let_bind_value_string, db "let* missing value in bindings list",10 static let_missing_body_string, db "let* missing body",10 - + static eval_list_not_function, db "list does not begin with a function",10 + ;; Symbols used for comparison @@ -56,15 +57,6 @@ section .data static_symbol if_symbol, 'if' static_symbol fn_symbol, 'fn*' -;; Empty list value, passed to functions without args -;; Note this is just a single byte, so the rest of the -;; list must never be accessed. -static_empty_list: ISTRUC Cons -AT Cons.typecar, db maltype_empty_list -AT Cons.typecdr, db 0 -AT Cons.refcount, dw -1 -IEND - ;; Startup string. This is evaluated on startup static mal_startup_string, db "(def! not (fn* (a) (if a false true)))" @@ -233,6 +225,11 @@ eval_ast: ret .symbol: + ; Check if first character of symbol is ':' + mov al, BYTE [rsi + Array.data] + cmp al, ':' + je .keyword + ; look in environment push rsi xchg rsi, rdi @@ -256,7 +253,15 @@ eval_ast: pop rsi jmp error_throw - + + ; ------------------------------ + +.keyword: + ; Just return keywords unaltered + call incref_object + mov rax, rsi + ret + ; ------------------------------ .list: ; Evaluate each element of the list @@ -638,7 +643,7 @@ eval: ; Check type mov al, BYTE [rsi] cmp al, maltype_empty_list - je .empty_list ; empty list, return unchanged + je .return_nil and al, container_mask cmp al, container_list @@ -654,6 +659,7 @@ eval: ; Check if the first element is a symbol mov al, BYTE [rsi] + and al, content_mask cmp al, content_pointer jne .list_eval @@ -1107,7 +1113,7 @@ eval: ; Skip the next item mov al, BYTE [r11 + Cons.typecdr] cmp al, content_pointer - jne .if_nil + jne .return_nil mov r11, [r11 + Cons.cdr] @@ -1115,7 +1121,7 @@ eval: ; Get the next item in the list and evaluate it mov al, BYTE [r11 + Cons.typecdr] cmp al, content_pointer - jne .if_nil + jne .return_nil ; Nothing to return mov r11, [r11 + Cons.cdr] @@ -1147,7 +1153,7 @@ eval: mov [rax + Cons.typecdr], BYTE content_nil ret -.if_nil: +.return_nil: call alloc_cons mov [rax], BYTE maltype_nil mov [rax + Cons.typecdr], BYTE content_nil @@ -1266,11 +1272,12 @@ eval: ; ----------------------------- .list_eval: - + push rsi mov rdi, r15 ; Environment push r15 call eval_ast pop r15 + pop rsi ; Check that the first element of the return is a function mov bl, BYTE [rax] @@ -1287,8 +1294,13 @@ eval: mov cl, BYTE [rax + Cons.typecdr] cmp cl, content_pointer je .list_got_args + ; No arguments - mov rsi, static_empty_list ; Point to an empty list + push rbx + call alloc_cons + mov [rax], BYTE maltype_empty_list + pop rbx + mov rsi, rax jmp .list_function_call .list_got_args: mov rsi, [rax + Cons.cdr] ; Rest of list @@ -1308,11 +1320,16 @@ eval: .list_not_function: ; Not a function. Probably an error - ret + push rsi + + mov rsi, rax + call release_object + + print_str_mac error_string + print_str_mac eval_list_not_function + pop rsi + jmp error_throw -.empty_list: - mov rax, rsi - ret ;; Applies a user-defined function ;; diff --git a/nasm/types.asm b/nasm/types.asm index 60b13cf9c9..fb693ccc28 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -789,11 +789,19 @@ compare_objects_rec: ; Check type mov al, BYTE [rsi] mov bl, BYTE [rdi] - cmp al, bl + + mov ah, al + mov bh, bl + + ; Don't distinguish between [] and () + and ah, (block_mask + content_mask) + and bh, (block_mask + content_mask) + + cmp ah, bh jne .false - + ; Check the container type - and bl, block_mask + and bh, block_mask jnz .array ; Check if a pointer to something @@ -813,6 +821,11 @@ compare_objects_rec: .array: ; Comparing arrays + + ; Container type (symbol/string) does matter + cmp al, bl + jne .false + call compare_char_array cmp rax, 0 ret ; Array has no next From 17d45192b97d87d5a476c6a968d131ecc69482db Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 9 Nov 2017 00:13:20 +0000 Subject: [PATCH 0589/2308] print_readably flag, str and println functions Additional flag to pr_str in RDI. If zero, returns string without escaping special characters. Added str and println functions, using most of the same code as pr-str and prn Only one test now failing for step 4. --- nasm/core.asm | 21 ++++++++++++++++++--- nasm/printer.asm | 12 ++++++++++++ nasm/step4_if_fn_do.asm | 5 ++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 613b095640..e776538d02 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -28,6 +28,8 @@ section .data static core_pr_str_symbol, db "pr-str" static core_prn_symbol, db "prn" + static core_str_symbol, db "str" + static core_println_symbol, db "println" ;; Strings @@ -85,6 +87,8 @@ core_environment: core_env_native core_pr_str_symbol, core_pr_str core_env_native core_prn_symbol, core_prn + core_env_native core_str_symbol, core_str + core_env_native core_println_symbol, core_println ; ----------------- ; Put the environment in RAX @@ -435,7 +439,11 @@ core_list: ;; Convert arguments to a readable string, separated by a space ;; core_pr_str: - + mov rdi, 1 ; print_readably + jmp core_str_functions +core_str: + xor rdi, rdi +core_str_functions: mov al, BYTE [rsi] mov ah, al and ah, content_mask @@ -503,13 +511,17 @@ core_pr_str: ; More inputs mov rsi, [rsi + Cons.cdr] ; pointer + cmp rdi, 0 ; print_readably + je .end_append_char ; No separator if not printing readably + ; Add separator push rsi mov rsi, r8 mov cl, ' ' call string_append_char pop rsi - +.end_append_char: + ; Get the type in ah for comparison at start of loop mov al, BYTE [rsi] mov ah, al @@ -527,8 +539,11 @@ core_pr_str: ;; Print arguments readably, return nil core_prn: - ; Convert to string call core_pr_str + jmp core_prn_functions +core_println: + call core_str +core_prn_functions: mov rsi, rax ; Put newline at the end diff --git a/nasm/printer.asm b/nasm/printer.asm index 0322c55ff5..2e0b91f66f 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -17,6 +17,7 @@ section .data section .text ;; Input: Address of object in RSI +;; print_readably in RDI. Set to zero for false ;; ;; Output: Address of string in RAX ;; @@ -41,6 +42,11 @@ pr_str: ; --------------------------- ; Handle string + + cmp rdi, 0 + je .string_not_readable + + ; printing readably, so escape characters call string_new ; Output string in rax @@ -102,6 +108,12 @@ pr_str: ret +.string_not_readable: + ; Just return the string + call incref_object + mov rax, rsi + ret + ; ---------------------------- .not_string: ; Now test the container type (value, list, map, vector) diff --git a/nasm/step4_if_fn_do.asm b/nasm/step4_if_fn_do.asm index 9fc58d4dd9..852192c403 100644 --- a/nasm/step4_if_fn_do.asm +++ b/nasm/step4_if_fn_do.asm @@ -184,6 +184,7 @@ error_throw: ; Print the object in RSI then quit cmp rsi, 0 je .done ; nothing to print + mov rdi, 1 ; print_readably call pr_str mov rsi, rax call print_string @@ -1454,7 +1455,8 @@ _start: push rax ; Save result ; Put into pr_str - mov rsi, rax + mov rsi, rax + mov rdi, 1 ; print_readably call pr_str push rax ; Save output string @@ -1488,6 +1490,7 @@ _start: ; Check if an object was thrown cmp rsi, 0 je .catch_done_print ; nothing to print + mov rdi, 1 call pr_str mov rsi, rax call print_string From b43cd5b63905b38bd62e948c0653b36319a99d3c Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 9 Nov 2017 23:25:37 +0000 Subject: [PATCH 0590/2308] Fixes for long strings Extra code in string_append_char, string_append_string, and print_string to handle long strings. Printing *env* no longer produces segfault. --- nasm/types.asm | 48 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/nasm/types.asm b/nasm/types.asm index fb693ccc28..a795dc2fbc 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -431,14 +431,39 @@ raw_to_symbol: ;; Appends a character to a string ;; Input: Address of string in RSI, character in CL +;; +;; Modifies +;; RAX string_append_char: + push rsi + ; Get the end of the string +.get_end: + mov rax, [rsi + Array.next] + cmp rax, 0 + jz .got_dest_end + mov rsi, rax + jmp .get_end +.got_dest_end: + + ; Check if this chunk is full mov eax, DWORD [rsi + Array.length] + cmp eax, (array_chunk_len*8) + jne .append + + ; full, need to allocate another + call alloc_array + mov [rsi + Array.next], rax + mov rsi, rax + xor eax, eax ; Set length to zero +.append: inc eax mov DWORD [rsi + Array.length], eax dec eax add rax, rsi add rax, Array.data ; End of data - mov [rax], BYTE cl + mov [rax], BYTE cl + + pop rsi ; Restore original value ret ;; Appends a string to the end of a string @@ -468,6 +493,9 @@ string_append_string: mov r11, r10 mov r8d, DWORD [rbx + Array.length] add r11, r8 + + cmp r8d, 0 + je .return ; Appending zero-size array ; Find the end of the string in RSI ; and put the address of the Array object into rax @@ -485,7 +513,7 @@ string_append_string: mov r8, rax add r8, Array.data add r8d, DWORD [rax + Array.length] - + ; destination data end into r9 mov r9, rax add r9, Array.size @@ -500,7 +528,7 @@ string_append_string: ; Check if we've reached the end of this Array cmp r10, r11 jne .source_ok - + ; have reached the end of the source Array mov rbx, QWORD [rbx + Array.next] ; Get the next Array address cmp rbx, 0 ; Test if it's null @@ -523,6 +551,7 @@ string_append_string: cmp r8, r9 jne .copy_loop ; Next byte +.alloc_dest: ; Reached the end of the destination ; Need to allocate another Array push rax @@ -541,6 +570,7 @@ string_append_string: mov r9, rax add r9, Array.size + jmp .copy_loop .finished: ; Compare r8 (destination) with data start @@ -550,7 +580,7 @@ string_append_string: inc r8 ; r8 now contains length mov DWORD [rax + Array.length], r8d - +.return: ret ;; ------------------------------------------ @@ -567,11 +597,19 @@ print_string: mov al, [rsi] cmp al, maltype_string jne .error - + +.print_chunk: ; write(1, string, length) + push rsi mov edx, [rsi + Array.length] ; number of bytes add rsi, Array.data ; address of raw string to output call print_rawstring + pop rsi + + ; Check if this is the end + mov rsi, QWORD [rsi + Array.next] + cmp rsi, 0 + jne .print_chunk ; next chunk ; Restore registers pop rsi From 2edb2d3cf2d5e0ff0e6e72409488f7f29fffd73f Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 11 Nov 2017 01:04:11 +0000 Subject: [PATCH 0591/2308] Step 5: Tail call optimisation Not working yet, still get memory-related errors --- nasm/Makefile | 4 + nasm/env.asm | 1 + nasm/step5_tco.asm | 1603 ++++++++++++++++++++++++++++++++++++++++++++ nasm/types.asm | 6 + 4 files changed, 1614 insertions(+) create mode 100644 nasm/step5_tco.asm diff --git a/nasm/Makefile b/nasm/Makefile index 6c6b46d030..1756968a9a 100644 --- a/nasm/Makefile +++ b/nasm/Makefile @@ -19,3 +19,7 @@ step3_env: step3_env.asm $(COMPONENTS) step4_if_fn_do: step4_if_fn_do.asm $(COMPONENTS) nasm -felf64 step4_if_fn_do.asm ld -o $@ step4_if_fn_do.o + +step5_tco: step5_tco.asm $(COMPONENTS) + nasm -felf64 step5_tco.asm + ld -o $@ step5_tco.o diff --git a/nasm/env.asm b/nasm/env.asm index ad35d8d01b..95c96deb9d 100644 --- a/nasm/env.asm +++ b/nasm/env.asm @@ -65,6 +65,7 @@ env_new: ;; ;; Modifies registers ;; RBX +;; RDX ;; R8 ;; R9 ;; R10 diff --git a/nasm/step5_tco.asm b/nasm/step5_tco.asm new file mode 100644 index 0000000000..18807bd0de --- /dev/null +++ b/nasm/step5_tco.asm @@ -0,0 +1,1603 @@ +;; +;; nasm -felf64 step5_tco.asm && ld step5_tco.o && ./a.out +;; +;; Calling convention: Address of input is in RSI +;; Address of return value is in RAX +;; + +global _start + +%include "types.asm" ; Data types, memory +%include "env.asm" ; Environment type +%include "system.asm" ; System calls +%include "reader.asm" ; String -> Data structures +%include "core.asm" ; Core functions +%include "printer.asm" ; Data structures -> String + +section .bss + +;; Top-level (REPL) environment +repl_env:resq 1 + +;; Error handler list +error_handler: resq 1 + +section .data + +;; ------------------------------------------ +;; Fixed strings for printing + + static prompt_string, db 10,"user> " ; The string to print at the prompt + + static error_string, db 27,'[31m',"Error",27,'[0m',": " + + static not_found_string, db " not found.",10 + + static def_missing_arg_string, db "missing argument to def!",10 + + static def_expecting_symbol_string, db "expecting symbol as first argument to def!",10 + + static let_missing_bindings_string, db "let* missing bindings",10 + + static let_bindings_list_string, db "let* expected a list or vector of bindings",10 + + static let_bind_symbol_string, db "let* expected a symbol in bindings list",10 + + static let_bind_value_string, db "let* missing value in bindings list",10 + + static let_missing_body_string, db "let* missing body",10 + static eval_list_not_function, db "list does not begin with a function",10 + + static if_missing_condition_string, db "missing condition in if expression",10 + +;; Symbols used for comparison + + static_symbol def_symbol, 'def!' + static_symbol let_symbol, 'let*' + static_symbol do_symbol, 'do' + static_symbol if_symbol, 'if' + static_symbol fn_symbol, 'fn*' + +;; Startup string. This is evaluated on startup + static mal_startup_string, db "(def! not (fn* (a) (if a false true)))" + + +section .text + +;; ---------------------------------------------- +;; +;; Error handling +;; +;; A handler consists of: +;; - A stack pointer address to reset to +;; - An address to jump to +;; - An optional data structure to pass +;; +;; When jumped to, an error handler will be given: +;; - the object thrown in RSI +;; - the optional data structure in RDI +;; + + +;; Add an error handler to the front of the list +;; +;; Input: RSI - Stack pointer +;; RDI - Address to jump to +;; RCX - Data structure. Set to zero for none. +;; If not zero, reference count incremented +;; +;; Modifies registers: +;; RAX +;; RBX +error_handler_push: + call alloc_cons + ; car will point to a list (stack, addr, data) + ; cdr will point to the previous handler + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov rbx, [error_handler] + cmp rbx, 0 ; Check if previous handler was zero + je .create_handler ; Zero, so leave null + ; Not zero, so create pointer to it + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rbx + + ; note: not incrementing reference count, since + ; we're replacing one reference with another +.create_handler: + mov [error_handler], rax ; new error handler + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.car], rax + ; Store stack pointer + mov [rax], BYTE (block_cons + container_list + content_function) + mov [rax + Cons.car], rsi ; stack pointer + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.typecdr], BYTE content_pointer + mov [rdx + Cons.cdr], rax + ; Store function pointer to jump to + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], rdi + + ; Check if there is an object to pass to handler + cmp rcx, 0 + je .done + + ; Set the final CDR to point to the object + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rcx + + mov rsi, rcx + call incref_object + +.done: + ret + + +;; Removes an error handler from the list +;; +;; Modifies registers: +;; RSI +;; RAX +;; RCX +error_handler_pop: + ; get the address + mov rsi, [error_handler] + cmp rsi, 0 + je .done ; Nothing to remove + + push rsi + mov rsi, [rsi + Cons.cdr] ; next handler + mov [error_handler], rsi + call incref_object ; needed because releasing soon + + pop rsi ; handler being removed + call release_cons + +.done: + ret + + +;; Throw an error +;; Object to pass to handler should be in RSI +error_throw: + ; Get the next error handler + mov rax, [error_handler] + cmp rax, 0 + je .no_handler + + ; Got a handler + mov rax, [rax + Cons.car] ; handler + mov rbx, [rax + Cons.car] ; stack pointer + mov rax, [rax + Cons.cdr] + mov rcx, [rax + Cons.car] ; function + mov rdi, [rax + Cons.cdr] ; data structure + + ; Reset stack + mov rsp, rbx + + ; Jump to the handler + jmp rcx + +.no_handler: + ; Print the object in RSI then quit + cmp rsi, 0 + je .done ; nothing to print + mov rdi, 1 ; print_readably + call pr_str + mov rsi, rax + call print_string +.done: + jmp quit_error + +;; ---------------------------------------------- +;; Evaluates a form +;; +;; Inputs: RSI Form to evaluate +;; RDI Environment +;; +eval_ast: + mov r15, rdi ; Save Env in r15 + + ; Check the type + mov al, BYTE [rsi] + + ; Check if this is a list + mov ah, al + and ah, container_mask + cmp ah, container_list + je .list + + cmp ah, container_map + je .map + + cmp ah, container_vector + je .vector + + ; Not a list, map or vector + cmp ah, container_symbol + je .symbol + + ; Not a symbol, list, map or vector + call incref_object ; Increment reference count + + mov rax, rsi + ret + +.symbol: + ; Check if first character of symbol is ':' + mov al, BYTE [rsi + Array.data] + cmp al, ':' + je .keyword + + ; look in environment + push rsi + xchg rsi, rdi + ; symbol is the key in rdi + ; Environment in rsi + call env_get + pop rsi + je .done ; result in RAX + + ; Not found, throw an error + push rsi + print_str_mac error_string ; print 'Error: ' + + pop rsi + push rsi + mov edx, [rsi + Array.length] + add rsi, Array.data + call print_rawstring ; print symbol + + print_str_mac not_found_string ; print ' not found' + pop rsi + + jmp error_throw + + ; ------------------------------ + +.keyword: + ; Just return keywords unaltered + call incref_object + mov rax, rsi + ret + + ; ------------------------------ +.list: + ; Evaluate each element of the list + ; + xor r8, r8 ; The list to return + ; r9 contains head of list + +.list_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .list_pointer + + ; A value in RSI, so copy + + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_list) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .list_append + +.list_pointer: + ; List element is a pointer to something + push rsi + push r8 + push r9 + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Get the address + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi + + call eval ; Evaluate it, result in rax + pop r15 + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .list_eval_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_list + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .list_append + +.list_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_list) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi + + ; Fall through to .list_append +.list_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .list_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .list_next + +.list_first: + mov r8, rax + mov r9, rax + ; fall through to .list_next + +.list_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .list_done ; finished list + mov rsi, [rsi + Cons.cdr] ; next in list + jmp .list_loop + +.list_done: + mov rax, r8 ; Return the list + ret + + ; --------------------- +.map: + ; Create a new map, evaluating all the values + + ; Check if the map is empty + cmp al, maltype_empty_map + jne .map_not_empty + + ; map empty. Just return it + call incref_object + mov rax, rsi + ret + +.map_not_empty: + + mov r10, rsi ; input in R10 + xor r12, r12 ; New map in r12 + + ; Now loop through each key-value pair + ; NOTE: This method relies on the implementation + ; of map as a list + +.map_loop: + ; Copy the key + call alloc_cons ; New Cons in RAX + + mov bl, [r10 + Cons.typecar] ; Type in BL + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] ; Value in RCX + mov [rax + Cons.car], rcx + + ; Check the type of the key + and bl, content_mask + cmp bl, content_pointer + jne .map_got_key ; a value + + ; a pointer, so increment reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.map_got_key: + cmp r12,0 + jne .append_key + + ; First key + mov r12, rax + mov r13, rax + jmp .map_value + +.append_key: + ; Appending to previous value in r13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + +.map_value: + ; Check that we have a value + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_error_missing_value + mov r10, [r10 + Cons.cdr] + + ; Now got value in r10 + + ; Check the type of the value + mov bl, [r10 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .map_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r10 + Cons.typecar] + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .map_got_value +.map_value_pointer: + ; A pointer, so need to evaluate + push r10 ; Input + push r12 ; start of result + push r13 ; Current head of result + push r15 ; Env + mov rsi, [r10 + Cons.car] ; Get the address + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi + + call eval ; Evaluate it, result in rax + pop r15 + pop r13 + pop r12 + pop r10 + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + + jne .map_eval_pointer + + ; A value, so just change the type to a map + and bl, content_mask + add bl, (block_cons + container_map) + mov [rax], BYTE bl + jmp .map_got_value + +.map_eval_pointer: + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_map + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + +.map_got_value: + ; Append RAX to list in R13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + + ; Check if there's another key + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_done ; finished map + mov r10, [r10 + Cons.cdr] ; next in map + jmp .map_loop + +.map_done: + mov rax, r12 + ret + +.map_error_missing_value: + mov rax, r12 + ret + + ; ------------------------------ +.vector: + ; Evaluate each element of the vector + ; + xor r8, r8 ; The vector to return + ; r9 contains head of vector + +.vector_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .vector_pointer + + ; A value, so copy + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_vector) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .vector_append + +.vector_pointer: + ; Vector element is a pointer to something + push rsi + push r8 + push r9 + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Get the address + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi + + call eval ; Evaluate it, result in rax + pop r15 + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .vector_eval_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_vector + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .vector_append + +.vector_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_vector) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi + +.vector_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .vector_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .vector_next + +.vector_first: + mov r8, rax + mov r9, rax + ; fall through to .vector_next + +.vector_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .vector_done ; finished vector + mov rsi, [rsi + Cons.cdr] ; next in vector + jmp .vector_loop + +.vector_done: + mov rax, r8 ; Return the vector + ret + + ; --------------------- +.done: + ret + + + +;; Comparison of symbols for eval function +;; Compares the symbol in RSI with specified symbol +;; Preserves RSI and RBX +;; Modifies RDI +%macro eval_cmp_symbol 1 + push rsi + push rbx + mov rsi, rbx + mov rdi, %1 + call compare_char_array + pop rbx + pop rsi + cmp rax, 0 +%endmacro + +;; ---------------------------------------------------- +;; Evaluates a form +;; +;; Input: RSI Form to evaluate +;; RDI Environment +;; +;; Returns: Result in RAX +;; +;; Note: The environment in RDI will have its reference count +;; reduced by one (released). This is to make tail call optimisation easier +;; +eval: + mov r15, rdi ; Env + + ; Check type + mov al, BYTE [rsi] + cmp al, maltype_empty_list + je .return_nil + + and al, container_mask + cmp al, container_list + je .list + + ; Not a list. Evaluate and return + call eval_ast + jmp .return ; Releases Env + + ; -------------------- +.list: + ; A list + + ; Check if the first element is a symbol + mov al, BYTE [rsi] + + and al, content_mask + cmp al, content_pointer + jne .list_eval + + mov rbx, [rsi + Cons.car] + mov al, BYTE [rbx] + cmp al, maltype_symbol + jne .list_eval + + ; Is a symbol, address in RBX + + ; Compare against special form symbols + + eval_cmp_symbol def_symbol ; def! + je .def_symbol + + eval_cmp_symbol let_symbol ; let* + je .let_symbol + + eval_cmp_symbol do_symbol ; do + je .do_symbol + + eval_cmp_symbol if_symbol ; if + je .if_symbol + + eval_cmp_symbol fn_symbol ; fn + je .fn_symbol + + ; Unrecognised + jmp .list_eval + +.def_symbol: + ; Define a new symbol in current environment + + ; Next item should be a symbol + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Now should have a symbol + + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + jne .def_error_expecting_symbol + mov r8, [rsi + Cons.car] ; Symbol (?) + + mov al, BYTE [r8] + cmp al, maltype_symbol + jne .def_error_expecting_symbol + + ; R8 now contains a symbol + + ; expecting a value or pointer next + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a pointer + mov al, BYTE [rsi] + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .def_pointer + + ; A value, so copy + push rax + call alloc_cons + pop rbx ; BL now contains type + and bl, content_mask + add bl, (block_cons + container_value) + mov [rax], BYTE bl + mov rcx, [rsi + Cons.car] + mov [rax + Cons.car], rcx + mov rsi, rax + + jmp .def_got_value + +.def_pointer: + ; A pointer, so evaluate + + ; This may throw an error, so define a handler + + + push r8 ; the symbol + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Pointer + mov rdi, r15 + call eval + mov rsi, rax + pop r15 + pop r8 + +.def_got_value: + ; Symbol in R8, value in RSI + mov rdi, r8 ; key (symbol) + mov rcx, rsi ; Value + mov rsi, r15 ; Environment + call env_set + + mov rax, rcx + jmp .return + +.def_error_missing_arg: + mov rsi, def_missing_arg_string + mov rdx, def_missing_arg_string.len + jmp .def_handle_error + +.def_error_expecting_symbol: + mov rsi, def_expecting_symbol_string + mov rdx, def_expecting_symbol_string.len + jmp .def_handle_error + +.def_handle_error: + push rsi + push rdx + print_str_mac error_string ; print 'Error: ' + + pop rdx + pop rsi + call print_rawstring ; print message + + xor rsi, rsi ; no object to throw + jmp error_throw ; No return + + ; ----------------------------- +.let_symbol: + ; Create a new environment + + mov r11, rsi ; Let form in R11 + + mov rsi, r15 ; Outer env + call env_new ; Increments R15's ref count + mov r14, rax ; New environment in R14 + + mov rsi, r15 + call release_object ; Decrement R15 ref count + + ; Second element should be the bindings + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_bindings + mov r11, [r11 + Cons.cdr] + + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .let_error_bindings_list + + mov r12, [r11 + Cons.car] ; should be bindings list + mov al, BYTE [r12] + and al, (block_mask + container_mask) + ; Can be either a list or vector + cmp al, block_cons + container_list + je .let_bind_loop + cmp al, block_cons + container_vector + je .let_bind_loop + + ; Not a list or vector + jmp .let_error_bindings_list + +.let_bind_loop: + ; R12 now contains a list with an even number of items + ; The first should be a symbol, then a value to evaluate + + ; Get the symbol + mov al, BYTE [r12] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_symbol + + mov r13, [r12 + Cons.car] ; Symbol (?) + mov al, BYTE [r13] + cmp al, maltype_symbol + jne .let_error_bind_symbol + + ; R13 now contains a symbol to bind + ; The next item in the bindings list (R12) + ; should be a value or expression to evaluate + + mov al, BYTE [r12 + Cons.typecdr] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_value + mov r12, [r12 + Cons.cdr] + + ; got value in R12 + + ; Check the type of the value + mov bl, [r12 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .let_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r12 + Cons.typecar] + and bl, content_mask + ;or bl, (block_cons + container_value) ; 0 + mov [rax + Cons.typecar], bl + mov rcx, [r12 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .let_got_value + +.let_value_pointer: + ; A pointer, so need to evaluate + push r11 ; let* form list + push r12 ; Position in bindings list + push r13 ; symbol to bind + push r14 ; new environment + mov rsi, [r12 + Cons.car] ; Get the address + mov rdi, r14 + call eval ; Evaluate it, result in rax + pop r14 + pop r13 + pop r12 + pop r11 + +.let_got_value: + + mov rsi, r14 ; Env + mov rdi, r13 ; key + mov rcx, rax ; value + call env_set + + ; Release the value + mov rsi, rcx ; The value + call release_object + + ; Check if there are more bindings + mov al, BYTE [r12 + Cons.typecdr] + cmp al, content_pointer + jne .let_done_binding + mov r12, [r12 + Cons.cdr] ; Next + jmp .let_bind_loop + +.let_done_binding: + ; Done bindings. + ; Evaluate next item in let* form in new environment + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_body + mov r11, [r11 + Cons.cdr] ; Now contains value to evaluate + ; Check type of the value + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + je .body_pointer + + ; Just a value, so copy + call alloc_cons + mov bl, BYTE [r11] + and bl, content_mask + mov [rax], BYTE bl ; set type + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx ; copy value + jmp .let_done + +.body_pointer: + ; Evaluate using new environment + + mov rsi, [r11 + Cons.car] ; Object pointed to + mov rdi, r14 ; New environment + + jmp eval ; Tail call + ; Note: eval will release the new environment on return + +.let_done: + ; Release the new environment + push rax + mov rsi, r14 + call release_object + pop rax + jmp .return + +.let_error_missing_bindings: + mov rsi, let_missing_bindings_string + mov rdx, let_missing_bindings_string.len + jmp .let_handle_error + +.let_error_bindings_list: ; expected a list or vector, got something else + mov rsi, let_bindings_list_string + mov rdx, let_bindings_list_string.len + jmp .let_handle_error + +.let_error_bind_symbol: ; expected a symbol, got something else + mov rsi, let_bind_symbol_string + mov rdx, let_bind_symbol_string.len + jmp .let_handle_error + +.let_error_bind_value: ; Missing value in binding list + mov rsi, let_bind_value_string + mov rdx, let_bind_value_string.len + jmp .let_handle_error + +.let_error_missing_body: ; Missing body to evaluate + mov rsi, let_missing_body_string + mov rdx, let_missing_body_string.len + jmp .let_handle_error + +.let_handle_error: + push r11 ; For printing later + + push rsi + push rdx + + print_str_mac error_string ; print 'Error: ' + + pop rdx + pop rsi + call print_rawstring ; print message + + pop rsi ; let* form + jmp error_throw ; No return + + ; ----------------------------- + +.do_symbol: + mov r11, rsi ; do form in RSI + ; Environment in R15 + + ; Check if there is a body + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .do_no_body ; error + + mov r11, [r11 + Cons.cdr] ; Body in R11 + +.do_symbol_loop: + + ; Need to test if this is the last form + ; so we can handle tail call + + mov bl, BYTE [r11 + Cons.typecdr] + cmp bl, content_pointer + jne .do_body_last ; Last expression + + ; not the last expression + + ; Check if this is a value or pointer + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + jne .do_next ; A value, so skip + + ; A pointer, so evaluate + + push r15 + push r11 + + mov rsi, r15 + call incref_object ; Increase Env reference + ; since eval will release Env + + mov rsi, [r11 + Cons.car] ; Form + mov rdi, r15 ; Env + call eval ; Result in RAX + + ; Another form after this. + ; Discard the result of the last eval + mov rsi, rax + call release_object + + pop r11 + pop r15 + +.do_next: + mov r11, [r11 + Cons.cdr] ; Next in list + + jmp .do_symbol_loop + +.do_body_last: + ; The last form is in R11, which will be returned + + ; Check if this is a value or pointer + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + jne .do_body_value_return + jmp .do_body_expr_return + +.do_body_value_return: + ; Got a value as last form (in R11). + ; Copy and return + + push rax ; Type of value to return + + ; release Env + mov rsi, r15 + call release_object + + ; Allocate a Cons object to hold value + call alloc_cons + pop rbx ; type in BL + mov [rax], BYTE bl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + ret + +.do_body_expr_return: + ; An expression to evaluate as the last form + ; Tail call optimise, jumping to eval + ; Don't increment Env reference count + + mov rsi, [r11 + Cons.car] ; Form + mov rdi, r15 ; Env + jmp eval ; Tail call + +.do_no_body: + ; No expressions to evaluate. Return nil + + mov rsi, r15 + call release_object ; Release Env + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret + + ; ----------------------------- + +.if_symbol: + mov r11, rsi ; if form in R11 + ; Environment in R15 + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .if_no_condition + + mov r11, [r11 + Cons.cdr] ; Should be a condition + + ; Check if value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .if_cond_value + + ; A pointer, so evaluate + + push r15 + push r11 + + mov rsi, r15 + call incref_object ; Increase Env reference + + mov rsi, [r11 + Cons.car] ; Form + mov rdi, r15 ; Env + call eval ; Result in RAX + pop r11 + pop r15 + + ; Get type of result + mov bl, BYTE [rax] + + ; release value + push rbx + mov rsi, rax + call release_object + pop rbx + + ; Check type + cmp bl, maltype_nil + je .if_false + cmp bl, maltype_false + je .if_false + + jmp .if_true + +.if_cond_value: + + ; A value + cmp al, content_nil + je .if_false + cmp al, content_false + je .if_false + + jmp .if_true + +.if_false: + ; Skip the next item + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .return_nil + + mov r11, [r11 + Cons.cdr] + +.if_true: + ; Get the next item in the list and evaluate it + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; Nothing to return + + mov r11, [r11 + Cons.cdr] + + ; Check if value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + je .if_got_pointer + +.if_got_value: + ; copy value in r11 + call alloc_cons + mov bl, BYTE [r11] + and bl, content_mask + mov [rax], BYTE bl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + + jmp .return + +.if_got_pointer: + mov rsi, [r11 + Cons.car] ; Form + mov rdi, r15 ; Env + jmp eval ; Tail call + +.if_no_condition: ; just (if) without a condition + + print_str_mac error_string + print_str_mac if_missing_condition_string + + ; Release environment + mov rsi, r15 + call release_object + xor rsi, rsi ; No object to throw + jmp error_throw + +.return_nil: + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + +.return: + push rax + ; Release environment + mov rsi, r15 + call release_object + pop rax + + ret + + ; ----------------------------- + +.fn_symbol: + mov r11, rsi ; fn form in R11 + ; Environment in R15 + + ; Get the binds and body of the function + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .fn_empty + + mov r11, [r11 + Cons.cdr] + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .fn_binds_not_list + + mov r12, [r11 + Cons.car] ; Should be binds list + mov al, BYTE [r12] + and al, (block_mask + container_mask) + cmp al, (block_cons + container_list) + je .fn_got_binds ; Can be list + cmp al, (block_cons + container_vector) + je .fn_got_binds ; or vector + jmp .fn_binds_not_list + +.fn_got_binds: + + ; Next get the body of the function + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .fn_no_body + + mov r11, [r11 + Cons.cdr] + ; Check value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .fn_is_value ; Body in r11 + mov r11, [r11 + Cons.car] + jmp .fn_got_body + +.fn_is_value: + ; Body is just a value, no expression + mov [r11], BYTE al ; Mark as value, not list + +.fn_got_body: + + ; Now put into function type + ; Addr is "apply_fn", the address to call + ; Env in R15 + ; Binds in R12 + ; Body in R11 + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_function) + mov rbx, apply_fn + mov [rax + Cons.car], rbx ; Address of apply function + mov [rax + Cons.typecdr], BYTE content_pointer + + mov r13, rax ; Return list in R13 + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r15 ; Environment + mov [rax + Cons.typecdr], BYTE content_pointer + + mov [r13 + Cons.cdr], rax ; Append to list + mov r14, rax + + push rax + mov rsi, r15 + call incref_object + pop rax + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r12 ; Binds list + mov [rax + Cons.typecdr], BYTE content_pointer + + mov [r14 + Cons.cdr], rax ; Append to list + mov r14, rax + + push rax + mov rsi, r12 + call incref_object + pop rax + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r11 ; Body of function + + mov [r14 + Cons.cdr], rax + + mov rsi, r11 + call incref_object + + mov rax, r13 + jmp .return + +.fn_empty: +.fn_binds_not_list: +.fn_no_body: + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + jmp .return + + + ; ----------------------------- + +.list_eval: + push rsi + mov rdi, r15 ; Environment + push r15 + call eval_ast ; List of evaluated forms in RAX + pop r15 + pop rsi + + ; Check that the first element of the return is a function + mov bl, BYTE [rax] + and bl, content_mask + cmp bl, content_pointer + jne .list_not_function + + mov rbx, [rax + Cons.car] ; Get the address + mov cl, BYTE [rbx] + cmp cl, maltype_function + jne .list_not_function + + ; Check the rest of the args + mov cl, BYTE [rax + Cons.typecdr] + cmp cl, content_pointer + je .list_got_args + + ; No arguments + push rbx + call alloc_cons + mov [rax], BYTE maltype_empty_list + pop rbx + mov rsi, rax + jmp .list_function_call +.list_got_args: + mov rsi, [rax + Cons.cdr] ; Rest of list +.list_function_call: + ; Call the function with the rest of the list in RSI + + mov rdx, rax ; List to release + mov rdi, rbx ; Function object in RDI + + mov rbx, [rbx + Cons.car] ; Call function + cmp rbx, apply_fn + je apply_fn ; Jump to user function apply + + ; A built-in function, so call (no recursion) + push rax + push r15 + + call rbx + + ; Result in rax + pop r15 + pop rsi ; eval'ed list + + push rax + call release_cons + pop rax + + jmp .return ; Releases Env + +.list_not_function: + ; Not a function. Probably an error + push rsi + + mov rsi, rax + call release_object + + print_str_mac error_string + print_str_mac eval_list_not_function + pop rsi + jmp error_throw + + +;; Applies a user-defined function +;; +;; Input: RSI - Arguments to bind +;; RDI - Function object +;; RDX - list to release after binding +;; R15 - Env (will be released) +;; +;; Output: Result in RAX +apply_fn: + push rsi + ; Extract values from the list in RDI + mov rax, [rdi + Cons.cdr] + mov rsi, [rax + Cons.car] ; Env + mov rax, [rax + Cons.cdr] + mov rdi, [rax + Cons.car] ; Binds + mov rax, [rax + Cons.cdr] + mov rax, [rax + Cons.car] ; Body + pop rcx ; Exprs + + ; Check the type of the body + mov bl, BYTE [rax] + and bl, block_mask + container_mask + jnz .bind + ; Just a value (in RAX). No eval needed + + push rax + mov rsi, rax + call incref_object + + ; Release the list passed in RDX + mov rsi, rdx + call release_object + + ; Release the environment + mov rsi, r15 + call release_object + + pop rax + ret +.bind: + ; Create a new environment, binding arguments + push rax + + push rdx + call env_new_bind + pop rdx + + mov rdi, rax ; New environment in RDI + + ; Release the list passed in RDX +.release: + mov rsi, rdx + call release_cons + + ; Release the environment + mov rsi, r15 + call release_object + + pop rsi ; Body + + jmp eval ; Tail call + ; The new environment (in RDI) will be released by eval + + +;; Read-Eval-Print in sequence +rep_seq: + call read_str + mov rsi, rax ; Output of read into input of eval + call eval + mov rsi, rax ; Output of eval into input of print + call pr_str + mov rsi, rax ; Return value + ret + + +_start: + ; Create and print the core environment + call core_environment ; Environment in RAX + + mov [repl_env], rax ; store in memory + + ; Set the error handler + mov rsi, rsp ; Stack pointer + mov rdi, .catch ; Address to jump to + xor rcx, rcx ; No data + call error_handler_push + + ; Evaluate the startup string + + mov rsi, mal_startup_string + mov edx, mal_startup_string.len + call raw_to_string ; String in RAX + + push rax + mov rsi, rax + call read_str ; AST in RAX + pop rsi ; string + + push rax ; AST + call release_array ; string + pop rsi ; AST + + push rsi + mov rdi, [repl_env] ; Environment + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call eval + pop rsi + + push rax + call release_object ; AST + pop rsi + call release_object ; Return from eval + + ; ----------------------------- + ; Main loop + +.mainLoop: + ; print the prompt + print_str_mac prompt_string + + call read_line + + ; Check if we have a zero-length string + cmp DWORD [rax+Array.length], 0 + je .mainLoopEnd + + push rax ; Save address of the input string + + ; Put into read_str + mov rsi, rax + call read_str + push rax ; Save AST + + ; Eval + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call eval + push rax ; Save result + + ; Put into pr_str + mov rsi, rax + mov rdi, 1 ; print_readably + call pr_str + push rax ; Save output string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from pr_str + pop rsi + call release_array + + ; Release result of eval + pop rsi + call release_object + + ; Release the object from read_str + pop rsi + call release_object ; Could be Cons or Array + + ; Release the input string + pop rsi + call release_array + + jmp .mainLoop +.mainLoopEnd: + + jmp quit + +.catch: + ; Jumps here on error + + ; Check if an object was thrown + cmp rsi, 0 + je .catch_done_print ; nothing to print + mov rdi, 1 + call pr_str + mov rsi, rax + call print_string +.catch_done_print: + jmp .mainLoop ; Go back to the prompt + diff --git a/nasm/types.asm b/nasm/types.asm index a795dc2fbc..3e9fed8c63 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -353,6 +353,12 @@ release_cons: ;; Releases either a Cons or Array ;; Address of object in RSI +;; +;; May modify: +;; RAX +;; RBX +;; RCX +;; release_object: mov al, BYTE [rsi] ; Get first byte and al, block_mask ; Test block type From cc38947443e7f97c4037b54220559de20a0457f9 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 11 Nov 2017 08:05:27 +0000 Subject: [PATCH 0592/2308] Bugfix in let and def forms Was releasing the environment too many times, so it got deleted/reused when it shouldn't. Step 5 tests now pass. --- nasm/step5_tco.asm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nasm/step5_tco.asm b/nasm/step5_tco.asm index 18807bd0de..d861773f62 100644 --- a/nasm/step5_tco.asm +++ b/nasm/step5_tco.asm @@ -770,6 +770,11 @@ eval: push r15 ; Env mov rsi, [rsi + Cons.car] ; Pointer mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + call eval mov rsi, rax pop r15 @@ -958,7 +963,7 @@ eval: mov rsi, r14 call release_object pop rax - jmp .return + ret ; already released env .let_error_missing_bindings: mov rsi, let_missing_bindings_string From cd34fb3b2e235febb27d300da7f9902cc70fc8aa Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 11 Nov 2017 23:33:58 +0000 Subject: [PATCH 0593/2308] Added read-string and slurp functions System calls using Linux 64-bit syscalls in system.asm. Wrapper code in core.asm --- nasm/core.asm | 51 ++++++++++++++++++++++ nasm/system.asm | 113 ++++++++++++++++++++++++++++++++++++++++++++++++ nasm/types.asm | 6 ++- 3 files changed, 169 insertions(+), 1 deletion(-) diff --git a/nasm/core.asm b/nasm/core.asm index e776538d02..38f1e02068 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -30,6 +30,9 @@ section .data static core_prn_symbol, db "prn" static core_str_symbol, db "str" static core_println_symbol, db "println" + + static core_read_string_symbol, db "read-string" + static core_slurp_symbol, db "slurp" ;; Strings @@ -89,6 +92,9 @@ core_environment: core_env_native core_prn_symbol, core_prn core_env_native core_str_symbol, core_str core_env_native core_println_symbol, core_println + + core_env_native core_read_string_symbol, core_read_string + core_env_native core_slurp_symbol, core_slurp ; ----------------- ; Put the environment in RAX @@ -562,3 +568,48 @@ core_prn_functions: call alloc_cons mov [rax], BYTE maltype_nil ret + +;; Given a string, calls read_str to get an AST +core_read_string: + mov al, BYTE [rsi] + mov ah, al + and ah, content_mask + cmp ah, content_pointer + jne .no_string + + mov rsi, [rsi + Cons.car] + mov al, BYTE [rsi] + cmp al, maltype_string + jne .no_string + + call read_str + ret + +.no_string: + ; Didn't get a string input + call alloc_cons + mov [rax], BYTE maltype_nil + ret + + +;; Reads a file into a string +core_slurp: + mov al, BYTE [rsi] + mov ah, al + and ah, content_mask + cmp ah, content_pointer + jne .no_string + + mov rsi, [rsi + Cons.car] + mov al, BYTE [rsi] + cmp al, maltype_string + jne .no_string + + call read_file + ret + +.no_string: + ; Didn't get a string input + call alloc_cons + mov [rax], BYTE maltype_nil + ret diff --git a/nasm/system.asm b/nasm/system.asm index 6389b2a64f..de4d793bef 100644 --- a/nasm/system.asm +++ b/nasm/system.asm @@ -3,6 +3,11 @@ ;;; This file contains system-specific functions, ;;; which use calls to the operating system (Linux) +section .data + static error_open_file_string, db "Error opening file " + static error_read_file_string, db "Error reading file " + +section .text ;; ------------------------------------------- ;; Prints a raw string to stdout @@ -91,3 +96,111 @@ read_line: pop rax ; Restore pointer to string mov DWORD [rax + Array.length], ebx ; Set string length ret + +;; Reads a file into a string +;; +;; Input: RSI - File name string (char Array) +;; +;; Returns: string in RAX +;; +;; Pieces from https://stackoverflow.com/questions/20133698/how-to-read-from-and-write-to-files-using-nasm-for-x86-64bit +read_file: + + mov rdi, rsi ; Filename + + ; Need to add null terminator + mov eax, DWORD [rdi + Array.length] + cmp eax, (array_chunk_len * 8) + je .error_filename ; File name too long + + ; Insert a null terminator + add rax, rdi + mov [rax + Array.data], BYTE 0 + + ; Open the file + mov rax, 2 + add rdi, Array.data; filename in RDI + xor rsi, rsi ; O_RDONLY in RSI + syscall + + ; Check for error (return -1) + cmp eax, 0 + jl .error_open + + mov rdi, rax ; File handle in RDI + + ; Create a string + push rdi + call string_new ; In RAX + pop rdi + + mov r9, rax ; Current Array + push rax ; This is popped in .done +.loop: + ; Read next chunk + push r9 + + mov rsi, r9 + add rsi, Array.data ; address + + mov rax, 0 ; sys_read + ; file handle in RDI + mov rdx, (array_chunk_len * 8) ; count + syscall + + pop r9 + + ; Characters read in RAX + + cmp rax, 0 + jl .error_read + + cmp rax, (array_chunk_len * 8) + jg .error_read + + mov [r9 + Array.length], DWORD eax + + jl .done + + ; May still be more to read. + ; Allocate another + call string_new + mov [r9 + Array.next], rax + mov r9, rax + jmp .loop + +.done: + ; Close the file + mov rax, 3 + ;rdi = file handle + syscall + + pop rax + ret + +.error_filename: +.error_open: + ; File name in RDI + sub rdi, Array.data + + ; Make the error message + mov rsi, error_open_file_string + mov edx, error_open_file_string.len + call raw_to_string + mov rsi, rax + mov cl, 39 ; (') + call string_append_char + mov rdx, rdi ; file name + call string_append_string + mov cl, 39 + call string_append_char + + ; Error message in RSI + jmp error_throw + +.error_read: + mov rsi, error_read_file_string + mov edx, error_read_file_string.len + call raw_to_string + mov rsi, rax + jmp error_throw diff --git a/nasm/types.asm b/nasm/types.asm index 3e9fed8c63..7de8cf6a67 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -387,8 +387,12 @@ incref_object: ;; ------------------------------------------- ;; String type - +;; ;; Create a new string, address in RAX +;; +;; Modifies registers +;; RBX +;; string_new: call alloc_array mov [rax], BYTE maltype_string From 193bb9201623f6984d76c88781d34770d9889376 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sun, 12 Nov 2017 08:01:19 +0000 Subject: [PATCH 0594/2308] Added eval and load-file functions Had to add code to reader to ignore LF and CR as whitespace. --- nasm/core.asm | 28 + nasm/reader.asm | 7 +- nasm/step6_file.asm | 1608 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1642 insertions(+), 1 deletion(-) create mode 100644 nasm/step6_file.asm diff --git a/nasm/core.asm b/nasm/core.asm index 38f1e02068..d8330f1c01 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -33,6 +33,7 @@ section .data static core_read_string_symbol, db "read-string" static core_slurp_symbol, db "slurp" + static core_eval_symbol, db "eval" ;; Strings @@ -95,6 +96,7 @@ core_environment: core_env_native core_read_string_symbol, core_read_string core_env_native core_slurp_symbol, core_slurp + core_env_native core_eval_symbol, core_eval ; ----------------- ; Put the environment in RAX @@ -613,3 +615,29 @@ core_slurp: call alloc_cons mov [rax], BYTE maltype_nil ret + +;; Evaluate an expression in the REPL environment +;; +core_eval: + mov al, BYTE [rsi] + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .pointer + + ; Just a value, so return it + call incref_object + ret + +.pointer: + ; A pointer, so need to eval + mov rsi, [rsi + Cons.car] + + mov rdi, [repl_env] ; Environment + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call eval + ret diff --git a/nasm/reader.asm b/nasm/reader.asm index d4273f4c90..80939cbe0c 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -731,7 +731,12 @@ tokenizer_next: cmp cl, ',' ; Comma je .next_char cmp cl, 9 ; Tab - + je .next_char + cmp cl, 10 ; Line Feed + je .next_char + cmp cl, 13 ; Carriage Return + je .next_char + ; Special characters. These are returned in CL as-is cmp cl, '(' je .found diff --git a/nasm/step6_file.asm b/nasm/step6_file.asm new file mode 100644 index 0000000000..e7eaf620aa --- /dev/null +++ b/nasm/step6_file.asm @@ -0,0 +1,1608 @@ +;; +;; nasm -felf64 step6_file.asm && ld step6_file.o && ./a.out +;; +;; Calling convention: Address of input is in RSI +;; Address of return value is in RAX +;; + +global _start + +%include "types.asm" ; Data types, memory +%include "env.asm" ; Environment type +%include "system.asm" ; System calls +%include "reader.asm" ; String -> Data structures +%include "core.asm" ; Core functions +%include "printer.asm" ; Data structures -> String + +section .bss + +;; Top-level (REPL) environment +repl_env:resq 1 + +;; Error handler list +error_handler: resq 1 + +section .data + +;; ------------------------------------------ +;; Fixed strings for printing + + static prompt_string, db 10,"user> " ; The string to print at the prompt + + static error_string, db 27,'[31m',"Error",27,'[0m',": " + + static not_found_string, db " not found.",10 + + static def_missing_arg_string, db "missing argument to def!",10 + + static def_expecting_symbol_string, db "expecting symbol as first argument to def!",10 + + static let_missing_bindings_string, db "let* missing bindings",10 + + static let_bindings_list_string, db "let* expected a list or vector of bindings",10 + + static let_bind_symbol_string, db "let* expected a symbol in bindings list",10 + + static let_bind_value_string, db "let* missing value in bindings list",10 + + static let_missing_body_string, db "let* missing body",10 + static eval_list_not_function, db "list does not begin with a function",10 + + static if_missing_condition_string, db "missing condition in if expression",10 + +;; Symbols used for comparison + + static_symbol def_symbol, 'def!' + static_symbol let_symbol, 'let*' + static_symbol do_symbol, 'do' + static_symbol if_symbol, 'if' + static_symbol fn_symbol, 'fn*' + +;; Startup string. This is evaluated on startup + static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) )" + + +section .text + +;; ---------------------------------------------- +;; +;; Error handling +;; +;; A handler consists of: +;; - A stack pointer address to reset to +;; - An address to jump to +;; - An optional data structure to pass +;; +;; When jumped to, an error handler will be given: +;; - the object thrown in RSI +;; - the optional data structure in RDI +;; + + +;; Add an error handler to the front of the list +;; +;; Input: RSI - Stack pointer +;; RDI - Address to jump to +;; RCX - Data structure. Set to zero for none. +;; If not zero, reference count incremented +;; +;; Modifies registers: +;; RAX +;; RBX +error_handler_push: + call alloc_cons + ; car will point to a list (stack, addr, data) + ; cdr will point to the previous handler + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov rbx, [error_handler] + cmp rbx, 0 ; Check if previous handler was zero + je .create_handler ; Zero, so leave null + ; Not zero, so create pointer to it + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rbx + + ; note: not incrementing reference count, since + ; we're replacing one reference with another +.create_handler: + mov [error_handler], rax ; new error handler + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.car], rax + ; Store stack pointer + mov [rax], BYTE (block_cons + container_list + content_function) + mov [rax + Cons.car], rsi ; stack pointer + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.typecdr], BYTE content_pointer + mov [rdx + Cons.cdr], rax + ; Store function pointer to jump to + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], rdi + + ; Check if there is an object to pass to handler + cmp rcx, 0 + je .done + + ; Set the final CDR to point to the object + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rcx + + mov rsi, rcx + call incref_object + +.done: + ret + + +;; Removes an error handler from the list +;; +;; Modifies registers: +;; RSI +;; RAX +;; RCX +error_handler_pop: + ; get the address + mov rsi, [error_handler] + cmp rsi, 0 + je .done ; Nothing to remove + + push rsi + mov rsi, [rsi + Cons.cdr] ; next handler + mov [error_handler], rsi + call incref_object ; needed because releasing soon + + pop rsi ; handler being removed + call release_cons + +.done: + ret + + +;; Throw an error +;; Object to pass to handler should be in RSI +error_throw: + ; Get the next error handler + mov rax, [error_handler] + cmp rax, 0 + je .no_handler + + ; Got a handler + mov rax, [rax + Cons.car] ; handler + mov rbx, [rax + Cons.car] ; stack pointer + mov rax, [rax + Cons.cdr] + mov rcx, [rax + Cons.car] ; function + mov rdi, [rax + Cons.cdr] ; data structure + + ; Reset stack + mov rsp, rbx + + ; Jump to the handler + jmp rcx + +.no_handler: + ; Print the object in RSI then quit + cmp rsi, 0 + je .done ; nothing to print + mov rdi, 1 ; print_readably + call pr_str + mov rsi, rax + call print_string +.done: + jmp quit_error + +;; ---------------------------------------------- +;; Evaluates a form +;; +;; Inputs: RSI Form to evaluate +;; RDI Environment +;; +eval_ast: + mov r15, rdi ; Save Env in r15 + + ; Check the type + mov al, BYTE [rsi] + + ; Check if this is a list + mov ah, al + and ah, container_mask + cmp ah, container_list + je .list + + cmp ah, container_map + je .map + + cmp ah, container_vector + je .vector + + ; Not a list, map or vector + cmp ah, container_symbol + je .symbol + + ; Not a symbol, list, map or vector + call incref_object ; Increment reference count + + mov rax, rsi + ret + +.symbol: + ; Check if first character of symbol is ':' + mov al, BYTE [rsi + Array.data] + cmp al, ':' + je .keyword + + ; look in environment + push rsi + xchg rsi, rdi + ; symbol is the key in rdi + ; Environment in rsi + call env_get + pop rsi + je .done ; result in RAX + + ; Not found, throw an error + push rsi + print_str_mac error_string ; print 'Error: ' + + pop rsi + push rsi + mov edx, [rsi + Array.length] + add rsi, Array.data + call print_rawstring ; print symbol + + print_str_mac not_found_string ; print ' not found' + pop rsi + + jmp error_throw + + ; ------------------------------ + +.keyword: + ; Just return keywords unaltered + call incref_object + mov rax, rsi + ret + + ; ------------------------------ +.list: + ; Evaluate each element of the list + ; + xor r8, r8 ; The list to return + ; r9 contains head of list + +.list_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .list_pointer + + ; A value in RSI, so copy + + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_list) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .list_append + +.list_pointer: + ; List element is a pointer to something + push rsi + push r8 + push r9 + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Get the address + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi + + call eval ; Evaluate it, result in rax + pop r15 + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .list_eval_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_list + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .list_append + +.list_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_list) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi + + ; Fall through to .list_append +.list_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .list_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .list_next + +.list_first: + mov r8, rax + mov r9, rax + ; fall through to .list_next + +.list_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .list_done ; finished list + mov rsi, [rsi + Cons.cdr] ; next in list + jmp .list_loop + +.list_done: + mov rax, r8 ; Return the list + ret + + ; --------------------- +.map: + ; Create a new map, evaluating all the values + + ; Check if the map is empty + cmp al, maltype_empty_map + jne .map_not_empty + + ; map empty. Just return it + call incref_object + mov rax, rsi + ret + +.map_not_empty: + + mov r10, rsi ; input in R10 + xor r12, r12 ; New map in r12 + + ; Now loop through each key-value pair + ; NOTE: This method relies on the implementation + ; of map as a list + +.map_loop: + ; Copy the key + call alloc_cons ; New Cons in RAX + + mov bl, [r10 + Cons.typecar] ; Type in BL + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] ; Value in RCX + mov [rax + Cons.car], rcx + + ; Check the type of the key + and bl, content_mask + cmp bl, content_pointer + jne .map_got_key ; a value + + ; a pointer, so increment reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.map_got_key: + cmp r12,0 + jne .append_key + + ; First key + mov r12, rax + mov r13, rax + jmp .map_value + +.append_key: + ; Appending to previous value in r13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + +.map_value: + ; Check that we have a value + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_error_missing_value + mov r10, [r10 + Cons.cdr] + + ; Now got value in r10 + + ; Check the type of the value + mov bl, [r10 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .map_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r10 + Cons.typecar] + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .map_got_value +.map_value_pointer: + ; A pointer, so need to evaluate + push r10 ; Input + push r12 ; start of result + push r13 ; Current head of result + push r15 ; Env + mov rsi, [r10 + Cons.car] ; Get the address + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi + + call eval ; Evaluate it, result in rax + pop r15 + pop r13 + pop r12 + pop r10 + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + + jne .map_eval_pointer + + ; A value, so just change the type to a map + and bl, content_mask + add bl, (block_cons + container_map) + mov [rax], BYTE bl + jmp .map_got_value + +.map_eval_pointer: + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_map + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + +.map_got_value: + ; Append RAX to list in R13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + + ; Check if there's another key + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_done ; finished map + mov r10, [r10 + Cons.cdr] ; next in map + jmp .map_loop + +.map_done: + mov rax, r12 + ret + +.map_error_missing_value: + mov rax, r12 + ret + + ; ------------------------------ +.vector: + ; Evaluate each element of the vector + ; + xor r8, r8 ; The vector to return + ; r9 contains head of vector + +.vector_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .vector_pointer + + ; A value, so copy + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_vector) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .vector_append + +.vector_pointer: + ; Vector element is a pointer to something + push rsi + push r8 + push r9 + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Get the address + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi + + call eval ; Evaluate it, result in rax + pop r15 + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .vector_eval_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_vector + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .vector_append + +.vector_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_vector) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi + +.vector_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .vector_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .vector_next + +.vector_first: + mov r8, rax + mov r9, rax + ; fall through to .vector_next + +.vector_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .vector_done ; finished vector + mov rsi, [rsi + Cons.cdr] ; next in vector + jmp .vector_loop + +.vector_done: + mov rax, r8 ; Return the vector + ret + + ; --------------------- +.done: + ret + + + +;; Comparison of symbols for eval function +;; Compares the symbol in RSI with specified symbol +;; Preserves RSI and RBX +;; Modifies RDI +%macro eval_cmp_symbol 1 + push rsi + push rbx + mov rsi, rbx + mov rdi, %1 + call compare_char_array + pop rbx + pop rsi + cmp rax, 0 +%endmacro + +;; ---------------------------------------------------- +;; Evaluates a form +;; +;; Input: RSI Form to evaluate +;; RDI Environment +;; +;; Returns: Result in RAX +;; +;; Note: The environment in RDI will have its reference count +;; reduced by one (released). This is to make tail call optimisation easier +;; +eval: + mov r15, rdi ; Env + + ; Check type + mov al, BYTE [rsi] + cmp al, maltype_empty_list + je .return_nil + + and al, container_mask + cmp al, container_list + je .list + + ; Not a list. Evaluate and return + call eval_ast + jmp .return ; Releases Env + + ; -------------------- +.list: + ; A list + + ; Check if the first element is a symbol + mov al, BYTE [rsi] + + and al, content_mask + cmp al, content_pointer + jne .list_eval + + mov rbx, [rsi + Cons.car] + mov al, BYTE [rbx] + cmp al, maltype_symbol + jne .list_eval + + ; Is a symbol, address in RBX + + ; Compare against special form symbols + + eval_cmp_symbol def_symbol ; def! + je .def_symbol + + eval_cmp_symbol let_symbol ; let* + je .let_symbol + + eval_cmp_symbol do_symbol ; do + je .do_symbol + + eval_cmp_symbol if_symbol ; if + je .if_symbol + + eval_cmp_symbol fn_symbol ; fn + je .fn_symbol + + ; Unrecognised + jmp .list_eval + +.def_symbol: + ; Define a new symbol in current environment + + ; Next item should be a symbol + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Now should have a symbol + + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + jne .def_error_expecting_symbol + mov r8, [rsi + Cons.car] ; Symbol (?) + + mov al, BYTE [r8] + cmp al, maltype_symbol + jne .def_error_expecting_symbol + + ; R8 now contains a symbol + + ; expecting a value or pointer next + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a pointer + mov al, BYTE [rsi] + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .def_pointer + + ; A value, so copy + push rax + call alloc_cons + pop rbx ; BL now contains type + and bl, content_mask + add bl, (block_cons + container_value) + mov [rax], BYTE bl + mov rcx, [rsi + Cons.car] + mov [rax + Cons.car], rcx + mov rsi, rax + + jmp .def_got_value + +.def_pointer: + ; A pointer, so evaluate + + ; This may throw an error, so define a handler + + + push r8 ; the symbol + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Pointer + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call eval + mov rsi, rax + pop r15 + pop r8 + +.def_got_value: + ; Symbol in R8, value in RSI + mov rdi, r8 ; key (symbol) + mov rcx, rsi ; Value + mov rsi, r15 ; Environment + call env_set + + mov rax, rcx + jmp .return + +.def_error_missing_arg: + mov rsi, def_missing_arg_string + mov rdx, def_missing_arg_string.len + jmp .def_handle_error + +.def_error_expecting_symbol: + mov rsi, def_expecting_symbol_string + mov rdx, def_expecting_symbol_string.len + jmp .def_handle_error + +.def_handle_error: + push rsi + push rdx + print_str_mac error_string ; print 'Error: ' + + pop rdx + pop rsi + call print_rawstring ; print message + + xor rsi, rsi ; no object to throw + jmp error_throw ; No return + + ; ----------------------------- +.let_symbol: + ; Create a new environment + + mov r11, rsi ; Let form in R11 + + mov rsi, r15 ; Outer env + call env_new ; Increments R15's ref count + mov r14, rax ; New environment in R14 + + mov rsi, r15 + call release_object ; Decrement R15 ref count + + ; Second element should be the bindings + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_bindings + mov r11, [r11 + Cons.cdr] + + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .let_error_bindings_list + + mov r12, [r11 + Cons.car] ; should be bindings list + mov al, BYTE [r12] + and al, (block_mask + container_mask) + ; Can be either a list or vector + cmp al, block_cons + container_list + je .let_bind_loop + cmp al, block_cons + container_vector + je .let_bind_loop + + ; Not a list or vector + jmp .let_error_bindings_list + +.let_bind_loop: + ; R12 now contains a list with an even number of items + ; The first should be a symbol, then a value to evaluate + + ; Get the symbol + mov al, BYTE [r12] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_symbol + + mov r13, [r12 + Cons.car] ; Symbol (?) + mov al, BYTE [r13] + cmp al, maltype_symbol + jne .let_error_bind_symbol + + ; R13 now contains a symbol to bind + ; The next item in the bindings list (R12) + ; should be a value or expression to evaluate + + mov al, BYTE [r12 + Cons.typecdr] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_value + mov r12, [r12 + Cons.cdr] + + ; got value in R12 + + ; Check the type of the value + mov bl, [r12 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .let_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r12 + Cons.typecar] + and bl, content_mask + ;or bl, (block_cons + container_value) ; 0 + mov [rax + Cons.typecar], bl + mov rcx, [r12 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .let_got_value + +.let_value_pointer: + ; A pointer, so need to evaluate + push r11 ; let* form list + push r12 ; Position in bindings list + push r13 ; symbol to bind + push r14 ; new environment + mov rsi, [r12 + Cons.car] ; Get the address + mov rdi, r14 + call eval ; Evaluate it, result in rax + pop r14 + pop r13 + pop r12 + pop r11 + +.let_got_value: + + mov rsi, r14 ; Env + mov rdi, r13 ; key + mov rcx, rax ; value + call env_set + + ; Release the value + mov rsi, rcx ; The value + call release_object + + ; Check if there are more bindings + mov al, BYTE [r12 + Cons.typecdr] + cmp al, content_pointer + jne .let_done_binding + mov r12, [r12 + Cons.cdr] ; Next + jmp .let_bind_loop + +.let_done_binding: + ; Done bindings. + ; Evaluate next item in let* form in new environment + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_body + mov r11, [r11 + Cons.cdr] ; Now contains value to evaluate + ; Check type of the value + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + je .body_pointer + + ; Just a value, so copy + call alloc_cons + mov bl, BYTE [r11] + and bl, content_mask + mov [rax], BYTE bl ; set type + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx ; copy value + jmp .let_done + +.body_pointer: + ; Evaluate using new environment + + mov rsi, [r11 + Cons.car] ; Object pointed to + mov rdi, r14 ; New environment + + jmp eval ; Tail call + ; Note: eval will release the new environment on return + +.let_done: + ; Release the new environment + push rax + mov rsi, r14 + call release_object + pop rax + ret ; already released env + +.let_error_missing_bindings: + mov rsi, let_missing_bindings_string + mov rdx, let_missing_bindings_string.len + jmp .let_handle_error + +.let_error_bindings_list: ; expected a list or vector, got something else + mov rsi, let_bindings_list_string + mov rdx, let_bindings_list_string.len + jmp .let_handle_error + +.let_error_bind_symbol: ; expected a symbol, got something else + mov rsi, let_bind_symbol_string + mov rdx, let_bind_symbol_string.len + jmp .let_handle_error + +.let_error_bind_value: ; Missing value in binding list + mov rsi, let_bind_value_string + mov rdx, let_bind_value_string.len + jmp .let_handle_error + +.let_error_missing_body: ; Missing body to evaluate + mov rsi, let_missing_body_string + mov rdx, let_missing_body_string.len + jmp .let_handle_error + +.let_handle_error: + push r11 ; For printing later + + push rsi + push rdx + + print_str_mac error_string ; print 'Error: ' + + pop rdx + pop rsi + call print_rawstring ; print message + + pop rsi ; let* form + jmp error_throw ; No return + + ; ----------------------------- + +.do_symbol: + mov r11, rsi ; do form in RSI + ; Environment in R15 + + ; Check if there is a body + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .do_no_body ; error + + mov r11, [r11 + Cons.cdr] ; Body in R11 + +.do_symbol_loop: + + ; Need to test if this is the last form + ; so we can handle tail call + + mov bl, BYTE [r11 + Cons.typecdr] + cmp bl, content_pointer + jne .do_body_last ; Last expression + + ; not the last expression + + ; Check if this is a value or pointer + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + jne .do_next ; A value, so skip + + ; A pointer, so evaluate + + push r15 + push r11 + + mov rsi, r15 + call incref_object ; Increase Env reference + ; since eval will release Env + + mov rsi, [r11 + Cons.car] ; Form + mov rdi, r15 ; Env + call eval ; Result in RAX + + ; Another form after this. + ; Discard the result of the last eval + mov rsi, rax + call release_object + + pop r11 + pop r15 + +.do_next: + mov r11, [r11 + Cons.cdr] ; Next in list + + jmp .do_symbol_loop + +.do_body_last: + ; The last form is in R11, which will be returned + + ; Check if this is a value or pointer + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + jne .do_body_value_return + jmp .do_body_expr_return + +.do_body_value_return: + ; Got a value as last form (in R11). + ; Copy and return + + push rax ; Type of value to return + + ; release Env + mov rsi, r15 + call release_object + + ; Allocate a Cons object to hold value + call alloc_cons + pop rbx ; type in BL + mov [rax], BYTE bl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + ret + +.do_body_expr_return: + ; An expression to evaluate as the last form + ; Tail call optimise, jumping to eval + ; Don't increment Env reference count + + mov rsi, [r11 + Cons.car] ; Form + mov rdi, r15 ; Env + jmp eval ; Tail call + +.do_no_body: + ; No expressions to evaluate. Return nil + + mov rsi, r15 + call release_object ; Release Env + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret + + ; ----------------------------- + +.if_symbol: + mov r11, rsi ; if form in R11 + ; Environment in R15 + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .if_no_condition + + mov r11, [r11 + Cons.cdr] ; Should be a condition + + ; Check if value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .if_cond_value + + ; A pointer, so evaluate + + push r15 + push r11 + + mov rsi, r15 + call incref_object ; Increase Env reference + + mov rsi, [r11 + Cons.car] ; Form + mov rdi, r15 ; Env + call eval ; Result in RAX + pop r11 + pop r15 + + ; Get type of result + mov bl, BYTE [rax] + + ; release value + push rbx + mov rsi, rax + call release_object + pop rbx + + ; Check type + cmp bl, maltype_nil + je .if_false + cmp bl, maltype_false + je .if_false + + jmp .if_true + +.if_cond_value: + + ; A value + cmp al, content_nil + je .if_false + cmp al, content_false + je .if_false + + jmp .if_true + +.if_false: + ; Skip the next item + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .return_nil + + mov r11, [r11 + Cons.cdr] + +.if_true: + ; Get the next item in the list and evaluate it + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; Nothing to return + + mov r11, [r11 + Cons.cdr] + + ; Check if value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + je .if_got_pointer + +.if_got_value: + ; copy value in r11 + call alloc_cons + mov bl, BYTE [r11] + and bl, content_mask + mov [rax], BYTE bl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + + jmp .return + +.if_got_pointer: + mov rsi, [r11 + Cons.car] ; Form + mov rdi, r15 ; Env + jmp eval ; Tail call + +.if_no_condition: ; just (if) without a condition + + print_str_mac error_string + print_str_mac if_missing_condition_string + + ; Release environment + mov rsi, r15 + call release_object + xor rsi, rsi ; No object to throw + jmp error_throw + +.return_nil: + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + +.return: + push rax + ; Release environment + mov rsi, r15 + call release_object + pop rax + + ret + + ; ----------------------------- + +.fn_symbol: + mov r11, rsi ; fn form in R11 + ; Environment in R15 + + ; Get the binds and body of the function + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .fn_empty + + mov r11, [r11 + Cons.cdr] + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .fn_binds_not_list + + mov r12, [r11 + Cons.car] ; Should be binds list + mov al, BYTE [r12] + and al, (block_mask + container_mask) + cmp al, (block_cons + container_list) + je .fn_got_binds ; Can be list + cmp al, (block_cons + container_vector) + je .fn_got_binds ; or vector + jmp .fn_binds_not_list + +.fn_got_binds: + + ; Next get the body of the function + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .fn_no_body + + mov r11, [r11 + Cons.cdr] + ; Check value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .fn_is_value ; Body in r11 + mov r11, [r11 + Cons.car] + jmp .fn_got_body + +.fn_is_value: + ; Body is just a value, no expression + mov [r11], BYTE al ; Mark as value, not list + +.fn_got_body: + + ; Now put into function type + ; Addr is "apply_fn", the address to call + ; Env in R15 + ; Binds in R12 + ; Body in R11 + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_function) + mov rbx, apply_fn + mov [rax + Cons.car], rbx ; Address of apply function + mov [rax + Cons.typecdr], BYTE content_pointer + + mov r13, rax ; Return list in R13 + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r15 ; Environment + mov [rax + Cons.typecdr], BYTE content_pointer + + mov [r13 + Cons.cdr], rax ; Append to list + mov r14, rax + + push rax + mov rsi, r15 + call incref_object + pop rax + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r12 ; Binds list + mov [rax + Cons.typecdr], BYTE content_pointer + + mov [r14 + Cons.cdr], rax ; Append to list + mov r14, rax + + push rax + mov rsi, r12 + call incref_object + pop rax + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r11 ; Body of function + + mov [r14 + Cons.cdr], rax + + mov rsi, r11 + call incref_object + + mov rax, r13 + jmp .return + +.fn_empty: +.fn_binds_not_list: +.fn_no_body: + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + jmp .return + + + ; ----------------------------- + +.list_eval: + push rsi + mov rdi, r15 ; Environment + push r15 + call eval_ast ; List of evaluated forms in RAX + pop r15 + pop rsi + + ; Check that the first element of the return is a function + mov bl, BYTE [rax] + and bl, content_mask + cmp bl, content_pointer + jne .list_not_function + + mov rbx, [rax + Cons.car] ; Get the address + mov cl, BYTE [rbx] + cmp cl, maltype_function + jne .list_not_function + + ; Check the rest of the args + mov cl, BYTE [rax + Cons.typecdr] + cmp cl, content_pointer + je .list_got_args + + ; No arguments + push rbx + call alloc_cons + mov [rax], BYTE maltype_empty_list + pop rbx + mov rsi, rax + jmp .list_function_call +.list_got_args: + mov rsi, [rax + Cons.cdr] ; Rest of list +.list_function_call: + ; Call the function with the rest of the list in RSI + + mov rdx, rax ; List to release + mov rdi, rbx ; Function object in RDI + + mov rbx, [rbx + Cons.car] ; Call function + cmp rbx, apply_fn + je apply_fn ; Jump to user function apply + + ; A built-in function, so call (no recursion) + push rax + push r15 + + call rbx + + ; Result in rax + pop r15 + pop rsi ; eval'ed list + + push rax + call release_cons + pop rax + + jmp .return ; Releases Env + +.list_not_function: + ; Not a function. Probably an error + push rsi + + mov rsi, rax + call release_object + + print_str_mac error_string + print_str_mac eval_list_not_function + pop rsi + jmp error_throw + + +;; Applies a user-defined function +;; +;; Input: RSI - Arguments to bind +;; RDI - Function object +;; RDX - list to release after binding +;; R15 - Env (will be released) +;; +;; Output: Result in RAX +apply_fn: + push rsi + ; Extract values from the list in RDI + mov rax, [rdi + Cons.cdr] + mov rsi, [rax + Cons.car] ; Env + mov rax, [rax + Cons.cdr] + mov rdi, [rax + Cons.car] ; Binds + mov rax, [rax + Cons.cdr] + mov rax, [rax + Cons.car] ; Body + pop rcx ; Exprs + + ; Check the type of the body + mov bl, BYTE [rax] + and bl, block_mask + container_mask + jnz .bind + ; Just a value (in RAX). No eval needed + + push rax + mov rsi, rax + call incref_object + + ; Release the list passed in RDX + mov rsi, rdx + call release_object + + ; Release the environment + mov rsi, r15 + call release_object + + pop rax + ret +.bind: + ; Create a new environment, binding arguments + push rax + + push rdx + call env_new_bind + pop rdx + + mov rdi, rax ; New environment in RDI + + ; Release the list passed in RDX +.release: + mov rsi, rdx + call release_cons + + ; Release the environment + mov rsi, r15 + call release_object + + pop rsi ; Body + + jmp eval ; Tail call + ; The new environment (in RDI) will be released by eval + + +;; Read-Eval-Print in sequence +rep_seq: + call read_str + mov rsi, rax ; Output of read into input of eval + call eval + mov rsi, rax ; Output of eval into input of print + call pr_str + mov rsi, rax ; Return value + ret + + +_start: + ; Create and print the core environment + call core_environment ; Environment in RAX + + mov [repl_env], rax ; store in memory + + ; Set the error handler + mov rsi, rsp ; Stack pointer + mov rdi, .catch ; Address to jump to + xor rcx, rcx ; No data + call error_handler_push + + ; Evaluate the startup string + + mov rsi, mal_startup_string + mov edx, mal_startup_string.len + call raw_to_string ; String in RAX + + push rax + mov rsi, rax + call read_str ; AST in RAX + pop rsi ; string + + push rax ; AST + call release_array ; string + pop rsi ; AST + + push rsi + mov rdi, [repl_env] ; Environment + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call eval + pop rsi + + push rax + call release_object ; AST + pop rsi + call release_object ; Return from eval + + ; ----------------------------- + ; Main loop + +.mainLoop: + ; print the prompt + print_str_mac prompt_string + + call read_line + + ; Check if we have a zero-length string + cmp DWORD [rax+Array.length], 0 + je .mainLoopEnd + + push rax ; Save address of the input string + + ; Put into read_str + mov rsi, rax + call read_str + push rax ; Save AST + + ; Eval + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call eval + push rax ; Save result + + ; Put into pr_str + mov rsi, rax + mov rdi, 1 ; print_readably + call pr_str + push rax ; Save output string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from pr_str + pop rsi + call release_array + + ; Release result of eval + pop rsi + call release_object + + ; Release the object from read_str + pop rsi + call release_object ; Could be Cons or Array + + ; Release the input string + pop rsi + call release_array + + jmp .mainLoop +.mainLoopEnd: + + jmp quit + +.catch: + ; Jumps here on error + + ; Check if an object was thrown + cmp rsi, 0 + je .catch_done_print ; nothing to print + mov rdi, 1 + call pr_str + mov rsi, rax + call print_string +.catch_done_print: + jmp .mainLoop ; Go back to the prompt + From 1b30ad76a886c519eebc42ed84b4b1ea3cea5221 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sun, 12 Nov 2017 23:46:18 +0000 Subject: [PATCH 0595/2308] Adding support for atoms * Functions atom, atom?, deref, reset! * Printer support for atoms * Type maltype_atom --- nasm/Makefile | 4 + nasm/core.asm | 208 +++++++++++++++++++++++++++++++++++++++++++++++ nasm/printer.asm | 27 ++++++ nasm/types.asm | 10 ++- 4 files changed, 246 insertions(+), 3 deletions(-) diff --git a/nasm/Makefile b/nasm/Makefile index 1756968a9a..298b0e38e9 100644 --- a/nasm/Makefile +++ b/nasm/Makefile @@ -23,3 +23,7 @@ step4_if_fn_do: step4_if_fn_do.asm $(COMPONENTS) step5_tco: step5_tco.asm $(COMPONENTS) nasm -felf64 step5_tco.asm ld -o $@ step5_tco.o + +step6_file: step6_file.asm $(COMPONENTS) + nasm -felf64 step6_file.asm + ld -o $@ step6_file.o diff --git a/nasm/core.asm b/nasm/core.asm index d8330f1c01..da0efd111a 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -34,12 +34,21 @@ section .data static core_read_string_symbol, db "read-string" static core_slurp_symbol, db "slurp" static core_eval_symbol, db "eval" + + static core_atom_symbol, db "atom" + static core_deref_symbol, db "deref" + static core_atomp_symbol, db "atom?" + static core_reset_symbol, db "reset!" ;; Strings static core_emptyp_error_string, db "empty? expects a list, vector or map",10 static core_count_error_string, db "count expects a list or vector",10 static core_numeric_expect_ints, db "comparison operator expected two numbers",10 + + static core_deref_not_atom, db "Error: argument to deref is not an atom" + static core_reset_not_atom, db "Error: argument to reset is not an atom" + static core_reset_no_value, db "Error: missing value argument to reset" section .text ;; Add a native function to the core environment @@ -97,6 +106,11 @@ core_environment: core_env_native core_read_string_symbol, core_read_string core_env_native core_slurp_symbol, core_slurp core_env_native core_eval_symbol, core_eval + + core_env_native core_atom_symbol, core_atom + core_env_native core_deref_symbol, core_deref + core_env_native core_atomp_symbol, core_atomp + core_env_native core_reset_symbol, core_reset ; ----------------- ; Put the environment in RAX @@ -641,3 +655,197 @@ core_eval: call eval ret + +;; Create an atom +core_atom: + push rsi + call alloc_cons ; To hold the pointer + pop rsi + mov [rax], BYTE maltype_atom + + ; Check the type of the first argument + mov bl, BYTE [rsi] + mov bh, bl + and bh, content_mask + cmp bh, content_pointer + je .pointer + + ; A value + + ; make a copy + push rax + push rsi + push rbx + call alloc_cons + pop rbx + + mov bl, bh + mov [rax], BYTE bl ; Set type + + mov rbx, rax + pop rsi + pop rax + + mov rcx, [rsi + Cons.car] + mov [rbx + Cons.car], rcx ; Set value + + ; Set the atom to point to it + mov [rax + Cons.car], rbx + + ret + +.pointer: + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx + + push rax + mov rsi, rbx + call incref_object ; Storing in atom + pop rax + ret + +;; Get the value from the atom +core_deref: + ; Check the type of the first argument + mov bl, BYTE [rsi] + mov bh, bl + and bh, content_mask + cmp bh, content_pointer + jne .not_atom + + ; Get the atom + mov rsi, [rsi + Cons.car] + mov bl, BYTE [rsi] + cmp bl, maltype_atom + jne .not_atom + + ; Return what it points to + mov rsi, [rsi + Cons.car] + call incref_object + mov rax, rsi + ret + +.not_atom: + ; Not an atom, so throw an error + mov rsi, core_deref_not_atom + mov edx, core_deref_not_atom.len + call raw_to_symbol + mov rsi, rax + jmp error_throw + +;; Test if given object is an atom +core_atomp: + mov bl, BYTE [rsi] + mov bh, bl + and bh, content_mask + cmp bh, content_pointer + jne .false + + mov rsi, [rsi + Cons.car] + mov bl, BYTE [rsi] + cmp bl, maltype_atom + jne .false + + ; Got an atom, return true + call alloc_cons + mov [rax], BYTE maltype_true + ret + +.false: + call alloc_cons + mov [rax], BYTE maltype_false + ret + +;; Change the value of an atom +core_reset: + ; Check the type of the first argument + mov bl, BYTE [rsi] + mov bh, bl + and bh, content_mask + cmp bh, content_pointer + jne .not_atom + + ; Get the atom + mov rax, [rsi + Cons.car] ; Atom in RAX + mov bl, BYTE [rax] + cmp bl, maltype_atom + jne .not_atom + + ; Get the next argument + mov bl, BYTE [rsi + Cons.typecdr] + cmp bl, content_pointer + jne .no_value + + mov rsi, [rsi + Cons.cdr] + + ; Got something in RSI + ; release the current value of the atom + push rax + push rsi + + mov rsi, [rax + Cons.car] ; The value the atom points to + call release_object + + pop rsi + pop rax + + ; Check the type of the first argument + mov bl, BYTE [rsi] + mov bh, bl + and bh, content_mask + cmp bh, content_pointer + je .pointer + + ; A value + + ; make a copy + push rax + push rsi + push rbx + call alloc_cons + pop rbx + + mov bl, bh + mov [rax], BYTE bl ; Set type + + mov rbx, rax + pop rsi + pop rax + + mov rcx, [rsi + Cons.car] + mov [rbx + Cons.car], rcx ; Set value + + ; Set the atom to point to it + mov [rax + Cons.car], rbx + + ; Increment refcount since return value will be released + mov rsi, rbx + call incref_object + mov rax, rsi + ret + +.pointer: + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx + + mov rsi, rbx + call incref_object ; Storing in atom + call incref_object ; Returning + mov rax, rsi + ret + +.not_atom: + ; Not an atom, so throw an error + mov rsi, core_reset_not_atom + mov edx, core_reset_not_atom.len + call raw_to_symbol + mov rsi, rax + jmp error_throw + +.no_value: + ; No value given + mov rsi, core_reset_no_value + mov edx, core_reset_no_value.len + call raw_to_symbol + mov rsi, rax + jmp error_throw diff --git a/nasm/printer.asm b/nasm/printer.asm index 2e0b91f66f..a1e8a0627c 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -137,6 +137,9 @@ pr_str: cmp ch, container_function je .function + + cmp ch, container_atom + je .atom ; Unknown mov rsi, unknown_type_string @@ -496,3 +499,27 @@ pr_str: mov edx, function_type_string.len call raw_to_string ; Puts a String in RAX ret + + ; -------------------------------- +.atom: + mov rsi, [rsi + Cons.car] ; What the atom points to + + call string_new ; String in rax + + ; Start string with '(atom' + mov rbx, '(atom ' + mov [rax + Array.data], rbx + mov [rax + Array.length], DWORD 6 + + push rax + call pr_str + mov rdx, rax ; string to be copied + pop rsi ; Output string + + call string_append_string + + ; closing bracket + mov cl, ')' + call string_append_char + mov rax, rsi + ret diff --git a/nasm/types.asm b/nasm/types.asm index 7de8cf6a67..a01d79f462 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -29,7 +29,7 @@ ;; 6 3 - Keyword. Only for Array blocks ;; 8 4 - Map ;; 10 5 - Function -;; 12 6 - Macro +;; 12 6 - Atom ;; 14 7 - Vector ;; ;; Content type [4 bits]: @@ -42,6 +42,7 @@ ;; 96 6 - Function (instruction address) ;; 112 7 - Empty (distinct from Nil) ;; 208 8 - False +;; 224 9 - Macro ;; ;; ;; These represent MAL data types as follows: @@ -58,7 +59,7 @@ ;; string Array Value Char ;; keyword Array Keyword Char ;; hash-map Cons Map Alternate key, values -;; atom Cons Value Pointer +;; atom Cons Atom Pointer ;; %include "macros.mac" @@ -105,7 +106,7 @@ ENDSTRUC %define container_keyword 6 %define container_map 8 %define container_function 10 -%define container_macro 12 +%define container_atom 12 %define container_vector 14 ;; Content type @@ -118,6 +119,7 @@ ENDSTRUC %define content_function 96 ; Function pointer %define content_empty 112 %define content_false 208 +%define content_macro 224 ;; Common combinations for MAL types %define maltype_integer (block_cons + container_value + content_int) @@ -131,6 +133,8 @@ ENDSTRUC %define maltype_macro (block_cons + container_macro + content_function) %define maltype_true (block_cons + container_value + content_true) %define maltype_false (block_cons + container_value + content_false) +%define maltype_atom (block_cons + container_atom + content_pointer) + ;; ------------------------------------------ section .data From be99f655c18859e95b12fd5076657649af9a26d7 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 14 Nov 2017 21:08:43 +0000 Subject: [PATCH 0596/2308] Improved handling of comments, whitespace Previously a comment would end input. Now looks for a line feed and resumes parsing. --- nasm/reader.asm | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/nasm/reader.asm b/nasm/reader.asm index 80939cbe0c..4fe92921f1 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -762,7 +762,7 @@ tokenizer_next: je .handle_tilde cmp cl, ';' ; Start of a comment - je .tokens_finished + je .comment cmp cl, 34 ; Opening string quotes je .handle_string @@ -781,6 +781,20 @@ tokenizer_next: ; Here an integer jmp .handle_integer + +.comment: + ; Start of a comment. Keep reading until a new line or end + + ; Fetch the next char into CL + call tokenizer_next_char + + cmp cl, 0 + je .found ; End, no more tokens + + cmp cl, 10 + je .next_char ; Next line, start reading again + + jmp .comment .handle_minus: @@ -901,6 +915,11 @@ tokenizer_next: cmp cl, ',' ; Comma je .symbol_finished cmp cl, 9 ; Tab + je .symbol_finished + cmp cl, 10 ; Line Feed + je .symbol_finished + cmp cl, 13 ; Carriage Return + je .symbol_finished cmp cl, '(' je .symbol_finished @@ -1006,11 +1025,7 @@ tokenizer_next: ret ; --------------------------------- - -.tokens_finished: - mov cl, 0 ; End of tokens - ret - + .handle_tilde: ; Could have '~' or '~@'. Need to peek at the next char @@ -1034,8 +1049,7 @@ tokenizer_next: pop r11 pop r10 pop r9 - ; fall through to found - + ; fall through to .found .found: ret From ea550eba37889968f1523550e0464bf4641e35ea Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 14 Nov 2017 22:07:12 +0000 Subject: [PATCH 0597/2308] Fixed bug in str and eval, add string_copy * str was not copying the first argument, which was then being appended to. If the str call was inside a function, then a string literal in the function body would be modified with every call, leading to strange behaviour. * eval was crashing on a value input like (eval nil) because the return was not put into rax as it should. Added string_copy, used in str function to copy the first input so that it can then be modified. Most step 6 tests now pass. 10 failing due to missing *ARGV* and swap! --- nasm/core.asm | 28 ++++++++++++++++++++---- nasm/types.asm | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index da0efd111a..4da66b7471 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -506,24 +506,37 @@ core_str_functions: cmp r8, 0 jne .append - ; first string - mov r8, rax ; Output string + ; first string. Since this string will be + ; appended to, it needs to be a copy + push rsi ; input + + push rax ; string to copy + mov rsi, rax + call string_copy ; New string in RAX + pop rsi ; copied string + + push rax ; the copy + call release_object ; release the copied string + pop r8 ; the copy + + pop rsi ; input + jmp .next .append: + push r8 push rsi push rax mov rsi, r8 ; Output string mov rdx, rax ; String to be copied call string_append_string - mov r8, rax pop rsi ; Was in rax, temporary string call release_array ; Release the string pop rsi ; Restore input - + pop r8 ; Output string .next: ; Check if there's another mov al, BYTE [rsi + Cons.typecdr] @@ -537,11 +550,13 @@ core_str_functions: je .end_append_char ; No separator if not printing readably ; Add separator + push r8 push rsi mov rsi, r8 mov cl, ' ' call string_append_char pop rsi + pop r8 .end_append_char: ; Get the type in ah for comparison at start of loop @@ -641,6 +656,11 @@ core_eval: ; Just a value, so return it call incref_object + + mov al, BYTE [rsi] + and al, content_mask + mov [rsi], BYTE al ; Removes list + mov rax, rsi ret .pointer: diff --git a/nasm/types.asm b/nasm/types.asm index a01d79f462..940f910ebf 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -639,6 +639,64 @@ print_string: call print_rawstring ; exit jmp quit_error + +;; Copy a string +;; +;; Input: RSI - String to copy +;; +;; Output: New string in RAX +;; +;; Modifies: +;; RBX +;; RCX +;; RDX +;; RSI +;; +string_copy: + call string_new ; new string in RAX + + push rsi + push rax + + ; Get lengths + mov ebx, DWORD [rsi + Array.length] + mov [rax + Array.length], ebx + + ; Copy the whole block of data + ; Not sure if this is quicker than copying byte-by-byte + ; Could divide ebx by 8 (rounded up) to get the number + ; of blocks needed + + add rsi, Array.data ; Start of input data + add rax, Array.data ; Start of output data + mov ecx, array_chunk_len ; Number of 64-bit chunks + +.loop: + mov rbx, QWORD [rsi] + mov [rax], QWORD rbx + add rsi, 8 + add rax, 8 + dec ecx + jnz .loop + + pop rax + pop rsi + ; Now check if there's another block + mov rsi, [rsi + Array.next] + cmp rsi, 0 + jz .done ; Result in RAX + + ; Another array chunk + push rax ; Save output + + call string_copy ; Copy next chunk + mov rbx, rax ; The copy in RBX + + pop rax + ; append + mov [rax + Array.next], rbx +.done: + ret ;; ------------------------------------------ ;; String itostring(Integer number) From 1f819490b642ffc539d1754612f588acaac813ab Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 16 Nov 2017 22:56:21 +0000 Subject: [PATCH 0598/2308] swap! function tests pass Added a core function swap! It works, but has some warts: * No error message is thrown on error yet, needs adding * To run the function, swap! calls into the middle of eval. This probably indicates that eval needs to be split up. --- nasm/core.asm | 109 +++++++++++++++++++++++++++++++++++++++++++- nasm/printer.asm | 2 + nasm/step6_file.asm | 10 +++- nasm/types.asm | 4 +- 4 files changed, 121 insertions(+), 4 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 4da66b7471..9b783c5869 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -39,7 +39,7 @@ section .data static core_deref_symbol, db "deref" static core_atomp_symbol, db "atom?" static core_reset_symbol, db "reset!" - + static core_swap_symbol, db "swap!" ;; Strings static core_emptyp_error_string, db "empty? expects a list, vector or map",10 @@ -111,6 +111,7 @@ core_environment: core_env_native core_deref_symbol, core_deref core_env_native core_atomp_symbol, core_atomp core_env_native core_reset_symbol, core_reset + core_env_native core_swap_symbol, core_swap ; ----------------- ; Put the environment in RAX @@ -869,3 +870,109 @@ core_reset: call raw_to_symbol mov rsi, rax jmp error_throw + +;; Applies a function to an atom, along with optional arguments +;; +;; In RSI should be a list consisting of +;; [ atom, pointer->Function , args...] +;; +;; The atom is dereferenced, and inserted into the list: +;; +;; [ pointer->Function , atom value , args...] +;; +;; This is then passed to eval.list_exec +;; which executes the function +;; +core_swap: + ; Check the type of the first argument (an atom) + mov bl, BYTE [rsi] + mov bh, bl + and bh, content_mask + cmp bh, content_pointer + jne .not_atom + + ; Get the atom + mov r8, [rsi + Cons.car] ; Atom in R8 + mov bl, BYTE [r8] + cmp bl, maltype_atom + jne .not_atom + + ; Get the second argument (a function) + mov bl, BYTE [rsi + Cons.typecdr] + cmp bl, content_pointer + jne .no_function + + mov r9, [rsi + Cons.cdr] ; List with function first + + ; Get a new Cons to insert into the list + ; containing the value in the atom + call alloc_cons ; In RAX + + ; Splice into the list + mov bl, BYTE [r9 + Cons.typecdr] + mov rcx, [r9 + Cons.cdr] + mov [rax + Cons.typecdr], bl + mov [rax + Cons.cdr], rcx + mov [r9 + Cons.typecdr], BYTE content_pointer + mov [r9 + Cons.cdr], rax + + ; Now get the value in the atom + mov rdx, [r8 + Cons.car] ; The object pointed to + + ; Check what it is + mov bl, BYTE [rdx] + mov bh, bl + and bh, (block_mask + container_mask) + jz .atom_value ; Just a value + + ; Not a simple value, so point to it + mov [rax + Cons.car], rdx + mov [rax], BYTE (container_list + content_pointer) + jmp .list_done + +.atom_value: + ; Copy the value + mov rcx, [rdx + Cons.car] + mov [rax + Cons.car], rcx + and bl, content_mask ; keep just the content + or bl, container_list ; mark as part of a list + mov [rax], BYTE bl + +.list_done: + ; Now have a list with function followed by args + ; This is the same state as after a call to eval_ast + ; + ; Note: Because eval releases the environment in R15 + ; on return, this needs to have its references + ; incremented before the call + ; + ; The list passed in RAX will be released by eval + + mov rsi, r15 + call incref_object + + mov rax, r9 + push r8 ; The atom + call eval.list_exec ; Result in RAX + pop r8 + + ; release the current value of the atom + push rax ; The result + mov rsi, [r8 + Cons.car] + call release_object + pop rsi + + ; Put into atom + mov [r8 + Cons.car], rsi + + ; Increase reference of new object + ; because when it is returned it will be released + push rsi + call incref_object + pop rax + ret + +.not_atom: +.no_function: + xor rsi,rsi + jmp error_throw diff --git a/nasm/printer.asm b/nasm/printer.asm index a1e8a0627c..8ea122869d 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -98,6 +98,8 @@ pr_str: .string_finished: + + mov [r12], BYTE '"' ; At the end inc r12 ; Calculate length of string diff --git a/nasm/step6_file.asm b/nasm/step6_file.asm index e7eaf620aa..8aff290d5d 100644 --- a/nasm/step6_file.asm +++ b/nasm/step6_file.asm @@ -1350,7 +1350,15 @@ eval: call eval_ast ; List of evaluated forms in RAX pop r15 pop rsi - + + +.list_exec: + ; This point can be called to run a function + ; used by swap! + ; + ; Inputs: RAX - List with function as first element + ; NOTE: This list is released + ; ; Check that the first element of the return is a function mov bl, BYTE [rax] and bl, content_mask diff --git a/nasm/types.asm b/nasm/types.asm index 940f910ebf..425d1434a1 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -96,11 +96,11 @@ ENDSTRUC %define content_mask 16 + 32 + 64 + 128 ; Four bits for content type ;; Block types -%define block_cons 0 +%define block_cons 0 ; Note: This must be zero %define block_array 1 ;; Container types -%define container_value 0 +%define container_value 0 ; Note: This must be zero %define container_list 2 %define container_symbol 4 %define container_keyword 6 From 43092cf0e146e60ea79da89014ab7efc795128d2 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 17 Nov 2017 22:08:59 +0000 Subject: [PATCH 0599/2308] Fix bug in swap! for complex types Need to increment the reference count or the object will be garbage collected after evaluation. --- nasm/core.asm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nasm/core.asm b/nasm/core.asm index 9b783c5869..caed13a94d 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -928,6 +928,12 @@ core_swap: ; Not a simple value, so point to it mov [rax + Cons.car], rdx mov [rax], BYTE (container_list + content_pointer) + + ; Since the list will be released after eval + ; we need to increment the reference count + mov rsi, rdx + call incref_object + jmp .list_done .atom_value: From 0a5a3ab3390817d0d0169ce382dd50f4493c3edf Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 17 Nov 2017 23:30:43 +0000 Subject: [PATCH 0600/2308] Adding *ARGV* command-line arguments Can now run a script passed as first argument, and puts any remaining arguments into a list *ARGV*. All step 6 tests now pass Some missing/buggy things: * The error handler should be different to the REPL one, or go into the REPL correctly on error * Strange errors occur if the file is too long. Suspect string problems --- nasm/step6_file.asm | 178 ++++++++++++++++++++++++++++++++++---------- nasm/types.asm | 37 ++++++++- 2 files changed, 174 insertions(+), 41 deletions(-) diff --git a/nasm/step6_file.asm b/nasm/step6_file.asm index 8aff290d5d..c18e58ed6c 100644 --- a/nasm/step6_file.asm +++ b/nasm/step6_file.asm @@ -57,12 +57,15 @@ section .data static_symbol do_symbol, 'do' static_symbol if_symbol, 'if' static_symbol fn_symbol, 'fn*' + + static_symbol argv_symbol, '*ARGV*' ;; Startup string. This is evaluated on startup static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) )" - - -section .text + +;; Command to run, appending the name of the script to run + static run_script_string, db "(load-file ",34 +section .text ;; ---------------------------------------------- ;; @@ -1488,13 +1491,49 @@ apply_fn: ;; Read-Eval-Print in sequence +;; +;; Input string in RSI rep_seq: + ; ------------- + ; Read call read_str - mov rsi, rax ; Output of read into input of eval + push rax ; Save AST + + ; ------------- + ; Eval + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + call eval - mov rsi, rax ; Output of eval into input of print + push rax ; Save result of eval + + ; ------------- + ; Print + + ; Put into pr_str + mov rsi, rax + mov rdi, 1 ; print_readably call pr_str - mov rsi, rax ; Return value + push rax ; Save output string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from pr_str + pop rsi + call release_array + + ; Release result of eval + pop rsi + call release_object + + ; Release the object from read_str + pop rsi + call release_object ; Could be Cons or Array ret @@ -1539,6 +1578,21 @@ _start: call release_object ; AST pop rsi call release_object ; Return from eval + + ; ----------------------------- + ; Check command-line arguments + + pop rax ; Number of arguments + cmp rax, 1 ; Always have at least one, the path to executable + jg run_script + + ; No extra arguments, so just set *ARGV* to an empty list + call alloc_cons ; in RAX + mov [rax], BYTE maltype_empty_list + mov rcx, rax ; value (empty list) + mov rdi, argv_symbol ; symbol (*ARGV*) + mov rsi, [repl_env] ; environment + call env_set ; ----------------------------- ; Main loop @@ -1557,40 +1611,7 @@ _start: ; Put into read_str mov rsi, rax - call read_str - push rax ; Save AST - - ; Eval - mov rsi, rax ; Form to evaluate - mov rdi, [repl_env] ; Environment - - xchg rsi, rdi - call incref_object ; Environment increment refs - xchg rsi, rdi ; since it will be decremented by eval - - call eval - push rax ; Save result - - ; Put into pr_str - mov rsi, rax - mov rdi, 1 ; print_readably - call pr_str - push rax ; Save output string - - mov rsi, rax ; Put into input of print_string - call print_string - - ; Release string from pr_str - pop rsi - call release_array - - ; Release result of eval - pop rsi - call release_object - - ; Release the object from read_str - pop rsi - call release_object ; Could be Cons or Array + call rep_seq ; Release the input string pop rsi @@ -1614,3 +1635,80 @@ _start: .catch_done_print: jmp .mainLoop ; Go back to the prompt + + +run_script: + ; Called with number of command-line arguments in RAX + mov r8, rax + pop rbx ; executable + dec r8 + + pop rsi ; Address of first arg + call cstring_to_string ; string in RAX + mov r9, rax + + ; get the rest of the args + xor r10, r10 ; Zero + dec r8 + jz .no_args + + ; Got some arguments +.arg_loop: + ; Got an argument left. + pop rsi ; Address of C string + call cstring_to_string ; String in RAX + mov r12, rax + + ;Make a Cons to point to the string + call alloc_cons ; in RAX + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], r12 + + test r10, r10 + jnz .append + + ; R10 zero, so first arg + mov r10, rax ; Head of list + mov r11, rax ; Tail of list + jmp .next +.append: + ; R10 not zero, so append to list tail + mov [r11 + Cons.cdr], rax + mov [r11 + Cons.typecdr], BYTE content_pointer + mov r11, rax +.next: + dec r8 + jnz .arg_loop + jmp .got_args + +.no_args: + ; No arguments. Create an emoty list + call alloc_cons ; in RAX + mov [rax], BYTE maltype_empty_list + mov r10, rax + +.got_args: + push r9 ; File name string + + mov rcx, r10 ; value (list) + mov rdi, argv_symbol ; symbol (*ARGV*) + mov rsi, [repl_env] ; environment + call env_set + + mov rsi, run_script_string ; load-file function + mov edx, run_script_string.len + call raw_to_string ; String in RAX + + mov rsi, rax + pop rdx ; File name string + call string_append_string + + mov cl, 34 ; " + call string_append_char + mov cl, ')' + call string_append_char ; closing brace + + ; Read-Eval-Print "(load-file )" + call rep_seq + + jmp quit diff --git a/nasm/types.asm b/nasm/types.asm index 425d1434a1..ee1b400c7b 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -442,6 +442,41 @@ raw_to_symbol: ; set the content type mov [rax], BYTE (block_array + container_symbol + content_char) ret + +;; Convert a NUL terminated C string to string +;; +;; Input: RSI - Address of string +;; +;; Returns: String in RAX +;; +;; Modifies: +;; RBX +;; RCX +;; RDX + +cstring_to_string: + push rsi + call string_new ; in RAX + pop rsi + + mov rbx, rax + add rbx, Array.data ; Start of output + mov rcx, rax + add rcx, Array.size ; End of output +.loop: + mov dl, BYTE [rsi] + test dl, dl ; Check if NUL (0) + jz .done + mov [rbx], BYTE dl + inc rbx + inc rsi + jmp .loop +.done: + sub rbx, rax + sub rbx, Array.data + ; rbx now contains the length + mov [rax + Array.length], DWORD ebx + ret ;; Appends a character to a string ;; Input: Address of string in RSI, character in CL @@ -453,7 +488,7 @@ string_append_char: ; Get the end of the string .get_end: mov rax, [rsi + Array.next] - cmp rax, 0 + test rax, rax jz .got_dest_end mov rsi, rax jmp .get_end From 4459835a1c7222b66be7b6b0b3dc257230ffc7ae Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 18 Nov 2017 14:34:02 +0000 Subject: [PATCH 0601/2308] Fix bug in string_append_string When copying from a long string, the wrong length was used for the source string. This caused junk to get into the destination string. --- nasm/types.asm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nasm/types.asm b/nasm/types.asm index ee1b400c7b..5a49395cd5 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -551,7 +551,7 @@ string_append_string: mov rax, rsi .find_string_end: mov r8, QWORD [rax + Array.next] - cmp r8, 0 ; Next chunk is null + test r8, r8 ; Next chunk is 0 je .got_dest_end ; so reached end mov rax, r8 ; Go to next chunk @@ -580,7 +580,7 @@ string_append_string: ; have reached the end of the source Array mov rbx, QWORD [rbx + Array.next] ; Get the next Array address - cmp rbx, 0 ; Test if it's null + test rbx, rbx ; Test if it's null je .finished ; No more, so we're done ; Move on to next Array object @@ -589,8 +589,8 @@ string_append_string: add r10, Array.data ; Start of the data ; Source end address - mov r11, rbx - add r11, Array.size + mov r11d, DWORD [rbx + Array.length] ; Length of the array + add r11, r10 .source_ok: From 5fc8a017d96ebd9b8d252395b044839d60c4e8de Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 18 Nov 2017 23:04:53 +0000 Subject: [PATCH 0602/2308] Add quote special form Just returns its argument unmodified, but some differences in handling values and pointers needed. Added generic Makefile rule, builds step7_quote --- nasm/Makefile | 7 + nasm/step7_quote.asm | 1749 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1756 insertions(+) create mode 100644 nasm/step7_quote.asm diff --git a/nasm/Makefile b/nasm/Makefile index 298b0e38e9..b04192b7f6 100644 --- a/nasm/Makefile +++ b/nasm/Makefile @@ -27,3 +27,10 @@ step5_tco: step5_tco.asm $(COMPONENTS) step6_file: step6_file.asm $(COMPONENTS) nasm -felf64 step6_file.asm ld -o $@ step6_file.o + +%.o: %.asm + nasm -felf64 $< + +%: %.o + ld -o $@ $< + diff --git a/nasm/step7_quote.asm b/nasm/step7_quote.asm new file mode 100644 index 0000000000..8834a14944 --- /dev/null +++ b/nasm/step7_quote.asm @@ -0,0 +1,1749 @@ +;; +;; nasm -felf64 step7_quote.asm && ld step7_quote.o && ./a.out +;; +;; Calling convention: Address of input is in RSI +;; Address of return value is in RAX +;; + +global _start + +%include "types.asm" ; Data types, memory +%include "env.asm" ; Environment type +%include "system.asm" ; System calls +%include "reader.asm" ; String -> Data structures +%include "core.asm" ; Core functions +%include "printer.asm" ; Data structures -> String + +section .bss + +;; Top-level (REPL) environment +repl_env:resq 1 + +;; Error handler list +error_handler: resq 1 + +section .data + +;; ------------------------------------------ +;; Fixed strings for printing + + static prompt_string, db 10,"user> " ; The string to print at the prompt + + static error_string, db 27,'[31m',"Error",27,'[0m',": " + + static not_found_string, db " not found.",10 + + static def_missing_arg_string, db "missing argument to def!",10 + + static def_expecting_symbol_string, db "expecting symbol as first argument to def!",10 + + static let_missing_bindings_string, db "let* missing bindings",10 + + static let_bindings_list_string, db "let* expected a list or vector of bindings",10 + + static let_bind_symbol_string, db "let* expected a symbol in bindings list",10 + + static let_bind_value_string, db "let* missing value in bindings list",10 + + static let_missing_body_string, db "let* missing body",10 + static eval_list_not_function, db "list does not begin with a function",10 + + static if_missing_condition_string, db "missing condition in if expression",10 + +;; Symbols used for comparison + + static_symbol def_symbol, 'def!' + static_symbol let_symbol, 'let*' + static_symbol do_symbol, 'do' + static_symbol if_symbol, 'if' + static_symbol fn_symbol, 'fn*' + + static_symbol argv_symbol, '*ARGV*' + + static_symbol quote_symbol, 'quote' + static_symbol quasiquote_symbol, 'quasiquote' + +;; Startup string. This is evaluated on startup + static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) )" + +;; Command to run, appending the name of the script to run + static run_script_string, db "(load-file ",34 +section .text + +;; ---------------------------------------------- +;; +;; Error handling +;; +;; A handler consists of: +;; - A stack pointer address to reset to +;; - An address to jump to +;; - An optional data structure to pass +;; +;; When jumped to, an error handler will be given: +;; - the object thrown in RSI +;; - the optional data structure in RDI +;; + + +;; Add an error handler to the front of the list +;; +;; Input: RSI - Stack pointer +;; RDI - Address to jump to +;; RCX - Data structure. Set to zero for none. +;; If not zero, reference count incremented +;; +;; Modifies registers: +;; RAX +;; RBX +error_handler_push: + call alloc_cons + ; car will point to a list (stack, addr, data) + ; cdr will point to the previous handler + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov rbx, [error_handler] + cmp rbx, 0 ; Check if previous handler was zero + je .create_handler ; Zero, so leave null + ; Not zero, so create pointer to it + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rbx + + ; note: not incrementing reference count, since + ; we're replacing one reference with another +.create_handler: + mov [error_handler], rax ; new error handler + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.car], rax + ; Store stack pointer + mov [rax], BYTE (block_cons + container_list + content_function) + mov [rax + Cons.car], rsi ; stack pointer + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.typecdr], BYTE content_pointer + mov [rdx + Cons.cdr], rax + ; Store function pointer to jump to + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], rdi + + ; Check if there is an object to pass to handler + cmp rcx, 0 + je .done + + ; Set the final CDR to point to the object + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rcx + + mov rsi, rcx + call incref_object + +.done: + ret + + +;; Removes an error handler from the list +;; +;; Modifies registers: +;; RSI +;; RAX +;; RCX +error_handler_pop: + ; get the address + mov rsi, [error_handler] + cmp rsi, 0 + je .done ; Nothing to remove + + push rsi + mov rsi, [rsi + Cons.cdr] ; next handler + mov [error_handler], rsi + call incref_object ; needed because releasing soon + + pop rsi ; handler being removed + call release_cons + +.done: + ret + + +;; Throw an error +;; Object to pass to handler should be in RSI +error_throw: + ; Get the next error handler + mov rax, [error_handler] + cmp rax, 0 + je .no_handler + + ; Got a handler + mov rax, [rax + Cons.car] ; handler + mov rbx, [rax + Cons.car] ; stack pointer + mov rax, [rax + Cons.cdr] + mov rcx, [rax + Cons.car] ; function + mov rdi, [rax + Cons.cdr] ; data structure + + ; Reset stack + mov rsp, rbx + + ; Jump to the handler + jmp rcx + +.no_handler: + ; Print the object in RSI then quit + cmp rsi, 0 + je .done ; nothing to print + mov rdi, 1 ; print_readably + call pr_str + mov rsi, rax + call print_string +.done: + jmp quit_error + +;; ---------------------------------------------- +;; Evaluates a form +;; +;; Inputs: RSI Form to evaluate +;; RDI Environment +;; +eval_ast: + mov r15, rdi ; Save Env in r15 + + ; Check the type + mov al, BYTE [rsi] + + ; Check if this is a list + mov ah, al + and ah, container_mask + cmp ah, container_list + je .list + + cmp ah, container_map + je .map + + cmp ah, container_vector + je .vector + + ; Not a list, map or vector + cmp ah, container_symbol + je .symbol + + ; Not a symbol, list, map or vector + call incref_object ; Increment reference count + + mov rax, rsi + ret + +.symbol: + ; Check if first character of symbol is ':' + mov al, BYTE [rsi + Array.data] + cmp al, ':' + je .keyword + + ; look in environment + push rsi + xchg rsi, rdi + ; symbol is the key in rdi + ; Environment in rsi + call env_get + pop rsi + je .done ; result in RAX + + ; Not found, throw an error + push rsi + print_str_mac error_string ; print 'Error: ' + + pop rsi + push rsi + mov edx, [rsi + Array.length] + add rsi, Array.data + call print_rawstring ; print symbol + + print_str_mac not_found_string ; print ' not found' + pop rsi + + jmp error_throw + + ; ------------------------------ + +.keyword: + ; Just return keywords unaltered + call incref_object + mov rax, rsi + ret + + ; ------------------------------ +.list: + ; Evaluate each element of the list + ; + xor r8, r8 ; The list to return + ; r9 contains head of list + +.list_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .list_pointer + + ; A value in RSI, so copy + + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_list) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .list_append + +.list_pointer: + ; List element is a pointer to something + push rsi + push r8 + push r9 + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Get the address + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi + + call eval ; Evaluate it, result in rax + pop r15 + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .list_eval_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_list + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .list_append + +.list_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_list) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi + + ; Fall through to .list_append +.list_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .list_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .list_next + +.list_first: + mov r8, rax + mov r9, rax + ; fall through to .list_next + +.list_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .list_done ; finished list + mov rsi, [rsi + Cons.cdr] ; next in list + jmp .list_loop + +.list_done: + mov rax, r8 ; Return the list + ret + + ; --------------------- +.map: + ; Create a new map, evaluating all the values + + ; Check if the map is empty + cmp al, maltype_empty_map + jne .map_not_empty + + ; map empty. Just return it + call incref_object + mov rax, rsi + ret + +.map_not_empty: + + mov r10, rsi ; input in R10 + xor r12, r12 ; New map in r12 + + ; Now loop through each key-value pair + ; NOTE: This method relies on the implementation + ; of map as a list + +.map_loop: + ; Copy the key + call alloc_cons ; New Cons in RAX + + mov bl, [r10 + Cons.typecar] ; Type in BL + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] ; Value in RCX + mov [rax + Cons.car], rcx + + ; Check the type of the key + and bl, content_mask + cmp bl, content_pointer + jne .map_got_key ; a value + + ; a pointer, so increment reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.map_got_key: + cmp r12,0 + jne .append_key + + ; First key + mov r12, rax + mov r13, rax + jmp .map_value + +.append_key: + ; Appending to previous value in r13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + +.map_value: + ; Check that we have a value + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_error_missing_value + mov r10, [r10 + Cons.cdr] + + ; Now got value in r10 + + ; Check the type of the value + mov bl, [r10 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .map_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r10 + Cons.typecar] + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .map_got_value +.map_value_pointer: + ; A pointer, so need to evaluate + push r10 ; Input + push r12 ; start of result + push r13 ; Current head of result + push r15 ; Env + mov rsi, [r10 + Cons.car] ; Get the address + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi + + call eval ; Evaluate it, result in rax + pop r15 + pop r13 + pop r12 + pop r10 + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + + jne .map_eval_pointer + + ; A value, so just change the type to a map + and bl, content_mask + add bl, (block_cons + container_map) + mov [rax], BYTE bl + jmp .map_got_value + +.map_eval_pointer: + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_map + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + +.map_got_value: + ; Append RAX to list in R13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + + ; Check if there's another key + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_done ; finished map + mov r10, [r10 + Cons.cdr] ; next in map + jmp .map_loop + +.map_done: + mov rax, r12 + ret + +.map_error_missing_value: + mov rax, r12 + ret + + ; ------------------------------ +.vector: + ; Evaluate each element of the vector + ; + xor r8, r8 ; The vector to return + ; r9 contains head of vector + +.vector_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .vector_pointer + + ; A value, so copy + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_vector) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .vector_append + +.vector_pointer: + ; Vector element is a pointer to something + push rsi + push r8 + push r9 + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Get the address + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi + + call eval ; Evaluate it, result in rax + pop r15 + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .vector_eval_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_vector + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .vector_append + +.vector_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_vector) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi + +.vector_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .vector_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .vector_next + +.vector_first: + mov r8, rax + mov r9, rax + ; fall through to .vector_next + +.vector_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .vector_done ; finished vector + mov rsi, [rsi + Cons.cdr] ; next in vector + jmp .vector_loop + +.vector_done: + mov rax, r8 ; Return the vector + ret + + ; --------------------- +.done: + ret + + + +;; Comparison of symbols for eval function +;; Compares the symbol in RSI with specified symbol +;; Preserves RSI and RBX +;; Modifies RDI +%macro eval_cmp_symbol 1 + push rsi + push rbx + mov rsi, rbx + mov rdi, %1 + call compare_char_array + pop rbx + pop rsi + test rax, rax ; ZF set if rax = 0 (equal) +%endmacro + +;; ---------------------------------------------------- +;; Evaluates a form +;; +;; Input: RSI Form to evaluate +;; RDI Environment +;; +;; Returns: Result in RAX +;; +;; Note: The environment in RDI will have its reference count +;; reduced by one (released). This is to make tail call optimisation easier +;; +eval: + mov r15, rdi ; Env + + ; Check type + mov al, BYTE [rsi] + cmp al, maltype_empty_list + je .return_nil + + and al, container_mask + cmp al, container_list + je .list + + ; Not a list. Evaluate and return + call eval_ast + jmp .return ; Releases Env + + ; -------------------- +.list: + ; A list + + ; Check if the first element is a symbol + mov al, BYTE [rsi] + + and al, content_mask + cmp al, content_pointer + jne .list_eval + + mov rbx, [rsi + Cons.car] + mov al, BYTE [rbx] + cmp al, maltype_symbol + jne .list_eval + + ; Is a symbol, address in RBX + + ; Compare against special form symbols + + eval_cmp_symbol def_symbol ; def! + je .def_symbol + + eval_cmp_symbol let_symbol ; let* + je .let_symbol + + eval_cmp_symbol do_symbol ; do + je .do_symbol + + eval_cmp_symbol if_symbol ; if + je .if_symbol + + eval_cmp_symbol fn_symbol ; fn + je .fn_symbol + + eval_cmp_symbol quote_symbol ; quote + je .quote_symbol + + ; Unrecognised + jmp .list_eval + +.def_symbol: + ; Define a new symbol in current environment + + ; Next item should be a symbol + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Now should have a symbol + + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + jne .def_error_expecting_symbol + mov r8, [rsi + Cons.car] ; Symbol (?) + + mov al, BYTE [r8] + cmp al, maltype_symbol + jne .def_error_expecting_symbol + + ; R8 now contains a symbol + + ; expecting a value or pointer next + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a pointer + mov al, BYTE [rsi] + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .def_pointer + + ; A value, so copy + push rax + call alloc_cons + pop rbx ; BL now contains type + and bl, content_mask + add bl, (block_cons + container_value) + mov [rax], BYTE bl + mov rcx, [rsi + Cons.car] + mov [rax + Cons.car], rcx + mov rsi, rax + + jmp .def_got_value + +.def_pointer: + ; A pointer, so evaluate + + ; This may throw an error, so define a handler + + + push r8 ; the symbol + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Pointer + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call eval + mov rsi, rax + pop r15 + pop r8 + +.def_got_value: + ; Symbol in R8, value in RSI + mov rdi, r8 ; key (symbol) + mov rcx, rsi ; Value + mov rsi, r15 ; Environment + call env_set + + mov rax, rcx + jmp .return + +.def_error_missing_arg: + mov rsi, def_missing_arg_string + mov rdx, def_missing_arg_string.len + jmp .def_handle_error + +.def_error_expecting_symbol: + mov rsi, def_expecting_symbol_string + mov rdx, def_expecting_symbol_string.len + jmp .def_handle_error + +.def_handle_error: + push rsi + push rdx + print_str_mac error_string ; print 'Error: ' + + pop rdx + pop rsi + call print_rawstring ; print message + + xor rsi, rsi ; no object to throw + jmp error_throw ; No return + + ; ----------------------------- +.let_symbol: + ; Create a new environment + + mov r11, rsi ; Let form in R11 + + mov rsi, r15 ; Outer env + call env_new ; Increments R15's ref count + mov r14, rax ; New environment in R14 + + mov rsi, r15 + call release_object ; Decrement R15 ref count + + ; Second element should be the bindings + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_bindings + mov r11, [r11 + Cons.cdr] + + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .let_error_bindings_list + + mov r12, [r11 + Cons.car] ; should be bindings list + mov al, BYTE [r12] + and al, (block_mask + container_mask) + ; Can be either a list or vector + cmp al, block_cons + container_list + je .let_bind_loop + cmp al, block_cons + container_vector + je .let_bind_loop + + ; Not a list or vector + jmp .let_error_bindings_list + +.let_bind_loop: + ; R12 now contains a list with an even number of items + ; The first should be a symbol, then a value to evaluate + + ; Get the symbol + mov al, BYTE [r12] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_symbol + + mov r13, [r12 + Cons.car] ; Symbol (?) + mov al, BYTE [r13] + cmp al, maltype_symbol + jne .let_error_bind_symbol + + ; R13 now contains a symbol to bind + ; The next item in the bindings list (R12) + ; should be a value or expression to evaluate + + mov al, BYTE [r12 + Cons.typecdr] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_value + mov r12, [r12 + Cons.cdr] + + ; got value in R12 + + ; Check the type of the value + mov bl, [r12 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .let_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r12 + Cons.typecar] + and bl, content_mask + ;or bl, (block_cons + container_value) ; 0 + mov [rax + Cons.typecar], bl + mov rcx, [r12 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .let_got_value + +.let_value_pointer: + ; A pointer, so need to evaluate + push r11 ; let* form list + push r12 ; Position in bindings list + push r13 ; symbol to bind + push r14 ; new environment + mov rsi, [r12 + Cons.car] ; Get the address + mov rdi, r14 + call eval ; Evaluate it, result in rax + pop r14 + pop r13 + pop r12 + pop r11 + +.let_got_value: + + mov rsi, r14 ; Env + mov rdi, r13 ; key + mov rcx, rax ; value + call env_set + + ; Release the value + mov rsi, rcx ; The value + call release_object + + ; Check if there are more bindings + mov al, BYTE [r12 + Cons.typecdr] + cmp al, content_pointer + jne .let_done_binding + mov r12, [r12 + Cons.cdr] ; Next + jmp .let_bind_loop + +.let_done_binding: + ; Done bindings. + ; Evaluate next item in let* form in new environment + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_body + mov r11, [r11 + Cons.cdr] ; Now contains value to evaluate + ; Check type of the value + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + je .body_pointer + + ; Just a value, so copy + call alloc_cons + mov bl, BYTE [r11] + and bl, content_mask + mov [rax], BYTE bl ; set type + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx ; copy value + jmp .let_done + +.body_pointer: + ; Evaluate using new environment + + mov rsi, [r11 + Cons.car] ; Object pointed to + mov rdi, r14 ; New environment + + jmp eval ; Tail call + ; Note: eval will release the new environment on return + +.let_done: + ; Release the new environment + push rax + mov rsi, r14 + call release_object + pop rax + ret ; already released env + +.let_error_missing_bindings: + mov rsi, let_missing_bindings_string + mov rdx, let_missing_bindings_string.len + jmp .let_handle_error + +.let_error_bindings_list: ; expected a list or vector, got something else + mov rsi, let_bindings_list_string + mov rdx, let_bindings_list_string.len + jmp .let_handle_error + +.let_error_bind_symbol: ; expected a symbol, got something else + mov rsi, let_bind_symbol_string + mov rdx, let_bind_symbol_string.len + jmp .let_handle_error + +.let_error_bind_value: ; Missing value in binding list + mov rsi, let_bind_value_string + mov rdx, let_bind_value_string.len + jmp .let_handle_error + +.let_error_missing_body: ; Missing body to evaluate + mov rsi, let_missing_body_string + mov rdx, let_missing_body_string.len + jmp .let_handle_error + +.let_handle_error: + push r11 ; For printing later + + push rsi + push rdx + + print_str_mac error_string ; print 'Error: ' + + pop rdx + pop rsi + call print_rawstring ; print message + + pop rsi ; let* form + jmp error_throw ; No return + + ; ----------------------------- + +.do_symbol: + mov r11, rsi ; do form in RSI + ; Environment in R15 + + ; Check if there is a body + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .do_no_body ; error + + mov r11, [r11 + Cons.cdr] ; Body in R11 + +.do_symbol_loop: + + ; Need to test if this is the last form + ; so we can handle tail call + + mov bl, BYTE [r11 + Cons.typecdr] + cmp bl, content_pointer + jne .do_body_last ; Last expression + + ; not the last expression + + ; Check if this is a value or pointer + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + jne .do_next ; A value, so skip + + ; A pointer, so evaluate + + push r15 + push r11 + + mov rsi, r15 + call incref_object ; Increase Env reference + ; since eval will release Env + + mov rsi, [r11 + Cons.car] ; Form + mov rdi, r15 ; Env + call eval ; Result in RAX + + ; Another form after this. + ; Discard the result of the last eval + mov rsi, rax + call release_object + + pop r11 + pop r15 + +.do_next: + mov r11, [r11 + Cons.cdr] ; Next in list + + jmp .do_symbol_loop + +.do_body_last: + ; The last form is in R11, which will be returned + + ; Check if this is a value or pointer + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + jne .do_body_value_return + jmp .do_body_expr_return + +.do_body_value_return: + ; Got a value as last form (in R11). + ; Copy and return + + push rax ; Type of value to return + + ; release Env + mov rsi, r15 + call release_object + + ; Allocate a Cons object to hold value + call alloc_cons + pop rbx ; type in BL + mov [rax], BYTE bl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + ret + +.do_body_expr_return: + ; An expression to evaluate as the last form + ; Tail call optimise, jumping to eval + ; Don't increment Env reference count + + mov rsi, [r11 + Cons.car] ; Form + mov rdi, r15 ; Env + jmp eval ; Tail call + +.do_no_body: + ; No expressions to evaluate. Return nil + + mov rsi, r15 + call release_object ; Release Env + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret + + ; ----------------------------- + +.if_symbol: + mov r11, rsi ; if form in R11 + ; Environment in R15 + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .if_no_condition + + mov r11, [r11 + Cons.cdr] ; Should be a condition + + ; Check if value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .if_cond_value + + ; A pointer, so evaluate + + push r15 + push r11 + + mov rsi, r15 + call incref_object ; Increase Env reference + + mov rsi, [r11 + Cons.car] ; Form + mov rdi, r15 ; Env + call eval ; Result in RAX + pop r11 + pop r15 + + ; Get type of result + mov bl, BYTE [rax] + + ; release value + push rbx + mov rsi, rax + call release_object + pop rbx + + ; Check type + cmp bl, maltype_nil + je .if_false + cmp bl, maltype_false + je .if_false + + jmp .if_true + +.if_cond_value: + + ; A value + cmp al, content_nil + je .if_false + cmp al, content_false + je .if_false + + jmp .if_true + +.if_false: + ; Skip the next item + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .return_nil + + mov r11, [r11 + Cons.cdr] + +.if_true: + ; Get the next item in the list and evaluate it + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; Nothing to return + + mov r11, [r11 + Cons.cdr] + + ; Check if value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + je .if_got_pointer + +.if_got_value: + ; copy value in r11 + call alloc_cons + mov bl, BYTE [r11] + and bl, content_mask + mov [rax], BYTE bl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + + jmp .return + +.if_got_pointer: + mov rsi, [r11 + Cons.car] ; Form + mov rdi, r15 ; Env + jmp eval ; Tail call + +.if_no_condition: ; just (if) without a condition + + print_str_mac error_string + print_str_mac if_missing_condition_string + + ; Release environment + mov rsi, r15 + call release_object + xor rsi, rsi ; No object to throw + jmp error_throw + +.return_nil: + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + +.return: + push rax + ; Release environment + mov rsi, r15 + call release_object + pop rax + + ret + + ; ----------------------------- + +.fn_symbol: + mov r11, rsi ; fn form in R11 + ; Environment in R15 + + ; Get the binds and body of the function + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .fn_empty + + mov r11, [r11 + Cons.cdr] + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .fn_binds_not_list + + mov r12, [r11 + Cons.car] ; Should be binds list + mov al, BYTE [r12] + and al, (block_mask + container_mask) + cmp al, (block_cons + container_list) + je .fn_got_binds ; Can be list + cmp al, (block_cons + container_vector) + je .fn_got_binds ; or vector + jmp .fn_binds_not_list + +.fn_got_binds: + + ; Next get the body of the function + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .fn_no_body + + mov r11, [r11 + Cons.cdr] + ; Check value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .fn_is_value ; Body in r11 + mov r11, [r11 + Cons.car] + jmp .fn_got_body + +.fn_is_value: + ; Body is just a value, no expression + mov [r11], BYTE al ; Mark as value, not list + +.fn_got_body: + + ; Now put into function type + ; Addr is "apply_fn", the address to call + ; Env in R15 + ; Binds in R12 + ; Body in R11 + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_function) + mov rbx, apply_fn + mov [rax + Cons.car], rbx ; Address of apply function + mov [rax + Cons.typecdr], BYTE content_pointer + + mov r13, rax ; Return list in R13 + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r15 ; Environment + mov [rax + Cons.typecdr], BYTE content_pointer + + mov [r13 + Cons.cdr], rax ; Append to list + mov r14, rax + + push rax + mov rsi, r15 + call incref_object + pop rax + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r12 ; Binds list + mov [rax + Cons.typecdr], BYTE content_pointer + + mov [r14 + Cons.cdr], rax ; Append to list + mov r14, rax + + push rax + mov rsi, r12 + call incref_object + pop rax + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r11 ; Body of function + + mov [r14 + Cons.cdr], rax + + mov rsi, r11 + call incref_object + + mov rax, r13 + jmp .return + +.fn_empty: +.fn_binds_not_list: +.fn_no_body: + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + jmp .return + + ; ----------------------------- + +.quote_symbol: + ; Just return the arguments in rsi cdr + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; quote empty, so return nil + + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a value or pointer + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + je .quote_pointer + + ; RSI contains a value. Remove the list container + mov [rsi + Cons.typecar], BYTE al + call incref_object + mov rax, rsi + jmp .return + +.quote_pointer: + ; RSI contains a pointer, so get the object pointed to + mov rsi, [rsi + Cons.car] + call incref_object + mov rax, rsi + jmp .return + + ; ----------------------------- + +.list_eval: + push rsi + mov rdi, r15 ; Environment + push r15 + call eval_ast ; List of evaluated forms in RAX + pop r15 + pop rsi + + +.list_exec: + ; This point can be called to run a function + ; used by swap! + ; + ; Inputs: RAX - List with function as first element + ; NOTE: This list is released + ; + ; Check that the first element of the return is a function + mov bl, BYTE [rax] + and bl, content_mask + cmp bl, content_pointer + jne .list_not_function + + mov rbx, [rax + Cons.car] ; Get the address + mov cl, BYTE [rbx] + cmp cl, maltype_function + jne .list_not_function + + ; Check the rest of the args + mov cl, BYTE [rax + Cons.typecdr] + cmp cl, content_pointer + je .list_got_args + + ; No arguments + push rbx + call alloc_cons + mov [rax], BYTE maltype_empty_list + pop rbx + mov rsi, rax + jmp .list_function_call +.list_got_args: + mov rsi, [rax + Cons.cdr] ; Rest of list +.list_function_call: + ; Call the function with the rest of the list in RSI + + mov rdx, rax ; List to release + mov rdi, rbx ; Function object in RDI + + mov rbx, [rbx + Cons.car] ; Call function + cmp rbx, apply_fn + je apply_fn ; Jump to user function apply + + ; A built-in function, so call (no recursion) + push rax + push r15 + + call rbx + + ; Result in rax + pop r15 + pop rsi ; eval'ed list + + push rax + call release_cons + pop rax + + jmp .return ; Releases Env + +.list_not_function: + ; Not a function. Probably an error + push rsi + + mov rsi, rax + call release_object + + print_str_mac error_string + print_str_mac eval_list_not_function + pop rsi + jmp error_throw + + +;; Applies a user-defined function +;; +;; Input: RSI - Arguments to bind +;; RDI - Function object +;; RDX - list to release after binding +;; R15 - Env (will be released) +;; +;; Output: Result in RAX +apply_fn: + push rsi + ; Extract values from the list in RDI + mov rax, [rdi + Cons.cdr] + mov rsi, [rax + Cons.car] ; Env + mov rax, [rax + Cons.cdr] + mov rdi, [rax + Cons.car] ; Binds + mov rax, [rax + Cons.cdr] + mov rax, [rax + Cons.car] ; Body + pop rcx ; Exprs + + ; Check the type of the body + mov bl, BYTE [rax] + and bl, block_mask + container_mask + jnz .bind + ; Just a value (in RAX). No eval needed + + push rax + mov rsi, rax + call incref_object + + ; Release the list passed in RDX + mov rsi, rdx + call release_object + + ; Release the environment + mov rsi, r15 + call release_object + + pop rax + ret +.bind: + ; Create a new environment, binding arguments + push rax + + push rdx + call env_new_bind + pop rdx + + mov rdi, rax ; New environment in RDI + + ; Release the list passed in RDX +.release: + mov rsi, rdx + call release_cons + + ; Release the environment + mov rsi, r15 + call release_object + + pop rsi ; Body + + jmp eval ; Tail call + ; The new environment (in RDI) will be released by eval + + +;; Read-Eval-Print in sequence +;; +;; Input string in RSI +rep_seq: + ; ------------- + ; Read + call read_str + push rax ; Save AST + + ; ------------- + ; Eval + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call eval + push rax ; Save result of eval + + ; ------------- + ; Print + + ; Put into pr_str + mov rsi, rax + mov rdi, 1 ; print_readably + call pr_str + push rax ; Save output string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from pr_str + pop rsi + call release_array + + ; Release result of eval + pop rsi + call release_object + + ; Release the object from read_str + pop rsi + call release_object ; Could be Cons or Array + ret + + +_start: + ; Create and print the core environment + call core_environment ; Environment in RAX + + mov [repl_env], rax ; store in memory + + ; Set the error handler + mov rsi, rsp ; Stack pointer + mov rdi, .catch ; Address to jump to + xor rcx, rcx ; No data + call error_handler_push + + ; Evaluate the startup string + + mov rsi, mal_startup_string + mov edx, mal_startup_string.len + call raw_to_string ; String in RAX + + push rax + mov rsi, rax + call read_str ; AST in RAX + pop rsi ; string + + push rax ; AST + call release_array ; string + pop rsi ; AST + + push rsi + mov rdi, [repl_env] ; Environment + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call eval + pop rsi + + push rax + call release_object ; AST + pop rsi + call release_object ; Return from eval + + ; ----------------------------- + ; Check command-line arguments + + pop rax ; Number of arguments + cmp rax, 1 ; Always have at least one, the path to executable + jg run_script + + ; No extra arguments, so just set *ARGV* to an empty list + call alloc_cons ; in RAX + mov [rax], BYTE maltype_empty_list + mov rcx, rax ; value (empty list) + mov rdi, argv_symbol ; symbol (*ARGV*) + mov rsi, [repl_env] ; environment + call env_set + + ; ----------------------------- + ; Main loop + +.mainLoop: + ; print the prompt + print_str_mac prompt_string + + call read_line + + ; Check if we have a zero-length string + cmp DWORD [rax+Array.length], 0 + je .mainLoopEnd + + push rax ; Save address of the input string + + ; Put into read_str + mov rsi, rax + call rep_seq + + ; Release the input string + pop rsi + call release_array + + jmp .mainLoop +.mainLoopEnd: + + jmp quit + +.catch: + ; Jumps here on error + + ; Check if an object was thrown + cmp rsi, 0 + je .catch_done_print ; nothing to print + mov rdi, 1 + call pr_str + mov rsi, rax + call print_string +.catch_done_print: + jmp .mainLoop ; Go back to the prompt + + + +run_script: + ; Called with number of command-line arguments in RAX + mov r8, rax + pop rbx ; executable + dec r8 + + pop rsi ; Address of first arg + call cstring_to_string ; string in RAX + mov r9, rax + + ; get the rest of the args + xor r10, r10 ; Zero + dec r8 + jz .no_args + + ; Got some arguments +.arg_loop: + ; Got an argument left. + pop rsi ; Address of C string + call cstring_to_string ; String in RAX + mov r12, rax + + ;Make a Cons to point to the string + call alloc_cons ; in RAX + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], r12 + + test r10, r10 + jnz .append + + ; R10 zero, so first arg + mov r10, rax ; Head of list + mov r11, rax ; Tail of list + jmp .next +.append: + ; R10 not zero, so append to list tail + mov [r11 + Cons.cdr], rax + mov [r11 + Cons.typecdr], BYTE content_pointer + mov r11, rax +.next: + dec r8 + jnz .arg_loop + jmp .got_args + +.no_args: + ; No arguments. Create an emoty list + call alloc_cons ; in RAX + mov [rax], BYTE maltype_empty_list + mov r10, rax + +.got_args: + push r9 ; File name string + + mov rcx, r10 ; value (list) + mov rdi, argv_symbol ; symbol (*ARGV*) + mov rsi, [repl_env] ; environment + call env_set + + mov rsi, run_script_string ; load-file function + mov edx, run_script_string.len + call raw_to_string ; String in RAX + + mov rsi, rax + pop rdx ; File name string + call string_append_string + + mov cl, 34 ; " + call string_append_char + mov cl, ')' + call string_append_char ; closing brace + + ; Read-Eval-Print "(load-file )" + call rep_seq + + jmp quit From a69523e790f9b7c95f947ccd6dfd88bdff36e7ca Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 20 Nov 2017 23:37:52 +0000 Subject: [PATCH 0603/2308] Add cons, concat functions * Helper function cons_seq_copy, which copies a list or vector This is used in concat to copy prepended lists (final list not copied). * Bug fix in printer, which would segfault if the container of a Cons did not match that of the first Cons e.g. a vector in a list. * Seems to be a bug in print, which now segfaults on printing *env* --- nasm/core.asm | 201 +++++++++++++++++++++++++++++++++++++++++++++++ nasm/printer.asm | 20 +++-- nasm/types.asm | 79 +++++++++++++++++++ 3 files changed, 292 insertions(+), 8 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index caed13a94d..d2df3d386f 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -40,6 +40,10 @@ section .data static core_atomp_symbol, db "atom?" static core_reset_symbol, db "reset!" static core_swap_symbol, db "swap!" + + static core_cons_symbol, db "cons" + static core_concat_symbol, db "concat" + ;; Strings static core_emptyp_error_string, db "empty? expects a list, vector or map",10 @@ -49,6 +53,11 @@ section .data static core_deref_not_atom, db "Error: argument to deref is not an atom" static core_reset_not_atom, db "Error: argument to reset is not an atom" static core_reset_no_value, db "Error: missing value argument to reset" + + static core_cons_missing_arg, db "Error: missing argument to cons" + static core_cons_not_vector, db "Error: cons expects a list or vector" + + static core_concat_not_list, db "Error: concat expects lists or vectors" section .text ;; Add a native function to the core environment @@ -112,6 +121,9 @@ core_environment: core_env_native core_atomp_symbol, core_atomp core_env_native core_reset_symbol, core_reset core_env_native core_swap_symbol, core_swap + + core_env_native core_cons_symbol, core_cons + core_env_native core_concat_symbol, core_concat ; ----------------- ; Put the environment in RAX @@ -982,3 +994,192 @@ core_swap: .no_function: xor rsi,rsi jmp error_throw + + +;; Takes two arguments, and prepends the first argument onto the second +;; The second argument can be a list or a vector, but the return is always +;; a list +core_cons: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_empty + je .missing_args + + mov r8, rsi ; The object to prepend + + ; Check if there's a second argument + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .missing_args + + mov rsi, [rsi + Cons.cdr] + + ; Check that the second argument is a list or vector + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .not_vector + + mov r9, [rsi + Cons.car] ; Should be a list or vector + mov al, BYTE [r9] + and al, container_mask + cmp al, container_list + je .got_args + cmp al, container_vector + je .got_args + jmp .not_vector + +.got_args: + ; Got an object in R8 and list/vector in R9 + + ;call alloc_cons + ;mov r9, rax + ;mov [r9], BYTE container_list + content_nil ;; NOTE: Segfault if list changed to vector. Printer? + + call alloc_cons ; new Cons in RAX + + ; Mark as the same content in a list container + mov bl, BYTE [r8] + and bl, content_mask + mov bh, bl ; Save content in BH for checking if pointer later + or bl, block_cons + container_list + mov [rax], BYTE bl + + ; Copy the content + mov rcx, [r8 + Cons.car] + mov [rax + Cons.car], rcx + + ; Put the list into CDR + mov [rax + Cons.cdr], r9 + ; mark CDR as a pointer + mov [rax + Cons.typecdr], BYTE content_pointer + + push rax ; popped before return + + ; Check if the new Cons contains a pointer + cmp bh, content_pointer + jne .done + + ; A pointer, so increment number of references + mov rsi, rcx + call incref_object + +.done: + ; Increment reference count of list + mov rsi, r9 + call incref_object + pop rax + + ret + +.missing_args: + mov rsi, core_cons_missing_arg + mov edx,core_cons_missing_arg.len + jmp .throw + +.not_vector: + mov rsi, core_cons_not_vector + mov edx, core_cons_not_vector.len + +.throw: + call raw_to_string + mov rsi, rax + jmp error_throw + + +;; Concatenate lists, returning a new list +;; +;; Notes: +;; * The last list does not need to be copied, but all others do +;; +core_concat: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_empty + je .missing_args + + cmp al, content_pointer + jne .not_list + + ; Check if there is only one argument + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + je .start_loop ; Start copy loop + + ; Only one input. + ; Just increment reference count and return + + mov rsi, [rsi + Cons.car] + call incref_object + mov rax, rsi + ret + +.start_loop: ; Have at least two inputs + xor r11, r11 ; Head of list. Start in R12 + +.loop: + + ; Check the type + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .not_list + + ; Check if this is the last + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .last + + ; not the last list, so need to copy + + push rsi + mov rsi, [rsi + Cons.car] ; The list + call cons_seq_copy ; Copy in RAX + pop rsi + + ; Check if this is the first + test r11, r11 + jnz .append + + ; First list + mov r11, rbx ; Last Cons in list + mov r12, rax ; Output list + jmp .next +.append: + ; End of previous list points to start of new list + mov [r11 + Cons.cdr], rax + mov [r11 + Cons.typecdr], BYTE content_pointer + ; Put end of new list into R11 + mov r11, rbx + +.next: + mov rsi, [rsi + Cons.cdr] + jmp .loop + +.last: + ; last list, so can just prepend + mov rsi, [rsi + Cons.car] + + call incref_object + + mov [r11 + Cons.cdr], rsi + mov [r11 + Cons.typecdr], BYTE content_pointer + + mov rax, r12 ; output list + ret + +.missing_args: + ; Return nil + call alloc_cons + mov [rax], BYTE maltype_nil + ret + +.not_list: + ; Got an argument which is not a list + mov rsi, core_concat_not_list + mov edx, core_concat_not_list.len + +.throw: + call raw_to_string + mov rsi, rax + jmp error_throw diff --git a/nasm/printer.asm b/nasm/printer.asm index 8ea122869d..f4f37398c4 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -225,17 +225,19 @@ pr_str: je .list_check_end ; A value (nil, int etc. or function) - xor cl, container_list ; Remove list type -> value + mov ch, cl ; Save type, container + and cl, content_mask ; Remove list type -> value mov BYTE [rsi], cl + push rcx push r13 push r12 call pr_str ; String in rax pop r12 pop r13 - - mov cl, BYTE [r12] - or cl, container_list ; Restore list type + pop rcx + + mov cl, ch ; Restore list type mov BYTE [r12], cl jmp .list_loop_got_str .list_loop_pointer: @@ -434,17 +436,19 @@ pr_str: je .vector_check_end ; A value (nil, int etc. or function) - xor cl, container_vector ; Remove vector type -> value + mov ch, cl ; Save type, container + and cl, content_mask ; Remove vector type -> value mov BYTE [rsi], cl + push rcx push r13 push r12 call pr_str ; String in rax pop r12 pop r13 - - mov cl, BYTE [r12] - or cl, container_vector ; Restore vector type + pop rcx + + mov cl, ch ; Restore vector type mov BYTE [r12], cl jmp .vector_loop_got_str .vector_loop_pointer: diff --git a/nasm/types.asm b/nasm/types.asm index 5a49395cd5..9af933d660 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -388,6 +388,85 @@ incref_object: ; Check for overflow? mov [rsi + Cons.refcount], WORD ax ret + +;; ------------------------------------------- +;; Copying lists/vectors +;; This does a shallow copy, copying only the +;; top level of objects. Any objects pointed to are not copied +;; +;; Input: RSI - address of list/vector +;; +;; Returns: New list/vector in RAX, last Cons in RBX +;; +;; Modifies: +;; RBX +;; RCX +;; RDX +;; R8 +;; R9 +;; R10 +;; +cons_seq_copy: + push rsi ; Restored at the end + + mov r8, rsi ; Input in R8 + xor r9, r9 ; Head of list in R9, start in R10 +.loop: + ; Check the type + mov cl, BYTE [r8] + mov ch, cl + and ch, block_mask + jnz .not_seq ; Not a Cons object + + call alloc_cons + mov rdx, rax ; New Cons in RDX + mov [rdx], BYTE cl ; Copy type in RCX + mov rbx, [r8 + Cons.car] ; Value in RBX + mov [rdx + Cons.car], rbx ; Copy value + + and cl, content_mask + cmp cl, content_pointer + jne .copied + + ; A pointer, so increment the reference count + mov rsi, rbx + call incref_object + +.copied: + ; Check if this is the first + test r9,r9 + jnz .append + + ; First Cons + mov r9, rdx + mov r10, rdx ; Start of the list, will be returned + jmp .next + +.append: + ; Appending to last Cons + mov [r9 + Cons.cdr], rdx + mov [r9 + Cons.typecdr], BYTE content_pointer + ; Replace + mov r9, rdx + +.next: + ; Check if there's another + mov al, BYTE [r8 + Cons.typecdr] + cmp al, content_pointer + jne .done ; No more + ; Got another + mov r8, [r8 + Cons.cdr] + jmp .loop + +.done: + pop rsi ; Restore input + mov rax, r10 ; Output list + mov rbx, r9 ; Last Cons + ret + +.not_seq: + xor rsi,rsi + jmp error_throw ;; ------------------------------------------- ;; String type From 12ab92c37936321e12a258c96d3d483b0e84be6b Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 21 Nov 2017 12:00:25 +0000 Subject: [PATCH 0604/2308] Fix bugs in string manipulation Handling corner cases when string chunks (Arrays) are full. This caused segfaults if string lengths hit magic numbers. --- nasm/core.asm | 4 ++-- nasm/printer.asm | 20 +++++++++++--------- nasm/types.asm | 10 +++++++--- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index d2df3d386f..61d04ad34b 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -1169,9 +1169,9 @@ core_concat: ret .missing_args: - ; Return nil + ; Return empty list call alloc_cons - mov [rax], BYTE maltype_nil + mov [rax], BYTE maltype_empty_list ret .not_list: diff --git a/nasm/printer.asm b/nasm/printer.asm index f4f37398c4..2d7b8cf080 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -265,8 +265,8 @@ pr_str: .list_check_end: ; Check if this is the end of the list mov cl, BYTE [r12 + Cons.typecdr] - cmp cl, content_nil - je .list_finished + cmp cl, content_pointer + jne .list_finished ; More left in the list @@ -344,18 +344,20 @@ pr_str: je .map_check_end ; A value (nil, int etc. or function) - xor cl, container_map ; Remove map type -> value - mov BYTE [rsi], cl + xchg ch, cl + mov [rsi], BYTE cl ; Remove map type -> value + xchg ch, cl + push rcx push r13 push r12 call pr_str ; String in rax pop r12 pop r13 + pop rcx + + mov cl, BYTE [r12] ; Restore map type - mov cl, BYTE [r12] - or cl, container_map ; Restore map type - mov BYTE [r12], cl jmp .map_loop_got_str .map_loop_pointer: mov rsi, [rsi + Cons.car] ; Address of object @@ -476,8 +478,8 @@ pr_str: .vector_check_end: ; Check if this is the end of the vector mov cl, BYTE [r12 + Cons.typecdr] - cmp cl, content_nil - je .vector_finished + cmp cl, content_pointer + jne .vector_finished ; More left in the vector diff --git a/nasm/types.asm b/nasm/types.asm index 9af933d660..089240abf1 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -160,7 +160,7 @@ section .data heap_cons_next: dd heap_cons_store ; Address of next cons in memory heap_cons_free: dq 0 ; Address of start of free list -%define heap_array_limit 300 ; Number of array objects which can be created +%define heap_array_limit 1000 ; Number of array objects which can be created heap_array_next: dd heap_array_store heap_array_free: dq 0 @@ -622,8 +622,8 @@ string_append_string: mov r8d, DWORD [rbx + Array.length] add r11, r8 - cmp r8d, 0 - je .return ; Appending zero-size array + test r8d, r8d + jz .return ; Appending zero-size array ; Find the end of the string in RSI ; and put the address of the Array object into rax @@ -645,6 +645,10 @@ string_append_string: ; destination data end into r9 mov r9, rax add r9, Array.size + + ; Check if we are at the end of the destination + cmp r8, r9 + je .alloc_dest .copy_loop: ; Copy one byte from source to destination From 6b0641cbab6fee2ca2cf98485db61940884bd37b Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 22 Nov 2017 23:29:46 +0000 Subject: [PATCH 0605/2308] Working on quasiquote. Not yet done Quoting not yet working properly, so symbols in the input AST are evaluated when they should not be. Example failing test: (quasiquote (1 a 3)) The quasiquote function turns this into (cons 1 (cons a (quote (3)))) rather than (cons 1 (cons (quote a) (quote (3)))) --- nasm/core.asm | 19 ++- nasm/macros.mac | 1 + nasm/step7_quote.asm | 392 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 409 insertions(+), 3 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 61d04ad34b..c688992098 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -1130,11 +1130,18 @@ core_concat: cmp al, content_pointer jne .last + ; Check if the list is empty + mov rbx, [rsi + Cons.car] ; The list + mov al, BYTE [rbx] + and al, content_mask + cmp al, content_empty ; If empty list or vector + je .next ; Skip to next + ; not the last list, so need to copy push rsi - mov rsi, [rsi + Cons.car] ; The list - call cons_seq_copy ; Copy in RAX + mov rsi, rbx ; The list + call cons_seq_copy ; Copy in RAX, last Cons in RBX pop rsi ; Check if this is the first @@ -1159,12 +1166,18 @@ core_concat: .last: ; last list, so can just prepend mov rsi, [rsi + Cons.car] + + ; Check if the list is empty + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_empty ; If empty list or vector + je .done ; Omit the empty list call incref_object mov [r11 + Cons.cdr], rsi mov [r11 + Cons.typecdr], BYTE content_pointer - +.done: mov rax, r12 ; output list ret diff --git a/nasm/macros.mac b/nasm/macros.mac index 5f0d5fda90..f3db819bdf 100644 --- a/nasm/macros.mac +++ b/nasm/macros.mac @@ -25,6 +25,7 @@ %1: ISTRUC Array AT Array.type, db maltype_symbol + AT Array.refcount, dw 1 AT Array.length, dd slen AT Array.data, db %2 IEND diff --git a/nasm/step7_quote.asm b/nasm/step7_quote.asm index 8834a14944..2295012c3b 100644 --- a/nasm/step7_quote.asm +++ b/nasm/step7_quote.asm @@ -62,6 +62,10 @@ section .data static_symbol quote_symbol, 'quote' static_symbol quasiquote_symbol, 'quasiquote' + static_symbol unquote_symbol, 'unquote' + static_symbol splice_unquote_symbol, 'splice-unquote' + static_symbol concat_symbol, 'concat' + static_symbol cons_symbol, 'cons' ;; Startup string. This is evaluated on startup static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) )" @@ -716,6 +720,9 @@ eval: eval_cmp_symbol quote_symbol ; quote je .quote_symbol + + eval_cmp_symbol quasiquote_symbol ; quasiquote + je .quasiquote_symbol ; Unrecognised jmp .list_eval @@ -1381,6 +1388,50 @@ eval: ; ----------------------------- +.quasiquote_symbol: + ; call quasiquote function with first argument + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; quasiquote empty, so return nil + + mov r11, rsi ; Save original AST in R11 + + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a value or pointer + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + je .quasiquote_pointer + + ; RSI contains a value. Remove the list container + mov [rsi + Cons.typecar], BYTE al + call incref_object + mov rax, rsi + jmp .return + +.quasiquote_pointer: + ; RSI contains a pointer, so get the object pointed to + mov rsi, [rsi + Cons.car] + + call quasiquote + ret + + push r15 ; Environment + push r11 ; Original AST + call quasiquote + ; New AST in RAX + pop rsi ; Old AST + push rax + call release_object ; Release old AST + pop rsi + pop rdi ; Environment + + jmp eval ; Tail call + + ; ----------------------------- + .list_eval: push rsi mov rdi, r15 ; Environment @@ -1523,8 +1574,349 @@ apply_fn: jmp eval ; Tail call ; The new environment (in RDI) will be released by eval + + +;; Set ZF if RSI is a non-empty list or vector +;; Modifies RAX, does not modify RSI +is_pair: + mov al, BYTE [rsi] + test al, block_mask + jnz .false ; Not a Cons + cmp al, maltype_empty_list + je .false ; Empty list + cmp al, maltype_empty_vector + je .false ; Empty vector + + ; Something non empty + and al, container_mask + cmp al, container_list + jmp .true + cmp al, container_vector + jmp .true + ; Not a list or vector -> false + +.false: + lahf ; flags in AH + and ah, 255-64 ; clear zero flag + sahf + ret +.true: + lahf ; flags in AH + or ah, 64 ; set zero flag + sahf + ret + +;; Called by eval with AST in RSI +quasiquote: + ; i. Check if AST is an empty list + call is_pair + jne .quote_ast + + ; ii. Check if the first element of RSI is the symbol + ; 'unquote' + + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .not_unquote ; Not a pointer + mov rdi, [rsi + Cons.car] ; Get the pointer + mov cl, BYTE [rdi] + cmp cl, maltype_symbol + jne .not_unquote + + ; Compare against 'unquote' + mov r8, rsi + mov r9, rax + + mov rsi, unquote_symbol + call compare_char_array + test rax, rax + + mov rax, r9 + mov rsi, r8 + + je .unquote + +.not_unquote: + ; iii. Handle splice-unquote + ; RSI -> ( ( splice-unquote ? ) ? ) + + ; Test if RSI contains a pointer + + cmp al, content_pointer + jne .not_splice + + mov rbx, [rsi + Cons.car] ; Get the object pointer + + ; RBX -> ( splice-unquote ? ) + + xchg rbx, rsi + call is_pair + xchg rbx, rsi + jne .not_splice ; First element not a pair + + ; Check if this list in RBX starts with 'splice-unquote' symbol + mov al, BYTE [rbx] + and al, content_mask + cmp al, content_pointer + jne .not_splice + + + mov rdi, [rbx + Cons.car] ; Get the pointer + mov al, BYTE [rdi] + cmp al, maltype_symbol + jne .not_splice + + mov r8, rsi + mov r9, rbx + + ; Compare against 'splice-unquote' + mov rsi, splice_unquote_symbol + call compare_char_array + test rax, rax + + mov rbx, r9 + mov rsi, r8 + + je .splice_unquote + +.not_splice: + + ; iv. Cons first and rest of AST in RSI + + ; Check if this is the end of the list + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .quote_ast ; Put in quote + + ; Not the end of the AST, so need to cons + ; check if pointer or value + mov cl, BYTE [rsi] + and cl, content_mask + cmp cl, content_pointer + je .cons_pointer + + ; a value, so copy + call alloc_cons + or cl, container_list + mov [rax], BYTE cl ; List + Content + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx + mov rcx, rax + jmp .cons_first + +.cons_pointer: + ; Get the pointer and increment references + mov rcx, [rsi + Cons.car] + xchg rcx, rsi + call incref_object + xchg rcx, rsi ; Object in RCX + + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rcx ; The value + mov rcx, rax + +.cons_first: + ; Have Cons with first object in RCX + + ; Call quasiquote on the rest of the AST + push rcx + mov rsi, [rsi + Cons.cdr] + call quasiquote + mov rdx, rax ; List in RDX + pop rcx ; Value in RCX + + ; cons RCX and RDX + ; Work from the end of the list to the front + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rdx ; The rest of AST + + ; Link to the RCX Cons + mov [rcx + Cons.typecdr], BYTE content_pointer + mov [rcx + Cons.cdr], rax + mov rdx, rcx + + call alloc_cons ; Cons for cons symbol + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rdx + mov rdx, rax + + ; Get the cons symbol + mov rsi, cons_symbol + call incref_object + + mov [rdx], BYTE (container_list + content_pointer) + mov [rdx + Cons.car], rsi + + mov rax, rdx + ret + +.quote_ast: + ; Return (quote RSI) + + call incref_object ; RSI reference count + + ; Cons for RSI + call alloc_cons + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], rsi + mov rsi, rax + + ; Cons for quote symbol + call alloc_cons + mov rbx, rax + mov [rbx + Cons.typecdr], BYTE content_pointer + mov [rbx + Cons.cdr], rsi + + ; Get a quote symbol, incrementing references + mov rsi, quote_symbol + call incref_object + + ; Put into the Cons in RBX + mov [rbx + Cons.car], rsi + mov [rbx], BYTE (block_cons + container_list + content_pointer) + mov rax, rbx + ret + ; ----------------------- + +.unquote: + + ; Got unquote symbol. Return second element of RSI + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .empty_list ; No second element + + mov rsi, [rsi + Cons.cdr] + + ; Check if it's a value or pointer + mov cl, BYTE [rsi] + and cl, content_mask + cmp cl, content_pointer + je .unquote_pointer + + ; A value, so need a new Cons + call alloc_cons + mov [rax], BYTE cl ; content + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; Copy content + ret + +.unquote_pointer: + mov rsi, [rsi + Cons.car] + call incref_object + mov rax, rsi + ret + + ; ----------------------- +.splice_unquote: + ; RSI -> ( RBX->( splice-unquote A ) B ) + ; + ; RBX Car points to splice-unquote symbol + + ; Check if there is anything after the symbol + mov al, BYTE [rbx + Cons.typecdr] + cmp al, content_pointer + jne .splice_unquote_empty + + ; Point to the second element of the splice-unquote list + mov rcx, [rbx + Cons.cdr] + + ; Check whether it's a value or pointer + mov al, BYTE [rcx] + and al, content_mask + cmp al, content_pointer + je .splice_unquote_pointer + + ; A value, so change the container to a value + mov [rcx], BYTE al + ; Remove pointer from RBX + mov [rbx + Cons.typecdr], BYTE 0 + jmp .splice_unquote_first ; Got the value in RCX + +.splice_unquote_pointer: + mov rcx, [rcx + Cons.car] ; Get the object pointed to + xchg rcx, rsi + call incref_object + xchg rcx, rsi ; Object in RCX + +.splice_unquote_first: ; Got the first object in RCX + + ; Check if RSI contains anything else + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .splice_unquote_notail + + mov rsi, [rsi + Cons.cdr] + + ; Now have: + ; ( ( splice-unquote A ) B ) + ; RCX->A RSI->( B ) + ; Need to call quasiquote on the rest of the list + push rcx + call quasiquote + mov rdx, rax + pop rcx + ; Need to concat rcx and rdx + ; Work from the end of the list to the front + + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rdx ; The rest of AST + mov rdx, rax ; Push list into RDX + + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rcx ; The splice-unquote object + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rdx + mov rdx, rax + + call alloc_cons ; Cons for concat symbol + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rdx + mov rdx, rax + + ; Get the concat symbol + mov rsi, concat_symbol + call incref_object + + mov [rdx], BYTE (container_list + content_pointer) + mov [rdx + Cons.car], rsi + + mov rax, rdx + ret + +.splice_unquote_notail: + ; Just return the object in RCX + ; since nothing to concatenate with + mov rax, rcx + ret + +.splice_unquote_empty: + ; Nothing in the (splice-unquote) list, so ignore + ; Just call quasiquote on the rest of RSI + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .empty_list ; Nothing else + + mov rsi, [rsi + Cons.cdr] + jmp quasiquote ; Tail call + +.empty_list: + ; Return an empty list + call alloc_cons + mov [rax], BYTE maltype_empty_list + ret + + + + + ;; Read-Eval-Print in sequence ;; ;; Input string in RSI From 8d054892e11424beb6d7bd6197e99bb6049ea0f6 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 23 Nov 2017 00:06:34 +0000 Subject: [PATCH 0606/2308] quasiquote tests pass Needed to call quasiquote with first AST element, which then wrapped symbol in (quote ) Two tests still failing. One for cons with empty list, and another due to concat returning a vector --- nasm/step7_quote.asm | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/nasm/step7_quote.asm b/nasm/step7_quote.asm index 2295012c3b..5ca4a90a04 100644 --- a/nasm/step7_quote.asm +++ b/nasm/step7_quote.asm @@ -1415,8 +1415,8 @@ eval: ; RSI contains a pointer, so get the object pointed to mov rsi, [rsi + Cons.car] - call quasiquote - ret + ;call quasiquote + ;ret push r15 ; Environment push r11 ; Original AST @@ -1707,15 +1707,16 @@ quasiquote: jmp .cons_first .cons_pointer: - ; Get the pointer and increment references - mov rcx, [rsi + Cons.car] - xchg rcx, rsi - call incref_object - xchg rcx, rsi ; Object in RCX - + ; Get the pointer and call quasiquote + push rsi + mov rsi, [rsi + Cons.car] + call quasiquote + mov rcx, rax + pop rsi + call alloc_cons mov [rax], BYTE (container_list + content_pointer) - mov [rax + Cons.car], rcx ; The value + mov [rax + Cons.car], rcx mov rcx, rax .cons_first: From 181dd159a3f4106d78c96d0dbf0b5f0285615912 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 24 Nov 2017 22:51:39 +0000 Subject: [PATCH 0607/2308] All step 7 tests pass concat sets the container type so that return is always a list. NOTE: The container type of every Cons is not changed, so if the list is sliced then the pieces may be vectors or lists. --- nasm/core.asm | 65 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index c688992098..8d3544b5a7 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -1031,10 +1031,6 @@ core_cons: .got_args: ; Got an object in R8 and list/vector in R9 - - ;call alloc_cons - ;mov r9, rax - ;mov [r9], BYTE container_list + content_nil ;; NOTE: Segfault if list changed to vector. Printer? call alloc_cons ; new Cons in RAX @@ -1048,12 +1044,19 @@ core_cons: ; Copy the content mov rcx, [r8 + Cons.car] mov [rax + Cons.car], rcx + + ; Check if R9 is empty + mov cl, BYTE [r9] + and cl, content_mask + cmp cl, content_empty + je .end_append ; Don't append the list ; Put the list into CDR mov [rax + Cons.cdr], r9 ; mark CDR as a pointer mov [rax + Cons.typecdr], BYTE content_pointer +.end_append: push rax ; popped before return ; Check if the new Cons contains a pointer @@ -1107,9 +1110,52 @@ core_concat: je .start_loop ; Start copy loop ; Only one input. + mov rsi, [rsi + Cons.car] + + ; Check if it's a list or vector + mov al, BYTE [rsi] + mov cl, al + and al, container_mask + cmp al, (block_cons + container_list) + je .single_list + + cmp al, (block_cons + container_vector) + jne .not_list ; not a list or vector + + ; A vector. Need to create a new Cons + ; for the first element, to mark it as a list + + call alloc_cons + and cl, content_mask + or cl, container_list + mov [rax], BYTE cl ; Set type + + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; Set content + + mov dl, BYTE [rsi + Cons.typecdr] + mov [rax + Cons.typecdr], BYTE dl ; CDR type + + mov rbx, [rsi + Cons.cdr] + mov [rax + Cons.cdr], rbx ; Set CDR content + + ; Check if CDR is a pointer + cmp dl, content_pointer + je .single_vector_incref + ; not a pointer, just return + ret + +.single_vector_incref: + ; increment the reference count of object pointed to + mov r12, rax ; The return Cons + mov rsi, rbx ; The object address + call incref_object + mov rax, r12 + ret + +.single_list: ; Just increment reference count and return - mov rsi, [rsi + Cons.car] call incref_object mov rax, rsi ret @@ -1164,11 +1210,12 @@ core_concat: jmp .loop .last: - ; last list, so can just prepend + ; last list, so can just append mov rsi, [rsi + Cons.car] ; Check if the list is empty mov al, BYTE [rsi] + mov ah, al and al, content_mask cmp al, content_empty ; If empty list or vector je .done ; Omit the empty list @@ -1178,7 +1225,13 @@ core_concat: mov [r11 + Cons.cdr], rsi mov [r11 + Cons.typecdr], BYTE content_pointer .done: + ; Make sure that return is a list + mov bl, BYTE [r12] + and bl, content_mask + or bl, container_list + mov [r12], BYTE bl mov rax, r12 ; output list + ret .missing_args: From 4a99a16c415c3c0d91127480d633b5153756d816 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 27 Nov 2017 14:07:00 +0000 Subject: [PATCH 0608/2308] Adding test to step7 for quasiquote Handling of the last element of the AST is tested by having an unquote as the last element. --- tests/step7_quote.mal | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/step7_quote.mal b/tests/step7_quote.mal index bd5b22fe6f..973acbc743 100644 --- a/tests/step7_quote.mal +++ b/tests/step7_quote.mal @@ -72,7 +72,8 @@ b ;=>(1 b 3) (quasiquote (1 (unquote b) 3)) ;=>(1 (1 "b" "d") 3) - +(quasiquote ((unquote 1) (unquote 2))) +;=>(1 2) ;; Testing splice-unquote (def! c (quote (1 "b" "d"))) From 5ad295bd9d73c6903a5aa2b14099140e2983b7af Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 27 Nov 2017 15:51:15 +0000 Subject: [PATCH 0609/2308] Fix cons and quasiquote functions * Better handling of the final element in quasiquote * Cons now increments reference counts of objects pointed to so they are not accidentally deleted Now passes step7 tests again, including new test --- nasm/core.asm | 28 +++++++++++++++------------- nasm/step7_quote.asm | 38 +++++++++++++++++++++++++++----------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 8d3544b5a7..ec446f5857 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -1042,13 +1042,13 @@ core_cons: mov [rax], BYTE bl ; Copy the content - mov rcx, [r8 + Cons.car] + mov rcx, [r8 + Cons.car] ; Content in RCX mov [rax + Cons.car], rcx ; Check if R9 is empty - mov cl, BYTE [r9] - and cl, content_mask - cmp cl, content_empty + mov dl, BYTE [r9] + and dl, content_mask + cmp dl, content_empty je .end_append ; Don't append the list ; Put the list into CDR @@ -1056,23 +1056,25 @@ core_cons: ; mark CDR as a pointer mov [rax + Cons.typecdr], BYTE content_pointer -.end_append: - push rax ; popped before return + ; Increment reference count + push rax + mov rsi, r9 + call incref_object + pop rax +.end_append: ; Check if the new Cons contains a pointer - cmp bh, content_pointer + mov bl, BYTE [rax] + and bl, content_mask + cmp bl, content_pointer jne .done ; A pointer, so increment number of references + push rax mov rsi, rcx call incref_object - -.done: - ; Increment reference count of list - mov rsi, r9 - call incref_object pop rax - +.done: ret .missing_args: diff --git a/nasm/step7_quote.asm b/nasm/step7_quote.asm index 5ca4a90a04..6f80eb23e2 100644 --- a/nasm/step7_quote.asm +++ b/nasm/step7_quote.asm @@ -1590,11 +1590,11 @@ is_pair: ; Something non empty and al, container_mask cmp al, container_list - jmp .true + je .true cmp al, container_vector - jmp .true + je .true ; Not a list or vector -> false - + .false: lahf ; flags in AH and ah, 255-64 ; clear zero flag @@ -1685,12 +1685,6 @@ quasiquote: ; iv. Cons first and rest of AST in RSI - ; Check if this is the end of the list - mov al, BYTE [rsi + Cons.typecdr] - cmp al, content_pointer - jne .quote_ast ; Put in quote - - ; Not the end of the AST, so need to cons ; check if pointer or value mov cl, BYTE [rsi] and cl, content_mask @@ -1723,12 +1717,34 @@ quasiquote: ; Have Cons with first object in RCX ; Call quasiquote on the rest of the AST + ; Check if this is the end of the list + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .cons_ast_end + + mov rsi, [rsi + Cons.cdr] ; Rest of the list + + call incref_object ; Will release after quasiquote call + + jmp .cons_quasiquote_ast + +.cons_ast_end: + ; End of the AST, so make an empty list + call alloc_cons + mov [rax], BYTE maltype_empty_list + mov rsi, rax + +.cons_quasiquote_ast: push rcx - mov rsi, [rsi + Cons.cdr] + push rsi call quasiquote mov rdx, rax ; List in RDX + + pop rsi + call release_object ; Release input + pop rcx ; Value in RCX - + ; cons RCX and RDX ; Work from the end of the list to the front From 08caa2faa063f9ec31106630f1f0b6b2c426146e Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 28 Nov 2017 15:03:13 +0000 Subject: [PATCH 0610/2308] Adding core first function Returns the first element of a list or vector --- nasm/core.asm | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/nasm/core.asm b/nasm/core.asm index ec446f5857..805bcd5b6b 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -43,6 +43,8 @@ section .data static core_cons_symbol, db "cons" static core_concat_symbol, db "concat" + + static core_first_symbol, db "first" ;; Strings @@ -58,6 +60,9 @@ section .data static core_cons_not_vector, db "Error: cons expects a list or vector" static core_concat_not_list, db "Error: concat expects lists or vectors" + + static core_first_missing_arg, db "Error: missing argument to first" + static core_first_not_list, db "Error: first expects a list or vector" section .text ;; Add a native function to the core environment @@ -125,6 +130,8 @@ core_environment: core_env_native core_cons_symbol, core_cons core_env_native core_concat_symbol, core_concat + core_env_native core_first_symbol, core_first + ; ----------------- ; Put the environment in RAX mov rax, rsi @@ -1251,3 +1258,78 @@ core_concat: call raw_to_string mov rsi, rax jmp error_throw + + +;; Returns the first element of a list +;; +core_first: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_empty + je .missing_args + + cmp al, content_nil + je .return_nil + + cmp al, content_pointer + jne .not_list + + ; Get the list + mov rsi, [rsi + Cons.car] + + mov al, BYTE [rsi] + + ; Check for nil + cmp al, maltype_nil + je .return_nil + + mov ah, al + and ah, (block_mask + container_mask) + cmp ah, container_list + je .got_list + cmp ah, container_vector + jne .not_list ; Not a list or vector + +.got_list: + ; Check if list is empty + and al, content_mask + cmp al, content_empty + je .return_nil + + cmp al, content_pointer + je .return_pointer + + ; Returning a value, so need to copy + mov cl, al + call alloc_cons + mov [rax], BYTE cl ; Set type + + ; Copy value + mov rcx, [rsi + Cons.car] + mov [rax + Cons.car], rcx + ret + +.return_pointer: + mov rsi, [rsi + Cons.car] + call incref_object + mov rax, rsi + ret + +.return_nil: + call alloc_cons + mov [rax], BYTE maltype_nil + ret + +.missing_args: + mov rsi, core_first_missing_arg + mov edx, core_first_missing_arg.len + jmp .throw + +.not_list: + mov rsi, core_first_not_list + mov edx, core_first_not_list.len +.throw: + call raw_to_string + mov rsi, rax + jmp error_throw + From f2030f86968e726bc6c87db46b72568f965a68f0 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 28 Nov 2017 15:08:34 +0000 Subject: [PATCH 0611/2308] Fix raw_to_string function for long strings Previously assumed that the string would fit into a single Array object. Now handles the allocation of a chain of Array objects. --- nasm/types.asm | 56 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/nasm/types.asm b/nasm/types.asm index 089240abf1..7352c7499e 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -130,7 +130,7 @@ ENDSTRUC %define maltype_empty_map (block_cons + container_map + content_empty) %define maltype_empty_vector (block_cons + container_vector + content_empty) %define maltype_function (block_cons + container_function + content_function) -%define maltype_macro (block_cons + container_macro + content_function) +%define maltype_macro (block_cons + container_function + content_macro) %define maltype_true (block_cons + container_value + content_true) %define maltype_false (block_cons + container_value + content_false) %define maltype_atom (block_cons + container_atom + content_pointer) @@ -489,25 +489,75 @@ string_new: ;; Output: Address of string in RAX ;; ;; Modifies registers: R8,R9,RCX +;; raw_to_string: + ; Save registers to restore at the end + push r10 + push r11 + push rsi push rdx call string_new ; String now in RAX pop rdx pop rsi - mov [rax + Array.length], DWORD edx + mov r8, rax add r8, Array.data ; Address of string data + mov r10, rax + add r10, Array.size ; End of the destination data + mov r11, rax ; First Array to return + mov r9, rsi ; Address of raw data mov ecx, edx ; Count + .copy_loop: + test ecx, ecx ; Check if count is zero + jz .done + ; Copy one byte mov bl, BYTE [r9] mov [r8], BYTE bl + + ; Move the destination inc r8 + cmp r8, r10 + jne .dest_ok + + ; Hit the end. Set the length of the array + mov [rax + Array.length], DWORD (array_chunk_len * 8) + + push rax ; Last Array + push rsi + push rdx + call string_new ; String now in RAX + pop rdx + pop rsi + pop rbx ; Last Array + mov [rbx + Array.next], rax ; Point to new Array + + mov r8, rax + add r8, Array.data ; Address of string data + mov r10, rax + add r10, Array.size ; End of the destination data + +.dest_ok: + inc r9 dec ecx - jnz .copy_loop + jmp .copy_loop +.done: + ; Set the length of the destination array + sub r8, Array.data + sub r8, rax + mov [rax + Array.length], DWORD r8d + + ; Move first Array into RAX + mov rax, r11 + + ; Restore registers + pop r11 + pop r10 + ret ;; Convert a raw string to a symbol From 07ac7a27141cfcedf9d9236c682e67ef3f2c9791 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 28 Nov 2017 16:16:53 +0000 Subject: [PATCH 0612/2308] Adding nth and rest functions nth returns an element at given index in a list. rest returns a list or vector with the first element removed. Currently this works by just returning a pointer to the second element. --- nasm/core.asm | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/nasm/core.asm b/nasm/core.asm index 805bcd5b6b..3d05131490 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -45,6 +45,8 @@ section .data static core_concat_symbol, db "concat" static core_first_symbol, db "first" + static core_rest_symbol, db "rest" + static core_nth_symbol, db "nth" ;; Strings @@ -63,6 +65,15 @@ section .data static core_first_missing_arg, db "Error: missing argument to first" static core_first_not_list, db "Error: first expects a list or vector" + + static core_rest_missing_arg, db "Error: missing argument to rest" + static core_rest_not_list, db "Error: rest expects a list or vector" + + static core_nth_missing_arg, db "Error: missing argument to nth" + static core_nth_not_list, db "Error: nth expects a list or vector as first argument" + static core_nth_not_int, db "Error: nth expects an integer as second argument" + static core_nth_out_of_range, db "Error: nth index out of range" + section .text ;; Add a native function to the core environment @@ -131,6 +142,9 @@ core_environment: core_env_native core_concat_symbol, core_concat core_env_native core_first_symbol, core_first + core_env_native core_rest_symbol, core_rest + core_env_native core_nth_symbol, core_nth + ; ----------------- ; Put the environment in RAX @@ -1333,3 +1347,172 @@ core_first: mov rsi, rax jmp error_throw + +;; Return a list with the first element removed +core_rest: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_empty + je .missing_args + + cmp al, content_nil + je .return_nil + + cmp al, content_pointer + jne .not_list + + ; Get the list + mov rsi, [rsi + Cons.car] + + mov al, BYTE [rsi] + + ; Check for nil + cmp al, maltype_nil + je .return_nil + + mov ah, al + and ah, (block_mask + container_mask) + cmp ah, container_list + je .got_list + cmp ah, container_vector + jne .not_list ; Not a list or vector + +.got_list: + ; Check if list is empty + and al, content_mask + cmp al, content_empty + je .empty_list + + ; Check if there is more in the list + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + je .return_rest + + ; No more list, so return empty list +.empty_list: + call alloc_cons + mov [rax], BYTE maltype_empty_list + ret + +.return_rest: + + mov rsi, [rsi + Cons.cdr] + call incref_object + mov rax, rsi + ret + +.return_nil: + call alloc_cons + mov [rax], BYTE maltype_nil + ret + +.missing_args: + mov rsi, core_rest_missing_arg + mov edx, core_rest_missing_arg.len + jmp .throw + +.not_list: + mov rsi, core_rest_not_list + mov edx, core_rest_not_list.len +.throw: + call raw_to_string + mov rsi, rax + jmp error_throw + + +;; Return the nth element of a list or vector +core_nth: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_empty + je .missing_args + + cmp al, content_nil + je .return_nil + + cmp al, content_pointer + jne .not_list + + ; Get the list into R8 + mov r8, [rsi + Cons.car] + + ; Check if we have a second argument + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .missing_args + + mov r9, [rsi + Cons.cdr] + + ; Check that it is a number + mov al, BYTE [r9] + and al, content_mask + cmp al, content_int + jne .not_int + + ; Get the number in RBX + mov rbx, [r9 + Cons.car] + + ; Now loop through the list, moving along n elements +.loop: + test rbx, rbx ; Test if zero + jz .done + + ; Move along next element + + mov al, BYTE [r8 + Cons.typecdr] + cmp al, content_pointer + jne .out_of_range ; No element + + mov r8, [r8 + Cons.cdr] + dec rbx + jmp .loop + +.done: + ; Take the head of the list in R8 + mov al, BYTE [r8] + and al, content_mask + cmp al, content_pointer + je .return_pointer + + ; Copy a value + mov cl, al + call alloc_cons + mov [rax], BYTE cl + mov rcx, [r8 + Cons.car] + mov [rax + Cons.car], rcx + ret + +.return_pointer: + mov rsi, [r8 + Cons.car] + call incref_object + mov rax, rsi + ret + +.return_nil: + call alloc_cons + mov [rax], BYTE maltype_nil + ret + +.missing_args: + mov rsi, core_nth_missing_arg + mov edx, core_nth_missing_arg.len + jmp .throw + +.not_list: + mov rsi, core_nth_not_list + mov edx, core_nth_not_list.len + jmp .throw + +.not_int: + mov rsi, core_nth_not_int + mov edx, core_nth_not_int.len + jmp .throw + +.out_of_range: + mov rsi, core_nth_out_of_range + mov edx, core_nth_out_of_range.len + +.throw: + call raw_to_string + mov rsi, rax + jmp error_throw From 6df6dfd35886ad3c27688afdcb1dd5ae28ec9956 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 29 Nov 2017 15:13:10 +0000 Subject: [PATCH 0613/2308] Fix memory leak, eval releases AST Quasiquote (and macroexpand) replace the AST in eval. This makes releasing the memory harder, so could give rise to both double-frees, and memory leaks. Eval now releases both its environment (for tail calls) and the AST. --- nasm/core.asm | 9 +-- nasm/step7_quote.asm | 146 +++++++++++++++++++++++++++++++------------ 2 files changed, 112 insertions(+), 43 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 3d05131490..eb72711a28 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -699,14 +699,15 @@ core_eval: .pointer: ; A pointer, so need to eval - mov rsi, [rsi + Cons.car] + mov rdi, [rsi + Cons.car] - mov rdi, [repl_env] ; Environment + mov rsi, [repl_env] ; Environment - xchg rsi, rdi call incref_object ; Environment increment refs - xchg rsi, rdi ; since it will be decremented by eval + xchg rsi, rdi ; Env in RDI, AST in RSI + call incref_object ; AST increment refs + call eval ret diff --git a/nasm/step7_quote.asm b/nasm/step7_quote.asm index 6f80eb23e2..2b71b70aa4 100644 --- a/nasm/step7_quote.asm +++ b/nasm/step7_quote.asm @@ -69,7 +69,9 @@ section .data ;; Startup string. This is evaluated on startup static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) )" - + + ;static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) )" + ;; Command to run, appending the name of the script to run static run_script_string, db "(load-file ",34 section .text @@ -307,12 +309,13 @@ eval_ast: push r8 push r9 push r15 ; Env - mov rsi, [rsi + Cons.car] ; Get the address - mov rdi, r15 + mov rdi, [rsi + Cons.car] ; Get the address + mov rsi, r15 - xchg rsi, rdi call incref_object ; Environment increment refs - xchg rsi, rdi + xchg rsi, rdi ; Env in RDI, AST in RSI + + call incref_object ; AST increment refs call eval ; Evaluate it, result in rax pop r15 @@ -476,6 +479,8 @@ eval_ast: xchg rsi, rdi call incref_object ; Environment increment refs xchg rsi, rdi + + call incref_object call eval ; Evaluate it, result in rax pop r15 @@ -564,6 +569,8 @@ eval_ast: xchg rsi, rdi call incref_object ; Environment increment refs xchg rsi, rdi + + call incref_object call eval ; Evaluate it, result in rax pop r15 @@ -659,16 +666,19 @@ eval_ast: ;; ---------------------------------------------------- ;; Evaluates a form ;; -;; Input: RSI Form to evaluate -;; RDI Environment +;; Input: RSI AST to evaluate [ Released ] +;; RDI Environment [ Released ] ;; ;; Returns: Result in RAX ;; -;; Note: The environment in RDI will have its reference count -;; reduced by one (released). This is to make tail call optimisation easier +;; Note: Both the form and environment will have their reference count +;; reduced by one (released). This is for tail call optimisation (Env), +;; quasiquote and macroexpand (AST) ;; eval: mov r15, rdi ; Env + + push rsi ; AST pushed, must be popped before return ; Check type mov al, BYTE [rsi] @@ -790,6 +800,8 @@ eval: xchg rsi, rdi call incref_object ; Environment increment refs xchg rsi, rdi ; since it will be decremented by eval + + call incref_object ; AST increment refs call eval mov rsi, rax @@ -916,6 +928,9 @@ eval: push r13 ; symbol to bind push r14 ; new environment mov rsi, [r12 + Cons.car] ; Get the address + + call incref_object ; Increment ref count of AST + mov rdi, r14 call eval ; Evaluate it, result in rax pop r14 @@ -968,6 +983,13 @@ eval: ; Evaluate using new environment mov rsi, [r11 + Cons.car] ; Object pointed to + call incref_object ; will be released by eval + + mov r11, rsi ; save new AST + pop rsi ; Old AST + call release_object + mov rsi, r11 ; New AST + mov rdi, r14 ; New environment jmp eval ; Tail call @@ -979,6 +1001,12 @@ eval: mov rsi, r14 call release_object pop rax + + ; Release the AST + pop rsi + push rax + call release_object + pop rax ret ; already released env .let_error_missing_bindings: @@ -1061,6 +1089,8 @@ eval: ; since eval will release Env mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Increment ref count since eval will release + mov rdi, r15 ; Env call eval ; Result in RAX @@ -1103,14 +1133,29 @@ eval: mov [rax], BYTE bl mov rbx, [r11 + Cons.car] mov [rax + Cons.car], rbx + + ; release the AST + pop rsi + mov r15, rax ; not modified by release + call release_object + mov rax, r15 + ret .do_body_expr_return: ; An expression to evaluate as the last form ; Tail call optimise, jumping to eval ; Don't increment Env reference count + + + mov rsi, [r11 + Cons.car] ; new AST form + call incref_object ; This will be released by eval + + mov r11, rsi ; Save new AST + pop rsi ; Remove old AST from stack + call release_object + mov rsi, r11 - mov rsi, [r11 + Cons.car] ; Form mov rdi, r15 ; Env jmp eval ; Tail call @@ -1119,6 +1164,10 @@ eval: mov rsi, r15 call release_object ; Release Env + + ; release the AST + pop rsi + call release_object call alloc_cons mov [rax], BYTE maltype_nil @@ -1152,6 +1201,8 @@ eval: call incref_object ; Increase Env reference mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Increase Form/AST ref count + mov rdi, r15 ; Env call eval ; Result in RAX pop r11 @@ -1219,6 +1270,13 @@ eval: .if_got_pointer: mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Will be released by eval + + mov r11, rsi + pop rsi + call release_object ; Release old AST + mov rsi, r11 ; New AST + mov rdi, r15 ; Env jmp eval ; Tail call @@ -1239,12 +1297,16 @@ eval: mov [rax + Cons.typecdr], BYTE content_nil .return: - push rax ; Release environment mov rsi, r15 + mov r15, rax ; Save RAX (return value) call release_object - pop rax - + + ; Release the AST + pop rsi ; Pushed at start of eval + call release_object + + mov rax, r15 ; return value ret ; ----------------------------- @@ -1419,15 +1481,17 @@ eval: ;ret push r15 ; Environment - push r11 ; Original AST + ; Original AST already on stack + call quasiquote ; New AST in RAX - pop rsi ; Old AST - push rax - call release_object ; Release old AST - pop rsi pop rdi ; Environment + pop rsi ; Old AST + mov r11, rax ; New AST + call release_object ; Release old AST + mov rsi, r11 ; New AST in RSI + jmp eval ; Tail call ; ----------------------------- @@ -1520,6 +1584,9 @@ eval: ;; R15 - Env (will be released) ;; ;; Output: Result in RAX +;; +;; This is jumped to from eval, so if it returns +;; then it will return to the caller of eval, not to eval apply_fn: push rsi ; Extract values from the list in RDI @@ -1537,7 +1604,8 @@ apply_fn: jnz .bind ; Just a value (in RAX). No eval needed - push rax + mov r14, rax ; Save return value in R14 + mov rsi, rax call incref_object @@ -1548,12 +1616,16 @@ apply_fn: ; Release the environment mov rsi, r15 call release_object + + ; Release the AST, pushed at start of eval + pop rsi + call release_object - pop rax + mov rax, r14 ret .bind: ; Create a new environment, binding arguments - push rax + push rax ; Body push rdx call env_new_bind @@ -1571,6 +1643,7 @@ apply_fn: call release_object pop rsi ; Body + call incref_object ; Will be released by eval jmp eval ; Tail call ; The new environment (in RDI) will be released by eval @@ -1606,7 +1679,9 @@ is_pair: sahf ret -;; Called by eval with AST in RSI +;; Called by eval with AST in RSI [ modified ] +;; Returns new AST in RAX + quasiquote: ; i. Check if AST is an empty list call is_pair @@ -1941,8 +2016,7 @@ rep_seq: ; ------------- ; Read call read_str - push rax ; Save AST - + ; ------------- ; Eval mov rsi, rax ; Form to evaluate @@ -1952,7 +2026,7 @@ rep_seq: call incref_object ; Environment increment refs xchg rsi, rdi ; since it will be decremented by eval - call eval + call eval ; This releases Env and Form/AST push rax ; Save result of eval ; ------------- @@ -1975,9 +2049,8 @@ rep_seq: pop rsi call release_object - ; Release the object from read_str - pop rsi - call release_object ; Could be Cons or Array + ; The AST from read_str is released by eval + ret @@ -2006,21 +2079,16 @@ _start: push rax ; AST call release_array ; string - pop rsi ; AST - - push rsi - mov rdi, [repl_env] ; Environment - - xchg rsi, rdi + pop rdi ; AST in RDI + + mov rsi, [repl_env] ; Environment in RSI + call incref_object ; Environment increment refs - xchg rsi, rdi ; since it will be decremented by eval + xchg rsi, rdi ; Env in RDI, AST in RSI call eval - pop rsi - push rax - call release_object ; AST - pop rsi + mov rsi, rax call release_object ; Return from eval ; ----------------------------- From d00896dcadfd634d7c38670bed1796291eef182d Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 30 Nov 2017 00:20:25 +0000 Subject: [PATCH 0614/2308] step 8 macros * Added macroexpand functions and special symbol * Some changes to how AST is freed by apply_fn, to avoid having to shuffle the stack after a call. 7 failing tests. "rest" can return vector or list, but should always return a list. Other errors in -> and ->> macros seem more difficult to solve. --- nasm/core.asm | 2 +- nasm/printer.asm | 15 +- nasm/step8_macros.asm | 2453 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2467 insertions(+), 3 deletions(-) create mode 100644 nasm/step8_macros.asm diff --git a/nasm/core.asm b/nasm/core.asm index eb72711a28..dc94c95f23 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -1357,7 +1357,7 @@ core_rest: je .missing_args cmp al, content_nil - je .return_nil + je .empty_list cmp al, content_pointer jne .not_list diff --git a/nasm/printer.asm b/nasm/printer.asm index 2d7b8cf080..21e72ee7a8 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -10,6 +10,7 @@ section .data static unknown_type_string, db "#" static unknown_value_string, db "#" static function_type_string, db "#" + static macro_type_string, db "#" static nil_value_string, db "nil" static true_value_string, db "true" static false_value_string, db "false" @@ -138,7 +139,7 @@ pr_str: je .vector cmp ch, container_function - je .function + je .function_or_macro cmp ch, container_atom je .atom @@ -502,12 +503,22 @@ pr_str: ret ; -------------------------------- -.function: +.function_or_macro: + cmp cl, maltype_macro + je .macro + + ; a function mov rsi, function_type_string mov edx, function_type_string.len call raw_to_string ; Puts a String in RAX ret +.macro: + mov rsi, macro_type_string + mov edx, macro_type_string.len + call raw_to_string ; Puts a String in RAX + ret + ; -------------------------------- .atom: mov rsi, [rsi + Cons.car] ; What the atom points to diff --git a/nasm/step8_macros.asm b/nasm/step8_macros.asm new file mode 100644 index 0000000000..b0d1714bbb --- /dev/null +++ b/nasm/step8_macros.asm @@ -0,0 +1,2453 @@ +;; +;; nasm -felf64 step8_macros.asm && ld step8_macros.o && ./a.out +;; +;; Calling convention: Address of input is in RSI +;; Address of return value is in RAX +;; + +global _start + +%include "types.asm" ; Data types, memory +%include "env.asm" ; Environment type +%include "system.asm" ; System calls +%include "reader.asm" ; String -> Data structures +%include "core.asm" ; Core functions +%include "printer.asm" ; Data structures -> String + +section .bss + +;; Top-level (REPL) environment +repl_env:resq 1 + +;; Error handler list +error_handler: resq 1 + +section .data + +;; ------------------------------------------ +;; Fixed strings for printing + + static prompt_string, db 10,"user> " ; The string to print at the prompt + + static error_string, db 27,'[31m',"Error",27,'[0m',": " + + static not_found_string, db " not found.",10 + + static def_missing_arg_string, db "missing argument to def!",10 + + static def_expecting_symbol_string, db "expecting symbol as first argument to def!",10 + + static defmacro_expecting_function_string, db "defmacro expects function",10 + + static let_missing_bindings_string, db "let* missing bindings",10 + + static let_bindings_list_string, db "let* expected a list or vector of bindings",10 + + static let_bind_symbol_string, db "let* expected a symbol in bindings list",10 + + static let_bind_value_string, db "let* missing value in bindings list",10 + + static let_missing_body_string, db "let* missing body",10 + static eval_list_not_function, db "list does not begin with a function",10 + + static if_missing_condition_string, db "missing condition in if expression",10 + +;; Symbols used for comparison + + ; Special symbols + static_symbol def_symbol, 'def!' + static_symbol let_symbol, 'let*' + static_symbol do_symbol, 'do' + static_symbol if_symbol, 'if' + static_symbol fn_symbol, 'fn*' + static_symbol defmacro_symbol, 'defmacro!' + static_symbol macroexpand_symbol, 'macroexpand' + + static_symbol argv_symbol, '*ARGV*' + + static_symbol quote_symbol, 'quote' + static_symbol quasiquote_symbol, 'quasiquote' + static_symbol unquote_symbol, 'unquote' + static_symbol splice_unquote_symbol, 'splice-unquote' + static_symbol concat_symbol, 'concat' + static_symbol cons_symbol, 'cons' + ; + +;; Startup string. This is evaluated on startup + static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) (defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw ",34,"odd number of forms to cond",34,")) (cons 'cond (rest (rest xs))))))) (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs)))))))) )" + +;; Command to run, appending the name of the script to run + static run_script_string, db "(load-file ",34 +section .text + +;; ---------------------------------------------- +;; +;; Error handling +;; +;; A handler consists of: +;; - A stack pointer address to reset to +;; - An address to jump to +;; - An optional data structure to pass +;; +;; When jumped to, an error handler will be given: +;; - the object thrown in RSI +;; - the optional data structure in RDI +;; + + +;; Add an error handler to the front of the list +;; +;; Input: RSI - Stack pointer +;; RDI - Address to jump to +;; RCX - Data structure. Set to zero for none. +;; If not zero, reference count incremented +;; +;; Modifies registers: +;; RAX +;; RBX +error_handler_push: + call alloc_cons + ; car will point to a list (stack, addr, data) + ; cdr will point to the previous handler + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov rbx, [error_handler] + cmp rbx, 0 ; Check if previous handler was zero + je .create_handler ; Zero, so leave null + ; Not zero, so create pointer to it + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rbx + + ; note: not incrementing reference count, since + ; we're replacing one reference with another +.create_handler: + mov [error_handler], rax ; new error handler + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.car], rax + ; Store stack pointer + mov [rax], BYTE (block_cons + container_list + content_function) + mov [rax + Cons.car], rsi ; stack pointer + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.typecdr], BYTE content_pointer + mov [rdx + Cons.cdr], rax + ; Store function pointer to jump to + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], rdi + + ; Check if there is an object to pass to handler + cmp rcx, 0 + je .done + + ; Set the final CDR to point to the object + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rcx + + mov rsi, rcx + call incref_object + +.done: + ret + + +;; Removes an error handler from the list +;; +;; Modifies registers: +;; RSI +;; RAX +;; RCX +error_handler_pop: + ; get the address + mov rsi, [error_handler] + cmp rsi, 0 + je .done ; Nothing to remove + + push rsi + mov rsi, [rsi + Cons.cdr] ; next handler + mov [error_handler], rsi + call incref_object ; needed because releasing soon + + pop rsi ; handler being removed + call release_cons + +.done: + ret + + +;; Throw an error +;; Object to pass to handler should be in RSI +error_throw: + ; Get the next error handler + mov rax, [error_handler] + cmp rax, 0 + je .no_handler + + ; Got a handler + mov rax, [rax + Cons.car] ; handler + mov rbx, [rax + Cons.car] ; stack pointer + mov rax, [rax + Cons.cdr] + mov rcx, [rax + Cons.car] ; function + mov rdi, [rax + Cons.cdr] ; data structure + + ; Reset stack + mov rsp, rbx + + ; Jump to the handler + jmp rcx + +.no_handler: + ; Print the object in RSI then quit + cmp rsi, 0 + je .done ; nothing to print + mov rdi, 1 ; print_readably + call pr_str + mov rsi, rax + call print_string +.done: + jmp quit_error + +;; ---------------------------------------------- +;; Evaluates a form +;; +;; Inputs: RSI Form to evaluate +;; RDI Environment +;; +eval_ast: + mov r15, rdi ; Save Env in r15 + + ; Check the type + mov al, BYTE [rsi] + + ; Check if this is a list + mov ah, al + and ah, container_mask + cmp ah, container_list + je .list + + cmp ah, container_map + je .map + + cmp ah, container_vector + je .vector + + ; Not a list, map or vector + cmp ah, container_symbol + je .symbol + + ; Not a symbol, list, map or vector + call incref_object ; Increment reference count + + mov rax, rsi + ret + +.symbol: + ; Check if first character of symbol is ':' + mov al, BYTE [rsi + Array.data] + cmp al, ':' + je .keyword + + ; look in environment + push rsi + xchg rsi, rdi + ; symbol is the key in rdi + ; Environment in rsi + call env_get + pop rsi + je .done ; result in RAX + + ; Not found, throw an error + push rsi + print_str_mac error_string ; print 'Error: ' + + pop rsi + push rsi + mov edx, [rsi + Array.length] + add rsi, Array.data + call print_rawstring ; print symbol + + print_str_mac not_found_string ; print ' not found' + pop rsi + + jmp error_throw + + ; ------------------------------ + +.keyword: + ; Just return keywords unaltered + call incref_object + mov rax, rsi + ret + + ; ------------------------------ +.list: + ; Evaluate each element of the list + ; + xor r8, r8 ; The list to return + ; r9 contains head of list + +.list_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .list_pointer + + ; A value in RSI, so copy + + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_list) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .list_append + +.list_pointer: + ; List element is a pointer to something + push rsi + push r8 + push r9 + push r15 ; Env + mov rdi, [rsi + Cons.car] ; Get the address + mov rsi, r15 + + call incref_object ; Environment increment refs + xchg rsi, rdi ; Env in RDI, AST in RSI + + call incref_object ; AST increment refs + + call eval ; Evaluate it, result in rax + pop r15 + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .list_eval_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_list + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .list_append + +.list_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_list) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi + + ; Fall through to .list_append +.list_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .list_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .list_next + +.list_first: + mov r8, rax + mov r9, rax + ; fall through to .list_next + +.list_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .list_done ; finished list + mov rsi, [rsi + Cons.cdr] ; next in list + jmp .list_loop + +.list_done: + mov rax, r8 ; Return the list + ret + + ; --------------------- +.map: + ; Create a new map, evaluating all the values + + ; Check if the map is empty + cmp al, maltype_empty_map + jne .map_not_empty + + ; map empty. Just return it + call incref_object + mov rax, rsi + ret + +.map_not_empty: + + mov r10, rsi ; input in R10 + xor r12, r12 ; New map in r12 + + ; Now loop through each key-value pair + ; NOTE: This method relies on the implementation + ; of map as a list + +.map_loop: + ; Copy the key + call alloc_cons ; New Cons in RAX + + mov bl, [r10 + Cons.typecar] ; Type in BL + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] ; Value in RCX + mov [rax + Cons.car], rcx + + ; Check the type of the key + and bl, content_mask + cmp bl, content_pointer + jne .map_got_key ; a value + + ; a pointer, so increment reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.map_got_key: + cmp r12,0 + jne .append_key + + ; First key + mov r12, rax + mov r13, rax + jmp .map_value + +.append_key: + ; Appending to previous value in r13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + +.map_value: + ; Check that we have a value + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_error_missing_value + mov r10, [r10 + Cons.cdr] + + ; Now got value in r10 + + ; Check the type of the value + mov bl, [r10 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .map_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r10 + Cons.typecar] + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .map_got_value +.map_value_pointer: + ; A pointer, so need to evaluate + push r10 ; Input + push r12 ; start of result + push r13 ; Current head of result + push r15 ; Env + mov rsi, [r10 + Cons.car] ; Get the address + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi + + call incref_object + + call eval ; Evaluate it, result in rax + pop r15 + pop r13 + pop r12 + pop r10 + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + + jne .map_eval_pointer + + ; A value, so just change the type to a map + and bl, content_mask + add bl, (block_cons + container_map) + mov [rax], BYTE bl + jmp .map_got_value + +.map_eval_pointer: + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_map + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + +.map_got_value: + ; Append RAX to list in R13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + + ; Check if there's another key + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_done ; finished map + mov r10, [r10 + Cons.cdr] ; next in map + jmp .map_loop + +.map_done: + mov rax, r12 + ret + +.map_error_missing_value: + mov rax, r12 + ret + + ; ------------------------------ +.vector: + ; Evaluate each element of the vector + ; + xor r8, r8 ; The vector to return + ; r9 contains head of vector + +.vector_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .vector_pointer + + ; A value, so copy + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_vector) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .vector_append + +.vector_pointer: + ; Vector element is a pointer to something + push rsi + push r8 + push r9 + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Get the address + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi + + call incref_object + + call eval ; Evaluate it, result in rax + pop r15 + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .vector_eval_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_vector + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .vector_append + +.vector_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_vector) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi + +.vector_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .vector_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .vector_next + +.vector_first: + mov r8, rax + mov r9, rax + ; fall through to .vector_next + +.vector_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .vector_done ; finished vector + mov rsi, [rsi + Cons.cdr] ; next in vector + jmp .vector_loop + +.vector_done: + mov rax, r8 ; Return the vector + ret + + ; --------------------- +.done: + ret + + + +;; Comparison of symbols for eval function +;; Compares the symbol in RSI with specified symbol +;; Preserves RSI and RBX +;; Modifies RDI +%macro eval_cmp_symbol 1 + push rsi + push rbx + mov rsi, rbx + mov rdi, %1 + call compare_char_array + pop rbx + pop rsi + test rax, rax ; ZF set if rax = 0 (equal) +%endmacro + +;; ---------------------------------------------------- +;; Evaluates a form +;; +;; Input: RSI AST to evaluate [ Released ] +;; RDI Environment [ Released ] +;; +;; Returns: Result in RAX +;; +;; Note: Both the form and environment will have their reference count +;; reduced by one (released). This is for tail call optimisation (Env), +;; quasiquote and macroexpand (AST) +;; +eval: + mov r15, rdi ; Env + + push rsi ; AST pushed, must be popped before return + + ; Check type + mov al, BYTE [rsi] + cmp al, maltype_empty_list + je .return_nil + + and al, container_mask + cmp al, container_list + je .list + + ; Not a list. Evaluate and return + call eval_ast + jmp .return ; Releases Env + + ; -------------------- +.list: + ; A list + + ; Macro expand + pop rax ; Old AST, discard from stack + call macroexpand ; Replaces RSI + push rsi ; New AST + + ; Check if RSI is a list, and if + ; the first element is a symbol + mov al, BYTE [rsi] + + mov ah, al + and ah, container_mask + cmp ah, container_list + je .list_still_list + + ; Not a list, so call eval_ast on it + mov rdi, r15 ; Environment + call eval_ast + jmp .return + +.list_still_list: + and al, content_mask + cmp al, content_pointer + jne .list_eval + + mov rbx, [rsi + Cons.car] + mov al, BYTE [rbx] + cmp al, maltype_symbol + jne .list_eval + + ; Is a symbol, address in RBX + + ; Compare against special form symbols + + eval_cmp_symbol def_symbol ; def! + je .def_symbol + + eval_cmp_symbol let_symbol ; let* + je .let_symbol + + eval_cmp_symbol do_symbol ; do + je .do_symbol + + eval_cmp_symbol if_symbol ; if + je .if_symbol + + eval_cmp_symbol fn_symbol ; fn + je .fn_symbol + + eval_cmp_symbol quote_symbol ; quote + je .quote_symbol + + eval_cmp_symbol quasiquote_symbol ; quasiquote + je .quasiquote_symbol + + eval_cmp_symbol defmacro_symbol ; defmacro! + je .defmacro_symbol + + eval_cmp_symbol macroexpand_symbol ; macroexpand + je .macroexpand_symbol + + ; Unrecognised + jmp .list_eval + + + ; ----------------------------- + +.defmacro_symbol: + mov r9, 1 + jmp .def_common +.def_symbol: + xor r9, r9 ; Set R9 to 0 +.def_common: + ; Define a new symbol in current environment + ; If R9 is set to 1 then defmacro + + ; Next item should be a symbol + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Now should have a symbol + + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + jne .def_error_expecting_symbol + mov r8, [rsi + Cons.car] ; Symbol (?) + + mov al, BYTE [r8] + cmp al, maltype_symbol + jne .def_error_expecting_symbol + + ; R8 now contains a symbol + + ; expecting a value or pointer next + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a pointer + mov al, BYTE [rsi] + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .def_pointer + + ; A value, so copy + + ; Test if this is defmacro! + test r9, r9 + jnz .defmacro_not_function + + push rax + call alloc_cons + pop rbx ; BL now contains type + and bl, content_mask + add bl, (block_cons + container_value) + mov [rax], BYTE bl + mov rcx, [rsi + Cons.car] + mov [rax + Cons.car], rcx + mov rsi, rax + + jmp .def_got_value + +.def_pointer: + ; A pointer, so evaluate + + ; This may throw an error, so define a handler + + push r8 ; the symbol + push r15 ; Env + push r9 + mov rsi, [rsi + Cons.car] ; Pointer + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call incref_object ; AST increment refs + + call eval + mov rsi, rax + + pop r9 + + ; If this is defmacro, and the object in RSI is a function, + ; then change to a macro + test r9, r9 + jz .def_not_macro ; Not defmacro + + ; Check RSI + mov al, BYTE [rsi] + cmp al, maltype_function + jne .defmacro_not_function + + ; Got a function, change to macro + mov [rsi], BYTE maltype_macro + +.def_not_macro: + + pop r15 + pop r8 + +.def_got_value: + ; Symbol in R8, value in RSI + mov rdi, r8 ; key (symbol) + mov rcx, rsi ; Value + mov rsi, r15 ; Environment + call env_set + + mov rax, rcx + jmp .return + +.def_error_missing_arg: + mov rsi, def_missing_arg_string + mov rdx, def_missing_arg_string.len + jmp .def_handle_error + +.def_error_expecting_symbol: + mov rsi, def_expecting_symbol_string + mov rdx, def_expecting_symbol_string.len + jmp .def_handle_error + +.defmacro_not_function: + mov rsi, defmacro_expecting_function_string + mov rdx, defmacro_expecting_function_string.len + jmp .def_handle_error + +.def_handle_error: + push rsi + push rdx + print_str_mac error_string ; print 'Error: ' + + pop rdx + pop rsi + call print_rawstring ; print message + + xor rsi, rsi ; no object to throw + jmp error_throw ; No return + + ; ----------------------------- +.let_symbol: + ; Create a new environment + + mov r11, rsi ; Let form in R11 + + mov rsi, r15 ; Outer env + call env_new ; Increments R15's ref count + mov r14, rax ; New environment in R14 + + mov rsi, r15 + call release_object ; Decrement R15 ref count + + ; Second element should be the bindings + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_bindings + mov r11, [r11 + Cons.cdr] + + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .let_error_bindings_list + + mov r12, [r11 + Cons.car] ; should be bindings list + mov al, BYTE [r12] + and al, (block_mask + container_mask) + ; Can be either a list or vector + cmp al, block_cons + container_list + je .let_bind_loop + cmp al, block_cons + container_vector + je .let_bind_loop + + ; Not a list or vector + jmp .let_error_bindings_list + +.let_bind_loop: + ; R12 now contains a list with an even number of items + ; The first should be a symbol, then a value to evaluate + + ; Get the symbol + mov al, BYTE [r12] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_symbol + + mov r13, [r12 + Cons.car] ; Symbol (?) + mov al, BYTE [r13] + cmp al, maltype_symbol + jne .let_error_bind_symbol + + ; R13 now contains a symbol to bind + ; The next item in the bindings list (R12) + ; should be a value or expression to evaluate + + mov al, BYTE [r12 + Cons.typecdr] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_value + mov r12, [r12 + Cons.cdr] + + ; got value in R12 + + ; Check the type of the value + mov bl, [r12 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .let_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r12 + Cons.typecar] + and bl, content_mask + ;or bl, (block_cons + container_value) ; 0 + mov [rax + Cons.typecar], bl + mov rcx, [r12 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .let_got_value + +.let_value_pointer: + ; A pointer, so need to evaluate + push r11 ; let* form list + push r12 ; Position in bindings list + push r13 ; symbol to bind + push r14 ; new environment + + mov rsi, r14 + call incref_object + mov rdi, r14 + + mov rsi, [r12 + Cons.car] ; Get the address + + call incref_object ; Increment ref count of AST + + call eval ; Evaluate it, result in rax + pop r14 + pop r13 + pop r12 + pop r11 + +.let_got_value: + + mov rsi, r14 ; Env + mov rdi, r13 ; key + mov rcx, rax ; value + call env_set + + ; Release the value + mov rsi, rcx ; The value + call release_object + + ; Check if there are more bindings + mov al, BYTE [r12 + Cons.typecdr] + cmp al, content_pointer + jne .let_done_binding + mov r12, [r12 + Cons.cdr] ; Next + jmp .let_bind_loop + +.let_done_binding: + ; Done bindings. + ; Evaluate next item in let* form in new environment + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_body + mov r11, [r11 + Cons.cdr] ; Now contains value to evaluate + ; Check type of the value + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + je .body_pointer + + ; Just a value, so copy + call alloc_cons + mov bl, BYTE [r11] + and bl, content_mask + mov [rax], BYTE bl ; set type + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx ; copy value + jmp .let_done + +.body_pointer: + ; Evaluate using new environment + + mov rsi, [r11 + Cons.car] ; Object pointed to + call incref_object ; will be released by eval + + mov r11, rsi ; save new AST + pop rsi ; Old AST + call release_object + mov rsi, r11 ; New AST + + mov rdi, r14 ; New environment + + jmp eval ; Tail call + ; Note: eval will release the new environment on return + +.let_done: + ; Release the new environment + push rax + mov rsi, r14 + call release_object + pop rax + + ; Release the AST + pop rsi + push rax + call release_object + pop rax + ret ; already released env + +.let_error_missing_bindings: + mov rsi, let_missing_bindings_string + mov rdx, let_missing_bindings_string.len + jmp .let_handle_error + +.let_error_bindings_list: ; expected a list or vector, got something else + mov rsi, let_bindings_list_string + mov rdx, let_bindings_list_string.len + jmp .let_handle_error + +.let_error_bind_symbol: ; expected a symbol, got something else + mov rsi, let_bind_symbol_string + mov rdx, let_bind_symbol_string.len + jmp .let_handle_error + +.let_error_bind_value: ; Missing value in binding list + mov rsi, let_bind_value_string + mov rdx, let_bind_value_string.len + jmp .let_handle_error + +.let_error_missing_body: ; Missing body to evaluate + mov rsi, let_missing_body_string + mov rdx, let_missing_body_string.len + jmp .let_handle_error + +.let_handle_error: + push r11 ; For printing later + + push rsi + push rdx + + print_str_mac error_string ; print 'Error: ' + + pop rdx + pop rsi + call print_rawstring ; print message + + pop rsi ; let* form + jmp error_throw ; No return + + ; ----------------------------- + +.do_symbol: + mov r11, rsi ; do form in RSI + ; Environment in R15 + + ; Check if there is a body + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .do_no_body ; error + + mov r11, [r11 + Cons.cdr] ; Body in R11 + +.do_symbol_loop: + + ; Need to test if this is the last form + ; so we can handle tail call + + mov bl, BYTE [r11 + Cons.typecdr] + cmp bl, content_pointer + jne .do_body_last ; Last expression + + ; not the last expression + + ; Check if this is a value or pointer + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + jne .do_next ; A value, so skip + + ; A pointer, so evaluate + + push r15 + push r11 + + mov rsi, r15 + call incref_object ; Increase Env reference + ; since eval will release Env + + mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Increment ref count since eval will release + + mov rdi, r15 ; Env + call eval ; Result in RAX + + ; Another form after this. + ; Discard the result of the last eval + mov rsi, rax + call release_object + + pop r11 + pop r15 + +.do_next: + mov r11, [r11 + Cons.cdr] ; Next in list + + jmp .do_symbol_loop + +.do_body_last: + ; The last form is in R11, which will be returned + + ; Check if this is a value or pointer + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + jne .do_body_value_return + jmp .do_body_expr_return + +.do_body_value_return: + ; Got a value as last form (in R11). + ; Copy and return + + push rax ; Type of value to return + + ; release Env + mov rsi, r15 + call release_object + + ; Allocate a Cons object to hold value + call alloc_cons + pop rbx ; type in BL + mov [rax], BYTE bl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + + ; release the AST + pop rsi + mov r15, rax ; not modified by release + call release_object + mov rax, r15 + + ret + +.do_body_expr_return: + ; An expression to evaluate as the last form + ; Tail call optimise, jumping to eval + ; Don't increment Env reference count + + mov rsi, [r11 + Cons.car] ; new AST form + call incref_object ; This will be released by eval + + mov r11, rsi ; Save new AST + pop rsi ; Remove old AST from stack + call release_object + mov rsi, r11 + + mov rdi, r15 ; Env + jmp eval ; Tail call + +.do_no_body: + ; No expressions to evaluate. Return nil + + mov rsi, r15 + call release_object ; Release Env + + ; release the AST + pop rsi + call release_object + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret + + ; ----------------------------- + +.if_symbol: + mov r11, rsi ; if form in R11 + ; Environment in R15 + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .if_no_condition + + mov r11, [r11 + Cons.cdr] ; Should be a condition + + ; Check if value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .if_cond_value + + ; A pointer, so evaluate + + push r15 + push r11 + + mov rsi, r15 + call incref_object ; Increase Env reference + + mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Increase Form/AST ref count + + mov rdi, r15 ; Env + call eval ; Result in RAX + pop r11 + pop r15 + + ; Get type of result + mov bl, BYTE [rax] + + ; release value + push rbx + mov rsi, rax + call release_object + pop rbx + + ; Check type + cmp bl, maltype_nil + je .if_false + cmp bl, maltype_false + je .if_false + + jmp .if_true + +.if_cond_value: + + ; A value + cmp al, content_nil + je .if_false + cmp al, content_false + je .if_false + + jmp .if_true + +.if_false: + ; Skip the next item + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .return_nil + + mov r11, [r11 + Cons.cdr] + +.if_true: + ; Get the next item in the list and evaluate it + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; Nothing to return + + mov r11, [r11 + Cons.cdr] + + ; Check if value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + je .if_got_pointer + +.if_got_value: + ; copy value in r11 + call alloc_cons + mov bl, BYTE [r11] + and bl, content_mask + mov [rax], BYTE bl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + + jmp .return + +.if_got_pointer: + mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Will be released by eval + + mov r11, rsi + pop rsi + call release_object ; Release old AST + mov rsi, r11 ; New AST + + mov rdi, r15 ; Env + jmp eval ; Tail call + +.if_no_condition: ; just (if) without a condition + + print_str_mac error_string + print_str_mac if_missing_condition_string + + ; Release environment + mov rsi, r15 + call release_object + xor rsi, rsi ; No object to throw + jmp error_throw + +.return_nil: + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + +.return: + ; Release environment + mov rsi, r15 + mov r15, rax ; Save RAX (return value) + call release_object + + ; Release the AST + pop rsi ; Pushed at start of eval + call release_object + + mov rax, r15 ; return value + ret + + ; ----------------------------- + +.fn_symbol: + mov r11, rsi ; fn form in R11 + ; Environment in R15 + + ; Get the binds and body of the function + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .fn_empty + + mov r11, [r11 + Cons.cdr] + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .fn_binds_not_list + + mov r12, [r11 + Cons.car] ; Should be binds list + mov al, BYTE [r12] + and al, (block_mask + container_mask) + cmp al, (block_cons + container_list) + je .fn_got_binds ; Can be list + cmp al, (block_cons + container_vector) + je .fn_got_binds ; or vector + jmp .fn_binds_not_list + +.fn_got_binds: + + ; Next get the body of the function + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .fn_no_body + + mov r11, [r11 + Cons.cdr] + ; Check value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .fn_is_value ; Body in r11 + mov r11, [r11 + Cons.car] + jmp .fn_got_body + +.fn_is_value: + ; Body is just a value, no expression + mov [r11], BYTE al ; Mark as value, not list + +.fn_got_body: + + ; Now put into function type + ; Addr is "apply_fn", the address to call + ; Env in R15 + ; Binds in R12 + ; Body in R11 + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_function) + mov rbx, apply_fn + mov [rax + Cons.car], rbx ; Address of apply function + mov [rax + Cons.typecdr], BYTE content_pointer + + mov r13, rax ; Return list in R13 + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r15 ; Environment + mov [rax + Cons.typecdr], BYTE content_pointer + + mov [r13 + Cons.cdr], rax ; Append to list + mov r14, rax + + push rax + mov rsi, r15 + call incref_object + pop rax + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r12 ; Binds list + mov [rax + Cons.typecdr], BYTE content_pointer + + mov [r14 + Cons.cdr], rax ; Append to list + mov r14, rax + + push rax + mov rsi, r12 + call incref_object + pop rax + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r11 ; Body of function + + mov [r14 + Cons.cdr], rax + + mov rsi, r11 + call incref_object + + mov rax, r13 + jmp .return + +.fn_empty: +.fn_binds_not_list: +.fn_no_body: + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + jmp .return + + ; ----------------------------- + +.quote_symbol: + ; Just return the arguments in rsi cdr + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; quote empty, so return nil + + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a value or pointer + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + je .quote_pointer + + ; RSI contains a value. Remove the list container + mov [rsi + Cons.typecar], BYTE al + call incref_object + mov rax, rsi + jmp .return + +.quote_pointer: + ; RSI contains a pointer, so get the object pointed to + mov rsi, [rsi + Cons.car] + call incref_object + mov rax, rsi + jmp .return + + ; ----------------------------- + +.quasiquote_symbol: + ; call quasiquote function with first argument + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; quasiquote empty, so return nil + + mov r11, rsi ; Save original AST in R11 + + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a value or pointer + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + je .quasiquote_pointer + + ; RSI contains a value. Remove the list container + mov [rsi + Cons.typecar], BYTE al + call incref_object + mov rax, rsi + jmp .return + +.quasiquote_pointer: + ; RSI contains a pointer, so get the object pointed to + mov rsi, [rsi + Cons.car] + + ; Uncomment these two lines to test quasiquote + ;call quasiquote + ;ret + + push r15 ; Environment + ; Original AST already on stack + + call quasiquote + ; New AST in RAX + pop rdi ; Environment + pop rsi ; Old AST + + mov r11, rax ; New AST + call release_object ; Release old AST + mov rsi, r11 ; New AST in RSI + + jmp eval ; Tail call + + ; ----------------------------- +.macroexpand_symbol: + ; Check if we have a second list element + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; No argument + + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a value or pointer + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + je .macroexpand_pointer + + ; RSI contains a value. Remove the list container + mov [rsi + Cons.typecar], BYTE al + call incref_object + mov rax, rsi + jmp .return + +.macroexpand_pointer: + mov rsi, [rsi + Cons.car] + call incref_object ; Since RSI will be released + + call macroexpand ; May release and replace RSI + + mov rax, rsi + jmp .return ; Releases original AST + + ; ----------------------------- + +.list_eval: + push rsi + mov rdi, r15 ; Environment + push r15 + call eval_ast ; List of evaluated forms in RAX + pop r15 + pop rsi + + +.list_exec: + ; This point can be called to run a function + ; used by swap! + ; + ; Inputs: RAX - List with function as first element + ; NOTE: This list is released + ; + ; Check that the first element of the return is a function + mov bl, BYTE [rax] + and bl, content_mask + cmp bl, content_pointer + jne .list_not_function + + mov rbx, [rax + Cons.car] ; Get the address + mov cl, BYTE [rbx] + cmp cl, maltype_function + jne .list_not_function + + ; Check the rest of the args + mov cl, BYTE [rax + Cons.typecdr] + cmp cl, content_pointer + je .list_got_args + + ; No arguments + push rbx + call alloc_cons + mov [rax], BYTE maltype_empty_list + pop rbx + mov rsi, rax + jmp .list_function_call +.list_got_args: + mov rsi, [rax + Cons.cdr] ; Rest of list +.list_function_call: + ; Call the function with the rest of the list in RSI + + mov rdx, rax ; List to release + mov rdi, rbx ; Function object in RDI + + mov rbx, [rbx + Cons.car] ; Call function + cmp rbx, apply_fn + je apply_fn_jmp ; Jump to user function apply + + ; A built-in function, so call (no recursion) + push rax + push r15 + + call rbx + + ; Result in rax + pop r15 + pop rsi ; eval'ed list + + push rax + call release_cons + pop rax + + jmp .return ; Releases Env + +.list_not_function: + ; Not a function. Probably an error + push rsi + + mov rsi, rax + call release_object + + print_str_mac error_string + print_str_mac eval_list_not_function + pop rsi + jmp error_throw + + +;; Applies a user-defined function +;; +;; Input: RSI - Arguments to bind +;; RDI - Function object +;; RDX - list to release after binding +;; R15 - Env (will be released) +;; R13 - AST released before return +;; +;; +;; Output: Result in RAX +;; +;; This is jumped to from eval, so if it returns +;; then it will return to the caller of eval, not to eval +apply_fn_jmp: + ; This is jumped to from eval with AST on the stack + pop r13 +apply_fn: + push rsi + ; Extract values from the list in RDI + mov rax, [rdi + Cons.cdr] + mov rsi, [rax + Cons.car] ; Env + mov rax, [rax + Cons.cdr] + mov rdi, [rax + Cons.car] ; Binds + mov rax, [rax + Cons.cdr] + mov rax, [rax + Cons.car] ; Body + pop rcx ; Exprs + + ; Check the type of the body + mov bl, BYTE [rax] + and bl, block_mask + container_mask + jnz .bind + ; Just a value (in RAX). No eval needed + + mov r14, rax ; Save return value in R14 + + mov rsi, rax + call incref_object + + ; Release the list passed in RDX + mov rsi, rdx + call release_object + + ; Release the environment + mov rsi, r15 + call release_object + + ; Release the AST + mov rsi, r13 + call release_object + + mov rax, r14 + ret +.bind: + ; Create a new environment, binding arguments + push rax ; Body + + mov r14, r13 ; Old AST. R13 used by env_new_bind + + push rdx + call env_new_bind + pop rdx + + mov rdi, rax ; New environment in RDI + + ; Release the list passed in RDX + mov rsi, rdx + call release_cons + + ; Release the environment + mov rsi, r15 + call release_object + + ; Release the old AST + mov rsi, r14 + call release_object + + pop rsi ; Body + call incref_object ; Will be released by eval + + jmp eval ; Tail call + ; The new environment (in RDI) will be released by eval + + +;; Set ZF if RSI is a non-empty list or vector +;; Modifies RAX, does not modify RSI +is_pair: + mov al, BYTE [rsi] + test al, block_mask + jnz .false ; Not a Cons + cmp al, maltype_empty_list + je .false ; Empty list + cmp al, maltype_empty_vector + je .false ; Empty vector + + ; Something non empty + and al, container_mask + cmp al, container_list + je .true + cmp al, container_vector + je .true + ; Not a list or vector -> false + +.false: + lahf ; flags in AH + and ah, 255-64 ; clear zero flag + sahf + ret +.true: + lahf ; flags in AH + or ah, 64 ; set zero flag + sahf + ret + +;; Called by eval with AST in RSI [ modified ] +;; Returns new AST in RAX +quasiquote: + ; i. Check if AST is an empty list + call is_pair + jne .quote_ast + + ; ii. Check if the first element of RSI is the symbol + ; 'unquote' + + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .not_unquote ; Not a pointer + + mov rdi, [rsi + Cons.car] ; Get the pointer + mov cl, BYTE [rdi] + cmp cl, maltype_symbol + jne .not_unquote + + ; Compare against 'unquote' + mov r8, rsi + mov r9, rax + + mov rsi, unquote_symbol + call compare_char_array + test rax, rax + + mov rax, r9 + mov rsi, r8 + + je .unquote + +.not_unquote: + ; iii. Handle splice-unquote + ; RSI -> ( ( splice-unquote ? ) ? ) + + ; Test if RSI contains a pointer + + cmp al, content_pointer + jne .not_splice + + mov rbx, [rsi + Cons.car] ; Get the object pointer + + ; RBX -> ( splice-unquote ? ) + + xchg rbx, rsi + call is_pair + xchg rbx, rsi + jne .not_splice ; First element not a pair + + ; Check if this list in RBX starts with 'splice-unquote' symbol + mov al, BYTE [rbx] + and al, content_mask + cmp al, content_pointer + jne .not_splice + + + mov rdi, [rbx + Cons.car] ; Get the pointer + mov al, BYTE [rdi] + cmp al, maltype_symbol + jne .not_splice + + mov r8, rsi + mov r9, rbx + + ; Compare against 'splice-unquote' + mov rsi, splice_unquote_symbol + call compare_char_array + test rax, rax + + mov rbx, r9 + mov rsi, r8 + + je .splice_unquote + +.not_splice: + + ; iv. Cons first and rest of AST in RSI + + ; check if pointer or value + mov cl, BYTE [rsi] + and cl, content_mask + cmp cl, content_pointer + je .cons_pointer + + ; a value, so copy + call alloc_cons + or cl, container_list + mov [rax], BYTE cl ; List + Content + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx + mov rcx, rax + jmp .cons_first + +.cons_pointer: + ; Get the pointer and call quasiquote + push rsi + mov rsi, [rsi + Cons.car] + call quasiquote + mov rcx, rax + pop rsi + + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rcx + mov rcx, rax + +.cons_first: + ; Have Cons with first object in RCX + + ; Call quasiquote on the rest of the AST + ; Check if this is the end of the list + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .cons_ast_end + + mov rsi, [rsi + Cons.cdr] ; Rest of the list + + call incref_object ; Will release after quasiquote call + + jmp .cons_quasiquote_ast + +.cons_ast_end: + ; End of the AST, so make an empty list + call alloc_cons + mov [rax], BYTE maltype_empty_list + mov rsi, rax + +.cons_quasiquote_ast: + push rcx + push rsi + call quasiquote + mov rdx, rax ; List in RDX + + pop rsi + call release_object ; Release input + + pop rcx ; Value in RCX + + ; cons RCX and RDX + ; Work from the end of the list to the front + + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rdx ; The rest of AST + + ; Link to the RCX Cons + mov [rcx + Cons.typecdr], BYTE content_pointer + mov [rcx + Cons.cdr], rax + mov rdx, rcx + + call alloc_cons ; Cons for cons symbol + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rdx + mov rdx, rax + + ; Get the cons symbol + mov rsi, cons_symbol + call incref_object + + mov [rdx], BYTE (container_list + content_pointer) + mov [rdx + Cons.car], rsi + + mov rax, rdx + ret + +.quote_ast: + ; Return (quote RSI) + + call incref_object ; RSI reference count + + ; Cons for RSI + call alloc_cons + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], rsi + mov rsi, rax + + ; Cons for quote symbol + call alloc_cons + mov rbx, rax + mov [rbx + Cons.typecdr], BYTE content_pointer + mov [rbx + Cons.cdr], rsi + + ; Get a quote symbol, incrementing references + mov rsi, quote_symbol + call incref_object + + ; Put into the Cons in RBX + mov [rbx + Cons.car], rsi + mov [rbx], BYTE (block_cons + container_list + content_pointer) + mov rax, rbx + ret + ; ----------------------- + +.unquote: + + ; Got unquote symbol. Return second element of RSI + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .empty_list ; No second element + + mov rsi, [rsi + Cons.cdr] + + ; Check if it's a value or pointer + mov cl, BYTE [rsi] + and cl, content_mask + cmp cl, content_pointer + je .unquote_pointer + + ; A value, so need a new Cons + call alloc_cons + mov [rax], BYTE cl ; content + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; Copy content + ret + +.unquote_pointer: + mov rsi, [rsi + Cons.car] + call incref_object + mov rax, rsi + ret + + ; ----------------------- +.splice_unquote: + ; RSI -> ( RBX->( splice-unquote A ) B ) + ; + ; RBX Car points to splice-unquote symbol + + ; Check if there is anything after the symbol + mov al, BYTE [rbx + Cons.typecdr] + cmp al, content_pointer + jne .splice_unquote_empty + + ; Point to the second element of the splice-unquote list + mov rcx, [rbx + Cons.cdr] + + ; Check whether it's a value or pointer + mov al, BYTE [rcx] + and al, content_mask + cmp al, content_pointer + je .splice_unquote_pointer + + ; A value, so change the container to a value + mov [rcx], BYTE al + ; Remove pointer from RBX + mov [rbx + Cons.typecdr], BYTE 0 + jmp .splice_unquote_first ; Got the value in RCX + +.splice_unquote_pointer: + mov rcx, [rcx + Cons.car] ; Get the object pointed to + xchg rcx, rsi + call incref_object + xchg rcx, rsi ; Object in RCX + +.splice_unquote_first: ; Got the first object in RCX + + ; Check if RSI contains anything else + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .splice_unquote_notail + + mov rsi, [rsi + Cons.cdr] + + ; Now have: + ; ( ( splice-unquote A ) B ) + ; RCX->A RSI->( B ) + ; Need to call quasiquote on the rest of the list + push rcx + call quasiquote + mov rdx, rax + pop rcx + ; Need to concat rcx and rdx + ; Work from the end of the list to the front + + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rdx ; The rest of AST + mov rdx, rax ; Push list into RDX + + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rcx ; The splice-unquote object + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rdx + mov rdx, rax + + call alloc_cons ; Cons for concat symbol + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rdx + mov rdx, rax + + ; Get the concat symbol + mov rsi, concat_symbol + call incref_object + + mov [rdx], BYTE (container_list + content_pointer) + mov [rdx + Cons.car], rsi + + mov rax, rdx + ret + +.splice_unquote_notail: + ; Just return the object in RCX + ; since nothing to concatenate with + mov rax, rcx + ret + +.splice_unquote_empty: + ; Nothing in the (splice-unquote) list, so ignore + ; Just call quasiquote on the rest of RSI + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .empty_list ; Nothing else + + mov rsi, [rsi + Cons.cdr] + jmp quasiquote ; Tail call + +.empty_list: + ; Return an empty list + call alloc_cons + mov [rax], BYTE maltype_empty_list +.return: + ret + + +;; Tests if an AST in RSI is a list containing +;; a macro defined in the ENV in R15 +;; +;; Inputs: AST in RSI (not modified) +;; ENV in R15 (not modified) +;; +;; Returns: Sets ZF if macro call. If set (true), +;; then the macro object is in RAX +;; +;; Modifies: +;; RAX +;; RBX +;; RCX +;; RDX +;; R8 +;; R9 +is_macro_call: + ; Test if RSI is a list which contains a pointer + mov al, BYTE [rsi] + cmp al, (block_cons + container_list + content_pointer) + jne .false + + ; Test if this is a symbol + mov rbx, [rsi + Cons.car] + mov al, BYTE [rbx] + cmp al, maltype_symbol + jne .false + + ; Look up symbol in Env + push rsi + push r15 + mov rdi, rbx ; symbol in RDI + mov rsi, r15 ; Environment in RSI + call env_get + pop r15 + pop rsi + jne .false ; Not in environment + + ; Object in RAX + ; If this is not a macro then needs to be released + mov dl, BYTE [rax] + + cmp dl, maltype_macro + je .true + + ; Not a macro, so release + mov r8, rsi + mov rsi, rax + call release_object + mov rsi, r8 + +.false: + lahf ; flags in AH + and ah, 255-64 ; clear zero flag + sahf + ret +.true: + mov rbx, rax ; Returning Macro object + lahf ; flags in AH + or ah, 64 ; set zero flag + sahf + mov rax, rbx + ret + +;; Expands macro calls +;; +;; Input: AST in RSI (released and replaced) +;; Env in R15 (not modified) +;; +;; Result: New AST in RSI +macroexpand: + push r15 + + call is_macro_call + jne .done + + mov r13, rsi + + mov rdi, rax ; Macro in RDI + + ; Check the rest of the args + mov cl, BYTE [rsi + Cons.typecdr] + cmp cl, content_pointer + je .got_args + + ; No arguments. Create an empty list + call alloc_cons + mov [rax], BYTE maltype_empty_list + mov rdx, rax + + mov rsi, rdx ; Arguments (empty list) + call incref_object + jmp .macro_call +.got_args: + mov rsi, [rsi + Cons.cdr] ; Rest of list + call incref_object + mov rdx, rsi ; Released +.macro_call: + ; Here have: + ; RSI - Arguments + ; RDI - Macro object + ; RDX - List to release + ; R15 - Environment + ; R13 - AST + + ; Increment reference for Environment + ; since this will be released by apply_fn + xchg rsi, r15 + call incref_object + xchg rsi, r15 + + call apply_fn + + mov rsi, rax ; Result in RSI + + pop r15 + jmp macroexpand +.done: + pop r15 + ret + +;; Read-Eval-Print in sequence +;; +;; Input string in RSI +rep_seq: + ; ------------- + ; Read + call read_str + + ; ------------- + ; Eval + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call eval ; This releases Env and Form/AST + push rax ; Save result of eval + + ; ------------- + ; Print + + ; Put into pr_str + mov rsi, rax + mov rdi, 1 ; print_readably + call pr_str + push rax ; Save output string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from pr_str + pop rsi + call release_array + + ; Release result of eval + pop rsi + call release_object + + ; The AST from read_str is released by eval + + ret + + +_start: + ; Create and print the core environment + call core_environment ; Environment in RAX + + mov [repl_env], rax ; store in memory + + ; Set the error handler + mov rsi, rsp ; Stack pointer + mov rdi, .catch ; Address to jump to + xor rcx, rcx ; No data + call error_handler_push + + ; Evaluate the startup string + + mov rsi, mal_startup_string + mov edx, mal_startup_string.len + call raw_to_string ; String in RAX + + push rax + mov rsi, rax + call read_str ; AST in RAX + pop rsi ; string + + push rax ; AST + call release_array ; string + pop rdi ; AST in RDI + + mov rsi, [repl_env] ; Environment in RSI + + call incref_object ; Environment increment refs + xchg rsi, rdi ; Env in RDI, AST in RSI + + call eval + + mov rsi, rax + call release_object ; Return from eval + + ; ----------------------------- + ; Check command-line arguments + + pop rax ; Number of arguments + cmp rax, 1 ; Always have at least one, the path to executable + jg run_script + + ; No extra arguments, so just set *ARGV* to an empty list + call alloc_cons ; in RAX + mov [rax], BYTE maltype_empty_list + mov rcx, rax ; value (empty list) + mov rdi, argv_symbol ; symbol (*ARGV*) + mov rsi, [repl_env] ; environment + call env_set + + ; ----------------------------- + ; Main loop + +.mainLoop: + ; print the prompt + print_str_mac prompt_string + + call read_line + + ; Check if we have a zero-length string + cmp DWORD [rax+Array.length], 0 + je .mainLoopEnd + + push rax ; Save address of the input string + + ; Put into read_str + mov rsi, rax + call rep_seq + + ; Release the input string + pop rsi + call release_array + + jmp .mainLoop +.mainLoopEnd: + + jmp quit + +.catch: + ; Jumps here on error + + ; Check if an object was thrown + cmp rsi, 0 + je .catch_done_print ; nothing to print + mov rdi, 1 + call pr_str + mov rsi, rax + call print_string +.catch_done_print: + jmp .mainLoop ; Go back to the prompt + + + +run_script: + ; Called with number of command-line arguments in RAX + mov r8, rax + pop rbx ; executable + dec r8 + + pop rsi ; Address of first arg + call cstring_to_string ; string in RAX + mov r9, rax + + ; get the rest of the args + xor r10, r10 ; Zero + dec r8 + jz .no_args + + ; Got some arguments +.arg_loop: + ; Got an argument left. + pop rsi ; Address of C string + call cstring_to_string ; String in RAX + mov r12, rax + + ;Make a Cons to point to the string + call alloc_cons ; in RAX + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], r12 + + test r10, r10 + jnz .append + + ; R10 zero, so first arg + mov r10, rax ; Head of list + mov r11, rax ; Tail of list + jmp .next +.append: + ; R10 not zero, so append to list tail + mov [r11 + Cons.cdr], rax + mov [r11 + Cons.typecdr], BYTE content_pointer + mov r11, rax +.next: + dec r8 + jnz .arg_loop + jmp .got_args + +.no_args: + ; No arguments. Create an emoty list + call alloc_cons ; in RAX + mov [rax], BYTE maltype_empty_list + mov r10, rax + +.got_args: + push r9 ; File name string + + mov rcx, r10 ; value (list) + mov rdi, argv_symbol ; symbol (*ARGV*) + mov rsi, [repl_env] ; environment + call env_set + + mov rsi, run_script_string ; load-file function + mov edx, run_script_string.len + call raw_to_string ; String in RAX + + mov rsi, rax + pop rdx ; File name string + call string_append_string + + mov cl, 34 ; " + call string_append_char + mov cl, ')' + call string_append_char ; closing brace + + ; Read-Eval-Print "(load-file )" + call rep_seq + + jmp quit From bd1961f76be05d85d2db4abde93833dcef329d84 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 30 Nov 2017 23:41:32 +0000 Subject: [PATCH 0615/2308] rest core function always returns list If given a vector, allocates a new Cons for the head of the list, pointing to the rest of the elements. This is still O(1), since only needs to copy one element. --- nasm/core.asm | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/nasm/core.asm b/nasm/core.asm index dc94c95f23..08561113c5 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -1398,8 +1398,58 @@ core_rest: .return_rest: mov rsi, [rsi + Cons.cdr] + + ; Check if this is a list or a vector + mov cl, BYTE [rsi] + mov ch, cl + and ch, container_mask + cmp ch, container_list + je .return_list + + ; Need to allocate a new Cons to replace this first element + call alloc_cons + and cl, content_mask + mov ch, cl ; Save CAR content type in ch + or cl, container_list ; Keep content type, set container type to list + mov [rax], BYTE cl + + mov dl, BYTE [rsi + Cons.typecdr] ; CDR type in DL + mov [rax + Cons.typecdr], BYTE dl + + ; Copy content of CAR and CDR + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx + + mov rcx, [rsi + Cons.cdr] + mov [rax + Cons.cdr], rcx ; Note: Might be pointer + + ; Check if car contains a pointer + cmp ch, content_pointer + jne .check_cdr + + ; CAR contains a pointer, so increment reference count + + mov r8, rax ; Save return Cons + mov rsi, rbx ; Content of CAR + call incref_object + mov rax, r8 ; Restore return Cons + +.check_cdr: + ; Check if cdr contains a pointer + cmp dl, content_pointer + jne .return ; Not a pointer, so just return + + ; A pointer, so increment its reference count + mov rbx, rax ; Save the return Cons + mov rsi, rcx ; The pointer in CDR + call incref_object + mov rax, rbx ; Restore the return Cons + ret + +.return_list: call incref_object mov rax, rsi +.return: ret .return_nil: From 7410ca9e12595a325f59b5dc4be9962577ccb453 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 30 Nov 2017 23:42:48 +0000 Subject: [PATCH 0616/2308] Fix bug in read_str for long strings Symbols are read by the tokenizer into RSI, but RSI was overwritten when a new string chunk was fetched. If the reader happened to be reading a symbol as the end of an Array chunk was reached, then the symbol would be changed to part of the input string. All step 8 tests now pass. --- nasm/reader.asm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nasm/reader.asm b/nasm/reader.asm index 4fe92921f1..05872d0537 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -666,8 +666,10 @@ tokenizer_next_chunk: cmp r10, 0 je .no_more ; More chunks left + push rsi mov rsi, r10 call tokenizer_init + pop rsi ret .no_more: ; No more chunks left. R10 is zero From ad66dd9d7d76bdedb8df19b03aa870a02c592440 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 1 Dec 2017 22:35:19 +0000 Subject: [PATCH 0617/2308] Adding predicate functions to core nil? true? false? number? test the contents type symbol? string? fn? and macro? test object types, similar to the atom? function These two sets of functions are each implemented as one function with different input parameters. --- nasm/core.asm | 82 +++++++++++++++++++++++++++++++++++++++++++++++-- nasm/reader.asm | 2 +- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 08561113c5..05eef977ac 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -47,7 +47,16 @@ section .data static core_first_symbol, db "first" static core_rest_symbol, db "rest" static core_nth_symbol, db "nth" - + + static core_nilp_symbol, db "nil?" + static core_truep_symbol, db "true?" + static core_falsep_symbol, db "false?" + static core_numberp_symbol, db "number?" + + static core_symbolp_symbol, db "symbol?" + static core_stringp_symbol, db "string?" + static core_fnp_symbol, db "fn?" + static core_macrop_symbol, db "macro?" ;; Strings static core_emptyp_error_string, db "empty? expects a list, vector or map",10 @@ -73,7 +82,8 @@ section .data static core_nth_not_list, db "Error: nth expects a list or vector as first argument" static core_nth_not_int, db "Error: nth expects an integer as second argument" static core_nth_out_of_range, db "Error: nth index out of range" - + + static core_value_p_missing_args, db "Error: value predicate (nil/true/false) missing args" section .text ;; Add a native function to the core environment @@ -145,6 +155,15 @@ core_environment: core_env_native core_rest_symbol, core_rest core_env_native core_nth_symbol, core_nth + core_env_native core_nilp_symbol, core_nilp + core_env_native core_truep_symbol, core_truep + core_env_native core_falsep_symbol, core_falsep + core_env_native core_numberp_symbol, core_numberp + + core_env_native core_symbolp_symbol, core_symbolp + core_env_native core_stringp_symbol, core_stringp + core_env_native core_fnp_symbol, core_fnp + core_env_native core_macrop_symbol, core_macrop ; ----------------- ; Put the environment in RAX @@ -790,6 +809,22 @@ core_deref: ;; Test if given object is an atom core_atomp: + mov al, maltype_atom + jmp core_pointer_type_p +core_symbolp: + mov al, maltype_symbol + jmp core_pointer_type_p +core_stringp: + mov al, maltype_string + jmp core_pointer_type_p +core_fnp: + mov al, maltype_function + jmp core_pointer_type_p +core_macrop: + mov al, maltype_macro + jmp core_pointer_type_p + +core_pointer_type_p: mov bl, BYTE [rsi] mov bh, bl and bh, content_mask @@ -798,7 +833,7 @@ core_atomp: mov rsi, [rsi + Cons.car] mov bl, BYTE [rsi] - cmp bl, maltype_atom + cmp bl, al jne .false ; Got an atom, return true @@ -1567,3 +1602,44 @@ core_nth: call raw_to_string mov rsi, rax jmp error_throw + +;; Check if the argument is a given value type +core_nilp: + mov al, BYTE content_nil + jmp core_value_type_p +core_truep: + mov al, BYTE content_true + jmp core_value_type_p +core_falsep: + mov al, BYTE content_false + jmp core_value_type_p +core_numberp: + mov al, BYTE content_int +;; predicates for nil, true, false and number jump here +core_value_type_p: + mov bl, BYTE [rsi] + and bl, content_mask + cmp bl, content_empty + je .missing_args + + cmp al, bl + je .true + + ; false + call alloc_cons + mov [rax], BYTE maltype_false + ret +.true: + call alloc_cons + mov [rax], BYTE maltype_true + ret + +.missing_args: + mov rsi, core_value_p_missing_args + mov edx, core_value_p_missing_args.len + + call raw_to_string + mov rsi, rax + jmp error_throw + + diff --git a/nasm/reader.asm b/nasm/reader.asm index 05872d0537..57c8aa92d2 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -666,7 +666,7 @@ tokenizer_next_chunk: cmp r10, 0 je .no_more ; More chunks left - push rsi + push rsi ; Because symbol reading uses RSI (tokenizer_next.handle_symbol) mov rsi, r10 call tokenizer_init pop rsi From c71ed43f49580bd8fde6d1babe1b7767d064e809 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sun, 3 Dec 2017 23:20:37 +0000 Subject: [PATCH 0618/2308] Add vector? map? and contains? vector? and map? almost the same as list? so just change list? to take a container type argument. --- nasm/core.asm | 85 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 05eef977ac..13e28632cb 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -57,6 +57,10 @@ section .data static core_stringp_symbol, db "string?" static core_fnp_symbol, db "fn?" static core_macrop_symbol, db "macro?" + + static core_containsp_symbol, db "contains?" + static core_vectorp_symbol, db "vector?" + static core_mapp_symbol, db "map?" ;; Strings static core_emptyp_error_string, db "empty? expects a list, vector or map",10 @@ -84,6 +88,9 @@ section .data static core_nth_out_of_range, db "Error: nth index out of range" static core_value_p_missing_args, db "Error: value predicate (nil/true/false) missing args" + + static core_containsp_not_map, db "Error: contains? expects map as first argument" + static core_containsp_no_key, db "Error: contains? missing key argument" section .text ;; Add a native function to the core environment @@ -164,6 +171,11 @@ core_environment: core_env_native core_stringp_symbol, core_stringp core_env_native core_fnp_symbol, core_fnp core_env_native core_macrop_symbol, core_macrop + + core_env_native core_containsp_symbol, core_containsp + + core_env_native core_vectorp_symbol, core_vectorp + core_env_native core_mapp_symbol, core_mapp ; ----------------- ; Put the environment in RAX @@ -381,6 +393,15 @@ core_compare_num: ;; Input list in RSI ;; Returns true or false in RAX core_listp: + mov bl, (block_cons + container_list) + jmp core_container_p +core_vectorp: + mov bl, (block_cons + container_vector) + jmp core_container_p +core_mapp: + mov bl, (block_cons + container_map) + ;jmp core_container_p +core_container_p: mov al, BYTE [rsi] and al, content_mask cmp al, content_pointer @@ -389,7 +410,7 @@ core_listp: mov rax, [rsi + Cons.car] mov al, BYTE [rax] and al, (block_mask + container_mask) - cmp al, (block_cons + container_list) + cmp al, bl jne .false ; Is a list, return true @@ -501,13 +522,73 @@ core_keys: call map_keys ret +;; Given a map and a key, return true if the key is in the map +;; +core_containsp: + ; Check the type of the first argument + mov bl, BYTE [rsi] + and bl, content_mask + cmp bl, content_pointer + jne .not_map + + mov rcx, [rsi + Cons.car] ; Map in RCX + mov bl, BYTE [rcx] + and bl, (block_mask + container_mask) + cmp bl, container_map + jne .not_map + + ; Check second argument + mov bl, BYTE [rsi + Cons.typecdr] + cmp bl, content_pointer + jne .no_key + mov rsi, [rsi + Cons.cdr] + mov dl, BYTE [rsi] + and dl, content_mask + cmp dl, content_pointer + jne .key_value + + ; Pointer, so put into RDI + mov rdi, [rsi + Cons.car] + jmp .find + +.key_value: + ; A value + mov [rsi], BYTE dl + mov rdi, rsi ; Value in RDI + +.find: + mov rsi, rcx ; Map + call map_find + je .true + + ; false + call alloc_cons + mov [rax], BYTE maltype_false + ret +.true: + call alloc_cons + mov [rax], BYTE maltype_true + ret + +.not_map: + mov rsi, core_containsp_not_map + mov edx, core_containsp_not_map.len + jmp .throw +.no_key: + mov rsi, core_containsp_no_key + mov edx, core_containsp_no_key.len +.throw: + call raw_to_string + mov rsi, rax + jmp error_throw + ;; Return arguments as a list ;; core_list: call incref_object mov rax, rsi ret - + ;; ------------------------------------------------ ;; String functions From 2c686f42eafee4953c2a2f21e7250cf6286dd7ce Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 8 Dec 2017 08:17:35 +0000 Subject: [PATCH 0619/2308] Fix bug in apply_fn releasing memory The list was released too early, before the function body had its reference count incremented. If the function was a lambda function then this released the function, leading to odd behaviour e.g. ((fn* [x] (+ 1 x)) 2) --- nasm/step8_macros.asm | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/nasm/step8_macros.asm b/nasm/step8_macros.asm index b0d1714bbb..60973781f6 100644 --- a/nasm/step8_macros.asm +++ b/nasm/step8_macros.asm @@ -1602,8 +1602,7 @@ eval: call eval_ast ; List of evaluated forms in RAX pop r15 pop rsi - - + .list_exec: ; This point can be called to run a function ; used by swap! @@ -1655,11 +1654,10 @@ eval: ; Result in rax pop r15 pop rsi ; eval'ed list - + push rax call release_cons - pop rax - + pop rax jmp .return ; Releases Env .list_not_function: @@ -1739,6 +1737,15 @@ apply_fn: mov rdi, rax ; New environment in RDI + ; Note: Need to increment the reference count + ; of the function body before releasing anything, + ; since if the function was defined in-place (lambda) + ; then the body may be released early + + pop rsi ; Body + call incref_object ; Will be released by eval + mov r8, rsi ; Body in R8 + ; Release the list passed in RDX mov rsi, rdx call release_cons @@ -1750,9 +1757,8 @@ apply_fn: ; Release the old AST mov rsi, r14 call release_object - - pop rsi ; Body - call incref_object ; Will be released by eval + + mov rsi, r8 ; Body jmp eval ; Tail call ; The new environment (in RDI) will be released by eval From a4ca0426a23e62b8e655424994498ff853acc906 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 8 Dec 2017 08:19:57 +0000 Subject: [PATCH 0620/2308] Adding map and throw functions Also some more useful error messages for the arithmetic operators, rather than returning nil. --- nasm/core.asm | 299 ++++++++++++++++++++++++++++++++++++++++++------ nasm/macros.mac | 6 + 2 files changed, 273 insertions(+), 32 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 13e28632cb..79a3bc4519 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -61,8 +61,15 @@ section .data static core_containsp_symbol, db "contains?" static core_vectorp_symbol, db "vector?" static core_mapp_symbol, db "map?" + + static core_throw_symbol, db "throw" + + static core_map_symbol, db "map" ;; Strings + static core_arith_missing_args, db "integer arithmetic missing arguments" + static core_arith_not_int, db "non-integer argument to integer arithmetic" + static core_emptyp_error_string, db "empty? expects a list, vector or map",10 static core_count_error_string, db "count expects a list or vector",10 static core_numeric_expect_ints, db "comparison operator expected two numbers",10 @@ -91,6 +98,11 @@ section .data static core_containsp_not_map, db "Error: contains? expects map as first argument" static core_containsp_no_key, db "Error: contains? missing key argument" + + static core_map_missing_args, db "Error: map expects two arguments (function, list/vector)" + static core_map_not_function, db "Error: map expects a ufunction for first argument" + static core_map_not_seq, db "Error: map expects a list or vector as second argument" + section .text ;; Add a native function to the core environment @@ -110,6 +122,7 @@ section .text pop rsi ; environment call env_set %endmacro + ;; Create an Environment with core functions ;; @@ -176,6 +189,10 @@ core_environment: core_env_native core_vectorp_symbol, core_vectorp core_env_native core_mapp_symbol, core_mapp + + core_env_native core_throw_symbol, core_throw + + core_env_native core_map_symbol, core_map ; ----------------- ; Put the environment in RAX @@ -184,6 +201,17 @@ core_environment: ;; ---------------------------------------------------- +;; Jumped to from many core functions, with +;; string address in RSI and length in EDX +core_throw_str: + call raw_to_string + mov rsi, rax + jmp error_throw + +;; ---------------------------------------------------- + + + ;; Integer arithmetic operations ;; ;; Adds a list of numbers, address in RSI @@ -208,11 +236,15 @@ core_arithmetic: mov ch, cl and ch, block_mask cmp ch, block_cons - jne .error + jne .missing_args + mov ch, cl and ch, content_mask + cmp ch, content_empty + je .missing_args + cmp ch, content_int - jne .error + jne .not_int ; Put the starting value in rax mov rax, [rsi + Cons.car] @@ -220,18 +252,16 @@ core_arithmetic: .add_loop: ; Fetch the next value mov cl, [rsi + Cons.typecdr] - cmp cl, content_nil - je .finished ; Nothing let cmp cl, content_pointer - jne .error - + jne .finished ; Nothing let + mov rsi, [rsi + Cons.cdr] ; Get next cons ; Check that it is an integer mov cl, BYTE [rsi] and cl, content_mask cmp cl, content_int - jne .error + jne .not_int ; Jump to the required operation, address in RBX jmp rbx @@ -260,12 +290,13 @@ core_arithmetic: mov [rax], BYTE maltype_integer mov [rax + Cons.car], rbx ret -.error: - ; Return nil - call alloc_cons - mov [rax], BYTE maltype_nil - mov [rax + Cons.typecdr], BYTE content_nil - ret + +.missing_args: + load_static core_arith_missing_args + jmp core_throw_str +.not_int: + load_static core_arith_not_int + jmp core_throw_str ;; compare objects for equality core_equalp: @@ -571,16 +602,11 @@ core_containsp: ret .not_map: - mov rsi, core_containsp_not_map - mov edx, core_containsp_not_map.len - jmp .throw + load_static core_containsp_not_map + jmp core_throw_str .no_key: - mov rsi, core_containsp_no_key - mov edx, core_containsp_no_key.len -.throw: - call raw_to_string - mov rsi, rax - jmp error_throw + load_static core_containsp_no_key + jmp core_throw_str ;; Return arguments as a list ;; @@ -1216,18 +1242,12 @@ core_cons: ret .missing_args: - mov rsi, core_cons_missing_arg - mov edx,core_cons_missing_arg.len - jmp .throw + load_static core_cons_missing_arg + jmp core_throw_str .not_vector: - mov rsi, core_cons_not_vector - mov edx, core_cons_not_vector.len - -.throw: - call raw_to_string - mov rsi, rax - jmp error_throw + load_static core_cons_not_vector + jmp core_throw_str ;; Concatenate lists, returning a new list @@ -1723,4 +1743,219 @@ core_value_type_p: mov rsi, rax jmp error_throw +;; Throws an exception +core_throw: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_empty + je .throw_nil ; No arguments + + cmp al, content_pointer + je .throw_pointer + + ; A value. Remove list content type + mov [rsi], BYTE al + jmp error_throw + +.throw_pointer: + mov rsi, [rsi + Cons.car] + jmp error_throw + +.throw_nil: + call alloc_cons + mov [rax], BYTE maltype_nil + mov rsi, rax + jmp error_throw + +;; Applies a function to a list or vector +;; +;; Uses registers +;; R8 - function +;; R9 - Input list/vector +;; R10 - Current end of return list (for appending) +core_map: + xor r10,r10 ; Zero, signal no list + + ; First argument should be a function + mov bl, BYTE [rsi] + and bl, content_mask + cmp bl, content_empty + je .missing_args + + ; Check the first argument is a pointer + cmp bl, content_pointer + jne .not_function + + mov r8, [rsi + Cons.car] ; Function in R8 + mov bl, BYTE [r8] + cmp bl, maltype_function + jne .not_function + + ; Check for second argument + mov bl, BYTE [rsi + Cons.typecdr] + cmp bl, content_pointer + jne .missing_args + + mov rsi, [rsi + Cons.cdr] + + ; Should be a pointer to a list or vector + mov bl, BYTE [rsi] + and bl, content_mask + cmp bl, content_pointer + jne .not_seq + + mov r9, [rsi + Cons.car] ; List or vector in R9 + + mov bl, BYTE [r9] + + mov bh, bl + and bh, content_mask + cmp bh, content_empty + je .empty_list + + and bl, (block_mask + container_mask) + cmp bl, container_list + je .start + cmp bl, container_vector + je .start + + ; not list or vector + jmp .not_seq + +.start: + ; Got function in R8, list or vector in R9 + + mov cl, BYTE [r9] + and cl, content_mask + + call alloc_cons + mov [rax], BYTE cl ; set content type + mov rbx, [r9 + Cons.car] + mov [rax + Cons.car], rbx ; Copy content + mov rsi, rax + + cmp cl, content_pointer + jne .run + + ; A pointer, so increment ref count + + mov rcx, rsi + mov rsi, rbx + call incref_object + mov rsi, rcx + +.run: + ; Here have function in R8, args in RSI + ; Check whether the function is built-in or user + mov rax, [r8 + Cons.car] + cmp rax, apply_fn + je .user_function + + ; A built-in function + push r8 ; function + push r9 ; input list/vector + push r10 ; End of return list + push rsi + + call rax + ; Result in RAX + + pop rsi + pop r10 + pop r9 + pop r8 + + push rax + call release_object ; Release arguments + pop rax + + jmp .got_return + +.user_function: + ; a user-defined function, so need to evaluate + ; RSI - Args + + mov rdi, r8 ; Function in RDI + mov rdx, rsi ; Release args after binding + + mov rsi, r15 ; Environment + call incref_object ; Released by eval + call incref_object ; also released from R13 + mov r13, r15 + + mov rsi, rdx + + push r8 + push r9 + push r10 + call apply_fn ; Result in RAX + pop r10 + pop r9 + pop r8 + +.got_return: + ; Have a return result in RAX + ; Check if it's a value type + mov bl, BYTE [rax] + mov bh, bl + and bl, (block_mask + container_mask) + jz .return_value + + ; A more complicated type, point to it + mov rcx, rax + call alloc_cons ; Create a Cons for address + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rcx + jmp .update_return + +.return_value: + mov bl, bh + and bl, content_mask + or bl, container_list + mov [rax], BYTE bl ; mark as a list + +.update_return: + ; Now append to result list + test r10,r10 + jnz .append + + ; First value + mov r10, rax ; End of list + push r10 ; popped before return + jmp .next +.append: + mov [r10 + Cons.cdr], rax ; Point to new Cons + mov [r10 + Cons.typecdr], BYTE content_pointer + mov r10, rax +.next: + ; Check if there is another value + mov al, [r9 + Cons.typecdr] + cmp al, content_pointer + jne .done ; no more + + mov r9, [r9 + Cons.cdr] ; next + jmp .start + +.done: + pop rax ; Pushed in .update_return + ret + +.empty_list: + ; Got an empty list, so return an empty list + call alloc_cons + mov [rax], BYTE maltype_empty_list + ret + +.missing_args: + ; Either zero or one args, expect two + load_static core_map_missing_args + jmp core_throw_str +.not_function: + ; First argument not a function + load_static core_map_not_function + jmp core_throw_str +.not_seq: + ; Second argument not list or vector + load_static core_map_not_seq + jmp core_throw_str diff --git a/nasm/macros.mac b/nasm/macros.mac index f3db819bdf..8adac59e30 100644 --- a/nasm/macros.mac +++ b/nasm/macros.mac @@ -12,6 +12,12 @@ %1.len: equ $ - %1 %endmacro +;; Puts address of data in RSI, length in EDX +%macro load_static 1 + mov rsi, %1 + mov edx, %1.len +%endmacro + ;; Define a symbol which can be compared against ;; ;; static_symbol name, string From 4a65988988ca046097db18db9cd71ca79b8c5bc6 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 8 Dec 2017 20:49:02 +0000 Subject: [PATCH 0621/2308] Add apply function to core Tested both built-in and user functions, seems to be working --- nasm/core.asm | 164 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 1 deletion(-) diff --git a/nasm/core.asm b/nasm/core.asm index 79a3bc4519..69b40c60a6 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -65,6 +65,8 @@ section .data static core_throw_symbol, db "throw" static core_map_symbol, db "map" + static core_apply_symbol, db "apply" + ;; Strings static core_arith_missing_args, db "integer arithmetic missing arguments" @@ -102,7 +104,10 @@ section .data static core_map_missing_args, db "Error: map expects two arguments (function, list/vector)" static core_map_not_function, db "Error: map expects a ufunction for first argument" static core_map_not_seq, db "Error: map expects a list or vector as second argument" - + + static core_apply_not_function, db "Error: apply expects function as first argument" + static core_apply_missing_args, db "Error: apply missing arguments" + static core_apply_not_seq, db "Error: apply last argument must be list or vector" section .text ;; Add a native function to the core environment @@ -193,6 +198,7 @@ core_environment: core_env_native core_throw_symbol, core_throw core_env_native core_map_symbol, core_map + core_env_native core_apply_symbol, core_apply ; ----------------- ; Put the environment in RAX @@ -1959,3 +1965,159 @@ core_map: load_static core_map_not_seq jmp core_throw_str + +;; Applies a function to a list of arguments, concatenated with +;; a final list of args +;; (function, ..., []) +core_apply: + ; First argument should be a function + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .not_function + + mov r8, [rsi + Cons.car] ; function in R8 + mov al, BYTE [r8] + cmp al, maltype_function + jne .not_function + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .missing_args + + xor r9,r9 + ; Optional args, followed by final list/vector +.loop: + mov rsi, [rsi + Cons.cdr] + + ; Check if this is the last + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .last + + ; Not the last, so copy + call alloc_cons ; New Cons in RAX + mov bl, BYTE [rsi] + mov [rax], BYTE bl + mov rcx, [rsi + Cons.car] + mov [rax + Cons.car], rcx + + and bl, content_mask + cmp bl, content_pointer + jne .got_value + + ; A pointer, so increment reference + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.got_value: + ; Now append this Cons to the list + test r9,r9 + jnz .append + + ; First + mov r9, rax ; Start of the list + mov r10, rax ; End of the list + jmp .loop + +.append: + mov [r10 + Cons.typecdr], BYTE content_pointer + mov [r10 + Cons.cdr], rax + mov r10, rax + jmp .loop + +.last: + ; Check that it's a list or vector + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .not_seq + + mov rsi, [rsi + Cons.car] ; Vector/list in RSI + mov al, BYTE [rsi] + and al, container_mask + cmp al, container_list + je .last_seq + cmp al, container_vector + jne .not_seq + +.last_seq: + ; Check if there were any previous args + test r9, r9 + jnz .last_append + + ; R9 is zero, so no previous args + mov r9, rsi + jmp .run + +.last_append: + ; Append RSI to the end of the list [R9]...[R10] + mov [r10 + Cons.typecdr], BYTE content_pointer + mov [r10 + Cons.cdr], rsi + +.run: + ; Have arguments list in R9 + mov rsi, r9 + ; Here have function in R8, args in RSI + ; Check whether the function is built-in or user + mov rax, [r8 + Cons.car] + cmp rax, apply_fn + je .user_function + + ; A built-in function + push r8 ; function + push r9 ; input list/vector + push r10 ; End of return list + push rsi + + call rax + ; Result in RAX + + pop rsi + pop r10 + pop r9 + pop r8 + + push rax + call release_object ; Release arguments + pop rax + + ret + +.user_function: + ; a user-defined function, so need to evaluate + ; RSI - Args + + mov rdi, r8 ; Function in RDI + mov rdx, rsi ; Release args after binding + + mov rsi, r15 ; Environment + call incref_object ; Released by eval + call incref_object ; also released from R13 + mov r13, r15 + + mov rsi, rdx + + push r8 + push r9 + push r10 + call apply_fn ; Result in RAX + pop r10 + pop r9 + pop r8 + + ret + +.not_function: + load_static core_apply_not_function + jmp core_throw_str + +.missing_args: + load_static core_apply_missing_args + jmp core_throw_str + +.not_seq: + load_static core_apply_not_seq + jmp core_throw_str + From b584079d06a53fdfc86f65243ec0281c03789a1d Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 8 Dec 2017 21:26:30 +0000 Subject: [PATCH 0622/2308] Fix swap! function Changes to how eval works mean the previous approach no longer works. Now uses the same method as apply and map functions, calling apply_fn for user-defined functions. --- nasm/core.asm | 129 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 92 insertions(+), 37 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 69b40c60a6..f24d656a15 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -80,6 +80,9 @@ section .data static core_reset_not_atom, db "Error: argument to reset is not an atom" static core_reset_no_value, db "Error: missing value argument to reset" + static core_swap_not_atom, db "Error: swap! expects atom as first argument" + static core_swap_no_function, db "Error: swap! expects function as second argument" + static core_cons_missing_arg, db "Error: missing argument to cons" static core_cons_not_vector, db "Error: cons expects a list or vector" @@ -1074,8 +1077,8 @@ core_swap: jne .not_atom ; Get the atom - mov r8, [rsi + Cons.car] ; Atom in R8 - mov bl, BYTE [r8] + mov r9, [rsi + Cons.car] ; Atom in R9 + mov bl, BYTE [r9] cmp bl, maltype_atom jne .not_atom @@ -1084,22 +1087,42 @@ core_swap: cmp bl, content_pointer jne .no_function - mov r9, [rsi + Cons.cdr] ; List with function first + mov rsi, [rsi + Cons.cdr] ; List with function first + + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .no_function + + mov r8, [rsi + Cons.car] ; Function in R8 + mov al, BYTE [r8] + cmp al, maltype_function + jne .no_function - ; Get a new Cons to insert into the list + ; Get a new Cons ; containing the value in the atom call alloc_cons ; In RAX - ; Splice into the list - mov bl, BYTE [r9 + Cons.typecdr] - mov rcx, [r9 + Cons.cdr] + ; Prepend to the list + mov bl, BYTE [rsi + Cons.typecdr] mov [rax + Cons.typecdr], bl + cmp bl, content_pointer + jne .done_prepend + + ; A pointer to more args, + + mov rcx, [rsi + Cons.cdr] mov [rax + Cons.cdr], rcx - mov [r9 + Cons.typecdr], BYTE content_pointer - mov [r9 + Cons.cdr], rax + + ; increment reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.done_prepend: ; Now get the value in the atom - mov rdx, [r8 + Cons.car] ; The object pointed to + mov rdx, [r9 + Cons.car] ; The object pointed to ; Check what it is mov bl, BYTE [rdx] @@ -1113,10 +1136,11 @@ core_swap: ; Since the list will be released after eval ; we need to increment the reference count - mov rsi, rdx - call incref_object + mov bx, WORD [rdx + Cons.refcount] + inc bx + mov [rdx + Cons.refcount], WORD bx - jmp .list_done + jmp .run .atom_value: ; Copy the value @@ -1126,44 +1150,75 @@ core_swap: or bl, container_list ; mark as part of a list mov [rax], BYTE bl -.list_done: - ; Now have a list with function followed by args - ; This is the same state as after a call to eval_ast - ; - ; Note: Because eval releases the environment in R15 - ; on return, this needs to have its references - ; incremented before the call - ; - ; The list passed in RAX will be released by eval - - mov rsi, r15 - call incref_object +.run: + mov rsi, rax + + ; Here have function in R8, args in RSI + ; Check whether the function is built-in or user + mov rax, [r8 + Cons.car] + cmp rax, apply_fn + je .user_function + + ; A built-in function + push r9 ; atom + push rsi ; Args + + call rax + ; Result in RAX - mov rax, r9 - push r8 ; The atom - call eval.list_exec ; Result in RAX - pop r8 + pop rsi + pop r9 + + push rax + call release_object ; Release arguments + pop rax + + jmp .got_return + +.user_function: + ; a user-defined function, so need to evaluate + ; RSI - Args + + mov rdi, r8 ; Function in RDI + mov rdx, rsi ; Release args after binding + + mov rsi, r15 ; Environment + call incref_object ; Released by eval + call incref_object ; also released from R13 + mov r13, r15 + + mov rsi, rdx + + push r9 + call apply_fn ; Result in RAX + pop r9 + +.got_return: + ; Have a return result in RAX ; release the current value of the atom push rax ; The result - mov rsi, [r8 + Cons.car] + mov rsi, [r9 + Cons.car] call release_object - pop rsi + pop rax ; Put into atom - mov [r8 + Cons.car], rsi + mov [r9 + Cons.car], rax ; Increase reference of new object ; because when it is returned it will be released - push rsi - call incref_object - pop rax + mov bx, WORD [rax + Cons.refcount] + inc bx + mov [rax + Cons.refcount], WORD bx + ret .not_atom: + load_static core_swap_not_atom + jmp core_throw_str .no_function: - xor rsi,rsi - jmp error_throw + load_static core_swap_no_function + jmp core_throw_str ;; Takes two arguments, and prepends the first argument onto the second From 7511108d0eb4efa3794a9ed55ce049ef009a8cbc Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 9 Dec 2017 11:05:53 +0000 Subject: [PATCH 0623/2308] Add get, sequential?, symbol, vector and hash-map functions hash-map currently doesn't check that the number of arguments is even, but just marks the list as a map. --- nasm/core.asm | 210 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 207 insertions(+), 3 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index f24d656a15..e7ef2acc2d 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -59,13 +59,19 @@ section .data static core_macrop_symbol, db "macro?" static core_containsp_symbol, db "contains?" + static core_get_symbol, db "get" static core_vectorp_symbol, db "vector?" static core_mapp_symbol, db "map?" - + static core_sequentialp_symbol, db "sequential?" + static core_throw_symbol, db "throw" static core_map_symbol, db "map" static core_apply_symbol, db "apply" + + static core_symbol_symbol, db "symbol" + static core_vector_symbol, db "vector" + static core_hashmap_symbol, db "hash-map" ;; Strings @@ -104,6 +110,9 @@ section .data static core_containsp_not_map, db "Error: contains? expects map as first argument" static core_containsp_no_key, db "Error: contains? missing key argument" + static core_get_not_map, db "Error: get expects map as first argument" + static core_get_no_key, db "Error: get missing key argument" + static core_map_missing_args, db "Error: map expects two arguments (function, list/vector)" static core_map_not_function, db "Error: map expects a ufunction for first argument" static core_map_not_seq, db "Error: map expects a list or vector as second argument" @@ -111,6 +120,8 @@ section .data static core_apply_not_function, db "Error: apply expects function as first argument" static core_apply_missing_args, db "Error: apply missing arguments" static core_apply_not_seq, db "Error: apply last argument must be list or vector" + + static core_symbol_not_string, db "Error: symbol expects a string argument" section .text ;; Add a native function to the core environment @@ -194,14 +205,20 @@ core_environment: core_env_native core_macrop_symbol, core_macrop core_env_native core_containsp_symbol, core_containsp - + core_env_native core_get_symbol, core_get + core_env_native core_vectorp_symbol, core_vectorp core_env_native core_mapp_symbol, core_mapp - + core_env_native core_sequentialp_symbol, core_sequentialp + core_env_native core_throw_symbol, core_throw core_env_native core_map_symbol, core_map core_env_native core_apply_symbol, core_apply + + core_env_native core_symbol_symbol, core_symbol + core_env_native core_vector_symbol, core_vector + core_env_native core_hashmap_symbol, core_hashmap ; ----------------- ; Put the environment in RAX @@ -463,6 +480,33 @@ core_container_p: mov [rax], BYTE maltype_false ret +;; Return true if vector or list +core_sequentialp: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .false ; Should be a pointer + + mov rax, [rsi + Cons.car] + mov al, BYTE [rax] + and al, (block_mask + container_mask) + cmp al, container_list + je .true + cmp al, container_vector + jne .false +.true: + ; Is a list or vector, return true + call alloc_cons + mov [rax], BYTE maltype_true + ret + +.false: + call alloc_cons + mov [rax], BYTE maltype_false + ret + + + ;; Test if the given list, vector or map is empty core_emptyp: mov al, BYTE [rsi] @@ -616,6 +660,62 @@ core_containsp: .no_key: load_static core_containsp_no_key jmp core_throw_str + + +;; Given a map and a key, return the value in the map +;; or nil if not found +;; +core_get: + ; Check the type of the first argument + mov bl, BYTE [rsi] + and bl, content_mask + cmp bl, content_pointer + jne .not_map + + mov rcx, [rsi + Cons.car] ; Map in RCX + mov bl, BYTE [rcx] + and bl, (block_mask + container_mask) + cmp bl, container_map + jne .not_map + + ; Check second argument + mov bl, BYTE [rsi + Cons.typecdr] + cmp bl, content_pointer + jne .no_key + mov rsi, [rsi + Cons.cdr] + mov dl, BYTE [rsi] + and dl, content_mask + cmp dl, content_pointer + jne .key_value + + ; Pointer, so put into RDI + mov rdi, [rsi + Cons.car] + jmp .find + +.key_value: + ; A value + mov [rsi], BYTE dl + mov rdi, rsi ; Value in RDI + +.find: + mov rsi, rcx ; Map + call map_get ; Value in RAX + je .found + + ; Not found + call alloc_cons + mov [rax], BYTE maltype_nil + ret +.found: + ret + +.not_map: + load_static core_get_not_map + jmp core_throw_str +.no_key: + load_static core_get_no_key + jmp core_throw_str + ;; Return arguments as a list ;; @@ -623,6 +723,89 @@ core_list: call incref_object mov rax, rsi ret + +;; Convert arguments into a vector +core_vector: + ; Copy first element and mark as vector + call alloc_cons ; in RAX + mov bl, BYTE [rsi] + and bl, content_mask + mov bh, bl ; store content for comparison + or bl, container_vector + mov [rax], BYTE bl ; Set type + + mov rcx, [rsi + Cons.car] + mov [rax + Cons.car], rcx ; Set content + + ; Check if the first element is a pointer + cmp bh, content_pointer + jne .done_car + + ; A pointer, so increment reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.done_car: + ; Copy the CDR type and content + mov bl, [rsi + Cons.typecdr] + mov [rax + Cons.typecdr], bl + + mov rdx, [rsi + Cons.cdr] + mov [rax + Cons.cdr], rdx + + cmp bl, content_pointer + jne .done + + ; A pointer + mov bx, WORD [rdx + Cons.refcount] + inc bx + mov [rdx + Cons.refcount], WORD bx + +.done: + ret + + +;; Convert arguments into a map +core_hashmap: + ; Copy first element and mark as map + call alloc_cons ; in RAX + mov bl, BYTE [rsi] + and bl, content_mask + mov bh, bl ; store content for comparison + or bl, container_map + mov [rax], BYTE bl ; Set type + + mov rcx, [rsi + Cons.car] + mov [rax + Cons.car], rcx ; Set content + + ; Check if the first element is a pointer + cmp bh, content_pointer + jne .done_car + + ; A pointer, so increment reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.done_car: + ; Copy the CDR type and content + mov bl, [rsi + Cons.typecdr] + mov [rax + Cons.typecdr], bl + + mov rdx, [rsi + Cons.cdr] + mov [rax + Cons.cdr], rdx + + cmp bl, content_pointer + jne .done + + ; A pointer + mov bx, WORD [rdx + Cons.refcount] + inc bx + mov [rdx + Cons.refcount], WORD bx + +.done: + ret ;; ------------------------------------------------ ;; String functions @@ -2175,4 +2358,25 @@ core_apply: .not_seq: load_static core_apply_not_seq jmp core_throw_str + +;; Converts a string to a symbol +core_symbol: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .not_string + + mov rsi, [rsi + Cons.car] + mov al, BYTE [rsi] + cmp al, maltype_string + jne .not_string + + ; Copy the string + call string_copy ; result in RAX + + mov [rax], BYTE maltype_symbol + ret +.not_string: + load_static core_symbol_not_string + jmp core_throw_str From ff31757288ce0ea70b9d13092d25ad6d5493f76b Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 9 Dec 2017 11:52:50 +0000 Subject: [PATCH 0624/2308] Add keyword and keyword? functions --- nasm/core.asm | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/nasm/core.asm b/nasm/core.asm index e7ef2acc2d..3471a57d5d 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -57,6 +57,7 @@ section .data static core_stringp_symbol, db "string?" static core_fnp_symbol, db "fn?" static core_macrop_symbol, db "macro?" + static core_keywordp_symbol, db "keyword?" static core_containsp_symbol, db "contains?" static core_get_symbol, db "get" @@ -72,6 +73,7 @@ section .data static core_symbol_symbol, db "symbol" static core_vector_symbol, db "vector" static core_hashmap_symbol, db "hash-map" + static core_keyword_symbol, db "keyword" ;; Strings @@ -122,6 +124,8 @@ section .data static core_apply_not_seq, db "Error: apply last argument must be list or vector" static core_symbol_not_string, db "Error: symbol expects a string argument" + + static core_keyword_not_string, db "Error: keyword expects a string argument" section .text ;; Add a native function to the core environment @@ -203,7 +207,8 @@ core_environment: core_env_native core_stringp_symbol, core_stringp core_env_native core_fnp_symbol, core_fnp core_env_native core_macrop_symbol, core_macrop - + core_env_native core_keywordp_symbol, core_keywordp + core_env_native core_containsp_symbol, core_containsp core_env_native core_get_symbol, core_get @@ -219,6 +224,7 @@ core_environment: core_env_native core_symbol_symbol, core_symbol core_env_native core_vector_symbol, core_vector core_env_native core_hashmap_symbol, core_hashmap + core_env_native core_keyword_symbol, core_keyword ; ----------------- ; Put the environment in RAX @@ -668,7 +674,12 @@ core_containsp: core_get: ; Check the type of the first argument mov bl, BYTE [rsi] + and bl, content_mask + + cmp bl, content_nil + jmp .not_found + cmp bl, content_pointer jne .not_map @@ -702,6 +713,7 @@ core_get: call map_get ; Value in RAX je .found +.not_found: ; Not found call alloc_cons mov [rax], BYTE maltype_nil @@ -1145,6 +1157,34 @@ core_pointer_type_p: mov [rax], BYTE maltype_false ret +;; Tests if argument is a keyword +core_keywordp: + mov bl, BYTE [rsi] + mov bh, bl + and bh, content_mask + cmp bh, content_pointer + jne .false + + mov rsi, [rsi + Cons.car] + mov bl, BYTE [rsi] + cmp bl, maltype_symbol + jne .false + + ; Check if first character is ':' + mov bl, BYTE [rsi + Array.data] + cmp bl, ':' + jne .false + + ; Return true + call alloc_cons + mov [rax], BYTE maltype_true + ret + +.false: + call alloc_cons + mov [rax], BYTE maltype_false + ret + ;; Change the value of an atom core_reset: ; Check the type of the first argument @@ -2380,3 +2420,33 @@ core_symbol: .not_string: load_static core_symbol_not_string jmp core_throw_str + +;; Converts a string to a keyword +core_keyword: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .not_string + + mov r8, [rsi + Cons.car] ; String in R8 + mov al, BYTE [r8] + cmp al, maltype_string + jne .not_string + + call string_new ; String in RAX + mov rsi, rax + mov cl, ':' + call string_append_char ; Puts ':' first + + mov rdx, r8 + call string_append_string ; append + + ; Mark as keyword + mov [rsi], BYTE maltype_symbol + + mov rax, rsi + ret + +.not_string: + load_static core_keyword_not_string + jmp core_throw_str From a4e26fd0e64b80097061e478e6db19732be2b915 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 9 Dec 2017 16:14:12 +0000 Subject: [PATCH 0625/2308] Add try*/catch* 20 failing tests for step 9. Mainly assoc not yet implemented --- nasm/Makefile | 29 +- nasm/step9_try.asm | 2672 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2673 insertions(+), 28 deletions(-) create mode 100644 nasm/step9_try.asm diff --git a/nasm/Makefile b/nasm/Makefile index b04192b7f6..d220cb58bc 100644 --- a/nasm/Makefile +++ b/nasm/Makefile @@ -1,34 +1,7 @@ -step0_repl: step0_repl.asm - nasm -felf64 step0_repl.asm - ld -o $@ step0_repl.o COMPONENTS=core.asm reader.asm printer.asm types.asm system.asm -step1_read_print: step1_read_print.asm $(COMPONENTS) - nasm -felf64 step1_read_print.asm - ld -o $@ step1_read_print.o - -step2_eval: step2_eval.asm $(COMPONENTS) - nasm -felf64 step2_eval.asm - ld -o $@ step2_eval.o - -step3_env: step3_env.asm $(COMPONENTS) - nasm -felf64 step3_env.asm - ld -o $@ step3_env.o - -step4_if_fn_do: step4_if_fn_do.asm $(COMPONENTS) - nasm -felf64 step4_if_fn_do.asm - ld -o $@ step4_if_fn_do.o - -step5_tco: step5_tco.asm $(COMPONENTS) - nasm -felf64 step5_tco.asm - ld -o $@ step5_tco.o - -step6_file: step6_file.asm $(COMPONENTS) - nasm -felf64 step6_file.asm - ld -o $@ step6_file.o - -%.o: %.asm +%.o: %.asm $(COMPONENTS) nasm -felf64 $< %: %.o diff --git a/nasm/step9_try.asm b/nasm/step9_try.asm new file mode 100644 index 0000000000..d251a81c87 --- /dev/null +++ b/nasm/step9_try.asm @@ -0,0 +1,2672 @@ +;; +;; nasm -felf64 step9_try.asm && ld step9_try.o && ./a.out +;; +;; Calling convention: Address of input is in RSI +;; Address of return value is in RAX +;; + +global _start + +%include "types.asm" ; Data types, memory +%include "env.asm" ; Environment type +%include "system.asm" ; System calls +%include "reader.asm" ; String -> Data structures +%include "core.asm" ; Core functions +%include "printer.asm" ; Data structures -> String + +section .bss + +;; Top-level (REPL) environment +repl_env:resq 1 + +;; Error handler list +error_handler: resq 1 + +section .data + +;; ------------------------------------------ +;; Fixed strings for printing + + static prompt_string, db 10,"user> " ; The string to print at the prompt + + static error_string, db 27,'[31m',"Error",27,'[0m',": " + + static not_found_string, db " not found" + + static def_missing_arg_string, db "missing argument to def!",10 + + static def_expecting_symbol_string, db "expecting symbol as first argument to def!",10 + + static defmacro_expecting_function_string, db "defmacro expects function",10 + + static let_missing_bindings_string, db "let* missing bindings",10 + + static let_bindings_list_string, db "let* expected a list or vector of bindings",10 + + static let_bind_symbol_string, db "let* expected a symbol in bindings list",10 + + static let_bind_value_string, db "let* missing value in bindings list",10 + + static let_missing_body_string, db "let* missing body",10 + static eval_list_not_function, db "list does not begin with a function",10 + + static if_missing_condition_string, db "missing condition in if expression",10 + + static try_missing_catch, db "try* missing catch*" + static catch_missing_symbol, db "catch* missing symbol" + static catch_missing_form, db "catch* missing form" + +;; Symbols used for comparison + + ; Special symbols + static_symbol def_symbol, 'def!' + static_symbol let_symbol, 'let*' + static_symbol do_symbol, 'do' + static_symbol if_symbol, 'if' + static_symbol fn_symbol, 'fn*' + static_symbol defmacro_symbol, 'defmacro!' + static_symbol macroexpand_symbol, 'macroexpand' + static_symbol try_symbol, 'try*' + static_symbol catch_symbol, 'catch*' + + static_symbol argv_symbol, '*ARGV*' + + static_symbol quote_symbol, 'quote' + static_symbol quasiquote_symbol, 'quasiquote' + static_symbol unquote_symbol, 'unquote' + static_symbol splice_unquote_symbol, 'splice-unquote' + static_symbol concat_symbol, 'concat' + static_symbol cons_symbol, 'cons' + ; + +;; Startup string. This is evaluated on startup + static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) (defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw ",34,"odd number of forms to cond",34,")) (cons 'cond (rest (rest xs))))))) (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs)))))))) )" + +;; Command to run, appending the name of the script to run + static run_script_string, db "(load-file ",34 +section .text + +;; ---------------------------------------------- +;; +;; Error handling +;; +;; A handler consists of: +;; - A stack pointer address to reset to +;; - An address to jump to +;; - An optional data structure to pass +;; +;; When jumped to, an error handler will be given: +;; - the object thrown in RSI +;; - the optional data structure in RDI +;; + + +;; Add an error handler to the front of the list +;; +;; Input: RSI - Stack pointer +;; RDI - Address to jump to +;; RCX - Data structure. Set to zero for none. +;; If not zero, reference count incremented +;; +;; Modifies registers: +;; RAX +;; RBX +error_handler_push: + call alloc_cons + ; car will point to a list (stack, addr, data) + ; cdr will point to the previous handler + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov rbx, [error_handler] + cmp rbx, 0 ; Check if previous handler was zero + je .create_handler ; Zero, so leave null + ; Not zero, so create pointer to it + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rbx + + ; note: not incrementing reference count, since + ; we're replacing one reference with another +.create_handler: + mov [error_handler], rax ; new error handler + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.car], rax + ; Store stack pointer + mov [rax], BYTE (block_cons + container_list + content_function) + mov [rax + Cons.car], rsi ; stack pointer + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.typecdr], BYTE content_pointer + mov [rdx + Cons.cdr], rax + ; Store function pointer to jump to + ; Note: This can't use content_pointer or release + ; will try to release this memory address + mov [rax], BYTE (block_cons + container_list + content_function) + mov [rax + Cons.car], rdi + + ; Check if there is an object to pass to handler + cmp rcx, 0 + je .done + + ; Set the final CDR to point to the object + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rcx + + mov rsi, rcx + call incref_object + +.done: + ret + + +;; Removes an error handler from the list +;; +;; Modifies registers: +;; RSI +;; RAX +;; RCX +error_handler_pop: + ; get the address + mov rsi, [error_handler] + cmp rsi, 0 + je .done ; Nothing to remove + + push rsi + mov rsi, [rsi + Cons.cdr] ; next handler + mov [error_handler], rsi + ;call incref_object ; needed because releasing soon + + pop rsi ; handler being removed + mov [rsi + Cons.typecdr], BYTE 0 + call release_cons + +.done: + ret + + +;; Throw an error +;; Object to pass to handler should be in RSI +error_throw: + ; Get the next error handler + mov rax, [error_handler] + cmp rax, 0 + je .no_handler + + ; Got a handler + mov rax, [rax + Cons.car] ; handler + mov rbx, [rax + Cons.car] ; stack pointer + mov rax, [rax + Cons.cdr] + mov rcx, [rax + Cons.car] ; function + mov rdi, [rax + Cons.cdr] ; data structure + + ; Reset stack + mov rsp, rbx + + ; Jump to the handler + jmp rcx + +.no_handler: + ; Print the object in RSI then quit + cmp rsi, 0 + je .done ; nothing to print + mov rdi, 1 ; print_readably + call pr_str + mov rsi, rax + call print_string +.done: + jmp quit_error + +;; ---------------------------------------------- +;; Evaluates a form +;; +;; Inputs: RSI Form to evaluate +;; RDI Environment +;; +eval_ast: + mov r15, rdi ; Save Env in r15 + + ; Check the type + mov al, BYTE [rsi] + + ; Check if this is a list + mov ah, al + and ah, container_mask + cmp ah, container_list + je .list + + cmp ah, container_map + je .map + + cmp ah, container_vector + je .vector + + ; Not a list, map or vector + cmp ah, container_symbol + je .symbol + + ; Not a symbol, list, map or vector + call incref_object ; Increment reference count + + mov rax, rsi + ret + +.symbol: + ; Check if first character of symbol is ':' + mov al, BYTE [rsi + Array.data] + cmp al, ':' + je .keyword + + ; look in environment + push rsi + xchg rsi, rdi + ; symbol is the key in rdi + ; Environment in rsi + call env_get + pop rsi + je .done ; result in RAX + + ; Not found, throw an error + mov r11, rsi ; Symbol in R11 + + call string_new + mov rsi, rax ; New string in RSI + + mov cl, 39 ; quote ' + call string_append_char + + mov rdx, r11 ; symbol + call string_append_string + + mov cl, 39 + call string_append_char + + mov r11, rsi + + mov rsi, not_found_string + mov edx, not_found_string.len + call raw_to_string ; ' not found' + + mov r12, rax + + mov rdx, rax + mov rsi, r11 + call string_append_string + + mov r11, rsi + mov rsi, r12 + call release_array + mov rsi, r11 + + jmp error_throw + + ; ------------------------------ + +.keyword: + ; Just return keywords unaltered + call incref_object + mov rax, rsi + ret + + ; ------------------------------ +.list: + ; Evaluate each element of the list + ; + xor r8, r8 ; The list to return + ; r9 contains head of list + +.list_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .list_pointer + + ; A value in RSI, so copy + + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_list) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .list_append + +.list_pointer: + ; List element is a pointer to something + push rsi + push r8 + push r9 + push r15 ; Env + mov rdi, [rsi + Cons.car] ; Get the address + mov rsi, r15 + + call incref_object ; Environment increment refs + xchg rsi, rdi ; Env in RDI, AST in RSI + + call incref_object ; AST increment refs + + call eval ; Evaluate it, result in rax + pop r15 + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .list_eval_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_list + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .list_append + +.list_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_list) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi + + ; Fall through to .list_append +.list_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .list_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .list_next + +.list_first: + mov r8, rax + mov r9, rax + ; fall through to .list_next + +.list_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .list_done ; finished list + mov rsi, [rsi + Cons.cdr] ; next in list + jmp .list_loop + +.list_done: + mov rax, r8 ; Return the list + ret + + ; --------------------- +.map: + ; Create a new map, evaluating all the values + + ; Check if the map is empty + cmp al, maltype_empty_map + jne .map_not_empty + + ; map empty. Just return it + call incref_object + mov rax, rsi + ret + +.map_not_empty: + + mov r10, rsi ; input in R10 + xor r12, r12 ; New map in r12 + + ; Now loop through each key-value pair + ; NOTE: This method relies on the implementation + ; of map as a list + +.map_loop: + ; Copy the key + call alloc_cons ; New Cons in RAX + + mov bl, [r10 + Cons.typecar] ; Type in BL + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] ; Value in RCX + mov [rax + Cons.car], rcx + + ; Check the type of the key + and bl, content_mask + cmp bl, content_pointer + jne .map_got_key ; a value + + ; a pointer, so increment reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.map_got_key: + cmp r12,0 + jne .append_key + + ; First key + mov r12, rax + mov r13, rax + jmp .map_value + +.append_key: + ; Appending to previous value in r13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + +.map_value: + ; Check that we have a value + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_error_missing_value + mov r10, [r10 + Cons.cdr] + + ; Now got value in r10 + + ; Check the type of the value + mov bl, [r10 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .map_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r10 + Cons.typecar] + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .map_got_value +.map_value_pointer: + ; A pointer, so need to evaluate + push r10 ; Input + push r12 ; start of result + push r13 ; Current head of result + push r15 ; Env + mov rsi, [r10 + Cons.car] ; Get the address + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi + + call incref_object + + call eval ; Evaluate it, result in rax + pop r15 + pop r13 + pop r12 + pop r10 + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + + jne .map_eval_pointer + + ; A value, so just change the type to a map + and bl, content_mask + add bl, (block_cons + container_map) + mov [rax], BYTE bl + jmp .map_got_value + +.map_eval_pointer: + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_map + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + +.map_got_value: + ; Append RAX to list in R13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + + ; Check if there's another key + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_done ; finished map + mov r10, [r10 + Cons.cdr] ; next in map + jmp .map_loop + +.map_done: + mov rax, r12 + ret + +.map_error_missing_value: + mov rax, r12 + ret + + ; ------------------------------ +.vector: + ; Evaluate each element of the vector + ; + xor r8, r8 ; The vector to return + ; r9 contains head of vector + +.vector_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .vector_pointer + + ; A value, so copy + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_vector) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .vector_append + +.vector_pointer: + ; Vector element is a pointer to something + push rsi + push r8 + push r9 + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Get the address + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi + + call incref_object + + call eval ; Evaluate it, result in rax + pop r15 + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .vector_eval_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_vector + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .vector_append + +.vector_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_vector) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi + +.vector_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .vector_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .vector_next + +.vector_first: + mov r8, rax + mov r9, rax + ; fall through to .vector_next + +.vector_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .vector_done ; finished vector + mov rsi, [rsi + Cons.cdr] ; next in vector + jmp .vector_loop + +.vector_done: + mov rax, r8 ; Return the vector + ret + + ; --------------------- +.done: + ret + + + +;; Comparison of symbols for eval function +;; Compares the symbol in RSI with specified symbol +;; Preserves RSI and RBX +;; Modifies RDI +%macro eval_cmp_symbol 1 + push rsi + push rbx + mov rsi, rbx + mov rdi, %1 + call compare_char_array + pop rbx + pop rsi + test rax, rax ; ZF set if rax = 0 (equal) +%endmacro + +;; ---------------------------------------------------- +;; Evaluates a form +;; +;; Input: RSI AST to evaluate [ Released ] +;; RDI Environment [ Released ] +;; +;; Returns: Result in RAX +;; +;; Note: Both the form and environment will have their reference count +;; reduced by one (released). This is for tail call optimisation (Env), +;; quasiquote and macroexpand (AST) +;; +eval: + mov r15, rdi ; Env + + push rsi ; AST pushed, must be popped before return + + ; Check type + mov al, BYTE [rsi] + cmp al, maltype_empty_list + je .return_nil + + and al, container_mask + cmp al, container_list + je .list + + ; Not a list. Evaluate and return + call eval_ast + jmp .return ; Releases Env + + ; -------------------- +.list: + ; A list + + ; Macro expand + pop rax ; Old AST, discard from stack + call macroexpand ; Replaces RSI + push rsi ; New AST + + ; Check if RSI is a list, and if + ; the first element is a symbol + mov al, BYTE [rsi] + + mov ah, al + and ah, container_mask + cmp ah, container_list + je .list_still_list + + ; Not a list, so call eval_ast on it + mov rdi, r15 ; Environment + call eval_ast + jmp .return + +.list_still_list: + and al, content_mask + cmp al, content_pointer + jne .list_eval + + mov rbx, [rsi + Cons.car] + mov al, BYTE [rbx] + cmp al, maltype_symbol + jne .list_eval + + ; Is a symbol, address in RBX + + ; Compare against special form symbols + + eval_cmp_symbol def_symbol ; def! + je .def_symbol + + eval_cmp_symbol let_symbol ; let* + je .let_symbol + + eval_cmp_symbol do_symbol ; do + je .do_symbol + + eval_cmp_symbol if_symbol ; if + je .if_symbol + + eval_cmp_symbol fn_symbol ; fn + je .fn_symbol + + eval_cmp_symbol quote_symbol ; quote + je .quote_symbol + + eval_cmp_symbol quasiquote_symbol ; quasiquote + je .quasiquote_symbol + + eval_cmp_symbol defmacro_symbol ; defmacro! + je .defmacro_symbol + + eval_cmp_symbol macroexpand_symbol ; macroexpand + je .macroexpand_symbol + + eval_cmp_symbol try_symbol ; try* + je .try_symbol + + ; Unrecognised + jmp .list_eval + + + ; ----------------------------- + +.defmacro_symbol: + mov r9, 1 + jmp .def_common +.def_symbol: + xor r9, r9 ; Set R9 to 0 +.def_common: + ; Define a new symbol in current environment + ; If R9 is set to 1 then defmacro + + ; Next item should be a symbol + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Now should have a symbol + + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + jne .def_error_expecting_symbol + mov r8, [rsi + Cons.car] ; Symbol (?) + + mov al, BYTE [r8] + cmp al, maltype_symbol + jne .def_error_expecting_symbol + + ; R8 now contains a symbol + + ; expecting a value or pointer next + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a pointer + mov al, BYTE [rsi] + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .def_pointer + + ; A value, so copy + + ; Test if this is defmacro! + test r9, r9 + jnz .defmacro_not_function + + push rax + call alloc_cons + pop rbx ; BL now contains type + and bl, content_mask + add bl, (block_cons + container_value) + mov [rax], BYTE bl + mov rcx, [rsi + Cons.car] + mov [rax + Cons.car], rcx + mov rsi, rax + + jmp .def_got_value + +.def_pointer: + ; A pointer, so evaluate + + ; This may throw an error, so define a handler + + push r8 ; the symbol + push r15 ; Env + push r9 + mov rsi, [rsi + Cons.car] ; Pointer + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call incref_object ; AST increment refs + + call eval + mov rsi, rax + + pop r9 + + ; If this is defmacro, and the object in RSI is a function, + ; then change to a macro + test r9, r9 + jz .def_not_macro ; Not defmacro + + ; Check RSI + mov al, BYTE [rsi] + cmp al, maltype_function + jne .defmacro_not_function + + ; Got a function, change to macro + mov [rsi], BYTE maltype_macro + +.def_not_macro: + + pop r15 + pop r8 + +.def_got_value: + ; Symbol in R8, value in RSI + mov rdi, r8 ; key (symbol) + mov rcx, rsi ; Value + mov rsi, r15 ; Environment + call env_set + + mov rax, rcx + jmp .return + +.def_error_missing_arg: + mov rsi, def_missing_arg_string + mov rdx, def_missing_arg_string.len + jmp .def_handle_error + +.def_error_expecting_symbol: + mov rsi, def_expecting_symbol_string + mov rdx, def_expecting_symbol_string.len + jmp .def_handle_error + +.defmacro_not_function: + mov rsi, defmacro_expecting_function_string + mov rdx, defmacro_expecting_function_string.len + jmp .def_handle_error + +.def_handle_error: + push rsi + push rdx + print_str_mac error_string ; print 'Error: ' + + pop rdx + pop rsi + call print_rawstring ; print message + + xor rsi, rsi ; no object to throw + jmp error_throw ; No return + + ; ----------------------------- +.let_symbol: + ; Create a new environment + + mov r11, rsi ; Let form in R11 + + mov rsi, r15 ; Outer env + call env_new ; Increments R15's ref count + mov r14, rax ; New environment in R14 + + mov rsi, r15 + call release_object ; Decrement R15 ref count + + ; Second element should be the bindings + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_bindings + mov r11, [r11 + Cons.cdr] + + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .let_error_bindings_list + + mov r12, [r11 + Cons.car] ; should be bindings list + mov al, BYTE [r12] + and al, (block_mask + container_mask) + ; Can be either a list or vector + cmp al, block_cons + container_list + je .let_bind_loop + cmp al, block_cons + container_vector + je .let_bind_loop + + ; Not a list or vector + jmp .let_error_bindings_list + +.let_bind_loop: + ; R12 now contains a list with an even number of items + ; The first should be a symbol, then a value to evaluate + + ; Get the symbol + mov al, BYTE [r12] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_symbol + + mov r13, [r12 + Cons.car] ; Symbol (?) + mov al, BYTE [r13] + cmp al, maltype_symbol + jne .let_error_bind_symbol + + ; R13 now contains a symbol to bind + ; The next item in the bindings list (R12) + ; should be a value or expression to evaluate + + mov al, BYTE [r12 + Cons.typecdr] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_value + mov r12, [r12 + Cons.cdr] + + ; got value in R12 + + ; Check the type of the value + mov bl, [r12 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .let_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r12 + Cons.typecar] + and bl, content_mask + ;or bl, (block_cons + container_value) ; 0 + mov [rax + Cons.typecar], bl + mov rcx, [r12 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .let_got_value + +.let_value_pointer: + ; A pointer, so need to evaluate + push r11 ; let* form list + push r12 ; Position in bindings list + push r13 ; symbol to bind + push r14 ; new environment + + mov rsi, r14 + call incref_object + mov rdi, r14 + + mov rsi, [r12 + Cons.car] ; Get the address + + call incref_object ; Increment ref count of AST + + call eval ; Evaluate it, result in rax + pop r14 + pop r13 + pop r12 + pop r11 + +.let_got_value: + + mov rsi, r14 ; Env + mov rdi, r13 ; key + mov rcx, rax ; value + call env_set + + ; Release the value + mov rsi, rcx ; The value + call release_object + + ; Check if there are more bindings + mov al, BYTE [r12 + Cons.typecdr] + cmp al, content_pointer + jne .let_done_binding + mov r12, [r12 + Cons.cdr] ; Next + jmp .let_bind_loop + +.let_done_binding: + ; Done bindings. + ; Evaluate next item in let* form in new environment + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_body + mov r11, [r11 + Cons.cdr] ; Now contains value to evaluate + ; Check type of the value + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + je .body_pointer + + ; Just a value, so copy + call alloc_cons + mov bl, BYTE [r11] + and bl, content_mask + mov [rax], BYTE bl ; set type + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx ; copy value + jmp .let_done + +.body_pointer: + ; Evaluate using new environment + + mov rsi, [r11 + Cons.car] ; Object pointed to + call incref_object ; will be released by eval + + mov r11, rsi ; save new AST + pop rsi ; Old AST + call release_object + mov rsi, r11 ; New AST + + mov rdi, r14 ; New environment + + jmp eval ; Tail call + ; Note: eval will release the new environment on return + +.let_done: + ; Release the new environment + push rax + mov rsi, r14 + call release_object + pop rax + + ; Release the AST + pop rsi + push rax + call release_object + pop rax + ret ; already released env + +.let_error_missing_bindings: + mov rsi, let_missing_bindings_string + mov rdx, let_missing_bindings_string.len + jmp .let_handle_error + +.let_error_bindings_list: ; expected a list or vector, got something else + mov rsi, let_bindings_list_string + mov rdx, let_bindings_list_string.len + jmp .let_handle_error + +.let_error_bind_symbol: ; expected a symbol, got something else + mov rsi, let_bind_symbol_string + mov rdx, let_bind_symbol_string.len + jmp .let_handle_error + +.let_error_bind_value: ; Missing value in binding list + mov rsi, let_bind_value_string + mov rdx, let_bind_value_string.len + jmp .let_handle_error + +.let_error_missing_body: ; Missing body to evaluate + mov rsi, let_missing_body_string + mov rdx, let_missing_body_string.len + jmp .let_handle_error + +.let_handle_error: + push r11 ; For printing later + + push rsi + push rdx + + print_str_mac error_string ; print 'Error: ' + + pop rdx + pop rsi + call print_rawstring ; print message + + pop rsi ; let* form + jmp error_throw ; No return + + ; ----------------------------- + +.do_symbol: + mov r11, rsi ; do form in RSI + ; Environment in R15 + + ; Check if there is a body + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .do_no_body ; error + + mov r11, [r11 + Cons.cdr] ; Body in R11 + +.do_symbol_loop: + + ; Need to test if this is the last form + ; so we can handle tail call + + mov bl, BYTE [r11 + Cons.typecdr] + cmp bl, content_pointer + jne .do_body_last ; Last expression + + ; not the last expression + + ; Check if this is a value or pointer + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + jne .do_next ; A value, so skip + + ; A pointer, so evaluate + + push r15 + push r11 + + mov rsi, r15 + call incref_object ; Increase Env reference + ; since eval will release Env + + mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Increment ref count since eval will release + + mov rdi, r15 ; Env + call eval ; Result in RAX + + ; Another form after this. + ; Discard the result of the last eval + mov rsi, rax + call release_object + + pop r11 + pop r15 + +.do_next: + mov r11, [r11 + Cons.cdr] ; Next in list + + jmp .do_symbol_loop + +.do_body_last: + ; The last form is in R11, which will be returned + + ; Check if this is a value or pointer + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + jne .do_body_value_return + jmp .do_body_expr_return + +.do_body_value_return: + ; Got a value as last form (in R11). + ; Copy and return + + push rax ; Type of value to return + + ; release Env + mov rsi, r15 + call release_object + + ; Allocate a Cons object to hold value + call alloc_cons + pop rbx ; type in BL + mov [rax], BYTE bl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + + ; release the AST + pop rsi + mov r15, rax ; not modified by release + call release_object + mov rax, r15 + + ret + +.do_body_expr_return: + ; An expression to evaluate as the last form + ; Tail call optimise, jumping to eval + ; Don't increment Env reference count + + mov rsi, [r11 + Cons.car] ; new AST form + call incref_object ; This will be released by eval + + mov r11, rsi ; Save new AST + pop rsi ; Remove old AST from stack + call release_object + mov rsi, r11 + + mov rdi, r15 ; Env + jmp eval ; Tail call + +.do_no_body: + ; No expressions to evaluate. Return nil + + mov rsi, r15 + call release_object ; Release Env + + ; release the AST + pop rsi + call release_object + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret + + ; ----------------------------- + +.if_symbol: + mov r11, rsi ; if form in R11 + ; Environment in R15 + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .if_no_condition + + mov r11, [r11 + Cons.cdr] ; Should be a condition + + ; Check if value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .if_cond_value + + ; A pointer, so evaluate + + push r15 + push r11 + + mov rsi, r15 + call incref_object ; Increase Env reference + + mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Increase Form/AST ref count + + mov rdi, r15 ; Env + call eval ; Result in RAX + pop r11 + pop r15 + + ; Get type of result + mov bl, BYTE [rax] + + ; release value + push rbx + mov rsi, rax + call release_object + pop rbx + + ; Check type + cmp bl, maltype_nil + je .if_false + cmp bl, maltype_false + je .if_false + + jmp .if_true + +.if_cond_value: + + ; A value + cmp al, content_nil + je .if_false + cmp al, content_false + je .if_false + + jmp .if_true + +.if_false: + ; Skip the next item + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .return_nil + + mov r11, [r11 + Cons.cdr] + +.if_true: + ; Get the next item in the list and evaluate it + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; Nothing to return + + mov r11, [r11 + Cons.cdr] + + ; Check if value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + je .if_got_pointer + +.if_got_value: + ; copy value in r11 + call alloc_cons + mov bl, BYTE [r11] + and bl, content_mask + mov [rax], BYTE bl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + + jmp .return + +.if_got_pointer: + mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Will be released by eval + + mov r11, rsi + pop rsi + call release_object ; Release old AST + mov rsi, r11 ; New AST + + mov rdi, r15 ; Env + jmp eval ; Tail call + +.if_no_condition: ; just (if) without a condition + + print_str_mac error_string + print_str_mac if_missing_condition_string + + ; Release environment + mov rsi, r15 + call release_object + xor rsi, rsi ; No object to throw + jmp error_throw + +.return_nil: + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + +.return: + ; Release environment + mov rsi, r15 + mov r15, rax ; Save RAX (return value) + call release_object + + ; Release the AST + pop rsi ; Pushed at start of eval + call release_object + + mov rax, r15 ; return value + ret + + ; ----------------------------- + +.fn_symbol: + mov r11, rsi ; fn form in R11 + ; Environment in R15 + + ; Get the binds and body of the function + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .fn_empty + + mov r11, [r11 + Cons.cdr] + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .fn_binds_not_list + + mov r12, [r11 + Cons.car] ; Should be binds list + mov al, BYTE [r12] + and al, (block_mask + container_mask) + cmp al, (block_cons + container_list) + je .fn_got_binds ; Can be list + cmp al, (block_cons + container_vector) + je .fn_got_binds ; or vector + jmp .fn_binds_not_list + +.fn_got_binds: + + ; Next get the body of the function + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .fn_no_body + + mov r11, [r11 + Cons.cdr] + ; Check value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .fn_is_value ; Body in r11 + mov r11, [r11 + Cons.car] + jmp .fn_got_body + +.fn_is_value: + ; Body is just a value, no expression + mov [r11], BYTE al ; Mark as value, not list + +.fn_got_body: + + ; Now put into function type + ; Addr is "apply_fn", the address to call + ; Env in R15 + ; Binds in R12 + ; Body in R11 + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_function) + mov rbx, apply_fn + mov [rax + Cons.car], rbx ; Address of apply function + mov [rax + Cons.typecdr], BYTE content_pointer + + mov r13, rax ; Return list in R13 + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r15 ; Environment + mov [rax + Cons.typecdr], BYTE content_pointer + + mov [r13 + Cons.cdr], rax ; Append to list + mov r14, rax + + push rax + mov rsi, r15 + call incref_object + pop rax + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r12 ; Binds list + mov [rax + Cons.typecdr], BYTE content_pointer + + mov [r14 + Cons.cdr], rax ; Append to list + mov r14, rax + + push rax + mov rsi, r12 + call incref_object + pop rax + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r11 ; Body of function + + mov [r14 + Cons.cdr], rax + + mov rsi, r11 + call incref_object + + mov rax, r13 + jmp .return + +.fn_empty: +.fn_binds_not_list: +.fn_no_body: + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + jmp .return + + ; ----------------------------- + +.quote_symbol: + ; Just return the arguments in rsi cdr + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; quote empty, so return nil + + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a value or pointer + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + je .quote_pointer + + ; RSI contains a value. Remove the list container + mov [rsi + Cons.typecar], BYTE al + call incref_object + mov rax, rsi + jmp .return + +.quote_pointer: + ; RSI contains a pointer, so get the object pointed to + mov rsi, [rsi + Cons.car] + call incref_object + mov rax, rsi + jmp .return + + ; ----------------------------- + +.quasiquote_symbol: + ; call quasiquote function with first argument + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; quasiquote empty, so return nil + + mov r11, rsi ; Save original AST in R11 + + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a value or pointer + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + je .quasiquote_pointer + + ; RSI contains a value. Remove the list container + mov [rsi + Cons.typecar], BYTE al + call incref_object + mov rax, rsi + jmp .return + +.quasiquote_pointer: + ; RSI contains a pointer, so get the object pointed to + mov rsi, [rsi + Cons.car] + + ; Uncomment these two lines to test quasiquote + ;call quasiquote + ;ret + + push r15 ; Environment + ; Original AST already on stack + + call quasiquote + ; New AST in RAX + pop rdi ; Environment + pop rsi ; Old AST + + mov r11, rax ; New AST + call release_object ; Release old AST + mov rsi, r11 ; New AST in RSI + + jmp eval ; Tail call + + ; ----------------------------- +.macroexpand_symbol: + ; Check if we have a second list element + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; No argument + + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a value or pointer + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + je .macroexpand_pointer + + ; RSI contains a value. Remove the list container + mov [rsi + Cons.typecar], BYTE al + call incref_object + mov rax, rsi + jmp .return + +.macroexpand_pointer: + mov rsi, [rsi + Cons.car] + call incref_object ; Since RSI will be released + + call macroexpand ; May release and replace RSI + + mov rax, rsi + jmp .return ; Releases original AST + + ; ----------------------------- + +.try_symbol: + ; Should have the form + ; + ; (try* A (catch* B C)) + ; + ; where B is a symbol, A and C are forms to evaluate + + ; Check first arg A + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; No argument + + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a value or pointer + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + je .try_pointer + + ; RSI contains a value. Copy and return + mov cl, al + call alloc_cons + mov [rax], BYTE cl ; Set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx + jmp .return + +.try_pointer: + + mov r8, [rsi + Cons.car] ; form A in R8 + + ; Check second arg B + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .try_missing_catch + + mov rsi, [rsi + Cons.cdr] + + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .try_missing_catch + + mov r9, [rsi + Cons.car] ; (catch* B C) in R9 + + mov al, BYTE [r9] + cmp al, (container_list + content_pointer) + jne .try_missing_catch + + mov rsi, [r9 + Cons.car] ; Should be catch* symbol + mov al, BYTE [rsi] + cmp al, maltype_symbol + jne .try_missing_catch + + mov rdi, catch_symbol + call compare_char_array + test rax, rax ; ZF set if rax = 0 (equal) + jnz .try_missing_catch + + ; Check that B is a symbol + mov al, [r9 + Cons.typecdr] + cmp al, content_pointer + jne .catch_missing_symbol + + mov r9, [r9 + Cons.cdr] ; (B C) in R9 + + mov al, BYTE [r9] + and al, content_mask + cmp al, content_pointer + jne .catch_missing_symbol + + mov r10, [r9 + Cons.car] ; B in R10 + mov al, BYTE [r10] + cmp al, maltype_symbol + jne .catch_missing_symbol + + mov al, BYTE [r9 + Cons.typecdr] + cmp al, content_pointer + jne .catch_missing_form + mov r9, [r9 + Cons.cdr] ; C in R9 + + ; Now have extracted from (try* A (catch* B C)) + ; A in R8 + ; B in R10 + ; C in T9 + + push R9 + push R10 + + ; Set the error handler + mov rsi, rsp ; Stack pointer + mov rdi, .catch ; Address to jump to + xor rcx, rcx ; No data + call error_handler_push + + ; Evaluate the form in R8 + mov rsi, r15 + call incref_object ; Env released by eval + mov rdi, r15 ; Env in RDI + + mov rsi, r8 ; The form to evaluate (A) + + call incref_object ; AST released by eval + + call eval + mov r8, rax ; Result in R8 + + ; Discard B and C + ;add rsi, 8 ; pop R10 and R9 + pop r10 + pop r9 + + ; Remove error handler + call error_handler_pop + mov rax, r8 + jmp .return + +.catch: + ; Jumps here on error + ; Value thrown in RSI + ; + + pop r12 ; B (symbol to bind) + pop r13 ; C (form to evaluate) + + ; Check if C is a value or pointer + + mov cl, BYTE [r13] + and cl, content_mask + cmp cl, content_pointer + je .catch_C_pointer + + ; A value, so copy and return + call alloc_cons + mov [rax], BYTE cl ; Set type + mov rbx, [r13 + Cons.car] + mov [rax + Cons.car], rbx ; Set value + + jmp .return + +.catch_C_pointer: + + mov r11, rsi ; Value thrown in R11 + + mov rsi, r15 ; Outer env + call env_new ; Increments R15's ref count + + mov rsi, rax ; New environment in RSI + mov rdi, r12 ; key (symbol) + mov rcx, r11 ; value + call env_set + + mov rdi, rsi ; Env in RDI (will be released) + mov rsi, [r13 + Cons.car] ; Form to evaluate + call incref_object ; will be released + + call eval + + jmp .return + +.try_missing_catch: + load_static try_missing_catch + call raw_to_string + mov rsi, rax + jmp error_throw + +.catch_missing_symbol: + load_static catch_missing_symbol + call raw_to_string + mov rsi, rax + jmp error_throw + +.catch_missing_form: + load_static catch_missing_form + call raw_to_string + mov rsi, rax + jmp error_throw + + ; ----------------------------- + +.list_eval: + push rsi + mov rdi, r15 ; Environment + push r15 + call eval_ast ; List of evaluated forms in RAX + pop r15 + pop rsi + +.list_exec: + ; This point can be called to run a function + ; used by swap! + ; + ; Inputs: RAX - List with function as first element + ; NOTE: This list is released + ; + ; Check that the first element of the return is a function + mov bl, BYTE [rax] + and bl, content_mask + cmp bl, content_pointer + jne .list_not_function + + mov rbx, [rax + Cons.car] ; Get the address + mov cl, BYTE [rbx] + cmp cl, maltype_function + jne .list_not_function + + ; Check the rest of the args + mov cl, BYTE [rax + Cons.typecdr] + cmp cl, content_pointer + je .list_got_args + + ; No arguments + push rbx + call alloc_cons + mov [rax], BYTE maltype_empty_list + pop rbx + mov rsi, rax + jmp .list_function_call +.list_got_args: + mov rsi, [rax + Cons.cdr] ; Rest of list +.list_function_call: + ; Call the function with the rest of the list in RSI + + mov rdx, rax ; List to release + mov rdi, rbx ; Function object in RDI + + mov rbx, [rbx + Cons.car] ; Call function + cmp rbx, apply_fn + je apply_fn_jmp ; Jump to user function apply + + ; A built-in function, so call (no recursion) + push rax + push r15 + + call rbx + + ; Result in rax + pop r15 + pop rsi ; eval'ed list + + push rax + call release_cons + pop rax + jmp .return ; Releases Env + +.list_not_function: + ; Not a function. Probably an error + push rsi + + mov rsi, rax + call release_object + + print_str_mac error_string + print_str_mac eval_list_not_function + pop rsi + jmp error_throw + + +;; Applies a user-defined function +;; +;; Input: RSI - Arguments to bind +;; RDI - Function object +;; RDX - list to release after binding +;; R15 - Env (will be released) +;; R13 - AST released before return +;; +;; +;; Output: Result in RAX +;; +;; This is jumped to from eval, so if it returns +;; then it will return to the caller of eval, not to eval +apply_fn_jmp: + ; This is jumped to from eval with AST on the stack + pop r13 +apply_fn: + push rsi + ; Extract values from the list in RDI + mov rax, [rdi + Cons.cdr] + mov rsi, [rax + Cons.car] ; Env + mov rax, [rax + Cons.cdr] + mov rdi, [rax + Cons.car] ; Binds + mov rax, [rax + Cons.cdr] + mov rax, [rax + Cons.car] ; Body + pop rcx ; Exprs + + ; Check the type of the body + mov bl, BYTE [rax] + and bl, block_mask + container_mask + jnz .bind + ; Just a value (in RAX). No eval needed + + mov r14, rax ; Save return value in R14 + + mov rsi, rax + call incref_object + + ; Release the list passed in RDX + mov rsi, rdx + call release_object + + ; Release the environment + mov rsi, r15 + call release_object + + ; Release the AST + mov rsi, r13 + call release_object + + mov rax, r14 + ret +.bind: + ; Create a new environment, binding arguments + push rax ; Body + + mov r14, r13 ; Old AST. R13 used by env_new_bind + + push rdx + call env_new_bind + pop rdx + + mov rdi, rax ; New environment in RDI + + ; Note: Need to increment the reference count + ; of the function body before releasing anything, + ; since if the function was defined in-place (lambda) + ; then the body may be released early + + pop rsi ; Body + call incref_object ; Will be released by eval + mov r8, rsi ; Body in R8 + + ; Release the list passed in RDX + mov rsi, rdx + call release_cons + + ; Release the environment + mov rsi, r15 + call release_object + + ; Release the old AST + mov rsi, r14 + call release_object + + mov rsi, r8 ; Body + + jmp eval ; Tail call + ; The new environment (in RDI) will be released by eval + + +;; Set ZF if RSI is a non-empty list or vector +;; Modifies RAX, does not modify RSI +is_pair: + mov al, BYTE [rsi] + test al, block_mask + jnz .false ; Not a Cons + cmp al, maltype_empty_list + je .false ; Empty list + cmp al, maltype_empty_vector + je .false ; Empty vector + + ; Something non empty + and al, container_mask + cmp al, container_list + je .true + cmp al, container_vector + je .true + ; Not a list or vector -> false + +.false: + lahf ; flags in AH + and ah, 255-64 ; clear zero flag + sahf + ret +.true: + lahf ; flags in AH + or ah, 64 ; set zero flag + sahf + ret + +;; Called by eval with AST in RSI [ modified ] +;; Returns new AST in RAX +quasiquote: + ; i. Check if AST is an empty list + call is_pair + jne .quote_ast + + ; ii. Check if the first element of RSI is the symbol + ; 'unquote' + + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .not_unquote ; Not a pointer + + mov rdi, [rsi + Cons.car] ; Get the pointer + mov cl, BYTE [rdi] + cmp cl, maltype_symbol + jne .not_unquote + + ; Compare against 'unquote' + mov r8, rsi + mov r9, rax + + mov rsi, unquote_symbol + call compare_char_array + test rax, rax + + mov rax, r9 + mov rsi, r8 + + je .unquote + +.not_unquote: + ; iii. Handle splice-unquote + ; RSI -> ( ( splice-unquote ? ) ? ) + + ; Test if RSI contains a pointer + + cmp al, content_pointer + jne .not_splice + + mov rbx, [rsi + Cons.car] ; Get the object pointer + + ; RBX -> ( splice-unquote ? ) + + xchg rbx, rsi + call is_pair + xchg rbx, rsi + jne .not_splice ; First element not a pair + + ; Check if this list in RBX starts with 'splice-unquote' symbol + mov al, BYTE [rbx] + and al, content_mask + cmp al, content_pointer + jne .not_splice + + + mov rdi, [rbx + Cons.car] ; Get the pointer + mov al, BYTE [rdi] + cmp al, maltype_symbol + jne .not_splice + + mov r8, rsi + mov r9, rbx + + ; Compare against 'splice-unquote' + mov rsi, splice_unquote_symbol + call compare_char_array + test rax, rax + + mov rbx, r9 + mov rsi, r8 + + je .splice_unquote + +.not_splice: + + ; iv. Cons first and rest of AST in RSI + + ; check if pointer or value + mov cl, BYTE [rsi] + and cl, content_mask + cmp cl, content_pointer + je .cons_pointer + + ; a value, so copy + call alloc_cons + or cl, container_list + mov [rax], BYTE cl ; List + Content + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx + mov rcx, rax + jmp .cons_first + +.cons_pointer: + ; Get the pointer and call quasiquote + push rsi + mov rsi, [rsi + Cons.car] + call quasiquote + mov rcx, rax + pop rsi + + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rcx + mov rcx, rax + +.cons_first: + ; Have Cons with first object in RCX + + ; Call quasiquote on the rest of the AST + ; Check if this is the end of the list + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .cons_ast_end + + mov rsi, [rsi + Cons.cdr] ; Rest of the list + + call incref_object ; Will release after quasiquote call + + jmp .cons_quasiquote_ast + +.cons_ast_end: + ; End of the AST, so make an empty list + call alloc_cons + mov [rax], BYTE maltype_empty_list + mov rsi, rax + +.cons_quasiquote_ast: + push rcx + push rsi + call quasiquote + mov rdx, rax ; List in RDX + + pop rsi + call release_object ; Release input + + pop rcx ; Value in RCX + + ; cons RCX and RDX + ; Work from the end of the list to the front + + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rdx ; The rest of AST + + ; Link to the RCX Cons + mov [rcx + Cons.typecdr], BYTE content_pointer + mov [rcx + Cons.cdr], rax + mov rdx, rcx + + call alloc_cons ; Cons for cons symbol + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rdx + mov rdx, rax + + ; Get the cons symbol + mov rsi, cons_symbol + call incref_object + + mov [rdx], BYTE (container_list + content_pointer) + mov [rdx + Cons.car], rsi + + mov rax, rdx + ret + +.quote_ast: + ; Return (quote RSI) + + call incref_object ; RSI reference count + + ; Cons for RSI + call alloc_cons + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], rsi + mov rsi, rax + + ; Cons for quote symbol + call alloc_cons + mov rbx, rax + mov [rbx + Cons.typecdr], BYTE content_pointer + mov [rbx + Cons.cdr], rsi + + ; Get a quote symbol, incrementing references + mov rsi, quote_symbol + call incref_object + + ; Put into the Cons in RBX + mov [rbx + Cons.car], rsi + mov [rbx], BYTE (block_cons + container_list + content_pointer) + mov rax, rbx + ret + ; ----------------------- + +.unquote: + + ; Got unquote symbol. Return second element of RSI + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .empty_list ; No second element + + mov rsi, [rsi + Cons.cdr] + + ; Check if it's a value or pointer + mov cl, BYTE [rsi] + and cl, content_mask + cmp cl, content_pointer + je .unquote_pointer + + ; A value, so need a new Cons + call alloc_cons + mov [rax], BYTE cl ; content + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; Copy content + ret + +.unquote_pointer: + mov rsi, [rsi + Cons.car] + call incref_object + mov rax, rsi + ret + + ; ----------------------- +.splice_unquote: + ; RSI -> ( RBX->( splice-unquote A ) B ) + ; + ; RBX Car points to splice-unquote symbol + + ; Check if there is anything after the symbol + mov al, BYTE [rbx + Cons.typecdr] + cmp al, content_pointer + jne .splice_unquote_empty + + ; Point to the second element of the splice-unquote list + mov rcx, [rbx + Cons.cdr] + + ; Check whether it's a value or pointer + mov al, BYTE [rcx] + and al, content_mask + cmp al, content_pointer + je .splice_unquote_pointer + + ; A value, so change the container to a value + mov [rcx], BYTE al + ; Remove pointer from RBX + mov [rbx + Cons.typecdr], BYTE 0 + jmp .splice_unquote_first ; Got the value in RCX + +.splice_unquote_pointer: + mov rcx, [rcx + Cons.car] ; Get the object pointed to + xchg rcx, rsi + call incref_object + xchg rcx, rsi ; Object in RCX + +.splice_unquote_first: ; Got the first object in RCX + + ; Check if RSI contains anything else + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .splice_unquote_notail + + mov rsi, [rsi + Cons.cdr] + + ; Now have: + ; ( ( splice-unquote A ) B ) + ; RCX->A RSI->( B ) + ; Need to call quasiquote on the rest of the list + push rcx + call quasiquote + mov rdx, rax + pop rcx + ; Need to concat rcx and rdx + ; Work from the end of the list to the front + + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rdx ; The rest of AST + mov rdx, rax ; Push list into RDX + + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rcx ; The splice-unquote object + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rdx + mov rdx, rax + + call alloc_cons ; Cons for concat symbol + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rdx + mov rdx, rax + + ; Get the concat symbol + mov rsi, concat_symbol + call incref_object + + mov [rdx], BYTE (container_list + content_pointer) + mov [rdx + Cons.car], rsi + + mov rax, rdx + ret + +.splice_unquote_notail: + ; Just return the object in RCX + ; since nothing to concatenate with + mov rax, rcx + ret + +.splice_unquote_empty: + ; Nothing in the (splice-unquote) list, so ignore + ; Just call quasiquote on the rest of RSI + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .empty_list ; Nothing else + + mov rsi, [rsi + Cons.cdr] + jmp quasiquote ; Tail call + +.empty_list: + ; Return an empty list + call alloc_cons + mov [rax], BYTE maltype_empty_list +.return: + ret + + +;; Tests if an AST in RSI is a list containing +;; a macro defined in the ENV in R15 +;; +;; Inputs: AST in RSI (not modified) +;; ENV in R15 (not modified) +;; +;; Returns: Sets ZF if macro call. If set (true), +;; then the macro object is in RAX +;; +;; Modifies: +;; RAX +;; RBX +;; RCX +;; RDX +;; R8 +;; R9 +is_macro_call: + ; Test if RSI is a list which contains a pointer + mov al, BYTE [rsi] + cmp al, (block_cons + container_list + content_pointer) + jne .false + + ; Test if this is a symbol + mov rbx, [rsi + Cons.car] + mov al, BYTE [rbx] + cmp al, maltype_symbol + jne .false + + ; Look up symbol in Env + push rsi + push r15 + mov rdi, rbx ; symbol in RDI + mov rsi, r15 ; Environment in RSI + call env_get + pop r15 + pop rsi + jne .false ; Not in environment + + ; Object in RAX + ; If this is not a macro then needs to be released + mov dl, BYTE [rax] + + cmp dl, maltype_macro + je .true + + ; Not a macro, so release + mov r8, rsi + mov rsi, rax + call release_object + mov rsi, r8 + +.false: + lahf ; flags in AH + and ah, 255-64 ; clear zero flag + sahf + ret +.true: + mov rbx, rax ; Returning Macro object + lahf ; flags in AH + or ah, 64 ; set zero flag + sahf + mov rax, rbx + ret + +;; Expands macro calls +;; +;; Input: AST in RSI (released and replaced) +;; Env in R15 (not modified) +;; +;; Result: New AST in RSI +macroexpand: + push r15 + + call is_macro_call + jne .done + + mov r13, rsi + + mov rdi, rax ; Macro in RDI + + ; Check the rest of the args + mov cl, BYTE [rsi + Cons.typecdr] + cmp cl, content_pointer + je .got_args + + ; No arguments. Create an empty list + call alloc_cons + mov [rax], BYTE maltype_empty_list + mov rdx, rax + + mov rsi, rdx ; Arguments (empty list) + call incref_object + jmp .macro_call +.got_args: + mov rsi, [rsi + Cons.cdr] ; Rest of list + call incref_object + mov rdx, rsi ; Released +.macro_call: + ; Here have: + ; RSI - Arguments + ; RDI - Macro object + ; RDX - List to release + ; R15 - Environment + ; R13 - AST + + ; Increment reference for Environment + ; since this will be released by apply_fn + xchg rsi, r15 + call incref_object + xchg rsi, r15 + + call apply_fn + + mov rsi, rax ; Result in RSI + + pop r15 + jmp macroexpand +.done: + pop r15 + ret + +;; Read-Eval-Print in sequence +;; +;; Input string in RSI +rep_seq: + ; ------------- + ; Read + call read_str + + ; ------------- + ; Eval + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call eval ; This releases Env and Form/AST + push rax ; Save result of eval + + ; ------------- + ; Print + + ; Put into pr_str + mov rsi, rax + mov rdi, 1 ; print_readably + call pr_str + push rax ; Save output string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from pr_str + pop rsi + call release_array + + ; Release result of eval + pop rsi + call release_object + + ; The AST from read_str is released by eval + + ret + + +_start: + ; Create and print the core environment + call core_environment ; Environment in RAX + + mov [repl_env], rax ; store in memory + + ; Set the error handler + mov rsi, rsp ; Stack pointer + mov rdi, .catch ; Address to jump to + xor rcx, rcx ; No data + call error_handler_push + + ; Evaluate the startup string + + mov rsi, mal_startup_string + mov edx, mal_startup_string.len + call raw_to_string ; String in RAX + + push rax + mov rsi, rax + call read_str ; AST in RAX + pop rsi ; string + + push rax ; AST + call release_array ; string + pop rdi ; AST in RDI + + mov rsi, [repl_env] ; Environment in RSI + + call incref_object ; Environment increment refs + xchg rsi, rdi ; Env in RDI, AST in RSI + + call eval + + mov rsi, rax + call release_object ; Return from eval + + ; ----------------------------- + ; Check command-line arguments + + pop rax ; Number of arguments + cmp rax, 1 ; Always have at least one, the path to executable + jg run_script + + ; No extra arguments, so just set *ARGV* to an empty list + call alloc_cons ; in RAX + mov [rax], BYTE maltype_empty_list + mov rcx, rax ; value (empty list) + mov rdi, argv_symbol ; symbol (*ARGV*) + mov rsi, [repl_env] ; environment + call env_set + + ; ----------------------------- + ; Main loop + +.mainLoop: + ; print the prompt + print_str_mac prompt_string + + call read_line + + ; Check if we have a zero-length string + cmp DWORD [rax+Array.length], 0 + je .mainLoopEnd + + push rax ; Save address of the input string + + ; Put into read_str + mov rsi, rax + call rep_seq + + ; Release the input string + pop rsi + call release_array + + jmp .mainLoop +.mainLoopEnd: + + jmp quit + +.catch: + ; Jumps here on error + + ; Check if an object was thrown + cmp rsi, 0 + je .catch_done_print ; nothing to print + mov rdi, 1 + call pr_str + mov rsi, rax + call print_string +.catch_done_print: + jmp .mainLoop ; Go back to the prompt + + + +run_script: + ; Called with number of command-line arguments in RAX + mov r8, rax + pop rbx ; executable + dec r8 + + pop rsi ; Address of first arg + call cstring_to_string ; string in RAX + mov r9, rax + + ; get the rest of the args + xor r10, r10 ; Zero + dec r8 + jz .no_args + + ; Got some arguments +.arg_loop: + ; Got an argument left. + pop rsi ; Address of C string + call cstring_to_string ; String in RAX + mov r12, rax + + ;Make a Cons to point to the string + call alloc_cons ; in RAX + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], r12 + + test r10, r10 + jnz .append + + ; R10 zero, so first arg + mov r10, rax ; Head of list + mov r11, rax ; Tail of list + jmp .next +.append: + ; R10 not zero, so append to list tail + mov [r11 + Cons.cdr], rax + mov [r11 + Cons.typecdr], BYTE content_pointer + mov r11, rax +.next: + dec r8 + jnz .arg_loop + jmp .got_args + +.no_args: + ; No arguments. Create an emoty list + call alloc_cons ; in RAX + mov [rax], BYTE maltype_empty_list + mov r10, rax + +.got_args: + push r9 ; File name string + + mov rcx, r10 ; value (list) + mov rdi, argv_symbol ; symbol (*ARGV*) + mov rsi, [repl_env] ; environment + call env_set + + mov rsi, run_script_string ; load-file function + mov edx, run_script_string.len + call raw_to_string ; String in RAX + + mov rsi, rax + pop rdx ; File name string + call string_append_string + + mov cl, 34 ; " + call string_append_char + mov cl, ')' + call string_append_char ; closing brace + + ; Read-Eval-Print "(load-file )" + call rep_seq + + jmp quit From 39ad52ec27d28672ae380c2885d8bc86ce1a0ac7 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 9 Dec 2017 19:19:13 +0000 Subject: [PATCH 0626/2308] Add assoc function, small fixes Step 9 now 3 failing, 9 soft fail missing vals and dissoc functions. --- nasm/core.asm | 200 ++++++++++++++++++++++++++++++++++++++++++++++++- nasm/types.asm | 87 ++++++++++++++++++++- 2 files changed, 284 insertions(+), 3 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 3471a57d5d..9bbcac927a 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -74,6 +74,8 @@ section .data static core_vector_symbol, db "vector" static core_hashmap_symbol, db "hash-map" static core_keyword_symbol, db "keyword" + + static core_assoc_symbol, db "assoc" ;; Strings @@ -126,6 +128,12 @@ section .data static core_symbol_not_string, db "Error: symbol expects a string argument" static core_keyword_not_string, db "Error: keyword expects a string argument" + + static core_list_not_seq, db "Error: list expects a list or vector" + + static core_assoc_not_map, db "Error: assoc expects a map as first argument" + static core_assoc_missing_value, db "Error: assoc missing value" + section .text ;; Add a native function to the core environment @@ -225,6 +233,8 @@ core_environment: core_env_native core_vector_symbol, core_vector core_env_native core_hashmap_symbol, core_hashmap core_env_native core_keyword_symbol, core_keyword + + core_env_native core_assoc_symbol, core_assoc ; ----------------- ; Put the environment in RAX @@ -678,7 +688,7 @@ core_get: and bl, content_mask cmp bl, content_nil - jmp .not_found + je .not_found cmp bl, content_pointer jne .not_map @@ -694,6 +704,7 @@ core_get: cmp bl, content_pointer jne .no_key mov rsi, [rsi + Cons.cdr] + mov dl, BYTE [rsi] and dl, content_mask cmp dl, content_pointer @@ -736,6 +747,10 @@ core_list: mov rax, rsi ret +.not_seq: + load_static core_list_not_seq + jmp core_throw_str + ;; Convert arguments into a vector core_vector: ; Copy first element and mark as vector @@ -1147,7 +1162,16 @@ core_pointer_type_p: cmp bl, al jne .false - ; Got an atom, return true + ; Check for keyword (not symbol) + cmp al, maltype_symbol + jne .true + + mov al, BYTE [rsi + Array.data] + cmp al, ':' + je .false ; a keyword + +.true: + ; Return true call alloc_cons mov [rax], BYTE maltype_true ret @@ -2326,7 +2350,54 @@ core_apply: jnz .last_append ; R9 is zero, so no previous args + + ; check that this is a list + ; and convert vector to list + mov r9, rsi + + ; Check if R9 is a list + mov al, BYTE [r9] + mov cl, al + and al, container_mask + cmp al, container_list + je .run + + ; Convert vector to list by copying first element + + call alloc_cons + and cl, content_mask + or cl, container_list + mov [rax], BYTE cl + mov rdx, [r9 + Cons.car] + mov [rax + Cons.car], rdx + + ; check if contains a pointer + cmp cl, (container_list + content_pointer) + jne .copy_cdr + + ; A pointer, so increment reference + mov bx, WORD [rdx + Cons.refcount] + inc bx + mov [rdx + Cons.refcount], WORD bx + +.copy_cdr: + mov bl, BYTE [r9 + Cons.typecdr] + mov rcx, [r9 + Cons.cdr] + mov [rax + Cons.typecdr], BYTE bl + mov [rax + Cons.cdr], rcx + + ; Replace R9 with this new element + mov r9, rax + + cmp bl, content_pointer + jne .run + + ; A pointer, so increment reference + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + jmp .run .last_append: @@ -2450,3 +2521,128 @@ core_keyword: .not_string: load_static core_keyword_not_string jmp core_throw_str + +;; Sets values in a map +core_assoc: + ; check first arg + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .not_map + + mov r8, [rsi + Cons.car] ; map in R8 + mov al, BYTE [r8] + and al, container_mask + cmp al, container_map + jne .not_map + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + je .start + + ; No keys to set, so just increment and return + mov rsi, r8 + call incref_object + mov rax, rsi + ret + +.start: + mov r11, [rsi + Cons.cdr] ; List of keys/values in R11 + + ; Copy the original list + mov rsi, r8 + call map_copy + mov rsi, rax ; new map in RSI + +.loop: + ; Get key then value from R11 list + + mov cl, BYTE [r11] + and cl, content_mask + cmp cl, content_pointer + je .key_pointer + + ; Key is a value, so copy into a Cons + call alloc_cons + mov [rax], BYTE cl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + mov rdi, rax ; Key in RDI + jmp .get_value + +.key_pointer: + mov rdi, [r11 + Cons.car] + ; increment reference count because the key will be + ; released after setting (to allow value Cons to be + ; freed) + + mov bx, WORD [rdi + Cons.refcount] + inc bx + mov [rdi + Cons.refcount], WORD bx + +.get_value: + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .missing_value + + mov r11, [r11 + Cons.cdr] + + ; Check if value is a pointer + mov cl, BYTE [r11] + and cl, content_mask + cmp cl, content_pointer + je .value_pointer + + ; Value is a value, so copy into a Cons + call alloc_cons + mov [rax], BYTE cl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + mov rcx, rax ; Key in RCX + jmp .set_pair + +.value_pointer: + mov rcx, [r11 + Cons.car] + ; increment reference count because the value will be + ; released after setting (to allow value Cons to be + ; freed) + + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.set_pair: + ; Here have: + ; map in RSI + ; key in RDI + ; value in RCX + + call map_set + + mov r8, rsi ; map + mov rsi, rdi ; key + call release_object + mov rsi, rcx ; value + call release_object + mov rsi, r8 ; map + + ; Check if there's another pair + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .done + + ; got another pair + mov r11, [r11 + Cons.cdr] + jmp .loop + +.done: + mov rax, rsi ; new map + ret + +.not_map: + load_static core_assoc_not_map + jmp core_throw_str + +.missing_value: + load_static core_assoc_missing_value + jmp core_throw_str diff --git a/nasm/types.asm b/nasm/types.asm index 7352c7499e..2b8eed6a7d 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -155,7 +155,7 @@ section .data ;; is free'd it is pushed onto the heap_x_free list. -%define heap_cons_limit 1000 ; Number of cons objects which can be created +%define heap_cons_limit 2000 ; Number of cons objects which can be created heap_cons_next: dd heap_cons_store ; Address of next cons in memory heap_cons_free: dq 0 ; Address of start of free list @@ -1010,6 +1010,9 @@ compare_objects: mov cl, BYTE [rsi] ; Type of RSI mov bl, BYTE [rdi] ; Type of RDI + mov ch, cl + mov bh, bl + ; Don't care about container type and cl, block_mask + content_mask and bl, block_mask + content_mask @@ -1019,6 +1022,19 @@ compare_objects: ; Here the same block, content type ; May be different container (value/list, string/symbol) + + ; Need to distinguish between map and vector/list + and ch, container_mask + and bh, container_mask + cmp ch, bh + je .same_container + ; if either is a map, then different types + cmp ch, container_map + je .different_types + cmp bh, container_map + je .different_types + +.same_container: cmp bl, block_cons + content_nil je .objects_equal ; nil @@ -1077,6 +1093,22 @@ compare_objects_rec: cmp ah, bh jne .false + + ; Need to distinguish between map and vector/list + mov ah, al + mov bh, bl + + and ah, container_mask + and bh, container_mask + cmp ah, bh + je .same_container + ; if either is a map, then different types + cmp ah, container_map + je .false + cmp bh, container_map + je .false + +.same_container: ; Check the container type and bh, block_mask @@ -1224,6 +1256,59 @@ map_new: mov [rax], BYTE (block_cons + container_map + content_empty) mov [rax + Cons.typecdr], BYTE content_nil ret + +;; Copy map +;; +;; Input: RSI - map +;; +;; Returns: new map in RAX +;; +;; Modifies: +;; RAX, RBX, RCX, R13, R14, R15 +;; +map_copy: + mov r14, rsi + + call alloc_cons + mov r15, rax ; start of new map + xor r13, r13 +.loop: + mov bl, BYTE [rsi] + mov rcx, [rsi + Cons.car] + mov [rax], BYTE bl ; copy type + mov [rax + Cons.car], rcx ; copy value + + and bl, content_mask + cmp bl, content_pointer + jne .set_cdr + + ; A pointer in CAR. Increase reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.set_cdr: + test r13,r13 + jz .next + + ; R13 contains last Cons + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax +.next: + mov r13, rax + + ; Check if there's another Cons + mov bl, BYTE [rsi + Cons.typecdr] + cmp bl, content_pointer + jne .done ; no more + + mov rsi, [rsi + Cons.cdr] ; next + call alloc_cons + jmp .loop +.done: + mov rax, r15 + mov rsi, r14 + ret ;; Add to map. Input is a list with an even number of values From 13b2edea4ca27bb990c9040848a9de8d268c335e Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 11 Dec 2017 22:39:42 +0000 Subject: [PATCH 0627/2308] Add vals and dissoc functions step 9 now only 3 soft-fail tests. All comparisons of maps, where the order of keys shouldn't matter but does. --- nasm/core.asm | 215 ++++++++++++++++++++++++++++++++++++++++++++++++- nasm/types.asm | 87 +++++++++++++++++++- 2 files changed, 299 insertions(+), 3 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 9bbcac927a..a2a7988429 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -23,6 +23,7 @@ section .data static core_count_symbol, db "count" static core_keys_symbol, db "keys" + static core_vals_symbol, db "vals" static core_list_symbol, db "list" @@ -76,6 +77,7 @@ section .data static core_keyword_symbol, db "keyword" static core_assoc_symbol, db "assoc" + static core_dissoc_symbol, db "dissoc" ;; Strings @@ -84,6 +86,10 @@ section .data static core_emptyp_error_string, db "empty? expects a list, vector or map",10 static core_count_error_string, db "count expects a list or vector",10 + + static core_keys_not_map, db "keys expects a map as first argument" + static core_vals_not_map, db "vals expects a map as first argument" + static core_numeric_expect_ints, db "comparison operator expected two numbers",10 static core_deref_not_atom, db "Error: argument to deref is not an atom" @@ -133,6 +139,9 @@ section .data static core_assoc_not_map, db "Error: assoc expects a map as first argument" static core_assoc_missing_value, db "Error: assoc missing value" + + static core_dissoc_not_map, db "dissoc expects a map as first argument" + static core_dissoc_missing_value, db "Missing value in map passed to dissoc" section .text @@ -182,6 +191,8 @@ core_environment: core_env_native core_le_symbol, core_le core_env_native core_keys_symbol, core_keys + core_env_native core_vals_symbol, core_vals + core_env_native core_list_symbol, core_list core_env_native core_pr_str_symbol, core_pr_str @@ -235,6 +246,7 @@ core_environment: core_env_native core_keyword_symbol, core_keyword core_env_native core_assoc_symbol, core_assoc + core_env_native core_dissoc_symbol, core_dissoc ; ----------------- ; Put the environment in RAX @@ -618,10 +630,32 @@ core_count: ;; Input: List in RSI with one Map element ;; Returns: List in RAX core_keys: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .not_map + mov rsi, [rsi + Cons.car] call map_keys ret - +.not_map: + load_static core_keys_not_map + jmp core_throw_str + +;; Get a list of values from a map +core_vals: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .not_map + + mov rsi, [rsi + Cons.car] + call map_vals + ret +.not_map: + load_static core_vals_not_map + jmp core_throw_str + ;; Given a map and a key, return true if the key is in the map ;; core_containsp: @@ -2646,3 +2680,182 @@ core_assoc: .missing_value: load_static core_assoc_missing_value jmp core_throw_str + + +;; Removes keys from a map by making +;; a copy of a map without the given keys +core_dissoc: + ; Check that the first argument is a map + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .not_map + + mov r8, [rsi + Cons.car] ; Map in R8 + mov al, BYTE [r8] + mov ah, al + and al, container_mask + cmp al, container_map + jne .not_map + + ; Check if the map is empty + cmp ah, maltype_empty_map + je .inc_and_return + + ; Now check if there are other arguments + + mov al, [rsi + Cons.typecdr] + cmp al, content_pointer + je .start + +.inc_and_return: + ; No keys to remove + ; just increment the map reference count and return + mov rsi, r8 + call incref_object + mov rax, rsi + ret + +.start: + ; Some keys to remove + mov r9, [rsi + Cons.cdr] + + ; R9 now contains a list of keys + ; R8 contains the map to copy + + xor r11, r11 ; Head of list to return + ; R12 contains tail + +.loop: + ; Check the key in R8 against the list in R9 + mov r10, r9 ; point in list being searched + + ; loop through the list in R10 + ; comparing each element against R8 +.search_loop: + mov rsi, r8 + mov rdi, r10 + call compare_objects + test rax, rax + jz .found ; objects are equal + + ; Not found so check next in list + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .not_found ; End of list + + mov r10, [r10 + Cons.cdr] ; next + jmp .search_loop + +.found: + ; Removing this key, so skip + mov al, BYTE [r8 + Cons.typecdr] + cmp al, content_pointer + jne .missing_value + + mov r8, [r8 + Cons.cdr] ; now a value + jmp .next + +.not_found: + ; Key not in list, so keeping + ; Create a Cons to copy + call alloc_cons + mov bl, [r8] + mov rcx, [r8 + Cons.car] + + mov [rax], BYTE bl + mov [rax + Cons.car], rcx + + ; Check if a pointer or value + and bl, content_mask + cmp bl, content_pointer + jne .done_key ; A value + + ; a pointer, so increment reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.done_key: + ; append to list + + test r11, r11 + jnz .key_append + + ; First one + mov r11, rax + mov r12, rax + jmp .copy_value +.key_append: + + mov [r12 + Cons.typecdr], BYTE content_pointer + mov [r12 + Cons.cdr], rax + mov r12, rax + +.copy_value: + + ; Check there is a value + mov al, BYTE [r8 + Cons.typecdr] + cmp al, content_pointer + jne .missing_value + + mov r8, [r8 + Cons.cdr] ; Value + + ; Same as for key; create a Cons and copy + call alloc_cons + mov bl, [r8] + mov rcx, [r8 + Cons.car] + + mov [rax], BYTE bl + mov [rax + Cons.car], rcx + + ; Check if a pointer or value + and bl, content_mask + cmp bl, content_pointer + jne .done_value ; A value + + ; a pointer, so increment reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.done_value: + ; append to list + mov [r12 + Cons.typecdr], BYTE content_pointer + mov [r12 + Cons.cdr], rax + mov r12, rax + +.next: + ; Here R8 contains a value + + ; Check if there's another key + mov al, [r8 + Cons.typecdr] + cmp al, content_pointer + jne .done + + ; Still more + + mov r8, [r8 + Cons.cdr] + jmp .loop + +.done: + ; Check if the map is empty + test r11, r11 + jz .return_empty + + ; not empty, so return + mov rax, r11 + ret + +.return_empty: + call alloc_cons + mov [rax], BYTE maltype_empty_map + ret + +.not_map: + load_static core_dissoc_not_map + jmp core_throw_str + +.missing_value: + load_static core_dissoc_missing_value + jmp core_throw_str diff --git a/nasm/types.asm b/nasm/types.asm index 2b8eed6a7d..445bfabc31 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -994,8 +994,8 @@ compare_get_value: ;; but will just compare the first element ;; ;; Modifies registers -;; RCX -;; RBX +;; RAX, RBX, RCX, RDX +;; compare_objects: ; Get the value that RSI points to call compare_get_value @@ -1823,7 +1823,90 @@ map_keys: call alloc_cons mov [rax], BYTE maltype_empty_list ret + +;; Get a list of values +;; +;; Input: Map in RSI +;; +;; Returns: List in RAX +;; +;; Modifies registers: +;; RAX +;; RBX +;; RCX +;; R8 +;; R9 +map_vals: + ; check type + mov al, BYTE [rsi] + cmp al, maltype_empty_map + je .empty_map + + and al, container_mask + cmp al, container_map + jne .empty_map ; error + + xor r8, r8 ; Return list + +.loop: + ; Here should have a key in RSI + + ; First get the value + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .done ; error. Should be a value + + mov rsi, [rsi + Cons.cdr] ; Now have value in RSI + ; Create a new Cons for this value + call alloc_cons + mov cl, BYTE [rsi] + and cl, content_mask + add cl, block_cons + container_list + mov [rax], BYTE cl ; Set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; Set value + + and cl, content_mask + cmp cl, content_pointer + jne .append + + ; A pointer, so increment reference count + mov cx, WORD [rbx + Cons.refcount] + inc cx + mov [rbx + Cons.refcount], WORD cx + +.append: + cmp r8, 0 + je .first + + ; appending + mov [r9 + Cons.typecdr], BYTE content_pointer + mov [r9 + Cons.cdr], rax + mov r9, rax + jmp .next +.first: + ; First key, so put into r8 + mov r8, rax + mov r9, rax +.next: + ; Get the next key + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .done + mov rsi, [rsi + Cons.cdr] + jmp .loop +.done: + ; Finished, return the list + mov rax, r8 + ret + +.empty_map: + ; return empty list + call alloc_cons + mov [rax], BYTE maltype_empty_list + ret + ;; ------------------------------------------------------------ ;; Function type From 4f9baa2bc4520aca5b3d54a4ddbb35464c797b87 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 11 Dec 2017 23:21:48 +0000 Subject: [PATCH 0628/2308] Start step A, add readline prints header string, readline working. --- nasm/core.asm | 43 + nasm/stepA_mal.asm | 2689 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2732 insertions(+) create mode 100644 nasm/stepA_mal.asm diff --git a/nasm/core.asm b/nasm/core.asm index a2a7988429..5c8eb564e4 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -78,6 +78,8 @@ section .data static core_assoc_symbol, db "assoc" static core_dissoc_symbol, db "dissoc" + + static core_readline_symbol, db "readline" ;; Strings @@ -247,6 +249,8 @@ core_environment: core_env_native core_assoc_symbol, core_assoc core_env_native core_dissoc_symbol, core_dissoc + + core_env_native core_readline_symbol, core_readline ; ----------------- ; Put the environment in RAX @@ -2859,3 +2863,42 @@ core_dissoc: .missing_value: load_static core_dissoc_missing_value jmp core_throw_str + + +;; Takes a string prompt for the user, and returns +;; a string or nil +core_readline: + ; Check the input + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .no_prompt + + mov rsi, [rsi + Cons.car] + mov al, BYTE [rsi] + cmp al, maltype_string + jne .no_prompt + + ; Got a string in RSI + call print_string + +.no_prompt: + + ; Get string from user + call read_line + + ; Check if we have a zero-length string + cmp DWORD [rax+Array.length], 0 + je .return_nil + + ; return the string in RAX + ret + +.return_nil: + ; release string in RAX + mov rsi, rax + call release_array + + call alloc_cons + mov [rax], BYTE maltype_nil + ret diff --git a/nasm/stepA_mal.asm b/nasm/stepA_mal.asm new file mode 100644 index 0000000000..0d0d9bb8ef --- /dev/null +++ b/nasm/stepA_mal.asm @@ -0,0 +1,2689 @@ +;; +;; nasm -felf64 stepA_mal.asm && ld stepA_mal.o && ./a.out +;; +;; Calling convention: Address of input is in RSI +;; Address of return value is in RAX +;; + +global _start + +%include "types.asm" ; Data types, memory +%include "env.asm" ; Environment type +%include "system.asm" ; System calls +%include "reader.asm" ; String -> Data structures +%include "core.asm" ; Core functions +%include "printer.asm" ; Data structures -> String + +section .bss + +;; Top-level (REPL) environment +repl_env:resq 1 + +;; Error handler list +error_handler: resq 1 + +section .data + +;; ------------------------------------------ +;; Fixed strings for printing + + static prompt_string, db 10,"user> " ; The string to print at the prompt + + static error_string, db 27,'[31m',"Error",27,'[0m',": " + + static not_found_string, db " not found" + + static def_missing_arg_string, db "missing argument to def!",10 + + static def_expecting_symbol_string, db "expecting symbol as first argument to def!",10 + + static defmacro_expecting_function_string, db "defmacro expects function",10 + + static let_missing_bindings_string, db "let* missing bindings",10 + + static let_bindings_list_string, db "let* expected a list or vector of bindings",10 + + static let_bind_symbol_string, db "let* expected a symbol in bindings list",10 + + static let_bind_value_string, db "let* missing value in bindings list",10 + + static let_missing_body_string, db "let* missing body",10 + static eval_list_not_function, db "list does not begin with a function",10 + + static if_missing_condition_string, db "missing condition in if expression",10 + + static try_missing_catch, db "try* missing catch*" + static catch_missing_symbol, db "catch* missing symbol" + static catch_missing_form, db "catch* missing form" + +;; Symbols used for comparison + + ; Special symbols + static_symbol def_symbol, 'def!' + static_symbol let_symbol, 'let*' + static_symbol do_symbol, 'do' + static_symbol if_symbol, 'if' + static_symbol fn_symbol, 'fn*' + static_symbol defmacro_symbol, 'defmacro!' + static_symbol macroexpand_symbol, 'macroexpand' + static_symbol try_symbol, 'try*' + static_symbol catch_symbol, 'catch*' + + static_symbol argv_symbol, '*ARGV*' + + static_symbol quote_symbol, 'quote' + static_symbol quasiquote_symbol, 'quasiquote' + static_symbol unquote_symbol, 'unquote' + static_symbol splice_unquote_symbol, 'splice-unquote' + static_symbol concat_symbol, 'concat' + static_symbol cons_symbol, 'cons' + ; + +;; Startup string. This is evaluated on startup + static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) (defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw ",34,"odd number of forms to cond",34,")) (cons 'cond (rest (rest xs))))))) (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs)))))))) (def! *host-language* ",34,"nasm",34,") )" + +;; Command to run, appending the name of the script to run + static run_script_string, db "(load-file ",34 + +;; Command to run at start of REPL + static mal_startup_header, db "(println (str ",34,"Mal [",34," *host-language* ",34,"]",34,"))" +section .text + +;; ---------------------------------------------- +;; +;; Error handling +;; +;; A handler consists of: +;; - A stack pointer address to reset to +;; - An address to jump to +;; - An optional data structure to pass +;; +;; When jumped to, an error handler will be given: +;; - the object thrown in RSI +;; - the optional data structure in RDI +;; + + +;; Add an error handler to the front of the list +;; +;; Input: RSI - Stack pointer +;; RDI - Address to jump to +;; RCX - Data structure. Set to zero for none. +;; If not zero, reference count incremented +;; +;; Modifies registers: +;; RAX +;; RBX +error_handler_push: + call alloc_cons + ; car will point to a list (stack, addr, data) + ; cdr will point to the previous handler + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov rbx, [error_handler] + cmp rbx, 0 ; Check if previous handler was zero + je .create_handler ; Zero, so leave null + ; Not zero, so create pointer to it + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rbx + + ; note: not incrementing reference count, since + ; we're replacing one reference with another +.create_handler: + mov [error_handler], rax ; new error handler + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.car], rax + ; Store stack pointer + mov [rax], BYTE (block_cons + container_list + content_function) + mov [rax + Cons.car], rsi ; stack pointer + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.typecdr], BYTE content_pointer + mov [rdx + Cons.cdr], rax + ; Store function pointer to jump to + ; Note: This can't use content_pointer or release + ; will try to release this memory address + mov [rax], BYTE (block_cons + container_list + content_function) + mov [rax + Cons.car], rdi + + ; Check if there is an object to pass to handler + cmp rcx, 0 + je .done + + ; Set the final CDR to point to the object + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rcx + + mov rsi, rcx + call incref_object + +.done: + ret + + +;; Removes an error handler from the list +;; +;; Modifies registers: +;; RSI +;; RAX +;; RCX +error_handler_pop: + ; get the address + mov rsi, [error_handler] + cmp rsi, 0 + je .done ; Nothing to remove + + push rsi + mov rsi, [rsi + Cons.cdr] ; next handler + mov [error_handler], rsi + ;call incref_object ; needed because releasing soon + + pop rsi ; handler being removed + mov [rsi + Cons.typecdr], BYTE 0 + call release_cons + +.done: + ret + + +;; Throw an error +;; Object to pass to handler should be in RSI +error_throw: + ; Get the next error handler + mov rax, [error_handler] + cmp rax, 0 + je .no_handler + + ; Got a handler + mov rax, [rax + Cons.car] ; handler + mov rbx, [rax + Cons.car] ; stack pointer + mov rax, [rax + Cons.cdr] + mov rcx, [rax + Cons.car] ; function + mov rdi, [rax + Cons.cdr] ; data structure + + ; Reset stack + mov rsp, rbx + + ; Jump to the handler + jmp rcx + +.no_handler: + ; Print the object in RSI then quit + cmp rsi, 0 + je .done ; nothing to print + mov rdi, 1 ; print_readably + call pr_str + mov rsi, rax + call print_string +.done: + jmp quit_error + +;; ---------------------------------------------- +;; Evaluates a form +;; +;; Inputs: RSI Form to evaluate +;; RDI Environment +;; +eval_ast: + mov r15, rdi ; Save Env in r15 + + ; Check the type + mov al, BYTE [rsi] + + ; Check if this is a list + mov ah, al + and ah, container_mask + cmp ah, container_list + je .list + + cmp ah, container_map + je .map + + cmp ah, container_vector + je .vector + + ; Not a list, map or vector + cmp ah, container_symbol + je .symbol + + ; Not a symbol, list, map or vector + call incref_object ; Increment reference count + + mov rax, rsi + ret + +.symbol: + ; Check if first character of symbol is ':' + mov al, BYTE [rsi + Array.data] + cmp al, ':' + je .keyword + + ; look in environment + push rsi + xchg rsi, rdi + ; symbol is the key in rdi + ; Environment in rsi + call env_get + pop rsi + je .done ; result in RAX + + ; Not found, throw an error + mov r11, rsi ; Symbol in R11 + + call string_new + mov rsi, rax ; New string in RSI + + mov cl, 39 ; quote ' + call string_append_char + + mov rdx, r11 ; symbol + call string_append_string + + mov cl, 39 + call string_append_char + + mov r11, rsi + + mov rsi, not_found_string + mov edx, not_found_string.len + call raw_to_string ; ' not found' + + mov r12, rax + + mov rdx, rax + mov rsi, r11 + call string_append_string + + mov r11, rsi + mov rsi, r12 + call release_array + mov rsi, r11 + + jmp error_throw + + ; ------------------------------ + +.keyword: + ; Just return keywords unaltered + call incref_object + mov rax, rsi + ret + + ; ------------------------------ +.list: + ; Evaluate each element of the list + ; + xor r8, r8 ; The list to return + ; r9 contains head of list + +.list_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .list_pointer + + ; A value in RSI, so copy + + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_list) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .list_append + +.list_pointer: + ; List element is a pointer to something + push rsi + push r8 + push r9 + push r15 ; Env + mov rdi, [rsi + Cons.car] ; Get the address + mov rsi, r15 + + call incref_object ; Environment increment refs + xchg rsi, rdi ; Env in RDI, AST in RSI + + call incref_object ; AST increment refs + + call eval ; Evaluate it, result in rax + pop r15 + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .list_eval_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_list + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .list_append + +.list_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_list) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi + + ; Fall through to .list_append +.list_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .list_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .list_next + +.list_first: + mov r8, rax + mov r9, rax + ; fall through to .list_next + +.list_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .list_done ; finished list + mov rsi, [rsi + Cons.cdr] ; next in list + jmp .list_loop + +.list_done: + mov rax, r8 ; Return the list + ret + + ; --------------------- +.map: + ; Create a new map, evaluating all the values + + ; Check if the map is empty + cmp al, maltype_empty_map + jne .map_not_empty + + ; map empty. Just return it + call incref_object + mov rax, rsi + ret + +.map_not_empty: + + mov r10, rsi ; input in R10 + xor r12, r12 ; New map in r12 + + ; Now loop through each key-value pair + ; NOTE: This method relies on the implementation + ; of map as a list + +.map_loop: + ; Copy the key + call alloc_cons ; New Cons in RAX + + mov bl, [r10 + Cons.typecar] ; Type in BL + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] ; Value in RCX + mov [rax + Cons.car], rcx + + ; Check the type of the key + and bl, content_mask + cmp bl, content_pointer + jne .map_got_key ; a value + + ; a pointer, so increment reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + +.map_got_key: + cmp r12,0 + jne .append_key + + ; First key + mov r12, rax + mov r13, rax + jmp .map_value + +.append_key: + ; Appending to previous value in r13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + +.map_value: + ; Check that we have a value + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_error_missing_value + mov r10, [r10 + Cons.cdr] + + ; Now got value in r10 + + ; Check the type of the value + mov bl, [r10 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .map_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r10 + Cons.typecar] + mov [rax + Cons.typecar], bl + mov rcx, [r10 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .map_got_value +.map_value_pointer: + ; A pointer, so need to evaluate + push r10 ; Input + push r12 ; start of result + push r13 ; Current head of result + push r15 ; Env + mov rsi, [r10 + Cons.car] ; Get the address + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi + + call incref_object + + call eval ; Evaluate it, result in rax + pop r15 + pop r13 + pop r12 + pop r10 + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + + jne .map_eval_pointer + + ; A value, so just change the type to a map + and bl, content_mask + add bl, (block_cons + container_map) + mov [rax], BYTE bl + jmp .map_got_value + +.map_eval_pointer: + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_map + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + +.map_got_value: + ; Append RAX to list in R13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + mov r13, rax + + ; Check if there's another key + mov al, BYTE [r10 + Cons.typecdr] + cmp al, content_pointer + jne .map_done ; finished map + mov r10, [r10 + Cons.cdr] ; next in map + jmp .map_loop + +.map_done: + mov rax, r12 + ret + +.map_error_missing_value: + mov rax, r12 + ret + + ; ------------------------------ +.vector: + ; Evaluate each element of the vector + ; + xor r8, r8 ; The vector to return + ; r9 contains head of vector + +.vector_loop: + mov al, BYTE [rsi] ; Check type + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .vector_pointer + + ; A value, so copy + call alloc_cons + mov bl, BYTE [rsi] + and bl, content_mask + add bl, (block_cons + container_vector) + mov [rax], BYTE bl ; set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy value + + ; Result in RAX + jmp .vector_append + +.vector_pointer: + ; Vector element is a pointer to something + push rsi + push r8 + push r9 + push r15 ; Env + mov rsi, [rsi + Cons.car] ; Get the address + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi + + call incref_object + + call eval ; Evaluate it, result in rax + pop r15 + pop r9 + pop r8 + pop rsi + + ; Check the type it's evaluated to + mov bl, BYTE [rax] + mov bh, bl + and bh, (block_mask + container_mask) + cmp bh, (block_cons + container_value) + je .vector_eval_value + + ; Not a value, so need a pointer to it + push rax + call alloc_cons + mov [rax], BYTE (block_cons + container_vector + content_pointer) + pop rbx ; Address to point to + mov [rax + Cons.car], rbx + jmp .vector_append + +.vector_eval_value: + ; Got value in RAX, so copy + push rax + call alloc_cons ; Copy in RAX + pop rbx ; Value to copy in RBX + mov cl, BYTE [rbx] + and cl, content_mask + or cl, (block_cons + container_vector) + mov [rax], BYTE cl ; set type + mov rcx, [rbx + Cons.car] + mov [rax + Cons.car], rcx ; copy value + + ; Release the value in RBX + push rsi + push rax + mov rsi, rbx + call release_cons + pop rax + pop rsi + +.vector_append: + ; In RAX + + cmp r8, 0 ; Check if this is the first + je .vector_first + + ; append to r9 + mov [r9 + Cons.cdr], rax + mov [r9 + Cons.typecdr], BYTE content_pointer + mov r9, rax + jmp .vector_next + +.vector_first: + mov r8, rax + mov r9, rax + ; fall through to .vector_next + +.vector_next: + ; Check if there's another + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .vector_done ; finished vector + mov rsi, [rsi + Cons.cdr] ; next in vector + jmp .vector_loop + +.vector_done: + mov rax, r8 ; Return the vector + ret + + ; --------------------- +.done: + ret + + + +;; Comparison of symbols for eval function +;; Compares the symbol in RSI with specified symbol +;; Preserves RSI and RBX +;; Modifies RDI +%macro eval_cmp_symbol 1 + push rsi + push rbx + mov rsi, rbx + mov rdi, %1 + call compare_char_array + pop rbx + pop rsi + test rax, rax ; ZF set if rax = 0 (equal) +%endmacro + +;; ---------------------------------------------------- +;; Evaluates a form +;; +;; Input: RSI AST to evaluate [ Released ] +;; RDI Environment [ Released ] +;; +;; Returns: Result in RAX +;; +;; Note: Both the form and environment will have their reference count +;; reduced by one (released). This is for tail call optimisation (Env), +;; quasiquote and macroexpand (AST) +;; +eval: + mov r15, rdi ; Env + + push rsi ; AST pushed, must be popped before return + + ; Check type + mov al, BYTE [rsi] + cmp al, maltype_empty_list + je .return_nil + + and al, container_mask + cmp al, container_list + je .list + + ; Not a list. Evaluate and return + call eval_ast + jmp .return ; Releases Env + + ; -------------------- +.list: + ; A list + + ; Macro expand + pop rax ; Old AST, discard from stack + call macroexpand ; Replaces RSI + push rsi ; New AST + + ; Check if RSI is a list, and if + ; the first element is a symbol + mov al, BYTE [rsi] + + mov ah, al + and ah, container_mask + cmp ah, container_list + je .list_still_list + + ; Not a list, so call eval_ast on it + mov rdi, r15 ; Environment + call eval_ast + jmp .return + +.list_still_list: + and al, content_mask + cmp al, content_pointer + jne .list_eval + + mov rbx, [rsi + Cons.car] + mov al, BYTE [rbx] + cmp al, maltype_symbol + jne .list_eval + + ; Is a symbol, address in RBX + + ; Compare against special form symbols + + eval_cmp_symbol def_symbol ; def! + je .def_symbol + + eval_cmp_symbol let_symbol ; let* + je .let_symbol + + eval_cmp_symbol do_symbol ; do + je .do_symbol + + eval_cmp_symbol if_symbol ; if + je .if_symbol + + eval_cmp_symbol fn_symbol ; fn + je .fn_symbol + + eval_cmp_symbol quote_symbol ; quote + je .quote_symbol + + eval_cmp_symbol quasiquote_symbol ; quasiquote + je .quasiquote_symbol + + eval_cmp_symbol defmacro_symbol ; defmacro! + je .defmacro_symbol + + eval_cmp_symbol macroexpand_symbol ; macroexpand + je .macroexpand_symbol + + eval_cmp_symbol try_symbol ; try* + je .try_symbol + + ; Unrecognised + jmp .list_eval + + + ; ----------------------------- + +.defmacro_symbol: + mov r9, 1 + jmp .def_common +.def_symbol: + xor r9, r9 ; Set R9 to 0 +.def_common: + ; Define a new symbol in current environment + ; If R9 is set to 1 then defmacro + + ; Next item should be a symbol + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Now should have a symbol + + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + jne .def_error_expecting_symbol + mov r8, [rsi + Cons.car] ; Symbol (?) + + mov al, BYTE [r8] + cmp al, maltype_symbol + jne .def_error_expecting_symbol + + ; R8 now contains a symbol + + ; expecting a value or pointer next + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .def_error_missing_arg + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a pointer + mov al, BYTE [rsi] + mov ah, al + and ah, content_mask + cmp ah, content_pointer + je .def_pointer + + ; A value, so copy + + ; Test if this is defmacro! + test r9, r9 + jnz .defmacro_not_function + + push rax + call alloc_cons + pop rbx ; BL now contains type + and bl, content_mask + add bl, (block_cons + container_value) + mov [rax], BYTE bl + mov rcx, [rsi + Cons.car] + mov [rax + Cons.car], rcx + mov rsi, rax + + jmp .def_got_value + +.def_pointer: + ; A pointer, so evaluate + + ; This may throw an error, so define a handler + + push r8 ; the symbol + push r15 ; Env + push r9 + mov rsi, [rsi + Cons.car] ; Pointer + mov rdi, r15 + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call incref_object ; AST increment refs + + call eval + mov rsi, rax + + pop r9 + + ; If this is defmacro, and the object in RSI is a function, + ; then change to a macro + test r9, r9 + jz .def_not_macro ; Not defmacro + + ; Check RSI + mov al, BYTE [rsi] + cmp al, maltype_function + jne .defmacro_not_function + + ; Got a function, change to macro + mov [rsi], BYTE maltype_macro + +.def_not_macro: + + pop r15 + pop r8 + +.def_got_value: + ; Symbol in R8, value in RSI + mov rdi, r8 ; key (symbol) + mov rcx, rsi ; Value + mov rsi, r15 ; Environment + call env_set + + mov rax, rcx + jmp .return + +.def_error_missing_arg: + mov rsi, def_missing_arg_string + mov rdx, def_missing_arg_string.len + jmp .def_handle_error + +.def_error_expecting_symbol: + mov rsi, def_expecting_symbol_string + mov rdx, def_expecting_symbol_string.len + jmp .def_handle_error + +.defmacro_not_function: + mov rsi, defmacro_expecting_function_string + mov rdx, defmacro_expecting_function_string.len + jmp .def_handle_error + +.def_handle_error: + push rsi + push rdx + print_str_mac error_string ; print 'Error: ' + + pop rdx + pop rsi + call print_rawstring ; print message + + xor rsi, rsi ; no object to throw + jmp error_throw ; No return + + ; ----------------------------- +.let_symbol: + ; Create a new environment + + mov r11, rsi ; Let form in R11 + + mov rsi, r15 ; Outer env + call env_new ; Increments R15's ref count + mov r14, rax ; New environment in R14 + + mov rsi, r15 + call release_object ; Decrement R15 ref count + + ; Second element should be the bindings + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_bindings + mov r11, [r11 + Cons.cdr] + + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .let_error_bindings_list + + mov r12, [r11 + Cons.car] ; should be bindings list + mov al, BYTE [r12] + and al, (block_mask + container_mask) + ; Can be either a list or vector + cmp al, block_cons + container_list + je .let_bind_loop + cmp al, block_cons + container_vector + je .let_bind_loop + + ; Not a list or vector + jmp .let_error_bindings_list + +.let_bind_loop: + ; R12 now contains a list with an even number of items + ; The first should be a symbol, then a value to evaluate + + ; Get the symbol + mov al, BYTE [r12] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_symbol + + mov r13, [r12 + Cons.car] ; Symbol (?) + mov al, BYTE [r13] + cmp al, maltype_symbol + jne .let_error_bind_symbol + + ; R13 now contains a symbol to bind + ; The next item in the bindings list (R12) + ; should be a value or expression to evaluate + + mov al, BYTE [r12 + Cons.typecdr] + and al, content_mask + cmp al, content_pointer + jne .let_error_bind_value + mov r12, [r12 + Cons.cdr] + + ; got value in R12 + + ; Check the type of the value + mov bl, [r12 + Cons.typecar] ; Type in BL + and bl, content_mask + cmp bl, content_pointer + je .let_value_pointer + + ; Not a pointer, so make a copy + call alloc_cons + mov bl, [r12 + Cons.typecar] + and bl, content_mask + ;or bl, (block_cons + container_value) ; 0 + mov [rax + Cons.typecar], bl + mov rcx, [r12 + Cons.car] + mov [rax + Cons.car], rcx + + jmp .let_got_value + +.let_value_pointer: + ; A pointer, so need to evaluate + push r11 ; let* form list + push r12 ; Position in bindings list + push r13 ; symbol to bind + push r14 ; new environment + + mov rsi, r14 + call incref_object + mov rdi, r14 + + mov rsi, [r12 + Cons.car] ; Get the address + + call incref_object ; Increment ref count of AST + + call eval ; Evaluate it, result in rax + pop r14 + pop r13 + pop r12 + pop r11 + +.let_got_value: + + mov rsi, r14 ; Env + mov rdi, r13 ; key + mov rcx, rax ; value + call env_set + + ; Release the value + mov rsi, rcx ; The value + call release_object + + ; Check if there are more bindings + mov al, BYTE [r12 + Cons.typecdr] + cmp al, content_pointer + jne .let_done_binding + mov r12, [r12 + Cons.cdr] ; Next + jmp .let_bind_loop + +.let_done_binding: + ; Done bindings. + ; Evaluate next item in let* form in new environment + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .let_error_missing_body + mov r11, [r11 + Cons.cdr] ; Now contains value to evaluate + ; Check type of the value + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + je .body_pointer + + ; Just a value, so copy + call alloc_cons + mov bl, BYTE [r11] + and bl, content_mask + mov [rax], BYTE bl ; set type + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx ; copy value + jmp .let_done + +.body_pointer: + ; Evaluate using new environment + + mov rsi, [r11 + Cons.car] ; Object pointed to + call incref_object ; will be released by eval + + mov r11, rsi ; save new AST + pop rsi ; Old AST + call release_object + mov rsi, r11 ; New AST + + mov rdi, r14 ; New environment + + jmp eval ; Tail call + ; Note: eval will release the new environment on return + +.let_done: + ; Release the new environment + push rax + mov rsi, r14 + call release_object + pop rax + + ; Release the AST + pop rsi + push rax + call release_object + pop rax + ret ; already released env + +.let_error_missing_bindings: + mov rsi, let_missing_bindings_string + mov rdx, let_missing_bindings_string.len + jmp .let_handle_error + +.let_error_bindings_list: ; expected a list or vector, got something else + mov rsi, let_bindings_list_string + mov rdx, let_bindings_list_string.len + jmp .let_handle_error + +.let_error_bind_symbol: ; expected a symbol, got something else + mov rsi, let_bind_symbol_string + mov rdx, let_bind_symbol_string.len + jmp .let_handle_error + +.let_error_bind_value: ; Missing value in binding list + mov rsi, let_bind_value_string + mov rdx, let_bind_value_string.len + jmp .let_handle_error + +.let_error_missing_body: ; Missing body to evaluate + mov rsi, let_missing_body_string + mov rdx, let_missing_body_string.len + jmp .let_handle_error + +.let_handle_error: + push r11 ; For printing later + + push rsi + push rdx + + print_str_mac error_string ; print 'Error: ' + + pop rdx + pop rsi + call print_rawstring ; print message + + pop rsi ; let* form + jmp error_throw ; No return + + ; ----------------------------- + +.do_symbol: + mov r11, rsi ; do form in RSI + ; Environment in R15 + + ; Check if there is a body + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .do_no_body ; error + + mov r11, [r11 + Cons.cdr] ; Body in R11 + +.do_symbol_loop: + + ; Need to test if this is the last form + ; so we can handle tail call + + mov bl, BYTE [r11 + Cons.typecdr] + cmp bl, content_pointer + jne .do_body_last ; Last expression + + ; not the last expression + + ; Check if this is a value or pointer + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + jne .do_next ; A value, so skip + + ; A pointer, so evaluate + + push r15 + push r11 + + mov rsi, r15 + call incref_object ; Increase Env reference + ; since eval will release Env + + mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Increment ref count since eval will release + + mov rdi, r15 ; Env + call eval ; Result in RAX + + ; Another form after this. + ; Discard the result of the last eval + mov rsi, rax + call release_object + + pop r11 + pop r15 + +.do_next: + mov r11, [r11 + Cons.cdr] ; Next in list + + jmp .do_symbol_loop + +.do_body_last: + ; The last form is in R11, which will be returned + + ; Check if this is a value or pointer + mov al, BYTE [r11] + and al, block_mask + content_mask + cmp al, content_pointer + jne .do_body_value_return + jmp .do_body_expr_return + +.do_body_value_return: + ; Got a value as last form (in R11). + ; Copy and return + + push rax ; Type of value to return + + ; release Env + mov rsi, r15 + call release_object + + ; Allocate a Cons object to hold value + call alloc_cons + pop rbx ; type in BL + mov [rax], BYTE bl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + + ; release the AST + pop rsi + mov r15, rax ; not modified by release + call release_object + mov rax, r15 + + ret + +.do_body_expr_return: + ; An expression to evaluate as the last form + ; Tail call optimise, jumping to eval + ; Don't increment Env reference count + + mov rsi, [r11 + Cons.car] ; new AST form + call incref_object ; This will be released by eval + + mov r11, rsi ; Save new AST + pop rsi ; Remove old AST from stack + call release_object + mov rsi, r11 + + mov rdi, r15 ; Env + jmp eval ; Tail call + +.do_no_body: + ; No expressions to evaluate. Return nil + + mov rsi, r15 + call release_object ; Release Env + + ; release the AST + pop rsi + call release_object + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + ret + + ; ----------------------------- + +.if_symbol: + mov r11, rsi ; if form in R11 + ; Environment in R15 + + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .if_no_condition + + mov r11, [r11 + Cons.cdr] ; Should be a condition + + ; Check if value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .if_cond_value + + ; A pointer, so evaluate + + push r15 + push r11 + + mov rsi, r15 + call incref_object ; Increase Env reference + + mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Increase Form/AST ref count + + mov rdi, r15 ; Env + call eval ; Result in RAX + pop r11 + pop r15 + + ; Get type of result + mov bl, BYTE [rax] + + ; release value + push rbx + mov rsi, rax + call release_object + pop rbx + + ; Check type + cmp bl, maltype_nil + je .if_false + cmp bl, maltype_false + je .if_false + + jmp .if_true + +.if_cond_value: + + ; A value + cmp al, content_nil + je .if_false + cmp al, content_false + je .if_false + + jmp .if_true + +.if_false: + ; Skip the next item + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .return_nil + + mov r11, [r11 + Cons.cdr] + +.if_true: + ; Get the next item in the list and evaluate it + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; Nothing to return + + mov r11, [r11 + Cons.cdr] + + ; Check if value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + je .if_got_pointer + +.if_got_value: + ; copy value in r11 + call alloc_cons + mov bl, BYTE [r11] + and bl, content_mask + mov [rax], BYTE bl + mov rbx, [r11 + Cons.car] + mov [rax + Cons.car], rbx + + jmp .return + +.if_got_pointer: + mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Will be released by eval + + mov r11, rsi + pop rsi + call release_object ; Release old AST + mov rsi, r11 ; New AST + + mov rdi, r15 ; Env + jmp eval ; Tail call + +.if_no_condition: ; just (if) without a condition + + print_str_mac error_string + print_str_mac if_missing_condition_string + + ; Release environment + mov rsi, r15 + call release_object + xor rsi, rsi ; No object to throw + jmp error_throw + +.return_nil: + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + +.return: + ; Release environment + mov rsi, r15 + mov r15, rax ; Save RAX (return value) + call release_object + + ; Release the AST + pop rsi ; Pushed at start of eval + call release_object + + mov rax, r15 ; return value + ret + + ; ----------------------------- + +.fn_symbol: + mov r11, rsi ; fn form in R11 + ; Environment in R15 + + ; Get the binds and body of the function + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .fn_empty + + mov r11, [r11 + Cons.cdr] + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .fn_binds_not_list + + mov r12, [r11 + Cons.car] ; Should be binds list + mov al, BYTE [r12] + and al, (block_mask + container_mask) + cmp al, (block_cons + container_list) + je .fn_got_binds ; Can be list + cmp al, (block_cons + container_vector) + je .fn_got_binds ; or vector + jmp .fn_binds_not_list + +.fn_got_binds: + + ; Next get the body of the function + mov al, BYTE [r11 + Cons.typecdr] + cmp al, content_pointer + jne .fn_no_body + + mov r11, [r11 + Cons.cdr] + ; Check value or pointer + mov al, BYTE [r11] + and al, content_mask + cmp al, content_pointer + jne .fn_is_value ; Body in r11 + mov r11, [r11 + Cons.car] + jmp .fn_got_body + +.fn_is_value: + ; Body is just a value, no expression + mov [r11], BYTE al ; Mark as value, not list + +.fn_got_body: + + ; Now put into function type + ; Addr is "apply_fn", the address to call + ; Env in R15 + ; Binds in R12 + ; Body in R11 + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_function) + mov rbx, apply_fn + mov [rax + Cons.car], rbx ; Address of apply function + mov [rax + Cons.typecdr], BYTE content_pointer + + mov r13, rax ; Return list in R13 + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r15 ; Environment + mov [rax + Cons.typecdr], BYTE content_pointer + + mov [r13 + Cons.cdr], rax ; Append to list + mov r14, rax + + push rax + mov rsi, r15 + call incref_object + pop rax + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r12 ; Binds list + mov [rax + Cons.typecdr], BYTE content_pointer + + mov [r14 + Cons.cdr], rax ; Append to list + mov r14, rax + + push rax + mov rsi, r12 + call incref_object + pop rax + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_pointer) + mov [rax + Cons.car], r11 ; Body of function + + mov [r14 + Cons.cdr], rax + + mov rsi, r11 + call incref_object + + mov rax, r13 + jmp .return + +.fn_empty: +.fn_binds_not_list: +.fn_no_body: + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_nil + jmp .return + + ; ----------------------------- + +.quote_symbol: + ; Just return the arguments in rsi cdr + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; quote empty, so return nil + + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a value or pointer + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + je .quote_pointer + + ; RSI contains a value. Remove the list container + mov [rsi + Cons.typecar], BYTE al + call incref_object + mov rax, rsi + jmp .return + +.quote_pointer: + ; RSI contains a pointer, so get the object pointed to + mov rsi, [rsi + Cons.car] + call incref_object + mov rax, rsi + jmp .return + + ; ----------------------------- + +.quasiquote_symbol: + ; call quasiquote function with first argument + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; quasiquote empty, so return nil + + mov r11, rsi ; Save original AST in R11 + + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a value or pointer + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + je .quasiquote_pointer + + ; RSI contains a value. Remove the list container + mov [rsi + Cons.typecar], BYTE al + call incref_object + mov rax, rsi + jmp .return + +.quasiquote_pointer: + ; RSI contains a pointer, so get the object pointed to + mov rsi, [rsi + Cons.car] + + ; Uncomment these two lines to test quasiquote + ;call quasiquote + ;ret + + push r15 ; Environment + ; Original AST already on stack + + call quasiquote + ; New AST in RAX + pop rdi ; Environment + pop rsi ; Old AST + + mov r11, rax ; New AST + call release_object ; Release old AST + mov rsi, r11 ; New AST in RSI + + jmp eval ; Tail call + + ; ----------------------------- +.macroexpand_symbol: + ; Check if we have a second list element + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; No argument + + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a value or pointer + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + je .macroexpand_pointer + + ; RSI contains a value. Remove the list container + mov [rsi + Cons.typecar], BYTE al + call incref_object + mov rax, rsi + jmp .return + +.macroexpand_pointer: + mov rsi, [rsi + Cons.car] + call incref_object ; Since RSI will be released + + call macroexpand ; May release and replace RSI + + mov rax, rsi + jmp .return ; Releases original AST + + ; ----------------------------- + +.try_symbol: + ; Should have the form + ; + ; (try* A (catch* B C)) + ; + ; where B is a symbol, A and C are forms to evaluate + + ; Check first arg A + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .return_nil ; No argument + + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a value or pointer + mov al, BYTE [rsi + Cons.typecar] + and al, content_mask + cmp al, content_pointer + je .try_pointer + + ; RSI contains a value. Copy and return + mov cl, al + call alloc_cons + mov [rax], BYTE cl ; Set type + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx + jmp .return + +.try_pointer: + + mov r8, [rsi + Cons.car] ; form A in R8 + + ; Check second arg B + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .try_missing_catch + + mov rsi, [rsi + Cons.cdr] + + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .try_missing_catch + + mov r9, [rsi + Cons.car] ; (catch* B C) in R9 + + mov al, BYTE [r9] + cmp al, (container_list + content_pointer) + jne .try_missing_catch + + mov rsi, [r9 + Cons.car] ; Should be catch* symbol + mov al, BYTE [rsi] + cmp al, maltype_symbol + jne .try_missing_catch + + mov rdi, catch_symbol + call compare_char_array + test rax, rax ; ZF set if rax = 0 (equal) + jnz .try_missing_catch + + ; Check that B is a symbol + mov al, [r9 + Cons.typecdr] + cmp al, content_pointer + jne .catch_missing_symbol + + mov r9, [r9 + Cons.cdr] ; (B C) in R9 + + mov al, BYTE [r9] + and al, content_mask + cmp al, content_pointer + jne .catch_missing_symbol + + mov r10, [r9 + Cons.car] ; B in R10 + mov al, BYTE [r10] + cmp al, maltype_symbol + jne .catch_missing_symbol + + mov al, BYTE [r9 + Cons.typecdr] + cmp al, content_pointer + jne .catch_missing_form + mov r9, [r9 + Cons.cdr] ; C in R9 + + ; Now have extracted from (try* A (catch* B C)) + ; A in R8 + ; B in R10 + ; C in T9 + + push R9 + push R10 + + ; Set the error handler + mov rsi, rsp ; Stack pointer + mov rdi, .catch ; Address to jump to + xor rcx, rcx ; No data + call error_handler_push + + ; Evaluate the form in R8 + mov rsi, r15 + call incref_object ; Env released by eval + mov rdi, r15 ; Env in RDI + + mov rsi, r8 ; The form to evaluate (A) + + call incref_object ; AST released by eval + + call eval + mov r8, rax ; Result in R8 + + ; Discard B and C + ;add rsi, 8 ; pop R10 and R9 + pop r10 + pop r9 + + ; Remove error handler + call error_handler_pop + mov rax, r8 + jmp .return + +.catch: + ; Jumps here on error + ; Value thrown in RSI + ; + + pop r12 ; B (symbol to bind) + pop r13 ; C (form to evaluate) + + ; Check if C is a value or pointer + + mov cl, BYTE [r13] + and cl, content_mask + cmp cl, content_pointer + je .catch_C_pointer + + ; A value, so copy and return + call alloc_cons + mov [rax], BYTE cl ; Set type + mov rbx, [r13 + Cons.car] + mov [rax + Cons.car], rbx ; Set value + + jmp .return + +.catch_C_pointer: + + mov r11, rsi ; Value thrown in R11 + + mov rsi, r15 ; Outer env + call env_new ; Increments R15's ref count + + mov rsi, rax ; New environment in RSI + mov rdi, r12 ; key (symbol) + mov rcx, r11 ; value + call env_set + + mov rdi, rsi ; Env in RDI (will be released) + mov rsi, [r13 + Cons.car] ; Form to evaluate + call incref_object ; will be released + + call eval + + jmp .return + +.try_missing_catch: + load_static try_missing_catch + call raw_to_string + mov rsi, rax + jmp error_throw + +.catch_missing_symbol: + load_static catch_missing_symbol + call raw_to_string + mov rsi, rax + jmp error_throw + +.catch_missing_form: + load_static catch_missing_form + call raw_to_string + mov rsi, rax + jmp error_throw + + ; ----------------------------- + +.list_eval: + push rsi + mov rdi, r15 ; Environment + push r15 + call eval_ast ; List of evaluated forms in RAX + pop r15 + pop rsi + +.list_exec: + ; This point can be called to run a function + ; used by swap! + ; + ; Inputs: RAX - List with function as first element + ; NOTE: This list is released + ; + ; Check that the first element of the return is a function + mov bl, BYTE [rax] + and bl, content_mask + cmp bl, content_pointer + jne .list_not_function + + mov rbx, [rax + Cons.car] ; Get the address + mov cl, BYTE [rbx] + cmp cl, maltype_function + jne .list_not_function + + ; Check the rest of the args + mov cl, BYTE [rax + Cons.typecdr] + cmp cl, content_pointer + je .list_got_args + + ; No arguments + push rbx + call alloc_cons + mov [rax], BYTE maltype_empty_list + pop rbx + mov rsi, rax + jmp .list_function_call +.list_got_args: + mov rsi, [rax + Cons.cdr] ; Rest of list +.list_function_call: + ; Call the function with the rest of the list in RSI + + mov rdx, rax ; List to release + mov rdi, rbx ; Function object in RDI + + mov rbx, [rbx + Cons.car] ; Call function + cmp rbx, apply_fn + je apply_fn_jmp ; Jump to user function apply + + ; A built-in function, so call (no recursion) + push rax + push r15 + + call rbx + + ; Result in rax + pop r15 + pop rsi ; eval'ed list + + push rax + call release_cons + pop rax + jmp .return ; Releases Env + +.list_not_function: + ; Not a function. Probably an error + push rsi + + mov rsi, rax + call release_object + + print_str_mac error_string + print_str_mac eval_list_not_function + pop rsi + jmp error_throw + + +;; Applies a user-defined function +;; +;; Input: RSI - Arguments to bind +;; RDI - Function object +;; RDX - list to release after binding +;; R15 - Env (will be released) +;; R13 - AST released before return +;; +;; +;; Output: Result in RAX +;; +;; This is jumped to from eval, so if it returns +;; then it will return to the caller of eval, not to eval +apply_fn_jmp: + ; This is jumped to from eval with AST on the stack + pop r13 +apply_fn: + push rsi + ; Extract values from the list in RDI + mov rax, [rdi + Cons.cdr] + mov rsi, [rax + Cons.car] ; Env + mov rax, [rax + Cons.cdr] + mov rdi, [rax + Cons.car] ; Binds + mov rax, [rax + Cons.cdr] + mov rax, [rax + Cons.car] ; Body + pop rcx ; Exprs + + ; Check the type of the body + mov bl, BYTE [rax] + and bl, block_mask + container_mask + jnz .bind + ; Just a value (in RAX). No eval needed + + mov r14, rax ; Save return value in R14 + + mov rsi, rax + call incref_object + + ; Release the list passed in RDX + mov rsi, rdx + call release_object + + ; Release the environment + mov rsi, r15 + call release_object + + ; Release the AST + mov rsi, r13 + call release_object + + mov rax, r14 + ret +.bind: + ; Create a new environment, binding arguments + push rax ; Body + + mov r14, r13 ; Old AST. R13 used by env_new_bind + + push rdx + call env_new_bind + pop rdx + + mov rdi, rax ; New environment in RDI + + ; Note: Need to increment the reference count + ; of the function body before releasing anything, + ; since if the function was defined in-place (lambda) + ; then the body may be released early + + pop rsi ; Body + call incref_object ; Will be released by eval + mov r8, rsi ; Body in R8 + + ; Release the list passed in RDX + mov rsi, rdx + call release_cons + + ; Release the environment + mov rsi, r15 + call release_object + + ; Release the old AST + mov rsi, r14 + call release_object + + mov rsi, r8 ; Body + + jmp eval ; Tail call + ; The new environment (in RDI) will be released by eval + + +;; Set ZF if RSI is a non-empty list or vector +;; Modifies RAX, does not modify RSI +is_pair: + mov al, BYTE [rsi] + test al, block_mask + jnz .false ; Not a Cons + cmp al, maltype_empty_list + je .false ; Empty list + cmp al, maltype_empty_vector + je .false ; Empty vector + + ; Something non empty + and al, container_mask + cmp al, container_list + je .true + cmp al, container_vector + je .true + ; Not a list or vector -> false + +.false: + lahf ; flags in AH + and ah, 255-64 ; clear zero flag + sahf + ret +.true: + lahf ; flags in AH + or ah, 64 ; set zero flag + sahf + ret + +;; Called by eval with AST in RSI [ modified ] +;; Returns new AST in RAX +quasiquote: + ; i. Check if AST is an empty list + call is_pair + jne .quote_ast + + ; ii. Check if the first element of RSI is the symbol + ; 'unquote' + + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .not_unquote ; Not a pointer + + mov rdi, [rsi + Cons.car] ; Get the pointer + mov cl, BYTE [rdi] + cmp cl, maltype_symbol + jne .not_unquote + + ; Compare against 'unquote' + mov r8, rsi + mov r9, rax + + mov rsi, unquote_symbol + call compare_char_array + test rax, rax + + mov rax, r9 + mov rsi, r8 + + je .unquote + +.not_unquote: + ; iii. Handle splice-unquote + ; RSI -> ( ( splice-unquote ? ) ? ) + + ; Test if RSI contains a pointer + + cmp al, content_pointer + jne .not_splice + + mov rbx, [rsi + Cons.car] ; Get the object pointer + + ; RBX -> ( splice-unquote ? ) + + xchg rbx, rsi + call is_pair + xchg rbx, rsi + jne .not_splice ; First element not a pair + + ; Check if this list in RBX starts with 'splice-unquote' symbol + mov al, BYTE [rbx] + and al, content_mask + cmp al, content_pointer + jne .not_splice + + + mov rdi, [rbx + Cons.car] ; Get the pointer + mov al, BYTE [rdi] + cmp al, maltype_symbol + jne .not_splice + + mov r8, rsi + mov r9, rbx + + ; Compare against 'splice-unquote' + mov rsi, splice_unquote_symbol + call compare_char_array + test rax, rax + + mov rbx, r9 + mov rsi, r8 + + je .splice_unquote + +.not_splice: + + ; iv. Cons first and rest of AST in RSI + + ; check if pointer or value + mov cl, BYTE [rsi] + and cl, content_mask + cmp cl, content_pointer + je .cons_pointer + + ; a value, so copy + call alloc_cons + or cl, container_list + mov [rax], BYTE cl ; List + Content + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx + mov rcx, rax + jmp .cons_first + +.cons_pointer: + ; Get the pointer and call quasiquote + push rsi + mov rsi, [rsi + Cons.car] + call quasiquote + mov rcx, rax + pop rsi + + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rcx + mov rcx, rax + +.cons_first: + ; Have Cons with first object in RCX + + ; Call quasiquote on the rest of the AST + ; Check if this is the end of the list + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .cons_ast_end + + mov rsi, [rsi + Cons.cdr] ; Rest of the list + + call incref_object ; Will release after quasiquote call + + jmp .cons_quasiquote_ast + +.cons_ast_end: + ; End of the AST, so make an empty list + call alloc_cons + mov [rax], BYTE maltype_empty_list + mov rsi, rax + +.cons_quasiquote_ast: + push rcx + push rsi + call quasiquote + mov rdx, rax ; List in RDX + + pop rsi + call release_object ; Release input + + pop rcx ; Value in RCX + + ; cons RCX and RDX + ; Work from the end of the list to the front + + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rdx ; The rest of AST + + ; Link to the RCX Cons + mov [rcx + Cons.typecdr], BYTE content_pointer + mov [rcx + Cons.cdr], rax + mov rdx, rcx + + call alloc_cons ; Cons for cons symbol + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rdx + mov rdx, rax + + ; Get the cons symbol + mov rsi, cons_symbol + call incref_object + + mov [rdx], BYTE (container_list + content_pointer) + mov [rdx + Cons.car], rsi + + mov rax, rdx + ret + +.quote_ast: + ; Return (quote RSI) + + call incref_object ; RSI reference count + + ; Cons for RSI + call alloc_cons + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], rsi + mov rsi, rax + + ; Cons for quote symbol + call alloc_cons + mov rbx, rax + mov [rbx + Cons.typecdr], BYTE content_pointer + mov [rbx + Cons.cdr], rsi + + ; Get a quote symbol, incrementing references + mov rsi, quote_symbol + call incref_object + + ; Put into the Cons in RBX + mov [rbx + Cons.car], rsi + mov [rbx], BYTE (block_cons + container_list + content_pointer) + mov rax, rbx + ret + ; ----------------------- + +.unquote: + + ; Got unquote symbol. Return second element of RSI + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .empty_list ; No second element + + mov rsi, [rsi + Cons.cdr] + + ; Check if it's a value or pointer + mov cl, BYTE [rsi] + and cl, content_mask + cmp cl, content_pointer + je .unquote_pointer + + ; A value, so need a new Cons + call alloc_cons + mov [rax], BYTE cl ; content + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; Copy content + ret + +.unquote_pointer: + mov rsi, [rsi + Cons.car] + call incref_object + mov rax, rsi + ret + + ; ----------------------- +.splice_unquote: + ; RSI -> ( RBX->( splice-unquote A ) B ) + ; + ; RBX Car points to splice-unquote symbol + + ; Check if there is anything after the symbol + mov al, BYTE [rbx + Cons.typecdr] + cmp al, content_pointer + jne .splice_unquote_empty + + ; Point to the second element of the splice-unquote list + mov rcx, [rbx + Cons.cdr] + + ; Check whether it's a value or pointer + mov al, BYTE [rcx] + and al, content_mask + cmp al, content_pointer + je .splice_unquote_pointer + + ; A value, so change the container to a value + mov [rcx], BYTE al + ; Remove pointer from RBX + mov [rbx + Cons.typecdr], BYTE 0 + jmp .splice_unquote_first ; Got the value in RCX + +.splice_unquote_pointer: + mov rcx, [rcx + Cons.car] ; Get the object pointed to + xchg rcx, rsi + call incref_object + xchg rcx, rsi ; Object in RCX + +.splice_unquote_first: ; Got the first object in RCX + + ; Check if RSI contains anything else + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .splice_unquote_notail + + mov rsi, [rsi + Cons.cdr] + + ; Now have: + ; ( ( splice-unquote A ) B ) + ; RCX->A RSI->( B ) + ; Need to call quasiquote on the rest of the list + push rcx + call quasiquote + mov rdx, rax + pop rcx + ; Need to concat rcx and rdx + ; Work from the end of the list to the front + + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rdx ; The rest of AST + mov rdx, rax ; Push list into RDX + + call alloc_cons + mov [rax], BYTE (container_list + content_pointer) + mov [rax + Cons.car], rcx ; The splice-unquote object + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rdx + mov rdx, rax + + call alloc_cons ; Cons for concat symbol + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rdx + mov rdx, rax + + ; Get the concat symbol + mov rsi, concat_symbol + call incref_object + + mov [rdx], BYTE (container_list + content_pointer) + mov [rdx + Cons.car], rsi + + mov rax, rdx + ret + +.splice_unquote_notail: + ; Just return the object in RCX + ; since nothing to concatenate with + mov rax, rcx + ret + +.splice_unquote_empty: + ; Nothing in the (splice-unquote) list, so ignore + ; Just call quasiquote on the rest of RSI + + mov al, BYTE [rsi + Cons.typecdr] + cmp al, content_pointer + jne .empty_list ; Nothing else + + mov rsi, [rsi + Cons.cdr] + jmp quasiquote ; Tail call + +.empty_list: + ; Return an empty list + call alloc_cons + mov [rax], BYTE maltype_empty_list +.return: + ret + + +;; Tests if an AST in RSI is a list containing +;; a macro defined in the ENV in R15 +;; +;; Inputs: AST in RSI (not modified) +;; ENV in R15 (not modified) +;; +;; Returns: Sets ZF if macro call. If set (true), +;; then the macro object is in RAX +;; +;; Modifies: +;; RAX +;; RBX +;; RCX +;; RDX +;; R8 +;; R9 +is_macro_call: + ; Test if RSI is a list which contains a pointer + mov al, BYTE [rsi] + cmp al, (block_cons + container_list + content_pointer) + jne .false + + ; Test if this is a symbol + mov rbx, [rsi + Cons.car] + mov al, BYTE [rbx] + cmp al, maltype_symbol + jne .false + + ; Look up symbol in Env + push rsi + push r15 + mov rdi, rbx ; symbol in RDI + mov rsi, r15 ; Environment in RSI + call env_get + pop r15 + pop rsi + jne .false ; Not in environment + + ; Object in RAX + ; If this is not a macro then needs to be released + mov dl, BYTE [rax] + + cmp dl, maltype_macro + je .true + + ; Not a macro, so release + mov r8, rsi + mov rsi, rax + call release_object + mov rsi, r8 + +.false: + lahf ; flags in AH + and ah, 255-64 ; clear zero flag + sahf + ret +.true: + mov rbx, rax ; Returning Macro object + lahf ; flags in AH + or ah, 64 ; set zero flag + sahf + mov rax, rbx + ret + +;; Expands macro calls +;; +;; Input: AST in RSI (released and replaced) +;; Env in R15 (not modified) +;; +;; Result: New AST in RSI +macroexpand: + push r15 + + call is_macro_call + jne .done + + mov r13, rsi + + mov rdi, rax ; Macro in RDI + + ; Check the rest of the args + mov cl, BYTE [rsi + Cons.typecdr] + cmp cl, content_pointer + je .got_args + + ; No arguments. Create an empty list + call alloc_cons + mov [rax], BYTE maltype_empty_list + mov rdx, rax + + mov rsi, rdx ; Arguments (empty list) + call incref_object + jmp .macro_call +.got_args: + mov rsi, [rsi + Cons.cdr] ; Rest of list + call incref_object + mov rdx, rsi ; Released +.macro_call: + ; Here have: + ; RSI - Arguments + ; RDI - Macro object + ; RDX - List to release + ; R15 - Environment + ; R13 - AST + + ; Increment reference for Environment + ; since this will be released by apply_fn + xchg rsi, r15 + call incref_object + xchg rsi, r15 + + call apply_fn + + mov rsi, rax ; Result in RSI + + pop r15 + jmp macroexpand +.done: + pop r15 + ret + +;; Read-Eval-Print in sequence +;; +;; Input string in RSI +rep_seq: + ; ------------- + ; Read + call read_str + + ; ------------- + ; Eval + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call eval ; This releases Env and Form/AST + push rax ; Save result of eval + + ; ------------- + ; Print + + ; Put into pr_str + mov rsi, rax + mov rdi, 1 ; print_readably + call pr_str + push rax ; Save output string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from pr_str + pop rsi + call release_array + + ; Release result of eval + pop rsi + call release_object + + ; The AST from read_str is released by eval + + ret + + +_start: + ; Create and print the core environment + call core_environment ; Environment in RAX + + mov [repl_env], rax ; store in memory + + ; Set the error handler + mov rsi, rsp ; Stack pointer + mov rdi, .catch ; Address to jump to + xor rcx, rcx ; No data + call error_handler_push + + ; Evaluate the startup string + + mov rsi, mal_startup_string + mov edx, mal_startup_string.len + call raw_to_string ; String in RAX + + push rax + mov rsi, rax + call read_str ; AST in RAX + pop rsi ; string + + push rax ; AST + call release_array ; string + pop rdi ; AST in RDI + + mov rsi, [repl_env] ; Environment in RSI + + call incref_object ; Environment increment refs + xchg rsi, rdi ; Env in RDI, AST in RSI + + call eval + + mov rsi, rax + call release_object ; Return from eval + + ; ----------------------------- + ; Check command-line arguments + + pop rax ; Number of arguments + cmp rax, 1 ; Always have at least one, the path to executable + jg run_script + + ; No extra arguments, so just set *ARGV* to an empty list + call alloc_cons ; in RAX + mov [rax], BYTE maltype_empty_list + mov rcx, rax ; value (empty list) + mov rdi, argv_symbol ; symbol (*ARGV*) + mov rsi, [repl_env] ; environment + call env_set + + ; ----------------------------- + ; Header + + load_static mal_startup_header + call raw_to_string + push rax + + mov rsi, rax + call rep_seq + + ; Release the input string + pop rsi + call release_array + + ; ----------------------------- + ; Main loop + +.mainLoop: + ; print the prompt + print_str_mac prompt_string + + call read_line + + ; Check if we have a zero-length string + cmp DWORD [rax+Array.length], 0 + je .mainLoopEnd + + push rax ; Save address of the input string + + ; Put into read_str + mov rsi, rax + call rep_seq + + ; Release the input string + pop rsi + call release_array + + jmp .mainLoop +.mainLoopEnd: + + jmp quit + +.catch: + ; Jumps here on error + + ; Check if an object was thrown + cmp rsi, 0 + je .catch_done_print ; nothing to print + mov rdi, 1 + call pr_str + mov rsi, rax + call print_string +.catch_done_print: + jmp .mainLoop ; Go back to the prompt + + + +run_script: + ; Called with number of command-line arguments in RAX + mov r8, rax + pop rbx ; executable + dec r8 + + pop rsi ; Address of first arg + call cstring_to_string ; string in RAX + mov r9, rax + + ; get the rest of the args + xor r10, r10 ; Zero + dec r8 + jz .no_args + + ; Got some arguments +.arg_loop: + ; Got an argument left. + pop rsi ; Address of C string + call cstring_to_string ; String in RAX + mov r12, rax + + ;Make a Cons to point to the string + call alloc_cons ; in RAX + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], r12 + + test r10, r10 + jnz .append + + ; R10 zero, so first arg + mov r10, rax ; Head of list + mov r11, rax ; Tail of list + jmp .next +.append: + ; R10 not zero, so append to list tail + mov [r11 + Cons.cdr], rax + mov [r11 + Cons.typecdr], BYTE content_pointer + mov r11, rax +.next: + dec r8 + jnz .arg_loop + jmp .got_args + +.no_args: + ; No arguments. Create an emoty list + call alloc_cons ; in RAX + mov [rax], BYTE maltype_empty_list + mov r10, rax + +.got_args: + push r9 ; File name string + + mov rcx, r10 ; value (list) + mov rdi, argv_symbol ; symbol (*ARGV*) + mov rsi, [repl_env] ; environment + call env_set + + mov rsi, run_script_string ; load-file function + mov edx, run_script_string.len + call raw_to_string ; String in RAX + + mov rsi, rax + pop rdx ; File name string + call string_append_string + + mov cl, 34 ; " + call string_append_char + mov cl, ')' + call string_append_char ; closing brace + + ; Read-Eval-Print "(load-file )" + call rep_seq + + jmp quit From 1f254872a3db1ee2944f1efff8a2ee7f8ffaabd7 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 12 Dec 2017 23:49:07 +0000 Subject: [PATCH 0629/2308] Add meta, with-meta and ^ reader Native and user-defined functions now have metadata, which can be set using with-data, and retrieved using meta. The ^ reader converts "^ A B" into (with-meta B A) --- nasm/core.asm | 151 +++++++++++++++++++++++++++++++++++++++++++++ nasm/reader.asm | 62 ++++++++++++++++++- nasm/stepA_mal.asm | 16 ++++- nasm/types.asm | 21 +++++-- 4 files changed, 241 insertions(+), 9 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 5c8eb564e4..1132b775ff 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -80,6 +80,9 @@ section .data static core_dissoc_symbol, db "dissoc" static core_readline_symbol, db "readline" + + static core_meta_symbol, db "meta" + static core_with_meta_symbol, db "with-meta" ;; Strings @@ -144,6 +147,9 @@ section .data static core_dissoc_not_map, db "dissoc expects a map as first argument" static core_dissoc_missing_value, db "Missing value in map passed to dissoc" + + static core_with_meta_no_function, db "with-meta expects a function as first argument" + static core_with_meta_no_value, db "with-meta expects a value as second argument" section .text @@ -251,6 +257,9 @@ core_environment: core_env_native core_dissoc_symbol, core_dissoc core_env_native core_readline_symbol, core_readline + + core_env_native core_meta_symbol, core_meta + core_env_native core_with_meta_symbol, core_with_meta ; ----------------- ; Put the environment in RAX @@ -2902,3 +2911,145 @@ core_readline: call alloc_cons mov [rax], BYTE maltype_nil ret + + +;; Return the meta data associated with a given function +core_meta: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .return_nil + + mov rsi, [rsi + Cons.car] + mov al, BYTE [rsi] + cmp al, (block_cons + container_function + content_function) + jne .return_nil + + ; Here got a function + mov rsi, [rsi + Cons.cdr] + + ; RSI should now contain the meta data + mov cl, BYTE [rsi] + and cl, content_mask + cmp cl, content_pointer + je .pointer + + ; A value, so copy + call alloc_cons + mov [rax], BYTE cl + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx + ret + +.pointer: + ; A pointer, so increment reference count and return + mov rsi, [rsi + Cons.car] + call incref_object + mov rax, rsi + ret + +.return_nil: + call alloc_cons + mov [rax], BYTE maltype_nil + ret + + +;; Associates a value with a function (native or user) +core_with_meta: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + jne .no_function + + mov r8, [rsi + Cons.car] ; Function in R8 + mov al, BYTE [r8] + cmp al, (block_cons + container_function + content_function) + jne .no_function + + mov bl, BYTE [rsi + Cons.typecdr] + cmp bl, content_pointer + jne .no_value + + mov rsi, [rsi + Cons.cdr] + + ; Function in R8, new value in RSI + + call alloc_cons + mov [rax], BYTE (block_cons + container_function + content_function) ; Type + mov rbx, [r8 + Cons.car] + mov [rax + Cons.car], rbx ; Function address + + mov r10, rax ; Return address + + ; Copy the meta data + + mov r8, [r8 + Cons.cdr] ; R8 now old meta data (not used) + + call alloc_cons + + mov cl, BYTE [rsi] + and cl, content_mask + mov ch, cl + or cl, container_function + mov [rax], BYTE cl ; Set type + + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; Copy value + + ; append to function + mov [r10 + Cons.typecdr], BYTE content_pointer + mov [r10 + Cons.cdr], rax + mov r11, rax + + ; Check if meta is a value or pointer + cmp ch, content_pointer + jne .copy_rest + + ; increment reference count of meta + mov cx, WORD [rbx + Cons.refcount] + inc cx + mov [rbx + Cons.refcount], WORD cx + +.copy_rest: + ; Copy remainder of function (if any) + ; If a user function, has (env binds body) + mov al, [r8 + Cons.typecdr] + cmp al, content_pointer + jne .done + + ; Still more to copy + mov r8, [r8 + Cons.cdr] + + call alloc_cons + mov bl, BYTE [r8] + mov [rax], BYTE bl ; Copy type + mov rcx, [r8 + Cons.car] + mov [rax + Cons.car], rcx ; Copy value + + ; append + mov [r11 + Cons.typecdr], BYTE content_pointer + mov [r11 + Cons.cdr], rax + mov r11, rax + + ; Check if it's a pointer + and bl, content_mask + cmp bl, content_pointer + jne .copy_rest + + ; a pointer, so increment reference count + mov bx, WORD [rcx + Cons.refcount] + inc bx + mov [rcx + Cons.refcount], WORD bx + jmp .copy_rest + +.done: + mov rax, r10 + ret + +.no_function: + load_static core_with_meta_no_function + jmp core_throw_str + +.no_value: + load_static core_with_meta_no_value + jmp core_throw_str diff --git a/nasm/reader.asm b/nasm/reader.asm index 57c8aa92d2..8c040b5418 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -9,7 +9,7 @@ section .data static unquote_symbol_string, db "unquote" static splice_unquote_symbol_string, db "splice-unquote" static deref_symbol_string, db "deref" - + static with_meta_symbol_string, db "with-meta" ;; Error message strings @@ -113,6 +113,9 @@ read_str: cmp cl, '@' je .handle_deref + cmp cl, '^' + je .handle_with_meta + ; Unknown jmp .return_nil @@ -523,7 +526,7 @@ read_str: pop r9 pop r8 jmp .wrap_next_object ; From there the same as handle_quote - + ; -------------------------------- .handle_deref: @@ -541,6 +544,61 @@ read_str: pop r9 pop r8 jmp .wrap_next_object ; From there the same as handle_quote + + ; -------------------------------- + +.handle_with_meta: + ; Turn ^ a b into (with-meta b a) + + call alloc_cons ; Address in rax + mov r12, rax + + ; Get a symbol "with-meta" + push r8 + push r9 + mov rsi, with_meta_symbol_string + mov edx, with_meta_symbol_string.len + call raw_to_string ; Address in rax + pop r9 + pop r8 + + mov [rax], BYTE maltype_symbol + mov [r12], BYTE (block_cons + container_list + content_pointer) + mov [r12 + Cons.car], rax + + ; Get the next two objects + push r12 + call .read_loop ; object in rax + pop r12 + push rax + push r12 + call .read_loop ; in RAX + pop r12 + + mov r13, rax + + call alloc_cons ; Address in rax + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov [rax + Cons.car], r13 + + ; Cons object in rax. Append to object in r12 + mov [r12 + Cons.typecdr], BYTE content_pointer + mov [r12 + Cons.cdr], rax + + mov r13, rax + + call alloc_cons ; Address in rax + mov [rax], BYTE (block_cons + container_list + content_pointer) + + pop rdi ; First object + mov [rax + Cons.car], rdi + + ; Append to object in R13 + mov [r13 + Cons.typecdr], BYTE content_pointer + mov [r13 + Cons.cdr], rax + + mov rax, r12 + ret ; -------------------------------- .symbol: diff --git a/nasm/stepA_mal.asm b/nasm/stepA_mal.asm index 0d0d9bb8ef..fff7f55972 100644 --- a/nasm/stepA_mal.asm +++ b/nasm/stepA_mal.asm @@ -1471,19 +1471,32 @@ eval: mov [rax + Cons.typecdr], BYTE content_pointer mov r13, rax ; Return list in R13 + + ; Meta + + call alloc_cons + mov [rax], BYTE maltype_nil + mov [rax + Cons.typecdr], BYTE content_pointer + + mov [r13 + Cons.cdr], rax ; Append + mov r14, rax + + ; Env call alloc_cons mov [rax], BYTE (block_cons + container_function + content_pointer) mov [rax + Cons.car], r15 ; Environment mov [rax + Cons.typecdr], BYTE content_pointer - mov [r13 + Cons.cdr], rax ; Append to list + mov [r14 + Cons.cdr], rax ; Append to list mov r14, rax push rax mov rsi, r15 call incref_object pop rax + + ; Binds call alloc_cons mov [rax], BYTE (block_cons + container_function + content_pointer) @@ -1909,6 +1922,7 @@ apply_fn: push rsi ; Extract values from the list in RDI mov rax, [rdi + Cons.cdr] + mov rax, [rax + Cons.cdr] ; Meta (don't need) mov rsi, [rax + Cons.car] ; Env mov rax, [rax + Cons.cdr] mov rdi, [rax + Cons.car] ; Binds diff --git a/nasm/types.asm b/nasm/types.asm index 445bfabc31..bd6a682766 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -1913,18 +1913,27 @@ map_vals: ;; ;; Functions are consist of a list ;; - First car is the function address to call -;; - Second is the environment -;; - Third is the binds list -;; - Fourth is the body of the function +;; - Second is the Meta data (nil by default) +;; - Third is the environment +;; - Fourth is the binds list +;; - Fifth is the body of the function ;; -;; ( addr env binds body ) +;; ( addr meta env binds body ) ;; ;; ;; Address of native function in RSI ;; returns Function object in RAX -native_function: - call alloc_cons +native_function: + call alloc_cons ; for meta + mov [rax], BYTE maltype_nil + push rax + + call alloc_cons ; For function address mov [rax], BYTE (block_cons + container_function + content_function) mov [rax + Cons.car], rsi + + mov [rax + Cons.typecdr], BYTE content_pointer + pop rbx ; meta + mov [rax + Cons.cdr], rbx ret From 0119b269f2be6b921d6e15cce1257bbdbdbb23d2 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 12 Dec 2017 23:50:34 +0000 Subject: [PATCH 0630/2308] Adding gensym, clean or macro Replacement for earlier macro, now using gensym --- nasm/stepA_mal.asm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nasm/stepA_mal.asm b/nasm/stepA_mal.asm index fff7f55972..39ca481fc3 100644 --- a/nasm/stepA_mal.asm +++ b/nasm/stepA_mal.asm @@ -80,7 +80,7 @@ section .data ; ;; Startup string. This is evaluated on startup - static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) (defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw ",34,"odd number of forms to cond",34,")) (cons 'cond (rest (rest xs))))))) (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs)))))))) (def! *host-language* ",34,"nasm",34,") )" + static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) (defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw ",34,"odd number of forms to cond",34,")) (cons 'cond (rest (rest xs))))))) (def! *gensym-counter* (atom 0)) (def! gensym (fn* [] (symbol (str ",34,"G__",34," (swap! *gensym-counter* (fn* [x] (+ 1 x))))))) (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs))))))))) (def! *host-language* ",34,"nasm",34,") )" ;; Command to run, appending the name of the script to run static run_script_string, db "(load-file ",34 From a22fcc1b0df51046d839deaabd3c0bae7695e514 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 13 Dec 2017 00:06:17 +0000 Subject: [PATCH 0631/2308] Fix small bug in comparison Comparing maps clobbered the block type, needed when comparing array types --- nasm/types.asm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nasm/types.asm b/nasm/types.asm index bd6a682766..e86e88a4f1 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -1024,8 +1024,8 @@ compare_objects: ; May be different container (value/list, string/symbol) ; Need to distinguish between map and vector/list - and ch, container_mask - and bh, container_mask + and ch, (block_mask + container_mask) + and bh, (block_mask + container_mask) cmp ch, bh je .same_container ; if either is a map, then different types @@ -1098,8 +1098,8 @@ compare_objects_rec: mov ah, al mov bh, bl - and ah, container_mask - and bh, container_mask + and ah, (block_mask + container_mask) + and bh, (block_mask + container_mask) cmp ah, bh je .same_container ; if either is a map, then different types @@ -1131,7 +1131,7 @@ compare_objects_rec: .array: ; Comparing arrays - + ; Container type (symbol/string) does matter cmp al, bl jne .false From 6b47dc878fbcbedd858bb6b4aec43c7f0449c1b3 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 13 Dec 2017 23:45:50 +0000 Subject: [PATCH 0632/2308] Fix bug in concat. MAL step 2 now passes Wasn't incrementing reference count when copying content of a vector single argument. --- nasm/core.asm | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nasm/core.asm b/nasm/core.asm index 1132b775ff..f01bd89c8b 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -1648,6 +1648,16 @@ core_concat: mov rbx, [rsi + Cons.car] mov [rax + Cons.car], rbx ; Set content + ; Check if CAR is a pointer + cmp cl, (container_list + content_pointer) + jne .single_done_car + + ; a pointer, so increment reference count + mov cx, WORD [rbx + Cons.refcount] + inc cx + mov [rbx + Cons.refcount], WORD cx + +.single_done_car: mov dl, BYTE [rsi + Cons.typecdr] mov [rax + Cons.typecdr], BYTE dl ; CDR type From 524c684990e4d6c14c087e99d697c4fed47dfe53 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 14 Dec 2017 23:06:19 +0000 Subject: [PATCH 0633/2308] Fix bug in rest and println * rest wasn't incrementing reference count of CAR pointer since cl was overwritten by a write to RCX * println wasn't putting a space separator between items MAL self-hosting now runs to step 6. Fails since (time-ms) not defined. --- nasm/core.asm | 38 ++++++++++++++++++++++++-------------- nasm/printer.asm | 6 +++--- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index f01bd89c8b..279ef95656 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -887,10 +887,14 @@ core_hashmap: ;; Convert arguments to a readable string, separated by a space ;; core_pr_str: - mov rdi, 1 ; print_readably + mov rdi, 3 ; print_readably & separator jmp core_str_functions core_str: xor rdi, rdi + jmp core_str_functions +core_str_sep: + mov rdi, 2 ; separator + core_str_functions: mov al, BYTE [rsi] mov ah, al @@ -972,8 +976,8 @@ core_str_functions: ; More inputs mov rsi, [rsi + Cons.cdr] ; pointer - cmp rdi, 0 ; print_readably - je .end_append_char ; No separator if not printing readably + test rdi, 2 ; print_readably + jz .end_append_char ; No separator ; Add separator push r8 @@ -1005,7 +1009,7 @@ core_prn: call core_pr_str jmp core_prn_functions core_println: - call core_str + call core_str_sep core_prn_functions: mov rsi, rax @@ -1865,7 +1869,7 @@ core_rest: ; Get the list mov rsi, [rsi + Cons.car] - + mov al, BYTE [rsi] ; Check for nil @@ -1880,7 +1884,7 @@ core_rest: jne .not_list ; Not a list or vector .got_list: - ; Check if list is empty + ; Check if list or vector is empty and al, content_mask cmp al, content_empty je .empty_list @@ -1897,9 +1901,11 @@ core_rest: ret .return_rest: - + mov rsi, [rsi + Cons.cdr] + + ; Check if this is a list or a vector mov cl, BYTE [rsi] mov ch, cl @@ -1917,25 +1923,29 @@ core_rest: mov dl, BYTE [rsi + Cons.typecdr] ; CDR type in DL mov [rax + Cons.typecdr], BYTE dl - ; Copy content of CAR and CDR + ; Copy content of CAR mov rbx, [rsi + Cons.car] mov [rax + Cons.car], rbx - - mov rcx, [rsi + Cons.cdr] - mov [rax + Cons.cdr], rcx ; Note: Might be pointer - + ; Check if car contains a pointer cmp ch, content_pointer jne .check_cdr - + ; CAR contains a pointer, so increment reference count mov r8, rax ; Save return Cons + mov r9, rsi ; Save input list mov rsi, rbx ; Content of CAR call incref_object mov rax, r8 ; Restore return Cons + mov rsi, r9 ; Restore input list + +.check_cdr: + ; Copy content of CDR + + mov rcx, [rsi + Cons.cdr] + mov [rax + Cons.cdr], rcx ; Note: Might be pointer -.check_cdr: ; Check if cdr contains a pointer cmp dl, content_pointer jne .return ; Not a pointer, so just return diff --git a/nasm/printer.asm b/nasm/printer.asm index 21e72ee7a8..8973f908b3 100644 --- a/nasm/printer.asm +++ b/nasm/printer.asm @@ -18,7 +18,7 @@ section .data section .text ;; Input: Address of object in RSI -;; print_readably in RDI. Set to zero for false +;; print_readably in RDI. First bit set to zero for false ;; ;; Output: Address of string in RAX ;; @@ -44,8 +44,8 @@ pr_str: ; --------------------------- ; Handle string - cmp rdi, 0 - je .string_not_readable + test rdi, 1 + jz .string_not_readable ; printing readably, so escape characters From dfefe35bd9b6bc6ff1acd17abaa3cd524acdd0ad Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 15 Dec 2017 23:11:20 +0000 Subject: [PATCH 0634/2308] Add time-ms function Uses clock_gettime (syscall 228) to get time in seconds and nanoseconds, then converts to ms. --- nasm/core.asm | 16 ++++++++++++++++ nasm/system.asm | 27 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/nasm/core.asm b/nasm/core.asm index 279ef95656..2a3401829a 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -83,6 +83,8 @@ section .data static core_meta_symbol, db "meta" static core_with_meta_symbol, db "with-meta" + + static core_time_ms_symbol, db "time-ms" ;; Strings @@ -260,6 +262,8 @@ core_environment: core_env_native core_meta_symbol, core_meta core_env_native core_with_meta_symbol, core_with_meta + + core_env_native core_time_ms_symbol, core_time_ms ; ----------------- ; Put the environment in RAX @@ -3073,3 +3077,15 @@ core_with_meta: .no_value: load_static core_with_meta_no_value jmp core_throw_str + + +;; Returns the current time in ms +core_time_ms: + call clock_time_ms + mov rsi, rax + + call alloc_cons + mov [rax], BYTE maltype_integer + mov [rax + Cons.car], rsi + ret + diff --git a/nasm/system.asm b/nasm/system.asm index de4d793bef..e22ae13750 100644 --- a/nasm/system.asm +++ b/nasm/system.asm @@ -6,6 +6,10 @@ section .data static error_open_file_string, db "Error opening file " static error_read_file_string, db "Error reading file " + +section .bss + +timespec: RESQ 2 section .text @@ -204,3 +208,26 @@ read_file: call raw_to_string mov rsi, rax jmp error_throw + + + +;; Returns the time in ms in RAX +clock_time_ms: + mov rax, 228 ; clock_gettime + mov rdi, 0 ; CLOCK_REALTIME + mov rsi, timespec + syscall + + mov rax, [timespec + 8] ; nanoseconds + cqo ; Sign extend RAX into RDX + mov rcx, 1000000 + idiv rcx ; Divide RAX by 1e6 -> ms + mov rbx, rax + ; -> ms in RBX + + mov rax, [timespec] ; Seconds + mov rcx, 1000 + imul rcx ; Convert to ms + add rax, rbx ; Add RBX + + ret From ccd081039ea19d4745868aed265bcc6b42c4e636 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 15 Dec 2017 23:56:49 +0000 Subject: [PATCH 0635/2308] Fix list and repl output * (list) wasn't always incrementing reference counts when needed * The value from eval of a file shouldn't be printed, so defined a small routine read_eval which doesn't print the final value. Self-hosting now runs to step8, where two tests of the -> macro fail. --- nasm/core.asm | 12 +++++++----- nasm/stepA_mal.asm | 28 ++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 2a3401829a..66b3cb1934 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -797,10 +797,6 @@ core_list: call incref_object mov rax, rsi ret - -.not_seq: - load_static core_list_not_seq - jmp core_throw_str ;; Convert arguments into a vector core_vector: @@ -2432,8 +2428,14 @@ core_apply: mov cl, al and al, container_mask cmp al, container_list - je .run + jne .last_convert_to_list + + ; Already a list, just increment reference count + mov rsi, r9 + call incref_object + jmp .run +.last_convert_to_list: ; Convert vector to list by copying first element call alloc_cons diff --git a/nasm/stepA_mal.asm b/nasm/stepA_mal.asm index 39ca481fc3..b742f96134 100644 --- a/nasm/stepA_mal.asm +++ b/nasm/stepA_mal.asm @@ -80,7 +80,7 @@ section .data ; ;; Startup string. This is evaluated on startup - static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) (defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw ",34,"odd number of forms to cond",34,")) (cons 'cond (rest (rest xs))))))) (def! *gensym-counter* (atom 0)) (def! gensym (fn* [] (symbol (str ",34,"G__",34," (swap! *gensym-counter* (fn* [x] (+ 1 x))))))) (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs))))))))) (def! *host-language* ",34,"nasm",34,") )" + static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) (defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw ",34,"odd number of forms to cond",34,")) (cons 'cond (rest (rest xs))))))) (def! *gensym-counter* (atom 0)) (def! gensym (fn* [] (symbol (str ",34,"G__",34," (swap! *gensym-counter* (fn* [x] (+ 1 x))))))) (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs))))))))) (def! *host-language* ",34,"nasm",34,") (def! conj nil) (def! seq nil) )" ;; Command to run, appending the name of the script to run static run_script_string, db "(load-file ",34 @@ -2471,6 +2471,24 @@ macroexpand: .done: pop r15 ret + +;; Read and eval +read_eval: + ; ------------- + ; Read + call read_str + + ; ------------- + ; Eval + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + jmp eval ; This releases Env and Form/AST + ;; Read-Eval-Print in sequence ;; @@ -2577,7 +2595,9 @@ _start: push rax mov rsi, rax - call rep_seq + call read_eval ; no print ('nil') + mov rsi, rax + call release_object ; Release result of eval ; Release the input string pop rsi @@ -2697,7 +2717,7 @@ run_script: mov cl, ')' call string_append_char ; closing brace - ; Read-Eval-Print "(load-file )" - call rep_seq + ; Read-Eval "(load-file )" + call read_eval jmp quit From ef2d054491729b5f8546d00d05bee426a0761e10 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 16 Dec 2017 23:52:59 +0000 Subject: [PATCH 0636/2308] Fix bug in concat, add test to step 7 Concatenating multiple empty lists resulted in a return which should be empty but was not checked. (concat (list) (list)) -> () Test added to step 7 to catch, since this appeared in self-hosting step 8 in the -> macro expansion. --- nasm/core.asm | 9 +++++++++ tests/step7_quote.mal | 2 ++ 2 files changed, 11 insertions(+) diff --git a/nasm/core.asm b/nasm/core.asm index 66b3cb1934..92e3aa0670 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -1754,6 +1754,10 @@ core_concat: mov [r11 + Cons.cdr], rsi mov [r11 + Cons.typecdr], BYTE content_pointer .done: + ; Check there is anything to return + test r11, r11 + jz .empty_list + ; Make sure that return is a list mov bl, BYTE [r12] and bl, content_mask @@ -1762,6 +1766,11 @@ core_concat: mov rax, r12 ; output list ret + +.empty_list: + call alloc_cons + mov [rax], BYTE maltype_empty_list + ret .missing_args: ; Return empty list diff --git a/tests/step7_quote.mal b/tests/step7_quote.mal index 973acbc743..4f3e3569ba 100644 --- a/tests/step7_quote.mal +++ b/tests/step7_quote.mal @@ -25,6 +25,8 @@ a ;=>(1 2 3 4 5 6) (concat (concat)) ;=>() +(concat (list) (list)) +;=>() (def! a (list 1 2)) (def! b (list 3 4)) From 7eb930db461bb3382361350471feccf94e669485 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 18 Dec 2017 23:25:59 +0000 Subject: [PATCH 0637/2308] Fix bugs in try*/catch* Wasn't pushing Env, so wasn't releasing correctly. Resulted in errors when in nested expressions. Catch was also not popping the exception handler, so repeated exceptions would not be handled correctly. --- nasm/stepA_mal.asm | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/nasm/stepA_mal.asm b/nasm/stepA_mal.asm index b742f96134..41fdcdc44b 100644 --- a/nasm/stepA_mal.asm +++ b/nasm/stepA_mal.asm @@ -1732,7 +1732,8 @@ eval: push R9 push R10 - + push r15 ; Env + ; Set the error handler mov rsi, rsp ; Stack pointer mov rdi, .catch ; Address to jump to @@ -1747,12 +1748,14 @@ eval: mov rsi, r8 ; The form to evaluate (A) call incref_object ; AST released by eval - + call eval + mov r8, rax ; Result in R8 + pop r15 ; Environment ; Discard B and C - ;add rsi, 8 ; pop R10 and R9 + ;add rsi, 8 ; pop R10 and R9 pop r10 pop r9 @@ -1765,7 +1768,12 @@ eval: ; Jumps here on error ; Value thrown in RSI ; - + + push rsi + call error_handler_pop + pop rsi + + pop r15 ; Env pop r12 ; B (symbol to bind) pop r13 ; C (form to evaluate) @@ -1799,9 +1807,11 @@ eval: mov rdi, rsi ; Env in RDI (will be released) mov rsi, [r13 + Cons.car] ; Form to evaluate call incref_object ; will be released - - call eval + push r15 + call eval + pop r15 + jmp .return .try_missing_catch: From 7f5572b0a60d4c3a81ee57be57d46f86d52ac27b Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 20 Dec 2017 23:02:02 +0000 Subject: [PATCH 0638/2308] Fix bug in apply, tests for double-free errors * Apply didn't increment reference count when appending a list to the end of the arguments list * Added checks in release_cons and release_array, to ensure that objects are not released more than once. --- nasm/core.asm | 5 ++++- nasm/types.asm | 33 +++++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 92e3aa0670..d7c80428f4 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -1802,7 +1802,7 @@ core_first: cmp al, content_pointer jne .not_list - + ; Get the list mov rsi, [rsi + Cons.car] @@ -2272,7 +2272,9 @@ core_map: push r8 push r9 push r10 + push r15 call apply_fn ; Result in RAX + pop r15 pop r10 pop r9 pop r8 @@ -2486,6 +2488,7 @@ core_apply: ; Append RSI to the end of the list [R9]...[R10] mov [r10 + Cons.typecdr], BYTE content_pointer mov [r10 + Cons.cdr], rsi + call incref_object .run: ; Have arguments list in R9 diff --git a/nasm/types.asm b/nasm/types.asm index e86e88a4f1..1bd6bda624 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -144,6 +144,9 @@ section .data static error_msg_print_string, db "Error in print string",10 static error_array_memory_limit, db "Error: Run out of memory for Array objects. Increase heap_array_limit.",10 static error_cons_memory_limit, db "Error: Run out of memory for Cons objects. Increase heap_cons_limit.",10 + + static error_cons_double_free, db "Error: double free error releasing Cons" + static error_array_double_free, db "Error: double free error releasing Array" ;; ------------------------------------------ ;; Memory management @@ -155,12 +158,12 @@ section .data ;; is free'd it is pushed onto the heap_x_free list. -%define heap_cons_limit 2000 ; Number of cons objects which can be created +%define heap_cons_limit 5000 ; Number of cons objects which can be created heap_cons_next: dd heap_cons_store ; Address of next cons in memory heap_cons_free: dq 0 ; Address of start of free list -%define heap_array_limit 1000 ; Number of array objects which can be created +%define heap_array_limit 2000 ; Number of array objects which can be created heap_array_next: dd heap_array_store heap_array_free: dq 0 @@ -229,6 +232,10 @@ alloc_array: ;; onto the free list release_array: mov ax, WORD [rsi + Array.refcount] + + ; Check if reference count is already zero + test ax,ax + jz .double_free dec ax mov WORD [rsi + Array.refcount], ax @@ -254,6 +261,11 @@ release_array: call release_array ret +.double_free: + load_static error_cons_double_free + call print_rawstring + jmp error_throw + ;; ------------------------------------------ ;; Cons alloc_cons() ;; @@ -314,6 +326,11 @@ alloc_cons: ;; release_cons: mov ax, WORD [rsi + Cons.refcount] + + ; Check if already released + test ax,ax + jz .double_free + dec ax mov WORD [rsi + Cons.refcount], ax jz .free ; If the count reaches zero then put on free list @@ -354,6 +371,10 @@ release_cons: .done: ret +.double_free: ; Already released + load_static error_cons_double_free + call print_rawstring + jmp error_throw ;; Releases either a Cons or Array ;; Address of object in RSI @@ -367,12 +388,8 @@ release_object: mov al, BYTE [rsi] ; Get first byte and al, block_mask ; Test block type cmp al, block_array ; Test if it's an array - je .array - call release_cons - ret -.array: - call release_array - ret + je release_array + jmp release_cons ;; Increment reference count of Cons or Array ;; Address of object in RSI From e2ada64e741d640df9034633de1bca59a4e1c658 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 21 Dec 2017 10:56:14 +0000 Subject: [PATCH 0639/2308] Fix bug in map: should pass args as list This appeared only for variadic function which is used in EVAL. Added a test to step 9 to catch this earlier. --- nasm/core.asm | 6 ++++-- tests/step9_try.mal | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index d7c80428f4..541fa5377a 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -2211,14 +2211,16 @@ core_map: mov cl, BYTE [r9] and cl, content_mask - + mov ch, cl + or cl, container_list + call alloc_cons mov [rax], BYTE cl ; set content type mov rbx, [r9 + Cons.car] mov [rax + Cons.car], rbx ; Copy content mov rsi, rax - cmp cl, content_pointer + cmp ch, content_pointer jne .run ; A pointer, so increment ref count diff --git a/tests/step9_try.mal b/tests/step9_try.mal index 17cfae240e..d50250ecca 100644 --- a/tests/step9_try.mal +++ b/tests/step9_try.mal @@ -139,6 +139,9 @@ (map (fn* (a) (* 2 a)) [1 2 3]) ;=>(2 4 6) +(map (fn* [& args] (list? args)) [1 2]) +;=>(true true) + ;; Testing vector functions (vector? [10 11]) From 0b92f020345aaf91894bdfe6d9bf1bf50bc4a214 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 21 Dec 2017 11:53:03 +0000 Subject: [PATCH 0640/2308] Add seq core function. Self-hosting tests pass (!) Only soft failures, mainly from: * conj function not yet defined * map comparison depends on the order in which keys appear (since it's treated like a list) * meta data only for functions Some odd/concerning soft fails: Expected : '(= (gensym) (gensym))\r\nfalse' Got : '(= (gensym) (gensym))\r\nUncaught exception: non-integer argument to integer arithmetic' Expected : '(let* [or_FIXME 23] (or false (+ or_FIXME 100)))\r\n123' Got : '(let* [or_FIXME 23] (or false (+ or_FIXME 100)))\r\nUncaught exception: non-integer argument to integer arithmetic' Expected : '(dissoc {:cde 345 :fgh 456} :cde)\r\n{:fgh 456}' Got : '(dissoc {:cde 345 :fgh 456} :cde)\r\n[:fgh 456]' --- nasm/core.asm | 163 +++++++++++++++++++++++++++++++++++++++++++++ nasm/stepA_mal.asm | 2 +- 2 files changed, 164 insertions(+), 1 deletion(-) diff --git a/nasm/core.asm b/nasm/core.asm index 541fa5377a..0cebe05fb8 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -85,6 +85,8 @@ section .data static core_with_meta_symbol, db "with-meta" static core_time_ms_symbol, db "time-ms" + + static core_seq_symbol, db "seq" ;; Strings @@ -152,6 +154,9 @@ section .data static core_with_meta_no_function, db "with-meta expects a function as first argument" static core_with_meta_no_value, db "with-meta expects a value as second argument" + + static core_seq_missing_arg, db "seq missing argument" + static core_seq_wrong_type, db "seq expects a list, vector, string or nil" section .text @@ -264,6 +269,8 @@ core_environment: core_env_native core_with_meta_symbol, core_with_meta core_env_native core_time_ms_symbol, core_time_ms + + core_env_native core_seq_symbol, core_seq ; ----------------- ; Put the environment in RAX @@ -3104,4 +3111,160 @@ core_time_ms: mov [rax], BYTE maltype_integer mov [rax + Cons.car], rsi ret + +;; Convert sequences, including strings, into lists +core_seq: + mov al, BYTE [rsi] + and al, content_mask + cmp al, content_pointer + je .pointer + + cmp al, content_empty + je .missing_arg + + cmp al, content_nil + jne .wrong_type + +.return_nil: + call alloc_cons + mov [rax], BYTE maltype_nil + ret + +.pointer: + mov r8, [rsi + Cons.car] + mov al, BYTE [r8] + + cmp al, maltype_string + je .string + + mov ah, al + and ah, (block_mask + content_mask) + cmp ah, (block_cons + content_empty) + je .return_nil + + and al, (block_mask + container_mask) + + cmp al, (block_cons + container_list) + je .list + + cmp al, (block_cons + container_vector) + jne .wrong_type + + ; Convert vector to list by replacing the first Cons + call alloc_cons + mov bl, BYTE [r8] + and bl, content_mask + or bl, container_list + mov [rax], BYTE bl ; Set type + + mov rcx, [r8 + Cons.car] + mov [rax + Cons.car], rcx + + ; Check if it's a pointer + cmp bl, (container_list + content_pointer) + jne .copy_cdr + + ; Increment reference count + mov bx, WORD [rcx + Cons.refcount] ; Same for Array + inc bx + mov [rcx + Cons.refcount], WORD bx + +.copy_cdr: + mov rcx, [r8 + Cons.cdr] + mov [rax + Cons.cdr], rcx + + mov bl, [r8 + Cons.typecdr] + mov [rax + Cons.typecdr], bl + + cmp bl, content_pointer + jne .return + + ; Increment reference count + mov bx, WORD [rcx + Cons.refcount] ; Same for Array + inc bx + mov [rcx + Cons.refcount], WORD bx + +.return: + ret + +.list: + ; Return list unchanged + mov rsi, r8 + call incref_object + mov rax, r8 + ret + +.string: + ; Split a string into characters + ; Input string in R8 + + mov ebx, DWORD [r8 + Array.length] + test ebx,ebx + jz .return_nil ; empty string + + ; Not empty, so allocate first Cons + call alloc_cons + mov r9, rax ; Return Cons in R9 + mov r10, rax ; End of list in R10 + +.loop: + mov ebx, DWORD [r8 + Array.length] + mov r11, r8 + add r11, Array.data ; Start of string data in R11 + mov r12, r11 + add r12, rbx ; End of string data in R12 + +.inner_loop: + ; Get a new string + call string_new ; in RAX + mov bl, BYTE [r11] ; Get the next character + mov [rax + Array.data], BYTE bl + mov [rax + Array.length], DWORD 1 + + ; Put string into Cons at end of list + mov [r10 + Cons.car], rax + + ; Set type + mov [r10], BYTE (container_list + content_pointer) + + inc r11 + cmp r11, r12 + je .inner_done + + ; more characters, so allocate another Cons + call alloc_cons + + mov [r10 + Cons.typecdr], BYTE content_pointer + mov [r10 + Cons.cdr], rax + mov r10, rax + jmp .inner_loop + +.inner_done: + ; No more characters in this Array + ; check if there are more + mov r8, QWORD [r8 + Array.next] ; Get the next Array address + test r8, r8 ; Test if it's null + jz .string_finished + + ; Another chunk in the string + + call alloc_cons + mov [r10 + Cons.typecdr], BYTE content_pointer + mov [r10 + Cons.cdr], rax + mov r10, rax + jmp .loop + +.string_finished: + mov rax, r9 + ret + +.missing_arg: + ; No arguments + load_static core_seq_missing_arg + jmp core_throw_str + +.wrong_type: + ; Not a list, vector, string or nil + load_static core_seq_wrong_type + jmp core_throw_str diff --git a/nasm/stepA_mal.asm b/nasm/stepA_mal.asm index 41fdcdc44b..fe244353ce 100644 --- a/nasm/stepA_mal.asm +++ b/nasm/stepA_mal.asm @@ -80,7 +80,7 @@ section .data ; ;; Startup string. This is evaluated on startup - static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) (defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw ",34,"odd number of forms to cond",34,")) (cons 'cond (rest (rest xs))))))) (def! *gensym-counter* (atom 0)) (def! gensym (fn* [] (symbol (str ",34,"G__",34," (swap! *gensym-counter* (fn* [x] (+ 1 x))))))) (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs))))))))) (def! *host-language* ",34,"nasm",34,") (def! conj nil) (def! seq nil) )" + static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) (defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw ",34,"odd number of forms to cond",34,")) (cons 'cond (rest (rest xs))))))) (def! *gensym-counter* (atom 0)) (def! gensym (fn* [] (symbol (str ",34,"G__",34," (swap! *gensym-counter* (fn* [x] (+ 1 x))))))) (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs))))))))) (def! *host-language* ",34,"nasm",34,") (def! conj nil) )" ;; Command to run, appending the name of the script to run static run_script_string, db "(load-file ",34 From fb52de99c37597604175614d0ade773218bfafb4 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 22 Dec 2017 22:58:06 +0000 Subject: [PATCH 0641/2308] Fix bug in map, modifying function return values map was modifying the container type to list, to avoid having to copy values. Now checks if there are multiple references, and if so makes a copy. Fixes (= (gensym) (gensym)) error self-host test. --- nasm/core.asm | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index 0cebe05fb8..c4c8eb6ca7 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -1428,7 +1428,7 @@ core_swap: ; Now get the value in the atom mov rdx, [r9 + Cons.car] ; The object pointed to - + ; Check what it is mov bl, BYTE [rdx] mov bh, bl @@ -1506,7 +1506,7 @@ core_swap: mov rsi, [r9 + Cons.car] call release_object pop rax - + ; Put into atom mov [r9 + Cons.car], rax @@ -2290,6 +2290,7 @@ core_map: .got_return: ; Have a return result in RAX + ; Check if it's a value type mov bl, BYTE [rax] mov bh, bl @@ -2304,10 +2305,40 @@ core_map: jmp .update_return .return_value: + ; Check if this value is shared (e.g. in an atom) + mov cx, WORD [rax + Cons.refcount] + dec cx + jz .return_value_modify ; If reference count is 1 + + ; Need to copy to avoid modifying + push rsi + mov rsi, rax ; Original in RSI + + mov cl, bh ; Type + call alloc_cons + and cl, content_mask + or cl, container_list + mov [rax], BYTE cl ; mark as a list + + mov rbx, [rsi + Cons.car] + mov [rax + Cons.car], rbx ; copy content + + ; Release original + push rax + call release_object + pop rax + pop rsi + + jmp .update_return + +.return_value_modify: + ; Only one reference, + ; so can change the container type to list. + ; Original type in bh mov bl, bh and bl, content_mask or bl, container_list - mov [rax], BYTE bl ; mark as a list + mov [rax], BYTE bl .update_return: ; Now append to result list From e8915efe8eb6c026d21713e309cd994bec17618b Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 26 Dec 2017 23:31:16 +0000 Subject: [PATCH 0642/2308] Fix bugs, performance test now runs * Functions with an empty argument list resulted in a leak of a single Cons object. This caused the performance test to fail. * The heap 'next' pointers were dwords, rather than qwords. This doesn't seem to have made a difference, but could if the heap was made very large * Double free errors now throw a string rather than printing --- nasm/stepA_mal.asm | 9 ++++++++- nasm/types.asm | 10 ++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/nasm/stepA_mal.asm b/nasm/stepA_mal.asm index fe244353ce..bf76e7f81e 100644 --- a/nasm/stepA_mal.asm +++ b/nasm/stepA_mal.asm @@ -1866,9 +1866,16 @@ eval: je .list_got_args ; No arguments - push rbx + + push rbx ; Function object + + mov rsi, rax ; List with function first + call release_object ; Can be freed now + + ; Create an empty list for the arguments call alloc_cons mov [rax], BYTE maltype_empty_list + pop rbx mov rsi, rax jmp .list_function_call diff --git a/nasm/types.asm b/nasm/types.asm index 1bd6bda624..97b9634c0d 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -160,12 +160,12 @@ section .data %define heap_cons_limit 5000 ; Number of cons objects which can be created -heap_cons_next: dd heap_cons_store ; Address of next cons in memory +heap_cons_next: dq heap_cons_store ; Address of next cons in memory heap_cons_free: dq 0 ; Address of start of free list %define heap_array_limit 2000 ; Number of array objects which can be created -heap_array_next: dd heap_array_store +heap_array_next: dq heap_array_store heap_array_free: dq 0 section .bss @@ -263,7 +263,8 @@ release_array: .double_free: load_static error_cons_double_free - call print_rawstring + call raw_to_string + mov rsi, rax jmp error_throw ;; ------------------------------------------ @@ -373,7 +374,8 @@ release_cons: .double_free: ; Already released load_static error_cons_double_free - call print_rawstring + call raw_to_string + mov rsi, rax jmp error_throw ;; Releases either a Cons or Array From e64592a1da0a281659e640f5097dc715363cd6e9 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 26 Dec 2017 23:41:15 +0000 Subject: [PATCH 0643/2308] Moving error handler into exceptions.asm Needed by core routines, so will need to add to earlier steps. --- nasm/exceptions.asm | 138 ++++++++++++++++++++++++++++++++++++++ nasm/stepA_mal.asm | 160 +++++++------------------------------------- 2 files changed, 163 insertions(+), 135 deletions(-) create mode 100644 nasm/exceptions.asm diff --git a/nasm/exceptions.asm b/nasm/exceptions.asm new file mode 100644 index 0000000000..8630761c11 --- /dev/null +++ b/nasm/exceptions.asm @@ -0,0 +1,138 @@ + +;; ---------------------------------------------- +;; +;; Error handling +;; +;; A handler consists of: +;; - A stack pointer address to reset to +;; - An address to jump to +;; - An optional data structure to pass +;; +;; When jumped to, an error handler will be given: +;; - the object thrown in RSI +;; - the optional data structure in RDI +;; + +section .bss + +;; Error handler list +error_handler: resq 1 + +section .text + +;; Add an error handler to the front of the list +;; +;; Input: RSI - Stack pointer +;; RDI - Address to jump to +;; RCX - Data structure. Set to zero for none. +;; If not zero, reference count incremented +;; +;; Modifies registers: +;; RAX +;; RBX +error_handler_push: + call alloc_cons + ; car will point to a list (stack, addr, data) + ; cdr will point to the previous handler + mov [rax], BYTE (block_cons + container_list + content_pointer) + mov rbx, [error_handler] + cmp rbx, 0 ; Check if previous handler was zero + je .create_handler ; Zero, so leave null + ; Not zero, so create pointer to it + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rbx + + ; note: not incrementing reference count, since + ; we're replacing one reference with another +.create_handler: + mov [error_handler], rax ; new error handler + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.car], rax + ; Store stack pointer + mov [rax], BYTE (block_cons + container_list + content_function) + mov [rax + Cons.car], rsi ; stack pointer + + mov rdx, rax + call alloc_cons + mov [rdx + Cons.typecdr], BYTE content_pointer + mov [rdx + Cons.cdr], rax + ; Store function pointer to jump to + ; Note: This can't use content_pointer or release + ; will try to release this memory address + mov [rax], BYTE (block_cons + container_list + content_function) + mov [rax + Cons.car], rdi + + ; Check if there is an object to pass to handler + cmp rcx, 0 + je .done + + ; Set the final CDR to point to the object + mov [rax + Cons.typecdr], BYTE content_pointer + mov [rax + Cons.cdr], rcx + + mov rsi, rcx + call incref_object + +.done: + ret + + +;; Removes an error handler from the list +;; +;; Modifies registers: +;; RSI +;; RAX +;; RCX +error_handler_pop: + ; get the address + mov rsi, [error_handler] + cmp rsi, 0 + je .done ; Nothing to remove + + push rsi + mov rsi, [rsi + Cons.cdr] ; next handler + mov [error_handler], rsi + ;call incref_object ; needed because releasing soon + + pop rsi ; handler being removed + mov [rsi + Cons.typecdr], BYTE 0 + call release_cons + +.done: + ret + + +;; Throw an error +;; Object to pass to handler should be in RSI +error_throw: + ; Get the next error handler + mov rax, [error_handler] + cmp rax, 0 + je .no_handler + + ; Got a handler + mov rax, [rax + Cons.car] ; handler + mov rbx, [rax + Cons.car] ; stack pointer + mov rax, [rax + Cons.cdr] + mov rcx, [rax + Cons.car] ; function + mov rdi, [rax + Cons.cdr] ; data structure + + ; Reset stack + mov rsp, rbx + + ; Jump to the handler + jmp rcx + +.no_handler: + ; Print the object in RSI then quit + cmp rsi, 0 + je .done ; nothing to print + mov rdi, 1 ; print_readably + call pr_str + mov rsi, rax + call print_string +.done: + jmp quit_error + diff --git a/nasm/stepA_mal.asm b/nasm/stepA_mal.asm index bf76e7f81e..3aace8bc04 100644 --- a/nasm/stepA_mal.asm +++ b/nasm/stepA_mal.asm @@ -13,15 +13,13 @@ global _start %include "reader.asm" ; String -> Data structures %include "core.asm" ; Core functions %include "printer.asm" ; Data structures -> String +%include "exceptions.asm" ; Error handling section .bss ;; Top-level (REPL) environment repl_env:resq 1 -;; Error handler list -error_handler: resq 1 - section .data ;; ------------------------------------------ @@ -87,139 +85,9 @@ section .data ;; Command to run at start of REPL static mal_startup_header, db "(println (str ",34,"Mal [",34," *host-language* ",34,"]",34,"))" -section .text - -;; ---------------------------------------------- -;; -;; Error handling -;; -;; A handler consists of: -;; - A stack pointer address to reset to -;; - An address to jump to -;; - An optional data structure to pass -;; -;; When jumped to, an error handler will be given: -;; - the object thrown in RSI -;; - the optional data structure in RDI -;; - - -;; Add an error handler to the front of the list -;; -;; Input: RSI - Stack pointer -;; RDI - Address to jump to -;; RCX - Data structure. Set to zero for none. -;; If not zero, reference count incremented -;; -;; Modifies registers: -;; RAX -;; RBX -error_handler_push: - call alloc_cons - ; car will point to a list (stack, addr, data) - ; cdr will point to the previous handler - mov [rax], BYTE (block_cons + container_list + content_pointer) - mov rbx, [error_handler] - cmp rbx, 0 ; Check if previous handler was zero - je .create_handler ; Zero, so leave null - ; Not zero, so create pointer to it - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rbx - - ; note: not incrementing reference count, since - ; we're replacing one reference with another -.create_handler: - mov [error_handler], rax ; new error handler - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.car], rax - ; Store stack pointer - mov [rax], BYTE (block_cons + container_list + content_function) - mov [rax + Cons.car], rsi ; stack pointer - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.typecdr], BYTE content_pointer - mov [rdx + Cons.cdr], rax - ; Store function pointer to jump to - ; Note: This can't use content_pointer or release - ; will try to release this memory address - mov [rax], BYTE (block_cons + container_list + content_function) - mov [rax + Cons.car], rdi - - ; Check if there is an object to pass to handler - cmp rcx, 0 - je .done - - ; Set the final CDR to point to the object - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rcx - - mov rsi, rcx - call incref_object - -.done: - ret - - -;; Removes an error handler from the list -;; -;; Modifies registers: -;; RSI -;; RAX -;; RCX -error_handler_pop: - ; get the address - mov rsi, [error_handler] - cmp rsi, 0 - je .done ; Nothing to remove - - push rsi - mov rsi, [rsi + Cons.cdr] ; next handler - mov [error_handler], rsi - ;call incref_object ; needed because releasing soon - - pop rsi ; handler being removed - mov [rsi + Cons.typecdr], BYTE 0 - call release_cons - -.done: - ret - - -;; Throw an error -;; Object to pass to handler should be in RSI -error_throw: - ; Get the next error handler - mov rax, [error_handler] - cmp rax, 0 - je .no_handler - - ; Got a handler - mov rax, [rax + Cons.car] ; handler - mov rbx, [rax + Cons.car] ; stack pointer - mov rax, [rax + Cons.cdr] - mov rcx, [rax + Cons.car] ; function - mov rdi, [rax + Cons.cdr] ; data structure - - ; Reset stack - mov rsp, rbx - - ; Jump to the handler - jmp rcx - -.no_handler: - ; Print the object in RSI then quit - cmp rsi, 0 - je .done ; nothing to print - mov rdi, 1 ; print_readably - call pr_str - mov rsi, rax - call print_string -.done: - jmp quit_error +section .text + ;; ---------------------------------------------- ;; Evaluates a form ;; @@ -1946,6 +1814,28 @@ apply_fn: mov rax, [rax + Cons.cdr] mov rax, [rax + Cons.car] ; Body pop rcx ; Exprs + +;;; + ; push rax + ; push rcx + ; push rsi + ; push rdi + ; push rdx + ; push r15 + ; push r13 + + ; mov rsi, rcx + ; call core_println + + ; pop r13 + ; pop r15 + ; pop rdx + ; pop rdi + ; pop rsi + ; pop rcx + ; pop rax + +;;; ; Check the type of the body mov bl, BYTE [rax] From 90fb8655146cea0d5a22b04cdf6905f8e9e53cd7 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 26 Dec 2017 23:51:58 +0000 Subject: [PATCH 0644/2308] Step 9 passes again 3 soft failures, due to hash-map comparisons --- nasm/step9_try.asm | 157 ++++++--------------------------------------- nasm/stepA_mal.asm | 21 ------ 2 files changed, 18 insertions(+), 160 deletions(-) diff --git a/nasm/step9_try.asm b/nasm/step9_try.asm index d251a81c87..4faeecf9e6 100644 --- a/nasm/step9_try.asm +++ b/nasm/step9_try.asm @@ -13,14 +13,12 @@ global _start %include "reader.asm" ; String -> Data structures %include "core.asm" ; Core functions %include "printer.asm" ; Data structures -> String +%include "exceptions.asm" ; Error handling section .bss ;; Top-level (REPL) environment repl_env:resq 1 - -;; Error handler list -error_handler: resq 1 section .data @@ -86,137 +84,6 @@ section .data static run_script_string, db "(load-file ",34 section .text -;; ---------------------------------------------- -;; -;; Error handling -;; -;; A handler consists of: -;; - A stack pointer address to reset to -;; - An address to jump to -;; - An optional data structure to pass -;; -;; When jumped to, an error handler will be given: -;; - the object thrown in RSI -;; - the optional data structure in RDI -;; - - -;; Add an error handler to the front of the list -;; -;; Input: RSI - Stack pointer -;; RDI - Address to jump to -;; RCX - Data structure. Set to zero for none. -;; If not zero, reference count incremented -;; -;; Modifies registers: -;; RAX -;; RBX -error_handler_push: - call alloc_cons - ; car will point to a list (stack, addr, data) - ; cdr will point to the previous handler - mov [rax], BYTE (block_cons + container_list + content_pointer) - mov rbx, [error_handler] - cmp rbx, 0 ; Check if previous handler was zero - je .create_handler ; Zero, so leave null - ; Not zero, so create pointer to it - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rbx - - ; note: not incrementing reference count, since - ; we're replacing one reference with another -.create_handler: - mov [error_handler], rax ; new error handler - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.car], rax - ; Store stack pointer - mov [rax], BYTE (block_cons + container_list + content_function) - mov [rax + Cons.car], rsi ; stack pointer - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.typecdr], BYTE content_pointer - mov [rdx + Cons.cdr], rax - ; Store function pointer to jump to - ; Note: This can't use content_pointer or release - ; will try to release this memory address - mov [rax], BYTE (block_cons + container_list + content_function) - mov [rax + Cons.car], rdi - - ; Check if there is an object to pass to handler - cmp rcx, 0 - je .done - - ; Set the final CDR to point to the object - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rcx - - mov rsi, rcx - call incref_object - -.done: - ret - - -;; Removes an error handler from the list -;; -;; Modifies registers: -;; RSI -;; RAX -;; RCX -error_handler_pop: - ; get the address - mov rsi, [error_handler] - cmp rsi, 0 - je .done ; Nothing to remove - - push rsi - mov rsi, [rsi + Cons.cdr] ; next handler - mov [error_handler], rsi - ;call incref_object ; needed because releasing soon - - pop rsi ; handler being removed - mov [rsi + Cons.typecdr], BYTE 0 - call release_cons - -.done: - ret - - -;; Throw an error -;; Object to pass to handler should be in RSI -error_throw: - ; Get the next error handler - mov rax, [error_handler] - cmp rax, 0 - je .no_handler - - ; Got a handler - mov rax, [rax + Cons.car] ; handler - mov rbx, [rax + Cons.car] ; stack pointer - mov rax, [rax + Cons.cdr] - mov rcx, [rax + Cons.car] ; function - mov rdi, [rax + Cons.cdr] ; data structure - - ; Reset stack - mov rsp, rbx - - ; Jump to the handler - jmp rcx - -.no_handler: - ; Print the object in RSI then quit - cmp rsi, 0 - je .done ; nothing to print - mov rdi, 1 ; print_readably - call pr_str - mov rsi, rax - call print_string -.done: - jmp quit_error - ;; ---------------------------------------------- ;; Evaluates a form ;; @@ -1481,6 +1348,8 @@ eval: mov rsi, r15 call incref_object pop rax + + ; Binds call alloc_cons mov [rax], BYTE (block_cons + container_function + content_pointer) @@ -1716,7 +1585,8 @@ eval: push R9 push R10 - + push r15 ; Env + ; Set the error handler mov rsi, rsp ; Stack pointer mov rdi, .catch ; Address to jump to @@ -1731,10 +1601,12 @@ eval: mov rsi, r8 ; The form to evaluate (A) call incref_object ; AST released by eval - + call eval + mov r8, rax ; Result in R8 + pop r15 ; Environment ; Discard B and C ;add rsi, 8 ; pop R10 and R9 pop r10 @@ -1749,7 +1621,12 @@ eval: ; Jumps here on error ; Value thrown in RSI ; - + + push rsi + call error_handler_pop + pop rsi + + pop r15 ; Env pop r12 ; B (symbol to bind) pop r13 ; C (form to evaluate) @@ -1783,9 +1660,11 @@ eval: mov rdi, rsi ; Env in RDI (will be released) mov rsi, [r13 + Cons.car] ; Form to evaluate call incref_object ; will be released - - call eval + push r15 + call eval + pop r15 + jmp .return .try_missing_catch: diff --git a/nasm/stepA_mal.asm b/nasm/stepA_mal.asm index 3aace8bc04..c1e3d7a233 100644 --- a/nasm/stepA_mal.asm +++ b/nasm/stepA_mal.asm @@ -1815,27 +1815,6 @@ apply_fn: mov rax, [rax + Cons.car] ; Body pop rcx ; Exprs -;;; - ; push rax - ; push rcx - ; push rsi - ; push rdi - ; push rdx - ; push r15 - ; push r13 - - ; mov rsi, rcx - ; call core_println - - ; pop r13 - ; pop r15 - ; pop rdx - ; pop rdi - ; pop rsi - ; pop rcx - ; pop rax - -;;; ; Check the type of the body mov bl, BYTE [rax] From 7f8ae811e1070ea21eb61a787422131bc8fbf24c Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 26 Dec 2017 23:55:18 +0000 Subject: [PATCH 0645/2308] Step 8 passes --- nasm/step8_macros.asm | 134 +----------------------------------------- 1 file changed, 3 insertions(+), 131 deletions(-) diff --git a/nasm/step8_macros.asm b/nasm/step8_macros.asm index 60973781f6..3b6d7d5f14 100644 --- a/nasm/step8_macros.asm +++ b/nasm/step8_macros.asm @@ -13,14 +13,12 @@ global _start %include "reader.asm" ; String -> Data structures %include "core.asm" ; Core functions %include "printer.asm" ; Data structures -> String +%include "exceptions.asm" ; Error handling section .bss ;; Top-level (REPL) environment repl_env:resq 1 - -;; Error handler list -error_handler: resq 1 section .data @@ -80,134 +78,6 @@ section .data static run_script_string, db "(load-file ",34 section .text -;; ---------------------------------------------- -;; -;; Error handling -;; -;; A handler consists of: -;; - A stack pointer address to reset to -;; - An address to jump to -;; - An optional data structure to pass -;; -;; When jumped to, an error handler will be given: -;; - the object thrown in RSI -;; - the optional data structure in RDI -;; - - -;; Add an error handler to the front of the list -;; -;; Input: RSI - Stack pointer -;; RDI - Address to jump to -;; RCX - Data structure. Set to zero for none. -;; If not zero, reference count incremented -;; -;; Modifies registers: -;; RAX -;; RBX -error_handler_push: - call alloc_cons - ; car will point to a list (stack, addr, data) - ; cdr will point to the previous handler - mov [rax], BYTE (block_cons + container_list + content_pointer) - mov rbx, [error_handler] - cmp rbx, 0 ; Check if previous handler was zero - je .create_handler ; Zero, so leave null - ; Not zero, so create pointer to it - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rbx - - ; note: not incrementing reference count, since - ; we're replacing one reference with another -.create_handler: - mov [error_handler], rax ; new error handler - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.car], rax - ; Store stack pointer - mov [rax], BYTE (block_cons + container_list + content_function) - mov [rax + Cons.car], rsi ; stack pointer - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.typecdr], BYTE content_pointer - mov [rdx + Cons.cdr], rax - ; Store function pointer to jump to - mov [rax], BYTE (block_cons + container_list + content_pointer) - mov [rax + Cons.car], rdi - - ; Check if there is an object to pass to handler - cmp rcx, 0 - je .done - - ; Set the final CDR to point to the object - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rcx - - mov rsi, rcx - call incref_object - -.done: - ret - - -;; Removes an error handler from the list -;; -;; Modifies registers: -;; RSI -;; RAX -;; RCX -error_handler_pop: - ; get the address - mov rsi, [error_handler] - cmp rsi, 0 - je .done ; Nothing to remove - - push rsi - mov rsi, [rsi + Cons.cdr] ; next handler - mov [error_handler], rsi - call incref_object ; needed because releasing soon - - pop rsi ; handler being removed - call release_cons - -.done: - ret - - -;; Throw an error -;; Object to pass to handler should be in RSI -error_throw: - ; Get the next error handler - mov rax, [error_handler] - cmp rax, 0 - je .no_handler - - ; Got a handler - mov rax, [rax + Cons.car] ; handler - mov rbx, [rax + Cons.car] ; stack pointer - mov rax, [rax + Cons.cdr] - mov rcx, [rax + Cons.car] ; function - mov rdi, [rax + Cons.cdr] ; data structure - - ; Reset stack - mov rsp, rbx - - ; Jump to the handler - jmp rcx - -.no_handler: - ; Print the object in RSI then quit - cmp rsi, 0 - je .done ; nothing to print - mov rdi, 1 ; print_readably - call pr_str - mov rsi, rax - call print_string -.done: - jmp quit_error - ;; ---------------------------------------------- ;; Evaluates a form ;; @@ -1450,6 +1320,8 @@ eval: mov rsi, r15 call incref_object pop rax + + ; Binds call alloc_cons mov [rax], BYTE (block_cons + container_function + content_pointer) From 0a96ee8abe5fd21081980771e6b1874614e6e702 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 27 Dec 2017 17:32:01 +0000 Subject: [PATCH 0646/2308] All tests pass Some modifications needed to earlier steps so they compile with the new core functions. Need to disable double free error checks in types, because step2 fails otherwise. --- nasm/step1_read_print.asm | 4 +- nasm/step2_eval.asm | 14 +- nasm/step3_env.asm | 132 +------------- nasm/step4_if_fn_do.asm | 131 +------------- nasm/step5_tco.asm | 372 +++++++++++++++++--------------------- nasm/step6_file.asm | 323 +++++++++++++++------------------ nasm/types.asm | 2 + 7 files changed, 337 insertions(+), 641 deletions(-) diff --git a/nasm/step1_read_print.asm b/nasm/step1_read_print.asm index a651e86b2d..5aa2fe5504 100644 --- a/nasm/step1_read_print.asm +++ b/nasm/step1_read_print.asm @@ -11,7 +11,8 @@ global _start %include "system.asm" ; System calls %include "reader.asm" ; String -> Data structures %include "printer.asm" ; Data structures -> String - +%include "exceptions.asm" ; Error handling + section .data test_string1: db 10, "test1", 10 @@ -94,6 +95,7 @@ _start: ; Put into pr_str mov rsi, rax + mov rdi, 1 ; print readably call pr_str push rax diff --git a/nasm/step2_eval.asm b/nasm/step2_eval.asm index 98025a0208..2a495c62be 100644 --- a/nasm/step2_eval.asm +++ b/nasm/step2_eval.asm @@ -1,5 +1,5 @@ ;; -;; nasm -felf64 step1_read_print.asm && ld step1_read_print.o && ./a.out +;; nasm -felf64 step2_eval.asm && ld step2_eval.o && ./a.out ;; Calling convention: Address of input is in RSI ;; Address of return value is in RAX @@ -13,6 +13,7 @@ global _start %include "reader.asm" ; String -> Data structures %include "core.asm" ; Core functions %include "printer.asm" ; Data structures -> String +%include "exceptions.asm" ; Error handling section .bss @@ -27,6 +28,8 @@ section .data prompt_string: db 10,"user> " ; The string to print at the prompt .len: equ $ - prompt_string +error_string: db 27,'[31m',"Error",27,'[0m',": " +.len: equ $ - error_string def_symbol: ISTRUC Array AT Array.type, db maltype_symbol @@ -40,8 +43,12 @@ AT Array.length, dd 4 AT Array.data, db 'let*' IEND -section .text - +section .text + +;; This is a dummy function so that core routines compile +apply_fn: + jmp quit + ;; Evaluates a form in RSI eval_ast: ; Check the type @@ -537,6 +544,7 @@ _start: ; Put into pr_str mov rsi, rax + mov rdi, 1 ; print readably call pr_str push rax ; Save output string diff --git a/nasm/step3_env.asm b/nasm/step3_env.asm index bdc9e017b4..132f72529e 100644 --- a/nasm/step3_env.asm +++ b/nasm/step3_env.asm @@ -13,14 +13,12 @@ global _start %include "reader.asm" ; String -> Data structures %include "core.asm" ; Core functions %include "printer.asm" ; Data structures -> String +%include "exceptions.asm" ; Error handling section .bss ;; Top-level (REPL) environment repl_env:resq 1 - -;; Error handler list -error_handler: resq 1 section .data @@ -55,133 +53,11 @@ section .data section .text -;; ---------------------------------------------- -;; -;; Error handling -;; -;; A handler consists of: -;; - A stack pointer address to reset to -;; - An address to jump to -;; - An optional data structure to pass -;; -;; When jumped to, an error handler will be given: -;; - the object thrown in RSI -;; - the optional data structure in RDI -;; - - -;; Add an error handler to the front of the list -;; -;; Input: RSI - Stack pointer -;; RDI - Address to jump to -;; RCX - Data structure. Set to zero for none. -;; If not zero, reference count incremented -;; -;; Modifies registers: -;; RAX -;; RBX -error_handler_push: - call alloc_cons - ; car will point to a list (stack, addr, data) - ; cdr will point to the previous handler - mov [rax], BYTE (block_cons + container_list + content_pointer) - mov rbx, [error_handler] - cmp rbx, 0 ; Check if previous handler was zero - je .create_handler ; Zero, so leave null - ; Not zero, so create pointer to it - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rbx - - ; note: not incrementing reference count, since - ; we're replacing one reference with another -.create_handler: - mov [error_handler], rax ; new error handler - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.car], rax - ; Store stack pointer - mov [rax], BYTE (block_cons + container_list + content_function) - mov [rax + Cons.car], rsi ; stack pointer - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.typecdr], BYTE content_pointer - mov [rdx + Cons.cdr], rax - ; Store function pointer to jump to - mov [rax], BYTE (block_cons + container_list + content_pointer) - mov [rax + Cons.car], rdi - - ; Check if there is an object to pass to handler - cmp rcx, 0 - je .done - - ; Set the final CDR to point to the object - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rcx - - mov rsi, rcx - call incref_object - -.done: - ret - - -;; Removes an error handler from the list -;; -;; Modifies registers: -;; RSI -;; RAX -;; RCX -error_handler_pop: - ; get the address - mov rsi, [error_handler] - cmp rsi, 0 - je .done ; Nothing to remove - - push rsi - mov rsi, [rsi + Cons.cdr] ; next handler - mov [error_handler], rsi - call incref_object ; needed because releasing soon - - pop rsi ; handler being removed - call release_cons - -.done: - ret - - -;; Throw an error -;; Object to pass to handler should be in RSI -error_throw: - ; Get the next error handler - mov rax, [error_handler] - cmp rax, 0 - je .no_handler - - ; Got a handler - mov rax, [rax + Cons.car] ; handler - mov rbx, [rax + Cons.car] ; stack pointer - mov rax, [rax + Cons.cdr] - mov rcx, [rax + Cons.car] ; function - mov rdi, [rax + Cons.cdr] ; data structure +;; This is a dummy function so that core routines compile +apply_fn: + jmp quit - ; Reset stack - mov rsp, rbx - ; Jump to the handler - jmp rcx - -.no_handler: - ; Print the object in RSI then quit - cmp rsi, 0 - je .done ; nothing to print - call pr_str - mov rsi, rax - call print_string -.done: - jmp quit_error - ;; ---------------------------------------------- ;; Evaluates a form ;; diff --git a/nasm/step4_if_fn_do.asm b/nasm/step4_if_fn_do.asm index 852192c403..110cc2d66c 100644 --- a/nasm/step4_if_fn_do.asm +++ b/nasm/step4_if_fn_do.asm @@ -13,14 +13,12 @@ global _start %include "reader.asm" ; String -> Data structures %include "core.asm" ; Core functions %include "printer.asm" ; Data structures -> String +%include "exceptions.asm" ; Error handling section .bss ;; Top-level (REPL) environment repl_env:resq 1 - -;; Error handler list -error_handler: resq 1 section .data @@ -63,134 +61,7 @@ section .data section .text -;; ---------------------------------------------- -;; -;; Error handling -;; -;; A handler consists of: -;; - A stack pointer address to reset to -;; - An address to jump to -;; - An optional data structure to pass -;; -;; When jumped to, an error handler will be given: -;; - the object thrown in RSI -;; - the optional data structure in RDI -;; - - -;; Add an error handler to the front of the list -;; -;; Input: RSI - Stack pointer -;; RDI - Address to jump to -;; RCX - Data structure. Set to zero for none. -;; If not zero, reference count incremented -;; -;; Modifies registers: -;; RAX -;; RBX -error_handler_push: - call alloc_cons - ; car will point to a list (stack, addr, data) - ; cdr will point to the previous handler - mov [rax], BYTE (block_cons + container_list + content_pointer) - mov rbx, [error_handler] - cmp rbx, 0 ; Check if previous handler was zero - je .create_handler ; Zero, so leave null - ; Not zero, so create pointer to it - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rbx - - ; note: not incrementing reference count, since - ; we're replacing one reference with another -.create_handler: - mov [error_handler], rax ; new error handler - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.car], rax - ; Store stack pointer - mov [rax], BYTE (block_cons + container_list + content_function) - mov [rax + Cons.car], rsi ; stack pointer - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.typecdr], BYTE content_pointer - mov [rdx + Cons.cdr], rax - ; Store function pointer to jump to - mov [rax], BYTE (block_cons + container_list + content_pointer) - mov [rax + Cons.car], rdi - - ; Check if there is an object to pass to handler - cmp rcx, 0 - je .done - - ; Set the final CDR to point to the object - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rcx - - mov rsi, rcx - call incref_object - -.done: - ret - - -;; Removes an error handler from the list -;; -;; Modifies registers: -;; RSI -;; RAX -;; RCX -error_handler_pop: - ; get the address - mov rsi, [error_handler] - cmp rsi, 0 - je .done ; Nothing to remove - - push rsi - mov rsi, [rsi + Cons.cdr] ; next handler - mov [error_handler], rsi - call incref_object ; needed because releasing soon - - pop rsi ; handler being removed - call release_cons - -.done: - ret - - -;; Throw an error -;; Object to pass to handler should be in RSI -error_throw: - ; Get the next error handler - mov rax, [error_handler] - cmp rax, 0 - je .no_handler - - ; Got a handler - mov rax, [rax + Cons.car] ; handler - mov rbx, [rax + Cons.car] ; stack pointer - mov rax, [rax + Cons.cdr] - mov rcx, [rax + Cons.car] ; function - mov rdi, [rax + Cons.cdr] ; data structure - - ; Reset stack - mov rsp, rbx - ; Jump to the handler - jmp rcx - -.no_handler: - ; Print the object in RSI then quit - cmp rsi, 0 - je .done ; nothing to print - mov rdi, 1 ; print_readably - call pr_str - mov rsi, rax - call print_string -.done: - jmp quit_error - ;; ---------------------------------------------- ;; Evaluates a form ;; diff --git a/nasm/step5_tco.asm b/nasm/step5_tco.asm index d861773f62..af5525f425 100644 --- a/nasm/step5_tco.asm +++ b/nasm/step5_tco.asm @@ -13,14 +13,12 @@ global _start %include "reader.asm" ; String -> Data structures %include "core.asm" ; Core functions %include "printer.asm" ; Data structures -> String +%include "exceptions.asm" ; Error handling section .bss ;; Top-level (REPL) environment repl_env:resq 1 - -;; Error handler list -error_handler: resq 1 section .data @@ -64,134 +62,7 @@ section .data section .text -;; ---------------------------------------------- -;; -;; Error handling -;; -;; A handler consists of: -;; - A stack pointer address to reset to -;; - An address to jump to -;; - An optional data structure to pass -;; -;; When jumped to, an error handler will be given: -;; - the object thrown in RSI -;; - the optional data structure in RDI -;; - - -;; Add an error handler to the front of the list -;; -;; Input: RSI - Stack pointer -;; RDI - Address to jump to -;; RCX - Data structure. Set to zero for none. -;; If not zero, reference count incremented -;; -;; Modifies registers: -;; RAX -;; RBX -error_handler_push: - call alloc_cons - ; car will point to a list (stack, addr, data) - ; cdr will point to the previous handler - mov [rax], BYTE (block_cons + container_list + content_pointer) - mov rbx, [error_handler] - cmp rbx, 0 ; Check if previous handler was zero - je .create_handler ; Zero, so leave null - ; Not zero, so create pointer to it - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rbx - - ; note: not incrementing reference count, since - ; we're replacing one reference with another -.create_handler: - mov [error_handler], rax ; new error handler - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.car], rax - ; Store stack pointer - mov [rax], BYTE (block_cons + container_list + content_function) - mov [rax + Cons.car], rsi ; stack pointer - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.typecdr], BYTE content_pointer - mov [rdx + Cons.cdr], rax - ; Store function pointer to jump to - mov [rax], BYTE (block_cons + container_list + content_pointer) - mov [rax + Cons.car], rdi - - ; Check if there is an object to pass to handler - cmp rcx, 0 - je .done - - ; Set the final CDR to point to the object - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rcx - - mov rsi, rcx - call incref_object - -.done: - ret - - -;; Removes an error handler from the list -;; -;; Modifies registers: -;; RSI -;; RAX -;; RCX -error_handler_pop: - ; get the address - mov rsi, [error_handler] - cmp rsi, 0 - je .done ; Nothing to remove - - push rsi - mov rsi, [rsi + Cons.cdr] ; next handler - mov [error_handler], rsi - call incref_object ; needed because releasing soon - - pop rsi ; handler being removed - call release_cons - -.done: - ret - - -;; Throw an error -;; Object to pass to handler should be in RSI -error_throw: - ; Get the next error handler - mov rax, [error_handler] - cmp rax, 0 - je .no_handler - - ; Got a handler - mov rax, [rax + Cons.car] ; handler - mov rbx, [rax + Cons.car] ; stack pointer - mov rax, [rax + Cons.cdr] - mov rcx, [rax + Cons.car] ; function - mov rdi, [rax + Cons.cdr] ; data structure - - ; Reset stack - mov rsp, rbx - ; Jump to the handler - jmp rcx - -.no_handler: - ; Print the object in RSI then quit - cmp rsi, 0 - je .done ; nothing to print - mov rdi, 1 ; print_readably - call pr_str - mov rsi, rax - call print_string -.done: - jmp quit_error - ;; ---------------------------------------------- ;; Evaluates a form ;; @@ -297,12 +168,13 @@ eval_ast: push r8 push r9 push r15 ; Env - mov rsi, [rsi + Cons.car] ; Get the address - mov rdi, r15 + mov rdi, [rsi + Cons.car] ; Get the address + mov rsi, r15 - xchg rsi, rdi call incref_object ; Environment increment refs - xchg rsi, rdi + xchg rsi, rdi ; Env in RDI, AST in RSI + + call incref_object ; AST increment refs call eval ; Evaluate it, result in rax pop r15 @@ -466,6 +338,8 @@ eval_ast: xchg rsi, rdi call incref_object ; Environment increment refs xchg rsi, rdi + + call incref_object call eval ; Evaluate it, result in rax pop r15 @@ -554,6 +428,8 @@ eval_ast: xchg rsi, rdi call incref_object ; Environment increment refs xchg rsi, rdi + + call incref_object call eval ; Evaluate it, result in rax pop r15 @@ -643,22 +519,25 @@ eval_ast: call compare_char_array pop rbx pop rsi - cmp rax, 0 + test rax, rax ; ZF set if rax = 0 (equal) %endmacro ;; ---------------------------------------------------- ;; Evaluates a form ;; -;; Input: RSI Form to evaluate -;; RDI Environment +;; Input: RSI AST to evaluate [ Released ] +;; RDI Environment [ Released ] ;; ;; Returns: Result in RAX ;; -;; Note: The environment in RDI will have its reference count -;; reduced by one (released). This is to make tail call optimisation easier +;; Note: Both the form and environment will have their reference count +;; reduced by one (released). This is for tail call optimisation (Env), +;; quasiquote and macroexpand (AST) ;; eval: mov r15, rdi ; Env + + push rsi ; AST pushed, must be popped before return ; Check type mov al, BYTE [rsi] @@ -774,9 +653,12 @@ eval: xchg rsi, rdi call incref_object ; Environment increment refs xchg rsi, rdi ; since it will be decremented by eval + + call incref_object ; AST increment refs call eval mov rsi, rax + pop r15 pop r8 @@ -899,8 +781,15 @@ eval: push r12 ; Position in bindings list push r13 ; symbol to bind push r14 ; new environment - mov rsi, [r12 + Cons.car] ; Get the address + + mov rsi, r14 + call incref_object mov rdi, r14 + + mov rsi, [r12 + Cons.car] ; Get the address + + call incref_object ; Increment ref count of AST + call eval ; Evaluate it, result in rax pop r14 pop r13 @@ -952,6 +841,13 @@ eval: ; Evaluate using new environment mov rsi, [r11 + Cons.car] ; Object pointed to + call incref_object ; will be released by eval + + mov r11, rsi ; save new AST + pop rsi ; Old AST + call release_object + mov rsi, r11 ; New AST + mov rdi, r14 ; New environment jmp eval ; Tail call @@ -963,6 +859,12 @@ eval: mov rsi, r14 call release_object pop rax + + ; Release the AST + pop rsi + push rax + call release_object + pop rax ret ; already released env .let_error_missing_bindings: @@ -1045,6 +947,8 @@ eval: ; since eval will release Env mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Increment ref count since eval will release + mov rdi, r15 ; Env call eval ; Result in RAX @@ -1087,6 +991,13 @@ eval: mov [rax], BYTE bl mov rbx, [r11 + Cons.car] mov [rax + Cons.car], rbx + + ; release the AST + pop rsi + mov r15, rax ; not modified by release + call release_object + mov rax, r15 + ret .do_body_expr_return: @@ -1094,7 +1005,14 @@ eval: ; Tail call optimise, jumping to eval ; Don't increment Env reference count - mov rsi, [r11 + Cons.car] ; Form + mov rsi, [r11 + Cons.car] ; new AST form + call incref_object ; This will be released by eval + + mov r11, rsi ; Save new AST + pop rsi ; Remove old AST from stack + call release_object + mov rsi, r11 + mov rdi, r15 ; Env jmp eval ; Tail call @@ -1103,6 +1021,10 @@ eval: mov rsi, r15 call release_object ; Release Env + + ; release the AST + pop rsi + call release_object call alloc_cons mov [rax], BYTE maltype_nil @@ -1136,6 +1058,8 @@ eval: call incref_object ; Increase Env reference mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Increase Form/AST ref count + mov rdi, r15 ; Env call eval ; Result in RAX pop r11 @@ -1203,6 +1127,13 @@ eval: .if_got_pointer: mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Will be released by eval + + mov r11, rsi + pop rsi + call release_object ; Release old AST + mov rsi, r11 ; New AST + mov rdi, r15 ; Env jmp eval ; Tail call @@ -1223,12 +1154,16 @@ eval: mov [rax + Cons.typecdr], BYTE content_nil .return: - push rax ; Release environment mov rsi, r15 + mov r15, rax ; Save RAX (return value) call release_object - pop rax - + + ; Release the AST + pop rsi ; Pushed at start of eval + call release_object + + mov rax, r15 ; return value ret ; ----------------------------- @@ -1305,6 +1240,8 @@ eval: mov rsi, r15 call incref_object pop rax + + ; Binds call alloc_cons mov [rax], BYTE (block_cons + container_function + content_pointer) @@ -1351,6 +1288,13 @@ eval: pop r15 pop rsi +.list_exec: + ; This point can be called to run a function + ; used by swap! + ; + ; Inputs: RAX - List with function as first element + ; NOTE: This list is released + ; ; Check that the first element of the return is a function mov bl, BYTE [rax] and bl, content_mask @@ -1384,7 +1328,7 @@ eval: mov rbx, [rbx + Cons.car] ; Call function cmp rbx, apply_fn - je apply_fn ; Jump to user function apply + je apply_fn_jmp ; Jump to user function apply ; A built-in function, so call (no recursion) push rax @@ -1395,11 +1339,10 @@ eval: ; Result in rax pop r15 pop rsi ; eval'ed list - + push rax call release_cons - pop rax - + pop rax jmp .return ; Releases Env .list_not_function: @@ -1421,8 +1364,16 @@ eval: ;; RDI - Function object ;; RDX - list to release after binding ;; R15 - Env (will be released) +;; R13 - AST released before return +;; ;; ;; Output: Result in RAX +;; +;; This is jumped to from eval, so if it returns +;; then it will return to the caller of eval, not to eval +apply_fn_jmp: + ; This is jumped to from eval with AST on the stack + pop r13 apply_fn: push rsi ; Extract values from the list in RDI @@ -1440,7 +1391,8 @@ apply_fn: jnz .bind ; Just a value (in RAX). No eval needed - push rax + mov r14, rax ; Save return value in R14 + mov rsi, rax call incref_object @@ -1451,12 +1403,18 @@ apply_fn: ; Release the environment mov rsi, r15 call release_object + + ; Release the AST + mov rsi, r13 + call release_object - pop rax + mov rax, r14 ret .bind: ; Create a new environment, binding arguments - push rax + push rax ; Body + + mov r14, r13 ; Old AST. R13 used by env_new_bind push rdx call env_new_bind @@ -1464,29 +1422,75 @@ apply_fn: mov rdi, rax ; New environment in RDI + ; Note: Need to increment the reference count + ; of the function body before releasing anything, + ; since if the function was defined in-place (lambda) + ; then the body may be released early + + pop rsi ; Body + call incref_object ; Will be released by eval + mov r8, rsi ; Body in R8 + ; Release the list passed in RDX -.release: mov rsi, rdx call release_cons ; Release the environment mov rsi, r15 call release_object - - pop rsi ; Body + + ; Release the old AST + mov rsi, r14 + call release_object + + mov rsi, r8 ; Body jmp eval ; Tail call ; The new environment (in RDI) will be released by eval ;; Read-Eval-Print in sequence +;; +;; Input string in RSI rep_seq: + ; ------------- + ; Read call read_str - mov rsi, rax ; Output of read into input of eval - call eval - mov rsi, rax ; Output of eval into input of print + + ; ------------- + ; Eval + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + call eval ; This releases Env and Form/AST + push rax ; Save result of eval + + ; ------------- + ; Print + + ; Put into pr_str + mov rsi, rax + mov rdi, 1 ; print_readably call pr_str - mov rsi, rax ; Return value + push rax ; Save output string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from pr_str + pop rsi + call release_array + + ; Release result of eval + pop rsi + call release_object + + ; The AST from read_str is released by eval + ret @@ -1515,21 +1519,16 @@ _start: push rax ; AST call release_array ; string - pop rsi ; AST - - push rsi - mov rdi, [repl_env] ; Environment - - xchg rsi, rdi + pop rdi ; AST in RDI + + mov rsi, [repl_env] ; Environment in RSI + call incref_object ; Environment increment refs - xchg rsi, rdi ; since it will be decremented by eval + xchg rsi, rdi ; Env in RDI, AST in RSI call eval - pop rsi - push rax - call release_object ; AST - pop rsi + mov rsi, rax call release_object ; Return from eval ; ----------------------------- @@ -1549,40 +1548,7 @@ _start: ; Put into read_str mov rsi, rax - call read_str - push rax ; Save AST - - ; Eval - mov rsi, rax ; Form to evaluate - mov rdi, [repl_env] ; Environment - - xchg rsi, rdi - call incref_object ; Environment increment refs - xchg rsi, rdi ; since it will be decremented by eval - - call eval - push rax ; Save result - - ; Put into pr_str - mov rsi, rax - mov rdi, 1 ; print_readably - call pr_str - push rax ; Save output string - - mov rsi, rax ; Put into input of print_string - call print_string - - ; Release string from pr_str - pop rsi - call release_array - - ; Release result of eval - pop rsi - call release_object - - ; Release the object from read_str - pop rsi - call release_object ; Could be Cons or Array + call rep_seq ; Release the input string pop rsi diff --git a/nasm/step6_file.asm b/nasm/step6_file.asm index c18e58ed6c..0020d559bc 100644 --- a/nasm/step6_file.asm +++ b/nasm/step6_file.asm @@ -13,14 +13,12 @@ global _start %include "reader.asm" ; String -> Data structures %include "core.asm" ; Core functions %include "printer.asm" ; Data structures -> String +%include "exceptions.asm" ; Error handling section .bss ;; Top-level (REPL) environment repl_env:resq 1 - -;; Error handler list -error_handler: resq 1 section .data @@ -67,134 +65,6 @@ section .data static run_script_string, db "(load-file ",34 section .text -;; ---------------------------------------------- -;; -;; Error handling -;; -;; A handler consists of: -;; - A stack pointer address to reset to -;; - An address to jump to -;; - An optional data structure to pass -;; -;; When jumped to, an error handler will be given: -;; - the object thrown in RSI -;; - the optional data structure in RDI -;; - - -;; Add an error handler to the front of the list -;; -;; Input: RSI - Stack pointer -;; RDI - Address to jump to -;; RCX - Data structure. Set to zero for none. -;; If not zero, reference count incremented -;; -;; Modifies registers: -;; RAX -;; RBX -error_handler_push: - call alloc_cons - ; car will point to a list (stack, addr, data) - ; cdr will point to the previous handler - mov [rax], BYTE (block_cons + container_list + content_pointer) - mov rbx, [error_handler] - cmp rbx, 0 ; Check if previous handler was zero - je .create_handler ; Zero, so leave null - ; Not zero, so create pointer to it - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rbx - - ; note: not incrementing reference count, since - ; we're replacing one reference with another -.create_handler: - mov [error_handler], rax ; new error handler - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.car], rax - ; Store stack pointer - mov [rax], BYTE (block_cons + container_list + content_function) - mov [rax + Cons.car], rsi ; stack pointer - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.typecdr], BYTE content_pointer - mov [rdx + Cons.cdr], rax - ; Store function pointer to jump to - mov [rax], BYTE (block_cons + container_list + content_pointer) - mov [rax + Cons.car], rdi - - ; Check if there is an object to pass to handler - cmp rcx, 0 - je .done - - ; Set the final CDR to point to the object - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rcx - - mov rsi, rcx - call incref_object - -.done: - ret - - -;; Removes an error handler from the list -;; -;; Modifies registers: -;; RSI -;; RAX -;; RCX -error_handler_pop: - ; get the address - mov rsi, [error_handler] - cmp rsi, 0 - je .done ; Nothing to remove - - push rsi - mov rsi, [rsi + Cons.cdr] ; next handler - mov [error_handler], rsi - call incref_object ; needed because releasing soon - - pop rsi ; handler being removed - call release_cons - -.done: - ret - - -;; Throw an error -;; Object to pass to handler should be in RSI -error_throw: - ; Get the next error handler - mov rax, [error_handler] - cmp rax, 0 - je .no_handler - - ; Got a handler - mov rax, [rax + Cons.car] ; handler - mov rbx, [rax + Cons.car] ; stack pointer - mov rax, [rax + Cons.cdr] - mov rcx, [rax + Cons.car] ; function - mov rdi, [rax + Cons.cdr] ; data structure - - ; Reset stack - mov rsp, rbx - - ; Jump to the handler - jmp rcx - -.no_handler: - ; Print the object in RSI then quit - cmp rsi, 0 - je .done ; nothing to print - mov rdi, 1 ; print_readably - call pr_str - mov rsi, rax - call print_string -.done: - jmp quit_error - ;; ---------------------------------------------- ;; Evaluates a form ;; @@ -300,12 +170,13 @@ eval_ast: push r8 push r9 push r15 ; Env - mov rsi, [rsi + Cons.car] ; Get the address - mov rdi, r15 + mov rdi, [rsi + Cons.car] ; Get the address + mov rsi, r15 - xchg rsi, rdi call incref_object ; Environment increment refs - xchg rsi, rdi + xchg rsi, rdi ; Env in RDI, AST in RSI + + call incref_object ; AST increment refs call eval ; Evaluate it, result in rax pop r15 @@ -469,6 +340,8 @@ eval_ast: xchg rsi, rdi call incref_object ; Environment increment refs xchg rsi, rdi + + call incref_object call eval ; Evaluate it, result in rax pop r15 @@ -557,6 +430,8 @@ eval_ast: xchg rsi, rdi call incref_object ; Environment increment refs xchg rsi, rdi + + call incref_object call eval ; Evaluate it, result in rax pop r15 @@ -646,22 +521,25 @@ eval_ast: call compare_char_array pop rbx pop rsi - cmp rax, 0 + test rax, rax ; ZF set if rax = 0 (equal) %endmacro ;; ---------------------------------------------------- ;; Evaluates a form ;; -;; Input: RSI Form to evaluate -;; RDI Environment +;; Input: RSI AST to evaluate [ Released ] +;; RDI Environment [ Released ] ;; ;; Returns: Result in RAX ;; -;; Note: The environment in RDI will have its reference count -;; reduced by one (released). This is to make tail call optimisation easier +;; Note: Both the form and environment will have their reference count +;; reduced by one (released). This is for tail call optimisation (Env), +;; quasiquote and macroexpand (AST) ;; eval: mov r15, rdi ; Env + + push rsi ; AST pushed, must be popped before return ; Check type mov al, BYTE [rsi] @@ -777,9 +655,12 @@ eval: xchg rsi, rdi call incref_object ; Environment increment refs xchg rsi, rdi ; since it will be decremented by eval + + call incref_object ; AST increment refs call eval mov rsi, rax + pop r15 pop r8 @@ -902,8 +783,15 @@ eval: push r12 ; Position in bindings list push r13 ; symbol to bind push r14 ; new environment - mov rsi, [r12 + Cons.car] ; Get the address + + mov rsi, r14 + call incref_object mov rdi, r14 + + mov rsi, [r12 + Cons.car] ; Get the address + + call incref_object ; Increment ref count of AST + call eval ; Evaluate it, result in rax pop r14 pop r13 @@ -955,6 +843,13 @@ eval: ; Evaluate using new environment mov rsi, [r11 + Cons.car] ; Object pointed to + call incref_object ; will be released by eval + + mov r11, rsi ; save new AST + pop rsi ; Old AST + call release_object + mov rsi, r11 ; New AST + mov rdi, r14 ; New environment jmp eval ; Tail call @@ -966,6 +861,12 @@ eval: mov rsi, r14 call release_object pop rax + + ; Release the AST + pop rsi + push rax + call release_object + pop rax ret ; already released env .let_error_missing_bindings: @@ -1048,6 +949,8 @@ eval: ; since eval will release Env mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Increment ref count since eval will release + mov rdi, r15 ; Env call eval ; Result in RAX @@ -1090,6 +993,13 @@ eval: mov [rax], BYTE bl mov rbx, [r11 + Cons.car] mov [rax + Cons.car], rbx + + ; release the AST + pop rsi + mov r15, rax ; not modified by release + call release_object + mov rax, r15 + ret .do_body_expr_return: @@ -1097,7 +1007,14 @@ eval: ; Tail call optimise, jumping to eval ; Don't increment Env reference count - mov rsi, [r11 + Cons.car] ; Form + mov rsi, [r11 + Cons.car] ; new AST form + call incref_object ; This will be released by eval + + mov r11, rsi ; Save new AST + pop rsi ; Remove old AST from stack + call release_object + mov rsi, r11 + mov rdi, r15 ; Env jmp eval ; Tail call @@ -1106,6 +1023,10 @@ eval: mov rsi, r15 call release_object ; Release Env + + ; release the AST + pop rsi + call release_object call alloc_cons mov [rax], BYTE maltype_nil @@ -1139,6 +1060,8 @@ eval: call incref_object ; Increase Env reference mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Increase Form/AST ref count + mov rdi, r15 ; Env call eval ; Result in RAX pop r11 @@ -1206,6 +1129,13 @@ eval: .if_got_pointer: mov rsi, [r11 + Cons.car] ; Form + call incref_object ; Will be released by eval + + mov r11, rsi + pop rsi + call release_object ; Release old AST + mov rsi, r11 ; New AST + mov rdi, r15 ; Env jmp eval ; Tail call @@ -1226,12 +1156,16 @@ eval: mov [rax + Cons.typecdr], BYTE content_nil .return: - push rax ; Release environment mov rsi, r15 + mov r15, rax ; Save RAX (return value) call release_object - pop rax - + + ; Release the AST + pop rsi ; Pushed at start of eval + call release_object + + mov rax, r15 ; return value ret ; ----------------------------- @@ -1308,6 +1242,8 @@ eval: mov rsi, r15 call incref_object pop rax + + ; Binds call alloc_cons mov [rax], BYTE (block_cons + container_function + content_pointer) @@ -1353,8 +1289,7 @@ eval: call eval_ast ; List of evaluated forms in RAX pop r15 pop rsi - - + .list_exec: ; This point can be called to run a function ; used by swap! @@ -1395,7 +1330,7 @@ eval: mov rbx, [rbx + Cons.car] ; Call function cmp rbx, apply_fn - je apply_fn ; Jump to user function apply + je apply_fn_jmp ; Jump to user function apply ; A built-in function, so call (no recursion) push rax @@ -1406,11 +1341,10 @@ eval: ; Result in rax pop r15 pop rsi ; eval'ed list - + push rax call release_cons - pop rax - + pop rax jmp .return ; Releases Env .list_not_function: @@ -1432,8 +1366,16 @@ eval: ;; RDI - Function object ;; RDX - list to release after binding ;; R15 - Env (will be released) +;; R13 - AST released before return +;; ;; ;; Output: Result in RAX +;; +;; This is jumped to from eval, so if it returns +;; then it will return to the caller of eval, not to eval +apply_fn_jmp: + ; This is jumped to from eval with AST on the stack + pop r13 apply_fn: push rsi ; Extract values from the list in RDI @@ -1451,7 +1393,8 @@ apply_fn: jnz .bind ; Just a value (in RAX). No eval needed - push rax + mov r14, rax ; Save return value in R14 + mov rsi, rax call incref_object @@ -1462,12 +1405,18 @@ apply_fn: ; Release the environment mov rsi, r15 call release_object + + ; Release the AST + mov rsi, r13 + call release_object - pop rax + mov rax, r14 ret .bind: ; Create a new environment, binding arguments - push rax + push rax ; Body + + mov r14, r13 ; Old AST. R13 used by env_new_bind push rdx call env_new_bind @@ -1475,21 +1424,50 @@ apply_fn: mov rdi, rax ; New environment in RDI + ; Note: Need to increment the reference count + ; of the function body before releasing anything, + ; since if the function was defined in-place (lambda) + ; then the body may be released early + + pop rsi ; Body + call incref_object ; Will be released by eval + mov r8, rsi ; Body in R8 + ; Release the list passed in RDX -.release: mov rsi, rdx call release_cons ; Release the environment mov rsi, r15 call release_object - - pop rsi ; Body + + ; Release the old AST + mov rsi, r14 + call release_object + + mov rsi, r8 ; Body jmp eval ; Tail call ; The new environment (in RDI) will be released by eval +;; Read and eval +read_eval: + ; ------------- + ; Read + call read_str + + ; ------------- + ; Eval + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + jmp eval ; This releases Env and Form/AST + + ;; Read-Eval-Print in sequence ;; ;; Input string in RSI @@ -1497,8 +1475,7 @@ rep_seq: ; ------------- ; Read call read_str - push rax ; Save AST - + ; ------------- ; Eval mov rsi, rax ; Form to evaluate @@ -1508,7 +1485,7 @@ rep_seq: call incref_object ; Environment increment refs xchg rsi, rdi ; since it will be decremented by eval - call eval + call eval ; This releases Env and Form/AST push rax ; Save result of eval ; ------------- @@ -1531,9 +1508,8 @@ rep_seq: pop rsi call release_object - ; Release the object from read_str - pop rsi - call release_object ; Could be Cons or Array + ; The AST from read_str is released by eval + ret @@ -1562,21 +1538,16 @@ _start: push rax ; AST call release_array ; string - pop rsi ; AST - - push rsi - mov rdi, [repl_env] ; Environment - - xchg rsi, rdi + pop rdi ; AST in RDI + + mov rsi, [repl_env] ; Environment in RSI + call incref_object ; Environment increment refs - xchg rsi, rdi ; since it will be decremented by eval + xchg rsi, rdi ; Env in RDI, AST in RSI call eval - pop rsi - push rax - call release_object ; AST - pop rsi + mov rsi, rax call release_object ; Return from eval ; ----------------------------- @@ -1708,7 +1679,7 @@ run_script: mov cl, ')' call string_append_char ; closing brace - ; Read-Eval-Print "(load-file )" - call rep_seq + ; Read-Eval "(load-file )" + call read_eval jmp quit diff --git a/nasm/types.asm b/nasm/types.asm index 97b9634c0d..c29d8fa3e0 100644 --- a/nasm/types.asm +++ b/nasm/types.asm @@ -262,6 +262,7 @@ release_array: ret .double_free: + ret load_static error_cons_double_free call raw_to_string mov rsi, rax @@ -373,6 +374,7 @@ release_cons: ret .double_free: ; Already released + ret load_static error_cons_double_free call raw_to_string mov rsi, rax From 331ddfed79851142aea3b6380d9c8540f5183329 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 27 Dec 2017 23:33:03 +0000 Subject: [PATCH 0647/2308] Adding README.md file and Make all target * README.md in nasm/ directory lists some features and limitations * Added NASM entry to root directory README.md * Added an 'all' target to the nasm Makefile --- README.md | 14 ++++++++++++++ nasm/Makefile | 4 ++++ nasm/README.md | 30 ++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 nasm/README.md diff --git a/README.md b/README.md index 8e9c9433da..c423c24607 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ Mal is implemented in 71 languages: * mal itself * Matlab (GNU Octave and MATLAB) * [miniMAL](https://github.com/kanaka/miniMAL) +* NASM * Nim * Object Pascal * Objective C @@ -617,6 +618,19 @@ cd make make -f stepX_YYY.mk ``` +### NASM + +*The NASM implementation was created by [Ben Dudson](https://github.com/bendudson)* + +The NASM implementation of mal is written for x86-64 Linux, and has been tested +with Linux 3.16.0-4-amd64 and NASM version 2.11.05. + +``` +cd nasm +make +./stepX_YYY +``` + ### Nim 0.17.0 *The Nim implementation was created by [Dennis Felsing (def-)](https://github.com/def-)* diff --git a/nasm/Makefile b/nasm/Makefile index d220cb58bc..98028e93a2 100644 --- a/nasm/Makefile +++ b/nasm/Makefile @@ -1,6 +1,10 @@ +STEPS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do step5_tco step6_file step7_quote step8_macros step9_try stepA_mal + COMPONENTS=core.asm reader.asm printer.asm types.asm system.asm +all: $(STEPS) + %.o: %.asm $(COMPONENTS) nasm -felf64 $< diff --git a/nasm/README.md b/nasm/README.md new file mode 100644 index 0000000000..170a7c2c58 --- /dev/null +++ b/nasm/README.md @@ -0,0 +1,30 @@ +# x86_64 NASM implementation + +Notes and known issues: + +* No library dependencies, only Linux system calls + +* Simple readline implemented, just supporting backspace for editing + +* Reference counting used for memory management. No attempt is made + to find circular references, so leaks are possible. In particular + defining a function with def! creates a circular reference loop. + +* The exception/error handling just resets the stack and jumps to a handler, + so does not release memory + +* Memory is allocated by two fixed-size allocators (`Cons` and `Array` objects) + which have limits specified in types.asm. If more memory is needed + then this must currently be done at compile-time, but adding sys_brk + calls could be done. + +* The hash map implementation is just a list of key-value pairs. + Moving symbols around in the core environment makes a significant difference + (20-30%) to the performance test. A simple optimisation could be to + move items when found to the start of the list so that frequently + searched keys are nearer the front. + +* `conj` function not yet implemented + +* `*env*` symbol evaluates to current Environment. + From 860f35aae5a75ab51d508ee21c852c541f72daa0 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 27 Dec 2017 23:34:30 +0000 Subject: [PATCH 0648/2308] Moved macro-related functions to start of core Env Changes the performance test 3 from ~ 3500/s to ~4300/s Probably indicates that Env lookups are a big part of the runtime. --- nasm/core.asm | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nasm/core.asm b/nasm/core.asm index c4c8eb6ca7..8282b1ba9a 100644 --- a/nasm/core.asm +++ b/nasm/core.asm @@ -189,6 +189,13 @@ core_environment: xor rsi, rsi ; Set outer to nil call env_new mov rsi, rax ; Environment in RSI + + core_env_native core_cons_symbol, core_cons + core_env_native core_concat_symbol, core_concat + + core_env_native core_first_symbol, core_first + core_env_native core_rest_symbol, core_rest + core_env_native core_nth_symbol, core_nth core_env_native core_add_symbol, core_add core_env_native core_sub_symbol, core_sub @@ -224,13 +231,6 @@ core_environment: core_env_native core_atomp_symbol, core_atomp core_env_native core_reset_symbol, core_reset core_env_native core_swap_symbol, core_swap - - core_env_native core_cons_symbol, core_cons - core_env_native core_concat_symbol, core_concat - - core_env_native core_first_symbol, core_first - core_env_native core_rest_symbol, core_rest - core_env_native core_nth_symbol, core_nth core_env_native core_nilp_symbol, core_nilp core_env_native core_truep_symbol, core_truep From 990c7b5ee664191e4b419b0246c21f42844d1f46 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 27 Dec 2017 23:57:34 +0000 Subject: [PATCH 0649/2308] Add docker file. Docker tests pass Tested with: ``` docker build -t kanaka/mal-test-nasm" . make DOCKERIZE=1 "test^nasm" make DOCKERIZE=1 MAL_IMPL=nasm "test^mal" ``` Docker image not pushed to server, so Travis script fails. --- nasm/Dockerfile | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 nasm/Dockerfile diff --git a/nasm/Dockerfile b/nasm/Dockerfile new file mode 100644 index 0000000000..53bcd32624 --- /dev/null +++ b/nasm/Dockerfile @@ -0,0 +1,26 @@ +FROM ubuntu +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# Install nasm +RUN apt-get -y install nasm + From 265041fa76d5847aef87e1d5a749124d367abd57 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Fri, 29 Dec 2017 22:44:22 +0000 Subject: [PATCH 0650/2308] Removing new tests from step7 and step9 These will be separated into another branch. --- tests/step7_quote.mal | 5 +---- tests/step9_try.mal | 3 --- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/step7_quote.mal b/tests/step7_quote.mal index 4f3e3569ba..bd5b22fe6f 100644 --- a/tests/step7_quote.mal +++ b/tests/step7_quote.mal @@ -25,8 +25,6 @@ a ;=>(1 2 3 4 5 6) (concat (concat)) ;=>() -(concat (list) (list)) -;=>() (def! a (list 1 2)) (def! b (list 3 4)) @@ -74,8 +72,7 @@ b ;=>(1 b 3) (quasiquote (1 (unquote b) 3)) ;=>(1 (1 "b" "d") 3) -(quasiquote ((unquote 1) (unquote 2))) -;=>(1 2) + ;; Testing splice-unquote (def! c (quote (1 "b" "d"))) diff --git a/tests/step9_try.mal b/tests/step9_try.mal index d50250ecca..17cfae240e 100644 --- a/tests/step9_try.mal +++ b/tests/step9_try.mal @@ -139,9 +139,6 @@ (map (fn* (a) (* 2 a)) [1 2 3]) ;=>(2 4 6) -(map (fn* [& args] (list? args)) [1 2]) -;=>(true true) - ;; Testing vector functions (vector? [10 11]) From 89f7cef6dcc8e85e6db31063bf1d0b10f08c0d18 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 27 Nov 2017 14:07:00 +0000 Subject: [PATCH 0651/2308] Adding test to step7 for quasiquote Handling of the last element of the AST is tested by having an unquote as the last element. --- tests/step7_quote.mal | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/step7_quote.mal b/tests/step7_quote.mal index bd5b22fe6f..973acbc743 100644 --- a/tests/step7_quote.mal +++ b/tests/step7_quote.mal @@ -72,7 +72,8 @@ b ;=>(1 b 3) (quasiquote (1 (unquote b) 3)) ;=>(1 (1 "b" "d") 3) - +(quasiquote ((unquote 1) (unquote 2))) +;=>(1 2) ;; Testing splice-unquote (def! c (quote (1 "b" "d"))) From 40eb57d65c5f95890edfd9d149853c7358a3d481 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 16 Dec 2017 23:52:59 +0000 Subject: [PATCH 0652/2308] Fix bug in concat, add test to step 7 Concatenating multiple empty lists resulted in a return which should be empty but was not checked. (concat (list) (list)) -> () Test added to step 7 to catch, since this appeared in self-hosting step 8 in the -> macro expansion. --- tests/step7_quote.mal | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/step7_quote.mal b/tests/step7_quote.mal index 973acbc743..4f3e3569ba 100644 --- a/tests/step7_quote.mal +++ b/tests/step7_quote.mal @@ -25,6 +25,8 @@ a ;=>(1 2 3 4 5 6) (concat (concat)) ;=>() +(concat (list) (list)) +;=>() (def! a (list 1 2)) (def! b (list 3 4)) From f86d275f081f65e1aa54c0d2d96185fe173ebb76 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 21 Dec 2017 10:56:14 +0000 Subject: [PATCH 0653/2308] Fix bug in map: should pass args as list This appeared only for variadic function which is used in EVAL. Added a test to step 9 to catch this earlier. --- tests/step9_try.mal | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/step9_try.mal b/tests/step9_try.mal index 17cfae240e..d50250ecca 100644 --- a/tests/step9_try.mal +++ b/tests/step9_try.mal @@ -139,6 +139,9 @@ (map (fn* (a) (* 2 a)) [1 2 3]) ;=>(2 4 6) +(map (fn* [& args] (list? args)) [1 2]) +;=>(true true) + ;; Testing vector functions (vector? [10 11]) From bebce74bf5177f51657c0552fea3a20875748649 Mon Sep 17 00:00:00 2001 From: Jonas Lundberg Date: Sat, 30 Dec 2017 19:18:20 +0100 Subject: [PATCH 0654/2308] Update guide with note on reader macro with-meta Guide was missing the deferrable step of having a reader macro for ^ (which is included in the tests for step A) which caused some confusion as to what it was. It's included in the tests/step1_read_print.mal but not mentioned in the docs. --- process/guide.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/process/guide.md b/process/guide.md index 455a76e3f1..025995db4c 100644 --- a/process/guide.md +++ b/process/guide.md @@ -1527,6 +1527,11 @@ diff -urp ../process/step9_try.txt ../process/stepA_mal.txt returned that has its `meta` attribute set to the second argument. Note that it is important that the environment and macro attribute of mal function are retained when it is copied. + * Add a reader-macro that expands the token "^" to + return a new list that contains the symbol "with-meta" and the + result of reading the next next form (2nd argument) (`read_form`) and the + next form (1st argument) in that order + (metadata comes first with the ^ macro and the function second). * Add a new "\*host-language\*" (symbol) entry to your REPL environment. The value of this entry should be a mal string From 3ea9bb9493d43465e516cc1b75e414e5dfd6cd78 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 30 Dec 2017 13:18:34 -0600 Subject: [PATCH 0655/2308] Swift: try forcing Travis to xcode version 7.3 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2f641cb216..c189898bc9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -81,7 +81,7 @@ matrix: - {env: IMPL=scheme scheme_MODE=cyclone, services: [docker]} # - {env: IMPL=scheme scheme_MODE=foment, services: [docker]} - {env: IMPL=skew, services: [docker]} - - {env: IMPL=swift NO_DOCKER=1, os: osx, osx_image: xcode7} + - {env: IMPL=swift NO_DOCKER=1, os: osx, osx_image: xcode7.3} - {env: IMPL=swift3, services: [docker]} - {env: IMPL=swift3 NO_DOCKER=1, os: osx, osx_image: xcode8} - {env: IMPL=tcl, services: [docker]} From 3e30ec9aa1afedf8c465750c2f963cf34ea86894 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 2 Jan 2018 23:37:42 +0000 Subject: [PATCH 0656/2308] Dockerfile xenial and binutils install * Changed from generic ubuntu to ubuntu:xenial * Now requires binutils to be installed for `ld` --- nasm/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nasm/Dockerfile b/nasm/Dockerfile index 53bcd32624..29008471ee 100644 --- a/nasm/Dockerfile +++ b/nasm/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu +FROM ubuntu:xenial MAINTAINER Joel Martin ########################################################## @@ -21,6 +21,6 @@ WORKDIR /mal # Specific implementation requirements ########################################################## -# Install nasm -RUN apt-get -y install nasm +# Install nasm and ld +RUN apt-get -y install nasm binutils From 918f2bee967f88ee1b5ff17620c0a1e97cc26f1b Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 2 Jan 2018 23:39:01 +0000 Subject: [PATCH 0657/2308] Bump language count in README Updated to 72 languages --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c423c24607..cc88deb97d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 71 languages: +Mal is implemented in 72 languages: * Ada * GNU awk From c4152b5fc2de09fb00c907caacbce6f427a9b2f1 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Tue, 2 Jan 2018 23:48:51 +0000 Subject: [PATCH 0658/2308] Add stats and stats-lisp targets Updated list of source files, added targets with `;` as the comment character --- nasm/Makefile | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/nasm/Makefile b/nasm/Makefile index 98028e93a2..9ec65de2f1 100644 --- a/nasm/Makefile +++ b/nasm/Makefile @@ -1,7 +1,7 @@ STEPS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do step5_tco step6_file step7_quote step8_macros step9_try stepA_mal -COMPONENTS=core.asm reader.asm printer.asm types.asm system.asm +COMPONENTS = env.asm core.asm reader.asm printer.asm types.asm system.asm exceptions.asm all: $(STEPS) @@ -11,3 +11,17 @@ all: $(STEPS) %: %.o ld -o $@ $< +###################### + +SOURCES_BASE = reader.asm printer.asm types.asm system.asm exceptions.asm +SOURCES_LISP = env.asm core.asm stepA_mal.asm +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +.PHONY: stats stats-lisp + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" From 20e02704c4974071894528c59ea84af82ffc4b76 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 3 Jan 2018 15:43:22 +0000 Subject: [PATCH 0659/2308] Add clean target Deletes executables and object files --- nasm/Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nasm/Makefile b/nasm/Makefile index 9ec65de2f1..bb690ea120 100644 --- a/nasm/Makefile +++ b/nasm/Makefile @@ -3,6 +3,7 @@ STEPS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do step5_tc COMPONENTS = env.asm core.asm reader.asm printer.asm types.asm system.asm exceptions.asm + all: $(STEPS) %.o: %.asm $(COMPONENTS) @@ -11,6 +12,10 @@ all: $(STEPS) %: %.o ld -o $@ $< +.PHONY: clean +clean: + rm -f $(STEPS) $(STEPS:%=%.o) + ###################### SOURCES_BASE = reader.asm printer.asm types.asm system.asm exceptions.asm From 39b031b7b83be6140b604ca5adc2144444314eea Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 3 Jan 2018 15:47:05 +0000 Subject: [PATCH 0660/2308] Adding nasm to Travis yml file Enables testing of NASM implementation --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 2f641cb216..7a040ee48a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,6 +51,7 @@ matrix: - {env: IMPL=mal BUILD_IMPL=js NO_PERF=1, services: [docker]} - {env: IMPL=matlab, services: [docker]} # Uses Octave - {env: IMPL=miniMAL, services: [docker]} + - {env: IMPL=nasm, services: [docker]} - {env: IMPL=nim, services: [docker]} - {env: IMPL=objpascal, services: [docker]} - {env: IMPL=objc NO_DOCKER=1, os: osx, osx_image: xcode7} From 2f61f4a8d300085e8389817d81e776a1ec84a49a Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Wed, 3 Jan 2018 23:50:34 +0000 Subject: [PATCH 0661/2308] Tidying up steps 0-3 (in progress) Trying to remove common code, and minimise differences between steps. --- nasm/step0_repl.asm | 275 ++------------------------------------ nasm/step1_read_print.asm | 81 ++++------- nasm/step2_eval.asm | 100 +++++++------- nasm/step3_env.asm | 69 +++++----- 4 files changed, 122 insertions(+), 403 deletions(-) diff --git a/nasm/step0_repl.asm b/nasm/step0_repl.asm index decdf4de2e..eece4e8161 100644 --- a/nasm/step0_repl.asm +++ b/nasm/step0_repl.asm @@ -1,209 +1,25 @@ -;; nasm -felf64 mal.asm && ld mal.o && ./a.out +;; +;; nasm -felf64 step0_repl.asm && ld step0_repl.o && ./a.out ;; Calling convention: Address of input is in RSI ;; Address of return value is in RAX ;; -;; Data structures -;; Memory management is done by having two fixed-size datatypes, -;; Cons and Array. -;; -;; Both Cons and Array have the following in common: -;; a type field at the start, a reference count, followed by data -;; [ type (8) | (8) | refs (16) | data ] - +global _start -;; -STRUC Cons -.typecar: RESB 1 ; Type information for car (8 bit) -.typecdr: RESB 1 ; Type information for cdr (8 bits) -.refcount: RESW 1 ; Number of references to this Cons (16 bit) -.car: RESQ 1 ; First value (64 bit) -.cdr: RESQ 1 ; Second value (64 bit) -.size: ; Total size of struc -ENDSTRUC - - -%define array_chunk_len 32 ; Number of 64-bit values which can be stored in a single chunk - -STRUC Array -.type: RESB 1 ; Type information (8 bits) -.control: RESB 1 ; Control data (8 bits) -.refcount: RESW 1 ; Number of references to this Array (16 bit) -.length: RESD 1 ; Number of elements in array (32 bit) -.next RESQ 1 ; Pointer to the next chunk (64 bit) -.data: RESQ array_chunk_len ; Data storage -.size: ; Total size of struc -ENDSTRUC - -;; Type information -%define type_char 1 ; Character type -%define type_integer 2 ; Integer type -%define type_float 3 ; Floating point number -%define type_array 128 ; Last bit tests if array or cons - - - - global _start +%include "types.asm" ; Data types, memory +%include "system.asm" ; System calls +%include "printer.asm" ; Data structures -> String +%include "exceptions.asm" ; Error handling section .data -str: ISTRUC Array -AT Array.type, db type_char + type_array -AT Array.length, dd 6 -AT Array.data, db 'hello',10 -IEND - -prompt_string: db 10,"user> " ; The string to print at the prompt -.len: equ $ - prompt_string - -error_msg_print_string: db "Error in print string",10 -.len: equ $ - error_msg_print_string - -section .bss - -section .text - ;; ------------------------------------------ -;; Array alloc_array() -;; -;; Returns the address of an Array object in RAX -alloc_array: - mov rax, str - ret - -;; ------------------------------------------- -;; Prints a raw string to stdout -;; String address in rsi, string length in rdx -print_rawstring: - push rax - push rdi +;; Fixed strings for printing - ; write(1, string, length) - mov rax, 1 ; system call 1 is write - mov rdi, 1 ; file handle 1 is stdout - syscall - - pop rdi - pop rax - - ret + static prompt_string, db 10,"user> " ; The string to print at the prompt -;; ------------------------------------------ -;; void print_string(char array) -;; Address of the char Array should be in RSI -print_string: - ; Push registers we're going to use - push rax - push rdi - push rdx - push rsi - - ; Check that we have a char array - mov al, [rsi] - cmp al, type_char + type_array - jne .error - - ; write(1, string, length) - mov edx, [rsi + Array.length] ; number of bytes - add rsi, Array.data ; address of raw string to output - call print_rawstring - - ; Restore registers - pop rsi - pop rdx - pop rdi - pop rax - - ret -.error: - ; An error occurred - mov rdx, error_msg_print_string.len ; number of bytes - mov rsi, error_msg_print_string ; address of raw string to output - call print_rawstring - ; exit - jmp quit_error - -;; ------------------------------------------ -;; String itostring(Integer number) -;; -;; Converts an integer to a string (array of chars) -;; -;; Input in RAX -;; Return string address in RAX -itostring: - ; Save registers to restore afterwards - push rcx - push rdx - push rsi - push rdi - - mov rcx, 0 ; counter of how many bytes we need to print in the end - -.divideLoop: - inc rcx ; count each byte to print - number of characters - xor rdx, rdx - mov rsi, 10 - idiv rsi ; divide rax by rsi - add rdx, 48 ; convert rdx to it's ascii representation - rdx holds the remainder after a divide instruction - ; Character is now in DL - dec rsp - mov BYTE [rsp], dl ; Put onto stack - - cmp rax, 0 ; can the integer be divided anymore? - jnz .divideLoop ; jump if not zero to the label divideLoop - - ; Get an Array object to put the string into - call alloc_array ; Address in RAX - - ; put length into string - mov [rax + Array.length], ecx - - ; copy data from stack into string - ; Note: Currently this does not handle long strings - mov rdi, rax - add rdi, Array.data ; Address where raw string will go -.copyLoop: - mov BYTE dl, [rsp] ; Copy one byte at a time. Could be more efficient - mov [rdi], BYTE dl - inc rsp - inc rdi - dec rcx - cmp rcx, 0 - jnz .copyLoop - - ; Restore registers - pop rdi - pop rsi - pop rdx - pop rcx - - ret - -;; ---------------------------- -;; int stringtoi(String) -;; -;; Convert a string (char array) to an integer -;; -;; Address of input string is in RSI -;; Output integer in RAX -stringtoi: - - ret - -;------------------------------------------ -; void exit() -; Exit program and restore resources -quit: - mov eax, 60 ; system call 60 is exit - xor rdi, rdi ; exit code 0 - syscall ; invoke operating system to exit - -quit_error: - mov eax, 60 ; system call 60 is exit - mov rdi, 1 ; exit code 1 - syscall - +section .text ;; Takes a string as input and processes it into a form read: @@ -230,65 +46,7 @@ rep_seq: mov rsi, rax ; Return value ret -;; Read a line from stdin -;; Gets a new string array, fills it until a newline or EOF is reached -;; Returns pointer to string in RAX -read_line: - ; Get an array to put the string into - ; Address in rax - call alloc_array - ; Mark it as a character array (string) - mov BYTE [rax + Array.type], type_char + type_array - - push rax ; Save pointer to string - - ; Read character by character until either newline or end of input - mov ebx, 0 ; Count how many characters read - mov rsi, rax - add rsi, Array.data ; Point to the data -.readLoop: - mov rax, 0 ; sys_read - mov rdi, 0 ; stdin - mov rdx, 1 ; count - syscall - - ; Characters read in RAX - cmp rax, 0 ; end loop if read <= 0 - jle .readLoopEnd - - mov cl, BYTE [rsi] - - cmp cl, 10 ; End if we read a newline - je .readLoopEnd - - cmp cl, 8 ; Backspace? - je .handleBackspace - - cmp cl, 31 ; Below space - jle .readLoop ; Ignore, keep going - - cmp cl, 127 ; DEL or above - jge .readLoop ; Ignore, keep going - - inc ebx - inc rsi ; Move to next point in the array - jmp .readLoop ; Get another character - -.handleBackspace: - ; Check if we've read any characters - cmp ebx, 0 - je .readLoop ; If not, carry on the loop - ; Characters have been read. Remove one - dec ebx - dec rsi - jmp .readLoop -.readLoopEnd: - pop rax ; Restore pointer to string - mov DWORD [rax + Array.length], ebx ; Set string length - ret - - _start: ; ----------------------------- @@ -296,8 +54,7 @@ _start: .mainLoop: ; print the prompt - mov rdx, prompt_string.len ; number of bytes - mov rsi, prompt_string ; address of raw string to output + load_static prompt_string ; Into RSI and EDX call print_rawstring call read_line @@ -312,15 +69,5 @@ _start: jmp .mainLoop .mainLoopEnd: - ;mov rdx, 1 - ;mov rsi, - ;call print_rawstring - ;inc rsp - - ;mov rax, 1223 - ;call itostring - ;mov rsi, rax - ;call print_string - jmp quit diff --git a/nasm/step1_read_print.asm b/nasm/step1_read_print.asm index 5aa2fe5504..f64d321561 100644 --- a/nasm/step1_read_print.asm +++ b/nasm/step1_read_print.asm @@ -15,57 +15,43 @@ global _start section .data -test_string1: db 10, "test1", 10 -.len: equ $ - test_string1 - -test_string2: db 10, "test2", 10 -.len: equ $ - test_string2 - -;str: ISTRUC Array -;AT Array.type, db maltype_string -;AT Array.length, dd 6 -;AT Array.data, db 'hello',10 -;IEND - -test_cons: ISTRUC Cons -AT Cons.typecar, db ( maltype_integer + 2 ) -AT Cons.typecdr, db 0 -AT Cons.car, dq 123 -IEND - -test_cons2: ISTRUC Cons -AT Cons.typecar, db ( maltype_integer + 2 ) -AT Cons.typecdr, db content_pointer -AT Cons.car, dq 456 -AT Cons.cdr, dq test_cons -IEND - ;; ------------------------------------------ ;; Fixed strings for printing + + static prompt_string, db 10,"user> " ; The string to print at the prompt -prompt_string: db 10,"user> " ; The string to print at the prompt -.len: equ $ - prompt_string - -section .text +section .text + +;; Takes a string as input and processes it into a form +read: + jmp read_str ; In reader.asm -;; Evaluates a form +;; Evaluates a form in RSI eval: mov rax, rsi ; Return the input ret ;; Prints the result print: - mov rax, rsi ; Return the input - ret + mov rdi, 1 ; print readably + jmp pr_str ;; Read-Eval-Print in sequence rep_seq: - call read_str + call read + push rax ; Save form + mov rsi, rax ; Output of read into input of eval call eval - mov rsi, rax ; Output of eval into input of print - call print - mov rsi, rax ; Return value + + mov rsi, rax ; Output of eval into input of print + call print ; String in RAX + + mov r8, rax ; Save output + pop rsi ; Form returned by read + call release_object + mov rax, r8 + ret @@ -76,8 +62,7 @@ _start: .mainLoop: ; print the prompt - mov rdx, prompt_string.len ; number of bytes - mov rsi, prompt_string ; address of raw string to output + load_static prompt_string ; Into RSI and EDX call print_rawstring call read_line @@ -88,29 +73,19 @@ _start: push rax ; Save address of the string - ; Put into read_str - mov rsi, rax - call read_str - push rax - - ; Put into pr_str mov rsi, rax - mov rdi, 1 ; print readably - call pr_str - push rax + call rep_seq ; Read-Eval-Print + + push rax ; Save returned string mov rsi, rax ; Put into input of print_string call print_string - ; Release string from pr_str + ; Release string from rep_seq pop rsi call release_array - ; Release the object from read_str - pop rsi - call release_object ; Could be Cons or Array - - ; Release the string + ; Release the input string pop rsi call release_array diff --git a/nasm/step2_eval.asm b/nasm/step2_eval.asm index 2a495c62be..592fbb0fa8 100644 --- a/nasm/step2_eval.asm +++ b/nasm/step2_eval.asm @@ -19,37 +19,38 @@ section .bss ;; Top-level (REPL) environment repl_env:resq 1 - + section .data ;; ------------------------------------------ ;; Fixed strings for printing -prompt_string: db 10,"user> " ; The string to print at the prompt -.len: equ $ - prompt_string + static prompt_string, db 10,"user> " ; The string to print at the prompt -error_string: db 27,'[31m',"Error",27,'[0m',": " -.len: equ $ - error_string + static error_string, db 27,'[31m',"Error",27,'[0m',": " -def_symbol: ISTRUC Array -AT Array.type, db maltype_symbol -AT Array.length, dd 4 -AT Array.data, db 'def!' -IEND - -let_symbol: ISTRUC Array -AT Array.type, db maltype_symbol -AT Array.length, dd 4 -AT Array.data, db 'let*' -IEND + +;; Symbols used for comparison + static_symbol def_symbol, 'def!' + static_symbol let_symbol, 'let*' + section .text +;; Takes a string as input and processes it into a form +read: + jmp read_str ; In reader.asm + ;; This is a dummy function so that core routines compile apply_fn: jmp quit + +;; ---------------------------------------------- ;; Evaluates a form in RSI +;; +;; Inputs: RSI Form to evaluate +;; eval_ast: ; Check the type mov al, BYTE [rsi] @@ -104,7 +105,8 @@ eval_ast: cmp ah, content_pointer je .list_pointer - ; A value, so copy + ; A value in RSI, so copy + call alloc_cons mov bl, BYTE [rsi] and bl, content_mask @@ -393,7 +395,13 @@ eval_ast: .done: ret -;; Evaluates a form in RSI +;; ---------------------------------------------------- +;; Evaluates a form +;; +;; Input: RSI Form to evaluate +;; +;; Returns: Result in RAX +;; eval: ; Check type mov al, BYTE [rsi] @@ -489,17 +497,29 @@ eval: ;; Prints the result print: - mov rax, rsi ; Return the input - ret + mov rdi, 1 ; print readably + jmp pr_str ;; Read-Eval-Print in sequence rep_seq: - call read_str + call read + push rax ; Save form + mov rsi, rax ; Output of read into input of eval call eval - mov rsi, rax ; Output of eval into input of print - call print - mov rsi, rax ; Return value + push rax ; Save result + + mov rsi, rax ; Output of eval into input of print + call print ; String in RAX + + mov r8, rax ; Save output + + pop rsi ; Result from eval + call release_object + pop rsi ; Form returned by read + call release_object + mov rax, r8 + ret @@ -520,8 +540,7 @@ _start: .mainLoop: ; print the prompt - mov rdx, prompt_string.len ; number of bytes - mov rsi, prompt_string ; address of raw string to output + load_static prompt_string ; Into RSI and EDX call print_rawstring call read_line @@ -530,38 +549,19 @@ _start: cmp DWORD [rax+Array.length], 0 je .mainLoopEnd - push rax ; Save address of the input string - - ; Put into read_str - mov rsi, rax - call read_str - push rax ; Save AST + push rax ; Save address of the string - ; Eval mov rsi, rax - call eval - push rax ; Save result - - ; Put into pr_str - mov rsi, rax - mov rdi, 1 ; print readably - call pr_str - push rax ; Save output string + call rep_seq ; Read-Eval-Print + + push rax ; Save returned string mov rsi, rax ; Put into input of print_string call print_string - ; Release string from pr_str + ; Release string from rep_seq pop rsi call release_array - - ; Release result of eval - pop rsi - call release_object - - ; Release the object from read_str - pop rsi - call release_object ; Could be Cons or Array ; Release the input string pop rsi diff --git a/nasm/step3_env.asm b/nasm/step3_env.asm index 132f72529e..e75da577d8 100644 --- a/nasm/step3_env.asm +++ b/nasm/step3_env.asm @@ -19,7 +19,7 @@ section .bss ;; Top-level (REPL) environment repl_env:resq 1 - + section .data ;; ------------------------------------------ @@ -50,8 +50,12 @@ section .data static_symbol def_symbol, 'def!' static_symbol let_symbol, 'let*' - -section .text + +section .text + +;; Takes a string as input and processes it into a form +read: + jmp read_str ; In reader.asm ;; This is a dummy function so that core routines compile apply_fn: @@ -59,7 +63,7 @@ apply_fn: ;; ---------------------------------------------- -;; Evaluates a form +;; Evaluates a form in RSI ;; ;; Inputs: RSI Form to evaluate ;; RDI Environment @@ -869,17 +873,30 @@ eval: ;; Prints the result print: - mov rax, rsi ; Return the input - ret + mov rdi, 1 ; print readably + jmp pr_str ;; Read-Eval-Print in sequence rep_seq: - call read_str + call read + push rax ; Save form + mov rsi, rax ; Output of read into input of eval + mov rdi, [repl_env] ; Environment call eval - mov rsi, rax ; Output of eval into input of print - call print - mov rsi, rax ; Return value + push rax ; Save result + + mov rsi, rax ; Output of eval into input of print + call print ; String in RAX + + mov r8, rax ; Save output + + pop rsi ; Result from eval + call release_object + pop rsi ; Form returned by read + call release_object + mov rax, r8 + ret @@ -900,8 +917,7 @@ _start: .mainLoop: ; print the prompt - mov rdx, prompt_string.len ; number of bytes - mov rsi, prompt_string ; address of raw string to output + load_static prompt_string ; Into RSI and EDX call print_rawstring call read_line @@ -910,38 +926,19 @@ _start: cmp DWORD [rax+Array.length], 0 je .mainLoopEnd - push rax ; Save address of the input string - - ; Put into read_str + push rax ; Save address of the string + mov rsi, rax - call read_str - push rax ; Save AST + call rep_seq ; Read-Eval-Print - ; Eval - mov rsi, rax ; Form to evaluate - mov rdi, [repl_env] ; Environment - call eval - push rax ; Save result - - ; Put into pr_str - mov rsi, rax - call pr_str - push rax ; Save output string + push rax ; Save returned string mov rsi, rax ; Put into input of print_string call print_string - ; Release string from pr_str + ; Release string from rep_seq pop rsi call release_array - - ; Release result of eval - pop rsi - call release_object - - ; Release the object from read_str - pop rsi - call release_object ; Could be Cons or Array ; Release the input string pop rsi From 16ca3eee57bb1be320618b2c822d1c4a4425c642 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 4 Jan 2018 14:36:53 +0000 Subject: [PATCH 0662/2308] Removing env and core from step2 Changed Env to Map. Need to define arithmetic operators in step2 rather than core for this step. --- nasm/step2_eval.asm | 135 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 119 insertions(+), 16 deletions(-) diff --git a/nasm/step2_eval.asm b/nasm/step2_eval.asm index 592fbb0fa8..83504df9d8 100644 --- a/nasm/step2_eval.asm +++ b/nasm/step2_eval.asm @@ -8,10 +8,8 @@ global _start %include "types.asm" ; Data types, memory -%include "env.asm" ; Environment type %include "system.asm" ; System calls %include "reader.asm" ; String -> Data structures -%include "core.asm" ; Core functions %include "printer.asm" ; Data structures -> String %include "exceptions.asm" ; Error handling @@ -34,18 +32,121 @@ section .data static_symbol def_symbol, 'def!' static_symbol let_symbol, 'let*' - + + static core_add_symbol, db "+" + static core_sub_symbol, db "-" + static core_mul_symbol, db "*" + static core_div_symbol, db "/" + section .text +;; Integer arithmetic operations +;; +;; Adds a list of numbers, address in RSI +;; Returns the sum as a number object with address in RAX +;; Since most of the code is common to all operators, +;; RBX is used to jump to the required instruction +core_add: + mov rbx, core_arithmetic.do_addition + jmp core_arithmetic +core_sub: + mov rbx, core_arithmetic.do_subtraction + jmp core_arithmetic +core_mul: + mov rbx, core_arithmetic.do_multiply + jmp core_arithmetic +core_div: + mov rbx, core_arithmetic.do_division + ; Fall through to core_arithmetic +core_arithmetic: + ; Check that the first object is a number + mov cl, BYTE [rsi] + mov ch, cl + and ch, block_mask + cmp ch, block_cons + jne .missing_args + + mov ch, cl + and ch, content_mask + cmp ch, content_empty + je .missing_args + + cmp ch, content_int + jne .not_int + + ; Put the starting value in rax + mov rax, [rsi + Cons.car] + +.add_loop: + ; Fetch the next value + mov cl, [rsi + Cons.typecdr] + cmp cl, content_pointer + jne .finished ; Nothing let + + mov rsi, [rsi + Cons.cdr] ; Get next cons + + ; Check that it is an integer + mov cl, BYTE [rsi] + and cl, content_mask + cmp cl, content_int + jne .not_int + + ; Jump to the required operation, address in RBX + jmp rbx + +.do_addition: + add rax, [rsi + Cons.car] + jmp .add_loop +.do_subtraction: + sub rax, [rsi + Cons.car] + jmp .add_loop +.do_multiply: + imul rax, [rsi + Cons.car] + jmp .add_loop +.do_division: + cqo ; Sign extend RAX into RDX + mov rcx, [rsi + Cons.car] + idiv rcx + jmp .add_loop + +.finished: + ; Value in rbx + push rax + ; Get a Cons object to put the result into + call alloc_cons + pop rbx + mov [rax], BYTE maltype_integer + mov [rax + Cons.car], rbx + ret + +.missing_args: +.not_int: + jmp quit + +;; Add a native function to the core environment +;; This is used in core_environment +%macro core_env_native 2 + push rsi ; environment + mov rsi, %1 + mov edx, %1.len + call raw_to_symbol ; Symbol in RAX + push rax + + mov rsi, %2 + call native_function ; Function in RAX + + mov rcx, rax ; value (function) + pop rdi ; key (symbol) + pop rsi ; environment + call map_set +%endmacro + + + ;; Takes a string as input and processes it into a form read: jmp read_str ; In reader.asm -;; This is a dummy function so that core routines compile -apply_fn: - jmp quit - - ;; ---------------------------------------------- ;; Evaluates a form in RSI ;; @@ -81,7 +182,7 @@ eval_ast: ; look in environment mov rdi, rsi ; symbol is the key mov rsi, [repl_env] ; Environment - call env_get + call map_get je .done ; result in RAX ; Not found, should raise an error @@ -525,16 +626,18 @@ rep_seq: _start: ; Create and print the core environment - call core_environment ; Environment in RAX + call map_new ; Environment in RAX mov [repl_env], rax ; store in memory - - mov rsi, rax - call pr_str - - mov rsi, rax ; Put into input of print_string - call print_string + mov rsi, rax ; Environment + + ; Add +,-,*,/ to environment + core_env_native core_add_symbol, core_add + core_env_native core_sub_symbol, core_sub + core_env_native core_mul_symbol, core_mul + core_env_native core_div_symbol, core_div + ; ----------------------------- ; Main loop From 9cb95668977bf7948bf3e5dd7d7a304e75a9c0f1 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Thu, 4 Jan 2018 16:05:03 +0000 Subject: [PATCH 0663/2308] Tidying, minimising differences between steps Merging fixes in stepA into earlier steps, and removing differences in comments and spacing --- nasm/step0_repl.asm | 19 ++- nasm/step1_read_print.asm | 24 +++- nasm/step2_eval.asm | 26 ++-- nasm/step3_env.asm | 213 +++++++++++++++++++++++------- nasm/step4_if_fn_do.asm | 88 +++++++------ nasm/step5_tco.asm | 56 ++++---- nasm/step6_file.asm | 56 ++++---- nasm/step7_quote.asm | 264 +++++++++++++------------------------- nasm/step8_macros.asm | 82 +++++++----- nasm/step9_try.asm | 80 +++++++----- nasm/stepA_mal.asm | 50 +++----- 11 files changed, 530 insertions(+), 428 deletions(-) diff --git a/nasm/step0_repl.asm b/nasm/step0_repl.asm index eece4e8161..850f69ee81 100644 --- a/nasm/step0_repl.asm +++ b/nasm/step0_repl.asm @@ -16,7 +16,7 @@ section .data ;; ------------------------------------------ ;; Fixed strings for printing - + static prompt_string, db 10,"user> " ; The string to print at the prompt section .text @@ -25,7 +25,8 @@ section .text read: mov rax, rsi ; Return the input ret - + +;; ---------------------------------------------- ;; Evaluates a form eval: mov rax, rsi ; Return the input @@ -38,12 +39,21 @@ print: ;; Read-Eval-Print in sequence rep_seq: + ; ------------- + ; Read call read + + ; ------------- + ; Eval mov rsi, rax ; Output of read into input of eval call eval + + ; ------------- + ; Print + mov rsi, rax ; Output of eval into input of print call print - mov rsi, rax ; Return value + ret @@ -54,8 +64,7 @@ _start: .mainLoop: ; print the prompt - load_static prompt_string ; Into RSI and EDX - call print_rawstring + print_str_mac prompt_string call read_line diff --git a/nasm/step1_read_print.asm b/nasm/step1_read_print.asm index f64d321561..e02ebee968 100644 --- a/nasm/step1_read_print.asm +++ b/nasm/step1_read_print.asm @@ -17,7 +17,7 @@ section .data ;; ------------------------------------------ ;; Fixed strings for printing - + static prompt_string, db 10,"user> " ; The string to print at the prompt section .text @@ -25,8 +25,12 @@ section .text ;; Takes a string as input and processes it into a form read: jmp read_str ; In reader.asm - -;; Evaluates a form in RSI + +;; ---------------------------------------------- +;; Evaluates a form +;; +;; Inputs: RSI Form to evaluate +;; eval: mov rax, rsi ; Return the input ret @@ -38,13 +42,20 @@ print: ;; Read-Eval-Print in sequence rep_seq: + ; ------------- + ; Read call read push rax ; Save form - + + ; ------------- + ; Eval mov rsi, rax ; Output of read into input of eval call eval + + ; ------------- + ; Print - mov rsi, rax ; Output of eval into input of print + mov rsi, rax ; Output of eval into input of print call print ; String in RAX mov r8, rax ; Save output @@ -62,8 +73,7 @@ _start: .mainLoop: ; print the prompt - load_static prompt_string ; Into RSI and EDX - call print_rawstring + print_str_mac prompt_string call read_line diff --git a/nasm/step2_eval.asm b/nasm/step2_eval.asm index 83504df9d8..119871bbaa 100644 --- a/nasm/step2_eval.asm +++ b/nasm/step2_eval.asm @@ -148,7 +148,7 @@ read: jmp read_str ; In reader.asm ;; ---------------------------------------------- -;; Evaluates a form in RSI +;; Evaluates a form ;; ;; Inputs: RSI Form to evaluate ;; @@ -499,7 +499,7 @@ eval_ast: ;; ---------------------------------------------------- ;; Evaluates a form ;; -;; Input: RSI Form to evaluate +;; Input: RSI AST to evaluate ;; ;; Returns: Result in RAX ;; @@ -520,18 +520,18 @@ eval: ; -------------------- .list: ; A list - + ; Check if the first element is a symbol mov al, BYTE [rsi] - and bl, content_mask - cmp bl, content_pointer + and al, content_mask + cmp al, content_pointer jne .list_eval mov rbx, [rsi + Cons.car] mov al, BYTE [rbx] cmp al, maltype_symbol jne .list_eval - + ; Is a symbol, address in RBX push rsi @@ -542,7 +542,7 @@ eval: pop rsi cmp rax, 0 je .def_symbol - + push rsi mov rdi, let_symbol call compare_char_array @@ -603,13 +603,20 @@ print: ;; Read-Eval-Print in sequence rep_seq: + ; ------------- + ; Read call read push rax ; Save form - + + ; ------------- + ; Eval mov rsi, rax ; Output of read into input of eval call eval push rax ; Save result + ; ------------- + ; Print + mov rsi, rax ; Output of eval into input of print call print ; String in RAX @@ -643,8 +650,7 @@ _start: .mainLoop: ; print the prompt - load_static prompt_string ; Into RSI and EDX - call print_rawstring + print_str_mac prompt_string call read_line diff --git a/nasm/step3_env.asm b/nasm/step3_env.asm index e75da577d8..70460bafae 100644 --- a/nasm/step3_env.asm +++ b/nasm/step3_env.asm @@ -11,7 +11,6 @@ global _start %include "env.asm" ; Environment type %include "system.asm" ; System calls %include "reader.asm" ; String -> Data structures -%include "core.asm" ; Core functions %include "printer.asm" ; Data structures -> String %include "exceptions.asm" ; Error handling @@ -29,7 +28,7 @@ section .data static error_string, db 27,'[31m',"Error",27,'[0m',": " - static not_found_string, db " not found.",10 + static not_found_string, db " not found" static def_missing_arg_string, db "missing argument to def!",10 @@ -50,20 +49,123 @@ section .data static_symbol def_symbol, 'def!' static_symbol let_symbol, 'let*' - + + static core_add_symbol, db "+" + static core_sub_symbol, db "-" + static core_mul_symbol, db "*" + static core_div_symbol, db "/" + section .text +;; Integer arithmetic operations +;; +;; Adds a list of numbers, address in RSI +;; Returns the sum as a number object with address in RAX +;; Since most of the code is common to all operators, +;; RBX is used to jump to the required instruction +core_add: + mov rbx, core_arithmetic.do_addition + jmp core_arithmetic +core_sub: + mov rbx, core_arithmetic.do_subtraction + jmp core_arithmetic +core_mul: + mov rbx, core_arithmetic.do_multiply + jmp core_arithmetic +core_div: + mov rbx, core_arithmetic.do_division + ; Fall through to core_arithmetic +core_arithmetic: + ; Check that the first object is a number + mov cl, BYTE [rsi] + mov ch, cl + and ch, block_mask + cmp ch, block_cons + jne .missing_args + + mov ch, cl + and ch, content_mask + cmp ch, content_empty + je .missing_args + + cmp ch, content_int + jne .not_int + + ; Put the starting value in rax + mov rax, [rsi + Cons.car] + +.add_loop: + ; Fetch the next value + mov cl, [rsi + Cons.typecdr] + cmp cl, content_pointer + jne .finished ; Nothing let + + mov rsi, [rsi + Cons.cdr] ; Get next cons + + ; Check that it is an integer + mov cl, BYTE [rsi] + and cl, content_mask + cmp cl, content_int + jne .not_int + + ; Jump to the required operation, address in RBX + jmp rbx + +.do_addition: + add rax, [rsi + Cons.car] + jmp .add_loop +.do_subtraction: + sub rax, [rsi + Cons.car] + jmp .add_loop +.do_multiply: + imul rax, [rsi + Cons.car] + jmp .add_loop +.do_division: + cqo ; Sign extend RAX into RDX + mov rcx, [rsi + Cons.car] + idiv rcx + jmp .add_loop + +.finished: + ; Value in rbx + push rax + ; Get a Cons object to put the result into + call alloc_cons + pop rbx + mov [rax], BYTE maltype_integer + mov [rax + Cons.car], rbx + ret + +.missing_args: +.not_int: + jmp quit + +;; Add a native function to the core environment +;; This is used in core_environment +%macro core_env_native 2 + push rsi ; environment + mov rsi, %1 + mov edx, %1.len + call raw_to_symbol ; Symbol in RAX + push rax + + mov rsi, %2 + call native_function ; Function in RAX + + mov rcx, rax ; value (function) + pop rdi ; key (symbol) + pop rsi ; environment + call env_set +%endmacro + + + ;; Takes a string as input and processes it into a form read: jmp read_str ; In reader.asm -;; This is a dummy function so that core routines compile -apply_fn: - jmp quit - - ;; ---------------------------------------------- -;; Evaluates a form in RSI +;; Evaluates a form ;; ;; Inputs: RSI Form to evaluate ;; RDI Environment @@ -108,19 +210,15 @@ eval_ast: ; Not found, throw an error push rsi - mov rsi, error_string - mov rdx, error_string.len - call print_rawstring ; print 'Error: ' - + print_str_mac error_string ; print 'Error: ' + pop rsi push rsi mov edx, [rsi + Array.length] add rsi, Array.data call print_rawstring ; print symbol - - mov rsi, not_found_string - mov rdx, not_found_string.len - call print_rawstring ; print ' not found' + + print_str_mac not_found_string ; print ' not found' pop rsi jmp error_throw @@ -475,10 +573,27 @@ eval_ast: .done: ret + + +;; Comparison of symbols for eval function +;; Compares the symbol in RSI with specified symbol +;; Preserves RSI and RBX +;; Modifies RDI +%macro eval_cmp_symbol 1 + push rsi + push rbx + mov rsi, rbx + mov rdi, %1 + call compare_char_array + pop rbx + pop rsi + test rax, rax ; ZF set if rax = 0 (equal) +%endmacro + ;; ---------------------------------------------------- ;; Evaluates a form ;; -;; Input: RSI Form to evaluate +;; Input: RSI AST to evaluate ;; RDI Environment ;; ;; Returns: Result in RAX @@ -515,26 +630,13 @@ eval: jne .list_eval ; Is a symbol, address in RBX - push rsi - push rbx - ; Compare against def! - mov rsi, rbx - mov rdi, def_symbol - call compare_char_array - pop rbx - pop rsi - cmp rax, 0 + ; Compare against special form symbols + + eval_cmp_symbol def_symbol ; def! je .def_symbol - push rsi - push rbx - mov rsi, rbx - mov rdi, let_symbol - call compare_char_array - pop rbx - pop rsi - cmp rax, 0 + eval_cmp_symbol let_symbol ; let* je .let_symbol ; Unrecognised @@ -627,9 +729,7 @@ eval: .def_handle_error: push rsi push rdx - mov rsi, error_string - mov rdx, error_string.len - call print_rawstring ; print 'Error: ' + print_str_mac error_string ; print 'Error: ' pop rdx pop rsi @@ -645,7 +745,7 @@ eval: mov r11, rsi ; Let form in R11 mov rsi, r15 ; Outer env - call env_new + call env_new ; Increments R15's ref count mov r14, rax ; New environment in R14 ; Second element should be the bindings @@ -818,9 +918,8 @@ eval: push rsi push rdx - mov rsi, error_string - mov rdx, error_string.len - call print_rawstring ; print 'Error: ' + + print_str_mac error_string ; print 'Error: ' pop rdx pop rsi @@ -832,11 +931,12 @@ eval: ; ----------------------------- .list_eval: - + push rsi mov rdi, r15 ; Environment push r15 - call eval_ast + call eval_ast ; List of evaluated forms in RAX pop r15 + pop rsi ; Check that the first element of the return is a function mov bl, BYTE [rax] @@ -878,14 +978,21 @@ print: ;; Read-Eval-Print in sequence rep_seq: + ; ------------- + ; Read call read push rax ; Save form - - mov rsi, rax ; Output of read into input of eval + + ; ------------- + ; Eval + mov rsi, rax ; Form to evaluate mov rdi, [repl_env] ; Environment call eval push rax ; Save result + ; ------------- + ; Print + mov rsi, rax ; Output of eval into input of print call print ; String in RAX @@ -902,10 +1009,18 @@ rep_seq: _start: ; Create and print the core environment - call core_environment ; Environment in RAX + call env_new ; Environment in RAX mov [repl_env], rax ; store in memory + mov rsi, rax ; Environment + + ; Add +,-,*,/ to environment + core_env_native core_add_symbol, core_add + core_env_native core_sub_symbol, core_sub + core_env_native core_mul_symbol, core_mul + core_env_native core_div_symbol, core_div + ; Set the error handler mov rsi, rsp ; Stack pointer mov rdi, .catch ; Address to jump to @@ -917,8 +1032,7 @@ _start: .mainLoop: ; print the prompt - load_static prompt_string ; Into RSI and EDX - call print_rawstring + print_str_mac prompt_string call read_line @@ -955,6 +1069,7 @@ _start: ; Check if an object was thrown cmp rsi, 0 je .catch_done_print ; nothing to print + mov rdi, 1 call pr_str mov rsi, rax call print_string diff --git a/nasm/step4_if_fn_do.asm b/nasm/step4_if_fn_do.asm index 110cc2d66c..27343a0ed7 100644 --- a/nasm/step4_if_fn_do.asm +++ b/nasm/step4_if_fn_do.asm @@ -19,7 +19,7 @@ section .bss ;; Top-level (REPL) environment repl_env:resq 1 - + section .data ;; ------------------------------------------ @@ -29,7 +29,7 @@ section .data static error_string, db 27,'[31m',"Error",27,'[0m',": " - static not_found_string, db " not found.",10 + static not_found_string, db " not found" static def_missing_arg_string, db "missing argument to def!",10 @@ -498,13 +498,13 @@ eval_ast: call compare_char_array pop rbx pop rsi - cmp rax, 0 + test rax, rax ; ZF set if rax = 0 (equal) %endmacro ;; ---------------------------------------------------- ;; Evaluates a form ;; -;; Input: RSI Form to evaluate +;; Input: RSI AST to evaluate ;; RDI Environment ;; ;; Returns: Result in RAX @@ -531,7 +531,6 @@ eval: ; Check if the first element is a symbol mov al, BYTE [rsi] - and al, content_mask cmp al, content_pointer jne .list_eval @@ -562,6 +561,9 @@ eval: ; Unrecognised jmp .list_eval + + + ; ----------------------------- .def_symbol: ; Define a new symbol in current environment @@ -617,13 +619,13 @@ eval: ; This may throw an error, so define a handler - push r8 ; the symbol push r15 ; Env mov rsi, [rsi + Cons.car] ; Pointer mov rdi, r15 call eval mov rsi, rax + pop r15 pop r8 @@ -666,7 +668,7 @@ eval: mov r11, rsi ; Let form in R11 mov rsi, r15 ; Outer env - call env_new + call env_new ; Increments R15's ref count mov r14, rax ; New environment in R14 ; Second element should be the bindings @@ -885,7 +887,6 @@ eval: mov rsi, rax call release_object .do_next: - mov r11, [r11 + Cons.cdr] ; Next in list jmp .do_symbol_loop @@ -954,7 +955,7 @@ eval: pop r11 pop r15 - ; Get type + ; Get type of result mov bl, BYTE [rax] ; release value @@ -1105,6 +1106,8 @@ eval: mov rsi, r15 call incref_object pop rax + + ; Binds call alloc_cons mov [rax], BYTE (block_cons + container_function + content_pointer) @@ -1147,7 +1150,7 @@ eval: push rsi mov rdi, r15 ; Environment push r15 - call eval_ast + call eval_ast ; List of evaluated forms in RAX pop r15 pop rsi @@ -1208,7 +1211,9 @@ eval: ;; Input: RSI - Arguments to bind ;; RDI - Function object ;; +;; ;; Output: Result in RAX +;; apply_fn: push rsi ; Extract values from the list in RDI @@ -1252,13 +1257,36 @@ apply_fn: ;; Read-Eval-Print in sequence +;; +;; Input string in RSI rep_seq: + ; ------------- + ; Read call read_str - mov rsi, rax ; Output of read into input of eval + push rax ; Save form + + ; ------------- + ; Eval + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment call eval - mov rsi, rax ; Output of eval into input of print - call pr_str - mov rsi, rax ; Return value + push rax ; Save result + + ; ------------- + ; Print + + mov rsi, rax ; Output of eval into input of print + mov rdi, 1 ; print readably + call pr_str ; String in RAX + + mov r8, rax ; Save output + + pop rsi ; Result from eval + call release_object + pop rsi ; Form returned by read + call release_object + mov rax, r8 + ret @@ -1279,7 +1307,7 @@ _start: mov rsi, mal_startup_string mov edx, mal_startup_string.len call raw_to_string ; String in RAX - + push rax mov rsi, rax call read_str ; AST in RAX @@ -1312,39 +1340,19 @@ _start: cmp DWORD [rax+Array.length], 0 je .mainLoopEnd - push rax ; Save address of the input string - - ; Put into read_str - mov rsi, rax - call read_str - push rax ; Save AST + push rax ; Save address of the string - ; Eval - mov rsi, rax ; Form to evaluate - mov rdi, [repl_env] ; Environment - call eval - push rax ; Save result - - ; Put into pr_str mov rsi, rax - mov rdi, 1 ; print_readably - call pr_str - push rax ; Save output string + call rep_seq ; Read-Eval-Print + + push rax ; Save returned string mov rsi, rax ; Put into input of print_string call print_string - ; Release string from pr_str + ; Release string from rep_seq pop rsi call release_array - - ; Release result of eval - pop rsi - call release_object - - ; Release the object from read_str - pop rsi - call release_object ; Could be Cons or Array ; Release the input string pop rsi diff --git a/nasm/step5_tco.asm b/nasm/step5_tco.asm index af5525f425..76aaea2483 100644 --- a/nasm/step5_tco.asm +++ b/nasm/step5_tco.asm @@ -19,7 +19,7 @@ section .bss ;; Top-level (REPL) environment repl_env:resq 1 - + section .data ;; ------------------------------------------ @@ -29,7 +29,7 @@ section .data static error_string, db 27,'[31m',"Error",27,'[0m',": " - static not_found_string, db " not found.",10 + static not_found_string, db " not found" static def_missing_arg_string, db "missing argument to def!",10 @@ -589,6 +589,9 @@ eval: ; Unrecognised jmp .list_eval + + + ; ----------------------------- .def_symbol: ; Define a new symbol in current environment @@ -644,7 +647,6 @@ eval: ; This may throw an error, so define a handler - push r8 ; the symbol push r15 ; Env mov rsi, [rsi + Cons.car] ; Pointer @@ -1312,9 +1314,16 @@ eval: je .list_got_args ; No arguments - push rbx + + push rbx ; Function object + + mov rsi, rax ; List with function first + call release_object ; Can be freed now + + ; Create an empty list for the arguments call alloc_cons mov [rax], BYTE maltype_empty_list + pop rbx mov rsi, rax jmp .list_function_call @@ -1471,25 +1480,16 @@ rep_seq: ; ------------- ; Print - - ; Put into pr_str - mov rsi, rax - mov rdi, 1 ; print_readably - call pr_str - push rax ; Save output string - - mov rsi, rax ; Put into input of print_string - call print_string - ; Release string from pr_str - pop rsi - call release_array + mov rsi, rax ; Output of eval into input of print + mov rdi, 1 ; print readably + call pr_str ; String in RAX - ; Release result of eval - pop rsi + mov r8, rax ; Save output + + pop rsi ; Result from eval call release_object - - ; The AST from read_str is released by eval + mov rax, r8 ret @@ -1544,11 +1544,19 @@ _start: cmp DWORD [rax+Array.length], 0 je .mainLoopEnd - push rax ; Save address of the input string - - ; Put into read_str + push rax ; Save address of the string + mov rsi, rax - call rep_seq + call rep_seq ; Read-Eval-Print + + push rax ; Save returned string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from rep_seq + pop rsi + call release_array ; Release the input string pop rsi diff --git a/nasm/step6_file.asm b/nasm/step6_file.asm index 0020d559bc..d6e7c3f9e1 100644 --- a/nasm/step6_file.asm +++ b/nasm/step6_file.asm @@ -19,7 +19,7 @@ section .bss ;; Top-level (REPL) environment repl_env:resq 1 - + section .data ;; ------------------------------------------ @@ -29,7 +29,7 @@ section .data static error_string, db 27,'[31m',"Error",27,'[0m',": " - static not_found_string, db " not found.",10 + static not_found_string, db " not found" static def_missing_arg_string, db "missing argument to def!",10 @@ -591,6 +591,9 @@ eval: ; Unrecognised jmp .list_eval + + + ; ----------------------------- .def_symbol: ; Define a new symbol in current environment @@ -646,7 +649,6 @@ eval: ; This may throw an error, so define a handler - push r8 ; the symbol push r15 ; Env mov rsi, [rsi + Cons.car] ; Pointer @@ -1314,9 +1316,16 @@ eval: je .list_got_args ; No arguments - push rbx + + push rbx ; Function object + + mov rsi, rax ; List with function first + call release_object ; Can be freed now + + ; Create an empty list for the arguments call alloc_cons mov [rax], BYTE maltype_empty_list + pop rbx mov rsi, rax jmp .list_function_call @@ -1490,25 +1499,16 @@ rep_seq: ; ------------- ; Print - - ; Put into pr_str - mov rsi, rax - mov rdi, 1 ; print_readably - call pr_str - push rax ; Save output string - - mov rsi, rax ; Put into input of print_string - call print_string - ; Release string from pr_str - pop rsi - call release_array + mov rsi, rax ; Output of eval into input of print + mov rdi, 1 ; print readably + call pr_str ; String in RAX - ; Release result of eval - pop rsi + mov r8, rax ; Save output + + pop rsi ; Result from eval call release_object - - ; The AST from read_str is released by eval + mov rax, r8 ret @@ -1578,11 +1578,19 @@ _start: cmp DWORD [rax+Array.length], 0 je .mainLoopEnd - push rax ; Save address of the input string - - ; Put into read_str + push rax ; Save address of the string + mov rsi, rax - call rep_seq + call rep_seq ; Read-Eval-Print + + push rax ; Save returned string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from rep_seq + pop rsi + call release_array ; Release the input string pop rsi diff --git a/nasm/step7_quote.asm b/nasm/step7_quote.asm index 2b71b70aa4..493d04b1d6 100644 --- a/nasm/step7_quote.asm +++ b/nasm/step7_quote.asm @@ -13,15 +13,13 @@ global _start %include "reader.asm" ; String -> Data structures %include "core.asm" ; Core functions %include "printer.asm" ; Data structures -> String +%include "exceptions.asm" ; Error handling section .bss ;; Top-level (REPL) environment repl_env:resq 1 -;; Error handler list -error_handler: resq 1 - section .data ;; ------------------------------------------ @@ -31,7 +29,7 @@ section .data static error_string, db 27,'[31m',"Error",27,'[0m',": " - static not_found_string, db " not found.",10 + static not_found_string, db " not found" static def_missing_arg_string, db "missing argument to def!",10 @@ -69,141 +67,11 @@ section .data ;; Startup string. This is evaluated on startup static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) )" - - ;static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) )" - + ;; Command to run, appending the name of the script to run static run_script_string, db "(load-file ",34 section .text -;; ---------------------------------------------- -;; -;; Error handling -;; -;; A handler consists of: -;; - A stack pointer address to reset to -;; - An address to jump to -;; - An optional data structure to pass -;; -;; When jumped to, an error handler will be given: -;; - the object thrown in RSI -;; - the optional data structure in RDI -;; - - -;; Add an error handler to the front of the list -;; -;; Input: RSI - Stack pointer -;; RDI - Address to jump to -;; RCX - Data structure. Set to zero for none. -;; If not zero, reference count incremented -;; -;; Modifies registers: -;; RAX -;; RBX -error_handler_push: - call alloc_cons - ; car will point to a list (stack, addr, data) - ; cdr will point to the previous handler - mov [rax], BYTE (block_cons + container_list + content_pointer) - mov rbx, [error_handler] - cmp rbx, 0 ; Check if previous handler was zero - je .create_handler ; Zero, so leave null - ; Not zero, so create pointer to it - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rbx - - ; note: not incrementing reference count, since - ; we're replacing one reference with another -.create_handler: - mov [error_handler], rax ; new error handler - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.car], rax - ; Store stack pointer - mov [rax], BYTE (block_cons + container_list + content_function) - mov [rax + Cons.car], rsi ; stack pointer - - mov rdx, rax - call alloc_cons - mov [rdx + Cons.typecdr], BYTE content_pointer - mov [rdx + Cons.cdr], rax - ; Store function pointer to jump to - mov [rax], BYTE (block_cons + container_list + content_pointer) - mov [rax + Cons.car], rdi - - ; Check if there is an object to pass to handler - cmp rcx, 0 - je .done - - ; Set the final CDR to point to the object - mov [rax + Cons.typecdr], BYTE content_pointer - mov [rax + Cons.cdr], rcx - - mov rsi, rcx - call incref_object - -.done: - ret - - -;; Removes an error handler from the list -;; -;; Modifies registers: -;; RSI -;; RAX -;; RCX -error_handler_pop: - ; get the address - mov rsi, [error_handler] - cmp rsi, 0 - je .done ; Nothing to remove - - push rsi - mov rsi, [rsi + Cons.cdr] ; next handler - mov [error_handler], rsi - call incref_object ; needed because releasing soon - - pop rsi ; handler being removed - call release_cons - -.done: - ret - - -;; Throw an error -;; Object to pass to handler should be in RSI -error_throw: - ; Get the next error handler - mov rax, [error_handler] - cmp rax, 0 - je .no_handler - - ; Got a handler - mov rax, [rax + Cons.car] ; handler - mov rbx, [rax + Cons.car] ; stack pointer - mov rax, [rax + Cons.cdr] - mov rcx, [rax + Cons.car] ; function - mov rdi, [rax + Cons.cdr] ; data structure - - ; Reset stack - mov rsp, rbx - - ; Jump to the handler - jmp rcx - -.no_handler: - ; Print the object in RSI then quit - cmp rsi, 0 - je .done ; nothing to print - mov rdi, 1 ; print_readably - call pr_str - mov rsi, rax - call print_string -.done: - jmp quit_error - ;; ---------------------------------------------- ;; Evaluates a form ;; @@ -736,6 +604,9 @@ eval: ; Unrecognised jmp .list_eval + + + ; ----------------------------- .def_symbol: ; Define a new symbol in current environment @@ -791,7 +662,6 @@ eval: ; This may throw an error, so define a handler - push r8 ; the symbol push r15 ; Env mov rsi, [rsi + Cons.car] ; Pointer @@ -805,6 +675,7 @@ eval: call eval mov rsi, rax + pop r15 pop r8 @@ -927,11 +798,15 @@ eval: push r12 ; Position in bindings list push r13 ; symbol to bind push r14 ; new environment + + mov rsi, r14 + call incref_object + mov rdi, r14 + mov rsi, [r12 + Cons.car] ; Get the address call incref_object ; Increment ref count of AST - mov rdi, r14 call eval ; Evaluate it, result in rax pop r14 pop r13 @@ -1146,7 +1021,6 @@ eval: ; An expression to evaluate as the last form ; Tail call optimise, jumping to eval ; Don't increment Env reference count - mov rsi, [r11 + Cons.car] ; new AST form call incref_object ; This will be released by eval @@ -1383,6 +1257,8 @@ eval: mov rsi, r15 call incref_object pop rax + + ; Binds call alloc_cons mov [rax], BYTE (block_cons + container_function + content_pointer) @@ -1476,9 +1352,6 @@ eval: .quasiquote_pointer: ; RSI contains a pointer, so get the object pointed to mov rsi, [rsi + Cons.car] - - ;call quasiquote - ;ret push r15 ; Environment ; Original AST already on stack @@ -1503,8 +1376,7 @@ eval: call eval_ast ; List of evaluated forms in RAX pop r15 pop rsi - - + .list_exec: ; This point can be called to run a function ; used by swap! @@ -1529,9 +1401,16 @@ eval: je .list_got_args ; No arguments - push rbx + + push rbx ; Function object + + mov rsi, rax ; List with function first + call release_object ; Can be freed now + + ; Create an empty list for the arguments call alloc_cons mov [rax], BYTE maltype_empty_list + pop rbx mov rsi, rax jmp .list_function_call @@ -1545,7 +1424,7 @@ eval: mov rbx, [rbx + Cons.car] ; Call function cmp rbx, apply_fn - je apply_fn ; Jump to user function apply + je apply_fn_jmp ; Jump to user function apply ; A built-in function, so call (no recursion) push rax @@ -1556,11 +1435,10 @@ eval: ; Result in rax pop r15 pop rsi ; eval'ed list - + push rax call release_cons - pop rax - + pop rax jmp .return ; Releases Env .list_not_function: @@ -1582,11 +1460,16 @@ eval: ;; RDI - Function object ;; RDX - list to release after binding ;; R15 - Env (will be released) +;; R13 - AST released before return +;; ;; ;; Output: Result in RAX ;; ;; This is jumped to from eval, so if it returns ;; then it will return to the caller of eval, not to eval +apply_fn_jmp: + ; This is jumped to from eval with AST on the stack + pop r13 apply_fn: push rsi ; Extract values from the list in RDI @@ -1617,8 +1500,8 @@ apply_fn: mov rsi, r15 call release_object - ; Release the AST, pushed at start of eval - pop rsi + ; Release the AST + mov rsi, r13 call release_object mov rax, r14 @@ -1627,23 +1510,36 @@ apply_fn: ; Create a new environment, binding arguments push rax ; Body + mov r14, r13 ; Old AST. R13 used by env_new_bind + push rdx call env_new_bind pop rdx mov rdi, rax ; New environment in RDI + ; Note: Need to increment the reference count + ; of the function body before releasing anything, + ; since if the function was defined in-place (lambda) + ; then the body may be released early + + pop rsi ; Body + call incref_object ; Will be released by eval + mov r8, rsi ; Body in R8 + ; Release the list passed in RDX -.release: mov rsi, rdx call release_cons ; Release the environment mov rsi, r15 call release_object - - pop rsi ; Body - call incref_object ; Will be released by eval + + ; Release the old AST + mov rsi, r14 + call release_object + + mov rsi, r8 ; Body jmp eval ; Tail call ; The new environment (in RDI) will be released by eval @@ -1681,7 +1577,6 @@ is_pair: ;; Called by eval with AST in RSI [ modified ] ;; Returns new AST in RAX - quasiquote: ; i. Check if AST is an empty list call is_pair @@ -2007,7 +1902,23 @@ quasiquote: +;; Read and eval +read_eval: + ; ------------- + ; Read + call read_str + + ; ------------- + ; Eval + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + jmp eval ; This releases Env and Form/AST + ;; Read-Eval-Print in sequence ;; @@ -2031,25 +1942,16 @@ rep_seq: ; ------------- ; Print - - ; Put into pr_str - mov rsi, rax - mov rdi, 1 ; print_readably - call pr_str - push rax ; Save output string - - mov rsi, rax ; Put into input of print_string - call print_string - ; Release string from pr_str - pop rsi - call release_array + mov rsi, rax ; Output of eval into input of print + mov rdi, 1 ; print readably + call pr_str ; String in RAX - ; Release result of eval - pop rsi + mov r8, rax ; Save output + + pop rsi ; Result from eval call release_object - - ; The AST from read_str is released by eval + mov rax, r8 ret @@ -2119,11 +2021,19 @@ _start: cmp DWORD [rax+Array.length], 0 je .mainLoopEnd - push rax ; Save address of the input string - - ; Put into read_str + push rax ; Save address of the string + mov rsi, rax - call rep_seq + call rep_seq ; Read-Eval-Print + + push rax ; Save returned string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from rep_seq + pop rsi + call release_array ; Release the input string pop rsi @@ -2220,7 +2130,7 @@ run_script: mov cl, ')' call string_append_char ; closing brace - ; Read-Eval-Print "(load-file )" - call rep_seq + ; Read-Eval "(load-file )" + call read_eval jmp quit diff --git a/nasm/step8_macros.asm b/nasm/step8_macros.asm index 3b6d7d5f14..b4c6bd12ea 100644 --- a/nasm/step8_macros.asm +++ b/nasm/step8_macros.asm @@ -19,7 +19,7 @@ section .bss ;; Top-level (REPL) environment repl_env:resq 1 - + section .data ;; ------------------------------------------ @@ -29,7 +29,7 @@ section .data static error_string, db 27,'[31m',"Error",27,'[0m',": " - static not_found_string, db " not found.",10 + static not_found_string, db " not found" static def_missing_arg_string, db "missing argument to def!",10 @@ -51,8 +51,7 @@ section .data static if_missing_condition_string, db "missing condition in if expression",10 ;; Symbols used for comparison - - ; Special symbols + static_symbol def_symbol, 'def!' static_symbol let_symbol, 'let*' static_symbol do_symbol, 'do' @@ -69,7 +68,6 @@ section .data static_symbol splice_unquote_symbol, 'splice-unquote' static_symbol concat_symbol, 'concat' static_symbol cons_symbol, 'cons' - ; ;; Startup string. This is evaluated on startup static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) (defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw ",34,"odd number of forms to cond",34,")) (cons 'cond (rest (rest xs))))))) (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs)))))))) )" @@ -1415,10 +1413,6 @@ eval: .quasiquote_pointer: ; RSI contains a pointer, so get the object pointed to mov rsi, [rsi + Cons.car] - - ; Uncomment these two lines to test quasiquote - ;call quasiquote - ;ret push r15 ; Environment ; Original AST already on stack @@ -1499,9 +1493,16 @@ eval: je .list_got_args ; No arguments - push rbx + + push rbx ; Function object + + mov rsi, rax ; List with function first + call release_object ; Can be freed now + + ; Create an empty list for the arguments call alloc_cons mov [rax], BYTE maltype_empty_list + pop rbx mov rsi, rax jmp .list_function_call @@ -2113,6 +2114,24 @@ macroexpand: .done: pop r15 ret + +;; Read and eval +read_eval: + ; ------------- + ; Read + call read_str + + ; ------------- + ; Eval + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + jmp eval ; This releases Env and Form/AST + ;; Read-Eval-Print in sequence ;; @@ -2136,25 +2155,16 @@ rep_seq: ; ------------- ; Print - - ; Put into pr_str - mov rsi, rax - mov rdi, 1 ; print_readably - call pr_str - push rax ; Save output string - - mov rsi, rax ; Put into input of print_string - call print_string - ; Release string from pr_str - pop rsi - call release_array + mov rsi, rax ; Output of eval into input of print + mov rdi, 1 ; print readably + call pr_str ; String in RAX - ; Release result of eval - pop rsi + mov r8, rax ; Save output + + pop rsi ; Result from eval call release_object - - ; The AST from read_str is released by eval + mov rax, r8 ret @@ -2224,11 +2234,19 @@ _start: cmp DWORD [rax+Array.length], 0 je .mainLoopEnd - push rax ; Save address of the input string - - ; Put into read_str + push rax ; Save address of the string + mov rsi, rax - call rep_seq + call rep_seq ; Read-Eval-Print + + push rax ; Save returned string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from rep_seq + pop rsi + call release_array ; Release the input string pop rsi @@ -2325,7 +2343,7 @@ run_script: mov cl, ')' call string_append_char ; closing brace - ; Read-Eval-Print "(load-file )" - call rep_seq + ; Read-Eval "(load-file )" + call read_eval jmp quit diff --git a/nasm/step9_try.asm b/nasm/step9_try.asm index 4faeecf9e6..5c24b599fa 100644 --- a/nasm/step9_try.asm +++ b/nasm/step9_try.asm @@ -19,7 +19,7 @@ section .bss ;; Top-level (REPL) environment repl_env:resq 1 - + section .data ;; ------------------------------------------ @@ -56,7 +56,6 @@ section .data ;; Symbols used for comparison - ; Special symbols static_symbol def_symbol, 'def!' static_symbol let_symbol, 'let*' static_symbol do_symbol, 'do' @@ -75,7 +74,6 @@ section .data static_symbol splice_unquote_symbol, 'splice-unquote' static_symbol concat_symbol, 'concat' static_symbol cons_symbol, 'cons' - ; ;; Startup string. This is evaluated on startup static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) (defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw ",34,"odd number of forms to cond",34,")) (cons 'cond (rest (rest xs))))))) (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs)))))))) )" @@ -1443,10 +1441,6 @@ eval: .quasiquote_pointer: ; RSI contains a pointer, so get the object pointed to mov rsi, [rsi + Cons.car] - - ; Uncomment these two lines to test quasiquote - ;call quasiquote - ;ret push r15 ; Environment ; Original AST already on stack @@ -1491,7 +1485,7 @@ eval: call macroexpand ; May release and replace RSI mov rax, rsi - jmp .return ; Releases original AST + jmp .return ; Releases original AST ; ----------------------------- @@ -1719,9 +1713,16 @@ eval: je .list_got_args ; No arguments - push rbx + + push rbx ; Function object + + mov rsi, rax ; List with function first + call release_object ; Can be freed now + + ; Create an empty list for the arguments call alloc_cons mov [rax], BYTE maltype_empty_list + pop rbx mov rsi, rax jmp .list_function_call @@ -2333,6 +2334,24 @@ macroexpand: .done: pop r15 ret + +;; Read and eval +read_eval: + ; ------------- + ; Read + call read_str + + ; ------------- + ; Eval + mov rsi, rax ; Form to evaluate + mov rdi, [repl_env] ; Environment + + xchg rsi, rdi + call incref_object ; Environment increment refs + xchg rsi, rdi ; since it will be decremented by eval + + jmp eval ; This releases Env and Form/AST + ;; Read-Eval-Print in sequence ;; @@ -2356,25 +2375,16 @@ rep_seq: ; ------------- ; Print - - ; Put into pr_str - mov rsi, rax - mov rdi, 1 ; print_readably - call pr_str - push rax ; Save output string - - mov rsi, rax ; Put into input of print_string - call print_string - ; Release string from pr_str - pop rsi - call release_array + mov rsi, rax ; Output of eval into input of print + mov rdi, 1 ; print readably + call pr_str ; String in RAX - ; Release result of eval - pop rsi + mov r8, rax ; Save output + + pop rsi ; Result from eval call release_object - - ; The AST from read_str is released by eval + mov rax, r8 ret @@ -2444,11 +2454,19 @@ _start: cmp DWORD [rax+Array.length], 0 je .mainLoopEnd - push rax ; Save address of the input string - - ; Put into read_str + push rax ; Save address of the string + mov rsi, rax - call rep_seq + call rep_seq ; Read-Eval-Print + + push rax ; Save returned string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from rep_seq + pop rsi + call release_array ; Release the input string pop rsi @@ -2545,7 +2563,7 @@ run_script: mov cl, ')' call string_append_char ; closing brace - ; Read-Eval-Print "(load-file )" - call rep_seq + ; Read-Eval "(load-file )" + call read_eval jmp quit diff --git a/nasm/stepA_mal.asm b/nasm/stepA_mal.asm index c1e3d7a233..c47ee2f6d9 100644 --- a/nasm/stepA_mal.asm +++ b/nasm/stepA_mal.asm @@ -56,7 +56,6 @@ section .data ;; Symbols used for comparison - ; Special symbols static_symbol def_symbol, 'def!' static_symbol let_symbol, 'let*' static_symbol do_symbol, 'do' @@ -75,7 +74,6 @@ section .data static_symbol splice_unquote_symbol, 'splice-unquote' static_symbol concat_symbol, 'concat' static_symbol cons_symbol, 'cons' - ; ;; Startup string. This is evaluated on startup static mal_startup_string, db "(do (def! not (fn* (a) (if a false true))) (def! load-file (fn* (f) (eval (read-string (str ",34,"(do",34," (slurp f) ",34,")",34," ))))) (defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw ",34,"odd number of forms to cond",34,")) (cons 'cond (rest (rest xs))))))) (def! *gensym-counter* (atom 0)) (def! gensym (fn* [] (symbol (str ",34,"G__",34," (swap! *gensym-counter* (fn* [x] (+ 1 x))))))) (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs))))))))) (def! *host-language* ",34,"nasm",34,") (def! conj nil) )" @@ -1458,10 +1456,6 @@ eval: .quasiquote_pointer: ; RSI contains a pointer, so get the object pointed to mov rsi, [rsi + Cons.car] - - ; Uncomment these two lines to test quasiquote - ;call quasiquote - ;ret push r15 ; Environment ; Original AST already on stack @@ -1506,7 +1500,7 @@ eval: call macroexpand ; May release and replace RSI mov rax, rsi - jmp .return ; Releases original AST + jmp .return ; Releases original AST ; ----------------------------- @@ -1623,7 +1617,7 @@ eval: pop r15 ; Environment ; Discard B and C - ;add rsi, 8 ; pop R10 and R9 + ;add rsi, 8 ; pop R10 and R9 pop r10 pop r9 @@ -1814,7 +1808,6 @@ apply_fn: mov rax, [rax + Cons.cdr] mov rax, [rax + Cons.car] ; Body pop rcx ; Exprs - ; Check the type of the body mov bl, BYTE [rax] @@ -2398,25 +2391,16 @@ rep_seq: ; ------------- ; Print - - ; Put into pr_str - mov rsi, rax - mov rdi, 1 ; print_readably - call pr_str - push rax ; Save output string - - mov rsi, rax ; Put into input of print_string - call print_string - ; Release string from pr_str - pop rsi - call release_array + mov rsi, rax ; Output of eval into input of print + mov rdi, 1 ; print readably + call pr_str ; String in RAX - ; Release result of eval - pop rsi + mov r8, rax ; Save output + + pop rsi ; Result from eval call release_object - - ; The AST from read_str is released by eval + mov rax, r8 ret @@ -2502,11 +2486,19 @@ _start: cmp DWORD [rax+Array.length], 0 je .mainLoopEnd - push rax ; Save address of the input string - - ; Put into read_str + push rax ; Save address of the string + mov rsi, rax - call rep_seq + call rep_seq ; Read-Eval-Print + + push rax ; Save returned string + + mov rsi, rax ; Put into input of print_string + call print_string + + ; Release string from rep_seq + pop rsi + call release_array ; Release the input string pop rsi From ba46e3b027e7df2ebd2d0cee1616d5a41a2b3d4e Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 2 Apr 2018 11:01:24 -0500 Subject: [PATCH 0664/2308] [clojure] update lumo/image to fix module mismatch. --- clojure/Dockerfile | 2 +- clojure/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clojure/Dockerfile b/clojure/Dockerfile index 2a8eb37114..5060a80322 100644 --- a/clojure/Dockerfile +++ b/clojure/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:xenial +FROM ubuntu:17.10 MAINTAINER Joel Martin ########################################################## diff --git a/clojure/package.json b/clojure/package.json index ea4479f733..e2248254f0 100644 --- a/clojure/package.json +++ b/clojure/package.json @@ -4,6 +4,6 @@ "description": "Make a Lisp (mal) language implemented in ClojureScript", "dependencies": { "ffi": "2.2.x", - "lumo-cljs": "^1.6.0" + "lumo-cljs": "1.7.x" } } From 92708e3488ad2e994f481edd05ff24e70e6d6867 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Sun, 29 Apr 2018 12:53:32 +0000 Subject: [PATCH 0665/2308] Add Fantom implementation --- .gitignore | 1 + Makefile | 3 +- README.md | 15 +- fantom/Dockerfile | 37 ++++ fantom/Makefile | 31 +++ fantom/run | 4 + fantom/src/mallib/build.fan | 11 ++ fantom/src/mallib/fan/core.fan | 117 ++++++++++++ fantom/src/mallib/fan/env.fan | 40 ++++ fantom/src/mallib/fan/interop.fan | 61 ++++++ fantom/src/mallib/fan/reader.fan | 104 ++++++++++ fantom/src/mallib/fan/types.fan | 234 +++++++++++++++++++++++ fantom/src/step0_repl/build.fan | 11 ++ fantom/src/step0_repl/fan/main.fan | 32 ++++ fantom/src/step1_read_print/build.fan | 11 ++ fantom/src/step1_read_print/fan/main.fan | 37 ++++ fantom/src/step2_eval/build.fan | 11 ++ fantom/src/step2_eval/fan/main.fan | 70 +++++++ fantom/src/step3_env/build.fan | 11 ++ fantom/src/step3_env/fan/main.fan | 79 ++++++++ fantom/src/step4_if_fn_do/build.fan | 11 ++ fantom/src/step4_if_fn_do/fan/main.fan | 91 +++++++++ fantom/src/step5_tco/build.fan | 11 ++ fantom/src/step5_tco/fan/main.fan | 113 +++++++++++ fantom/src/step6_file/build.fan | 11 ++ fantom/src/step6_file/fan/main.fan | 122 ++++++++++++ fantom/src/step7_quote/build.fan | 11 ++ fantom/src/step7_quote/fan/main.fan | 143 ++++++++++++++ fantom/src/step8_macros/build.fan | 11 ++ fantom/src/step8_macros/fan/main.fan | 174 +++++++++++++++++ fantom/src/step9_try/build.fan | 11 ++ fantom/src/step9_try/fan/main.fan | 186 ++++++++++++++++++ fantom/src/stepA_mal/build.fan | 11 ++ fantom/src/stepA_mal/fan/main.fan | 190 ++++++++++++++++++ fantom/tests/step5_tco.mal | 15 ++ fantom/tests/stepA_mal.mal | 32 ++++ 36 files changed, 2061 insertions(+), 2 deletions(-) create mode 100644 fantom/Dockerfile create mode 100644 fantom/Makefile create mode 100755 fantom/run create mode 100644 fantom/src/mallib/build.fan create mode 100644 fantom/src/mallib/fan/core.fan create mode 100644 fantom/src/mallib/fan/env.fan create mode 100644 fantom/src/mallib/fan/interop.fan create mode 100644 fantom/src/mallib/fan/reader.fan create mode 100644 fantom/src/mallib/fan/types.fan create mode 100644 fantom/src/step0_repl/build.fan create mode 100644 fantom/src/step0_repl/fan/main.fan create mode 100644 fantom/src/step1_read_print/build.fan create mode 100644 fantom/src/step1_read_print/fan/main.fan create mode 100644 fantom/src/step2_eval/build.fan create mode 100644 fantom/src/step2_eval/fan/main.fan create mode 100644 fantom/src/step3_env/build.fan create mode 100644 fantom/src/step3_env/fan/main.fan create mode 100644 fantom/src/step4_if_fn_do/build.fan create mode 100644 fantom/src/step4_if_fn_do/fan/main.fan create mode 100644 fantom/src/step5_tco/build.fan create mode 100644 fantom/src/step5_tco/fan/main.fan create mode 100644 fantom/src/step6_file/build.fan create mode 100644 fantom/src/step6_file/fan/main.fan create mode 100644 fantom/src/step7_quote/build.fan create mode 100644 fantom/src/step7_quote/fan/main.fan create mode 100644 fantom/src/step8_macros/build.fan create mode 100644 fantom/src/step8_macros/fan/main.fan create mode 100644 fantom/src/step9_try/build.fan create mode 100644 fantom/src/step9_try/fan/main.fan create mode 100644 fantom/src/stepA_mal/build.fan create mode 100644 fantom/src/stepA_mal/fan/main.fan create mode 100644 fantom/tests/step5_tco.mal create mode 100644 fantom/tests/stepA_mal.mal diff --git a/.gitignore b/.gitignore index 5ecac391f2..1b8b6e41f0 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,7 @@ erlang/src/*.beam es6/mal.js es6/.esm-cache factor/mal.factor +fantom/lib forth/mal.fs fsharp/*.exe fsharp/*.dll diff --git a/Makefile b/Makefile index d88cd89741..0b061af50b 100644 --- a/Makefile +++ b/Makefile @@ -79,7 +79,7 @@ DOCKERIZE = # IMPLS = ada awk bash basic c chuck clojure coffee common-lisp cpp crystal cs d dart \ - elisp elixir elm erlang es6 factor forth fsharp go groovy gst guile haskell \ + elisp elixir elm erlang es6 factor fantom forth fsharp go groovy gst guile haskell \ haxe hy io java js julia kotlin livescript logo lua make mal matlab miniMAL \ nasm nim objc objpascal ocaml perl perl6 php pil plpgsql plsql powershell ps \ python r racket rexx rpython ruby rust scala scheme skew swift swift3 tcl \ @@ -191,6 +191,7 @@ elm_STEP_TO_PROG = elm/$($(1)).js erlang_STEP_TO_PROG = erlang/$($(1)) es6_STEP_TO_PROG = es6/$($(1)).mjs factor_STEP_TO_PROG = factor/$($(1))/$($(1)).factor +fantom_STEP_TO_PROG = fantom/lib/fan/$($(1)).pod forth_STEP_TO_PROG = forth/$($(1)).fs fsharp_STEP_TO_PROG = fsharp/$($(1)).exe go_STEP_TO_PROG = go/$($(1)) diff --git a/README.md b/README.md index cc88deb97d..d35c81042c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 72 languages: +Mal is implemented in 73 languages: * Ada * GNU awk @@ -29,6 +29,7 @@ Mal is implemented in 72 languages: * ES6 (ECMAScript 6 / ECMAScript 2015) * F# * Factor +* Fantom * Forth * Go * Groovy @@ -419,6 +420,18 @@ cd factor FACTOR_ROOTS=. factor -run=stepX_YYY ``` +### Fantom + +*The Fantom implementation was created by [Dov Murik](https://github.com/dubek)* + +The Fantom implementation of mal has been tested with Fantom 1.0.70. + +``` +cd fantom +make lib/fan/stepX_YYY.pod +STEP=stepX_YYY ./run +``` + ### Forth *The Forth implementation was created by [Chris Houser (chouser)](https://github.com/chouser)* diff --git a/fantom/Dockerfile b/fantom/Dockerfile new file mode 100644 index 0000000000..a52d7e069b --- /dev/null +++ b/fantom/Dockerfile @@ -0,0 +1,37 @@ +FROM ubuntu:bionic +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# Java and Unzip +RUN apt-get -y install openjdk-8-jdk unzip + +# Fantom and JLine +RUN cd /tmp && curl -sfLO https://bitbucket.org/fantom/fan-1.0/downloads/fantom-1.0.70.zip \ + && unzip -q fantom-1.0.70.zip \ + && rm fantom-1.0.70.zip \ + && mv fantom-1.0.70 /opt/fantom \ + && cd /opt/fantom \ + && bash adm/unixsetup \ + && curl -sfL -o /opt/fantom/lib/java/jline.jar https://repo1.maven.org/maven2/jline/jline/2.14.6/jline-2.14.6.jar + +ENV PATH /opt/fantom/bin:$PATH +ENV HOME /mal diff --git a/fantom/Makefile b/fantom/Makefile new file mode 100644 index 0000000000..97ebecc100 --- /dev/null +++ b/fantom/Makefile @@ -0,0 +1,31 @@ +SOURCES_BASE = src/mallib/fan/interop.fan src/mallib/fan/reader.fan src/mallib/fan/types.fan +SOURCES_LISP = src/mallib/fan/env.fan src/mallib/fan/core.fan src/stepA_mal/fan/main.fan +SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) + +all: dist + +dist: lib/fan/mal.pod + +lib/fan: + mkdir -p $@ + +lib/fan/mal.pod: lib/fan/stepA_mal.pod + cp -a $< $@ + +lib/fan/step%.pod: src/step%/build.fan src/step%/fan/*.fan lib/fan/mallib.pod + FAN_ENV=util::PathEnv FAN_ENV_PATH=. fan $< + +lib/fan/mallib.pod: src/mallib/build.fan src/mallib/fan/*.fan lib/fan + FAN_ENV=util::PathEnv FAN_ENV_PATH=. fan $< + +clean: + rm -rf lib + +.PHONY: stats + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/fantom/run b/fantom/run new file mode 100755 index 0000000000..3d75d6e53a --- /dev/null +++ b/fantom/run @@ -0,0 +1,4 @@ +#!/bin/bash +export FAN_ENV=util::PathEnv +export FAN_ENV_PATH="$(dirname $0)" +exec fan ${STEP:-stepA_mal} "$@" diff --git a/fantom/src/mallib/build.fan b/fantom/src/mallib/build.fan new file mode 100644 index 0000000000..275b9daf95 --- /dev/null +++ b/fantom/src/mallib/build.fan @@ -0,0 +1,11 @@ +class Build : build::BuildPod +{ + new make() + { + podName = "mallib" + summary = "mal library pod" + depends = ["sys 1.0", "compiler 1.0"] + srcDirs = [`fan/`] + outPodDir = `lib/fan/` + } +} diff --git a/fantom/src/mallib/fan/core.fan b/fantom/src/mallib/fan/core.fan new file mode 100644 index 0000000000..4331db4c54 --- /dev/null +++ b/fantom/src/mallib/fan/core.fan @@ -0,0 +1,117 @@ +class Core +{ + static private MalVal prn(MalVal[] a) + { + echo(a.join(" ") { it.toString(true) }) + return MalNil.INSTANCE + } + + static private MalVal println(MalVal[] a) + { + echo(a.join(" ") { it.toString(false) }) + return MalNil.INSTANCE + } + + static private MalVal readline(MalVal[] a) + { + line := Env.cur.prompt((a[0] as MalString).value) + return line == null ? MalNil.INSTANCE : MalString.make(line) + } + + static private MalVal concat(MalVal[] a) + { + return MalList(a.reduce(MalVal[,]) |MalVal[] r, MalSeq v -> MalVal[]| { return r.addAll(v.value) }) + } + + static private MalVal apply(MalVal[] a) + { + f := a[0] as MalFunc + args := a[1..-2] + args.addAll(((MalSeq)a[-1]).value) + return f.call(args) + } + + static private MalVal swap_bang(MalVal[] a) + { + atom := a[0] as MalAtom + MalVal[] args := [atom.value] + args.addAll(a[2..-1]) + f := a[1] as MalFunc + return atom.set(f.call(args)) + } + + static Str:MalFunc ns() + { + return [ + "=": MalFunc { MalTypes.toMalBool(it[0] == it[1]) }, + "throw": MalFunc { throw MalException(it[0]) }, + + "nil?": MalFunc { MalTypes.toMalBool(it[0] is MalNil) }, + "true?": MalFunc { MalTypes.toMalBool(it[0] is MalTrue) }, + "false?": MalFunc { MalTypes.toMalBool(it[0] is MalFalse) }, + "string?": MalFunc { MalTypes.toMalBool(it[0] is MalString && !((MalString)it[0]).isKeyword) }, + "symbol": MalFunc { MalSymbol.makeFromVal(it[0]) }, + "symbol?": MalFunc { MalTypes.toMalBool(it[0] is MalSymbol) }, + "keyword": MalFunc { MalString.makeKeyword((it[0] as MalString).value) }, + "keyword?": MalFunc { MalTypes.toMalBool(it[0] is MalString && ((MalString)it[0]).isKeyword) }, + "number?": MalFunc { MalTypes.toMalBool(it[0] is MalInteger) }, + "fn?": MalFunc { MalTypes.toMalBool(it[0] is MalFunc && !((it[0] as MalUserFunc)?->isMacro ?: false)) }, + "macro?": MalFunc { MalTypes.toMalBool(it[0] is MalUserFunc && ((MalUserFunc)it[0]).isMacro) }, + + "pr-str": MalFunc { MalString.make(it.join(" ") |MalVal e -> Str| { e.toString(true) }) }, + "str": MalFunc { MalString.make(it.join("") |MalVal e -> Str| { e.toString(false) }) }, + "prn": MalFunc(#prn.func), + "println": MalFunc(#println.func), + "read-string": MalFunc { Reader.read_str((it[0] as MalString).value) }, + "readline": MalFunc(#readline.func), + "slurp": MalFunc { MalString.make(File((it[0] as MalString).value.toUri).readAllStr) }, + + "<": MalFunc { MalTypes.toMalBool((it[0] as MalInteger).value < (it[1] as MalInteger).value) }, + "<=": MalFunc { MalTypes.toMalBool((it[0] as MalInteger).value <= (it[1] as MalInteger).value) }, + ">": MalFunc { MalTypes.toMalBool((it[0] as MalInteger).value > (it[1] as MalInteger).value) }, + ">=": MalFunc { MalTypes.toMalBool((it[0] as MalInteger).value >= (it[1] as MalInteger).value) }, + "+": MalFunc { MalInteger((it[0] as MalInteger).value + (it[1] as MalInteger).value) }, + "-": MalFunc { MalInteger((it[0] as MalInteger).value - (it[1] as MalInteger).value) }, + "*": MalFunc { MalInteger((it[0] as MalInteger).value * (it[1] as MalInteger).value) }, + "/": MalFunc { MalInteger((it[0] as MalInteger).value / (it[1] as MalInteger).value) }, + "time-ms": MalFunc { MalInteger(DateTime.nowTicks / 1000000) }, + + "list": MalFunc { MalList(it) }, + "list?": MalFunc { MalTypes.toMalBool(it[0] is MalList) }, + "vector": MalFunc { MalVector(it) }, + "vector?": MalFunc { MalTypes.toMalBool(it[0] is MalVector) }, + "hash-map": MalFunc { MalHashMap.fromList(it) }, + "map?": MalFunc { MalTypes.toMalBool(it[0] is MalHashMap) }, + "assoc": MalFunc { (it[0] as MalHashMap).assoc(it[1..-1]) }, + "dissoc": MalFunc { (it[0] as MalHashMap).dissoc(it[1..-1]) }, + "get": MalFunc { it[0] is MalNil ? MalNil.INSTANCE : (it[0] as MalHashMap).get2((MalString)it[1], MalNil.INSTANCE) }, + "contains?": MalFunc { MalTypes.toMalBool((it[0] as MalHashMap).containsKey((MalString)it[1])) }, + "keys": MalFunc { MalList((it[0] as MalHashMap).keys) }, + "vals": MalFunc { MalList((it[0] as MalHashMap).vals) }, + + "sequential?": MalFunc { MalTypes.toMalBool(it[0] is MalSeq) }, + "cons": MalFunc { MalList([it[0]].addAll((it[1] as MalSeq).value)) }, + "concat": MalFunc(#concat.func), + "nth": MalFunc { (it[0] as MalSeq).nth((it[1] as MalInteger).value) }, + "first": MalFunc { (it[0] as MalSeq)?.first ?: MalNil.INSTANCE }, + "rest": MalFunc { (it[0] as MalSeq)?.rest ?: MalList([,]) }, + "empty?": MalFunc { MalTypes.toMalBool((it[0] as MalSeq).isEmpty) }, + "count": MalFunc { MalInteger(it[0].count) }, + "apply": MalFunc(#apply.func), + "map": MalFunc { (it[1] as MalSeq).map(it[0]) }, + + "conj": MalFunc { (it[0] as MalSeq).conj(it[1..-1]) }, + "seq": MalFunc { it[0].seq }, + + "meta": MalFunc { it[0].meta() }, + "with-meta": MalFunc { it[0].with_meta(it[1]) }, + "atom": MalFunc { MalAtom(it[0]) }, + "atom?": MalFunc { MalTypes.toMalBool(it[0] is MalAtom) }, + "deref": MalFunc { (it[0] as MalAtom).value }, + "reset!": MalFunc { (it[0] as MalAtom).set(it[1]) }, + "swap!": MalFunc(#swap_bang.func), + + "fantom-eval": MalFunc { Interop.fantomEvaluate((it[0] as MalString).value) } + ] + } +} diff --git a/fantom/src/mallib/fan/env.fan b/fantom/src/mallib/fan/env.fan new file mode 100644 index 0000000000..644c181470 --- /dev/null +++ b/fantom/src/mallib/fan/env.fan @@ -0,0 +1,40 @@ +class MalEnv +{ + private Str:MalVal data := [:] + private MalEnv? outer + + new make(MalEnv? outer := null, MalSeq? binds := null, MalSeq? exprs := null) + { + this.outer = outer + if (binds != null && exprs != null) + { + for (i := 0; i < binds.count; i++) + { + if ((binds[i] as MalSymbol).value == "&") + { + set(binds[i + 1], MalList(exprs[i..-1])) + break + } + else + set(binds[i], exprs[i]) + } + } + } + + MalVal set(MalSymbol key, MalVal value) + { + data[key.value] = value + return value + } + + MalEnv? find(MalSymbol key) + { + return data.containsKey(key.value) ? this : outer?.find(key) + } + + MalVal get(MalSymbol key) + { + foundEnv := find(key) ?: throw Err("'$key.value' not found") + return (MalVal)foundEnv.data[key.value] + } +} diff --git a/fantom/src/mallib/fan/interop.fan b/fantom/src/mallib/fan/interop.fan new file mode 100644 index 0000000000..71b0d99a0b --- /dev/null +++ b/fantom/src/mallib/fan/interop.fan @@ -0,0 +1,61 @@ +using compiler + +internal class Interop +{ + static Pod? compile(Str innerBody) + { + ci := CompilerInput + { + podName = "mal_fantom_interop_${DateTime.nowUnique}" + summary = "" + isScript = true + version = Version.defVal + log.level = LogLevel.silent + output = CompilerOutputMode.transientPod + mode = CompilerInputMode.str + srcStr = "class InteropDummyClass {\nstatic Obj? _evalfunc() {\n $innerBody \n}\n}" + srcStrLoc = Loc("mal_fantom_interop") + } + try + return Compiler(ci).compile.transientPod + catch (CompilerErr e) + return null + } + + static Obj? evaluate(Str line) + { + p := compile(line) + if (p == null) + p = compile("return $line") + if (p == null) + p = compile("$line\nreturn null") + if (p == null) + return null + method := p.types.first.method("_evalfunc") + try + return method.call() + catch (Err e) + return null + } + + static MalVal fantomToMal(Obj? obj) + { + if (obj == null) + return MalNil.INSTANCE + else if (obj is Bool) + return MalTypes.toMalBool((Bool)obj) + else if (obj is Int) + return MalInteger((Int)obj) + else if (obj is List) + return MalList((obj as List).map { fantomToMal(it) }) + else if (obj is Map) + return MalHashMap.fromMap((obj as Map).map { fantomToMal(it) }) + else + return MalString.make(obj.toStr) + } + + static MalVal fantomEvaluate(Str line) + { + return fantomToMal(evaluate(line)) + } +} diff --git a/fantom/src/mallib/fan/reader.fan b/fantom/src/mallib/fan/reader.fan new file mode 100644 index 0000000000..edf9fe1337 --- /dev/null +++ b/fantom/src/mallib/fan/reader.fan @@ -0,0 +1,104 @@ +internal class TokenReader +{ + const Str[] tokens + private Int position := 0 + + new make(Str[] new_tokens) { tokens = new_tokens } + + Str? peek() + { + if (position >= tokens.size) return null + return tokens[position] + } + + Str next() { return tokens[position++] } +} + +class Reader +{ + private static Str[] tokenize(Str s) + { + r := Regex <|[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)|> + m := r.matcher(s) + tokens := Str[,] + while (m.find()) + { + token := m.group(1) + if (token.isEmpty || token[0] == ';') continue + tokens.add(m.group(1)) + } + return tokens + } + + private static Str unescape_str(Str s) + { + return s.replace("\\\\", "\u029e").replace("\\\"", "\"").replace("\\n", "\n").replace("\u029e", "\\") + } + + private static MalVal read_atom(TokenReader reader) + { + token := reader.next + intRegex := Regex <|^-?\d+$|> + if (token == "nil") return MalNil.INSTANCE + if (token == "true") return MalTrue.INSTANCE + if (token == "false") return MalFalse.INSTANCE + if (intRegex.matches(token)) return MalInteger(token.toInt) + if (token[0] == '"') return MalString.make(unescape_str(token[1..-2])) + if (token[0] == ':') return MalString.makeKeyword(token[1..-1]) + return MalSymbol(token) + } + + private static MalVal[] read_seq(TokenReader reader, Str open, Str close) + { + reader.next + values := MalVal[,] + token := reader.peek + while (token != close) + { + if (token == null) throw Err("expected '$close', got EOF") + values.add(read_form(reader)) + token = reader.peek + } + if (token != close) throw Err("Missing '$close'") + reader.next + return values + } + + private static MalVal read_form(TokenReader reader) + { + switch (reader.peek) + { + case "\'": + reader.next + return MalList([MalSymbol("quote"), read_form(reader)]) + case "`": + reader.next + return MalList([MalSymbol("quasiquote"), read_form(reader)]) + case "~": + reader.next + return MalList([MalSymbol("unquote"), read_form(reader)]) + case "~@": + reader.next + return MalList([MalSymbol("splice-unquote"), read_form(reader)]) + case "^": + reader.next + meta := read_form(reader) + return MalList([MalSymbol("with-meta"), read_form(reader), meta]) + case "@": + reader.next + return MalList([MalSymbol("deref"), read_form(reader)]) + case "(": return MalList(read_seq(reader, "(", ")")) + case ")": throw Err("unexpected ')'") + case "[": return MalVector(read_seq(reader, "[", "]")) + case "]": throw Err("unexpected ']'") + case "{": return MalHashMap.fromList(read_seq(reader, "{", "}")) + case "}": throw Err("unexpected '}'") + default: return read_atom(reader) + } + } + + static MalVal read_str(Str s) + { + return read_form(TokenReader(tokenize(s))); + } +} diff --git a/fantom/src/mallib/fan/types.fan b/fantom/src/mallib/fan/types.fan new file mode 100644 index 0000000000..c30ff124ea --- /dev/null +++ b/fantom/src/mallib/fan/types.fan @@ -0,0 +1,234 @@ +mixin MalVal +{ + virtual Str toString(Bool readable) { return toStr } + virtual Int count() { throw Err("count not implemented") } + virtual MalVal seq() { throw Err("seq not implemented") } + abstract MalVal meta() + abstract MalVal with_meta(MalVal newMeta) +} + +const mixin MalValNoMeta : MalVal +{ + override MalVal meta() { return MalNil.INSTANCE } + override MalVal with_meta(MalVal newMeta) { return this } +} + +const mixin MalFalseyVal +{ +} + +const class MalNil : MalValNoMeta, MalFalseyVal +{ + static const MalNil INSTANCE := MalNil() + override Bool equals(Obj? that) { return that is MalNil } + override Str toString(Bool readable) { return "nil" } + override Int count() { return 0 } + override MalVal seq() { return this } +} + +const class MalTrue : MalValNoMeta +{ + static const MalTrue INSTANCE := MalTrue() + override Bool equals(Obj? that) { return that is MalTrue } + override Str toString(Bool readable) { return "true" } +} + +const class MalFalse : MalValNoMeta, MalFalseyVal +{ + static const MalFalse INSTANCE := MalFalse() + override Bool equals(Obj? that) { return that is MalFalse } + override Str toString(Bool readable) { return "false" } +} + +const class MalInteger : MalValNoMeta +{ + const Int value + new make(Int v) { value = v } + override Bool equals(Obj? that) { return that is MalInteger && (that as MalInteger).value == value } + override Str toString(Bool readable) { return value.toStr } +} + +abstract class MalValBase : MalVal +{ + private MalVal? metaVal := null + override Str toString(Bool readable) { return toStr } + override Int count() { throw Err("count not implemented") } + override MalVal seq() { throw Err("seq not implemented") } + abstract This dup() + override MalVal meta() { return metaVal ?: MalNil.INSTANCE } + override MalVal with_meta(MalVal newMeta) + { + v := dup + v.metaVal = newMeta + return v + } +} + +class MalSymbol : MalValBase +{ + const Str value + new make(Str v) { value = v } + new makeFromVal(MalVal v) + { + if (v is MalSymbol) return v + value = (v as MalString).value + } + override Bool equals(Obj? that) { return that is MalSymbol && (that as MalSymbol).value == value } + override Str toString(Bool readable) { return value } + override This dup() { return make(value) } +} + +class MalString : MalValBase +{ + const Str value + new make(Str v) { value = v } + new makeKeyword(Str v) { value = "\u029e$v" } + override Bool equals(Obj? that) { return that is MalString && (that as MalString).value == value } + override Str toString(Bool readable) + { + if (isKeyword) return ":${value[1..-1]}" + if (readable) + return "\"${escapeStr(value)}\"" + else + return value + } + Bool isKeyword() { return !value.isEmpty && value[0] == '\u029e' } + static Str escapeStr(Str s) + { + return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n") + } + override MalVal seq() + { + if (value.size == 0) return MalNil.INSTANCE + return MalList(value.chars.map |Int c -> MalString| { MalString.make(Str.fromChars([c])) }) + } + override This dup() { return make(value) } +} + +abstract class MalSeq : MalValBase +{ + MalVal[] value { protected set } + new make(MalVal[] v) { value = v.ro } + override Bool equals(Obj? that) { return that is MalSeq && (that as MalSeq).value == value } + Bool isEmpty() { return value.isEmpty } + override Int count() { return value.size } + @Operator MalVal get(Int index) { return value[index] } + @Operator MalVal[] getRange(Range range) { return value[range] } + protected Str serialize(Bool readable) { return value.join(" ") { it.toString(readable) } } + abstract MalSeq drop(Int n) + MalVal nth(Int index) { return index < count ? get(index) : throw Err("nth: index out of range") } + MalVal first() { return isEmpty ? MalNil.INSTANCE : value[0] } + MalList rest() { return MalList(isEmpty ? [,] : value[1..-1]) } + MalList map(MalFunc f) { return MalList(value.map { f.call([it]) } ) } + abstract MalSeq conj(MalVal[] args) +} + +class MalList : MalSeq +{ + new make(MalVal[] v) : super.make(v) {} + override Str toString(Bool readable) { return "(${serialize(readable)})" } + override MalList drop(Int n) { return make(value[n..-1]) } + override MalVal seq() { return isEmpty ? MalNil.INSTANCE : this } + override MalList conj(MalVal[] args) { return MalList(value.rw.insertAll(0, args.reverse)) } + override This dup() { return make(value) } +} + +class MalVector : MalSeq +{ + new make(MalVal[] v) : super.make(v) {} + override Str toString(Bool readable) { return "[${serialize(readable)}]" } + override MalVector drop(Int n) { return make(value[n..-1]) } + override MalVal seq() { return isEmpty ? MalNil.INSTANCE : MalList(value) } + override MalVector conj(MalVal[] args) { return MalVector(value.rw.addAll(args)) } + override This dup() { return make(value) } +} + +class MalHashMap : MalValBase +{ + Str:MalVal value { private set } + new fromList(MalVal[] lst) { + m := [Str:MalVal][:] + for (i := 0; i < lst.size; i += 2) + m.add((lst[i] as MalString).value, (MalVal)lst[i + 1]) + value = m.ro + } + new fromMap(Str:MalVal m) { value = m.ro } + override Bool equals(Obj? that) { return that is MalHashMap && (that as MalHashMap).value == value } + override Str toString(Bool readable) + { + elements := Str[,] + value.each(|MalVal v, Str k| { elements.add(MalString.make(k).toString(readable)); elements.add(v.toString(readable)) }) + s := elements.join(" ") + return "{$s}" + } + override Int count() { return value.size } + @Operator MalVal get(Str key) { return value[key] } + MalVal get2(MalString key, MalVal? def := null) { return value.get(key.value, def) } + Bool containsKey(MalString key) { return value.containsKey(key.value) } + MalVal[] keys() { return value.keys.map { MalString.make(it) } } + MalVal[] vals() { return value.vals } + MalHashMap assoc(MalVal[] args) + { + newValue := value.dup + for (i := 0; i < args.size; i += 2) + newValue.set((args[i] as MalString).value, args[i + 1]) + return fromMap(newValue) + } + MalHashMap dissoc(MalVal[] args) + { + newValue := value.dup + args.each { newValue.remove((it as MalString).value) } + return fromMap(newValue) + } + override This dup() { return fromMap(value) } +} + +class MalFunc : MalValBase +{ + protected |MalVal[] a -> MalVal| f + new make(|MalVal[] a -> MalVal| func) { f = func } + MalVal call(MalVal[] a) { return f(a) } + override Str toString(Bool readable) { return "" } + override This dup() { return make(f) } +} + +class MalUserFunc : MalFunc +{ + MalVal ast { private set } + private MalEnv env + private MalSeq params + Bool isMacro := false + new make(MalVal ast, MalEnv env, MalSeq params, |MalVal[] a -> MalVal| func, Bool isMacro := false) : super.make(func) + { + this.ast = ast + this.env = env + this.params = params + this.isMacro = isMacro + } + MalEnv genEnv(MalSeq args) { return MalEnv(env, params, args) } + override Str toString(Bool readable) { return "" } + override This dup() { return make(ast, env, params, f, isMacro) } +} + +class MalAtom : MalValBase +{ + MalVal value + new make(MalVal v) { value = v } + override Str toString(Bool readable) { return "(atom ${value.toString(readable)})" } + override Bool equals(Obj? that) { return that is MalAtom && (that as MalAtom).value == value } + MalVal set(MalVal v) { value = v; return value } + override This dup() { return make(value) } +} + +class MalTypes +{ + static MalVal toMalBool(Bool cond) { return cond ? MalTrue.INSTANCE : MalFalse.INSTANCE } + static Bool isPair(MalVal a) { return a is MalSeq && !(a as MalSeq).isEmpty } +} + +const class MalException : Err +{ + const Str serializedValue + new make(MalVal v) : super.make("Mal exception") { serializedValue = v.toString(true) } + MalVal getValue() { return Reader.read_str(serializedValue) } +} diff --git a/fantom/src/step0_repl/build.fan b/fantom/src/step0_repl/build.fan new file mode 100644 index 0000000000..e16a2a3f8c --- /dev/null +++ b/fantom/src/step0_repl/build.fan @@ -0,0 +1,11 @@ +class Build : build::BuildPod +{ + new make() + { + podName = "step0_repl" + summary = "mal step0_repl pod" + depends = ["sys 1.0"] + srcDirs = [`fan/`] + outPodDir = `lib/fan/` + } +} diff --git a/fantom/src/step0_repl/fan/main.fan b/fantom/src/step0_repl/fan/main.fan new file mode 100644 index 0000000000..efccdebd02 --- /dev/null +++ b/fantom/src/step0_repl/fan/main.fan @@ -0,0 +1,32 @@ +class Main +{ + static Str READ(Str s) + { + return s + } + + static Str EVAL(Str ast, Str env) + { + return ast + } + + static Str PRINT(Str exp) + { + return exp + } + + static Str REP(Str s, Str env) + { + return PRINT(EVAL(READ(s), env)) + } + + static Void main() + { + while (true) { + line := Env.cur.prompt("user> ") + if (line == null) break + if (line.isSpace) continue + echo(REP(line, "")) + } + } +} diff --git a/fantom/src/step1_read_print/build.fan b/fantom/src/step1_read_print/build.fan new file mode 100644 index 0000000000..3bb399898f --- /dev/null +++ b/fantom/src/step1_read_print/build.fan @@ -0,0 +1,11 @@ +class Build : build::BuildPod +{ + new make() + { + podName = "step1_read_print" + summary = "mal step1_read_print pod" + depends = ["sys 1.0", "mallib 1.0"] + srcDirs = [`fan/`] + outPodDir = `lib/fan/` + } +} diff --git a/fantom/src/step1_read_print/fan/main.fan b/fantom/src/step1_read_print/fan/main.fan new file mode 100644 index 0000000000..5e6f27d95a --- /dev/null +++ b/fantom/src/step1_read_print/fan/main.fan @@ -0,0 +1,37 @@ +using mallib + +class Main +{ + static MalVal READ(Str s) + { + return Reader.read_str(s) + } + + static MalVal EVAL(MalVal ast, Str env) + { + return ast + } + + static Str PRINT(MalVal exp) + { + return exp.toString(true) + } + + static Str REP(Str s, Str env) + { + return PRINT(EVAL(READ(s), env)) + } + + static Void main() + { + while (true) { + line := Env.cur.prompt("user> ") + if (line == null) break + if (line.isSpace) continue + try + echo(REP(line, "")) + catch (Err e) + echo("Error: $e.msg") + } + } +} diff --git a/fantom/src/step2_eval/build.fan b/fantom/src/step2_eval/build.fan new file mode 100644 index 0000000000..792a7f722e --- /dev/null +++ b/fantom/src/step2_eval/build.fan @@ -0,0 +1,11 @@ +class Build : build::BuildPod +{ + new make() + { + podName = "step2_eval" + summary = "mal step2_eval pod" + depends = ["sys 1.0", "mallib 1.0"] + srcDirs = [`fan/`] + outPodDir = `lib/fan/` + } +} diff --git a/fantom/src/step2_eval/fan/main.fan b/fantom/src/step2_eval/fan/main.fan new file mode 100644 index 0000000000..0ea7bb6d20 --- /dev/null +++ b/fantom/src/step2_eval/fan/main.fan @@ -0,0 +1,70 @@ +using mallib + +class Main +{ + static MalVal READ(Str s) + { + return Reader.read_str(s) + } + + static MalVal eval_ast(MalVal ast, Str:MalFunc env) + { + switch (ast.typeof) + { + case MalSymbol#: + varName := (ast as MalSymbol).value + varVal := env[varName] ?: throw Err("'$varName' not found") + return (MalVal)varVal + case MalList#: + newElements := (ast as MalList).value.map { EVAL(it, env) } + return MalList(newElements) + case MalVector#: + newElements := (ast as MalVector).value.map { EVAL(it, env) } + return MalVector(newElements) + case MalHashMap#: + newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) } + return MalHashMap.fromMap(newElements) + default: + return ast + } + } + + static MalVal EVAL(MalVal ast, Str:MalFunc env) + { + if (!(ast is MalList)) return eval_ast(ast, env) + astList := ast as MalList + if (astList.isEmpty) return ast + evaled_ast := eval_ast(ast, env) as MalList + f := evaled_ast[0] as MalFunc + return f.call(evaled_ast[1..-1]) + } + + static Str PRINT(MalVal exp) + { + return exp.toString(true) + } + + static Str REP(Str s, Str:MalFunc env) + { + return PRINT(EVAL(READ(s), env)) + } + + static Void main() + { + env := [ + "+": MalFunc { MalInteger((it[0] as MalInteger).value + (it[1] as MalInteger).value) }, + "-": MalFunc { MalInteger((it[0] as MalInteger).value - (it[1] as MalInteger).value) }, + "*": MalFunc { MalInteger((it[0] as MalInteger).value * (it[1] as MalInteger).value) }, + "/": MalFunc { MalInteger((it[0] as MalInteger).value / (it[1] as MalInteger).value) } + ] + while (true) { + line := Env.cur.prompt("user> ") + if (line == null) break + if (line.isSpace) continue + try + echo(REP(line, env)) + catch (Err e) + echo("Error: $e.msg") + } + } +} diff --git a/fantom/src/step3_env/build.fan b/fantom/src/step3_env/build.fan new file mode 100644 index 0000000000..598092fb24 --- /dev/null +++ b/fantom/src/step3_env/build.fan @@ -0,0 +1,11 @@ +class Build : build::BuildPod +{ + new make() + { + podName = "step3_env" + summary = "mal step3_env pod" + depends = ["sys 1.0", "mallib 1.0"] + srcDirs = [`fan/`] + outPodDir = `lib/fan/` + } +} diff --git a/fantom/src/step3_env/fan/main.fan b/fantom/src/step3_env/fan/main.fan new file mode 100644 index 0000000000..4f8294b4df --- /dev/null +++ b/fantom/src/step3_env/fan/main.fan @@ -0,0 +1,79 @@ +using mallib + +class Main +{ + static MalVal READ(Str s) + { + return Reader.read_str(s) + } + + static MalVal eval_ast(MalVal ast, MalEnv env) + { + switch (ast.typeof) + { + case MalSymbol#: + return env.get(ast) + case MalList#: + newElements := (ast as MalList).value.map { EVAL(it, env) } + return MalList(newElements) + case MalVector#: + newElements := (ast as MalVector).value.map { EVAL(it, env) } + return MalVector(newElements) + case MalHashMap#: + newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) } + return MalHashMap.fromMap(newElements) + default: + return ast + } + } + + static MalVal EVAL(MalVal ast, MalEnv env) + { + if (!(ast is MalList)) return eval_ast(ast, env) + astList := ast as MalList + if (astList.isEmpty) return ast + switch ((astList[0] as MalSymbol).value) + { + case "def!": + return env.set(astList[1], EVAL(astList[2], env)) + case "let*": + let_env := MalEnv(env) + varList := (astList[1] as MalSeq) + for (i := 0; i < varList.count; i += 2) + let_env.set(varList[i], EVAL(varList[i + 1], let_env)) + return EVAL(astList[2], let_env) + default: + evaled_ast := eval_ast(ast, env) as MalList + f := evaled_ast[0] as MalFunc + return f.call(evaled_ast[1..-1]) + } + } + + static Str PRINT(MalVal exp) + { + return exp.toString(true) + } + + static Str REP(Str s, MalEnv env) + { + return PRINT(EVAL(READ(s), env)) + } + + static Void main() + { + repl_env := MalEnv() + repl_env.set(MalSymbol("+"), MalFunc { MalInteger((it[0] as MalInteger).value + (it[1] as MalInteger).value) }) + repl_env.set(MalSymbol("-"), MalFunc { MalInteger((it[0] as MalInteger).value - (it[1] as MalInteger).value) }) + repl_env.set(MalSymbol("*"), MalFunc { MalInteger((it[0] as MalInteger).value * (it[1] as MalInteger).value) }) + repl_env.set(MalSymbol("/"), MalFunc { MalInteger((it[0] as MalInteger).value / (it[1] as MalInteger).value) }) + while (true) { + line := Env.cur.prompt("user> ") + if (line == null) break + if (line.isSpace) continue + try + echo(REP(line, repl_env)) + catch (Err e) + echo("Error: $e.msg") + } + } +} diff --git a/fantom/src/step4_if_fn_do/build.fan b/fantom/src/step4_if_fn_do/build.fan new file mode 100644 index 0000000000..7cf25b342b --- /dev/null +++ b/fantom/src/step4_if_fn_do/build.fan @@ -0,0 +1,11 @@ +class Build : build::BuildPod +{ + new make() + { + podName = "step4_if_fn_do" + summary = "mal step4_if_fn_do pod" + depends = ["sys 1.0", "mallib 1.0"] + srcDirs = [`fan/`] + outPodDir = `lib/fan/` + } +} diff --git a/fantom/src/step4_if_fn_do/fan/main.fan b/fantom/src/step4_if_fn_do/fan/main.fan new file mode 100644 index 0000000000..22d6ea4f02 --- /dev/null +++ b/fantom/src/step4_if_fn_do/fan/main.fan @@ -0,0 +1,91 @@ +using mallib + +class Main +{ + static MalVal READ(Str s) + { + return Reader.read_str(s) + } + + static MalVal eval_ast(MalVal ast, MalEnv env) + { + switch (ast.typeof) + { + case MalSymbol#: + return env.get(ast) + case MalList#: + newElements := (ast as MalList).value.map { EVAL(it, env) } + return MalList(newElements) + case MalVector#: + newElements := (ast as MalVector).value.map { EVAL(it, env) } + return MalVector(newElements) + case MalHashMap#: + newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) } + return MalHashMap.fromMap(newElements) + default: + return ast + } + } + + static MalVal EVAL(MalVal ast, MalEnv env) + { + if (!(ast is MalList)) return eval_ast(ast, env) + astList := ast as MalList + if (astList.isEmpty) return ast + switch ((astList[0] as MalSymbol)?.value) + { + case "def!": + return env.set(astList[1], EVAL(astList[2], env)) + case "let*": + let_env := MalEnv(env) + varList := astList[1] as MalSeq + for (i := 0; i < varList.count; i += 2) + let_env.set(varList[i], EVAL(varList[i + 1], let_env)) + return EVAL(astList[2], let_env) + case "do": + eval_ast(MalList(astList[1..-2]), env) + return EVAL(astList[-1], env) + case "if": + if (EVAL(astList[1], env) is MalFalseyVal) + return astList.count > 3 ? EVAL(astList[3], env) : MalNil.INSTANCE + else + return EVAL(astList[2], env) + case "fn*": + return MalFunc { EVAL(astList[2], MalEnv(env, (astList[1] as MalSeq), MalList(it))) } + default: + evaled_ast := eval_ast(ast, env) as MalList + f := evaled_ast[0] as MalFunc + return f.call(evaled_ast[1..-1]) + } + } + + static Str PRINT(MalVal exp) + { + return exp.toString(true) + } + + static Str REP(Str s, MalEnv env) + { + return PRINT(EVAL(READ(s), env)) + } + + static Void main() + { + repl_env := MalEnv() + // core.fan: defined using Fantom + Core.ns.each |MalFunc V, Str K| { repl_env.set(MalSymbol(K), V) } + + // core.mal: defined using the language itself + REP("(def! not (fn* (a) (if a false true)))", repl_env) + + while (true) { + line := Env.cur.prompt("user> ") + if (line == null) break + if (line.isSpace) continue + try + echo(REP(line, repl_env)) + catch (Err e) + echo("Error: $e.msg") + } + } +} diff --git a/fantom/src/step5_tco/build.fan b/fantom/src/step5_tco/build.fan new file mode 100644 index 0000000000..d96402c8ba --- /dev/null +++ b/fantom/src/step5_tco/build.fan @@ -0,0 +1,11 @@ +class Build : build::BuildPod +{ + new make() + { + podName = "step5_tco" + summary = "mal step5_tco pod" + depends = ["sys 1.0", "mallib 1.0"] + srcDirs = [`fan/`] + outPodDir = `lib/fan/` + } +} diff --git a/fantom/src/step5_tco/fan/main.fan b/fantom/src/step5_tco/fan/main.fan new file mode 100644 index 0000000000..eeab71a07d --- /dev/null +++ b/fantom/src/step5_tco/fan/main.fan @@ -0,0 +1,113 @@ +using mallib + +class Main +{ + static MalVal READ(Str s) + { + return Reader.read_str(s) + } + + static MalVal eval_ast(MalVal ast, MalEnv env) + { + switch (ast.typeof) + { + case MalSymbol#: + return env.get(ast) + case MalList#: + newElements := (ast as MalList).value.map { EVAL(it, env) } + return MalList(newElements) + case MalVector#: + newElements := (ast as MalVector).value.map { EVAL(it, env) } + return MalVector(newElements) + case MalHashMap#: + newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) } + return MalHashMap.fromMap(newElements) + default: + return ast + } + } + + static MalVal EVAL(MalVal ast, MalEnv env) + { + while (true) + { + if (!(ast is MalList)) return eval_ast(ast, env) + astList := ast as MalList + if (astList.isEmpty) return ast + switch ((astList[0] as MalSymbol)?.value) + { + case "def!": + return env.set(astList[1], EVAL(astList[2], env)) + case "let*": + let_env := MalEnv(env) + varList := astList[1] as MalSeq + for (i := 0; i < varList.count; i += 2) + let_env.set(varList[i], EVAL(varList[i + 1], let_env)) + env = let_env + ast = astList[2] + // TCO + case "do": + eval_ast(MalList(astList[1..-2]), env) + ast = astList[-1] + // TCO + case "if": + if (EVAL(astList[1], env) is MalFalseyVal) + ast = astList.count > 3 ? astList[3] : MalNil.INSTANCE + else + ast = astList[2] + // TCO + case "fn*": + f := |MalVal[] a -> MalVal| + { + return EVAL(astList[2], MalEnv(env, (astList[1] as MalSeq), MalList(a))) + } + return MalUserFunc(astList[2], env, (MalSeq)astList[1], f) + default: + evaled_ast := eval_ast(ast, env) as MalList + switch (evaled_ast[0].typeof) + { + case MalUserFunc#: + user_fn := evaled_ast[0] as MalUserFunc + ast = user_fn.ast + env = user_fn.genEnv(evaled_ast.drop(1)) + // TCO + case MalFunc#: + return (evaled_ast[0] as MalFunc).call(evaled_ast[1..-1]) + default: + throw Err("Unknown type") + } + } + } + return MalNil.INSTANCE // never reached + } + + static Str PRINT(MalVal exp) + { + return exp.toString(true) + } + + static Str REP(Str s, MalEnv env) + { + return PRINT(EVAL(READ(s), env)) + } + + static Void main() + { + repl_env := MalEnv() + // core.fan: defined using Fantom + Core.ns.each |MalFunc V, Str K| { repl_env.set(MalSymbol(K), V) } + + // core.mal: defined using the language itself + REP("(def! not (fn* (a) (if a false true)))", repl_env) + + while (true) { + line := Env.cur.prompt("user> ") + if (line == null) break + if (line.isSpace) continue + try + echo(REP(line, repl_env)) + catch (Err e) + echo("Error: $e.msg") + } + } +} diff --git a/fantom/src/step6_file/build.fan b/fantom/src/step6_file/build.fan new file mode 100644 index 0000000000..93e255f5f1 --- /dev/null +++ b/fantom/src/step6_file/build.fan @@ -0,0 +1,11 @@ +class Build : build::BuildPod +{ + new make() + { + podName = "step6_file" + summary = "mal step6_file pod" + depends = ["sys 1.0", "mallib 1.0"] + srcDirs = [`fan/`] + outPodDir = `lib/fan/` + } +} diff --git a/fantom/src/step6_file/fan/main.fan b/fantom/src/step6_file/fan/main.fan new file mode 100644 index 0000000000..aad03665d5 --- /dev/null +++ b/fantom/src/step6_file/fan/main.fan @@ -0,0 +1,122 @@ +using mallib + +class Main +{ + static MalVal READ(Str s) + { + return Reader.read_str(s) + } + + static MalVal eval_ast(MalVal ast, MalEnv env) + { + switch (ast.typeof) + { + case MalSymbol#: + return env.get(ast) + case MalList#: + newElements := (ast as MalList).value.map { EVAL(it, env) } + return MalList(newElements) + case MalVector#: + newElements := (ast as MalVector).value.map { EVAL(it, env) } + return MalVector(newElements) + case MalHashMap#: + newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) } + return MalHashMap.fromMap(newElements) + default: + return ast + } + } + + static MalVal EVAL(MalVal ast, MalEnv env) + { + while (true) + { + if (!(ast is MalList)) return eval_ast(ast, env) + astList := ast as MalList + if (astList.isEmpty) return ast + switch ((astList[0] as MalSymbol)?.value) + { + case "def!": + return env.set(astList[1], EVAL(astList[2], env)) + case "let*": + let_env := MalEnv(env) + varList := astList[1] as MalSeq + for (i := 0; i < varList.count; i += 2) + let_env.set(varList[i], EVAL(varList[i + 1], let_env)) + env = let_env + ast = astList[2] + // TCO + case "do": + eval_ast(MalList(astList[1..-2]), env) + ast = astList[-1] + // TCO + case "if": + if (EVAL(astList[1], env) is MalFalseyVal) + ast = astList.count > 3 ? astList[3] : MalNil.INSTANCE + else + ast = astList[2] + // TCO + case "fn*": + f := |MalVal[] a -> MalVal| + { + return EVAL(astList[2], MalEnv(env, (astList[1] as MalSeq), MalList(a))) + } + return MalUserFunc(astList[2], env, (MalSeq)astList[1], f) + default: + evaled_ast := eval_ast(ast, env) as MalList + switch (evaled_ast[0].typeof) + { + case MalUserFunc#: + user_fn := evaled_ast[0] as MalUserFunc + ast = user_fn.ast + env = user_fn.genEnv(evaled_ast.drop(1)) + // TCO + case MalFunc#: + return (evaled_ast[0] as MalFunc).call(evaled_ast[1..-1]) + default: + throw Err("Unknown type") + } + } + } + return MalNil.INSTANCE // never reached + } + + static Str PRINT(MalVal exp) + { + return exp.toString(true) + } + + static Str REP(Str s, MalEnv env) + { + return PRINT(EVAL(READ(s), env)) + } + + static Void main(Str[] args) + { + repl_env := MalEnv() + // core.fan: defined using Fantom + Core.ns.each |MalFunc V, Str K| { repl_env.set(MalSymbol(K), V) } + repl_env.set(MalSymbol("eval"), MalFunc { EVAL(it[0], repl_env) }) + repl_env.set(MalSymbol("*ARGV*"), MalList((args.isEmpty ? args : args[1..-1]).map { MalString.make(it) })) + + // core.mal: defined using the language itself + REP("(def! not (fn* (a) (if a false true)))", repl_env) + REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env) + + if (!args.isEmpty) + { + REP("(load-file \"${args[0]}\")", repl_env) + return + } + + while (true) { + line := Env.cur.prompt("user> ") + if (line == null) break + if (line.isSpace) continue + try + echo(REP(line, repl_env)) + catch (Err e) + echo("Error: $e.msg") + } + } +} diff --git a/fantom/src/step7_quote/build.fan b/fantom/src/step7_quote/build.fan new file mode 100644 index 0000000000..a32dfca1f9 --- /dev/null +++ b/fantom/src/step7_quote/build.fan @@ -0,0 +1,11 @@ +class Build : build::BuildPod +{ + new make() + { + podName = "step7_quote" + summary = "mal step7_quote pod" + depends = ["sys 1.0", "mallib 1.0"] + srcDirs = [`fan/`] + outPodDir = `lib/fan/` + } +} diff --git a/fantom/src/step7_quote/fan/main.fan b/fantom/src/step7_quote/fan/main.fan new file mode 100644 index 0000000000..7158f03383 --- /dev/null +++ b/fantom/src/step7_quote/fan/main.fan @@ -0,0 +1,143 @@ +using mallib + +class Main +{ + static MalVal quasiquote(MalVal ast) + { + if (!MalTypes.isPair(ast)) + return MalList(MalVal[MalSymbol("quote"), ast]) + astSeq := ast as MalSeq + if ((astSeq[0] as MalSymbol)?.value == "unquote") + return astSeq[1] + if (MalTypes.isPair(astSeq[0])) + { + ast0Seq := astSeq[0] as MalSeq + if ((ast0Seq[0] as MalSymbol)?.value == "splice-unquote") + return MalList(MalVal[MalSymbol("concat"), ast0Seq[1], quasiquote(astSeq.drop(1))]) + } + return MalList(MalVal[MalSymbol("cons"), quasiquote(astSeq[0]), quasiquote(astSeq.drop(1))]) + } + + static MalVal READ(Str s) + { + return Reader.read_str(s) + } + + static MalVal eval_ast(MalVal ast, MalEnv env) + { + switch (ast.typeof) + { + case MalSymbol#: + return env.get(ast) + case MalList#: + newElements := (ast as MalList).value.map { EVAL(it, env) } + return MalList(newElements) + case MalVector#: + newElements := (ast as MalVector).value.map { EVAL(it, env) } + return MalVector(newElements) + case MalHashMap#: + newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) } + return MalHashMap.fromMap(newElements) + default: + return ast + } + } + + static MalVal EVAL(MalVal ast, MalEnv env) + { + while (true) + { + if (!(ast is MalList)) return eval_ast(ast, env) + astList := ast as MalList + if (astList.isEmpty) return ast + switch ((astList[0] as MalSymbol)?.value) + { + case "def!": + return env.set(astList[1], EVAL(astList[2], env)) + case "let*": + let_env := MalEnv(env) + varList := astList[1] as MalSeq + for (i := 0; i < varList.count; i += 2) + let_env.set(varList[i], EVAL(varList[i + 1], let_env)) + env = let_env + ast = astList[2] + // TCO + case "quote": + return astList[1] + case "quasiquote": + ast = quasiquote(astList[1]) + // TCO + case "do": + eval_ast(MalList(astList[1..-2]), env) + ast = astList[-1] + // TCO + case "if": + if (EVAL(astList[1], env) is MalFalseyVal) + ast = astList.count > 3 ? astList[3] : MalNil.INSTANCE + else + ast = astList[2] + // TCO + case "fn*": + f := |MalVal[] a -> MalVal| + { + return EVAL(astList[2], MalEnv(env, (astList[1] as MalSeq), MalList(a))) + } + return MalUserFunc(astList[2], env, (MalSeq)astList[1], f) + default: + evaled_ast := eval_ast(ast, env) as MalList + switch (evaled_ast[0].typeof) + { + case MalUserFunc#: + user_fn := evaled_ast[0] as MalUserFunc + ast = user_fn.ast + env = user_fn.genEnv(evaled_ast.drop(1)) + // TCO + case MalFunc#: + return (evaled_ast[0] as MalFunc).call(evaled_ast[1..-1]) + default: + throw Err("Unknown type") + } + } + } + return MalNil.INSTANCE // never reached + } + + static Str PRINT(MalVal exp) + { + return exp.toString(true) + } + + static Str REP(Str s, MalEnv env) + { + return PRINT(EVAL(READ(s), env)) + } + + static Void main(Str[] args) + { + repl_env := MalEnv() + // core.fan: defined using Fantom + Core.ns.each |MalFunc V, Str K| { repl_env.set(MalSymbol(K), V) } + repl_env.set(MalSymbol("eval"), MalFunc { EVAL(it[0], repl_env) }) + repl_env.set(MalSymbol("*ARGV*"), MalList((args.isEmpty ? args : args[1..-1]).map { MalString.make(it) })) + + // core.mal: defined using the language itself + REP("(def! not (fn* (a) (if a false true)))", repl_env) + REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env) + + if (!args.isEmpty) + { + REP("(load-file \"${args[0]}\")", repl_env) + return + } + + while (true) { + line := Env.cur.prompt("user> ") + if (line == null) break + if (line.isSpace) continue + try + echo(REP(line, repl_env)) + catch (Err e) + echo("Error: $e.msg") + } + } +} diff --git a/fantom/src/step8_macros/build.fan b/fantom/src/step8_macros/build.fan new file mode 100644 index 0000000000..d6333c9854 --- /dev/null +++ b/fantom/src/step8_macros/build.fan @@ -0,0 +1,11 @@ +class Build : build::BuildPod +{ + new make() + { + podName = "step8_macros" + summary = "mal step8_macros pod" + depends = ["sys 1.0", "mallib 1.0"] + srcDirs = [`fan/`] + outPodDir = `lib/fan/` + } +} diff --git a/fantom/src/step8_macros/fan/main.fan b/fantom/src/step8_macros/fan/main.fan new file mode 100644 index 0000000000..243295c47f --- /dev/null +++ b/fantom/src/step8_macros/fan/main.fan @@ -0,0 +1,174 @@ +using mallib + +class Main +{ + static MalVal quasiquote(MalVal ast) + { + if (!MalTypes.isPair(ast)) + return MalList(MalVal[MalSymbol("quote"), ast]) + astSeq := ast as MalSeq + if ((astSeq[0] as MalSymbol)?.value == "unquote") + return astSeq[1] + if (MalTypes.isPair(astSeq[0])) + { + ast0Seq := astSeq[0] as MalSeq + if ((ast0Seq[0] as MalSymbol)?.value == "splice-unquote") + return MalList(MalVal[MalSymbol("concat"), ast0Seq[1], quasiquote(astSeq.drop(1))]) + } + return MalList(MalVal[MalSymbol("cons"), quasiquote(astSeq[0]), quasiquote(astSeq.drop(1))]) + } + + static Bool isMacroCall(MalVal ast, MalEnv env) + { + if (!(ast is MalList)) return false + astList := ast as MalList + if (astList.isEmpty) return false + if (!(astList[0] is MalSymbol)) return false + ast0 := astList[0] as MalSymbol + f := env.find(ast0)?.get(ast0) + return (f as MalUserFunc)?.isMacro ?: false + } + + static MalVal macroexpand(MalVal ast, MalEnv env) + { + while (isMacroCall(ast, env)) + { + mac := env.get((ast as MalList)[0]) as MalUserFunc + ast = mac.call((ast as MalSeq).drop(1).value) + } + return ast + } + + static MalVal READ(Str s) + { + return Reader.read_str(s) + } + + static MalVal eval_ast(MalVal ast, MalEnv env) + { + switch (ast.typeof) + { + case MalSymbol#: + return env.get(ast) + case MalList#: + newElements := (ast as MalList).value.map { EVAL(it, env) } + return MalList(newElements) + case MalVector#: + newElements := (ast as MalVector).value.map { EVAL(it, env) } + return MalVector(newElements) + case MalHashMap#: + newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) } + return MalHashMap.fromMap(newElements) + default: + return ast + } + } + + static MalVal EVAL(MalVal ast, MalEnv env) + { + while (true) + { + if (!(ast is MalList)) return eval_ast(ast, env) + ast = macroexpand(ast, env) + if (!(ast is MalList)) return eval_ast(ast, env) + astList := ast as MalList + if (astList.isEmpty) return ast + switch ((astList[0] as MalSymbol)?.value) + { + case "def!": + return env.set(astList[1], EVAL(astList[2], env)) + case "let*": + let_env := MalEnv(env) + varList := astList[1] as MalSeq + for (i := 0; i < varList.count; i += 2) + let_env.set(varList[i], EVAL(varList[i + 1], let_env)) + env = let_env + ast = astList[2] + // TCO + case "quote": + return astList[1] + case "quasiquote": + ast = quasiquote(astList[1]) + // TCO + case "defmacro!": + f := EVAL(astList[2], env) as MalUserFunc + f.isMacro = true + return env.set(astList[1], f) + case "macroexpand": + return macroexpand(astList[1], env) + case "do": + eval_ast(MalList(astList[1..-2]), env) + ast = astList[-1] + // TCO + case "if": + if (EVAL(astList[1], env) is MalFalseyVal) + ast = astList.count > 3 ? astList[3] : MalNil.INSTANCE + else + ast = astList[2] + // TCO + case "fn*": + f := |MalVal[] a -> MalVal| + { + return EVAL(astList[2], MalEnv(env, (astList[1] as MalSeq), MalList(a))) + } + return MalUserFunc(astList[2], env, (MalSeq)astList[1], f) + default: + evaled_ast := eval_ast(ast, env) as MalList + switch (evaled_ast[0].typeof) + { + case MalUserFunc#: + user_fn := evaled_ast[0] as MalUserFunc + ast = user_fn.ast + env = user_fn.genEnv(evaled_ast.drop(1)) + // TCO + case MalFunc#: + return (evaled_ast[0] as MalFunc).call(evaled_ast[1..-1]) + default: + throw Err("Unknown type") + } + } + } + return MalNil.INSTANCE // never reached + } + + static Str PRINT(MalVal exp) + { + return exp.toString(true) + } + + static Str REP(Str s, MalEnv env) + { + return PRINT(EVAL(READ(s), env)) + } + + static Void main(Str[] args) + { + repl_env := MalEnv() + // core.fan: defined using Fantom + Core.ns.each |MalFunc V, Str K| { repl_env.set(MalSymbol(K), V) } + repl_env.set(MalSymbol("eval"), MalFunc { EVAL(it[0], repl_env) }) + repl_env.set(MalSymbol("*ARGV*"), MalList((args.isEmpty ? args : args[1..-1]).map { MalString.make(it) })) + + // core.mal: defined using the language itself + REP("(def! not (fn* (a) (if a false true)))", repl_env) + REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env) + REP("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", repl_env) + REP("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", repl_env) + + if (!args.isEmpty) + { + REP("(load-file \"${args[0]}\")", repl_env) + return + } + + while (true) { + line := Env.cur.prompt("user> ") + if (line == null) break + if (line.isSpace) continue + try + echo(REP(line, repl_env)) + catch (Err e) + echo("Error: $e.msg") + } + } +} diff --git a/fantom/src/step9_try/build.fan b/fantom/src/step9_try/build.fan new file mode 100644 index 0000000000..8d3b048052 --- /dev/null +++ b/fantom/src/step9_try/build.fan @@ -0,0 +1,11 @@ +class Build : build::BuildPod +{ + new make() + { + podName = "step9_try" + summary = "mal step9_try pod" + depends = ["sys 1.0", "mallib 1.0"] + srcDirs = [`fan/`] + outPodDir = `lib/fan/` + } +} diff --git a/fantom/src/step9_try/fan/main.fan b/fantom/src/step9_try/fan/main.fan new file mode 100644 index 0000000000..1cede16369 --- /dev/null +++ b/fantom/src/step9_try/fan/main.fan @@ -0,0 +1,186 @@ +using mallib + +class Main +{ + static MalVal quasiquote(MalVal ast) + { + if (!MalTypes.isPair(ast)) + return MalList(MalVal[MalSymbol("quote"), ast]) + astSeq := ast as MalSeq + if ((astSeq[0] as MalSymbol)?.value == "unquote") + return astSeq[1] + if (MalTypes.isPair(astSeq[0])) + { + ast0Seq := astSeq[0] as MalSeq + if ((ast0Seq[0] as MalSymbol)?.value == "splice-unquote") + return MalList(MalVal[MalSymbol("concat"), ast0Seq[1], quasiquote(astSeq.drop(1))]) + } + return MalList(MalVal[MalSymbol("cons"), quasiquote(astSeq[0]), quasiquote(astSeq.drop(1))]) + } + + static Bool isMacroCall(MalVal ast, MalEnv env) + { + if (!(ast is MalList)) return false + astList := ast as MalList + if (astList.isEmpty) return false + if (!(astList[0] is MalSymbol)) return false + ast0 := astList[0] as MalSymbol + f := env.find(ast0)?.get(ast0) + return (f as MalUserFunc)?.isMacro ?: false + } + + static MalVal macroexpand(MalVal ast, MalEnv env) + { + while (isMacroCall(ast, env)) + { + mac := env.get((ast as MalList)[0]) as MalUserFunc + ast = mac.call((ast as MalSeq).drop(1).value) + } + return ast + } + + static MalVal READ(Str s) + { + return Reader.read_str(s) + } + + static MalVal eval_ast(MalVal ast, MalEnv env) + { + switch (ast.typeof) + { + case MalSymbol#: + return env.get(ast) + case MalList#: + newElements := (ast as MalList).value.map { EVAL(it, env) } + return MalList(newElements) + case MalVector#: + newElements := (ast as MalVector).value.map { EVAL(it, env) } + return MalVector(newElements) + case MalHashMap#: + newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) } + return MalHashMap.fromMap(newElements) + default: + return ast + } + } + + static MalVal EVAL(MalVal ast, MalEnv env) + { + while (true) + { + if (!(ast is MalList)) return eval_ast(ast, env) + ast = macroexpand(ast, env) + if (!(ast is MalList)) return eval_ast(ast, env) + astList := ast as MalList + if (astList.isEmpty) return ast + switch ((astList[0] as MalSymbol)?.value) + { + case "def!": + return env.set(astList[1], EVAL(astList[2], env)) + case "let*": + let_env := MalEnv(env) + varList := astList[1] as MalSeq + for (i := 0; i < varList.count; i += 2) + let_env.set(varList[i], EVAL(varList[i + 1], let_env)) + env = let_env + ast = astList[2] + // TCO + case "quote": + return astList[1] + case "quasiquote": + ast = quasiquote(astList[1]) + // TCO + case "defmacro!": + f := EVAL(astList[2], env) as MalUserFunc + f.isMacro = true + return env.set(astList[1], f) + case "macroexpand": + return macroexpand(astList[1], env) + case "try*": + MalVal exc := MalNil.INSTANCE + try + return EVAL(astList[1], env) + catch (MalException e) + exc = e.getValue + catch (Err e) + exc = MalString.make(e.msg) + catchClause := astList[2] as MalList + return EVAL(catchClause[2], MalEnv(env, MalList([catchClause[1]]), MalList([exc]))) + case "do": + eval_ast(MalList(astList[1..-2]), env) + ast = astList[-1] + // TCO + case "if": + if (EVAL(astList[1], env) is MalFalseyVal) + ast = astList.count > 3 ? astList[3] : MalNil.INSTANCE + else + ast = astList[2] + // TCO + case "fn*": + f := |MalVal[] a -> MalVal| + { + return EVAL(astList[2], MalEnv(env, (astList[1] as MalSeq), MalList(a))) + } + return MalUserFunc(astList[2], env, (MalSeq)astList[1], f) + default: + evaled_ast := eval_ast(ast, env) as MalList + switch (evaled_ast[0].typeof) + { + case MalUserFunc#: + user_fn := evaled_ast[0] as MalUserFunc + ast = user_fn.ast + env = user_fn.genEnv(evaled_ast.drop(1)) + // TCO + case MalFunc#: + return (evaled_ast[0] as MalFunc).call(evaled_ast[1..-1]) + default: + throw Err("Unknown type") + } + } + } + return MalNil.INSTANCE // never reached + } + + static Str PRINT(MalVal exp) + { + return exp.toString(true) + } + + static Str REP(Str s, MalEnv env) + { + return PRINT(EVAL(READ(s), env)) + } + + static Void main(Str[] args) + { + repl_env := MalEnv() + // core.fan: defined using Fantom + Core.ns.each |MalFunc V, Str K| { repl_env.set(MalSymbol(K), V) } + repl_env.set(MalSymbol("eval"), MalFunc { EVAL(it[0], repl_env) }) + repl_env.set(MalSymbol("*ARGV*"), MalList((args.isEmpty ? args : args[1..-1]).map { MalString.make(it) })) + + // core.mal: defined using the language itself + REP("(def! not (fn* (a) (if a false true)))", repl_env) + REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env) + REP("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", repl_env) + REP("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", repl_env) + + if (!args.isEmpty) + { + REP("(load-file \"${args[0]}\")", repl_env) + return + } + + while (true) { + line := Env.cur.prompt("user> ") + if (line == null) break + if (line.isSpace) continue + try + echo(REP(line, repl_env)) + catch (MalException e) + echo("Error: ${e.serializedValue}") + catch (Err e) + echo("Error: $e.msg") + } + } +} diff --git a/fantom/src/stepA_mal/build.fan b/fantom/src/stepA_mal/build.fan new file mode 100644 index 0000000000..a4c40d7f57 --- /dev/null +++ b/fantom/src/stepA_mal/build.fan @@ -0,0 +1,11 @@ +class Build : build::BuildPod +{ + new make() + { + podName = "stepA_mal" + summary = "mal stepA_mal pod" + depends = ["sys 1.0", "mallib 1.0"] + srcDirs = [`fan/`] + outPodDir = `lib/fan/` + } +} diff --git a/fantom/src/stepA_mal/fan/main.fan b/fantom/src/stepA_mal/fan/main.fan new file mode 100644 index 0000000000..c42b659657 --- /dev/null +++ b/fantom/src/stepA_mal/fan/main.fan @@ -0,0 +1,190 @@ +using mallib + +class Main +{ + static MalVal quasiquote(MalVal ast) + { + if (!MalTypes.isPair(ast)) + return MalList(MalVal[MalSymbol("quote"), ast]) + astSeq := ast as MalSeq + if ((astSeq[0] as MalSymbol)?.value == "unquote") + return astSeq[1] + if (MalTypes.isPair(astSeq[0])) + { + ast0Seq := astSeq[0] as MalSeq + if ((ast0Seq[0] as MalSymbol)?.value == "splice-unquote") + return MalList(MalVal[MalSymbol("concat"), ast0Seq[1], quasiquote(astSeq.drop(1))]) + } + return MalList(MalVal[MalSymbol("cons"), quasiquote(astSeq[0]), quasiquote(astSeq.drop(1))]) + } + + static Bool isMacroCall(MalVal ast, MalEnv env) + { + if (!(ast is MalList)) return false + astList := ast as MalList + if (astList.isEmpty) return false + if (!(astList[0] is MalSymbol)) return false + ast0 := astList[0] as MalSymbol + f := env.find(ast0)?.get(ast0) + return (f as MalUserFunc)?.isMacro ?: false + } + + static MalVal macroexpand(MalVal ast, MalEnv env) + { + while (isMacroCall(ast, env)) + { + mac := env.get((ast as MalList)[0]) as MalUserFunc + ast = mac.call((ast as MalSeq).drop(1).value) + } + return ast + } + + static MalVal READ(Str s) + { + return Reader.read_str(s) + } + + static MalVal eval_ast(MalVal ast, MalEnv env) + { + switch (ast.typeof) + { + case MalSymbol#: + return env.get(ast) + case MalList#: + newElements := (ast as MalList).value.map { EVAL(it, env) } + return MalList(newElements) + case MalVector#: + newElements := (ast as MalVector).value.map { EVAL(it, env) } + return MalVector(newElements) + case MalHashMap#: + newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) } + return MalHashMap.fromMap(newElements) + default: + return ast + } + } + + static MalVal EVAL(MalVal ast, MalEnv env) + { + while (true) + { + if (!(ast is MalList)) return eval_ast(ast, env) + ast = macroexpand(ast, env) + if (!(ast is MalList)) return eval_ast(ast, env) + astList := ast as MalList + if (astList.isEmpty) return ast + switch ((astList[0] as MalSymbol)?.value) + { + case "def!": + return env.set(astList[1], EVAL(astList[2], env)) + case "let*": + let_env := MalEnv(env) + varList := astList[1] as MalSeq + for (i := 0; i < varList.count; i += 2) + let_env.set(varList[i], EVAL(varList[i + 1], let_env)) + env = let_env + ast = astList[2] + // TCO + case "quote": + return astList[1] + case "quasiquote": + ast = quasiquote(astList[1]) + // TCO + case "defmacro!": + f := EVAL(astList[2], env) as MalUserFunc + f.isMacro = true + return env.set(astList[1], f) + case "macroexpand": + return macroexpand(astList[1], env) + case "try*": + MalVal exc := MalNil.INSTANCE + try + return EVAL(astList[1], env) + catch (MalException e) + exc = e.getValue + catch (Err e) + exc = MalString.make(e.msg) + catchClause := astList[2] as MalList + return EVAL(catchClause[2], MalEnv(env, MalList([catchClause[1]]), MalList([exc]))) + case "do": + eval_ast(MalList(astList[1..-2]), env) + ast = astList[-1] + // TCO + case "if": + if (EVAL(astList[1], env) is MalFalseyVal) + ast = astList.count > 3 ? astList[3] : MalNil.INSTANCE + else + ast = astList[2] + // TCO + case "fn*": + f := |MalVal[] a -> MalVal| + { + return EVAL(astList[2], MalEnv(env, (astList[1] as MalSeq), MalList(a))) + } + return MalUserFunc(astList[2], env, (MalSeq)astList[1], f) + default: + evaled_ast := eval_ast(ast, env) as MalList + switch (evaled_ast[0].typeof) + { + case MalUserFunc#: + user_fn := evaled_ast[0] as MalUserFunc + ast = user_fn.ast + env = user_fn.genEnv(evaled_ast.drop(1)) + // TCO + case MalFunc#: + return (evaled_ast[0] as MalFunc).call(evaled_ast[1..-1]) + default: + throw Err("Unknown type") + } + } + } + return MalNil.INSTANCE // never reached + } + + static Str PRINT(MalVal exp) + { + return exp.toString(true) + } + + static Str REP(Str s, MalEnv env) + { + return PRINT(EVAL(READ(s), env)) + } + + static Void main(Str[] args) + { + repl_env := MalEnv() + // core.fan: defined using Fantom + Core.ns.each |MalFunc V, Str K| { repl_env.set(MalSymbol(K), V) } + repl_env.set(MalSymbol("eval"), MalFunc { EVAL(it[0], repl_env) }) + repl_env.set(MalSymbol("*ARGV*"), MalList((args.isEmpty ? args : args[1..-1]).map { MalString.make(it) })) + + // core.mal: defined using the language itself + REP("(def! *host-language* \"fantom\")", repl_env) + REP("(def! not (fn* (a) (if a false true)))", repl_env) + REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env) + REP("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", repl_env) + REP("(def! *gensym-counter* (atom 0))", repl_env) + REP("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))", repl_env) + REP("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))", repl_env) + + if (!args.isEmpty) + { + REP("(load-file \"${args[0]}\")", repl_env) + return + } + + REP("(println (str \"Mal [\" *host-language* \"]\"))", repl_env) + while (true) { + line := Env.cur.prompt("user> ") + if (line == null) break + if (line.isSpace) continue + try + echo(REP(line, repl_env)) + catch (MalException e) + echo("Error: ${e.serializedValue}") + catch (Err e) + echo("Error: $e.msg") + } + } +} diff --git a/fantom/tests/step5_tco.mal b/fantom/tests/step5_tco.mal new file mode 100644 index 0000000000..d20df25db7 --- /dev/null +++ b/fantom/tests/step5_tco.mal @@ -0,0 +1,15 @@ +;; Test recursive non-tail call function + +(def! sum-to (fn* (n) (if (= n 0) 0 (+ n (sum-to (- n 1)))))) + +(sum-to 10) +;=>55 + +;;; no try* yet, so test completion of side-effects +(def! res1 nil) +;=>nil +;;; For implementations without their own TCO this should fail and +;;; leave res1 unchanged +(def! res1 (sum-to 10000)) +res1 +;=>nil diff --git a/fantom/tests/stepA_mal.mal b/fantom/tests/stepA_mal.mal new file mode 100644 index 0000000000..a8c37d819d --- /dev/null +++ b/fantom/tests/stepA_mal.mal @@ -0,0 +1,32 @@ +;; Testing basic fantom interop + +(fantom-eval "7") +;=>7 + +(fantom-eval "return 3 * 9") +;=>27 + +(fantom-eval "\"7\"") +;=>"7" + +(fantom-eval "\"abcd\".upper") +;=>"ABCD" + +(fantom-eval "[7,8,9]") +;=>(7 8 9) + +(fantom-eval "[\"abc\": 789]") +;=>{"abc" 789} + +(fantom-eval "echo(\"hello\")") +; hello +;=>nil + +(fantom-eval "[\"a\",\"b\",\"c\"].join(\" \") { \"X${it}Y\" }") +;=>"XaY XbY XcY" + +(fantom-eval "[1,2,3].map { 1 + it }") +;=>(2 3 4) + +(fantom-eval "Env.cur.runtime") +;=>"java" From 80beb411e073f236e3d31c104b21ed98bf1dbcae Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Sun, 3 Jun 2018 20:14:27 +0000 Subject: [PATCH 0666/2308] travis: Add fantom to build CI matrix --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 76a1a3cf27..d6d3e49787 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,7 @@ matrix: - {env: IMPL=erlang NO_PERF=1, services: [docker]} # perf runs out of memory - {env: IMPL=es6, services: [docker]} - {env: IMPL=factor, services: [docker]} + - {env: IMPL=fantom, services: [docker]} - {env: IMPL=forth, services: [docker]} - {env: IMPL=fsharp, services: [docker]} - {env: IMPL=go, services: [docker]} From 8ea36c892f7fcd3a08a3f8a1bf16494a1801cd52 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Sun, 17 Jun 2018 19:54:54 +0200 Subject: [PATCH 0667/2308] Rename gst and pil to gnu-smalltalk and picolisp --- Makefile | 14 +++++++------- {gst => gnu-smalltalk}/Dockerfile | 0 {gst => gnu-smalltalk}/Makefile | 0 {gst => gnu-smalltalk}/core.st | 0 {gst => gnu-smalltalk}/env.st | 0 {gst => gnu-smalltalk}/func.st | 0 {gst => gnu-smalltalk}/printer.st | 0 {gst => gnu-smalltalk}/reader.st | 0 {gst => gnu-smalltalk}/readline.st | 0 {gst => gnu-smalltalk}/run | 0 {gst => gnu-smalltalk}/step0_repl.st | 0 {gst => gnu-smalltalk}/step1_read_print.st | 0 {gst => gnu-smalltalk}/step2_eval.st | 0 {gst => gnu-smalltalk}/step3_env.st | 0 {gst => gnu-smalltalk}/step4_if_fn_do.st | 0 {gst => gnu-smalltalk}/step5_tco.st | 0 {gst => gnu-smalltalk}/step6_file.st | 0 {gst => gnu-smalltalk}/step7_quote.st | 0 {gst => gnu-smalltalk}/step8_macros.st | 0 {gst => gnu-smalltalk}/step9_try.st | 0 {gst => gnu-smalltalk}/stepA_mal.st | 0 {gst => gnu-smalltalk}/tests/stepA_mal.mal | 0 {gst => gnu-smalltalk}/types.st | 0 {gst => gnu-smalltalk}/util.st | 0 {pil => picolisp}/Dockerfile | 0 {pil => picolisp}/Makefile | 0 {pil => picolisp}/core.l | 0 {pil => picolisp}/env.l | 0 {pil => picolisp}/func.l | 0 {pil => picolisp}/printer.l | 0 {pil => picolisp}/reader.l | 0 {pil => picolisp}/readline.l | 0 {pil => picolisp}/run | 0 {pil => picolisp}/step0_repl.l | 0 {pil => picolisp}/step1_read_print.l | 0 {pil => picolisp}/step2_eval.l | 0 {pil => picolisp}/step3_env.l | 0 {pil => picolisp}/step4_if_fn_do.l | 0 {pil => picolisp}/step5_tco.l | 0 {pil => picolisp}/step6_file.l | 0 {pil => picolisp}/step7_quote.l | 0 {pil => picolisp}/step8_macros.l | 0 {pil => picolisp}/step9_try.l | 0 {pil => picolisp}/stepA_mal.l | 0 {pil => picolisp}/tests/step5_tco.mal | 0 {pil => picolisp}/tests/stepA_mal.mal | 0 {pil => picolisp}/types.l | 0 47 files changed, 7 insertions(+), 7 deletions(-) rename {gst => gnu-smalltalk}/Dockerfile (100%) rename {gst => gnu-smalltalk}/Makefile (100%) rename {gst => gnu-smalltalk}/core.st (100%) rename {gst => gnu-smalltalk}/env.st (100%) rename {gst => gnu-smalltalk}/func.st (100%) rename {gst => gnu-smalltalk}/printer.st (100%) rename {gst => gnu-smalltalk}/reader.st (100%) rename {gst => gnu-smalltalk}/readline.st (100%) rename {gst => gnu-smalltalk}/run (100%) rename {gst => gnu-smalltalk}/step0_repl.st (100%) rename {gst => gnu-smalltalk}/step1_read_print.st (100%) rename {gst => gnu-smalltalk}/step2_eval.st (100%) rename {gst => gnu-smalltalk}/step3_env.st (100%) rename {gst => gnu-smalltalk}/step4_if_fn_do.st (100%) rename {gst => gnu-smalltalk}/step5_tco.st (100%) rename {gst => gnu-smalltalk}/step6_file.st (100%) rename {gst => gnu-smalltalk}/step7_quote.st (100%) rename {gst => gnu-smalltalk}/step8_macros.st (100%) rename {gst => gnu-smalltalk}/step9_try.st (100%) rename {gst => gnu-smalltalk}/stepA_mal.st (100%) rename {gst => gnu-smalltalk}/tests/stepA_mal.mal (100%) rename {gst => gnu-smalltalk}/types.st (100%) rename {gst => gnu-smalltalk}/util.st (100%) rename {pil => picolisp}/Dockerfile (100%) rename {pil => picolisp}/Makefile (100%) rename {pil => picolisp}/core.l (100%) rename {pil => picolisp}/env.l (100%) rename {pil => picolisp}/func.l (100%) rename {pil => picolisp}/printer.l (100%) rename {pil => picolisp}/reader.l (100%) rename {pil => picolisp}/readline.l (100%) rename {pil => picolisp}/run (100%) rename {pil => picolisp}/step0_repl.l (100%) rename {pil => picolisp}/step1_read_print.l (100%) rename {pil => picolisp}/step2_eval.l (100%) rename {pil => picolisp}/step3_env.l (100%) rename {pil => picolisp}/step4_if_fn_do.l (100%) rename {pil => picolisp}/step5_tco.l (100%) rename {pil => picolisp}/step6_file.l (100%) rename {pil => picolisp}/step7_quote.l (100%) rename {pil => picolisp}/step8_macros.l (100%) rename {pil => picolisp}/step9_try.l (100%) rename {pil => picolisp}/stepA_mal.l (100%) rename {pil => picolisp}/tests/step5_tco.mal (100%) rename {pil => picolisp}/tests/stepA_mal.mal (100%) rename {pil => picolisp}/types.l (100%) diff --git a/Makefile b/Makefile index 0b061af50b..6c09413982 100644 --- a/Makefile +++ b/Makefile @@ -79,11 +79,11 @@ DOCKERIZE = # IMPLS = ada awk bash basic c chuck clojure coffee common-lisp cpp crystal cs d dart \ - elisp elixir elm erlang es6 factor fantom forth fsharp go groovy gst guile haskell \ - haxe hy io java js julia kotlin livescript logo lua make mal matlab miniMAL \ - nasm nim objc objpascal ocaml perl perl6 php pil plpgsql plsql powershell ps \ - python r racket rexx rpython ruby rust scala scheme skew swift swift3 tcl \ - ts vb vhdl vimscript yorick + elisp elixir elm erlang es6 factor fantom forth fsharp go groovy gnu-smalltalk \ + guile haskell haxe hy io java js julia kotlin livescript logo lua make mal \ + matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp plpgsql \ + plsql powershell ps python r racket rexx rpython ruby rust scala scheme skew \ + swift swift3 tcl ts vb vhdl vimscript yorick EXTENSION = .mal @@ -196,7 +196,7 @@ forth_STEP_TO_PROG = forth/$($(1)).fs fsharp_STEP_TO_PROG = fsharp/$($(1)).exe go_STEP_TO_PROG = go/$($(1)) groovy_STEP_TO_PROG = groovy/$($(1)).groovy -gst_STEP_TO_PROG = gst/$($(1)).st +gnu-smalltalk_STEP_TO_PROG = gnu-smalltalk/$($(1)).st guile_STEP_TO_PROG = guile/$($(1)).scm haskell_STEP_TO_PROG = haskell/$($(1)) haxe_STEP_TO_PROG = $(haxe_STEP_TO_PROG_$(haxe_MODE)) @@ -221,7 +221,7 @@ ocaml_STEP_TO_PROG = ocaml/$($(1)) perl_STEP_TO_PROG = perl/$($(1)).pl perl6_STEP_TO_PROG = perl6/$($(1)).pl php_STEP_TO_PROG = php/$($(1)).php -pil_STEP_TO_PROG = pil/$($(1)).l +picolisp_STEP_TO_PROG = picolisp/$($(1)).l plpgsql_STEP_TO_PROG = plpgsql/$($(1)).sql plsql_STEP_TO_PROG = plsql/$($(1)).sql powershell_STEP_TO_PROG = powershell/$($(1)).ps1 diff --git a/gst/Dockerfile b/gnu-smalltalk/Dockerfile similarity index 100% rename from gst/Dockerfile rename to gnu-smalltalk/Dockerfile diff --git a/gst/Makefile b/gnu-smalltalk/Makefile similarity index 100% rename from gst/Makefile rename to gnu-smalltalk/Makefile diff --git a/gst/core.st b/gnu-smalltalk/core.st similarity index 100% rename from gst/core.st rename to gnu-smalltalk/core.st diff --git a/gst/env.st b/gnu-smalltalk/env.st similarity index 100% rename from gst/env.st rename to gnu-smalltalk/env.st diff --git a/gst/func.st b/gnu-smalltalk/func.st similarity index 100% rename from gst/func.st rename to gnu-smalltalk/func.st diff --git a/gst/printer.st b/gnu-smalltalk/printer.st similarity index 100% rename from gst/printer.st rename to gnu-smalltalk/printer.st diff --git a/gst/reader.st b/gnu-smalltalk/reader.st similarity index 100% rename from gst/reader.st rename to gnu-smalltalk/reader.st diff --git a/gst/readline.st b/gnu-smalltalk/readline.st similarity index 100% rename from gst/readline.st rename to gnu-smalltalk/readline.st diff --git a/gst/run b/gnu-smalltalk/run similarity index 100% rename from gst/run rename to gnu-smalltalk/run diff --git a/gst/step0_repl.st b/gnu-smalltalk/step0_repl.st similarity index 100% rename from gst/step0_repl.st rename to gnu-smalltalk/step0_repl.st diff --git a/gst/step1_read_print.st b/gnu-smalltalk/step1_read_print.st similarity index 100% rename from gst/step1_read_print.st rename to gnu-smalltalk/step1_read_print.st diff --git a/gst/step2_eval.st b/gnu-smalltalk/step2_eval.st similarity index 100% rename from gst/step2_eval.st rename to gnu-smalltalk/step2_eval.st diff --git a/gst/step3_env.st b/gnu-smalltalk/step3_env.st similarity index 100% rename from gst/step3_env.st rename to gnu-smalltalk/step3_env.st diff --git a/gst/step4_if_fn_do.st b/gnu-smalltalk/step4_if_fn_do.st similarity index 100% rename from gst/step4_if_fn_do.st rename to gnu-smalltalk/step4_if_fn_do.st diff --git a/gst/step5_tco.st b/gnu-smalltalk/step5_tco.st similarity index 100% rename from gst/step5_tco.st rename to gnu-smalltalk/step5_tco.st diff --git a/gst/step6_file.st b/gnu-smalltalk/step6_file.st similarity index 100% rename from gst/step6_file.st rename to gnu-smalltalk/step6_file.st diff --git a/gst/step7_quote.st b/gnu-smalltalk/step7_quote.st similarity index 100% rename from gst/step7_quote.st rename to gnu-smalltalk/step7_quote.st diff --git a/gst/step8_macros.st b/gnu-smalltalk/step8_macros.st similarity index 100% rename from gst/step8_macros.st rename to gnu-smalltalk/step8_macros.st diff --git a/gst/step9_try.st b/gnu-smalltalk/step9_try.st similarity index 100% rename from gst/step9_try.st rename to gnu-smalltalk/step9_try.st diff --git a/gst/stepA_mal.st b/gnu-smalltalk/stepA_mal.st similarity index 100% rename from gst/stepA_mal.st rename to gnu-smalltalk/stepA_mal.st diff --git a/gst/tests/stepA_mal.mal b/gnu-smalltalk/tests/stepA_mal.mal similarity index 100% rename from gst/tests/stepA_mal.mal rename to gnu-smalltalk/tests/stepA_mal.mal diff --git a/gst/types.st b/gnu-smalltalk/types.st similarity index 100% rename from gst/types.st rename to gnu-smalltalk/types.st diff --git a/gst/util.st b/gnu-smalltalk/util.st similarity index 100% rename from gst/util.st rename to gnu-smalltalk/util.st diff --git a/pil/Dockerfile b/picolisp/Dockerfile similarity index 100% rename from pil/Dockerfile rename to picolisp/Dockerfile diff --git a/pil/Makefile b/picolisp/Makefile similarity index 100% rename from pil/Makefile rename to picolisp/Makefile diff --git a/pil/core.l b/picolisp/core.l similarity index 100% rename from pil/core.l rename to picolisp/core.l diff --git a/pil/env.l b/picolisp/env.l similarity index 100% rename from pil/env.l rename to picolisp/env.l diff --git a/pil/func.l b/picolisp/func.l similarity index 100% rename from pil/func.l rename to picolisp/func.l diff --git a/pil/printer.l b/picolisp/printer.l similarity index 100% rename from pil/printer.l rename to picolisp/printer.l diff --git a/pil/reader.l b/picolisp/reader.l similarity index 100% rename from pil/reader.l rename to picolisp/reader.l diff --git a/pil/readline.l b/picolisp/readline.l similarity index 100% rename from pil/readline.l rename to picolisp/readline.l diff --git a/pil/run b/picolisp/run similarity index 100% rename from pil/run rename to picolisp/run diff --git a/pil/step0_repl.l b/picolisp/step0_repl.l similarity index 100% rename from pil/step0_repl.l rename to picolisp/step0_repl.l diff --git a/pil/step1_read_print.l b/picolisp/step1_read_print.l similarity index 100% rename from pil/step1_read_print.l rename to picolisp/step1_read_print.l diff --git a/pil/step2_eval.l b/picolisp/step2_eval.l similarity index 100% rename from pil/step2_eval.l rename to picolisp/step2_eval.l diff --git a/pil/step3_env.l b/picolisp/step3_env.l similarity index 100% rename from pil/step3_env.l rename to picolisp/step3_env.l diff --git a/pil/step4_if_fn_do.l b/picolisp/step4_if_fn_do.l similarity index 100% rename from pil/step4_if_fn_do.l rename to picolisp/step4_if_fn_do.l diff --git a/pil/step5_tco.l b/picolisp/step5_tco.l similarity index 100% rename from pil/step5_tco.l rename to picolisp/step5_tco.l diff --git a/pil/step6_file.l b/picolisp/step6_file.l similarity index 100% rename from pil/step6_file.l rename to picolisp/step6_file.l diff --git a/pil/step7_quote.l b/picolisp/step7_quote.l similarity index 100% rename from pil/step7_quote.l rename to picolisp/step7_quote.l diff --git a/pil/step8_macros.l b/picolisp/step8_macros.l similarity index 100% rename from pil/step8_macros.l rename to picolisp/step8_macros.l diff --git a/pil/step9_try.l b/picolisp/step9_try.l similarity index 100% rename from pil/step9_try.l rename to picolisp/step9_try.l diff --git a/pil/stepA_mal.l b/picolisp/stepA_mal.l similarity index 100% rename from pil/stepA_mal.l rename to picolisp/stepA_mal.l diff --git a/pil/tests/step5_tco.mal b/picolisp/tests/step5_tco.mal similarity index 100% rename from pil/tests/step5_tco.mal rename to picolisp/tests/step5_tco.mal diff --git a/pil/tests/stepA_mal.mal b/picolisp/tests/stepA_mal.mal similarity index 100% rename from pil/tests/stepA_mal.mal rename to picolisp/tests/stepA_mal.mal diff --git a/pil/types.l b/picolisp/types.l similarity index 100% rename from pil/types.l rename to picolisp/types.l From b440380f64e3dd52042161f409d9c078f69836bf Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Fri, 22 Jun 2018 18:20:38 +0200 Subject: [PATCH 0668/2308] Rename gst and pil directories in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d35c81042c..1361df846b 100644 --- a/README.md +++ b/README.md @@ -480,7 +480,7 @@ guile -L ./ stepX_YYY.scm The Smalltalk implementation of mal has been tested with GNU Smalltalk 3.2.91. ``` -cd gst +cd gnu-smalltalk ./run ``` @@ -761,7 +761,7 @@ The Picolisp implementation requires libreadline and Picolisp 3.1.11 or later. ``` -cd pil +cd picolisp ./run ``` From 808c940d861e412771580d58b7ff2b1be9a38f05 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Fri, 22 Jun 2018 18:24:47 +0200 Subject: [PATCH 0669/2308] Use correct implementation names in build matrix --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d6d3e49787..0079c27890 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ matrix: - {env: IMPL=fsharp, services: [docker]} - {env: IMPL=go, services: [docker]} - {env: IMPL=groovy, services: [docker]} - - {env: IMPL=gst, services: [docker]} + - {env: IMPL=gnu-smalltalk, services: [docker]} - {env: IMPL=guile, services: [docker]} - {env: IMPL=haskell, services: [docker]} - {env: IMPL=haxe haxe_MODE=neko, services: [docker]} @@ -61,7 +61,7 @@ matrix: - {env: IMPL=perl, services: [docker]} - {env: IMPL=perl6, services: [docker]} - {env: IMPL=php, services: [docker]} - - {env: IMPL=pil, services: [docker]} + - {env: IMPL=picolisp, services: [docker]} - {env: IMPL=plpgsql, services: [docker]} # - {env: IMPL=plsql, services: [docker]} - {env: IMPL=ps, services: [docker]} From d7f720e653dc7d26419be6e4b1c5611e2ee27a0c Mon Sep 17 00:00:00 2001 From: James Vaughan Date: Sat, 23 Jun 2018 16:08:15 -0700 Subject: [PATCH 0670/2308] Fix typo in Common Lisp section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1361df846b..3c366e5642 100644 --- a/README.md +++ b/README.md @@ -259,7 +259,7 @@ cd chuck The implementation has been tested with SBCL, CCL, CMUCL, GNU CLISP, ECL and Allegro CL on Ubuntu 16.04 and Ubuntu 12.04, see -the [README][common-lisp/README.org] for more details. Provided you have the +the [README](common-lisp/README.org) for more details. Provided you have the dependencies mentioned installed, do the following to run the implementation ``` From 9106f92cbcff54f4c46058bbd340fe593d5c95f1 Mon Sep 17 00:00:00 2001 From: Chris McCormick Date: Sun, 24 Jun 2018 19:45:29 +0800 Subject: [PATCH 0671/2308] PHP: refactor web running mechanism. --- .gitignore | 1 + php/Makefile | 5 ++++- php/README.md | 20 ++++++++++++-------- php/stepA_mal.php | 8 +------- php/webrunner.php | 8 ++++++++ 5 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 php/webrunner.php diff --git a/.gitignore b/.gitignore index c7d90d9a16..3a0dafcc60 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,7 @@ objpascal/regexpr/Source/RegExpr.ppu perl/mal.pl perl6/.precomp/ php/mal.php +php/mal-web.php ps/mal.ps python/mal.pyz r/mal.r diff --git a/php/Makefile b/php/Makefile index 981b6fa7e7..fbf83b0a3b 100644 --- a/php/Makefile +++ b/php/Makefile @@ -17,8 +17,11 @@ mal: mal.php cat $< >> $@ chmod +x $@ +mal-web.php: mal.php + cat $< | ( IFS="NON-MATCHING-IFS"; while read -r line; do if [ "$$line" = "// run mal file" ]; then echo "?>"; cat webrunner.php; echo " $@ + clean: - rm -f mal.php mal + rm -f mal.php mal mal-web.php .PHONY: stats tests $(TESTS) diff --git a/php/README.md b/php/README.md index 1333636620..53f9849d29 100644 --- a/php/README.md +++ b/php/README.md @@ -1,16 +1,20 @@ ### Running .mal scripts on PHP hosting ### -Create a symlink to `mal.php` with the same name as your `.mal` script and your script will be executed as if it was PHP. +Create a symlink to `mal-web.php` with the same name as your `.mal` script and your script will be executed as if it was PHP. -Here's an example using local dev: +Here's an example using local dev. - cd php - make mal.php - echo '(prn "Hello world!")' > myscript.mal - ln -s mal.php myscript.php - php -S 0.0.0.0:8000 +First build `mal-web.php`: -Then browse to http://localhost:8000/myscript.php and you should see "Hello world!" in your browser as `myscript.mal` is run. + cd mal/php + make mal-web.php + +Now you can create a web runnable mal script: + + echo '(println "Hello world!")' > myscript.mal + ln -s mal-web.php myscript.php + +Start a development server with `php -S 0.0.0.0:8000` and then browse to http://localhost:8000/myscript.php and you should see "Hello world!" in your browser as `myscript.mal` is run. You can do the same thing on live PHP web hosting by copying `mal.php` up and creating a symlink for each `.mal` file you want to be web-executable. diff --git a/php/stepA_mal.php b/php/stepA_mal.php index 2ac4c301a3..3518b7fed9 100644 --- a/php/stepA_mal.php +++ b/php/stepA_mal.php @@ -225,13 +225,7 @@ function rep($str) { rep("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))"); rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))"); -// if we're called in a webserver context, auto-resolve to mal file -if (php_sapi_name() != "cli") { - $malfile = str_replace(".php", ".mal", $_SERVER['SCRIPT_FILENAME']); - rep('(load-file "' . $malfile . '")'); - exit(0); -} - +// run mal file if (count($argv) > 1) { rep('(load-file "' . $argv[1] . '")'); exit(0); diff --git a/php/webrunner.php b/php/webrunner.php new file mode 100644 index 0000000000..ce720a7f20 --- /dev/null +++ b/php/webrunner.php @@ -0,0 +1,8 @@ + From ba218d6f0a342fa909919f53d8a9485b419ec961 Mon Sep 17 00:00:00 2001 From: Chris McCormick Date: Sun, 24 Jun 2018 16:31:40 +0800 Subject: [PATCH 0672/2308] Updated PHP native interop interface. --- php/README.md | 8 ++++---- php/interop.php | 16 ++++++++++++++++ php/reader.php | 6 +++++- php/stepA_mal.php | 24 ++---------------------- php/tests/stepA_mal.mal | 11 ++++++++--- 5 files changed, 35 insertions(+), 30 deletions(-) diff --git a/php/README.md b/php/README.md index 53f9849d29..24500da82e 100644 --- a/php/README.md +++ b/php/README.md @@ -32,11 +32,11 @@ Eval PHP code: Native function call: - (! date "Y-m-d" 0) - 1970-01-01 + (php/date "Y-m-d" 0) + "1970-01-01" Accessing PHP "superglobal" variables: - (get ($ "_SERVER") "PHP_SELF") - ./mal + (get php/_SERVER "PHP_SELF") + "./mal" diff --git a/php/interop.php b/php/interop.php index e83c0ef3a5..eca03ad134 100644 --- a/php/interop.php +++ b/php/interop.php @@ -45,4 +45,20 @@ function _to_mal($obj) { } } +function _to_native($name, $env) { + if (is_callable($name)) { + return _function(function() use ($name) { + $args = array_map("_to_php", func_get_args()); + $res = call_user_func_array($name, $args); + return _to_mal($res); + }); + } else if (in_array($name, ["_SERVER", "_GET", "_POST", "_FILES", "_REQUEST", "_SESSION", "_ENV", "_COOKIE"])) { + $val = $GLOBALS[$name]; + } else if (defined($name)) { + $val = constant($name); + } else { + $val = ${$name}; + } + return _to_mal($val); +} ?> diff --git a/php/reader.php b/php/reader.php index 54ed8b11c2..3408b1ae1f 100644 --- a/php/reader.php +++ b/php/reader.php @@ -27,7 +27,7 @@ function _real_token($s) { } function tokenize($str) { - $pat = "/[\s,]*(~@|[\[\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;.*|[^\s\[\]{}('\"`,;)]*)/"; + $pat = "/[\s,]*(php\/|~@|[\[\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;.*|[^\s\[\]{}('\"`,;)]*)/"; preg_match_all($pat, $str, $matches); return array_values(array_filter($matches[1], '_real_token')); } @@ -102,6 +102,10 @@ function read_form($reader) { return _list(_symbol('deref'), read_form($reader)); + case 'php/': $reader->next(); + return _list(_symbol('to-native'), + read_form($reader)); + case ')': throw new Exception("unexpected ')'"); case '(': return read_list($reader); case ']': throw new Exception("unexpected ']'"); diff --git a/php/stepA_mal.php b/php/stepA_mal.php index 3cec087791..115732b6ca 100644 --- a/php/stepA_mal.php +++ b/php/stepA_mal.php @@ -72,7 +72,6 @@ function eval_ast($ast, $env) { } function MAL_EVAL($ast, $env) { - $_SUPERGLOBALS = ["_SERVER", "_GET", "_POST", "_FILES", "_REQUEST", "_SESSION", "_ENV", "_COOKIE"]; while (true) { #echo "MAL_EVAL: " . _pr_str($ast) . "\n"; @@ -152,27 +151,8 @@ function MAL_EVAL($ast, $env) { case "fn*": return _function('MAL_EVAL', 'native', $ast[2], $env, $ast[1]); - case "$": - $var = MAL_EVAL($ast[1], $env); - if (_symbol_Q($var)) { - $varname = $var->value; - } elseif (gettype($var) === "string") { - $varname = $var; - } else { - throw new Exception("$ arg unknown type: " . gettype($var)); - } - if (in_array($varname, $_SUPERGLOBALS)) { - $val = $GLOBALS[$varname]; - } else { - $val = ${$varname}; - } - return _to_mal($val); - case "!": - $fn = $ast[1]->value; - $el = eval_ast($ast->slice(2), $env); - $args = _to_php($el); - $res = call_user_func_array($fn, $args); - return _to_mal($res); + case "to-native": + return _to_native($ast[1]->value, $env); default: $el = eval_ast($ast, $env); $f = $el[0]; diff --git a/php/tests/stepA_mal.mal b/php/tests/stepA_mal.mal index f86faeef3b..80d9a0bdec 100644 --- a/php/tests/stepA_mal.mal +++ b/php/tests/stepA_mal.mal @@ -26,15 +26,20 @@ ;; testing native function calling -(! date "Y-m-d" 0) +(php/date "Y-m-d" 0) ;=>"1970-01-01" ;; testing native function with mal callback -(! array_map (fn* [t] (if (> t 3) t)) [1 2 3 4 5 6]) +(php/array_map (fn* [t] (if (> t 3) t)) [1 2 3 4 5 6]) ;=>(nil nil nil 4 5 6) ;; testing superglobal variable access -(get ($ "_SERVER") "PHP_SELF") +(get php/_SERVER "PHP_SELF") ;=>"../php/stepA_mal.php" + +;; testing PHP constants access + +php/FILE_APPEND +;=>8 From b0687d365bdb9a497b622bbd12f6229527adb991 Mon Sep 17 00:00:00 2001 From: Chris McCormick Date: Tue, 26 Jun 2018 15:27:45 +0800 Subject: [PATCH 0673/2308] Added README section for projects using mal. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 3c366e5642..150ad85edc 100644 --- a/README.md +++ b/README.md @@ -1217,6 +1217,10 @@ make "docker-build^IMPL" out. These dependencies are download to dot-files in the /mal directory so they will persist between runs. +## Projects using mal + + * [malc](https://github.com/dubek/malc) - Mal (Make A Lisp) compiler. Compiles a Mal program to LLVM assembly language, then binary. + * [frock](https://github.com/chr15m/frock) - Clojure-flavoured PHP. Uses mal/php to run programs. ## License From 07bd1c1ff1a17ae959ddac1c6a34d8598f50151c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20=C3=8D=C3=B1igo?= Date: Fri, 6 Jul 2018 13:01:57 +0200 Subject: [PATCH 0674/2308] Go: Fix panic on call to function with wrong number of arguments --- go/src/core/core.go | 91 +++++++++++++++++++++++++++++++++ go/src/step2_eval/step2_eval.go | 19 +++++++ go/src/step3_env/step3_env.go | 19 +++++++ go/tests/step2_eval.mal | 34 ++++++++++++ go/tests/step3_env.mal | 34 ++++++++++++ go/tests/step4_if_fn_do.mal | 34 ++++++++++++ go/tests/step5_tco.mal | 35 +++++++++++++ go/tests/step6_file.mal | 34 ++++++++++++ go/tests/step7_quote.mal | 34 ++++++++++++ go/tests/step8_macros.mal | 34 ++++++++++++ go/tests/step9_try.mal | 34 ++++++++++++ go/tests/stepA_mal.mal | 34 ++++++++++++ 12 files changed, 436 insertions(+) create mode 100644 go/tests/step2_eval.mal create mode 100644 go/tests/step3_env.mal create mode 100644 go/tests/step4_if_fn_do.mal create mode 100644 go/tests/step6_file.mal create mode 100644 go/tests/step7_quote.mal create mode 100644 go/tests/step8_macros.mal create mode 100644 go/tests/step9_try.mal create mode 100644 go/tests/stepA_mal.mal diff --git a/go/src/core/core.go b/go/src/core/core.go index abbaae8986..615f0c46c1 100644 --- a/go/src/core/core.go +++ b/go/src/core/core.go @@ -446,28 +446,52 @@ func swap_BANG(a []MalType) (MalType, error) { // core namespace var NS = map[string]MalType{ "=": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return Equal_Q(a[0], a[1]), nil }, "throw": throw, "nil?": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return Nil_Q(a[0]), nil }, "true?": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return True_Q(a[0]), nil }, "false?": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return False_Q(a[0]), nil }, "symbol": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return Symbol{a[0].(string)}, nil }, "symbol?": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return Symbol_Q(a[0]), nil }, "string?": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return (String_Q(a[0]) && !Keyword_Q(a[0])), nil }, "keyword": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } if Keyword_Q(a[0]) { return a[0], nil } else { @@ -475,13 +499,22 @@ var NS = map[string]MalType{ } }, "keyword?": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return Keyword_Q(a[0]), nil }, "number?": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return Number_Q(a[0]), nil }, "fn?": fn_q, "macro?": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return MalFunc_Q(a[0]) && a[0].(MalFunc).GetMacro(), nil }, @@ -490,35 +523,65 @@ var NS = map[string]MalType{ "prn": func(a []MalType) (MalType, error) { return prn(a) }, "println": func(a []MalType) (MalType, error) { return println(a) }, "read-string": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return reader.Read_str(a[0].(string)) }, "slurp": slurp, "readline": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return readline.Readline(a[0].(string)) }, "<": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) < a[1].(int), nil }, "<=": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) <= a[1].(int), nil }, ">": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) > a[1].(int), nil }, ">=": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) >= a[1].(int), nil }, "+": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) + a[1].(int), nil }, "-": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) - a[1].(int), nil }, "*": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) * a[1].(int), nil }, "/": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) / a[1].(int), nil }, "time-ms": time_ms, @@ -527,30 +590,45 @@ var NS = map[string]MalType{ return List{a, nil}, nil }, "list?": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return List_Q(a[0]), nil }, "vector": func(a []MalType) (MalType, error) { return Vector{a, nil}, nil }, "vector?": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return Vector_Q(a[0]), nil }, "hash-map": func(a []MalType) (MalType, error) { return NewHashMap(List{a, nil}) }, "map?": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return HashMap_Q(a[0]), nil }, "assoc": assoc, "dissoc": dissoc, "get": get, "contains?": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return contains_Q(a[0], a[1]) }, "keys": keys, "vals": vals, "sequential?": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return Sequential_Q(a[0]), nil }, "cons": cons, @@ -568,12 +646,25 @@ var NS = map[string]MalType{ "with-meta": with_meta, "meta": meta, "atom": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return &Atom{a[0], nil}, nil }, "atom?": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 1); e != nil { + return nil, e + } return Atom_Q(a[0]), nil }, "deref": deref, "reset!": reset_BANG, "swap!": swap_BANG, } + +func assertArgNum(a []MalType, n int) error { + if len(a) != n { + return errors.New("wrong number of arguments") + } + return nil +} diff --git a/go/src/step2_eval/step2_eval.go b/go/src/step2_eval/step2_eval.go index 9517695613..6eb67b7616 100644 --- a/go/src/step2_eval/step2_eval.go +++ b/go/src/step2_eval/step2_eval.go @@ -102,19 +102,38 @@ func PRINT(exp MalType) (string, error) { var repl_env = map[string]MalType{ "+": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) + a[1].(int), nil }, "-": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) - a[1].(int), nil }, "*": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) * a[1].(int), nil }, "/": func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) / a[1].(int), nil }, } +func assertArgNum(a []MalType, n int) error { + if len(a) != n { + return errors.New("wrong number of arguments") + } + return nil +} + // repl func rep(str string) (MalType, error) { var exp MalType diff --git a/go/src/step3_env/step3_env.go b/go/src/step3_env/step3_env.go index 2c0575c7ce..3b2e99a169 100644 --- a/go/src/step3_env/step3_env.go +++ b/go/src/step3_env/step3_env.go @@ -164,15 +164,27 @@ func rep(str string) (MalType, error) { func main() { repl_env.Set(Symbol{"+"}, func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) + a[1].(int), nil }) repl_env.Set(Symbol{"-"}, func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) - a[1].(int), nil }) repl_env.Set(Symbol{"*"}, func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) * a[1].(int), nil }) repl_env.Set(Symbol{"/"}, func(a []MalType) (MalType, error) { + if e := assertArgNum(a, 2); e != nil { + return nil, e + } return a[0].(int) / a[1].(int), nil }) @@ -195,3 +207,10 @@ func main() { fmt.Printf("%v\n", out) } } + +func assertArgNum(a []MalType, n int) error { + if len(a) != n { + return errors.New("wrong number of arguments") + } + return nil +} diff --git a/go/tests/step2_eval.mal b/go/tests/step2_eval.mal new file mode 100644 index 0000000000..4b3a4bf27d --- /dev/null +++ b/go/tests/step2_eval.mal @@ -0,0 +1,34 @@ +;; Testing evaluation of excessive arguments +(+ 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(+ 1 2) +;=>3 + + +;; Testing evaluation of missing arguments +(+ 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(+) +;=>Error: wrong number of arguments + +;; Testing evaluation of excessive arguments +(- 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(- 1 2) +;=>-1 + + +;; Testing evaluation of missing arguments +(- 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(-) +;=>Error: wrong number of arguments + diff --git a/go/tests/step3_env.mal b/go/tests/step3_env.mal new file mode 100644 index 0000000000..4b3a4bf27d --- /dev/null +++ b/go/tests/step3_env.mal @@ -0,0 +1,34 @@ +;; Testing evaluation of excessive arguments +(+ 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(+ 1 2) +;=>3 + + +;; Testing evaluation of missing arguments +(+ 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(+) +;=>Error: wrong number of arguments + +;; Testing evaluation of excessive arguments +(- 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(- 1 2) +;=>-1 + + +;; Testing evaluation of missing arguments +(- 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(-) +;=>Error: wrong number of arguments + diff --git a/go/tests/step4_if_fn_do.mal b/go/tests/step4_if_fn_do.mal new file mode 100644 index 0000000000..bc9ea985d2 --- /dev/null +++ b/go/tests/step4_if_fn_do.mal @@ -0,0 +1,34 @@ +;; Testing evaluation of excessive arguments +(+ 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(+ 1 2) +;=>3 + + +;; Testing evaluation of missing arguments +(+ 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(+) +;=>Error: wrong number of arguments + +;; Testing evaluation of excessive arguments +(= 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(= 1 2) +;=>false + + +;; Testing evaluation of missing arguments +(= 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(=) +;=>Error: wrong number of arguments + diff --git a/go/tests/step5_tco.mal b/go/tests/step5_tco.mal index 6fa1da6fdf..f589099870 100644 --- a/go/tests/step5_tco.mal +++ b/go/tests/step5_tco.mal @@ -1,2 +1,37 @@ ;; Go: skipping non-TCO recursion ;; Reason: completes even at 100,000 + +;; Testing evaluation of excessive arguments +(+ 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(+ 1 2) +;=>3 + + +;; Testing evaluation of missing arguments +(+ 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(+) +;=>Error: wrong number of arguments + +;; Testing evaluation of excessive arguments +(= 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(= 1 2) +;=>false + + +;; Testing evaluation of missing arguments +(= 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(=) +;=>Error: wrong number of arguments + diff --git a/go/tests/step6_file.mal b/go/tests/step6_file.mal new file mode 100644 index 0000000000..bc9ea985d2 --- /dev/null +++ b/go/tests/step6_file.mal @@ -0,0 +1,34 @@ +;; Testing evaluation of excessive arguments +(+ 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(+ 1 2) +;=>3 + + +;; Testing evaluation of missing arguments +(+ 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(+) +;=>Error: wrong number of arguments + +;; Testing evaluation of excessive arguments +(= 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(= 1 2) +;=>false + + +;; Testing evaluation of missing arguments +(= 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(=) +;=>Error: wrong number of arguments + diff --git a/go/tests/step7_quote.mal b/go/tests/step7_quote.mal new file mode 100644 index 0000000000..bc9ea985d2 --- /dev/null +++ b/go/tests/step7_quote.mal @@ -0,0 +1,34 @@ +;; Testing evaluation of excessive arguments +(+ 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(+ 1 2) +;=>3 + + +;; Testing evaluation of missing arguments +(+ 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(+) +;=>Error: wrong number of arguments + +;; Testing evaluation of excessive arguments +(= 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(= 1 2) +;=>false + + +;; Testing evaluation of missing arguments +(= 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(=) +;=>Error: wrong number of arguments + diff --git a/go/tests/step8_macros.mal b/go/tests/step8_macros.mal new file mode 100644 index 0000000000..bc9ea985d2 --- /dev/null +++ b/go/tests/step8_macros.mal @@ -0,0 +1,34 @@ +;; Testing evaluation of excessive arguments +(+ 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(+ 1 2) +;=>3 + + +;; Testing evaluation of missing arguments +(+ 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(+) +;=>Error: wrong number of arguments + +;; Testing evaluation of excessive arguments +(= 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(= 1 2) +;=>false + + +;; Testing evaluation of missing arguments +(= 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(=) +;=>Error: wrong number of arguments + diff --git a/go/tests/step9_try.mal b/go/tests/step9_try.mal new file mode 100644 index 0000000000..bc9ea985d2 --- /dev/null +++ b/go/tests/step9_try.mal @@ -0,0 +1,34 @@ +;; Testing evaluation of excessive arguments +(+ 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(+ 1 2) +;=>3 + + +;; Testing evaluation of missing arguments +(+ 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(+) +;=>Error: wrong number of arguments + +;; Testing evaluation of excessive arguments +(= 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(= 1 2) +;=>false + + +;; Testing evaluation of missing arguments +(= 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(=) +;=>Error: wrong number of arguments + diff --git a/go/tests/stepA_mal.mal b/go/tests/stepA_mal.mal new file mode 100644 index 0000000000..bc9ea985d2 --- /dev/null +++ b/go/tests/stepA_mal.mal @@ -0,0 +1,34 @@ +;; Testing evaluation of excessive arguments +(+ 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(+ 1 2) +;=>3 + + +;; Testing evaluation of missing arguments +(+ 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(+) +;=>Error: wrong number of arguments + +;; Testing evaluation of excessive arguments +(= 1 2 3) +;=>Error: wrong number of arguments + +;; Valid call +(= 1 2) +;=>false + + +;; Testing evaluation of missing arguments +(= 1) +;=>Error: wrong number of arguments + +;; Testing evaluation of missing arguments +(=) +;=>Error: wrong number of arguments + From 9bc61630d2c592b2e16c47001ecb8673def42f57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20=C3=8D=C3=B1igo?= Date: Sat, 7 Jul 2018 22:04:52 +0200 Subject: [PATCH 0675/2308] Go: removed some redundant tests --- go/tests/step3_env.mal | 34 ---------------------------------- go/tests/step5_tco.mal | 37 ------------------------------------- go/tests/step6_file.mal | 34 ---------------------------------- go/tests/step7_quote.mal | 34 ---------------------------------- go/tests/step8_macros.mal | 34 ---------------------------------- go/tests/step9_try.mal | 34 ---------------------------------- go/tests/stepA_mal.mal | 34 ---------------------------------- 7 files changed, 241 deletions(-) delete mode 100644 go/tests/step3_env.mal delete mode 100644 go/tests/step5_tco.mal delete mode 100644 go/tests/step6_file.mal delete mode 100644 go/tests/step7_quote.mal delete mode 100644 go/tests/step8_macros.mal delete mode 100644 go/tests/step9_try.mal delete mode 100644 go/tests/stepA_mal.mal diff --git a/go/tests/step3_env.mal b/go/tests/step3_env.mal deleted file mode 100644 index 4b3a4bf27d..0000000000 --- a/go/tests/step3_env.mal +++ /dev/null @@ -1,34 +0,0 @@ -;; Testing evaluation of excessive arguments -(+ 1 2 3) -;=>Error: wrong number of arguments - -;; Valid call -(+ 1 2) -;=>3 - - -;; Testing evaluation of missing arguments -(+ 1) -;=>Error: wrong number of arguments - -;; Testing evaluation of missing arguments -(+) -;=>Error: wrong number of arguments - -;; Testing evaluation of excessive arguments -(- 1 2 3) -;=>Error: wrong number of arguments - -;; Valid call -(- 1 2) -;=>-1 - - -;; Testing evaluation of missing arguments -(- 1) -;=>Error: wrong number of arguments - -;; Testing evaluation of missing arguments -(-) -;=>Error: wrong number of arguments - diff --git a/go/tests/step5_tco.mal b/go/tests/step5_tco.mal deleted file mode 100644 index f589099870..0000000000 --- a/go/tests/step5_tco.mal +++ /dev/null @@ -1,37 +0,0 @@ -;; Go: skipping non-TCO recursion -;; Reason: completes even at 100,000 - -;; Testing evaluation of excessive arguments -(+ 1 2 3) -;=>Error: wrong number of arguments - -;; Valid call -(+ 1 2) -;=>3 - - -;; Testing evaluation of missing arguments -(+ 1) -;=>Error: wrong number of arguments - -;; Testing evaluation of missing arguments -(+) -;=>Error: wrong number of arguments - -;; Testing evaluation of excessive arguments -(= 1 2 3) -;=>Error: wrong number of arguments - -;; Valid call -(= 1 2) -;=>false - - -;; Testing evaluation of missing arguments -(= 1) -;=>Error: wrong number of arguments - -;; Testing evaluation of missing arguments -(=) -;=>Error: wrong number of arguments - diff --git a/go/tests/step6_file.mal b/go/tests/step6_file.mal deleted file mode 100644 index bc9ea985d2..0000000000 --- a/go/tests/step6_file.mal +++ /dev/null @@ -1,34 +0,0 @@ -;; Testing evaluation of excessive arguments -(+ 1 2 3) -;=>Error: wrong number of arguments - -;; Valid call -(+ 1 2) -;=>3 - - -;; Testing evaluation of missing arguments -(+ 1) -;=>Error: wrong number of arguments - -;; Testing evaluation of missing arguments -(+) -;=>Error: wrong number of arguments - -;; Testing evaluation of excessive arguments -(= 1 2 3) -;=>Error: wrong number of arguments - -;; Valid call -(= 1 2) -;=>false - - -;; Testing evaluation of missing arguments -(= 1) -;=>Error: wrong number of arguments - -;; Testing evaluation of missing arguments -(=) -;=>Error: wrong number of arguments - diff --git a/go/tests/step7_quote.mal b/go/tests/step7_quote.mal deleted file mode 100644 index bc9ea985d2..0000000000 --- a/go/tests/step7_quote.mal +++ /dev/null @@ -1,34 +0,0 @@ -;; Testing evaluation of excessive arguments -(+ 1 2 3) -;=>Error: wrong number of arguments - -;; Valid call -(+ 1 2) -;=>3 - - -;; Testing evaluation of missing arguments -(+ 1) -;=>Error: wrong number of arguments - -;; Testing evaluation of missing arguments -(+) -;=>Error: wrong number of arguments - -;; Testing evaluation of excessive arguments -(= 1 2 3) -;=>Error: wrong number of arguments - -;; Valid call -(= 1 2) -;=>false - - -;; Testing evaluation of missing arguments -(= 1) -;=>Error: wrong number of arguments - -;; Testing evaluation of missing arguments -(=) -;=>Error: wrong number of arguments - diff --git a/go/tests/step8_macros.mal b/go/tests/step8_macros.mal deleted file mode 100644 index bc9ea985d2..0000000000 --- a/go/tests/step8_macros.mal +++ /dev/null @@ -1,34 +0,0 @@ -;; Testing evaluation of excessive arguments -(+ 1 2 3) -;=>Error: wrong number of arguments - -;; Valid call -(+ 1 2) -;=>3 - - -;; Testing evaluation of missing arguments -(+ 1) -;=>Error: wrong number of arguments - -;; Testing evaluation of missing arguments -(+) -;=>Error: wrong number of arguments - -;; Testing evaluation of excessive arguments -(= 1 2 3) -;=>Error: wrong number of arguments - -;; Valid call -(= 1 2) -;=>false - - -;; Testing evaluation of missing arguments -(= 1) -;=>Error: wrong number of arguments - -;; Testing evaluation of missing arguments -(=) -;=>Error: wrong number of arguments - diff --git a/go/tests/step9_try.mal b/go/tests/step9_try.mal deleted file mode 100644 index bc9ea985d2..0000000000 --- a/go/tests/step9_try.mal +++ /dev/null @@ -1,34 +0,0 @@ -;; Testing evaluation of excessive arguments -(+ 1 2 3) -;=>Error: wrong number of arguments - -;; Valid call -(+ 1 2) -;=>3 - - -;; Testing evaluation of missing arguments -(+ 1) -;=>Error: wrong number of arguments - -;; Testing evaluation of missing arguments -(+) -;=>Error: wrong number of arguments - -;; Testing evaluation of excessive arguments -(= 1 2 3) -;=>Error: wrong number of arguments - -;; Valid call -(= 1 2) -;=>false - - -;; Testing evaluation of missing arguments -(= 1) -;=>Error: wrong number of arguments - -;; Testing evaluation of missing arguments -(=) -;=>Error: wrong number of arguments - diff --git a/go/tests/stepA_mal.mal b/go/tests/stepA_mal.mal deleted file mode 100644 index bc9ea985d2..0000000000 --- a/go/tests/stepA_mal.mal +++ /dev/null @@ -1,34 +0,0 @@ -;; Testing evaluation of excessive arguments -(+ 1 2 3) -;=>Error: wrong number of arguments - -;; Valid call -(+ 1 2) -;=>3 - - -;; Testing evaluation of missing arguments -(+ 1) -;=>Error: wrong number of arguments - -;; Testing evaluation of missing arguments -(+) -;=>Error: wrong number of arguments - -;; Testing evaluation of excessive arguments -(= 1 2 3) -;=>Error: wrong number of arguments - -;; Valid call -(= 1 2) -;=>false - - -;; Testing evaluation of missing arguments -(= 1) -;=>Error: wrong number of arguments - -;; Testing evaluation of missing arguments -(=) -;=>Error: wrong number of arguments - From ed1819f423cb590683ea1a2c461fc2ec5c493f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20=C3=8D=C3=B1igo?= Date: Sun, 8 Jul 2018 01:23:00 +0200 Subject: [PATCH 0676/2308] Go: added argument count checks to functions --- go/src/core/core.go | 343 +++++++++++++++----------------------------- 1 file changed, 114 insertions(+), 229 deletions(-) diff --git a/go/src/core/core.go b/go/src/core/core.go index 615f0c46c1..975f0447a2 100644 --- a/go/src/core/core.go +++ b/go/src/core/core.go @@ -115,9 +115,6 @@ func dissoc(a []MalType) (MalType, error) { } func get(a []MalType) (MalType, error) { - if len(a) != 2 { - return nil, errors.New("get requires 2 arguments") - } if Nil_Q(a[0]) { return nil, nil } @@ -154,6 +151,7 @@ func keys(a []MalType) (MalType, error) { } return List{slc, nil}, nil } + func vals(a []MalType) (MalType, error) { if !HashMap_Q(a[0]) { return nil, errors.New("keys called on non-hash map") @@ -173,7 +171,6 @@ func cons(a []MalType) (MalType, error) { if e != nil { return nil, e } - return List{append([]MalType{val}, lst...), nil}, nil } @@ -285,9 +282,6 @@ func apply(a []MalType) (MalType, error) { } func do_map(a []MalType) (MalType, error) { - if len(a) != 2 { - return nil, errors.New("map requires 2 args") - } f := a[0] results := []MalType{} args, e := GetSlice(a[1]) @@ -367,9 +361,6 @@ func seq(a []MalType) (MalType, error) { // Metadata functions func with_meta(a []MalType) (MalType, error) { - if len(a) != 2 { - return nil, errors.New("with-meta requires 2 args") - } obj := a[0] m := a[1] switch tobj := obj.(type) { @@ -428,9 +419,6 @@ func swap_BANG(a []MalType) (MalType, error) { if !Atom_Q(a[0]) { return nil, errors.New("swap! called with non-atom") } - if len(a) < 2 { - return nil, errors.New("swap! requires at least 2 args") - } atm := a[0].(*Atom) args := []MalType{atm.Val} f := a[1] @@ -445,226 +433,123 @@ func swap_BANG(a []MalType) (MalType, error) { // core namespace var NS = map[string]MalType{ - "=": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 2); e != nil { - return nil, e - } - return Equal_Q(a[0], a[1]), nil - }, - "throw": throw, - "nil?": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return Nil_Q(a[0]), nil - }, - "true?": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return True_Q(a[0]), nil - }, - "false?": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return False_Q(a[0]), nil - }, - "symbol": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return Symbol{a[0].(string)}, nil - }, - "symbol?": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return Symbol_Q(a[0]), nil - }, - "string?": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return (String_Q(a[0]) && !Keyword_Q(a[0])), nil - }, - "keyword": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } + "=": call2b(Equal_Q), + "throw": call1e(throw), + "nil?": call1b(Nil_Q), + "true?": call1b(True_Q), + "false?": call1b(False_Q), + "symbol": call1e(func(a []MalType) (MalType, error) { return Symbol{a[0].(string)}, nil }), + "symbol?": call1b(Symbol_Q), + "string?": call1e(func(a []MalType) (MalType, error) { return (String_Q(a[0]) && !Keyword_Q(a[0])), nil }), + "keyword": call1e(func(a []MalType) (MalType, error) { if Keyword_Q(a[0]) { return a[0], nil } else { return NewKeyword(a[0].(string)) } - }, - "keyword?": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return Keyword_Q(a[0]), nil - }, - "number?": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return Number_Q(a[0]), nil - }, - "fn?": fn_q, - "macro?": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return MalFunc_Q(a[0]) && a[0].(MalFunc).GetMacro(), nil - }, - - "pr-str": func(a []MalType) (MalType, error) { return pr_str(a) }, - "str": func(a []MalType) (MalType, error) { return str(a) }, - "prn": func(a []MalType) (MalType, error) { return prn(a) }, - "println": func(a []MalType) (MalType, error) { return println(a) }, - "read-string": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return reader.Read_str(a[0].(string)) - }, - "slurp": slurp, - "readline": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return readline.Readline(a[0].(string)) - }, - - "<": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 2); e != nil { - return nil, e - } - return a[0].(int) < a[1].(int), nil - }, - "<=": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 2); e != nil { - return nil, e - } - return a[0].(int) <= a[1].(int), nil - }, - ">": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 2); e != nil { - return nil, e - } - return a[0].(int) > a[1].(int), nil - }, - ">=": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 2); e != nil { - return nil, e - } - return a[0].(int) >= a[1].(int), nil - }, - "+": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 2); e != nil { - return nil, e - } - return a[0].(int) + a[1].(int), nil - }, - "-": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 2); e != nil { - return nil, e - } - return a[0].(int) - a[1].(int), nil - }, - "*": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 2); e != nil { - return nil, e - } - return a[0].(int) * a[1].(int), nil - }, - "/": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 2); e != nil { - return nil, e - } - return a[0].(int) / a[1].(int), nil - }, - "time-ms": time_ms, - - "list": func(a []MalType) (MalType, error) { - return List{a, nil}, nil - }, - "list?": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return List_Q(a[0]), nil - }, - "vector": func(a []MalType) (MalType, error) { - return Vector{a, nil}, nil - }, - "vector?": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return Vector_Q(a[0]), nil - }, - "hash-map": func(a []MalType) (MalType, error) { - return NewHashMap(List{a, nil}) - }, - "map?": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return HashMap_Q(a[0]), nil - }, - "assoc": assoc, - "dissoc": dissoc, - "get": get, - "contains?": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 2); e != nil { - return nil, e - } - return contains_Q(a[0], a[1]) - }, - "keys": keys, - "vals": vals, - - "sequential?": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return Sequential_Q(a[0]), nil - }, - "cons": cons, - "concat": concat, - "nth": nth, - "first": first, - "rest": rest, - "empty?": empty_Q, - "count": count, - "apply": apply, - "map": do_map, - "conj": conj, - "seq": seq, - - "with-meta": with_meta, - "meta": meta, - "atom": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return &Atom{a[0], nil}, nil - }, - "atom?": func(a []MalType) (MalType, error) { - if e := assertArgNum(a, 1); e != nil { - return nil, e - } - return Atom_Q(a[0]), nil - }, - "deref": deref, - "reset!": reset_BANG, - "swap!": swap_BANG, -} - -func assertArgNum(a []MalType, n int) error { - if len(a) != n { - return errors.New("wrong number of arguments") + }), + "keyword?": call1b(Keyword_Q), + "number?": call1b(Number_Q), + "fn?": call1e(fn_q), + "macro?": call1e(func(a []MalType) (MalType, error) { return MalFunc_Q(a[0]) && a[0].(MalFunc).GetMacro(), nil }), + "pr-str": callNe(pr_str), + "str": callNe(str), + "prn": callNe(prn), + "println": callNe(println), + "read-string": call1e(func(a []MalType) (MalType, error) { return reader.Read_str(a[0].(string)) }), + "slurp": call1e(slurp), + "readline": call1e(func(a []MalType) (MalType, error) { return readline.Readline(a[0].(string)) }), + "<": call2e(func(a []MalType) (MalType, error) { return a[0].(int) < a[1].(int), nil }), + "<=": call2e(func(a []MalType) (MalType, error) { return a[0].(int) <= a[1].(int), nil }), + ">": call2e(func(a []MalType) (MalType, error) { return a[0].(int) > a[1].(int), nil }), + ">=": call2e(func(a []MalType) (MalType, error) { return a[0].(int) >= a[1].(int), nil }), + "+": call2e(func(a []MalType) (MalType, error) { return a[0].(int) + a[1].(int), nil }), + "-": call2e(func(a []MalType) (MalType, error) { return a[0].(int) - a[1].(int), nil }), + "*": call2e(func(a []MalType) (MalType, error) { return a[0].(int) * a[1].(int), nil }), + "/": call2e(func(a []MalType) (MalType, error) { return a[0].(int) / a[1].(int), nil }), + "time-ms": call0e(time_ms), + "list": callNe(func(a []MalType) (MalType, error) { return List{a, nil}, nil }), + "list?": call1b(List_Q), + "vector": callNe(func(a []MalType) (MalType, error) { return Vector{a, nil}, nil }), + "vector?": call1b(Vector_Q), + "hash-map": callNe(func(a []MalType) (MalType, error) { return NewHashMap(List{a, nil}) }), + "map?": call1b(HashMap_Q), + "assoc": callNe(assoc), // at least 3 + "dissoc": callNe(dissoc), // at least 2 + "get": call2e(get), + "contains?": call2e(func(a []MalType) (MalType, error) { return contains_Q(a[0], a[1]) }), + "keys": call1e(keys), + "vals": call1e(vals), + "sequential?": call1b(Sequential_Q), + "cons": call2e(cons), + "concat": callNe(concat), + "nth": call2e(nth), + "first": call1e(first), + "rest": call1e(rest), + "empty?": call1e(empty_Q), + "count": call1e(count), + "apply": callNe(apply), // at least 2 + "map": call2e(do_map), + "conj": callNe(conj), // at least 2 + "seq": call1e(seq), + "with-meta": call2e(with_meta), + "meta": call1e(meta), + "atom": call1e(func(a []MalType) (MalType, error) { return &Atom{a[0], nil}, nil }), + "atom?": call1b(Atom_Q), + "deref": call1e(deref), + "reset!": call2e(reset_BANG), + "swap!": callNe(swap_BANG), +} + +// callXX functions check the number of arguments +func call0e(f func([]MalType) (MalType, error)) func([]MalType) (MalType, error) { + return func(args []MalType) (MalType, error) { + if len(args) != 0 { + return nil, fmt.Errorf("wrong number of arguments (%d instead of 0)", len(args)) + } + return f(args) + } +} + +func call1e(f func([]MalType) (MalType, error)) func([]MalType) (MalType, error) { + return func(args []MalType) (MalType, error) { + if len(args) != 1 { + return nil, fmt.Errorf("wrong number of arguments (%d instead of 1)", len(args)) + } + return f(args) + } +} + +func call2e(f func([]MalType) (MalType, error)) func([]MalType) (MalType, error) { + return func(args []MalType) (MalType, error) { + if len(args) != 2 { + return nil, fmt.Errorf("wrong number of arguments (%d instead of 2)", len(args)) + } + return f(args) + } +} + +func callNe(f func([]MalType) (MalType, error)) func([]MalType) (MalType, error) { + // just for documenting purposes, does not check anything + return func(args []MalType) (MalType, error) { + return f(args) + } +} + +func call1b(f func(MalType) bool) func([]MalType) (MalType, error) { + return func(args []MalType) (MalType, error) { + if len(args) != 1 { + return nil, fmt.Errorf("wrong number of arguments (%d instead of 1)", len(args)) + } + return f(args[0]), nil + } +} + +func call2b(f func(MalType, MalType) bool) func([]MalType) (MalType, error) { + return func(args []MalType) (MalType, error) { + if len(args) != 2 { + return nil, fmt.Errorf("wrong number of arguments (%d instead of 2)", len(args)) + } + return f(args[0], args[1]), nil } - return nil } From 3932a29be3219bc24d5358b8bee1177b522ada84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20=C3=8D=C3=B1igo?= Date: Sun, 8 Jul 2018 01:44:28 +0200 Subject: [PATCH 0677/2308] Go: modified error messages --- go/tests/step4_if_fn_do.mal | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/go/tests/step4_if_fn_do.mal b/go/tests/step4_if_fn_do.mal index bc9ea985d2..2134ce66f8 100644 --- a/go/tests/step4_if_fn_do.mal +++ b/go/tests/step4_if_fn_do.mal @@ -1,6 +1,6 @@ ;; Testing evaluation of excessive arguments (+ 1 2 3) -;=>Error: wrong number of arguments +;=>Error: wrong number of arguments (3 instead of 2) ;; Valid call (+ 1 2) @@ -9,15 +9,15 @@ ;; Testing evaluation of missing arguments (+ 1) -;=>Error: wrong number of arguments +;=>Error: wrong number of arguments (1 instead of 2) ;; Testing evaluation of missing arguments (+) -;=>Error: wrong number of arguments +;=>Error: wrong number of arguments (0 instead of 2) ;; Testing evaluation of excessive arguments (= 1 2 3) -;=>Error: wrong number of arguments +;=>Error: wrong number of arguments (3 instead of 2) ;; Valid call (= 1 2) @@ -26,9 +26,9 @@ ;; Testing evaluation of missing arguments (= 1) -;=>Error: wrong number of arguments +;=>Error: wrong number of arguments (1 instead of 2) ;; Testing evaluation of missing arguments (=) -;=>Error: wrong number of arguments +;=>Error: wrong number of arguments (0 instead of 2) From a536db2105c2e29cc2ee4d1133a937128171de1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20=C3=8D=C3=B1igo?= Date: Sun, 8 Jul 2018 11:20:46 +0200 Subject: [PATCH 0678/2308] Go: added file back accidentally removed --- go/tests/step5_tco.mal | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 go/tests/step5_tco.mal diff --git a/go/tests/step5_tco.mal b/go/tests/step5_tco.mal new file mode 100644 index 0000000000..093d5872fe --- /dev/null +++ b/go/tests/step5_tco.mal @@ -0,0 +1,2 @@ +;; Go: skipping non-TCO recursion +;; Reason: completes even at 100,000 From 1e8fc91409d2b138634693b3cd3a8da7a4308b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20=C3=8D=C3=B1igo?= Date: Sun, 8 Jul 2018 11:22:49 +0200 Subject: [PATCH 0679/2308] Go: Minor --- go/tests/step5_tco.mal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/tests/step5_tco.mal b/go/tests/step5_tco.mal index 093d5872fe..6fa1da6fdf 100644 --- a/go/tests/step5_tco.mal +++ b/go/tests/step5_tco.mal @@ -1,2 +1,2 @@ -;; Go: skipping non-TCO recursion +;; Go: skipping non-TCO recursion ;; Reason: completes even at 100,000 From e2352e739b5174bea7e427f8f018614ee62036e5 Mon Sep 17 00:00:00 2001 From: Tim Morgan Date: Sun, 8 Jul 2018 22:21:01 -0500 Subject: [PATCH 0680/2308] Test that (not nil) returns true --- tests/step4_if_fn_do.mal | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/step4_if_fn_do.mal b/tests/step4_if_fn_do.mal index 6e503f9748..1117818d4b 100644 --- a/tests/step4_if_fn_do.mal +++ b/tests/step4_if_fn_do.mal @@ -243,6 +243,8 @@ a ;; Testing language defined not function (not false) ;=>true +(not nil) +;=>true (not true) ;=>false (not "a") From 634ca5e98b0e9fac232159a0a23a15e13b7a7906 Mon Sep 17 00:00:00 2001 From: Chris McCormick Date: Thu, 12 Jul 2018 20:21:03 +0800 Subject: [PATCH 0681/2308] PHP: Wrap some native "language constructs". Common ones which can't be called otherwise. Can now be reached via php/exit, php/print, php/require. Fixes chr15m/frock#7 --- php/interop.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/php/interop.php b/php/interop.php index eca03ad134..bb14d880ed 100644 --- a/php/interop.php +++ b/php/interop.php @@ -52,6 +52,22 @@ function _to_native($name, $env) { $res = call_user_func_array($name, $args); return _to_mal($res); }); + // special case for language constructs + } else if ($name == "print") { + return _function(function($value) { + print(_to_php($value)); + return null; + }); + } else if ($name == "exit") { + return _function(function($value) { + exit(_to_php($value)); + return null; + }); + } else if ($name == "require") { + return _function(function($value) { + require(_to_php($value)); + return null; + }); } else if (in_array($name, ["_SERVER", "_GET", "_POST", "_FILES", "_REQUEST", "_SESSION", "_ENV", "_COOKIE"])) { $val = $GLOBALS[$name]; } else if (defined($name)) { From 63462732fa1121a017c6f657c746b5e2dec90cc3 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 12 Jul 2018 09:27:21 -0500 Subject: [PATCH 0682/2308] java: fix by updating to xenial, java 8, maven 3 The vivid build with java 7 and maven 2 was no longer able to retrieve upstream deps and was failing. --- java/Dockerfile | 6 +++--- java/pom.xml | 1 + java/run | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/java/Dockerfile b/java/Dockerfile index 0dc69c55d5..7c9fdd4aea 100644 --- a/java/Dockerfile +++ b/java/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:xenial MAINTAINER Joel Martin ########################################################## @@ -22,7 +22,7 @@ WORKDIR /mal ########################################################## # Java and maven -RUN apt-get -y install openjdk-7-jdk -RUN apt-get -y install maven2 +RUN apt-get -y install openjdk-8-jdk +RUN apt-get -y install maven ENV MAVEN_OPTS -Duser.home=/mal diff --git a/java/pom.xml b/java/pom.xml index 5ee5b7c73b..63621f87b5 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -29,6 +29,7 @@ maven-compiler-plugin + 3.0 1.7 1.7 diff --git a/java/run b/java/run index 8252305dac..7119297e78 100755 --- a/java/run +++ b/java/run @@ -6,4 +6,4 @@ if [ "$#" -gt 0 ]; then args="$args '$a'" done fi -exec mvn -quiet exec:java -Dexec.mainClass="mal.${STEP:-stepA_mal}" "$args" +exec mvn -quiet -e exec:java -Dexec.mainClass="mal.${STEP:-stepA_mal}" ${args:+"$args"} From 6e59c4f26d213399f68aeafed2ed2566daa469d8 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 12 Jul 2018 13:51:02 -0500 Subject: [PATCH 0683/2308] scala: fix w/ xenial, sbt 0.14.6, update build. Build had become out of date so update docker image to xenial and sbt to 0.14.6 and update Scala build files to go with new sbt version. --- scala/Dockerfile | 4 ++-- scala/assembly.sbt | 6 ++---- scala/build.sbt | 1 - scala/project/assembly.sbt | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/scala/Dockerfile b/scala/Dockerfile index 989c32e17d..1aa29b5dde 100644 --- a/scala/Dockerfile +++ b/scala/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:xenial MAINTAINER Joel Martin ########################################################## @@ -22,7 +22,7 @@ WORKDIR /mal ########################################################## # Java and maven -RUN apt-get -y install openjdk-7-jdk +RUN apt-get -y install openjdk-8-jdk #RUN apt-get -y install maven2 #ENV MAVEN_OPTS -Duser.home=/mal diff --git a/scala/assembly.sbt b/scala/assembly.sbt index 89a285dc1f..0b3ef91c19 100644 --- a/scala/assembly.sbt +++ b/scala/assembly.sbt @@ -1,8 +1,6 @@ -import AssemblyKeys._ // put this at the top of the file - -assemblySettings +import sbtassembly.AssemblyPlugin.defaultShellScript test in assembly := {} -jarName in assembly := "mal.jar" +assemblyJarName in assembly := "mal.jar" mainClass in assembly := Some("stepA_mal") assemblyOption in assembly ~= { _.copy(prependShellScript = Some(defaultShellScript)) } diff --git a/scala/build.sbt b/scala/build.sbt index 8c7430bd07..c5bfda397c 100644 --- a/scala/build.sbt +++ b/scala/build.sbt @@ -1,5 +1,4 @@ lazy val root = (project in file(".")). - settings(assemblySettings: _*). settings( name := "mal", version := "0.1", diff --git a/scala/project/assembly.sbt b/scala/project/assembly.sbt index 54c32528e9..652a3b93be 100644 --- a/scala/project/assembly.sbt +++ b/scala/project/assembly.sbt @@ -1 +1 @@ -addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2") +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6") From e44c0be968f56fc12c34133a90ffd502cd706997 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 12 Jul 2018 14:21:55 -0500 Subject: [PATCH 0684/2308] TypeScript: fix HOME bug and node symlink. Update the Dockerfile to fix a symlink loop. Update node_readline.ts to fix a failure with usage of process.env.HOME which started failing for some reason. --- ts/Dockerfile | 1 - ts/node_readline.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ts/Dockerfile b/ts/Dockerfile index 8926b363f5..1bb0452dc9 100644 --- a/ts/Dockerfile +++ b/ts/Dockerfile @@ -24,6 +24,5 @@ WORKDIR /mal RUN apt-get -y install build-essential RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - RUN apt-get -y install nodejs -RUN ln -sf nodejs /usr/bin/node ENV NPM_CONFIG_CACHE /mal/.npm diff --git a/ts/node_readline.ts b/ts/node_readline.ts index dca3ac5610..cc0af945d8 100644 --- a/ts/node_readline.ts +++ b/ts/node_readline.ts @@ -6,7 +6,7 @@ import * as fs from "fs"; const RL_LIB = "libreadline"; // NOTE: libreadline is GPL // var RL_LIB = "libedit"; -const HISTORY_FILE = path.join(process.env.HOME, ".mal-history"); +const HISTORY_FILE = path.join(process.env.HOME || ".", ".mal-history"); const rllib = ffi.Library(RL_LIB, { "readline": ["string", ["string"]], From bbd62dc97c5deb43eec6ed9cc5c7bace06b76671 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 12 Jul 2018 14:24:14 -0500 Subject: [PATCH 0685/2308] tests/stepA: allow time-ms result to be signed. In Java the time-ms value was negative. This actually still works fine because arithmetic comparison is still correct. So allow signed time-ms results. --- tests/stepA_mal.mal | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/stepA_mal.mal b/tests/stepA_mal.mal index 1d3601f098..ac8a717f80 100644 --- a/tests/stepA_mal.mal +++ b/tests/stepA_mal.mal @@ -268,8 +268,8 @@ ;; ;; Testing time-ms function (def! start-time (time-ms)) -(> start-time 0) -;=>true +(= start-time 0) +;=>false (let* [sumdown (fn* (N) (if (> N 0) (+ N (sumdown (- N 1))) 0))] (sumdown 10)) ; Waste some time ;=>55 (> (time-ms) start-time) From 9a66ffcd2e411e0dff235e3055e453b1bb2800a1 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 28 Jun 2018 22:02:42 -0500 Subject: [PATCH 0686/2308] rust: remove rustyline ANSI CSI codes from output --- runtest.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/runtest.py b/runtest.py index 1930b62c1d..1f9dd7033c 100755 --- a/runtest.py +++ b/runtest.py @@ -133,6 +133,13 @@ def read_to_prompt(self, prompts, timeout): else: self.buf += new_data self.buf = self.buf.replace("\r\r", "\r") + # Remove ANSI codes generally + #ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]') + # Remove rustyline ANSI CSI codes: + # - [6C - CR + cursor forward + # - [6K - CR + erase in line + ansi_escape = re.compile(r'\r\x1B\[[0-9]*[CK]') + self.buf = ansi_escape.sub('', self.buf) for prompt in prompts: regexp = re.compile(prompt) match = regexp.search(self.buf) From 4ef4b17cd0bd0c2c8ee7ae908a405db2af849f70 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 12 Jul 2018 00:23:40 -0500 Subject: [PATCH 0687/2308] rust: Update rust and update/refactor implementation This rewrites the rust implementation to use many new features of the current version of rust. The refactor is much more concise (only 2/3rds the size) and switches to using a lot of the more functional features (iterators, closures, etc) that have been added or improved in rust. Unfortunately, the implementation is a fair bit slower (about 30% on perf3). It's not clear why this is the case but concision and being more idiomatic wins over performance. --- Makefile | 2 +- rust/.gitignore | 1 + rust/Cargo.lock | 186 ++++++++++ rust/Cargo.toml | 62 +++- rust/Dockerfile | 40 +-- rust/Makefile | 43 +-- rust/core.rs | 327 ++++++++++++++++++ rust/env.rs | 84 +++++ rust/printer.rs | 60 ++++ rust/reader.rs | 142 ++++++++ rust/readline.rs | 0 rust/run | 2 +- rust/src/bin/step0_repl.rs | 26 -- rust/src/bin/step1_read_print.rs | 39 --- rust/src/bin/step2_eval.rs | 114 ------- rust/src/bin/step3_env.rs | 165 --------- rust/src/bin/step4_if_fn_do.rs | 185 ---------- rust/src/bin/step5_tco.rs | 202 ----------- rust/src/bin/step6_file.rs | 232 ------------- rust/src/bin/step7_quote.rs | 281 ---------------- rust/src/bin/step8_macros.rs | 350 ------------------- rust/src/bin/step9_try.rs | 380 --------------------- rust/src/bin/stepA_mal.rs | 385 --------------------- rust/src/core.rs | 560 ------------------------------- rust/src/env.rs | 106 ------ rust/src/lib.rs | 15 - rust/src/printer.rs | 47 --- rust/src/reader.rs | 198 ----------- rust/src/readline.rs | 81 ----- rust/src/types.rs | 430 ------------------------ rust/step0_repl.rs | 33 ++ rust/step1_read_print.rs | 53 +++ rust/step2_eval.rs | 135 ++++++++ rust/step3_env.rs | 159 +++++++++ rust/step4_if_fn_do.rs | 182 ++++++++++ rust/step5_tco.rs | 209 ++++++++++++ rust/step6_file.rs | 232 +++++++++++++ rust/step7_quote.rs | 274 +++++++++++++++ rust/step8_macros.rs | 343 +++++++++++++++++++ rust/step9_try.rs | 364 ++++++++++++++++++++ rust/stepA_mal.rs | 370 ++++++++++++++++++++ rust/tests/step5_tco.mal | 2 - rust/types.rs | 232 +++++++++++++ 43 files changed, 3467 insertions(+), 3866 deletions(-) create mode 100644 rust/.gitignore create mode 100644 rust/Cargo.lock create mode 100644 rust/core.rs create mode 100644 rust/env.rs create mode 100644 rust/printer.rs create mode 100644 rust/reader.rs create mode 100644 rust/readline.rs delete mode 100644 rust/src/bin/step0_repl.rs delete mode 100644 rust/src/bin/step1_read_print.rs delete mode 100644 rust/src/bin/step2_eval.rs delete mode 100644 rust/src/bin/step3_env.rs delete mode 100644 rust/src/bin/step4_if_fn_do.rs delete mode 100644 rust/src/bin/step5_tco.rs delete mode 100644 rust/src/bin/step6_file.rs delete mode 100644 rust/src/bin/step7_quote.rs delete mode 100644 rust/src/bin/step8_macros.rs delete mode 100644 rust/src/bin/step9_try.rs delete mode 100644 rust/src/bin/stepA_mal.rs delete mode 100644 rust/src/core.rs delete mode 100644 rust/src/env.rs delete mode 100644 rust/src/lib.rs delete mode 100644 rust/src/printer.rs delete mode 100644 rust/src/reader.rs delete mode 100644 rust/src/readline.rs delete mode 100644 rust/src/types.rs create mode 100644 rust/step0_repl.rs create mode 100644 rust/step1_read_print.rs create mode 100644 rust/step2_eval.rs create mode 100644 rust/step3_env.rs create mode 100644 rust/step4_if_fn_do.rs create mode 100644 rust/step5_tco.rs create mode 100644 rust/step6_file.rs create mode 100644 rust/step7_quote.rs create mode 100644 rust/step8_macros.rs create mode 100644 rust/step9_try.rs create mode 100644 rust/stepA_mal.rs delete mode 100644 rust/tests/step5_tco.mal create mode 100644 rust/types.rs diff --git a/Makefile b/Makefile index 6c09413982..62c9b330fa 100644 --- a/Makefile +++ b/Makefile @@ -232,7 +232,7 @@ racket_STEP_TO_PROG = racket/$($(1)).rkt rexx_STEP_TO_PROG = rexx/$($(1)).rexxpp rpython_STEP_TO_PROG = rpython/$($(1)) ruby_STEP_TO_PROG = ruby/$($(1)).rb -rust_STEP_TO_PROG = rust/target/release/$($(1)) +rust_STEP_TO_PROG = rust/$($(1)) scala_STEP_TO_PROG = scala/target/scala-2.11/classes/$($(1)).class scheme_STEP_TO_PROG = $(scheme_STEP_TO_PROG_$(scheme_MODE)) skew_STEP_TO_PROG = skew/$($(1)).js diff --git a/rust/.gitignore b/rust/.gitignore new file mode 100644 index 0000000000..9fe342cbf4 --- /dev/null +++ b/rust/.gitignore @@ -0,0 +1 @@ +./target diff --git a/rust/Cargo.lock b/rust/Cargo.lock new file mode 100644 index 0000000000..c4891eb4d3 --- /dev/null +++ b/rust/Cargo.lock @@ -0,0 +1,186 @@ +[[package]] +name = "aho-corasick" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "either" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "encode_unicode" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "itertools" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.42" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nix" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rust2" +version = "0.1.0" +dependencies = [ + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustyline 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustyline" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "encode_unicode 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ucd-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "utf8-ranges" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum aho-corasick 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f0ba20154ea1f47ce2793322f049c5646cc6d0fa9759d5f333f286e507bf8080" +"checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" +"checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" +"checksum encode_unicode 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "28d65f1f5841ef7c6792861294b72beda34c664deb8be27970f36c306b7da1ce" +"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +"checksum itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)" = "f58856976b776fedd95533137617a02fb25719f40e7d9b01c7043cd65474f450" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739" +"checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1" +"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" +"checksum nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bfb3ddedaa14746434a02041940495bf11325c22f6d36125d3bdd56090d50a79" +"checksum regex 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13c93d55961981ba9226a213b385216f83ab43bd6ac53ab16b2eeb47e337cf4e" +"checksum regex-syntax 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05b06a75f5217880fc5e905952a42750bf44787e56a6c6d6852ed0992f5e1d54" +"checksum rustyline 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "00b06ac9c8e8e3e83b33d175d39a9f7b6c2c930c82990593719c8e48788ae2d9" +"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" +"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 70e24d3beb..3eb30b08eb 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,11 +1,57 @@ [package] - -name = "mal" -version = "0.0.1" -authors = [ "Your name " ] +name = "rust2" +version = "0.1.0" +authors = ["root"] [dependencies] -time = "0.1" -regex = "0.1" -libc = "0.1" -num = "*" +rustyline = "1.0.0" +lazy_static = "1.0.1" + +regex = "1.0.0" +itertools = "0.7.4" +fnv = "1.0.3" + + +[[bin]] +name = "step0_repl" +path = "step0_repl.rs" + +[[bin]] +name = "step1_read_print" +path = "step1_read_print.rs" + +[[bin]] +name = "step2_eval" +path = "step2_eval.rs" + +[[bin]] +name = "step3_env" +path = "step3_env.rs" + +[[bin]] +name = "step4_if_fn_do" +path = "step4_if_fn_do.rs" + +[[bin]] +name = "step5_tco" +path = "step5_tco.rs" + +[[bin]] +name = "step6_file" +path = "step6_file.rs" + +[[bin]] +name = "step7_quote" +path = "step7_quote.rs" + +[[bin]] +name = "step8_macros" +path = "step8_macros.rs" + +[[bin]] +name = "step9_try" +path = "step9_try.rs" + +[[bin]] +name = "stepA_mal" +path = "stepA_mal.rs" diff --git a/rust/Dockerfile b/rust/Dockerfile index ba373a258f..fb1fdd69ee 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -1,39 +1,3 @@ -FROM ubuntu:vivid -MAINTAINER Joel Martin +FROM rust -########################################################## -# General requirements for testing or common across many -# implementations -########################################################## - -RUN apt-get -y update - -# Required for running tests -RUN apt-get -y install make python - -# Some typical implementation and test requirements -RUN apt-get -y install curl libreadline-dev libedit-dev - -RUN mkdir -p /mal -WORKDIR /mal - -########################################################## -# Specific implementation requirements -########################################################## - -# Install g++ for any C/C++ based implementations -RUN apt-get -y install g++ - -# Based on https://github.com/Scorpil/docker-rust/blob/master/stable/Dockerfile - -ENV RUST_ARCHIVE=rust-1.14.0-x86_64-unknown-linux-gnu.tar.gz -ENV RUST_DOWNLOAD_URL=https://static.rust-lang.org/dist/$RUST_ARCHIVE - -RUN mkdir -p /rust && cd /rust \ - && curl -fsOSL $RUST_DOWNLOAD_URL \ - && curl -s $RUST_DOWNLOAD_URL.sha256 | sha256sum -c - \ - && tar -C /rust -xzf $RUST_ARCHIVE --strip-components=1 \ - && rm $RUST_ARCHIVE \ - && ./install.sh - -ENV CARGO_HOME /mal/.cargo +ENV CARGO_HOME=/mal diff --git a/rust/Makefile b/rust/Makefile index 03a1a6936e..49fc80fb21 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -1,40 +1,43 @@ -##################### -SOURCES_BASE = src/types.rs src/readline.rs \ - src/reader.rs src/printer.rs \ - src/env.rs src/core.rs -SOURCES_LISP = src/env.rs src/core.rs src/bin/stepA_mal.rs +UPPER_STEPS = step4_if_fn_do step5_tco step6_file step7_quote step8_macros step9_try stepA_mal +STEPS = step0_repl step1_read_print step2_eval step3_env $(UPPER_STEPS) + +SOURCES_BASE = types.rs reader.rs printer.rs +SOURCES_LISP = env.rs core.rs stepA_mal.rs SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -##################### +all: $(STEPS) -SRCS = step0_repl.rs step1_read_print.rs step2_eval.rs step3_env.rs \ - step4_if_fn_do.rs step5_tco.rs step6_file.rs step7_quote.rs \ - step8_macros.rs step9_try.rs stepA_mal.rs -BINS = $(SRCS:%.rs=target/release/%) +dist: mal -##################### +mal: stepA_mal + cp $< $@ -all: $(BINS) +%: %.rs + cargo build --release --bin $* + cp target/release/$* $@ -dist: mal +STEP0_DEPS = readline.rs +STEP1_DEPS = $(STEP0_DEPS) types.rs reader.rs printer.rs +STEP3_DEPS = $(STEP1_DEPS) env.rs +STEP4_DEPS = $(STEP3_DEPS) core.rs -mal: target/release/stepA_mal - cp $< $@ +step0_repl: $(STEP0_DEPS) +step1_read_print step2_eval: $(STEP1_DEPS) +step3_env: $(STEP3_DEPS) +$(UPPER_STEPS): $(STEP4_DEPS) -# TODO: would be nice to build just the step requested -$(BINS): target/release/%: src/bin/%.rs $(wildcard src/*.rs) - cargo build --release +.PHONY: clean stats stats-lisp clean: cargo clean + rm -f $(STEPS) rm -f mal -.PHONY: stats stats-lisp mal - stats: $(SOURCES) @wc $^ @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" stats-lisp: $(SOURCES_LISP) @wc $^ @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" + diff --git a/rust/core.rs b/rust/core.rs new file mode 100644 index 0000000000..438c8486c2 --- /dev/null +++ b/rust/core.rs @@ -0,0 +1,327 @@ +use std::rc::Rc; +use std::fs::File; +use std::io::Read; +use std::sync::Mutex; +use std::time::{SystemTime, UNIX_EPOCH}; + +extern crate rustyline; +use rustyline::error::ReadlineError; +use rustyline::Editor; + +use types::{MalVal,MalArgs,MalRet,error,func,hash_map,_assoc,_dissoc,atom}; +use types::MalVal::{Nil,Bool,Int,Str,Sym,List,Vector,Hash,Func,MalFunc,Atom}; +use types::MalErr::{ErrMalVal}; +use reader::read_str; +use printer::pr_seq; + +macro_rules! fn_t_int_int { + ($ret:ident, $fn:expr) => {{ + |a:MalArgs| { + match (a[0].clone(), a[1].clone()) { + (Int(a0), Int(a1)) => Ok($ret($fn(a0, a1))), + _ => error("expecting (int,int) args"), + } + } + }}; +} + +macro_rules! fn_is_type { + ($($ps:pat),*) => {{ + |a:MalArgs| { Ok(Bool(match a[0] { $($ps => true,)* _ => false})) } + }}; + ($p:pat if $e:expr) => {{ + |a:MalArgs| { Ok(Bool(match a[0] { $p if $e => true, _ => false})) } + }}; + ($p:pat if $e:expr,$($ps:pat),*) => {{ + |a:MalArgs| { Ok(Bool(match a[0] { $p if $e => true, $($ps => true,)* _ => false})) } + }}; +} + +macro_rules! fn_str { + ($fn:expr) => {{ + |a:MalArgs| { + match a[0].clone() { + Str(a0) => $fn(a0), + _ => error("expecting (str) arg"), + } + } + }}; +} + +fn symbol(a: MalArgs) -> MalRet { + match a[0] { + Str(ref s) => Ok(Sym(s.to_string())), + _ => error("illegal symbol call") + } +} + +fn readline(a: MalArgs) -> MalRet { + lazy_static! { + static ref RL: Mutex> = Mutex::new(Editor::<()>::new()); + } + //let mut rl = Editor::<()>::new(); + + match a[0] { + Str(ref p) => { + //match rl.readline(p) { + match RL.lock().unwrap().readline(p) { + Ok(line) => Ok(Str(line)), + Err(ReadlineError::Eof) => Ok(Nil), + Err(e) => error(&format!("{:?}", e)) + } + }, + _ => error("readline: prompt is not Str"), + } +} + +fn slurp(f: String) -> MalRet { + let mut s = String::new(); + match File::open(f).and_then(|mut f| f.read_to_string(&mut s)) { + Ok(_) => Ok(Str(s)), + Err(e) => error(&e.to_string()), + } +} + +fn time_ms(_a: MalArgs) -> MalRet { + let ms_e = match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(d) => d, + Err(e) => return error(&format!("{:?}", e)), + }; + Ok(Int(ms_e.as_secs() as i64 * 1000 + + ms_e.subsec_nanos() as i64 / 1_000_000)) +} + +fn get(a: MalArgs) -> MalRet { + match (a[0].clone(), a[1].clone()) { + (Nil, _) => Ok(Nil), + (Hash(ref hm,_), Str(ref s)) => { + match hm.get(s) { + Some(mv) => Ok(mv.clone()), + None => Ok(Nil), + } + }, + _ => error("illegal get args") + } +} + +fn assoc(a: MalArgs) -> MalRet { + match a[0] { + Hash(ref hm,_) => _assoc((**hm).clone(), a[1..].to_vec()), + _ => error("assoc on non-Hash Map") + } +} + +fn dissoc(a: MalArgs) -> MalRet { + match a[0] { + Hash(ref hm,_) => _dissoc((**hm).clone(), a[1..].to_vec()), + _ => error("dissoc on non-Hash Map") + } +} + +fn contains_q(a: MalArgs) -> MalRet { + match (a[0].clone(), a[1].clone()) { + (Hash(ref hm,_), Str(ref s)) => { + Ok(Bool(hm.contains_key(s))) + }, + _ => error("illegal get args") + } +} + +fn keys(a: MalArgs) -> MalRet { + match a[0] { + Hash(ref hm,_) => { + Ok(list!(hm.keys().map(|k|{Str(k.to_string())}).collect())) + }, + _ => error("keys requires Hash Map") + } +} + +fn vals(a: MalArgs) -> MalRet { + match a[0] { + Hash(ref hm,_) => { + Ok(list!(hm.values().map(|v|{v.clone()}).collect())) + }, + _ => error("keys requires Hash Map") + } +} + +fn cons(a: MalArgs) -> MalRet { + match a[1].clone() { + List(v,_) | Vector(v,_) => { + let mut new_v = vec![a[0].clone()]; + new_v.extend_from_slice(&v); + Ok(list!(new_v.to_vec())) + }, + _ => error("cons expects seq as second arg"), + } +} + +fn concat(a: MalArgs) -> MalRet { + let mut new_v = vec![]; + for seq in a.iter() { + match seq { + List(v,_) | Vector(v,_) => new_v.extend_from_slice(v), + _ => return error("non-seq passed to concat"), + } + } + Ok(list!(new_v.to_vec())) +} + +fn nth(a: MalArgs) -> MalRet { + match (a[0].clone(), a[1].clone()) { + (List(seq,_), Int(idx)) | (Vector(seq,_), Int(idx)) => { + if seq.len() <= idx as usize { + return error("nth: index out of range"); + } + Ok(seq[idx as usize].clone()) + } + _ => error("invalid args to nth"), + } +} + +fn first(a: MalArgs) -> MalRet { + match a[0].clone() { + List(ref seq,_) | Vector(ref seq,_) if seq.len() == 0 => Ok(Nil), + List(ref seq,_) | Vector(ref seq,_) => Ok(seq[0].clone()), + Nil => Ok(Nil), + _ => error("invalid args to first"), + } +} + +fn rest(a: MalArgs) -> MalRet { + match a[0].clone() { + List(ref seq,_) | Vector(ref seq,_) => { + if seq.len() > 1 { + Ok(list!(seq[1..].to_vec())) + } else { + Ok(list![]) + } + }, + Nil => Ok(list![]), + _ => error("invalid args to first"), + } +} + +fn apply(a: MalArgs) -> MalRet { + match a[a.len()-1] { + List(ref v,_) | Vector(ref v,_) => { + let f = &a[0]; + let mut fargs = a[1..a.len()-1].to_vec(); + fargs.extend_from_slice(&v); + f.apply(fargs) + }, + _ => error("apply called with non-seq"), + } +} + +fn map(a: MalArgs) -> MalRet { + match a[1] { + List(ref v,_) | Vector(ref v,_) => { + let mut res = vec![]; + for mv in v.iter() { + res.push(a[0].apply(vec![mv.clone()])?) + } + Ok(list!(res)) + }, + _ => error("map called with non-seq"), + } +} + +fn conj(a: MalArgs) -> MalRet { + match a[0] { + List(ref v,_) => { + let sl = a[1..].iter().rev().map(|a|{a.clone()}).collect::>(); + Ok(list!([&sl[..],v].concat())) + }, + Vector(ref v,_) => Ok(vector!([v,&a[1..]].concat())), + _ => error("conj: called with non-seq"), + } +} + +fn seq(a: MalArgs) -> MalRet { + match a[0] { + List(ref v,_) | Vector(ref v,_) if v.len() == 0 => Ok(Nil), + List(ref v,_) | Vector(ref v,_) => Ok(list!(v.to_vec())), + Str(ref s) if s.len() == 0 => Ok(Nil), + Str(ref s) if !a[0].keyword_q() => { + Ok(list!(s.chars().map(|c|{Str(c.to_string())}).collect())) + }, + Nil => Ok(Nil), + _ => error("seq: called with non-seq"), + } +} + +pub fn ns() -> Vec<(&'static str, MalVal)> { + vec![ + ("=", func(|a|{Ok(Bool(a[0] == a[1]))})), + ("throw", func(|a|{Err(ErrMalVal(a[0].clone()))})), + + ("nil?", func(fn_is_type!(Nil))), + ("true?", func(fn_is_type!(Bool(true)))), + ("false?", func(fn_is_type!(Bool(false)))), + ("symbol", func(symbol)), + ("symbol?", func(fn_is_type!(Sym(_)))), + ("string?", func(fn_is_type!(Str(ref s) if !s.starts_with("\u{29e}")))), + ("keyword", func(|a|{a[0].keyword()})), + ("keyword?", func(fn_is_type!(Str(ref s) if s.starts_with("\u{29e}")))), + ("number?", func(fn_is_type!(Int(_)))), + ("fn?", func(fn_is_type!(MalFunc{is_macro,..} if !is_macro,Func(_,_)))), + ("macro?", func(fn_is_type!(MalFunc{is_macro,..} if is_macro))), + + ("pr-str", func(|a|Ok(Str(pr_seq(&a, true, "", "", " "))))), + ("str", func(|a|Ok(Str(pr_seq(&a, false, "", "", ""))))), + ("prn", func(|a|{println!("{}", pr_seq(&a, true, "", "", " ")); Ok(Nil)})), + ("println", func(|a|{println!("{}", pr_seq(&a, false, "", "", " ")); Ok(Nil)})), + ("read-string", func(fn_str!(|s|{read_str(s)}))), + ("readline", func(readline)), + ("slurp", func(fn_str!(|f|{slurp(f)}))), + + ("<", func(fn_t_int_int!(Bool,|i,j|{i", func(fn_t_int_int!(Bool,|i,j|{i>j}))), + (">=", func(fn_t_int_int!(Bool,|i,j|{i>=j}))), + ("+", func(fn_t_int_int!(Int,|i,j|{i+j}))), + ("-", func(fn_t_int_int!(Int,|i,j|{i-j}))), + ("*", func(fn_t_int_int!(Int,|i,j|{i*j}))), + ("/", func(fn_t_int_int!(Int,|i,j|{i/j}))), + ("time-ms", func(time_ms)), + + ("sequential?", func(fn_is_type!(List(_,_),Vector(_,_)))), + ("list", func(|a|{Ok(list!(a))})), + ("list?", func(fn_is_type!(List(_,_)))), + ("vector", func(|a|{Ok(vector!(a))})), + ("vector?", func(fn_is_type!(Vector(_,_)))), + ("hash-map", func(|a|{hash_map(a)})), + ("map?", func(fn_is_type!(Hash(_,_)))), + ("assoc", func(assoc)), + ("dissoc", func(dissoc)), + ("get", func(get)), + ("contains?", func(contains_q)), + ("keys", func(keys)), + ("vals", func(vals)), + + ("cons", func(cons)), + ("concat", func(concat)), + ("empty?", func(|a|{a[0].empty_q()})), + ("nth", func(nth)), + ("first", func(first)), + ("rest", func(rest)), + ("count", func(|a|{a[0].count()})), + ("apply", func(apply)), + ("map", func(map)), + + ("conj", func(conj)), + ("seq", func(seq)), + + ("meta", func(|a|{a[0].get_meta()})), + ("with-meta", func(|a|{a[0].clone().with_meta(&a[1])})), + ("atom", func(|a|{Ok(atom(&a[0]))})), + ("atom?", func(fn_is_type!(Atom(_)))), + ("deref", func(|a|{a[0].deref()})), + ("reset!", func(|a|{a[0].reset_bang(&a[1])})), + ("swap!", func(|a|{a[0].swap_bang(&a[1..].to_vec())})), + ] +} + +// vim: ts=2:sw=2:expandtab + diff --git a/rust/env.rs b/rust/env.rs new file mode 100644 index 0000000000..130bca250b --- /dev/null +++ b/rust/env.rs @@ -0,0 +1,84 @@ +use std::rc::Rc; +use std::cell::RefCell; +//use std::collections::HashMap; +use fnv::FnvHashMap; + +use types::{MalVal,MalRet,MalErr,error}; +use types::MalVal::{Nil,Sym,List,Vector}; +use types::MalErr::{ErrString}; + +#[derive(Debug)] +pub struct EnvStruct { + data: RefCell>, + pub outer: Option, +} + +pub type Env = Rc; + +// TODO: it would be nice to use impl here but it doesn't work on +// a deftype (i.e. Env) + +pub fn env_new(outer: Option) -> Env { + Rc::new(EnvStruct{data: RefCell::new(FnvHashMap::default()), outer: outer}) +} + +// TODO: mbinds and exprs as & types +pub fn env_bind(outer: Option, mbinds: MalVal, + exprs: Vec) -> Result { + let env = env_new(outer); + match mbinds { + List(binds,_) | Vector(binds,_) => { + for (i, b) in binds.iter().enumerate() { + match b { + Sym(s) if s == "&" => { + env_set(&env, binds[i+1].clone(), list!(exprs[i..].to_vec()))?; + break; + }, + _ => { + env_set(&env, b.clone(), exprs[i].clone())?; + }, + } + } + Ok(env) + }, + _ => Err(ErrString("env_bind binds not List/Vector".to_string())), + } +} + +pub fn env_find(env: &Env, key: &str) -> Option { + match (env.data.borrow().contains_key(key), env.outer.clone()) { + (true, _) => Some(env.clone()), + (false, Some(o)) => env_find(&o, key), + _ => None, + } +} + +pub fn env_get(env: &Env, key: &MalVal) -> MalRet { + match key { + Sym(ref s) => { + match env_find(env, s) { + Some(e) => Ok(e.data.borrow().get(s) + .ok_or(ErrString(format!("'{}' not found", s)))? + .clone()), + _ => error(&format!("'{}' not found", s)), + } + }, + _ => error("Env.get called with non-Str"), + } +} + +pub fn env_set(env: &Env, key: MalVal, val: MalVal) -> MalRet { + match key { + Sym(ref s) => { + env.data.borrow_mut().insert(s.to_string(), val.clone()); + Ok(val) + }, + _ => error("Env.set called with non-Str") + } +} + +pub fn env_sets(env: &Env, key: &str, val: MalVal) { + env.data.borrow_mut().insert(key.to_string(), val); +} + +// vim: ts=2:sw=2:expandtab diff --git a/rust/printer.rs b/rust/printer.rs new file mode 100644 index 0000000000..01a7ff6b0f --- /dev/null +++ b/rust/printer.rs @@ -0,0 +1,60 @@ +use types::MalVal; +use types::MalVal::{Nil,Bool,Int,Str,Sym,List,Vector,Hash,Func,MalFunc,Atom}; + +fn escape_str(s: &str) -> String { + s.chars().map(|c| { + match c { + '"' => "\\\"".to_string(), + '\n' => "\\n".to_string(), + '\\' => "\\\\".to_string(), + _ => c.to_string(), + } + }).collect::>().join("") +} + +impl MalVal { + pub fn pr_str(&self, print_readably: bool) -> String { + match self { + Nil => String::from("nil"), + Bool(true) => String::from("true"), + Bool(false) => String::from("false"), + Int(i) => format!("{}", i), + //Float(f) => format!("{}", f), + Str(s) => { + if s.starts_with("\u{29e}") { + format!(":{}", &s[2..]) + } else if print_readably { + format!("\"{}\"", escape_str(s)) + } else { + s.clone() + } + } + Sym(s) => s.clone(), + List(l,_) => pr_seq(&**l, print_readably, "(", ")", " "), + Vector(l,_) => pr_seq(&**l, print_readably, "[", "]", " "), + Hash(hm,_) => { + let l: Vec = hm + .iter() + .flat_map(|(k, v)| { vec![Str(k.to_string()), v.clone()] }) + .collect(); + pr_seq(&l, print_readably, "{", "}", " ") + }, + Func(f,_) => format!("#", f), + MalFunc{ast: a, params: p, ..} => { + format!("(fn* {} {})", p.pr_str(true), a.pr_str(true)) + }, + Atom(a) => format!("(atom {})", a.borrow().pr_str(true)), + } + } +} + +pub fn pr_seq(seq: &Vec, print_readably: bool, + start: &str, end: &str, join: &str) -> String { + let strs: Vec = seq + .iter() + .map(|x| x.pr_str(print_readably)) + .collect(); + format!("{}{}{}", start, strs.join(join), end) +} + +// vim: ts=2:sw=2:expandtab diff --git a/rust/reader.rs b/rust/reader.rs new file mode 100644 index 0000000000..cae8223cfa --- /dev/null +++ b/rust/reader.rs @@ -0,0 +1,142 @@ +use std::rc::Rc; +use regex::{Regex,Captures}; + +use types::{MalVal,MalRet,MalErr,error,hash_map}; +use types::MalVal::{Nil,Bool,Int,Str,Sym,List,Vector}; +use types::MalErr::ErrString; + +#[derive(Debug, Clone)] +struct Reader { + tokens: Vec, + pos: usize, +} + +impl Reader { + fn next(&mut self) -> Result { + self.pos = self.pos + 1; + Ok(self.tokens.get(self.pos-1) + .ok_or(ErrString("underflow".to_string()))?.to_string()) + } + fn peek(&self) -> Result { + Ok(self.tokens.get(self.pos) + .ok_or(ErrString("underflow".to_string()))?.to_string()) + } +} + +fn tokenize(str: &str) -> Vec { + lazy_static! { + static ref RE: Regex = Regex::new(r###"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]+)"###).unwrap(); + } + + let mut res = vec![]; + for cap in RE.captures_iter(str) { + if cap[1].starts_with(";") { continue } + res.push(String::from(&cap[1])); + } + res +} + +fn unescape_str(s: &str) -> String { + lazy_static! { + static ref RE: Regex = Regex::new(r#"\\(.)"#).unwrap(); + } + RE.replace_all(&s, |caps: &Captures| { + format!("{}", if &caps[1] == "n" { "\n" } else { &caps[1] }) + }).to_string() +} + +fn read_atom(rdr: &mut Reader) -> MalRet { + lazy_static! { + static ref INT_RE: Regex = Regex::new(r"^-?[0-9]+$").unwrap(); + } + let token = rdr.next()?; + match &token[..] { + "nil" => Ok(Nil), + "false" => Ok(Bool(false)), + "true" => Ok(Bool(true)), + _ => { + if INT_RE.is_match(&token) { + Ok(Int(token.parse().unwrap())) + } else if token.starts_with("\"") { + if token.ends_with("\"") { + Ok(Str(unescape_str(&token[1..token.len()-1]))) + } else { + error("expected '\"', got EOF") + } + } else if token.starts_with(":") { + Ok(Str(format!("\u{29e}{}", &token[1..]))) + } else { + Ok(Sym(token.to_string())) + } + } + } +} + +fn read_seq(rdr: &mut Reader, end: &str) -> MalRet { + let mut seq : Vec = vec![]; + rdr.next()?; + loop { + let token = match rdr.peek() { + Ok(t) => t, + Err(_) => return error(&format!("expected '{}', got EOF", end)) + }; + if token == end { break } + seq.push(read_form(rdr)?) + } + let _ = rdr.next(); + match end { + ")" => Ok(list!(seq)), + "]" => Ok(vector!(seq)), + "}" => hash_map(seq), + _ => error("read_seq unknown end value"), + } +} + +fn read_form(rdr: &mut Reader) -> MalRet { + let token = rdr.peek()?; + match &token[..] { + "'" => { + let _ = rdr.next(); + Ok(list![Sym("quote".to_string()), read_form(rdr)?]) + }, + "`" => { + let _ = rdr.next(); + Ok(list![Sym("quasiquote".to_string()), read_form(rdr)?]) + }, + "~" => { + let _ = rdr.next(); + Ok(list![Sym("unquote".to_string()), read_form(rdr)?]) + }, + "~@" => { + let _ = rdr.next(); + Ok(list![Sym("splice-unquote".to_string()), read_form(rdr)?]) + }, + "^" => { + let _ = rdr.next(); + let meta = read_form(rdr)?; + Ok(list![Sym("with-meta".to_string()), read_form(rdr)?, meta]) + }, + "@" => { + let _ = rdr.next(); + Ok(list![Sym("deref".to_string()), read_form(rdr)?]) + }, + ")" => error("unexpected ')'"), + "(" => read_seq(rdr, ")"), + "]" => error("unexpected ']'"), + "[" => read_seq(rdr, "]"), + "}" => error("unexpected '}'"), + "{" => read_seq(rdr, "}"), + _ => read_atom(rdr), + } +} + +pub fn read_str(str: String) -> MalRet { + let tokens = tokenize(&str); + //println!("tokens: {:?}", tokens); + if tokens.len() == 0 { + return error("no input"); + } + read_form(&mut Reader { pos: 0, tokens: tokens }) +} + +// vim: ts=2:sw=2:expandtab diff --git a/rust/readline.rs b/rust/readline.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/rust/run b/rust/run index 06764851ce..8ba68a5484 100755 --- a/rust/run +++ b/rust/run @@ -1,2 +1,2 @@ #!/bin/bash -exec $(dirname $0)/target/release/${STEP:-stepA_mal} "${@}" +exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/rust/src/bin/step0_repl.rs b/rust/src/bin/step0_repl.rs deleted file mode 100644 index ba5f5fe08b..0000000000 --- a/rust/src/bin/step0_repl.rs +++ /dev/null @@ -1,26 +0,0 @@ -extern crate mal; - -use mal::readline; - -// read -fn read(str: String) -> String { - str -} - -// eval -fn eval(ast: String) -> String { - ast -} - -// print -fn print(exp: String) -> String { - exp -} - -fn main() { - loop { - let line = readline::mal_readline("user> "); - match line { None => break, _ => () } - println!("{}", print(eval(read(line.unwrap())))); - } -} diff --git a/rust/src/bin/step1_read_print.rs b/rust/src/bin/step1_read_print.rs deleted file mode 100644 index 02b2da7bf8..0000000000 --- a/rust/src/bin/step1_read_print.rs +++ /dev/null @@ -1,39 +0,0 @@ -extern crate mal; - -use mal::types::{MalVal, MalRet, MalError}; -use mal::types::MalError::{ErrString, ErrMalVal}; -use mal::{readline, reader}; - -// read -fn read(str: String) -> MalRet { - reader::read_str(str) -} - -// eval -fn eval(ast: MalVal) -> MalRet { - Ok(ast) -} - -// print -fn print(exp: MalVal) -> String { - exp.pr_str(true) -} - -fn rep(str: &str) -> Result { - let ast = try!(read(str.to_string())); - //println!("read: {}", ast); - let exp = try!(eval(ast)); - Ok(print(exp)) -} - -fn main() { - loop { - let line = readline::mal_readline("user> "); - match line { None => break, _ => () } - match rep(&line.unwrap()) { - Ok(str) => println!("{}", str), - Err(ErrMalVal(_)) => (), // Blank line - Err(ErrString(s)) => println!("Error: {}", s), - } - } -} diff --git a/rust/src/bin/step2_eval.rs b/rust/src/bin/step2_eval.rs deleted file mode 100644 index c807e3d73c..0000000000 --- a/rust/src/bin/step2_eval.rs +++ /dev/null @@ -1,114 +0,0 @@ -extern crate mal; - -use std::collections::HashMap; - -use mal::types::{MalVal, MalRet, MalError, err_str, err_string}; -use mal::types::{list, vector, hash_map, _int, func}; -use mal::types::MalError::{ErrString, ErrMalVal}; -use mal::types::MalType::{Sym, List, Vector, Hash_Map, Int}; -use mal::{readline, reader}; - -// read -fn read(str: String) -> MalRet { - reader::read_str(str) -} - -// eval -fn eval_ast(ast: MalVal, env: &HashMap) -> MalRet { - match *ast { - Sym(ref sym) => { - match env.get(sym) { - Some(mv) => Ok(mv.clone()), - //None => Ok(_nil()), - None => err_string(format!("'{}' not found", sym)), - } - }, - List(ref a,_) | Vector(ref a,_) => { - let mut ast_vec : Vec = vec![]; - for mv in a.iter() { - let mv2 = mv.clone(); - ast_vec.push(try!(eval(mv2, env))); - } - Ok(match *ast { List(_,_) => list(ast_vec), - _ => vector(ast_vec) }) - } - Hash_Map(ref hm,_) => { - let mut new_hm: HashMap = HashMap::new(); - for (key, value) in hm.iter() { - new_hm.insert(key.to_string(), - try!(eval(value.clone(), env))); - } - Ok(hash_map(new_hm)) - } - _ => Ok(ast.clone()), - } -} - -fn eval(ast: MalVal, env: &HashMap) -> MalRet { - //println!("eval: {}", ast); - match *ast { - List(_,_) => (), // continue - _ => return eval_ast(ast, env), - } - - // apply list - match *try!(eval_ast(ast, env)) { - List(ref args,_) => { - match args.len() { - 0 => - Ok(list(vec![])), - _ => { - let ref f = args.clone()[0]; - f.apply(args[1..].to_vec()) - } - } - }, - _ => return err_str("Expected list"), - } -} - -// print -fn print(exp: MalVal) -> String { - exp.pr_str(true) -} - -fn rep(str: &str, env: &HashMap) -> Result { - let ast = try!(read(str.to_string())); - //println!("read: {}", ast); - let exp = try!(eval(ast, env)); - Ok(print(exp)) -} - -fn int_op(f: F, a:Vec) -> MalRet - where F: FnOnce(isize, isize) -> isize -{ - match *a[0] { - Int(a0) => match *a[1] { - Int(a1) => Ok(_int(f(a0,a1))), - _ => err_str("second arg must be an int"), - }, - _ => err_str("first arg must be an int"), - } -} -fn add(a:Vec) -> MalRet { int_op(|i,j| { i+j }, a) } -fn sub(a:Vec) -> MalRet { int_op(|i,j| { i-j }, a) } -fn mul(a:Vec) -> MalRet { int_op(|i,j| { i*j }, a) } -fn div(a:Vec) -> MalRet { int_op(|i,j| { i/j }, a) } - -fn main() { - let mut repl_env : HashMap = HashMap::new(); - repl_env.insert("+".to_string(), func(add)); - repl_env.insert("-".to_string(), func(sub)); - repl_env.insert("*".to_string(), func(mul)); - repl_env.insert("/".to_string(), func(div)); - - loop { - let line = readline::mal_readline("user> "); - match line { None => break, _ => () } - match rep(&line.unwrap(), &repl_env) { - Ok(str) => println!("{}", str), - Err(ErrMalVal(_)) => (), // Blank line - Err(ErrString(s)) => println!("Error: {}", s), - } - } -} diff --git a/rust/src/bin/step3_env.rs b/rust/src/bin/step3_env.rs deleted file mode 100644 index afadc178d8..0000000000 --- a/rust/src/bin/step3_env.rs +++ /dev/null @@ -1,165 +0,0 @@ -extern crate mal; - -use std::collections::HashMap; - -use mal::types::{MalVal, MalRet, MalError, err_str}; -use mal::types::{symbol, _int, list, vector, hash_map, func}; -use mal::types::MalError::{ErrString, ErrMalVal}; -use mal::types::MalType::{Int, Sym, List, Vector, Hash_Map}; -use mal::{readline, reader}; -use mal::env::{Env, env_new, env_set, env_get}; - - -// read -fn read(str: String) -> MalRet { - reader::read_str(str) -} - -// eval -fn eval_ast(ast: MalVal, env: Env) -> MalRet { - match *ast { - Sym(_) => env_get(&env, &ast), - List(ref a,_) | Vector(ref a,_) => { - let mut ast_vec : Vec = vec![]; - for mv in a.iter() { - let mv2 = mv.clone(); - ast_vec.push(try!(eval(mv2, env.clone()))); - } - Ok(match *ast { List(_,_) => list(ast_vec), - _ => vector(ast_vec) }) - } - Hash_Map(ref hm,_) => { - let mut new_hm: HashMap = HashMap::new(); - for (key, value) in hm.iter() { - new_hm.insert(key.to_string(), - try!(eval(value.clone(), env.clone()))); - } - Ok(hash_map(new_hm)) - } - _ => Ok(ast.clone()), - } -} - -fn eval(ast: MalVal, env: Env) -> MalRet { - //println!("eval: {}, {}", ast, env.borrow()); - //println!("eval: {}", ast); - match *ast { - List(_,_) => (), // continue - _ => return eval_ast(ast, env), - } - - // apply list - match *ast { - List(_,_) => (), // continue - _ => return Ok(ast), - } - - let tmp = ast; - let (args, a0sym) = match *tmp { - List(ref args,_) => { - if args.len() == 0 { - return Ok(tmp.clone()); - } - let ref a0 = *args[0]; - match *a0 { - Sym(ref a0sym) => (args, &a0sym[..]), - _ => (args, "____"), - } - }, - _ => return err_str("Expected list"), - }; - - match a0sym { - "def!" => { - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - let r = try!(eval(a2, env.clone())); - match *a1 { - Sym(_) => { - env_set(&env.clone(), a1, r.clone()); - return Ok(r); - }, - _ => return err_str("def! of non-symbol"), - } - }, - "let*" => { - let let_env = env_new(Some(env.clone())); - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - match *a1 { - List(ref binds,_) | Vector(ref binds,_) => { - let mut it = binds.iter(); - while it.len() >= 2 { - let b = it.next().unwrap(); - let exp = it.next().unwrap(); - match **b { - Sym(_) => { - let r = try!(eval(exp.clone(), let_env.clone())); - env_set(&let_env, b.clone(), r); - }, - _ => return err_str("let* with non-symbol binding"), - } - } - }, - _ => return err_str("let* with non-list bindings"), - } - return eval(a2, let_env.clone()); - }, - _ => { // function call - let el = try!(eval_ast(tmp.clone(), env.clone())); - let args = match *el { - List(ref args,_) => args, - _ => return err_str("Invalid apply"), - }; - let ref f = args.clone()[0]; - f.apply(args[1..].to_vec()) - }, - } -} - -// print -fn print(exp: MalVal) -> String { - exp.pr_str(true) -} - -fn rep(str: &str, env: Env) -> Result { - let ast = try!(read(str.to_string())); - //println!("read: {}", ast); - let exp = try!(eval(ast, env)); - Ok(print(exp)) -} - -fn int_op(f: F, a:Vec) -> MalRet - where F: FnOnce(isize, isize) -> isize -{ - match *a[0] { - Int(a0) => match *a[1] { - Int(a1) => Ok(_int(f(a0,a1))), - _ => err_str("second arg must be an int"), - }, - _ => err_str("first arg must be an int"), - } -} -fn add(a:Vec) -> MalRet { int_op(|i,j| { i+j }, a) } -fn sub(a:Vec) -> MalRet { int_op(|i,j| { i-j }, a) } -fn mul(a:Vec) -> MalRet { int_op(|i,j| { i*j }, a) } -fn div(a:Vec) -> MalRet { int_op(|i,j| { i/j }, a) } - -fn main() { - let repl_env = env_new(None); - env_set(&repl_env, symbol("+"), func(add)); - env_set(&repl_env, symbol("-"), func(sub)); - env_set(&repl_env, symbol("*"), func(mul)); - env_set(&repl_env, symbol("/"), func(div)); - - // repl loop - loop { - let line = readline::mal_readline("user> "); - match line { None => break, _ => () } - match rep(&line.unwrap(), repl_env.clone()) { - Ok(str) => println!("{}", str), - Err(ErrMalVal(_)) => (), // Blank line - Err(ErrString(s)) => println!("Error: {}", s), - } - } -} diff --git a/rust/src/bin/step4_if_fn_do.rs b/rust/src/bin/step4_if_fn_do.rs deleted file mode 100644 index 2be0f6bd11..0000000000 --- a/rust/src/bin/step4_if_fn_do.rs +++ /dev/null @@ -1,185 +0,0 @@ -extern crate mal; - -use std::collections::HashMap; - -use mal::types::{MalVal, MalRet, MalError, err_str}; -use mal::types::{symbol, _nil, list, vector, hash_map, malfunc}; -use mal::types::MalError::{ErrString, ErrMalVal}; -use mal::types::MalType::{Nil, False, Sym, List, Vector, Hash_Map}; -use mal::{readline, reader, core}; -use mal::env::{env_set, env_get, env_new, Env}; - - -// read -fn read(str: String) -> MalRet { - reader::read_str(str) -} - -// eval -fn eval_ast(ast: MalVal, env: Env) -> MalRet { - match *ast { - Sym(_) => env_get(&env, &ast), - List(ref a,_) | Vector(ref a,_) => { - let mut ast_vec : Vec = vec![]; - for mv in a.iter() { - let mv2 = mv.clone(); - ast_vec.push(try!(eval(mv2, env.clone()))); - } - Ok(match *ast { List(_,_) => list(ast_vec), - _ => vector(ast_vec) }) - } - Hash_Map(ref hm,_) => { - let mut new_hm: HashMap = HashMap::new(); - for (key, value) in hm.iter() { - new_hm.insert(key.to_string(), - try!(eval(value.clone(), env.clone()))); - } - Ok(hash_map(new_hm)) - } - _ => Ok(ast.clone()), - } -} - -fn eval(ast: MalVal, env: Env) -> MalRet { - //println!("eval: {}, {}", ast, env.borrow()); - //println!("eval: {}", ast); - match *ast { - List(_,_) => (), // continue - _ => return eval_ast(ast, env), - } - - // apply list - match *ast { - List(_,_) => (), // continue - _ => return Ok(ast), - } - - let tmp = ast; - let (args, a0sym) = match *tmp { - List(ref args,_) => { - if args.len() == 0 { - return Ok(tmp.clone()); - } - let ref a0 = *args[0]; - match *a0 { - Sym(ref a0sym) => (args, &a0sym[..]), - _ => (args, "____"), - } - }, - _ => return err_str("Expected list"), - }; - - match a0sym { - "def!" => { - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - let r = try!(eval(a2, env.clone())); - match *a1 { - Sym(_) => { - env_set(&env.clone(), a1, r.clone()); - return Ok(r); - }, - _ => return err_str("def! of non-symbol"), - } - }, - "let*" => { - let let_env = env_new(Some(env.clone())); - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - match *a1 { - List(ref binds,_) | Vector(ref binds,_) => { - let mut it = binds.iter(); - while it.len() >= 2 { - let b = it.next().unwrap(); - let exp = it.next().unwrap(); - match **b { - Sym(_) => { - let r = try!(eval(exp.clone(), let_env.clone())); - env_set(&let_env, b.clone(), r); - }, - _ => return err_str("let* with non-symbol binding"), - } - } - }, - _ => return err_str("let* with non-list bindings"), - } - return eval(a2, let_env.clone()); - }, - "do" => { - let el = list(args[1..].to_vec()); - match *try!(eval_ast(el, env.clone())) { - List(ref lst,_) => { - let ref last = lst[lst.len()-1]; - return Ok(last.clone()); - } - _ => return err_str("invalid do call"), - } - }, - "if" => { - let a1 = (*args)[1].clone(); - let c = try!(eval(a1, env.clone())); - match *c { - False | Nil => { - if args.len() >= 4 { - let a3 = (*args)[3].clone(); - return eval(a3, env.clone()); - } else { - return Ok(_nil()); - } - }, - _ => { - let a2 = (*args)[2].clone(); - return eval(a2, env.clone()); - }, - } - }, - "fn*" => { - let a1 = args[1].clone(); - let a2 = args[2].clone(); - return Ok(malfunc(eval, a2, env, a1, _nil())); - }, - _ => { // function call - let el = try!(eval_ast(tmp.clone(), env.clone())); - let args = match *el { - List(ref args,_) => args, - _ => return err_str("Invalid apply"), - }; - let ref f = args.clone()[0]; - f.apply(args[1..].to_vec()) - }, - } -} - -// print -fn print(exp: MalVal) -> String { - exp.pr_str(true) -} - -fn rep(str: &str, env: Env) -> Result { - let ast = try!(read(str.to_string())); - //println!("read: {}", ast); - let exp = try!(eval(ast, env)); - Ok(print(exp)) -} - -fn main() { - // core.rs: defined using rust - let repl_env = env_new(None); - for (k, v) in core::ns().into_iter() { - env_set(&repl_env, symbol(&k), v); - } - - // core.mal: defined using the language itself - let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone()); - - // repl loop - loop { - let line = readline::mal_readline("user> "); - match line { None => break, _ => () } - match rep(&line.unwrap(), repl_env.clone()) { - Ok(str) => println!("{}", str), - Err(ErrMalVal(_)) => (), // Blank line - Err(ErrString(s)) => println!("Error: {}", s), - } - } -} diff --git a/rust/src/bin/step5_tco.rs b/rust/src/bin/step5_tco.rs deleted file mode 100644 index 9a2a4ced0e..0000000000 --- a/rust/src/bin/step5_tco.rs +++ /dev/null @@ -1,202 +0,0 @@ -extern crate mal; - -use std::collections::HashMap; - -use mal::types::{MalVal, MalRet, MalError, err_str}; -use mal::types::{symbol, _nil, list, vector, hash_map, malfunc}; -use mal::types::MalError::{ErrString, ErrMalVal}; -use mal::types::MalType::{Nil, False, Sym, List, Vector, Hash_Map, Func, MalFunc}; -use mal::{readline, reader, core}; -use mal::env::{env_set, env_get, env_new, env_bind, Env}; - - -// read -fn read(str: String) -> MalRet { - reader::read_str(str) -} - -// eval -fn eval_ast(ast: MalVal, env: Env) -> MalRet { - match *ast { - Sym(_) => env_get(&env, &ast), - List(ref a,_) | Vector(ref a,_) => { - let mut ast_vec : Vec = vec![]; - for mv in a.iter() { - let mv2 = mv.clone(); - ast_vec.push(try!(eval(mv2, env.clone()))); - } - Ok(match *ast { List(_,_) => list(ast_vec), - _ => vector(ast_vec) }) - } - Hash_Map(ref hm,_) => { - let mut new_hm: HashMap = HashMap::new(); - for (key, value) in hm.iter() { - new_hm.insert(key.to_string(), - try!(eval(value.clone(), env.clone()))); - } - Ok(hash_map(new_hm)) - } - _ => Ok(ast.clone()), - } -} - -fn eval(mut ast: MalVal, mut env: Env) -> MalRet { - 'tco: loop { - - //println!("eval: {}, {}", ast, env.borrow()); - //println!("eval: {}", ast); - match *ast { - List(_,_) => (), // continue - _ => return eval_ast(ast, env), - } - - // apply list - match *ast { - List(_,_) => (), // continue - _ => return Ok(ast), - } - - let tmp = ast; - let (args, a0sym) = match *tmp { - List(ref args,_) => { - if args.len() == 0 { - return Ok(tmp.clone()); - } - let ref a0 = *args[0]; - match *a0 { - Sym(ref a0sym) => (args, &a0sym[..]), - _ => (args, "____"), - } - }, - _ => return err_str("Expected list"), - }; - - match a0sym { - "def!" => { - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - let r = try!(eval(a2, env.clone())); - match *a1 { - Sym(_) => { - env_set(&env.clone(), a1, r.clone()); - return Ok(r); - }, - _ => return err_str("def! of non-symbol"), - } - }, - "let*" => { - let let_env = env_new(Some(env.clone())); - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - match *a1 { - List(ref binds,_) | Vector(ref binds,_) => { - let mut it = binds.iter(); - while it.len() >= 2 { - let b = it.next().unwrap(); - let exp = it.next().unwrap(); - match **b { - Sym(_) => { - let r = try!(eval(exp.clone(), let_env.clone())); - env_set(&let_env, b.clone(), r); - }, - _ => return err_str("let* with non-symbol binding"), - } - } - }, - _ => return err_str("let* with non-list bindings"), - } - ast = a2; - env = let_env.clone(); - continue 'tco; - }, - "do" => { - let el = list(args[1..args.len()-1].to_vec()); - try!(eval_ast(el, env.clone())); - ast = args[args.len() - 1].clone(); - continue 'tco; - }, - "if" => { - let a1 = (*args)[1].clone(); - let c = try!(eval(a1, env.clone())); - match *c { - False | Nil => { - if args.len() >= 4 { - ast = args[3].clone(); - continue 'tco; - } else { - return Ok(_nil()); - } - }, - _ => { - ast = args[2].clone(); - continue 'tco; - }, - } - }, - "fn*" => { - let a1 = args[1].clone(); - let a2 = args[2].clone(); - return Ok(malfunc(eval, a2, env, a1, _nil())); - }, - _ => { // function call - let el = try!(eval_ast(tmp.clone(), env.clone())); - let args = match *el { - List(ref args,_) => args, - _ => return err_str("Invalid apply"), - }; - return match *args.clone()[0] { - Func(f,_) => f(args[1..].to_vec()), - MalFunc(ref mf,_) => { - let mfc = mf.clone(); - let alst = list(args[1..].to_vec()); - let new_env = env_new(Some(mfc.env.clone())); - match env_bind(&new_env, mfc.params, alst) { - Ok(_) => { - ast = mfc.exp; - env = new_env; - continue 'tco; - }, - Err(e) => err_str(&e), - } - }, - _ => err_str("attempt to call non-function"), - } - }, - } - - } -} - -// print -fn print(exp: MalVal) -> String { - exp.pr_str(true) -} - -fn rep(str: &str, env: Env) -> Result { - let ast = try!(read(str.to_string())); - //println!("read: {}", ast); - let exp = try!(eval(ast, env)); - Ok(print(exp)) -} - -fn main() { - // core.rs: defined using rust - let repl_env = env_new(None); - for (k, v) in core::ns().into_iter() { - env_set(&repl_env, symbol(&k), v); - } - - // core.mal: defined using the language itself - let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone()); - - // repl loop - loop { - let line = readline::mal_readline("user> "); - match line { None => break, _ => () } - match rep(&line.unwrap(), repl_env.clone()) { - Ok(str) => println!("{}", str), - Err(ErrMalVal(_)) => (), // Blank line - Err(ErrString(s)) => println!("Error: {}", s), - } - } -} diff --git a/rust/src/bin/step6_file.rs b/rust/src/bin/step6_file.rs deleted file mode 100644 index c2f0d57754..0000000000 --- a/rust/src/bin/step6_file.rs +++ /dev/null @@ -1,232 +0,0 @@ - -extern crate mal; - -use std::collections::HashMap; -use std::env as stdenv; -use std::process as process; - -use mal::types::{MalVal, MalRet, MalError, err_str}; -use mal::types::{symbol, _nil, string, list, vector, hash_map, malfunc}; -use mal::types::MalError::{ErrString, ErrMalVal}; -use mal::types::MalType::{Nil, False, Sym, List, Vector, Hash_Map, Func, MalFunc}; -use mal::{readline, reader, core}; -use mal::env::{env_set, env_get, env_new, env_bind, env_root, Env}; - - -// read -fn read(str: String) -> MalRet { - reader::read_str(str) -} - -// eval -fn eval_ast(ast: MalVal, env: Env) -> MalRet { - match *ast { - Sym(_) => env_get(&env, &ast), - List(ref a,_) | Vector(ref a,_) => { - let mut ast_vec : Vec = vec![]; - for mv in a.iter() { - let mv2 = mv.clone(); - ast_vec.push(try!(eval(mv2, env.clone()))); - } - Ok(match *ast { List(_,_) => list(ast_vec), - _ => vector(ast_vec) }) - } - Hash_Map(ref hm,_) => { - let mut new_hm: HashMap = HashMap::new(); - for (key, value) in hm.iter() { - new_hm.insert(key.to_string(), - try!(eval(value.clone(), env.clone()))); - } - Ok(hash_map(new_hm)) - } - _ => Ok(ast.clone()), - } -} - -fn eval(mut ast: MalVal, mut env: Env) -> MalRet { - 'tco: loop { - - //println!("eval: {}, {}", ast, env.borrow()); - //println!("eval: {}", ast); - match *ast { - List(_,_) => (), // continue - _ => return eval_ast(ast, env), - } - - // apply list - match *ast { - List(_,_) => (), // continue - _ => return Ok(ast), - } - - let tmp = ast; - let (args, a0sym) = match *tmp { - List(ref args,_) => { - if args.len() == 0 { - return Ok(tmp.clone()); - } - let ref a0 = *args[0]; - match *a0 { - Sym(ref a0sym) => (args, &a0sym[..]), - _ => (args, "____"), - } - }, - _ => return err_str("Expected list"), - }; - - match a0sym { - "def!" => { - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - let r = try!(eval(a2, env.clone())); - match *a1 { - Sym(_) => { - env_set(&env.clone(), a1, r.clone()); - return Ok(r); - }, - _ => return err_str("def! of non-symbol"), - } - }, - "let*" => { - let let_env = env_new(Some(env.clone())); - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - match *a1 { - List(ref binds,_) | Vector(ref binds,_) => { - let mut it = binds.iter(); - while it.len() >= 2 { - let b = it.next().unwrap(); - let exp = it.next().unwrap(); - match **b { - Sym(_) => { - let r = try!(eval(exp.clone(), let_env.clone())); - env_set(&let_env, b.clone(), r); - }, - _ => return err_str("let* with non-symbol binding"), - } - } - }, - _ => return err_str("let* with non-list bindings"), - } - ast = a2; - env = let_env.clone(); - continue 'tco; - }, - "do" => { - let el = list(args[1..args.len()-1].to_vec()); - try!(eval_ast(el, env.clone())); - ast = args[args.len() - 1].clone(); - continue 'tco; - }, - "if" => { - let a1 = (*args)[1].clone(); - let c = try!(eval(a1, env.clone())); - match *c { - False | Nil => { - if args.len() >= 4 { - ast = args[3].clone(); - continue 'tco; - } else { - return Ok(_nil()); - } - }, - _ => { - ast = args[2].clone(); - continue 'tco; - }, - } - }, - "fn*" => { - let a1 = args[1].clone(); - let a2 = args[2].clone(); - return Ok(malfunc(eval, a2, env, a1, _nil())); - }, - "eval" => { - let a1 = (*args)[1].clone(); - ast = try!(eval(a1, env.clone())); - env = env_root(&env); - continue 'tco; - }, - _ => { // function call - let el = try!(eval_ast(tmp.clone(), env.clone())); - let args = match *el { - List(ref args,_) => args, - _ => return err_str("Invalid apply"), - }; - return match *args.clone()[0] { - Func(f,_) => f(args[1..].to_vec()), - MalFunc(ref mf,_) => { - let mfc = mf.clone(); - let alst = list(args[1..].to_vec()); - let new_env = env_new(Some(mfc.env.clone())); - match env_bind(&new_env, mfc.params, alst) { - Ok(_) => { - ast = mfc.exp; - env = new_env; - continue 'tco; - }, - Err(e) => err_str(&e), - } - }, - _ => err_str("attempt to call non-function"), - } - }, - } - - } -} - -// print -fn print(exp: MalVal) -> String { - exp.pr_str(true) -} - -fn rep(str: &str, env: Env) -> Result { - let ast = try!(read(str.to_string())); - //println!("read: {}", ast); - let exp = try!(eval(ast, env)); - Ok(print(exp)) -} - -fn main() { - // core.rs: defined using rust - let repl_env = env_new(None); - for (k, v) in core::ns().into_iter() { - env_set(&repl_env, symbol(&k), v); - } - // see eval() for definition of "eval" - env_set(&repl_env, symbol("*ARGV*"), list(vec![])); - - // core.mal: defined using the language itself - let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone()); - let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env.clone()); - - // Invoked with command line arguments - let args = stdenv::args(); - if args.len() > 1 { - let mv_args = args.skip(2) - .map(|a| string(a)) - .collect::>(); - env_set(&repl_env, symbol("*ARGV*"), list(mv_args)); - let lf = format!("(load-file \"{}\")", - stdenv::args().skip(1).next().unwrap()); - return match rep(&lf, repl_env.clone()) { - Ok(_) => process::exit(0), - Err(str) => { - println!("Error: {:?}", str); - process::exit(1); - } - }; - } - - // repl loop - loop { - let line = readline::mal_readline("user> "); - match line { None => break, _ => () } - match rep(&line.unwrap(), repl_env.clone()) { - Ok(str) => println!("{}", str), - Err(ErrMalVal(_)) => (), // Blank line - Err(ErrString(s)) => println!("Error: {}", s), - } - } -} diff --git a/rust/src/bin/step7_quote.rs b/rust/src/bin/step7_quote.rs deleted file mode 100644 index 7b0788db2d..0000000000 --- a/rust/src/bin/step7_quote.rs +++ /dev/null @@ -1,281 +0,0 @@ - -extern crate mal; - -use std::collections::HashMap; -use std::env as stdenv; -use std::process as process; - -use mal::types::{MalVal, MalRet, MalError, err_str}; -use mal::types::{symbol, _nil, string, list, vector, hash_map, malfunc}; -use mal::types::MalError::{ErrString, ErrMalVal}; -use mal::types::MalType::{Nil, False, Sym, List, Vector, Hash_Map, Func, MalFunc}; -use mal::{readline, reader, core}; -use mal::env::{env_set, env_get, env_new, env_bind, env_root, Env}; - - -// read -fn read(str: String) -> MalRet { - reader::read_str(str) -} - -// eval -fn is_pair(x: MalVal) -> bool { - match *x { - List(ref lst,_) | Vector(ref lst,_) => lst.len() > 0, - _ => false, - } -} - -fn quasiquote(ast: MalVal) -> MalVal { - if !is_pair(ast.clone()) { - return list(vec![symbol("quote"), ast]) - } - - match *ast.clone() { - List(ref args,_) | Vector(ref args,_) => { - let ref a0 = args[0]; - match **a0 { - Sym(ref s) if *s == "unquote" => return args[1].clone(), - _ => (), - } - if is_pair(a0.clone()) { - match **a0 { - List(ref a0args,_) | Vector(ref a0args,_) => { - match *a0args[0] { - Sym(ref s) if *s == "splice-unquote" => { - return list(vec![symbol("concat"), - a0args[1].clone(), - quasiquote(list(args[1..].to_vec()))]) - }, - _ => (), - } - }, - _ => (), - } - } - let rest = list(args[1..].to_vec()); - return list(vec![symbol("cons"), - quasiquote(a0.clone()), - quasiquote(rest)]) - }, - _ => _nil(), // should never reach - } -} - -fn eval_ast(ast: MalVal, env: Env) -> MalRet { - match *ast { - Sym(_) => env_get(&env, &ast), - List(ref a,_) | Vector(ref a,_) => { - let mut ast_vec : Vec = vec![]; - for mv in a.iter() { - let mv2 = mv.clone(); - ast_vec.push(try!(eval(mv2, env.clone()))); - } - Ok(match *ast { List(_,_) => list(ast_vec), - _ => vector(ast_vec) }) - } - Hash_Map(ref hm,_) => { - let mut new_hm: HashMap = HashMap::new(); - for (key, value) in hm.iter() { - new_hm.insert(key.to_string(), - try!(eval(value.clone(), env.clone()))); - } - Ok(hash_map(new_hm)) - } - _ => Ok(ast.clone()), - } -} - -fn eval(mut ast: MalVal, mut env: Env) -> MalRet { - 'tco: loop { - - //println!("eval: {}, {}", ast, env.borrow()); - //println!("eval: {}", ast); - match *ast { - List(_,_) => (), // continue - _ => return eval_ast(ast, env), - } - - // apply list - match *ast { - List(_,_) => (), // continue - _ => return Ok(ast), - } - - let tmp = ast; - let (args, a0sym) = match *tmp { - List(ref args,_) => { - if args.len() == 0 { - return Ok(tmp.clone()); - } - let ref a0 = *args[0]; - match *a0 { - Sym(ref a0sym) => (args, &a0sym[..]), - _ => (args, "____"), - } - }, - _ => return err_str("Expected list"), - }; - - match a0sym { - "def!" => { - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - let r = try!(eval(a2, env.clone())); - match *a1 { - Sym(_) => { - env_set(&env.clone(), a1, r.clone()); - return Ok(r); - }, - _ => return err_str("def! of non-symbol"), - } - }, - "let*" => { - let let_env = env_new(Some(env.clone())); - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - match *a1 { - List(ref binds,_) | Vector(ref binds,_) => { - let mut it = binds.iter(); - while it.len() >= 2 { - let b = it.next().unwrap(); - let exp = it.next().unwrap(); - match **b { - Sym(_) => { - let r = try!(eval(exp.clone(), let_env.clone())); - env_set(&let_env, b.clone(), r); - }, - _ => return err_str("let* with non-symbol binding"), - } - } - }, - _ => return err_str("let* with non-list bindings"), - } - ast = a2; - env = let_env.clone(); - continue 'tco; - }, - "quote" => return Ok((*args)[1].clone()), - "quasiquote" => { - let a1 = (*args)[1].clone(); - ast = quasiquote(a1); - continue 'tco; - }, - "do" => { - let el = list(args[1..args.len()-1].to_vec()); - try!(eval_ast(el, env.clone())); - ast = args[args.len() - 1].clone(); - continue 'tco; - }, - "if" => { - let a1 = (*args)[1].clone(); - let c = try!(eval(a1, env.clone())); - match *c { - False | Nil => { - if args.len() >= 4 { - ast = args[3].clone(); - continue 'tco; - } else { - return Ok(_nil()); - } - }, - _ => { - ast = args[2].clone(); - continue 'tco; - }, - } - }, - "fn*" => { - let a1 = args[1].clone(); - let a2 = args[2].clone(); - return Ok(malfunc(eval, a2, env, a1, _nil())); - }, - "eval" => { - let a1 = (*args)[1].clone(); - ast = try!(eval(a1, env.clone())); - env = env_root(&env); - continue 'tco; - }, - _ => { // function call - let el = try!(eval_ast(tmp.clone(), env.clone())); - let args = match *el { - List(ref args,_) => args, - _ => return err_str("Invalid apply"), - }; - return match *args.clone()[0] { - Func(f,_) => f(args[1..].to_vec()), - MalFunc(ref mf,_) => { - let mfc = mf.clone(); - let alst = list(args[1..].to_vec()); - let new_env = env_new(Some(mfc.env.clone())); - match env_bind(&new_env, mfc.params, alst) { - Ok(_) => { - ast = mfc.exp; - env = new_env; - continue 'tco; - }, - Err(e) => err_str(&e), - } - }, - _ => err_str("attempt to call non-function"), - } - }, - } - - } -} - -// print -fn print(exp: MalVal) -> String { - exp.pr_str(true) -} - -fn rep(str: &str, env: Env) -> Result { - let ast = try!(read(str.to_string())); - //println!("read: {}", ast); - let exp = try!(eval(ast, env)); - Ok(print(exp)) -} - -fn main() { - // core.rs: defined using rust - let repl_env = env_new(None); - for (k, v) in core::ns().into_iter() { - env_set(&repl_env, symbol(&k), v); - } - // see eval() for definition of "eval" - env_set(&repl_env, symbol("*ARGV*"), list(vec![])); - - // core.mal: defined using the language itself - let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone()); - let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env.clone()); - - // Invoked with command line arguments - let args = stdenv::args(); - if args.len() > 1 { - let mv_args = args.skip(2) - .map(|a| string(a)) - .collect::>(); - env_set(&repl_env, symbol("*ARGV*"), list(mv_args)); - let lf = format!("(load-file \"{}\")", - stdenv::args().skip(1).next().unwrap()); - return match rep(&lf, repl_env.clone()) { - Ok(_) => process::exit(0), - Err(str) => { - println!("Error: {:?}", str); - process::exit(1); - } - }; - } - - // repl loop - loop { - let line = readline::mal_readline("user> "); - match line { None => break, _ => () } - match rep(&line.unwrap(), repl_env.clone()) { - Ok(str) => println!("{}", str), - Err(ErrMalVal(_)) => (), // Blank line - Err(ErrString(s)) => println!("Error: {}", s), - } - } -} diff --git a/rust/src/bin/step8_macros.rs b/rust/src/bin/step8_macros.rs deleted file mode 100644 index ea52d42b5b..0000000000 --- a/rust/src/bin/step8_macros.rs +++ /dev/null @@ -1,350 +0,0 @@ - -extern crate mal; - -use std::collections::HashMap; -use std::env as stdenv; -use std::process as process; - -use mal::types::{MalVal, MalRet, MalError, err_str}; -use mal::types::{symbol, _nil, string, list, vector, hash_map, malfunc, malfuncd}; -use mal::types::MalError::{ErrString, ErrMalVal}; -use mal::types::MalType::{Nil, False, Sym, List, Vector, Hash_Map, Func, MalFunc}; -use mal::{readline, reader, core}; -use mal::env::{env_set, env_get, env_new, env_bind, env_find, env_root, Env}; - - -// read -fn read(str: String) -> MalRet { - reader::read_str(str) -} - -// eval -fn is_pair(x: MalVal) -> bool { - match *x { - List(ref lst,_) | Vector(ref lst,_) => lst.len() > 0, - _ => false, - } -} - -fn quasiquote(ast: MalVal) -> MalVal { - if !is_pair(ast.clone()) { - return list(vec![symbol("quote"), ast]) - } - - match *ast.clone() { - List(ref args,_) | Vector(ref args,_) => { - let ref a0 = args[0]; - match **a0 { - Sym(ref s) if *s == "unquote" => return args[1].clone(), - _ => (), - } - if is_pair(a0.clone()) { - match **a0 { - List(ref a0args,_) | Vector(ref a0args,_) => { - match *a0args[0] { - Sym(ref s) if *s == "splice-unquote" => { - return list(vec![symbol("concat"), - a0args[1].clone(), - quasiquote(list(args[1..].to_vec()))]) - }, - _ => (), - } - }, - _ => (), - } - } - let rest = list(args[1..].to_vec()); - return list(vec![symbol("cons"), - quasiquote(a0.clone()), - quasiquote(rest)]) - }, - _ => _nil(), // should never reach - } -} - -fn is_macro_call(ast: MalVal, env: Env) -> bool { - let lst = match *ast { - List(ref lst,_) => &lst[0], - _ => return false - }; - match **lst { - Sym(_) => {}, - _ => return false - } - if env_find(&env, lst).is_none() { - return false - } - let f = match env_get(&env, lst) { - Ok(f) => f, - _ => return false - }; - match *f { - MalFunc(ref mfd,_) => mfd.is_macro, - _ => false, - } -} - -fn macroexpand(mut ast: MalVal, env: Env) -> MalRet { - while is_macro_call(ast.clone(), env.clone()) { - let ast2 = ast.clone(); - let args = match *ast2 { - List(ref args,_) => args, - _ => break, - }; - let ref a0 = args[0]; - let mf = match **a0 { - Sym(_) => try!(env_get(&env, &a0)), - _ => break, - }; - match *mf { - MalFunc(_,_) => ast = try!(mf.apply(args[1..].to_vec())), - _ => break, - } - } - Ok(ast) -} - -fn eval_ast(ast: MalVal, env: Env) -> MalRet { - match *ast { - Sym(_) => env_get(&env, &ast), - List(ref a,_) | Vector(ref a,_) => { - let mut ast_vec : Vec = vec![]; - for mv in a.iter() { - let mv2 = mv.clone(); - ast_vec.push(try!(eval(mv2, env.clone()))); - } - Ok(match *ast { List(_,_) => list(ast_vec), - _ => vector(ast_vec) }) - } - Hash_Map(ref hm,_) => { - let mut new_hm: HashMap = HashMap::new(); - for (key, value) in hm.iter() { - new_hm.insert(key.to_string(), - try!(eval(value.clone(), env.clone()))); - } - Ok(hash_map(new_hm)) - } - _ => Ok(ast.clone()), - } -} - -fn eval(mut ast: MalVal, mut env: Env) -> MalRet { - 'tco: loop { - - //println!("eval: {}, {}", ast, env.borrow()); - //println!("eval: {}", ast); - match *ast { - List(_,_) => (), // continue - _ => return eval_ast(ast, env), - } - - // apply list - ast = try!(macroexpand(ast, env.clone())); - match *ast { - List(_,_) => (), // continue - _ => return eval_ast(ast, env), - } - - let tmp = ast; - let (args, a0sym) = match *tmp { - List(ref args,_) => { - if args.len() == 0 { - return Ok(tmp.clone()); - } - let ref a0 = *args[0]; - match *a0 { - Sym(ref a0sym) => (args, &a0sym[..]), - _ => (args, "____"), - } - }, - _ => return err_str("Expected list"), - }; - - match a0sym { - "def!" => { - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - let r = try!(eval(a2, env.clone())); - match *a1 { - Sym(_) => { - env_set(&env.clone(), a1, r.clone()); - return Ok(r); - }, - _ => return err_str("def! of non-symbol"), - } - }, - "let*" => { - let let_env = env_new(Some(env.clone())); - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - match *a1 { - List(ref binds,_) | Vector(ref binds,_) => { - let mut it = binds.iter(); - while it.len() >= 2 { - let b = it.next().unwrap(); - let exp = it.next().unwrap(); - match **b { - Sym(_) => { - let r = try!(eval(exp.clone(), let_env.clone())); - env_set(&let_env, b.clone(), r); - }, - _ => return err_str("let* with non-symbol binding"), - } - } - }, - _ => return err_str("let* with non-list bindings"), - } - ast = a2; - env = let_env.clone(); - continue 'tco; - }, - "quote" => return Ok((*args)[1].clone()), - "quasiquote" => { - let a1 = (*args)[1].clone(); - ast = quasiquote(a1); - continue 'tco; - }, - "defmacro!" => { - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - let r = try!(eval(a2, env.clone())); - match *r { - MalFunc(ref mfd,_) => { - match *a1 { - Sym(_) => { - let mut new_mfd = mfd.clone(); - new_mfd.is_macro = true; - let mf = malfuncd(new_mfd,_nil()); - env_set(&env.clone(), a1.clone(), mf.clone()); - return Ok(mf); - }, - _ => return err_str("def! of non-symbol"), - } - }, - _ => return err_str("def! of non-symbol"), - } - }, - "macroexpand" => { - let a1 = (*args)[1].clone(); - return macroexpand(a1, env.clone()) - }, - "do" => { - let el = list(args[1..args.len()-1].to_vec()); - try!(eval_ast(el, env.clone())); - ast = args[args.len() - 1].clone(); - continue 'tco; - }, - "if" => { - let a1 = (*args)[1].clone(); - let c = try!(eval(a1, env.clone())); - match *c { - False | Nil => { - if args.len() >= 4 { - ast = args[3].clone(); - continue 'tco; - } else { - return Ok(_nil()); - } - }, - _ => { - ast = args[2].clone(); - continue 'tco; - }, - } - }, - "fn*" => { - let a1 = args[1].clone(); - let a2 = args[2].clone(); - return Ok(malfunc(eval, a2, env, a1, _nil())); - }, - "eval" => { - let a1 = (*args)[1].clone(); - ast = try!(eval(a1, env.clone())); - env = env_root(&env); - continue 'tco; - }, - _ => { // function call - let el = try!(eval_ast(tmp.clone(), env.clone())); - let args = match *el { - List(ref args,_) => args, - _ => return err_str("Invalid apply"), - }; - return match *args.clone()[0] { - Func(f,_) => f(args[1..].to_vec()), - MalFunc(ref mf,_) => { - let mfc = mf.clone(); - let alst = list(args[1..].to_vec()); - let new_env = env_new(Some(mfc.env.clone())); - match env_bind(&new_env, mfc.params, alst) { - Ok(_) => { - ast = mfc.exp; - env = new_env; - continue 'tco; - }, - Err(e) => err_str(&e), - } - }, - _ => err_str("attempt to call non-function"), - } - }, - } - - } -} - -// print -fn print(exp: MalVal) -> String { - exp.pr_str(true) -} - -fn rep(str: &str, env: Env) -> Result { - let ast = try!(read(str.to_string())); - //println!("read: {}", ast); - let exp = try!(eval(ast, env)); - Ok(print(exp)) -} - -fn main() { - // core.rs: defined using rust - let repl_env = env_new(None); - for (k, v) in core::ns().into_iter() { - env_set(&repl_env, symbol(&k), v); - } - // see eval() for definition of "eval" - env_set(&repl_env, symbol("*ARGV*"), list(vec![])); - - // core.mal: defined using the language itself - let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone()); - let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env.clone()); - let _ = rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", repl_env.clone()); - let _ = rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", repl_env.clone()); - - // Invoked with command line arguments - let args = stdenv::args(); - if args.len() > 1 { - let mv_args = args.skip(2) - .map(|a| string(a)) - .collect::>(); - env_set(&repl_env, symbol("*ARGV*"), list(mv_args)); - let lf = format!("(load-file \"{}\")", - stdenv::args().skip(1).next().unwrap()); - return match rep(&lf, repl_env.clone()) { - Ok(_) => process::exit(0), - Err(str) => { - println!("Error: {:?}", str); - process::exit(1); - } - }; - } - - // repl loop - loop { - let line = readline::mal_readline("user> "); - match line { None => break, _ => () } - match rep(&line.unwrap(), repl_env.clone()) { - Ok(str) => println!("{}", str), - Err(ErrMalVal(_)) => (), // Blank line - Err(ErrString(s)) => println!("Error: {}", s), - } - } -} diff --git a/rust/src/bin/step9_try.rs b/rust/src/bin/step9_try.rs deleted file mode 100644 index 10a70b0157..0000000000 --- a/rust/src/bin/step9_try.rs +++ /dev/null @@ -1,380 +0,0 @@ - -extern crate mal; - -use std::collections::HashMap; -use std::env as stdenv; -use std::process as process; - -use mal::types::{MalVal, MalRet, MalError, err_str}; -use mal::types::{symbol, _nil, string, list, vector, hash_map, malfunc, malfuncd}; -use mal::types::MalError::{ErrString, ErrMalVal}; -use mal::types::MalType::{Nil, False, Sym, List, Vector, Hash_Map, Func, MalFunc}; -use mal::{readline, reader, core}; -use mal::env::{env_set, env_get, env_new, env_bind, env_find, env_root, Env}; - - -// read -fn read(str: String) -> MalRet { - reader::read_str(str) -} - -// eval -fn is_pair(x: MalVal) -> bool { - match *x { - List(ref lst,_) | Vector(ref lst,_) => lst.len() > 0, - _ => false, - } -} - -fn quasiquote(ast: MalVal) -> MalVal { - if !is_pair(ast.clone()) { - return list(vec![symbol("quote"), ast]) - } - - match *ast.clone() { - List(ref args,_) | Vector(ref args,_) => { - let ref a0 = args[0]; - match **a0 { - Sym(ref s) if *s == "unquote" => return args[1].clone(), - _ => (), - } - if is_pair(a0.clone()) { - match **a0 { - List(ref a0args,_) | Vector(ref a0args,_) => { - match *a0args[0] { - Sym(ref s) if *s == "splice-unquote" => { - return list(vec![symbol("concat"), - a0args[1].clone(), - quasiquote(list(args[1..].to_vec()))]) - }, - _ => (), - } - }, - _ => (), - } - } - let rest = list(args[1..].to_vec()); - return list(vec![symbol("cons"), - quasiquote(a0.clone()), - quasiquote(rest)]) - }, - _ => _nil(), // should never reach - } -} - -fn is_macro_call(ast: MalVal, env: Env) -> bool { - let lst = match *ast { - List(ref lst,_) => &lst[0], - _ => return false - }; - match **lst { - Sym(_) => {}, - _ => return false - } - if env_find(&env, lst).is_none() { - return false - } - let f = match env_get(&env, lst) { - Ok(f) => f, - _ => return false - }; - match *f { - MalFunc(ref mfd,_) => mfd.is_macro, - _ => false, - } -} - -fn macroexpand(mut ast: MalVal, env: Env) -> MalRet { - while is_macro_call(ast.clone(), env.clone()) { - let ast2 = ast.clone(); - let args = match *ast2 { - List(ref args,_) => args, - _ => break, - }; - let ref a0 = args[0]; - let mf = match **a0 { - Sym(_) => try!(env_get(&env, &a0)), - _ => break, - }; - match *mf { - MalFunc(_,_) => ast = try!(mf.apply(args[1..].to_vec())), - _ => break, - } - } - Ok(ast) -} - -fn eval_ast(ast: MalVal, env: Env) -> MalRet { - match *ast { - Sym(_) => env_get(&env, &ast), - List(ref a,_) | Vector(ref a,_) => { - let mut ast_vec : Vec = vec![]; - for mv in a.iter() { - let mv2 = mv.clone(); - ast_vec.push(try!(eval(mv2, env.clone()))); - } - Ok(match *ast { List(_,_) => list(ast_vec), - _ => vector(ast_vec) }) - } - Hash_Map(ref hm,_) => { - let mut new_hm: HashMap = HashMap::new(); - for (key, value) in hm.iter() { - new_hm.insert(key.to_string(), - try!(eval(value.clone(), env.clone()))); - } - Ok(hash_map(new_hm)) - } - _ => Ok(ast.clone()), - } -} - -fn eval(mut ast: MalVal, mut env: Env) -> MalRet { - 'tco: loop { - - //println!("eval: {}, {}", ast, env.borrow()); - //println!("eval: {}", ast); - match *ast { - List(_,_) => (), // continue - _ => return eval_ast(ast, env), - } - - // apply list - ast = try!(macroexpand(ast, env.clone())); - match *ast { - List(_,_) => (), // continue - _ => return eval_ast(ast, env), - } - - let tmp = ast; - let (args, a0sym) = match *tmp { - List(ref args,_) => { - if args.len() == 0 { - return Ok(tmp.clone()); - } - let ref a0 = *args[0]; - match *a0 { - Sym(ref a0sym) => (args, &a0sym[..]), - _ => (args, "____"), - } - }, - _ => return err_str("Expected list"), - }; - - match a0sym { - "def!" => { - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - let r = try!(eval(a2, env.clone())); - match *a1 { - Sym(_) => { - env_set(&env.clone(), a1, r.clone()); - return Ok(r); - }, - _ => return err_str("def! of non-symbol"), - } - }, - "let*" => { - let let_env = env_new(Some(env.clone())); - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - match *a1 { - List(ref binds,_) | Vector(ref binds,_) => { - let mut it = binds.iter(); - while it.len() >= 2 { - let b = it.next().unwrap(); - let exp = it.next().unwrap(); - match **b { - Sym(_) => { - let r = try!(eval(exp.clone(), let_env.clone())); - env_set(&let_env, b.clone(), r); - }, - _ => return err_str("let* with non-symbol binding"), - } - } - }, - _ => return err_str("let* with non-list bindings"), - } - ast = a2; - env = let_env.clone(); - continue 'tco; - }, - "quote" => return Ok((*args)[1].clone()), - "quasiquote" => { - let a1 = (*args)[1].clone(); - ast = quasiquote(a1); - continue 'tco; - }, - "defmacro!" => { - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - let r = try!(eval(a2, env.clone())); - match *r { - MalFunc(ref mfd,_) => { - match *a1 { - Sym(_) => { - let mut new_mfd = mfd.clone(); - new_mfd.is_macro = true; - let mf = malfuncd(new_mfd,_nil()); - env_set(&env.clone(), a1.clone(), mf.clone()); - return Ok(mf); - }, - _ => return err_str("def! of non-symbol"), - } - }, - _ => return err_str("def! of non-symbol"), - } - }, - "macroexpand" => { - let a1 = (*args)[1].clone(); - return macroexpand(a1, env.clone()) - }, - "try*" => { - let a1 = (*args)[1].clone(); - match eval(a1, env.clone()) { - Ok(res) => return Ok(res), - Err(err) => { - if args.len() < 3 { return Err(err); } - let a2 = (*args)[2].clone(); - let cat = match *a2 { - List(ref cat,_) => cat, - _ => return err_str("invalid catch* clause"), - }; - if cat.len() != 3 { - return err_str("wrong arity to catch* clause"); - } - let c1 = (*cat)[1].clone(); - match *c1 { - Sym(_) => {}, - _ => return err_str("invalid catch* binding"), - }; - let exc = match err { - ErrMalVal(mv) => mv, - ErrString(s) => string(s), - }; - let bind_env = env_new(Some(env.clone())); - env_set(&bind_env, c1.clone(), exc); - let c2 = (*cat)[2].clone(); - return eval(c2, bind_env); - }, - }; - } - "do" => { - let el = list(args[1..args.len()-1].to_vec()); - try!(eval_ast(el, env.clone())); - ast = args[args.len() - 1].clone(); - continue 'tco; - }, - "if" => { - let a1 = (*args)[1].clone(); - let c = try!(eval(a1, env.clone())); - match *c { - False | Nil => { - if args.len() >= 4 { - ast = args[3].clone(); - continue 'tco; - } else { - return Ok(_nil()); - } - }, - _ => { - ast = args[2].clone(); - continue 'tco; - }, - } - }, - "fn*" => { - let a1 = args[1].clone(); - let a2 = args[2].clone(); - return Ok(malfunc(eval, a2, env, a1, _nil())); - }, - "eval" => { - let a1 = (*args)[1].clone(); - ast = try!(eval(a1, env.clone())); - env = env_root(&env); - continue 'tco; - }, - _ => { // function call - let el = try!(eval_ast(tmp.clone(), env.clone())); - let args = match *el { - List(ref args,_) => args, - _ => return err_str("Invalid apply"), - }; - return match *args.clone()[0] { - Func(f,_) => f(args[1..].to_vec()), - MalFunc(ref mf,_) => { - let mfc = mf.clone(); - let alst = list(args[1..].to_vec()); - let new_env = env_new(Some(mfc.env.clone())); - match env_bind(&new_env, mfc.params, alst) { - Ok(_) => { - ast = mfc.exp; - env = new_env; - continue 'tco; - }, - Err(e) => err_str(&e), - } - }, - _ => err_str("attempt to call non-function"), - } - }, - } - - } -} - -// print -fn print(exp: MalVal) -> String { - exp.pr_str(true) -} - -fn rep(str: &str, env: Env) -> Result { - let ast = try!(read(str.to_string())); - //println!("read: {}", ast); - let exp = try!(eval(ast, env)); - Ok(print(exp)) -} - -fn main() { - // core.rs: defined using rust - let repl_env = env_new(None); - for (k, v) in core::ns().into_iter() { - env_set(&repl_env, symbol(&k), v); - } - // see eval() for definition of "eval" - env_set(&repl_env, symbol("*ARGV*"), list(vec![])); - - // core.mal: defined using the language itself - let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone()); - let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env.clone()); - let _ = rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", repl_env.clone()); - let _ = rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", repl_env.clone()); - - // Invoked with command line arguments - let args = stdenv::args(); - if args.len() > 1 { - let mv_args = args.skip(2) - .map(|a| string(a)) - .collect::>(); - env_set(&repl_env, symbol("*ARGV*"), list(mv_args)); - let lf = format!("(load-file \"{}\")", - stdenv::args().skip(1).next().unwrap()); - return match rep(&lf, repl_env.clone()) { - Ok(_) => process::exit(0), - Err(str) => { - println!("Error: {:?}", str); - process::exit(1); - } - }; - } - - // repl loop - loop { - let line = readline::mal_readline("user> "); - match line { None => break, _ => () } - match rep(&line.unwrap(), repl_env.clone()) { - Ok(str) => println!("{}", str), - Err(ErrMalVal(_)) => (), // Blank line - Err(ErrString(s)) => println!("Error: {}", s), - } - } -} diff --git a/rust/src/bin/stepA_mal.rs b/rust/src/bin/stepA_mal.rs deleted file mode 100644 index 59b966e5a4..0000000000 --- a/rust/src/bin/stepA_mal.rs +++ /dev/null @@ -1,385 +0,0 @@ -#![allow(non_snake_case)] - -extern crate mal; - -use std::collections::HashMap; -use std::env as stdenv; -use std::process as process; - -use mal::types::{MalVal, MalRet, MalError, err_str}; -use mal::types::{symbol, _nil, string, list, vector, hash_map, malfunc, malfuncd}; -use mal::types::MalError::{ErrString, ErrMalVal}; -use mal::types::MalType::{Nil, False, Sym, List, Vector, Hash_Map, Func, MalFunc}; -use mal::{readline, reader, core}; -use mal::env::{env_set, env_get, env_new, env_bind, env_find, env_root, Env}; - - -// read -fn read(str: String) -> MalRet { - reader::read_str(str) -} - -// eval -fn is_pair(x: MalVal) -> bool { - match *x { - List(ref lst,_) | Vector(ref lst,_) => lst.len() > 0, - _ => false, - } -} - -fn quasiquote(ast: MalVal) -> MalVal { - if !is_pair(ast.clone()) { - return list(vec![symbol("quote"), ast]) - } - - match *ast.clone() { - List(ref args,_) | Vector(ref args,_) => { - let ref a0 = args[0]; - match **a0 { - Sym(ref s) if *s == "unquote" => return args[1].clone(), - _ => (), - } - if is_pair(a0.clone()) { - match **a0 { - List(ref a0args,_) | Vector(ref a0args,_) => { - match *a0args[0] { - Sym(ref s) if *s == "splice-unquote" => { - return list(vec![symbol("concat"), - a0args[1].clone(), - quasiquote(list(args[1..].to_vec()))]) - }, - _ => (), - } - }, - _ => (), - } - } - let rest = list(args[1..].to_vec()); - return list(vec![symbol("cons"), - quasiquote(a0.clone()), - quasiquote(rest)]) - }, - _ => _nil(), // should never reach - } -} - -fn is_macro_call(ast: MalVal, env: Env) -> bool { - let lst = match *ast { - List(ref lst,_) => &lst[0], - _ => return false - }; - match **lst { - Sym(_) => {}, - _ => return false - } - if env_find(&env, lst).is_none() { - return false - } - let f = match env_get(&env, lst) { - Ok(f) => f, - _ => return false - }; - match *f { - MalFunc(ref mfd,_) => mfd.is_macro, - _ => false, - } -} - -fn macroexpand(mut ast: MalVal, env: Env) -> MalRet { - while is_macro_call(ast.clone(), env.clone()) { - let ast2 = ast.clone(); - let args = match *ast2 { - List(ref args,_) => args, - _ => break, - }; - let ref a0 = args[0]; - let mf = match **a0 { - Sym(_) => try!(env_get(&env, &a0)), - _ => break, - }; - match *mf { - MalFunc(_,_) => ast = try!(mf.apply(args[1..].to_vec())), - _ => break, - } - } - Ok(ast) -} - -fn eval_ast(ast: MalVal, env: Env) -> MalRet { - match *ast { - Sym(_) => env_get(&env, &ast), - List(ref a,_) | Vector(ref a,_) => { - let mut ast_vec : Vec = vec![]; - for mv in a.iter() { - let mv2 = mv.clone(); - ast_vec.push(try!(eval(mv2, env.clone()))); - } - Ok(match *ast { List(_,_) => list(ast_vec), - _ => vector(ast_vec) }) - } - Hash_Map(ref hm,_) => { - let mut new_hm: HashMap = HashMap::new(); - for (key, value) in hm.iter() { - new_hm.insert(key.to_string(), - try!(eval(value.clone(), env.clone()))); - } - Ok(hash_map(new_hm)) - } - _ => Ok(ast.clone()), - } -} - -fn eval(mut ast: MalVal, mut env: Env) -> MalRet { - 'tco: loop { - - //println!("eval: {}, {}", ast, env.borrow()); - //println!("eval: {}", ast); - match *ast { - List(_,_) => (), // continue - _ => return eval_ast(ast, env), - } - - // apply list - ast = try!(macroexpand(ast, env.clone())); - match *ast { - List(_,_) => (), // continue - _ => return eval_ast(ast, env), - } - - let tmp = ast; - let (args, a0sym) = match *tmp { - List(ref args,_) => { - if args.len() == 0 { - return Ok(tmp.clone()); - } - let ref a0 = *args[0]; - match *a0 { - Sym(ref a0sym) => (args, &a0sym[..]), - _ => (args, "____"), - } - }, - _ => return err_str("Expected list"), - }; - - match a0sym { - "def!" => { - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - let r = try!(eval(a2, env.clone())); - match *a1 { - Sym(_) => { - env_set(&env.clone(), a1, r.clone()); - return Ok(r); - }, - _ => return err_str("def! of non-symbol"), - } - }, - "let*" => { - let let_env = env_new(Some(env.clone())); - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - match *a1 { - List(ref binds,_) | Vector(ref binds,_) => { - let mut it = binds.iter(); - while it.len() >= 2 { - let b = it.next().unwrap(); - let exp = it.next().unwrap(); - match **b { - Sym(_) => { - let r = try!(eval(exp.clone(), let_env.clone())); - env_set(&let_env, b.clone(), r); - }, - _ => return err_str("let* with non-symbol binding"), - } - } - }, - _ => return err_str("let* with non-list bindings"), - } - ast = a2; - env = let_env.clone(); - continue 'tco; - }, - "quote" => return Ok((*args)[1].clone()), - "quasiquote" => { - let a1 = (*args)[1].clone(); - ast = quasiquote(a1); - continue 'tco; - }, - "defmacro!" => { - let a1 = (*args)[1].clone(); - let a2 = (*args)[2].clone(); - let r = try!(eval(a2, env.clone())); - match *r { - MalFunc(ref mfd,_) => { - match *a1 { - Sym(_) => { - let mut new_mfd = mfd.clone(); - new_mfd.is_macro = true; - let mf = malfuncd(new_mfd,_nil()); - env_set(&env.clone(), a1.clone(), mf.clone()); - return Ok(mf); - }, - _ => return err_str("def! of non-symbol"), - } - }, - _ => return err_str("def! of non-symbol"), - } - }, - "macroexpand" => { - let a1 = (*args)[1].clone(); - return macroexpand(a1, env.clone()) - }, - "try*" => { - let a1 = (*args)[1].clone(); - match eval(a1, env.clone()) { - Ok(res) => return Ok(res), - Err(err) => { - if args.len() < 3 { return Err(err); } - let a2 = (*args)[2].clone(); - let cat = match *a2 { - List(ref cat,_) => cat, - _ => return err_str("invalid catch* clause"), - }; - if cat.len() != 3 { - return err_str("wrong arity to catch* clause"); - } - let c1 = (*cat)[1].clone(); - match *c1 { - Sym(_) => {}, - _ => return err_str("invalid catch* binding"), - }; - let exc = match err { - ErrMalVal(mv) => mv, - ErrString(s) => string(s), - }; - let bind_env = env_new(Some(env.clone())); - env_set(&bind_env, c1.clone(), exc); - let c2 = (*cat)[2].clone(); - return eval(c2, bind_env); - }, - }; - } - "do" => { - let el = list(args[1..args.len()-1].to_vec()); - try!(eval_ast(el, env.clone())); - ast = args[args.len() - 1].clone(); - continue 'tco; - }, - "if" => { - let a1 = (*args)[1].clone(); - let c = try!(eval(a1, env.clone())); - match *c { - False | Nil => { - if args.len() >= 4 { - ast = args[3].clone(); - continue 'tco; - } else { - return Ok(_nil()); - } - }, - _ => { - ast = args[2].clone(); - continue 'tco; - }, - } - }, - "fn*" => { - let a1 = args[1].clone(); - let a2 = args[2].clone(); - return Ok(malfunc(eval, a2, env, a1, _nil())); - }, - "eval" => { - let a1 = (*args)[1].clone(); - ast = try!(eval(a1, env.clone())); - env = env_root(&env); - continue 'tco; - }, - _ => { // function call - let el = try!(eval_ast(tmp.clone(), env.clone())); - let args = match *el { - List(ref args,_) => args, - _ => return err_str("Invalid apply"), - }; - return match *args.clone()[0] { - Func(f,_) => f(args[1..].to_vec()), - MalFunc(ref mf,_) => { - let mfc = mf.clone(); - let alst = list(args[1..].to_vec()); - let new_env = env_new(Some(mfc.env.clone())); - match env_bind(&new_env, mfc.params, alst) { - Ok(_) => { - ast = mfc.exp; - env = new_env; - continue 'tco; - }, - Err(e) => err_str(&e), - } - }, - _ => err_str("attempt to call non-function"), - } - }, - } - - } -} - -// print -fn print(exp: MalVal) -> String { - exp.pr_str(true) -} - -fn rep(str: &str, env: Env) -> Result { - let ast = try!(read(str.to_string())); - //println!("read: {}", ast); - let exp = try!(eval(ast, env)); - Ok(print(exp)) -} - -fn main() { - // core.rs: defined using rust - let repl_env = env_new(None); - for (k, v) in core::ns().into_iter() { - env_set(&repl_env, symbol(&k), v); - } - // see eval() for definition of "eval" - env_set(&repl_env, symbol("*ARGV*"), list(vec![])); - - // core.mal: defined using the language itself - let _ = rep("(def! *host-language* \"rust\")", repl_env.clone()); - let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone()); - let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env.clone()); - let _ = rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", repl_env.clone()); - let _ = rep("(def! *gensym-counter* (atom 0))", repl_env.clone()); - let _ = rep("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))", repl_env.clone()); - let _ = rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))", repl_env.clone()); - - // Invoked with command line arguments - let args = stdenv::args(); - if args.len() > 1 { - let mv_args = args.skip(2) - .map(|a| string(a)) - .collect::>(); - env_set(&repl_env, symbol("*ARGV*"), list(mv_args)); - let lf = format!("(load-file \"{}\")", - stdenv::args().skip(1).next().unwrap()); - return match rep(&lf, repl_env.clone()) { - Ok(_) => process::exit(0), - Err(str) => { - println!("Error: {:?}", str); - process::exit(1); - } - }; - } - - // repl loop - let _ = rep("(println (str \"Mal [\" *host-language* \"]\"))", repl_env.clone()); - loop { - let line = readline::mal_readline("user> "); - match line { None => break, _ => () } - match rep(&line.unwrap(), repl_env.clone()) { - Ok(str) => println!("{}", str), - Err(ErrMalVal(_)) => (), // Blank line - Err(ErrString(s)) => println!("Error: {}", s), - } - } -} diff --git a/rust/src/core.rs b/rust/src/core.rs deleted file mode 100644 index a598f6cf17..0000000000 --- a/rust/src/core.rs +++ /dev/null @@ -1,560 +0,0 @@ -#![allow(dead_code)] - -use std::collections::HashMap; -use std::fs::File; -use std::io::prelude::*; -//use std::num::ToPrimitive; -use num::traits::ToPrimitive; -use time; - -use types::{MalVal,MalRet,err_val,err_str,err_string, - _nil,_true,_false,_int,string, - list,vector,listm,vectorm,hash_mapm,func,funcm,malfuncd}; -use types::MalType::{Nil, Int, Strn, List, Vector, Hash_Map, Func, MalFunc, Atom}; -use types; -use readline; -use reader; -use printer; - -// General functions -fn equal_q(a: Vec) -> MalRet { - if a.len() != 2 { - return err_str("Wrong arity to equal? call"); - } - if a[0] == a[1] {Ok(_true())} else {Ok(_false())} -} - -// Errors/Exceptions -fn throw(a: Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to throw call"); - } - err_val(a[0].clone()) -} - -// String routines -fn pr_str(a: Vec) -> MalRet { - Ok(string(printer::pr_list(&a, true, "", "", " "))) -} - -fn str(a: Vec) -> MalRet { - Ok(string(printer::pr_list(&a, false, "", "", ""))) -} - -fn prn(a: Vec) -> MalRet { - println!("{}", printer::pr_list(&a, true, "", "", " ")); - Ok(_nil()) -} - -fn println(a: Vec) -> MalRet { - println!("{}", printer::pr_list(&a, false, "", "", " ")); - Ok(_nil()) -} - -fn readline(a: Vec) -> MalRet { - match *a[0] { - Strn(ref a0) => match readline::mal_readline(&a0) { - Some(line) => Ok(string(line)), - None => err_val(_nil()), - }, - _ => err_str("read_string called with non-string"), - } -} - -fn read_string(a: Vec) -> MalRet { - match *a[0] { - Strn(ref a0) => reader::read_str(a0.to_string()), - _ => err_str("read_string called with non-string"), - } -} - -fn slurp(a: Vec) -> MalRet { - match *a[0] { - Strn(ref a0) => { - let mut s = String::new(); - match File::open(a0).and_then(|mut f| f.read_to_string(&mut s)) { - Ok(_) => Ok(string(s)), - Err(e) => err_string(e.to_string()), - } - }, - _ => err_str("slurp called with non-string"), - } -} - - -// Numeric functions -fn int_op(f: F, a: Vec) -> MalRet - where F: FnOnce(isize, isize) -> isize -{ - match *a[0] { - Int(a0) => match *a[1] { - Int(a1) => Ok(_int(f(a0,a1))), - _ => err_str("second arg must be an int"), - }, - _ => err_str("first arg must be an int"), - } -} - -fn bool_op(f: F, a: Vec) -> MalRet - where F: FnOnce(isize, isize) -> bool -{ - match *a[0] { - Int(a0) => match *a[1] { - Int(a1) => { - match f(a0,a1) { - true => Ok(_true()), - false => Ok(_false()), - } - }, - _ => err_str("second arg must be an int"), - }, - _ => err_str("first arg must be an int"), - } -} - -pub fn add(a: Vec) -> MalRet { int_op(|i,j| { i+j }, a) } -pub fn sub(a: Vec) -> MalRet { int_op(|i,j| { i-j }, a) } -pub fn mul(a: Vec) -> MalRet { int_op(|i,j| { i*j }, a) } -pub fn div(a: Vec) -> MalRet { int_op(|i,j| { i/j }, a) } - -pub fn lt (a: Vec) -> MalRet { bool_op(|i,j| { i) -> MalRet { bool_op(|i,j| { i<=j }, a) } -pub fn gt (a: Vec) -> MalRet { bool_op(|i,j| { i>j }, a) } -pub fn gte(a: Vec) -> MalRet { bool_op(|i,j| { i>=j }, a) } - -pub fn time_ms(_a: Vec) -> MalRet { - //let x = time::now(); - let now = time::get_time(); - let now_ms = (now.sec * 1000).to_isize().unwrap() + - (now.nsec.to_isize().unwrap() / 1000000); - Ok(_int(now_ms)) -} - - -// Hash Map functions -pub fn assoc(a: Vec) -> MalRet { - if a.len() < 3 { - return err_str("Wrong arity to assoc call"); - } - match *a[0] { - Hash_Map(ref hm,_) => types::_assoc(hm, a[1..].to_vec()), - Nil => types::hash_mapv(a[1..].to_vec()), - _ => err_str("assoc onto non-hash map"), - } -} - -pub fn dissoc(a: Vec) -> MalRet { - if a.len() < 2 { - return err_str("Wrong arity to dissoc call"); - } - match *a[0] { - Hash_Map(ref hm,_) => types::_dissoc(hm, a[1..].to_vec()), - Nil => Ok(_nil()), - _ => err_str("dissoc onto non-hash map"), - } -} - -pub fn get(a: Vec) -> MalRet { - if a.len() != 2 { - return err_str("Wrong arity to get call"); - } - let hm = match *a[0] { - Hash_Map(ref hm,_) => hm, - Nil => return Ok(_nil()), - _ => return err_str("get on non-hash map"), - }; - match *a[1] { - Strn(ref key) => { - match hm.get(key) { - Some(v) => Ok(v.clone()), - None => Ok(_nil()), - } - }, - _ => err_str("get with non-string key"), - } -} - -pub fn contains_q(a: Vec) -> MalRet { - if a.len() != 2 { - return err_str("Wrong arity to contains? call"); - } - let hm = match *a[0] { - Hash_Map(ref hm,_) => hm, - Nil => return Ok(_false()), - _ => return err_str("contains? on non-hash map"), - }; - match *a[1] { - Strn(ref key) => { - match hm.contains_key(key) { - true => Ok(_true()), - false => Ok(_false()), - } - } - _ => err_str("contains? with non-string key"), - } -} - -pub fn keys(a: Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to keys call"); - } - let hm = match *a[0] { - Hash_Map(ref hm,_) => hm, - Nil => return Ok(_nil()), - _ => return err_str("contains? on non-hash map"), - }; - Ok(list(hm.keys().map(|s| string(s.to_string())).collect())) -} - -pub fn vals(a: Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to values call"); - } - let hm = match *a[0] { - Hash_Map(ref hm,_) => hm, - Nil => return Ok(_nil()), - _ => return err_str("contains? on non-hash map"), - }; - Ok(list(hm.values().map(|s| s.clone()).collect())) -} - -// Sequence functions -pub fn cons(a: Vec) -> MalRet { - match *a[1] { - List(ref v,_) | Vector(ref v,_) => { - let mut new_v = v.clone(); - new_v.insert(0, a[0].clone()); - Ok(list(new_v)) - }, - _ => err_str("Second arg to cons not a sequence"), - } -} - -pub fn concat(a: Vec) -> MalRet { - let mut new_v: Vec = vec![]; - for lst in a.iter() { - match **lst { - List(ref l,_) | Vector(ref l,_) => new_v.extend(l.clone()), - _ => return err_str("concat called with non-sequence"), - } - } - Ok(list(new_v)) -} - -pub fn nth(a: Vec) -> MalRet { - if a.len() != 2 { - return err_str("Wrong arity to nth call"); - } - let seq = match *a[0] { - List(ref v,_) | Vector(ref v,_) => v, - _ => return err_str("nth called with non-sequence"), - }; - let idx = match *a[1] { - Int(i) => { - match i.to_usize() { - Some(ui) => ui, - None => return Ok(_nil()), - } - }, - _ => return err_str("nth called with non-integer index"), - }; - if idx >= seq.len() { - err_str("nth: index out of range") - } else { - Ok(seq[idx].clone()) - } -} - -pub fn first(a: Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to first call"); - } - let seq = match *a[0] { - List(ref v,_) | Vector(ref v,_) => v, - Nil => return Ok(_nil()), - _ => return err_str("first called with non-sequence"), - }; - if seq.len() == 0 { - Ok(_nil()) - } else { - Ok(seq[0].clone()) - } -} - -pub fn rest(a: Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to rest call"); - } - let seq = match *a[0] { - List(ref v,_) | Vector(ref v,_) => v, - Nil => return Ok(list(vec![])), - _ => return err_str("rest called with non-sequence"), - }; - if seq.len() == 0 { - Ok(list(vec![])) - } else { - Ok(list(seq[1..].to_vec())) - } -} - -pub fn empty_q(a: Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to empty? call"); - } - match *a[0] { - List(ref v,_) | Vector(ref v,_) => { - match v.len() { - 0 => Ok(_true()), - _ => Ok(_false()), - } - }, - _ => err_str("empty? called on non-sequence"), - } -} - -pub fn count(a: Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to count call"); - } - match *a[0] { - List(ref v,_) | Vector(ref v,_) => Ok(_int(v.len().to_isize().unwrap())), - Nil => Ok(_int(0)), - _ => err_str("count called on non-sequence"), - } -} - -pub fn apply(a: Vec) -> MalRet { - if a.len() < 2 { - return err_str("apply call needs 2 or more arguments"); - } - let ref f = a[0]; - let mut args = a[1..a.len()-1].to_vec(); - match *a[a.len()-1] { - List(ref v, _) | Vector(ref v, _) => { - args.extend(v.clone()); - f.apply(args) - }, - _ => err_str("apply call with non-sequence"), - } -} - -pub fn map(a: Vec) -> MalRet { - if a.len() != 2 { - return err_str("Wrong arity to map call"); - } - let mut results:Vec = vec![]; - match *a[1] { - List(ref v,_) | Vector(ref v,_) => { - for mv in v.iter() { - let res = try!(a[0].apply(vec![mv.clone()])); - results.push(res); - } - }, - _ => return err_str("map call with non-sequence"), - } - Ok(list(results)) -} - -pub fn conj(a: Vec) -> MalRet { - if a.len() < 2 { - return err_str("Wrong arity to conj call"); - } - let mut new_v: Vec = vec![]; - match *a[0] { - List(ref l,_) => { - new_v.extend(l.clone()); - for mv in a.iter().skip(1) { - new_v.insert(0, mv.clone()); - } - Ok(list(new_v)) - } - Vector(ref l,_) => { - new_v.extend(l.clone()); - for mv in a.iter().skip(1) { - new_v.push(mv.clone()); - } - Ok(vector(new_v)) - } - _ => err_str("conj called with non-sequence"), - } -} - -pub fn seq(a: Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to seq call"); - } - let mut new_v: Vec = vec![]; - match *a[0] { - List(ref l,_) | - Vector(ref l,_) => { - if l.len() == 0 { - Ok(_nil()) - } else { - new_v.extend(l.clone()); - Ok(list(new_v)) - } - }, - Strn(ref s) => { - if s.len() == 0 { - Ok(_nil()) - } else if s.starts_with("\u{29e}") { - err_str("seq: called with non-sequence") - } else { - for c in s.chars() { - new_v.push(string(c.to_string())); - } - Ok(list(new_v)) - } - }, - Nil => Ok(_nil()), - _ => err_str("seq: called with non-sequence"), - } -} - - - -// Metadata functions -fn with_meta(a: Vec) -> MalRet { - if a.len() != 2 { - return err_str("Wrong arity to with-meta call"); - } - let meta = a[1].clone(); - match *a[0] { - List(ref v,_) => Ok(listm(v.clone(), meta)), - Vector(ref v,_) => Ok(vectorm(v.clone(), meta)), - Hash_Map(ref hm,_) => Ok(hash_mapm(hm.clone(), meta)), - MalFunc(ref mfd,_) => Ok(malfuncd(mfd.clone(), meta)), - Func(f,_) => Ok(funcm(f, meta)), - _ => err_str("type does not support metadata"), - } -} - -fn meta(a: Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to meta call"); - } - match *a[0] { - List(_,ref meta) | - Vector(_,ref meta) | - Hash_Map(_,ref meta) | - MalFunc(_,ref meta) | - Func(_,ref meta) => Ok(meta.clone()), - _ => err_str("type does not support metadata"), - } -} - -// Atom functions -fn deref(a: Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to deref call"); - } - match *a[0] { - Atom(ref val) => Ok(val.borrow().clone()), - _ => err_str("deref called on non-atom"), - } -} - -fn reset_bang(a: Vec) -> MalRet { - if a.len() != 2 { - return err_str("Wrong arity to map call"); - } - match *a[0] { - Atom(ref val) => { - let mut val_cell = val.borrow_mut(); - *val_cell = a[1].clone(); - Ok(a[1].clone()) - }, - _ => err_str("reset! called on non-atom"), - } -} - -fn swap_bang(a: Vec) -> MalRet { - if a.len() < 2 { - return err_str("Wrong arity to swap_q call"); - } - let f = a[1].clone(); - match *a[0] { - Atom(ref val) => { - let mut val_cell = val.borrow_mut(); - let mut args = a[2..].to_vec(); - args.insert(0, val_cell.clone()); - *val_cell = try!(f.apply(args)); - Ok(val_cell.clone()) - }, - _ => err_str("swap! called on non-atom"), - } -} - - -pub fn ns() -> HashMap { - let mut ns = HashMap::new();; - - ns.insert("=".to_string(), func(equal_q)); - ns.insert("throw".to_string(), func(throw)); - ns.insert("nil?".to_string(), func(types::nil_q)); - ns.insert("true?".to_string(), func(types::true_q)); - ns.insert("false?".to_string(), func(types::false_q)); - ns.insert("string?".to_string(), func(types::string_q)); - ns.insert("symbol".to_string(), func(types::_symbol)); - ns.insert("symbol?".to_string(), func(types::symbol_q)); - ns.insert("keyword".to_string(), func(types::_keyword)); - ns.insert("keyword?".to_string(), func(types::keyword_q)); - ns.insert("number?".to_string(), func(types::int_q)); - ns.insert("fn?".to_string(), func(types::fn_q)); - ns.insert("macro?".to_string(), func(types::macro_q)); - - ns.insert("pr-str".to_string(), func(pr_str)); - ns.insert("str".to_string(), func(str)); - ns.insert("prn".to_string(), func(prn)); - ns.insert("println".to_string(), func(println)); - ns.insert("readline".to_string(), func(readline)); - ns.insert("read-string".to_string(), func(read_string)); - ns.insert("slurp".to_string(), func(slurp)); - - ns.insert("<".to_string(), func(lt)); - ns.insert("<=".to_string(), func(lte)); - ns.insert(">".to_string(), func(gt)); - ns.insert(">=".to_string(), func(gte)); - ns.insert("+".to_string(), func(add)); - ns.insert("-".to_string(), func(sub)); - ns.insert("*".to_string(), func(mul)); - ns.insert("/".to_string(), func(div)); - ns.insert("time-ms".to_string(), func(time_ms)); - - ns.insert("list".to_string(), func(types::listv)); - ns.insert("list?".to_string(), func(types::list_q)); - ns.insert("vector".to_string(), func(types::vectorv)); - ns.insert("vector?".to_string(), func(types::vector_q)); - ns.insert("hash-map".to_string(), func(types::hash_mapv)); - ns.insert("map?".to_string(), func(types::hash_map_q)); - ns.insert("assoc".to_string(), func(assoc)); - ns.insert("dissoc".to_string(), func(dissoc)); - ns.insert("get".to_string(), func(get)); - ns.insert("contains?".to_string(), func(contains_q)); - ns.insert("keys".to_string(), func(keys)); - ns.insert("vals".to_string(), func(vals)); - - ns.insert("sequential?".to_string(), func(types::sequential_q)); - ns.insert("cons".to_string(), func(cons)); - ns.insert("concat".to_string(), func(concat)); - ns.insert("empty?".to_string(), func(empty_q)); - ns.insert("nth".to_string(), func(nth)); - ns.insert("first".to_string(), func(first)); - ns.insert("rest".to_string(), func(rest)); - ns.insert("count".to_string(), func(count)); - ns.insert("apply".to_string(), func(apply)); - ns.insert("map".to_string(), func(map)); - - ns.insert("conj".to_string(), func(conj)); - ns.insert("seq".to_string(), func(seq)); - - ns.insert("with-meta".to_string(), func(with_meta)); - ns.insert("meta".to_string(), func(meta)); - ns.insert("atom".to_string(), func(types::atom)); - ns.insert("atom?".to_string(), func(types::atom_q)); - ns.insert("deref".to_string(), func(deref)); - ns.insert("reset!".to_string(), func(reset_bang)); - ns.insert("swap!".to_string(), func(swap_bang)); - - return ns; -} diff --git a/rust/src/env.rs b/rust/src/env.rs deleted file mode 100644 index a1588065ae..0000000000 --- a/rust/src/env.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::rc::Rc; -use std::cell::RefCell; -use std::collections::HashMap; - -use types::{MalVal, MalRet, _nil, list, err_string}; -use types::MalType::{Sym, List, Vector}; - -pub struct EnvType { - data: HashMap, - outer: Option, -} - -pub type Env = Rc>; - -pub fn env_new(outer: Option) -> Env { - Rc::new(RefCell::new(EnvType{data: HashMap::new(), outer: outer})) -} - -pub fn env_bind(env: &Env, - mbinds: MalVal, - mexprs: MalVal) -> Result { - let mut variadic = false; - match *mbinds { - List(ref binds,_) | Vector(ref binds,_) => { - match *mexprs { - List(ref exprs,_) | Vector(ref exprs,_) => { - let mut it = binds.iter().enumerate(); - for (i, b) in it.by_ref() { - match **b { - Sym(ref strn) => { - if *strn == "&" { - variadic = true; - break; - } else { - env_set(env, b.clone(), exprs[i].clone()); - } - } - _ => return Err("non-symbol bind".to_string()), - } - } - if variadic { - let (i, sym) = it.next().unwrap(); - match **sym { - Sym(_) => { - let rest = exprs[i-1..].to_vec(); - env_set(env, sym.clone(), list(rest)); - } - _ => return Err("& bind to non-symbol".to_string()), - } - } - Ok(env.clone()) - }, - _ => Err("exprs must be a list".to_string()), - } - }, - _ => Err("binds must be a list".to_string()), - } -} - -pub fn env_find(env: &Env, key: &MalVal) -> Option { - match **key { - Sym(ref k) => { - let map = env.borrow(); - if map.data.contains_key(k) { - Some(env.clone()) - } else { - match map.outer { - Some(ref e) => env_find(e, key), - None => None, - } - } - }, - _ => None - } -} - -pub fn env_root(env: &Env) -> Env { - match env.borrow().outer { - Some(ref ei) => env_root(ei), - None => env.clone(), - } -} - -pub fn env_set(env: &Env, key: MalVal, val: MalVal) { - match *key { - Sym(ref k) => { env.borrow_mut().data.insert(k.to_string(), val); } - _ => {}, - } -} - -pub fn env_get(env: &Env, key: &MalVal) -> MalRet { - match **key { - Sym(ref k) => { - match env_find(env, key) { - Some(e) => { - match e.borrow().data.get(k) { - Some(v) => Ok(v.clone()), - None => Ok(_nil()), - } - }, - None => err_string(format!("'{}' not found", k)), - } - } - _ => err_string("env_get called with non-symbol key".to_string()), - } -} diff --git a/rust/src/lib.rs b/rust/src/lib.rs deleted file mode 100644 index 0961bcc3e3..0000000000 --- a/rust/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -extern crate libc; -extern crate regex; -extern crate time; -extern crate num; - -macro_rules! regex { - ($e:expr) => (::regex::Regex::new($e).unwrap()) -} - -pub mod core; -pub mod env; -pub mod printer; -pub mod reader; -pub mod readline; -pub mod types; diff --git a/rust/src/printer.rs b/rust/src/printer.rs deleted file mode 100644 index 1bb11e3c27..0000000000 --- a/rust/src/printer.rs +++ /dev/null @@ -1,47 +0,0 @@ -use types::MalVal; -use regex::Captures; - -pub fn escape_str(s: &str) -> String { - let mut escaped = String::new(); - escaped.push('"'); - for c in s.chars() { - let _ = match c { - '"' => escaped.push_str("\\\""), - '\\' => escaped.push_str("\\\\"), - '\x08' => escaped.push_str("\\b"), - '\x0c' => escaped.push_str("\\f"), - '\n' => escaped.push_str("\\n"), - '\r' => escaped.push_str("\\r"), - '\t' => escaped.push_str("\\t"), - _ => escaped.push(c), - }; - }; - - escaped.push('"'); - - escaped -} - -pub fn unescape_str(s: &str) -> String { - let re = regex!(r#"\\(.)"#); - re.replace_all(&s, |caps: &Captures| { - format!("{}", if &caps[1] == "n" { "\n" } else { &caps[1] }) - }) -} - -pub fn pr_list(lst: &Vec, pr: bool, - start: &str , end: &str, join: &str) -> String { - let mut first = true; - let mut res = String::new(); - res.push_str(start); - for mv in lst.iter() { - if first { - first = false; - } else { - res.push_str(join); - } - res.push_str(&mv.pr_str(pr)); - } - res.push_str(end); - res -} diff --git a/rust/src/reader.rs b/rust/src/reader.rs deleted file mode 100644 index 94093db2c4..0000000000 --- a/rust/src/reader.rs +++ /dev/null @@ -1,198 +0,0 @@ -use std::borrow::ToOwned; -use types::MalError::{ErrString, ErrMalVal}; -use types::{MalVal, MalRet, - _nil, _true, _false, _int, symbol, string, list, vector, hash_mapv, - err_str, err_string, err_val}; -use super::printer::unescape_str; - -#[derive(Debug, Clone)] -struct Reader { - tokens: Vec, - position: usize, -} - -impl Reader { - fn next(&mut self) -> Option { - if self.position < self.tokens.len() { - self.position += 1; - Some(self.tokens[self.position-1].to_string()) - } else { - None - } - } - fn peek(&self) -> Option { - if self.position < self.tokens.len() { - Some(self.tokens[self.position].to_string()) - } else { - None - } - } -} - -fn tokenize(str: String) -> Vec { - let mut results = vec![]; - let re = regex!(r###"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)"###); - for cap in re.captures_iter(&str) { - let group = cap.at(1).unwrap_or(""); - if group == "" { break; } - if group.starts_with(";") { continue; } - results.push(group.to_owned()); - } - results -} - -fn read_atom(rdr : &mut Reader) -> MalRet { - let otoken = rdr.next(); - //println!("read_atom: {}", otoken); - if otoken.is_none() { return err_str("read_atom underflow"); } - let stoken = otoken.unwrap(); - let token = &stoken[..]; - if regex!(r"^-?[0-9]+$").is_match(token) { - let num : Option = token.parse().ok(); - Ok(_int(num.unwrap())) - } else if regex!(r#"^".*"$"#).is_match(token) { - let new_str = &token[1..token.len()-1]; - Ok(string(unescape_str(new_str))) - } else if regex!(r#"^:"#).is_match(token) { - Ok(string(format!("\u{29e}{}", &token[1..]))) - } else if token == "nil" { - Ok(_nil()) - } else if token == "true" { - Ok(_true()) - } else if token == "false" { - Ok(_false()) - } else { - Ok(symbol(token)) - } -} - -fn read_seq(rdr : &mut Reader, start: &str, end: &str) -> Result,String> { - let otoken = rdr.next(); - if otoken.is_none() { - return Err("read_atom underflow".to_string()); - } - let stoken = otoken.unwrap(); - let token = &stoken[..]; - if token != start { - return Err(format!("expected '{}'", start)) - } - - let mut ast_vec : Vec = vec![]; - loop { - let otoken = rdr.peek(); - if otoken.is_none() { - return Err(format!("expected '{}', got EOF", end)); - } - let stoken = otoken.unwrap(); - let token = &stoken[..]; - if token == end { break; } - - match read_form(rdr) { - Ok(mv) => ast_vec.push(mv), - Err(ErrString(es)) => return Err(es), - Err(ErrMalVal(_)) => return Err("read_seq exception".to_string()), - } - } - rdr.next(); - - Ok(ast_vec) -} - -fn read_list(rdr : &mut Reader) -> MalRet { - match read_seq(rdr, "(", ")") { - Ok(seq) => Ok(list(seq)), - Err(es) => err_string(es), - } -} - -fn read_vector(rdr : &mut Reader) -> MalRet { - match read_seq(rdr, "[", "]") { - Ok(seq) => Ok(vector(seq)), - Err(es) => err_string(es), - } -} - -fn read_hash_map(rdr : &mut Reader) -> MalRet { - match read_seq(rdr, "{", "}") { - Ok(seq) => hash_mapv(seq), - Err(es) => err_string(es), - } -} - -fn read_form(rdr : &mut Reader) -> MalRet { - let otoken = rdr.peek(); - //println!("read_form: {}", otoken); - let stoken = otoken.unwrap(); - let token = &stoken[..]; - match token { - "'" => { - let _ = rdr.next(); - match read_form(rdr) { - Ok(f) => Ok(list(vec![symbol("quote"), f])), - Err(e) => Err(e), - } - }, - "`" => { - let _ = rdr.next(); - match read_form(rdr) { - Ok(f) => Ok(list(vec![symbol("quasiquote"), f])), - Err(e) => Err(e), - } - }, - "~" => { - let _ = rdr.next(); - match read_form(rdr) { - Ok(f) => Ok(list(vec![symbol("unquote"), f])), - Err(e) => Err(e), - } - }, - "~@" => { - let _ = rdr.next(); - match read_form(rdr) { - Ok(f) => Ok(list(vec![symbol("splice-unquote"), f])), - Err(e) => Err(e), - } - }, - "^" => { - let _ = rdr.next(); - match read_form(rdr) { - Ok(meta) => { - match read_form(rdr) { - Ok(f) => Ok(list(vec![symbol("with-meta"), f, meta])), - Err(e) => Err(e), - } - }, - Err(e) => Err(e), - } - }, - "@" => { - let _ = rdr.next(); - match read_form(rdr) { - Ok(f) => Ok(list(vec![symbol("deref"), f])), - Err(e) => Err(e), - } - }, - - ")" => err_str("unexected ')'"), - "(" => read_list(rdr), - - "]" => err_str("unexected ']'"), - "[" => read_vector(rdr), - - "}" => err_str("unexected '}'"), - "{" => read_hash_map(rdr), - - _ => read_atom(rdr) - } -} - -pub fn read_str(str :String) -> MalRet { - let tokens = tokenize(str); - if tokens.len() == 0 { - // any malval as the error slot means empty line - return err_val(_nil()) - } - //println!("tokens: {}", tokens); - let rdr = &mut Reader{tokens: tokens, position: 0}; - read_form(rdr) -} diff --git a/rust/src/readline.rs b/rust/src/readline.rs deleted file mode 100644 index 53caf8f9eb..0000000000 --- a/rust/src/readline.rs +++ /dev/null @@ -1,81 +0,0 @@ -// Based on: https://github.com/shaleh/rust-readline (MIT) -use libc; - -use std::ffi::{CStr, CString}; -use std::fs::{OpenOptions, File}; -use std::io::BufReader; -use std::io::prelude::*; -use std::str; - -mod ext_readline { - extern crate libc; - use self::libc::c_char; - #[link(name = "readline")] - extern { - pub fn add_history(line: *const c_char); - pub fn readline(p: *const c_char) -> *const c_char; - } -} - -pub fn add_history(line: &str) { - unsafe { - ext_readline::add_history(CString::new(line).unwrap().as_ptr()); - } -} - -pub fn readline(prompt: &str) -> Option { - let cprmt = CString::new(prompt).unwrap(); - unsafe { - let ptr = ext_readline::readline(cprmt.as_ptr()); - if ptr.is_null() { // user pressed Ctrl-D - None - } else { - let ret = str::from_utf8(CStr::from_ptr(ptr).to_bytes()); - let ret = ret.ok().map(|s| s.to_string()); - libc::free(ptr as *mut _); - return ret; - } - } -} - -// -------------------------------------------- - -static mut HISTORY_LOADED : bool = false; -static HISTORY_FILE: &'static str = "/home/joelm/.mal-history"; - -fn load_history() { - unsafe { - if HISTORY_LOADED { return; } - HISTORY_LOADED = true; - } - - let file = match File::open(HISTORY_FILE) { - Ok(f) => f, - Err(..) => return - }; - let file = BufReader::new(file); - for line in file.lines() { - let rt: &[_] = &['\r', '\n']; - let line2 = line.unwrap(); - let line3 = line2.trim_right_matches(rt); - add_history(line3); - } -} - -fn append_to_history(line: &str) { - let file = OpenOptions::new().append(true).write(true).create(true) - .open(HISTORY_FILE); - let mut file = match file { Ok(f) => f, Err(..) => return }; - let _ = file.write_all(line.as_bytes()); - let _ = file.write_all(b"\n"); -} - -pub fn mal_readline (prompt: &str) -> Option { - load_history(); - let line = readline(prompt); - if let Some(ref s) = line { - add_history(s); - append_to_history(s); - } - line -} diff --git a/rust/src/types.rs b/rust/src/types.rs deleted file mode 100644 index 0fdf8de21e..0000000000 --- a/rust/src/types.rs +++ /dev/null @@ -1,430 +0,0 @@ -#![allow(dead_code)] - -use std::rc::Rc; -use std::cell::RefCell; -use std::collections::HashMap; -use std::fmt; -use super::printer::{escape_str,pr_list}; -use super::env::{Env,env_new,env_bind}; - -use self::MalType::*; -use self::MalError::*; - -#[derive(Clone)] -#[allow(non_camel_case_types)] -pub enum MalType { - Nil, - True, - False, - Int(isize), - Strn(String), - Sym(String), - List(Vec, MalVal), - Vector(Vec, MalVal), - Hash_Map(HashMap, MalVal), - Func(fn(Vec) -> MalRet, MalVal), - MalFunc(MalFuncData, MalVal), - Atom(RefCell), -} - -pub type MalVal = Rc; - -#[derive(Debug)] -pub enum MalError { - ErrString(String), - ErrMalVal(MalVal), -} - -pub type MalRet = Result; - - -pub fn err_string(s: String) -> MalRet { - Err(ErrString(s)) -} - -pub fn err_str(s: &str) -> MalRet { - Err(ErrString(s.to_string())) -} - -pub fn err_val(mv: MalVal) -> MalRet { - Err(ErrMalVal(mv)) -} - -#[derive(Clone)] -pub struct MalFuncData { - pub eval: fn(MalVal, Env) -> MalRet, - pub exp: MalVal, - pub env: Env, - pub params: MalVal, - pub is_macro: bool, -} - -impl MalType { - pub fn pr_str(&self, print_readably: bool) -> String { - let _r = print_readably; - match *self { - Nil => "nil".to_string(), - True => "true".to_string(), - False => "false".to_string(), - Int(v) => v.to_string(), - Sym(ref v) => v.clone(), - Strn(ref v) => { - if v.starts_with("\u{29e}") { - format!(":{}", &v[2..]) - } else if print_readably { - escape_str(v) - } else { - v.clone() - } - }, - List(ref v,_) => { - pr_list(v, _r, "(", ")", " ") - }, - Vector(ref v,_) => { - pr_list(v, _r, "[", "]", " ") - }, - Hash_Map(ref v,_) => { - let mut res = String::new(); - res.push_str("{"); - for (i, (key, value)) in v.iter().enumerate() { - if i != 0 { res.push_str(" "); } - if key.starts_with("\u{29e}") { - res.push_str(":"); - res.push_str(&key[2..]) - } else if print_readably { - res.push_str(&escape_str(key)) - } else { - res.push_str(key) - } - res.push_str(" "); - res.push_str(&value.pr_str(_r)); - } - res.push_str("}"); - res - }, - // TODO: better native function representation - Func(_, _) => format!("#"), - MalFunc(ref mf,_) => format!("(fn* {:?} {:?})", mf.params, mf.exp), - Atom(ref v) => format!("(atom {:?})", &**v.borrow()), - } - } - - pub fn apply(&self, args:Vec) -> MalRet { - match *self { - Func(f,_) => f(args), - MalFunc(ref mf,_) => { - let mfc = mf.clone(); - let alst = list(args); - let new_env = env_new(Some(mfc.env.clone())); - match env_bind(&new_env, mfc.params, alst) { - Ok(_) => (mfc.eval)(mfc.exp, new_env), - Err(e) => err_string(e), - } - }, - _ => err_str("attempt to call non-function"), - } - - } -} - -impl PartialEq for MalType { - fn eq(&self, other: &MalType) -> bool { - match (self, other) { - (&Nil, &Nil) | - (&True, &True) | - (&False, &False) => true, - (&Int(ref a), &Int(ref b)) => a == b, - (&Strn(ref a), &Strn(ref b)) => a == b, - (&Sym(ref a), &Sym(ref b)) => a == b, - (&List(ref a,_), &List(ref b,_)) | - (&Vector(ref a,_), &Vector(ref b,_)) | - (&List(ref a,_), &Vector(ref b,_)) | - (&Vector(ref a,_), &List(ref b,_)) => a == b, - (&Hash_Map(ref a,_), &Hash_Map(ref b,_)) => a == b, - // TODO: fix this - (&Func(_,_), &Func(_,_)) => false, - (&MalFunc(_,_), &MalFunc(_,_)) => false, - _ => return false, - } - } -} - -impl fmt::Debug for MalType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.pr_str(true)) - } -} - - -// Scalars -pub fn _nil() -> MalVal { Rc::new(Nil) } -pub fn nil_q(a:Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to nil? call"); - } - match *a[0].clone() { - Nil => Ok(_true()), - _ => Ok(_false()), - } -} - -pub fn _true() -> MalVal { Rc::new(True) } -pub fn true_q(a:Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to true? call"); - } - match *a[0].clone() { - True => Ok(_true()), - _ => Ok(_false()), - } -} - -pub fn _false() -> MalVal { Rc::new(False) } -pub fn false_q(a:Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to false? call"); - } - match *a[0].clone() { - False => Ok(_true()), - _ => Ok(_false()), - } -} - -pub fn string_q(a:Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to string? call"); - } - match *a[0].clone() { - Strn(ref s) => { - if s.starts_with("\u{29e}") { - Ok(_false()) - } else { - Ok(_true()) - } - }, - _ => Ok(_false()), - } -} - -pub fn _int(i: isize) -> MalVal { Rc::new(Int(i)) } -pub fn int_q(a:Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to number? call"); - } - match *a[0] { - Int(_) => Ok(_true()), - _ => Ok(_false()), - } -} - - -// Symbols -pub fn symbol(strn: &str) -> MalVal { Rc::new(Sym(strn.to_string())) } -pub fn _symbol(a: Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to symbol call"); - } - match *a[0].clone() { - Strn(ref s) => { - Ok(Rc::new(Sym(s.to_string()))) - }, - _ => return err_str("symbol called on non-string"), - } -} -pub fn symbol_q(a:Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to symbol? call"); - } - match *a[0].clone() { - Sym(_) => Ok(_true()), - _ => Ok(_false()), - } -} - -// Keywords -pub fn _keyword(a: Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to keyword call"); - } - match *a[0] { - Strn(ref s) => Ok(Rc::new(Strn(format!("\u{29e}{}", s)))), - _ => err_str("keyword called on non-string"), - } -} -pub fn keyword_q(a:Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to keyword? call"); - } - match *a[0].clone() { - Strn(ref s) => { - if s.starts_with("\u{29e}") { - Ok(_true()) - } else { - Ok(_false()) - } - }, - _ => Ok(_false()), - } -} - - -// Strings -pub fn strn(strn: &str) -> MalVal { Rc::new(Strn(strn.to_string())) } -pub fn string(strn: String) -> MalVal { Rc::new(Strn(strn)) } - -// Lists -pub fn list(seq: Vec) -> MalVal { Rc::new(List(seq,_nil())) } -pub fn listm(seq: Vec, meta: MalVal) -> MalVal { - Rc::new(List(seq,meta)) -} -pub fn listv(seq:Vec) -> MalRet { Ok(list(seq)) } -pub fn list_q(a:Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to list? call"); - } - match *a[0].clone() { - List(_,_) => Ok(_true()), - _ => Ok(_false()), - } -} - -// Vectors -pub fn vector(seq: Vec) -> MalVal { Rc::new(Vector(seq,_nil())) } -pub fn vectorm(seq: Vec, meta: MalVal) -> MalVal { - Rc::new(Vector(seq,meta)) -} -pub fn vectorv(seq: Vec) -> MalRet { Ok(vector(seq)) } -pub fn vector_q(a:Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to vector? call"); - } - match *a[0].clone() { - Vector(_,_) => Ok(_true()), - _ => Ok(_false()), - } -} - -// Hash Maps -pub fn hash_map(hm: HashMap) -> MalVal { - Rc::new(Hash_Map(hm,_nil())) -} -pub fn hash_mapm(hm: HashMap, meta: MalVal) -> MalVal { - Rc::new(Hash_Map(hm,meta)) -} -pub fn _assoc(hm: &HashMap, a:Vec) -> MalRet { - if a.len() % 2 == 1 { - return err_str("odd number of hash-map keys/values"); - } - let mut new_hm = hm.clone(); - let mut it = a.iter(); - loop { - let k = match it.next() { - Some(mv) => match *mv.clone() { - Strn(ref s) => s.to_string(), - _ => return err_str("key is not a string in hash-map call"), - }, - None => break, - }; - let v = it.next().unwrap(); - new_hm.insert(k, v.clone()); - } - Ok(Rc::new(Hash_Map(new_hm,_nil()))) -} -pub fn _dissoc(hm: &HashMap, a:Vec) -> MalRet { - let mut new_hm = hm.clone(); - let mut it = a.iter(); - loop { - let k = match it.next() { - Some(mv) => match *mv.clone() { - Strn(ref s) => s.to_string(), - _ => return err_str("key is not a string in hash-map call"), - }, - None => break, - }; - new_hm.remove(&k); - } - Ok(Rc::new(Hash_Map(new_hm,_nil()))) -} -pub fn hash_mapv(seq: Vec) -> MalRet { - let new_hm: HashMap = HashMap::new(); - _assoc(&new_hm, seq) -} -pub fn hash_map_q(a:Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to map? call"); - } - match *a[0].clone() { - Hash_Map(_,_) => Ok(_true()), - _ => Ok(_false()), - } -} - -// Functions -pub fn func(f: fn(Vec) -> MalRet) -> MalVal { - Rc::new(Func(f, _nil())) -} -pub fn funcm(f: fn(Vec) -> MalRet, meta: MalVal) -> MalVal { - Rc::new(Func(f, meta)) -} -pub fn malfunc(eval: fn(MalVal, Env) -> MalRet, - exp: MalVal, - env: Env, - params: MalVal, - meta: MalVal) -> MalVal { - Rc::new(MalFunc(MalFuncData{eval: eval, - exp: exp, - env: env, - params: params, - is_macro: false},meta)) -} -pub fn malfuncd(mfd: MalFuncData, meta: MalVal) -> MalVal { - Rc::new(MalFunc(mfd,meta)) -} -pub fn fn_q(a:Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to fn? call"); - } - match *a[0] { - Func(..) | MalFunc(MalFuncData { is_macro: false, .. }, _) => Ok(_true()), - _ => Ok(_false()), - } -} -pub fn macro_q(a:Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to macro? call"); - } - match *a[0] { - MalFunc(MalFuncData { is_macro: true, .. }, _) => Ok(_true()), - _ => Ok(_false()), - } -} - - -// Atoms -pub fn atom_q(a:Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to atom? call"); - } - match *a[0].clone() { - Atom(_) => Ok(_true()), - _ => Ok(_false()), - } -} -pub fn atom(a:Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to atom call"); - } - Ok(Rc::new(Atom(RefCell::new(a[0].clone())))) -} - - -// General functions -pub fn sequential_q(a:Vec) -> MalRet { - if a.len() != 1 { - return err_str("Wrong arity to sequential? call"); - } - match *a[0].clone() { - List(_,_) | Vector(_,_) => Ok(_true()), - _ => Ok(_false()), - } -} diff --git a/rust/step0_repl.rs b/rust/step0_repl.rs new file mode 100644 index 0000000000..18eb94ff0d --- /dev/null +++ b/rust/step0_repl.rs @@ -0,0 +1,33 @@ +extern crate rustyline; + +use rustyline::error::ReadlineError; +use rustyline::Editor; + +fn main() { + // `()` can be used when no completer is required + let mut rl = Editor::<()>::new(); + if rl.load_history(".mal-history").is_err() { + println!("No previous history."); + } + + loop { + let readline = rl.readline("user> "); + match readline { + Ok(line) => { + rl.add_history_entry(&line); + rl.save_history(".mal-history").unwrap(); + if line.len() > 0 { + println!("{}", line); + } + }, + Err(ReadlineError::Interrupted) => continue, + Err(ReadlineError::Eof) => break, + Err(err) => { + println!("Error: {:?}", err); + break + } + } + } +} + +// vim: ts=2:sw=2:expandtab diff --git a/rust/step1_read_print.rs b/rust/step1_read_print.rs new file mode 100644 index 0000000000..6d4c4f723b --- /dev/null +++ b/rust/step1_read_print.rs @@ -0,0 +1,53 @@ +#[macro_use] +extern crate lazy_static; +extern crate regex; +extern crate itertools; +extern crate fnv; + +extern crate rustyline; +use rustyline::error::ReadlineError; +use rustyline::Editor; + +#[macro_use] +#[allow(dead_code)] +mod types; +use types::{format_error}; +mod reader; +mod printer; +// TODO: figure out a way to avoid including env +#[allow(dead_code)] +mod env; + +fn main() { + // `()` can be used when no completer is required + let mut rl = Editor::<()>::new(); + if rl.load_history(".mal-history").is_err() { + println!("No previous history."); + } + + loop { + let readline = rl.readline("user> "); + match readline { + Ok(line) => { + rl.add_history_entry(&line); + rl.save_history(".mal-history").unwrap(); + if line.len() > 0 { + match reader::read_str(line) { + Ok(mv) => { + println!("{}", mv.pr_str(true)); + }, + Err(e) => println!("Error: {}", format_error(e)), + } + } + }, + Err(ReadlineError::Interrupted) => continue, + Err(ReadlineError::Eof) => break, + Err(err) => { + println!("Error: {:?}", err); + break + } + } + } +} + +// vim: ts=2:sw=2:expandtab diff --git a/rust/step2_eval.rs b/rust/step2_eval.rs new file mode 100644 index 0000000000..729cee4d52 --- /dev/null +++ b/rust/step2_eval.rs @@ -0,0 +1,135 @@ +use std::rc::Rc; +//use std::collections::HashMap; +use fnv::FnvHashMap; + +#[macro_use] +extern crate lazy_static; +extern crate regex; +extern crate itertools; +extern crate fnv; + +extern crate rustyline; +use rustyline::error::ReadlineError; +use rustyline::Editor; + +#[macro_use] +#[allow(dead_code)] +mod types; +use types::{MalVal,MalArgs,MalRet,MalErr,error,format_error,func}; +use types::MalVal::{Nil,Int,Sym,List,Vector,Hash}; +use types::MalErr::{ErrString}; +mod reader; +mod printer; +// TODO: figure out a way to avoid including env +#[allow(dead_code)] +mod env; + +pub type Env = FnvHashMap; + +// read +fn read(str: &str) -> MalRet { + reader::read_str(str.to_string()) +} + +// eval +fn eval_ast(ast: &MalVal, env: &Env) -> MalRet { + match ast { + Sym(sym) => { + Ok(env.get(sym) + .ok_or(ErrString(format!("'{}' not found", sym)))? + .clone()) + }, + List(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(list!(lst)) + }, + Vector(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(vector!(lst)) + }, + Hash(hm,_) => { + let mut new_hm: FnvHashMap = FnvHashMap::default(); + for (k,v) in hm.iter() { + new_hm.insert(k.to_string(), eval(v.clone(), env.clone())?); + } + Ok(Hash(Rc::new(new_hm),Rc::new(Nil))) + }, + _ => Ok(ast.clone()), + } +} + +fn eval(ast: MalVal, env: Env) -> MalRet { + match ast.clone() { + List(l,_) => { + if l.len() == 0 { return Ok(ast); } + match eval_ast(&ast, &env)? { + List(ref el,_) => { + let ref f = el[0].clone(); + f.apply(el[1..].to_vec()) + }, + _ => { + error("expected a list") + } + } + }, + _ => eval_ast(&ast, &env), + } +} + +// print +fn print(ast: &MalVal) -> String { + ast.pr_str(true) +} + +fn rep(str: &str, env: &Env) -> Result { + let ast = read(str)?; + let exp = eval(ast, env.clone())?; + Ok(print(&exp)) +} + +fn int_op(op: fn(i64, i64) -> i64, a:MalArgs) -> MalRet { + match (a[0].clone(), a[1].clone()) { + (Int(a0), Int(a1)) => Ok(Int(op(a0,a1))), + _ => error("invalid int_op args"), + } +} + +fn main() { + // `()` can be used when no completer is required + let mut rl = Editor::<()>::new(); + if rl.load_history(".mal-history").is_err() { + println!("No previous history."); + } + + let mut repl_env = Env::default(); + repl_env.insert("+".to_string(), func(|a:MalArgs|{int_op(|i,j|{i+j},a)})); + repl_env.insert("-".to_string(), func(|a:MalArgs|{int_op(|i,j|{i-j},a)})); + repl_env.insert("*".to_string(), func(|a:MalArgs|{int_op(|i,j|{i*j},a)})); + repl_env.insert("/".to_string(), func(|a:MalArgs|{int_op(|i,j|{i/j},a)})); + + loop { + let readline = rl.readline("user> "); + match readline { + Ok(line) => { + rl.add_history_entry(&line); + rl.save_history(".mal-history").unwrap(); + if line.len() > 0 { + match rep(&line, &repl_env) { + Ok(out) => println!("{}", out), + Err(e) => println!("Error: {}", format_error(e)), + } + } + }, + Err(ReadlineError::Interrupted) => continue, + Err(ReadlineError::Eof) => break, + Err(err) => { + println!("Error: {:?}", err); + break + } + } + } +} + +// vim: ts=2:sw=2:expandtab diff --git a/rust/step3_env.rs b/rust/step3_env.rs new file mode 100644 index 0000000000..24fffa03fb --- /dev/null +++ b/rust/step3_env.rs @@ -0,0 +1,159 @@ +use std::rc::Rc; +//use std::collections::HashMap; +use fnv::FnvHashMap; +use itertools::Itertools; + +#[macro_use] +extern crate lazy_static; +extern crate regex; +extern crate itertools; +extern crate fnv; + +extern crate rustyline; +use rustyline::error::ReadlineError; +use rustyline::Editor; + +#[macro_use] +#[allow(dead_code)] +mod types; +use types::{MalVal,MalArgs,MalRet,MalErr,error,format_error,func}; +use types::MalVal::{Nil,Int,Sym,List,Vector,Hash}; +mod reader; +mod printer; +mod env; +use env::{Env,env_new,env_get,env_set,env_sets}; + +// read +fn read(str: &str) -> MalRet { + reader::read_str(str.to_string()) +} + +// eval +fn eval_ast(ast: &MalVal, env: &Env) -> MalRet { + match ast { + Sym(_) => Ok(env_get(&env, &ast)?), + List(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(list!(lst)) + }, + Vector(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(vector!(lst)) + }, + Hash(hm,_) => { + let mut new_hm: FnvHashMap = FnvHashMap::default(); + for (k,v) in hm.iter() { + new_hm.insert(k.to_string(), eval(v.clone(), env.clone())?); + } + Ok(Hash(Rc::new(new_hm),Rc::new(Nil))) + }, + _ => Ok(ast.clone()), + } +} + +fn eval(ast: MalVal, env: Env) -> MalRet { + match ast.clone() { + List(l,_) => { + if l.len() == 0 { return Ok(ast); } + let a0 = &l[0]; + match a0 { + Sym(ref a0sym) if a0sym == "def!" => { + env_set(&env, l[1].clone(), eval(l[2].clone(), env.clone())?) + }, + Sym(ref a0sym) if a0sym == "let*" => { + let let_env = env_new(Some(env.clone())); + let (a1, a2) = (l[1].clone(), l[2].clone()); + match a1 { + List(ref binds,_) | Vector(ref binds,_) => { + for (b, e) in binds.iter().tuples() { + match b { + Sym(_) => { + let _ = env_set(&let_env, b.clone(), + eval(e.clone(), let_env.clone())?); + }, + _ => { + return error("let* with non-Sym binding"); + } + } + } + }, + _ => { + return error("let* with non-List bindings"); + } + }; + eval(a2, let_env) + }, + _ => { + match eval_ast(&ast, &env)? { + List(ref el,_) => { + let ref f = el[0].clone(); + f.apply(el[1..].to_vec()) + }, + _ => { + error("expected a list") + } + } + } + } + }, + _ => eval_ast(&ast, &env), + } +} + +// print +fn print(ast: &MalVal) -> String { + ast.pr_str(true) +} + +fn rep(str: &str, env: &Env) -> Result { + let ast = read(str)?; + let exp = eval(ast, env.clone())?; + Ok(print(&exp)) +} + +fn int_op(op: fn(i64, i64) -> i64, a:MalArgs) -> MalRet { + match (a[0].clone(), a[1].clone()) { + (Int(a0), Int(a1)) => Ok(Int(op(a0,a1))), + _ => error("invalid int_op args"), + } +} + +fn main() { + // `()` can be used when no completer is required + let mut rl = Editor::<()>::new(); + if rl.load_history(".mal-history").is_err() { + println!("No previous history."); + } + + let repl_env = env_new(None); + env_sets(&repl_env, "+", func(|a:MalArgs|{int_op(|i,j|{i+j},a)})); + env_sets(&repl_env, "-", func(|a:MalArgs|{int_op(|i,j|{i-j},a)})); + env_sets(&repl_env, "*", func(|a:MalArgs|{int_op(|i,j|{i*j},a)})); + env_sets(&repl_env, "/", func(|a:MalArgs|{int_op(|i,j|{i/j},a)})); + + loop { + let readline = rl.readline("user> "); + match readline { + Ok(line) => { + rl.add_history_entry(&line); + rl.save_history(".mal-history").unwrap(); + if line.len() > 0 { + match rep(&line, &repl_env) { + Ok(out) => println!("{}", out), + Err(e) => println!("Error: {}", format_error(e)), + } + } + }, + Err(ReadlineError::Interrupted) => continue, + Err(ReadlineError::Eof) => break, + Err(err) => { + println!("Error: {:?}", err); + break + } + } + } +} + +// vim: ts=2:sw=2:expandtab diff --git a/rust/step4_if_fn_do.rs b/rust/step4_if_fn_do.rs new file mode 100644 index 0000000000..9359e7fd22 --- /dev/null +++ b/rust/step4_if_fn_do.rs @@ -0,0 +1,182 @@ +use std::rc::Rc; +//use std::collections::HashMap; +use fnv::FnvHashMap; +use itertools::Itertools; + +#[macro_use] +extern crate lazy_static; +extern crate regex; +extern crate itertools; +extern crate fnv; + +extern crate rustyline; +use rustyline::error::ReadlineError; +use rustyline::Editor; + +#[macro_use] +mod types; +use types::{MalVal,MalArgs,MalRet,MalErr,error,format_error}; +use types::MalVal::{Nil,Bool,Sym,List,Vector,Hash,MalFunc}; +mod reader; +mod printer; +mod env; +use env::{Env,env_new,env_get,env_set,env_sets}; +#[macro_use] +mod core; + +// read +fn read(str: &str) -> MalRet { + reader::read_str(str.to_string()) +} + +// eval +fn eval_ast(ast: &MalVal, env: &Env) -> MalRet { + match ast { + Sym(_) => Ok(env_get(&env, &ast)?), + List(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(list!(lst)) + }, + Vector(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(vector!(lst)) + }, + Hash(hm,_) => { + let mut new_hm: FnvHashMap = FnvHashMap::default(); + for (k,v) in hm.iter() { + new_hm.insert(k.to_string(), eval(v.clone(), env.clone())?); + } + Ok(Hash(Rc::new(new_hm),Rc::new(Nil))) + }, + _ => Ok(ast.clone()), + } +} + +fn eval(ast: MalVal, env: Env) -> MalRet { + match ast.clone() { + List(l,_) => { + if l.len() == 0 { return Ok(ast); } + let a0 = &l[0]; + match a0 { + Sym(ref a0sym) if a0sym == "def!" => { + env_set(&env, l[1].clone(), eval(l[2].clone(), env.clone())?) + }, + Sym(ref a0sym) if a0sym == "let*" => { + let let_env = env_new(Some(env.clone())); + let (a1, a2) = (l[1].clone(), l[2].clone()); + match a1 { + List(ref binds,_) | Vector(ref binds,_) => { + for (b, e) in binds.iter().tuples() { + match b { + Sym(_) => { + let _ = env_set(&let_env, b.clone(), + eval(e.clone(), let_env.clone())?); + }, + _ => { + return error("let* with non-Sym binding"); + } + } + } + }, + _ => { + return error("let* with non-List bindings"); + } + }; + eval(a2, let_env) + }, + Sym(ref a0sym) if a0sym == "do" => { + match eval_ast(&list!(l[1..].to_vec()), &env)? { + List(el,_) => Ok(el.last().unwrap_or(&Nil).clone()), + _ => error("invalid do form"), + } + }, + Sym(ref a0sym) if a0sym == "if" => { + let cond = eval(l[1].clone(), env.clone())?; + match cond { + Bool(false) | Nil if l.len() >= 4 => { + eval(l[3].clone(), env.clone()) + }, + Bool(false) | Nil => Ok(Nil), + _ if l.len() >= 3 => { + eval(l[2].clone(), env.clone()) + }, + _ => Ok(Nil) + } + }, + Sym(ref a0sym) if a0sym == "fn*" => { + let (a1, a2) = (l[1].clone(), l[2].clone()); + Ok(MalFunc{eval: eval, ast: Rc::new(a2), env: env, + params: Rc::new(a1), is_macro: false, + meta: Rc::new(Nil)}) + }, + _ => { + match eval_ast(&ast, &env)? { + List(ref el,_) => { + let ref f = el[0].clone(); + f.apply(el[1..].to_vec()) + }, + _ => { + error("expected a list") + } + } + } + } + }, + _ => eval_ast(&ast, &env), + } +} + +// print +fn print(ast: &MalVal) -> String { + ast.pr_str(true) +} + +fn rep(str: &str, env: &Env) -> Result { + let ast = read(str)?; + let exp = eval(ast, env.clone())?; + Ok(print(&exp)) +} + +fn main() { + // `()` can be used when no completer is required + let mut rl = Editor::<()>::new(); + if rl.load_history(".mal-history").is_err() { + println!("No previous history."); + } + + // core.rs: defined using rust + let repl_env = env_new(None); + for (k, v) in core::ns() { + env_sets(&repl_env, k, v); + } + + // core.mal: defined using the language itself + let _ = rep("(def! not (fn* (a) (if a false true)))", &repl_env); + + // main repl loop + loop { + let readline = rl.readline("user> "); + match readline { + Ok(line) => { + rl.add_history_entry(&line); + rl.save_history(".mal-history").unwrap(); + if line.len() > 0 { + match rep(&line, &repl_env) { + Ok(out) => println!("{}", out), + Err(e) => println!("Error: {}", format_error(e)), + } + } + }, + Err(ReadlineError::Interrupted) => continue, + Err(ReadlineError::Eof) => break, + Err(err) => { + println!("Error: {:?}", err); + break + } + } + } +} + +// vim: ts=2:sw=2:expandtab diff --git a/rust/step5_tco.rs b/rust/step5_tco.rs new file mode 100644 index 0000000000..d483ff83bb --- /dev/null +++ b/rust/step5_tco.rs @@ -0,0 +1,209 @@ +use std::rc::Rc; +//use std::collections::HashMap; +use fnv::FnvHashMap; +use itertools::Itertools; + +#[macro_use] +extern crate lazy_static; +extern crate regex; +extern crate itertools; +extern crate fnv; + +extern crate rustyline; +use rustyline::error::ReadlineError; +use rustyline::Editor; + +#[macro_use] +mod types; +use types::{MalVal,MalArgs,MalRet,MalErr,error,format_error}; +use types::MalVal::{Nil,Bool,Sym,List,Vector,Hash,Func,MalFunc}; +mod reader; +mod printer; +mod env; +use env::{Env,env_new,env_bind,env_get,env_set,env_sets}; +#[macro_use] +mod core; + +// read +fn read(str: &str) -> MalRet { + reader::read_str(str.to_string()) +} + +// eval +fn eval_ast(ast: &MalVal, env: &Env) -> MalRet { + match ast { + Sym(_) => Ok(env_get(&env, &ast)?), + List(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(list!(lst)) + }, + Vector(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(vector!(lst)) + }, + Hash(hm,_) => { + let mut new_hm: FnvHashMap = FnvHashMap::default(); + for (k,v) in hm.iter() { + new_hm.insert(k.to_string(), eval(v.clone(), env.clone())?); + } + Ok(Hash(Rc::new(new_hm),Rc::new(Nil))) + }, + _ => Ok(ast.clone()), + } +} + +fn eval(mut ast: MalVal, mut env: Env) -> MalRet { + let ret: MalRet; + + 'tco: loop { + + ret = match ast.clone() { + List(l,_) => { + if l.len() == 0 { return Ok(ast); } + let a0 = &l[0]; + match a0 { + Sym(ref a0sym) if a0sym == "def!" => { + env_set(&env, l[1].clone(), eval(l[2].clone(), env.clone())?) + }, + Sym(ref a0sym) if a0sym == "let*" => { + env = env_new(Some(env.clone())); + let (a1, a2) = (l[1].clone(), l[2].clone()); + match a1 { + List(ref binds,_) | Vector(ref binds,_) => { + for (b, e) in binds.iter().tuples() { + match b { + Sym(_) => { + let _ = env_set(&env, b.clone(), + eval(e.clone(), env.clone())?); + }, + _ => { + return error("let* with non-Sym binding"); + } + } + } + }, + _ => { + return error("let* with non-List bindings"); + } + }; + ast = a2; + continue 'tco; + }, + Sym(ref a0sym) if a0sym == "do" => { + match eval_ast(&list!(l[1..l.len()-1].to_vec()), &env)? { + List(_,_) => { + ast = l.last().unwrap_or(&Nil).clone(); + continue 'tco; + }, + _ => error("invalid do form"), + } + }, + Sym(ref a0sym) if a0sym == "if" => { + let cond = eval(l[1].clone(), env.clone())?; + match cond { + Bool(false) | Nil if l.len() >= 4 => { + ast = l[3].clone(); + continue 'tco; + }, + Bool(false) | Nil => Ok(Nil), + _ if l.len() >= 3 => { + ast = l[2].clone(); + continue 'tco; + }, + _ => Ok(Nil) + } + }, + Sym(ref a0sym) if a0sym == "fn*" => { + let (a1, a2) = (l[1].clone(), l[2].clone()); + Ok(MalFunc{eval: eval, ast: Rc::new(a2), env: env, + params: Rc::new(a1), is_macro: false, + meta: Rc::new(Nil)}) + }, + _ => { + match eval_ast(&ast, &env)? { + List(ref el,_) => { + let ref f = el[0].clone(); + let args = el[1..].to_vec(); + match f { + Func(_,_) => f.apply(args), + MalFunc{ast: mast, env: menv, params, ..} => { + let a = &**mast; + let p = &**params; + env = env_bind(Some(menv.clone()), p.clone(), args)?; + ast = a.clone(); + continue 'tco; + }, + _ => error("attempt to call non-function"), + } + }, + _ => { + error("expected a list") + } + } + } + } + }, + _ => eval_ast(&ast, &env), + }; + + break; + + } // end 'tco loop + + ret +} + +// print +fn print(ast: &MalVal) -> String { + ast.pr_str(true) +} + +fn rep(str: &str, env: &Env) -> Result { + let ast = read(str)?; + let exp = eval(ast, env.clone())?; + Ok(print(&exp)) +} + +fn main() { + // `()` can be used when no completer is required + let mut rl = Editor::<()>::new(); + if rl.load_history(".mal-history").is_err() { + println!("No previous history."); + } + + // core.rs: defined using rust + let repl_env = env_new(None); + for (k, v) in core::ns() { + env_sets(&repl_env, k, v); + } + + // core.mal: defined using the language itself + let _ = rep("(def! not (fn* (a) (if a false true)))", &repl_env); + + // main repl loop + loop { + let readline = rl.readline("user> "); + match readline { + Ok(line) => { + rl.add_history_entry(&line); + rl.save_history(".mal-history").unwrap(); + if line.len() > 0 { + match rep(&line, &repl_env) { + Ok(out) => println!("{}", out), + Err(e) => println!("Error: {}", format_error(e)), + } + } + }, + Err(ReadlineError::Interrupted) => continue, + Err(ReadlineError::Eof) => break, + Err(err) => { + println!("Error: {:?}", err); + break + } + } + } +} + +// vim: ts=2:sw=2:expandtab diff --git a/rust/step6_file.rs b/rust/step6_file.rs new file mode 100644 index 0000000000..02cd45dff0 --- /dev/null +++ b/rust/step6_file.rs @@ -0,0 +1,232 @@ +use std::rc::Rc; +//use std::collections::HashMap; +use fnv::FnvHashMap; +use itertools::Itertools; + +#[macro_use] +extern crate lazy_static; +extern crate regex; +extern crate itertools; +extern crate fnv; + +extern crate rustyline; +use rustyline::error::ReadlineError; +use rustyline::Editor; + +#[macro_use] +mod types; +use types::{MalVal,MalArgs,MalRet,MalErr,error,format_error}; +use types::MalVal::{Nil,Bool,Str,Sym,List,Vector,Hash,Func,MalFunc}; +mod reader; +mod printer; +mod env; +use env::{Env,env_new,env_bind,env_get,env_set,env_sets}; +#[macro_use] +mod core; + +// read +fn read(str: &str) -> MalRet { + reader::read_str(str.to_string()) +} + +// eval +fn eval_ast(ast: &MalVal, env: &Env) -> MalRet { + match ast { + Sym(_) => Ok(env_get(&env, &ast)?), + List(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(list!(lst)) + }, + Vector(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(vector!(lst)) + }, + Hash(hm,_) => { + let mut new_hm: FnvHashMap = FnvHashMap::default(); + for (k,v) in hm.iter() { + new_hm.insert(k.to_string(), eval(v.clone(), env.clone())?); + } + Ok(Hash(Rc::new(new_hm),Rc::new(Nil))) + }, + _ => Ok(ast.clone()), + } +} + +fn eval(mut ast: MalVal, mut env: Env) -> MalRet { + let ret: MalRet; + + 'tco: loop { + + ret = match ast.clone() { + List(l,_) => { + if l.len() == 0 { return Ok(ast); } + let a0 = &l[0]; + match a0 { + Sym(ref a0sym) if a0sym == "def!" => { + env_set(&env, l[1].clone(), eval(l[2].clone(), env.clone())?) + }, + Sym(ref a0sym) if a0sym == "let*" => { + env = env_new(Some(env.clone())); + let (a1, a2) = (l[1].clone(), l[2].clone()); + match a1 { + List(ref binds,_) | Vector(ref binds,_) => { + for (b, e) in binds.iter().tuples() { + match b { + Sym(_) => { + let _ = env_set(&env, b.clone(), + eval(e.clone(), env.clone())?); + }, + _ => { + return error("let* with non-Sym binding"); + } + } + } + }, + _ => { + return error("let* with non-List bindings"); + } + }; + ast = a2; + continue 'tco; + }, + Sym(ref a0sym) if a0sym == "do" => { + match eval_ast(&list!(l[1..l.len()-1].to_vec()), &env)? { + List(_,_) => { + ast = l.last().unwrap_or(&Nil).clone(); + continue 'tco; + }, + _ => error("invalid do form"), + } + }, + Sym(ref a0sym) if a0sym == "if" => { + let cond = eval(l[1].clone(), env.clone())?; + match cond { + Bool(false) | Nil if l.len() >= 4 => { + ast = l[3].clone(); + continue 'tco; + }, + Bool(false) | Nil => Ok(Nil), + _ if l.len() >= 3 => { + ast = l[2].clone(); + continue 'tco; + }, + _ => Ok(Nil) + } + }, + Sym(ref a0sym) if a0sym == "fn*" => { + let (a1, a2) = (l[1].clone(), l[2].clone()); + Ok(MalFunc{eval: eval, ast: Rc::new(a2), env: env, + params: Rc::new(a1), is_macro: false, + meta: Rc::new(Nil)}) + }, + Sym(ref a0sym) if a0sym == "eval" => { + ast = eval(l[1].clone(), env.clone())?; + while let Some(ref e) = env.clone().outer { + env = e.clone(); + } + continue 'tco; + }, + _ => { + match eval_ast(&ast, &env)? { + List(ref el,_) => { + let ref f = el[0].clone(); + let args = el[1..].to_vec(); + match f { + Func(_,_) => f.apply(args), + MalFunc{ast: mast, env: menv, params, ..} => { + let a = &**mast; + let p = &**params; + env = env_bind(Some(menv.clone()), p.clone(), args)?; + ast = a.clone(); + continue 'tco; + }, + _ => error("attempt to call non-function"), + } + }, + _ => { + error("expected a list") + } + } + } + } + }, + _ => eval_ast(&ast, &env), + }; + + break; + + } // end 'tco loop + + ret +} + +// print +fn print(ast: &MalVal) -> String { + ast.pr_str(true) +} + +fn rep(str: &str, env: &Env) -> Result { + let ast = read(str)?; + let exp = eval(ast, env.clone())?; + Ok(print(&exp)) +} + +fn main() { + let mut args = std::env::args(); + let arg1 = args.nth(1); + + // `()` can be used when no completer is required + let mut rl = Editor::<()>::new(); + if rl.load_history(".mal-history").is_err() { + println!("No previous history."); + } + + // core.rs: defined using rust + let repl_env = env_new(None); + for (k, v) in core::ns() { + env_sets(&repl_env, k, v); + } + env_sets(&repl_env, "*ARGV*", list!(args.map(Str).collect())); + + // core.mal: defined using the language itself + let _ = rep("(def! not (fn* (a) (if a false true)))", &repl_env); + let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", &repl_env); + + // Invoked with arguments + if let Some(f) = arg1 { + match rep(&format!("(load-file \"{}\")",f), &repl_env) { + Ok(_) => std::process::exit(0), + Err(e) => { + println!("Error: {}", format_error(e)); + std::process::exit(1); + } + } + } + + // main repl loop + loop { + let readline = rl.readline("user> "); + match readline { + Ok(line) => { + rl.add_history_entry(&line); + rl.save_history(".mal-history").unwrap(); + if line.len() > 0 { + match rep(&line, &repl_env) { + Ok(out) => println!("{}", out), + Err(e) => println!("Error: {}", format_error(e)), + } + } + }, + Err(ReadlineError::Interrupted) => continue, + Err(ReadlineError::Eof) => break, + Err(err) => { + println!("Error: {:?}", err); + break + } + } + } +} + +// vim: ts=2:sw=2:expandtab diff --git a/rust/step7_quote.rs b/rust/step7_quote.rs new file mode 100644 index 0000000000..163b5852bd --- /dev/null +++ b/rust/step7_quote.rs @@ -0,0 +1,274 @@ +use std::rc::Rc; +//use std::collections::HashMap; +use fnv::FnvHashMap; +use itertools::Itertools; + +#[macro_use] +extern crate lazy_static; +extern crate regex; +extern crate itertools; +extern crate fnv; + +extern crate rustyline; +use rustyline::error::ReadlineError; +use rustyline::Editor; + +#[macro_use] +mod types; +use types::{MalVal,MalArgs,MalRet,MalErr,error,format_error}; +use types::MalVal::{Nil,Bool,Str,Sym,List,Vector,Hash,Func,MalFunc}; +mod reader; +mod printer; +mod env; +use env::{Env,env_new,env_bind,env_get,env_set,env_sets}; +#[macro_use] +mod core; + +// read +fn read(str: &str) -> MalRet { + reader::read_str(str.to_string()) +} + +// eval +fn quasiquote(ast: &MalVal) -> MalVal { + match ast { + List(ref v,_) | Vector(ref v,_) if v.len() > 0 => { + let a0 = &v[0]; + match a0 { + Sym(ref s) if s == "unquote" => v[1].clone(), + _ => { + match a0 { + List(ref v0,_) | Vector(ref v0,_) if v0.len() > 0 => { + match v0[0] { + Sym(ref s) if s == "splice-unquote" => { + list![Sym("concat".to_string()), + v0[1].clone(), + quasiquote(&list!(v[1..].to_vec()))] + }, + _ => { + list![Sym("cons".to_string()), + quasiquote(a0), + quasiquote(&list!(v[1..].to_vec()))] + }, + } + }, + _ => { + list![Sym("cons".to_string()), + quasiquote(a0), + quasiquote(&list!(v[1..].to_vec()))] + } + } + } + } + }, + _ => list![Sym("quote".to_string()), ast.clone()] + } +} + +fn eval_ast(ast: &MalVal, env: &Env) -> MalRet { + match ast { + Sym(_) => Ok(env_get(&env, &ast)?), + List(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(list!(lst)) + }, + Vector(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(vector!(lst)) + }, + Hash(hm,_) => { + let mut new_hm: FnvHashMap = FnvHashMap::default(); + for (k,v) in hm.iter() { + new_hm.insert(k.to_string(), eval(v.clone(), env.clone())?); + } + Ok(Hash(Rc::new(new_hm),Rc::new(Nil))) + }, + _ => Ok(ast.clone()), + } +} + +fn eval(mut ast: MalVal, mut env: Env) -> MalRet { + let ret: MalRet; + + 'tco: loop { + + ret = match ast.clone() { + List(l,_) => { + if l.len() == 0 { return Ok(ast); } + let a0 = &l[0]; + match a0 { + Sym(ref a0sym) if a0sym == "def!" => { + env_set(&env, l[1].clone(), eval(l[2].clone(), env.clone())?) + }, + Sym(ref a0sym) if a0sym == "let*" => { + env = env_new(Some(env.clone())); + let (a1, a2) = (l[1].clone(), l[2].clone()); + match a1 { + List(ref binds,_) | Vector(ref binds,_) => { + for (b, e) in binds.iter().tuples() { + match b { + Sym(_) => { + let _ = env_set(&env, b.clone(), + eval(e.clone(), env.clone())?); + }, + _ => { + return error("let* with non-Sym binding"); + } + } + } + }, + _ => { + return error("let* with non-List bindings"); + } + }; + ast = a2; + continue 'tco; + }, + Sym(ref a0sym) if a0sym == "quote" => { + Ok(l[1].clone()) + }, + Sym(ref a0sym) if a0sym == "quasiquote" => { + ast = quasiquote(&l[1]); + continue 'tco; + }, + Sym(ref a0sym) if a0sym == "do" => { + match eval_ast(&list!(l[1..l.len()-1].to_vec()), &env)? { + List(_,_) => { + ast = l.last().unwrap_or(&Nil).clone(); + continue 'tco; + }, + _ => error("invalid do form"), + } + }, + Sym(ref a0sym) if a0sym == "if" => { + let cond = eval(l[1].clone(), env.clone())?; + match cond { + Bool(false) | Nil if l.len() >= 4 => { + ast = l[3].clone(); + continue 'tco; + }, + Bool(false) | Nil => Ok(Nil), + _ if l.len() >= 3 => { + ast = l[2].clone(); + continue 'tco; + }, + _ => Ok(Nil) + } + }, + Sym(ref a0sym) if a0sym == "fn*" => { + let (a1, a2) = (l[1].clone(), l[2].clone()); + Ok(MalFunc{eval: eval, ast: Rc::new(a2), env: env, + params: Rc::new(a1), is_macro: false, + meta: Rc::new(Nil)}) + }, + Sym(ref a0sym) if a0sym == "eval" => { + ast = eval(l[1].clone(), env.clone())?; + while let Some(ref e) = env.clone().outer { + env = e.clone(); + } + continue 'tco; + }, + _ => { + match eval_ast(&ast, &env)? { + List(ref el,_) => { + let ref f = el[0].clone(); + let args = el[1..].to_vec(); + match f { + Func(_,_) => f.apply(args), + MalFunc{ast: mast, env: menv, params, ..} => { + let a = &**mast; + let p = &**params; + env = env_bind(Some(menv.clone()), p.clone(), args)?; + ast = a.clone(); + continue 'tco; + }, + _ => error("attempt to call non-function"), + } + }, + _ => { + error("expected a list") + } + } + } + } + }, + _ => eval_ast(&ast, &env), + }; + + break; + + } // end 'tco loop + + ret +} + +// print +fn print(ast: &MalVal) -> String { + ast.pr_str(true) +} + +fn rep(str: &str, env: &Env) -> Result { + let ast = read(str)?; + let exp = eval(ast, env.clone())?; + Ok(print(&exp)) +} + +fn main() { + let mut args = std::env::args(); + let arg1 = args.nth(1); + + // `()` can be used when no completer is required + let mut rl = Editor::<()>::new(); + if rl.load_history(".mal-history").is_err() { + println!("No previous history."); + } + + // core.rs: defined using rust + let repl_env = env_new(None); + for (k, v) in core::ns() { + env_sets(&repl_env, k, v); + } + env_sets(&repl_env, "*ARGV*", list!(args.map(Str).collect())); + + // core.mal: defined using the language itself + let _ = rep("(def! not (fn* (a) (if a false true)))", &repl_env); + let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", &repl_env); + + // Invoked with arguments + if let Some(f) = arg1 { + match rep(&format!("(load-file \"{}\")",f), &repl_env) { + Ok(_) => std::process::exit(0), + Err(e) => { + println!("Error: {}", format_error(e)); + std::process::exit(1); + } + } + } + + // main repl loop + loop { + let readline = rl.readline("user> "); + match readline { + Ok(line) => { + rl.add_history_entry(&line); + rl.save_history(".mal-history").unwrap(); + if line.len() > 0 { + match rep(&line, &repl_env) { + Ok(out) => println!("{}", out), + Err(e) => println!("Error: {}", format_error(e)), + } + } + }, + Err(ReadlineError::Interrupted) => continue, + Err(ReadlineError::Eof) => break, + Err(err) => { + println!("Error: {:?}", err); + break + } + } + } +} + +// vim: ts=2:sw=2:expandtab diff --git a/rust/step8_macros.rs b/rust/step8_macros.rs new file mode 100644 index 0000000000..70e03fe73e --- /dev/null +++ b/rust/step8_macros.rs @@ -0,0 +1,343 @@ +use std::rc::Rc; +//use std::collections::HashMap; +use fnv::FnvHashMap; +use itertools::Itertools; + +#[macro_use] +extern crate lazy_static; +extern crate regex; +extern crate itertools; +extern crate fnv; + +extern crate rustyline; +use rustyline::error::ReadlineError; +use rustyline::Editor; + +#[macro_use] +mod types; +use types::{MalVal,MalArgs,MalRet,MalErr,error,format_error}; +use types::MalVal::{Nil,Bool,Str,Sym,List,Vector,Hash,Func,MalFunc}; +mod reader; +mod printer; +mod env; +use env::{Env,env_new,env_bind,env_find,env_get,env_set,env_sets}; +#[macro_use] +mod core; + +// read +fn read(str: &str) -> MalRet { + reader::read_str(str.to_string()) +} + +// eval +fn quasiquote(ast: &MalVal) -> MalVal { + match ast { + List(ref v,_) | Vector(ref v,_) if v.len() > 0 => { + let a0 = &v[0]; + match a0 { + Sym(ref s) if s == "unquote" => v[1].clone(), + _ => { + match a0 { + List(ref v0,_) | Vector(ref v0,_) if v0.len() > 0 => { + match v0[0] { + Sym(ref s) if s == "splice-unquote" => { + list![Sym("concat".to_string()), + v0[1].clone(), + quasiquote(&list!(v[1..].to_vec()))] + }, + _ => { + list![Sym("cons".to_string()), + quasiquote(a0), + quasiquote(&list!(v[1..].to_vec()))] + }, + } + }, + _ => { + list![Sym("cons".to_string()), + quasiquote(a0), + quasiquote(&list!(v[1..].to_vec()))] + } + } + } + } + }, + _ => list![Sym("quote".to_string()), ast.clone()] + } +} + +fn is_macro_call(ast: &MalVal, env: &Env) -> Option<(MalVal,MalArgs)> { + match ast { + List(v,_) => { + match v[0] { + Sym(ref s) => { + match env_find(env, s) { + Some(e) => { + match env_get(&e, &v[0]) { + Ok(f @ MalFunc{is_macro: true, ..}) => { + Some((f, v[1..].to_vec())) + }, + _ => None, + } + }, + _ => None, + } + }, + _ => None, + } + }, + _ => None, + } +} + +fn macroexpand(mut ast: MalVal, env: &Env) -> (bool, MalRet) { + let mut was_expanded = false; + while let Some((mf, args)) = is_macro_call(&ast, env) { + //println!("macroexpand 1: {:?}", ast); + ast = match mf.apply(args) { + Err(e) => return (false, Err(e)), + Ok(a) => a, + }; + //println!("macroexpand 2: {:?}", ast); + was_expanded = true; + } + ((was_expanded, Ok(ast))) +} + +fn eval_ast(ast: &MalVal, env: &Env) -> MalRet { + match ast { + Sym(_) => Ok(env_get(&env, &ast)?), + List(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(list!(lst)) + }, + Vector(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(vector!(lst)) + }, + Hash(hm,_) => { + let mut new_hm: FnvHashMap = FnvHashMap::default(); + for (k,v) in hm.iter() { + new_hm.insert(k.to_string(), eval(v.clone(), env.clone())?); + } + Ok(Hash(Rc::new(new_hm),Rc::new(Nil))) + }, + _ => Ok(ast.clone()), + } +} + +fn eval(mut ast: MalVal, mut env: Env) -> MalRet { + let ret: MalRet; + + 'tco: loop { + + ret = match ast.clone() { + List(l,_) => { + match macroexpand(ast.clone(), &env) { + (true, Ok(new_ast)) => { + ast = new_ast; + continue 'tco; + } + (_, Err(e)) => return Err(e), + _ => (), + } + + if l.len() == 0 { return Ok(ast); } + let a0 = &l[0]; + match a0 { + Sym(ref a0sym) if a0sym == "def!" => { + env_set(&env, l[1].clone(), eval(l[2].clone(), env.clone())?) + }, + Sym(ref a0sym) if a0sym == "let*" => { + env = env_new(Some(env.clone())); + let (a1, a2) = (l[1].clone(), l[2].clone()); + match a1 { + List(ref binds,_) | Vector(ref binds,_) => { + for (b, e) in binds.iter().tuples() { + match b { + Sym(_) => { + let _ = env_set(&env, b.clone(), + eval(e.clone(), env.clone())?); + }, + _ => { + return error("let* with non-Sym binding"); + } + } + } + }, + _ => { + return error("let* with non-List bindings"); + } + }; + ast = a2; + continue 'tco; + }, + Sym(ref a0sym) if a0sym == "quote" => { + Ok(l[1].clone()) + }, + Sym(ref a0sym) if a0sym == "quasiquote" => { + ast = quasiquote(&l[1]); + continue 'tco; + }, + Sym(ref a0sym) if a0sym == "defmacro!" => { + let (a1, a2) = (l[1].clone(), l[2].clone()); + let r = eval(a2, env.clone())?; + match r { + MalFunc{eval, ast, env, params, ..} => { + Ok(env_set(&env, a1.clone(), + MalFunc{eval: eval, ast: ast.clone(), env: env.clone(), + params: params.clone(), is_macro: true, + meta: Rc::new(Nil)})?) + }, + _ => error("set_macro on non-function"), + } + }, + Sym(ref a0sym) if a0sym == "macroexpand" => { + match macroexpand(l[1].clone(), &env) { + (_, Ok(new_ast)) => Ok(new_ast), + (_, e) => return e, + } + }, + Sym(ref a0sym) if a0sym == "do" => { + match eval_ast(&list!(l[1..l.len()-1].to_vec()), &env)? { + List(_,_) => { + ast = l.last().unwrap_or(&Nil).clone(); + continue 'tco; + }, + _ => error("invalid do form"), + } + }, + Sym(ref a0sym) if a0sym == "if" => { + let cond = eval(l[1].clone(), env.clone())?; + match cond { + Bool(false) | Nil if l.len() >= 4 => { + ast = l[3].clone(); + continue 'tco; + }, + Bool(false) | Nil => Ok(Nil), + _ if l.len() >= 3 => { + ast = l[2].clone(); + continue 'tco; + }, + _ => Ok(Nil) + } + }, + Sym(ref a0sym) if a0sym == "fn*" => { + let (a1, a2) = (l[1].clone(), l[2].clone()); + Ok(MalFunc{eval: eval, ast: Rc::new(a2), env: env, + params: Rc::new(a1), is_macro: false, + meta: Rc::new(Nil)}) + }, + Sym(ref a0sym) if a0sym == "eval" => { + ast = eval(l[1].clone(), env.clone())?; + while let Some(ref e) = env.clone().outer { + env = e.clone(); + } + continue 'tco; + }, + _ => { + match eval_ast(&ast, &env)? { + List(ref el,_) => { + let ref f = el[0].clone(); + let args = el[1..].to_vec(); + match f { + Func(_,_) => f.apply(args), + MalFunc{ast: mast, env: menv, params, ..} => { + let a = &**mast; + let p = &**params; + env = env_bind(Some(menv.clone()), p.clone(), args)?; + ast = a.clone(); + continue 'tco; + }, + _ => error("attempt to call non-function"), + } + }, + _ => { + error("expected a list") + } + } + } + } + }, + _ => eval_ast(&ast, &env), + }; + + break; + + } // end 'tco loop + + ret +} + +// print +fn print(ast: &MalVal) -> String { + ast.pr_str(true) +} + +fn rep(str: &str, env: &Env) -> Result { + let ast = read(str)?; + let exp = eval(ast, env.clone())?; + Ok(print(&exp)) +} + +fn main() { + let mut args = std::env::args(); + let arg1 = args.nth(1); + + // `()` can be used when no completer is required + let mut rl = Editor::<()>::new(); + if rl.load_history(".mal-history").is_err() { + println!("No previous history."); + } + + // core.rs: defined using rust + let repl_env = env_new(None); + for (k, v) in core::ns() { + env_sets(&repl_env, k, v); + } + env_sets(&repl_env, "*ARGV*", list!(args.map(Str).collect())); + + // core.mal: defined using the language itself + let _ = rep("(def! not (fn* (a) (if a false true)))", &repl_env); + let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", &repl_env); + let _ = rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", &repl_env); + let _ = rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", &repl_env); + + + // Invoked with arguments + if let Some(f) = arg1 { + match rep(&format!("(load-file \"{}\")",f), &repl_env) { + Ok(_) => std::process::exit(0), + Err(e) => { + println!("Error: {}", format_error(e)); + std::process::exit(1); + } + } + } + + // main repl loop + loop { + let readline = rl.readline("user> "); + match readline { + Ok(line) => { + rl.add_history_entry(&line); + rl.save_history(".mal-history").unwrap(); + if line.len() > 0 { + match rep(&line, &repl_env) { + Ok(out) => println!("{}", out), + Err(e) => println!("Error: {}", format_error(e)), + } + } + }, + Err(ReadlineError::Interrupted) => continue, + Err(ReadlineError::Eof) => break, + Err(err) => { + println!("Error: {:?}", err); + break + } + } + } +} + +// vim: ts=2:sw=2:expandtab diff --git a/rust/step9_try.rs b/rust/step9_try.rs new file mode 100644 index 0000000000..5e362147da --- /dev/null +++ b/rust/step9_try.rs @@ -0,0 +1,364 @@ +use std::rc::Rc; +//use std::collections::HashMap; +use fnv::FnvHashMap; +use itertools::Itertools; + +#[macro_use] +extern crate lazy_static; +extern crate regex; +extern crate itertools; +extern crate fnv; + +extern crate rustyline; +use rustyline::error::ReadlineError; +use rustyline::Editor; + +#[macro_use] +mod types; +use types::{MalVal,MalArgs,MalRet,MalErr,error,format_error}; +use types::MalVal::{Nil,Bool,Str,Sym,List,Vector,Hash,Func,MalFunc}; +use types::MalErr::{ErrString,ErrMalVal}; +mod reader; +mod printer; +mod env; +use env::{Env,env_new,env_bind,env_find,env_get,env_set,env_sets}; +#[macro_use] +mod core; + +// read +fn read(str: &str) -> MalRet { + reader::read_str(str.to_string()) +} + +// eval +fn quasiquote(ast: &MalVal) -> MalVal { + match ast { + List(ref v,_) | Vector(ref v,_) if v.len() > 0 => { + let a0 = &v[0]; + match a0 { + Sym(ref s) if s == "unquote" => v[1].clone(), + _ => { + match a0 { + List(ref v0,_) | Vector(ref v0,_) if v0.len() > 0 => { + match v0[0] { + Sym(ref s) if s == "splice-unquote" => { + list![Sym("concat".to_string()), + v0[1].clone(), + quasiquote(&list!(v[1..].to_vec()))] + }, + _ => { + list![Sym("cons".to_string()), + quasiquote(a0), + quasiquote(&list!(v[1..].to_vec()))] + }, + } + }, + _ => { + list![Sym("cons".to_string()), + quasiquote(a0), + quasiquote(&list!(v[1..].to_vec()))] + } + } + } + } + }, + _ => list![Sym("quote".to_string()), ast.clone()] + } +} + +fn is_macro_call(ast: &MalVal, env: &Env) -> Option<(MalVal,MalArgs)> { + match ast { + List(v,_) => { + match v[0] { + Sym(ref s) => { + match env_find(env, s) { + Some(e) => { + match env_get(&e, &v[0]) { + Ok(f @ MalFunc{is_macro: true, ..}) => { + Some((f, v[1..].to_vec())) + }, + _ => None, + } + }, + _ => None, + } + }, + _ => None, + } + }, + _ => None, + } +} + +fn macroexpand(mut ast: MalVal, env: &Env) -> (bool, MalRet) { + let mut was_expanded = false; + while let Some((mf, args)) = is_macro_call(&ast, env) { + //println!("macroexpand 1: {:?}", ast); + ast = match mf.apply(args) { + Err(e) => return (false, Err(e)), + Ok(a) => a, + }; + //println!("macroexpand 2: {:?}", ast); + was_expanded = true; + } + ((was_expanded, Ok(ast))) +} + +fn eval_ast(ast: &MalVal, env: &Env) -> MalRet { + match ast { + Sym(_) => Ok(env_get(&env, &ast)?), + List(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(list!(lst)) + }, + Vector(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(vector!(lst)) + }, + Hash(hm,_) => { + let mut new_hm: FnvHashMap = FnvHashMap::default(); + for (k,v) in hm.iter() { + new_hm.insert(k.to_string(), eval(v.clone(), env.clone())?); + } + Ok(Hash(Rc::new(new_hm),Rc::new(Nil))) + }, + _ => Ok(ast.clone()), + } +} + +fn eval(mut ast: MalVal, mut env: Env) -> MalRet { + let ret: MalRet; + + 'tco: loop { + + ret = match ast.clone() { + List(l,_) => { + match macroexpand(ast.clone(), &env) { + (true, Ok(new_ast)) => { + ast = new_ast; + continue 'tco; + } + (_, Err(e)) => return Err(e), + _ => (), + } + + if l.len() == 0 { return Ok(ast); } + let a0 = &l[0]; + match a0 { + Sym(ref a0sym) if a0sym == "def!" => { + env_set(&env, l[1].clone(), eval(l[2].clone(), env.clone())?) + }, + Sym(ref a0sym) if a0sym == "let*" => { + env = env_new(Some(env.clone())); + let (a1, a2) = (l[1].clone(), l[2].clone()); + match a1 { + List(ref binds,_) | Vector(ref binds,_) => { + for (b, e) in binds.iter().tuples() { + match b { + Sym(_) => { + let _ = env_set(&env, b.clone(), + eval(e.clone(), env.clone())?); + }, + _ => { + return error("let* with non-Sym binding"); + } + } + } + }, + _ => { + return error("let* with non-List bindings"); + } + }; + ast = a2; + continue 'tco; + }, + Sym(ref a0sym) if a0sym == "quote" => { + Ok(l[1].clone()) + }, + Sym(ref a0sym) if a0sym == "quasiquote" => { + ast = quasiquote(&l[1]); + continue 'tco; + }, + Sym(ref a0sym) if a0sym == "defmacro!" => { + let (a1, a2) = (l[1].clone(), l[2].clone()); + let r = eval(a2, env.clone())?; + match r { + MalFunc{eval, ast, env, params, ..} => { + Ok(env_set(&env, a1.clone(), + MalFunc{eval: eval, ast: ast.clone(), env: env.clone(), + params: params.clone(), is_macro: true, + meta: Rc::new(Nil)})?) + }, + _ => error("set_macro on non-function"), + } + }, + Sym(ref a0sym) if a0sym == "macroexpand" => { + match macroexpand(l[1].clone(), &env) { + (_, Ok(new_ast)) => Ok(new_ast), + (_, e) => return e, + } + }, + Sym(ref a0sym) if a0sym == "try*" => { + match eval(l[1].clone(), env.clone()) { + Err(ref e) if l.len() >= 3 => { + let exc = match e { + ErrMalVal(mv) => mv.clone(), + ErrString(s) => Str(s.to_string()), + }; + match l[2].clone() { + List(c,_) => { + let catch_env = env_bind(Some(env.clone()), + list!(vec![c[1].clone()]), + vec![exc])?; + eval(c[2].clone(), catch_env) + }, + _ => error("invalid catch block"), + } + }, + res => res, + } + }, + Sym(ref a0sym) if a0sym == "do" => { + match eval_ast(&list!(l[1..l.len()-1].to_vec()), &env)? { + List(_,_) => { + ast = l.last().unwrap_or(&Nil).clone(); + continue 'tco; + }, + _ => error("invalid do form"), + } + }, + Sym(ref a0sym) if a0sym == "if" => { + let cond = eval(l[1].clone(), env.clone())?; + match cond { + Bool(false) | Nil if l.len() >= 4 => { + ast = l[3].clone(); + continue 'tco; + }, + Bool(false) | Nil => Ok(Nil), + _ if l.len() >= 3 => { + ast = l[2].clone(); + continue 'tco; + }, + _ => Ok(Nil) + } + }, + Sym(ref a0sym) if a0sym == "fn*" => { + let (a1, a2) = (l[1].clone(), l[2].clone()); + Ok(MalFunc{eval: eval, ast: Rc::new(a2), env: env, + params: Rc::new(a1), is_macro: false, + meta: Rc::new(Nil)}) + }, + Sym(ref a0sym) if a0sym == "eval" => { + ast = eval(l[1].clone(), env.clone())?; + while let Some(ref e) = env.clone().outer { + env = e.clone(); + } + continue 'tco; + }, + _ => { + match eval_ast(&ast, &env)? { + List(ref el,_) => { + let ref f = el[0].clone(); + let args = el[1..].to_vec(); + match f { + Func(_,_) => f.apply(args), + MalFunc{ast: mast, env: menv, params, ..} => { + let a = &**mast; + let p = &**params; + env = env_bind(Some(menv.clone()), p.clone(), args)?; + ast = a.clone(); + continue 'tco; + }, + _ => error("attempt to call non-function"), + } + }, + _ => { + error("expected a list") + } + } + } + } + }, + _ => eval_ast(&ast, &env), + }; + + break; + + } // end 'tco loop + + ret +} + +// print +fn print(ast: &MalVal) -> String { + ast.pr_str(true) +} + +fn rep(str: &str, env: &Env) -> Result { + let ast = read(str)?; + let exp = eval(ast, env.clone())?; + Ok(print(&exp)) +} + +fn main() { + let mut args = std::env::args(); + let arg1 = args.nth(1); + + // `()` can be used when no completer is required + let mut rl = Editor::<()>::new(); + if rl.load_history(".mal-history").is_err() { + println!("No previous history."); + } + + // core.rs: defined using rust + let repl_env = env_new(None); + for (k, v) in core::ns() { + env_sets(&repl_env, k, v); + } + env_sets(&repl_env, "*ARGV*", list!(args.map(Str).collect())); + + // core.mal: defined using the language itself + let _ = rep("(def! not (fn* (a) (if a false true)))", &repl_env); + let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", &repl_env); + let _ = rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", &repl_env); + let _ = rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", &repl_env); + + + // Invoked with arguments + if let Some(f) = arg1 { + match rep(&format!("(load-file \"{}\")",f), &repl_env) { + Ok(_) => std::process::exit(0), + Err(e) => { + println!("Error: {}", format_error(e)); + std::process::exit(1); + } + } + } + + // main repl loop + loop { + let readline = rl.readline("user> "); + match readline { + Ok(line) => { + rl.add_history_entry(&line); + rl.save_history(".mal-history").unwrap(); + if line.len() > 0 { + match rep(&line, &repl_env) { + Ok(out) => println!("{}", out), + Err(e) => println!("Error: {}", format_error(e)), + } + } + }, + Err(ReadlineError::Interrupted) => continue, + Err(ReadlineError::Eof) => break, + Err(err) => { + println!("Error: {:?}", err); + break + } + } + } +} + +// vim: ts=2:sw=2:expandtab diff --git a/rust/stepA_mal.rs b/rust/stepA_mal.rs new file mode 100644 index 0000000000..89e9135f65 --- /dev/null +++ b/rust/stepA_mal.rs @@ -0,0 +1,370 @@ +#![allow(non_snake_case)] + +use std::rc::Rc; +//use std::collections::HashMap; +use fnv::FnvHashMap; +use itertools::Itertools; + +#[macro_use] +extern crate lazy_static; +extern crate regex; +extern crate itertools; +extern crate fnv; + +extern crate rustyline; +use rustyline::error::ReadlineError; +use rustyline::Editor; + +#[macro_use] +mod types; +use types::{MalVal,MalArgs,MalRet,MalErr,error,format_error}; +use types::MalVal::{Nil,Bool,Str,Sym,List,Vector,Hash,Func,MalFunc}; +use types::MalErr::{ErrString,ErrMalVal}; +mod reader; +mod printer; +mod env; +use env::{Env,env_new,env_bind,env_find,env_get,env_set,env_sets}; +#[macro_use] +mod core; + +// read +fn read(str: &str) -> MalRet { + reader::read_str(str.to_string()) +} + +// eval +fn quasiquote(ast: &MalVal) -> MalVal { + match ast { + List(ref v,_) | Vector(ref v,_) if v.len() > 0 => { + let a0 = &v[0]; + match a0 { + Sym(ref s) if s == "unquote" => v[1].clone(), + _ => { + match a0 { + List(ref v0,_) | Vector(ref v0,_) if v0.len() > 0 => { + match v0[0] { + Sym(ref s) if s == "splice-unquote" => { + list![Sym("concat".to_string()), + v0[1].clone(), + quasiquote(&list!(v[1..].to_vec()))] + }, + _ => { + list![Sym("cons".to_string()), + quasiquote(a0), + quasiquote(&list!(v[1..].to_vec()))] + }, + } + }, + _ => { + list![Sym("cons".to_string()), + quasiquote(a0), + quasiquote(&list!(v[1..].to_vec()))] + } + } + } + } + }, + _ => list![Sym("quote".to_string()), ast.clone()] + } +} + +fn is_macro_call(ast: &MalVal, env: &Env) -> Option<(MalVal,MalArgs)> { + match ast { + List(v,_) => { + match v[0] { + Sym(ref s) => { + match env_find(env, s) { + Some(e) => { + match env_get(&e, &v[0]) { + Ok(f @ MalFunc{is_macro: true, ..}) => { + Some((f, v[1..].to_vec())) + }, + _ => None, + } + }, + _ => None, + } + }, + _ => None, + } + }, + _ => None, + } +} + +fn macroexpand(mut ast: MalVal, env: &Env) -> (bool, MalRet) { + let mut was_expanded = false; + while let Some((mf, args)) = is_macro_call(&ast, env) { + //println!("macroexpand 1: {:?}", ast); + ast = match mf.apply(args) { + Err(e) => return (false, Err(e)), + Ok(a) => a, + }; + //println!("macroexpand 2: {:?}", ast); + was_expanded = true; + } + ((was_expanded, Ok(ast))) +} + +fn eval_ast(ast: &MalVal, env: &Env) -> MalRet { + match ast { + Sym(_) => Ok(env_get(&env, &ast)?), + List(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(list!(lst)) + }, + Vector(v,_) => { + let mut lst: MalArgs = vec![]; + for a in v.iter() { lst.push(eval(a.clone(), env.clone())?) } + Ok(vector!(lst)) + }, + Hash(hm,_) => { + let mut new_hm: FnvHashMap = FnvHashMap::default(); + for (k,v) in hm.iter() { + new_hm.insert(k.to_string(), eval(v.clone(), env.clone())?); + } + Ok(Hash(Rc::new(new_hm),Rc::new(Nil))) + }, + _ => Ok(ast.clone()), + } +} + +fn eval(mut ast: MalVal, mut env: Env) -> MalRet { + let ret: MalRet; + + 'tco: loop { + + ret = match ast.clone() { + List(l,_) => { + match macroexpand(ast.clone(), &env) { + (true, Ok(new_ast)) => { + ast = new_ast; + continue 'tco; + } + (_, Err(e)) => return Err(e), + _ => (), + } + + if l.len() == 0 { return Ok(ast); } + let a0 = &l[0]; + match a0 { + Sym(ref a0sym) if a0sym == "def!" => { + env_set(&env, l[1].clone(), eval(l[2].clone(), env.clone())?) + }, + Sym(ref a0sym) if a0sym == "let*" => { + env = env_new(Some(env.clone())); + let (a1, a2) = (l[1].clone(), l[2].clone()); + match a1 { + List(ref binds,_) | Vector(ref binds,_) => { + for (b, e) in binds.iter().tuples() { + match b { + Sym(_) => { + let _ = env_set(&env, b.clone(), + eval(e.clone(), env.clone())?); + }, + _ => { + return error("let* with non-Sym binding"); + } + } + } + }, + _ => { + return error("let* with non-List bindings"); + } + }; + ast = a2; + continue 'tco; + }, + Sym(ref a0sym) if a0sym == "quote" => { + Ok(l[1].clone()) + }, + Sym(ref a0sym) if a0sym == "quasiquote" => { + ast = quasiquote(&l[1]); + continue 'tco; + }, + Sym(ref a0sym) if a0sym == "defmacro!" => { + let (a1, a2) = (l[1].clone(), l[2].clone()); + let r = eval(a2, env.clone())?; + match r { + MalFunc{eval, ast, env, params, ..} => { + Ok(env_set(&env, a1.clone(), + MalFunc{eval: eval, ast: ast.clone(), env: env.clone(), + params: params.clone(), is_macro: true, + meta: Rc::new(Nil)})?) + }, + _ => error("set_macro on non-function"), + } + }, + Sym(ref a0sym) if a0sym == "macroexpand" => { + match macroexpand(l[1].clone(), &env) { + (_, Ok(new_ast)) => Ok(new_ast), + (_, e) => return e, + } + }, + Sym(ref a0sym) if a0sym == "try*" => { + match eval(l[1].clone(), env.clone()) { + Err(ref e) if l.len() >= 3 => { + let exc = match e { + ErrMalVal(mv) => mv.clone(), + ErrString(s) => Str(s.to_string()), + }; + match l[2].clone() { + List(c,_) => { + let catch_env = env_bind(Some(env.clone()), + list!(vec![c[1].clone()]), + vec![exc])?; + eval(c[2].clone(), catch_env) + }, + _ => error("invalid catch block"), + } + }, + res => res, + } + }, + Sym(ref a0sym) if a0sym == "do" => { + match eval_ast(&list!(l[1..l.len()-1].to_vec()), &env)? { + List(_,_) => { + ast = l.last().unwrap_or(&Nil).clone(); + continue 'tco; + }, + _ => error("invalid do form"), + } + }, + Sym(ref a0sym) if a0sym == "if" => { + let cond = eval(l[1].clone(), env.clone())?; + match cond { + Bool(false) | Nil if l.len() >= 4 => { + ast = l[3].clone(); + continue 'tco; + }, + Bool(false) | Nil => Ok(Nil), + _ if l.len() >= 3 => { + ast = l[2].clone(); + continue 'tco; + }, + _ => Ok(Nil) + } + }, + Sym(ref a0sym) if a0sym == "fn*" => { + let (a1, a2) = (l[1].clone(), l[2].clone()); + Ok(MalFunc{eval: eval, ast: Rc::new(a2), env: env, + params: Rc::new(a1), is_macro: false, + meta: Rc::new(Nil)}) + }, + Sym(ref a0sym) if a0sym == "eval" => { + ast = eval(l[1].clone(), env.clone())?; + while let Some(ref e) = env.clone().outer { + env = e.clone(); + } + continue 'tco; + }, + _ => { + match eval_ast(&ast, &env)? { + List(ref el,_) => { + let ref f = el[0].clone(); + let args = el[1..].to_vec(); + match f { + Func(_,_) => f.apply(args), + MalFunc{ast: mast, env: menv, params, ..} => { + let a = &**mast; + let p = &**params; + env = env_bind(Some(menv.clone()), p.clone(), args)?; + ast = a.clone(); + continue 'tco; + }, + _ => error("attempt to call non-function"), + } + }, + _ => { + error("expected a list") + } + } + } + } + }, + _ => eval_ast(&ast, &env), + }; + + break; + + } // end 'tco loop + + ret +} + +// print +fn print(ast: &MalVal) -> String { + ast.pr_str(true) +} + +fn rep(str: &str, env: &Env) -> Result { + let ast = read(str)?; + let exp = eval(ast, env.clone())?; + Ok(print(&exp)) +} + +fn main() { + let mut args = std::env::args(); + let arg1 = args.nth(1); + + // `()` can be used when no completer is required + let mut rl = Editor::<()>::new(); + if rl.load_history(".mal-history").is_err() { + println!("No previous history."); + } + + // core.rs: defined using rust + let repl_env = env_new(None); + for (k, v) in core::ns() { + env_sets(&repl_env, k, v); + } + env_sets(&repl_env, "*ARGV*", list!(args.map(Str).collect())); + + // core.mal: defined using the language itself + let _ = rep("(def! *host-language* \"rust\")", &repl_env); + let _ = rep("(def! not (fn* (a) (if a false true)))", &repl_env); + let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", &repl_env); + let _ = rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", &repl_env); + let _ = rep("(def! *gensym-counter* (atom 0))", &repl_env); + let _ = rep("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))", &repl_env); + let _ = rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))", &repl_env); + + + // Invoked with arguments + if let Some(f) = arg1 { + match rep(&format!("(load-file \"{}\")",f), &repl_env) { + Ok(_) => std::process::exit(0), + Err(e) => { + println!("Error: {}", format_error(e)); + std::process::exit(1); + } + } + } + + // main repl loop + let _ = rep("(println (str \"Mal [\" *host-language* \"]\"))", &repl_env); + loop { + let readline = rl.readline("user> "); + match readline { + Ok(line) => { + rl.add_history_entry(&line); + rl.save_history(".mal-history").unwrap(); + if line.len() > 0 { + match rep(&line, &repl_env) { + Ok(out) => println!("{}", out), + Err(e) => println!("Error: {}", format_error(e)), + } + } + }, + Err(ReadlineError::Interrupted) => continue, + Err(ReadlineError::Eof) => break, + Err(err) => { + println!("Error: {:?}", err); + break + } + } + } +} + +// vim: ts=2:sw=2:expandtab diff --git a/rust/tests/step5_tco.mal b/rust/tests/step5_tco.mal deleted file mode 100644 index 5cf775f657..0000000000 --- a/rust/tests/step5_tco.mal +++ /dev/null @@ -1,2 +0,0 @@ -;; Rust: skipping non-TCO recursion -;; Reason: unrecoverable stack overflow diff --git a/rust/types.rs b/rust/types.rs new file mode 100644 index 0000000000..d924f950ef --- /dev/null +++ b/rust/types.rs @@ -0,0 +1,232 @@ +use std::rc::Rc; +use std::cell::RefCell; +//use std::collections::HashMap; +use fnv::FnvHashMap; +use itertools::Itertools; + +use types::MalErr::{ErrString,ErrMalVal}; +use types::MalVal::{Nil,Bool,Int,Str,Sym,List,Vector,Hash,Func,MalFunc,Atom}; +use env::{Env,env_bind}; + +#[derive(Debug, Clone)] +pub enum MalVal { + Nil, + Bool(bool), + Int(i64), + //Float(f64), + Str(String), + Sym(String), + List(Rc>, Rc), + Vector(Rc>, Rc), + Hash(Rc>, Rc), + Func(fn(MalArgs) -> MalRet, Rc), + MalFunc { + eval: fn(ast: MalVal, env: Env) -> MalRet, + ast: Rc, + env: Env, + params: Rc, + is_macro: bool, + meta: Rc, + }, + Atom(Rc>), +} + +#[derive(Debug)] +pub enum MalErr { + ErrString(String), + ErrMalVal(MalVal), +} + +pub type MalArgs = Vec; +pub type MalRet = Result; + +// type utility macros + +macro_rules! list { + ($seq:expr) => {{ + List(Rc::new($seq),Rc::new(Nil)) + }}; + [$($args:expr),*] => {{ + let v: Vec = vec![$($args),*]; + List(Rc::new(v),Rc::new(Nil)) + }} +} + +macro_rules! vector { + ($seq:expr) => {{ + Vector(Rc::new($seq),Rc::new(Nil)) + }}; + [$($args:expr),*] => {{ + let v: Vec = vec![$($args),*]; + Vector(Rc::new(v),Rc::new(Nil)) + }} +} + +// type utility functions + +pub fn error(s: &str) -> MalRet { + Err(ErrString(s.to_string())) +} + +pub fn format_error(e: MalErr) -> String { + match e { + ErrString(s) => s.clone(), + ErrMalVal(mv) => mv.pr_str(true), + } +} + +pub fn atom(mv: &MalVal) -> MalVal { + Atom(Rc::new(RefCell::new(mv.clone()))) +} + +impl MalVal { + pub fn keyword(&self) -> MalRet { + match self { + Str(s) if s.starts_with("\u{29e}") => Ok(Str(s.to_string())), + Str(s) => Ok(Str(format!("\u{29e}{}", s))), + _ => error("invalid type for keyword"), + } + } + + pub fn empty_q(&self) -> MalRet { + match self { + List(l,_) | Vector(l,_) => Ok(Bool(l.len() == 0)), + Nil => Ok(Bool(true)), + _ => error("invalid type for empty?"), + } + } + + pub fn count(&self) -> MalRet { + match self { + List(l,_) | Vector(l,_) => Ok(Int(l.len() as i64)), + Nil => Ok(Int(0)), + _ => error("invalid type for count"), + } + } + + pub fn apply(&self, args: MalArgs) -> MalRet { + match *self { + Func(f,_) => f(args), + MalFunc{eval, ref ast, ref env, ref params, ..} => { + let a = &**ast; + let p = &**params; + let fn_env = env_bind(Some(env.clone()), p.clone(), args)?; + Ok(eval(a.clone(), fn_env)?) + } + _ => error("attempt to call non-function"), + } + } + + pub fn keyword_q(&self) -> bool { + match self { + Str(s) if s.starts_with("\u{29e}") => true, + _ => false, + } + } + + pub fn deref(&self) -> MalRet { + match self { + Atom(a) => Ok(a.borrow().clone()), + _ => error("attempt to deref a non-Atom"), + } + } + + pub fn reset_bang(&self, new: &MalVal) -> MalRet { + match self { + Atom(a) => { + *a.borrow_mut() = new.clone(); + Ok(new.clone()) + }, + _ => error("attempt to reset! a non-Atom"), + } + } + + pub fn swap_bang(&self, args: &MalArgs) -> MalRet { + match self { + Atom(a) => { + let f = &args[0]; + let mut fargs = args[1..].to_vec(); + fargs.insert(0, a.borrow().clone()); + *a.borrow_mut() = f.apply(fargs)?; + Ok(a.borrow().clone()) + }, + _ => error("attempt to swap! a non-Atom"), + } + } + + pub fn get_meta(&self) -> MalRet { + match self { + List(_,meta) | Vector(_,meta) | Hash(_,meta) => Ok((&**meta).clone()), + Func(_,meta) => Ok((&**meta).clone()), + MalFunc{meta,..} => Ok((&**meta).clone()), + _ => error("meta not supported by type"), + } + } + + pub fn with_meta(&mut self, new_meta: &MalVal) -> MalRet { + match self { + List(_, ref mut meta) | + Vector(_, ref mut meta) | + Hash(_, ref mut meta) | + Func(_,ref mut meta) | + MalFunc{ref mut meta, ..} => { + *meta = Rc::new((&*new_meta).clone()); + }, + _ => return error("with-meta not supported by type"), + }; + Ok(self.clone()) + } +} + +impl PartialEq for MalVal { + fn eq(&self, other: &MalVal) -> bool { + match (self, other) { + (Nil,Nil) => true, + (Bool(ref a),Bool(ref b)) => a == b, + (Int(ref a),Int(ref b)) => a == b, + (Str(ref a),Str(ref b)) => a == b, + (Sym(ref a),Sym(ref b)) => a == b, + (List(ref a,_),List(ref b,_)) | + (Vector(ref a,_),Vector(ref b,_)) | + (List(ref a,_),Vector(ref b,_)) | + (Vector(ref a,_),List(ref b,_)) => a == b, + (Hash(ref a,_),Hash(ref b,_)) => a == b, + (MalFunc{..}, MalFunc{..}) => false, + _ => false, + } + } +} + +pub fn func(f: fn(MalArgs) -> MalRet) -> MalVal { + Func(f, Rc::new(Nil)) +} + +pub fn _assoc(mut hm: FnvHashMap, kvs: MalArgs) -> MalRet { + if kvs.len() % 2 != 0 { + return error("odd number of elements") + } + for (k, v) in kvs.iter().tuples() { + match k { + Str(s) => { hm.insert(s.to_string(), v.clone()); }, + _ => { return error("key is not string") }, + } + } + Ok(Hash(Rc::new(hm),Rc::new(Nil))) +} + +pub fn _dissoc(mut hm: FnvHashMap, ks: MalArgs) -> MalRet { + for k in ks.iter() { + match k { + Str(ref s) => { hm.remove(s); }, + _ => { return error("key is not string") }, + } + } + Ok(Hash(Rc::new(hm),Rc::new(Nil))) +} + +pub fn hash_map(kvs: MalArgs) -> MalRet { + let hm: FnvHashMap = FnvHashMap::default(); + _assoc(hm, kvs) +} + +// vim: ts=2:sw=2:expandtab From 56d9fa6036a646c0dc819684761ab0ebec59a1ba Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 13 Jul 2018 15:24:18 -0500 Subject: [PATCH 0688/2308] scheme: enable foment to test if hangs are fixed --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0079c27890..29a84b05f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -81,7 +81,7 @@ matrix: - {env: IMPL=scheme scheme_MODE=chicken, services: [docker]} - {env: IMPL=scheme scheme_MODE=sagittarius, services: [docker]} - {env: IMPL=scheme scheme_MODE=cyclone, services: [docker]} -# - {env: IMPL=scheme scheme_MODE=foment, services: [docker]} + - {env: IMPL=scheme scheme_MODE=foment, services: [docker]} - {env: IMPL=skew, services: [docker]} - {env: IMPL=swift NO_DOCKER=1, os: osx, osx_image: xcode7.3} - {env: IMPL=swift3, services: [docker]} From 0777a9a7620d8c499aaf810bbbaf2672761e52e1 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 14 Jul 2018 13:32:35 -0500 Subject: [PATCH 0689/2308] [travis] disable foment. Still hangs in perf. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 29a84b05f3..0079c27890 100644 --- a/.travis.yml +++ b/.travis.yml @@ -81,7 +81,7 @@ matrix: - {env: IMPL=scheme scheme_MODE=chicken, services: [docker]} - {env: IMPL=scheme scheme_MODE=sagittarius, services: [docker]} - {env: IMPL=scheme scheme_MODE=cyclone, services: [docker]} - - {env: IMPL=scheme scheme_MODE=foment, services: [docker]} +# - {env: IMPL=scheme scheme_MODE=foment, services: [docker]} - {env: IMPL=skew, services: [docker]} - {env: IMPL=swift NO_DOCKER=1, os: osx, osx_image: xcode7.3} - {env: IMPL=swift3, services: [docker]} From cd30e52b3158e9bb1d9a1ee4ea1cbfc2b1380b6a Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 14 Jul 2018 19:27:11 -0500 Subject: [PATCH 0690/2308] rust: fix Dockerfile to include WORKDIR=/mal --- rust/Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/Dockerfile b/rust/Dockerfile index fb1fdd69ee..6d0081e4df 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -1,3 +1,5 @@ -FROM rust +FROM rust:1.27.1 -ENV CARGO_HOME=/mal +ENV CARGO_HOME /mal + +WORKDIR /mal From 6da59ec2f48bdebabee68a2029877b8a7218edfe Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 16 Jul 2018 09:34:37 -0500 Subject: [PATCH 0691/2308] perf: fix perf3 to show total iters instead of / 3 The run-fn-for function was originally name run-fn and was hard-coded to run for 3 seconds. I forgot to replace the 3 with the max-secs variable. However, given that some implementations have less than 1 iteration per second, I'm just going to drop that division and report the iterations per max-seconds so that for the slowest implementations we have some relative comparison instead of all of them just reporting 0. Thanks to Tim Morgan for discovering this bug: https://github.com/kanaka/mal/commit/699f0ad23aca21076edb6a51838d879ca580ffd5 --- perf.mal | 2 +- tests/perf3.mal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/perf.mal b/perf.mal index 83bbc0da99..2fe9dd52b1 100644 --- a/perf.mal +++ b/perf.mal @@ -24,4 +24,4 @@ ;; Warm it up first (run-fn-for* fn 1000 0 0) ;; Now do the test - (/ (run-fn-for* fn (* 1000 max-secs) 0 0) 3)))) + (run-fn-for* fn (* 1000 max-secs) 0 0)))) diff --git a/tests/perf3.mal b/tests/perf3.mal index 5ac54146e3..1a4fbadecf 100644 --- a/tests/perf3.mal +++ b/tests/perf3.mal @@ -5,7 +5,7 @@ (def! atm (atom (list 0 1 2 3 4 5 6 7 8 9))) -(println "iters/s:" +(println "iters over 10 seconds:" (run-fn-for (fn* [] (do From 5ffa3314f97236147005455d4b85fb228f6a2db3 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 16 Jul 2018 12:24:05 -0500 Subject: [PATCH 0692/2308] perf: simplify perf3 iter calc. Enable perf^mal. Since the iter count increases by 1 each time through the "loop", when we hit more than 10 seconds elapsed time we can just return the previous iter count rather than doing a percentage calculation. This fixes results for basic modes which couldn't do the percent calculation because itermediate values were greater than 32,768 which is the limit for basic MODES. Also, the mal implementation now runs and returns legit results so re-enable perf^impl. --- Makefile | 2 -- perf.mal | 10 +++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 62c9b330fa..de8f929226 100644 --- a/Makefile +++ b/Makefile @@ -125,8 +125,6 @@ test_EXCLUDES += test^plsql^step5 # too slow for 10,000 test_EXCLUDES += test^powershell^step5 # too slow for 10,000 test_EXCLUDES += $(if $(filter cpp,$(haxe_MODE)),test^haxe^step5,) # cpp finishes 10,000, segfaults at 100,000 -perf_EXCLUDES = mal # TODO: fix this - dist_EXCLUDES += mal # TODO: still need to implement dist dist_EXCLUDES += guile io julia matlab swift diff --git a/perf.mal b/perf.mal index 2fe9dd52b1..e4833bffe3 100644 --- a/perf.mal +++ b/perf.mal @@ -7,16 +7,16 @@ ret_FIXME)))) (def! run-fn-for* - (fn* [fn max-ms acc-ms iters] + (fn* [fn max-ms acc-ms last-iters] (let* [start (time-ms) _ (fn) elapsed (- (time-ms) start) - new-iters (+ 1 iters) + iters (+ 1 last-iters) new-acc-ms (+ acc-ms elapsed)] - ;(do (prn "here:" new-acc-ms "/" max-ms "iters:" new-iters) ) + ;(do (prn "new-acc-ms:" new-acc-ms "iters:" iters)) (if (>= new-acc-ms max-ms) - (/ (* max-ms iters) new-acc-ms) - (run-fn-for* fn max-ms new-acc-ms new-iters))))) + iters + (run-fn-for* fn max-ms new-acc-ms iters))))) (def! run-fn-for (fn* [fn max-secs] From 133221575478844ff8861faae6dddaa1f5b203be Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 16 Jul 2018 12:33:36 -0500 Subject: [PATCH 0693/2308] perf: more accurate iter count (last-iters). We should return the iter count prior to then final iteration because the final one was incomplete when the 10 seconds expired. --- perf.mal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perf.mal b/perf.mal index e4833bffe3..55f94092b4 100644 --- a/perf.mal +++ b/perf.mal @@ -15,7 +15,7 @@ new-acc-ms (+ acc-ms elapsed)] ;(do (prn "new-acc-ms:" new-acc-ms "iters:" iters)) (if (>= new-acc-ms max-ms) - iters + last-iters (run-fn-for* fn max-ms new-acc-ms iters))))) (def! run-fn-for From 17d2773245e58e287258bc92d39b52364d9f809a Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 16 Jul 2018 17:45:55 -0500 Subject: [PATCH 0694/2308] Travis: trigger build after github push error. git push gave this error and the commit was successfully pushed but it didn't result in Travis starting a build: remote: Resolving deltas: 100% (2/2), completed with 2 local objects. remote: Unexpected system error after push was received. remote: These changes may not be reflected on github.com! remote: Your unique error code: 1d9d84300a34930b94914e59e9c3b381 To git@github.com:kanaka/mal.git 5ffa331..1332215 master -> master From 53c3bccc43fe621f64395ea08f17b8092e7c4af3 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 16 Jul 2018 18:15:19 -0500 Subject: [PATCH 0695/2308] Travis: enable mal impl for perf test. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0079c27890..94c0488c8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,7 +49,7 @@ matrix: - {env: IMPL=logo, services: [docker]} - {env: IMPL=lua, services: [docker]} - {env: IMPL=make, services: [docker]} - - {env: IMPL=mal BUILD_IMPL=js NO_PERF=1, services: [docker]} + - {env: IMPL=mal BUILD_IMPL=js, services: [docker]} - {env: IMPL=matlab, services: [docker]} # Uses Octave - {env: IMPL=miniMAL, services: [docker]} - {env: IMPL=nasm, services: [docker]} From 5d52baf1acdef1aa70ff4c03cf3945a015f80814 Mon Sep 17 00:00:00 2001 From: Tim Morgan Date: Mon, 16 Jul 2018 18:41:12 -0500 Subject: [PATCH 0696/2308] Fix link to FAQ document --- process/guide.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/process/guide.md b/process/guide.md index 025995db4c..3e58b366ec 100644 --- a/process/guide.md +++ b/process/guide.md @@ -1669,9 +1669,9 @@ For extra information read [Peter Seibel's thorough discussion about * If you have created an implementation for a new target language (or a unique and interesting variant of an existing implementation), consider sending a pull request to add it into the main mal - repository. The [FAQ](FAQ.md#add_implementation) describes general - requirements for getting an implementation merged into the main - repository. + repository. The [FAQ](../docs/FAQ.md#will-you-add-my-new-implementation) + describes general requirements for getting an implementation merged + into the main repository. * Take your interpreter implementation and have it emit source code in the target language rather than immediately evaluating it. In other words, create a compiler. From a520ffd9a1cfe016f32ab92b94d92855a3a8a035 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 16 Jul 2018 19:10:00 -0500 Subject: [PATCH 0697/2308] Travis: re-active perf for erlang. Local testing shows that the test completes and doesn't hit OOM. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 94c0488c8c..ab99fe9077 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ matrix: - {env: IMPL=elisp, services: [docker]} - {env: IMPL=elixir, services: [docker]} - {env: IMPL=elm, services: [docker]} - - {env: IMPL=erlang NO_PERF=1, services: [docker]} # perf runs out of memory + - {env: IMPL=erlang, services: [docker]} # perf runs out of memory - {env: IMPL=es6, services: [docker]} - {env: IMPL=factor, services: [docker]} - {env: IMPL=fantom, services: [docker]} From 9581ba403eef4a7c8c35224886f4a497b75c8957 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 25 Aug 2018 17:30:33 -0500 Subject: [PATCH 0698/2308] Add link to Rust implementation by Tim Morgan. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 150ad85edc..aa10fe1bab 100644 --- a/README.md +++ b/README.md @@ -891,6 +891,8 @@ cd rust cargo run --release --bin stepX_YYY ``` +There is also a separate implementation in [Rust by Tim Morgan](https://github.com/seven1m/mal-rust). + ### Scala ### Install scala and sbt (http://www.scala-sbt.org/0.13/tutorial/Installing-sbt-on-Linux.html): From 5185c56e92b9e6946d64d9148ac0a8accc85a200 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sat, 27 Oct 2018 15:20:36 -0500 Subject: [PATCH 0699/2308] Update Crystal implementation --- README.md | 2 +- crystal/Makefile | 5 +- crystal/core.cr | 734 ++++++++++++++++++------------------ crystal/env.cr | 6 +- crystal/error.cr | 1 + crystal/printer.cr | 8 +- crystal/reader.cr | 16 +- crystal/readline.cr | 21 -- crystal/step0_repl.cr | 14 +- crystal/step1_read_print.cr | 6 +- crystal/step2_eval.cr | 27 +- crystal/step3_env.cr | 30 +- crystal/step4_if_fn_do.cr | 28 +- crystal/step5_tco.cr | 122 +++--- crystal/step6_file.cr | 126 +++---- crystal/step7_quote.cr | 138 +++---- crystal/step8_macros.cr | 157 ++++---- crystal/step9_try.cr | 187 +++++---- crystal/stepA_mal.cr | 187 +++++---- crystal/types.cr | 80 ++-- 20 files changed, 934 insertions(+), 961 deletions(-) delete mode 100644 crystal/readline.cr diff --git a/README.md b/README.md index aa10fe1bab..ecda6ebd5f 100644 --- a/README.md +++ b/README.md @@ -290,7 +290,7 @@ coffee ./stepX_YYY *The Crystal implementation of mal was created by [Linda_pp](https://github.com/rhysd)* -The Crystal implementation of mal has been tested with Crystal 0.18.4. +The Crystal implementation of mal has been tested with Crystal 0.26.1. ``` cd crystal diff --git a/crystal/Makefile b/crystal/Makefile index a26b02d13e..8373692259 100644 --- a/crystal/Makefile +++ b/crystal/Makefile @@ -2,7 +2,6 @@ STEPS = step0_repl.cr step1_read_print.cr step2_eval.cr step3_env.cr \ step4_if_fn_do.cr step5_tco.cr step6_file.cr step7_quote.cr \ step8_macros.cr step9_try.cr stepA_mal.cr -STEP0_DEPS = readline.cr STEP1_DEPS = $(STEP0_DEPS) reader.cr printer.cr STEP2_DEPS = $(STEP1_DEPS) types.cr STEP3_DEPS = $(STEP2_DEPS) env.cr @@ -19,7 +18,7 @@ mal: $(LAST_STEP_BIN) cp $< $@ $(STEP_BINS): %: %.cr - crystal compile --release $< + crystal build --release $< step0_repl: $(STEP0_DEPS) step1_read_print: $(STEP1_DEPS) @@ -30,7 +29,7 @@ step4_if_fn_do step5_tco step6_file step7_quote step8_macros step9_try stepA_mal clean: rm -rf $(STEP_BINS) mal .crystal -stats: types.cr error.cr readline.cr reader.cr printer.cr env.cr core.cr stepA_mal.cr +stats: types.cr error.cr reader.cr printer.cr env.cr core.cr stepA_mal.cr @wc $^ @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" stats-lisp: env.cr core.cr stepA_mal.cr diff --git a/crystal/core.cr b/crystal/core.cr index 2694cf8542..52b66c89e1 100644 --- a/crystal/core.cr +++ b/crystal/core.cr @@ -1,456 +1,454 @@ require "time" +require "readline" require "./types" require "./error" require "./printer" require "./reader" -require "./readline" module Mal + macro calc_op(op) + -> (args : Array(Mal::Type)) { + x, y = args[0].unwrap, args[1].unwrap + eval_error "invalid arguments for binary operator {{op.id}}" unless x.is_a?(Int64) && y.is_a?(Int64) + Mal::Type.new(x {{op.id}} y) + } + end -macro calc_op(op) - -> (args : Array(Mal::Type)) { - x, y = args[0].unwrap, args[1].unwrap - eval_error "invalid arguments for binary operator {{op.id}}" unless x.is_a?(Int64) && y.is_a?(Int64) - Mal::Type.new(x {{op.id}} y) - } -end - -def self.list(args) - args.to_mal -end + def self.list(args) + args.to_mal + end -def self.list?(args) - args.first.unwrap.is_a? Mal::List -end + def self.list?(args) + args.first.unwrap.is_a? Mal::List + end -def self.empty?(args) - a = args.first.unwrap - a.is_a?(Array) ? a.empty? : false -end + def self.empty?(args) + a = args.first.unwrap + a.is_a?(Array) ? a.empty? : false + end -def self.count(args) - a = args.first.unwrap - case a - when Array - a.size.to_i64 - when Nil - 0i64 - else - eval_error "invalid argument for function 'count'" + def self.count(args) + a = args.first.unwrap + case a + when Array + a.size.to_i64 + when Nil + 0i64 + else + eval_error "invalid argument for function 'count'" + end end -end -def self.pr_str_(args) - args.map{|a| pr_str(a)}.join(" ") -end + def self.pr_str_(args) + args.map { |a| pr_str(a) }.join(" ") + end -def self.str(args) - args.map{|a| pr_str(a, false)}.join -end + def self.str(args) + args.map { |a| pr_str(a, false) }.join + end -def self.prn(args) - puts self.pr_str_(args) - nil -end + def self.prn(args) + puts self.pr_str_(args) + nil + end -def self.println(args) - puts args.map{|a| pr_str(a, false)}.join(" ") - nil -end + def self.println(args) + puts args.map { |a| pr_str(a, false) }.join(" ") + nil + end -def self.read_string(args) - head = args.first.unwrap - eval_error "argument of read-str must be string" unless head.is_a? String - read_str head -end + def self.read_string(args) + head = args.first.unwrap + eval_error "argument of read-str must be string" unless head.is_a? String + read_str head + end -def self.slurp(args) - head = args.first.unwrap - eval_error "argument of slurp must be string" unless head.is_a? String - begin - File.read head - rescue e : Errno - eval_error "no such file" + def self.slurp(args) + head = args.first.unwrap + eval_error "argument of slurp must be string" unless head.is_a? String + begin + File.read head + rescue e : Errno + eval_error "no such file" + end end -end -def self.cons(args) - head, tail = args[0] as Mal::Type, args[1].unwrap - eval_error "2nd arg of cons must be list" unless tail.is_a? Array - ([head] + tail).to_mal -end + def self.cons(args) + head, tail = args[0].as(Mal::Type), args[1].unwrap + eval_error "2nd arg of cons must be list" unless tail.is_a? Array + ([head] + tail).to_mal + end -def self.concat(args) - args.each_with_object(Mal::List.new) do |arg, list| - a = arg.unwrap - eval_error "arguments of concat must be list" unless a.is_a?(Array) - a.each{|e| list << e} + def self.concat(args) + args.each_with_object(Mal::List.new) do |arg, list| + a = arg.unwrap + eval_error "arguments of concat must be list" unless a.is_a?(Array) + a.each { |e| list << e } + end end -end -def self.nth(args) - a0, a1 = args[0].unwrap, args[1].unwrap - eval_error "1st argument of nth must be list or vector" unless a0.is_a? Array - eval_error "2nd argument of nth must be integer" unless a1.is_a? Int64 - a0[a1] -end + def self.nth(args) + a0, a1 = args[0].unwrap, args[1].unwrap + eval_error "1st argument of nth must be list or vector" unless a0.is_a? Array + eval_error "2nd argument of nth must be integer" unless a1.is_a? Int64 + a0[a1] + end -def self.first(args) - a0 = args[0].unwrap + def self.first(args) + a0 = args[0].unwrap - return nil if a0.nil? - eval_error "1st argument of first must be list or vector or nil" unless a0.is_a? Array - a0.empty? ? nil : a0.first -end + return nil if a0.nil? + eval_error "1st argument of first must be list or vector or nil" unless a0.is_a? Array + a0.empty? ? nil : a0.first + end -def self.rest(args) - a0 = args[0].unwrap + def self.rest(args) + a0 = args[0].unwrap - return Mal::List.new if a0.nil? - eval_error "1st argument of first must be list or vector or nil" unless a0.is_a? Array - return Mal::List.new if a0.empty? - a0[1..-1].to_mal -end + return Mal::List.new if a0.nil? + eval_error "1st argument of first must be list or vector or nil" unless a0.is_a? Array + return Mal::List.new if a0.empty? + a0[1..-1].to_mal + end -def self.apply(args) - eval_error "apply must take at least 2 arguments" unless args.size >= 2 + def self.apply(args) + eval_error "apply must take at least 2 arguments" unless args.size >= 2 - head = args.first.unwrap - last = args.last.unwrap + head = args.first.unwrap + last = args.last.unwrap - eval_error "last argument of apply must be list or vector" unless last.is_a? Array + eval_error "last argument of apply must be list or vector" unless last.is_a? Array - case head - when Mal::Closure - head.fn.call(args[1..-2] + last) - when Mal::Func - head.call(args[1..-2] + last) - else - eval_error "1st argument of apply must be function or closure" + case head + when Mal::Closure + head.fn.call(args[1..-2] + last) + when Mal::Func + head.call(args[1..-2] + last) + else + eval_error "1st argument of apply must be function or closure" + end end -end -def self.map(args) - func = args.first.unwrap - list = args[1].unwrap + def self.map(args) + func = args.first.unwrap + list = args[1].unwrap - eval_error "2nd argument of map must be list or vector" unless list.is_a? Array + eval_error "2nd argument of map must be list or vector" unless list.is_a? Array - f = case func - when Mal::Closure then func.fn - when Mal::Func then func - else eval_error "1st argument of map must be function" - end + f = case func + when Mal::Closure then func.fn + when Mal::Func then func + else eval_error "1st argument of map must be function" + end - list.each_with_object(Mal::List.new) do |elem, mapped| - mapped << f.call([elem]) + list.each_with_object(Mal::List.new) do |elem, mapped| + mapped << f.call([elem]) + end end -end - -def self.nil_value?(args) - args.first.unwrap.nil? -end -def self.true?(args) - a = args.first.unwrap - a.is_a?(Bool) && a -end - -def self.false?(args) - a = args.first.unwrap - a.is_a?(Bool) && !a -end - -def self.symbol?(args) - args.first.unwrap.is_a?(Mal::Symbol) -end + def self.nil_value?(args) + args.first.unwrap.nil? + end -def self.symbol(args) - head = args.first.unwrap - eval_error "1st argument of symbol function must be string" unless head.is_a? String - Mal::Symbol.new head -end + def self.true?(args) + a = args.first.unwrap + a.is_a?(Bool) && a + end -def self.string?(args) - head = args.first.unwrap - head.is_a?(String) && (head.empty? || head[0] != '\u029e') -end + def self.false?(args) + a = args.first.unwrap + a.is_a?(Bool) && !a + end -def self.keyword(args) - head = args.first.unwrap - eval_error "1st argument of symbol function must be string" unless head.is_a? String - "\u029e" + head -end + def self.symbol?(args) + args.first.unwrap.is_a?(Mal::Symbol) + end -def self.keyword?(args) - head = args.first.unwrap - head.is_a?(String) && !head.empty? && head[0] == '\u029e' -end + def self.symbol(args) + head = args.first.unwrap + eval_error "1st argument of symbol function must be string" unless head.is_a? String + Mal::Symbol.new head + end -def self.number?(args) - args.first.unwrap.is_a?(Int64) -end + def self.string?(args) + head = args.first.unwrap + head.is_a?(String) && (head.empty? || head[0] != '\u029e') + end -def self.fn?(args) - return false if args.first.macro? - head = args.first.unwrap - head.is_a?(Mal::Func) || head.is_a?(Mal::Closure) -end + def self.keyword(args) + head = args.first.unwrap + eval_error "1st argument of symbol function must be string" unless head.is_a? String + "\u029e" + head + end -def self.macro?(args) - args.first.macro? -end + def self.keyword?(args) + head = args.first.unwrap + head.is_a?(String) && !head.empty? && head[0] == '\u029e' + end -def self.vector(args) - args.to_mal(Mal::Vector) -end + def self.number?(args) + args.first.unwrap.is_a?(Int64) + end -def self.vector?(args) - args.first.unwrap.is_a? Mal::Vector -end + def self.fn?(args) + return false if args.first.macro? + head = args.first.unwrap + head.is_a?(Mal::Func) || head.is_a?(Mal::Closure) + end -def self.hash_map(args) - eval_error "hash-map must take even number of arguments" unless args.size.even? - map = Mal::HashMap.new - args.each_slice(2) do |kv| - k = kv[0].unwrap - eval_error "key must be string" unless k.is_a? String - map[k] = kv[1] + def self.macro?(args) + args.first.macro? end - map -end -def self.map?(args) - args.first.unwrap.is_a? Mal::HashMap -end + def self.vector(args) + args.to_mal(Mal::Vector) + end -def self.assoc(args) - head = args.first.unwrap - eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap - eval_error "assoc must take a list and even number of arguments" unless (args.size - 1).even? + def self.vector?(args) + args.first.unwrap.is_a? Mal::Vector + end - map = Mal::HashMap.new - head.each{|k, v| map[k] = v} + def self.hash_map(args) + eval_error "hash-map must take even number of arguments" unless args.size.even? + map = Mal::HashMap.new + args.each_slice(2) do |kv| + k = kv[0].unwrap + eval_error "key must be string" unless k.is_a? String + map[k] = kv[1] + end + map + end - args[1..-1].each_slice(2) do |kv| - k = kv[0].unwrap - eval_error "key must be string" unless k.is_a? String - map[k] = kv[1] + def self.map?(args) + args.first.unwrap.is_a? Mal::HashMap end - map -end + def self.assoc(args) + head = args.first.unwrap + eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap + eval_error "assoc must take a list and even number of arguments" unless (args.size - 1).even? -def self.dissoc(args) - head = args.first.unwrap - eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap + map = Mal::HashMap.new + head.each { |k, v| map[k] = v } - map = Mal::HashMap.new - head.each{|k,v| map[k] = v} + args[1..-1].each_slice(2) do |kv| + k = kv[0].unwrap + eval_error "key must be string" unless k.is_a? String + map[k] = kv[1] + end - args[1..-1].each do |arg| - key = arg.unwrap - eval_error "key must be string" unless key.is_a? String - map.delete key + map end - map -end + def self.dissoc(args) + head = args.first.unwrap + eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap -def self.get(args) - a0, a1 = args[0].unwrap, args[1].unwrap - return nil unless a0.is_a? Mal::HashMap - eval_error "2nd argument of get must be string" unless a1.is_a? String + map = Mal::HashMap.new + head.each { |k, v| map[k] = v } - # a0[a1]? isn't available because type ofa0[a1] is infered NoReturn - a0.has_key?(a1) ? a0[a1] : nil -end + args[1..-1].each do |arg| + key = arg.unwrap + eval_error "key must be string" unless key.is_a? String + map.delete key + end -def self.contains?(args) - a0, a1 = args[0].unwrap, args[1].unwrap - eval_error "1st argument of get must be hashmap" unless a0.is_a? Mal::HashMap - eval_error "2nd argument of get must be string" unless a1.is_a? String - a0.has_key? a1 -end + map + end -def self.keys(args) - head = args.first.unwrap - eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap - head.keys.each_with_object(Mal::List.new){|e,l| l << Mal::Type.new(e)} -end + def self.get(args) + a0, a1 = args[0].unwrap, args[1].unwrap + return nil unless a0.is_a? Mal::HashMap + eval_error "2nd argument of get must be string" unless a1.is_a? String -def self.vals(args) - head = args.first.unwrap - eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap - head.values.to_mal -end + # a0[a1]? isn't available because type ofa0[a1] is infered NoReturn + a0.has_key?(a1) ? a0[a1] : nil + end -def self.sequential?(args) - args.first.unwrap.is_a? Array -end + def self.contains?(args) + a0, a1 = args[0].unwrap, args[1].unwrap + eval_error "1st argument of get must be hashmap" unless a0.is_a? Mal::HashMap + eval_error "2nd argument of get must be string" unless a1.is_a? String + a0.has_key? a1 + end -def self.readline(args) - head = args.first.unwrap - eval_error "1st argument of readline must be string" unless head.is_a? String - my_readline head -end + def self.keys(args) + head = args.first.unwrap + eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap + head.keys.each_with_object(Mal::List.new) { |e, l| l << Mal::Type.new(e) } + end -def self.meta(args) - m = args.first.meta - m.nil? ? nil : m -end + def self.vals(args) + head = args.first.unwrap + eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap + head.values.to_mal + end -def self.with_meta(args) - t = args.first.dup - t.meta = args[1] - t -end + def self.sequential?(args) + args.first.unwrap.is_a? Array + end -def self.atom(args) - Mal::Atom.new args.first -end + def self.readline(args) + head = args.first.unwrap + eval_error "1st argument of readline must be string" unless head.is_a? String + Readline.readline(head, true) + end -def self.atom?(args) - args.first.unwrap.is_a? Mal::Atom -end + def self.meta(args) + m = args.first.meta + m.nil? ? nil : m + end -def self.deref(args) - head = args.first.unwrap - eval_error "1st argument of deref must be atom" unless head.is_a? Mal::Atom - head.val -end + def self.with_meta(args) + t = args.first.dup + t.meta = args[1] + t + end -def self.reset!(args) - head = args.first.unwrap - eval_error "1st argument of reset! must be atom" unless head.is_a? Mal::Atom - head.val = args[1] -end + def self.atom(args) + Mal::Atom.new args.first + end -def self.swap!(args) - atom = args.first.unwrap - eval_error "1st argument of swap! must be atom" unless atom.is_a? Mal::Atom + def self.atom?(args) + args.first.unwrap.is_a? Mal::Atom + end - a = [atom.val] + args[2..-1] + def self.deref(args) + head = args.first.unwrap + eval_error "1st argument of deref must be atom" unless head.is_a? Mal::Atom + head.val + end - func = args[1].unwrap - case func - when Mal::Func - atom.val = func.call a - when Mal::Closure - atom.val = func.fn.call a - else - eval_error "2nd argumetn of swap! must be function" + def self.reset!(args) + head = args.first.unwrap + eval_error "1st argument of reset! must be atom" unless head.is_a? Mal::Atom + head.val = args[1] end -end -def self.conj(args) - seq = args.first.unwrap - case seq - when Mal::List - (args[1..-1].reverse + seq).to_mal - when Mal::Vector - (seq + args[1..-1]).to_mal(Mal::Vector) - else - eval_error "1st argument of conj must be list or vector" + def self.swap!(args) + atom = args.first.unwrap + eval_error "1st argument of swap! must be atom" unless atom.is_a? Mal::Atom + + a = [atom.val] + args[2..-1] + + func = args[1].unwrap + case func + when Mal::Func + atom.val = func.call a + when Mal::Closure + atom.val = func.fn.call a + else + eval_error "2nd argumetn of swap! must be function" + end end -end -def self.seq(args) - obj = args.first.unwrap - case obj - when nil - nil - when Mal::List - return nil if obj.empty? - obj - when Mal::Vector - return nil if obj.empty? - obj.to_mal - when String - return nil if obj.empty? - obj.split("").each_with_object(Mal::List.new){|e,l| l << Mal::Type.new(e)} - else - eval_error "argument of seq must be list or vector or string or nil" + def self.conj(args) + seq = args.first.unwrap + case seq + when Mal::List + (args[1..-1].reverse + seq).to_mal + when Mal::Vector + (seq + args[1..-1]).to_mal(Mal::Vector) + else + eval_error "1st argument of conj must be list or vector" + end end -end -def self.time_ms(args) - Time.now.epoch_ms.to_i64 -end + def self.seq(args) + obj = args.first.unwrap + case obj + when nil + nil + when Mal::List + return nil if obj.empty? + obj + when Mal::Vector + return nil if obj.empty? + obj.to_mal + when String + return nil if obj.empty? + obj.split("").each_with_object(Mal::List.new) { |e, l| l << Mal::Type.new(e) } + else + eval_error "argument of seq must be list or vector or string or nil" + end + end -# Note: -# Simply using ->self.some_func doesn't work -macro func(name) - -> (args : Array(Mal::Type)) { Mal::Type.new self.{{name.id}}(args) } -end + def self.time_ms(args) + Time.now.epoch_ms.to_i64 + end -macro rel_op(op) --> (args : Array(Mal::Type)) { Mal::Type.new (args[0] {{op.id}} args[1]) } -end + # Note: + # Simply using ->self.some_func doesn't work + macro func(name) + -> (args : Array(Mal::Type)) { Mal::Type.new self.{{name.id}}(args) } + end -NS = { - "+" => calc_op(:+), - "-" => calc_op(:-), - "*" => calc_op(:*), - "/" => calc_op(:/), - "list" => func(:list), - "list?" => func(:list?), - "empty?" => func(:empty?), - "count" => func(:count), - "=" => rel_op(:==), - "<" => rel_op(:<), - ">" => rel_op(:>), - "<=" => rel_op(:<=), - ">=" => rel_op(:>=), - "pr-str" => func(:pr_str_), - "str" => func(:str), - "prn" => func(:prn), - "println" => func(:println), - "read-string" => func(:read_string), - "slurp" => func(:slurp), - "cons" => func(:cons), - "concat" => func(:concat), - "nth" => func(:nth), - "first" => func(:first), - "rest" => func(:rest), - "throw" => -> (args : Array(Mal::Type)) { raise Mal::RuntimeException.new args[0] }, - "apply" => func(:apply), - "map" => func(:map), - "nil?" => func(:nil_value?), - "true?" => func(:true?), - "false?" => func(:false?), - "symbol?" => func(:symbol?), - "symbol" => func(:symbol), - "string?" => func(:string?), - "keyword" => func(:keyword), - "keyword?" => func(:keyword?), - "number?" => func(:number?), - "fn?" => func(:fn?), - "macro?" => func(:macro?), - "vector" => func(:vector), - "vector?" => func(:vector?), - "hash-map" => func(:hash_map), - "map?" => func(:map?), - "assoc" => func(:assoc), - "dissoc" => func(:dissoc), - "get" => func(:get), - "contains?" => func(:contains?), - "keys" => func(:keys), - "vals" => func(:vals), - "sequential?" => func(:sequential?), - "readline" => func(:readline), - "meta" => func(:meta), - "with-meta" => func(:with_meta), - "atom" => func(:atom), - "atom?" => func(:atom?), - "deref" => func(:deref), - "deref" => func(:deref), - "reset!" => func(:reset!), - "swap!" => func(:swap!), - "conj" => func(:conj), - "seq" => func(:seq), - "time-ms" => func(:time_ms), -} of String => Mal::Func + macro rel_op(op) + -> (args : Array(Mal::Type)) { Mal::Type.new (args[0] {{op.id}} args[1]) } + end + NS = { + "+" => calc_op(:+), + "-" => calc_op(:-), + "*" => calc_op(:*), + "/" => calc_op(:/), + "list" => func(:list), + "list?" => func(:list?), + "empty?" => func(:empty?), + "count" => func(:count), + "=" => rel_op(:==), + "<" => rel_op(:<), + ">" => rel_op(:>), + "<=" => rel_op(:<=), + ">=" => rel_op(:>=), + "pr-str" => func(:pr_str_), + "str" => func(:str), + "prn" => func(:prn), + "println" => func(:println), + "read-string" => func(:read_string), + "slurp" => func(:slurp), + "cons" => func(:cons), + "concat" => func(:concat), + "nth" => func(:nth), + "first" => func(:first), + "rest" => func(:rest), + "throw" => ->(args : Array(Mal::Type)) { raise Mal::RuntimeException.new args[0] }, + "apply" => func(:apply), + "map" => func(:map), + "nil?" => func(:nil_value?), + "true?" => func(:true?), + "false?" => func(:false?), + "symbol?" => func(:symbol?), + "symbol" => func(:symbol), + "string?" => func(:string?), + "keyword" => func(:keyword), + "keyword?" => func(:keyword?), + "number?" => func(:number?), + "fn?" => func(:fn?), + "macro?" => func(:macro?), + "vector" => func(:vector), + "vector?" => func(:vector?), + "hash-map" => func(:hash_map), + "map?" => func(:map?), + "assoc" => func(:assoc), + "dissoc" => func(:dissoc), + "get" => func(:get), + "contains?" => func(:contains?), + "keys" => func(:keys), + "vals" => func(:vals), + "sequential?" => func(:sequential?), + "readline" => func(:readline), + "meta" => func(:meta), + "with-meta" => func(:with_meta), + "atom" => func(:atom), + "atom?" => func(:atom?), + "deref" => func(:deref), + "deref" => func(:deref), + "reset!" => func(:reset!), + "swap!" => func(:swap!), + "conj" => func(:conj), + "seq" => func(:seq), + "time-ms" => func(:time_ms), + } of String => Mal::Func end diff --git a/crystal/env.cr b/crystal/env.cr index 572ceea34a..9c7a46253a 100644 --- a/crystal/env.cr +++ b/crystal/env.cr @@ -2,7 +2,6 @@ require "./types" require "./error" module Mal - class Env property data @@ -23,10 +22,10 @@ module Mal if sym.str == "&" eval_error "missing variable parameter name" if binds.size == idx - next_param = binds[idx+1].unwrap + next_param = binds[idx + 1].unwrap eval_error "bind name must be symbol" unless next_param.is_a? Mal::Symbol var_args = Mal::List.new - exprs[idx..-1].each{|e| var_args << e} if idx < exprs.size + exprs[idx..-1].each { |e| var_args << e } if idx < exprs.size @data[next_param.str] = Mal::Type.new var_args break end @@ -64,5 +63,4 @@ module Mal e.data[key] end end - end diff --git a/crystal/error.cr b/crystal/error.cr index b308a8a2c3..fb8f56c623 100644 --- a/crystal/error.cr +++ b/crystal/error.cr @@ -9,6 +9,7 @@ module Mal class RuntimeException < Exception getter :thrown + def initialize(@thrown : Type) super() end diff --git a/crystal/printer.cr b/crystal/printer.cr index cb9bf3611b..b6aeaab4fd 100644 --- a/crystal/printer.cr +++ b/crystal/printer.cr @@ -5,17 +5,17 @@ def pr_str(value, print_readably = true) when Nil then "nil" when Bool then value.to_s when Int64 then value.to_s - when Mal::List then "(#{value.map{|v| pr_str(v, print_readably) as String}.join(" ")})" - when Mal::Vector then "[#{value.map{|v| pr_str(v, print_readably) as String}.join(" ")}]" + when Mal::List then "(#{value.map { |v| pr_str(v, print_readably).as(String) }.join(" ")})" + when Mal::Vector then "[#{value.map { |v| pr_str(v, print_readably).as(String) }.join(" ")}]" when Mal::Symbol then value.str.to_s when Mal::Func then "" when Mal::Closure then "" when Mal::HashMap # step1_read_print.cr requires specifying type - "{#{value.map{|k, v| "#{pr_str(k, print_readably)} #{pr_str(v, print_readably)}" as String}.join(" ")}}" + "{#{value.map { |k, v| "#{pr_str(k, print_readably)} #{pr_str(v, print_readably)}".as(String) }.join(" ")}}" when String case - when value.empty?() + when value.empty? print_readably ? value.inspect : value when value[0] == '\u029e' ":#{value[1..-1]}" diff --git a/crystal/reader.cr b/crystal/reader.cr index 34b469220c..3fbbd7c97a 100644 --- a/crystal/reader.cr +++ b/crystal/reader.cr @@ -30,7 +30,7 @@ class Reader def read_sequence(init, open, close) token = self.next parse_error "expected '#{open}', got EOF" unless token - parse_error "expected '#{open}', got #{token}" unless token[0] == open + parse_error "expected '#{open}', got #{token}" unless token[0] == open loop do token = peek @@ -81,11 +81,11 @@ class Reader when token == "true" then true when token == "false" then false when token == "nil" then nil - when token[0] == '"' then token[1..-2].gsub(/\\(.)/, {"\\\"" => "\"", - "\\n" => "\n", - "\\\\" => "\\"}) - when token[0] == ':' then "\u029e#{token[1..-1]}" - else Mal::Symbol.new token + when token[0] == '"' then token[1..-2].gsub(/\\(.)/, {"\\\"" => "\"", + "\\n" => "\n", + "\\\\" => "\\"}) + when token[0] == ':' then "\u029e#{token[1..-1]}" + else Mal::Symbol.new token end end @@ -118,12 +118,11 @@ class Reader else read_atom end end - end def tokenize(str) regex = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/ - str.scan(regex).map{|m| m[1]}.reject(&.empty?) + str.scan(regex).map { |m| m[1] }.reject(&.empty?) end def read_str(str) @@ -136,4 +135,3 @@ def read_str(str) end end end - diff --git a/crystal/readline.cr b/crystal/readline.cr deleted file mode 100644 index e57099a954..0000000000 --- a/crystal/readline.cr +++ /dev/null @@ -1,21 +0,0 @@ -# Note: -# Crystal already has "readline" library. -# I implemented a subset of it again for practice. - -@[Link("readline")] -lib LibReadline - fun readline(prompt : UInt8*) : UInt8* - fun add_history(line : UInt8*) -end - -def my_readline(prompt = "") - line = LibReadline.readline(prompt) - if line - LibReadline.add_history(line) - String.new(line) - else - nil - end -ensure - LibC.free(line as Void*) if line -end diff --git a/crystal/step0_repl.cr b/crystal/step0_repl.cr index e1fe58a398..a9c67d68f2 100755 --- a/crystal/step0_repl.cr +++ b/crystal/step0_repl.cr @@ -1,26 +1,26 @@ #! /usr/bin/env crystal run -require "./readline" +require "readline" # Note: # Employed downcase names because Crystal prohibits uppercase names for methods def read(x) - x + x end def eval(x) - x + x end def print(x) - x + x end def rep(x) - read(eval(print(x))) + read(eval(print(x))) end -while line = my_readline("user> ") - puts rep(line) +while line = Readline.readline("user> ") + puts rep(line) end diff --git a/crystal/step1_read_print.cr b/crystal/step1_read_print.cr index 9da58c35fc..cdc05d57e1 100755 --- a/crystal/step1_read_print.cr +++ b/crystal/step1_read_print.cr @@ -1,6 +1,6 @@ #! /usr/bin/env crystal run -require "./readline" +require "readline" require "./reader" require "./printer" @@ -15,7 +15,7 @@ module Mal end def eval(x) - x + x end def print(result) @@ -27,7 +27,7 @@ module Mal end end -while line = my_readline("user> ") +while line = Readline.readline("user> ", true) begin puts Mal.rep(line) rescue e diff --git a/crystal/step2_eval.cr b/crystal/step2_eval.cr index f93b957e73..eeef93c360 100755 --- a/crystal/step2_eval.cr +++ b/crystal/step2_eval.cr @@ -1,6 +1,6 @@ #! /usr/bin/env crystal run -require "./readline" +require "readline" require "./reader" require "./printer" require "./types" @@ -16,7 +16,7 @@ module Mal end def num_func(func) - -> (args : Array(Mal::Type)) { + ->(args : Array(Mal::Type)) { x, y = args[0].unwrap, args[1].unwrap eval_error "invalid arguments" unless x.is_a?(Int64) && y.is_a?(Int64) Mal::Type.new func.call(x, y) @@ -24,7 +24,7 @@ module Mal end def eval_ast(a, env) - return a.map{|n| eval(n, env) as Mal::Type} if a.is_a? Mal::List + return a.map { |n| eval(n, env).as(Mal::Type) } if a.is_a? Mal::List return a unless a ast = a.unwrap @@ -36,11 +36,12 @@ module Mal eval_error "'#{ast.str}' not found" end when Mal::List - ast.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} + ast.each_with_object(Mal::List.new) { |n, l| l << eval(n, env) } when Mal::Vector - ast.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} + ast.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) } when Mal::HashMap - ast.each{|k, v| ast[k] = eval(v, env)} + ast.each { |k, v| ast[k] = eval(v, env) } + ast else ast end @@ -74,18 +75,18 @@ module Mal end def rep(str) - print(eval(read(str), $repl_env)) + print(eval(read(str), REPL_ENV)) end end -$repl_env = { - "+" => Mal.num_func(->(x : Int64, y : Int64){ x + y }), - "-" => Mal.num_func(->(x : Int64, y : Int64){ x - y }), - "*" => Mal.num_func(->(x : Int64, y : Int64){ x * y }), - "/" => Mal.num_func(->(x : Int64, y : Int64){ x / y }), +REPL_ENV = { + "+" => Mal.num_func(->(x : Int64, y : Int64) { x + y }), + "-" => Mal.num_func(->(x : Int64, y : Int64) { x - y }), + "*" => Mal.num_func(->(x : Int64, y : Int64) { x * y }), + "/" => Mal.num_func(->(x : Int64, y : Int64) { x / y }), } of String => Mal::Func -while line = my_readline("user> ") +while line = Readline.readline("user> ", true) begin puts Mal.rep(line) rescue e diff --git a/crystal/step3_env.cr b/crystal/step3_env.cr index 62543e81e4..171dd89a2f 100755 --- a/crystal/step3_env.cr +++ b/crystal/step3_env.cr @@ -1,6 +1,6 @@ #! /usr/bin/env crystal run -require "./readline" +require "readline" require "./reader" require "./printer" require "./types" @@ -14,24 +14,24 @@ def eval_error(msg) end def num_func(func) - -> (args : Array(Mal::Type)) { + ->(args : Array(Mal::Type)) { x, y = args[0].unwrap, args[1].unwrap eval_error "invalid arguments" unless x.is_a?(Int64) && y.is_a?(Int64) Mal::Type.new func.call(x, y) } end -$repl_env = Mal::Env.new nil -$repl_env.set("+", Mal::Type.new num_func(->(x : Int64, y : Int64){ x + y })) -$repl_env.set("-", Mal::Type.new num_func(->(x : Int64, y : Int64){ x - y })) -$repl_env.set("*", Mal::Type.new num_func(->(x : Int64, y : Int64){ x * y })) -$repl_env.set("/", Mal::Type.new num_func(->(x : Int64, y : Int64){ x / y })) +REPL_ENV = Mal::Env.new nil +REPL_ENV.set("+", Mal::Type.new num_func(->(x : Int64, y : Int64) { x + y })) +REPL_ENV.set("-", Mal::Type.new num_func(->(x : Int64, y : Int64) { x - y })) +REPL_ENV.set("*", Mal::Type.new num_func(->(x : Int64, y : Int64) { x * y })) +REPL_ENV.set("/", Mal::Type.new num_func(->(x : Int64, y : Int64) { x / y })) module Mal extend self def eval_ast(a, env) - return a.map{|n| eval(n, env) } if a.is_a? Array + return a.map { |n| eval(n, env) } if a.is_a? Array Mal::Type.new case ast = a.unwrap when Mal::Symbol @@ -41,12 +41,12 @@ module Mal eval_error "'#{ast.str}' not found" end when Mal::List - ast.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} + ast.each_with_object(Mal::List.new) { |n, l| l << eval(n, env) } when Mal::Vector - ast.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} + ast.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) } when Mal::HashMap new_map = Mal::HashMap.new - ast.each{|k, v| new_map[k] = eval(v, env)} + ast.each { |k, v| new_map[k] = eval(v, env) } new_map else ast @@ -71,7 +71,7 @@ module Mal eval_error "wrong number of argument for 'def!'" unless ast.size == 3 a1 = ast[1].unwrap eval_error "1st argument of 'def!' must be symbol" unless a1.is_a?(Mal::Symbol) - env.set(a1.str, eval(ast[2], env) as Mal::Type) + env.set(a1.str, eval(ast[2], env).as(Mal::Type)) when "let*" eval_error "wrong number of argument for 'def!'" unless ast.size == 3 @@ -93,7 +93,7 @@ module Mal args = eval_ast(ast, env) if f.is_a?(Mal::Type) && (f2 = f.unwrap).is_a?(Mal::Func) - f2.call(args as Array(Mal::Type)) + f2.call(args.as(Array(Mal::Type))) else eval_error "expected function symbol as the first symbol of list" end @@ -105,11 +105,11 @@ module Mal end def rep(str) - print(eval(read(str), $repl_env)) + print(eval(read(str), REPL_ENV)) end end -while line = my_readline("user> ") +while line = Readline.readline("user> ", true) begin puts Mal.rep(line) rescue e diff --git a/crystal/step4_if_fn_do.cr b/crystal/step4_if_fn_do.cr index de3e65bbd6..c20f1300d2 100755 --- a/crystal/step4_if_fn_do.cr +++ b/crystal/step4_if_fn_do.cr @@ -1,6 +1,6 @@ #! /usr/bin/env crystal run -require "./readline" +require "readline" require "./reader" require "./printer" require "./types" @@ -15,14 +15,14 @@ module Mal extend self def func_of(env, binds, body) - -> (args : Array(Mal::Type)) { + ->(args : Array(Mal::Type)) { new_env = Mal::Env.new(env, binds, args) eval(body, new_env) - } as Mal::Func + }.as(Mal::Func) end def eval_ast(ast, env) - return ast.map{|n| eval(n, env) as Mal::Type} if ast.is_a? Mal::List + return ast.map { |n| eval(n, env).as(Mal::Type) } if ast.is_a? Mal::List val = ast.unwrap @@ -34,11 +34,11 @@ module Mal eval_error "'#{val.str}' not found" end when Mal::List - val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} + val.each_with_object(Mal::List.new) { |n, l| l << eval(n, env) } when Mal::Vector - val.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} + val.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) } when Mal::HashMap - val.each{|k, v| val[k] = eval(v, env)} + val.each { |k, v| val[k] = eval(v, env) } val else val @@ -48,7 +48,7 @@ module Mal def eval_invocation(list, env) f = eval(list.first, env).unwrap eval_error "expected function symbol as the first symbol of list" unless f.is_a? Mal::Func - f.call eval_ast(list[1..-1].each_with_object(Mal::List.new){|i, l| l << i}, env) + f.call eval_ast(list[1..-1].each_with_object(Mal::List.new) { |i, l| l << i }, env) end def read(str) @@ -58,7 +58,7 @@ module Mal def eval(ast, env) list = ast.unwrap - return eval_ast(ast, env) unless list.is_a? Mal::List + return eval_ast(ast, env) unless list.is_a? Mal::List return gen_type Mal::List if list.empty? head = list.first.unwrap @@ -96,7 +96,7 @@ module Mal when Nil list.size >= 4 ? eval(list[3], env) : nil when false - list.size >= 4 ? eval(list[3], env) : nil + list.size >= 4 ? eval(list[3], env) : nil else eval(list[2], env) end @@ -117,15 +117,15 @@ module Mal end def rep(str) - print(eval(read(str), $repl_env)) + print(eval(read(str), REPL_ENV)) end end -$repl_env = Mal::Env.new nil -Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))} +REPL_ENV = Mal::Env.new nil +Mal::NS.each { |k, v| REPL_ENV.set(k, Mal::Type.new(v)) } Mal.rep "(def! not (fn* (a) (if a false true)))" -while line = my_readline("user> ") +while line = Readline.readline("user> ") begin puts Mal.rep(line) rescue e diff --git a/crystal/step5_tco.cr b/crystal/step5_tco.cr index 061293fc3f..e56db4a6b9 100755 --- a/crystal/step5_tco.cr +++ b/crystal/step5_tco.cr @@ -1,6 +1,6 @@ #! /usr/bin/env crystal run -require "./readline" +require "readline" require "./reader" require "./printer" require "./types" @@ -15,14 +15,14 @@ module Mal extend self def func_of(env, binds, body) - -> (args : Array(Mal::Type)) { + ->(args : Array(Mal::Type)) { new_env = Mal::Env.new(env, binds, args) eval(body, new_env) - } as Mal::Func + }.as(Mal::Func) end def eval_ast(ast, env) - return ast.map{|n| eval(n, env) as Mal::Type} if ast.is_a? Mal::List + return ast.map { |n| eval(n, env).as(Mal::Type) } if ast.is_a? Mal::List val = ast.unwrap @@ -34,13 +34,13 @@ module Mal eval_error "'#{val.str}' not found" end when Mal::List - val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} + val.each_with_object(Mal::List.new) { |n, l| l << eval(n, env) } when Mal::Vector - val.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} + val.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) } when Array(Mal::Type) - val.map{|n| eval(n, env)} + val.map { |n| eval(n, env).as(Mal::Type) } when Mal::HashMap - val.each{|k, v| val[k] = eval(v, env)} + val.each { |k, v| val[k] = eval(v, env) } val else val @@ -51,9 +51,9 @@ module Mal f = eval(list.first, env).unwrap case f when Mal::Closure - f.fn.call eval_ast(list[1..-1].each_with_object(Mal::List.new){|i, l| l << i}, env) + f.fn.call eval_ast(list[1..-1].each_with_object(Mal::List.new) { |i, l| l << i }, env) when Mal::Func - f.call eval_ast(list[1..-1].each_with_object(Mal::List.new){|i, l| l << i}, env) + f.call eval_ast(list[1..-1].each_with_object(Mal::List.new) { |i, l| l << i }, env) else eval_error "expected function as the first argument" end @@ -94,55 +94,55 @@ module Mal end return Mal::Type.new case head.str - when "def!" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'def!' must be symbol" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env)) - when "let*" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - - bindings = list[1].unwrap - eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array - eval_error "size of binding list must be even" unless bindings.size.even? - - new_env = Mal::Env.new env - bindings.each_slice(2) do |binding| - key, value = binding - name = key.unwrap - eval_error "name of binding must be specified as symbol" unless name.is_a? Mal::Symbol - new_env.set(name.str, eval(value, new_env)) - end - - ast, env = list[2], new_env - next # TCO - when "do" - if list.empty? - ast = Mal::Type.new nil - next - end - - eval_ast(list[1..-2].each_with_object(Mal::List.new){|i,l| l << i}, env) - ast = list.last - next # TCO - when "if" - ast = unless eval(list[1], env).unwrap - list.size >= 4 ? list[3] : Mal::Type.new(nil) - else - list[2] - end - next # TCO - when "fn*" - # Note: - # If writing lambda expression here directly, compiler will fail to infer type of 'list'. (Error 'Nil for empty?') - params = list[1].unwrap - unless params.is_a? Array - eval_error "'fn*' parameters must be list" - end - Mal::Closure.new(list[2], params, env, func_of(env, list[1].unwrap, list[2])) + when "def!" + eval_error "wrong number of argument for 'def!'" unless list.size == 3 + a1 = list[1].unwrap + eval_error "1st argument of 'def!' must be symbol" unless a1.is_a? Mal::Symbol + env.set(a1.str, eval(list[2], env)) + when "let*" + eval_error "wrong number of argument for 'def!'" unless list.size == 3 + + bindings = list[1].unwrap + eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array + eval_error "size of binding list must be even" unless bindings.size.even? + + new_env = Mal::Env.new env + bindings.each_slice(2) do |binding| + key, value = binding + name = key.unwrap + eval_error "name of binding must be specified as symbol" unless name.is_a? Mal::Symbol + new_env.set(name.str, eval(value, new_env)) + end + + ast, env = list[2], new_env + next # TCO + when "do" + if list.empty? + ast = Mal::Type.new nil + next + end + + eval_ast(list[1..-2].each_with_object(Mal::List.new) { |i, l| l << i }, env) + ast = list.last + next # TCO + when "if" + ast = unless eval(list[1], env).unwrap + list.size >= 4 ? list[3] : Mal::Type.new(nil) else - invoke_list list + list[2] end + next # TCO + when "fn*" + # Note: + # If writing lambda expression here directly, compiler will fail to infer type of 'list'. (Error 'Nil for empty?') + params = list[1].unwrap + unless params.is_a? Array + eval_error "'fn*' parameters must be list" + end + Mal::Closure.new(list[2], params, env, func_of(env, list[1].unwrap, list[2])) + else + invoke_list list + end end end @@ -151,15 +151,15 @@ module Mal end def rep(str) - print(eval(read(str), $repl_env)) + print(eval(read(str), REPL_ENV)) end end -$repl_env = Mal::Env.new nil -Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))} +REPL_ENV = Mal::Env.new nil +Mal::NS.each { |k, v| REPL_ENV.set(k, Mal::Type.new(v)) } Mal.rep "(def! not (fn* (a) (if a false true)))" -while line = my_readline("user> ") +while line = Readline.readline("user> ", true) begin puts Mal.rep(line) rescue e diff --git a/crystal/step6_file.cr b/crystal/step6_file.cr index 594ed9279c..9997a2f7fa 100755 --- a/crystal/step6_file.cr +++ b/crystal/step6_file.cr @@ -1,6 +1,6 @@ #! /usr/bin/env crystal run -require "./readline" +require "readline" require "./reader" require "./printer" require "./types" @@ -15,14 +15,14 @@ module Mal extend self def func_of(env, binds, body) - -> (args : Array(Mal::Type)) { + ->(args : Array(Mal::Type)) { new_env = Mal::Env.new(env, binds, args) eval(body, new_env) - } as Mal::Func + }.as(Mal::Func) end def eval_ast(ast, env) - return ast.map{|n| eval(n, env) as Mal::Type} if ast.is_a? Mal::List + return ast.map { |n| eval(n, env).as(Mal::Type) } if ast.is_a? Mal::List val = ast.unwrap @@ -34,13 +34,13 @@ module Mal eval_error "'#{val.str}' not found" end when Mal::List - val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} + val.each_with_object(Mal::List.new) { |n, l| l << eval(n, env) } when Mal::Vector - val.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} + val.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) } when Array(Mal::Type) - val.map{|n| eval(n, env)} + val.map { |n| eval(n, env).as(Mal::Type) } when Mal::HashMap - val.each{|k, v| val[k] = eval(v, env)} + val.each { |k, v| val[k] = eval(v, env) } val else val @@ -51,9 +51,9 @@ module Mal f = eval(list.first, env).unwrap case f when Mal::Closure - f.fn.call eval_ast(list[1..-1].each_with_object(Mal::List.new){|i, l| l << i}, env) + f.fn.call eval_ast(list[1..-1].each_with_object(Mal::List.new) { |i, l| l << i }, env) when Mal::Func - f.call eval_ast(list[1..-1].each_with_object(Mal::List.new){|i, l| l << i}, env) + f.call eval_ast(list[1..-1].each_with_object(Mal::List.new) { |i, l| l << i }, env) else eval_error "expected function as the first argument" end @@ -94,53 +94,53 @@ module Mal end return Mal::Type.new case head.str - when "def!" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'def!' must be symbol" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env)) - when "let*" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - - bindings = list[1].unwrap - eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array - eval_error "size of binding list must be even" unless bindings.size.even? - - new_env = Mal::Env.new env - bindings.each_slice(2) do |binding| - key, value = binding - name = key.unwrap - eval_error "name of binding must be specified as symbol" unless name.is_a? Mal::Symbol - new_env.set(name.str, eval(value, new_env)) - end - - ast, env = list[2], new_env - next # TCO - when "do" - if list.empty? - ast = Mal::Type.new nil - next - end - - eval_ast(list[1..-2].each_with_object(Mal::List.new){|i,l| l << i}, env) - ast = list.last - next # TCO - when "if" - ast = unless eval(list[1], env).unwrap - list.size >= 4 ? list[3] : Mal::Type.new(nil) - else - list[2] - end - next # TCO - when "fn*" - params = list[1].unwrap - unless params.is_a? Array - eval_error "'fn*' parameters must be list" - end - Mal::Closure.new(list[2], params, env, func_of(env, params, list[2])) + when "def!" + eval_error "wrong number of argument for 'def!'" unless list.size == 3 + a1 = list[1].unwrap + eval_error "1st argument of 'def!' must be symbol" unless a1.is_a? Mal::Symbol + env.set(a1.str, eval(list[2], env)) + when "let*" + eval_error "wrong number of argument for 'def!'" unless list.size == 3 + + bindings = list[1].unwrap + eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array + eval_error "size of binding list must be even" unless bindings.size.even? + + new_env = Mal::Env.new env + bindings.each_slice(2) do |binding| + key, value = binding + name = key.unwrap + eval_error "name of binding must be specified as symbol" unless name.is_a? Mal::Symbol + new_env.set(name.str, eval(value, new_env)) + end + + ast, env = list[2], new_env + next # TCO + when "do" + if list.empty? + ast = Mal::Type.new nil + next + end + + eval_ast(list[1..-2].each_with_object(Mal::List.new) { |i, l| l << i }, env) + ast = list.last + next # TCO + when "if" + ast = unless eval(list[1], env).unwrap + list.size >= 4 ? list[3] : Mal::Type.new(nil) else - invoke_list(list, env) + list[2] end + next # TCO + when "fn*" + params = list[1].unwrap + unless params.is_a? Array + eval_error "'fn*' parameters must be list" + end + Mal::Closure.new(list[2], params, env, func_of(env, params, list[2])) + else + invoke_list(list, env) + end end end @@ -149,22 +149,22 @@ module Mal end def rep(str) - print(eval(read(str), $repl_env)) + print(eval(read(str), REPL_ENV)) end end -$repl_env = Mal::Env.new nil -Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))} -$repl_env.set("eval", Mal::Type.new -> (args: Array(Mal::Type)){ Mal.eval(args[0], $repl_env) }) +REPL_ENV = Mal::Env.new nil +Mal::NS.each { |k, v| REPL_ENV.set(k, Mal::Type.new(v)) } +REPL_ENV.set("eval", Mal::Type.new ->(args : Array(Mal::Type)) { Mal.eval(args[0], REPL_ENV) }) Mal.rep "(def! not (fn* (a) (if a false true)))" Mal.rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" -$argv = Mal::List.new -$repl_env.set("*ARGV*", Mal::Type.new $argv) +argv = Mal::List.new +REPL_ENV.set("*ARGV*", Mal::Type.new argv) unless ARGV.empty? if ARGV.size > 1 ARGV[1..-1].each do |a| - $argv << Mal::Type.new(a) + argv << Mal::Type.new(a) end end @@ -172,7 +172,7 @@ unless ARGV.empty? exit end -while line = my_readline("user> ") +while line = Readline.readline("user> ", true) begin puts Mal.rep(line) rescue e diff --git a/crystal/step7_quote.cr b/crystal/step7_quote.cr index a1d5708ff3..92d2ab44ec 100755 --- a/crystal/step7_quote.cr +++ b/crystal/step7_quote.cr @@ -1,6 +1,6 @@ #! /usr/bin/env crystal run -require "./readline" +require "readline" require "./reader" require "./printer" require "./types" @@ -15,14 +15,14 @@ module Mal extend self def func_of(env, binds, body) - -> (args : Array(Mal::Type)) { + ->(args : Array(Mal::Type)) { new_env = Mal::Env.new(env, binds, args) eval(body, new_env) - } as Mal::Func + }.as(Mal::Func) end def eval_ast(ast, env) - return ast.map{|n| eval(n, env) as Mal::Type} if ast.is_a? Mal::List + return ast.map { |n| eval(n, env).as(Mal::Type) } if ast.is_a? Mal::List val = ast.unwrap @@ -34,13 +34,13 @@ module Mal eval_error "'#{val.str}' not found" end when Mal::List - val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} + val.each_with_object(Mal::List.new) { |n, l| l << eval(n, env) } when Mal::Vector - val.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} + val.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) } when Array(Mal::Type) - val.map{|n| eval(n, env)} + val.map { |n| eval(n, env).as(Mal::Type) } when Mal::HashMap - val.each{|k, v| val[k] = eval(v, env)} + val.each { |k, v| val[k] = eval(v, env) } val else val @@ -70,14 +70,14 @@ module Mal # ("unquote" ...) when head.is_a?(Mal::Symbol) && head.str == "unquote" list[1] - # (("splice-unquote" ...) ...) + # (("splice-unquote" ...) ...) when is_pair(head) && (arg0 = head.first.unwrap).is_a?(Mal::Symbol) && arg0.str == "splice-unquote" - tail = Mal::Type.new list[1..-1].each_with_object(Mal::List.new){|e,l| l << e} + tail = Mal::Type.new list[1..-1].each_with_object(Mal::List.new) { |e, l| l << e } Mal::Type.new( Mal::List.new << gen_type(Mal::Symbol, "concat") << head[1] << quasiquote(tail) ) else - tail = Mal::Type.new list[1..-1].each_with_object(Mal::List.new){|e,l| l << e} + tail = Mal::Type.new list[1..-1].each_with_object(Mal::List.new) { |e, l| l << e } Mal::Type.new( Mal::List.new << gen_type(Mal::Symbol, "cons") << quasiquote(list.first) << quasiquote(tail) ) @@ -115,58 +115,58 @@ module Mal end return Mal::Type.new case head.str - when "def!" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'def!' must be symbol" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env)) - when "let*" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - - bindings = list[1].unwrap - eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array - eval_error "size of binding list must be even" unless bindings.size.even? - - new_env = Mal::Env.new env - bindings.each_slice(2) do |binding| - key, value = binding - name = key.unwrap - eval_error "name of binding must be specified as symbol" unless name.is_a? Mal::Symbol - new_env.set(name.str, eval(value, new_env)) - end - - ast, env = list[2], new_env - next # TCO - when "do" - if list.empty? - ast = Mal::Type.new nil - next - end - - eval_ast(list[1..-2].each_with_object(Mal::List.new){|i,l| l << i}, env) - ast = list.last - next # TCO - when "if" - ast = unless eval(list[1], env).unwrap - list.size >= 4 ? list[3] : Mal::Type.new(nil) - else - list[2] - end - next # TCO - when "fn*" - params = list[1].unwrap - unless params.is_a? Array - eval_error "'fn*' parameters must be list" - end - Mal::Closure.new(list[2], params, env, func_of(env, params, list[2])) - when "quote" - list[1] - when "quasiquote" - ast = quasiquote list[1] - next # TCO + when "def!" + eval_error "wrong number of argument for 'def!'" unless list.size == 3 + a1 = list[1].unwrap + eval_error "1st argument of 'def!' must be symbol" unless a1.is_a? Mal::Symbol + env.set(a1.str, eval(list[2], env)) + when "let*" + eval_error "wrong number of argument for 'def!'" unless list.size == 3 + + bindings = list[1].unwrap + eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array + eval_error "size of binding list must be even" unless bindings.size.even? + + new_env = Mal::Env.new env + bindings.each_slice(2) do |binding| + key, value = binding + name = key.unwrap + eval_error "name of binding must be specified as symbol" unless name.is_a? Mal::Symbol + new_env.set(name.str, eval(value, new_env)) + end + + ast, env = list[2], new_env + next # TCO + when "do" + if list.empty? + ast = Mal::Type.new nil + next + end + + eval_ast(list[1..-2].each_with_object(Mal::List.new) { |i, l| l << i }, env) + ast = list.last + next # TCO + when "if" + ast = unless eval(list[1], env).unwrap + list.size >= 4 ? list[3] : Mal::Type.new(nil) else - invoke_list(list, env) + list[2] end + next # TCO + when "fn*" + params = list[1].unwrap + unless params.is_a? Array + eval_error "'fn*' parameters must be list" + end + Mal::Closure.new(list[2], params, env, func_of(env, params, list[2])) + when "quote" + list[1] + when "quasiquote" + ast = quasiquote list[1] + next # TCO + else + invoke_list(list, env) + end end end @@ -175,22 +175,22 @@ module Mal end def rep(str) - print(eval(read(str), $repl_env)) + print(eval(read(str), REPL_ENV)) end end -$repl_env = Mal::Env.new nil -Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))} -$repl_env.set("eval", Mal::Type.new -> (args: Array(Mal::Type)){ Mal.eval(args[0], $repl_env) }) +REPL_ENV = Mal::Env.new nil +Mal::NS.each { |k, v| REPL_ENV.set(k, Mal::Type.new(v)) } +REPL_ENV.set("eval", Mal::Type.new ->(args : Array(Mal::Type)) { Mal.eval(args[0], REPL_ENV) }) Mal.rep "(def! not (fn* (a) (if a false true)))" Mal.rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" -$argv = Mal::List.new -$repl_env.set("*ARGV*", Mal::Type.new $argv) +argv = Mal::List.new +REPL_ENV.set("*ARGV*", Mal::Type.new argv) unless ARGV.empty? if ARGV.size > 1 ARGV[1..-1].each do |a| - $argv << Mal::Type.new(a) + argv << Mal::Type.new(a) end end @@ -202,7 +202,7 @@ unless ARGV.empty? exit end -while line = my_readline("user> ") +while line = Readline.readline("user> ", true) begin puts Mal.rep(line) rescue e diff --git a/crystal/step8_macros.cr b/crystal/step8_macros.cr index 5f1c63af7f..061649b9af 100755 --- a/crystal/step8_macros.cr +++ b/crystal/step8_macros.cr @@ -1,6 +1,6 @@ #! /usr/bin/env crystal run -require "./readline" +require "readline" require "./reader" require "./printer" require "./types" @@ -15,14 +15,14 @@ module Mal extend self def func_of(env, binds, body) - -> (args : Array(Mal::Type)) { + ->(args : Array(Mal::Type)) { new_env = Mal::Env.new(env, binds, args) eval(body, new_env) - } as Mal::Func + }.as(Mal::Func) end def eval_ast(ast, env) - return ast.map{|n| eval(n, env) as Mal::Type} if ast.is_a? Mal::List + return ast.map { |n| eval(n, env).as(Mal::Type) } if ast.is_a? Mal::List val = ast.unwrap @@ -34,13 +34,13 @@ module Mal eval_error "'#{val.str}' not found" end when Mal::List - val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} + val.each_with_object(Mal::List.new) { |n, l| l << eval(n, env) } when Mal::Vector - val.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} + val.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) } when Array(Mal::Type) - val.map{|n| eval(n, env)} + val.map { |n| eval(n, env).as(Mal::Type) } when Mal::HashMap - val.each{|k, v| val[k] = eval(v, env)} + val.each { |k, v| val[k] = eval(v, env) } val else val @@ -70,14 +70,14 @@ module Mal # ("unquote" ...) when head.is_a?(Mal::Symbol) && head.str == "unquote" list[1] - # (("splice-unquote" ...) ...) + # (("splice-unquote" ...) ...) when pair?(head) && (arg0 = head.first.unwrap).is_a?(Mal::Symbol) && arg0.str == "splice-unquote" - tail = Mal::Type.new list[1..-1].each_with_object(Mal::List.new){|e,l| l << e} + tail = Mal::Type.new list[1..-1].each_with_object(Mal::List.new) { |e, l| l << e } Mal::Type.new( Mal::List.new << gen_type(Mal::Symbol, "concat") << head[1] << quasiquote(tail) ) else - tail = Mal::Type.new list[1..-1].each_with_object(Mal::List.new){|e,l| l << e} + tail = Mal::Type.new list[1..-1].each_with_object(Mal::List.new) { |e, l| l << e } Mal::Type.new( Mal::List.new << gen_type(Mal::Symbol, "cons") << quasiquote(list.first) << quasiquote(tail) ) @@ -99,10 +99,9 @@ module Mal def macroexpand(ast, env) while macro_call?(ast, env) - # Already checked in macro_call? - list = ast.unwrap as Mal::List - func_sym = list[0].unwrap as Mal::Symbol + list = ast.unwrap.as(Mal::List) + func_sym = list[0].unwrap.as(Mal::Symbol) func = env.get(func_sym.str).unwrap case func @@ -151,65 +150,65 @@ module Mal return invoke_list(list, env) unless head.is_a? Mal::Symbol return Mal::Type.new case head.str - when "def!" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'def!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env)) - when "let*" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - - bindings = list[1].unwrap - eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array - eval_error "size of binding list must be even" unless bindings.size.even? - - new_env = Mal::Env.new env - bindings.each_slice(2) do |binding| - key, value = binding - name = key.unwrap - eval_error "name of binding must be specified as symbol #{name}" unless name.is_a? Mal::Symbol - new_env.set(name.str, eval(value, new_env)) - end - - ast, env = list[2], new_env - next # TCO - when "do" - if list.empty? - ast = Mal::Type.new nil - next - end - - eval_ast(list[1..-2].each_with_object(Mal::List.new){|i,l| l << i}, env) - ast = list.last - next # TCO - when "if" - ast = unless eval(list[1], env).unwrap - list.size >= 4 ? list[3] : Mal::Type.new(nil) - else - list[2] - end - next # TCO - when "fn*" - params = list[1].unwrap - unless params.is_a? Array - eval_error "'fn*' parameters must be list or vector: #{params}" - end - Mal::Closure.new(list[2], params, env, func_of(env, params, list[2])) - when "quote" - list[1] - when "quasiquote" - ast = quasiquote list[1] - next # TCO - when "defmacro!" - eval_error "wrong number of argument for 'defmacro!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'defmacro!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env).tap{|n| n.is_macro = true}) - when "macroexpand" - macroexpand(list[1], env) + when "def!" + eval_error "wrong number of argument for 'def!'" unless list.size == 3 + a1 = list[1].unwrap + eval_error "1st argument of 'def!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol + env.set(a1.str, eval(list[2], env)) + when "let*" + eval_error "wrong number of argument for 'def!'" unless list.size == 3 + + bindings = list[1].unwrap + eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array + eval_error "size of binding list must be even" unless bindings.size.even? + + new_env = Mal::Env.new env + bindings.each_slice(2) do |binding| + key, value = binding + name = key.unwrap + eval_error "name of binding must be specified as symbol #{name}" unless name.is_a? Mal::Symbol + new_env.set(name.str, eval(value, new_env)) + end + + ast, env = list[2], new_env + next # TCO + when "do" + if list.empty? + ast = Mal::Type.new nil + next + end + + eval_ast(list[1..-2].each_with_object(Mal::List.new) { |i, l| l << i }, env) + ast = list.last + next # TCO + when "if" + ast = unless eval(list[1], env).unwrap + list.size >= 4 ? list[3] : Mal::Type.new(nil) else - invoke_list(list, env) + list[2] + end + next # TCO + when "fn*" + params = list[1].unwrap + unless params.is_a? Array + eval_error "'fn*' parameters must be list or vector: #{params}" end + Mal::Closure.new(list[2], params, env, func_of(env, params, list[2])) + when "quote" + list[1] + when "quasiquote" + ast = quasiquote list[1] + next # TCO + when "defmacro!" + eval_error "wrong number of argument for 'defmacro!'" unless list.size == 3 + a1 = list[1].unwrap + eval_error "1st argument of 'defmacro!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol + env.set(a1.str, eval(list[2], env).tap { |n| n.is_macro = true }) + when "macroexpand" + macroexpand(list[1], env) + else + invoke_list(list, env) + end end end @@ -218,25 +217,25 @@ module Mal end def rep(str) - print(eval(read(str), $repl_env)) + print(eval(read(str), REPL_ENV)) end end -$repl_env = Mal::Env.new nil -Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))} -$repl_env.set("eval", Mal::Type.new -> (args: Array(Mal::Type)){ Mal.eval(args[0], $repl_env) }) +REPL_ENV = Mal::Env.new nil +Mal::NS.each { |k, v| REPL_ENV.set(k, Mal::Type.new(v)) } +REPL_ENV.set("eval", Mal::Type.new ->(args : Array(Mal::Type)) { Mal.eval(args[0], REPL_ENV) }) Mal.rep "(def! not (fn* (a) (if a false true)))" Mal.rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" Mal.rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" Mal.rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" -$argv = Mal::List.new -$repl_env.set("*ARGV*", Mal::Type.new $argv) +argv = Mal::List.new +REPL_ENV.set("*ARGV*", Mal::Type.new argv) unless ARGV.empty? if ARGV.size > 1 ARGV[1..-1].each do |a| - $argv << Mal::Type.new(a) + argv << Mal::Type.new(a) end end @@ -248,7 +247,7 @@ unless ARGV.empty? exit end -while line = my_readline("user> ") +while line = Readline.readline("user> ", true) begin puts Mal.rep(line) rescue e diff --git a/crystal/step9_try.cr b/crystal/step9_try.cr index 74685d4ae5..4853cc80f3 100755 --- a/crystal/step9_try.cr +++ b/crystal/step9_try.cr @@ -1,6 +1,6 @@ #! /usr/bin/env crystal run -require "./readline" +require "readline" require "./reader" require "./printer" require "./types" @@ -15,14 +15,14 @@ module Mal extend self def func_of(env, binds, body) - -> (args : Array(Mal::Type)) { + ->(args : Array(Mal::Type)) { new_env = Mal::Env.new(env, binds, args) eval(body, new_env) - } as Mal::Func + }.as(Mal::Func) end def eval_ast(ast, env) - return ast.map{|n| eval(n, env) as Mal::Type} if ast.is_a? Mal::List + return ast.map { |n| eval(n, env).as(Mal::Type) } if ast.is_a? Mal::List val = ast.unwrap @@ -34,13 +34,13 @@ module Mal eval_error "'#{val.str}' not found" end when Mal::List - val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} + val.each_with_object(Mal::List.new) { |n, l| l << eval(n, env) } when Mal::Vector - val.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} + val.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) } when Array(Mal::Type) - val.map{|n| eval(n, env)} + val.map { |n| eval(n, env).as(Mal::Type) } when Mal::HashMap - val.each{|k, v| val[k] = eval(v, env)} + val.each { |k, v| val[k] = eval(v, env) } val else val @@ -70,7 +70,7 @@ module Mal # ("unquote" ...) when head.is_a?(Mal::Symbol) && head.str == "unquote" list[1] - # (("splice-unquote" ...) ...) + # (("splice-unquote" ...) ...) when pair?(head) && (arg0 = head.first.unwrap).is_a?(Mal::Symbol) && arg0.str == "splice-unquote" tail = Mal::Type.new list[1..-1].to_mal Mal::Type.new( @@ -99,10 +99,9 @@ module Mal def macroexpand(ast, env) while macro_call?(ast, env) - # Already checked in macro_call? - list = ast.unwrap as Mal::List - func_sym = list[0].unwrap as Mal::Symbol + list = ast.unwrap.as(Mal::List) + func_sym = list[0].unwrap.as(Mal::Symbol) func = env.get(func_sym.str).unwrap case func @@ -151,82 +150,82 @@ module Mal return invoke_list(list, env) unless head.is_a? Mal::Symbol return Mal::Type.new case head.str - when "def!" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'def!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env)) - when "let*" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - - bindings = list[1].unwrap - eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array - eval_error "size of binding list must be even" unless bindings.size.even? - - new_env = Mal::Env.new env - bindings.each_slice(2) do |binding| - key, value = binding - name = key.unwrap - eval_error "name of binding must be specified as symbol #{name}" unless name.is_a? Mal::Symbol - new_env.set(name.str, eval(value, new_env)) - end - - ast, env = list[2], new_env - next # TCO - when "do" - if list.empty? - ast = Mal::Type.new nil - next - end - - eval_ast(list[1..-2].to_mal, env) - ast = list.last - next # TCO - when "if" - ast = unless eval(list[1], env).unwrap - list.size >= 4 ? list[3] : Mal::Type.new(nil) - else - list[2] - end - next # TCO - when "fn*" - params = list[1].unwrap - unless params.is_a? Array - eval_error "'fn*' parameters must be list or vector: #{params}" - end - Mal::Closure.new(list[2], params, env, func_of(env, params, list[2])) - when "quote" - list[1] - when "quasiquote" - ast = quasiquote list[1] - next # TCO - when "defmacro!" - eval_error "wrong number of argument for 'defmacro!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'defmacro!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env).tap{|n| n.is_macro = true}) - when "macroexpand" - macroexpand(list[1], env) - when "try*" - catch_list = list[2].unwrap - return eval(list[1], env) unless catch_list.is_a? Mal::List - - catch_head = catch_list.first.unwrap - return eval(list[1], env) unless catch_head.is_a? Mal::Symbol - return eval(list[1], env) unless catch_head.str == "catch*" - - begin - eval(list[1], env) - rescue e : Mal::RuntimeException - new_env = Mal::Env.new(env, [catch_list[1]], [e.thrown]) - eval(catch_list[2], new_env) - rescue e - new_env = Mal::Env.new(env, [catch_list[1]], [Mal::Type.new e.message]) - eval(catch_list[2], new_env) - end + when "def!" + eval_error "wrong number of argument for 'def!'" unless list.size == 3 + a1 = list[1].unwrap + eval_error "1st argument of 'def!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol + env.set(a1.str, eval(list[2], env)) + when "let*" + eval_error "wrong number of argument for 'def!'" unless list.size == 3 + + bindings = list[1].unwrap + eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array + eval_error "size of binding list must be even" unless bindings.size.even? + + new_env = Mal::Env.new env + bindings.each_slice(2) do |binding| + key, value = binding + name = key.unwrap + eval_error "name of binding must be specified as symbol #{name}" unless name.is_a? Mal::Symbol + new_env.set(name.str, eval(value, new_env)) + end + + ast, env = list[2], new_env + next # TCO + when "do" + if list.empty? + ast = Mal::Type.new nil + next + end + + eval_ast(list[1..-2].to_mal, env) + ast = list.last + next # TCO + when "if" + ast = unless eval(list[1], env).unwrap + list.size >= 4 ? list[3] : Mal::Type.new(nil) else - invoke_list(list, env) + list[2] end + next # TCO + when "fn*" + params = list[1].unwrap + unless params.is_a? Array + eval_error "'fn*' parameters must be list or vector: #{params}" + end + Mal::Closure.new(list[2], params, env, func_of(env, params, list[2])) + when "quote" + list[1] + when "quasiquote" + ast = quasiquote list[1] + next # TCO + when "defmacro!" + eval_error "wrong number of argument for 'defmacro!'" unless list.size == 3 + a1 = list[1].unwrap + eval_error "1st argument of 'defmacro!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol + env.set(a1.str, eval(list[2], env).tap { |n| n.is_macro = true }) + when "macroexpand" + macroexpand(list[1], env) + when "try*" + catch_list = list[2].unwrap + return eval(list[1], env) unless catch_list.is_a? Mal::List + + catch_head = catch_list.first.unwrap + return eval(list[1], env) unless catch_head.is_a? Mal::Symbol + return eval(list[1], env) unless catch_head.str == "catch*" + + begin + eval(list[1], env) + rescue e : Mal::RuntimeException + new_env = Mal::Env.new(env, [catch_list[1]], [e.thrown]) + eval(catch_list[2], new_env) + rescue e + new_env = Mal::Env.new(env, [catch_list[1]], [Mal::Type.new e.message]) + eval(catch_list[2], new_env) + end + else + invoke_list(list, env) + end end end @@ -235,25 +234,25 @@ module Mal end def rep(str) - print(eval(read(str), $repl_env)) + print(eval(read(str), REPL_ENV)) end end -$repl_env = Mal::Env.new nil -Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))} -$repl_env.set("eval", Mal::Type.new -> (args: Array(Mal::Type)){ Mal.eval(args[0], $repl_env) }) +REPL_ENV = Mal::Env.new nil +Mal::NS.each { |k, v| REPL_ENV.set(k, Mal::Type.new(v)) } +REPL_ENV.set("eval", Mal::Type.new ->(args : Array(Mal::Type)) { Mal.eval(args[0], REPL_ENV) }) Mal.rep "(def! not (fn* (a) (if a false true)))" Mal.rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" Mal.rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" Mal.rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" -$argv = Mal::List.new -$repl_env.set("*ARGV*", Mal::Type.new $argv) +argv = Mal::List.new +REPL_ENV.set("*ARGV*", Mal::Type.new argv) unless ARGV.empty? if ARGV.size > 1 ARGV[1..-1].each do |a| - $argv << Mal::Type.new(a) + argv << Mal::Type.new(a) end end @@ -265,7 +264,7 @@ unless ARGV.empty? exit end -while line = my_readline("user> ") +while line = Readline.readline("user> ", true) begin puts Mal.rep(line) rescue e diff --git a/crystal/stepA_mal.cr b/crystal/stepA_mal.cr index baaa8a6c9c..f71be26aa3 100755 --- a/crystal/stepA_mal.cr +++ b/crystal/stepA_mal.cr @@ -2,7 +2,7 @@ require "colorize" -require "./readline" +require "readline" require "./reader" require "./printer" require "./types" @@ -17,14 +17,14 @@ module Mal extend self def func_of(env, binds, body) - -> (args : Array(Mal::Type)) { + ->(args : Array(Mal::Type)) { new_env = Mal::Env.new(env, binds, args) eval(body, new_env) - } as Mal::Func + }.as(Mal::Func) end def eval_ast(ast, env) - return ast.map{|n| eval(n, env) as Mal::Type} if ast.is_a? Array + return ast.map { |n| eval(n, env).as(Mal::Type) } if ast.is_a? Array val = ast.unwrap @@ -36,12 +36,12 @@ module Mal eval_error "'#{val.str}' not found" end when Mal::List - val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} + val.each_with_object(Mal::List.new) { |n, l| l << eval(n, env) } when Mal::Vector - val.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} + val.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) } when Mal::HashMap new_map = Mal::HashMap.new - val.each{|k, v| new_map[k] = eval(v, env)} + val.each { |k, v| new_map[k] = eval(v, env) } new_map else val @@ -71,7 +71,7 @@ module Mal # ("unquote" ...) when head.is_a?(Mal::Symbol) && head.str == "unquote" list[1] - # (("splice-unquote" ...) ...) + # (("splice-unquote" ...) ...) when pair?(head) && (arg0 = head.first.unwrap).is_a?(Mal::Symbol) && arg0.str == "splice-unquote" tail = Mal::Type.new list[1..-1].to_mal Mal::Type.new( @@ -101,10 +101,9 @@ module Mal def macroexpand(ast, env) while macro_call?(ast, env) - # Already checked in macro_call? - list = ast.unwrap as Mal::List - func_sym = list[0].unwrap as Mal::Symbol + list = ast.unwrap.as(Mal::List) + func_sym = list[0].unwrap.as(Mal::Symbol) func = env.get(func_sym.str).unwrap case func @@ -122,7 +121,7 @@ module Mal macro invoke_list(l, env) f = eval({{l}}.first, {{env}}).unwrap - args = eval_ast({{l}}[1..-1], {{env}}) as Array + args = eval_ast({{l}}[1..-1], {{env}}).as(Array) case f when Mal::Closure @@ -158,82 +157,82 @@ module Mal return invoke_list(list, env) unless head.is_a? Mal::Symbol return Mal::Type.new case head.str - when "def!" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'def!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env)) - when "let*" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - - bindings = list[1].unwrap - eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array - eval_error "size of binding list must be even" unless bindings.size.even? - - new_env = Mal::Env.new env - bindings.each_slice(2) do |binding| - key, value = binding - name = key.unwrap - eval_error "name of binding must be specified as symbol #{name}" unless name.is_a? Mal::Symbol - new_env.set(name.str, eval(value, new_env)) - end - - ast, env = list[2], new_env - next # TCO - when "do" - if list.empty? - ast = Mal::Type.new nil - next - end - - eval_ast(list[1..-2].to_mal, env) - ast = list.last - next # TCO - when "if" - ast = unless eval(list[1], env).unwrap - list.size >= 4 ? list[3] : Mal::Type.new(nil) - else - list[2] - end - next # TCO - when "fn*" - params = list[1].unwrap - unless params.is_a? Array - eval_error "'fn*' parameters must be list or vector: #{params}" - end - Mal::Closure.new(list[2], params, env, func_of(env, params, list[2])) - when "quote" - list[1] - when "quasiquote" - ast = quasiquote list[1] - next # TCO - when "defmacro!" - eval_error "wrong number of argument for 'defmacro!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'defmacro!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env).tap{|n| n.is_macro = true}) - when "macroexpand" - macroexpand(list[1], env) - when "try*" - catch_list = list[2].unwrap - return eval(list[1], env) unless catch_list.is_a? Mal::List - - catch_head = catch_list.first.unwrap - return eval(list[1], env) unless catch_head.is_a? Mal::Symbol - return eval(list[1], env) unless catch_head.str == "catch*" - - begin - eval(list[1], env) - rescue e : Mal::RuntimeException - new_env = Mal::Env.new(env, [catch_list[1]], [e.thrown]) - eval(catch_list[2], new_env) - rescue e - new_env = Mal::Env.new(env, [catch_list[1]], [Mal::Type.new e.message]) - eval(catch_list[2], new_env) - end + when "def!" + eval_error "wrong number of argument for 'def!'" unless list.size == 3 + a1 = list[1].unwrap + eval_error "1st argument of 'def!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol + env.set(a1.str, eval(list[2], env)) + when "let*" + eval_error "wrong number of argument for 'def!'" unless list.size == 3 + + bindings = list[1].unwrap + eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array + eval_error "size of binding list must be even" unless bindings.size.even? + + new_env = Mal::Env.new env + bindings.each_slice(2) do |binding| + key, value = binding + name = key.unwrap + eval_error "name of binding must be specified as symbol #{name}" unless name.is_a? Mal::Symbol + new_env.set(name.str, eval(value, new_env)) + end + + ast, env = list[2], new_env + next # TCO + when "do" + if list.empty? + ast = Mal::Type.new nil + next + end + + eval_ast(list[1..-2].to_mal, env) + ast = list.last + next # TCO + when "if" + ast = unless eval(list[1], env).unwrap + list.size >= 4 ? list[3] : Mal::Type.new(nil) else - invoke_list(list, env) + list[2] end + next # TCO + when "fn*" + params = list[1].unwrap + unless params.is_a? Array + eval_error "'fn*' parameters must be list or vector: #{params}" + end + Mal::Closure.new(list[2], params, env, func_of(env, params, list[2])) + when "quote" + list[1] + when "quasiquote" + ast = quasiquote list[1] + next # TCO + when "defmacro!" + eval_error "wrong number of argument for 'defmacro!'" unless list.size == 3 + a1 = list[1].unwrap + eval_error "1st argument of 'defmacro!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol + env.set(a1.str, eval(list[2], env).tap { |n| n.is_macro = true }) + when "macroexpand" + macroexpand(list[1], env) + when "try*" + catch_list = list[2].unwrap + return eval(list[1], env) unless catch_list.is_a? Mal::List + + catch_head = catch_list.first.unwrap + return eval(list[1], env) unless catch_head.is_a? Mal::Symbol + return eval(list[1], env) unless catch_head.str == "catch*" + + begin + eval(list[1], env) + rescue e : Mal::RuntimeException + new_env = Mal::Env.new(env, [catch_list[1]], [e.thrown]) + eval(catch_list[2], new_env) + rescue e + new_env = Mal::Env.new(env, [catch_list[1]], [Mal::Type.new e.message]) + eval(catch_list[2], new_env) + end + else + invoke_list(list, env) + end end end @@ -242,13 +241,13 @@ module Mal end def rep(str) - print(eval(read(str), $repl_env)) + print(eval(read(str), REPL_ENV)) end end -$repl_env = Mal::Env.new nil -Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))} -$repl_env.set("eval", Mal::Type.new -> (args: Array(Mal::Type)){ Mal.eval(args[0], $repl_env) }) +REPL_ENV = Mal::Env.new nil +Mal::NS.each { |k, v| REPL_ENV.set(k, Mal::Type.new(v)) } +REPL_ENV.set("eval", Mal::Type.new ->(args : Array(Mal::Type)) { Mal.eval(args[0], REPL_ENV) }) Mal.rep "(def! not (fn* (a) (if a false true)))" Mal.rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" Mal.rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" @@ -257,13 +256,13 @@ Mal.rep "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* Mal.rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))" Mal.rep("(def! *host-language* \"crystal\")") -$argv = Mal::List.new -$repl_env.set("*ARGV*", Mal::Type.new $argv) +argv = Mal::List.new +REPL_ENV.set("*ARGV*", Mal::Type.new argv) unless ARGV.empty? if ARGV.size > 1 ARGV[1..-1].each do |a| - $argv << Mal::Type.new(a) + argv << Mal::Type.new(a) end end @@ -277,7 +276,7 @@ end Mal.rep("(println (str \"Mal [\" *host-language* \"]\"))") -while line = my_readline("user> ") +while line = Readline.readline("user> ", true) begin puts Mal.rep(line) rescue e diff --git a/crystal/types.cr b/crystal/types.cr index 0c879f923f..10c36da5fa 100644 --- a/crystal/types.cr +++ b/crystal/types.cr @@ -1,50 +1,14 @@ require "./printer" module Mal - class Symbol - property :str - def initialize(@str : String) - end - - def ==(other : Symbol) - @str == other.str - end - end - - class List < Array(Type) - end - - class Vector < Array(Type) - end - - class HashMap < Hash(String, Type) - end - - class Atom - property :val - def initialize(@val : Type) - end - - def ==(rhs : Atom) - @val == rhs.val - end - end - - class Closure - property :ast, :params, :env, :fn - def initialize(@ast : Type, @params : List | Vector, @env : Env, @fn : Func) - end - end - class Type alias Func = (Array(Type) -> Type) - alias ValueType = Nil | Bool | Int64 | String | Symbol | List | Vector | HashMap | Func | Closure | Atom property :is_macro, :meta def initialize(@val : ValueType) @is_macro = false - @meta = nil as Type? + @meta = nil.as(Type | Nil) end def initialize(other : Type) @@ -96,6 +60,45 @@ module Mal rel_op :<, :>, :<=, :>= end + class Symbol + property :str + + def initialize(@str : String) + end + + def ==(other : Symbol) + @str == other.str + end + end + + class List < Array(Type) + end + + class Vector < Array(Type) + end + + class HashMap < Hash(String, Type) + end + + class Atom + property :val + + def initialize(@val : Type) + end + + def ==(rhs : Atom) + @val == rhs.val + end + end + + class Closure + property :ast, :params, :env, :fn + + def initialize(@ast : Type, @params : Array(Mal::Type) | List | Vector, @env : Env, @fn : Func) + end + end + + alias Type::ValueType = Nil | Bool | Int64 | String | Symbol | List | Vector | HashMap | Func | Closure | Atom alias Func = Type::Func end @@ -105,7 +108,6 @@ end class Array def to_mal(t = Mal::List) - each_with_object(t.new){|e, l| l << e} + each_with_object(t.new) { |e, l| l << e } end end - From 9bd2b1ef868fd3621c73355b966fd336762a67f3 Mon Sep 17 00:00:00 2001 From: Vitaly Shukela Date: Sat, 10 Nov 2018 00:24:56 +0300 Subject: [PATCH 0700/2308] Add link to my Rust implementation I assume this README serves as a wiki for collecting impls. --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ecda6ebd5f..9e6585e6a7 100644 --- a/README.md +++ b/README.md @@ -891,7 +891,10 @@ cd rust cargo run --release --bin stepX_YYY ``` -There is also a separate implementation in [Rust by Tim Morgan](https://github.com/seven1m/mal-rust). +Alternative out-of-tee Rust implementations: + +* [by Tim Morgan](https://github.com/seven1m/mal-rust). +* [by vi](https://github.com/vi/mal-rust-vi) - using [Pest](https://pest.rs/) grammar, not using typical Mal infrastructure (cargo-ized steps and built-in converted tests). ### Scala ### From e10ceff503b702b513ab61233bde06ef00281df1 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 28 Nov 2018 17:12:17 -0600 Subject: [PATCH 0701/2308] Update JS impls: use ffi-napi and Ubuntu 18.04 --- .gitignore | 3 +++ clojure/Dockerfile | 11 +++++++---- clojure/package.json | 4 ++-- clojure/project.clj | 2 +- clojure/src/mal/node_readline.js | 2 +- clojure/src/mal/readline.cljs | 2 +- coffee/Dockerfile | 12 +++++------- coffee/node_readline.coffee | 2 +- coffee/package.json | 4 ++-- crystal/Dockerfile | 4 ++-- elm/Dockerfile | 14 +++++++++----- elm/node_readline.js | 2 +- elm/package.json | 2 +- es6/Dockerfile | 8 ++++---- es6/core.mjs | 8 +++++--- es6/node_readline.js | 2 +- es6/package.json | 6 +++--- es6/run | 2 +- es6/step0_repl.mjs | 3 ++- es6/step1_read_print.mjs | 3 ++- es6/step2_eval.mjs | 3 ++- es6/step3_env.mjs | 3 ++- es6/step4_if_fn_do.mjs | 3 ++- es6/step5_tco.mjs | 3 ++- es6/step6_file.mjs | 3 ++- es6/step7_quote.mjs | 3 ++- es6/step8_macros.mjs | 3 ++- es6/step9_try.mjs | 3 ++- es6/stepA_mal.mjs | 3 ++- haxe/Dockerfile | 10 ++++------ haxe/node_readline.js | 2 +- haxe/package.json | 2 +- js/Dockerfile | 11 ++++------- js/node_readline.js | 2 +- js/package.json | 2 +- livescript/Dockerfile | 8 ++++---- livescript/node_readline.js | 2 +- livescript/package.json | 4 ++-- miniMAL/Dockerfile | 10 ++++------ miniMAL/node_readline.js | 2 +- miniMAL/package.json | 2 +- ts/Dockerfile | 12 +++++++++--- ts/node_readline.ts | 2 +- ts/package.json | 4 ++-- 44 files changed, 109 insertions(+), 89 deletions(-) diff --git a/.gitignore b/.gitignore index a7558f61af..f3eac16c45 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,8 @@ haxe/*.js java/mal.jar java/target/ java/dependency-reduced-pom.xml +.npm/ +.node-gyp/ js/mal.js js/web/mal.js kotlin/*.jar @@ -103,6 +105,7 @@ ps/mal.ps python/mal.pyz r/mal.r ruby/mal.rb +.cargo/ rust/target/ rust/Cargo.lock rust/.cargo diff --git a/clojure/Dockerfile b/clojure/Dockerfile index 5060a80322..2d1dea49a3 100644 --- a/clojure/Dockerfile +++ b/clojure/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:17.10 +FROM ubuntu:18.04 MAINTAINER Joel Martin ########################################################## @@ -40,12 +40,15 @@ ENV LEIN_JVM_OPTS -Duser.home=/mal # For building node modules RUN apt-get -y install g++ -# Add nodesource apt repo config for 8.X -RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - +# Add nodesource apt repo config for 10.x stable +RUN apt-get -y install gnupg +RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - -# Install nodejs. +# Install nodejs RUN apt-get -y install nodejs +ENV NPM_CONFIG_CACHE /mal/.npm + ## Install ffi and lumo-cljs modules globally #RUN npm install -g ffi lumo-cljs diff --git a/clojure/package.json b/clojure/package.json index e2248254f0..904cd367e2 100644 --- a/clojure/package.json +++ b/clojure/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Make a Lisp (mal) language implemented in ClojureScript", "dependencies": { - "ffi": "2.2.x", - "lumo-cljs": "1.7.x" + "ffi-napi": "2.4.x", + "lumo-cljs": "1.9.x" } } diff --git a/clojure/project.clj b/clojure/project.clj index f2eea933b3..5bb5f960d0 100644 --- a/clojure/project.clj +++ b/clojure/project.clj @@ -1,7 +1,7 @@ (defproject mal "0.0.1-SNAPSHOT" :description "Make-A-Lisp" - :dependencies [[org.clojure/clojure "1.8.0-RC4"] + :dependencies [[org.clojure/clojure "1.9.0"] [org.clojure/tools.reader "0.8.3"] [net.n01se/clojure-jna "1.0.0"]] diff --git a/clojure/src/mal/node_readline.js b/clojure/src/mal/node_readline.js index dc64e3f642..6042eaa0af 100644 --- a/clojure/src/mal/node_readline.js +++ b/clojure/src/mal/node_readline.js @@ -6,7 +6,7 @@ var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history'); var rlwrap = {}; // namespace for this module in web context -var ffi = require('ffi'), +var ffi = require('ffi-napi'), fs = require('fs'); var rllib = ffi.Library(RL_LIB, { diff --git a/clojure/src/mal/readline.cljs b/clojure/src/mal/readline.cljs index 88f3117a24..ea21874541 100644 --- a/clojure/src/mal/readline.cljs +++ b/clojure/src/mal/readline.cljs @@ -1,3 +1,3 @@ (ns mal.readline) -(def readline (.-readline (js/require "./src/mal/node_readline.js"))) +(def readline (.-readline (js/require "../src/mal/node_readline.js"))) diff --git a/coffee/Dockerfile b/coffee/Dockerfile index 895a5634bc..075c304e7f 100644 --- a/coffee/Dockerfile +++ b/coffee/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:18.04 MAINTAINER Joel Martin ########################################################## @@ -24,18 +24,16 @@ WORKDIR /mal # For building node modules RUN apt-get -y install g++ -# Add nodesource apt repo config for 0.12 stable -RUN curl -sL https://deb.nodesource.com/setup_0.12 | bash - +# Add nodesource apt repo config for 10.x stable +RUN apt-get -y install gnupg +RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - # Install nodejs RUN apt-get -y install nodejs -# Link common name -RUN ln -sf nodejs /usr/bin/node - ENV NPM_CONFIG_CACHE /mal/.npm # CoffeeScript specific -RUN npm install -g coffee-script +RUN npm install -g coffeescript RUN touch /.coffee_history && chmod go+w /.coffee_history diff --git a/coffee/node_readline.coffee b/coffee/node_readline.coffee index 87c8d3765a..fecddc80f2 100644 --- a/coffee/node_readline.coffee +++ b/coffee/node_readline.coffee @@ -6,7 +6,7 @@ HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history') rlwrap = {} # namespace for this module in web context -ffi = require('ffi') +ffi = require('ffi-napi') fs = require('fs') rllib = ffi.Library(RL_LIB, { diff --git a/coffee/package.json b/coffee/package.json index d28b74bdd0..9850858436 100644 --- a/coffee/package.json +++ b/coffee/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Make a Lisp (mal) language implemented in CoffeeScript", "dependencies": { - "ffi": "1.3.x", - "coffee-script": "~1.8" + "ffi-napi": "2.4.x", + "coffeescript": "~1.8" } } diff --git a/crystal/Dockerfile b/crystal/Dockerfile index 1c24bbd64d..0d023752f4 100644 --- a/crystal/Dockerfile +++ b/crystal/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:18.04 MAINTAINER Joel Martin ########################################################## @@ -25,6 +25,6 @@ WORKDIR /mal RUN apt-get -y install g++ # Crystal -RUN apt-get -y install apt-transport-https +RUN apt-get -y install apt-transport-https gnupg RUN curl http://dist.crystal-lang.org/apt/setup.sh | bash RUN apt-get -y install crystal diff --git a/elm/Dockerfile b/elm/Dockerfile index 5b3bc3e271..b0553bd56b 100644 --- a/elm/Dockerfile +++ b/elm/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:xenial +FROM ubuntu:18.04 MAINTAINER Joel Martin ########################################################## @@ -21,14 +21,18 @@ WORKDIR /mal # Specific implementation requirements ########################################################## -# For building node modules and pulling elm packages -RUN apt-get -y install g++ netbase +# For building node modules +RUN apt-get -y install g++ -# Add nodesource apt repo config for 7.X -RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - +# Add nodesource apt repo config for 10.x stable +RUN apt-get -y install gnupg +RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - # Install nodejs RUN apt-get -y install nodejs +# For pulling elm packages +RUN apt-get -y install netbase + ENV HOME /mal ENV NPM_CONFIG_CACHE /mal/.npm diff --git a/elm/node_readline.js b/elm/node_readline.js index e59a62bd1e..0a50f91f36 100644 --- a/elm/node_readline.js +++ b/elm/node_readline.js @@ -6,7 +6,7 @@ var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history'); var rlwrap = {}; // namespace for this module in web context -var ffi = require('ffi'), +var ffi = require('ffi-napi'), fs = require('fs'); var rllib = ffi.Library(RL_LIB, { diff --git a/elm/package.json b/elm/package.json index dfbc2c7c58..67d412784d 100644 --- a/elm/package.json +++ b/elm/package.json @@ -4,7 +4,7 @@ "description": "", "main": "bootstrap.js", "dependencies": { - "ffi": "2.0.x" + "ffi-napi": "2.4.x" }, "devDependencies": { "elm": "^0.18.0" diff --git a/es6/Dockerfile b/es6/Dockerfile index ddcadb5b86..f7677e91c8 100644 --- a/es6/Dockerfile +++ b/es6/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:xenial +FROM ubuntu:18.04 MAINTAINER Joel Martin ########################################################## @@ -24,11 +24,11 @@ WORKDIR /mal # For building node modules RUN apt-get -y install g++ -# Add nodesource apt repo config for 7.X -RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - +# Add nodesource apt repo config for 10.x stable +RUN apt-get -y install gnupg +RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - # Install nodejs RUN apt-get -y install nodejs -# Link common name ENV NPM_CONFIG_CACHE /mal/.npm diff --git a/es6/core.mjs b/es6/core.mjs index 64a7436aa4..d99c3c5df0 100644 --- a/es6/core.mjs +++ b/es6/core.mjs @@ -1,15 +1,17 @@ import { _equal_Q, _clone, _keyword, _keyword_Q } from './types' import { _list_Q, Vector, _assoc_BANG, Atom } from './types' import { pr_str } from './printer' -import { readline } from './node_readline' +import rl from './node_readline' +const readline = rl.readline import { read_str } from './reader' +import { readFileSync } from 'fs' function _error(e) { throw new Error(e) } // String functions function slurp(f) { - if (typeof require !== 'undefined') { - return require('fs').readFileSync(f, 'utf-8') + if (typeof process !== 'undefined') { + return readFileSync(f, 'utf-8') } else { var req = new XMLHttpRequest() req.open('GET', f, false) diff --git a/es6/node_readline.js b/es6/node_readline.js index 9e2fb864ba..500f892211 100644 --- a/es6/node_readline.js +++ b/es6/node_readline.js @@ -4,7 +4,7 @@ var RL_LIB = "libreadline"; // NOTE: libreadline is GPL var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history'); -var ffi = require('ffi'), +var ffi = require('ffi-napi'), fs = require('fs'); var rllib = ffi.Library(RL_LIB, { diff --git a/es6/package.json b/es6/package.json index 627da97fec..850dff0917 100644 --- a/es6/package.json +++ b/es6/package.json @@ -3,10 +3,10 @@ "version": "0.0.1", "description": "Make a Lisp (mal) language implemented in ES6 (ECMAScript 6 / ECMAScript 2015)", "dependencies": { - "@std/esm": "^0.11.0", - "ffi": "2.0.x" + "esm": "3.0.x", + "ffi-napi": "2.4.x" }, - "@std/esm": { + "esm": { "cjs": true } } diff --git a/es6/run b/es6/run index a6c8bf8387..ad5a837b2a 100755 --- a/es6/run +++ b/es6/run @@ -1,2 +1,2 @@ #!/bin/bash -exec node -r @std/esm $(dirname $0)/${STEP:-stepA_mal}.mjs "${@}" +exec node -r esm $(dirname $0)/${STEP:-stepA_mal}.mjs "${@}" diff --git a/es6/step0_repl.mjs b/es6/step0_repl.mjs index 6679a09262..6eaca056fa 100644 --- a/es6/step0_repl.mjs +++ b/es6/step0_repl.mjs @@ -1,4 +1,5 @@ -import { readline } from './node_readline' +import rl from './node_readline.js' +const readline = rl.readline // read const READ = str => str diff --git a/es6/step1_read_print.mjs b/es6/step1_read_print.mjs index 6ff1cee7d5..8af709fade 100644 --- a/es6/step1_read_print.mjs +++ b/es6/step1_read_print.mjs @@ -1,4 +1,5 @@ -import { readline } from './node_readline' +import rl from './node_readline.js' +const readline = rl.readline import { BlankException, read_str } from './reader' import { pr_str } from './printer' diff --git a/es6/step2_eval.mjs b/es6/step2_eval.mjs index 40abe093e8..f49b78ad6a 100644 --- a/es6/step2_eval.mjs +++ b/es6/step2_eval.mjs @@ -1,4 +1,5 @@ -import { readline } from './node_readline' +import rl from './node_readline.js' +const readline = rl.readline import { _list_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' diff --git a/es6/step3_env.mjs b/es6/step3_env.mjs index 0026b3472b..034315a814 100644 --- a/es6/step3_env.mjs +++ b/es6/step3_env.mjs @@ -1,4 +1,5 @@ -import { readline } from './node_readline' +import rl from './node_readline.js' +const readline = rl.readline import { _list_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' diff --git a/es6/step4_if_fn_do.mjs b/es6/step4_if_fn_do.mjs index 4fe1f90e29..e3c7073030 100644 --- a/es6/step4_if_fn_do.mjs +++ b/es6/step4_if_fn_do.mjs @@ -1,4 +1,5 @@ -import { readline } from './node_readline' +import rl from './node_readline.js' +const readline = rl.readline import { _list_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' diff --git a/es6/step5_tco.mjs b/es6/step5_tco.mjs index 7bcb778980..bf83f4a69d 100644 --- a/es6/step5_tco.mjs +++ b/es6/step5_tco.mjs @@ -1,4 +1,5 @@ -import { readline } from './node_readline' +import rl from './node_readline.js' +const readline = rl.readline import { _list_Q, _malfunc, _malfunc_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' diff --git a/es6/step6_file.mjs b/es6/step6_file.mjs index 7396197d57..1053976a33 100644 --- a/es6/step6_file.mjs +++ b/es6/step6_file.mjs @@ -1,4 +1,5 @@ -import { readline } from './node_readline' +import rl from './node_readline.js' +const readline = rl.readline import { _list_Q, _malfunc, _malfunc_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' diff --git a/es6/step7_quote.mjs b/es6/step7_quote.mjs index 947c3a2194..31642da170 100644 --- a/es6/step7_quote.mjs +++ b/es6/step7_quote.mjs @@ -1,4 +1,5 @@ -import { readline } from './node_readline' +import rl from './node_readline.js' +const readline = rl.readline import { _list_Q, _malfunc, _malfunc_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' diff --git a/es6/step8_macros.mjs b/es6/step8_macros.mjs index 9aa6e1cc9c..e106975c23 100644 --- a/es6/step8_macros.mjs +++ b/es6/step8_macros.mjs @@ -1,4 +1,5 @@ -import { readline } from './node_readline' +import rl from './node_readline.js' +const readline = rl.readline import { _list_Q, _malfunc, _malfunc_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' diff --git a/es6/step9_try.mjs b/es6/step9_try.mjs index ede914d984..389c1c8db9 100644 --- a/es6/step9_try.mjs +++ b/es6/step9_try.mjs @@ -1,4 +1,5 @@ -import { readline } from './node_readline' +import rl from './node_readline.js' +const readline = rl.readline import { _list_Q, _malfunc, _malfunc_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' diff --git a/es6/stepA_mal.mjs b/es6/stepA_mal.mjs index c883980ebe..a86199385e 100644 --- a/es6/stepA_mal.mjs +++ b/es6/stepA_mal.mjs @@ -1,4 +1,5 @@ -import { readline } from './node_readline' +import rl from './node_readline.js' +const readline = rl.readline import { _list_Q, _malfunc, _malfunc_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' diff --git a/haxe/Dockerfile b/haxe/Dockerfile index 545c7a612a..a0b55723c8 100644 --- a/haxe/Dockerfile +++ b/haxe/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:18.04 MAINTAINER Joel Martin ########################################################## @@ -27,15 +27,13 @@ WORKDIR /mal # For building node modules RUN apt-get -y install g++ -# Add nodesource apt repo config for 0.12 stable -RUN curl -sL https://deb.nodesource.com/setup_0.12 | bash - +# Add nodesource apt repo config for 10.x stable +RUN apt-get -y install gnupg +RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - # Install nodejs RUN apt-get -y install nodejs -# Link common name -RUN ln -sf nodejs /usr/bin/node - ENV NPM_CONFIG_CACHE /mal/.npm diff --git a/haxe/node_readline.js b/haxe/node_readline.js index 2045d66632..80885cf27b 100644 --- a/haxe/node_readline.js +++ b/haxe/node_readline.js @@ -6,7 +6,7 @@ var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history'); var rlwrap = {}; // namespace for this module in web context -var ffi = require('ffi'), +var ffi = require('ffi-napi'), fs = require('fs'); var rllib = ffi.Library(RL_LIB, { diff --git a/haxe/package.json b/haxe/package.json index 34fc239904..e84e49ad95 100644 --- a/haxe/package.json +++ b/haxe/package.json @@ -3,6 +3,6 @@ "version": "0.0.1", "description": "Make a Lisp (mal) language implemented in Haxe/Javascript", "dependencies": { - "ffi": "1.3.x" + "ffi-napi": "2.4.x" } } diff --git a/js/Dockerfile b/js/Dockerfile index 0559f7a9ad..f7677e91c8 100644 --- a/js/Dockerfile +++ b/js/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:18.04 MAINTAINER Joel Martin ########################################################## @@ -24,14 +24,11 @@ WORKDIR /mal # For building node modules RUN apt-get -y install g++ -# Add nodesource apt repo config for 0.12 stable -RUN curl -sL https://deb.nodesource.com/setup_0.12 | bash - +# Add nodesource apt repo config for 10.x stable +RUN apt-get -y install gnupg +RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - # Install nodejs RUN apt-get -y install nodejs -# Link common name -RUN ln -sf nodejs /usr/bin/node - ENV NPM_CONFIG_CACHE /mal/.npm - diff --git a/js/node_readline.js b/js/node_readline.js index dc64e3f642..6042eaa0af 100644 --- a/js/node_readline.js +++ b/js/node_readline.js @@ -6,7 +6,7 @@ var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history'); var rlwrap = {}; // namespace for this module in web context -var ffi = require('ffi'), +var ffi = require('ffi-napi'), fs = require('fs'); var rllib = ffi.Library(RL_LIB, { diff --git a/js/package.json b/js/package.json index 4eec27025a..e6246a8cbd 100644 --- a/js/package.json +++ b/js/package.json @@ -3,6 +3,6 @@ "version": "0.0.1", "description": "Make a Lisp (mal) language implemented in Javascript", "dependencies": { - "ffi": "2.0.x" + "ffi-napi": "2.4.x" } } diff --git a/livescript/Dockerfile b/livescript/Dockerfile index edfa6948c6..f7677e91c8 100644 --- a/livescript/Dockerfile +++ b/livescript/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:xenial +FROM ubuntu:18.04 MAINTAINER Joel Martin ########################################################## @@ -24,11 +24,11 @@ WORKDIR /mal # For building node modules RUN apt-get -y install g++ -# Add nodesource apt repo config for 7.X -RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - +# Add nodesource apt repo config for 10.x stable +RUN apt-get -y install gnupg +RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - # Install nodejs RUN apt-get -y install nodejs ENV NPM_CONFIG_CACHE /mal/.npm - diff --git a/livescript/node_readline.js b/livescript/node_readline.js index e59a62bd1e..0a50f91f36 100644 --- a/livescript/node_readline.js +++ b/livescript/node_readline.js @@ -6,7 +6,7 @@ var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history'); var rlwrap = {}; // namespace for this module in web context -var ffi = require('ffi'), +var ffi = require('ffi-napi'), fs = require('fs'); var rllib = ffi.Library(RL_LIB, { diff --git a/livescript/package.json b/livescript/package.json index af94704c1e..fe5b58944b 100644 --- a/livescript/package.json +++ b/livescript/package.json @@ -4,11 +4,11 @@ "description": "", "main": "index.js", "dependencies": { - "ffi": "2.0.x", + "ffi-napi": "2.4.x", "prelude-ls": "^1.1.2" }, "devDependencies": { - "livescript": "^1.5.0" + "livescript": "^1.6.0" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/miniMAL/Dockerfile b/miniMAL/Dockerfile index 152c82a201..fa54933ccb 100644 --- a/miniMAL/Dockerfile +++ b/miniMAL/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:xenial +FROM ubuntu:18.04 MAINTAINER Joel Martin ########################################################## @@ -24,15 +24,13 @@ WORKDIR /mal # For building node modules RUN apt-get -y install g++ -# Add nodesource apt repo config for 7.X -RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - +# Add nodesource apt repo config for 10.x stable +RUN apt-get -y install gnupg +RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - # Install nodejs RUN apt-get -y install nodejs -# Link common name -RUN ln -sf nodejs /usr/bin/node - ENV NPM_CONFIG_CACHE /mal/.npm # install miniMAL diff --git a/miniMAL/node_readline.js b/miniMAL/node_readline.js index dc64e3f642..6042eaa0af 100644 --- a/miniMAL/node_readline.js +++ b/miniMAL/node_readline.js @@ -6,7 +6,7 @@ var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history'); var rlwrap = {}; // namespace for this module in web context -var ffi = require('ffi'), +var ffi = require('ffi-napi'), fs = require('fs'); var rllib = ffi.Library(RL_LIB, { diff --git a/miniMAL/package.json b/miniMAL/package.json index 0c87452b7e..bd9e2cf659 100644 --- a/miniMAL/package.json +++ b/miniMAL/package.json @@ -4,6 +4,6 @@ "description": "Make a Lisp (mal) language implemented in miniMAL", "dependencies": { "minimal-lisp": "1.0.2", - "ffi": "2.0.x" + "ffi-napi": "2.4.x" } } diff --git a/ts/Dockerfile b/ts/Dockerfile index 1bb0452dc9..f7677e91c8 100644 --- a/ts/Dockerfile +++ b/ts/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:xenial +FROM ubuntu:18.04 MAINTAINER Joel Martin ########################################################## @@ -21,8 +21,14 @@ WORKDIR /mal # Specific implementation requirements ########################################################## -RUN apt-get -y install build-essential -RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - +# For building node modules +RUN apt-get -y install g++ + +# Add nodesource apt repo config for 10.x stable +RUN apt-get -y install gnupg +RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - + +# Install nodejs RUN apt-get -y install nodejs ENV NPM_CONFIG_CACHE /mal/.npm diff --git a/ts/node_readline.ts b/ts/node_readline.ts index cc0af945d8..6018c92cec 100644 --- a/ts/node_readline.ts +++ b/ts/node_readline.ts @@ -1,5 +1,5 @@ import * as path from "path"; -import * as ffi from "ffi"; +import * as ffi from "ffi-napi"; import * as fs from "fs"; // IMPORTANT: choose one diff --git a/ts/package.json b/ts/package.json index 162c75250c..6a5bd97ede 100644 --- a/ts/package.json +++ b/ts/package.json @@ -19,10 +19,10 @@ "test:stepA": "cd .. && make 'test^ts^stepA'" }, "dependencies": { - "ffi": "^2.2.0" + "ffi-napi": "^2.4.0" }, "devDependencies": { - "@types/ffi": "0.0.19", + "@types/ffi-napi": "2.4.0", "@types/node": "^7.0.5", "typescript": "^2.2.1", "typescript-formatter": "^4.1.2" From 16e0a5e6f2e2ef101e9d86c3fffe2d04efbb18c4 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 28 Nov 2018 20:45:28 -0600 Subject: [PATCH 0702/2308] mal: update Dockerfile to Ubuntu 18.04 and node 10 --- mal/Dockerfile | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/mal/Dockerfile b/mal/Dockerfile index 0559f7a9ad..f7677e91c8 100644 --- a/mal/Dockerfile +++ b/mal/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:18.04 MAINTAINER Joel Martin ########################################################## @@ -24,14 +24,11 @@ WORKDIR /mal # For building node modules RUN apt-get -y install g++ -# Add nodesource apt repo config for 0.12 stable -RUN curl -sL https://deb.nodesource.com/setup_0.12 | bash - +# Add nodesource apt repo config for 10.x stable +RUN apt-get -y install gnupg +RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - # Install nodejs RUN apt-get -y install nodejs -# Link common name -RUN ln -sf nodejs /usr/bin/node - ENV NPM_CONFIG_CACHE /mal/.npm - From b5ec219b4920fe9d6218f96f36dfe600b4ffddc0 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 28 Nov 2018 21:33:56 -0600 Subject: [PATCH 0703/2308] awk: fix ignored escaped 'u' warning. --- awk/reader.awk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awk/reader.awk b/awk/reader.awk index 6dcaf804f0..934c2f27e0 100644 --- a/awk/reader.awk +++ b/awk/reader.awk @@ -1,10 +1,10 @@ function reader_read_string(token, v, r) { token = substr(token, 1, length(token) - 1) - gsub(/\\\\/, "\u029e", token) + gsub(/\\\\/, "\xf7", token) gsub(/\\"/, "\"", token) gsub(/\\n/, "\n", token) - gsub("\u029e", "\\", token) + gsub("\xf7", "\\", token) return token } From 96c09dcd40b3759f3aaa8a9393444938a1a42269 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 28 Nov 2018 21:04:44 -0600 Subject: [PATCH 0704/2308] runtest.py: more generic prompt matching. --- runtest.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/runtest.py b/runtest.py index 1f9dd7033c..bd1d24b513 100755 --- a/runtest.py +++ b/runtest.py @@ -145,7 +145,7 @@ def read_to_prompt(self, prompts, timeout): match = regexp.search(self.buf) if match: end = match.end() - buf = self.buf[0:end-len(prompt)] + buf = self.buf[0:match.start()] self.buf = self.buf[end:] self.last_prompt = prompt return buf @@ -254,7 +254,7 @@ def assert_prompt(runner, prompts, timeout): # Wait for the initial prompt try: - assert_prompt(r, ['user> ', 'mal-user> '], args.start_timeout) + assert_prompt(r, ['[^\s()<>]+> '], args.start_timeout) except: _, exc, _ = sys.exc_info() log("\nException: %s" % repr(exc)) @@ -299,8 +299,7 @@ def assert_prompt(runner, prompts, timeout): r.writeline(t.form) try: test_cnt += 1 - res = r.read_to_prompt(['\r\nuser> ', '\nuser> ', - '\r\nmal-user> ', '\nmal-user> '], + res = r.read_to_prompt(['\r\n[^\s()<>]+> ', '\n[^\s()<>]+> '], timeout=args.test_timeout) #print "%s,%s,%s" % (idx, repr(p.before), repr(p.after)) if t.ret == "*" or res in expected: From f6f5d4f2a3b708d9f272dfef82d21c54a2642cc0 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 29 Nov 2018 00:39:53 -0600 Subject: [PATCH 0705/2308] runtest.py: process output/errors as regex match. Update output test data to be regex compatible. --- runtest.py | 28 +++++++++++++++++------- tests/step1_read_print.mal | 13 ++++++----- tests/step2_eval.mal | 3 ++- tests/step3_env.mal | 2 +- tests/step4_if_fn_do.mal | 44 +++++++++++++++++++------------------- tests/step6_file.mal | 2 +- tests/step9_try.mal | 12 +++++------ 7 files changed, 60 insertions(+), 44 deletions(-) diff --git a/runtest.py b/runtest.py index bd1d24b513..6e2a495e76 100755 --- a/runtest.py +++ b/runtest.py @@ -215,15 +215,19 @@ def next(self): self.line_num += 1 self.data.pop(0) break - elif line[0:2] == "; ": + elif line[0:2] == ";/": self.out = self.out + line[2:] + sep self.line_num += 1 self.data.pop(0) else: - self.ret = "*" + self.ret = "" break - if self.ret: break + if self.ret != None: break + if self.out[-2:] == sep and not self.ret: + # If there is no return value, output should not end in + # separator + self.out = self.out[0:-2] return self.form args = parser.parse_args(sys.argv[1:]) @@ -293,8 +297,11 @@ def assert_prompt(runner, prompts, timeout): # The repeated form is to get around an occasional OS X issue # where the form is repeated. # https://github.com/kanaka/mal/issues/30 - expected = ["%s%s%s%s" % (t.form, sep, t.out, t.ret), - "%s%s%s%s%s%s" % (t.form, sep, t.form, sep, t.out, t.ret)] + expects = ["%s%s%s%s" % (re.escape(t.form), sep, + t.out, re.escape(t.ret)), + "%s%s%s%s%s%s" % (re.escape(t.form), sep, + re.escape(t.form), sep, + t.out, re.escape(t.ret))] r.writeline(t.form) try: @@ -302,7 +309,11 @@ def assert_prompt(runner, prompts, timeout): res = r.read_to_prompt(['\r\n[^\s()<>]+> ', '\n[^\s()<>]+> '], timeout=args.test_timeout) #print "%s,%s,%s" % (idx, repr(p.before), repr(p.after)) - if t.ret == "*" or res in expected: + if (t.ret == "" and t.out == ""): + log(" -> SUCCESS (result ignored)") + pass_cnt += 1 + elif (re.search(expects[0], res, re.S) or + re.search(expects[1], res, re.S)): log(" -> SUCCESS") pass_cnt += 1 else: @@ -314,11 +325,12 @@ def assert_prompt(runner, prompts, timeout): log(" -> FAIL (line %d):" % t.line_num) fail_cnt += 1 fail_type = "" - log(" Expected : %s" % repr(expected[0])) + log(" Expected : %s" % repr(expects[0])) log(" Got : %s" % repr(res)) failed_test = """%sFAILED TEST (line %d): %s -> [%s,%s]: Expected : %s - Got : %s""" % (fail_type, t.line_num, t.form, repr(t.out), t.ret, repr(expected[0]), repr(res)) + Got : %s""" % (fail_type, t.line_num, t.form, repr(t.out), + t.ret, repr(expects[0]), repr(res)) failures.append(failed_test) except: _, exc, _ = sys.exc_info() diff --git a/tests/step1_read_print.mal b/tests/step1_read_print.mal index a4d40a0503..266b016033 100644 --- a/tests/step1_read_print.mal +++ b/tests/step1_read_print.mal @@ -75,15 +75,18 @@ false ;=>"" ;; Testing reader errors -;;; TODO: fix these so they fail correctly (1 2 -; expected ')', got EOF +;/.*(EOF|end of input|unbalanced).* [1 2 -; expected ']', got EOF +;/.*(EOF|end of input|unbalanced).* + +;;; These should throw some error with no return value "abc -; expected '"', got EOF +;/.+ (1 "abc -; expected ')', got EOF +;/.+ +(1 "abc" +;/.+ ;; Testing read of quoting '1 diff --git a/tests/step2_eval.mal b/tests/step2_eval.mal index c92fa844ca..16a3589a3a 100644 --- a/tests/step2_eval.mal +++ b/tests/step2_eval.mal @@ -20,8 +20,9 @@ (/ (- (+ 515 (* -87 311)) 296) 27) ;=>-994 +;;; This should throw an error with no return value (abc 1 2 3) -; .*\'abc\' not found.* +;/.+ ;; Testing empty list () diff --git a/tests/step3_env.mal b/tests/step3_env.mal index ebd2e8ab7e..1539e5625e 100644 --- a/tests/step3_env.mal +++ b/tests/step3_env.mal @@ -31,7 +31,7 @@ MYNUM ;; Check env lookup non-fatal error (abc 1 2 3) -; .*\'abc\' not found.* +;/.*\'?abc\'? not found.* ;; Check that error aborts def! (def! w 123) (def! w (abc)) diff --git a/tests/step4_if_fn_do.mal b/tests/step4_if_fn_do.mal index 1117818d4b..991b44b730 100644 --- a/tests/step4_if_fn_do.mal +++ b/tests/step4_if_fn_do.mal @@ -173,14 +173,14 @@ ;; Testing do form (do (prn "prn output1")) -; "prn output1" +;/"prn output1" ;=>nil (do (prn "prn output2") 7) -; "prn output2" +;/"prn output2" ;=>7 (do (prn "prn output1") (prn "prn output2") (+ 1 2)) -; "prn output1" -; "prn output2" +;/"prn output1" +;/"prn output2" ;=>3 (do (def! a 6) 7 (+ a 8)) @@ -341,69 +341,69 @@ a ;; Testing prn (prn) -; +;/ ;=>nil (prn "") -; "" +;/"" ;=>nil (prn "abc") -; "abc" +;/"abc" ;=>nil (prn "abc def" "ghi jkl") -; "abc def" "ghi jkl" +;/"abc def" "ghi jkl" (prn "\"") -; "\"" +;/"\\"" ;=>nil (prn "abc\ndef\nghi") -; "abc\ndef\nghi" +;/"abc\\ndef\\nghi" ;=>nil (prn "abc\\def\\ghi") -; "abc\\def\\ghi" +;/"abc\\\\def\\\\ghi" nil (prn (list 1 2 "abc" "\"") "def") -; (1 2 "abc" "\"") "def" +;/\(1 2 "abc" "\\""\) "def" ;=>nil ;; Testing println (println) -; +;/ ;=>nil (println "") -; +;/ ;=>nil (println "abc") -; abc +;/abc ;=>nil (println "abc def" "ghi jkl") -; abc def ghi jkl +;/abc def ghi jkl (println "\"") -; " +;/" ;=>nil (println "abc\ndef\nghi") -; abc -; def -; ghi +;/abc +;/def +;/ghi ;=>nil (println "abc\\def\\ghi") -; abc\def\ghi +;/abc\\def\\ghi ;=>nil (println (list 1 2 "abc" "\"") "def") -; (1 2 abc ") def +;/\(1 2 abc "\) def ;=>nil ;>>> optional=True diff --git a/tests/step6_file.mal b/tests/step6_file.mal index c024e0d629..486725ee4b 100644 --- a/tests/step6_file.mal +++ b/tests/step6_file.mal @@ -99,7 +99,7 @@ ;; Testing comments in a file (load-file "../tests/incB.mal") -; "incB.mal finished" +;/"incB.mal finished" ;=>"incB.mal return string" (inc4 7) ;=>11 diff --git a/tests/step9_try.mal b/tests/step9_try.mal index d50250ecca..5108914994 100644 --- a/tests/step9_try.mal +++ b/tests/step9_try.mal @@ -5,11 +5,11 @@ ;=>123 (try* (abc 1 2) (catch* exc (prn "exc is:" exc))) -; "exc is:" "'abc' not found" +;/"exc is:" "'abc' not found" ;=>nil (try* (throw "my exception") (catch* exc (do (prn "exc:" exc) 7))) -; "exc:" "my exception" +;/"exc:" "my exception" ;=>7 ;;; Test that throw is a function: @@ -48,10 +48,10 @@ (apply + 4 (list 5)) ;=>9 (apply prn (list 1 2 "3" (list))) -; 1 2 "3" () +;/1 2 "3" \(\) ;=>nil (apply prn 1 2 (list "3" (list))) -; 1 2 "3" () +;/1 2 "3" \(\) ;=>nil (apply list (list)) ;=>() @@ -124,7 +124,7 @@ (apply + 4 [5]) ;=>9 (apply prn 1 2 ["3" 4]) -; 1 2 "3" 4 +;/1 2 "3" 4 ;=>nil (apply list []) ;=>() @@ -307,7 +307,7 @@ ;; ;; Testing throwing non-strings (try* (throw (list 1 2 3)) (catch* exc (do (prn "err:" exc) 7))) -; "err:" (1 2 3) +;/"err:" \(1 2 3\) ;=>7 ;; From 9e2a4ab0f995c1f2b151c98fb5b032600f202a92 Mon Sep 17 00:00:00 2001 From: Joel Martin & Chouser Date: Fri, 30 Nov 2018 14:54:21 -0600 Subject: [PATCH 0706/2308] forth: fix errors/reporting in steps 1-8. --- forth/core.fs | 7 +++++++ forth/step0_repl.fs | 10 ++++++---- forth/step1_read_print.fs | 23 +++++++++++++++++------ forth/step2_eval.fs | 28 ++++++++++++++++++---------- forth/step3_env.fs | 26 ++++++++++++++++++-------- forth/step4_if_fn_do.fs | 26 ++++++++++++++++++-------- forth/step5_tco.fs | 26 ++++++++++++++++++-------- forth/step6_file.fs | 26 ++++++++++++++++++-------- forth/step7_quote.fs | 26 ++++++++++++++++++-------- forth/step8_macros.fs | 26 ++++++++++++++++++-------- forth/step9_try.fs | 35 ++++++++++++++++------------------- forth/stepA_mal.fs | 35 ++++++++++++++++------------------- 12 files changed, 188 insertions(+), 106 deletions(-) diff --git a/forth/core.fs b/forth/core.fs index 593229c93f..6b48c53287 100644 --- a/forth/core.fs +++ b/forth/core.fs @@ -80,6 +80,13 @@ defcore str ( argv argc ) defcore read-string drop @ unpack-str read-str ;; defcore slurp drop @ unpack-str slurp-file MalString. ;; +create core-buff 128 allot +defcore readline ( argv argc -- mal-string ) + drop @ unpack-str type stdout flush-file drop + core-buff 128 stdin read-line throw + if core-buff swap MalString. else drop mal-nil endif ;; + + defcore cons ( argv[item,coll] argc ) drop dup @ swap cell+ @ ( item coll ) to-list conj ;; diff --git a/forth/step0_repl.fs b/forth/step0_repl.fs index 2483c12c84..f69a97d849 100644 --- a/forth/step0_repl.fs +++ b/forth/step0_repl.fs @@ -15,9 +15,11 @@ create buff 128 allot begin ." user> " buff 128 stdin read-line throw - while - buff swap - rep type cr + while ( num-bytes-read ) + dup 0 <> if + buff swap + rep type cr + endif repeat ; -read-lines \ No newline at end of file +read-lines diff --git a/forth/step1_read_print.fs b/forth/step1_read_print.fs index 9e42995bbb..5d0ee31353 100644 --- a/forth/step1_read_print.fs +++ b/forth/step1_read_print.fs @@ -21,12 +21,23 @@ create buff 128 allot stack-leak-detect buff 128 stdin read-line throw while ( num-bytes-read ) - buff swap ( str-addr str-len ) - ['] rep - \ execute safe-type - catch ?dup 0= if safe-type else ." Caught error " . endif - cr - stack-leak-detect <> if ." --stack leak--" cr endif + dup 0 <> if + buff swap ( str-addr str-len ) + ['] rep + \ execute ['] nop \ uncomment to see stack traces + catch ?dup 0= if + safe-type cr + stack-leak-detect <> if ." --stack leak--" cr endif + else { errno } + begin stack-leak-detect = until + errno 1 <> if + s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc + to exception-object + endif + ." Uncaught exception: " + exception-object pr-str safe-type cr + endif + endif repeat ; read-lines diff --git a/forth/step2_eval.fs b/forth/step2_eval.fs index 68e189e051..ba8f30b52d 100644 --- a/forth/step2_eval.fs +++ b/forth/step2_eval.fs @@ -60,10 +60,7 @@ MalSymbol 0 sym env get dup 0= if drop - ." Symbol '" - sym pr-str safe-type - ." ' not found." cr - 1 throw + 0 0 s" ' not found" sym pr-str s" '" ...throw-str endif ;; drop @@ -111,12 +108,23 @@ create buff 128 allot stack-leak-detect buff 128 stdin read-line throw while ( num-bytes-read ) - buff swap ( str-addr str-len ) - ['] rep - \ execute safe-type - catch ?dup 0= if safe-type else ." Caught error " . endif - cr - stack-leak-detect <> if ." --stack leak--" cr endif + dup 0 <> if + buff swap ( str-addr str-len ) + ['] rep + \ execute ['] nop \ uncomment to see stack traces + catch ?dup 0= if + safe-type cr + stack-leak-detect <> if ." --stack leak--" cr endif + else { errno } + begin stack-leak-detect = until + errno 1 <> if + s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc + to exception-object + endif + ." Uncaught exception: " + exception-object pr-str safe-type cr + endif + endif repeat ; read-lines diff --git a/forth/step3_env.fs b/forth/step3_env.fs index fcc40a3d76..939afce4da 100644 --- a/forth/step3_env.fs +++ b/forth/step3_env.fs @@ -94,8 +94,7 @@ MalSymbol sym env env/get-addr dup 0= if drop - ." Symbol '" sym pr-str safe-type ." ' not found." cr - 1 throw + 0 0 s" ' not found" sym pr-str s" '" ...throw-str else @ endif ;; @@ -145,12 +144,23 @@ create buff 128 allot stack-leak-detect buff 128 stdin read-line throw while ( num-bytes-read ) - buff swap ( str-addr str-len ) - ['] rep - \ execute safe-type - catch ?dup 0= if safe-type else ." Caught error " . endif - cr - stack-leak-detect <> if ." --stack leak--" cr endif + dup 0 <> if + buff swap ( str-addr str-len ) + ['] rep + \ execute ['] nop \ uncomment to see stack traces + catch ?dup 0= if + safe-type cr + stack-leak-detect <> if ." --stack leak--" cr endif + else { errno } + begin stack-leak-detect = until + errno 1 <> if + s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc + to exception-object + endif + ." Uncaught exception: " + exception-object pr-str safe-type cr + endif + endif repeat ; read-lines diff --git a/forth/step4_if_fn_do.fs b/forth/step4_if_fn_do.fs index 2ecde297f3..56e1e4535e 100644 --- a/forth/step4_if_fn_do.fs +++ b/forth/step4_if_fn_do.fs @@ -154,8 +154,7 @@ MalSymbol sym env env/get-addr dup 0= if drop - ." Symbol '" sym pr-str safe-type ." ' not found." cr - 1 throw + 0 0 s" ' not found" sym pr-str s" '" ...throw-str else @ endif ;; @@ -205,12 +204,23 @@ create buff 128 allot stack-leak-detect buff 128 stdin read-line throw while ( num-bytes-read ) - buff swap ( str-addr str-len ) - ['] rep - \ execute safe-type - catch ?dup 0= if safe-type else ." Caught error " . endif - cr - stack-leak-detect <> if ." --stack leak--" cr endif + dup 0 <> if + buff swap ( str-addr str-len ) + ['] rep + \ execute ['] nop \ uncomment to see stack traces + catch ?dup 0= if + safe-type cr + stack-leak-detect <> if ." --stack leak--" cr endif + else { errno } + begin stack-leak-detect = until + errno 1 <> if + s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc + to exception-object + endif + ." Uncaught exception: " + exception-object pr-str safe-type cr + endif + endif repeat ; read-lines diff --git a/forth/step5_tco.fs b/forth/step5_tco.fs index e5c92f1d31..499149de85 100644 --- a/forth/step5_tco.fs +++ b/forth/step5_tco.fs @@ -165,8 +165,7 @@ MalSymbol sym env env/get-addr dup 0= if drop - ." Symbol '" sym pr-str safe-type ." ' not found." cr - 1 throw + 0 0 s" ' not found" sym pr-str s" '" ...throw-str else @ endif ;; @@ -216,12 +215,23 @@ create buff 128 allot stack-leak-detect buff 128 stdin read-line throw while ( num-bytes-read ) - buff swap ( str-addr str-len ) - ['] rep - \ execute safe-type - catch ?dup 0= if safe-type else ." Caught error " . endif - cr - stack-leak-detect <> if ." --stack leak--" cr endif + dup 0 <> if + buff swap ( str-addr str-len ) + ['] rep + \ execute ['] nop \ uncomment to see stack traces + catch ?dup 0= if + safe-type cr + stack-leak-detect <> if ." --stack leak--" cr endif + else { errno } + begin stack-leak-detect = until + errno 1 <> if + s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc + to exception-object + endif + ." Uncaught exception: " + exception-object pr-str safe-type cr + endif + endif repeat ; read-lines diff --git a/forth/step6_file.fs b/forth/step6_file.fs index cca5b4eb54..d2573204c8 100644 --- a/forth/step6_file.fs +++ b/forth/step6_file.fs @@ -170,8 +170,7 @@ MalSymbol sym env env/get-addr dup 0= if drop - ." Symbol '" sym pr-str safe-type ." ' not found." cr - 1 throw + 0 0 s" ' not found" sym pr-str s" '" ...throw-str else @ endif ;; @@ -249,12 +248,23 @@ s\" (def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\"))) stack-leak-detect buff 128 stdin read-line throw while ( num-bytes-read ) - buff swap ( str-addr str-len ) - ['] rep - \ execute type - catch ?dup 0= if safe-type else ." Caught error " . endif - cr - stack-leak-detect <> if ." --stack leak--" cr endif + dup 0 <> if + buff swap ( str-addr str-len ) + ['] rep + \ execute ['] nop \ uncomment to see stack traces + catch ?dup 0= if + safe-type cr + stack-leak-detect <> if ." --stack leak--" cr endif + else { errno } + begin stack-leak-detect = until + errno 1 <> if + s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc + to exception-object + endif + ." Uncaught exception: " + exception-object pr-str safe-type cr + endif + endif repeat ; : main ( -- ) diff --git a/forth/step7_quote.fs b/forth/step7_quote.fs index 75af3f10a7..51bd4bfafe 100644 --- a/forth/step7_quote.fs +++ b/forth/step7_quote.fs @@ -212,8 +212,7 @@ MalSymbol sym env env/get-addr dup 0= if drop - ." Symbol '" sym pr-str safe-type ." ' not found." cr - 1 throw + 0 0 s" ' not found" sym pr-str s" '" ...throw-str else @ endif ;; @@ -291,12 +290,23 @@ s\" (def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\"))) stack-leak-detect buff 128 stdin read-line throw while ( num-bytes-read ) - buff swap ( str-addr str-len ) - ['] rep - \ execute type - catch ?dup 0= if safe-type else ." Caught error " . endif - cr - stack-leak-detect <> if ." --stack leak--" cr endif + dup 0 <> if + buff swap ( str-addr str-len ) + ['] rep + \ execute ['] nop \ uncomment to see stack traces + catch ?dup 0= if + safe-type cr + stack-leak-detect <> if ." --stack leak--" cr endif + else { errno } + begin stack-leak-detect = until + errno 1 <> if + s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc + to exception-object + endif + ." Uncaught exception: " + exception-object pr-str safe-type cr + endif + endif repeat ; : main ( -- ) diff --git a/forth/step8_macros.fs b/forth/step8_macros.fs index 68f80524ec..9c230769a1 100644 --- a/forth/step8_macros.fs +++ b/forth/step8_macros.fs @@ -237,8 +237,7 @@ MalSymbol sym env env/get-addr dup 0= if drop - ." Symbol '" sym pr-str safe-type ." ' not found." cr - 1 throw + 0 0 s" ' not found" sym pr-str s" '" ...throw-str else @ endif ;; @@ -318,12 +317,23 @@ s\" (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs stack-leak-detect buff 128 stdin read-line throw while ( num-bytes-read ) - buff swap ( str-addr str-len ) - ['] rep - \ execute type - catch ?dup 0= if safe-type else ." Caught error " . endif - cr - stack-leak-detect <> if ." --stack leak--" cr endif + dup 0 <> if + buff swap ( str-addr str-len ) + ['] rep + \ execute ['] nop \ uncomment to see stack traces + catch ?dup 0= if + safe-type cr + stack-leak-detect <> if ." --stack leak--" cr endif + else { errno } + begin stack-leak-detect = until + errno 1 <> if + s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc + to exception-object + endif + ." Uncaught exception: " + exception-object pr-str safe-type cr + endif + endif repeat ; : main ( -- ) diff --git a/forth/step9_try.fs b/forth/step9_try.fs index b16c2ac157..a5626da86a 100644 --- a/forth/step9_try.fs +++ b/forth/step9_try.fs @@ -346,11 +346,6 @@ defcore map ( argv argc -- list ) cell +loop here>MalList ;; -defcore readline ( argv argc -- mal-string ) - drop @ unpack-str type stdout flush-file drop - buff 128 stdin read-line throw - if buff swap MalString. else drop mal-nil endif ;; - s\" (def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" rep 2drop s\" (defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" rep 2drop s\" (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" rep 2drop @@ -361,20 +356,22 @@ s\" (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs stack-leak-detect buff 128 stdin read-line throw while ( num-bytes-read ) - buff swap ( str-addr str-len ) - ['] rep - \ execute ['] nop \ uncomment to see stack traces - catch ?dup 0= if - safe-type cr - stack-leak-detect <> if ." --stack leak--" cr endif - else { errno } - begin stack-leak-detect = until - errno 1 <> if - s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc - to exception-object - endif - ." Uncaught exception: " - exception-object pr-str safe-type cr + dup 0 <> if + buff swap ( str-addr str-len ) + ['] rep + \ execute ['] nop \ uncomment to see stack traces + catch ?dup 0= if + safe-type cr + stack-leak-detect <> if ." --stack leak--" cr endif + else { errno } + begin stack-leak-detect = until + errno 1 <> if + s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc + to exception-object + endif + ." Uncaught exception: " + exception-object pr-str safe-type cr + endif endif repeat ; diff --git a/forth/stepA_mal.fs b/forth/stepA_mal.fs index 394b638477..38dcdb6bd8 100644 --- a/forth/stepA_mal.fs +++ b/forth/stepA_mal.fs @@ -353,11 +353,6 @@ defcore map ( argv argc -- list ) cell +loop here>MalList ;; -defcore readline ( argv argc -- mal-string ) - drop @ unpack-str type stdout flush-file drop - buff 128 stdin read-line throw - if buff swap MalString. else drop mal-nil endif ;; - s\" (def! *host-language* \"forth\")" rep 2drop s\" (def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" rep 2drop s\" (defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" rep 2drop @@ -372,20 +367,22 @@ s\" (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs stack-leak-detect buff 128 stdin read-line throw while ( num-bytes-read ) - buff swap ( str-addr str-len ) - ['] rep - \ execute ['] nop \ uncomment to see stack traces - catch ?dup 0= if - safe-type cr - stack-leak-detect <> if ." --stack leak--" cr endif - else { errno } - begin stack-leak-detect = until - errno 1 <> if - s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc - to exception-object - endif - ." Uncaught exception: " - exception-object pr-str safe-type cr + dup 0 <> if + buff swap ( str-addr str-len ) + ['] rep + \ execute ['] nop \ uncomment to see stack traces + catch ?dup 0= if + safe-type cr + stack-leak-detect <> if ." --stack leak--" cr endif + else { errno } + begin stack-leak-detect = until + errno 1 <> if + s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc + to exception-object + endif + ." Uncaught exception: " + exception-object pr-str safe-type cr + endif endif repeat ; From 970935dac9fd91cc08ff4a61126cfa886d2282f6 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 29 Nov 2018 00:41:47 -0600 Subject: [PATCH 0707/2308] awk, bash, c, coffee, js: fix errors. --- awk/reader.awk | 4 ++-- bash/reader.sh | 10 +++++++--- bash/step1_read_print.sh | 2 +- c/step2_eval.c | 4 +++- coffee/reader.coffee | 3 ++- js/reader.js | 5 ++++- js/step2_eval.js | 6 +++++- js/tests/stepA_mal.mal | 2 +- 8 files changed, 25 insertions(+), 11 deletions(-) diff --git a/awk/reader.awk b/awk/reader.awk index 934c2f27e0..905ef52d13 100644 --- a/awk/reader.awk +++ b/awk/reader.awk @@ -46,7 +46,7 @@ function reader_read_list(reader, type, end, idx, len, ret) } types_heap[idx]["len"] = len types_release(type idx) - return "!\"expect " end ", got EOF" + return "!\"expected '" end "', got EOF" } function reader_read_hash(reader, idx, key, val) @@ -79,7 +79,7 @@ function reader_read_hash(reader, idx, key, val) types_heap[idx][key] = val } types_release("{" idx) - return "!\"expect }, got EOF" + return "!\"expected '}', got EOF" } function reader_read_abbrev(reader, symbol, val, idx) diff --git a/bash/reader.sh b/bash/reader.sh index fa6064ce73..7b163285ef 100644 --- a/bash/reader.sh +++ b/bash/reader.sh @@ -13,7 +13,11 @@ READ_ATOM () { case "${token}" in [0-9]*) _number "${token}" ;; -[0-9]*) _number "${token}" ;; - \"*) token="${token:1:-1}" + \"*) if [ "${token: -1}" != "\"" ]; then + _error "expected '\"', got EOF" + return + fi + token="${token:1:-1}" token="${token//\\\\/${__keyw}}" token="${token//\\\"/\"}" token="${token//\\n/$'\n'}" @@ -46,7 +50,7 @@ READ_SEQ () { while [[ "${token}" != "${end}" ]]; do if [[ ! "${token}" ]]; then r= - _error "exepected '${end}', got EOF" + _error "expected '${end}', got EOF" return fi READ_FORM @@ -118,7 +122,7 @@ TOKENIZE () { chunk=$(( chunk + ${chunksz} )) fi (( ${#str} == 0 )) && break - [[ "${str}" =~ ^^([][{}\(\)^@])|^(~@)|(\"(\\.|[^\\\"])*\")|^(;[^$'\n']*)|^([~\'\`])|^([^][ ~\`\'\";{}\(\)^@\,]+)|^[,]|^[[:space:]]+ ]] + [[ "${str}" =~ ^^([][{}\(\)^@])|^(~@)|(\"(\\.|[^\\\"])*\"?)|^(;[^$'\n']*)|^([~\'\`])|^([^][ ~\`\'\";{}\(\)^@\,]+)|^[,]|^[[:space:]]+ ]] match=${BASH_REMATCH[0]} str="${str:${#match}}" token="${match//$'\n'/}" diff --git a/bash/step1_read_print.sh b/bash/step1_read_print.sh index 881c0c3ee7..8011fa6b86 100755 --- a/bash/step1_read_print.sh +++ b/bash/step1_read_print.sh @@ -21,7 +21,7 @@ EVAL () { # print PRINT () { if [[ "${__ERROR}" ]]; then - _pr_str "${__ERROR}" yes + _pr_str "${__ERROR}" no r="Error: ${r}" __ERROR= else diff --git a/c/step2_eval.c b/c/step2_eval.c index 3417e32747..d075842455 100644 --- a/c/step2_eval.c +++ b/c/step2_eval.c @@ -34,7 +34,9 @@ MalVal *eval_ast(MalVal *ast, GHashTable *env) { if (ast->type == MAL_SYMBOL) { //g_print("EVAL symbol: %s\n", ast->val.string); // TODO: check if not found - return g_hash_table_lookup(env, ast->val.string); + MalVal *res = g_hash_table_lookup(env, ast->val.string); + assert(res, "'%s' not found", ast->val.string); + return res; } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); diff --git a/coffee/reader.coffee b/coffee/reader.coffee index c6fb7d9179..f0253eb685 100644 --- a/coffee/reader.coffee +++ b/coffee/reader.coffee @@ -11,7 +11,7 @@ class Reader @ tokenize = (str) -> - re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g + re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/g results = [] while (match = re.exec(str)[1]) != "" continue if match[0] == ';' @@ -23,6 +23,7 @@ read_atom = (rdr) -> if token.match /^-?[0-9]+$/ then parseInt token,10 else if token.match /^-?[0-9][0-9.]*$/ then parseFloat token,10 else if token[0] == '"' + throw new Error "expected '\"', got EOF" if token[-1..-1] != '"' token.slice(1, token.length-1) .replace(/\\(.)/g, (_, c) -> if c == 'n' then '\n' else c) else if token[0] == ':' then types._keyword(token[1..]) diff --git a/js/reader.js b/js/reader.js index b147c13b8a..864b973e5b 100644 --- a/js/reader.js +++ b/js/reader.js @@ -15,7 +15,7 @@ Reader.prototype.next = function() { return this.tokens[this.position++]; } Reader.prototype.peek = function() { return this.tokens[this.position]; } function tokenize(str) { - var re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g; + var re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/g; var results = []; while ((match = re.exec(str)[1]) != '') { if (match[0] === ';') { continue; } @@ -32,6 +32,9 @@ function read_atom (reader) { } else if (token.match(/^-?[0-9][0-9.]*$/)) { return parseFloat(token,10); // float } else if (token[0] === "\"") { + if (token.slice(-1) !== "\"") { + throw new Error("expected '\"', got EOF"); + } return token.slice(1,token.length-1) .replace(/\\(.)/g, function (_, c) { return c === "n" ? "\n" : c}) } else if (token[0] === ":") { diff --git a/js/step2_eval.js b/js/step2_eval.js index 42a60d2ab7..3b1adbd648 100644 --- a/js/step2_eval.js +++ b/js/step2_eval.js @@ -13,7 +13,11 @@ function READ(str) { // eval function eval_ast(ast, env) { if (types._symbol_Q(ast)) { - return env[ast]; + if (ast in env) { + return env[ast]; + } else { + throw new Error("'" + ast.value + "' not found"); + } } else if (types._list_Q(ast)) { return ast.map(function(a) { return EVAL(a, env); }); } else if (types._vector_Q(ast)) { diff --git a/js/tests/stepA_mal.mal b/js/tests/stepA_mal.mal index d23a1b2bc6..4502bfff26 100644 --- a/js/tests/stepA_mal.mal +++ b/js/tests/stepA_mal.mal @@ -27,7 +27,7 @@ ;=>60 (. "console.log" "abc" 123 '(4 5 6) {"kk" "vv"} (= 1 1) nil) -; abc 123 [ 4, 5, 6 ] { kk: 'vv' } true null +; abc 123 \[ 4, 5, 6 \] \{ kk: 'vv' \} true null ;=>nil (js-eval "myobj = { v: 10, myfunc: function(a,b,c) { return a * b * c * this.v; } }") From 2adfa11c9853706d0a3db77704c1cc49a1526d4e Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 29 Nov 2018 15:31:50 -0600 Subject: [PATCH 0708/2308] ada, d, elixir, es6, factor, haxe: fix errors. --- ada/reader.adb | 4 ++-- d/reader.d | 4 ++-- elixir/lib/mal/reader.ex | 5 ++++- es6/reader.mjs | 5 ++++- factor/lib/reader/reader.factor | 2 +- haxe/reader/Reader.hx | 5 ++++- 6 files changed, 17 insertions(+), 8 deletions(-) diff --git a/ada/reader.adb b/ada/reader.adb index f2c2640fac..d4f32810ce 100644 --- a/ada/reader.adb +++ b/ada/reader.adb @@ -254,7 +254,7 @@ package body Reader is loop if Is_Null (MTA) then - return New_Error_Mal_Type (Str => "expected '" & Close & "'"); + return New_Error_Mal_Type (Str => "expected '" & Close & "', got EOF"); end if; exit when Deref (MTA).Sym_Type = Sym and then @@ -386,7 +386,7 @@ package body Reader is exception when String_Error => - return New_Error_Mal_Type (Str => "expected '""'"); + return New_Error_Mal_Type (Str => "expected '""', got EOF"); end Read_Str; diff --git a/d/reader.d b/d/reader.d index ac33b84656..2f6ed44911 100644 --- a/d/reader.d +++ b/d/reader.d @@ -103,7 +103,7 @@ MalType read_atom(Reader reader) MalType[] read_items(Reader reader, string start, string end) { auto open_paren = reader.next(); - if (open_paren != start) throw new Exception("expected '" ~ start ~ "'"); + if (open_paren != start) throw new Exception("expected '" ~ start ~ "', got EOF"); string token; MalType[] res; @@ -111,7 +111,7 @@ MalType[] read_items(Reader reader, string start, string end) { if (token is null) { - throw new Exception("expected '" ~ end ~ "'"); + throw new Exception("expected '" ~ end ~ "', got EOF"); } res ~= read_form(reader); } diff --git a/elixir/lib/mal/reader.ex b/elixir/lib/mal/reader.ex index 82ecc8d7d6..867042909c 100644 --- a/elixir/lib/mal/reader.ex +++ b/elixir/lib/mal/reader.ex @@ -11,7 +11,7 @@ defmodule Mal.Reader do end def tokenize(input) do - regex = ~r/[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/ + regex = ~r/[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/ Regex.scan(regex, input, capture: :all_but_first) |> List.flatten |> List.delete_at(-1) # Remove the last match, which is an empty string @@ -88,6 +88,9 @@ defmodule Mal.Reader do |> Code.string_to_quoted |> elem(1) + String.starts_with?(token, "\"") -> + throw({:error, "expected '\"', got EOF"}) + integer?(token) -> Integer.parse(token) |> elem(0) diff --git a/es6/reader.mjs b/es6/reader.mjs index ae23ddf084..597e739494 100644 --- a/es6/reader.mjs +++ b/es6/reader.mjs @@ -12,7 +12,7 @@ class Reader { } function tokenize(str) { - const re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g + const re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/g let match = null let results = [] while ((match = re.exec(str)[1]) != '') { @@ -30,6 +30,9 @@ function read_atom (reader) { } else if (token.match(/^-?[0-9][0-9.]*$/)) { return parseFloat(token,10) // float } else if (token[0] === "\"") { + if (token.slice(-1) !== "\"") { + throw new Error("expected '\"', got EOF"); + } return token.slice(1,token.length-1) .replace(/\\(.)/g, (_, c) => c === "n" ? "\n" : c) } else if (token[0] === ":") { diff --git a/factor/lib/reader/reader.factor b/factor/lib/reader/reader.factor index 767241ae26..2c4092a04a 100644 --- a/factor/lib/reader/reader.factor +++ b/factor/lib/reader/reader.factor @@ -34,7 +34,7 @@ DEFER: read-form :: read-sequence ( seq closer exemplar -- seq maltype ) seq [ [ - [ "expected " closer append throw ] + [ "expected " closer ", got EOF" append throw ] [ dup first closer = ] if-empty ] [ read-form , diff --git a/haxe/reader/Reader.hx b/haxe/reader/Reader.hx index 3955a3d4d5..d06f8008fe 100644 --- a/haxe/reader/Reader.hx +++ b/haxe/reader/Reader.hx @@ -27,7 +27,7 @@ class Reader { // Static functions grouped with Reader class static function tokenize(str:String) { - var re = ~/[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g; + var re = ~/[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/g; var tokens = new Array(); var pos = 0; while (re.matchSub(str, pos)) { @@ -45,6 +45,7 @@ class Reader { static function read_atom(rdr:Reader) { var re_int = ~/^-?[0-9][0-9]*$/; var re_str = ~/^".*"$/; + var re_str_bad = ~/^".*$/; var token = rdr.next(); return switch (token) { case "nil": @@ -72,6 +73,8 @@ class Reader { "\n"), "\""), "\\")); + case _ if (re_str.match(token)): + throw 'expected \'"\', got EOF'; case _: MalSymbol(token); } From 5f80c83f9d6d10cd683b1e0ba6a09f04cba44fc2 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 29 Nov 2018 15:32:27 -0600 Subject: [PATCH 0709/2308] lua, nasm, perl, rexx, vimscript: fix errors. --- lua/reader.lua | 5 ++++- lua/step1_read_print.lua | 1 + nasm/reader.asm | 2 +- perl/reader.pm | 13 +++++++++---- rexx/reader.rexx | 6 +++++- rexx/step1_read_print.rexx | 9 ++++++--- vimscript/reader.vim | 12 ++++++++---- 7 files changed, 34 insertions(+), 14 deletions(-) diff --git a/lua/reader.lua b/lua/reader.lua index 261f3ecbc3..34e4239a74 100644 --- a/lua/reader.lua +++ b/lua/reader.lua @@ -24,7 +24,7 @@ end function M.tokenize(str) local results = {} local re_pos = 1 - local re = rex.new("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;[^\n]*|[^\\s\\[\\]{}('\"`,;)]*)", rex.flags().EXTENDED) + local re = rex.new("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"?|;[^\n]*|[^\\s\\[\\]{}('\"`,;)]*)", rex.flags().EXTENDED) while true do local s, e, t = re:exec(str, re_pos) if not s or s > e then break end @@ -44,6 +44,9 @@ function M.read_atom(rdr) if int_re:exec(token) then return tonumber(token) elseif float_re:exec(token) then return tonumber(token) elseif string.sub(token,1,1) == '"' then + if string.sub(token,-1) ~= '"' then + throw("expected '\"', got EOF") + end local sval = string.sub(token,2,string.len(token)-1) sval = string.gsub(sval, '\\\\', '\177') sval = string.gsub(sval, '\\"', '"') diff --git a/lua/step1_read_print.lua b/lua/step1_read_print.lua index 46d71f5cac..424001c026 100755 --- a/lua/step1_read_print.lua +++ b/lua/step1_read_print.lua @@ -2,6 +2,7 @@ local readline = require('readline') local utils = require('utils') +local types = require('types') local reader = require('reader') local printer = require('printer') diff --git a/nasm/reader.asm b/nasm/reader.asm index 8c040b5418..d0dfe20966 100644 --- a/nasm/reader.asm +++ b/nasm/reader.asm @@ -13,7 +13,7 @@ section .data ;; Error message strings - static error_string_unexpected_end, db "Error: Unexpected end of input. Could be a missing )", 10 + static error_string_unexpected_end, db "Error: Unexpected end of input (EOF). Could be a missing ) or ]", 10 static error_string_bracket_not_brace, db "Error: Expecting '}' but got ')'" ;; Symbols for comparison diff --git a/perl/reader.pm b/perl/reader.pm index a4196badb0..55616d6e7f 100644 --- a/perl/reader.pm +++ b/perl/reader.pm @@ -22,7 +22,7 @@ use Data::Dumper; sub tokenize { my($str) = @_; - my @tokens = $str =~ /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g; + my @tokens = $str =~ /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/g; return grep {! /^;|^$/} @tokens; } @@ -31,12 +31,15 @@ sub read_atom { my $token = $rdr->next(); given ($token) { when(/^-?[0-9]+$/) { return Integer->new($token) } - when(/^"/) { + when(/^".*"$/) { my %escaped_chars = ( "\\\\" => "\\", "\\\"" => "\"", "\\n" => "\n" ); my $str = substr $token, 1, -1; $str =~ s/\\./$escaped_chars{$&}/ge; return String->new($str) } + when(/^".*/) { + die "expected '\"', got EOF"; + } when(/^:/) { return _keyword(substr($token,1)) } when(/^nil$/) { return $nil } when(/^true$/) { return $true } @@ -55,10 +58,12 @@ sub read_list { if ($token ne $start) { die "expected '$start'"; } - while (($token = $rdr->peek()) ne $end) { - if (! defined $token) { + while (1) { + $token = $rdr->peek(); + if (! defined($token)) { die "expected '$end', got EOF"; } + last if ($token eq $end); push(@lst, read_form($rdr)); } $rdr->next(); diff --git a/rexx/reader.rexx b/rexx/reader.rexx index 3b992f9679..619e034a89 100644 --- a/rexx/reader.rexx +++ b/rexx/reader.rexx @@ -119,7 +119,7 @@ read_atom: procedure expose values. tokens. pos /* read_atom() */ return new_symbol(token) end -read_sequence: procedure expose values. tokens. pos /* read_sequence(type, end_char) */ +read_sequence: procedure expose values. tokens. pos err /* read_sequence(type, end_char) */ type = arg(1) end_char = arg(2) pos = pos + 1 /* Consume the open paren */ @@ -133,6 +133,10 @@ read_sequence: procedure expose values. tokens. pos /* read_sequence(type, end_c else seq = seq || " " || element token = tokens.pos + if token == "" then do + err = "expected '" || end_char || "', got EOF" + return "ERR" + end end pos = pos + 1 /* Consume the close paren */ return new_seq(type, seq) diff --git a/rexx/step1_read_print.rexx b/rexx/step1_read_print.rexx index eb697ff075..3101d67286 100644 --- a/rexx/step1_read_print.rexx +++ b/rexx/step1_read_print.rexx @@ -5,7 +5,7 @@ exit #include "reader.rexx" #include "printer.rexx" -read: procedure expose values. /* read(str) */ +read: procedure expose values. err /* read(str) */ return read_str(arg(1)) eval: procedure expose values. /* eval(exp, env) */ @@ -14,8 +14,11 @@ eval: procedure expose values. /* eval(exp, env) */ print: procedure expose values. /* print(exp) */ return pr_str(arg(1), 1) -rep: procedure expose values. /* rep(str) */ - return print(eval(read(arg(1), ""))) +rep: procedure expose values. env. err /* rep(str) */ + ast = read(arg(1)) + if ast == "ERR" then return "ERR" + exp = eval(ast) + return print(exp) main: values. = "" diff --git a/vimscript/reader.vim b/vimscript/reader.vim index f22debe6db..ed1a694cd1 100644 --- a/vimscript/reader.vim +++ b/vimscript/reader.vim @@ -24,6 +24,7 @@ function Tokenize(str) \ "\\~@\\|" . \ "[\\[\\]{}()'`~^@]\\|" . \ "\"\\%(\\\\.\\|[^\\\\\"]\\)*\"\\|" . + \ "\"\\%(\\\\.\\|[^\\\\\"]\\)*\\|" . \ ";[^\\n]*\\|" . \ "[^[:blank:]\\n\\[\\]{}('\"`,;)]*" . \ "\\)" @@ -66,6 +67,8 @@ function ReadAtom(rdr) return FloatNew(str2float(token)) elseif token =~ "^\".*\"$" return StringNew(ParseString(token)) + elseif token =~ "^\".*$" + throw "expected '\"', got EOF" elseif token =~ "^:" return KeywordNew(token[1:-1]) elseif token == "nil" @@ -87,11 +90,12 @@ function ReadTokensList(rdr, start, last) endif let token = a:rdr.peek() while token != a:last - if token == "" - throw "expected '" . a:last . "', got EOF" - endif call add(elements, ReadForm(a:rdr)) - let token = a:rdr.peek() + try + let token = a:rdr.peek() + catch + throw "expected '" . a:last . "', got EOF" + endtry endwhile call a:rdr.nexttoken() return elements From ae6e2220b3ae7d41febf3c386ccfb2d345dfd3de Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 29 Nov 2018 17:09:28 -0600 Subject: [PATCH 0710/2308] guile, hy, java, make, matlab, miniMAL: fix errors --- guile/reader.scm | 4 ++-- hy/step3_env.hy | 11 ++++++----- java/src/main/java/mal/step3_env.java | 2 ++ make/reader.mk | 8 ++++---- matlab/reader.m | 4 ++-- miniMAL/reader.json | 2 +- 6 files changed, 17 insertions(+), 14 deletions(-) diff --git a/guile/reader.scm b/guile/reader.scm index 38cebbe066..f6eabd5a2b 100644 --- a/guile/reader.scm +++ b/guile/reader.scm @@ -38,7 +38,7 @@ (define (delim-read reader delim) (let lp((next (reader 'peek)) (ret '())) (cond - ((null? next) (throw 'mal-error (format #f "expected '~a'" delim))) + ((null? next) (throw 'mal-error (format #f "expected '~a', got EOF" delim))) ((string=? next delim) (reader 'next) (reverse ret)) (else (let* ((cur (read_form reader)) @@ -85,7 +85,7 @@ ((eqv? (string-ref token 0) #\") (if (eqv? (string-ref token (- (string-length token) 1)) #\") (with-input-from-string token read) - (throw 'mal-error "expected '\"'"))) + (throw 'mal-error "expected '\"', got EOF"))) ((string-match "^:(.*)" token) => (lambda (m) (string->keyword (match:substring m 1)))) ((string=? "nil" token) nil) diff --git a/hy/step3_env.hy b/hy/step3_env.hy index 94c9fad380..62ef837e59 100755 --- a/hy/step3_env.hy +++ b/hy/step3_env.hy @@ -57,14 +57,15 @@ (pr-str exp True)) ;; repl -(def repl-env {'+ + - '- - - '* * - '/ (fn [a b] (int (/ a b)))}) - +(def repl-env (env-new)) (defn REP [str] (PRINT (EVAL (READ str) repl-env))) +(env-set repl-env '+ +) +(env-set repl-env '- -) +(env-set repl-env '* *) +(env-set repl-env '/ /) + (defmain [&rest args] ;; indented to match later steps (while True diff --git a/java/src/main/java/mal/step3_env.java b/java/src/main/java/mal/step3_env.java index d3e221b334..65649718b8 100644 --- a/java/src/main/java/mal/step3_env.java +++ b/java/src/main/java/mal/step3_env.java @@ -144,6 +144,8 @@ public static void main(String[] args) throws MalThrowable { System.out.println(PRINT(RE(repl_env, line))); } catch (MalContinue e) { continue; + } catch (MalException e) { + System.out.println("Error: " + printer._pr_str(e.getValue(), false)); } catch (MalThrowable t) { System.out.println("Error: " + t.getMessage()); continue; diff --git a/make/reader.mk b/make/reader.mk index 6f2707c029..3f620a1171 100755 --- a/make/reader.mk +++ b/make/reader.mk @@ -108,7 +108,7 @@ $(foreach ch,$(word 1,$($(1))),\ ,\ $(call READ_FORM,$(1))\ $(call READ_UNTIL,$(1),$(2),$(3))),\ - $(call _error,Expected '$(3)'))) + $(call _error,Expected '$(3)'$(COMMA) got EOF))) endef define DROP_UNTIL @@ -163,7 +163,7 @@ $(foreach ch,$(word 1,$($(1))),\ $(call do,$(call _assoc_seq!,$(thm),$(strip $(call READ_UNTIL,$(1),$(_RC),$(RCURLY)))))\ $(eval $(if $(filter $(_RC),$(word 1,$($(1)))),\ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1)))),\ - $(call _error,Expected '$(RCURLY)')))\ + $(call _error,Expected '$(RCURLY)'$(COMMA) got EOF)))\ $(thm)),\ $(if $(filter $(_RP),$(ch)),\ $(call _error,Unexpected '$(RPAREN)'),\ @@ -174,7 +174,7 @@ $(foreach ch,$(word 1,$($(1))),\ $(call do,$(call _conj!,$(tlist),$(item)))))\ $(eval $(if $(filter $(_RP),$(word 1,$($(1)))),\ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1)))),\ - $(call _error,Expected '$(RPAREN)')))\ + $(call _error,Expected '$(RPAREN)'$(COMMA) got EOF)))\ $(tlist)),\ $(if $(filter $(RBRACKET),$(ch)),\ $(call _error,Unexpected '$(RBRACKET)'),\ @@ -185,7 +185,7 @@ $(foreach ch,$(word 1,$($(1))),\ $(call do,$(call _conj!,$(tvec),$(item)))))\ $(eval $(if $(filter $(RBRACKET),$(word 1,$($(1)))),\ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1)))),\ - $(call _error,Expected '$(RBRACKET)')))\ + $(call _error,Expected '$(RBRACKET)'$(COMMA) got EOF)))\ $(tvec)),\ $(call READ_ATOM,$(1)))))))))))))))) $(call READ_SPACES,$(1)) diff --git a/matlab/reader.m b/matlab/reader.m index 8cea773ac4..937c9534dd 100644 --- a/matlab/reader.m +++ b/matlab/reader.m @@ -39,12 +39,12 @@ seq = {}; token = rdr.next(); if not(strcmp(token, start)) - error(sprintf('expected ''%s''', start)); + error(sprintf('expected ''%s'', got EOF', start)); end token = rdr.peek(); while true if eq(token, false) - error(sprintf('expected ''%s''', last)); + error(sprintf('expected ''%s'', got EOF', last)); end if strcmp(token, last), break, end seq{end+1} = reader.read_form(rdr); diff --git a/miniMAL/reader.json b/miniMAL/reader.json index 830158e96e..dafa691f8c 100644 --- a/miniMAL/reader.json +++ b/miniMAL/reader.json @@ -61,7 +61,7 @@ ["`", []], ["cons", ["read-form", "rdr"], ["read-list-entries", "rdr", "start", "end"]]], - ["throw", ["str", ["`", "expected "], "end"]]]]]], + ["throw", ["str", ["`", "expected "], "end", ["`", ", got EOF"]]]]]]], ["def", "read-list", ["fn", ["rdr", "start", "end"], ["let", ["token", ["rdr-next", "rdr"]], From 2492836307768600f5529e8fbbdc4e1ff296f732 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 29 Nov 2018 17:10:01 -0600 Subject: [PATCH 0711/2308] plpgsql, racket, tcl, yorick: fix errors. --- plpgsql/reader.sql | 4 ++-- racket/reader.rkt | 4 ++-- tcl/reader.tcl | 4 ++-- yorick/reader.i | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plpgsql/reader.sql b/plpgsql/reader.sql index 2368df23e3..cf1d8fdb5c 100644 --- a/plpgsql/reader.sql +++ b/plpgsql/reader.sql @@ -68,12 +68,12 @@ BEGIN token := tokens[p]; p := p + 1; IF token <> first THEN - RAISE EXCEPTION 'expected ''%''', first; + RAISE EXCEPTION 'expected ''%'', got EOF', first; END IF; items := ARRAY[]::integer[]; LOOP IF p > array_length(tokens, 1) THEN - RAISE EXCEPTION 'expected ''%''', last; + RAISE EXCEPTION 'expected ''%'', got EOF', last; END IF; token := tokens[p]; IF token = last THEN EXIT; END IF; diff --git a/racket/reader.rkt b/racket/reader.rkt index 6ff34cfb99..1e7f5fa779 100644 --- a/racket/reader.rkt +++ b/racket/reader.rkt @@ -42,7 +42,7 @@ (define (read_list_entries rdr end) (let ([tok (send rdr peek)]) (cond - [(eq? tok '()) (raise (string-append "expected '" end "'"))] + [(eq? tok '()) (raise (string-append "expected '" end "', got EOF"))] [(equal? end tok) '()] [else (cons (read_form rdr) (read_list_entries rdr end))]))) @@ -53,7 +53,7 @@ (let ([lst (read_list_entries rdr end)]) (send rdr next) lst) - (raise (string-append "expected '" start "'"))))) + (raise (string-append "expected '" start "', got EOF"))))) (define (read_form rdr) (let ([token (send rdr peek)]) diff --git a/tcl/reader.tcl b/tcl/reader.tcl index 16f21d9284..b5b4a6f297 100644 --- a/tcl/reader.tcl +++ b/tcl/reader.tcl @@ -31,14 +31,14 @@ proc tokenize str { proc read_tokens_list {reader start_char end_char} { set token [$reader next] if {$token != $start_char} { - error "expected '$start_char'" + error "expected '$start_char', got EOF" } set elements {} set token [$reader peek] while {$token != $end_char} { if {$token == ""} { - error "expected '$end_char'" + error "expected '$end_char', got EOF" } lappend elements [read_form $reader] set token [$reader peek] diff --git a/yorick/reader.i b/yorick/reader.i index b6bb15e92f..6da51d4041 100644 --- a/yorick/reader.i +++ b/yorick/reader.i @@ -71,14 +71,14 @@ func read_seq(rdr, start_char, end_char) { token = reader_next(rdr) if (token != start_char) { - return MalError(message=("expected '" + start_char + "'")) + return MalError(message=("expected '" + start_char + "', got EOF")) } elements = [] token = reader_peek(rdr) while (token != end_char) { if (token == string(0)) { - return MalError(message=("expected '" + end_char + "'")) + return MalError(message=("expected '" + end_char + "', got EOF")) } e = read_form(rdr) if (structof(e) == MalError) return e From b16fe73ec1ce93fb5ebcdea5208892d525aa27a5 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 13 Oct 2018 00:26:38 -0500 Subject: [PATCH 0712/2308] wasm: Steps 0-3 ported from basic. Preprocessor. Needs wace from https://github.com/kanaka/wac to run. wastpp.py processes the listed modules and outputs a single combined module. It finds inline strings and hoists them to the top into a data section and creates pointer variables into that memory and replaces the inline strings with pointer lookups. --- .gitignore | 2 + Makefile | 3 +- wasm/Dockerfile | 19 ++ wasm/Makefile | 24 ++ wasm/debug.wast | 194 ++++++++++++++++ wasm/env.wast | 90 ++++++++ wasm/mem.wast | 461 +++++++++++++++++++++++++++++++++++++ wasm/printer.wast | 148 ++++++++++++ wasm/reader.wast | 321 ++++++++++++++++++++++++++ wasm/run | 2 + wasm/step0_repl.wast | 41 ++++ wasm/step1_read_print.wast | 81 +++++++ wasm/step2_eval.wast | 269 ++++++++++++++++++++++ wasm/step3_env.wast | 329 ++++++++++++++++++++++++++ wasm/types.wast | 202 ++++++++++++++++ wasm/util.wast | 275 ++++++++++++++++++++++ wasm/wastpp.py | 123 ++++++++++ 17 files changed, 2583 insertions(+), 1 deletion(-) create mode 100644 wasm/Dockerfile create mode 100644 wasm/Makefile create mode 100644 wasm/debug.wast create mode 100644 wasm/env.wast create mode 100644 wasm/mem.wast create mode 100644 wasm/printer.wast create mode 100644 wasm/reader.wast create mode 100755 wasm/run create mode 100644 wasm/step0_repl.wast create mode 100644 wasm/step1_read_print.wast create mode 100644 wasm/step2_eval.wast create mode 100644 wasm/step3_env.wast create mode 100644 wasm/types.wast create mode 100644 wasm/util.wast create mode 100755 wasm/wastpp.py diff --git a/.gitignore b/.gitignore index f3eac16c45..d5c41ff45f 100644 --- a/.gitignore +++ b/.gitignore @@ -144,3 +144,5 @@ elm/elm-stuff elm/*.js !elm/node_readline.js !elm/bootstrap.js +wasm/*.wat +wasm/*.wasm diff --git a/Makefile b/Makefile index de8f929226..c7febc3eed 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,7 @@ IMPLS = ada awk bash basic c chuck clojure coffee common-lisp cpp crystal cs d d guile haskell haxe hy io java js julia kotlin livescript logo lua make mal \ matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp plpgsql \ plsql powershell ps python r racket rexx rpython ruby rust scala scheme skew \ - swift swift3 tcl ts vb vhdl vimscript yorick + swift swift3 tcl ts vb vhdl vimscript wasm yorick EXTENSION = .mal @@ -241,6 +241,7 @@ ts_STEP_TO_PROG = ts/$($(1)).js vb_STEP_TO_PROG = vb/$($(1)).exe vhdl_STEP_TO_PROG = vhdl/$($(1)) vimscript_STEP_TO_PROG = vimscript/$($(1)).vim +wasm_STEP_TO_PROG = wasm/$($(1)).wasm yorick_STEP_TO_PROG = yorick/$($(1)).i diff --git a/wasm/Dockerfile b/wasm/Dockerfile new file mode 100644 index 0000000000..a40c6fe035 --- /dev/null +++ b/wasm/Dockerfile @@ -0,0 +1,19 @@ +FROM ubuntu:18.04 + +RUN dpkg --add-architecture i386 && \ + apt-get -y update && \ + apt-get -y install \ + git-core cmake g++ lib32gcc-8-dev \ + libsdl2-dev:i386 libsdl2-image-dev:i386 \ + libedit-dev:i386 + +# TODO: merge up +RUN apt-get -y install python + +RUN git clone https://github.com/WebAssembly/binaryen/ && \ + cd binaryen && \ + cmake . && make && \ + make install + +# TODO: merge up +RUN apt-get -y install freeglut3-dev:i386 lib32gcc-7-dev libreadline-dev:i386 diff --git a/wasm/Makefile b/wasm/Makefile new file mode 100644 index 0000000000..46650e42fb --- /dev/null +++ b/wasm/Makefile @@ -0,0 +1,24 @@ +STEP0_DEPS = util.wast +STEP1_DEPS = $(STEP0_DEPS) types.wast mem.wast debug.wast reader.wast printer.wast +STEP2_DEPS = $(STEP1_DEPS) +STEP3_DEPS = $(STEP2_DEPS) env.wast + +STEPS = step0_repl step1_read_print step2_eval step3_env \ + step4_if_fn_do step5_tco step6_file step7_quote \ + step8_macros step9_try stepA_mal + +all: $(foreach s,$(STEPS),$(s).wasm) + +%.wasm: + ./wastpp.py $^ > $*.wat + wasm-as $*.wat -o $@ + +step0_repl.wasm: $(STEP0_DEPS) step0_repl.wast +step1_read_print.wasm: $(STEP1_DEPS) step1_read_print.wast +step2_eval.wasm: $(STEP2_DEPS) step2_eval.wast +step3_env.wasm: $(STEP3_DEPS) step3_env.wast + +.PHONY: clean + +clean: + rm -f *.wat *.wasm diff --git a/wasm/debug.wast b/wasm/debug.wast new file mode 100644 index 0000000000..18e5fa1c35 --- /dev/null +++ b/wasm/debug.wast @@ -0,0 +1,194 @@ +(module $debug + + (func $PR_VALUE (param $fmt i32) (param $mv i32) + (local $temp i32) + (set_local $temp (call $pr_str (get_local $mv))) + (call $printf_1 (get_local $fmt) (call $to_String (get_local $temp))) + (call $RELEASE (get_local $temp)) + ) + + (func $PR_MEMORY_VALUE (param $idx i32) (result i32) + (local $mv i32) + (local $type i32) + (local $size i32) + (local $val0 i32) + ;;; mv = mem + idx + (set_local $mv (call $MalVal_ptr (get_local $idx))) + (set_local $type (call $TYPE (get_local $mv))) + (set_local $size (call $MalVal_size (get_local $mv))) + (set_local $val0 (call $MalVal_val (get_local $idx) (i32.const 0))) + + ;;; printf(" %3d: type: %2d", idx, type) + (call $printf_2 (STRING " 0x%x: type: %d") + (get_local $idx) (get_local $type)) + + (if (i32.eq (get_local $type) (i32.const 15)) + (then + ;;; printf(", size: %2d", size) + (call $printf_1 (STRING ", size: %d") (get_local $size))) + (else + ;;; printf(", refs: %2d", (mv->refcnt_type - type)>>5) + (call $printf_1 (STRING ", refs: %d") (call $REFS (get_local $mv))))) + + ;;; printf(", [ %3d | %3d", mv->refcnt_type, val0) + (call $printf_2 (STRING ", [ 0x%x | 0x%x") + (call $MalVal_refcnt_type (get_local $idx)) + (get_local $val0)) + + (if (i32.eq (get_local $size) (i32.const 2)) + (then + (call $print (STRING " | --- | --- ]"))) + (else + ;;; printf(" | %3d", mv->val[1]) + (call $printf_1 (STRING " | 0x%x") + (call $MalVal_val (get_local $idx) (i32.const 1))) + (if (i32.eq (get_local $size) (i32.const 3)) + (then + (call $print (STRING " | --- ]"))) + (else + ;;; printf(" | %3d ]", mv->val[2]) + (call $printf_1 (STRING " | 0x%x ]") + (call $MalVal_val (get_local $idx) (i32.const 2))))))) + + ;;; printf(" >> ") + (call $print (STRING " >> ")) + + (block $done (block $unknown + (block (block (block (block (block (block (block (block + (block (block (block (block (block (block (block (block + (br_table 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $unknown (get_local $type))) + ;; 0: nil + (call $print (STRING "nil")) + (br $done)) + ;; 1: boolean + (if (i32.eq (get_local $val0) (i32.const 0)) + ;; true + (call $print (STRING "false")) + ;; false + (call $print (STRING "true"))) + (br $done)) + ;; 2: integer + (call $printf_1 (STRING "%d") (get_local $val0)) + (br $done)) + ;; 3: float/ERROR + (call $print (STRING " *** GOT FLOAT *** ")) + (br $done)) + ;; 4: string/kw + (call $printf_1 (STRING "'%s'") (call $to_String (get_local $mv))) + (br $done)) + ;; 5: symbol + (call $print (call $to_String (get_local $mv))) + (br $done)) + ;; 6: list + (if (i32.le_u (get_local $mv) (get_global $EMPTY_HASHMAP)) + (then + (call $print (STRING "()"))) + (else + ;;; printf("(... %d ...), next: %d\n", mv->val[1], mv->val[0]) + (call $printf_2 (STRING "(... 0x%x ...), next: 0x%x") + (call $MalVal_val (get_local $idx) (i32.const 1)) + (call $MalVal_val (get_local $idx) (i32.const 0))))) + (br $done)) + ;; 7: vector + (if (i32.le_u (get_local $mv) (get_global $EMPTY_HASHMAP)) + (then + (call $print (STRING "[]"))) + (else + ;;; printf("[... %d ...], next: %d\n", mv->val[1], mv->val[0])val + (call $printf_2 (STRING "[... %d ...], next: %d") + (call $MalVal_val (get_local $idx) (i32.const 1)) + (call $MalVal_val (get_local $idx) (i32.const 0))))) + (br $done)) + ;; 8: hashmap + (if (i32.le_u (get_local $mv) (get_global $EMPTY_HASHMAP)) + (then + (call $print (STRING "{}"))) + (else + ;;; printf("{... '%s'(%d) : %d ...}\n", + ;; to_String(mem + mv->val[1]), mv->val[1], mv->val[2]) + (call $printf_3 (STRING "{... '%s'(%d) : %d ...}") + (call $to_String + (call $MalVal_ptr + (call $MalVal_val (get_local $idx) (i32.const 1)))) + (call $MalVal_val (get_local $idx) (i32.const 1)) + (call $MalVal_val (get_local $idx) (i32.const 2))))) + (br $done)) + ;; 9: function + (call $print (STRING "function")) + (br $done)) + ;; 10: mal function + (call $print (STRING "mal function")) + (br $done)) + ;; 11: macro fn + (call $print (STRING "macro fn")) + (br $done)) + ;; 12: atom + (call $print (STRING "atom")) + (br $done)) + ;; 13: environment + (call $print (STRING "environment")) + (br $done)) + ;; 14: metadata + (call $print (STRING "metadata")) + (br $done)) + ;; 15: FREE + (call $printf_1 (STRING "FREE next: 0x%x") (get_local $val0)) + (if (i32.eq (get_local $idx) (get_global $mem_free_list)) + (call $print (STRING " (free start)"))) + (if (i32.eq (get_local $val0) (get_global $mem_unused_start)) + (call $print (STRING " (free end)"))) + (br $done)) + ;; 16: unknown + (call $print (STRING "unknown")) + ) + + (drop (call $putchar (i32.const 0xA))) + + (i32.add (get_local $size) (get_local $idx)) + ) + + (func $PR_MEMORY (param $start i32) (param $end i32) + (local $idx i32) + (if (i32.lt_s (get_local $start) (i32.const 0)) + (set_local $start (get_global $mem_user_start))) + (if (i32.lt_s (get_local $end) (i32.const 0)) + (set_local $end (get_global $mem_unused_start))) + ;;; printf("Values - (mem) showing %d -> %d", start, end) + ;;; printf(" (unused start: %d, free list: %d):\n", + ;;; mem_unused_start, mem_free_list) + (call $printf_4 (STRING "Values - (mem) showing 0x%x -> 0x%x (unused start: 0x%x, free list: 0x%x):\n") + (get_local $start) + (get_local $end) + (get_global $mem_unused_start) + (get_global $mem_free_list)) + + (if (i32.le_s (get_local $end) (get_local $start)) + (then + (call $print (STRING " ---\n")) + (set_local $end (get_global $mem_unused_start))) + (else + (set_local $idx (get_local $start)) + ;;; while (idx < end) + (block $loopvals_exit + (loop $loopvals + (if (i32.ge_s (get_local $idx) (get_local $end)) + (br $loopvals_exit)) + (set_local $idx (call $PR_MEMORY_VALUE (get_local $idx))) + (br $loopvals) + ) + ))) + ) + + (func $PR_MEMORY_RAW (param $start i32) (param $end i32) + (block $loop_exit + (loop $loop + (if (i32.ge_u (get_local $start) (get_local $end)) + (br $loop_exit)) + (call $printf_2 (STRING "0x%x 0x%x\n") + (get_local $start) (i32.load (get_local $start))) + (set_local $start (i32.add (i32.const 4) (get_local $start))) + (br $loop) + ) + ) + ) +) diff --git a/wasm/env.wast b/wasm/env.wast new file mode 100644 index 0000000000..9e36586062 --- /dev/null +++ b/wasm/env.wast @@ -0,0 +1,90 @@ +(module $env + + (func $ENV_NEW (param $outer i32) (result i32) + (local $data i32) + (local $env i32) + + ;; allocate the data hashmap + (set_local $data (call $HASHMAP)) + + (set_local $env (call $ALLOC (get_global $ENVIRONMENT_T) + (get_local $data) (get_local $outer) (i32.const 0))) + ;; environment takes ownership + (call $RELEASE (get_local $data)) + (get_local $env) + ) + + (func $ENV_SET (param $env i32) (param $key i32) (param $value i32) + (result i32) + (local $data i32) + (set_local $data (call $MEM_VAL0_ptr (get_local $env))) + (i32.store (call $VAL0_ptr (get_local $env)) + (call $MalVal_index + (call $ASSOC1 (get_local $data) + (get_local $key) (get_local $value)))) + (get_local $value) + ) + + (func $ENV_SET_S (param $env i32) (param $key i32) (param $value i32) + (result i32) + (local $data i32) + (set_local $data (call $MEM_VAL0_ptr (get_local $env))) + (i32.store (call $VAL0_ptr (get_local $env)) + (call $MalVal_index + (call $ASSOC1_S (get_local $data) + (get_local $key) (get_local $value)))) + (get_local $value) + ) + + (func $ENV_FIND (param $env i32) (param $key i32) (result i64) + (local $res i32) + (local $data i32) + (local $found_res i64) + + (set_local $res (i32.const 0)) + + (block $done + (loop $loop + (set_local $data (call $MEM_VAL0_ptr (get_local $env))) + (set_local $found_res (call $HASHMAP_GET (get_local $data) + (get_local $key))) + ;;; if (found) + (if (i32.wrap/i64 (i64.shr_u (get_local $found_res) + (i64.const 32))) + (then + (set_local $res (i32.wrap/i64 (get_local $found_res))) + (br $done))) + (set_local $env (call $MEM_VAL1_ptr (get_local $env))) + (if (i32.eq (get_local $env) (get_global $NIL)) + (then + (set_local $env (i32.const 0)) + (br $done))) + (br $loop) + ) + ) + + ;; combine res/env as hi 32/low 32 of i64 + (i64.or + (i64.shl_u (i64.extend_u/i32 (get_local $res)) + (i64.const 32)) + (i64.extend_u/i32 (get_local $env))) + ) + + (func $ENV_GET (param $env i32) (param $key i32) (result i32) + (local $res i32) + (local $res_env i64) + (set_local $res (i32.const 0)) + + (set_local $res_env (call $ENV_FIND (get_local $env) (get_local $key))) + (set_local $env (i32.wrap/i64 (get_local $res_env))) + (set_local $res (i32.wrap/i64 (i64.shr_u (get_local $res_env) + (i64.const 32)))) + + (if (i32.eqz (get_local $env)) + (then + (call $THROW_STR_1 (STRING "'%s' not found") + (call $to_String (get_local $key))) + (return (get_local $res)))) + (return (call $INC_REF (get_local $res))) + ) +) diff --git a/wasm/mem.wast b/wasm/mem.wast new file mode 100644 index 0000000000..43464cd0d8 --- /dev/null +++ b/wasm/mem.wast @@ -0,0 +1,461 @@ +(module $mem + (global $MEM_SIZE i32 (i32.const 1048576)) + (global $STRING_MEM_SIZE i32 (i32.const 1048576)) + + (global $heap_start (mut i32) (i32.const 0)) + (global $heap_end (mut i32) (i32.const 0)) + + (global $mem (mut i32) (i32.const 0)) + (global $mem_unused_start (mut i32) (i32.const 0)) + (global $mem_free_list (mut i32) (i32.const 0)) + (global $mem_user_start (mut i32) (i32.const 0)) + +;; (global $string_mem (mut i32) (i32.const 0)) +;; (global $string_mem_next (mut i32) (i32.const 0)) +;; (global $string_mem_user_start (mut i32) (i32.const 0)) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; General type storage/pointer functions + + (func $VAL0_ptr (param $mv i32) (result i32) + (i32.add (get_local $mv) (i32.const 4))) + (func $VAL1_ptr (param $mv i32) (result i32) + (i32.add (get_local $mv) (i32.const 8))) + + (func $VAL0 (param $mv i32) (result i32) + (i32.load (i32.add (get_local $mv) (i32.const 4)))) + (func $VAL1 (param $mv i32) (result i32) + (i32.load (i32.add (get_local $mv) (i32.const 8)))) + + + (func $MEM_VAL0_ptr (param $mv i32) (result i32) + (i32.add (get_global $mem) + (i32.mul_u (i32.load (i32.add (get_local $mv) (i32.const 4))) + (i32.const 8)))) + (func $MEM_VAL1_ptr (param $mv i32) (result i32) + (i32.add (get_global $mem) + (i32.mul_u (i32.load (i32.add (get_local $mv) (i32.const 8))) + (i32.const 8)))) + (func $MEM_VAL2_ptr (param $mv i32) (result i32) + (i32.add (get_global $mem) + (i32.mul_u (i32.load (i32.add (get_local $mv) (i32.const 12))) + (i32.const 8)))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ;; Returns the address of 'mem[mv_idx]' + (func $MalVal_ptr (param $mv_idx i32) (result i32) + ;; MalVal memory 64 bit (2 * i32) aligned + ;;; mem[mv_idx].refcnt_type + (i32.add (get_global $mem) + (i32.mul_u (get_local $mv_idx) (i32.const 8)))) + + ;; Returns the memory index mem of mv + ;; Will usually be used with a load or store by the caller + (func $MalVal_index (param $mv i32) (result i32) + ;; MalVal memory 64 bit (2 * i32) aligned + (i32.div_u (i32.sub_u (get_local $mv) (get_global $mem)) + (i32.const 8))) + + ;; Returns the address of 'mem[mv_idx].refcnt_type' + (func $MalVal_refcnt_type (param $mv_idx i32) (result i32) + (i32.load (call $MalVal_ptr (get_local $mv_idx)))) + + (func $TYPE (param $mv i32) (result i32) + ;;; type = mv->refcnt_type & 31 + (i32.and (i32.load (get_local $mv)) + (i32.const 0x1f))) ;; 0x1f == 31 + + (func $REFS (param $mv i32) (result i32) + ;;; type = mv->refcnt_type & 31 + (i32.shr_u (i32.load (get_local $mv)) + (i32.const 5))) ;; / 32 + + ;; Returns the address of 'mem[mv_idx].val[val]' + ;; Will usually be used with a load or store by the caller + (func $MalVal_val_ptr (param $mv_idx i32) (param $val i32) (result i32) + (i32.add (i32.add (call $MalVal_ptr (get_local $mv_idx)) + (i32.const 4)) + (i32.mul_u (get_local $val) + (i32.const 4)))) + + ;; Returns the value of 'mem[mv_idx].val[val]' + (func $MalVal_val (param $mv_idx i32) (param $val i32) (result i32) + (i32.load (call $MalVal_val_ptr (get_local $mv_idx) (get_local $val)))) + + (func $MalType_size (param $type i32) (result i32) + ;;; if (type <= 5 || type == 9 || type == 12) + (if i32 (i32.or (i32.le_u (get_local $type) (i32.const 5)) + (i32.or (i32.eq (get_local $type) (i32.const 9)) + (i32.eq (get_local $type) (i32.const 12)))) + (then (i32.const 2)) + (else + ;;; else if (type == 8 || type == 10 || type == 11) + (if i32 (i32.or (i32.eq (get_local $type) (i32.const 8)) + (i32.or (i32.eq (get_local $type) (i32.const 10)) + (i32.eq (get_local $type) (i32.const 11)))) + (then (i32.const 4)) + (else (i32.const 3)))))) + + (func $MalVal_size (param $mv i32) (result i32) + (local $type i32) + (set_local $type (call $TYPE (get_local $mv))) + ;; if (type == FREE_T) + (if i32 (i32.eq (get_local $type) (get_global $FREE_T)) + (then + ;;; return (mv->refcnt_type & 0xffe0)>>5 + (i32.shr_u + (i32.and + (i32.load (get_local $mv)) + (i32.const 0xffe0)) + (i32.const 5))) ;;; / 32 + (else + ;;; return MalType_size(type) + (call $MalType_size (get_local $type))))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; init_memory + + (func $init_memory + (local $heap_size i32) + +;; (call $print (STRING ">>> init_memory\n")) + + (call $init_sprintf_mem) + + ;; 100 character error_str static buffer + (set_global $error_str (STRING " ")) + ;; 256 character token static buffer + (set_global $token (STRING " ")) + + (set_local $heap_size (i32.add (get_global $MEM_SIZE) + (get_global $STRING_MEM_SIZE))) + (set_global $heap_start (i32.add (get_global $memoryBase) + (get_global $S_STRING_END))) + (set_global $heap_end (i32.add (get_global $heap_start) + (get_local $heap_size))) + + (set_global $mem (get_global $heap_start)) + (set_global $mem_unused_start (i32.const 0)) + (set_global $mem_free_list (i32.const 0)) + +;; (set_global $string_mem (i32.add (get_global $heap_start) +;; (get_global $MEM_SIZE))) +;; (set_global $string_mem_next (get_global $string_mem)) + + ;; Empty values + (set_global $NIL + (call $ALLOC_SCALAR (get_global $NIL_T) (i32.const 0))) + (set_global $FALSE + (call $ALLOC_SCALAR (get_global $BOOLEAN_T) (i32.const 0))) + (set_global $TRUE + (call $ALLOC_SCALAR (get_global $BOOLEAN_T) (i32.const 1))) + (set_global $EMPTY_LIST + (call $ALLOC (get_global $LIST_T) + (get_global $NIL) (get_global $NIL) (get_global $NIL))) + (set_global $EMPTY_VECTOR + (call $ALLOC (get_global $VECTOR_T) + (get_global $NIL) (get_global $NIL) (get_global $NIL))) + (set_global $EMPTY_HASHMAP + (call $ALLOC (get_global $HASHMAP_T) + (get_global $NIL) (get_global $NIL) (get_global $NIL))) + +;; (call $print (STRING "<<< init_memory\n")) + + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; memory management + + (func $ALLOC_INTERNAL (param $type i32) (param $val1 i32) + (param $val2 i32) (param $val3 i32) (result i32) + (local $prev i32) + (local $res i32) + (local $size i32) + (set_local $prev (get_global $mem_free_list)) + (set_local $res (get_global $mem_free_list)) + (set_local $size (call $MalType_size (get_local $type))) + + (block $loop_done + (loop $loop + ;; res == mem_unused_start + (if (i32.eq (get_local $res) (get_global $mem_unused_start)) + (then + ;; ALLOC_UNUSED + ;;; if (res + size > MEM_SIZE) + (if (i32.gt_u (i32.add (get_local $res) (get_local $size)) + (get_global $MEM_SIZE)) + (then + ;; Out of memory, exit + (call $print (STRING "Out of mal memory!\n")) + (call $exit (i32.const 1)))) + ;;; if (mem_unused_start += size) + (set_global $mem_unused_start + (i32.add (get_global $mem_unused_start) + (get_local $size))) + ;;; if (prev == res) + (if (i32.eq (get_local $prev) (get_local $res)) + (then + (set_global $mem_free_list (get_global $mem_unused_start))) + (else + ;;; mem[prev].val[0] = mem_unused_start + (i32.store + (call $MalVal_val_ptr (get_local $prev) (i32.const 0)) + (get_global $mem_unused_start)))) + (br $loop_done))) + ;; if (MalVal_size(mem+res) == size) + (if (i32.eq (call $MalVal_size (call $MalVal_ptr (get_local $res))) + (get_local $size)) + (then + ;; ALLOC_MIDDLE + ;;; if (res == mem_free_list) + (if (i32.eq (get_local $res) (get_global $mem_free_list)) + ;; set free pointer (mem_free_list) to next free + ;;; mem_free_list = mem[res].val[0]; + (set_global $mem_free_list + (call $MalVal_val (get_local $res) (i32.const 0)))) + ;; if (res != mem_free_list) + (if (i32.ne (get_local $res) (get_global $mem_free_list)) + ;; set previous free to next free + ;;; mem[prev].val[0] = mem[res].val[0] + (i32.store (call $MalVal_val_ptr (get_local $prev) (i32.const 0)) + (call $MalVal_val (get_local $res) (i32.const 0)))) + (br $loop_done))) + ;;; prev = res + (set_local $prev (get_local $res)) + ;;; res = mem[res].val[0] + (set_local $res (call $MalVal_val (get_local $res) (i32.const 0))) + (br $loop) + ) + ) + ;; ALLOC_DONE + ;;; mem[res].refcnt_type = type + 32 + (i32.store (call $MalVal_ptr (get_local $res)) + (i32.add (get_local $type) (i32.const 32))) + ;; set val to default val1 + ;;; mem[res].val[0] = val1 + (i32.store (call $MalVal_val_ptr (get_local $res) (i32.const 0)) + (get_local $val1)) + ;;; if (type > 5 && type != 9) + (if (i32.and (i32.gt_u (get_local $type) (i32.const 5)) + (i32.ne (get_local $type) (i32.const 9))) + (then + ;; inc refcnt of referenced value + ;;; mem[val1].refcnt_type += 32 + (i32.store (call $MalVal_ptr (get_local $val1)) + (i32.add (call $MalVal_refcnt_type (get_local $val1)) + (i32.const 32))))) + ;;; if (size > 2) + (if (i32.gt_u (get_local $size) (i32.const 2)) + (then + ;; inc refcnt of referenced value + ;;; mem[val2].refcnt_type += 32 + (i32.store (call $MalVal_ptr (get_local $val2)) + (i32.add (call $MalVal_refcnt_type (get_local $val2)) + (i32.const 32))) + ;;; mem[res].val[1] = val2 + (i32.store (call $MalVal_val_ptr (get_local $res) (i32.const 1)) + (get_local $val2)))) + ;;; if (size > 3) + (if (i32.gt_u (get_local $size) (i32.const 3)) + (then + ;; inc refcnt of referenced value + ;;; mem[val3].refcnt_type += 32 + (i32.store (call $MalVal_ptr (get_local $val3)) + (i32.add (call $MalVal_refcnt_type (get_local $val3)) + (i32.const 32))) + ;;; mem[res].val[2] = val3 + (i32.store (call $MalVal_val_ptr (get_local $res) (i32.const 2)) + (get_local $val3)))) + + ;;; return mem + res + (call $MalVal_ptr (get_local $res)) + ) + + (func $ALLOC_SCALAR (param $type i32) (param $val1 i32) + (result i32) + (call $ALLOC_INTERNAL + (get_local $type) + (get_local $val1) + (i32.const 0) + (i32.const 0)) + ) + + (func $ALLOC (param $type i32) (param $val1 i32) + (param $val2 i32) (param $val3 i32) (result i32) + (call $ALLOC_INTERNAL + (get_local $type) + (call $MalVal_index (get_local $val1)) + (call $MalVal_index (get_local $val2)) + (call $MalVal_index (get_local $val3))) + ) + + (func $RELEASE (param $mv i32) + (local $idx i32) + (local $type i32) + (local $size i32) + + ;; Ignore NULLs + ;;; if (mv == NULL) { return; } + (if (i32.eqz (get_local $mv)) (return)) + ;;; idx = mv - mem + (set_local $idx (call $MalVal_index (get_local $mv))) + ;;; type = mv->refcnt_type & 31 + (set_local $type (i32.and (i32.load (get_local $mv)) + (i32.const 0x1f))) ;; 0x1f == 31 + ;;; size = MalType_size(type) + (set_local $size (call $MalType_size (get_local $type))) + + ;; DEBUG + ;;; printf(">>> RELEASE idx: %d, type: %d, size: %d\n", idx, type, size) +;; (call $print (STRING ">>> RELEASE idx: ")) +;; (call $printhex (get_local $idx)) +;; (call $print (STRING ", type: ")) +;; (call $printnum (get_local $type) (i32.const 10)) +;; (call $print (STRING ", size: ")) +;; (call $printnum (get_local $size) (i32.const 10)) +;; (call $print (STRING "\n")) + + (if (i32.eq (i32.const 0) (get_local $mv)) + (then + (call $print (STRING "RELEASE of NULL!\n")) + (call $exit (i32.const 1)))) + + (if (i32.eq (get_global $FREE_T) (get_local $type)) + (then + (call $printf_2 (STRING "RELEASE of already free mv: 0x%x, idx: 0x%x\n") + (get_local $mv) (get_local $idx)) + (call $exit (i32.const 1)))) + (if (i32.lt_u (call $MalVal_refcnt_type (get_local $idx)) + (i32.const 15)) + (then + (call $printf_2 (STRING "RELEASE of unowned mv: 0x%x, idx: 0x%x\n") + (get_local $mv) (get_local $idx)) + (call $exit (i32.const 1)))) + + ;; decrease reference count by one + (i32.store (call $MalVal_ptr (get_local $idx)) + (i32.sub_u (call $MalVal_refcnt_type (get_local $idx)) + (i32.const 32))) + + ;; nil, false, true, empty sequences + (if (i32.le_u (get_local $mv) (get_global $EMPTY_HASHMAP)) + (then + (if (i32.lt_u (call $MalVal_refcnt_type (get_local $idx)) + (i32.const 32)) + (then + (call $printf_2 (STRING "RELEASE of unowned mv: 0x%x, idx: 0x%x\n") + (get_local $mv) (get_local $idx)) + (call $exit (i32.const 1)))) + (return))) + + ;; our reference count is not 0, so don't release + (if (i32.ge_u (call $MalVal_refcnt_type (get_local $idx)) + (i32.const 32)) + (return)) + + (block $done + (block (block (block (block (block (block + (br_table 0 0 0 0 1 1 2 2 3 5 5 5 5 4 5 5 5 (get_local $type))) + ;; nil, boolean, integer, float + (br $done)) + ;; string, kw, symbol + ;; release string, then FREE reference + (call $RELEASE_STRING (get_local $mv)) + (br $done)) + ;; list, vector + (if (i32.ne (call $MalVal_val (get_local $idx) (i32.const 0)) + (i32.const 0)) + (then + ;; release next element and value + (call $RELEASE (call $MEM_VAL0_ptr (get_local $mv))) + (call $RELEASE (call $MEM_VAL1_ptr (get_local $mv))))) + (br $done)) + ;; hashmap + (if (i32.ne (call $MalVal_val (get_local $idx) (i32.const 0)) + (i32.const 0)) + (then + ;; release next element, value, and key + (call $RELEASE (call $MEM_VAL0_ptr (get_local $mv))) + (call $RELEASE (call $MEM_VAL2_ptr (get_local $mv))) + (call $RELEASE (call $MEM_VAL1_ptr (get_local $mv))))) + (br $done)) + ;; env + ;; if outer is set then release outer + (if (i32.ne (call $MalVal_val (get_local $idx) (i32.const 1)) + (i32.const 0)) + (call $RELEASE (call $MEM_VAL1_ptr (get_local $mv)))) + ;; release the hashmap data + (call $RELEASE (call $MEM_VAL0_ptr (get_local $mv))) + (br $done)) + ;; default/unknown + ) + + ;; FREE, free the current element + + ;; set type(FREE/15) and size + ;;; mv->refcnt_type = size*32 + FREE_T + (i32.store (get_local $mv) + (i32.add (i32.mul_u (get_local $size) + (i32.const 32)) + (get_global $FREE_T))) + (i32.store (call $MalVal_val_ptr (get_local $idx) (i32.const 0)) + (get_global $mem_free_list)) + (set_global $mem_free_list (get_local $idx)) + (if (i32.ge_u (get_local $size) (i32.const 3)) + (i32.store (call $MalVal_val_ptr (get_local $idx) (i32.const 1)) + (i32.const 0))) + (if (i32.eq (get_local $size) (i32.const 4)) + (i32.store (call $MalVal_val_ptr (get_local $idx) (i32.const 2)) + (i32.const 0))) + ) + + ;; Allocate a string as follows: + ;; refcnt (i32 set to 1), string data, NULL byte + (func $STRING_DUPE (param $str i32) (result i32) + (local $len i32) + (local $cur i32) + (local $new i32) + (local $idx i32) + + ;; Calculate length of string needed + (set_local $len (call $STRING_LEN (get_local $str))) + + ;; leading i32 refcnt + trailing NULL + (set_local $new (call $malloc (i32.add (i32.const 5) (get_local $len)))) + + ;; DEBUG +;; (call $debug (STRING "STRING_DUPE - malloc returned: ") (get_local $new)) + + ;; set initial refcnt to 1 + (i32.store (get_local $new) (i32.const 1)) + ;; skip refcnt + (set_local $cur (i32.add (get_local $new) (i32.const 4))) + ;; Set NULL terminator + (i32.store8_u (i32.add (get_local $cur) (get_local $len)) (i32.const 0)) + + ;; Copy the characters + (call $MEM_COPY (get_local $cur) (get_local $str) (get_local $len)) + (get_local $new) + ) + + ;; Duplicate regular character array string into a Mal string and + ;; return the MalVal pointer + (func $STRING (param $type i32) (param $str i32) (result i32) + (call $ALLOC_SCALAR + (get_local $type) + (call $STRING_DUPE (get_local $str))) + ) + + (func $RELEASE_STRING (param $mv i32) + (local $str i32) + (set_local $str (call $MalVal_val + (call $MalVal_index (get_local $mv)) + (i32.const 0))) + + ;; DEBUG +;; (call $debug (STRING "RELEASE_STRING - calling free on: ") (get_local $str)) + + (call $free (get_local $str)) + ) +) diff --git a/wasm/printer.wast b/wasm/printer.wast new file mode 100644 index 0000000000..7f88ded823 --- /dev/null +++ b/wasm/printer.wast @@ -0,0 +1,148 @@ +(module $printer + + (func $pr_str_val (param $res i32) (param $mv i32) (result i32) + (local $type i32) + (local $val0 i32) + (local $sval i32) + (set_local $type (call $TYPE (get_local $mv))) + (set_local $val0 (call $MalVal_val (call $MalVal_index (get_local $mv)) + (i32.const 0))) + + ;;; switch(type) + (block $done + (block $default + (block (block (block (block (block (block (block (block + (block (block (block (block (block (block (block (block + (br_table 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (get_local $type))) + ;; 0: nil + (call $MEM_COPY (get_local $res) (STRING "nil") (i32.const 4)) + (set_local $res (i32.add (i32.const 3) (get_local $res))) + (br $done)) + ;; 1: boolean + (if (i32.eq (get_local $val0) (i32.const 0)) + (then + ;; false + (call $MEM_COPY (get_local $res) (STRING "false") (i32.const 5)) + (set_local $res (i32.add (i32.const 5) (get_local $res)))) + (else + ;; true + (call $MEM_COPY (get_local $res) (STRING "true") (i32.const 4)) + (set_local $res (i32.add (i32.const 4) (get_local $res))))) + (br $done)) + ;; 2: integer + (set_local $res (call $sprintf_1 (get_local $res) (STRING "%d") + (get_local $val0))) + (br $done)) + ;; 3: float/ERROR + (set_local $res (call $sprintf_1 (get_local $res) (STRING "%d") + (STRING " *** GOT FLOAT *** "))) + (br $done)) + ;; 4: string/kw + (set_local $sval (call $to_String (get_local $mv))) + (if (i32.eq (i32.load8_u (get_local $sval)) (CHAR "\x7f")) + (then + (set_local $res (call $sprintf_1 (get_local $res) (STRING ":%s") + (i32.add (get_local $sval) (i32.const 1))))) + (else + (set_local $res (call $sprintf_1 (get_local $res) (STRING "\"%s\"") + (call $to_String (get_local $mv)))))) + (br $done)) + ;; 5: symbol + (set_local $res (call $sprintf_1 (get_local $res) (STRING "%s") + (call $to_String (get_local $mv)))) + (br $done)) + ;; 6: list, fallthrouogh + ) + ;; 7: vector, fallthrough + ) + ;; 8: hashmap + (set_local + $res (call $sprintf_1 (get_local $res) (STRING "%c") + (if i32 (i32.eq (get_local $type) (get_global $LIST_T)) + (CHAR "(") + (else (if i32 (i32.eq (get_local $type) (get_global $VECTOR_T)) + (CHAR "[") + (else (CHAR "{"))))))) + ;; PR_SEQ_LOOP + ;;; while (VAL0(mv) != 0) + (block $done_seq + (loop $seq_loop + (if (i32.eq (call $VAL0 (get_local $mv)) (i32.const 0)) + (br $done_seq)) + ;;; res = pr_str_val(res, MEM_VAL1(mv), print_readably) + (set_local $res (call $pr_str_val (get_local $res) + (call $MEM_VAL1_ptr (get_local $mv)))) + + ;; if this is a hash-map, print the next element + (if (i32.eq (get_local $type) (get_global $HASHMAP_T)) + (then + ;;; res += snprintf(res, 2, " ") + (set_local $res (call $sprintf_1 (get_local $res) (STRING " ") + (i32.const 0))) + (set_local $res (call $pr_str_val (get_local $res) + (call $MEM_VAL2_ptr (get_local $mv)))))) + ;;; mv = MEM_VAL0(mv) + (set_local $mv (call $MEM_VAL0_ptr (get_local $mv))) + ;;; if (VAL0(mv) != 0) + (if (i32.ne (call $VAL0 (get_local $mv)) (i32.const 0)) + ;;; res += snprintf(res, 2, " ") + (set_local $res (call $sprintf_1 (get_local $res) (STRING " ") + (i32.const 0)))) + ;;(call $print (STRING "here4\n")) + (br $seq_loop) + ) + ) + + (set_local + $res (call $sprintf_1 (get_local $res) (STRING "%c") + (if i32 (i32.eq (get_local $type) (get_global $LIST_T)) + (CHAR ")") + (else (if i32 (i32.eq (get_local $type) (get_global $VECTOR_T)) + (CHAR "]") + (else (CHAR "}"))))))) + (br $done)) + ;; 9: function + (call $MEM_COPY (get_local $res) (STRING "#") (i32.const 10)) + (set_local $res (i32.add (i32.const 9) (get_local $res))) + (br $done)) + ;; 10: mal function + (call $MEM_COPY (get_local $res) (STRING "(fn* ...)") (i32.const 10)) + (set_local $res (i32.add (i32.const 9) (get_local $res))) + (br $done)) + ;; 11: macro fn + (call $print (STRING "macro fn")) + (call $MEM_COPY (get_local $res) (STRING "#") (i32.const 13)) + (set_local $res (i32.add (i32.const 12) (get_local $res))) + (br $done)) + ;; 12: atom + (call $MEM_COPY (get_local $res) (STRING "(atom ...)") (i32.const 11)) + (set_local $res (i32.add (i32.const 10) (get_local $res))) + (br $done)) + ;; 13: environment + (call $MEM_COPY (get_local $res) (STRING "#") (i32.const 11)) + (set_local $res (i32.add (i32.const 10) (get_local $res))) + (br $done)) + ;; 14: metadata + (call $MEM_COPY (get_local $res) (STRING "#") (i32.const 12)) + (set_local $res (i32.add (i32.const 11) (get_local $res))) + (br $done)) + ;; 15: FREE + (call $MEM_COPY (get_local $res) (STRING "#") (i32.const 12)) + (set_local $res (i32.add (i32.const 11) (get_local $res))) + (br $done)) + ;; 16: default + (call $MEM_COPY (get_local $res) (STRING "#") (i32.const 11)) + (set_local $res (i32.add (i32.const 10) (get_local $res))) + ) + + (get_local $res) + ) + + (func $pr_str (param $mv i32) (result i32) + (drop (call $pr_str_val (get_global $sprintf_buf) (get_local $mv))) + (call $STRING (get_global $STRING_T) (get_global $sprintf_buf)) + ) + + (export "pr_str" (func $pr_str)) + +) diff --git a/wasm/reader.wast b/wasm/reader.wast new file mode 100644 index 0000000000..3b16df3acb --- /dev/null +++ b/wasm/reader.wast @@ -0,0 +1,321 @@ +(module $reader + + ;; TODO: global warning + (global $token (mut i32) (i32.const 0)) + (global $read_index (mut i32) (i32.const 0)) + + (func $skip_spaces (param $str i32) (result i32) + (local $found i32) + (local $c i32) + (set_local $found (i32.const 0)) + (set_local $c (i32.load8_u (i32.add (get_local $str) + (get_global $read_index)))) + (block $done + (loop $loop + ;;; while (c == ' ' || c == ',' || c == '\n') + (if (i32.and (i32.and + (i32.ne (get_local $c) (CHAR " ")) + (i32.ne (get_local $c) (CHAR ","))) + (i32.ne (get_local $c) (CHAR "\n"))) + (br $done)) + (set_local $found (i32.const 1)) + ;;; c=str[++(*index)] + (set_global $read_index (i32.add (get_global $read_index) + (i32.const 1))) + (set_local $c (i32.load8_u (i32.add (get_local $str) + (get_global $read_index)))) + (br $loop) + ) + ) +;; (call $debug (STRING ">>> skip_spaces:") (get_local $found)) + (get_local $found) + ) + + (func $skip_to_eol (param $str i32) (result i32) + (local $found i32) + (local $c i32) + (set_local $found (i32.const 0)) + (set_local $c (i32.load8_c (i32.add (get_local $str) + (get_global $read_index)))) + (if (i32.eq (get_local $c) (CHAR ";")) + (then + (set_local $found (i32.const 1)) + (block $done + (loop $loop + ;;; c=str[++(*index)] + (set_global $read_index (i32.add (get_global $read_index) + (i32.const 1))) + (set_local $c (i32.load8_u (i32.add (get_local $str) + (get_global $read_index)))) + ;;; while (c != '\0' && c != '\n') + (if (i32.and (i32.ne (get_local $c) (CHAR "\x00")) + (i32.ne (get_local $c) (CHAR "\n"))) + (br $loop)) + ) + ))) +;; (call $debug (STRING ">>> skip_to_eol:") (get_local $found)) + (get_local $found) + ) + + (func $skip_spaces_comments (param $str i32) + (loop $loop + ;; skip spaces + (if (call $skip_spaces (get_local $str)) (br $loop)) + ;; skip comments + (if (call $skip_to_eol (get_local $str)) (br $loop)) + ) + ) + + (func $read_token (param $str i32) (result i32) + (local $token_index i32) + (local $instring i32) + (local $escaped i32) + (local $c i32) + (set_local $token_index (i32.const 0)) + (set_local $instring (i32.const 0)) + (set_local $escaped (i32.const 0)) + + (call $skip_spaces_comments (get_local $str)) + + ;; read first character + ;;; c=str[++(*index)] + (set_local $c (i32.load8_u (i32.add (get_local $str) + (get_global $read_index)))) + (set_global $read_index (i32.add (get_global $read_index) + (i32.const 1))) + ;; read first character + ;;; token[token_index++] = c + (i32.store8_u (i32.add (get_global $token) (get_local $token_index)) + (get_local $c)) + (set_local $token_index (i32.add (get_local $token_index) + (i32.const 1))) + ;; single/double character token + (if (i32.or (i32.eq (get_local $c) (CHAR "(")) + (i32.or (i32.eq (get_local $c) (CHAR ")")) + (i32.or (i32.eq (get_local $c) (CHAR "[")) + (i32.or (i32.eq (get_local $c) (CHAR "]")) + (i32.or (i32.eq (get_local $c) (CHAR "{")) + (i32.or (i32.eq (get_local $c) (CHAR "}")) + (i32.or (i32.eq (get_local $c) (CHAR "'")) + (i32.or (i32.eq (get_local $c) (CHAR "`")) + (i32.or (i32.eq (get_local $c) (CHAR "@")) + (i32.and (i32.eq (get_local $c) (CHAR "~")) + (i32.eq (i32.load8_u (i32.add (get_local $str) + (get_global $read_index))) + (CHAR "@")))))))))))) + + (then + ;; continue + (nop)) + (else + ;;; if (c == '"') instring = true + (set_local $instring (i32.eq (get_local $c) (CHAR "\""))) + (block $done + (loop $loop + ;; peek at next character + ;;; c = str[*index] + (set_local $c (i32.load8_u (i32.add (get_local $str) + (get_global $read_index)))) + ;;; if (c == '\0') break + (if (i32.eq (get_local $c) (i32.const 0)) (br $done)) + ;;; if (!instring) + (if (i32.eqz (get_local $instring)) + (then + ;; next character is token delimiter + (if (i32.or (i32.eq (get_local $c) (CHAR "(")) + (i32.or (i32.eq (get_local $c) (CHAR ")")) + (i32.or (i32.eq (get_local $c) (CHAR "[")) + (i32.or (i32.eq (get_local $c) (CHAR "]")) + (i32.or (i32.eq (get_local $c) (CHAR "{")) + (i32.or (i32.eq (get_local $c) (CHAR "}")) + (i32.or (i32.eq (get_local $c) (CHAR " ")) + (i32.or (i32.eq (get_local $c) (CHAR ",")) + (i32.eq (get_local $c) (CHAR "\n")))))))))) + (br $done)))) + ;; read next character + ;;; token[token_index++] = str[(*index)++] + (i32.store8_u (i32.add (get_global $token) + (get_local $token_index)) + (i32.load8_u (i32.add (get_local $str) + (get_global $read_index)))) + (set_local $token_index (i32.add (get_local $token_index) + (i32.const 1))) + (set_global $read_index (i32.add (get_global $read_index) + (i32.const 1))) + ;;; if (token[0] == '~' && token[1] == '@') break + (if (i32.and (i32.eq (i32.load8_u (i32.add (get_global $token) + (i32.const 0))) + (CHAR "~")) + (i32.eq (i32.load8_u (i32.add (get_global $token) + (i32.const 1))) + (i32.const 0x40))) + (br $done)) + + ;;; if ((!instring) || escaped) + (if (i32.or (i32.eqz (get_local $instring)) + (get_local $escaped)) + (then + (set_local $escaped (i32.const 0)) + (br $loop))) + (if (i32.eq (get_local $c) (CHAR "\\")) + (set_local $escaped (i32.const 1))) + (if (i32.eq (get_local $c) (CHAR "\"")) + (br $done)) + (br $loop) + ) + ))) + + ;;; token[token_index] = '\0' + (i32.store8_u (i32.add (get_global $token) (get_local $token_index)) + (i32.const 0)) + (get_global $token) + ) + + (func $read_seq (param $str i32) (param $type i32) (param $end i32) + (result i32) + (local $res i32) + (local $val2 i32) + (local $val3 i32) + (local $c i32) + + ;; MAP_LOOP stack + (local $ret i32) + (local $empty i32) + (local $current i32) + + ;; MAP_LOOP_START + (set_local $res (call $MAP_LOOP_START (get_local $type))) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret (get_local $res)) + (set_local $current (get_local $res)) + (set_local $empty (get_local $res)) + + ;; READ_SEQ_LOOP + (block $done + (loop $loop + (call $skip_spaces_comments (get_local $str)) + + ;; peek at next character + ;;; c = str[*index] + (set_local $c (i32.load8_u (i32.add (get_local $str) + (get_global $read_index)))) + (if (i32.eq (get_local $c) (CHAR "\x00")) + (then + (call $THROW_STR_0 (STRING "unexpected EOF")) + (br $done))) + (if (i32.eq (get_local $c) (get_local $end)) + (then + ;; read next character + ;;; c = str[(*index)++] + (set_local $c (i32.load8_u (i32.add (get_local $str) + (get_global $read_index)))) + (set_global $read_index (i32.add (get_global $read_index) + (i32.const 1))) + (br $done))) + + ;; value (or key for hash-maps) + (set_local $val2 (call $read_form (get_local $str))) + + ;; if error, release the unattached element + (if (get_global $error_type) + (then + (call $RELEASE (get_local $val2)) + (br $done))) + + ;; if this is a hash-map, READ_FORM again + (if (i32.eq (get_local $type) (get_global $HASHMAP_T)) + (set_local $val3 (call $read_form (get_local $str)))) + + ;; update the return sequence structure + ;; MAP_LOOP_UPDATE + (set_local $res (call $MAP_LOOP_UPDATE (get_local $type) + (get_local $empty) (get_local $current) + (get_local $val2) (get_local $val3))) + (if (i32.le_u (get_local $current) (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret (get_local $res))) + ;; update current to point to new element + (set_local $current (get_local $res)) + + (br $loop) + ) + ) + + ;; MAP_LOOP_DONE + (get_local $ret) + ) + + (func $read_form (param $str i32) (result i32) + ;;(call $STRING (get_global $STRING_T) (get_local $str)) + (local $tok i32) + (local $c0 i32) + (local $c1 i32) + (local $res i32) + + (if (get_global $error_type) (return (i32.const 0))) + + (set_local $tok (call $read_token (get_local $str))) +;; (call $debug (STRING ">>> read_form 1:") (get_local $tok)) + ;;; c0 = token[0] + (set_local $c0 (i32.load8_u (get_local $tok))) + (set_local $c1 (i32.load8_u (i32.add (get_local $tok) (i32.const 1)))) + + (if (i32.eq (get_local $c0) (i32.const 0)) + (then + (return (call $INC_REF (get_global $NIL)))) + (else (if (i32.or + (i32.and + (i32.ge_u (get_local $c0) (CHAR "0")) + (i32.le_u (get_local $c0) (CHAR "9"))) + (i32.and + (i32.eq (get_local $c0) (CHAR "-")) + (i32.and (i32.ge_u (get_local $c1) (CHAR "0")) + (i32.le_u (get_local $c1) (CHAR "9"))))) + (then + (return (call $INTEGER (call $ATOI (get_local $tok))))) + (else (if (i32.eq (get_local $c0) (CHAR ":")) + (then + (i32.store8_u (get_local $tok) (CHAR "\x7f")) + (return (call $STRING (get_global $STRING_T) (get_local $tok)))) + (else (if (i32.eq (get_local $c0) (CHAR "\"")) + (then + ;; TODO: unescape + (i32.store8_u (i32.sub_u + (i32.add (get_local $tok) + (call $STRING_LEN (get_local $tok))) + (i32.const 1)) + (CHAR "\x00")) + (return (call $STRING (get_global $STRING_T) (i32.add (get_local $tok) + (i32.const 1))))) + (else (if (i32.eq (get_local $c0) (CHAR "(")) + (then + (return (call $read_seq (get_local $str) + (get_global $LIST_T) (CHAR ")")))) + (else (if (i32.eq (get_local $c0) (CHAR "[")) + (then + (return (call $read_seq (get_local $str) + (get_global $VECTOR_T) (CHAR "]")))) + (else (if (i32.eq (get_local $c0) (CHAR "{")) + (then + (return (call $read_seq (get_local $str) + (get_global $HASHMAP_T) (CHAR "}")))) + (else (if (i32.or (i32.eq (get_local $c0) (CHAR ")")) + (i32.or (i32.eq (get_local $c0) (CHAR "]")) + (i32.eq (get_local $c0) (CHAR "}")))) + (then + (call $THROW_STR_1 (STRING "unexpected '%c'") (get_local $c0)) + (return (i32.const 0))) + (else + (return (call $STRING (get_global $SYMBOL_T) + (get_local $tok))))))))))))))))))) + ) + + (func $read_str (param $str i32) (result i32) + (set_global $read_index (i32.const 0)) + (call $read_form (get_local $str)) + ) + + (export "read_str" (func $read_str)) + +) diff --git a/wasm/run b/wasm/run new file mode 100755 index 0000000000..f91d7b1593 --- /dev/null +++ b/wasm/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec wace $(dirname $0)/${STEP:-stepA_mal}.wasm "${@}" diff --git a/wasm/step0_repl.wast b/wasm/step0_repl.wast new file mode 100644 index 0000000000..652bb560cd --- /dev/null +++ b/wasm/step0_repl.wast @@ -0,0 +1,41 @@ +(module $step0_repl + (import "env" "memory" (memory $0 256)) + (import "env" "memoryBase" (global $memoryBase i32)) + + (func $READ (param $str i32) (result i32) + (get_local $str)) + + (func $EVAL (param $ast i32) (param $env i32) (result i32) + (get_local $ast)) + + (func $PRINT (param $ast i32) (result i32) + (get_local $ast)) + + (func $rep (param $str i32) (result i32) + (call $PRINT + (call $EVAL + (call $READ (get_local $str)) + (i32.const 0)))) + + (func $main (result i32) + ;; Constant location/value definitions + (local $line i32) + + ;; Start + (block $repl_done + (loop $repl_loop + (set_local $line (call $readline (STRING "user> "))) + (if (i32.eqz (get_local $line)) (br $repl_done)) + (call $printf_1 (STRING "%s\n") (call $rep (get_local $line))) + (call $free (get_local $line)) + (br $repl_loop))) + + (call $print (STRING "\n")) + (i32.const 0) + ) + + + (export "_main" (func $main)) + (export "__post_instantiate" (func $init_sprintf_mem)) +) + diff --git a/wasm/step1_read_print.wast b/wasm/step1_read_print.wast new file mode 100644 index 0000000000..51fcb41ac4 --- /dev/null +++ b/wasm/step1_read_print.wast @@ -0,0 +1,81 @@ +(module $step1_read_print + (import "env" "memory" (memory $0 256)) + (import "env" "memoryBase" (global $memoryBase i32)) + + (func $READ (param $str i32) (result i32) + (call $read_str (get_local $str))) + + (func $EVAL (param $ast i32) (param $env i32) (result i32) + (get_local $ast)) + + (func $PRINT (param $ast i32) (result i32) + (call $pr_str (get_local $ast))) + + (func $rep (param $line i32) (param $env i32) (result i32) + (local $mv1 i32) + (local $mv2 i32) + (local $ms i32) + (block $rep_done + (set_local $mv1 (call $READ (get_local $line))) + (if (get_global $error_type) (br $rep_done)) + + (set_local $mv2 (call $EVAL (get_local $mv1) (get_local $env))) + (if (get_global $error_type) (br $rep_done)) + +;; (call $PR_MEMORY (i32.const -1) (i32.const -1)) + (set_local $ms (call $PRINT (get_local $mv2))) + ) + +;; (call $PR_MEMORY (i32.const -1) (i32.const -1)) + (call $RELEASE (get_local $mv1)) + (get_local $ms) + ) + + (func $main (result i32) + ;; Constant location/value definitions + (local $line i32) + (local $res i32) + + ;; DEBUG + (call $printf_1 (STRING "memoryBase: %d\n") (get_global $memoryBase)) + (call $printf_1 (STRING "heap_start: %d\n") (get_global $heap_start)) + (call $printf_1 (STRING "heap_end: %d\n") (get_global $heap_end)) + (call $printf_1 (STRING "mem: %d\n") (get_global $mem)) +;; (call $printf_1 (STRING "string_mem: %d\n") (get_global $string_mem)) + (call $PR_MEMORY (i32.const -1) (i32.const -1)) +;; (call $PR_MEMORY_RAW (get_global $mem) +;; (i32.add (get_global $mem) +;; (i32.mul_u (get_global $mem_unused_start) +;; (i32.const 8)))) + + ;; Start + (block $repl_done + (loop $repl_loop + (set_local $line (call $readline (STRING "user> "))) + (if (i32.eqz (get_local $line)) (br $repl_done)) + (if (i32.eq (i32.load8_u (get_local $line)) (i32.const 0)) + (then + (call $free (get_local $line)) + (br $repl_loop))) + (set_local $res (call $rep (get_local $line) (i32.const 0))) + (if (get_global $error_type) + (then + (call $printf_1 (STRING "Error: %s\n") (get_global $error_str)) + (set_global $error_type (i32.const 0))) + (else + (call $printf_1 (STRING "%s\n") (call $to_String (get_local $res))))) + (call $RELEASE (get_local $res)) +;; (call $PR_MEMORY (i32.const -1) (i32.const -1)) + (call $free (get_local $line)) + (br $repl_loop))) + + (call $print (STRING "\n")) + (call $PR_MEMORY (i32.const -1) (i32.const -1)) + (i32.const 0) + ) + + + (export "_main" (func $main)) + (export "__post_instantiate" (func $init_memory)) +) + diff --git a/wasm/step2_eval.wast b/wasm/step2_eval.wast new file mode 100644 index 0000000000..ac30da431c --- /dev/null +++ b/wasm/step2_eval.wast @@ -0,0 +1,269 @@ +(module $step1_read_print + (import "env" "memory" (memory $0 256)) + (import "env" "memoryBase" (global $memoryBase i32)) + + ;; READ + (func $READ (param $str i32) (result i32) + (call $read_str (get_local $str)) + ) + + ;; EVAL + (func $EVAL_AST (param $ast i32) (param $env i32) (result i32) + (local $res i32) + (local $val2 i32) + (local $val3 i32) + (local $ret i32) + (local $empty i32) + (local $current i32) + (local $type i32) + (local $res2 i64) + (local $found i32) + + (if (get_global $error_type) (return (i32.const 0))) + (set_local $type (call $TYPE (get_local $ast))) + + ;;; switch(type) + (block $done + (block $default (block (block + (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 (get_local $type))) + ;; symbol + ;; found/res returned as hi 32/lo 32 of i64 + (set_local $res2 (call $HASHMAP_GET (get_local $env) (get_local $ast))) + (set_local $res (i32.wrap/i64 (get_local $res2))) + (set_local $found (i32.wrap/i64 (i64.shr_u (get_local $res2) + (i64.const 32)))) + (if (i32.eqz (get_local $found)) + (call $THROW_STR_1 (STRING "'%s' not found") + (call $to_String (get_local $ast)))) + (set_local $res (call $INC_REF (get_local $res))) + + (br $done)) + ;; list, vector, hashmap + ;; MAP_LOOP_START + (set_local $res (call $MAP_LOOP_START (get_local $type))) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret (get_local $res)) + (set_local $current (get_local $res)) + (set_local $empty (get_local $res)) + + (block $done + (loop $loop + ;; check if we are done evaluating the source sequence + (if (i32.eq (call $VAL0 (get_local $ast)) (i32.const 0)) + (br $done)) + + (if (i32.eq (get_local $type) (get_global $HASHMAP_T)) + (then + (set_local $res (call $EVAL (call $MEM_VAL2_ptr (get_local $ast)) + (get_local $env)))) + (else + (set_local $res (call $EVAL (call $MEM_VAL1_ptr (get_local $ast)) + (get_local $env))))) + (set_local $val2 (get_local $res)) + + ;; if error, release the unattached element + (if (get_global $error_type) + (then + (call $RELEASE (get_local $res)) + (set_local $res (i32.const 0)) + (br $done))) + + ;; for hash-maps, copy the key (inc ref since we are going + ;; to release it below) + (if (i32.eq (get_local $type) (get_global $HASHMAP_T)) + (then + (set_local $val3 (get_local $val2)) + (set_local $val2 (call $MEM_VAL1_ptr (get_local $ast))) + (drop (call $INC_REF (get_local $ast))))) + + ;; MAP_LOOP_UPDATE + (set_local $res (call $MAP_LOOP_UPDATE (get_local $type) + (get_local $empty) (get_local $current) + (get_local $val2) (get_local $val3))) + (if (i32.le_u (get_local $current) (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret (get_local $res))) + ;; update current to point to new element + (set_local $current (get_local $res)) + + (set_local $ast (call $MEM_VAL0_ptr (get_local $ast))) + + (br $loop) + ) + ) + ;; MAP_LOOP_DONE + (set_local $res (get_local $ret)) + ;; EVAL_AST_RETURN: nothing to do + (br $done)) + ;; default + (set_local $res (call $INC_REF (get_local $ast))) + ) + + (get_local $res) + ) + + (type $fnT (func (param i32) (result i32))) + + (table anyfunc + (elem + $add $subtract $multiply $divide)) + + (func $EVAL (param $ast i32) (param $env i32) (result i32) + (local $res i32) + (local $f_args i32) + (local $f i32) + (local $args i32) + (local $type i32) + (local $ftype i32) + + (set_local $res (i32.const 0)) + (set_local $f_args (i32.const 0)) + (set_local $f (i32.const 0)) + (set_local $args (i32.const 0)) + (set_local $type (call $TYPE (get_local $ast))) + + (if (get_global $error_type) (return (i32.const 0))) + + (if (i32.ne (get_local $type) (get_global $LIST_T)) + (return (call $EVAL_AST (get_local $ast) (get_local $env)))) + + ;; APPLY_LIST + (if (call $EMPTY_Q (get_local $ast)) + (return (call $INC_REF (get_local $ast)))) + + ;; EVAL_INVOKE + (set_local $res (call $EVAL_AST (get_local $ast) (get_local $env))) + (set_local $f_args (get_local $res)) + + ;; if error, return f/args for release by caller + (if (get_global $error_type) (return (get_local $f_args))) + + ;; rest + (set_local $args (call $MEM_VAL0_ptr (get_local $f_args))) + ;; value + (set_local $f (call $MEM_VAL1_ptr (get_local $f_args))) + + (set_local $ftype (call $TYPE (get_local $f))) + (if (i32.eq (get_local $ftype) (get_global $FUNCTION_T)) + (then + (set_local $res (call_indirect (type $fnT) (get_local $args) + (call $VAL0 (get_local $f))))) + (else + (call $THROW_STR_1 (STRING "apply of non-function type: %d\n") + (get_local $type)) + (set_local $res (i32.const 0)))) + + (call $RELEASE (get_local $f_args)) + + (get_local $res) + ) + + (func $PRINT (param $ast i32) (result i32) + (call $pr_str (get_local $ast)) + ) + + ;; REPL + (func $rep (param $line i32) (param $env i32) (result i32) + (local $mv1 i32) + (local $mv2 i32) + (local $ms i32) + (block $rep_done + (set_local $mv1 (call $READ (get_local $line))) + (if (get_global $error_type) (br $rep_done)) + + (set_local $mv2 (call $EVAL (get_local $mv1) (get_local $env))) + (if (get_global $error_type) (br $rep_done)) + +;; (call $PR_MEMORY (i32.const -1) (i32.const -1)) + (set_local $ms (call $PRINT (get_local $mv2))) + ) + + ;; release memory from MAL_READ and EVAL + (call $RELEASE (get_local $mv2)) + (call $RELEASE (get_local $mv1)) + (get_local $ms) + ) + + (func $add (param $args i32) (result i32) + (call $INTEGER + (i32.add (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) + (call $VAL0 (call $MEM_VAL1_ptr + (call $MEM_VAL0_ptr (get_local $args))))))) + (func $subtract (param $args i32) (result i32) + (call $INTEGER + (i32.sub_s (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) + (call $VAL0 (call $MEM_VAL1_ptr + (call $MEM_VAL0_ptr (get_local $args))))))) + (func $multiply (param $args i32) (result i32) + (call $INTEGER + (i32.mul_s (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) + (call $VAL0 (call $MEM_VAL1_ptr + (call $MEM_VAL0_ptr (get_local $args))))))) + (func $divide (param $args i32) (result i32) + (call $INTEGER + (i32.div_s (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) + (call $VAL0 (call $MEM_VAL1_ptr + (call $MEM_VAL0_ptr (get_local $args))))))) + + (func $main (result i32) + ;; Constant location/value definitions + (local $line i32) + (local $res i32) + (local $repl_env i32) + + ;; DEBUG + (call $printf_1 (STRING "memoryBase: %d\n") (get_global $memoryBase)) + (call $printf_1 (STRING "heap_start: %d\n") (get_global $heap_start)) + (call $printf_1 (STRING "heap_end: %d\n") (get_global $heap_end)) + (call $printf_1 (STRING "mem: %d\n") (get_global $mem)) +;; (call $printf_1 (STRING "string_mem: %d\n") (get_global $string_mem)) + + (set_local $repl_env (call $HASHMAP)) + + (set_local $repl_env (call $ASSOC1_S (get_local $repl_env) + (STRING "+") (call $FUNCTION (i32.const 0)))) + (set_local $repl_env (call $ASSOC1_S (get_local $repl_env) + (STRING "-") (call $FUNCTION (i32.const 1)))) + (set_local $repl_env (call $ASSOC1_S (get_local $repl_env) + (STRING "*") (call $FUNCTION (i32.const 2)))) + (set_local $repl_env (call $ASSOC1_S (get_local $repl_env) + (STRING "/") (call $FUNCTION (i32.const 3)))) + + (call $PR_MEMORY (i32.const -1) (i32.const -1)) +;; (call $PR_MEMORY_RAW (get_global $mem) +;; (i32.add (get_global $mem) +;; (i32.mul_u (get_global $mem_unused_start) +;; (i32.const 8)))) + + ;; Start + (block $repl_done + (loop $repl_loop + (set_local $line (call $readline (STRING "user> "))) + (if (i32.eqz (get_local $line)) (br $repl_done)) + (if (i32.eq (i32.load8_u (get_local $line)) (i32.const 0)) + (then + (call $free (get_local $line)) + (br $repl_loop))) + (set_local $res (call $rep (get_local $line) (get_local $repl_env))) + (if (get_global $error_type) + (then + (call $printf_1 (STRING "Error: %s\n") (get_global $error_str)) + (set_global $error_type (i32.const 0))) + (else + (call $printf_1 (STRING "%s\n") (call $to_String (get_local $res))))) + (call $RELEASE (get_local $res)) +;; (call $PR_MEMORY (i32.const -1) (i32.const -1)) + (call $free (get_local $line)) + (br $repl_loop))) + + (call $print (STRING "\n")) + (call $PR_MEMORY (i32.const -1) (i32.const -1)) + (i32.const 0) + ) + + + (export "_main" (func $main)) + (export "__post_instantiate" (func $init_memory)) +) + diff --git a/wasm/step3_env.wast b/wasm/step3_env.wast new file mode 100644 index 0000000000..21afa31cdf --- /dev/null +++ b/wasm/step3_env.wast @@ -0,0 +1,329 @@ +(module $step1_read_print + (import "env" "memory" (memory $0 256)) + (import "env" "memoryBase" (global $memoryBase i32)) + + ;; READ + (func $READ (param $str i32) (result i32) + (call $read_str (get_local $str)) + ) + + ;; EVAL + (func $EVAL_AST (param $ast i32) (param $env i32) (result i32) + (local $res i32) + (local $val2 i32) + (local $val3 i32) + (local $ret i32) + (local $empty i32) + (local $current i32) + (local $type i32) + (local $found i32) + + (if (get_global $error_type) (return (i32.const 0))) + (set_local $type (call $TYPE (get_local $ast))) + + ;;; switch(type) + (block $done + (block $default (block (block + (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 (get_local $type))) + ;; symbol + ;; found/res returned as hi 32/lo 32 of i64 + (set_local $res (call $ENV_GET (get_local $env) (get_local $ast))) + (br $done)) + ;; list, vector, hashmap + ;; MAP_LOOP_START + (set_local $res (call $MAP_LOOP_START (get_local $type))) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret (get_local $res)) + (set_local $current (get_local $res)) + (set_local $empty (get_local $res)) + + (block $done + (loop $loop + ;; check if we are done evaluating the source sequence + (if (i32.eq (call $VAL0 (get_local $ast)) (i32.const 0)) + (br $done)) + + (if (i32.eq (get_local $type) (get_global $HASHMAP_T)) + (then + (set_local $res (call $EVAL (call $MEM_VAL2_ptr (get_local $ast)) + (get_local $env)))) + (else + (set_local $res (call $EVAL (call $MEM_VAL1_ptr (get_local $ast)) + (get_local $env))))) + (set_local $val2 (get_local $res)) + + ;; if error, release the unattached element + (if (get_global $error_type) + (then + (call $RELEASE (get_local $res)) + (set_local $res (i32.const 0)) + (br $done))) + + ;; for hash-maps, copy the key (inc ref since we are going + ;; to release it below) + (if (i32.eq (get_local $type) (get_global $HASHMAP_T)) + (then + (set_local $val3 (get_local $val2)) + (set_local $val2 (call $MEM_VAL1_ptr (get_local $ast))) + (drop (call $INC_REF (get_local $ast))))) + + ;; MAP_LOOP_UPDATE + (set_local $res (call $MAP_LOOP_UPDATE (get_local $type) + (get_local $empty) (get_local $current) + (get_local $val2) (get_local $val3))) + (if (i32.le_u (get_local $current) (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret (get_local $res))) + ;; update current to point to new element + (set_local $current (get_local $res)) + + (set_local $ast (call $MEM_VAL0_ptr (get_local $ast))) + + (br $loop) + ) + ) + ;; MAP_LOOP_DONE + (set_local $res (get_local $ret)) + ;; EVAL_AST_RETURN: nothing to do + (br $done)) + ;; default + (set_local $res (call $INC_REF (get_local $ast))) + ) + + (get_local $res) + ) + + (type $fnT (func (param i32) (result i32))) + + (table anyfunc + (elem + $add $subtract $multiply $divide)) + + (func $MAL_GET_A1 (param $ast i32) (result i32) + (call $MEM_VAL1_ptr (call $MEM_VAL0_ptr (get_local $ast)))) + (func $MAL_GET_A2 (param $ast i32) (result i32) + (call $MEM_VAL1_ptr (call $MEM_VAL0_ptr (call $MEM_VAL0_ptr (get_local $ast))))) + (func $MAL_GET_A3 (param $ast i32) (result i32) + (call $MEM_VAL1_ptr (call $MEM_VAL0_ptr (call $MEM_VAL0_ptr (call $MEM_VAL0_ptr (get_local $ast)))))) + + (func $EVAL (param $ast i32) (param $env i32) (result i32) + (local $res i32) + (local $f_args i32) + (local $f i32) + (local $args i32) + (local $type i32) + (local $ftype i32) + (local $a0 i32) + (local $a0sym i32) + (local $a1 i32) + (local $a2 i32) + (local $let_env i32) + + (set_local $res (i32.const 0)) + (set_local $f_args (i32.const 0)) + (set_local $f (i32.const 0)) + (set_local $args (i32.const 0)) + (set_local $type (call $TYPE (get_local $ast))) + + ;;(call $PR_VALUE (STRING ">>> EVAL ast: '%s'\n") (get_local $ast)) + + (if (get_global $error_type) (return (i32.const 0))) + + (if (i32.ne (get_local $type) (get_global $LIST_T)) + (return (call $EVAL_AST (get_local $ast) (get_local $env)))) + + ;; APPLY_LIST + (if (call $EMPTY_Q (get_local $ast)) + (return (call $INC_REF (get_local $ast)))) + + (set_local $a0 (call $MEM_VAL1_ptr (get_local $ast))) + (set_local $a0sym (STRING "")) + (if (i32.eq (call $TYPE (get_local $a0)) (get_global $SYMBOL_T)) + (set_local $a0sym (call $to_String (get_local $a0)))) + + (if (i32.eqz (call $strcmp (STRING "def!") (get_local $a0sym))) + (then + (set_local $a1 (call $MAL_GET_A1 (get_local $ast))) + (set_local $a2 (call $MAL_GET_A2 (get_local $ast))) + (set_local $res (call $EVAL (get_local $a2) (get_local $env))) + (if (get_global $error_type) (return (get_local $res))) + + ;; set a1 in env to a2 + (set_local $res (call $ENV_SET (get_local $env) + (get_local $a1) (get_local $res)))) + (else (if (i32.eqz (call $strcmp (STRING "let*") (get_local $a0sym))) + (then + (set_local $a1 (call $MAL_GET_A1 (get_local $ast))) + (set_local $a2 (call $MAL_GET_A2 (get_local $ast))) + + ;; create new environment with outer as current environment + (set_local $let_env (call $ENV_NEW (get_local $env))) + + (block $done + (loop $loop + (if (i32.eqz (call $VAL0 (get_local $a1))) + (br $done)) + ;; eval current A1 odd element + (set_local $res (call $EVAL (call $MEM_VAL1_ptr + (call $MEM_VAL0_ptr + (get_local $a1))) + (get_local $let_env))) + + (if (get_global $error_type) (br $done)) + + ;; set key/value in the let environment + (set_local $res (call $ENV_SET (get_local $let_env) + (call $MEM_VAL1_ptr (get_local $a1)) + (get_local $res))) + ;; release our use, ENV_SET took ownership + (call $RELEASE (get_local $res)) + + ;; skip to the next pair of a1 elements + (set_local $a1 (call $MEM_VAL0_ptr + (call $MEM_VAL0_ptr (get_local $a1)))) + (br $loop) + ) + ) + (set_local $res (call $EVAL (get_local $a2) (get_local $let_env))) + ;; EVAL_RETURN + (call $RELEASE (get_local $let_env))) + (else + ;; EVAL_INVOKE + (set_local $res (call $EVAL_AST (get_local $ast) (get_local $env))) + (set_local $f_args (get_local $res)) + + ;; if error, return f/args for release by caller + (if (get_global $error_type) (return (get_local $f_args))) + + ;; rest + (set_local $args (call $MEM_VAL0_ptr (get_local $f_args))) + ;; value + (set_local $f (call $MEM_VAL1_ptr (get_local $f_args))) + + (set_local $ftype (call $TYPE (get_local $f))) + (if (i32.eq (get_local $ftype) (get_global $FUNCTION_T)) + (then + (set_local $res (call_indirect (type $fnT) (get_local $args) + (call $VAL0 (get_local $f))))) + (else + (call $THROW_STR_1 (STRING "apply of non-function type: %d\n") + (get_local $type)) + (set_local $res (i32.const 0)))) + + (call $RELEASE (get_local $f_args)))))) + + (get_local $res) + ) + + (func $PRINT (param $ast i32) (result i32) + (call $pr_str (get_local $ast)) + ) + + ;; REPL + (func $rep (param $line i32) (param $env i32) (result i32) + (local $mv1 i32) + (local $mv2 i32) + (local $ms i32) + (block $rep_done + (set_local $mv1 (call $READ (get_local $line))) + (if (get_global $error_type) (br $rep_done)) + + (set_local $mv2 (call $EVAL (get_local $mv1) (get_local $env))) + (if (get_global $error_type) (br $rep_done)) + +;; (call $PR_MEMORY (i32.const -1) (i32.const -1)) + (set_local $ms (call $PRINT (get_local $mv2))) + ) + + ;; release memory from MAL_READ and EVAL + (call $RELEASE (get_local $mv2)) + (call $RELEASE (get_local $mv1)) + (get_local $ms) + ) + + (func $add (param $args i32) (result i32) + (call $INTEGER + (i32.add (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) + (call $VAL0 (call $MEM_VAL1_ptr + (call $MEM_VAL0_ptr (get_local $args))))))) + (func $subtract (param $args i32) (result i32) + (call $INTEGER + (i32.sub_s (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) + (call $VAL0 (call $MEM_VAL1_ptr + (call $MEM_VAL0_ptr (get_local $args))))))) + (func $multiply (param $args i32) (result i32) + (call $INTEGER + (i32.mul_s (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) + (call $VAL0 (call $MEM_VAL1_ptr + (call $MEM_VAL0_ptr (get_local $args))))))) + (func $divide (param $args i32) (result i32) + (call $INTEGER + (i32.div_s (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) + (call $VAL0 (call $MEM_VAL1_ptr + (call $MEM_VAL0_ptr (get_local $args))))))) + (func $pr_memory (param $args i32) (result i32) + (call $PR_MEMORY (i32.const -1) (i32.const -1)) + (call $INC_REF (get_global $NIL))) + + (func $main (result i32) + ;; Constant location/value definitions + (local $line i32) + (local $res i32) + (local $repl_env i32) + + ;; DEBUG + (call $printf_1 (STRING "memoryBase: 0x%x\n") (get_global $memoryBase)) + (call $printf_1 (STRING "heap_start: 0x%x\n") (get_global $heap_start)) + (call $printf_1 (STRING "heap_end: 0x%x\n") (get_global $heap_end)) + (call $printf_1 (STRING "mem: 0x%x\n") (get_global $mem)) +;; (call $printf_1 (STRING "string_mem: %d\n") (get_global $string_mem)) + + (set_local $repl_env (call $ENV_NEW (get_global $NIL))) + + (drop (call $ENV_SET_S (get_local $repl_env) + (STRING "+") (call $FUNCTION (i32.const 0)))) + (drop (call $ENV_SET_S (get_local $repl_env) + (STRING "-") (call $FUNCTION (i32.const 1)))) + (drop (call $ENV_SET_S (get_local $repl_env) + (STRING "*") (call $FUNCTION (i32.const 2)))) + (drop (call $ENV_SET_S (get_local $repl_env) + (STRING "/") (call $FUNCTION (i32.const 3)))) + + (call $PR_MEMORY (i32.const -1) (i32.const -1)) +;; (call $PR_MEMORY_RAW (get_global $mem) +;; (i32.add (get_global $mem) +;; (i32.mul_u (get_global $mem_unused_start) +;; (i32.const 8)))) + + ;; Start + (block $repl_done + (loop $repl_loop + (set_local $line (call $readline (STRING "user> "))) + (if (i32.eqz (get_local $line)) (br $repl_done)) + (if (i32.eq (i32.load8_u (get_local $line)) (i32.const 0)) + (then + (call $free (get_local $line)) + (br $repl_loop))) + (set_local $res (call $rep (get_local $line) (get_local $repl_env))) + (if (get_global $error_type) + (then + (call $printf_1 (STRING "Error: %s\n") (get_global $error_str)) + (set_global $error_type (i32.const 0))) + (else + (call $printf_1 (STRING "%s\n") (call $to_String (get_local $res))))) + (call $RELEASE (get_local $res)) +;; (call $PR_MEMORY (i32.const -1) (i32.const -1)) + (call $free (get_local $line)) + (br $repl_loop))) + + (call $print (STRING "\n")) + (call $PR_MEMORY (i32.const -1) (i32.const -1)) + (i32.const 0) + ) + + + (export "_main" (func $main)) + (export "__post_instantiate" (func $init_memory)) +) + diff --git a/wasm/types.wast b/wasm/types.wast new file mode 100644 index 0000000000..cd1212c167 --- /dev/null +++ b/wasm/types.wast @@ -0,0 +1,202 @@ +;; Mal value memory layout +;; type words +;; ---------- ---------- +;; nil ref/ 0 | 0 | | +;; false ref/ 1 | 0 | | +;; true ref/ 1 | 1 | | +;; integer ref/ 2 | int | | +;; float ref/ 3 | ??? | | +;; string/kw ref/ 4 | string ptr | | +;; symbol ref/ 5 | string ptr | | +;; list ref/ 6 | next mem idx | val mem idx | +;; vector ref/ 7 | next mem idx | val mem idx | +;; hashmap ref/ 8 | next mem idx | key mem idx | val mem idx +;; function ref/ 9 | fn idx | | +;; mal function ref/10 | body mem idx | param mem idx | env mem idx +;; macro fn ref/11 | body mem idx | param mem idx | env mem idx +;; atom ref/12 | val mem idx | | +;; environment ref/13 | hmap mem idx | outer mem idx | +;; metadata ref/14 | obj mem idx | meta mem idx | +;; FREE sz/15 | next mem idx | | + +(module $types + + (global $NIL_T i32 (i32.const 0)) + (global $BOOLEAN_T i32 (i32.const 1)) + (global $INTEGER_T i32 (i32.const 2)) + (global $FLOAT_T i32 (i32.const 3)) + (global $STRING_T i32 (i32.const 4)) + (global $SYMBOL_T i32 (i32.const 5)) + (global $LIST_T i32 (i32.const 6)) + (global $VECTOR_T i32 (i32.const 7)) + (global $HASHMAP_T i32 (i32.const 8)) + (global $FUNCTION_T i32 (i32.const 9)) + (global $MALFUNC_T i32 (i32.const 10)) + (global $MACRO_T i32 (i32.const 11)) + (global $ATOM_T i32 (i32.const 12)) + (global $ENVIRONMENT_T i32 (i32.const 13)) + (global $METADATA_T i32 (i32.const 14)) + (global $FREE_T i32 (i32.const 15)) + + (global $error_type (mut i32) (i32.const 0)) + (global $error_val (mut i32) (i32.const 0)) + ;; Index into static string memory (static.wast) + (global $error_str (mut i32) (i32.const 0)) + + (global $NIL (mut i32) (i32.const 0)) + (global $FALSE (mut i32) (i32.const 0)) + (global $TRUE (mut i32) (i32.const 0)) + (global $EMPTY_LIST (mut i32) (i32.const 0)) + (global $EMPTY_VECTOR (mut i32) (i32.const 0)) + (global $EMPTY_HASHMAP (mut i32) (i32.const 0)) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; General functions + + (func $INC_REF (param $mv i32) (result i32) + (i32.store (get_local $mv) + (i32.add (i32.load (get_local $mv)) + (i32.const 32))) + (get_local $mv)) + + (func $THROW_STR_0 (param $fmt i32) + (drop (call $sprintf_1 (get_global $error_str) (get_local $fmt) (STRING ""))) + (set_global $error_type (i32.const 1))) + + (func $THROW_STR_1 (param $fmt i32) (param $v0 i32) + (drop (call $sprintf_1 (get_global $error_str) (get_local $fmt) (get_local $v0))) + (set_global $error_type (i32.const 1))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; numeric functions + + (func $INTEGER (param $val i32) (result i32) + (call $ALLOC_SCALAR (get_global $INTEGER_T) (get_local $val))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; sequence functions + + (func $MAP_LOOP_START (param $type i32) (result i32) + (local $res i32) + (set_local $res (if i32 (i32.eq (get_local $type) + (get_global $LIST_T)) + (get_global $EMPTY_LIST) + (else (if i32 (i32.eq (get_local $type) + (get_global $VECTOR_T)) + (get_global $EMPTY_VECTOR) + (else (if i32 (i32.eq (get_local $type) + (get_global $HASHMAP_T)) + (get_global $EMPTY_HASHMAP) + (else + (call $THROW_STR_1 (STRING "read_seq invalid type %d") + (get_local $type)) + (i32.const 0)))))))) + + (call $INC_REF (get_local $res)) + ) + + (func $MAP_LOOP_UPDATE (param $type i32) (param $empty i32) + (param $current i32) (param $val2 i32) (param $val3 i32) + (result i32) + (local $res i32) + + (set_local $res (call $ALLOC (get_local $type) (get_local $empty) + (get_local $val2) (get_local $val3))) + ;; sequence took ownership + (call $RELEASE (get_local $empty)) + (call $RELEASE (get_local $val2)) + (if (i32.eq (get_local $type) (get_global $HASHMAP_T)) + (call $RELEASE (get_local $val3))) + (if (i32.gt_u (get_local $current) (get_global $EMPTY_HASHMAP)) + ;; if not first element, set current next to point to new element + (i32.store (call $VAL0_ptr (get_local $current)) + (call $MalVal_index (get_local $res)))) + + (get_local $res) + ) + + (func $EMPTY_Q (param $mv i32) (result i32) + (i32.eq (call $VAL0 (get_local $mv)) (i32.const 0)) + ) + + (func $HASHMAP (result i32) + ;; just point to static empty hash-map + (call $INC_REF (get_global $EMPTY_HASHMAP)) + ) + + (func $ASSOC1 (param $hm i32) (param $k i32) (param $v i32) (result i32) + (local $res i32) + (set_local $res (call $ALLOC (get_global $HASHMAP_T) (get_local $hm) + (get_local $k) (get_local $v))) + ;; we took ownership of previous release + (call $RELEASE (get_local $hm)) + (get_local $res) + ) + + (func $ASSOC1_S (param $hm i32) (param $k i32) (param $v i32) (result i32) + (local $kmv i32) + (local $res i32) + (set_local $kmv (call $STRING (get_global $STRING_T) (get_local $k))) + (set_local $res (call $ASSOC1 (get_local $hm) + (get_local $kmv) (get_local $v))) + ;; map took ownership of key + (call $RELEASE (get_local $kmv)) + (get_local $res) + ) + + (func $HASHMAP_GET (param $hm i32) (param $key_mv i32) (result i64) + (local $res i32) + (local $found i32) + (local $key i32) + (local $test_key_mv i32) + + (set_local $key (call $to_String (get_local $key_mv))) + (set_local $found (i32.const 0)) + + + (block $done + (loop $loop + ;;; if (VAL0(hm) == 0) + (if (i32.eq (call $VAL0 (get_local $hm)) (i32.const 0)) + (then + (set_local $res (get_global $NIL)) + (br $done))) + ;;; test_key_mv = MEM_VAL1(hm) + (set_local $test_key_mv (call $MEM_VAL1_ptr (get_local $hm))) + ;;; if (strcmp(key, to_String(test_key_mv)) == 0) + (if (i32.eq (call $strcmp (get_local $key) + (call $to_String (get_local $test_key_mv))) + (i32.const 0)) + (then + (set_local $found (i32.const 1)) + (set_local $res (call $MEM_VAL2_ptr (get_local $hm))) + (br $done))) + (set_local $hm (call $MEM_VAL0_ptr (get_local $hm))) + + (br $loop) + ) + ) + + ;; combine found/res as hi 32/low 32 of i64 + (i64.or + (i64.shl_u (i64.extend_u/i32 (get_local $found)) + (i64.const 32)) + (i64.extend_u/i32 (get_local $res))) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; function functions + + (func $FUNCTION (param $index i32) (result i32) + (call $ALLOC_SCALAR (get_global $FUNCTION_T) (get_local $index)) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; string functions + + (func $to_String (param $mv i32) (result i32) + (i32.add (i32.const 4) ;; skip string refcnt + (call $MalVal_val + (call $MalVal_index (get_local $mv)) + (i32.const 0)))) +) diff --git a/wasm/util.wast b/wasm/util.wast new file mode 100644 index 0000000000..acbe12285a --- /dev/null +++ b/wasm/util.wast @@ -0,0 +1,275 @@ +(module $util + (import "env" "malloc" (func $malloc (param i32) (result i32))) + (import "env" "free" (func $free (param i32))) + (import "env" "exit" (func $exit (param i32))) + + (import "env" "stdout" (global $stdout i32)) + (import "env" "putchar" (func $putchar (param i32) (result i32))) + (import "env" "fputs" (func $fputs (param i32 i32) (result i32))) + ;;(import "env" "readline" (func $readline (param i32) (result i32))) + (import "libedit.so" "readline" (func $readline (param i32) (result i32))) + ;;(import "libreadline.so" "readline" (func $readline (param i32) (result i32))) + + (global $sprintf_buf (mut i32) (i32.const 0)) + + (func $init_sprintf_mem + ;; 256 character sprintf static buffer + (set_global $sprintf_buf (STRING " ")) + ) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ;; Copy len chatacters from src to dst + ;; Returns len + (func $MEM_COPY (param $dst i32) (param $src i32) (param $len i32) + (local $idx i32) + (set_local $idx (i32.const 0)) + (loop $copy + (i32.store8_u (i32.add (get_local $idx) (get_local $dst)) + (i32.load8_u (i32.add (get_local $idx) + (get_local $src)))) + (set_local $idx (i32.add (i32.const 1) (get_local $idx))) + (br_if $copy (i32.lt_u (get_local $idx) (get_local $len))) + ) + ) + + (func $STRING_LEN (param $str i32) (result i32) + (local $cur i32) + (set_local $cur (get_local $str)) + (loop $count + (if (i32.ne (i32.const 0) (i32.load8_u (get_local $cur))) + (then + (set_local $cur (i32.add (get_local $cur) (i32.const 1))) + (br $count))) + ) + (i32.sub_u (get_local $cur) (get_local $str)) + ) + + (func $ATOI (param $str i32) (result i32) + (local $acc i32) + (local $i i32) + (local $neg i32) + (local $ch i32) + (set_local $acc (i32.const 0)) + (set_local $i (i32.const 0)) + (set_local $neg (i32.const 0)) + (block $done + (loop $loop + (set_local $ch (i32.load8_u (i32.add (get_local $str) + (get_local $i)))) + (if (i32.and (i32.ne (get_local $ch) (CHAR '-')) + (i32.or (i32.lt_u (get_local $ch) (CHAR '0')) + (i32.gt_u (get_local $ch) (CHAR '9')))) + (br $done)) + (set_local $i (i32.add (get_local $i) (i32.const 1))) + (if (i32.eq (get_local $ch) (CHAR '-')) + (then + (set_local $neg (i32.const 1))) + (else + (set_local $acc (i32.add (i32.mul_u (get_local $acc) (i32.const 10)) + (i32.sub_u (get_local $ch) (CHAR '0')))))) + (br $loop) + ) + ) + (if i32 (get_local $neg) + (then (i32.sub_s (i32.const 0) (get_local $acc))) + (else (get_local $acc))) + ) + + (func $strcmp (param $s1 i32) (param $s2 i32) (result i32) + (block $done + (loop $loop + (if (i32.or (i32.eqz (i32.load8_u (get_local $s1))) + (i32.eqz (i32.load8_u (get_local $s2)))) + (br $done)) + (if (i32.ne (i32.load8_u (get_local $s1)) + (i32.load8_u (get_local $s2))) + (br $done)) + (set_local $s1 (i32.add (get_local $s1) (i32.const 1))) + (set_local $s2 (i32.add (get_local $s2) (i32.const 1))) + (br $loop) + ) + ) + (if i32 (i32.eq (i32.load8_u (get_local $s1)) + (i32.load8_u (get_local $s2))) + (then (i32.const 0)) + (else + (if i32 (i32.lt_u (i32.load8_u (get_local $s1)) + (i32.load8_u (get_local $s2))) + (then (i32.const -1)) + (else (i32.const 1))))) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $print (param $addr i32) + (drop (call $fputs (get_local $addr) (get_global $stdout)))) + + (func $printf_1 (param $fmt i32) + (param $v0 i32) + (drop (call $sprintf_6 (get_global $sprintf_buf) (get_local $fmt) + (get_local $v0) (i32.const 0) (i32.const 0) + (i32.const 0) (i32.const 0) (i32.const 0))) + (call $print (get_global $sprintf_buf)) + ) + + (func $printf_2 (param $fmt i32) + (param $v0 i32) (param $v1 i32) + (drop (call $sprintf_6 (get_global $sprintf_buf) (get_local $fmt) + (get_local $v0) (get_local $v1) (i32.const 0) + (i32.const 0) (i32.const 0) (i32.const 0))) + (call $print (get_global $sprintf_buf)) + ) + + (func $printf_3 (param $fmt i32) + (param $v0 i32) (param $v1 i32) (param $v2 i32) + (drop (call $sprintf_6 (get_global $sprintf_buf) (get_local $fmt) + (get_local $v0) (get_local $v1) (get_local $v2) + (i32.const 0) (i32.const 0) (i32.const 0))) + (call $print (get_global $sprintf_buf)) + ) + + (func $printf_4 (param $fmt i32) + (param $v0 i32) (param $v1 i32) (param $v2 i32) + (param $v3 i32) + (drop (call $sprintf_6 (get_global $sprintf_buf) (get_local $fmt) + (get_local $v0) (get_local $v1) (get_local $v2) + (get_local $v3) (i32.const 0) (i32.const 0))) + (call $print (get_global $sprintf_buf)) + ) + + (func $printf_5 (param $fmt i32) + (param $v0 i32) (param $v1 i32) (param $v2 i32) + (param $v3 i32) (param $v4 i32) + (drop (call $sprintf_6 (get_global $sprintf_buf) (get_local $fmt) + (get_local $v0) (get_local $v1) (get_local $v2) + (get_local $v3) (get_local $v4) (i32.const 0))) + (call $print (get_global $sprintf_buf)) + ) + + (func $printf_6 (param $fmt i32) + (param $v0 i32) (param $v1 i32) (param $v2 i32) + (param $v3 i32) (param $v4 i32) (param $v5 i32) + (drop (call $sprintf_6 (get_global $sprintf_buf) (get_local $fmt) + (get_local $v0) (get_local $v1) (get_local $v2) + (get_local $v3) (get_local $v4) (get_local $v5))) + (call $print (get_global $sprintf_buf)) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $_sprintdigit (param $str i32) (param $num i32) (param $base i32) + (local $n i32) + (local $ch i32) + (set_local $n (i32.rem_u (get_local $num) (get_local $base))) + (set_local $ch (if (result i32) (i32.lt_u (get_local $n) (i32.const 10)) + (i32.const 48) + (i32.const 55))) + (i32.store8_u (get_local $str) (i32.add (get_local $n) (get_local $ch))) + ) + + ;; TODO: switch to snprint* (add buffer len) + (func $_sprintnum (param $str i32) (param $num i32) (param $base i32) + (result i32) + (if (i32.and (i32.eq (get_local $base) (i32.const 10)) + (i32.lt_s (get_local $num) (i32.const 0))) + (then + ;; Print '-' if negative + (i32.store8_u (get_local $str) (CHAR '-')) + (set_local $str (i32.add (get_local $str) (i32.const 1))) + ;; Reverse the sign + (set_local $num (i32.sub_s (i32.const 0) (get_local $num))))) + (if (i32.gt_u (i32.div_u (get_local $num) (get_local $base)) + (i32.const 0)) + (set_local + $str + (call $_sprintnum (get_local $str) + (i32.div_u (get_local $num) (get_local $base)) + (get_local $base)))) + (call $_sprintdigit (get_local $str) (get_local $num) (get_local $base)) + (i32.add (i32.const 1) (get_local $str)) + ) + + ;; TODO: switch to snprint* (add buffer len) + (func $sprintf_1 (param $str i32) (param $fmt i32) + (param $v0 i32) (result i32) + (call $sprintf_6 (get_local $str) (get_local $fmt) + (get_local $v0) (i32.const 0) (i32.const 0) + (i32.const 0) (i32.const 0) (i32.const 0)) + ) + + (func $sprintf_6 (param $str i32) (param $fmt i32) + (param $v0 i32) (param $v1 i32) (param $v2 i32) + (param $v3 i32) (param $v4 i32) (param $v5 i32) + (result i32) + (local $ch i32) + (local $pstr i32) + (local $v i32) + (local $vidx i32) + (local $len i32) + (set_local $pstr (get_local $str)) + (set_local $vidx (i32.const 0)) + + (block $done + (loop $loop + (block $after_v + (block (block (block (block (block (block + (br_table 0 1 2 3 4 5 0 (get_local $vidx))) + (; 0 ;) (set_local $v (get_local $v0)) (br $after_v)) + (; 1 ;) (set_local $v (get_local $v1)) (br $after_v)) + (; 2 ;) (set_local $v (get_local $v2)) (br $after_v)) + (; 3 ;) (set_local $v (get_local $v3)) (br $after_v)) + (; 4 ;) (set_local $v (get_local $v4)) (br $after_v)) + (; 5 ;) (set_local $v (get_local $v5)) (br $after_v) + ) + + ;;; while ((ch=*(fmt++))) + (set_local $ch (i32.load8_u (get_local $fmt))) + (set_local $fmt (i32.add (i32.const 1) (get_local $fmt))) + (if (i32.eqz (get_local $ch)) (br $done)) + ;; TODO: check buffer length + + (if (i32.ne (get_local $ch) (CHAR '%')) + (then + ;; TODO: check buffer length + (i32.store8_u (get_local $pstr) (get_local $ch)) + (set_local $pstr (i32.add (i32.const 1) (get_local $pstr))) + (br $loop))) + + ;;; ch=*(fmt++) + (set_local $ch (i32.load8_u (get_local $fmt))) + (set_local $fmt (i32.add (i32.const 1) (get_local $fmt))) + + (if (i32.eq (CHAR 'd') (get_local $ch)) + (then + (set_local $pstr (call $_sprintnum (get_local $pstr) + (get_local $v) (i32.const 10)))) + (else (if (i32.eq (CHAR 'x') (get_local $ch)) + (then + (set_local $pstr (call $_sprintnum (get_local $pstr) + (get_local $v) (i32.const 16)))) + (else (if (i32.eq (CHAR 's') (get_local $ch)) + (then + (set_local $len (call $STRING_LEN (get_local $v))) + (call $MEM_COPY (get_local $pstr) (get_local $v) (get_local $len)) + (set_local $pstr (i32.add (get_local $pstr) (get_local $len)))) + (else (if (i32.eq (CHAR 'c') (get_local $ch)) + (then + (i32.store8_u (get_local $pstr) (get_local $v)) + (set_local $pstr (i32.add (get_local $pstr) (i32.const 1)))) + (else + (call $print (STRING "Illegal format character: ")) + (drop (call $putchar (get_local $ch))) + (drop (call $putchar (CHAR '\n'))) + (call $exit (i32.const 3)))))))))) + + (set_local $vidx (i32.add (i32.const 1) (get_local $vidx))) + (br $loop) + ) + ) + + (i32.store8_u (get_local $pstr) (CHAR '\x00')) + (get_local $pstr) + ) + +) + diff --git a/wasm/wastpp.py b/wasm/wastpp.py new file mode 100755 index 0000000000..22266dfcf9 --- /dev/null +++ b/wasm/wastpp.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 + +from itertools import tee +from ast import literal_eval +import os +import pprint +import re +import sys + +def pairwise(iterable): + "s -> (s0,s1), (s1,s2), (s2, s3), ..." + a, b = tee(iterable) + next(b, None) + return zip(a, b) + +def _escape(s): + return s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n') + + +tokre = re.compile(r"""([\s][\s]*|[(];|;[)]|[\[\]{}()`~^@]|'(?:[\\].|[^\\'])*'?|"(?:[\\].|[^\\"])*"?|;;.*|[^\s\[\]{}()'"`@,;]+)""") + +file_tokens = [] +strings = [] +string_map = {} + +depth = 0 +module = None +type = None + +for f in sys.argv[1:]: + content = open(f).read() + tokens = [t for t in re.findall(tokre, content)] + #print(tokens[0:100], file=sys.stderr) + pairs = ["".join(p) for p in pairwise(tokens)] + pairs.append("") + + index = 0 + while index < len(tokens): + token = tokens[index] + pair = pairs[index] + if pair in ["(STRING", "(CHAR"]: + arg = tokens[index+3] + #print("arg: %s" % arg, file=sys.stderr) + if tokens[index+4] != ')': + raise Exception("Invalid %s) macro, too many/few args" % pair) + if arg.startswith('"') and arg.endswith('"'): + pass + elif arg.startswith("'") and arg.endswith("'"): + pass + else: + raise Exception ("Invalid %s) macro, invalid string arg" % pair) + if pair == "(STRING": + str = literal_eval(arg) + if str in string_map: + # Duplicate string, re-use address + file_tokens.append("(i32.add (get_global $memoryBase) (get_global %s))" % string_map[str]) + else: + str_name = "$S_STRING_%d" % len(strings) + file_tokens.append("(i32.add (get_global $memoryBase) (get_global %s))" % str_name) + strings.append(str) + string_map[str] = str_name + if pair == "(CHAR": + c = literal_eval(arg) + if len(c) != 1: + raise Exception ("Invalid (CHAR) macro, must be 1 character") + file_tokens.append("(i32.const 0x%x (; %s ;))" % (ord(c), arg)) + # Skip the rest of the macro + index += 5 + continue + index += 1 + if token == '(': + depth += 1 + if token == ')': + depth -= 1 + if depth == 0: + module = None + if token == ')': continue + if depth == 1: + type = None + if pair == '(module': + index += 1 + continue + if token.startswith('$'): + module = token[1:] + #print("module:", module, file=sys.stderr) + file_tokens.append('\n ;;\n ;; module "%s"\n ;;\n' % module) + continue + if depth == 2: + if token == '(': + type = tokens[index] + if type == 'data': + raise Exception("User data section not supported") + #print(" type:", type, file=sys.stderr) + file_tokens.append(token) + +# TODO: remove duplicates +# Create data section with static strings +string_tokens = [] +if strings: + string_tokens.append(" (data\n (get_global $memoryBase)\n") + string_offset = 0 + for string in strings: + string_tokens.append(' %-30s ;; %d\n' % ( + '"'+_escape(string)+'\\00"', string_offset)) + string_offset += len(string)+1 + string_tokens.append(" )\n\n") + + # Create string names/pointers + string_offset = 0 + for index, string in enumerate(strings): + string_tokens.append(' (global $S_STRING_%d i32 (i32.const %d))\n' % ( + index, string_offset)) + string_offset += len(string)+1 + # Terminator so we know how much memory we took + string_tokens.append(' (global $S_STRING_END i32 (i32.const %d))\n' % ( + string_offset)) + +all_tokens = ["(module\n"] +all_tokens.extend(string_tokens) +all_tokens.extend(file_tokens) +all_tokens.append("\n)") + +print("".join(all_tokens)) From 33309c6a6246ea0d5699b4eb0d90a2fd0c67ec8a Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 13 Oct 2018 23:10:28 -0500 Subject: [PATCH 0713/2308] wasm: Convert to wam syntax using wamp. - Convert sources to much more concise wam syntax supported by wamp. - Rename sources from *.wast to *.wam and generate *.wast files by translating via wamp. --- .gitignore | 2 +- wasm/Makefile | 20 +- wasm/debug.wam | 186 +++++++++ wasm/debug.wast | 194 ---------- wasm/env.wam | 79 ++++ wasm/env.wast | 90 ----- wasm/mem.wam | 401 ++++++++++++++++++++ wasm/mem.wast | 461 ----------------------- wasm/printer.wam | 139 +++++++ wasm/printer.wast | 148 -------- wasm/reader.wam | 287 ++++++++++++++ wasm/reader.wast | 321 ---------------- wasm/{step0_repl.wast => step0_repl.wam} | 32 +- wasm/step1_read_print.wam | 88 +++++ wasm/step1_read_print.wast | 81 ---- wasm/step2_eval.wam | 255 +++++++++++++ wasm/step2_eval.wast | 269 ------------- wasm/step3_env.wam | 308 +++++++++++++++ wasm/step3_env.wast | 329 ---------------- wasm/types.wam | 186 +++++++++ wasm/types.wast | 202 ---------- wasm/util.wam | 254 +++++++++++++ wasm/util.wast | 275 -------------- wasm/wastpp.py | 123 ------ 24 files changed, 2212 insertions(+), 2518 deletions(-) create mode 100644 wasm/debug.wam delete mode 100644 wasm/debug.wast create mode 100644 wasm/env.wam delete mode 100644 wasm/env.wast create mode 100644 wasm/mem.wam delete mode 100644 wasm/mem.wast create mode 100644 wasm/printer.wam delete mode 100644 wasm/printer.wast create mode 100644 wasm/reader.wam delete mode 100644 wasm/reader.wast rename wasm/{step0_repl.wast => step0_repl.wam} (52%) create mode 100644 wasm/step1_read_print.wam delete mode 100644 wasm/step1_read_print.wast create mode 100644 wasm/step2_eval.wam delete mode 100644 wasm/step2_eval.wast create mode 100644 wasm/step3_env.wam delete mode 100644 wasm/step3_env.wast create mode 100644 wasm/types.wam delete mode 100644 wasm/types.wast create mode 100644 wasm/util.wam delete mode 100644 wasm/util.wast delete mode 100755 wasm/wastpp.py diff --git a/.gitignore b/.gitignore index d5c41ff45f..aa4aa493c8 100644 --- a/.gitignore +++ b/.gitignore @@ -144,5 +144,5 @@ elm/elm-stuff elm/*.js !elm/node_readline.js !elm/bootstrap.js -wasm/*.wat +wasm/*.wast wasm/*.wasm diff --git a/wasm/Makefile b/wasm/Makefile index 46650e42fb..aeac222726 100644 --- a/wasm/Makefile +++ b/wasm/Makefile @@ -1,7 +1,7 @@ -STEP0_DEPS = util.wast -STEP1_DEPS = $(STEP0_DEPS) types.wast mem.wast debug.wast reader.wast printer.wast +STEP0_DEPS = util.wam +STEP1_DEPS = $(STEP0_DEPS) types.wam mem.wam debug.wam reader.wam printer.wam STEP2_DEPS = $(STEP1_DEPS) -STEP3_DEPS = $(STEP2_DEPS) env.wast +STEP3_DEPS = $(STEP2_DEPS) env.wam STEPS = step0_repl step1_read_print step2_eval step3_env \ step4_if_fn_do step5_tco step6_file step7_quote \ @@ -10,15 +10,15 @@ STEPS = step0_repl step1_read_print step2_eval step3_env \ all: $(foreach s,$(STEPS),$(s).wasm) %.wasm: - ./wastpp.py $^ > $*.wat - wasm-as $*.wat -o $@ + wamp $^ > $*.wast + wasm-as $*.wast -o $@ -step0_repl.wasm: $(STEP0_DEPS) step0_repl.wast -step1_read_print.wasm: $(STEP1_DEPS) step1_read_print.wast -step2_eval.wasm: $(STEP2_DEPS) step2_eval.wast -step3_env.wasm: $(STEP3_DEPS) step3_env.wast +step0_repl.wasm: $(STEP0_DEPS) step0_repl.wam +step1_read_print.wasm: $(STEP1_DEPS) step1_read_print.wam +step2_eval.wasm: $(STEP2_DEPS) step2_eval.wam +step3_env.wasm: $(STEP3_DEPS) step3_env.wam .PHONY: clean clean: - rm -f *.wat *.wasm + rm -f *.wast *.wasm diff --git a/wasm/debug.wam b/wasm/debug.wam new file mode 100644 index 0000000000..7eb0795461 --- /dev/null +++ b/wasm/debug.wam @@ -0,0 +1,186 @@ +(module $debug + + (func $PR_VALUE (param $fmt i32) (param $mv i32) + (local $temp i32) + (set_local $temp ($pr_str $mv)) + ($printf_1 $fmt ($to_String $temp)) + ($RELEASE $temp) + ) + + (func $PR_MEMORY_VALUE (param $idx i32) (result i32) + (local $mv i32) + (local $type i32) + (local $size i32) + (local $val0 i32) + ;;; mv = mem + idx + (set_local $mv ($MalVal_ptr $idx)) + (set_local $type ($TYPE $mv)) + (set_local $size ($MalVal_size $mv)) + (set_local $val0 ($MalVal_val $idx 0)) + + ;;; printf(" %3d: type: %2d", idx, type) + ($printf_2 " 0x%x: type: %d" $idx $type) + + (if (i32.eq $type 15) + (then + ;;; printf(", size: %2d", size) + ($printf_1 ", size: %d" $size)) + (else + ;;; printf(", refs: %2d", (mv->refcnt_type - type)>>5) + ($printf_1 ", refs: %d" ($REFS $mv)))) + + ;;; printf(", [ %3d | %3d", mv->refcnt_type, val0) + ($printf_2 ", [ 0x%x | 0x%x" ($MalVal_refcnt_type $idx) $val0) + + (if (i32.eq $size 2) + (then + ($print " | --- | --- ]")) + (else + ;;; printf(" | %3d", mv->val[1]) + ($printf_1 " | 0x%x" ($MalVal_val $idx 1)) + (if (i32.eq $size 3) + (then + ($print " | --- ]")) + (else + ;;; printf(" | %3d ]", mv->val[2]) + ($printf_1 " | 0x%x ]" ($MalVal_val $idx 2)))))) + + ;;; printf(" >> ") + ($print " >> ") + + (block $done (block $unknown + (block (block (block (block (block (block (block (block + (block (block (block (block (block (block (block (block + (br_table 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + $unknown $type)) + ;; 0: nil + ($print "nil") + (br $done)) + ;; 1: boolean + (if (i32.eq $val0 0) + ;; true + ($print "false") + ;; false + ($print "true")) + (br $done)) + ;; 2: integer + ($printf_1 "%d" $val0) + (br $done)) + ;; 3: float/ERROR + ($print " *** GOT FLOAT *** ") + (br $done)) + ;; 4: string/kw + ($printf_1 "'%s'" ($to_String $mv)) + (br $done)) + ;; 5: symbol + ($print ($to_String $mv)) + (br $done)) + ;; 6: list + (if (i32.le_u $mv (get_global $EMPTY_HASHMAP)) + (then + ($print "()")) + (else + ;;; printf("(... %d ...), next: %d\n", mv->val[1], mv->val[0]) + ($printf_2 "(... 0x%x ...), next: 0x%x" + ($MalVal_val $idx 1) + ($MalVal_val $idx 0)))) + (br $done)) + ;; 7: vector + (if (i32.le_u $mv (get_global $EMPTY_HASHMAP)) + (then + ($print "[]")) + (else + ;;; printf("[... %d ...], next: %d\n", mv->val[1], mv->val[0])val + ($printf_2 "[... %d ...], next: %d" + ($MalVal_val $idx 1) + ($MalVal_val $idx 0)))) + (br $done)) + ;; 8: hashmap + (if (i32.le_u $mv (get_global $EMPTY_HASHMAP)) + (then + ($print "{}")) + (else + ;;; printf("{... '%s'(%d) : %d ...}\n", + ;; to_String(mem + mv->val[1]), mv->val[1], mv->val[2]) + ($printf_3 "{... '%s'(%d) : %d ...}" + ($to_String ($MalVal_ptr ($MalVal_val $idx 1))) + ($MalVal_val $idx 1) + ($MalVal_val $idx 2)))) + (br $done)) + ;; 9: function + ($print "function") + (br $done)) + ;; 10: mal function + ($print "mal function") + (br $done)) + ;; 11: macro fn + ($print "macro fn") + (br $done)) + ;; 12: atom + ($print "atom") + (br $done)) + ;; 13: environment + ($print "environment") + (br $done)) + ;; 14: metadata + ($print "metadata") + (br $done)) + ;; 15: FREE + ($printf_1 "FREE next: 0x%x" $val0) + (if (i32.eq $idx (get_global $mem_free_list)) + ($print " (free start)")) + (if (i32.eq $val0 (get_global $mem_unused_start)) + ($print " (free end)")) + (br $done)) + ;; 16: unknown + ($print "unknown") + ) + + (drop ($putchar 0xA)) + + (i32.add $size $idx) + ) + + (func $PR_MEMORY (param $start i32) (param $end i32) + (local $idx i32) + (if (i32.lt_s $start 0) + (set_local $start (get_global $mem_user_start))) + (if (i32.lt_s $end 0) + (set_local $end (get_global $mem_unused_start))) + ;;; printf("Values - (mem) showing %d -> %d", start, end) + ;;; printf(" (unused start: %d, free list: %d):\n", + ;;; mem_unused_start, mem_free_list) + ($printf_4 "Values - (mem) showing 0x%x -> 0x%x (unused start: 0x%x, free list: 0x%x):\n" + $start + $end + (get_global $mem_unused_start) + (get_global $mem_free_list)) + + (if (i32.le_s $end $start) + (then + ($print " ---\n") + (set_local $end (get_global $mem_unused_start))) + (else + (set_local $idx $start) + ;;; while (idx < end) + (block $loopvals_exit + (loop $loopvals + (if (i32.ge_s $idx $end) + (br $loopvals_exit)) + (set_local $idx ($PR_MEMORY_VALUE $idx)) + (br $loopvals) + ) + ))) + ) + + (func $PR_MEMORY_RAW (param $start i32) (param $end i32) + (block $loop_exit + (loop $loop + (if (i32.ge_u $start $end) (br $loop_exit)) + ($printf_2 "0x%x 0x%x\n" $start (i32.load $start)) + (set_local $start (i32.add 4 $start)) + (br $loop) + ) + ) + ) +) diff --git a/wasm/debug.wast b/wasm/debug.wast deleted file mode 100644 index 18e5fa1c35..0000000000 --- a/wasm/debug.wast +++ /dev/null @@ -1,194 +0,0 @@ -(module $debug - - (func $PR_VALUE (param $fmt i32) (param $mv i32) - (local $temp i32) - (set_local $temp (call $pr_str (get_local $mv))) - (call $printf_1 (get_local $fmt) (call $to_String (get_local $temp))) - (call $RELEASE (get_local $temp)) - ) - - (func $PR_MEMORY_VALUE (param $idx i32) (result i32) - (local $mv i32) - (local $type i32) - (local $size i32) - (local $val0 i32) - ;;; mv = mem + idx - (set_local $mv (call $MalVal_ptr (get_local $idx))) - (set_local $type (call $TYPE (get_local $mv))) - (set_local $size (call $MalVal_size (get_local $mv))) - (set_local $val0 (call $MalVal_val (get_local $idx) (i32.const 0))) - - ;;; printf(" %3d: type: %2d", idx, type) - (call $printf_2 (STRING " 0x%x: type: %d") - (get_local $idx) (get_local $type)) - - (if (i32.eq (get_local $type) (i32.const 15)) - (then - ;;; printf(", size: %2d", size) - (call $printf_1 (STRING ", size: %d") (get_local $size))) - (else - ;;; printf(", refs: %2d", (mv->refcnt_type - type)>>5) - (call $printf_1 (STRING ", refs: %d") (call $REFS (get_local $mv))))) - - ;;; printf(", [ %3d | %3d", mv->refcnt_type, val0) - (call $printf_2 (STRING ", [ 0x%x | 0x%x") - (call $MalVal_refcnt_type (get_local $idx)) - (get_local $val0)) - - (if (i32.eq (get_local $size) (i32.const 2)) - (then - (call $print (STRING " | --- | --- ]"))) - (else - ;;; printf(" | %3d", mv->val[1]) - (call $printf_1 (STRING " | 0x%x") - (call $MalVal_val (get_local $idx) (i32.const 1))) - (if (i32.eq (get_local $size) (i32.const 3)) - (then - (call $print (STRING " | --- ]"))) - (else - ;;; printf(" | %3d ]", mv->val[2]) - (call $printf_1 (STRING " | 0x%x ]") - (call $MalVal_val (get_local $idx) (i32.const 2))))))) - - ;;; printf(" >> ") - (call $print (STRING " >> ")) - - (block $done (block $unknown - (block (block (block (block (block (block (block (block - (block (block (block (block (block (block (block (block - (br_table 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $unknown (get_local $type))) - ;; 0: nil - (call $print (STRING "nil")) - (br $done)) - ;; 1: boolean - (if (i32.eq (get_local $val0) (i32.const 0)) - ;; true - (call $print (STRING "false")) - ;; false - (call $print (STRING "true"))) - (br $done)) - ;; 2: integer - (call $printf_1 (STRING "%d") (get_local $val0)) - (br $done)) - ;; 3: float/ERROR - (call $print (STRING " *** GOT FLOAT *** ")) - (br $done)) - ;; 4: string/kw - (call $printf_1 (STRING "'%s'") (call $to_String (get_local $mv))) - (br $done)) - ;; 5: symbol - (call $print (call $to_String (get_local $mv))) - (br $done)) - ;; 6: list - (if (i32.le_u (get_local $mv) (get_global $EMPTY_HASHMAP)) - (then - (call $print (STRING "()"))) - (else - ;;; printf("(... %d ...), next: %d\n", mv->val[1], mv->val[0]) - (call $printf_2 (STRING "(... 0x%x ...), next: 0x%x") - (call $MalVal_val (get_local $idx) (i32.const 1)) - (call $MalVal_val (get_local $idx) (i32.const 0))))) - (br $done)) - ;; 7: vector - (if (i32.le_u (get_local $mv) (get_global $EMPTY_HASHMAP)) - (then - (call $print (STRING "[]"))) - (else - ;;; printf("[... %d ...], next: %d\n", mv->val[1], mv->val[0])val - (call $printf_2 (STRING "[... %d ...], next: %d") - (call $MalVal_val (get_local $idx) (i32.const 1)) - (call $MalVal_val (get_local $idx) (i32.const 0))))) - (br $done)) - ;; 8: hashmap - (if (i32.le_u (get_local $mv) (get_global $EMPTY_HASHMAP)) - (then - (call $print (STRING "{}"))) - (else - ;;; printf("{... '%s'(%d) : %d ...}\n", - ;; to_String(mem + mv->val[1]), mv->val[1], mv->val[2]) - (call $printf_3 (STRING "{... '%s'(%d) : %d ...}") - (call $to_String - (call $MalVal_ptr - (call $MalVal_val (get_local $idx) (i32.const 1)))) - (call $MalVal_val (get_local $idx) (i32.const 1)) - (call $MalVal_val (get_local $idx) (i32.const 2))))) - (br $done)) - ;; 9: function - (call $print (STRING "function")) - (br $done)) - ;; 10: mal function - (call $print (STRING "mal function")) - (br $done)) - ;; 11: macro fn - (call $print (STRING "macro fn")) - (br $done)) - ;; 12: atom - (call $print (STRING "atom")) - (br $done)) - ;; 13: environment - (call $print (STRING "environment")) - (br $done)) - ;; 14: metadata - (call $print (STRING "metadata")) - (br $done)) - ;; 15: FREE - (call $printf_1 (STRING "FREE next: 0x%x") (get_local $val0)) - (if (i32.eq (get_local $idx) (get_global $mem_free_list)) - (call $print (STRING " (free start)"))) - (if (i32.eq (get_local $val0) (get_global $mem_unused_start)) - (call $print (STRING " (free end)"))) - (br $done)) - ;; 16: unknown - (call $print (STRING "unknown")) - ) - - (drop (call $putchar (i32.const 0xA))) - - (i32.add (get_local $size) (get_local $idx)) - ) - - (func $PR_MEMORY (param $start i32) (param $end i32) - (local $idx i32) - (if (i32.lt_s (get_local $start) (i32.const 0)) - (set_local $start (get_global $mem_user_start))) - (if (i32.lt_s (get_local $end) (i32.const 0)) - (set_local $end (get_global $mem_unused_start))) - ;;; printf("Values - (mem) showing %d -> %d", start, end) - ;;; printf(" (unused start: %d, free list: %d):\n", - ;;; mem_unused_start, mem_free_list) - (call $printf_4 (STRING "Values - (mem) showing 0x%x -> 0x%x (unused start: 0x%x, free list: 0x%x):\n") - (get_local $start) - (get_local $end) - (get_global $mem_unused_start) - (get_global $mem_free_list)) - - (if (i32.le_s (get_local $end) (get_local $start)) - (then - (call $print (STRING " ---\n")) - (set_local $end (get_global $mem_unused_start))) - (else - (set_local $idx (get_local $start)) - ;;; while (idx < end) - (block $loopvals_exit - (loop $loopvals - (if (i32.ge_s (get_local $idx) (get_local $end)) - (br $loopvals_exit)) - (set_local $idx (call $PR_MEMORY_VALUE (get_local $idx))) - (br $loopvals) - ) - ))) - ) - - (func $PR_MEMORY_RAW (param $start i32) (param $end i32) - (block $loop_exit - (loop $loop - (if (i32.ge_u (get_local $start) (get_local $end)) - (br $loop_exit)) - (call $printf_2 (STRING "0x%x 0x%x\n") - (get_local $start) (i32.load (get_local $start))) - (set_local $start (i32.add (i32.const 4) (get_local $start))) - (br $loop) - ) - ) - ) -) diff --git a/wasm/env.wam b/wasm/env.wam new file mode 100644 index 0000000000..88945ff34a --- /dev/null +++ b/wasm/env.wam @@ -0,0 +1,79 @@ +(module $env + + (func $ENV_NEW (param $outer i32) (result i32) + (local $data i32) + (local $env i32) + + ;; allocate the data hashmap + (set_local $data ($HASHMAP)) + + (set_local $env ($ALLOC (get_global $ENVIRONMENT_T) $data $outer 0)) + ;; environment takes ownership + ($RELEASE $data) + $env + ) + + (func $ENV_SET (param $env i32) (param $key i32) (param $value i32) + (result i32) + (local $data i32) + (set_local $data ($MEM_VAL0_ptr $env)) + (i32.store ($VAL0_ptr $env) ($MalVal_index ($ASSOC1 $data $key $value))) + $value + ) + + (func $ENV_SET_S (param $env i32) (param $key i32) (param $value i32) + (result i32) + (local $data i32) + (set_local $data ($MEM_VAL0_ptr $env)) + (i32.store ($VAL0_ptr $env) ($MalVal_index ($ASSOC1_S $data $key $value))) + $value + ) + + (func $ENV_FIND (param $env i32) (param $key i32) (result i64) + (local $res i32) + (local $data i32) + (local $found_res i64) + + (set_local $res 0) + + (block $done + (loop $loop + (set_local $data ($MEM_VAL0_ptr $env)) + (set_local $found_res ($HASHMAP_GET $data + $key)) + ;;; if (found) + (if (i32.wrap/i64 (i64.shr_u $found_res (i64.const 32))) + (then + (set_local $res (i32.wrap/i64 $found_res)) + (br $done))) + (set_local $env ($MEM_VAL1_ptr $env)) + (if (i32.eq $env (get_global $NIL)) + (then + (set_local $env 0) + (br $done))) + (br $loop) + ) + ) + + ;; combine res/env as hi 32/low 32 of i64 + (i64.or + (i64.shl_u (i64.extend_u/i32 $res) (i64.const 32)) + (i64.extend_u/i32 $env)) + ) + + (func $ENV_GET (param $env i32) (param $key i32) (result i32) + (local $res i32) + (local $res_env i64) + (set_local $res 0) + + (set_local $res_env ($ENV_FIND $env $key)) + (set_local $env (i32.wrap/i64 $res_env)) + (set_local $res (i32.wrap/i64 (i64.shr_u $res_env (i64.const 32)))) + + (if (i32.eqz $env) + (then + ($THROW_STR_1 "'%s' not found" ($to_String $key)) + (return $res))) + (return ($INC_REF $res)) + ) +) diff --git a/wasm/env.wast b/wasm/env.wast deleted file mode 100644 index 9e36586062..0000000000 --- a/wasm/env.wast +++ /dev/null @@ -1,90 +0,0 @@ -(module $env - - (func $ENV_NEW (param $outer i32) (result i32) - (local $data i32) - (local $env i32) - - ;; allocate the data hashmap - (set_local $data (call $HASHMAP)) - - (set_local $env (call $ALLOC (get_global $ENVIRONMENT_T) - (get_local $data) (get_local $outer) (i32.const 0))) - ;; environment takes ownership - (call $RELEASE (get_local $data)) - (get_local $env) - ) - - (func $ENV_SET (param $env i32) (param $key i32) (param $value i32) - (result i32) - (local $data i32) - (set_local $data (call $MEM_VAL0_ptr (get_local $env))) - (i32.store (call $VAL0_ptr (get_local $env)) - (call $MalVal_index - (call $ASSOC1 (get_local $data) - (get_local $key) (get_local $value)))) - (get_local $value) - ) - - (func $ENV_SET_S (param $env i32) (param $key i32) (param $value i32) - (result i32) - (local $data i32) - (set_local $data (call $MEM_VAL0_ptr (get_local $env))) - (i32.store (call $VAL0_ptr (get_local $env)) - (call $MalVal_index - (call $ASSOC1_S (get_local $data) - (get_local $key) (get_local $value)))) - (get_local $value) - ) - - (func $ENV_FIND (param $env i32) (param $key i32) (result i64) - (local $res i32) - (local $data i32) - (local $found_res i64) - - (set_local $res (i32.const 0)) - - (block $done - (loop $loop - (set_local $data (call $MEM_VAL0_ptr (get_local $env))) - (set_local $found_res (call $HASHMAP_GET (get_local $data) - (get_local $key))) - ;;; if (found) - (if (i32.wrap/i64 (i64.shr_u (get_local $found_res) - (i64.const 32))) - (then - (set_local $res (i32.wrap/i64 (get_local $found_res))) - (br $done))) - (set_local $env (call $MEM_VAL1_ptr (get_local $env))) - (if (i32.eq (get_local $env) (get_global $NIL)) - (then - (set_local $env (i32.const 0)) - (br $done))) - (br $loop) - ) - ) - - ;; combine res/env as hi 32/low 32 of i64 - (i64.or - (i64.shl_u (i64.extend_u/i32 (get_local $res)) - (i64.const 32)) - (i64.extend_u/i32 (get_local $env))) - ) - - (func $ENV_GET (param $env i32) (param $key i32) (result i32) - (local $res i32) - (local $res_env i64) - (set_local $res (i32.const 0)) - - (set_local $res_env (call $ENV_FIND (get_local $env) (get_local $key))) - (set_local $env (i32.wrap/i64 (get_local $res_env))) - (set_local $res (i32.wrap/i64 (i64.shr_u (get_local $res_env) - (i64.const 32)))) - - (if (i32.eqz (get_local $env)) - (then - (call $THROW_STR_1 (STRING "'%s' not found") - (call $to_String (get_local $key))) - (return (get_local $res)))) - (return (call $INC_REF (get_local $res))) - ) -) diff --git a/wasm/mem.wam b/wasm/mem.wam new file mode 100644 index 0000000000..1134ae1ccc --- /dev/null +++ b/wasm/mem.wam @@ -0,0 +1,401 @@ +(module $mem + (global $MEM_SIZE i32 1048576) + (global $STRING_MEM_SIZE i32 1048576) + + (global $heap_start (mut i32) 0) + (global $heap_end (mut i32) 0) + + (global $mem (mut i32) 0) + (global $mem_unused_start (mut i32) 0) + (global $mem_free_list (mut i32) 0) + (global $mem_user_start (mut i32) 0) + +;; (global $string_mem (mut i32) 0) +;; (global $string_mem_next (mut i32) 0) +;; (global $string_mem_user_start (mut i32) 0) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; General type storage/pointer functions + + (func $VAL0_ptr (param $mv i32) (result i32) + (i32.add $mv 4)) + (func $VAL1_ptr (param $mv i32) (result i32) + (i32.add $mv 8)) + + (func $VAL0 (param $mv i32) (result i32) + (i32.load (i32.add $mv 4))) + (func $VAL1 (param $mv i32) (result i32) + (i32.load (i32.add $mv 8))) + + + (func $MEM_VAL0_ptr (param $mv i32) (result i32) + (i32.add (get_global $mem) + (i32.mul_u (i32.load (i32.add $mv 4)) 8))) + (func $MEM_VAL1_ptr (param $mv i32) (result i32) + (i32.add (get_global $mem) + (i32.mul_u (i32.load (i32.add $mv 8)) 8))) + (func $MEM_VAL2_ptr (param $mv i32) (result i32) + (i32.add (get_global $mem) + (i32.mul_u (i32.load (i32.add $mv 12)) 8))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ;; Returns the address of 'mem[mv_idx]' + (func $MalVal_ptr (param $mv_idx i32) (result i32) + ;; MalVal memory 64 bit (2 * i32) aligned + ;;; mem[mv_idx].refcnt_type + (i32.add (get_global $mem) (i32.mul_u $mv_idx 8))) + + ;; Returns the memory index mem of mv + ;; Will usually be used with a load or store by the caller + (func $MalVal_index (param $mv i32) (result i32) + ;; MalVal memory 64 bit (2 * i32) aligned + (i32.div_u (i32.sub_u $mv (get_global $mem)) 8)) + + ;; Returns the address of 'mem[mv_idx].refcnt_type' + (func $MalVal_refcnt_type (param $mv_idx i32) (result i32) + (i32.load ($MalVal_ptr $mv_idx))) + + (func $TYPE (param $mv i32) (result i32) + ;;; type = mv->refcnt_type & 31 + (i32.and (i32.load $mv) 0x1f)) ;; 0x1f == 31 + + (func $REFS (param $mv i32) (result i32) + ;;; type = mv->refcnt_type & 31 + (i32.shr_u (i32.load $mv) 5)) ;; / 32 + + ;; Returns the address of 'mem[mv_idx].val[val]' + ;; Will usually be used with a load or store by the caller + (func $MalVal_val_ptr (param $mv_idx i32) (param $val i32) (result i32) + (i32.add (i32.add ($MalVal_ptr $mv_idx) 4) + (i32.mul_u $val 4))) + + ;; Returns the value of 'mem[mv_idx].val[val]' + (func $MalVal_val (param $mv_idx i32) (param $val i32) (result i32) + (i32.load ($MalVal_val_ptr $mv_idx $val))) + + (func $MalType_size (param $type i32) (result i32) + ;;; if (type <= 5 || type == 9 || type == 12) + (if i32 (i32.or (i32.le_u $type 5) + (i32.or (i32.eq $type 9) + (i32.eq $type 12))) + (then 2) + (else + ;;; else if (type == 8 || type == 10 || type == 11) + (if i32 (i32.or (i32.eq $type 8) + (i32.or (i32.eq $type 10) + (i32.eq $type 11))) + (then 4) + (else 3))))) + + (func $MalVal_size (param $mv i32) (result i32) + (local $type i32) + (set_local $type ($TYPE $mv)) + ;; if (type == FREE_T) + (if i32 (i32.eq $type (get_global $FREE_T)) + (then + ;;; return (mv->refcnt_type & 0xffe0)>>5 + (i32.shr_u (i32.and (i32.load $mv) 0xffe0) 5)) ;;; / 32 + (else + ;;; return MalType_size(type) + ($MalType_size $type)))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; init_memory + + (func $init_memory + (local $heap_size i32) + +;; ($print ">>> init_memory\n") + + ($init_sprintf_mem) + + ;; 100 character error_str static buffer + (set_global $error_str " ") + ;; 256 character token static buffer + (set_global $token " ") + + (set_local $heap_size (i32.add (get_global $MEM_SIZE) + (get_global $STRING_MEM_SIZE))) + (set_global $heap_start (i32.add (get_global $memoryBase) + (get_global $S_STRING_END))) + (set_global $heap_end (i32.add (get_global $heap_start) + $heap_size)) + + (set_global $mem (get_global $heap_start)) + (set_global $mem_unused_start 0) + (set_global $mem_free_list 0) + +;; (set_global $string_mem (i32.add (get_global $heap_start) +;; (get_global $MEM_SIZE))) +;; (set_global $string_mem_next (get_global $string_mem)) + + ;; Empty values + (set_global $NIL + ($ALLOC_SCALAR (get_global $NIL_T) 0)) + (set_global $FALSE + ($ALLOC_SCALAR (get_global $BOOLEAN_T) 0)) + (set_global $TRUE + ($ALLOC_SCALAR (get_global $BOOLEAN_T) 1)) + (set_global $EMPTY_LIST + ($ALLOC (get_global $LIST_T) + (get_global $NIL) (get_global $NIL) (get_global $NIL))) + (set_global $EMPTY_VECTOR + ($ALLOC (get_global $VECTOR_T) + (get_global $NIL) (get_global $NIL) (get_global $NIL))) + (set_global $EMPTY_HASHMAP + ($ALLOC (get_global $HASHMAP_T) + (get_global $NIL) (get_global $NIL) (get_global $NIL))) + +;; ($print "<<< init_memory\n") + + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; memory management + + (func $ALLOC_INTERNAL (param $type i32) (param $val1 i32) + (param $val2 i32) (param $val3 i32) (result i32) + (local $prev i32) + (local $res i32) + (local $size i32) + (set_local $prev (get_global $mem_free_list)) + (set_local $res (get_global $mem_free_list)) + (set_local $size ($MalType_size $type)) + + (block $loop_done + (loop $loop + ;; res == mem_unused_start + (if (i32.eq $res (get_global $mem_unused_start)) + (then + ;; ALLOC_UNUSED + ;;; if (res + size > MEM_SIZE) + (if (i32.gt_u (i32.add $res $size) (get_global $MEM_SIZE)) + (then + ;; Out of memory, exit + ($print "Out of mal memory!\n") + ($exit 1))) + ;;; if (mem_unused_start += size) + (set_global $mem_unused_start + (i32.add (get_global $mem_unused_start) $size)) + ;;; if (prev == res) + (if (i32.eq $prev $res) + (then + (set_global $mem_free_list (get_global $mem_unused_start))) + (else + ;;; mem[prev].val[0] = mem_unused_start + (i32.store + ($MalVal_val_ptr $prev 0) + (get_global $mem_unused_start)))) + (br $loop_done))) + ;; if (MalVal_size(mem+res) == size) + (if (i32.eq ($MalVal_size ($MalVal_ptr $res)) + $size) + (then + ;; ALLOC_MIDDLE + ;;; if (res == mem_free_list) + (if (i32.eq $res (get_global $mem_free_list)) + ;; set free pointer (mem_free_list) to next free + ;;; mem_free_list = mem[res].val[0]; + (set_global $mem_free_list ($MalVal_val $res 0))) + ;; if (res != mem_free_list) + (if (i32.ne $res (get_global $mem_free_list)) + ;; set previous free to next free + ;;; mem[prev].val[0] = mem[res].val[0] + (i32.store ($MalVal_val_ptr $prev 0) ($MalVal_val $res 0))) + (br $loop_done))) + ;;; prev = res + (set_local $prev $res) + ;;; res = mem[res].val[0] + (set_local $res ($MalVal_val $res 0)) + (br $loop) + ) + ) + ;; ALLOC_DONE + ;;; mem[res].refcnt_type = type + 32 + (i32.store ($MalVal_ptr $res) (i32.add $type 32)) + ;; set val to default val1 + ;;; mem[res].val[0] = val1 + (i32.store ($MalVal_val_ptr $res 0) $val1) + ;;; if (type > 5 && type != 9) + (if (i32.and (i32.gt_u $type 5) (i32.ne $type 9)) + (then + ;; inc refcnt of referenced value + ;;; mem[val1].refcnt_type += 32 + (i32.store ($MalVal_ptr $val1) + (i32.add ($MalVal_refcnt_type $val1) 32)))) + ;;; if (size > 2) + (if (i32.gt_u $size 2) + (then + ;; inc refcnt of referenced value + ;;; mem[val2].refcnt_type += 32 + (i32.store ($MalVal_ptr $val2) + (i32.add ($MalVal_refcnt_type $val2) 32)) + ;;; mem[res].val[1] = val2 + (i32.store ($MalVal_val_ptr $res 1) $val2))) + ;;; if (size > 3) + (if (i32.gt_u $size 3) + (then + ;; inc refcnt of referenced value + ;;; mem[val3].refcnt_type += 32 + (i32.store ($MalVal_ptr $val3) + (i32.add ($MalVal_refcnt_type $val3) 32)) + ;;; mem[res].val[2] = val3 + (i32.store ($MalVal_val_ptr $res 2) $val3))) + + ;;; return mem + res + ($MalVal_ptr $res) + ) + + (func $ALLOC_SCALAR (param $type i32) (param $val1 i32) (result i32) + ($ALLOC_INTERNAL $type $val1 0 0) + ) + + (func $ALLOC (param $type i32) (param $val1 i32) + (param $val2 i32) (param $val3 i32) (result i32) + ($ALLOC_INTERNAL $type + ($MalVal_index $val1) + ($MalVal_index $val2) + ($MalVal_index $val3)) + ) + + (func $RELEASE (param $mv i32) + (local $idx i32) + (local $type i32) + (local $size i32) + + ;; Ignore NULLs + ;;; if (mv == NULL) { return; } + (if (i32.eqz $mv) (return)) + ;;; idx = mv - mem + (set_local $idx ($MalVal_index $mv)) + ;;; type = mv->refcnt_type & 31 + (set_local $type (i32.and (i32.load $mv) 0x1f)) ;; 0x1f == 31 + ;;; size = MalType_size(type) + (set_local $size ($MalType_size $type)) + + ;; DEBUG + ;;; printf(">>> RELEASE idx: %d, type: %d, size: %d\n", idx, type, size) + + (if (i32.eq 0 $mv) + (then + ($print "RELEASE of NULL!\n") + ($exit 1))) + + (if (i32.eq (get_global $FREE_T) $type) + (then + ($printf_2 "RELEASE of already free mv: 0x%x, idx: 0x%x\n" $mv $idx) + ($exit 1))) + (if (i32.lt_u ($MalVal_refcnt_type $idx) 15) + (then + ($printf_2 "RELEASE of unowned mv: 0x%x, idx: 0x%x\n" $mv $idx) + ($exit 1))) + + ;; decrease reference count by one + (i32.store ($MalVal_ptr $idx) + (i32.sub_u ($MalVal_refcnt_type $idx) 32)) + + ;; nil, false, true, empty sequences + (if (i32.le_u $mv (get_global $EMPTY_HASHMAP)) + (then + (if (i32.lt_u ($MalVal_refcnt_type $idx) 32) + (then + ($printf_2 "RELEASE of unowned mv: 0x%x, idx: 0x%x\n" $mv $idx) + ($exit 1))) + (return))) + + ;; our reference count is not 0, so don't release + (if (i32.ge_u ($MalVal_refcnt_type $idx) 32) + (return)) + + (block $done + (block (block (block (block (block (block + (br_table 0 0 0 0 1 1 2 2 3 5 5 5 5 4 5 5 5 $type)) + ;; nil, boolean, integer, float + (br $done)) + ;; string, kw, symbol + ;; release string, then FREE reference + ($RELEASE_STRING $mv) + (br $done)) + ;; list, vector + (if (i32.ne ($MalVal_val $idx 0) 0) + (then + ;; release next element and value + ($RELEASE ($MEM_VAL0_ptr $mv)) + ($RELEASE ($MEM_VAL1_ptr $mv)))) + (br $done)) + ;; hashmap + (if (i32.ne ($MalVal_val $idx 0) 0) + (then + ;; release next element, value, and key + ($RELEASE ($MEM_VAL0_ptr $mv)) + ($RELEASE ($MEM_VAL2_ptr $mv)) + ($RELEASE ($MEM_VAL1_ptr $mv)))) + (br $done)) + ;; env + ;; if outer is set then release outer + (if (i32.ne ($MalVal_val $idx 1) 0) + ($RELEASE ($MEM_VAL1_ptr $mv))) + ;; release the hashmap data + ($RELEASE ($MEM_VAL0_ptr $mv)) + (br $done)) + ;; default/unknown + ) + + ;; FREE, free the current element + + ;; set type(FREE/15) and size + ;;; mv->refcnt_type = size*32 + FREE_T + (i32.store $mv (i32.add (i32.mul_u $size 32) (get_global $FREE_T))) + (i32.store ($MalVal_val_ptr $idx 0) (get_global $mem_free_list)) + (set_global $mem_free_list $idx) + (if (i32.ge_u $size 3) (i32.store ($MalVal_val_ptr $idx 1) 0)) + (if (i32.eq $size 4) (i32.store ($MalVal_val_ptr $idx 2) 0)) + ) + + ;; Allocate a string as follows: + ;; refcnt (i32 set to 1), string data, NULL byte + (func $STRING_DUPE (param $str i32) (result i32) + (local $len i32) + (local $cur i32) + (local $new i32) + (local $idx i32) + + ;; Calculate length of string needed + (set_local $len ($STRING_LEN $str)) + + ;; leading i32 refcnt + trailing NULL + (set_local $new ($malloc (i32.add 5 $len))) + + ;; set initial refcnt to 1 + (i32.store $new 1) + ;; skip refcnt + (set_local $cur (i32.add $new 4)) + ;; Set NULL terminator + (i32.store8_u (i32.add $cur $len) 0) + + ;; Copy the characters + ($MEM_COPY $cur $str $len) + $new + ) + + ;; Duplicate regular character array string into a Mal string and + ;; return the MalVal pointer + (func $STRING (param $type i32) (param $str i32) (result i32) + ($ALLOC_SCALAR + $type + ($STRING_DUPE $str)) + ) + + (func $RELEASE_STRING (param $mv i32) + (local $str i32) + (set_local $str ($MalVal_val + ($MalVal_index $mv) + 0)) + + ;; DEBUG +;; ($printf_1 "RELEASE_STRING - calling free on: %d" $str) + + ($free $str) + ) +) diff --git a/wasm/mem.wast b/wasm/mem.wast deleted file mode 100644 index 43464cd0d8..0000000000 --- a/wasm/mem.wast +++ /dev/null @@ -1,461 +0,0 @@ -(module $mem - (global $MEM_SIZE i32 (i32.const 1048576)) - (global $STRING_MEM_SIZE i32 (i32.const 1048576)) - - (global $heap_start (mut i32) (i32.const 0)) - (global $heap_end (mut i32) (i32.const 0)) - - (global $mem (mut i32) (i32.const 0)) - (global $mem_unused_start (mut i32) (i32.const 0)) - (global $mem_free_list (mut i32) (i32.const 0)) - (global $mem_user_start (mut i32) (i32.const 0)) - -;; (global $string_mem (mut i32) (i32.const 0)) -;; (global $string_mem_next (mut i32) (i32.const 0)) -;; (global $string_mem_user_start (mut i32) (i32.const 0)) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; General type storage/pointer functions - - (func $VAL0_ptr (param $mv i32) (result i32) - (i32.add (get_local $mv) (i32.const 4))) - (func $VAL1_ptr (param $mv i32) (result i32) - (i32.add (get_local $mv) (i32.const 8))) - - (func $VAL0 (param $mv i32) (result i32) - (i32.load (i32.add (get_local $mv) (i32.const 4)))) - (func $VAL1 (param $mv i32) (result i32) - (i32.load (i32.add (get_local $mv) (i32.const 8)))) - - - (func $MEM_VAL0_ptr (param $mv i32) (result i32) - (i32.add (get_global $mem) - (i32.mul_u (i32.load (i32.add (get_local $mv) (i32.const 4))) - (i32.const 8)))) - (func $MEM_VAL1_ptr (param $mv i32) (result i32) - (i32.add (get_global $mem) - (i32.mul_u (i32.load (i32.add (get_local $mv) (i32.const 8))) - (i32.const 8)))) - (func $MEM_VAL2_ptr (param $mv i32) (result i32) - (i32.add (get_global $mem) - (i32.mul_u (i32.load (i32.add (get_local $mv) (i32.const 12))) - (i32.const 8)))) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - ;; Returns the address of 'mem[mv_idx]' - (func $MalVal_ptr (param $mv_idx i32) (result i32) - ;; MalVal memory 64 bit (2 * i32) aligned - ;;; mem[mv_idx].refcnt_type - (i32.add (get_global $mem) - (i32.mul_u (get_local $mv_idx) (i32.const 8)))) - - ;; Returns the memory index mem of mv - ;; Will usually be used with a load or store by the caller - (func $MalVal_index (param $mv i32) (result i32) - ;; MalVal memory 64 bit (2 * i32) aligned - (i32.div_u (i32.sub_u (get_local $mv) (get_global $mem)) - (i32.const 8))) - - ;; Returns the address of 'mem[mv_idx].refcnt_type' - (func $MalVal_refcnt_type (param $mv_idx i32) (result i32) - (i32.load (call $MalVal_ptr (get_local $mv_idx)))) - - (func $TYPE (param $mv i32) (result i32) - ;;; type = mv->refcnt_type & 31 - (i32.and (i32.load (get_local $mv)) - (i32.const 0x1f))) ;; 0x1f == 31 - - (func $REFS (param $mv i32) (result i32) - ;;; type = mv->refcnt_type & 31 - (i32.shr_u (i32.load (get_local $mv)) - (i32.const 5))) ;; / 32 - - ;; Returns the address of 'mem[mv_idx].val[val]' - ;; Will usually be used with a load or store by the caller - (func $MalVal_val_ptr (param $mv_idx i32) (param $val i32) (result i32) - (i32.add (i32.add (call $MalVal_ptr (get_local $mv_idx)) - (i32.const 4)) - (i32.mul_u (get_local $val) - (i32.const 4)))) - - ;; Returns the value of 'mem[mv_idx].val[val]' - (func $MalVal_val (param $mv_idx i32) (param $val i32) (result i32) - (i32.load (call $MalVal_val_ptr (get_local $mv_idx) (get_local $val)))) - - (func $MalType_size (param $type i32) (result i32) - ;;; if (type <= 5 || type == 9 || type == 12) - (if i32 (i32.or (i32.le_u (get_local $type) (i32.const 5)) - (i32.or (i32.eq (get_local $type) (i32.const 9)) - (i32.eq (get_local $type) (i32.const 12)))) - (then (i32.const 2)) - (else - ;;; else if (type == 8 || type == 10 || type == 11) - (if i32 (i32.or (i32.eq (get_local $type) (i32.const 8)) - (i32.or (i32.eq (get_local $type) (i32.const 10)) - (i32.eq (get_local $type) (i32.const 11)))) - (then (i32.const 4)) - (else (i32.const 3)))))) - - (func $MalVal_size (param $mv i32) (result i32) - (local $type i32) - (set_local $type (call $TYPE (get_local $mv))) - ;; if (type == FREE_T) - (if i32 (i32.eq (get_local $type) (get_global $FREE_T)) - (then - ;;; return (mv->refcnt_type & 0xffe0)>>5 - (i32.shr_u - (i32.and - (i32.load (get_local $mv)) - (i32.const 0xffe0)) - (i32.const 5))) ;;; / 32 - (else - ;;; return MalType_size(type) - (call $MalType_size (get_local $type))))) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; init_memory - - (func $init_memory - (local $heap_size i32) - -;; (call $print (STRING ">>> init_memory\n")) - - (call $init_sprintf_mem) - - ;; 100 character error_str static buffer - (set_global $error_str (STRING " ")) - ;; 256 character token static buffer - (set_global $token (STRING " ")) - - (set_local $heap_size (i32.add (get_global $MEM_SIZE) - (get_global $STRING_MEM_SIZE))) - (set_global $heap_start (i32.add (get_global $memoryBase) - (get_global $S_STRING_END))) - (set_global $heap_end (i32.add (get_global $heap_start) - (get_local $heap_size))) - - (set_global $mem (get_global $heap_start)) - (set_global $mem_unused_start (i32.const 0)) - (set_global $mem_free_list (i32.const 0)) - -;; (set_global $string_mem (i32.add (get_global $heap_start) -;; (get_global $MEM_SIZE))) -;; (set_global $string_mem_next (get_global $string_mem)) - - ;; Empty values - (set_global $NIL - (call $ALLOC_SCALAR (get_global $NIL_T) (i32.const 0))) - (set_global $FALSE - (call $ALLOC_SCALAR (get_global $BOOLEAN_T) (i32.const 0))) - (set_global $TRUE - (call $ALLOC_SCALAR (get_global $BOOLEAN_T) (i32.const 1))) - (set_global $EMPTY_LIST - (call $ALLOC (get_global $LIST_T) - (get_global $NIL) (get_global $NIL) (get_global $NIL))) - (set_global $EMPTY_VECTOR - (call $ALLOC (get_global $VECTOR_T) - (get_global $NIL) (get_global $NIL) (get_global $NIL))) - (set_global $EMPTY_HASHMAP - (call $ALLOC (get_global $HASHMAP_T) - (get_global $NIL) (get_global $NIL) (get_global $NIL))) - -;; (call $print (STRING "<<< init_memory\n")) - - ) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; memory management - - (func $ALLOC_INTERNAL (param $type i32) (param $val1 i32) - (param $val2 i32) (param $val3 i32) (result i32) - (local $prev i32) - (local $res i32) - (local $size i32) - (set_local $prev (get_global $mem_free_list)) - (set_local $res (get_global $mem_free_list)) - (set_local $size (call $MalType_size (get_local $type))) - - (block $loop_done - (loop $loop - ;; res == mem_unused_start - (if (i32.eq (get_local $res) (get_global $mem_unused_start)) - (then - ;; ALLOC_UNUSED - ;;; if (res + size > MEM_SIZE) - (if (i32.gt_u (i32.add (get_local $res) (get_local $size)) - (get_global $MEM_SIZE)) - (then - ;; Out of memory, exit - (call $print (STRING "Out of mal memory!\n")) - (call $exit (i32.const 1)))) - ;;; if (mem_unused_start += size) - (set_global $mem_unused_start - (i32.add (get_global $mem_unused_start) - (get_local $size))) - ;;; if (prev == res) - (if (i32.eq (get_local $prev) (get_local $res)) - (then - (set_global $mem_free_list (get_global $mem_unused_start))) - (else - ;;; mem[prev].val[0] = mem_unused_start - (i32.store - (call $MalVal_val_ptr (get_local $prev) (i32.const 0)) - (get_global $mem_unused_start)))) - (br $loop_done))) - ;; if (MalVal_size(mem+res) == size) - (if (i32.eq (call $MalVal_size (call $MalVal_ptr (get_local $res))) - (get_local $size)) - (then - ;; ALLOC_MIDDLE - ;;; if (res == mem_free_list) - (if (i32.eq (get_local $res) (get_global $mem_free_list)) - ;; set free pointer (mem_free_list) to next free - ;;; mem_free_list = mem[res].val[0]; - (set_global $mem_free_list - (call $MalVal_val (get_local $res) (i32.const 0)))) - ;; if (res != mem_free_list) - (if (i32.ne (get_local $res) (get_global $mem_free_list)) - ;; set previous free to next free - ;;; mem[prev].val[0] = mem[res].val[0] - (i32.store (call $MalVal_val_ptr (get_local $prev) (i32.const 0)) - (call $MalVal_val (get_local $res) (i32.const 0)))) - (br $loop_done))) - ;;; prev = res - (set_local $prev (get_local $res)) - ;;; res = mem[res].val[0] - (set_local $res (call $MalVal_val (get_local $res) (i32.const 0))) - (br $loop) - ) - ) - ;; ALLOC_DONE - ;;; mem[res].refcnt_type = type + 32 - (i32.store (call $MalVal_ptr (get_local $res)) - (i32.add (get_local $type) (i32.const 32))) - ;; set val to default val1 - ;;; mem[res].val[0] = val1 - (i32.store (call $MalVal_val_ptr (get_local $res) (i32.const 0)) - (get_local $val1)) - ;;; if (type > 5 && type != 9) - (if (i32.and (i32.gt_u (get_local $type) (i32.const 5)) - (i32.ne (get_local $type) (i32.const 9))) - (then - ;; inc refcnt of referenced value - ;;; mem[val1].refcnt_type += 32 - (i32.store (call $MalVal_ptr (get_local $val1)) - (i32.add (call $MalVal_refcnt_type (get_local $val1)) - (i32.const 32))))) - ;;; if (size > 2) - (if (i32.gt_u (get_local $size) (i32.const 2)) - (then - ;; inc refcnt of referenced value - ;;; mem[val2].refcnt_type += 32 - (i32.store (call $MalVal_ptr (get_local $val2)) - (i32.add (call $MalVal_refcnt_type (get_local $val2)) - (i32.const 32))) - ;;; mem[res].val[1] = val2 - (i32.store (call $MalVal_val_ptr (get_local $res) (i32.const 1)) - (get_local $val2)))) - ;;; if (size > 3) - (if (i32.gt_u (get_local $size) (i32.const 3)) - (then - ;; inc refcnt of referenced value - ;;; mem[val3].refcnt_type += 32 - (i32.store (call $MalVal_ptr (get_local $val3)) - (i32.add (call $MalVal_refcnt_type (get_local $val3)) - (i32.const 32))) - ;;; mem[res].val[2] = val3 - (i32.store (call $MalVal_val_ptr (get_local $res) (i32.const 2)) - (get_local $val3)))) - - ;;; return mem + res - (call $MalVal_ptr (get_local $res)) - ) - - (func $ALLOC_SCALAR (param $type i32) (param $val1 i32) - (result i32) - (call $ALLOC_INTERNAL - (get_local $type) - (get_local $val1) - (i32.const 0) - (i32.const 0)) - ) - - (func $ALLOC (param $type i32) (param $val1 i32) - (param $val2 i32) (param $val3 i32) (result i32) - (call $ALLOC_INTERNAL - (get_local $type) - (call $MalVal_index (get_local $val1)) - (call $MalVal_index (get_local $val2)) - (call $MalVal_index (get_local $val3))) - ) - - (func $RELEASE (param $mv i32) - (local $idx i32) - (local $type i32) - (local $size i32) - - ;; Ignore NULLs - ;;; if (mv == NULL) { return; } - (if (i32.eqz (get_local $mv)) (return)) - ;;; idx = mv - mem - (set_local $idx (call $MalVal_index (get_local $mv))) - ;;; type = mv->refcnt_type & 31 - (set_local $type (i32.and (i32.load (get_local $mv)) - (i32.const 0x1f))) ;; 0x1f == 31 - ;;; size = MalType_size(type) - (set_local $size (call $MalType_size (get_local $type))) - - ;; DEBUG - ;;; printf(">>> RELEASE idx: %d, type: %d, size: %d\n", idx, type, size) -;; (call $print (STRING ">>> RELEASE idx: ")) -;; (call $printhex (get_local $idx)) -;; (call $print (STRING ", type: ")) -;; (call $printnum (get_local $type) (i32.const 10)) -;; (call $print (STRING ", size: ")) -;; (call $printnum (get_local $size) (i32.const 10)) -;; (call $print (STRING "\n")) - - (if (i32.eq (i32.const 0) (get_local $mv)) - (then - (call $print (STRING "RELEASE of NULL!\n")) - (call $exit (i32.const 1)))) - - (if (i32.eq (get_global $FREE_T) (get_local $type)) - (then - (call $printf_2 (STRING "RELEASE of already free mv: 0x%x, idx: 0x%x\n") - (get_local $mv) (get_local $idx)) - (call $exit (i32.const 1)))) - (if (i32.lt_u (call $MalVal_refcnt_type (get_local $idx)) - (i32.const 15)) - (then - (call $printf_2 (STRING "RELEASE of unowned mv: 0x%x, idx: 0x%x\n") - (get_local $mv) (get_local $idx)) - (call $exit (i32.const 1)))) - - ;; decrease reference count by one - (i32.store (call $MalVal_ptr (get_local $idx)) - (i32.sub_u (call $MalVal_refcnt_type (get_local $idx)) - (i32.const 32))) - - ;; nil, false, true, empty sequences - (if (i32.le_u (get_local $mv) (get_global $EMPTY_HASHMAP)) - (then - (if (i32.lt_u (call $MalVal_refcnt_type (get_local $idx)) - (i32.const 32)) - (then - (call $printf_2 (STRING "RELEASE of unowned mv: 0x%x, idx: 0x%x\n") - (get_local $mv) (get_local $idx)) - (call $exit (i32.const 1)))) - (return))) - - ;; our reference count is not 0, so don't release - (if (i32.ge_u (call $MalVal_refcnt_type (get_local $idx)) - (i32.const 32)) - (return)) - - (block $done - (block (block (block (block (block (block - (br_table 0 0 0 0 1 1 2 2 3 5 5 5 5 4 5 5 5 (get_local $type))) - ;; nil, boolean, integer, float - (br $done)) - ;; string, kw, symbol - ;; release string, then FREE reference - (call $RELEASE_STRING (get_local $mv)) - (br $done)) - ;; list, vector - (if (i32.ne (call $MalVal_val (get_local $idx) (i32.const 0)) - (i32.const 0)) - (then - ;; release next element and value - (call $RELEASE (call $MEM_VAL0_ptr (get_local $mv))) - (call $RELEASE (call $MEM_VAL1_ptr (get_local $mv))))) - (br $done)) - ;; hashmap - (if (i32.ne (call $MalVal_val (get_local $idx) (i32.const 0)) - (i32.const 0)) - (then - ;; release next element, value, and key - (call $RELEASE (call $MEM_VAL0_ptr (get_local $mv))) - (call $RELEASE (call $MEM_VAL2_ptr (get_local $mv))) - (call $RELEASE (call $MEM_VAL1_ptr (get_local $mv))))) - (br $done)) - ;; env - ;; if outer is set then release outer - (if (i32.ne (call $MalVal_val (get_local $idx) (i32.const 1)) - (i32.const 0)) - (call $RELEASE (call $MEM_VAL1_ptr (get_local $mv)))) - ;; release the hashmap data - (call $RELEASE (call $MEM_VAL0_ptr (get_local $mv))) - (br $done)) - ;; default/unknown - ) - - ;; FREE, free the current element - - ;; set type(FREE/15) and size - ;;; mv->refcnt_type = size*32 + FREE_T - (i32.store (get_local $mv) - (i32.add (i32.mul_u (get_local $size) - (i32.const 32)) - (get_global $FREE_T))) - (i32.store (call $MalVal_val_ptr (get_local $idx) (i32.const 0)) - (get_global $mem_free_list)) - (set_global $mem_free_list (get_local $idx)) - (if (i32.ge_u (get_local $size) (i32.const 3)) - (i32.store (call $MalVal_val_ptr (get_local $idx) (i32.const 1)) - (i32.const 0))) - (if (i32.eq (get_local $size) (i32.const 4)) - (i32.store (call $MalVal_val_ptr (get_local $idx) (i32.const 2)) - (i32.const 0))) - ) - - ;; Allocate a string as follows: - ;; refcnt (i32 set to 1), string data, NULL byte - (func $STRING_DUPE (param $str i32) (result i32) - (local $len i32) - (local $cur i32) - (local $new i32) - (local $idx i32) - - ;; Calculate length of string needed - (set_local $len (call $STRING_LEN (get_local $str))) - - ;; leading i32 refcnt + trailing NULL - (set_local $new (call $malloc (i32.add (i32.const 5) (get_local $len)))) - - ;; DEBUG -;; (call $debug (STRING "STRING_DUPE - malloc returned: ") (get_local $new)) - - ;; set initial refcnt to 1 - (i32.store (get_local $new) (i32.const 1)) - ;; skip refcnt - (set_local $cur (i32.add (get_local $new) (i32.const 4))) - ;; Set NULL terminator - (i32.store8_u (i32.add (get_local $cur) (get_local $len)) (i32.const 0)) - - ;; Copy the characters - (call $MEM_COPY (get_local $cur) (get_local $str) (get_local $len)) - (get_local $new) - ) - - ;; Duplicate regular character array string into a Mal string and - ;; return the MalVal pointer - (func $STRING (param $type i32) (param $str i32) (result i32) - (call $ALLOC_SCALAR - (get_local $type) - (call $STRING_DUPE (get_local $str))) - ) - - (func $RELEASE_STRING (param $mv i32) - (local $str i32) - (set_local $str (call $MalVal_val - (call $MalVal_index (get_local $mv)) - (i32.const 0))) - - ;; DEBUG -;; (call $debug (STRING "RELEASE_STRING - calling free on: ") (get_local $str)) - - (call $free (get_local $str)) - ) -) diff --git a/wasm/printer.wam b/wasm/printer.wam new file mode 100644 index 0000000000..28e9e1a87e --- /dev/null +++ b/wasm/printer.wam @@ -0,0 +1,139 @@ +(module $printer + + (func $pr_str_val (param $res i32) (param $mv i32) (result i32) + (local $type i32) + (local $val0 i32) + (local $sval i32) + (set_local $type ($TYPE $mv)) + (set_local $val0 ($MalVal_val ($MalVal_index $mv) + 0)) + + ;;; switch(type) + (block $done + (block $default + (block (block (block (block (block (block (block (block + (block (block (block (block (block (block (block (block + (br_table 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $type)) + ;; 0: nil + ($MEM_COPY $res "nil" 4) + (set_local $res (i32.add 3 $res)) + (br $done)) + ;; 1: boolean + (if (i32.eq $val0 0) + (then + ;; false + ($MEM_COPY $res "false" 5) + (set_local $res (i32.add 5 $res))) + (else + ;; true + ($MEM_COPY $res "true" 4) + (set_local $res (i32.add 4 $res)))) + (br $done)) + ;; 2: integer + (set_local $res ($sprintf_1 $res "%d" $val0)) + (br $done)) + ;; 3: float/ERROR + (set_local $res ($sprintf_1 $res "%d" " *** GOT FLOAT *** ")) + (br $done)) + ;; 4: string/kw + (set_local $sval ($to_String $mv)) + (if (i32.eq (i32.load8_u $sval) (CHR "\x7f")) + (then + (set_local $res ($sprintf_1 $res ":%s" (i32.add $sval 1)))) + (else + (set_local $res ($sprintf_1 $res "\"%s\"" ($to_String $mv))))) + (br $done)) + ;; 5: symbol + (set_local $res ($sprintf_1 $res "%s" ($to_String $mv))) + (br $done)) + ;; 6: list, fallthrouogh + ) + ;; 7: vector, fallthrough + ) + ;; 8: hashmap + (set_local + $res ($sprintf_1 $res "%c" + (if i32 (i32.eq $type (get_global $LIST_T)) + (CHR "(") + (else (if i32 (i32.eq $type (get_global $VECTOR_T)) + (CHR "[") + (else (CHR "{"))))))) + ;; PR_SEQ_LOOP + ;;; while (VAL0(mv) != 0) + (block $done_seq + (loop $seq_loop + (if (i32.eq ($VAL0 $mv) 0) + (br $done_seq)) + ;;; res = pr_str_val(res, MEM_VAL1(mv), print_readably) + (set_local $res ($pr_str_val $res ($MEM_VAL1_ptr $mv))) + + ;; if this is a hash-map, print the next element + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + ;;; res += snprintf(res, 2, " ") + (set_local $res ($sprintf_1 $res " " 0)) + (set_local $res ($pr_str_val $res ($MEM_VAL2_ptr $mv))))) + ;;; mv = MEM_VAL0(mv) + (set_local $mv ($MEM_VAL0_ptr $mv)) + ;;; if (VAL0(mv) != 0) + (if (i32.ne ($VAL0 $mv) 0) + ;;; res += snprintf(res, 2, " ") + (set_local $res ($sprintf_1 $res " " 0))) + ;;($print "here4\n") + (br $seq_loop) + ) + ) + + (set_local + $res ($sprintf_1 $res "%c" + (if i32 (i32.eq $type (get_global $LIST_T)) + (CHR ")") + (else (if i32 (i32.eq $type (get_global $VECTOR_T)) + (CHR "]") + (else (CHR "}"))))))) + (br $done)) + ;; 9: function + ($MEM_COPY $res "#" 10) + (set_local $res (i32.add 9 $res)) + (br $done)) + ;; 10: mal function + ($MEM_COPY $res "(fn* ...)" 10) + (set_local $res (i32.add 9 $res)) + (br $done)) + ;; 11: macro fn + ($print "macro fn") + ($MEM_COPY $res "#" 13) + (set_local $res (i32.add 12 $res)) + (br $done)) + ;; 12: atom + ($MEM_COPY $res "(atom ...)" 11) + (set_local $res (i32.add 10 $res)) + (br $done)) + ;; 13: environment + ($MEM_COPY $res "#" 11) + (set_local $res (i32.add 10 $res)) + (br $done)) + ;; 14: metadata + ($MEM_COPY $res "#" 12) + (set_local $res (i32.add 11 $res)) + (br $done)) + ;; 15: FREE + ($MEM_COPY $res "#" 12) + (set_local $res (i32.add 11 $res)) + (br $done)) + ;; 16: default + ($MEM_COPY $res "#" 11) + (set_local $res (i32.add 10 $res)) + ) + + $res + ) + + (func $pr_str (param $mv i32) (result i32) + (drop ($pr_str_val (get_global $sprintf_buf) $mv)) + ($STRING (get_global $STRING_T) (get_global $sprintf_buf)) + ) + + (export "pr_str" (func $pr_str)) + +) diff --git a/wasm/printer.wast b/wasm/printer.wast deleted file mode 100644 index 7f88ded823..0000000000 --- a/wasm/printer.wast +++ /dev/null @@ -1,148 +0,0 @@ -(module $printer - - (func $pr_str_val (param $res i32) (param $mv i32) (result i32) - (local $type i32) - (local $val0 i32) - (local $sval i32) - (set_local $type (call $TYPE (get_local $mv))) - (set_local $val0 (call $MalVal_val (call $MalVal_index (get_local $mv)) - (i32.const 0))) - - ;;; switch(type) - (block $done - (block $default - (block (block (block (block (block (block (block (block - (block (block (block (block (block (block (block (block - (br_table 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (get_local $type))) - ;; 0: nil - (call $MEM_COPY (get_local $res) (STRING "nil") (i32.const 4)) - (set_local $res (i32.add (i32.const 3) (get_local $res))) - (br $done)) - ;; 1: boolean - (if (i32.eq (get_local $val0) (i32.const 0)) - (then - ;; false - (call $MEM_COPY (get_local $res) (STRING "false") (i32.const 5)) - (set_local $res (i32.add (i32.const 5) (get_local $res)))) - (else - ;; true - (call $MEM_COPY (get_local $res) (STRING "true") (i32.const 4)) - (set_local $res (i32.add (i32.const 4) (get_local $res))))) - (br $done)) - ;; 2: integer - (set_local $res (call $sprintf_1 (get_local $res) (STRING "%d") - (get_local $val0))) - (br $done)) - ;; 3: float/ERROR - (set_local $res (call $sprintf_1 (get_local $res) (STRING "%d") - (STRING " *** GOT FLOAT *** "))) - (br $done)) - ;; 4: string/kw - (set_local $sval (call $to_String (get_local $mv))) - (if (i32.eq (i32.load8_u (get_local $sval)) (CHAR "\x7f")) - (then - (set_local $res (call $sprintf_1 (get_local $res) (STRING ":%s") - (i32.add (get_local $sval) (i32.const 1))))) - (else - (set_local $res (call $sprintf_1 (get_local $res) (STRING "\"%s\"") - (call $to_String (get_local $mv)))))) - (br $done)) - ;; 5: symbol - (set_local $res (call $sprintf_1 (get_local $res) (STRING "%s") - (call $to_String (get_local $mv)))) - (br $done)) - ;; 6: list, fallthrouogh - ) - ;; 7: vector, fallthrough - ) - ;; 8: hashmap - (set_local - $res (call $sprintf_1 (get_local $res) (STRING "%c") - (if i32 (i32.eq (get_local $type) (get_global $LIST_T)) - (CHAR "(") - (else (if i32 (i32.eq (get_local $type) (get_global $VECTOR_T)) - (CHAR "[") - (else (CHAR "{"))))))) - ;; PR_SEQ_LOOP - ;;; while (VAL0(mv) != 0) - (block $done_seq - (loop $seq_loop - (if (i32.eq (call $VAL0 (get_local $mv)) (i32.const 0)) - (br $done_seq)) - ;;; res = pr_str_val(res, MEM_VAL1(mv), print_readably) - (set_local $res (call $pr_str_val (get_local $res) - (call $MEM_VAL1_ptr (get_local $mv)))) - - ;; if this is a hash-map, print the next element - (if (i32.eq (get_local $type) (get_global $HASHMAP_T)) - (then - ;;; res += snprintf(res, 2, " ") - (set_local $res (call $sprintf_1 (get_local $res) (STRING " ") - (i32.const 0))) - (set_local $res (call $pr_str_val (get_local $res) - (call $MEM_VAL2_ptr (get_local $mv)))))) - ;;; mv = MEM_VAL0(mv) - (set_local $mv (call $MEM_VAL0_ptr (get_local $mv))) - ;;; if (VAL0(mv) != 0) - (if (i32.ne (call $VAL0 (get_local $mv)) (i32.const 0)) - ;;; res += snprintf(res, 2, " ") - (set_local $res (call $sprintf_1 (get_local $res) (STRING " ") - (i32.const 0)))) - ;;(call $print (STRING "here4\n")) - (br $seq_loop) - ) - ) - - (set_local - $res (call $sprintf_1 (get_local $res) (STRING "%c") - (if i32 (i32.eq (get_local $type) (get_global $LIST_T)) - (CHAR ")") - (else (if i32 (i32.eq (get_local $type) (get_global $VECTOR_T)) - (CHAR "]") - (else (CHAR "}"))))))) - (br $done)) - ;; 9: function - (call $MEM_COPY (get_local $res) (STRING "#") (i32.const 10)) - (set_local $res (i32.add (i32.const 9) (get_local $res))) - (br $done)) - ;; 10: mal function - (call $MEM_COPY (get_local $res) (STRING "(fn* ...)") (i32.const 10)) - (set_local $res (i32.add (i32.const 9) (get_local $res))) - (br $done)) - ;; 11: macro fn - (call $print (STRING "macro fn")) - (call $MEM_COPY (get_local $res) (STRING "#") (i32.const 13)) - (set_local $res (i32.add (i32.const 12) (get_local $res))) - (br $done)) - ;; 12: atom - (call $MEM_COPY (get_local $res) (STRING "(atom ...)") (i32.const 11)) - (set_local $res (i32.add (i32.const 10) (get_local $res))) - (br $done)) - ;; 13: environment - (call $MEM_COPY (get_local $res) (STRING "#") (i32.const 11)) - (set_local $res (i32.add (i32.const 10) (get_local $res))) - (br $done)) - ;; 14: metadata - (call $MEM_COPY (get_local $res) (STRING "#") (i32.const 12)) - (set_local $res (i32.add (i32.const 11) (get_local $res))) - (br $done)) - ;; 15: FREE - (call $MEM_COPY (get_local $res) (STRING "#") (i32.const 12)) - (set_local $res (i32.add (i32.const 11) (get_local $res))) - (br $done)) - ;; 16: default - (call $MEM_COPY (get_local $res) (STRING "#") (i32.const 11)) - (set_local $res (i32.add (i32.const 10) (get_local $res))) - ) - - (get_local $res) - ) - - (func $pr_str (param $mv i32) (result i32) - (drop (call $pr_str_val (get_global $sprintf_buf) (get_local $mv))) - (call $STRING (get_global $STRING_T) (get_global $sprintf_buf)) - ) - - (export "pr_str" (func $pr_str)) - -) diff --git a/wasm/reader.wam b/wasm/reader.wam new file mode 100644 index 0000000000..af13e90123 --- /dev/null +++ b/wasm/reader.wam @@ -0,0 +1,287 @@ +(module $reader + + ;; TODO: global warning + (global $token (mut i32) 0) + (global $read_index (mut i32) 0) + + (func $skip_spaces (param $str i32) (result i32) + (local $found i32) + (local $c i32) + (set_local $found 0) + (set_local $c (i32.load8_u (i32.add $str (get_global $read_index)))) + (block $done + (loop $loop + ;;; while (c == ' ' || c == ',' || c == '\n') + (if (i32.and (i32.and + (i32.ne $c (CHR " ")) + (i32.ne $c (CHR ","))) + (i32.ne $c (CHR "\n"))) + (br $done)) + (set_local $found 1) + ;;; c=str[++(*index)] + (set_global $read_index (i32.add (get_global $read_index) 1)) + (set_local $c (i32.load8_u (i32.add $str (get_global $read_index)))) + (br $loop) + ) + ) +;; ($debug ">>> skip_spaces:" $found) + $found + ) + + (func $skip_to_eol (param $str i32) (result i32) + (local $found i32) + (local $c i32) + (set_local $found 0) + (set_local $c (i32.load8_c (i32.add $str (get_global $read_index)))) + (if (i32.eq $c (CHR ";")) + (then + (set_local $found 1) + (block $done + (loop $loop + ;;; c=str[++(*index)] + (set_global $read_index (i32.add (get_global $read_index) 1)) + (set_local $c (i32.load8_u (i32.add $str + (get_global $read_index)))) + ;;; while (c != '\0' && c != '\n') + (if (i32.and (i32.ne $c (CHR "\x00")) (i32.ne $c (CHR "\n"))) + (br $loop)) + ) + ))) +;; ($debug ">>> skip_to_eol:" $found) + $found + ) + + (func $skip_spaces_comments (param $str i32) + (loop $loop + ;; skip spaces + (if ($skip_spaces $str) (br $loop)) + ;; skip comments + (if ($skip_to_eol $str) (br $loop)) + ) + ) + + (func $read_token (param $str i32) (result i32) + (local $token_index i32) + (local $instring i32) + (local $escaped i32) + (local $c i32) + (set_local $token_index 0) + (set_local $instring 0) + (set_local $escaped 0) + + ($skip_spaces_comments $str) + + ;; read first character + ;;; c=str[++(*index)] + (set_local $c (i32.load8_u (i32.add $str (get_global $read_index)))) + (set_global $read_index (i32.add (get_global $read_index) 1)) + ;; read first character + ;;; token[token_index++] = c + (i32.store8_u (i32.add (get_global $token) $token_index) $c) + (set_local $token_index (i32.add $token_index 1)) + ;; single/double character token + (if (i32.or (i32.eq $c (CHR "(")) + (i32.or (i32.eq $c (CHR ")")) + (i32.or (i32.eq $c (CHR "[")) + (i32.or (i32.eq $c (CHR "]")) + (i32.or (i32.eq $c (CHR "{")) + (i32.or (i32.eq $c (CHR "}")) + (i32.or (i32.eq $c (CHR "'")) + (i32.or (i32.eq $c (CHR "`")) + (i32.or (i32.eq $c (CHR "@")) + (i32.and (i32.eq $c (CHR "~")) + (i32.eq (i32.load8_u (i32.add $str (get_global $read_index))) + (CHR "@")))))))))))) + + (then + ;; continue + (nop)) + (else + ;;; if (c == '"') instring = true + (set_local $instring (i32.eq $c (CHR "\""))) + (block $done + (loop $loop + ;; peek at next character + ;;; c = str[*index] + (set_local $c (i32.load8_u + (i32.add $str (get_global $read_index)))) + ;;; if (c == '\0') break + (if (i32.eq $c 0) (br $done)) + ;;; if (!instring) + (if (i32.eqz $instring) + (then + ;; next character is token delimiter + (if (i32.or (i32.eq $c (CHR "(")) + (i32.or (i32.eq $c (CHR ")")) + (i32.or (i32.eq $c (CHR "[")) + (i32.or (i32.eq $c (CHR "]")) + (i32.or (i32.eq $c (CHR "{")) + (i32.or (i32.eq $c (CHR "}")) + (i32.or (i32.eq $c (CHR " ")) + (i32.or (i32.eq $c (CHR ",")) + (i32.eq $c (CHR "\n")))))))))) + (br $done)))) + ;; read next character + ;;; token[token_index++] = str[(*index)++] + (i32.store8_u (i32.add (get_global $token) $token_index) + (i32.load8_u + (i32.add $str (get_global $read_index)))) + (set_local $token_index (i32.add $token_index 1)) + (set_global $read_index (i32.add (get_global $read_index) 1)) + ;;; if (token[0] == '~' && token[1] == '@') break + (if (i32.and (i32.eq (i32.load8_u (i32.add (get_global $token) 0)) + (CHR "~")) + (i32.eq (i32.load8_u (i32.add (get_global $token) 1)) + 0x40)) + (br $done)) + + ;;; if ((!instring) || escaped) + (if (i32.or (i32.eqz $instring) $escaped) + (then + (set_local $escaped 0) + (br $loop))) + (if (i32.eq $c (CHR "\\")) + (set_local $escaped 1)) + (if (i32.eq $c (CHR "\"")) + (br $done)) + (br $loop) + ) + ))) + + ;;; token[token_index] = '\0' + (i32.store8_u (i32.add (get_global $token) $token_index) 0) + (get_global $token) + ) + + (func $read_seq (param $str i32) (param $type i32) (param $end i32) + (result i32) + (local $res i32) + (local $val2 i32) + (local $val3 i32) + (local $c i32) + + ;; MAP_LOOP stack + (local $ret i32) + (local $empty i32) + (local $current i32) + + ;; MAP_LOOP_START + (set_local $res ($MAP_LOOP_START $type)) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + ;; READ_SEQ_LOOP + (block $done + (loop $loop + ($skip_spaces_comments $str) + + ;; peek at next character + ;;; c = str[*index] + (set_local $c (i32.load8_u (i32.add $str (get_global $read_index)))) + (if (i32.eq $c (CHR "\x00")) + (then + ($THROW_STR_0 "unexpected EOF") + (br $done))) + (if (i32.eq $c $end) + (then + ;; read next character + ;;; c = str[(*index)++] + (set_local $c (i32.load8_u (i32.add $str (get_global $read_index)))) + (set_global $read_index (i32.add (get_global $read_index) 1)) + (br $done))) + + ;; value (or key for hash-maps) + (set_local $val2 ($read_form $str)) + + ;; if error, release the unattached element + (if (get_global $error_type) + (then + ($RELEASE $val2) + (br $done))) + + ;; if this is a hash-map, READ_FORM again + (if (i32.eq $type (get_global $HASHMAP_T)) + (set_local $val3 ($read_form $str))) + + ;; update the return sequence structure + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (br $loop) + ) + ) + + ;; MAP_LOOP_DONE + $ret + ) + + (func $read_form (param $str i32) (result i32) + ;;($STRING (get_global $STRING_T) $str) + (local $tok i32) + (local $c0 i32) + (local $c1 i32) + (local $res i32) + + (if (get_global $error_type) (return 0)) + + (set_local $tok ($read_token $str)) +;; ($debug ">>> read_form 1:" $tok) + ;;; c0 = token[0] + (set_local $c0 (i32.load8_u $tok)) + (set_local $c1 (i32.load8_u (i32.add $tok 1))) + + (if (i32.eq $c0 0) + (then + (return ($INC_REF (get_global $NIL)))) + (else (if (i32.or (i32.and (i32.ge_u $c0 (CHR "0")) + (i32.le_u $c0 (CHR "9"))) + (i32.and (i32.eq $c0 (CHR "-")) + (i32.and (i32.ge_u $c1 (CHR "0")) + (i32.le_u $c1 (CHR "9"))))) + (then + (return ($INTEGER ($ATOI $tok)))) + (else (if (i32.eq $c0 (CHR ":")) + (then + (i32.store8_u $tok (CHR "\x7f")) + (return ($STRING (get_global $STRING_T) $tok))) + (else (if (i32.eq $c0 (CHR "\"")) + (then + ;; TODO: unescape + (i32.store8_u (i32.sub_u (i32.add $tok ($STRING_LEN $tok)) 1) + (CHR "\x00")) + (return ($STRING (get_global $STRING_T) (i32.add $tok 1)))) + (else (if (i32.eq $c0 (CHR "(")) + (then + (return ($read_seq $str (get_global $LIST_T) (CHR ")")))) + (else (if (i32.eq $c0 (CHR "[")) + (then + (return ($read_seq $str (get_global $VECTOR_T) (CHR "]")))) + (else (if (i32.eq $c0 (CHR "{")) + (then + (return ($read_seq $str (get_global $HASHMAP_T) (CHR "}")))) + (else (if (i32.or (i32.eq $c0 (CHR ")")) + (i32.or (i32.eq $c0 (CHR "]")) + (i32.eq $c0 (CHR "}")))) + (then + ($THROW_STR_1 "unexpected '%c'" $c0) + (return 0)) + (else + (return ($STRING (get_global $SYMBOL_T) $tok)))))))))))))))))) + ) + + (func $read_str (param $str i32) (result i32) + (set_global $read_index 0) + ($read_form $str) + ) + + (export "read_str" (func $read_str)) + +) diff --git a/wasm/reader.wast b/wasm/reader.wast deleted file mode 100644 index 3b16df3acb..0000000000 --- a/wasm/reader.wast +++ /dev/null @@ -1,321 +0,0 @@ -(module $reader - - ;; TODO: global warning - (global $token (mut i32) (i32.const 0)) - (global $read_index (mut i32) (i32.const 0)) - - (func $skip_spaces (param $str i32) (result i32) - (local $found i32) - (local $c i32) - (set_local $found (i32.const 0)) - (set_local $c (i32.load8_u (i32.add (get_local $str) - (get_global $read_index)))) - (block $done - (loop $loop - ;;; while (c == ' ' || c == ',' || c == '\n') - (if (i32.and (i32.and - (i32.ne (get_local $c) (CHAR " ")) - (i32.ne (get_local $c) (CHAR ","))) - (i32.ne (get_local $c) (CHAR "\n"))) - (br $done)) - (set_local $found (i32.const 1)) - ;;; c=str[++(*index)] - (set_global $read_index (i32.add (get_global $read_index) - (i32.const 1))) - (set_local $c (i32.load8_u (i32.add (get_local $str) - (get_global $read_index)))) - (br $loop) - ) - ) -;; (call $debug (STRING ">>> skip_spaces:") (get_local $found)) - (get_local $found) - ) - - (func $skip_to_eol (param $str i32) (result i32) - (local $found i32) - (local $c i32) - (set_local $found (i32.const 0)) - (set_local $c (i32.load8_c (i32.add (get_local $str) - (get_global $read_index)))) - (if (i32.eq (get_local $c) (CHAR ";")) - (then - (set_local $found (i32.const 1)) - (block $done - (loop $loop - ;;; c=str[++(*index)] - (set_global $read_index (i32.add (get_global $read_index) - (i32.const 1))) - (set_local $c (i32.load8_u (i32.add (get_local $str) - (get_global $read_index)))) - ;;; while (c != '\0' && c != '\n') - (if (i32.and (i32.ne (get_local $c) (CHAR "\x00")) - (i32.ne (get_local $c) (CHAR "\n"))) - (br $loop)) - ) - ))) -;; (call $debug (STRING ">>> skip_to_eol:") (get_local $found)) - (get_local $found) - ) - - (func $skip_spaces_comments (param $str i32) - (loop $loop - ;; skip spaces - (if (call $skip_spaces (get_local $str)) (br $loop)) - ;; skip comments - (if (call $skip_to_eol (get_local $str)) (br $loop)) - ) - ) - - (func $read_token (param $str i32) (result i32) - (local $token_index i32) - (local $instring i32) - (local $escaped i32) - (local $c i32) - (set_local $token_index (i32.const 0)) - (set_local $instring (i32.const 0)) - (set_local $escaped (i32.const 0)) - - (call $skip_spaces_comments (get_local $str)) - - ;; read first character - ;;; c=str[++(*index)] - (set_local $c (i32.load8_u (i32.add (get_local $str) - (get_global $read_index)))) - (set_global $read_index (i32.add (get_global $read_index) - (i32.const 1))) - ;; read first character - ;;; token[token_index++] = c - (i32.store8_u (i32.add (get_global $token) (get_local $token_index)) - (get_local $c)) - (set_local $token_index (i32.add (get_local $token_index) - (i32.const 1))) - ;; single/double character token - (if (i32.or (i32.eq (get_local $c) (CHAR "(")) - (i32.or (i32.eq (get_local $c) (CHAR ")")) - (i32.or (i32.eq (get_local $c) (CHAR "[")) - (i32.or (i32.eq (get_local $c) (CHAR "]")) - (i32.or (i32.eq (get_local $c) (CHAR "{")) - (i32.or (i32.eq (get_local $c) (CHAR "}")) - (i32.or (i32.eq (get_local $c) (CHAR "'")) - (i32.or (i32.eq (get_local $c) (CHAR "`")) - (i32.or (i32.eq (get_local $c) (CHAR "@")) - (i32.and (i32.eq (get_local $c) (CHAR "~")) - (i32.eq (i32.load8_u (i32.add (get_local $str) - (get_global $read_index))) - (CHAR "@")))))))))))) - - (then - ;; continue - (nop)) - (else - ;;; if (c == '"') instring = true - (set_local $instring (i32.eq (get_local $c) (CHAR "\""))) - (block $done - (loop $loop - ;; peek at next character - ;;; c = str[*index] - (set_local $c (i32.load8_u (i32.add (get_local $str) - (get_global $read_index)))) - ;;; if (c == '\0') break - (if (i32.eq (get_local $c) (i32.const 0)) (br $done)) - ;;; if (!instring) - (if (i32.eqz (get_local $instring)) - (then - ;; next character is token delimiter - (if (i32.or (i32.eq (get_local $c) (CHAR "(")) - (i32.or (i32.eq (get_local $c) (CHAR ")")) - (i32.or (i32.eq (get_local $c) (CHAR "[")) - (i32.or (i32.eq (get_local $c) (CHAR "]")) - (i32.or (i32.eq (get_local $c) (CHAR "{")) - (i32.or (i32.eq (get_local $c) (CHAR "}")) - (i32.or (i32.eq (get_local $c) (CHAR " ")) - (i32.or (i32.eq (get_local $c) (CHAR ",")) - (i32.eq (get_local $c) (CHAR "\n")))))))))) - (br $done)))) - ;; read next character - ;;; token[token_index++] = str[(*index)++] - (i32.store8_u (i32.add (get_global $token) - (get_local $token_index)) - (i32.load8_u (i32.add (get_local $str) - (get_global $read_index)))) - (set_local $token_index (i32.add (get_local $token_index) - (i32.const 1))) - (set_global $read_index (i32.add (get_global $read_index) - (i32.const 1))) - ;;; if (token[0] == '~' && token[1] == '@') break - (if (i32.and (i32.eq (i32.load8_u (i32.add (get_global $token) - (i32.const 0))) - (CHAR "~")) - (i32.eq (i32.load8_u (i32.add (get_global $token) - (i32.const 1))) - (i32.const 0x40))) - (br $done)) - - ;;; if ((!instring) || escaped) - (if (i32.or (i32.eqz (get_local $instring)) - (get_local $escaped)) - (then - (set_local $escaped (i32.const 0)) - (br $loop))) - (if (i32.eq (get_local $c) (CHAR "\\")) - (set_local $escaped (i32.const 1))) - (if (i32.eq (get_local $c) (CHAR "\"")) - (br $done)) - (br $loop) - ) - ))) - - ;;; token[token_index] = '\0' - (i32.store8_u (i32.add (get_global $token) (get_local $token_index)) - (i32.const 0)) - (get_global $token) - ) - - (func $read_seq (param $str i32) (param $type i32) (param $end i32) - (result i32) - (local $res i32) - (local $val2 i32) - (local $val3 i32) - (local $c i32) - - ;; MAP_LOOP stack - (local $ret i32) - (local $empty i32) - (local $current i32) - - ;; MAP_LOOP_START - (set_local $res (call $MAP_LOOP_START (get_local $type))) - ;; push MAP_LOOP stack - ;;; empty = current = ret = res - (set_local $ret (get_local $res)) - (set_local $current (get_local $res)) - (set_local $empty (get_local $res)) - - ;; READ_SEQ_LOOP - (block $done - (loop $loop - (call $skip_spaces_comments (get_local $str)) - - ;; peek at next character - ;;; c = str[*index] - (set_local $c (i32.load8_u (i32.add (get_local $str) - (get_global $read_index)))) - (if (i32.eq (get_local $c) (CHAR "\x00")) - (then - (call $THROW_STR_0 (STRING "unexpected EOF")) - (br $done))) - (if (i32.eq (get_local $c) (get_local $end)) - (then - ;; read next character - ;;; c = str[(*index)++] - (set_local $c (i32.load8_u (i32.add (get_local $str) - (get_global $read_index)))) - (set_global $read_index (i32.add (get_global $read_index) - (i32.const 1))) - (br $done))) - - ;; value (or key for hash-maps) - (set_local $val2 (call $read_form (get_local $str))) - - ;; if error, release the unattached element - (if (get_global $error_type) - (then - (call $RELEASE (get_local $val2)) - (br $done))) - - ;; if this is a hash-map, READ_FORM again - (if (i32.eq (get_local $type) (get_global $HASHMAP_T)) - (set_local $val3 (call $read_form (get_local $str)))) - - ;; update the return sequence structure - ;; MAP_LOOP_UPDATE - (set_local $res (call $MAP_LOOP_UPDATE (get_local $type) - (get_local $empty) (get_local $current) - (get_local $val2) (get_local $val3))) - (if (i32.le_u (get_local $current) (get_global $EMPTY_HASHMAP)) - ;; if first element, set return to new element - (set_local $ret (get_local $res))) - ;; update current to point to new element - (set_local $current (get_local $res)) - - (br $loop) - ) - ) - - ;; MAP_LOOP_DONE - (get_local $ret) - ) - - (func $read_form (param $str i32) (result i32) - ;;(call $STRING (get_global $STRING_T) (get_local $str)) - (local $tok i32) - (local $c0 i32) - (local $c1 i32) - (local $res i32) - - (if (get_global $error_type) (return (i32.const 0))) - - (set_local $tok (call $read_token (get_local $str))) -;; (call $debug (STRING ">>> read_form 1:") (get_local $tok)) - ;;; c0 = token[0] - (set_local $c0 (i32.load8_u (get_local $tok))) - (set_local $c1 (i32.load8_u (i32.add (get_local $tok) (i32.const 1)))) - - (if (i32.eq (get_local $c0) (i32.const 0)) - (then - (return (call $INC_REF (get_global $NIL)))) - (else (if (i32.or - (i32.and - (i32.ge_u (get_local $c0) (CHAR "0")) - (i32.le_u (get_local $c0) (CHAR "9"))) - (i32.and - (i32.eq (get_local $c0) (CHAR "-")) - (i32.and (i32.ge_u (get_local $c1) (CHAR "0")) - (i32.le_u (get_local $c1) (CHAR "9"))))) - (then - (return (call $INTEGER (call $ATOI (get_local $tok))))) - (else (if (i32.eq (get_local $c0) (CHAR ":")) - (then - (i32.store8_u (get_local $tok) (CHAR "\x7f")) - (return (call $STRING (get_global $STRING_T) (get_local $tok)))) - (else (if (i32.eq (get_local $c0) (CHAR "\"")) - (then - ;; TODO: unescape - (i32.store8_u (i32.sub_u - (i32.add (get_local $tok) - (call $STRING_LEN (get_local $tok))) - (i32.const 1)) - (CHAR "\x00")) - (return (call $STRING (get_global $STRING_T) (i32.add (get_local $tok) - (i32.const 1))))) - (else (if (i32.eq (get_local $c0) (CHAR "(")) - (then - (return (call $read_seq (get_local $str) - (get_global $LIST_T) (CHAR ")")))) - (else (if (i32.eq (get_local $c0) (CHAR "[")) - (then - (return (call $read_seq (get_local $str) - (get_global $VECTOR_T) (CHAR "]")))) - (else (if (i32.eq (get_local $c0) (CHAR "{")) - (then - (return (call $read_seq (get_local $str) - (get_global $HASHMAP_T) (CHAR "}")))) - (else (if (i32.or (i32.eq (get_local $c0) (CHAR ")")) - (i32.or (i32.eq (get_local $c0) (CHAR "]")) - (i32.eq (get_local $c0) (CHAR "}")))) - (then - (call $THROW_STR_1 (STRING "unexpected '%c'") (get_local $c0)) - (return (i32.const 0))) - (else - (return (call $STRING (get_global $SYMBOL_T) - (get_local $tok))))))))))))))))))) - ) - - (func $read_str (param $str i32) (result i32) - (set_global $read_index (i32.const 0)) - (call $read_form (get_local $str)) - ) - - (export "read_str" (func $read_str)) - -) diff --git a/wasm/step0_repl.wast b/wasm/step0_repl.wam similarity index 52% rename from wasm/step0_repl.wast rename to wasm/step0_repl.wam index 652bb560cd..fd663b607c 100644 --- a/wasm/step0_repl.wast +++ b/wasm/step0_repl.wam @@ -2,20 +2,24 @@ (import "env" "memory" (memory $0 256)) (import "env" "memoryBase" (global $memoryBase i32)) + ;; READ (func $READ (param $str i32) (result i32) - (get_local $str)) + $str + ) (func $EVAL (param $ast i32) (param $env i32) (result i32) - (get_local $ast)) + $ast + ) + ;; PRINT (func $PRINT (param $ast i32) (result i32) - (get_local $ast)) + $ast + ) - (func $rep (param $str i32) (result i32) - (call $PRINT - (call $EVAL - (call $READ (get_local $str)) - (i32.const 0)))) + ;; REPL + (func $rep (param $line i32) (result i32) + ($PRINT ($EVAL ($READ $line) 0)) + ) (func $main (result i32) ;; Constant location/value definitions @@ -24,14 +28,14 @@ ;; Start (block $repl_done (loop $repl_loop - (set_local $line (call $readline (STRING "user> "))) - (if (i32.eqz (get_local $line)) (br $repl_done)) - (call $printf_1 (STRING "%s\n") (call $rep (get_local $line))) - (call $free (get_local $line)) + (set_local $line ($readline "user> ")) + (if (i32.eqz $line) (br $repl_done)) + ($printf_1 "%s\n" ($rep $line)) + ($free $line) (br $repl_loop))) - (call $print (STRING "\n")) - (i32.const 0) + ($print "\n") + 0 ) diff --git a/wasm/step1_read_print.wam b/wasm/step1_read_print.wam new file mode 100644 index 0000000000..2253021df1 --- /dev/null +++ b/wasm/step1_read_print.wam @@ -0,0 +1,88 @@ +(module $step1_read_print + (import "env" "memory" (memory $0 256)) + (import "env" "memoryBase" (global $memoryBase i32)) + + ;; READ + (func $READ (param $str i32) (result i32) + ($read_str $str) + ) + + ;; EVAL + (func $EVAL (param $ast i32) (param $env i32) (result i32) + $ast + ) + + ;; PRINT + (func $PRINT (param $ast i32) (result i32) + ($pr_str $ast) + ) + + ;; REPL + (func $rep (param $line i32) (param $env i32) (result i32) + (local $mv1 i32) + (local $mv2 i32) + (local $ms i32) + (block $rep_done + (set_local $mv1 ($READ $line)) + (if (get_global $error_type) (br $rep_done)) + + (set_local $mv2 ($EVAL $mv1 $env)) + (if (get_global $error_type) (br $rep_done)) + +;; ($PR_MEMORY -1 -1) + (set_local $ms ($PRINT $mv2)) + ) + +;; ($PR_MEMORY -1 -1) + ($RELEASE $mv1) + $ms + ) + + (func $main (result i32) + ;; Constant location/value definitions + (local $line i32) + (local $res i32) + + ;; DEBUG + ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) + ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) + ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) + ($printf_1 "mem: 0x%x\n" (get_global $mem)) +;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) + ($PR_MEMORY -1 -1) +;; ($PR_MEMORY_RAW (get_global $mem) +;; (i32.add (get_global $mem) +;; (i32.mul_u (get_global $mem_unused_start) +;; 8))) + + ;; Start + (block $repl_done + (loop $repl_loop + (set_local $line ($readline "user> ")) + (if (i32.eqz $line) (br $repl_done)) + (if (i32.eq (i32.load8_u $line) 0) + (then + ($free $line) + (br $repl_loop))) + (set_local $res ($rep $line 0)) + (if (get_global $error_type) + (then + ($printf_1 "Error: %s\n" (get_global $error_str)) + (set_global $error_type 0)) + (else + ($printf_1 "%s\n" ($to_String $res)))) + ($RELEASE $res) +;; ($PR_MEMORY -1 -1) + ($free $line) + (br $repl_loop))) + + ($print "\n") + ($PR_MEMORY -1 -1) + 0 + ) + + + (export "_main" (func $main)) + (export "__post_instantiate" (func $init_memory)) +) + diff --git a/wasm/step1_read_print.wast b/wasm/step1_read_print.wast deleted file mode 100644 index 51fcb41ac4..0000000000 --- a/wasm/step1_read_print.wast +++ /dev/null @@ -1,81 +0,0 @@ -(module $step1_read_print - (import "env" "memory" (memory $0 256)) - (import "env" "memoryBase" (global $memoryBase i32)) - - (func $READ (param $str i32) (result i32) - (call $read_str (get_local $str))) - - (func $EVAL (param $ast i32) (param $env i32) (result i32) - (get_local $ast)) - - (func $PRINT (param $ast i32) (result i32) - (call $pr_str (get_local $ast))) - - (func $rep (param $line i32) (param $env i32) (result i32) - (local $mv1 i32) - (local $mv2 i32) - (local $ms i32) - (block $rep_done - (set_local $mv1 (call $READ (get_local $line))) - (if (get_global $error_type) (br $rep_done)) - - (set_local $mv2 (call $EVAL (get_local $mv1) (get_local $env))) - (if (get_global $error_type) (br $rep_done)) - -;; (call $PR_MEMORY (i32.const -1) (i32.const -1)) - (set_local $ms (call $PRINT (get_local $mv2))) - ) - -;; (call $PR_MEMORY (i32.const -1) (i32.const -1)) - (call $RELEASE (get_local $mv1)) - (get_local $ms) - ) - - (func $main (result i32) - ;; Constant location/value definitions - (local $line i32) - (local $res i32) - - ;; DEBUG - (call $printf_1 (STRING "memoryBase: %d\n") (get_global $memoryBase)) - (call $printf_1 (STRING "heap_start: %d\n") (get_global $heap_start)) - (call $printf_1 (STRING "heap_end: %d\n") (get_global $heap_end)) - (call $printf_1 (STRING "mem: %d\n") (get_global $mem)) -;; (call $printf_1 (STRING "string_mem: %d\n") (get_global $string_mem)) - (call $PR_MEMORY (i32.const -1) (i32.const -1)) -;; (call $PR_MEMORY_RAW (get_global $mem) -;; (i32.add (get_global $mem) -;; (i32.mul_u (get_global $mem_unused_start) -;; (i32.const 8)))) - - ;; Start - (block $repl_done - (loop $repl_loop - (set_local $line (call $readline (STRING "user> "))) - (if (i32.eqz (get_local $line)) (br $repl_done)) - (if (i32.eq (i32.load8_u (get_local $line)) (i32.const 0)) - (then - (call $free (get_local $line)) - (br $repl_loop))) - (set_local $res (call $rep (get_local $line) (i32.const 0))) - (if (get_global $error_type) - (then - (call $printf_1 (STRING "Error: %s\n") (get_global $error_str)) - (set_global $error_type (i32.const 0))) - (else - (call $printf_1 (STRING "%s\n") (call $to_String (get_local $res))))) - (call $RELEASE (get_local $res)) -;; (call $PR_MEMORY (i32.const -1) (i32.const -1)) - (call $free (get_local $line)) - (br $repl_loop))) - - (call $print (STRING "\n")) - (call $PR_MEMORY (i32.const -1) (i32.const -1)) - (i32.const 0) - ) - - - (export "_main" (func $main)) - (export "__post_instantiate" (func $init_memory)) -) - diff --git a/wasm/step2_eval.wam b/wasm/step2_eval.wam new file mode 100644 index 0000000000..ca9a24a7ac --- /dev/null +++ b/wasm/step2_eval.wam @@ -0,0 +1,255 @@ +(module $step1_read_print + (import "env" "memory" (memory $0 256)) + (import "env" "memoryBase" (global $memoryBase i32)) + + ;; READ + (func $READ (param $str i32) (result i32) + ($read_str $str) + ) + + ;; EVAL + (func $EVAL_AST (param $ast i32) (param $env i32) (result i32) + (local $res i32) + (local $val2 i32) + (local $val3 i32) + (local $ret i32) + (local $empty i32) + (local $current i32) + (local $type i32) + (local $res2 i64) + (local $found i32) + + (if (get_global $error_type) (return 0)) + (set_local $type ($TYPE $ast)) + + ;;; switch(type) + (block $done + (block $default (block (block + (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) + ;; symbol + ;; found/res returned as hi 32/lo 32 of i64 + (set_local $res2 ($HASHMAP_GET $env $ast)) + (set_local $res (i32.wrap/i64 $res2)) + (set_local $found (i32.wrap/i64 (i64.shr_u $res2 + (i64.const 32)))) + (if (i32.eqz $found) + ($THROW_STR_1 "'%s' not found" + ($to_String $ast))) + (set_local $res ($INC_REF $res)) + + (br $done)) + ;; list, vector, hashmap + ;; MAP_LOOP_START + (set_local $res ($MAP_LOOP_START $type)) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (block $done + (loop $loop + ;; check if we are done evaluating the source sequence + (if (i32.eq ($VAL0 $ast) 0) + (br $done)) + + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (else + (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (set_local $val2 $res) + + ;; if error, release the unattached element + (if (get_global $error_type) + (then + ($RELEASE $res) + (set_local $res 0) + (br $done))) + + ;; for hash-maps, copy the key (inc ref since we are going + ;; to release it below) + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $val3 $val2) + (set_local $val2 ($MEM_VAL1_ptr $ast)) + (drop ($INC_REF $ast)))) + + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (set_local $ast ($MEM_VAL0_ptr $ast)) + + (br $loop) + ) + ) + ;; MAP_LOOP_DONE + (set_local $res $ret) + ;; EVAL_AST_RETURN: nothing to do + (br $done)) + ;; default + (set_local $res ($INC_REF $ast)) + ) + + $res + ) + + (type $fnT (func (param i32) (result i32))) + + (table anyfunc + (elem + $add $subtract $multiply $divide)) + + (func $EVAL (param $ast i32) (param $env i32) (result i32) + (local $res i32) + (local $f_args i32) + (local $f i32) + (local $args i32) + (local $type i32) + (local $ftype i32) + + (set_local $res 0) + (set_local $f_args 0) + (set_local $f 0) + (set_local $args 0) + (set_local $type ($TYPE $ast)) + + (if (get_global $error_type) (return 0)) + + (if (i32.ne $type (get_global $LIST_T)) + (return ($EVAL_AST $ast $env))) + + ;; APPLY_LIST + (if ($EMPTY_Q $ast) (return ($INC_REF $ast))) + + ;; EVAL_INVOKE + (set_local $res ($EVAL_AST $ast $env)) + (set_local $f_args $res) + + ;; if error, return f/args for release by caller + (if (get_global $error_type) (return $f_args)) + + ;; rest + (set_local $args ($MEM_VAL0_ptr $f_args)) + ;; value + (set_local $f ($MEM_VAL1_ptr $f_args)) + + (set_local $ftype ($TYPE $f)) + (if (i32.eq $ftype (get_global $FUNCTION_T)) + (then + (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f)))) + (else + ($THROW_STR_1 "apply of non-function type: %d\n" $type) + (set_local $res 0))) + + ($RELEASE $f_args) + + $res + ) + + ;; PRINT + (func $PRINT (param $ast i32) (result i32) + ($pr_str $ast) + ) + + ;; REPL + (func $rep (param $line i32) (param $env i32) (result i32) + (local $mv1 i32) + (local $mv2 i32) + (local $ms i32) + (block $rep_done + (set_local $mv1 ($READ $line)) + (if (get_global $error_type) (br $rep_done)) + + (set_local $mv2 ($EVAL $mv1 $env)) + (if (get_global $error_type) (br $rep_done)) + +;; ($PR_MEMORY -1 -1) + (set_local $ms ($PRINT $mv2)) + ) + + ;; release memory from MAL_READ and EVAL + ($RELEASE $mv2) + ($RELEASE $mv1) + $ms + ) + + (func $add (param $args i32) (result i32) + ($INTEGER + (i32.add ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + (func $subtract (param $args i32) (result i32) + ($INTEGER + (i32.sub_s ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + (func $multiply (param $args i32) (result i32) + ($INTEGER + (i32.mul_s ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + (func $divide (param $args i32) (result i32) + ($INTEGER + (i32.div_s ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + + (func $main (result i32) + ;; Constant location/value definitions + (local $line i32) + (local $res i32) + (local $repl_env i32) + + ;; DEBUG + ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) + ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) + ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) + ($printf_1 "mem: 0x%x\n" (get_global $mem)) +;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) + + (set_local $repl_env ($HASHMAP)) + + (set_local $repl_env ($ASSOC1_S $repl_env "+" ($FUNCTION 0))) + (set_local $repl_env ($ASSOC1_S $repl_env "-" ($FUNCTION 1))) + (set_local $repl_env ($ASSOC1_S $repl_env "*" ($FUNCTION 2))) + (set_local $repl_env ($ASSOC1_S $repl_env "/" ($FUNCTION 3))) + + ($PR_MEMORY -1 -1) +;; ($PR_MEMORY_RAW (get_global $mem) +;; (i32.add (get_global $mem) +;; (i32.mul_u (get_global $mem_unused_start) +;; 8))) + + ;; Start + (block $repl_done + (loop $repl_loop + (set_local $line ($readline "user> ")) + (if (i32.eqz $line) (br $repl_done)) + (if (i32.eq (i32.load8_u $line) 0) + (then + ($free $line) + (br $repl_loop))) + (set_local $res ($rep $line $repl_env)) + (if (get_global $error_type) + (then + ($printf_1 "Error: %s\n" (get_global $error_str)) + (set_global $error_type 0)) + (else + ($printf_1 "%s\n" ($to_String $res)))) + ($RELEASE $res) +;; ($PR_MEMORY -1 -1) + ($free $line) + (br $repl_loop))) + + ($print "\n") + ($PR_MEMORY -1 -1) + 0 + ) + + + (export "_main" (func $main)) + (export "__post_instantiate" (func $init_memory)) +) + diff --git a/wasm/step2_eval.wast b/wasm/step2_eval.wast deleted file mode 100644 index ac30da431c..0000000000 --- a/wasm/step2_eval.wast +++ /dev/null @@ -1,269 +0,0 @@ -(module $step1_read_print - (import "env" "memory" (memory $0 256)) - (import "env" "memoryBase" (global $memoryBase i32)) - - ;; READ - (func $READ (param $str i32) (result i32) - (call $read_str (get_local $str)) - ) - - ;; EVAL - (func $EVAL_AST (param $ast i32) (param $env i32) (result i32) - (local $res i32) - (local $val2 i32) - (local $val3 i32) - (local $ret i32) - (local $empty i32) - (local $current i32) - (local $type i32) - (local $res2 i64) - (local $found i32) - - (if (get_global $error_type) (return (i32.const 0))) - (set_local $type (call $TYPE (get_local $ast))) - - ;;; switch(type) - (block $done - (block $default (block (block - (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 (get_local $type))) - ;; symbol - ;; found/res returned as hi 32/lo 32 of i64 - (set_local $res2 (call $HASHMAP_GET (get_local $env) (get_local $ast))) - (set_local $res (i32.wrap/i64 (get_local $res2))) - (set_local $found (i32.wrap/i64 (i64.shr_u (get_local $res2) - (i64.const 32)))) - (if (i32.eqz (get_local $found)) - (call $THROW_STR_1 (STRING "'%s' not found") - (call $to_String (get_local $ast)))) - (set_local $res (call $INC_REF (get_local $res))) - - (br $done)) - ;; list, vector, hashmap - ;; MAP_LOOP_START - (set_local $res (call $MAP_LOOP_START (get_local $type))) - ;; push MAP_LOOP stack - ;;; empty = current = ret = res - (set_local $ret (get_local $res)) - (set_local $current (get_local $res)) - (set_local $empty (get_local $res)) - - (block $done - (loop $loop - ;; check if we are done evaluating the source sequence - (if (i32.eq (call $VAL0 (get_local $ast)) (i32.const 0)) - (br $done)) - - (if (i32.eq (get_local $type) (get_global $HASHMAP_T)) - (then - (set_local $res (call $EVAL (call $MEM_VAL2_ptr (get_local $ast)) - (get_local $env)))) - (else - (set_local $res (call $EVAL (call $MEM_VAL1_ptr (get_local $ast)) - (get_local $env))))) - (set_local $val2 (get_local $res)) - - ;; if error, release the unattached element - (if (get_global $error_type) - (then - (call $RELEASE (get_local $res)) - (set_local $res (i32.const 0)) - (br $done))) - - ;; for hash-maps, copy the key (inc ref since we are going - ;; to release it below) - (if (i32.eq (get_local $type) (get_global $HASHMAP_T)) - (then - (set_local $val3 (get_local $val2)) - (set_local $val2 (call $MEM_VAL1_ptr (get_local $ast))) - (drop (call $INC_REF (get_local $ast))))) - - ;; MAP_LOOP_UPDATE - (set_local $res (call $MAP_LOOP_UPDATE (get_local $type) - (get_local $empty) (get_local $current) - (get_local $val2) (get_local $val3))) - (if (i32.le_u (get_local $current) (get_global $EMPTY_HASHMAP)) - ;; if first element, set return to new element - (set_local $ret (get_local $res))) - ;; update current to point to new element - (set_local $current (get_local $res)) - - (set_local $ast (call $MEM_VAL0_ptr (get_local $ast))) - - (br $loop) - ) - ) - ;; MAP_LOOP_DONE - (set_local $res (get_local $ret)) - ;; EVAL_AST_RETURN: nothing to do - (br $done)) - ;; default - (set_local $res (call $INC_REF (get_local $ast))) - ) - - (get_local $res) - ) - - (type $fnT (func (param i32) (result i32))) - - (table anyfunc - (elem - $add $subtract $multiply $divide)) - - (func $EVAL (param $ast i32) (param $env i32) (result i32) - (local $res i32) - (local $f_args i32) - (local $f i32) - (local $args i32) - (local $type i32) - (local $ftype i32) - - (set_local $res (i32.const 0)) - (set_local $f_args (i32.const 0)) - (set_local $f (i32.const 0)) - (set_local $args (i32.const 0)) - (set_local $type (call $TYPE (get_local $ast))) - - (if (get_global $error_type) (return (i32.const 0))) - - (if (i32.ne (get_local $type) (get_global $LIST_T)) - (return (call $EVAL_AST (get_local $ast) (get_local $env)))) - - ;; APPLY_LIST - (if (call $EMPTY_Q (get_local $ast)) - (return (call $INC_REF (get_local $ast)))) - - ;; EVAL_INVOKE - (set_local $res (call $EVAL_AST (get_local $ast) (get_local $env))) - (set_local $f_args (get_local $res)) - - ;; if error, return f/args for release by caller - (if (get_global $error_type) (return (get_local $f_args))) - - ;; rest - (set_local $args (call $MEM_VAL0_ptr (get_local $f_args))) - ;; value - (set_local $f (call $MEM_VAL1_ptr (get_local $f_args))) - - (set_local $ftype (call $TYPE (get_local $f))) - (if (i32.eq (get_local $ftype) (get_global $FUNCTION_T)) - (then - (set_local $res (call_indirect (type $fnT) (get_local $args) - (call $VAL0 (get_local $f))))) - (else - (call $THROW_STR_1 (STRING "apply of non-function type: %d\n") - (get_local $type)) - (set_local $res (i32.const 0)))) - - (call $RELEASE (get_local $f_args)) - - (get_local $res) - ) - - (func $PRINT (param $ast i32) (result i32) - (call $pr_str (get_local $ast)) - ) - - ;; REPL - (func $rep (param $line i32) (param $env i32) (result i32) - (local $mv1 i32) - (local $mv2 i32) - (local $ms i32) - (block $rep_done - (set_local $mv1 (call $READ (get_local $line))) - (if (get_global $error_type) (br $rep_done)) - - (set_local $mv2 (call $EVAL (get_local $mv1) (get_local $env))) - (if (get_global $error_type) (br $rep_done)) - -;; (call $PR_MEMORY (i32.const -1) (i32.const -1)) - (set_local $ms (call $PRINT (get_local $mv2))) - ) - - ;; release memory from MAL_READ and EVAL - (call $RELEASE (get_local $mv2)) - (call $RELEASE (get_local $mv1)) - (get_local $ms) - ) - - (func $add (param $args i32) (result i32) - (call $INTEGER - (i32.add (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) - (call $VAL0 (call $MEM_VAL1_ptr - (call $MEM_VAL0_ptr (get_local $args))))))) - (func $subtract (param $args i32) (result i32) - (call $INTEGER - (i32.sub_s (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) - (call $VAL0 (call $MEM_VAL1_ptr - (call $MEM_VAL0_ptr (get_local $args))))))) - (func $multiply (param $args i32) (result i32) - (call $INTEGER - (i32.mul_s (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) - (call $VAL0 (call $MEM_VAL1_ptr - (call $MEM_VAL0_ptr (get_local $args))))))) - (func $divide (param $args i32) (result i32) - (call $INTEGER - (i32.div_s (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) - (call $VAL0 (call $MEM_VAL1_ptr - (call $MEM_VAL0_ptr (get_local $args))))))) - - (func $main (result i32) - ;; Constant location/value definitions - (local $line i32) - (local $res i32) - (local $repl_env i32) - - ;; DEBUG - (call $printf_1 (STRING "memoryBase: %d\n") (get_global $memoryBase)) - (call $printf_1 (STRING "heap_start: %d\n") (get_global $heap_start)) - (call $printf_1 (STRING "heap_end: %d\n") (get_global $heap_end)) - (call $printf_1 (STRING "mem: %d\n") (get_global $mem)) -;; (call $printf_1 (STRING "string_mem: %d\n") (get_global $string_mem)) - - (set_local $repl_env (call $HASHMAP)) - - (set_local $repl_env (call $ASSOC1_S (get_local $repl_env) - (STRING "+") (call $FUNCTION (i32.const 0)))) - (set_local $repl_env (call $ASSOC1_S (get_local $repl_env) - (STRING "-") (call $FUNCTION (i32.const 1)))) - (set_local $repl_env (call $ASSOC1_S (get_local $repl_env) - (STRING "*") (call $FUNCTION (i32.const 2)))) - (set_local $repl_env (call $ASSOC1_S (get_local $repl_env) - (STRING "/") (call $FUNCTION (i32.const 3)))) - - (call $PR_MEMORY (i32.const -1) (i32.const -1)) -;; (call $PR_MEMORY_RAW (get_global $mem) -;; (i32.add (get_global $mem) -;; (i32.mul_u (get_global $mem_unused_start) -;; (i32.const 8)))) - - ;; Start - (block $repl_done - (loop $repl_loop - (set_local $line (call $readline (STRING "user> "))) - (if (i32.eqz (get_local $line)) (br $repl_done)) - (if (i32.eq (i32.load8_u (get_local $line)) (i32.const 0)) - (then - (call $free (get_local $line)) - (br $repl_loop))) - (set_local $res (call $rep (get_local $line) (get_local $repl_env))) - (if (get_global $error_type) - (then - (call $printf_1 (STRING "Error: %s\n") (get_global $error_str)) - (set_global $error_type (i32.const 0))) - (else - (call $printf_1 (STRING "%s\n") (call $to_String (get_local $res))))) - (call $RELEASE (get_local $res)) -;; (call $PR_MEMORY (i32.const -1) (i32.const -1)) - (call $free (get_local $line)) - (br $repl_loop))) - - (call $print (STRING "\n")) - (call $PR_MEMORY (i32.const -1) (i32.const -1)) - (i32.const 0) - ) - - - (export "_main" (func $main)) - (export "__post_instantiate" (func $init_memory)) -) - diff --git a/wasm/step3_env.wam b/wasm/step3_env.wam new file mode 100644 index 0000000000..8044dff42e --- /dev/null +++ b/wasm/step3_env.wam @@ -0,0 +1,308 @@ +(module $step1_read_print + (import "env" "memory" (memory $0 256)) + (import "env" "memoryBase" (global $memoryBase i32)) + + ;; READ + (func $READ (param $str i32) (result i32) + ($read_str $str) + ) + + ;; EVAL + (func $EVAL_AST (param $ast i32) (param $env i32) (result i32) + (local $res i32) + (local $val2 i32) + (local $val3 i32) + (local $ret i32) + (local $empty i32) + (local $current i32) + (local $type i32) + (local $found i32) + + (if (get_global $error_type) (return 0)) + (set_local $type ($TYPE $ast)) + + ;;; switch(type) + (block $done + (block $default (block (block + (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) + ;; symbol + ;; found/res returned as hi 32/lo 32 of i64 + (set_local $res ($ENV_GET $env $ast)) + (br $done)) + ;; list, vector, hashmap + ;; MAP_LOOP_START + (set_local $res ($MAP_LOOP_START $type)) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (block $done + (loop $loop + ;; check if we are done evaluating the source sequence + (if (i32.eq ($VAL0 $ast) 0) + (br $done)) + + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (else + (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (set_local $val2 $res) + + ;; if error, release the unattached element + (if (get_global $error_type) + (then + ($RELEASE $res) + (set_local $res 0) + (br $done))) + + ;; for hash-maps, copy the key (inc ref since we are going + ;; to release it below) + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $val3 $val2) + (set_local $val2 ($MEM_VAL1_ptr $ast)) + (drop ($INC_REF $ast)))) + + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (set_local $ast ($MEM_VAL0_ptr $ast)) + + (br $loop) + ) + ) + ;; MAP_LOOP_DONE + (set_local $res $ret) + ;; EVAL_AST_RETURN: nothing to do + (br $done)) + ;; default + (set_local $res ($INC_REF $ast)) + ) + + $res + ) + + (type $fnT (func (param i32) (result i32))) + + (table anyfunc + (elem + $add $subtract $multiply $divide)) + + (func $MAL_GET_A1 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))) + (func $MAL_GET_A2 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast)))) + (func $MAL_GET_A3 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) + + (func $EVAL (param $ast i32) (param $env i32) (result i32) + (local $res i32) + (local $f_args i32) + (local $f i32) + (local $args i32) + (local $type i32) + (local $ftype i32) + (local $a0 i32) + (local $a0sym i32) + (local $a1 i32) + (local $a2 i32) + (local $let_env i32) + + (set_local $res 0) + (set_local $f_args 0) + (set_local $f 0) + (set_local $args 0) + (set_local $type ($TYPE $ast)) + + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + + (if (get_global $error_type) (return 0)) + + (if (i32.ne $type (get_global $LIST_T)) + (return ($EVAL_AST $ast $env))) + + ;; APPLY_LIST + (if ($EMPTY_Q $ast) (return ($INC_REF $ast))) + + (set_local $a0 ($MEM_VAL1_ptr $ast)) + (set_local $a0sym "") + (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) + (set_local $a0sym ($to_String $a0))) + + (if (i32.eqz ($strcmp "def!" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($EVAL $a2 $env)) + (if (get_global $error_type) (return $res)) + + ;; set a1 in env to a2 + (set_local $res ($ENV_SET $env $a1 $res))) + (else (if (i32.eqz ($strcmp "let*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + + ;; create new environment with outer as current environment + (set_local $let_env ($ENV_NEW $env)) + + (block $done + (loop $loop + (if (i32.eqz ($VAL0 $a1)) + (br $done)) + ;; eval current A1 odd element + (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) + $let_env)) + + (if (get_global $error_type) (br $done)) + + ;; set key/value in the let environment + (set_local $res ($ENV_SET $let_env ($MEM_VAL1_ptr $a1) $res)) + ;; release our use, ENV_SET took ownership + ($RELEASE $res) + + ;; skip to the next pair of a1 elements + (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (br $loop) + ) + ) + (set_local $res ($EVAL $a2 $let_env)) + ;; EVAL_RETURN + ($RELEASE $let_env)) + (else + ;; EVAL_INVOKE + (set_local $res ($EVAL_AST $ast $env)) + (set_local $f_args $res) + + ;; if error, return f/args for release by caller + (if (get_global $error_type) (return $f_args)) + + ;; rest + (set_local $args ($MEM_VAL0_ptr $f_args)) + ;; value + (set_local $f ($MEM_VAL1_ptr $f_args)) + + (set_local $ftype ($TYPE $f)) + (if (i32.eq $ftype (get_global $FUNCTION_T)) + (then + (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f)))) + (else + ($THROW_STR_1 "apply of non-function type: %d\n" $type) + (set_local $res 0))) + + ($RELEASE $f_args))))) + + $res + ) + + (func $PRINT (param $ast i32) (result i32) + ($pr_str $ast) + ) + + ;; REPL + (func $rep (param $line i32) (param $env i32) (result i32) + (local $mv1 i32) + (local $mv2 i32) + (local $ms i32) + (block $rep_done + (set_local $mv1 ($READ $line)) + (if (get_global $error_type) (br $rep_done)) + + (set_local $mv2 ($EVAL $mv1 $env)) + (if (get_global $error_type) (br $rep_done)) + +;; ($PR_MEMORY -1 -1) + (set_local $ms ($PRINT $mv2)) + ) + + ;; release memory from MAL_READ and EVAL + ($RELEASE $mv2) + ($RELEASE $mv1) + $ms + ) + + (func $add (param $args i32) (result i32) + ($INTEGER + (i32.add ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + (func $subtract (param $args i32) (result i32) + ($INTEGER + (i32.sub_s ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + (func $multiply (param $args i32) (result i32) + ($INTEGER + (i32.mul_s ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + (func $divide (param $args i32) (result i32) + ($INTEGER + (i32.div_s ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + (func $pr_memory (param $args i32) (result i32) + ($PR_MEMORY -1 -1) + ($INC_REF (get_global $NIL))) + + (func $main (result i32) + ;; Constant location/value definitions + (local $line i32) + (local $res i32) + (local $repl_env i32) + + ;; DEBUG + ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) + ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) + ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) + ($printf_1 "mem: 0x%x\n" (get_global $mem)) +;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) + + (set_local $repl_env ($ENV_NEW (get_global $NIL))) + + (drop ($ENV_SET_S $repl_env "+" ($FUNCTION 0))) + (drop ($ENV_SET_S $repl_env "-" ($FUNCTION 1))) + (drop ($ENV_SET_S $repl_env "*" ($FUNCTION 2))) + (drop ($ENV_SET_S $repl_env "/" ($FUNCTION 3))) + + ($PR_MEMORY -1 -1) +;; ($PR_MEMORY_RAW (get_global $mem) +;; (i32.add (get_global $mem) +;; (i32.mul_u (get_global $mem_unused_start) +;; 8))) + + ;; Start + (block $repl_done + (loop $repl_loop + (set_local $line ($readline "user> ")) + (if (i32.eqz $line) (br $repl_done)) + (if (i32.eq (i32.load8_u $line) 0) + (then + ($free $line) + (br $repl_loop))) + (set_local $res ($rep $line $repl_env)) + (if (get_global $error_type) + (then + ($printf_1 "Error: %s\n" (get_global $error_str)) + (set_global $error_type 0)) + (else + ($printf_1 "%s\n" ($to_String $res)))) + ($RELEASE $res) +;; ($PR_MEMORY -1 -1) + ($free $line) + (br $repl_loop))) + + ($print "\n") + ($PR_MEMORY -1 -1) + 0 + ) + + + (export "_main" (func $main)) + (export "__post_instantiate" (func $init_memory)) +) + diff --git a/wasm/step3_env.wast b/wasm/step3_env.wast deleted file mode 100644 index 21afa31cdf..0000000000 --- a/wasm/step3_env.wast +++ /dev/null @@ -1,329 +0,0 @@ -(module $step1_read_print - (import "env" "memory" (memory $0 256)) - (import "env" "memoryBase" (global $memoryBase i32)) - - ;; READ - (func $READ (param $str i32) (result i32) - (call $read_str (get_local $str)) - ) - - ;; EVAL - (func $EVAL_AST (param $ast i32) (param $env i32) (result i32) - (local $res i32) - (local $val2 i32) - (local $val3 i32) - (local $ret i32) - (local $empty i32) - (local $current i32) - (local $type i32) - (local $found i32) - - (if (get_global $error_type) (return (i32.const 0))) - (set_local $type (call $TYPE (get_local $ast))) - - ;;; switch(type) - (block $done - (block $default (block (block - (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 (get_local $type))) - ;; symbol - ;; found/res returned as hi 32/lo 32 of i64 - (set_local $res (call $ENV_GET (get_local $env) (get_local $ast))) - (br $done)) - ;; list, vector, hashmap - ;; MAP_LOOP_START - (set_local $res (call $MAP_LOOP_START (get_local $type))) - ;; push MAP_LOOP stack - ;;; empty = current = ret = res - (set_local $ret (get_local $res)) - (set_local $current (get_local $res)) - (set_local $empty (get_local $res)) - - (block $done - (loop $loop - ;; check if we are done evaluating the source sequence - (if (i32.eq (call $VAL0 (get_local $ast)) (i32.const 0)) - (br $done)) - - (if (i32.eq (get_local $type) (get_global $HASHMAP_T)) - (then - (set_local $res (call $EVAL (call $MEM_VAL2_ptr (get_local $ast)) - (get_local $env)))) - (else - (set_local $res (call $EVAL (call $MEM_VAL1_ptr (get_local $ast)) - (get_local $env))))) - (set_local $val2 (get_local $res)) - - ;; if error, release the unattached element - (if (get_global $error_type) - (then - (call $RELEASE (get_local $res)) - (set_local $res (i32.const 0)) - (br $done))) - - ;; for hash-maps, copy the key (inc ref since we are going - ;; to release it below) - (if (i32.eq (get_local $type) (get_global $HASHMAP_T)) - (then - (set_local $val3 (get_local $val2)) - (set_local $val2 (call $MEM_VAL1_ptr (get_local $ast))) - (drop (call $INC_REF (get_local $ast))))) - - ;; MAP_LOOP_UPDATE - (set_local $res (call $MAP_LOOP_UPDATE (get_local $type) - (get_local $empty) (get_local $current) - (get_local $val2) (get_local $val3))) - (if (i32.le_u (get_local $current) (get_global $EMPTY_HASHMAP)) - ;; if first element, set return to new element - (set_local $ret (get_local $res))) - ;; update current to point to new element - (set_local $current (get_local $res)) - - (set_local $ast (call $MEM_VAL0_ptr (get_local $ast))) - - (br $loop) - ) - ) - ;; MAP_LOOP_DONE - (set_local $res (get_local $ret)) - ;; EVAL_AST_RETURN: nothing to do - (br $done)) - ;; default - (set_local $res (call $INC_REF (get_local $ast))) - ) - - (get_local $res) - ) - - (type $fnT (func (param i32) (result i32))) - - (table anyfunc - (elem - $add $subtract $multiply $divide)) - - (func $MAL_GET_A1 (param $ast i32) (result i32) - (call $MEM_VAL1_ptr (call $MEM_VAL0_ptr (get_local $ast)))) - (func $MAL_GET_A2 (param $ast i32) (result i32) - (call $MEM_VAL1_ptr (call $MEM_VAL0_ptr (call $MEM_VAL0_ptr (get_local $ast))))) - (func $MAL_GET_A3 (param $ast i32) (result i32) - (call $MEM_VAL1_ptr (call $MEM_VAL0_ptr (call $MEM_VAL0_ptr (call $MEM_VAL0_ptr (get_local $ast)))))) - - (func $EVAL (param $ast i32) (param $env i32) (result i32) - (local $res i32) - (local $f_args i32) - (local $f i32) - (local $args i32) - (local $type i32) - (local $ftype i32) - (local $a0 i32) - (local $a0sym i32) - (local $a1 i32) - (local $a2 i32) - (local $let_env i32) - - (set_local $res (i32.const 0)) - (set_local $f_args (i32.const 0)) - (set_local $f (i32.const 0)) - (set_local $args (i32.const 0)) - (set_local $type (call $TYPE (get_local $ast))) - - ;;(call $PR_VALUE (STRING ">>> EVAL ast: '%s'\n") (get_local $ast)) - - (if (get_global $error_type) (return (i32.const 0))) - - (if (i32.ne (get_local $type) (get_global $LIST_T)) - (return (call $EVAL_AST (get_local $ast) (get_local $env)))) - - ;; APPLY_LIST - (if (call $EMPTY_Q (get_local $ast)) - (return (call $INC_REF (get_local $ast)))) - - (set_local $a0 (call $MEM_VAL1_ptr (get_local $ast))) - (set_local $a0sym (STRING "")) - (if (i32.eq (call $TYPE (get_local $a0)) (get_global $SYMBOL_T)) - (set_local $a0sym (call $to_String (get_local $a0)))) - - (if (i32.eqz (call $strcmp (STRING "def!") (get_local $a0sym))) - (then - (set_local $a1 (call $MAL_GET_A1 (get_local $ast))) - (set_local $a2 (call $MAL_GET_A2 (get_local $ast))) - (set_local $res (call $EVAL (get_local $a2) (get_local $env))) - (if (get_global $error_type) (return (get_local $res))) - - ;; set a1 in env to a2 - (set_local $res (call $ENV_SET (get_local $env) - (get_local $a1) (get_local $res)))) - (else (if (i32.eqz (call $strcmp (STRING "let*") (get_local $a0sym))) - (then - (set_local $a1 (call $MAL_GET_A1 (get_local $ast))) - (set_local $a2 (call $MAL_GET_A2 (get_local $ast))) - - ;; create new environment with outer as current environment - (set_local $let_env (call $ENV_NEW (get_local $env))) - - (block $done - (loop $loop - (if (i32.eqz (call $VAL0 (get_local $a1))) - (br $done)) - ;; eval current A1 odd element - (set_local $res (call $EVAL (call $MEM_VAL1_ptr - (call $MEM_VAL0_ptr - (get_local $a1))) - (get_local $let_env))) - - (if (get_global $error_type) (br $done)) - - ;; set key/value in the let environment - (set_local $res (call $ENV_SET (get_local $let_env) - (call $MEM_VAL1_ptr (get_local $a1)) - (get_local $res))) - ;; release our use, ENV_SET took ownership - (call $RELEASE (get_local $res)) - - ;; skip to the next pair of a1 elements - (set_local $a1 (call $MEM_VAL0_ptr - (call $MEM_VAL0_ptr (get_local $a1)))) - (br $loop) - ) - ) - (set_local $res (call $EVAL (get_local $a2) (get_local $let_env))) - ;; EVAL_RETURN - (call $RELEASE (get_local $let_env))) - (else - ;; EVAL_INVOKE - (set_local $res (call $EVAL_AST (get_local $ast) (get_local $env))) - (set_local $f_args (get_local $res)) - - ;; if error, return f/args for release by caller - (if (get_global $error_type) (return (get_local $f_args))) - - ;; rest - (set_local $args (call $MEM_VAL0_ptr (get_local $f_args))) - ;; value - (set_local $f (call $MEM_VAL1_ptr (get_local $f_args))) - - (set_local $ftype (call $TYPE (get_local $f))) - (if (i32.eq (get_local $ftype) (get_global $FUNCTION_T)) - (then - (set_local $res (call_indirect (type $fnT) (get_local $args) - (call $VAL0 (get_local $f))))) - (else - (call $THROW_STR_1 (STRING "apply of non-function type: %d\n") - (get_local $type)) - (set_local $res (i32.const 0)))) - - (call $RELEASE (get_local $f_args)))))) - - (get_local $res) - ) - - (func $PRINT (param $ast i32) (result i32) - (call $pr_str (get_local $ast)) - ) - - ;; REPL - (func $rep (param $line i32) (param $env i32) (result i32) - (local $mv1 i32) - (local $mv2 i32) - (local $ms i32) - (block $rep_done - (set_local $mv1 (call $READ (get_local $line))) - (if (get_global $error_type) (br $rep_done)) - - (set_local $mv2 (call $EVAL (get_local $mv1) (get_local $env))) - (if (get_global $error_type) (br $rep_done)) - -;; (call $PR_MEMORY (i32.const -1) (i32.const -1)) - (set_local $ms (call $PRINT (get_local $mv2))) - ) - - ;; release memory from MAL_READ and EVAL - (call $RELEASE (get_local $mv2)) - (call $RELEASE (get_local $mv1)) - (get_local $ms) - ) - - (func $add (param $args i32) (result i32) - (call $INTEGER - (i32.add (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) - (call $VAL0 (call $MEM_VAL1_ptr - (call $MEM_VAL0_ptr (get_local $args))))))) - (func $subtract (param $args i32) (result i32) - (call $INTEGER - (i32.sub_s (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) - (call $VAL0 (call $MEM_VAL1_ptr - (call $MEM_VAL0_ptr (get_local $args))))))) - (func $multiply (param $args i32) (result i32) - (call $INTEGER - (i32.mul_s (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) - (call $VAL0 (call $MEM_VAL1_ptr - (call $MEM_VAL0_ptr (get_local $args))))))) - (func $divide (param $args i32) (result i32) - (call $INTEGER - (i32.div_s (call $VAL0 (call $MEM_VAL1_ptr (get_local $args))) - (call $VAL0 (call $MEM_VAL1_ptr - (call $MEM_VAL0_ptr (get_local $args))))))) - (func $pr_memory (param $args i32) (result i32) - (call $PR_MEMORY (i32.const -1) (i32.const -1)) - (call $INC_REF (get_global $NIL))) - - (func $main (result i32) - ;; Constant location/value definitions - (local $line i32) - (local $res i32) - (local $repl_env i32) - - ;; DEBUG - (call $printf_1 (STRING "memoryBase: 0x%x\n") (get_global $memoryBase)) - (call $printf_1 (STRING "heap_start: 0x%x\n") (get_global $heap_start)) - (call $printf_1 (STRING "heap_end: 0x%x\n") (get_global $heap_end)) - (call $printf_1 (STRING "mem: 0x%x\n") (get_global $mem)) -;; (call $printf_1 (STRING "string_mem: %d\n") (get_global $string_mem)) - - (set_local $repl_env (call $ENV_NEW (get_global $NIL))) - - (drop (call $ENV_SET_S (get_local $repl_env) - (STRING "+") (call $FUNCTION (i32.const 0)))) - (drop (call $ENV_SET_S (get_local $repl_env) - (STRING "-") (call $FUNCTION (i32.const 1)))) - (drop (call $ENV_SET_S (get_local $repl_env) - (STRING "*") (call $FUNCTION (i32.const 2)))) - (drop (call $ENV_SET_S (get_local $repl_env) - (STRING "/") (call $FUNCTION (i32.const 3)))) - - (call $PR_MEMORY (i32.const -1) (i32.const -1)) -;; (call $PR_MEMORY_RAW (get_global $mem) -;; (i32.add (get_global $mem) -;; (i32.mul_u (get_global $mem_unused_start) -;; (i32.const 8)))) - - ;; Start - (block $repl_done - (loop $repl_loop - (set_local $line (call $readline (STRING "user> "))) - (if (i32.eqz (get_local $line)) (br $repl_done)) - (if (i32.eq (i32.load8_u (get_local $line)) (i32.const 0)) - (then - (call $free (get_local $line)) - (br $repl_loop))) - (set_local $res (call $rep (get_local $line) (get_local $repl_env))) - (if (get_global $error_type) - (then - (call $printf_1 (STRING "Error: %s\n") (get_global $error_str)) - (set_global $error_type (i32.const 0))) - (else - (call $printf_1 (STRING "%s\n") (call $to_String (get_local $res))))) - (call $RELEASE (get_local $res)) -;; (call $PR_MEMORY (i32.const -1) (i32.const -1)) - (call $free (get_local $line)) - (br $repl_loop))) - - (call $print (STRING "\n")) - (call $PR_MEMORY (i32.const -1) (i32.const -1)) - (i32.const 0) - ) - - - (export "_main" (func $main)) - (export "__post_instantiate" (func $init_memory)) -) - diff --git a/wasm/types.wam b/wasm/types.wam new file mode 100644 index 0000000000..2e809534b9 --- /dev/null +++ b/wasm/types.wam @@ -0,0 +1,186 @@ +;; Mal value memory layout +;; type words +;; ---------- ---------- +;; nil ref/ 0 | 0 | | +;; false ref/ 1 | 0 | | +;; true ref/ 1 | 1 | | +;; integer ref/ 2 | int | | +;; float ref/ 3 | ??? | | +;; string/kw ref/ 4 | string ptr | | +;; symbol ref/ 5 | string ptr | | +;; list ref/ 6 | next mem idx | val mem idx | +;; vector ref/ 7 | next mem idx | val mem idx | +;; hashmap ref/ 8 | next mem idx | key mem idx | val mem idx +;; function ref/ 9 | fn idx | | +;; mal function ref/10 | body mem idx | param mem idx | env mem idx +;; macro fn ref/11 | body mem idx | param mem idx | env mem idx +;; atom ref/12 | val mem idx | | +;; environment ref/13 | hmap mem idx | outer mem idx | +;; metadata ref/14 | obj mem idx | meta mem idx | +;; FREE sz/15 | next mem idx | | + +(module $types + + (global $NIL_T i32 0) + (global $BOOLEAN_T i32 1) + (global $INTEGER_T i32 2) + (global $FLOAT_T i32 3) + (global $STRING_T i32 4) + (global $SYMBOL_T i32 5) + (global $LIST_T i32 6) + (global $VECTOR_T i32 7) + (global $HASHMAP_T i32 8) + (global $FUNCTION_T i32 9) + (global $MALFUNC_T i32 10) + (global $MACRO_T i32 11) + (global $ATOM_T i32 12) + (global $ENVIRONMENT_T i32 13) + (global $METADATA_T i32 14) + (global $FREE_T i32 15) + + (global $error_type (mut i32) 0) + (global $error_val (mut i32) 0) + ;; Index into static string memory (static.wast) + (global $error_str (mut i32) 0) + + (global $NIL (mut i32) 0) + (global $FALSE (mut i32) 0) + (global $TRUE (mut i32) 0) + (global $EMPTY_LIST (mut i32) 0) + (global $EMPTY_VECTOR (mut i32) 0) + (global $EMPTY_HASHMAP (mut i32) 0) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; General functions + + (func $INC_REF (param $mv i32) (result i32) + (i32.store $mv (i32.add (i32.load $mv) 32)) + $mv) + + (func $THROW_STR_0 (param $fmt i32) + (drop ($sprintf_1 (get_global $error_str) $fmt "")) + (set_global $error_type 1)) + + (func $THROW_STR_1 (param $fmt i32) (param $v0 i32) + (drop ($sprintf_1 (get_global $error_str) $fmt $v0)) + (set_global $error_type 1)) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; numeric functions + + (func $INTEGER (param $val i32) (result i32) + ($ALLOC_SCALAR (get_global $INTEGER_T) $val)) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; sequence functions + + (func $MAP_LOOP_START (param $type i32) (result i32) + (local $res i32) + (set_local $res (if i32 (i32.eq $type (get_global $LIST_T)) + (get_global $EMPTY_LIST) + (else (if i32 (i32.eq $type (get_global $VECTOR_T)) + (get_global $EMPTY_VECTOR) + (else (if i32 (i32.eq $type (get_global $HASHMAP_T)) + (get_global $EMPTY_HASHMAP) + (else + ($THROW_STR_1 "read_seq invalid type %d" $type) + 0))))))) + + ($INC_REF $res) + ) + + (func $MAP_LOOP_UPDATE (param $type i32) (param $empty i32) + (param $current i32) (param $val2 i32) (param $val3 i32) + (result i32) + (local $res i32) + + (set_local $res ($ALLOC $type $empty $val2 $val3)) + ;; sequence took ownership + ($RELEASE $empty) + ($RELEASE $val2) + (if (i32.eq $type (get_global $HASHMAP_T)) + ($RELEASE $val3)) + (if (i32.gt_u $current (get_global $EMPTY_HASHMAP)) + ;; if not first element, set current next to point to new element + (i32.store ($VAL0_ptr $current) ($MalVal_index $res))) + + $res + ) + + (func $EMPTY_Q (param $mv i32) (result i32) + (i32.eq ($VAL0 $mv) 0) + ) + + (func $HASHMAP (result i32) + ;; just point to static empty hash-map + ($INC_REF (get_global $EMPTY_HASHMAP)) + ) + + (func $ASSOC1 (param $hm i32) (param $k i32) (param $v i32) (result i32) + (local $res i32) + (set_local $res ($ALLOC (get_global $HASHMAP_T) $hm $k $v)) + ;; we took ownership of previous release + ($RELEASE $hm) + $res + ) + + (func $ASSOC1_S (param $hm i32) (param $k i32) (param $v i32) (result i32) + (local $kmv i32) + (local $res i32) + (set_local $kmv ($STRING (get_global $STRING_T) $k)) + (set_local $res ($ASSOC1 $hm $kmv $v)) + ;; map took ownership of key + ($RELEASE $kmv) + $res + ) + + (func $HASHMAP_GET (param $hm i32) (param $key_mv i32) (result i64) + (local $res i32) + (local $found i32) + (local $key i32) + (local $test_key_mv i32) + + (set_local $key ($to_String $key_mv)) + (set_local $found 0) + + + (block $done + (loop $loop + ;;; if (VAL0(hm) == 0) + (if (i32.eq ($VAL0 $hm) 0) + (then + (set_local $res (get_global $NIL)) + (br $done))) + ;;; test_key_mv = MEM_VAL1(hm) + (set_local $test_key_mv ($MEM_VAL1_ptr $hm)) + ;;; if (strcmp(key, to_String(test_key_mv)) == 0) + (if (i32.eq ($strcmp $key ($to_String $test_key_mv)) 0) + (then + (set_local $found 1) + (set_local $res ($MEM_VAL2_ptr $hm)) + (br $done))) + (set_local $hm ($MEM_VAL0_ptr $hm)) + + (br $loop) + ) + ) + + ;; combine found/res as hi 32/low 32 of i64 + (i64.or (i64.shl_u (i64.extend_u/i32 $found) (i64.const 32)) + (i64.extend_u/i32 $res)) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; function functions + + (func $FUNCTION (param $index i32) (result i32) + ($ALLOC_SCALAR (get_global $FUNCTION_T) $index) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; string functions + + (func $to_String (param $mv i32) (result i32) + ;; skip string refcnt + (i32.add 4 ($MalVal_val ($MalVal_index $mv) 0))) +) diff --git a/wasm/types.wast b/wasm/types.wast deleted file mode 100644 index cd1212c167..0000000000 --- a/wasm/types.wast +++ /dev/null @@ -1,202 +0,0 @@ -;; Mal value memory layout -;; type words -;; ---------- ---------- -;; nil ref/ 0 | 0 | | -;; false ref/ 1 | 0 | | -;; true ref/ 1 | 1 | | -;; integer ref/ 2 | int | | -;; float ref/ 3 | ??? | | -;; string/kw ref/ 4 | string ptr | | -;; symbol ref/ 5 | string ptr | | -;; list ref/ 6 | next mem idx | val mem idx | -;; vector ref/ 7 | next mem idx | val mem idx | -;; hashmap ref/ 8 | next mem idx | key mem idx | val mem idx -;; function ref/ 9 | fn idx | | -;; mal function ref/10 | body mem idx | param mem idx | env mem idx -;; macro fn ref/11 | body mem idx | param mem idx | env mem idx -;; atom ref/12 | val mem idx | | -;; environment ref/13 | hmap mem idx | outer mem idx | -;; metadata ref/14 | obj mem idx | meta mem idx | -;; FREE sz/15 | next mem idx | | - -(module $types - - (global $NIL_T i32 (i32.const 0)) - (global $BOOLEAN_T i32 (i32.const 1)) - (global $INTEGER_T i32 (i32.const 2)) - (global $FLOAT_T i32 (i32.const 3)) - (global $STRING_T i32 (i32.const 4)) - (global $SYMBOL_T i32 (i32.const 5)) - (global $LIST_T i32 (i32.const 6)) - (global $VECTOR_T i32 (i32.const 7)) - (global $HASHMAP_T i32 (i32.const 8)) - (global $FUNCTION_T i32 (i32.const 9)) - (global $MALFUNC_T i32 (i32.const 10)) - (global $MACRO_T i32 (i32.const 11)) - (global $ATOM_T i32 (i32.const 12)) - (global $ENVIRONMENT_T i32 (i32.const 13)) - (global $METADATA_T i32 (i32.const 14)) - (global $FREE_T i32 (i32.const 15)) - - (global $error_type (mut i32) (i32.const 0)) - (global $error_val (mut i32) (i32.const 0)) - ;; Index into static string memory (static.wast) - (global $error_str (mut i32) (i32.const 0)) - - (global $NIL (mut i32) (i32.const 0)) - (global $FALSE (mut i32) (i32.const 0)) - (global $TRUE (mut i32) (i32.const 0)) - (global $EMPTY_LIST (mut i32) (i32.const 0)) - (global $EMPTY_VECTOR (mut i32) (i32.const 0)) - (global $EMPTY_HASHMAP (mut i32) (i32.const 0)) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; General functions - - (func $INC_REF (param $mv i32) (result i32) - (i32.store (get_local $mv) - (i32.add (i32.load (get_local $mv)) - (i32.const 32))) - (get_local $mv)) - - (func $THROW_STR_0 (param $fmt i32) - (drop (call $sprintf_1 (get_global $error_str) (get_local $fmt) (STRING ""))) - (set_global $error_type (i32.const 1))) - - (func $THROW_STR_1 (param $fmt i32) (param $v0 i32) - (drop (call $sprintf_1 (get_global $error_str) (get_local $fmt) (get_local $v0))) - (set_global $error_type (i32.const 1))) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; numeric functions - - (func $INTEGER (param $val i32) (result i32) - (call $ALLOC_SCALAR (get_global $INTEGER_T) (get_local $val))) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; sequence functions - - (func $MAP_LOOP_START (param $type i32) (result i32) - (local $res i32) - (set_local $res (if i32 (i32.eq (get_local $type) - (get_global $LIST_T)) - (get_global $EMPTY_LIST) - (else (if i32 (i32.eq (get_local $type) - (get_global $VECTOR_T)) - (get_global $EMPTY_VECTOR) - (else (if i32 (i32.eq (get_local $type) - (get_global $HASHMAP_T)) - (get_global $EMPTY_HASHMAP) - (else - (call $THROW_STR_1 (STRING "read_seq invalid type %d") - (get_local $type)) - (i32.const 0)))))))) - - (call $INC_REF (get_local $res)) - ) - - (func $MAP_LOOP_UPDATE (param $type i32) (param $empty i32) - (param $current i32) (param $val2 i32) (param $val3 i32) - (result i32) - (local $res i32) - - (set_local $res (call $ALLOC (get_local $type) (get_local $empty) - (get_local $val2) (get_local $val3))) - ;; sequence took ownership - (call $RELEASE (get_local $empty)) - (call $RELEASE (get_local $val2)) - (if (i32.eq (get_local $type) (get_global $HASHMAP_T)) - (call $RELEASE (get_local $val3))) - (if (i32.gt_u (get_local $current) (get_global $EMPTY_HASHMAP)) - ;; if not first element, set current next to point to new element - (i32.store (call $VAL0_ptr (get_local $current)) - (call $MalVal_index (get_local $res)))) - - (get_local $res) - ) - - (func $EMPTY_Q (param $mv i32) (result i32) - (i32.eq (call $VAL0 (get_local $mv)) (i32.const 0)) - ) - - (func $HASHMAP (result i32) - ;; just point to static empty hash-map - (call $INC_REF (get_global $EMPTY_HASHMAP)) - ) - - (func $ASSOC1 (param $hm i32) (param $k i32) (param $v i32) (result i32) - (local $res i32) - (set_local $res (call $ALLOC (get_global $HASHMAP_T) (get_local $hm) - (get_local $k) (get_local $v))) - ;; we took ownership of previous release - (call $RELEASE (get_local $hm)) - (get_local $res) - ) - - (func $ASSOC1_S (param $hm i32) (param $k i32) (param $v i32) (result i32) - (local $kmv i32) - (local $res i32) - (set_local $kmv (call $STRING (get_global $STRING_T) (get_local $k))) - (set_local $res (call $ASSOC1 (get_local $hm) - (get_local $kmv) (get_local $v))) - ;; map took ownership of key - (call $RELEASE (get_local $kmv)) - (get_local $res) - ) - - (func $HASHMAP_GET (param $hm i32) (param $key_mv i32) (result i64) - (local $res i32) - (local $found i32) - (local $key i32) - (local $test_key_mv i32) - - (set_local $key (call $to_String (get_local $key_mv))) - (set_local $found (i32.const 0)) - - - (block $done - (loop $loop - ;;; if (VAL0(hm) == 0) - (if (i32.eq (call $VAL0 (get_local $hm)) (i32.const 0)) - (then - (set_local $res (get_global $NIL)) - (br $done))) - ;;; test_key_mv = MEM_VAL1(hm) - (set_local $test_key_mv (call $MEM_VAL1_ptr (get_local $hm))) - ;;; if (strcmp(key, to_String(test_key_mv)) == 0) - (if (i32.eq (call $strcmp (get_local $key) - (call $to_String (get_local $test_key_mv))) - (i32.const 0)) - (then - (set_local $found (i32.const 1)) - (set_local $res (call $MEM_VAL2_ptr (get_local $hm))) - (br $done))) - (set_local $hm (call $MEM_VAL0_ptr (get_local $hm))) - - (br $loop) - ) - ) - - ;; combine found/res as hi 32/low 32 of i64 - (i64.or - (i64.shl_u (i64.extend_u/i32 (get_local $found)) - (i64.const 32)) - (i64.extend_u/i32 (get_local $res))) - ) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; function functions - - (func $FUNCTION (param $index i32) (result i32) - (call $ALLOC_SCALAR (get_global $FUNCTION_T) (get_local $index)) - ) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; string functions - - (func $to_String (param $mv i32) (result i32) - (i32.add (i32.const 4) ;; skip string refcnt - (call $MalVal_val - (call $MalVal_index (get_local $mv)) - (i32.const 0)))) -) diff --git a/wasm/util.wam b/wasm/util.wam new file mode 100644 index 0000000000..e663d4adb0 --- /dev/null +++ b/wasm/util.wam @@ -0,0 +1,254 @@ +(module $util + (import "env" "malloc" (func $malloc (param i32) (result i32))) + (import "env" "free" (func $free (param i32))) + (import "env" "exit" (func $exit (param i32))) + + (import "env" "stdout" (global $stdout i32)) + (import "env" "putchar" (func $putchar (param i32) (result i32))) + (import "env" "fputs" (func $fputs (param i32 i32) (result i32))) + ;;(import "env" "readline" (func $readline (param i32) (result i32))) + (import "libedit.so" "readline" (func $readline (param i32) (result i32))) + ;;(import "libreadline.so" "readline" (func $readline (param i32) (result i32))) + + (global $sprintf_buf (mut i32) 0) + + (func $init_sprintf_mem + ;; 256 character sprintf static buffer + (set_global $sprintf_buf " ") + ) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ;; Copy len chatacters from src to dst + ;; Returns len + (func $MEM_COPY (param $dst i32) (param $src i32) (param $len i32) + (local $idx i32) + (set_local $idx 0) + (loop $copy + (i32.store8_u (i32.add $idx $dst) + (i32.load8_u (i32.add $idx $src))) + (set_local $idx (i32.add 1 $idx)) + (br_if $copy (i32.lt_u $idx $len)) + ) + ) + + (func $STRING_LEN (param $str i32) (result i32) + (local $cur i32) + (set_local $cur $str) + (loop $count + (if (i32.ne 0 (i32.load8_u $cur)) + (then + (set_local $cur (i32.add $cur 1)) + (br $count))) + ) + (i32.sub_u $cur $str) + ) + + (func $ATOI (param $str i32) (result i32) + (local $acc i32) + (local $i i32) + (local $neg i32) + (local $ch i32) + (set_local $acc 0) + (set_local $i 0) + (set_local $neg 0) + (block $done + (loop $loop + (set_local $ch (i32.load8_u (i32.add $str $i))) + (if (i32.and (i32.ne $ch (CHR "-")) + (i32.or (i32.lt_u $ch (CHR "0")) + (i32.gt_u $ch (CHR "9")))) + (br $done)) + (set_local $i (i32.add $i 1)) + (if (i32.eq $ch (CHR "-")) + (then + (set_local $neg 1)) + (else + (set_local $acc (i32.add (i32.mul_u $acc 10) + (i32.sub_u $ch (CHR "0")))))) + (br $loop) + ) + ) + (if i32 $neg + (then (i32.sub_s 0 $acc)) + (else $acc)) + ) + + (func $strcmp (param $s1 i32) (param $s2 i32) (result i32) + (block $done + (loop $loop + (if (i32.or (i32.eqz (i32.load8_u $s1)) + (i32.eqz (i32.load8_u $s2))) + (br $done)) + (if (i32.ne (i32.load8_u $s1) + (i32.load8_u $s2)) + (br $done)) + (set_local $s1 (i32.add $s1 1)) + (set_local $s2 (i32.add $s2 1)) + (br $loop) + ) + ) + (if i32 (i32.eq (i32.load8_u $s1) + (i32.load8_u $s2)) + (then 0) + (else + (if i32 (i32.lt_u (i32.load8_u $s1) + (i32.load8_u $s2)) + (then -1) + (else 1)))) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $print (param $addr i32) + (drop ($fputs $addr (get_global $stdout)))) + + (func $printf_1 (param $fmt i32) + (param $v0 i32) + (drop ($sprintf_6 (get_global $sprintf_buf) $fmt $v0 0 0 0 0 0)) + ($print (get_global $sprintf_buf)) + ) + + (func $printf_2 (param $fmt i32) + (param $v0 i32) (param $v1 i32) + (drop ($sprintf_6 (get_global $sprintf_buf) $fmt $v0 $v1 0 0 0 0)) + ($print (get_global $sprintf_buf)) + ) + + (func $printf_3 (param $fmt i32) + (param $v0 i32) (param $v1 i32) (param $v2 i32) + (drop ($sprintf_6 (get_global $sprintf_buf) $fmt $v0 $v1 $v2 0 0 0)) + ($print (get_global $sprintf_buf)) + ) + + (func $printf_4 (param $fmt i32) + (param $v0 i32) (param $v1 i32) (param $v2 i32) + (param $v3 i32) + (drop ($sprintf_6 (get_global $sprintf_buf) $fmt $v0 $v1 $v2 $v3 0 0)) + ($print (get_global $sprintf_buf)) + ) + + (func $printf_5 (param $fmt i32) + (param $v0 i32) (param $v1 i32) (param $v2 i32) + (param $v3 i32) (param $v4 i32) + (drop ($sprintf_6 (get_global $sprintf_buf) $fmt $v0 $v1 $v2 $v3 $v4 0)) + ($print (get_global $sprintf_buf)) + ) + + (func $printf_6 (param $fmt i32) + (param $v0 i32) (param $v1 i32) (param $v2 i32) + (param $v3 i32) (param $v4 i32) (param $v5 i32) + (drop ($sprintf_6 (get_global $sprintf_buf) $fmt $v0 $v1 $v2 $v3 $v4 $v5)) + ($print (get_global $sprintf_buf)) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $_sprintdigit (param $str i32) (param $num i32) (param $base i32) + (local $n i32) + (local $ch i32) + (set_local $n (i32.rem_u $num $base)) + (set_local $ch (if (result i32) (i32.lt_u $n 10) + 48 + 55)) + (i32.store8_u $str (i32.add $n $ch)) + ) + + ;; TODO: switch to snprint* (add buffer len) + (func $_sprintnum (param $str i32) (param $num i32) (param $base i32) + (result i32) + (if (i32.and (i32.eq $base 10) + (i32.lt_s $num 0)) + (then + ;; Print '-' if negative + (i32.store8_u $str (CHR "-")) + (set_local $str (i32.add $str 1)) + ;; Reverse the sign + (set_local $num (i32.sub_s 0 $num)))) + (if (i32.gt_u (i32.div_u $num $base) 0) + (set_local + $str + ($_sprintnum $str (i32.div_u $num $base) $base))) + ($_sprintdigit $str $num $base) + (i32.add 1 $str) + ) + + ;; TODO: switch to snprint* (add buffer len) + (func $sprintf_1 (param $str i32) (param $fmt i32) + (param $v0 i32) (result i32) + ($sprintf_6 $str $fmt $v0 0 0 0 0 0) + ) + + (func $sprintf_6 (param $str i32) (param $fmt i32) + (param $v0 i32) (param $v1 i32) (param $v2 i32) + (param $v3 i32) (param $v4 i32) (param $v5 i32) + (result i32) + (local $ch i32) + (local $pstr i32) + (local $v i32) + (local $vidx i32) + (local $len i32) + (set_local $pstr $str) + (set_local $vidx 0) + + (block $done + (loop $loop + (block $after_v + (block (block (block (block (block (block + (br_table 0 1 2 3 4 5 0 $vidx)) + (; 0 ;) (set_local $v $v0) (br $after_v)) + (; 1 ;) (set_local $v $v1) (br $after_v)) + (; 2 ;) (set_local $v $v2) (br $after_v)) + (; 3 ;) (set_local $v $v3) (br $after_v)) + (; 4 ;) (set_local $v $v4) (br $after_v)) + (; 5 ;) (set_local $v $v5) (br $after_v) + ) + + ;;; while ((ch=*(fmt++))) + (set_local $ch (i32.load8_u $fmt)) + (set_local $fmt (i32.add 1 $fmt)) + (if (i32.eqz $ch) (br $done)) + ;; TODO: check buffer length + + (if (i32.ne $ch (CHR "%")) + (then + ;; TODO: check buffer length + (i32.store8_u $pstr $ch) + (set_local $pstr (i32.add 1 $pstr)) + (br $loop))) + + ;;; ch=*(fmt++) + (set_local $ch (i32.load8_u $fmt)) + (set_local $fmt (i32.add 1 $fmt)) + + (if (i32.eq (CHR "d") $ch) + (then + (set_local $pstr ($_sprintnum $pstr $v 10))) + (else (if (i32.eq (CHR "x") $ch) + (then + (set_local $pstr ($_sprintnum $pstr $v 10))) + (else (if (i32.eq (CHR "s") $ch) + (then + (set_local $len ($STRING_LEN $v)) + ($MEM_COPY $pstr $v $len) + (set_local $pstr (i32.add $pstr $len))) + (else (if (i32.eq (CHR "c") $ch) + (then + (i32.store8_u $pstr $v) + (set_local $pstr (i32.add $pstr 1))) + (else + ($print "Illegal format character: ") + (drop ($putchar $ch)) + (drop ($putchar (CHR "\n"))) + ($exit 3))))))))) + + (set_local $vidx (i32.add 1 $vidx)) + (br $loop) + ) + ) + + (i32.store8_u $pstr (CHR "\x00")) + $pstr + ) + +) + diff --git a/wasm/util.wast b/wasm/util.wast deleted file mode 100644 index acbe12285a..0000000000 --- a/wasm/util.wast +++ /dev/null @@ -1,275 +0,0 @@ -(module $util - (import "env" "malloc" (func $malloc (param i32) (result i32))) - (import "env" "free" (func $free (param i32))) - (import "env" "exit" (func $exit (param i32))) - - (import "env" "stdout" (global $stdout i32)) - (import "env" "putchar" (func $putchar (param i32) (result i32))) - (import "env" "fputs" (func $fputs (param i32 i32) (result i32))) - ;;(import "env" "readline" (func $readline (param i32) (result i32))) - (import "libedit.so" "readline" (func $readline (param i32) (result i32))) - ;;(import "libreadline.so" "readline" (func $readline (param i32) (result i32))) - - (global $sprintf_buf (mut i32) (i32.const 0)) - - (func $init_sprintf_mem - ;; 256 character sprintf static buffer - (set_global $sprintf_buf (STRING " ")) - ) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - ;; Copy len chatacters from src to dst - ;; Returns len - (func $MEM_COPY (param $dst i32) (param $src i32) (param $len i32) - (local $idx i32) - (set_local $idx (i32.const 0)) - (loop $copy - (i32.store8_u (i32.add (get_local $idx) (get_local $dst)) - (i32.load8_u (i32.add (get_local $idx) - (get_local $src)))) - (set_local $idx (i32.add (i32.const 1) (get_local $idx))) - (br_if $copy (i32.lt_u (get_local $idx) (get_local $len))) - ) - ) - - (func $STRING_LEN (param $str i32) (result i32) - (local $cur i32) - (set_local $cur (get_local $str)) - (loop $count - (if (i32.ne (i32.const 0) (i32.load8_u (get_local $cur))) - (then - (set_local $cur (i32.add (get_local $cur) (i32.const 1))) - (br $count))) - ) - (i32.sub_u (get_local $cur) (get_local $str)) - ) - - (func $ATOI (param $str i32) (result i32) - (local $acc i32) - (local $i i32) - (local $neg i32) - (local $ch i32) - (set_local $acc (i32.const 0)) - (set_local $i (i32.const 0)) - (set_local $neg (i32.const 0)) - (block $done - (loop $loop - (set_local $ch (i32.load8_u (i32.add (get_local $str) - (get_local $i)))) - (if (i32.and (i32.ne (get_local $ch) (CHAR '-')) - (i32.or (i32.lt_u (get_local $ch) (CHAR '0')) - (i32.gt_u (get_local $ch) (CHAR '9')))) - (br $done)) - (set_local $i (i32.add (get_local $i) (i32.const 1))) - (if (i32.eq (get_local $ch) (CHAR '-')) - (then - (set_local $neg (i32.const 1))) - (else - (set_local $acc (i32.add (i32.mul_u (get_local $acc) (i32.const 10)) - (i32.sub_u (get_local $ch) (CHAR '0')))))) - (br $loop) - ) - ) - (if i32 (get_local $neg) - (then (i32.sub_s (i32.const 0) (get_local $acc))) - (else (get_local $acc))) - ) - - (func $strcmp (param $s1 i32) (param $s2 i32) (result i32) - (block $done - (loop $loop - (if (i32.or (i32.eqz (i32.load8_u (get_local $s1))) - (i32.eqz (i32.load8_u (get_local $s2)))) - (br $done)) - (if (i32.ne (i32.load8_u (get_local $s1)) - (i32.load8_u (get_local $s2))) - (br $done)) - (set_local $s1 (i32.add (get_local $s1) (i32.const 1))) - (set_local $s2 (i32.add (get_local $s2) (i32.const 1))) - (br $loop) - ) - ) - (if i32 (i32.eq (i32.load8_u (get_local $s1)) - (i32.load8_u (get_local $s2))) - (then (i32.const 0)) - (else - (if i32 (i32.lt_u (i32.load8_u (get_local $s1)) - (i32.load8_u (get_local $s2))) - (then (i32.const -1)) - (else (i32.const 1))))) - ) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - (func $print (param $addr i32) - (drop (call $fputs (get_local $addr) (get_global $stdout)))) - - (func $printf_1 (param $fmt i32) - (param $v0 i32) - (drop (call $sprintf_6 (get_global $sprintf_buf) (get_local $fmt) - (get_local $v0) (i32.const 0) (i32.const 0) - (i32.const 0) (i32.const 0) (i32.const 0))) - (call $print (get_global $sprintf_buf)) - ) - - (func $printf_2 (param $fmt i32) - (param $v0 i32) (param $v1 i32) - (drop (call $sprintf_6 (get_global $sprintf_buf) (get_local $fmt) - (get_local $v0) (get_local $v1) (i32.const 0) - (i32.const 0) (i32.const 0) (i32.const 0))) - (call $print (get_global $sprintf_buf)) - ) - - (func $printf_3 (param $fmt i32) - (param $v0 i32) (param $v1 i32) (param $v2 i32) - (drop (call $sprintf_6 (get_global $sprintf_buf) (get_local $fmt) - (get_local $v0) (get_local $v1) (get_local $v2) - (i32.const 0) (i32.const 0) (i32.const 0))) - (call $print (get_global $sprintf_buf)) - ) - - (func $printf_4 (param $fmt i32) - (param $v0 i32) (param $v1 i32) (param $v2 i32) - (param $v3 i32) - (drop (call $sprintf_6 (get_global $sprintf_buf) (get_local $fmt) - (get_local $v0) (get_local $v1) (get_local $v2) - (get_local $v3) (i32.const 0) (i32.const 0))) - (call $print (get_global $sprintf_buf)) - ) - - (func $printf_5 (param $fmt i32) - (param $v0 i32) (param $v1 i32) (param $v2 i32) - (param $v3 i32) (param $v4 i32) - (drop (call $sprintf_6 (get_global $sprintf_buf) (get_local $fmt) - (get_local $v0) (get_local $v1) (get_local $v2) - (get_local $v3) (get_local $v4) (i32.const 0))) - (call $print (get_global $sprintf_buf)) - ) - - (func $printf_6 (param $fmt i32) - (param $v0 i32) (param $v1 i32) (param $v2 i32) - (param $v3 i32) (param $v4 i32) (param $v5 i32) - (drop (call $sprintf_6 (get_global $sprintf_buf) (get_local $fmt) - (get_local $v0) (get_local $v1) (get_local $v2) - (get_local $v3) (get_local $v4) (get_local $v5))) - (call $print (get_global $sprintf_buf)) - ) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - (func $_sprintdigit (param $str i32) (param $num i32) (param $base i32) - (local $n i32) - (local $ch i32) - (set_local $n (i32.rem_u (get_local $num) (get_local $base))) - (set_local $ch (if (result i32) (i32.lt_u (get_local $n) (i32.const 10)) - (i32.const 48) - (i32.const 55))) - (i32.store8_u (get_local $str) (i32.add (get_local $n) (get_local $ch))) - ) - - ;; TODO: switch to snprint* (add buffer len) - (func $_sprintnum (param $str i32) (param $num i32) (param $base i32) - (result i32) - (if (i32.and (i32.eq (get_local $base) (i32.const 10)) - (i32.lt_s (get_local $num) (i32.const 0))) - (then - ;; Print '-' if negative - (i32.store8_u (get_local $str) (CHAR '-')) - (set_local $str (i32.add (get_local $str) (i32.const 1))) - ;; Reverse the sign - (set_local $num (i32.sub_s (i32.const 0) (get_local $num))))) - (if (i32.gt_u (i32.div_u (get_local $num) (get_local $base)) - (i32.const 0)) - (set_local - $str - (call $_sprintnum (get_local $str) - (i32.div_u (get_local $num) (get_local $base)) - (get_local $base)))) - (call $_sprintdigit (get_local $str) (get_local $num) (get_local $base)) - (i32.add (i32.const 1) (get_local $str)) - ) - - ;; TODO: switch to snprint* (add buffer len) - (func $sprintf_1 (param $str i32) (param $fmt i32) - (param $v0 i32) (result i32) - (call $sprintf_6 (get_local $str) (get_local $fmt) - (get_local $v0) (i32.const 0) (i32.const 0) - (i32.const 0) (i32.const 0) (i32.const 0)) - ) - - (func $sprintf_6 (param $str i32) (param $fmt i32) - (param $v0 i32) (param $v1 i32) (param $v2 i32) - (param $v3 i32) (param $v4 i32) (param $v5 i32) - (result i32) - (local $ch i32) - (local $pstr i32) - (local $v i32) - (local $vidx i32) - (local $len i32) - (set_local $pstr (get_local $str)) - (set_local $vidx (i32.const 0)) - - (block $done - (loop $loop - (block $after_v - (block (block (block (block (block (block - (br_table 0 1 2 3 4 5 0 (get_local $vidx))) - (; 0 ;) (set_local $v (get_local $v0)) (br $after_v)) - (; 1 ;) (set_local $v (get_local $v1)) (br $after_v)) - (; 2 ;) (set_local $v (get_local $v2)) (br $after_v)) - (; 3 ;) (set_local $v (get_local $v3)) (br $after_v)) - (; 4 ;) (set_local $v (get_local $v4)) (br $after_v)) - (; 5 ;) (set_local $v (get_local $v5)) (br $after_v) - ) - - ;;; while ((ch=*(fmt++))) - (set_local $ch (i32.load8_u (get_local $fmt))) - (set_local $fmt (i32.add (i32.const 1) (get_local $fmt))) - (if (i32.eqz (get_local $ch)) (br $done)) - ;; TODO: check buffer length - - (if (i32.ne (get_local $ch) (CHAR '%')) - (then - ;; TODO: check buffer length - (i32.store8_u (get_local $pstr) (get_local $ch)) - (set_local $pstr (i32.add (i32.const 1) (get_local $pstr))) - (br $loop))) - - ;;; ch=*(fmt++) - (set_local $ch (i32.load8_u (get_local $fmt))) - (set_local $fmt (i32.add (i32.const 1) (get_local $fmt))) - - (if (i32.eq (CHAR 'd') (get_local $ch)) - (then - (set_local $pstr (call $_sprintnum (get_local $pstr) - (get_local $v) (i32.const 10)))) - (else (if (i32.eq (CHAR 'x') (get_local $ch)) - (then - (set_local $pstr (call $_sprintnum (get_local $pstr) - (get_local $v) (i32.const 16)))) - (else (if (i32.eq (CHAR 's') (get_local $ch)) - (then - (set_local $len (call $STRING_LEN (get_local $v))) - (call $MEM_COPY (get_local $pstr) (get_local $v) (get_local $len)) - (set_local $pstr (i32.add (get_local $pstr) (get_local $len)))) - (else (if (i32.eq (CHAR 'c') (get_local $ch)) - (then - (i32.store8_u (get_local $pstr) (get_local $v)) - (set_local $pstr (i32.add (get_local $pstr) (i32.const 1)))) - (else - (call $print (STRING "Illegal format character: ")) - (drop (call $putchar (get_local $ch))) - (drop (call $putchar (CHAR '\n'))) - (call $exit (i32.const 3)))))))))) - - (set_local $vidx (i32.add (i32.const 1) (get_local $vidx))) - (br $loop) - ) - ) - - (i32.store8_u (get_local $pstr) (CHAR '\x00')) - (get_local $pstr) - ) - -) - diff --git a/wasm/wastpp.py b/wasm/wastpp.py deleted file mode 100755 index 22266dfcf9..0000000000 --- a/wasm/wastpp.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env python3 - -from itertools import tee -from ast import literal_eval -import os -import pprint -import re -import sys - -def pairwise(iterable): - "s -> (s0,s1), (s1,s2), (s2, s3), ..." - a, b = tee(iterable) - next(b, None) - return zip(a, b) - -def _escape(s): - return s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n') - - -tokre = re.compile(r"""([\s][\s]*|[(];|;[)]|[\[\]{}()`~^@]|'(?:[\\].|[^\\'])*'?|"(?:[\\].|[^\\"])*"?|;;.*|[^\s\[\]{}()'"`@,;]+)""") - -file_tokens = [] -strings = [] -string_map = {} - -depth = 0 -module = None -type = None - -for f in sys.argv[1:]: - content = open(f).read() - tokens = [t for t in re.findall(tokre, content)] - #print(tokens[0:100], file=sys.stderr) - pairs = ["".join(p) for p in pairwise(tokens)] - pairs.append("") - - index = 0 - while index < len(tokens): - token = tokens[index] - pair = pairs[index] - if pair in ["(STRING", "(CHAR"]: - arg = tokens[index+3] - #print("arg: %s" % arg, file=sys.stderr) - if tokens[index+4] != ')': - raise Exception("Invalid %s) macro, too many/few args" % pair) - if arg.startswith('"') and arg.endswith('"'): - pass - elif arg.startswith("'") and arg.endswith("'"): - pass - else: - raise Exception ("Invalid %s) macro, invalid string arg" % pair) - if pair == "(STRING": - str = literal_eval(arg) - if str in string_map: - # Duplicate string, re-use address - file_tokens.append("(i32.add (get_global $memoryBase) (get_global %s))" % string_map[str]) - else: - str_name = "$S_STRING_%d" % len(strings) - file_tokens.append("(i32.add (get_global $memoryBase) (get_global %s))" % str_name) - strings.append(str) - string_map[str] = str_name - if pair == "(CHAR": - c = literal_eval(arg) - if len(c) != 1: - raise Exception ("Invalid (CHAR) macro, must be 1 character") - file_tokens.append("(i32.const 0x%x (; %s ;))" % (ord(c), arg)) - # Skip the rest of the macro - index += 5 - continue - index += 1 - if token == '(': - depth += 1 - if token == ')': - depth -= 1 - if depth == 0: - module = None - if token == ')': continue - if depth == 1: - type = None - if pair == '(module': - index += 1 - continue - if token.startswith('$'): - module = token[1:] - #print("module:", module, file=sys.stderr) - file_tokens.append('\n ;;\n ;; module "%s"\n ;;\n' % module) - continue - if depth == 2: - if token == '(': - type = tokens[index] - if type == 'data': - raise Exception("User data section not supported") - #print(" type:", type, file=sys.stderr) - file_tokens.append(token) - -# TODO: remove duplicates -# Create data section with static strings -string_tokens = [] -if strings: - string_tokens.append(" (data\n (get_global $memoryBase)\n") - string_offset = 0 - for string in strings: - string_tokens.append(' %-30s ;; %d\n' % ( - '"'+_escape(string)+'\\00"', string_offset)) - string_offset += len(string)+1 - string_tokens.append(" )\n\n") - - # Create string names/pointers - string_offset = 0 - for index, string in enumerate(strings): - string_tokens.append(' (global $S_STRING_%d i32 (i32.const %d))\n' % ( - index, string_offset)) - string_offset += len(string)+1 - # Terminator so we know how much memory we took - string_tokens.append(' (global $S_STRING_END i32 (i32.const %d))\n' % ( - string_offset)) - -all_tokens = ["(module\n"] -all_tokens.extend(string_tokens) -all_tokens.extend(file_tokens) -all_tokens.append("\n)") - -print("".join(all_tokens)) From 3ea098865215e726a1fb2008fd050bf66cd4c4f3 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sun, 14 Oct 2018 16:44:08 -0500 Subject: [PATCH 0714/2308] wasm: Steps 4-A, hashmaps, metadata. - Use more concise param and local syntax. - Separate out string.wam. - Add pr_str_seq functionality and add in print_readably functionality. - Add REPLACE3 function. - Remove use of putchar and simplify util.wam - Move string functions from util.wam to string.wam including some renames to be more consistent with C library names. - Add temporary content to 256 character buffers so that they are de-duplicated/combined. - Use AND/OR macros. - Improve Makefile deps and use more general wasm target. - Properly cleanup memory in try* and use TCO as part of try*. --- wasm/Makefile | 17 +- wasm/core.wam | 754 ++++++++++++++++++++++++++++++++++++++ wasm/debug.wam | 103 +++++- wasm/env.wam | 62 +++- wasm/mem.wam | 122 +++--- wasm/printer.wam | 101 +++-- wasm/reader.wam | 187 ++++++---- wasm/step0_repl.wam | 4 +- wasm/step1_read_print.wam | 28 +- wasm/step2_eval.wam | 61 ++- wasm/step3_env.wam | 70 ++-- wasm/step4_if_fn_do.wam | 325 ++++++++++++++++ wasm/step5_tco.wam | 374 +++++++++++++++++++ wasm/step6_file.wam | 430 ++++++++++++++++++++++ wasm/step7_quote.wam | 494 +++++++++++++++++++++++++ wasm/step8_macros.wam | 580 +++++++++++++++++++++++++++++ wasm/step9_try.wam | 630 +++++++++++++++++++++++++++++++ wasm/stepA_mal.wam | 634 ++++++++++++++++++++++++++++++++ wasm/string.wam | 226 ++++++++++++ wasm/types.wam | 223 ++++++++++- wasm/util.wam | 215 +++++------ 21 files changed, 5203 insertions(+), 437 deletions(-) create mode 100644 wasm/core.wam create mode 100644 wasm/step4_if_fn_do.wam create mode 100644 wasm/step5_tco.wam create mode 100644 wasm/step6_file.wam create mode 100644 wasm/step7_quote.wam create mode 100644 wasm/step8_macros.wam create mode 100644 wasm/step9_try.wam create mode 100644 wasm/stepA_mal.wam create mode 100644 wasm/string.wam diff --git a/wasm/Makefile b/wasm/Makefile index aeac222726..e44953e0f2 100644 --- a/wasm/Makefile +++ b/wasm/Makefile @@ -1,7 +1,7 @@ -STEP0_DEPS = util.wam +STEP0_DEPS = string.wam util.wam STEP1_DEPS = $(STEP0_DEPS) types.wam mem.wam debug.wam reader.wam printer.wam -STEP2_DEPS = $(STEP1_DEPS) -STEP3_DEPS = $(STEP2_DEPS) env.wam +STEP3_DEPS = $(STEP1_DEPS) env.wam +STEP4_DEPS = $(STEP3_DEPS) core.wam STEPS = step0_repl step1_read_print step2_eval step3_env \ step4_if_fn_do step5_tco step6_file step7_quote \ @@ -9,14 +9,15 @@ STEPS = step0_repl step1_read_print step2_eval step3_env \ all: $(foreach s,$(STEPS),$(s).wasm) -%.wasm: +%.wasm: %.wam wamp $^ > $*.wast wasm-as $*.wast -o $@ -step0_repl.wasm: $(STEP0_DEPS) step0_repl.wam -step1_read_print.wasm: $(STEP1_DEPS) step1_read_print.wam -step2_eval.wasm: $(STEP2_DEPS) step2_eval.wam -step3_env.wasm: $(STEP3_DEPS) step3_env.wam +step0_repl.wasm: $(STEP0_DEPS) +step1_read_print.wasm step2_eval.wasm: $(STEP1_DEPS) +step3_env.wasm: $(STEP3_DEPS) +step4_if_fn_do.wasm step5_tco.wasm step6_file.wasm: $(STEP4_DEPS) +step7_quote.wasm step8_macros.wasm step9_try.wasm stepA_mal.wasm: $(STEP4_DEPS) .PHONY: clean diff --git a/wasm/core.wam b/wasm/core.wam new file mode 100644 index 0000000000..ed73dc8138 --- /dev/null +++ b/wasm/core.wam @@ -0,0 +1,754 @@ +(module $core + + + ;; it would be nice to have this in types.wam but it uses + ;; ENV_NEW_BINDS which is not available until step3 but types is + ;; used in step1 + + (func $APPLY (param $f i32) (param $args i32) (result i32) + (local $res i32 $env i32 $ftype i32 $a i32) + (set_local $f ($DEREF_META $f)) + (set_local $ftype ($TYPE $f)) + (if (i32.eq $ftype (get_global $FUNCTION_T)) + (then + ;; Must be kept in sync with EVAL's FUNCTION_T evaluation + (if (i32.eq ($VAL0 $f) 0) ;; eval + (then + (set_local $res ($EVAL ($MEM_VAL1_ptr $args) + (get_global $repl_env)))) + (else + (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f)))))) + (else (if (OR (i32.eq $ftype (get_global $MALFUNC_T)) + (i32.eq $ftype (get_global $MACRO_T))) + (then + ;; create new environment using env and params stored in function + (set_local $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + ($MEM_VAL1_ptr $f) $args)) + + ;; claim the AST before releasing the list containing it + (set_local $a ($MEM_VAL0_ptr $f)) + (drop ($INC_REF $a)) + + (set_local $res ($EVAL $a $env)) + + ($RELEASE $env) + ($RELEASE $a)) + (else + ($THROW_STR_1 "APPLY of non-function type: %d\n" $ftype) + (set_local $res 0))))) + $res + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; core functions + + (type $fnT (func (param i32) (result i32))) + + (func $equal_Q (param $args i32) (result i32) + ($TRUE_FALSE ($EQUAL_Q ($MEM_VAL1_ptr $args) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))))) + + (func $throw (param $args i32) (result i32) + (set_global $error_type 2) + (set_global $error_val ($INC_REF ($MEM_VAL1_ptr $args))) + 0 + ) + + (func $nil_Q (param $args i32) (result i32) + ($TRUE_FALSE (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) + (get_global $NIL_T)))) + (func $true_Q (param $args i32) (result i32) + (local $ast i32) + (set_local $ast ($MEM_VAL1_ptr $args)) + ($TRUE_FALSE (AND (i32.eq ($TYPE $ast) (get_global $BOOLEAN_T)) + (i32.eq ($VAL0 $ast) 1))) + ) + (func $false_Q (param $args i32) (result i32) + (local $ast i32) + (set_local $ast ($MEM_VAL1_ptr $args)) + ($TRUE_FALSE (AND (i32.eq ($TYPE $ast) (get_global $BOOLEAN_T)) + (i32.eq ($VAL0 $ast) 0))) + ) + (func $number_Q (param $args i32) (result i32) + ($TRUE_FALSE (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) + (get_global $INTEGER_T)))) + (func $string_Q (param $args i32) (result i32) + (local $mv i32) + (set_local $mv ($MEM_VAL1_ptr $args)) + ($TRUE_FALSE (AND (i32.eq ($TYPE $mv) (get_global $STRING_T)) + (i32.ne (i32.load8_u ($to_String $mv)) + (CHR "\x7f")))) + ) + + (func $keyword (param $args i32) (result i32) + (local $str i32) + (set_local $str ($to_String ($MEM_VAL1_ptr $args))) + (if i32 (i32.eq (i32.load8_u $str) (CHR "\x7f")) + (then ($INC_REF ($MEM_VAL1_ptr $args))) + (else + (drop ($sprintf_1 (get_global $util_buf) "\x7f%s" $str)) + ($STRING (get_global $STRING_T) (get_global $util_buf)))) + ) + + (func $keyword_Q (param $args i32) (result i32) + (local $ast i32) + (set_local $ast ($MEM_VAL1_ptr $args)) + ($TRUE_FALSE (AND (i32.eq ($TYPE $ast) (get_global $STRING_T)) + (i32.eq (i32.load8_u ($to_String $ast)) + (CHR "\x7f")))) + ) + (func $fn_Q (param $args i32) (result i32) + (local $type i32) + (set_local $type ($TYPE ($MEM_VAL1_ptr $args))) + ($TRUE_FALSE (OR (i32.eq $type (get_global $FUNCTION_T)) + (i32.eq $type (get_global $MALFUNC_T))))) + (func $macro_Q (param $args i32) (result i32) + ($TRUE_FALSE (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) + (get_global $MACRO_T)))) + + (func $symbol (param $args i32) (result i32) + ($STRING (get_global $SYMBOL_T) ($to_String ($MEM_VAL1_ptr $args)))) + + (func $symbol_Q (param $args i32) (result i32) + ($TRUE_FALSE (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) + (get_global $SYMBOL_T)))) + + (func $core_pr_str (param $args i32) (result i32) + ($pr_str_seq $args 1 " ")) + (func $str (param $args i32) (result i32) + ($pr_str_seq $args 0 "")) + (func $prn (param $args i32) (result i32) + (local $res i32) + (set_local $res ($pr_str_seq $args 1 " ")) + ($printf_1 "%s\n" ($to_String $res)) + ($RELEASE $res) + ($INC_REF (get_global $NIL)) + ) + (func $println (param $args i32) (result i32) + (local $res i32) + (set_local $res ($pr_str_seq $args 0 " ")) + ($printf_1 "%s\n" ($to_String $res)) + ($RELEASE $res) + ($INC_REF (get_global $NIL)) + ) + + (func $core_readline (param $args i32) (result i32) + (local $line i32 $mv i32) + (set_local $line ($readline ($to_String ($MEM_VAL1_ptr $args)))) + (if (i32.eqz $line) (return ($INC_REF (get_global $NIL)))) + (set_local $mv ($STRING (get_global $STRING_T) $line)) + ($free $line) + $mv + ) + + (func $read_string (param $args i32) (result i32) + ($read_str ($to_String ($MEM_VAL1_ptr $args)))) + + (func $slurp (param $args i32) (result i32) + (local $content i32 $mv i32) + (set_local $content ($read_file ($to_String ($MEM_VAL1_ptr $args)))) + (if (i32.le_s $content 0) + (then + ($THROW_STR_1 "failed to read file '%s'" ($to_String ($MEM_VAL1_ptr $args))) + (return ($INC_REF (get_global $NIL))))) + (set_local $mv ($STRING (get_global $STRING_T) $content)) + ($free $content) + $mv + ) + + (func $lt (param $args i32) (result i32) + ($TRUE_FALSE + (i32.lt_s ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + (func $lte (param $args i32) (result i32) + ($TRUE_FALSE + (i32.le_s ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + (func $gt (param $args i32) (result i32) + ($TRUE_FALSE + (i32.gt_s ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + (func $gte (param $args i32) (result i32) + ($TRUE_FALSE + (i32.ge_s ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + (func $add (param $args i32) (result i32) + ($INTEGER + (i32.add ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + (func $subtract (param $args i32) (result i32) + ($INTEGER + (i32.sub_s ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + (func $multiply (param $args i32) (result i32) + ($INTEGER + (i32.mul_s ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + (func $divide (param $args i32) (result i32) + ($INTEGER + (i32.div_s ($VAL0 ($MEM_VAL1_ptr $args)) + ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) + + (func $time_ms (param $args i32) (result i32) + ($INTEGER ($get_time_ms))) + + ;;; + + (func $list (param $args i32) (result i32) + ($INC_REF $args)) + + (func $list_Q (param $args i32) (result i32) + ($TRUE_FALSE (i32.eq ($TYPE ($DEREF_META ($MEM_VAL1_ptr $args))) + (get_global $LIST_T)))) + + (func $vector (param $args i32) (result i32) + ($FORCE_SEQ_TYPE (get_global $VECTOR_T) $args)) + + (func $vector_Q (param $args i32) (result i32) + ($TRUE_FALSE (i32.eq ($TYPE ($DEREF_META ($MEM_VAL1_ptr $args))) + (get_global $VECTOR_T)))) + + (func $hash_map (param $args i32) (result i32) + (local $res i32 $type i32 $val2 i32 $val3 i32 $c i32) + (local $ret i32 $empty i32 $current i32) ;; MAP_LOOP stack + (set_local $type (get_global $HASHMAP_T)) + + ;; MAP_LOOP_START + (set_local $res ($MAP_LOOP_START $type)) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + ;; READ_SEQ_LOOP + (block $done + (loop $loop + (if (i32.eqz ($VAL0 $args)) (br $done)) + + (set_local $val2 ($INC_REF ($MEM_VAL1_ptr $args))) + (set_local $val3 ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))) + + ;; skip two + (set_local $args ($MEM_VAL0_ptr ($MEM_VAL0_ptr $args))) + + ;; update the return sequence structure + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (br $loop) + ) + ) + + ;; MAP_LOOP_DONE + $ret + ) + + + (func $hash_map_Q (param $args i32) (result i32) + ($TRUE_FALSE (i32.eq ($TYPE ($DEREF_META ($MEM_VAL1_ptr $args))) + (get_global $HASHMAP_T)))) + + (func $assoc (param $args i32) (result i32) + (local $hm i32 $key i32) + (set_local $hm ($MEM_VAL1_ptr $args)) + (set_local $args ($MEM_VAL0_ptr $args)) + (drop ($INC_REF $hm)) + (block $done + (loop $loop + (if (OR (i32.eqz ($VAL0 $args)) + (i32.eqz ($VAL0 ($MEM_VAL0_ptr $args)))) + (br $done)) + (set_local $hm ($ASSOC1 $hm ($MEM_VAL1_ptr $args) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))) + (set_local $args ($MEM_VAL0_ptr ($MEM_VAL0_ptr $args))) + + (br $loop) + ) + ) + $hm + ) + + (func $get (param $args i32) (result i32) + (local $hm i32 $key i32) + (set_local $hm ($MEM_VAL1_ptr $args)) + (set_local $key ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) + (if i32 (i32.eq $hm (get_global $NIL)) + (then ($INC_REF (get_global $NIL))) + (else ($INC_REF (i32.wrap/i64 ($HASHMAP_GET $hm $key))))) + ) + + (func $contains_Q (param $args i32) (result i32) + (local $hm i32 $key i32) + (set_local $hm ($MEM_VAL1_ptr $args)) + (set_local $key ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) + ($TRUE_FALSE + (if i32 (i32.eq $hm (get_global $NIL)) + (then 0) + (else (i32.wrap/i64 + (i64.shr_u ($HASHMAP_GET $hm $key) (i64.const 32)))))) + ) + + (func $keys_or_vals (param $hm i32 $keys i32) (result i32) + (local $res i32 $val2 i32) + (local $ret i32 $empty i32 $current i32) ;; MAP_LOOP stack + + ;; MAP_LOOP_START + (set_local $res ($MAP_LOOP_START (get_global $LIST_T))) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (block $done + (loop $loop + (if (i32.eqz ($VAL0 $hm)) (br $done)) + + (if $keys + (then (set_local $val2 ($INC_REF ($MEM_VAL1_ptr $hm)))) + (else (set_local $val2 ($INC_REF ($MEM_VAL2_ptr $hm))))) + + ;; next element + (set_local $hm ($MEM_VAL0_ptr $hm)) + + ;; update the return sequence structure + ;; do not release val2 since we are pulling it from the + ;; arguments and not creating it here + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE (get_global $LIST_T) + $empty $current $val2 0)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (br $loop) + ) + ) + + ;; MAP_LOOP_DONE + $ret + ) + + (func $keys (param $args i32) (result i32) + ($keys_or_vals ($MEM_VAL1_ptr $args) 1)) + + (func $vals (param $args i32) (result i32) + ($keys_or_vals ($MEM_VAL1_ptr $args) 0)) + + (func $sequential_Q (param $args i32) (result i32) + ($TRUE_FALSE (OR (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) + (get_global $LIST_T)) + (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) + (get_global $VECTOR_T))))) + + (func $cons (param $args i32) (result i32) + ($LIST ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)) ($MEM_VAL1_ptr $args))) + + (func $concat (param $args i32) (result i32) + (local $res i32 $current i32 $sl i32 $last i32 $last_sl i64 $arg i32) + (set_local $res ($INC_REF (get_global $EMPTY_LIST))) + (set_local $current $res) + (set_local $sl 0) + (set_local $last 0) + (block $done + (loop $loop + (if (i32.le_u $args (get_global $EMPTY_HASHMAP)) + (br $done)) + (set_local $arg ($MEM_VAL1_ptr $args)) + ;; skip empty elements + (if (i32.le_s $arg (get_global $EMPTY_HASHMAP)) + (then + (set_local $args ($MEM_VAL0_ptr $args)) + (br $loop))) + (set_local $last_sl ($SLICE $arg 0 -1)) + (set_local $sl (i32.wrap/i64 $last_sl)) + (set_local $last (i32.wrap/i64 (i64.shr_u $last_sl (i64.const 32)))) + (if (i32.eq $res (get_global $EMPTY_LIST)) + (then + ;; if this is the first element, set the return to the slice + (set_local $res $sl)) + (else + ;; otherwise attach current to sliced + (i32.store ($VAL0_ptr $current) ($IDX $sl)))) + ;; update current to end of sliced list + (set_local $current $last) + ;; release empty since no longer part of the slice + ($RELEASE (get_global $EMPTY_LIST)) + + (set_local $args ($MEM_VAL0_ptr $args)) + (br $loop) + ) + ) + $res + ) + + (func $nth (param $args i32) (result i32) + (local $a i32 $idx i32 $i i32) + (set_local $a ($MEM_VAL1_ptr $args)) + (set_local $idx ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))) + + (set_local $i 0) + (block $done + (loop $loop + (if (OR (i32.ge_s $i $idx) (i32.eqz ($VAL0 $a))) + (br $done)) + (set_local $i (i32.add $i 1)) + (set_local $a ($MEM_VAL0_ptr $a)) + (br $loop) + ) + ) + (if (i32.eq ($VAL0 $a) 0) + (then + ($THROW_STR_0 "nth: index out of range") + (return 0))) + + ($INC_REF ($MEM_VAL1_ptr $a)) + ) + + (func $first (param $args i32) (result i32) + (local $res i32 $a i32) + (set_local $res (get_global $NIL)) + (set_local $a ($MEM_VAL1_ptr $args)) + (if (AND (i32.ne $a (get_global $NIL)) + (i32.ne ($VAL0 $a) 0)) + (set_local $res ($MEM_VAL1_ptr $a))) + ($INC_REF $res) + ) + + (func $rest (param $args i32) (result i32) + (local $a i32) + (set_local $a ($MEM_VAL1_ptr $args)) + (if (i32.eq $a (get_global $NIL)) + (return ($INC_REF (get_global $EMPTY_LIST)))) + (if (i32.ne ($VAL0 $a) 0) + (set_local $a ($MEM_VAL0_ptr $a))) + ($FORCE_SEQ_TYPE (get_global $LIST_T) $a) + ) + + ;;; + + (func $empty_Q (param $args i32) (result i32) + ($TRUE_FALSE ($EMPTY_Q ($MEM_VAL1_ptr $args)))) + + (func $count (param $args i32) (result i32) + ($INTEGER ($COUNT ($MEM_VAL1_ptr $args)))) + + (func $apply (param $args i32) (result i32) + (local $f i32 $f_args i32 $rest_args i32 $rest_count i32) + (local $last_sl i64 $last i32 $res i32) + + (set_local $f ($MEM_VAL1_ptr $args)) + (set_local $rest_args ($MEM_VAL0_ptr $args)) + (set_local $rest_count ($COUNT $rest_args)) + + (if (i32.le_s $rest_count 1) + (then + ;; no intermediate args + (if (i32.ne ($TYPE ($MEM_VAL1_ptr $rest_args)) (get_global $LIST_T)) + (then + ;; not a list, so convert it first + (set_local $f_args ($FORCE_SEQ_TYPE (get_global $LIST_T) + ($MEM_VAL1_ptr $rest_args)))) + (else + ;; inc ref since we will release after APPLY + (set_local $f_args ($INC_REF ($MEM_VAL1_ptr $rest_args)))))) + (else + ;; 1 or more intermediate args + (set_local $last_sl ($SLICE $rest_args 0 (i32.sub_s $rest_count 1))) + (set_local $f_args (i32.wrap/i64 $last_sl)) + (set_local $last (i32.wrap/i64 (i64.shr_u $last_sl (i64.const 32)))) + ;; release the terminator of the new list (we skip over it) + ;; we already checked for an empty list above, so $last is + ;; a real non-empty list + ($RELEASE ($MEM_VAL0_ptr $last)) + ;; attach end of slice to final args element + (i32.store ($VAL0_ptr $last) ($IDX ($LAST $rest_args))) + )) + + (set_local $res ($APPLY $f $f_args)) + + ;; release new args + ($RELEASE $f_args) + $res + ) + + (func $map (param $args i32) (result i32) + (local $f i32 $rest_args i32 $f_args i32 $res i32) + (local $ret i32 $empty i32 $current i32) ;; MAP_LOOP stack + + (set_local $f ($MEM_VAL1_ptr $args)) + (set_local $rest_args ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) + + ;; MAP_LOOP_START + (set_local $res ($MAP_LOOP_START (get_global $LIST_T))) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (block $done + (loop $loop + (if (i32.eqz ($VAL1 $rest_args)) (br $done)) + + ;; create argument list for apply + (set_local $f_args ($ALLOC (get_global $LIST_T) + (get_global $EMPTY_LIST) + ($MEM_VAL1_ptr $rest_args) + 0)) + + (set_local $res ($APPLY $f $f_args)) + ($RELEASE $f_args) + + ;; go to the next element + (set_local $rest_args ($MEM_VAL0_ptr $rest_args)) + + (if (get_global $error_type) + (then + ;; if error, release the unattached element + ($RELEASE $res) + (br $done))) + + ;; update the return sequence structure + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE (get_global $LIST_T) + $empty $current $res 0)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (br $loop) + ) + ) + + ;; MAP_LOOP_DONE + $ret + ) + + ;;; + + (func $with_meta (param $args i32) (result i32) + (local $mv i32 $meta i32) + (set_local $mv ($MEM_VAL1_ptr $args)) + (set_local $meta ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) + ;; remove existing metadata first + ($ALLOC (get_global $METADATA_T) ($DEREF_META $mv) $meta 0) + ) + + (func $meta (param $args i32) (result i32) + (if i32 (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) (get_global $METADATA_T)) + (then ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL1_ptr $args)))) + (else ($INC_REF (get_global $NIL))))) + + (func $atom (param $args i32) (result i32) + ($ALLOC_SCALAR (get_global $ATOM_T) ($VAL1 $args))) + + (func $atom_Q (param $args i32) (result i32) + ($TRUE_FALSE (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) (get_global $ATOM_T)))) + + (func $deref (param $args i32) (result i32) + ($INC_REF ($MEM_VAL0_ptr ($MEM_VAL1_ptr $args)))) + + (func $_reset_BANG (param $atom i32 $val i32) (result i32) + ;; release current value since we are about to overwrite it + ($RELEASE ($MEM_VAL0_ptr $atom)) + ;; inc ref by 2 for atom ownership and since we are returning it + (drop ($INC_REF ($INC_REF $val))) + ;; update the value + (i32.store ($VAL0_ptr $atom) ($IDX $val)) + $val + ) + + (func $reset_BANG (param $args i32) (result i32) + (local $atom i32 $val i32) + (set_local $atom ($MEM_VAL1_ptr $args)) + (set_local $val ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) + ($_reset_BANG $atom $val) + ) + + (func $swap_BANG (param $args i32) (result i32) + (local $atom i32 $f_args i32 $s_args i32 $rest_args i32 $f i32 $res i32) + (set_local $atom ($MEM_VAL1_ptr $args)) + (set_local $f_args ($MEM_VAL0_ptr $args)) + (set_local $rest_args ($MEM_VAL0_ptr $f_args)) + (set_local $f ($MEM_VAL1_ptr $f_args)) + (set_local $res 0) + ;; add atom value to front of the args list + (set_local $s_args ($LIST $rest_args ($MEM_VAL0_ptr $atom))) ;; cons + (set_local $res ($APPLY $f $s_args)) + ;; release args + ($RELEASE $s_args) + ;; use reset to update the value + (drop ($_reset_BANG $atom $res)) + ;; but decrease the ref cnt of return by 1 (not sure why) + ($RELEASE $res) + $res + ) + + ;;; + + (func $pr_memory_summary (param $args i32) (result i32) + ($PR_MEMORY_SUMMARY_SMALL) + ($INC_REF (get_global $NIL)) + ) + + (func $nop (param $args i32) (result i32) + ($INC_REF (get_global $NIL))) + + (table + anyfunc + (elem $nop ;; placeholder for eval which will use 0 + $equal_Q + $throw + $nil_Q + $true_Q + $false_Q + $number_Q + $string_Q + $symbol + $symbol_Q + $keyword + $keyword_Q + $fn_Q + $macro_Q + + ;; 14 + $core_pr_str + $str + $prn + $println + $core_readline + $read_string + $slurp + $lt + $lte + $gt + $gte + $add + $subtract + $multiply + $divide + $time_ms + + ;; 30 + $list + $list_Q + $vector + $vector_Q + $hash_map + $hash_map_Q + $assoc + $nop ;; $dissoc + $get + $contains_Q + $keys + $vals + + ;; 42 + $sequential_Q + $cons + $concat + $nth + $first + $rest + $empty_Q + $count + $apply + $map + $nop ;; $conj + $nop ;; $seq + + ;; 54 + $with_meta + $meta + $atom + $atom_Q + $deref + $reset_BANG + $swap_BANG + + $pr_memory_summary + ) + ) + + (func $add_core_ns (param $env i32) + ;;(drop ($ENV_SET_S $env "eval" ($FUNCTION 0))) + (drop ($ENV_SET_S $env "=" ($FUNCTION 1))) + (drop ($ENV_SET_S $env "throw" ($FUNCTION 2))) + (drop ($ENV_SET_S $env "nil?" ($FUNCTION 3))) + (drop ($ENV_SET_S $env "true?" ($FUNCTION 4))) + (drop ($ENV_SET_S $env "false?" ($FUNCTION 5))) + (drop ($ENV_SET_S $env "number?" ($FUNCTION 6))) + (drop ($ENV_SET_S $env "string?" ($FUNCTION 7))) + (drop ($ENV_SET_S $env "symbol" ($FUNCTION 8))) + (drop ($ENV_SET_S $env "symbol?" ($FUNCTION 9))) + (drop ($ENV_SET_S $env "keyword" ($FUNCTION 10))) + (drop ($ENV_SET_S $env "keyword?" ($FUNCTION 11))) + (drop ($ENV_SET_S $env "fn?" ($FUNCTION 12))) + (drop ($ENV_SET_S $env "macro?" ($FUNCTION 13))) + + (drop ($ENV_SET_S $env "pr-str" ($FUNCTION 14))) + (drop ($ENV_SET_S $env "str" ($FUNCTION 15))) + (drop ($ENV_SET_S $env "prn" ($FUNCTION 16))) + (drop ($ENV_SET_S $env "println" ($FUNCTION 17))) + (drop ($ENV_SET_S $env "readline" ($FUNCTION 18))) + (drop ($ENV_SET_S $env "read-string" ($FUNCTION 19))) + (drop ($ENV_SET_S $env "slurp" ($FUNCTION 20))) + (drop ($ENV_SET_S $env "<" ($FUNCTION 21))) + (drop ($ENV_SET_S $env "<=" ($FUNCTION 22))) + (drop ($ENV_SET_S $env ">" ($FUNCTION 23))) + (drop ($ENV_SET_S $env ">=" ($FUNCTION 24))) + (drop ($ENV_SET_S $env "+" ($FUNCTION 25))) + (drop ($ENV_SET_S $env "-" ($FUNCTION 26))) + (drop ($ENV_SET_S $env "*" ($FUNCTION 27))) + (drop ($ENV_SET_S $env "/" ($FUNCTION 28))) + (drop ($ENV_SET_S $env "time-ms" ($FUNCTION 29))) + + (drop ($ENV_SET_S $env "list" ($FUNCTION 30))) + (drop ($ENV_SET_S $env "list?" ($FUNCTION 31))) + (drop ($ENV_SET_S $env "vector" ($FUNCTION 32))) + (drop ($ENV_SET_S $env "vector?" ($FUNCTION 33))) + (drop ($ENV_SET_S $env "hash-map" ($FUNCTION 34))) + (drop ($ENV_SET_S $env "map?" ($FUNCTION 35))) + (drop ($ENV_SET_S $env "assoc" ($FUNCTION 36))) + (drop ($ENV_SET_S $env "dissoc" ($FUNCTION 37))) + (drop ($ENV_SET_S $env "get" ($FUNCTION 38))) + (drop ($ENV_SET_S $env "contains?" ($FUNCTION 39))) + (drop ($ENV_SET_S $env "keys" ($FUNCTION 40))) + (drop ($ENV_SET_S $env "vals" ($FUNCTION 41))) + + (drop ($ENV_SET_S $env "sequential?" ($FUNCTION 42))) + (drop ($ENV_SET_S $env "cons" ($FUNCTION 43))) + (drop ($ENV_SET_S $env "concat" ($FUNCTION 44))) + (drop ($ENV_SET_S $env "nth" ($FUNCTION 45))) + (drop ($ENV_SET_S $env "first" ($FUNCTION 46))) + (drop ($ENV_SET_S $env "rest" ($FUNCTION 47))) + (drop ($ENV_SET_S $env "empty?" ($FUNCTION 48))) + (drop ($ENV_SET_S $env "count" ($FUNCTION 49))) + (drop ($ENV_SET_S $env "apply" ($FUNCTION 50))) + (drop ($ENV_SET_S $env "map" ($FUNCTION 51))) + + (drop ($ENV_SET_S $env "conj" ($FUNCTION 52))) + (drop ($ENV_SET_S $env "seq" ($FUNCTION 53))) + + (drop ($ENV_SET_S $env "with-meta" ($FUNCTION 54))) + (drop ($ENV_SET_S $env "meta" ($FUNCTION 55))) + (drop ($ENV_SET_S $env "atom" ($FUNCTION 56))) + (drop ($ENV_SET_S $env "atom?" ($FUNCTION 57))) + (drop ($ENV_SET_S $env "deref" ($FUNCTION 58))) + (drop ($ENV_SET_S $env "reset!" ($FUNCTION 59))) + (drop ($ENV_SET_S $env "swap!" ($FUNCTION 60))) + + (drop ($ENV_SET_S $env "pr-memory-summary" ($FUNCTION 61))) + ) +) diff --git a/wasm/debug.wam b/wasm/debug.wam index 7eb0795461..4ad2231482 100644 --- a/wasm/debug.wam +++ b/wasm/debug.wam @@ -1,17 +1,89 @@ (module $debug - (func $PR_VALUE (param $fmt i32) (param $mv i32) + (func $CHECK_FREE_LIST (result i32) + (local $first i32 $count i32) + (set_local $first (i32.add + (get_global $mem) + (i32.mul_u (get_global $mem_free_list) + 4))) + (set_local $count 0) + + (block $done + (loop $loop + (if (i32.ge_s $first (i32.add + (get_global $mem) + (i32.mul_u (get_global $mem_unused_start) + 4))) + (br $done)) + (set_local $count (i32.add $count ($MalVal_size $first))) + (set_local $first (i32.add (get_global $mem) (i32.mul_u 4 ($VAL0 $first)))) + (br $loop) + ) + ) + $count + ) + + (func $PR_MEMORY_SUMMARY_SMALL + (local $free i32 $free_list_count i32 $mv i32 $mem_ref_count i32) + + (set_local $free (i32.sub_s (get_global $MEM_SIZE) + (i32.mul_u (get_global $mem_unused_start) 4))) + (set_local $free_list_count ($CHECK_FREE_LIST)) + (set_local $mem_ref_count 0) + + (set_local $mv (get_global $NIL)) + (block $done + (loop $loop + (if (i32.ge_s $mv (i32.add + (get_global $mem) + (i32.mul_u (get_global $mem_unused_start) 4))) + (br $done)) + (if (i32.ne ($TYPE $mv) (get_global $FREE_T)) + (set_local $mem_ref_count (i32.add $mem_ref_count + (i32.shr_u + (i32.load $mv) + 5)))) + (set_local $mv (i32.add $mv (i32.mul_u 4 ($MalVal_size $mv)))) + (br $loop) + ) + ) + + ($printf_3 "Free: %d, Values: %d (refs: %d), Emptys: " + $free + (i32.sub_s + (i32.sub_s (get_global $mem_unused_start) 1) + $free_list_count) + $mem_ref_count) + (set_local $mv (get_global $NIL)) + (block $done + (loop $loop + (if (i32.gt_s $mv (get_global $TRUE)) (br $done)) + ($printf_1 "%d," (i32.div_s (i32.load $mv) 32)) + (set_local $mv (i32.add $mv 8)) + (br $loop) + ) + ) + (set_local $mv (get_global $EMPTY_LIST)) + (block $done + (loop $loop + (if (i32.gt_s $mv (get_global $EMPTY_HASHMAP)) (br $done)) + ($printf_1 "%d," (i32.div_s (i32.load $mv) 32)) + (set_local $mv (i32.add $mv 12)) + (br $loop) + ) + ) + ($print "\n") + ) + + (func $PR_VALUE (param $fmt i32 $mv i32) (local $temp i32) - (set_local $temp ($pr_str $mv)) + (set_local $temp ($pr_str $mv 1)) ($printf_1 $fmt ($to_String $temp)) ($RELEASE $temp) ) (func $PR_MEMORY_VALUE (param $idx i32) (result i32) - (local $mv i32) - (local $type i32) - (local $size i32) - (local $val0 i32) + (local $mv i32 $type i32 $size i32 $val0 i32) ;;; mv = mem + idx (set_local $mv ($MalVal_ptr $idx)) (set_local $type ($TYPE $mv)) @@ -19,7 +91,7 @@ (set_local $val0 ($MalVal_val $idx 0)) ;;; printf(" %3d: type: %2d", idx, type) - ($printf_2 " 0x%x: type: %d" $idx $type) + ($printf_2 " %d: type: %d" $idx $type) (if (i32.eq $type 15) (then @@ -30,20 +102,24 @@ ($printf_1 ", refs: %d" ($REFS $mv)))) ;;; printf(", [ %3d | %3d", mv->refcnt_type, val0) - ($printf_2 ", [ 0x%x | 0x%x" ($MalVal_refcnt_type $idx) $val0) + (if (OR (i32.eq $type (get_global $STRING_T)) + (i32.eq $type (get_global $SYMBOL_T))) + ;; for strings/symbolx pointers, print hex values + (then ($printf_2 ", [ 0x%x | 0x%x" ($MalVal_refcnt_type $idx) $val0)) + (else ($printf_2 ", [ %d | %d" ($MalVal_refcnt_type $idx) $val0))) (if (i32.eq $size 2) (then ($print " | --- | --- ]")) (else ;;; printf(" | %3d", mv->val[1]) - ($printf_1 " | 0x%x" ($MalVal_val $idx 1)) + ($printf_1 " | %d" ($MalVal_val $idx 1)) (if (i32.eq $size 3) (then ($print " | --- ]")) (else ;;; printf(" | %3d ]", mv->val[2]) - ($printf_1 " | 0x%x ]" ($MalVal_val $idx 2)))))) + ($printf_1 " | %d ]" ($MalVal_val $idx 2)))))) ;;; printf(" >> ") ($print " >> ") @@ -136,12 +212,12 @@ ($print "unknown") ) - (drop ($putchar 0xA)) + ($print "\n") (i32.add $size $idx) ) - (func $PR_MEMORY (param $start i32) (param $end i32) + (func $PR_MEMORY (param $start i32 $end i32) (local $idx i32) (if (i32.lt_s $start 0) (set_local $start (get_global $mem_user_start))) @@ -171,9 +247,10 @@ (br $loopvals) ) ))) + ($PR_MEMORY_SUMMARY_SMALL) ) - (func $PR_MEMORY_RAW (param $start i32) (param $end i32) + (func $PR_MEMORY_RAW (param $start i32 $end i32) (block $loop_exit (loop $loop (if (i32.ge_u $start $end) (br $loop_exit)) diff --git a/wasm/env.wam b/wasm/env.wam index 88945ff34a..64e7dd9b13 100644 --- a/wasm/env.wam +++ b/wasm/env.wam @@ -1,8 +1,7 @@ (module $env (func $ENV_NEW (param $outer i32) (result i32) - (local $data i32) - (local $env i32) + (local $data i32 $env i32) ;; allocate the data hashmap (set_local $data ($HASHMAP)) @@ -13,26 +12,60 @@ $env ) - (func $ENV_SET (param $env i32) (param $key i32) (param $value i32) - (result i32) + (func $ENV_NEW_BINDS (param $outer i32 $binds i32 $exprs i32) (result i32) + (local $env i32 $key i32) + (set_local $env ($ENV_NEW $outer)) + + ;; process bindings + (block $done + (loop $loop + (if (i32.eqz ($VAL0 $binds)) (br $done)) + + ;; get/deref the key from binds + (set_local $key ($MEM_VAL1_ptr $binds)) + (if (i32.eqz ($strcmp "&" ($to_String $key))) + (then + ;; ENV_NEW_BIND_VARGS + ;; get/deref the key from the next element of binds + (set_local $binds ($MEM_VAL0_ptr $binds)) + (set_local $key ($MEM_VAL1_ptr $binds)) + ;; the value is the remaining list in exprs + (set_local $exprs ($FORCE_SEQ_TYPE (get_global $LIST_T) $exprs)) + ;; set the binding in the environment data + (drop ($ENV_SET $env $key $exprs)) + ;; list is owned by the environment + ($RELEASE $exprs) + (br $done)) + (else + ;; ENV_NEW_BIND_1x1 + ;; set the binding in the environment data + (drop ($ENV_SET $env $key ($MEM_VAL1_ptr $exprs))) + ;; go to next element of binds and exprs + (set_local $binds ($MEM_VAL0_ptr $binds)) + (set_local $exprs ($MEM_VAL0_ptr $exprs)))) + + (br $loop) + ) + ) + $env + ) + + (func $ENV_SET (param $env i32 $key i32 $value i32) (result i32) (local $data i32) (set_local $data ($MEM_VAL0_ptr $env)) - (i32.store ($VAL0_ptr $env) ($MalVal_index ($ASSOC1 $data $key $value))) + (i32.store ($VAL0_ptr $env) ($IDX ($ASSOC1 $data $key $value))) $value ) - (func $ENV_SET_S (param $env i32) (param $key i32) (param $value i32) - (result i32) + (func $ENV_SET_S (param $env i32 $key i32 $value i32) (result i32) (local $data i32) (set_local $data ($MEM_VAL0_ptr $env)) - (i32.store ($VAL0_ptr $env) ($MalVal_index ($ASSOC1_S $data $key $value))) + (i32.store ($VAL0_ptr $env) ($IDX ($ASSOC1_S $data $key $value))) $value ) - (func $ENV_FIND (param $env i32) (param $key i32) (result i64) - (local $res i32) - (local $data i32) - (local $found_res i64) + (func $ENV_FIND (param $env i32 $key i32) (result i64) + (local $res i32 $data i32 $found_res i64) (set_local $res 0) @@ -61,9 +94,8 @@ (i64.extend_u/i32 $env)) ) - (func $ENV_GET (param $env i32) (param $key i32) (result i32) - (local $res i32) - (local $res_env i64) + (func $ENV_GET (param $env i32 $key i32) (result i32) + (local $res i32 $res_env i64) (set_local $res 0) (set_local $res_env ($ENV_FIND $env $key)) diff --git a/wasm/mem.wam b/wasm/mem.wam index 1134ae1ccc..e67824bd67 100644 --- a/wasm/mem.wam +++ b/wasm/mem.wam @@ -30,13 +30,19 @@ (func $MEM_VAL0_ptr (param $mv i32) (result i32) (i32.add (get_global $mem) - (i32.mul_u (i32.load (i32.add $mv 4)) 8))) + (i32.mul_u (i32.load (i32.add $mv 4)) 4))) (func $MEM_VAL1_ptr (param $mv i32) (result i32) (i32.add (get_global $mem) - (i32.mul_u (i32.load (i32.add $mv 8)) 8))) + (i32.mul_u (i32.load (i32.add $mv 8)) 4))) (func $MEM_VAL2_ptr (param $mv i32) (result i32) (i32.add (get_global $mem) - (i32.mul_u (i32.load (i32.add $mv 12)) 8))) + (i32.mul_u (i32.load (i32.add $mv 12)) 4))) + + ;; Returns the memory index mem of mv + ;; Will usually be used with a load or store by the caller + (func $IDX (param $mv i32) (result i32) + ;; MalVal memory 64 bit (2 * i32) aligned + (i32.div_u (i32.sub_u $mv (get_global $mem)) 4)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -44,13 +50,7 @@ (func $MalVal_ptr (param $mv_idx i32) (result i32) ;; MalVal memory 64 bit (2 * i32) aligned ;;; mem[mv_idx].refcnt_type - (i32.add (get_global $mem) (i32.mul_u $mv_idx 8))) - - ;; Returns the memory index mem of mv - ;; Will usually be used with a load or store by the caller - (func $MalVal_index (param $mv i32) (result i32) - ;; MalVal memory 64 bit (2 * i32) aligned - (i32.div_u (i32.sub_u $mv (get_global $mem)) 8)) + (i32.add (get_global $mem) (i32.mul_u $mv_idx 4))) ;; Returns the address of 'mem[mv_idx].refcnt_type' (func $MalVal_refcnt_type (param $mv_idx i32) (result i32) @@ -60,31 +60,40 @@ ;;; type = mv->refcnt_type & 31 (i32.and (i32.load $mv) 0x1f)) ;; 0x1f == 31 + (func $SET_TYPE (param $mv i32 $type i32) + ;;; type = mv->refcnt_type & 31 + ;;; mv->refcnt_type += - (mv->refcnt_type & 31) + type + (i32.store $mv (i32.or + (i32.and $type 0x1f) ;; 0x1f == 31 + (i32.and (i32.load $mv) 0xffffffe1))) + ) + + (func $REFS (param $mv i32) (result i32) ;;; type = mv->refcnt_type & 31 (i32.shr_u (i32.load $mv) 5)) ;; / 32 ;; Returns the address of 'mem[mv_idx].val[val]' ;; Will usually be used with a load or store by the caller - (func $MalVal_val_ptr (param $mv_idx i32) (param $val i32) (result i32) + (func $MalVal_val_ptr (param $mv_idx i32 $val i32) (result i32) (i32.add (i32.add ($MalVal_ptr $mv_idx) 4) (i32.mul_u $val 4))) ;; Returns the value of 'mem[mv_idx].val[val]' - (func $MalVal_val (param $mv_idx i32) (param $val i32) (result i32) + (func $MalVal_val (param $mv_idx i32 $val i32) (result i32) (i32.load ($MalVal_val_ptr $mv_idx $val))) (func $MalType_size (param $type i32) (result i32) ;;; if (type <= 5 || type == 9 || type == 12) - (if i32 (i32.or (i32.le_u $type 5) - (i32.or (i32.eq $type 9) - (i32.eq $type 12))) + (if i32 (OR (i32.le_u $type 5) + (i32.eq $type 9) + (i32.eq $type 12)) (then 2) (else ;;; else if (type == 8 || type == 10 || type == 11) - (if i32 (i32.or (i32.eq $type 8) - (i32.or (i32.eq $type 10) - (i32.eq $type 11))) + (if i32 (OR (i32.eq $type 8) + (i32.eq $type 10) + (i32.eq $type 11)) (then 4) (else 3))))) @@ -110,10 +119,12 @@ ($init_sprintf_mem) - ;; 100 character error_str static buffer - (set_global $error_str " ") - ;; 256 character token static buffer - (set_global $token " ") + ;; error_str string buffer + (set_global $error_str (STATIC_ARRAY 100)) + ;; reader token string buffer + (set_global $token_buf (STATIC_ARRAY 256)) + ;; printer string buffer + (set_global $printer_buf (STATIC_ARRAY 4096)) (set_local $heap_size (i32.add (get_global $MEM_SIZE) (get_global $STRING_MEM_SIZE))) @@ -154,11 +165,9 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; memory management - (func $ALLOC_INTERNAL (param $type i32) (param $val1 i32) - (param $val2 i32) (param $val3 i32) (result i32) - (local $prev i32) - (local $res i32) - (local $size i32) + (func $ALLOC_INTERNAL (param $type i32 + $val1 i32 $val2 i32 $val3 i32) (result i32) + (local $prev i32 $res i32 $size i32) (set_local $prev (get_global $mem_free_list)) (set_local $res (get_global $mem_free_list)) (set_local $size ($MalType_size $type)) @@ -218,7 +227,8 @@ ;;; mem[res].val[0] = val1 (i32.store ($MalVal_val_ptr $res 0) $val1) ;;; if (type > 5 && type != 9) - (if (i32.and (i32.gt_u $type 5) (i32.ne $type 9)) + (if (AND (i32.gt_u $type 5) + (i32.ne $type 9)) (then ;; inc refcnt of referenced value ;;; mem[val1].refcnt_type += 32 @@ -247,28 +257,22 @@ ($MalVal_ptr $res) ) - (func $ALLOC_SCALAR (param $type i32) (param $val1 i32) (result i32) + (func $ALLOC_SCALAR (param $type i32 $val1 i32) (result i32) ($ALLOC_INTERNAL $type $val1 0 0) ) - (func $ALLOC (param $type i32) (param $val1 i32) - (param $val2 i32) (param $val3 i32) (result i32) - ($ALLOC_INTERNAL $type - ($MalVal_index $val1) - ($MalVal_index $val2) - ($MalVal_index $val3)) + (func $ALLOC (param $type i32 $val1 i32 $val2 i32 $val3 i32) (result i32) + ($ALLOC_INTERNAL $type ($IDX $val1) ($IDX $val2) ($IDX $val3)) ) (func $RELEASE (param $mv i32) - (local $idx i32) - (local $type i32) - (local $size i32) + (local $idx i32 $type i32 $size i32) ;; Ignore NULLs ;;; if (mv == NULL) { return; } (if (i32.eqz $mv) (return)) ;;; idx = mv - mem - (set_local $idx ($MalVal_index $mv)) + (set_local $idx ($IDX $mv)) ;;; type = mv->refcnt_type & 31 (set_local $type (i32.and (i32.load $mv) 0x1f)) ;; 0x1f == 31 ;;; size = MalType_size(type) @@ -309,8 +313,8 @@ (return)) (block $done - (block (block (block (block (block (block - (br_table 0 0 0 0 1 1 2 2 3 5 5 5 5 4 5 5 5 $type)) + (block (block (block (block (block (block (block (block (block + (br_table 0 0 0 0 1 1 2 2 3 0 4 4 5 6 7 8 8 $type)) ;; nil, boolean, integer, float (br $done)) ;; string, kw, symbol @@ -332,12 +336,27 @@ ($RELEASE ($MEM_VAL2_ptr $mv)) ($RELEASE ($MEM_VAL1_ptr $mv)))) (br $done)) + ;; mal / macro function + ;; release ast, params, and environment + ($RELEASE ($MEM_VAL2_ptr $mv)) + ($RELEASE ($MEM_VAL1_ptr $mv)) + ($RELEASE ($MEM_VAL0_ptr $mv)) + (br $done)) + ;; atom + ;; release contained/referred value + ($RELEASE ($MEM_VAL0_ptr $mv)) + (br $done)) ;; env ;; if outer is set then release outer (if (i32.ne ($MalVal_val $idx 1) 0) ($RELEASE ($MEM_VAL1_ptr $mv))) - ;; release the hashmap data + ;; release the env data (hashmap) + ($RELEASE ($MEM_VAL0_ptr $mv)) + (br $done)) + ;; metadata + ;; release object and metdata object ($RELEASE ($MEM_VAL0_ptr $mv)) + ($RELEASE ($MEM_VAL1_ptr $mv)) (br $done)) ;; default/unknown ) @@ -356,13 +375,10 @@ ;; Allocate a string as follows: ;; refcnt (i32 set to 1), string data, NULL byte (func $STRING_DUPE (param $str i32) (result i32) - (local $len i32) - (local $cur i32) - (local $new i32) - (local $idx i32) + (local $len i32 $cur i32 $new i32 $idx i32) ;; Calculate length of string needed - (set_local $len ($STRING_LEN $str)) + (set_local $len ($strlen $str)) ;; leading i32 refcnt + trailing NULL (set_local $new ($malloc (i32.add 5 $len))) @@ -375,23 +391,19 @@ (i32.store8_u (i32.add $cur $len) 0) ;; Copy the characters - ($MEM_COPY $cur $str $len) + ($memmove $cur $str $len) $new ) ;; Duplicate regular character array string into a Mal string and ;; return the MalVal pointer - (func $STRING (param $type i32) (param $str i32) (result i32) - ($ALLOC_SCALAR - $type - ($STRING_DUPE $str)) + (func $STRING (param $type i32 $str i32) (result i32) + ($ALLOC_SCALAR $type ($STRING_DUPE $str)) ) (func $RELEASE_STRING (param $mv i32) (local $str i32) - (set_local $str ($MalVal_val - ($MalVal_index $mv) - 0)) + (set_local $str ($MalVal_val ($IDX $mv) 0)) ;; DEBUG ;; ($printf_1 "RELEASE_STRING - calling free on: %d" $str) diff --git a/wasm/printer.wam b/wasm/printer.wam index 28e9e1a87e..95afdc482d 100644 --- a/wasm/printer.wam +++ b/wasm/printer.wam @@ -1,12 +1,11 @@ (module $printer - (func $pr_str_val (param $res i32) (param $mv i32) (result i32) - (local $type i32) - (local $val0 i32) - (local $sval i32) + (global $printer_buf (mut i32) 0) + + (func $pr_str_val (param $res i32 $mv i32 $print_readably i32) (result i32) + (local $type i32 $val0 i32 $sval i32) (set_local $type ($TYPE $mv)) - (set_local $val0 ($MalVal_val ($MalVal_index $mv) - 0)) + (set_local $val0 ($VAL0 $mv)) ;;; switch(type) (block $done @@ -15,18 +14,18 @@ (block (block (block (block (block (block (block (block (br_table 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $type)) ;; 0: nil - ($MEM_COPY $res "nil" 4) + ($memmove $res "nil" 4) (set_local $res (i32.add 3 $res)) (br $done)) ;; 1: boolean (if (i32.eq $val0 0) (then ;; false - ($MEM_COPY $res "false" 5) + ($memmove $res "false" 6) (set_local $res (i32.add 5 $res))) (else ;; true - ($MEM_COPY $res "true" 4) + ($memmove $res "true" 5) (set_local $res (i32.add 4 $res)))) (br $done)) ;; 2: integer @@ -40,8 +39,17 @@ (if (i32.eq (i32.load8_u $sval) (CHR "\x7f")) (then (set_local $res ($sprintf_1 $res ":%s" (i32.add $sval 1)))) - (else - (set_local $res ($sprintf_1 $res "\"%s\"" ($to_String $mv))))) + (else (if $print_readably + (then + ;; escape backslashes, quotes, and newlines + (set_local $res ($sprintf_1 $res "\"" 0)) + (set_local $res (i32.add $res ($REPLACE3 $res ($to_String $mv) + "\\" "\\\\" + "\"" "\\\"" + "\n" "\\n"))) + (set_local $res ($sprintf_1 $res "\"" 0))) + (else + (set_local $res ($sprintf_1 $res "%s" $sval)))))) (br $done)) ;; 5: symbol (set_local $res ($sprintf_1 $res "%s" ($to_String $mv))) @@ -65,21 +73,21 @@ (if (i32.eq ($VAL0 $mv) 0) (br $done_seq)) ;;; res = pr_str_val(res, MEM_VAL1(mv), print_readably) - (set_local $res ($pr_str_val $res ($MEM_VAL1_ptr $mv))) + (set_local $res ($pr_str_val $res ($MEM_VAL1_ptr $mv) $print_readably)) ;; if this is a hash-map, print the next element (if (i32.eq $type (get_global $HASHMAP_T)) (then ;;; res += snprintf(res, 2, " ") (set_local $res ($sprintf_1 $res " " 0)) - (set_local $res ($pr_str_val $res ($MEM_VAL2_ptr $mv))))) + (set_local $res ($pr_str_val $res ($MEM_VAL2_ptr $mv) + $print_readably)))) ;;; mv = MEM_VAL0(mv) (set_local $mv ($MEM_VAL0_ptr $mv)) ;;; if (VAL0(mv) != 0) (if (i32.ne ($VAL0 $mv) 0) ;;; res += snprintf(res, 2, " ") (set_local $res ($sprintf_1 $res " " 0))) - ;;($print "here4\n") (br $seq_loop) ) ) @@ -93,45 +101,80 @@ (else (CHR "}"))))))) (br $done)) ;; 9: function - ($MEM_COPY $res "#" 10) + ($memmove $res "#" 10) (set_local $res (i32.add 9 $res)) (br $done)) ;; 10: mal function - ($MEM_COPY $res "(fn* ...)" 10) - (set_local $res (i32.add 9 $res)) + ($memmove $res "(fn* " 6) + (set_local $res (i32.add 5 $res)) + (set_local $res ($pr_str_val $res ($MEM_VAL1_ptr $mv) $print_readably)) + ($memmove $res " " 2) + (set_local $res (i32.add 1 $res)) + (set_local $res ($pr_str_val $res ($MEM_VAL0_ptr $mv) $print_readably)) + ($memmove $res ")" 2) + (set_local $res (i32.add 1 $res)) (br $done)) ;; 11: macro fn - ($print "macro fn") - ($MEM_COPY $res "#" 13) + ($memmove $res "#" 13) (set_local $res (i32.add 12 $res)) (br $done)) ;; 12: atom - ($MEM_COPY $res "(atom ...)" 11) - (set_local $res (i32.add 10 $res)) + ($memmove $res "(atom " 7) + (set_local $res (i32.add 6 $res)) + (set_local $res ($pr_str_val $res ($MEM_VAL0_ptr $mv) $print_readably)) + ($memmove $res ")" 2) + (set_local $res (i32.add 1 $res)) (br $done)) ;; 13: environment - ($MEM_COPY $res "#" 11) + ($memmove $res "#" 11) (set_local $res (i32.add 10 $res)) (br $done)) ;; 14: metadata - ($MEM_COPY $res "#" 12) - (set_local $res (i32.add 11 $res)) + ;; recur on object itself + (set_local $res ($pr_str_val $res ($MEM_VAL0_ptr $mv) $print_readably)) (br $done)) ;; 15: FREE - ($MEM_COPY $res "#" 12) + ($memmove $res "#" 12) (set_local $res (i32.add 11 $res)) (br $done)) ;; 16: default - ($MEM_COPY $res "#" 11) + ($memmove $res "#" 11) (set_local $res (i32.add 10 $res)) ) $res ) - (func $pr_str (param $mv i32) (result i32) - (drop ($pr_str_val (get_global $sprintf_buf) $mv)) - ($STRING (get_global $STRING_T) (get_global $sprintf_buf)) + (func $pr_str_internal (param $seq i32) (param $mv i32) + (param $print_readably i32) (param $sep i32) (result i32) + (local $res i32) + (set_local $res (get_global $printer_buf)) + (i32.store8_u $res 0) + + (if $seq + (then + (block $done + (loop $loop + (if (i32.eqz ($VAL0 $mv)) (br $done)) + (set_local $res ($pr_str_val $res ($MEM_VAL1_ptr $mv) $print_readably)) + (set_local $mv ($MEM_VAL0_ptr $mv)) + (if (i32.ne ($VAL0 $mv) 0) + (set_local $res ($sprintf_1 $res "%s" $sep))) + (br $loop) + ) + )) + (else + (set_local $res ($pr_str_val $res $mv $print_readably)))) + + ($STRING (get_global $STRING_T) (get_global $printer_buf)) + ) + + (func $pr_str (param $mv i32 $print_readably i32) (result i32) + ($pr_str_internal 0 $mv $print_readably "") + ) + + (func $pr_str_seq (param $mv i32 $print_readably i32 $sep i32) (result i32) + ($pr_str_internal 1 $mv $print_readably $sep) ) (export "pr_str" (func $pr_str)) diff --git a/wasm/reader.wam b/wasm/reader.wam index af13e90123..6d728d96f3 100644 --- a/wasm/reader.wam +++ b/wasm/reader.wam @@ -1,21 +1,19 @@ (module $reader ;; TODO: global warning - (global $token (mut i32) 0) + (global $token_buf (mut i32) 0) (global $read_index (mut i32) 0) (func $skip_spaces (param $str i32) (result i32) - (local $found i32) - (local $c i32) + (local $found i32 $c i32) (set_local $found 0) (set_local $c (i32.load8_u (i32.add $str (get_global $read_index)))) (block $done (loop $loop ;;; while (c == ' ' || c == ',' || c == '\n') - (if (i32.and (i32.and - (i32.ne $c (CHR " ")) - (i32.ne $c (CHR ","))) - (i32.ne $c (CHR "\n"))) + (if (AND (i32.ne $c (CHR " ")) + (i32.ne $c (CHR ",")) + (i32.ne $c (CHR "\n"))) (br $done)) (set_local $found 1) ;;; c=str[++(*index)] @@ -29,8 +27,7 @@ ) (func $skip_to_eol (param $str i32) (result i32) - (local $found i32) - (local $c i32) + (local $found i32 $c i32) (set_local $found 0) (set_local $c (i32.load8_c (i32.add $str (get_global $read_index)))) (if (i32.eq $c (CHR ";")) @@ -43,7 +40,8 @@ (set_local $c (i32.load8_u (i32.add $str (get_global $read_index)))) ;;; while (c != '\0' && c != '\n') - (if (i32.and (i32.ne $c (CHR "\x00")) (i32.ne $c (CHR "\n"))) + (if (AND (i32.ne $c (CHR "\x00")) + (i32.ne $c (CHR "\n"))) (br $loop)) ) ))) @@ -61,10 +59,7 @@ ) (func $read_token (param $str i32) (result i32) - (local $token_index i32) - (local $instring i32) - (local $escaped i32) - (local $c i32) + (local $token_index i32 $instring i32 $escaped i32 $c i32) (set_local $token_index 0) (set_local $instring 0) (set_local $escaped 0) @@ -77,21 +72,21 @@ (set_global $read_index (i32.add (get_global $read_index) 1)) ;; read first character ;;; token[token_index++] = c - (i32.store8_u (i32.add (get_global $token) $token_index) $c) + (i32.store8_u (i32.add (get_global $token_buf) $token_index) $c) (set_local $token_index (i32.add $token_index 1)) ;; single/double character token - (if (i32.or (i32.eq $c (CHR "(")) - (i32.or (i32.eq $c (CHR ")")) - (i32.or (i32.eq $c (CHR "[")) - (i32.or (i32.eq $c (CHR "]")) - (i32.or (i32.eq $c (CHR "{")) - (i32.or (i32.eq $c (CHR "}")) - (i32.or (i32.eq $c (CHR "'")) - (i32.or (i32.eq $c (CHR "`")) - (i32.or (i32.eq $c (CHR "@")) - (i32.and (i32.eq $c (CHR "~")) - (i32.eq (i32.load8_u (i32.add $str (get_global $read_index))) - (CHR "@")))))))))))) + (if (OR (i32.eq $c (CHR "(")) + (i32.eq $c (CHR ")")) + (i32.eq $c (CHR "[")) + (i32.eq $c (CHR "]")) + (i32.eq $c (CHR "{")) + (i32.eq $c (CHR "}")) + (i32.eq $c (CHR "'")) + (i32.eq $c (CHR "`")) + (i32.eq $c (CHR "@")) + (AND (i32.eq $c (CHR "~")) + (i32.ne (i32.load8_u (i32.add $str (get_global $read_index))) + (CHR "@")))) (then ;; continue @@ -111,32 +106,32 @@ (if (i32.eqz $instring) (then ;; next character is token delimiter - (if (i32.or (i32.eq $c (CHR "(")) - (i32.or (i32.eq $c (CHR ")")) - (i32.or (i32.eq $c (CHR "[")) - (i32.or (i32.eq $c (CHR "]")) - (i32.or (i32.eq $c (CHR "{")) - (i32.or (i32.eq $c (CHR "}")) - (i32.or (i32.eq $c (CHR " ")) - (i32.or (i32.eq $c (CHR ",")) - (i32.eq $c (CHR "\n")))))))))) + (if (OR (i32.eq $c (CHR "(")) + (i32.eq $c (CHR ")")) + (i32.eq $c (CHR "[")) + (i32.eq $c (CHR "]")) + (i32.eq $c (CHR "{")) + (i32.eq $c (CHR "}")) + (i32.eq $c (CHR " ")) + (i32.eq $c (CHR ",")) + (i32.eq $c (CHR "\n"))) (br $done)))) ;; read next character ;;; token[token_index++] = str[(*index)++] - (i32.store8_u (i32.add (get_global $token) $token_index) + (i32.store8_u (i32.add (get_global $token_buf) $token_index) (i32.load8_u (i32.add $str (get_global $read_index)))) (set_local $token_index (i32.add $token_index 1)) (set_global $read_index (i32.add (get_global $read_index) 1)) ;;; if (token[0] == '~' && token[1] == '@') break - (if (i32.and (i32.eq (i32.load8_u (i32.add (get_global $token) 0)) - (CHR "~")) - (i32.eq (i32.load8_u (i32.add (get_global $token) 1)) - 0x40)) + (if (AND (i32.eq (i32.load8_u (i32.add (get_global $token_buf) 0)) + (CHR "~")) + (i32.eq (i32.load8_u (i32.add (get_global $token_buf) 1)) + (CHR "@"))) (br $done)) ;;; if ((!instring) || escaped) - (if (i32.or (i32.eqz $instring) $escaped) + (if (OR (i32.eqz $instring) $escaped) (then (set_local $escaped 0) (br $loop))) @@ -149,21 +144,15 @@ ))) ;;; token[token_index] = '\0' - (i32.store8_u (i32.add (get_global $token) $token_index) 0) - (get_global $token) + (i32.store8_u (i32.add (get_global $token_buf) $token_index) 0) + (get_global $token_buf) ) - (func $read_seq (param $str i32) (param $type i32) (param $end i32) - (result i32) - (local $res i32) - (local $val2 i32) - (local $val3 i32) - (local $c i32) + (func $read_seq (param $str i32 $type i32 $end i32) (result i32) + (local $res i32 $val2 i32 $val3 i32 $c i32) ;; MAP_LOOP stack - (local $ret i32) - (local $empty i32) - (local $current i32) + (local $ret i32 $empty i32 $current i32) ;; MAP_LOOP_START (set_local $res ($MAP_LOOP_START $type)) @@ -223,12 +212,29 @@ $ret ) + (func $read_macro (param $str i32 $sym i32 $with_meta i32) (result i32) + (local $first i32 $second i32 $third i32 $res i32) + (set_local $first ($STRING (get_global $SYMBOL_T) $sym)) + (set_local $second ($read_form $str)) + (set_local $res $second) + (if (get_global $error_type) (return $res)) + (if (i32.eqz $with_meta) + (then + (set_local $res ($LIST2 $first $second))) + (else + (set_local $third ($read_form $str)) + (set_local $res ($LIST3 $first $third $second)) + ;; release values, list has ownership + ($RELEASE $third))) + ;; release values, list has ownership + ($RELEASE $second) + ($RELEASE $first) + $res + ) + (func $read_form (param $str i32) (result i32) ;;($STRING (get_global $STRING_T) $str) - (local $tok i32) - (local $c0 i32) - (local $c1 i32) - (local $res i32) + (local $tok i32 $c0 i32 $c1 i32 $res i32 $slen i32) (if (get_global $error_type) (return 0)) @@ -241,40 +247,67 @@ (if (i32.eq $c0 0) (then (return ($INC_REF (get_global $NIL)))) - (else (if (i32.or (i32.and (i32.ge_u $c0 (CHR "0")) - (i32.le_u $c0 (CHR "9"))) - (i32.and (i32.eq $c0 (CHR "-")) - (i32.and (i32.ge_u $c1 (CHR "0")) - (i32.le_u $c1 (CHR "9"))))) + (else (if (OR (AND (i32.ge_u $c0 (CHR "0")) + (i32.le_u $c0 (CHR "9"))) + (AND (i32.eq $c0 (CHR "-")) + (i32.ge_u $c1 (CHR "0")) + (i32.le_u $c1 (CHR "9")))) (then - (return ($INTEGER ($ATOI $tok)))) + (return ($INTEGER ($atoi $tok)))) (else (if (i32.eq $c0 (CHR ":")) (then (i32.store8_u $tok (CHR "\x7f")) (return ($STRING (get_global $STRING_T) $tok))) (else (if (i32.eq $c0 (CHR "\"")) (then - ;; TODO: unescape - (i32.store8_u (i32.sub_u (i32.add $tok ($STRING_LEN $tok)) 1) - (CHR "\x00")) - (return ($STRING (get_global $STRING_T) (i32.add $tok 1)))) + (set_local $slen ($strlen (i32.add $tok 1))) + (if (i32.ne (i32.load8_u (i32.add $tok $slen)) (CHR "\"")) + (then + ($THROW_STR_0 "expected '\"'") + (return 0)) + (else + ;; unescape backslashes, quotes, and newlines + ;; remove the trailing quote + (i32.store8_u (i32.add $tok $slen) (CHR "\x00")) + (set_local $tok (i32.add $tok 1)) + (drop ($REPLACE3 0 $tok + "\\\"" "\"" + "\\n" "\n" + "\\\\" "\\")) + (return ($STRING (get_global $STRING_T) $tok))))) + (else (if (i32.eqz ($strcmp "nil" $tok)) + (then (return ($INC_REF (get_global $NIL)))) + (else (if (i32.eqz ($strcmp "false" $tok)) + (then (return ($INC_REF (get_global $FALSE)))) + (else (if (i32.eqz ($strcmp "true" $tok)) + (then (return ($INC_REF (get_global $TRUE)))) + (else (if (i32.eqz ($strcmp "'" $tok)) + (then (return ($read_macro $str "quote" 0))) + (else (if (i32.eqz ($strcmp "`" $tok)) + (then (return ($read_macro $str "quasiquote" 0))) + (else (if (i32.eqz ($strcmp "~@" $tok)) + (then (return ($read_macro $str "splice-unquote" 0))) + (else (if (i32.eqz ($strcmp "~" $tok)) + (then (return ($read_macro $str "unquote" 0))) + (else (if (i32.eqz ($strcmp "^" $tok)) + (then (return ($read_macro $str "with-meta" 1))) + (else (if (i32.eqz ($strcmp "@" $tok)) + (then (return ($read_macro $str "deref" 0))) (else (if (i32.eq $c0 (CHR "(")) - (then - (return ($read_seq $str (get_global $LIST_T) (CHR ")")))) + (then (return ($read_seq $str (get_global $LIST_T) (CHR ")")))) (else (if (i32.eq $c0 (CHR "[")) - (then - (return ($read_seq $str (get_global $VECTOR_T) (CHR "]")))) + (then (return ($read_seq $str (get_global $VECTOR_T) (CHR "]")))) (else (if (i32.eq $c0 (CHR "{")) - (then - (return ($read_seq $str (get_global $HASHMAP_T) (CHR "}")))) - (else (if (i32.or (i32.eq $c0 (CHR ")")) - (i32.or (i32.eq $c0 (CHR "]")) - (i32.eq $c0 (CHR "}")))) + (then (return ($read_seq $str (get_global $HASHMAP_T) (CHR "}")))) + (else (if (OR (i32.eq $c0 (CHR ")")) + (i32.eq $c0 (CHR "]")) + (i32.eq $c0 (CHR "}"))) (then ($THROW_STR_1 "unexpected '%c'" $c0) (return 0)) (else - (return ($STRING (get_global $SYMBOL_T) $tok)))))))))))))))))) + (return ($STRING (get_global $SYMBOL_T) $tok)))) + )))))))))))))))))))))))))))))))) ) (func $read_str (param $str i32) (result i32) diff --git a/wasm/step0_repl.wam b/wasm/step0_repl.wam index fd663b607c..a1b4a07add 100644 --- a/wasm/step0_repl.wam +++ b/wasm/step0_repl.wam @@ -1,6 +1,4 @@ (module $step0_repl - (import "env" "memory" (memory $0 256)) - (import "env" "memoryBase" (global $memoryBase i32)) ;; READ (func $READ (param $str i32) (result i32) @@ -25,7 +23,7 @@ ;; Constant location/value definitions (local $line i32) - ;; Start + ;; Start REPL (block $repl_done (loop $repl_loop (set_local $line ($readline "user> ")) diff --git a/wasm/step1_read_print.wam b/wasm/step1_read_print.wam index 2253021df1..a9e017b0b0 100644 --- a/wasm/step1_read_print.wam +++ b/wasm/step1_read_print.wam @@ -1,6 +1,4 @@ (module $step1_read_print - (import "env" "memory" (memory $0 256)) - (import "env" "memoryBase" (global $memoryBase i32)) ;; READ (func $READ (param $str i32) (result i32) @@ -8,20 +6,18 @@ ) ;; EVAL - (func $EVAL (param $ast i32) (param $env i32) (result i32) + (func $EVAL (param $ast i32 $env i32) (result i32) $ast ) ;; PRINT (func $PRINT (param $ast i32) (result i32) - ($pr_str $ast) + ($pr_str $ast 1) ) ;; REPL - (func $rep (param $line i32) (param $env i32) (result i32) - (local $mv1 i32) - (local $mv2 i32) - (local $ms i32) + (func $REP (param $line i32 $env i32) (result i32) + (local $mv1 i32 $mv2 i32 $ms i32) (block $rep_done (set_local $mv1 ($READ $line)) (if (get_global $error_type) (br $rep_done)) @@ -33,15 +29,13 @@ (set_local $ms ($PRINT $mv2)) ) -;; ($PR_MEMORY -1 -1) + ;; release memory from MAL_READ ($RELEASE $mv1) $ms ) (func $main (result i32) - ;; Constant location/value definitions - (local $line i32) - (local $res i32) + (local $line i32 $res i32) ;; DEBUG ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) @@ -49,11 +43,7 @@ ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) ($printf_1 "mem: 0x%x\n" (get_global $mem)) ;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) - ($PR_MEMORY -1 -1) -;; ($PR_MEMORY_RAW (get_global $mem) -;; (i32.add (get_global $mem) -;; (i32.mul_u (get_global $mem_unused_start) -;; 8))) + ;;($PR_MEMORY -1 -1) ;; Start (block $repl_done @@ -64,7 +54,7 @@ (then ($free $line) (br $repl_loop))) - (set_local $res ($rep $line 0)) + (set_local $res ($REP $line 0)) (if (get_global $error_type) (then ($printf_1 "Error: %s\n" (get_global $error_str)) @@ -77,7 +67,7 @@ (br $repl_loop))) ($print "\n") - ($PR_MEMORY -1 -1) + ;;($PR_MEMORY -1 -1) 0 ) diff --git a/wasm/step2_eval.wam b/wasm/step2_eval.wam index ca9a24a7ac..c8852852b2 100644 --- a/wasm/step2_eval.wam +++ b/wasm/step2_eval.wam @@ -1,6 +1,6 @@ -(module $step1_read_print - (import "env" "memory" (memory $0 256)) - (import "env" "memoryBase" (global $memoryBase i32)) +(module $step2_eval + + (global $repl_env (mut i32) (i32.const 0)) ;; READ (func $READ (param $str i32) (result i32) @@ -8,20 +8,16 @@ ) ;; EVAL - (func $EVAL_AST (param $ast i32) (param $env i32) (result i32) - (local $res i32) - (local $val2 i32) - (local $val3 i32) - (local $ret i32) - (local $empty i32) - (local $current i32) - (local $type i32) + (func $EVAL_AST (param $ast i32 $env i32) (result i32) + (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) + (local $ret i32 $empty i32 $current i32) (local $res2 i64) - (local $found i32) (if (get_global $error_type) (return 0)) (set_local $type ($TYPE $ast)) + ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) + ;;; switch(type) (block $done (block $default (block (block @@ -50,8 +46,7 @@ (block $done (loop $loop ;; check if we are done evaluating the source sequence - (if (i32.eq ($VAL0 $ast) 0) - (br $done)) + (if (i32.eq ($VAL0 $ast) 0) (br $done)) (if (i32.eq $type (get_global $HASHMAP_T)) (then @@ -73,7 +68,7 @@ (then (set_local $val3 $val2) (set_local $val2 ($MEM_VAL1_ptr $ast)) - (drop ($INC_REF $ast)))) + (drop ($INC_REF $val2)))) ;; MAP_LOOP_UPDATE (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) @@ -105,13 +100,9 @@ (elem $add $subtract $multiply $divide)) - (func $EVAL (param $ast i32) (param $env i32) (result i32) + (func $EVAL (param $ast i32 $env i32) (result i32) (local $res i32) - (local $f_args i32) - (local $f i32) - (local $args i32) - (local $type i32) - (local $ftype i32) + (local $type i32 $ftype i32 $f_args i32 $f i32 $args i32) (set_local $res 0) (set_local $f_args 0) @@ -154,14 +145,12 @@ ;; PRINT (func $PRINT (param $ast i32) (result i32) - ($pr_str $ast) + ($pr_str $ast 1) ) ;; REPL - (func $rep (param $line i32) (param $env i32) (result i32) - (local $mv1 i32) - (local $mv2 i32) - (local $ms i32) + (func $REP (param $line i32 $env i32) (result i32) + (local $mv1 i32 $mv2 i32 $ms i32) (block $rep_done (set_local $mv1 ($READ $line)) (if (get_global $error_type) (br $rep_done)) @@ -197,10 +186,7 @@ ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) (func $main (result i32) - ;; Constant location/value definitions - (local $line i32) - (local $res i32) - (local $repl_env i32) + (local $line i32 $res i32 $repl_env i32) ;; DEBUG ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) @@ -209,20 +195,17 @@ ($printf_1 "mem: 0x%x\n" (get_global $mem)) ;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) - (set_local $repl_env ($HASHMAP)) + (set_global $repl_env ($HASHMAP)) + (set_local $repl_env (get_global $repl_env)) (set_local $repl_env ($ASSOC1_S $repl_env "+" ($FUNCTION 0))) (set_local $repl_env ($ASSOC1_S $repl_env "-" ($FUNCTION 1))) (set_local $repl_env ($ASSOC1_S $repl_env "*" ($FUNCTION 2))) (set_local $repl_env ($ASSOC1_S $repl_env "/" ($FUNCTION 3))) - ($PR_MEMORY -1 -1) -;; ($PR_MEMORY_RAW (get_global $mem) -;; (i32.add (get_global $mem) -;; (i32.mul_u (get_global $mem_unused_start) -;; 8))) + ;;($PR_MEMORY -1 -1) - ;; Start + ;; Start REPL (block $repl_done (loop $repl_loop (set_local $line ($readline "user> ")) @@ -231,7 +214,7 @@ (then ($free $line) (br $repl_loop))) - (set_local $res ($rep $line $repl_env)) + (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then ($printf_1 "Error: %s\n" (get_global $error_str)) @@ -244,7 +227,7 @@ (br $repl_loop))) ($print "\n") - ($PR_MEMORY -1 -1) + ;;($PR_MEMORY -1 -1) 0 ) diff --git a/wasm/step3_env.wam b/wasm/step3_env.wam index 8044dff42e..83de79e720 100644 --- a/wasm/step3_env.wam +++ b/wasm/step3_env.wam @@ -1,6 +1,6 @@ -(module $step1_read_print - (import "env" "memory" (memory $0 256)) - (import "env" "memoryBase" (global $memoryBase i32)) +(module $step3_env + + (global $repl_env (mut i32) (i32.const 0)) ;; READ (func $READ (param $str i32) (result i32) @@ -8,19 +8,15 @@ ) ;; EVAL - (func $EVAL_AST (param $ast i32) (param $env i32) (result i32) - (local $res i32) - (local $val2 i32) - (local $val3 i32) - (local $ret i32) - (local $empty i32) - (local $current i32) - (local $type i32) - (local $found i32) + (func $EVAL_AST (param $ast i32 $env i32) (result i32) + (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) + (local $ret i32 $empty i32 $current i32) (if (get_global $error_type) (return 0)) (set_local $type ($TYPE $ast)) + ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) + ;;; switch(type) (block $done (block $default (block (block @@ -41,8 +37,7 @@ (block $done (loop $loop ;; check if we are done evaluating the source sequence - (if (i32.eq ($VAL0 $ast) 0) - (br $done)) + (if (i32.eq ($VAL0 $ast) 0) (br $done)) (if (i32.eq $type (get_global $HASHMAP_T)) (then @@ -64,7 +59,7 @@ (then (set_local $val3 $val2) (set_local $val2 ($MEM_VAL1_ptr $ast)) - (drop ($INC_REF $ast)))) + (drop ($INC_REF $val2)))) ;; MAP_LOOP_UPDATE (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) @@ -103,17 +98,10 @@ (func $MAL_GET_A3 (param $ast i32) (result i32) ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) - (func $EVAL (param $ast i32) (param $env i32) (result i32) + (func $EVAL (param $ast i32 $env i32) (result i32) (local $res i32) - (local $f_args i32) - (local $f i32) - (local $args i32) - (local $type i32) - (local $ftype i32) - (local $a0 i32) - (local $a0sym i32) - (local $a1 i32) - (local $a2 i32) + (local $type i32 $ftype i32 $f_args i32 $f i32 $args i32) + (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) (local $let_env i32) (set_local $res 0) @@ -126,8 +114,7 @@ (if (get_global $error_type) (return 0)) - (if (i32.ne $type (get_global $LIST_T)) - (return ($EVAL_AST $ast $env))) + (if (i32.ne $type (get_global $LIST_T)) (return ($EVAL_AST $ast $env))) ;; APPLY_LIST (if ($EMPTY_Q $ast) (return ($INC_REF $ast))) @@ -203,15 +190,14 @@ $res ) + ;; PRINT (func $PRINT (param $ast i32) (result i32) - ($pr_str $ast) + ($pr_str $ast 1) ) ;; REPL - (func $rep (param $line i32) (param $env i32) (result i32) - (local $mv1 i32) - (local $mv2 i32) - (local $ms i32) + (func $REP (param $line i32 $env i32) (result i32) + (local $mv1 i32 $mv2 i32 $ms i32) (block $rep_done (set_local $mv1 ($READ $line)) (if (get_global $error_type) (br $rep_done)) @@ -250,10 +236,7 @@ ($INC_REF (get_global $NIL))) (func $main (result i32) - ;; Constant location/value definitions - (local $line i32) - (local $res i32) - (local $repl_env i32) + (local $line i32 $res i32 $repl_env i32) ;; DEBUG ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) @@ -262,20 +245,17 @@ ($printf_1 "mem: 0x%x\n" (get_global $mem)) ;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) - (set_local $repl_env ($ENV_NEW (get_global $NIL))) + (set_global $repl_env ($ENV_NEW (get_global $NIL))) + (set_local $repl_env (get_global $repl_env)) (drop ($ENV_SET_S $repl_env "+" ($FUNCTION 0))) (drop ($ENV_SET_S $repl_env "-" ($FUNCTION 1))) (drop ($ENV_SET_S $repl_env "*" ($FUNCTION 2))) (drop ($ENV_SET_S $repl_env "/" ($FUNCTION 3))) - ($PR_MEMORY -1 -1) -;; ($PR_MEMORY_RAW (get_global $mem) -;; (i32.add (get_global $mem) -;; (i32.mul_u (get_global $mem_unused_start) -;; 8))) + ;;($PR_MEMORY -1 -1) - ;; Start + ;; Start REPL (block $repl_done (loop $repl_loop (set_local $line ($readline "user> ")) @@ -284,7 +264,7 @@ (then ($free $line) (br $repl_loop))) - (set_local $res ($rep $line $repl_env)) + (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then ($printf_1 "Error: %s\n" (get_global $error_str)) @@ -297,7 +277,7 @@ (br $repl_loop))) ($print "\n") - ($PR_MEMORY -1 -1) + ;;($PR_MEMORY -1 -1) 0 ) diff --git a/wasm/step4_if_fn_do.wam b/wasm/step4_if_fn_do.wam new file mode 100644 index 0000000000..f87754176a --- /dev/null +++ b/wasm/step4_if_fn_do.wam @@ -0,0 +1,325 @@ +(module $step4_if_fn_do + + (global $repl_env (mut i32) (i32.const 0)) + + ;; READ + (func $READ (param $str i32) (result i32) + ($read_str $str) + ) + + ;; EVAL + (func $EVAL_AST (param $ast i32 $env i32) (result i32) + (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) + (local $ret i32 $empty i32 $current i32) + + (if (get_global $error_type) (return 0)) + (set_local $type ($TYPE $ast)) + + ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) + + ;;; switch(type) + (block $done + (block $default (block (block + (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) + ;; symbol + ;; found/res returned as hi 32/lo 32 of i64 + (set_local $res ($ENV_GET $env $ast)) + (br $done)) + ;; list, vector, hashmap + ;; MAP_LOOP_START + (set_local $res ($MAP_LOOP_START $type)) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (block $done + (loop $loop + ;; check if we are done evaluating the source sequence + (if (i32.eq ($VAL0 $ast) 0) (br $done)) + + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (else + (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (set_local $val2 $res) + + ;; if error, release the unattached element + (if (get_global $error_type) + (then + ($RELEASE $res) + (set_local $res 0) + (br $done))) + + ;; for hash-maps, copy the key (inc ref since we are going + ;; to release it below) + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $val3 $val2) + (set_local $val2 ($MEM_VAL1_ptr $ast)) + (drop ($INC_REF $val2)))) + + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (set_local $ast ($MEM_VAL0_ptr $ast)) + + (br $loop) + ) + ) + ;; MAP_LOOP_DONE + (set_local $res $ret) + ;; EVAL_AST_RETURN: nothing to do + (br $done)) + ;; default + (set_local $res ($INC_REF $ast)) + ) + + $res + ) + + (func $MAL_GET_A1 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))) + (func $MAL_GET_A2 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast)))) + (func $MAL_GET_A3 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) + + (func $EVAL (param $ast i32 $env i32) (result i32) + (local $res i32 $el i32) + (local $type i32 $ftype i32 $f_args i32 $f i32 $args i32) + (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32 $a3 i32) + (local $let_env i32 $fn_env i32 $a i32) + + (set_local $res 0) + (set_local $f_args 0) + (set_local $f 0) + (set_local $args 0) + + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + + (if (get_global $error_type) + (return 0)) + + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (return ($EVAL_AST $ast $env))) + + ;; APPLY_LIST + (if ($EMPTY_Q $ast) + (return ($INC_REF $ast))) + + (set_local $a0 ($MEM_VAL1_ptr $ast)) + (set_local $a0sym "") + (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) + (set_local $a0sym ($to_String $a0))) + + (if (i32.eqz ($strcmp "def!" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($EVAL $a2 $env)) + (if (get_global $error_type) (return $res)) + + ;; set a1 in env to a2 + (set_local $res ($ENV_SET $env $a1 $res))) + (else (if (i32.eqz ($strcmp "let*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + + ;; create new environment with outer as current environment + (set_local $let_env ($ENV_NEW $env)) + + (block $done + (loop $loop + (if (i32.eqz ($VAL0 $a1)) + (br $done)) + ;; eval current A1 odd element + (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) + $let_env)) + + (if (get_global $error_type) (br $done)) + + ;; set key/value in the let environment + (set_local $res ($ENV_SET $let_env ($MEM_VAL1_ptr $a1) $res)) + ;; release our use, ENV_SET took ownership + ($RELEASE $res) + + ;; skip to the next pair of a1 elements + (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (br $loop) + ) + ) + (set_local $res ($EVAL $a2 $let_env)) + ;; EVAL_RETURN + ($RELEASE $let_env)) + (else (if (i32.eqz ($strcmp "do" $a0sym)) + (then + (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env)) + (set_local $res ($LAST $el)) + ($RELEASE $el)) + (else (if (i32.eqz ($strcmp "if" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $res ($EVAL $a1 $env)) + + (if (get_global $error_type) + (then (nop)) + (else (if (OR (i32.eq $res (get_global $NIL)) + (i32.eq $res (get_global $FALSE))) + (then + ($RELEASE $res) + ;; if no false case (A3), return nil + (if (i32.lt_u ($COUNT $ast) 4) + (then + (set_local $res ($INC_REF (get_global $NIL)))) + (else + (set_local $a3 ($MAL_GET_A3 $ast)) + (set_local $res ($EVAL $a3 $env))))) + (else + ($RELEASE $res) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($EVAL $a2 $env))))))) + (else (if (i32.eqz ($strcmp "fn*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($ALLOC (get_global $MALFUNC_T) $a2 $a1 $env))) + (else + ;; EVAL_INVOKE + (set_local $res ($EVAL_AST $ast $env)) + (set_local $f_args $res) + + ;; if error, return f/args for release by caller + (if (get_global $error_type) (return $f_args)) + + ;; rest + (set_local $args ($MEM_VAL0_ptr $f_args)) + ;; value + (set_local $f ($MEM_VAL1_ptr $f_args)) + + (set_local $ftype ($TYPE $f)) + (if (i32.eq $ftype (get_global $FUNCTION_T)) + (then + (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f))) + ;; release f/args + ($RELEASE $f_args)) + (else (if (i32.eq $ftype (get_global $MALFUNC_T)) + (then + (set_local $fn_env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + ($MEM_VAL1_ptr $f) $args)) + + ;; claim the AST before releasing the list containing it + (set_local $a ($MEM_VAL0_ptr $f)) + (drop ($INC_REF $a)) + + ;; release f/args + ($RELEASE $f_args) + + (set_local $res ($EVAL $a $fn_env)) + ;; EVAL_RETURN + ($RELEASE $fn_env) + ($RELEASE $a)) + (else + ;; create new environment using env and params stored in function + ($THROW_STR_1 "apply of non-function type: %d\n" $type) + (set_local $res 0) + ($RELEASE $f_args))))))))))))))) + + $res + ) + + ;; PRINT + (func $PRINT (param $ast i32) (result i32) + ($pr_str $ast 1) + ) + + ;; REPL + (func $RE (param $line i32 $env i32) (result i32) + (local $mv1 i32 $res i32) + (block $rep_done + (set_local $mv1 ($READ $line)) + (if (get_global $error_type) (br $rep_done)) + + (set_local $res ($EVAL $mv1 $env)) + ) + + ;; release memory from MAL_READ + ($RELEASE $mv1) + $res + ) + + (func $REP (param $line i32 $env i32) (result i32) + (local $mv2 i32 $ms i32) + (block $rep_done + (set_local $mv2 ($RE $line $env)) + (if (get_global $error_type) (br $rep_done)) + +;; ($PR_MEMORY -1 -1) + (set_local $ms ($PRINT $mv2)) + ) + + ;; release memory from RE + ($RELEASE $mv2) + $ms + ) + + (func $main (result i32) + (local $line i32 $res i32 $repl_env i32) + + ;; DEBUG + ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) + ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) + ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) + ($printf_1 "mem: 0x%x\n" (get_global $mem)) +;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) + + (set_global $repl_env ($ENV_NEW (get_global $NIL))) + (set_local $repl_env (get_global $repl_env)) + + ;; core.EXT: defined in wasm + ($add_core_ns $repl_env) + + ;; core.mal: defined using the language itself + ($RELEASE ($RE "(def! not (fn* (a) (if a false true)))" $repl_env)) + + ;;($PR_MEMORY -1 -1) + + ;; Start REPL + (block $repl_done + (loop $repl_loop + (set_local $line ($readline "user> ")) + (if (i32.eqz $line) (br $repl_done)) + (if (i32.eq (i32.load8_u $line) 0) + (then + ($free $line) + (br $repl_loop))) + (set_local $res ($REP $line $repl_env)) + (if (get_global $error_type) + (then + ($printf_1 "Error: %s\n" (get_global $error_str)) + (set_global $error_type 0)) + (else + ($printf_1 "%s\n" ($to_String $res)))) + ($RELEASE $res) +;; ($PR_MEMORY -1 -1) + ($free $line) + (br $repl_loop))) + + ($print "\n") + ;;($PR_MEMORY -1 -1) + 0 + ) + + + (export "_main" (func $main)) + (export "__post_instantiate" (func $init_memory)) +) + diff --git a/wasm/step5_tco.wam b/wasm/step5_tco.wam new file mode 100644 index 0000000000..e9db72eb5b --- /dev/null +++ b/wasm/step5_tco.wam @@ -0,0 +1,374 @@ +(module $step5_tco + + (global $repl_env (mut i32) (i32.const 0)) + + ;; READ + (func $READ (param $str i32) (result i32) + ($read_str $str) + ) + + ;; EVAL + (func $EVAL_AST (param $ast i32 $env i32 $skiplast i32) (result i32) + (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) + (local $ret i32 $empty i32 $current i32) + + (if (get_global $error_type) (return 0)) + (set_local $type ($TYPE $ast)) + + ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) + + ;;; switch(type) + (block $done + (block $default (block (block + (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) + ;; symbol + ;; found/res returned as hi 32/lo 32 of i64 + (set_local $res ($ENV_GET $env $ast)) + (br $done)) + ;; list, vector, hashmap + ;; MAP_LOOP_START + (set_local $res ($MAP_LOOP_START $type)) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (block $done + (loop $loop + ;; check if we are done evaluating the source sequence + (if (i32.eq ($VAL0 $ast) 0) (br $done)) + + (if $skiplast + (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0) (br $done))) + + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (else + (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (set_local $val2 $res) + + ;; if error, release the unattached element + (if (get_global $error_type) + (then + ($RELEASE $res) + (set_local $res 0) + (br $done))) + + ;; for hash-maps, copy the key (inc ref since we are going + ;; to release it below) + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $val3 $val2) + (set_local $val2 ($MEM_VAL1_ptr $ast)) + (drop ($INC_REF $val2)))) + + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (set_local $ast ($MEM_VAL0_ptr $ast)) + + (br $loop) + ) + ) + ;; MAP_LOOP_DONE + (set_local $res $ret) + ;; EVAL_AST_RETURN: nothing to do + (br $done)) + ;; default + (set_local $res ($INC_REF $ast)) + ) + + $res + ) + + (func $MAL_GET_A1 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))) + (func $MAL_GET_A2 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast)))) + (func $MAL_GET_A3 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) + + (func $EVAL (param $orig_ast i32 $orig_env i32) (result i32) + (local $ast i32 $env i32 $prev_ast i32 $prev_env i32 $res i32 $el i32) + (local $type i32 $ftype i32 $f_args i32 $f i32 $args i32) + (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) + + (set_local $ast $orig_ast) + (set_local $env $orig_env) + (set_local $prev_ast 0) + (set_local $prev_env 0) + (set_local $res 0) + + (block $EVAL_return + (loop $TCO_loop + + (set_local $f_args 0) + (set_local $f 0) + (set_local $args 0) + + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + + (if (get_global $error_type) + (then + (set_local $res 0) + (br $EVAL_return))) + + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (then + (set_local $res ($EVAL_AST $ast $env 0)) + (br $EVAL_return))) + + ;; APPLY_LIST + (if ($EMPTY_Q $ast) + (then + (set_local $res ($INC_REF $ast)) + (br $EVAL_return))) + + (set_local $a0 ($MEM_VAL1_ptr $ast)) + (set_local $a0sym "") + (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) + (set_local $a0sym ($to_String $a0))) + + (if (i32.eqz ($strcmp "def!" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($EVAL $a2 $env)) + (if (get_global $error_type) (br $EVAL_return)) + + ;; set a1 in env to a2 + (set_local $res ($ENV_SET $env $a1 $res)) + (br $EVAL_return)) + (else (if (i32.eqz ($strcmp "let*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + + ;; create new environment with outer as current environment + (set_local $prev_env $env) ;; save env for later release + (set_local $env ($ENV_NEW $env)) + + (block $done + (loop $loop + (if (i32.eqz ($VAL0 $a1)) + (br $done)) + ;; eval current A1 odd element + (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) + + (if (get_global $error_type) (br $done)) + + ;; set key/value in the let environment + (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) + ;; release our use, ENV_SET took ownership + ($RELEASE $res) + + ;; skip to the next pair of a1 elements + (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (br $loop) + ) + ) + + ;; release previous environment if not the current EVAL env + (if (i32.ne $prev_env $orig_env) + (then + ($RELEASE $prev_env) + (set_local $prev_env 0))) + + (set_local $ast $a2) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "do" $a0sym)) + (then + ;; EVAL the rest through second to last + (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) + (set_local $ast ($LAST $ast)) + ($RELEASE $el) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "if" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $res ($EVAL $a1 $env)) + + (if (get_global $error_type) + (then (nop)) + (else (if (OR (i32.eq $res (get_global $NIL)) + (i32.eq $res (get_global $FALSE))) + (then + ($RELEASE $res) + ;; if no false case (A3), return nil + (if (i32.lt_u ($COUNT $ast) 4) + (then + (set_local $res ($INC_REF (get_global $NIL))) + (br $EVAL_return)) + (else + (set_local $ast ($MAL_GET_A3 $ast))))) + (else + ($RELEASE $res) + (set_local $ast ($MAL_GET_A2 $ast)))))) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "fn*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($MALFUNC $a2 $a1 $env)) + (br $EVAL_return)) + (else + ;; EVAL_INVOKE + (set_local $res ($EVAL_AST $ast $env 0)) + (set_local $f_args $res) + + ;; if error, return f/args for release by caller + (if (get_global $error_type) + (then + (set_local $res $f_args) + (br $EVAL_return))) + + (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest + (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value + + (set_local $ftype ($TYPE $f)) + (if (i32.eq $ftype (get_global $FUNCTION_T)) + (then + (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f))) + ;; release f/args + ($RELEASE $f_args) + (br $EVAL_return)) + (else (if (i32.eq $ftype (get_global $MALFUNC_T)) + (then + ;; save the current environment for release + (set_local $prev_env $env) + ;; create new environment using env and params stored in function + (set_local $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + ($MEM_VAL1_ptr $f) $args)) + + ;; release previous environment if not the current EVAL env + ;; because our new env refers to it and we no longer need to + ;; track it (since we are TCO recurring) + (if (i32.ne $prev_env $orig_env) + (then + ($RELEASE $prev_env) + (set_local $prev_env 0))) + + ;; claim the AST before releasing the list containing it + (set_local $ast ($MEM_VAL0_ptr $f)) + (drop ($INC_REF $ast)) + + ;; if we have already been here via TCO, release previous + ;; ast + ;; PEND_A_LV + (if $prev_ast ($RELEASE $prev_ast)) + (set_local $prev_ast $ast) + + ;; release f/args + ($RELEASE $f_args) + + (br $TCO_loop)) + (else + ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) + (set_local $res 0) + ($RELEASE $f_args) + (br $EVAL_return))))))))))))))) + + ) ;; end of TCO_loop + ) ;; end of EVAL_return + + ;; EVAL_RETURN + (if (i32.ne $env $orig_env) ($RELEASE $env)) + (if $prev_ast ($RELEASE $prev_ast)) + + $res + ) + + ;; PRINT + (func $PRINT (param $ast i32) (result i32) + ($pr_str $ast 1) + ) + + ;; REPL + (func $RE (param $line i32 $env i32) (result i32) + (local $mv1 i32 $res i32) + (block $rep_done + (set_local $mv1 ($READ $line)) + (if (get_global $error_type) (br $rep_done)) + + (set_local $res ($EVAL $mv1 $env)) + ) + + ;; release memory from MAL_READ + ($RELEASE $mv1) + $res + ) + + (func $REP (param $line i32 $env i32) (result i32) + (local $mv2 i32 $ms i32) + (block $rep_done + (set_local $mv2 ($RE $line $env)) + (if (get_global $error_type) (br $rep_done)) + +;; ($PR_MEMORY -1 -1) + (set_local $ms ($PRINT $mv2)) + ) + + ;; release memory from RE + ($RELEASE $mv2) + $ms + ) + + (func $main (result i32) + (local $line i32 $res i32 $repl_env i32) + + ;; DEBUG + ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) + ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) + ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) + ($printf_1 "mem: 0x%x\n" (get_global $mem)) +;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) + + (set_global $repl_env ($ENV_NEW (get_global $NIL))) + (set_local $repl_env (get_global $repl_env)) + + ;; core.EXT: defined in wasm + ($add_core_ns $repl_env) + + ;; core.mal: defined using the language itself + ($RELEASE ($RE "(def! not (fn* (a) (if a false true)))" $repl_env)) + + ;;($PR_MEMORY -1 -1) + + ;; Start REPL + (block $repl_done + (loop $repl_loop + (set_local $line ($readline "user> ")) + (if (i32.eqz $line) (br $repl_done)) + (if (i32.eq (i32.load8_u $line) 0) + (then + ($free $line) + (br $repl_loop))) + (set_local $res ($REP $line $repl_env)) + (if (get_global $error_type) + (then + ($printf_1 "Error: %s\n" (get_global $error_str)) + (set_global $error_type 0)) + (else + ($printf_1 "%s\n" ($to_String $res)))) + ($RELEASE $res) +;; ($PR_MEMORY -1 -1) + ($free $line) + (br $repl_loop))) + + ($print "\n") + ;;($PR_MEMORY -1 -1) + 0 + ) + + + (export "_main" (func $main)) + (export "__post_instantiate" (func $init_memory)) +) + diff --git a/wasm/step6_file.wam b/wasm/step6_file.wam new file mode 100644 index 0000000000..e58c424529 --- /dev/null +++ b/wasm/step6_file.wam @@ -0,0 +1,430 @@ +(module $step6_file + + (global $repl_env (mut i32) (i32.const 0)) + + ;; READ + (func $READ (param $str i32) (result i32) + ($read_str $str) + ) + + ;; EVAL + (func $EVAL_AST (param $ast i32 $env i32 $skiplast i32) (result i32) + (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) + (local $ret i32 $empty i32 $current i32) + + (if (get_global $error_type) (return 0)) + (set_local $type ($TYPE $ast)) + + ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) + + ;;; switch(type) + (block $done + (block $default (block (block + (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) + ;; symbol + ;; found/res returned as hi 32/lo 32 of i64 + (set_local $res ($ENV_GET $env $ast)) + (br $done)) + ;; list, vector, hashmap + ;; MAP_LOOP_START + (set_local $res ($MAP_LOOP_START $type)) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (block $done + (loop $loop + ;; check if we are done evaluating the source sequence + (if (i32.eq ($VAL0 $ast) 0) (br $done)) + + (if $skiplast + (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0) (br $done))) + + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (else + (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (set_local $val2 $res) + + ;; if error, release the unattached element + (if (get_global $error_type) + (then + ($RELEASE $res) + (set_local $res 0) + (br $done))) + + ;; for hash-maps, copy the key (inc ref since we are going + ;; to release it below) + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $val3 $val2) + (set_local $val2 ($MEM_VAL1_ptr $ast)) + (drop ($INC_REF $val2)))) + + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (set_local $ast ($MEM_VAL0_ptr $ast)) + + (br $loop) + ) + ) + ;; MAP_LOOP_DONE + (set_local $res $ret) + ;; EVAL_AST_RETURN: nothing to do + (br $done)) + ;; default + (set_local $res ($INC_REF $ast)) + ) + + $res + ) + + (func $MAL_GET_A1 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))) + (func $MAL_GET_A2 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast)))) + (func $MAL_GET_A3 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) + + (func $EVAL (param $orig_ast i32 $orig_env i32) (result i32) + (local $ast i32 $env i32 $prev_ast i32 $prev_env i32 $res i32 $el i32) + (local $type i32 $ftype i32 $f_args i32 $f i32 $args i32) + (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) + + (set_local $ast $orig_ast) + (set_local $env $orig_env) + (set_local $prev_ast 0) + (set_local $prev_env 0) + (set_local $res 0) + + (block $EVAL_return + (loop $TCO_loop + + (set_local $f_args 0) + (set_local $f 0) + (set_local $args 0) + + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + + (if (get_global $error_type) + (then + (set_local $res 0) + (br $EVAL_return))) + + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (then + (set_local $res ($EVAL_AST $ast $env 0)) + (br $EVAL_return))) + + ;; APPLY_LIST + (if ($EMPTY_Q $ast) + (then + (set_local $res ($INC_REF $ast)) + (br $EVAL_return))) + + (set_local $a0 ($MEM_VAL1_ptr $ast)) + (set_local $a0sym "") + (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) + (set_local $a0sym ($to_String $a0))) + + (if (i32.eqz ($strcmp "def!" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($EVAL $a2 $env)) + (if (get_global $error_type) (br $EVAL_return)) + + ;; set a1 in env to a2 + (set_local $res ($ENV_SET $env $a1 $res)) + (br $EVAL_return)) + (else (if (i32.eqz ($strcmp "let*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + + ;; create new environment with outer as current environment + (set_local $prev_env $env) ;; save env for later release + (set_local $env ($ENV_NEW $env)) + + (block $done + (loop $loop + (if (i32.eqz ($VAL0 $a1)) + (br $done)) + ;; eval current A1 odd element + (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) + + (if (get_global $error_type) (br $done)) + + ;; set key/value in the let environment + (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) + ;; release our use, ENV_SET took ownership + ($RELEASE $res) + + ;; skip to the next pair of a1 elements + (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (br $loop) + ) + ) + + ;; release previous environment if not the current EVAL env + (if (i32.ne $prev_env $orig_env) + (then + ($RELEASE $prev_env) + (set_local $prev_env 0))) + + (set_local $ast $a2) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "do" $a0sym)) + (then + ;; EVAL the rest through second to last + (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) + (set_local $ast ($LAST $ast)) + ($RELEASE $el) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "if" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $res ($EVAL $a1 $env)) + + (if (get_global $error_type) + (then (nop)) + (else (if (OR (i32.eq $res (get_global $NIL)) + (i32.eq $res (get_global $FALSE))) + (then + ($RELEASE $res) + ;; if no false case (A3), return nil + (if (i32.lt_u ($COUNT $ast) 4) + (then + (set_local $res ($INC_REF (get_global $NIL))) + (br $EVAL_return)) + (else + (set_local $ast ($MAL_GET_A3 $ast))))) + (else + ($RELEASE $res) + (set_local $ast ($MAL_GET_A2 $ast)))))) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "fn*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($MALFUNC $a2 $a1 $env)) + (br $EVAL_return)) + (else + ;; EVAL_INVOKE + (set_local $res ($EVAL_AST $ast $env 0)) + (set_local $f_args $res) + + ;; if error, return f/args for release by caller + (if (get_global $error_type) + (then + (set_local $res $f_args) + (br $EVAL_return))) + + (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest + (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value + + (set_local $ftype ($TYPE $f)) + (if (i32.eq $ftype (get_global $FUNCTION_T)) + (then + (if (i32.eq ($VAL0 $f) 0) ;; eval + (then + (set_local $res ($EVAL ($MEM_VAL1_ptr $args) + (get_global $repl_env)))) + (else + (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f))))) + ;; release f/args + ($RELEASE $f_args) + (br $EVAL_return)) + (else (if (i32.eq $ftype (get_global $MALFUNC_T)) + (then + ;; save the current environment for release + (set_local $prev_env $env) + ;; create new environment using env and params stored in function + (set_local $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + ($MEM_VAL1_ptr $f) $args)) + + ;; release previous environment if not the current EVAL env + ;; because our new env refers to it and we no longer need to + ;; track it (since we are TCO recurring) + (if (i32.ne $prev_env $orig_env) + (then + ($RELEASE $prev_env) + (set_local $prev_env 0))) + + ;; claim the AST before releasing the list containing it + (set_local $ast ($MEM_VAL0_ptr $f)) + (drop ($INC_REF $ast)) + + ;; if we have already been here via TCO, release previous + ;; ast + ;; PEND_A_LV + (if $prev_ast ($RELEASE $prev_ast)) + (set_local $prev_ast $ast) + + ;; release f/args + ($RELEASE $f_args) + + (br $TCO_loop)) + (else + ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) + (set_local $res 0) + ($RELEASE $f_args) + (br $EVAL_return))))))))))))))) + + ) ;; end of TCO_loop + ) ;; end of EVAL_return + + ;; EVAL_RETURN + (if (i32.ne $env $orig_env) ($RELEASE $env)) + (if $prev_ast ($RELEASE $prev_ast)) + + $res + ) + + ;; PRINT + (func $PRINT (param $ast i32) (result i32) + ($pr_str $ast 1) + ) + + ;; REPL + (func $RE (param $line i32 $env i32) (result i32) + (local $mv1 i32 $res i32) + (block $rep_done + (set_local $mv1 ($READ $line)) + (if (get_global $error_type) (br $rep_done)) + + (set_local $res ($EVAL $mv1 $env)) + ) + + ;; release memory from MAL_READ + ($RELEASE $mv1) + $res + ) + + (func $REP (param $line i32 $env i32) (result i32) + (local $mv2 i32 $ms i32) + (block $rep_done + (set_local $mv2 ($RE $line $env)) + (if (get_global $error_type) (br $rep_done)) + +;; ($PR_MEMORY -1 -1) + (set_local $ms ($PRINT $mv2)) + ) + + ;; release memory from RE + ($RELEASE $mv2) + $ms + ) + + (func $main (param $argc i32 $argv i32) (result i32) + (local $line i32 $res i32 $repl_env i32) + ;; argument processing + (local $i i32 $ret i32 $empty i32 $current i32 $tmp i32 $val2 i32) + + ;; DEBUG +;; ($printf_1 "argc: 0x%x\n" $argc) +;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) +;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) + + (set_global $repl_env ($ENV_NEW (get_global $NIL))) + (set_local $repl_env (get_global $repl_env)) + + ;; core.EXT: defined in wasm + ($add_core_ns $repl_env) + (drop ($ENV_SET_S $repl_env "eval" ($FUNCTION 0))) + + ;; core.mal: defined using the language itself + ($RELEASE ($RE "(def! not (fn* (a) (if a false true)))" $repl_env)) + ($RELEASE ($RE "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" $repl_env)) + + + ;; Command line arguments + (set_local $res ($MAP_LOOP_START (get_global $LIST_T))) + ;; push MAP_LOP stack + ;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (set_local $i 2) + (block $done + (loop $loop + (if (i32.ge_u $i $argc) (br $done)) + + (set_local $val2 ($STRING (get_global $STRING_T) + (i32.load (i32.add $argv (i32.mul_u $i 4))))) + + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE + (get_global $LIST_T) $empty $current $val2 0)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (set_local $i (i32.add $i 1)) + (br $loop) + ) + ) + (drop ($ENV_SET_S $repl_env "*ARGV*" $ret)) + + + ;;($PR_MEMORY -1 -1) + + (if (i32.gt_u $argc 1) + (then + (drop ($ENV_SET_S $repl_env + "*FILE*" ($STRING (get_global $STRING_T) + (i32.load (i32.add $argv 4))))) + ($RELEASE ($RE "(load-file *FILE*)" $repl_env)) + (if (get_global $error_type) + (then + ($printf_1 "Error: %s\n" (get_global $error_str)) + (return 1)) + (else + (return 0))))) + + ;; Start REPL + (block $repl_done + (loop $repl_loop + (set_local $line ($readline "user> ")) + (if (i32.eqz $line) (br $repl_done)) + (if (i32.eq (i32.load8_u $line) 0) + (then + ($free $line) + (br $repl_loop))) + (set_local $res ($REP $line $repl_env)) + (if (get_global $error_type) + (then + ($printf_1 "Error: %s\n" (get_global $error_str)) + (set_global $error_type 0)) + (else + ($printf_1 "%s\n" ($to_String $res)))) + ($RELEASE $res) +;; ($PR_MEMORY -1 -1) + ($free $line) + (br $repl_loop))) + + ($print "\n") + ;;($PR_MEMORY -1 -1) + 0 + ) + + + (export "_main" (func $main)) + (export "__post_instantiate" (func $init_memory)) +) + diff --git a/wasm/step7_quote.wam b/wasm/step7_quote.wam new file mode 100644 index 0000000000..16b4e8555e --- /dev/null +++ b/wasm/step7_quote.wam @@ -0,0 +1,494 @@ +(module $step7_quote + + (global $repl_env (mut i32) (i32.const 0)) + + ;; READ + (func $READ (param $str i32) (result i32) + ($read_str $str) + ) + + ;; EVAL + (func $is_pair (param $ast i32) (result i32) + (local $type i32) + (set_local $type ($TYPE $ast)) + (AND (OR (i32.eq $type (get_global $LIST_T)) + (i32.eq $type (get_global $VECTOR_T))) + (i32.ne ($VAL0 $ast) 0)) + ) + + (func $QUASIQUOTE (param $ast i32) (result i32) + (local $sym i32 $res i32 $second i32 $third i32) + (set_local $res 0) + (if (i32.eqz ($is_pair $ast)) ;; QQ_QUOTE + (then + (set_local $sym ($STRING (get_global $SYMBOL_T) "quote")) + ;; ['quote ast] + (set_local $res ($LIST2 $sym $ast)) + ($RELEASE $sym)) + (else + (set_local $res ($MEM_VAL1_ptr $ast)) + (if (AND (i32.eq ($TYPE $res) (get_global $SYMBOL_T)) + (i32.eqz ($strcmp "unquote" ($to_String $res)))) + (then + ;; ast[1] + (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))))) + (else (if (AND ($is_pair $res) + (i32.eq ($TYPE ($MEM_VAL1_ptr $res)) + (get_global $SYMBOL_T)) + (i32.eqz ($strcmp "splice-unquote" + ($to_String ($MEM_VAL1_ptr $res))))) + (then + ;; ['concat, ast[0][1], quasiquote(ast[1..])] + (set_local $sym ($STRING (get_global $SYMBOL_T) "concat")) + (set_local $second + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL1_ptr $ast)))) + (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (set_local $res ($LIST3 $sym $second $third)) + ;; release inner quasiquoted since outer list take ownership + ($RELEASE $third) + ($RELEASE $sym)) + (else + ;; ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] + (set_local $sym ($STRING (get_global $SYMBOL_T) "cons")) + (set_local $second ($QUASIQUOTE ($MEM_VAL1_ptr $ast))) + (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (set_local $res ($LIST3 $sym $second $third)) + ;; release inner quasiquoted since outer list takes ownership + ($RELEASE $third) + ($RELEASE $second) + ($RELEASE $sym))))))) + $res + ) + + (func $EVAL_AST (param $ast i32 $env i32 $skiplast i32) (result i32) + (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) + (local $ret i32 $empty i32 $current i32) + + (if (get_global $error_type) (return 0)) + (set_local $type ($TYPE $ast)) + + ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) + + ;;; switch(type) + (block $done + (block $default (block (block + (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) + ;; symbol + ;; found/res returned as hi 32/lo 32 of i64 + (set_local $res ($ENV_GET $env $ast)) + (br $done)) + ;; list, vector, hashmap + ;; MAP_LOOP_START + (set_local $res ($MAP_LOOP_START $type)) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (block $done + (loop $loop + ;; check if we are done evaluating the source sequence + (if (i32.eq ($VAL0 $ast) 0) (br $done)) + + (if $skiplast + (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0) (br $done))) + + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (else + (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (set_local $val2 $res) + + ;; if error, release the unattached element + (if (get_global $error_type) + (then + ($RELEASE $res) + (set_local $res 0) + (br $done))) + + ;; for hash-maps, copy the key (inc ref since we are going + ;; to release it below) + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $val3 $val2) + (set_local $val2 ($MEM_VAL1_ptr $ast)) + (drop ($INC_REF $val2)))) + + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (set_local $ast ($MEM_VAL0_ptr $ast)) + + (br $loop) + ) + ) + ;; MAP_LOOP_DONE + (set_local $res $ret) + ;; EVAL_AST_RETURN: nothing to do + (br $done)) + ;; default + (set_local $res ($INC_REF $ast)) + ) + + $res + ) + + (func $MAL_GET_A1 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))) + (func $MAL_GET_A2 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast)))) + (func $MAL_GET_A3 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) + + (func $EVAL (param $orig_ast i32 $orig_env i32) (result i32) + (local $ast i32 $env i32 $prev_ast i32 $prev_env i32 $res i32 $el i32) + (local $type i32 $ftype i32 $f_args i32 $f i32 $args i32) + (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) + + (set_local $ast $orig_ast) + (set_local $env $orig_env) + (set_local $prev_ast 0) + (set_local $prev_env 0) + (set_local $res 0) + + (block $EVAL_return + (loop $TCO_loop + + (set_local $f_args 0) + (set_local $f 0) + (set_local $args 0) + + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + + (if (get_global $error_type) + (then + (set_local $res 0) + (br $EVAL_return))) + + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (then + (set_local $res ($EVAL_AST $ast $env 0)) + (br $EVAL_return))) + + ;; APPLY_LIST + (if ($EMPTY_Q $ast) + (then + (set_local $res ($INC_REF $ast)) + (br $EVAL_return))) + + (set_local $a0 ($MEM_VAL1_ptr $ast)) + (set_local $a0sym "") + (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) + (set_local $a0sym ($to_String $a0))) + + (if (i32.eqz ($strcmp "def!" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($EVAL $a2 $env)) + (if (get_global $error_type) (br $EVAL_return)) + + ;; set a1 in env to a2 + (set_local $res ($ENV_SET $env $a1 $res)) + (br $EVAL_return)) + (else (if (i32.eqz ($strcmp "let*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + + ;; create new environment with outer as current environment + (set_local $prev_env $env) ;; save env for later release + (set_local $env ($ENV_NEW $env)) + + (block $done + (loop $loop + (if (i32.eqz ($VAL0 $a1)) + (br $done)) + ;; eval current A1 odd element + (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) + + (if (get_global $error_type) (br $done)) + + ;; set key/value in the let environment + (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) + ;; release our use, ENV_SET took ownership + ($RELEASE $res) + + ;; skip to the next pair of a1 elements + (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (br $loop) + ) + ) + + ;; release previous environment if not the current EVAL env + (if (i32.ne $prev_env $orig_env) + (then + ($RELEASE $prev_env) + (set_local $prev_env 0))) + + (set_local $ast $a2) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "do" $a0sym)) + (then + ;; EVAL the rest through second to last + (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) + (set_local $ast ($LAST $ast)) + ($RELEASE $el) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "quote" $a0sym)) + (then + (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + (br $EVAL_return)) + (else (if (i32.eqz ($strcmp "quasiquote" $a0sym)) + (then + (set_local $ast ($QUASIQUOTE ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + + ;; if we have already been here via TCO, release previous ast + (if $prev_ast ($RELEASE $prev_ast)) + (set_local $prev_ast $ast) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "if" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $res ($EVAL $a1 $env)) + + (if (get_global $error_type) + (then (nop)) + (else (if (OR (i32.eq $res (get_global $NIL)) + (i32.eq $res (get_global $FALSE))) + (then + ($RELEASE $res) + ;; if no false case (A3), return nil + (if (i32.lt_u ($COUNT $ast) 4) + (then + (set_local $res ($INC_REF (get_global $NIL))) + (br $EVAL_return)) + (else + (set_local $ast ($MAL_GET_A3 $ast))))) + (else + ($RELEASE $res) + (set_local $ast ($MAL_GET_A2 $ast)))))) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "fn*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($MALFUNC $a2 $a1 $env)) + (br $EVAL_return)) + (else + ;; EVAL_INVOKE + (set_local $res ($EVAL_AST $ast $env 0)) + (set_local $f_args $res) + + ;; if error, return f/args for release by caller + (if (get_global $error_type) + (then + (set_local $res $f_args) + (br $EVAL_return))) + + (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest + (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value + + (set_local $ftype ($TYPE $f)) + (if (i32.eq $ftype (get_global $FUNCTION_T)) + (then + (if (i32.eq ($VAL0 $f) 0) ;; eval + (then + (set_local $res ($EVAL ($MEM_VAL1_ptr $args) + (get_global $repl_env)))) + (else + (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f))))) + ;; release f/args + ($RELEASE $f_args) + (br $EVAL_return)) + (else (if (i32.eq $ftype (get_global $MALFUNC_T)) + (then + ;; save the current environment for release + (set_local $prev_env $env) + ;; create new environment using env and params stored in function + (set_local $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + ($MEM_VAL1_ptr $f) $args)) + + ;; release previous environment if not the current EVAL env + ;; because our new env refers to it and we no longer need to + ;; track it (since we are TCO recurring) + (if (i32.ne $prev_env $orig_env) + (then + ($RELEASE $prev_env) + (set_local $prev_env 0))) + + ;; claim the AST before releasing the list containing it + (set_local $ast ($MEM_VAL0_ptr $f)) + (drop ($INC_REF $ast)) + + ;; if we have already been here via TCO, release previous + ;; ast + ;; PEND_A_LV + (if $prev_ast ($RELEASE $prev_ast)) + (set_local $prev_ast $ast) + + ;; release f/args + ($RELEASE $f_args) + + (br $TCO_loop)) + (else + ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) + (set_local $res 0) + ($RELEASE $f_args) + (br $EVAL_return))))))))))))))))))) + + ) ;; end of TCO_loop + ) ;; end of EVAL_return + + ;; EVAL_RETURN + (if (i32.ne $env $orig_env) ($RELEASE $env)) + (if $prev_ast ($RELEASE $prev_ast)) + + $res + ) + + ;; PRINT + (func $PRINT (param $ast i32) (result i32) + ($pr_str $ast 1) + ) + + ;; REPL + (func $RE (param $line i32 $env i32) (result i32) + (local $mv1 i32 $res i32) + (block $rep_done + (set_local $mv1 ($READ $line)) + (if (get_global $error_type) (br $rep_done)) + + (set_local $res ($EVAL $mv1 $env)) + ) + + ;; release memory from MAL_READ + ($RELEASE $mv1) + $res + ) + + (func $REP (param $line i32 $env i32) (result i32) + (local $mv2 i32 $ms i32) + (block $rep_done + (set_local $mv2 ($RE $line $env)) + (if (get_global $error_type) (br $rep_done)) + +;; ($PR_MEMORY -1 -1) + (set_local $ms ($PRINT $mv2)) + ) + + ;; release memory from RE + ($RELEASE $mv2) + $ms + ) + + (func $main (param $argc i32 $argv i32) (result i32) + (local $line i32 $res i32 $repl_env i32) + ;; argument processing + (local $i i32 $ret i32 $empty i32 $current i32 $tmp i32 $val2 i32) + + ;; DEBUG +;; ($printf_1 "argc: 0x%x\n" $argc) +;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) +;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) + + (set_global $repl_env ($ENV_NEW (get_global $NIL))) + (set_local $repl_env (get_global $repl_env)) + + ;; core.EXT: defined in wasm + ($add_core_ns $repl_env) + (drop ($ENV_SET_S $repl_env "eval" ($FUNCTION 0))) + + ;; core.mal: defined using the language itself + ($RELEASE ($RE "(def! not (fn* (a) (if a false true)))" $repl_env)) + ($RELEASE ($RE "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" $repl_env)) + + + ;; Command line arguments + (set_local $res ($MAP_LOOP_START (get_global $LIST_T))) + ;; push MAP_LOP stack + ;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (set_local $i 2) + (block $done + (loop $loop + (if (i32.ge_u $i $argc) (br $done)) + + (set_local $val2 ($STRING (get_global $STRING_T) + (i32.load (i32.add $argv (i32.mul_u $i 4))))) + + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE + (get_global $LIST_T) $empty $current $val2 0)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (set_local $i (i32.add $i 1)) + (br $loop) + ) + ) + (drop ($ENV_SET_S $repl_env "*ARGV*" $ret)) + + + ;;($PR_MEMORY -1 -1) + + (if (i32.gt_u $argc 1) + (then + (drop ($ENV_SET_S $repl_env + "*FILE*" ($STRING (get_global $STRING_T) + (i32.load (i32.add $argv 4))))) + ($RELEASE ($RE "(load-file *FILE*)" $repl_env)) + (if (get_global $error_type) + (then + ($printf_1 "Error: %s\n" (get_global $error_str)) + (return 1)) + (else + (return 0))))) + + ;; Start REPL + (block $repl_done + (loop $repl_loop + (set_local $line ($readline "user> ")) + (if (i32.eqz $line) (br $repl_done)) + (if (i32.eq (i32.load8_u $line) 0) + (then + ($free $line) + (br $repl_loop))) + (set_local $res ($REP $line $repl_env)) + (if (get_global $error_type) + (then + ($printf_1 "Error: %s\n" (get_global $error_str)) + (set_global $error_type 0)) + (else + ($printf_1 "%s\n" ($to_String $res)))) + ($RELEASE $res) +;; ($PR_MEMORY -1 -1) + ($free $line) + (br $repl_loop))) + + ($print "\n") + ;;($PR_MEMORY -1 -1) + 0 + ) + + + (export "_main" (func $main)) + (export "__post_instantiate" (func $init_memory)) +) + diff --git a/wasm/step8_macros.wam b/wasm/step8_macros.wam new file mode 100644 index 0000000000..6fb6da1f2e --- /dev/null +++ b/wasm/step8_macros.wam @@ -0,0 +1,580 @@ +(module $step8_macros + + (global $repl_env (mut i32) (i32.const 0)) + + ;; READ + (func $READ (param $str i32) (result i32) + ($read_str $str) + ) + + ;; EVAL + (func $is_pair (param $ast i32) (result i32) + (local $type i32) + (set_local $type ($TYPE $ast)) + (AND (OR (i32.eq $type (get_global $LIST_T)) + (i32.eq $type (get_global $VECTOR_T))) + (i32.ne ($VAL0 $ast) 0)) + ) + + (func $QUASIQUOTE (param $ast i32) (result i32) + (local $sym i32 $res i32 $second i32 $third i32) + (set_local $res 0) + (if (i32.eqz ($is_pair $ast)) ;; QQ_QUOTE + (then + (set_local $sym ($STRING (get_global $SYMBOL_T) "quote")) + ;; ['quote ast] + (set_local $res ($LIST2 $sym $ast)) + ($RELEASE $sym)) + (else + (set_local $res ($MEM_VAL1_ptr $ast)) + (if (AND (i32.eq ($TYPE $res) (get_global $SYMBOL_T)) + (i32.eqz ($strcmp "unquote" ($to_String $res)))) + (then + ;; ast[1] + (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))))) + (else (if (AND ($is_pair $res) + (i32.eq ($TYPE ($MEM_VAL1_ptr $res)) + (get_global $SYMBOL_T)) + (i32.eqz ($strcmp "splice-unquote" + ($to_String ($MEM_VAL1_ptr $res))))) + (then + ;; ['concat, ast[0][1], quasiquote(ast[1..])] + (set_local $sym ($STRING (get_global $SYMBOL_T) "concat")) + (set_local $second + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL1_ptr $ast)))) + (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (set_local $res ($LIST3 $sym $second $third)) + ;; release inner quasiquoted since outer list take ownership + ($RELEASE $third) + ($RELEASE $sym)) + (else + ;; ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] + (set_local $sym ($STRING (get_global $SYMBOL_T) "cons")) + (set_local $second ($QUASIQUOTE ($MEM_VAL1_ptr $ast))) + (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (set_local $res ($LIST3 $sym $second $third)) + ;; release inner quasiquoted since outer list takes ownership + ($RELEASE $third) + ($RELEASE $second) + ($RELEASE $sym))))))) + $res + ) + + (global $mac_ast_stack (mut i32) (i32.const 0)) + (global $mac_ast_stack_top (mut i32) (i32.const -1)) + + (func $MACROEXPAND (param $orig_ast i32 $env i32) (result i32) + (local $ast i32 $mac i32 $mac_env i64) + (set_global $mac_ast_stack (STATIC_ARRAY 128)) + (set_local $ast $orig_ast) + (set_local $mac 0) + (block $done + (loop $loop + (if (OR (i32.ne ($TYPE $ast) (get_global $LIST_T)) ;; a list + (i32.eqz ($VAL0 $ast)) ;; non-empty + (i32.ne ($TYPE ($MEM_VAL1_ptr $ast)) ;; leading symbol + (get_global $SYMBOL_T))) + (br $done)) + (set_local $mac_env ($ENV_FIND $env ($MEM_VAL1_ptr $ast))) + (set_local $mac (i32.wrap/i64 (i64.shr_u $mac_env (i64.const 32)))) + (if (OR (i32.eqz (i32.wrap/i64 $mac_env)) ;; defined in env + (i32.ne ($TYPE $mac) ;; a macro + (get_global $MACRO_T))) + (then + (br $done))) + + (set_local $ast ($APPLY $mac ($MEM_VAL0_ptr $ast))) + ;; PEND_A_LV + ;; if ast is not the first ast that was passed in, then add it + ;; to the pending release list. + (if (i32.ne $ast $orig_ast) + (then + (set_global $mac_ast_stack_top + (i32.add (get_global $mac_ast_stack_top) 1)) + (i32.store (i32.add + (get_global $mac_ast_stack) + (i32.mul_s (get_global $mac_ast_stack_top) 4)) + $ast))) + (if (get_global $error_type) + (br $done)) + + (br $loop) + ) + ) + $ast + ) + + (func $EVAL_AST (param $ast i32 $env i32 $skiplast i32) (result i32) + (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) + (local $ret i32 $empty i32 $current i32) + + (if (get_global $error_type) (return 0)) + (set_local $type ($TYPE $ast)) + + ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) + + ;;; switch(type) + (block $done + (block $default (block (block + (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) + ;; symbol + ;; found/res returned as hi 32/lo 32 of i64 + (set_local $res ($ENV_GET $env $ast)) + (br $done)) + ;; list, vector, hashmap + ;; MAP_LOOP_START + (set_local $res ($MAP_LOOP_START $type)) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (block $done + (loop $loop + ;; check if we are done evaluating the source sequence + (if (i32.eq ($VAL0 $ast) 0) (br $done)) + + (if $skiplast + (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0) (br $done))) + + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (else + (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (set_local $val2 $res) + + ;; if error, release the unattached element + (if (get_global $error_type) + (then + ($RELEASE $res) + (set_local $res 0) + (br $done))) + + ;; for hash-maps, copy the key (inc ref since we are going + ;; to release it below) + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $val3 $val2) + (set_local $val2 ($MEM_VAL1_ptr $ast)) + (drop ($INC_REF $val2)))) + + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (set_local $ast ($MEM_VAL0_ptr $ast)) + + (br $loop) + ) + ) + ;; MAP_LOOP_DONE + (set_local $res $ret) + ;; EVAL_AST_RETURN: nothing to do + (br $done)) + ;; default + (set_local $res ($INC_REF $ast)) + ) + + $res + ) + + (func $MAL_GET_A1 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))) + (func $MAL_GET_A2 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast)))) + (func $MAL_GET_A3 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) + + (func $EVAL (param $orig_ast i32 $orig_env i32) (result i32) + (local $ast i32 $env i32 $prev_ast i32 $prev_env i32 $res i32 $el i32) + (local $ftype i32 $f_args i32 $f i32 $args i32) + (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) + + (set_local $ast $orig_ast) + (set_local $env $orig_env) + (set_local $prev_ast 0) + (set_local $prev_env 0) + (set_local $res 0) + + (block $EVAL_return + (loop $TCO_loop + + (set_local $f_args 0) + (set_local $f 0) + (set_local $args 0) + + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + + (if (get_global $error_type) + (then + (set_local $res 0) + (br $EVAL_return))) + + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (then + (set_local $res ($EVAL_AST $ast $env 0)) + (br $EVAL_return))) + + ;; APPLY_LIST + (set_local $ast ($MACROEXPAND $ast $env)) + ;;($PR_VALUE ">>> >>> EVAL ast: '%s'\n" $ast) + + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (then + (set_local $res ($EVAL_AST $ast $env 0)) + (br $EVAL_return))) + + (if ($EMPTY_Q $ast) + (then + (set_local $res ($INC_REF $ast)) + (br $EVAL_return))) + + (set_local $a0 ($MEM_VAL1_ptr $ast)) + (set_local $a0sym "") + (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) + (set_local $a0sym ($to_String $a0))) + + (if (i32.eqz ($strcmp "def!" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($EVAL $a2 $env)) + (if (get_global $error_type) (br $EVAL_return)) + + ;; set a1 in env to a2 + (set_local $res ($ENV_SET $env $a1 $res)) + (br $EVAL_return)) + (else (if (i32.eqz ($strcmp "let*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + + ;; create new environment with outer as current environment + (set_local $prev_env $env) ;; save env for later release + (set_local $env ($ENV_NEW $env)) + + (block $done + (loop $loop + (if (i32.eqz ($VAL0 $a1)) + (br $done)) + ;; eval current A1 odd element + (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) + + (if (get_global $error_type) (br $done)) + + ;; set key/value in the let environment + (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) + ;; release our use, ENV_SET took ownership + ($RELEASE $res) + + ;; skip to the next pair of a1 elements + (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (br $loop) + ) + ) + + ;; release previous environment if not the current EVAL env + (if (i32.ne $prev_env $orig_env) + (then + ($RELEASE $prev_env) + (set_local $prev_env 0))) + + (set_local $ast $a2) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "do" $a0sym)) + (then + ;; EVAL the rest through second to last + (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) + (set_local $ast ($LAST $ast)) + ($RELEASE $el) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "quote" $a0sym)) + (then + (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + (br $EVAL_return)) + (else (if (i32.eqz ($strcmp "quasiquote" $a0sym)) + (then + (set_local $ast ($QUASIQUOTE ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + + ;; if we have already been here via TCO, release previous ast + (if $prev_ast ($RELEASE $prev_ast)) + (set_local $prev_ast $ast) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "defmacro!" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($EVAL $a2 $env)) + ($SET_TYPE $res (get_global $MACRO_T)) + (if (get_global $error_type) + (br $EVAL_return)) + + ;; set a1 in env to a2 + (set_local $res ($ENV_SET $env $a1 $res)) + (br $EVAL_return)) + (else (if (i32.eqz ($strcmp "macroexpand" $a0sym)) + (then + ;; since we are returning it unevaluated, inc the ref cnt + (set_local $res ($INC_REF ($MACROEXPAND + ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)) + $env)))) + (else (if (i32.eqz ($strcmp "if" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $res ($EVAL $a1 $env)) + + (if (get_global $error_type) + (then (nop)) + (else (if (OR (i32.eq $res (get_global $NIL)) + (i32.eq $res (get_global $FALSE))) + (then + ($RELEASE $res) + ;; if no false case (A3), return nil + (if (i32.lt_u ($COUNT $ast) 4) + (then + (set_local $res ($INC_REF (get_global $NIL))) + (br $EVAL_return)) + (else + (set_local $ast ($MAL_GET_A3 $ast))))) + (else + ($RELEASE $res) + (set_local $ast ($MAL_GET_A2 $ast)))))) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "fn*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($MALFUNC $a2 $a1 $env)) + (br $EVAL_return)) + (else + ;; EVAL_INVOKE + (set_local $res ($EVAL_AST $ast $env 0)) + (set_local $f_args $res) + + ;; if error, return f/args for release by caller + (if (get_global $error_type) + (then + (set_local $res $f_args) + (br $EVAL_return))) + + (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest + (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value + + (set_local $ftype ($TYPE $f)) + (if (i32.eq $ftype (get_global $FUNCTION_T)) + (then + (if (i32.eq ($VAL0 $f) 0) ;; eval + (then + (set_local $res ($EVAL ($MEM_VAL1_ptr $args) + (get_global $repl_env)))) + (else + (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f))))) + ;; release f/args + ($RELEASE $f_args) + (br $EVAL_return)) + (else (if (i32.eq $ftype (get_global $MALFUNC_T)) + (then + ;; save the current environment for release + (set_local $prev_env $env) + ;; create new environment using env and params stored in function + (set_local $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + ($MEM_VAL1_ptr $f) $args)) + + ;; release previous environment if not the current EVAL env + ;; because our new env refers to it and we no longer need to + ;; track it (since we are TCO recurring) + (if (i32.ne $prev_env $orig_env) + (then + ($RELEASE $prev_env) + (set_local $prev_env 0))) + + ;; claim the AST before releasing the list containing it + (set_local $ast ($MEM_VAL0_ptr $f)) + (drop ($INC_REF $ast)) + + ;; if we have already been here via TCO, release previous + ;; ast + ;; PEND_A_LV + (if $prev_ast ($RELEASE $prev_ast)) + (set_local $prev_ast $ast) + + ;; release f/args + ($RELEASE $f_args) + + (br $TCO_loop)) + (else + ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) + (set_local $res 0) + ($RELEASE $f_args) + (br $EVAL_return))))))))))))))))))))))) + + ) ;; end of TCO_loop + ) ;; end of EVAL_return + + ;; EVAL_RETURN + (if (i32.ne $env $orig_env) ($RELEASE $env)) + (if $prev_ast ($RELEASE $prev_ast)) + + $res + ) + + ;; PRINT + (func $PRINT (param $ast i32) (result i32) + ($pr_str $ast 1) + ) + + ;; REPL + (func $RE (param $line i32 $env i32) (result i32) + (local $mv1 i32 $res i32) + (block $rep_done + (set_local $mv1 ($READ $line)) + (if (get_global $error_type) (br $rep_done)) + + (set_local $res ($EVAL $mv1 $env)) + ) + + ;; release memory from MAL_READ + ($RELEASE $mv1) + ;; release memory from MACROEXPAND + ;; TODO: needs to happen in EVAL + (block $done + (loop $loop + (if (i32.lt_s (get_global $mac_ast_stack_top) 0) + (br $done)) + ($RELEASE (i32.load (i32.add + (get_global $mac_ast_stack) + (i32.mul_s (get_global $mac_ast_stack_top) 4)))) + (set_global $mac_ast_stack_top + (i32.sub_s (get_global $mac_ast_stack_top) 1)) + (br $loop) + ) + ) + $res + ) + + (func $REP (param $line i32 $env i32) (result i32) + (local $mv2 i32 $ms i32) + (block $rep_done + (set_local $mv2 ($RE $line $env)) + (if (get_global $error_type) (br $rep_done)) + +;; ($PR_MEMORY -1 -1) + (set_local $ms ($PRINT $mv2)) + ) + + ;; release memory from RE + ($RELEASE $mv2) + $ms + ) + + (func $main (param $argc i32 $argv i32) (result i32) + (local $line i32 $res i32 $repl_env i32) + ;; argument processing + (local $i i32 $ret i32 $empty i32 $current i32 $tmp i32 $val2 i32) + + ;; DEBUG +;; ($printf_1 "argc: 0x%x\n" $argc) +;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) +;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) + + (set_global $repl_env ($ENV_NEW (get_global $NIL))) + (set_local $repl_env (get_global $repl_env)) + + ;; core.EXT: defined in wasm + ($add_core_ns $repl_env) + (drop ($ENV_SET_S $repl_env "eval" ($FUNCTION 0))) + + ;; core.mal: defined using the language itself + ($RELEASE ($RE "(def! not (fn* (a) (if a false true)))" $repl_env)) + ($RELEASE ($RE "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" $repl_env)) + ($RELEASE ($RE "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" $repl_env)) + ($RELEASE ($RE "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" $repl_env)) + + + ;; Command line arguments + (set_local $res ($MAP_LOOP_START (get_global $LIST_T))) + ;; push MAP_LOP stack + ;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (set_local $i 2) + (block $done + (loop $loop + (if (i32.ge_u $i $argc) (br $done)) + + (set_local $val2 ($STRING (get_global $STRING_T) + (i32.load (i32.add $argv (i32.mul_u $i 4))))) + + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE + (get_global $LIST_T) $empty $current $val2 0)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (set_local $i (i32.add $i 1)) + (br $loop) + ) + ) + (drop ($ENV_SET_S $repl_env "*ARGV*" $ret)) + + + ;;($PR_MEMORY -1 -1) + + (if (i32.gt_u $argc 1) + (then + (drop ($ENV_SET_S $repl_env + "*FILE*" ($STRING (get_global $STRING_T) + (i32.load (i32.add $argv 4))))) + ($RELEASE ($RE "(load-file *FILE*)" $repl_env)) + (if (get_global $error_type) + (then + ($printf_1 "Error: %s\n" (get_global $error_str)) + (return 1)) + (else + (return 0))))) + + ;; Start REPL + (block $repl_done + (loop $repl_loop + (set_local $line ($readline "user> ")) + (if (i32.eqz $line) (br $repl_done)) + (if (i32.eq (i32.load8_u $line) 0) + (then + ($free $line) + (br $repl_loop))) + (set_local $res ($REP $line $repl_env)) + (if (get_global $error_type) + (then + ($printf_1 "Error: %s\n" (get_global $error_str)) + (set_global $error_type 0)) + (else + ($printf_1 "%s\n" ($to_String $res)))) + ($RELEASE $res) +;; ($PR_MEMORY -1 -1) + ($free $line) + (br $repl_loop))) + + ($print "\n") + ;;($PR_MEMORY -1 -1) + 0 + ) + + + (export "_main" (func $main)) + (export "__post_instantiate" (func $init_memory)) +) + diff --git a/wasm/step9_try.wam b/wasm/step9_try.wam new file mode 100644 index 0000000000..3ebeee1369 --- /dev/null +++ b/wasm/step9_try.wam @@ -0,0 +1,630 @@ +(module $step8_macros + + (global $repl_env (mut i32) (i32.const 0)) + + ;; READ + (func $READ (param $str i32) (result i32) + ($read_str $str) + ) + + ;; EVAL + (func $is_pair (param $ast i32) (result i32) + (local $type i32) + (set_local $type ($TYPE $ast)) + (AND (OR (i32.eq $type (get_global $LIST_T)) + (i32.eq $type (get_global $VECTOR_T))) + (i32.ne ($VAL0 $ast) 0)) + ) + + (func $QUASIQUOTE (param $ast i32) (result i32) + (local $sym i32 $res i32 $second i32 $third i32) + (set_local $res 0) + (if (i32.eqz ($is_pair $ast)) ;; QQ_QUOTE + (then + (set_local $sym ($STRING (get_global $SYMBOL_T) "quote")) + ;; ['quote ast] + (set_local $res ($LIST2 $sym $ast)) + ($RELEASE $sym)) + (else + (set_local $res ($MEM_VAL1_ptr $ast)) + (if (AND (i32.eq ($TYPE $res) (get_global $SYMBOL_T)) + (i32.eqz ($strcmp "unquote" ($to_String $res)))) + (then + ;; ast[1] + (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))))) + (else (if (AND ($is_pair $res) + (i32.eq ($TYPE ($MEM_VAL1_ptr $res)) + (get_global $SYMBOL_T)) + (i32.eqz ($strcmp "splice-unquote" + ($to_String ($MEM_VAL1_ptr $res))))) + (then + ;; ['concat, ast[0][1], quasiquote(ast[1..])] + (set_local $sym ($STRING (get_global $SYMBOL_T) "concat")) + (set_local $second + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL1_ptr $ast)))) + (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (set_local $res ($LIST3 $sym $second $third)) + ;; release inner quasiquoted since outer list take ownership + ($RELEASE $third) + ($RELEASE $sym)) + (else + ;; ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] + (set_local $sym ($STRING (get_global $SYMBOL_T) "cons")) + (set_local $second ($QUASIQUOTE ($MEM_VAL1_ptr $ast))) + (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (set_local $res ($LIST3 $sym $second $third)) + ;; release inner quasiquoted since outer list takes ownership + ($RELEASE $third) + ($RELEASE $second) + ($RELEASE $sym))))))) + $res + ) + + (global $mac_ast_stack (mut i32) (i32.const 0)) + (global $mac_ast_stack_top (mut i32) (i32.const -1)) + + (func $MACROEXPAND (param $orig_ast i32 $env i32) (result i32) + (local $ast i32 $mac i32 $mac_env i64) + (set_global $mac_ast_stack (STATIC_ARRAY 128)) + (set_local $ast $orig_ast) + (set_local $mac 0) + (block $done + (loop $loop + (if (OR (i32.ne ($TYPE $ast) (get_global $LIST_T)) ;; a list + (i32.eqz ($VAL0 $ast)) ;; non-empty + (i32.ne ($TYPE ($MEM_VAL1_ptr $ast)) ;; leading symbol + (get_global $SYMBOL_T))) + (br $done)) + (set_local $mac_env ($ENV_FIND $env ($MEM_VAL1_ptr $ast))) + (set_local $mac (i32.wrap/i64 (i64.shr_u $mac_env (i64.const 32)))) + (if (OR (i32.eqz (i32.wrap/i64 $mac_env)) ;; defined in env + (i32.ne ($TYPE $mac) ;; a macro + (get_global $MACRO_T))) + (then + (br $done))) + + (set_local $ast ($APPLY $mac ($MEM_VAL0_ptr $ast))) + ;; PEND_A_LV + ;; if ast is not the first ast that was passed in, then add it + ;; to the pending release list. + (if (i32.ne $ast $orig_ast) + (then + (set_global $mac_ast_stack_top + (i32.add (get_global $mac_ast_stack_top) 1)) + (i32.store (i32.add + (get_global $mac_ast_stack) + (i32.mul_s (get_global $mac_ast_stack_top) 4)) + $ast))) + (if (get_global $error_type) + (br $done)) + + (br $loop) + ) + ) + $ast + ) + + (func $EVAL_AST (param $ast i32 $env i32 $skiplast i32) (result i32) + (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) + (local $ret i32 $empty i32 $current i32) + + (if (get_global $error_type) (return 0)) + (set_local $type ($TYPE $ast)) + + ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) + + ;;; switch(type) + (block $done + (block $default (block (block + (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) + ;; symbol + ;; found/res returned as hi 32/lo 32 of i64 + (set_local $res ($ENV_GET $env $ast)) + (br $done)) + ;; list, vector, hashmap + ;; MAP_LOOP_START + (set_local $res ($MAP_LOOP_START $type)) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (block $done + (loop $loop + ;; check if we are done evaluating the source sequence + (if (i32.eq ($VAL0 $ast) 0) (br $done)) + + (if $skiplast + (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0) (br $done))) + + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (else + (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (set_local $val2 $res) + + ;; if error, release the unattached element + (if (get_global $error_type) + (then + ($RELEASE $res) + (set_local $res 0) + (br $done))) + + ;; for hash-maps, copy the key (inc ref since we are going + ;; to release it below) + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $val3 $val2) + (set_local $val2 ($MEM_VAL1_ptr $ast)) + (drop ($INC_REF $val2)))) + + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (set_local $ast ($MEM_VAL0_ptr $ast)) + + (br $loop) + ) + ) + ;; MAP_LOOP_DONE + (set_local $res $ret) + ;; EVAL_AST_RETURN: nothing to do + (br $done)) + ;; default + (set_local $res ($INC_REF $ast)) + ) + + $res + ) + + (func $MAL_GET_A1 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))) + (func $MAL_GET_A2 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast)))) + (func $MAL_GET_A3 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) + + (func $EVAL (param $orig_ast i32 $orig_env i32) (result i32) + (local $ast i32 $env i32 $prev_ast i32 $prev_env i32 $res i32 $el i32) + (local $ftype i32 $f_args i32 $f i32 $args i32) + (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) + (local $err i32) + + (set_local $ast $orig_ast) + (set_local $env $orig_env) + (set_local $prev_ast 0) + (set_local $prev_env 0) + (set_local $res 0) + + (block $EVAL_return + (loop $TCO_loop + + (set_local $f_args 0) + (set_local $f 0) + (set_local $args 0) + + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + + (if (get_global $error_type) + (then + (set_local $res 0) + (br $EVAL_return))) + + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (then + (set_local $res ($EVAL_AST $ast $env 0)) + (br $EVAL_return))) + + ;; APPLY_LIST + (set_local $ast ($MACROEXPAND $ast $env)) + ;;($PR_VALUE ">>> >>> EVAL ast: '%s'\n" $ast) + + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (then + (set_local $res ($EVAL_AST $ast $env 0)) + (br $EVAL_return))) + + (if ($EMPTY_Q $ast) + (then + (set_local $res ($INC_REF $ast)) + (br $EVAL_return))) + + (set_local $a0 ($MEM_VAL1_ptr $ast)) + (set_local $a0sym "") + (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) + (set_local $a0sym ($to_String $a0))) + + (if (i32.eqz ($strcmp "def!" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($EVAL $a2 $env)) + (if (get_global $error_type) (br $EVAL_return)) + + ;; set a1 in env to a2 + (set_local $res ($ENV_SET $env $a1 $res)) + (br $EVAL_return)) + (else (if (i32.eqz ($strcmp "let*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + + ;; create new environment with outer as current environment + (set_local $prev_env $env) ;; save env for later release + (set_local $env ($ENV_NEW $env)) + + (block $done + (loop $loop + (if (i32.eqz ($VAL0 $a1)) + (br $done)) + ;; eval current A1 odd element + (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) + + (if (get_global $error_type) (br $done)) + + ;; set key/value in the let environment + (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) + ;; release our use, ENV_SET took ownership + ($RELEASE $res) + + ;; skip to the next pair of a1 elements + (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (br $loop) + ) + ) + + ;; release previous environment if not the current EVAL env + (if (i32.ne $prev_env $orig_env) + (then + ($RELEASE $prev_env) + (set_local $prev_env 0))) + + (set_local $ast $a2) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "do" $a0sym)) + (then + ;; EVAL the rest through second to last + (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) + (set_local $ast ($LAST $ast)) + ($RELEASE $el) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "quote" $a0sym)) + (then + (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + (br $EVAL_return)) + (else (if (i32.eqz ($strcmp "quasiquote" $a0sym)) + (then + (set_local $ast ($QUASIQUOTE ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + + ;; if we have already been here via TCO, release previous ast + (if $prev_ast ($RELEASE $prev_ast)) + (set_local $prev_ast $ast) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "defmacro!" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($EVAL $a2 $env)) + ($SET_TYPE $res (get_global $MACRO_T)) + (if (get_global $error_type) + (br $EVAL_return)) + + ;; set a1 in env to a2 + (set_local $res ($ENV_SET $env $a1 $res)) + (br $EVAL_return)) + (else (if (i32.eqz ($strcmp "macroexpand" $a0sym)) + (then + ;; since we are returning it unevaluated, inc the ref cnt + (set_local $res ($INC_REF ($MACROEXPAND + ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)) + $env)))) + (else (if (i32.eqz ($strcmp "try*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $res ($EVAL $a1 $env)) + + ;; if there is no error, return + (if (i32.eqz (get_global $error_type)) + (br $EVAL_return)) + ;; if there is an error and res is set, we need to free it + ($printf_1 "res value: %d\n" $res) + ($RELEASE $res) + ;; if there is no catch block then return + (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast)))) + (br $EVAL_return)) + + ;; save the current environment for release + (set_local $prev_env $env) + ;; create environment for the catch block eval + (set_local $env ($ENV_NEW $env)) + + ;; set a1 and a2 from the catch block + (set_local $a1 ($MAL_GET_A1 ($MAL_GET_A2 $ast))) + (set_local $a2 ($MAL_GET_A2 ($MAL_GET_A2 $ast))) + + ;; create object for string errors + (if (i32.eq (get_global $error_type) 1) + (then + (set_local $err ($STRING (get_global $STRING_T) + (get_global $error_str)))) + (else + (set_local $err (get_global $error_val)))) + ;; bind the catch symbol to the error object + (drop ($ENV_SET $env $a1 $err)) + ;; release our use, env took ownership + ($RELEASE $err) + + ;; unset error for catch eval + (set_global $error_type 0) + (i32.store (get_global $error_str) (CHR "\x00")) + + ;; release previous environment if not the current EVAL env + (if (i32.ne $prev_env $orig_env) + (then + ($RELEASE $prev_env) + (set_local $prev_env 0))) + + (set_local $ast $a2) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "if" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $res ($EVAL $a1 $env)) + + (if (get_global $error_type) + (then (nop)) + (else (if (OR (i32.eq $res (get_global $NIL)) + (i32.eq $res (get_global $FALSE))) + (then + ($RELEASE $res) + ;; if no false case (A3), return nil + (if (i32.lt_u ($COUNT $ast) 4) + (then + (set_local $res ($INC_REF (get_global $NIL))) + (br $EVAL_return)) + (else + (set_local $ast ($MAL_GET_A3 $ast))))) + (else + ($RELEASE $res) + (set_local $ast ($MAL_GET_A2 $ast)))))) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "fn*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($MALFUNC $a2 $a1 $env)) + (br $EVAL_return)) + (else + ;; EVAL_INVOKE + (set_local $res ($EVAL_AST $ast $env 0)) + (set_local $f_args $res) + + ;; if error, return f/args for release by caller + (if (get_global $error_type) + (then + (set_local $res $f_args) + (br $EVAL_return))) + + (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest + (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value + + (set_local $ftype ($TYPE $f)) + (if (i32.eq $ftype (get_global $FUNCTION_T)) + (then + (if (i32.eq ($VAL0 $f) 0) ;; eval + (then + (set_local $res ($EVAL ($MEM_VAL1_ptr $args) + (get_global $repl_env)))) + (else + (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f))))) + ;; release f/args + ($RELEASE $f_args) + (br $EVAL_return)) + (else (if (i32.eq $ftype (get_global $MALFUNC_T)) + (then + ;; save the current environment for release + (set_local $prev_env $env) + ;; create new environment using env and params stored in function + (set_local $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + ($MEM_VAL1_ptr $f) $args)) + + ;; release previous environment if not the current EVAL env + ;; because our new env refers to it and we no longer need to + ;; track it (since we are TCO recurring) + (if (i32.ne $prev_env $orig_env) + (then + ($RELEASE $prev_env) + (set_local $prev_env 0))) + + ;; claim the AST before releasing the list containing it + (set_local $ast ($MEM_VAL0_ptr $f)) + (drop ($INC_REF $ast)) + + ;; if we have already been here via TCO, release previous + ;; ast + ;; PEND_A_LV + (if $prev_ast ($RELEASE $prev_ast)) + (set_local $prev_ast $ast) + + ;; release f/args + ($RELEASE $f_args) + + (br $TCO_loop)) + (else + ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) + (set_local $res 0) + ($RELEASE $f_args) + (br $EVAL_return))))))))))))))))))))))))) + + ) ;; end of TCO_loop + ) ;; end of EVAL_return + + ;; EVAL_RETURN + (if (i32.ne $env $orig_env) ($RELEASE $env)) + (if $prev_ast ($RELEASE $prev_ast)) + + $res + ) + + ;; PRINT + (func $PRINT (param $ast i32) (result i32) + ($pr_str $ast 1) + ) + + ;; REPL + (func $RE (param $line i32 $env i32) (result i32) + (local $mv1 i32 $res i32) + (block $rep_done + (set_local $mv1 ($READ $line)) + (if (get_global $error_type) (br $rep_done)) + + (set_local $res ($EVAL $mv1 $env)) + ) + + ;; release memory from MAL_READ + ($RELEASE $mv1) + ;; release memory from MACROEXPAND + ;; TODO: needs to happen in EVAL + (block $done + (loop $loop + (if (i32.lt_s (get_global $mac_ast_stack_top) 0) + (br $done)) + ($RELEASE (i32.load (i32.add + (get_global $mac_ast_stack) + (i32.mul_s (get_global $mac_ast_stack_top) 4)))) + (set_global $mac_ast_stack_top + (i32.sub_s (get_global $mac_ast_stack_top) 1)) + (br $loop) + ) + ) + $res + ) + + (func $REP (param $line i32 $env i32) (result i32) + (local $mv2 i32 $ms i32) + (block $rep_done + (set_local $mv2 ($RE $line $env)) + (if (get_global $error_type) (br $rep_done)) + +;; ($PR_MEMORY -1 -1) + (set_local $ms ($PRINT $mv2)) + ) + + ;; release memory from RE + ($RELEASE $mv2) + $ms + ) + + (func $main (param $argc i32 $argv i32) (result i32) + (local $line i32 $res i32 $repl_env i32) + ;; argument processing + (local $i i32 $ret i32 $empty i32 $current i32 $tmp i32 $val2 i32) + + ;; DEBUG +;; ($printf_1 "argc: 0x%x\n" $argc) +;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) +;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) + + (set_global $repl_env ($ENV_NEW (get_global $NIL))) + (set_local $repl_env (get_global $repl_env)) + + ;; core.EXT: defined in wasm + ($add_core_ns $repl_env) + (drop ($ENV_SET_S $repl_env "eval" ($FUNCTION 0))) + + + ;; core.mal: defined using the language itself + ($RELEASE ($RE "(def! not (fn* (a) (if a false true)))" $repl_env)) + ($RELEASE ($RE "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" $repl_env)) + ($RELEASE ($RE "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" $repl_env)) + ($RELEASE ($RE "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" $repl_env)) + + + ;; Command line arguments + (set_local $res ($MAP_LOOP_START (get_global $LIST_T))) + ;; push MAP_LOP stack + ;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (set_local $i 2) + (block $done + (loop $loop + (if (i32.ge_u $i $argc) (br $done)) + + (set_local $val2 ($STRING (get_global $STRING_T) + (i32.load (i32.add $argv (i32.mul_u $i 4))))) + + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE + (get_global $LIST_T) $empty $current $val2 0)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (set_local $i (i32.add $i 1)) + (br $loop) + ) + ) + (drop ($ENV_SET_S $repl_env "*ARGV*" $ret)) + + + ;;($PR_MEMORY -1 -1) + + (if (i32.gt_u $argc 1) + (then + (drop ($ENV_SET_S $repl_env + "*FILE*" ($STRING (get_global $STRING_T) + (i32.load (i32.add $argv 4))))) + ($RELEASE ($RE "(load-file *FILE*)" $repl_env)) + (if (get_global $error_type) + (then + ($printf_1 "Error: %s\n" (get_global $error_str)) + (return 1)) + (else + (return 0))))) + + ;; Start REPL + (block $repl_done + (loop $repl_loop + (set_local $line ($readline "user> ")) + (if (i32.eqz $line) (br $repl_done)) + (if (i32.eq (i32.load8_u $line) 0) + (then + ($free $line) + (br $repl_loop))) + (set_local $res ($REP $line $repl_env)) + (if (get_global $error_type) + (then + ($printf_1 "Error: %s\n" (get_global $error_str)) + (set_global $error_type 0)) + (else + ($printf_1 "%s\n" ($to_String $res)))) + ($RELEASE $res) +;; ($PR_MEMORY -1 -1) + ($free $line) + (br $repl_loop))) + + ($print "\n") + ;;($PR_MEMORY -1 -1) + 0 + ) + + + (export "_main" (func $main)) + (export "__post_instantiate" (func $init_memory)) +) + diff --git a/wasm/stepA_mal.wam b/wasm/stepA_mal.wam new file mode 100644 index 0000000000..487cf28b27 --- /dev/null +++ b/wasm/stepA_mal.wam @@ -0,0 +1,634 @@ +(module $step8_macros + + (global $repl_env (mut i32) (i32.const 0)) + + ;; READ + (func $READ (param $str i32) (result i32) + ($read_str $str) + ) + + ;; EVAL + (func $is_pair (param $ast i32) (result i32) + (local $type i32) + (set_local $type ($TYPE $ast)) + (AND (OR (i32.eq $type (get_global $LIST_T)) + (i32.eq $type (get_global $VECTOR_T))) + (i32.ne ($VAL0 $ast) 0)) + ) + + (func $QUASIQUOTE (param $ast i32) (result i32) + (local $sym i32 $res i32 $second i32 $third i32) + (set_local $res 0) + (if (i32.eqz ($is_pair $ast)) ;; QQ_QUOTE + (then + (set_local $sym ($STRING (get_global $SYMBOL_T) "quote")) + ;; ['quote ast] + (set_local $res ($LIST2 $sym $ast)) + ($RELEASE $sym)) + (else + (set_local $res ($MEM_VAL1_ptr $ast)) + (if (AND (i32.eq ($TYPE $res) (get_global $SYMBOL_T)) + (i32.eqz ($strcmp "unquote" ($to_String $res)))) + (then + ;; ast[1] + (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))))) + (else (if (AND ($is_pair $res) + (i32.eq ($TYPE ($MEM_VAL1_ptr $res)) + (get_global $SYMBOL_T)) + (i32.eqz ($strcmp "splice-unquote" + ($to_String ($MEM_VAL1_ptr $res))))) + (then + ;; ['concat, ast[0][1], quasiquote(ast[1..])] + (set_local $sym ($STRING (get_global $SYMBOL_T) "concat")) + (set_local $second + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL1_ptr $ast)))) + (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (set_local $res ($LIST3 $sym $second $third)) + ;; release inner quasiquoted since outer list take ownership + ($RELEASE $third) + ($RELEASE $sym)) + (else + ;; ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] + (set_local $sym ($STRING (get_global $SYMBOL_T) "cons")) + (set_local $second ($QUASIQUOTE ($MEM_VAL1_ptr $ast))) + (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (set_local $res ($LIST3 $sym $second $third)) + ;; release inner quasiquoted since outer list takes ownership + ($RELEASE $third) + ($RELEASE $second) + ($RELEASE $sym))))))) + $res + ) + + (global $mac_ast_stack (mut i32) (i32.const 0)) + (global $mac_ast_stack_top (mut i32) (i32.const -1)) + + (func $MACROEXPAND (param $orig_ast i32 $env i32) (result i32) + (local $ast i32 $mac i32 $mac_env i64) + (set_global $mac_ast_stack (STATIC_ARRAY 128)) + (set_local $ast $orig_ast) + (set_local $mac 0) + (block $done + (loop $loop + (if (OR (i32.ne ($TYPE $ast) (get_global $LIST_T)) ;; a list + (i32.eqz ($VAL0 $ast)) ;; non-empty + (i32.ne ($TYPE ($MEM_VAL1_ptr $ast)) ;; leading symbol + (get_global $SYMBOL_T))) + (br $done)) + (set_local $mac_env ($ENV_FIND $env ($MEM_VAL1_ptr $ast))) + (set_local $mac (i32.wrap/i64 (i64.shr_u $mac_env (i64.const 32)))) + (if (OR (i32.eqz (i32.wrap/i64 $mac_env)) ;; defined in env + (i32.ne ($TYPE $mac) ;; a macro + (get_global $MACRO_T))) + (then + (br $done))) + + (set_local $ast ($APPLY $mac ($MEM_VAL0_ptr $ast))) + ;; PEND_A_LV + ;; if ast is not the first ast that was passed in, then add it + ;; to the pending release list. + (if (i32.ne $ast $orig_ast) + (then + (set_global $mac_ast_stack_top + (i32.add (get_global $mac_ast_stack_top) 1)) + (i32.store (i32.add + (get_global $mac_ast_stack) + (i32.mul_s (get_global $mac_ast_stack_top) 4)) + $ast))) + (if (get_global $error_type) + (br $done)) + + (br $loop) + ) + ) + $ast + ) + + (func $EVAL_AST (param $ast i32 $env i32 $skiplast i32) (result i32) + (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) + (local $ret i32 $empty i32 $current i32) + + (if (get_global $error_type) (return 0)) + (set_local $type ($TYPE $ast)) + + ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) + + ;;; switch(type) + (block $done + (block $default (block (block + (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) + ;; symbol + ;; found/res returned as hi 32/lo 32 of i64 + (set_local $res ($ENV_GET $env $ast)) + (br $done)) + ;; list, vector, hashmap + ;; MAP_LOOP_START + (set_local $res ($MAP_LOOP_START $type)) + ;; push MAP_LOOP stack + ;;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (block $done + (loop $loop + ;; check if we are done evaluating the source sequence + (if (i32.eq ($VAL0 $ast) 0) (br $done)) + + (if $skiplast + (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0) (br $done))) + + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (else + (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (set_local $val2 $res) + + ;; if error, release the unattached element + (if (get_global $error_type) + (then + ($RELEASE $res) + (set_local $res 0) + (br $done))) + + ;; for hash-maps, copy the key (inc ref since we are going + ;; to release it below) + (if (i32.eq $type (get_global $HASHMAP_T)) + (then + (set_local $val3 $val2) + (set_local $val2 ($MEM_VAL1_ptr $ast)) + (drop ($INC_REF $val2)))) + + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (set_local $ast ($MEM_VAL0_ptr $ast)) + + (br $loop) + ) + ) + ;; MAP_LOOP_DONE + (set_local $res $ret) + ;; EVAL_AST_RETURN: nothing to do + (br $done)) + ;; default + (set_local $res ($INC_REF $ast)) + ) + + $res + ) + + (func $MAL_GET_A1 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))) + (func $MAL_GET_A2 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast)))) + (func $MAL_GET_A3 (param $ast i32) (result i32) + ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) + + (func $EVAL (param $orig_ast i32 $orig_env i32) (result i32) + (local $ast i32 $env i32 $prev_ast i32 $prev_env i32 $res i32 $el i32) + (local $ftype i32 $f_args i32 $f i32 $args i32) + (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) + (local $err i32) + + (set_local $ast $orig_ast) + (set_local $env $orig_env) + (set_local $prev_ast 0) + (set_local $prev_env 0) + (set_local $res 0) + + (block $EVAL_return + (loop $TCO_loop + + (set_local $f_args 0) + (set_local $f 0) + (set_local $args 0) + + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + + (if (get_global $error_type) + (then + (set_local $res 0) + (br $EVAL_return))) + + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (then + (set_local $res ($EVAL_AST $ast $env 0)) + (br $EVAL_return))) + + ;; APPLY_LIST + (set_local $ast ($MACROEXPAND $ast $env)) + ;;($PR_VALUE ">>> >>> EVAL ast: '%s'\n" $ast) + + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (then + (set_local $res ($EVAL_AST $ast $env 0)) + (br $EVAL_return))) + + (if ($EMPTY_Q $ast) + (then + (set_local $res ($INC_REF $ast)) + (br $EVAL_return))) + + (set_local $a0 ($MEM_VAL1_ptr $ast)) + (set_local $a0sym "") + (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) + (set_local $a0sym ($to_String $a0))) + + (if (i32.eqz ($strcmp "def!" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($EVAL $a2 $env)) + (if (get_global $error_type) (br $EVAL_return)) + + ;; set a1 in env to a2 + (set_local $res ($ENV_SET $env $a1 $res)) + (br $EVAL_return)) + (else (if (i32.eqz ($strcmp "let*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + + ;; create new environment with outer as current environment + (set_local $prev_env $env) ;; save env for later release + (set_local $env ($ENV_NEW $env)) + + (block $done + (loop $loop + (if (i32.eqz ($VAL0 $a1)) + (br $done)) + ;; eval current A1 odd element + (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) + + (if (get_global $error_type) (br $done)) + + ;; set key/value in the let environment + (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) + ;; release our use, ENV_SET took ownership + ($RELEASE $res) + + ;; skip to the next pair of a1 elements + (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (br $loop) + ) + ) + + ;; release previous environment if not the current EVAL env + (if (i32.ne $prev_env $orig_env) + (then + ($RELEASE $prev_env) + (set_local $prev_env 0))) + + (set_local $ast $a2) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "do" $a0sym)) + (then + ;; EVAL the rest through second to last + (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) + (set_local $ast ($LAST $ast)) + ($RELEASE $el) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "quote" $a0sym)) + (then + (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + (br $EVAL_return)) + (else (if (i32.eqz ($strcmp "quasiquote" $a0sym)) + (then + (set_local $ast ($QUASIQUOTE ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + + ;; if we have already been here via TCO, release previous ast + (if $prev_ast ($RELEASE $prev_ast)) + (set_local $prev_ast $ast) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "defmacro!" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($EVAL $a2 $env)) + ($SET_TYPE $res (get_global $MACRO_T)) + (if (get_global $error_type) + (br $EVAL_return)) + + ;; set a1 in env to a2 + (set_local $res ($ENV_SET $env $a1 $res)) + (br $EVAL_return)) + (else (if (i32.eqz ($strcmp "macroexpand" $a0sym)) + (then + ;; since we are returning it unevaluated, inc the ref cnt + (set_local $res ($INC_REF ($MACROEXPAND + ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)) + $env)))) + (else (if (i32.eqz ($strcmp "try*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $res ($EVAL $a1 $env)) + + ;; if there is no error, return + (if (i32.eqz (get_global $error_type)) + (br $EVAL_return)) + ;; if there is an error and res is set, we need to free it + ($printf_1 "res value: %d\n" $res) + ($RELEASE $res) + ;; if there is no catch block then return + (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast)))) + (br $EVAL_return)) + + ;; save the current environment for release + (set_local $prev_env $env) + ;; create environment for the catch block eval + (set_local $env ($ENV_NEW $env)) + + ;; set a1 and a2 from the catch block + (set_local $a1 ($MAL_GET_A1 ($MAL_GET_A2 $ast))) + (set_local $a2 ($MAL_GET_A2 ($MAL_GET_A2 $ast))) + + ;; create object for string errors + (if (i32.eq (get_global $error_type) 1) + (then + (set_local $err ($STRING (get_global $STRING_T) + (get_global $error_str)))) + (else + (set_local $err (get_global $error_val)))) + ;; bind the catch symbol to the error object + (drop ($ENV_SET $env $a1 $err)) + ;; release our use, env took ownership + ($RELEASE $err) + + ;; unset error for catch eval + (set_global $error_type 0) + (i32.store (get_global $error_str) (CHR "\x00")) + + ;; release previous environment if not the current EVAL env + (if (i32.ne $prev_env $orig_env) + (then + ($RELEASE $prev_env) + (set_local $prev_env 0))) + + (set_local $ast $a2) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "if" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $res ($EVAL $a1 $env)) + + (if (get_global $error_type) + (then (nop)) + (else (if (OR (i32.eq $res (get_global $NIL)) + (i32.eq $res (get_global $FALSE))) + (then + ($RELEASE $res) + ;; if no false case (A3), return nil + (if (i32.lt_u ($COUNT $ast) 4) + (then + (set_local $res ($INC_REF (get_global $NIL))) + (br $EVAL_return)) + (else + (set_local $ast ($MAL_GET_A3 $ast))))) + (else + ($RELEASE $res) + (set_local $ast ($MAL_GET_A2 $ast)))))) + (br $TCO_loop)) + (else (if (i32.eqz ($strcmp "fn*" $a0sym)) + (then + (set_local $a1 ($MAL_GET_A1 $ast)) + (set_local $a2 ($MAL_GET_A2 $ast)) + (set_local $res ($MALFUNC $a2 $a1 $env)) + (br $EVAL_return)) + (else + ;; EVAL_INVOKE + (set_local $res ($EVAL_AST $ast $env 0)) + (set_local $f_args $res) + + ;; if error, return f/args for release by caller + (if (get_global $error_type) + (then + (set_local $res $f_args) + (br $EVAL_return))) + + (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest + (set_local $f ($DEREF_META ($MEM_VAL1_ptr $f_args))) ;; value + + (set_local $ftype ($TYPE $f)) + (if (i32.eq $ftype (get_global $FUNCTION_T)) + (then + (if (i32.eq ($VAL0 $f) 0) ;; eval + (then + (set_local $res ($EVAL ($MEM_VAL1_ptr $args) + (get_global $repl_env)))) + (else + (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f))))) + ;; release f/args + ($RELEASE $f_args) + (br $EVAL_return)) + (else (if (i32.eq $ftype (get_global $MALFUNC_T)) + (then + ;; save the current environment for release + (set_local $prev_env $env) + ;; create new environment using env and params stored in function + (set_local $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + ($MEM_VAL1_ptr $f) $args)) + + ;; release previous environment if not the current EVAL env + ;; because our new env refers to it and we no longer need to + ;; track it (since we are TCO recurring) + (if (i32.ne $prev_env $orig_env) + (then + ($RELEASE $prev_env) + (set_local $prev_env 0))) + + ;; claim the AST before releasing the list containing it + (set_local $ast ($MEM_VAL0_ptr $f)) + (drop ($INC_REF $ast)) + + ;; if we have already been here via TCO, release previous + ;; ast + ;; PEND_A_LV + (if $prev_ast ($RELEASE $prev_ast)) + (set_local $prev_ast $ast) + + ;; release f/args + ($RELEASE $f_args) + + (br $TCO_loop)) + (else + ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) + (set_local $res 0) + ($RELEASE $f_args) + (br $EVAL_return))))))))))))))))))))))))) + + ) ;; end of TCO_loop + ) ;; end of EVAL_return + + ;; EVAL_RETURN + (if (i32.ne $env $orig_env) ($RELEASE $env)) + (if $prev_ast ($RELEASE $prev_ast)) + + $res + ) + + ;; PRINT + (func $PRINT (param $ast i32) (result i32) + ($pr_str $ast 1) + ) + + ;; REPL + (func $RE (param $line i32 $env i32) (result i32) + (local $mv1 i32 $res i32) + (block $rep_done + (set_local $mv1 ($READ $line)) + (if (get_global $error_type) (br $rep_done)) + + (set_local $res ($EVAL $mv1 $env)) + ) + + ;; release memory from MAL_READ + ($RELEASE $mv1) + ;; release memory from MACROEXPAND + ;; TODO: needs to happen in EVAL + (block $done + (loop $loop + (if (i32.lt_s (get_global $mac_ast_stack_top) 0) + (br $done)) + ($RELEASE (i32.load (i32.add + (get_global $mac_ast_stack) + (i32.mul_s (get_global $mac_ast_stack_top) 4)))) + (set_global $mac_ast_stack_top + (i32.sub_s (get_global $mac_ast_stack_top) 1)) + (br $loop) + ) + ) + $res + ) + + (func $REP (param $line i32 $env i32) (result i32) + (local $mv2 i32 $ms i32) + (block $rep_done + (set_local $mv2 ($RE $line $env)) + (if (get_global $error_type) (br $rep_done)) + +;; ($PR_MEMORY -1 -1) + (set_local $ms ($PRINT $mv2)) + ) + + ;; release memory from RE + ($RELEASE $mv2) + $ms + ) + + (func $main (param $argc i32 $argv i32) (result i32) + (local $line i32 $res i32 $repl_env i32) + ;; argument processing + (local $i i32 $ret i32 $empty i32 $current i32 $tmp i32 $val2 i32) + + ;; DEBUG +;; ($printf_1 "argc: 0x%x\n" $argc) +;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) +;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) + + (set_global $repl_env ($ENV_NEW (get_global $NIL))) + (set_local $repl_env (get_global $repl_env)) + + ;; core.EXT: defined in wasm + ($add_core_ns $repl_env) + (drop ($ENV_SET_S $repl_env "eval" ($FUNCTION 0))) + + + ;; core.mal: defined using the language itself + ($RELEASE ($RE "(def! *host-language* \"WebAssembly\")" $repl_env)) + ($RELEASE ($RE "(def! not (fn* (a) (if a false true)))" $repl_env)) + ($RELEASE ($RE "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" $repl_env)) + ($RELEASE ($RE "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" $repl_env)) + ($RELEASE ($RE "(def! *gensym-counter* (atom 0))" $repl_env)) + ($RELEASE ($RE "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))" $repl_env)) + ($RELEASE ($RE "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (c (gensym)) `(let* (~c ~(first xs)) (if ~c ~c (or ~@(rest xs)))))))))" $repl_env)) + + ;; Command line arguments + (set_local $res ($MAP_LOOP_START (get_global $LIST_T))) + ;; push MAP_LOP stack + ;; empty = current = ret = res + (set_local $ret $res) + (set_local $current $res) + (set_local $empty $res) + + (set_local $i 2) + (block $done + (loop $loop + (if (i32.ge_u $i $argc) (br $done)) + + (set_local $val2 ($STRING (get_global $STRING_T) + (i32.load (i32.add $argv (i32.mul_u $i 4))))) + + ;; MAP_LOOP_UPDATE + (set_local $res ($MAP_LOOP_UPDATE + (get_global $LIST_T) $empty $current $val2 0)) + (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + ;; if first element, set return to new element + (set_local $ret $res)) + ;; update current to point to new element + (set_local $current $res) + + (set_local $i (i32.add $i 1)) + (br $loop) + ) + ) + (drop ($ENV_SET_S $repl_env "*ARGV*" $ret)) + + + ;;($PR_MEMORY -1 -1) + + (if (i32.gt_u $argc 1) + (then + (drop ($ENV_SET_S $repl_env + "*FILE*" ($STRING (get_global $STRING_T) + (i32.load (i32.add $argv 4))))) + ($RELEASE ($RE "(load-file *FILE*)" $repl_env)) + (if (get_global $error_type) + (then + ($printf_1 "Error: %s\n" (get_global $error_str)) + (return 1)) + (else + (return 0))))) + + ($RELEASE ($RE "(println (str \"Mal [\" *host-language* \"]\"))" $repl_env)) + + ;; Start REPL + (block $repl_done + (loop $repl_loop + (set_local $line ($readline "user> ")) + (if (i32.eqz $line) (br $repl_done)) + (if (i32.eq (i32.load8_u $line) 0) + (then + ($free $line) + (br $repl_loop))) + (set_local $res ($REP $line $repl_env)) + (if (get_global $error_type) + (then + ($printf_1 "Error: %s\n" (get_global $error_str)) + (set_global $error_type 0)) + (else + ($printf_1 "%s\n" ($to_String $res)))) + ($RELEASE $res) + ($free $line) + ;;($PR_MEMORY_SUMMARY_SMALL) + (br $repl_loop))) + + ($print "\n") + ;;($PR_MEMORY -1 -1) + 0 + ) + + + (export "_main" (func $main)) + (export "__post_instantiate" (func $init_memory)) +) + diff --git a/wasm/string.wam b/wasm/string.wam new file mode 100644 index 0000000000..4a68c0c369 --- /dev/null +++ b/wasm/string.wam @@ -0,0 +1,226 @@ +(module $string + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ;; Copy len bytes from src to dst + ;; Returns len + (func $memmove (param $dst i32 $src i32 $len i32) + (local $idx i32) + (set_local $idx 0) + (loop $copy + (i32.store8_u (i32.add $idx $dst) + (i32.load8_u (i32.add $idx $src))) + (set_local $idx (i32.add 1 $idx)) + (br_if $copy (i32.lt_u $idx $len)) + ) + ) + + (func $strlen (param $str i32) (result i32) + (local $cur i32) + (set_local $cur $str) + (loop $count + (if (i32.ne 0 (i32.load8_u $cur)) + (then + (set_local $cur (i32.add $cur 1)) + (br $count))) + ) + (i32.sub_u $cur $str) + ) + + ;; Based on https://stackoverflow.com/a/25705264/471795 + ;; This could be made much more efficient + (func $strstr (param $haystack i32 $needle i32) (result i32) + (local $i i32 $needle_len i32 $len i32) + + (set_local $needle_len ($strlen $needle)) + (set_local $len ($strlen $haystack)) + + (if (i32.eq $needle_len 0) (return $haystack)) + + (set_local $i 0) + (block $done + (loop $loop + (if (i32.gt_s $i (i32.sub_s $len $needle_len)) (br $done)) + + (if (AND (i32.eq (i32.load8_u $haystack 0) + (i32.load8_u $needle 0)) + (i32.eqz ($strncmp $haystack $needle $needle_len))) + (return $haystack)) + (set_local $haystack (i32.add $haystack 1)) + (set_local $i (i32.add $i 1)) + (br $loop) + ) + ) + 0 + ) + + (func $atoi (param $str i32) (result i32) + (local $acc i32) + (local $i i32) + (local $neg i32) + (local $ch i32) + (set_local $acc 0) + (set_local $i 0) + (set_local $neg 0) + (block $done + (loop $loop + (set_local $ch (i32.load8_u (i32.add $str $i))) + (if (AND (i32.ne $ch (CHR "-")) + (OR (i32.lt_u $ch (CHR "0")) + (i32.gt_u $ch (CHR "9")))) + (br $done)) + (set_local $i (i32.add $i 1)) + (if (i32.eq $ch (CHR "-")) + (then + (set_local $neg 1)) + (else + (set_local $acc (i32.add (i32.mul_u $acc 10) + (i32.sub_u $ch (CHR "0")))))) + (br $loop) + ) + ) + (if i32 $neg + (then (i32.sub_s 0 $acc)) + (else $acc)) + ) + + (func $strcmp (param $s1 i32 $s2 i32) (result i32) + (block $done + (loop $loop + (if (OR (i32.eqz (i32.load8_u $s1)) (i32.eqz (i32.load8_u $s2))) + (br $done)) + (if (i32.ne (i32.load8_u $s1) (i32.load8_u $s2)) + (br $done)) + (set_local $s1 (i32.add $s1 1)) + (set_local $s2 (i32.add $s2 1)) + (br $loop) + ) + ) + (if i32 (i32.eq (i32.load8_u $s1) (i32.load8_u $s2)) + (then 0) + (else + (if i32 (i32.lt_u (i32.load8_u $s1) (i32.load8_u $s2)) + (then -1) + (else 1)))) + ) + + (func $strncmp (param $s1 i32 $s2 i32 $len i32) (result i32) + (local $i i32) + (set_local $i 0) + (if (i32.eq $len 0) (return 0)) + (block $done + (loop $loop + (if (i32.ge_u $i $len) (br $done)) + (if (i32.eqz (i32.load8_u (i32.add $i $s1))) (br $done)) + (if (i32.ne (i32.load8_u (i32.add $i $s1)) + (i32.load8_u (i32.add $i $s2))) (br $done)) + (set_local $i (i32.add $i 1)) + (br $loop) + ) + ) + (if (OR (i32.eq $i $len) + (i32.eq (i32.load8_u (i32.add $i $s1)) + (i32.load8_u (i32.add $i $s2)))) + (return 0)) + (if i32 (i32.lt_u (i32.load8_u (i32.add $i $s1)) + (i32.load8_u (i32.add $i $s2))) + (then -1) + (else 1)) + ) + + ;; Writes new string to grass with all needles in haystack replaced. + ;; If the length of replace is equal to of less than needle then + ;; grass can be NULL. + ;; Returns length of grass. + (func $REPLACE3 (param $grass i32 $haystack i32 + $needle0 i32 $replace0 i32 + $needle1 i32 $replace1 i32 + $needle2 i32 $replace2 i32) (result i32) + (local $needle i32 $replace i32) + (local $haystack_len i32 $needle_len i32 $replace_len i32) + (local $src_str i32 $dst_str i32 $s i32 $found_tmp i32 $found i32) + (local $replace_s i32 $replace_len_s i32 $needle_len_s i32) + + (set_local $haystack_len ($strlen $haystack)) + (set_local $src_str $haystack) + (set_local $dst_str $grass) + + ;; in-place + (if (i32.eqz $grass) + (then + ;; check that we aren't expanding in place + (set_local $s 0) + (block $done + (loop $loop + (if (i32.ge_u $s 3) (br $done)) + (set_local $needle (if i32 (i32.eq $s 0) $needle0 + (if i32 (i32.eq $s 1) $needle1 + $needle2))) + (set_local $replace (if i32 (i32.eq $s 0) $replace0 + (if i32 (i32.eq $s 1) $replace1 + $replace2))) + (set_local $needle_len ($strlen $needle)) + (set_local $replace_len ($strlen $replace)) + (if (i32.gt_u $replace_len $needle_len) + (then + ($print "REPLACE: invalid expanding in-place call\n") + ($exit 1))) + (set_local $s (i32.add $s 1)) + (br $loop) + ) + ) + (set_local $grass $haystack) + (set_local $dst_str $grass))) + + (block $done1 + (loop $loop1 + (if (i32.ge_s (i32.sub_s $src_str $haystack) $haystack_len) + (br $done1)) + + ;; Find the earliest match + (set_local $found 0) + (set_local $s 0) + (block $done2 + (loop $loop2 + (if (i32.ge_u $s 3) (br $done2)) + (set_local $needle (if i32 (i32.eq $s 0) $needle0 + (if i32 (i32.eq $s 1) $needle1 + $needle2))) + (set_local $replace (if i32 (i32.eq $s 0) $replace0 + (if i32 (i32.eq $s 1) $replace1 + $replace2))) + (set_local $s (i32.add $s 1)) + (set_local $found_tmp ($strstr $src_str $needle)) + (if (i32.eqz $found_tmp) (br $loop2)) + (if (OR (i32.eqz $found) (i32.lt_s $found_tmp $found)) + (then + (set_local $found $found_tmp) + (set_local $needle_len_s ($strlen $needle)) + (set_local $replace_s $replace) + (set_local $replace_len_s ($strlen $replace)))) + (br $loop2) + ) + ) + (if (i32.eqz $found) (br $done1)) + ;; copy before the match + ($memmove $dst_str $src_str (i32.add (i32.sub_s $found $src_str) 1)) + (set_local $dst_str (i32.add $dst_str (i32.sub_s $found $src_str))) + ;; add the replace string + ($memmove $dst_str $replace_s (i32.add $replace_len_s 1)) + (set_local $dst_str (i32.add $dst_str $replace_len_s)) + ;; Move to after the match + (set_local $src_str (i32.add $found $needle_len_s)) + (br $loop1) + ) + ) + + ;; Copy the left-over + ($memmove $dst_str $src_str ($strlen $src_str)) + (set_local $dst_str (i32.add $dst_str ($strlen $src_str))) + (i32.store8_u $dst_str (CHR "\x00")) + + (i32.sub_s $dst_str $grass) + ) + +) + diff --git a/wasm/types.wam b/wasm/types.wam index 2e809534b9..8f14ad9a3a 100644 --- a/wasm/types.wam +++ b/wasm/types.wam @@ -55,21 +55,87 @@ (func $INC_REF (param $mv i32) (result i32) (i32.store $mv (i32.add (i32.load $mv) 32)) - $mv) + $mv + ) + + (func $TRUE_FALSE (param $val i32) (result i32) + ($INC_REF (if i32 $val (get_global $TRUE) (get_global $FALSE))) + ) (func $THROW_STR_0 (param $fmt i32) (drop ($sprintf_1 (get_global $error_str) $fmt "")) - (set_global $error_type 1)) + (set_global $error_type 1) + ) (func $THROW_STR_1 (param $fmt i32) (param $v0 i32) (drop ($sprintf_1 (get_global $error_str) $fmt $v0)) - (set_global $error_type 1)) + (set_global $error_type 1) + ) + + (func $EQUAL_Q (param $a i32 $b i32) (result i32) + (local $ta i32 $tb i32) + (set_local $ta ($TYPE $a)) + (set_local $tb ($TYPE $b)) + + (if (AND (OR (i32.eq $ta (get_global $LIST_T)) + (i32.eq $ta (get_global $VECTOR_T))) + (OR (i32.eq $tb (get_global $LIST_T)) + (i32.eq $tb (get_global $VECTOR_T)))) + (then + ;; EQUAL_Q_SEQ + (block $done + (loop $loop + (if (OR (i32.eq ($VAL0 $a) 0) (i32.eq ($VAL0 $b) 0)) + (br $done)) + (if ($EQUAL_Q ($MEM_VAL1_ptr $a) ($MEM_VAL1_ptr $b)) + (then + (set_local $a ($MEM_VAL0_ptr $a)) + (set_local $b ($MEM_VAL0_ptr $b))) + (else + (return 0))) + (br $loop) + ) + ) + (return (AND (i32.eq ($VAL0 $a) 0) (i32.eq ($VAL0 $b) 0)))) + (else (if (AND (i32.eq $ta (get_global $HASHMAP_T)) + (i32.eq $tb (get_global $HASHMAP_T))) + ;; EQUAL_Q_HM + (return 1)) + ;; TODO: remove this once strings are interned + (else (if (OR (AND (i32.eq $ta (get_global $STRING_T)) + (i32.eq $tb (get_global $STRING_T))) + (AND (i32.eq $ta (get_global $SYMBOL_T)) + (i32.eq $tb (get_global $SYMBOL_T)))) + (return (i32.eqz ($strcmp ($to_String $a) ($to_String $b)))) + (else + (return (AND (i32.eq $ta $tb) + (i32.eq ($VAL0 $a) ($VAL0 $b))))))))) + ) + + (func $DEREF_META (param $mv i32) (result i32) + (loop $loop + (if (i32.eq ($TYPE $mv) (get_global $METADATA_T)) + (then + (set_local $mv ($MEM_VAL0_ptr $mv)) + (br $loop))) + ) + $mv + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; string functions + + (func $to_String (param $mv i32) (result i32) + ;; skip string refcnt + (i32.add 4 ($VAL0 $mv)) + ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; numeric functions (func $INTEGER (param $val i32) (result i32) - ($ALLOC_SCALAR (get_global $INTEGER_T) $val)) + ($ALLOC_SCALAR (get_global $INTEGER_T) $val) + ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; sequence functions @@ -102,21 +168,148 @@ ($RELEASE $val3)) (if (i32.gt_u $current (get_global $EMPTY_HASHMAP)) ;; if not first element, set current next to point to new element - (i32.store ($VAL0_ptr $current) ($MalVal_index $res))) + (i32.store ($VAL0_ptr $current) ($IDX $res))) + + $res + ) + + (func $FORCE_SEQ_TYPE (param $type i32) (param $mv i32) (result i32) + (local $res i32) + ;; if it's already the right type, inc ref cnt and return it + (if (i32.eq $type ($TYPE $mv)) (return ($INC_REF $mv))) + ;; if it's empty, return the sequence match + (if (i32.le_u $mv (get_global $EMPTY_HASHMAP)) + (return ($MAP_LOOP_START $type))) + ;; otherwise, copy first element to turn it into correct type + ($ALLOC $type ($MEM_VAL0_ptr $mv) ($MEM_VAL1_ptr $mv) 0) + ) + + (func $LIST (param $seq i32 $first i32) (result i32) + ($ALLOC (get_global $LIST_T) $seq $first 0) + ) + (func $LIST2 (param $first i32 $second i32) (result i32) + ;; last element is empty list + (local $tmp i32 $res i32) + (set_local $tmp ($LIST (get_global $EMPTY_LIST) $second)) + (set_local $res ($LIST $tmp $first)) + ($RELEASE $tmp) ;; new list takes ownership of previous $res ) + (func $LIST3 (param $first i32 $second i32 $third i32) (result i32) + (local $tmp i32 $res i32) + (set_local $tmp ($LIST2 $second $third)) + (set_local $res ($LIST $tmp $first)) + ($RELEASE $tmp) ;; new list takes ownership of previous + $res + ) + + (func $LIST_Q (param $mv i32) (result i32) + (i32.eq ($TYPE $mv) (get_global $LIST_T)) + ) + (func $EMPTY_Q (param $mv i32) (result i32) (i32.eq ($VAL0 $mv) 0) ) + (func $COUNT (param $mv i32) (result i32) + (local $cnt i32) + (set_local $cnt 0) + (block $done + (loop $loop + (if (i32.eq ($VAL0 $mv) 0) (br $done)) + (set_local $cnt (i32.add $cnt 1)) + (set_local $mv ($MEM_VAL0_ptr $mv)) + (br $loop) + ) + ) + $cnt + ) + + (func $LAST (param $mv i32) (result i32) + (local $cur i32) + ;; TODO: check that actually a list/vector + (if (i32.eq ($VAL0 $mv) 0) + ;; empty seq, return nil + (return ($INC_REF (get_global $NIL)))) + (block $done + (loop $loop + ;; end, return previous value + (if (i32.eq ($VAL0 $mv) 0) (br $done)) + ;; current becomes previous entry + (set_local $cur $mv) + ;; next entry + (set_local $mv ($MEM_VAL0_ptr $mv)) + (br $loop) + ) + ) + ($INC_REF ($MEM_VAL1_ptr $cur)) + ) + + ;; make a copy of sequence seq from index start to end + ;; set last to last element of slice before the empty + ;; set after to element following slice (or original) + (func $SLICE (param $seq i32) (param $start i32) (param $end i32) + (result i64) + (local $idx i32 $res i32 $tmp i32 $last i32) + (set_local $idx 0) + (set_local $res ($INC_REF (get_global $EMPTY_LIST))) + (set_local $last 0) + (set_local $tmp $res) + ;; advance seq to start + (block $done + (loop $loop + (if (OR (i32.ge_s $idx $start) + (i32.eqz ($VAL0 $seq))) + (br $done)) + (set_local $seq ($MEM_VAL0_ptr $seq)) + (set_local $idx (i32.add $idx 1)) + (br $loop) + ) + ) + (block $done + (loop $loop + ;; if current position is at end, then return or if we reached + ;; end seq, then return + (if (OR (AND (i32.ne $end -1) + (i32.ge_s $idx $end)) + (i32.eqz ($VAL0 $seq))) + (then + (set_local $res $tmp) + (br $done))) + ;; allocate new list element with copied value + (set_local $res ($LIST (get_global $EMPTY_LIST) + ($MEM_VAL1_ptr $seq))) + ;; sequence took ownership + ($RELEASE (get_global $EMPTY_LIST)) + (if (i32.eqz $last) + (then + ;; if first element, set return value to new element + (set_local $tmp $res)) + (else + ;; if not the first element, set return value to new element + (i32.store ($VAL0_ptr $last) ($IDX $res)))) + (set_local $last $res) ;; update last list element + ;; advance to next element of seq + (set_local $seq ($MEM_VAL0_ptr $seq)) + (set_local $idx (i32.add $idx 1)) + (br $loop) + ) + ) + + ;; combine last/res as hi 32/low 32 of i64 + (i64.or + (i64.shl_u (i64.extend_u/i32 $last) (i64.const 32)) + (i64.extend_u/i32 $res)) + ) + (func $HASHMAP (result i32) ;; just point to static empty hash-map ($INC_REF (get_global $EMPTY_HASHMAP)) ) - (func $ASSOC1 (param $hm i32) (param $k i32) (param $v i32) (result i32) + (func $ASSOC1 (param $hm i32 $k i32 $v i32) (result i32) (local $res i32) (set_local $res ($ALLOC (get_global $HASHMAP_T) $hm $k $v)) ;; we took ownership of previous release @@ -124,9 +317,8 @@ $res ) - (func $ASSOC1_S (param $hm i32) (param $k i32) (param $v i32) (result i32) - (local $kmv i32) - (local $res i32) + (func $ASSOC1_S (param $hm i32 $k i32 $v i32) (result i32) + (local $kmv i32 $res i32) (set_local $kmv ($STRING (get_global $STRING_T) $k)) (set_local $res ($ASSOC1 $hm $kmv $v)) ;; map took ownership of key @@ -135,10 +327,7 @@ ) (func $HASHMAP_GET (param $hm i32) (param $key_mv i32) (result i64) - (local $res i32) - (local $found i32) - (local $key i32) - (local $test_key_mv i32) + (local $res i32 $found i32 $key i32 $test_key_mv i32) (set_local $key ($to_String $key_mv)) (set_local $found 0) @@ -177,10 +366,8 @@ ($ALLOC_SCALAR (get_global $FUNCTION_T) $index) ) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; string functions + (func $MALFUNC (param $ast i32 $params i32 $env i32) (result i32) + ($ALLOC (get_global $MALFUNC_T) $ast $params $env) + ) - (func $to_String (param $mv i32) (result i32) - ;; skip string refcnt - (i32.add 4 ($MalVal_val ($MalVal_index $mv) 0))) ) diff --git a/wasm/util.wam b/wasm/util.wam index e663d4adb0..926d26765d 100644 --- a/wasm/util.wam +++ b/wasm/util.wam @@ -4,160 +4,85 @@ (import "env" "exit" (func $exit (param i32))) (import "env" "stdout" (global $stdout i32)) - (import "env" "putchar" (func $putchar (param i32) (result i32))) (import "env" "fputs" (func $fputs (param i32 i32) (result i32))) ;;(import "env" "readline" (func $readline (param i32) (result i32))) (import "libedit.so" "readline" (func $readline (param i32) (result i32))) ;;(import "libreadline.so" "readline" (func $readline (param i32) (result i32))) - (global $sprintf_buf (mut i32) 0) + (global $util_buf (mut i32) 0) + + ;; read_file defintions / FFI information + (global $STAT_SIZE i32 88) + (global $STAT_ST_SIZE_OFFSET i32 44) + (global $STAT_VER_LINUX i32 3) + (global $O_RDONLY i32 0) + (import "env" "open" (func $open (param i32 i32 i32) (result i32))) + (import "env" "read" (func $read (param i32 i32 i32) (result i32))) + (import "env" "__fxstat" (func $__fxstat (param i32 i32 i32) (result i32))) + (global $TIMEVAL_SIZE i32 8) + (global $TV_SEC_OFFSET i32 0) + (global $TV_USEC_OFFSET i32 4) + (import "env" "gettimeofday" (func $gettimeofday (param i32 i32) (result i32))) (func $init_sprintf_mem - ;; 256 character sprintf static buffer - (set_global $sprintf_buf " ") + ;; sprintf static buffer + (set_global $util_buf (STATIC_ARRAY 256)) ) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - ;; Copy len chatacters from src to dst - ;; Returns len - (func $MEM_COPY (param $dst i32) (param $src i32) (param $len i32) - (local $idx i32) - (set_local $idx 0) - (loop $copy - (i32.store8_u (i32.add $idx $dst) - (i32.load8_u (i32.add $idx $src))) - (set_local $idx (i32.add 1 $idx)) - (br_if $copy (i32.lt_u $idx $len)) - ) - ) - - (func $STRING_LEN (param $str i32) (result i32) - (local $cur i32) - (set_local $cur $str) - (loop $count - (if (i32.ne 0 (i32.load8_u $cur)) - (then - (set_local $cur (i32.add $cur 1)) - (br $count))) - ) - (i32.sub_u $cur $str) - ) - - (func $ATOI (param $str i32) (result i32) - (local $acc i32) - (local $i i32) - (local $neg i32) - (local $ch i32) - (set_local $acc 0) - (set_local $i 0) - (set_local $neg 0) - (block $done - (loop $loop - (set_local $ch (i32.load8_u (i32.add $str $i))) - (if (i32.and (i32.ne $ch (CHR "-")) - (i32.or (i32.lt_u $ch (CHR "0")) - (i32.gt_u $ch (CHR "9")))) - (br $done)) - (set_local $i (i32.add $i 1)) - (if (i32.eq $ch (CHR "-")) - (then - (set_local $neg 1)) - (else - (set_local $acc (i32.add (i32.mul_u $acc 10) - (i32.sub_u $ch (CHR "0")))))) - (br $loop) - ) - ) - (if i32 $neg - (then (i32.sub_s 0 $acc)) - (else $acc)) - ) - - (func $strcmp (param $s1 i32) (param $s2 i32) (result i32) - (block $done - (loop $loop - (if (i32.or (i32.eqz (i32.load8_u $s1)) - (i32.eqz (i32.load8_u $s2))) - (br $done)) - (if (i32.ne (i32.load8_u $s1) - (i32.load8_u $s2)) - (br $done)) - (set_local $s1 (i32.add $s1 1)) - (set_local $s2 (i32.add $s2 1)) - (br $loop) - ) - ) - (if i32 (i32.eq (i32.load8_u $s1) - (i32.load8_u $s2)) - (then 0) - (else - (if i32 (i32.lt_u (i32.load8_u $s1) - (i32.load8_u $s2)) - (then -1) - (else 1)))) - ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (func $print (param $addr i32) (drop ($fputs $addr (get_global $stdout)))) - (func $printf_1 (param $fmt i32) - (param $v0 i32) - (drop ($sprintf_6 (get_global $sprintf_buf) $fmt $v0 0 0 0 0 0)) - ($print (get_global $sprintf_buf)) + (func $printf_1 (param $fmt i32) (param $v0 i32) + (drop ($sprintf_6 (get_global $util_buf) $fmt $v0 0 0 0 0 0)) + ($print (get_global $util_buf)) ) - (func $printf_2 (param $fmt i32) - (param $v0 i32) (param $v1 i32) - (drop ($sprintf_6 (get_global $sprintf_buf) $fmt $v0 $v1 0 0 0 0)) - ($print (get_global $sprintf_buf)) + (func $printf_2 (param $fmt i32 $v0 i32 $v1 i32) + (drop ($sprintf_6 (get_global $util_buf) $fmt $v0 $v1 0 0 0 0)) + ($print (get_global $util_buf)) ) (func $printf_3 (param $fmt i32) (param $v0 i32) (param $v1 i32) (param $v2 i32) - (drop ($sprintf_6 (get_global $sprintf_buf) $fmt $v0 $v1 $v2 0 0 0)) - ($print (get_global $sprintf_buf)) + (drop ($sprintf_6 (get_global $util_buf) $fmt $v0 $v1 $v2 0 0 0)) + ($print (get_global $util_buf)) ) (func $printf_4 (param $fmt i32) (param $v0 i32) (param $v1 i32) (param $v2 i32) (param $v3 i32) - (drop ($sprintf_6 (get_global $sprintf_buf) $fmt $v0 $v1 $v2 $v3 0 0)) - ($print (get_global $sprintf_buf)) + (drop ($sprintf_6 (get_global $util_buf) $fmt $v0 $v1 $v2 $v3 0 0)) + ($print (get_global $util_buf)) ) (func $printf_5 (param $fmt i32) (param $v0 i32) (param $v1 i32) (param $v2 i32) (param $v3 i32) (param $v4 i32) - (drop ($sprintf_6 (get_global $sprintf_buf) $fmt $v0 $v1 $v2 $v3 $v4 0)) - ($print (get_global $sprintf_buf)) + (drop ($sprintf_6 (get_global $util_buf) $fmt $v0 $v1 $v2 $v3 $v4 0)) + ($print (get_global $util_buf)) ) (func $printf_6 (param $fmt i32) (param $v0 i32) (param $v1 i32) (param $v2 i32) (param $v3 i32) (param $v4 i32) (param $v5 i32) - (drop ($sprintf_6 (get_global $sprintf_buf) $fmt $v0 $v1 $v2 $v3 $v4 $v5)) - ($print (get_global $sprintf_buf)) + (drop ($sprintf_6 (get_global $util_buf) $fmt $v0 $v1 $v2 $v3 $v4 $v5)) + ($print (get_global $util_buf)) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (func $_sprintdigit (param $str i32) (param $num i32) (param $base i32) - (local $n i32) - (local $ch i32) + (local $n i32 $ch i32) (set_local $n (i32.rem_u $num $base)) - (set_local $ch (if (result i32) (i32.lt_u $n 10) - 48 - 55)) + (set_local $ch (if (result i32) (i32.lt_u $n 10) 48 55)) (i32.store8_u $str (i32.add $n $ch)) ) ;; TODO: switch to snprint* (add buffer len) - (func $_sprintnum (param $str i32) (param $num i32) (param $base i32) - (result i32) - (if (i32.and (i32.eq $base 10) - (i32.lt_s $num 0)) + (func $_sprintnum (param $str i32 $num i32 $base i32) (result i32) + (if (AND (i32.eq $base 10) (i32.lt_s $num 0)) (then ;; Print '-' if negative (i32.store8_u $str (CHR "-")) @@ -225,20 +150,18 @@ (set_local $pstr ($_sprintnum $pstr $v 10))) (else (if (i32.eq (CHR "x") $ch) (then - (set_local $pstr ($_sprintnum $pstr $v 10))) + (set_local $pstr ($_sprintnum $pstr $v 16))) (else (if (i32.eq (CHR "s") $ch) (then - (set_local $len ($STRING_LEN $v)) - ($MEM_COPY $pstr $v $len) + (set_local $len ($strlen $v)) + ($memmove $pstr $v $len) (set_local $pstr (i32.add $pstr $len))) (else (if (i32.eq (CHR "c") $ch) (then (i32.store8_u $pstr $v) (set_local $pstr (i32.add $pstr 1))) (else - ($print "Illegal format character: ") - (drop ($putchar $ch)) - (drop ($putchar (CHR "\n"))) + ($printf_1 "Illegal format character: '%c'\n" $ch) ($exit 3))))))))) (set_local $vidx (i32.add 1 $vidx)) @@ -250,5 +173,65 @@ $pstr ) -) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ;; Returns malloc'd string. Must be free by caller + (func $read_file (param $path i32) (result i32) + (local $fst i32 $fd i32 $str i32 $st_size i32 $sz i32) + (set_local $str 0) + (set_local $fst ($malloc (get_global $STAT_SIZE))) + (if (i32.le_s $fst 0) + (then + ($printf_1 "ERROR: malloc of %d bytes failed\n" + (get_global $STAT_SIZE)) + (return 0))) + (block $free_fst + (set_local $fd ($open $path (get_global $O_RDONLY) 0)) + (if (i32.lt_s $fd 0) + (then + ($printf_1 "ERROR: slurp failed to open '%s'\n" $path) + (br $free_fst))) + (if (i32.lt_s ($__fxstat (get_global $STAT_VER_LINUX) $fd $fst) 0) + (then + ($printf_1 "ERROR: slurp failed to stat '%s'\n" $path) + (br $free_fst))) + (set_local $st_size (i32.load + (i32.add $fst (get_global $STAT_ST_SIZE_OFFSET)))) + (set_local $str ($malloc (i32.add 1 $st_size))) + (if (i32.le_s $str 0) + (then + ($printf_1 "ERROR: malloc of %d bytes failed\n" $st_size) + (br $free_fst))) + (set_local $sz ($read $fd $str $st_size)) + (if (i32.ne $sz $st_size) + (then + ($free $str) + (set_local $str 0) + ($printf_1 "ERROR: slurp failed to stat '%s'\n" $path) + (br $free_fst))) + ;; Add null to string + (i32.store8_u (i32.add $str $st_size) 0) + ) + ($free $fst) + $str + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + + (func $get_time_ms (result i32) + (local $tv i32 $secs i32 $usecs i32 $msecs i32) + (set_local $tv ($malloc (get_global $TIMEVAL_SIZE))) + (drop ($gettimeofday $tv 0)) + (set_local $secs (i32.load (i32.add $tv (get_global $TV_SEC_OFFSET)))) + ;; subtract 30 years to make sure secs is positive and can be + ;; multiplied by 1000 + (set_local $secs (i32.sub_s $secs 0x38640900)) + (set_local $usecs (i32.load (i32.add $tv (get_global $TV_USEC_OFFSET)))) + (set_local $msecs (i32.add (i32.mul_u $secs 1000) + (i32.div_u $usecs 1000))) + ($free $tv) + $msecs + ) +) From 456d0a21beb08af77a2c0310c39c95baf17a4684 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sun, 11 Nov 2018 15:07:27 -0600 Subject: [PATCH 0715/2308] wasm: improve debug output using padding. --- wasm/debug.wam | 34 +++++------- wasm/reader.wam | 3 +- wasm/stepA_mal.wam | 1 + wasm/util.wam | 125 +++++++++++++++++++++++++++++++++++++-------- 4 files changed, 119 insertions(+), 44 deletions(-) diff --git a/wasm/debug.wam b/wasm/debug.wam index 4ad2231482..8ec023d9e6 100644 --- a/wasm/debug.wam +++ b/wasm/debug.wam @@ -1,5 +1,9 @@ (module $debug + (func $checkpoint_user_memory + (set_global $mem_user_start (get_global $mem_unused_start)) + ) + (func $CHECK_FREE_LIST (result i32) (local $first i32 $count i32) (set_local $first (i32.add @@ -90,36 +94,26 @@ (set_local $size ($MalVal_size $mv)) (set_local $val0 ($MalVal_val $idx 0)) - ;;; printf(" %3d: type: %2d", idx, type) - ($printf_2 " %d: type: %d" $idx $type) + ($printf_2 "%4d: type %2d" $idx $type) (if (i32.eq $type 15) - (then - ;;; printf(", size: %2d", size) - ($printf_1 ", size: %d" $size)) - (else - ;;; printf(", refs: %2d", (mv->refcnt_type - type)>>5) - ($printf_1 ", refs: %d" ($REFS $mv)))) + (then ($printf_1 ", size %2d" $size)) + (else ($printf_1 ", refs %2d" ($REFS $mv)))) - ;;; printf(", [ %3d | %3d", mv->refcnt_type, val0) (if (OR (i32.eq $type (get_global $STRING_T)) (i32.eq $type (get_global $SYMBOL_T))) ;; for strings/symbolx pointers, print hex values - (then ($printf_2 ", [ 0x%x | 0x%x" ($MalVal_refcnt_type $idx) $val0)) - (else ($printf_2 ", [ %d | %d" ($MalVal_refcnt_type $idx) $val0))) + (then ($printf_2 " [%4d|%3ds" ($MalVal_refcnt_type $idx) $val0)) + (else ($printf_2 " [%4d|%4d" ($MalVal_refcnt_type $idx) $val0))) (if (i32.eq $size 2) (then - ($print " | --- | --- ]")) + ($print "|----|----]")) (else - ;;; printf(" | %3d", mv->val[1]) - ($printf_1 " | %d" ($MalVal_val $idx 1)) + ($printf_1 "|%4d" ($MalVal_val $idx 1)) (if (i32.eq $size 3) - (then - ($print " | --- ]")) - (else - ;;; printf(" | %3d ]", mv->val[2]) - ($printf_1 " | %d ]" ($MalVal_val $idx 2)))))) + (then ($print "|----]")) + (else ($printf_1 "|%4d]" ($MalVal_val $idx 2)))))) ;;; printf(" >> ") ($print " >> ") @@ -157,7 +151,7 @@ ($print "()")) (else ;;; printf("(... %d ...), next: %d\n", mv->val[1], mv->val[0]) - ($printf_2 "(... 0x%x ...), next: 0x%x" + ($printf_2 "(... %d ...), next: %d" ($MalVal_val $idx 1) ($MalVal_val $idx 0)))) (br $done)) diff --git a/wasm/reader.wam b/wasm/reader.wam index 6d728d96f3..05890c7051 100644 --- a/wasm/reader.wam +++ b/wasm/reader.wam @@ -233,13 +233,12 @@ ) (func $read_form (param $str i32) (result i32) - ;;($STRING (get_global $STRING_T) $str) (local $tok i32 $c0 i32 $c1 i32 $res i32 $slen i32) (if (get_global $error_type) (return 0)) (set_local $tok ($read_token $str)) -;; ($debug ">>> read_form 1:" $tok) + ;;($printf_1 ">>> read_form 1: %s\n" $tok) ;;; c0 = token[0] (set_local $c0 (i32.load8_u $tok)) (set_local $c1 (i32.load8_u (i32.add $tok 1))) diff --git a/wasm/stepA_mal.wam b/wasm/stepA_mal.wam index 487cf28b27..4d0788fd54 100644 --- a/wasm/stepA_mal.wam +++ b/wasm/stepA_mal.wam @@ -542,6 +542,7 @@ ($add_core_ns $repl_env) (drop ($ENV_SET_S $repl_env "eval" ($FUNCTION 0))) + ($checkpoint_user_memory) ;; core.mal: defined using the language itself ($RELEASE ($RE "(def! *host-language* \"WebAssembly\")" $repl_env)) diff --git a/wasm/util.wam b/wasm/util.wam index 926d26765d..f0238101df 100644 --- a/wasm/util.wam +++ b/wasm/util.wam @@ -80,21 +80,65 @@ (i32.store8_u $str (i32.add $n $ch)) ) - ;; TODO: switch to snprint* (add buffer len) - (func $_sprintnum (param $str i32 $num i32 $base i32) (result i32) - (if (AND (i32.eq $base 10) (i32.lt_s $num 0)) + ;; TODO: add max buf length (i.e. snprintnum) + (func $_sprintnum (param $buf i32) (param $val i32) (param $radix i32) + (param $pad_cnt i32) (param $pad_char i32) (result i32) + (local $pbuf i32 $i i32 $j i32 $k i32 $len i32 $neg i32 $digit i32) + (set_local $pbuf $buf) + (set_local $neg 0) + + (if (AND (i32.lt_s $val 0) (i32.eq $radix 10)) (then - ;; Print '-' if negative - (i32.store8_u $str (CHR "-")) - (set_local $str (i32.add $str 1)) - ;; Reverse the sign - (set_local $num (i32.sub_s 0 $num)))) - (if (i32.gt_u (i32.div_u $num $base) 0) - (set_local - $str - ($_sprintnum $str (i32.div_u $num $base) $base))) - ($_sprintdigit $str $num $base) - (i32.add 1 $str) + (set_local $neg 1) + (set_local $val (i32.sub_s 0 $val)))) + + ;; Calculate smallest to most significant digit + (loop $loop + (set_local $digit (i32.rem_u $val $radix)) + (i32.store8_u $pbuf (if i32 (i32.lt_s $digit 10) + (i32.add (CHR "0") $digit) + (i32.sub_s (i32.add (CHR "A") $digit) 10))) + (set_local $pbuf (i32.add $pbuf 1)) + (set_local $val (i32.div_s $val $radix)) + (if (i32.gt_s $val 0) (br $loop)) + ) + + (set_local $i (i32.sub_s $pbuf $buf)) + (block $done + (loop $loop + (if (i32.ge_s $i $pad_cnt) (br $done)) + (i32.store8_u $pbuf $pad_char) + (set_local $pbuf (i32.add $pbuf 1)) + (set_local $i (i32.add $i 1)) + (br $loop) + ) + ) + + (if $neg + (then + (i32.store8_u $pbuf (CHR "-")) + (set_local $pbuf (i32.add $pbuf 1)))) + + (i32.store8_u $pbuf (CHR "\x00")) + + ;; now reverse it + (set_local $len (i32.sub_s $pbuf $buf)) + (set_local $i 0) + (block $done + (loop $loop + (if (i32.ge_s $i (i32.div_s $len 2)) + (br $done)) + + (set_local $j (i32.load8_u (i32.add $buf $i))) + (set_local $k (i32.add $buf (i32.sub_s (i32.sub_s $len $i) 1))) + (i32.store8_u (i32.add $buf $i) (i32.load8_u $k)) + (i32.store8_u $k $j) + (set_local $i (i32.add $i 1)) + (br $loop) + ) + ) + + (i32.add $buf $len) ) ;; TODO: switch to snprint* (add buffer len) @@ -107,17 +151,15 @@ (param $v0 i32) (param $v1 i32) (param $v2 i32) (param $v3 i32) (param $v4 i32) (param $v5 i32) (result i32) - (local $ch i32) - (local $pstr i32) - (local $v i32) - (local $vidx i32) - (local $len i32) + (local $ch i32 $pstr i32 $v i32 $vidx i32 $len i32) + (local $pad_cnt i32 $pad_char i32) (set_local $pstr $str) (set_local $vidx 0) (block $done (loop $loop (block $after_v + ;; set $v to the current parameter (block (block (block (block (block (block (br_table 0 1 2 3 4 5 0 $vidx)) (; 0 ;) (set_local $v $v0) (br $after_v)) @@ -144,25 +186,64 @@ ;;; ch=*(fmt++) (set_local $ch (i32.load8_u $fmt)) (set_local $fmt (i32.add 1 $fmt)) + (if (i32.eqz $ch) (br $done)) + + (set_local $pad_cnt 0) + (set_local $pad_char (CHR " ")) + (if (AND (i32.ge_s $ch (CHR "0")) (i32.le_s $ch (CHR "9"))) + (then + ;; padding requested + (if (i32.eq $ch (CHR "0")) + (then + ;; zero padding requested + (set_local $pad_char (CHR "0")) + ;;; ch=*(fmt++) + (set_local $ch (i32.load8_u $fmt)) + (set_local $fmt (i32.add 1 $fmt)) + (if (i32.eqz $ch) (br $done)))) + (loop $loop + (set_local $pad_cnt (i32.mul_s $pad_cnt 10)) + (set_local $pad_cnt (i32.add $pad_cnt + (i32.sub_s $ch (CHR "0")))) + (set_local $ch (i32.load8_u $fmt)) + (set_local $fmt (i32.add 1 $fmt)) + (if (AND (i32.ge_s $ch (CHR "0")) (i32.le_s $ch (CHR "9"))) + (br $loop)) + ))) (if (i32.eq (CHR "d") $ch) (then - (set_local $pstr ($_sprintnum $pstr $v 10))) + (set_local $pstr ($_sprintnum $pstr $v 10 $pad_cnt $pad_char))) (else (if (i32.eq (CHR "x") $ch) (then - (set_local $pstr ($_sprintnum $pstr $v 16))) + (set_local $pstr ($_sprintnum $pstr $v 16 $pad_cnt $pad_char))) (else (if (i32.eq (CHR "s") $ch) (then (set_local $len ($strlen $v)) + (block $done + (loop $loop + (if (i32.le_s $pad_cnt $len) + (br $done)) + (i32.store8_u $pstr (CHR " ")) + (set_local $pstr (i32.add $pstr 1)) + (set_local $pad_cnt (i32.sub_s $pad_cnt 1)) + (br $loop) + ) + ) ($memmove $pstr $v $len) (set_local $pstr (i32.add $pstr $len))) (else (if (i32.eq (CHR "c") $ch) (then (i32.store8_u $pstr $v) (set_local $pstr (i32.add $pstr 1))) + (else (if (i32.eq (CHR "%") $ch) + (then + (i32.store8_u $pstr (CHR "%")) + (set_local $pstr (i32.add $pstr 1)) + (br $loop)) ;; don't increase vidx (else ($printf_1 "Illegal format character: '%c'\n" $ch) - ($exit 3))))))))) + ($exit 3))))))))))) (set_local $vidx (i32.add 1 $vidx)) (br $loop) From 50eea9ad9caa6aa31c7a39f98a9f712133923e07 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sun, 11 Nov 2018 15:30:42 -0600 Subject: [PATCH 0716/2308] wasm: String refactor. Release macro memory Also split platform out. --- wasm/Makefile | 4 +- wasm/core.wam | 21 ++--- wasm/debug.wam | 38 ++++++++- wasm/mem.wam | 149 +++++++++++++++++++++++----------- wasm/platform_libc.wam | 98 ++++++++++++++++++++++ wasm/platform_os.wam | 54 ++++++++++++ wasm/printer.wam | 18 ++-- wasm/{util.wam => printf.wam} | 140 ++++++-------------------------- wasm/step0_repl.wam | 16 ++-- wasm/step1_read_print.wam | 30 ++++--- wasm/step2_eval.wam | 21 +++-- wasm/step3_env.wam | 28 +++---- wasm/step4_if_fn_do.wam | 25 +++--- wasm/step5_tco.wam | 32 ++++---- wasm/step6_file.wam | 23 +++--- wasm/step7_quote.wam | 23 +++--- wasm/step8_macros.wam | 74 +++++++++-------- wasm/step9_try.wam | 76 +++++++++-------- wasm/stepA_mal.wam | 73 +++++++++-------- wasm/string.wam | 4 +- wasm/types.wam | 61 +++++++++++++- 21 files changed, 625 insertions(+), 383 deletions(-) create mode 100644 wasm/platform_libc.wam create mode 100644 wasm/platform_os.wam rename wasm/{util.wam => printf.wam} (57%) diff --git a/wasm/Makefile b/wasm/Makefile index e44953e0f2..c4bb8544fc 100644 --- a/wasm/Makefile +++ b/wasm/Makefile @@ -1,4 +1,6 @@ -STEP0_DEPS = string.wam util.wam +MODE ?= libc + +STEP0_DEPS = platform_$(MODE).wam string.wam printf.wam STEP1_DEPS = $(STEP0_DEPS) types.wam mem.wam debug.wam reader.wam printer.wam STEP3_DEPS = $(STEP1_DEPS) env.wam STEP4_DEPS = $(STEP3_DEPS) core.wam diff --git a/wasm/core.wam b/wasm/core.wam index ed73dc8138..1709018c8c 100644 --- a/wasm/core.wam +++ b/wasm/core.wam @@ -86,8 +86,8 @@ (if i32 (i32.eq (i32.load8_u $str) (CHR "\x7f")) (then ($INC_REF ($MEM_VAL1_ptr $args))) (else - (drop ($sprintf_1 (get_global $util_buf) "\x7f%s" $str)) - ($STRING (get_global $STRING_T) (get_global $util_buf)))) + (drop ($sprintf_1 (get_global $printf_buf) "\x7f%s" $str)) + ($STRING (get_global $STRING_T) (get_global $printf_buf)))) ) (func $keyword_Q (param $args i32) (result i32) @@ -134,10 +134,10 @@ (func $core_readline (param $args i32) (result i32) (local $line i32 $mv i32) - (set_local $line ($readline ($to_String ($MEM_VAL1_ptr $args)))) - (if (i32.eqz $line) (return ($INC_REF (get_global $NIL)))) + (set_local $line (STATIC_ARRAY 201)) + (if (i32.eqz ($readline ($to_String ($MEM_VAL1_ptr $args)) $line)) + (return ($INC_REF (get_global $NIL)))) (set_local $mv ($STRING (get_global $STRING_T) $line)) - ($free $line) $mv ) @@ -145,14 +145,15 @@ ($read_str ($to_String ($MEM_VAL1_ptr $args)))) (func $slurp (param $args i32) (result i32) - (local $content i32 $mv i32) - (set_local $content ($read_file ($to_String ($MEM_VAL1_ptr $args)))) - (if (i32.le_s $content 0) + (local $mv i32 $size i32) + (set_local $mv ($STRING_INIT (get_global $STRING_T))) + (set_local $size ($read_file ($to_String ($MEM_VAL1_ptr $args)) + ($to_String $mv))) + (if (i32.eqz $size) (then ($THROW_STR_1 "failed to read file '%s'" ($to_String ($MEM_VAL1_ptr $args))) (return ($INC_REF (get_global $NIL))))) - (set_local $mv ($STRING (get_global $STRING_T) $content)) - ($free $content) + (set_local $mv ($STRING_FINALIZE $mv $size)) $mv ) diff --git a/wasm/debug.wam b/wasm/debug.wam index 8ec023d9e6..b998b9614b 100644 --- a/wasm/debug.wam +++ b/wasm/debug.wam @@ -2,6 +2,7 @@ (func $checkpoint_user_memory (set_global $mem_user_start (get_global $mem_unused_start)) + (set_global $string_mem_user_start (get_global $string_mem_next)) ) (func $CHECK_FREE_LIST (result i32) @@ -211,10 +212,42 @@ (i32.add $size $idx) ) + (func $PR_STRINGS (param $start i32) + (local $ms i32 $idx i32) + ($printf_2 "String - showing %d -> %d:\n" + $start (i32.sub_s (get_global $string_mem_next) + (get_global $string_mem))) + (if (i32.le_s (i32.sub_s (get_global $string_mem_next) + (get_global $string_mem)) + $start) + (then ($print " ---\n")) + (else + (set_local $ms (get_global $string_mem)) + (block $done + (loop $loop + (if (i32.ge_u $ms (get_global $string_mem_next)) + (br $done)) + (set_local $idx (i32.sub_u $ms (get_global $string_mem))) + (if (i32.ge_s $idx $start) + ($printf_4 "%4d: refs %2d, size %2d >> '%s'\n" + $idx + (i32.load16_u $ms) + (i32.load16_u (i32.add $ms 2)) + (i32.add $ms 4))) + + (set_local $ms (i32.add $ms (i32.load16_u (i32.add $ms 2)))) + (br $loop) + ) + ))) + ) + (func $PR_MEMORY (param $start i32 $end i32) - (local $idx i32) + (local $string_start i32 $idx i32) (if (i32.lt_s $start 0) - (set_local $start (get_global $mem_user_start))) + (then + (set_local $start (get_global $mem_user_start)) + (set_local $string_start (i32.sub_s (get_global $string_mem_user_start) + (get_global $string_mem))))) (if (i32.lt_s $end 0) (set_local $end (get_global $mem_unused_start))) ;;; printf("Values - (mem) showing %d -> %d", start, end) @@ -241,6 +274,7 @@ (br $loopvals) ) ))) + ($PR_STRINGS $string_start) ($PR_MEMORY_SUMMARY_SMALL) ) diff --git a/wasm/mem.wam b/wasm/mem.wam index e67824bd67..8a416ee2bd 100644 --- a/wasm/mem.wam +++ b/wasm/mem.wam @@ -10,9 +10,9 @@ (global $mem_free_list (mut i32) 0) (global $mem_user_start (mut i32) 0) -;; (global $string_mem (mut i32) 0) -;; (global $string_mem_next (mut i32) 0) -;; (global $string_mem_user_start (mut i32) 0) + (global $string_mem (mut i32) 0) + (global $string_mem_next (mut i32) 0) + (global $string_mem_user_start (mut i32) 0) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; General type storage/pointer functions @@ -117,7 +117,7 @@ ;; ($print ">>> init_memory\n") - ($init_sprintf_mem) + ($init_printf_mem) ;; error_str string buffer (set_global $error_str (STATIC_ARRAY 100)) @@ -137,9 +137,12 @@ (set_global $mem_unused_start 0) (set_global $mem_free_list 0) -;; (set_global $string_mem (i32.add (get_global $heap_start) -;; (get_global $MEM_SIZE))) -;; (set_global $string_mem_next (get_global $string_mem)) + (set_global $string_mem (i32.add (get_global $heap_start) + (get_global $MEM_SIZE))) + (set_global $string_mem_next (get_global $string_mem)) + + (set_global $mem_user_start (get_global $mem_unused_start)) + (set_global $string_mem_user_start (get_global $string_mem_next)) ;; Empty values (set_global $NIL @@ -180,10 +183,8 @@ ;; ALLOC_UNUSED ;;; if (res + size > MEM_SIZE) (if (i32.gt_u (i32.add $res $size) (get_global $MEM_SIZE)) - (then - ;; Out of memory, exit - ($print "Out of mal memory!\n") - ($exit 1))) + ;; Out of memory, exit + ($fatal 7 "Out of mal memory!\n")) ;;; if (mem_unused_start += size) (set_global $mem_unused_start (i32.add (get_global $mem_unused_start) $size)) @@ -282,18 +283,16 @@ ;;; printf(">>> RELEASE idx: %d, type: %d, size: %d\n", idx, type, size) (if (i32.eq 0 $mv) - (then - ($print "RELEASE of NULL!\n") - ($exit 1))) + ($fatal 7 "RELEASE of NULL!\n")) (if (i32.eq (get_global $FREE_T) $type) (then ($printf_2 "RELEASE of already free mv: 0x%x, idx: 0x%x\n" $mv $idx) - ($exit 1))) + ($fatal 1 ""))) (if (i32.lt_u ($MalVal_refcnt_type $idx) 15) (then ($printf_2 "RELEASE of unowned mv: 0x%x, idx: 0x%x\n" $mv $idx) - ($exit 1))) + ($fatal 1 ""))) ;; decrease reference count by one (i32.store ($MalVal_ptr $idx) @@ -305,7 +304,7 @@ (if (i32.lt_u ($MalVal_refcnt_type $idx) 32) (then ($printf_2 "RELEASE of unowned mv: 0x%x, idx: 0x%x\n" $mv $idx) - ($exit 1))) + ($fatal 1 ""))) (return))) ;; our reference count is not 0, so don't release @@ -319,7 +318,7 @@ (br $done)) ;; string, kw, symbol ;; release string, then FREE reference - ($RELEASE_STRING $mv) + ($RELEASE_STRING (i32.add (get_global $string_mem) ($VAL0 $mv))) (br $done)) ;; list, vector (if (i32.ne ($MalVal_val $idx 0) 0) @@ -372,42 +371,98 @@ (if (i32.eq $size 4) (i32.store ($MalVal_val_ptr $idx 2) 0)) ) - ;; Allocate a string as follows: - ;; refcnt (i32 set to 1), string data, NULL byte - (func $STRING_DUPE (param $str i32) (result i32) - (local $len i32 $cur i32 $new i32 $idx i32) - - ;; Calculate length of string needed - (set_local $len ($strlen $str)) + ;; find string in string memory or 0 if not found + (func $FIND_STRING (param $str i32) (result i32) + (local $ms i32) + (set_local $ms (get_global $string_mem)) + (block $done + (loop $loop + (br_if $done (i32.ge_s $ms (get_global $string_mem_next))) + (if (i32.eqz ($strcmp $str (i32.add $ms 4))) + (return $ms)) - ;; leading i32 refcnt + trailing NULL - (set_local $new ($malloc (i32.add 5 $len))) + (set_local $ms (i32.add $ms (i32.load16_u (i32.add $ms 2)))) + (br $loop) + ) + ) + 0 + ) - ;; set initial refcnt to 1 - (i32.store $new 1) - ;; skip refcnt - (set_local $cur (i32.add $new 4)) - ;; Set NULL terminator - (i32.store8_u (i32.add $cur $len) 0) + ;; str is a NULL terminated string + ;; size is number of characters in the string not including the + ;; trailing NULL + (func $ALLOC_STRING (param $str i32 $size i32 $intern i32) (result i32) + (local $ms i32) - ;; Copy the characters - ($memmove $cur $str $len) - $new + ;; search for matching string in string_mem + (if $intern + (then + (set_local $ms ($FIND_STRING $str)) + (if $ms + (then + ;;; ms->refcnt += 1 + (i32.store16_u $ms (i32.add (i32.load16_u $ms) 1)) + (return $ms))))) + + ;; no existing matching string so create a new one + (set_local $ms (get_global $string_mem_next)) + (i32.store16_u $ms 1) + ;;; ms->size = sizeof(MalString)+size+1 + (i32.store16_u offset=2 $ms (i32.add (i32.add 4 $size) 1)) + ($memmove (i32.add $ms 4) $str (i32.add $size 1)) + ;;; string_mem_next = (void *)ms + ms->size + (set_global $string_mem_next + ;;(i32.add $ms (i32.load16_u (i32.add $ms 2)))) + (i32.add $ms (i32.load16_u offset=2 $ms))) + +;;($printf_2 "ALLOC_STRING 6 ms 0x%x, refs: %d\n" $ms (i32.load16_u $ms)) + $ms ) - ;; Duplicate regular character array string into a Mal string and - ;; return the MalVal pointer - (func $STRING (param $type i32 $str i32) (result i32) - ($ALLOC_SCALAR $type ($STRING_DUPE $str)) - ) + (func $RELEASE_STRING (param $ms i32) + (local $size i32 $next i32 $ms_idx i32 $idx i32 $type i32 $mv i32) - (func $RELEASE_STRING (param $mv i32) - (local $str i32) - (set_local $str ($MalVal_val ($IDX $mv) 0)) + (if (i32.le_s (i32.load16_u $ms) 0) + (then + ($printf_2 "Release of already free string: %d (0x%x)\n" + (i32.sub_s $ms (get_global $string_mem)) $ms) + ($fatal 1 ""))) - ;; DEBUG -;; ($printf_1 "RELEASE_STRING - calling free on: %d" $str) + ;;; size = ms->size + (set_local $size (i32.load16_u (i32.add $ms 2))) + ;;; *next = (void *)ms + size + (set_local $next (i32.add $ms $size)) + + ;;; ms->refcnt -= 1 + (i32.store16_u $ms (i32.sub_u (i32.load16_u $ms) 1)) - ($free $str) + (if (i32.eqz (i32.load16_u $ms)) + (then + (if (i32.gt_s (get_global $string_mem_next) $next) + (then + ;; If no more references to this string then free it up by + ;; shifting up every string afterwards to fill the gap + ;; (splice). + ($memmove $ms $next (i32.sub_s (get_global $string_mem_next) + $next)) + + ;; Scan the mem values for string types after the freed + ;; string and shift their indexes by size + (set_local $ms_idx (i32.sub_s $ms (get_global $string_mem))) + (set_local $idx ($IDX (get_global $EMPTY_HASHMAP))) + (loop $loop + (set_local $mv ($MalVal_ptr $idx)) + (set_local $type ($TYPE $mv)) + (if (AND (i32.gt_s ($VAL0 $mv) $ms_idx) + (OR (i32.eq $type (get_global $STRING_T)) + (i32.eq $type (get_global $SYMBOL_T)))) + (i32.store ($VAL0_ptr $mv) (i32.sub_s ($VAL0 $mv) $size))) + (set_local $idx (i32.add $idx ($MalVal_size $mv))) + + (br_if $loop (i32.lt_s $idx (get_global $mem_unused_start))) + ))) + + (set_global $string_mem_next + (i32.sub_s (get_global $string_mem_next) $size)))) ) ) diff --git a/wasm/platform_libc.wam b/wasm/platform_libc.wam new file mode 100644 index 0000000000..c561d64952 --- /dev/null +++ b/wasm/platform_libc.wam @@ -0,0 +1,98 @@ +(module $platform_libc + + (import "env" "exit" (func $lib_exit (param i32))) + + (import "env" "stdout" (global $lib_stdout i32)) + (import "env" "fputs" (func $lib_fputs (param i32 i32) (result i32))) + + (import "env" "free" (func $lib_free (param i32))) + (import "env" "readline" (func $lib_readline (param i32) (result i32))) + (import "env" "add_history" (func $lib_add_history (param i32))) + + ;; read_file defintions / FFI information + (global $STAT_SIZE i32 88) + (global $STAT_ST_SIZE_OFFSET i32 44) + (global $STAT_VER_LINUX i32 3) + (global $O_RDONLY i32 0) + (import "env" "open" (func $lib_open (param i32 i32 i32) (result i32))) + (import "env" "read" (func $lib_read (param i32 i32 i32) (result i32))) + (import "env" "__fxstat" (func $lib___fxstat (param i32 i32 i32) (result i32))) + (global $TIMEVAL_SIZE i32 8) + (global $TV_SEC_OFFSET i32 0) + (global $TV_USEC_OFFSET i32 4) + (import "env" "gettimeofday" (func $lib_gettimeofday (param i32 i32) (result i32))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $fatal (param $code i32 $msg i32) + ($print $msg) + ($lib_exit $code) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $print (param $addr i32) + (drop ($lib_fputs $addr (get_global $lib_stdout)))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $readline (param $prompt i32 $buf i32) (result i32) + (local $line i32 $len i32) + (set_local $len 0) + + (set_local $line ($lib_readline $prompt)) + (if $line + (then + ($lib_add_history $line) + (set_local $len ($strlen $line)) + ($memmove $buf $line $len) + ($lib_free $line))) + (i32.store8_u (i32.add $buf $len) (CHR "\x00")) + (return (if i32 $line 1 0)) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ;; Returns malloc'd string. Must be free by caller + (func $read_file (param $path i32 $buf i32) (result i32) + (local $fst i32 $fd i32 $st_size i32 $sz i32) + (set_local $fst (STATIC_ARRAY 100)) ;; at least STAT_SIZE + + (set_local $fd ($lib_open $path (get_global $O_RDONLY) 0)) + (if (i32.lt_s $fd 0) + (then + ($printf_1 "ERROR: slurp failed to open '%s'\n" $path) + (return 0))) + (if (i32.lt_s ($lib___fxstat (get_global $STAT_VER_LINUX) $fd $fst) 0) + (then + ($printf_1 "ERROR: slurp failed to stat '%s'\n" $path) + (return 0))) + (set_local $st_size (i32.load + (i32.add $fst (get_global $STAT_ST_SIZE_OFFSET)))) + (set_local $sz ($lib_read $fd $buf $st_size)) + (if (i32.ne $sz $st_size) + (then + ($printf_1 "ERROR: slurp failed to stat '%s'\n" $path) + (return 0))) + ;; Add null to string + (i32.store8_u (i32.add $buf $st_size) 0) + (i32.add 1 $st_size) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + + (func $get_time_ms (result i32) + (local $tv i32 $secs i32 $usecs i32 $msecs i32) + (set_local $tv (STATIC_ARRAY 10)) ;; at least TIMEVAL_SIZE + (drop ($lib_gettimeofday $tv 0)) + (set_local $secs (i32.load (i32.add $tv (get_global $TV_SEC_OFFSET)))) + ;; subtract 30 years to make sure secs is positive and can be + ;; multiplied by 1000 + (set_local $secs (i32.sub_s $secs 0x38640900)) + (set_local $usecs (i32.load (i32.add $tv (get_global $TV_USEC_OFFSET)))) + (set_local $msecs (i32.add (i32.mul_u $secs 1000) + (i32.div_u $usecs 1000))) + $msecs + ) +) diff --git a/wasm/platform_os.wam b/wasm/platform_os.wam new file mode 100644 index 0000000000..f7e0ec029a --- /dev/null +++ b/wasm/platform_os.wam @@ -0,0 +1,54 @@ +(module $platform_os + + (import "env" "exit" (func $lib_exit (param i32))) + + (import "env" "stdout" (global $lib_stdout i32)) + (import "env" "fputs" (func $lib_fputs (param i32 i32) (result i32))) + + (import "env" "readline" (func $lib_readline (param i32 i32 i32) (result i32))) + (import "env" "add_history" (func $lib_add_history (param i32))) + + (import "env" "read_file" (func $lib_read_file (param i32 i32) (result i32))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $fatal (param $code i32 $msg i32) + ($print $msg) + ($lib_exit $code) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $print (param $addr i32) + (drop ($lib_fputs $addr (get_global $lib_stdout)))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $readline (param $prompt i32 $buf i32) (result i32) + (local $res i32) + + ;; TODO: don't hardcode count to 200 + (set_local $res ($lib_readline $prompt $buf 200)) + (if $res + ($lib_add_history $buf)) + $res + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $read_file (param $path i32 $buf i32) (result i32) + (local $size i32) + (set_local $size ($lib_read_file $path $buf)) + ;; Add null to string + (i32.store8_u (i32.add $buf $size) 0) + (i32.add $size 1) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + + (func $get_time_ms (result i32) + 0 + ) + +) diff --git a/wasm/printer.wam b/wasm/printer.wam index 95afdc482d..34cba901e5 100644 --- a/wasm/printer.wam +++ b/wasm/printer.wam @@ -147,26 +147,28 @@ (func $pr_str_internal (param $seq i32) (param $mv i32) (param $print_readably i32) (param $sep i32) (result i32) - (local $res i32) - (set_local $res (get_global $printer_buf)) - (i32.store8_u $res 0) + (local $res i32 $res_str i32) + (set_local $res ($STRING_INIT (get_global $STRING_T))) + (set_local $res_str ($to_String $res)) (if $seq (then (block $done (loop $loop - (if (i32.eqz ($VAL0 $mv)) (br $done)) - (set_local $res ($pr_str_val $res ($MEM_VAL1_ptr $mv) $print_readably)) + (br_if $done (i32.eqz ($VAL0 $mv))) + (set_local $res_str ($pr_str_val $res_str ($MEM_VAL1_ptr $mv) $print_readably)) (set_local $mv ($MEM_VAL0_ptr $mv)) (if (i32.ne ($VAL0 $mv) 0) - (set_local $res ($sprintf_1 $res "%s" $sep))) + (set_local $res_str ($sprintf_1 $res_str "%s" $sep))) (br $loop) ) )) (else - (set_local $res ($pr_str_val $res $mv $print_readably)))) + (set_local $res_str ($pr_str_val $res_str $mv $print_readably)))) - ($STRING (get_global $STRING_T) (get_global $printer_buf)) + (set_local $res ($STRING_FINALIZE $res (i32.sub_s $res_str ($to_String $res)))) + + $res ) (func $pr_str (param $mv i32 $print_readably i32) (result i32) diff --git a/wasm/util.wam b/wasm/printf.wam similarity index 57% rename from wasm/util.wam rename to wasm/printf.wam index f0238101df..68ca24ca43 100644 --- a/wasm/util.wam +++ b/wasm/printf.wam @@ -1,74 +1,49 @@ -(module $util - (import "env" "malloc" (func $malloc (param i32) (result i32))) - (import "env" "free" (func $free (param i32))) - (import "env" "exit" (func $exit (param i32))) +(module $printf - (import "env" "stdout" (global $stdout i32)) - (import "env" "fputs" (func $fputs (param i32 i32) (result i32))) - ;;(import "env" "readline" (func $readline (param i32) (result i32))) - (import "libedit.so" "readline" (func $readline (param i32) (result i32))) - ;;(import "libreadline.so" "readline" (func $readline (param i32) (result i32))) + (global $printf_buf (mut i32) 0) - (global $util_buf (mut i32) 0) - - ;; read_file defintions / FFI information - (global $STAT_SIZE i32 88) - (global $STAT_ST_SIZE_OFFSET i32 44) - (global $STAT_VER_LINUX i32 3) - (global $O_RDONLY i32 0) - (import "env" "open" (func $open (param i32 i32 i32) (result i32))) - (import "env" "read" (func $read (param i32 i32 i32) (result i32))) - (import "env" "__fxstat" (func $__fxstat (param i32 i32 i32) (result i32))) - (global $TIMEVAL_SIZE i32 8) - (global $TV_SEC_OFFSET i32 0) - (global $TV_USEC_OFFSET i32 4) - (import "env" "gettimeofday" (func $gettimeofday (param i32 i32) (result i32))) - - (func $init_sprintf_mem + (func $init_printf_mem ;; sprintf static buffer - (set_global $util_buf (STATIC_ARRAY 256)) - ) + (set_global $printf_buf (STATIC_ARRAY 256)) + ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - (func $print (param $addr i32) - (drop ($fputs $addr (get_global $stdout)))) - (func $printf_1 (param $fmt i32) (param $v0 i32) - (drop ($sprintf_6 (get_global $util_buf) $fmt $v0 0 0 0 0 0)) - ($print (get_global $util_buf)) + (drop ($sprintf_6 (get_global $printf_buf) $fmt $v0 0 0 0 0 0)) + ($print (get_global $printf_buf)) ) (func $printf_2 (param $fmt i32 $v0 i32 $v1 i32) - (drop ($sprintf_6 (get_global $util_buf) $fmt $v0 $v1 0 0 0 0)) - ($print (get_global $util_buf)) + (drop ($sprintf_6 (get_global $printf_buf) $fmt $v0 $v1 0 0 0 0)) + ($print (get_global $printf_buf)) ) (func $printf_3 (param $fmt i32) (param $v0 i32) (param $v1 i32) (param $v2 i32) - (drop ($sprintf_6 (get_global $util_buf) $fmt $v0 $v1 $v2 0 0 0)) - ($print (get_global $util_buf)) + (drop ($sprintf_6 (get_global $printf_buf) $fmt $v0 $v1 $v2 0 0 0)) + ($print (get_global $printf_buf)) ) (func $printf_4 (param $fmt i32) (param $v0 i32) (param $v1 i32) (param $v2 i32) (param $v3 i32) - (drop ($sprintf_6 (get_global $util_buf) $fmt $v0 $v1 $v2 $v3 0 0)) - ($print (get_global $util_buf)) + (drop ($sprintf_6 (get_global $printf_buf) $fmt $v0 $v1 $v2 $v3 0 0)) + ($print (get_global $printf_buf)) ) (func $printf_5 (param $fmt i32) (param $v0 i32) (param $v1 i32) (param $v2 i32) (param $v3 i32) (param $v4 i32) - (drop ($sprintf_6 (get_global $util_buf) $fmt $v0 $v1 $v2 $v3 $v4 0)) - ($print (get_global $util_buf)) + (drop ($sprintf_6 (get_global $printf_buf) $fmt $v0 $v1 $v2 $v3 $v4 0)) + ($print (get_global $printf_buf)) ) (func $printf_6 (param $fmt i32) (param $v0 i32) (param $v1 i32) (param $v2 i32) (param $v3 i32) (param $v4 i32) (param $v5 i32) - (drop ($sprintf_6 (get_global $util_buf) $fmt $v0 $v1 $v2 $v3 $v4 $v5)) - ($print (get_global $util_buf)) + (drop ($sprintf_6 (get_global $printf_buf) $fmt $v0 $v1 $v2 $v3 $v4 $v5)) + ($print (get_global $printf_buf)) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -95,18 +70,18 @@ ;; Calculate smallest to most significant digit (loop $loop (set_local $digit (i32.rem_u $val $radix)) - (i32.store8_u $pbuf (if i32 (i32.lt_s $digit 10) + (i32.store8_u $pbuf (if i32 (i32.lt_u $digit 10) (i32.add (CHR "0") $digit) - (i32.sub_s (i32.add (CHR "A") $digit) 10))) + (i32.sub_u (i32.add (CHR "A") $digit) 10))) (set_local $pbuf (i32.add $pbuf 1)) - (set_local $val (i32.div_s $val $radix)) - (if (i32.gt_s $val 0) (br $loop)) + (set_local $val (i32.div_u $val $radix)) + (if (i32.gt_u $val 0) (br $loop)) ) - (set_local $i (i32.sub_s $pbuf $buf)) + (set_local $i (i32.sub_u $pbuf $buf)) (block $done (loop $loop - (if (i32.ge_s $i $pad_cnt) (br $done)) + (if (i32.ge_u $i $pad_cnt) (br $done)) (i32.store8_u $pbuf $pad_char) (set_local $pbuf (i32.add $pbuf 1)) (set_local $i (i32.add $i 1)) @@ -122,15 +97,15 @@ (i32.store8_u $pbuf (CHR "\x00")) ;; now reverse it - (set_local $len (i32.sub_s $pbuf $buf)) + (set_local $len (i32.sub_u $pbuf $buf)) (set_local $i 0) (block $done (loop $loop - (if (i32.ge_s $i (i32.div_s $len 2)) + (if (i32.ge_u $i (i32.div_u $len 2)) (br $done)) (set_local $j (i32.load8_u (i32.add $buf $i))) - (set_local $k (i32.add $buf (i32.sub_s (i32.sub_s $len $i) 1))) + (set_local $k (i32.add $buf (i32.sub_u (i32.sub_u $len $i) 1))) (i32.store8_u (i32.add $buf $i) (i32.load8_u $k)) (i32.store8_u $k $j) (set_local $i (i32.add $i 1)) @@ -243,7 +218,7 @@ (br $loop)) ;; don't increase vidx (else ($printf_1 "Illegal format character: '%c'\n" $ch) - ($exit 3))))))))))) + ($fatal 3 ""))))))))))) (set_local $vidx (i32.add 1 $vidx)) (br $loop) @@ -254,65 +229,4 @@ $pstr ) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - ;; Returns malloc'd string. Must be free by caller - (func $read_file (param $path i32) (result i32) - (local $fst i32 $fd i32 $str i32 $st_size i32 $sz i32) - (set_local $str 0) - (set_local $fst ($malloc (get_global $STAT_SIZE))) - (if (i32.le_s $fst 0) - (then - ($printf_1 "ERROR: malloc of %d bytes failed\n" - (get_global $STAT_SIZE)) - (return 0))) - - (block $free_fst - (set_local $fd ($open $path (get_global $O_RDONLY) 0)) - (if (i32.lt_s $fd 0) - (then - ($printf_1 "ERROR: slurp failed to open '%s'\n" $path) - (br $free_fst))) - (if (i32.lt_s ($__fxstat (get_global $STAT_VER_LINUX) $fd $fst) 0) - (then - ($printf_1 "ERROR: slurp failed to stat '%s'\n" $path) - (br $free_fst))) - (set_local $st_size (i32.load - (i32.add $fst (get_global $STAT_ST_SIZE_OFFSET)))) - (set_local $str ($malloc (i32.add 1 $st_size))) - (if (i32.le_s $str 0) - (then - ($printf_1 "ERROR: malloc of %d bytes failed\n" $st_size) - (br $free_fst))) - (set_local $sz ($read $fd $str $st_size)) - (if (i32.ne $sz $st_size) - (then - ($free $str) - (set_local $str 0) - ($printf_1 "ERROR: slurp failed to stat '%s'\n" $path) - (br $free_fst))) - ;; Add null to string - (i32.store8_u (i32.add $str $st_size) 0) - ) - ($free $fst) - $str - ) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - - (func $get_time_ms (result i32) - (local $tv i32 $secs i32 $usecs i32 $msecs i32) - (set_local $tv ($malloc (get_global $TIMEVAL_SIZE))) - (drop ($gettimeofday $tv 0)) - (set_local $secs (i32.load (i32.add $tv (get_global $TV_SEC_OFFSET)))) - ;; subtract 30 years to make sure secs is positive and can be - ;; multiplied by 1000 - (set_local $secs (i32.sub_s $secs 0x38640900)) - (set_local $usecs (i32.load (i32.add $tv (get_global $TV_USEC_OFFSET)))) - (set_local $msecs (i32.add (i32.mul_u $secs 1000) - (i32.div_u $usecs 1000))) - ($free $tv) - $msecs - ) ) diff --git a/wasm/step0_repl.wam b/wasm/step0_repl.wam index a1b4a07add..167c773335 100644 --- a/wasm/step0_repl.wam +++ b/wasm/step0_repl.wam @@ -22,15 +22,21 @@ (func $main (result i32) ;; Constant location/value definitions (local $line i32) + (set_local $line (STATIC_ARRAY 201)) + + ;; DEBUG + ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) ;; Start REPL (block $repl_done (loop $repl_loop - (set_local $line ($readline "user> ")) - (if (i32.eqz $line) (br $repl_done)) + (br_if $repl_done (i32.eqz ($readline "user> " $line))) + (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) + ;;($printf_1 "here1 %d\n", 7); ($printf_1 "%s\n" ($rep $line)) - ($free $line) - (br $repl_loop))) + (br $repl_loop) + ) + ) ($print "\n") 0 @@ -38,6 +44,6 @@ (export "_main" (func $main)) - (export "__post_instantiate" (func $init_sprintf_mem)) + (export "__post_instantiate" (func $init_printf_mem)) ) diff --git a/wasm/step1_read_print.wam b/wasm/step1_read_print.wam index a9e017b0b0..d9ef2b85ef 100644 --- a/wasm/step1_read_print.wam +++ b/wasm/step1_read_print.wam @@ -20,13 +20,14 @@ (local $mv1 i32 $mv2 i32 $ms i32) (block $rep_done (set_local $mv1 ($READ $line)) - (if (get_global $error_type) (br $rep_done)) + (br_if $rep_done (get_global $error_type)) (set_local $mv2 ($EVAL $mv1 $env)) - (if (get_global $error_type) (br $rep_done)) + (br_if $rep_done (get_global $error_type)) ;; ($PR_MEMORY -1 -1) (set_local $ms ($PRINT $mv2)) + ) ;; release memory from MAL_READ @@ -36,6 +37,7 @@ (func $main (result i32) (local $line i32 $res i32) + (set_local $line (STATIC_ARRAY 201)) ;; DEBUG ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) @@ -43,17 +45,18 @@ ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) ($printf_1 "mem: 0x%x\n" (get_global $mem)) ;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) - ;;($PR_MEMORY -1 -1) - ;; Start +;; ($PR_MEMORY_RAW +;; (get_global $mem) (i32.add (get_global $mem) +;; (i32.mul_u (get_global $mem_unused_start) 4))) + + ($PR_MEMORY -1 -1) + + ;; Start REPL (block $repl_done (loop $repl_loop - (set_local $line ($readline "user> ")) - (if (i32.eqz $line) (br $repl_done)) - (if (i32.eq (i32.load8_u $line) 0) - (then - ($free $line) - (br $repl_loop))) + (br_if $repl_done (i32.eqz ($readline "user> " $line))) + (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) (set_local $res ($REP $line 0)) (if (get_global $error_type) (then @@ -62,9 +65,10 @@ (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) -;; ($PR_MEMORY -1 -1) - ($free $line) - (br $repl_loop))) + ;;($PR_MEMORY_SUMMARY_SMALL) + (br $repl_loop) + ) + ) ($print "\n") ;;($PR_MEMORY -1 -1) diff --git a/wasm/step2_eval.wam b/wasm/step2_eval.wam index c8852852b2..656dca3bf7 100644 --- a/wasm/step2_eval.wam +++ b/wasm/step2_eval.wam @@ -112,8 +112,9 @@ (if (get_global $error_type) (return 0)) - (if (i32.ne $type (get_global $LIST_T)) - (return ($EVAL_AST $ast $env))) + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + + (if (i32.ne $type (get_global $LIST_T)) (return ($EVAL_AST $ast $env))) ;; APPLY_LIST (if ($EMPTY_Q $ast) (return ($INC_REF $ast))) @@ -187,6 +188,7 @@ (func $main (result i32) (local $line i32 $res i32 $repl_env i32) + (set_local $line (STATIC_ARRAY 201)) ;; DEBUG ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) @@ -208,12 +210,8 @@ ;; Start REPL (block $repl_done (loop $repl_loop - (set_local $line ($readline "user> ")) - (if (i32.eqz $line) (br $repl_done)) - (if (i32.eq (i32.load8_u $line) 0) - (then - ($free $line) - (br $repl_loop))) + (br_if $repl_done (i32.eqz ($readline "user> " $line))) + (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then @@ -222,9 +220,10 @@ (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) -;; ($PR_MEMORY -1 -1) - ($free $line) - (br $repl_loop))) + ;;($PR_MEMORY_SUMMARY_SMALL) + (br $repl_loop) + ) + ) ($print "\n") ;;($PR_MEMORY -1 -1) diff --git a/wasm/step3_env.wam b/wasm/step3_env.wam index 83de79e720..f089c98c9d 100644 --- a/wasm/step3_env.wam +++ b/wasm/step3_env.wam @@ -100,7 +100,7 @@ (func $EVAL (param $ast i32 $env i32) (result i32) (local $res i32) - (local $type i32 $ftype i32 $f_args i32 $f i32 $args i32) + (local $ftype i32 $f_args i32 $f i32 $args i32) (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) (local $let_env i32) @@ -108,13 +108,13 @@ (set_local $f_args 0) (set_local $f 0) (set_local $args 0) - (set_local $type ($TYPE $ast)) - - ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) (if (get_global $error_type) (return 0)) - (if (i32.ne $type (get_global $LIST_T)) (return ($EVAL_AST $ast $env))) + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (return ($EVAL_AST $ast $env))) ;; APPLY_LIST (if ($EMPTY_Q $ast) (return ($INC_REF $ast))) @@ -182,7 +182,7 @@ (then (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f)))) (else - ($THROW_STR_1 "apply of non-function type: %d\n" $type) + ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) (set_local $res 0))) ($RELEASE $f_args))))) @@ -237,6 +237,7 @@ (func $main (result i32) (local $line i32 $res i32 $repl_env i32) + (set_local $line (STATIC_ARRAY 201)) ;; DEBUG ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) @@ -258,12 +259,8 @@ ;; Start REPL (block $repl_done (loop $repl_loop - (set_local $line ($readline "user> ")) - (if (i32.eqz $line) (br $repl_done)) - (if (i32.eq (i32.load8_u $line) 0) - (then - ($free $line) - (br $repl_loop))) + (br_if $repl_done (i32.eqz ($readline "user> " $line))) + (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then @@ -272,9 +269,10 @@ (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) -;; ($PR_MEMORY -1 -1) - ($free $line) - (br $repl_loop))) + ;;($PR_MEMORY_SUMMARY_SMALL) + (br $repl_loop) + ) + ) ($print "\n") ;;($PR_MEMORY -1 -1) diff --git a/wasm/step4_if_fn_do.wam b/wasm/step4_if_fn_do.wam index f87754176a..273ccf7b76 100644 --- a/wasm/step4_if_fn_do.wam +++ b/wasm/step4_if_fn_do.wam @@ -94,7 +94,7 @@ (func $EVAL (param $ast i32 $env i32) (result i32) (local $res i32 $el i32) - (local $type i32 $ftype i32 $f_args i32 $f i32 $args i32) + (local $ftype i32 $f_args i32 $f i32 $args i32) (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32 $a3 i32) (local $let_env i32 $fn_env i32 $a i32) @@ -103,10 +103,9 @@ (set_local $f 0) (set_local $args 0) - ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + (if (get_global $error_type) (return 0)) - (if (get_global $error_type) - (return 0)) + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) (return ($EVAL_AST $ast $env))) @@ -229,7 +228,7 @@ ($RELEASE $a)) (else ;; create new environment using env and params stored in function - ($THROW_STR_1 "apply of non-function type: %d\n" $type) + ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) (set_local $res 0) ($RELEASE $f_args))))))))))))))) @@ -273,6 +272,7 @@ (func $main (result i32) (local $line i32 $res i32 $repl_env i32) + (set_local $line (STATIC_ARRAY 201)) ;; DEBUG ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) @@ -295,12 +295,8 @@ ;; Start REPL (block $repl_done (loop $repl_loop - (set_local $line ($readline "user> ")) - (if (i32.eqz $line) (br $repl_done)) - (if (i32.eq (i32.load8_u $line) 0) - (then - ($free $line) - (br $repl_loop))) + (br_if $repl_done (i32.eqz ($readline "user> " $line))) + (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then @@ -309,9 +305,10 @@ (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) -;; ($PR_MEMORY -1 -1) - ($free $line) - (br $repl_loop))) + ;;($PR_MEMORY_SUMMARY_SMALL) + (br $repl_loop) + ) + ) ($print "\n") ;;($PR_MEMORY -1 -1) diff --git a/wasm/step5_tco.wam b/wasm/step5_tco.wam index e9db72eb5b..2aca251315 100644 --- a/wasm/step5_tco.wam +++ b/wasm/step5_tco.wam @@ -113,13 +113,13 @@ (set_local $f 0) (set_local $args 0) - ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (get_global $error_type) (then (set_local $res 0) (br $EVAL_return))) + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) (then (set_local $res ($EVAL_AST $ast $env 0)) @@ -188,6 +188,7 @@ ;; EVAL the rest through second to last (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) (set_local $ast ($LAST $ast)) + ($RELEASE $ast) ;; we already own it via ast ($RELEASE $el) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "if" $a0sym)) @@ -322,12 +323,14 @@ (func $main (result i32) (local $line i32 $res i32 $repl_env i32) + (set_local $line (STATIC_ARRAY 201)) ;; DEBUG - ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) - ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) - ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) - ($printf_1 "mem: 0x%x\n" (get_global $mem)) +;; ($printf_1 "argc: 0x%x\n" $argc) +;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) ;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) (set_global $repl_env ($ENV_NEW (get_global $NIL))) @@ -336,6 +339,8 @@ ;; core.EXT: defined in wasm ($add_core_ns $repl_env) + ($checkpoint_user_memory) + ;; core.mal: defined using the language itself ($RELEASE ($RE "(def! not (fn* (a) (if a false true)))" $repl_env)) @@ -344,12 +349,8 @@ ;; Start REPL (block $repl_done (loop $repl_loop - (set_local $line ($readline "user> ")) - (if (i32.eqz $line) (br $repl_done)) - (if (i32.eq (i32.load8_u $line) 0) - (then - ($free $line) - (br $repl_loop))) + (br_if $repl_done (i32.eqz ($readline "user> " $line))) + (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then @@ -358,9 +359,10 @@ (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) -;; ($PR_MEMORY -1 -1) - ($free $line) - (br $repl_loop))) + ;;($PR_MEMORY_SUMMARY_SMALL) + (br $repl_loop) + ) + ) ($print "\n") ;;($PR_MEMORY -1 -1) diff --git a/wasm/step6_file.wam b/wasm/step6_file.wam index e58c424529..33da6cfdff 100644 --- a/wasm/step6_file.wam +++ b/wasm/step6_file.wam @@ -113,13 +113,13 @@ (set_local $f 0) (set_local $args 0) - ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (get_global $error_type) (then (set_local $res 0) (br $EVAL_return))) + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) (then (set_local $res ($EVAL_AST $ast $env 0)) @@ -188,6 +188,7 @@ ;; EVAL the rest through second to last (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) (set_local $ast ($LAST $ast)) + ($RELEASE $ast) ;; we already own it via ast ($RELEASE $el) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "if" $a0sym)) @@ -329,6 +330,7 @@ (local $line i32 $res i32 $repl_env i32) ;; argument processing (local $i i32 $ret i32 $empty i32 $current i32 $tmp i32 $val2 i32) + (set_local $line (STATIC_ARRAY 201)) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -345,6 +347,8 @@ ($add_core_ns $repl_env) (drop ($ENV_SET_S $repl_env "eval" ($FUNCTION 0))) + ($checkpoint_user_memory) + ;; core.mal: defined using the language itself ($RELEASE ($RE "(def! not (fn* (a) (if a false true)))" $repl_env)) ($RELEASE ($RE "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" $repl_env)) @@ -400,12 +404,8 @@ ;; Start REPL (block $repl_done (loop $repl_loop - (set_local $line ($readline "user> ")) - (if (i32.eqz $line) (br $repl_done)) - (if (i32.eq (i32.load8_u $line) 0) - (then - ($free $line) - (br $repl_loop))) + (br_if $repl_done (i32.eqz ($readline "user> " $line))) + (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then @@ -414,9 +414,10 @@ (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) -;; ($PR_MEMORY -1 -1) - ($free $line) - (br $repl_loop))) + ;;($PR_MEMORY_SUMMARY_SMALL) + (br $repl_loop) + ) + ) ($print "\n") ;;($PR_MEMORY -1 -1) diff --git a/wasm/step7_quote.wam b/wasm/step7_quote.wam index 16b4e8555e..9a718cc1a9 100644 --- a/wasm/step7_quote.wam +++ b/wasm/step7_quote.wam @@ -165,13 +165,13 @@ (set_local $f 0) (set_local $args 0) - ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (get_global $error_type) (then (set_local $res 0) (br $EVAL_return))) + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) (then (set_local $res ($EVAL_AST $ast $env 0)) @@ -240,6 +240,7 @@ ;; EVAL the rest through second to last (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) (set_local $ast ($LAST $ast)) + ($RELEASE $ast) ;; we already own it via ast ($RELEASE $el) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "quote" $a0sym)) @@ -393,6 +394,7 @@ (local $line i32 $res i32 $repl_env i32) ;; argument processing (local $i i32 $ret i32 $empty i32 $current i32 $tmp i32 $val2 i32) + (set_local $line (STATIC_ARRAY 201)) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -409,6 +411,8 @@ ($add_core_ns $repl_env) (drop ($ENV_SET_S $repl_env "eval" ($FUNCTION 0))) + ($checkpoint_user_memory) + ;; core.mal: defined using the language itself ($RELEASE ($RE "(def! not (fn* (a) (if a false true)))" $repl_env)) ($RELEASE ($RE "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" $repl_env)) @@ -464,12 +468,8 @@ ;; Start REPL (block $repl_done (loop $repl_loop - (set_local $line ($readline "user> ")) - (if (i32.eqz $line) (br $repl_done)) - (if (i32.eq (i32.load8_u $line) 0) - (then - ($free $line) - (br $repl_loop))) + (br_if $repl_done (i32.eqz ($readline "user> " $line))) + (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then @@ -478,9 +478,10 @@ (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) -;; ($PR_MEMORY -1 -1) - ($free $line) - (br $repl_loop))) + ;;($PR_MEMORY_SUMMARY_SMALL) + (br $repl_loop) + ) + ) ($print "\n") ;;($PR_MEMORY -1 -1) diff --git a/wasm/step8_macros.wam b/wasm/step8_macros.wam index 6fb6da1f2e..389977b532 100644 --- a/wasm/step8_macros.wam +++ b/wasm/step8_macros.wam @@ -60,12 +60,12 @@ $res ) - (global $mac_ast_stack (mut i32) (i32.const 0)) - (global $mac_ast_stack_top (mut i32) (i32.const -1)) + (global $mac_stack (mut i32) (i32.const 0)) + (global $mac_stack_top (mut i32) (i32.const -1)) (func $MACROEXPAND (param $orig_ast i32 $env i32) (result i32) (local $ast i32 $mac i32 $mac_env i64) - (set_global $mac_ast_stack (STATIC_ARRAY 128)) + (set_global $mac_stack (STATIC_ARRAY 1024)) ;; 256 * 4 (set_local $ast $orig_ast) (set_local $mac 0) (block $done @@ -89,16 +89,18 @@ ;; to the pending release list. (if (i32.ne $ast $orig_ast) (then - (set_global $mac_ast_stack_top - (i32.add (get_global $mac_ast_stack_top) 1)) + (set_global $mac_stack_top + (i32.add (get_global $mac_stack_top) 1)) + (if (i32.ge_s (i32.mul_s (get_global $mac_stack_top) 4) 1024) ;; 256 * 4 + ($fatal 7 "Exhausted mac_stack!\n")) (i32.store (i32.add - (get_global $mac_ast_stack) - (i32.mul_s (get_global $mac_ast_stack_top) 4)) + (get_global $mac_stack) + (i32.mul_s (get_global $mac_stack_top) 4)) $ast))) (if (get_global $error_type) (br $done)) - (br $loop) + (br $loop) ) ) $ast @@ -195,12 +197,14 @@ (local $ast i32 $env i32 $prev_ast i32 $prev_env i32 $res i32 $el i32) (local $ftype i32 $f_args i32 $f i32 $args i32) (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) + (local $orig_mac_stack_top i32) (set_local $ast $orig_ast) (set_local $env $orig_env) (set_local $prev_ast 0) (set_local $prev_env 0) (set_local $res 0) + (set_local $orig_mac_stack_top (get_global $mac_stack_top)) (block $EVAL_return (loop $TCO_loop @@ -209,13 +213,13 @@ (set_local $f 0) (set_local $args 0) - ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (get_global $error_type) (then (set_local $res 0) (br $EVAL_return))) + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) (then (set_local $res ($EVAL_AST $ast $env 0)) @@ -292,6 +296,7 @@ ;; EVAL the rest through second to last (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) (set_local $ast ($LAST $ast)) + ($RELEASE $ast) ;; we already own it via ast ($RELEASE $el) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "quote" $a0sym)) @@ -421,6 +426,23 @@ (if (i32.ne $env $orig_env) ($RELEASE $env)) (if $prev_ast ($RELEASE $prev_ast)) + ;; release memory from MACROEXPAND + ;; TODO: needs to happen here so self-hosting doesn't leak + (block $done + (loop $loop + (if (i32.le_s (get_global $mac_stack_top) $orig_mac_stack_top) + (br $done)) +;;($printf_1 "free orig_mac_stack_top: %d\n" $orig_mac_stack_top) +;;($printf_1 "free mac_stack_top: %d\n" (get_global $mac_stack_top)) + ($RELEASE (i32.load (i32.add + (get_global $mac_stack) + (i32.mul_s (get_global $mac_stack_top) 4)))) + (set_global $mac_stack_top + (i32.sub_s (get_global $mac_stack_top) 1)) + (br $loop) + ) + ) + $res ) @@ -441,20 +463,6 @@ ;; release memory from MAL_READ ($RELEASE $mv1) - ;; release memory from MACROEXPAND - ;; TODO: needs to happen in EVAL - (block $done - (loop $loop - (if (i32.lt_s (get_global $mac_ast_stack_top) 0) - (br $done)) - ($RELEASE (i32.load (i32.add - (get_global $mac_ast_stack) - (i32.mul_s (get_global $mac_ast_stack_top) 4)))) - (set_global $mac_ast_stack_top - (i32.sub_s (get_global $mac_ast_stack_top) 1)) - (br $loop) - ) - ) $res ) @@ -477,6 +485,7 @@ (local $line i32 $res i32 $repl_env i32) ;; argument processing (local $i i32 $ret i32 $empty i32 $current i32 $tmp i32 $val2 i32) + (set_local $line (STATIC_ARRAY 201)) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -493,6 +502,8 @@ ($add_core_ns $repl_env) (drop ($ENV_SET_S $repl_env "eval" ($FUNCTION 0))) + ($checkpoint_user_memory) + ;; core.mal: defined using the language itself ($RELEASE ($RE "(def! not (fn* (a) (if a false true)))" $repl_env)) ($RELEASE ($RE "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" $repl_env)) @@ -550,12 +561,8 @@ ;; Start REPL (block $repl_done (loop $repl_loop - (set_local $line ($readline "user> ")) - (if (i32.eqz $line) (br $repl_done)) - (if (i32.eq (i32.load8_u $line) 0) - (then - ($free $line) - (br $repl_loop))) + (br_if $repl_done (i32.eqz ($readline "user> " $line))) + (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then @@ -564,9 +571,10 @@ (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) -;; ($PR_MEMORY -1 -1) - ($free $line) - (br $repl_loop))) + ;;($PR_MEMORY_SUMMARY_SMALL) + (br $repl_loop) + ) + ) ($print "\n") ;;($PR_MEMORY -1 -1) diff --git a/wasm/step9_try.wam b/wasm/step9_try.wam index 3ebeee1369..67fd7fda36 100644 --- a/wasm/step9_try.wam +++ b/wasm/step9_try.wam @@ -1,4 +1,4 @@ -(module $step8_macros +(module $step9_try (global $repl_env (mut i32) (i32.const 0)) @@ -60,12 +60,12 @@ $res ) - (global $mac_ast_stack (mut i32) (i32.const 0)) - (global $mac_ast_stack_top (mut i32) (i32.const -1)) + (global $mac_stack (mut i32) (i32.const 0)) + (global $mac_stack_top (mut i32) (i32.const -1)) (func $MACROEXPAND (param $orig_ast i32 $env i32) (result i32) (local $ast i32 $mac i32 $mac_env i64) - (set_global $mac_ast_stack (STATIC_ARRAY 128)) + (set_global $mac_stack (STATIC_ARRAY 1024)) ;; 256 * 4 (set_local $ast $orig_ast) (set_local $mac 0) (block $done @@ -89,16 +89,18 @@ ;; to the pending release list. (if (i32.ne $ast $orig_ast) (then - (set_global $mac_ast_stack_top - (i32.add (get_global $mac_ast_stack_top) 1)) + (set_global $mac_stack_top + (i32.add (get_global $mac_stack_top) 1)) + (if (i32.ge_s (i32.mul_s (get_global $mac_stack_top) 4) 1024) ;; 256 * 4 + ($fatal 7 "Exhausted mac_stack!\n")) (i32.store (i32.add - (get_global $mac_ast_stack) - (i32.mul_s (get_global $mac_ast_stack_top) 4)) + (get_global $mac_stack) + (i32.mul_s (get_global $mac_stack_top) 4)) $ast))) (if (get_global $error_type) (br $done)) - (br $loop) + (br $loop) ) ) $ast @@ -196,12 +198,14 @@ (local $ftype i32 $f_args i32 $f i32 $args i32) (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) (local $err i32) + (local $orig_mac_stack_top i32) (set_local $ast $orig_ast) (set_local $env $orig_env) (set_local $prev_ast 0) (set_local $prev_env 0) (set_local $res 0) + (set_local $orig_mac_stack_top (get_global $mac_stack_top)) (block $EVAL_return (loop $TCO_loop @@ -210,13 +214,13 @@ (set_local $f 0) (set_local $args 0) - ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (get_global $error_type) (then (set_local $res 0) (br $EVAL_return))) + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) (then (set_local $res ($EVAL_AST $ast $env 0)) @@ -293,6 +297,7 @@ ;; EVAL the rest through second to last (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) (set_local $ast ($LAST $ast)) + ($RELEASE $ast) ;; we already own it via ast ($RELEASE $el) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "quote" $a0sym)) @@ -334,7 +339,6 @@ (if (i32.eqz (get_global $error_type)) (br $EVAL_return)) ;; if there is an error and res is set, we need to free it - ($printf_1 "res value: %d\n" $res) ($RELEASE $res) ;; if there is no catch block then return (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast)))) @@ -470,6 +474,23 @@ (if (i32.ne $env $orig_env) ($RELEASE $env)) (if $prev_ast ($RELEASE $prev_ast)) + ;; release memory from MACROEXPAND + ;; TODO: needs to happen here so self-hosting doesn't leak + (block $done + (loop $loop + (if (i32.le_s (get_global $mac_stack_top) $orig_mac_stack_top) + (br $done)) +;;($printf_1 "free orig_mac_stack_top: %d\n" $orig_mac_stack_top) +;;($printf_1 "free mac_stack_top: %d\n" (get_global $mac_stack_top)) + ($RELEASE (i32.load (i32.add + (get_global $mac_stack) + (i32.mul_s (get_global $mac_stack_top) 4)))) + (set_global $mac_stack_top + (i32.sub_s (get_global $mac_stack_top) 1)) + (br $loop) + ) + ) + $res ) @@ -490,20 +511,6 @@ ;; release memory from MAL_READ ($RELEASE $mv1) - ;; release memory from MACROEXPAND - ;; TODO: needs to happen in EVAL - (block $done - (loop $loop - (if (i32.lt_s (get_global $mac_ast_stack_top) 0) - (br $done)) - ($RELEASE (i32.load (i32.add - (get_global $mac_ast_stack) - (i32.mul_s (get_global $mac_ast_stack_top) 4)))) - (set_global $mac_ast_stack_top - (i32.sub_s (get_global $mac_ast_stack_top) 1)) - (br $loop) - ) - ) $res ) @@ -526,6 +533,7 @@ (local $line i32 $res i32 $repl_env i32) ;; argument processing (local $i i32 $ret i32 $empty i32 $current i32 $tmp i32 $val2 i32) + (set_local $line (STATIC_ARRAY 201)) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -542,6 +550,7 @@ ($add_core_ns $repl_env) (drop ($ENV_SET_S $repl_env "eval" ($FUNCTION 0))) + ($checkpoint_user_memory) ;; core.mal: defined using the language itself ($RELEASE ($RE "(def! not (fn* (a) (if a false true)))" $repl_env)) @@ -600,12 +609,8 @@ ;; Start REPL (block $repl_done (loop $repl_loop - (set_local $line ($readline "user> ")) - (if (i32.eqz $line) (br $repl_done)) - (if (i32.eq (i32.load8_u $line) 0) - (then - ($free $line) - (br $repl_loop))) + (br_if $repl_done (i32.eqz ($readline "user> " $line))) + (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then @@ -614,9 +619,10 @@ (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) -;; ($PR_MEMORY -1 -1) - ($free $line) - (br $repl_loop))) + ;;($PR_MEMORY_SUMMARY_SMALL) + (br $repl_loop) + ) + ) ($print "\n") ;;($PR_MEMORY -1 -1) diff --git a/wasm/stepA_mal.wam b/wasm/stepA_mal.wam index 4d0788fd54..f196fabc98 100644 --- a/wasm/stepA_mal.wam +++ b/wasm/stepA_mal.wam @@ -1,4 +1,4 @@ -(module $step8_macros +(module $stepA_mal (global $repl_env (mut i32) (i32.const 0)) @@ -60,12 +60,12 @@ $res ) - (global $mac_ast_stack (mut i32) (i32.const 0)) - (global $mac_ast_stack_top (mut i32) (i32.const -1)) + (global $mac_stack (mut i32) (i32.const 0)) + (global $mac_stack_top (mut i32) (i32.const -1)) (func $MACROEXPAND (param $orig_ast i32 $env i32) (result i32) (local $ast i32 $mac i32 $mac_env i64) - (set_global $mac_ast_stack (STATIC_ARRAY 128)) + (set_global $mac_stack (STATIC_ARRAY 1024)) ;; 256 * 4 (set_local $ast $orig_ast) (set_local $mac 0) (block $done @@ -89,16 +89,18 @@ ;; to the pending release list. (if (i32.ne $ast $orig_ast) (then - (set_global $mac_ast_stack_top - (i32.add (get_global $mac_ast_stack_top) 1)) + (set_global $mac_stack_top + (i32.add (get_global $mac_stack_top) 1)) + (if (i32.ge_s (i32.mul_s (get_global $mac_stack_top) 4) 1024) ;; 256 * 4 + ($fatal 7 "Exhausted mac_stack!\n")) (i32.store (i32.add - (get_global $mac_ast_stack) - (i32.mul_s (get_global $mac_ast_stack_top) 4)) + (get_global $mac_stack) + (i32.mul_s (get_global $mac_stack_top) 4)) $ast))) (if (get_global $error_type) (br $done)) - (br $loop) + (br $loop) ) ) $ast @@ -196,12 +198,14 @@ (local $ftype i32 $f_args i32 $f i32 $args i32) (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) (local $err i32) + (local $orig_mac_stack_top i32) (set_local $ast $orig_ast) (set_local $env $orig_env) (set_local $prev_ast 0) (set_local $prev_env 0) (set_local $res 0) + (set_local $orig_mac_stack_top (get_global $mac_stack_top)) (block $EVAL_return (loop $TCO_loop @@ -210,13 +214,13 @@ (set_local $f 0) (set_local $args 0) - ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (get_global $error_type) (then (set_local $res 0) (br $EVAL_return))) + ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) (then (set_local $res ($EVAL_AST $ast $env 0)) @@ -293,6 +297,7 @@ ;; EVAL the rest through second to last (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) (set_local $ast ($LAST $ast)) + ($RELEASE $ast) ;; we already own it via ast ($RELEASE $el) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "quote" $a0sym)) @@ -334,7 +339,6 @@ (if (i32.eqz (get_global $error_type)) (br $EVAL_return)) ;; if there is an error and res is set, we need to free it - ($printf_1 "res value: %d\n" $res) ($RELEASE $res) ;; if there is no catch block then return (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast)))) @@ -470,6 +474,23 @@ (if (i32.ne $env $orig_env) ($RELEASE $env)) (if $prev_ast ($RELEASE $prev_ast)) + ;; release memory from MACROEXPAND + ;; TODO: needs to happen here so self-hosting doesn't leak + (block $done + (loop $loop + (if (i32.le_s (get_global $mac_stack_top) $orig_mac_stack_top) + (br $done)) +;;($printf_1 "free orig_mac_stack_top: %d\n" $orig_mac_stack_top) +;;($printf_1 "free mac_stack_top: %d\n" (get_global $mac_stack_top)) + ($RELEASE (i32.load (i32.add + (get_global $mac_stack) + (i32.mul_s (get_global $mac_stack_top) 4)))) + (set_global $mac_stack_top + (i32.sub_s (get_global $mac_stack_top) 1)) + (br $loop) + ) + ) + $res ) @@ -490,20 +511,6 @@ ;; release memory from MAL_READ ($RELEASE $mv1) - ;; release memory from MACROEXPAND - ;; TODO: needs to happen in EVAL - (block $done - (loop $loop - (if (i32.lt_s (get_global $mac_ast_stack_top) 0) - (br $done)) - ($RELEASE (i32.load (i32.add - (get_global $mac_ast_stack) - (i32.mul_s (get_global $mac_ast_stack_top) 4)))) - (set_global $mac_ast_stack_top - (i32.sub_s (get_global $mac_ast_stack_top) 1)) - (br $loop) - ) - ) $res ) @@ -526,6 +533,7 @@ (local $line i32 $res i32 $repl_env i32) ;; argument processing (local $i i32 $ret i32 $empty i32 $current i32 $tmp i32 $val2 i32) + (set_local $line (STATIC_ARRAY 201)) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -605,12 +613,8 @@ ;; Start REPL (block $repl_done (loop $repl_loop - (set_local $line ($readline "user> ")) - (if (i32.eqz $line) (br $repl_done)) - (if (i32.eq (i32.load8_u $line) 0) - (then - ($free $line) - (br $repl_loop))) + (br_if $repl_done (i32.eqz ($readline "user> " $line))) + (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then @@ -619,9 +623,10 @@ (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) - ($free $line) ;;($PR_MEMORY_SUMMARY_SMALL) - (br $repl_loop))) + (br $repl_loop) + ) + ) ($print "\n") ;;($PR_MEMORY -1 -1) diff --git a/wasm/string.wam b/wasm/string.wam index 4a68c0c369..dd2ff3cd04 100644 --- a/wasm/string.wam +++ b/wasm/string.wam @@ -162,9 +162,7 @@ (set_local $needle_len ($strlen $needle)) (set_local $replace_len ($strlen $replace)) (if (i32.gt_u $replace_len $needle_len) - (then - ($print "REPLACE: invalid expanding in-place call\n") - ($exit 1))) + ($fatal 7 "REPLACE: invalid expanding in-place call\n")) (set_local $s (i32.add $s 1)) (br $loop) ) diff --git a/wasm/types.wam b/wasm/types.wam index 8f14ad9a3a..5574c3d3be 100644 --- a/wasm/types.wam +++ b/wasm/types.wam @@ -125,9 +125,66 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; string functions + (func $to_MalString (param $mv i32) (result i32) + ;; TODO: assert mv is a string/keyword/symbol + (i32.add (get_global $string_mem) ($VAL0 $mv)) + ) + (func $to_String (param $mv i32) (result i32) - ;; skip string refcnt - (i32.add 4 ($VAL0 $mv)) + ;; skip string refcnt and size + (i32.add 4 ($to_MalString $mv)) + ) + + ;; Duplicate regular character array string into a Mal string and + ;; return the MalVal pointer + (func $STRING (param $type i32 $str i32) (result i32) + (local $ms i32) + ;; TODO: assert mv is a string/keyword/symbol + (set_local $ms ($ALLOC_STRING $str ($strlen $str) 1)) + ($ALLOC_SCALAR $type (i32.sub_u $ms (get_global $string_mem))) + ) + + ;; Find first duplicate (internet) of mv. If one is found, free up + ;; mv and return the interned version. If no duplicate is found, + ;; return NULL. + (func $INTERN_STRING (param $mv i32) (result i32) + (local $res i32 $ms i32 $existing_ms i32 $tmp i32) + (set_local $res 0) + (set_local $ms ($to_MalString $mv)) + (set_local $existing_ms ($FIND_STRING (i32.add $ms 4))) + (if (AND $existing_ms (i32.lt_s $existing_ms $ms)) + (then + (set_local $tmp $mv) + (set_local $res ($ALLOC_SCALAR (get_global $STRING_T) + (i32.sub_s $existing_ms + (get_global $string_mem)))) + (i32.store16_u $existing_ms (i32.add (i32.load16_u $existing_ms) 1)) + ($RELEASE $tmp))) + $res + ) + + (func $STRING_INIT (param $type i32) (result i32) + (local $ms i32) + (set_local $ms ($ALLOC_STRING "" 0 0)) + ($ALLOC_SCALAR $type (i32.sub_s $ms (get_global $string_mem))) + ) + + (func $STRING_FINALIZE (param $mv i32 $size i32) (result i32) + (local $tmp i32 $ms i32) + ;; Check if the new string can be interned. + (set_local $tmp ($INTERN_STRING $mv)) + (set_local $ms ($to_MalString $mv)) + (if $tmp + (then + (set_local $mv $tmp)) + (else + ;;; ms->size = sizeof(MalString) + size + 1 + (i32.store16_u (i32.add $ms 2) + (i32.add (i32.add 4 $size) 1)) + ;;; string_mem_next = (void *)ms + ms->size + (set_global $string_mem_next + (i32.add $ms (i32.load16_u (i32.add $ms 2)))))) + $mv ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; From 349faa83e3e8ffd96851514dbe6243ef3353ab7f Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sun, 11 Nov 2018 17:55:38 -0600 Subject: [PATCH 0717/2308] wasm: use LET macro and br_if. --- wasm/core.wam | 188 ++++++++++++++++---------------------- wasm/debug.wam | 70 +++++++------- wasm/env.wam | 31 +++---- wasm/mem.wam | 21 ++--- wasm/platform_libc.wam | 18 ++-- wasm/platform_os.wam | 7 +- wasm/printer.wam | 14 ++- wasm/printf.wam | 36 +++----- wasm/reader.wam | 99 +++++++++----------- wasm/step0_repl.wam | 4 +- wasm/step1_read_print.wam | 27 +++--- wasm/step2_eval.wam | 50 +++++----- wasm/step3_env.wam | 53 ++++++----- wasm/step4_if_fn_do.wam | 56 ++++++------ wasm/step5_tco.wam | 45 ++++----- wasm/step6_file.wam | 51 +++++------ wasm/step7_quote.wam | 57 +++++------- wasm/step8_macros.wam | 97 +++++++++----------- wasm/step9_try.wam | 106 +++++++++------------ wasm/stepA_mal.wam | 106 +++++++++------------ wasm/string.wam | 41 ++++----- wasm/types.wam | 90 ++++++++---------- 22 files changed, 560 insertions(+), 707 deletions(-) diff --git a/wasm/core.wam b/wasm/core.wam index 1709018c8c..12d2090849 100644 --- a/wasm/core.wam +++ b/wasm/core.wam @@ -58,14 +58,12 @@ ($TRUE_FALSE (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) (get_global $NIL_T)))) (func $true_Q (param $args i32) (result i32) - (local $ast i32) - (set_local $ast ($MEM_VAL1_ptr $args)) + (LET $ast ($MEM_VAL1_ptr $args)) ($TRUE_FALSE (AND (i32.eq ($TYPE $ast) (get_global $BOOLEAN_T)) (i32.eq ($VAL0 $ast) 1))) ) (func $false_Q (param $args i32) (result i32) - (local $ast i32) - (set_local $ast ($MEM_VAL1_ptr $args)) + (LET $ast ($MEM_VAL1_ptr $args)) ($TRUE_FALSE (AND (i32.eq ($TYPE $ast) (get_global $BOOLEAN_T)) (i32.eq ($VAL0 $ast) 0))) ) @@ -73,16 +71,14 @@ ($TRUE_FALSE (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) (get_global $INTEGER_T)))) (func $string_Q (param $args i32) (result i32) - (local $mv i32) - (set_local $mv ($MEM_VAL1_ptr $args)) + (LET $mv ($MEM_VAL1_ptr $args)) ($TRUE_FALSE (AND (i32.eq ($TYPE $mv) (get_global $STRING_T)) (i32.ne (i32.load8_u ($to_String $mv)) (CHR "\x7f")))) ) (func $keyword (param $args i32) (result i32) - (local $str i32) - (set_local $str ($to_String ($MEM_VAL1_ptr $args))) + (LET $str ($to_String ($MEM_VAL1_ptr $args))) (if i32 (i32.eq (i32.load8_u $str) (CHR "\x7f")) (then ($INC_REF ($MEM_VAL1_ptr $args))) (else @@ -91,15 +87,13 @@ ) (func $keyword_Q (param $args i32) (result i32) - (local $ast i32) - (set_local $ast ($MEM_VAL1_ptr $args)) + (LET $ast ($MEM_VAL1_ptr $args)) ($TRUE_FALSE (AND (i32.eq ($TYPE $ast) (get_global $STRING_T)) (i32.eq (i32.load8_u ($to_String $ast)) (CHR "\x7f")))) ) (func $fn_Q (param $args i32) (result i32) - (local $type i32) - (set_local $type ($TYPE ($MEM_VAL1_ptr $args))) + (LET $type ($TYPE ($MEM_VAL1_ptr $args))) ($TRUE_FALSE (OR (i32.eq $type (get_global $FUNCTION_T)) (i32.eq $type (get_global $MALFUNC_T))))) (func $macro_Q (param $args i32) (result i32) @@ -118,23 +112,21 @@ (func $str (param $args i32) (result i32) ($pr_str_seq $args 0 "")) (func $prn (param $args i32) (result i32) - (local $res i32) - (set_local $res ($pr_str_seq $args 1 " ")) + (LET $res ($pr_str_seq $args 1 " ")) ($printf_1 "%s\n" ($to_String $res)) ($RELEASE $res) ($INC_REF (get_global $NIL)) ) (func $println (param $args i32) (result i32) - (local $res i32) - (set_local $res ($pr_str_seq $args 0 " ")) + (LET $res ($pr_str_seq $args 0 " ")) ($printf_1 "%s\n" ($to_String $res)) ($RELEASE $res) ($INC_REF (get_global $NIL)) ) (func $core_readline (param $args i32) (result i32) - (local $line i32 $mv i32) - (set_local $line (STATIC_ARRAY 201)) + (LET $line (STATIC_ARRAY 201) + $mv 0) (if (i32.eqz ($readline ($to_String ($MEM_VAL1_ptr $args)) $line)) (return ($INC_REF (get_global $NIL)))) (set_local $mv ($STRING (get_global $STRING_T) $line)) @@ -145,10 +137,9 @@ ($read_str ($to_String ($MEM_VAL1_ptr $args)))) (func $slurp (param $args i32) (result i32) - (local $mv i32 $size i32) - (set_local $mv ($STRING_INIT (get_global $STRING_T))) - (set_local $size ($read_file ($to_String ($MEM_VAL1_ptr $args)) - ($to_String $mv))) + (LET $mv ($STRING_INIT (get_global $STRING_T)) + $size ($read_file ($to_String ($MEM_VAL1_ptr $args)) + ($to_String $mv))) (if (i32.eqz $size) (then ($THROW_STR_1 "failed to read file '%s'" ($to_String ($MEM_VAL1_ptr $args))) @@ -210,22 +201,20 @@ (get_global $VECTOR_T)))) (func $hash_map (param $args i32) (result i32) - (local $res i32 $type i32 $val2 i32 $val3 i32 $c i32) - (local $ret i32 $empty i32 $current i32) ;; MAP_LOOP stack - (set_local $type (get_global $HASHMAP_T)) - - ;; MAP_LOOP_START - (set_local $res ($MAP_LOOP_START $type)) - ;; push MAP_LOOP stack - ;;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (LET $type (get_global $HASHMAP_T) + $res ($MAP_LOOP_START $type) + $val2 0 + $val3 0 + $c 0 + ;; push MAP_LOOP stack + $ret $res + $current $res + $empty $res) ;; READ_SEQ_LOOP (block $done (loop $loop - (if (i32.eqz ($VAL0 $args)) (br $done)) + (br_if $done (i32.eqz ($VAL0 $args))) (set_local $val2 ($INC_REF ($MEM_VAL1_ptr $args))) (set_local $val3 ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))) @@ -256,15 +245,15 @@ (get_global $HASHMAP_T)))) (func $assoc (param $args i32) (result i32) - (local $hm i32 $key i32) - (set_local $hm ($MEM_VAL1_ptr $args)) + (LET $hm ($MEM_VAL1_ptr $args) + $key 0) (set_local $args ($MEM_VAL0_ptr $args)) + (drop ($INC_REF $hm)) (block $done (loop $loop - (if (OR (i32.eqz ($VAL0 $args)) - (i32.eqz ($VAL0 ($MEM_VAL0_ptr $args)))) - (br $done)) + (br_if $done (OR (i32.eqz ($VAL0 $args)) + (i32.eqz ($VAL0 ($MEM_VAL0_ptr $args))))) (set_local $hm ($ASSOC1 $hm ($MEM_VAL1_ptr $args) ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))) (set_local $args ($MEM_VAL0_ptr ($MEM_VAL0_ptr $args))) @@ -276,18 +265,16 @@ ) (func $get (param $args i32) (result i32) - (local $hm i32 $key i32) - (set_local $hm ($MEM_VAL1_ptr $args)) - (set_local $key ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) + (LET $hm ($MEM_VAL1_ptr $args) + $key ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) (if i32 (i32.eq $hm (get_global $NIL)) (then ($INC_REF (get_global $NIL))) (else ($INC_REF (i32.wrap/i64 ($HASHMAP_GET $hm $key))))) ) (func $contains_Q (param $args i32) (result i32) - (local $hm i32 $key i32) - (set_local $hm ($MEM_VAL1_ptr $args)) - (set_local $key ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) + (LET $hm ($MEM_VAL1_ptr $args) + $key ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) ($TRUE_FALSE (if i32 (i32.eq $hm (get_global $NIL)) (then 0) @@ -296,20 +283,16 @@ ) (func $keys_or_vals (param $hm i32 $keys i32) (result i32) - (local $res i32 $val2 i32) - (local $ret i32 $empty i32 $current i32) ;; MAP_LOOP stack - - ;; MAP_LOOP_START - (set_local $res ($MAP_LOOP_START (get_global $LIST_T))) - ;; push MAP_LOOP stack - ;;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (LET $res ($MAP_LOOP_START (get_global $LIST_T)) + $val2 0 + ;; MAP_LOOP stack + $ret $res + $current $res + $empty $res) (block $done (loop $loop - (if (i32.eqz ($VAL0 $hm)) (br $done)) + (br_if $done (i32.eqz ($VAL0 $hm))) (if $keys (then (set_local $val2 ($INC_REF ($MEM_VAL1_ptr $hm)))) @@ -354,15 +337,15 @@ ($LIST ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)) ($MEM_VAL1_ptr $args))) (func $concat (param $args i32) (result i32) - (local $res i32 $current i32 $sl i32 $last i32 $last_sl i64 $arg i32) - (set_local $res ($INC_REF (get_global $EMPTY_LIST))) - (set_local $current $res) - (set_local $sl 0) - (set_local $last 0) + (local $last_sl i64) + (LET $res ($INC_REF (get_global $EMPTY_LIST)) + $current $res + $sl 0 + $last 0 + $arg 0) (block $done (loop $loop - (if (i32.le_u $args (get_global $EMPTY_HASHMAP)) - (br $done)) + (br_if $done (i32.le_u $args (get_global $EMPTY_HASHMAP))) (set_local $arg ($MEM_VAL1_ptr $args)) ;; skip empty elements (if (i32.le_s $arg (get_global $EMPTY_HASHMAP)) @@ -392,15 +375,13 @@ ) (func $nth (param $args i32) (result i32) - (local $a i32 $idx i32 $i i32) - (set_local $a ($MEM_VAL1_ptr $args)) - (set_local $idx ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))) + (LET $a ($MEM_VAL1_ptr $args) + $idx ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) + $i 0) - (set_local $i 0) (block $done (loop $loop - (if (OR (i32.ge_s $i $idx) (i32.eqz ($VAL0 $a))) - (br $done)) + (br_if $done (OR (i32.ge_s $i $idx) (i32.eqz ($VAL0 $a)))) (set_local $i (i32.add $i 1)) (set_local $a ($MEM_VAL0_ptr $a)) (br $loop) @@ -415,9 +396,8 @@ ) (func $first (param $args i32) (result i32) - (local $res i32 $a i32) - (set_local $res (get_global $NIL)) - (set_local $a ($MEM_VAL1_ptr $args)) + (LET $res (get_global $NIL) + $a ($MEM_VAL1_ptr $args)) (if (AND (i32.ne $a (get_global $NIL)) (i32.ne ($VAL0 $a) 0)) (set_local $res ($MEM_VAL1_ptr $a))) @@ -425,8 +405,7 @@ ) (func $rest (param $args i32) (result i32) - (local $a i32) - (set_local $a ($MEM_VAL1_ptr $args)) + (LET $a ($MEM_VAL1_ptr $args)) (if (i32.eq $a (get_global $NIL)) (return ($INC_REF (get_global $EMPTY_LIST)))) (if (i32.ne ($VAL0 $a) 0) @@ -443,12 +422,13 @@ ($INTEGER ($COUNT ($MEM_VAL1_ptr $args)))) (func $apply (param $args i32) (result i32) - (local $f i32 $f_args i32 $rest_args i32 $rest_count i32) - (local $last_sl i64 $last i32 $res i32) - - (set_local $f ($MEM_VAL1_ptr $args)) - (set_local $rest_args ($MEM_VAL0_ptr $args)) - (set_local $rest_count ($COUNT $rest_args)) + (local $last_sl i64) + (LET $f ($MEM_VAL1_ptr $args) + $f_args 0 + $rest_args ($MEM_VAL0_ptr $args) + $rest_count ($COUNT $rest_args) + $last 0 + $res 0) (if (i32.le_s $rest_count 1) (then @@ -482,23 +462,18 @@ ) (func $map (param $args i32) (result i32) - (local $f i32 $rest_args i32 $f_args i32 $res i32) - (local $ret i32 $empty i32 $current i32) ;; MAP_LOOP stack - - (set_local $f ($MEM_VAL1_ptr $args)) - (set_local $rest_args ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) - - ;; MAP_LOOP_START - (set_local $res ($MAP_LOOP_START (get_global $LIST_T))) - ;; push MAP_LOOP stack - ;;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (LET $f ($MEM_VAL1_ptr $args) + $rest_args ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)) + $f_args 0 + $res ($MAP_LOOP_START (get_global $LIST_T)) + ;; push MAP_LOOP stack + $ret $res + $current $res + $empty $res) (block $done (loop $loop - (if (i32.eqz ($VAL1 $rest_args)) (br $done)) + (br_if $done (i32.eqz ($VAL1 $rest_args))) ;; create argument list for apply (set_local $f_args ($ALLOC (get_global $LIST_T) @@ -539,9 +514,8 @@ ;;; (func $with_meta (param $args i32) (result i32) - (local $mv i32 $meta i32) - (set_local $mv ($MEM_VAL1_ptr $args)) - (set_local $meta ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) + (LET $mv ($MEM_VAL1_ptr $args) + $meta ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) ;; remove existing metadata first ($ALLOC (get_global $METADATA_T) ($DEREF_META $mv) $meta 0) ) @@ -572,21 +546,19 @@ (func $reset_BANG (param $args i32) (result i32) (local $atom i32 $val i32) - (set_local $atom ($MEM_VAL1_ptr $args)) - (set_local $val ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) + (LET $atom ($MEM_VAL1_ptr $args) + $val ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) ($_reset_BANG $atom $val) ) (func $swap_BANG (param $args i32) (result i32) - (local $atom i32 $f_args i32 $s_args i32 $rest_args i32 $f i32 $res i32) - (set_local $atom ($MEM_VAL1_ptr $args)) - (set_local $f_args ($MEM_VAL0_ptr $args)) - (set_local $rest_args ($MEM_VAL0_ptr $f_args)) - (set_local $f ($MEM_VAL1_ptr $f_args)) - (set_local $res 0) - ;; add atom value to front of the args list - (set_local $s_args ($LIST $rest_args ($MEM_VAL0_ptr $atom))) ;; cons - (set_local $res ($APPLY $f $s_args)) + (LET $atom ($MEM_VAL1_ptr $args) + $f_args ($MEM_VAL0_ptr $args) + $rest_args ($MEM_VAL0_ptr $f_args) + ;; add atom value to front of the args list + $s_args ($LIST $rest_args ($MEM_VAL0_ptr $atom)) ;; cons + $f ($MEM_VAL1_ptr $f_args) + $res ($APPLY $f $s_args)) ;; release args ($RELEASE $s_args) ;; use reset to update the value diff --git a/wasm/debug.wam b/wasm/debug.wam index b998b9614b..6afbd5bedb 100644 --- a/wasm/debug.wam +++ b/wasm/debug.wam @@ -6,20 +6,18 @@ ) (func $CHECK_FREE_LIST (result i32) - (local $first i32 $count i32) - (set_local $first (i32.add - (get_global $mem) - (i32.mul_u (get_global $mem_free_list) - 4))) - (set_local $count 0) + (LET $first (i32.add + (get_global $mem) + (i32.mul_u (get_global $mem_free_list) 4)) + $count 0) (block $done (loop $loop - (if (i32.ge_s $first (i32.add - (get_global $mem) - (i32.mul_u (get_global $mem_unused_start) - 4))) - (br $done)) + (br_if $done + (i32.ge_s $first + (i32.add (get_global $mem) + (i32.mul_u (get_global $mem_unused_start) + 4)))) (set_local $count (i32.add $count ($MalVal_size $first))) (set_local $first (i32.add (get_global $mem) (i32.mul_u 4 ($VAL0 $first)))) (br $loop) @@ -29,20 +27,18 @@ ) (func $PR_MEMORY_SUMMARY_SMALL - (local $free i32 $free_list_count i32 $mv i32 $mem_ref_count i32) + (LET $free (i32.sub_s (get_global $MEM_SIZE) + (i32.mul_u (get_global $mem_unused_start) 4)) + $free_list_count ($CHECK_FREE_LIST) + $mv (get_global $NIL) + $mem_ref_count 0) - (set_local $free (i32.sub_s (get_global $MEM_SIZE) - (i32.mul_u (get_global $mem_unused_start) 4))) - (set_local $free_list_count ($CHECK_FREE_LIST)) - (set_local $mem_ref_count 0) - - (set_local $mv (get_global $NIL)) (block $done (loop $loop - (if (i32.ge_s $mv (i32.add - (get_global $mem) - (i32.mul_u (get_global $mem_unused_start) 4))) - (br $done)) + (br_if $done (i32.ge_s $mv (i32.add + (get_global $mem) + (i32.mul_u (get_global $mem_unused_start) + 4)))) (if (i32.ne ($TYPE $mv) (get_global $FREE_T)) (set_local $mem_ref_count (i32.add $mem_ref_count (i32.shr_u @@ -62,7 +58,7 @@ (set_local $mv (get_global $NIL)) (block $done (loop $loop - (if (i32.gt_s $mv (get_global $TRUE)) (br $done)) + (br_if $done (i32.gt_s $mv (get_global $TRUE))) ($printf_1 "%d," (i32.div_s (i32.load $mv) 32)) (set_local $mv (i32.add $mv 8)) (br $loop) @@ -71,7 +67,7 @@ (set_local $mv (get_global $EMPTY_LIST)) (block $done (loop $loop - (if (i32.gt_s $mv (get_global $EMPTY_HASHMAP)) (br $done)) + (br_if $done (i32.gt_s $mv (get_global $EMPTY_HASHMAP))) ($printf_1 "%d," (i32.div_s (i32.load $mv) 32)) (set_local $mv (i32.add $mv 12)) (br $loop) @@ -81,19 +77,17 @@ ) (func $PR_VALUE (param $fmt i32 $mv i32) - (local $temp i32) - (set_local $temp ($pr_str $mv 1)) + (LET $temp ($pr_str $mv 1)) ($printf_1 $fmt ($to_String $temp)) ($RELEASE $temp) ) (func $PR_MEMORY_VALUE (param $idx i32) (result i32) - (local $mv i32 $type i32 $size i32 $val0 i32) ;;; mv = mem + idx - (set_local $mv ($MalVal_ptr $idx)) - (set_local $type ($TYPE $mv)) - (set_local $size ($MalVal_size $mv)) - (set_local $val0 ($MalVal_val $idx 0)) + (LET $mv ($MalVal_ptr $idx) + $type ($TYPE $mv) + $size ($MalVal_size $mv) + $val0 ($MalVal_val $idx 0)) ($printf_2 "%4d: type %2d" $idx $type) @@ -213,7 +207,8 @@ ) (func $PR_STRINGS (param $start i32) - (local $ms i32 $idx i32) + (LET $ms 0 + $idx 0) ($printf_2 "String - showing %d -> %d:\n" $start (i32.sub_s (get_global $string_mem_next) (get_global $string_mem))) @@ -225,8 +220,7 @@ (set_local $ms (get_global $string_mem)) (block $done (loop $loop - (if (i32.ge_u $ms (get_global $string_mem_next)) - (br $done)) + (br_if $done (i32.ge_u $ms (get_global $string_mem_next))) (set_local $idx (i32.sub_u $ms (get_global $string_mem))) (if (i32.ge_s $idx $start) ($printf_4 "%4d: refs %2d, size %2d >> '%s'\n" @@ -242,7 +236,8 @@ ) (func $PR_MEMORY (param $start i32 $end i32) - (local $string_start i32 $idx i32) + (LET $string_start 0 + $idx 0) (if (i32.lt_s $start 0) (then (set_local $start (get_global $mem_user_start)) @@ -268,8 +263,7 @@ ;;; while (idx < end) (block $loopvals_exit (loop $loopvals - (if (i32.ge_s $idx $end) - (br $loopvals_exit)) + (br_if $loopvals_exit (i32.ge_s $idx $end)) (set_local $idx ($PR_MEMORY_VALUE $idx)) (br $loopvals) ) @@ -281,7 +275,7 @@ (func $PR_MEMORY_RAW (param $start i32 $end i32) (block $loop_exit (loop $loop - (if (i32.ge_u $start $end) (br $loop_exit)) + (br_if $loop_exit (i32.ge_u $start $end)) ($printf_2 "0x%x 0x%x\n" $start (i32.load $start)) (set_local $start (i32.add 4 $start)) (br $loop) diff --git a/wasm/env.wam b/wasm/env.wam index 64e7dd9b13..6515375dca 100644 --- a/wasm/env.wam +++ b/wasm/env.wam @@ -3,23 +3,21 @@ (func $ENV_NEW (param $outer i32) (result i32) (local $data i32 $env i32) - ;; allocate the data hashmap - (set_local $data ($HASHMAP)) - - (set_local $env ($ALLOC (get_global $ENVIRONMENT_T) $data $outer 0)) + (LET $data ($HASHMAP) ;; allocate the data hashmap + $env ($ALLOC (get_global $ENVIRONMENT_T) $data $outer 0)) ;; environment takes ownership ($RELEASE $data) $env ) (func $ENV_NEW_BINDS (param $outer i32 $binds i32 $exprs i32) (result i32) - (local $env i32 $key i32) - (set_local $env ($ENV_NEW $outer)) + (LET $env ($ENV_NEW $outer) + $key 0) ;; process bindings (block $done (loop $loop - (if (i32.eqz ($VAL0 $binds)) (br $done)) + (br_if $done (i32.eqz ($VAL0 $binds))) ;; get/deref the key from binds (set_local $key ($MEM_VAL1_ptr $binds)) @@ -51,29 +49,26 @@ ) (func $ENV_SET (param $env i32 $key i32 $value i32) (result i32) - (local $data i32) - (set_local $data ($MEM_VAL0_ptr $env)) + (LET $data ($MEM_VAL0_ptr $env)) (i32.store ($VAL0_ptr $env) ($IDX ($ASSOC1 $data $key $value))) $value ) (func $ENV_SET_S (param $env i32 $key i32 $value i32) (result i32) - (local $data i32) - (set_local $data ($MEM_VAL0_ptr $env)) + (LET $data ($MEM_VAL0_ptr $env)) (i32.store ($VAL0_ptr $env) ($IDX ($ASSOC1_S $data $key $value))) $value ) (func $ENV_FIND (param $env i32 $key i32) (result i64) - (local $res i32 $data i32 $found_res i64) - - (set_local $res 0) + (local $found_res i64) + (LET $res 0 + $data 0) (block $done (loop $loop (set_local $data ($MEM_VAL0_ptr $env)) - (set_local $found_res ($HASHMAP_GET $data - $key)) + (set_local $found_res ($HASHMAP_GET $data $key)) ;;; if (found) (if (i32.wrap/i64 (i64.shr_u $found_res (i64.const 32))) (then @@ -95,8 +90,8 @@ ) (func $ENV_GET (param $env i32 $key i32) (result i32) - (local $res i32 $res_env i64) - (set_local $res 0) + (local $res_env i64) + (LET $res 0) (set_local $res_env ($ENV_FIND $env $key)) (set_local $env (i32.wrap/i64 $res_env)) diff --git a/wasm/mem.wam b/wasm/mem.wam index 8a416ee2bd..67d0dc3325 100644 --- a/wasm/mem.wam +++ b/wasm/mem.wam @@ -98,8 +98,7 @@ (else 3))))) (func $MalVal_size (param $mv i32) (result i32) - (local $type i32) - (set_local $type ($TYPE $mv)) + (LET $type ($TYPE $mv)) ;; if (type == FREE_T) (if i32 (i32.eq $type (get_global $FREE_T)) (then @@ -113,7 +112,7 @@ ;; init_memory (func $init_memory - (local $heap_size i32) + (LET $heap_size 0) ;; ($print ">>> init_memory\n") @@ -170,10 +169,9 @@ (func $ALLOC_INTERNAL (param $type i32 $val1 i32 $val2 i32 $val3 i32) (result i32) - (local $prev i32 $res i32 $size i32) - (set_local $prev (get_global $mem_free_list)) - (set_local $res (get_global $mem_free_list)) - (set_local $size ($MalType_size $type)) + (LET $prev (get_global $mem_free_list) + $res (get_global $mem_free_list) + $size ($MalType_size $type)) (block $loop_done (loop $loop @@ -267,7 +265,7 @@ ) (func $RELEASE (param $mv i32) - (local $idx i32 $type i32 $size i32) + (LET $idx 0 $type 0 $size 0) ;; Ignore NULLs ;;; if (mv == NULL) { return; } @@ -373,8 +371,7 @@ ;; find string in string memory or 0 if not found (func $FIND_STRING (param $str i32) (result i32) - (local $ms i32) - (set_local $ms (get_global $string_mem)) + (LET $ms (get_global $string_mem)) (block $done (loop $loop (br_if $done (i32.ge_s $ms (get_global $string_mem_next))) @@ -392,7 +389,7 @@ ;; size is number of characters in the string not including the ;; trailing NULL (func $ALLOC_STRING (param $str i32 $size i32 $intern i32) (result i32) - (local $ms i32) + (LET $ms 0) ;; search for matching string in string_mem (if $intern @@ -420,7 +417,7 @@ ) (func $RELEASE_STRING (param $ms i32) - (local $size i32 $next i32 $ms_idx i32 $idx i32 $type i32 $mv i32) + (LET $size 0 $next 0 $ms_idx 0 $idx 0 $type 0 $mv 0) (if (i32.le_s (i32.load16_u $ms) 0) (then diff --git a/wasm/platform_libc.wam b/wasm/platform_libc.wam index c561d64952..7965b6ca58 100644 --- a/wasm/platform_libc.wam +++ b/wasm/platform_libc.wam @@ -37,10 +37,9 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (func $readline (param $prompt i32 $buf i32) (result i32) - (local $line i32 $len i32) - (set_local $len 0) + (LET $line ($lib_readline $prompt) + $len 0) - (set_local $line ($lib_readline $prompt)) (if $line (then ($lib_add_history $line) @@ -55,10 +54,11 @@ ;; Returns malloc'd string. Must be free by caller (func $read_file (param $path i32 $buf i32) (result i32) - (local $fst i32 $fd i32 $st_size i32 $sz i32) - (set_local $fst (STATIC_ARRAY 100)) ;; at least STAT_SIZE + (LET $fst (STATIC_ARRAY 100) ;; at least STAT_SIZE + $fd ($lib_open $path (get_global $O_RDONLY) 0) + $st_size 0 + $sz 0) - (set_local $fd ($lib_open $path (get_global $O_RDONLY) 0)) (if (i32.lt_s $fd 0) (then ($printf_1 "ERROR: slurp failed to open '%s'\n" $path) @@ -83,8 +83,10 @@ (func $get_time_ms (result i32) - (local $tv i32 $secs i32 $usecs i32 $msecs i32) - (set_local $tv (STATIC_ARRAY 10)) ;; at least TIMEVAL_SIZE + (LET $tv (STATIC_ARRAY 10) ;; at least TIMEVAL_SIZE + $secs 0 + $usecs 0 + $msecs 0) (drop ($lib_gettimeofday $tv 0)) (set_local $secs (i32.load (i32.add $tv (get_global $TV_SEC_OFFSET)))) ;; subtract 30 years to make sure secs is positive and can be diff --git a/wasm/platform_os.wam b/wasm/platform_os.wam index f7e0ec029a..4c67a44b8b 100644 --- a/wasm/platform_os.wam +++ b/wasm/platform_os.wam @@ -25,10 +25,8 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (func $readline (param $prompt i32 $buf i32) (result i32) - (local $res i32) - ;; TODO: don't hardcode count to 200 - (set_local $res ($lib_readline $prompt $buf 200)) + (LET $res ($lib_readline $prompt $buf 200)) (if $res ($lib_add_history $buf)) $res @@ -37,8 +35,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (func $read_file (param $path i32 $buf i32) (result i32) - (local $size i32) - (set_local $size ($lib_read_file $path $buf)) + (LET $size ($lib_read_file $path $buf)) ;; Add null to string (i32.store8_u (i32.add $buf $size) 0) (i32.add $size 1) diff --git a/wasm/printer.wam b/wasm/printer.wam index 34cba901e5..ebcddfd924 100644 --- a/wasm/printer.wam +++ b/wasm/printer.wam @@ -3,9 +3,9 @@ (global $printer_buf (mut i32) 0) (func $pr_str_val (param $res i32 $mv i32 $print_readably i32) (result i32) - (local $type i32 $val0 i32 $sval i32) - (set_local $type ($TYPE $mv)) - (set_local $val0 ($VAL0 $mv)) + (LET $type ($TYPE $mv) + $val0 ($VAL0 $mv) + $sval 0) ;;; switch(type) (block $done @@ -70,8 +70,7 @@ ;;; while (VAL0(mv) != 0) (block $done_seq (loop $seq_loop - (if (i32.eq ($VAL0 $mv) 0) - (br $done_seq)) + (br_if $done_seq (i32.eq ($VAL0 $mv) 0)) ;;; res = pr_str_val(res, MEM_VAL1(mv), print_readably) (set_local $res ($pr_str_val $res ($MEM_VAL1_ptr $mv) $print_readably)) @@ -147,9 +146,8 @@ (func $pr_str_internal (param $seq i32) (param $mv i32) (param $print_readably i32) (param $sep i32) (result i32) - (local $res i32 $res_str i32) - (set_local $res ($STRING_INIT (get_global $STRING_T))) - (set_local $res_str ($to_String $res)) + (LET $res ($STRING_INIT (get_global $STRING_T)) + $res_str ($to_String $res)) (if $seq (then diff --git a/wasm/printf.wam b/wasm/printf.wam index 68ca24ca43..ea6a64a057 100644 --- a/wasm/printf.wam +++ b/wasm/printf.wam @@ -49,18 +49,16 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (func $_sprintdigit (param $str i32) (param $num i32) (param $base i32) - (local $n i32 $ch i32) - (set_local $n (i32.rem_u $num $base)) - (set_local $ch (if (result i32) (i32.lt_u $n 10) 48 55)) + (LET $n (i32.rem_u $num $base) + $ch (if (result i32) (i32.lt_u $n 10) 48 55)) (i32.store8_u $str (i32.add $n $ch)) ) ;; TODO: add max buf length (i.e. snprintnum) (func $_sprintnum (param $buf i32) (param $val i32) (param $radix i32) (param $pad_cnt i32) (param $pad_char i32) (result i32) - (local $pbuf i32 $i i32 $j i32 $k i32 $len i32 $neg i32 $digit i32) - (set_local $pbuf $buf) - (set_local $neg 0) + (LET $pbuf $buf + $neg 0 $i 0 $j 0 $k 0 $len 0 $digit 0) (if (AND (i32.lt_s $val 0) (i32.eq $radix 10)) (then @@ -75,13 +73,13 @@ (i32.sub_u (i32.add (CHR "A") $digit) 10))) (set_local $pbuf (i32.add $pbuf 1)) (set_local $val (i32.div_u $val $radix)) - (if (i32.gt_u $val 0) (br $loop)) + (br_if $loop (i32.gt_u $val 0)) ) (set_local $i (i32.sub_u $pbuf $buf)) (block $done (loop $loop - (if (i32.ge_u $i $pad_cnt) (br $done)) + (br_if $done (i32.ge_u $i $pad_cnt)) (i32.store8_u $pbuf $pad_char) (set_local $pbuf (i32.add $pbuf 1)) (set_local $i (i32.add $i 1)) @@ -101,8 +99,7 @@ (set_local $i 0) (block $done (loop $loop - (if (i32.ge_u $i (i32.div_u $len 2)) - (br $done)) + (br_if $done (i32.ge_u $i (i32.div_u $len 2))) (set_local $j (i32.load8_u (i32.add $buf $i))) (set_local $k (i32.add $buf (i32.sub_u (i32.sub_u $len $i) 1))) @@ -126,10 +123,8 @@ (param $v0 i32) (param $v1 i32) (param $v2 i32) (param $v3 i32) (param $v4 i32) (param $v5 i32) (result i32) - (local $ch i32 $pstr i32 $v i32 $vidx i32 $len i32) - (local $pad_cnt i32 $pad_char i32) - (set_local $pstr $str) - (set_local $vidx 0) + (LET $pstr $str + $vidx 0 $ch 0 $v 0 $len 0 $pad_cnt 0 $pad_char 0) (block $done (loop $loop @@ -148,7 +143,7 @@ ;;; while ((ch=*(fmt++))) (set_local $ch (i32.load8_u $fmt)) (set_local $fmt (i32.add 1 $fmt)) - (if (i32.eqz $ch) (br $done)) + (br_if $done (i32.eqz $ch)) ;; TODO: check buffer length (if (i32.ne $ch (CHR "%")) @@ -161,7 +156,7 @@ ;;; ch=*(fmt++) (set_local $ch (i32.load8_u $fmt)) (set_local $fmt (i32.add 1 $fmt)) - (if (i32.eqz $ch) (br $done)) + (br_if $done (i32.eqz $ch)) (set_local $pad_cnt 0) (set_local $pad_char (CHR " ")) @@ -175,15 +170,15 @@ ;;; ch=*(fmt++) (set_local $ch (i32.load8_u $fmt)) (set_local $fmt (i32.add 1 $fmt)) - (if (i32.eqz $ch) (br $done)))) + (br_if $done (i32.eqz $ch)))) (loop $loop (set_local $pad_cnt (i32.mul_s $pad_cnt 10)) (set_local $pad_cnt (i32.add $pad_cnt (i32.sub_s $ch (CHR "0")))) (set_local $ch (i32.load8_u $fmt)) (set_local $fmt (i32.add 1 $fmt)) - (if (AND (i32.ge_s $ch (CHR "0")) (i32.le_s $ch (CHR "9"))) - (br $loop)) + (br_if $loop (AND (i32.ge_s $ch (CHR "0")) + (i32.le_s $ch (CHR "9")))) ))) (if (i32.eq (CHR "d") $ch) @@ -197,8 +192,7 @@ (set_local $len ($strlen $v)) (block $done (loop $loop - (if (i32.le_s $pad_cnt $len) - (br $done)) + (br_if $done (i32.le_s $pad_cnt $len)) (i32.store8_u $pstr (CHR " ")) (set_local $pstr (i32.add $pstr 1)) (set_local $pad_cnt (i32.sub_s $pad_cnt 1)) diff --git a/wasm/reader.wam b/wasm/reader.wam index 05890c7051..0894928b50 100644 --- a/wasm/reader.wam +++ b/wasm/reader.wam @@ -5,16 +5,14 @@ (global $read_index (mut i32) 0) (func $skip_spaces (param $str i32) (result i32) - (local $found i32 $c i32) - (set_local $found 0) - (set_local $c (i32.load8_u (i32.add $str (get_global $read_index)))) + (LET $found 0 + $c (i32.load8_u (i32.add $str (get_global $read_index)))) (block $done (loop $loop ;;; while (c == ' ' || c == ',' || c == '\n') - (if (AND (i32.ne $c (CHR " ")) - (i32.ne $c (CHR ",")) - (i32.ne $c (CHR "\n"))) - (br $done)) + (br_if $done (AND (i32.ne $c (CHR " ")) + (i32.ne $c (CHR ",")) + (i32.ne $c (CHR "\n")))) (set_local $found 1) ;;; c=str[++(*index)] (set_global $read_index (i32.add (get_global $read_index) 1)) @@ -27,9 +25,8 @@ ) (func $skip_to_eol (param $str i32) (result i32) - (local $found i32 $c i32) - (set_local $found 0) - (set_local $c (i32.load8_c (i32.add $str (get_global $read_index)))) + (LET $found 0 + $c (i32.load8_c (i32.add $str (get_global $read_index)))) (if (i32.eq $c (CHR ";")) (then (set_local $found 1) @@ -40,9 +37,8 @@ (set_local $c (i32.load8_u (i32.add $str (get_global $read_index)))) ;;; while (c != '\0' && c != '\n') - (if (AND (i32.ne $c (CHR "\x00")) - (i32.ne $c (CHR "\n"))) - (br $loop)) + (br_if $loop (AND (i32.ne $c (CHR "\x00")) + (i32.ne $c (CHR "\n")))) ) ))) ;; ($debug ">>> skip_to_eol:" $found) @@ -52,17 +48,17 @@ (func $skip_spaces_comments (param $str i32) (loop $loop ;; skip spaces - (if ($skip_spaces $str) (br $loop)) + (br_if $loop ($skip_spaces $str)) ;; skip comments - (if ($skip_to_eol $str) (br $loop)) + (br_if $loop ($skip_to_eol $str)) ) ) (func $read_token (param $str i32) (result i32) - (local $token_index i32 $instring i32 $escaped i32 $c i32) - (set_local $token_index 0) - (set_local $instring 0) - (set_local $escaped 0) + (LET $token_index 0 + $instring 0 + $escaped 0 + $c 0) ($skip_spaces_comments $str) @@ -101,21 +97,20 @@ (set_local $c (i32.load8_u (i32.add $str (get_global $read_index)))) ;;; if (c == '\0') break - (if (i32.eq $c 0) (br $done)) + (br_if $done (i32.eq $c 0)) ;;; if (!instring) (if (i32.eqz $instring) (then ;; next character is token delimiter - (if (OR (i32.eq $c (CHR "(")) - (i32.eq $c (CHR ")")) - (i32.eq $c (CHR "[")) - (i32.eq $c (CHR "]")) - (i32.eq $c (CHR "{")) - (i32.eq $c (CHR "}")) - (i32.eq $c (CHR " ")) - (i32.eq $c (CHR ",")) - (i32.eq $c (CHR "\n"))) - (br $done)))) + (br_if $done (OR (i32.eq $c (CHR "(")) + (i32.eq $c (CHR ")")) + (i32.eq $c (CHR "[")) + (i32.eq $c (CHR "]")) + (i32.eq $c (CHR "{")) + (i32.eq $c (CHR "}")) + (i32.eq $c (CHR " ")) + (i32.eq $c (CHR ",")) + (i32.eq $c (CHR "\n")))))) ;; read next character ;;; token[token_index++] = str[(*index)++] (i32.store8_u (i32.add (get_global $token_buf) $token_index) @@ -124,11 +119,12 @@ (set_local $token_index (i32.add $token_index 1)) (set_global $read_index (i32.add (get_global $read_index) 1)) ;;; if (token[0] == '~' && token[1] == '@') break - (if (AND (i32.eq (i32.load8_u (i32.add (get_global $token_buf) 0)) - (CHR "~")) - (i32.eq (i32.load8_u (i32.add (get_global $token_buf) 1)) - (CHR "@"))) - (br $done)) + (br_if $done (AND (i32.eq (i32.load8_u + (i32.add (get_global $token_buf) 0)) + (CHR "~")) + (i32.eq (i32.load8_u + (i32.add (get_global $token_buf) 1)) + (CHR "@")))) ;;; if ((!instring) || escaped) (if (OR (i32.eqz $instring) $escaped) @@ -137,8 +133,7 @@ (br $loop))) (if (i32.eq $c (CHR "\\")) (set_local $escaped 1)) - (if (i32.eq $c (CHR "\"")) - (br $done)) + (br_if $done (i32.eq $c (CHR "\""))) (br $loop) ) ))) @@ -149,18 +144,14 @@ ) (func $read_seq (param $str i32 $type i32 $end i32) (result i32) - (local $res i32 $val2 i32 $val3 i32 $c i32) - - ;; MAP_LOOP stack - (local $ret i32 $empty i32 $current i32) - - ;; MAP_LOOP_START - (set_local $res ($MAP_LOOP_START $type)) - ;; push MAP_LOOP stack - ;;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (LET $res ($MAP_LOOP_START $type) + $val2 0 + $val3 0 + $c 0 + ;; MAP_LOOP stack + $ret $res + $empty $res + $current $res) ;; READ_SEQ_LOOP (block $done @@ -213,10 +204,10 @@ ) (func $read_macro (param $str i32 $sym i32 $with_meta i32) (result i32) - (local $first i32 $second i32 $third i32 $res i32) - (set_local $first ($STRING (get_global $SYMBOL_T) $sym)) - (set_local $second ($read_form $str)) - (set_local $res $second) + (LET $first ($STRING (get_global $SYMBOL_T) $sym) + $second ($read_form $str) + $third 0 + $res $second) (if (get_global $error_type) (return $res)) (if (i32.eqz $with_meta) (then @@ -233,7 +224,7 @@ ) (func $read_form (param $str i32) (result i32) - (local $tok i32 $c0 i32 $c1 i32 $res i32 $slen i32) + (LET $tok 0 $c0 0 $c1 0 $res 0 $slen 0) (if (get_global $error_type) (return 0)) diff --git a/wasm/step0_repl.wam b/wasm/step0_repl.wam index 167c773335..dd7658495b 100644 --- a/wasm/step0_repl.wam +++ b/wasm/step0_repl.wam @@ -21,8 +21,7 @@ (func $main (result i32) ;; Constant location/value definitions - (local $line i32) - (set_local $line (STATIC_ARRAY 201)) + (LET $line (STATIC_ARRAY 201)) ;; DEBUG ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) @@ -32,7 +31,6 @@ (loop $repl_loop (br_if $repl_done (i32.eqz ($readline "user> " $line))) (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) - ;;($printf_1 "here1 %d\n", 7); ($printf_1 "%s\n" ($rep $line)) (br $repl_loop) ) diff --git a/wasm/step1_read_print.wam b/wasm/step1_read_print.wam index d9ef2b85ef..8b5e5d60b1 100644 --- a/wasm/step1_read_print.wam +++ b/wasm/step1_read_print.wam @@ -17,17 +17,16 @@ ;; REPL (func $REP (param $line i32 $env i32) (result i32) - (local $mv1 i32 $mv2 i32 $ms i32) - (block $rep_done + (LET $mv1 0 $mv2 0 $ms 0) + (block $done (set_local $mv1 ($READ $line)) - (br_if $rep_done (get_global $error_type)) + (br_if $done (get_global $error_type)) (set_local $mv2 ($EVAL $mv1 $env)) - (br_if $rep_done (get_global $error_type)) + (br_if $done (get_global $error_type)) ;; ($PR_MEMORY -1 -1) (set_local $ms ($PRINT $mv2)) - ) ;; release memory from MAL_READ @@ -36,21 +35,25 @@ ) (func $main (result i32) - (local $line i32 $res i32) - (set_local $line (STATIC_ARRAY 201)) + (LET $line (STATIC_ARRAY 201) + $res 0) ;; DEBUG - ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) - ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) - ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) - ($printf_1 "mem: 0x%x\n" (get_global $mem)) +;; ($printf_1 "argc: 0x%x\n" $argc) +;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) ;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) ;; ($PR_MEMORY_RAW ;; (get_global $mem) (i32.add (get_global $mem) ;; (i32.mul_u (get_global $mem_unused_start) 4))) - ($PR_MEMORY -1 -1) + (drop ($STRING (get_global $STRING_T) "uvw")) + (drop ($STRING (get_global $STRING_T) "xyz")) + + ;;($PR_MEMORY -1 -1) ;; Start REPL (block $repl_done diff --git a/wasm/step2_eval.wam b/wasm/step2_eval.wam index 656dca3bf7..82f772ef5d 100644 --- a/wasm/step2_eval.wam +++ b/wasm/step2_eval.wam @@ -9,9 +9,9 @@ ;; EVAL (func $EVAL_AST (param $ast i32 $env i32) (result i32) - (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) - (local $ret i32 $empty i32 $current i32) (local $res2 i64) + (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 + $ret 0 $empty 0 $current 0) (if (get_global $error_type) (return 0)) (set_local $type ($TYPE $ast)) @@ -46,7 +46,7 @@ (block $done (loop $loop ;; check if we are done evaluating the source sequence - (if (i32.eq ($VAL0 $ast) 0) (br $done)) + (br_if $done (i32.eq ($VAL0 $ast) 0)) (if (i32.eq $type (get_global $HASHMAP_T)) (then @@ -101,42 +101,41 @@ $add $subtract $multiply $divide)) (func $EVAL (param $ast i32 $env i32) (result i32) - (local $res i32) - (local $type i32 $ftype i32 $f_args i32 $f i32 $args i32) + (LET $res 0 + $ftype 0 $f_args 0 $f 0 $args 0) - (set_local $res 0) (set_local $f_args 0) (set_local $f 0) (set_local $args 0) - (set_local $type ($TYPE $ast)) (if (get_global $error_type) (return 0)) ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (i32.ne $type (get_global $LIST_T)) (return ($EVAL_AST $ast $env))) + (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (return ($EVAL_AST $ast $env))) ;; APPLY_LIST - (if ($EMPTY_Q $ast) (return ($INC_REF $ast))) + (if ($EMPTY_Q $ast) + (return ($INC_REF $ast))) ;; EVAL_INVOKE (set_local $res ($EVAL_AST $ast $env)) (set_local $f_args $res) ;; if error, return f/args for release by caller - (if (get_global $error_type) (return $f_args)) + (if (get_global $error_type) + (return $f_args)) - ;; rest - (set_local $args ($MEM_VAL0_ptr $f_args)) - ;; value - (set_local $f ($MEM_VAL1_ptr $f_args)) + (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest + (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value (set_local $ftype ($TYPE $f)) (if (i32.eq $ftype (get_global $FUNCTION_T)) (then (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f)))) (else - ($THROW_STR_1 "apply of non-function type: %d\n" $type) + ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) (set_local $res 0))) ($RELEASE $f_args) @@ -151,13 +150,13 @@ ;; REPL (func $REP (param $line i32 $env i32) (result i32) - (local $mv1 i32 $mv2 i32 $ms i32) - (block $rep_done + (LET $mv1 0 $mv2 0 $ms 0) + (block $done (set_local $mv1 ($READ $line)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) (set_local $mv2 ($EVAL $mv1 $env)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) ;; ($PR_MEMORY -1 -1) (set_local $ms ($PRINT $mv2)) @@ -187,14 +186,15 @@ ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) (func $main (result i32) - (local $line i32 $res i32 $repl_env i32) - (set_local $line (STATIC_ARRAY 201)) + (LET $line (STATIC_ARRAY 201) + $res 0 $repl_env 0) ;; DEBUG - ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) - ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) - ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) - ($printf_1 "mem: 0x%x\n" (get_global $mem)) +;; ($printf_1 "argc: 0x%x\n" $argc) +;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) ;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) (set_global $repl_env ($HASHMAP)) diff --git a/wasm/step3_env.wam b/wasm/step3_env.wam index f089c98c9d..6095459579 100644 --- a/wasm/step3_env.wam +++ b/wasm/step3_env.wam @@ -9,8 +9,8 @@ ;; EVAL (func $EVAL_AST (param $ast i32 $env i32) (result i32) - (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) - (local $ret i32 $empty i32 $current i32) + (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 + $ret 0 $empty 0 $current 0) (if (get_global $error_type) (return 0)) (set_local $type ($TYPE $ast)) @@ -37,7 +37,7 @@ (block $done (loop $loop ;; check if we are done evaluating the source sequence - (if (i32.eq ($VAL0 $ast) 0) (br $done)) + (br_if $done (i32.eq ($VAL0 $ast) 0)) (if (i32.eq $type (get_global $HASHMAP_T)) (then @@ -99,12 +99,11 @@ ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) (func $EVAL (param $ast i32 $env i32) (result i32) - (local $res i32) - (local $ftype i32 $f_args i32 $f i32 $args i32) - (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) - (local $let_env i32) + (LET $res 0 + $ftype 0 $f_args 0 $f 0 $args 0 + $a0 0 $a0sym 0 $a1 0 $a2 0 + $let_env 0) - (set_local $res 0) (set_local $f_args 0) (set_local $f 0) (set_local $args 0) @@ -117,7 +116,8 @@ (return ($EVAL_AST $ast $env))) ;; APPLY_LIST - (if ($EMPTY_Q $ast) (return ($INC_REF $ast))) + (if ($EMPTY_Q $ast) + (return ($INC_REF $ast))) (set_local $a0 ($MEM_VAL1_ptr $ast)) (set_local $a0sym "") @@ -143,13 +143,12 @@ (block $done (loop $loop - (if (i32.eqz ($VAL0 $a1)) - (br $done)) + (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $let_env)) - (if (get_global $error_type) (br $done)) + (br_if $done (get_global $error_type)) ;; set key/value in the let environment (set_local $res ($ENV_SET $let_env ($MEM_VAL1_ptr $a1) $res)) @@ -170,12 +169,11 @@ (set_local $f_args $res) ;; if error, return f/args for release by caller - (if (get_global $error_type) (return $f_args)) + (if (get_global $error_type) + (return $f_args)) - ;; rest - (set_local $args ($MEM_VAL0_ptr $f_args)) - ;; value - (set_local $f ($MEM_VAL1_ptr $f_args)) + (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest + (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value (set_local $ftype ($TYPE $f)) (if (i32.eq $ftype (get_global $FUNCTION_T)) @@ -197,13 +195,13 @@ ;; REPL (func $REP (param $line i32 $env i32) (result i32) - (local $mv1 i32 $mv2 i32 $ms i32) - (block $rep_done + (LET $mv1 0 $mv2 0 $ms 0) + (block $done (set_local $mv1 ($READ $line)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) (set_local $mv2 ($EVAL $mv1 $env)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) ;; ($PR_MEMORY -1 -1) (set_local $ms ($PRINT $mv2)) @@ -236,14 +234,15 @@ ($INC_REF (get_global $NIL))) (func $main (result i32) - (local $line i32 $res i32 $repl_env i32) - (set_local $line (STATIC_ARRAY 201)) + (LET $line (STATIC_ARRAY 201) + $res 0 $repl_env 0) ;; DEBUG - ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) - ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) - ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) - ($printf_1 "mem: 0x%x\n" (get_global $mem)) +;; ($printf_1 "argc: 0x%x\n" $argc) +;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) ;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) (set_global $repl_env ($ENV_NEW (get_global $NIL))) diff --git a/wasm/step4_if_fn_do.wam b/wasm/step4_if_fn_do.wam index 273ccf7b76..d52da0e6ee 100644 --- a/wasm/step4_if_fn_do.wam +++ b/wasm/step4_if_fn_do.wam @@ -9,8 +9,8 @@ ;; EVAL (func $EVAL_AST (param $ast i32 $env i32) (result i32) - (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) - (local $ret i32 $empty i32 $current i32) + (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 + $ret 0 $empty 0 $current 0) (if (get_global $error_type) (return 0)) (set_local $type ($TYPE $ast)) @@ -37,7 +37,7 @@ (block $done (loop $loop ;; check if we are done evaluating the source sequence - (if (i32.eq ($VAL0 $ast) 0) (br $done)) + (br_if $done (i32.eq ($VAL0 $ast) 0)) (if (i32.eq $type (get_global $HASHMAP_T)) (then @@ -93,12 +93,11 @@ ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) (func $EVAL (param $ast i32 $env i32) (result i32) - (local $res i32 $el i32) - (local $ftype i32 $f_args i32 $f i32 $args i32) - (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32 $a3 i32) - (local $let_env i32 $fn_env i32 $a i32) + (LET $res 0 $el 0 + $ftype 0 $f_args 0 $f 0 $args 0 + $a0 0 $a0sym 0 $a1 0 $a2 0 $a3 0 + $let_env 0 $fn_env 0 $a 0) - (set_local $res 0) (set_local $f_args 0) (set_local $f 0) (set_local $args 0) @@ -138,13 +137,12 @@ (block $done (loop $loop - (if (i32.eqz ($VAL0 $a1)) - (br $done)) + (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $let_env)) - (if (get_global $error_type) (br $done)) + (br_if $done (get_global $error_type)) ;; set key/value in the let environment (set_local $res ($ENV_SET $let_env ($MEM_VAL1_ptr $a1) $res)) @@ -197,12 +195,11 @@ (set_local $f_args $res) ;; if error, return f/args for release by caller - (if (get_global $error_type) (return $f_args)) + (if (get_global $error_type) + (return $f_args)) - ;; rest - (set_local $args ($MEM_VAL0_ptr $f_args)) - ;; value - (set_local $f ($MEM_VAL1_ptr $f_args)) + (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest + (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value (set_local $ftype ($TYPE $f)) (if (i32.eq $ftype (get_global $FUNCTION_T)) @@ -242,10 +239,10 @@ ;; REPL (func $RE (param $line i32 $env i32) (result i32) - (local $mv1 i32 $res i32) - (block $rep_done + (LET $mv1 0 $res 0) + (block $done (set_local $mv1 ($READ $line)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) (set_local $res ($EVAL $mv1 $env)) ) @@ -256,10 +253,10 @@ ) (func $REP (param $line i32 $env i32) (result i32) - (local $mv2 i32 $ms i32) - (block $rep_done + (LET $mv2 0 $ms 0) + (block $done (set_local $mv2 ($RE $line $env)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) ;; ($PR_MEMORY -1 -1) (set_local $ms ($PRINT $mv2)) @@ -271,14 +268,15 @@ ) (func $main (result i32) - (local $line i32 $res i32 $repl_env i32) - (set_local $line (STATIC_ARRAY 201)) + (LET $line (STATIC_ARRAY 201) + $res 0 $repl_env 0) ;; DEBUG - ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) - ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) - ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) - ($printf_1 "mem: 0x%x\n" (get_global $mem)) +;; ($printf_1 "argc: 0x%x\n" $argc) +;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) ;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) (set_global $repl_env ($ENV_NEW (get_global $NIL))) @@ -287,6 +285,8 @@ ;; core.EXT: defined in wasm ($add_core_ns $repl_env) + ($checkpoint_user_memory) + ;; core.mal: defined using the language itself ($RELEASE ($RE "(def! not (fn* (a) (if a false true)))" $repl_env)) diff --git a/wasm/step5_tco.wam b/wasm/step5_tco.wam index 2aca251315..161a6c5d78 100644 --- a/wasm/step5_tco.wam +++ b/wasm/step5_tco.wam @@ -9,8 +9,8 @@ ;; EVAL (func $EVAL_AST (param $ast i32 $env i32 $skiplast i32) (result i32) - (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) - (local $ret i32 $empty i32 $current i32) + (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 + $ret 0 $empty 0 $current 0) (if (get_global $error_type) (return 0)) (set_local $type ($TYPE $ast)) @@ -37,10 +37,10 @@ (block $done (loop $loop ;; check if we are done evaluating the source sequence - (if (i32.eq ($VAL0 $ast) 0) (br $done)) + (br_if $done (i32.eq ($VAL0 $ast) 0)) (if $skiplast - (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0) (br $done))) + (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0))) (if (i32.eq $type (get_global $HASHMAP_T)) (then @@ -96,15 +96,11 @@ ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) (func $EVAL (param $orig_ast i32 $orig_env i32) (result i32) - (local $ast i32 $env i32 $prev_ast i32 $prev_env i32 $res i32 $el i32) - (local $type i32 $ftype i32 $f_args i32 $f i32 $args i32) - (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) - - (set_local $ast $orig_ast) - (set_local $env $orig_env) - (set_local $prev_ast 0) - (set_local $prev_env 0) - (set_local $res 0) + (LET $ast $orig_ast + $env $orig_env + $prev_ast 0 $prev_env 0 $res 0 $el 0 + $ftype 0 $f_args 0 $f 0 $args 0 + $a0 0 $a0sym 0 $a1 0 $a2 0) (block $EVAL_return (loop $TCO_loop @@ -141,7 +137,7 @@ (set_local $a1 ($MAL_GET_A1 $ast)) (set_local $a2 ($MAL_GET_A2 $ast)) (set_local $res ($EVAL $a2 $env)) - (if (get_global $error_type) (br $EVAL_return)) + (br_if $EVAL_return (get_global $error_type)) ;; set a1 in env to a2 (set_local $res ($ENV_SET $env $a1 $res)) @@ -157,12 +153,11 @@ (block $done (loop $loop - (if (i32.eqz ($VAL0 $a1)) - (br $done)) + (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) - (if (get_global $error_type) (br $done)) + (br_if $done (get_global $error_type)) ;; set key/value in the let environment (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) @@ -293,10 +288,10 @@ ;; REPL (func $RE (param $line i32 $env i32) (result i32) - (local $mv1 i32 $res i32) - (block $rep_done + (LET $mv1 0 $res 0) + (block $done (set_local $mv1 ($READ $line)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) (set_local $res ($EVAL $mv1 $env)) ) @@ -307,10 +302,10 @@ ) (func $REP (param $line i32 $env i32) (result i32) - (local $mv2 i32 $ms i32) - (block $rep_done + (LET $mv2 0 $ms 0) + (block $done (set_local $mv2 ($RE $line $env)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) ;; ($PR_MEMORY -1 -1) (set_local $ms ($PRINT $mv2)) @@ -322,8 +317,8 @@ ) (func $main (result i32) - (local $line i32 $res i32 $repl_env i32) - (set_local $line (STATIC_ARRAY 201)) + (LET $line (STATIC_ARRAY 201) + $res 0 $repl_env 0) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) diff --git a/wasm/step6_file.wam b/wasm/step6_file.wam index 33da6cfdff..b41bf4ff16 100644 --- a/wasm/step6_file.wam +++ b/wasm/step6_file.wam @@ -9,8 +9,8 @@ ;; EVAL (func $EVAL_AST (param $ast i32 $env i32 $skiplast i32) (result i32) - (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) - (local $ret i32 $empty i32 $current i32) + (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 + $ret 0 $empty 0 $current 0) (if (get_global $error_type) (return 0)) (set_local $type ($TYPE $ast)) @@ -37,10 +37,10 @@ (block $done (loop $loop ;; check if we are done evaluating the source sequence - (if (i32.eq ($VAL0 $ast) 0) (br $done)) + (br_if $done (i32.eq ($VAL0 $ast) 0)) (if $skiplast - (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0) (br $done))) + (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0))) (if (i32.eq $type (get_global $HASHMAP_T)) (then @@ -96,15 +96,11 @@ ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) (func $EVAL (param $orig_ast i32 $orig_env i32) (result i32) - (local $ast i32 $env i32 $prev_ast i32 $prev_env i32 $res i32 $el i32) - (local $type i32 $ftype i32 $f_args i32 $f i32 $args i32) - (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) - - (set_local $ast $orig_ast) - (set_local $env $orig_env) - (set_local $prev_ast 0) - (set_local $prev_env 0) - (set_local $res 0) + (LET $ast $orig_ast + $env $orig_env + $prev_ast 0 $prev_env 0 $res 0 $el 0 + $ftype 0 $f_args 0 $f 0 $args 0 + $a0 0 $a0sym 0 $a1 0 $a2 0) (block $EVAL_return (loop $TCO_loop @@ -141,7 +137,7 @@ (set_local $a1 ($MAL_GET_A1 $ast)) (set_local $a2 ($MAL_GET_A2 $ast)) (set_local $res ($EVAL $a2 $env)) - (if (get_global $error_type) (br $EVAL_return)) + (br_if $EVAL_return (get_global $error_type)) ;; set a1 in env to a2 (set_local $res ($ENV_SET $env $a1 $res)) @@ -157,12 +153,11 @@ (block $done (loop $loop - (if (i32.eqz ($VAL0 $a1)) - (br $done)) + (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) - (if (get_global $error_type) (br $done)) + (br_if $done (get_global $error_type)) ;; set key/value in the let environment (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) @@ -298,10 +293,10 @@ ;; REPL (func $RE (param $line i32 $env i32) (result i32) - (local $mv1 i32 $res i32) - (block $rep_done + (LET $mv1 0 $res 0) + (block $done (set_local $mv1 ($READ $line)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) (set_local $res ($EVAL $mv1 $env)) ) @@ -312,10 +307,10 @@ ) (func $REP (param $line i32 $env i32) (result i32) - (local $mv2 i32 $ms i32) - (block $rep_done + (LET $mv2 0 $ms 0) + (block $done (set_local $mv2 ($RE $line $env)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) ;; ($PR_MEMORY -1 -1) (set_local $ms ($PRINT $mv2)) @@ -327,10 +322,10 @@ ) (func $main (param $argc i32 $argv i32) (result i32) - (local $line i32 $res i32 $repl_env i32) - ;; argument processing - (local $i i32 $ret i32 $empty i32 $current i32 $tmp i32 $val2 i32) - (set_local $line (STATIC_ARRAY 201)) + (LET $line (STATIC_ARRAY 201) + $res 0 $repl_env 0 + ;; argument processing + $i 0 $ret 0 $empty 0 $current 0 $tmp 0 $val2 0) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -365,7 +360,7 @@ (set_local $i 2) (block $done (loop $loop - (if (i32.ge_u $i $argc) (br $done)) + (br_if $done (i32.ge_u $i $argc)) (set_local $val2 ($STRING (get_global $STRING_T) (i32.load (i32.add $argv (i32.mul_u $i 4))))) diff --git a/wasm/step7_quote.wam b/wasm/step7_quote.wam index 9a718cc1a9..49859ecfa1 100644 --- a/wasm/step7_quote.wam +++ b/wasm/step7_quote.wam @@ -9,16 +9,14 @@ ;; EVAL (func $is_pair (param $ast i32) (result i32) - (local $type i32) - (set_local $type ($TYPE $ast)) + (LET $type ($TYPE $ast)) (AND (OR (i32.eq $type (get_global $LIST_T)) (i32.eq $type (get_global $VECTOR_T))) (i32.ne ($VAL0 $ast) 0)) ) (func $QUASIQUOTE (param $ast i32) (result i32) - (local $sym i32 $res i32 $second i32 $third i32) - (set_local $res 0) + (LET $res 0 $sym 0 $second 0 $third 0) (if (i32.eqz ($is_pair $ast)) ;; QQ_QUOTE (then (set_local $sym ($STRING (get_global $SYMBOL_T) "quote")) @@ -61,8 +59,8 @@ ) (func $EVAL_AST (param $ast i32 $env i32 $skiplast i32) (result i32) - (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) - (local $ret i32 $empty i32 $current i32) + (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 + $ret 0 $empty 0 $current 0) (if (get_global $error_type) (return 0)) (set_local $type ($TYPE $ast)) @@ -89,10 +87,10 @@ (block $done (loop $loop ;; check if we are done evaluating the source sequence - (if (i32.eq ($VAL0 $ast) 0) (br $done)) + (br_if $done (i32.eq ($VAL0 $ast) 0)) (if $skiplast - (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0) (br $done))) + (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0))) (if (i32.eq $type (get_global $HASHMAP_T)) (then @@ -148,15 +146,11 @@ ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) (func $EVAL (param $orig_ast i32 $orig_env i32) (result i32) - (local $ast i32 $env i32 $prev_ast i32 $prev_env i32 $res i32 $el i32) - (local $type i32 $ftype i32 $f_args i32 $f i32 $args i32) - (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) - - (set_local $ast $orig_ast) - (set_local $env $orig_env) - (set_local $prev_ast 0) - (set_local $prev_env 0) - (set_local $res 0) + (LET $ast $orig_ast + $env $orig_env + $prev_ast 0 $prev_env 0 $res 0 $el 0 + $ftype 0 $f_args 0 $f 0 $args 0 + $a0 0 $a0sym 0 $a1 0 $a2 0) (block $EVAL_return (loop $TCO_loop @@ -193,7 +187,7 @@ (set_local $a1 ($MAL_GET_A1 $ast)) (set_local $a2 ($MAL_GET_A2 $ast)) (set_local $res ($EVAL $a2 $env)) - (if (get_global $error_type) (br $EVAL_return)) + (br_if $EVAL_return (get_global $error_type)) ;; set a1 in env to a2 (set_local $res ($ENV_SET $env $a1 $res)) @@ -209,12 +203,11 @@ (block $done (loop $loop - (if (i32.eqz ($VAL0 $a1)) - (br $done)) + (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) - (if (get_global $error_type) (br $done)) + (br_if $done (get_global $error_type)) ;; set key/value in the let environment (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) @@ -362,10 +355,10 @@ ;; REPL (func $RE (param $line i32 $env i32) (result i32) - (local $mv1 i32 $res i32) - (block $rep_done + (LET $mv1 0 $res 0) + (block $done (set_local $mv1 ($READ $line)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) (set_local $res ($EVAL $mv1 $env)) ) @@ -376,10 +369,10 @@ ) (func $REP (param $line i32 $env i32) (result i32) - (local $mv2 i32 $ms i32) - (block $rep_done + (LET $mv2 0 $ms 0) + (block $done (set_local $mv2 ($RE $line $env)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) ;; ($PR_MEMORY -1 -1) (set_local $ms ($PRINT $mv2)) @@ -391,10 +384,10 @@ ) (func $main (param $argc i32 $argv i32) (result i32) - (local $line i32 $res i32 $repl_env i32) - ;; argument processing - (local $i i32 $ret i32 $empty i32 $current i32 $tmp i32 $val2 i32) - (set_local $line (STATIC_ARRAY 201)) + (LET $line (STATIC_ARRAY 201) + $res 0 $repl_env 0 + ;; argument processing + $i 0 $ret 0 $empty 0 $current 0 $tmp 0 $val2 0) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -429,7 +422,7 @@ (set_local $i 2) (block $done (loop $loop - (if (i32.ge_u $i $argc) (br $done)) + (br_if $done (i32.ge_u $i $argc)) (set_local $val2 ($STRING (get_global $STRING_T) (i32.load (i32.add $argv (i32.mul_u $i 4))))) diff --git a/wasm/step8_macros.wam b/wasm/step8_macros.wam index 389977b532..c7ad71fa86 100644 --- a/wasm/step8_macros.wam +++ b/wasm/step8_macros.wam @@ -9,16 +9,14 @@ ;; EVAL (func $is_pair (param $ast i32) (result i32) - (local $type i32) - (set_local $type ($TYPE $ast)) + (LET $type ($TYPE $ast)) (AND (OR (i32.eq $type (get_global $LIST_T)) (i32.eq $type (get_global $VECTOR_T))) (i32.ne ($VAL0 $ast) 0)) ) (func $QUASIQUOTE (param $ast i32) (result i32) - (local $sym i32 $res i32 $second i32 $third i32) - (set_local $res 0) + (LET $res 0 $sym 0 $second 0 $third 0) (if (i32.eqz ($is_pair $ast)) ;; QQ_QUOTE (then (set_local $sym ($STRING (get_global $SYMBOL_T) "quote")) @@ -64,24 +62,22 @@ (global $mac_stack_top (mut i32) (i32.const -1)) (func $MACROEXPAND (param $orig_ast i32 $env i32) (result i32) - (local $ast i32 $mac i32 $mac_env i64) - (set_global $mac_stack (STATIC_ARRAY 1024)) ;; 256 * 4 - (set_local $ast $orig_ast) - (set_local $mac 0) + (local $mac_env i64) + (LET $ast $orig_ast + $mac 0) + (set_global $mac_stack (STATIC_ARRAY 1024)) ;; 256 * 4, TODO: move to init (block $done (loop $loop - (if (OR (i32.ne ($TYPE $ast) (get_global $LIST_T)) ;; a list - (i32.eqz ($VAL0 $ast)) ;; non-empty - (i32.ne ($TYPE ($MEM_VAL1_ptr $ast)) ;; leading symbol - (get_global $SYMBOL_T))) - (br $done)) + (br_if $done + (OR (i32.ne ($TYPE $ast) (get_global $LIST_T)) ;; a list + (i32.eqz ($VAL0 $ast)) ;; non-empty + (i32.ne ($TYPE ($MEM_VAL1_ptr $ast)) ;; leading symbol + (get_global $SYMBOL_T)))) (set_local $mac_env ($ENV_FIND $env ($MEM_VAL1_ptr $ast))) (set_local $mac (i32.wrap/i64 (i64.shr_u $mac_env (i64.const 32)))) - (if (OR (i32.eqz (i32.wrap/i64 $mac_env)) ;; defined in env - (i32.ne ($TYPE $mac) ;; a macro - (get_global $MACRO_T))) - (then - (br $done))) + (br_if $done (OR (i32.eqz (i32.wrap/i64 $mac_env)) ;; defined in env + (i32.ne ($TYPE $mac) ;; a macro + (get_global $MACRO_T)))) (set_local $ast ($APPLY $mac ($MEM_VAL0_ptr $ast))) ;; PEND_A_LV @@ -97,8 +93,7 @@ (get_global $mac_stack) (i32.mul_s (get_global $mac_stack_top) 4)) $ast))) - (if (get_global $error_type) - (br $done)) + (br_if $done (get_global $error_type)) (br $loop) ) @@ -107,8 +102,8 @@ ) (func $EVAL_AST (param $ast i32 $env i32 $skiplast i32) (result i32) - (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) - (local $ret i32 $empty i32 $current i32) + (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 + $ret 0 $empty 0 $current 0) (if (get_global $error_type) (return 0)) (set_local $type ($TYPE $ast)) @@ -135,10 +130,10 @@ (block $done (loop $loop ;; check if we are done evaluating the source sequence - (if (i32.eq ($VAL0 $ast) 0) (br $done)) + (br_if $done (i32.eq ($VAL0 $ast) 0)) (if $skiplast - (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0) (br $done))) + (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0))) (if (i32.eq $type (get_global $HASHMAP_T)) (then @@ -194,17 +189,12 @@ ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) (func $EVAL (param $orig_ast i32 $orig_env i32) (result i32) - (local $ast i32 $env i32 $prev_ast i32 $prev_env i32 $res i32 $el i32) - (local $ftype i32 $f_args i32 $f i32 $args i32) - (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) - (local $orig_mac_stack_top i32) - - (set_local $ast $orig_ast) - (set_local $env $orig_env) - (set_local $prev_ast 0) - (set_local $prev_env 0) - (set_local $res 0) - (set_local $orig_mac_stack_top (get_global $mac_stack_top)) + (LET $ast $orig_ast + $env $orig_env + $orig_mac_stack_top (get_global $mac_stack_top) + $prev_ast 0 $prev_env 0 $res 0 $el 0 + $ftype 0 $f_args 0 $f 0 $args 0 + $a0 0 $a0sym 0 $a1 0 $a2 0) (block $EVAL_return (loop $TCO_loop @@ -249,7 +239,7 @@ (set_local $a1 ($MAL_GET_A1 $ast)) (set_local $a2 ($MAL_GET_A2 $ast)) (set_local $res ($EVAL $a2 $env)) - (if (get_global $error_type) (br $EVAL_return)) + (br_if $EVAL_return (get_global $error_type)) ;; set a1 in env to a2 (set_local $res ($ENV_SET $env $a1 $res)) @@ -265,12 +255,11 @@ (block $done (loop $loop - (if (i32.eqz ($VAL0 $a1)) - (br $done)) + (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) - (if (get_global $error_type) (br $done)) + (br_if $done (get_global $error_type)) ;; set key/value in the let environment (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) @@ -317,8 +306,7 @@ (set_local $a2 ($MAL_GET_A2 $ast)) (set_local $res ($EVAL $a2 $env)) ($SET_TYPE $res (get_global $MACRO_T)) - (if (get_global $error_type) - (br $EVAL_return)) + (br_if $EVAL_return (get_global $error_type)) ;; set a1 in env to a2 (set_local $res ($ENV_SET $env $a1 $res)) @@ -430,10 +418,7 @@ ;; TODO: needs to happen here so self-hosting doesn't leak (block $done (loop $loop - (if (i32.le_s (get_global $mac_stack_top) $orig_mac_stack_top) - (br $done)) -;;($printf_1 "free orig_mac_stack_top: %d\n" $orig_mac_stack_top) -;;($printf_1 "free mac_stack_top: %d\n" (get_global $mac_stack_top)) + (br_if $done (i32.le_s (get_global $mac_stack_top) $orig_mac_stack_top)) ($RELEASE (i32.load (i32.add (get_global $mac_stack) (i32.mul_s (get_global $mac_stack_top) 4)))) @@ -453,10 +438,10 @@ ;; REPL (func $RE (param $line i32 $env i32) (result i32) - (local $mv1 i32 $res i32) - (block $rep_done + (LET $mv1 0 $res 0) + (block $done (set_local $mv1 ($READ $line)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) (set_local $res ($EVAL $mv1 $env)) ) @@ -467,10 +452,10 @@ ) (func $REP (param $line i32 $env i32) (result i32) - (local $mv2 i32 $ms i32) - (block $rep_done + (LET $mv2 0 $ms 0) + (block $done (set_local $mv2 ($RE $line $env)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) ;; ($PR_MEMORY -1 -1) (set_local $ms ($PRINT $mv2)) @@ -482,10 +467,10 @@ ) (func $main (param $argc i32 $argv i32) (result i32) - (local $line i32 $res i32 $repl_env i32) - ;; argument processing - (local $i i32 $ret i32 $empty i32 $current i32 $tmp i32 $val2 i32) - (set_local $line (STATIC_ARRAY 201)) + (LET $line (STATIC_ARRAY 201) + $res 0 $repl_env 0 + ;; argument processing + $i 0 $ret 0 $empty 0 $current 0 $tmp 0 $val2 0) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -522,7 +507,7 @@ (set_local $i 2) (block $done (loop $loop - (if (i32.ge_u $i $argc) (br $done)) + (br_if $done (i32.ge_u $i $argc)) (set_local $val2 ($STRING (get_global $STRING_T) (i32.load (i32.add $argv (i32.mul_u $i 4))))) diff --git a/wasm/step9_try.wam b/wasm/step9_try.wam index 67fd7fda36..8021724207 100644 --- a/wasm/step9_try.wam +++ b/wasm/step9_try.wam @@ -9,16 +9,14 @@ ;; EVAL (func $is_pair (param $ast i32) (result i32) - (local $type i32) - (set_local $type ($TYPE $ast)) + (LET $type ($TYPE $ast)) (AND (OR (i32.eq $type (get_global $LIST_T)) (i32.eq $type (get_global $VECTOR_T))) (i32.ne ($VAL0 $ast) 0)) ) (func $QUASIQUOTE (param $ast i32) (result i32) - (local $sym i32 $res i32 $second i32 $third i32) - (set_local $res 0) + (LET $res 0 $sym 0 $second 0 $third 0) (if (i32.eqz ($is_pair $ast)) ;; QQ_QUOTE (then (set_local $sym ($STRING (get_global $SYMBOL_T) "quote")) @@ -64,24 +62,22 @@ (global $mac_stack_top (mut i32) (i32.const -1)) (func $MACROEXPAND (param $orig_ast i32 $env i32) (result i32) - (local $ast i32 $mac i32 $mac_env i64) - (set_global $mac_stack (STATIC_ARRAY 1024)) ;; 256 * 4 - (set_local $ast $orig_ast) - (set_local $mac 0) + (local $mac_env i64) + (LET $ast $orig_ast + $mac 0) + (set_global $mac_stack (STATIC_ARRAY 1024)) ;; 256 * 4, TODO: move to init (block $done (loop $loop - (if (OR (i32.ne ($TYPE $ast) (get_global $LIST_T)) ;; a list - (i32.eqz ($VAL0 $ast)) ;; non-empty - (i32.ne ($TYPE ($MEM_VAL1_ptr $ast)) ;; leading symbol - (get_global $SYMBOL_T))) - (br $done)) + (br_if $done + (OR (i32.ne ($TYPE $ast) (get_global $LIST_T)) ;; a list + (i32.eqz ($VAL0 $ast)) ;; non-empty + (i32.ne ($TYPE ($MEM_VAL1_ptr $ast)) ;; leading symbol + (get_global $SYMBOL_T)))) (set_local $mac_env ($ENV_FIND $env ($MEM_VAL1_ptr $ast))) (set_local $mac (i32.wrap/i64 (i64.shr_u $mac_env (i64.const 32)))) - (if (OR (i32.eqz (i32.wrap/i64 $mac_env)) ;; defined in env - (i32.ne ($TYPE $mac) ;; a macro - (get_global $MACRO_T))) - (then - (br $done))) + (br_if $done (OR (i32.eqz (i32.wrap/i64 $mac_env)) ;; defined in env + (i32.ne ($TYPE $mac) ;; a macro + (get_global $MACRO_T)))) (set_local $ast ($APPLY $mac ($MEM_VAL0_ptr $ast))) ;; PEND_A_LV @@ -97,8 +93,7 @@ (get_global $mac_stack) (i32.mul_s (get_global $mac_stack_top) 4)) $ast))) - (if (get_global $error_type) - (br $done)) + (br_if $done (get_global $error_type)) (br $loop) ) @@ -107,8 +102,8 @@ ) (func $EVAL_AST (param $ast i32 $env i32 $skiplast i32) (result i32) - (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) - (local $ret i32 $empty i32 $current i32) + (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 + $ret 0 $empty 0 $current 0) (if (get_global $error_type) (return 0)) (set_local $type ($TYPE $ast)) @@ -135,10 +130,10 @@ (block $done (loop $loop ;; check if we are done evaluating the source sequence - (if (i32.eq ($VAL0 $ast) 0) (br $done)) + (br_if $done (i32.eq ($VAL0 $ast) 0)) (if $skiplast - (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0) (br $done))) + (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0))) (if (i32.eq $type (get_global $HASHMAP_T)) (then @@ -194,18 +189,13 @@ ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) (func $EVAL (param $orig_ast i32 $orig_env i32) (result i32) - (local $ast i32 $env i32 $prev_ast i32 $prev_env i32 $res i32 $el i32) - (local $ftype i32 $f_args i32 $f i32 $args i32) - (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) - (local $err i32) - (local $orig_mac_stack_top i32) - - (set_local $ast $orig_ast) - (set_local $env $orig_env) - (set_local $prev_ast 0) - (set_local $prev_env 0) - (set_local $res 0) - (set_local $orig_mac_stack_top (get_global $mac_stack_top)) + (LET $ast $orig_ast + $env $orig_env + $orig_mac_stack_top (get_global $mac_stack_top) + $prev_ast 0 $prev_env 0 $res 0 $el 0 + $ftype 0 $f_args 0 $f 0 $args 0 + $a0 0 $a0sym 0 $a1 0 $a2 0 + $err 0) (block $EVAL_return (loop $TCO_loop @@ -250,7 +240,7 @@ (set_local $a1 ($MAL_GET_A1 $ast)) (set_local $a2 ($MAL_GET_A2 $ast)) (set_local $res ($EVAL $a2 $env)) - (if (get_global $error_type) (br $EVAL_return)) + (br_if $EVAL_return (get_global $error_type)) ;; set a1 in env to a2 (set_local $res ($ENV_SET $env $a1 $res)) @@ -266,12 +256,11 @@ (block $done (loop $loop - (if (i32.eqz ($VAL0 $a1)) - (br $done)) + (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) - (if (get_global $error_type) (br $done)) + (br_if $done (get_global $error_type)) ;; set key/value in the let environment (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) @@ -318,8 +307,7 @@ (set_local $a2 ($MAL_GET_A2 $ast)) (set_local $res ($EVAL $a2 $env)) ($SET_TYPE $res (get_global $MACRO_T)) - (if (get_global $error_type) - (br $EVAL_return)) + (br_if $EVAL_return (get_global $error_type)) ;; set a1 in env to a2 (set_local $res ($ENV_SET $env $a1 $res)) @@ -336,13 +324,12 @@ (set_local $res ($EVAL $a1 $env)) ;; if there is no error, return - (if (i32.eqz (get_global $error_type)) - (br $EVAL_return)) + (br_if $EVAL_return (i32.eqz (get_global $error_type))) ;; if there is an error and res is set, we need to free it ($RELEASE $res) ;; if there is no catch block then return - (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast)))) - (br $EVAL_return)) + (br_if $EVAL_return + (i32.eqz ($VAL0 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) ;; save the current environment for release (set_local $prev_env $env) @@ -478,10 +465,7 @@ ;; TODO: needs to happen here so self-hosting doesn't leak (block $done (loop $loop - (if (i32.le_s (get_global $mac_stack_top) $orig_mac_stack_top) - (br $done)) -;;($printf_1 "free orig_mac_stack_top: %d\n" $orig_mac_stack_top) -;;($printf_1 "free mac_stack_top: %d\n" (get_global $mac_stack_top)) + (br_if $done (i32.le_s (get_global $mac_stack_top) $orig_mac_stack_top)) ($RELEASE (i32.load (i32.add (get_global $mac_stack) (i32.mul_s (get_global $mac_stack_top) 4)))) @@ -501,10 +485,10 @@ ;; REPL (func $RE (param $line i32 $env i32) (result i32) - (local $mv1 i32 $res i32) - (block $rep_done + (LET $mv1 0 $res 0) + (block $done (set_local $mv1 ($READ $line)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) (set_local $res ($EVAL $mv1 $env)) ) @@ -515,10 +499,10 @@ ) (func $REP (param $line i32 $env i32) (result i32) - (local $mv2 i32 $ms i32) - (block $rep_done + (LET $mv2 0 $ms 0) + (block $done (set_local $mv2 ($RE $line $env)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) ;; ($PR_MEMORY -1 -1) (set_local $ms ($PRINT $mv2)) @@ -530,10 +514,10 @@ ) (func $main (param $argc i32 $argv i32) (result i32) - (local $line i32 $res i32 $repl_env i32) - ;; argument processing - (local $i i32 $ret i32 $empty i32 $current i32 $tmp i32 $val2 i32) - (set_local $line (STATIC_ARRAY 201)) + (LET $line (STATIC_ARRAY 201) + $res 0 $repl_env 0 + ;; argument processing + $i 0 $ret 0 $empty 0 $current 0 $tmp 0 $val2 0) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -570,7 +554,7 @@ (set_local $i 2) (block $done (loop $loop - (if (i32.ge_u $i $argc) (br $done)) + (br_if $done (i32.ge_u $i $argc)) (set_local $val2 ($STRING (get_global $STRING_T) (i32.load (i32.add $argv (i32.mul_u $i 4))))) diff --git a/wasm/stepA_mal.wam b/wasm/stepA_mal.wam index f196fabc98..3ce28c063f 100644 --- a/wasm/stepA_mal.wam +++ b/wasm/stepA_mal.wam @@ -9,16 +9,14 @@ ;; EVAL (func $is_pair (param $ast i32) (result i32) - (local $type i32) - (set_local $type ($TYPE $ast)) + (LET $type ($TYPE $ast)) (AND (OR (i32.eq $type (get_global $LIST_T)) (i32.eq $type (get_global $VECTOR_T))) (i32.ne ($VAL0 $ast) 0)) ) (func $QUASIQUOTE (param $ast i32) (result i32) - (local $sym i32 $res i32 $second i32 $third i32) - (set_local $res 0) + (LET $res 0 $sym 0 $second 0 $third 0) (if (i32.eqz ($is_pair $ast)) ;; QQ_QUOTE (then (set_local $sym ($STRING (get_global $SYMBOL_T) "quote")) @@ -64,24 +62,22 @@ (global $mac_stack_top (mut i32) (i32.const -1)) (func $MACROEXPAND (param $orig_ast i32 $env i32) (result i32) - (local $ast i32 $mac i32 $mac_env i64) - (set_global $mac_stack (STATIC_ARRAY 1024)) ;; 256 * 4 - (set_local $ast $orig_ast) - (set_local $mac 0) + (local $mac_env i64) + (LET $ast $orig_ast + $mac 0) + (set_global $mac_stack (STATIC_ARRAY 1024)) ;; 256 * 4, TODO: move to init (block $done (loop $loop - (if (OR (i32.ne ($TYPE $ast) (get_global $LIST_T)) ;; a list - (i32.eqz ($VAL0 $ast)) ;; non-empty - (i32.ne ($TYPE ($MEM_VAL1_ptr $ast)) ;; leading symbol - (get_global $SYMBOL_T))) - (br $done)) + (br_if $done + (OR (i32.ne ($TYPE $ast) (get_global $LIST_T)) ;; a list + (i32.eqz ($VAL0 $ast)) ;; non-empty + (i32.ne ($TYPE ($MEM_VAL1_ptr $ast)) ;; leading symbol + (get_global $SYMBOL_T)))) (set_local $mac_env ($ENV_FIND $env ($MEM_VAL1_ptr $ast))) (set_local $mac (i32.wrap/i64 (i64.shr_u $mac_env (i64.const 32)))) - (if (OR (i32.eqz (i32.wrap/i64 $mac_env)) ;; defined in env - (i32.ne ($TYPE $mac) ;; a macro - (get_global $MACRO_T))) - (then - (br $done))) + (br_if $done (OR (i32.eqz (i32.wrap/i64 $mac_env)) ;; defined in env + (i32.ne ($TYPE $mac) ;; a macro + (get_global $MACRO_T)))) (set_local $ast ($APPLY $mac ($MEM_VAL0_ptr $ast))) ;; PEND_A_LV @@ -97,8 +93,7 @@ (get_global $mac_stack) (i32.mul_s (get_global $mac_stack_top) 4)) $ast))) - (if (get_global $error_type) - (br $done)) + (br_if $done (get_global $error_type)) (br $loop) ) @@ -107,8 +102,8 @@ ) (func $EVAL_AST (param $ast i32 $env i32 $skiplast i32) (result i32) - (local $res i32 $val2 i32 $val3 i32 $type i32 $found i32) - (local $ret i32 $empty i32 $current i32) + (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 + $ret 0 $empty 0 $current 0) (if (get_global $error_type) (return 0)) (set_local $type ($TYPE $ast)) @@ -135,10 +130,10 @@ (block $done (loop $loop ;; check if we are done evaluating the source sequence - (if (i32.eq ($VAL0 $ast) 0) (br $done)) + (br_if $done (i32.eq ($VAL0 $ast) 0)) (if $skiplast - (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0) (br $done))) + (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0))) (if (i32.eq $type (get_global $HASHMAP_T)) (then @@ -194,18 +189,13 @@ ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) (func $EVAL (param $orig_ast i32 $orig_env i32) (result i32) - (local $ast i32 $env i32 $prev_ast i32 $prev_env i32 $res i32 $el i32) - (local $ftype i32 $f_args i32 $f i32 $args i32) - (local $a0 i32 $a0sym i32 $a1 i32 $a2 i32) - (local $err i32) - (local $orig_mac_stack_top i32) - - (set_local $ast $orig_ast) - (set_local $env $orig_env) - (set_local $prev_ast 0) - (set_local $prev_env 0) - (set_local $res 0) - (set_local $orig_mac_stack_top (get_global $mac_stack_top)) + (LET $ast $orig_ast + $env $orig_env + $orig_mac_stack_top (get_global $mac_stack_top) + $prev_ast 0 $prev_env 0 $res 0 $el 0 + $ftype 0 $f_args 0 $f 0 $args 0 + $a0 0 $a0sym 0 $a1 0 $a2 0 + $err 0) (block $EVAL_return (loop $TCO_loop @@ -250,7 +240,7 @@ (set_local $a1 ($MAL_GET_A1 $ast)) (set_local $a2 ($MAL_GET_A2 $ast)) (set_local $res ($EVAL $a2 $env)) - (if (get_global $error_type) (br $EVAL_return)) + (br_if $EVAL_return (get_global $error_type)) ;; set a1 in env to a2 (set_local $res ($ENV_SET $env $a1 $res)) @@ -266,12 +256,11 @@ (block $done (loop $loop - (if (i32.eqz ($VAL0 $a1)) - (br $done)) + (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) - (if (get_global $error_type) (br $done)) + (br_if $done (get_global $error_type)) ;; set key/value in the let environment (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) @@ -318,8 +307,7 @@ (set_local $a2 ($MAL_GET_A2 $ast)) (set_local $res ($EVAL $a2 $env)) ($SET_TYPE $res (get_global $MACRO_T)) - (if (get_global $error_type) - (br $EVAL_return)) + (br_if $EVAL_return (get_global $error_type)) ;; set a1 in env to a2 (set_local $res ($ENV_SET $env $a1 $res)) @@ -336,13 +324,12 @@ (set_local $res ($EVAL $a1 $env)) ;; if there is no error, return - (if (i32.eqz (get_global $error_type)) - (br $EVAL_return)) + (br_if $EVAL_return (i32.eqz (get_global $error_type))) ;; if there is an error and res is set, we need to free it ($RELEASE $res) ;; if there is no catch block then return - (if (i32.eqz ($VAL0 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast)))) - (br $EVAL_return)) + (br_if $EVAL_return + (i32.eqz ($VAL0 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) ;; save the current environment for release (set_local $prev_env $env) @@ -478,10 +465,7 @@ ;; TODO: needs to happen here so self-hosting doesn't leak (block $done (loop $loop - (if (i32.le_s (get_global $mac_stack_top) $orig_mac_stack_top) - (br $done)) -;;($printf_1 "free orig_mac_stack_top: %d\n" $orig_mac_stack_top) -;;($printf_1 "free mac_stack_top: %d\n" (get_global $mac_stack_top)) + (br_if $done (i32.le_s (get_global $mac_stack_top) $orig_mac_stack_top)) ($RELEASE (i32.load (i32.add (get_global $mac_stack) (i32.mul_s (get_global $mac_stack_top) 4)))) @@ -501,10 +485,10 @@ ;; REPL (func $RE (param $line i32 $env i32) (result i32) - (local $mv1 i32 $res i32) - (block $rep_done + (LET $mv1 0 $res 0) + (block $done (set_local $mv1 ($READ $line)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) (set_local $res ($EVAL $mv1 $env)) ) @@ -515,10 +499,10 @@ ) (func $REP (param $line i32 $env i32) (result i32) - (local $mv2 i32 $ms i32) - (block $rep_done + (LET $mv2 0 $ms 0) + (block $done (set_local $mv2 ($RE $line $env)) - (if (get_global $error_type) (br $rep_done)) + (br_if $done (get_global $error_type)) ;; ($PR_MEMORY -1 -1) (set_local $ms ($PRINT $mv2)) @@ -530,10 +514,10 @@ ) (func $main (param $argc i32 $argv i32) (result i32) - (local $line i32 $res i32 $repl_env i32) - ;; argument processing - (local $i i32 $ret i32 $empty i32 $current i32 $tmp i32 $val2 i32) - (set_local $line (STATIC_ARRAY 201)) + (LET $line (STATIC_ARRAY 201) + $res 0 $repl_env 0 + ;; argument processing + $i 0 $ret 0 $empty 0 $current 0 $tmp 0 $val2 0) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -572,7 +556,7 @@ (set_local $i 2) (block $done (loop $loop - (if (i32.ge_u $i $argc) (br $done)) + (br_if $done (i32.ge_u $i $argc)) (set_local $val2 ($STRING (get_global $STRING_T) (i32.load (i32.add $argv (i32.mul_u $i 4))))) diff --git a/wasm/string.wam b/wasm/string.wam index dd2ff3cd04..0ad81c42dd 100644 --- a/wasm/string.wam +++ b/wasm/string.wam @@ -5,8 +5,7 @@ ;; Copy len bytes from src to dst ;; Returns len (func $memmove (param $dst i32 $src i32 $len i32) - (local $idx i32) - (set_local $idx 0) + (LET $idx 0) (loop $copy (i32.store8_u (i32.add $idx $dst) (i32.load8_u (i32.add $idx $src))) @@ -16,8 +15,7 @@ ) (func $strlen (param $str i32) (result i32) - (local $cur i32) - (set_local $cur $str) + (LET $cur $str) (loop $count (if (i32.ne 0 (i32.load8_u $cur)) (then @@ -30,10 +28,9 @@ ;; Based on https://stackoverflow.com/a/25705264/471795 ;; This could be made much more efficient (func $strstr (param $haystack i32 $needle i32) (result i32) - (local $i i32 $needle_len i32 $len i32) - - (set_local $needle_len ($strlen $needle)) - (set_local $len ($strlen $haystack)) + (LET $i 0 + $needle_len ($strlen $needle) + $len ($strlen $haystack)) (if (i32.eq $needle_len 0) (return $haystack)) @@ -55,13 +52,10 @@ ) (func $atoi (param $str i32) (result i32) - (local $acc i32) - (local $i i32) - (local $neg i32) - (local $ch i32) - (set_local $acc 0) - (set_local $i 0) - (set_local $neg 0) + (LET $acc 0 + $i 0 + $neg 0 + $ch 0) (block $done (loop $loop (set_local $ch (i32.load8_u (i32.add $str $i))) @@ -105,8 +99,7 @@ ) (func $strncmp (param $s1 i32 $s2 i32 $len i32) (result i32) - (local $i i32) - (set_local $i 0) + (LET $i 0) (if (i32.eq $len 0) (return 0)) (block $done (loop $loop @@ -136,14 +129,12 @@ $needle0 i32 $replace0 i32 $needle1 i32 $replace1 i32 $needle2 i32 $replace2 i32) (result i32) - (local $needle i32 $replace i32) - (local $haystack_len i32 $needle_len i32 $replace_len i32) - (local $src_str i32 $dst_str i32 $s i32 $found_tmp i32 $found i32) - (local $replace_s i32 $replace_len_s i32 $needle_len_s i32) - - (set_local $haystack_len ($strlen $haystack)) - (set_local $src_str $haystack) - (set_local $dst_str $grass) + (LET $haystack_len ($strlen $haystack) + $src_str $haystack + $dst_str $grass + $s 0 $found_tmp 0 $found 0 + $needle 0 $replace 0 $needle_len 0 $replace_len 0 + $replace_s 0 $replace_len_s 0 $needle_len_s 0) ;; in-place (if (i32.eqz $grass) diff --git a/wasm/types.wam b/wasm/types.wam index 5574c3d3be..c8e33718c0 100644 --- a/wasm/types.wam +++ b/wasm/types.wam @@ -73,9 +73,8 @@ ) (func $EQUAL_Q (param $a i32 $b i32) (result i32) - (local $ta i32 $tb i32) - (set_local $ta ($TYPE $a)) - (set_local $tb ($TYPE $b)) + (LET $ta ($TYPE $a) + $tb ($TYPE $b)) (if (AND (OR (i32.eq $ta (get_global $LIST_T)) (i32.eq $ta (get_global $VECTOR_T))) @@ -138,9 +137,7 @@ ;; Duplicate regular character array string into a Mal string and ;; return the MalVal pointer (func $STRING (param $type i32 $str i32) (result i32) - (local $ms i32) - ;; TODO: assert mv is a string/keyword/symbol - (set_local $ms ($ALLOC_STRING $str ($strlen $str) 1)) + (LET $ms ($ALLOC_STRING $str ($strlen $str) 1)) ($ALLOC_SCALAR $type (i32.sub_u $ms (get_global $string_mem))) ) @@ -148,10 +145,10 @@ ;; mv and return the interned version. If no duplicate is found, ;; return NULL. (func $INTERN_STRING (param $mv i32) (result i32) - (local $res i32 $ms i32 $existing_ms i32 $tmp i32) - (set_local $res 0) - (set_local $ms ($to_MalString $mv)) - (set_local $existing_ms ($FIND_STRING (i32.add $ms 4))) + (LET $res 0 + $ms ($to_MalString $mv) + $existing_ms ($FIND_STRING (i32.add $ms 4)) + $tmp 0) (if (AND $existing_ms (i32.lt_s $existing_ms $ms)) (then (set_local $tmp $mv) @@ -164,16 +161,14 @@ ) (func $STRING_INIT (param $type i32) (result i32) - (local $ms i32) - (set_local $ms ($ALLOC_STRING "" 0 0)) + (LET $ms ($ALLOC_STRING "" 0 0)) ($ALLOC_SCALAR $type (i32.sub_s $ms (get_global $string_mem))) ) (func $STRING_FINALIZE (param $mv i32 $size i32) (result i32) - (local $tmp i32 $ms i32) ;; Check if the new string can be interned. - (set_local $tmp ($INTERN_STRING $mv)) - (set_local $ms ($to_MalString $mv)) + (LET $tmp ($INTERN_STRING $mv) + $ms ($to_MalString $mv)) (if $tmp (then (set_local $mv $tmp)) @@ -198,16 +193,15 @@ ;; sequence functions (func $MAP_LOOP_START (param $type i32) (result i32) - (local $res i32) - (set_local $res (if i32 (i32.eq $type (get_global $LIST_T)) - (get_global $EMPTY_LIST) - (else (if i32 (i32.eq $type (get_global $VECTOR_T)) - (get_global $EMPTY_VECTOR) - (else (if i32 (i32.eq $type (get_global $HASHMAP_T)) - (get_global $EMPTY_HASHMAP) - (else - ($THROW_STR_1 "read_seq invalid type %d" $type) - 0))))))) + (LET $res (if i32 (i32.eq $type (get_global $LIST_T)) + (get_global $EMPTY_LIST) + (else (if i32 (i32.eq $type (get_global $VECTOR_T)) + (get_global $EMPTY_VECTOR) + (else (if i32 (i32.eq $type (get_global $HASHMAP_T)) + (get_global $EMPTY_HASHMAP) + (else + ($THROW_STR_1 "read_seq invalid type %d" $type) + 0))))))) ($INC_REF $res) ) @@ -215,9 +209,8 @@ (func $MAP_LOOP_UPDATE (param $type i32) (param $empty i32) (param $current i32) (param $val2 i32) (param $val3 i32) (result i32) - (local $res i32) + (LET $res ($ALLOC $type $empty $val2 $val3)) - (set_local $res ($ALLOC $type $empty $val2 $val3)) ;; sequence took ownership ($RELEASE $empty) ($RELEASE $val2) @@ -231,7 +224,7 @@ ) (func $FORCE_SEQ_TYPE (param $type i32) (param $mv i32) (result i32) - (local $res i32) + (LET $res 0) ;; if it's already the right type, inc ref cnt and return it (if (i32.eq $type ($TYPE $mv)) (return ($INC_REF $mv))) ;; if it's empty, return the sequence match @@ -247,17 +240,15 @@ (func $LIST2 (param $first i32 $second i32) (result i32) ;; last element is empty list - (local $tmp i32 $res i32) - (set_local $tmp ($LIST (get_global $EMPTY_LIST) $second)) - (set_local $res ($LIST $tmp $first)) + (LET $tmp ($LIST (get_global $EMPTY_LIST) $second) + $res ($LIST $tmp $first)) ($RELEASE $tmp) ;; new list takes ownership of previous $res ) (func $LIST3 (param $first i32 $second i32 $third i32) (result i32) - (local $tmp i32 $res i32) - (set_local $tmp ($LIST2 $second $third)) - (set_local $res ($LIST $tmp $first)) + (LET $tmp ($LIST2 $second $third) + $res ($LIST $tmp $first)) ($RELEASE $tmp) ;; new list takes ownership of previous $res ) @@ -271,8 +262,7 @@ ) (func $COUNT (param $mv i32) (result i32) - (local $cnt i32) - (set_local $cnt 0) + (LET $cnt 0) (block $done (loop $loop (if (i32.eq ($VAL0 $mv) 0) (br $done)) @@ -285,7 +275,7 @@ ) (func $LAST (param $mv i32) (result i32) - (local $cur i32) + (LET $cur 0) ;; TODO: check that actually a list/vector (if (i32.eq ($VAL0 $mv) 0) ;; empty seq, return nil @@ -309,11 +299,10 @@ ;; set after to element following slice (or original) (func $SLICE (param $seq i32) (param $start i32) (param $end i32) (result i64) - (local $idx i32 $res i32 $tmp i32 $last i32) - (set_local $idx 0) - (set_local $res ($INC_REF (get_global $EMPTY_LIST))) - (set_local $last 0) - (set_local $tmp $res) + (LET $idx 0 + $res ($INC_REF (get_global $EMPTY_LIST)) + $last 0 + $tmp $res) ;; advance seq to start (block $done (loop $loop @@ -367,28 +356,25 @@ ) (func $ASSOC1 (param $hm i32 $k i32 $v i32) (result i32) - (local $res i32) - (set_local $res ($ALLOC (get_global $HASHMAP_T) $hm $k $v)) + (LET $res ($ALLOC (get_global $HASHMAP_T) $hm $k $v)) ;; we took ownership of previous release ($RELEASE $hm) $res ) (func $ASSOC1_S (param $hm i32 $k i32 $v i32) (result i32) - (local $kmv i32 $res i32) - (set_local $kmv ($STRING (get_global $STRING_T) $k)) - (set_local $res ($ASSOC1 $hm $kmv $v)) + (LET $kmv ($STRING (get_global $STRING_T) $k) + $res ($ASSOC1 $hm $kmv $v)) ;; map took ownership of key ($RELEASE $kmv) $res ) (func $HASHMAP_GET (param $hm i32) (param $key_mv i32) (result i64) - (local $res i32 $found i32 $key i32 $test_key_mv i32) - - (set_local $key ($to_String $key_mv)) - (set_local $found 0) - + (LET $key ($to_String $key_mv) + $found 0 + $res 0 + $test_key_mv 0) (block $done (loop $loop From f2858819cce56f6e2e3ea1852a46ee5d3ebc1254 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 7 Dec 2018 01:11:05 -0600 Subject: [PATCH 0718/2308] wasm: add line count rules. --- wasm/Makefile | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/wasm/Makefile b/wasm/Makefile index c4bb8544fc..6b978bbcbf 100644 --- a/wasm/Makefile +++ b/wasm/Makefile @@ -25,3 +25,13 @@ step7_quote.wasm step8_macros.wasm step9_try.wasm stepA_mal.wasm: $(STEP4_DEPS) clean: rm -f *.wast *.wasm + +.PHONY: stats tests + +stats: $(STEP4_DEPS) stepA_mal.wam + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(filter-out $(STEP1_DEPS),$(STEP4_DEPS)) stepA_mal.wam + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" + From 0c62f14e6adb12f0d31d77429c0971eb9b6d623f Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 8 Dec 2018 15:35:49 -0600 Subject: [PATCH 0719/2308] wasm: drop unnecessary signed suffixes. Apparently some of the assemblers ignored these sign indicators and so this worked in the past. --- wasm/core.wam | 6 +++--- wasm/debug.wam | 26 ++++++++++++------------- wasm/env.wam | 2 +- wasm/mem.wam | 34 ++++++++++++++++----------------- wasm/platform_libc.wam | 8 ++++---- wasm/platform_os.wam | 2 +- wasm/printer.wam | 2 +- wasm/printf.wam | 40 +++++++++++++++++++-------------------- wasm/reader.wam | 12 ++++++------ wasm/step1_read_print.wam | 2 +- wasm/step2_eval.wam | 4 ++-- wasm/step3_env.wam | 4 ++-- wasm/step6_file.wam | 2 +- wasm/step7_quote.wam | 2 +- wasm/step8_macros.wam | 10 +++++----- wasm/step9_try.wam | 10 +++++----- wasm/stepA_mal.wam | 10 +++++----- wasm/string.wam | 22 ++++++++++----------- wasm/types.wam | 14 +++++++------- 19 files changed, 106 insertions(+), 106 deletions(-) diff --git a/wasm/core.wam b/wasm/core.wam index 12d2090849..ef2da7f281 100644 --- a/wasm/core.wam +++ b/wasm/core.wam @@ -170,11 +170,11 @@ ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) (func $subtract (param $args i32) (result i32) ($INTEGER - (i32.sub_s ($VAL0 ($MEM_VAL1_ptr $args)) + (i32.sub ($VAL0 ($MEM_VAL1_ptr $args)) ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) (func $multiply (param $args i32) (result i32) ($INTEGER - (i32.mul_s ($VAL0 ($MEM_VAL1_ptr $args)) + (i32.mul ($VAL0 ($MEM_VAL1_ptr $args)) ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) (func $divide (param $args i32) (result i32) ($INTEGER @@ -443,7 +443,7 @@ (set_local $f_args ($INC_REF ($MEM_VAL1_ptr $rest_args)))))) (else ;; 1 or more intermediate args - (set_local $last_sl ($SLICE $rest_args 0 (i32.sub_s $rest_count 1))) + (set_local $last_sl ($SLICE $rest_args 0 (i32.sub $rest_count 1))) (set_local $f_args (i32.wrap/i64 $last_sl)) (set_local $last (i32.wrap/i64 (i64.shr_u $last_sl (i64.const 32)))) ;; release the terminator of the new list (we skip over it) diff --git a/wasm/debug.wam b/wasm/debug.wam index 6afbd5bedb..19e5c10f6f 100644 --- a/wasm/debug.wam +++ b/wasm/debug.wam @@ -8,7 +8,7 @@ (func $CHECK_FREE_LIST (result i32) (LET $first (i32.add (get_global $mem) - (i32.mul_u (get_global $mem_free_list) 4)) + (i32.mul (get_global $mem_free_list) 4)) $count 0) (block $done @@ -16,10 +16,10 @@ (br_if $done (i32.ge_s $first (i32.add (get_global $mem) - (i32.mul_u (get_global $mem_unused_start) + (i32.mul (get_global $mem_unused_start) 4)))) (set_local $count (i32.add $count ($MalVal_size $first))) - (set_local $first (i32.add (get_global $mem) (i32.mul_u 4 ($VAL0 $first)))) + (set_local $first (i32.add (get_global $mem) (i32.mul 4 ($VAL0 $first)))) (br $loop) ) ) @@ -27,8 +27,8 @@ ) (func $PR_MEMORY_SUMMARY_SMALL - (LET $free (i32.sub_s (get_global $MEM_SIZE) - (i32.mul_u (get_global $mem_unused_start) 4)) + (LET $free (i32.sub (get_global $MEM_SIZE) + (i32.mul (get_global $mem_unused_start) 4)) $free_list_count ($CHECK_FREE_LIST) $mv (get_global $NIL) $mem_ref_count 0) @@ -37,22 +37,22 @@ (loop $loop (br_if $done (i32.ge_s $mv (i32.add (get_global $mem) - (i32.mul_u (get_global $mem_unused_start) + (i32.mul (get_global $mem_unused_start) 4)))) (if (i32.ne ($TYPE $mv) (get_global $FREE_T)) (set_local $mem_ref_count (i32.add $mem_ref_count (i32.shr_u (i32.load $mv) 5)))) - (set_local $mv (i32.add $mv (i32.mul_u 4 ($MalVal_size $mv)))) + (set_local $mv (i32.add $mv (i32.mul 4 ($MalVal_size $mv)))) (br $loop) ) ) ($printf_3 "Free: %d, Values: %d (refs: %d), Emptys: " $free - (i32.sub_s - (i32.sub_s (get_global $mem_unused_start) 1) + (i32.sub + (i32.sub (get_global $mem_unused_start) 1) $free_list_count) $mem_ref_count) (set_local $mv (get_global $NIL)) @@ -210,9 +210,9 @@ (LET $ms 0 $idx 0) ($printf_2 "String - showing %d -> %d:\n" - $start (i32.sub_s (get_global $string_mem_next) + $start (i32.sub (get_global $string_mem_next) (get_global $string_mem))) - (if (i32.le_s (i32.sub_s (get_global $string_mem_next) + (if (i32.le_s (i32.sub (get_global $string_mem_next) (get_global $string_mem)) $start) (then ($print " ---\n")) @@ -221,7 +221,7 @@ (block $done (loop $loop (br_if $done (i32.ge_u $ms (get_global $string_mem_next))) - (set_local $idx (i32.sub_u $ms (get_global $string_mem))) + (set_local $idx (i32.sub $ms (get_global $string_mem))) (if (i32.ge_s $idx $start) ($printf_4 "%4d: refs %2d, size %2d >> '%s'\n" $idx @@ -241,7 +241,7 @@ (if (i32.lt_s $start 0) (then (set_local $start (get_global $mem_user_start)) - (set_local $string_start (i32.sub_s (get_global $string_mem_user_start) + (set_local $string_start (i32.sub (get_global $string_mem_user_start) (get_global $string_mem))))) (if (i32.lt_s $end 0) (set_local $end (get_global $mem_unused_start))) diff --git a/wasm/env.wam b/wasm/env.wam index 6515375dca..37ff695446 100644 --- a/wasm/env.wam +++ b/wasm/env.wam @@ -85,7 +85,7 @@ ;; combine res/env as hi 32/low 32 of i64 (i64.or - (i64.shl_u (i64.extend_u/i32 $res) (i64.const 32)) + (i64.shl (i64.extend_u/i32 $res) (i64.const 32)) (i64.extend_u/i32 $env)) ) diff --git a/wasm/mem.wam b/wasm/mem.wam index 67d0dc3325..9524f934bb 100644 --- a/wasm/mem.wam +++ b/wasm/mem.wam @@ -30,19 +30,19 @@ (func $MEM_VAL0_ptr (param $mv i32) (result i32) (i32.add (get_global $mem) - (i32.mul_u (i32.load (i32.add $mv 4)) 4))) + (i32.mul (i32.load (i32.add $mv 4)) 4))) (func $MEM_VAL1_ptr (param $mv i32) (result i32) (i32.add (get_global $mem) - (i32.mul_u (i32.load (i32.add $mv 8)) 4))) + (i32.mul (i32.load (i32.add $mv 8)) 4))) (func $MEM_VAL2_ptr (param $mv i32) (result i32) (i32.add (get_global $mem) - (i32.mul_u (i32.load (i32.add $mv 12)) 4))) + (i32.mul (i32.load (i32.add $mv 12)) 4))) ;; Returns the memory index mem of mv ;; Will usually be used with a load or store by the caller (func $IDX (param $mv i32) (result i32) ;; MalVal memory 64 bit (2 * i32) aligned - (i32.div_u (i32.sub_u $mv (get_global $mem)) 4)) + (i32.div_u (i32.sub $mv (get_global $mem)) 4)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -50,7 +50,7 @@ (func $MalVal_ptr (param $mv_idx i32) (result i32) ;; MalVal memory 64 bit (2 * i32) aligned ;;; mem[mv_idx].refcnt_type - (i32.add (get_global $mem) (i32.mul_u $mv_idx 4))) + (i32.add (get_global $mem) (i32.mul $mv_idx 4))) ;; Returns the address of 'mem[mv_idx].refcnt_type' (func $MalVal_refcnt_type (param $mv_idx i32) (result i32) @@ -77,7 +77,7 @@ ;; Will usually be used with a load or store by the caller (func $MalVal_val_ptr (param $mv_idx i32 $val i32) (result i32) (i32.add (i32.add ($MalVal_ptr $mv_idx) 4) - (i32.mul_u $val 4))) + (i32.mul $val 4))) ;; Returns the value of 'mem[mv_idx].val[val]' (func $MalVal_val (param $mv_idx i32 $val i32) (result i32) @@ -294,7 +294,7 @@ ;; decrease reference count by one (i32.store ($MalVal_ptr $idx) - (i32.sub_u ($MalVal_refcnt_type $idx) 32)) + (i32.sub ($MalVal_refcnt_type $idx) 32)) ;; nil, false, true, empty sequences (if (i32.le_u $mv (get_global $EMPTY_HASHMAP)) @@ -362,7 +362,7 @@ ;; set type(FREE/15) and size ;;; mv->refcnt_type = size*32 + FREE_T - (i32.store $mv (i32.add (i32.mul_u $size 32) (get_global $FREE_T))) + (i32.store $mv (i32.add (i32.mul $size 32) (get_global $FREE_T))) (i32.store ($MalVal_val_ptr $idx 0) (get_global $mem_free_list)) (set_global $mem_free_list $idx) (if (i32.ge_u $size 3) (i32.store ($MalVal_val_ptr $idx 1) 0)) @@ -398,14 +398,14 @@ (if $ms (then ;;; ms->refcnt += 1 - (i32.store16_u $ms (i32.add (i32.load16_u $ms) 1)) + (i32.store16 $ms (i32.add (i32.load16_u $ms) 1)) (return $ms))))) ;; no existing matching string so create a new one (set_local $ms (get_global $string_mem_next)) - (i32.store16_u $ms 1) + (i32.store16 $ms 1) ;;; ms->size = sizeof(MalString)+size+1 - (i32.store16_u offset=2 $ms (i32.add (i32.add 4 $size) 1)) + (i32.store16 offset=2 $ms (i32.add (i32.add 4 $size) 1)) ($memmove (i32.add $ms 4) $str (i32.add $size 1)) ;;; string_mem_next = (void *)ms + ms->size (set_global $string_mem_next @@ -422,7 +422,7 @@ (if (i32.le_s (i32.load16_u $ms) 0) (then ($printf_2 "Release of already free string: %d (0x%x)\n" - (i32.sub_s $ms (get_global $string_mem)) $ms) + (i32.sub $ms (get_global $string_mem)) $ms) ($fatal 1 ""))) ;;; size = ms->size @@ -431,7 +431,7 @@ (set_local $next (i32.add $ms $size)) ;;; ms->refcnt -= 1 - (i32.store16_u $ms (i32.sub_u (i32.load16_u $ms) 1)) + (i32.store16 $ms (i32.sub (i32.load16_u $ms) 1)) (if (i32.eqz (i32.load16_u $ms)) (then @@ -440,12 +440,12 @@ ;; If no more references to this string then free it up by ;; shifting up every string afterwards to fill the gap ;; (splice). - ($memmove $ms $next (i32.sub_s (get_global $string_mem_next) + ($memmove $ms $next (i32.sub (get_global $string_mem_next) $next)) ;; Scan the mem values for string types after the freed ;; string and shift their indexes by size - (set_local $ms_idx (i32.sub_s $ms (get_global $string_mem))) + (set_local $ms_idx (i32.sub $ms (get_global $string_mem))) (set_local $idx ($IDX (get_global $EMPTY_HASHMAP))) (loop $loop (set_local $mv ($MalVal_ptr $idx)) @@ -453,13 +453,13 @@ (if (AND (i32.gt_s ($VAL0 $mv) $ms_idx) (OR (i32.eq $type (get_global $STRING_T)) (i32.eq $type (get_global $SYMBOL_T)))) - (i32.store ($VAL0_ptr $mv) (i32.sub_s ($VAL0 $mv) $size))) + (i32.store ($VAL0_ptr $mv) (i32.sub ($VAL0 $mv) $size))) (set_local $idx (i32.add $idx ($MalVal_size $mv))) (br_if $loop (i32.lt_s $idx (get_global $mem_unused_start))) ))) (set_global $string_mem_next - (i32.sub_s (get_global $string_mem_next) $size)))) + (i32.sub (get_global $string_mem_next) $size)))) ) ) diff --git a/wasm/platform_libc.wam b/wasm/platform_libc.wam index 7965b6ca58..0327f52c7f 100644 --- a/wasm/platform_libc.wam +++ b/wasm/platform_libc.wam @@ -46,7 +46,7 @@ (set_local $len ($strlen $line)) ($memmove $buf $line $len) ($lib_free $line))) - (i32.store8_u (i32.add $buf $len) (CHR "\x00")) + (i32.store8 (i32.add $buf $len) (CHR "\x00")) (return (if i32 $line 1 0)) ) @@ -75,7 +75,7 @@ ($printf_1 "ERROR: slurp failed to stat '%s'\n" $path) (return 0))) ;; Add null to string - (i32.store8_u (i32.add $buf $st_size) 0) + (i32.store8 (i32.add $buf $st_size) 0) (i32.add 1 $st_size) ) @@ -91,9 +91,9 @@ (set_local $secs (i32.load (i32.add $tv (get_global $TV_SEC_OFFSET)))) ;; subtract 30 years to make sure secs is positive and can be ;; multiplied by 1000 - (set_local $secs (i32.sub_s $secs 0x38640900)) + (set_local $secs (i32.sub $secs 0x38640900)) (set_local $usecs (i32.load (i32.add $tv (get_global $TV_USEC_OFFSET)))) - (set_local $msecs (i32.add (i32.mul_u $secs 1000) + (set_local $msecs (i32.add (i32.mul $secs 1000) (i32.div_u $usecs 1000))) $msecs ) diff --git a/wasm/platform_os.wam b/wasm/platform_os.wam index 4c67a44b8b..6615ce7551 100644 --- a/wasm/platform_os.wam +++ b/wasm/platform_os.wam @@ -37,7 +37,7 @@ (func $read_file (param $path i32 $buf i32) (result i32) (LET $size ($lib_read_file $path $buf)) ;; Add null to string - (i32.store8_u (i32.add $buf $size) 0) + (i32.store8 (i32.add $buf $size) 0) (i32.add $size 1) ) diff --git a/wasm/printer.wam b/wasm/printer.wam index ebcddfd924..7aea5b412b 100644 --- a/wasm/printer.wam +++ b/wasm/printer.wam @@ -164,7 +164,7 @@ (else (set_local $res_str ($pr_str_val $res_str $mv $print_readably)))) - (set_local $res ($STRING_FINALIZE $res (i32.sub_s $res_str ($to_String $res)))) + (set_local $res ($STRING_FINALIZE $res (i32.sub $res_str ($to_String $res)))) $res ) diff --git a/wasm/printf.wam b/wasm/printf.wam index ea6a64a057..d1bd8d8443 100644 --- a/wasm/printf.wam +++ b/wasm/printf.wam @@ -51,7 +51,7 @@ (func $_sprintdigit (param $str i32) (param $num i32) (param $base i32) (LET $n (i32.rem_u $num $base) $ch (if (result i32) (i32.lt_u $n 10) 48 55)) - (i32.store8_u $str (i32.add $n $ch)) + (i32.store8 $str (i32.add $n $ch)) ) ;; TODO: add max buf length (i.e. snprintnum) @@ -63,24 +63,24 @@ (if (AND (i32.lt_s $val 0) (i32.eq $radix 10)) (then (set_local $neg 1) - (set_local $val (i32.sub_s 0 $val)))) + (set_local $val (i32.sub 0 $val)))) ;; Calculate smallest to most significant digit (loop $loop (set_local $digit (i32.rem_u $val $radix)) - (i32.store8_u $pbuf (if i32 (i32.lt_u $digit 10) + (i32.store8 $pbuf (if i32 (i32.lt_u $digit 10) (i32.add (CHR "0") $digit) - (i32.sub_u (i32.add (CHR "A") $digit) 10))) + (i32.sub (i32.add (CHR "A") $digit) 10))) (set_local $pbuf (i32.add $pbuf 1)) (set_local $val (i32.div_u $val $radix)) (br_if $loop (i32.gt_u $val 0)) ) - (set_local $i (i32.sub_u $pbuf $buf)) + (set_local $i (i32.sub $pbuf $buf)) (block $done (loop $loop (br_if $done (i32.ge_u $i $pad_cnt)) - (i32.store8_u $pbuf $pad_char) + (i32.store8 $pbuf $pad_char) (set_local $pbuf (i32.add $pbuf 1)) (set_local $i (i32.add $i 1)) (br $loop) @@ -89,22 +89,22 @@ (if $neg (then - (i32.store8_u $pbuf (CHR "-")) + (i32.store8 $pbuf (CHR "-")) (set_local $pbuf (i32.add $pbuf 1)))) - (i32.store8_u $pbuf (CHR "\x00")) + (i32.store8 $pbuf (CHR "\x00")) ;; now reverse it - (set_local $len (i32.sub_u $pbuf $buf)) + (set_local $len (i32.sub $pbuf $buf)) (set_local $i 0) (block $done (loop $loop (br_if $done (i32.ge_u $i (i32.div_u $len 2))) (set_local $j (i32.load8_u (i32.add $buf $i))) - (set_local $k (i32.add $buf (i32.sub_u (i32.sub_u $len $i) 1))) - (i32.store8_u (i32.add $buf $i) (i32.load8_u $k)) - (i32.store8_u $k $j) + (set_local $k (i32.add $buf (i32.sub (i32.sub $len $i) 1))) + (i32.store8 (i32.add $buf $i) (i32.load8_u $k)) + (i32.store8 $k $j) (set_local $i (i32.add $i 1)) (br $loop) ) @@ -149,7 +149,7 @@ (if (i32.ne $ch (CHR "%")) (then ;; TODO: check buffer length - (i32.store8_u $pstr $ch) + (i32.store8 $pstr $ch) (set_local $pstr (i32.add 1 $pstr)) (br $loop))) @@ -172,9 +172,9 @@ (set_local $fmt (i32.add 1 $fmt)) (br_if $done (i32.eqz $ch)))) (loop $loop - (set_local $pad_cnt (i32.mul_s $pad_cnt 10)) + (set_local $pad_cnt (i32.mul $pad_cnt 10)) (set_local $pad_cnt (i32.add $pad_cnt - (i32.sub_s $ch (CHR "0")))) + (i32.sub $ch (CHR "0")))) (set_local $ch (i32.load8_u $fmt)) (set_local $fmt (i32.add 1 $fmt)) (br_if $loop (AND (i32.ge_s $ch (CHR "0")) @@ -193,9 +193,9 @@ (block $done (loop $loop (br_if $done (i32.le_s $pad_cnt $len)) - (i32.store8_u $pstr (CHR " ")) + (i32.store8 $pstr (CHR " ")) (set_local $pstr (i32.add $pstr 1)) - (set_local $pad_cnt (i32.sub_s $pad_cnt 1)) + (set_local $pad_cnt (i32.sub $pad_cnt 1)) (br $loop) ) ) @@ -203,11 +203,11 @@ (set_local $pstr (i32.add $pstr $len))) (else (if (i32.eq (CHR "c") $ch) (then - (i32.store8_u $pstr $v) + (i32.store8 $pstr $v) (set_local $pstr (i32.add $pstr 1))) (else (if (i32.eq (CHR "%") $ch) (then - (i32.store8_u $pstr (CHR "%")) + (i32.store8 $pstr (CHR "%")) (set_local $pstr (i32.add $pstr 1)) (br $loop)) ;; don't increase vidx (else @@ -219,7 +219,7 @@ ) ) - (i32.store8_u $pstr (CHR "\x00")) + (i32.store8 $pstr (CHR "\x00")) $pstr ) diff --git a/wasm/reader.wam b/wasm/reader.wam index 0894928b50..45715caf2c 100644 --- a/wasm/reader.wam +++ b/wasm/reader.wam @@ -26,7 +26,7 @@ (func $skip_to_eol (param $str i32) (result i32) (LET $found 0 - $c (i32.load8_c (i32.add $str (get_global $read_index)))) + $c (i32.load8_u (i32.add $str (get_global $read_index)))) (if (i32.eq $c (CHR ";")) (then (set_local $found 1) @@ -68,7 +68,7 @@ (set_global $read_index (i32.add (get_global $read_index) 1)) ;; read first character ;;; token[token_index++] = c - (i32.store8_u (i32.add (get_global $token_buf) $token_index) $c) + (i32.store8 (i32.add (get_global $token_buf) $token_index) $c) (set_local $token_index (i32.add $token_index 1)) ;; single/double character token (if (OR (i32.eq $c (CHR "(")) @@ -113,7 +113,7 @@ (i32.eq $c (CHR "\n")))))) ;; read next character ;;; token[token_index++] = str[(*index)++] - (i32.store8_u (i32.add (get_global $token_buf) $token_index) + (i32.store8 (i32.add (get_global $token_buf) $token_index) (i32.load8_u (i32.add $str (get_global $read_index)))) (set_local $token_index (i32.add $token_index 1)) @@ -139,7 +139,7 @@ ))) ;;; token[token_index] = '\0' - (i32.store8_u (i32.add (get_global $token_buf) $token_index) 0) + (i32.store8 (i32.add (get_global $token_buf) $token_index) 0) (get_global $token_buf) ) @@ -246,7 +246,7 @@ (return ($INTEGER ($atoi $tok)))) (else (if (i32.eq $c0 (CHR ":")) (then - (i32.store8_u $tok (CHR "\x7f")) + (i32.store8 $tok (CHR "\x7f")) (return ($STRING (get_global $STRING_T) $tok))) (else (if (i32.eq $c0 (CHR "\"")) (then @@ -258,7 +258,7 @@ (else ;; unescape backslashes, quotes, and newlines ;; remove the trailing quote - (i32.store8_u (i32.add $tok $slen) (CHR "\x00")) + (i32.store8 (i32.add $tok $slen) (CHR "\x00")) (set_local $tok (i32.add $tok 1)) (drop ($REPLACE3 0 $tok "\\\"" "\"" diff --git a/wasm/step1_read_print.wam b/wasm/step1_read_print.wam index 8b5e5d60b1..ad9c272cc6 100644 --- a/wasm/step1_read_print.wam +++ b/wasm/step1_read_print.wam @@ -48,7 +48,7 @@ ;; ($PR_MEMORY_RAW ;; (get_global $mem) (i32.add (get_global $mem) -;; (i32.mul_u (get_global $mem_unused_start) 4))) +;; (i32.mul (get_global $mem_unused_start) 4))) (drop ($STRING (get_global $STRING_T) "uvw")) (drop ($STRING (get_global $STRING_T) "xyz")) diff --git a/wasm/step2_eval.wam b/wasm/step2_eval.wam index 82f772ef5d..ee169e6e51 100644 --- a/wasm/step2_eval.wam +++ b/wasm/step2_eval.wam @@ -174,11 +174,11 @@ ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) (func $subtract (param $args i32) (result i32) ($INTEGER - (i32.sub_s ($VAL0 ($MEM_VAL1_ptr $args)) + (i32.sub ($VAL0 ($MEM_VAL1_ptr $args)) ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) (func $multiply (param $args i32) (result i32) ($INTEGER - (i32.mul_s ($VAL0 ($MEM_VAL1_ptr $args)) + (i32.mul ($VAL0 ($MEM_VAL1_ptr $args)) ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) (func $divide (param $args i32) (result i32) ($INTEGER diff --git a/wasm/step3_env.wam b/wasm/step3_env.wam index 6095459579..5d87b08a90 100644 --- a/wasm/step3_env.wam +++ b/wasm/step3_env.wam @@ -219,11 +219,11 @@ ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) (func $subtract (param $args i32) (result i32) ($INTEGER - (i32.sub_s ($VAL0 ($MEM_VAL1_ptr $args)) + (i32.sub ($VAL0 ($MEM_VAL1_ptr $args)) ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) (func $multiply (param $args i32) (result i32) ($INTEGER - (i32.mul_s ($VAL0 ($MEM_VAL1_ptr $args)) + (i32.mul ($VAL0 ($MEM_VAL1_ptr $args)) ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) (func $divide (param $args i32) (result i32) ($INTEGER diff --git a/wasm/step6_file.wam b/wasm/step6_file.wam index b41bf4ff16..902626c96b 100644 --- a/wasm/step6_file.wam +++ b/wasm/step6_file.wam @@ -363,7 +363,7 @@ (br_if $done (i32.ge_u $i $argc)) (set_local $val2 ($STRING (get_global $STRING_T) - (i32.load (i32.add $argv (i32.mul_u $i 4))))) + (i32.load (i32.add $argv (i32.mul $i 4))))) ;; MAP_LOOP_UPDATE (set_local $res ($MAP_LOOP_UPDATE diff --git a/wasm/step7_quote.wam b/wasm/step7_quote.wam index 49859ecfa1..5a699b08e8 100644 --- a/wasm/step7_quote.wam +++ b/wasm/step7_quote.wam @@ -425,7 +425,7 @@ (br_if $done (i32.ge_u $i $argc)) (set_local $val2 ($STRING (get_global $STRING_T) - (i32.load (i32.add $argv (i32.mul_u $i 4))))) + (i32.load (i32.add $argv (i32.mul $i 4))))) ;; MAP_LOOP_UPDATE (set_local $res ($MAP_LOOP_UPDATE diff --git a/wasm/step8_macros.wam b/wasm/step8_macros.wam index c7ad71fa86..c45f2f3a8e 100644 --- a/wasm/step8_macros.wam +++ b/wasm/step8_macros.wam @@ -87,11 +87,11 @@ (then (set_global $mac_stack_top (i32.add (get_global $mac_stack_top) 1)) - (if (i32.ge_s (i32.mul_s (get_global $mac_stack_top) 4) 1024) ;; 256 * 4 + (if (i32.ge_s (i32.mul (get_global $mac_stack_top) 4) 1024) ;; 256 * 4 ($fatal 7 "Exhausted mac_stack!\n")) (i32.store (i32.add (get_global $mac_stack) - (i32.mul_s (get_global $mac_stack_top) 4)) + (i32.mul (get_global $mac_stack_top) 4)) $ast))) (br_if $done (get_global $error_type)) @@ -421,9 +421,9 @@ (br_if $done (i32.le_s (get_global $mac_stack_top) $orig_mac_stack_top)) ($RELEASE (i32.load (i32.add (get_global $mac_stack) - (i32.mul_s (get_global $mac_stack_top) 4)))) + (i32.mul (get_global $mac_stack_top) 4)))) (set_global $mac_stack_top - (i32.sub_s (get_global $mac_stack_top) 1)) + (i32.sub (get_global $mac_stack_top) 1)) (br $loop) ) ) @@ -510,7 +510,7 @@ (br_if $done (i32.ge_u $i $argc)) (set_local $val2 ($STRING (get_global $STRING_T) - (i32.load (i32.add $argv (i32.mul_u $i 4))))) + (i32.load (i32.add $argv (i32.mul $i 4))))) ;; MAP_LOOP_UPDATE (set_local $res ($MAP_LOOP_UPDATE diff --git a/wasm/step9_try.wam b/wasm/step9_try.wam index 8021724207..d7636b6b39 100644 --- a/wasm/step9_try.wam +++ b/wasm/step9_try.wam @@ -87,11 +87,11 @@ (then (set_global $mac_stack_top (i32.add (get_global $mac_stack_top) 1)) - (if (i32.ge_s (i32.mul_s (get_global $mac_stack_top) 4) 1024) ;; 256 * 4 + (if (i32.ge_s (i32.mul (get_global $mac_stack_top) 4) 1024) ;; 256 * 4 ($fatal 7 "Exhausted mac_stack!\n")) (i32.store (i32.add (get_global $mac_stack) - (i32.mul_s (get_global $mac_stack_top) 4)) + (i32.mul (get_global $mac_stack_top) 4)) $ast))) (br_if $done (get_global $error_type)) @@ -468,9 +468,9 @@ (br_if $done (i32.le_s (get_global $mac_stack_top) $orig_mac_stack_top)) ($RELEASE (i32.load (i32.add (get_global $mac_stack) - (i32.mul_s (get_global $mac_stack_top) 4)))) + (i32.mul (get_global $mac_stack_top) 4)))) (set_global $mac_stack_top - (i32.sub_s (get_global $mac_stack_top) 1)) + (i32.sub (get_global $mac_stack_top) 1)) (br $loop) ) ) @@ -557,7 +557,7 @@ (br_if $done (i32.ge_u $i $argc)) (set_local $val2 ($STRING (get_global $STRING_T) - (i32.load (i32.add $argv (i32.mul_u $i 4))))) + (i32.load (i32.add $argv (i32.mul $i 4))))) ;; MAP_LOOP_UPDATE (set_local $res ($MAP_LOOP_UPDATE diff --git a/wasm/stepA_mal.wam b/wasm/stepA_mal.wam index 3ce28c063f..6aa05c94da 100644 --- a/wasm/stepA_mal.wam +++ b/wasm/stepA_mal.wam @@ -87,11 +87,11 @@ (then (set_global $mac_stack_top (i32.add (get_global $mac_stack_top) 1)) - (if (i32.ge_s (i32.mul_s (get_global $mac_stack_top) 4) 1024) ;; 256 * 4 + (if (i32.ge_s (i32.mul (get_global $mac_stack_top) 4) 1024) ;; 256 * 4 ($fatal 7 "Exhausted mac_stack!\n")) (i32.store (i32.add (get_global $mac_stack) - (i32.mul_s (get_global $mac_stack_top) 4)) + (i32.mul (get_global $mac_stack_top) 4)) $ast))) (br_if $done (get_global $error_type)) @@ -468,9 +468,9 @@ (br_if $done (i32.le_s (get_global $mac_stack_top) $orig_mac_stack_top)) ($RELEASE (i32.load (i32.add (get_global $mac_stack) - (i32.mul_s (get_global $mac_stack_top) 4)))) + (i32.mul (get_global $mac_stack_top) 4)))) (set_global $mac_stack_top - (i32.sub_s (get_global $mac_stack_top) 1)) + (i32.sub (get_global $mac_stack_top) 1)) (br $loop) ) ) @@ -559,7 +559,7 @@ (br_if $done (i32.ge_u $i $argc)) (set_local $val2 ($STRING (get_global $STRING_T) - (i32.load (i32.add $argv (i32.mul_u $i 4))))) + (i32.load (i32.add $argv (i32.mul $i 4))))) ;; MAP_LOOP_UPDATE (set_local $res ($MAP_LOOP_UPDATE diff --git a/wasm/string.wam b/wasm/string.wam index 0ad81c42dd..28190a858e 100644 --- a/wasm/string.wam +++ b/wasm/string.wam @@ -7,7 +7,7 @@ (func $memmove (param $dst i32 $src i32 $len i32) (LET $idx 0) (loop $copy - (i32.store8_u (i32.add $idx $dst) + (i32.store8 (i32.add $idx $dst) (i32.load8_u (i32.add $idx $src))) (set_local $idx (i32.add 1 $idx)) (br_if $copy (i32.lt_u $idx $len)) @@ -22,7 +22,7 @@ (set_local $cur (i32.add $cur 1)) (br $count))) ) - (i32.sub_u $cur $str) + (i32.sub $cur $str) ) ;; Based on https://stackoverflow.com/a/25705264/471795 @@ -37,7 +37,7 @@ (set_local $i 0) (block $done (loop $loop - (if (i32.gt_s $i (i32.sub_s $len $needle_len)) (br $done)) + (if (i32.gt_s $i (i32.sub $len $needle_len)) (br $done)) (if (AND (i32.eq (i32.load8_u $haystack 0) (i32.load8_u $needle 0)) @@ -68,13 +68,13 @@ (then (set_local $neg 1)) (else - (set_local $acc (i32.add (i32.mul_u $acc 10) - (i32.sub_u $ch (CHR "0")))))) + (set_local $acc (i32.add (i32.mul $acc 10) + (i32.sub $ch (CHR "0")))))) (br $loop) ) ) (if i32 $neg - (then (i32.sub_s 0 $acc)) + (then (i32.sub 0 $acc)) (else $acc)) ) @@ -163,7 +163,7 @@ (block $done1 (loop $loop1 - (if (i32.ge_s (i32.sub_s $src_str $haystack) $haystack_len) + (if (i32.ge_s (i32.sub $src_str $haystack) $haystack_len) (br $done1)) ;; Find the earliest match @@ -192,8 +192,8 @@ ) (if (i32.eqz $found) (br $done1)) ;; copy before the match - ($memmove $dst_str $src_str (i32.add (i32.sub_s $found $src_str) 1)) - (set_local $dst_str (i32.add $dst_str (i32.sub_s $found $src_str))) + ($memmove $dst_str $src_str (i32.add (i32.sub $found $src_str) 1)) + (set_local $dst_str (i32.add $dst_str (i32.sub $found $src_str))) ;; add the replace string ($memmove $dst_str $replace_s (i32.add $replace_len_s 1)) (set_local $dst_str (i32.add $dst_str $replace_len_s)) @@ -206,9 +206,9 @@ ;; Copy the left-over ($memmove $dst_str $src_str ($strlen $src_str)) (set_local $dst_str (i32.add $dst_str ($strlen $src_str))) - (i32.store8_u $dst_str (CHR "\x00")) + (i32.store8 $dst_str (CHR "\x00")) - (i32.sub_s $dst_str $grass) + (i32.sub $dst_str $grass) ) ) diff --git a/wasm/types.wam b/wasm/types.wam index c8e33718c0..b432efde42 100644 --- a/wasm/types.wam +++ b/wasm/types.wam @@ -138,7 +138,7 @@ ;; return the MalVal pointer (func $STRING (param $type i32 $str i32) (result i32) (LET $ms ($ALLOC_STRING $str ($strlen $str) 1)) - ($ALLOC_SCALAR $type (i32.sub_u $ms (get_global $string_mem))) + ($ALLOC_SCALAR $type (i32.sub $ms (get_global $string_mem))) ) ;; Find first duplicate (internet) of mv. If one is found, free up @@ -153,16 +153,16 @@ (then (set_local $tmp $mv) (set_local $res ($ALLOC_SCALAR (get_global $STRING_T) - (i32.sub_s $existing_ms + (i32.sub $existing_ms (get_global $string_mem)))) - (i32.store16_u $existing_ms (i32.add (i32.load16_u $existing_ms) 1)) + (i32.store16 $existing_ms (i32.add (i32.load16_u $existing_ms) 1)) ($RELEASE $tmp))) $res ) (func $STRING_INIT (param $type i32) (result i32) (LET $ms ($ALLOC_STRING "" 0 0)) - ($ALLOC_SCALAR $type (i32.sub_s $ms (get_global $string_mem))) + ($ALLOC_SCALAR $type (i32.sub $ms (get_global $string_mem))) ) (func $STRING_FINALIZE (param $mv i32 $size i32) (result i32) @@ -174,7 +174,7 @@ (set_local $mv $tmp)) (else ;;; ms->size = sizeof(MalString) + size + 1 - (i32.store16_u (i32.add $ms 2) + (i32.store16 (i32.add $ms 2) (i32.add (i32.add 4 $size) 1)) ;;; string_mem_next = (void *)ms + ms->size (set_global $string_mem_next @@ -346,7 +346,7 @@ ;; combine last/res as hi 32/low 32 of i64 (i64.or - (i64.shl_u (i64.extend_u/i32 $last) (i64.const 32)) + (i64.shl (i64.extend_u/i32 $last) (i64.const 32)) (i64.extend_u/i32 $res)) ) @@ -398,7 +398,7 @@ ) ;; combine found/res as hi 32/low 32 of i64 - (i64.or (i64.shl_u (i64.extend_u/i32 $found) (i64.const 32)) + (i64.or (i64.shl (i64.extend_u/i32 $found) (i64.const 32)) (i64.extend_u/i32 $res)) ) From 77bf4e612d0dc281332909162b9da1a829e8b3ea Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 8 Dec 2018 16:51:16 -0600 Subject: [PATCH 0720/2308] wasm: fix to work with wabt's wat2wasm. --- wasm/core.wam | 9 ++++----- wasm/env.wam | 2 -- wasm/mem.wam | 6 +++--- wasm/platform_libc.wam | 2 +- wasm/printer.wam | 16 ++++++++-------- wasm/printf.wam | 2 +- wasm/reader.wam | 1 + wasm/step5_tco.wam | 2 +- wasm/step6_file.wam | 2 +- wasm/step7_quote.wam | 2 +- wasm/step8_macros.wam | 2 +- wasm/step9_try.wam | 2 +- wasm/stepA_mal.wam | 2 +- wasm/string.wam | 28 ++++++++++++++-------------- wasm/types.wam | 21 +++++++++++---------- 15 files changed, 49 insertions(+), 50 deletions(-) diff --git a/wasm/core.wam b/wasm/core.wam index ef2da7f281..59e906782e 100644 --- a/wasm/core.wam +++ b/wasm/core.wam @@ -79,7 +79,7 @@ (func $keyword (param $args i32) (result i32) (LET $str ($to_String ($MEM_VAL1_ptr $args))) - (if i32 (i32.eq (i32.load8_u $str) (CHR "\x7f")) + (if (result i32) (i32.eq (i32.load8_u $str) (CHR "\x7f")) (then ($INC_REF ($MEM_VAL1_ptr $args))) (else (drop ($sprintf_1 (get_global $printf_buf) "\x7f%s" $str)) @@ -267,7 +267,7 @@ (func $get (param $args i32) (result i32) (LET $hm ($MEM_VAL1_ptr $args) $key ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) - (if i32 (i32.eq $hm (get_global $NIL)) + (if (result i32) (i32.eq $hm (get_global $NIL)) (then ($INC_REF (get_global $NIL))) (else ($INC_REF (i32.wrap/i64 ($HASHMAP_GET $hm $key))))) ) @@ -276,7 +276,7 @@ (LET $hm ($MEM_VAL1_ptr $args) $key ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) ($TRUE_FALSE - (if i32 (i32.eq $hm (get_global $NIL)) + (if (result i32) (i32.eq $hm (get_global $NIL)) (then 0) (else (i32.wrap/i64 (i64.shr_u ($HASHMAP_GET $hm $key) (i64.const 32)))))) @@ -521,7 +521,7 @@ ) (func $meta (param $args i32) (result i32) - (if i32 (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) (get_global $METADATA_T)) + (if (result i32) (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) (get_global $METADATA_T)) (then ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL1_ptr $args)))) (else ($INC_REF (get_global $NIL))))) @@ -545,7 +545,6 @@ ) (func $reset_BANG (param $args i32) (result i32) - (local $atom i32 $val i32) (LET $atom ($MEM_VAL1_ptr $args) $val ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) ($_reset_BANG $atom $val) diff --git a/wasm/env.wam b/wasm/env.wam index 37ff695446..3132b4a9bb 100644 --- a/wasm/env.wam +++ b/wasm/env.wam @@ -1,8 +1,6 @@ (module $env (func $ENV_NEW (param $outer i32) (result i32) - (local $data i32 $env i32) - (LET $data ($HASHMAP) ;; allocate the data hashmap $env ($ALLOC (get_global $ENVIRONMENT_T) $data $outer 0)) ;; environment takes ownership diff --git a/wasm/mem.wam b/wasm/mem.wam index 9524f934bb..7a6e634c1f 100644 --- a/wasm/mem.wam +++ b/wasm/mem.wam @@ -85,13 +85,13 @@ (func $MalType_size (param $type i32) (result i32) ;;; if (type <= 5 || type == 9 || type == 12) - (if i32 (OR (i32.le_u $type 5) + (if (result i32) (OR (i32.le_u $type 5) (i32.eq $type 9) (i32.eq $type 12)) (then 2) (else ;;; else if (type == 8 || type == 10 || type == 11) - (if i32 (OR (i32.eq $type 8) + (if (result i32) (OR (i32.eq $type 8) (i32.eq $type 10) (i32.eq $type 11)) (then 4) @@ -100,7 +100,7 @@ (func $MalVal_size (param $mv i32) (result i32) (LET $type ($TYPE $mv)) ;; if (type == FREE_T) - (if i32 (i32.eq $type (get_global $FREE_T)) + (if (result i32) (i32.eq $type (get_global $FREE_T)) (then ;;; return (mv->refcnt_type & 0xffe0)>>5 (i32.shr_u (i32.and (i32.load $mv) 0xffe0) 5)) ;;; / 32 diff --git a/wasm/platform_libc.wam b/wasm/platform_libc.wam index 0327f52c7f..1c769af00a 100644 --- a/wasm/platform_libc.wam +++ b/wasm/platform_libc.wam @@ -47,7 +47,7 @@ ($memmove $buf $line $len) ($lib_free $line))) (i32.store8 (i32.add $buf $len) (CHR "\x00")) - (return (if i32 $line 1 0)) + (return (if (result i32) $line 1 0)) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/wasm/printer.wam b/wasm/printer.wam index 7aea5b412b..0bd08236c7 100644 --- a/wasm/printer.wam +++ b/wasm/printer.wam @@ -61,10 +61,10 @@ ;; 8: hashmap (set_local $res ($sprintf_1 $res "%c" - (if i32 (i32.eq $type (get_global $LIST_T)) - (CHR "(") - (else (if i32 (i32.eq $type (get_global $VECTOR_T)) - (CHR "[") + (if (result i32) (i32.eq $type (get_global $LIST_T)) + (then (CHR "(")) + (else (if (result i32) (i32.eq $type (get_global $VECTOR_T)) + (then (CHR "[")) (else (CHR "{"))))))) ;; PR_SEQ_LOOP ;;; while (VAL0(mv) != 0) @@ -93,10 +93,10 @@ (set_local $res ($sprintf_1 $res "%c" - (if i32 (i32.eq $type (get_global $LIST_T)) - (CHR ")") - (else (if i32 (i32.eq $type (get_global $VECTOR_T)) - (CHR "]") + (if (result i32) (i32.eq $type (get_global $LIST_T)) + (then (CHR ")")) + (else (if (result i32) (i32.eq $type (get_global $VECTOR_T)) + (then (CHR "]")) (else (CHR "}"))))))) (br $done)) ;; 9: function diff --git a/wasm/printf.wam b/wasm/printf.wam index d1bd8d8443..b06a23864e 100644 --- a/wasm/printf.wam +++ b/wasm/printf.wam @@ -68,7 +68,7 @@ ;; Calculate smallest to most significant digit (loop $loop (set_local $digit (i32.rem_u $val $radix)) - (i32.store8 $pbuf (if i32 (i32.lt_u $digit 10) + (i32.store8 $pbuf (if (result i32) (i32.lt_u $digit 10) (i32.add (CHR "0") $digit) (i32.sub (i32.add (CHR "A") $digit) 10))) (set_local $pbuf (i32.add $pbuf 1)) diff --git a/wasm/reader.wam b/wasm/reader.wam index 45715caf2c..e87c01eb5f 100644 --- a/wasm/reader.wam +++ b/wasm/reader.wam @@ -298,6 +298,7 @@ (else (return ($STRING (get_global $SYMBOL_T) $tok)))) )))))))))))))))))))))))))))))))) + 0 ;; not reachable ) (func $read_str (param $str i32) (result i32) diff --git a/wasm/step5_tco.wam b/wasm/step5_tco.wam index 161a6c5d78..c7a0984438 100644 --- a/wasm/step5_tco.wam +++ b/wasm/step5_tco.wam @@ -40,7 +40,7 @@ (br_if $done (i32.eq ($VAL0 $ast) 0)) (if $skiplast - (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0))) + (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast))))) (if (i32.eq $type (get_global $HASHMAP_T)) (then diff --git a/wasm/step6_file.wam b/wasm/step6_file.wam index 902626c96b..4898f21a18 100644 --- a/wasm/step6_file.wam +++ b/wasm/step6_file.wam @@ -40,7 +40,7 @@ (br_if $done (i32.eq ($VAL0 $ast) 0)) (if $skiplast - (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0))) + (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast))))) (if (i32.eq $type (get_global $HASHMAP_T)) (then diff --git a/wasm/step7_quote.wam b/wasm/step7_quote.wam index 5a699b08e8..bfbfed10f7 100644 --- a/wasm/step7_quote.wam +++ b/wasm/step7_quote.wam @@ -90,7 +90,7 @@ (br_if $done (i32.eq ($VAL0 $ast) 0)) (if $skiplast - (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0))) + (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast))))) (if (i32.eq $type (get_global $HASHMAP_T)) (then diff --git a/wasm/step8_macros.wam b/wasm/step8_macros.wam index c45f2f3a8e..445d0fea11 100644 --- a/wasm/step8_macros.wam +++ b/wasm/step8_macros.wam @@ -133,7 +133,7 @@ (br_if $done (i32.eq ($VAL0 $ast) 0)) (if $skiplast - (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0))) + (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast))))) (if (i32.eq $type (get_global $HASHMAP_T)) (then diff --git a/wasm/step9_try.wam b/wasm/step9_try.wam index d7636b6b39..61d91003cd 100644 --- a/wasm/step9_try.wam +++ b/wasm/step9_try.wam @@ -133,7 +133,7 @@ (br_if $done (i32.eq ($VAL0 $ast) 0)) (if $skiplast - (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0))) + (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast))))) (if (i32.eq $type (get_global $HASHMAP_T)) (then diff --git a/wasm/stepA_mal.wam b/wasm/stepA_mal.wam index 6aa05c94da..495e3e6807 100644 --- a/wasm/stepA_mal.wam +++ b/wasm/stepA_mal.wam @@ -133,7 +133,7 @@ (br_if $done (i32.eq ($VAL0 $ast) 0)) (if $skiplast - (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast)) 0))) + (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast))))) (if (i32.eq $type (get_global $HASHMAP_T)) (then diff --git a/wasm/string.wam b/wasm/string.wam index 28190a858e..593ea1c1e0 100644 --- a/wasm/string.wam +++ b/wasm/string.wam @@ -39,8 +39,8 @@ (loop $loop (if (i32.gt_s $i (i32.sub $len $needle_len)) (br $done)) - (if (AND (i32.eq (i32.load8_u $haystack 0) - (i32.load8_u $needle 0)) + (if (AND (i32.eq (i32.load8_u $haystack) + (i32.load8_u $needle)) (i32.eqz ($strncmp $haystack $needle $needle_len))) (return $haystack)) (set_local $haystack (i32.add $haystack 1)) @@ -73,7 +73,7 @@ (br $loop) ) ) - (if i32 $neg + (if (result i32) $neg (then (i32.sub 0 $acc)) (else $acc)) ) @@ -90,10 +90,10 @@ (br $loop) ) ) - (if i32 (i32.eq (i32.load8_u $s1) (i32.load8_u $s2)) + (if (result i32) (i32.eq (i32.load8_u $s1) (i32.load8_u $s2)) (then 0) (else - (if i32 (i32.lt_u (i32.load8_u $s1) (i32.load8_u $s2)) + (if (result i32) (i32.lt_u (i32.load8_u $s1) (i32.load8_u $s2)) (then -1) (else 1)))) ) @@ -115,7 +115,7 @@ (i32.eq (i32.load8_u (i32.add $i $s1)) (i32.load8_u (i32.add $i $s2)))) (return 0)) - (if i32 (i32.lt_u (i32.load8_u (i32.add $i $s1)) + (if (result i32) (i32.lt_u (i32.load8_u (i32.add $i $s1)) (i32.load8_u (i32.add $i $s2))) (then -1) (else 1)) @@ -144,11 +144,11 @@ (block $done (loop $loop (if (i32.ge_u $s 3) (br $done)) - (set_local $needle (if i32 (i32.eq $s 0) $needle0 - (if i32 (i32.eq $s 1) $needle1 + (set_local $needle (if (result i32) (i32.eq $s 0) $needle0 + (if (result i32) (i32.eq $s 1) $needle1 $needle2))) - (set_local $replace (if i32 (i32.eq $s 0) $replace0 - (if i32 (i32.eq $s 1) $replace1 + (set_local $replace (if (result i32) (i32.eq $s 0) $replace0 + (if (result i32) (i32.eq $s 1) $replace1 $replace2))) (set_local $needle_len ($strlen $needle)) (set_local $replace_len ($strlen $replace)) @@ -172,11 +172,11 @@ (block $done2 (loop $loop2 (if (i32.ge_u $s 3) (br $done2)) - (set_local $needle (if i32 (i32.eq $s 0) $needle0 - (if i32 (i32.eq $s 1) $needle1 + (set_local $needle (if (result i32) (i32.eq $s 0) $needle0 + (if (result i32) (i32.eq $s 1) $needle1 $needle2))) - (set_local $replace (if i32 (i32.eq $s 0) $replace0 - (if i32 (i32.eq $s 1) $replace1 + (set_local $replace (if (result i32) (i32.eq $s 0) $replace0 + (if (result i32) (i32.eq $s 1) $replace1 $replace2))) (set_local $s (i32.add $s 1)) (set_local $found_tmp ($strstr $src_str $needle)) diff --git a/wasm/types.wam b/wasm/types.wam index b432efde42..a2d0cd36e3 100644 --- a/wasm/types.wam +++ b/wasm/types.wam @@ -59,7 +59,7 @@ ) (func $TRUE_FALSE (param $val i32) (result i32) - ($INC_REF (if i32 $val (get_global $TRUE) (get_global $FALSE))) + ($INC_REF (if (result i32) $val (get_global $TRUE) (get_global $FALSE))) ) (func $THROW_STR_0 (param $fmt i32) @@ -99,16 +99,17 @@ (else (if (AND (i32.eq $ta (get_global $HASHMAP_T)) (i32.eq $tb (get_global $HASHMAP_T))) ;; EQUAL_Q_HM - (return 1)) + (then (return 1)) ;; TODO: remove this once strings are interned (else (if (OR (AND (i32.eq $ta (get_global $STRING_T)) (i32.eq $tb (get_global $STRING_T))) (AND (i32.eq $ta (get_global $SYMBOL_T)) (i32.eq $tb (get_global $SYMBOL_T)))) - (return (i32.eqz ($strcmp ($to_String $a) ($to_String $b)))) + (then (return (i32.eqz ($strcmp ($to_String $a) ($to_String $b))))) (else (return (AND (i32.eq $ta $tb) - (i32.eq ($VAL0 $a) ($VAL0 $b))))))))) + (i32.eq ($VAL0 $a) ($VAL0 $b)))))))))) + 0 ;; not reachable ) (func $DEREF_META (param $mv i32) (result i32) @@ -193,12 +194,12 @@ ;; sequence functions (func $MAP_LOOP_START (param $type i32) (result i32) - (LET $res (if i32 (i32.eq $type (get_global $LIST_T)) - (get_global $EMPTY_LIST) - (else (if i32 (i32.eq $type (get_global $VECTOR_T)) - (get_global $EMPTY_VECTOR) - (else (if i32 (i32.eq $type (get_global $HASHMAP_T)) - (get_global $EMPTY_HASHMAP) + (LET $res (if (result i32) (i32.eq $type (get_global $LIST_T)) + (then (get_global $EMPTY_LIST)) + (else (if (result i32) (i32.eq $type (get_global $VECTOR_T)) + (then (get_global $EMPTY_VECTOR)) + (else (if (result i32) (i32.eq $type (get_global $HASHMAP_T)) + (then (get_global $EMPTY_HASHMAP)) (else ($THROW_STR_1 "read_seq invalid type %d" $type) 0))))))) From df43e71977c77183da56c9d02fa12230146b59f9 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 8 Dec 2018 16:52:48 -0600 Subject: [PATCH 0721/2308] wasm: Update Dockerfile. WAMP and WASM_AS params. --- wasm/Dockerfile | 51 ++++++++++++++++++++++++++++++++++++++++++++++--- wasm/Makefile | 19 +++++++++++++----- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/wasm/Dockerfile b/wasm/Dockerfile index a40c6fe035..2a342839be 100644 --- a/wasm/Dockerfile +++ b/wasm/Dockerfile @@ -1,5 +1,45 @@ FROM ubuntu:18.04 +MAINTAINER Joel Martin +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# +# node +# + +# For building node modules +RUN apt-get -y install g++ + +# Add nodesource apt repo config for 10.x stable +RUN apt-get -y install gnupg +RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - + +# Install nodejs +RUN apt-get -y install nodejs + +ENV NPM_CONFIG_CACHE /mal/.npm + +# +# binaryen +# RUN dpkg --add-architecture i386 && \ apt-get -y update && \ apt-get -y install \ @@ -7,9 +47,6 @@ RUN dpkg --add-architecture i386 && \ libsdl2-dev:i386 libsdl2-image-dev:i386 \ libedit-dev:i386 -# TODO: merge up -RUN apt-get -y install python - RUN git clone https://github.com/WebAssembly/binaryen/ && \ cd binaryen && \ cmake . && make && \ @@ -17,3 +54,11 @@ RUN git clone https://github.com/WebAssembly/binaryen/ && \ # TODO: merge up RUN apt-get -y install freeglut3-dev:i386 lib32gcc-7-dev libreadline-dev:i386 + +# +# wac/wace +# +RUN git clone https://github.com/kanaka/wac/ && \ + cd wac && \ + make USE_SDL= wac wace && \ + cp wac wace /usr/bin diff --git a/wasm/Makefile b/wasm/Makefile index 6b978bbcbf..7a949adc90 100644 --- a/wasm/Makefile +++ b/wasm/Makefile @@ -1,6 +1,9 @@ MODE ?= libc -STEP0_DEPS = platform_$(MODE).wam string.wam printf.wam +WASM_AS ?= wasm-as +WAMP ?= node_modules/.bin/wamp + +STEP0_DEPS = $(WAMP) platform_$(MODE).wam string.wam printf.wam STEP1_DEPS = $(STEP0_DEPS) types.wam mem.wam debug.wam reader.wam printer.wam STEP3_DEPS = $(STEP1_DEPS) env.wam STEP4_DEPS = $(STEP3_DEPS) core.wam @@ -11,9 +14,12 @@ STEPS = step0_repl step1_read_print step2_eval step3_env \ all: $(foreach s,$(STEPS),$(s).wasm) +node_modules/.bin/wamp: + npm install + %.wasm: %.wam - wamp $^ > $*.wast - wasm-as $*.wast -o $@ + $(WAMP) $(filter %.wam,$^) > $*.wast + $(WASM_AS) $*.wast -o $@ step0_repl.wasm: $(STEP0_DEPS) step1_read_print.wasm step2_eval.wasm: $(STEP1_DEPS) @@ -28,10 +34,13 @@ clean: .PHONY: stats tests -stats: $(STEP4_DEPS) stepA_mal.wam +SOURCES_ALL = $(filter %.wam,$(STEP4_DEPS)) stepA_mal.wam +SOURCES_LISP = $(filter-out $(STEP1_DEPS),$(SOURCES_ALL)) + +stats: $(SOURCES_ALL) @wc $^ @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(filter-out $(STEP1_DEPS),$(STEP4_DEPS)) stepA_mal.wam +stats-lisp: $(SOURCES_LISP) @wc $^ @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" From d72395aa8b0926c6b2981d61ff46579de371ffa5 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 8 Dec 2018 16:56:59 -0600 Subject: [PATCH 0722/2308] wasm: update README. Activate in travis. --- .travis.yml | 1 + README.md | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ab99fe9077..9d3bb470b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -91,6 +91,7 @@ matrix: - {env: IMPL=vb, services: [docker]} - {env: IMPL=vhdl, services: [docker]} - {env: IMPL=vimscript, services: [docker]} + - {env: IMPL=wasm, services: [docker]} - {env: IMPL=yorick, services: [docker]} script: diff --git a/README.md b/README.md index 9e6585e6a7..da71ca2a5f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mal is a Clojure inspired Lisp interpreter. -Mal is implemented in 73 languages: +Mal is implemented in 74 languages: * Ada * GNU awk @@ -80,6 +80,7 @@ Mal is implemented in 73 languages: * VHDL * Vimscript * Visual Basic.NET +* WebAssembly (wasm) * Yorick @@ -1039,6 +1040,19 @@ make mono ./stepX_YYY.exe ``` +### WebAssembly (wasm) ### + +The WebAssembly implementation has been written in +[Wam](https://github.com/kanaka/wam) (WebAssembly Macro language) and +runs under the [wac/wace](https://github.com/kanaka/wac) WebAssembly +runtime. + +``` +cd wasm +make +wace ./stepX_YYY.wasm +``` + ### Yorick *The Yorick implementation was created by [Dov Murik](https://github.com/dubek)* From 8faa94c66a803db4acc77c6972ef8334cc7d1e57 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 8 Dec 2018 18:11:19 -0600 Subject: [PATCH 0723/2308] wasm: add package.json to pull in wamp. --- wasm/package.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 wasm/package.json diff --git a/wasm/package.json b/wasm/package.json new file mode 100644 index 0000000000..854d48984d --- /dev/null +++ b/wasm/package.json @@ -0,0 +1,8 @@ +{ + "name": "mal", + "version": "0.0.1", + "description": "Make a Lisp (mal) language implemented in Javascript", + "dependencies": { + "@kanaka/wamp": "1.0.4" + } +} From 27359631a6423d8b4a47dcb8e1cdc1fd8a2cc9af Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 8 Dec 2018 18:26:13 -0600 Subject: [PATCH 0724/2308] README: add missing talk youtube links. --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index da71ca2a5f..8adf552ef2 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,10 @@ Unlocked: A Better Path to Language Learning". Joel gave a presentation on "Make Your Own Lisp Interpreter in 10 Incremental Steps" at LambdaConf 2016: [Part 1](https://www.youtube.com/watch?v=jVhupfthTEk), [Part -2](https://www.youtube.com/watch?v=X5OQBMGpaTU), +2](https://www.youtube.com/watch?v=X5OQBMGpaTU), [Part +3](https://www.youtube.com/watch?v=6mARZzGgX4U), [Part +4](https://www.youtube.com/watch?v=dCO1SYR5kDU), + [Slides](http://kanaka.github.io/lambdaconf/). If you are interesting in creating a mal implementation (or just @@ -1042,7 +1045,7 @@ mono ./stepX_YYY.exe ### WebAssembly (wasm) ### -The WebAssembly implementation has been written in +The WebAssembly implementation is written in [Wam](https://github.com/kanaka/wam) (WebAssembly Macro language) and runs under the [wac/wace](https://github.com/kanaka/wac) WebAssembly runtime. From 86633b336574865cba8ac2b013c79a19d52aac67 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 8 Dec 2018 18:29:28 -0600 Subject: [PATCH 0725/2308] README: fix formatting. --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8adf552ef2..1b68bbfeb9 100644 --- a/README.md +++ b/README.md @@ -114,12 +114,11 @@ Unlocked: A Better Path to Language Learning". [Video](https://www.youtube.com/watch?v=lgyOAiRtZGw), [Slides](http://kanaka.github.io/midwest.io.mal/). More recently Joel gave a presentation on "Make Your Own Lisp Interpreter in -10 Incremental Steps" at LambdaConf 2016: [Part -1](https://www.youtube.com/watch?v=jVhupfthTEk), [Part -2](https://www.youtube.com/watch?v=X5OQBMGpaTU), [Part -3](https://www.youtube.com/watch?v=6mARZzGgX4U), [Part -4](https://www.youtube.com/watch?v=dCO1SYR5kDU), - +10 Incremental Steps" at LambdaConf 2016: +[Part 1](https://www.youtube.com/watch?v=jVhupfthTEk), +[Part 2](https://www.youtube.com/watch?v=X5OQBMGpaTU), +[Part 3](https://www.youtube.com/watch?v=6mARZzGgX4U), +[Part 4](https://www.youtube.com/watch?v=dCO1SYR5kDU), [Slides](http://kanaka.github.io/lambdaconf/). If you are interesting in creating a mal implementation (or just From 89149437904c1c8877eb6a9526ec5b1c89b17ce3 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sun, 9 Dec 2018 22:55:49 -0600 Subject: [PATCH 0726/2308] wasm: enable execution with Node Add node mode to travis alongside wace_libc. --- .travis.yml | 3 +- Makefile | 2 + wasm/Makefile | 2 +- wasm/node_readline.js | 46 +++++++++++++ wasm/package.json | 4 +- wasm/run | 10 ++- wasm/run.js | 149 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 wasm/node_readline.js create mode 100755 wasm/run.js diff --git a/.travis.yml b/.travis.yml index 9d3bb470b8..2cb06e3125 100644 --- a/.travis.yml +++ b/.travis.yml @@ -91,7 +91,8 @@ matrix: - {env: IMPL=vb, services: [docker]} - {env: IMPL=vhdl, services: [docker]} - {env: IMPL=vimscript, services: [docker]} - - {env: IMPL=wasm, services: [docker]} + - {env: IMPL=wasm wasm_MODE=wace_libc, services: [docker]} + - {env: IMPL=wasm wasm_MODE=node, services: [docker]} - {env: IMPL=yorick, services: [docker]} script: diff --git a/Makefile b/Makefile index c7febc3eed..e00d6444c0 100644 --- a/Makefile +++ b/Makefile @@ -58,6 +58,8 @@ matlab_MODE = octave python_MODE = python # scheme (chibi, kawa, gauche, chicken, sagittarius, cyclone, foment) scheme_MODE = chibi +# node wace_libc wace_fooboot +wasm_MODE = wace_libc # Extra options to pass to runtest.py TEST_OPTS = diff --git a/wasm/Makefile b/wasm/Makefile index 7a949adc90..b69cca47fe 100644 --- a/wasm/Makefile +++ b/wasm/Makefile @@ -1,4 +1,4 @@ -MODE ?= libc +MODE ?= $(if $(filter wace_fooboot node js,$(wasm_MODE)),os,libc) WASM_AS ?= wasm-as WAMP ?= node_modules/.bin/wamp diff --git a/wasm/node_readline.js b/wasm/node_readline.js new file mode 100644 index 0000000000..6042eaa0af --- /dev/null +++ b/wasm/node_readline.js @@ -0,0 +1,46 @@ +// IMPORTANT: choose one +var RL_LIB = "libreadline"; // NOTE: libreadline is GPL +//var RL_LIB = "libedit"; + +var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history'); + +var rlwrap = {}; // namespace for this module in web context + +var ffi = require('ffi-napi'), + fs = require('fs'); + +var rllib = ffi.Library(RL_LIB, { + 'readline': [ 'string', [ 'string' ] ], + 'add_history': [ 'int', [ 'string' ] ]}); + +var rl_history_loaded = false; + +exports.readline = rlwrap.readline = function(prompt) { + prompt = typeof prompt !== 'undefined' ? prompt : "user> "; + + if (!rl_history_loaded) { + rl_history_loaded = true; + var lines = []; + if (fs.existsSync(HISTORY_FILE)) { + lines = fs.readFileSync(HISTORY_FILE).toString().split("\n"); + } + // Max of 2000 lines + lines = lines.slice(Math.max(lines.length - 2000, 0)); + for (var i=0; i&2 "wace_fooboot mode not yet supported" ;; +wace_libc|*) + exec wace $(dirname $0)/${STEP:-stepA_mal}.wasm "${@}" ;; +esac diff --git a/wasm/run.js b/wasm/run.js new file mode 100755 index 0000000000..b9e21a796e --- /dev/null +++ b/wasm/run.js @@ -0,0 +1,149 @@ +#!/usr/bin/env node + +// Copyright Joel Martin +// License MIT + +const { promisify } = require('util') +const fs = require('fs') +const readFile = promisify(fs.readFile) +const assert = require('assert') +const { TextDecoder, TextEncoder } = require('text-encoding') +const node_readline = require('./node_readline.js') + +assert('WebAssembly' in global, 'WebAssembly not detected') + +// +// Memory interaction utilities +// + +// Convert node Buffer to Uint8Array +function toUint8Array(buf) { + let u = new Uint8Array(buf.length) + for (let i = 0; i < buf.length; ++i) { + u[i] = buf[i] + } + return u +} + +// Read null terminated string out of webassembly memory +function get_string(memory, addr) { + //console.warn("get_string:", addr) + let u8 = new Uint8Array(memory.buffer, addr) + let length = u8.findIndex(e => e == 0) + let bytes = new Uint8Array(memory.buffer, addr, length) + let str = new TextDecoder('utf8').decode(bytes) + return str +} + +// Write null terminated string into webassembly memory +function put_string(memory, addr, str, max_length) { + let buf8 = new Uint8Array(memory.buffer, addr) + + let bytes = new TextEncoder('utf8').encode(str) + if (max_length && bytes.length > max_length) { + bytes = bytes.slice(0, max_length) + } + + buf8.set(bytes, 0) + buf8[bytes.length] = 0 // null terminator + return bytes.length+1 +} + +// Put argv structure at beginning of memory +function marshal_argv(memory, offset, args) { + let view = new DataView(memory.buffer, offset) + let buf8 = new Uint8Array(memory.buffer, offset) + + let stringStart = (args.length + 1) * 4 + for (let i = 0; i < args.length; i++) { + let len = put_string(memory, stringStart, args[i]) + view.setUint32(i*4, stringStart, true) + stringStart = stringStart + len + } + view.setUint32(args.length*4, 0, true) + return offset + stringStart // start of free memory +} + +// Based on: +// https://gist.github.com/kripken/59c67556dc03bb6d57052fedef1e61ab + +// Loads a WebAssembly dynamic library, returns a promise. +async function loadWebAssembly(filename, args) { + // Fetch the file and compile it + const wasm_str = await readFile(filename) + const wasm_bin = toUint8Array(wasm_str) + const module = await WebAssembly.compile(wasm_bin) + let memory = new WebAssembly.Memory({ initial: 256 }) + // Core imports + function fputs(addr, stream) { + console.log(get_string(memory, addr).replace(/\n$/, '')) + } + + // Returns addr on success and -1 on failure + // Truncates to max_length + function readline(prompt, addr, max_length) { + let line = node_readline.readline(get_string(memory, prompt)) + if (line === null) { return 0 } + put_string(memory, addr, line, max_length) + return 1 + } + + function read_file(path_addr, buf) { + let path = get_string(memory, path_addr) + let contents = fs.readFileSync(path, 'utf8') + return put_string(memory, buf, contents) + } + + // Marshal arguments + const memoryStart = 0 + let memoryBase = marshal_argv(memory, memoryStart, args) + memoryBase = memoryBase + (8 - (memoryBase % 8)) + + // Create the imports for the module, including the + // standard dynamic library imports + imports = {} + imports.env = {} + imports.env.exit = process.exit + imports.env.stdout = 1 + imports.env.fputs = fputs; + imports.env.readline = readline + // ignore add_history, node_readline.readline already calls it + imports.env.add_history = (buf) => { } + imports.env.read_file = read_file + + imports.env.memory = memory + imports.env.memoryBase = memoryBase + imports.env.table = new WebAssembly.Table({ initial: 0, element: 'anyfunc' }) + imports.env.tableBase = imports.env.tableBase || 0 + // Create the instance. + return [new WebAssembly.Instance(module, imports), args.length, 0] +} + +async function main() { + assert(process.argv.length >= 3, + 'Usage: ./run.js prog.wasm [ARGS...]') + + const wasm = process.argv[2] + const args = process.argv.slice(2) + const [instance, argc, argv] = await loadWebAssembly(wasm, args) + + let exports = instance.exports + assert(exports, 'no exports found') + assert('_main' in exports, '_main not found in wasm module exports') + if ('__post_instantiate' in exports) { + //console.warn('calling exports.__post_instantiate()') + exports['__post_instantiate']() + } + //console.warn(`calling exports._main(${argc}, ${argv})`) + let start = new Date() + let res = exports['_main'](argc, argv) + let end = new Date() + //console.warn('runtime: ' + (end-start) + 'ms') + process.exit(res) +} + +if (module.parent) { + module.exports.loadWebAssembly = loadWebAssembly +} else { + main() +} From 76adfab958e24e75df358ee0c9013476dc03e95a Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 10 Dec 2018 00:30:59 -0600 Subject: [PATCH 0727/2308] wasm: split js mode platform code out. Simplify the interface to remove imports that aren't used (stdout and add_history) and simplify (rename fputs to printline). --- Makefile | 2 +- wasm/Makefile | 2 +- wasm/platform_js.wam | 44 ++++++++++++++++++++++++++++++++++++++++++++ wasm/platform_os.wam | 11 +++++------ wasm/run.js | 12 +++++++----- 5 files changed, 58 insertions(+), 13 deletions(-) create mode 100644 wasm/platform_js.wam diff --git a/Makefile b/Makefile index e00d6444c0..52cf142ec6 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ matlab_MODE = octave python_MODE = python # scheme (chibi, kawa, gauche, chicken, sagittarius, cyclone, foment) scheme_MODE = chibi -# node wace_libc wace_fooboot +# js wace_libc wace_fooboot wasm_MODE = wace_libc # Extra options to pass to runtest.py diff --git a/wasm/Makefile b/wasm/Makefile index b69cca47fe..c86f2ff167 100644 --- a/wasm/Makefile +++ b/wasm/Makefile @@ -1,4 +1,4 @@ -MODE ?= $(if $(filter wace_fooboot node js,$(wasm_MODE)),os,libc) +MODE ?= $(if $(filter node js,$(wasm_MODE)),js,$(if $(filter wace_fooboot,$(wasm_MODE)),os,libc)) WASM_AS ?= wasm-as WAMP ?= node_modules/.bin/wamp diff --git a/wasm/platform_js.wam b/wasm/platform_js.wam new file mode 100644 index 0000000000..3993e16330 --- /dev/null +++ b/wasm/platform_js.wam @@ -0,0 +1,44 @@ +(module $platform_js + + (import "env" "exit" (func $lib_exit (param i32))) + (import "env" "printline" (func $lib_printline (param i32) (result i32))) + (import "env" "readline" (func $lib_readline (param i32 i32 i32) (result i32))) + (import "env" "read_file" (func $lib_read_file (param i32 i32) (result i32))) + (import "env" "time_ms" (func $lib_time_ms (result i32))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $fatal (param $code i32 $msg i32) + ($print $msg) + ($lib_exit $code) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $print (param $addr i32) + (drop ($lib_printline $addr))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $readline (param $prompt i32 $buf i32) (result i32) + ;; TODO: don't hardcode count to 200 + (LET $res ($lib_readline $prompt $buf 200)) + $res + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $read_file (param $path i32 $buf i32) (result i32) + (LET $size ($lib_read_file $path $buf)) + ;; Add null to string + (i32.store8 (i32.add $buf $size) 0) + (i32.add $size 1) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (func $get_time_ms (result i32) + ($lib_time_ms) + ) + +) diff --git a/wasm/platform_os.wam b/wasm/platform_os.wam index 6615ce7551..c0ae33c147 100644 --- a/wasm/platform_os.wam +++ b/wasm/platform_os.wam @@ -1,14 +1,14 @@ (module $platform_os - (import "env" "exit" (func $lib_exit (param i32))) + (import "env" "exit" (func $lib_exit (param i32))) - (import "env" "stdout" (global $lib_stdout i32)) - (import "env" "fputs" (func $lib_fputs (param i32 i32) (result i32))) + (import "env" "stdout" (global $lib_stdout i32)) + (import "env" "fputs" (func $lib_fputs (param i32 i32) (result i32))) - (import "env" "readline" (func $lib_readline (param i32 i32 i32) (result i32))) + (import "env" "readline" (func $lib_readline (param i32 i32 i32) (result i32))) (import "env" "add_history" (func $lib_add_history (param i32))) - (import "env" "read_file" (func $lib_read_file (param i32 i32) (result i32))) + (import "env" "read_file" (func $lib_read_file (param i32 i32) (result i32))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -43,7 +43,6 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - (func $get_time_ms (result i32) 0 ) diff --git a/wasm/run.js b/wasm/run.js index b9e21a796e..da01761ae8 100755 --- a/wasm/run.js +++ b/wasm/run.js @@ -75,7 +75,7 @@ async function loadWebAssembly(filename, args) { const module = await WebAssembly.compile(wasm_bin) let memory = new WebAssembly.Memory({ initial: 256 }) // Core imports - function fputs(addr, stream) { + function printline(addr, stream) { console.log(get_string(memory, addr).replace(/\n$/, '')) } @@ -94,6 +94,10 @@ async function loadWebAssembly(filename, args) { return put_string(memory, buf, contents) } + function time_ms() { + return (new Date()).getTime() + } + // Marshal arguments const memoryStart = 0 let memoryBase = marshal_argv(memory, memoryStart, args) @@ -104,12 +108,10 @@ async function loadWebAssembly(filename, args) { imports = {} imports.env = {} imports.env.exit = process.exit - imports.env.stdout = 1 - imports.env.fputs = fputs; + imports.env.printline = printline imports.env.readline = readline - // ignore add_history, node_readline.readline already calls it - imports.env.add_history = (buf) => { } imports.env.read_file = read_file + imports.env.time_ms = time_ms imports.env.memory = memory imports.env.memoryBase = memoryBase From 18616b105e6872705ab290eec22fcec3661db700 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 10 Dec 2018 12:30:58 -0600 Subject: [PATCH 0728/2308] runtest: support carriage returns in tests. This allows carriage returns to be specified in tests which allows multiline tests to be written i.e. by using Ctrl-V Enter to put a literal carriage return (^M) into the test definition. The effect of this is to send a single string to the REPL that contains newlines which normally can't be done because readline behavior will split it into two separate lines. --- runtest.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/runtest.py b/runtest.py index 6e2a495e76..8953a10756 100755 --- a/runtest.py +++ b/runtest.py @@ -68,7 +68,7 @@ def log(data, end='\n'): help="Disable optional tests that follow a ';>>> optional=True'") parser.set_defaults(optional=True) -parser.add_argument('test_file', type=argparse.FileType('r'), +parser.add_argument('test_file', type=str, help="a test file formatted as with mal test data") parser.add_argument('mal_cmd', nargs="*", help="Mal implementation command line. Use '--' to " @@ -148,14 +148,14 @@ def read_to_prompt(self, prompts, timeout): buf = self.buf[0:match.start()] self.buf = self.buf[end:] self.last_prompt = prompt - return buf + return buf.replace("^M", "\r") return None def writeline(self, str): def _to_bytes(s): return bytes(s, "utf-8") if IS_PY_3 else s - self.stdin.write(_to_bytes(str + "\n")) + self.stdin.write(_to_bytes(str.replace('\r', '\x16\r') + "\n")) def cleanup(self): #print "cleaning up" @@ -169,7 +169,8 @@ def cleanup(self): class TestReader: def __init__(self, test_file): self.line_num = 0 - self.data = test_file.read().split('\n') + f = open(test_file, newline='') if IS_PY_3 else open(test_file) + self.data = f.read().split('\n') self.soft = False self.deferrable = False self.optional = False @@ -292,7 +293,7 @@ def assert_prompt(runner, prompts, timeout): if t.form == None: continue - log("TEST: %s -> [%s,%s]" % (t.form, repr(t.out), t.ret), end='') + log("TEST: %s -> [%s,%s]" % (repr(t.form), repr(t.out), t.ret), end='') # The repeated form is to get around an occasional OS X issue # where the form is repeated. @@ -349,7 +350,7 @@ def assert_prompt(runner, prompts, timeout): %3d: failing tests %3d: passing tests %3d: total tests -""" % (args.test_file.name, soft_fail_cnt, fail_cnt, +""" % (args.test_file, soft_fail_cnt, fail_cnt, pass_cnt, test_cnt) log(results) From dd7a4f55f3bf8b172b3ddaab2f40fbc39e4cea31 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 3 Dec 2018 13:20:44 -0600 Subject: [PATCH 0729/2308] Test uncaught throw, catchless try* . Fix 46 impls. Fixes made to: ada, c, chuck, clojure, coffee, common-lisp, cpp, crystal, d, dart, elm, erlang, es6, factor, fsharp, gnu-smalltalk, groovy, guile, haxe, hy, js, livescript, matlab, miniMAL, nasm, nim, objc, objpascal, ocaml, perl, perl6, php, plsql, ps, python, r, rpython, ruby, scheme, swift3, tcl, ts, vb, vimscript, wasm, yorick. Catchless try* test is an optional test. Not all implementations support catchless try* but a number were fixed so they at least don't crash on catchless try*. --- ada/core.adb | 14 ++-- ada/step4_if_fn_do.adb | 9 ++- ada/step5_tco.adb | 27 ++++--- ada/step6_file.adb | 20 +++-- ada/step7_quote.adb | 19 +++-- ada/step8_macros.adb | 18 +++-- ada/step9_try.adb | 17 +++- ada/stepa_mal.adb | 36 +++++---- ada/types-vector.adb | 2 +- ada/types.adb | 4 +- ada/types.ads | 2 + c/step0_repl.c | 4 +- c/step1_read_print.c | 11 +-- c/step2_eval.c | 11 +-- c/step3_env.c | 11 +-- c/step4_if_fn_do.c | 11 +-- c/step5_tco.c | 11 +-- c/step6_file.c | 11 +-- c/step7_quote.c | 11 +-- c/step8_macros.c | 13 ++-- c/step9_try.c | 18 +++-- c/stepA_mal.c | 19 +++-- chuck/step1_read_print.ck | 10 +-- chuck/step2_eval.ck | 10 +-- chuck/step3_env.ck | 10 +-- chuck/step4_if_fn_do.ck | 10 +-- chuck/step5_tco.ck | 10 +-- chuck/step6_file.ck | 10 +-- chuck/step7_quote.ck | 10 +-- chuck/step8_macros.ck | 10 +-- chuck/step9_try.ck | 12 +-- chuck/stepA_mal.ck | 12 +-- clojure/src/mal/step9_try.cljc | 6 +- clojure/src/mal/stepA_mal.cljc | 6 +- coffee/core.coffee | 2 +- coffee/step1_read_print.coffee | 4 +- coffee/step2_eval.coffee | 4 +- coffee/step3_env.coffee | 4 +- coffee/step4_if_fn_do.coffee | 4 +- coffee/step5_tco.coffee | 4 +- coffee/step6_file.coffee | 4 +- coffee/step7_quote.coffee | 4 +- coffee/step8_macros.coffee | 4 +- coffee/step9_try.coffee | 11 ++- coffee/stepA_mal.coffee | 11 ++- common-lisp/src/step9_try.lisp | 9 ++- common-lisp/src/stepA_mal.lisp | 9 ++- cpp/Reader.cpp | 10 +-- cpp/step9_try.cpp | 5 +- cpp/stepA_mal.cpp | 5 +- crystal/step1_read_print.cr | 4 +- crystal/step2_eval.cr | 4 +- crystal/step3_env.cr | 4 +- crystal/step4_if_fn_do.cr | 4 +- crystal/step5_tco.cr | 4 +- crystal/step6_file.cr | 4 +- crystal/step7_quote.cr | 4 +- crystal/step8_macros.cr | 4 +- crystal/step9_try.cr | 4 +- crystal/stepA_mal.cr | 4 +- d/step9_try.d | 4 + d/stepA_mal.d | 4 + dart/core.dart | 2 +- dart/step1_read_print.dart | 11 +-- dart/step2_eval.dart | 21 ++--- dart/step3_env.dart | 21 ++--- dart/step4_if_fn_do.dart | 24 +++--- dart/step5_tco.dart | 24 +++--- dart/step6_file.dart | 24 +++--- dart/step7_quote.dart | 24 +++--- dart/step8_macros.dart | 26 +++---- dart/step9_try.dart | 30 ++++--- dart/stepA_mal.dart | 30 ++++--- dart/types.dart | 8 -- elm/step2_eval.elm | 2 +- elm/step3_env.elm | 2 +- elm/step4_if_fn_do.elm | 2 +- elm/step5_tco.elm | 2 +- elm/step6_file.elm | 2 +- elm/step7_quote.elm | 2 +- elm/step8_macros.elm | 2 +- elm/step9_try.elm | 2 +- elm/stepA_mal.elm | 2 +- erlang/src/step9_try.erl | 3 +- erlang/src/stepA_mal.erl | 3 +- es6/step1_read_print.mjs | 4 +- es6/step2_eval.mjs | 4 +- es6/step3_env.mjs | 4 +- es6/step4_if_fn_do.mjs | 4 +- es6/step5_tco.mjs | 4 +- es6/step6_file.mjs | 4 +- es6/step7_quote.mjs | 4 +- es6/step8_macros.mjs | 4 +- es6/step9_try.mjs | 4 +- es6/stepA_mal.mjs | 4 +- factor/lib/reader/reader.factor | 2 +- .../step1_read_print/step1_read_print.factor | 6 +- factor/step2_eval/step2_eval.factor | 6 +- factor/step3_env/step3_env.factor | 6 +- factor/step4_if_fn_do/step4_if_fn_do.factor | 6 +- factor/step5_tco/step5_tco.factor | 6 +- factor/step6_file/step6_file.factor | 6 +- factor/step7_quote/step7_quote.factor | 6 +- factor/step8_macros/step8_macros.factor | 6 +- factor/step9_try/step9_try.factor | 14 +++- fsharp/step1_read_print.fs | 14 ++-- fsharp/step2_eval.fs | 42 ++++------ fsharp/step3_env.fs | 30 +++---- fsharp/step4_if_fn_do.fs | 30 +++---- fsharp/step5_tco.fs | 30 +++---- fsharp/step6_file.fs | 30 +++---- fsharp/step7_quote.fs | 30 +++---- fsharp/step8_macros.fs | 30 +++---- fsharp/step9_try.fs | 35 ++++----- fsharp/stepA_mal.fs | 35 ++++----- gnu-smalltalk/step9_try.st | 3 + gnu-smalltalk/stepA_mal.st | 3 + groovy/step1_read_print.groovy | 2 +- groovy/step2_eval.groovy | 2 +- groovy/step3_env.groovy | 2 +- groovy/step4_if_fn_do.groovy | 2 +- groovy/step5_tco.groovy | 2 +- groovy/step6_file.groovy | 2 +- groovy/step7_quote.groovy | 2 +- groovy/step8_macros.groovy | 2 +- groovy/step9_try.groovy | 2 +- groovy/stepA_mal.groovy | 2 +- guile/step0_repl.scm | 14 +++- guile/step1_read_print.scm | 19 +++-- guile/step2_eval.scm | 30 ++++--- guile/step3_env.scm | 38 ++++----- guile/step4_if_fn_do.scm | 49 ++++++------ guile/step5_tco.scm | 73 +++++++++--------- guile/step6_file.scm | 52 ++++++------- guile/step7_quote.scm | 61 +++++++-------- guile/step8_macros.scm | 51 ++++++------ guile/step9_try.scm | 64 ++++++++------- guile/stepA_mal.scm | 53 +++++-------- haxe/Step0_repl.hx | 2 +- haxe/Step1_read_print.hx | 2 +- haxe/Step2_eval.hx | 2 +- haxe/Step3_env.hx | 2 +- haxe/Step4_if_fn_do.hx | 7 +- haxe/Step5_tco.hx | 7 +- haxe/Step6_file.hx | 7 +- haxe/Step7_quote.hx | 7 +- haxe/Step8_macros.hx | 7 +- haxe/Step9_try.hx | 6 +- haxe/StepA_mal.hx | 6 +- hy/step1_read_print.hy | 2 +- hy/step2_eval.hy | 2 +- hy/step3_env.hy | 2 +- hy/step4_if_fn_do.hy | 10 ++- hy/step5_tco.hy | 10 ++- hy/step6_file.hy | 10 ++- hy/step7_quote.hy | 10 ++- hy/step8_macros.hy | 10 ++- hy/step9_try.hy | 11 ++- hy/stepA_mal.hy | 11 ++- io/step4_if_fn_do.io | 5 +- io/step5_tco.io | 5 +- io/step6_file.io | 5 +- io/step7_quote.io | 5 +- io/step8_macros.io | 5 +- io/step9_try.io | 6 +- io/stepA_mal.io | 6 +- js/step0_repl.js | 8 +- js/step1_read_print.js | 6 +- js/step2_eval.js | 6 +- js/step3_env.js | 6 +- js/step4_if_fn_do.js | 6 +- js/step5_tco.js | 6 +- js/step6_file.js | 6 +- js/step7_quote.js | 6 +- js/step8_macros.js | 6 +- js/step9_try.js | 6 +- js/stepA_mal.js | 6 +- livescript/step3_env.ls | 6 +- livescript/step4_if_fn_do.ls | 8 +- livescript/step5_tco.ls | 8 +- livescript/step6_file.ls | 8 +- livescript/step7_quote.ls | 8 +- livescript/step8_macros.ls | 8 +- livescript/step9_try.ls | 16 ++-- livescript/stepA_mal.ls | 14 ++-- matlab/core.m | 18 ++++- matlab/step9_try.m | 2 +- matlab/stepA_mal.m | 2 +- miniMAL/step4_if_fn_do.json | 5 +- miniMAL/step5_tco.json | 5 +- miniMAL/step6_file.json | 5 +- miniMAL/step7_quote.json | 5 +- miniMAL/step8_macros.json | 5 +- miniMAL/step9_try.json | 11 ++- miniMAL/stepA_mal.json | 11 ++- nasm/step9_try.asm | 5 ++ nasm/stepA_mal.asm | 5 ++ nim/step9_try.nim | 15 ++-- nim/stepA_mal.nim | 6 ++ objc/step4_if_fn_do.m | 3 + objc/step5_tco.m | 3 + objc/step6_file.m | 3 + objc/step7_quote.m | 3 + objc/step8_macros.m | 3 + objc/step9_try.m | 3 + objc/stepA_mal.m | 3 + objpascal/step9_try.pas | 5 +- objpascal/stepA_mal.pas | 5 +- ocaml/core.ml | 3 +- ocaml/step9_try.ml | 8 +- ocaml/stepA_mal.ml | 8 +- perl/step9_try.pl | 8 +- perl/stepA_mal.pl | 8 +- perl6/step9_try.pl | 4 +- perl6/stepA_mal.pl | 4 +- php/step9_try.php | 2 + php/stepA_mal.php | 2 + plsql/step9_try.sql | 6 +- plsql/stepA_mal.sql | 6 +- ps/core.ps | Bin 8534 -> 8751 bytes ps/step9_try.ps | Bin 8862 -> 9144 bytes ps/stepA_mal.ps | Bin 9490 -> 9772 bytes python/core.py | 4 +- python/mal_types.py | 13 +++- python/step9_try.py | 15 +++- python/stepA_mal.py | 15 +++- r/step9_try.r | 2 +- r/stepA_mal.r | 2 +- rpython/step9_try.py | 2 + rpython/stepA_mal.py | 2 + ruby/step9_try.rb | 6 +- ruby/stepA_mal.rb | 6 +- scheme/step4_if_fn_do.scm | 6 +- scheme/step5_tco.scm | 6 +- scheme/step6_file.scm | 6 +- scheme/step7_quote.scm | 6 +- scheme/step8_macros.scm | 6 +- scheme/step9_try.scm | 28 ++++--- scheme/stepA_mal.scm | 28 ++++--- swift3/Sources/step4_if_fn_do/main.swift | 2 + swift3/Sources/step5_tco/main.swift | 2 + swift3/Sources/step6_file/main.swift | 2 + swift3/Sources/step7_quote/main.swift | 2 + swift3/Sources/step8_macros/main.swift | 2 + swift3/Sources/step9_try/main.swift | 2 + swift3/Sources/stepA_mal/main.swift | 2 + tcl/step9_try.tcl | 7 +- tcl/stepA_mal.tcl | 7 +- tests/step9_try.mal | 18 +++++ ts/Makefile | 2 +- ts/step4_if_fn_do.ts | 10 ++- ts/step5_tco.ts | 10 ++- ts/step6_file.ts | 10 ++- ts/step7_quote.ts | 10 ++- ts/step8_macros.ts | 10 ++- ts/step9_try.ts | 11 ++- ts/stepA_mal.ts | 11 ++- vb/step9_try.vb | 2 +- vb/stepA_mal.vb | 2 +- vimscript/step9_try.vim | 6 +- vimscript/stepA_mal.vim | 6 +- wasm/step4_if_fn_do.wam | 11 ++- wasm/step5_tco.wam | 11 ++- wasm/step6_file.wam | 13 +++- wasm/step7_quote.wam | 13 +++- wasm/step8_macros.wam | 13 +++- wasm/step9_try.wam | 13 +++- wasm/stepA_mal.wam | 13 +++- yorick/step9_try.i | 11 ++- yorick/stepA_mal.i | 14 +++- 270 files changed, 1527 insertions(+), 1144 deletions(-) diff --git a/ada/core.adb b/ada/core.adb index 3f9e590ca1..69fd5ae317 100644 --- a/ada/core.adb +++ b/ada/core.adb @@ -193,7 +193,7 @@ package body Core is New_Val := Deref_Lambda (Func_Param).Apply (Param_List); when Func => New_Val := Deref_Func (Func_Param).Call_Func (Param_List); - when others => raise Mal_Exception with "Swap with bad func"; + when others => raise Runtime_Exception with "Swap with bad func"; end case; Deref_Atom (Atom_Param).Set_Atom (New_Val); return New_Val; @@ -272,7 +272,7 @@ package body Core is when Nil => return Null_List (List_List); when others => null; end case; - raise Evaluation_Error with "Expecting a List"; + raise Runtime_Exception with "Expecting a List"; return Null_List (List_List); end Eval_As_List; @@ -449,14 +449,14 @@ package body Core is else - raise Mal_Exception with "Bind failed in Apply"; + raise Runtime_Exception with "Bind failed in Apply"; end if; end; else -- neither a Lambda or a Func - raise Mal_Exception; + raise Runtime_Exception with "Deref called on non-Func/Lambda"; end if; end Apply; @@ -938,7 +938,7 @@ package body Core is Rest_List := Deref_List (Cdr (Rest_List)).all; end loop; return Res; - when Hashed_List => raise Mal_Exception with "Conj on Hashed_Map"; + when Hashed_List => raise Runtime_Exception with "Conj on Hashed_Map"; end case; end Conj; @@ -964,7 +964,7 @@ package body Core is else return Vector.Duplicate (Vector.Deref_Vector (First_Param).all); end if; - when others => raise Mal_Exception; + when others => raise Runtime_Exception; end case; when Str => declare @@ -984,7 +984,7 @@ package body Core is return Res; end if; end; - when others => raise Mal_Exception; + when others => raise Runtime_Exception; end case; end Seq; diff --git a/ada/step4_if_fn_do.adb b/ada/step4_if_fn_do.adb index 3fafd98b30..d41e9bc084 100644 --- a/ada/step4_if_fn_do.adb +++ b/ada/step4_if_fn_do.adb @@ -248,7 +248,6 @@ procedure Step4_If_Fn_Do is return Printer.Pr_Str (Param); end Print; - function Rep (Param : String; Env : Envs.Env_Handle) return String is AST, Evaluated_AST : Types.Mal_Handle; begin @@ -312,7 +311,13 @@ begin when E : others => Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, - Ada.Exceptions.Exception_Information (E)); + "Error: " & Ada.Exceptions.Exception_Information (E)); + if Types.Mal_Exception_Value /= Smart_Pointers.Null_Smart_Pointer then + Ada.Text_IO.Put_Line + (Ada.Text_IO.Standard_Error, + Printer.Pr_Str (Types.Mal_Exception_Value)); + Types.Mal_Exception_Value := Smart_Pointers.Null_Smart_Pointer; + end if; end; end loop; end Step4_If_Fn_Do; diff --git a/ada/step5_tco.adb b/ada/step5_tco.adb index a0f76098f4..2234b21309 100644 --- a/ada/step5_tco.adb +++ b/ada/step5_tco.adb @@ -278,7 +278,7 @@ procedure Step5_TCO is end if; - else + else -- Not a List_List return Eval_Ast (Param, Env); @@ -293,20 +293,21 @@ procedure Step5_TCO is end Print; function Rep (Param : String; Env : Envs.Env_Handle) return String is - AST, Evaluated_AST : Types.Mal_Handle; + AST, Evaluated_AST : Types.Mal_Handle; begin - AST := Read (Param); + AST := Read (Param); - if Types.Is_Null (AST) then - return ""; - else - Evaluated_AST := Eval (AST, Env); - return Print (Evaluated_AST); - end if; + if Types.Is_Null (AST) then + return ""; + else + Evaluated_AST := Eval (AST, Env); + return Print (Evaluated_AST); + end if; end Rep; + Repl_Env : Envs.Env_Handle; @@ -364,7 +365,13 @@ begin when E : others => Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, - Ada.Exceptions.Exception_Information (E)); + "Error: " & Ada.Exceptions.Exception_Information (E)); + if Types.Mal_Exception_Value /= Smart_Pointers.Null_Smart_Pointer then + Ada.Text_IO.Put_Line + (Ada.Text_IO.Standard_Error, + Printer.Pr_Str (Types.Mal_Exception_Value)); + Types.Mal_Exception_Value := Smart_Pointers.Null_Smart_Pointer; + end if; end; end loop; end Step5_TCO; diff --git a/ada/step6_file.adb b/ada/step6_file.adb index 40ba474415..dbf65347ef 100644 --- a/ada/step6_file.adb +++ b/ada/step6_file.adb @@ -9,10 +9,8 @@ with Reader; with Smart_Pointers; with Types; - procedure Step6_File is - use Types; @@ -269,21 +267,21 @@ procedure Step6_File is else - raise Mal_Exception with "Bind failed in Apply"; + raise Runtime_Exception with "Bind failed in Apply"; end if; end; else -- neither a Lambda or a Func - raise Mal_Exception; + raise Runtime_Exception with "Deref called on non-Func/Lambda"; end if; end; end if; - else + else -- not a List_List return Eval_Ast (Param, Env); @@ -297,7 +295,6 @@ procedure Step6_File is return Printer.Pr_Str (Param); end Print; - function Rep (Param : String; Env : Envs.Env_Handle) return String is AST, Evaluated_AST : Types.Mal_Handle; begin @@ -352,6 +349,8 @@ begin Repl_Env := Envs.New_Env; + -- Core init also creates the first environment. + -- This is needed for the def!'s below. Core.Init (Repl_Env); -- Register the eval command. This needs to be done here rather than Core.Init @@ -359,7 +358,6 @@ begin Envs.Set (Repl_Env, "eval", New_Func_Mal_Type ("eval", Do_Eval'Unrestricted_Access)); RE ("(def! not (fn* (a) (if a false true)))"); - RE ("(def! load-file (fn* (f) (eval (read-string (str ""(do "" (slurp f) "")"")))))"); -- Command line processing. @@ -399,7 +397,13 @@ begin when E : others => Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, - Ada.Exceptions.Exception_Information (E)); + "Error: " & Ada.Exceptions.Exception_Information (E)); + if Types.Mal_Exception_Value /= Smart_Pointers.Null_Smart_Pointer then + Ada.Text_IO.Put_Line + (Ada.Text_IO.Standard_Error, + Printer.Pr_Str (Types.Mal_Exception_Value)); + Types.Mal_Exception_Value := Smart_Pointers.Null_Smart_Pointer; + end if; end; end loop; end if; diff --git a/ada/step7_quote.adb b/ada/step7_quote.adb index 70b2544633..848142ae7d 100644 --- a/ada/step7_quote.adb +++ b/ada/step7_quote.adb @@ -101,8 +101,6 @@ procedure Step7_Quote is end Eval_Ast; - - function Quasi_Quote_Processing (Param : Mal_Handle) return Mal_Handle is Res, First_Elem, FE_0 : Mal_Handle; L : List_Ptr; @@ -360,21 +358,21 @@ procedure Step7_Quote is else - raise Mal_Exception with "Bind failed in Apply"; + raise Runtime_Exception with "Bind failed in Apply"; end if; end; else -- neither a Lambda or a Func - raise Mal_Exception; + raise Runtime_Exception with "Deref called on non-Func/Lambda"; end if; end; end if; - else + else -- not a List_List return Eval_Ast (Param, Env); @@ -451,9 +449,10 @@ begin Envs.Set (Repl_Env, "eval", New_Func_Mal_Type ("eval", Do_Eval'Unrestricted_Access)); RE ("(def! not (fn* (a) (if a false true)))"); - RE ("(def! load-file (fn* (f) (eval (read-string (str ""(do "" (slurp f) "")"")))))"); + -- Command line processing. + Cmd_Args := 0; Command_Args := Types.New_List_Mal_Type (Types.List_List); Command_List := Types.Deref_List (Command_Args); @@ -489,7 +488,13 @@ begin when E : others => Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, - Ada.Exceptions.Exception_Information (E)); + "Error: " & Ada.Exceptions.Exception_Information (E)); + if Types.Mal_Exception_Value /= Smart_Pointers.Null_Smart_Pointer then + Ada.Text_IO.Put_Line + (Ada.Text_IO.Standard_Error, + Printer.Pr_Str (Types.Mal_Exception_Value)); + Types.Mal_Exception_Value := Smart_Pointers.Null_Smart_Pointer; + end if; end; end loop; end if; diff --git a/ada/step8_macros.adb b/ada/step8_macros.adb index 2c68e57a73..25cc8f42b7 100644 --- a/ada/step8_macros.adb +++ b/ada/step8_macros.adb @@ -434,21 +434,21 @@ procedure Step8_Macros is else - raise Mal_Exception with "Bind failed in Apply"; + raise Runtime_Exception with "Bind failed in Apply"; end if; end; else -- neither a Lambda or a Func - raise Mal_Exception; + raise Runtime_Exception with "Deref called on non-Func/Lambda"; end if; end; end if; - else + else -- not a List_List return Eval_Ast (Param, Env); @@ -529,6 +529,8 @@ begin RE ("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw ""odd number of forms to cond"")) (cons 'cond (rest (rest xs)))))))"); RE ("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); + -- Command line processing. + Cmd_Args := 0; Command_Args := Types.New_List_Mal_Type (Types.List_List); Command_List := Types.Deref_List (Command_Args); @@ -558,13 +560,19 @@ begin loop begin Ada.Text_IO.Put ("user> "); - exit when Ada.Text_IO.End_Of_Line; + exit when Ada.Text_IO.End_Of_File; Ada.Text_IO.Put_Line (Rep (Ada.Text_IO.Get_Line, Repl_Env)); exception when E : others => Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, - Ada.Exceptions.Exception_Information (E)); + "Error: " & Ada.Exceptions.Exception_Information (E)); + if Types.Mal_Exception_Value /= Smart_Pointers.Null_Smart_Pointer then + Ada.Text_IO.Put_Line + (Ada.Text_IO.Standard_Error, + Printer.Pr_Str (Types.Mal_Exception_Value)); + Types.Mal_Exception_Value := Smart_Pointers.Null_Smart_Pointer; + end if; end; end loop; end if; diff --git a/ada/step9_try.adb b/ada/step9_try.adb index 4ea09c386a..bd7fcd5c12 100644 --- a/ada/step9_try.adb +++ b/ada/step9_try.adb @@ -423,6 +423,9 @@ procedure Step9_Try is elsif Deref (First_Param).Sym_Type = Sym and then Deref_Sym (First_Param).Get_Sym = "try*" then + if Length (Rest_List) = 1 then + return Eval (Car (Rest_List), Env); + end if; declare Res : Mal_Handle; begin @@ -484,14 +487,14 @@ procedure Step9_Try is else - raise Mal_Exception with "Bind failed in Apply"; + raise Runtime_Exception with "Bind failed in Apply"; end if; end; else -- neither a Lambda or a Func - raise Mal_Exception; + raise Runtime_Exception with "Deref called on non-Func/Lambda"; end if; end; @@ -579,6 +582,8 @@ begin RE ("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw ""odd number of forms to cond"")) (cons 'cond (rest (rest xs)))))))"); RE ("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); + -- Command line processing. + Cmd_Args := 0; Command_Args := Types.New_List_Mal_Type (Types.List_List); Command_List := Types.Deref_List (Command_Args); @@ -614,7 +619,13 @@ begin when E : others => Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, - Ada.Exceptions.Exception_Information (E)); + "Error: " & Ada.Exceptions.Exception_Information (E)); + if Types.Mal_Exception_Value /= Smart_Pointers.Null_Smart_Pointer then + Ada.Text_IO.Put_Line + (Ada.Text_IO.Standard_Error, + Printer.Pr_Str (Types.Mal_Exception_Value)); + Types.Mal_Exception_Value := Smart_Pointers.Null_Smart_Pointer; + end if; end; end loop; end if; diff --git a/ada/stepa_mal.adb b/ada/stepa_mal.adb index 0b015b28d9..e49dae65ae 100644 --- a/ada/stepa_mal.adb +++ b/ada/stepa_mal.adb @@ -423,6 +423,9 @@ procedure StepA_Mal is elsif Deref (First_Param).Sym_Type = Sym and then Deref_Sym (First_Param).Get_Sym = "try*" then + if Length (Rest_List) = 1 then + return Eval (Car (Rest_List), Env); + end if; declare Res : Mal_Handle; begin @@ -484,14 +487,14 @@ procedure StepA_Mal is else - raise Mal_Exception with "Bind failed in Apply"; + raise Runtime_Exception with "Bind failed in Apply"; end if; end; else -- neither a Lambda or a Func - raise Mal_Exception; + raise Runtime_Exception with "Deref called on non-Func/Lambda"; end if; end; @@ -512,19 +515,18 @@ procedure StepA_Mal is return Printer.Pr_Str (Param); end Print; - function Rep (Param : String; Env : Envs.Env_Handle) return String is - AST, Evaluated_AST : Types.Mal_Handle; + AST, Evaluated_AST : Types.Mal_Handle; begin - AST := Read (Param); + AST := Read (Param); - if Types.Is_Null (AST) then - return ""; - else - Evaluated_AST := Eval (AST, Env); - return Print (Evaluated_AST); - end if; + if Types.Is_Null (AST) then + return ""; + else + Evaluated_AST := Eval (AST, Env); + return Print (Evaluated_AST); + end if; end Rep; @@ -558,10 +560,8 @@ procedure StepA_Mal is Command_List : Types.List_Ptr; File_Processed : Boolean := False; - begin - -- Save a function pointer back to the Eval function. -- Can't use 'Access here because of Ada rules but 'Unrestricted_Access is OK -- as we know Eval will be in scope for the lifetime of the program. @@ -584,6 +584,8 @@ begin RE ("(def! gensym (fn* [] (symbol (str ""G__"" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))"); RE ("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))"); + -- Command line processing. + Cmd_Args := 0; Command_Args := Types.New_List_Mal_Type (Types.List_List); Command_List := Types.Deref_List (Command_Args); @@ -620,7 +622,13 @@ begin when E : others => Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, - Ada.Exceptions.Exception_Information (E)); + "Error: " & Ada.Exceptions.Exception_Information (E)); + if Types.Mal_Exception_Value /= Smart_Pointers.Null_Smart_Pointer then + Ada.Text_IO.Put_Line + (Ada.Text_IO.Standard_Error, + Printer.Pr_Str (Types.Mal_Exception_Value)); + Types.Mal_Exception_Value := Smart_Pointers.Null_Smart_Pointer; + end if; end; end loop; end if; diff --git a/ada/types-vector.adb b/ada/types-vector.adb index 3d1ae0403a..b4dc70f56e 100644 --- a/ada/types-vector.adb +++ b/ada/types-vector.adb @@ -91,7 +91,7 @@ package body Types.Vector is overriding function Nth (L : Vector_Mal_Type; N : Natural) return Mal_Handle is begin if N >= L.Length then - raise Mal_Exception with "Nth (vector): Index out of range"; + raise Runtime_Exception with "Nth (vector): Index out of range"; else return Mal_Vectors.Element (L.Vec, Vec_Index (N)); end if; diff --git a/ada/types.adb b/ada/types.adb index 56eba7476f..29b8d2b181 100644 --- a/ada/types.adb +++ b/ada/types.adb @@ -841,7 +841,7 @@ package body Types is end loop; - raise Mal_Exception with "Nth (list): Index out of range"; + raise Runtime_Exception with "Nth (list): Index out of range"; end Nth; @@ -1064,7 +1064,7 @@ package body Types is else - raise Mal_Exception with "Bind failed in Apply"; + raise Runtime_Exception with "Bind failed in Apply"; end if; diff --git a/ada/types.ads b/ada/types.ads index d2a52fd7a8..8329453bca 100644 --- a/ada/types.ads +++ b/ada/types.ads @@ -311,6 +311,8 @@ package Types is with function Float_Rel_Op (A, B : Mal_Float) return Boolean; function Rel_Op (A, B : Mal_Handle) return Mal_Handle; + Runtime_Exception : exception; + Mal_Exception : exception; -- So tempting to call this Mal_Function but... Mal_Exception_Value : Mal_Handle; -- Used by mal's throw command diff --git a/c/step0_repl.c b/c/step0_repl.c index af69bd6ea9..ddb7f6e2b7 100644 --- a/c/step0_repl.c +++ b/c/step0_repl.c @@ -32,13 +32,13 @@ int main() // Set the initial prompt snprintf(prompt, sizeof(prompt), "user> "); - + for(;;) { ast = READ(prompt); if (!ast) return 0; exp = EVAL(ast, NULL); puts(PRINT(exp)); - + free(ast); // Free input string } } diff --git a/c/step1_read_print.c b/c/step1_read_print.c index 215624e422..cdc45cd441 100644 --- a/c/step1_read_print.c +++ b/c/step1_read_print.c @@ -34,9 +34,6 @@ MalVal *EVAL(MalVal *ast, GHashTable *env) { // print char *PRINT(MalVal *exp) { if (mal_error) { - fprintf(stderr, "Error: %s\n", mal_error->val.string); - malval_free(mal_error); - mal_error = NULL; return NULL; } return _pr_str(exp,1); @@ -66,7 +63,7 @@ int main() // Set the initial prompt snprintf(prompt, sizeof(prompt), "user> "); - + // repl loop for(;;) { exp = RE(NULL, prompt, NULL); @@ -75,7 +72,11 @@ int main() } output = PRINT(exp); - if (output) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", _pr_str(mal_error,1)); + malval_free(mal_error); + mal_error = NULL; + } else if (output) { puts(output); MAL_GC_FREE(output); // Free output string } diff --git a/c/step2_eval.c b/c/step2_eval.c index d075842455..8c54184c05 100644 --- a/c/step2_eval.c +++ b/c/step2_eval.c @@ -87,9 +87,6 @@ MalVal *EVAL(MalVal *ast, GHashTable *env) { // print char *PRINT(MalVal *exp) { if (mal_error) { - fprintf(stderr, "Error: %s\n", mal_error->val.string); - malval_free(mal_error); - mal_error = NULL; return NULL; } return _pr_str(exp,1); @@ -137,7 +134,7 @@ int main() // Set the initial prompt and environment snprintf(prompt, sizeof(prompt), "user> "); init_repl_env(); - + // repl loop for(;;) { exp = RE(repl_env, prompt, NULL); @@ -146,7 +143,11 @@ int main() } output = PRINT(exp); - if (output) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", _pr_str(mal_error,1)); + malval_free(mal_error); + mal_error = NULL; + } else if (output) { puts(output); MAL_GC_FREE(output); // Free output string } diff --git a/c/step3_env.c b/c/step3_env.c index ba932792ea..d51704e053 100644 --- a/c/step3_env.c +++ b/c/step3_env.c @@ -112,9 +112,6 @@ MalVal *EVAL(MalVal *ast, Env *env) { // print char *PRINT(MalVal *exp) { if (mal_error) { - fprintf(stderr, "Error: %s\n", mal_error->val.string); - malval_free(mal_error); - mal_error = NULL; return NULL; } return _pr_str(exp,1); @@ -162,7 +159,7 @@ int main() // Set the initial prompt and environment snprintf(prompt, sizeof(prompt), "user> "); init_repl_env(); - + // repl loop for(;;) { exp = RE(repl_env, prompt, NULL); @@ -171,7 +168,11 @@ int main() } output = PRINT(exp); - if (output) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", _pr_str(mal_error,1)); + malval_free(mal_error); + mal_error = NULL; + } else if (output) { puts(output); MAL_GC_FREE(output); // Free output string } diff --git a/c/step4_if_fn_do.c b/c/step4_if_fn_do.c index efe45e2c82..c6628c16c2 100644 --- a/c/step4_if_fn_do.c +++ b/c/step4_if_fn_do.c @@ -149,9 +149,6 @@ MalVal *EVAL(MalVal *ast, Env *env) { // print char *PRINT(MalVal *exp) { if (mal_error) { - fprintf(stderr, "Error: %s\n", mal_error->val.string); - malval_free(mal_error); - mal_error = NULL; return NULL; } return _pr_str(exp,1); @@ -200,7 +197,7 @@ int main() // Set the initial prompt and environment snprintf(prompt, sizeof(prompt), "user> "); init_repl_env(); - + // repl loop for(;;) { exp = RE(repl_env, prompt, NULL); @@ -209,7 +206,11 @@ int main() } output = PRINT(exp); - if (output) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", _pr_str(mal_error,1)); + malval_free(mal_error); + mal_error = NULL; + } else if (output) { puts(output); MAL_GC_FREE(output); // Free output string } diff --git a/c/step5_tco.c b/c/step5_tco.c index 094e87e976..917e3e3807 100644 --- a/c/step5_tco.c +++ b/c/step5_tco.c @@ -162,9 +162,6 @@ MalVal *EVAL(MalVal *ast, Env *env) { // print char *PRINT(MalVal *exp) { if (mal_error) { - fprintf(stderr, "Error: %s\n", mal_error->val.string); - malval_free(mal_error); - mal_error = NULL; return NULL; } return _pr_str(exp,1); @@ -213,7 +210,7 @@ int main() // Set the initial prompt and environment snprintf(prompt, sizeof(prompt), "user> "); init_repl_env(); - + // repl loop for(;;) { exp = RE(repl_env, prompt, NULL); @@ -222,7 +219,11 @@ int main() } output = PRINT(exp); - if (output) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", _pr_str(mal_error,1)); + malval_free(mal_error); + mal_error = NULL; + } else if (output) { puts(output); MAL_GC_FREE(output); // Free output string } diff --git a/c/step6_file.c b/c/step6_file.c index 588e2c932c..2feb873ca9 100644 --- a/c/step6_file.c +++ b/c/step6_file.c @@ -162,9 +162,6 @@ MalVal *EVAL(MalVal *ast, Env *env) { // print char *PRINT(MalVal *exp) { if (mal_error) { - fprintf(stderr, "Error: %s\n", mal_error->val.string); - malval_free(mal_error); - mal_error = NULL; return NULL; } return _pr_str(exp,1); @@ -227,7 +224,7 @@ int main(int argc, char *argv[]) // Set the initial prompt and environment snprintf(prompt, sizeof(prompt), "user> "); init_repl_env(argc, argv); - + if (argc > 1) { char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); RE(repl_env, "", cmd); @@ -242,7 +239,11 @@ int main(int argc, char *argv[]) } output = PRINT(exp); - if (output) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", _pr_str(mal_error,1)); + malval_free(mal_error); + mal_error = NULL; + } else if (output) { puts(output); MAL_GC_FREE(output); // Free output string } diff --git a/c/step7_quote.c b/c/step7_quote.c index ac8d825621..2a601b99aa 100644 --- a/c/step7_quote.c +++ b/c/step7_quote.c @@ -199,9 +199,6 @@ MalVal *EVAL(MalVal *ast, Env *env) { // print char *PRINT(MalVal *exp) { if (mal_error) { - fprintf(stderr, "Error: %s\n", mal_error->val.string); - malval_free(mal_error); - mal_error = NULL; return NULL; } return _pr_str(exp,1); @@ -264,7 +261,7 @@ int main(int argc, char *argv[]) // Set the initial prompt and environment snprintf(prompt, sizeof(prompt), "user> "); init_repl_env(argc, argv); - + if (argc > 1) { char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); RE(repl_env, "", cmd); @@ -279,7 +276,11 @@ int main(int argc, char *argv[]) } output = PRINT(exp); - if (output) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", _pr_str(mal_error,1)); + malval_free(mal_error); + mal_error = NULL; + } else if (output) { puts(output); MAL_GC_FREE(output); // Free output string } diff --git a/c/step8_macros.c b/c/step8_macros.c index 01aac07447..335642ba85 100644 --- a/c/step8_macros.c +++ b/c/step8_macros.c @@ -65,7 +65,7 @@ int is_macro_call(MalVal *ast, Env *env) { env_find(env, a0) && env_get(env, a0)->ismacro; } - + MalVal *macroexpand(MalVal *ast, Env *env) { if (!ast || mal_error) return NULL; while (is_macro_call(ast, env)) { @@ -241,9 +241,6 @@ MalVal *EVAL(MalVal *ast, Env *env) { // print char *PRINT(MalVal *exp) { if (mal_error) { - fprintf(stderr, "Error: %s\n", mal_error->val.string); - malval_free(mal_error); - mal_error = NULL; return NULL; } return _pr_str(exp,1); @@ -308,7 +305,7 @@ int main(int argc, char *argv[]) // Set the initial prompt and environment snprintf(prompt, sizeof(prompt), "user> "); init_repl_env(argc, argv); - + if (argc > 1) { char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); RE(repl_env, "", cmd); @@ -323,7 +320,11 @@ int main(int argc, char *argv[]) } output = PRINT(exp); - if (output) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", _pr_str(mal_error,1)); + malval_free(mal_error); + mal_error = NULL; + } else if (output) { puts(output); MAL_GC_FREE(output); // Free output string } diff --git a/c/step9_try.c b/c/step9_try.c index 4f40fa50a3..9c555aca1f 100644 --- a/c/step9_try.c +++ b/c/step9_try.c @@ -66,7 +66,7 @@ int is_macro_call(MalVal *ast, Env *env) { env_find(env, a0) && env_get(env, a0)->ismacro; } - + MalVal *macroexpand(MalVal *ast, Env *env) { if (!ast || mal_error) return NULL; while (is_macro_call(ast, env)) { @@ -189,8 +189,11 @@ MalVal *EVAL(MalVal *ast, Env *env) { strcmp("try*", a0->val.string) == 0) { //g_print("eval apply try*\n"); MalVal *a1 = _nth(ast, 1); - MalVal *a2 = _nth(ast, 2); MalVal *res = EVAL(a1, env); + if (ast->val.array->len < 3) { + return &mal_nil; + } + MalVal *a2 = _nth(ast, 2); if (!mal_error) { return res; } MalVal *a20 = _nth(a2, 0); if (strcmp("catch*", a20->val.string) == 0) { @@ -263,9 +266,6 @@ MalVal *EVAL(MalVal *ast, Env *env) { // print char *PRINT(MalVal *exp) { if (mal_error) { - fprintf(stderr, "Error: %s\n", mal_error->val.string); - malval_free(mal_error); - mal_error = NULL; return NULL; } return _pr_str(exp,1); @@ -330,7 +330,7 @@ int main(int argc, char *argv[]) // Set the initial prompt and environment snprintf(prompt, sizeof(prompt), "user> "); init_repl_env(argc, argv); - + if (argc > 1) { char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); RE(repl_env, "", cmd); @@ -345,7 +345,11 @@ int main(int argc, char *argv[]) } output = PRINT(exp); - if (output) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", _pr_str(mal_error,1)); + malval_free(mal_error); + mal_error = NULL; + } else if (output) { puts(output); MAL_GC_FREE(output); // Free output string } diff --git a/c/stepA_mal.c b/c/stepA_mal.c index c9237f3895..736eb0964d 100644 --- a/c/stepA_mal.c +++ b/c/stepA_mal.c @@ -66,7 +66,7 @@ int is_macro_call(MalVal *ast, Env *env) { env_find(env, a0) && env_get(env, a0)->ismacro; } - + MalVal *macroexpand(MalVal *ast, Env *env) { if (!ast || mal_error) return NULL; while (is_macro_call(ast, env)) { @@ -194,8 +194,11 @@ MalVal *EVAL(MalVal *ast, Env *env) { strcmp("try*", a0->val.string) == 0) { //g_print("eval apply try*\n"); MalVal *a1 = _nth(ast, 1); - MalVal *a2 = _nth(ast, 2); MalVal *res = EVAL(a1, env); + if (ast->val.array->len < 3) { + return &mal_nil; + } + MalVal *a2 = _nth(ast, 2); if (!mal_error) { return res; } MalVal *a20 = _nth(a2, 0); if (strcmp("catch*", a20->val.string) == 0) { @@ -268,9 +271,6 @@ MalVal *EVAL(MalVal *ast, Env *env) { // print char *PRINT(MalVal *exp) { if (mal_error) { - fprintf(stderr, "Error: %s\n", mal_error->val.string); - malval_free(mal_error); - mal_error = NULL; return NULL; } return _pr_str(exp,1); @@ -292,6 +292,7 @@ MalVal *RE(Env *env, char *prompt, char *str) { // Setup the initial REPL environment Env *repl_env; + MalVal *do_eval(MalVal *ast) { return EVAL(ast, repl_env); } void init_repl_env(int argc, char *argv[]) { @@ -337,7 +338,7 @@ int main(int argc, char *argv[]) // Set the initial prompt and environment snprintf(prompt, sizeof(prompt), "user> "); init_repl_env(argc, argv); - + if (argc > 1) { char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); RE(repl_env, "", cmd); @@ -353,7 +354,11 @@ int main(int argc, char *argv[]) } output = PRINT(exp); - if (output) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", _pr_str(mal_error,1)); + malval_free(mal_error); + mal_error = NULL; + } else if (output) { puts(output); MAL_GC_FREE(output); // Free output string } diff --git a/chuck/step1_read_print.ck b/chuck/step1_read_print.ck index b173cf36c4..103d03ba9b 100644 --- a/chuck/step1_read_print.ck +++ b/chuck/step1_read_print.ck @@ -35,15 +35,7 @@ fun string PRINT(MalObject m) fun string errorMessage(MalObject m) { (m$MalError).value() @=> MalObject value; - - if( value.type == "string" ) - { - return Printer.pr_str(value, false); - } - else - { - return "exception: " + Printer.pr_str(value, true); - } + return "exception: " + Printer.pr_str(value, true); } fun string rep(string input) diff --git a/chuck/step2_eval.ck b/chuck/step2_eval.ck index d3753ae8ce..2d740a7467 100644 --- a/chuck/step2_eval.ck +++ b/chuck/step2_eval.ck @@ -138,15 +138,7 @@ new MalDiv @=> repl_env["/"]; fun string errorMessage(MalObject m) { (m$MalError).value() @=> MalObject value; - - if( value.type == "string" ) - { - return Printer.pr_str(value, false); - } - else - { - return "exception: " + Printer.pr_str(value, true); - } + return "exception: " + Printer.pr_str(value, true); } fun string rep(string input) diff --git a/chuck/step3_env.ck b/chuck/step3_env.ck index 7c0bb7cd31..ad8de3c7bd 100644 --- a/chuck/step3_env.ck +++ b/chuck/step3_env.ck @@ -167,15 +167,7 @@ repl_env.set("/", new MalDiv); fun string errorMessage(MalObject m) { (m$MalError).value() @=> MalObject value; - - if( value.type == "string" ) - { - return Printer.pr_str(value, false); - } - else - { - return "exception: " + Printer.pr_str(value, true); - } + return "exception: " + Printer.pr_str(value, true); } fun string rep(string input) diff --git a/chuck/step4_if_fn_do.ck b/chuck/step4_if_fn_do.ck index 16a4cd91e5..dd6c92507e 100644 --- a/chuck/step4_if_fn_do.ck +++ b/chuck/step4_if_fn_do.ck @@ -236,15 +236,7 @@ for( 0 => int i; i < Core.names.size(); i++ ) fun string errorMessage(MalObject m) { (m$MalError).value() @=> MalObject value; - - if( value.type == "string" ) - { - return Printer.pr_str(value, false); - } - else - { - return "exception: " + Printer.pr_str(value, true); - } + return "exception: " + Printer.pr_str(value, true); } fun string rep(string input) diff --git a/chuck/step5_tco.ck b/chuck/step5_tco.ck index cc07f5f780..33be7e9c18 100644 --- a/chuck/step5_tco.ck +++ b/chuck/step5_tco.ck @@ -242,15 +242,7 @@ for( 0 => int i; i < Core.names.size(); i++ ) fun string errorMessage(MalObject m) { (m$MalError).value() @=> MalObject value; - - if( value.type == "string" ) - { - return Printer.pr_str(value, false); - } - else - { - return "exception: " + Printer.pr_str(value, true); - } + return "exception: " + Printer.pr_str(value, true); } fun string rep(string input) diff --git a/chuck/step6_file.ck b/chuck/step6_file.ck index f9c5a4b583..5d1c55f8fc 100644 --- a/chuck/step6_file.ck +++ b/chuck/step6_file.ck @@ -286,15 +286,7 @@ repl_env.set("*ARGV*", MalList.create(MalArgv(args))); fun string errorMessage(MalObject m) { (m$MalError).value() @=> MalObject value; - - if( value.type == "string" ) - { - return Printer.pr_str(value, false); - } - else - { - return "exception: " + Printer.pr_str(value, true); - } + return "exception: " + Printer.pr_str(value, true); } fun string rep(string input) diff --git a/chuck/step7_quote.ck b/chuck/step7_quote.ck index d1aef647e7..6470456a3d 100644 --- a/chuck/step7_quote.ck +++ b/chuck/step7_quote.ck @@ -341,15 +341,7 @@ repl_env.set("*ARGV*", MalList.create(MalArgv(args))); fun string errorMessage(MalObject m) { (m$MalError).value() @=> MalObject value; - - if( value.type == "string" ) - { - return Printer.pr_str(value, false); - } - else - { - return "exception: " + Printer.pr_str(value, true); - } + return "exception: " + Printer.pr_str(value, true); } fun string rep(string input) diff --git a/chuck/step8_macros.ck b/chuck/step8_macros.ck index 09b3a54ffe..f52e0de4a7 100644 --- a/chuck/step8_macros.ck +++ b/chuck/step8_macros.ck @@ -412,15 +412,7 @@ repl_env.set("*ARGV*", MalList.create(MalArgv(args))); fun string errorMessage(MalObject m) { (m$MalError).value() @=> MalObject value; - - if( value.type == "string" ) - { - return Printer.pr_str(value, false); - } - else - { - return "exception: " + Printer.pr_str(value, true); - } + return "exception: " + Printer.pr_str(value, true); } fun string rep(string input) diff --git a/chuck/step9_try.ck b/chuck/step9_try.ck index c040462929..539d1d8fd1 100644 --- a/chuck/step9_try.ck +++ b/chuck/step9_try.ck @@ -212,7 +212,7 @@ fun MalObject EVAL(MalObject m, Env env) { EVAL(ast[1], env) @=> MalObject value; - if( value.type != "error" ) + if( (value.type != "error") || (ast.size() < 3) ) { return value; } @@ -429,15 +429,7 @@ repl_env.set("*ARGV*", MalList.create(MalArgv(args))); fun string errorMessage(MalObject m) { (m$MalError).value() @=> MalObject value; - - if( value.type == "string" ) - { - return Printer.pr_str(value, false); - } - else - { - return "exception: " + Printer.pr_str(value, true); - } + return "exception: " + Printer.pr_str(value, true); } fun string rep(string input) diff --git a/chuck/stepA_mal.ck b/chuck/stepA_mal.ck index 59accd51b1..5c73489feb 100644 --- a/chuck/stepA_mal.ck +++ b/chuck/stepA_mal.ck @@ -212,7 +212,7 @@ fun MalObject EVAL(MalObject m, Env env) { EVAL(ast[1], env) @=> MalObject value; - if( value.type != "error" ) + if( (value.type != "error") || (ast.size() < 3) ) { return value; } @@ -431,15 +431,7 @@ repl_env.set("*host-language*", MalString.create("chuck")); fun string errorMessage(MalObject m) { (m$MalError).value() @=> MalObject value; - - if( value.type == "string" ) - { - return Printer.pr_str(value, false); - } - else - { - return "exception: " + Printer.pr_str(value, true); - } + return "exception: " + Printer.pr_str(value, true); } fun string rep(string input) diff --git a/clojure/src/mal/step9_try.cljc b/clojure/src/mal/step9_try.cljc index 529c41c2ab..2e0b91c75d 100644 --- a/clojure/src/mal/step9_try.cljc +++ b/clojure/src/mal/step9_try.cljc @@ -109,7 +109,8 @@ (catch #?(:clj Throwable :cljs :default) t (EVAL (nth a2 2) (env/env env [(nth a2 1)] - [#?(:clj (.getMessage t) + [#?(:clj (or (.getMessage t) + (.toString t)) :cljs (.-message t))])))) (EVAL a1 env)) @@ -169,6 +170,9 @@ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment (try (println (rep line)) + #?(:cljs (catch ExceptionInfo e + (println "Error:" (or (:data (ex-data e)) + (.-stack e))))) #?(:clj (catch Throwable e (clojure.repl/pst e)) :cljs (catch js/Error e (println (.-stack e)))))) (recur)))) diff --git a/clojure/src/mal/stepA_mal.cljc b/clojure/src/mal/stepA_mal.cljc index 5dd7508fcc..efa7494684 100644 --- a/clojure/src/mal/stepA_mal.cljc +++ b/clojure/src/mal/stepA_mal.cljc @@ -117,7 +117,8 @@ (catch #?(:clj Throwable :cljs :default) t (EVAL (nth a2 2) (env/env env [(nth a2 1)] - [#?(:clj (.getMessage t) + [#?(:clj (or (.getMessage t) + (.toString t)) :cljs (.-message t))])))) (EVAL a1 env)) @@ -181,6 +182,9 @@ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment (try (println (rep line)) + #?(:cljs (catch ExceptionInfo e + (println "Error:" (or (:data (ex-data e)) + (.-stack e))))) #?(:clj (catch Throwable e (clojure.repl/pst e)) :cljs (catch js/Error e (println (.-stack e)))))) (recur)))) diff --git a/coffee/core.coffee b/coffee/core.coffee index 518a4c8259..4339beed7a 100644 --- a/coffee/core.coffee +++ b/coffee/core.coffee @@ -38,7 +38,7 @@ with_meta = (obj,m) -> exports.ns = { '=': (a,b) -> types._equal_Q(a,b), - 'throw': (a) -> throw a, + 'throw': (a) -> throw {"object": a}, 'nil?': types._nil_Q, 'true?': types._true_Q, 'false?': types._false_Q, diff --git a/coffee/step1_read_print.coffee b/coffee/step1_read_print.coffee index d5ab6b7a1b..df5cf74855 100644 --- a/coffee/step1_read_print.coffee +++ b/coffee/step1_read_print.coffee @@ -23,7 +23,7 @@ while (line = readline.readline("user> ")) != null continue if exc instanceof reader.BlankException if exc.stack? and exc.stack.length > 2000 console.log exc.stack.slice(0,1000) + "\n ..." + exc.stack.slice(-1000) - else if exc.stack? console.log exc.stack - else console.log exc + else if exc.stack? then console.log exc.stack + else console.log exc # vim: ts=2:sw=2 diff --git a/coffee/step2_eval.coffee b/coffee/step2_eval.coffee index a6fe840a1e..8e82ba28d5 100644 --- a/coffee/step2_eval.coffee +++ b/coffee/step2_eval.coffee @@ -49,7 +49,7 @@ while (line = readline.readline("user> ")) != null continue if exc instanceof reader.BlankException if exc.stack? and exc.stack.length > 2000 console.log exc.stack.slice(0,1000) + "\n ..." + exc.stack.slice(-1000) - else if exc.stack? console.log exc.stack - else console.log exc + else if exc.stack? then console.log exc.stack + else console.log exc # vim: ts=2:sw=2 diff --git a/coffee/step3_env.coffee b/coffee/step3_env.coffee index 254c2bee95..9dac61a897 100644 --- a/coffee/step3_env.coffee +++ b/coffee/step3_env.coffee @@ -60,7 +60,7 @@ while (line = readline.readline("user> ")) != null continue if exc instanceof reader.BlankException if exc.stack? and exc.stack.length > 2000 console.log exc.stack.slice(0,1000) + "\n ..." + exc.stack.slice(-1000) - else if exc.stack? console.log exc.stack - else console.log exc + else if exc.stack? then console.log exc.stack + else console.log exc # vim: ts=2:sw=2 diff --git a/coffee/step4_if_fn_do.coffee b/coffee/step4_if_fn_do.coffee index f4e0c45662..2210aa3409 100644 --- a/coffee/step4_if_fn_do.coffee +++ b/coffee/step4_if_fn_do.coffee @@ -73,7 +73,7 @@ while (line = readline.readline("user> ")) != null continue if exc instanceof reader.BlankException if exc.stack? and exc.stack.length > 2000 console.log exc.stack.slice(0,1000) + "\n ..." + exc.stack.slice(-1000) - else if exc.stack? console.log exc.stack - else console.log exc + else if exc.stack? then console.log exc.stack + else console.log exc # vim: ts=2:sw=2 diff --git a/coffee/step5_tco.coffee b/coffee/step5_tco.coffee index 1541d468ef..9ed9ce8364 100644 --- a/coffee/step5_tco.coffee +++ b/coffee/step5_tco.coffee @@ -79,7 +79,7 @@ while (line = readline.readline("user> ")) != null continue if exc instanceof reader.BlankException if exc.stack? and exc.stack.length > 2000 console.log exc.stack.slice(0,1000) + "\n ..." + exc.stack.slice(-1000) - else if exc.stack? console.log exc.stack - else console.log exc + else if exc.stack? then console.log exc.stack + else console.log exc # vim: ts=2:sw=2 diff --git a/coffee/step6_file.coffee b/coffee/step6_file.coffee index ed6481f7f7..b1b786c1fb 100644 --- a/coffee/step6_file.coffee +++ b/coffee/step6_file.coffee @@ -87,7 +87,7 @@ while (line = readline.readline("user> ")) != null continue if exc instanceof reader.BlankException if exc.stack? and exc.stack.length > 2000 console.log exc.stack.slice(0,1000) + "\n ..." + exc.stack.slice(-1000) - else if exc.stack? console.log exc.stack - else console.log exc + else if exc.stack? then console.log exc.stack + else console.log exc # vim: ts=2:sw=2 diff --git a/coffee/step7_quote.coffee b/coffee/step7_quote.coffee index 1304c8fde4..bde2a8790c 100644 --- a/coffee/step7_quote.coffee +++ b/coffee/step7_quote.coffee @@ -103,7 +103,7 @@ while (line = readline.readline("user> ")) != null continue if exc instanceof reader.BlankException if exc.stack? and exc.stack.length > 2000 console.log exc.stack.slice(0,1000) + "\n ..." + exc.stack.slice(-1000) - else if exc.stack? console.log exc.stack - else console.log exc + else if exc.stack? then console.log exc.stack + else console.log exc # vim: ts=2:sw=2 diff --git a/coffee/step8_macros.coffee b/coffee/step8_macros.coffee index fe4d06bb7e..3b552f8d54 100644 --- a/coffee/step8_macros.coffee +++ b/coffee/step8_macros.coffee @@ -123,7 +123,7 @@ while (line = readline.readline("user> ")) != null continue if exc instanceof reader.BlankException if exc.stack? and exc.stack.length > 2000 console.log exc.stack.slice(0,1000) + "\n ..." + exc.stack.slice(-1000) - else if exc.stack? console.log exc.stack - else console.log exc + else if exc.stack? then console.log exc.stack + else console.log exc # vim: ts=2:sw=2 diff --git a/coffee/step9_try.coffee b/coffee/step9_try.coffee index a73f5d40ff..d5bbe010a3 100644 --- a/coffee/step9_try.coffee +++ b/coffee/step9_try.coffee @@ -75,7 +75,8 @@ EVAL = (ast, env) -> try return EVAL(a1, env) catch exc if a2 && a2[0].name == "catch*" - if exc instanceof Error then exc = exc.message + if exc.object? then exc = exc.object + else exc = exc.message return EVAL a2[2], new Env(env, [a2[1]], [exc]) else throw exc @@ -131,7 +132,11 @@ while (line = readline.readline("user> ")) != null continue if exc instanceof reader.BlankException if exc.stack? and exc.stack.length > 2000 console.log exc.stack.slice(0,1000) + "\n ..." + exc.stack.slice(-1000) - else if exc.stack? console.log exc.stack - else console.log exc + else if exc.stack? + console.log exc.stack + else if exc.object? + console.log "Error:", printer._pr_str exc.object, true + else + console.log exc # vim: ts=2:sw=2 diff --git a/coffee/stepA_mal.coffee b/coffee/stepA_mal.coffee index 3aeb455810..2780e84f70 100644 --- a/coffee/stepA_mal.coffee +++ b/coffee/stepA_mal.coffee @@ -75,7 +75,8 @@ EVAL = (ast, env) -> try return EVAL(a1, env) catch exc if a2 && a2[0].name == "catch*" - if exc instanceof Error then exc = exc.message + if exc.object? then exc = exc.object + else exc = exc.message return EVAL a2[2], new Env(env, [a2[1]], [exc]) else throw exc @@ -141,7 +142,11 @@ while (line = readline.readline("user> ")) != null continue if exc instanceof reader.BlankException if exc.stack? and exc.stack.length > 2000 console.log exc.stack.slice(0,1000) + "\n ..." + exc.stack.slice(-1000) - else if exc.stack? console.log exc.stack - else console.log exc + else if exc.stack? + console.log exc.stack + else if exc.object? + console.log "Error:", printer._pr_str exc.object, true + else + console.log exc # vim: ts=2:sw=2 diff --git a/common-lisp/src/step9_try.lisp b/common-lisp/src/step9_try.lisp index e6257a3900..87cc341f9b 100644 --- a/common-lisp/src/step9_try.lisp +++ b/common-lisp/src/step9_try.lisp @@ -195,10 +195,11 @@ (cons :is-macro nil)))))) ((mal-data-value= mal-try* (first forms)) - (handler-case - (return (mal-eval (second forms) env)) - (error (condition) - (when (third forms) + (if (not (third forms)) + (return (mal-eval (second forms) env)) + (handler-case + (return (mal-eval (second forms) env)) + (error (condition) (let ((catch-forms (mal-data-value (third forms)))) (when (mal-data-value= mal-catch* (first catch-forms)) diff --git a/common-lisp/src/stepA_mal.lisp b/common-lisp/src/stepA_mal.lisp index 73dc67cbef..3001b9d184 100644 --- a/common-lisp/src/stepA_mal.lisp +++ b/common-lisp/src/stepA_mal.lisp @@ -194,10 +194,11 @@ (cons :is-macro nil)))))) ((mal-data-value= mal-try* (first forms)) - (handler-case - (return (mal-eval (second forms) env)) - (error (condition) - (when (third forms) + (if (not (third forms)) + (return (mal-eval (second forms) env)) + (handler-case + (return (mal-eval (second forms) env)) + (error (condition) (let ((catch-forms (mal-data-value (third forms)))) (when (mal-data-value= mal-catch* (first catch-forms)) diff --git a/cpp/Reader.cpp b/cpp/Reader.cpp index cdb4e91078..dbcb6c9c83 100644 --- a/cpp/Reader.cpp +++ b/cpp/Reader.cpp @@ -98,10 +98,10 @@ void Tokeniser::nextToken() String mismatch(m_iter, m_end); if (mismatch[0] == '"') { - MAL_CHECK(false, "Expected \", got EOF"); + MAL_CHECK(false, "expected '\"', got EOF"); } else { - MAL_CHECK(false, "Unexpected \"%s\"", mismatch.c_str()); + MAL_CHECK(false, "unexpected '%s'", mismatch.c_str()); } } @@ -129,11 +129,11 @@ malValuePtr readStr(const String& input) static malValuePtr readForm(Tokeniser& tokeniser) { - MAL_CHECK(!tokeniser.eof(), "Expected form, got EOF"); + MAL_CHECK(!tokeniser.eof(), "expected form, got EOF"); String token = tokeniser.peek(); MAL_CHECK(!std::regex_match(token, closeRegex), - "Unexpected \"%s\"", token.c_str()); + "unexpected '%s'", token.c_str()); if (token == "(") { tokeniser.next(); @@ -213,7 +213,7 @@ static void readList(Tokeniser& tokeniser, malValueVec* items, const String& end) { while (1) { - MAL_CHECK(!tokeniser.eof(), "Expected \"%s\", got EOF", end.c_str()); + MAL_CHECK(!tokeniser.eof(), "expected '%s', got EOF", end.c_str()); if (tokeniser.peek() == end) { tokeniser.next(); return; diff --git a/cpp/step9_try.cpp b/cpp/step9_try.cpp index 17cf178cc0..62933bdfc5 100644 --- a/cpp/step9_try.cpp +++ b/cpp/step9_try.cpp @@ -50,8 +50,11 @@ static String safeRep(const String& input, malEnvPtr env) catch (malEmptyInputException&) { return String(); } + catch (malValuePtr& mv) { + return "Error: " + mv->print(true); + } catch (String& s) { - return s; + return "Error: " + s; }; } diff --git a/cpp/stepA_mal.cpp b/cpp/stepA_mal.cpp index 70ed6ac9a3..cda56f08cb 100644 --- a/cpp/stepA_mal.cpp +++ b/cpp/stepA_mal.cpp @@ -51,8 +51,11 @@ static String safeRep(const String& input, malEnvPtr env) catch (malEmptyInputException&) { return String(); } + catch (malValuePtr& mv) { + return "Error: " + mv->print(true); + } catch (String& s) { - return s; + return "Error: " + s; }; } diff --git a/crystal/step1_read_print.cr b/crystal/step1_read_print.cr index cdc05d57e1..4d7195895e 100755 --- a/crystal/step1_read_print.cr +++ b/crystal/step1_read_print.cr @@ -30,7 +30,9 @@ end while line = Readline.readline("user> ", true) begin puts Mal.rep(line) + rescue e : Mal::RuntimeException + STDERR.puts "Error: #{pr_str(e.thrown, true)}" rescue e - STDERR.puts e + STDERR.puts "Error: #{e}" end end diff --git a/crystal/step2_eval.cr b/crystal/step2_eval.cr index eeef93c360..fd441d33a6 100755 --- a/crystal/step2_eval.cr +++ b/crystal/step2_eval.cr @@ -89,7 +89,9 @@ REPL_ENV = { while line = Readline.readline("user> ", true) begin puts Mal.rep(line) + rescue e : Mal::RuntimeException + STDERR.puts "Error: #{pr_str(e.thrown, true)}" rescue e - STDERR.puts e + STDERR.puts "Error: #{e}" end end diff --git a/crystal/step3_env.cr b/crystal/step3_env.cr index 171dd89a2f..a740080439 100755 --- a/crystal/step3_env.cr +++ b/crystal/step3_env.cr @@ -112,7 +112,9 @@ end while line = Readline.readline("user> ", true) begin puts Mal.rep(line) + rescue e : Mal::RuntimeException + STDERR.puts "Error: #{pr_str(e.thrown, true)}" rescue e - STDERR.puts e + STDERR.puts "Error: #{e}" end end diff --git a/crystal/step4_if_fn_do.cr b/crystal/step4_if_fn_do.cr index c20f1300d2..f4a41da132 100755 --- a/crystal/step4_if_fn_do.cr +++ b/crystal/step4_if_fn_do.cr @@ -128,7 +128,9 @@ Mal.rep "(def! not (fn* (a) (if a false true)))" while line = Readline.readline("user> ") begin puts Mal.rep(line) + rescue e : Mal::RuntimeException + STDERR.puts "Error: #{pr_str(e.thrown, true)}" rescue e - STDERR.puts e + STDERR.puts "Error: #{e}" end end diff --git a/crystal/step5_tco.cr b/crystal/step5_tco.cr index e56db4a6b9..66f144bfe1 100755 --- a/crystal/step5_tco.cr +++ b/crystal/step5_tco.cr @@ -162,7 +162,9 @@ Mal.rep "(def! not (fn* (a) (if a false true)))" while line = Readline.readline("user> ", true) begin puts Mal.rep(line) + rescue e : Mal::RuntimeException + STDERR.puts "Error: #{pr_str(e.thrown, true)}" rescue e - STDERR.puts e + STDERR.puts "Error: #{e}" end end diff --git a/crystal/step6_file.cr b/crystal/step6_file.cr index 9997a2f7fa..12f60792ff 100755 --- a/crystal/step6_file.cr +++ b/crystal/step6_file.cr @@ -175,7 +175,9 @@ end while line = Readline.readline("user> ", true) begin puts Mal.rep(line) + rescue e : Mal::RuntimeException + STDERR.puts "Error: #{pr_str(e.thrown, true)}" rescue e - STDERR.puts e + STDERR.puts "Error: #{e}" end end diff --git a/crystal/step7_quote.cr b/crystal/step7_quote.cr index 92d2ab44ec..3cb80a6769 100755 --- a/crystal/step7_quote.cr +++ b/crystal/step7_quote.cr @@ -205,7 +205,9 @@ end while line = Readline.readline("user> ", true) begin puts Mal.rep(line) + rescue e : Mal::RuntimeException + STDERR.puts "Error: #{pr_str(e.thrown, true)}" rescue e - STDERR.puts e + STDERR.puts "Error: #{e}" end end diff --git a/crystal/step8_macros.cr b/crystal/step8_macros.cr index 061649b9af..f7b5cb39ce 100755 --- a/crystal/step8_macros.cr +++ b/crystal/step8_macros.cr @@ -250,7 +250,9 @@ end while line = Readline.readline("user> ", true) begin puts Mal.rep(line) + rescue e : Mal::RuntimeException + STDERR.puts "Error: #{pr_str(e.thrown, true)}" rescue e - STDERR.puts e + STDERR.puts "Error: #{e}" end end diff --git a/crystal/step9_try.cr b/crystal/step9_try.cr index 4853cc80f3..99314c030a 100755 --- a/crystal/step9_try.cr +++ b/crystal/step9_try.cr @@ -267,7 +267,9 @@ end while line = Readline.readline("user> ", true) begin puts Mal.rep(line) + rescue e : Mal::RuntimeException + STDERR.puts "Error: #{pr_str(e.thrown, true)}" rescue e - STDERR.puts e + STDERR.puts "Error: #{e}" end end diff --git a/crystal/stepA_mal.cr b/crystal/stepA_mal.cr index f71be26aa3..03727aff19 100755 --- a/crystal/stepA_mal.cr +++ b/crystal/stepA_mal.cr @@ -279,7 +279,9 @@ Mal.rep("(println (str \"Mal [\" *host-language* \"]\"))") while line = Readline.readline("user> ", true) begin puts Mal.rep(line) + rescue e : Mal::RuntimeException + STDERR.puts "Error: #{pr_str(e.thrown, true)}" rescue e - STDERR.puts e + STDERR.puts "Error: #{e}" end end diff --git a/d/step9_try.d b/d/step9_try.d index 554831ffeb..9c378068e1 100644 --- a/d/step9_try.d +++ b/d/step9_try.d @@ -313,6 +313,10 @@ void main(string[] args) { writeln(rep(line, repl_env)); } + catch (MalException e) + { + writeln("Error: ", pr_str(e.data)); + } catch (Exception e) { writeln("Error: ", e.msg); diff --git a/d/stepA_mal.d b/d/stepA_mal.d index f1b42f12a4..1262d5fddd 100644 --- a/d/stepA_mal.d +++ b/d/stepA_mal.d @@ -317,6 +317,10 @@ void main(string[] args) { writeln(rep(line, repl_env)); } + catch (MalException e) + { + writeln("Error: ", pr_str(e.data)); + } catch (Exception e) { writeln("Error: ", e.msg); diff --git a/dart/core.dart b/dart/core.dart index 01462e603d..047ed82c43 100644 --- a/dart/core.dart +++ b/dart/core.dart @@ -131,7 +131,7 @@ Map ns = { try { return indexable[index.value]; } on RangeError catch (e) { - throw new MalNativeException(e); + throw new MalException(new MalString(e.toString())); } }), new MalSymbol('first'): new MalBuiltin((List args) { diff --git a/dart/step1_read_print.dart b/dart/step1_read_print.dart index 0799fa0aa3..5269bf2295 100644 --- a/dart/step1_read_print.dart +++ b/dart/step1_read_print.dart @@ -11,13 +11,7 @@ MalType EVAL(MalType x) => x; String PRINT(MalType x) => printer.pr_str(x); String rep(String x) { - var parsed; - try { - parsed = READ(x); - } on reader.ParseException catch (e) { - return e.message; - } - return PRINT(EVAL(parsed)); + return PRINT(EVAL(READ(x))); } const prompt = 'user> '; @@ -29,6 +23,9 @@ main() { var output; try { output = rep(input); + } on reader.ParseException catch (e) { + stdout.writeln("Error: '${e.message}'"); + continue; } on reader.NoInputException { continue; } diff --git a/dart/step2_eval.dart b/dart/step2_eval.dart index cdc8870fd0..820c8d769f 100644 --- a/dart/step2_eval.dart +++ b/dart/step2_eval.dart @@ -60,20 +60,7 @@ EVAL(MalType ast, Map env) { String PRINT(MalType x) => printer.pr_str(x); String rep(String x) { - var parsed; - try { - parsed = READ(x); - } on reader.ParseException catch (e) { - return e.message; - } - - var evaledAst; - try { - evaledAst = EVAL(parsed, replEnv); - } on NotFoundException catch (e) { - return "'${e.value}' not found"; - } - return PRINT(evaledAst); + return PRINT(EVAL(READ(x), replEnv)); } const prompt = 'user> '; @@ -85,6 +72,12 @@ main() { var output; try { output = rep(input); + } on reader.ParseException catch (e) { + stdout.writeln("Error: '${e.message}'"); + continue; + } on NotFoundException catch (e) { + stdout.writeln("Error: '${e.value}' not found"); + continue; } on reader.NoInputException { continue; } diff --git a/dart/step3_env.dart b/dart/step3_env.dart index 5df9a9d416..0c1a0af963 100644 --- a/dart/step3_env.dart +++ b/dart/step3_env.dart @@ -99,20 +99,7 @@ MalType EVAL(MalType ast, Env env) { String PRINT(MalType x) => printer.pr_str(x); String rep(String x) { - var parsed; - try { - parsed = READ(x); - } on reader.ParseException catch (e) { - return e.message; - } - - var evaledAst; - try { - evaledAst = EVAL(parsed, replEnv); - } on NotFoundException catch (e) { - return "'${e.value}' not found"; - } - return PRINT(evaledAst); + return PRINT(EVAL(READ(x), replEnv)); } const prompt = 'user> '; @@ -125,6 +112,12 @@ main() { var output; try { output = rep(input); + } on reader.ParseException catch (e) { + stdout.writeln("Error: '${e.message}'"); + continue; + } on NotFoundException catch (e) { + stdout.writeln("Error: '${e.value}' not found"); + continue; } on reader.NoInputException { continue; } diff --git a/dart/step4_if_fn_do.dart b/dart/step4_if_fn_do.dart index 73bef70313..7559da64d2 100644 --- a/dart/step4_if_fn_do.dart +++ b/dart/step4_if_fn_do.dart @@ -112,20 +112,7 @@ MalType EVAL(MalType ast, Env env) { String PRINT(MalType x) => printer.pr_str(x); String rep(String x) { - var parsed; - try { - parsed = READ(x); - } on reader.ParseException catch (e) { - return e.message; - } - - var evaledAst; - try { - evaledAst = EVAL(parsed, replEnv); - } on NotFoundException catch (e) { - return "'${e.value}' not found"; - } - return PRINT(evaledAst); + return PRINT(EVAL(READ(x), replEnv)); } const prompt = 'user> '; @@ -138,6 +125,15 @@ main() { var output; try { output = rep(input); + } on reader.ParseException catch (e) { + stdout.writeln("Error: '${e.message}'"); + continue; + } on NotFoundException catch (e) { + stdout.writeln("Error: '${e.value}' not found"); + continue; + } on MalException catch (e) { + stdout.writeln("Error: ${printer.pr_str(e.value)}"); + continue; } on reader.NoInputException { continue; } diff --git a/dart/step5_tco.dart b/dart/step5_tco.dart index b8dd03b4a0..b2733a028e 100644 --- a/dart/step5_tco.dart +++ b/dart/step5_tco.dart @@ -127,20 +127,7 @@ MalType EVAL(MalType ast, Env env) { String PRINT(MalType x) => printer.pr_str(x); String rep(String x) { - var parsed; - try { - parsed = READ(x); - } on reader.ParseException catch (e) { - return e.message; - } - - var evaledAst; - try { - evaledAst = EVAL(parsed, replEnv); - } on NotFoundException catch (e) { - return "'${e.value}' not found"; - } - return PRINT(evaledAst); + return PRINT(EVAL(READ(x), replEnv)); } const prompt = 'user> '; @@ -153,6 +140,15 @@ main() { var output; try { output = rep(input); + } on reader.ParseException catch (e) { + stdout.writeln("Error: '${e.message}'"); + continue; + } on NotFoundException catch (e) { + stdout.writeln("Error: '${e.value}' not found"); + continue; + } on MalException catch (e) { + stdout.writeln("Error: ${printer.pr_str(e.value)}"); + continue; } on reader.NoInputException { continue; } diff --git a/dart/step6_file.dart b/dart/step6_file.dart index d4e5fb3a40..c33a17400d 100644 --- a/dart/step6_file.dart +++ b/dart/step6_file.dart @@ -134,20 +134,7 @@ MalType EVAL(MalType ast, Env env) { String PRINT(MalType x) => printer.pr_str(x); String rep(String x) { - var parsed; - try { - parsed = READ(x); - } on reader.ParseException catch (e) { - return e.message; - } - - var evaledAst; - try { - evaledAst = EVAL(parsed, replEnv); - } on NotFoundException catch (e) { - return "'${e.value}' not found"; - } - return PRINT(evaledAst); + return PRINT(EVAL(READ(x), replEnv)); } const prompt = 'user> '; @@ -164,6 +151,15 @@ main(List args) { var output; try { output = rep(input); + } on reader.ParseException catch (e) { + stdout.writeln("Error: '${e.message}'"); + continue; + } on NotFoundException catch (e) { + stdout.writeln("Error: '${e.value}' not found"); + continue; + } on MalException catch (e) { + stdout.writeln("Error: ${printer.pr_str(e.value)}"); + continue; } on reader.NoInputException { continue; } diff --git a/dart/step7_quote.dart b/dart/step7_quote.dart index a2b45f8b8e..02b07bb0b2 100644 --- a/dart/step7_quote.dart +++ b/dart/step7_quote.dart @@ -167,20 +167,7 @@ MalType EVAL(MalType ast, Env env) { String PRINT(MalType x) => printer.pr_str(x); String rep(String x) { - var parsed; - try { - parsed = READ(x); - } on reader.ParseException catch (e) { - return e.message; - } - - var evaledAst; - try { - evaledAst = EVAL(parsed, replEnv); - } on NotFoundException catch (e) { - return "'${e.value}' not found"; - } - return PRINT(evaledAst); + return PRINT(EVAL(READ(x), replEnv)); } const prompt = 'user> '; @@ -197,6 +184,15 @@ main(List args) { var output; try { output = rep(input); + } on reader.ParseException catch (e) { + stdout.writeln("Error: '${e.message}'"); + continue; + } on NotFoundException catch (e) { + stdout.writeln("Error: '${e.value}' not found"); + continue; + } on MalException catch (e) { + stdout.writeln("Error: ${printer.pr_str(e.value)}"); + continue; } on reader.NoInputException { continue; } diff --git a/dart/step8_macros.dart b/dart/step8_macros.dart index 558b0fd91b..de9fbbf0d7 100644 --- a/dart/step8_macros.dart +++ b/dart/step8_macros.dart @@ -217,22 +217,7 @@ MalType EVAL(MalType ast, Env env) { String PRINT(MalType x) => printer.pr_str(x); String rep(String x) { - var parsed; - try { - parsed = READ(x); - } on reader.ParseException catch (e) { - return e.message; - } - - var evaledAst; - try { - evaledAst = EVAL(parsed, replEnv); - } on NotFoundException catch (e) { - return "'${e.value}' not found"; - } on MalNativeException catch (e) { - return "${e.error}"; - } - return PRINT(evaledAst); + return PRINT(EVAL(READ(x), replEnv)); } const prompt = 'user> '; @@ -249,6 +234,15 @@ main(List args) { var output; try { output = rep(input); + } on reader.ParseException catch (e) { + stdout.writeln("Error: '${e.message}'"); + continue; + } on NotFoundException catch (e) { + stdout.writeln("Error: '${e.value}' not found"); + continue; + } on MalException catch (e) { + stdout.writeln("Error: ${printer.pr_str(e.value)}"); + continue; } on reader.NoInputException { continue; } diff --git a/dart/step9_try.dart b/dart/step9_try.dart index aa8fece5e6..147958e51f 100644 --- a/dart/step9_try.dart +++ b/dart/step9_try.dart @@ -198,6 +198,10 @@ MalType EVAL(MalType ast, Env env) { continue; } else if (symbol.value == 'try*') { var body = args.first; + if (args.length < 2) { + ast = EVAL(body, env); + continue; + } var catchClause = args[1] as MalList; try { ast = EVAL(body, env); @@ -237,22 +241,7 @@ MalType EVAL(MalType ast, Env env) { String PRINT(MalType x) => printer.pr_str(x); String rep(String x) { - var parsed; - try { - parsed = READ(x); - } on reader.ParseException catch (e) { - return e.message; - } - - var evaledAst; - try { - evaledAst = EVAL(parsed, replEnv); - } on NotFoundException catch (e) { - return "'${e.value}' not found"; - } on MalNativeException catch (e) { - return "${e.error}"; - } - return PRINT(evaledAst); + return PRINT(EVAL(READ(x), replEnv)); } const prompt = 'user> '; @@ -269,6 +258,15 @@ main(List args) { var output; try { output = rep(input); + } on reader.ParseException catch (e) { + stdout.writeln("Error: '${e.message}'"); + continue; + } on NotFoundException catch (e) { + stdout.writeln("Error: '${e.value}' not found"); + continue; + } on MalException catch (e) { + stdout.writeln("Error: ${printer.pr_str(e.value)}"); + continue; } on reader.NoInputException { continue; } diff --git a/dart/stepA_mal.dart b/dart/stepA_mal.dart index f27ec05b36..66208fb937 100644 --- a/dart/stepA_mal.dart +++ b/dart/stepA_mal.dart @@ -208,6 +208,10 @@ MalType EVAL(MalType ast, Env env) { continue; } else if (symbol.value == 'try*') { var body = args.first; + if (args.length < 2) { + ast = EVAL(body, env); + continue; + } var catchClause = args[1] as MalList; try { ast = EVAL(body, env); @@ -247,22 +251,7 @@ MalType EVAL(MalType ast, Env env) { String PRINT(MalType x) => printer.pr_str(x); String rep(String x) { - var parsed; - try { - parsed = READ(x); - } on reader.ParseException catch (e) { - return e.message; - } - - var evaledAst; - try { - evaledAst = EVAL(parsed, replEnv); - } on NotFoundException catch (e) { - return "'${e.value}' not found"; - } on MalNativeException catch (e) { - return "${e.error}"; - } - return PRINT(evaledAst); + return PRINT(EVAL(READ(x), replEnv)); } const prompt = 'user> '; @@ -280,6 +269,15 @@ main(List args) { var output; try { output = rep(input); + } on reader.ParseException catch (e) { + stdout.writeln("Error: '${e.message}'"); + continue; + } on NotFoundException catch (e) { + stdout.writeln("Error: '${e.value}' not found"); + continue; + } on MalException catch (e) { + stdout.writeln("Error: ${printer.pr_str(e.value)}"); + continue; } on reader.NoInputException { continue; } diff --git a/dart/types.dart b/dart/types.dart index 98022b9755..5d757b0acb 100644 --- a/dart/types.dart +++ b/dart/types.dart @@ -263,14 +263,6 @@ class MalClosure extends MalCallable { } } -class MalNativeException implements Exception { - final Error error; - - MalNativeException(this.error); - - String toString() => error.toString(); -} - class MalException implements Exception { final MalType value; diff --git a/elm/step2_eval.elm b/elm/step2_eval.elm index d23e1d8cea..25b6d43e83 100644 --- a/elm/step2_eval.elm +++ b/elm/step2_eval.elm @@ -100,7 +100,7 @@ makeOutput result = str Err msg -> - "ERR:" ++ msg + "Error: " ++ msg prompt : String diff --git a/elm/step3_env.elm b/elm/step3_env.elm index 59a592c874..40404ddbd0 100644 --- a/elm/step3_env.elm +++ b/elm/step3_env.elm @@ -95,7 +95,7 @@ makeOutput result = str Err msg -> - "ERR:" ++ msg + "Error: " ++ msg prompt : String diff --git a/elm/step4_if_fn_do.elm b/elm/step4_if_fn_do.elm index 113e2253c7..a988b54d59 100644 --- a/elm/step4_if_fn_do.elm +++ b/elm/step4_if_fn_do.elm @@ -477,7 +477,7 @@ print env = printError : Env -> MalExpr -> String printError env expr = - "ERR:" ++ (printString env False expr) + "Error: " ++ (printString env False expr) {-| Read-Eval-Print. diff --git a/elm/step5_tco.elm b/elm/step5_tco.elm index c460102c03..d9fb3c0332 100644 --- a/elm/step5_tco.elm +++ b/elm/step5_tco.elm @@ -515,7 +515,7 @@ print env = printError : Env -> MalExpr -> String printError env expr = - "ERR:" ++ (printString env False expr) + "Error: " ++ (printString env False expr) {-| Read-Eval-Print. diff --git a/elm/step6_file.elm b/elm/step6_file.elm index 670cdadaac..d19c501d52 100644 --- a/elm/step6_file.elm +++ b/elm/step6_file.elm @@ -585,7 +585,7 @@ print env = printError : Env -> MalExpr -> String printError env expr = - "ERR:" ++ (printString env False expr) + "Error: " ++ (printString env False expr) {-| Read-Eval-Print. diff --git a/elm/step7_quote.elm b/elm/step7_quote.elm index 1e396dce5b..489c701c90 100644 --- a/elm/step7_quote.elm +++ b/elm/step7_quote.elm @@ -625,7 +625,7 @@ print env = printError : Env -> MalExpr -> String printError env expr = - "ERR:" ++ (printString env False expr) + "Error: " ++ (printString env False expr) {-| Read-Eval-Print. diff --git a/elm/step8_macros.elm b/elm/step8_macros.elm index c089a90f08..023d1179ae 100644 --- a/elm/step8_macros.elm +++ b/elm/step8_macros.elm @@ -702,7 +702,7 @@ print env = printError : Env -> MalExpr -> String printError env expr = - "ERR:" ++ (printString env False expr) + "Error: " ++ (printString env False expr) {-| Read-Eval-Print. diff --git a/elm/step9_try.elm b/elm/step9_try.elm index 356584ec5f..90e04418ad 100644 --- a/elm/step9_try.elm +++ b/elm/step9_try.elm @@ -724,7 +724,7 @@ print env = printError : Env -> MalExpr -> String printError env expr = - "ERR:" ++ (printString env False expr) + "Error: " ++ (printString env False expr) {-| Read-Eval-Print. diff --git a/elm/stepA_mal.elm b/elm/stepA_mal.elm index 71bdac5613..5b28c60afd 100644 --- a/elm/stepA_mal.elm +++ b/elm/stepA_mal.elm @@ -740,7 +740,7 @@ print env = printError : Env -> MalExpr -> String printError env expr = - "ERR:" ++ (printString env False expr) + "Error: " ++ (printString env False expr) {-| Read-Eval-Print. diff --git a/erlang/src/step9_try.erl b/erlang/src/step9_try.erl index 41116205e8..9ca6303cd1 100644 --- a/erlang/src/step9_try.erl +++ b/erlang/src/step9_try.erl @@ -37,7 +37,8 @@ rep(Input, Env) -> none -> none; Result -> printer:pr_str(Result, true) catch - error:Reason -> printer:pr_str({error, Reason}, true) + error:Reason -> printer:pr_str({error, Reason}, true); + throw:Reason -> printer:pr_str({error, printer:pr_str(Reason, true)}, true) end. read(Input) -> diff --git a/erlang/src/stepA_mal.erl b/erlang/src/stepA_mal.erl index b763f2bb15..f848b91503 100644 --- a/erlang/src/stepA_mal.erl +++ b/erlang/src/stepA_mal.erl @@ -41,7 +41,8 @@ rep(Input, Env) -> none -> none; Result -> printer:pr_str(Result, true) catch - error:Reason -> printer:pr_str({error, Reason}, true) + error:Reason -> printer:pr_str({error, Reason}, true); + throw:Reason -> printer:pr_str({error, printer:pr_str(Reason, true)}, true) end. read(Input) -> diff --git a/es6/step1_read_print.mjs b/es6/step1_read_print.mjs index 8af709fade..48932cad53 100644 --- a/es6/step1_read_print.mjs +++ b/es6/step1_read_print.mjs @@ -22,7 +22,7 @@ while (true) { if (line) { console.log(REP(line)) } } catch (exc) { if (exc instanceof BlankException) { continue } - if (exc.stack) { console.log(exc.stack) } - else { console.log(`Error: ${exc}`) } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn(`Error: ${exc}`) } } } diff --git a/es6/step2_eval.mjs b/es6/step2_eval.mjs index f49b78ad6a..3274fd3b29 100644 --- a/es6/step2_eval.mjs +++ b/es6/step2_eval.mjs @@ -51,7 +51,7 @@ while (true) { if (line) { console.log(REP(line)) } } catch (exc) { if (exc instanceof BlankException) { continue } - if (exc.stack) { console.log(exc.stack) } - else { console.log(`Error: ${exc}`) } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn(`Error: ${exc}`) } } } diff --git a/es6/step3_env.mjs b/es6/step3_env.mjs index 034315a814..5ecea0d683 100644 --- a/es6/step3_env.mjs +++ b/es6/step3_env.mjs @@ -62,7 +62,7 @@ while (true) { if (line) { console.log(REP(line)) } } catch (exc) { if (exc instanceof BlankException) { continue } - if (exc.stack) { console.log(exc.stack) } - else { console.log(`Error: ${exc}`) } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn(`Error: ${exc}`) } } } diff --git a/es6/step4_if_fn_do.mjs b/es6/step4_if_fn_do.mjs index e3c7073030..3369595c75 100644 --- a/es6/step4_if_fn_do.mjs +++ b/es6/step4_if_fn_do.mjs @@ -76,7 +76,7 @@ while (true) { if (line) { console.log(REP(line)) } } catch (exc) { if (exc instanceof BlankException) { continue } - if (exc.stack) { console.log(exc.stack) } - else { console.log(`Error: ${exc}`) } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn(`Error: ${exc}`) } } } diff --git a/es6/step5_tco.mjs b/es6/step5_tco.mjs index bf83f4a69d..035f2f1df8 100644 --- a/es6/step5_tco.mjs +++ b/es6/step5_tco.mjs @@ -90,7 +90,7 @@ while (true) { if (line) { console.log(REP(line)) } } catch (exc) { if (exc instanceof BlankException) { continue } - if (exc.stack) { console.log(exc.stack) } - else { console.log(`Error: ${exc}`) } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn(`Error: ${exc}`) } } } diff --git a/es6/step6_file.mjs b/es6/step6_file.mjs index 1053976a33..e3fc7bbb55 100644 --- a/es6/step6_file.mjs +++ b/es6/step6_file.mjs @@ -100,7 +100,7 @@ while (true) { if (line) { console.log(REP(line)) } } catch (exc) { if (exc instanceof BlankException) { continue } - if (exc.stack) { console.log(exc.stack) } - else { console.log(`Error: ${exc}`) } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn(`Error: ${exc}`) } } } diff --git a/es6/step7_quote.mjs b/es6/step7_quote.mjs index 31642da170..2fea25cb09 100644 --- a/es6/step7_quote.mjs +++ b/es6/step7_quote.mjs @@ -123,7 +123,7 @@ while (true) { if (line) { console.log(REP(line)) } } catch (exc) { if (exc instanceof BlankException) { continue } - if (exc.stack) { console.log(exc.stack) } - else { console.log(`Error: ${exc}`) } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn(`Error: ${exc}`) } } } diff --git a/es6/step8_macros.mjs b/es6/step8_macros.mjs index e106975c23..5857c7e2ab 100644 --- a/es6/step8_macros.mjs +++ b/es6/step8_macros.mjs @@ -144,7 +144,7 @@ while (true) { if (line) { console.log(REP(line)) } } catch (exc) { if (exc instanceof BlankException) { continue } - if (exc.stack) { console.log(exc.stack) } - else { console.log(`Error: ${exc}`) } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn(`Error: ${exc}`) } } } diff --git a/es6/step9_try.mjs b/es6/step9_try.mjs index 389c1c8db9..73706c3da8 100644 --- a/es6/step9_try.mjs +++ b/es6/step9_try.mjs @@ -155,7 +155,7 @@ while (true) { if (line) { console.log(REP(line)) } } catch (exc) { if (exc instanceof BlankException) { continue } - if (exc.stack) { console.log(exc.stack) } - else { console.log(`Error: ${exc}`) } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn(`Error: ${pr_str(exc, true)}`) } } } diff --git a/es6/stepA_mal.mjs b/es6/stepA_mal.mjs index a86199385e..81eff7d0be 100644 --- a/es6/stepA_mal.mjs +++ b/es6/stepA_mal.mjs @@ -158,7 +158,7 @@ while (true) { if (line) { console.log(REP(line)) } } catch (exc) { if (exc instanceof BlankException) { continue } - if (exc.stack) { console.log(exc.stack) } - else { console.log(`Error: ${exc}`) } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn(`Error: ${pr_str(exc, true)}`) } } } diff --git a/factor/lib/reader/reader.factor b/factor/lib/reader/reader.factor index 2c4092a04a..da72277e00 100644 --- a/factor/lib/reader/reader.factor +++ b/factor/lib/reader/reader.factor @@ -34,7 +34,7 @@ DEFER: read-form :: read-sequence ( seq closer exemplar -- seq maltype ) seq [ [ - [ "expected " closer ", got EOF" append throw ] + [ "expected '" closer "', got EOF" append append throw ] [ dup first closer = ] if-empty ] [ read-form , diff --git a/factor/step1_read_print/step1_read_print.factor b/factor/step1_read_print/step1_read_print.factor index fe42cad03d..3d23d9c31e 100755 --- a/factor/step1_read_print/step1_read_print.factor +++ b/factor/step1_read_print/step1_read_print.factor @@ -11,7 +11,11 @@ IN: step1_read_print : PRINT ( maltype -- str ) pr-str ; : REP ( str -- str ) - [ READ EVAL ] [ nip ] recover PRINT ; + [ + READ EVAL PRINT + ] [ + nip pr-str "Error: " swap append + ] recover ; : REPL ( -- ) [ diff --git a/factor/step2_eval/step2_eval.factor b/factor/step2_eval/step2_eval.factor index aa1c06ea0e..2892911b3b 100755 --- a/factor/step2_eval/step2_eval.factor +++ b/factor/step2_eval/step2_eval.factor @@ -43,7 +43,11 @@ DEFER: EVAL : PRINT ( maltype -- str ) pr-str ; : REP ( str -- str ) - [ READ repl-env EVAL ] [ nip ] recover PRINT ; + [ + READ repl-env EVAL PRINT + ] [ + nip pr-str "Error: " swap append + ] recover ; : REPL ( -- ) [ diff --git a/factor/step3_env/step3_env.factor b/factor/step3_env/step3_env.factor index 3dbdc002c9..310fea19f0 100755 --- a/factor/step3_env/step3_env.factor +++ b/factor/step3_env/step3_env.factor @@ -54,7 +54,11 @@ DEFER: EVAL : PRINT ( maltype -- str ) pr-str ; : REP ( str -- str ) - [ READ repl-env get EVAL ] [ nip ] recover PRINT ; + [ + READ repl-env get EVAL PRINT + ] [ + nip pr-str "Error: " swap append + ] recover ; : REPL ( -- ) f repl-bindings repl-env set diff --git a/factor/step4_if_fn_do/step4_if_fn_do.factor b/factor/step4_if_fn_do/step4_if_fn_do.factor index d48f2414bc..6f51449ead 100755 --- a/factor/step4_if_fn_do/step4_if_fn_do.factor +++ b/factor/step4_if_fn_do/step4_if_fn_do.factor @@ -71,7 +71,11 @@ M: callable apply call( x -- y ) ; : PRINT ( maltype -- str ) pr-str ; : REP ( str -- str ) - [ READ repl-env get EVAL ] [ nip ] recover PRINT ; + [ + READ repl-env get EVAL PRINT + ] [ + nip pr-str "Error: " swap append + ] recover ; : REPL ( -- ) [ diff --git a/factor/step5_tco/step5_tco.factor b/factor/step5_tco/step5_tco.factor index face303a81..d14fbe9b3d 100755 --- a/factor/step5_tco/step5_tco.factor +++ b/factor/step5_tco/step5_tco.factor @@ -78,7 +78,11 @@ M: callable apply call( x -- y ) f ; : PRINT ( maltype -- str ) pr-str ; : REP ( str -- str ) - [ READ repl-env get EVAL ] [ nip ] recover PRINT ; + [ + READ repl-env get EVAL PRINT + ] [ + nip pr-str "Error: " swap append + ] recover ; : REPL ( -- ) [ diff --git a/factor/step6_file/step6_file.factor b/factor/step6_file/step6_file.factor index 4509ccf30d..3db684087c 100755 --- a/factor/step6_file/step6_file.factor +++ b/factor/step6_file/step6_file.factor @@ -80,7 +80,11 @@ M: callable apply call( x -- y ) f ; : PRINT ( maltype -- str ) pr-str ; : REP ( str -- str ) - [ READ repl-env get EVAL ] [ nip ] recover PRINT ; + [ + READ repl-env get EVAL PRINT + ] [ + nip pr-str "Error: " swap append + ] recover ; : REPL ( -- ) [ diff --git a/factor/step7_quote/step7_quote.factor b/factor/step7_quote/step7_quote.factor index f980509c7e..99264e8bc3 100755 --- a/factor/step7_quote/step7_quote.factor +++ b/factor/step7_quote/step7_quote.factor @@ -95,7 +95,11 @@ M: callable apply call( x -- y ) f ; : PRINT ( maltype -- str ) pr-str ; : REP ( str -- str ) - [ READ repl-env get EVAL ] [ nip ] recover PRINT ; + [ + READ repl-env get EVAL PRINT + ] [ + nip pr-str "Error: " swap append + ] recover ; : REPL ( -- ) [ diff --git a/factor/step8_macros/step8_macros.factor b/factor/step8_macros/step8_macros.factor index 9b77f97da1..7dda02ed74 100755 --- a/factor/step8_macros/step8_macros.factor +++ b/factor/step8_macros/step8_macros.factor @@ -113,7 +113,11 @@ M: callable apply call( x -- y ) f ; : PRINT ( maltype -- str ) pr-str ; : REP ( str -- str ) - [ READ repl-env get EVAL ] [ nip ] recover PRINT ; + [ + READ repl-env get EVAL PRINT + ] [ + nip pr-str "Error: " swap append + ] recover ; : REPL ( -- ) [ diff --git a/factor/step9_try/step9_try.factor b/factor/step9_try/step9_try.factor index 403b9e2fe1..92fae10371 100755 --- a/factor/step9_try/step9_try.factor +++ b/factor/step9_try/step9_try.factor @@ -50,8 +50,12 @@ DEFER: EVAL :: eval-try* ( params env -- maltype ) [ params first env EVAL ] [ - params second second env new-env [ env-set ] keep - params second third swap EVAL + params length 1 > [ + params second second env new-env [ env-set ] keep + params second third swap EVAL + ] [ + throw + ] if ] recover ; : args-split ( bindlist -- bindlist restbinding/f ) @@ -121,7 +125,11 @@ M: callable apply call( x -- y ) f ; : PRINT ( maltype -- str ) pr-str ; : REP ( str -- str ) - [ READ repl-env get EVAL ] [ nip ] recover PRINT ; + [ + READ repl-env get EVAL PRINT + ] [ + nip pr-str "Error: " swap append + ] recover ; : REPL ( -- ) [ diff --git a/fsharp/step1_read_print.fs b/fsharp/step1_read_print.fs index 27751f51c2..1ce61408ff 100644 --- a/fsharp/step1_read_print.fs +++ b/fsharp/step1_read_print.fs @@ -32,10 +32,12 @@ module REPL Readline.Mode.Terminal [] - let rec main args = + let main args = let mode = getReadlineMode args - match Readline.read "user> " mode with - | null -> 0 - | input -> - REP input - main args + let rec loop () = + match Readline.read "user> " mode with + | null -> 0 + | input -> + REP input + loop() + loop () diff --git a/fsharp/step2_eval.fs b/fsharp/step2_eval.fs index d14615f128..62bdc4299d 100644 --- a/fsharp/step2_eval.fs +++ b/fsharp/step2_eval.fs @@ -20,27 +20,10 @@ module REPL | node -> node |> eval_ast env let READ input = - try - Reader.read_str input - with - | Error.ReaderError(msg) -> - printfn "%s" msg - [] + Reader.read_str input let EVAL env ast = - try - Some(eval env ast) - with - | Error.EvalError(str) - | Error.ReaderError(str) -> - printfn "%s" str - None - | Error.MalError(node) -> - printfn "%s" (Printer.pr_str [node]) - None - | ex -> - printfn "%s" (ex.Message) - None + Some(eval env ast) let PRINT v = v @@ -61,11 +44,20 @@ module REPL Readline.Mode.Terminal [] - let rec main args = + let main args = let mode = getReadlineMode args let env = Env.makeRootEnv () - match Readline.read "user> " mode with - | null -> 0 - | input -> - REP env input - main args + let rec loop () = + match Readline.read "user> " mode with + | null -> 0 + | input -> + try + REP env input + with + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "Error: %s" str + | ex -> + printfn "Error: %s" (ex.Message) + loop () + loop () diff --git a/fsharp/step3_env.fs b/fsharp/step3_env.fs index 4c7954ac37..db6408f4cd 100644 --- a/fsharp/step3_env.fs +++ b/fsharp/step3_env.fs @@ -56,27 +56,10 @@ module REPL | node -> node |> eval_ast env let READ input = - try - Reader.read_str input - with - | Error.ReaderError(msg) -> - printfn "%s" msg - [] + Reader.read_str input let EVAL env ast = - try - Some(eval env ast) - with - | Error.EvalError(str) - | Error.ReaderError(str) -> - printfn "%s" str - None - | Error.MalError(node) -> - printfn "%s" (Printer.pr_str [node]) - None - | ex -> - printfn "%s" (ex.Message) - None + Some(eval env ast) let PRINT v = v @@ -104,6 +87,13 @@ module REPL match Readline.read "user> " mode with | null -> 0 | input -> - REP env input + try + REP env input + with + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "Error: %s" str + | ex -> + printfn "Error: %s" (ex.Message) loop () loop () diff --git a/fsharp/step4_if_fn_do.fs b/fsharp/step4_if_fn_do.fs index 5ffedda78a..8b48f64b65 100644 --- a/fsharp/step4_if_fn_do.fs +++ b/fsharp/step4_if_fn_do.fs @@ -92,27 +92,10 @@ module REPL | node -> node |> eval_ast env let READ input = - try - Reader.read_str input - with - | Error.ReaderError(msg) -> - printfn "%s" msg - [] + Reader.read_str input let EVAL env ast = - try - Some(eval env ast) - with - | Error.EvalError(str) - | Error.ReaderError(str) -> - printfn "%s" str - None - | Error.MalError(node) -> - printfn "%s" (Printer.pr_str [node]) - None - | ex -> - printfn "%s" (ex.Message) - None + Some(eval env ast) let PRINT v = v @@ -147,6 +130,13 @@ module REPL match Readline.read "user> " mode with | null -> 0 | input -> - REP env input + try + REP env input + with + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "Error: %s" str + | ex -> + printfn "Error: %s" (ex.Message) loop () loop () diff --git a/fsharp/step5_tco.fs b/fsharp/step5_tco.fs index ab96f441d4..1c82c67a93 100644 --- a/fsharp/step5_tco.fs +++ b/fsharp/step5_tco.fs @@ -94,27 +94,10 @@ module REPL | node -> node |> eval_ast env let READ input = - try - Reader.read_str input - with - | Error.ReaderError(msg) -> - printfn "%s" msg - [] + Reader.read_str input let EVAL env ast = - try - Some(eval env ast) - with - | Error.EvalError(str) - | Error.ReaderError(str) -> - printfn "%s" str - None - | Error.MalError(node) -> - printfn "%s" (Printer.pr_str [node]) - None - | ex -> - printfn "%s" (ex.Message) - None + Some(eval env ast) let PRINT v = v @@ -149,6 +132,13 @@ module REPL match Readline.read "user> " mode with | null -> 0 | input -> - REP env input + try + REP env input + with + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "Error: %s" str + | ex -> + printfn "Error: %s" (ex.Message) loop () loop () diff --git a/fsharp/step6_file.fs b/fsharp/step6_file.fs index 0d1af99744..1e03c55f9e 100644 --- a/fsharp/step6_file.fs +++ b/fsharp/step6_file.fs @@ -94,27 +94,10 @@ module REPL | node -> node |> eval_ast env let READ input = - try - Reader.read_str input - with - | Error.ReaderError(msg) -> - printfn "%s" msg - [] + Reader.read_str input let EVAL env ast = - try - Some(eval env ast) - with - | Error.EvalError(str) - | Error.ReaderError(str) -> - printfn "%s" str - None - | Error.MalError(node) -> - printfn "%s" (Printer.pr_str [node]) - None - | ex -> - printfn "%s" (ex.Message) - None + Some(eval env ast) let PRINT v = v @@ -175,6 +158,13 @@ module REPL match Readline.read "user> " mode with | null -> 0 | input -> - REP env input + try + REP env input + with + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "Error: %s" str + | ex -> + printfn "Error: %s" (ex.Message) loop () loop () diff --git a/fsharp/step7_quote.fs b/fsharp/step7_quote.fs index 7fbe9d4d69..de6eb19164 100644 --- a/fsharp/step7_quote.fs +++ b/fsharp/step7_quote.fs @@ -114,27 +114,10 @@ module REPL | node -> node |> eval_ast env let READ input = - try - Reader.read_str input - with - | Error.ReaderError(msg) -> - printfn "%s" msg - [] + Reader.read_str input let EVAL env ast = - try - Some(eval env ast) - with - | Error.EvalError(str) - | Error.ReaderError(str) -> - printfn "%s" str - None - | Error.MalError(node) -> - printfn "%s" (Printer.pr_str [node]) - None - | ex -> - printfn "%s" (ex.Message) - None + Some(eval env ast) let PRINT v = v @@ -195,6 +178,13 @@ module REPL match Readline.read "user> " mode with | null -> 0 | input -> - REP env input + try + REP env input + with + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "Error: %s" str + | ex -> + printfn "Error: %s" (ex.Message) loop () loop () diff --git a/fsharp/step8_macros.fs b/fsharp/step8_macros.fs index 8a80b56d36..f893f52be7 100644 --- a/fsharp/step8_macros.fs +++ b/fsharp/step8_macros.fs @@ -142,27 +142,10 @@ module REPL | node -> node |> eval_ast env let READ input = - try - Reader.read_str input - with - | Error.ReaderError(msg) -> - printfn "%s" msg - [] + Reader.read_str input let EVAL env ast = - try - Some(eval env ast) - with - | Error.EvalError(str) - | Error.ReaderError(str) -> - printfn "%s" str - None - | Error.MalError(node) -> - printfn "%s" (Printer.pr_str [node]) - None - | ex -> - printfn "%s" (ex.Message) - None + Some(eval env ast) let PRINT v = v @@ -225,6 +208,13 @@ module REPL match Readline.read "user> " mode with | null -> 0 | input -> - REP env input + try + REP env input + with + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "Error: %s" str + | ex -> + printfn "Error: %s" (ex.Message) loop () loop () diff --git a/fsharp/step9_try.fs b/fsharp/step9_try.fs index babb2f1f4c..6bf6549edf 100644 --- a/fsharp/step9_try.fs +++ b/fsharp/step9_try.fs @@ -122,7 +122,10 @@ module REPL | List(_, [_; _; _]) -> raise <| Error.argMismatch () | _ -> raise <| Error.wrongArity () + and tryForm env = function + | [exp] -> + eval env exp | [exp; catchClause] -> try eval env exp @@ -159,27 +162,10 @@ module REPL | node -> node |> eval_ast env let READ input = - try - Reader.read_str input - with - | Error.ReaderError(msg) -> - printfn "%s" msg - [] + Reader.read_str input let EVAL env ast = - try - Some(eval env ast) - with - | Error.EvalError(str) - | Error.ReaderError(str) -> - printfn "%s" str - None - | Error.MalError(node) -> - printfn "%s" (Printer.pr_str [node]) - None - | ex -> - printfn "%s" (ex.Message) - None + Some(eval env ast) let PRINT v = v @@ -242,6 +228,15 @@ module REPL match Readline.read "user> " mode with | null -> 0 | input -> - REP env input + try + REP env input + with + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "Error: %s" str + | Error.MalError(node) -> + printfn "Error: %s" (Printer.pr_str [node]) + | ex -> + printfn "Error: %s" (ex.Message) loop () loop () diff --git a/fsharp/stepA_mal.fs b/fsharp/stepA_mal.fs index 8cdaa6836c..1657ad1063 100644 --- a/fsharp/stepA_mal.fs +++ b/fsharp/stepA_mal.fs @@ -122,7 +122,10 @@ module REPL | List(_, [_; _; _]) -> raise <| Error.argMismatch () | _ -> raise <| Error.wrongArity () + and tryForm env = function + | [exp] -> + eval env exp | [exp; catchClause] -> try eval env exp @@ -160,27 +163,10 @@ module REPL | node -> node |> eval_ast env let READ input = - try - Reader.read_str input - with - | Error.ReaderError(msg) -> - printfn "%s" msg - [] + Reader.read_str input let EVAL env ast = - try - Some(eval env ast) - with - | Error.EvalError(str) - | Error.ReaderError(str) -> - printfn "%s" str - None - | Error.MalError(node) -> - printfn "%s" (Printer.pr_str [node]) - None - | ex -> - printfn "%s" (ex.Message) - None + Some(eval env ast) let PRINT v = v @@ -256,6 +242,15 @@ module REPL match Readline.read "user> " mode with | null -> 0 | input -> - REP env input + try + REP env input + with + | Error.EvalError(str) + | Error.ReaderError(str) -> + printfn "Error: %s" str + | Error.MalError(node) -> + printfn "Error: %s" (Printer.pr_str [node]) + | ex -> + printfn "Error: %s" (ex.Message) loop () loop () diff --git a/gnu-smalltalk/step9_try.st b/gnu-smalltalk/step9_try.st index 08bb9768de..39022321b0 100644 --- a/gnu-smalltalk/step9_try.st +++ b/gnu-smalltalk/step9_try.st @@ -220,6 +220,9 @@ Object subclass: MAL [ a0_ = #'try*' ifTrue: [ | A B C | A := ast second. + ast at: 3 ifAbsent: [ + ^self EVAL: A env: env. + ]. a2_ := ast third value. B := a2_ second value. C := a2_ third. diff --git a/gnu-smalltalk/stepA_mal.st b/gnu-smalltalk/stepA_mal.st index dea76c217c..acba962242 100644 --- a/gnu-smalltalk/stepA_mal.st +++ b/gnu-smalltalk/stepA_mal.st @@ -220,6 +220,9 @@ Object subclass: MAL [ a0_ = #'try*' ifTrue: [ | A B C | A := ast second. + ast at: 3 ifAbsent: [ + ^self EVAL: A env: env. + ]. a2_ := ast third value. B := a2_ second value. C := a2_ third. diff --git a/groovy/step1_read_print.groovy b/groovy/step1_read_print.groovy index f857c054ee..c9775a6a68 100644 --- a/groovy/step1_read_print.groovy +++ b/groovy/step1_read_print.groovy @@ -30,7 +30,7 @@ while (true) { try { println REP(line) } catch(MalException ex) { - println "Error: ${ex.message}" + println "Error: ${printer.pr_str(ex.obj, true)}" } catch(ex) { println "Error: $ex" ex.printStackTrace() diff --git a/groovy/step2_eval.groovy b/groovy/step2_eval.groovy index 3159db25bd..a2ced70ccc 100644 --- a/groovy/step2_eval.groovy +++ b/groovy/step2_eval.groovy @@ -62,7 +62,7 @@ while (true) { try { println REP(line) } catch(MalException ex) { - println "Error: ${ex.message}" + println "Error: ${printer.pr_str(ex.obj, true)}" } catch(ex) { println "Error: $ex" ex.printStackTrace() diff --git a/groovy/step3_env.groovy b/groovy/step3_env.groovy index 5f375540c2..6d59f011e1 100644 --- a/groovy/step3_env.groovy +++ b/groovy/step3_env.groovy @@ -70,7 +70,7 @@ while (true) { try { println REP(line) } catch(MalException ex) { - println "Error: ${ex.message}" + println "Error: ${printer.pr_str(ex.obj, true)}" } catch(ex) { println "Error: $ex" ex.printStackTrace() diff --git a/groovy/step4_if_fn_do.groovy b/groovy/step4_if_fn_do.groovy index db86b993d4..df7d984055 100644 --- a/groovy/step4_if_fn_do.groovy +++ b/groovy/step4_if_fn_do.groovy @@ -92,7 +92,7 @@ while (true) { try { println REP(line) } catch(MalException ex) { - println "Error: ${ex.message}" + println "Error: ${printer.pr_str(ex.obj, true)}" } catch(ex) { println "Error: $ex" ex.printStackTrace() diff --git a/groovy/step5_tco.groovy b/groovy/step5_tco.groovy index 50f4673218..a487365540 100644 --- a/groovy/step5_tco.groovy +++ b/groovy/step5_tco.groovy @@ -106,7 +106,7 @@ while (true) { try { println REP(line) } catch(MalException ex) { - println "Error: ${ex.message}" + println "Error: ${printer.pr_str(ex.obj, true)}" } catch(StackOverflowError ex) { println "Error: ${ex}" } catch(ex) { diff --git a/groovy/step6_file.groovy b/groovy/step6_file.groovy index 664a59af8c..ebdae63517 100644 --- a/groovy/step6_file.groovy +++ b/groovy/step6_file.groovy @@ -114,7 +114,7 @@ while (true) { try { println REP(line) } catch(MalException ex) { - println "Error: ${ex.message}" + println "Error: ${printer.pr_str(ex.obj, true)}" } catch(StackOverflowError ex) { println "Error: ${ex}" } catch(ex) { diff --git a/groovy/step7_quote.groovy b/groovy/step7_quote.groovy index d50a4a19f3..ee2e33940d 100644 --- a/groovy/step7_quote.groovy +++ b/groovy/step7_quote.groovy @@ -135,7 +135,7 @@ while (true) { try { println REP(line) } catch(MalException ex) { - println "Error: ${ex.message}" + println "Error: ${printer.pr_str(ex.obj, true)}" } catch(StackOverflowError ex) { println "Error: ${ex}" } catch(ex) { diff --git a/groovy/step8_macros.groovy b/groovy/step8_macros.groovy index 7606526b30..24b1a90af8 100644 --- a/groovy/step8_macros.groovy +++ b/groovy/step8_macros.groovy @@ -167,7 +167,7 @@ while (true) { try { println REP(line) } catch(MalException ex) { - println "Error: ${ex.message}" + println "Error: ${printer.pr_str(ex.obj, true)}" } catch(StackOverflowError ex) { println "Error: ${ex}" } catch(ex) { diff --git a/groovy/step9_try.groovy b/groovy/step9_try.groovy index 991803488c..c921bd6d12 100644 --- a/groovy/step9_try.groovy +++ b/groovy/step9_try.groovy @@ -185,7 +185,7 @@ while (true) { try { println REP(line) } catch(MalException ex) { - println "Error: ${ex.message}" + println "Error: ${printer.pr_str(ex.obj, true)}" } catch(StackOverflowError ex) { println "Error: ${ex}" } catch(ex) { diff --git a/groovy/stepA_mal.groovy b/groovy/stepA_mal.groovy index abf96dfd5a..bb4bf63f5a 100644 --- a/groovy/stepA_mal.groovy +++ b/groovy/stepA_mal.groovy @@ -189,7 +189,7 @@ while (true) { try { println REP(line) } catch(MalException ex) { - println "Error: ${ex.message}" + println "Error: ${printer.pr_str(ex.obj, true)}" } catch(StackOverflowError ex) { println "Error: ${ex}" } catch(ex) { diff --git a/guile/step0_repl.scm b/guile/step0_repl.scm index dd598b6d2e..9680c773a0 100644 --- a/guile/step0_repl.scm +++ b/guile/step0_repl.scm @@ -15,18 +15,24 @@ (import (readline)) -(define (READ) (_readline "user> ")) +(define (READ str) + str) (define (EVAL ast env) ast) (define (PRINT str) - (and (not (eof-object? str)) - (format #t "~a~%" str))) + (format #t "~a~%" str)) (define (LOOP continue?) (and continue? (REPL))) (define (REPL) - (LOOP (PRINT (EVAL (READ) '())))) + (LOOP + (let ((line (_readline "user> "))) + (cond + ((eof-object? line) #f) + ((string=? line "") #t) + (else + (PRINT (EVAL (READ line) '()))))))) (REPL) diff --git a/guile/step1_read_print.scm b/guile/step1_read_print.scm index 3517f7b791..cfb9a2ad06 100644 --- a/guile/step1_read_print.scm +++ b/guile/step1_read_print.scm @@ -15,8 +15,8 @@ (import (readline) (reader) (printer)) -(define (READ) - (read_str (_readline "user> "))) +(define (READ str) + (read_str str)) (define (EVAL ast env) ast) @@ -29,11 +29,14 @@ (define (REPL) (LOOP - (catch 'mal-error - (lambda () (PRINT (EVAL (READ) '()))) - (lambda (k . e) - (if (string=? (car e) "blank line") - (display "") - (format #t "Error: ~a~%" (car e))))))) + (let ((line (_readline "user> "))) + (cond + ((eof-object? line) #f) + ((string=? line "") #t) + (else + (catch 'mal-error + (lambda () (PRINT (EVAL (READ line) '()))) + (lambda (k . e) + (format #t "Error: ~a~%" (pr_str (car e) #t))))))))) (REPL) diff --git a/guile/step2_eval.scm b/guile/step2_eval.scm index a621118874..27759d4568 100644 --- a/guile/step2_eval.scm +++ b/guile/step2_eval.scm @@ -21,8 +21,8 @@ (* . ,*) (/ . ,/))) -(define (READ) - (read_str (_readline "user> "))) +(define (READ str) + (read_str str)) (define (eval_ast ast env) (define (_eval x) (EVAL x env)) @@ -37,17 +37,12 @@ ht) (else ast))) -(define (eval_func ast env) - (define expr (eval_ast ast env)) - (match expr - (((? procedure? proc) args ...) - (apply proc args)) - (else (throw 'mal-error (format #f "'~a' not found" (car expr)))))) - (define (EVAL ast env) (match ast (() ast) - ((? list?) (eval_func ast env)) + ((? list?) + (let ((el (eval_ast ast env))) + (apply (car el) (cdr el)))) (else (eval_ast ast env)))) (define (PRINT exp) @@ -59,11 +54,14 @@ (define (REPL) (LOOP - (catch 'mal-error - (lambda () (PRINT (EVAL (READ) *toplevel*))) - (lambda (k . e) - (if (string=? (car e) "blank line") - (display "") - (format #t "Error: ~a~%" (car e))))))) + (let ((line (_readline "user> "))) + (cond + ((eof-object? line) #f) + ((string=? line "") #t) + (else + (catch 'mal-error + (lambda () (PRINT (EVAL (READ line) *toplevel*))) + (lambda (k . e) + (format #t "Error: ~a~%" (pr_str (car e) #t))))))))) (REPL) diff --git a/guile/step3_env.scm b/guile/step3_env.scm index 40a891b127..546335ec87 100644 --- a/guile/step3_env.scm +++ b/guile/step3_env.scm @@ -26,8 +26,8 @@ (receive (b e) (unzip2 *primitives*) (make-Env #:binds b #:exprs e))) -(define (READ) - (read_str (_readline "user> "))) +(define (READ str) + (read_str str)) (define (eval_ast ast env) (define (_eval x) (EVAL x env)) @@ -36,17 +36,11 @@ ((? list? lst) (map _eval lst)) ((? vector? vec) (vector-map (lambda (i x) (_eval x)) vec)) ((? hash-table? ht) - (hash-for-each (lambda (k v) (hash-set! ht k (_eval v))) ht) - ht) + ;; NOTE: we must allocate a new hashmap here to avoid any side-effects, or + ;; there'll be strange bugs!!! + (list->hash-map (hash-fold (lambda (k v p) (cons k (cons (_eval v) p))) '() ht))) (else ast))) -(define (eval_func ast env) - (define expr (eval_ast ast env)) - (match expr - (((? procedure? proc) args ...) - (apply proc args)) - (else (throw 'mal-error (format #f "'~a' not found" (car expr)))))) - (define (EVAL ast env) (define (->list kvs) ((if (vector? kvs) vector->list identity) kvs)) (define (%unzip2 kvs) @@ -54,7 +48,8 @@ (cond ;; NOTE: reverse is very important here! ((null? next) (values (reverse k) (reverse v))) - ((null? (cdr next)) (throw 'mal-error "let*: Invalid binding form" kvs)) + ((null? (cdr next)) + (throw 'mal-error (format #f "let*: Invalid binding form '~a'" kvs))) (else (lp (cddr next) (cons (car next) k) (cons (cadr next) v)))))) (match ast ((? (lambda (x) (not (list? x)))) (eval_ast ast env)) @@ -66,7 +61,9 @@ (receive (keys vals) (%unzip2 (->list kvs)) (for-each setter keys vals)) (EVAL body new-env))) - (else (eval_func ast env)))) + (else + (let ((el (eval_ast ast env))) + (apply (car el) (cdr el)))))) (define (PRINT exp) (and (not (eof-object? exp)) @@ -77,11 +74,14 @@ (define (REPL) (LOOP - (catch 'mal-error - (lambda () (PRINT (EVAL (READ) *toplevel*))) - (lambda (k . e) - (if (string=? (car e) "blank line") - (display "") - (format #t "Error: ~a~%" (car e))))))) + (let ((line (_readline "user> "))) + (cond + ((eof-object? line) #f) + ((string=? line "") #t) + (else + (catch 'mal-error + (lambda () (PRINT (EVAL (READ line) *toplevel*))) + (lambda (k . e) + (format #t "Error: ~a~%" (pr_str (car e) #t))))))))) (REPL) diff --git a/guile/step4_if_fn_do.scm b/guile/step4_if_fn_do.scm index ac330e84a1..7323c932d0 100644 --- a/guile/step4_if_fn_do.scm +++ b/guile/step4_if_fn_do.scm @@ -17,11 +17,11 @@ (srfi srfi-1) (ice-9 receive) (env) (core) (types)) (define *toplevel* - (receive (b e) (unzip2 core.ns) + (receive (b e) (unzip2 core.ns) (make-Env #:binds b #:exprs e))) -(define (READ) - (read_str (_readline "user> "))) +(define (READ str) + (read_str str)) (define (eval_ast ast env) (define (_eval x) (EVAL x env)) @@ -31,17 +31,11 @@ ((? list? lst) (map _eval lst)) ((? vector? vec) (vector-map (lambda (i x) (_eval x)) vec)) ((? hash-table? ht) - (hash-for-each (lambda (k v) (hash-set! ht k (_eval v))) ht) - ht) + ;; NOTE: we must allocate a new hashmap here to avoid any side-effects, or + ;; there'll be strange bugs!!! + (list->hash-map (hash-fold (lambda (k v p) (cons k (cons (_eval v) p))) '() ht))) (else ast))) -(define (eval_func ast env) - (define expr (eval_ast ast env)) - (match expr - (((? procedure? proc) args ...) - (apply proc args)) - (else (throw 'mal-error (format #f "'~a' not found" (car expr)))))) - (define (eval_seq ast env) (cond ((null? ast) nil) @@ -51,15 +45,16 @@ (eval_seq (cdr ast) env)))) (define (EVAL ast env) - (define (->list kvs) ((if (vector? kvs) vector->list identity) kvs)) (define (%unzip2 kvs) (let lp((next kvs) (k '()) (v '())) (cond ;; NOTE: reverse is very important here! ((null? next) (values (reverse k) (reverse v))) - ((null? (cdr next)) (throw 'mal-error "let*: Invalid binding form" kvs)) + ((null? (cdr next)) + (throw 'mal-error (format #f "let*: Invalid binding form '~a'" kvs))) (else (lp (cddr next) (cons (car next) k) (cons (cadr next) v)))))) (match ast + ((? non-list?) (eval_ast ast env)) (() ast) (('def! k v) ((env 'set) k (EVAL v env))) (('let* kvs body) @@ -68,19 +63,22 @@ (receive (keys vals) (%unzip2 (->list kvs)) (for-each setter keys vals)) (EVAL body new-env))) - (('do rest ...) (eval_seq rest env)) + (('do rest ...) + (eval_seq rest env)) (('if cnd thn els ...) (cond ((and (not (null? els)) (not (null? (cdr els)))) ;; Invalid `if' form - (throw 'mal-error "failed to match any pattern in form " ast)) + (throw 'mal-error + (format #f "if: failed to match any pattern in form '~a'" ast))) ((cond-true? (EVAL cnd env)) (EVAL thn env)) (else (if (null? els) nil (EVAL (car els) env))))) (('fn* params body ...) ; function definition (lambda args (eval_seq body (make-Env #:outer env #:binds (->list params) #:exprs args)))) - ((? list?) (eval_func ast env)) ; function calling - (else (eval_ast ast env)))) + (else + (let ((el (map (lambda (x) (EVAL x env)) ast))) + (apply (car el) (cdr el)))))) (define (PRINT exp) (and (not (eof-object? exp)) @@ -91,11 +89,14 @@ (define (REPL) (LOOP - (catch 'mal-error - (lambda () (PRINT (EVAL (READ) *toplevel*))) - (lambda (k . e) - (if (string=? (car e) "blank line") - (display "") - (format #t "Error: ~a~%" (car e))))))) + (let ((line (_readline "user> "))) + (cond + ((eof-object? line) #f) + ((string=? line "") #t) + (else + (catch 'mal-error + (lambda () (PRINT (EVAL (READ line) *toplevel*))) + (lambda (k . e) + (format #t "Error: ~a~%" (pr_str (car e) #t))))))))) (REPL) diff --git a/guile/step5_tco.scm b/guile/step5_tco.scm index 5971aa4a34..030c0dc308 100644 --- a/guile/step5_tco.scm +++ b/guile/step5_tco.scm @@ -17,11 +17,11 @@ (srfi srfi-1) (ice-9 receive) (env) (core) (types)) (define *toplevel* - (receive (b e) (unzip2 core.ns) - (make-Env #:binds b #:exprs e))) + (receive (b e) (unzip2 core.ns) + (make-Env #:binds b #:exprs (map make-func e)))) -(define (READ) - (read_str (_readline "user> "))) +(define (READ str) + (read_str str)) (define (eval_ast ast env) (define (_eval x) (EVAL x env)) @@ -31,17 +31,11 @@ ((? list? lst) (map _eval lst)) ((? vector? vec) (vector-map (lambda (i x) (_eval x)) vec)) ((? hash-table? ht) - (hash-for-each (lambda (k v) (hash-set! ht k (_eval v))) ht) - ht) + ;; NOTE: we must allocate a new hashmap here to avoid any side-effects, or + ;; there'll be strange bugs!!! + (list->hash-map (hash-fold (lambda (k v p) (cons k (cons (_eval v) p))) '() ht))) (else ast))) -(define (eval_func ast env) - (define expr (eval_ast ast env)) - (match expr - (((? procedure? proc) args ...) - (apply proc args)) - (else (throw 'mal-error (format #f "'~a' not found" (car expr)))))) - (define (eval_seq ast env) (cond ((null? ast) nil) @@ -51,13 +45,13 @@ (eval_seq (cdr ast) env)))) (define (EVAL ast env) - (define (->list kvs) ((if (vector? kvs) vector->list identity) kvs)) (define (%unzip2 kvs) (let lp((next kvs) (k '()) (v '())) (cond ;; NOTE: reverse is very important here! ((null? next) (values (reverse k) (reverse v))) - ((null? (cdr next)) (throw 'mal-error "let*: Invalid binding form" kvs)) + ((null? (cdr next)) + (throw 'mal-error (format #f "let*: Invalid binding form '~a'" kvs))) (else (lp (cddr next) (cons (car next) k) (cons (cadr next) v)))))) ;; NOTE: I wish I can use (while #t ...) for that, but this is not Lispy, which means ;; it'll bring some trouble in control flow. We have to use continuations to return @@ -68,6 +62,7 @@ ;; If you're Lispy enough, there's no recursive at all while you saw named let loop. (let tco-loop((ast ast) (env env)) (match ast + ((? non-list?) (eval_ast ast env)) (() ast) (('def! k v) ((env 'set) k (EVAL v env))) (('let* kvs body) @@ -78,7 +73,8 @@ (tco-loop body new-env))) (('do rest ...) (cond - ((null? rest) (throw 'mal-error "do: Invalid form!" rest)) + ((null? rest) + (throw 'mal-error (format #f "do: Invalid form! '~a'" rest))) ((= 1 (length rest)) (tco-loop (car rest) env)) (else (let ((mexpr (take rest (1- (length rest)))) @@ -89,22 +85,26 @@ (cond ((and (not (null? els)) (not (null? (cdr els)))) ;; Invalid `if' form - (throw 'mal-error "if: failed to match any pattern in form " ast)) + (throw 'mal-error + (format #f "if: failed to match any pattern in form '~a'" ast))) ((cond-true? (EVAL cnd env)) (tco-loop thn env)) (else (if (null? els) nil (tco-loop (car els) env))))) (('fn* params body ...) ; function definition - (lambda args - (let ((nenv (make-Env #:outer env #:binds (->list params) #:exprs args))) - (cond - ((null? body) (throw 'mal-error "fn*: bad lambda in form " ast)) - ((= 1 (length body)) (tco-loop (car body) nenv)) - (else - (let ((mexpr (take body (1- (length body)))) - (tail-call (car (take-right body 1)))) - (eval_seq mexpr nenv) - (tco-loop tail-call nenv))))))) - ((? list?) (eval_func ast env)) ; function calling - (else (eval_ast ast env))))) + (make-func + (lambda args + (let ((nenv (make-Env #:outer env #:binds (->list params) #:exprs args))) + (cond + ((null? body) + (throw 'mal-error (format #f "fn*: bad lambda in form '~a'" ast))) + ((= 1 (length body)) (tco-loop (car body) nenv)) + (else + (let ((mexpr (take body (1- (length body)))) + (tail-call (car (take-right body 1)))) + (eval_seq mexpr nenv) + (tco-loop tail-call nenv)))))))) + (else + (let ((el (map (lambda (x) (EVAL x env)) ast))) + (callable-apply (car el) (cdr el))))))) (define (PRINT exp) (and (not (eof-object? exp)) @@ -115,12 +115,15 @@ (define (REPL) (LOOP - (catch 'mal-error - (lambda () (PRINT (EVAL (READ) *toplevel*))) - (lambda (k . e) - (if (string=? (car e) "blank line") - (display "") - (format #t "Error: ~a~%" (car e))))))) + (let ((line (_readline "user> "))) + (cond + ((eof-object? line) #f) + ((string=? line "") #t) + (else + (catch 'mal-error + (lambda () (PRINT (EVAL (READ line) *toplevel*))) + (lambda (k . e) + (format #t "Error: ~a~%" (pr_str (car e) #t))))))))) ;; NOTE: we have to reduce stack size to pass step5 test ((@ (system vm vm) call-with-stack-overflow-handler) diff --git a/guile/step6_file.scm b/guile/step6_file.scm index ccb4a0adb3..19bf58373c 100644 --- a/guile/step6_file.scm +++ b/guile/step6_file.scm @@ -20,8 +20,8 @@ (receive (b e) (unzip2 core.ns) (make-Env #:binds b #:exprs (map make-func e)))) -(define (READ) - (read_str (_readline "user> "))) +(define (READ str) + (read_str str)) (define (eval_ast ast env) (define (_eval x) (EVAL x env)) @@ -31,19 +31,11 @@ ((? list? lst) (map _eval lst)) ((? vector? vec) (vector-map (lambda (i x) (_eval x)) vec)) ((? hash-table? ht) - (hash-for-each (lambda (k v) (hash-set! ht k (_eval v))) ht) - ht) + ;; NOTE: we must allocate a new hashmap here to avoid any side-effects, or + ;; there'll be strange bugs!!! + (list->hash-map (hash-fold (lambda (k v p) (cons k (cons (_eval v) p))) '() ht))) (else ast))) -(define (eval_func ast env) - (define (_eval o) (EVAL o env)) - (define (func? x) (and=> ((env 'get) x) is-func)) - (cond - ((func? (car ast)) - => (lambda (c) - (callable-apply c (map _eval (cdr ast))))) - (else (throw 'mal-error (format #f "'~a' not found" (car ast)))))) - (define (eval_seq ast env) (cond ((null? ast) nil) @@ -53,13 +45,13 @@ (eval_seq (cdr ast) env)))) (define (EVAL ast env) - (define (->list kvs) ((if (vector? kvs) vector->list identity) kvs)) (define (%unzip2 kvs) (let lp((next kvs) (k '()) (v '())) (cond ;; NOTE: reverse is very important here! ((null? next) (values (reverse k) (reverse v))) - ((null? (cdr next)) (throw 'mal-error "let*: Invalid binding form" kvs)) + ((null? (cdr next)) + (throw 'mal-error (format #f "let*: Invalid binding form '~a'" kvs))) (else (lp (cddr next) (cons (car next) k) (cons (cadr next) v)))))) ;; NOTE: I wish I can use (while #t ...) for that, but this is not Lispy, which means ;; it'll bring some trouble in control flow. We have to use continuations to return @@ -70,6 +62,7 @@ ;; If you're Lispy enough, there's no recursive at all while you saw named let loop. (let tco-loop((ast ast) (env env)) (match ast + ((? non-list?) (eval_ast ast env)) (() ast) (('def! k v) ((env 'set) k (EVAL v env))) (('let* kvs body) @@ -80,7 +73,8 @@ (tco-loop body new-env))) (('do rest ...) (cond - ((null? rest) (throw 'mal-error "do: Invalid form!" rest)) + ((null? rest) + (throw 'mal-error (format #f "do: Invalid form! '~a'" rest))) ((= 1 (length rest)) (tco-loop (car rest) env)) (else (let ((mexpr (take rest (1- (length rest)))) @@ -91,7 +85,8 @@ (cond ((and (not (null? els)) (not (null? (cdr els)))) ;; Invalid `if' form - (throw 'mal-error "if: failed to match any pattern in form " ast)) + (throw 'mal-error + (format #f "if: failed to match any pattern in form '~a'" ast))) ((cond-true? (EVAL cnd env)) (tco-loop thn env)) (else (if (null? els) nil (tco-loop (car els) env))))) (('fn* params body ...) ; function definition @@ -99,15 +94,17 @@ (lambda args (let ((nenv (make-Env #:outer env #:binds (->list params) #:exprs args))) (cond - ((null? body) (throw 'mal-error "fn*: bad lambda in form " ast)) + ((null? body) + (throw 'mal-error (format #f "fn*: bad lambda in form '~a'" ast))) ((= 1 (length body)) (tco-loop (car body) nenv)) (else (let ((mexpr (take body (1- (length body)))) (tail-call (car (take-right body 1)))) (eval_seq mexpr nenv) (tco-loop tail-call nenv)))))))) - ((? list?) (eval_func ast env)) ; function calling - (else (eval_ast ast env))))) + (else + (let ((el (map (lambda (x) (EVAL x env)) ast))) + (callable-apply (car el) (cdr el))))))) (define (EVAL-string str) (EVAL (read_str str) *toplevel*)) @@ -121,12 +118,15 @@ (define (REPL) (LOOP - (catch 'mal-error - (lambda () (PRINT (EVAL (READ) *toplevel*))) - (lambda (k . e) - (if (string=? (car e) "blank line") - (display "") - (format #t "Error: ~a~%" (car e))))))) + (let ((line (_readline "user> "))) + (cond + ((eof-object? line) #f) + ((string=? line "") #t) + (else + (catch 'mal-error + (lambda () (PRINT (EVAL (READ line) *toplevel*))) + (lambda (k . e) + (format #t "Error: ~a~%" (pr_str (car e) #t))))))))) ;; initialization ((*toplevel* 'set) 'eval (make-func (lambda (ast) (EVAL ast *toplevel*)))) diff --git a/guile/step7_quote.scm b/guile/step7_quote.scm index bffd8de22c..bdf9998f3e 100644 --- a/guile/step7_quote.scm +++ b/guile/step7_quote.scm @@ -20,8 +20,8 @@ (receive (b e) (unzip2 core.ns) (make-Env #:binds b #:exprs (map make-func e)))) -(define (READ) - (read_str (_readline "user> "))) +(define (READ str) + (read_str str)) (define (eval_ast ast env) (define (_eval x) (EVAL x env)) @@ -31,19 +31,11 @@ ((? list? lst) (map _eval lst)) ((? vector? vec) (vector-map (lambda (i x) (_eval x)) vec)) ((? hash-table? ht) - (hash-for-each (lambda (k v) (hash-set! ht k (_eval v))) ht) - ht) + ;; NOTE: we must allocate a new hashmap here to avoid any side-effects, or + ;; there'll be strange bugs!!! + (list->hash-map (hash-fold (lambda (k v p) (cons k (cons (_eval v) p))) '() ht))) (else ast))) -(define (eval_func ast env) - (define (_eval o) (EVAL o env)) - (define (func? x) (and=> ((env 'get) x) is-func)) - (cond - ((func? (car ast)) - => (lambda (c) - (callable-apply c (map _eval (cdr ast))))) - (else (throw 'mal-error (format #f "'~a' not found" (car ast)))))) - (define (eval_seq ast env) (cond ((null? ast) nil) @@ -58,22 +50,15 @@ (cond ;; NOTE: reverse is very important here! ((null? next) (values (reverse k) (reverse v))) - ((null? (cdr next)) (throw 'mal-error "let*: Invalid binding form" kvs)) + ((null? (cdr next)) + (throw 'mal-error (format #f "let*: Invalid binding form '~a'" kvs))) (else (lp (cddr next) (cons (car next) k) (cons (cadr next) v)))))) - ;; (define (_quasiquote ast) - ;; (define (non-pair? x) (not (pair? x))) - ;; (match ast - ;; ((? non-pair?) `(quote ,ast)) - ;; (('unquote unq) unq) - ;; (((? pair? p) ('splice-unquote unqsp) rest ...) - ;; `(concat ,p ,unqsp ,(_quasiquote rest))) - ;; (else `(cons ,(_quasiquote (car ast)) ,(_quasiquote (cdr ast)))))) (define (_quasiquote obj) (match obj ((('unquote unq) rest ...) `(cons ,unq ,(_quasiquote rest))) (('unquote unq) unq) ((('splice-unquote unqsp) rest ...) `(concat ,unqsp ,(_quasiquote rest))) - ((head rest ...) (list 'cons (list 'quote head) (_quasiquote rest))) + ((head rest ...) (list 'cons (_quasiquote head) (_quasiquote rest))) (else `(quote ,obj)))) ;; NOTE: I wish I can use (while #t ...) for that, but this is not Lispy, which means ;; it'll bring some trouble in control flow. We have to use continuations to return @@ -84,6 +69,7 @@ ;; If you're Lispy enough, there's no recursive at all while you saw named let loop. (let tco-loop((ast ast) (env env)) (match ast + ((? non-list?) (eval_ast ast env)) (() ast) (('quote obj) obj) (('quasiquote obj) (EVAL (_quasiquote (->list obj)) env)) @@ -96,7 +82,8 @@ (tco-loop body new-env))) (('do rest ...) (cond - ((null? rest) (throw 'mal-error "do: Invalid form!" rest)) + ((null? rest) + (throw 'mal-error (format #f "do: Invalid form! '~a'" rest))) ((= 1 (length rest)) (tco-loop (car rest) env)) (else (let ((mexpr (take rest (1- (length rest)))) @@ -107,7 +94,8 @@ (cond ((and (not (null? els)) (not (null? (cdr els)))) ;; Invalid `if' form - (throw 'mal-error "if: failed to match any pattern in form " ast)) + (throw 'mal-error + (format #f "if: failed to match any pattern in form '~a'" ast))) ((cond-true? (EVAL cnd env)) (tco-loop thn env)) (else (if (null? els) nil (tco-loop (car els) env))))) (('fn* params body ...) ; function definition @@ -115,15 +103,17 @@ (lambda args (let ((nenv (make-Env #:outer env #:binds (->list params) #:exprs args))) (cond - ((null? body) (throw 'mal-error "fn*: bad lambda in form " ast)) + ((null? body) + (throw 'mal-error (format #f "fn*: bad lambda in form '~a'" ast))) ((= 1 (length body)) (tco-loop (car body) nenv)) (else (let ((mexpr (take body (1- (length body)))) (tail-call (car (take-right body 1)))) (eval_seq mexpr nenv) (tco-loop tail-call nenv)))))))) - ((? list?) (eval_func ast env)) ; function calling - (else (eval_ast ast env))))) + (else + (let ((el (map (lambda (x) (EVAL x env)) ast))) + (callable-apply (car el) (cdr el))))))) (define (EVAL-string str) (EVAL (read_str str) *toplevel*)) @@ -137,12 +127,15 @@ (define (REPL) (LOOP - (catch 'mal-error - (lambda () (PRINT (EVAL (READ) *toplevel*))) - (lambda (k . e) - (if (string=? (car e) "blank line") - (display "") - (format #t "Error: ~a~%" (car e))))))) + (let ((line (_readline "user> "))) + (cond + ((eof-object? line) #f) + ((string=? line "") #t) + (else + (catch 'mal-error + (lambda () (PRINT (EVAL (READ line) *toplevel*))) + (lambda (k . e) + (format #t "Error: ~a~%" (pr_str (car e) #t))))))))) ;; initialization ((*toplevel* 'set) 'eval (make-func (lambda (ast) (EVAL ast *toplevel*)))) diff --git a/guile/step8_macros.scm b/guile/step8_macros.scm index aa098c412c..fd63938913 100644 --- a/guile/step8_macros.scm +++ b/guile/step8_macros.scm @@ -20,8 +20,8 @@ (receive (b e) (unzip2 core.ns) (make-Env #:binds b #:exprs (map make-func e)))) -(define (READ) - (read_str (_readline "user> "))) +(define (READ str) + (read_str str)) (define (eval_ast ast env) (define (_eval x) (EVAL x env)) @@ -31,19 +31,11 @@ ((? list? lst) (map _eval lst)) ((? vector? vec) (vector-map (lambda (i x) (_eval x)) vec)) ((? hash-table? ht) - (hash-for-each (lambda (k v) (hash-set! ht k (_eval v))) ht) - ht) + ;; NOTE: we must allocate a new hashmap here to avoid any side-effects, or + ;; there'll be strange bugs!!! + (list->hash-map (hash-fold (lambda (k v p) (cons k (cons (_eval v) p))) '() ht))) (else ast))) -(define (eval_func ast env) - (define (_eval o) (EVAL o env)) - (define (func? x) (and=> ((env 'get) x) is-func)) - (cond - ((func? (car ast)) - => (lambda (c) - (callable-apply c (map _eval (cdr ast))))) - (else (throw 'mal-error (format #f "'~a' not found" (car ast)))))) - (define (eval_seq ast env) (cond ((null? ast) nil) @@ -72,7 +64,8 @@ (cond ;; NOTE: reverse is very important here! ((null? next) (values (reverse k) (reverse v))) - ((null? (cdr next)) (throw 'mal-error "let*: Invalid binding form" kvs)) + ((null? (cdr next)) + (throw 'mal-error (format #f "let*: Invalid binding form '~a'" kvs))) (else (lp (cddr next) (cons (car next) k) (cons (cadr next) v)))))) (define (_quasiquote obj) (match obj @@ -109,7 +102,8 @@ (tco-loop body new-env))) (('do rest ...) (cond - ((null? rest) (throw 'mal-error "do: Invalid form!" rest)) + ((null? rest) + (throw 'mal-error (format #f "do: Invalid form! '~a'" rest))) ((= 1 (length rest)) (tco-loop (car rest) env)) (else (let ((mexpr (take rest (1- (length rest)))) @@ -120,22 +114,26 @@ (cond ((and (not (null? els)) (not (null? (cdr els)))) ;; Invalid `if' form - (throw 'mal-error "if: failed to match any pattern in form " ast)) + (throw 'mal-error + (format #f "if: failed to match any pattern in form '~a'" ast))) ((cond-true? (EVAL cnd env)) (tco-loop thn env)) (else (if (null? els) nil (tco-loop (car els) env))))) (('fn* params body ...) ; function definition - (make-func + (make-anonymous-func (lambda args (let ((nenv (make-Env #:outer env #:binds (->list params) #:exprs args))) (cond - ((null? body) (throw 'mal-error "fn*: bad lambda in form " ast)) + ((null? body) + (throw 'mal-error (format #f "fn*: bad lambda in form '~a'" ast))) ((= 1 (length body)) (tco-loop (car body) nenv)) (else (let ((mexpr (take body (1- (length body)))) (tail-call (car (take-right body 1)))) (eval_seq mexpr nenv) (tco-loop tail-call nenv)))))))) - (else (eval_func ast env)))))) + (else + (let ((el (map (lambda (x) (EVAL x env)) ast))) + (callable-apply (car el) (cdr el)))))))) (define (EVAL-string str) (EVAL (read_str str) *toplevel*)) @@ -149,12 +147,15 @@ (define (REPL) (LOOP - (catch 'mal-error - (lambda () (PRINT (EVAL (READ) *toplevel*))) - (lambda (k . e) - (if (string=? (car e) "blank line") - (display "") - (format #t "Error: ~a~%" (car e))))))) + (let ((line (_readline "user> "))) + (cond + ((eof-object? line) #f) + ((string=? line "") #t) + (else + (catch 'mal-error + (lambda () (PRINT (EVAL (READ line) *toplevel*))) + (lambda (k . e) + (format #t "Error: ~a~%" (pr_str (car e) #t))))))))) ;; initialization ((*toplevel* 'set) 'eval (make-func (lambda (ast) (EVAL ast *toplevel*)))) diff --git a/guile/step9_try.scm b/guile/step9_try.scm index 33de5622c3..95f71fc95e 100644 --- a/guile/step9_try.scm +++ b/guile/step9_try.scm @@ -16,12 +16,21 @@ (import (readline) (reader) (printer) (ice-9 match) (srfi srfi-43) (srfi srfi-1) (ice-9 receive) (env) (core) (types)) +;; Primitives which doesn't unbox args in default. +;; This is a trick to implement meta-info taking advange of the original +;; types of Guile as possible. +(define *unbox-exception* '(meta assoc swap!)) + (define *toplevel* (receive (b e) (unzip2 core.ns) - (make-Env #:binds b #:exprs (map make-func e)))) + (let ((env (make-Env #:binds b #:exprs (map make-func e)))) + (for-each (lambda (f) + (callable-unbox-set! ((env 'get) f) #f)) + *unbox-exception*) + env))) -(define (READ) - (read_str (_readline "user> "))) +(define (READ str) + (read_str str)) (define (eval_ast ast env) (define (_eval x) (EVAL x env)) @@ -31,19 +40,11 @@ ((? list? lst) (map _eval lst)) ((? vector? vec) (vector-map (lambda (i x) (_eval x)) vec)) ((? hash-table? ht) - (hash-for-each (lambda (k v) (hash-set! ht k (_eval v))) ht) - ht) + ;; NOTE: we must allocate a new hashmap here to avoid any side-effects, or + ;; there'll be strange bugs!!! + (list->hash-map (hash-fold (lambda (k v p) (cons k (cons (_eval v) p))) '() ht))) (else ast))) -(define (eval_func ast env) - (define (_eval o) (EVAL o env)) - (define (func? x) (and=> (env-check x env) is-func)) - ;;(format #t "AAA: ~a~%" (func? (car ast))) - (cond - ((func? (car ast)) - => (lambda (c) - (callable-apply c (map _eval (cdr ast))))) - (else (throw 'mal-error (format #f "'~a' not found" (car ast)))))) (define (eval_seq ast env) (cond @@ -75,7 +76,8 @@ (cond ;; NOTE: reverse is very important here! ((null? next) (values (reverse k) (reverse v))) - ((null? (cdr next)) (throw 'mal-error "let*: Invalid binding form" kvs)) + ((null? (cdr next)) + (throw 'mal-error (format #f "let*: Invalid binding form '~a'" kvs))) (else (lp (cddr next) (cons (car next) k) (cons (cadr next) v)))))) (define (_quasiquote obj) (match obj @@ -113,7 +115,8 @@ (tco-loop body new-env))) (('do rest ...) (cond - ((null? rest) (throw 'mal-error "do: Invalid form!" rest)) + ((null? rest) + (throw 'mal-error (format #f "do: Invalid form! '~a'" rest))) ((= 1 (length rest)) (tco-loop (car rest) env)) (else (let ((mexpr (take rest (1- (length rest)))) @@ -124,21 +127,25 @@ (cond ((and (not (null? els)) (not (null? (cdr els)))) ;; Invalid `if' form - (throw 'mal-error "if: failed to match any pattern in form " ast)) + (throw 'mal-error + (format #f "if: failed to match any pattern in form '~a'" ast))) ((cond-true? (EVAL cnd env)) (tco-loop thn env)) (else (if (null? els) nil (tco-loop (car els) env))))) (('fn* params body ...) ; function definition - (make-func + (make-anonymous-func (lambda args (let ((nenv (make-Env #:outer env #:binds (->list params) #:exprs args))) (cond - ((null? body) (throw 'mal-error "fn*: bad lambda in form " ast)) + ((null? body) + (throw 'mal-error (format #f "fn*: bad lambda in form '~a'" ast))) ((= 1 (length body)) (tco-loop (car body) nenv)) (else (let ((mexpr (take body (1- (length body)))) (tail-call (car (take-right body 1)))) (eval_seq mexpr nenv) (tco-loop tail-call nenv)))))))) + (('try* A) + (EVAL A env)) (('try* A ('catch* B C)) (catch #t @@ -146,7 +153,9 @@ (lambda e (let ((nenv (make-Env #:outer env #:binds (list B) #:exprs (cdr e)))) (EVAL C nenv))))) - (else (eval_func ast env)))))) + (else + (let ((el (map (lambda (x) (EVAL x env)) ast))) + (callable-apply (car el) (cdr el)))))))) (define (EVAL-string str) (EVAL (read_str str) *toplevel*)) @@ -160,12 +169,15 @@ (define (REPL) (LOOP - (catch 'mal-error - (lambda () (PRINT (EVAL (READ) *toplevel*))) - (lambda (k . e) - (if (string=? (car e) "blank line") - (display "") - (format #t "Error: ~a~%" (car e))))))) + (let ((line (_readline "user> "))) + (cond + ((eof-object? line) #f) + ((string=? line "") #t) + (else + (catch 'mal-error + (lambda () (PRINT (EVAL (READ line) *toplevel*))) + (lambda (k . e) + (format #t "Error: ~a~%" (pr_str (car e) #t))))))))) ;; initialization ((*toplevel* 'set) 'eval (make-func (lambda (ast) (EVAL ast *toplevel*)))) diff --git a/guile/stepA_mal.scm b/guile/stepA_mal.scm index 1f9bf0e3bc..4a5f4ea3af 100644 --- a/guile/stepA_mal.scm +++ b/guile/stepA_mal.scm @@ -29,12 +29,13 @@ *unbox-exception*) env))) -(define (READ) - (read_str (_readline "user> "))) +(define (READ str) + (read_str str)) (define (eval_ast ast env) (define (_eval x) (EVAL x env)) (match ast + ((? _nil? obj) obj) ((? symbol? sym) (env-has sym env)) ((? list? lst) (map _eval lst)) ((? vector? vec) (vector-map (lambda (i x) (_eval x)) vec)) @@ -44,20 +45,6 @@ (list->hash-map (hash-fold (lambda (k v p) (cons k (cons (_eval v) p))) '() ht))) (else ast))) -(define (eval_func ast env) - (define (_eval o) (EVAL o env)) - (define (func? x) - (let ((f (if (list? x) - (EVAL x env) - x))) - (if (callable? f) - f - (and=> (env-check f env) is-func)))) - (cond - ((func? (car ast)) - => (lambda (c) - (callable-apply c (map _eval (cdr ast))))) - (else (throw 'mal-error (format #f "'~a' not found" (car ast)))))) (define (eval_seq ast env) (cond @@ -154,18 +141,18 @@ (tail-call (car (take-right body 1)))) (eval_seq mexpr nenv) (tco-loop tail-call nenv)))))))) + (('try* A) + (EVAL A env)) (('try* A ('catch* B C)) (catch #t (lambda () (EVAL A env)) - (lambda (k . e) - (case k - ((mal-error) - (let ((nenv (make-Env #:outer env #:binds (list B) #:exprs e))) - (EVAL C nenv))) - ;; TODO: add backtrace - (else (print-exception (current-output-port) #f k e)))))) - (else (eval_func ast env)))))) + (lambda e + (let ((nenv (make-Env #:outer env #:binds (list B) #:exprs (cdr e)))) + (EVAL C nenv))))) + (else + (let ((el (map (lambda (x) (EVAL x env)) ast))) + (callable-apply (car el) (cdr el)))))))) (define (EVAL-string str) (EVAL (read_str str) *toplevel*)) @@ -179,15 +166,15 @@ (define (REPL) (LOOP - (catch #t - (lambda () (PRINT (EVAL (READ) *toplevel*))) - (lambda (k . e) - (case k - ((mal-error) - (if (string=? (car e) "blank line") - (display "") - (format #t "Error: ~a~%" (car e)))) - (else (print-exception (current-output-port) #f k e))))))) + (let ((line (_readline "user> "))) + (cond + ((eof-object? line) #f) + ((string=? line "") #t) + (else + (catch 'mal-error + (lambda () (PRINT (EVAL (READ line) *toplevel*))) + (lambda (k . e) + (format #t "Error: ~a~%" (pr_str (car e) #t))))))))) ;; initialization ((*toplevel* 'set) 'eval (make-func (lambda (ast) (EVAL ast *toplevel*)))) diff --git a/haxe/Step0_repl.hx b/haxe/Step0_repl.hx index 9f5dbcb749..d34806a8e3 100644 --- a/haxe/Step0_repl.hx +++ b/haxe/Step0_repl.hx @@ -29,7 +29,7 @@ class Step0_repl { } catch (exc:haxe.io.Eof) { Compat.exit(0); } catch (exc:Dynamic) { - Compat.println(exc); + Compat.println("Error: " + exc); } } } diff --git a/haxe/Step1_read_print.hx b/haxe/Step1_read_print.hx index 3be96f66c3..dd22fb95b2 100644 --- a/haxe/Step1_read_print.hx +++ b/haxe/Step1_read_print.hx @@ -35,7 +35,7 @@ class Step1_read_print { } catch (exc:haxe.io.Eof) { Compat.exit(0); } catch (exc:Dynamic) { - Compat.println(exc); + Compat.println("Error: " + exc); } } } diff --git a/haxe/Step2_eval.hx b/haxe/Step2_eval.hx index a61239ade1..02d34f4a54 100644 --- a/haxe/Step2_eval.hx +++ b/haxe/Step2_eval.hx @@ -85,7 +85,7 @@ class Step2_eval { } catch (exc:haxe.io.Eof) { Compat.exit(0); } catch (exc:Dynamic) { - Compat.println(exc); + Compat.println("Error: " + exc); } } } diff --git a/haxe/Step3_env.hx b/haxe/Step3_env.hx index 805b984e6d..5166025841 100644 --- a/haxe/Step3_env.hx +++ b/haxe/Step3_env.hx @@ -96,7 +96,7 @@ class Step3_env { } catch (exc:haxe.io.Eof) { Compat.exit(0); } catch (exc:Dynamic) { - Compat.println(exc); + Compat.println("Error: " + exc); } } } diff --git a/haxe/Step4_if_fn_do.hx b/haxe/Step4_if_fn_do.hx index 064df794cb..6821da74fa 100644 --- a/haxe/Step4_if_fn_do.hx +++ b/haxe/Step4_if_fn_do.hx @@ -1,6 +1,7 @@ import Compat; import types.Types.MalType; import types.Types.*; +import types.MalException; import reader.*; import printer.*; import env.*; @@ -107,7 +108,11 @@ class Step4_if_fn_do { } catch (exc:haxe.io.Eof) { Compat.exit(0); } catch (exc:Dynamic) { - Compat.println(exc); + if (Type.getClass(exc) == MalException) { + Compat.println("Error: " + Printer.pr_str(exc.obj, true)); + } else { + Compat.println("Error: " + exc); + }; } } } diff --git a/haxe/Step5_tco.hx b/haxe/Step5_tco.hx index 190e3e8704..f1b69b9c67 100644 --- a/haxe/Step5_tco.hx +++ b/haxe/Step5_tco.hx @@ -1,6 +1,7 @@ import Compat; import types.Types.MalType; import types.Types.*; +import types.MalException; import reader.*; import printer.*; import env.*; @@ -122,7 +123,11 @@ class Step5_tco { } catch (exc:haxe.io.Eof) { Compat.exit(0); } catch (exc:Dynamic) { - Compat.println(exc); + if (Type.getClass(exc) == MalException) { + Compat.println("Error: " + Printer.pr_str(exc.obj, true)); + } else { + Compat.println("Error: " + exc); + }; } } } diff --git a/haxe/Step6_file.hx b/haxe/Step6_file.hx index 93513a928f..4e7856b533 100644 --- a/haxe/Step6_file.hx +++ b/haxe/Step6_file.hx @@ -1,6 +1,7 @@ import Compat; import types.Types.MalType; import types.Types.*; +import types.MalException; import reader.*; import printer.*; import env.*; @@ -137,7 +138,11 @@ class Step6_file { } catch (exc:haxe.io.Eof) { Compat.exit(0); } catch (exc:Dynamic) { - Compat.println(exc); + if (Type.getClass(exc) == MalException) { + Compat.println("Error: " + Printer.pr_str(exc.obj, true)); + } else { + Compat.println("Error: " + exc); + }; } } } diff --git a/haxe/Step7_quote.hx b/haxe/Step7_quote.hx index c1fe9ec7d7..8cf7e45eeb 100644 --- a/haxe/Step7_quote.hx +++ b/haxe/Step7_quote.hx @@ -1,6 +1,7 @@ import Compat; import types.Types.MalType; import types.Types.*; +import types.MalException; import reader.*; import printer.*; import env.*; @@ -170,7 +171,11 @@ class Step7_quote { } catch (exc:haxe.io.Eof) { Compat.exit(0); } catch (exc:Dynamic) { - Compat.println(exc); + if (Type.getClass(exc) == MalException) { + Compat.println("Error: " + Printer.pr_str(exc.obj, true)); + } else { + Compat.println("Error: " + exc); + }; } } } diff --git a/haxe/Step8_macros.hx b/haxe/Step8_macros.hx index 525b03be48..dc115bd7d1 100644 --- a/haxe/Step8_macros.hx +++ b/haxe/Step8_macros.hx @@ -1,6 +1,7 @@ import Compat; import types.Types.MalType; import types.Types.*; +import types.MalException; import reader.*; import printer.*; import env.*; @@ -209,7 +210,11 @@ class Step8_macros { } catch (exc:haxe.io.Eof) { Compat.exit(0); } catch (exc:Dynamic) { - Compat.println(exc); + if (Type.getClass(exc) == MalException) { + Compat.println("Error: " + Printer.pr_str(exc.obj, true)); + } else { + Compat.println("Error: " + exc); + }; } } } diff --git a/haxe/Step9_try.hx b/haxe/Step9_try.hx index 52d7480fa9..07b7f0603e 100644 --- a/haxe/Step9_try.hx +++ b/haxe/Step9_try.hx @@ -232,7 +232,11 @@ class Step9_try { } catch (exc:haxe.io.Eof) { Compat.exit(0); } catch (exc:Dynamic) { - Compat.println(exc); + if (Type.getClass(exc) == MalException) { + Compat.println("Error: " + Printer.pr_str(exc.obj, true)); + } else { + Compat.println("Error: " + exc); + }; } } } diff --git a/haxe/StepA_mal.hx b/haxe/StepA_mal.hx index 2d00764806..2b566d85ba 100644 --- a/haxe/StepA_mal.hx +++ b/haxe/StepA_mal.hx @@ -236,7 +236,11 @@ class StepA_mal { } catch (exc:haxe.io.Eof) { Compat.exit(0); } catch (exc:Dynamic) { - Compat.println(exc); + if (Type.getClass(exc) == MalException) { + Compat.println("Error: " + Printer.pr_str(exc.obj, true)); + } else { + Compat.println("Error: " + exc); + }; } } } diff --git a/hy/step1_read_print.hy b/hy/step1_read_print.hy index 179b09f884..ba8670e14b 100755 --- a/hy/step1_read_print.hy +++ b/hy/step1_read_print.hy @@ -25,6 +25,6 @@ (print (REP line))) (except [EOFError] (break)) (except [Blank]) - (except [] + (except [e Exception] (print (.join "" (apply traceback.format_exception (.exc_info sys)))))))) diff --git a/hy/step2_eval.hy b/hy/step2_eval.hy index 7d8b8fe7a0..a513ce5f08 100755 --- a/hy/step2_eval.hy +++ b/hy/step2_eval.hy @@ -59,6 +59,6 @@ (print (REP line))) (except [EOFError] (break)) (except [Blank]) - (except [] + (except [e Exception] (print (.join "" (apply traceback.format_exception (.exc_info sys)))))))) diff --git a/hy/step3_env.hy b/hy/step3_env.hy index 62ef837e59..c615f9e220 100755 --- a/hy/step3_env.hy +++ b/hy/step3_env.hy @@ -75,6 +75,6 @@ (print (REP line))) (except [EOFError] (break)) (except [Blank]) - (except [] + (except [e Exception] (print (.join "" (apply traceback.format_exception (.exc_info sys)))))))) diff --git a/hy/step4_if_fn_do.hy b/hy/step4_if_fn_do.hy index b75831d08d..17276ae043 100755 --- a/hy/step4_if_fn_do.hy +++ b/hy/step4_if_fn_do.hy @@ -2,6 +2,7 @@ (import [hy.models [HySymbol :as Sym]]) (import sys traceback) +(import [mal_types [MalException]]) (import [reader [read-str Blank]]) (import [printer [pr-str]]) (import [env [env-new env-get env-set]]) @@ -95,6 +96,9 @@ (print (REP line))) (except [EOFError] (break)) (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception - (.exc_info sys)))))))) + (except [e Exception] + (setv msg (.rstrip (.join "" (apply traceback.format_exception + (.exc_info sys))))) + (if (instance? MalException e) + (setv msg (+ (.rstrip msg) ": " (pr-str e.val True)))) + (print msg))))) diff --git a/hy/step5_tco.hy b/hy/step5_tco.hy index 55e07d67aa..c137a459bf 100755 --- a/hy/step5_tco.hy +++ b/hy/step5_tco.hy @@ -2,6 +2,7 @@ (import [hy.models [HySymbol :as Sym]]) (import sys traceback) +(import [mal_types [MalException]]) (import [reader [read-str Blank]]) (import [printer [pr-str]]) (import [env [env-new env-get env-set]]) @@ -112,6 +113,9 @@ (print (REP line))) (except [EOFError] (break)) (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception - (.exc_info sys)))))))) + (except [e Exception] + (setv msg (.rstrip (.join "" (apply traceback.format_exception + (.exc_info sys))))) + (if (instance? MalException e) + (setv msg (+ (.rstrip msg) ": " (pr-str e.val True)))) + (print msg))))) diff --git a/hy/step6_file.hy b/hy/step6_file.hy index e74ec2dc17..409297de7f 100755 --- a/hy/step6_file.hy +++ b/hy/step6_file.hy @@ -2,6 +2,7 @@ (import [hy.models [HyString :as Str HySymbol :as Sym]]) (import sys traceback) +(import [mal_types [MalException]]) (import [reader [read-str Blank]]) (import [printer [pr-str]]) (import [env [env-new env-get env-set]]) @@ -119,6 +120,9 @@ (print (REP line))) (except [EOFError] (break)) (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception - (.exc_info sys)))))))))) + (except [e Exception] + (setv msg (.rstrip (.join "" (apply traceback.format_exception + (.exc_info sys))))) + (if (instance? MalException e) + (setv msg (+ (.rstrip msg) ": " (pr-str e.val True)))) + (print msg))))))) diff --git a/hy/step7_quote.hy b/hy/step7_quote.hy index c78259a730..bd9ce05af6 100755 --- a/hy/step7_quote.hy +++ b/hy/step7_quote.hy @@ -2,6 +2,7 @@ (import [hy.models [HyString :as Str HySymbol :as Sym]]) (import sys traceback) +(import [mal_types [MalException]]) (import [reader [read-str Blank]]) (import [printer [pr-str]]) (import [env [env-new env-get env-set]]) @@ -143,6 +144,9 @@ (print (REP line))) (except [EOFError] (break)) (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception - (.exc_info sys)))))))))) + (except [e Exception] + (setv msg (.rstrip (.join "" (apply traceback.format_exception + (.exc_info sys))))) + (if (instance? MalException e) + (setv msg (+ (.rstrip msg) ": " (pr-str e.val True)))) + (print msg))))))) diff --git a/hy/step8_macros.hy b/hy/step8_macros.hy index 68832345d4..7297a2a882 100755 --- a/hy/step8_macros.hy +++ b/hy/step8_macros.hy @@ -2,6 +2,7 @@ (import [hy.models [HyString :as Str HySymbol :as Sym]]) (import sys traceback) +(import [mal_types [MalException]]) (import [reader [read-str Blank]]) (import [printer [pr-str]]) (import [env [env-new env-get env-set env-find]]) @@ -173,6 +174,9 @@ (print (REP line))) (except [EOFError] (break)) (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception - (.exc_info sys)))))))))) + (except [e Exception] + (setv msg (.rstrip (.join "" (apply traceback.format_exception + (.exc_info sys))))) + (if (instance? MalException e) + (setv msg (+ (.rstrip msg) ": " (pr-str e.val True)))) + (print msg))))))) diff --git a/hy/step9_try.hy b/hy/step9_try.hy index b1a7d50ced..d436aa701a 100755 --- a/hy/step9_try.hy +++ b/hy/step9_try.hy @@ -104,7 +104,7 @@ (macroexpand a1 env) (= (Sym "try*") a0) - (if (= (Sym "catch*") (nth a2 0)) + (if (and a2 (= (Sym "catch*") (nth a2 0))) (try (EVAL a1 env) (except [e Exception] @@ -186,6 +186,9 @@ (print (REP line))) (except [EOFError] (break)) (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception - (.exc_info sys)))))))))) + (except [e Exception] + (setv msg (.rstrip (.join "" (apply traceback.format_exception + (.exc_info sys))))) + (if (instance? MalException e) + (setv msg (+ (.rstrip msg) ": " (pr-str e.val True)))) + (print msg))))))) diff --git a/hy/stepA_mal.hy b/hy/stepA_mal.hy index 0981895fd9..2883ff4c7f 100755 --- a/hy/stepA_mal.hy +++ b/hy/stepA_mal.hy @@ -104,7 +104,7 @@ (macroexpand a1 env) (= (Sym "try*") a0) - (if (= (Sym "catch*") (nth a2 0)) + (if (and a2 (= (Sym "catch*") (nth a2 0))) (try (EVAL a1 env) (except [e Exception] @@ -190,6 +190,9 @@ (print (REP line))) (except [EOFError] (break)) (except [Blank]) - (except [] - (print (.join "" (apply traceback.format_exception - (.exc_info sys)))))))))) + (except [e Exception] + (setv msg (.rstrip (.join "" (apply traceback.format_exception + (.exc_info sys))))) + (if (instance? MalException e) + (setv msg (+ (.rstrip msg) ": " (pr-str e.val True)))) + (print msg))))))) diff --git a/io/step4_if_fn_do.io b/io/step4_if_fn_do.io index 9f1644d542..587c4d23ae 100644 --- a/io/step4_if_fn_do.io +++ b/io/step4_if_fn_do.io @@ -70,6 +70,9 @@ loop( if(line isEmpty, continue) e := try(REP(line) println) e catch(Exception, - ("Error: " .. (e error)) println + if(e type == "MalException", + ("Error: " .. ((e val) malPrint(true))) println, + ("Error: " .. (e error)) println + ) ) ) diff --git a/io/step5_tco.io b/io/step5_tco.io index 944bf6d716..db0867f772 100644 --- a/io/step5_tco.io +++ b/io/step5_tco.io @@ -85,6 +85,9 @@ loop( if(line isEmpty, continue) e := try(REP(line) println) e catch(Exception, - ("Error: " .. (e error)) println + if(e type == "MalException", + ("Error: " .. ((e val) malPrint(true))) println, + ("Error: " .. (e error)) println + ) ) ) diff --git a/io/step6_file.io b/io/step6_file.io index cf985b7d7c..3bdc6a7f80 100644 --- a/io/step6_file.io +++ b/io/step6_file.io @@ -94,6 +94,9 @@ loop( if(line isEmpty, continue) e := try(REP(line) println) e catch(Exception, - ("Error: " .. (e error)) println + if(e type == "MalException", + ("Error: " .. ((e val) malPrint(true))) println, + ("Error: " .. (e error)) println + ) ) ) diff --git a/io/step7_quote.io b/io/step7_quote.io index ce4b962424..0a0c4aeb83 100644 --- a/io/step7_quote.io +++ b/io/step7_quote.io @@ -112,6 +112,9 @@ loop( if(line isEmpty, continue) e := try(REP(line) println) e catch(Exception, - ("Error: " .. (e error)) println + if(e type == "MalException", + ("Error: " .. ((e val) malPrint(true))) println, + ("Error: " .. (e error)) println + ) ) ) diff --git a/io/step8_macros.io b/io/step8_macros.io index 6ab02f4798..b451019f24 100644 --- a/io/step8_macros.io +++ b/io/step8_macros.io @@ -139,6 +139,9 @@ loop( if(line isEmpty, continue) e := try(REP(line) println) e catch(Exception, - ("Error: " .. (e error)) println + if(e type == "MalException", + ("Error: " .. ((e val) malPrint(true))) println, + ("Error: " .. (e error)) println + ) ) ) diff --git a/io/step9_try.io b/io/step9_try.io index 990bcd13d2..6c56b10963 100644 --- a/io/step9_try.io +++ b/io/step9_try.io @@ -92,6 +92,7 @@ EVAL := method(ast, env, "macroexpand", return(macroexpand(ast at(1), env)), "try*", + if(ast at(2) == nil, return(EVAL(ast at(1), env))) e := try(result := EVAL(ast at(1), env)) e catch(Exception, exc := if(e type == "MalException", e val, e error) @@ -149,6 +150,9 @@ loop( if(line isEmpty, continue) e := try(REP(line) println) e catch(Exception, - ("Error: " .. (e error)) println + if(e type == "MalException", + ("Error: " .. ((e val) malPrint(true))) println, + ("Error: " .. (e error)) println + ) ) ) diff --git a/io/stepA_mal.io b/io/stepA_mal.io index 911b21f175..cb6fd83a61 100644 --- a/io/stepA_mal.io +++ b/io/stepA_mal.io @@ -92,6 +92,7 @@ EVAL := method(ast, env, "macroexpand", return(macroexpand(ast at(1), env)), "try*", + if(ast at(2) == nil, return(EVAL(ast at(1), env))) e := try(result := EVAL(ast at(1), env)) e catch(Exception, exc := if(e type == "MalException", e val, e error) @@ -153,6 +154,9 @@ loop( if(line isEmpty, continue) e := try(REP(line) println) e catch(Exception, - ("Error: " .. (e error)) println + if(e type == "MalException", + ("Error: " .. ((e val) malPrint(true))) println, + ("Error: " .. (e error)) println + ) ) ) diff --git a/js/step0_repl.js b/js/step0_repl.js index 126a436f30..488c82f19a 100644 --- a/js/step0_repl.js +++ b/js/step0_repl.js @@ -27,12 +27,6 @@ if (typeof require !== 'undefined' && require.main === module) { while (true) { var line = readline.readline("user> "); if (line === null) { break; } - try { - if (line) { printer.println(rep(line)); } - } catch (exc) { - - if (exc.stack) { printer.println(exc.stack); } - else { printer.println(exc); } - } + if (line) { printer.println(rep(line)); } } } diff --git a/js/step1_read_print.js b/js/step1_read_print.js index f1fb0269be..d712a2f2bd 100644 --- a/js/step1_read_print.js +++ b/js/step1_read_print.js @@ -33,9 +33,9 @@ if (typeof require !== 'undefined' && require.main === module) { try { if (line) { printer.println(rep(line)); } } catch (exc) { - if (exc instanceof reader.BlankException) { continue; } - if (exc.stack) { printer.println(exc.stack); } - else { printer.println(exc); } + if (exc instanceof reader.BlankException) { continue } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn("Error: " + printer._pr_str(exc, true)) } } } } diff --git a/js/step2_eval.js b/js/step2_eval.js index 3b1adbd648..83c04b834a 100644 --- a/js/step2_eval.js +++ b/js/step2_eval.js @@ -77,9 +77,9 @@ if (typeof require !== 'undefined' && require.main === module) { try { if (line) { printer.println(rep(line)); } } catch (exc) { - if (exc instanceof reader.BlankException) { continue; } - if (exc.stack) { printer.println(exc.stack); } - else { printer.println(exc); } + if (exc instanceof reader.BlankException) { continue } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn("Error: " + printer._pr_str(exc, true)) } } } } diff --git a/js/step3_env.js b/js/step3_env.js index 999531fcbb..cddf5d88ca 100644 --- a/js/step3_env.js +++ b/js/step3_env.js @@ -87,9 +87,9 @@ if (typeof require !== 'undefined' && require.main === module) { try { if (line) { printer.println(rep(line)); } } catch (exc) { - if (exc instanceof reader.BlankException) { continue; } - if (exc.stack) { printer.println(exc.stack); } - else { printer.println(exc); } + if (exc instanceof reader.BlankException) { continue } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn("Error: " + printer._pr_str(exc, true)) } } } } diff --git a/js/step4_if_fn_do.js b/js/step4_if_fn_do.js index 3878b03915..ce89e632d4 100644 --- a/js/step4_if_fn_do.js +++ b/js/step4_if_fn_do.js @@ -103,9 +103,9 @@ if (typeof require !== 'undefined' && require.main === module) { try { if (line) { printer.println(rep(line)); } } catch (exc) { - if (exc instanceof reader.BlankException) { continue; } - if (exc.stack) { printer.println(exc.stack); } - else { printer.println(exc); } + if (exc instanceof reader.BlankException) { continue } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn("Error: " + printer._pr_str(exc, true)) } } } } diff --git a/js/step5_tco.js b/js/step5_tco.js index a449194aa9..2a78715b9a 100644 --- a/js/step5_tco.js +++ b/js/step5_tco.js @@ -114,9 +114,9 @@ if (typeof require !== 'undefined' && require.main === module) { try { if (line) { printer.println(rep(line)); } } catch (exc) { - if (exc instanceof reader.BlankException) { continue; } - if (exc.stack) { printer.println(exc.stack); } - else { printer.println(exc); } + if (exc instanceof reader.BlankException) { continue } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn("Error: " + printer._pr_str(exc, true)) } } } } diff --git a/js/step6_file.js b/js/step6_file.js index 71b8eb5737..b166e7e1c9 100644 --- a/js/step6_file.js +++ b/js/step6_file.js @@ -124,9 +124,9 @@ if (typeof require !== 'undefined' && require.main === module) { try { if (line) { printer.println(rep(line)); } } catch (exc) { - if (exc instanceof reader.BlankException) { continue; } - if (exc.stack) { printer.println(exc.stack); } - else { printer.println(exc); } + if (exc instanceof reader.BlankException) { continue } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn("Error: " + printer._pr_str(exc, true)) } } } } diff --git a/js/step7_quote.js b/js/step7_quote.js index 6f22844cd2..3b414f177d 100644 --- a/js/step7_quote.js +++ b/js/step7_quote.js @@ -149,9 +149,9 @@ if (typeof require !== 'undefined' && require.main === module) { try { if (line) { printer.println(rep(line)); } } catch (exc) { - if (exc instanceof reader.BlankException) { continue; } - if (exc.stack) { printer.println(exc.stack); } - else { printer.println(exc); } + if (exc instanceof reader.BlankException) { continue } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn("Error: " + printer._pr_str(exc, true)) } } } } diff --git a/js/step8_macros.js b/js/step8_macros.js index fc8eb5fd2d..54e7a22a5c 100644 --- a/js/step8_macros.js +++ b/js/step8_macros.js @@ -177,9 +177,9 @@ if (typeof require !== 'undefined' && require.main === module) { try { if (line) { printer.println(rep(line)); } } catch (exc) { - if (exc instanceof reader.BlankException) { continue; } - if (exc.stack) { printer.println(exc.stack); } - else { printer.println(exc); } + if (exc instanceof reader.BlankException) { continue } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn("Error: " + printer._pr_str(exc, true)) } } } } diff --git a/js/step9_try.js b/js/step9_try.js index 3eac1c9804..211e82aed9 100644 --- a/js/step9_try.js +++ b/js/step9_try.js @@ -188,9 +188,9 @@ if (typeof require !== 'undefined' && require.main === module) { try { if (line) { printer.println(rep(line)); } } catch (exc) { - if (exc instanceof reader.BlankException) { continue; } - if (exc.stack) { printer.println(exc.stack); } - else { printer.println(exc); } + if (exc instanceof reader.BlankException) { continue } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn("Error: " + printer._pr_str(exc, true)) } } } } diff --git a/js/stepA_mal.js b/js/stepA_mal.js index b6dbab0c37..4e66230304 100644 --- a/js/stepA_mal.js +++ b/js/stepA_mal.js @@ -192,9 +192,9 @@ if (typeof require !== 'undefined' && require.main === module) { try { if (line) { printer.println(rep(line)); } } catch (exc) { - if (exc instanceof reader.BlankException) { continue; } - if (exc.stack) { printer.println(exc.stack); } - else { printer.println(exc); } + if (exc instanceof reader.BlankException) { continue } + if (exc instanceof Error) { console.warn(exc.stack) } + else { console.warn("Error: " + printer._pr_str(exc, true)) } } } } diff --git a/livescript/step3_env.ls b/livescript/step3_env.ls index 3c7dfc5cb8..b6100f2d1b 100644 --- a/livescript/step3_env.ls +++ b/livescript/step3_env.ls @@ -120,5 +120,7 @@ loop break if not line? or line == '' try console.log rep line - catch {message} - console.error message + catch error + if error.message + then console.error error.message + else console.error "Error:", pr_str error, print_readably=true diff --git a/livescript/step4_if_fn_do.ls b/livescript/step4_if_fn_do.ls index e368ff2e42..212dd755c4 100644 --- a/livescript/step4_if_fn_do.ls +++ b/livescript/step4_if_fn_do.ls @@ -168,7 +168,7 @@ eval_fn = (env, params) -> eval_apply = (env, list) -> [fn, ...args] = list |> map eval_ast env if fn.type != \function - runtime-error "#{fn.value} is not a function" + runtime-error "#{fn.value} is not a function, got a #{fn.type}" fn.value.apply env, args @@ -190,5 +190,7 @@ loop break if not line? or line == '' try console.log rep line - catch {message} - console.error message + catch error + if error.message + then console.error error.message + else console.error "Error:", pr_str error, print_readably=true diff --git a/livescript/step5_tco.ls b/livescript/step5_tco.ls index 04be185498..f1279bd103 100644 --- a/livescript/step5_tco.ls +++ b/livescript/step5_tco.ls @@ -186,7 +186,7 @@ eval_fn = (env, params) -> eval_apply = (env, list) -> [fn, ...args] = list |> map eval_ast env if fn.type != \function - runtime-error "#{fn.value} is not a function" + runtime-error "#{fn.value} is not a function, got a #{fn.type}" fn.value.apply env, args @@ -208,5 +208,7 @@ loop break if not line? or line == '' try console.log rep line - catch {message} - console.error message + catch error + if error.message + then console.error error.message + else console.error "Error:", pr_str error, print_readably=true diff --git a/livescript/step6_file.ls b/livescript/step6_file.ls index 1844ebe80f..d1b3d6d7d1 100644 --- a/livescript/step6_file.ls +++ b/livescript/step6_file.ls @@ -187,7 +187,7 @@ eval_fn = (env, params) -> eval_apply = (env, list) -> [fn, ...args] = list |> map eval_ast env if fn.type != \function - runtime-error "#{fn.value} is not a function" + runtime-error "#{fn.value} is not a function, got a #{fn.type}" fn.value.apply env, args @@ -237,5 +237,7 @@ else break if not line? or line == '' try console.log rep line - catch {message} - console.error message + catch error + if error.message + then console.error error.message + else console.error "Error:", pr_str error, print_readably=true diff --git a/livescript/step7_quote.ls b/livescript/step7_quote.ls index 0deea8647f..c3635e9628 100644 --- a/livescript/step7_quote.ls +++ b/livescript/step7_quote.ls @@ -194,7 +194,7 @@ eval_fn = (env, params) -> eval_apply = (env, list) -> [fn, ...args] = list |> map eval_ast env if fn.type != \function - runtime-error "#{fn.value} is not a function" + runtime-error "#{fn.value} is not a function, got a #{fn.type}" fn.value.apply env, args @@ -278,5 +278,7 @@ else break if not line? or line == '' try console.log rep line - catch {message} - console.error message + catch error + if error.message + then console.error error.message + else console.error "Error:", pr_str error, print_readably=true diff --git a/livescript/step8_macros.ls b/livescript/step8_macros.ls index d256ca9a47..713191658c 100644 --- a/livescript/step8_macros.ls +++ b/livescript/step8_macros.ls @@ -199,7 +199,7 @@ eval_fn = (env, params) -> eval_apply = (env, list) -> [fn, ...args] = list |> map eval_ast env if fn.type != \function - runtime-error "#{fn.value} is not a function" + runtime-error "#{fn.value} is not a function, got a #{fn.type}" fn.value.apply env, args @@ -351,5 +351,7 @@ else break if not line? or line == '' try console.log rep line - catch {message} - console.error message + catch error + if error.message + then console.error error.message + else console.error "Error:", pr_str error, print_readably=true diff --git a/livescript/step9_try.ls b/livescript/step9_try.ls index 1b6fd3b2af..7170464cbf 100644 --- a/livescript/step9_try.ls +++ b/livescript/step9_try.ls @@ -200,7 +200,7 @@ eval_fn = (env, params) -> eval_apply = (env, list) -> [fn, ...args] = list |> map eval_ast env if fn.type != \function - runtime-error "#{fn.value} is not a function" + runtime-error "#{fn.value} is not a function, got a #{fn.type}" fn.value.apply env, args @@ -284,11 +284,13 @@ eval_macroexpand = (env, params) -> eval_try = (env, params) -> - if params.length != 2 - runtime-error "'try*' expected 2 parameters, + if params.length > 2 + runtime-error "'try*' expected 1 or 2 parameters, got #{params.length}" - try-form = params[0] + if params.length == 1 + return eval_ast env, try-form + catch-clause = params[1] if catch-clause.type != \list or catch-clause.value.length != 3 or @@ -380,5 +382,7 @@ else break if not line? or line == '' try console.log rep line - catch {message} - console.error message + catch error + if error.message + then console.error error.message + else console.error "Error:", pr_str error, print_readably=true diff --git a/livescript/stepA_mal.ls b/livescript/stepA_mal.ls index eb39d8f9bb..68db80f077 100644 --- a/livescript/stepA_mal.ls +++ b/livescript/stepA_mal.ls @@ -284,11 +284,13 @@ eval_macroexpand = (env, params) -> eval_try = (env, params) -> - if params.length != 2 - runtime-error "'try*' expected 2 parameters, + if params.length > 2 + runtime-error "'try*' expected 1 or 2 parameters, got #{params.length}" - try-form = params[0] + if params.length == 1 + return eval_ast env, try-form + catch-clause = params[1] if catch-clause.type != \list or catch-clause.value.length != 3 or @@ -393,5 +395,7 @@ else break if not line? or line == '' try console.log rep line - catch {message} - console.error message + catch error + if error.message + then console.error error.message + else console.error "Error:", pr_str error, print_readably=true diff --git a/matlab/core.m b/matlab/core.m index fe455d9491..2b2d10477d 100644 --- a/matlab/core.m +++ b/matlab/core.m @@ -118,8 +118,13 @@ function ret = nth(seq, idx) if idx+1 > length(seq) - throw(MException('Range:nth', ... - 'nth: index out of range')) + if exist('OCTAVE_VERSION', 'builtin') ~= 0 + error('Range:nth', ... + 'nth: index out of range'); + else + throw(MException('Range:nth', ... + 'nth: index out of range')) + end end ret = seq.get(idx+1); end @@ -180,8 +185,13 @@ elseif isa(obj, 'types.Nil') ret = type_utils.nil; else - throw(MException('Type:seq',... - 'seq: called on non-sequence')) + if exist('OCTAVE_VERSION', 'builtin') ~= 0 + error('Type:seq', ... + 'seq: called on non-sequence'); + else + throw(MException('Type:seq',... + 'seq: called on non-sequence')) + end end end diff --git a/matlab/step9_try.m b/matlab/step9_try.m index b54461e3be..da6447a799 100644 --- a/matlab/step9_try.m +++ b/matlab/step9_try.m @@ -142,7 +142,7 @@ function step9_try(varargin), main(varargin), end ret = EVAL(ast.get(3).get(3), catch_env); return; else - throw(e); + rethrow(e); end end case 'do' diff --git a/matlab/stepA_mal.m b/matlab/stepA_mal.m index 8883ac85b4..8094889f99 100644 --- a/matlab/stepA_mal.m +++ b/matlab/stepA_mal.m @@ -142,7 +142,7 @@ function stepA_mal(varargin), main(varargin), end ret = EVAL(ast.get(3).get(3), catch_env); return; else - throw(e); + rethrow(e); end end case 'do' diff --git a/miniMAL/step4_if_fn_do.json b/miniMAL/step4_if_fn_do.json index 699aa6d6ae..f4406719c2 100644 --- a/miniMAL/step4_if_fn_do.json +++ b/miniMAL/step4_if_fn_do.json @@ -76,7 +76,10 @@ ["try", ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]], ["catch", "exc", - ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]], + ["str", ["`", "Error: "], + ["if", ["isa", "exc", "Error"], + [".", "exc", ["`", "toString"]], + ["pr-str", "exc", true]]]]]]], ["`", "core.mal: defined using miniMAL"], ["map", ["fn", ["k"], ["env-set", "repl-env", diff --git a/miniMAL/step5_tco.json b/miniMAL/step5_tco.json index bdc12a0b4d..5377cca35f 100644 --- a/miniMAL/step5_tco.json +++ b/miniMAL/step5_tco.json @@ -84,7 +84,10 @@ ["try", ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]], ["catch", "exc", - ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]], + ["str", ["`", "Error: "], + ["if", ["isa", "exc", "Error"], + [".", "exc", ["`", "toString"]], + ["pr-str", "exc", true]]]]]]], ["`", "core.mal: defined using miniMAL"], ["map", ["fn", ["k"], ["env-set", "repl-env", diff --git a/miniMAL/step6_file.json b/miniMAL/step6_file.json index 67c608832f..27f4155390 100644 --- a/miniMAL/step6_file.json +++ b/miniMAL/step6_file.json @@ -84,7 +84,10 @@ ["try", ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]], ["catch", "exc", - ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]], + ["str", ["`", "Error: "], + ["if", ["isa", "exc", "Error"], + [".", "exc", ["`", "toString"]], + ["pr-str", "exc", true]]]]]]], ["`", "core.mal: defined using miniMAL"], ["map", ["fn", ["k"], ["env-set", "repl-env", diff --git a/miniMAL/step7_quote.json b/miniMAL/step7_quote.json index 28b062c7ca..b4d6aabd24 100644 --- a/miniMAL/step7_quote.json +++ b/miniMAL/step7_quote.json @@ -109,7 +109,10 @@ ["try", ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]], ["catch", "exc", - ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]], + ["str", ["`", "Error: "], + ["if", ["isa", "exc", "Error"], + [".", "exc", ["`", "toString"]], + ["pr-str", "exc", true]]]]]]], ["`", "core.mal: defined using miniMAL"], ["map", ["fn", ["k"], ["env-set", "repl-env", diff --git a/miniMAL/step8_macros.json b/miniMAL/step8_macros.json index 2383e4107e..b0b894d6e4 100644 --- a/miniMAL/step8_macros.json +++ b/miniMAL/step8_macros.json @@ -133,7 +133,10 @@ ["try", ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]], ["catch", "exc", - ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]], + ["str", ["`", "Error: "], + ["if", ["isa", "exc", "Error"], + [".", "exc", ["`", "toString"]], + ["pr-str", "exc", true]]]]]]], ["`", "core.mal: defined using miniMAL"], ["map", ["fn", ["k"], ["env-set", "repl-env", diff --git a/miniMAL/step9_try.json b/miniMAL/step9_try.json index 8dfc0d2752..8b05eb8d3b 100644 --- a/miniMAL/step9_try.json +++ b/miniMAL/step9_try.json @@ -97,8 +97,10 @@ ["if", ["=", ["`", "macroexpand"], "a0"], ["macroexpand", ["nth", "ast", 1], "env"], ["if", ["=", ["`", "try*"], "a0"], - ["if", ["=", ["`", "catch*"], - ["get", ["nth", ["nth", "ast", 2], 0], ["`", "val"]]], + ["if", ["and", [">", ["count", "ast"], 2], + ["=", ["`", "catch*"], + ["get", ["nth", ["nth", "ast", 2], 0], + ["`", "val"]]]], ["try", ["EVAL", ["nth", "ast", 1], "env"], ["catch", "exc", @@ -144,7 +146,10 @@ ["try", ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]], ["catch", "exc", - ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]], + ["str", ["`", "Error: "], + ["if", ["isa", "exc", "Error"], + [".", "exc", ["`", "toString"]], + ["pr-str", "exc", true]]]]]]], ["`", "core.mal: defined using miniMAL"], ["map", ["fn", ["k"], ["env-set", "repl-env", diff --git a/miniMAL/stepA_mal.json b/miniMAL/stepA_mal.json index 789f88a429..30683b8743 100644 --- a/miniMAL/stepA_mal.json +++ b/miniMAL/stepA_mal.json @@ -97,8 +97,10 @@ ["if", ["=", ["`", "macroexpand"], "a0"], ["macroexpand", ["nth", "ast", 1], "env"], ["if", ["=", ["`", "try*"], "a0"], - ["if", ["=", ["`", "catch*"], - ["get", ["nth", ["nth", "ast", 2], 0], ["`", "val"]]], + ["if", ["and", [">", ["count", "ast"], 2], + ["=", ["`", "catch*"], + ["get", ["nth", ["nth", "ast", 2], 0], + ["`", "val"]]]], ["try", ["EVAL", ["nth", "ast", 1], "env"], ["catch", "exc", @@ -144,7 +146,10 @@ ["try", ["PRINT", ["EVAL", ["READ", "strng"], "repl-env"]], ["catch", "exc", - ["str", ["`", "Error: "], [".", "exc", ["`", "toString"]]]]]]], + ["str", ["`", "Error: "], + ["if", ["isa", "exc", "Error"], + [".", "exc", ["`", "toString"]], + ["pr-str", "exc", true]]]]]]], ["`", "core.mal: defined using miniMAL"], ["map", ["fn", ["k"], ["env-set", "repl-env", diff --git a/nasm/step9_try.asm b/nasm/step9_try.asm index 5c24b599fa..a9657287f1 100644 --- a/nasm/step9_try.asm +++ b/nasm/step9_try.asm @@ -2483,6 +2483,11 @@ _start: ; Check if an object was thrown cmp rsi, 0 je .catch_done_print ; nothing to print + + push rsi + print_str_mac error_string ; print 'Error: ' + pop rsi + mov rdi, 1 call pr_str mov rsi, rax diff --git a/nasm/stepA_mal.asm b/nasm/stepA_mal.asm index c47ee2f6d9..1a5014f4d1 100644 --- a/nasm/stepA_mal.asm +++ b/nasm/stepA_mal.asm @@ -2515,6 +2515,11 @@ _start: ; Check if an object was thrown cmp rsi, 0 je .catch_done_print ; nothing to print + + push rsi + print_str_mac error_string ; print 'Error: ' + pop rsi + mov rdi, 1 call pr_str mov rsi, rax diff --git a/nim/step9_try.nim b/nim/step9_try.nim index 97884ddc16..864ca91133 100644 --- a/nim/step9_try.nim +++ b/nim/step9_try.nim @@ -17,7 +17,7 @@ proc quasiquote(ast: MalType): MalType = return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. ^1]))) proc is_macro_call(ast: MalType, env: Env): bool = - ast.kind == List and ast.list[0].kind == Symbol and + ast.kind == List and ast.list.len > 0 and ast.list[0].kind == Symbol and env.find(ast.list[0].str) != nil and env.get(ast.list[0].str).fun_is_macro proc macroexpand(ast: MalType, env: Env): MalType = @@ -108,6 +108,8 @@ proc eval(ast: MalType, env: Env): MalType = let a1 = ast.list[1] a2 = ast.list[2] + if ast.list.len <= 2: + return a1.eval(env) if a2.list[0].str == "catch*": try: return a1.eval(env) @@ -149,11 +151,9 @@ proc eval(ast: MalType, env: Env): MalType = a2.eval(newEnv) return malfun(fn, a2, a1, env) - else: - defaultApply() + else: defaultApply() - else: - defaultApply() + else: defaultApply() proc print(exp: MalType): string = exp.pr_str @@ -185,6 +185,11 @@ while true: let line = readLineFromStdin("user> ") echo line.rep except Blank: discard + except IOError: quit() + except MalError: + let exc = (ref MalError) getCurrentException() + echo "Error: " & exc.t.list[0].pr_str except: + stdout.write "Error: " echo getCurrentExceptionMsg() echo getCurrentException().getStackTrace() diff --git a/nim/stepA_mal.nim b/nim/stepA_mal.nim index abc3fbc1f6..4932da837a 100644 --- a/nim/stepA_mal.nim +++ b/nim/stepA_mal.nim @@ -108,6 +108,8 @@ proc eval(ast: MalType, env: Env): MalType = let a1 = ast.list[1] a2 = ast.list[2] + if ast.list.len <= 2: + return a1.eval(env) if a2.list[0].str == "catch*": try: return a1.eval(env) @@ -189,6 +191,10 @@ while true: echo line.rep except Blank: discard except IOError: quit() + except MalError: + let exc = (ref MalError) getCurrentException() + echo "Error: " & exc.t.list[0].pr_str except: + stdout.write "Error: " echo getCurrentExceptionMsg() echo getCurrentException().getStackTrace() diff --git a/objc/step4_if_fn_do.m b/objc/step4_if_fn_do.m index 27e24827eb..a33ce66381 100644 --- a/objc/step4_if_fn_do.m +++ b/objc/step4_if_fn_do.m @@ -134,6 +134,9 @@ int main () { printf("%s\n", [[REP(line, repl_env) description] UTF8String]); } @catch(NSString *e) { printf("Error: %s\n", [e UTF8String]); + } @catch(NSObject *e) { + NSObject * exc = e; + printf("Exception: %s\n", [_pr_str(exc, true) UTF8String]); } @catch(NSException *e) { if ([[e name] isEqualTo:@"ReaderContinue"]) { continue; } printf("Exception: %s\n", [[e reason] UTF8String]); diff --git a/objc/step5_tco.m b/objc/step5_tco.m index 541e74c4c8..b5f61e7c5d 100644 --- a/objc/step5_tco.m +++ b/objc/step5_tco.m @@ -136,6 +136,9 @@ int main () { printf("%s\n", [[REP(line, repl_env) description] UTF8String]); } @catch(NSString *e) { printf("Error: %s\n", [e UTF8String]); + } @catch(NSObject *e) { + NSObject * exc = e; + printf("Exception: %s\n", [_pr_str(exc, true) UTF8String]); } @catch(NSException *e) { if ([[e name] isEqualTo:@"ReaderContinue"]) { continue; } printf("Exception: %s\n", [[e reason] UTF8String]); diff --git a/objc/step6_file.m b/objc/step6_file.m index 843e57e3cc..0c8c421f3b 100644 --- a/objc/step6_file.m +++ b/objc/step6_file.m @@ -157,6 +157,9 @@ int main () { printf("%s\n", [[REP(line, repl_env) description] UTF8String]); } @catch(NSString *e) { printf("Error: %s\n", [e UTF8String]); + } @catch(NSObject *e) { + NSObject * exc = e; + printf("Exception: %s\n", [_pr_str(exc, true) UTF8String]); } @catch(NSException *e) { if ([[e name] isEqualTo:@"ReaderContinue"]) { continue; } printf("Exception: %s\n", [[e reason] UTF8String]); diff --git a/objc/step7_quote.m b/objc/step7_quote.m index 4917b9abb5..34ef83bbd6 100644 --- a/objc/step7_quote.m +++ b/objc/step7_quote.m @@ -191,6 +191,9 @@ int main () { printf("%s\n", [[REP(line, repl_env) description] UTF8String]); } @catch(NSString *e) { printf("Error: %s\n", [e UTF8String]); + } @catch(NSObject *e) { + NSObject * exc = e; + printf("Exception: %s\n", [_pr_str(exc, true) UTF8String]); } @catch(NSException *e) { if ([[e name] isEqualTo:@"ReaderContinue"]) { continue; } printf("Exception: %s\n", [[e reason] UTF8String]); diff --git a/objc/step8_macros.m b/objc/step8_macros.m index e9d772a32a..5c3f69e922 100644 --- a/objc/step8_macros.m +++ b/objc/step8_macros.m @@ -227,6 +227,9 @@ int main () { printf("%s\n", [[REP(line, repl_env) description] UTF8String]); } @catch(NSString *e) { printf("Error: %s\n", [e UTF8String]); + } @catch(NSObject *e) { + NSObject * exc = e; + printf("Exception: %s\n", [_pr_str(exc, true) UTF8String]); } @catch(NSException *e) { if ([[e name] isEqualTo:@"ReaderContinue"]) { continue; } printf("Exception: %s\n", [[e reason] UTF8String]); diff --git a/objc/step9_try.m b/objc/step9_try.m index ac5039d1fd..23fe89f0c5 100644 --- a/objc/step9_try.m +++ b/objc/step9_try.m @@ -246,6 +246,9 @@ int main () { printf("%s\n", [[REP(line, repl_env) description] UTF8String]); } @catch(NSString *e) { printf("Error: %s\n", [e UTF8String]); + } @catch(NSObject *e) { + NSObject * exc = e; + printf("Exception: %s\n", [_pr_str(exc, true) UTF8String]); } @catch(NSException *e) { if ([[e name] isEqualTo:@"ReaderContinue"]) { continue; } printf("Exception: %s\n", [[e reason] UTF8String]); diff --git a/objc/stepA_mal.m b/objc/stepA_mal.m index 80822234e4..1a155ac812 100644 --- a/objc/stepA_mal.m +++ b/objc/stepA_mal.m @@ -249,6 +249,9 @@ int main () { printf("%s\n", [[REP(line, repl_env) description] UTF8String]); } @catch(NSString *e) { printf("Error: %s\n", [e UTF8String]); + } @catch(NSObject *e) { + NSObject * exc = e; + printf("Exception: %s\n", [_pr_str(exc, true) UTF8String]); } @catch(NSException *e) { if ([[e name] isEqualTo:@"ReaderContinue"]) { continue; } printf("Exception: %s\n", [[e reason] UTF8String]); diff --git a/objpascal/step9_try.pas b/objpascal/step9_try.pas index e8626974c0..7df833d937 100644 --- a/objpascal/step9_try.pas +++ b/objpascal/step9_try.pas @@ -331,7 +331,10 @@ function do_eval(Args : TMalArray) : TMal; On E : MalEOF do Halt(0); On E : Exception do begin - WriteLn('Error: ' + E.message); + if E.ClassType = TMalException then + WriteLn('Error: ' + pr_str((E as TMalException).Val, True)) + else + WriteLn('Error: ' + E.message); WriteLn('Backtrace:'); WriteLn(GetBacktrace(E)); end; diff --git a/objpascal/stepA_mal.pas b/objpascal/stepA_mal.pas index 93002dff22..bf5afdfa41 100644 --- a/objpascal/stepA_mal.pas +++ b/objpascal/stepA_mal.pas @@ -336,7 +336,10 @@ function do_eval(Args : TMalArray) : TMal; On E : MalEOF do Halt(0); On E : Exception do begin - WriteLn('Error: ' + E.message); + if E.ClassType = TMalException then + WriteLn('Error: ' + pr_str((E as TMalException).Val, True)) + else + WriteLn('Error: ' + E.message); WriteLn('Backtrace:'); WriteLn(GetBacktrace(E)); end; diff --git a/ocaml/core.ml b/ocaml/core.ml index 5c6f19761f..73a1378807 100644 --- a/ocaml/core.ml +++ b/ocaml/core.ml @@ -128,7 +128,8 @@ let init env = begin in concat)); Env.set env (Types.symbol "nth") - (Types.fn (function [xs; T.Int i] -> List.nth (seq xs) i | _ -> T.Nil)); + (Types.fn (function [xs; T.Int i] -> + (try List.nth (seq xs) i with _ -> raise (Invalid_argument "nth: index out of range")) | _ -> T.Nil)); Env.set env (Types.symbol "first") (Types.fn (function | [xs] -> (match seq xs with x :: _ -> x | _ -> T.Nil) diff --git a/ocaml/step9_try.ml b/ocaml/step9_try.ml index 61cee2decc..daea9d8eaf 100644 --- a/ocaml/step9_try.ml +++ b/ocaml/step9_try.ml @@ -99,6 +99,8 @@ and eval ast env = eval (quasiquote ast) env | T.List { T.value = [T.Symbol { T.value = "macroexpand" }; ast] } -> macroexpand ast env + | T.List { T.value = [T.Symbol { T.value = "try*" }; scary]} -> + (eval scary env) | T.List { T.value = [T.Symbol { T.value = "try*" }; scary ; T.List { T.value = [T.Symbol { T.value = "catch*" }; local ; handler]}]} -> @@ -107,7 +109,7 @@ and eval ast env = let value = match exn with | Types.MalExn value -> value | Invalid_argument msg -> T.String msg - | _ -> (T.String "OCaml exception") in + | e -> (T.String (Printexc.to_string e)) in let sub_env = Env.make (Some env) in Env.set sub_env local value; eval handler sub_env) @@ -156,8 +158,8 @@ let rec main = | Invalid_argument x -> output_string stderr ("Invalid_argument exception: " ^ x ^ "\n"); flush stderr - | _ -> - output_string stderr ("Erroringness!\n"); + | e -> + output_string stderr ((Printexc.to_string e) ^ "\n"); flush stderr done with End_of_file -> () diff --git a/ocaml/stepA_mal.ml b/ocaml/stepA_mal.ml index a8cbd1c3a3..d427f8c079 100644 --- a/ocaml/stepA_mal.ml +++ b/ocaml/stepA_mal.ml @@ -99,6 +99,8 @@ and eval ast env = eval (quasiquote ast) env | T.List { T.value = [T.Symbol { T.value = "macroexpand" }; ast] } -> macroexpand ast env + | T.List { T.value = [T.Symbol { T.value = "try*" }; scary]} -> + (eval scary env) | T.List { T.value = [T.Symbol { T.value = "try*" }; scary ; T.List { T.value = [T.Symbol { T.value = "catch*" }; local ; handler]}]} -> @@ -107,7 +109,7 @@ and eval ast env = let value = match exn with | Types.MalExn value -> value | Invalid_argument msg -> T.String msg - | _ -> (T.String "OCaml exception") in + | e -> (T.String (Printexc.to_string e)) in let sub_env = Env.make (Some env) in Env.set sub_env local value; eval handler sub_env) @@ -160,8 +162,8 @@ let rec main = | Invalid_argument x -> output_string stderr ("Invalid_argument exception: " ^ x ^ "\n"); flush stderr - | _ -> - output_string stderr ("Erroringness!\n"); + | e -> + output_string stderr ((Printexc.to_string e) ^ "\n"); flush stderr done end diff --git a/perl/step9_try.pl b/perl/step9_try.pl index 8b4b06a9ec..17ce2d4d1b 100644 --- a/perl/step9_try.pl +++ b/perl/step9_try.pl @@ -254,8 +254,12 @@ sub REP { # ignore and continue } default { - chomp $err; - print "Error: $err\n"; + if (ref $err) { + print "Error: ".printer::_pr_str($err)."\n"; + } else { + chomp $err; + print "Error: $err\n"; + } } } }; diff --git a/perl/stepA_mal.pl b/perl/stepA_mal.pl index 2911ccf15b..a875ed168b 100644 --- a/perl/stepA_mal.pl +++ b/perl/stepA_mal.pl @@ -261,8 +261,12 @@ sub REP { # ignore and continue } default { - chomp $err; - print "Error: $err\n"; + if (ref $err) { + print "Error: ".printer::_pr_str($err)."\n"; + } else { + chomp $err; + print "Error: $err\n"; + } } } }; diff --git a/perl6/step9_try.pl b/perl6/step9_try.pl index 30c3a4ce74..23615889f3 100644 --- a/perl6/step9_try.pl +++ b/perl6/step9_try.pl @@ -103,6 +103,7 @@ ($ast is copy, $env is copy) when 'try*' { return eval($a1, $env); CATCH { + .rethrow if !$a2; my $ex = $_ ~~ X::MalThrow ?? .value !! MalString(.Str); my $new_env = $env; $env.set($a2[1].val, $ex); @@ -146,7 +147,8 @@ ($source_file?, *@args) while (my $line = prompt 'user> ').defined { say rep($line); CATCH { - when X::MalException { .Str.say } + when X::MalThrow { say "Error: " ~ pr_str(.value, True) } + when X::MalException { say "Error: " ~ .Str } } } } diff --git a/perl6/stepA_mal.pl b/perl6/stepA_mal.pl index 76c843ad2a..1f7dc5157e 100644 --- a/perl6/stepA_mal.pl +++ b/perl6/stepA_mal.pl @@ -103,6 +103,7 @@ ($ast is copy, $env is copy) when 'try*' { return eval($a1, $env); CATCH { + .rethrow if !$a2; my $ex = $_ ~~ X::MalThrow ?? .value !! MalString(.Str); my $new_env = $env; $env.set($a2[1].val, $ex); @@ -150,7 +151,8 @@ ($source_file?, *@args) while (my $line = prompt 'user> ').defined { say rep($line); CATCH { - when X::MalException { .Str.say } + when X::MalThrow { say "Error: " ~ pr_str(.value, True) } + when X::MalException { say "Error: " ~ .Str } } } } diff --git a/php/step9_try.php b/php/step9_try.php index 7b03d4ecfe..7f87f1b706 100644 --- a/php/step9_try.php +++ b/php/step9_try.php @@ -209,6 +209,8 @@ function rep($str) { } } catch (BlankException $e) { continue; + } catch (_Error $e) { + echo "Error: " . _pr_str($e->obj, True) . "\n"; } catch (Exception $e) { echo "Error: " . $e->getMessage() . "\n"; echo $e->getTraceAsString() . "\n"; diff --git a/php/stepA_mal.php b/php/stepA_mal.php index 115732b6ca..d1cb507f4c 100644 --- a/php/stepA_mal.php +++ b/php/stepA_mal.php @@ -222,6 +222,8 @@ function rep($str) { } } catch (BlankException $e) { continue; + } catch (_Error $e) { + echo "Error: " . _pr_str($e->obj, True) . "\n"; } catch (Exception $e) { echo "Error: " . $e->getMessage() . "\n"; echo $e->getTraceAsString() . "\n"; diff --git a/plsql/step9_try.sql b/plsql/step9_try.sql index a9205b49a1..d69eee379b 100644 --- a/plsql/step9_try.sql +++ b/plsql/step9_try.sql @@ -441,7 +441,11 @@ BEGIN io.close(1); -- close output stream RETURN 0; END IF; - io.writeline('Error: ' || SQLERRM); + IF SQLCODE <> -20000 THEN + io.writeline('Error: ' || SQLERRM); + ELSE + io.writeline('Error: ' || printer.pr_str(M, H, err_val)); + END IF; io.writeline(dbms_utility.format_error_backtrace); END; END LOOP; diff --git a/plsql/stepA_mal.sql b/plsql/stepA_mal.sql index 2391e3fefa..ea69541668 100644 --- a/plsql/stepA_mal.sql +++ b/plsql/stepA_mal.sql @@ -445,7 +445,11 @@ BEGIN io.close(1); -- close output stream RETURN 0; END IF; - io.writeline('Error: ' || SQLERRM); + IF SQLCODE <> -20000 THEN + io.writeline('Error: ' || SQLERRM); + ELSE + io.writeline('Error: ' || printer.pr_str(M, H, err_val)); + END IF; io.writeline(dbms_utility.format_error_backtrace); END; END LOOP; diff --git a/ps/core.ps b/ps/core.ps index de77ab27d56cfbbed9b88874730d6ff13bf01a2f..ae4aa8e0db90d14109340af4151ec1e04796a4f8 100644 GIT binary patch delta 166 zcmccSwBBXI22SbX)Ix>Kl!{mdT|0%mk_-@)pOnR=4`ffS5!YY_%1>tGS6~LJnY@5q zG#W@Nfb`^~=A{Fb=aeW^b16Up)GUSc)Do~*Eka5IXr7fqW?o8ag+hL5i9&vwLQ!H~ kda9;Ed`U)8emO|}*Q!Y&dK}&tdrjh3Qqnbs1BA36Xu_6B%}yt2Mc+E={Z6=AbOGr%j7dc`e32oLat!i zL0Ak-hY9O~>1JU+Fnv)NtZtHs5twTPrD4jeM9jhB8%4k-PrfUn4d&k!;g~ESssd!Q zPTnBSGuctp1T0=EDhH;u#YDk!)nZ(eH;cN1#eRuGe1$CMEe7$il%T-mTu~0F9*7fH oh=I%pdPPhEOg|A5oU9HsSCWN?8$IM62r5l}FCn~{O?(D70BZVXNdN!< delta 176 zcmXYnF$)1<0EQRoO5(fk?)xMWXOWXT1_LF-C5!Q_egQwhk9aZ>WiYTPi%mB9DHId4 zONOW3p7%NRSHWQ_*b2=>_xo8yNvB>gh9=b>}&bcZ#RdWJQ{%FzBiTo^}}N RGBcfF5%o8fJmxaX#21I9J39aX diff --git a/ps/stepA_mal.ps b/ps/stepA_mal.ps index 8e6611a41a4c534d2b6dbd3374195e54202fcfbc..c2b10eece6a41b494fe16279ce511fc869c67811 100644 GIT binary patch delta 385 zcmbQ_wZ><|HlfMYV%(GG2y%;NBo-?qCzd29e7zLpA|9$ z)Bl7tfi&ynXg z#1%}h78L{2b47H)^gkiC$rnUCz-$RoeK74VY6PY$p)^d-K~ZzC_zzKt+5Tc2lXb)( zZuJ)fiLp+u5pw{mc`wc}`G^?AE*lBn$@M@!NdALZAXwf}9O5@rIj+f*#Kpn#E5#+C z>bWML5r^3EUR(t%CoBPyW0|Zi0SX7v$&xHQe5k=Vd84?<??7U F2LQYAZ{Pp` delta 182 zcmZ4EGs$biHlfLHg~TUw2pdke71o?AAjL8{PgrJhpRn>IQU1wCgcT>h6!x5KCZaPr zO~iQeToKpF&qTx~iRw-^6!n|jEUG_wo2b#`H$V&&o@^s#KDk^>XYz6}?a4D$r6(&& zicaPdx1Bssf@5;9xY*=eaZ#`h1rqF&CyKjIz9VjkB4#QfJ~==_0;)y}s840`F(BU& OXwGj5&dte^CwTyjcRHN_ diff --git a/python/core.py b/python/core.py index 86a7ec8eb0..414a5a037f 100644 --- a/python/core.py +++ b/python/core.py @@ -2,13 +2,13 @@ from itertools import chain import mal_types as types -from mal_types import List, Vector +from mal_types import MalException, List, Vector import mal_readline import reader import printer # Errors/Exceptions -def throw(exc): raise Exception(exc) +def throw(obj): raise MalException(obj) # String functions diff --git a/python/mal_types.py b/python/mal_types.py index 32c42e1d69..a4bf288b01 100644 --- a/python/mal_types.py +++ b/python/mal_types.py @@ -30,11 +30,9 @@ def _equal_Q(a, b): if not _equal_Q(a[i], b[i]): return False return True elif _hash_map_Q(a): - akeys = a.keys() - bkeys = b.keys() + akeys = sorted(a.keys()) + bkeys = sorted(b.keys()) if len(akeys) != len(bkeys): return False - akeys.sort() - bkeys.sort() for i in range(len(akeys)): if akeys[i] != bkeys[i]: return False if not _equal_Q(a[akeys[i]], b[bkeys[i]]): return False @@ -58,6 +56,13 @@ def _clone(obj): else: return copy.copy(obj) +# +# Exception type +# + +class MalException(Exception): + def __init__(self, object): + self.object = object # Scalars def _nil_Q(exp): return exp is None diff --git a/python/step9_try.py b/python/step9_try.py index 80b7f5d2f5..a6fb42903d 100644 --- a/python/step9_try.py +++ b/python/step9_try.py @@ -99,14 +99,19 @@ def EVAL(ast, env): exec(compile(ast[1], '', 'single') in globals()) return None elif "try*" == a0: + if len(ast) < 3: + return EVAL(ast[1], env) a1, a2 = ast[1], ast[2] if a2[0] == "catch*": + err = None try: - return EVAL(a1, env); + return EVAL(a1, env) + except types.MalException as exc: + err = exc.object except Exception as exc: - exc = exc.args[0] - catch_env = Env(env, [a2[1]], [exc]) - return EVAL(a2[2], catch_env) + err = exc.args[0] + catch_env = Env(env, [a2[1]], [err]) + return EVAL(a2[2], catch_env) else: return EVAL(a1, env); elif "do" == a0: @@ -166,5 +171,7 @@ def REP(str): if line == "": continue print(REP(line)) except reader.Blank: continue + except types.MalException as e: + print("Error:", printer._pr_str(e.object)) except Exception as e: print("".join(traceback.format_exception(*sys.exc_info()))) diff --git a/python/stepA_mal.py b/python/stepA_mal.py index 2da3f7190f..576bc5ef90 100644 --- a/python/stepA_mal.py +++ b/python/stepA_mal.py @@ -102,14 +102,19 @@ def EVAL(ast, env): f = eval(ast[1]) return f(*el) elif "try*" == a0: + if len(ast) < 3: + return EVAL(ast[1], env) a1, a2 = ast[1], ast[2] if a2[0] == "catch*": + err = None try: - return EVAL(a1, env); + return EVAL(a1, env) + except types.MalException as exc: + err = exc.object except Exception as exc: - exc = exc.args[0] - catch_env = Env(env, [a2[1]], [exc]) - return EVAL(a2[2], catch_env) + err = exc.args[0] + catch_env = Env(env, [a2[1]], [err]) + return EVAL(a2[2], catch_env) else: return EVAL(a1, env); elif "do" == a0: @@ -173,5 +178,7 @@ def REP(str): if line == "": continue print(REP(line)) except reader.Blank: continue + except types.MalException as e: + print("Error:", printer._pr_str(e.object)) except Exception as e: print("".join(traceback.format_exception(*sys.exc_info()))) diff --git a/r/step9_try.r b/r/step9_try.r index 80c49487e3..2575baac34 100644 --- a/r/step9_try.r +++ b/r/step9_try.r @@ -189,7 +189,7 @@ repeat { tryCatch({ cat(rep(line),"\n", sep="") }, error=function(err) { - cat("Error: ", get_error(err),"\n", sep="") + cat("Error: ", .pr_str(get_error(err),TRUE),"\n", sep="") }) # R debug/fatal with tracebacks: #cat(rep(line),"\n", sep="") diff --git a/r/stepA_mal.r b/r/stepA_mal.r index 7db7972ee9..ecdb89cfbf 100644 --- a/r/stepA_mal.r +++ b/r/stepA_mal.r @@ -193,7 +193,7 @@ repeat { tryCatch({ cat(rep(line),"\n", sep="") }, error=function(err) { - cat("Error: ", get_error(err),"\n", sep="") + cat("Error: ", .pr_str(get_error(err),TRUE),"\n", sep="") }) # R debug/fatal with tracebacks: #cat(rep(line),"\n", sep="") diff --git a/rpython/step9_try.py b/rpython/step9_try.py index c39f808a00..6d2d8db3f5 100644 --- a/rpython/step9_try.py +++ b/rpython/step9_try.py @@ -111,6 +111,8 @@ def EVAL(ast, env): elif u"macroexpand" == a0sym: return macroexpand(ast[1], env) elif u"try*" == a0sym: + if len(ast) < 3: + return EVAL(ast[1], env); a1, a2 = ast[1], ast[2] a20 = a2[0] if isinstance(a20, MalSym): diff --git a/rpython/stepA_mal.py b/rpython/stepA_mal.py index 68a9960798..0e3d54e84f 100644 --- a/rpython/stepA_mal.py +++ b/rpython/stepA_mal.py @@ -120,6 +120,8 @@ def EVAL(ast, env): elif u"macroexpand" == a0sym: return macroexpand(ast[1], env) elif u"try*" == a0sym: + if len(ast) < 3: + return EVAL(ast[1], env); a1, a2 = ast[1], ast[2] a20 = a2[0] if isinstance(a20, MalSym): diff --git a/ruby/step9_try.rb b/ruby/step9_try.rb index 828f8c15ba..71d16be752 100644 --- a/ruby/step9_try.rb +++ b/ruby/step9_try.rb @@ -174,7 +174,11 @@ def PRINT(exp) begin puts REP[line] rescue Exception => e - puts "Error: #{e}" + if e.is_a? MalException + puts "Error: #{_pr_str(e.data, true)}" + else + puts "Error: #{e}" + end puts "\t#{e.backtrace.join("\n\t")}" end end diff --git a/ruby/stepA_mal.rb b/ruby/stepA_mal.rb index 54a660cc40..9071b74e74 100644 --- a/ruby/stepA_mal.rb +++ b/ruby/stepA_mal.rb @@ -184,7 +184,11 @@ def PRINT(exp) begin puts REP[line] rescue Exception => e - puts "Error: #{e}" + if e.is_a? MalException + puts "Error: #{_pr_str(e.data, true)}" + else + puts "Error: #{e}" + end puts "\t#{e.backtrace.join("\n\t")}" end end diff --git a/scheme/step4_if_fn_do.scm b/scheme/step4_if_fn_do.scm index 3a112d028e..b077be9a11 100644 --- a/scheme/step4_if_fn_do.scm +++ b/scheme/step4_if_fn_do.scm @@ -102,7 +102,11 @@ (when (not (memv 'empty-input (error-object-irritants ex))) (display "[error] ") (display (error-object-message ex)) - (newline)))) + (newline))) + ((and (pair? ex) (eq? (car ex) 'user-error)) + (display "[error] ") + (display (pr-str (cdr ex) #t)) + (newline))) (display (rep input)) (newline)) (loop)))) diff --git a/scheme/step5_tco.scm b/scheme/step5_tco.scm index 51506e72bd..87a029158c 100644 --- a/scheme/step5_tco.scm +++ b/scheme/step5_tco.scm @@ -108,7 +108,11 @@ (when (not (memv 'empty-input (error-object-irritants ex))) (display "[error] ") (display (error-object-message ex)) - (newline)))) + (newline))) + ((and (pair? ex) (eq? (car ex) 'user-error)) + (display "[error] ") + (display (pr-str (cdr ex) #t)) + (newline))) (display (rep input)) (newline)) (loop)))) diff --git a/scheme/step6_file.scm b/scheme/step6_file.scm index 79c34e51e7..1c7e956f73 100644 --- a/scheme/step6_file.scm +++ b/scheme/step6_file.scm @@ -116,7 +116,11 @@ (when (not (memv 'empty-input (error-object-irritants ex))) (display "[error] ") (display (error-object-message ex)) - (newline)))) + (newline))) + ((and (pair? ex) (eq? (car ex) 'user-error)) + (display "[error] ") + (display (pr-str (cdr ex) #t)) + (newline))) (display (rep input)) (newline)) (loop)))) diff --git a/scheme/step7_quote.scm b/scheme/step7_quote.scm index 487a61037e..f0f2c80596 100644 --- a/scheme/step7_quote.scm +++ b/scheme/step7_quote.scm @@ -144,7 +144,11 @@ (when (not (memv 'empty-input (error-object-irritants ex))) (display "[error] ") (display (error-object-message ex)) - (newline)))) + (newline))) + ((and (pair? ex) (eq? (car ex) 'user-error)) + (display "[error] ") + (display (pr-str (cdr ex) #t)) + (newline))) (display (rep input)) (newline)) (loop)))) diff --git a/scheme/step8_macros.scm b/scheme/step8_macros.scm index 8df319a3d2..c9f177e3ed 100644 --- a/scheme/step8_macros.scm +++ b/scheme/step8_macros.scm @@ -185,7 +185,11 @@ (when (not (memv 'empty-input (error-object-irritants ex))) (display "[error] ") (display (error-object-message ex)) - (newline)))) + (newline))) + ((and (pair? ex) (eq? (car ex) 'user-error)) + (display "[error] ") + (display (pr-str (cdr ex) #t)) + (newline))) (display (rep input)) (newline)) (loop)))) diff --git a/scheme/step9_try.scm b/scheme/step9_try.scm index e9c0d3975d..b28e786ce2 100644 --- a/scheme/step9_try.scm +++ b/scheme/step9_try.scm @@ -103,16 +103,18 @@ ((macroexpand) (macroexpand (cadr items) env)) ((try*) - (let* ((form (cadr items)) - (handler (mal-value (list-ref items 2)))) - (guard - (ex ((error-object? ex) - (handle-catch - (mal-string (error-object-message ex)) - handler)) - ((and (pair? ex) (eq? (car ex) 'user-error)) - (handle-catch (cdr ex) handler))) - (EVAL form env)))) + (if (< (length items) 3) + (EVAL (cadr items) env) + (let* ((form (cadr items)) + (handler (mal-value (list-ref items 2)))) + (guard + (ex ((error-object? ex) + (handle-catch + (mal-string (error-object-message ex)) + handler)) + ((and (pair? ex) (eq? (car ex) 'user-error)) + (handle-catch (cdr ex) handler))) + (EVAL form env))))) ((let*) (let ((env* (make-env env)) (binds (->list (mal-value (cadr items)))) @@ -201,7 +203,11 @@ (when (not (memv 'empty-input (error-object-irritants ex))) (display "[error] ") (display (error-object-message ex)) - (newline)))) + (newline))) + ((and (pair? ex) (eq? (car ex) 'user-error)) + (display "[error] ") + (display (pr-str (cdr ex) #t)) + (newline))) (display (rep input)) (newline)) (loop)))) diff --git a/scheme/stepA_mal.scm b/scheme/stepA_mal.scm index f2da4e98f8..1a64c51d48 100644 --- a/scheme/stepA_mal.scm +++ b/scheme/stepA_mal.scm @@ -103,16 +103,18 @@ ((macroexpand) (macroexpand (cadr items) env)) ((try*) - (let* ((form (cadr items)) - (handler (mal-value (list-ref items 2)))) - (guard - (ex ((error-object? ex) - (handle-catch - (mal-string (error-object-message ex)) - handler)) - ((and (pair? ex) (eq? (car ex) 'user-error)) - (handle-catch (cdr ex) handler))) - (EVAL form env)))) + (if (< (length items) 3) + (EVAL (cadr items) env) + (let* ((form (cadr items)) + (handler (mal-value (list-ref items 2)))) + (guard + (ex ((error-object? ex) + (handle-catch + (mal-string (error-object-message ex)) + handler)) + ((and (pair? ex) (eq? (car ex) 'user-error)) + (handle-catch (cdr ex) handler))) + (EVAL form env))))) ((let*) (let ((env* (make-env env)) (binds (->list (mal-value (cadr items)))) @@ -206,7 +208,11 @@ (when (not (memv 'empty-input (error-object-irritants ex))) (display "[error] ") (display (error-object-message ex)) - (newline)))) + (newline))) + ((and (pair? ex) (eq? (car ex) 'user-error)) + (display "[error] ") + (display (pr-str (cdr ex) #t)) + (newline))) (display (rep input)) (newline)) (loop)))) diff --git a/swift3/Sources/step4_if_fn_do/main.swift b/swift3/Sources/step4_if_fn_do/main.swift index dabb4f2c1a..17b4c90b14 100644 --- a/swift3/Sources/step4_if_fn_do/main.swift +++ b/swift3/Sources/step4_if_fn_do/main.swift @@ -127,5 +127,7 @@ while true { print("Error: \(msg)") } catch (MalError.General(let msg)) { print("Error: \(msg)") + } catch (MalError.MalException(let obj)) { + print("Error: \(pr_str(obj, true))") } } diff --git a/swift3/Sources/step5_tco/main.swift b/swift3/Sources/step5_tco/main.swift index ae6e12a6bd..337d76f1ed 100644 --- a/swift3/Sources/step5_tco/main.swift +++ b/swift3/Sources/step5_tco/main.swift @@ -132,5 +132,7 @@ while true { print("Error: \(msg)") } catch (MalError.General(let msg)) { print("Error: \(msg)") + } catch (MalError.MalException(let obj)) { + print("Error: \(pr_str(obj, true))") } } diff --git a/swift3/Sources/step6_file/main.swift b/swift3/Sources/step6_file/main.swift index 84667cf884..e24a6b39ad 100644 --- a/swift3/Sources/step6_file/main.swift +++ b/swift3/Sources/step6_file/main.swift @@ -147,5 +147,7 @@ while true { print("Error: \(msg)") } catch (MalError.General(let msg)) { print("Error: \(msg)") + } catch (MalError.MalException(let obj)) { + print("Error: \(pr_str(obj, true))") } } diff --git a/swift3/Sources/step7_quote/main.swift b/swift3/Sources/step7_quote/main.swift index d228591a71..252d89f6d6 100644 --- a/swift3/Sources/step7_quote/main.swift +++ b/swift3/Sources/step7_quote/main.swift @@ -185,5 +185,7 @@ while true { print("Error: \(msg)") } catch (MalError.General(let msg)) { print("Error: \(msg)") + } catch (MalError.MalException(let obj)) { + print("Error: \(pr_str(obj, true))") } } diff --git a/swift3/Sources/step8_macros/main.swift b/swift3/Sources/step8_macros/main.swift index 11e851cee8..a53483f0f7 100644 --- a/swift3/Sources/step8_macros/main.swift +++ b/swift3/Sources/step8_macros/main.swift @@ -237,5 +237,7 @@ while true { print("Error: \(msg)") } catch (MalError.General(let msg)) { print("Error: \(msg)") + } catch (MalError.MalException(let obj)) { + print("Error: \(pr_str(obj, true))") } } diff --git a/swift3/Sources/step9_try/main.swift b/swift3/Sources/step9_try/main.swift index 6660900808..900dd3e371 100644 --- a/swift3/Sources/step9_try/main.swift +++ b/swift3/Sources/step9_try/main.swift @@ -270,5 +270,7 @@ while true { print("Error: \(msg)") } catch (MalError.General(let msg)) { print("Error: \(msg)") + } catch (MalError.MalException(let obj)) { + print("Error: \(pr_str(obj, true))") } } diff --git a/swift3/Sources/stepA_mal/main.swift b/swift3/Sources/stepA_mal/main.swift index 56d8fe4e88..8c796a057b 100644 --- a/swift3/Sources/stepA_mal/main.swift +++ b/swift3/Sources/stepA_mal/main.swift @@ -273,5 +273,7 @@ while true { print("Error: \(msg)") } catch (MalError.General(let msg)) { print("Error: \(msg)") + } catch (MalError.MalException(let obj)) { + print("Error: \(pr_str(obj, true))") } } diff --git a/tcl/step9_try.tcl b/tcl/step9_try.tcl index cbe6e798c3..d6a97d312a 100644 --- a/tcl/step9_try.tcl +++ b/tcl/step9_try.tcl @@ -267,7 +267,12 @@ while {true} { continue } if { [catch { puts [REP $line $repl_env] } exception] } { - puts "Error: $exception" + if {$exception == "__MalException__"} { + set res [pr_str $::mal_exception_obj 1] + puts "Error: $res" + } else { + puts "Error: $exception" + } if { $DEBUG_MODE } { puts $::errorInfo } diff --git a/tcl/stepA_mal.tcl b/tcl/stepA_mal.tcl index 010c2f5230..23dcdb07a4 100644 --- a/tcl/stepA_mal.tcl +++ b/tcl/stepA_mal.tcl @@ -275,7 +275,12 @@ while {true} { continue } if { [catch { puts [REP $line $repl_env] } exception] } { - puts "Error: $exception" + if {$exception == "__MalException__"} { + set res [pr_str $::mal_exception_obj 1] + puts "Error: $res" + } else { + puts "Error: $exception" + } if { $DEBUG_MODE } { puts $::errorInfo } diff --git a/tests/step9_try.mal b/tests/step9_try.mal index 5108914994..21e4132dbb 100644 --- a/tests/step9_try.mal +++ b/tests/step9_try.mal @@ -1,3 +1,11 @@ +;; +;; Testing throw + +(throw "err1") +;/.*([Ee][Rr][Rr][Oo][Rr]|[Ee]xception).*err1.* +(throw {:msg "err2"}) +;/.*([Ee][Rr][Rr][Oo][Rr]|[Ee]xception).*msg.*err2.* + ;; ;; Testing try*/catch* @@ -8,6 +16,11 @@ ;/"exc is:" "'abc' not found" ;=>nil +;; Make sure error from core can be caught +(try* (nth [] 1) (catch* exc (prn "exc is:" exc))) +;/"exc is:".*(length|range|[Bb]ounds|beyond).* +;=>nil + (try* (throw "my exception") (catch* exc (do (prn "exc:" exc) 7))) ;/"exc:" "my exception" ;=>7 @@ -304,6 +317,11 @@ ;;;; "exc is:" ["data" "foo"] ;;;;=>7 ;;;;=>7 +;; +;; Testing try* without catch* +(try* xyz) +;/.*\'?xyz\'? not found.* + ;; ;; Testing throwing non-strings (try* (throw (list 1 2 3)) (catch* exc (do (prn "err:" exc) 7))) diff --git a/ts/Makefile b/ts/Makefile index 902f2771c3..5b36261c10 100644 --- a/ts/Makefile +++ b/ts/Makefile @@ -11,7 +11,7 @@ all: ts node_modules: npm install -step%.js: node_modules types.ts reader.ts printer.ts env.ts core.ts +step%.js: node_modules types.ts reader.ts printer.ts env.ts core.ts step%.ts ./node_modules/.bin/tsc -p ./ diff --git a/ts/step4_if_fn_do.ts b/ts/step4_if_fn_do.ts index e98b4beae6..fd42ed738b 100644 --- a/ts/step4_if_fn_do.ts +++ b/ts/step4_if_fn_do.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { Node, MalType, MalNil, MalList, MalVector, MalHashMap, MalFunction, isSeq } from "./types"; +import { Node, MalType, MalNil, MalList, MalVector, MalHashMap, MalFunction, isAST, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -159,7 +159,11 @@ while (true) { try { console.log(rep(line)); } catch (e) { - const err: Error = e; - console.error(err.message); + if (isAST(e)) { + console.error("Error:", prStr(e)); + } else { + const err: Error = e; + console.error("Error:", err.message); + } } } diff --git a/ts/step5_tco.ts b/ts/step5_tco.ts index 02e881bbb6..04f76d4816 100644 --- a/ts/step5_tco.ts +++ b/ts/step5_tco.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { Node, MalType, MalNil, MalList, MalVector, MalHashMap, MalFunction, isSeq } from "./types"; +import { Node, MalType, MalNil, MalList, MalVector, MalHashMap, MalFunction, isAST, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -165,7 +165,11 @@ while (true) { try { console.log(rep(line)); } catch (e) { - const err: Error = e; - console.error(err.message); + if (isAST(e)) { + console.error("Error:", prStr(e)); + } else { + const err: Error = e; + console.error("Error:", err.message); + } } } diff --git a/ts/step6_file.ts b/ts/step6_file.ts index 225385da19..d86525f637 100644 --- a/ts/step6_file.ts +++ b/ts/step6_file.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; +import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -180,7 +180,11 @@ while (true) { try { console.log(rep(line)); } catch (e) { - const err: Error = e; - console.error(err.message); + if (isAST(e)) { + console.error("Error:", prStr(e)); + } else { + const err: Error = e; + console.error("Error:", err.message); + } } } diff --git a/ts/step7_quote.ts b/ts/step7_quote.ts index f360bfee61..df84c68346 100644 --- a/ts/step7_quote.ts +++ b/ts/step7_quote.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; +import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -226,7 +226,11 @@ while (true) { try { console.log(rep(line)); } catch (e) { - const err: Error = e; - console.error(err.message); + if (isAST(e)) { + console.error("Error:", prStr(e)); + } else { + const err: Error = e; + console.error("Error:", err.message); + } } } diff --git a/ts/step8_macros.ts b/ts/step8_macros.ts index d3152fb298..5de9415e9a 100644 --- a/ts/step8_macros.ts +++ b/ts/step8_macros.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; +import { Node, MalType, MalString, MalNil, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -292,7 +292,11 @@ while (true) { try { console.log(rep(line)); } catch (e) { - const err: Error = e; - console.error(err.message); + if (isAST(e)) { + console.error("Error:", prStr(e)); + } else { + const err: Error = e; + console.error("Error:", err.message); + } } } diff --git a/ts/step9_try.ts b/ts/step9_try.ts index cae3d1dc9b..d949938f1b 100644 --- a/ts/step9_try.ts +++ b/ts/step9_try.ts @@ -194,6 +194,9 @@ function evalMal(ast: MalType, env: Env): MalType { try { return evalMal(ast.list[1], env); } catch (e) { + if (ast.list.length < 3) { + throw e; + } const catchBody = ast.list[2]; if (!isSeq(catchBody)) { throw new Error(`unexpected return type: ${catchBody.type}, expected: list or vector`); @@ -314,7 +317,11 @@ while (true) { try { console.log(rep(line)); } catch (e) { - const err: Error = e; - console.error(err.message); + if (isAST(e)) { + console.error("Error:", prStr(e)); + } else { + const err: Error = e; + console.error("Error:", err.message); + } } } diff --git a/ts/stepA_mal.ts b/ts/stepA_mal.ts index 6943ce6c6d..9546cbd47c 100644 --- a/ts/stepA_mal.ts +++ b/ts/stepA_mal.ts @@ -194,6 +194,9 @@ function evalMal(ast: MalType, env: Env): MalType { try { return evalMal(ast.list[1], env); } catch (e) { + if (ast.list.length < 3) { + throw e; + } const catchBody = ast.list[2]; if (!isSeq(catchBody)) { throw new Error(`unexpected return type: ${catchBody.type}, expected: list or vector`); @@ -318,7 +321,11 @@ while (true) { try { console.log(rep(line)); } catch (e) { - const err: Error = e; - console.error(err.message); + if (isAST(e)) { + console.error("Error:", prStr(e)); + } else { + const err: Error = e; + console.error("Error:", err.message); + } } } diff --git a/vb/step9_try.vb b/vb/step9_try.vb index e94cca7824..65158adde3 100644 --- a/vb/step9_try.vb +++ b/vb/step9_try.vb @@ -191,8 +191,8 @@ Namespace Mal DirectCast(a2,MalList).slice(1,2), New MalList(exc))) End If - Throw e End If + Throw e End Try Case "do" eval_ast(ast.slice(1, ast.size()-1), env) diff --git a/vb/stepA_mal.vb b/vb/stepA_mal.vb index 08ba138c6f..39a2a1eed6 100644 --- a/vb/stepA_mal.vb +++ b/vb/stepA_mal.vb @@ -191,8 +191,8 @@ Namespace Mal DirectCast(a2,MalList).slice(1,2), New MalList(exc))) End If - Throw e End If + Throw e End Try Case "do" eval_ast(ast.slice(1, ast.size()-1), env) diff --git a/vimscript/step9_try.vim b/vimscript/step9_try.vim index 421e59cd74..da0ea89922 100644 --- a/vimscript/step9_try.vim +++ b/vimscript/step9_try.vim @@ -247,7 +247,11 @@ while 1 try call PrintLn(REP(line, repl_env)) catch - call PrintLn("Error: " . v:exception) + if v:exception == "__MalException__" + call PrintLn("Error: " . PrStr(g:MalExceptionObj, 1)) + else + call PrintLn("Error: " . v:exception) + end endtry endwhile qall! diff --git a/vimscript/stepA_mal.vim b/vimscript/stepA_mal.vim index 2f64d98747..4c416ad943 100644 --- a/vimscript/stepA_mal.vim +++ b/vimscript/stepA_mal.vim @@ -252,7 +252,11 @@ while 1 try call PrintLn(REP(line, repl_env)) catch - call PrintLn("Error: " . v:exception) + if v:exception == "__MalException__" + call PrintLn("Error: " . PrStr(g:MalExceptionObj, 1)) + else + call PrintLn("Error: " . v:exception) + end endtry endwhile qall! diff --git a/wasm/step4_if_fn_do.wam b/wasm/step4_if_fn_do.wam index d52da0e6ee..902ec36a52 100644 --- a/wasm/step4_if_fn_do.wam +++ b/wasm/step4_if_fn_do.wam @@ -269,7 +269,7 @@ (func $main (result i32) (LET $line (STATIC_ARRAY 201) - $res 0 $repl_env 0) + $res 0 $repl_env 0 $ms 0) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -300,7 +300,14 @@ (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then - ($printf_1 "Error: %s\n" (get_global $error_str)) + (if (i32.eq 2 (get_global $error_type)) + (then + (set_local $ms ($pr_str (get_global $error_val) 1)) + ($printf_1 "Error: %s\n" ($to_String $ms)) + ($RELEASE $ms) + ($RELEASE (get_global $error_val))) + (else + ($printf_1 "Error: %s\n" (get_global $error_str)))) (set_global $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) diff --git a/wasm/step5_tco.wam b/wasm/step5_tco.wam index c7a0984438..ca0aeb1364 100644 --- a/wasm/step5_tco.wam +++ b/wasm/step5_tco.wam @@ -318,7 +318,7 @@ (func $main (result i32) (LET $line (STATIC_ARRAY 201) - $res 0 $repl_env 0) + $res 0 $repl_env 0 $ms 0) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -349,7 +349,14 @@ (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then - ($printf_1 "Error: %s\n" (get_global $error_str)) + (if (i32.eq 2 (get_global $error_type)) + (then + (set_local $ms ($pr_str (get_global $error_val) 1)) + ($printf_1 "Error: %s\n" ($to_String $ms)) + ($RELEASE $ms) + ($RELEASE (get_global $error_val))) + (else + ($printf_1 "Error: %s\n" (get_global $error_str)))) (set_global $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) diff --git a/wasm/step6_file.wam b/wasm/step6_file.wam index 4898f21a18..e3029b03be 100644 --- a/wasm/step6_file.wam +++ b/wasm/step6_file.wam @@ -323,9 +323,9 @@ (func $main (param $argc i32 $argv i32) (result i32) (LET $line (STATIC_ARRAY 201) - $res 0 $repl_env 0 + $res 0 $repl_env 0 $ms 0 ;; argument processing - $i 0 $ret 0 $empty 0 $current 0 $tmp 0 $val2 0) + $i 0 $ret 0 $empty 0 $current 0 $val2 0) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -404,7 +404,14 @@ (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then - ($printf_1 "Error: %s\n" (get_global $error_str)) + (if (i32.eq 2 (get_global $error_type)) + (then + (set_local $ms ($pr_str (get_global $error_val) 1)) + ($printf_1 "Error: %s\n" ($to_String $ms)) + ($RELEASE $ms) + ($RELEASE (get_global $error_val))) + (else + ($printf_1 "Error: %s\n" (get_global $error_str)))) (set_global $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) diff --git a/wasm/step7_quote.wam b/wasm/step7_quote.wam index bfbfed10f7..753f97a8ad 100644 --- a/wasm/step7_quote.wam +++ b/wasm/step7_quote.wam @@ -385,9 +385,9 @@ (func $main (param $argc i32 $argv i32) (result i32) (LET $line (STATIC_ARRAY 201) - $res 0 $repl_env 0 + $res 0 $repl_env 0 $ms 0 ;; argument processing - $i 0 $ret 0 $empty 0 $current 0 $tmp 0 $val2 0) + $i 0 $ret 0 $empty 0 $current 0 $val2 0) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -466,7 +466,14 @@ (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then - ($printf_1 "Error: %s\n" (get_global $error_str)) + (if (i32.eq 2 (get_global $error_type)) + (then + (set_local $ms ($pr_str (get_global $error_val) 1)) + ($printf_1 "Error: %s\n" ($to_String $ms)) + ($RELEASE $ms) + ($RELEASE (get_global $error_val))) + (else + ($printf_1 "Error: %s\n" (get_global $error_str)))) (set_global $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) diff --git a/wasm/step8_macros.wam b/wasm/step8_macros.wam index 445d0fea11..95d0642d8b 100644 --- a/wasm/step8_macros.wam +++ b/wasm/step8_macros.wam @@ -468,9 +468,9 @@ (func $main (param $argc i32 $argv i32) (result i32) (LET $line (STATIC_ARRAY 201) - $res 0 $repl_env 0 + $res 0 $repl_env 0 $ms 0 ;; argument processing - $i 0 $ret 0 $empty 0 $current 0 $tmp 0 $val2 0) + $i 0 $ret 0 $empty 0 $current 0 $val2 0) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -551,7 +551,14 @@ (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then - ($printf_1 "Error: %s\n" (get_global $error_str)) + (if (i32.eq 2 (get_global $error_type)) + (then + (set_local $ms ($pr_str (get_global $error_val) 1)) + ($printf_1 "Error: %s\n" ($to_String $ms)) + ($RELEASE $ms) + ($RELEASE (get_global $error_val))) + (else + ($printf_1 "Error: %s\n" (get_global $error_str)))) (set_global $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) diff --git a/wasm/step9_try.wam b/wasm/step9_try.wam index 61d91003cd..312314b380 100644 --- a/wasm/step9_try.wam +++ b/wasm/step9_try.wam @@ -515,9 +515,9 @@ (func $main (param $argc i32 $argv i32) (result i32) (LET $line (STATIC_ARRAY 201) - $res 0 $repl_env 0 + $res 0 $repl_env 0 $ms 0 ;; argument processing - $i 0 $ret 0 $empty 0 $current 0 $tmp 0 $val2 0) + $i 0 $ret 0 $empty 0 $current 0 $val2 0) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -598,7 +598,14 @@ (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then - ($printf_1 "Error: %s\n" (get_global $error_str)) + (if (i32.eq 2 (get_global $error_type)) + (then + (set_local $ms ($pr_str (get_global $error_val) 1)) + ($printf_1 "Error: %s\n" ($to_String $ms)) + ($RELEASE $ms) + ($RELEASE (get_global $error_val))) + (else + ($printf_1 "Error: %s\n" (get_global $error_str)))) (set_global $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) diff --git a/wasm/stepA_mal.wam b/wasm/stepA_mal.wam index 495e3e6807..f1bed3d709 100644 --- a/wasm/stepA_mal.wam +++ b/wasm/stepA_mal.wam @@ -515,9 +515,9 @@ (func $main (param $argc i32 $argv i32) (result i32) (LET $line (STATIC_ARRAY 201) - $res 0 $repl_env 0 + $res 0 $repl_env 0 $ms 0 ;; argument processing - $i 0 $ret 0 $empty 0 $current 0 $tmp 0 $val2 0) + $i 0 $ret 0 $empty 0 $current 0 $val2 0) ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) @@ -602,7 +602,14 @@ (set_local $res ($REP $line $repl_env)) (if (get_global $error_type) (then - ($printf_1 "Error: %s\n" (get_global $error_str)) + (if (i32.eq 2 (get_global $error_type)) + (then + (set_local $ms ($pr_str (get_global $error_val) 1)) + ($printf_1 "Error: %s\n" ($to_String $ms)) + ($RELEASE $ms) + ($RELEASE (get_global $error_val))) + (else + ($printf_1 "Error: %s\n" (get_global $error_str)))) (set_global $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) diff --git a/yorick/step9_try.i b/yorick/step9_try.i index a4434974ea..bb1c2123be 100644 --- a/yorick/step9_try.i +++ b/yorick/step9_try.i @@ -134,7 +134,7 @@ func EVAL(ast, env) return macroexpand(*lst(2), env) } else if (a1 == "try*") { ret = EVAL(*lst(2), env) - if (structof(ret) == MalError) { + if (structof(ret) == MalError && numberof(lst) > 2) { exc = *ret.obj if (is_void(exc)) { exc = MalString(val=ret.message) @@ -255,7 +255,14 @@ func main(void) if (!line) break if (strlen(line) > 0) { result = REP(line, repl_env) - if (structof(result) == MalError) write, format="Error: %s\n", result.message + if (structof(result) == MalError) { + exc = *result.obj + if (is_void(exc)) { + write, format="Error: %s\n", result.message + } else { + write, format="Error: %s\n", pr_str(exc, 1) + } + } else write, format="%s\n", result } } diff --git a/yorick/stepA_mal.i b/yorick/stepA_mal.i index 53829636dc..1a629028b3 100644 --- a/yorick/stepA_mal.i +++ b/yorick/stepA_mal.i @@ -134,7 +134,7 @@ func EVAL(ast, env) return macroexpand(*lst(2), env) } else if (a1 == "try*") { ret = EVAL(*lst(2), env) - if (structof(ret) == MalError) { + if (structof(ret) == MalError && numberof(lst) > 2) { exc = *ret.obj if (is_void(exc)) { exc = MalString(val=ret.message) @@ -223,12 +223,10 @@ func prepare_argv_list(args) } repl_env = nil -stdin_file = open("/dev/stdin", "r") func main(void) { extern repl_env - extern stdin_file repl_env = env_new(pointer(0)) // core.i: defined using Yorick @@ -254,13 +252,21 @@ func main(void) } RE, "(println (str \"Mal [\" *host-language* \"]\"))", repl_env + stdin_file = open("/dev/stdin", "r") while (1) { write, format="%s", "user> " line = rdline(stdin_file, prompt="") if (!line) break if (strlen(line) > 0) { result = REP(line, repl_env) - if (structof(result) == MalError) write, format="Error: %s\n", result.message + if (structof(result) == MalError) { + exc = *result.obj + if (is_void(exc)) { + write, format="Error: %s\n", result.message + } else { + write, format="Error: %s\n", pr_str(exc, 1) + } + } else write, format="%s\n", result } } From 24c6bbf7a69929d47429ad329d233585962a3f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=86=E9=81=A5?= Date: Tue, 18 Dec 2018 14:05:56 +0800 Subject: [PATCH 0730/2308] In objc. Modified conj, meta and with-meta to pass all the tests in stepA. --- objc/core.m | 20 +++++++++++++++----- objc/types.m | 4 ++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/objc/core.m b/objc/core.m index 2acceeec8b..3a77d0db86 100644 --- a/objc/core.m +++ b/objc/core.m @@ -6,6 +6,7 @@ #import "printer.h" #import "malfunc.h" #import "core.h" +#import NSObject * wrap_tf(BOOL val) { return val ? [MalTrue alloc] : [MalFalse alloc]; @@ -268,7 +269,7 @@ + (NSDictionary *)ns { if ([args[0] isKindOfClass:[MalVector class]]) { [res addObjectsFromArray:args[0]]; [res addObjectsFromArray:_rest(args)]; - return (NSObject *)[MalVector arrayWithArray:res]; + return (NSObject *)[MalVector fromArray:res]; } else { [res addObjectsFromArray:[[_rest(args) reverseObjectEnumerator] allObjects]]; @@ -299,20 +300,29 @@ + (NSDictionary *)ns { } }, - @"meta": ^(NSArray *args){ + @"meta": ^id (NSArray *args){ if ([args[0] isKindOfClass:[MalFunc class]]) { return [(MalFunc *)args[0] meta]; } else { - return (NSObject *)[NSNull alloc]; + id res = objc_getAssociatedObject(args[0], @"meta"); + return res ? res : (NSObject *)[NSNull alloc]; } }, - @"with-meta": ^(NSArray *args){ + @"with-meta": ^id (NSArray *args){ if ([args[0] isKindOfClass:[MalFunc class]]) { MalFunc * cmf = [(MalFunc *)args[0] copy]; cmf.meta = args[1]; return cmf; + } else if (!block_Q(args[0])) { + id res = [args[0] copy]; + objc_setAssociatedObject(res, @"meta", args[1], OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return res; } else { - @throw @"with-meta: object type not supported"; + id (^blk)(NSArray *args) = args[0]; + id (^wrapBlock)(NSArray *args) = ^id (NSArray *args) { return blk(args); }; + id (^res)(NSArray *args) = [wrapBlock copy]; // under mrc: copy to get a malloc block instead of a stack block. + objc_setAssociatedObject(res, @"meta", args[1], OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return res; } }, @"atom": ^(NSArray *args){ diff --git a/objc/types.m b/objc/types.m index 55a2e43532..356c746223 100644 --- a/objc/types.m +++ b/objc/types.m @@ -91,6 +91,10 @@ - (id)objectAtIndex:(NSUInteger)index { return _array[index]; } +- (id)copyWithZone:(NSZone *)zone { + return [[MalVector alloc] initWithArray:[_array copy]]; +} + @end From 7aaf054b0a880921135847fad9233545048a7ba0 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 4 Jan 2019 17:03:00 -0600 Subject: [PATCH 0731/2308] tests/step3_env: don't assume def! is lexical. --- tests/step3_env.mal | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/step3_env.mal b/tests/step3_env.mal index 1539e5625e..cc8270d87f 100644 --- a/tests/step3_env.mal +++ b/tests/step3_env.mal @@ -62,10 +62,6 @@ y ;=>4 (let* (z 2) (let* (q 9) a)) ;=>4 -(let* (x 4) (def! a 5)) -;=>5 -a -;=>4 ;>>> deferrable=True ;>>> optional=True From 70cefa38a9e247a46a1d84a2c44e74162b8b5978 Mon Sep 17 00:00:00 2001 From: Raj Mahey Date: Thu, 10 Jan 2019 13:30:21 -0800 Subject: [PATCH 0732/2308] Updated README change "yes the" into "yes, the". Stylised make command and changed download to downloaded --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1b68bbfeb9..9b5f7a763c 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ The mal (make a lisp) steps are: Mal was presented publicly for the first time in a lightning talk at Clojure West 2014 (unfortunately there is no video). See examples/clojurewest2014.mal for the presentation that was given at the -conference (yes the presentation is a mal program). At Midwest.io +conference (yes, the presentation is a mal program). At Midwest.io 2015, Joel Martin gave a presentation on Mal titled "Achievement Unlocked: A Better Path to Language Learning". [Video](https://www.youtube.com/watch?v=lgyOAiRtZGw), @@ -131,7 +131,7 @@ FAQ](docs/FAQ.md) where I attempt to answer some common questions. The simplest way to run any given implementation is to use docker. Every implementation has a docker image pre-built with language -dependencies installed. You can launch the REPL using a convenience +dependencies installed. You can launch the REPL using a convenient target in the top level Makefile (where IMPL is the implementation directory name and stepX is the step to run): @@ -1232,10 +1232,10 @@ make "docker-build^IMPL" **Notes**: * Docker images are named *"kanaka/mal-test-IMPL"* * JVM-based language implementations (Groovy, Java, Clojure, Scala): - you will probably need to run these implementations once manually - first (make DOCKERIZE=1 "repl^IMPL")before you can run tests because + you will probably need to run this command once manually + first `make DOCKERIZE=1 "repl^IMPL"` before you can run tests because runtime dependencies need to be downloaded to avoid the tests timing - out. These dependencies are download to dot-files in the /mal + out. These dependencies are downloaded to dot-files in the /mal directory so they will persist between runs. ## Projects using mal From 99472e257661daa131a2e16f0c905c9a586115dd Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 12 Jan 2019 14:57:58 -0600 Subject: [PATCH 0733/2308] Clojure: reader/printer instead of monkey patching Update the Clojure/ClojureScript implementation to have full reader and printer/pr-str impementation instead of monkey patching Clojure's reader and print functions. --- clojure/Makefile | 2 +- clojure/src/mal/core.cljc | 26 +++--- clojure/src/mal/printer.cljc | 90 +++++++-------------- clojure/src/mal/reader.cljc | 110 ++++++++++++++++++-------- clojure/src/mal/step1_read_print.cljc | 2 +- clojure/src/mal/step2_eval.cljc | 2 +- clojure/src/mal/step3_env.cljc | 2 +- clojure/src/mal/step4_if_fn_do.cljc | 2 +- clojure/src/mal/step5_tco.cljc | 2 +- clojure/src/mal/step6_file.cljc | 2 +- clojure/src/mal/step7_quote.cljc | 2 +- clojure/src/mal/step8_macros.cljc | 2 +- clojure/src/mal/step9_try.cljc | 2 +- clojure/src/mal/stepA_mal.cljc | 2 +- 14 files changed, 127 insertions(+), 121 deletions(-) diff --git a/clojure/Makefile b/clojure/Makefile index f994b5ea0a..f5b4f20ccc 100644 --- a/clojure/Makefile +++ b/clojure/Makefile @@ -1,6 +1,6 @@ clojure_MODE ?= clj SOURCES_UTIL = src/mal/readline.$(clojure_MODE) -SOURCES_BASE = $(SOURCES_UTIL) src/mal/printer.cljc +SOURCES_BASE = $(SOURCES_UTIL) src/mal/reader.cljc src/mal/printer.cljc SOURCES_LISP = src/mal/env.cljc src/mal/core.cljc src/mal/stepA_mal.cljc SRCS = $(SOURCES_BASE) src/mal/env.cljc src/mal/core.cljc SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) diff --git a/clojure/src/mal/core.cljc b/clojure/src/mal/core.cljc index 283c9a3907..3e023b307a 100644 --- a/clojure/src/mal/core.cljc +++ b/clojure/src/mal/core.cljc @@ -1,7 +1,9 @@ (ns mal.core - (:require [mal.readline :as readline] + (:refer-clojure :exclude [pr-str]) + (:require [clojure.string :refer [join]] + [mal.readline :as readline] [mal.reader :as reader] - [mal.printer :as printer])) + [mal.printer :refer [pr-str atom?]])) ;; Errors/exceptions (defn mal_throw [obj] @@ -14,10 +16,6 @@ #?(:clj (defn time-ms [] (System/currentTimeMillis)) :cljs (defn time-ms [] (.getTime (js/Date.)))) -;; Atom functions -#?(:clj (defn atom? [atm] (= (type atm) clojure.lang.Atom)) - :cljs (defn atom? [atm] (satisfies? IAtom atm))) - ;; Metadata functions ;; - store metadata at :meta key of the real metadata (defn mal_with_meta [obj m] @@ -43,10 +41,10 @@ ['fn? (fn [o] (if (and (fn? o) (not (:ismacro (meta o)))) true false))] ['macro? (fn [o] (if (and (fn? o) (:ismacro (meta o))) true false))] - ['pr-str pr-str] - ['str printer/_str] - ['prn prn] - ['println println] + ['pr-str (fn [& xs] (join " " (map #(pr-str % true) xs)))] + ['str (fn [& xs] (join "" (map #(pr-str % false) xs)))] + ['prn (fn [& xs] (println (join " " (map #(pr-str % true) xs))))] + ['println (fn [& xs] (println (join " " (map #(pr-str % false) xs))))] ['readline readline/readline] ['read-string reader/read-string] ['slurp slurp] @@ -59,7 +57,7 @@ ['* *] ['/ /] ['time-ms time-ms] - + ['list list] ['list? seq?] ['vector vector] @@ -72,17 +70,17 @@ ['contains? contains?] ['keys (fn [hm] (let [ks (keys hm)] (if (nil? ks) '() ks)))] ['vals (fn [hm] (let [vs (vals hm)] (if (nil? vs) '() vs)))] - + ['sequential? sequential?] ['cons cons] - ['concat concat] + ['concat #(apply list (apply concat %&))] ['nth nth] ['first first] ['rest rest] ['empty? empty?] ['count count] ['apply apply] - ['map #(doall (map %1 %2))] + ['map #(apply list (map %1 %2))] ['conj conj] ['seq (fn [obj] (seq (if (string? obj) (map str obj) obj)))] diff --git a/clojure/src/mal/printer.cljc b/clojure/src/mal/printer.cljc index dc4d76a405..1d1d92ee99 100644 --- a/clojure/src/mal/printer.cljc +++ b/clojure/src/mal/printer.cljc @@ -1,63 +1,29 @@ -(ns mal.printer) +(ns mal.printer + (:refer-clojure :exclude [pr-str]) + (:require [clojure.string :as S])) + +;; atom? +#?(:clj (defn atom? [atm] (= (type atm) clojure.lang.Atom)) + :cljs (defn atom? [atm] (satisfies? IAtom atm))) + +(defn escape [s] + (-> s (S/replace "\\" "\\\\") + (S/replace "\"" "\\\"") + (S/replace "\n" "\\n"))) + +(defn pr-str + ([obj] (pr-str obj true)) + ([obj print-readably] + (let [_r print-readably] + (cond + (= nil obj) "nil" + (string? obj) (if _r (str "\"" (escape obj) "\"") obj) + + (list? obj) (str "(" (S/join " " (map #(pr-str % _r) obj)) ")") + (vector? obj) (str "[" (S/join " " (map #(pr-str % _r) obj)) "]") + (map? obj) (str "{" (S/join " " (map (fn [[k v]] + (str (pr-str k _r) " " + (pr-str v _r))) obj)) "}") + (atom? obj) (str "(atom " (pr-str @obj _r) ")") + :else (str obj))))) -#?(:clj (import '(java.io Writer))) - -;; TODO Better: -;; (extend-protocol IPrintWithWriter -;; Atom -;; ... -;; PersistentArrayMap -;; ... -;; PersistentHashMap -;; ...) - -;; Override atom printer -#?(:clj (defmethod clojure.core/print-method clojure.lang.Atom [a writer] - (.write writer "(atom ") - (.write writer (pr-str @a)) - (.write writer ")")) - :cljs (extend-type Atom - IPrintWithWriter - (-pr-writer [a writer _] - (-write writer (str "(atom " (pr-str @a) ")"))))) - - -;; Override hash-map printer to remove comma separators -#?(:clj (defmethod print-method clojure.lang.IPersistentMap [hm ^Writer w] - (.write w "{") - (when-let [xs (seq hm)] - (loop [[[k v] & xs] xs] - (print-method k w) - (.write w " ") - (print-method v w) - (when xs (.write w " ") (recur xs)))) - (.write w "}")) - :cljs (extend-type PersistentHashMap - IPrintWithWriter - (-pr-writer [hm w _] - (-write w "{") - (when-let [xs (seq hm)] - (loop [[[k v] & xs] xs] - (-write w (pr-str k)) - (-write w " ") - (-write w (pr-str v)) - (when xs (-write w " ") (recur xs)))) - (-write w "}")))) - - -;; Add a version of str that is the same all the way down (no -;; print-readably and nil printing all the way down) -(defn- pr- - ([] nil) - ([x] - #?(:clj (print-method x *out*) - :cljs (pr x))) - ([x & more] - (pr- x) - (if-let [nmore (next more)] - (recur (first more) nmore) - (apply pr- more)))) - -(defn _str [& xs] - (binding [*print-readably* nil] - (with-out-str (apply pr- xs)))) diff --git a/clojure/src/mal/reader.cljc b/clojure/src/mal/reader.cljc index fbe36d5788..93b2df3b52 100644 --- a/clojure/src/mal/reader.cljc +++ b/clojure/src/mal/reader.cljc @@ -1,37 +1,79 @@ (ns mal.reader - (:refer-clojure :exclude [read-string]) - #?(:clj (:require [clojure.tools.reader :as r] - [clojure.tools.reader.reader-types :as rt])) - #?(:cljs (:require [cljs.tools.reader :as r] - [cljs.tools.reader.reader-types :as rt]))) - -;; change tools.reader syntax-quote to quasiquote -(defn- wrap [sym] - (fn [rdr _] (list sym (#'r/read rdr true nil)))) - -(defn- wrap-with [sym] - (fn [rdr arg _] (list sym (#'r/read rdr true nil) arg))) - -;; Override some tools.reader reader macros so that we can do our own -;; metadata and quasiquote handling -(def new-rmacros - (fn [f] - (fn [ch] - (case ch - \` (wrap 'quasiquote) - \~ (fn [rdr comma] - (if-let [ch (rt/peek-char rdr)] - (if (identical? \@ ch) - ((wrap 'splice-unquote) (doto rdr rt/read-char) \@) - ((wrap 'unquote) rdr \~)))) - \^ (fn [rdr comma] - (let [m (#'r/read rdr)] - ((wrap-with 'with-meta) rdr m \^))) - \@ (wrap 'deref) - (f ch))))) - -#?(:clj (alter-var-root #'r/macros new-rmacros) - :cljs (set! r/macros (new-rmacros r/macros))) + (:refer-clojure :exclude [read-string]) + (:require [clojure.string :as S])) + +(defn throw-str [s] + (throw #?(:cljs (js/Error. s) + :clj (Exception. s)))) + +(defn rdr [tokens] + {:tokens (vec tokens) :position (atom 0)}) + +(defn rdr-peek [rdr] + (get (vec (:tokens rdr)) @(:position rdr))) + +(defn rdr-next [rdr] + (get (vec (:tokens rdr)) (dec (swap! (:position rdr) inc)))) + +(def tok-re #"[\s,]*(~@|[\[\]{}()'`~^@]|\"(?:[\\].|[^\\\"])*\"?|;.*|[^\s\[\]{}()'\"`@,;]+)") +(def int-re #"^-?[0-9]+$") +(def str-re #"^\"(.*)\"$") + +(defn tokenize [s] + (filter #(not= \; (first %)) + (map second (re-seq tok-re s)))) + +(defn unescape [s] + (-> s (S/replace "\\\\" "\u029e") + (S/replace "\\\"" "\"") + (S/replace "\\n" "\n") + (S/replace "\u029e" "\\"))) + +(defn read-atom [rdr] + (let [token (rdr-next rdr)] + (cond + (re-seq int-re token) #?(:cljs (js/parseInt token) + :clj (Integer/parseInt token)) + (re-seq str-re token) (unescape (second (re-find str-re token))) + (= \: (get token 0)) (keyword (subs token 1)) + (= "nil" token) nil + (= "true" token) true + (= "false" token) false + :else (symbol token)))) + +(declare read-form) + +(defn read-seq [rdr start end] + (assert (= start (rdr-next rdr))) ;; pull off start + (loop [lst []] + (let [token (rdr-peek rdr)] + (cond + (= token end) (do (rdr-next rdr) lst) + (not token) (throw-str (str "expected '" end "', got EOF")) + :else (recur (conj lst (read-form rdr))))))) + +(defn read-form [rdr] + (let [tok (rdr-peek rdr)] + (cond + (= "'" tok) (do (rdr-next rdr) (list 'quote (read-form rdr))) + (= "`" tok) (do (rdr-next rdr) (list 'quasiquote (read-form rdr))) + (= "~" tok) (do (rdr-next rdr) (list 'unquote (read-form rdr))) + (= "~@" tok) (do (rdr-next rdr) (list 'splice-unquote (read-form rdr))) + + (= "^" tok) (do (rdr-next rdr) (let [m (read-form rdr)] + (list 'with-meta (read-form rdr) m))) + (= "@" tok) (do (rdr-next rdr) (list 'deref (read-form rdr))) + + (= ")" tok) (throw-str "unexpected ')'") + (= "(" tok) (apply list (read-seq rdr "(" ")")) + + (= "]" tok) (throw-str "unexpected ']'") + (= "[" tok) (vec (read-seq rdr "[" "]")) + + (= "}" tok) (throw-str "unexpected '}'") + (= "{" tok) (apply hash-map (read-seq rdr "{" "}")) + + :else (read-atom rdr)))) (defn read-string [s] - (r/read-string s)) + (read-form (rdr (tokenize s)))) diff --git a/clojure/src/mal/step1_read_print.cljc b/clojure/src/mal/step1_read_print.cljc index d942174286..2b95bffbdb 100644 --- a/clojure/src/mal/step1_read_print.cljc +++ b/clojure/src/mal/step1_read_print.cljc @@ -14,7 +14,7 @@ ast) ;; print -(defn PRINT [exp] (pr-str exp)) +(defn PRINT [exp] (printer/pr-str exp)) ;; repl (defn rep diff --git a/clojure/src/mal/step2_eval.cljc b/clojure/src/mal/step2_eval.cljc index 51c0042239..2727f0585c 100644 --- a/clojure/src/mal/step2_eval.cljc +++ b/clojure/src/mal/step2_eval.cljc @@ -42,7 +42,7 @@ (apply f args))))) ;; print -(defn PRINT [exp] (pr-str exp)) +(defn PRINT [exp] (printer/pr-str exp)) ;; repl (def repl-env {'+ + diff --git a/clojure/src/mal/step3_env.cljc b/clojure/src/mal/step3_env.cljc index 2779574ac9..6a2c8da5a3 100644 --- a/clojure/src/mal/step3_env.cljc +++ b/clojure/src/mal/step3_env.cljc @@ -54,7 +54,7 @@ (apply f args)))))) ;; print -(defn PRINT [exp] (pr-str exp)) +(defn PRINT [exp] (printer/pr-str exp)) ;; repl (def repl-env (env/env)) diff --git a/clojure/src/mal/step4_if_fn_do.cljc b/clojure/src/mal/step4_if_fn_do.cljc index 4cca3a3b2c..fe3e4c9b02 100644 --- a/clojure/src/mal/step4_if_fn_do.cljc +++ b/clojure/src/mal/step4_if_fn_do.cljc @@ -70,7 +70,7 @@ (apply f args)))))) ;; print -(defn PRINT [exp] (pr-str exp)) +(defn PRINT [exp] (printer/pr-str exp)) ;; repl (def repl-env (env/env)) diff --git a/clojure/src/mal/step5_tco.cljc b/clojure/src/mal/step5_tco.cljc index 886c4911a0..dc1209e1e6 100644 --- a/clojure/src/mal/step5_tco.cljc +++ b/clojure/src/mal/step5_tco.cljc @@ -79,7 +79,7 @@ (apply f args)))))))) ;; print -(defn PRINT [exp] (pr-str exp)) +(defn PRINT [exp] (printer/pr-str exp)) ;; repl (def repl-env (env/env)) diff --git a/clojure/src/mal/step6_file.cljc b/clojure/src/mal/step6_file.cljc index e7b884dcc0..6e9ac76279 100644 --- a/clojure/src/mal/step6_file.cljc +++ b/clojure/src/mal/step6_file.cljc @@ -79,7 +79,7 @@ (apply f args)))))))) ;; print -(defn PRINT [exp] (pr-str exp)) +(defn PRINT [exp] (printer/pr-str exp)) ;; repl (def repl-env (env/env)) diff --git a/clojure/src/mal/step7_quote.cljc b/clojure/src/mal/step7_quote.cljc index a22645b019..f1eccfed46 100644 --- a/clojure/src/mal/step7_quote.cljc +++ b/clojure/src/mal/step7_quote.cljc @@ -102,7 +102,7 @@ (apply f args)))))))) ;; print -(defn PRINT [exp] (pr-str exp)) +(defn PRINT [exp] (printer/pr-str exp)) ;; repl (def repl-env (env/env)) diff --git a/clojure/src/mal/step8_macros.cljc b/clojure/src/mal/step8_macros.cljc index f5a552084a..48f8551f84 100644 --- a/clojure/src/mal/step8_macros.cljc +++ b/clojure/src/mal/step8_macros.cljc @@ -127,7 +127,7 @@ (apply f args)))))))))) ;; print -(defn PRINT [exp] (pr-str exp)) +(defn PRINT [exp] (printer/pr-str exp)) ;; repl (def repl-env (env/env)) diff --git a/clojure/src/mal/step9_try.cljc b/clojure/src/mal/step9_try.cljc index 2e0b91c75d..4859ba8aeb 100644 --- a/clojure/src/mal/step9_try.cljc +++ b/clojure/src/mal/step9_try.cljc @@ -144,7 +144,7 @@ (apply f args)))))))))) ;; print -(defn PRINT [exp] (pr-str exp)) +(defn PRINT [exp] (printer/pr-str exp)) ;; repl (def repl-env (env/env)) diff --git a/clojure/src/mal/stepA_mal.cljc b/clojure/src/mal/stepA_mal.cljc index efa7494684..1758ec0e4a 100644 --- a/clojure/src/mal/stepA_mal.cljc +++ b/clojure/src/mal/stepA_mal.cljc @@ -152,7 +152,7 @@ (apply f args)))))))))) ;; print -(defn PRINT [exp] (pr-str exp)) +(defn PRINT [exp] (printer/pr-str exp)) ;; repl (def repl-env (env/env)) From 6a51946b52f5b5cfe2e883e7c67b5e000d6dae57 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 14 Jan 2019 22:37:46 -0600 Subject: [PATCH 0734/2308] wasm: use platform_os for JS mode. Drop platform_js and use refactor platform_os to support JS mode. Add get_time_ms import to platform_os so this depends on run.js and fooboot providing that. --- wasm/Makefile | 2 +- wasm/platform_js.wam | 44 --------------------------------------- wasm/platform_os.wam | 9 ++------ wasm/run.js | 8 +++++-- wasm/step0_repl.wam | 2 +- wasm/step1_read_print.wam | 1 - wasm/step2_eval.wam | 1 - wasm/step3_env.wam | 1 - wasm/step4_if_fn_do.wam | 1 - wasm/step5_tco.wam | 1 - 10 files changed, 10 insertions(+), 60 deletions(-) delete mode 100644 wasm/platform_js.wam diff --git a/wasm/Makefile b/wasm/Makefile index c86f2ff167..8f920e5809 100644 --- a/wasm/Makefile +++ b/wasm/Makefile @@ -1,4 +1,4 @@ -MODE ?= $(if $(filter node js,$(wasm_MODE)),js,$(if $(filter wace_fooboot,$(wasm_MODE)),os,libc)) +MODE ?= $(if $(filter node js wace_fooboot,$(wasm_MODE)),os,libc) WASM_AS ?= wasm-as WAMP ?= node_modules/.bin/wamp diff --git a/wasm/platform_js.wam b/wasm/platform_js.wam deleted file mode 100644 index 3993e16330..0000000000 --- a/wasm/platform_js.wam +++ /dev/null @@ -1,44 +0,0 @@ -(module $platform_js - - (import "env" "exit" (func $lib_exit (param i32))) - (import "env" "printline" (func $lib_printline (param i32) (result i32))) - (import "env" "readline" (func $lib_readline (param i32 i32 i32) (result i32))) - (import "env" "read_file" (func $lib_read_file (param i32 i32) (result i32))) - (import "env" "time_ms" (func $lib_time_ms (result i32))) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - (func $fatal (param $code i32 $msg i32) - ($print $msg) - ($lib_exit $code) - ) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - (func $print (param $addr i32) - (drop ($lib_printline $addr))) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - (func $readline (param $prompt i32 $buf i32) (result i32) - ;; TODO: don't hardcode count to 200 - (LET $res ($lib_readline $prompt $buf 200)) - $res - ) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - (func $read_file (param $path i32 $buf i32) (result i32) - (LET $size ($lib_read_file $path $buf)) - ;; Add null to string - (i32.store8 (i32.add $buf $size) 0) - (i32.add $size 1) - ) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - (func $get_time_ms (result i32) - ($lib_time_ms) - ) - -) diff --git a/wasm/platform_os.wam b/wasm/platform_os.wam index c0ae33c147..197c81e200 100644 --- a/wasm/platform_os.wam +++ b/wasm/platform_os.wam @@ -1,14 +1,11 @@ (module $platform_os (import "env" "exit" (func $lib_exit (param i32))) - (import "env" "stdout" (global $lib_stdout i32)) (import "env" "fputs" (func $lib_fputs (param i32 i32) (result i32))) - (import "env" "readline" (func $lib_readline (param i32 i32 i32) (result i32))) - (import "env" "add_history" (func $lib_add_history (param i32))) - (import "env" "read_file" (func $lib_read_file (param i32 i32) (result i32))) + (import "env" "get_time_ms" (func $lib_get_time_ms (result i32))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -27,8 +24,6 @@ (func $readline (param $prompt i32 $buf i32) (result i32) ;; TODO: don't hardcode count to 200 (LET $res ($lib_readline $prompt $buf 200)) - (if $res - ($lib_add_history $buf)) $res ) @@ -44,7 +39,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (func $get_time_ms (result i32) - 0 + ($lib_get_time_ms) ) ) diff --git a/wasm/run.js b/wasm/run.js index da01761ae8..849483128e 100755 --- a/wasm/run.js +++ b/wasm/run.js @@ -94,7 +94,7 @@ async function loadWebAssembly(filename, args) { return put_string(memory, buf, contents) } - function time_ms() { + function get_time_ms() { return (new Date()).getTime() } @@ -111,7 +111,11 @@ async function loadWebAssembly(filename, args) { imports.env.printline = printline imports.env.readline = readline imports.env.read_file = read_file - imports.env.time_ms = time_ms + imports.env.get_time_ms = get_time_ms + + + imports.env.stdout = 0 + imports.env.fputs = printline imports.env.memory = memory imports.env.memoryBase = memoryBase diff --git a/wasm/step0_repl.wam b/wasm/step0_repl.wam index dd7658495b..26b80fdf9e 100644 --- a/wasm/step0_repl.wam +++ b/wasm/step0_repl.wam @@ -24,7 +24,7 @@ (LET $line (STATIC_ARRAY 201)) ;; DEBUG - ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) + ;;($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) ;; Start REPL (block $repl_done diff --git a/wasm/step1_read_print.wam b/wasm/step1_read_print.wam index ad9c272cc6..1de13a9965 100644 --- a/wasm/step1_read_print.wam +++ b/wasm/step1_read_print.wam @@ -39,7 +39,6 @@ $res 0) ;; DEBUG -;; ($printf_1 "argc: 0x%x\n" $argc) ;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) ;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) ;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) diff --git a/wasm/step2_eval.wam b/wasm/step2_eval.wam index ee169e6e51..c843ab59ce 100644 --- a/wasm/step2_eval.wam +++ b/wasm/step2_eval.wam @@ -190,7 +190,6 @@ $res 0 $repl_env 0) ;; DEBUG -;; ($printf_1 "argc: 0x%x\n" $argc) ;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) ;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) ;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) diff --git a/wasm/step3_env.wam b/wasm/step3_env.wam index 5d87b08a90..67e29fbdbb 100644 --- a/wasm/step3_env.wam +++ b/wasm/step3_env.wam @@ -238,7 +238,6 @@ $res 0 $repl_env 0) ;; DEBUG -;; ($printf_1 "argc: 0x%x\n" $argc) ;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) ;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) ;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) diff --git a/wasm/step4_if_fn_do.wam b/wasm/step4_if_fn_do.wam index 902ec36a52..24f3c7f7d5 100644 --- a/wasm/step4_if_fn_do.wam +++ b/wasm/step4_if_fn_do.wam @@ -272,7 +272,6 @@ $res 0 $repl_env 0 $ms 0) ;; DEBUG -;; ($printf_1 "argc: 0x%x\n" $argc) ;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) ;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) ;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) diff --git a/wasm/step5_tco.wam b/wasm/step5_tco.wam index ca0aeb1364..6186604080 100644 --- a/wasm/step5_tco.wam +++ b/wasm/step5_tco.wam @@ -321,7 +321,6 @@ $res 0 $repl_env 0 $ms 0) ;; DEBUG -;; ($printf_1 "argc: 0x%x\n" $argc) ;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) ;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) ;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) From 0a19c2f1c794acac8cbd5b39f8d943f0225bed6e Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 16 Jan 2019 00:13:51 -0600 Subject: [PATCH 0735/2308] wasm: update to 2019 wat syntax, use .wat extension wasm: update to wat syntax as of Jan 2019. Examples: - get_local -> local.get - i32.wrap/i64 -> i32.warp_i64 - etc The distinction between wat and wast has been clarified: - wat: textual format for web assembly modules - wast: superset of wat used in the specification to define tests. --- .gitignore | 2 +- wasm/Makefile | 6 +- wasm/core.wam | 222 +++++++++++------------ wasm/debug.wam | 96 +++++----- wasm/env.wam | 38 ++-- wasm/mem.wam | 160 ++++++++--------- wasm/package.json | 4 +- wasm/platform_libc.wam | 22 +-- wasm/platform_os.wam | 2 +- wasm/printer.wam | 88 +++++----- wasm/printf.wam | 112 ++++++------ wasm/reader.wam | 120 ++++++------- wasm/step0_repl.wam | 2 +- wasm/step1_read_print.wam | 36 ++-- wasm/step2_eval.wam | 122 ++++++------- wasm/step3_env.wam | 144 +++++++-------- wasm/step4_if_fn_do.wam | 186 ++++++++++---------- wasm/step5_tco.wam | 198 ++++++++++----------- wasm/step6_file.wam | 232 ++++++++++++------------ wasm/step7_quote.wam | 270 ++++++++++++++-------------- wasm/step8_macros.wam | 330 +++++++++++++++++----------------- wasm/step9_try.wam | 360 +++++++++++++++++++------------------- wasm/stepA_mal.wam | 360 +++++++++++++++++++------------------- wasm/string.wam | 68 +++---- wasm/types.wam | 138 +++++++-------- 25 files changed, 1659 insertions(+), 1659 deletions(-) diff --git a/.gitignore b/.gitignore index aa4aa493c8..d5c41ff45f 100644 --- a/.gitignore +++ b/.gitignore @@ -144,5 +144,5 @@ elm/elm-stuff elm/*.js !elm/node_readline.js !elm/bootstrap.js -wasm/*.wast +wasm/*.wat wasm/*.wasm diff --git a/wasm/Makefile b/wasm/Makefile index 8f920e5809..0c5e1b1df2 100644 --- a/wasm/Makefile +++ b/wasm/Makefile @@ -18,8 +18,8 @@ node_modules/.bin/wamp: npm install %.wasm: %.wam - $(WAMP) $(filter %.wam,$^) > $*.wast - $(WASM_AS) $*.wast -o $@ + $(WAMP) $(filter %.wam,$^) > $*.wat + $(WASM_AS) $*.wat -o $@ step0_repl.wasm: $(STEP0_DEPS) step1_read_print.wasm step2_eval.wasm: $(STEP1_DEPS) @@ -30,7 +30,7 @@ step7_quote.wasm step8_macros.wasm step9_try.wasm stepA_mal.wasm: $(STEP4_DEPS) .PHONY: clean clean: - rm -f *.wast *.wasm + rm -f *.wat *.wasm .PHONY: stats tests diff --git a/wasm/core.wam b/wasm/core.wam index 59e906782e..5b719fcd8b 100644 --- a/wasm/core.wam +++ b/wasm/core.wam @@ -7,35 +7,35 @@ (func $APPLY (param $f i32) (param $args i32) (result i32) (local $res i32 $env i32 $ftype i32 $a i32) - (set_local $f ($DEREF_META $f)) - (set_local $ftype ($TYPE $f)) - (if (i32.eq $ftype (get_global $FUNCTION_T)) + (local.set $f ($DEREF_META $f)) + (local.set $ftype ($TYPE $f)) + (if (i32.eq $ftype (global.get $FUNCTION_T)) (then ;; Must be kept in sync with EVAL's FUNCTION_T evaluation (if (i32.eq ($VAL0 $f) 0) ;; eval (then - (set_local $res ($EVAL ($MEM_VAL1_ptr $args) - (get_global $repl_env)))) + (local.set $res ($EVAL ($MEM_VAL1_ptr $args) + (global.get $repl_env)))) (else - (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f)))))) - (else (if (OR (i32.eq $ftype (get_global $MALFUNC_T)) - (i32.eq $ftype (get_global $MACRO_T))) + (local.set $res (call_indirect (type $fnT) $args ($VAL0 $f)))))) + (else (if (OR (i32.eq $ftype (global.get $MALFUNC_T)) + (i32.eq $ftype (global.get $MACRO_T))) (then ;; create new environment using env and params stored in function - (set_local $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + (local.set $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) ($MEM_VAL1_ptr $f) $args)) ;; claim the AST before releasing the list containing it - (set_local $a ($MEM_VAL0_ptr $f)) + (local.set $a ($MEM_VAL0_ptr $f)) (drop ($INC_REF $a)) - (set_local $res ($EVAL $a $env)) + (local.set $res ($EVAL $a $env)) ($RELEASE $env) ($RELEASE $a)) (else ($THROW_STR_1 "APPLY of non-function type: %d\n" $ftype) - (set_local $res 0))))) + (local.set $res 0))))) $res ) @@ -49,30 +49,30 @@ ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))))) (func $throw (param $args i32) (result i32) - (set_global $error_type 2) - (set_global $error_val ($INC_REF ($MEM_VAL1_ptr $args))) + (global.set $error_type 2) + (global.set $error_val ($INC_REF ($MEM_VAL1_ptr $args))) 0 ) (func $nil_Q (param $args i32) (result i32) ($TRUE_FALSE (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) - (get_global $NIL_T)))) + (global.get $NIL_T)))) (func $true_Q (param $args i32) (result i32) (LET $ast ($MEM_VAL1_ptr $args)) - ($TRUE_FALSE (AND (i32.eq ($TYPE $ast) (get_global $BOOLEAN_T)) + ($TRUE_FALSE (AND (i32.eq ($TYPE $ast) (global.get $BOOLEAN_T)) (i32.eq ($VAL0 $ast) 1))) ) (func $false_Q (param $args i32) (result i32) (LET $ast ($MEM_VAL1_ptr $args)) - ($TRUE_FALSE (AND (i32.eq ($TYPE $ast) (get_global $BOOLEAN_T)) + ($TRUE_FALSE (AND (i32.eq ($TYPE $ast) (global.get $BOOLEAN_T)) (i32.eq ($VAL0 $ast) 0))) ) (func $number_Q (param $args i32) (result i32) ($TRUE_FALSE (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) - (get_global $INTEGER_T)))) + (global.get $INTEGER_T)))) (func $string_Q (param $args i32) (result i32) (LET $mv ($MEM_VAL1_ptr $args)) - ($TRUE_FALSE (AND (i32.eq ($TYPE $mv) (get_global $STRING_T)) + ($TRUE_FALSE (AND (i32.eq ($TYPE $mv) (global.get $STRING_T)) (i32.ne (i32.load8_u ($to_String $mv)) (CHR "\x7f")))) ) @@ -82,30 +82,30 @@ (if (result i32) (i32.eq (i32.load8_u $str) (CHR "\x7f")) (then ($INC_REF ($MEM_VAL1_ptr $args))) (else - (drop ($sprintf_1 (get_global $printf_buf) "\x7f%s" $str)) - ($STRING (get_global $STRING_T) (get_global $printf_buf)))) + (drop ($sprintf_1 (global.get $printf_buf) "\x7f%s" $str)) + ($STRING (global.get $STRING_T) (global.get $printf_buf)))) ) (func $keyword_Q (param $args i32) (result i32) (LET $ast ($MEM_VAL1_ptr $args)) - ($TRUE_FALSE (AND (i32.eq ($TYPE $ast) (get_global $STRING_T)) + ($TRUE_FALSE (AND (i32.eq ($TYPE $ast) (global.get $STRING_T)) (i32.eq (i32.load8_u ($to_String $ast)) (CHR "\x7f")))) ) (func $fn_Q (param $args i32) (result i32) (LET $type ($TYPE ($MEM_VAL1_ptr $args))) - ($TRUE_FALSE (OR (i32.eq $type (get_global $FUNCTION_T)) - (i32.eq $type (get_global $MALFUNC_T))))) + ($TRUE_FALSE (OR (i32.eq $type (global.get $FUNCTION_T)) + (i32.eq $type (global.get $MALFUNC_T))))) (func $macro_Q (param $args i32) (result i32) ($TRUE_FALSE (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) - (get_global $MACRO_T)))) + (global.get $MACRO_T)))) (func $symbol (param $args i32) (result i32) - ($STRING (get_global $SYMBOL_T) ($to_String ($MEM_VAL1_ptr $args)))) + ($STRING (global.get $SYMBOL_T) ($to_String ($MEM_VAL1_ptr $args)))) (func $symbol_Q (param $args i32) (result i32) ($TRUE_FALSE (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) - (get_global $SYMBOL_T)))) + (global.get $SYMBOL_T)))) (func $core_pr_str (param $args i32) (result i32) ($pr_str_seq $args 1 " ")) @@ -115,21 +115,21 @@ (LET $res ($pr_str_seq $args 1 " ")) ($printf_1 "%s\n" ($to_String $res)) ($RELEASE $res) - ($INC_REF (get_global $NIL)) + ($INC_REF (global.get $NIL)) ) (func $println (param $args i32) (result i32) (LET $res ($pr_str_seq $args 0 " ")) ($printf_1 "%s\n" ($to_String $res)) ($RELEASE $res) - ($INC_REF (get_global $NIL)) + ($INC_REF (global.get $NIL)) ) (func $core_readline (param $args i32) (result i32) (LET $line (STATIC_ARRAY 201) $mv 0) (if (i32.eqz ($readline ($to_String ($MEM_VAL1_ptr $args)) $line)) - (return ($INC_REF (get_global $NIL)))) - (set_local $mv ($STRING (get_global $STRING_T) $line)) + (return ($INC_REF (global.get $NIL)))) + (local.set $mv ($STRING (global.get $STRING_T) $line)) $mv ) @@ -137,14 +137,14 @@ ($read_str ($to_String ($MEM_VAL1_ptr $args)))) (func $slurp (param $args i32) (result i32) - (LET $mv ($STRING_INIT (get_global $STRING_T)) + (LET $mv ($STRING_INIT (global.get $STRING_T)) $size ($read_file ($to_String ($MEM_VAL1_ptr $args)) ($to_String $mv))) (if (i32.eqz $size) (then ($THROW_STR_1 "failed to read file '%s'" ($to_String ($MEM_VAL1_ptr $args))) - (return ($INC_REF (get_global $NIL))))) - (set_local $mv ($STRING_FINALIZE $mv $size)) + (return ($INC_REF (global.get $NIL))))) + (local.set $mv ($STRING_FINALIZE $mv $size)) $mv ) @@ -191,17 +191,17 @@ (func $list_Q (param $args i32) (result i32) ($TRUE_FALSE (i32.eq ($TYPE ($DEREF_META ($MEM_VAL1_ptr $args))) - (get_global $LIST_T)))) + (global.get $LIST_T)))) (func $vector (param $args i32) (result i32) - ($FORCE_SEQ_TYPE (get_global $VECTOR_T) $args)) + ($FORCE_SEQ_TYPE (global.get $VECTOR_T) $args)) (func $vector_Q (param $args i32) (result i32) ($TRUE_FALSE (i32.eq ($TYPE ($DEREF_META ($MEM_VAL1_ptr $args))) - (get_global $VECTOR_T)))) + (global.get $VECTOR_T)))) (func $hash_map (param $args i32) (result i32) - (LET $type (get_global $HASHMAP_T) + (LET $type (global.get $HASHMAP_T) $res ($MAP_LOOP_START $type) $val2 0 $val3 0 @@ -216,20 +216,20 @@ (loop $loop (br_if $done (i32.eqz ($VAL0 $args))) - (set_local $val2 ($INC_REF ($MEM_VAL1_ptr $args))) - (set_local $val3 ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))) + (local.set $val2 ($INC_REF ($MEM_VAL1_ptr $args))) + (local.set $val3 ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))) ;; skip two - (set_local $args ($MEM_VAL0_ptr ($MEM_VAL0_ptr $args))) + (local.set $args ($MEM_VAL0_ptr ($MEM_VAL0_ptr $args))) ;; update the return sequence structure ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) (br $loop) ) @@ -242,21 +242,21 @@ (func $hash_map_Q (param $args i32) (result i32) ($TRUE_FALSE (i32.eq ($TYPE ($DEREF_META ($MEM_VAL1_ptr $args))) - (get_global $HASHMAP_T)))) + (global.get $HASHMAP_T)))) (func $assoc (param $args i32) (result i32) (LET $hm ($MEM_VAL1_ptr $args) $key 0) - (set_local $args ($MEM_VAL0_ptr $args)) + (local.set $args ($MEM_VAL0_ptr $args)) (drop ($INC_REF $hm)) (block $done (loop $loop (br_if $done (OR (i32.eqz ($VAL0 $args)) (i32.eqz ($VAL0 ($MEM_VAL0_ptr $args))))) - (set_local $hm ($ASSOC1 $hm ($MEM_VAL1_ptr $args) + (local.set $hm ($ASSOC1 $hm ($MEM_VAL1_ptr $args) ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))) - (set_local $args ($MEM_VAL0_ptr ($MEM_VAL0_ptr $args))) + (local.set $args ($MEM_VAL0_ptr ($MEM_VAL0_ptr $args))) (br $loop) ) @@ -267,23 +267,23 @@ (func $get (param $args i32) (result i32) (LET $hm ($MEM_VAL1_ptr $args) $key ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) - (if (result i32) (i32.eq $hm (get_global $NIL)) - (then ($INC_REF (get_global $NIL))) - (else ($INC_REF (i32.wrap/i64 ($HASHMAP_GET $hm $key))))) + (if (result i32) (i32.eq $hm (global.get $NIL)) + (then ($INC_REF (global.get $NIL))) + (else ($INC_REF (i32.wrap_i64 ($HASHMAP_GET $hm $key))))) ) (func $contains_Q (param $args i32) (result i32) (LET $hm ($MEM_VAL1_ptr $args) $key ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) ($TRUE_FALSE - (if (result i32) (i32.eq $hm (get_global $NIL)) + (if (result i32) (i32.eq $hm (global.get $NIL)) (then 0) - (else (i32.wrap/i64 + (else (i32.wrap_i64 (i64.shr_u ($HASHMAP_GET $hm $key) (i64.const 32)))))) ) (func $keys_or_vals (param $hm i32 $keys i32) (result i32) - (LET $res ($MAP_LOOP_START (get_global $LIST_T)) + (LET $res ($MAP_LOOP_START (global.get $LIST_T)) $val2 0 ;; MAP_LOOP stack $ret $res @@ -295,23 +295,23 @@ (br_if $done (i32.eqz ($VAL0 $hm))) (if $keys - (then (set_local $val2 ($INC_REF ($MEM_VAL1_ptr $hm)))) - (else (set_local $val2 ($INC_REF ($MEM_VAL2_ptr $hm))))) + (then (local.set $val2 ($INC_REF ($MEM_VAL1_ptr $hm)))) + (else (local.set $val2 ($INC_REF ($MEM_VAL2_ptr $hm))))) ;; next element - (set_local $hm ($MEM_VAL0_ptr $hm)) + (local.set $hm ($MEM_VAL0_ptr $hm)) ;; update the return sequence structure ;; do not release val2 since we are pulling it from the ;; arguments and not creating it here ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE (get_global $LIST_T) + (local.set $res ($MAP_LOOP_UPDATE (global.get $LIST_T) $empty $current $val2 0)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) (br $loop) ) @@ -329,45 +329,45 @@ (func $sequential_Q (param $args i32) (result i32) ($TRUE_FALSE (OR (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) - (get_global $LIST_T)) + (global.get $LIST_T)) (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) - (get_global $VECTOR_T))))) + (global.get $VECTOR_T))))) (func $cons (param $args i32) (result i32) ($LIST ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)) ($MEM_VAL1_ptr $args))) (func $concat (param $args i32) (result i32) (local $last_sl i64) - (LET $res ($INC_REF (get_global $EMPTY_LIST)) + (LET $res ($INC_REF (global.get $EMPTY_LIST)) $current $res $sl 0 $last 0 $arg 0) (block $done (loop $loop - (br_if $done (i32.le_u $args (get_global $EMPTY_HASHMAP))) - (set_local $arg ($MEM_VAL1_ptr $args)) + (br_if $done (i32.le_u $args (global.get $EMPTY_HASHMAP))) + (local.set $arg ($MEM_VAL1_ptr $args)) ;; skip empty elements - (if (i32.le_s $arg (get_global $EMPTY_HASHMAP)) + (if (i32.le_s $arg (global.get $EMPTY_HASHMAP)) (then - (set_local $args ($MEM_VAL0_ptr $args)) + (local.set $args ($MEM_VAL0_ptr $args)) (br $loop))) - (set_local $last_sl ($SLICE $arg 0 -1)) - (set_local $sl (i32.wrap/i64 $last_sl)) - (set_local $last (i32.wrap/i64 (i64.shr_u $last_sl (i64.const 32)))) - (if (i32.eq $res (get_global $EMPTY_LIST)) + (local.set $last_sl ($SLICE $arg 0 -1)) + (local.set $sl (i32.wrap_i64 $last_sl)) + (local.set $last (i32.wrap_i64 (i64.shr_u $last_sl (i64.const 32)))) + (if (i32.eq $res (global.get $EMPTY_LIST)) (then ;; if this is the first element, set the return to the slice - (set_local $res $sl)) + (local.set $res $sl)) (else ;; otherwise attach current to sliced (i32.store ($VAL0_ptr $current) ($IDX $sl)))) ;; update current to end of sliced list - (set_local $current $last) + (local.set $current $last) ;; release empty since no longer part of the slice - ($RELEASE (get_global $EMPTY_LIST)) + ($RELEASE (global.get $EMPTY_LIST)) - (set_local $args ($MEM_VAL0_ptr $args)) + (local.set $args ($MEM_VAL0_ptr $args)) (br $loop) ) ) @@ -382,8 +382,8 @@ (block $done (loop $loop (br_if $done (OR (i32.ge_s $i $idx) (i32.eqz ($VAL0 $a)))) - (set_local $i (i32.add $i 1)) - (set_local $a ($MEM_VAL0_ptr $a)) + (local.set $i (i32.add $i 1)) + (local.set $a ($MEM_VAL0_ptr $a)) (br $loop) ) ) @@ -396,21 +396,21 @@ ) (func $first (param $args i32) (result i32) - (LET $res (get_global $NIL) + (LET $res (global.get $NIL) $a ($MEM_VAL1_ptr $args)) - (if (AND (i32.ne $a (get_global $NIL)) + (if (AND (i32.ne $a (global.get $NIL)) (i32.ne ($VAL0 $a) 0)) - (set_local $res ($MEM_VAL1_ptr $a))) + (local.set $res ($MEM_VAL1_ptr $a))) ($INC_REF $res) ) (func $rest (param $args i32) (result i32) (LET $a ($MEM_VAL1_ptr $args)) - (if (i32.eq $a (get_global $NIL)) - (return ($INC_REF (get_global $EMPTY_LIST)))) + (if (i32.eq $a (global.get $NIL)) + (return ($INC_REF (global.get $EMPTY_LIST)))) (if (i32.ne ($VAL0 $a) 0) - (set_local $a ($MEM_VAL0_ptr $a))) - ($FORCE_SEQ_TYPE (get_global $LIST_T) $a) + (local.set $a ($MEM_VAL0_ptr $a))) + ($FORCE_SEQ_TYPE (global.get $LIST_T) $a) ) ;;; @@ -433,19 +433,19 @@ (if (i32.le_s $rest_count 1) (then ;; no intermediate args - (if (i32.ne ($TYPE ($MEM_VAL1_ptr $rest_args)) (get_global $LIST_T)) + (if (i32.ne ($TYPE ($MEM_VAL1_ptr $rest_args)) (global.get $LIST_T)) (then ;; not a list, so convert it first - (set_local $f_args ($FORCE_SEQ_TYPE (get_global $LIST_T) + (local.set $f_args ($FORCE_SEQ_TYPE (global.get $LIST_T) ($MEM_VAL1_ptr $rest_args)))) (else ;; inc ref since we will release after APPLY - (set_local $f_args ($INC_REF ($MEM_VAL1_ptr $rest_args)))))) + (local.set $f_args ($INC_REF ($MEM_VAL1_ptr $rest_args)))))) (else ;; 1 or more intermediate args - (set_local $last_sl ($SLICE $rest_args 0 (i32.sub $rest_count 1))) - (set_local $f_args (i32.wrap/i64 $last_sl)) - (set_local $last (i32.wrap/i64 (i64.shr_u $last_sl (i64.const 32)))) + (local.set $last_sl ($SLICE $rest_args 0 (i32.sub $rest_count 1))) + (local.set $f_args (i32.wrap_i64 $last_sl)) + (local.set $last (i32.wrap_i64 (i64.shr_u $last_sl (i64.const 32)))) ;; release the terminator of the new list (we skip over it) ;; we already checked for an empty list above, so $last is ;; a real non-empty list @@ -454,7 +454,7 @@ (i32.store ($VAL0_ptr $last) ($IDX ($LAST $rest_args))) )) - (set_local $res ($APPLY $f $f_args)) + (local.set $res ($APPLY $f $f_args)) ;; release new args ($RELEASE $f_args) @@ -465,7 +465,7 @@ (LET $f ($MEM_VAL1_ptr $args) $rest_args ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)) $f_args 0 - $res ($MAP_LOOP_START (get_global $LIST_T)) + $res ($MAP_LOOP_START (global.get $LIST_T)) ;; push MAP_LOOP stack $ret $res $current $res @@ -476,18 +476,18 @@ (br_if $done (i32.eqz ($VAL1 $rest_args))) ;; create argument list for apply - (set_local $f_args ($ALLOC (get_global $LIST_T) - (get_global $EMPTY_LIST) + (local.set $f_args ($ALLOC (global.get $LIST_T) + (global.get $EMPTY_LIST) ($MEM_VAL1_ptr $rest_args) 0)) - (set_local $res ($APPLY $f $f_args)) + (local.set $res ($APPLY $f $f_args)) ($RELEASE $f_args) ;; go to the next element - (set_local $rest_args ($MEM_VAL0_ptr $rest_args)) + (local.set $rest_args ($MEM_VAL0_ptr $rest_args)) - (if (get_global $error_type) + (if (global.get $error_type) (then ;; if error, release the unattached element ($RELEASE $res) @@ -495,13 +495,13 @@ ;; update the return sequence structure ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE (get_global $LIST_T) + (local.set $res ($MAP_LOOP_UPDATE (global.get $LIST_T) $empty $current $res 0)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) (br $loop) ) @@ -517,19 +517,19 @@ (LET $mv ($MEM_VAL1_ptr $args) $meta ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args))) ;; remove existing metadata first - ($ALLOC (get_global $METADATA_T) ($DEREF_META $mv) $meta 0) + ($ALLOC (global.get $METADATA_T) ($DEREF_META $mv) $meta 0) ) (func $meta (param $args i32) (result i32) - (if (result i32) (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) (get_global $METADATA_T)) + (if (result i32) (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) (global.get $METADATA_T)) (then ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL1_ptr $args)))) - (else ($INC_REF (get_global $NIL))))) + (else ($INC_REF (global.get $NIL))))) (func $atom (param $args i32) (result i32) - ($ALLOC_SCALAR (get_global $ATOM_T) ($VAL1 $args))) + ($ALLOC_SCALAR (global.get $ATOM_T) ($VAL1 $args))) (func $atom_Q (param $args i32) (result i32) - ($TRUE_FALSE (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) (get_global $ATOM_T)))) + ($TRUE_FALSE (i32.eq ($TYPE ($MEM_VAL1_ptr $args)) (global.get $ATOM_T)))) (func $deref (param $args i32) (result i32) ($INC_REF ($MEM_VAL0_ptr ($MEM_VAL1_ptr $args)))) @@ -571,14 +571,14 @@ (func $pr_memory_summary (param $args i32) (result i32) ($PR_MEMORY_SUMMARY_SMALL) - ($INC_REF (get_global $NIL)) + ($INC_REF (global.get $NIL)) ) (func $nop (param $args i32) (result i32) - ($INC_REF (get_global $NIL))) + ($INC_REF (global.get $NIL))) (table - anyfunc + funcref (elem $nop ;; placeholder for eval which will use 0 $equal_Q $throw diff --git a/wasm/debug.wam b/wasm/debug.wam index 19e5c10f6f..66ad533a4a 100644 --- a/wasm/debug.wam +++ b/wasm/debug.wam @@ -1,25 +1,25 @@ (module $debug (func $checkpoint_user_memory - (set_global $mem_user_start (get_global $mem_unused_start)) - (set_global $string_mem_user_start (get_global $string_mem_next)) + (global.set $mem_user_start (global.get $mem_unused_start)) + (global.set $string_mem_user_start (global.get $string_mem_next)) ) (func $CHECK_FREE_LIST (result i32) (LET $first (i32.add - (get_global $mem) - (i32.mul (get_global $mem_free_list) 4)) + (global.get $mem) + (i32.mul (global.get $mem_free_list) 4)) $count 0) (block $done (loop $loop (br_if $done (i32.ge_s $first - (i32.add (get_global $mem) - (i32.mul (get_global $mem_unused_start) + (i32.add (global.get $mem) + (i32.mul (global.get $mem_unused_start) 4)))) - (set_local $count (i32.add $count ($MalVal_size $first))) - (set_local $first (i32.add (get_global $mem) (i32.mul 4 ($VAL0 $first)))) + (local.set $count (i32.add $count ($MalVal_size $first))) + (local.set $first (i32.add (global.get $mem) (i32.mul 4 ($VAL0 $first)))) (br $loop) ) ) @@ -27,24 +27,24 @@ ) (func $PR_MEMORY_SUMMARY_SMALL - (LET $free (i32.sub (get_global $MEM_SIZE) - (i32.mul (get_global $mem_unused_start) 4)) + (LET $free (i32.sub (global.get $MEM_SIZE) + (i32.mul (global.get $mem_unused_start) 4)) $free_list_count ($CHECK_FREE_LIST) - $mv (get_global $NIL) + $mv (global.get $NIL) $mem_ref_count 0) (block $done (loop $loop (br_if $done (i32.ge_s $mv (i32.add - (get_global $mem) - (i32.mul (get_global $mem_unused_start) + (global.get $mem) + (i32.mul (global.get $mem_unused_start) 4)))) - (if (i32.ne ($TYPE $mv) (get_global $FREE_T)) - (set_local $mem_ref_count (i32.add $mem_ref_count + (if (i32.ne ($TYPE $mv) (global.get $FREE_T)) + (local.set $mem_ref_count (i32.add $mem_ref_count (i32.shr_u (i32.load $mv) 5)))) - (set_local $mv (i32.add $mv (i32.mul 4 ($MalVal_size $mv)))) + (local.set $mv (i32.add $mv (i32.mul 4 ($MalVal_size $mv)))) (br $loop) ) ) @@ -52,24 +52,24 @@ ($printf_3 "Free: %d, Values: %d (refs: %d), Emptys: " $free (i32.sub - (i32.sub (get_global $mem_unused_start) 1) + (i32.sub (global.get $mem_unused_start) 1) $free_list_count) $mem_ref_count) - (set_local $mv (get_global $NIL)) + (local.set $mv (global.get $NIL)) (block $done (loop $loop - (br_if $done (i32.gt_s $mv (get_global $TRUE))) + (br_if $done (i32.gt_s $mv (global.get $TRUE))) ($printf_1 "%d," (i32.div_s (i32.load $mv) 32)) - (set_local $mv (i32.add $mv 8)) + (local.set $mv (i32.add $mv 8)) (br $loop) ) ) - (set_local $mv (get_global $EMPTY_LIST)) + (local.set $mv (global.get $EMPTY_LIST)) (block $done (loop $loop - (br_if $done (i32.gt_s $mv (get_global $EMPTY_HASHMAP))) + (br_if $done (i32.gt_s $mv (global.get $EMPTY_HASHMAP))) ($printf_1 "%d," (i32.div_s (i32.load $mv) 32)) - (set_local $mv (i32.add $mv 12)) + (local.set $mv (i32.add $mv 12)) (br $loop) ) ) @@ -95,8 +95,8 @@ (then ($printf_1 ", size %2d" $size)) (else ($printf_1 ", refs %2d" ($REFS $mv)))) - (if (OR (i32.eq $type (get_global $STRING_T)) - (i32.eq $type (get_global $SYMBOL_T))) + (if (OR (i32.eq $type (global.get $STRING_T)) + (i32.eq $type (global.get $SYMBOL_T))) ;; for strings/symbolx pointers, print hex values (then ($printf_2 " [%4d|%3ds" ($MalVal_refcnt_type $idx) $val0)) (else ($printf_2 " [%4d|%4d" ($MalVal_refcnt_type $idx) $val0))) @@ -141,7 +141,7 @@ ($print ($to_String $mv)) (br $done)) ;; 6: list - (if (i32.le_u $mv (get_global $EMPTY_HASHMAP)) + (if (i32.le_u $mv (global.get $EMPTY_HASHMAP)) (then ($print "()")) (else @@ -151,7 +151,7 @@ ($MalVal_val $idx 0)))) (br $done)) ;; 7: vector - (if (i32.le_u $mv (get_global $EMPTY_HASHMAP)) + (if (i32.le_u $mv (global.get $EMPTY_HASHMAP)) (then ($print "[]")) (else @@ -161,7 +161,7 @@ ($MalVal_val $idx 0)))) (br $done)) ;; 8: hashmap - (if (i32.le_u $mv (get_global $EMPTY_HASHMAP)) + (if (i32.le_u $mv (global.get $EMPTY_HASHMAP)) (then ($print "{}")) (else @@ -192,9 +192,9 @@ (br $done)) ;; 15: FREE ($printf_1 "FREE next: 0x%x" $val0) - (if (i32.eq $idx (get_global $mem_free_list)) + (if (i32.eq $idx (global.get $mem_free_list)) ($print " (free start)")) - (if (i32.eq $val0 (get_global $mem_unused_start)) + (if (i32.eq $val0 (global.get $mem_unused_start)) ($print " (free end)")) (br $done)) ;; 16: unknown @@ -210,18 +210,18 @@ (LET $ms 0 $idx 0) ($printf_2 "String - showing %d -> %d:\n" - $start (i32.sub (get_global $string_mem_next) - (get_global $string_mem))) - (if (i32.le_s (i32.sub (get_global $string_mem_next) - (get_global $string_mem)) + $start (i32.sub (global.get $string_mem_next) + (global.get $string_mem))) + (if (i32.le_s (i32.sub (global.get $string_mem_next) + (global.get $string_mem)) $start) (then ($print " ---\n")) (else - (set_local $ms (get_global $string_mem)) + (local.set $ms (global.get $string_mem)) (block $done (loop $loop - (br_if $done (i32.ge_u $ms (get_global $string_mem_next))) - (set_local $idx (i32.sub $ms (get_global $string_mem))) + (br_if $done (i32.ge_u $ms (global.get $string_mem_next))) + (local.set $idx (i32.sub $ms (global.get $string_mem))) (if (i32.ge_s $idx $start) ($printf_4 "%4d: refs %2d, size %2d >> '%s'\n" $idx @@ -229,7 +229,7 @@ (i32.load16_u (i32.add $ms 2)) (i32.add $ms 4))) - (set_local $ms (i32.add $ms (i32.load16_u (i32.add $ms 2)))) + (local.set $ms (i32.add $ms (i32.load16_u (i32.add $ms 2)))) (br $loop) ) ))) @@ -240,31 +240,31 @@ $idx 0) (if (i32.lt_s $start 0) (then - (set_local $start (get_global $mem_user_start)) - (set_local $string_start (i32.sub (get_global $string_mem_user_start) - (get_global $string_mem))))) + (local.set $start (global.get $mem_user_start)) + (local.set $string_start (i32.sub (global.get $string_mem_user_start) + (global.get $string_mem))))) (if (i32.lt_s $end 0) - (set_local $end (get_global $mem_unused_start))) + (local.set $end (global.get $mem_unused_start))) ;;; printf("Values - (mem) showing %d -> %d", start, end) ;;; printf(" (unused start: %d, free list: %d):\n", ;;; mem_unused_start, mem_free_list) ($printf_4 "Values - (mem) showing 0x%x -> 0x%x (unused start: 0x%x, free list: 0x%x):\n" $start $end - (get_global $mem_unused_start) - (get_global $mem_free_list)) + (global.get $mem_unused_start) + (global.get $mem_free_list)) (if (i32.le_s $end $start) (then ($print " ---\n") - (set_local $end (get_global $mem_unused_start))) + (local.set $end (global.get $mem_unused_start))) (else - (set_local $idx $start) + (local.set $idx $start) ;;; while (idx < end) (block $loopvals_exit (loop $loopvals (br_if $loopvals_exit (i32.ge_s $idx $end)) - (set_local $idx ($PR_MEMORY_VALUE $idx)) + (local.set $idx ($PR_MEMORY_VALUE $idx)) (br $loopvals) ) ))) @@ -277,7 +277,7 @@ (loop $loop (br_if $loop_exit (i32.ge_u $start $end)) ($printf_2 "0x%x 0x%x\n" $start (i32.load $start)) - (set_local $start (i32.add 4 $start)) + (local.set $start (i32.add 4 $start)) (br $loop) ) ) diff --git a/wasm/env.wam b/wasm/env.wam index 3132b4a9bb..cd10c76918 100644 --- a/wasm/env.wam +++ b/wasm/env.wam @@ -2,7 +2,7 @@ (func $ENV_NEW (param $outer i32) (result i32) (LET $data ($HASHMAP) ;; allocate the data hashmap - $env ($ALLOC (get_global $ENVIRONMENT_T) $data $outer 0)) + $env ($ALLOC (global.get $ENVIRONMENT_T) $data $outer 0)) ;; environment takes ownership ($RELEASE $data) $env @@ -18,15 +18,15 @@ (br_if $done (i32.eqz ($VAL0 $binds))) ;; get/deref the key from binds - (set_local $key ($MEM_VAL1_ptr $binds)) + (local.set $key ($MEM_VAL1_ptr $binds)) (if (i32.eqz ($strcmp "&" ($to_String $key))) (then ;; ENV_NEW_BIND_VARGS ;; get/deref the key from the next element of binds - (set_local $binds ($MEM_VAL0_ptr $binds)) - (set_local $key ($MEM_VAL1_ptr $binds)) + (local.set $binds ($MEM_VAL0_ptr $binds)) + (local.set $key ($MEM_VAL1_ptr $binds)) ;; the value is the remaining list in exprs - (set_local $exprs ($FORCE_SEQ_TYPE (get_global $LIST_T) $exprs)) + (local.set $exprs ($FORCE_SEQ_TYPE (global.get $LIST_T) $exprs)) ;; set the binding in the environment data (drop ($ENV_SET $env $key $exprs)) ;; list is owned by the environment @@ -37,8 +37,8 @@ ;; set the binding in the environment data (drop ($ENV_SET $env $key ($MEM_VAL1_ptr $exprs))) ;; go to next element of binds and exprs - (set_local $binds ($MEM_VAL0_ptr $binds)) - (set_local $exprs ($MEM_VAL0_ptr $exprs)))) + (local.set $binds ($MEM_VAL0_ptr $binds)) + (local.set $exprs ($MEM_VAL0_ptr $exprs)))) (br $loop) ) @@ -65,17 +65,17 @@ (block $done (loop $loop - (set_local $data ($MEM_VAL0_ptr $env)) - (set_local $found_res ($HASHMAP_GET $data $key)) + (local.set $data ($MEM_VAL0_ptr $env)) + (local.set $found_res ($HASHMAP_GET $data $key)) ;;; if (found) - (if (i32.wrap/i64 (i64.shr_u $found_res (i64.const 32))) + (if (i32.wrap_i64 (i64.shr_u $found_res (i64.const 32))) (then - (set_local $res (i32.wrap/i64 $found_res)) + (local.set $res (i32.wrap_i64 $found_res)) (br $done))) - (set_local $env ($MEM_VAL1_ptr $env)) - (if (i32.eq $env (get_global $NIL)) + (local.set $env ($MEM_VAL1_ptr $env)) + (if (i32.eq $env (global.get $NIL)) (then - (set_local $env 0) + (local.set $env 0) (br $done))) (br $loop) ) @@ -83,17 +83,17 @@ ;; combine res/env as hi 32/low 32 of i64 (i64.or - (i64.shl (i64.extend_u/i32 $res) (i64.const 32)) - (i64.extend_u/i32 $env)) + (i64.shl (i64.extend_i32_u $res) (i64.const 32)) + (i64.extend_i32_u $env)) ) (func $ENV_GET (param $env i32 $key i32) (result i32) (local $res_env i64) (LET $res 0) - (set_local $res_env ($ENV_FIND $env $key)) - (set_local $env (i32.wrap/i64 $res_env)) - (set_local $res (i32.wrap/i64 (i64.shr_u $res_env (i64.const 32)))) + (local.set $res_env ($ENV_FIND $env $key)) + (local.set $env (i32.wrap_i64 $res_env)) + (local.set $res (i32.wrap_i64 (i64.shr_u $res_env (i64.const 32)))) (if (i32.eqz $env) (then diff --git a/wasm/mem.wam b/wasm/mem.wam index 7a6e634c1f..0eda0175ad 100644 --- a/wasm/mem.wam +++ b/wasm/mem.wam @@ -29,20 +29,20 @@ (func $MEM_VAL0_ptr (param $mv i32) (result i32) - (i32.add (get_global $mem) + (i32.add (global.get $mem) (i32.mul (i32.load (i32.add $mv 4)) 4))) (func $MEM_VAL1_ptr (param $mv i32) (result i32) - (i32.add (get_global $mem) + (i32.add (global.get $mem) (i32.mul (i32.load (i32.add $mv 8)) 4))) (func $MEM_VAL2_ptr (param $mv i32) (result i32) - (i32.add (get_global $mem) + (i32.add (global.get $mem) (i32.mul (i32.load (i32.add $mv 12)) 4))) ;; Returns the memory index mem of mv ;; Will usually be used with a load or store by the caller (func $IDX (param $mv i32) (result i32) ;; MalVal memory 64 bit (2 * i32) aligned - (i32.div_u (i32.sub $mv (get_global $mem)) 4)) + (i32.div_u (i32.sub $mv (global.get $mem)) 4)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -50,7 +50,7 @@ (func $MalVal_ptr (param $mv_idx i32) (result i32) ;; MalVal memory 64 bit (2 * i32) aligned ;;; mem[mv_idx].refcnt_type - (i32.add (get_global $mem) (i32.mul $mv_idx 4))) + (i32.add (global.get $mem) (i32.mul $mv_idx 4))) ;; Returns the address of 'mem[mv_idx].refcnt_type' (func $MalVal_refcnt_type (param $mv_idx i32) (result i32) @@ -100,7 +100,7 @@ (func $MalVal_size (param $mv i32) (result i32) (LET $type ($TYPE $mv)) ;; if (type == FREE_T) - (if (result i32) (i32.eq $type (get_global $FREE_T)) + (if (result i32) (i32.eq $type (global.get $FREE_T)) (then ;;; return (mv->refcnt_type & 0xffe0)>>5 (i32.shr_u (i32.and (i32.load $mv) 0xffe0) 5)) ;;; / 32 @@ -119,46 +119,46 @@ ($init_printf_mem) ;; error_str string buffer - (set_global $error_str (STATIC_ARRAY 100)) + (global.set $error_str (STATIC_ARRAY 100)) ;; reader token string buffer - (set_global $token_buf (STATIC_ARRAY 256)) + (global.set $token_buf (STATIC_ARRAY 256)) ;; printer string buffer - (set_global $printer_buf (STATIC_ARRAY 4096)) + (global.set $printer_buf (STATIC_ARRAY 4096)) - (set_local $heap_size (i32.add (get_global $MEM_SIZE) - (get_global $STRING_MEM_SIZE))) - (set_global $heap_start (i32.add (get_global $memoryBase) - (get_global $S_STRING_END))) - (set_global $heap_end (i32.add (get_global $heap_start) + (local.set $heap_size (i32.add (global.get $MEM_SIZE) + (global.get $STRING_MEM_SIZE))) + (global.set $heap_start (i32.add (global.get $memoryBase) + (global.get $S_STRING_END))) + (global.set $heap_end (i32.add (global.get $heap_start) $heap_size)) - (set_global $mem (get_global $heap_start)) - (set_global $mem_unused_start 0) - (set_global $mem_free_list 0) + (global.set $mem (global.get $heap_start)) + (global.set $mem_unused_start 0) + (global.set $mem_free_list 0) - (set_global $string_mem (i32.add (get_global $heap_start) - (get_global $MEM_SIZE))) - (set_global $string_mem_next (get_global $string_mem)) + (global.set $string_mem (i32.add (global.get $heap_start) + (global.get $MEM_SIZE))) + (global.set $string_mem_next (global.get $string_mem)) - (set_global $mem_user_start (get_global $mem_unused_start)) - (set_global $string_mem_user_start (get_global $string_mem_next)) + (global.set $mem_user_start (global.get $mem_unused_start)) + (global.set $string_mem_user_start (global.get $string_mem_next)) ;; Empty values - (set_global $NIL - ($ALLOC_SCALAR (get_global $NIL_T) 0)) - (set_global $FALSE - ($ALLOC_SCALAR (get_global $BOOLEAN_T) 0)) - (set_global $TRUE - ($ALLOC_SCALAR (get_global $BOOLEAN_T) 1)) - (set_global $EMPTY_LIST - ($ALLOC (get_global $LIST_T) - (get_global $NIL) (get_global $NIL) (get_global $NIL))) - (set_global $EMPTY_VECTOR - ($ALLOC (get_global $VECTOR_T) - (get_global $NIL) (get_global $NIL) (get_global $NIL))) - (set_global $EMPTY_HASHMAP - ($ALLOC (get_global $HASHMAP_T) - (get_global $NIL) (get_global $NIL) (get_global $NIL))) + (global.set $NIL + ($ALLOC_SCALAR (global.get $NIL_T) 0)) + (global.set $FALSE + ($ALLOC_SCALAR (global.get $BOOLEAN_T) 0)) + (global.set $TRUE + ($ALLOC_SCALAR (global.get $BOOLEAN_T) 1)) + (global.set $EMPTY_LIST + ($ALLOC (global.get $LIST_T) + (global.get $NIL) (global.get $NIL) (global.get $NIL))) + (global.set $EMPTY_VECTOR + ($ALLOC (global.get $VECTOR_T) + (global.get $NIL) (global.get $NIL) (global.get $NIL))) + (global.set $EMPTY_HASHMAP + ($ALLOC (global.get $HASHMAP_T) + (global.get $NIL) (global.get $NIL) (global.get $NIL))) ;; ($print "<<< init_memory\n") @@ -169,32 +169,32 @@ (func $ALLOC_INTERNAL (param $type i32 $val1 i32 $val2 i32 $val3 i32) (result i32) - (LET $prev (get_global $mem_free_list) - $res (get_global $mem_free_list) + (LET $prev (global.get $mem_free_list) + $res (global.get $mem_free_list) $size ($MalType_size $type)) (block $loop_done (loop $loop ;; res == mem_unused_start - (if (i32.eq $res (get_global $mem_unused_start)) + (if (i32.eq $res (global.get $mem_unused_start)) (then ;; ALLOC_UNUSED ;;; if (res + size > MEM_SIZE) - (if (i32.gt_u (i32.add $res $size) (get_global $MEM_SIZE)) + (if (i32.gt_u (i32.add $res $size) (global.get $MEM_SIZE)) ;; Out of memory, exit ($fatal 7 "Out of mal memory!\n")) ;;; if (mem_unused_start += size) - (set_global $mem_unused_start - (i32.add (get_global $mem_unused_start) $size)) + (global.set $mem_unused_start + (i32.add (global.get $mem_unused_start) $size)) ;;; if (prev == res) (if (i32.eq $prev $res) (then - (set_global $mem_free_list (get_global $mem_unused_start))) + (global.set $mem_free_list (global.get $mem_unused_start))) (else ;;; mem[prev].val[0] = mem_unused_start (i32.store ($MalVal_val_ptr $prev 0) - (get_global $mem_unused_start)))) + (global.get $mem_unused_start)))) (br $loop_done))) ;; if (MalVal_size(mem+res) == size) (if (i32.eq ($MalVal_size ($MalVal_ptr $res)) @@ -202,20 +202,20 @@ (then ;; ALLOC_MIDDLE ;;; if (res == mem_free_list) - (if (i32.eq $res (get_global $mem_free_list)) + (if (i32.eq $res (global.get $mem_free_list)) ;; set free pointer (mem_free_list) to next free ;;; mem_free_list = mem[res].val[0]; - (set_global $mem_free_list ($MalVal_val $res 0))) + (global.set $mem_free_list ($MalVal_val $res 0))) ;; if (res != mem_free_list) - (if (i32.ne $res (get_global $mem_free_list)) + (if (i32.ne $res (global.get $mem_free_list)) ;; set previous free to next free ;;; mem[prev].val[0] = mem[res].val[0] (i32.store ($MalVal_val_ptr $prev 0) ($MalVal_val $res 0))) (br $loop_done))) ;;; prev = res - (set_local $prev $res) + (local.set $prev $res) ;;; res = mem[res].val[0] - (set_local $res ($MalVal_val $res 0)) + (local.set $res ($MalVal_val $res 0)) (br $loop) ) ) @@ -271,11 +271,11 @@ ;;; if (mv == NULL) { return; } (if (i32.eqz $mv) (return)) ;;; idx = mv - mem - (set_local $idx ($IDX $mv)) + (local.set $idx ($IDX $mv)) ;;; type = mv->refcnt_type & 31 - (set_local $type (i32.and (i32.load $mv) 0x1f)) ;; 0x1f == 31 + (local.set $type (i32.and (i32.load $mv) 0x1f)) ;; 0x1f == 31 ;;; size = MalType_size(type) - (set_local $size ($MalType_size $type)) + (local.set $size ($MalType_size $type)) ;; DEBUG ;;; printf(">>> RELEASE idx: %d, type: %d, size: %d\n", idx, type, size) @@ -283,7 +283,7 @@ (if (i32.eq 0 $mv) ($fatal 7 "RELEASE of NULL!\n")) - (if (i32.eq (get_global $FREE_T) $type) + (if (i32.eq (global.get $FREE_T) $type) (then ($printf_2 "RELEASE of already free mv: 0x%x, idx: 0x%x\n" $mv $idx) ($fatal 1 ""))) @@ -297,7 +297,7 @@ (i32.sub ($MalVal_refcnt_type $idx) 32)) ;; nil, false, true, empty sequences - (if (i32.le_u $mv (get_global $EMPTY_HASHMAP)) + (if (i32.le_u $mv (global.get $EMPTY_HASHMAP)) (then (if (i32.lt_u ($MalVal_refcnt_type $idx) 32) (then @@ -316,7 +316,7 @@ (br $done)) ;; string, kw, symbol ;; release string, then FREE reference - ($RELEASE_STRING (i32.add (get_global $string_mem) ($VAL0 $mv))) + ($RELEASE_STRING (i32.add (global.get $string_mem) ($VAL0 $mv))) (br $done)) ;; list, vector (if (i32.ne ($MalVal_val $idx 0) 0) @@ -362,23 +362,23 @@ ;; set type(FREE/15) and size ;;; mv->refcnt_type = size*32 + FREE_T - (i32.store $mv (i32.add (i32.mul $size 32) (get_global $FREE_T))) - (i32.store ($MalVal_val_ptr $idx 0) (get_global $mem_free_list)) - (set_global $mem_free_list $idx) + (i32.store $mv (i32.add (i32.mul $size 32) (global.get $FREE_T))) + (i32.store ($MalVal_val_ptr $idx 0) (global.get $mem_free_list)) + (global.set $mem_free_list $idx) (if (i32.ge_u $size 3) (i32.store ($MalVal_val_ptr $idx 1) 0)) (if (i32.eq $size 4) (i32.store ($MalVal_val_ptr $idx 2) 0)) ) ;; find string in string memory or 0 if not found (func $FIND_STRING (param $str i32) (result i32) - (LET $ms (get_global $string_mem)) + (LET $ms (global.get $string_mem)) (block $done (loop $loop - (br_if $done (i32.ge_s $ms (get_global $string_mem_next))) + (br_if $done (i32.ge_s $ms (global.get $string_mem_next))) (if (i32.eqz ($strcmp $str (i32.add $ms 4))) (return $ms)) - (set_local $ms (i32.add $ms (i32.load16_u (i32.add $ms 2)))) + (local.set $ms (i32.add $ms (i32.load16_u (i32.add $ms 2)))) (br $loop) ) ) @@ -394,7 +394,7 @@ ;; search for matching string in string_mem (if $intern (then - (set_local $ms ($FIND_STRING $str)) + (local.set $ms ($FIND_STRING $str)) (if $ms (then ;;; ms->refcnt += 1 @@ -402,13 +402,13 @@ (return $ms))))) ;; no existing matching string so create a new one - (set_local $ms (get_global $string_mem_next)) + (local.set $ms (global.get $string_mem_next)) (i32.store16 $ms 1) ;;; ms->size = sizeof(MalString)+size+1 (i32.store16 offset=2 $ms (i32.add (i32.add 4 $size) 1)) ($memmove (i32.add $ms 4) $str (i32.add $size 1)) ;;; string_mem_next = (void *)ms + ms->size - (set_global $string_mem_next + (global.set $string_mem_next ;;(i32.add $ms (i32.load16_u (i32.add $ms 2)))) (i32.add $ms (i32.load16_u offset=2 $ms))) @@ -422,44 +422,44 @@ (if (i32.le_s (i32.load16_u $ms) 0) (then ($printf_2 "Release of already free string: %d (0x%x)\n" - (i32.sub $ms (get_global $string_mem)) $ms) + (i32.sub $ms (global.get $string_mem)) $ms) ($fatal 1 ""))) ;;; size = ms->size - (set_local $size (i32.load16_u (i32.add $ms 2))) + (local.set $size (i32.load16_u (i32.add $ms 2))) ;;; *next = (void *)ms + size - (set_local $next (i32.add $ms $size)) + (local.set $next (i32.add $ms $size)) ;;; ms->refcnt -= 1 (i32.store16 $ms (i32.sub (i32.load16_u $ms) 1)) (if (i32.eqz (i32.load16_u $ms)) (then - (if (i32.gt_s (get_global $string_mem_next) $next) + (if (i32.gt_s (global.get $string_mem_next) $next) (then ;; If no more references to this string then free it up by ;; shifting up every string afterwards to fill the gap ;; (splice). - ($memmove $ms $next (i32.sub (get_global $string_mem_next) + ($memmove $ms $next (i32.sub (global.get $string_mem_next) $next)) ;; Scan the mem values for string types after the freed ;; string and shift their indexes by size - (set_local $ms_idx (i32.sub $ms (get_global $string_mem))) - (set_local $idx ($IDX (get_global $EMPTY_HASHMAP))) + (local.set $ms_idx (i32.sub $ms (global.get $string_mem))) + (local.set $idx ($IDX (global.get $EMPTY_HASHMAP))) (loop $loop - (set_local $mv ($MalVal_ptr $idx)) - (set_local $type ($TYPE $mv)) + (local.set $mv ($MalVal_ptr $idx)) + (local.set $type ($TYPE $mv)) (if (AND (i32.gt_s ($VAL0 $mv) $ms_idx) - (OR (i32.eq $type (get_global $STRING_T)) - (i32.eq $type (get_global $SYMBOL_T)))) + (OR (i32.eq $type (global.get $STRING_T)) + (i32.eq $type (global.get $SYMBOL_T)))) (i32.store ($VAL0_ptr $mv) (i32.sub ($VAL0 $mv) $size))) - (set_local $idx (i32.add $idx ($MalVal_size $mv))) + (local.set $idx (i32.add $idx ($MalVal_size $mv))) - (br_if $loop (i32.lt_s $idx (get_global $mem_unused_start))) + (br_if $loop (i32.lt_s $idx (global.get $mem_unused_start))) ))) - (set_global $string_mem_next - (i32.sub (get_global $string_mem_next) $size)))) + (global.set $string_mem_next + (i32.sub (global.get $string_mem_next) $size)))) ) ) diff --git a/wasm/package.json b/wasm/package.json index 6eecf267fc..3f19a299fc 100644 --- a/wasm/package.json +++ b/wasm/package.json @@ -1,9 +1,9 @@ { "name": "mal", "version": "0.0.1", - "description": "Make a Lisp (mal) language implemented in Javascript", + "description": "Make a Lisp (mal) language implemented in WebAssembly", "dependencies": { - "@kanaka/wamp": "1.0.4", + "@kanaka/wamp": "1.0.6", "ffi-napi": "^2.4.4", "text-encoding": "0.6.4" } diff --git a/wasm/platform_libc.wam b/wasm/platform_libc.wam index 1c769af00a..351f1f4c05 100644 --- a/wasm/platform_libc.wam +++ b/wasm/platform_libc.wam @@ -32,7 +32,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (func $print (param $addr i32) - (drop ($lib_fputs $addr (get_global $lib_stdout)))) + (drop ($lib_fputs $addr (global.get $lib_stdout)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -43,7 +43,7 @@ (if $line (then ($lib_add_history $line) - (set_local $len ($strlen $line)) + (local.set $len ($strlen $line)) ($memmove $buf $line $len) ($lib_free $line))) (i32.store8 (i32.add $buf $len) (CHR "\x00")) @@ -55,7 +55,7 @@ ;; Returns malloc'd string. Must be free by caller (func $read_file (param $path i32 $buf i32) (result i32) (LET $fst (STATIC_ARRAY 100) ;; at least STAT_SIZE - $fd ($lib_open $path (get_global $O_RDONLY) 0) + $fd ($lib_open $path (global.get $O_RDONLY) 0) $st_size 0 $sz 0) @@ -63,13 +63,13 @@ (then ($printf_1 "ERROR: slurp failed to open '%s'\n" $path) (return 0))) - (if (i32.lt_s ($lib___fxstat (get_global $STAT_VER_LINUX) $fd $fst) 0) + (if (i32.lt_s ($lib___fxstat (global.get $STAT_VER_LINUX) $fd $fst) 0) (then ($printf_1 "ERROR: slurp failed to stat '%s'\n" $path) (return 0))) - (set_local $st_size (i32.load - (i32.add $fst (get_global $STAT_ST_SIZE_OFFSET)))) - (set_local $sz ($lib_read $fd $buf $st_size)) + (local.set $st_size (i32.load + (i32.add $fst (global.get $STAT_ST_SIZE_OFFSET)))) + (local.set $sz ($lib_read $fd $buf $st_size)) (if (i32.ne $sz $st_size) (then ($printf_1 "ERROR: slurp failed to stat '%s'\n" $path) @@ -88,12 +88,12 @@ $usecs 0 $msecs 0) (drop ($lib_gettimeofday $tv 0)) - (set_local $secs (i32.load (i32.add $tv (get_global $TV_SEC_OFFSET)))) + (local.set $secs (i32.load (i32.add $tv (global.get $TV_SEC_OFFSET)))) ;; subtract 30 years to make sure secs is positive and can be ;; multiplied by 1000 - (set_local $secs (i32.sub $secs 0x38640900)) - (set_local $usecs (i32.load (i32.add $tv (get_global $TV_USEC_OFFSET)))) - (set_local $msecs (i32.add (i32.mul $secs 1000) + (local.set $secs (i32.sub $secs 0x38640900)) + (local.set $usecs (i32.load (i32.add $tv (global.get $TV_USEC_OFFSET)))) + (local.set $msecs (i32.add (i32.mul $secs 1000) (i32.div_u $usecs 1000))) $msecs ) diff --git a/wasm/platform_os.wam b/wasm/platform_os.wam index 197c81e200..cb76c7ff93 100644 --- a/wasm/platform_os.wam +++ b/wasm/platform_os.wam @@ -17,7 +17,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (func $print (param $addr i32) - (drop ($lib_fputs $addr (get_global $lib_stdout)))) + (drop ($lib_fputs $addr (global.get $lib_stdout)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/wasm/printer.wam b/wasm/printer.wam index 0bd08236c7..65708e13b9 100644 --- a/wasm/printer.wam +++ b/wasm/printer.wam @@ -15,55 +15,55 @@ (br_table 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $type)) ;; 0: nil ($memmove $res "nil" 4) - (set_local $res (i32.add 3 $res)) + (local.set $res (i32.add 3 $res)) (br $done)) ;; 1: boolean (if (i32.eq $val0 0) (then ;; false ($memmove $res "false" 6) - (set_local $res (i32.add 5 $res))) + (local.set $res (i32.add 5 $res))) (else ;; true ($memmove $res "true" 5) - (set_local $res (i32.add 4 $res)))) + (local.set $res (i32.add 4 $res)))) (br $done)) ;; 2: integer - (set_local $res ($sprintf_1 $res "%d" $val0)) + (local.set $res ($sprintf_1 $res "%d" $val0)) (br $done)) ;; 3: float/ERROR - (set_local $res ($sprintf_1 $res "%d" " *** GOT FLOAT *** ")) + (local.set $res ($sprintf_1 $res "%d" " *** GOT FLOAT *** ")) (br $done)) ;; 4: string/kw - (set_local $sval ($to_String $mv)) + (local.set $sval ($to_String $mv)) (if (i32.eq (i32.load8_u $sval) (CHR "\x7f")) (then - (set_local $res ($sprintf_1 $res ":%s" (i32.add $sval 1)))) + (local.set $res ($sprintf_1 $res ":%s" (i32.add $sval 1)))) (else (if $print_readably (then ;; escape backslashes, quotes, and newlines - (set_local $res ($sprintf_1 $res "\"" 0)) - (set_local $res (i32.add $res ($REPLACE3 $res ($to_String $mv) + (local.set $res ($sprintf_1 $res "\"" 0)) + (local.set $res (i32.add $res ($REPLACE3 $res ($to_String $mv) "\\" "\\\\" "\"" "\\\"" "\n" "\\n"))) - (set_local $res ($sprintf_1 $res "\"" 0))) + (local.set $res ($sprintf_1 $res "\"" 0))) (else - (set_local $res ($sprintf_1 $res "%s" $sval)))))) + (local.set $res ($sprintf_1 $res "%s" $sval)))))) (br $done)) ;; 5: symbol - (set_local $res ($sprintf_1 $res "%s" ($to_String $mv))) + (local.set $res ($sprintf_1 $res "%s" ($to_String $mv))) (br $done)) ;; 6: list, fallthrouogh ) ;; 7: vector, fallthrough ) ;; 8: hashmap - (set_local + (local.set $res ($sprintf_1 $res "%c" - (if (result i32) (i32.eq $type (get_global $LIST_T)) + (if (result i32) (i32.eq $type (global.get $LIST_T)) (then (CHR "(")) - (else (if (result i32) (i32.eq $type (get_global $VECTOR_T)) + (else (if (result i32) (i32.eq $type (global.get $VECTOR_T)) (then (CHR "[")) (else (CHR "{"))))))) ;; PR_SEQ_LOOP @@ -72,73 +72,73 @@ (loop $seq_loop (br_if $done_seq (i32.eq ($VAL0 $mv) 0)) ;;; res = pr_str_val(res, MEM_VAL1(mv), print_readably) - (set_local $res ($pr_str_val $res ($MEM_VAL1_ptr $mv) $print_readably)) + (local.set $res ($pr_str_val $res ($MEM_VAL1_ptr $mv) $print_readably)) ;; if this is a hash-map, print the next element - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then ;;; res += snprintf(res, 2, " ") - (set_local $res ($sprintf_1 $res " " 0)) - (set_local $res ($pr_str_val $res ($MEM_VAL2_ptr $mv) + (local.set $res ($sprintf_1 $res " " 0)) + (local.set $res ($pr_str_val $res ($MEM_VAL2_ptr $mv) $print_readably)))) ;;; mv = MEM_VAL0(mv) - (set_local $mv ($MEM_VAL0_ptr $mv)) + (local.set $mv ($MEM_VAL0_ptr $mv)) ;;; if (VAL0(mv) != 0) (if (i32.ne ($VAL0 $mv) 0) ;;; res += snprintf(res, 2, " ") - (set_local $res ($sprintf_1 $res " " 0))) + (local.set $res ($sprintf_1 $res " " 0))) (br $seq_loop) ) ) - (set_local + (local.set $res ($sprintf_1 $res "%c" - (if (result i32) (i32.eq $type (get_global $LIST_T)) + (if (result i32) (i32.eq $type (global.get $LIST_T)) (then (CHR ")")) - (else (if (result i32) (i32.eq $type (get_global $VECTOR_T)) + (else (if (result i32) (i32.eq $type (global.get $VECTOR_T)) (then (CHR "]")) (else (CHR "}"))))))) (br $done)) ;; 9: function ($memmove $res "#" 10) - (set_local $res (i32.add 9 $res)) + (local.set $res (i32.add 9 $res)) (br $done)) ;; 10: mal function ($memmove $res "(fn* " 6) - (set_local $res (i32.add 5 $res)) - (set_local $res ($pr_str_val $res ($MEM_VAL1_ptr $mv) $print_readably)) + (local.set $res (i32.add 5 $res)) + (local.set $res ($pr_str_val $res ($MEM_VAL1_ptr $mv) $print_readably)) ($memmove $res " " 2) - (set_local $res (i32.add 1 $res)) - (set_local $res ($pr_str_val $res ($MEM_VAL0_ptr $mv) $print_readably)) + (local.set $res (i32.add 1 $res)) + (local.set $res ($pr_str_val $res ($MEM_VAL0_ptr $mv) $print_readably)) ($memmove $res ")" 2) - (set_local $res (i32.add 1 $res)) + (local.set $res (i32.add 1 $res)) (br $done)) ;; 11: macro fn ($memmove $res "#" 13) - (set_local $res (i32.add 12 $res)) + (local.set $res (i32.add 12 $res)) (br $done)) ;; 12: atom ($memmove $res "(atom " 7) - (set_local $res (i32.add 6 $res)) - (set_local $res ($pr_str_val $res ($MEM_VAL0_ptr $mv) $print_readably)) + (local.set $res (i32.add 6 $res)) + (local.set $res ($pr_str_val $res ($MEM_VAL0_ptr $mv) $print_readably)) ($memmove $res ")" 2) - (set_local $res (i32.add 1 $res)) + (local.set $res (i32.add 1 $res)) (br $done)) ;; 13: environment ($memmove $res "#" 11) - (set_local $res (i32.add 10 $res)) + (local.set $res (i32.add 10 $res)) (br $done)) ;; 14: metadata ;; recur on object itself - (set_local $res ($pr_str_val $res ($MEM_VAL0_ptr $mv) $print_readably)) + (local.set $res ($pr_str_val $res ($MEM_VAL0_ptr $mv) $print_readably)) (br $done)) ;; 15: FREE ($memmove $res "#" 12) - (set_local $res (i32.add 11 $res)) + (local.set $res (i32.add 11 $res)) (br $done)) ;; 16: default ($memmove $res "#" 11) - (set_local $res (i32.add 10 $res)) + (local.set $res (i32.add 10 $res)) ) $res @@ -146,7 +146,7 @@ (func $pr_str_internal (param $seq i32) (param $mv i32) (param $print_readably i32) (param $sep i32) (result i32) - (LET $res ($STRING_INIT (get_global $STRING_T)) + (LET $res ($STRING_INIT (global.get $STRING_T)) $res_str ($to_String $res)) (if $seq @@ -154,17 +154,17 @@ (block $done (loop $loop (br_if $done (i32.eqz ($VAL0 $mv))) - (set_local $res_str ($pr_str_val $res_str ($MEM_VAL1_ptr $mv) $print_readably)) - (set_local $mv ($MEM_VAL0_ptr $mv)) + (local.set $res_str ($pr_str_val $res_str ($MEM_VAL1_ptr $mv) $print_readably)) + (local.set $mv ($MEM_VAL0_ptr $mv)) (if (i32.ne ($VAL0 $mv) 0) - (set_local $res_str ($sprintf_1 $res_str "%s" $sep))) + (local.set $res_str ($sprintf_1 $res_str "%s" $sep))) (br $loop) ) )) (else - (set_local $res_str ($pr_str_val $res_str $mv $print_readably)))) + (local.set $res_str ($pr_str_val $res_str $mv $print_readably)))) - (set_local $res ($STRING_FINALIZE $res (i32.sub $res_str ($to_String $res)))) + (local.set $res ($STRING_FINALIZE $res (i32.sub $res_str ($to_String $res)))) $res ) diff --git a/wasm/printf.wam b/wasm/printf.wam index b06a23864e..7c5d730d0d 100644 --- a/wasm/printf.wam +++ b/wasm/printf.wam @@ -4,46 +4,46 @@ (func $init_printf_mem ;; sprintf static buffer - (set_global $printf_buf (STATIC_ARRAY 256)) + (global.set $printf_buf (STATIC_ARRAY 256)) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (func $printf_1 (param $fmt i32) (param $v0 i32) - (drop ($sprintf_6 (get_global $printf_buf) $fmt $v0 0 0 0 0 0)) - ($print (get_global $printf_buf)) + (drop ($sprintf_6 (global.get $printf_buf) $fmt $v0 0 0 0 0 0)) + ($print (global.get $printf_buf)) ) (func $printf_2 (param $fmt i32 $v0 i32 $v1 i32) - (drop ($sprintf_6 (get_global $printf_buf) $fmt $v0 $v1 0 0 0 0)) - ($print (get_global $printf_buf)) + (drop ($sprintf_6 (global.get $printf_buf) $fmt $v0 $v1 0 0 0 0)) + ($print (global.get $printf_buf)) ) (func $printf_3 (param $fmt i32) (param $v0 i32) (param $v1 i32) (param $v2 i32) - (drop ($sprintf_6 (get_global $printf_buf) $fmt $v0 $v1 $v2 0 0 0)) - ($print (get_global $printf_buf)) + (drop ($sprintf_6 (global.get $printf_buf) $fmt $v0 $v1 $v2 0 0 0)) + ($print (global.get $printf_buf)) ) (func $printf_4 (param $fmt i32) (param $v0 i32) (param $v1 i32) (param $v2 i32) (param $v3 i32) - (drop ($sprintf_6 (get_global $printf_buf) $fmt $v0 $v1 $v2 $v3 0 0)) - ($print (get_global $printf_buf)) + (drop ($sprintf_6 (global.get $printf_buf) $fmt $v0 $v1 $v2 $v3 0 0)) + ($print (global.get $printf_buf)) ) (func $printf_5 (param $fmt i32) (param $v0 i32) (param $v1 i32) (param $v2 i32) (param $v3 i32) (param $v4 i32) - (drop ($sprintf_6 (get_global $printf_buf) $fmt $v0 $v1 $v2 $v3 $v4 0)) - ($print (get_global $printf_buf)) + (drop ($sprintf_6 (global.get $printf_buf) $fmt $v0 $v1 $v2 $v3 $v4 0)) + ($print (global.get $printf_buf)) ) (func $printf_6 (param $fmt i32) (param $v0 i32) (param $v1 i32) (param $v2 i32) (param $v3 i32) (param $v4 i32) (param $v5 i32) - (drop ($sprintf_6 (get_global $printf_buf) $fmt $v0 $v1 $v2 $v3 $v4 $v5)) - ($print (get_global $printf_buf)) + (drop ($sprintf_6 (global.get $printf_buf) $fmt $v0 $v1 $v2 $v3 $v4 $v5)) + ($print (global.get $printf_buf)) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -62,27 +62,27 @@ (if (AND (i32.lt_s $val 0) (i32.eq $radix 10)) (then - (set_local $neg 1) - (set_local $val (i32.sub 0 $val)))) + (local.set $neg 1) + (local.set $val (i32.sub 0 $val)))) ;; Calculate smallest to most significant digit (loop $loop - (set_local $digit (i32.rem_u $val $radix)) + (local.set $digit (i32.rem_u $val $radix)) (i32.store8 $pbuf (if (result i32) (i32.lt_u $digit 10) (i32.add (CHR "0") $digit) (i32.sub (i32.add (CHR "A") $digit) 10))) - (set_local $pbuf (i32.add $pbuf 1)) - (set_local $val (i32.div_u $val $radix)) + (local.set $pbuf (i32.add $pbuf 1)) + (local.set $val (i32.div_u $val $radix)) (br_if $loop (i32.gt_u $val 0)) ) - (set_local $i (i32.sub $pbuf $buf)) + (local.set $i (i32.sub $pbuf $buf)) (block $done (loop $loop (br_if $done (i32.ge_u $i $pad_cnt)) (i32.store8 $pbuf $pad_char) - (set_local $pbuf (i32.add $pbuf 1)) - (set_local $i (i32.add $i 1)) + (local.set $pbuf (i32.add $pbuf 1)) + (local.set $i (i32.add $i 1)) (br $loop) ) ) @@ -90,22 +90,22 @@ (if $neg (then (i32.store8 $pbuf (CHR "-")) - (set_local $pbuf (i32.add $pbuf 1)))) + (local.set $pbuf (i32.add $pbuf 1)))) (i32.store8 $pbuf (CHR "\x00")) ;; now reverse it - (set_local $len (i32.sub $pbuf $buf)) - (set_local $i 0) + (local.set $len (i32.sub $pbuf $buf)) + (local.set $i 0) (block $done (loop $loop (br_if $done (i32.ge_u $i (i32.div_u $len 2))) - (set_local $j (i32.load8_u (i32.add $buf $i))) - (set_local $k (i32.add $buf (i32.sub (i32.sub $len $i) 1))) + (local.set $j (i32.load8_u (i32.add $buf $i))) + (local.set $k (i32.add $buf (i32.sub (i32.sub $len $i) 1))) (i32.store8 (i32.add $buf $i) (i32.load8_u $k)) (i32.store8 $k $j) - (set_local $i (i32.add $i 1)) + (local.set $i (i32.add $i 1)) (br $loop) ) ) @@ -132,17 +132,17 @@ ;; set $v to the current parameter (block (block (block (block (block (block (br_table 0 1 2 3 4 5 0 $vidx)) - (; 0 ;) (set_local $v $v0) (br $after_v)) - (; 1 ;) (set_local $v $v1) (br $after_v)) - (; 2 ;) (set_local $v $v2) (br $after_v)) - (; 3 ;) (set_local $v $v3) (br $after_v)) - (; 4 ;) (set_local $v $v4) (br $after_v)) - (; 5 ;) (set_local $v $v5) (br $after_v) + (; 0 ;) (local.set $v $v0) (br $after_v)) + (; 1 ;) (local.set $v $v1) (br $after_v)) + (; 2 ;) (local.set $v $v2) (br $after_v)) + (; 3 ;) (local.set $v $v3) (br $after_v)) + (; 4 ;) (local.set $v $v4) (br $after_v)) + (; 5 ;) (local.set $v $v5) (br $after_v) ) ;;; while ((ch=*(fmt++))) - (set_local $ch (i32.load8_u $fmt)) - (set_local $fmt (i32.add 1 $fmt)) + (local.set $ch (i32.load8_u $fmt)) + (local.set $fmt (i32.add 1 $fmt)) (br_if $done (i32.eqz $ch)) ;; TODO: check buffer length @@ -150,71 +150,71 @@ (then ;; TODO: check buffer length (i32.store8 $pstr $ch) - (set_local $pstr (i32.add 1 $pstr)) + (local.set $pstr (i32.add 1 $pstr)) (br $loop))) ;;; ch=*(fmt++) - (set_local $ch (i32.load8_u $fmt)) - (set_local $fmt (i32.add 1 $fmt)) + (local.set $ch (i32.load8_u $fmt)) + (local.set $fmt (i32.add 1 $fmt)) (br_if $done (i32.eqz $ch)) - (set_local $pad_cnt 0) - (set_local $pad_char (CHR " ")) + (local.set $pad_cnt 0) + (local.set $pad_char (CHR " ")) (if (AND (i32.ge_s $ch (CHR "0")) (i32.le_s $ch (CHR "9"))) (then ;; padding requested (if (i32.eq $ch (CHR "0")) (then ;; zero padding requested - (set_local $pad_char (CHR "0")) + (local.set $pad_char (CHR "0")) ;;; ch=*(fmt++) - (set_local $ch (i32.load8_u $fmt)) - (set_local $fmt (i32.add 1 $fmt)) + (local.set $ch (i32.load8_u $fmt)) + (local.set $fmt (i32.add 1 $fmt)) (br_if $done (i32.eqz $ch)))) (loop $loop - (set_local $pad_cnt (i32.mul $pad_cnt 10)) - (set_local $pad_cnt (i32.add $pad_cnt + (local.set $pad_cnt (i32.mul $pad_cnt 10)) + (local.set $pad_cnt (i32.add $pad_cnt (i32.sub $ch (CHR "0")))) - (set_local $ch (i32.load8_u $fmt)) - (set_local $fmt (i32.add 1 $fmt)) + (local.set $ch (i32.load8_u $fmt)) + (local.set $fmt (i32.add 1 $fmt)) (br_if $loop (AND (i32.ge_s $ch (CHR "0")) (i32.le_s $ch (CHR "9")))) ))) (if (i32.eq (CHR "d") $ch) (then - (set_local $pstr ($_sprintnum $pstr $v 10 $pad_cnt $pad_char))) + (local.set $pstr ($_sprintnum $pstr $v 10 $pad_cnt $pad_char))) (else (if (i32.eq (CHR "x") $ch) (then - (set_local $pstr ($_sprintnum $pstr $v 16 $pad_cnt $pad_char))) + (local.set $pstr ($_sprintnum $pstr $v 16 $pad_cnt $pad_char))) (else (if (i32.eq (CHR "s") $ch) (then - (set_local $len ($strlen $v)) + (local.set $len ($strlen $v)) (block $done (loop $loop (br_if $done (i32.le_s $pad_cnt $len)) (i32.store8 $pstr (CHR " ")) - (set_local $pstr (i32.add $pstr 1)) - (set_local $pad_cnt (i32.sub $pad_cnt 1)) + (local.set $pstr (i32.add $pstr 1)) + (local.set $pad_cnt (i32.sub $pad_cnt 1)) (br $loop) ) ) ($memmove $pstr $v $len) - (set_local $pstr (i32.add $pstr $len))) + (local.set $pstr (i32.add $pstr $len))) (else (if (i32.eq (CHR "c") $ch) (then (i32.store8 $pstr $v) - (set_local $pstr (i32.add $pstr 1))) + (local.set $pstr (i32.add $pstr 1))) (else (if (i32.eq (CHR "%") $ch) (then (i32.store8 $pstr (CHR "%")) - (set_local $pstr (i32.add $pstr 1)) + (local.set $pstr (i32.add $pstr 1)) (br $loop)) ;; don't increase vidx (else ($printf_1 "Illegal format character: '%c'\n" $ch) ($fatal 3 ""))))))))))) - (set_local $vidx (i32.add 1 $vidx)) + (local.set $vidx (i32.add 1 $vidx)) (br $loop) ) ) diff --git a/wasm/reader.wam b/wasm/reader.wam index e87c01eb5f..0a46736535 100644 --- a/wasm/reader.wam +++ b/wasm/reader.wam @@ -6,17 +6,17 @@ (func $skip_spaces (param $str i32) (result i32) (LET $found 0 - $c (i32.load8_u (i32.add $str (get_global $read_index)))) + $c (i32.load8_u (i32.add $str (global.get $read_index)))) (block $done (loop $loop ;;; while (c == ' ' || c == ',' || c == '\n') (br_if $done (AND (i32.ne $c (CHR " ")) (i32.ne $c (CHR ",")) (i32.ne $c (CHR "\n")))) - (set_local $found 1) + (local.set $found 1) ;;; c=str[++(*index)] - (set_global $read_index (i32.add (get_global $read_index) 1)) - (set_local $c (i32.load8_u (i32.add $str (get_global $read_index)))) + (global.set $read_index (i32.add (global.get $read_index) 1)) + (local.set $c (i32.load8_u (i32.add $str (global.get $read_index)))) (br $loop) ) ) @@ -26,16 +26,16 @@ (func $skip_to_eol (param $str i32) (result i32) (LET $found 0 - $c (i32.load8_u (i32.add $str (get_global $read_index)))) + $c (i32.load8_u (i32.add $str (global.get $read_index)))) (if (i32.eq $c (CHR ";")) (then - (set_local $found 1) + (local.set $found 1) (block $done (loop $loop ;;; c=str[++(*index)] - (set_global $read_index (i32.add (get_global $read_index) 1)) - (set_local $c (i32.load8_u (i32.add $str - (get_global $read_index)))) + (global.set $read_index (i32.add (global.get $read_index) 1)) + (local.set $c (i32.load8_u (i32.add $str + (global.get $read_index)))) ;;; while (c != '\0' && c != '\n') (br_if $loop (AND (i32.ne $c (CHR "\x00")) (i32.ne $c (CHR "\n")))) @@ -64,12 +64,12 @@ ;; read first character ;;; c=str[++(*index)] - (set_local $c (i32.load8_u (i32.add $str (get_global $read_index)))) - (set_global $read_index (i32.add (get_global $read_index) 1)) + (local.set $c (i32.load8_u (i32.add $str (global.get $read_index)))) + (global.set $read_index (i32.add (global.get $read_index) 1)) ;; read first character ;;; token[token_index++] = c - (i32.store8 (i32.add (get_global $token_buf) $token_index) $c) - (set_local $token_index (i32.add $token_index 1)) + (i32.store8 (i32.add (global.get $token_buf) $token_index) $c) + (local.set $token_index (i32.add $token_index 1)) ;; single/double character token (if (OR (i32.eq $c (CHR "(")) (i32.eq $c (CHR ")")) @@ -81,7 +81,7 @@ (i32.eq $c (CHR "`")) (i32.eq $c (CHR "@")) (AND (i32.eq $c (CHR "~")) - (i32.ne (i32.load8_u (i32.add $str (get_global $read_index))) + (i32.ne (i32.load8_u (i32.add $str (global.get $read_index))) (CHR "@")))) (then @@ -89,13 +89,13 @@ (nop)) (else ;;; if (c == '"') instring = true - (set_local $instring (i32.eq $c (CHR "\""))) + (local.set $instring (i32.eq $c (CHR "\""))) (block $done (loop $loop ;; peek at next character ;;; c = str[*index] - (set_local $c (i32.load8_u - (i32.add $str (get_global $read_index)))) + (local.set $c (i32.load8_u + (i32.add $str (global.get $read_index)))) ;;; if (c == '\0') break (br_if $done (i32.eq $c 0)) ;;; if (!instring) @@ -113,34 +113,34 @@ (i32.eq $c (CHR "\n")))))) ;; read next character ;;; token[token_index++] = str[(*index)++] - (i32.store8 (i32.add (get_global $token_buf) $token_index) + (i32.store8 (i32.add (global.get $token_buf) $token_index) (i32.load8_u - (i32.add $str (get_global $read_index)))) - (set_local $token_index (i32.add $token_index 1)) - (set_global $read_index (i32.add (get_global $read_index) 1)) + (i32.add $str (global.get $read_index)))) + (local.set $token_index (i32.add $token_index 1)) + (global.set $read_index (i32.add (global.get $read_index) 1)) ;;; if (token[0] == '~' && token[1] == '@') break (br_if $done (AND (i32.eq (i32.load8_u - (i32.add (get_global $token_buf) 0)) + (i32.add (global.get $token_buf) 0)) (CHR "~")) (i32.eq (i32.load8_u - (i32.add (get_global $token_buf) 1)) + (i32.add (global.get $token_buf) 1)) (CHR "@")))) ;;; if ((!instring) || escaped) (if (OR (i32.eqz $instring) $escaped) (then - (set_local $escaped 0) + (local.set $escaped 0) (br $loop))) (if (i32.eq $c (CHR "\\")) - (set_local $escaped 1)) + (local.set $escaped 1)) (br_if $done (i32.eq $c (CHR "\""))) (br $loop) ) ))) ;;; token[token_index] = '\0' - (i32.store8 (i32.add (get_global $token_buf) $token_index) 0) - (get_global $token_buf) + (i32.store8 (i32.add (global.get $token_buf) $token_index) 0) + (global.get $token_buf) ) (func $read_seq (param $str i32 $type i32 $end i32) (result i32) @@ -160,7 +160,7 @@ ;; peek at next character ;;; c = str[*index] - (set_local $c (i32.load8_u (i32.add $str (get_global $read_index)))) + (local.set $c (i32.load8_u (i32.add $str (global.get $read_index)))) (if (i32.eq $c (CHR "\x00")) (then ($THROW_STR_0 "unexpected EOF") @@ -169,31 +169,31 @@ (then ;; read next character ;;; c = str[(*index)++] - (set_local $c (i32.load8_u (i32.add $str (get_global $read_index)))) - (set_global $read_index (i32.add (get_global $read_index) 1)) + (local.set $c (i32.load8_u (i32.add $str (global.get $read_index)))) + (global.set $read_index (i32.add (global.get $read_index) 1)) (br $done))) ;; value (or key for hash-maps) - (set_local $val2 ($read_form $str)) + (local.set $val2 ($read_form $str)) ;; if error, release the unattached element - (if (get_global $error_type) + (if (global.get $error_type) (then ($RELEASE $val2) (br $done))) ;; if this is a hash-map, READ_FORM again - (if (i32.eq $type (get_global $HASHMAP_T)) - (set_local $val3 ($read_form $str))) + (if (i32.eq $type (global.get $HASHMAP_T)) + (local.set $val3 ($read_form $str))) ;; update the return sequence structure ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) (br $loop) ) @@ -204,17 +204,17 @@ ) (func $read_macro (param $str i32 $sym i32 $with_meta i32) (result i32) - (LET $first ($STRING (get_global $SYMBOL_T) $sym) + (LET $first ($STRING (global.get $SYMBOL_T) $sym) $second ($read_form $str) $third 0 $res $second) - (if (get_global $error_type) (return $res)) + (if (global.get $error_type) (return $res)) (if (i32.eqz $with_meta) (then - (set_local $res ($LIST2 $first $second))) + (local.set $res ($LIST2 $first $second))) (else - (set_local $third ($read_form $str)) - (set_local $res ($LIST3 $first $third $second)) + (local.set $third ($read_form $str)) + (local.set $res ($LIST3 $first $third $second)) ;; release values, list has ownership ($RELEASE $third))) ;; release values, list has ownership @@ -226,17 +226,17 @@ (func $read_form (param $str i32) (result i32) (LET $tok 0 $c0 0 $c1 0 $res 0 $slen 0) - (if (get_global $error_type) (return 0)) + (if (global.get $error_type) (return 0)) - (set_local $tok ($read_token $str)) + (local.set $tok ($read_token $str)) ;;($printf_1 ">>> read_form 1: %s\n" $tok) ;;; c0 = token[0] - (set_local $c0 (i32.load8_u $tok)) - (set_local $c1 (i32.load8_u (i32.add $tok 1))) + (local.set $c0 (i32.load8_u $tok)) + (local.set $c1 (i32.load8_u (i32.add $tok 1))) (if (i32.eq $c0 0) (then - (return ($INC_REF (get_global $NIL)))) + (return ($INC_REF (global.get $NIL)))) (else (if (OR (AND (i32.ge_u $c0 (CHR "0")) (i32.le_u $c0 (CHR "9"))) (AND (i32.eq $c0 (CHR "-")) @@ -247,10 +247,10 @@ (else (if (i32.eq $c0 (CHR ":")) (then (i32.store8 $tok (CHR "\x7f")) - (return ($STRING (get_global $STRING_T) $tok))) + (return ($STRING (global.get $STRING_T) $tok))) (else (if (i32.eq $c0 (CHR "\"")) (then - (set_local $slen ($strlen (i32.add $tok 1))) + (local.set $slen ($strlen (i32.add $tok 1))) (if (i32.ne (i32.load8_u (i32.add $tok $slen)) (CHR "\"")) (then ($THROW_STR_0 "expected '\"'") @@ -259,18 +259,18 @@ ;; unescape backslashes, quotes, and newlines ;; remove the trailing quote (i32.store8 (i32.add $tok $slen) (CHR "\x00")) - (set_local $tok (i32.add $tok 1)) + (local.set $tok (i32.add $tok 1)) (drop ($REPLACE3 0 $tok "\\\"" "\"" "\\n" "\n" "\\\\" "\\")) - (return ($STRING (get_global $STRING_T) $tok))))) + (return ($STRING (global.get $STRING_T) $tok))))) (else (if (i32.eqz ($strcmp "nil" $tok)) - (then (return ($INC_REF (get_global $NIL)))) + (then (return ($INC_REF (global.get $NIL)))) (else (if (i32.eqz ($strcmp "false" $tok)) - (then (return ($INC_REF (get_global $FALSE)))) + (then (return ($INC_REF (global.get $FALSE)))) (else (if (i32.eqz ($strcmp "true" $tok)) - (then (return ($INC_REF (get_global $TRUE)))) + (then (return ($INC_REF (global.get $TRUE)))) (else (if (i32.eqz ($strcmp "'" $tok)) (then (return ($read_macro $str "quote" 0))) (else (if (i32.eqz ($strcmp "`" $tok)) @@ -284,11 +284,11 @@ (else (if (i32.eqz ($strcmp "@" $tok)) (then (return ($read_macro $str "deref" 0))) (else (if (i32.eq $c0 (CHR "(")) - (then (return ($read_seq $str (get_global $LIST_T) (CHR ")")))) + (then (return ($read_seq $str (global.get $LIST_T) (CHR ")")))) (else (if (i32.eq $c0 (CHR "[")) - (then (return ($read_seq $str (get_global $VECTOR_T) (CHR "]")))) + (then (return ($read_seq $str (global.get $VECTOR_T) (CHR "]")))) (else (if (i32.eq $c0 (CHR "{")) - (then (return ($read_seq $str (get_global $HASHMAP_T) (CHR "}")))) + (then (return ($read_seq $str (global.get $HASHMAP_T) (CHR "}")))) (else (if (OR (i32.eq $c0 (CHR ")")) (i32.eq $c0 (CHR "]")) (i32.eq $c0 (CHR "}"))) @@ -296,13 +296,13 @@ ($THROW_STR_1 "unexpected '%c'" $c0) (return 0)) (else - (return ($STRING (get_global $SYMBOL_T) $tok)))) + (return ($STRING (global.get $SYMBOL_T) $tok)))) )))))))))))))))))))))))))))))))) 0 ;; not reachable ) (func $read_str (param $str i32) (result i32) - (set_global $read_index 0) + (global.set $read_index 0) ($read_form $str) ) diff --git a/wasm/step0_repl.wam b/wasm/step0_repl.wam index 26b80fdf9e..354d46ce55 100644 --- a/wasm/step0_repl.wam +++ b/wasm/step0_repl.wam @@ -24,7 +24,7 @@ (LET $line (STATIC_ARRAY 201)) ;; DEBUG - ;;($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) + ;;($printf_1 "memoryBase: 0x%x\n" (global.get $memoryBase)) ;; Start REPL (block $repl_done diff --git a/wasm/step1_read_print.wam b/wasm/step1_read_print.wam index 1de13a9965..a41d3331cd 100644 --- a/wasm/step1_read_print.wam +++ b/wasm/step1_read_print.wam @@ -19,14 +19,14 @@ (func $REP (param $line i32 $env i32) (result i32) (LET $mv1 0 $mv2 0 $ms 0) (block $done - (set_local $mv1 ($READ $line)) - (br_if $done (get_global $error_type)) + (local.set $mv1 ($READ $line)) + (br_if $done (global.get $error_type)) - (set_local $mv2 ($EVAL $mv1 $env)) - (br_if $done (get_global $error_type)) + (local.set $mv2 ($EVAL $mv1 $env)) + (br_if $done (global.get $error_type)) ;; ($PR_MEMORY -1 -1) - (set_local $ms ($PRINT $mv2)) + (local.set $ms ($PRINT $mv2)) ) ;; release memory from MAL_READ @@ -39,18 +39,18 @@ $res 0) ;; DEBUG -;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) -;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) -;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) -;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) -;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) +;; ($printf_1 "memoryBase: 0x%x\n" (global.get $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (global.get $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (global.get $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (global.get $mem)) +;; ($printf_1 "string_mem: %d\n" (global.get $string_mem)) ;; ($PR_MEMORY_RAW -;; (get_global $mem) (i32.add (get_global $mem) -;; (i32.mul (get_global $mem_unused_start) 4))) +;; (global.get $mem) (i32.add (global.get $mem) +;; (i32.mul (global.get $mem_unused_start) 4))) - (drop ($STRING (get_global $STRING_T) "uvw")) - (drop ($STRING (get_global $STRING_T) "xyz")) + (drop ($STRING (global.get $STRING_T) "uvw")) + (drop ($STRING (global.get $STRING_T) "xyz")) ;;($PR_MEMORY -1 -1) @@ -59,11 +59,11 @@ (loop $repl_loop (br_if $repl_done (i32.eqz ($readline "user> " $line))) (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) - (set_local $res ($REP $line 0)) - (if (get_global $error_type) + (local.set $res ($REP $line 0)) + (if (global.get $error_type) (then - ($printf_1 "Error: %s\n" (get_global $error_str)) - (set_global $error_type 0)) + ($printf_1 "Error: %s\n" (global.get $error_str)) + (global.set $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) diff --git a/wasm/step2_eval.wam b/wasm/step2_eval.wam index c843ab59ce..0ce9712b5c 100644 --- a/wasm/step2_eval.wam +++ b/wasm/step2_eval.wam @@ -13,8 +13,8 @@ (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 $ret 0 $empty 0 $current 0) - (if (get_global $error_type) (return 0)) - (set_local $type ($TYPE $ast)) + (if (global.get $error_type) (return 0)) + (local.set $type ($TYPE $ast)) ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) @@ -24,71 +24,71 @@ (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) ;; symbol ;; found/res returned as hi 32/lo 32 of i64 - (set_local $res2 ($HASHMAP_GET $env $ast)) - (set_local $res (i32.wrap/i64 $res2)) - (set_local $found (i32.wrap/i64 (i64.shr_u $res2 + (local.set $res2 ($HASHMAP_GET $env $ast)) + (local.set $res (i32.wrap_i64 $res2)) + (local.set $found (i32.wrap_i64 (i64.shr_u $res2 (i64.const 32)))) (if (i32.eqz $found) ($THROW_STR_1 "'%s' not found" ($to_String $ast))) - (set_local $res ($INC_REF $res)) + (local.set $res ($INC_REF $res)) (br $done)) ;; list, vector, hashmap ;; MAP_LOOP_START - (set_local $res ($MAP_LOOP_START $type)) + (local.set $res ($MAP_LOOP_START $type)) ;; push MAP_LOOP stack ;;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (local.set $ret $res) + (local.set $current $res) + (local.set $empty $res) (block $done (loop $loop ;; check if we are done evaluating the source sequence (br_if $done (i32.eq ($VAL0 $ast) 0)) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (local.set $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) (else - (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) - (set_local $val2 $res) + (local.set $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (local.set $val2 $res) ;; if error, release the unattached element - (if (get_global $error_type) + (if (global.get $error_type) (then ($RELEASE $res) - (set_local $res 0) + (local.set $res 0) (br $done))) ;; for hash-maps, copy the key (inc ref since we are going ;; to release it below) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $val3 $val2) - (set_local $val2 ($MEM_VAL1_ptr $ast)) + (local.set $val3 $val2) + (local.set $val2 ($MEM_VAL1_ptr $ast)) (drop ($INC_REF $val2)))) ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) - (set_local $ast ($MEM_VAL0_ptr $ast)) + (local.set $ast ($MEM_VAL0_ptr $ast)) (br $loop) ) ) ;; MAP_LOOP_DONE - (set_local $res $ret) + (local.set $res $ret) ;; EVAL_AST_RETURN: nothing to do (br $done)) ;; default - (set_local $res ($INC_REF $ast)) + (local.set $res ($INC_REF $ast)) ) $res @@ -96,7 +96,7 @@ (type $fnT (func (param i32) (result i32))) - (table anyfunc + (table funcref (elem $add $subtract $multiply $divide)) @@ -104,15 +104,15 @@ (LET $res 0 $ftype 0 $f_args 0 $f 0 $args 0) - (set_local $f_args 0) - (set_local $f 0) - (set_local $args 0) + (local.set $f_args 0) + (local.set $f 0) + (local.set $args 0) - (if (get_global $error_type) (return 0)) + (if (global.get $error_type) (return 0)) ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (if (i32.ne ($TYPE $ast) (global.get $LIST_T)) (return ($EVAL_AST $ast $env))) ;; APPLY_LIST @@ -120,23 +120,23 @@ (return ($INC_REF $ast))) ;; EVAL_INVOKE - (set_local $res ($EVAL_AST $ast $env)) - (set_local $f_args $res) + (local.set $res ($EVAL_AST $ast $env)) + (local.set $f_args $res) ;; if error, return f/args for release by caller - (if (get_global $error_type) + (if (global.get $error_type) (return $f_args)) - (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest - (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value + (local.set $args ($MEM_VAL0_ptr $f_args)) ;; rest + (local.set $f ($MEM_VAL1_ptr $f_args)) ;; value - (set_local $ftype ($TYPE $f)) - (if (i32.eq $ftype (get_global $FUNCTION_T)) + (local.set $ftype ($TYPE $f)) + (if (i32.eq $ftype (global.get $FUNCTION_T)) (then - (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f)))) + (local.set $res (call_indirect (type $fnT) $args ($VAL0 $f)))) (else ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) - (set_local $res 0))) + (local.set $res 0))) ($RELEASE $f_args) @@ -152,14 +152,14 @@ (func $REP (param $line i32 $env i32) (result i32) (LET $mv1 0 $mv2 0 $ms 0) (block $done - (set_local $mv1 ($READ $line)) - (br_if $done (get_global $error_type)) + (local.set $mv1 ($READ $line)) + (br_if $done (global.get $error_type)) - (set_local $mv2 ($EVAL $mv1 $env)) - (br_if $done (get_global $error_type)) + (local.set $mv2 ($EVAL $mv1 $env)) + (br_if $done (global.get $error_type)) ;; ($PR_MEMORY -1 -1) - (set_local $ms ($PRINT $mv2)) + (local.set $ms ($PRINT $mv2)) ) ;; release memory from MAL_READ and EVAL @@ -190,19 +190,19 @@ $res 0 $repl_env 0) ;; DEBUG -;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) -;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) -;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) -;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) -;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) +;; ($printf_1 "memoryBase: 0x%x\n" (global.get $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (global.get $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (global.get $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (global.get $mem)) +;; ($printf_1 "string_mem: %d\n" (global.get $string_mem)) - (set_global $repl_env ($HASHMAP)) - (set_local $repl_env (get_global $repl_env)) + (global.set $repl_env ($HASHMAP)) + (local.set $repl_env (global.get $repl_env)) - (set_local $repl_env ($ASSOC1_S $repl_env "+" ($FUNCTION 0))) - (set_local $repl_env ($ASSOC1_S $repl_env "-" ($FUNCTION 1))) - (set_local $repl_env ($ASSOC1_S $repl_env "*" ($FUNCTION 2))) - (set_local $repl_env ($ASSOC1_S $repl_env "/" ($FUNCTION 3))) + (local.set $repl_env ($ASSOC1_S $repl_env "+" ($FUNCTION 0))) + (local.set $repl_env ($ASSOC1_S $repl_env "-" ($FUNCTION 1))) + (local.set $repl_env ($ASSOC1_S $repl_env "*" ($FUNCTION 2))) + (local.set $repl_env ($ASSOC1_S $repl_env "/" ($FUNCTION 3))) ;;($PR_MEMORY -1 -1) @@ -211,11 +211,11 @@ (loop $repl_loop (br_if $repl_done (i32.eqz ($readline "user> " $line))) (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) - (set_local $res ($REP $line $repl_env)) - (if (get_global $error_type) + (local.set $res ($REP $line $repl_env)) + (if (global.get $error_type) (then - ($printf_1 "Error: %s\n" (get_global $error_str)) - (set_global $error_type 0)) + ($printf_1 "Error: %s\n" (global.get $error_str)) + (global.set $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) diff --git a/wasm/step3_env.wam b/wasm/step3_env.wam index 67e29fbdbb..df04633b31 100644 --- a/wasm/step3_env.wam +++ b/wasm/step3_env.wam @@ -12,8 +12,8 @@ (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 $ret 0 $empty 0 $current 0) - (if (get_global $error_type) (return 0)) - (set_local $type ($TYPE $ast)) + (if (global.get $error_type) (return 0)) + (local.set $type ($TYPE $ast)) ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) @@ -23,63 +23,63 @@ (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) ;; symbol ;; found/res returned as hi 32/lo 32 of i64 - (set_local $res ($ENV_GET $env $ast)) + (local.set $res ($ENV_GET $env $ast)) (br $done)) ;; list, vector, hashmap ;; MAP_LOOP_START - (set_local $res ($MAP_LOOP_START $type)) + (local.set $res ($MAP_LOOP_START $type)) ;; push MAP_LOOP stack ;;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (local.set $ret $res) + (local.set $current $res) + (local.set $empty $res) (block $done (loop $loop ;; check if we are done evaluating the source sequence (br_if $done (i32.eq ($VAL0 $ast) 0)) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (local.set $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) (else - (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) - (set_local $val2 $res) + (local.set $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (local.set $val2 $res) ;; if error, release the unattached element - (if (get_global $error_type) + (if (global.get $error_type) (then ($RELEASE $res) - (set_local $res 0) + (local.set $res 0) (br $done))) ;; for hash-maps, copy the key (inc ref since we are going ;; to release it below) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $val3 $val2) - (set_local $val2 ($MEM_VAL1_ptr $ast)) + (local.set $val3 $val2) + (local.set $val2 ($MEM_VAL1_ptr $ast)) (drop ($INC_REF $val2)))) ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) - (set_local $ast ($MEM_VAL0_ptr $ast)) + (local.set $ast ($MEM_VAL0_ptr $ast)) (br $loop) ) ) ;; MAP_LOOP_DONE - (set_local $res $ret) + (local.set $res $ret) ;; EVAL_AST_RETURN: nothing to do (br $done)) ;; default - (set_local $res ($INC_REF $ast)) + (local.set $res ($INC_REF $ast)) ) $res @@ -87,7 +87,7 @@ (type $fnT (func (param i32) (result i32))) - (table anyfunc + (table funcref (elem $add $subtract $multiply $divide)) @@ -104,84 +104,84 @@ $a0 0 $a0sym 0 $a1 0 $a2 0 $let_env 0) - (set_local $f_args 0) - (set_local $f 0) - (set_local $args 0) + (local.set $f_args 0) + (local.set $f 0) + (local.set $args 0) - (if (get_global $error_type) (return 0)) + (if (global.get $error_type) (return 0)) ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (if (i32.ne ($TYPE $ast) (global.get $LIST_T)) (return ($EVAL_AST $ast $env))) ;; APPLY_LIST (if ($EMPTY_Q $ast) (return ($INC_REF $ast))) - (set_local $a0 ($MEM_VAL1_ptr $ast)) - (set_local $a0sym "") - (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) - (set_local $a0sym ($to_String $a0))) + (local.set $a0 ($MEM_VAL1_ptr $ast)) + (local.set $a0sym "") + (if (i32.eq ($TYPE $a0) (global.get $SYMBOL_T)) + (local.set $a0sym ($to_String $a0))) (if (i32.eqz ($strcmp "def!" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($EVAL $a2 $env)) - (if (get_global $error_type) (return $res)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($EVAL $a2 $env)) + (if (global.get $error_type) (return $res)) ;; set a1 in env to a2 - (set_local $res ($ENV_SET $env $a1 $res))) + (local.set $res ($ENV_SET $env $a1 $res))) (else (if (i32.eqz ($strcmp "let*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) ;; create new environment with outer as current environment - (set_local $let_env ($ENV_NEW $env)) + (local.set $let_env ($ENV_NEW $env)) (block $done (loop $loop (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element - (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) + (local.set $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $let_env)) - (br_if $done (get_global $error_type)) + (br_if $done (global.get $error_type)) ;; set key/value in the let environment - (set_local $res ($ENV_SET $let_env ($MEM_VAL1_ptr $a1) $res)) + (local.set $res ($ENV_SET $let_env ($MEM_VAL1_ptr $a1) $res)) ;; release our use, ENV_SET took ownership ($RELEASE $res) ;; skip to the next pair of a1 elements - (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (local.set $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) (br $loop) ) ) - (set_local $res ($EVAL $a2 $let_env)) + (local.set $res ($EVAL $a2 $let_env)) ;; EVAL_RETURN ($RELEASE $let_env)) (else ;; EVAL_INVOKE - (set_local $res ($EVAL_AST $ast $env)) - (set_local $f_args $res) + (local.set $res ($EVAL_AST $ast $env)) + (local.set $f_args $res) ;; if error, return f/args for release by caller - (if (get_global $error_type) + (if (global.get $error_type) (return $f_args)) - (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest - (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value + (local.set $args ($MEM_VAL0_ptr $f_args)) ;; rest + (local.set $f ($MEM_VAL1_ptr $f_args)) ;; value - (set_local $ftype ($TYPE $f)) - (if (i32.eq $ftype (get_global $FUNCTION_T)) + (local.set $ftype ($TYPE $f)) + (if (i32.eq $ftype (global.get $FUNCTION_T)) (then - (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f)))) + (local.set $res (call_indirect (type $fnT) $args ($VAL0 $f)))) (else ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) - (set_local $res 0))) + (local.set $res 0))) ($RELEASE $f_args))))) @@ -197,14 +197,14 @@ (func $REP (param $line i32 $env i32) (result i32) (LET $mv1 0 $mv2 0 $ms 0) (block $done - (set_local $mv1 ($READ $line)) - (br_if $done (get_global $error_type)) + (local.set $mv1 ($READ $line)) + (br_if $done (global.get $error_type)) - (set_local $mv2 ($EVAL $mv1 $env)) - (br_if $done (get_global $error_type)) + (local.set $mv2 ($EVAL $mv1 $env)) + (br_if $done (global.get $error_type)) ;; ($PR_MEMORY -1 -1) - (set_local $ms ($PRINT $mv2)) + (local.set $ms ($PRINT $mv2)) ) ;; release memory from MAL_READ and EVAL @@ -231,21 +231,21 @@ ($VAL0 ($MEM_VAL1_ptr ($MEM_VAL0_ptr $args)))))) (func $pr_memory (param $args i32) (result i32) ($PR_MEMORY -1 -1) - ($INC_REF (get_global $NIL))) + ($INC_REF (global.get $NIL))) (func $main (result i32) (LET $line (STATIC_ARRAY 201) $res 0 $repl_env 0) ;; DEBUG -;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) -;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) -;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) -;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) -;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) +;; ($printf_1 "memoryBase: 0x%x\n" (global.get $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (global.get $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (global.get $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (global.get $mem)) +;; ($printf_1 "string_mem: %d\n" (global.get $string_mem)) - (set_global $repl_env ($ENV_NEW (get_global $NIL))) - (set_local $repl_env (get_global $repl_env)) + (global.set $repl_env ($ENV_NEW (global.get $NIL))) + (local.set $repl_env (global.get $repl_env)) (drop ($ENV_SET_S $repl_env "+" ($FUNCTION 0))) (drop ($ENV_SET_S $repl_env "-" ($FUNCTION 1))) @@ -259,11 +259,11 @@ (loop $repl_loop (br_if $repl_done (i32.eqz ($readline "user> " $line))) (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) - (set_local $res ($REP $line $repl_env)) - (if (get_global $error_type) + (local.set $res ($REP $line $repl_env)) + (if (global.get $error_type) (then - ($printf_1 "Error: %s\n" (get_global $error_str)) - (set_global $error_type 0)) + ($printf_1 "Error: %s\n" (global.get $error_str)) + (global.set $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) diff --git a/wasm/step4_if_fn_do.wam b/wasm/step4_if_fn_do.wam index 24f3c7f7d5..da16f570cf 100644 --- a/wasm/step4_if_fn_do.wam +++ b/wasm/step4_if_fn_do.wam @@ -12,8 +12,8 @@ (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 $ret 0 $empty 0 $current 0) - (if (get_global $error_type) (return 0)) - (set_local $type ($TYPE $ast)) + (if (global.get $error_type) (return 0)) + (local.set $type ($TYPE $ast)) ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) @@ -23,63 +23,63 @@ (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) ;; symbol ;; found/res returned as hi 32/lo 32 of i64 - (set_local $res ($ENV_GET $env $ast)) + (local.set $res ($ENV_GET $env $ast)) (br $done)) ;; list, vector, hashmap ;; MAP_LOOP_START - (set_local $res ($MAP_LOOP_START $type)) + (local.set $res ($MAP_LOOP_START $type)) ;; push MAP_LOOP stack ;;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (local.set $ret $res) + (local.set $current $res) + (local.set $empty $res) (block $done (loop $loop ;; check if we are done evaluating the source sequence (br_if $done (i32.eq ($VAL0 $ast) 0)) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (local.set $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) (else - (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) - (set_local $val2 $res) + (local.set $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (local.set $val2 $res) ;; if error, release the unattached element - (if (get_global $error_type) + (if (global.get $error_type) (then ($RELEASE $res) - (set_local $res 0) + (local.set $res 0) (br $done))) ;; for hash-maps, copy the key (inc ref since we are going ;; to release it below) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $val3 $val2) - (set_local $val2 ($MEM_VAL1_ptr $ast)) + (local.set $val3 $val2) + (local.set $val2 ($MEM_VAL1_ptr $ast)) (drop ($INC_REF $val2)))) ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) - (set_local $ast ($MEM_VAL0_ptr $ast)) + (local.set $ast ($MEM_VAL0_ptr $ast)) (br $loop) ) ) ;; MAP_LOOP_DONE - (set_local $res $ret) + (local.set $res $ret) ;; EVAL_AST_RETURN: nothing to do (br $done)) ;; default - (set_local $res ($INC_REF $ast)) + (local.set $res ($INC_REF $ast)) ) $res @@ -98,135 +98,135 @@ $a0 0 $a0sym 0 $a1 0 $a2 0 $a3 0 $let_env 0 $fn_env 0 $a 0) - (set_local $f_args 0) - (set_local $f 0) - (set_local $args 0) + (local.set $f_args 0) + (local.set $f 0) + (local.set $args 0) - (if (get_global $error_type) (return 0)) + (if (global.get $error_type) (return 0)) ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (if (i32.ne ($TYPE $ast) (global.get $LIST_T)) (return ($EVAL_AST $ast $env))) ;; APPLY_LIST (if ($EMPTY_Q $ast) (return ($INC_REF $ast))) - (set_local $a0 ($MEM_VAL1_ptr $ast)) - (set_local $a0sym "") - (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) - (set_local $a0sym ($to_String $a0))) + (local.set $a0 ($MEM_VAL1_ptr $ast)) + (local.set $a0sym "") + (if (i32.eq ($TYPE $a0) (global.get $SYMBOL_T)) + (local.set $a0sym ($to_String $a0))) (if (i32.eqz ($strcmp "def!" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($EVAL $a2 $env)) - (if (get_global $error_type) (return $res)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($EVAL $a2 $env)) + (if (global.get $error_type) (return $res)) ;; set a1 in env to a2 - (set_local $res ($ENV_SET $env $a1 $res))) + (local.set $res ($ENV_SET $env $a1 $res))) (else (if (i32.eqz ($strcmp "let*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) ;; create new environment with outer as current environment - (set_local $let_env ($ENV_NEW $env)) + (local.set $let_env ($ENV_NEW $env)) (block $done (loop $loop (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element - (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) + (local.set $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $let_env)) - (br_if $done (get_global $error_type)) + (br_if $done (global.get $error_type)) ;; set key/value in the let environment - (set_local $res ($ENV_SET $let_env ($MEM_VAL1_ptr $a1) $res)) + (local.set $res ($ENV_SET $let_env ($MEM_VAL1_ptr $a1) $res)) ;; release our use, ENV_SET took ownership ($RELEASE $res) ;; skip to the next pair of a1 elements - (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (local.set $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) (br $loop) ) ) - (set_local $res ($EVAL $a2 $let_env)) + (local.set $res ($EVAL $a2 $let_env)) ;; EVAL_RETURN ($RELEASE $let_env)) (else (if (i32.eqz ($strcmp "do" $a0sym)) (then - (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env)) - (set_local $res ($LAST $el)) + (local.set $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env)) + (local.set $res ($LAST $el)) ($RELEASE $el)) (else (if (i32.eqz ($strcmp "if" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $res ($EVAL $a1 $env)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $res ($EVAL $a1 $env)) - (if (get_global $error_type) + (if (global.get $error_type) (then (nop)) - (else (if (OR (i32.eq $res (get_global $NIL)) - (i32.eq $res (get_global $FALSE))) + (else (if (OR (i32.eq $res (global.get $NIL)) + (i32.eq $res (global.get $FALSE))) (then ($RELEASE $res) ;; if no false case (A3), return nil (if (i32.lt_u ($COUNT $ast) 4) (then - (set_local $res ($INC_REF (get_global $NIL)))) + (local.set $res ($INC_REF (global.get $NIL)))) (else - (set_local $a3 ($MAL_GET_A3 $ast)) - (set_local $res ($EVAL $a3 $env))))) + (local.set $a3 ($MAL_GET_A3 $ast)) + (local.set $res ($EVAL $a3 $env))))) (else ($RELEASE $res) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($EVAL $a2 $env))))))) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($EVAL $a2 $env))))))) (else (if (i32.eqz ($strcmp "fn*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($ALLOC (get_global $MALFUNC_T) $a2 $a1 $env))) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($ALLOC (global.get $MALFUNC_T) $a2 $a1 $env))) (else ;; EVAL_INVOKE - (set_local $res ($EVAL_AST $ast $env)) - (set_local $f_args $res) + (local.set $res ($EVAL_AST $ast $env)) + (local.set $f_args $res) ;; if error, return f/args for release by caller - (if (get_global $error_type) + (if (global.get $error_type) (return $f_args)) - (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest - (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value + (local.set $args ($MEM_VAL0_ptr $f_args)) ;; rest + (local.set $f ($MEM_VAL1_ptr $f_args)) ;; value - (set_local $ftype ($TYPE $f)) - (if (i32.eq $ftype (get_global $FUNCTION_T)) + (local.set $ftype ($TYPE $f)) + (if (i32.eq $ftype (global.get $FUNCTION_T)) (then - (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f))) + (local.set $res (call_indirect (type $fnT) $args ($VAL0 $f))) ;; release f/args ($RELEASE $f_args)) - (else (if (i32.eq $ftype (get_global $MALFUNC_T)) + (else (if (i32.eq $ftype (global.get $MALFUNC_T)) (then - (set_local $fn_env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + (local.set $fn_env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) ($MEM_VAL1_ptr $f) $args)) ;; claim the AST before releasing the list containing it - (set_local $a ($MEM_VAL0_ptr $f)) + (local.set $a ($MEM_VAL0_ptr $f)) (drop ($INC_REF $a)) ;; release f/args ($RELEASE $f_args) - (set_local $res ($EVAL $a $fn_env)) + (local.set $res ($EVAL $a $fn_env)) ;; EVAL_RETURN ($RELEASE $fn_env) ($RELEASE $a)) (else ;; create new environment using env and params stored in function ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) - (set_local $res 0) + (local.set $res 0) ($RELEASE $f_args))))))))))))))) $res @@ -241,10 +241,10 @@ (func $RE (param $line i32 $env i32) (result i32) (LET $mv1 0 $res 0) (block $done - (set_local $mv1 ($READ $line)) - (br_if $done (get_global $error_type)) + (local.set $mv1 ($READ $line)) + (br_if $done (global.get $error_type)) - (set_local $res ($EVAL $mv1 $env)) + (local.set $res ($EVAL $mv1 $env)) ) ;; release memory from MAL_READ @@ -255,11 +255,11 @@ (func $REP (param $line i32 $env i32) (result i32) (LET $mv2 0 $ms 0) (block $done - (set_local $mv2 ($RE $line $env)) - (br_if $done (get_global $error_type)) + (local.set $mv2 ($RE $line $env)) + (br_if $done (global.get $error_type)) ;; ($PR_MEMORY -1 -1) - (set_local $ms ($PRINT $mv2)) + (local.set $ms ($PRINT $mv2)) ) ;; release memory from RE @@ -272,14 +272,14 @@ $res 0 $repl_env 0 $ms 0) ;; DEBUG -;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) -;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) -;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) -;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) -;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) +;; ($printf_1 "memoryBase: 0x%x\n" (global.get $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (global.get $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (global.get $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (global.get $mem)) +;; ($printf_1 "string_mem: %d\n" (global.get $string_mem)) - (set_global $repl_env ($ENV_NEW (get_global $NIL))) - (set_local $repl_env (get_global $repl_env)) + (global.set $repl_env ($ENV_NEW (global.get $NIL))) + (local.set $repl_env (global.get $repl_env)) ;; core.EXT: defined in wasm ($add_core_ns $repl_env) @@ -296,18 +296,18 @@ (loop $repl_loop (br_if $repl_done (i32.eqz ($readline "user> " $line))) (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) - (set_local $res ($REP $line $repl_env)) - (if (get_global $error_type) + (local.set $res ($REP $line $repl_env)) + (if (global.get $error_type) (then - (if (i32.eq 2 (get_global $error_type)) + (if (i32.eq 2 (global.get $error_type)) (then - (set_local $ms ($pr_str (get_global $error_val) 1)) + (local.set $ms ($pr_str (global.get $error_val) 1)) ($printf_1 "Error: %s\n" ($to_String $ms)) ($RELEASE $ms) - ($RELEASE (get_global $error_val))) + ($RELEASE (global.get $error_val))) (else - ($printf_1 "Error: %s\n" (get_global $error_str)))) - (set_global $error_type 0)) + ($printf_1 "Error: %s\n" (global.get $error_str)))) + (global.set $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) diff --git a/wasm/step5_tco.wam b/wasm/step5_tco.wam index 6186604080..20d8fda7d3 100644 --- a/wasm/step5_tco.wam +++ b/wasm/step5_tco.wam @@ -12,8 +12,8 @@ (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 $ret 0 $empty 0 $current 0) - (if (get_global $error_type) (return 0)) - (set_local $type ($TYPE $ast)) + (if (global.get $error_type) (return 0)) + (local.set $type ($TYPE $ast)) ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) @@ -23,16 +23,16 @@ (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) ;; symbol ;; found/res returned as hi 32/lo 32 of i64 - (set_local $res ($ENV_GET $env $ast)) + (local.set $res ($ENV_GET $env $ast)) (br $done)) ;; list, vector, hashmap ;; MAP_LOOP_START - (set_local $res ($MAP_LOOP_START $type)) + (local.set $res ($MAP_LOOP_START $type)) ;; push MAP_LOOP stack ;;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (local.set $ret $res) + (local.set $current $res) + (local.set $empty $res) (block $done (loop $loop @@ -42,47 +42,47 @@ (if $skiplast (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast))))) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (local.set $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) (else - (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) - (set_local $val2 $res) + (local.set $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (local.set $val2 $res) ;; if error, release the unattached element - (if (get_global $error_type) + (if (global.get $error_type) (then ($RELEASE $res) - (set_local $res 0) + (local.set $res 0) (br $done))) ;; for hash-maps, copy the key (inc ref since we are going ;; to release it below) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $val3 $val2) - (set_local $val2 ($MEM_VAL1_ptr $ast)) + (local.set $val3 $val2) + (local.set $val2 ($MEM_VAL1_ptr $ast)) (drop ($INC_REF $val2)))) ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) - (set_local $ast ($MEM_VAL0_ptr $ast)) + (local.set $ast ($MEM_VAL0_ptr $ast)) (br $loop) ) ) ;; MAP_LOOP_DONE - (set_local $res $ret) + (local.set $res $ret) ;; EVAL_AST_RETURN: nothing to do (br $done)) ;; default - (set_local $res ($INC_REF $ast)) + (local.set $res ($INC_REF $ast)) ) $res @@ -105,67 +105,67 @@ (block $EVAL_return (loop $TCO_loop - (set_local $f_args 0) - (set_local $f 0) - (set_local $args 0) + (local.set $f_args 0) + (local.set $f 0) + (local.set $args 0) - (if (get_global $error_type) + (if (global.get $error_type) (then - (set_local $res 0) + (local.set $res 0) (br $EVAL_return))) ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (if (i32.ne ($TYPE $ast) (global.get $LIST_T)) (then - (set_local $res ($EVAL_AST $ast $env 0)) + (local.set $res ($EVAL_AST $ast $env 0)) (br $EVAL_return))) ;; APPLY_LIST (if ($EMPTY_Q $ast) (then - (set_local $res ($INC_REF $ast)) + (local.set $res ($INC_REF $ast)) (br $EVAL_return))) - (set_local $a0 ($MEM_VAL1_ptr $ast)) - (set_local $a0sym "") - (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) - (set_local $a0sym ($to_String $a0))) + (local.set $a0 ($MEM_VAL1_ptr $ast)) + (local.set $a0sym "") + (if (i32.eq ($TYPE $a0) (global.get $SYMBOL_T)) + (local.set $a0sym ($to_String $a0))) (if (i32.eqz ($strcmp "def!" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($EVAL $a2 $env)) - (br_if $EVAL_return (get_global $error_type)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($EVAL $a2 $env)) + (br_if $EVAL_return (global.get $error_type)) ;; set a1 in env to a2 - (set_local $res ($ENV_SET $env $a1 $res)) + (local.set $res ($ENV_SET $env $a1 $res)) (br $EVAL_return)) (else (if (i32.eqz ($strcmp "let*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) ;; create new environment with outer as current environment - (set_local $prev_env $env) ;; save env for later release - (set_local $env ($ENV_NEW $env)) + (local.set $prev_env $env) ;; save env for later release + (local.set $env ($ENV_NEW $env)) (block $done (loop $loop (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element - (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) + (local.set $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) - (br_if $done (get_global $error_type)) + (br_if $done (global.get $error_type)) ;; set key/value in the let environment - (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) + (local.set $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) ;; release our use, ENV_SET took ownership ($RELEASE $res) ;; skip to the next pair of a1 elements - (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (local.set $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) (br $loop) ) ) @@ -174,73 +174,73 @@ (if (i32.ne $prev_env $orig_env) (then ($RELEASE $prev_env) - (set_local $prev_env 0))) + (local.set $prev_env 0))) - (set_local $ast $a2) + (local.set $ast $a2) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "do" $a0sym)) (then ;; EVAL the rest through second to last - (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) - (set_local $ast ($LAST $ast)) + (local.set $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) + (local.set $ast ($LAST $ast)) ($RELEASE $ast) ;; we already own it via ast ($RELEASE $el) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "if" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $res ($EVAL $a1 $env)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $res ($EVAL $a1 $env)) - (if (get_global $error_type) + (if (global.get $error_type) (then (nop)) - (else (if (OR (i32.eq $res (get_global $NIL)) - (i32.eq $res (get_global $FALSE))) + (else (if (OR (i32.eq $res (global.get $NIL)) + (i32.eq $res (global.get $FALSE))) (then ($RELEASE $res) ;; if no false case (A3), return nil (if (i32.lt_u ($COUNT $ast) 4) (then - (set_local $res ($INC_REF (get_global $NIL))) + (local.set $res ($INC_REF (global.get $NIL))) (br $EVAL_return)) (else - (set_local $ast ($MAL_GET_A3 $ast))))) + (local.set $ast ($MAL_GET_A3 $ast))))) (else ($RELEASE $res) - (set_local $ast ($MAL_GET_A2 $ast)))))) + (local.set $ast ($MAL_GET_A2 $ast)))))) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "fn*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($MALFUNC $a2 $a1 $env)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($MALFUNC $a2 $a1 $env)) (br $EVAL_return)) (else ;; EVAL_INVOKE - (set_local $res ($EVAL_AST $ast $env 0)) - (set_local $f_args $res) + (local.set $res ($EVAL_AST $ast $env 0)) + (local.set $f_args $res) ;; if error, return f/args for release by caller - (if (get_global $error_type) + (if (global.get $error_type) (then - (set_local $res $f_args) + (local.set $res $f_args) (br $EVAL_return))) - (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest - (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value + (local.set $args ($MEM_VAL0_ptr $f_args)) ;; rest + (local.set $f ($MEM_VAL1_ptr $f_args)) ;; value - (set_local $ftype ($TYPE $f)) - (if (i32.eq $ftype (get_global $FUNCTION_T)) + (local.set $ftype ($TYPE $f)) + (if (i32.eq $ftype (global.get $FUNCTION_T)) (then - (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f))) + (local.set $res (call_indirect (type $fnT) $args ($VAL0 $f))) ;; release f/args ($RELEASE $f_args) (br $EVAL_return)) - (else (if (i32.eq $ftype (get_global $MALFUNC_T)) + (else (if (i32.eq $ftype (global.get $MALFUNC_T)) (then ;; save the current environment for release - (set_local $prev_env $env) + (local.set $prev_env $env) ;; create new environment using env and params stored in function - (set_local $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + (local.set $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) ($MEM_VAL1_ptr $f) $args)) ;; release previous environment if not the current EVAL env @@ -249,17 +249,17 @@ (if (i32.ne $prev_env $orig_env) (then ($RELEASE $prev_env) - (set_local $prev_env 0))) + (local.set $prev_env 0))) ;; claim the AST before releasing the list containing it - (set_local $ast ($MEM_VAL0_ptr $f)) + (local.set $ast ($MEM_VAL0_ptr $f)) (drop ($INC_REF $ast)) ;; if we have already been here via TCO, release previous ;; ast ;; PEND_A_LV (if $prev_ast ($RELEASE $prev_ast)) - (set_local $prev_ast $ast) + (local.set $prev_ast $ast) ;; release f/args ($RELEASE $f_args) @@ -267,7 +267,7 @@ (br $TCO_loop)) (else ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) - (set_local $res 0) + (local.set $res 0) ($RELEASE $f_args) (br $EVAL_return))))))))))))))) @@ -290,10 +290,10 @@ (func $RE (param $line i32 $env i32) (result i32) (LET $mv1 0 $res 0) (block $done - (set_local $mv1 ($READ $line)) - (br_if $done (get_global $error_type)) + (local.set $mv1 ($READ $line)) + (br_if $done (global.get $error_type)) - (set_local $res ($EVAL $mv1 $env)) + (local.set $res ($EVAL $mv1 $env)) ) ;; release memory from MAL_READ @@ -304,11 +304,11 @@ (func $REP (param $line i32 $env i32) (result i32) (LET $mv2 0 $ms 0) (block $done - (set_local $mv2 ($RE $line $env)) - (br_if $done (get_global $error_type)) + (local.set $mv2 ($RE $line $env)) + (br_if $done (global.get $error_type)) ;; ($PR_MEMORY -1 -1) - (set_local $ms ($PRINT $mv2)) + (local.set $ms ($PRINT $mv2)) ) ;; release memory from RE @@ -321,14 +321,14 @@ $res 0 $repl_env 0 $ms 0) ;; DEBUG -;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) -;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) -;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) -;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) -;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) +;; ($printf_1 "memoryBase: 0x%x\n" (global.get $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (global.get $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (global.get $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (global.get $mem)) +;; ($printf_1 "string_mem: %d\n" (global.get $string_mem)) - (set_global $repl_env ($ENV_NEW (get_global $NIL))) - (set_local $repl_env (get_global $repl_env)) + (global.set $repl_env ($ENV_NEW (global.get $NIL))) + (local.set $repl_env (global.get $repl_env)) ;; core.EXT: defined in wasm ($add_core_ns $repl_env) @@ -345,18 +345,18 @@ (loop $repl_loop (br_if $repl_done (i32.eqz ($readline "user> " $line))) (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) - (set_local $res ($REP $line $repl_env)) - (if (get_global $error_type) + (local.set $res ($REP $line $repl_env)) + (if (global.get $error_type) (then - (if (i32.eq 2 (get_global $error_type)) + (if (i32.eq 2 (global.get $error_type)) (then - (set_local $ms ($pr_str (get_global $error_val) 1)) + (local.set $ms ($pr_str (global.get $error_val) 1)) ($printf_1 "Error: %s\n" ($to_String $ms)) ($RELEASE $ms) - ($RELEASE (get_global $error_val))) + ($RELEASE (global.get $error_val))) (else - ($printf_1 "Error: %s\n" (get_global $error_str)))) - (set_global $error_type 0)) + ($printf_1 "Error: %s\n" (global.get $error_str)))) + (global.set $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) diff --git a/wasm/step6_file.wam b/wasm/step6_file.wam index e3029b03be..e5b56a644c 100644 --- a/wasm/step6_file.wam +++ b/wasm/step6_file.wam @@ -12,8 +12,8 @@ (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 $ret 0 $empty 0 $current 0) - (if (get_global $error_type) (return 0)) - (set_local $type ($TYPE $ast)) + (if (global.get $error_type) (return 0)) + (local.set $type ($TYPE $ast)) ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) @@ -23,16 +23,16 @@ (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) ;; symbol ;; found/res returned as hi 32/lo 32 of i64 - (set_local $res ($ENV_GET $env $ast)) + (local.set $res ($ENV_GET $env $ast)) (br $done)) ;; list, vector, hashmap ;; MAP_LOOP_START - (set_local $res ($MAP_LOOP_START $type)) + (local.set $res ($MAP_LOOP_START $type)) ;; push MAP_LOOP stack ;;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (local.set $ret $res) + (local.set $current $res) + (local.set $empty $res) (block $done (loop $loop @@ -42,47 +42,47 @@ (if $skiplast (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast))))) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (local.set $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) (else - (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) - (set_local $val2 $res) + (local.set $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (local.set $val2 $res) ;; if error, release the unattached element - (if (get_global $error_type) + (if (global.get $error_type) (then ($RELEASE $res) - (set_local $res 0) + (local.set $res 0) (br $done))) ;; for hash-maps, copy the key (inc ref since we are going ;; to release it below) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $val3 $val2) - (set_local $val2 ($MEM_VAL1_ptr $ast)) + (local.set $val3 $val2) + (local.set $val2 ($MEM_VAL1_ptr $ast)) (drop ($INC_REF $val2)))) ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) - (set_local $ast ($MEM_VAL0_ptr $ast)) + (local.set $ast ($MEM_VAL0_ptr $ast)) (br $loop) ) ) ;; MAP_LOOP_DONE - (set_local $res $ret) + (local.set $res $ret) ;; EVAL_AST_RETURN: nothing to do (br $done)) ;; default - (set_local $res ($INC_REF $ast)) + (local.set $res ($INC_REF $ast)) ) $res @@ -105,67 +105,67 @@ (block $EVAL_return (loop $TCO_loop - (set_local $f_args 0) - (set_local $f 0) - (set_local $args 0) + (local.set $f_args 0) + (local.set $f 0) + (local.set $args 0) - (if (get_global $error_type) + (if (global.get $error_type) (then - (set_local $res 0) + (local.set $res 0) (br $EVAL_return))) ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (if (i32.ne ($TYPE $ast) (global.get $LIST_T)) (then - (set_local $res ($EVAL_AST $ast $env 0)) + (local.set $res ($EVAL_AST $ast $env 0)) (br $EVAL_return))) ;; APPLY_LIST (if ($EMPTY_Q $ast) (then - (set_local $res ($INC_REF $ast)) + (local.set $res ($INC_REF $ast)) (br $EVAL_return))) - (set_local $a0 ($MEM_VAL1_ptr $ast)) - (set_local $a0sym "") - (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) - (set_local $a0sym ($to_String $a0))) + (local.set $a0 ($MEM_VAL1_ptr $ast)) + (local.set $a0sym "") + (if (i32.eq ($TYPE $a0) (global.get $SYMBOL_T)) + (local.set $a0sym ($to_String $a0))) (if (i32.eqz ($strcmp "def!" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($EVAL $a2 $env)) - (br_if $EVAL_return (get_global $error_type)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($EVAL $a2 $env)) + (br_if $EVAL_return (global.get $error_type)) ;; set a1 in env to a2 - (set_local $res ($ENV_SET $env $a1 $res)) + (local.set $res ($ENV_SET $env $a1 $res)) (br $EVAL_return)) (else (if (i32.eqz ($strcmp "let*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) ;; create new environment with outer as current environment - (set_local $prev_env $env) ;; save env for later release - (set_local $env ($ENV_NEW $env)) + (local.set $prev_env $env) ;; save env for later release + (local.set $env ($ENV_NEW $env)) (block $done (loop $loop (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element - (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) + (local.set $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) - (br_if $done (get_global $error_type)) + (br_if $done (global.get $error_type)) ;; set key/value in the let environment - (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) + (local.set $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) ;; release our use, ENV_SET took ownership ($RELEASE $res) ;; skip to the next pair of a1 elements - (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (local.set $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) (br $loop) ) ) @@ -174,78 +174,78 @@ (if (i32.ne $prev_env $orig_env) (then ($RELEASE $prev_env) - (set_local $prev_env 0))) + (local.set $prev_env 0))) - (set_local $ast $a2) + (local.set $ast $a2) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "do" $a0sym)) (then ;; EVAL the rest through second to last - (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) - (set_local $ast ($LAST $ast)) + (local.set $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) + (local.set $ast ($LAST $ast)) ($RELEASE $ast) ;; we already own it via ast ($RELEASE $el) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "if" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $res ($EVAL $a1 $env)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $res ($EVAL $a1 $env)) - (if (get_global $error_type) + (if (global.get $error_type) (then (nop)) - (else (if (OR (i32.eq $res (get_global $NIL)) - (i32.eq $res (get_global $FALSE))) + (else (if (OR (i32.eq $res (global.get $NIL)) + (i32.eq $res (global.get $FALSE))) (then ($RELEASE $res) ;; if no false case (A3), return nil (if (i32.lt_u ($COUNT $ast) 4) (then - (set_local $res ($INC_REF (get_global $NIL))) + (local.set $res ($INC_REF (global.get $NIL))) (br $EVAL_return)) (else - (set_local $ast ($MAL_GET_A3 $ast))))) + (local.set $ast ($MAL_GET_A3 $ast))))) (else ($RELEASE $res) - (set_local $ast ($MAL_GET_A2 $ast)))))) + (local.set $ast ($MAL_GET_A2 $ast)))))) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "fn*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($MALFUNC $a2 $a1 $env)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($MALFUNC $a2 $a1 $env)) (br $EVAL_return)) (else ;; EVAL_INVOKE - (set_local $res ($EVAL_AST $ast $env 0)) - (set_local $f_args $res) + (local.set $res ($EVAL_AST $ast $env 0)) + (local.set $f_args $res) ;; if error, return f/args for release by caller - (if (get_global $error_type) + (if (global.get $error_type) (then - (set_local $res $f_args) + (local.set $res $f_args) (br $EVAL_return))) - (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest - (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value + (local.set $args ($MEM_VAL0_ptr $f_args)) ;; rest + (local.set $f ($MEM_VAL1_ptr $f_args)) ;; value - (set_local $ftype ($TYPE $f)) - (if (i32.eq $ftype (get_global $FUNCTION_T)) + (local.set $ftype ($TYPE $f)) + (if (i32.eq $ftype (global.get $FUNCTION_T)) (then (if (i32.eq ($VAL0 $f) 0) ;; eval (then - (set_local $res ($EVAL ($MEM_VAL1_ptr $args) - (get_global $repl_env)))) + (local.set $res ($EVAL ($MEM_VAL1_ptr $args) + (global.get $repl_env)))) (else - (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f))))) + (local.set $res (call_indirect (type $fnT) $args ($VAL0 $f))))) ;; release f/args ($RELEASE $f_args) (br $EVAL_return)) - (else (if (i32.eq $ftype (get_global $MALFUNC_T)) + (else (if (i32.eq $ftype (global.get $MALFUNC_T)) (then ;; save the current environment for release - (set_local $prev_env $env) + (local.set $prev_env $env) ;; create new environment using env and params stored in function - (set_local $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + (local.set $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) ($MEM_VAL1_ptr $f) $args)) ;; release previous environment if not the current EVAL env @@ -254,17 +254,17 @@ (if (i32.ne $prev_env $orig_env) (then ($RELEASE $prev_env) - (set_local $prev_env 0))) + (local.set $prev_env 0))) ;; claim the AST before releasing the list containing it - (set_local $ast ($MEM_VAL0_ptr $f)) + (local.set $ast ($MEM_VAL0_ptr $f)) (drop ($INC_REF $ast)) ;; if we have already been here via TCO, release previous ;; ast ;; PEND_A_LV (if $prev_ast ($RELEASE $prev_ast)) - (set_local $prev_ast $ast) + (local.set $prev_ast $ast) ;; release f/args ($RELEASE $f_args) @@ -272,7 +272,7 @@ (br $TCO_loop)) (else ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) - (set_local $res 0) + (local.set $res 0) ($RELEASE $f_args) (br $EVAL_return))))))))))))))) @@ -295,10 +295,10 @@ (func $RE (param $line i32 $env i32) (result i32) (LET $mv1 0 $res 0) (block $done - (set_local $mv1 ($READ $line)) - (br_if $done (get_global $error_type)) + (local.set $mv1 ($READ $line)) + (br_if $done (global.get $error_type)) - (set_local $res ($EVAL $mv1 $env)) + (local.set $res ($EVAL $mv1 $env)) ) ;; release memory from MAL_READ @@ -309,11 +309,11 @@ (func $REP (param $line i32 $env i32) (result i32) (LET $mv2 0 $ms 0) (block $done - (set_local $mv2 ($RE $line $env)) - (br_if $done (get_global $error_type)) + (local.set $mv2 ($RE $line $env)) + (br_if $done (global.get $error_type)) ;; ($PR_MEMORY -1 -1) - (set_local $ms ($PRINT $mv2)) + (local.set $ms ($PRINT $mv2)) ) ;; release memory from RE @@ -329,14 +329,14 @@ ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) -;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) -;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) -;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) -;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) -;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) +;; ($printf_1 "memoryBase: 0x%x\n" (global.get $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (global.get $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (global.get $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (global.get $mem)) +;; ($printf_1 "string_mem: %d\n" (global.get $string_mem)) - (set_global $repl_env ($ENV_NEW (get_global $NIL))) - (set_local $repl_env (get_global $repl_env)) + (global.set $repl_env ($ENV_NEW (global.get $NIL))) + (local.set $repl_env (global.get $repl_env)) ;; core.EXT: defined in wasm ($add_core_ns $repl_env) @@ -350,31 +350,31 @@ ;; Command line arguments - (set_local $res ($MAP_LOOP_START (get_global $LIST_T))) + (local.set $res ($MAP_LOOP_START (global.get $LIST_T))) ;; push MAP_LOP stack ;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (local.set $ret $res) + (local.set $current $res) + (local.set $empty $res) - (set_local $i 2) + (local.set $i 2) (block $done (loop $loop (br_if $done (i32.ge_u $i $argc)) - (set_local $val2 ($STRING (get_global $STRING_T) + (local.set $val2 ($STRING (global.get $STRING_T) (i32.load (i32.add $argv (i32.mul $i 4))))) ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE - (get_global $LIST_T) $empty $current $val2 0)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE + (global.get $LIST_T) $empty $current $val2 0)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) - (set_local $i (i32.add $i 1)) + (local.set $i (i32.add $i 1)) (br $loop) ) ) @@ -386,12 +386,12 @@ (if (i32.gt_u $argc 1) (then (drop ($ENV_SET_S $repl_env - "*FILE*" ($STRING (get_global $STRING_T) + "*FILE*" ($STRING (global.get $STRING_T) (i32.load (i32.add $argv 4))))) ($RELEASE ($RE "(load-file *FILE*)" $repl_env)) - (if (get_global $error_type) + (if (global.get $error_type) (then - ($printf_1 "Error: %s\n" (get_global $error_str)) + ($printf_1 "Error: %s\n" (global.get $error_str)) (return 1)) (else (return 0))))) @@ -401,18 +401,18 @@ (loop $repl_loop (br_if $repl_done (i32.eqz ($readline "user> " $line))) (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) - (set_local $res ($REP $line $repl_env)) - (if (get_global $error_type) + (local.set $res ($REP $line $repl_env)) + (if (global.get $error_type) (then - (if (i32.eq 2 (get_global $error_type)) + (if (i32.eq 2 (global.get $error_type)) (then - (set_local $ms ($pr_str (get_global $error_val) 1)) + (local.set $ms ($pr_str (global.get $error_val) 1)) ($printf_1 "Error: %s\n" ($to_String $ms)) ($RELEASE $ms) - ($RELEASE (get_global $error_val))) + ($RELEASE (global.get $error_val))) (else - ($printf_1 "Error: %s\n" (get_global $error_str)))) - (set_global $error_type 0)) + ($printf_1 "Error: %s\n" (global.get $error_str)))) + (global.set $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) diff --git a/wasm/step7_quote.wam b/wasm/step7_quote.wam index 753f97a8ad..e609a56d4b 100644 --- a/wasm/step7_quote.wam +++ b/wasm/step7_quote.wam @@ -10,8 +10,8 @@ ;; EVAL (func $is_pair (param $ast i32) (result i32) (LET $type ($TYPE $ast)) - (AND (OR (i32.eq $type (get_global $LIST_T)) - (i32.eq $type (get_global $VECTOR_T))) + (AND (OR (i32.eq $type (global.get $LIST_T)) + (i32.eq $type (global.get $VECTOR_T))) (i32.ne ($VAL0 $ast) 0)) ) @@ -19,38 +19,38 @@ (LET $res 0 $sym 0 $second 0 $third 0) (if (i32.eqz ($is_pair $ast)) ;; QQ_QUOTE (then - (set_local $sym ($STRING (get_global $SYMBOL_T) "quote")) + (local.set $sym ($STRING (global.get $SYMBOL_T) "quote")) ;; ['quote ast] - (set_local $res ($LIST2 $sym $ast)) + (local.set $res ($LIST2 $sym $ast)) ($RELEASE $sym)) (else - (set_local $res ($MEM_VAL1_ptr $ast)) - (if (AND (i32.eq ($TYPE $res) (get_global $SYMBOL_T)) + (local.set $res ($MEM_VAL1_ptr $ast)) + (if (AND (i32.eq ($TYPE $res) (global.get $SYMBOL_T)) (i32.eqz ($strcmp "unquote" ($to_String $res)))) (then ;; ast[1] - (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))))) + (local.set $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))))) (else (if (AND ($is_pair $res) (i32.eq ($TYPE ($MEM_VAL1_ptr $res)) - (get_global $SYMBOL_T)) + (global.get $SYMBOL_T)) (i32.eqz ($strcmp "splice-unquote" ($to_String ($MEM_VAL1_ptr $res))))) (then ;; ['concat, ast[0][1], quasiquote(ast[1..])] - (set_local $sym ($STRING (get_global $SYMBOL_T) "concat")) - (set_local $second + (local.set $sym ($STRING (global.get $SYMBOL_T) "concat")) + (local.set $second ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL1_ptr $ast)))) - (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) - (set_local $res ($LIST3 $sym $second $third)) + (local.set $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (local.set $res ($LIST3 $sym $second $third)) ;; release inner quasiquoted since outer list take ownership ($RELEASE $third) ($RELEASE $sym)) (else ;; ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - (set_local $sym ($STRING (get_global $SYMBOL_T) "cons")) - (set_local $second ($QUASIQUOTE ($MEM_VAL1_ptr $ast))) - (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) - (set_local $res ($LIST3 $sym $second $third)) + (local.set $sym ($STRING (global.get $SYMBOL_T) "cons")) + (local.set $second ($QUASIQUOTE ($MEM_VAL1_ptr $ast))) + (local.set $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (local.set $res ($LIST3 $sym $second $third)) ;; release inner quasiquoted since outer list takes ownership ($RELEASE $third) ($RELEASE $second) @@ -62,8 +62,8 @@ (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 $ret 0 $empty 0 $current 0) - (if (get_global $error_type) (return 0)) - (set_local $type ($TYPE $ast)) + (if (global.get $error_type) (return 0)) + (local.set $type ($TYPE $ast)) ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) @@ -73,16 +73,16 @@ (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) ;; symbol ;; found/res returned as hi 32/lo 32 of i64 - (set_local $res ($ENV_GET $env $ast)) + (local.set $res ($ENV_GET $env $ast)) (br $done)) ;; list, vector, hashmap ;; MAP_LOOP_START - (set_local $res ($MAP_LOOP_START $type)) + (local.set $res ($MAP_LOOP_START $type)) ;; push MAP_LOOP stack ;;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (local.set $ret $res) + (local.set $current $res) + (local.set $empty $res) (block $done (loop $loop @@ -92,47 +92,47 @@ (if $skiplast (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast))))) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (local.set $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) (else - (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) - (set_local $val2 $res) + (local.set $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (local.set $val2 $res) ;; if error, release the unattached element - (if (get_global $error_type) + (if (global.get $error_type) (then ($RELEASE $res) - (set_local $res 0) + (local.set $res 0) (br $done))) ;; for hash-maps, copy the key (inc ref since we are going ;; to release it below) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $val3 $val2) - (set_local $val2 ($MEM_VAL1_ptr $ast)) + (local.set $val3 $val2) + (local.set $val2 ($MEM_VAL1_ptr $ast)) (drop ($INC_REF $val2)))) ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) - (set_local $ast ($MEM_VAL0_ptr $ast)) + (local.set $ast ($MEM_VAL0_ptr $ast)) (br $loop) ) ) ;; MAP_LOOP_DONE - (set_local $res $ret) + (local.set $res $ret) ;; EVAL_AST_RETURN: nothing to do (br $done)) ;; default - (set_local $res ($INC_REF $ast)) + (local.set $res ($INC_REF $ast)) ) $res @@ -155,67 +155,67 @@ (block $EVAL_return (loop $TCO_loop - (set_local $f_args 0) - (set_local $f 0) - (set_local $args 0) + (local.set $f_args 0) + (local.set $f 0) + (local.set $args 0) - (if (get_global $error_type) + (if (global.get $error_type) (then - (set_local $res 0) + (local.set $res 0) (br $EVAL_return))) ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (if (i32.ne ($TYPE $ast) (global.get $LIST_T)) (then - (set_local $res ($EVAL_AST $ast $env 0)) + (local.set $res ($EVAL_AST $ast $env 0)) (br $EVAL_return))) ;; APPLY_LIST (if ($EMPTY_Q $ast) (then - (set_local $res ($INC_REF $ast)) + (local.set $res ($INC_REF $ast)) (br $EVAL_return))) - (set_local $a0 ($MEM_VAL1_ptr $ast)) - (set_local $a0sym "") - (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) - (set_local $a0sym ($to_String $a0))) + (local.set $a0 ($MEM_VAL1_ptr $ast)) + (local.set $a0sym "") + (if (i32.eq ($TYPE $a0) (global.get $SYMBOL_T)) + (local.set $a0sym ($to_String $a0))) (if (i32.eqz ($strcmp "def!" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($EVAL $a2 $env)) - (br_if $EVAL_return (get_global $error_type)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($EVAL $a2 $env)) + (br_if $EVAL_return (global.get $error_type)) ;; set a1 in env to a2 - (set_local $res ($ENV_SET $env $a1 $res)) + (local.set $res ($ENV_SET $env $a1 $res)) (br $EVAL_return)) (else (if (i32.eqz ($strcmp "let*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) ;; create new environment with outer as current environment - (set_local $prev_env $env) ;; save env for later release - (set_local $env ($ENV_NEW $env)) + (local.set $prev_env $env) ;; save env for later release + (local.set $env ($ENV_NEW $env)) (block $done (loop $loop (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element - (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) + (local.set $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) - (br_if $done (get_global $error_type)) + (br_if $done (global.get $error_type)) ;; set key/value in the let environment - (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) + (local.set $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) ;; release our use, ENV_SET took ownership ($RELEASE $res) ;; skip to the next pair of a1 elements - (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (local.set $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) (br $loop) ) ) @@ -224,90 +224,90 @@ (if (i32.ne $prev_env $orig_env) (then ($RELEASE $prev_env) - (set_local $prev_env 0))) + (local.set $prev_env 0))) - (set_local $ast $a2) + (local.set $ast $a2) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "do" $a0sym)) (then ;; EVAL the rest through second to last - (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) - (set_local $ast ($LAST $ast)) + (local.set $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) + (local.set $ast ($LAST $ast)) ($RELEASE $ast) ;; we already own it via ast ($RELEASE $el) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "quote" $a0sym)) (then - (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + (local.set $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) (br $EVAL_return)) (else (if (i32.eqz ($strcmp "quasiquote" $a0sym)) (then - (set_local $ast ($QUASIQUOTE ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + (local.set $ast ($QUASIQUOTE ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) ;; if we have already been here via TCO, release previous ast (if $prev_ast ($RELEASE $prev_ast)) - (set_local $prev_ast $ast) + (local.set $prev_ast $ast) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "if" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $res ($EVAL $a1 $env)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $res ($EVAL $a1 $env)) - (if (get_global $error_type) + (if (global.get $error_type) (then (nop)) - (else (if (OR (i32.eq $res (get_global $NIL)) - (i32.eq $res (get_global $FALSE))) + (else (if (OR (i32.eq $res (global.get $NIL)) + (i32.eq $res (global.get $FALSE))) (then ($RELEASE $res) ;; if no false case (A3), return nil (if (i32.lt_u ($COUNT $ast) 4) (then - (set_local $res ($INC_REF (get_global $NIL))) + (local.set $res ($INC_REF (global.get $NIL))) (br $EVAL_return)) (else - (set_local $ast ($MAL_GET_A3 $ast))))) + (local.set $ast ($MAL_GET_A3 $ast))))) (else ($RELEASE $res) - (set_local $ast ($MAL_GET_A2 $ast)))))) + (local.set $ast ($MAL_GET_A2 $ast)))))) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "fn*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($MALFUNC $a2 $a1 $env)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($MALFUNC $a2 $a1 $env)) (br $EVAL_return)) (else ;; EVAL_INVOKE - (set_local $res ($EVAL_AST $ast $env 0)) - (set_local $f_args $res) + (local.set $res ($EVAL_AST $ast $env 0)) + (local.set $f_args $res) ;; if error, return f/args for release by caller - (if (get_global $error_type) + (if (global.get $error_type) (then - (set_local $res $f_args) + (local.set $res $f_args) (br $EVAL_return))) - (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest - (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value + (local.set $args ($MEM_VAL0_ptr $f_args)) ;; rest + (local.set $f ($MEM_VAL1_ptr $f_args)) ;; value - (set_local $ftype ($TYPE $f)) - (if (i32.eq $ftype (get_global $FUNCTION_T)) + (local.set $ftype ($TYPE $f)) + (if (i32.eq $ftype (global.get $FUNCTION_T)) (then (if (i32.eq ($VAL0 $f) 0) ;; eval (then - (set_local $res ($EVAL ($MEM_VAL1_ptr $args) - (get_global $repl_env)))) + (local.set $res ($EVAL ($MEM_VAL1_ptr $args) + (global.get $repl_env)))) (else - (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f))))) + (local.set $res (call_indirect (type $fnT) $args ($VAL0 $f))))) ;; release f/args ($RELEASE $f_args) (br $EVAL_return)) - (else (if (i32.eq $ftype (get_global $MALFUNC_T)) + (else (if (i32.eq $ftype (global.get $MALFUNC_T)) (then ;; save the current environment for release - (set_local $prev_env $env) + (local.set $prev_env $env) ;; create new environment using env and params stored in function - (set_local $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + (local.set $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) ($MEM_VAL1_ptr $f) $args)) ;; release previous environment if not the current EVAL env @@ -316,17 +316,17 @@ (if (i32.ne $prev_env $orig_env) (then ($RELEASE $prev_env) - (set_local $prev_env 0))) + (local.set $prev_env 0))) ;; claim the AST before releasing the list containing it - (set_local $ast ($MEM_VAL0_ptr $f)) + (local.set $ast ($MEM_VAL0_ptr $f)) (drop ($INC_REF $ast)) ;; if we have already been here via TCO, release previous ;; ast ;; PEND_A_LV (if $prev_ast ($RELEASE $prev_ast)) - (set_local $prev_ast $ast) + (local.set $prev_ast $ast) ;; release f/args ($RELEASE $f_args) @@ -334,7 +334,7 @@ (br $TCO_loop)) (else ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) - (set_local $res 0) + (local.set $res 0) ($RELEASE $f_args) (br $EVAL_return))))))))))))))))))) @@ -357,10 +357,10 @@ (func $RE (param $line i32 $env i32) (result i32) (LET $mv1 0 $res 0) (block $done - (set_local $mv1 ($READ $line)) - (br_if $done (get_global $error_type)) + (local.set $mv1 ($READ $line)) + (br_if $done (global.get $error_type)) - (set_local $res ($EVAL $mv1 $env)) + (local.set $res ($EVAL $mv1 $env)) ) ;; release memory from MAL_READ @@ -371,11 +371,11 @@ (func $REP (param $line i32 $env i32) (result i32) (LET $mv2 0 $ms 0) (block $done - (set_local $mv2 ($RE $line $env)) - (br_if $done (get_global $error_type)) + (local.set $mv2 ($RE $line $env)) + (br_if $done (global.get $error_type)) ;; ($PR_MEMORY -1 -1) - (set_local $ms ($PRINT $mv2)) + (local.set $ms ($PRINT $mv2)) ) ;; release memory from RE @@ -391,14 +391,14 @@ ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) -;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) -;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) -;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) -;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) -;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) +;; ($printf_1 "memoryBase: 0x%x\n" (global.get $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (global.get $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (global.get $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (global.get $mem)) +;; ($printf_1 "string_mem: %d\n" (global.get $string_mem)) - (set_global $repl_env ($ENV_NEW (get_global $NIL))) - (set_local $repl_env (get_global $repl_env)) + (global.set $repl_env ($ENV_NEW (global.get $NIL))) + (local.set $repl_env (global.get $repl_env)) ;; core.EXT: defined in wasm ($add_core_ns $repl_env) @@ -412,31 +412,31 @@ ;; Command line arguments - (set_local $res ($MAP_LOOP_START (get_global $LIST_T))) + (local.set $res ($MAP_LOOP_START (global.get $LIST_T))) ;; push MAP_LOP stack ;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (local.set $ret $res) + (local.set $current $res) + (local.set $empty $res) - (set_local $i 2) + (local.set $i 2) (block $done (loop $loop (br_if $done (i32.ge_u $i $argc)) - (set_local $val2 ($STRING (get_global $STRING_T) + (local.set $val2 ($STRING (global.get $STRING_T) (i32.load (i32.add $argv (i32.mul $i 4))))) ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE - (get_global $LIST_T) $empty $current $val2 0)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE + (global.get $LIST_T) $empty $current $val2 0)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) - (set_local $i (i32.add $i 1)) + (local.set $i (i32.add $i 1)) (br $loop) ) ) @@ -448,12 +448,12 @@ (if (i32.gt_u $argc 1) (then (drop ($ENV_SET_S $repl_env - "*FILE*" ($STRING (get_global $STRING_T) + "*FILE*" ($STRING (global.get $STRING_T) (i32.load (i32.add $argv 4))))) ($RELEASE ($RE "(load-file *FILE*)" $repl_env)) - (if (get_global $error_type) + (if (global.get $error_type) (then - ($printf_1 "Error: %s\n" (get_global $error_str)) + ($printf_1 "Error: %s\n" (global.get $error_str)) (return 1)) (else (return 0))))) @@ -463,18 +463,18 @@ (loop $repl_loop (br_if $repl_done (i32.eqz ($readline "user> " $line))) (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) - (set_local $res ($REP $line $repl_env)) - (if (get_global $error_type) + (local.set $res ($REP $line $repl_env)) + (if (global.get $error_type) (then - (if (i32.eq 2 (get_global $error_type)) + (if (i32.eq 2 (global.get $error_type)) (then - (set_local $ms ($pr_str (get_global $error_val) 1)) + (local.set $ms ($pr_str (global.get $error_val) 1)) ($printf_1 "Error: %s\n" ($to_String $ms)) ($RELEASE $ms) - ($RELEASE (get_global $error_val))) + ($RELEASE (global.get $error_val))) (else - ($printf_1 "Error: %s\n" (get_global $error_str)))) - (set_global $error_type 0)) + ($printf_1 "Error: %s\n" (global.get $error_str)))) + (global.set $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) diff --git a/wasm/step8_macros.wam b/wasm/step8_macros.wam index 95d0642d8b..ff042d065e 100644 --- a/wasm/step8_macros.wam +++ b/wasm/step8_macros.wam @@ -10,8 +10,8 @@ ;; EVAL (func $is_pair (param $ast i32) (result i32) (LET $type ($TYPE $ast)) - (AND (OR (i32.eq $type (get_global $LIST_T)) - (i32.eq $type (get_global $VECTOR_T))) + (AND (OR (i32.eq $type (global.get $LIST_T)) + (i32.eq $type (global.get $VECTOR_T))) (i32.ne ($VAL0 $ast) 0)) ) @@ -19,38 +19,38 @@ (LET $res 0 $sym 0 $second 0 $third 0) (if (i32.eqz ($is_pair $ast)) ;; QQ_QUOTE (then - (set_local $sym ($STRING (get_global $SYMBOL_T) "quote")) + (local.set $sym ($STRING (global.get $SYMBOL_T) "quote")) ;; ['quote ast] - (set_local $res ($LIST2 $sym $ast)) + (local.set $res ($LIST2 $sym $ast)) ($RELEASE $sym)) (else - (set_local $res ($MEM_VAL1_ptr $ast)) - (if (AND (i32.eq ($TYPE $res) (get_global $SYMBOL_T)) + (local.set $res ($MEM_VAL1_ptr $ast)) + (if (AND (i32.eq ($TYPE $res) (global.get $SYMBOL_T)) (i32.eqz ($strcmp "unquote" ($to_String $res)))) (then ;; ast[1] - (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))))) + (local.set $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))))) (else (if (AND ($is_pair $res) (i32.eq ($TYPE ($MEM_VAL1_ptr $res)) - (get_global $SYMBOL_T)) + (global.get $SYMBOL_T)) (i32.eqz ($strcmp "splice-unquote" ($to_String ($MEM_VAL1_ptr $res))))) (then ;; ['concat, ast[0][1], quasiquote(ast[1..])] - (set_local $sym ($STRING (get_global $SYMBOL_T) "concat")) - (set_local $second + (local.set $sym ($STRING (global.get $SYMBOL_T) "concat")) + (local.set $second ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL1_ptr $ast)))) - (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) - (set_local $res ($LIST3 $sym $second $third)) + (local.set $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (local.set $res ($LIST3 $sym $second $third)) ;; release inner quasiquoted since outer list take ownership ($RELEASE $third) ($RELEASE $sym)) (else ;; ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - (set_local $sym ($STRING (get_global $SYMBOL_T) "cons")) - (set_local $second ($QUASIQUOTE ($MEM_VAL1_ptr $ast))) - (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) - (set_local $res ($LIST3 $sym $second $third)) + (local.set $sym ($STRING (global.get $SYMBOL_T) "cons")) + (local.set $second ($QUASIQUOTE ($MEM_VAL1_ptr $ast))) + (local.set $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (local.set $res ($LIST3 $sym $second $third)) ;; release inner quasiquoted since outer list takes ownership ($RELEASE $third) ($RELEASE $second) @@ -65,35 +65,35 @@ (local $mac_env i64) (LET $ast $orig_ast $mac 0) - (set_global $mac_stack (STATIC_ARRAY 1024)) ;; 256 * 4, TODO: move to init + (global.set $mac_stack (STATIC_ARRAY 1024)) ;; 256 * 4, TODO: move to init (block $done (loop $loop (br_if $done - (OR (i32.ne ($TYPE $ast) (get_global $LIST_T)) ;; a list + (OR (i32.ne ($TYPE $ast) (global.get $LIST_T)) ;; a list (i32.eqz ($VAL0 $ast)) ;; non-empty (i32.ne ($TYPE ($MEM_VAL1_ptr $ast)) ;; leading symbol - (get_global $SYMBOL_T)))) - (set_local $mac_env ($ENV_FIND $env ($MEM_VAL1_ptr $ast))) - (set_local $mac (i32.wrap/i64 (i64.shr_u $mac_env (i64.const 32)))) - (br_if $done (OR (i32.eqz (i32.wrap/i64 $mac_env)) ;; defined in env + (global.get $SYMBOL_T)))) + (local.set $mac_env ($ENV_FIND $env ($MEM_VAL1_ptr $ast))) + (local.set $mac (i32.wrap_i64 (i64.shr_u $mac_env (i64.const 32)))) + (br_if $done (OR (i32.eqz (i32.wrap_i64 $mac_env)) ;; defined in env (i32.ne ($TYPE $mac) ;; a macro - (get_global $MACRO_T)))) + (global.get $MACRO_T)))) - (set_local $ast ($APPLY $mac ($MEM_VAL0_ptr $ast))) + (local.set $ast ($APPLY $mac ($MEM_VAL0_ptr $ast))) ;; PEND_A_LV ;; if ast is not the first ast that was passed in, then add it ;; to the pending release list. (if (i32.ne $ast $orig_ast) (then - (set_global $mac_stack_top - (i32.add (get_global $mac_stack_top) 1)) - (if (i32.ge_s (i32.mul (get_global $mac_stack_top) 4) 1024) ;; 256 * 4 + (global.set $mac_stack_top + (i32.add (global.get $mac_stack_top) 1)) + (if (i32.ge_s (i32.mul (global.get $mac_stack_top) 4) 1024) ;; 256 * 4 ($fatal 7 "Exhausted mac_stack!\n")) (i32.store (i32.add - (get_global $mac_stack) - (i32.mul (get_global $mac_stack_top) 4)) + (global.get $mac_stack) + (i32.mul (global.get $mac_stack_top) 4)) $ast))) - (br_if $done (get_global $error_type)) + (br_if $done (global.get $error_type)) (br $loop) ) @@ -105,8 +105,8 @@ (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 $ret 0 $empty 0 $current 0) - (if (get_global $error_type) (return 0)) - (set_local $type ($TYPE $ast)) + (if (global.get $error_type) (return 0)) + (local.set $type ($TYPE $ast)) ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) @@ -116,16 +116,16 @@ (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) ;; symbol ;; found/res returned as hi 32/lo 32 of i64 - (set_local $res ($ENV_GET $env $ast)) + (local.set $res ($ENV_GET $env $ast)) (br $done)) ;; list, vector, hashmap ;; MAP_LOOP_START - (set_local $res ($MAP_LOOP_START $type)) + (local.set $res ($MAP_LOOP_START $type)) ;; push MAP_LOOP stack ;;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (local.set $ret $res) + (local.set $current $res) + (local.set $empty $res) (block $done (loop $loop @@ -135,47 +135,47 @@ (if $skiplast (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast))))) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (local.set $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) (else - (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) - (set_local $val2 $res) + (local.set $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (local.set $val2 $res) ;; if error, release the unattached element - (if (get_global $error_type) + (if (global.get $error_type) (then ($RELEASE $res) - (set_local $res 0) + (local.set $res 0) (br $done))) ;; for hash-maps, copy the key (inc ref since we are going ;; to release it below) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $val3 $val2) - (set_local $val2 ($MEM_VAL1_ptr $ast)) + (local.set $val3 $val2) + (local.set $val2 ($MEM_VAL1_ptr $ast)) (drop ($INC_REF $val2)))) ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) - (set_local $ast ($MEM_VAL0_ptr $ast)) + (local.set $ast ($MEM_VAL0_ptr $ast)) (br $loop) ) ) ;; MAP_LOOP_DONE - (set_local $res $ret) + (local.set $res $ret) ;; EVAL_AST_RETURN: nothing to do (br $done)) ;; default - (set_local $res ($INC_REF $ast)) + (local.set $res ($INC_REF $ast)) ) $res @@ -191,7 +191,7 @@ (func $EVAL (param $orig_ast i32 $orig_env i32) (result i32) (LET $ast $orig_ast $env $orig_env - $orig_mac_stack_top (get_global $mac_stack_top) + $orig_mac_stack_top (global.get $mac_stack_top) $prev_ast 0 $prev_env 0 $res 0 $el 0 $ftype 0 $f_args 0 $f 0 $args 0 $a0 0 $a0sym 0 $a1 0 $a2 0) @@ -199,75 +199,75 @@ (block $EVAL_return (loop $TCO_loop - (set_local $f_args 0) - (set_local $f 0) - (set_local $args 0) + (local.set $f_args 0) + (local.set $f 0) + (local.set $args 0) - (if (get_global $error_type) + (if (global.get $error_type) (then - (set_local $res 0) + (local.set $res 0) (br $EVAL_return))) ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (if (i32.ne ($TYPE $ast) (global.get $LIST_T)) (then - (set_local $res ($EVAL_AST $ast $env 0)) + (local.set $res ($EVAL_AST $ast $env 0)) (br $EVAL_return))) ;; APPLY_LIST - (set_local $ast ($MACROEXPAND $ast $env)) + (local.set $ast ($MACROEXPAND $ast $env)) ;;($PR_VALUE ">>> >>> EVAL ast: '%s'\n" $ast) - (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (if (i32.ne ($TYPE $ast) (global.get $LIST_T)) (then - (set_local $res ($EVAL_AST $ast $env 0)) + (local.set $res ($EVAL_AST $ast $env 0)) (br $EVAL_return))) (if ($EMPTY_Q $ast) (then - (set_local $res ($INC_REF $ast)) + (local.set $res ($INC_REF $ast)) (br $EVAL_return))) - (set_local $a0 ($MEM_VAL1_ptr $ast)) - (set_local $a0sym "") - (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) - (set_local $a0sym ($to_String $a0))) + (local.set $a0 ($MEM_VAL1_ptr $ast)) + (local.set $a0sym "") + (if (i32.eq ($TYPE $a0) (global.get $SYMBOL_T)) + (local.set $a0sym ($to_String $a0))) (if (i32.eqz ($strcmp "def!" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($EVAL $a2 $env)) - (br_if $EVAL_return (get_global $error_type)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($EVAL $a2 $env)) + (br_if $EVAL_return (global.get $error_type)) ;; set a1 in env to a2 - (set_local $res ($ENV_SET $env $a1 $res)) + (local.set $res ($ENV_SET $env $a1 $res)) (br $EVAL_return)) (else (if (i32.eqz ($strcmp "let*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) ;; create new environment with outer as current environment - (set_local $prev_env $env) ;; save env for later release - (set_local $env ($ENV_NEW $env)) + (local.set $prev_env $env) ;; save env for later release + (local.set $env ($ENV_NEW $env)) (block $done (loop $loop (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element - (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) + (local.set $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) - (br_if $done (get_global $error_type)) + (br_if $done (global.get $error_type)) ;; set key/value in the let environment - (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) + (local.set $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) ;; release our use, ENV_SET took ownership ($RELEASE $res) ;; skip to the next pair of a1 elements - (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (local.set $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) (br $loop) ) ) @@ -276,107 +276,107 @@ (if (i32.ne $prev_env $orig_env) (then ($RELEASE $prev_env) - (set_local $prev_env 0))) + (local.set $prev_env 0))) - (set_local $ast $a2) + (local.set $ast $a2) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "do" $a0sym)) (then ;; EVAL the rest through second to last - (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) - (set_local $ast ($LAST $ast)) + (local.set $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) + (local.set $ast ($LAST $ast)) ($RELEASE $ast) ;; we already own it via ast ($RELEASE $el) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "quote" $a0sym)) (then - (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + (local.set $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) (br $EVAL_return)) (else (if (i32.eqz ($strcmp "quasiquote" $a0sym)) (then - (set_local $ast ($QUASIQUOTE ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + (local.set $ast ($QUASIQUOTE ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) ;; if we have already been here via TCO, release previous ast (if $prev_ast ($RELEASE $prev_ast)) - (set_local $prev_ast $ast) + (local.set $prev_ast $ast) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "defmacro!" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($EVAL $a2 $env)) - ($SET_TYPE $res (get_global $MACRO_T)) - (br_if $EVAL_return (get_global $error_type)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($EVAL $a2 $env)) + ($SET_TYPE $res (global.get $MACRO_T)) + (br_if $EVAL_return (global.get $error_type)) ;; set a1 in env to a2 - (set_local $res ($ENV_SET $env $a1 $res)) + (local.set $res ($ENV_SET $env $a1 $res)) (br $EVAL_return)) (else (if (i32.eqz ($strcmp "macroexpand" $a0sym)) (then ;; since we are returning it unevaluated, inc the ref cnt - (set_local $res ($INC_REF ($MACROEXPAND + (local.set $res ($INC_REF ($MACROEXPAND ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)) $env)))) (else (if (i32.eqz ($strcmp "if" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $res ($EVAL $a1 $env)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $res ($EVAL $a1 $env)) - (if (get_global $error_type) + (if (global.get $error_type) (then (nop)) - (else (if (OR (i32.eq $res (get_global $NIL)) - (i32.eq $res (get_global $FALSE))) + (else (if (OR (i32.eq $res (global.get $NIL)) + (i32.eq $res (global.get $FALSE))) (then ($RELEASE $res) ;; if no false case (A3), return nil (if (i32.lt_u ($COUNT $ast) 4) (then - (set_local $res ($INC_REF (get_global $NIL))) + (local.set $res ($INC_REF (global.get $NIL))) (br $EVAL_return)) (else - (set_local $ast ($MAL_GET_A3 $ast))))) + (local.set $ast ($MAL_GET_A3 $ast))))) (else ($RELEASE $res) - (set_local $ast ($MAL_GET_A2 $ast)))))) + (local.set $ast ($MAL_GET_A2 $ast)))))) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "fn*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($MALFUNC $a2 $a1 $env)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($MALFUNC $a2 $a1 $env)) (br $EVAL_return)) (else ;; EVAL_INVOKE - (set_local $res ($EVAL_AST $ast $env 0)) - (set_local $f_args $res) + (local.set $res ($EVAL_AST $ast $env 0)) + (local.set $f_args $res) ;; if error, return f/args for release by caller - (if (get_global $error_type) + (if (global.get $error_type) (then - (set_local $res $f_args) + (local.set $res $f_args) (br $EVAL_return))) - (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest - (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value + (local.set $args ($MEM_VAL0_ptr $f_args)) ;; rest + (local.set $f ($MEM_VAL1_ptr $f_args)) ;; value - (set_local $ftype ($TYPE $f)) - (if (i32.eq $ftype (get_global $FUNCTION_T)) + (local.set $ftype ($TYPE $f)) + (if (i32.eq $ftype (global.get $FUNCTION_T)) (then (if (i32.eq ($VAL0 $f) 0) ;; eval (then - (set_local $res ($EVAL ($MEM_VAL1_ptr $args) - (get_global $repl_env)))) + (local.set $res ($EVAL ($MEM_VAL1_ptr $args) + (global.get $repl_env)))) (else - (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f))))) + (local.set $res (call_indirect (type $fnT) $args ($VAL0 $f))))) ;; release f/args ($RELEASE $f_args) (br $EVAL_return)) - (else (if (i32.eq $ftype (get_global $MALFUNC_T)) + (else (if (i32.eq $ftype (global.get $MALFUNC_T)) (then ;; save the current environment for release - (set_local $prev_env $env) + (local.set $prev_env $env) ;; create new environment using env and params stored in function - (set_local $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + (local.set $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) ($MEM_VAL1_ptr $f) $args)) ;; release previous environment if not the current EVAL env @@ -385,17 +385,17 @@ (if (i32.ne $prev_env $orig_env) (then ($RELEASE $prev_env) - (set_local $prev_env 0))) + (local.set $prev_env 0))) ;; claim the AST before releasing the list containing it - (set_local $ast ($MEM_VAL0_ptr $f)) + (local.set $ast ($MEM_VAL0_ptr $f)) (drop ($INC_REF $ast)) ;; if we have already been here via TCO, release previous ;; ast ;; PEND_A_LV (if $prev_ast ($RELEASE $prev_ast)) - (set_local $prev_ast $ast) + (local.set $prev_ast $ast) ;; release f/args ($RELEASE $f_args) @@ -403,7 +403,7 @@ (br $TCO_loop)) (else ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) - (set_local $res 0) + (local.set $res 0) ($RELEASE $f_args) (br $EVAL_return))))))))))))))))))))))) @@ -418,12 +418,12 @@ ;; TODO: needs to happen here so self-hosting doesn't leak (block $done (loop $loop - (br_if $done (i32.le_s (get_global $mac_stack_top) $orig_mac_stack_top)) + (br_if $done (i32.le_s (global.get $mac_stack_top) $orig_mac_stack_top)) ($RELEASE (i32.load (i32.add - (get_global $mac_stack) - (i32.mul (get_global $mac_stack_top) 4)))) - (set_global $mac_stack_top - (i32.sub (get_global $mac_stack_top) 1)) + (global.get $mac_stack) + (i32.mul (global.get $mac_stack_top) 4)))) + (global.set $mac_stack_top + (i32.sub (global.get $mac_stack_top) 1)) (br $loop) ) ) @@ -440,10 +440,10 @@ (func $RE (param $line i32 $env i32) (result i32) (LET $mv1 0 $res 0) (block $done - (set_local $mv1 ($READ $line)) - (br_if $done (get_global $error_type)) + (local.set $mv1 ($READ $line)) + (br_if $done (global.get $error_type)) - (set_local $res ($EVAL $mv1 $env)) + (local.set $res ($EVAL $mv1 $env)) ) ;; release memory from MAL_READ @@ -454,11 +454,11 @@ (func $REP (param $line i32 $env i32) (result i32) (LET $mv2 0 $ms 0) (block $done - (set_local $mv2 ($RE $line $env)) - (br_if $done (get_global $error_type)) + (local.set $mv2 ($RE $line $env)) + (br_if $done (global.get $error_type)) ;; ($PR_MEMORY -1 -1) - (set_local $ms ($PRINT $mv2)) + (local.set $ms ($PRINT $mv2)) ) ;; release memory from RE @@ -474,14 +474,14 @@ ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) -;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) -;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) -;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) -;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) -;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) +;; ($printf_1 "memoryBase: 0x%x\n" (global.get $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (global.get $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (global.get $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (global.get $mem)) +;; ($printf_1 "string_mem: %d\n" (global.get $string_mem)) - (set_global $repl_env ($ENV_NEW (get_global $NIL))) - (set_local $repl_env (get_global $repl_env)) + (global.set $repl_env ($ENV_NEW (global.get $NIL))) + (local.set $repl_env (global.get $repl_env)) ;; core.EXT: defined in wasm ($add_core_ns $repl_env) @@ -497,31 +497,31 @@ ;; Command line arguments - (set_local $res ($MAP_LOOP_START (get_global $LIST_T))) + (local.set $res ($MAP_LOOP_START (global.get $LIST_T))) ;; push MAP_LOP stack ;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (local.set $ret $res) + (local.set $current $res) + (local.set $empty $res) - (set_local $i 2) + (local.set $i 2) (block $done (loop $loop (br_if $done (i32.ge_u $i $argc)) - (set_local $val2 ($STRING (get_global $STRING_T) + (local.set $val2 ($STRING (global.get $STRING_T) (i32.load (i32.add $argv (i32.mul $i 4))))) ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE - (get_global $LIST_T) $empty $current $val2 0)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE + (global.get $LIST_T) $empty $current $val2 0)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) - (set_local $i (i32.add $i 1)) + (local.set $i (i32.add $i 1)) (br $loop) ) ) @@ -533,12 +533,12 @@ (if (i32.gt_u $argc 1) (then (drop ($ENV_SET_S $repl_env - "*FILE*" ($STRING (get_global $STRING_T) + "*FILE*" ($STRING (global.get $STRING_T) (i32.load (i32.add $argv 4))))) ($RELEASE ($RE "(load-file *FILE*)" $repl_env)) - (if (get_global $error_type) + (if (global.get $error_type) (then - ($printf_1 "Error: %s\n" (get_global $error_str)) + ($printf_1 "Error: %s\n" (global.get $error_str)) (return 1)) (else (return 0))))) @@ -548,18 +548,18 @@ (loop $repl_loop (br_if $repl_done (i32.eqz ($readline "user> " $line))) (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) - (set_local $res ($REP $line $repl_env)) - (if (get_global $error_type) + (local.set $res ($REP $line $repl_env)) + (if (global.get $error_type) (then - (if (i32.eq 2 (get_global $error_type)) + (if (i32.eq 2 (global.get $error_type)) (then - (set_local $ms ($pr_str (get_global $error_val) 1)) + (local.set $ms ($pr_str (global.get $error_val) 1)) ($printf_1 "Error: %s\n" ($to_String $ms)) ($RELEASE $ms) - ($RELEASE (get_global $error_val))) + ($RELEASE (global.get $error_val))) (else - ($printf_1 "Error: %s\n" (get_global $error_str)))) - (set_global $error_type 0)) + ($printf_1 "Error: %s\n" (global.get $error_str)))) + (global.set $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) diff --git a/wasm/step9_try.wam b/wasm/step9_try.wam index 312314b380..31567f1975 100644 --- a/wasm/step9_try.wam +++ b/wasm/step9_try.wam @@ -10,8 +10,8 @@ ;; EVAL (func $is_pair (param $ast i32) (result i32) (LET $type ($TYPE $ast)) - (AND (OR (i32.eq $type (get_global $LIST_T)) - (i32.eq $type (get_global $VECTOR_T))) + (AND (OR (i32.eq $type (global.get $LIST_T)) + (i32.eq $type (global.get $VECTOR_T))) (i32.ne ($VAL0 $ast) 0)) ) @@ -19,38 +19,38 @@ (LET $res 0 $sym 0 $second 0 $third 0) (if (i32.eqz ($is_pair $ast)) ;; QQ_QUOTE (then - (set_local $sym ($STRING (get_global $SYMBOL_T) "quote")) + (local.set $sym ($STRING (global.get $SYMBOL_T) "quote")) ;; ['quote ast] - (set_local $res ($LIST2 $sym $ast)) + (local.set $res ($LIST2 $sym $ast)) ($RELEASE $sym)) (else - (set_local $res ($MEM_VAL1_ptr $ast)) - (if (AND (i32.eq ($TYPE $res) (get_global $SYMBOL_T)) + (local.set $res ($MEM_VAL1_ptr $ast)) + (if (AND (i32.eq ($TYPE $res) (global.get $SYMBOL_T)) (i32.eqz ($strcmp "unquote" ($to_String $res)))) (then ;; ast[1] - (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))))) + (local.set $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))))) (else (if (AND ($is_pair $res) (i32.eq ($TYPE ($MEM_VAL1_ptr $res)) - (get_global $SYMBOL_T)) + (global.get $SYMBOL_T)) (i32.eqz ($strcmp "splice-unquote" ($to_String ($MEM_VAL1_ptr $res))))) (then ;; ['concat, ast[0][1], quasiquote(ast[1..])] - (set_local $sym ($STRING (get_global $SYMBOL_T) "concat")) - (set_local $second + (local.set $sym ($STRING (global.get $SYMBOL_T) "concat")) + (local.set $second ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL1_ptr $ast)))) - (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) - (set_local $res ($LIST3 $sym $second $third)) + (local.set $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (local.set $res ($LIST3 $sym $second $third)) ;; release inner quasiquoted since outer list take ownership ($RELEASE $third) ($RELEASE $sym)) (else ;; ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - (set_local $sym ($STRING (get_global $SYMBOL_T) "cons")) - (set_local $second ($QUASIQUOTE ($MEM_VAL1_ptr $ast))) - (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) - (set_local $res ($LIST3 $sym $second $third)) + (local.set $sym ($STRING (global.get $SYMBOL_T) "cons")) + (local.set $second ($QUASIQUOTE ($MEM_VAL1_ptr $ast))) + (local.set $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (local.set $res ($LIST3 $sym $second $third)) ;; release inner quasiquoted since outer list takes ownership ($RELEASE $third) ($RELEASE $second) @@ -65,35 +65,35 @@ (local $mac_env i64) (LET $ast $orig_ast $mac 0) - (set_global $mac_stack (STATIC_ARRAY 1024)) ;; 256 * 4, TODO: move to init + (global.set $mac_stack (STATIC_ARRAY 1024)) ;; 256 * 4, TODO: move to init (block $done (loop $loop (br_if $done - (OR (i32.ne ($TYPE $ast) (get_global $LIST_T)) ;; a list + (OR (i32.ne ($TYPE $ast) (global.get $LIST_T)) ;; a list (i32.eqz ($VAL0 $ast)) ;; non-empty (i32.ne ($TYPE ($MEM_VAL1_ptr $ast)) ;; leading symbol - (get_global $SYMBOL_T)))) - (set_local $mac_env ($ENV_FIND $env ($MEM_VAL1_ptr $ast))) - (set_local $mac (i32.wrap/i64 (i64.shr_u $mac_env (i64.const 32)))) - (br_if $done (OR (i32.eqz (i32.wrap/i64 $mac_env)) ;; defined in env + (global.get $SYMBOL_T)))) + (local.set $mac_env ($ENV_FIND $env ($MEM_VAL1_ptr $ast))) + (local.set $mac (i32.wrap_i64 (i64.shr_u $mac_env (i64.const 32)))) + (br_if $done (OR (i32.eqz (i32.wrap_i64 $mac_env)) ;; defined in env (i32.ne ($TYPE $mac) ;; a macro - (get_global $MACRO_T)))) + (global.get $MACRO_T)))) - (set_local $ast ($APPLY $mac ($MEM_VAL0_ptr $ast))) + (local.set $ast ($APPLY $mac ($MEM_VAL0_ptr $ast))) ;; PEND_A_LV ;; if ast is not the first ast that was passed in, then add it ;; to the pending release list. (if (i32.ne $ast $orig_ast) (then - (set_global $mac_stack_top - (i32.add (get_global $mac_stack_top) 1)) - (if (i32.ge_s (i32.mul (get_global $mac_stack_top) 4) 1024) ;; 256 * 4 + (global.set $mac_stack_top + (i32.add (global.get $mac_stack_top) 1)) + (if (i32.ge_s (i32.mul (global.get $mac_stack_top) 4) 1024) ;; 256 * 4 ($fatal 7 "Exhausted mac_stack!\n")) (i32.store (i32.add - (get_global $mac_stack) - (i32.mul (get_global $mac_stack_top) 4)) + (global.get $mac_stack) + (i32.mul (global.get $mac_stack_top) 4)) $ast))) - (br_if $done (get_global $error_type)) + (br_if $done (global.get $error_type)) (br $loop) ) @@ -105,8 +105,8 @@ (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 $ret 0 $empty 0 $current 0) - (if (get_global $error_type) (return 0)) - (set_local $type ($TYPE $ast)) + (if (global.get $error_type) (return 0)) + (local.set $type ($TYPE $ast)) ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) @@ -116,16 +116,16 @@ (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) ;; symbol ;; found/res returned as hi 32/lo 32 of i64 - (set_local $res ($ENV_GET $env $ast)) + (local.set $res ($ENV_GET $env $ast)) (br $done)) ;; list, vector, hashmap ;; MAP_LOOP_START - (set_local $res ($MAP_LOOP_START $type)) + (local.set $res ($MAP_LOOP_START $type)) ;; push MAP_LOOP stack ;;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (local.set $ret $res) + (local.set $current $res) + (local.set $empty $res) (block $done (loop $loop @@ -135,47 +135,47 @@ (if $skiplast (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast))))) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (local.set $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) (else - (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) - (set_local $val2 $res) + (local.set $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (local.set $val2 $res) ;; if error, release the unattached element - (if (get_global $error_type) + (if (global.get $error_type) (then ($RELEASE $res) - (set_local $res 0) + (local.set $res 0) (br $done))) ;; for hash-maps, copy the key (inc ref since we are going ;; to release it below) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $val3 $val2) - (set_local $val2 ($MEM_VAL1_ptr $ast)) + (local.set $val3 $val2) + (local.set $val2 ($MEM_VAL1_ptr $ast)) (drop ($INC_REF $val2)))) ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) - (set_local $ast ($MEM_VAL0_ptr $ast)) + (local.set $ast ($MEM_VAL0_ptr $ast)) (br $loop) ) ) ;; MAP_LOOP_DONE - (set_local $res $ret) + (local.set $res $ret) ;; EVAL_AST_RETURN: nothing to do (br $done)) ;; default - (set_local $res ($INC_REF $ast)) + (local.set $res ($INC_REF $ast)) ) $res @@ -191,7 +191,7 @@ (func $EVAL (param $orig_ast i32 $orig_env i32) (result i32) (LET $ast $orig_ast $env $orig_env - $orig_mac_stack_top (get_global $mac_stack_top) + $orig_mac_stack_top (global.get $mac_stack_top) $prev_ast 0 $prev_env 0 $res 0 $el 0 $ftype 0 $f_args 0 $f 0 $args 0 $a0 0 $a0sym 0 $a1 0 $a2 0 @@ -200,75 +200,75 @@ (block $EVAL_return (loop $TCO_loop - (set_local $f_args 0) - (set_local $f 0) - (set_local $args 0) + (local.set $f_args 0) + (local.set $f 0) + (local.set $args 0) - (if (get_global $error_type) + (if (global.get $error_type) (then - (set_local $res 0) + (local.set $res 0) (br $EVAL_return))) ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (if (i32.ne ($TYPE $ast) (global.get $LIST_T)) (then - (set_local $res ($EVAL_AST $ast $env 0)) + (local.set $res ($EVAL_AST $ast $env 0)) (br $EVAL_return))) ;; APPLY_LIST - (set_local $ast ($MACROEXPAND $ast $env)) + (local.set $ast ($MACROEXPAND $ast $env)) ;;($PR_VALUE ">>> >>> EVAL ast: '%s'\n" $ast) - (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (if (i32.ne ($TYPE $ast) (global.get $LIST_T)) (then - (set_local $res ($EVAL_AST $ast $env 0)) + (local.set $res ($EVAL_AST $ast $env 0)) (br $EVAL_return))) (if ($EMPTY_Q $ast) (then - (set_local $res ($INC_REF $ast)) + (local.set $res ($INC_REF $ast)) (br $EVAL_return))) - (set_local $a0 ($MEM_VAL1_ptr $ast)) - (set_local $a0sym "") - (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) - (set_local $a0sym ($to_String $a0))) + (local.set $a0 ($MEM_VAL1_ptr $ast)) + (local.set $a0sym "") + (if (i32.eq ($TYPE $a0) (global.get $SYMBOL_T)) + (local.set $a0sym ($to_String $a0))) (if (i32.eqz ($strcmp "def!" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($EVAL $a2 $env)) - (br_if $EVAL_return (get_global $error_type)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($EVAL $a2 $env)) + (br_if $EVAL_return (global.get $error_type)) ;; set a1 in env to a2 - (set_local $res ($ENV_SET $env $a1 $res)) + (local.set $res ($ENV_SET $env $a1 $res)) (br $EVAL_return)) (else (if (i32.eqz ($strcmp "let*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) ;; create new environment with outer as current environment - (set_local $prev_env $env) ;; save env for later release - (set_local $env ($ENV_NEW $env)) + (local.set $prev_env $env) ;; save env for later release + (local.set $env ($ENV_NEW $env)) (block $done (loop $loop (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element - (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) + (local.set $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) - (br_if $done (get_global $error_type)) + (br_if $done (global.get $error_type)) ;; set key/value in the let environment - (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) + (local.set $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) ;; release our use, ENV_SET took ownership ($RELEASE $res) ;; skip to the next pair of a1 elements - (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (local.set $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) (br $loop) ) ) @@ -277,54 +277,54 @@ (if (i32.ne $prev_env $orig_env) (then ($RELEASE $prev_env) - (set_local $prev_env 0))) + (local.set $prev_env 0))) - (set_local $ast $a2) + (local.set $ast $a2) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "do" $a0sym)) (then ;; EVAL the rest through second to last - (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) - (set_local $ast ($LAST $ast)) + (local.set $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) + (local.set $ast ($LAST $ast)) ($RELEASE $ast) ;; we already own it via ast ($RELEASE $el) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "quote" $a0sym)) (then - (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + (local.set $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) (br $EVAL_return)) (else (if (i32.eqz ($strcmp "quasiquote" $a0sym)) (then - (set_local $ast ($QUASIQUOTE ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + (local.set $ast ($QUASIQUOTE ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) ;; if we have already been here via TCO, release previous ast (if $prev_ast ($RELEASE $prev_ast)) - (set_local $prev_ast $ast) + (local.set $prev_ast $ast) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "defmacro!" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($EVAL $a2 $env)) - ($SET_TYPE $res (get_global $MACRO_T)) - (br_if $EVAL_return (get_global $error_type)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($EVAL $a2 $env)) + ($SET_TYPE $res (global.get $MACRO_T)) + (br_if $EVAL_return (global.get $error_type)) ;; set a1 in env to a2 - (set_local $res ($ENV_SET $env $a1 $res)) + (local.set $res ($ENV_SET $env $a1 $res)) (br $EVAL_return)) (else (if (i32.eqz ($strcmp "macroexpand" $a0sym)) (then ;; since we are returning it unevaluated, inc the ref cnt - (set_local $res ($INC_REF ($MACROEXPAND + (local.set $res ($INC_REF ($MACROEXPAND ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)) $env)))) (else (if (i32.eqz ($strcmp "try*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $res ($EVAL $a1 $env)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $res ($EVAL $a1 $env)) ;; if there is no error, return - (br_if $EVAL_return (i32.eqz (get_global $error_type))) + (br_if $EVAL_return (i32.eqz (global.get $error_type))) ;; if there is an error and res is set, we need to free it ($RELEASE $res) ;; if there is no catch block then return @@ -332,98 +332,98 @@ (i32.eqz ($VAL0 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) ;; save the current environment for release - (set_local $prev_env $env) + (local.set $prev_env $env) ;; create environment for the catch block eval - (set_local $env ($ENV_NEW $env)) + (local.set $env ($ENV_NEW $env)) ;; set a1 and a2 from the catch block - (set_local $a1 ($MAL_GET_A1 ($MAL_GET_A2 $ast))) - (set_local $a2 ($MAL_GET_A2 ($MAL_GET_A2 $ast))) + (local.set $a1 ($MAL_GET_A1 ($MAL_GET_A2 $ast))) + (local.set $a2 ($MAL_GET_A2 ($MAL_GET_A2 $ast))) ;; create object for string errors - (if (i32.eq (get_global $error_type) 1) + (if (i32.eq (global.get $error_type) 1) (then - (set_local $err ($STRING (get_global $STRING_T) - (get_global $error_str)))) + (local.set $err ($STRING (global.get $STRING_T) + (global.get $error_str)))) (else - (set_local $err (get_global $error_val)))) + (local.set $err (global.get $error_val)))) ;; bind the catch symbol to the error object (drop ($ENV_SET $env $a1 $err)) ;; release our use, env took ownership ($RELEASE $err) ;; unset error for catch eval - (set_global $error_type 0) - (i32.store (get_global $error_str) (CHR "\x00")) + (global.set $error_type 0) + (i32.store (global.get $error_str) (CHR "\x00")) ;; release previous environment if not the current EVAL env (if (i32.ne $prev_env $orig_env) (then ($RELEASE $prev_env) - (set_local $prev_env 0))) + (local.set $prev_env 0))) - (set_local $ast $a2) + (local.set $ast $a2) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "if" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $res ($EVAL $a1 $env)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $res ($EVAL $a1 $env)) - (if (get_global $error_type) + (if (global.get $error_type) (then (nop)) - (else (if (OR (i32.eq $res (get_global $NIL)) - (i32.eq $res (get_global $FALSE))) + (else (if (OR (i32.eq $res (global.get $NIL)) + (i32.eq $res (global.get $FALSE))) (then ($RELEASE $res) ;; if no false case (A3), return nil (if (i32.lt_u ($COUNT $ast) 4) (then - (set_local $res ($INC_REF (get_global $NIL))) + (local.set $res ($INC_REF (global.get $NIL))) (br $EVAL_return)) (else - (set_local $ast ($MAL_GET_A3 $ast))))) + (local.set $ast ($MAL_GET_A3 $ast))))) (else ($RELEASE $res) - (set_local $ast ($MAL_GET_A2 $ast)))))) + (local.set $ast ($MAL_GET_A2 $ast)))))) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "fn*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($MALFUNC $a2 $a1 $env)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($MALFUNC $a2 $a1 $env)) (br $EVAL_return)) (else ;; EVAL_INVOKE - (set_local $res ($EVAL_AST $ast $env 0)) - (set_local $f_args $res) + (local.set $res ($EVAL_AST $ast $env 0)) + (local.set $f_args $res) ;; if error, return f/args for release by caller - (if (get_global $error_type) + (if (global.get $error_type) (then - (set_local $res $f_args) + (local.set $res $f_args) (br $EVAL_return))) - (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest - (set_local $f ($MEM_VAL1_ptr $f_args)) ;; value + (local.set $args ($MEM_VAL0_ptr $f_args)) ;; rest + (local.set $f ($MEM_VAL1_ptr $f_args)) ;; value - (set_local $ftype ($TYPE $f)) - (if (i32.eq $ftype (get_global $FUNCTION_T)) + (local.set $ftype ($TYPE $f)) + (if (i32.eq $ftype (global.get $FUNCTION_T)) (then (if (i32.eq ($VAL0 $f) 0) ;; eval (then - (set_local $res ($EVAL ($MEM_VAL1_ptr $args) - (get_global $repl_env)))) + (local.set $res ($EVAL ($MEM_VAL1_ptr $args) + (global.get $repl_env)))) (else - (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f))))) + (local.set $res (call_indirect (type $fnT) $args ($VAL0 $f))))) ;; release f/args ($RELEASE $f_args) (br $EVAL_return)) - (else (if (i32.eq $ftype (get_global $MALFUNC_T)) + (else (if (i32.eq $ftype (global.get $MALFUNC_T)) (then ;; save the current environment for release - (set_local $prev_env $env) + (local.set $prev_env $env) ;; create new environment using env and params stored in function - (set_local $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + (local.set $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) ($MEM_VAL1_ptr $f) $args)) ;; release previous environment if not the current EVAL env @@ -432,17 +432,17 @@ (if (i32.ne $prev_env $orig_env) (then ($RELEASE $prev_env) - (set_local $prev_env 0))) + (local.set $prev_env 0))) ;; claim the AST before releasing the list containing it - (set_local $ast ($MEM_VAL0_ptr $f)) + (local.set $ast ($MEM_VAL0_ptr $f)) (drop ($INC_REF $ast)) ;; if we have already been here via TCO, release previous ;; ast ;; PEND_A_LV (if $prev_ast ($RELEASE $prev_ast)) - (set_local $prev_ast $ast) + (local.set $prev_ast $ast) ;; release f/args ($RELEASE $f_args) @@ -450,7 +450,7 @@ (br $TCO_loop)) (else ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) - (set_local $res 0) + (local.set $res 0) ($RELEASE $f_args) (br $EVAL_return))))))))))))))))))))))))) @@ -465,12 +465,12 @@ ;; TODO: needs to happen here so self-hosting doesn't leak (block $done (loop $loop - (br_if $done (i32.le_s (get_global $mac_stack_top) $orig_mac_stack_top)) + (br_if $done (i32.le_s (global.get $mac_stack_top) $orig_mac_stack_top)) ($RELEASE (i32.load (i32.add - (get_global $mac_stack) - (i32.mul (get_global $mac_stack_top) 4)))) - (set_global $mac_stack_top - (i32.sub (get_global $mac_stack_top) 1)) + (global.get $mac_stack) + (i32.mul (global.get $mac_stack_top) 4)))) + (global.set $mac_stack_top + (i32.sub (global.get $mac_stack_top) 1)) (br $loop) ) ) @@ -487,10 +487,10 @@ (func $RE (param $line i32 $env i32) (result i32) (LET $mv1 0 $res 0) (block $done - (set_local $mv1 ($READ $line)) - (br_if $done (get_global $error_type)) + (local.set $mv1 ($READ $line)) + (br_if $done (global.get $error_type)) - (set_local $res ($EVAL $mv1 $env)) + (local.set $res ($EVAL $mv1 $env)) ) ;; release memory from MAL_READ @@ -501,11 +501,11 @@ (func $REP (param $line i32 $env i32) (result i32) (LET $mv2 0 $ms 0) (block $done - (set_local $mv2 ($RE $line $env)) - (br_if $done (get_global $error_type)) + (local.set $mv2 ($RE $line $env)) + (br_if $done (global.get $error_type)) ;; ($PR_MEMORY -1 -1) - (set_local $ms ($PRINT $mv2)) + (local.set $ms ($PRINT $mv2)) ) ;; release memory from RE @@ -521,14 +521,14 @@ ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) -;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) -;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) -;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) -;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) -;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) +;; ($printf_1 "memoryBase: 0x%x\n" (global.get $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (global.get $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (global.get $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (global.get $mem)) +;; ($printf_1 "string_mem: %d\n" (global.get $string_mem)) - (set_global $repl_env ($ENV_NEW (get_global $NIL))) - (set_local $repl_env (get_global $repl_env)) + (global.set $repl_env ($ENV_NEW (global.get $NIL))) + (local.set $repl_env (global.get $repl_env)) ;; core.EXT: defined in wasm ($add_core_ns $repl_env) @@ -544,31 +544,31 @@ ;; Command line arguments - (set_local $res ($MAP_LOOP_START (get_global $LIST_T))) + (local.set $res ($MAP_LOOP_START (global.get $LIST_T))) ;; push MAP_LOP stack ;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (local.set $ret $res) + (local.set $current $res) + (local.set $empty $res) - (set_local $i 2) + (local.set $i 2) (block $done (loop $loop (br_if $done (i32.ge_u $i $argc)) - (set_local $val2 ($STRING (get_global $STRING_T) + (local.set $val2 ($STRING (global.get $STRING_T) (i32.load (i32.add $argv (i32.mul $i 4))))) ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE - (get_global $LIST_T) $empty $current $val2 0)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE + (global.get $LIST_T) $empty $current $val2 0)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) - (set_local $i (i32.add $i 1)) + (local.set $i (i32.add $i 1)) (br $loop) ) ) @@ -580,12 +580,12 @@ (if (i32.gt_u $argc 1) (then (drop ($ENV_SET_S $repl_env - "*FILE*" ($STRING (get_global $STRING_T) + "*FILE*" ($STRING (global.get $STRING_T) (i32.load (i32.add $argv 4))))) ($RELEASE ($RE "(load-file *FILE*)" $repl_env)) - (if (get_global $error_type) + (if (global.get $error_type) (then - ($printf_1 "Error: %s\n" (get_global $error_str)) + ($printf_1 "Error: %s\n" (global.get $error_str)) (return 1)) (else (return 0))))) @@ -595,18 +595,18 @@ (loop $repl_loop (br_if $repl_done (i32.eqz ($readline "user> " $line))) (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) - (set_local $res ($REP $line $repl_env)) - (if (get_global $error_type) + (local.set $res ($REP $line $repl_env)) + (if (global.get $error_type) (then - (if (i32.eq 2 (get_global $error_type)) + (if (i32.eq 2 (global.get $error_type)) (then - (set_local $ms ($pr_str (get_global $error_val) 1)) + (local.set $ms ($pr_str (global.get $error_val) 1)) ($printf_1 "Error: %s\n" ($to_String $ms)) ($RELEASE $ms) - ($RELEASE (get_global $error_val))) + ($RELEASE (global.get $error_val))) (else - ($printf_1 "Error: %s\n" (get_global $error_str)))) - (set_global $error_type 0)) + ($printf_1 "Error: %s\n" (global.get $error_str)))) + (global.set $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) diff --git a/wasm/stepA_mal.wam b/wasm/stepA_mal.wam index f1bed3d709..343762006d 100644 --- a/wasm/stepA_mal.wam +++ b/wasm/stepA_mal.wam @@ -10,8 +10,8 @@ ;; EVAL (func $is_pair (param $ast i32) (result i32) (LET $type ($TYPE $ast)) - (AND (OR (i32.eq $type (get_global $LIST_T)) - (i32.eq $type (get_global $VECTOR_T))) + (AND (OR (i32.eq $type (global.get $LIST_T)) + (i32.eq $type (global.get $VECTOR_T))) (i32.ne ($VAL0 $ast) 0)) ) @@ -19,38 +19,38 @@ (LET $res 0 $sym 0 $second 0 $third 0) (if (i32.eqz ($is_pair $ast)) ;; QQ_QUOTE (then - (set_local $sym ($STRING (get_global $SYMBOL_T) "quote")) + (local.set $sym ($STRING (global.get $SYMBOL_T) "quote")) ;; ['quote ast] - (set_local $res ($LIST2 $sym $ast)) + (local.set $res ($LIST2 $sym $ast)) ($RELEASE $sym)) (else - (set_local $res ($MEM_VAL1_ptr $ast)) - (if (AND (i32.eq ($TYPE $res) (get_global $SYMBOL_T)) + (local.set $res ($MEM_VAL1_ptr $ast)) + (if (AND (i32.eq ($TYPE $res) (global.get $SYMBOL_T)) (i32.eqz ($strcmp "unquote" ($to_String $res)))) (then ;; ast[1] - (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))))) + (local.set $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast))))) (else (if (AND ($is_pair $res) (i32.eq ($TYPE ($MEM_VAL1_ptr $res)) - (get_global $SYMBOL_T)) + (global.get $SYMBOL_T)) (i32.eqz ($strcmp "splice-unquote" ($to_String ($MEM_VAL1_ptr $res))))) (then ;; ['concat, ast[0][1], quasiquote(ast[1..])] - (set_local $sym ($STRING (get_global $SYMBOL_T) "concat")) - (set_local $second + (local.set $sym ($STRING (global.get $SYMBOL_T) "concat")) + (local.set $second ($MEM_VAL1_ptr ($MEM_VAL0_ptr ($MEM_VAL1_ptr $ast)))) - (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) - (set_local $res ($LIST3 $sym $second $third)) + (local.set $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (local.set $res ($LIST3 $sym $second $third)) ;; release inner quasiquoted since outer list take ownership ($RELEASE $third) ($RELEASE $sym)) (else ;; ['cons, quasiquote(ast[0]), quasiquote(ast[1..])] - (set_local $sym ($STRING (get_global $SYMBOL_T) "cons")) - (set_local $second ($QUASIQUOTE ($MEM_VAL1_ptr $ast))) - (set_local $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) - (set_local $res ($LIST3 $sym $second $third)) + (local.set $sym ($STRING (global.get $SYMBOL_T) "cons")) + (local.set $second ($QUASIQUOTE ($MEM_VAL1_ptr $ast))) + (local.set $third ($QUASIQUOTE ($MEM_VAL0_ptr $ast))) + (local.set $res ($LIST3 $sym $second $third)) ;; release inner quasiquoted since outer list takes ownership ($RELEASE $third) ($RELEASE $second) @@ -65,35 +65,35 @@ (local $mac_env i64) (LET $ast $orig_ast $mac 0) - (set_global $mac_stack (STATIC_ARRAY 1024)) ;; 256 * 4, TODO: move to init + (global.set $mac_stack (STATIC_ARRAY 1024)) ;; 256 * 4, TODO: move to init (block $done (loop $loop (br_if $done - (OR (i32.ne ($TYPE $ast) (get_global $LIST_T)) ;; a list + (OR (i32.ne ($TYPE $ast) (global.get $LIST_T)) ;; a list (i32.eqz ($VAL0 $ast)) ;; non-empty (i32.ne ($TYPE ($MEM_VAL1_ptr $ast)) ;; leading symbol - (get_global $SYMBOL_T)))) - (set_local $mac_env ($ENV_FIND $env ($MEM_VAL1_ptr $ast))) - (set_local $mac (i32.wrap/i64 (i64.shr_u $mac_env (i64.const 32)))) - (br_if $done (OR (i32.eqz (i32.wrap/i64 $mac_env)) ;; defined in env + (global.get $SYMBOL_T)))) + (local.set $mac_env ($ENV_FIND $env ($MEM_VAL1_ptr $ast))) + (local.set $mac (i32.wrap_i64 (i64.shr_u $mac_env (i64.const 32)))) + (br_if $done (OR (i32.eqz (i32.wrap_i64 $mac_env)) ;; defined in env (i32.ne ($TYPE $mac) ;; a macro - (get_global $MACRO_T)))) + (global.get $MACRO_T)))) - (set_local $ast ($APPLY $mac ($MEM_VAL0_ptr $ast))) + (local.set $ast ($APPLY $mac ($MEM_VAL0_ptr $ast))) ;; PEND_A_LV ;; if ast is not the first ast that was passed in, then add it ;; to the pending release list. (if (i32.ne $ast $orig_ast) (then - (set_global $mac_stack_top - (i32.add (get_global $mac_stack_top) 1)) - (if (i32.ge_s (i32.mul (get_global $mac_stack_top) 4) 1024) ;; 256 * 4 + (global.set $mac_stack_top + (i32.add (global.get $mac_stack_top) 1)) + (if (i32.ge_s (i32.mul (global.get $mac_stack_top) 4) 1024) ;; 256 * 4 ($fatal 7 "Exhausted mac_stack!\n")) (i32.store (i32.add - (get_global $mac_stack) - (i32.mul (get_global $mac_stack_top) 4)) + (global.get $mac_stack) + (i32.mul (global.get $mac_stack_top) 4)) $ast))) - (br_if $done (get_global $error_type)) + (br_if $done (global.get $error_type)) (br $loop) ) @@ -105,8 +105,8 @@ (LET $res 0 $val2 0 $val3 0 $type 0 $found 0 $ret 0 $empty 0 $current 0) - (if (get_global $error_type) (return 0)) - (set_local $type ($TYPE $ast)) + (if (global.get $error_type) (return 0)) + (local.set $type ($TYPE $ast)) ;;($PR_VALUE ">>> EVAL_AST ast: '%s'\n" $ast) @@ -116,16 +116,16 @@ (br_table 2 2 2 2 2 0 1 1 1 2 2 2 2 2 2 2 $type)) ;; symbol ;; found/res returned as hi 32/lo 32 of i64 - (set_local $res ($ENV_GET $env $ast)) + (local.set $res ($ENV_GET $env $ast)) (br $done)) ;; list, vector, hashmap ;; MAP_LOOP_START - (set_local $res ($MAP_LOOP_START $type)) + (local.set $res ($MAP_LOOP_START $type)) ;; push MAP_LOOP stack ;;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (local.set $ret $res) + (local.set $current $res) + (local.set $empty $res) (block $done (loop $loop @@ -135,47 +135,47 @@ (if $skiplast (br_if $done (i32.eqz ($VAL0 ($MEM_VAL0_ptr $ast))))) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) + (local.set $res ($EVAL ($MEM_VAL2_ptr $ast) $env))) (else - (set_local $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) - (set_local $val2 $res) + (local.set $res ($EVAL ($MEM_VAL1_ptr $ast) $env)))) + (local.set $val2 $res) ;; if error, release the unattached element - (if (get_global $error_type) + (if (global.get $error_type) (then ($RELEASE $res) - (set_local $res 0) + (local.set $res 0) (br $done))) ;; for hash-maps, copy the key (inc ref since we are going ;; to release it below) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) (then - (set_local $val3 $val2) - (set_local $val2 ($MEM_VAL1_ptr $ast)) + (local.set $val3 $val2) + (local.set $val2 ($MEM_VAL1_ptr $ast)) (drop ($INC_REF $val2)))) ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE $type $empty $current $val2 $val3)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) - (set_local $ast ($MEM_VAL0_ptr $ast)) + (local.set $ast ($MEM_VAL0_ptr $ast)) (br $loop) ) ) ;; MAP_LOOP_DONE - (set_local $res $ret) + (local.set $res $ret) ;; EVAL_AST_RETURN: nothing to do (br $done)) ;; default - (set_local $res ($INC_REF $ast)) + (local.set $res ($INC_REF $ast)) ) $res @@ -191,7 +191,7 @@ (func $EVAL (param $orig_ast i32 $orig_env i32) (result i32) (LET $ast $orig_ast $env $orig_env - $orig_mac_stack_top (get_global $mac_stack_top) + $orig_mac_stack_top (global.get $mac_stack_top) $prev_ast 0 $prev_env 0 $res 0 $el 0 $ftype 0 $f_args 0 $f 0 $args 0 $a0 0 $a0sym 0 $a1 0 $a2 0 @@ -200,75 +200,75 @@ (block $EVAL_return (loop $TCO_loop - (set_local $f_args 0) - (set_local $f 0) - (set_local $args 0) + (local.set $f_args 0) + (local.set $f 0) + (local.set $args 0) - (if (get_global $error_type) + (if (global.get $error_type) (then - (set_local $res 0) + (local.set $res 0) (br $EVAL_return))) ;;($PR_VALUE ">>> EVAL ast: '%s'\n" $ast) - (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (if (i32.ne ($TYPE $ast) (global.get $LIST_T)) (then - (set_local $res ($EVAL_AST $ast $env 0)) + (local.set $res ($EVAL_AST $ast $env 0)) (br $EVAL_return))) ;; APPLY_LIST - (set_local $ast ($MACROEXPAND $ast $env)) + (local.set $ast ($MACROEXPAND $ast $env)) ;;($PR_VALUE ">>> >>> EVAL ast: '%s'\n" $ast) - (if (i32.ne ($TYPE $ast) (get_global $LIST_T)) + (if (i32.ne ($TYPE $ast) (global.get $LIST_T)) (then - (set_local $res ($EVAL_AST $ast $env 0)) + (local.set $res ($EVAL_AST $ast $env 0)) (br $EVAL_return))) (if ($EMPTY_Q $ast) (then - (set_local $res ($INC_REF $ast)) + (local.set $res ($INC_REF $ast)) (br $EVAL_return))) - (set_local $a0 ($MEM_VAL1_ptr $ast)) - (set_local $a0sym "") - (if (i32.eq ($TYPE $a0) (get_global $SYMBOL_T)) - (set_local $a0sym ($to_String $a0))) + (local.set $a0 ($MEM_VAL1_ptr $ast)) + (local.set $a0sym "") + (if (i32.eq ($TYPE $a0) (global.get $SYMBOL_T)) + (local.set $a0sym ($to_String $a0))) (if (i32.eqz ($strcmp "def!" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($EVAL $a2 $env)) - (br_if $EVAL_return (get_global $error_type)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($EVAL $a2 $env)) + (br_if $EVAL_return (global.get $error_type)) ;; set a1 in env to a2 - (set_local $res ($ENV_SET $env $a1 $res)) + (local.set $res ($ENV_SET $env $a1 $res)) (br $EVAL_return)) (else (if (i32.eqz ($strcmp "let*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) ;; create new environment with outer as current environment - (set_local $prev_env $env) ;; save env for later release - (set_local $env ($ENV_NEW $env)) + (local.set $prev_env $env) ;; save env for later release + (local.set $env ($ENV_NEW $env)) (block $done (loop $loop (br_if $done (i32.eqz ($VAL0 $a1))) ;; eval current A1 odd element - (set_local $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) + (local.set $res ($EVAL ($MEM_VAL1_ptr ($MEM_VAL0_ptr $a1)) $env)) - (br_if $done (get_global $error_type)) + (br_if $done (global.get $error_type)) ;; set key/value in the let environment - (set_local $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) + (local.set $res ($ENV_SET $env ($MEM_VAL1_ptr $a1) $res)) ;; release our use, ENV_SET took ownership ($RELEASE $res) ;; skip to the next pair of a1 elements - (set_local $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) + (local.set $a1 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $a1))) (br $loop) ) ) @@ -277,54 +277,54 @@ (if (i32.ne $prev_env $orig_env) (then ($RELEASE $prev_env) - (set_local $prev_env 0))) + (local.set $prev_env 0))) - (set_local $ast $a2) + (local.set $ast $a2) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "do" $a0sym)) (then ;; EVAL the rest through second to last - (set_local $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) - (set_local $ast ($LAST $ast)) + (local.set $el ($EVAL_AST ($MEM_VAL0_ptr $ast) $env 1)) + (local.set $ast ($LAST $ast)) ($RELEASE $ast) ;; we already own it via ast ($RELEASE $el) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "quote" $a0sym)) (then - (set_local $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + (local.set $res ($INC_REF ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) (br $EVAL_return)) (else (if (i32.eqz ($strcmp "quasiquote" $a0sym)) (then - (set_local $ast ($QUASIQUOTE ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) + (local.set $ast ($QUASIQUOTE ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)))) ;; if we have already been here via TCO, release previous ast (if $prev_ast ($RELEASE $prev_ast)) - (set_local $prev_ast $ast) + (local.set $prev_ast $ast) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "defmacro!" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($EVAL $a2 $env)) - ($SET_TYPE $res (get_global $MACRO_T)) - (br_if $EVAL_return (get_global $error_type)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($EVAL $a2 $env)) + ($SET_TYPE $res (global.get $MACRO_T)) + (br_if $EVAL_return (global.get $error_type)) ;; set a1 in env to a2 - (set_local $res ($ENV_SET $env $a1 $res)) + (local.set $res ($ENV_SET $env $a1 $res)) (br $EVAL_return)) (else (if (i32.eqz ($strcmp "macroexpand" $a0sym)) (then ;; since we are returning it unevaluated, inc the ref cnt - (set_local $res ($INC_REF ($MACROEXPAND + (local.set $res ($INC_REF ($MACROEXPAND ($MEM_VAL1_ptr ($MEM_VAL0_ptr $ast)) $env)))) (else (if (i32.eqz ($strcmp "try*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $res ($EVAL $a1 $env)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $res ($EVAL $a1 $env)) ;; if there is no error, return - (br_if $EVAL_return (i32.eqz (get_global $error_type))) + (br_if $EVAL_return (i32.eqz (global.get $error_type))) ;; if there is an error and res is set, we need to free it ($RELEASE $res) ;; if there is no catch block then return @@ -332,98 +332,98 @@ (i32.eqz ($VAL0 ($MEM_VAL0_ptr ($MEM_VAL0_ptr $ast))))) ;; save the current environment for release - (set_local $prev_env $env) + (local.set $prev_env $env) ;; create environment for the catch block eval - (set_local $env ($ENV_NEW $env)) + (local.set $env ($ENV_NEW $env)) ;; set a1 and a2 from the catch block - (set_local $a1 ($MAL_GET_A1 ($MAL_GET_A2 $ast))) - (set_local $a2 ($MAL_GET_A2 ($MAL_GET_A2 $ast))) + (local.set $a1 ($MAL_GET_A1 ($MAL_GET_A2 $ast))) + (local.set $a2 ($MAL_GET_A2 ($MAL_GET_A2 $ast))) ;; create object for string errors - (if (i32.eq (get_global $error_type) 1) + (if (i32.eq (global.get $error_type) 1) (then - (set_local $err ($STRING (get_global $STRING_T) - (get_global $error_str)))) + (local.set $err ($STRING (global.get $STRING_T) + (global.get $error_str)))) (else - (set_local $err (get_global $error_val)))) + (local.set $err (global.get $error_val)))) ;; bind the catch symbol to the error object (drop ($ENV_SET $env $a1 $err)) ;; release our use, env took ownership ($RELEASE $err) ;; unset error for catch eval - (set_global $error_type 0) - (i32.store (get_global $error_str) (CHR "\x00")) + (global.set $error_type 0) + (i32.store (global.get $error_str) (CHR "\x00")) ;; release previous environment if not the current EVAL env (if (i32.ne $prev_env $orig_env) (then ($RELEASE $prev_env) - (set_local $prev_env 0))) + (local.set $prev_env 0))) - (set_local $ast $a2) + (local.set $ast $a2) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "if" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $res ($EVAL $a1 $env)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $res ($EVAL $a1 $env)) - (if (get_global $error_type) + (if (global.get $error_type) (then (nop)) - (else (if (OR (i32.eq $res (get_global $NIL)) - (i32.eq $res (get_global $FALSE))) + (else (if (OR (i32.eq $res (global.get $NIL)) + (i32.eq $res (global.get $FALSE))) (then ($RELEASE $res) ;; if no false case (A3), return nil (if (i32.lt_u ($COUNT $ast) 4) (then - (set_local $res ($INC_REF (get_global $NIL))) + (local.set $res ($INC_REF (global.get $NIL))) (br $EVAL_return)) (else - (set_local $ast ($MAL_GET_A3 $ast))))) + (local.set $ast ($MAL_GET_A3 $ast))))) (else ($RELEASE $res) - (set_local $ast ($MAL_GET_A2 $ast)))))) + (local.set $ast ($MAL_GET_A2 $ast)))))) (br $TCO_loop)) (else (if (i32.eqz ($strcmp "fn*" $a0sym)) (then - (set_local $a1 ($MAL_GET_A1 $ast)) - (set_local $a2 ($MAL_GET_A2 $ast)) - (set_local $res ($MALFUNC $a2 $a1 $env)) + (local.set $a1 ($MAL_GET_A1 $ast)) + (local.set $a2 ($MAL_GET_A2 $ast)) + (local.set $res ($MALFUNC $a2 $a1 $env)) (br $EVAL_return)) (else ;; EVAL_INVOKE - (set_local $res ($EVAL_AST $ast $env 0)) - (set_local $f_args $res) + (local.set $res ($EVAL_AST $ast $env 0)) + (local.set $f_args $res) ;; if error, return f/args for release by caller - (if (get_global $error_type) + (if (global.get $error_type) (then - (set_local $res $f_args) + (local.set $res $f_args) (br $EVAL_return))) - (set_local $args ($MEM_VAL0_ptr $f_args)) ;; rest - (set_local $f ($DEREF_META ($MEM_VAL1_ptr $f_args))) ;; value + (local.set $args ($MEM_VAL0_ptr $f_args)) ;; rest + (local.set $f ($DEREF_META ($MEM_VAL1_ptr $f_args))) ;; value - (set_local $ftype ($TYPE $f)) - (if (i32.eq $ftype (get_global $FUNCTION_T)) + (local.set $ftype ($TYPE $f)) + (if (i32.eq $ftype (global.get $FUNCTION_T)) (then (if (i32.eq ($VAL0 $f) 0) ;; eval (then - (set_local $res ($EVAL ($MEM_VAL1_ptr $args) - (get_global $repl_env)))) + (local.set $res ($EVAL ($MEM_VAL1_ptr $args) + (global.get $repl_env)))) (else - (set_local $res (call_indirect (type $fnT) $args ($VAL0 $f))))) + (local.set $res (call_indirect (type $fnT) $args ($VAL0 $f))))) ;; release f/args ($RELEASE $f_args) (br $EVAL_return)) - (else (if (i32.eq $ftype (get_global $MALFUNC_T)) + (else (if (i32.eq $ftype (global.get $MALFUNC_T)) (then ;; save the current environment for release - (set_local $prev_env $env) + (local.set $prev_env $env) ;; create new environment using env and params stored in function - (set_local $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) + (local.set $env ($ENV_NEW_BINDS ($MEM_VAL2_ptr $f) ($MEM_VAL1_ptr $f) $args)) ;; release previous environment if not the current EVAL env @@ -432,17 +432,17 @@ (if (i32.ne $prev_env $orig_env) (then ($RELEASE $prev_env) - (set_local $prev_env 0))) + (local.set $prev_env 0))) ;; claim the AST before releasing the list containing it - (set_local $ast ($MEM_VAL0_ptr $f)) + (local.set $ast ($MEM_VAL0_ptr $f)) (drop ($INC_REF $ast)) ;; if we have already been here via TCO, release previous ;; ast ;; PEND_A_LV (if $prev_ast ($RELEASE $prev_ast)) - (set_local $prev_ast $ast) + (local.set $prev_ast $ast) ;; release f/args ($RELEASE $f_args) @@ -450,7 +450,7 @@ (br $TCO_loop)) (else ($THROW_STR_1 "apply of non-function type: %d\n" $ftype) - (set_local $res 0) + (local.set $res 0) ($RELEASE $f_args) (br $EVAL_return))))))))))))))))))))))))) @@ -465,12 +465,12 @@ ;; TODO: needs to happen here so self-hosting doesn't leak (block $done (loop $loop - (br_if $done (i32.le_s (get_global $mac_stack_top) $orig_mac_stack_top)) + (br_if $done (i32.le_s (global.get $mac_stack_top) $orig_mac_stack_top)) ($RELEASE (i32.load (i32.add - (get_global $mac_stack) - (i32.mul (get_global $mac_stack_top) 4)))) - (set_global $mac_stack_top - (i32.sub (get_global $mac_stack_top) 1)) + (global.get $mac_stack) + (i32.mul (global.get $mac_stack_top) 4)))) + (global.set $mac_stack_top + (i32.sub (global.get $mac_stack_top) 1)) (br $loop) ) ) @@ -487,10 +487,10 @@ (func $RE (param $line i32 $env i32) (result i32) (LET $mv1 0 $res 0) (block $done - (set_local $mv1 ($READ $line)) - (br_if $done (get_global $error_type)) + (local.set $mv1 ($READ $line)) + (br_if $done (global.get $error_type)) - (set_local $res ($EVAL $mv1 $env)) + (local.set $res ($EVAL $mv1 $env)) ) ;; release memory from MAL_READ @@ -501,11 +501,11 @@ (func $REP (param $line i32 $env i32) (result i32) (LET $mv2 0 $ms 0) (block $done - (set_local $mv2 ($RE $line $env)) - (br_if $done (get_global $error_type)) + (local.set $mv2 ($RE $line $env)) + (br_if $done (global.get $error_type)) ;; ($PR_MEMORY -1 -1) - (set_local $ms ($PRINT $mv2)) + (local.set $ms ($PRINT $mv2)) ) ;; release memory from RE @@ -521,14 +521,14 @@ ;; DEBUG ;; ($printf_1 "argc: 0x%x\n" $argc) -;; ($printf_1 "memoryBase: 0x%x\n" (get_global $memoryBase)) -;; ($printf_1 "heap_start: 0x%x\n" (get_global $heap_start)) -;; ($printf_1 "heap_end: 0x%x\n" (get_global $heap_end)) -;; ($printf_1 "mem: 0x%x\n" (get_global $mem)) -;; ($printf_1 "string_mem: %d\n" (get_global $string_mem)) +;; ($printf_1 "memoryBase: 0x%x\n" (global.get $memoryBase)) +;; ($printf_1 "heap_start: 0x%x\n" (global.get $heap_start)) +;; ($printf_1 "heap_end: 0x%x\n" (global.get $heap_end)) +;; ($printf_1 "mem: 0x%x\n" (global.get $mem)) +;; ($printf_1 "string_mem: %d\n" (global.get $string_mem)) - (set_global $repl_env ($ENV_NEW (get_global $NIL))) - (set_local $repl_env (get_global $repl_env)) + (global.set $repl_env ($ENV_NEW (global.get $NIL))) + (local.set $repl_env (global.get $repl_env)) ;; core.EXT: defined in wasm ($add_core_ns $repl_env) @@ -546,31 +546,31 @@ ($RELEASE ($RE "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (c (gensym)) `(let* (~c ~(first xs)) (if ~c ~c (or ~@(rest xs)))))))))" $repl_env)) ;; Command line arguments - (set_local $res ($MAP_LOOP_START (get_global $LIST_T))) + (local.set $res ($MAP_LOOP_START (global.get $LIST_T))) ;; push MAP_LOP stack ;; empty = current = ret = res - (set_local $ret $res) - (set_local $current $res) - (set_local $empty $res) + (local.set $ret $res) + (local.set $current $res) + (local.set $empty $res) - (set_local $i 2) + (local.set $i 2) (block $done (loop $loop (br_if $done (i32.ge_u $i $argc)) - (set_local $val2 ($STRING (get_global $STRING_T) + (local.set $val2 ($STRING (global.get $STRING_T) (i32.load (i32.add $argv (i32.mul $i 4))))) ;; MAP_LOOP_UPDATE - (set_local $res ($MAP_LOOP_UPDATE - (get_global $LIST_T) $empty $current $val2 0)) - (if (i32.le_u $current (get_global $EMPTY_HASHMAP)) + (local.set $res ($MAP_LOOP_UPDATE + (global.get $LIST_T) $empty $current $val2 0)) + (if (i32.le_u $current (global.get $EMPTY_HASHMAP)) ;; if first element, set return to new element - (set_local $ret $res)) + (local.set $ret $res)) ;; update current to point to new element - (set_local $current $res) + (local.set $current $res) - (set_local $i (i32.add $i 1)) + (local.set $i (i32.add $i 1)) (br $loop) ) ) @@ -582,12 +582,12 @@ (if (i32.gt_u $argc 1) (then (drop ($ENV_SET_S $repl_env - "*FILE*" ($STRING (get_global $STRING_T) + "*FILE*" ($STRING (global.get $STRING_T) (i32.load (i32.add $argv 4))))) ($RELEASE ($RE "(load-file *FILE*)" $repl_env)) - (if (get_global $error_type) + (if (global.get $error_type) (then - ($printf_1 "Error: %s\n" (get_global $error_str)) + ($printf_1 "Error: %s\n" (global.get $error_str)) (return 1)) (else (return 0))))) @@ -599,18 +599,18 @@ (loop $repl_loop (br_if $repl_done (i32.eqz ($readline "user> " $line))) (br_if $repl_loop (i32.eq (i32.load8_u $line) 0)) - (set_local $res ($REP $line $repl_env)) - (if (get_global $error_type) + (local.set $res ($REP $line $repl_env)) + (if (global.get $error_type) (then - (if (i32.eq 2 (get_global $error_type)) + (if (i32.eq 2 (global.get $error_type)) (then - (set_local $ms ($pr_str (get_global $error_val) 1)) + (local.set $ms ($pr_str (global.get $error_val) 1)) ($printf_1 "Error: %s\n" ($to_String $ms)) ($RELEASE $ms) - ($RELEASE (get_global $error_val))) + ($RELEASE (global.get $error_val))) (else - ($printf_1 "Error: %s\n" (get_global $error_str)))) - (set_global $error_type 0)) + ($printf_1 "Error: %s\n" (global.get $error_str)))) + (global.set $error_type 0)) (else ($printf_1 "%s\n" ($to_String $res)))) ($RELEASE $res) diff --git a/wasm/string.wam b/wasm/string.wam index 593ea1c1e0..25b6ed094d 100644 --- a/wasm/string.wam +++ b/wasm/string.wam @@ -9,7 +9,7 @@ (loop $copy (i32.store8 (i32.add $idx $dst) (i32.load8_u (i32.add $idx $src))) - (set_local $idx (i32.add 1 $idx)) + (local.set $idx (i32.add 1 $idx)) (br_if $copy (i32.lt_u $idx $len)) ) ) @@ -19,7 +19,7 @@ (loop $count (if (i32.ne 0 (i32.load8_u $cur)) (then - (set_local $cur (i32.add $cur 1)) + (local.set $cur (i32.add $cur 1)) (br $count))) ) (i32.sub $cur $str) @@ -34,7 +34,7 @@ (if (i32.eq $needle_len 0) (return $haystack)) - (set_local $i 0) + (local.set $i 0) (block $done (loop $loop (if (i32.gt_s $i (i32.sub $len $needle_len)) (br $done)) @@ -43,8 +43,8 @@ (i32.load8_u $needle)) (i32.eqz ($strncmp $haystack $needle $needle_len))) (return $haystack)) - (set_local $haystack (i32.add $haystack 1)) - (set_local $i (i32.add $i 1)) + (local.set $haystack (i32.add $haystack 1)) + (local.set $i (i32.add $i 1)) (br $loop) ) ) @@ -58,17 +58,17 @@ $ch 0) (block $done (loop $loop - (set_local $ch (i32.load8_u (i32.add $str $i))) + (local.set $ch (i32.load8_u (i32.add $str $i))) (if (AND (i32.ne $ch (CHR "-")) (OR (i32.lt_u $ch (CHR "0")) (i32.gt_u $ch (CHR "9")))) (br $done)) - (set_local $i (i32.add $i 1)) + (local.set $i (i32.add $i 1)) (if (i32.eq $ch (CHR "-")) (then - (set_local $neg 1)) + (local.set $neg 1)) (else - (set_local $acc (i32.add (i32.mul $acc 10) + (local.set $acc (i32.add (i32.mul $acc 10) (i32.sub $ch (CHR "0")))))) (br $loop) ) @@ -85,8 +85,8 @@ (br $done)) (if (i32.ne (i32.load8_u $s1) (i32.load8_u $s2)) (br $done)) - (set_local $s1 (i32.add $s1 1)) - (set_local $s2 (i32.add $s2 1)) + (local.set $s1 (i32.add $s1 1)) + (local.set $s2 (i32.add $s2 1)) (br $loop) ) ) @@ -107,7 +107,7 @@ (if (i32.eqz (i32.load8_u (i32.add $i $s1))) (br $done)) (if (i32.ne (i32.load8_u (i32.add $i $s1)) (i32.load8_u (i32.add $i $s2))) (br $done)) - (set_local $i (i32.add $i 1)) + (local.set $i (i32.add $i 1)) (br $loop) ) ) @@ -140,26 +140,26 @@ (if (i32.eqz $grass) (then ;; check that we aren't expanding in place - (set_local $s 0) + (local.set $s 0) (block $done (loop $loop (if (i32.ge_u $s 3) (br $done)) - (set_local $needle (if (result i32) (i32.eq $s 0) $needle0 + (local.set $needle (if (result i32) (i32.eq $s 0) $needle0 (if (result i32) (i32.eq $s 1) $needle1 $needle2))) - (set_local $replace (if (result i32) (i32.eq $s 0) $replace0 + (local.set $replace (if (result i32) (i32.eq $s 0) $replace0 (if (result i32) (i32.eq $s 1) $replace1 $replace2))) - (set_local $needle_len ($strlen $needle)) - (set_local $replace_len ($strlen $replace)) + (local.set $needle_len ($strlen $needle)) + (local.set $replace_len ($strlen $replace)) (if (i32.gt_u $replace_len $needle_len) ($fatal 7 "REPLACE: invalid expanding in-place call\n")) - (set_local $s (i32.add $s 1)) + (local.set $s (i32.add $s 1)) (br $loop) ) ) - (set_local $grass $haystack) - (set_local $dst_str $grass))) + (local.set $grass $haystack) + (local.set $dst_str $grass))) (block $done1 (loop $loop1 @@ -167,45 +167,45 @@ (br $done1)) ;; Find the earliest match - (set_local $found 0) - (set_local $s 0) + (local.set $found 0) + (local.set $s 0) (block $done2 (loop $loop2 (if (i32.ge_u $s 3) (br $done2)) - (set_local $needle (if (result i32) (i32.eq $s 0) $needle0 + (local.set $needle (if (result i32) (i32.eq $s 0) $needle0 (if (result i32) (i32.eq $s 1) $needle1 $needle2))) - (set_local $replace (if (result i32) (i32.eq $s 0) $replace0 + (local.set $replace (if (result i32) (i32.eq $s 0) $replace0 (if (result i32) (i32.eq $s 1) $replace1 $replace2))) - (set_local $s (i32.add $s 1)) - (set_local $found_tmp ($strstr $src_str $needle)) + (local.set $s (i32.add $s 1)) + (local.set $found_tmp ($strstr $src_str $needle)) (if (i32.eqz $found_tmp) (br $loop2)) (if (OR (i32.eqz $found) (i32.lt_s $found_tmp $found)) (then - (set_local $found $found_tmp) - (set_local $needle_len_s ($strlen $needle)) - (set_local $replace_s $replace) - (set_local $replace_len_s ($strlen $replace)))) + (local.set $found $found_tmp) + (local.set $needle_len_s ($strlen $needle)) + (local.set $replace_s $replace) + (local.set $replace_len_s ($strlen $replace)))) (br $loop2) ) ) (if (i32.eqz $found) (br $done1)) ;; copy before the match ($memmove $dst_str $src_str (i32.add (i32.sub $found $src_str) 1)) - (set_local $dst_str (i32.add $dst_str (i32.sub $found $src_str))) + (local.set $dst_str (i32.add $dst_str (i32.sub $found $src_str))) ;; add the replace string ($memmove $dst_str $replace_s (i32.add $replace_len_s 1)) - (set_local $dst_str (i32.add $dst_str $replace_len_s)) + (local.set $dst_str (i32.add $dst_str $replace_len_s)) ;; Move to after the match - (set_local $src_str (i32.add $found $needle_len_s)) + (local.set $src_str (i32.add $found $needle_len_s)) (br $loop1) ) ) ;; Copy the left-over ($memmove $dst_str $src_str ($strlen $src_str)) - (set_local $dst_str (i32.add $dst_str ($strlen $src_str))) + (local.set $dst_str (i32.add $dst_str ($strlen $src_str))) (i32.store8 $dst_str (CHR "\x00")) (i32.sub $dst_str $grass) diff --git a/wasm/types.wam b/wasm/types.wam index a2d0cd36e3..280fc0eb7a 100644 --- a/wasm/types.wam +++ b/wasm/types.wam @@ -59,27 +59,27 @@ ) (func $TRUE_FALSE (param $val i32) (result i32) - ($INC_REF (if (result i32) $val (get_global $TRUE) (get_global $FALSE))) + ($INC_REF (if (result i32) $val (global.get $TRUE) (global.get $FALSE))) ) (func $THROW_STR_0 (param $fmt i32) - (drop ($sprintf_1 (get_global $error_str) $fmt "")) - (set_global $error_type 1) + (drop ($sprintf_1 (global.get $error_str) $fmt "")) + (global.set $error_type 1) ) (func $THROW_STR_1 (param $fmt i32) (param $v0 i32) - (drop ($sprintf_1 (get_global $error_str) $fmt $v0)) - (set_global $error_type 1) + (drop ($sprintf_1 (global.get $error_str) $fmt $v0)) + (global.set $error_type 1) ) (func $EQUAL_Q (param $a i32 $b i32) (result i32) (LET $ta ($TYPE $a) $tb ($TYPE $b)) - (if (AND (OR (i32.eq $ta (get_global $LIST_T)) - (i32.eq $ta (get_global $VECTOR_T))) - (OR (i32.eq $tb (get_global $LIST_T)) - (i32.eq $tb (get_global $VECTOR_T)))) + (if (AND (OR (i32.eq $ta (global.get $LIST_T)) + (i32.eq $ta (global.get $VECTOR_T))) + (OR (i32.eq $tb (global.get $LIST_T)) + (i32.eq $tb (global.get $VECTOR_T)))) (then ;; EQUAL_Q_SEQ (block $done @@ -88,23 +88,23 @@ (br $done)) (if ($EQUAL_Q ($MEM_VAL1_ptr $a) ($MEM_VAL1_ptr $b)) (then - (set_local $a ($MEM_VAL0_ptr $a)) - (set_local $b ($MEM_VAL0_ptr $b))) + (local.set $a ($MEM_VAL0_ptr $a)) + (local.set $b ($MEM_VAL0_ptr $b))) (else (return 0))) (br $loop) ) ) (return (AND (i32.eq ($VAL0 $a) 0) (i32.eq ($VAL0 $b) 0)))) - (else (if (AND (i32.eq $ta (get_global $HASHMAP_T)) - (i32.eq $tb (get_global $HASHMAP_T))) + (else (if (AND (i32.eq $ta (global.get $HASHMAP_T)) + (i32.eq $tb (global.get $HASHMAP_T))) ;; EQUAL_Q_HM (then (return 1)) ;; TODO: remove this once strings are interned - (else (if (OR (AND (i32.eq $ta (get_global $STRING_T)) - (i32.eq $tb (get_global $STRING_T))) - (AND (i32.eq $ta (get_global $SYMBOL_T)) - (i32.eq $tb (get_global $SYMBOL_T)))) + (else (if (OR (AND (i32.eq $ta (global.get $STRING_T)) + (i32.eq $tb (global.get $STRING_T))) + (AND (i32.eq $ta (global.get $SYMBOL_T)) + (i32.eq $tb (global.get $SYMBOL_T)))) (then (return (i32.eqz ($strcmp ($to_String $a) ($to_String $b))))) (else (return (AND (i32.eq $ta $tb) @@ -114,9 +114,9 @@ (func $DEREF_META (param $mv i32) (result i32) (loop $loop - (if (i32.eq ($TYPE $mv) (get_global $METADATA_T)) + (if (i32.eq ($TYPE $mv) (global.get $METADATA_T)) (then - (set_local $mv ($MEM_VAL0_ptr $mv)) + (local.set $mv ($MEM_VAL0_ptr $mv)) (br $loop))) ) $mv @@ -127,7 +127,7 @@ (func $to_MalString (param $mv i32) (result i32) ;; TODO: assert mv is a string/keyword/symbol - (i32.add (get_global $string_mem) ($VAL0 $mv)) + (i32.add (global.get $string_mem) ($VAL0 $mv)) ) (func $to_String (param $mv i32) (result i32) @@ -139,7 +139,7 @@ ;; return the MalVal pointer (func $STRING (param $type i32 $str i32) (result i32) (LET $ms ($ALLOC_STRING $str ($strlen $str) 1)) - ($ALLOC_SCALAR $type (i32.sub $ms (get_global $string_mem))) + ($ALLOC_SCALAR $type (i32.sub $ms (global.get $string_mem))) ) ;; Find first duplicate (internet) of mv. If one is found, free up @@ -152,10 +152,10 @@ $tmp 0) (if (AND $existing_ms (i32.lt_s $existing_ms $ms)) (then - (set_local $tmp $mv) - (set_local $res ($ALLOC_SCALAR (get_global $STRING_T) + (local.set $tmp $mv) + (local.set $res ($ALLOC_SCALAR (global.get $STRING_T) (i32.sub $existing_ms - (get_global $string_mem)))) + (global.get $string_mem)))) (i32.store16 $existing_ms (i32.add (i32.load16_u $existing_ms) 1)) ($RELEASE $tmp))) $res @@ -163,7 +163,7 @@ (func $STRING_INIT (param $type i32) (result i32) (LET $ms ($ALLOC_STRING "" 0 0)) - ($ALLOC_SCALAR $type (i32.sub $ms (get_global $string_mem))) + ($ALLOC_SCALAR $type (i32.sub $ms (global.get $string_mem))) ) (func $STRING_FINALIZE (param $mv i32 $size i32) (result i32) @@ -172,13 +172,13 @@ $ms ($to_MalString $mv)) (if $tmp (then - (set_local $mv $tmp)) + (local.set $mv $tmp)) (else ;;; ms->size = sizeof(MalString) + size + 1 (i32.store16 (i32.add $ms 2) (i32.add (i32.add 4 $size) 1)) ;;; string_mem_next = (void *)ms + ms->size - (set_global $string_mem_next + (global.set $string_mem_next (i32.add $ms (i32.load16_u (i32.add $ms 2)))))) $mv ) @@ -187,19 +187,19 @@ ;; numeric functions (func $INTEGER (param $val i32) (result i32) - ($ALLOC_SCALAR (get_global $INTEGER_T) $val) + ($ALLOC_SCALAR (global.get $INTEGER_T) $val) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; sequence functions (func $MAP_LOOP_START (param $type i32) (result i32) - (LET $res (if (result i32) (i32.eq $type (get_global $LIST_T)) - (then (get_global $EMPTY_LIST)) - (else (if (result i32) (i32.eq $type (get_global $VECTOR_T)) - (then (get_global $EMPTY_VECTOR)) - (else (if (result i32) (i32.eq $type (get_global $HASHMAP_T)) - (then (get_global $EMPTY_HASHMAP)) + (LET $res (if (result i32) (i32.eq $type (global.get $LIST_T)) + (then (global.get $EMPTY_LIST)) + (else (if (result i32) (i32.eq $type (global.get $VECTOR_T)) + (then (global.get $EMPTY_VECTOR)) + (else (if (result i32) (i32.eq $type (global.get $HASHMAP_T)) + (then (global.get $EMPTY_HASHMAP)) (else ($THROW_STR_1 "read_seq invalid type %d" $type) 0))))))) @@ -215,9 +215,9 @@ ;; sequence took ownership ($RELEASE $empty) ($RELEASE $val2) - (if (i32.eq $type (get_global $HASHMAP_T)) + (if (i32.eq $type (global.get $HASHMAP_T)) ($RELEASE $val3)) - (if (i32.gt_u $current (get_global $EMPTY_HASHMAP)) + (if (i32.gt_u $current (global.get $EMPTY_HASHMAP)) ;; if not first element, set current next to point to new element (i32.store ($VAL0_ptr $current) ($IDX $res))) @@ -229,19 +229,19 @@ ;; if it's already the right type, inc ref cnt and return it (if (i32.eq $type ($TYPE $mv)) (return ($INC_REF $mv))) ;; if it's empty, return the sequence match - (if (i32.le_u $mv (get_global $EMPTY_HASHMAP)) + (if (i32.le_u $mv (global.get $EMPTY_HASHMAP)) (return ($MAP_LOOP_START $type))) ;; otherwise, copy first element to turn it into correct type ($ALLOC $type ($MEM_VAL0_ptr $mv) ($MEM_VAL1_ptr $mv) 0) ) (func $LIST (param $seq i32 $first i32) (result i32) - ($ALLOC (get_global $LIST_T) $seq $first 0) + ($ALLOC (global.get $LIST_T) $seq $first 0) ) (func $LIST2 (param $first i32 $second i32) (result i32) ;; last element is empty list - (LET $tmp ($LIST (get_global $EMPTY_LIST) $second) + (LET $tmp ($LIST (global.get $EMPTY_LIST) $second) $res ($LIST $tmp $first)) ($RELEASE $tmp) ;; new list takes ownership of previous $res @@ -255,7 +255,7 @@ ) (func $LIST_Q (param $mv i32) (result i32) - (i32.eq ($TYPE $mv) (get_global $LIST_T)) + (i32.eq ($TYPE $mv) (global.get $LIST_T)) ) (func $EMPTY_Q (param $mv i32) (result i32) @@ -267,8 +267,8 @@ (block $done (loop $loop (if (i32.eq ($VAL0 $mv) 0) (br $done)) - (set_local $cnt (i32.add $cnt 1)) - (set_local $mv ($MEM_VAL0_ptr $mv)) + (local.set $cnt (i32.add $cnt 1)) + (local.set $mv ($MEM_VAL0_ptr $mv)) (br $loop) ) ) @@ -280,15 +280,15 @@ ;; TODO: check that actually a list/vector (if (i32.eq ($VAL0 $mv) 0) ;; empty seq, return nil - (return ($INC_REF (get_global $NIL)))) + (return ($INC_REF (global.get $NIL)))) (block $done (loop $loop ;; end, return previous value (if (i32.eq ($VAL0 $mv) 0) (br $done)) ;; current becomes previous entry - (set_local $cur $mv) + (local.set $cur $mv) ;; next entry - (set_local $mv ($MEM_VAL0_ptr $mv)) + (local.set $mv ($MEM_VAL0_ptr $mv)) (br $loop) ) ) @@ -301,7 +301,7 @@ (func $SLICE (param $seq i32) (param $start i32) (param $end i32) (result i64) (LET $idx 0 - $res ($INC_REF (get_global $EMPTY_LIST)) + $res ($INC_REF (global.get $EMPTY_LIST)) $last 0 $tmp $res) ;; advance seq to start @@ -310,8 +310,8 @@ (if (OR (i32.ge_s $idx $start) (i32.eqz ($VAL0 $seq))) (br $done)) - (set_local $seq ($MEM_VAL0_ptr $seq)) - (set_local $idx (i32.add $idx 1)) + (local.set $seq ($MEM_VAL0_ptr $seq)) + (local.set $idx (i32.add $idx 1)) (br $loop) ) ) @@ -323,48 +323,48 @@ (i32.ge_s $idx $end)) (i32.eqz ($VAL0 $seq))) (then - (set_local $res $tmp) + (local.set $res $tmp) (br $done))) ;; allocate new list element with copied value - (set_local $res ($LIST (get_global $EMPTY_LIST) + (local.set $res ($LIST (global.get $EMPTY_LIST) ($MEM_VAL1_ptr $seq))) ;; sequence took ownership - ($RELEASE (get_global $EMPTY_LIST)) + ($RELEASE (global.get $EMPTY_LIST)) (if (i32.eqz $last) (then ;; if first element, set return value to new element - (set_local $tmp $res)) + (local.set $tmp $res)) (else ;; if not the first element, set return value to new element (i32.store ($VAL0_ptr $last) ($IDX $res)))) - (set_local $last $res) ;; update last list element + (local.set $last $res) ;; update last list element ;; advance to next element of seq - (set_local $seq ($MEM_VAL0_ptr $seq)) - (set_local $idx (i32.add $idx 1)) + (local.set $seq ($MEM_VAL0_ptr $seq)) + (local.set $idx (i32.add $idx 1)) (br $loop) ) ) ;; combine last/res as hi 32/low 32 of i64 (i64.or - (i64.shl (i64.extend_u/i32 $last) (i64.const 32)) - (i64.extend_u/i32 $res)) + (i64.shl (i64.extend_i32_u $last) (i64.const 32)) + (i64.extend_i32_u $res)) ) (func $HASHMAP (result i32) ;; just point to static empty hash-map - ($INC_REF (get_global $EMPTY_HASHMAP)) + ($INC_REF (global.get $EMPTY_HASHMAP)) ) (func $ASSOC1 (param $hm i32 $k i32 $v i32) (result i32) - (LET $res ($ALLOC (get_global $HASHMAP_T) $hm $k $v)) + (LET $res ($ALLOC (global.get $HASHMAP_T) $hm $k $v)) ;; we took ownership of previous release ($RELEASE $hm) $res ) (func $ASSOC1_S (param $hm i32 $k i32 $v i32) (result i32) - (LET $kmv ($STRING (get_global $STRING_T) $k) + (LET $kmv ($STRING (global.get $STRING_T) $k) $res ($ASSOC1 $hm $kmv $v)) ;; map took ownership of key ($RELEASE $kmv) @@ -382,36 +382,36 @@ ;;; if (VAL0(hm) == 0) (if (i32.eq ($VAL0 $hm) 0) (then - (set_local $res (get_global $NIL)) + (local.set $res (global.get $NIL)) (br $done))) ;;; test_key_mv = MEM_VAL1(hm) - (set_local $test_key_mv ($MEM_VAL1_ptr $hm)) + (local.set $test_key_mv ($MEM_VAL1_ptr $hm)) ;;; if (strcmp(key, to_String(test_key_mv)) == 0) (if (i32.eq ($strcmp $key ($to_String $test_key_mv)) 0) (then - (set_local $found 1) - (set_local $res ($MEM_VAL2_ptr $hm)) + (local.set $found 1) + (local.set $res ($MEM_VAL2_ptr $hm)) (br $done))) - (set_local $hm ($MEM_VAL0_ptr $hm)) + (local.set $hm ($MEM_VAL0_ptr $hm)) (br $loop) ) ) ;; combine found/res as hi 32/low 32 of i64 - (i64.or (i64.shl (i64.extend_u/i32 $found) (i64.const 32)) - (i64.extend_u/i32 $res)) + (i64.or (i64.shl (i64.extend_i32_u $found) (i64.const 32)) + (i64.extend_i32_u $res)) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; function functions (func $FUNCTION (param $index i32) (result i32) - ($ALLOC_SCALAR (get_global $FUNCTION_T) $index) + ($ALLOC_SCALAR (global.get $FUNCTION_T) $index) ) (func $MALFUNC (param $ast i32 $params i32 $env i32) (result i32) - ($ALLOC (get_global $MALFUNC_T) $ast $params $env) + ($ALLOC (global.get $MALFUNC_T) $ast $params $env) ) ) From 53619a6e59630bbef082926f1ddc804b3aac7e52 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 16 Jan 2019 01:12:00 -0600 Subject: [PATCH 0736/2308] wasm: Add warpy wasm_MODE. - Update Dockerfile to install rpython and then use that build and install warpy (nojit). - Support 'warpy' in wasm_MODE. Update wasm/Makefile and wasm/run to support the new mode. --- .travis.yml | 1 + wasm/Dockerfile | 40 ++++++++++++++++++++++++++++++++++++---- wasm/Makefile | 2 +- wasm/run | 2 ++ 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2cb06e3125..782480ee07 100644 --- a/.travis.yml +++ b/.travis.yml @@ -93,6 +93,7 @@ matrix: - {env: IMPL=vimscript, services: [docker]} - {env: IMPL=wasm wasm_MODE=wace_libc, services: [docker]} - {env: IMPL=wasm wasm_MODE=node, services: [docker]} + - {env: IMPL=wasm wasm_MODE=warpy, services: [docker]} - {env: IMPL=yorick, services: [docker]} script: diff --git a/wasm/Dockerfile b/wasm/Dockerfile index 2a342839be..d533d48339 100644 --- a/wasm/Dockerfile +++ b/wasm/Dockerfile @@ -45,20 +45,52 @@ RUN dpkg --add-architecture i386 && \ apt-get -y install \ git-core cmake g++ lib32gcc-8-dev \ libsdl2-dev:i386 libsdl2-image-dev:i386 \ - libedit-dev:i386 + libedit-dev:i386 freeglut3-dev:i386 lib32gcc-7-dev \ + libreadline-dev:i386 RUN git clone https://github.com/WebAssembly/binaryen/ && \ cd binaryen && \ cmake . && make && \ make install -# TODO: merge up -RUN apt-get -y install freeglut3-dev:i386 lib32gcc-7-dev libreadline-dev:i386 +# +# pypy / rpython +# + +# rpython deps +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get -y install libffi-dev pkg-config libz-dev \ + libbz2-dev libsqlite3-dev libncurses-dev libexpat1-dev \ + libssl-dev libgdbm-dev tcl-dev + +# install pypy, build and install pypy/rpython, remove prior pypy +RUN apt-get -y install software-properties-common && \ + add-apt-repository ppa:pypy && \ + apt-get -y update && \ + apt-get -y install pypy && \ + mkdir -p /opt/pypy && \ + curl -L https://bitbucket.org/pypy/pypy/downloads/pypy2-v6.0.0-src.tar.bz2 \ + | tar -xjf - -C /opt/pypy/ --strip-components=1 && \ + cd /opt/pypy && make && \ + chmod -R ugo+rw /opt/pypy/rpython/_cache && \ + ln -sf /opt/pypy/rpython/bin/rpython /usr/local/bin/rpython && \ + ln -sf /opt/pypy/pypy-c /usr/local/bin/pypy && \ + rm -rf /tmp/usession* && \ + ln -sf /opt/pypy/pypy/goal/pypy-c /usr/local/bin/pypy && \ + apt-get -y autoremove pypy # # wac/wace # RUN git clone https://github.com/kanaka/wac/ && \ - cd wac && \ + cd wac && \ make USE_SDL= wac wace && \ cp wac wace /usr/bin + +# +# warpy +# +RUN git clone https://github.com/kanaka/warpy/ && \ + cd warpy && \ + make warpy-nojit && \ + cp warpy-nojit /usr/bin/warpy diff --git a/wasm/Makefile b/wasm/Makefile index 0c5e1b1df2..02d4ea1654 100644 --- a/wasm/Makefile +++ b/wasm/Makefile @@ -1,4 +1,4 @@ -MODE ?= $(if $(filter node js wace_fooboot,$(wasm_MODE)),os,libc) +MODE ?= $(if $(filter node js wace_fooboot warpy,$(wasm_MODE)),os,libc) WASM_AS ?= wasm-as WAMP ?= node_modules/.bin/wamp diff --git a/wasm/run b/wasm/run index 90465475ec..0dcce47255 100755 --- a/wasm/run +++ b/wasm/run @@ -3,6 +3,8 @@ STEP=${STEP:-stepA_mal} case "${wasm_MODE}" in node|js) exec ./run.js $(dirname $0)/${STEP:-stepA_mal}.wasm "${@}" ;; +warpy) + exec warpy --argv --memory-pages 256 $(dirname $0)/${STEP:-stepA_mal}.wasm "${@}" ;; wace_fooboot) echo >&2 "wace_fooboot mode not yet supported" ;; wace_libc|*) From c4ce75c08f3b3ee4a5a906ca972d5a9c955b630b Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 16 Jan 2019 01:25:00 -0600 Subject: [PATCH 0737/2308] wasm: fix get_time_ms to return millis. Also, to avoid overflow, subtract 30 years so that the value won't wrap in i32. --- wasm/Dockerfile | 2 +- wasm/platform_os.wam | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/wasm/Dockerfile b/wasm/Dockerfile index d533d48339..9683e2aef2 100644 --- a/wasm/Dockerfile +++ b/wasm/Dockerfile @@ -91,6 +91,6 @@ RUN git clone https://github.com/kanaka/wac/ && \ # warpy # RUN git clone https://github.com/kanaka/warpy/ && \ - cd warpy && \ + cd warpy && \ make warpy-nojit && \ cp warpy-nojit /usr/bin/warpy diff --git a/wasm/platform_os.wam b/wasm/platform_os.wam index cb76c7ff93..46624d4047 100644 --- a/wasm/platform_os.wam +++ b/wasm/platform_os.wam @@ -5,7 +5,7 @@ (import "env" "fputs" (func $lib_fputs (param i32 i32) (result i32))) (import "env" "readline" (func $lib_readline (param i32 i32 i32) (result i32))) (import "env" "read_file" (func $lib_read_file (param i32 i32) (result i32))) - (import "env" "get_time_ms" (func $lib_get_time_ms (result i32))) + (import "env" "get_time_ms" (func $lib_get_time_ms (result i64))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -39,7 +39,11 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (func $get_time_ms (result i32) - ($lib_get_time_ms) + (local $epoch_ms i64 $ms i64) + (local.set $epoch_ms ($lib_get_time_ms)) + ;; subtract 30 years to make sure it fits into i32 without + ;; wrapping or becoming negative + (i32.wrap_i64 (i64.sub $epoch_ms (i64.const 0x38640900))) ) ) From 266391c5505719a49cfff74dcf973754241edea7 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 16 Jan 2019 20:03:37 -0600 Subject: [PATCH 0738/2308] wasm: revert to i32 get_time_ms (subtract 30 years) JS doesn't support i64 types for exports/imports so revert to i32 return type but subtract 30 years to avoid wrapping/negative values. --- wasm/platform_os.wam | 8 ++------ wasm/run.js | 4 +++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/wasm/platform_os.wam b/wasm/platform_os.wam index 46624d4047..cb76c7ff93 100644 --- a/wasm/platform_os.wam +++ b/wasm/platform_os.wam @@ -5,7 +5,7 @@ (import "env" "fputs" (func $lib_fputs (param i32 i32) (result i32))) (import "env" "readline" (func $lib_readline (param i32 i32 i32) (result i32))) (import "env" "read_file" (func $lib_read_file (param i32 i32) (result i32))) - (import "env" "get_time_ms" (func $lib_get_time_ms (result i64))) + (import "env" "get_time_ms" (func $lib_get_time_ms (result i32))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -39,11 +39,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (func $get_time_ms (result i32) - (local $epoch_ms i64 $ms i64) - (local.set $epoch_ms ($lib_get_time_ms)) - ;; subtract 30 years to make sure it fits into i32 without - ;; wrapping or becoming negative - (i32.wrap_i64 (i64.sub $epoch_ms (i64.const 0x38640900))) + ($lib_get_time_ms) ) ) diff --git a/wasm/run.js b/wasm/run.js index 849483128e..d9877e14ec 100755 --- a/wasm/run.js +++ b/wasm/run.js @@ -95,7 +95,9 @@ async function loadWebAssembly(filename, args) { } function get_time_ms() { - return (new Date()).getTime() + // subtract 30 years to make sure it fits into i32 without + // wrapping or becoming negative + return (new Date()).getTime() - 0x38640900 } // Marshal arguments From 5425eafbbc6e85da159de34df51d28af02de4e61 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 16 Jan 2019 21:01:15 -0600 Subject: [PATCH 0739/2308] wasm: update Dockerfile to force warpy build. --- wasm/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/Dockerfile b/wasm/Dockerfile index 9683e2aef2..9913f68d79 100644 --- a/wasm/Dockerfile +++ b/wasm/Dockerfile @@ -91,6 +91,6 @@ RUN git clone https://github.com/kanaka/wac/ && \ # warpy # RUN git clone https://github.com/kanaka/warpy/ && \ - cd warpy && \ + cd warpy && \ make warpy-nojit && \ cp warpy-nojit /usr/bin/warpy From f88802514c4d0fffa601bfdb15f5a4cae16ea963 Mon Sep 17 00:00:00 2001 From: Windfarer Date: Mon, 21 Jan 2019 17:08:05 +0800 Subject: [PATCH 0740/2308] fix function name "tokenize" --- process/guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/process/guide.md b/process/guide.md index 3e58b366ec..aba0022f09 100644 --- a/process/guide.md +++ b/process/guide.md @@ -309,11 +309,11 @@ expression support. returns the token at the current position. * Add a function `read_str` in `reader.qx`. This function - will call `tokenizer` and then create a new Reader object instance + will call `tokenize` and then create a new Reader object instance with the tokens. Then it will call `read_form` with the Reader instance. -* Add a function `tokenizer` in `reader.qx`. This function will take +* Add a function `tokenize` in `reader.qx`. This function will take a single string and return an array/list of all the tokens (strings) in it. The following regular expression (PCRE) will match all mal tokens. From 4aa0ebdf471eb0431feca137e5eb9fb4ab475025 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 24 Jan 2019 12:40:36 -0600 Subject: [PATCH 0741/2308] Error on unterminated strings. Add a step1 test to make sure that implementations are properly throwing an error on unclosed strings. Fix 47 implementations and update the guide to note the correct behavior. --- awk/reader.awk | 8 ++++++-- basic/reader.in.bas | 2 +- c/reader.c | 7 +++++-- clojure/src/mal/reader.cljc | 18 ++++++++++-------- crystal/reader.cr | 10 ++++++---- cs/reader.cs | 7 +++++-- d/reader.d | 6 +++++- dart/reader.dart | 5 ++++- factor/lib/reader/reader.factor | 22 +++++++++++++--------- fantom/src/mallib/fan/reader.fan | 6 +++++- go/src/reader/reader.go | 5 ++++- groovy/reader.groovy | 13 +++++++++---- haxe/reader/Reader.hx | 2 +- hy/reader.hy | 5 ++++- io/MalReader.io | 3 ++- java/src/main/java/mal/reader.java | 10 ++++++---- julia/reader.jl | 4 +++- kotlin/src/mal/reader.kt | 10 ++++++---- livescript/reader.ls | 4 +++- logo/reader.lg | 2 +- make/reader.mk | 2 +- matlab/reader.m | 5 ++++- miniMAL/reader.json | 18 ++++++++++-------- nim/reader.nim | 6 ++++-- objc/reader.m | 10 ++++++---- objpascal/reader.pas | 6 ++++-- ocaml/reader.ml | 16 ++++++++++------ php/reader.php | 5 ++++- plpgsql/reader.sql | 4 +++- plsql/reader.sql | 7 +++++-- powershell/reader.psm1 | 4 +++- process/guide.md | 8 +++++--- r/reader.r | 5 ++++- racket/reader.rkt | 4 +++- rexx/reader.rexx | 8 +++++++- rpython/reader.py | 6 ++++-- ruby/reader.rb | 3 ++- scala/reader.scala | 5 ++++- skew/reader.sk | 2 +- swift/reader.swift | 10 +++++++--- swift3/Sources/reader.swift | 2 +- tcl/reader.tcl | 3 ++- tests/step1_read_print.mal | 6 +++--- ts/reader.ts | 5 ++++- vb/reader.vb | 7 +++++-- vhdl/reader.vhdl | 9 +++++++-- wasm/reader.wam | 2 +- yorick/reader.i | 7 +++++-- 48 files changed, 218 insertions(+), 106 deletions(-) diff --git a/awk/reader.awk b/awk/reader.awk index 905ef52d13..a45e6e5679 100644 --- a/awk/reader.awk +++ b/awk/reader.awk @@ -18,7 +18,11 @@ function reader_read_atom(token) case /^:/: return ":" token case /^"/: - return reader_read_string(token) + if (token ~ /"$/) { + return reader_read_string(token) + } else { + return "!\"Expected '\"', got EOF." + } case /^-?[0-9]+$/: return "+" token default: @@ -147,7 +151,7 @@ function reader_read_from(reader, current) function reader_tokenizer(str, reader, len, r) { - for (len = 0; match(str, /^[ \t\r\n,]*(~@|[\[\]{}()'`~^@]|\"(\\[^\r\n]|[^\\"\r\n])*\"|;[^\r\n]*|[^ \t\r\n\[\]{}('"`,;)^~@][^ \t\r\n\[\]{}('"`,;)]*)/, r); ) { + for (len = 0; match(str, /^[ \t\r\n,]*(~@|[\[\]{}()'`~^@]|\"(\\[^\r\n]|[^\\"\r\n])*\"?|;[^\r\n]*|[^ \t\r\n\[\]{}('"`,;)^~@][^ \t\r\n\[\]{}('"`,;)]*)/, r); ) { if (substr(r[1], 1, 1) != ";") { reader[len++] = r[1] } diff --git a/basic/reader.in.bas b/basic/reader.in.bas index ee2e864e8d..2482646195 100644 --- a/basic/reader.in.bas +++ b/basic/reader.in.bas @@ -166,7 +166,7 @@ SUB READ_FORM READ_STRING: REM PRINT "READ_STRING" C=ASC(MID$(T$,LEN(T$),1)) - IF C<>34 THEN R=-1:ER=-1:E$="expected '"+CHR$(34)+"'":GOTO READ_FORM_RETURN + IF C<>34 THEN R=-1:ER=-1:E$="expected '"+CHR$(34)+"', got EOF":GOTO READ_FORM_RETURN R$=MID$(T$,2,LEN(T$)-2) S1$=CHR$(92)+CHR$(92):S2$=CHR$(127):GOSUB REPLACE: REM protect backslashes S1$=CHR$(92)+CHR$(34):S2$=CHR$(34):GOSUB REPLACE: REM unescape quotes diff --git a/c/reader.c b/c/reader.c index 2528b3a1d0..45615c5b88 100644 --- a/c/reader.c +++ b/c/reader.c @@ -52,7 +52,7 @@ Reader *tokenize(char *line) { Reader *reader = reader_new(); - regex = g_regex_new ("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)", 0, 0, &err); + regex = g_regex_new ("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"?|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)", 0, 0, &err); g_regex_match (regex, line, 0, &matchInfo); if (err != NULL) { @@ -89,7 +89,7 @@ MalVal *read_atom(Reader *reader) { token = reader_next(reader); //g_print("read_atom token: %s\n", token); - regex = g_regex_new ("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|:(.*)|(^[^\"]*$)", 0, 0, &err); + regex = g_regex_new ("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"?$|:(.*)|(^[^\"]*$)", 0, 0, &err); g_regex_match (regex, token, 0, &matchInfo); if (g_match_info_fetch_pos(matchInfo, 1, &pos, NULL) && pos != -1) { @@ -109,6 +109,9 @@ MalVal *read_atom(Reader *reader) { atom = &mal_false; } else if (g_match_info_fetch_pos(matchInfo, 6, &pos, NULL) && pos != -1) { //g_print("read_atom string: %s\n", token); + int end = strlen(token)-1; + if (token[end] != '"') { abort("expected '\"', got EOF"); } + token[end] = '\0'; atom = malval_new_string(g_strcompress(g_match_info_fetch(matchInfo, 6))); } else if (g_match_info_fetch_pos(matchInfo, 7, &pos, NULL) && pos != -1) { //g_print("read_atom keyword\n"); diff --git a/clojure/src/mal/reader.cljc b/clojure/src/mal/reader.cljc index 93b2df3b52..9fb1941b01 100644 --- a/clojure/src/mal/reader.cljc +++ b/clojure/src/mal/reader.cljc @@ -17,6 +17,7 @@ (def tok-re #"[\s,]*(~@|[\[\]{}()'`~^@]|\"(?:[\\].|[^\\\"])*\"?|;.*|[^\s\[\]{}()'\"`@,;]+)") (def int-re #"^-?[0-9]+$") +(def badstr-re #"^\"(.*)[^\"]$") (def str-re #"^\"(.*)\"$") (defn tokenize [s] @@ -32,14 +33,15 @@ (defn read-atom [rdr] (let [token (rdr-next rdr)] (cond - (re-seq int-re token) #?(:cljs (js/parseInt token) - :clj (Integer/parseInt token)) - (re-seq str-re token) (unescape (second (re-find str-re token))) - (= \: (get token 0)) (keyword (subs token 1)) - (= "nil" token) nil - (= "true" token) true - (= "false" token) false - :else (symbol token)))) + (re-seq int-re token) #?(:cljs (js/parseInt token) + :clj (Integer/parseInt token)) + (re-seq badstr-re token) (throw-str (str "expected '\"', got EOF")) + (re-seq str-re token) (unescape (second (re-find str-re token))) + (= \: (get token 0)) (keyword (subs token 1)) + (= "nil" token) nil + (= "true" token) true + (= "false" token) false + :else (symbol token)))) (declare read-form) diff --git a/crystal/reader.cr b/crystal/reader.cr index 3fbbd7c97a..5eca482f08 100644 --- a/crystal/reader.cr +++ b/crystal/reader.cr @@ -81,9 +81,11 @@ class Reader when token == "true" then true when token == "false" then false when token == "nil" then nil - when token[0] == '"' then token[1..-2].gsub(/\\(.)/, {"\\\"" => "\"", - "\\n" => "\n", - "\\\\" => "\\"}) + when token[0] == '"' + parse_error "expected '\"', got EOF" if token[-1] != '"' + token[1..-2].gsub(/\\(.)/, {"\\\"" => "\"", + "\\n" => "\n", + "\\\\" => "\\"}) when token[0] == ':' then "\u029e#{token[1..-1]}" else Mal::Symbol.new token end @@ -121,7 +123,7 @@ class Reader end def tokenize(str) - regex = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/ + regex = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/ str.scan(regex).map { |m| m[1] }.reject(&.empty?) end diff --git a/cs/reader.cs b/cs/reader.cs index 60798e37bf..3136b904c6 100644 --- a/cs/reader.cs +++ b/cs/reader.cs @@ -39,7 +39,7 @@ public string next() { public static List tokenize(string str) { List tokens = new List(); - string pattern = @"[\s ,]*(~@|[\[\]{}()'`~@]|""(?:[\\].|[^\\""])*""|;.*|[^\s \[\]{}()'""`~@,;]*)"; + string pattern = @"[\s ,]*(~@|[\[\]{}()'`~@]|""(?:[\\].|[^\\""])*""?|;.*|[^\s \[\]{}()'""`~@,;]*)"; Regex regex = new Regex(pattern); foreach (Match match in regex.Matches(str)) { string token = match.Groups[1].Value; @@ -53,7 +53,7 @@ public static List tokenize(string str) { public static MalVal read_atom(Reader rdr) { string token = rdr.next(); - string pattern = @"(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^("".*"")$|:(.*)|(^[^""]*$)"; + string pattern = @"(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^("".*)|:(.*)|(^[^""]*$)"; Regex regex = new Regex(pattern); Match match = regex.Match(token); //Console.WriteLine("token: ^" + token + "$"); @@ -70,6 +70,9 @@ public static MalVal read_atom(Reader rdr) { return Mal.types.False; } else if (match.Groups[6].Value != String.Empty) { string str = match.Groups[6].Value; + if (str[str.Length-1] != '"') { + throw new ParseError("expected '\"', got EOF"); + } str = str.Substring(1, str.Length-2) .Replace("\\\\", "\u029e") .Replace("\\\"", "\"") diff --git a/d/reader.d b/d/reader.d index 2f6ed44911..19681cb0c4 100644 --- a/d/reader.d +++ b/d/reader.d @@ -44,7 +44,7 @@ class Reader } } -auto tokenize_ctr = ctRegex!(r"[\s,]*(~@|[\[\]{}()'`~^@]|" `"` `(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"` r"`,;)]*)"); +auto tokenize_ctr = ctRegex!(r"[\s,]*(~@|[\[\]{}()'`~^@]|" `"` `(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"` r"`,;)]*)"); string[] tokenize(string str) { @@ -87,6 +87,10 @@ MalType read_atom(Reader reader) case ':': return new MalString("\u029e" ~ token[1..$]); case '"': + if (token[$-1] != '"') + { + throw new Exception("expected '\"', got EOF"); + } return parse_string(token); default: auto captures = matchFirst(token, integer_ctr); diff --git a/dart/reader.dart b/dart/reader.dart index e440ae054b..fc6eb51071 100644 --- a/dart/reader.dart +++ b/dart/reader.dart @@ -1,7 +1,7 @@ import 'types.dart'; final malRegExp = new RegExp( - r"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)"""); + r"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)"""); class Reader { final List tokens; @@ -115,6 +115,9 @@ MalType read_atom(Reader reader) { } if (token[0] == '"') { + if (token[token.length -1 ] != '"') { + throw new ParseException("expected '\"', got EOF"); + } var sanitizedToken = token // remove surrounding quotes .substring(1, token.length - 1) diff --git a/factor/lib/reader/reader.factor b/factor/lib/reader/reader.factor index da72277e00..568eae0f53 100644 --- a/factor/lib/reader/reader.factor +++ b/factor/lib/reader/reader.factor @@ -4,19 +4,23 @@ USING: arrays combinators grouping hashtables kernel lists locals make lib.types math.parser regexp sequences splitting strings ; IN: lib.reader -CONSTANT: token-regex R/ (~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)~^@]+)/ +CONSTANT: token-regex R/ (~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)~^@]+)/ DEFER: read-form : (read-string) ( str -- maltype ) - rest but-last R/ \\./ [ - { - { [ dup >string "\\\\" = ] [ drop "\\" ] } - { [ dup >string "\\n" = ] [ drop "\n" ] } - { [ dup >string "\\\"" = ] [ drop "\"" ] } - [ ] - } cond - ] re-replace-with ; + dup last CHAR: " = [ + rest but-last R/ \\./ [ + { + { [ dup >string "\\\\" = ] [ drop "\\" ] } + { [ dup >string "\\n" = ] [ drop "\n" ] } + { [ dup >string "\\\"" = ] [ drop "\"" ] } + [ ] + } cond + ] re-replace-with + ] [ + "expected '\"', got EOF" throw + ] if ; : (read-atom) ( str -- maltype ) { diff --git a/fantom/src/mallib/fan/reader.fan b/fantom/src/mallib/fan/reader.fan index edf9fe1337..d7a1f9c037 100644 --- a/fantom/src/mallib/fan/reader.fan +++ b/fantom/src/mallib/fan/reader.fan @@ -18,7 +18,7 @@ class Reader { private static Str[] tokenize(Str s) { - r := Regex <|[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)|> + r := Regex <|[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)|> m := r.matcher(s) tokens := Str[,] while (m.find()) @@ -39,10 +39,14 @@ class Reader { token := reader.next intRegex := Regex <|^-?\d+$|> + strRegex := Regex <|^".*"|> + strBadRegex := Regex <|^".*|> if (token == "nil") return MalNil.INSTANCE if (token == "true") return MalTrue.INSTANCE if (token == "false") return MalFalse.INSTANCE if (intRegex.matches(token)) return MalInteger(token.toInt) + if (strRegex.matches(token)) return MalString.make(unescape_str(token[1..-2])) + if (strBadRegex.matches(token)) throw Err("expected '\"', got EOF") if (token[0] == '"') return MalString.make(unescape_str(token[1..-2])) if (token[0] == ':') return MalString.makeKeyword(token[1..-1]) return MalSymbol(token) diff --git a/go/src/reader/reader.go b/go/src/reader/reader.go index dc0f54f46f..e16dca86be 100644 --- a/go/src/reader/reader.go +++ b/go/src/reader/reader.go @@ -42,7 +42,7 @@ func tokenize(str string) []string { results := make([]string, 0, 1) // Work around lack of quoting in backtick re := regexp.MustCompile(`[\s,]*(~@|[\[\]{}()'` + "`" + - `~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"` + "`" + + `~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"` + "`" + `,;)]*)`) for _, group := range re.FindAllStringSubmatch(str, -1) { if (group[1] == "") || (group[1][0] == ';') { @@ -66,6 +66,9 @@ func read_atom(rdr Reader) (MalType, error) { } return i, nil } else if (*token)[0] == '"' { + if (*token)[len(*token)-1] != '"' { + return nil, errors.New("expected '\"', got EOF") + } str := (*token)[1 : len(*token)-1] return strings.Replace( strings.Replace( diff --git a/groovy/reader.groovy b/groovy/reader.groovy index b47f357e50..8b168d1e17 100644 --- a/groovy/reader.groovy +++ b/groovy/reader.groovy @@ -29,7 +29,7 @@ class reader { } def static tokenizer(String str) { - def m = str =~ /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/ + def m = str =~ /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/ def tokens = [] while (m.find()) { String token = m.group(1) @@ -44,7 +44,7 @@ class reader { def static read_atom(Reader rdr) { def token = rdr.next() - def m = token =~ /(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^"(.*)"$|:(.*)|(^[^"]*$)/ + def m = token =~ /(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^"(.*)"$|^"(.*)$|:(.*)|(^[^"]*$)/ if (!m.find()) { throw new MalException("unrecognized token '$token'") } @@ -57,11 +57,16 @@ class reader { } else if (m.group(5) != null) { false } else if (m.group(6) != null) { + if (token[token.length() - 1] != '"') { + throw new MalException("expected '\"', got EOF") + } StringEscapeUtils.unescapeJava(m.group(6)) } else if (m.group(7) != null) { - "\u029e" + m.group(7) + throw new MalException("expected '\"', got EOF") } else if (m.group(8) != null) { - new MalSymbol(m.group(8)) + "\u029e" + m.group(8) + } else if (m.group(9) != null) { + new MalSymbol(m.group(9)) } else { throw new MalException("unrecognized '${m.group(0)}'") } diff --git a/haxe/reader/Reader.hx b/haxe/reader/Reader.hx index d06f8008fe..53d6b5181f 100644 --- a/haxe/reader/Reader.hx +++ b/haxe/reader/Reader.hx @@ -73,7 +73,7 @@ class Reader { "\n"), "\""), "\\")); - case _ if (re_str.match(token)): + case _ if (re_str_bad.match(token)): throw 'expected \'"\', got EOF'; case _: MalSymbol(token); diff --git a/hy/reader.hy b/hy/reader.hy index 4c9bd1d525..0bf9f9c17f 100644 --- a/hy/reader.hy +++ b/hy/reader.hy @@ -17,6 +17,8 @@ (def tok-re (.compile re "[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:[\\\\].|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}()'\"`@,;]+)")) (def int-re (.compile re "-?[0-9]+$")) +(def str-re (.compile re "^\".*\"$")) +(def str-bad-re (.compile re "^\".*$")) (defn tokenize [str] (list-comp @@ -34,7 +36,8 @@ (setv token (.next rdr)) (if (.match re int-re token) (int token) - (= "\"" (get token 0)) (Str (unescape (cut token 1 -1))) + (.match re str-re token) (Str (unescape (cut token 1 -1))) + (.match re str-bad-re token) (raise (Exception (+ "expected '\"', got EOF"))) (= ":" (get token 0)) (Keyword token) (= "nil" token) None (= "true" token) True diff --git a/io/MalReader.io b/io/MalReader.io index b753913706..354ec30b3c 100644 --- a/io/MalReader.io +++ b/io/MalReader.io @@ -16,7 +16,7 @@ MalReader := Object clone do ( ) ) - tokenizerRegex := Regex with("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)") + tokenizerRegex := Regex with("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"?|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)") tokenize := method(str, tokenizerRegex matchesIn(str) \ @@ -28,6 +28,7 @@ MalReader := Object clone do ( numberRegex := Regex with("^-?[0-9]+$") read_string := method(token, + (token endsWithSeq("\"")) ifFalse(Exception raise("expected '\"', got EOF")) placeholder := 127 asCharacter token exSlice(1, -1) replaceSeq("\\\\", placeholder) replaceSeq("\\\"", "\"") replaceSeq("\\n", "\n") replaceSeq(placeholder, "\\") ) diff --git a/java/src/main/java/mal/reader.java b/java/src/main/java/mal/reader.java index 7c9d3aa07b..41b9349f91 100644 --- a/java/src/main/java/mal/reader.java +++ b/java/src/main/java/mal/reader.java @@ -35,7 +35,7 @@ public String next() { public static ArrayList tokenize(String str) { ArrayList tokens = new ArrayList(); - Pattern pattern = Pattern.compile("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)"); + Pattern pattern = Pattern.compile("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"?|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)"); Matcher matcher = pattern.matcher(str); while (matcher.find()) { String token = matcher.group(1); @@ -51,7 +51,7 @@ public static ArrayList tokenize(String str) { public static MalVal read_atom(Reader rdr) throws ParseError { String token = rdr.next(); - Pattern pattern = Pattern.compile("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|:(.*)|(^[^\"]*$)"); + Pattern pattern = Pattern.compile("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|^\"(.*)$|:(.*)|(^[^\"]*$)"); Matcher matcher = pattern.matcher(token); if (!matcher.find()) { throw new ParseError("unrecognized token '" + token + "'"); @@ -67,9 +67,11 @@ public static MalVal read_atom(Reader rdr) } else if (matcher.group(6) != null) { return new MalString(StringEscapeUtils.unescapeJson(matcher.group(6))); } else if (matcher.group(7) != null) { - return new MalString("\u029e" + matcher.group(7)); + throw new ParseError("expected '\"', got EOF"); } else if (matcher.group(8) != null) { - return new MalSymbol(matcher.group(8)); + return new MalString("\u029e" + matcher.group(8)); + } else if (matcher.group(9) != null) { + return new MalSymbol(matcher.group(9)); } else { throw new ParseError("unrecognized '" + matcher.group(0) + "'"); } diff --git a/julia/reader.jl b/julia/reader.jl index 29b5dc9548..6763bce635 100644 --- a/julia/reader.jl +++ b/julia/reader.jl @@ -26,7 +26,7 @@ end function tokenize(str) - re = r"[\s,]*(~@|[\[\]{}()'`~^@]|\"(?:\\.|[^\\\"])*\"|;.*|[^\s\[\]{}('\"`,;)]*)" + re = r"[\s,]*(~@|[\[\]{}()'`~^@]|\"(?:\\.|[^\\\"])*\"?|;.*|[^\s\[\]{}('\"`,;)]*)" tokens = map((m) -> m.captures[1], eachmatch(re, str)) filter((t) -> t != "" && t[1] != ';', tokens) end @@ -41,6 +41,8 @@ function read_atom(rdr) replace(token[2:end-1], r"\\.", (r) -> get(Dict("\\n"=>"\n", "\\\""=>"\"", "\\\\"=>"\\"), r, r)) + elseif ismatch(r"^\".*$", token) + error("expected '\"', got EOF") elseif token[1] == ':' "\u029e$(token[2:end])" elseif token == "nil" diff --git a/kotlin/src/mal/reader.kt b/kotlin/src/mal/reader.kt index 3b23be5f04..48b258e3ad 100644 --- a/kotlin/src/mal/reader.kt +++ b/kotlin/src/mal/reader.kt @@ -2,8 +2,8 @@ package mal import kotlin.text.Regex -val TOKEN_REGEX = Regex("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;.*|[^\\s\\[\\]{}('\"`,;)]*)") -val ATOM_REGEX = Regex("(^-?[0-9]+$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|:(.*)|(^[^\"]*$)") +val TOKEN_REGEX = Regex("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}('\"`,;)]*)") +val ATOM_REGEX = Regex("(^-?[0-9]+$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|^\"(.*)$|:(.*)|(^[^\"]*$)") class Reader(sequence: Sequence) { val tokens = sequence.iterator() @@ -145,9 +145,11 @@ fun read_atom(reader: Reader): MalType { else m.groups[1]?.value.toString() }) } else if (groups[6]?.value != null) { - MalKeyword(groups[6]?.value as String) + throw MalReaderException("expected '\"', got EOF") } else if (groups[7]?.value != null) { - MalSymbol(groups[7]?.value as String) + MalKeyword(groups[7]?.value as String) + } else if (groups[8]?.value != null) { + MalSymbol(groups[8]?.value as String) } else { throw MalReaderException("Unrecognized token: " + next) } diff --git a/livescript/reader.ls b/livescript/reader.ls index 96aad575ac..0eba293d83 100644 --- a/livescript/reader.ls +++ b/livescript/reader.ls @@ -47,7 +47,7 @@ tokenizer = (str) -> [\s,]* # whitespace or commas ( ~@ # special two-char ~@ | [\[\]{}()'`~^@] # special single char one of []{}'`~^@ - | "(?:\\.| [^\\"])*" # double-quoted string + | "(?:\\.| [^\\"])*"? # double-quoted string | ;.* # any seq of chars starting ; | [^\s\[\]{}('"`,;)]+ # seq of non-special chars: symbols, numbers, ) # "true", "false" and "nil". @@ -112,6 +112,8 @@ read_atom = (reader) -> if token in constants {type: \const, value: reader.next!} else if token[0] == '"' + if not token.endsWith '"' + parse-error "expected '\"', got EOF" {type: \string, value: decode-string reader.next!} else if token.match /^-?\d+$/ {type: \int, value: parseInt reader.next!} diff --git a/logo/reader.lg b/logo/reader.lg index dc4c85171e..049428d0c3 100644 --- a/logo/reader.lg +++ b/logo/reader.lg @@ -84,7 +84,7 @@ while [not emptyp :rest] [ make "w word :w :c make "rest butfirst :rest ] -(throw "error [Expected closing quotes]) +(throw "error [Expected closing quotes, not EOF]) end to read_next_token :s diff --git a/make/reader.mk b/make/reader.mk index 3f620a1171..df635cf859 100755 --- a/make/reader.mk +++ b/make/reader.mk @@ -85,7 +85,7 @@ $(foreach ch,$(word 1,$($(1))),\ $(call __string,$(strip $(call READ_STRING,$(1))))\ $(eval $(if $(filter $(DQUOTE),$(word 1,$($(1)))),\ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1)))),\ - $(call _error,Expected '$(DQUOTE)' in; $($(1))))),\ + $(call _error,Expected '$(DQUOTE)' in; $($(1))$(COMMA) got EOF))),\ $(if $(filter $(COLON),$(ch)),\ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ $(call _keyword,$(call READ_KEYWORD,$(1))),\ diff --git a/matlab/reader.m b/matlab/reader.m index 937c9534dd..9bfc72be41 100644 --- a/matlab/reader.m +++ b/matlab/reader.m @@ -2,7 +2,7 @@ classdef reader methods (Static = true) function tokens = tokenize(str) - re = '[\s,]*(~@|[\[\]{}()''`~^@]|"(?:\\.|[^\\"])*"|;[^\n]*|[^\s\[\]{}(''"`,;)]*)'; + re = '[\s,]*(~@|[\[\]{}()''`~^@]|"(?:\\.|[^\\"])*"?|;[^\n]*|[^\s\[\]{}(''"`,;)]*)'; % extract the capture group (to ignore spaces and commas) tokens = cellfun(@(x) x(1), regexp(str, re, 'tokens')); comments = cellfun(@(x) length(x) > 0 && x(1) == ';', tokens); @@ -15,6 +15,9 @@ if not(isempty(regexp(token, '^-?[0-9]+$', 'match'))) atm = str2double(token); elseif strcmp(token(1), '"') + if not(token(end) == '"') + error('expected ''"'', got EOF'); + end atm = token(2:length(token)-1); atm = strrep(atm, '\\', char(255)); atm = strrep(atm, '\"', '"'); diff --git a/miniMAL/reader.json b/miniMAL/reader.json index dafa691f8c..b1de4d98ac 100644 --- a/miniMAL/reader.json +++ b/miniMAL/reader.json @@ -24,7 +24,7 @@ ["re-matches", "re", "strn", ["concat", "acc", "g1"]]]]]], ["def", "tokenize", ["fn", ["strn"], - ["let", ["re-str", ["`", "[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;.*|[^\\s\\[\\]{}('\"`,;)]*)"], + ["let", ["re-str", ["`", "[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}('\"`,;)]*)"], "re", ["RegExp", "re-str", ["`", "g"]]], [".", ["re-matches", "re", "strn", ["`", []]], @@ -37,13 +37,15 @@ ["if", [".", "token", ["`", "match"], ["RegExp", ["`", "^-?[0-9]+$"]]], ["parseInt", "token", 10], ["if", ["=", ["`", "\""], ["get", "token", 0]], - [".", - ["slice", "token", 1, ["-", ["count", "token"], 1]], - ["`", "replace"], ["RegExp", ["`", "\\\\(.)"], ["`", "g"]], - ["fn", ["_", "c"], - ["if", ["=", "c", ["`", "n"]], - ["`", "\n"], - "c"]]], + ["if", ["=", ["`", "\""], ["get", "token", ["-", ["count", "token"], 1]]], + [".", + ["slice", "token", 1, ["-", ["count", "token"], 1]], + ["`", "replace"], ["RegExp", ["`", "\\\\(.)"], ["`", "g"]], + ["fn", ["_", "c"], + ["if", ["=", "c", ["`", "n"]], + ["`", "\n"], + "c"]]], + ["throw", ["`", "expected '\"', got EOF"]]], ["if", ["=", ["`", ":"], ["get", "token", 0]], ["keyword", ["slice", "token", 1]], ["if", ["=", ["`", "nil"], "token"], diff --git a/nim/reader.nim b/nim/reader.nim index 6bc2997fee..7c61dbdde1 100644 --- a/nim/reader.nim +++ b/nim/reader.nim @@ -1,7 +1,7 @@ import re, strutils, sequtils, types let - tokenRE = re"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)""" + tokenRE = re"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)""" intRE = re"-?[0-9]+$" type @@ -61,7 +61,9 @@ proc read_hash_map(r: var Reader): MalType = proc read_atom(r: var Reader): MalType = let t = r.next if t.match(intRE): number t.parseInt - elif t[0] == '"': str t[1 .. '' then - read_atom := TMalString.Create(#127 + RE.Match[7]) + raise Exception.Create('expected ''"'', got EOF') else if RE.Match[8] <> '' then + read_atom := TMalString.Create(#127 + RE.Match[8]) + else if RE.Match[9] <> '' then read_atom := TMalSymbol.Create(Token); end else diff --git a/ocaml/reader.ml b/ocaml/reader.ml index fa009e255a..24cd1e927d 100644 --- a/ocaml/reader.ml +++ b/ocaml/reader.ml @@ -18,7 +18,7 @@ let gsub re f str = "" (List.map (function | Str.Delim x -> f x | Str.Text x -> x) (Str.full_split re str)) -let token_re = (Str.regexp "~@\\|[][{}()'`~^@]\\|\"\\(\\\\.\\|[^\"]\\)*\"\\|;.*\\|[^][ \n{}('\"`,;)]*") +let token_re = (Str.regexp "~@\\|[][{}()'`~^@]\\|\"\\(\\\\.\\|[^\"]\\)*\"?\\|;.*\\|[^][ \n{}('\"`,;)]*") type reader = { form : Types.mal_type; @@ -43,11 +43,15 @@ let read_atom token = | _ -> (match token.[1] with | '0'..'9' -> T.Int (int_of_string token) | _ -> Types.symbol token)) - | '"' -> T.String (gsub (Str.regexp "\\\\.") - (function - | "\\n" -> "\n" - | x -> String.sub x 1 1) - (String.sub token 1 ((String.length token) - 2))) + | '"' -> (match token.[String.length token - 1] with + | '"' -> T.String (gsub (Str.regexp "\\\\.") + (function + | "\\n" -> "\n" + | x -> String.sub x 1 1) + (String.sub token 1 ((String.length token) - 2))) + | _ -> output_string stderr ("expected '\"', got EOF\n"); + flush stderr; + raise End_of_file) | ':' -> T.Keyword (Str.replace_first (Str.regexp "^:") "" token) | _ -> Types.symbol token diff --git a/php/reader.php b/php/reader.php index 3408b1ae1f..055dee9b00 100644 --- a/php/reader.php +++ b/php/reader.php @@ -27,7 +27,7 @@ function _real_token($s) { } function tokenize($str) { - $pat = "/[\s,]*(php\/|~@|[\[\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;.*|[^\s\[\]{}('\"`,;)]*)/"; + $pat = "/[\s,]*(php\/|~@|[\[\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"?|;.*|[^\s\[\]{}('\"`,;)]*)/"; preg_match_all($pat, $str, $matches); return array_values(array_filter($matches[1], '_real_token')); } @@ -37,6 +37,9 @@ function read_atom($reader) { if (preg_match("/^-?[0-9]+$/", $token)) { return intval($token, 10); } elseif ($token[0] === "\"") { + if (substr($token, -1) !== "\"") { + throw new Exception("expected '\"', got EOF"); + } $str = substr($token, 1, -1); $str = str_replace('\\\\', chr(0x7f), $str); $str = str_replace('\\"', '"', $str); diff --git a/plpgsql/reader.sql b/plpgsql/reader.sql index cf1d8fdb5c..4210961592 100644 --- a/plpgsql/reader.sql +++ b/plpgsql/reader.sql @@ -5,7 +5,7 @@ CREATE SCHEMA reader; CREATE FUNCTION reader.tokenize(str varchar) RETURNS varchar[] AS $$ DECLARE - re varchar = E'[[:space:] ,]*(~@|[\\[\\]{}()\'`~@]|"(?:[\\\\].|[^\\\\"])*"|;[^\n]*|[^\\s \\[\\]{}()\'"`~@,;]*)'; + re varchar = E'[[:space:] ,]*(~@|[\\[\\]{}()\'`~@]|"(?:[\\\\].|[^\\\\"])*"?|;[^\n]*|[^\\s \\[\\]{}()\'"`~@,;]*)'; BEGIN RETURN ARRAY(SELECT tok FROM (SELECT (regexp_matches(str, re, 'g'))[1] AS tok) AS x @@ -44,6 +44,8 @@ BEGIN str := replace(str, '\n', E'\n'); str := replace(str, chr(CAST(x'7f' AS integer)), E'\\'); result := types._stringv(str); + ELSIF token ~ '^".*' THEN -- unclosed string + RAISE EXCEPTION 'expected ''"'', got EOF'; ELSIF token ~ '^:.*' THEN -- keyword -- keyword result := types._keywordv(substring(token FROM 2 FOR (char_length(token)-1))); diff --git a/plsql/reader.sql b/plsql/reader.sql index b48e7c0906..e5e37cf389 100644 --- a/plsql/reader.sql +++ b/plsql/reader.sql @@ -44,7 +44,7 @@ CREATE OR REPLACE PACKAGE BODY reader AS -- tokenize: -- takes a string and returns a nested table of token strings FUNCTION tokenize(str varchar) RETURN tokens IS - re varchar2(100) := '[[:space:] ,]*(~@|[][{}()''`~@]|"(([\].|[^\"])*)"|;[^' || chr(10) || ']*|[^][[:space:] {}()''"`~@,;]*)'; + re varchar2(100) := '[[:space:] ,]*(~@|[][{}()''`~@]|"(([\].|[^\"])*)"?|;[^' || chr(10) || ']*|[^][[:space:] {}()''"`~@,;]*)'; tok CLOB; toks tokens := tokens(); cnt integer; @@ -90,6 +90,9 @@ BEGIN str := REPLACE(str, '\n', chr(10)); str := REPLACE(str, '\\', chr(92)); result := types.string(M, str); + ELSIF REGEXP_LIKE(token, '^".*') THEN -- unclosed string + raise_application_error(-20003, + 'expected ''"'', got EOF', TRUE); ELSIF REGEXP_LIKE(token, '^:.*') THEN -- keyword -- keyword result := types.keyword(M, SUBSTR(token, 2, LENGTH(token)-1)); @@ -127,7 +130,7 @@ BEGIN token := rdr.peek(); IF token IS NULL THEN raise_application_error(-20003, - 'expected ''' || last || '''', TRUE); + 'expected ''' || last || ''', got EOF', TRUE); END IF; IF token = last THEN EXIT; END IF; items.EXTEND(); diff --git a/powershell/reader.psm1 b/powershell/reader.psm1 index b957ca2055..18d8f48477 100644 --- a/powershell/reader.psm1 +++ b/powershell/reader.psm1 @@ -20,7 +20,7 @@ Class Reader { function tokenize { - $r = [regex]"[\s,]*(~@|[\[\]{}()'``~^@]|`"(?:\\.|[^\\`"])*`"|;.*|[^\s\[\]{}('`"``,;)]*)" + $r = [regex]"[\s,]*(~@|[\[\]{}()'``~^@]|`"(?:\\.|[^\\`"])*`"?|;.*|[^\s\[\]{}('`"``,;)]*)" $r.Matches($args) | Where-Object { $_.Groups.Item(1).Value.Length -gt 0 -and $_.Groups.Item(1).Value[0] -ne ";" } | @@ -38,6 +38,8 @@ function read_atom([Reader] $rdr) { $s = $s -replace "\\n", "`n" $s = $s -replace "$([char]0x29e)", "\" return $s + } elseif ($token -match "^`".*") { + throw "expected '`"', got EOF" } elseif ($token -match ":.*") { return "$([char]0x29e)$($token.substring(1))" } elseif ($token -eq "true") { diff --git a/process/guide.md b/process/guide.md index aba0022f09..49bfb3b574 100644 --- a/process/guide.md +++ b/process/guide.md @@ -318,7 +318,7 @@ expression support. of all the tokens (strings) in it. The following regular expression (PCRE) will match all mal tokens. ``` -[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*) +[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*) ``` * For each match captured within the parenthesis starting at char 6 of the regular expression a new token will be created. @@ -331,9 +331,11 @@ expression support. * ```[\[\]{}()'`~^@]```: Captures any special single character, one of ```[]{}()'`~^@``` (tokenized). - * `"(?:\\.|[^\\"])*"`: Starts capturing at a double-quote and stops at the + * `"(?:\\.|[^\\"])*"?`: Starts capturing at a double-quote and stops at the next double-quote unless it was proceeded by a backslash in which case it - includes it until the next double-quote (tokenized). + includes it until the next double-quote (tokenized). It will also + match unbalanced strings (no ending double-quote) which should be + reported as an error. * `;.*`: Captures any sequence of characters starting with `;` (tokenized). diff --git a/r/reader.r b/r/reader.r index b2729f840e..be163e6306 100644 --- a/r/reader.r +++ b/r/reader.r @@ -21,7 +21,7 @@ Reader.next <- function(rdr) { } tokenize <- function(str) { - re <- "[\\s,]*(~@|[\\[\\]\\{\\}\\(\\)'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;.*|[^\\s\\[\\]\\{\\}\\('\"`,;\\)]*)" + re <- "[\\s,]*(~@|[\\[\\]\\{\\}\\(\\)'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]\\{\\}\\('\"`,;\\)]*)" m <- lapply(regmatches(str, gregexpr(re, str, perl=TRUE)), function(e) sub("^[\\s,]+", "", e, perl=TRUE)) res <- list() @@ -43,6 +43,9 @@ read_atom <- function(rdr) { } else if (re_match("^-?[0-9][0-9.]*$", token)) { as.double(token) } else if (substr(token,1,1) == "\"") { + if (substr(token, nchar(token), nchar(token)) != "\"") { + throw("expected '\"', got EOF") + } gsub("\x7f", "\\\\", gsub("\\\\n", "\n", gsub("\\\\\"", "\"", diff --git a/racket/reader.rkt b/racket/reader.rkt index 1e7f5fa779..93e49ef449 100644 --- a/racket/reader.rkt +++ b/racket/reader.rkt @@ -22,7 +22,7 @@ (define (tokenize str) (filter-not (lambda (s) (or (equal? s "") (equal? (substring s 0 1) ";"))) - (regexp-match* #px"[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;[^\n]*|[^\\s\\[\\]{}('\"`,;)]*)" + (regexp-match* #px"[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"?|;[^\n]*|[^\\s\\[\\]{}('\"`,;)]*)" str #:match-select cadr))) (define (read_atom rdr) @@ -33,6 +33,8 @@ (string->number token)] [(regexp-match #px"^\".*\"$" token) (with-input-from-string token read)] + [(regexp-match #px"^\".*$" token) + (raise "expected '\"', got EOF")] [(regexp-match #px"^:" token) (_keyword (substring token 1))] [(equal? "nil" token) nil] [(equal? "true" token) #t] diff --git a/rexx/reader.rexx b/rexx/reader.rexx index 619e034a89..9443865e4a 100644 --- a/rexx/reader.rexx +++ b/rexx/reader.rexx @@ -114,7 +114,13 @@ read_atom: procedure expose values. tokens. pos /* read_atom() */ when token == "true" then return new_true() when token == "false" then return new_false() when substr(token, 1, 1) == ':' then return new_keyword(parse_keyword(token)) - when substr(token, 1, 1) == '"' then return new_string(parse_string(token)) + when substr(token, 1, 1) == '"' then do + if substr(token, length(token), 1) \== '"' then do + err = "expected '" || end_char || "', got EOF" + return "ERR" + end + return new_string(parse_string(token)) + end otherwise return new_symbol(token) end diff --git a/rpython/reader.py b/rpython/reader.py index 4f5e3b4d8e..b9208be20d 100644 --- a/rpython/reader.py +++ b/rpython/reader.py @@ -28,7 +28,7 @@ def peek(self): return None def tokenize(str): - re_str = "[\s,]*(~@|[\[\]{}()'`~^@]|\"(?:[\\\\].|[^\\\\\"])*\"|;.*|[^\s\[\]{}()'\"`@,;]+)" + re_str = "[\s,]*(~@|[\[\]{}()'`~^@]|\"(?:[\\\\].|[^\\\\\"])*\"?|;.*|[^\s\[\]{}()'\"`@,;]+)" if IS_RPYTHON: tok_re = re_str else: @@ -47,8 +47,10 @@ def read_atom(reader): ## elif re.match(float_re, token): return int(token) elif token[0] == '"': end = len(token)-1 - if end < 2: + if end == 1: return MalStr(u"") + elif end < 1 or token[end] != '"': + types.throw_str("expected '\"', got EOF") else: s = unicode(token[1:end]) s = types._replace(u'\\\\', u"\u029e", s) diff --git a/ruby/reader.rb b/ruby/reader.rb index 446f7ae5d7..34d9fbe608 100644 --- a/ruby/reader.rb +++ b/ruby/reader.rb @@ -16,7 +16,7 @@ def next def tokenize(str) - re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/ + re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/ return str.scan(re).map{|m| m[0]}.select{ |t| t != "" && t[0..0] != ";" } @@ -32,6 +32,7 @@ def read_atom(rdr) when /^-?[0-9]+$/ then token.to_i # integer when /^-?[0-9][0-9.]*$/ then token.to_f # float when /^".*"$/ then parse_str(token) # string + when /^".*$/ then raise "expected '\"', got EOF" when /^:/ then "\u029e" + token[1..-1] # keyword when "nil" then nil when "true" then true diff --git a/scala/reader.scala b/scala/reader.scala index 891e677c03..cbd9ec52e4 100644 --- a/scala/reader.scala +++ b/scala/reader.scala @@ -19,7 +19,7 @@ object reader { } def tokenize(str: String): Array[String] = { - val re = """[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)""".r + val re = """[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)""".r re.findAllMatchIn(str).map{ _.group(1) } .filter{ s => s != "" && s(0) != ';' } .toArray @@ -38,11 +38,14 @@ object reader { val re_int = """^(-?[0-9]+)$""".r val re_flt = """^(-?[0-9][0-9.]*)$""".r val re_str = """^"(.*)"$""".r + val re_str_bad = """^"(.*)$""".r val re_key = """^:(.*)$""".r return token match { case re_int(i) => i.toLong // integer case re_flt(f) => f.toDouble // float case re_str(s) => parse_str(s) // string + case re_str_bad(s) => + throw new Exception("expected '\"', got EOF") case re_key(k) => "\u029e" + k // keyword case "nil" => null case "true" => true diff --git a/skew/reader.sk b/skew/reader.sk index 0aa723ba27..f383fdb9cd 100644 --- a/skew/reader.sk +++ b/skew/reader.sk @@ -17,7 +17,7 @@ class Reader { } def tokenize(str string) List { - var re = RegExp.new("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;.*|[^\\s\\[\\]{}('\"`,;)]*)", "g") + var re = RegExp.new("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}('\"`,;)]*)", "g") var tokens List = [] var match string while (match = re.exec(str)[1]) != "" { diff --git a/swift/reader.swift b/swift/reader.swift index 195cfe36b4..9e4cadf75e 100644 --- a/swift/reader.swift +++ b/swift/reader.swift @@ -14,7 +14,7 @@ private let token_pattern = "|" + "[\\[\\]{}()`'~^@]" + // Punctuation: Any one of []{}()`'~^@ "|" + - "\"(?:\\\\.|[^\\\\\"])*\"" + // Quoted string: characters other than \ or ", or any escaped characters + "\"(?:\\\\.|[^\\\\\"])*\"?" + // Quoted string: characters other than \ or ", or any escaped characters "|" + ";.*" + // Comment: semicolon followed by anything "|" + @@ -36,6 +36,8 @@ private let atom_pattern = "|" + "(^\".*\"$)" + // String "|" + + "(^\".*$)" + // Invalid/unclosed string + "|" + "(:.*)" + // Keyword "|" + "(^[^\"]*$)" // Symbol @@ -112,9 +114,11 @@ private func read_atom(token: String) throws -> MalVal { return make_false() } else if have_match(match, at_index: 7) { // String return make_string(unescape(token)) - } else if have_match(match, at_index: 8) { // Keyword + } else if have_match(match, at_index: 8) { // Invalid/unclosed string + try throw_error("expected '\"', got EOF") + } else if have_match(match, at_index: 9) { // Keyword return make_keyword(token[token.startIndex.successor() ..< token.endIndex]) - } else if have_match(match, at_index: 9) { // Symbol + } else if have_match(match, at_index: 10) { // Symbol return make_symbol(token) } } diff --git a/swift3/Sources/reader.swift b/swift3/Sources/reader.swift index 66cef2d3a2..f729dd6c8b 100644 --- a/swift3/Sources/reader.swift +++ b/swift3/Sources/reader.swift @@ -74,7 +74,7 @@ func read_string(_ rdr: Reader) throws -> MalVal { if rdr.str[cidx] == "\"" { break } cidx = rdr.pos } - if rdr.pos > rdr.str.endIndex { + if rdr.str[rdr.str.index(before: rdr.pos)] != "\"" { throw MalError.Reader(msg: "Expected '\"', got EOF") } let matchStr = rdr.str.substring(with: diff --git a/tcl/reader.tcl b/tcl/reader.tcl index b5b4a6f297..ee7a7ec4f5 100644 --- a/tcl/reader.tcl +++ b/tcl/reader.tcl @@ -18,7 +18,7 @@ oo::class create Reader { } proc tokenize str { - set re {[\s,]*(~@|[\[\]\{\}()'`~^@]|\"(?:\\.|[^\\\"])*\"|;.*|[^\s\[\]\{\}('\"`~^@,;)]*)} + set re {[\s,]*(~@|[\[\]\{\}()'`~^@]|\"(?:\\.|[^\\\"])*\"?|;.*|[^\s\[\]\{\}('\"`~^@,;)]*)} set tokens {} foreach {_ capture} [regexp -line -all -inline $re $str] { if {[string length $capture] > 0 && [string range $capture 0 0] != ";"} { @@ -84,6 +84,7 @@ proc read_atom {reader} { ^false$ { return $::mal_false } ^: { return [keyword_new [parse_keyword $token]] } ^\".*\"$ { return [string_new [parse_string $token]] } + ^\".*$ { error "expected '\"', got EOF" } default { return [symbol_new $token] } } } diff --git a/tests/step1_read_print.mal b/tests/step1_read_print.mal index 266b016033..a40e9dba19 100644 --- a/tests/step1_read_print.mal +++ b/tests/step1_read_print.mal @@ -82,11 +82,11 @@ false ;;; These should throw some error with no return value "abc -;/.+ +;/.*(EOF|end of input|unbalanced).* (1 "abc -;/.+ +;/.*(EOF|end of input|unbalanced).* (1 "abc" -;/.+ +;/.*(EOF|end of input|unbalanced).* ;; Testing read of quoting '1 diff --git a/ts/reader.ts b/ts/reader.ts index 6a900ab09f..107823e8db 100644 --- a/ts/reader.ts +++ b/ts/reader.ts @@ -23,7 +23,7 @@ export function readStr(input: string): MalType { } function tokenizer(input: string): string[] { - const regexp = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g; + const regexp = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/g; const tokens: string[] = []; while (true) { const matches = regexp.exec(input); @@ -123,6 +123,9 @@ function readAtom(reader: Reader): MalType { return new MalNumber(v); } if (token[0] === '"') { + if (token.slice(-1) !== '"') { + throw new Error("expected '\"', got EOF"); + } const v = token.slice(1, token.length - 1) .replace(/\\(.)/g, (_, c: string) => c == 'n' ? '\n' : c) return new MalString(v); diff --git a/vb/reader.vb b/vb/reader.vb index 89b2c64e1b..b0a56fac91 100644 --- a/vb/reader.vb +++ b/vb/reader.vb @@ -48,7 +48,7 @@ Namespace Mal Shared Function tokenize(str As String) As List(Of String) Dim tokens As New List(Of String) - Dim pattern As String = "[\s ,]*(~@|[\[\]{}()'`~@]|""(?:[\\].|[^\\""])*""|;.*|[^\s \[\]{}()'""`~@,;]*)" + Dim pattern As String = "[\s ,]*(~@|[\[\]{}()'`~@]|""(?:[\\].|[^\\""])*""?|;.*|[^\s \[\]{}()'""`~@,;]*)" Dim regex As New Regex(pattern) For Each match As Match In regex.Matches(str) Dim token As String = match.Groups(1).Value @@ -64,7 +64,7 @@ Namespace Mal Shared Function read_atom(rdr As Reader) As MalVal Dim token As String = rdr.get_next() - Dim pattern As String = "(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^("".*"")$|^:(.*)|(^[^""]*$)" + Dim pattern As String = "(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^("".*)|^:(.*)|(^[^""]*$)" Dim regex As Regex = New Regex(pattern) Dim match As Match = regex.Match(token) 'Console.WriteLine("token: ^" + token + "$") @@ -81,6 +81,9 @@ Namespace Mal return Mal.types.MalFalse Else If match.Groups(6).Value <> String.Empty Then Dim str As String = match.Groups(6).Value + If str(str.Length-1) <> """" Then + throw New ParseError("expected '""', got EOF") + End If return New Mal.types.MalString( str.Substring(1, str.Length-2) _ .Replace("\\", ChrW(&H029e)) _ diff --git a/vhdl/reader.vhdl b/vhdl/reader.vhdl index b19788411a..d4f7e2b02e 100644 --- a/vhdl/reader.vhdl +++ b/vhdl/reader.vhdl @@ -191,7 +191,7 @@ package body reader is deallocate(s); end procedure unescape_string_token; - procedure read_atom(r: inout reader_class; result: out mal_val_ptr) is + procedure read_atom(r: inout reader_class; result: out mal_val_ptr; err: out mal_val_ptr) is variable token, s: line; variable num: integer; variable ch: character; @@ -221,6 +221,11 @@ package body reader is s(1 to s'length) := token(2 to token'length); new_keyword(s, result); when '"' => + if token(token'length) /= '"' then + new_string("expected '""', got EOF", err); + result := null; + return; + end if; unescape_string_token(token, s); new_string(s, result); when others => @@ -328,7 +333,7 @@ package body reader is when ']' => new_string("unexcepted ']'", err); when '{' => read_sequence(mal_hashmap, "}", r, result, err); when '}' => new_string("unexcepted '}'", err); - when others => read_atom(r, result); + when others => read_atom(r, result, err); end case; end procedure read_form; diff --git a/wasm/reader.wam b/wasm/reader.wam index 0a46736535..548cacf85d 100644 --- a/wasm/reader.wam +++ b/wasm/reader.wam @@ -253,7 +253,7 @@ (local.set $slen ($strlen (i32.add $tok 1))) (if (i32.ne (i32.load8_u (i32.add $tok $slen)) (CHR "\"")) (then - ($THROW_STR_0 "expected '\"'") + ($THROW_STR_0 "expected '\"', got EOF") (return 0)) (else ;; unescape backslashes, quotes, and newlines diff --git a/yorick/reader.i b/yorick/reader.i index 6da51d4041..571192e202 100644 --- a/yorick/reader.i +++ b/yorick/reader.i @@ -1,7 +1,7 @@ #include "yeti_regex.i" require, "types.i" -TOKENIZER_REGEXP = regcomp("[[:space:],]*(~@|[][{}()'`~@]|\"([\\].|[^\\\"])*\"|;.*|[^][[:space:]{}()'\"`~@,;]*)", newline=1) +TOKENIZER_REGEXP = regcomp("[[:space:],]*(~@|[][{}()'`~@]|\"([\\].|[^\\\"])*\"?|;.*|[^][[:space:]{}()'\"`~@,;]*)", newline=1) func tokenize(str) { @@ -45,6 +45,8 @@ func reader_next(rdr) } NUMBER_REGEXP = regcomp("^-?[0-9]+$") +STR_REGEXP = regcomp("^\".*\"$") +STR_BAD_REGEXP = regcomp("^\".*$") func unescape(s) { @@ -62,7 +64,8 @@ func read_atom(rdr) else if (token == "true") return MAL_TRUE else if (token == "false") return MAL_FALSE else if (regmatch(NUMBER_REGEXP, token)) return MalNumber(val=tonum(token)) - else if (strpart(token, 1:1) == "\"") return MalString(val=unescape(token)) + else if (regmatch(STR_REGEXP, token)) return MalString(val=unescape(token)) + else if (regmatch(STR_BAD_REGEXP, token)) return MalError(message=("expected '\"', got EOF")) else if (strpart(token, 1:1) == ":") return MalKeyword(val=strpart(token, 2:)) else return MalSymbol(val=token) } From 08f92c5ae9f477019849195e6937b591d3d3f6d5 Mon Sep 17 00:00:00 2001 From: Andrea Crotti Date: Sun, 27 Jan 2019 18:21:57 +0000 Subject: [PATCH 0742/2308] upgrade clojure and tools.reader --- clojure/project.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clojure/project.clj b/clojure/project.clj index 5bb5f960d0..a675e2cc1e 100644 --- a/clojure/project.clj +++ b/clojure/project.clj @@ -1,8 +1,8 @@ (defproject mal "0.0.1-SNAPSHOT" :description "Make-A-Lisp" - :dependencies [[org.clojure/clojure "1.9.0"] - [org.clojure/tools.reader "0.8.3"] + :dependencies [[org.clojure/clojure "1.10.0"] + [org.clojure/tools.reader "1.3.2"] [net.n01se/clojure-jna "1.0.0"]] ;; To run a step with correct readline behavior: From 18f0ec21509231d6aa72af39149c28bd07ade057 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 28 Jan 2019 10:27:41 -0600 Subject: [PATCH 0743/2308] Runtest should fail on bad test data. Fix interop tests. The interop tests weren't updated when regex support was added to runtest for matching output data. This was causing a bunch of implementations to silently fail while testing interop. Fix runtest.py to fail on bad test data and fix 21 implementations with the old style output matches. --- bash/tests/stepA_mal.mal | 2 +- clojure/tests/stepA_mal.mal | 4 ++-- coffee/tests/stepA_mal.mal | 2 +- elisp/tests/stepA_mal.mal | 2 +- fantom/tests/stepA_mal.mal | 2 +- forth/tests/stepA_mal.mal | 16 ++++++++-------- gnu-smalltalk/tests/stepA_mal.mal | 2 +- io/tests/stepA_mal.mal | 2 +- js/tests/stepA_mal.mal | 4 ++-- logo/tests/stepA_mal.mal | 2 +- lua/tests/stepA_mal.mal | 2 +- make/tests/stepA_mal.mal | 2 +- perl/tests/stepA_mal.mal | 2 +- perl6/tests/stepA_mal.mal | 2 +- php/tests/stepA_mal.mal | 2 +- python/tests/stepA_mal.mal | 2 +- rexx/tests/stepA_mal.mal | 2 +- ruby/tests/stepA_mal.mal | 2 +- runtest.py | 3 +-- scheme/tests/stepA_mal.mal | 5 +++-- tcl/tests/stepA_mal.mal | 2 +- yorick/tests/stepA_mal.mal | 2 +- 22 files changed, 33 insertions(+), 33 deletions(-) diff --git a/bash/tests/stepA_mal.mal b/bash/tests/stepA_mal.mal index bf3eabdb23..c121efa9bd 100644 --- a/bash/tests/stepA_mal.mal +++ b/bash/tests/stepA_mal.mal @@ -4,7 +4,7 @@ ;=>"7" (sh* "echo >&2 hello") -; hello +;/hello ;=>"" (sh* "foo=8; echo ${foo}") diff --git a/clojure/tests/stepA_mal.mal b/clojure/tests/stepA_mal.mal index 4c7ec359f8..d77c8a24d3 100644 --- a/clojure/tests/stepA_mal.mal +++ b/clojure/tests/stepA_mal.mal @@ -13,10 +13,10 @@ ;=>{"abc" 123} (if clj (clj* "(prn \"foo\")") (js* "console.log('\"foo\"')")) -; "foo" +;/"foo" ;=>nil -(if clj (clj* "(for [x [1 2 3]] (+ 1 x))") '(2 3 4)) +(if clj (clj* "(apply list (for [x [1 2 3]] (+ 1 x)))") '(2 3 4)) ;=>(2 3 4) (if cljs (js* "[1,2,3].map(function(x) {return 1+x})") [2 3 4]) ;=>[2 3 4] diff --git a/coffee/tests/stepA_mal.mal b/coffee/tests/stepA_mal.mal index f785292d48..86699978dd 100644 --- a/coffee/tests/stepA_mal.mal +++ b/coffee/tests/stepA_mal.mal @@ -10,7 +10,7 @@ ;=>(7 8 9) (js* "console.log('hello');") -; hello +;/hello ;=>nil (js* "foo=8;") diff --git a/elisp/tests/stepA_mal.mal b/elisp/tests/stepA_mal.mal index 8f6e9a3b73..c655af6aa7 100644 --- a/elisp/tests/stepA_mal.mal +++ b/elisp/tests/stepA_mal.mal @@ -13,7 +13,7 @@ ;=>(1 2 3) (elisp-eval "(progn (princ \"Hello World!\n\") nil)") -; Hello World! +;/Hello World! ;=>nil (elisp-eval "(setq emacs-version-re (rx (+ digit) \".\" digit \".\" digit))") diff --git a/fantom/tests/stepA_mal.mal b/fantom/tests/stepA_mal.mal index a8c37d819d..29739c3ebb 100644 --- a/fantom/tests/stepA_mal.mal +++ b/fantom/tests/stepA_mal.mal @@ -19,7 +19,7 @@ ;=>{"abc" 789} (fantom-eval "echo(\"hello\")") -; hello +;/hello ;=>nil (fantom-eval "[\"a\",\"b\",\"c\"].join(\" \") { \"X${it}Y\" }") diff --git a/forth/tests/stepA_mal.mal b/forth/tests/stepA_mal.mal index c4a0e75613..3d8db0565b 100644 --- a/forth/tests/stepA_mal.mal +++ b/forth/tests/stepA_mal.mal @@ -6,7 +6,7 @@ (. "greetings" 'MalString.) ;=>"greetings" (. "hello" 'type 'cr 'mal-nil) -; hello +;/hello ;=>nil ;; Interop on non-literals @@ -31,11 +31,11 @@ ;=>7 (def! prn-chars (fn* (start count) (if (> count 0) (do (prn (. start 1 'MalString.)) (prn-chars (+ start 1) (- count 1)))))) (let* (msg (string-parts "sketchy")) (prn-chars (first msg) (first (rest msg)))) -; "s" -; "k" -; "e" -; "t" -; "c" -; "h" -; "y" +;/"s" +;/"k" +;/"e" +;/"t" +;/"c" +;/"h" +;/"y" ;=>nil diff --git a/gnu-smalltalk/tests/stepA_mal.mal b/gnu-smalltalk/tests/stepA_mal.mal index 32221a4580..f8ff39f8f6 100644 --- a/gnu-smalltalk/tests/stepA_mal.mal +++ b/gnu-smalltalk/tests/stepA_mal.mal @@ -8,4 +8,4 @@ ;=>"a b c" (gst-eval "'Hello World!' displayNl") -; Hello World! +;/Hello World! diff --git a/io/tests/stepA_mal.mal b/io/tests/stepA_mal.mal index 4a07a602ef..5b0f5dcdd3 100644 --- a/io/tests/stepA_mal.mal +++ b/io/tests/stepA_mal.mal @@ -19,7 +19,7 @@ ;=>{"abc" 789} (io-eval "\"hello\" println") -; hello +;/hello ;=>"hello" (io-eval "Lobby foo := 8") diff --git a/js/tests/stepA_mal.mal b/js/tests/stepA_mal.mal index 4502bfff26..54127682d9 100644 --- a/js/tests/stepA_mal.mal +++ b/js/tests/stepA_mal.mal @@ -10,7 +10,7 @@ ;=>(7 8 9) (js-eval "console.log('hello');") -; hello +;/hello ;=>nil (js-eval "foo=8;") @@ -27,7 +27,7 @@ ;=>60 (. "console.log" "abc" 123 '(4 5 6) {"kk" "vv"} (= 1 1) nil) -; abc 123 \[ 4, 5, 6 \] \{ kk: 'vv' \} true null +;/abc 123 \[ 4, 5, 6 \] \{ kk: 'vv' \} true null ;=>nil (js-eval "myobj = { v: 10, myfunc: function(a,b,c) { return a * b * c * this.v; } }") diff --git a/logo/tests/stepA_mal.mal b/logo/tests/stepA_mal.mal index 904b7db3bf..9175626d1f 100644 --- a/logo/tests/stepA_mal.mal +++ b/logo/tests/stepA_mal.mal @@ -16,7 +16,7 @@ ;=>false (logo-eval "print [hello world]") -; hello world +;/hello world ;=>nil (logo-eval "make \"foo 8") diff --git a/lua/tests/stepA_mal.mal b/lua/tests/stepA_mal.mal index b52a902ae3..70a142315d 100644 --- a/lua/tests/stepA_mal.mal +++ b/lua/tests/stepA_mal.mal @@ -24,7 +24,7 @@ ;=>{"abc" 789} (lua-eval "print('hello')") -; hello +;/hello ;=>nil (lua-eval "(function() foo = 8 end)()") diff --git a/make/tests/stepA_mal.mal b/make/tests/stepA_mal.mal index 768a9293c1..ed5551cacf 100644 --- a/make/tests/stepA_mal.mal +++ b/make/tests/stepA_mal.mal @@ -4,7 +4,7 @@ ;=>"7" (make* "$(info foo)") -; foo +;/foo ;=>"" (make* "$(eval foo := 8)") diff --git a/perl/tests/stepA_mal.mal b/perl/tests/stepA_mal.mal index 1335be4021..7f5abe79b7 100644 --- a/perl/tests/stepA_mal.mal +++ b/perl/tests/stepA_mal.mal @@ -18,5 +18,5 @@ ;; Testing eval of print statement (pl* "print 'hello\n';") -; hello +;/hello ;=>1 diff --git a/perl6/tests/stepA_mal.mal b/perl6/tests/stepA_mal.mal index a1b4a38a72..0a586b4c20 100644 --- a/perl6/tests/stepA_mal.mal +++ b/perl6/tests/stepA_mal.mal @@ -34,7 +34,7 @@ ;=>nil (perl6-eval "say 'hello' ") -; hello +;/hello ;=>true (perl6-eval "sub { my $foo = 8 }()") diff --git a/php/tests/stepA_mal.mal b/php/tests/stepA_mal.mal index 80d9a0bdec..de459cdbcf 100644 --- a/php/tests/stepA_mal.mal +++ b/php/tests/stepA_mal.mal @@ -13,7 +13,7 @@ ;=>{"abc" 789} (php* "print \"hello\n\";") -; hello +;/hello ;=>nil (php* "global $foo; $foo=8;") diff --git a/python/tests/stepA_mal.mal b/python/tests/stepA_mal.mal index dfe05106e9..79851922a7 100644 --- a/python/tests/stepA_mal.mal +++ b/python/tests/stepA_mal.mal @@ -14,7 +14,7 @@ ;; Testing Python statements (py!* "print('hello')") -; hello +;/hello ;=>nil (py!* "foo = 19 % 4") diff --git a/rexx/tests/stepA_mal.mal b/rexx/tests/stepA_mal.mal index cc05df486b..21a3f86050 100644 --- a/rexx/tests/stepA_mal.mal +++ b/rexx/tests/stepA_mal.mal @@ -13,7 +13,7 @@ ;=>"FE" (rexx-eval "say 'hello' 12.34 upper('rexx')" nil) -; hello 12.34 REXX +;/hello 12.34 REXX ;=>nil (rexx-eval "foo = 8" "foo + 3") diff --git a/ruby/tests/stepA_mal.mal b/ruby/tests/stepA_mal.mal index 2d7efb8147..79cca1984b 100644 --- a/ruby/tests/stepA_mal.mal +++ b/ruby/tests/stepA_mal.mal @@ -13,7 +13,7 @@ ;=>{"abc" 789} (rb* "print 'hello\n'") -; hello +;/hello ;=>nil (rb* "$foo=8;") diff --git a/runtest.py b/runtest.py index 8953a10756..f8779c754b 100755 --- a/runtest.py +++ b/runtest.py @@ -204,8 +204,7 @@ def next(self): return True continue elif line[0:1] == ";": # unexpected comment - log("Test data error at line %d:\n%s" % (self.line_num, line)) - return None + raise Exception("Test data error at line %d:\n%s" % (self.line_num, line)) self.form = line # the line is a form to send # Now find the output and return value diff --git a/scheme/tests/stepA_mal.mal b/scheme/tests/stepA_mal.mal index 85eb6f06ad..4ba3b9fab0 100644 --- a/scheme/tests/stepA_mal.mal +++ b/scheme/tests/stepA_mal.mal @@ -3,8 +3,9 @@ (scm-eval "(+ 1 1)") ;=>2 -(scm-eval "(begin (display \"Hello World!\") (newline))") -; "Hello World!" +(scm-eval "(begin (display \"Hello World!\") (newline) 7)") +;/Hello World! +;=>7 (scm-eval "(string->list \"MAL\")") ;=>("M" "A" "L") diff --git a/tcl/tests/stepA_mal.mal b/tcl/tests/stepA_mal.mal index 57bdd18378..a53ddab71d 100644 --- a/tcl/tests/stepA_mal.mal +++ b/tcl/tests/stepA_mal.mal @@ -13,7 +13,7 @@ ;=>"a b c d e f g" (tcl* "puts \"hello [expr {5 + 6}] world\"") -; hello 11 world +;/hello 11 world ;=>"" (tcl* "set ::foo 8") diff --git a/yorick/tests/stepA_mal.mal b/yorick/tests/stepA_mal.mal index 8c2229a12d..76bc44ea18 100644 --- a/yorick/tests/stepA_mal.mal +++ b/yorick/tests/stepA_mal.mal @@ -16,7 +16,7 @@ ;=>(7 8 9) (yorick-eval "write, format=\"%s-%d\\x0A\", \"hello\", 1234; return nil;") -; hello-1234 +;/hello-1234 ;=>nil (yorick-eval "extern my_global_var; my_global_var = 8; return nil;") From fdcb98b148693a0a4ce01b3988d46718cc99c79e Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 28 Jan 2019 11:52:14 -0600 Subject: [PATCH 0744/2308] clojure: tools.reader dep no longer necessary. --- clojure/project.clj | 1 - 1 file changed, 1 deletion(-) diff --git a/clojure/project.clj b/clojure/project.clj index a675e2cc1e..8f5c61b5a8 100644 --- a/clojure/project.clj +++ b/clojure/project.clj @@ -2,7 +2,6 @@ :description "Make-A-Lisp" :dependencies [[org.clojure/clojure "1.10.0"] - [org.clojure/tools.reader "1.3.2"] [net.n01se/clojure-jna "1.0.0"]] ;; To run a step with correct readline behavior: From a68c26aff8f72532f9105c9752426ff61be122ac Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 28 Jan 2019 13:31:28 -0600 Subject: [PATCH 0745/2308] lua: update to version 5.2 - Update to support the new behavior of varargs and table.pack/unpack in lua 5.2 - Bump up the stack overflow test since lua 5.2 seems to support much larger stacks before overflow. - Fix a latent issue in if forms where false in the 4th position would result in nil returned. Add a new test to catch this in the future. --- lua/Dockerfile | 4 ++-- lua/core.lua | 29 ++++++++++++++++++----------- lua/reader.lua | 2 +- lua/step2_eval.lua | 2 +- lua/step3_env.lua | 2 +- lua/step4_if_fn_do.lua | 6 +++--- lua/step5_tco.lua | 6 +++--- lua/step6_file.lua | 6 +++--- lua/step7_quote.lua | 6 +++--- lua/step8_macros.lua | 8 ++++---- lua/step9_try.lua | 8 ++++---- lua/stepA_mal.lua | 9 +++++---- lua/tests/step5_tco.mal | 2 +- lua/types.lua | 4 +++- tests/step4_if_fn_do.mal | 2 ++ 15 files changed, 54 insertions(+), 42 deletions(-) diff --git a/lua/Dockerfile b/lua/Dockerfile index f7bc915f29..e93a1d0ec0 100644 --- a/lua/Dockerfile +++ b/lua/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:18.04 MAINTAINER Joel Martin ########################################################## @@ -22,7 +22,7 @@ WORKDIR /mal ########################################################## # Lua -RUN apt-get -y install lua5.1 lua-rex-pcre luarocks +RUN apt-get -y install lua5.2 liblua5.2-dev lua-rex-pcre luarocks RUN luarocks install linenoise RUN luarocks install luasocket diff --git a/lua/core.lua b/lua/core.lua index 8c8d2c54ec..dbbb9cd1f9 100644 --- a/lua/core.lua +++ b/lua/core.lua @@ -13,24 +13,28 @@ local M = {} function pr_str(...) return table.concat( - utils.map(function(e) return _pr_str(e, true) end, arg), " ") + utils.map(function(e) return _pr_str(e, true) end, + table.pack(...)), " ") end function str(...) return table.concat( - utils.map(function(e) return _pr_str(e, false) end, arg), "") + utils.map(function(e) return _pr_str(e, false) end, + table.pack(...)), "") end function prn(...) print(table.concat( - utils.map(function(e) return _pr_str(e, true) end, arg), " ")) + utils.map(function(e) return _pr_str(e, true) end, + table.pack(...)), " ")) io.flush() return Nil end function println(...) print(table.concat( - utils.map(function(e) return _pr_str(e, false) end, arg), " ")) + utils.map(function(e) return _pr_str(e, false) end, + table.pack(...)), " ")) io.flush() return Nil end @@ -55,11 +59,11 @@ end -- hash map functions function assoc(hm, ...) - return types._assoc_BANG(types.copy(hm), unpack(arg)) + return types._assoc_BANG(types.copy(hm), ...) end function dissoc(hm, ...) - return types._dissoc_BANG(types.copy(hm), unpack(arg)) + return types._dissoc_BANG(types.copy(hm), ...) end function get(hm, key) @@ -93,6 +97,7 @@ function cons(a,lst) end function concat(...) + local arg = table.pack(...) local new_lst = {} for i = 1, #arg do for j = 1, #arg[i] do @@ -127,12 +132,13 @@ function rest(a) end function apply(f, ...) + local arg = table.pack(...) if types._malfunc_Q(f) then f = f.fn end local args = concat(types.slice(arg, 1, #arg-1), arg[#arg]) - return f(unpack(args)) + return f(table.unpack(args)) end function map(f, lst) @@ -162,13 +168,14 @@ function swap_BANG(atm,f,...) if types._malfunc_Q(f) then f = f.fn end - local args = List:new(arg) + local args = List:new(table.pack(...)) table.insert(args, 1, atm.val) - atm.val = f(unpack(args)) + atm.val = f(table.unpack(args)) return atm.val end local function conj(obj, ...) + local arg = table.pack(...) local new_obj = types.copy(obj) if types._list_Q(new_obj) then for i, v in ipairs(arg) do @@ -267,9 +274,9 @@ M.ns = { ['/'] = function(a,b) return math.floor(a/b) end, ['time-ms'] = function() return math.floor(socket.gettime() * 1000) end, - list = function(...) return List:new(arg) end, + list = function(...) return List:new(table.pack(...)) end, ['list?'] = function(a) return types._list_Q(a) end, - vector = function(...) return types.Vector:new(arg) end, + vector = function(...) return types.Vector:new(table.pack(...)) end, ['vector?'] = types._vector_Q, ['hash-map'] = types.hash_map, ['map?'] = types._hash_map_Q, diff --git a/lua/reader.lua b/lua/reader.lua index 34e4239a74..9425c6c346 100644 --- a/lua/reader.lua +++ b/lua/reader.lua @@ -87,7 +87,7 @@ end function M.read_hash_map(rdr) local seq = M.read_sequence(rdr, '{', '}') - return types._assoc_BANG(types.HashMap:new(), unpack(seq)) + return types._assoc_BANG(types.HashMap:new(), table.unpack(seq)) end function M.read_form(rdr) diff --git a/lua/step2_eval.lua b/lua/step2_eval.lua index 0b095f2ce2..6c2fbc97c7 100755 --- a/lua/step2_eval.lua +++ b/lua/step2_eval.lua @@ -42,7 +42,7 @@ function EVAL(ast, env) if #ast == 0 then return ast end local args = eval_ast(ast, env) local f = table.remove(args, 1) - return f(unpack(args)) + return f(table.unpack(args)) end -- print diff --git a/lua/step3_env.lua b/lua/step3_env.lua index 24cdfc7459..1e3c0add5d 100755 --- a/lua/step3_env.lua +++ b/lua/step3_env.lua @@ -52,7 +52,7 @@ function EVAL(ast, env) else local args = eval_ast(ast, env) local f = table.remove(args, 1) - return f(unpack(args)) + return f(table.unpack(args)) end end diff --git a/lua/step4_if_fn_do.lua b/lua/step4_if_fn_do.lua index 46302d1b6f..9dae7ba0de 100755 --- a/lua/step4_if_fn_do.lua +++ b/lua/step4_if_fn_do.lua @@ -56,18 +56,18 @@ function EVAL(ast, env) elseif 'if' == a0sym then local cond = EVAL(a1, env) if cond == types.Nil or cond == false then - if a3 then return EVAL(a3, env) else return types.Nil end + if #ast > 3 then return EVAL(a3, env) else return types.Nil end else return EVAL(a2, env) end elseif 'fn*' == a0sym then return function(...) - return EVAL(a2, Env:new(env, a1, arg)) + return EVAL(a2, Env:new(env, a1, table.pack(...))) end else local args = eval_ast(ast, env) local f = table.remove(args, 1) - return f(unpack(args)) + return f(table.unpack(args)) end end diff --git a/lua/step5_tco.lua b/lua/step5_tco.lua index b3d77950fd..9c28e4e739 100755 --- a/lua/step5_tco.lua +++ b/lua/step5_tco.lua @@ -58,13 +58,13 @@ function EVAL(ast, env) elseif 'if' == a0sym then local cond = EVAL(a1, env) if cond == types.Nil or cond == false then - if a3 then ast = a3 else return types.Nil end -- TCO + if #ast > 3 then ast = a3 else return types.Nil end -- TCO else ast = a2 -- TCO end elseif 'fn*' == a0sym then return types.MalFunc:new(function(...) - return EVAL(a2, Env:new(env, a1, arg)) + return EVAL(a2, Env:new(env, a1, table.pack(...))) end, a2, env, a1) else local args = eval_ast(ast, env) @@ -73,7 +73,7 @@ function EVAL(ast, env) ast = f.ast env = Env:new(f.env, f.params, args) -- TCO else - return f(unpack(args)) + return f(table.unpack(args)) end end end diff --git a/lua/step6_file.lua b/lua/step6_file.lua index 63d0208aa0..4a27a24a52 100755 --- a/lua/step6_file.lua +++ b/lua/step6_file.lua @@ -58,13 +58,13 @@ function EVAL(ast, env) elseif 'if' == a0sym then local cond = EVAL(a1, env) if cond == types.Nil or cond == false then - if a3 then ast = a3 else return types.Nil end -- TCO + if #ast > 3 then ast = a3 else return types.Nil end -- TCO else ast = a2 -- TCO end elseif 'fn*' == a0sym then return types.MalFunc:new(function(...) - return EVAL(a2, Env:new(env, a1, arg)) + return EVAL(a2, Env:new(env, a1, table.pack(...))) end, a2, env, a1) else local args = eval_ast(ast, env) @@ -73,7 +73,7 @@ function EVAL(ast, env) ast = f.ast env = Env:new(f.env, f.params, args) -- TCO else - return f(unpack(args)) + return f(table.unpack(args)) end end end diff --git a/lua/step7_quote.lua b/lua/step7_quote.lua index e978d681c7..d014cc7353 100755 --- a/lua/step7_quote.lua +++ b/lua/step7_quote.lua @@ -84,13 +84,13 @@ function EVAL(ast, env) elseif 'if' == a0sym then local cond = EVAL(a1, env) if cond == types.Nil or cond == false then - if a3 then ast = a3 else return types.Nil end -- TCO + if #ast > 3 then ast = a3 else return types.Nil end -- TCO else ast = a2 -- TCO end elseif 'fn*' == a0sym then return types.MalFunc:new(function(...) - return EVAL(a2, Env:new(env, a1, arg)) + return EVAL(a2, Env:new(env, a1, table.pack(...))) end, a2, env, a1) else local args = eval_ast(ast, env) @@ -99,7 +99,7 @@ function EVAL(ast, env) ast = f.ast env = Env:new(f.env, f.params, args) -- TCO else - return f(unpack(args)) + return f(table.unpack(args)) end end end diff --git a/lua/step8_macros.lua b/lua/step8_macros.lua index 800833cca8..33538154f9 100755 --- a/lua/step8_macros.lua +++ b/lua/step8_macros.lua @@ -51,7 +51,7 @@ end function macroexpand(ast, env) while is_macro_call(ast, env) do local mac = env:get(ast[1]) - ast = mac.fn(unpack(ast:slice(2))) + ast = mac.fn(table.unpack(ast:slice(2))) end return ast end @@ -111,13 +111,13 @@ function EVAL(ast, env) elseif 'if' == a0sym then local cond = EVAL(a1, env) if cond == types.Nil or cond == false then - if a3 then ast = a3 else return types.Nil end -- TCO + if #ast > 3 then ast = a3 else return types.Nil end -- TCO else ast = a2 -- TCO end elseif 'fn*' == a0sym then return types.MalFunc:new(function(...) - return EVAL(a2, Env:new(env, a1, arg)) + return EVAL(a2, Env:new(env, a1, table.pack(...))) end, a2, env, a1) else local args = eval_ast(ast, env) @@ -126,7 +126,7 @@ function EVAL(ast, env) ast = f.ast env = Env:new(f.env, f.params, args) -- TCO else - return f(unpack(args)) + return f(table.unpack(args)) end end end diff --git a/lua/step9_try.lua b/lua/step9_try.lua index a7a1db8f76..2cb5817155 100755 --- a/lua/step9_try.lua +++ b/lua/step9_try.lua @@ -51,7 +51,7 @@ end function macroexpand(ast, env) while is_macro_call(ast, env) do local mac = env:get(ast[1]) - ast = mac.fn(unpack(ast:slice(2))) + ast = mac.fn(table.unpack(ast:slice(2))) end return ast end @@ -129,13 +129,13 @@ function EVAL(ast, env) elseif 'if' == a0sym then local cond = EVAL(a1, env) if cond == types.Nil or cond == false then - if a3 then ast = a3 else return types.Nil end -- TCO + if #ast > 3 then ast = a3 else return types.Nil end -- TCO else ast = a2 -- TCO end elseif 'fn*' == a0sym then return types.MalFunc:new(function(...) - return EVAL(a2, Env:new(env, a1, arg)) + return EVAL(a2, Env:new(env, a1, table.pack(...))) end, a2, env, a1) else local args = eval_ast(ast, env) @@ -144,7 +144,7 @@ function EVAL(ast, env) ast = f.ast env = Env:new(f.env, f.params, args) -- TCO else - return f(unpack(args)) + return f(table.unpack(args)) end end end diff --git a/lua/stepA_mal.lua b/lua/stepA_mal.lua index 4dc5d571c6..00615560c9 100755 --- a/lua/stepA_mal.lua +++ b/lua/stepA_mal.lua @@ -2,6 +2,7 @@ local table = require('table') +package.path = '../lua/?.lua;' .. package.path local readline = require('readline') local utils = require('utils') local types = require('types') @@ -51,7 +52,7 @@ end function macroexpand(ast, env) while is_macro_call(ast, env) do local mac = env:get(ast[1]) - ast = mac.fn(unpack(ast:slice(2))) + ast = mac.fn(table.unpack(ast:slice(2))) end return ast end @@ -129,13 +130,13 @@ function EVAL(ast, env) elseif 'if' == a0sym then local cond = EVAL(a1, env) if cond == types.Nil or cond == false then - if a3 then ast = a3 else return types.Nil end -- TCO + if #ast > 3 then ast = a3 else return types.Nil end -- TCO else ast = a2 -- TCO end elseif 'fn*' == a0sym then return types.MalFunc:new(function(...) - return EVAL(a2, Env:new(env, a1, arg)) + return EVAL(a2, Env:new(env, a1, table.pack(...))) end, a2, env, a1) else local args = eval_ast(ast, env) @@ -144,7 +145,7 @@ function EVAL(ast, env) ast = f.ast env = Env:new(f.env, f.params, args) -- TCO else - return f(unpack(args)) + return f(table.unpack(args)) end end end diff --git a/lua/tests/step5_tco.mal b/lua/tests/step5_tco.mal index d20df25db7..087368335f 100644 --- a/lua/tests/step5_tco.mal +++ b/lua/tests/step5_tco.mal @@ -10,6 +10,6 @@ ;=>nil ;;; For implementations without their own TCO this should fail and ;;; leave res1 unchanged -(def! res1 (sum-to 10000)) +(def! res1 (sum-to 100000)) res1 ;=>nil diff --git a/lua/types.lua b/lua/types.lua index 62bc6a2fa9..44daa5196b 100644 --- a/lua/types.lua +++ b/lua/types.lua @@ -161,18 +161,20 @@ function M.HashMap:new(val) return setmetatable(newObj, self) end function M.hash_map(...) - return M._assoc_BANG(M.HashMap:new(), unpack(arg)) + return M._assoc_BANG(M.HashMap:new(), ...) end function M._hash_map_Q(obj) return utils.instanceOf(obj, M.HashMap) end function M._assoc_BANG(hm, ...) + local arg = table.pack(...) for i = 1, #arg, 2 do hm[arg[i]] = arg[i+1] end return hm end function M._dissoc_BANG(hm, ...) + local arg = table.pack(...) for i = 1, #arg do hm[arg[i]] = nil end diff --git a/tests/step4_if_fn_do.mal b/tests/step4_if_fn_do.mal index 991b44b730..ba71e82811 100644 --- a/tests/step4_if_fn_do.mal +++ b/tests/step4_if_fn_do.mal @@ -29,6 +29,8 @@ ;=>7 (if false 7 8) ;=>8 +(if false 7 false) +;=>false (if true (+ 1 7) (+ 1 8)) ;=>8 (if false (+ 1 7) (+ 1 8)) From 45a714987cad211c5c60bffbe3f10672cf72b71e Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 28 Jan 2019 13:44:24 -0600 Subject: [PATCH 0746/2308] README: the tested version of lua is now 5.2. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9b5f7a763c..5f4807afb9 100644 --- a/README.md +++ b/README.md @@ -606,8 +606,9 @@ logo stepX_YYY.lg ### Lua -Running the Lua implementation of mal requires lua 5.1 or later, -luarocks and the lua-rex-pcre library installed. +The Lua implementation of mal has been tested with Lua 5.2. The +implementation requires that luarocks and the lua-rex-pcre library +are installed. ``` cd lua From 07734c09319bda0ecc48396a609693fe98b39479 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 28 Jan 2019 16:07:27 -0600 Subject: [PATCH 0747/2308] README: add external implementations section. Move the alternate rust implementations to this section and add a link to HolyC implementation. --- README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5f4807afb9..8e6076e640 100644 --- a/README.md +++ b/README.md @@ -895,11 +895,6 @@ cd rust cargo run --release --bin stepX_YYY ``` -Alternative out-of-tee Rust implementations: - -* [by Tim Morgan](https://github.com/seven1m/mal-rust). -* [by vi](https://github.com/vi/mal-rust-vi) - using [Pest](https://pest.rs/) grammar, not using typical Mal infrastructure (cargo-ized steps and built-in converted tests). - ### Scala ### Install scala and sbt (http://www.scala-sbt.org/0.13/tutorial/Installing-sbt-on-Linux.html): @@ -1239,6 +1234,21 @@ make "docker-build^IMPL" out. These dependencies are downloaded to dot-files in the /mal directory so they will persist between runs. + +## External Implementations + +The following implementations are maintained as separate projects: + +### HolyC + +* [by Alexander Bagnalla](https://github.com/bagnalla/holyc_mal) + +### Rust + +* [by Tim Morgan](https://github.com/seven1m/mal-rust) +* [by vi](https://github.com/vi/mal-rust-vi) - using [Pest](https://pest.rs/) grammar, not using typical Mal infrastructure (cargo-ized steps and built-in converted tests). + + ## Projects using mal * [malc](https://github.com/dubek/malc) - Mal (Make A Lisp) compiler. Compiles a Mal program to LLVM assembly language, then binary. From 23e43428773e517561133f2d1ea0dc2402e907d4 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 28 Jan 2019 17:31:49 -0600 Subject: [PATCH 0748/2308] README: move creator info to first list. The creator information should be more prominent instead of hidden within the build/run instructions. --- README.md | 146 ++++++++++++++---------------------------------------- 1 file changed, 37 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index 8e6076e640..1e78d85a4f 100644 --- a/README.md +++ b/README.md @@ -8,57 +8,57 @@ Mal is a Clojure inspired Lisp interpreter. Mal is implemented in 74 languages: -* Ada -* GNU awk -* Bash shell +* Ada - *created by [Chris Moore](https://github.com/zmower)* +* GNU awk - *created by [Miutsuru Kariya](https://github.com/kariya-mitsuru)* +* Bash 4 * BASIC (C64 and QBasic) * C -* C++ +* C++ - *created by [Stephen Thirlwall](https://github.com/sdt)* * C# -* ChucK -* Common Lisp +* ChucK - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* +* Common Lisp - *created by [Iqbal Ansari](https://github.com/iqbalansari)* * Clojure (Clojure and ClojureScript) * CoffeeScript -* Crystal -* D -* Dart -* Elixir -* Elm -* Emacs Lisp -* Erlang +* Crystal - *created by [Linda_pp](https://github.com/rhysd)* +* D - *created by [Dov Murik](https://github.com/dubek)* +* Dart - *created by [Harry Terkelsen](https://github.com/hterkelsen)* +* Elixir - *created by [Martin Ek](https://github.com/ekmartin)* +* Elm - *created by [Jos van Bakel](https://github.com/c0deaddict)* +* Emacs Lisp - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* +* Erlang - *created by [Nathan Fiedler](https://github.com/nlfiedler)* * ES6 (ECMAScript 6 / ECMAScript 2015) -* F# -* Factor -* Fantom -* Forth +* F# - *created by [Peter Stephens](https://github.com/pstephens)* +* Factor - *created by [Jordan Lewis](https://github.com/jordanlewis)* +* Fantom - *created by [Dov Murik](https://github.com/dubek)* +* Forth - *created by [Chris Houser](https://github.com/chouser)* * Go * Groovy -* GNU Guile -* GNU Smalltalk +* GNU Guile - *created by [Mu Lei](https://github.com/NalaGinrut).* +* GNU Smalltalk - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* * Haskell * Haxe (Neko, Python, C++ and JavaScript) * Hy -* Io +* Io - *created by [Dov Murik](https://github.com/dubek)* * Java * JavaScript ([Online Demo](http://kanaka.github.io/mal)) * Julia -* Kotlin -* LiveScript -* Logo +* Kotlin - *created by [Javier Fernandez-Ivern](https://github.com/ivern)* +* LiveScript - *created by [Jos van Bakel](https://github.com/c0deaddict)* +* Logo - *created by [Dov Murik](https://github.com/dubek)* * Lua * GNU Make * mal itself * Matlab (GNU Octave and MATLAB) * [miniMAL](https://github.com/kanaka/miniMAL) -* NASM -* Nim +* NASM - *created by [Ben Dudson](https://github.com/bendudson)* +* Nim - *created by [Dennis Felsing](https://github.com/def-)* * Object Pascal * Objective C -* OCaml +* OCaml - *created by [Chris Houser](https://github.com/chouser)* * Perl -* Perl 6 +* Perl 6 - *created by [Hinrik Örn Sigurðsson](https://github.com/hinrik)* * PHP -* Picolisp +* Picolisp - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* * PL/pgSQL (Postgres) * PL/SQL (Oracle) * Postscript @@ -67,21 +67,21 @@ Mal is implemented in 74 languages: * RPython * R * Racket -* Rexx +* Rexx - *created by [Dov Murik](https://github.com/dubek)* * Ruby * Rust * Scala -* Scheme (R7RS) -* Skew -* Swift +* Scheme (R7RS) *created by [Vasilij Schneidermann](https://github.com/wasamasa)* +* Skew - *created by [Dov Murik](https://github.com/dubek)* +* Swift - *created by [Keith Rollin](https://github.com/keith-rollin)* * Swift 3 -* Tcl -* TypeScript -* VHDL -* Vimscript +* Tcl - *created by [Dov Murik](https://github.com/dubek)* +* TypeScript - *created by [Masahiro Wakame](https://github.com/vvakame)* +* VHDL - *created by [Dov Murik](https://github.com/dubek)* +* Vimscript - *created by [Dov Murik](https://github.com/dubek)* * Visual Basic.NET * WebAssembly (wasm) -* Yorick +* Yorick - *created by [Dov Murik](https://github.com/dubek)* Mal is a learning tool. See the [make-a-lisp process @@ -144,8 +144,6 @@ make DOCKERIZE=1 "repl^IMPL" ### Ada -*The Ada implementation was created by [Chris Moore](https://github.com/zmower)* - The Ada implementation was developed with GNAT 4.9 on debian. It also compiles unchanged on windows if you have windows versions of git, GNAT and (optionally) make. There are no external dependencies @@ -159,8 +157,6 @@ make ### GNU awk -*The GNU awk implementation was created by [Miutsuru kariya](https://github.com/kariya-mitsuru)* - The GNU awk implementation of mal has been tested with GNU awk 4.1.1. ``` @@ -218,8 +214,6 @@ make ### C++ -*The C++ implementation was created by [Stephen Thirlwall (sdt)](https://github.com/sdt)* - The C++ implementation of mal requires g++-4.9 or clang++-3.5 and a readline compatible library to build. See the `cpp/README.md` for more details: @@ -247,8 +241,6 @@ mono ./stepX_YYY.exe ### ChucK -*The ChucK implementation was created by [Vasilij Schneidermann](https://github.com/wasamasa)* - The ChucK implementation has been tested with ChucK 1.3.5.2. ``` @@ -258,8 +250,6 @@ cd chuck ### Common Lisp -*The Common Lisp implementation was created by [Iqbal Ansari](https://github.com/iqbalansari)* - The implementation has been tested with SBCL, CCL, CMUCL, GNU CLISP, ECL and Allegro CL on Ubuntu 16.04 and Ubuntu 12.04, see the [README](common-lisp/README.org) for more details. Provided you have the @@ -291,8 +281,6 @@ coffee ./stepX_YYY ### Crystal -*The Crystal implementation of mal was created by [Linda_pp](https://github.com/rhysd)* - The Crystal implementation of mal has been tested with Crystal 0.26.1. ``` @@ -305,8 +293,6 @@ make # needed to run tests ### D -*The D implementation was created by [Dov Murik](https://github.com/dubek)* - The D implementation of mal was tested with GDC 4.8. It requires the GNU readline library. @@ -318,8 +304,6 @@ make ### Dart -*The Dart implementation was created by [Harry Terkelsen](https://github.com/hterkelsen)* - The Dart implementation has been tested with Dart 1.20. ``` @@ -329,8 +313,6 @@ dart ./stepX_YYY ### Emacs Lisp -*The Emacs Lisp implementation was created by [Vasilij Schneidermann](https://github.com/wasamasa)* - The Emacs Lisp implementation of mal has been tested with Emacs 24.3 and 24.5. While there is very basic readline editing (`` and `C-d` work, `C-c` cancels the process), it is recommended to use @@ -345,8 +327,6 @@ rlwrap emacs -Q --batch --load stepX_YYY.el ### Elixir -*The Elixir implementation was created by [Martin Ek (ekmartin)](https://github.com/ekmartin)* - The Elixir implementation of mal has been tested with Elixir 1.0.5. ``` @@ -358,8 +338,6 @@ iex -S mix stepX_YYY ### Elm -*The Elm implementation was created by [Jos van Bakel](https://github.com/c0deaddict)* - The Elm implementation of mal has been tested with Elm 0.18.0 ``` @@ -370,8 +348,6 @@ STEP=stepX_YYY ./run ### Erlang -*The Erlang implementation was created by [Nathan Fiedler (nlfiedler)](https://github.com/nlfiedler)* - The Erlang implementation of mal requires [Erlang/OTP R17](http://www.erlang.org/download.html) and [rebar](https://github.com/rebar/rebar) to build. @@ -398,8 +374,6 @@ node build/stepX_YYY.js ### F# ### -*The F# implementation was created by [Peter Stephens (pstephens)](https://github.com/pstephens)* - The F# implementation of mal has been tested on Linux using the Mono F# compiler (fsharpc) and the Mono runtime (version 3.12.1). The mono C# compiler (mcs) is also necessary to compile the readline dependency. All are @@ -413,8 +387,6 @@ mono ./stepX_YYY.exe ### Factor -*The Factor implementation was created by [Jordan Lewis (jordanlewis)](https://github.com/jordanlewis)* - The Factor implementation of mal has been tested with Factor 0.97 ([factorcode.org](http://factorcode.org)). @@ -425,8 +397,6 @@ FACTOR_ROOTS=. factor -run=stepX_YYY ### Fantom -*The Fantom implementation was created by [Dov Murik](https://github.com/dubek)* - The Fantom implementation of mal has been tested with Fantom 1.0.70. ``` @@ -437,8 +407,6 @@ STEP=stepX_YYY ./run ### Forth -*The Forth implementation was created by [Chris Houser (chouser)](https://github.com/chouser)* - ``` cd forth gforth stepX_YYY.fs @@ -469,8 +437,6 @@ groovy ./stepX_YYY.groovy ### GNU Guile 2.1+ -*The Guile implementation was created by [Mu Lei (NalaGinrut)](https://github.com/NalaGinrut).* - ``` cd guile guile -L ./ stepX_YYY.scm @@ -478,8 +444,6 @@ guile -L ./ stepX_YYY.scm ### GNU Smalltalk -*The Smalltalk implementation was created by [Vasilij Schneidermann](https://github.com/wasamasa)* - The Smalltalk implementation of mal has been tested with GNU Smalltalk 3.2.91. ``` @@ -531,8 +495,6 @@ cd hy ### Io -*The Io implementation was created by [Dov Murik](https://github.com/dubek)* - The Io implementation of mal has been tested with Io version 20110905. ``` @@ -571,8 +533,6 @@ julia stepX_YYY.jl ### Kotlin -*The Kotlin implementation was created by [Javier Fernandez-Ivern](https://github.com/ivern)* - The Kotlin implementation of mal has been tested with Kotlin 1.0. ``` @@ -583,8 +543,6 @@ java -jar stepX_YYY.jar ### LiveScript -*The LiveScript implementation was created by [Jos van Bakel](https://github.com/c0deaddict)* - The LiveScript implementation of mal has been tested with LiveScript 1.5. ``` @@ -595,8 +553,6 @@ node_modules/.bin/lsc stepX_YYY.ls ### Logo -*The Logo implementation was created by [Dov Murik](https://github.com/dubek)* - The Logo implementation of mal has been tested with UCBLogo 6.0. ``` @@ -637,8 +593,6 @@ make -f stepX_YYY.mk ### NASM -*The NASM implementation was created by [Ben Dudson](https://github.com/bendudson)* - The NASM implementation of mal is written for x86-64 Linux, and has been tested with Linux 3.16.0-4-amd64 and NASM version 2.11.05. @@ -650,8 +604,6 @@ make ### Nim 0.17.0 -*The Nim implementation was created by [Dennis Felsing (def-)](https://github.com/def-)* - The Nim implementation of mal has been tested with Nim 0.17.0. ``` @@ -687,8 +639,6 @@ make ### OCaml 4.01.0 -*The OCaml implementation was created by [Chris Houser (chouser)](https://github.com/chouser)* - ``` cd ocaml make @@ -738,8 +688,6 @@ perl stepX_YYY.pl ### Perl 6 -*The Perl 6 implementation was created by [Hinrik Örn Sigurðsson](https://github.com/hinrik)* - The Perl 6 implementation was tested on Rakudo Perl 6 2016.04. ``` @@ -759,8 +707,6 @@ php stepX_YYY.php ### Picolisp -*The Picolisp implementation was created by [Vasilij Schneidermann](https://github.com/wasamasa)* - The Picolisp implementation requires libreadline and Picolisp 3.1.11 or later. @@ -868,8 +814,6 @@ cd racket ### Rexx -*The Rexx implementation was created by [Dov Murik](https://github.com/dubek)* - The Rexx implementation of mal has been tested with Regina Rexx 3.6. ``` @@ -909,8 +853,6 @@ scala -classpath target/scala*/classes stepX_YYY ### Scheme (R7RS) ### -*The Scheme implementation was created by [Vasilij Schneidermann](https://github.com/wasamasa)* - The Scheme implementation of mal has been tested with Chibi-Scheme 0.7.3, Kawa 2.4, Gauche 0.9.5, CHICKEN 4.11.0, Sagittarius 0.8.3, Cyclone 0.6.3 (Git version) and Foment 0.4 (Git version). You should @@ -942,8 +884,6 @@ scheme_MODE=foment ./run ### Skew ### -*The Skew implementation was created by [Dov Murik](https://github.com/dubek)* - The Skew implementation of mal has been tested with Skew 0.7.42. ``` @@ -955,8 +895,6 @@ node stepX_YYY.js ### Swift -*The Swift implementation was created by [Keith Rollin](https://github.com/keith-rollin)* - The Swift implementation of mal requires the Swift 2.0 compiler (XCode 7.0) to build. Older versions will not work due to changes in the language and standard library. @@ -980,8 +918,6 @@ make ### Tcl 8.6 -*The Tcl implementation was created by [Dov Murik](https://github.com/dubek)* - The Tcl implementation of mal requires Tcl 8.6 to run. For readline line editing support, install tclreadline. @@ -992,8 +928,6 @@ tclsh ./stepX_YYY.tcl ### TypeScript -*The TypeScript implementation was created by [vvakame](https://github.com/vvakame)* - The TypeScript implementation of mal requires the TypeScript 2.2 compiler. It has been tested with Node.js v6. @@ -1005,8 +939,6 @@ node ./stepX_YYY.js ### VHDL -*The VHDL implementation was created by [Dov Murik](https://github.com/dubek)* - The VHDL implementation of mal has been tested with GHDL 0.29. ``` @@ -1017,8 +949,6 @@ make ### Vimscript -*The Vimscript implementation was created by [Dov Murik](https://github.com/dubek)* - The Vimscript implementation of mal requires Vim 8.0 to run. ``` @@ -1053,8 +983,6 @@ wace ./stepX_YYY.wasm ### Yorick -*The Yorick implementation was created by [Dov Murik](https://github.com/dubek)* - The Yorick implementation of mal was tested on Yorick 2.2.04. ``` From f98792ade04c5ce9e93efec458ee7c318cf08c50 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 28 Jan 2019 17:43:04 -0600 Subject: [PATCH 0749/2308] README: bold language names to make clearer. --- README.md | 148 +++++++++++++++++++++++++++--------------------------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 1e78d85a4f..109080e321 100644 --- a/README.md +++ b/README.md @@ -8,80 +8,80 @@ Mal is a Clojure inspired Lisp interpreter. Mal is implemented in 74 languages: -* Ada - *created by [Chris Moore](https://github.com/zmower)* -* GNU awk - *created by [Miutsuru Kariya](https://github.com/kariya-mitsuru)* -* Bash 4 -* BASIC (C64 and QBasic) -* C -* C++ - *created by [Stephen Thirlwall](https://github.com/sdt)* -* C# -* ChucK - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* -* Common Lisp - *created by [Iqbal Ansari](https://github.com/iqbalansari)* -* Clojure (Clojure and ClojureScript) -* CoffeeScript -* Crystal - *created by [Linda_pp](https://github.com/rhysd)* -* D - *created by [Dov Murik](https://github.com/dubek)* -* Dart - *created by [Harry Terkelsen](https://github.com/hterkelsen)* -* Elixir - *created by [Martin Ek](https://github.com/ekmartin)* -* Elm - *created by [Jos van Bakel](https://github.com/c0deaddict)* -* Emacs Lisp - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* -* Erlang - *created by [Nathan Fiedler](https://github.com/nlfiedler)* -* ES6 (ECMAScript 6 / ECMAScript 2015) -* F# - *created by [Peter Stephens](https://github.com/pstephens)* -* Factor - *created by [Jordan Lewis](https://github.com/jordanlewis)* -* Fantom - *created by [Dov Murik](https://github.com/dubek)* -* Forth - *created by [Chris Houser](https://github.com/chouser)* -* Go -* Groovy -* GNU Guile - *created by [Mu Lei](https://github.com/NalaGinrut).* -* GNU Smalltalk - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* -* Haskell -* Haxe (Neko, Python, C++ and JavaScript) -* Hy -* Io - *created by [Dov Murik](https://github.com/dubek)* -* Java -* JavaScript ([Online Demo](http://kanaka.github.io/mal)) -* Julia -* Kotlin - *created by [Javier Fernandez-Ivern](https://github.com/ivern)* -* LiveScript - *created by [Jos van Bakel](https://github.com/c0deaddict)* -* Logo - *created by [Dov Murik](https://github.com/dubek)* -* Lua -* GNU Make -* mal itself -* Matlab (GNU Octave and MATLAB) -* [miniMAL](https://github.com/kanaka/miniMAL) -* NASM - *created by [Ben Dudson](https://github.com/bendudson)* -* Nim - *created by [Dennis Felsing](https://github.com/def-)* -* Object Pascal -* Objective C -* OCaml - *created by [Chris Houser](https://github.com/chouser)* -* Perl -* Perl 6 - *created by [Hinrik Örn Sigurðsson](https://github.com/hinrik)* -* PHP -* Picolisp - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* -* PL/pgSQL (Postgres) -* PL/SQL (Oracle) -* Postscript -* PowerShell -* Python (2.X and 3.X) -* RPython -* R -* Racket -* Rexx - *created by [Dov Murik](https://github.com/dubek)* -* Ruby -* Rust -* Scala -* Scheme (R7RS) *created by [Vasilij Schneidermann](https://github.com/wasamasa)* -* Skew - *created by [Dov Murik](https://github.com/dubek)* -* Swift - *created by [Keith Rollin](https://github.com/keith-rollin)* -* Swift 3 -* Tcl - *created by [Dov Murik](https://github.com/dubek)* -* TypeScript - *created by [Masahiro Wakame](https://github.com/vvakame)* -* VHDL - *created by [Dov Murik](https://github.com/dubek)* -* Vimscript - *created by [Dov Murik](https://github.com/dubek)* -* Visual Basic.NET -* WebAssembly (wasm) -* Yorick - *created by [Dov Murik](https://github.com/dubek)* +* **Ada** - *created by [Chris Moore](https://github.com/zmower)* +* **GNU awk** - *created by [Miutsuru Kariya](https://github.com/kariya-mitsuru)* +* **Bash 4** +* **BASIC** (C64 and QBasic) +* **C** +* **C++** - *created by [Stephen Thirlwall](https://github.com/sdt)* +* **C#** +* **ChucK** - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* +* **Common Lisp** - *created by [Iqbal Ansari](https://github.com/iqbalansari)* +* **Clojure** (Clojure and ClojureScript) +* **CoffeeScript** +* **Crystal** - *created by [Linda_pp](https://github.com/rhysd)* +* **D** - *created by [Dov Murik](https://github.com/dubek)* +* **Dart** - *created by [Harry Terkelsen](https://github.com/hterkelsen)* +* **Elixir** - *created by [Martin Ek](https://github.com/ekmartin)* +* **Elm** - *created by [Jos van Bakel](https://github.com/c0deaddict)* +* **Emacs Lisp** - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* +* **Erlang** - *created by [Nathan Fiedler](https://github.com/nlfiedler)* +* **ES6** (ECMAScript 6 / ECMAScript 2015) +* **F#** - *created by [Peter Stephens](https://github.com/pstephens)* +* **Factor** - *created by [Jordan Lewis](https://github.com/jordanlewis)* +* **Fantom** - *created by [Dov Murik](https://github.com/dubek)* +* **Forth** - *created by [Chris Houser](https://github.com/chouser)* +* **Go** +* **Groovy** +* **GNU Guile** - *created by [Mu Lei](https://github.com/NalaGinrut).* +* **GNU Smalltalk** - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* +* **Haskell** +* **Haxe** (Neko, Python, C++ and JavaScript) +* **Hy** +* **Io** - *created by [Dov Murik](https://github.com/dubek)* +* **Java** +* **JavaScript** ([Online Demo](http://kanaka.github.io/mal)) +* **Julia** +* **Kotlin** - *created by [Javier Fernandez-Ivern](https://github.com/ivern)* +* **LiveScript** - *created by [Jos van Bakel](https://github.com/c0deaddict)* +* **Logo** - *created by [Dov Murik](https://github.com/dubek)* +* **Lua** +* **GNU Make** +* **mal itself** +* **Matlab** (GNU Octave and MATLAB) +* **[miniMAL](https://github.com/kanaka/miniMAL)** +* **NASM** - *created by [Ben Dudson](https://github.com/bendudson)* +* **Nim** - *created by [Dennis Felsing](https://github.com/def-)* +* **Object Pascal** +* **Objective C** +* **OCaml** - *created by [Chris Houser](https://github.com/chouser)* +* **Perl** +* **Perl 6** - *created by [Hinrik Örn Sigurðsson](https://github.com/hinrik)* +* **PHP** +* **Picolisp** - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* +* **PL/pgSQL** (Postgres) +* **PL/SQL** (Oracle) +* **Postscript** +* **PowerShell** +* **Python** (2.X and 3.X) +* **RPython** +* **R** +* **Racket** +* **Rexx** - *created by [Dov Murik](https://github.com/dubek)* +* **Ruby** +* **Rust** +* **Scala** +* **Scheme (R7RS)** - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* +* **Skew** - *created by [Dov Murik](https://github.com/dubek)* +* **Swift** - *created by [Keith Rollin](https://github.com/keith-rollin)* +* **Swift 3** +* **Tcl** - *created by [Dov Murik](https://github.com/dubek)* +* **TypeScript** - *created by [Masahiro Wakame](https://github.com/vvakame)* +* **VHDL** - *created by [Dov Murik](https://github.com/dubek)* +* **Vimscript** - *created by [Dov Murik](https://github.com/dubek)* +* **Visual Basic.NET** +* **WebAssembly** (wasm) +* **Yorick** - *created by [Dov Murik](https://github.com/dubek)* Mal is a learning tool. See the [make-a-lisp process From 78c71a51adec8691c2d4fea6f913d5432eacb5fb Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Mon, 10 Oct 2016 19:05:19 +0200 Subject: [PATCH 0750/2308] In step1, test non-numbers starting with a dash. --- tests/step1_read_print.mal | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/step1_read_print.mal b/tests/step1_read_print.mal index a40e9dba19..51290a9a76 100644 --- a/tests/step1_read_print.mal +++ b/tests/step1_read_print.mal @@ -21,6 +21,13 @@ abc5 abc-def ;=>abc-def +;; Testing non-numbers starting with a dash. +- +;=>- +-abc +;=>-abc +->> +;=>->> ;; Testing read of lists (+ 1 2) From f29ccc407a895d11a6c21549ff5f45ecb416e9aa Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 30 Jan 2019 11:54:57 -0600 Subject: [PATCH 0751/2308] fsharp, swift3: fix parsing of single '-' Fixes https://github.com/kanaka/mal/pull/338 --- fsharp/tokenizer.fs | 2 +- swift3/Sources/reader.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fsharp/tokenizer.fs b/fsharp/tokenizer.fs index fb2834ce32..6bfbc74159 100644 --- a/fsharp/tokenizer.fs +++ b/fsharp/tokenizer.fs @@ -102,7 +102,7 @@ module Tokenizer | '@' -> At, n | '"' -> accumulateString n | ':' -> accumulateKeyword n - | '-' when isDigit str.[n] -> accumulateWhile isDigit Number p n + | '-' when n < len && isDigit str.[n] -> accumulateWhile isDigit Number p n | ch when isDigit ch -> accumulateWhile isDigit Number p n | ch when isTokenChar ch -> accumulateWhile isTokenChar Token p n | _ -> raise <| Error.unexpectedChar () diff --git a/swift3/Sources/reader.swift b/swift3/Sources/reader.swift index f729dd6c8b..e4ebd582d7 100644 --- a/swift3/Sources/reader.swift +++ b/swift3/Sources/reader.swift @@ -113,7 +113,7 @@ func read_atom(_ rdr: Reader) throws -> MalVal { throw MalError.Reader(msg: "Empty string passed to read_atom") } switch rdr.str[rdr.pos] { - case "-" where !int_char.contains(rdr.str[rdr.str.index(after: rdr.pos)]): + case "-" where rdr.str.characters.count == 1 || !int_char.contains(rdr.str[rdr.str.index(after: rdr.pos)]): return try read_symbol(rdr) case let c where int_char.contains(c): return read_int(rdr) From e47a5ec437728287c2eef0fce04c16fd0ed8d1a0 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 30 Jan 2019 11:56:01 -0600 Subject: [PATCH 0752/2308] Makefile: mention TEST_OPTS in help. --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 52cf142ec6..31da504b01 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,7 @@ all help: @echo 'make MAL_IMPL=IMPL "test^mal..." # use IMPL for self-host tests' @echo 'make REGRESS=1 "test..." # test with previous step tests too' @echo 'make DOCKERIZE=1 ... # to dockerize above rules/targets' + @echo 'make TEST_OPTS="--opt ..." # options to pass to runtest.py' @echo @echo 'Other:' @echo From d57f74df9b354d2dd294bdec6d4342d638e85f8c Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 30 Jan 2019 12:05:34 -0600 Subject: [PATCH 0753/2308] README: mention make help target. --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 109080e321..bcfc94a381 100644 --- a/README.md +++ b/README.md @@ -994,6 +994,14 @@ yorick -batch ./stepX_YYY.i ## Running tests +The top level Makefile has a number of useful targets to assist with +implementation development and testing. The `help` target provides +a list of the targets and options: + +``` +make help +``` + ### Functional tests The are over 600 generic functional tests (for all implementations) @@ -1003,9 +1011,6 @@ launches a Mal step implementation and then feeds the tests one at a time to the implementation and compares the output/return value to the expected output/return value. -To simplify the process of running tests, a top level Makefile is -provided with convenient test targets. - * To run all the tests across all implementations (be prepared to wait): ``` From d12bf4787c6f9213fe65c99063f22c2c6bd43ec5 Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Mon, 10 Oct 2016 19:06:37 +0200 Subject: [PATCH 0754/2308] In step6, test that eval uses Repl not Env. I have not managed to test this before step7 quotes. --- tests/step7_quote.mal | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/step7_quote.mal b/tests/step7_quote.mal index 4f3e3569ba..b4e7d4b997 100644 --- a/tests/step7_quote.mal +++ b/tests/step7_quote.mal @@ -181,3 +181,9 @@ b ;;; TODO: fix this ;;;;=>[1 1 "b" "d" 3] +;; Checking that eval does not use local environments. +;; Test step6, but requires a quote. +(def! a 1) +;=>1 +(let* (a 2) (eval 'a)) +;=>1 From fc7f8a4b008f8796144db6b163cfea4684737e0f Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Sun, 3 Feb 2019 14:56:34 +0100 Subject: [PATCH 0755/2308] Move test of eval builtin to step6. Fix nim implementation. The test makes more sense in step6. Thanks to kanaka for the read-string suggestion. Introduction of tail call optimization in step5 was making let* affect the parent environment. This was partially fixed in later steps. Introduction of macros in step8 was breaking the evaluation of an empty list. This was fixed by step 9 but never backported. --- nim/step5_tco.nim | 7 ++++--- nim/step6_file.nim | 7 ++++--- nim/step7_quote.nim | 7 ++++--- nim/step8_macros.nim | 4 ++-- nim/step9_try.nim | 2 +- nim/stepA_mal.nim | 2 +- tests/step6_file.mal | 5 +++++ tests/step7_quote.mal | 6 ------ 8 files changed, 21 insertions(+), 19 deletions(-) diff --git a/nim/step5_tco.nim b/nim/step5_tco.nim index f192ac612c..9077b095c5 100644 --- a/nim/step5_tco.nim +++ b/nim/step5_tco.nim @@ -2,7 +2,7 @@ import rdstdin, tables, sequtils, types, reader, printer, env, core proc read(str: string): MalType = str.read_str -proc eval(ast: MalType, env: var Env): MalType +proc eval(ast: MalType, env: Env): MalType proc eval_ast(ast: MalType, env: var Env): MalType = case ast.kind @@ -19,8 +19,9 @@ proc eval_ast(ast: MalType, env: var Env): MalType = else: result = ast -proc eval(ast: MalType, env: var Env): MalType = +proc eval(ast: MalType, env: Env): MalType = var ast = ast + var env = env template defaultApply = let el = ast.eval_ast(env) @@ -50,7 +51,7 @@ proc eval(ast: MalType, env: var Env): MalType = let a1 = ast.list[1] a2 = ast.list[2] - var let_env = env + var let_env = initEnv(env) case a1.kind of List, Vector: for i in countup(0, a1.list.high, 2): diff --git a/nim/step6_file.nim b/nim/step6_file.nim index 50155e21dc..7af6e6e1b9 100644 --- a/nim/step6_file.nim +++ b/nim/step6_file.nim @@ -2,7 +2,7 @@ import rdstdin, tables, sequtils, os, types, reader, printer, env, core proc read(str: string): MalType = str.read_str -proc eval(ast: MalType, env: var Env): MalType +proc eval(ast: MalType, env: Env): MalType proc eval_ast(ast: MalType, env: var Env): MalType = case ast.kind @@ -19,8 +19,9 @@ proc eval_ast(ast: MalType, env: var Env): MalType = else: result = ast -proc eval(ast: MalType, env: var Env): MalType = +proc eval(ast: MalType, env: Env): MalType = var ast = ast + var env = env template defaultApply = let el = ast.eval_ast(env) @@ -50,7 +51,7 @@ proc eval(ast: MalType, env: var Env): MalType = let a1 = ast.list[1] a2 = ast.list[2] - var let_env = env + var let_env = initEnv(env) case a1.kind of List, Vector: for i in countup(0, a1.list.high, 2): diff --git a/nim/step7_quote.nim b/nim/step7_quote.nim index 7917849d31..341072c7cc 100644 --- a/nim/step7_quote.nim +++ b/nim/step7_quote.nim @@ -16,7 +16,7 @@ proc quasiquote(ast: MalType): MalType = else: return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. ^1]))) -proc eval(ast: MalType, env: var Env): MalType +proc eval(ast: MalType, env: Env): MalType proc eval_ast(ast: MalType, env: var Env): MalType = case ast.kind @@ -33,8 +33,9 @@ proc eval_ast(ast: MalType, env: var Env): MalType = else: result = ast -proc eval(ast: MalType, env: var Env): MalType = +proc eval(ast: MalType, env: Env): MalType = var ast = ast + var env = env template defaultApply = let el = ast.eval_ast(env) @@ -64,7 +65,7 @@ proc eval(ast: MalType, env: var Env): MalType = let a1 = ast.list[1] a2 = ast.list[2] - var let_env = env + var let_env = initEnv(env) case a1.kind of List, Vector: for i in countup(0, a1.list.high, 2): diff --git a/nim/step8_macros.nim b/nim/step8_macros.nim index 0cd6776778..6de153b2a6 100644 --- a/nim/step8_macros.nim +++ b/nim/step8_macros.nim @@ -17,7 +17,7 @@ proc quasiquote(ast: MalType): MalType = return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. ^1]))) proc is_macro_call(ast: MalType, env: Env): bool = - ast.kind == List and ast.list[0].kind == Symbol and + ast.kind == List and ast.list.len > 0 and ast.list[0].kind == Symbol and env.find(ast.list[0].str) != nil and env.get(ast.list[0].str).fun_is_macro proc macroexpand(ast: MalType, env: Env): MalType = @@ -78,7 +78,7 @@ proc eval(ast: MalType, env: Env): MalType = let a1 = ast.list[1] a2 = ast.list[2] - var let_env = env + var let_env = initEnv(env) case a1.kind of List, Vector: for i in countup(0, a1.list.high, 2): diff --git a/nim/step9_try.nim b/nim/step9_try.nim index 864ca91133..314a42c9a2 100644 --- a/nim/step9_try.nim +++ b/nim/step9_try.nim @@ -79,7 +79,7 @@ proc eval(ast: MalType, env: Env): MalType = let a1 = ast.list[1] a2 = ast.list[2] - var let_env = env + var let_env = initEnv(env) case a1.kind of List, Vector: for i in countup(0, a1.list.high, 2): diff --git a/nim/stepA_mal.nim b/nim/stepA_mal.nim index 4932da837a..edfe443544 100644 --- a/nim/stepA_mal.nim +++ b/nim/stepA_mal.nim @@ -79,7 +79,7 @@ proc eval(ast: MalType, env: Env): MalType = let a1 = ast.list[1] a2 = ast.list[2] - var let_env = env + var let_env = initEnv(env) case a1.kind of List, Vector: for i in countup(0, a1.list.high, 2): diff --git a/tests/step6_file.mal b/tests/step6_file.mal index 486725ee4b..c9264da239 100644 --- a/tests/step6_file.mal +++ b/tests/step6_file.mal @@ -125,3 +125,8 @@ mymap (g 3) ;=>81 +;; Checking that eval does not use local environments. +(def! a 1) +;=>1 +(let* (a 2) (eval (read-string "a"))) +;=>1 diff --git a/tests/step7_quote.mal b/tests/step7_quote.mal index b4e7d4b997..4f3e3569ba 100644 --- a/tests/step7_quote.mal +++ b/tests/step7_quote.mal @@ -181,9 +181,3 @@ b ;;; TODO: fix this ;;;;=>[1 1 "b" "d" 3] -;; Checking that eval does not use local environments. -;; Test step6, but requires a quote. -(def! a 1) -;=>1 -(let* (a 2) (eval 'a)) -;=>1 From c5dd7ecba0267e3233e127dde56b675b4b80acef Mon Sep 17 00:00:00 2001 From: Juan de Bruin Date: Thu, 7 Feb 2019 12:04:54 +0200 Subject: [PATCH 0756/2308] Fix typo --- process/guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/process/guide.md b/process/guide.md index 49bfb3b574..18c9075d2b 100644 --- a/process/guide.md +++ b/process/guide.md @@ -332,7 +332,7 @@ expression support. ```[]{}()'`~^@``` (tokenized). * `"(?:\\.|[^\\"])*"?`: Starts capturing at a double-quote and stops at the - next double-quote unless it was proceeded by a backslash in which case it + next double-quote unless it was preceded by a backslash in which case it includes it until the next double-quote (tokenized). It will also match unbalanced strings (no ending double-quote) which should be reported as an error. From 5b6498bcf163a12bf222f8f5b984c189fd7feb7f Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 12 Feb 2019 11:10:42 -0800 Subject: [PATCH 0757/2308] README: description section formatting and diagram Number the 3 description sections highlights. Add the step A diagram to the educational highlight. --- README.md | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index bcfc94a381..1f2041da3a 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ ## Description -Mal is a Clojure inspired Lisp interpreter. +**1. Mal is a Clojure inspired Lisp interpreter** -Mal is implemented in 74 languages: +**2. Mal is implemented in 74 languages** * **Ada** - *created by [Chris Moore](https://github.com/zmower)* * **GNU awk** - *created by [Miutsuru Kariya](https://github.com/kariya-mitsuru)* @@ -84,13 +84,15 @@ Mal is implemented in 74 languages: * **Yorick** - *created by [Dov Murik](https://github.com/dubek)* -Mal is a learning tool. See the [make-a-lisp process -guide](process/guide.md). Each implementation of mal is separated into +**3. Mal is a learning tool** + +Each implementation of mal is separated into 11 incremental, self-contained (and testable) steps that demonstrate core concepts of Lisp. The last step is capable of self-hosting -(running the mal implementation of mal). +(running the mal implementation of mal). See the [make-a-lisp process +guide](process/guide.md). -The mal (make a lisp) steps are: +The make-a-lisp steps are: * [step0_repl](process/guide.md#step0) * [step1_read_print](process/guide.md#step1) @@ -104,29 +106,37 @@ The mal (make a lisp) steps are: * [step9_try](process/guide.md#step9) * [stepA_mal](process/guide.md#stepA) +Each make-a-lisp step has an associated architectural diagram. That elements that new for that step are highlighted in red. Here is the final diagram for [step A](process/guide.md#stepA): + +![stepA_mal architecture](process/stepA_mal.png) + +If you are interesting in creating a mal implementation (or just +interested in using mal for something), please drop by the #mal +channel on freenode. In addition to the [make-a-lisp process +guide](process/guide.md) there is also a [mal/make-a-lisp +FAQ](docs/FAQ.md) where I attempt to answer some common questions. + + +## Presentations Mal was presented publicly for the first time in a lightning talk at Clojure West 2014 (unfortunately there is no video). See examples/clojurewest2014.mal for the presentation that was given at the -conference (yes, the presentation is a mal program). At Midwest.io -2015, Joel Martin gave a presentation on Mal titled "Achievement -Unlocked: A Better Path to Language Learning". +conference (yes, the presentation is a mal program). + +At Midwest.io 2015, Joel Martin gave a presentation on Mal titled +"Achievement Unlocked: A Better Path to Language Learning". [Video](https://www.youtube.com/watch?v=lgyOAiRtZGw), -[Slides](http://kanaka.github.io/midwest.io.mal/). More recently -Joel gave a presentation on "Make Your Own Lisp Interpreter in -10 Incremental Steps" at LambdaConf 2016: +[Slides](http://kanaka.github.io/midwest.io.mal/). + +More recently Joel gave a presentation on "Make Your Own Lisp Interpreter +in 10 Incremental Steps" at LambdaConf 2016: [Part 1](https://www.youtube.com/watch?v=jVhupfthTEk), [Part 2](https://www.youtube.com/watch?v=X5OQBMGpaTU), [Part 3](https://www.youtube.com/watch?v=6mARZzGgX4U), [Part 4](https://www.youtube.com/watch?v=dCO1SYR5kDU), [Slides](http://kanaka.github.io/lambdaconf/). -If you are interesting in creating a mal implementation (or just -interested in using mal for something), please drop by the #mal -channel on freenode. In addition to the [make-a-lisp process -guide](process/guide.md) there is also a [mal/make-a-lisp -FAQ](docs/FAQ.md) where I attempt to answer some common questions. - ## Building/running implementations The simplest way to run any given implementation is to use docker. From 138cbb9c100a706cff594096dca6dd4ec82177a4 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 12 Feb 2019 11:13:46 -0800 Subject: [PATCH 0758/2308] README: fix typo --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1f2041da3a..ab58496559 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,9 @@ The make-a-lisp steps are: * [step9_try](process/guide.md#step9) * [stepA_mal](process/guide.md#stepA) -Each make-a-lisp step has an associated architectural diagram. That elements that new for that step are highlighted in red. Here is the final diagram for [step A](process/guide.md#stepA): +Each make-a-lisp step has an associated architectural diagram. That elements +that are new for that step are highlighted in red. +Here is the final diagram for [step A](process/guide.md#stepA): ![stepA_mal architecture](process/stepA_mal.png) From 2bce8a6901fd51da9ba94866506a7dbc6ebe65a1 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Tue, 19 Feb 2019 08:37:28 +0000 Subject: [PATCH 0759/2308] ruby: Fix exception when handling catchless try* --- ruby/step9_try.rb | 2 +- ruby/stepA_mal.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ruby/step9_try.rb b/ruby/step9_try.rb index 71d16be752..3a00491216 100644 --- a/ruby/step9_try.rb +++ b/ruby/step9_try.rb @@ -111,7 +111,7 @@ def EVAL(ast, env) if a2 && a2[0] == :"catch*" return EVAL(a2[2], Env.new(env, [a2[1]], [exc])) else - raise esc + raise exc end end when :do diff --git a/ruby/stepA_mal.rb b/ruby/stepA_mal.rb index 9071b74e74..2ca2db3cdf 100644 --- a/ruby/stepA_mal.rb +++ b/ruby/stepA_mal.rb @@ -117,7 +117,7 @@ def EVAL(ast, env) if a2 && a2[0] == :"catch*" return EVAL(a2[2], Env.new(env, [a2[1]], [exc])) else - raise esc + raise exc end end when :do From 7cdb44161daa258b709ece74d4ff5d826c92851e Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 19 Feb 2019 10:52:43 -0600 Subject: [PATCH 0760/2308] README: link to Tim Morgan's malcc project. Resolves https://github.com/kanaka/mal/issues/342 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ab58496559..98bb11fb4f 100644 --- a/README.md +++ b/README.md @@ -1194,9 +1194,10 @@ The following implementations are maintained as separate projects: * [by vi](https://github.com/vi/mal-rust-vi) - using [Pest](https://pest.rs/) grammar, not using typical Mal infrastructure (cargo-ized steps and built-in converted tests). -## Projects using mal +## Other mal Related Projects * [malc](https://github.com/dubek/malc) - Mal (Make A Lisp) compiler. Compiles a Mal program to LLVM assembly language, then binary. + * [malcc](https://git.sr.ht/~tim/malcc) - malcc is an incremental compiler implementation for the Mal language. It uses the Tiny C Compiler as the compiler backend and has full support for the Mal language, including macros, tail-call elimination, and even run-time eval. * [frock](https://github.com/chr15m/frock) - Clojure-flavoured PHP. Uses mal/php to run programs. ## License From daafc18dfa07d81be4ad6bb8515d558d628b8cd9 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 19 Feb 2019 10:54:04 -0600 Subject: [PATCH 0761/2308] README: better title "Other mal projects" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 98bb11fb4f..7372b4522a 100644 --- a/README.md +++ b/README.md @@ -1194,7 +1194,7 @@ The following implementations are maintained as separate projects: * [by vi](https://github.com/vi/mal-rust-vi) - using [Pest](https://pest.rs/) grammar, not using typical Mal infrastructure (cargo-ized steps and built-in converted tests). -## Other mal Related Projects +## Other mal Projects * [malc](https://github.com/dubek/malc) - Mal (Make A Lisp) compiler. Compiles a Mal program to LLVM assembly language, then binary. * [malcc](https://git.sr.ht/~tim/malcc) - malcc is an incremental compiler implementation for the Mal language. It uses the Tiny C Compiler as the compiler backend and has full support for the Mal language, including macros, tail-call elimination, and even run-time eval. From dad737adbf6a801d2d611708835ff4a6803662d3 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 19 Feb 2019 10:58:19 -0600 Subject: [PATCH 0762/2308] README: add Tim Morgan (malcc) github user. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7372b4522a..de0678f267 100644 --- a/README.md +++ b/README.md @@ -1197,7 +1197,7 @@ The following implementations are maintained as separate projects: ## Other mal Projects * [malc](https://github.com/dubek/malc) - Mal (Make A Lisp) compiler. Compiles a Mal program to LLVM assembly language, then binary. - * [malcc](https://git.sr.ht/~tim/malcc) - malcc is an incremental compiler implementation for the Mal language. It uses the Tiny C Compiler as the compiler backend and has full support for the Mal language, including macros, tail-call elimination, and even run-time eval. + * [malcc](https://git.sr.ht/~tim/malcc) (@seven1m) - malcc is an incremental compiler implementation for the Mal language. It uses the Tiny C Compiler as the compiler backend and has full support for the Mal language, including macros, tail-call elimination, and even run-time eval. * [frock](https://github.com/chr15m/frock) - Clojure-flavoured PHP. Uses mal/php to run programs. ## License From 5020037a09528a96974b3bcd859b64ab94bef942 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 19 Feb 2019 20:46:04 +0100 Subject: [PATCH 0763/2308] elisp: Reraise error correctly in try* --- elisp/step9_try.el | 2 +- elisp/stepA_mal.el | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/elisp/step9_try.el b/elisp/step9_try.el index c3b5907aa9..154111bf7e 100644 --- a/elisp/step9_try.el +++ b/elisp/step9_try.el @@ -128,7 +128,7 @@ (mal-string (error-message-string err)))) (env* (mal-env env (list identifier) (list err*)))) (throw 'return (EVAL form env*))) - (apply 'signal err))))) + (signal (car err) (list (cadr err))))))) ((eq a0* 'do) (let* ((a0... (cdr a)) (butlast (butlast a0...)) diff --git a/elisp/stepA_mal.el b/elisp/stepA_mal.el index 9f26edbca0..f0250bad44 100644 --- a/elisp/stepA_mal.el +++ b/elisp/stepA_mal.el @@ -128,7 +128,7 @@ (mal-string (error-message-string err)))) (env* (mal-env env (list identifier) (list err*)))) (throw 'return (EVAL form env*))) - (apply 'signal err))))) + (signal (car err) (list (cadr err))))))) ((eq a0* 'do) (let* ((a0... (cdr a)) (butlast (butlast a0...)) From 948eeb0422f862c01f2304fd0eaf7e8becb43a20 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 19 Feb 2019 20:49:53 +0100 Subject: [PATCH 0764/2308] elisp: Drop unused type definition --- elisp/types.el | 2 -- 1 file changed, 2 deletions(-) diff --git a/elisp/types.el b/elisp/types.el index 6abcbd2ad3..591b7fd27b 100644 --- a/elisp/types.el +++ b/elisp/types.el @@ -37,9 +37,7 @@ (mal-object vector) (mal-object map) -(mal-object env) (mal-object atom) - (mal-object fn) (mal-object func) From a3b3a3cc7cc8384500631ced698047aa3dcbebad Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 19 Feb 2019 20:50:14 +0100 Subject: [PATCH 0765/2308] elisp: Get rid of newline hack in printer --- elisp/printer.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elisp/printer.el b/elisp/printer.el index f00efa799c..48293529c8 100644 --- a/elisp/printer.el +++ b/elisp/printer.el @@ -12,8 +12,8 @@ (number-to-string (mal-value form))) ((eq type 'string) (if print-readably - ;; HACK prin1-to-string does only quotes and backslashes - (replace-regexp-in-string "\n" "\\\\n" (prin1-to-string value)) + (let ((print-escape-newlines t)) + (prin1-to-string value)) value)) ((or (eq type 'symbol) (eq type 'keyword)) (symbol-name value)) From 79c5b02eec9de5c801b27564a1e57a9673210620 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 19 Feb 2019 21:01:16 +0100 Subject: [PATCH 0766/2308] elisp: Disable curved quotes to fix test rig fails --- elisp/run | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elisp/run b/elisp/run index 84a936e50c..405cce4302 100755 --- a/elisp/run +++ b/elisp/run @@ -1,2 +1,2 @@ #!/bin/bash -exec emacs -Q --batch --load $(dirname $0)/${STEP:-stepA_mal}.el "${@}" +exec emacs -Q --batch --eval "(setq text-quoting-style 'straight)" --load $(dirname $0)/${STEP:-stepA_mal}.el "${@}" From fabdabe2b608b6a869e577617248153bde0f36b1 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 19 Feb 2019 21:01:42 +0100 Subject: [PATCH 0767/2308] elisp: Relax version RE check --- elisp/tests/stepA_mal.mal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elisp/tests/stepA_mal.mal b/elisp/tests/stepA_mal.mal index c655af6aa7..ec8c701f3c 100644 --- a/elisp/tests/stepA_mal.mal +++ b/elisp/tests/stepA_mal.mal @@ -16,6 +16,6 @@ ;/Hello World! ;=>nil -(elisp-eval "(setq emacs-version-re (rx (+ digit) \".\" digit \".\" digit))") +(elisp-eval "(setq emacs-version-re (rx (+ digit) \".\" digit))") (elisp-eval "(and (string-match-p emacs-version-re emacs-version) t)") ;=>true From b5df0de6f504cdc75f3bdedbe482bda9817958cd Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Tue, 19 Feb 2019 21:10:15 +0100 Subject: [PATCH 0768/2308] elisp: Get rid of load-relative hack --- elisp/core.el | 2 ++ elisp/func.el | 2 ++ elisp/{env.el => mal-env.el} | 2 ++ elisp/printer.el | 2 ++ elisp/reader.el | 2 ++ elisp/run | 2 +- elisp/step1_read_print.el | 11 +++-------- elisp/step2_eval.el | 11 +++-------- elisp/step3_env.el | 13 ++++--------- elisp/step4_if_fn_do.el | 15 +++++---------- elisp/step5_tco.el | 18 +++++++----------- elisp/step6_file.el | 17 ++++++----------- elisp/step7_quote.el | 17 ++++++----------- elisp/step8_macros.el | 17 ++++++----------- elisp/step9_try.el | 17 ++++++----------- elisp/stepA_mal.el | 17 ++++++----------- elisp/types.el | 2 ++ 17 files changed, 65 insertions(+), 102 deletions(-) rename elisp/{env.el => mal-env.el} (97%) diff --git a/elisp/core.el b/elisp/core.el index 7f4beca647..1d97f16396 100644 --- a/elisp/core.el +++ b/elisp/core.el @@ -258,3 +258,5 @@ (elisp-eval . ,(mal-fn (lambda (string) (elisp-to-mal (eval (read (mal-value string))))))) )) + +(provide 'core) diff --git a/elisp/func.el b/elisp/func.el index a7bc2207f0..55b33b6d9f 100644 --- a/elisp/func.el +++ b/elisp/func.el @@ -15,3 +15,5 @@ (defun mal-func-macro-p (mal-func) (aref (aref mal-func 1) 4)) + +(provide 'func) diff --git a/elisp/env.el b/elisp/mal-env.el similarity index 97% rename from elisp/env.el rename to elisp/mal-env.el index 2712dbccf6..e7eea800ab 100644 --- a/elisp/env.el +++ b/elisp/mal-env.el @@ -30,3 +30,5 @@ (if (not value) (error "'%s' not found" key) value))) + +(provide 'mal-env) diff --git a/elisp/printer.el b/elisp/printer.el index 48293529c8..5c08a04ca5 100644 --- a/elisp/printer.el +++ b/elisp/printer.el @@ -54,3 +54,5 @@ (lambda (item) (concat (car item) " " (cdr item))) (nreverse pairs) " "))) (concat "{" items "}")))) + +(provide 'printer) diff --git a/elisp/reader.el b/elisp/reader.el index af68fd1fad..2461bdd59b 100644 --- a/elisp/reader.el +++ b/elisp/reader.el @@ -153,3 +153,5 @@ ;; assume anything else is a symbol (mal-symbol (intern token)))) (signal 'end-of-token-stream nil)))) + +(provide 'reader) diff --git a/elisp/run b/elisp/run index 405cce4302..c68e97bf93 100755 --- a/elisp/run +++ b/elisp/run @@ -1,2 +1,2 @@ #!/bin/bash -exec emacs -Q --batch --eval "(setq text-quoting-style 'straight)" --load $(dirname $0)/${STEP:-stepA_mal}.el "${@}" +exec emacs -Q --batch -L $(dirname $0) --eval "(setq text-quoting-style 'straight)" --load $(dirname $0)/${STEP:-stepA_mal}.el "${@}" diff --git a/elisp/step1_read_print.el b/elisp/step1_read_print.el index af3ff7b09c..1f56733837 100644 --- a/elisp/step1_read_print.el +++ b/elisp/step1_read_print.el @@ -1,11 +1,6 @@ -(defun load-relative (file) - (let* ((current-file (or load-file-name buffer-file-name)) - (current-file-directory (file-name-directory current-file))) - (load (expand-file-name file current-file-directory) nil t))) - -(load-relative "types.el") -(load-relative "reader.el") -(load-relative "printer.el") +(require 'types) +(require 'reader) +(require 'printer) (defun READ (input) (read-str input)) diff --git a/elisp/step2_eval.el b/elisp/step2_eval.el index b5f07234cc..8ff9510374 100644 --- a/elisp/step2_eval.el +++ b/elisp/step2_eval.el @@ -1,11 +1,6 @@ -(defun load-relative (file) - (let* ((current-file (or load-file-name buffer-file-name)) - (current-file-directory (file-name-directory current-file))) - (load (expand-file-name file current-file-directory) nil t))) - -(load-relative "types.el") -(load-relative "reader.el") -(load-relative "printer.el") +(require 'types) +(require 'reader) +(require 'printer) (defvar repl-env (make-hash-table :test 'eq)) (puthash '+ (lambda (a b) (mal-number (+ (mal-value a) (mal-value b)))) repl-env) diff --git a/elisp/step3_env.el b/elisp/step3_env.el index 1544b1fbfb..05dac6d416 100644 --- a/elisp/step3_env.el +++ b/elisp/step3_env.el @@ -1,12 +1,7 @@ -(defun load-relative (file) - (let* ((current-file (or load-file-name buffer-file-name)) - (current-file-directory (file-name-directory current-file))) - (load (expand-file-name file current-file-directory) nil t))) - -(load-relative "types.el") -(load-relative "env.el") -(load-relative "reader.el") -(load-relative "printer.el") +(require 'types) +(require 'mal-env) +(require 'reader) +(require 'printer) (defvar repl-env (mal-env)) (mal-env-set repl-env '+ (lambda (a b) (mal-number (+ (mal-value a) (mal-value b))))) diff --git a/elisp/step4_if_fn_do.el b/elisp/step4_if_fn_do.el index 4eb141b2fe..21df7b8394 100644 --- a/elisp/step4_if_fn_do.el +++ b/elisp/step4_if_fn_do.el @@ -1,15 +1,10 @@ ;; -*- lexical-binding: t; -*- -(defun load-relative (file) - (let* ((current-file (or load-file-name buffer-file-name)) - (current-file-directory (file-name-directory current-file))) - (load (expand-file-name file current-file-directory) nil t))) - -(load-relative "types.el") -(load-relative "env.el") -(load-relative "reader.el") -(load-relative "printer.el") -(load-relative "core.el") +(require 'types) +(require 'mal-env) +(require 'reader) +(require 'printer) +(require 'core) (defvar repl-env (mal-env)) diff --git a/elisp/step5_tco.el b/elisp/step5_tco.el index 86babe8df8..31a9d0c2f0 100644 --- a/elisp/step5_tco.el +++ b/elisp/step5_tco.el @@ -1,16 +1,12 @@ ;; -*- lexical-binding: t; -*- -(defun load-relative (file) - (let* ((current-file (or load-file-name buffer-file-name)) - (current-file-directory (file-name-directory current-file))) - (load (expand-file-name file current-file-directory) nil t))) - -(load-relative "types.el") -(load-relative "env.el") -(load-relative "func.el") -(load-relative "reader.el") -(load-relative "printer.el") -(load-relative "core.el") +(setq debug-on-error t) +(require 'types) +(require 'func) +(require 'mal-env) +(require 'reader) +(require 'printer) +(require 'core) (defvar repl-env (mal-env)) diff --git a/elisp/step6_file.el b/elisp/step6_file.el index ba363d4846..aef72f6c7a 100644 --- a/elisp/step6_file.el +++ b/elisp/step6_file.el @@ -1,16 +1,11 @@ ;; -*- lexical-binding: t; -*- -(defun load-relative (file) - (let* ((current-file (or load-file-name buffer-file-name)) - (current-file-directory (file-name-directory current-file))) - (load (expand-file-name file current-file-directory) nil t))) - -(load-relative "types.el") -(load-relative "env.el") -(load-relative "func.el") -(load-relative "reader.el") -(load-relative "printer.el") -(load-relative "core.el") +(require 'types) +(require 'func) +(require 'mal-env) +(require 'reader) +(require 'printer) +(require 'core) (defvar repl-env (mal-env)) diff --git a/elisp/step7_quote.el b/elisp/step7_quote.el index aefde0e297..e31aa34775 100644 --- a/elisp/step7_quote.el +++ b/elisp/step7_quote.el @@ -1,16 +1,11 @@ ;; -*- lexical-binding: t; -*- -(defun load-relative (file) - (let* ((current-file (or load-file-name buffer-file-name)) - (current-file-directory (file-name-directory current-file))) - (load (expand-file-name file current-file-directory) nil t))) - -(load-relative "types.el") -(load-relative "env.el") -(load-relative "func.el") -(load-relative "reader.el") -(load-relative "printer.el") -(load-relative "core.el") +(require 'types) +(require 'func) +(require 'mal-env) +(require 'reader) +(require 'printer) +(require 'core) (defvar repl-env (mal-env)) diff --git a/elisp/step8_macros.el b/elisp/step8_macros.el index ee9f220605..1e0fa3833e 100644 --- a/elisp/step8_macros.el +++ b/elisp/step8_macros.el @@ -1,16 +1,11 @@ ;; -*- lexical-binding: t; -*- -(defun load-relative (file) - (let* ((current-file (or load-file-name buffer-file-name)) - (current-file-directory (file-name-directory current-file))) - (load (expand-file-name file current-file-directory) nil t))) - -(load-relative "types.el") -(load-relative "env.el") -(load-relative "func.el") -(load-relative "reader.el") -(load-relative "printer.el") -(load-relative "core.el") +(require 'types) +(require 'func) +(require 'mal-env) +(require 'reader) +(require 'printer) +(require 'core) (defvar repl-env (mal-env)) diff --git a/elisp/step9_try.el b/elisp/step9_try.el index 154111bf7e..d157892dc0 100644 --- a/elisp/step9_try.el +++ b/elisp/step9_try.el @@ -1,16 +1,11 @@ ;; -*- lexical-binding: t; -*- -(defun load-relative (file) - (let* ((current-file (or load-file-name buffer-file-name)) - (current-file-directory (file-name-directory current-file))) - (load (expand-file-name file current-file-directory) nil t))) - -(load-relative "types.el") -(load-relative "env.el") -(load-relative "func.el") -(load-relative "reader.el") -(load-relative "printer.el") -(load-relative "core.el") +(require 'types) +(require 'func) +(require 'mal-env) +(require 'reader) +(require 'printer) +(require 'core) (defvar repl-env (mal-env)) diff --git a/elisp/stepA_mal.el b/elisp/stepA_mal.el index f0250bad44..10445bffd9 100644 --- a/elisp/stepA_mal.el +++ b/elisp/stepA_mal.el @@ -1,16 +1,11 @@ ;; -*- lexical-binding: t; -*- -(defun load-relative (file) - (let* ((current-file (or load-file-name buffer-file-name)) - (current-file-directory (file-name-directory current-file))) - (load (expand-file-name file current-file-directory) nil t))) - -(load-relative "types.el") -(load-relative "env.el") -(load-relative "func.el") -(load-relative "reader.el") -(load-relative "printer.el") -(load-relative "core.el") +(require 'types) +(require 'func) +(require 'mal-env) +(require 'reader) +(require 'printer) +(require 'core) (defvar repl-env (mal-env)) diff --git a/elisp/types.el b/elisp/types.el index 591b7fd27b..e9d914cafc 100644 --- a/elisp/types.el +++ b/elisp/types.el @@ -100,3 +100,5 @@ Defaults to `error'." (define-error 'unterminated-sequence "Unterminated token sequence" 'mal) (define-error 'end-of-token-stream "End of token stream" 'mal) (define-error 'mal-custom "Custom error" 'mal) + +(provide 'types) From c5cd05aee87952117de855527bf5eba22edc5eb3 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 19 Feb 2019 15:40:02 -0600 Subject: [PATCH 0769/2308] README: language list to table with anchor links --- README.md | 158 +++++++++++++++++++++++++++--------------------------- 1 file changed, 80 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index de0678f267..0759b25c9f 100644 --- a/README.md +++ b/README.md @@ -8,80 +8,82 @@ **2. Mal is implemented in 74 languages** -* **Ada** - *created by [Chris Moore](https://github.com/zmower)* -* **GNU awk** - *created by [Miutsuru Kariya](https://github.com/kariya-mitsuru)* -* **Bash 4** -* **BASIC** (C64 and QBasic) -* **C** -* **C++** - *created by [Stephen Thirlwall](https://github.com/sdt)* -* **C#** -* **ChucK** - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* -* **Common Lisp** - *created by [Iqbal Ansari](https://github.com/iqbalansari)* -* **Clojure** (Clojure and ClojureScript) -* **CoffeeScript** -* **Crystal** - *created by [Linda_pp](https://github.com/rhysd)* -* **D** - *created by [Dov Murik](https://github.com/dubek)* -* **Dart** - *created by [Harry Terkelsen](https://github.com/hterkelsen)* -* **Elixir** - *created by [Martin Ek](https://github.com/ekmartin)* -* **Elm** - *created by [Jos van Bakel](https://github.com/c0deaddict)* -* **Emacs Lisp** - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* -* **Erlang** - *created by [Nathan Fiedler](https://github.com/nlfiedler)* -* **ES6** (ECMAScript 6 / ECMAScript 2015) -* **F#** - *created by [Peter Stephens](https://github.com/pstephens)* -* **Factor** - *created by [Jordan Lewis](https://github.com/jordanlewis)* -* **Fantom** - *created by [Dov Murik](https://github.com/dubek)* -* **Forth** - *created by [Chris Houser](https://github.com/chouser)* -* **Go** -* **Groovy** -* **GNU Guile** - *created by [Mu Lei](https://github.com/NalaGinrut).* -* **GNU Smalltalk** - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* -* **Haskell** -* **Haxe** (Neko, Python, C++ and JavaScript) -* **Hy** -* **Io** - *created by [Dov Murik](https://github.com/dubek)* -* **Java** -* **JavaScript** ([Online Demo](http://kanaka.github.io/mal)) -* **Julia** -* **Kotlin** - *created by [Javier Fernandez-Ivern](https://github.com/ivern)* -* **LiveScript** - *created by [Jos van Bakel](https://github.com/c0deaddict)* -* **Logo** - *created by [Dov Murik](https://github.com/dubek)* -* **Lua** -* **GNU Make** -* **mal itself** -* **Matlab** (GNU Octave and MATLAB) -* **[miniMAL](https://github.com/kanaka/miniMAL)** -* **NASM** - *created by [Ben Dudson](https://github.com/bendudson)* -* **Nim** - *created by [Dennis Felsing](https://github.com/def-)* -* **Object Pascal** -* **Objective C** -* **OCaml** - *created by [Chris Houser](https://github.com/chouser)* -* **Perl** -* **Perl 6** - *created by [Hinrik Örn Sigurðsson](https://github.com/hinrik)* -* **PHP** -* **Picolisp** - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* -* **PL/pgSQL** (Postgres) -* **PL/SQL** (Oracle) -* **Postscript** -* **PowerShell** -* **Python** (2.X and 3.X) -* **RPython** -* **R** -* **Racket** -* **Rexx** - *created by [Dov Murik](https://github.com/dubek)* -* **Ruby** -* **Rust** -* **Scala** -* **Scheme (R7RS)** - *created by [Vasilij Schneidermann](https://github.com/wasamasa)* -* **Skew** - *created by [Dov Murik](https://github.com/dubek)* -* **Swift** - *created by [Keith Rollin](https://github.com/keith-rollin)* -* **Swift 3** -* **Tcl** - *created by [Dov Murik](https://github.com/dubek)* -* **TypeScript** - *created by [Masahiro Wakame](https://github.com/vvakame)* -* **VHDL** - *created by [Dov Murik](https://github.com/dubek)* -* **Vimscript** - *created by [Dov Murik](https://github.com/dubek)* -* **Visual Basic.NET** -* **WebAssembly** (wasm) -* **Yorick** - *created by [Dov Murik](https://github.com/dubek)* +| Language | Creator | +| -------- | ------- | +| [Ada](#ada) | [Chris Moore](https://github.com/zmower) | +| [GNU awk](#gnu-awk) | [Miutsuru Kariya](https://github.com/kariya-mitsuru) | +| [Bash 4](#bash-4) | [Joel Martin](https://github.com/kanaka) | +| [BASIC](#basic-c64-and-qbasic) (C64 & QBasic) | [Joel Martin](https://github.com/kanaka) | +| [C](#c) | [Joel Martin](https://github.com/kanaka) | +| [C++](#c-1) | [Stephen Thirlwall](https://github.com/sdt) | +| [C#](#c-2) | [Joel Martin](https://github.com/kanaka) | +| [ChucK](#chuck) | [Vasilij Schneidermann](https://github.com/wasamasa) | +| [Common Lisp](#common-lisp) | [Iqbal Ansari](https://github.com/iqbalansari) | +| [Clojure](#clojure) (Clojure & ClojureScript) | [Joel Martin](https://github.com/kanaka) | +| [CoffeeScript](#coffeescript) | [Joel Martin](https://github.com/kanaka) | +| [Crystal](#crystal) | [Linda_pp](https://github.com/rhysd) | +| [D](#d) | [Dov Murik](https://github.com/dubek) | +| [Dart](#dart) | [Harry Terkelsen](https://github.com/hterkelsen) | +| [Elixir](#elixir) | [Martin Ek](https://github.com/ekmartin) | +| [Elm](#elm) | [Jos van Bakel](https://github.com/c0deaddict) | +| [Emacs Lisp](#emacs-lisp) | [Vasilij Schneidermann](https://github.com/wasamasa) | +| [Erlang](#erlang) | [Nathan Fiedler](https://github.com/nlfiedler) | +| [ES6](#es6-ecmascript-2015) (ECMAScript 2015) | [Joel Martin](https://github.com/kanaka) | +| [F#](#f) | [Peter Stephens](https://github.com/pstephens) | +| [Factor](#factor) | [Jordan Lewis](https://github.com/jordanlewis) | +| [Fantom](#fantom) | [Dov Murik](https://github.com/dubek) | +| [Forth](#forth) | [Chris Houser](https://github.com/chouser) | +| [Go](#go) | [Joel Martin](https://github.com/kanaka) | +| [Groovy](#groovy) | [Joel Martin](https://github.com/kanaka) | +| [GNU Guile](#gnu-guile-21) | [Mu Lei](https://github.com/NalaGinrut) | +| [GNU Smalltalk](#gnu-smalltalk) | [Vasilij Schneidermann](https://github.com/wasamasa) | +| [Haskell](#haskell) | [Joel Martin](https://github.com/kanaka) | +| [Haxe](#haxe-neko-python-c-and-javascript) (Neko, Python, C++, & JS) | [Joel Martin](https://github.com/kanaka) | +| [Hy](#hy) | [Joel Martin](https://github.com/kanaka) | +| [Io](#io) | [Dov Murik](https://github.com/dubek) | +| [Java](#java-17) | [Joel Martin](https://github.com/kanaka) | +| [JavaScript](#javascriptnode) ([Demo](http://kanaka.github.io/mal)) | [Joel Martin](https://github.com/kanaka) | +| [Julia](#julia) | [Joel Martin](https://github.com/kanaka) | +| [Kotlin](#kotlin) | [Javier Fernandez-Ivern](https://github.com/ivern) | +| [LiveScript](#livescript) | [Jos van Bakel](https://github.com/c0deaddict) | +| [Logo](#logo) | [Dov Murik](https://github.com/dubek) | +| [Lua](#lua) | [Joel Martin](https://github.com/kanaka) | +| [GNU Make](#gnu-make-381) | [Joel Martin](https://github.com/kanaka) | +| [mal itself](#mal) | [Joel Martin](https://github.com/kanaka) | +| [Matlab](#matlab-gnu-octave-and-matlab) (GNU Octave & MATLAB) | [Joel Martin](https://github.com/kanaka) | +| [miniMAL](#minimal) ([Repo](https://github.com/kanaka/miniMAL), [Demo](https://kanaka.github.io/miniMAL/)) | [Joel Martin](https://github.com/kanaka) | +| [NASM](#nasm) | [Ben Dudson](https://github.com/bendudson) | +| [Nim](#nim-0170) | [Dennis Felsing](https://github.com/def-) | +| [Object Pascal](#object-pascal) | [Joel Martin](https://github.com/kanaka) | +| [Objective C](#objective-c) | [Joel Martin](https://github.com/kanaka) | +| [OCaml](#ocaml-4010) | [Chris Houser](https://github.com/chouser) | +| [Perl](#perl-58) | [Joel Martin](https://github.com/kanaka) | +| [Perl 6](#perl-6) | [Hinrik Örn Sigurðsson](https://github.com/hinrik) | +| [PHP](#php-53) | [Joel Martin](https://github.com/kanaka) | +| [Picolisp](#picolisp) | [Vasilij Schneidermann](https://github.com/wasamasa) | +| [PL/pgSQL](#plpgsql-postgres-sql-procedural-language) (Postgres) | [Joel Martin](https://github.com/kanaka) | +| [PL/SQL](#plsql-oracle-sql-procedural-language) (Oracle) | [Joel Martin](https://github.com/kanaka) | +| [Postscript](#postscript-level-23) | [Joel Martin](https://github.com/kanaka) | +| [PowerShell](#powershell) | [Joel Martin](https://github.com/kanaka) | +| [Python](#python-2x-and-3x) (2.X & 3.X) | [Joel Martin](https://github.com/kanaka) | +| [RPython](#rpython) | [Joel Martin](https://github.com/kanaka) | +| [R](#r) | [Joel Martin](https://github.com/kanaka) | +| [Racket](#racket-53) | [Joel Martin](https://github.com/kanaka) | +| [Rexx](#rexx) | [Dov Murik](https://github.com/dubek) | +| [Ruby](#ruby-19) | [Joel Martin](https://github.com/kanaka) | +| [Rust](#rust-100-nightly) | [Joel Martin](https://github.com/kanaka) | +| [Scala](#scala) | [Joel Martin](https://github.com/kanaka) | +| [Scheme (R7RS)](#scheme-r7rs) | [Vasilij Schneidermann](https://github.com/wasamasa) | +| [Skew](#skew) | [Dov Murik](https://github.com/dubek) | +| [Swift](#swift) | [Keith Rollin](https://github.com/keith-rollin) | +| [Swift 3](#swift-3) | [Joel Martin](https://github.com/kanaka) | +| [Tcl](#tcl-86) | [Dov Murik](https://github.com/dubek) | +| [TypeScript](#typescript) | [Masahiro Wakame](https://github.com/vvakame) | +| [VHDL](#vhdl) | [Dov Murik](https://github.com/dubek) | +| [Vimscript](#vimscript) | [Dov Murik](https://github.com/dubek) | +| [Visual Basic.NET](#visual-basicnet) | [Joel Martin](https://github.com/kanaka) | +| [WebAssembly](#webassembly-wasm) (wasm) | [Joel Martin](https://github.com/kanaka) | +| [Yorick](#yorick) | [Dov Murik](https://github.com/dubek) | **3. Mal is a learning tool** @@ -371,11 +373,11 @@ MAL_STEP=stepX_YYY rebar compile escriptize # build individual step ./stepX_YYY ``` -### ES6 (ECMAScript 6 / ECMAScript 2015) +### ES6 (ECMAScript 2015) -The ES6 implementation uses the [babel](https://babeljs.io) compiler -to generate ES5 compatible JavaScript. The generated code has been -tested with Node 0.12.4. +The ES6 / ECMAScript 2015 implementation uses the +[babel](https://babeljs.io) compiler to generate ES5 compatible +JavaScript. The generated code has been tested with Node 0.12.4. ``` cd es6 From 9993f053eebafb7c6a56f1f9de94ca80ffb5a886 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Wed, 20 Feb 2019 07:04:55 +0000 Subject: [PATCH 0770/2308] vhdl: Support catchless try* --- vhdl/step9_try.vhdl | 3 ++- vhdl/stepA_mal.vhdl | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/vhdl/step9_try.vhdl b/vhdl/step9_try.vhdl index 6c811dd499..0e4b2d95c0 100644 --- a/vhdl/step9_try.vhdl +++ b/vhdl/step9_try.vhdl @@ -322,7 +322,8 @@ architecture test of step9_try is new_env(catch_env, env, vars, call_args); EVAL(ast.seq_val(2).seq_val(2), catch_env, result, err); else - new_nil(result); + err := sub_err; + return; end if; end if; return; diff --git a/vhdl/stepA_mal.vhdl b/vhdl/stepA_mal.vhdl index d67507bbfe..8ef8ecb87e 100644 --- a/vhdl/stepA_mal.vhdl +++ b/vhdl/stepA_mal.vhdl @@ -322,7 +322,8 @@ architecture test of stepA_mal is new_env(catch_env, env, vars, call_args); EVAL(ast.seq_val(2).seq_val(2), catch_env, result, err); else - new_nil(result); + err := sub_err; + return; end if; end if; return; From 625a68473723f6cfe2d300f9e1433a697a82735b Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Wed, 20 Feb 2019 07:10:44 +0000 Subject: [PATCH 0771/2308] tcl: Support catchless try* --- tcl/step9_try.tcl | 3 +++ tcl/stepA_mal.tcl | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tcl/step9_try.tcl b/tcl/step9_try.tcl index d6a97d312a..e190f8022f 100644 --- a/tcl/step9_try.tcl +++ b/tcl/step9_try.tcl @@ -143,6 +143,9 @@ proc EVAL {ast env} { return [macroexpand $a1 $env] } "try*" { + if {$a2 == ""} { + return [EVAL $a1 $env] + } set res {} if { [catch { set res [EVAL $a1 $env] } exception] } { set exc_var [obj_val [lindex [obj_val $a2] 1]] diff --git a/tcl/stepA_mal.tcl b/tcl/stepA_mal.tcl index 23dcdb07a4..e6ac6ac315 100644 --- a/tcl/stepA_mal.tcl +++ b/tcl/stepA_mal.tcl @@ -146,6 +146,9 @@ proc EVAL {ast env} { return [string_new [eval [obj_val $a1]]] } "try*" { + if {$a2 == ""} { + return [EVAL $a1 $env] + } set res {} if { [catch { set res [EVAL $a1 $env] } exception] } { set exc_var [obj_val [lindex [obj_val $a2] 1]] From 6d4201755acfafa88ef80260cf2fdba2ea4bf4fd Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Wed, 20 Feb 2019 07:14:23 +0000 Subject: [PATCH 0772/2308] skew: Support catchless try* --- skew/step9_try.sk | 3 +++ skew/stepA_mal.sk | 3 +++ 2 files changed, 6 insertions(+) diff --git a/skew/step9_try.sk b/skew/step9_try.sk index 179575e27c..e951631552 100644 --- a/skew/step9_try.sk +++ b/skew/step9_try.sk @@ -96,6 +96,9 @@ def EVAL(ast MalVal, env Env) MalVal { } else if a0sym.val == "macroexpand" { return macroexpand(astList[1], env) } else if a0sym.val == "try*" { + if astList.count < 3 { + return EVAL(astList[1], env) + } var exc MalVal try { return EVAL(astList[1], env) diff --git a/skew/stepA_mal.sk b/skew/stepA_mal.sk index 99d8a39e55..4bfa934e69 100644 --- a/skew/stepA_mal.sk +++ b/skew/stepA_mal.sk @@ -96,6 +96,9 @@ def EVAL(ast MalVal, env Env) MalVal { } else if a0sym.val == "macroexpand" { return macroexpand(astList[1], env) } else if a0sym.val == "try*" { + if astList.count < 3 { + return EVAL(astList[1], env) + } var exc MalVal try { return EVAL(astList[1], env) From a94e352cbb6d870226d439af25cd4df965173dc0 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Wed, 20 Feb 2019 07:21:48 +0000 Subject: [PATCH 0773/2308] fantom: Support catchless try* --- fantom/src/step9_try/fan/main.fan | 2 ++ fantom/src/stepA_mal/fan/main.fan | 2 ++ 2 files changed, 4 insertions(+) diff --git a/fantom/src/step9_try/fan/main.fan b/fantom/src/step9_try/fan/main.fan index 1cede16369..5a7332ccff 100644 --- a/fantom/src/step9_try/fan/main.fan +++ b/fantom/src/step9_try/fan/main.fan @@ -97,6 +97,8 @@ class Main case "macroexpand": return macroexpand(astList[1], env) case "try*": + if (astList.count < 3) + return EVAL(astList[1], env) MalVal exc := MalNil.INSTANCE try return EVAL(astList[1], env) diff --git a/fantom/src/stepA_mal/fan/main.fan b/fantom/src/stepA_mal/fan/main.fan index c42b659657..d1569b3cc5 100644 --- a/fantom/src/stepA_mal/fan/main.fan +++ b/fantom/src/stepA_mal/fan/main.fan @@ -97,6 +97,8 @@ class Main case "macroexpand": return macroexpand(astList[1], env) case "try*": + if (astList.count < 3) + return EVAL(astList[1], env) MalVal exc := MalNil.INSTANCE try return EVAL(astList[1], env) From 2b2ba8fc146ea7b03adf7da7faf39a954fe3977a Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Thu, 21 Feb 2019 08:15:18 +0000 Subject: [PATCH 0774/2308] d: Support catchless try* --- d/step9_try.d | 4 ++++ d/stepA_mal.d | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/d/step9_try.d b/d/step9_try.d index 9c378068e1..be04dab20d 100644 --- a/d/step9_try.d +++ b/d/step9_try.d @@ -173,6 +173,10 @@ MalType EVAL(MalType ast, Env env) return macroexpand(aste[1], env); case "try*": + if (aste.length < 3) + { + return EVAL(aste[1], env); + } MalType exc; try { diff --git a/d/stepA_mal.d b/d/stepA_mal.d index 1262d5fddd..234e1600bf 100644 --- a/d/stepA_mal.d +++ b/d/stepA_mal.d @@ -173,6 +173,10 @@ MalType EVAL(MalType ast, Env env) return macroexpand(aste[1], env); case "try*": + if (aste.length < 3) + { + return EVAL(aste[1], env); + } MalType exc; try { From e64dbbd4f485f0af17b0df645b6271c169599742 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Thu, 21 Feb 2019 09:14:56 +0000 Subject: [PATCH 0775/2308] logo: Support catchless try* --- logo/step9_try.lg | 8 ++++++-- logo/stepA_mal.lg | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/logo/step9_try.lg b/logo/step9_try.lg index 7639ff47c1..b5e0e7c410 100644 --- a/logo/step9_try.lg +++ b/logo/step9_try.lg @@ -104,10 +104,14 @@ forever [ output _macroexpand nth :ast 1 :env ] [[[symbol try*]] + localmake "a1 nth :ast 1 + if (_count :ast) < 3 [ + output _eval :a1 :env + ] localmake "result nil_new - catch "error [make "result _eval nth :ast 1 :env] + catch "error [make "result _eval :a1 :env] localmake "exception error - ifelse or emptyp :exception ((_count :ast) < 3) [ + ifelse emptyp :exception [ output :result ] [ localmake "e first butfirst :exception diff --git a/logo/stepA_mal.lg b/logo/stepA_mal.lg index 25db73000b..57f3acf925 100644 --- a/logo/stepA_mal.lg +++ b/logo/stepA_mal.lg @@ -104,10 +104,14 @@ forever [ output _macroexpand nth :ast 1 :env ] [[[symbol try*]] + localmake "a1 nth :ast 1 + if (_count :ast) < 3 [ + output _eval :a1 :env + ] localmake "result nil_new - catch "error [make "result _eval nth :ast 1 :env] + catch "error [make "result _eval :a1 :env] localmake "exception error - ifelse or emptyp :exception ((_count :ast) < 3) [ + ifelse emptyp :exception [ output :result ] [ localmake "e first butfirst :exception From 4afc9decd544fd5af56f936b63a6ddaba14ca5a2 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Thu, 21 Feb 2019 09:54:08 +0000 Subject: [PATCH 0776/2308] rexx: Support catchless try* --- rexx/step9_try.rexx | 1 + rexx/stepA_mal.rexx | 1 + 2 files changed, 2 insertions(+) diff --git a/rexx/step9_try.rexx b/rexx/step9_try.rexx index 36a1cc3c10..86a6d6e993 100644 --- a/rexx/step9_try.rexx +++ b/rexx/step9_try.rexx @@ -144,6 +144,7 @@ eval: procedure expose values. env. err /* eval(ast) */ when a0sym == "macroexpand" then return macroexpand(word(astval, 2), env_idx) when a0sym == "try*" then do res = eval(word(astval, 2), env_idx) + if words(astval) < 3 then return res if res == "ERR" then do if word(err, 1) == "__MAL_EXCEPTION__" then errobj = word(err, 2) diff --git a/rexx/stepA_mal.rexx b/rexx/stepA_mal.rexx index fb01015d8f..a9cdfe426a 100644 --- a/rexx/stepA_mal.rexx +++ b/rexx/stepA_mal.rexx @@ -144,6 +144,7 @@ eval: procedure expose values. env. err /* eval(ast) */ when a0sym == "macroexpand" then return macroexpand(word(astval, 2), env_idx) when a0sym == "try*" then do res = eval(word(astval, 2), env_idx) + if words(astval) < 3 then return res if res == "ERR" then do if word(err, 1) == "__MAL_EXCEPTION__" then errobj = word(err, 2) From c738a39f7ecd713f47a605ad31851e3fd44cf422 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Thu, 21 Feb 2019 11:06:20 +0000 Subject: [PATCH 0777/2308] awk: Support catchless try* --- awk/step9_try.awk | 12 +++++++++--- awk/stepA_mal.awk | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/awk/step9_try.awk b/awk/step9_try.awk index 23fee5221f..79e9ed1af2 100644 --- a/awk/step9_try.awk +++ b/awk/step9_try.awk @@ -257,11 +257,17 @@ function EVAL_defmacro(ast, env, idx, sym, ret, len) function EVAL_try(ast, env, catch_body, catch_env, idx, catch, catch_idx, catch_sym, ret, len, str) { idx = substr(ast, 2) - if (types_heap[idx]["len"] != 3) { - len = types_heap[idx]["len"] + len = types_heap[idx]["len"] + if (len != 2 && len != 3) { types_release(ast) env_release(env) - return "!\"Invalid argument length for 'try*'. Expects exactly 2 arguments, supplied" (len - 1) "." + return "!\"Invalid argument length for 'try*'. Expects 1 or 2 arguments, supplied" (len - 1) "." + } + if (len == 2) { + ret = EVAL(types_addref(types_heap[idx][1]), env) + types_release(ast) + env_release(env) + return ret } catch = types_heap[idx][2] if (catch !~ /^\(/) { diff --git a/awk/stepA_mal.awk b/awk/stepA_mal.awk index 06cebcef27..5661c53fef 100644 --- a/awk/stepA_mal.awk +++ b/awk/stepA_mal.awk @@ -257,11 +257,17 @@ function EVAL_defmacro(ast, env, idx, sym, ret, len) function EVAL_try(ast, env, catch_body, catch_env, idx, catch, catch_idx, catch_sym, ret, len, str) { idx = substr(ast, 2) - if (types_heap[idx]["len"] != 3) { - len = types_heap[idx]["len"] + len = types_heap[idx]["len"] + if (len != 2 && len != 3) { types_release(ast) env_release(env) - return "!\"Invalid argument length for 'try*'. Expects exactly 2 arguments, supplied" (len - 1) "." + return "!\"Invalid argument length for 'try*'. Expects 1 or 2 arguments, supplied" (len - 1) "." + } + if (len == 2) { + ret = EVAL(types_addref(types_heap[idx][1]), env) + types_release(ast) + env_release(env) + return ret } catch = types_heap[idx][2] if (catch !~ /^\(/) { From afc477fbb8774847baf6dc2e40dbe8b01e178dc4 Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Thu, 21 Feb 2019 11:24:13 +0000 Subject: [PATCH 0778/2308] forth: Support catchless try* --- forth/step9_try.fs | 30 +++++++++++++++++------------- forth/stepA_mal.fs | 30 +++++++++++++++++------------- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/forth/step9_try.fs b/forth/step9_try.fs index a5626da86a..982fea1f8f 100644 --- a/forth/step9_try.fs +++ b/forth/step9_try.fs @@ -245,20 +245,24 @@ defspecial macroexpand ( env list[_,form] -- form ) defspecial try* { env list -- val } list MalList/start @ cell+ { arg0 } - pre-try - env arg0 @ ['] eval catch ?dup 0= if - nip - else { errno } - begin pre-try = until - errno 1 <> if - s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc - to exception-object + list MalList/count @ 3 < if + env arg0 @ eval + else + pre-try + env arg0 @ ['] eval catch ?dup 0= if + nip + else { errno } + begin pre-try = until + errno 1 <> if + s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc + to exception-object + endif + arg0 cell+ @ ( list[catch*,sym,form] ) + MalList/start @ cell+ { catch0 } + env MalEnv. { catch-env } + catch0 @ exception-object catch-env env/set + catch-env catch0 cell+ @ TCO-eval endif - arg0 cell+ @ ( list[catch*,sym,form] ) - MalList/start @ cell+ { catch0 } - env MalEnv. { catch-env } - catch0 @ exception-object catch-env env/set - catch-env catch0 cell+ @ TCO-eval endif ;; MalSymbol diff --git a/forth/stepA_mal.fs b/forth/stepA_mal.fs index 38dcdb6bd8..619ad18595 100644 --- a/forth/stepA_mal.fs +++ b/forth/stepA_mal.fs @@ -245,20 +245,24 @@ defspecial macroexpand ( env list[_,form] -- form ) defspecial try* { env list -- val } list MalList/start @ cell+ { arg0 } - pre-try - env arg0 @ ['] eval catch ?dup 0= if - nip - else { errno } - begin pre-try = until - errno 1 <> if - s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc - to exception-object + list MalList/count @ 3 < if + env arg0 @ eval + else + pre-try + env arg0 @ ['] eval catch ?dup 0= if + nip + else { errno } + begin pre-try = until + errno 1 <> if + s" forth-errno" MalKeyword. errno MalInt. MalMap/Empty assoc + to exception-object + endif + arg0 cell+ @ ( list[catch*,sym,form] ) + MalList/start @ cell+ { catch0 } + env MalEnv. { catch-env } + catch0 @ exception-object catch-env env/set + catch-env catch0 cell+ @ TCO-eval endif - arg0 cell+ @ ( list[catch*,sym,form] ) - MalList/start @ cell+ { catch0 } - env MalEnv. { catch-env } - catch0 @ exception-object catch-env env/set - catch-env catch0 cell+ @ TCO-eval endif ;; defspecial . { env coll -- rtn-list } From c2b12e5bbdcfe2da27fa0ee332f4839d6d808c66 Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 25 Feb 2019 16:37:24 +0100 Subject: [PATCH 0779/2308] elisp: Avoid conflict by loading libs from mal/ --- elisp/Makefile | 4 ++-- elisp/{ => mal}/core.el | 2 +- elisp/{mal-env.el => mal/env.el} | 2 +- elisp/{ => mal}/func.el | 2 +- elisp/{ => mal}/printer.el | 2 +- elisp/{ => mal}/reader.el | 2 +- elisp/{ => mal}/types.el | 2 +- elisp/step1_read_print.el | 6 +++--- elisp/step2_eval.el | 6 +++--- elisp/step3_env.el | 8 ++++---- elisp/step4_if_fn_do.el | 10 +++++----- elisp/step5_tco.el | 12 ++++++------ elisp/step6_file.el | 12 ++++++------ elisp/step7_quote.el | 12 ++++++------ elisp/step8_macros.el | 12 ++++++------ elisp/step9_try.el | 12 ++++++------ elisp/stepA_mal.el | 12 ++++++------ 17 files changed, 59 insertions(+), 59 deletions(-) rename elisp/{ => mal}/core.el (99%) rename elisp/{mal-env.el => mal/env.el} (98%) rename elisp/{ => mal}/func.el (95%) rename elisp/{ => mal}/printer.el (98%) rename elisp/{ => mal}/reader.el (99%) rename elisp/{ => mal}/types.el (99%) diff --git a/elisp/Makefile b/elisp/Makefile index 21b02aaded..f554c38b3d 100644 --- a/elisp/Makefile +++ b/elisp/Makefile @@ -1,5 +1,5 @@ -SOURCES_BASE = reader.el printer.el types.el -SOURCES_LISP = env.el func.el core.el stepA_mal.el +SOURCES_BASE = mal/reader.el mal/printer.el mal/types.el +SOURCES_LISP = mal/env.el mal/func.el mal/core.el stepA_mal.el SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) all: diff --git a/elisp/core.el b/elisp/mal/core.el similarity index 99% rename from elisp/core.el rename to elisp/mal/core.el index 1d97f16396..76b6149ded 100644 --- a/elisp/core.el +++ b/elisp/mal/core.el @@ -259,4 +259,4 @@ (elisp-eval . ,(mal-fn (lambda (string) (elisp-to-mal (eval (read (mal-value string))))))) )) -(provide 'core) +(provide 'mal/core) diff --git a/elisp/mal-env.el b/elisp/mal/env.el similarity index 98% rename from elisp/mal-env.el rename to elisp/mal/env.el index e7eea800ab..f03902976c 100644 --- a/elisp/mal-env.el +++ b/elisp/mal/env.el @@ -31,4 +31,4 @@ (error "'%s' not found" key) value))) -(provide 'mal-env) +(provide 'mal/env) diff --git a/elisp/func.el b/elisp/mal/func.el similarity index 95% rename from elisp/func.el rename to elisp/mal/func.el index 55b33b6d9f..d4811bbdae 100644 --- a/elisp/func.el +++ b/elisp/mal/func.el @@ -16,4 +16,4 @@ (defun mal-func-macro-p (mal-func) (aref (aref mal-func 1) 4)) -(provide 'func) +(provide 'mal/func) diff --git a/elisp/printer.el b/elisp/mal/printer.el similarity index 98% rename from elisp/printer.el rename to elisp/mal/printer.el index 5c08a04ca5..ca864526d7 100644 --- a/elisp/printer.el +++ b/elisp/mal/printer.el @@ -55,4 +55,4 @@ (nreverse pairs) " "))) (concat "{" items "}")))) -(provide 'printer) +(provide 'mal/printer) diff --git a/elisp/reader.el b/elisp/mal/reader.el similarity index 99% rename from elisp/reader.el rename to elisp/mal/reader.el index 2461bdd59b..401b32bf90 100644 --- a/elisp/reader.el +++ b/elisp/mal/reader.el @@ -154,4 +154,4 @@ (mal-symbol (intern token)))) (signal 'end-of-token-stream nil)))) -(provide 'reader) +(provide 'mal/reader) diff --git a/elisp/types.el b/elisp/mal/types.el similarity index 99% rename from elisp/types.el rename to elisp/mal/types.el index e9d914cafc..4c88e5a12f 100644 --- a/elisp/types.el +++ b/elisp/mal/types.el @@ -101,4 +101,4 @@ Defaults to `error'." (define-error 'end-of-token-stream "End of token stream" 'mal) (define-error 'mal-custom "Custom error" 'mal) -(provide 'types) +(provide 'mal/types) diff --git a/elisp/step1_read_print.el b/elisp/step1_read_print.el index 1f56733837..0ba44e71b4 100644 --- a/elisp/step1_read_print.el +++ b/elisp/step1_read_print.el @@ -1,6 +1,6 @@ -(require 'types) -(require 'reader) -(require 'printer) +(require 'mal/types) +(require 'mal/reader) +(require 'mal/printer) (defun READ (input) (read-str input)) diff --git a/elisp/step2_eval.el b/elisp/step2_eval.el index 8ff9510374..68b66b539b 100644 --- a/elisp/step2_eval.el +++ b/elisp/step2_eval.el @@ -1,6 +1,6 @@ -(require 'types) -(require 'reader) -(require 'printer) +(require 'mal/types) +(require 'mal/reader) +(require 'mal/printer) (defvar repl-env (make-hash-table :test 'eq)) (puthash '+ (lambda (a b) (mal-number (+ (mal-value a) (mal-value b)))) repl-env) diff --git a/elisp/step3_env.el b/elisp/step3_env.el index 05dac6d416..9b2516a458 100644 --- a/elisp/step3_env.el +++ b/elisp/step3_env.el @@ -1,7 +1,7 @@ -(require 'types) -(require 'mal-env) -(require 'reader) -(require 'printer) +(require 'mal/types) +(require 'mal/env) +(require 'mal/reader) +(require 'mal/printer) (defvar repl-env (mal-env)) (mal-env-set repl-env '+ (lambda (a b) (mal-number (+ (mal-value a) (mal-value b))))) diff --git a/elisp/step4_if_fn_do.el b/elisp/step4_if_fn_do.el index 21df7b8394..0bf6a255cf 100644 --- a/elisp/step4_if_fn_do.el +++ b/elisp/step4_if_fn_do.el @@ -1,10 +1,10 @@ ;; -*- lexical-binding: t; -*- -(require 'types) -(require 'mal-env) -(require 'reader) -(require 'printer) -(require 'core) +(require 'mal/types) +(require 'mal/env) +(require 'mal/reader) +(require 'mal/printer) +(require 'mal/core) (defvar repl-env (mal-env)) diff --git a/elisp/step5_tco.el b/elisp/step5_tco.el index 31a9d0c2f0..e3410305ac 100644 --- a/elisp/step5_tco.el +++ b/elisp/step5_tco.el @@ -1,12 +1,12 @@ ;; -*- lexical-binding: t; -*- (setq debug-on-error t) -(require 'types) -(require 'func) -(require 'mal-env) -(require 'reader) -(require 'printer) -(require 'core) +(require 'mal/types) +(require 'mal/func) +(require 'mal/env) +(require 'mal/reader) +(require 'mal/printer) +(require 'mal/core) (defvar repl-env (mal-env)) diff --git a/elisp/step6_file.el b/elisp/step6_file.el index aef72f6c7a..5a5ebb2a1e 100644 --- a/elisp/step6_file.el +++ b/elisp/step6_file.el @@ -1,11 +1,11 @@ ;; -*- lexical-binding: t; -*- -(require 'types) -(require 'func) -(require 'mal-env) -(require 'reader) -(require 'printer) -(require 'core) +(require 'mal/types) +(require 'mal/func) +(require 'mal/env) +(require 'mal/reader) +(require 'mal/printer) +(require 'mal/core) (defvar repl-env (mal-env)) diff --git a/elisp/step7_quote.el b/elisp/step7_quote.el index e31aa34775..f5dfce60c5 100644 --- a/elisp/step7_quote.el +++ b/elisp/step7_quote.el @@ -1,11 +1,11 @@ ;; -*- lexical-binding: t; -*- -(require 'types) -(require 'func) -(require 'mal-env) -(require 'reader) -(require 'printer) -(require 'core) +(require 'mal/types) +(require 'mal/func) +(require 'mal/env) +(require 'mal/reader) +(require 'mal/printer) +(require 'mal/core) (defvar repl-env (mal-env)) diff --git a/elisp/step8_macros.el b/elisp/step8_macros.el index 1e0fa3833e..d9ed1da364 100644 --- a/elisp/step8_macros.el +++ b/elisp/step8_macros.el @@ -1,11 +1,11 @@ ;; -*- lexical-binding: t; -*- -(require 'types) -(require 'func) -(require 'mal-env) -(require 'reader) -(require 'printer) -(require 'core) +(require 'mal/types) +(require 'mal/func) +(require 'mal/env) +(require 'mal/reader) +(require 'mal/printer) +(require 'mal/core) (defvar repl-env (mal-env)) diff --git a/elisp/step9_try.el b/elisp/step9_try.el index d157892dc0..291651ba6e 100644 --- a/elisp/step9_try.el +++ b/elisp/step9_try.el @@ -1,11 +1,11 @@ ;; -*- lexical-binding: t; -*- -(require 'types) -(require 'func) -(require 'mal-env) -(require 'reader) -(require 'printer) -(require 'core) +(require 'mal/types) +(require 'mal/func) +(require 'mal/env) +(require 'mal/reader) +(require 'mal/printer) +(require 'mal/core) (defvar repl-env (mal-env)) diff --git a/elisp/stepA_mal.el b/elisp/stepA_mal.el index 10445bffd9..5aceea3ab9 100644 --- a/elisp/stepA_mal.el +++ b/elisp/stepA_mal.el @@ -1,11 +1,11 @@ ;; -*- lexical-binding: t; -*- -(require 'types) -(require 'func) -(require 'mal-env) -(require 'reader) -(require 'printer) -(require 'core) +(require 'mal/types) +(require 'mal/func) +(require 'mal/env) +(require 'mal/reader) +(require 'mal/printer) +(require 'mal/core) (defvar repl-env (mal-env)) From 5f15d3413557879ef80653ccdd208c76fd9108ba Mon Sep 17 00:00:00 2001 From: Vasilij Schneidermann Date: Mon, 25 Feb 2019 17:03:22 +0100 Subject: [PATCH 0780/2308] elisp: A list of cadr is its cdr --- elisp/step9_try.el | 2 +- elisp/stepA_mal.el | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/elisp/step9_try.el b/elisp/step9_try.el index 291651ba6e..7dc47a9676 100644 --- a/elisp/step9_try.el +++ b/elisp/step9_try.el @@ -123,7 +123,7 @@ (mal-string (error-message-string err)))) (env* (mal-env env (list identifier) (list err*)))) (throw 'return (EVAL form env*))) - (signal (car err) (list (cadr err))))))) + (signal (car err) (cdr err)))))) ((eq a0* 'do) (let* ((a0... (cdr a)) (butlast (butlast a0...)) diff --git a/elisp/stepA_mal.el b/elisp/stepA_mal.el index 5aceea3ab9..ee6a34b778 100644 --- a/elisp/stepA_mal.el +++ b/elisp/stepA_mal.el @@ -123,7 +123,7 @@ (mal-string (error-message-string err)))) (env* (mal-env env (list identifier) (list err*)))) (throw 'return (EVAL form env*))) - (signal (car err) (list (cadr err))))))) + (signal (car err) (cdr err)))))) ((eq a0* 'do) (let* ((a0... (cdr a)) (butlast (butlast a0...)) From 195977ceee0dd302f613444470ce61a0c60ff2ab Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 26 Feb 2019 17:38:05 -0600 Subject: [PATCH 0781/2308] Ignore step5 excludes during REGRESS too. --- Makefile | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 31da504b01..0cda4bf399 100644 --- a/Makefile +++ b/Makefile @@ -117,16 +117,16 @@ regress_step8 = $(regress_step7) step8 regress_step9 = $(regress_step8) step9 regress_stepA = $(regress_step9) stepA -test_EXCLUDES += test^bash^step5 # never completes at 10,000 -test_EXCLUDES += test^basic^step5 # too slow, and limited to ints of 2^16 -test_EXCLUDES += test^logo^step5 # too slow for 10,000 -test_EXCLUDES += test^make^step5 # no TCO capability (iteration or recursion) -test_EXCLUDES += test^mal^step5 # host impl dependent -test_EXCLUDES += test^matlab^step5 # never completes at 10,000 -test_EXCLUDES += test^plpgsql^step5 # too slow for 10,000 -test_EXCLUDES += test^plsql^step5 # too slow for 10,000 -test_EXCLUDES += test^powershell^step5 # too slow for 10,000 -test_EXCLUDES += $(if $(filter cpp,$(haxe_MODE)),test^haxe^step5,) # cpp finishes 10,000, segfaults at 100,000 +step5_EXCLUDES += bash # never completes at 10,000 +step5_EXCLUDES += basic # too slow, and limited to ints of 2^16 +step5_EXCLUDES += logo # too slow for 10,000 +step5_EXCLUDES += make # no TCO capability (iteration or recursion) +step5_EXCLUDES += mal # host impl dependent +step5_EXCLUDES += matlab # never completes at 10,000 +step5_EXCLUDES += plpgsql # too slow for 10,000 +step5_EXCLUDES += plsql # too slow for 10,000 +step5_EXCLUDES += powershell # too slow for 10,000 +step5_EXCLUDES += $(if $(filter cpp,$(haxe_MODE)),haxe,) # cpp finishes 10,000, segfaults at 100,000 dist_EXCLUDES += mal # TODO: still need to implement dist @@ -265,7 +265,10 @@ opt_OPTIONAL = $(if $(strip $(OPTIONAL)),$(if $(filter t true T True TRUE # test files will include step 2 tests through tests for the step # being tested. STEP_TEST_FILES = $(strip $(wildcard \ - $(foreach s,$(if $(strip $(REGRESS)),$(regress_$(2)),$(2)),\ + $(foreach s,$(if $(strip $(REGRESS)),\ + $(filter-out $(if $(filter $(1),$(step5_EXCLUDES)),step5,),\ + $(regress_$(2)))\ + ,$(2)),\ $(1)/tests/$($(s))$(EXTENSION) tests/$($(s))$(EXTENSION)))) # DOCKERIZE utility functions @@ -318,11 +321,11 @@ get_runtest_cmd = $(call get_run_prefix,$(1),$(2),$(if $(filter cs fsharp tcl vb get_argvtest_cmd = $(call get_run_prefix,$(1),$(2)) ../run_argv_test.sh # Derived lists -STEPS = $(sort $(filter step%,$(.VARIABLES))) +STEPS = $(sort $(filter-out %_EXCLUDES,$(filter step%,$(.VARIABLES)))) DO_IMPLS = $(filter-out $(SKIP_IMPLS),$(IMPLS)) IMPL_TESTS = $(foreach impl,$(DO_IMPLS),test^$(impl)) STEP_TESTS = $(foreach step,$(STEPS),test^$(step)) -ALL_TESTS = $(filter-out $(test_EXCLUDES),\ +ALL_TESTS = $(filter-out $(foreach e,$(step5_EXCLUDES),test^$(e)^step5),\ $(strip $(sort \ $(foreach impl,$(DO_IMPLS),\ $(foreach step,$(STEPS),test^$(impl)^$(step)))))) From 9e6b2a6d870a98ad74b09a1cbd3de4f3d306cf84 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 26 Feb 2019 17:41:50 -0600 Subject: [PATCH 0782/2308] basic: support catchless try* --- basic/step9_try.in.bas | 9 +++++---- basic/stepA_mal.in.bas | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/basic/step9_try.in.bas b/basic/step9_try.in.bas index 43ced642e2..26a18d771c 100755 --- a/basic/step9_try.in.bas +++ b/basic/step9_try.in.bas @@ -370,19 +370,20 @@ SUB EVAL EVAL_TRY: REM PRINT "try*" - GOSUB EVAL_GET_A1: REM set A1, A2, and A3 + GOSUB EVAL_GET_A1: REM set A1 GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL: REM eval A1 GOSUB POP_A: REM pop/restore A - REM if there is not error or catch block then return - IF ER=-2 OR Z%(A+1)=0 THEN GOTO EVAL_RETURN + GOSUB EVAL_GET_A2: REM set A1 and A2 + + REM if there is no error or catch block then return + IF ER=-2 OR A2=0 THEN GOTO EVAL_RETURN REM create environment for the catch block eval C=E:GOSUB ENV_NEW:E=R - GOSUB EVAL_GET_A2: REM set A1 and A2 A=A2:GOSUB EVAL_GET_A2: REM set A1 and A2 from catch block REM create object for ER=-1 type raw string errors diff --git a/basic/stepA_mal.in.bas b/basic/stepA_mal.in.bas index e4facc5a55..889a89b09c 100755 --- a/basic/stepA_mal.in.bas +++ b/basic/stepA_mal.in.bas @@ -367,19 +367,20 @@ SUB EVAL EVAL_TRY: REM PRINT "try*" - GOSUB EVAL_GET_A1: REM set A1, A2, and A3 + GOSUB EVAL_GET_A1: REM set A1 GOSUB PUSH_A: REM push/save A A=A1:CALL EVAL: REM eval A1 GOSUB POP_A: REM pop/restore A - REM if there is not error or catch block then return - IF ER=-2 OR Z%(A+1)=0 THEN GOTO EVAL_RETURN + GOSUB EVAL_GET_A2: REM set A1 and A2 + + REM if there is no error or catch block then return + IF ER=-2 OR A2=0 THEN GOTO EVAL_RETURN REM create environment for the catch block eval C=E:GOSUB ENV_NEW:E=R - GOSUB EVAL_GET_A2: REM set A1 and A2 A=A2:GOSUB EVAL_GET_A2: REM set A1 and A2 from catch block REM create object for ER=-1 type raw string errors From 757ebbfbbf93ba96aee66dd956159b8c8ff79831 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 27 Feb 2019 16:39:23 -0600 Subject: [PATCH 0783/2308] crystal, kotlin, mal, scala: Support catchless try* --- crystal/step9_try.cr | 2 +- crystal/stepA_mal.cr | 2 +- kotlin/src/mal/step9_try.kt | 25 +++++++++++++------------ kotlin/src/mal/stepA_mal.kt | 1 + mal/step9_try.mal | 7 ++++--- mal/stepA_mal.mal | 7 ++++--- scala/step9_try.scala | 1 + scala/stepA_mal.scala | 1 + 8 files changed, 26 insertions(+), 20 deletions(-) diff --git a/crystal/step9_try.cr b/crystal/step9_try.cr index 99314c030a..0de286ff81 100755 --- a/crystal/step9_try.cr +++ b/crystal/step9_try.cr @@ -207,7 +207,7 @@ module Mal when "macroexpand" macroexpand(list[1], env) when "try*" - catch_list = list[2].unwrap + catch_list = list.size >= 3 ? list[2].unwrap : Mal::Type.new(nil) return eval(list[1], env) unless catch_list.is_a? Mal::List catch_head = catch_list.first.unwrap diff --git a/crystal/stepA_mal.cr b/crystal/stepA_mal.cr index 03727aff19..bf1a201a0e 100755 --- a/crystal/stepA_mal.cr +++ b/crystal/stepA_mal.cr @@ -214,7 +214,7 @@ module Mal when "macroexpand" macroexpand(list[1], env) when "try*" - catch_list = list[2].unwrap + catch_list = list.size >= 3 ? list[2].unwrap : Mal::Type.new(nil) return eval(list[1], env) unless catch_list.is_a? Mal::List catch_head = catch_list.first.unwrap diff --git a/kotlin/src/mal/step9_try.kt b/kotlin/src/mal/step9_try.kt index ddead51e72..03d44f4e61 100644 --- a/kotlin/src/mal/step9_try.kt +++ b/kotlin/src/mal/step9_try.kt @@ -142,18 +142,19 @@ private fun defmacro(ast: MalList, env: Env): MalType { } private fun try_catch(ast: MalList, env: Env): MalType = - try { - eval(ast.nth(1), env) - } catch (e: Exception) { - val thrown = if (e is MalException) e else MalException(e.message) - val symbol = (ast.nth(2) as MalList).nth(1) as MalSymbol - - val catchBody = (ast.nth(2) as MalList).nth(2) - val catchEnv = Env(env) - catchEnv.set(symbol, thrown) - - eval(catchBody, catchEnv) - } + try { + eval(ast.nth(1), env) + } catch (e: Exception) { + if (ast.count() < 3) { throw e } + val thrown = if (e is MalException) e else MalException(e.message) + val symbol = (ast.nth(2) as MalList).nth(1) as MalSymbol + + val catchBody = (ast.nth(2) as MalList).nth(2) + val catchEnv = Env(env) + catchEnv.set(symbol, thrown) + + eval(catchBody, catchEnv) + } fun print(result: MalType) = pr_str(result, print_readably = true) diff --git a/kotlin/src/mal/stepA_mal.kt b/kotlin/src/mal/stepA_mal.kt index 062d2fa0b8..5bf73632db 100644 --- a/kotlin/src/mal/stepA_mal.kt +++ b/kotlin/src/mal/stepA_mal.kt @@ -145,6 +145,7 @@ private fun try_catch(ast: MalList, env: Env): MalType = try { eval(ast.nth(1), env) } catch (e: Exception) { + if (ast.count() < 3) { throw e } val thrown = if (e is MalException) e else MalException(e.message) val symbol = (ast.nth(2) as MalList).nth(1) as MalSymbol diff --git a/mal/step9_try.mal b/mal/step9_try.mal index 4e52a1b3c6..2975a5043e 100644 --- a/mal/step9_try.mal +++ b/mal/step9_try.mal @@ -110,15 +110,16 @@ (MACROEXPAND a1 env)) (= 'try* a0) - (if (= 'catch* (nth (nth ast 2) 0)) + (if (or (< (count ast) 3) + (not (= 'catch* (nth (nth ast 2) 0)))) + (EVAL (nth ast 1) env) (try* (EVAL (nth ast 1) env) (catch* exc (EVAL (nth (nth ast 2) 2) (new-env env [(nth (nth ast 2)1)] - [exc])))) - (EVAL (nth ast 1) env)) + [exc]))))) (= 'do a0) (let* [el (eval-ast (rest ast) env)] diff --git a/mal/stepA_mal.mal b/mal/stepA_mal.mal index d4e53de505..9f0af0e8ec 100644 --- a/mal/stepA_mal.mal +++ b/mal/stepA_mal.mal @@ -110,15 +110,16 @@ (MACROEXPAND a1 env)) (= 'try* a0) - (if (= 'catch* (nth (nth ast 2) 0)) + (if (or (< (count ast) 3) + (not (= 'catch* (nth (nth ast 2) 0)))) + (EVAL (nth ast 1) env) (try* (EVAL (nth ast 1) env) (catch* exc (EVAL (nth (nth ast 2) 2) (new-env env [(nth (nth ast 2)1)] - [exc])))) - (EVAL (nth ast 1) env)) + [exc]))))) (= 'do a0) (let* [el (eval-ast (rest ast) env)] diff --git a/scala/step9_try.scala b/scala/step9_try.scala index 068a43d65f..cc997bbe62 100644 --- a/scala/step9_try.scala +++ b/scala/step9_try.scala @@ -126,6 +126,7 @@ object step9_try { return EVAL(a1, env) } catch { case t: Throwable => { + if (rest.length == 0) throw t rest(0).asInstanceOf[MalList].value match { case List(Symbol("catch*"), a21, a22) => { val exc: Any = t match { diff --git a/scala/stepA_mal.scala b/scala/stepA_mal.scala index 91a6169c76..a3abbc3ac9 100644 --- a/scala/stepA_mal.scala +++ b/scala/stepA_mal.scala @@ -126,6 +126,7 @@ object stepA_mal { return EVAL(a1, env) } catch { case t: Throwable => { + if (rest.length == 0) throw t rest(0).asInstanceOf[MalList].value match { case List(Symbol("catch*"), a21, a22) => { val exc: Any = t match { From dc191336f8896205ed114c7dfcb31dd97addf2ad Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 27 Feb 2019 17:06:14 -0600 Subject: [PATCH 0784/2308] objpascal, r: Support catchless try*. --- objpascal/step9_try.pas | 2 ++ objpascal/stepA_mal.pas | 2 ++ r/step8_macros.r | 5 ++--- r/step9_try.r | 7 +++---- r/stepA_mal.r | 7 +++---- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/objpascal/step9_try.pas b/objpascal/step9_try.pas index 7df833d937..d8c5960f64 100644 --- a/objpascal/step9_try.pas +++ b/objpascal/step9_try.pas @@ -215,6 +215,8 @@ function EVAL(Ast: TMal; Env: TEnv) : TMal; except On E : Exception do begin + if Length(Arr) < 3 then + raise; SetLength(Err, 1); if E.ClassType = TMalException then Err[0] := (E as TMalException).Val diff --git a/objpascal/stepA_mal.pas b/objpascal/stepA_mal.pas index bf5afdfa41..1e8762e3a3 100644 --- a/objpascal/stepA_mal.pas +++ b/objpascal/stepA_mal.pas @@ -215,6 +215,8 @@ function EVAL(Ast: TMal; Env: TEnv) : TMal; except On E : Exception do begin + if Length(Arr) < 3 then + raise; SetLength(Err, 1); if E.ClassType = TMalException then Err[0] := (E as TMalException).Val diff --git a/r/step8_macros.r b/r/step8_macros.r index 012aa7c9bc..d8d3ba192f 100644 --- a/r/step8_macros.r +++ b/r/step8_macros.r @@ -75,9 +75,8 @@ EVAL <- function(ast, env) { repeat { #cat("EVAL: ", .pr_str(ast,TRUE), "\n", sep="") - if (!.list_q(ast)) { - return(eval_ast(ast, env)) - } + if (!.list_q(ast)) { return(eval_ast(ast, env)) } + if (length(ast) == 0) { return(ast) } # apply list ast <- macroexpand(ast, env) diff --git a/r/step9_try.r b/r/step9_try.r index 2575baac34..049d660542 100644 --- a/r/step9_try.r +++ b/r/step9_try.r @@ -75,9 +75,8 @@ EVAL <- function(ast, env) { repeat { #cat("EVAL: ", .pr_str(ast,TRUE), "\n", sep="") - if (!.list_q(ast)) { - return(eval_ast(ast, env)) - } + if (!.list_q(ast)) { return(eval_ast(ast, env)) } + if (length(ast) == 0) { return(ast) } # apply list ast <- macroexpand(ast, env) @@ -122,7 +121,7 @@ EVAL <- function(ast, env) { new.list(a2[[2]]), new.list(edata$exc)))) } else { - throw(err) + throw(edata$exc) } } else if (a0sym == "do") { eval_ast(slice(ast,2,length(ast)-1), env) diff --git a/r/stepA_mal.r b/r/stepA_mal.r index ecdb89cfbf..edf41de4ae 100644 --- a/r/stepA_mal.r +++ b/r/stepA_mal.r @@ -75,9 +75,8 @@ EVAL <- function(ast, env) { repeat { #cat("EVAL: ", .pr_str(ast,TRUE), "\n", sep="") - if (!.list_q(ast)) { - return(eval_ast(ast, env)) - } + if (!.list_q(ast)) { return(eval_ast(ast, env)) } + if (length(ast) == 0) { return(ast) } # apply list ast <- macroexpand(ast, env) @@ -122,7 +121,7 @@ EVAL <- function(ast, env) { new.list(a2[[2]]), new.list(edata$exc)))) } else { - throw(err) + throw(edata$exc) } } else if (a0sym == "do") { eval_ast(slice(ast,2,length(ast)-1), env) From 5d991812844506f3b1fb69822324ae286c8090a4 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 28 Feb 2019 17:36:45 -0600 Subject: [PATCH 0785/2308] cpp, racket: Support catchless try* --- cpp/step9_try.cpp | 7 ++++++- cpp/stepA_mal.cpp | 27 +++++++++++++++++---------- racket/step9_try.rkt | 7 ++++--- racket/stepA_mal.rkt | 7 ++++--- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/cpp/step9_try.cpp b/cpp/step9_try.cpp index 62933bdfc5..f3f2ddbac1 100644 --- a/cpp/step9_try.cpp +++ b/cpp/step9_try.cpp @@ -184,8 +184,13 @@ malValuePtr EVAL(malValuePtr ast, malEnvPtr env) } if (special == "try*") { - checkArgsIs("try*", 2, argCount); malValuePtr tryBody = list->item(1); + + if (argCount == 1) { + ast = EVAL(tryBody, env); + continue; // TCO + } + checkArgsIs("try*", 2, argCount); const malList* catchBlock = VALUE_CAST(malList, list->item(2)); checkArgsIs("catch*", 2, catchBlock->count() - 1); diff --git a/cpp/stepA_mal.cpp b/cpp/stepA_mal.cpp index cda56f08cb..87cd3ac5fe 100644 --- a/cpp/stepA_mal.cpp +++ b/cpp/stepA_mal.cpp @@ -185,8 +185,13 @@ malValuePtr EVAL(malValuePtr ast, malEnvPtr env) } if (special == "try*") { - checkArgsIs("try*", 2, argCount); malValuePtr tryBody = list->item(1); + + if (argCount == 1) { + ast = EVAL(tryBody, env); + continue; // TCO + } + checkArgsIs("try*", 2, argCount); const malList* catchBlock = VALUE_CAST(malList, list->item(2)); checkArgsIs("catch*", 2, catchBlock->count() - 1); @@ -336,15 +341,6 @@ static void installMacros(malEnvPtr env) } } -malValuePtr readline(const String& prompt) -{ - String input; - if (s_readLine.get(prompt, input)) { - return mal::string(input); - } - return mal::nilValue(); -} - static const char* malFunctionTable[] = { "(def! list (fn* (& items) items))", "(def! not (fn* (cond) (if cond false true)))", @@ -365,3 +361,14 @@ static void installFunctions(malEnvPtr env) { rep(function, env); } } + +// Added to keep the linker happy at step A +malValuePtr readline(const String& prompt) +{ + String input; + if (s_readLine.get(prompt, input)) { + return mal::string(input); + } + return mal::nilValue(); +} + diff --git a/racket/step9_try.rkt b/racket/step9_try.rkt index 9cecd2af6f..79f21b63b4 100755 --- a/racket/step9_try.rkt +++ b/racket/step9_try.rkt @@ -79,7 +79,9 @@ [(eq? 'macroexpand a0) (macroexpand (_nth ast 1) env)] [(eq? 'try* a0) - (if (eq? 'catch* (_nth (_nth ast 2) 0)) + (if (or (< (length ast) 3) + (not (eq? 'catch* (_nth (_nth ast 2) 0)))) + (EVAL (_nth ast 1) env) (let ([efn (lambda (exc) (EVAL (_nth (_nth ast 2) 2) (new Env% @@ -90,8 +92,7 @@ ([mal-exn? (lambda (exc) (efn (mal-exn-val exc)))] [string? (lambda (exc) (efn exc))] [exn:fail? (lambda (exc) (efn (format "~a" exc)))]) - (EVAL (_nth ast 1) env))) - (EVAL (_nth ast 1)))] + (EVAL (_nth ast 1) env))))] [(eq? 'do a0) (eval-ast (drop (drop-right ast 1) 1) env) (EVAL (last ast) env)] diff --git a/racket/stepA_mal.rkt b/racket/stepA_mal.rkt index 813d0eb708..875ef3b45a 100755 --- a/racket/stepA_mal.rkt +++ b/racket/stepA_mal.rkt @@ -79,7 +79,9 @@ [(eq? 'macroexpand a0) (macroexpand (_nth ast 1) env)] [(eq? 'try* a0) - (if (eq? 'catch* (_nth (_nth ast 2) 0)) + (if (or (< (length ast) 3) + (not (eq? 'catch* (_nth (_nth ast 2) 0)))) + (EVAL (_nth ast 1) env) (let ([efn (lambda (exc) (EVAL (_nth (_nth ast 2) 2) (new Env% @@ -90,8 +92,7 @@ ([mal-exn? (lambda (exc) (efn (mal-exn-val exc)))] [string? (lambda (exc) (efn exc))] [exn:fail? (lambda (exc) (efn (format "~a" exc)))]) - (EVAL (_nth ast 1) env))) - (EVAL (_nth ast 1)))] + (EVAL (_nth ast 1) env))))] [(eq? 'do a0) (eval-ast (drop (drop-right ast 1) 1) env) (EVAL (last ast) env)] From bb1e6df66300c60075fcc6b03c8359ed865ba23d Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 28 Feb 2019 17:36:54 -0600 Subject: [PATCH 0786/2308] elixir, elm, erlang: Support catchless try*. --- elixir/lib/mix/tasks/step9_try.ex | 3 +++ elixir/lib/mix/tasks/stepA_mal.ex | 3 +++ elm/step9_try.elm | 2 ++ elm/stepA_mal.elm | 2 ++ erlang/src/step9_try.erl | 2 ++ erlang/src/stepA_mal.erl | 2 ++ 6 files changed, 14 insertions(+) diff --git a/elixir/lib/mix/tasks/step9_try.ex b/elixir/lib/mix/tasks/step9_try.ex index 036952a647..b1b94c8ef2 100644 --- a/elixir/lib/mix/tasks/step9_try.ex +++ b/elixir/lib/mix/tasks/step9_try.ex @@ -232,6 +232,9 @@ defmodule Mix.Tasks.Step9Try do defp eval_list([{:symbol, "try*"}, try_form, {:list, catch_list, _meta}], env, _) do eval_try(try_form, catch_list, env) end + defp eval_list([{:symbol, "try*"}, try_form], env, _) do + eval(try_form, env) + end defp eval_list([{:symbol, "try*"}, _try_form, _], _env, _) do throw({:error, "try* requires a list as the second parameter"}) end diff --git a/elixir/lib/mix/tasks/stepA_mal.ex b/elixir/lib/mix/tasks/stepA_mal.ex index af7586eb13..c827b06040 100644 --- a/elixir/lib/mix/tasks/stepA_mal.ex +++ b/elixir/lib/mix/tasks/stepA_mal.ex @@ -251,6 +251,9 @@ defmodule Mix.Tasks.StepAMal do defp eval_list([{:symbol, "try*"}, try_form, {:list, catch_list, _meta}], env, _) do eval_try(try_form, catch_list, env) end + defp eval_list([{:symbol, "try*"}, try_form], env, _) do + eval(try_form, env) + end defp eval_list([{:symbol, "try*"}, _try_form, _], _env, _) do throw({:error, "try* requires a list as the second parameter"}) end diff --git a/elm/step9_try.elm b/elm/step9_try.elm index 90e04418ad..44b3180c1d 100644 --- a/elm/step9_try.elm +++ b/elm/step9_try.elm @@ -700,6 +700,8 @@ macroexpand expr = evalTry : List MalExpr -> Eval MalExpr evalTry args = case args of + [ body ] -> + eval body [ body, MalList [ MalSymbol "catch*", MalSymbol sym, handler ] ] -> eval body |> Eval.catchError diff --git a/elm/stepA_mal.elm b/elm/stepA_mal.elm index 5b28c60afd..83aed32135 100644 --- a/elm/stepA_mal.elm +++ b/elm/stepA_mal.elm @@ -716,6 +716,8 @@ macroexpand expr = evalTry : List MalExpr -> Eval MalExpr evalTry args = case args of + [ body ] -> + eval body [ body, MalList [ MalSymbol "catch*", MalSymbol sym, handler ] ] -> eval body |> Eval.catchError diff --git a/erlang/src/step9_try.erl b/erlang/src/step9_try.erl index 9ca6303cd1..c35da92f47 100644 --- a/erlang/src/step9_try.erl +++ b/erlang/src/step9_try.erl @@ -142,6 +142,8 @@ eval_list({list, [{symbol, "try*"}, A, {list, [{symbol, "catch*"}, B, C], _M1}], env:bind(NewEnv, [B], [Reason]), eval(C, NewEnv) end; +eval_list({list, [{symbol, "try*"}, AST], _Meta}, Env) -> + eval(AST, Env); eval_list({list, [{symbol, "try*"}|_], _Meta}, _Env) -> error("try*/catch* must be of the form (try* A (catch* B C))"); eval_list({list, List, Meta}, Env) -> diff --git a/erlang/src/stepA_mal.erl b/erlang/src/stepA_mal.erl index f848b91503..b60fdf7fcd 100644 --- a/erlang/src/stepA_mal.erl +++ b/erlang/src/stepA_mal.erl @@ -146,6 +146,8 @@ eval_list({list, [{symbol, "try*"}, A, {list, [{symbol, "catch*"}, B, C], _M1}], env:bind(NewEnv, [B], [Reason]), eval(C, NewEnv) end; +eval_list({list, [{symbol, "try*"}, AST], _Meta}, Env) -> + eval(AST, Env); eval_list({list, [{symbol, "try*"}|_], _Meta}, _Env) -> error("try*/catch* must be of the form (try* A (catch* B C))"); eval_list({list, List, Meta}, Env) -> From e005219d76c2ee928caf47328a97947e96293660 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 2 Mar 2019 18:52:03 +0000 Subject: [PATCH 0787/2308] Add catchless try to step 9 and step A (try* A) evaluates A without setting an error handler. --- nasm/step9_try.asm | 19 ++++++++++++++++++- nasm/stepA_mal.asm | 19 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/nasm/step9_try.asm b/nasm/step9_try.asm index a9657287f1..ea5209a7b0 100644 --- a/nasm/step9_try.asm +++ b/nasm/step9_try.asm @@ -1524,6 +1524,10 @@ eval: ; Check second arg B mov al, BYTE [rsi + Cons.typecdr] + ; If nil (catchless try) + cmp al, content_nil + je .catchless_try + cmp al, content_pointer jne .try_missing_catch @@ -1575,7 +1579,7 @@ eval: ; Now have extracted from (try* A (catch* B C)) ; A in R8 ; B in R10 - ; C in T9 + ; C in R9 push R9 push R10 @@ -1610,7 +1614,20 @@ eval: call error_handler_pop mov rax, r8 jmp .return + +.catchless_try: + ; Evaluate the form in R8 + mov rsi, r15 + call incref_object ; Env released by eval + mov rdi, r15 ; Env in RDI + + mov rsi, r8 ; The form to evaluate (A) + + call incref_object ; AST released by eval + call eval ; Result in RAX + + jmp .return .catch: ; Jumps here on error ; Value thrown in RSI diff --git a/nasm/stepA_mal.asm b/nasm/stepA_mal.asm index 1a5014f4d1..a326dbeaf0 100644 --- a/nasm/stepA_mal.asm +++ b/nasm/stepA_mal.asm @@ -1539,6 +1539,10 @@ eval: ; Check second arg B mov al, BYTE [rsi + Cons.typecdr] + ; If nil (catchless try) + cmp al, content_nil + je .catchless_try + cmp al, content_pointer jne .try_missing_catch @@ -1590,7 +1594,7 @@ eval: ; Now have extracted from (try* A (catch* B C)) ; A in R8 ; B in R10 - ; C in T9 + ; C in R9 push R9 push R10 @@ -1625,7 +1629,20 @@ eval: call error_handler_pop mov rax, r8 jmp .return + +.catchless_try: + ; Evaluate the form in R8 + mov rsi, r15 + call incref_object ; Env released by eval + mov rdi, r15 ; Env in RDI + + mov rsi, r8 ; The form to evaluate (A) + + call incref_object ; AST released by eval + call eval ; Result in RAX + + jmp .return .catch: ; Jumps here on error ; Value thrown in RSI From 022e8339878612ce248a92b2fe1d14cd658323d0 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sat, 2 Mar 2019 21:20:50 +0000 Subject: [PATCH 0788/2308] Push and pop environment in catchless try Need to remember to save and restore R15 with the environment pointer. --- nasm/step9_try.asm | 6 +++++- nasm/stepA_mal.asm | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/nasm/step9_try.asm b/nasm/step9_try.asm index ea5209a7b0..40645702e9 100644 --- a/nasm/step9_try.asm +++ b/nasm/step9_try.asm @@ -1616,7 +1616,9 @@ eval: jmp .return .catchless_try: - ; Evaluate the form in R8 + ;; Evaluate the form in R8 + push r15 ; Environment + mov rsi, r15 call incref_object ; Env released by eval mov rdi, r15 ; Env in RDI @@ -1626,6 +1628,8 @@ eval: call incref_object ; AST released by eval call eval ; Result in RAX + + pop r15 ; Environment jmp .return .catch: diff --git a/nasm/stepA_mal.asm b/nasm/stepA_mal.asm index a326dbeaf0..0bf675b8c6 100644 --- a/nasm/stepA_mal.asm +++ b/nasm/stepA_mal.asm @@ -1631,7 +1631,9 @@ eval: jmp .return .catchless_try: - ; Evaluate the form in R8 + ;; Evaluate the form in R8 + push r15 ; Environment + mov rsi, r15 call incref_object ; Env released by eval mov rdi, r15 ; Env in RDI @@ -1641,6 +1643,8 @@ eval: call incref_object ; AST released by eval call eval ; Result in RAX + + pop r15 ; Environment jmp .return .catch: From bdda2112954715824e26b877da528b2e6b6e6713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=86=E9=81=A5?= Date: Tue, 5 Mar 2019 16:40:22 +0800 Subject: [PATCH 0789/2308] add swift4 impliment. --- swift4/Dockerfile | 44 ++++ swift4/Makefile | 40 ++++ swift4/Sources/core.swift | 216 ++++++++++++++++++++ swift4/Sources/env.swift | 50 +++++ swift4/Sources/printer.swift | 58 ++++++ swift4/Sources/reader.swift | 154 ++++++++++++++ swift4/Sources/step0_repl/main.swift | 32 +++ swift4/Sources/step1_read_print/main.swift | 38 ++++ swift4/Sources/step2_eval/main.swift | 85 ++++++++ swift4/Sources/step3_env/main.swift | 101 ++++++++++ swift4/Sources/step4_if_fn_do/main.swift | 115 +++++++++++ swift4/Sources/step5_tco/main.swift | 145 ++++++++++++++ swift4/Sources/step6_file/main.swift | 143 +++++++++++++ swift4/Sources/step7_quote/main.swift | 165 +++++++++++++++ swift4/Sources/step8_macros/main.swift | 196 ++++++++++++++++++ swift4/Sources/step9_try/main.swift | 215 ++++++++++++++++++++ swift4/Sources/stepA_mal/main.swift | 221 +++++++++++++++++++++ swift4/Sources/types.swift | 166 ++++++++++++++++ swift4/run | 2 + 19 files changed, 2186 insertions(+) create mode 100644 swift4/Dockerfile create mode 100644 swift4/Makefile create mode 100644 swift4/Sources/core.swift create mode 100644 swift4/Sources/env.swift create mode 100644 swift4/Sources/printer.swift create mode 100644 swift4/Sources/reader.swift create mode 100644 swift4/Sources/step0_repl/main.swift create mode 100644 swift4/Sources/step1_read_print/main.swift create mode 100644 swift4/Sources/step2_eval/main.swift create mode 100644 swift4/Sources/step3_env/main.swift create mode 100644 swift4/Sources/step4_if_fn_do/main.swift create mode 100644 swift4/Sources/step5_tco/main.swift create mode 100644 swift4/Sources/step6_file/main.swift create mode 100644 swift4/Sources/step7_quote/main.swift create mode 100644 swift4/Sources/step8_macros/main.swift create mode 100644 swift4/Sources/step9_try/main.swift create mode 100644 swift4/Sources/stepA_mal/main.swift create mode 100644 swift4/Sources/types.swift create mode 100755 swift4/run diff --git a/swift4/Dockerfile b/swift4/Dockerfile new file mode 100644 index 0000000000..744f62b17c --- /dev/null +++ b/swift4/Dockerfile @@ -0,0 +1,44 @@ +FROM ubuntu:xenial +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# Swift +RUN apt-get -y install clang-3.6 cmake pkg-config \ + git ninja-build uuid-dev libicu-dev icu-devtools \ + libbsd-dev libedit-dev libxml2-dev libsqlite3-dev \ + swig libpython-dev libncurses5-dev + +# TODO: better way to do this? +RUN ln -sf /usr/lib/llvm-3.6/bin/clang++ /usr/bin/clang++ +RUN ln -sf /usr/lib/llvm-3.6/bin/clang /usr/bin/clang + +ENV SWIFT_PREFIX swift-4.2.3-RELEASE +ENV SWIFT_RELEASE ${SWIFT_PREFIX}-ubuntu16.04 + +RUN cd /opt && \ + curl -O https://swift.org/builds/swift-4.2.3-release/ubuntu1604/${SWIFT_PREFIX}/${SWIFT_RELEASE}.tar.gz && \ + tar xvzf ${SWIFT_RELEASE}.tar.gz && \ + rm ${SWIFT_RELEASE}.tar.gz + +ENV PATH /opt/${SWIFT_RELEASE}/usr/bin/:$PATH + + diff --git a/swift4/Makefile b/swift4/Makefile new file mode 100644 index 0000000000..1a16beae7c --- /dev/null +++ b/swift4/Makefile @@ -0,0 +1,40 @@ +ifneq ($(shell which xcrun),) + SWIFT = xcrun -sdk macosx swiftc +else + SWIFT = swiftc +endif + +STEP3_DEPS = Sources/types.swift Sources/reader.swift Sources/printer.swift Sources/env.swift +STEP4_DEPS = $(STEP3_DEPS) Sources/core.swift + +SOURCES = $(STEP4_DEPS) Sources/stepA_mal/main.swift +SOURCES_LISP = Sources/env.swift Sources/core.swift Sources/stepA_mal/main.swift + +STEPS = step0_repl step1_read_print step2_eval step3_env \ + step4_if_fn_do step5_tco step6_file step7_quote \ + step8_macros step9_try stepA_mal + +all: $(STEPS) + +dist: mal + +mal: stepA_mal + cp $< $@ + +step1_read_print step2_eval step3_env: $(STEP3_DEPS) +step4_if_fn_do step5_tco step6_file step7_quote step8_macros step9_try stepA_mal: $(STEP4_DEPS) + +step%: Sources/step%/main.swift + $(SWIFT) $+ -o $@ + +clean: + rm -f $(STEPS) mal + +.PHONY: stats tests + +stats: $(SOURCES) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +stats-lisp: $(SOURCES_LISP) + @wc $^ + @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/swift4/Sources/core.swift b/swift4/Sources/core.swift new file mode 100644 index 0000000000..cc9b2a9c4d --- /dev/null +++ b/swift4/Sources/core.swift @@ -0,0 +1,216 @@ +// +// core.swift +// swift4 +// +// Created by LuLouie on 2019/1/10. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +func calculate(_ args: [MalData], op: (Number, Number) -> Number) throws -> MalData { + guard args.count == 2, args[0] is Number, args[1] is Number else { + throw MalError.InvalidArgument + } + return op(args[0] as! Number, args[1] as! Number) +} + +func isEqualList(_ l: [MalData], _ r: [MalData]) -> Bool { + guard l.count == r.count else { + return false + } + for i in l.indices { + if !isEqual(l[i], r[i]) { return false } + } + return true +} + +func isEqualHashMap (_ l: [String: MalData], _ r: [String: MalData]) -> Bool { + guard l.count == r.count else { + return false + } + for key in l.keys { + guard let lValue = l[key], let rValue = r[key] else { return false } + if !isEqual(lValue, rValue) { return false } + } + return true +} + +func isEqual(_ l: MalData, _ r: MalData) -> Bool { + switch (l.dataType, r.dataType) { + case (.Symbol, .Symbol): + return (l as! Symbol).name == (r as! Symbol).name + case (.String, .String), (.Keyword, .Keyword): + return (l as! String) == (r as! String) + case (.Number, .Number): + return (l as! Number) == (r as! Number) + case (.List, .List), (.Vector, .Vector), (.List, .Vector), (. Vector, .List): + return isEqualList(l.listForm, r.listForm) + case (.HashMap, .HashMap): + return isEqualHashMap((l as! [String: MalData]), (r as! [String: MalData])) + case (.Nil, .Nil), (.True, .True), (.False, .False): + return true + default: // atom, function + return false + } +} + +func hashMap(fromList list: [MalData]) throws -> [String: MalData] { + var hashMap: [String: MalData] = [:] + for index in stride(from: 0, to: list.count, by: 2) { + guard list[index] is String, index+1 < list.count else { throw MalError.Error } + hashMap.updateValue(list[index+1], forKey: list[index] as! String) + } + return hashMap +} + +let ns: [String: ([MalData]) throws -> MalData] = + ["+": { try calculate($0, op: +) }, + "-": { try calculate($0, op: -) }, + "*": { try calculate($0, op: *) }, + "/": { try calculate($0, op: /) }, + "<": { args in (args[0] as! Number) < (args[1] as! Number) }, + ">": { args in (args[0] as! Number) > (args[1] as! Number) }, + "<=": { args in (args[0] as! Number) <= (args[1] as! Number) }, + ">=": { args in (args[0] as! Number) >= (args[1] as! Number) }, + + "=": { args in let left = args[0], right = args[1]; return isEqual(left, right) }, + + "pr-str": { $0.map { pr_str($0, print_readably: true)}.joined(separator: " ") }, + "str": { $0.map { pr_str($0, print_readably: false)}.joined(separator: "") }, + "prn": { print($0.map { pr_str($0, print_readably: true)}.joined(separator: " ")); return Nil() }, + "println": { print($0.map { pr_str($0, print_readably: false)}.joined(separator: " ")); return Nil() }, + + "list": { List($0) }, + "list?": { let param = $0[0]; return param is [MalData] }, + "empty?": { $0[0].count == 0 }, + "count": { $0[0].count }, + + "read-string": { try read_str($0[0] as! String) }, + "slurp": { try String(contentsOfFile: $0[0] as! String) }, + + "atom": { Atom($0[0]) }, + "atom?": { $0[0] is Atom }, + "deref": { ($0[0] as? Atom)?.value ?? Nil() }, + "reset!": { args in (args[0] as! Atom).value = args[1]; return args[1] }, + "swap!": { args in + let atom = args[0] as! Atom, fn = args[1] as! Function, + others = args.dropFirst(2).listForm + atom.value = try fn.fn([atom.value] + others) + return atom.value + }, + "cons": { args in [args[0]] + args[1].listForm }, + "concat": { $0.reduce([]) { (result, array ) in result + array.listForm } }, + + "nth": { args in + let list = args[0].listForm, i = args[1] as! Int + guard list.indices.contains(i) else { throw MalError.IndexOutOfBounds } + return list[i] + }, + "first": { $0[0].listForm.first ?? Nil() }, + "rest": { $0[0].listForm.dropFirst().listForm }, + + "throw": { throw MalError.MalException($0[0]) }, + "apply": { args in + let fn = args[0] as! Function + let newArgs = args.dropFirst().dropLast().listForm + args.last!.listForm + return try fn.fn(newArgs) + }, + "map": { args in + let fn = args[0] as! Function + let closure = fn.fn + var result: [MalData] = [] + for element in args[1].listForm { + result.append(try fn.fn([element])) } + return result + }, + + "nil?": { $0[0] is Nil }, + "true?": { $0[0].dataType == .True }, + "false?": { $0[0].dataType == .False }, + "symbol?": { $0[0].dataType == .Symbol }, + "symbol": { Symbol($0[0] as! String) }, + "keyword": { ($0[0].dataType == .Keyword) ? $0[0] : "\u{029E}" + ($0[0] as! String) }, + "keyword?":{ $0[0].dataType == .Keyword }, + "vector": { Vector($0) }, + "vector?": { $0[0].dataType == .Vector }, + "hash-map":{ try hashMap(fromList: $0) }, + "map?": { $0[0].dataType == .HashMap }, + "assoc": { + let map = $0[0] as! [String: MalData] + return map.merging(try hashMap(fromList: $0.dropFirst().listForm)) { (_, new) in new } + }, + "dissoc": { args in + let map = args[0] as! [String: MalData] + return map.filter { (key, _) in !(args.dropFirst().listForm as! [String]).contains(key) } + }, + "get": { + if let map = $0[0] as? [String: MalData] { + return map[$0[1] as! String] ?? Nil() } + return Nil() + }, + "contains?": { ($0[0] as! [String: MalData])[$0[1] as! String] != nil }, + "keys": { + ($0[0] as! [String: MalData]).reduce([]) { result, element in + let (key, _) = element + return result + [key] } + }, + "vals": { + ($0[0] as! [String: MalData]).reduce([]) { result, element in + let (_, value) = element + return result + [value] } + }, + "sequential?": { [.List, .Vector].contains($0[0].dataType) }, + + "readline": { + print($0[0] as! String, terminator: "") + return readLine(strippingNewline: true) ?? Nil() }, + + "meta": { + switch $0[0].dataType { + case .Function: + return ($0[0] as! Function).meta ?? Nil() + default: + return Nil() + }}, + "with-meta": { + switch $0[0].dataType { + case .Function: + return Function(withFunction: $0[0] as! Function, meta: $0[1]) + default: + return $0[0] + }}, + "time-ms": { _ in Int(Date().timeIntervalSince1970 * 1000) }, + "conj": { + if let list = $0[0] as? [MalData] { + return $0.dropFirst().reversed().listForm + list + } else { // vector + return ($0[0] as! Vector) + Vector($0.dropFirst()) + }}, + "string?": { $0[0].dataType == .String }, + "number?": { $0[0].dataType == .Number }, + "fn?": { + if let fn = $0[0] as? Function { + return !fn.isMacro + } else { + return false + }}, + "macro?": { + if let fn = $0[0] as? Function { + return fn.isMacro + } else { + return false + }}, + "seq": { + if $0[0].count == 0 { return Nil() } + switch $0[0].dataType { + case .List: + return $0[0] as! List + case .Vector: + return List($0[0] as! ContiguousArray) + case .String: + return List($0[0] as! String).map { String($0) } + default: + return Nil() + }}, +] diff --git a/swift4/Sources/env.swift b/swift4/Sources/env.swift new file mode 100644 index 0000000000..6b43500508 --- /dev/null +++ b/swift4/Sources/env.swift @@ -0,0 +1,50 @@ +// +// env.swift +// swift4 +// +// Created by LuLouie on 2019/1/7. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +class Env { + let outer: Env? + var data: [String: MalData] = [:] + + init(outer: Env) { + self.outer = outer + } + init() { + outer = nil + } + init(binds: [Symbol], exprs: [MalData], outer: Env) { + self.outer = outer + self.data = [:] + for i in binds.indices { + if binds[i].name == "&" { + data.updateValue(List(exprs[i.. Env? { + if let _ = data[key.name] { + return self + } else { + return outer?.find(key) + } + } + func get(forKey key: Symbol) throws -> MalData { + if let env = find(key), let value = env.data[key.name] { + return value + } else { + throw MalError.SymbolNotFound(key) + } + } +} diff --git a/swift4/Sources/printer.swift b/swift4/Sources/printer.swift new file mode 100644 index 0000000000..c55d4f7c68 --- /dev/null +++ b/swift4/Sources/printer.swift @@ -0,0 +1,58 @@ +// +// printer.swift +// swift4 +// +// Created by LuLouie on 2019/1/7. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +func pr_str(_ input: MalData, print_readably: Bool) -> String { + switch input.dataType { + case .Symbol: + let symbol = input as! Symbol + return symbol.name + case .Number: + let number = input as! Number + return String(number) + case .True: + return "true" + case .False: + return "false" + case .Nil: + return "nil" + case .Keyword: + let keyword = input as! String + return keyword.replacingCharacters(in: keyword.startIndex...keyword.startIndex, with: ":") + case .String: + let string = input as! String + if print_readably { + return "\"" + string.replacingOccurrences(of: "\\", with: "\\\\") + .replacingOccurrences(of: "\"", with: "\\\"") + .replacingOccurrences(of: "\n", with: "\\n") + "\"" + } else { + return string + } + case .List: + let list = input as! List + let stringOfElements = list.map { pr_str($0, print_readably: print_readably) }.joined(separator: " ") + return "(" + stringOfElements + ")" + case .Vector: + let vector = input as! Vector + let stringOfElements = vector.map { pr_str($0, print_readably: print_readably) }.joined(separator: " ") + return "[" + stringOfElements + "]" + case .HashMap: + let hashMap = input as! [String: MalData] + let stringOfElements = hashMap.map { (key, value) in + pr_str(key, print_readably: print_readably) + " " + pr_str(value, print_readably: print_readably) + }.joined(separator: " ") + return "{" + stringOfElements + "}" + case .Atom: + return pr_str("(atom \((input as! Atom).value))", print_readably: false) + case .Function: + return "#" + default: + return "error type!" + } +} diff --git a/swift4/Sources/reader.swift b/swift4/Sources/reader.swift new file mode 100644 index 0000000000..08c0e2ba55 --- /dev/null +++ b/swift4/Sources/reader.swift @@ -0,0 +1,154 @@ +// +// reader.swift +// swift4 +// +// Created by LuLouie on 2019/1/6. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +struct Reader { + let tokens: [String] + var position = 0 + + init(tokens: [String]) { + self.tokens = tokens + } + + mutating func next() -> String? { + guard tokens.indices.contains(position) else { + return nil + } + position += 1 + return tokens[position - 1] + } + + func peak() -> String? { + guard tokens.indices.contains(position) else { + return nil + } + return tokens[position] + } + + mutating func pass() { + guard tokens.indices.contains(position) else { + return + } + position += 1 + } + + mutating func read_form() throws -> MalData { + guard let token = peak() else { throw MalError.Error } + switch token { + case "(", "[", "{": + return try read_list(startWith: token) + case "'", "`", "~", "~@", "@": + let readerMacros = ["'": "quote", + "`": "quasiquote", + "~": "unquote", + "~@": "splice-unquote", + "@": "deref"] + pass() // pass the mark + return try [Symbol(readerMacros[token]!), read_form()] + case "^": + pass() // pass the mark + let meta = try read_form() + return try [Symbol("with-meta"), read_form(), meta] + default: + return try read_atom() + } + } + + + mutating func read_list(startWith leftParen: String) throws -> MalData { + pass() // pass the left paren + defer { + pass() // pass the right paren + } + + var list: [MalData] = [] + while ![")", "]", "}"].contains(peak()) { + guard peak() != nil else { + throw MalError.ParensMismatch + } + list.append(try read_form()) + } + + switch (leftParen, peak()) { + case ("(", ")"): + return list + case ("[", "]"): + return Vector(list) + case ("{", "}"): + var hashMap: [String: MalData] = [:] + for index in stride(from: 0, to: list.count, by: 2) { + guard list[index] is String, index+1 < list.count else { throw MalError.Error } + hashMap.updateValue(list[index+1], forKey: list[index] as! String) + } + return hashMap + default: + throw MalError.ParensMismatch + } + } + + mutating func read_atom() throws -> MalData { + let token = next()! + let regexInt = "^-?[0-9]+$" + let regexString = "\"(?:\\\\.|[^\\\\\"])*\"" + let regexStringUnbalanced = "\"(?:\\\\.|[^\\\\\"])*" + let regexKeyword = "^:" + func match(string: String, regex: String) -> Bool { + return token.range(of: regex, options: .regularExpression, range: token.startIndex.. [String] { + guard let regex = try? NSRegularExpression(pattern: "[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:[\\\\].|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}()'\"`@,;]+)", options: .useUnixLineSeparators) + else { return [] } + let matches = regex.matches(in: input, range: NSMakeRange(0, input.count)) + + return matches.map { match in + String(input[Range(match.range(at: 1), in: input)!]) + }.filter { token in + !token.hasPrefix(";") && !token.isEmpty } +} + + +func read_str(_ input: String) throws -> MalData { + let tokens = tokenizer(input) + guard tokens.count>0 else { + throw MalError.EmptyData + } + var reader = Reader(tokens: tokens) + return try reader.read_form() +} + + + + + diff --git a/swift4/Sources/step0_repl/main.swift b/swift4/Sources/step0_repl/main.swift new file mode 100644 index 0000000000..5a99fa7ab1 --- /dev/null +++ b/swift4/Sources/step0_repl/main.swift @@ -0,0 +1,32 @@ +// +// main.swift +// swift4 +// +// Created by LuLouie on 2019/1/6. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +func READ(_ input:String) -> String { + return input +} + +func EVAL(_ input:String) -> String { + return input +} + +func PRINT(_ input:String) -> String { + return input +} + +@discardableResult func rep(_ input:String) -> String { + return PRINT(EVAL(READ(input))) +} + +while true { + print("user> ", terminator: "") + if let input = readLine(strippingNewline: true) { + print(rep(input)) + } +} diff --git a/swift4/Sources/step1_read_print/main.swift b/swift4/Sources/step1_read_print/main.swift new file mode 100644 index 0000000000..df23a56181 --- /dev/null +++ b/swift4/Sources/step1_read_print/main.swift @@ -0,0 +1,38 @@ +// +// main.swift +// swift4 +// +// Created by LuLouie on 2019/1/6. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +func READ(_ input: String) throws -> MalData { + return try read_str(input) +} + +func EVAL(_ input: MalData) throws -> MalData { + return input +} + +func PRINT(_ input: MalData) -> String { + return pr_str(input, print_readably: true) +} + +@discardableResult func rep(_ input: String) throws -> String { + return try PRINT(EVAL(READ(input))) +} + + +while true { + print("user> ", terminator: "") + if let input = readLine(strippingNewline: true) { + guard input != "" else { continue } + do { + try print(rep(input)) + } catch let error as MalError { + print(error.info()) + } + } +} diff --git a/swift4/Sources/step2_eval/main.swift b/swift4/Sources/step2_eval/main.swift new file mode 100644 index 0000000000..de223033e9 --- /dev/null +++ b/swift4/Sources/step2_eval/main.swift @@ -0,0 +1,85 @@ +// +// main.swift +// swift4 +// +// Created by LuLouie on 2019/1/6. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +func READ(_ input: String) throws -> MalData { + return try read_str(input) +} + +func EVAL(_ ast: MalData, env: [String: MalData]) throws -> MalData { + switch ast.dataType { + case .Vector: + let vector = ast as! ContiguousArray + return try ContiguousArray(vector.map { element in try EVAL(element, env: env) }) + case .List: + let list = ast as! [MalData] + if list.isEmpty { return list } + let evaluated = try eval_ast(list, env: env) as! [MalData] + if let function = evaluated[0] as? Function { + return try function.fn(List(evaluated.dropFirst())) + } else { + throw MalError.SymbolNotFound(list[0] as! Symbol) + } + case .HashMap: + let hashMap = ast as! HashMap + return try hashMap.mapValues { value in try EVAL(value, env: env) } + default: + return try eval_ast(ast, env: env) + } +} + +func PRINT(_ input: MalData) -> String { + return pr_str(input, print_readably: true) +} + +@discardableResult func rep(_ input: String) throws -> String{ + func calculate(_ args: [MalData], op: (Number, Number) -> Number) throws -> MalData { + guard args.count == 2, args[0] is Number, args[1] is Number else { throw MalError.InvalidArgument } + return op(args[0] as! Number, args[1] as! Number) + } + + let repl_env = ["+": Function(fn: { args in try calculate(args, op: +) }), + "-": Function(fn: { args in try calculate(args, op: -) }), + "*": Function(fn: { args in try calculate(args, op: *) }), + "/": Function(fn: { args in try calculate(args, op: /) })] + + return try PRINT(EVAL(READ(input), env: repl_env)) +} + +func eval_ast(_ ast: MalData, env: [String: MalData]) throws -> MalData { + switch ast.dataType { + case .Symbol: + let sym = ast as! Symbol + if let function = env[sym.name] { + return function + } else { + throw MalError.SymbolNotFound(sym) + } + case .List: + let list = ast as! [MalData] + return try list.map { element in try EVAL(element, env: env) } + default: + return ast + } +} + + + + +while true { + print("user> ", terminator: "") + if let input = readLine(strippingNewline: true) { + guard input != "" else { continue } + do { + try print(rep(input)) + } catch let error as MalError { + print(error.info()) + } + } +} diff --git a/swift4/Sources/step3_env/main.swift b/swift4/Sources/step3_env/main.swift new file mode 100644 index 0000000000..3ed964e059 --- /dev/null +++ b/swift4/Sources/step3_env/main.swift @@ -0,0 +1,101 @@ +// +// main.swift +// swift4 +// +// Created by LuLouie on 2019/1/6. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +func READ(_ input: String) throws -> MalData { + return try read_str(input) +} + +func EVAL(_ ast: MalData, env: Env) throws -> MalData { + switch ast.dataType { + case .Vector: + let vector = ast as! ContiguousArray + return try ContiguousArray(vector.map { element in try EVAL(element, env: env) }) + case .List: + let list = ast as! [MalData] + guard !list.isEmpty else { return list } + guard let sym = list[0] as? Symbol else { throw MalError.Error } + + switch sym.name { + case "def!": + let value = try EVAL(list[2], env: env), key = list[1] as! Symbol + env.set(value, forKey: key) + return value + case "let*": + let newEnv = Env(outer: env), expr = list[2] + let bindings = (list[1] is Vector) ? List(list[1] as! Vector) : list[1] as! List + for i in stride(from: 0, to: bindings.count-1, by: 2) { + let key = bindings[i], value = bindings[i+1] + let result = try EVAL(value, env: newEnv) + newEnv.set(result, forKey: key as! Symbol) + } + return try EVAL(expr, env: newEnv) + default: + let evaluated = try eval_ast(list, env: env) as! [MalData] + if let function = evaluated[0] as? Function { + return try function.fn(List(evaluated.dropFirst())) + } else { + throw MalError.SymbolNotFound(list[0] as! Symbol) + } + } + case .HashMap: + let hashMap = ast as! HashMap + return try hashMap.mapValues { value in try EVAL(value, env: env) } + default: + return try eval_ast(ast, env: env) + } +} + +func PRINT(_ input: MalData) -> String { + return pr_str(input, print_readably: true) +} + +@discardableResult func rep(_ input: String, env: Env) throws -> String { + + return try PRINT(EVAL(READ(input), env: env)) +} + +func eval_ast(_ ast: MalData, env: Env) throws -> MalData { + switch ast.dataType { + case .Symbol: + let sym = ast as! Symbol + if let function = try? env.get(forKey: sym) { + return function + } else { + throw MalError.SymbolNotFound(sym) + } + case .List: + let list = ast as! [MalData] + return try list.map { element in try EVAL(element, env: env) } + default: + return ast + } +} + +func calculate(_ args: [MalData], op: (Number, Number) -> Number) throws -> MalData { + guard args.count == 2, args[0] is Number, args[1] is Number else { throw MalError.InvalidArgument } + return op(args[0] as! Number, args[1] as! Number) +} +let repl_env = Env() +repl_env.set(Function(fn: { args in try calculate(args, op: +) }), forKey: Symbol("+")) +repl_env.set(Function(fn: { args in try calculate(args, op: -) }), forKey: Symbol("-")) +repl_env.set(Function(fn: { args in try calculate(args, op: *) }), forKey: Symbol("*")) +repl_env.set(Function(fn: { args in try calculate(args, op: /) }), forKey: Symbol("/")) + +while true { + print("user> ", terminator: "") + if let input = readLine(strippingNewline: true) { + guard input != "" else { continue } + do { + try print(rep(input, env: repl_env)) + } catch let error as MalError { + print(error.info()) + } + } +} diff --git a/swift4/Sources/step4_if_fn_do/main.swift b/swift4/Sources/step4_if_fn_do/main.swift new file mode 100644 index 0000000000..2ab88a2b61 --- /dev/null +++ b/swift4/Sources/step4_if_fn_do/main.swift @@ -0,0 +1,115 @@ +// +// main.swift +// swift4 +// +// Created by LuLouie on 2019/1/6. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +func READ(_ input: String) throws -> MalData { + return try read_str(input) +} + +func EVAL(_ ast: MalData, env: Env) throws -> MalData { + switch ast.dataType { + case .Vector: + let vector = ast as! ContiguousArray + return try ContiguousArray(vector.map { element in try EVAL(element, env: env) }) + case .List: + let list = ast as! [MalData] + guard !list.isEmpty else { return list } + if let sym = list[0] as? Symbol { + switch sym.name { + case "def!": + let value = try EVAL(list[2], env: env), key = list[1] as! Symbol + env.set(value, forKey: key) + return value + case "let*": + let newEnv = Env(outer: env), expr = list[2] + let bindings = (list[1] is Vector) ? List(list[1] as! Vector) : list[1] as! List + for i in stride(from: 0, to: bindings.count-1, by: 2) { + let key = bindings[i], value = bindings[i+1] + let result = try EVAL(value, env: newEnv) + newEnv.set(result, forKey: key as! Symbol) + } + return try EVAL(expr, env: newEnv) + case "do": + return try list.dropFirst().map { try EVAL($0, env: env) }.last ?? Nil() + case "if": + let predicate = try EVAL(list[1], env: env) + if predicate as? Bool == false || predicate is Nil { + return list.count>3 ? try EVAL(list[3], env: env) : Nil() + } else { + return try EVAL(list[2], env: env) + } + case "fn*": + let ops = {(params: [MalData]) -> MalData in + let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env) + return try EVAL(list[2], env: newEnv) + } + return Function(fn: ops) + default: + break + } + } + // not a symbol. maybe: function, list, or some wrong type + let evaluated = try eval_ast(list, env: env) as! [MalData] + if let function = evaluated[0] as? Function { + return try function.fn(List(evaluated.dropFirst())) + } else { + throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol")) + } + case .HashMap: + let hashMap = ast as! HashMap + return try hashMap.mapValues { value in try EVAL(value, env: env) } + default: + return try eval_ast(ast, env: env) + } +} + +func PRINT(_ input: MalData) -> String { + return pr_str(input, print_readably: true) +} + +@discardableResult func rep(_ input: String, env: Env) throws -> String { + + return try PRINT(EVAL(READ(input), env: env)) +} + +func eval_ast(_ ast: MalData, env: Env) throws -> MalData { + switch ast.dataType { + case .Symbol: + let sym = ast as! Symbol + if let function = try? env.get(forKey: sym) { + return function + } else { + throw MalError.SymbolNotFound(sym) + } + case .List: + let list = ast as! [MalData] + return try list.map { element in try EVAL(element, env: env) } + default: + return ast + } +} + + +var repl_env = Env() +for (key, value) in ns { + repl_env.set(Function(fn: value), forKey: Symbol(key)) +} +try _ = rep("(def! not (fn* (a) (if a false true)))", env: repl_env) + +while true { + print("user> ", terminator: "") + if let input = readLine(strippingNewline: true) { + guard input != "" else { continue } + do { + try print(rep(input, env: repl_env)) + } catch let error as MalError { + print(error.info()) + } + } +} diff --git a/swift4/Sources/step5_tco/main.swift b/swift4/Sources/step5_tco/main.swift new file mode 100644 index 0000000000..b59ba68117 --- /dev/null +++ b/swift4/Sources/step5_tco/main.swift @@ -0,0 +1,145 @@ +// +// main.swift +// swift4 +// +// Created by LuLouie on 2019/1/6. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +func READ(_ input: String) throws -> MalData { + return try read_str(input) +} + +func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData { + var ast = anAst, env = anEnv + while true { + switch ast.dataType { + case .List: + let list = ast as! [MalData] + guard !list.isEmpty else { return list } + if let sym = list[0] as? Symbol { + switch sym.name { + case "def!": + let value = try EVAL(list[2], env: env), key = list[1] as! Symbol + env.set(value, forKey: key) + return value + case "let*": + let newEnv = Env(outer: env), expr = list[2] + let bindings = list[1].listForm + for i in stride(from: 0, to: bindings.count-1, by: 2) { + let key = bindings[i], value = bindings[i+1] + let result = try EVAL(value, env: newEnv) + newEnv.set(result, forKey: key as! Symbol) + } + env = newEnv + ast = expr + continue + case "do": + try _ = list.dropFirst().dropLast().map { try EVAL($0, env: env) } + ast = list.last ?? Nil() + continue + case "if": + let predicate = try EVAL(list[1], env: env) + if predicate as? Bool == false || predicate is Nil { + ast = list.count>3 ? list[3] : Nil() + } else { + ast = list[2] + } + continue + case "fn*": + let fn = {(params: [MalData]) -> MalData in + let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env) + return try EVAL(list[2], env: newEnv) + } + return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn) + // fn 是å¦éœ€è¦å­˜å‚¨ï¼Ÿ + default: + break + } + } + // not a symbol. maybe: function, list, or some wrong type + let evaluated = try eval_ast(list, env: env) as! [MalData] + guard let function = evaluated[0] as? Function else { + throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol")) + } + if let fnAst = function.ast { // a full fn + ast = fnAst + env = Env(binds: function.params!, exprs: evaluated.dropFirst().listForm, outer: function.env!) + } else { // normal function + return try function.fn(evaluated.dropFirst().listForm) + } + continue +/* fn 的尾递归优化 +fn 的语法形å¼ï¼š ((fn (a,b)(+ a b )) 1 2) å½¢å‚ï¼Œå‡½æ•°ä½“ï¼Œå®žå‚ +fn 本æ¥çš„实现。 + 1.生æˆï¼šåˆ¶é€ ä¸€ä¸ªé—­åŒ… + 1.1 闭包的功能:读入实å‚, 建立 å½¢å‚=å®žå‚ çš„çŽ¯å¢ƒï¼Œåœ¨è¿™ä¸ªçŽ¯å¢ƒä¸­ 求值函数体 + 1.2 闭包本身ä¸å¸¦æœ‰çŽ¯å¢ƒï¼Œå½“æ±‚å€¼é—­åŒ…æ—¶ä½¿ç”¨å½“æ—¶çš„çŽ¯å¢ƒ + 2.使用: + 以使用时的环境,使用实å‚调用闭包,闭包的返回值作为返回值。over (一次函数调用) +fn çš„ TCO 实现。 + 1.生æˆ: å½¢å‚ å‡½æ•°ä½“ é—­åŒ…ï¼ˆé—­åŒ…åŒ…å«æœ€åˆçš„å½¢å‚和函数体)+ 生æˆfn时的环境 + 2.使用: + å–出 函数体, + 使用求值时的形å‚,以 fn 中的 env 为外层 env 建立环境 () + 通过循环,在新建的环境中求值函数体 + */ + case .Vector: + let vector = ast as! ContiguousArray + return try ContiguousArray(vector.map { element in try EVAL(element, env: env) }) + case .HashMap: + let hashMap = ast as! HashMap + return try hashMap.mapValues { value in try EVAL(value, env: env) } + default: + return try eval_ast(ast, env: env) + } + } +} + + +func PRINT(_ input: MalData) -> String { + return pr_str(input, print_readably: true) +} + +@discardableResult func rep(_ input: String, env: Env) throws -> String { + + return try PRINT(EVAL(READ(input), env: env)) +} + +func eval_ast(_ ast: MalData, env: Env) throws -> MalData { + switch ast.dataType { + case .Symbol: + let sym = ast as! Symbol + if let function = try? env.get(forKey: sym) { + return function + } else { + throw MalError.SymbolNotFound(sym) + } + case .List: + let list = ast as! [MalData] + return try list.map { element in try EVAL(element, env: env) } + default: + return ast + } +} + + +var repl_env = Env() +for (key, value) in ns { + repl_env.set(Function(fn: value), forKey: Symbol(key)) +} +try _ = rep("(def! not (fn* (a) (if a false true)))", env: repl_env) + +while true { + print("user> ", terminator: "") + if let input = readLine(strippingNewline: true) { + guard input != "" else { continue } + do { + try print(rep(input, env: repl_env)) + } catch let error as MalError { + print(error.info()) + } + } +} diff --git a/swift4/Sources/step6_file/main.swift b/swift4/Sources/step6_file/main.swift new file mode 100644 index 0000000000..088cd4aa7c --- /dev/null +++ b/swift4/Sources/step6_file/main.swift @@ -0,0 +1,143 @@ +// +// main.swift +// swift4 +// +// Created by LuLouie on 2019/1/6. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +func READ(_ input: String) throws -> MalData { + return try read_str(input) +} + +func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData { + var ast = anAst, env = anEnv + while true { + switch ast.dataType { + case .List: + let list = ast as! [MalData] + guard !list.isEmpty else { return list } + if let sym = list[0] as? Symbol { + switch sym.name { + case "def!": + let value = try EVAL(list[2], env: env), key = list[1] as! Symbol + env.set(value, forKey: key) + return value + case "let*": + let newEnv = Env(outer: env), expr = list[2] + let bindings = list[1].listForm + for i in stride(from: 0, to: bindings.count-1, by: 2) { + let key = bindings[i], value = bindings[i+1] + let result = try EVAL(value, env: newEnv) + newEnv.set(result, forKey: key as! Symbol) + } + env = newEnv + ast = expr + continue + case "do": + try _ = list.dropFirst().dropLast().map { try EVAL($0, env: env) } + ast = list.last ?? Nil() + continue + case "if": + let predicate = try EVAL(list[1], env: env) + if predicate as? Bool == false || predicate is Nil { + ast = list.count>3 ? list[3] : Nil() + } else { + ast = list[2] + } + continue + case "fn*": + let fn = {(params: [MalData]) -> MalData in + let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env) + return try EVAL(list[2], env: newEnv) + } + return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn) + // fn 是å¦éœ€è¦å­˜å‚¨ï¼Ÿ + default: + break + } + } + // not a symbol. maybe: function, list, or some wrong type + let evaluated = try eval_ast(list, env: env) as! [MalData] + guard let function = evaluated[0] as? Function else { + throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol")) + } + if let fnAst = function.ast { // a full fn + ast = fnAst + env = Env(binds: function.params!, exprs: evaluated.dropFirst().listForm, outer: function.env!) + } else { // normal function + return try function.fn(evaluated.dropFirst().listForm) + } + continue + case .Vector: + let vector = ast as! ContiguousArray + return try ContiguousArray(vector.map { element in try EVAL(element, env: env) }) + case .HashMap: + let hashMap = ast as! HashMap + return try hashMap.mapValues { value in try EVAL(value, env: env) } + default: + return try eval_ast(ast, env: env) + } + } +} + +func PRINT(_ input: MalData) -> String { + return pr_str(input, print_readably: true) +} + +@discardableResult func rep(_ input: String, env: Env) throws -> String { + return try PRINT(EVAL(READ(input), env: env)) +} + +func eval_ast(_ ast: MalData, env: Env) throws -> MalData { + switch ast.dataType { + case .Symbol: + let sym = ast as! Symbol + if let function = try? env.get(forKey: sym) { + return function + } else { + throw MalError.SymbolNotFound(sym) + } + case .List: + let list = ast as! [MalData] + return try list.map { element in try EVAL(element, env: env) } + case .Atom: + return (ast as! Atom).value + default: + return ast + } +} + + + +var repl_env = Env() +for (key, value) in ns { + repl_env.set(Function(fn: value), forKey: Symbol(key)) +} +repl_env.set(Function(fn: { try EVAL($0[0], env: repl_env) }), forKey: Symbol("eval")) +repl_env.set([], forKey: Symbol("*ARGV*")) + +try rep("(def! not (fn* (a) (if a false true)))", env: repl_env) +try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env) + +if CommandLine.argc > 1 { + let fileName = CommandLine.arguments[1], + args = List(CommandLine.arguments.dropFirst(2)) + repl_env.set(args, forKey: Symbol("*ARGV*")) + try rep("(load-file \"\(fileName)\")", env: repl_env) + exit(0) +} + +while true { + print("user> ", terminator: "") + if let input = readLine(strippingNewline: true) { + guard input != "" else { continue } + do { + try print(rep(input, env: repl_env)) + } catch let error as MalError { + print(error.info()) + } + } +} diff --git a/swift4/Sources/step7_quote/main.swift b/swift4/Sources/step7_quote/main.swift new file mode 100644 index 0000000000..da1fbdeda1 --- /dev/null +++ b/swift4/Sources/step7_quote/main.swift @@ -0,0 +1,165 @@ +// +// main.swift +// swift4 +// +// Created by LuLouie on 2019/1/6. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +func READ(_ input: String) throws -> MalData { + return try read_str(input) +} + +func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData { + func is_pair(_ ast: MalData) -> Bool { // not used + return (ast is [MalData]) && (ast.count != 0) + } + func quasiquote(_ ast: MalData) -> MalData { + let list = ast.listForm + if list.isEmpty { + return [Symbol("quote"), ast] + } + if let sym = list[0] as? Symbol, sym.name == "unquote" { + return list[1] + } + let innerList = list[0].listForm + if !innerList.isEmpty, let sym = innerList[0] as? Symbol, sym.name == "splice-unquote" { + return [Symbol("concat"), innerList[1], quasiquote(list.dropFirst().listForm)] + } + return [Symbol("cons"), quasiquote(list[0]), quasiquote(list.dropFirst().listForm)] + } + + var ast = anAst, env = anEnv + while true { + switch ast.dataType { + case .List: + let list = ast as! [MalData] + guard !list.isEmpty else { return list } + if let sym = list[0] as? Symbol { + switch sym.name { + case "def!": + let value = try EVAL(list[2], env: env), key = list[1] as! Symbol + env.set(value, forKey: key) + return value + case "let*": + let newEnv = Env(outer: env), expr = list[2] + let bindings = list[1].listForm + for i in stride(from: 0, to: bindings.count-1, by: 2) { + let key = bindings[i], value = bindings[i+1] + let result = try EVAL(value, env: newEnv) + newEnv.set(result, forKey: key as! Symbol) + } + env = newEnv + ast = expr + continue + case "do": + try _ = list.dropFirst().dropLast().map { try EVAL($0, env: env) } + ast = list.last ?? Nil() + continue + case "if": + let predicate = try EVAL(list[1], env: env) + if predicate as? Bool == false || predicate is Nil { + ast = list.count>3 ? list[3] : Nil() + } else { + ast = list[2] + } + continue + case "fn*": // fn 是å¦éœ€è¦å­˜å‚¨ï¼Ÿ + let fn = {(params: [MalData]) -> MalData in + let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env) + return try EVAL(list[2], env: newEnv) + } + return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn) + case "quote": + return list[1] + case "quasiquote": + ast = quasiquote(list[1]) + continue + default: + break + } + } + // not a symbol. maybe: function, list, or some wrong type + let evaluated = try eval_ast(list, env: env) as! [MalData] + guard let function = evaluated[0] as? Function else { + throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol")) + } + if let fnAst = function.ast { // a full fn + ast = fnAst + env = Env(binds: function.params!, exprs: evaluated.dropFirst().listForm, outer: function.env!) + } else { // normal function + return try function.fn(evaluated.dropFirst().listForm) + } + continue + case .Vector: + let vector = ast as! ContiguousArray + return try ContiguousArray(vector.map { element in try EVAL(element, env: env) }) + case .HashMap: + let hashMap = ast as! HashMap + return try hashMap.mapValues { value in try EVAL(value, env: env) } + default: + return try eval_ast(ast, env: env) + } + } +} + +func PRINT(_ input: MalData) -> String { + return pr_str(input, print_readably: true) +} + +@discardableResult func rep(_ input: String, env: Env) throws -> String { + return try PRINT(EVAL(READ(input), env: env)) +} + +func eval_ast(_ ast: MalData, env: Env) throws -> MalData { + switch ast.dataType { + case .Symbol: + let sym = ast as! Symbol + if let function = try? env.get(forKey: sym) { + return function + } else { + throw MalError.SymbolNotFound(sym) + } + case .List: + let list = ast as! [MalData] + return try list.map { element in try EVAL(element, env: env) } + case .Atom: + return (ast as! Atom).value + default: + return ast + } +} + + + +var repl_env = Env() +for (key, value) in ns { + repl_env.set(Function(fn: value), forKey: Symbol(key)) +} +repl_env.set(Function(fn: { try EVAL($0[0], env: repl_env) }), forKey: Symbol("eval")) +repl_env.set([], forKey: Symbol("*ARGV*")) + +try rep("(def! not (fn* (a) (if a false true)))", env: repl_env) +try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env) + +if CommandLine.argc > 1 { + let fileName = CommandLine.arguments[1], + args = List(CommandLine.arguments.dropFirst(2)) + repl_env.set(args, forKey: Symbol("*ARGV*")) + try rep("(load-file \"\(fileName)\")", env: repl_env) + exit(0) +} + +while true { + print("user> ", terminator: "") + if let input = readLine(strippingNewline: true) { + guard input != "" else { continue } + do { + try print(rep(input, env: repl_env)) + } catch let error as MalError { + print(error.info()) + } + } +} diff --git a/swift4/Sources/step8_macros/main.swift b/swift4/Sources/step8_macros/main.swift new file mode 100644 index 0000000000..f362d71fe7 --- /dev/null +++ b/swift4/Sources/step8_macros/main.swift @@ -0,0 +1,196 @@ +// +// main.swift +// swift4 +// +// Created by LuLouie on 2019/1/6. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +func READ(_ input: String) throws -> MalData { + return try read_str(input) +} + +func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData { + func is_pair(_ ast: MalData) -> Bool { // not used + return (ast is [MalData]) && (ast.count != 0) + } + func quasiquote(_ ast: MalData) -> MalData { + let list = ast.listForm + if list.isEmpty { + return [Symbol("quote"), ast] + } + if let sym = list[0] as? Symbol, sym.name == "unquote" { + return list[1] + } + let innerList = list[0].listForm + if !innerList.isEmpty, let sym = innerList[0] as? Symbol, sym.name == "splice-unquote" { + return [Symbol("concat"), innerList[1], quasiquote(list.dropFirst().listForm)] + } + return [Symbol("cons"), quasiquote(list[0]), quasiquote(list.dropFirst().listForm)] + } + func macroexpand(_ anAst: MalData, env: Env) throws -> MalData { + func isMacro_call(_ ast: MalData, env: Env) -> Bool { // not used + if let list = ast as? [MalData], + let symbol = list[0] as? Symbol, + let fn = try? env.get(forKey: symbol) as? Function { + return fn?.isMacro ?? false + } + return false + } + + var ast = anAst + while let list = ast as? [MalData], + let symbol = list[0] as? Symbol, + let fn = try? env.get(forKey: symbol) as? Function, + let isMacro = fn?.isMacro, isMacro == true { + ast = try fn!.fn(List(list.dropFirst())) + } + return ast + } + + /// Apply + var ast = anAst, env = anEnv + while true { + switch ast.dataType { + case .List: + ast = try macroexpand(ast, env: env) + guard let list = ast as? [MalData] else { return try eval_ast(ast, env: env) } + guard !list.isEmpty else { return list } + if let sym = list[0] as? Symbol { + switch sym.name { + case "def!": + let value = try EVAL(list[2], env: env), key = list[1] as! Symbol + env.set(value, forKey: key) + return value + case "defmacro!": + let fn = try EVAL(list[2], env: env) as! Function, key = list[1] as! Symbol + let macro = Function(withFunction: fn, isMacro: true) + env.set(macro, forKey: key) + return macro + case "let*": + let newEnv = Env(outer: env), expr = list[2] + let bindings = list[1].listForm + for i in stride(from: 0, to: bindings.count-1, by: 2) { + let key = bindings[i], value = bindings[i+1] + let result = try EVAL(value, env: newEnv) + newEnv.set(result, forKey: key as! Symbol) + } + env = newEnv + ast = expr + continue + case "do": + try _ = list.dropFirst().dropLast().map { try EVAL($0, env: env) } + ast = list.last ?? Nil() + continue + case "if": + let predicate = try EVAL(list[1], env: env) + if predicate as? Bool == false || predicate is Nil { + ast = list.count>3 ? list[3] : Nil() + } else { + ast = list[2] + } + continue + case "fn*": // fn 是å¦éœ€è¦å­˜å‚¨ï¼Ÿ + let fn = {(params: [MalData]) -> MalData in + let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env) + return try EVAL(list[2], env: newEnv) + } + return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn) + case "quote": + return list[1] + case "quasiquote": + ast = quasiquote(list[1]) + continue + case "macroexpand": + return try macroexpand(list[1], env: env) + default: + break + } + } + // not a symbol. maybe: function, list, or some wrong type + let evaluated = try eval_ast(list, env: env) as! [MalData] + guard let function = evaluated[0] as? Function else { + throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol")) + } + if let fnAst = function.ast { // a full fn + ast = fnAst + env = Env(binds: function.params!, exprs: evaluated.dropFirst().listForm, outer: function.env!) + } else { // normal function + return try function.fn(evaluated.dropFirst().listForm) + } + continue + case .Vector: + let vector = ast as! ContiguousArray + return try ContiguousArray(vector.map { element in try EVAL(element, env: env) }) + case .HashMap: + let hashMap = ast as! HashMap + return try hashMap.mapValues { value in try EVAL(value, env: env) } + default: + return try eval_ast(ast, env: env) + } + } +} + +func PRINT(_ input: MalData) -> String { + return pr_str(input, print_readably: true) +} + +@discardableResult func rep(_ input: String, env: Env) throws -> String { + return try PRINT(EVAL(READ(input), env: env)) +} + +func eval_ast(_ ast: MalData, env: Env) throws -> MalData { + switch ast.dataType { + case .Symbol: + let sym = ast as! Symbol + if let function = try? env.get(forKey: sym) { + return function + } else { + throw MalError.SymbolNotFound(sym) + } + case .List: + let list = ast as! [MalData] + return try list.map { element in try EVAL(element, env: env) } + case .Atom: + return (ast as! Atom).value + default: + return ast + } +} + + + +var repl_env = Env() +for (key, value) in ns { + repl_env.set(Function(fn: value), forKey: Symbol(key)) +} +repl_env.set(Function(fn: { try EVAL($0[0], env: repl_env) }), forKey: Symbol("eval")) +repl_env.set([], forKey: Symbol("*ARGV*")) + +try rep("(def! not (fn* (a) (if a false true)))", env: repl_env) +try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env) +try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env) +try rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", env: repl_env) +try rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", env: repl_env) + +if CommandLine.argc > 1 { + let fileName = CommandLine.arguments[1], + args = List(CommandLine.arguments.dropFirst(2)) + repl_env.set(args, forKey: Symbol("*ARGV*")) + try rep("(load-file \"\(fileName)\")", env: repl_env) + exit(0) +} + +while true { + print("user> ", terminator: "") + if let input = readLine(strippingNewline: true) { + guard input != "" else { continue } + do { + try print(rep(input, env: repl_env)) + } catch let error as MalError { + print(error.info()) + } + } +} diff --git a/swift4/Sources/step9_try/main.swift b/swift4/Sources/step9_try/main.swift new file mode 100644 index 0000000000..0c56215fa7 --- /dev/null +++ b/swift4/Sources/step9_try/main.swift @@ -0,0 +1,215 @@ +// +// main.swift +// swift4 +// +// Created by LuLouie on 2019/1/6. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +func READ(_ input: String) throws -> MalData { + return try read_str(input) +} + +func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData { + func is_pair(_ ast: MalData) -> Bool { // not used + return (ast is [MalData]) && (ast.count != 0) + } + func quasiquote(_ ast: MalData) -> MalData { + let list = ast.listForm + if list.isEmpty { + return [Symbol("quote"), ast] + } + if let sym = list[0] as? Symbol, sym.name == "unquote" { + return list[1] + } + let innerList = list[0].listForm + if !innerList.isEmpty, let sym = innerList[0] as? Symbol, sym.name == "splice-unquote" { + return [Symbol("concat"), innerList[1], quasiquote(list.dropFirst().listForm)] + } + return [Symbol("cons"), quasiquote(list[0]), quasiquote(list.dropFirst().listForm)] + } + func macroexpand(_ anAst: MalData, env: Env) throws -> MalData { + func isMacro_call(_ ast: MalData, env: Env) -> Bool { // not used + if let list = ast as? [MalData], + let symbol = list[0] as? Symbol, + let fn = try? env.get(forKey: symbol) as? Function { + return fn?.isMacro ?? false + } + return false + } + + var ast = anAst + while let list = ast as? [MalData], + let symbol = list[0] as? Symbol, + let fn = try? env.get(forKey: symbol) as? Function, + let isMacro = fn?.isMacro, isMacro == true { + ast = try fn!.fn(List(list.dropFirst())) + } + return ast + } + + /// Apply + var ast = anAst, env = anEnv + while true { + switch ast.dataType { + case .List: + ast = try macroexpand(ast, env: env) + guard let list = ast as? [MalData] else { return try eval_ast(ast, env: env) } + guard !list.isEmpty else { return list } + if let sym = list[0] as? Symbol { + switch sym.name { + case "def!": + let value = try EVAL(list[2], env: env), key = list[1] as! Symbol + env.set(value, forKey: key) + return value + case "defmacro!": + let fn = try EVAL(list[2], env: env) as! Function, key = list[1] as! Symbol + let macro = Function(withFunction: fn, isMacro: true) + env.set(macro, forKey: key) + return macro + case "let*": + let newEnv = Env(outer: env), expr = list[2] + let bindings = list[1].listForm + for i in stride(from: 0, to: bindings.count-1, by: 2) { + let key = bindings[i], value = bindings[i+1] + let result = try EVAL(value, env: newEnv) + newEnv.set(result, forKey: key as! Symbol) + } + env = newEnv + ast = expr + continue + case "do": + try _ = list.dropFirst().dropLast().map { try EVAL($0, env: env) } + ast = list.last ?? Nil() + continue + case "if": + let predicate = try EVAL(list[1], env: env) + if predicate as? Bool == false || predicate is Nil { + ast = list.count>3 ? list[3] : Nil() + } else { + ast = list[2] + } + continue + case "fn*": // fn 是å¦éœ€è¦å­˜å‚¨ï¼Ÿ + let fn = {(params: [MalData]) -> MalData in + let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env) + return try EVAL(list[2], env: newEnv) + } + return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn) + case "quote": + return list[1] + case "quasiquote": + ast = quasiquote(list[1]) + continue + case "macroexpand": + return try macroexpand(list[1], env: env) +// (try* A (catch* B C)) + case "try*": + do { + return try EVAL(list[1], env: env) + } catch let error as MalError { + if list.count > 2 { + let catchList = list[2] as! [MalData] + let catchEnv = Env(binds: [catchList[1] as! Symbol], exprs:[error.info()] , outer: env) + return try EVAL(catchList[2], env: catchEnv) + } else { + throw error + } + } + default: + break + } + } + // not a symbol. maybe: function, list, or some wrong type + let evaluated = try eval_ast(list, env: env) as! [MalData] + guard let function = evaluated[0] as? Function else { + throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol")) + } + if let fnAst = function.ast { // a full fn + ast = fnAst + env = Env(binds: function.params!, exprs: evaluated.dropFirst().listForm, outer: function.env!) + } else { // normal function + return try function.fn(evaluated.dropFirst().listForm) + } + continue + case .Vector: + let vector = ast as! ContiguousArray + return try ContiguousArray(vector.map { element in try EVAL(element, env: env) }) + case .HashMap: + let hashMap = ast as! HashMap + return try hashMap.mapValues { value in try EVAL(value, env: env) } + default: + return try eval_ast(ast, env: env) + } + } +} + +func PRINT(_ input: MalData) -> String { + return pr_str(input, print_readably: true) +} + +@discardableResult func rep(_ input: String, env: Env) throws -> String { + return try PRINT(EVAL(READ(input), env: env)) +} + +func eval_ast(_ ast: MalData, env: Env) throws -> MalData { + switch ast.dataType { + case .Symbol: + let sym = ast as! Symbol + if let function = try? env.get(forKey: sym) { + return function + } else { + throw MalError.SymbolNotFound(sym) + } + case .List: + let list = ast as! [MalData] + return try list.map { element in try EVAL(element, env: env) } + case .Atom: + return (ast as! Atom).value + default: + return ast + } +} + + + +var repl_env = Env() +for (key, value) in ns { + repl_env.set(Function(fn: value), forKey: Symbol(key)) +} +repl_env.set(Function(fn: { try EVAL($0[0], env: repl_env) }), forKey: Symbol("eval")) +repl_env.set([], forKey: Symbol("*ARGV*")) + +try rep("(def! not (fn* (a) (if a false true)))", env: repl_env) +try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env) +try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env) +try rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", env: repl_env) +try rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", env: repl_env) + +if CommandLine.argc > 1 { + let fileName = CommandLine.arguments[1], + args = List(CommandLine.arguments.dropFirst(2)) + repl_env.set(args, forKey: Symbol("*ARGV*")) + try rep("(load-file \"\(fileName)\")", env: repl_env) + exit(0) +} + +while true { + print("user> ", terminator: "") + if let input = readLine(strippingNewline: true) { + guard input != "" else { continue } + do { + try print(rep(input, env: repl_env)) + } catch MalError.MalException(let data) { + if let description = data as? String { + print("Exception." + description) + } else if let dic = data as? [String: String], !dic.isEmpty { + print("Exception." + dic.keys.first! + "." + dic.values.first!) + } + } catch let error as MalError { + print((pr_str(error.info(), print_readably: false))) + } + } +} diff --git a/swift4/Sources/stepA_mal/main.swift b/swift4/Sources/stepA_mal/main.swift new file mode 100644 index 0000000000..f0f8e28004 --- /dev/null +++ b/swift4/Sources/stepA_mal/main.swift @@ -0,0 +1,221 @@ +// +// main.swift +// swift4 +// +// Created by LuLouie on 2019/1/6. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +func READ(_ input: String) throws -> MalData { + return try read_str(input) +} + +func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData { + func is_pair(_ ast: MalData) -> Bool { // not used + return (ast is [MalData]) && (ast.count != 0) + } + func quasiquote(_ ast: MalData) -> MalData { + let list = ast.listForm + if list.isEmpty { + return [Symbol("quote"), ast] + } + if let sym = list[0] as? Symbol, sym.name == "unquote" { + return list[1] + } + let innerList = list[0].listForm + if !innerList.isEmpty, let sym = innerList[0] as? Symbol, sym.name == "splice-unquote" { + return [Symbol("concat"), innerList[1], quasiquote(list.dropFirst().listForm)] + } + return [Symbol("cons"), quasiquote(list[0]), quasiquote(list.dropFirst().listForm)] + } + func macroexpand(_ anAst: MalData, env: Env) throws -> MalData { + func isMacro_call(_ ast: MalData, env: Env) -> Bool { // not used + if let list = ast as? [MalData], + let symbol = list[0] as? Symbol, + let fn = try? env.get(forKey: symbol) as? Function { + return fn?.isMacro ?? false + } + return false + } + + var ast = anAst + while let list = ast as? [MalData], + let symbol = list[0] as? Symbol, + let fn = try? env.get(forKey: symbol) as? Function, + let isMacro = fn?.isMacro, isMacro == true { + ast = try fn!.fn(List(list.dropFirst())) + } + return ast + } + + /// Apply + var ast = anAst, env = anEnv + while true { + switch ast.dataType { + case .List: + ast = try macroexpand(ast, env: env) + guard let list = ast as? [MalData] else { return try eval_ast(ast, env: env) } + guard !list.isEmpty else { return list } + if let sym = list[0] as? Symbol { + switch sym.name { + case "def!": + let value = try EVAL(list[2], env: env), key = list[1] as! Symbol + env.set(value, forKey: key) + return value + case "defmacro!": + let fn = try EVAL(list[2], env: env) as! Function, key = list[1] as! Symbol + let macro = Function(withFunction: fn, isMacro: true) + env.set(macro, forKey: key) + return macro + case "let*": + let newEnv = Env(outer: env), expr = list[2] + let bindings = list[1].listForm + for i in stride(from: 0, to: bindings.count-1, by: 2) { + let key = bindings[i], value = bindings[i+1] + let result = try EVAL(value, env: newEnv) + newEnv.set(result, forKey: key as! Symbol) + } + env = newEnv + ast = expr + continue + case "do": + try _ = list.dropFirst().dropLast().map { try EVAL($0, env: env) } + ast = list.last ?? Nil() + continue + case "if": + let predicate = try EVAL(list[1], env: env) + if predicate as? Bool == false || predicate is Nil { + ast = list.count>3 ? list[3] : Nil() + } else { + ast = list[2] + } + continue + case "fn*": // fn 是å¦éœ€è¦å­˜å‚¨ï¼Ÿ + let fn = {(params: [MalData]) -> MalData in + let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env) + return try EVAL(list[2], env: newEnv) + } + return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn) + case "quote": + return list[1] + case "quasiquote": + ast = quasiquote(list[1]) + continue + case "macroexpand": + return try macroexpand(list[1], env: env) + // (try* A (catch* B C)) + case "try*": + do { + return try EVAL(list[1], env: env) + } catch let error as MalError { + if list.count > 2 { + let catchList = list[2] as! [MalData] + let catchEnv = Env(binds: [catchList[1] as! Symbol], exprs:[error.info()] , outer: env) + return try EVAL(catchList[2], env: catchEnv) + } else { + throw error + } + } + default: + break + } + } + // not a symbol. maybe: function, list, or some wrong type + let evaluated = try eval_ast(list, env: env) as! [MalData] + guard let function = evaluated[0] as? Function else { + throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol")) + } + if let fnAst = function.ast { // a full fn + ast = fnAst + env = Env(binds: function.params!, exprs: evaluated.dropFirst().listForm, outer: function.env!) + } else { // normal function + return try function.fn(evaluated.dropFirst().listForm) + } + continue + case .Vector: + let vector = ast as! ContiguousArray + return try ContiguousArray(vector.map { element in try EVAL(element, env: env) }) + case .HashMap: + let hashMap = ast as! HashMap + return try hashMap.mapValues { value in try EVAL(value, env: env) } + default: + return try eval_ast(ast, env: env) + } + } +} + +func PRINT(_ input: MalData) -> String { + return pr_str(input, print_readably: true) +} + +@discardableResult func rep(_ input: String, env: Env) throws -> String { + return try PRINT(EVAL(READ(input), env: env)) +} + +func eval_ast(_ ast: MalData, env: Env) throws -> MalData { + switch ast.dataType { + case .Symbol: + let sym = ast as! Symbol + if let function = try? env.get(forKey: sym) { + return function + } else { + throw MalError.SymbolNotFound(sym) + } + case .List: + let list = ast as! [MalData] + return try list.map { element in try EVAL(element, env: env) } + case .Atom: + return (ast as! Atom).value + default: + return ast + } +} + + + +var repl_env = Env() +for (key, value) in ns { + repl_env.set(Function(fn: value), forKey: Symbol(key)) +} +repl_env.set(Function(fn: { try EVAL($0[0], env: repl_env) }), forKey: Symbol("eval")) +repl_env.set([], forKey: Symbol("*ARGV*")) +repl_env.set("Swift4", forKey: Symbol("*host-language*")) + + +try rep("(def! not (fn* (a) (if a false true)))", env: repl_env) +try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env) +try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env: repl_env) +try rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", env: repl_env) +try rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", env: repl_env) +try rep("(def! *gensym-counter* (atom 0))", env: repl_env) +try rep("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))", env: repl_env) +try rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))", env: repl_env) + +if CommandLine.argc > 1 { + let fileName = CommandLine.arguments[1], + args = List(CommandLine.arguments.dropFirst(2)) + repl_env.set(args, forKey: Symbol("*ARGV*")) + try rep("(load-file \"\(fileName)\")", env: repl_env) + exit(0) +} + +try rep("(println (str \"Mal [\" *host-language* \"]\"))", env: repl_env) +while true { + print("user> ", terminator: "") + if let input = readLine(strippingNewline: true) { + guard input != "" else { continue } + do { + try print(rep(input, env: repl_env)) + } catch MalError.MalException(let data) { + if let description = data as? String { + print("Exception." + description) + } else if let dic = data as? [String: String], !dic.isEmpty { + print("Exception." + dic.keys.first! + "." + dic.values.first!) + } + } catch let error as MalError { + print((pr_str(error.info(), print_readably: false))) + } + } +} diff --git a/swift4/Sources/types.swift b/swift4/Sources/types.swift new file mode 100644 index 0000000000..13369a483c --- /dev/null +++ b/swift4/Sources/types.swift @@ -0,0 +1,166 @@ +// +// types.swift +// swift4 +// +// Created by LuLouie on 2019/1/6. +// Copyright © 2019 llvm.xyz. All rights reserved. +// + +import Foundation + +enum MalDataType: String { + case Number, String, List, Vector, HashMap, Symbol, Keyword, Atom, Nil, True, False, Function, Unknown +} + +protocol MalData { + var dataType: MalDataType { get } + + var count: Int { get } + var listForm: [MalData] { get } +} +extension MalData { + var dataType: MalDataType { // not used + return MalDataType(rawValue: String(describing: type(of: self))) ?? MalDataType.Unknown + } + var count: Int { return 0 } + var listForm: [MalData] { return [] } +} + +typealias Number = Int +typealias List = Array +typealias Vector = ContiguousArray +typealias HashMap = Dictionary +//typealias MalClosureThrows = ([MalData]) throws -> MalData +//typealias MalClosure = ([MalData]) -> MalData + +struct Symbol: MalData { + let dataType = MalDataType.Symbol + let name: String + init(_ name: String) { + self.name = name + } +} + +struct Nil: MalData { + let dataType = MalDataType.Nil +} + +class Atom: MalData { + let dataType = MalDataType.Atom + var value: MalData + init(_ value: MalData) { + self.value = value + } +} + +struct Function: MalData { + let dataType = MalDataType.Function + + let ast: MalData? + let params: [Symbol]? + let env: Env? + let fn: (([MalData]) throws -> MalData) + let isMacro: Bool + let meta: MalData? + + init(ast: MalData? = nil, params: [Symbol]? = nil, env: Env? = nil, isMacro: Bool = false, meta: MalData? = nil, + fn: @escaping ([MalData]) throws -> MalData) { + self.ast = ast + self.params = params + self.env = env + self.isMacro = isMacro + self.fn = fn + self.meta = meta + } + init(withFunction function: Function, isMacro: Bool) { + self.ast = function.ast + self.params = function.params + self.env = function.env + self.fn = function.fn + self.meta = function.meta + self.isMacro = isMacro + } + init(withFunction function: Function, meta: MalData) { + self.ast = function.ast + self.params = function.params + self.env = function.env + self.fn = function.fn + self.isMacro = function.isMacro + self.meta = meta + } + +} + +//struct WithMeta: MalData { +// let dataType = MalDataType.WithMeta +// let data: MalData +// let meta: MalData +//} + + +extension String: MalData { + var dataType: MalDataType { + return !self.isEmpty && self[startIndex] == "\u{029E}" ? .Keyword : .String } +} +extension Number: MalData { + var dataType: MalDataType { return .Number } +} +extension Bool : MalData { + var dataType: MalDataType { return self == true ? .True : .False } +} + +extension List : MalData { + var dataType: MalDataType { return .List } + var listForm: [MalData] { return self as! [MalData] } +} +extension Vector: MalData { + var dataType: MalDataType { return .Vector } + var listForm: [MalData] { return List(self) as! [MalData] } +} +extension ArraySlice: MalData { + var dataType: MalDataType { return .List } + var listForm: [MalData] { return List(self) as! [MalData] } +} +extension HashMap: MalData { + var dataType: MalDataType { return .HashMap } + static func hashMap(fromList list: [MalData]) throws -> [String: MalData] { + var hashMap: [String: MalData] = [:] + for index in stride(from: 0, to: list.count, by: 2) { + guard list[index] is String, index+1 < list.count else { throw MalError.Error } + hashMap.updateValue(list[index+1], forKey: list[index] as! String) + } + return hashMap + } +} + +// MARK: Errors +enum MalError: Error { + case ParensMismatch + case QuotationMarkMismatch + case EmptyData + case SymbolNotFound(Symbol) + case InvalidArgument + case Error + case IndexOutOfBounds + case MalException(MalData) + func info() -> MalData { + switch self { + case .ParensMismatch: + return "unbalanced parens" + case .QuotationMarkMismatch: + return "unbalanced quotation mark" + case .EmptyData: + return "empty data" + case .InvalidArgument: + return "invalid argument" + case .SymbolNotFound(let symbol): + return "'\(symbol.name)' not found" + case .IndexOutOfBounds: + return "index out of bounds" + case .MalException(let data): + return data + default: + return "uncaught error!" + } + } +} diff --git a/swift4/run b/swift4/run new file mode 100755 index 0000000000..8ba68a5484 --- /dev/null +++ b/swift4/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec $(dirname $0)/${STEP:-stepA_mal} "${@}" From 573f6d0f7e7b2f7cf47b2a69ab3c60eec13415dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=86=E9=81=A5?= Date: Tue, 5 Mar 2019 18:27:26 +0800 Subject: [PATCH 0790/2308] Update Makefile --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0cda4bf399..9843ed9c3f 100644 --- a/Makefile +++ b/Makefile @@ -86,7 +86,7 @@ IMPLS = ada awk bash basic c chuck clojure coffee common-lisp cpp crystal cs d d guile haskell haxe hy io java js julia kotlin livescript logo lua make mal \ matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp plpgsql \ plsql powershell ps python r racket rexx rpython ruby rust scala scheme skew \ - swift swift3 tcl ts vb vhdl vimscript wasm yorick + swift swift3 swift4 tcl ts vb vhdl vimscript wasm yorick EXTENSION = .mal @@ -239,6 +239,7 @@ scheme_STEP_TO_PROG = $(scheme_STEP_TO_PROG_$(scheme_MODE)) skew_STEP_TO_PROG = skew/$($(1)).js swift_STEP_TO_PROG = swift/$($(1)) swift3_STEP_TO_PROG = swift3/$($(1)) +swift4_STEP_TO_PROG = swift4/$($(1)) tcl_STEP_TO_PROG = tcl/$($(1)).tcl ts_STEP_TO_PROG = ts/$($(1)).js vb_STEP_TO_PROG = vb/$($(1)).exe From 34264332a9615b5158f35cddef2cd2a1f8b2f86c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=86=E9=81=A5?= Date: Wed, 6 Mar 2019 09:02:48 +0800 Subject: [PATCH 0791/2308] remove auto-generated copyright declarations. --- swift4/Sources/core.swift | 7 ------- swift4/Sources/env.swift | 7 ------- swift4/Sources/printer.swift | 7 ------- swift4/Sources/reader.swift | 7 ------- swift4/Sources/step0_repl/main.swift | 7 ------- swift4/Sources/step1_read_print/main.swift | 7 ------- swift4/Sources/step2_eval/main.swift | 7 ------- swift4/Sources/step3_env/main.swift | 7 ------- swift4/Sources/step4_if_fn_do/main.swift | 7 ------- swift4/Sources/step5_tco/main.swift | 7 ------- swift4/Sources/step6_file/main.swift | 7 ------- swift4/Sources/step7_quote/main.swift | 7 ------- swift4/Sources/step8_macros/main.swift | 7 ------- swift4/Sources/step9_try/main.swift | 7 ------- swift4/Sources/stepA_mal/main.swift | 7 ------- swift4/Sources/types.swift | 7 ------- 16 files changed, 112 deletions(-) diff --git a/swift4/Sources/core.swift b/swift4/Sources/core.swift index cc9b2a9c4d..38212988ad 100644 --- a/swift4/Sources/core.swift +++ b/swift4/Sources/core.swift @@ -1,10 +1,3 @@ -// -// core.swift -// swift4 -// -// Created by LuLouie on 2019/1/10. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation diff --git a/swift4/Sources/env.swift b/swift4/Sources/env.swift index 6b43500508..50b8ca1cd3 100644 --- a/swift4/Sources/env.swift +++ b/swift4/Sources/env.swift @@ -1,10 +1,3 @@ -// -// env.swift -// swift4 -// -// Created by LuLouie on 2019/1/7. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation diff --git a/swift4/Sources/printer.swift b/swift4/Sources/printer.swift index c55d4f7c68..0ccfc1f93b 100644 --- a/swift4/Sources/printer.swift +++ b/swift4/Sources/printer.swift @@ -1,10 +1,3 @@ -// -// printer.swift -// swift4 -// -// Created by LuLouie on 2019/1/7. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation diff --git a/swift4/Sources/reader.swift b/swift4/Sources/reader.swift index 08c0e2ba55..44d3041b38 100644 --- a/swift4/Sources/reader.swift +++ b/swift4/Sources/reader.swift @@ -1,10 +1,3 @@ -// -// reader.swift -// swift4 -// -// Created by LuLouie on 2019/1/6. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation diff --git a/swift4/Sources/step0_repl/main.swift b/swift4/Sources/step0_repl/main.swift index 5a99fa7ab1..ef79a769df 100644 --- a/swift4/Sources/step0_repl/main.swift +++ b/swift4/Sources/step0_repl/main.swift @@ -1,10 +1,3 @@ -// -// main.swift -// swift4 -// -// Created by LuLouie on 2019/1/6. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation diff --git a/swift4/Sources/step1_read_print/main.swift b/swift4/Sources/step1_read_print/main.swift index df23a56181..120a820b23 100644 --- a/swift4/Sources/step1_read_print/main.swift +++ b/swift4/Sources/step1_read_print/main.swift @@ -1,10 +1,3 @@ -// -// main.swift -// swift4 -// -// Created by LuLouie on 2019/1/6. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation diff --git a/swift4/Sources/step2_eval/main.swift b/swift4/Sources/step2_eval/main.swift index de223033e9..be9680abdd 100644 --- a/swift4/Sources/step2_eval/main.swift +++ b/swift4/Sources/step2_eval/main.swift @@ -1,10 +1,3 @@ -// -// main.swift -// swift4 -// -// Created by LuLouie on 2019/1/6. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation diff --git a/swift4/Sources/step3_env/main.swift b/swift4/Sources/step3_env/main.swift index 3ed964e059..582ce41e9f 100644 --- a/swift4/Sources/step3_env/main.swift +++ b/swift4/Sources/step3_env/main.swift @@ -1,10 +1,3 @@ -// -// main.swift -// swift4 -// -// Created by LuLouie on 2019/1/6. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation diff --git a/swift4/Sources/step4_if_fn_do/main.swift b/swift4/Sources/step4_if_fn_do/main.swift index 2ab88a2b61..e0a2b2633e 100644 --- a/swift4/Sources/step4_if_fn_do/main.swift +++ b/swift4/Sources/step4_if_fn_do/main.swift @@ -1,10 +1,3 @@ -// -// main.swift -// swift4 -// -// Created by LuLouie on 2019/1/6. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation diff --git a/swift4/Sources/step5_tco/main.swift b/swift4/Sources/step5_tco/main.swift index b59ba68117..cb9e99a95c 100644 --- a/swift4/Sources/step5_tco/main.swift +++ b/swift4/Sources/step5_tco/main.swift @@ -1,10 +1,3 @@ -// -// main.swift -// swift4 -// -// Created by LuLouie on 2019/1/6. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation diff --git a/swift4/Sources/step6_file/main.swift b/swift4/Sources/step6_file/main.swift index 088cd4aa7c..4d068c0c38 100644 --- a/swift4/Sources/step6_file/main.swift +++ b/swift4/Sources/step6_file/main.swift @@ -1,10 +1,3 @@ -// -// main.swift -// swift4 -// -// Created by LuLouie on 2019/1/6. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation diff --git a/swift4/Sources/step7_quote/main.swift b/swift4/Sources/step7_quote/main.swift index da1fbdeda1..e4370353be 100644 --- a/swift4/Sources/step7_quote/main.swift +++ b/swift4/Sources/step7_quote/main.swift @@ -1,10 +1,3 @@ -// -// main.swift -// swift4 -// -// Created by LuLouie on 2019/1/6. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation diff --git a/swift4/Sources/step8_macros/main.swift b/swift4/Sources/step8_macros/main.swift index f362d71fe7..b707784d9e 100644 --- a/swift4/Sources/step8_macros/main.swift +++ b/swift4/Sources/step8_macros/main.swift @@ -1,10 +1,3 @@ -// -// main.swift -// swift4 -// -// Created by LuLouie on 2019/1/6. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation diff --git a/swift4/Sources/step9_try/main.swift b/swift4/Sources/step9_try/main.swift index 0c56215fa7..8e0e4beac6 100644 --- a/swift4/Sources/step9_try/main.swift +++ b/swift4/Sources/step9_try/main.swift @@ -1,10 +1,3 @@ -// -// main.swift -// swift4 -// -// Created by LuLouie on 2019/1/6. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation diff --git a/swift4/Sources/stepA_mal/main.swift b/swift4/Sources/stepA_mal/main.swift index f0f8e28004..60524dac50 100644 --- a/swift4/Sources/stepA_mal/main.swift +++ b/swift4/Sources/stepA_mal/main.swift @@ -1,10 +1,3 @@ -// -// main.swift -// swift4 -// -// Created by LuLouie on 2019/1/6. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation diff --git a/swift4/Sources/types.swift b/swift4/Sources/types.swift index 13369a483c..1706f475f2 100644 --- a/swift4/Sources/types.swift +++ b/swift4/Sources/types.swift @@ -1,10 +1,3 @@ -// -// types.swift -// swift4 -// -// Created by LuLouie on 2019/1/6. -// Copyright © 2019 llvm.xyz. All rights reserved. -// import Foundation From 2c1c26608512ea1ba058338b9eccc9448bcdffff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=86=E9=81=A5?= Date: Wed, 6 Mar 2019 09:35:31 +0800 Subject: [PATCH 0792/2308] Update readme and .travis.yml to add swift 4. --- .travis.yml | 2 ++ README.md | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 782480ee07..77a921a4da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -86,6 +86,8 @@ matrix: - {env: IMPL=swift NO_DOCKER=1, os: osx, osx_image: xcode7.3} - {env: IMPL=swift3, services: [docker]} - {env: IMPL=swift3 NO_DOCKER=1, os: osx, osx_image: xcode8} + - {env: IMPL=swift4, services: [docker]} + - {env: IMPL=swift4 NO_DOCKER=1, os: osx, osx_image: xcode10} - {env: IMPL=tcl, services: [docker]} - {env: IMPL=ts, services: [docker]} - {env: IMPL=vb, services: [docker]} diff --git a/README.md b/README.md index 0759b25c9f..9fc40d3c33 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ **1. Mal is a Clojure inspired Lisp interpreter** -**2. Mal is implemented in 74 languages** +**2. Mal is implemented in 75 languages** | Language | Creator | | -------- | ------- | @@ -77,6 +77,7 @@ | [Skew](#skew) | [Dov Murik](https://github.com/dubek) | | [Swift](#swift) | [Keith Rollin](https://github.com/keith-rollin) | | [Swift 3](#swift-3) | [Joel Martin](https://github.com/kanaka) | +| [Swift 4](#swift-4) | [陆é¥](https://github.com/LispLY) | | [Tcl](#tcl-86) | [Dov Murik](https://github.com/dubek) | | [TypeScript](#typescript) | [Masahiro Wakame](https://github.com/vvakame) | | [VHDL](#vhdl) | [Dov Murik](https://github.com/dubek) | @@ -930,6 +931,17 @@ make ./stepX_YYY ``` +### Swift 4 + +The Swift 4 implementation of mal requires the Swift 4.0 compiler. It +has been tested with Swift 4.2.3 release. + +``` +cd swift4 +make +./stepX_YYY +``` + ### Tcl 8.6 The Tcl implementation of mal requires Tcl 8.6 to run. For readline line From 56847c519cb43b72392bb241845e5ec37db5343d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=86=E9=81=A5?= Date: Wed, 6 Mar 2019 09:57:19 +0800 Subject: [PATCH 0793/2308] clean-up --- swift4/Sources/step5_tco/main.swift | 2 +- swift4/Sources/step6_file/main.swift | 2 +- swift4/Sources/step7_quote/main.swift | 2 +- swift4/Sources/step8_macros/main.swift | 2 +- swift4/Sources/step9_try/main.swift | 2 +- swift4/Sources/stepA_mal/main.swift | 3 +-- swift4/Sources/types.swift | 8 -------- 7 files changed, 6 insertions(+), 15 deletions(-) diff --git a/swift4/Sources/step5_tco/main.swift b/swift4/Sources/step5_tco/main.swift index cb9e99a95c..710e2e1a62 100644 --- a/swift4/Sources/step5_tco/main.swift +++ b/swift4/Sources/step5_tco/main.swift @@ -47,7 +47,7 @@ func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData { return try EVAL(list[2], env: newEnv) } return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn) - // fn 是å¦éœ€è¦å­˜å‚¨ï¼Ÿ + default: break } diff --git a/swift4/Sources/step6_file/main.swift b/swift4/Sources/step6_file/main.swift index 4d068c0c38..8ac611b84e 100644 --- a/swift4/Sources/step6_file/main.swift +++ b/swift4/Sources/step6_file/main.swift @@ -47,7 +47,7 @@ func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData { return try EVAL(list[2], env: newEnv) } return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn) - // fn 是å¦éœ€è¦å­˜å‚¨ï¼Ÿ + default: break } diff --git a/swift4/Sources/step7_quote/main.swift b/swift4/Sources/step7_quote/main.swift index e4370353be..ee8ad12cc3 100644 --- a/swift4/Sources/step7_quote/main.swift +++ b/swift4/Sources/step7_quote/main.swift @@ -59,7 +59,7 @@ func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData { ast = list[2] } continue - case "fn*": // fn 是å¦éœ€è¦å­˜å‚¨ï¼Ÿ + case "fn*": let fn = {(params: [MalData]) -> MalData in let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env) return try EVAL(list[2], env: newEnv) diff --git a/swift4/Sources/step8_macros/main.swift b/swift4/Sources/step8_macros/main.swift index b707784d9e..931d6d92c2 100644 --- a/swift4/Sources/step8_macros/main.swift +++ b/swift4/Sources/step8_macros/main.swift @@ -85,7 +85,7 @@ func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData { ast = list[2] } continue - case "fn*": // fn 是å¦éœ€è¦å­˜å‚¨ï¼Ÿ + case "fn*": let fn = {(params: [MalData]) -> MalData in let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env) return try EVAL(list[2], env: newEnv) diff --git a/swift4/Sources/step9_try/main.swift b/swift4/Sources/step9_try/main.swift index 8e0e4beac6..5c98419b71 100644 --- a/swift4/Sources/step9_try/main.swift +++ b/swift4/Sources/step9_try/main.swift @@ -85,7 +85,7 @@ func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData { ast = list[2] } continue - case "fn*": // fn 是å¦éœ€è¦å­˜å‚¨ï¼Ÿ + case "fn*": let fn = {(params: [MalData]) -> MalData in let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env) return try EVAL(list[2], env: newEnv) diff --git a/swift4/Sources/stepA_mal/main.swift b/swift4/Sources/stepA_mal/main.swift index 60524dac50..157750144f 100644 --- a/swift4/Sources/stepA_mal/main.swift +++ b/swift4/Sources/stepA_mal/main.swift @@ -85,7 +85,7 @@ func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData { ast = list[2] } continue - case "fn*": // fn 是å¦éœ€è¦å­˜å‚¨ï¼Ÿ + case "fn*": let fn = {(params: [MalData]) -> MalData in let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env) return try EVAL(list[2], env: newEnv) @@ -98,7 +98,6 @@ func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData { continue case "macroexpand": return try macroexpand(list[1], env: env) - // (try* A (catch* B C)) case "try*": do { return try EVAL(list[1], env: env) diff --git a/swift4/Sources/types.swift b/swift4/Sources/types.swift index 1706f475f2..0686b8dae4 100644 --- a/swift4/Sources/types.swift +++ b/swift4/Sources/types.swift @@ -23,8 +23,6 @@ typealias Number = Int typealias List = Array typealias Vector = ContiguousArray typealias HashMap = Dictionary -//typealias MalClosureThrows = ([MalData]) throws -> MalData -//typealias MalClosure = ([MalData]) -> MalData struct Symbol: MalData { let dataType = MalDataType.Symbol @@ -84,12 +82,6 @@ struct Function: MalData { } -//struct WithMeta: MalData { -// let dataType = MalDataType.WithMeta -// let data: MalData -// let meta: MalData -//} - extension String: MalData { var dataType: MalDataType { From cbbb51b465c06e3529b8efaa86c197f91507e2f2 Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Mon, 10 Oct 2016 19:08:59 +0200 Subject: [PATCH 0794/2308] Another Ada implementation. --- Makefile | 3 +- ada2/Makefile | 71 +++++ ada2/atoms.adb | 52 ++++ ada2/atoms.ads | 36 +++ ada2/core.adb | 581 ++++++++++++++++++++++++++++++++++++++ ada2/core.ads | 17 ++ ada2/environments.adb | 166 +++++++++++ ada2/environments.ads | 54 ++++ ada2/lists.adb | 95 +++++++ ada2/lists.ads | 47 +++ ada2/maps.adb | 160 +++++++++++ ada2/maps.ads | 59 ++++ ada2/names.ads | 87 ++++++ ada2/printer.adb | 177 ++++++++++++ ada2/printer.ads | 12 + ada2/reader.adb | 252 +++++++++++++++++ ada2/reader.ads | 12 + ada2/run | 2 + ada2/step0_repl.adb | 64 +++++ ada2/step1_read_print.adb | 75 +++++ ada2/step2_eval.adb | 175 ++++++++++++ ada2/step3_env.adb | 199 +++++++++++++ ada2/step4_if_fn_do.adb | 231 +++++++++++++++ ada2/step5_tco.adb | 234 +++++++++++++++ ada2/step6_file.adb | 260 +++++++++++++++++ ada2/step7_quote.adb | 326 +++++++++++++++++++++ ada2/step8_macros.adb | 383 +++++++++++++++++++++++++ ada2/step9_try.adb | 411 +++++++++++++++++++++++++++ ada2/stepa_mal.adb | 417 +++++++++++++++++++++++++++ ada2/strings.adb | 62 ++++ ada2/strings.ads | 65 +++++ ada2/types.adb | 37 +++ ada2/types.ads | 59 ++++ 33 files changed, 4880 insertions(+), 1 deletion(-) create mode 100644 ada2/Makefile create mode 100644 ada2/atoms.adb create mode 100644 ada2/atoms.ads create mode 100644 ada2/core.adb create mode 100644 ada2/core.ads create mode 100644 ada2/environments.adb create mode 100644 ada2/environments.ads create mode 100644 ada2/lists.adb create mode 100644 ada2/lists.ads create mode 100644 ada2/maps.adb create mode 100644 ada2/maps.ads create mode 100644 ada2/names.ads create mode 100644 ada2/printer.adb create mode 100644 ada2/printer.ads create mode 100644 ada2/reader.adb create mode 100644 ada2/reader.ads create mode 100755 ada2/run create mode 100644 ada2/step0_repl.adb create mode 100644 ada2/step1_read_print.adb create mode 100644 ada2/step2_eval.adb create mode 100644 ada2/step3_env.adb create mode 100644 ada2/step4_if_fn_do.adb create mode 100644 ada2/step5_tco.adb create mode 100644 ada2/step6_file.adb create mode 100644 ada2/step7_quote.adb create mode 100644 ada2/step8_macros.adb create mode 100644 ada2/step9_try.adb create mode 100644 ada2/stepa_mal.adb create mode 100644 ada2/strings.adb create mode 100644 ada2/strings.ads create mode 100644 ada2/types.adb create mode 100644 ada2/types.ads diff --git a/Makefile b/Makefile index 9843ed9c3f..d80bd7d2c6 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ DOCKERIZE = # Implementation specific settings # -IMPLS = ada awk bash basic c chuck clojure coffee common-lisp cpp crystal cs d dart \ +IMPLS = ada ada2 awk bash basic c chuck clojure coffee common-lisp cpp crystal cs d dart \ elisp elixir elm erlang es6 factor fantom forth fsharp go groovy gnu-smalltalk \ guile haskell haxe hy io java js julia kotlin livescript logo lua make mal \ matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp plpgsql \ @@ -173,6 +173,7 @@ scheme_STEP_TO_PROG_foment = scheme/$($(1)).scm # Map of step (e.g. "step8") to executable file for that step ada_STEP_TO_PROG = ada/$($(1)) +ada2_STEP_TO_PROG = ada2/$($(1)) awk_STEP_TO_PROG = awk/$($(1)).awk bash_STEP_TO_PROG = bash/$($(1)).sh basic_STEP_TO_PROG = $(basic_STEP_TO_PROG_$(basic_MODE)) diff --git a/ada2/Makefile b/ada2/Makefile new file mode 100644 index 0000000000..bb58c83206 --- /dev/null +++ b/ada2/Makefile @@ -0,0 +1,71 @@ +# Variables expected on the command line: +OPT := -O2 +GNATN := -gnatn +GNATP := -gnatp +ADAFLAGS := +LDFLAGS := +DEBUG := + +ifdef DEBUG + # Some warnings require -O1. + OPT := -O1 + GNATN := + GNATP := + ADAFLAGS := -Wall -Wextra -gnatw.eH.Y -gnatySdouxy -gnatVa -g -gnataEfoqQ \ + -fstack-check -pg + LDFLAGS := -pg +endif + +# Compiler arguments. +CARGS = -gnat2012 $(OPT) $(GNATN) $(GNATP) $(ADAFLAGS) +# Linker arguments. +LARGS = $(LDFLAGS) -lreadline + +step0 := step0_repl +step13 := step1_read_print \ + step2_eval \ + step3_env +step49 := step4_if_fn_do \ + step5_tco \ + step6_file \ + step7_quote \ + step8_macros \ + step9_try +stepa := stepA_mal +steps := $(step0) $(step13) $(step49) $(stepa) + +.PHONY: all clean +all: $(steps) +clean: + $(RM) *~ *.ali *.o b~*.ad[bs] gmon.out $(steps) + +# Tell Make how to detect out-of-date executables, and let gnatmake do +# the rest when it must be executed. +TYPES := \ + atoms.ads atoms.adb \ + environments.ads environments.adb \ + lists.ads lists.adb \ + maps.ads maps.adb \ + names.ads \ + printer.ads printer.adb \ + reader.ads reader.adb \ + types.ads types.adb \ + strings.ads strings.adb +CORE := \ + core.ads core.adb + +$(step0) : %: %.adb +$(step13): %: %.adb $(TYPES) +$(step49): %: %.adb $(TYPES) $(CORE) +$(stepa) : stepA%: stepa%.adb $(TYPES) $(CORE) +$(steps) : + gnatmake $< -o $@ -cargs $(CARGS) -largs $(LARGS) + +# Step 8 freezes during the "(or)" test with -gnatp. +step8%: GNATP := + +# The compiler crashes on types.adb with -gnatn. +$(step13) $(step49) $(stepa): types.o +types.o: GNATN := +types.o: $(TYPES) + gcc -c $(CARGS) types.adb diff --git a/ada2/atoms.adb b/ada2/atoms.adb new file mode 100644 index 0000000000..24650a04d1 --- /dev/null +++ b/ada2/atoms.adb @@ -0,0 +1,52 @@ +with Ada.Unchecked_Deallocation; +with Types; + +package body Atoms is + + type Atom_Record is limited record + Data : Types.Mal_Type; + Refs : Positive; + end record; + + procedure Free is new Ada.Unchecked_Deallocation (Object => Atom_Record, + Name => Atom_Access); + + ---------------------------------------------------------------------- + + procedure Adjust (Object : in out Ptr) is + begin + if Object.Ref /= null then + Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + end if; + end Adjust; + + function Alloc (New_Value : in Types.Mal_Type) return Ptr + is (Ada.Finalization.Controlled with + Ref => new Atom_Record'(Data => New_Value, + Refs => 1)); + + function Deref (Container : in Ptr) return Types.Mal_Type is + (Container.Ref.all.Data); + + procedure Finalize (Object : in out Ptr) + is + Refs : Positive; + begin + if Object.Ref /= null then + Refs := Object.Ref.all.Refs; + if 1 < Refs then + Object.Ref.all.Refs := Refs - 1; + Object.Ref := null; + else + Free (Object.Ref); + end if; + end if; + end Finalize; + + procedure Set (Container : in Ptr; + New_Value : in Types.Mal_Type) is + begin + Container.Ref.all.Data := New_Value; + end Set; + +end Atoms; diff --git a/ada2/atoms.ads b/ada2/atoms.ads new file mode 100644 index 0000000000..3fa5aae990 --- /dev/null +++ b/ada2/atoms.ads @@ -0,0 +1,36 @@ +private with Ada.Finalization; +limited with Types; + +package Atoms is + + -- Equivalent to a Lists.Ptr with zero or one elements. + + type Ptr is tagged private; + No_Element : constant Ptr; + + function Alloc (New_Value : in Types.Mal_Type) return Ptr + with Inline; + + function Deref (Container : in Ptr) return Types.Mal_Type + with Inline, Pre => Container /= No_Element; + + procedure Set (Container : in Ptr; + New_Value : in Types.Mal_Type) + with Inline, Pre => Container /= No_Element; + +private + + type Atom_Record; + type Atom_Access is access Atom_Record; + type Ptr is new Ada.Finalization.Controlled with record + Ref : Atom_Access := null; + end record; + overriding procedure Adjust (Object : in out Ptr) + with Inline; + overriding procedure Finalize (Object : in out Ptr) + with Inline; + -- Predefined equality is fine. + + No_Element : constant Ptr := (Ada.Finalization.Controlled with Ref => null); + +end Atoms; diff --git a/ada2/core.adb b/ada2/core.adb new file mode 100644 index 0000000000..aa8be989a0 --- /dev/null +++ b/ada2/core.adb @@ -0,0 +1,581 @@ +with Ada.Calendar; use type Ada.Calendar.Time; +with Ada.Characters.Latin_1; +with Ada.Strings.Unbounded; +with Ada.Text_IO.Unbounded_IO; +with Atoms; use type Atoms.Ptr; +with Lists; +with Maps; +with Names; +with Printer; +with Reader; +with Strings; use type Strings.Ptr; + +package body Core is + + use Types; + + Start_Time : constant Ada.Calendar.Time := Ada.Calendar.Clock; + + Eval : Eval_Callback_Type; + + function Concatenation_Of_Pr_Str + (Args : in Mal_Type_Array; + Print_Readably : in Boolean := True; + Separator : in String := " ") + return Ada.Strings.Unbounded.Unbounded_String; + + function Apply (Args : in Mal_Type_Array) return Mal_Type; + function Assoc (Args : in Mal_Type_Array) return Mal_Type; + function Atom (Args : in Mal_Type_Array) return Mal_Type; + function Concat (Args : in Mal_Type_Array) return Mal_Type; + function Conj (Args : in Mal_Type_Array) return Mal_Type; + function Cons (Args : in Mal_Type_Array) return Mal_Type; + function Contains (Args : in Mal_Type_Array) return Mal_Type; + function Count (Args : in Mal_Type_Array) return Mal_Type; + function Deref (Args : in Mal_Type_Array) return Mal_Type; + function Dissoc (Args : in Mal_Type_Array) return Mal_Type; + function Equals (Args : in Mal_Type_Array) return Mal_Type; + function First (Args : in Mal_Type_Array) return Mal_Type; + function Get (Args : in Mal_Type_Array) return Mal_Type; + function Hash_Map (Args : in Mal_Type_Array) return Mal_Type; + function Is_Empty (Args : in Mal_Type_Array) return Mal_Type; + function Is_False (Args : in Mal_Type_Array) return Mal_Type; + function Is_Sequential (Args : in Mal_Type_Array) return Mal_Type; + function Is_True (Args : in Mal_Type_Array) return Mal_Type; + function Keys (Args : in Mal_Type_Array) return Mal_Type; + function Keyword (Args : in Mal_Type_Array) return Mal_Type; + function List (Args : in Mal_Type_Array) return Mal_Type; + function Map (Args : in Mal_Type_Array) return Mal_Type; + function Meta (Args : in Mal_Type_Array) return Mal_Type; + function Nth (Args : in Mal_Type_Array) return Mal_Type; + function Pr_Str (Args : in Mal_Type_Array) return Mal_Type; + function Println (Args : in Mal_Type_Array) return Mal_Type; + function Prn (Args : in Mal_Type_Array) return Mal_Type; + function Read_String (Args : in Mal_Type_Array) return Mal_Type; + function Readline (Args : in Mal_Type_Array) return Mal_Type; + function Reset (Args : in Mal_Type_Array) return Mal_Type; + function Rest (Args : in Mal_Type_Array) return Mal_Type; + function Seq (Args : in Mal_Type_Array) return Mal_Type; + function Slurp (Args : in Mal_Type_Array) return Mal_Type; + function Str (Args : in Mal_Type_Array) return Mal_Type; + function Swap (Args : in Mal_Type_Array) return Mal_Type; + function Symbol (Args : in Mal_Type_Array) return Mal_Type; + function Throw (Args : in Mal_Type_Array) return Mal_Type; + function Time_Ms (Args : in Mal_Type_Array) return Mal_Type; + function Vals (Args : in Mal_Type_Array) return Mal_Type; + function Vector (Args : in Mal_Type_Array) return Mal_Type; + function With_Meta (Args : in Mal_Type_Array) return Mal_Type; + + generic + with function Ada_Operator (Left, Right : in Integer) return Integer; + function Generic_Mal_Operator (Args : in Mal_Type_Array) return Mal_Type; + function Generic_Mal_Operator (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Number, Atoms.No_Element, + Ada_Operator (Args (Args'First).Integer_Value, + Args (Args'First + 1).Integer_Value)); + function Addition is new Generic_Mal_Operator ("+"); + function Subtraction is new Generic_Mal_Operator ("-"); + function Product is new Generic_Mal_Operator ("*"); + function Division is new Generic_Mal_Operator ("/"); + + generic + with function Ada_Operator (Left, Right : in Integer) return Boolean; + function Generic_Comparison (Args : in Mal_Type_Array) return Mal_Type; + function Generic_Comparison (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Boolean, Atoms.No_Element, + Ada_Operator (Args (Args'First).Integer_Value, + Args (Args'First + 1).Integer_Value)); + function Greater_Than is new Generic_Comparison (">"); + function Greater_Equal is new Generic_Comparison (">="); + function Less_Than is new Generic_Comparison ("<"); + function Less_Equal is new Generic_Comparison ("<="); + + generic + Kind : Kind_Type; + function Generic_Kind_Test (Args : in Mal_Type_Array) return Mal_Type; + function Generic_Kind_Test (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Boolean, Atoms.No_Element, Args (Args'First).Kind = Kind); + function Is_Atom is new Generic_Kind_Test (Kind_Atom); + function Is_Keyword is new Generic_Kind_Test (Kind_Keyword); + function Is_List is new Generic_Kind_Test (Kind_List); + function Is_Map is new Generic_Kind_Test (Kind_Map); + function Is_Nil is new Generic_Kind_Test (Kind_Nil); + function Is_String is new Generic_Kind_Test (Kind_String); + function Is_Symbol is new Generic_Kind_Test (Kind_Symbol); + function Is_Vector is new Generic_Kind_Test (Kind_Vector); + + ---------------------------------------------------------------------- + + procedure Add_Built_In_Functions + (Repl : in Environments.Ptr; + Eval_Callback : in not null Eval_Callback_Type) + is + function N (N : in Native_Function_Access) return Mal_Type + is (Kind_Native, Atoms.No_Element, N) with Inline; + begin + Eval := Eval_Callback; + + Repl.Increase_Capacity (57); + + Repl.Set (Names.Apply, N (Apply'Access)); + Repl.Set (Names.Assoc, N (Assoc'Access)); + Repl.Set (Names.Asterisk, N (Product'Access)); + Repl.Set (Names.Atom, N (Atom'Access)); + Repl.Set (Names.Concat, N (Concat'Access)); + Repl.Set (Names.Conj, N (Conj'Access)); + Repl.Set (Names.Cons, N (Cons'Access)); + Repl.Set (Names.Contains, N (Contains'Access)); + Repl.Set (Names.Count, N (Count'Access)); + Repl.Set (Names.Deref, N (Deref'Access)); + Repl.Set (Names.Dissoc, N (Dissoc'Access)); + Repl.Set (Names.Equals, N (Equals'Access)); + Repl.Set (Names.First, N (First'Access)); + Repl.Set (Names.Get, N (Get'Access)); + Repl.Set (Names.Greater_Equal, N (Greater_Equal'Access)); + Repl.Set (Names.Greater_Than, N (Greater_Than'Access)); + Repl.Set (Names.Hash_Map, N (Hash_Map'Access)); + Repl.Set (Names.Is_Atom, N (Is_Atom'Access)); + Repl.Set (Names.Is_Empty, N (Is_Empty'Access)); + Repl.Set (Names.Is_False, N (Is_False'Access)); + Repl.Set (Names.Is_Keyword, N (Is_Keyword'Access)); + Repl.Set (Names.Is_List, N (Is_List'Access)); + Repl.Set (Names.Is_Map, N (Is_Map'Access)); + Repl.Set (Names.Is_Nil, N (Is_Nil'Access)); + Repl.Set (Names.Is_Sequential, N (Is_Sequential'Access)); + Repl.Set (Names.Is_String, N (Is_String'Access)); + Repl.Set (Names.Is_Symbol, N (Is_Symbol'Access)); + Repl.Set (Names.Is_True, N (Is_True'Access)); + Repl.Set (Names.Is_Vector, N (Is_Vector'Access)); + Repl.Set (Names.Keys, N (Keys'Access)); + Repl.Set (Names.Keyword, N (Keyword'Access)); + Repl.Set (Names.Less_Equal, N (Less_Equal'Access)); + Repl.Set (Names.Less_Than, N (Less_Than'Access)); + Repl.Set (Names.List, N (List'Access)); + Repl.Set (Names.Map, N (Map'Access)); + Repl.Set (Names.Meta, N (Meta'Access)); + Repl.Set (Names.Minus, N (Subtraction'Access)); + Repl.Set (Names.Nth, N (Nth'Access)); + Repl.Set (Names.Plus, N (Addition'Access)); + Repl.Set (Names.Pr_Str, N (Pr_Str'Access)); + Repl.Set (Names.Println, N (Println'Access)); + Repl.Set (Names.Prn, N (Prn'Access)); + Repl.Set (Names.Read_String, N (Read_String'Access)); + Repl.Set (Names.Readline, N (Readline'Access)); + Repl.Set (Names.Reset, N (Reset'Access)); + Repl.Set (Names.Rest, N (Rest'Access)); + Repl.Set (Names.Seq, N (Seq'Access)); + Repl.Set (Names.Slash, N (Division'Access)); + Repl.Set (Names.Slurp, N (Slurp'Access)); + Repl.Set (Names.Str, N (Str'Access)); + Repl.Set (Names.Swap, N (Swap'Access)); + Repl.Set (Names.Symbol, N (Symbol'Access)); + Repl.Set (Names.Throw, N (Throw'Access)); + Repl.Set (Names.Time_Ms, N (Time_Ms'Access)); + Repl.Set (Names.Vals, N (Vals'Access)); + Repl.Set (Names.Vector, N (Vector'Access)); + Repl.Set (Names.With_Meta, N (With_Meta'Access)); + end Add_Built_In_Functions; + + function Apply (Args : in Mal_Type_Array) return Mal_Type + is + Func : Mal_Type renames Args (Args'First); + List : Lists.Ptr renames Args (Args'Last).L; + Actuals : Mal_Type_Array (1 .. Args'Length - 2 + List.Length); + begin + Actuals (1 .. Args'Length - 2) := Args (Args'First + 1 .. Args'Last - 1); + for I in 1 .. List.Length loop + Actuals (Args'Length - 2 + I) := List.Element (I); + end loop; + if Func.Kind = Kind_Native then + return Func.Native.all (Actuals); + else + declare + Env : constant Environments.Ptr + := Environments.Alloc (Outer => Func.Environment); + begin + Env.Set_Binds (Func.Formals, Actuals); + return Eval.all (Func.Expression.Deref, Env); + end; + end if; + end Apply; + + function Assoc (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Map, Atoms.No_Element, + Args (Args'First).Map.Assoc (Args (Args'First + 1 .. Args'Last))); + + function Atom (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Atom, Atoms.No_Element, Atoms.Alloc (Args (Args'First))); + + function Concat (Args : in Mal_Type_Array) return Mal_Type + is + L : array (Args'Range) of Lists.Ptr; + Sum : Natural := 0; + Result : Lists.Ptr; + begin + for I in Args'Range loop + L (I) := Args (I).L; + Sum := Sum + L (I).Length; + end loop; + Result := Lists.Alloc (Sum); + Sum := 0; + for LI of L loop + for J in 1 .. LI.Length loop + Sum := Sum + 1; + Result.Replace_Element (Sum, LI.Element (J)); + end loop; + end loop; + return (Kind_List, Atoms.No_Element, Result); + end Concat; + + function Concatenation_Of_Pr_Str + (Args : in Mal_Type_Array; + Print_Readably : in Boolean := True; + Separator : in String := " ") + return Ada.Strings.Unbounded.Unbounded_String + is + use Ada.Strings.Unbounded; + Result : Unbounded_String; + begin + if 1 <= Args'Length then + Append (Result, Printer.Pr_Str (Args (Args'First), Print_Readably)); + for I in Args'First + 1 .. Args'Last loop + Append (Result, Separator); + Append (Result, Printer.Pr_Str (Args (I), Print_Readably)); + end loop; + end if; + return Result; + end Concatenation_Of_Pr_Str; + + function Conj (Args : in Mal_Type_Array) return Mal_Type + is + List : Lists.Ptr renames Args (Args'First).L; + Result : constant Lists.Ptr + := Lists.Alloc (List.Length + Args'Length - 1); + begin + if Args (Args'First).Kind = Kind_List then + for I in Args'First + 1 .. Args'Last loop + Result.Replace_Element (Args'Last + 1 - I, Args (I)); + end loop; + for I in 1 .. List.Length loop + Result.Replace_Element (Args'Length + I - 1, List.Element (I)); + end loop; + return (Kind_List, Atoms.No_Element, Result); + else + for I in 1 .. Args'Length - 1 loop + Result.Replace_Element (List.Length + I, Args (Args'First + I)); + end loop; + for I in 1 .. List.Length loop + Result.Replace_Element (I, List.Element (I)); + end loop; + return (Kind_Vector, Atoms.No_Element, Result); + end if; + end Conj; + + function Cons (Args : in Mal_Type_Array) return Mal_Type + is + List : Lists.Ptr renames Args (Args'First + 1).L; + Result : constant Lists.Ptr := Lists.Alloc (1 + List.Length); + begin + Result.Replace_Element (1, Args (Args'First)); + for I in 1 .. List.Length loop + Result.Replace_Element (I + 1, List.Element (I)); + end loop; + return (Kind_List, Atoms.No_Element, Result); + end Cons; + + function Contains (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Boolean, Atoms.No_Element, + Args (Args'First).Map.Contains (Args (Args'First + 1))); + + function Count (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Number, Atoms.No_Element, + (if Args (Args'First).Kind = Kind_Nil + then 0 + else Args (Args'First).L.Length)); + + function Deref (Args : in Mal_Type_Array) return Mal_Type + is (Args (Args'First).Reference.Deref); + + function Dissoc (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Map, Atoms.No_Element, + Args (Args'First).Map.Dissoc (Args (Args'First + 1 .. Args'Last))); + + function Equals (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Boolean, Atoms.No_Element, + Args (Args'First) = Args (Args'First + 1)); + + function First (Args : in Mal_Type_Array) return Mal_Type + is (if Args (Args'First).Kind = Kind_Nil + or else Args (Args'First).L.Length = 0 + then (Kind_Nil, Atoms.No_Element) + else Args (Args'First).L.Element (1)); + + function Get (Args : in Mal_Type_Array) return Mal_Type is + begin + if Args (Args'First).Kind = Kind_Nil then + return (Kind_Nil, Atoms.No_Element); + else + return Args (Args'First).Map.Get (Args (Args'First + 1)); + end if; + exception + when Maps.Unknown_Key => + return (Kind_Nil, Atoms.No_Element); + end Get; + + function Hash_Map (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Map, Atoms.No_Element, Maps.Hash_Map (Args)); + + function Is_Empty (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Boolean, Atoms.No_Element, Args (Args'First).L.Length = 0); + + function Is_False (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Boolean, Atoms.No_Element, + Args (Args'First).Kind = Kind_Boolean + and then not Args (Args'First).Boolean_Value); + + function Is_True (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Boolean, Atoms.No_Element, + Args (Args'First).Kind = Kind_Boolean + and then Args (Args'First).Boolean_Value); + + function Is_Sequential (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Boolean, Atoms.No_Element, + Args (Args'First).Kind in Kind_List | Kind_Vector); + + function Keyword (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Keyword, Atoms.No_Element, Args (Args'First).S); + + function Keys (Args : in Mal_Type_Array) return Mal_Type + is + M : Maps.Ptr renames Args (Args'First).Map; + Result : constant Mal_Type := (Kind_List, Atoms.No_Element, + Lists.Alloc (M.Length)); + I : Natural := 0; + procedure Process (Key, Element : in Mal_Type); + procedure Process (Key, Element : in Mal_Type) is + begin + I := I + 1; + Result.L.Replace_Element (I, Key); + pragma Unreferenced (Element); + end Process; + begin + M.Iterate (Process'Access); + return Result; + end Keys; + + function List (Args : in Mal_Type_Array) return Mal_Type + is (Kind_List, Atoms.No_Element, Lists.Alloc (Args)); + + function Map (Args : in Mal_Type_Array) return Mal_Type + is + Func : Mal_Type renames Args (Args'First); + List : Lists.Ptr renames Args (Args'First + 1).L; + Actuals : Mal_Type_Array (1 .. 1); + Result : constant Lists.Ptr := Lists.Alloc (List.Length); + begin + for I in 1 .. List.Length loop + Actuals (1) := List.Element (I); + if Func.Kind = Kind_Native then + Result.Replace_Element (I, Func.Native.all (Actuals)); + else + declare + Env : constant Environments.Ptr + := Environments.Alloc (Func.Environment); + begin + Env.Set_Binds (Func.Formals, Actuals); + Result.Replace_Element (I, Eval.all (Func.Expression.Deref, + Env)); + end; + end if; + end loop; + return (Kind_List, Atoms.No_Element, Result); + end Map; + + function Meta (Args : in Mal_Type_Array) return Mal_Type + is (if Args (Args'First).Meta = Atoms.No_Element + then (Kind_Nil, Atoms.No_Element) + else Args (Args'First).Meta.Deref); + + function Nth (Args : in Mal_Type_Array) return Mal_Type + is (Args (Args'First).L.Element (1 + Args (Args'First + 1).Integer_Value)); + + function Pr_Str (Args : in Mal_Type_Array) return Mal_Type + is (Kind_String, Atoms.No_Element, Strings.Alloc + (Ada.Strings.Unbounded.To_String (Concatenation_Of_Pr_Str (Args)))); + + function Println (Args : in Mal_Type_Array) return Mal_Type is + begin + Ada.Text_IO.Unbounded_IO.Put_Line (Concatenation_Of_Pr_Str + (Args, Print_Readably => False)); + return (Kind_Nil, Atoms.No_Element); + end Println; + + function Prn (Args : in Mal_Type_Array) return Mal_Type is + begin + Ada.Text_IO.Unbounded_IO.Put_Line (Concatenation_Of_Pr_Str (Args)); + return (Kind_Nil, Atoms.No_Element); + end Prn; + + function Readline (Args : in Mal_Type_Array) return Mal_Type is + begin + Ada.Text_IO.Put (Args (Args'First).S.Deref); + return (Kind_String, Atoms.No_Element, + Strings.Alloc (Ada.Text_IO.Get_Line)); + exception + when Ada.Text_IO.End_Error => + return (Kind_Nil, Atoms.No_Element); + end Readline; + + function Read_String (Args : in Mal_Type_Array) return Mal_Type + is (Reader.Read_Str (Args (Args'First).S.Deref)); + + function Reset (Args : in Mal_Type_Array) return Mal_Type is + begin + Args (Args'First).Reference.Set (Args (Args'Last)); + return Args (Args'Last); + end Reset; + + function Rest (Args : in Mal_Type_Array) return Mal_Type + is + List : Mal_Type renames Args (Args'First); + Len : Natural; + begin + return Result : Mal_Type (Kind_List) do + if List.Kind /= Kind_Nil then + Len := List.L.Length; + if 0 < Len then + Len := Len - 1; + Result.L := Lists.Alloc (Len); + for I in 1 .. Len loop + Result.L.Replace_Element (I, List.L.Element (I + 1)); + end loop; + end if; + end if; + end return; + end Rest; + + function Seq (Args : in Mal_Type_Array) return Mal_Type is + begin + if Args (Args'First).Kind = Kind_String then + declare + S : constant String := Args (Args'First).S.Deref; + Result : Lists.Ptr; + begin + if S'Length = 0 then + return (Kind_Nil, Atoms.No_Element); + else + Result := Lists.Alloc (S'Length); + for I in S'Range loop + Result.Replace_Element (I - S'First + 1, Mal_Type' + (Kind_String, Atoms.No_Element, + Strings.Alloc (S (I .. I)))); + end loop; + return (Kind_List, Atoms.No_Element, Result); + end if; + end; + elsif Args (Args'First).Kind = Kind_Nil + or else Args (Args'First).L.Length = 0 + then + return (Kind_Nil, Atoms.No_Element); + else + return (Kind_List, Atoms.No_Element, Args (Args'First).L); + end if; + end Seq; + + function Slurp (Args : in Mal_Type_Array) return Mal_Type + is + use Ada.Strings.Unbounded; + use Ada.Text_IO; + File : File_Type; + Buffer : Unbounded_String; + begin + Open (File, In_File, Args (Args'First).S.Deref); + while not End_Of_File (File) loop + Append (Buffer, Get_Line (File)); + Append (Buffer, Ada.Characters.Latin_1.LF); + end loop; + Close (File); + return (Kind_String, Atoms.No_Element, + Strings.Alloc (To_String (Buffer))); + exception + when others => + Close (File); + raise; + end Slurp; + + function Str (Args : in Mal_Type_Array) return Mal_Type + is (Kind_String, Atoms.No_Element, Strings.Alloc + (Ada.Strings.Unbounded.To_String + (Concatenation_Of_Pr_Str (Args, + Print_Readably => False, + Separator => "")))); + + function Swap (Args : in Mal_Type_Array) return Mal_Type + is + Atom : Mal_Type renames Args (Args'First); + Func : Mal_Type renames Args (Args'First + 1); + Actuals : Mal_Type_Array (Args'First + 1 .. Args'Last); + Result : Mal_Type; + begin + Actuals (Actuals'First) := Atom.Reference.Deref; + for I in Actuals'First + 1 .. Args'Last loop + Actuals (I) := Args (I); + end loop; + if Func.Kind = Kind_Native then + Result := Func.Native.all (Actuals); + else + declare + Env : constant Environments.Ptr + := Environments.Alloc (Outer => Func.Environment); + begin + Env.Set_Binds (Func.Formals, Actuals); + Result := Eval.all (Func.Expression.Deref, Env); + end; + end if; + Atom.Reference.Set (Result); + return Result; + end Swap; + + function Symbol (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Symbol, Atoms.No_Element, Args (Args'First).S); + + function Throw (Args : in Mal_Type_Array) return Mal_Type is + begin + Last_Exception := Args (Args'First); + raise Exception_Throwed; + return (Kind_Nil, Atoms.No_Element); -- GNAT wants a return. + end Throw; + + function Time_Ms (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Number, Atoms.No_Element, + Integer (1000.0 * (Ada.Calendar.Clock - Start_Time))); + + function Vals (Args : in Mal_Type_Array) return Mal_Type + is + M : Maps.Ptr renames Args (Args'First).Map; + Result : constant Mal_Type := (Kind_List, Atoms.No_Element, + Lists.Alloc (M.Length)); + I : Natural := 0; + procedure Process (Key, Element : in Mal_Type); + procedure Process (Key, Element : in Mal_Type) is + begin + I := I + 1; + Result.L.Replace_Element (I, Element); + pragma Unreferenced (Key); + end Process; + begin + M.Iterate (Process'Access); + return Result; + end Vals; + + function Vector (Args : in Mal_Type_Array) return Mal_Type + is (Kind_Vector, Atoms.No_Element, Lists.Alloc (Args)); + + function With_Meta (Args : in Mal_Type_Array) return Mal_Type is + begin + return Result : Mal_Type := Args (Args'First) do + Result.Meta := Atoms.Alloc (Args (Args'First + 1)); + end return; + end With_Meta; + +end Core; diff --git a/ada2/core.ads b/ada2/core.ads new file mode 100644 index 0000000000..89d5414cc8 --- /dev/null +++ b/ada2/core.ads @@ -0,0 +1,17 @@ +with Environments; +with Types; pragma Elaborate_All (Types); + +package Core is + + type Eval_Callback_Type is access + function (Ast : in Types.Mal_Type; + Env : in Environments.Ptr) return Types.Mal_Type; + + procedure Add_Built_In_Functions + (Repl : in Environments.Ptr; + Eval_Callback : in not null Eval_Callback_Type); + + Exception_Throwed : exception; + Last_Exception : Types.Mal_Type; + +end Core; diff --git a/ada2/environments.adb b/ada2/environments.adb new file mode 100644 index 0000000000..79c09bcd7f --- /dev/null +++ b/ada2/environments.adb @@ -0,0 +1,166 @@ +with Ada.Containers.Hashed_Maps; use type Ada.Containers.Count_Type; +with Ada.Unchecked_Deallocation; +with Atoms; +with Names; +with Strings; use type Strings.Ptr; +with Types; use type Types.Kind_Type; + +package body Environments is + + -- There must be a reference level so that functions may keep + -- track of their initial environment, and another one for + -- reallocations. + + package Maps is new Ada.Containers.Hashed_Maps + (Key_Type => Strings.Ptr, + Element_Type => Types.Mal_Type, + Hash => Strings.Hash, + Equivalent_Keys => Strings."=", + "=" => Types."="); + + type Env_Record is limited record + Data : Maps.Map; + Outer : Env_Access; + Refs : Positive; + end record; + + procedure Free is new Ada.Unchecked_Deallocation (Object => Env_Record, + Name => Env_Access); + + ---------------------------------------------------------------------- + + procedure Adjust (Object : in out Ptr) is + begin + Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + end Adjust; + + function Alloc return Ptr + is (Ada.Finalization.Controlled with + Ref => new Env_Record'(Data => Maps.Empty_Map, + Outer => null, + Refs => 1)); + + function Alloc (Outer : in Ptr) return Ptr is + begin + Outer.Ref.all.Refs := Outer.Ref.all.Refs + 1; + return (Ada.Finalization.Controlled with + Ref => new Env_Record'(Data => Maps.Empty_Map, + Outer => Outer.Ref, + Refs => 1)); + end Alloc; + + procedure Finalize (Object : in out Ptr) + is + Ref : Env_Access; + Refs : Positive; + begin + if Object.Ref /= null then + Ref := Object.Ref; + Object.Ref := null; + loop + Refs := Ref.all.Refs; + if 1 < Refs then + Ref.all.Refs := Refs - 1; + exit; + end if; + declare + Tmp : Env_Access := Ref; + begin + Ref := Ref.all.Outer; + Free (Tmp); + end; + exit when Ref = null; + end loop; + end if; + end Finalize; + + function Get (Container : in Ptr; + Key : in Strings.Ptr) return Types.Mal_Type + is + Ref : Env_Access := Container.Ref; + Position : Maps.Cursor; + begin + loop + Position := Ref.all.Data.Find (Key); + if Maps.Has_Element (Position) then + return Ref.all.Data (Position); + end if; + Ref := Ref.all.Outer; + exit when Ref = null; + end loop; + raise Unknown_Key with "'" & Key.Deref & "' not found"; + end Get; + + procedure Increase_Capacity (Container : in Ptr; + Capacity : in Natural) + is + New_Capacity : constant Ada.Containers.Count_Type + := Container.Ref.all.Data.Length + + Ada.Containers.Count_Type (Capacity); + begin + if Container.Ref.all.Data.Capacity < New_Capacity then + Container.Ref.all.Data.Reserve_Capacity (New_Capacity); + end if; + end Increase_Capacity; + + procedure Replace_With_Subenv (Item : in out Ptr) is + begin + if 1 < Item.Ref.all.Refs then + Item.Ref := new Env_Record'(Data => Maps.Empty_Map, + Outer => Item.Ref, + Refs => 1); + end if; + end Replace_With_Subenv; + + procedure Set (Container : in Ptr; + Key : in Strings.Ptr; + New_Item : in Types.Mal_Type) is + begin + Container.Ref.all.Data.Include (Key, New_Item); + end Set; + + procedure Set_Binds (Container : in Ptr; + Formals : in Lists.Ptr; + Actuals : in Types.Mal_Type_Array) + is + -- The assertions should be a precondition, but cannot be + -- expressed with a "limited with" view on Types. + begin + if Formals.Length <= 1 + or else Formals.Element (Formals.Length - 1).S /= Names.Ampersand + then + pragma Assert (Formals.Length = Actuals'Length); + pragma Assert (for all I in 1 .. Formals.Length => + Formals.Element (I).Kind = Types.Kind_Symbol + and then Formals.Element (I).S /= Names.Ampersand); + Increase_Capacity (Container, Formals.Length); + for I in 1 .. Formals.Length loop + Container.Ref.all.Data.Include (Formals.Element (I).S, + Actuals (Actuals'First + I - 1)); + end loop; + else + declare + Len : constant Natural := Formals.Length - 2; + begin + pragma Assert (Len <= Actuals'Length); + pragma Assert (for all I in 1 .. Len => + Formals.Element (I).Kind = Types.Kind_Symbol + and then Formals.Element (I).S /= Names.Ampersand); + pragma Assert (Formals.Element (Len + 1).Kind = Types.Kind_Symbol); + pragma Assert (Formals.Element (Len + 1).S = Names.Ampersand); + pragma Assert (Formals.Element (Len + 2).Kind = Types.Kind_Symbol); + pragma Assert (Formals.Element (Len + 2).S /= Names.Ampersand); + Increase_Capacity (Container, Len + 1); + for I in 1 .. Len loop + Container.Ref.all.Data.Include + (Formals.Element (I).S, Actuals (Actuals'First + I - 1)); + end loop; + Container.Ref.all.Data.Include + (Formals.Element (Formals.Length).S, + (Types.Kind_List, Atoms.No_Element, + Lists.Alloc (Actuals (Actuals'First + Len .. Actuals'Last)))); + end; + end if; + end Set_Binds; + +end Environments; diff --git a/ada2/environments.ads b/ada2/environments.ads new file mode 100644 index 0000000000..c1584be4b9 --- /dev/null +++ b/ada2/environments.ads @@ -0,0 +1,54 @@ +private with Ada.Finalization; +with Lists; +with Strings; +limited with Types; + +package Environments is + + type Ptr is tagged private; + -- Any variable must be assigned immediately with one of the two + -- following functions. + function Alloc return Ptr + with Inline; + function Alloc (Outer : in Ptr) return Ptr + with Inline; + -- A hidden invariant ensures this when assertions are enabled. + + procedure Increase_Capacity (Container : in Ptr; + Capacity : in Natural) + with Inline; + + procedure Replace_With_Subenv (Item : in out Ptr) + with Inline; + -- Equivalent to Item := Alloc (Outer => Item, Capacity), but + -- faster when Item was the last reference to its environment, as + -- the storage and maps are then reused. + + procedure Set (Container : in Ptr; + Key : in Strings.Ptr; + New_Item : in Types.Mal_Type) + with Inline; + + procedure Set_Binds (Container : in Ptr; + Formals : in Lists.Ptr; + Actuals : in Types.Mal_Type_Array); + + function Get (Container : in Ptr; + Key : in Strings.Ptr) return Types.Mal_Type; + Unknown_Key : exception; + + -- procedure Dump; + +private + + type Env_Record; + type Env_Access is access Env_Record; + type Ptr is new Ada.Finalization.Controlled with record + Ref : Env_Access := null; + end record + with Invariant => Ptr.Ref /= null; + overriding procedure Adjust (Object : in out Ptr) with Inline; + overriding procedure Finalize (Object : in out Ptr); + -- Predefined equality is fine. + +end Environments; diff --git a/ada2/lists.adb b/ada2/lists.adb new file mode 100644 index 0000000000..6aed887521 --- /dev/null +++ b/ada2/lists.adb @@ -0,0 +1,95 @@ +with Ada.Unchecked_Deallocation; +with Atoms; +with Types; + +package body Lists is + + type List_Record (Last : Positive) is limited record + Data : Types.Mal_Type_Array (1 .. Last); + Refs : Positive; + end record; + -- The invariant for Ptr is: + -- Ptr.Ref = null or else Ptr.First <= Ptr.Ref.all.Last + -- but we cannot express this in the specification because the limited + -- view on Types forbids to define List_Record there. + + procedure Free is new Ada.Unchecked_Deallocation (Object => List_Record, + Name => List_Access); + + ---------------------------------------------------------------------- + + function "=" (Left, Right : in Ptr) return Boolean is + (if Left.Ref = null + then Right.Ref = null + else + -- As strange as it may seem, this assertion fails when + -- running "(= [(list)] (list []))". + -- pragma Assert + -- ((Left.Ref.all.Data (1) = Right.Ref.all.Data (1)) + -- = + -- (Left.Ref.all.Data (1 .. 1) = Right.Ref.all.Data (1 .. 1))); + -- This may be a compiler bug. + Right.Ref /= null + and then Left.Ref.all.Last = Right.Ref.all.Last + and then (for all I in 1 .. Left.Ref.all.Last => + Types."=" (Left.Ref.all.Data (I), + Right.Ref.all.Data (I)))); + + procedure Adjust (Object : in out Ptr) is + begin + if Object.Ref /= null then + Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + end if; + end Adjust; + + function Element (Container : in Ptr; + Index : in Positive) return Types.Mal_Type is + (Container.Ref.all.Data (Index)); + + procedure Finalize (Object : in out Ptr) + is + Refs : Positive; + begin + -- Ensure that we can be called twice in a row (7.6.1(24)). + if Object.Ref /= null then + Refs := Object.Ref.all.Refs; + if 1 < Refs then + Object.Ref.all.Refs := Refs - 1; + Object.Ref := null; + else + -- pragma Assert (Ptr (Object.Ref.all.Id) = Object.Ref); + -- Ptr (Object.Ref.all.Id) := null; + Free (Object.Ref); + end if; + end if; + end Finalize; + + function Length (Source : in Ptr) return Natural is + (if Source.Ref = null then 0 else Source.Ref.all.Last); + + function Alloc (Source : in Types.Mal_Type_Array) return Ptr is + (if Source'Length = 0 + then Empty_List + else (Ada.Finalization.Controlled with + Ref => new List_Record'(Data => Source, + Last => Source'Length, + Refs => 1))); + + function Alloc (Length : in Natural) return Ptr is + (if Length = 0 + then Empty_List + else (Ada.Finalization.Controlled with + Ref => new List_Record' + (Data => (1 .. Length => (Types.Kind_Nil, Atoms.No_Element)), + Last => Length, + Refs => 1))); + + procedure Replace_Element (Source : in Ptr; + Index : in Positive; + New_Value : in Types.Mal_Type) is + begin + pragma Assert (Source.Ref.all.Refs = 1); + Source.Ref.all.Data (Index) := New_Value; + end Replace_Element; + +end Lists; diff --git a/ada2/lists.ads b/ada2/lists.ads new file mode 100644 index 0000000000..28c76cfe39 --- /dev/null +++ b/ada2/lists.ads @@ -0,0 +1,47 @@ +private with Ada.Finalization; +limited with Types; + +package Lists is + + -- A pointer to an array of Mal_Type elements. It differs from + -- Ada.Containers.Vectors because assignment give another pointer + -- to the same storage and does not copy contents. + + type Ptr is tagged private; + Empty_List : constant Ptr; -- The default value. + + function Length (Source : in Ptr) return Natural + with Inline; + + function Element (Container : in Ptr; + Index : in Positive) return Types.Mal_Type + with Inline, Pre => Index <= Container.Length; + + function Alloc (Length : in Natural) return Ptr + with Inline; + -- All elements are Nil, the default value for Mal_Type. + + function Alloc (Source : in Types.Mal_Type_Array) return Ptr + with Inline; + + procedure Replace_Element (Source : in Ptr; + Index : in Positive; + New_Value : in Types.Mal_Type) + with Inline, Pre => Index <= Source.Length; + -- An assertion checks that Source is the only reference to its + -- storage. + +private + + type List_Record; + type List_Access is access List_Record; + type Ptr is new Ada.Finalization.Controlled with record + Ref : List_Access := null; + end record; + overriding procedure Adjust (Object : in out Ptr) with Inline; + overriding procedure Finalize (Object : in out Ptr) with Inline; + overriding function "=" (Left, Right : in Ptr) return Boolean; + + Empty_List : constant Ptr := (Ada.Finalization.Controlled with Ref => null); + +end Lists; diff --git a/ada2/maps.adb b/ada2/maps.adb new file mode 100644 index 0000000000..f1e1eacc85 --- /dev/null +++ b/ada2/maps.adb @@ -0,0 +1,160 @@ +with Ada.Containers.Hashed_Maps; +with Ada.Unchecked_Deallocation; +with Strings; +with Types; + +package body Maps is + + function Hash (Item : in Types.Mal_Type) return Ada.Containers.Hash_Type + with Inline, Pre => Item.Kind in Types.Kind_String | Types.Kind_Keyword; + + package Hashed_Maps is new Ada.Containers.Hashed_Maps + (Key_Type => Types.Mal_Type, + Element_Type => Types.Mal_Type, + Hash => Hash, + Equivalent_Keys => Types."=", + "=" => Types."="); + + type Map_Record is limited record + Data : Hashed_Maps.Map; + Refs : Positive; + end record; + + procedure Free is new Ada.Unchecked_Deallocation (Object => Map_Record, + Name => Map_Access); + + use type Ada.Containers.Count_Type; + + ---------------------------------------------------------------------- + + function "=" (Left, Right : in Ptr) return Boolean is + (Hashed_Maps."=" (Left.Ref.all.Data, Right.Ref.all.Data)); + + procedure Adjust (Object : in out Ptr) is + begin + Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + end Adjust; + + function Assoc (Container : in Ptr; + Pairs : in Types.Mal_Type_Array) return Ptr + is + pragma Assert (Pairs'Length mod 2 = 0); + Pair_Count : constant Ada.Containers.Count_Type + := Ada.Containers.Count_Type (Pairs'Length) / 2; + Result : Ptr; + begin + Result.Ref.all.Data.Reserve_Capacity (Pair_Count + + Container.Ref.all.Data.Length); + Result.Ref.all.Data.Assign (Container.Ref.all.Data); + for I in 0 .. Pairs'Length / 2 - 1 loop + pragma Assert (Pairs (Pairs'First + 2 * I).Kind in Types.Kind_String + | Types.Kind_Keyword); + Result.Ref.all.Data.Include (Pairs (Pairs'First + 2 * I), + Pairs (Pairs'First + 2 * I + 1)); + end loop; + return Result; + end Assoc; + + function Contains (Container : in Ptr; + Key : in Types.Mal_Type) return Boolean is + (Container.Ref.all.Data.Contains (Key)); + + function Dissoc (Source : in Ptr; + Keys : in Types.Mal_Type_Array) return Ptr + is + Result : Ptr; + begin + Result.Ref.all.Data.Assign (Source.Ref.all.Data); + for I in Keys'Range loop + pragma Assert (Keys (I).Kind in Types.Kind_String + | Types.Kind_Keyword); + Result.Ref.all.Data.Exclude (Keys (I)); + end loop; + return Result; + end Dissoc; + + procedure Finalize (Object : in out Ptr) + is + Refs : Positive; + begin + -- Finalize may be called twice. + if Object.Ref /= null then + Refs := Object.Ref.all.Refs; + if 1 < Refs then + Object.Ref.all.Refs := Refs - 1; + Object.Ref := null; + else + Free (Object.Ref); + end if; + end if; + end Finalize; + + procedure Iterate + (Container : in Ptr; + Process : not null access procedure (Key : in Types.Mal_Type; + Element : in Types.Mal_Type)) is + begin + for Position in Container.Ref.all.Data.Iterate loop + Process.all (Hashed_Maps.Key (Position), + Hashed_Maps.Element (Position)); + end loop; + end Iterate; + + function Get (Container : in Ptr; + Key : in Types.Mal_Type) return Types.Mal_Type + is + Position : Hashed_Maps.Cursor; + begin + Position := Container.Ref.all.Data.Find (Key); + if Hashed_Maps.Has_Element (Position) then + return Hashed_Maps.Element (Position); + end if; + raise Unknown_Key with "'" & Key.S.Deref & "' not found"; + end Get; + + function Hash (Item : in Types.Mal_Type) return Ada.Containers.Hash_Type is + (Item.S.Hash); + + function Hash_Map (Pairs : in Types.Mal_Type_Array) return Ptr + is + pragma Assert (Pairs'Length mod 2 = 0); + Pair_Count : constant Ada.Containers.Count_Type + := Ada.Containers.Count_Type (Pairs'Length) / 2; + Result : Ptr; + begin + Result.Ref.all.Data.Reserve_Capacity (Pair_Count); + for I in 0 .. Pairs'Length / 2 - 1 loop + pragma Assert (Pairs (Pairs'First + 2 * I).Kind in Types.Kind_String + | Types.Kind_Keyword); + Result.Ref.all.Data.Include (Pairs (Pairs'First + 2 * I), + Pairs (Pairs'First + 2 * I + 1)); + end loop; + return Result; + end Hash_Map; + + procedure Initialize (Object : in out Ptr) is + begin + Object.Ref := new Map_Record'(Data => Hashed_Maps.Empty_Map, + Refs => 1); + end Initialize; + + function Length (Container : in Ptr) return Natural + is (Natural (Container.Ref.all.Data.Length)); + + function Map (Container : in Ptr; + F : not null access function (X : in Types.Mal_Type) + return Types.Mal_Type) + return Ptr + is + Result : Ptr; + begin + Result.Ref.all.Data.Assign (Container.Ref.all.Data); + -- Ensure the invariants before calling F, in case it raises exceptions. + for Position in Result.Ref.all.Data.Iterate loop + Result.Ref.all.Data.Replace_Element + (Position, F.all (Hashed_Maps.Element (Position))); + end loop; + return Result; + end Map; + +end Maps; diff --git a/ada2/maps.ads b/ada2/maps.ads new file mode 100644 index 0000000000..5bf81efa14 --- /dev/null +++ b/ada2/maps.ads @@ -0,0 +1,59 @@ +private with Ada.Finalization; +with Lists; +limited with Types; + +package Maps is + + -- A pointer to an Ada.Containers.Hashed_Maps.Map of + -- Types.Mal_Type. Keys must be Strings or Keywords. We can + -- probably not state this with a limited with, so this will + -- become an assertion. + + type Ptr is tagged private; + -- The default value is empty. + + function Length (Container : in Ptr) return Natural + with Inline; + + function Hash_Map (Pairs : in Types.Mal_Type_Array) return Ptr; + + function Assoc (Container : in Ptr; + Pairs : in Types.Mal_Type_Array) return Ptr; + + function Dissoc (Source : in Ptr; + Keys : in Types.Mal_Type_Array) return Ptr; + + function Map (Container : in Ptr; + F : not null access function (X : in Types.Mal_Type) + return Types.Mal_Type) + return Ptr; + + procedure Iterate + (Container : in Ptr; + Process : not null access procedure (Key : in Types.Mal_Type; + Element : in Types.Mal_Type)) + with Inline; + + function Contains (Container : in Ptr; + Key : in Types.Mal_Type) return Boolean + with Inline; + + function Get (Container : in Ptr; + Key : in Types.Mal_Type) return Types.Mal_Type + with Inline; + Unknown_Key : exception; + +private + + type Map_Record; + type Map_Access is access Map_Record; + type Ptr is new Ada.Finalization.Controlled with record + Ref : Map_Access := null; + end record + with Invariant => Ptr.Ref /= null; + overriding procedure Initialize (Object : in out Ptr) with Inline; + overriding procedure Adjust (Object : in out Ptr) with Inline; + overriding procedure Finalize (Object : in out Ptr) with Inline; + overriding function "=" (Left, Right : in Ptr) return Boolean with Inline; + +end Maps; diff --git a/ada2/names.ads b/ada2/names.ads new file mode 100644 index 0000000000..40613a160c --- /dev/null +++ b/ada2/names.ads @@ -0,0 +1,87 @@ +with Strings; use Strings; + +package Names is + + -- Symbols known at compile time are allocated at program + -- start, in order to avoid repeated allocations and + -- deallocations during each Read and /Eval/Print cycle. The + -- reference is kept so each usage does not trigger a search in + -- the global hash map. + + Ada2 : constant Ptr := Alloc ("ada2"); + Ampersand : constant Ptr := Alloc ("&"); + Apply : constant Ptr := Alloc ("apply"); + Argv : constant Ptr := Alloc ("*ARGV*"); + Assoc : constant Ptr := Alloc ("assoc"); + Asterisk : constant Ptr := Alloc ("*"); + Atom : constant Ptr := Alloc ("atom"); + Catch : constant Ptr := Alloc ("catch*"); + Concat : constant Ptr := Alloc ("concat"); + Conj : constant Ptr := Alloc ("conj"); + Cons : constant Ptr := Alloc ("cons"); + Contains : constant Ptr := Alloc ("contains?"); + Count : constant Ptr := Alloc ("count"); + Def : constant Ptr := Alloc ("def!"); + Defmacro : constant Ptr := Alloc ("defmacro!"); + Deref : constant Ptr := Alloc ("deref"); + Dissoc : constant Ptr := Alloc ("dissoc"); + Equals : constant Ptr := Alloc ("="); + Eval : constant Ptr := Alloc ("eval"); + First : constant Ptr := Alloc ("first"); + Fn : constant Ptr := Alloc ("fn*"); + Get : constant Ptr := Alloc ("get"); + Greater_Equal : constant Ptr := Alloc (">="); + Greater_Than : constant Ptr := Alloc (">"); + Hash_Map : constant Ptr := Alloc ("hash-map"); + Host_Language : constant Ptr := Alloc ("*host-language*"); + Is_Atom : constant Ptr := Alloc ("atom?"); + Is_Empty : constant Ptr := Alloc ("empty?"); + Is_False : constant Ptr := Alloc ("false?"); + Is_Keyword : constant Ptr := Alloc ("keyword?"); + Is_List : constant Ptr := Alloc ("list?"); + Is_Map : constant Ptr := Alloc ("map?"); + Is_Nil : constant Ptr := Alloc ("nil?"); + Is_Sequential : constant Ptr := Alloc ("sequential?"); + Is_String : constant Ptr := Alloc ("string?"); + Is_Symbol : constant Ptr := Alloc ("symbol?"); + Is_True : constant Ptr := Alloc ("true?"); + Is_Vector : constant Ptr := Alloc ("vector?"); + Keys : constant Ptr := Alloc ("keys"); + Keyword : constant Ptr := Alloc ("keyword"); + Less_Equal : constant Ptr := Alloc ("<="); + Less_Than : constant Ptr := Alloc ("<"); + Let : constant Ptr := Alloc ("let*"); + List : constant Ptr := Alloc ("list"); + Macroexpand : constant Ptr := Alloc ("macroexpand"); + Mal_Do : constant Ptr := Alloc ("do"); + Mal_If : constant Ptr := Alloc ("if"); + Map : constant Ptr := Alloc ("map"); + Meta : constant Ptr := Alloc ("meta"); + Minus : constant Ptr := Alloc ("-"); + Nth : constant Ptr := Alloc ("nth"); + Plus : constant Ptr := Alloc ("+"); + Pr_Str : constant Ptr := Alloc ("pr-str"); + Println : constant Ptr := Alloc ("println"); + Prn : constant Ptr := Alloc ("prn"); + Quasiquote : constant Ptr := Alloc ("quasiquote"); + Quote : constant Ptr := Alloc ("quote"); + Read_String : constant Ptr := Alloc ("read-string"); + Readline : constant Ptr := Alloc ("readline"); + Reset : constant Ptr := Alloc ("reset!"); + Rest : constant Ptr := Alloc ("rest"); + Seq : constant Ptr := Alloc ("seq"); + Slash : constant Ptr := Alloc ("/"); + Slurp : constant Ptr := Alloc ("slurp"); + Splice_Unquote : constant Ptr := Alloc ("splice-unquote"); + Str : constant Ptr := Alloc ("str"); + Swap : constant Ptr := Alloc ("swap!"); + Symbol : constant Ptr := Alloc ("symbol"); + Throw : constant Ptr := Alloc ("throw"); + Time_Ms : constant Ptr := Alloc ("time-ms"); + Try : constant Ptr := Alloc ("try*"); + Unquote : constant Ptr := Alloc ("unquote"); + Vals : constant Ptr := Alloc ("vals"); + Vector : constant Ptr := Alloc ("vector"); + With_Meta : constant Ptr := Alloc ("with-meta"); + +end Names; diff --git a/ada2/printer.adb b/ada2/printer.adb new file mode 100644 index 0000000000..6b21c4a7ef --- /dev/null +++ b/ada2/printer.adb @@ -0,0 +1,177 @@ +with Ada.Characters.Latin_1; +with Atoms; +with Lists; +with Maps; +with Strings; + +package body Printer is + + use Ada.Strings.Unbounded; + use Types; + + procedure Print_Form (Buffer : in out Unbounded_String; + Ast : in Mal_Type; + Print_Readably : in Boolean); + procedure Print_List (Buffer : in out Unbounded_String; + List : in Lists.Ptr; + Print_Readably : in Boolean) + with Inline; + procedure Print_Function (Buffer : in out Unbounded_String; + Formals : in Lists.Ptr; + Expression : in Atoms.Ptr; + Print_Readably : in Boolean) + with Inline; + procedure Print_Map (Buffer : in out Unbounded_String; + Map : in Maps.Ptr; + Print_Readably : in Boolean) + with Inline; + + ---------------------------------------------------------------------- + + procedure Print_Form (Buffer : in out Unbounded_String; + Ast : in Mal_Type; + Print_Readably : in Boolean) is + begin + case Ast.Kind is + + when Kind_Nil => + Append (Buffer, "nil"); + + when Kind_Boolean => + if Ast.Boolean_Value then + Append (Buffer, "true"); + else + Append (Buffer, "false"); + end if; + + when Kind_Symbol => + Append (Buffer, Ast.S.Deref); + + when Kind_Number => + declare + Img : constant String := Integer'Image (Ast.Integer_Value); + F : Positive := Img'First; + begin + if Img (F) = ' ' then + F := F + 1; + end if; + Append (Buffer, Img (F .. Img'Last)); + end; + + when Kind_Keyword => + Append (Buffer, ':'); + Append (Buffer, Ast.S.Deref); + + when Kind_String => + if Print_Readably then + Append (Buffer, '"'); + for C of Ast.S.Deref loop + case C is + when '"' | '\' => + Append (Buffer, '\'); + Append (Buffer, C); + when Ada.Characters.Latin_1.LF => + Append (Buffer, "\n"); + when others => + Append (Buffer, C); + end case; + end loop; + Append (Buffer, '"'); + else + Append (Buffer, Ast.S.Deref); + end if; + + when Kind_List => + Append (Buffer, '('); + Print_List (Buffer, Ast.L, Print_Readably); + Append (Buffer, ')'); + when Kind_Vector => + Append (Buffer, '['); + Print_List (Buffer, Ast.L, Print_Readably); + Append (Buffer, ']'); + + when Kind_Map => + Print_Map (Buffer, Ast.Map, Print_Readably); + + when Kind_Native => + Append (Buffer, "#"); + when Kind_Function => + Append (Buffer, "#'); + when Kind_Macro => + Append (Buffer, "#'); + + when Kind_Atom => + Append (Buffer, "(atom "); + Print_Form (Buffer, Ast.Reference.Deref, Print_Readably); + Append (Buffer, ')'); + + end case; + end Print_Form; + + procedure Print_Function (Buffer : in out Unbounded_String; + Formals : in Lists.Ptr; + Expression : in Atoms.Ptr; + Print_Readably : in Boolean) is + begin + if 0 < Formals.Length then + Print_List (Buffer, Formals, Print_Readably); + Append (Buffer, " -> "); + Print_Form (Buffer, Expression.Deref, Print_Readably); + end if; + end Print_Function; + + procedure Print_List (Buffer : in out Unbounded_String; + List : in Lists.Ptr; + Print_Readably : in Boolean) is + begin + if 1 <= List.Length then + Print_Form (Buffer, List.Element (1), Print_Readably); + for I in 2 .. List.Length loop + Append (Buffer, ' '); + Print_Form (Buffer, List.Element (I), Print_Readably); + end loop; + end if; + end Print_List; + + procedure Print_Map (Buffer : in out Unbounded_String; + Map : in Maps.Ptr; + Print_Readably : in Boolean) + is + Is_First : Boolean := True; + procedure Process (Key : in Mal_Type; + Element : in Mal_Type); + procedure Process (Key : in Mal_Type; + Element : in Mal_Type) is + begin + if Is_First then + Is_First := False; + else + Append (Buffer, ' '); + end if; + Print_Form (Buffer, Key, Print_Readably); + Append (Buffer, ' '); + Print_Form (Buffer, Element, Print_Readably); + end Process; + begin + Append (Buffer, '{'); + Map.Iterate (Process'Access); + Append (Buffer, '}'); + end Print_Map; + + function Pr_Str (Ast : in Mal_Type; + Print_Readably : in Boolean := True) + return Unbounded_String + is + Result : Unbounded_String; + begin + Print_Form (Result, Ast, Print_Readably); + return Result; + end Pr_Str; + +end Printer; diff --git a/ada2/printer.ads b/ada2/printer.ads new file mode 100644 index 0000000000..5071a801f7 --- /dev/null +++ b/ada2/printer.ads @@ -0,0 +1,12 @@ +with Ada.Strings.Unbounded; +with Types; + +package Printer is + + pragma Elaborate_Body; + + function Pr_Str (Ast : in Types.Mal_Type; + Print_Readably : in Boolean := True) + return Ada.Strings.Unbounded.Unbounded_String; + +end Printer; diff --git a/ada2/reader.adb b/ada2/reader.adb new file mode 100644 index 0000000000..d72af57749 --- /dev/null +++ b/ada2/reader.adb @@ -0,0 +1,252 @@ +with Ada.Characters.Latin_1; +with Atoms; +with Lists; +with Maps; +with Names; +with Strings; + +package body Reader is + + function Read_Str (Source : in String) return Types.Mal_Type + is + First : Positive; + Last : Natural := Source'First - 1; + + function Read_Form return Types.Mal_Type; + + procedure Find_Next_Token; + -- Search next token from index Last + 1. + -- If none is found, set First to Source'Last + 1. + -- Find_Next_Token is normally invoked right before Read_Form, + -- allowing the caller to check whether First <= Source'Last. + + -- Helpers: + + -- Read_Atom has been merged into the same case/switch + -- statement, for clarity and efficiency. + function Read_List (Ending : in Character) return Types.Mal_Type_Array + with Inline; + function Read_Quote (Symbol : in Strings.Ptr) return Types.Mal_Type + with Inline; + + ---------------------------------------------------------------------- + procedure Find_Next_Token + is + use Ada.Characters.Latin_1; + begin + First := Last + 1; + while First <= Source'Last loop + + case Source (First) is + + when ' ' | ',' | HT | VT | LF | CR => + First := First + 1; + + when '[' | ']' | '{' | '}' | '(' | ')' | ''' | '`' | '^' | '@' + => + Last := First; + exit; + + when '~' => + if First + 1 <= Source'Last + and then Source (First + 1) = '@' + then + Last := First + 1; + else + Last := First; + end if; + exit; + + when '"' => + Last := First + 1; + loop + if Source'Last < Last then + raise Reader_Error with "expected '""'"; + end if; + exit when Source (Last) = '"'; + if Source (Last) = '\' then + Last := Last + 1; + end if; + Last := Last + 1; + end loop; + exit; + + when ';' => + First := First + 1; + while First <= Source'Last loop + if Source (First) = LF then + First := First + 1; + exit; + end if; + First := First + 1; + end loop; + + when others => + Last := First; + while Last + 1 <= Source'Last + and then Source (Last + 1) not in + ' ' | ',' | HT | VT | LF | CR | '[' | ']' | '{' | '}' + | '(' | ')' | ''' | '`' | '^' | '@' | '~' | '"' | ';' + loop + Last := Last + 1; + end loop; + exit; + + end case; + end loop; + end Find_Next_Token; + + function Read_Form return Types.Mal_Type + is + use Types; + begin + case Source (First) is + + when '(' => + return (Kind_List, Atoms.No_Element, + Lists.Alloc (Read_List (')'))); + when '[' => + return (Kind_Vector, Atoms.No_Element, + Lists.Alloc (Read_List (']'))); + when '{' => + return (Kind_Map, Atoms.No_Element, + Maps.Hash_Map (Read_List ('}'))); + + when '"' => + declare + Buffer : String (First .. Last); + B_Last : Natural := Buffer'First - 1; + I : Positive := First + 1; + begin + while I <= Last - 1 loop + if Source (I) /= '\' or else I = Last - 1 then + B_Last := B_Last + 1; + Buffer (B_Last) := Source (I); + else + case Source (I + 1) is + when '\' | '"' => + B_Last := B_Last + 1; + Buffer (B_Last) := Source (I + 1); + I := I + 1; + when 'n' => + B_Last := B_Last + 1; + Buffer (B_Last) := Ada.Characters.Latin_1.LF; + I := I + 1; + when others => + B_Last := B_Last + 1; + Buffer (B_Last) := Source (I); + end case; + end if; + I := I + 1; + end loop; + return (Kind_String, Atoms.No_Element, + Strings.Alloc (Buffer (Buffer'First .. B_Last))); + end; + when ':' => + return (Kind_Keyword, Atoms.No_Element, + Strings.Alloc (Source (First + 1 .. Last))); + + when '-' => + if First < Last + and then (for all C of Source (First + 1 .. Last) => + C in '0' .. '9') + then + return (Kind_Number, Atoms.No_Element, + Integer'Value (Source (First .. Last))); + else + return (Kind_Symbol, Atoms.No_Element, + Strings.Alloc (Source (First .. Last))); + end if; + when '0' .. '9' => + return (Kind_Number, Atoms.No_Element, + Integer'Value (Source (First .. Last))); + + when ''' => + return Read_Quote (Names.Quote); + when '`' => + return Read_Quote (Names.Quasiquote); + when '@' => + return Read_Quote (Names.Deref); + when '~' => + if First = Last then + return Read_Quote (Names.Unquote); + else + return Read_Quote (Names.Splice_Unquote); + end if; + when '^' => + return Result : constant Mal_Type + := (Kind_List, Atoms.No_Element, Lists.Alloc (3)) + do + Result.L.Replace_Element (1, Mal_Type' + (Kind_Symbol, Atoms.No_Element, Names.With_Meta)); + Find_Next_Token; + if Source'Last < First then + raise Reader_Error with "Unfinished 'with-meta'"; + end if; + Result.L.Replace_Element (3, Read_Form); + Find_Next_Token; + if Source'Last < First then + raise Reader_Error with "Unfinished 'with-meta'"; + end if; + Result.L.Replace_Element (2, Read_Form); + end return; + + when others => + if Source (First .. Last) = "nil" then + return (Kind_Nil, Atoms.No_Element); + elsif Source (First .. Last) = "true" then + return (Kind_Boolean, Atoms.No_Element, True); + elsif Source (First .. Last) = "false" then + return (Kind_Boolean, Atoms.No_Element, False); + else + return (Kind_Symbol, Atoms.No_Element, + Strings.Alloc (Source (First .. Last))); + end if; + end case; + end Read_Form; + + function Read_List (Ending : in Character) return Types.Mal_Type_Array + is + -- Using big arrays on the stack is faster than doing + -- repeated dynamic reallocations. + Buffer : Types.Mal_Type_Array (First + 1 .. Source'Last); + B_Last : Natural := Buffer'First - 1; + begin + loop + Find_Next_Token; + if Source'Last < First then + raise Reader_Error with "expected '" & Ending & "'"; + end if; + exit when Source (First) = Ending; + B_Last := B_Last + 1; + Buffer (B_Last) := Read_Form; + end loop; + return Buffer (Buffer'First .. B_Last); + end Read_List; + + function Read_Quote (Symbol : in Strings.Ptr) return Types.Mal_Type is + use Types; + Result : constant Mal_Type + := (Kind_List, Atoms.No_Element, Lists.Alloc (2)); + begin + Result.L.Replace_Element (1, + Mal_Type'(Kind_Symbol, Atoms.No_Element, Symbol)); + Find_Next_Token; + if Source'Last < First then + raise Reader_Error with "Unfinished '" & Symbol.Deref & "'"; + end if; + Result.L.Replace_Element (2, Read_Form); + return Result; + end Read_Quote; + + ---------------------------------------------------------------------- + + begin + Find_Next_Token; + if Source'Last < First then + raise Empty_Source; + end if; + return Read_Form; + end Read_Str; + +end Reader; diff --git a/ada2/reader.ads b/ada2/reader.ads new file mode 100644 index 0000000000..d5e85b36bd --- /dev/null +++ b/ada2/reader.ads @@ -0,0 +1,12 @@ +with Types; + +package Reader is + + pragma Elaborate_Body; + + function Read_Str (Source : in String) return Types.Mal_Type; + + Empty_Source : exception; + Reader_Error : exception; + +end Reader; diff --git a/ada2/run b/ada2/run new file mode 100755 index 0000000000..8ba68a5484 --- /dev/null +++ b/ada2/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/ada2/step0_repl.adb b/ada2/step0_repl.adb new file mode 100644 index 0000000000..bf8d73ab17 --- /dev/null +++ b/ada2/step0_repl.adb @@ -0,0 +1,64 @@ +with Ada.Exceptions; +with Ada.Text_IO; +with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; + +procedure Step0_Repl is + + function Read (Source : in String) return String + is (Source); + + function Eval (Ast : in String) return String + is (Ast); + + function Print (Ast : in String) return String + is (Ast); + + function Rep (Source : in String) return String + is (Print (Eval (Read (Source)))); + + procedure Interactive_Loop; + + ---------------------------------------------------------------------- + + procedure Interactive_Loop + is + + function Readline (Prompt : in Interfaces.C.char_array) + return Interfaces.C.Strings.chars_ptr + with Import, Convention => C, External_Name => "readline"; + + procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "add_history"; + + procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "free"; + + Prompt : constant Interfaces.C.char_array + := Interfaces.C.To_C ("user> "); + C_Line : Interfaces.C.Strings.chars_ptr; + begin + loop + C_Line := Readline (Prompt); + exit when C_Line = Interfaces.C.Strings.Null_Ptr; + declare + Line : constant String := Interfaces.C.Strings.Value (C_Line); + begin + if Line /= "" then + Add_History (C_Line); + end if; + Free (C_Line); + Ada.Text_IO.Put_Line (Rep (Line)); + exception + when E : others => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- but go on proceeding. + end; + end loop; + Ada.Text_IO.New_Line; + end Interactive_Loop; + + ---------------------------------------------------------------------- + +begin + Interactive_Loop; +end Step0_Repl; diff --git a/ada2/step1_read_print.adb b/ada2/step1_read_print.adb new file mode 100644 index 0000000000..b68f1197c2 --- /dev/null +++ b/ada2/step1_read_print.adb @@ -0,0 +1,75 @@ +with Ada.Exceptions; +with Ada.Strings.Unbounded; +with Ada.Text_IO.Unbounded_IO; +with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; +with Printer; +with Reader; +with Types; + +procedure Step1_Read_Print is + + function Read (Source : in String) return Types.Mal_Type + renames Reader.Read_Str; + + function Eval (Ast : in Types.Mal_Type) return Types.Mal_Type + is (Ast); + + function Print (Ast : in Types.Mal_Type; + Print_Readably : in Boolean := True) + return Ada.Strings.Unbounded.Unbounded_String + renames Printer.Pr_Str; + + function Rep (Source : in String) + return Ada.Strings.Unbounded.Unbounded_String + is (Print (Eval (Read (Source)))) + with Inline; + + procedure Interactive_Loop + with Inline; + + ---------------------------------------------------------------------- + + procedure Interactive_Loop + is + + function Readline (Prompt : in Interfaces.C.char_array) + return Interfaces.C.Strings.chars_ptr + with Import, Convention => C, External_Name => "readline"; + + procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "add_history"; + + procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "free"; + + Prompt : constant Interfaces.C.char_array + := Interfaces.C.To_C ("user> "); + C_Line : Interfaces.C.Strings.chars_ptr; + begin + loop + C_Line := Readline (Prompt); + exit when C_Line = Interfaces.C.Strings.Null_Ptr; + declare + Line : constant String := Interfaces.C.Strings.Value (C_Line); + begin + if Line /= "" then + Add_History (C_Line); + end if; + Free (C_Line); + Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line)); + exception + when Reader.Empty_Source => + null; + when E : others => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- but go on proceeding. + end; + end loop; + Ada.Text_IO.New_Line; + end Interactive_Loop; + + ---------------------------------------------------------------------- + +begin + Interactive_Loop; +end Step1_Read_Print; diff --git a/ada2/step2_eval.adb b/ada2/step2_eval.adb new file mode 100644 index 0000000000..3c72b8c7d9 --- /dev/null +++ b/ada2/step2_eval.adb @@ -0,0 +1,175 @@ +with Ada.Containers.Indefinite_Hashed_Maps; +with Ada.Exceptions; +with Ada.Strings.Hash; +with Ada.Strings.Unbounded; +with Ada.Text_IO.Unbounded_IO; +with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; +with Atoms; +with Lists; +with Printer; +with Reader; +with Types; + +procedure Step2_Eval is + + package Environments is new Ada.Containers.Indefinite_Hashed_Maps + (Key_Type => String, + Element_Type => Types.Native_Function_Access, + Hash => Ada.Strings.Hash, + Equivalent_Keys => "=", + "=" => Types."="); + + function Read (Source : in String) return Types.Mal_Type + renames Reader.Read_Str; + + function Eval (Ast : in Types.Mal_Type; + Env : in out Environments.Map) return Types.Mal_Type; + Unable_To_Call : exception; + Unknown_Symbol : exception; + + function Print (Ast : in Types.Mal_Type; + Print_Readably : in Boolean := True) + return Ada.Strings.Unbounded.Unbounded_String + renames Printer.Pr_Str; + + function Rep (Source : in String; + Env : in out Environments.Map) + return Ada.Strings.Unbounded.Unbounded_String + is (Print (Eval (Read (Source), Env))) + with Inline; + + procedure Interactive_Loop (Repl : in out Environments.Map) + with Inline; + + generic + with function Ada_Operator (Left, Right : in Integer) return Integer; + function Generic_Mal_Operator (Args : in Types.Mal_Type_Array) + return Types.Mal_Type; + + ---------------------------------------------------------------------- + + function Eval (Ast : in Types.Mal_Type; + Env : in out Environments.Map) return Types.Mal_Type + is + use Types; + begin + case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => + return Ast; + + when Kind_Symbol => + declare + S : constant String := Ast.S.Deref; + C : constant Environments.Cursor := Env.Find (S); + begin + if Environments.Has_Element (C) then + return (Kind_Native, Atoms.No_Element, + Environments.Element (C)); + else + raise Unknown_Symbol with "'" & S & "' not found"; + end if; + end; + + when Kind_Map => + declare + function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); + begin + return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); + end; + + when Kind_Vector => + return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, + Lists.Alloc (Ast.L.Length)) + do + for I in 1 .. Ast.L.Length loop + R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); + end loop; + end return; + + when Kind_List => + if Ast.L.Length = 0 then + return Ast; + end if; + + -- Apply phase + declare + First : constant Mal_Type := Eval (Ast.L.Element (1), Env); + Args : Mal_Type_Array (2 .. Ast.L.Length); + begin + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + case First.Kind is + when Kind_Native => + return First.Native.all (Args); + when others => + raise Unable_To_Call + with Ada.Strings.Unbounded.To_String (Print (First)); + end case; + end; + end case; + end Eval; + + function Generic_Mal_Operator (Args : in Types.Mal_Type_Array) + return Types.Mal_Type + is (Types.Kind_Number, Atoms.No_Element, + Ada_Operator (Args (Args'First).Integer_Value, + Args (Args'First + 1).Integer_Value)); + + procedure Interactive_Loop (Repl : in out Environments.Map) + is + + function Readline (Prompt : in Interfaces.C.char_array) + return Interfaces.C.Strings.chars_ptr + with Import, Convention => C, External_Name => "readline"; + + procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "add_history"; + + procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "free"; + + Prompt : constant Interfaces.C.char_array + := Interfaces.C.To_C ("user> "); + C_Line : Interfaces.C.Strings.chars_ptr; + begin + loop + C_Line := Readline (Prompt); + exit when C_Line = Interfaces.C.Strings.Null_Ptr; + declare + Line : constant String := Interfaces.C.Strings.Value (C_Line); + begin + if Line /= "" then + Add_History (C_Line); + end if; + Free (C_Line); + Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); + exception + when Reader.Empty_Source => + null; + when E : others => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- but go on proceeding. + end; + end loop; + Ada.Text_IO.New_Line; + end Interactive_Loop; + + ---------------------------------------------------------------------- + + function Addition is new Generic_Mal_Operator ("+"); + function Subtraction is new Generic_Mal_Operator ("-"); + function Product is new Generic_Mal_Operator ("*"); + function Division is new Generic_Mal_Operator ("/"); + + Repl : Environments.Map; +begin + Repl.Include ("+", Addition 'Unrestricted_Access); + Repl.Include ("-", Subtraction'Unrestricted_Access); + Repl.Include ("*", Product 'Unrestricted_Access); + Repl.Include ("/", Division 'Unrestricted_Access); + + Interactive_Loop (Repl); + pragma Unreferenced (Repl); +end Step2_Eval; diff --git a/ada2/step3_env.adb b/ada2/step3_env.adb new file mode 100644 index 0000000000..b19d660b6a --- /dev/null +++ b/ada2/step3_env.adb @@ -0,0 +1,199 @@ +with Ada.Exceptions; +with Ada.Strings.Unbounded; +with Ada.Text_IO.Unbounded_IO; +with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; +with Atoms; +with Environments; +with Lists; +with Names; +with Printer; +with Reader; +with Strings; use type Strings.Ptr; +with Types; + +procedure Step3_Env is + + function Read (Source : in String) return Types.Mal_Type + renames Reader.Read_Str; + + function Eval (Ast : in Types.Mal_Type; + Env : in Environments.Ptr) return Types.Mal_Type; + Unable_To_Call : exception; + + function Print (Ast : in Types.Mal_Type; + Print_Readably : in Boolean := True) + return Ada.Strings.Unbounded.Unbounded_String + renames Printer.Pr_Str; + + function Rep (Source : in String; + Env : in Environments.Ptr) + return Ada.Strings.Unbounded.Unbounded_String + is (Print (Eval (Read (Source), Env))) + with Inline; + + procedure Interactive_Loop (Repl : in Environments.Ptr) + with Inline; + + generic + with function Ada_Operator (Left, Right : in Integer) return Integer; + function Generic_Mal_Operator (Args : in Types.Mal_Type_Array) + return Types.Mal_Type; + + ---------------------------------------------------------------------- + + function Eval (Ast : in Types.Mal_Type; + Env : in Environments.Ptr) return Types.Mal_Type + is + use Types; + First : Mal_Type; + begin + case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => + return Ast; + + when Kind_Symbol => + return Env.Get (Ast.S); + + when Kind_Map => + declare + function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); + begin + return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); + end; + + when Kind_Vector => + return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, + Lists.Alloc (Ast.L.Length)) + do + for I in 1 .. Ast.L.Length loop + R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); + end loop; + end return; + + when Kind_List => + if Ast.L.Length = 0 then + return Ast; + end if; + + First := Ast.L.Element (1); + + -- Special forms + if First.Kind = Kind_Symbol then + + if First.S = Names.Def then + pragma Assert (Ast.L.Length = 3); + pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); + return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).S, R); + end return; + + elsif First.S = Names.Let then + declare + pragma Assert (Ast.L.Length = 3); + pragma Assert + (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + pragma Assert (Bindings.Length mod 2 = 0); + New_Env : constant Environments.Ptr + := Environments.Alloc (Outer => Env); + begin + New_Env.Increase_Capacity (Bindings.Length / 2); + for I in 1 .. Bindings.Length / 2 loop + pragma Assert + (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); + New_Env.Set (Bindings.Element (2 * I - 1).S, + Eval (Bindings.Element (2 * I), New_Env)); + end loop; + return Eval (Ast.L.Element (3), New_Env); + end; + end if; + end if; + + -- No special form has been found, attempt to apply the + -- first element to the rest of the list. + declare + Args : Mal_Type_Array (2 .. Ast.L.Length); + begin + First := Eval (First, Env); + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + case First.Kind is + when Kind_Native => + return First.Native.all (Args); + when others => + raise Unable_To_Call + with Ada.Strings.Unbounded.To_String (Print (First)); + end case; + end; + end case; + end Eval; + + function Generic_Mal_Operator (Args : in Types.Mal_Type_Array) + return Types.Mal_Type + is (Types.Kind_Number, Atoms.No_Element, + Ada_Operator (Args (Args'First).Integer_Value, + Args (Args'First + 1).Integer_Value)); + + procedure Interactive_Loop (Repl : in Environments.Ptr) + is + + function Readline (Prompt : in Interfaces.C.char_array) + return Interfaces.C.Strings.chars_ptr + with Import, Convention => C, External_Name => "readline"; + + procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "add_history"; + + procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "free"; + + Prompt : constant Interfaces.C.char_array + := Interfaces.C.To_C ("user> "); + C_Line : Interfaces.C.Strings.chars_ptr; + begin + loop + C_Line := Readline (Prompt); + exit when C_Line = Interfaces.C.Strings.Null_Ptr; + declare + Line : constant String := Interfaces.C.Strings.Value (C_Line); + begin + if Line /= "" then + Add_History (C_Line); + end if; + Free (C_Line); + Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); + exception + when Reader.Empty_Source => + null; + when E : others => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- but go on proceeding. + end; + end loop; + Ada.Text_IO.New_Line; + end Interactive_Loop; + + ---------------------------------------------------------------------- + + function Addition is new Generic_Mal_Operator ("+"); + function Subtraction is new Generic_Mal_Operator ("-"); + function Product is new Generic_Mal_Operator ("*"); + function Division is new Generic_Mal_Operator ("/"); + + use Types; + Repl : constant Environments.Ptr := Environments.Alloc; +begin + Repl.Increase_Capacity (4); + Repl.Set (Names.Plus, Types.Mal_Type' + (Types.Kind_Native, Atoms.No_Element, Addition'Unrestricted_Access)); + Repl.Set (Names.Minus, Types.Mal_Type' + (Types.Kind_Native, Atoms.No_Element, Subtraction'Unrestricted_Access)); + Repl.Set (Names.Asterisk, Types.Mal_Type' + (Types.Kind_Native, Atoms.No_Element, Product'Unrestricted_Access)); + Repl.Set (Names.Slash, Types.Mal_Type' + (Types.Kind_Native, Atoms.No_Element, Division'Unrestricted_Access)); + + Interactive_Loop (Repl); +end Step3_Env; diff --git a/ada2/step4_if_fn_do.adb b/ada2/step4_if_fn_do.adb new file mode 100644 index 0000000000..c554a6ec12 --- /dev/null +++ b/ada2/step4_if_fn_do.adb @@ -0,0 +1,231 @@ +with Ada.Exceptions; +with Ada.Strings.Unbounded; +with Ada.Text_IO.Unbounded_IO; +with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; +with Atoms; +with Core; +with Environments; +with Lists; +with Names; +with Printer; +with Reader; +with Strings; use type Strings.Ptr; +with Types; + +procedure Step4_If_Fn_Do is + + function Read (Source : in String) return Types.Mal_Type + renames Reader.Read_Str; + + function Eval (Ast : in Types.Mal_Type; + Env : in Environments.Ptr) return Types.Mal_Type; + Unable_To_Call : exception; + + function Print (Ast : in Types.Mal_Type; + Print_Readably : in Boolean := True) + return Ada.Strings.Unbounded.Unbounded_String + renames Printer.Pr_Str; + + function Rep (Source : in String; + Env : in Environments.Ptr) + return Ada.Strings.Unbounded.Unbounded_String + is (Print (Eval (Read (Source), Env))) + with Inline; + + procedure Interactive_Loop (Repl : in Environments.Ptr) + with Inline; + + -- Convenient when the result of eval is of no interest. + procedure Discard (Ast : in Types.Mal_Type) is null; + + ---------------------------------------------------------------------- + + function Eval (Ast : in Types.Mal_Type; + Env : in Environments.Ptr) return Types.Mal_Type + is + use Types; + First : Mal_Type; + begin + case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => + return Ast; + + when Kind_Symbol => + return Env.Get (Ast.S); + + when Kind_Map => + declare + function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); + begin + return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); + end; + + when Kind_Vector => + return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, + Lists.Alloc (Ast.L.Length)) + do + for I in 1 .. Ast.L.Length loop + R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); + end loop; + end return; + + when Kind_List => + if Ast.L.Length = 0 then + return Ast; + end if; + + First := Ast.L.Element (1); + + -- Special forms + if First.Kind = Kind_Symbol then + + if First.S = Names.Def then + pragma Assert (Ast.L.Length = 3); + pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); + return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).S, R); + end return; + + elsif First.S = Names.Mal_Do then + for I in 2 .. Ast.L.Length - 1 loop + Discard (Eval (Ast.L.Element (I), Env)); + end loop; + return Eval (Ast.L.Element (Ast.L.Length), Env); + + elsif First.S = Names.Fn then + pragma Assert (Ast.L.Length = 3); + pragma Assert + (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); + pragma Assert + (for all I in 1 .. Ast.L.Element (2).L.Length => + Ast.L.Element (2).L.Element (I).Kind = Kind_Symbol); + pragma Assert + (Ast.L.Element (2).L.Length < 1 + or else Names.Ampersand /= + Ast.L.Element (2).L.Element (Ast.L.Element (2).L.Length).S); + pragma Assert + (for all I in 1 .. Ast.L.Element (2).L.Length - 2 => + Ast.L.Element (2).L.Element (I).S /= Names.Ampersand); + return (Kind => Kind_Function, + Meta => Atoms.No_Element, + Formals => Ast.L.Element (2).L, + Expression => Atoms.Alloc (Ast.L.Element (3)), + Environment => Env); + + elsif First.S = Names.Mal_If then + declare + pragma Assert (Ast.L.Length in 3 .. 4); + Test : constant Mal_Type := Eval (Ast.L.Element (2), Env); + begin + if (case Test.Kind is + when Kind_Nil => False, + when Kind_Boolean => Test.Boolean_Value, + when others => True) + then + return Eval (Ast.L.Element (3), Env); + elsif Ast.L.Length = 3 then + return (Kind_Nil, Atoms.No_Element); + else + return Eval (Ast.L.Element (4), Env); + end if; + end; + + elsif First.S = Names.Let then + declare + pragma Assert (Ast.L.Length = 3); + pragma Assert + (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + pragma Assert (Bindings.Length mod 2 = 0); + New_Env : constant Environments.Ptr + := Environments.Alloc (Outer => Env); + begin + New_Env.Increase_Capacity (Bindings.Length / 2); + for I in 1 .. Bindings.Length / 2 loop + pragma Assert + (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); + New_Env.Set (Bindings.Element (2 * I - 1).S, + Eval (Bindings.Element (2 * I), New_Env)); + end loop; + return Eval (Ast.L.Element (3), New_Env); + end; + end if; + end if; + + -- No special form has been found, attempt to apply the + -- first element to the rest of the list. + declare + Args : Mal_Type_Array (2 .. Ast.L.Length); + begin + First := Eval (First, Env); + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + case First.Kind is + when Kind_Native => + return First.Native.all (Args); + when Kind_Function => + declare + New_Env : constant Environments.Ptr + := Environments.Alloc (Outer => First.Environment); + begin + New_Env.Set_Binds (First.Formals, Args); + return Eval (First.Expression.Deref, New_Env); + end; + when others => + raise Unable_To_Call + with Ada.Strings.Unbounded.To_String (Print (First)); + end case; + end; + end case; + end Eval; + + procedure Interactive_Loop (Repl : in Environments.Ptr) + is + + function Readline (Prompt : in Interfaces.C.char_array) + return Interfaces.C.Strings.chars_ptr + with Import, Convention => C, External_Name => "readline"; + + procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "add_history"; + + procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "free"; + + Prompt : constant Interfaces.C.char_array + := Interfaces.C.To_C ("user> "); + C_Line : Interfaces.C.Strings.chars_ptr; + begin + loop + C_Line := Readline (Prompt); + exit when C_Line = Interfaces.C.Strings.Null_Ptr; + declare + Line : constant String := Interfaces.C.Strings.Value (C_Line); + begin + if Line /= "" then + Add_History (C_Line); + end if; + Free (C_Line); + Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); + exception + when Reader.Empty_Source => + null; + when E : others => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- but go on proceeding. + end; + end loop; + Ada.Text_IO.New_Line; + end Interactive_Loop; + + ---------------------------------------------------------------------- + + Repl : constant Environments.Ptr := Environments.Alloc; +begin + Core.Add_Built_In_Functions (Repl, Eval'Unrestricted_Access); + Discard (Eval (Read ("(def! not (fn* (a) (if a false true)))"), Repl)); + + Interactive_Loop (Repl); +end Step4_If_Fn_Do; diff --git a/ada2/step5_tco.adb b/ada2/step5_tco.adb new file mode 100644 index 0000000000..3838b6b3ad --- /dev/null +++ b/ada2/step5_tco.adb @@ -0,0 +1,234 @@ +with Ada.Exceptions; +with Ada.Strings.Unbounded; +with Ada.Text_IO.Unbounded_IO; +with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; +with Atoms; +with Core; +with Environments; +with Lists; +with Names; +with Printer; +with Reader; +with Strings; use type Strings.Ptr; +with Types; + +procedure Step5_Tco is + + function Read (Source : in String) return Types.Mal_Type + renames Reader.Read_Str; + + function Eval (Rec_Ast : in Types.Mal_Type; + Rec_Env : in Environments.Ptr) return Types.Mal_Type; + Unable_To_Call : exception; + + function Print (Ast : in Types.Mal_Type; + Print_Readably : in Boolean := True) + return Ada.Strings.Unbounded.Unbounded_String + renames Printer.Pr_Str; + + function Rep (Source : in String; + Env : in Environments.Ptr) + return Ada.Strings.Unbounded.Unbounded_String + is (Print (Eval (Read (Source), Env))) + with Inline; + + procedure Interactive_Loop (Repl : in Environments.Ptr) + with Inline; + + -- Convenient when the result of eval is of no interest. + procedure Discard (Ast : in Types.Mal_Type) is null; + + ---------------------------------------------------------------------- + + function Eval (Rec_Ast : in Types.Mal_Type; + Rec_Env : in Environments.Ptr) return Types.Mal_Type + is + use Types; + Ast : Types.Mal_Type := Rec_Ast; + Env : Environments.Ptr := Rec_Env; + First : Mal_Type; + begin + <> + case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => + return Ast; + + when Kind_Symbol => + return Env.Get (Ast.S); + + when Kind_Map => + declare + function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); + begin + return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); + end; + + when Kind_Vector => + return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, + Lists.Alloc (Ast.L.Length)) + do + for I in 1 .. Ast.L.Length loop + R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); + end loop; + end return; + + when Kind_List => + if Ast.L.Length = 0 then + return Ast; + end if; + + First := Ast.L.Element (1); + + -- Special forms + if First.Kind = Kind_Symbol then + + if First.S = Names.Def then + pragma Assert (Ast.L.Length = 3); + pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); + return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).S, R); + end return; + + elsif First.S = Names.Mal_Do then + for I in 2 .. Ast.L.Length - 1 loop + Discard (Eval (Ast.L.Element (I), Env)); + end loop; + Ast := Ast.L.Element (Ast.L.Length); + goto Restart; + + elsif First.S = Names.Fn then + pragma Assert (Ast.L.Length = 3); + pragma Assert + (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); + pragma Assert + (for all I in 1 .. Ast.L.Element (2).L.Length => + Ast.L.Element (2).L.Element (I).Kind = Kind_Symbol); + pragma Assert + (Ast.L.Element (2).L.Length < 1 + or else Names.Ampersand /= + Ast.L.Element (2).L.Element (Ast.L.Element (2).L.Length).S); + pragma Assert + (for all I in 1 .. Ast.L.Element (2).L.Length - 2 => + Ast.L.Element (2).L.Element (I).S /= Names.Ampersand); + return (Kind => Kind_Function, + Meta => Atoms.No_Element, + Formals => Ast.L.Element (2).L, + Expression => Atoms.Alloc (Ast.L.Element (3)), + Environment => Env); + + elsif First.S = Names.Mal_If then + declare + pragma Assert (Ast.L.Length in 3 .. 4); + Test : constant Mal_Type := Eval (Ast.L.Element (2), Env); + begin + if (case Test.Kind is + when Kind_Nil => False, + when Kind_Boolean => Test.Boolean_Value, + when others => True) + then + Ast := Ast.L.Element (3); + goto Restart; + elsif Ast.L.Length = 3 then + return (Kind_Nil, Atoms.No_Element); + else + Ast := Ast.L.Element (4); + goto Restart; + end if; + end; + + elsif First.S = Names.Let then + declare + pragma Assert (Ast.L.Length = 3); + pragma Assert + (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + pragma Assert (Bindings.Length mod 2 = 0); + begin + Env.Replace_With_Subenv; + Env.Increase_Capacity (Bindings.Length / 2); + for I in 1 .. Bindings.Length / 2 loop + pragma Assert + (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); + Env.Set (Bindings.Element (2 * I - 1).S, + Eval (Bindings.Element (2 * I), Env)); + end loop; + Ast := Ast.L.Element (3); + goto Restart; + end; + end if; + end if; + + -- No special form has been found, attempt to apply the + -- first element to the rest of the list. + declare + Args : Mal_Type_Array (2 .. Ast.L.Length); + begin + First := Eval (First, Env); + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + case First.Kind is + when Kind_Native => + return First.Native.all (Args); + when Kind_Function => + Env := Environments.Alloc (Outer => First.Environment); + Env.Set_Binds (First.Formals, Args); + Ast := First.Expression.Deref; + goto Restart; + when others => + raise Unable_To_Call + with Ada.Strings.Unbounded.To_String (Print (First)); + end case; + end; + end case; + end Eval; + + procedure Interactive_Loop (Repl : in Environments.Ptr) + is + + function Readline (Prompt : in Interfaces.C.char_array) + return Interfaces.C.Strings.chars_ptr + with Import, Convention => C, External_Name => "readline"; + + procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "add_history"; + + procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "free"; + + Prompt : constant Interfaces.C.char_array + := Interfaces.C.To_C ("user> "); + C_Line : Interfaces.C.Strings.chars_ptr; + begin + loop + C_Line := Readline (Prompt); + exit when C_Line = Interfaces.C.Strings.Null_Ptr; + declare + Line : constant String := Interfaces.C.Strings.Value (C_Line); + begin + if Line /= "" then + Add_History (C_Line); + end if; + Free (C_Line); + Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); + exception + when Reader.Empty_Source => + null; + when E : others => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- but go on proceeding. + end; + end loop; + Ada.Text_IO.New_Line; + end Interactive_Loop; + + ---------------------------------------------------------------------- + + Repl : constant Environments.Ptr := Environments.Alloc; +begin + Core.Add_Built_In_Functions (Repl, Eval'Unrestricted_Access); + Discard (Eval (Read ("(def! not (fn* (a) (if a false true)))"), Repl)); + + Interactive_Loop (Repl); +end Step5_Tco; diff --git a/ada2/step6_file.adb b/ada2/step6_file.adb new file mode 100644 index 0000000000..7670995b73 --- /dev/null +++ b/ada2/step6_file.adb @@ -0,0 +1,260 @@ +with Ada.Command_Line; +with Ada.Exceptions; +with Ada.Strings.Unbounded; +with Ada.Text_IO.Unbounded_IO; +with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; +with Atoms; +with Core; +with Environments; +with Lists; +with Names; +with Printer; +with Reader; +with Strings; use type Strings.Ptr; +with Types; + +procedure Step6_File is + + function Read (Source : in String) return Types.Mal_Type + renames Reader.Read_Str; + + function Eval (Rec_Ast : in Types.Mal_Type; + Rec_Env : in Environments.Ptr) return Types.Mal_Type; + Unable_To_Call : exception; + + function Print (Ast : in Types.Mal_Type; + Print_Readably : in Boolean := True) + return Ada.Strings.Unbounded.Unbounded_String + renames Printer.Pr_Str; + + function Rep (Source : in String; + Env : in Environments.Ptr) + return Ada.Strings.Unbounded.Unbounded_String + is (Print (Eval (Read (Source), Env))) + with Inline; + + procedure Interactive_Loop (Repl : in Environments.Ptr) + with Inline; + + -- Convenient when the result of eval is of no interest. + procedure Discard (Ast : in Types.Mal_Type) is null; + + -- Eval, with a profile compatible with Native_Function_Access. + function Eval_Native (Args : in Types.Mal_Type_Array) return Types.Mal_Type; + + ---------------------------------------------------------------------- + + function Eval (Rec_Ast : in Types.Mal_Type; + Rec_Env : in Environments.Ptr) return Types.Mal_Type + is + use Types; + Ast : Types.Mal_Type := Rec_Ast; + Env : Environments.Ptr := Rec_Env; + First : Mal_Type; + begin + <> + case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => + return Ast; + + when Kind_Symbol => + return Env.Get (Ast.S); + + when Kind_Map => + declare + function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); + begin + return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); + end; + + when Kind_Vector => + return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, + Lists.Alloc (Ast.L.Length)) + do + for I in 1 .. Ast.L.Length loop + R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); + end loop; + end return; + + when Kind_List => + if Ast.L.Length = 0 then + return Ast; + end if; + + First := Ast.L.Element (1); + + -- Special forms + if First.Kind = Kind_Symbol then + + if First.S = Names.Def then + pragma Assert (Ast.L.Length = 3); + pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); + return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).S, R); + end return; + + elsif First.S = Names.Mal_Do then + for I in 2 .. Ast.L.Length - 1 loop + Discard (Eval (Ast.L.Element (I), Env)); + end loop; + Ast := Ast.L.Element (Ast.L.Length); + goto Restart; + + elsif First.S = Names.Fn then + pragma Assert (Ast.L.Length = 3); + pragma Assert + (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); + pragma Assert + (for all I in 1 .. Ast.L.Element (2).L.Length => + Ast.L.Element (2).L.Element (I).Kind = Kind_Symbol); + pragma Assert + (Ast.L.Element (2).L.Length < 1 + or else Names.Ampersand /= + Ast.L.Element (2).L.Element (Ast.L.Element (2).L.Length).S); + pragma Assert + (for all I in 1 .. Ast.L.Element (2).L.Length - 2 => + Ast.L.Element (2).L.Element (I).S /= Names.Ampersand); + return (Kind => Kind_Function, + Meta => Atoms.No_Element, + Formals => Ast.L.Element (2).L, + Expression => Atoms.Alloc (Ast.L.Element (3)), + Environment => Env); + + elsif First.S = Names.Mal_If then + declare + pragma Assert (Ast.L.Length in 3 .. 4); + Test : constant Mal_Type := Eval (Ast.L.Element (2), Env); + begin + if (case Test.Kind is + when Kind_Nil => False, + when Kind_Boolean => Test.Boolean_Value, + when others => True) + then + Ast := Ast.L.Element (3); + goto Restart; + elsif Ast.L.Length = 3 then + return (Kind_Nil, Atoms.No_Element); + else + Ast := Ast.L.Element (4); + goto Restart; + end if; + end; + + elsif First.S = Names.Let then + declare + pragma Assert (Ast.L.Length = 3); + pragma Assert + (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + pragma Assert (Bindings.Length mod 2 = 0); + begin + Env.Replace_With_Subenv; + Env.Increase_Capacity (Bindings.Length / 2); + for I in 1 .. Bindings.Length / 2 loop + pragma Assert + (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); + Env.Set (Bindings.Element (2 * I - 1).S, + Eval (Bindings.Element (2 * I), Env)); + end loop; + Ast := Ast.L.Element (3); + goto Restart; + end; + end if; + end if; + + -- No special form has been found, attempt to apply the + -- first element to the rest of the list. + declare + Args : Mal_Type_Array (2 .. Ast.L.Length); + begin + First := Eval (First, Env); + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + case First.Kind is + when Kind_Native => + return First.Native.all (Args); + when Kind_Function => + Env := Environments.Alloc (Outer => First.Environment); + Env.Set_Binds (First.Formals, Args); + Ast := First.Expression.Deref; + goto Restart; + when others => + raise Unable_To_Call + with Ada.Strings.Unbounded.To_String (Print (First)); + end case; + end; + end case; + end Eval; + + procedure Interactive_Loop (Repl : in Environments.Ptr) + is + + function Readline (Prompt : in Interfaces.C.char_array) + return Interfaces.C.Strings.chars_ptr + with Import, Convention => C, External_Name => "readline"; + + procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "add_history"; + + procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "free"; + + Prompt : constant Interfaces.C.char_array + := Interfaces.C.To_C ("user> "); + C_Line : Interfaces.C.Strings.chars_ptr; + begin + loop + C_Line := Readline (Prompt); + exit when C_Line = Interfaces.C.Strings.Null_Ptr; + declare + Line : constant String := Interfaces.C.Strings.Value (C_Line); + begin + if Line /= "" then + Add_History (C_Line); + end if; + Free (C_Line); + Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); + exception + when Reader.Empty_Source => + null; + when E : others => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- but go on proceeding. + end; + end loop; + Ada.Text_IO.New_Line; + end Interactive_Loop; + + ---------------------------------------------------------------------- + + use Types; + Argv : Mal_Type (Kind_List); + Repl : constant Environments.Ptr := Environments.Alloc; + function Eval_Native (Args : in Mal_Type_Array) return Mal_Type is + (Eval (Args (Args'First), Repl)); +begin + Core.Add_Built_In_Functions (Repl, Eval'Unrestricted_Access); + Repl.Set (Names.Eval, Mal_Type'(Kind_Native, Atoms.No_Element, + Eval_Native'Unrestricted_Access)); + + Discard (Eval (Read ("(def! not (fn* (a) (if a false true)))"), Repl)); + Discard (Eval (Read ("(def! load-file (fn* (f) " + & "(eval (read-string (str ""(do "" (slurp f) "")"")))))"), Repl)); + + if Ada.Command_Line.Argument_Count = 0 then + Repl.Set (Names.Argv, Argv); + Interactive_Loop (Repl); + else + Argv.L := Lists.Alloc (Ada.Command_Line.Argument_Count - 1); + for I in 2 .. Ada.Command_Line.Argument_Count loop + Argv.L.Replace_Element (I - 1, + Mal_Type'(Kind_String, Atoms.No_Element, + Strings.Alloc (Ada.Command_Line.Argument (I)))); + end loop; + Repl.Set (Names.Argv, Argv); + Discard (Eval (Read ("(load-file """ & Ada.Command_Line.Argument (1) + & """)"), Repl)); + end if; +end Step6_File; diff --git a/ada2/step7_quote.adb b/ada2/step7_quote.adb new file mode 100644 index 0000000000..9286169aee --- /dev/null +++ b/ada2/step7_quote.adb @@ -0,0 +1,326 @@ +with Ada.Containers.Vectors; +with Ada.Command_Line; +with Ada.Exceptions; +with Ada.Strings.Unbounded; +with Ada.Text_IO.Unbounded_IO; +with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; +with Atoms; +with Core; +with Environments; +with Lists; +with Names; +with Printer; +with Reader; +with Strings; use type Strings.Ptr; +with Types; use type Types.Kind_Type; + +procedure Step7_Quote is + + function Read (Source : in String) return Types.Mal_Type + renames Reader.Read_Str; + + function Eval (Rec_Ast : in Types.Mal_Type; + Rec_Env : in Environments.Ptr) return Types.Mal_Type; + Unable_To_Call : exception; + + function Quasiquote (Ast : in Types.Mal_Type; + Env : in Environments.Ptr) return Types.Mal_Type; + function Quasiquote (List : in Lists.Ptr; + Env : in Environments.Ptr) return Lists.Ptr + with Inline; + -- Handle vectors and lists not starting with unquote. + + function Print (Ast : in Types.Mal_Type; + Print_Readably : in Boolean := True) + return Ada.Strings.Unbounded.Unbounded_String + renames Printer.Pr_Str; + + function Rep (Source : in String; + Env : in Environments.Ptr) + return Ada.Strings.Unbounded.Unbounded_String + is (Print (Eval (Read (Source), Env))) + with Inline; + + procedure Interactive_Loop (Repl : in Environments.Ptr) + with Inline; + + -- Convenient when the result of eval is of no interest. + procedure Discard (Ast : in Types.Mal_Type) is null; + + -- Eval, with a profile compatible with Native_Function_Access. + function Eval_Native (Args : in Types.Mal_Type_Array) return Types.Mal_Type; + + package Mal_Type_Vectors is new Ada.Containers.Vectors + (Index_Type => Positive, + Element_Type => Types.Mal_Type, + "=" => Types."="); + + ---------------------------------------------------------------------- + + function Eval (Rec_Ast : in Types.Mal_Type; + Rec_Env : in Environments.Ptr) return Types.Mal_Type + is + use Types; + Ast : Types.Mal_Type := Rec_Ast; + Env : Environments.Ptr := Rec_Env; + First : Mal_Type; + begin + <> + case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => + return Ast; + + when Kind_Symbol => + return Env.Get (Ast.S); + + when Kind_Map => + declare + function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); + begin + return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); + end; + + when Kind_Vector => + return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, + Lists.Alloc (Ast.L.Length)) + do + for I in 1 .. Ast.L.Length loop + R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); + end loop; + end return; + + when Kind_List => + if Ast.L.Length = 0 then + return Ast; + end if; + + First := Ast.L.Element (1); + + -- Special forms + if First.Kind = Kind_Symbol then + + if First.S = Names.Def then + pragma Assert (Ast.L.Length = 3); + pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); + return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).S, R); + end return; + + elsif First.S = Names.Mal_Do then + for I in 2 .. Ast.L.Length - 1 loop + Discard (Eval (Ast.L.Element (I), Env)); + end loop; + Ast := Ast.L.Element (Ast.L.Length); + goto Restart; + + elsif First.S = Names.Fn then + pragma Assert (Ast.L.Length = 3); + pragma Assert + (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); + pragma Assert + (for all I in 1 .. Ast.L.Element (2).L.Length => + Ast.L.Element (2).L.Element (I).Kind = Kind_Symbol); + pragma Assert + (Ast.L.Element (2).L.Length < 1 + or else Names.Ampersand /= + Ast.L.Element (2).L.Element (Ast.L.Element (2).L.Length).S); + pragma Assert + (for all I in 1 .. Ast.L.Element (2).L.Length - 2 => + Ast.L.Element (2).L.Element (I).S /= Names.Ampersand); + return (Kind => Kind_Function, + Meta => Atoms.No_Element, + Formals => Ast.L.Element (2).L, + Expression => Atoms.Alloc (Ast.L.Element (3)), + Environment => Env); + + elsif First.S = Names.Mal_If then + declare + pragma Assert (Ast.L.Length in 3 .. 4); + Test : constant Mal_Type := Eval (Ast.L.Element (2), Env); + begin + if (case Test.Kind is + when Kind_Nil => False, + when Kind_Boolean => Test.Boolean_Value, + when others => True) + then + Ast := Ast.L.Element (3); + goto Restart; + elsif Ast.L.Length = 3 then + return (Kind_Nil, Atoms.No_Element); + else + Ast := Ast.L.Element (4); + goto Restart; + end if; + end; + + elsif First.S = Names.Let then + declare + pragma Assert (Ast.L.Length = 3); + pragma Assert + (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + pragma Assert (Bindings.Length mod 2 = 0); + begin + Env.Replace_With_Subenv; + Env.Increase_Capacity (Bindings.Length / 2); + for I in 1 .. Bindings.Length / 2 loop + pragma Assert + (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); + Env.Set (Bindings.Element (2 * I - 1).S, + Eval (Bindings.Element (2 * I), Env)); + end loop; + Ast := Ast.L.Element (3); + goto Restart; + end; + + elsif First.S = Names.Quote then + pragma Assert (Ast.L.Length = 2); + return Ast.L.Element (2); + + elsif First.S = Names.Quasiquote then + pragma Assert (Ast.L.Length = 2); + return Quasiquote (Ast.L.Element (2), Env); + end if; + end if; + + -- No special form has been found, attempt to apply the + -- first element to the rest of the list. + declare + Args : Mal_Type_Array (2 .. Ast.L.Length); + begin + First := Eval (First, Env); + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + case First.Kind is + when Kind_Native => + return First.Native.all (Args); + when Kind_Function => + Env := Environments.Alloc (Outer => First.Environment); + Env.Set_Binds (First.Formals, Args); + Ast := First.Expression.Deref; + goto Restart; + when others => + raise Unable_To_Call + with Ada.Strings.Unbounded.To_String (Print (First)); + end case; + end; + end case; + end Eval; + + procedure Interactive_Loop (Repl : in Environments.Ptr) + is + + function Readline (Prompt : in Interfaces.C.char_array) + return Interfaces.C.Strings.chars_ptr + with Import, Convention => C, External_Name => "readline"; + + procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "add_history"; + + procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "free"; + + Prompt : constant Interfaces.C.char_array + := Interfaces.C.To_C ("user> "); + C_Line : Interfaces.C.Strings.chars_ptr; + begin + loop + C_Line := Readline (Prompt); + exit when C_Line = Interfaces.C.Strings.Null_Ptr; + declare + Line : constant String := Interfaces.C.Strings.Value (C_Line); + begin + if Line /= "" then + Add_History (C_Line); + end if; + Free (C_Line); + Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); + exception + when Reader.Empty_Source => + null; + when E : others => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- but go on proceeding. + end; + end loop; + Ada.Text_IO.New_Line; + end Interactive_Loop; + + function Quasiquote (Ast : in Types.Mal_Type; + Env : in Environments.Ptr) return Types.Mal_Type + is (case Ast.Kind is + when Types.Kind_Vector => + (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env)), + -- When the test is updated, replace Kind_List with Kind_Vector. + when Types.Kind_List => + (if 0 < Ast.L.Length + and then Ast.L.Element (1).Kind = Types.Kind_Symbol + and then Ast.L.Element (1).S = Names.Unquote + then Eval (Ast.L.Element (2), Env) + else (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env))), + when others => Ast); + + function Quasiquote (List : in Lists.Ptr; + Env : in Environments.Ptr) return Lists.Ptr + is + use Types; + Buffer : Mal_Type_Vectors.Vector; + Elt : Mal_Type; + begin + for I in 1 .. List.Length loop + Elt := List.Element (I); + if Elt.Kind in Kind_List | Kind_Vector + and then 0 < Elt.L.Length + and then Elt.L.Element (1).Kind = Kind_Symbol + and then Elt.L.Element (1).S = Names.Splice_Unquote + then + pragma Assert (Elt.L.Length = 2); + Elt := Eval (Elt.L.Element (2), Env); + pragma Assert (Elt.Kind = Kind_List); + for J in 1 .. Elt.L.Length loop + Buffer.Append (Elt.L.Element (J)); + end loop; + else + Buffer.Append (Quasiquote (Elt, Env)); + end if; + end loop; + return R : constant Lists.Ptr := Lists.Alloc (Natural (Buffer.Length)) do + for I in 1 .. R.Length loop + R.Replace_Element (I, Buffer.Element (I)); + end loop; + end return; + end Quasiquote; + + ---------------------------------------------------------------------- + + use Types; + Argv : Mal_Type (Kind_List); + Repl : constant Environments.Ptr := Environments.Alloc; + function Eval_Native (Args : in Mal_Type_Array) return Mal_Type is + (Eval (Args (Args'First), Repl)); +begin + Core.Add_Built_In_Functions (Repl, Eval'Unrestricted_Access); + Repl.Set (Names.Eval, Mal_Type'(Kind_Native, Atoms.No_Element, + Eval_Native'Unrestricted_Access)); + + Discard (Eval (Read ("(def! not (fn* (a) (if a false true)))"), Repl)); + Discard (Eval (Read ("(def! load-file (fn* (f) " + & "(eval (read-string (str ""(do "" (slurp f) "")"")))))"), Repl)); + + if Ada.Command_Line.Argument_Count = 0 then + Repl.Set (Names.Argv, Argv); + Interactive_Loop (Repl); + else + Argv.L := Lists.Alloc (Ada.Command_Line.Argument_Count - 1); + for I in 2 .. Ada.Command_Line.Argument_Count loop + Argv.L.Replace_Element (I - 1, + Mal_Type'(Kind_String, Atoms.No_Element, + Strings.Alloc (Ada.Command_Line.Argument (I)))); + end loop; + Repl.Set (Names.Argv, Argv); + Discard (Eval (Read ("(load-file """ & Ada.Command_Line.Argument (1) + & """)"), Repl)); + end if; +end Step7_Quote; diff --git a/ada2/step8_macros.adb b/ada2/step8_macros.adb new file mode 100644 index 0000000000..c85b0a3222 --- /dev/null +++ b/ada2/step8_macros.adb @@ -0,0 +1,383 @@ +with Ada.Containers.Vectors; +with Ada.Command_Line; +with Ada.Exceptions; +with Ada.Strings.Unbounded; +with Ada.Text_IO.Unbounded_IO; +with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; +with Atoms; +with Core; +with Environments; +with Lists; +with Names; +with Printer; +with Reader; +with Strings; use type Strings.Ptr; +with Types; use type Types.Kind_Type; + +procedure Step8_Macros is + + function Read (Source : in String) return Types.Mal_Type + renames Reader.Read_Str; + + function Eval (Rec_Ast : in Types.Mal_Type; + Rec_Env : in Environments.Ptr) return Types.Mal_Type; + Unable_To_Call : exception; + + function Quasiquote (Ast : in Types.Mal_Type; + Env : in Environments.Ptr) return Types.Mal_Type; + function Quasiquote (List : in Lists.Ptr; + Env : in Environments.Ptr) return Lists.Ptr + with Inline; + -- Handle vectors and lists not starting with unquote. + + function Print (Ast : in Types.Mal_Type; + Print_Readably : in Boolean := True) + return Ada.Strings.Unbounded.Unbounded_String + renames Printer.Pr_Str; + + function Rep (Source : in String; + Env : in Environments.Ptr) + return Ada.Strings.Unbounded.Unbounded_String + is (Print (Eval (Read (Source), Env))) + with Inline; + + procedure Interactive_Loop (Repl : in Environments.Ptr) + with Inline; + + -- Convenient when the result of eval is of no interest. + procedure Discard (Ast : in Types.Mal_Type) is null; + + -- Eval, with a profile compatible with Native_Function_Access. + function Eval_Native (Args : in Types.Mal_Type_Array) return Types.Mal_Type; + + package Mal_Type_Vectors is new Ada.Containers.Vectors + (Index_Type => Positive, + Element_Type => Types.Mal_Type, + "=" => Types."="); + + ---------------------------------------------------------------------- + + function Eval (Rec_Ast : in Types.Mal_Type; + Rec_Env : in Environments.Ptr) return Types.Mal_Type + is + use Types; + Ast : Types.Mal_Type := Rec_Ast; + Env : Environments.Ptr := Rec_Env; + Macroexpanding : Boolean := False; + First : Mal_Type; + begin + <> + case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => + return Ast; + + when Kind_Symbol => + return Env.Get (Ast.S); + + when Kind_Map => + declare + function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); + begin + return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); + end; + + when Kind_Vector => + return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, + Lists.Alloc (Ast.L.Length)) + do + for I in 1 .. Ast.L.Length loop + R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); + end loop; + end return; + + when Kind_List => + if Ast.L.Length = 0 then + return Ast; + end if; + + First := Ast.L.Element (1); + + -- Special forms + if First.Kind = Kind_Symbol then + + if First.S = Names.Def then + pragma Assert (Ast.L.Length = 3); + pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); + return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).S, R); + end return; + + elsif First.S = Names.Defmacro then + declare + pragma Assert (Ast.L.Length = 3); + pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); + F : constant Mal_Type := Eval (Ast.L.Element (3), Env); + pragma Assert (F.Kind = Kind_Function); + begin + return R : constant Mal_Type + := (Kind => Kind_Macro, + Meta => Atoms.No_Element, + Mac_Formals => F.Formals, + Mac_Expression => F.Expression) + do + Env.Set (Ast.L.Element (2).S, R); + end return; + end; + + elsif First.S = Names.Mal_Do then + for I in 2 .. Ast.L.Length - 1 loop + Discard (Eval (Ast.L.Element (I), Env)); + end loop; + Ast := Ast.L.Element (Ast.L.Length); + goto Restart; + + elsif First.S = Names.Fn then + pragma Assert (Ast.L.Length = 3); + pragma Assert + (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); + pragma Assert + (for all I in 1 .. Ast.L.Element (2).L.Length => + Ast.L.Element (2).L.Element (I).Kind = Kind_Symbol); + pragma Assert + (Ast.L.Element (2).L.Length < 1 + or else Names.Ampersand /= + Ast.L.Element (2).L.Element (Ast.L.Element (2).L.Length).S); + pragma Assert + (for all I in 1 .. Ast.L.Element (2).L.Length - 2 => + Ast.L.Element (2).L.Element (I).S /= Names.Ampersand); + return (Kind => Kind_Function, + Meta => Atoms.No_Element, + Formals => Ast.L.Element (2).L, + Expression => Atoms.Alloc (Ast.L.Element (3)), + Environment => Env); + + elsif First.S = Names.Mal_If then + declare + pragma Assert (Ast.L.Length in 3 .. 4); + Test : constant Mal_Type := Eval (Ast.L.Element (2), Env); + begin + if (case Test.Kind is + when Kind_Nil => False, + when Kind_Boolean => Test.Boolean_Value, + when others => True) + then + Ast := Ast.L.Element (3); + goto Restart; + elsif Ast.L.Length = 3 then + return (Kind_Nil, Atoms.No_Element); + else + Ast := Ast.L.Element (4); + goto Restart; + end if; + end; + + elsif First.S = Names.Let then + declare + pragma Assert (Ast.L.Length = 3); + pragma Assert + (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + pragma Assert (Bindings.Length mod 2 = 0); + begin + Env.Replace_With_Subenv; + Env.Increase_Capacity (Bindings.Length / 2); + for I in 1 .. Bindings.Length / 2 loop + pragma Assert + (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); + Env.Set (Bindings.Element (2 * I - 1).S, + Eval (Bindings.Element (2 * I), Env)); + end loop; + Ast := Ast.L.Element (3); + goto Restart; + end; + + elsif First.S = Names.Macroexpand then + pragma Assert (Ast.L.Length = 2); + Macroexpanding := True; + Ast := Ast.L.Element (2); + goto Restart; + + elsif First.S = Names.Quote then + pragma Assert (Ast.L.Length = 2); + return Ast.L.Element (2); + + elsif First.S = Names.Quasiquote then + pragma Assert (Ast.L.Length = 2); + return Quasiquote (Ast.L.Element (2), Env); + end if; + end if; + + -- No special form has been found, attempt to apply the + -- first element to the rest of the list. + declare + Args : Mal_Type_Array (2 .. Ast.L.Length); + begin + First := Eval (First, Env); + case First.Kind is + + when Kind_Native => + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + return First.Native.all (Args); + + when Kind_Function => + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + Env := Environments.Alloc (Outer => First.Environment); + Env.Set_Binds (First.Formals, Args); + Ast := First.Expression.Deref; + goto Restart; + + when Kind_Macro => + for I in Args'Range loop + Args (I) := Ast.L.Element (I); + end loop; + declare + New_Env : constant Environments.Ptr + := Environments.Alloc (Outer => Env); + begin + New_Env.Set_Binds (First.Mac_Formals, Args); + Ast := Eval (First.Mac_Expression.Deref, New_Env); + end; + if Macroexpanding then + return Ast; + end if; + goto Restart; + + when others => + raise Unable_To_Call + with Ada.Strings.Unbounded.To_String (Print (First)); + end case; + end; + end case; + end Eval; + + procedure Interactive_Loop (Repl : in Environments.Ptr) + is + + function Readline (Prompt : in Interfaces.C.char_array) + return Interfaces.C.Strings.chars_ptr + with Import, Convention => C, External_Name => "readline"; + + procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "add_history"; + + procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "free"; + + Prompt : constant Interfaces.C.char_array + := Interfaces.C.To_C ("user> "); + C_Line : Interfaces.C.Strings.chars_ptr; + begin + loop + C_Line := Readline (Prompt); + exit when C_Line = Interfaces.C.Strings.Null_Ptr; + declare + Line : constant String := Interfaces.C.Strings.Value (C_Line); + begin + if Line /= "" then + Add_History (C_Line); + end if; + Free (C_Line); + Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); + exception + when Reader.Empty_Source => + null; + when E : others => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- but go on proceeding. + end; + end loop; + Ada.Text_IO.New_Line; + end Interactive_Loop; + + function Quasiquote (Ast : in Types.Mal_Type; + Env : in Environments.Ptr) return Types.Mal_Type + is (case Ast.Kind is + when Types.Kind_Vector => + (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env)), + -- When the test is updated, replace Kind_List with Kind_Vector. + when Types.Kind_List => + (if 0 < Ast.L.Length + and then Ast.L.Element (1).Kind = Types.Kind_Symbol + and then Ast.L.Element (1).S = Names.Unquote + then Eval (Ast.L.Element (2), Env) + else (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env))), + when others => Ast); + + function Quasiquote (List : in Lists.Ptr; + Env : in Environments.Ptr) return Lists.Ptr + is + use Types; + Buffer : Mal_Type_Vectors.Vector; + Elt : Mal_Type; + begin + for I in 1 .. List.Length loop + Elt := List.Element (I); + if Elt.Kind in Kind_List | Kind_Vector + and then 0 < Elt.L.Length + and then Elt.L.Element (1).Kind = Kind_Symbol + and then Elt.L.Element (1).S = Names.Splice_Unquote + then + pragma Assert (Elt.L.Length = 2); + Elt := Eval (Elt.L.Element (2), Env); + pragma Assert (Elt.Kind = Kind_List); + for J in 1 .. Elt.L.Length loop + Buffer.Append (Elt.L.Element (J)); + end loop; + else + Buffer.Append (Quasiquote (Elt, Env)); + end if; + end loop; + return R : constant Lists.Ptr := Lists.Alloc (Natural (Buffer.Length)) do + for I in 1 .. R.Length loop + R.Replace_Element (I, Buffer.Element (I)); + end loop; + end return; + end Quasiquote; + + ---------------------------------------------------------------------- + + use Types; + Argv : Mal_Type (Kind_List); + Repl : constant Environments.Ptr := Environments.Alloc; + function Eval_Native (Args : in Mal_Type_Array) return Mal_Type is + (Eval (Args (Args'First), Repl)); +begin + Core.Add_Built_In_Functions (Repl, Eval'Unrestricted_Access); + Repl.Set (Names.Eval, Mal_Type'(Kind_Native, Atoms.No_Element, + Eval_Native'Unrestricted_Access)); + + Discard (Eval (Read ("(def! not (fn* (a) (if a false true)))"), Repl)); + Discard (Eval (Read ("(def! load-file (fn* (f) " + & "(eval (read-string (str ""(do "" (slurp f) "")"")))))"), Repl)); + Discard (Eval (Read ("(defmacro! cond " + & "(fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) " + & "(if (> (count xs) 1) (nth xs 1) " + & "(throw ""odd number of forms to cond"")) " + & "(cons 'cond (rest (rest xs)))))))"), Repl)); + Discard (Eval (Read ("(defmacro! or (fn* (& xs) " + & "(if (empty? xs) nil " + & "(if (= 1 (count xs)) (first xs) " + & "`(let* (or_FIXME ~(first xs)) " + & "(if or_FIXME or_FIXME " + & "(or ~@(rest xs))))))))"), Repl)); + + if Ada.Command_Line.Argument_Count = 0 then + Repl.Set (Names.Argv, Argv); + Interactive_Loop (Repl); + else + Argv.L := Lists.Alloc (Ada.Command_Line.Argument_Count - 1); + for I in 2 .. Ada.Command_Line.Argument_Count loop + Argv.L.Replace_Element (I - 1, + Mal_Type'(Kind_String, Atoms.No_Element, + Strings.Alloc (Ada.Command_Line.Argument (I)))); + end loop; + Repl.Set (Names.Argv, Argv); + Discard (Eval (Read ("(load-file """ & Ada.Command_Line.Argument (1) + & """)"), Repl)); + end if; +end Step8_Macros; diff --git a/ada2/step9_try.adb b/ada2/step9_try.adb new file mode 100644 index 0000000000..6651b5ba30 --- /dev/null +++ b/ada2/step9_try.adb @@ -0,0 +1,411 @@ +with Ada.Containers.Vectors; +with Ada.Command_Line; +with Ada.Exceptions; use type Ada.Exceptions.Exception_Id; +with Ada.Strings.Unbounded; +with Ada.Text_IO.Unbounded_IO; +with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; +with Atoms; +with Core; +with Environments; +with Lists; +with Names; +with Printer; +with Reader; +with Strings; use type Strings.Ptr; +with Types; use type Types.Kind_Type; + +procedure Step9_Try is + + function Read (Source : in String) return Types.Mal_Type + renames Reader.Read_Str; + + function Eval (Rec_Ast : in Types.Mal_Type; + Rec_Env : in Environments.Ptr) return Types.Mal_Type; + Unable_To_Call : exception; + + function Quasiquote (Ast : in Types.Mal_Type; + Env : in Environments.Ptr) return Types.Mal_Type; + function Quasiquote (List : in Lists.Ptr; + Env : in Environments.Ptr) return Lists.Ptr + with Inline; + -- Handle vectors and lists not starting with unquote. + + function Print (Ast : in Types.Mal_Type; + Print_Readably : in Boolean := True) + return Ada.Strings.Unbounded.Unbounded_String + renames Printer.Pr_Str; + + function Rep (Source : in String; + Env : in Environments.Ptr) + return Ada.Strings.Unbounded.Unbounded_String + is (Print (Eval (Read (Source), Env))) + with Inline; + + procedure Interactive_Loop (Repl : in Environments.Ptr) + with Inline; + + -- Convenient when the result of eval is of no interest. + procedure Discard (Ast : in Types.Mal_Type) is null; + + -- Eval, with a profile compatible with Native_Function_Access. + function Eval_Native (Args : in Types.Mal_Type_Array) return Types.Mal_Type; + + package Mal_Type_Vectors is new Ada.Containers.Vectors + (Index_Type => Positive, + Element_Type => Types.Mal_Type, + "=" => Types."="); + + ---------------------------------------------------------------------- + + function Eval (Rec_Ast : in Types.Mal_Type; + Rec_Env : in Environments.Ptr) return Types.Mal_Type + is + use Types; + Ast : Types.Mal_Type := Rec_Ast; + Env : Environments.Ptr := Rec_Env; + Macroexpanding : Boolean := False; + First : Mal_Type; + begin + <> + case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => + return Ast; + + when Kind_Symbol => + return Env.Get (Ast.S); + + when Kind_Map => + declare + function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); + begin + return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); + end; + + when Kind_Vector => + return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, + Lists.Alloc (Ast.L.Length)) + do + for I in 1 .. Ast.L.Length loop + R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); + end loop; + end return; + + when Kind_List => + if Ast.L.Length = 0 then + return Ast; + end if; + + First := Ast.L.Element (1); + + -- Special forms + if First.Kind = Kind_Symbol then + + if First.S = Names.Def then + pragma Assert (Ast.L.Length = 3); + pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); + return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).S, R); + end return; + + elsif First.S = Names.Defmacro then + declare + pragma Assert (Ast.L.Length = 3); + pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); + F : constant Mal_Type := Eval (Ast.L.Element (3), Env); + pragma Assert (F.Kind = Kind_Function); + begin + return R : constant Mal_Type + := (Kind => Kind_Macro, + Meta => Atoms.No_Element, + Mac_Formals => F.Formals, + Mac_Expression => F.Expression) + do + Env.Set (Ast.L.Element (2).S, R); + end return; + end; + + elsif First.S = Names.Mal_Do then + for I in 2 .. Ast.L.Length - 1 loop + Discard (Eval (Ast.L.Element (I), Env)); + end loop; + Ast := Ast.L.Element (Ast.L.Length); + goto Restart; + + elsif First.S = Names.Fn then + pragma Assert (Ast.L.Length = 3); + pragma Assert + (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); + pragma Assert + (for all I in 1 .. Ast.L.Element (2).L.Length => + Ast.L.Element (2).L.Element (I).Kind = Kind_Symbol); + pragma Assert + (Ast.L.Element (2).L.Length < 1 + or else Names.Ampersand /= + Ast.L.Element (2).L.Element (Ast.L.Element (2).L.Length).S); + pragma Assert + (for all I in 1 .. Ast.L.Element (2).L.Length - 2 => + Ast.L.Element (2).L.Element (I).S /= Names.Ampersand); + return (Kind => Kind_Function, + Meta => Atoms.No_Element, + Formals => Ast.L.Element (2).L, + Expression => Atoms.Alloc (Ast.L.Element (3)), + Environment => Env); + + elsif First.S = Names.Mal_If then + declare + pragma Assert (Ast.L.Length in 3 .. 4); + Test : constant Mal_Type := Eval (Ast.L.Element (2), Env); + begin + if (case Test.Kind is + when Kind_Nil => False, + when Kind_Boolean => Test.Boolean_Value, + when others => True) + then + Ast := Ast.L.Element (3); + goto Restart; + elsif Ast.L.Length = 3 then + return (Kind_Nil, Atoms.No_Element); + else + Ast := Ast.L.Element (4); + goto Restart; + end if; + end; + + elsif First.S = Names.Let then + declare + pragma Assert (Ast.L.Length = 3); + pragma Assert + (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + pragma Assert (Bindings.Length mod 2 = 0); + begin + Env.Replace_With_Subenv; + Env.Increase_Capacity (Bindings.Length / 2); + for I in 1 .. Bindings.Length / 2 loop + pragma Assert + (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); + Env.Set (Bindings.Element (2 * I - 1).S, + Eval (Bindings.Element (2 * I), Env)); + end loop; + Ast := Ast.L.Element (3); + goto Restart; + end; + + elsif First.S = Names.Macroexpand then + pragma Assert (Ast.L.Length = 2); + Macroexpanding := True; + Ast := Ast.L.Element (2); + goto Restart; + + elsif First.S = Names.Quote then + pragma Assert (Ast.L.Length = 2); + return Ast.L.Element (2); + + elsif First.S = Names.Quasiquote then + pragma Assert (Ast.L.Length = 2); + return Quasiquote (Ast.L.Element (2), Env); + + elsif First.S = Names.Try then + declare + pragma Assert (Ast.L.Length = 3); + pragma Assert (Ast.L.Element (3).Kind = Kind_List); + A3 : constant Lists.Ptr := Ast.L.Element (3).L; + pragma Assert (A3.Length = 3); + pragma Assert (A3.Element (1).Kind = Kind_Symbol); + pragma Assert (A3.Element (1).S = Names.Catch); + pragma Assert (A3.Element (2).Kind = Kind_Symbol); + begin + return Eval (Ast.L.Element (2), Env); + exception + when E : others => + Env.Replace_With_Subenv; + if Ada.Exceptions.Exception_Identity (E) + = Core.Exception_Throwed'Identity + then + Env.Set (A3.Element (2).S, Core.Last_Exception); + Core.Last_Exception := (Kind_Nil, Atoms.No_Element); + else + Env.Set (A3.Element (2).S, Mal_Type' + (Kind_String, Atoms.No_Element, Strings.Alloc + (Ada.Exceptions.Exception_Message (E)))); + end if; + Ast := A3.Element (3); + goto Restart; + end; + end if; + end if; + + -- No special form has been found, attempt to apply the + -- first element to the rest of the list. + declare + Args : Mal_Type_Array (2 .. Ast.L.Length); + begin + First := Eval (First, Env); + case First.Kind is + + when Kind_Native => + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + return First.Native.all (Args); + + when Kind_Function => + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + Env := Environments.Alloc (Outer => First.Environment); + Env.Set_Binds (First.Formals, Args); + Ast := First.Expression.Deref; + goto Restart; + + when Kind_Macro => + for I in Args'Range loop + Args (I) := Ast.L.Element (I); + end loop; + declare + New_Env : constant Environments.Ptr + := Environments.Alloc (Outer => Env); + begin + New_Env.Set_Binds (First.Mac_Formals, Args); + Ast := Eval (First.Mac_Expression.Deref, New_Env); + end; + if Macroexpanding then + return Ast; + end if; + goto Restart; + + when others => + raise Unable_To_Call + with Ada.Strings.Unbounded.To_String (Print (First)); + end case; + end; + end case; + end Eval; + + procedure Interactive_Loop (Repl : in Environments.Ptr) + is + + function Readline (Prompt : in Interfaces.C.char_array) + return Interfaces.C.Strings.chars_ptr + with Import, Convention => C, External_Name => "readline"; + + procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "add_history"; + + procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "free"; + + Prompt : constant Interfaces.C.char_array + := Interfaces.C.To_C ("user> "); + C_Line : Interfaces.C.Strings.chars_ptr; + begin + loop + C_Line := Readline (Prompt); + exit when C_Line = Interfaces.C.Strings.Null_Ptr; + declare + Line : constant String := Interfaces.C.Strings.Value (C_Line); + begin + if Line /= "" then + Add_History (C_Line); + end if; + Free (C_Line); + Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); + exception + when Reader.Empty_Source => + null; + when E : others => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- but go on proceeding. + end; + end loop; + Ada.Text_IO.New_Line; + end Interactive_Loop; + + function Quasiquote (Ast : in Types.Mal_Type; + Env : in Environments.Ptr) return Types.Mal_Type + is (case Ast.Kind is + when Types.Kind_Vector => + (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env)), + -- When the test is updated, replace Kind_List with Kind_Vector. + when Types.Kind_List => + (if 0 < Ast.L.Length + and then Ast.L.Element (1).Kind = Types.Kind_Symbol + and then Ast.L.Element (1).S = Names.Unquote + then Eval (Ast.L.Element (2), Env) + else (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env))), + when others => Ast); + + function Quasiquote (List : in Lists.Ptr; + Env : in Environments.Ptr) return Lists.Ptr + is + use Types; + Buffer : Mal_Type_Vectors.Vector; + Elt : Mal_Type; + begin + for I in 1 .. List.Length loop + Elt := List.Element (I); + if Elt.Kind in Kind_List | Kind_Vector + and then 0 < Elt.L.Length + and then Elt.L.Element (1).Kind = Kind_Symbol + and then Elt.L.Element (1).S = Names.Splice_Unquote + then + pragma Assert (Elt.L.Length = 2); + Elt := Eval (Elt.L.Element (2), Env); + pragma Assert (Elt.Kind = Kind_List); + for J in 1 .. Elt.L.Length loop + Buffer.Append (Elt.L.Element (J)); + end loop; + else + Buffer.Append (Quasiquote (Elt, Env)); + end if; + end loop; + return R : constant Lists.Ptr := Lists.Alloc (Natural (Buffer.Length)) do + for I in 1 .. R.Length loop + R.Replace_Element (I, Buffer.Element (I)); + end loop; + end return; + end Quasiquote; + + ---------------------------------------------------------------------- + + use Types; + Argv : Mal_Type (Kind_List); + Repl : constant Environments.Ptr := Environments.Alloc; + function Eval_Native (Args : in Mal_Type_Array) return Mal_Type is + (Eval (Args (Args'First), Repl)); +begin + Core.Add_Built_In_Functions (Repl, Eval'Unrestricted_Access); + Repl.Set (Names.Eval, Mal_Type'(Kind_Native, Atoms.No_Element, + Eval_Native'Unrestricted_Access)); + + Discard (Eval (Read ("(def! not (fn* (a) (if a false true)))"), Repl)); + Discard (Eval (Read ("(def! load-file (fn* (f) " + & "(eval (read-string (str ""(do "" (slurp f) "")"")))))"), Repl)); + Discard (Eval (Read ("(defmacro! cond " + & "(fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) " + & "(if (> (count xs) 1) (nth xs 1) " + & "(throw ""odd number of forms to cond"")) " + & "(cons 'cond (rest (rest xs)))))))"), Repl)); + Discard (Eval (Read ("(defmacro! or (fn* (& xs) " + & "(if (empty? xs) nil " + & "(if (= 1 (count xs)) (first xs) " + & "`(let* (or_FIXME ~(first xs)) " + & "(if or_FIXME or_FIXME " + & "(or ~@(rest xs))))))))"), Repl)); + + if Ada.Command_Line.Argument_Count = 0 then + Repl.Set (Names.Argv, Argv); + Interactive_Loop (Repl); + else + Argv.L := Lists.Alloc (Ada.Command_Line.Argument_Count - 1); + for I in 2 .. Ada.Command_Line.Argument_Count loop + Argv.L.Replace_Element (I - 1, + Mal_Type'(Kind_String, Atoms.No_Element, + Strings.Alloc (Ada.Command_Line.Argument (I)))); + end loop; + Repl.Set (Names.Argv, Argv); + Discard (Eval (Read ("(load-file """ & Ada.Command_Line.Argument (1) + & """)"), Repl)); + end if; +end Step9_Try; diff --git a/ada2/stepa_mal.adb b/ada2/stepa_mal.adb new file mode 100644 index 0000000000..a695b6e52e --- /dev/null +++ b/ada2/stepa_mal.adb @@ -0,0 +1,417 @@ +with Ada.Containers.Vectors; +with Ada.Command_Line; +with Ada.Exceptions; use type Ada.Exceptions.Exception_Id; +with Ada.Strings.Unbounded; +with Ada.Text_IO.Unbounded_IO; +with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; +with Atoms; +with Core; +with Environments; +with Lists; +with Names; +with Printer; +with Reader; +with Strings; use type Strings.Ptr; +with Types; use type Types.Kind_Type; + +procedure StepA_Mal is + + function Read (Source : in String) return Types.Mal_Type + renames Reader.Read_Str; + + function Eval (Rec_Ast : in Types.Mal_Type; + Rec_Env : in Environments.Ptr) return Types.Mal_Type; + Unable_To_Call : exception; + + function Quasiquote (Ast : in Types.Mal_Type; + Env : in Environments.Ptr) return Types.Mal_Type; + function Quasiquote (List : in Lists.Ptr; + Env : in Environments.Ptr) return Lists.Ptr + with Inline; + -- Handle vectors and lists not starting with unquote. + + function Print (Ast : in Types.Mal_Type; + Print_Readably : in Boolean := True) + return Ada.Strings.Unbounded.Unbounded_String + renames Printer.Pr_Str; + + function Rep (Source : in String; + Env : in Environments.Ptr) + return Ada.Strings.Unbounded.Unbounded_String + is (Print (Eval (Read (Source), Env))) + with Inline; + + procedure Interactive_Loop (Repl : in Environments.Ptr) + with Inline; + + -- Convenient when the result of eval is of no interest. + procedure Discard (Ast : in Types.Mal_Type) is null; + + -- Eval, with a profile compatible with Native_Function_Access. + function Eval_Native (Args : in Types.Mal_Type_Array) return Types.Mal_Type; + + package Mal_Type_Vectors is new Ada.Containers.Vectors + (Index_Type => Positive, + Element_Type => Types.Mal_Type, + "=" => Types."="); + + ---------------------------------------------------------------------- + + function Eval (Rec_Ast : in Types.Mal_Type; + Rec_Env : in Environments.Ptr) return Types.Mal_Type + is + use Types; + Ast : Types.Mal_Type := Rec_Ast; + Env : Environments.Ptr := Rec_Env; + Macroexpanding : Boolean := False; + First : Mal_Type; + begin + <> + case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => + return Ast; + + when Kind_Symbol => + return Env.Get (Ast.S); + + when Kind_Map => + declare + function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); + begin + return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); + end; + + when Kind_Vector => + return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, + Lists.Alloc (Ast.L.Length)) + do + for I in 1 .. Ast.L.Length loop + R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); + end loop; + end return; + + when Kind_List => + if Ast.L.Length = 0 then + return Ast; + end if; + + First := Ast.L.Element (1); + + -- Special forms + if First.Kind = Kind_Symbol then + + if First.S = Names.Def then + pragma Assert (Ast.L.Length = 3); + pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); + return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).S, R); + end return; + + elsif First.S = Names.Defmacro then + declare + pragma Assert (Ast.L.Length = 3); + pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); + F : constant Mal_Type := Eval (Ast.L.Element (3), Env); + pragma Assert (F.Kind = Kind_Function); + begin + return R : constant Mal_Type + := (Kind => Kind_Macro, + Meta => Atoms.No_Element, + Mac_Formals => F.Formals, + Mac_Expression => F.Expression) + do + Env.Set (Ast.L.Element (2).S, R); + end return; + end; + + elsif First.S = Names.Mal_Do then + for I in 2 .. Ast.L.Length - 1 loop + Discard (Eval (Ast.L.Element (I), Env)); + end loop; + Ast := Ast.L.Element (Ast.L.Length); + goto Restart; + + elsif First.S = Names.Fn then + pragma Assert (Ast.L.Length = 3); + pragma Assert + (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); + pragma Assert + (for all I in 1 .. Ast.L.Element (2).L.Length => + Ast.L.Element (2).L.Element (I).Kind = Kind_Symbol); + pragma Assert + (Ast.L.Element (2).L.Length < 1 + or else Names.Ampersand /= + Ast.L.Element (2).L.Element (Ast.L.Element (2).L.Length).S); + pragma Assert + (for all I in 1 .. Ast.L.Element (2).L.Length - 2 => + Ast.L.Element (2).L.Element (I).S /= Names.Ampersand); + return (Kind => Kind_Function, + Meta => Atoms.No_Element, + Formals => Ast.L.Element (2).L, + Expression => Atoms.Alloc (Ast.L.Element (3)), + Environment => Env); + + elsif First.S = Names.Mal_If then + declare + pragma Assert (Ast.L.Length in 3 .. 4); + Test : constant Mal_Type := Eval (Ast.L.Element (2), Env); + begin + if (case Test.Kind is + when Kind_Nil => False, + when Kind_Boolean => Test.Boolean_Value, + when others => True) + then + Ast := Ast.L.Element (3); + goto Restart; + elsif Ast.L.Length = 3 then + return (Kind_Nil, Atoms.No_Element); + else + Ast := Ast.L.Element (4); + goto Restart; + end if; + end; + + elsif First.S = Names.Let then + declare + pragma Assert (Ast.L.Length = 3); + pragma Assert + (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + pragma Assert (Bindings.Length mod 2 = 0); + begin + Env.Replace_With_Subenv; + Env.Increase_Capacity (Bindings.Length / 2); + for I in 1 .. Bindings.Length / 2 loop + pragma Assert + (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); + Env.Set (Bindings.Element (2 * I - 1).S, + Eval (Bindings.Element (2 * I), Env)); + end loop; + Ast := Ast.L.Element (3); + goto Restart; + end; + + elsif First.S = Names.Macroexpand then + pragma Assert (Ast.L.Length = 2); + Macroexpanding := True; + Ast := Ast.L.Element (2); + goto Restart; + + elsif First.S = Names.Quote then + pragma Assert (Ast.L.Length = 2); + return Ast.L.Element (2); + + elsif First.S = Names.Quasiquote then + pragma Assert (Ast.L.Length = 2); + return Quasiquote (Ast.L.Element (2), Env); + + elsif First.S = Names.Try then + declare + pragma Assert (Ast.L.Length = 3); + pragma Assert (Ast.L.Element (3).Kind = Kind_List); + A3 : constant Lists.Ptr := Ast.L.Element (3).L; + pragma Assert (A3.Length = 3); + pragma Assert (A3.Element (1).Kind = Kind_Symbol); + pragma Assert (A3.Element (1).S = Names.Catch); + pragma Assert (A3.Element (2).Kind = Kind_Symbol); + begin + return Eval (Ast.L.Element (2), Env); + exception + when E : others => + Env.Replace_With_Subenv; + if Ada.Exceptions.Exception_Identity (E) + = Core.Exception_Throwed'Identity + then + Env.Set (A3.Element (2).S, Core.Last_Exception); + Core.Last_Exception := (Kind_Nil, Atoms.No_Element); + else + Env.Set (A3.Element (2).S, Mal_Type' + (Kind_String, Atoms.No_Element, Strings.Alloc + (Ada.Exceptions.Exception_Message (E)))); + end if; + Ast := A3.Element (3); + goto Restart; + end; + end if; + end if; + + -- No special form has been found, attempt to apply the + -- first element to the rest of the list. + declare + Args : Mal_Type_Array (2 .. Ast.L.Length); + begin + First := Eval (First, Env); + case First.Kind is + + when Kind_Native => + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + return First.Native.all (Args); + + when Kind_Function => + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + Env := Environments.Alloc (Outer => First.Environment); + Env.Set_Binds (First.Formals, Args); + Ast := First.Expression.Deref; + goto Restart; + + when Kind_Macro => + for I in Args'Range loop + Args (I) := Ast.L.Element (I); + end loop; + declare + New_Env : constant Environments.Ptr + := Environments.Alloc (Outer => Env); + begin + New_Env.Set_Binds (First.Mac_Formals, Args); + Ast := Eval (First.Mac_Expression.Deref, New_Env); + end; + if Macroexpanding then + return Ast; + end if; + goto Restart; + + when others => + raise Unable_To_Call + with Ada.Strings.Unbounded.To_String (Print (First)); + end case; + end; + end case; + end Eval; + + procedure Interactive_Loop (Repl : in Environments.Ptr) + is + + function Readline (Prompt : in Interfaces.C.char_array) + return Interfaces.C.Strings.chars_ptr + with Import, Convention => C, External_Name => "readline"; + + procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "add_history"; + + procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + with Import, Convention => C, External_Name => "free"; + + Prompt : constant Interfaces.C.char_array + := Interfaces.C.To_C ("user> "); + C_Line : Interfaces.C.Strings.chars_ptr; + begin + loop + C_Line := Readline (Prompt); + exit when C_Line = Interfaces.C.Strings.Null_Ptr; + declare + Line : constant String := Interfaces.C.Strings.Value (C_Line); + begin + if Line /= "" then + Add_History (C_Line); + end if; + Free (C_Line); + Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); + exception + when Reader.Empty_Source => + null; + when E : others => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- but go on proceeding. + end; + end loop; + Ada.Text_IO.New_Line; + end Interactive_Loop; + + function Quasiquote (Ast : in Types.Mal_Type; + Env : in Environments.Ptr) return Types.Mal_Type + is (case Ast.Kind is + when Types.Kind_Vector => + (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env)), + -- When the test is updated, replace Kind_List with Kind_Vector. + when Types.Kind_List => + (if 0 < Ast.L.Length + and then Ast.L.Element (1).Kind = Types.Kind_Symbol + and then Ast.L.Element (1).S = Names.Unquote + then Eval (Ast.L.Element (2), Env) + else (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env))), + when others => Ast); + + function Quasiquote (List : in Lists.Ptr; + Env : in Environments.Ptr) return Lists.Ptr + is + use Types; + Buffer : Mal_Type_Vectors.Vector; + Elt : Mal_Type; + begin + for I in 1 .. List.Length loop + Elt := List.Element (I); + if Elt.Kind in Kind_List | Kind_Vector + and then 0 < Elt.L.Length + and then Elt.L.Element (1).Kind = Kind_Symbol + and then Elt.L.Element (1).S = Names.Splice_Unquote + then + pragma Assert (Elt.L.Length = 2); + Elt := Eval (Elt.L.Element (2), Env); + pragma Assert (Elt.Kind = Kind_List); + for J in 1 .. Elt.L.Length loop + Buffer.Append (Elt.L.Element (J)); + end loop; + else + Buffer.Append (Quasiquote (Elt, Env)); + end if; + end loop; + return R : constant Lists.Ptr := Lists.Alloc (Natural (Buffer.Length)) do + for I in 1 .. R.Length loop + R.Replace_Element (I, Buffer.Element (I)); + end loop; + end return; + end Quasiquote; + + ---------------------------------------------------------------------- + + use Types; + Argv : Mal_Type (Kind_List); + Repl : constant Environments.Ptr := Environments.Alloc; + function Eval_Native (Args : in Mal_Type_Array) return Mal_Type is + (Eval (Args (Args'First), Repl)); +begin + Core.Add_Built_In_Functions (Repl, Eval'Unrestricted_Access); + Repl.Set (Names.Eval, Mal_Type'(Kind_Native, Atoms.No_Element, + Eval_Native'Unrestricted_Access)); + + Discard (Eval (Read ("(def! not (fn* (a) (if a false true)))"), Repl)); + Discard (Eval (Read ("(def! load-file (fn* (f) " + & "(eval (read-string (str ""(do "" (slurp f) "")"")))))"), Repl)); + Discard (Eval (Read ("(defmacro! cond " + & "(fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) " + & "(if (> (count xs) 1) (nth xs 1) " + & "(throw ""odd number of forms to cond"")) " + & "(cons 'cond (rest (rest xs)))))))"), Repl)); + Discard (Eval (Read ("(def! *gensym-counter* (atom 0))"), Repl)); + Discard (Eval (Read ("(def! gensym (fn* [] (symbol (str ""G__"" " + & "(swap! *gensym-counter* (fn* [x] (+ 1 x)))))))"), Repl)); + Discard (Eval (Read ("(defmacro! or (fn* (& xs) (if (empty? xs) nil " + & "(if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) " + & "`(let* (~condvar ~(first xs)) (if ~condvar ~condvar " + & "(or ~@(rest xs)))))))))"), Repl)); + + Repl.Set (Names.Host_Language, + Mal_Type'(Kind_Symbol, Atoms.No_Element, Names.Ada2)); + + if Ada.Command_Line.Argument_Count = 0 then + Repl.Set (Names.Argv, Argv); + Discard (Eval (Read ("(println (str ""Mal ["" *host-language* ""]""))"), + Repl)); + Interactive_Loop (Repl); + else + Argv.L := Lists.Alloc (Ada.Command_Line.Argument_Count - 1); + for I in 2 .. Ada.Command_Line.Argument_Count loop + Argv.L.Replace_Element (I - 1, + Mal_Type'(Kind_String, Atoms.No_Element, + Strings.Alloc (Ada.Command_Line.Argument (I)))); + end loop; + Repl.Set (Names.Argv, Argv); + Discard (Eval (Read ("(load-file """ & Ada.Command_Line.Argument (1) + & """)"), Repl)); + end if; +end StepA_Mal; diff --git a/ada2/strings.adb b/ada2/strings.adb new file mode 100644 index 0000000000..6cee7acf03 --- /dev/null +++ b/ada2/strings.adb @@ -0,0 +1,62 @@ +with Ada.Strings.Hash; + +package body Strings is + + Dict : Sets.Set; + Empty_Hash : constant Ada.Containers.Hash_Type := Ada.Strings.Hash (""); + + ---------------------------------------------------------------------- + + procedure Adjust (Object : in out Ptr) is + begin + if Sets.Has_Element (Object.Position) then + Dict (Object.Position).Refs := Dict (Object.Position).Refs + 1; + end if; + end Adjust; + + function Alloc (Source : in String) return Ptr + is + Inserted : Boolean; + Position : Sets.Cursor; + begin + if Source /= "" then + Sets.Insert (Dict, + (Data => Source, + Hash => Ada.Strings.Hash (Source), + Last => Source'Length, + Refs => 1), + Position, + Inserted); + if not Inserted then + Dict (Position).Refs := Dict (Position).Refs + 1; + end if; + end if; + return (Ada.Finalization.Controlled with Position => Position); + end Alloc; + + function Deref (Source : in Ptr) return String is + (if Sets.Has_Element (Source.Position) + then Dict (Source.Position).Data + else ""); + + procedure Finalize (Object : in out Ptr) + is + Refs : Positive; + begin + if Sets.Has_Element (Object.Position) then + Refs := Dict (Object.Position).Refs; + if 1 < Refs then + Dict (Object.Position).Refs := Refs - 1; + Object.Position := Sets.No_Element; + else + Sets.Delete (Dict, Object.Position); + end if; + end if; + end Finalize; + + function Hash (Source : in Ptr) return Ada.Containers.Hash_Type is + (if Sets.Has_Element (Source.Position) + then Dict (Source.Position).Hash + else Empty_Hash); + +end Strings; diff --git a/ada2/strings.ads b/ada2/strings.ads new file mode 100644 index 0000000000..aeb8222725 --- /dev/null +++ b/ada2/strings.ads @@ -0,0 +1,65 @@ +with Ada.Containers; +private with Ada.Containers.Indefinite_Hashed_Sets; +private with Ada.Finalization; + +package Strings is + + pragma Elaborate_Body; + + -- An abstraction similar to Ada.Strings.Unbounded, except that + -- the type is immutable, and that only one instance is allocated + -- with a given content. This avoids many allocations and + -- deallocations, since symbols and keywords are expected to be + -- used many times. Using this for all strings even if they are + -- not used as keys in maps should not hurt. + + -- As a side effect, some frequent string comparisons (with "def!" + -- or "fn*" for example) will become a bit more efficient because + -- comparing pointers is faster than comparing strings. + + type Ptr is tagged private; + Empty_String : constant Ptr; -- The default value. + + function Alloc (Source : in String) return Ptr; + + function Deref (Source : in Ptr) return String + with Inline; + + -- We make the hash value visible so that environments and maps do + -- not need to recompute it. + function Hash (Source : in Ptr) return Ada.Containers.Hash_Type + with Inline; + +private + + type Element_Type (Last : Positive) is record + Data : String (1 .. Last); + Hash : Ada.Containers.Hash_Type; + Refs : Positive; + end record; + + function Hash (Element : Element_Type) return Ada.Containers.Hash_Type + is (Element.Hash) + with Inline; + + function Equivalent_Elements (Left, Right : Element_Type) return Boolean + is (Left.Data = Right.Data) + with Inline; + + package Sets is new Ada.Containers.Indefinite_Hashed_Sets + (Element_Type => Element_Type, + Hash => Hash, + Equivalent_Elements => Equivalent_Elements, + "=" => "="); + + type Ptr is new Ada.Finalization.Controlled with record + Position : Sets.Cursor := Sets.No_Element; + end record; + overriding procedure Adjust (Object : in out Ptr) with Inline; + overriding procedure Finalize (Object : in out Ptr) with Inline; + -- Predefined equality is fine. + + Empty_String : constant Ptr + := (Ada.Finalization.Controlled with Position => Sets.No_Element); + +end Strings; diff --git a/ada2/types.adb b/ada2/types.adb new file mode 100644 index 0000000000..d87297f3c8 --- /dev/null +++ b/ada2/types.adb @@ -0,0 +1,37 @@ +package body Types is + + function "=" (Left, Right : in Mal_Type) return Boolean is + (case Left.Kind is + when Kind_Nil => + Right.Kind = Kind_Nil, + when Kind_Atom => + Right.Kind = Kind_Atom + and then Atoms."=" (Left.Reference, Right.Reference), + when Kind_Boolean => + Right.Kind = Kind_Boolean + and then Left.Boolean_Value = Right.Boolean_Value, + when Kind_Number => + Right.Kind = Kind_Number + and then Left.Integer_Value = Right.Integer_Value, + when Kind_String | Kind_Keyword | Kind_Symbol => + Right.Kind = Left.Kind + and then Strings."=" (Left.S, Right.S), + when Kind_List | Kind_Vector => + Right.Kind in Kind_List | Kind_Vector + and then Lists."=" (Left.L, Right.L), + when Kind_Map => + Right.Kind = Kind_Map + and then Maps."=" (Left.Map, Right.Map), + when Kind_Function => + Right.Kind = Kind_Function + and then Lists."=" (Left.Formals, Right.Formals) + and then Atoms."=" (Left.Expression, Right.Expression) + and then Environments."=" (Left.Environment, Right.Environment), + when Kind_Native => + Right.Kind = Kind_Native and then Left.Native = Right.Native, + when Kind_Macro => + Right.Kind = Kind_Macro + and then Atoms."=" (Left.Mac_Expression, Right.Mac_Expression) + and then Lists."=" (Left.Mac_Formals, Right.Mac_Formals)); + +end Types; diff --git a/ada2/types.ads b/ada2/types.ads new file mode 100644 index 0000000000..24c4d51e03 --- /dev/null +++ b/ada2/types.ads @@ -0,0 +1,59 @@ +with Atoms; +with Environments; +with Lists; +with Maps; +with Strings; + +package Types is + + type Mal_Type; + type Mal_Type_Array; + type Native_Function_Access is not null access + function (Arguments : in Mal_Type_Array) return Mal_Type; + + -- Make similar kinds consecutive for efficient case statements. + type Kind_Type is + (Kind_Nil, + Kind_Atom, + Kind_Boolean, + Kind_Number, + Kind_String, Kind_Symbol, Kind_Keyword, + Kind_List, Kind_Vector, + Kind_Map, + Kind_Macro, Kind_Function, Kind_Native); + + type Mal_Type (Kind : Kind_Type := Kind_Nil) is record + Meta : Atoms.Ptr; + case Kind is + when Kind_Nil => + null; + when Kind_Boolean => + Boolean_Value : Boolean; + when Kind_Number => + Integer_Value : Integer; + when Kind_Atom => + Reference : Atoms.Ptr; + when Kind_String | Kind_Keyword | Kind_Symbol => + S : Strings.Ptr; + when Kind_List | Kind_Vector => + L : Lists.Ptr; + when Kind_Map => + Map : Maps.Ptr; + when Kind_Native => + Native : Native_Function_Access; + when Kind_Function => + Formals : Lists.Ptr; + Expression : Atoms.Ptr; + Environment : Environments.Ptr; + when Kind_Macro => + Mac_Formals : Lists.Ptr; + Mac_Expression : Atoms.Ptr; + end case; + end record; + + function "=" (Left, Right : in Mal_Type) return Boolean; + -- By default, a list /= a vector. + + type Mal_Type_Array is array (Positive range <>) of Types.Mal_Type; + +end Types; From daffc668e924daa703d83279adda4196cb1ce6ed Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Wed, 27 Feb 2019 22:27:12 +0100 Subject: [PATCH 0795/2308] Major rewrite, improving style and improving performances. --- ada2/Makefile | 59 ++- ada2/README | 42 ++ ada2/atoms.adb | 52 -- ada2/atoms.ads | 36 -- ada2/core.adb | 917 ++++++++++++++++------------------- ada2/core.ads | 22 +- ada2/environments.adb | 430 +++++++++++----- ada2/environments.ads | 137 ++++-- ada2/lists.adb | 95 ---- ada2/lists.ads | 47 -- ada2/maps.adb | 160 ------ ada2/maps.ads | 59 --- ada2/names.ads | 87 ---- ada2/printer.adb | 171 +++---- ada2/printer.ads | 9 +- ada2/reader.adb | 140 +++--- ada2/reader.ads | 8 +- ada2/step0_repl.adb | 47 +- ada2/step1_read_print.adb | 56 +-- ada2/step2_eval.adb | 166 +++---- ada2/step3_env.adb | 237 +++++---- ada2/step4_if_fn_do.adb | 324 ++++++------- ada2/step5_tco.adb | 330 +++++++------ ada2/step6_file.adb | 366 +++++++------- ada2/step7_quote.adb | 459 +++++++++--------- ada2/step8_macros.adb | 544 ++++++++++----------- ada2/step9_try.adb | 607 ++++++++++++----------- ada2/stepa_mal.adb | 621 ++++++++++++------------ ada2/strings.adb | 62 --- ada2/strings.ads | 65 --- ada2/types-atoms.adb | 62 +++ ada2/types-atoms.ads | 35 ++ ada2/types-builtins.adb | 53 ++ ada2/types-builtins.ads | 46 ++ ada2/types-functions.adb | 171 +++++++ ada2/types-functions.ads | 67 +++ ada2/types-lists.adb | 260 ++++++++++ ada2/types-lists.ads | 83 ++++ ada2/types-mal.adb | 31 ++ ada2/types-mal.ads | 84 ++++ ada2/types-maps.adb | 266 ++++++++++ ada2/types-maps.ads | 68 +++ ada2/types-symbols-names.ads | 31 ++ ada2/types-symbols.adb | 90 ++++ ada2/types-symbols.ads | 59 +++ ada2/types.adb | 37 -- ada2/types.ads | 55 +-- 47 files changed, 4261 insertions(+), 3592 deletions(-) create mode 100644 ada2/README delete mode 100644 ada2/atoms.adb delete mode 100644 ada2/atoms.ads delete mode 100644 ada2/lists.adb delete mode 100644 ada2/lists.ads delete mode 100644 ada2/maps.adb delete mode 100644 ada2/maps.ads delete mode 100644 ada2/names.ads delete mode 100644 ada2/strings.adb delete mode 100644 ada2/strings.ads create mode 100644 ada2/types-atoms.adb create mode 100644 ada2/types-atoms.ads create mode 100644 ada2/types-builtins.adb create mode 100644 ada2/types-builtins.ads create mode 100644 ada2/types-functions.adb create mode 100644 ada2/types-functions.ads create mode 100644 ada2/types-lists.adb create mode 100644 ada2/types-lists.ads create mode 100644 ada2/types-mal.adb create mode 100644 ada2/types-mal.ads create mode 100644 ada2/types-maps.adb create mode 100644 ada2/types-maps.ads create mode 100644 ada2/types-symbols-names.ads create mode 100644 ada2/types-symbols.adb create mode 100644 ada2/types-symbols.ads delete mode 100644 ada2/types.adb diff --git a/ada2/Makefile b/ada2/Makefile index bb58c83206..a74b81ff2d 100644 --- a/ada2/Makefile +++ b/ada2/Makefile @@ -1,23 +1,16 @@ -# Variables expected on the command line: -OPT := -O2 -GNATN := -gnatn -GNATP := -gnatp -ADAFLAGS := -LDFLAGS := -DEBUG := - ifdef DEBUG - # Some warnings require -O1. - OPT := -O1 - GNATN := - GNATP := ADAFLAGS := -Wall -Wextra -gnatw.eH.Y -gnatySdouxy -gnatVa -g -gnataEfoqQ \ -fstack-check -pg LDFLAGS := -pg +else + # -O3 is not recommended as the default by the GCC documentation, + # and -O2 seems to produce slightly better performances. + # See README for a discussion of -gnatp. + ADAFLAGS := -O2 -gnatnp endif # Compiler arguments. -CARGS = -gnat2012 $(OPT) $(GNATN) $(GNATP) $(ADAFLAGS) +CARGS = -gnat2012 $(OPT) $(ADAFLAGS) # Linker arguments. LARGS = $(LDFLAGS) -lreadline @@ -42,15 +35,18 @@ clean: # Tell Make how to detect out-of-date executables, and let gnatmake do # the rest when it must be executed. TYPES := \ - atoms.ads atoms.adb \ - environments.ads environments.adb \ - lists.ads lists.adb \ - maps.ads maps.adb \ - names.ads \ - printer.ads printer.adb \ - reader.ads reader.adb \ - types.ads types.adb \ - strings.ads strings.adb + environments.ads environments.adb \ + printer.ads printer.adb \ + reader.ads reader.adb \ + types-atoms.ads types-atoms.adb \ + types-builtins.ads types-builtins.adb \ + types-functions.ads types-functions.adb \ + types-lists.ads types-lists.adb \ + types-mal.ads types-mal.adb \ + types-maps.ads types-maps.adb \ + types-symbols-names.ads \ + types-symbols.ads types-symbols.adb \ + types.ads CORE := \ core.ads core.adb @@ -61,11 +57,14 @@ $(stepa) : stepA%: stepa%.adb $(TYPES) $(CORE) $(steps) : gnatmake $< -o $@ -cargs $(CARGS) -largs $(LARGS) -# Step 8 freezes during the "(or)" test with -gnatp. -step8%: GNATP := - -# The compiler crashes on types.adb with -gnatn. -$(step13) $(step49) $(stepa): types.o -types.o: GNATN := -types.o: $(TYPES) - gcc -c $(CARGS) types.adb +.PHONY: steps.diff +steps.diff: + diff -u step1*.adb step2*.adb; \ + diff -u step2*.adb step3*.adb; \ + diff -u step3*.adb step4*.adb; \ + diff -u step4*.adb step5*.adb; \ + diff -u step5*.adb step6*.adb; \ + diff -u step6*.adb step7*.adb; \ + diff -u step7*.adb step8*.adb; \ + diff -u step8*.adb step9*.adb; \ + diff -u step9*.adb stepa*.adb || true diff --git a/ada2/README b/ada2/README new file mode 100644 index 0000000000..1593347ac3 --- /dev/null +++ b/ada2/README @@ -0,0 +1,42 @@ +Comparison with the first Ada implementation. + +The first implementation was deliberately compatible with all Ada +compilers, while this one illustrates various Ada 2012 features, like +assertions, preconditions, invariants, initial assignment for limited +types, limited imports... + +The variant MAL type is implemented with a discriminant instead of +object-style dispatching. This allows more static and dynamic checks, +but also two crucial performance improvements: +* Nil, boolean and integers are passed by value without dynamic + allocation. +* Lists are implemented as C-style arrays, and most of them can be + allocated on the stack. + +Once each component has an explicit interface, various optimizations +have been added: unique allocation of symbols, stack-style allocation +of environments in the current execution path, reuse of existing +memory when the reference count reaches 1... + +The eventual performances compete with C-style languages, allthough +all user input is checked (implicit language-defined checks like array +bounds and discriminant consistency are only enabled during tests). + +There are also similarities with the first implementation. For +example, both rely on user-defined finalization to handle recursive +structures without garbage collecting. + + +About reference reference counting. + +* The finalize procedure may be called twice, so it does nothing when + the reference count is zero, meaning that we are reaching Finalize + recursively. +* In implementations, a consistent object (that will be deallocated + automatically) must be built before any exception is raised by user + code (for example 'map' may run user functions). + + +Known bugs: the third step of the perf^ada2 target fails during the +final storage deallocation when the executable is built with -gnatp. I +have failed to understand why so far. diff --git a/ada2/atoms.adb b/ada2/atoms.adb deleted file mode 100644 index 24650a04d1..0000000000 --- a/ada2/atoms.adb +++ /dev/null @@ -1,52 +0,0 @@ -with Ada.Unchecked_Deallocation; -with Types; - -package body Atoms is - - type Atom_Record is limited record - Data : Types.Mal_Type; - Refs : Positive; - end record; - - procedure Free is new Ada.Unchecked_Deallocation (Object => Atom_Record, - Name => Atom_Access); - - ---------------------------------------------------------------------- - - procedure Adjust (Object : in out Ptr) is - begin - if Object.Ref /= null then - Object.Ref.all.Refs := Object.Ref.all.Refs + 1; - end if; - end Adjust; - - function Alloc (New_Value : in Types.Mal_Type) return Ptr - is (Ada.Finalization.Controlled with - Ref => new Atom_Record'(Data => New_Value, - Refs => 1)); - - function Deref (Container : in Ptr) return Types.Mal_Type is - (Container.Ref.all.Data); - - procedure Finalize (Object : in out Ptr) - is - Refs : Positive; - begin - if Object.Ref /= null then - Refs := Object.Ref.all.Refs; - if 1 < Refs then - Object.Ref.all.Refs := Refs - 1; - Object.Ref := null; - else - Free (Object.Ref); - end if; - end if; - end Finalize; - - procedure Set (Container : in Ptr; - New_Value : in Types.Mal_Type) is - begin - Container.Ref.all.Data := New_Value; - end Set; - -end Atoms; diff --git a/ada2/atoms.ads b/ada2/atoms.ads deleted file mode 100644 index 3fa5aae990..0000000000 --- a/ada2/atoms.ads +++ /dev/null @@ -1,36 +0,0 @@ -private with Ada.Finalization; -limited with Types; - -package Atoms is - - -- Equivalent to a Lists.Ptr with zero or one elements. - - type Ptr is tagged private; - No_Element : constant Ptr; - - function Alloc (New_Value : in Types.Mal_Type) return Ptr - with Inline; - - function Deref (Container : in Ptr) return Types.Mal_Type - with Inline, Pre => Container /= No_Element; - - procedure Set (Container : in Ptr; - New_Value : in Types.Mal_Type) - with Inline, Pre => Container /= No_Element; - -private - - type Atom_Record; - type Atom_Access is access Atom_Record; - type Ptr is new Ada.Finalization.Controlled with record - Ref : Atom_Access := null; - end record; - overriding procedure Adjust (Object : in out Ptr) - with Inline; - overriding procedure Finalize (Object : in out Ptr) - with Inline; - -- Predefined equality is fine. - - No_Element : constant Ptr := (Ada.Finalization.Controlled with Ref => null); - -end Atoms; diff --git a/ada2/core.adb b/ada2/core.adb index aa8be989a0..5027457f7f 100644 --- a/ada2/core.adb +++ b/ada2/core.adb @@ -2,580 +2,477 @@ with Ada.Calendar; use type Ada.Calendar.Time; with Ada.Characters.Latin_1; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Atoms; use type Atoms.Ptr; -with Lists; -with Maps; -with Names; + +with Environments; +with Types.Atoms; +with Types.Builtins; +with Types.Functions; +with Types.Lists; +with Types.Maps; +with Types.Symbols.Names; with Printer; with Reader; -with Strings; use type Strings.Ptr; package body Core is + package ASU renames Ada.Strings.Unbounded; use Types; + use Types.Lists; + use type Mal.T; + use type Mal.T_Array; Start_Time : constant Ada.Calendar.Time := Ada.Calendar.Clock; - Eval : Eval_Callback_Type; - - function Concatenation_Of_Pr_Str - (Args : in Mal_Type_Array; - Print_Readably : in Boolean := True; - Separator : in String := " ") - return Ada.Strings.Unbounded.Unbounded_String; - - function Apply (Args : in Mal_Type_Array) return Mal_Type; - function Assoc (Args : in Mal_Type_Array) return Mal_Type; - function Atom (Args : in Mal_Type_Array) return Mal_Type; - function Concat (Args : in Mal_Type_Array) return Mal_Type; - function Conj (Args : in Mal_Type_Array) return Mal_Type; - function Cons (Args : in Mal_Type_Array) return Mal_Type; - function Contains (Args : in Mal_Type_Array) return Mal_Type; - function Count (Args : in Mal_Type_Array) return Mal_Type; - function Deref (Args : in Mal_Type_Array) return Mal_Type; - function Dissoc (Args : in Mal_Type_Array) return Mal_Type; - function Equals (Args : in Mal_Type_Array) return Mal_Type; - function First (Args : in Mal_Type_Array) return Mal_Type; - function Get (Args : in Mal_Type_Array) return Mal_Type; - function Hash_Map (Args : in Mal_Type_Array) return Mal_Type; - function Is_Empty (Args : in Mal_Type_Array) return Mal_Type; - function Is_False (Args : in Mal_Type_Array) return Mal_Type; - function Is_Sequential (Args : in Mal_Type_Array) return Mal_Type; - function Is_True (Args : in Mal_Type_Array) return Mal_Type; - function Keys (Args : in Mal_Type_Array) return Mal_Type; - function Keyword (Args : in Mal_Type_Array) return Mal_Type; - function List (Args : in Mal_Type_Array) return Mal_Type; - function Map (Args : in Mal_Type_Array) return Mal_Type; - function Meta (Args : in Mal_Type_Array) return Mal_Type; - function Nth (Args : in Mal_Type_Array) return Mal_Type; - function Pr_Str (Args : in Mal_Type_Array) return Mal_Type; - function Println (Args : in Mal_Type_Array) return Mal_Type; - function Prn (Args : in Mal_Type_Array) return Mal_Type; - function Read_String (Args : in Mal_Type_Array) return Mal_Type; - function Readline (Args : in Mal_Type_Array) return Mal_Type; - function Reset (Args : in Mal_Type_Array) return Mal_Type; - function Rest (Args : in Mal_Type_Array) return Mal_Type; - function Seq (Args : in Mal_Type_Array) return Mal_Type; - function Slurp (Args : in Mal_Type_Array) return Mal_Type; - function Str (Args : in Mal_Type_Array) return Mal_Type; - function Swap (Args : in Mal_Type_Array) return Mal_Type; - function Symbol (Args : in Mal_Type_Array) return Mal_Type; - function Throw (Args : in Mal_Type_Array) return Mal_Type; - function Time_Ms (Args : in Mal_Type_Array) return Mal_Type; - function Vals (Args : in Mal_Type_Array) return Mal_Type; - function Vector (Args : in Mal_Type_Array) return Mal_Type; - function With_Meta (Args : in Mal_Type_Array) return Mal_Type; + function Apply (Func : in Mal.T; + Args : in Mal.T_Array; + Name : in String) return Mal.T with Inline; + -- If Func is not executable, report an exception using "name" as + -- the built-in function name. generic - with function Ada_Operator (Left, Right : in Integer) return Integer; - function Generic_Mal_Operator (Args : in Mal_Type_Array) return Mal_Type; - function Generic_Mal_Operator (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Number, Atoms.No_Element, - Ada_Operator (Args (Args'First).Integer_Value, - Args (Args'First + 1).Integer_Value)); - function Addition is new Generic_Mal_Operator ("+"); - function Subtraction is new Generic_Mal_Operator ("-"); - function Product is new Generic_Mal_Operator ("*"); - function Division is new Generic_Mal_Operator ("/"); + Kind : in Kind_Type; + Name : in String; + function Generic_Kind_Test (Args : in Mal.T_Array) return Mal.T; + function Generic_Kind_Test (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 1 + then raise Argument_Error with Name & ": expects 1 argument" + else (Kind_Boolean, Args (Args'First).Kind = Kind)); generic - with function Ada_Operator (Left, Right : in Integer) return Boolean; - function Generic_Comparison (Args : in Mal_Type_Array) return Mal_Type; - function Generic_Comparison (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Boolean, Atoms.No_Element, - Ada_Operator (Args (Args'First).Integer_Value, - Args (Args'First + 1).Integer_Value)); - function Greater_Than is new Generic_Comparison (">"); - function Greater_Equal is new Generic_Comparison (">="); - function Less_Than is new Generic_Comparison ("<"); - function Less_Equal is new Generic_Comparison ("<="); + with function Ada_Operator (Left, Right : in Integer) return Integer; + Name : in String; + function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T; + function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 2 + then raise Argument_Error with Name & ": expects 2 arguments" + elsif (for some A of Args => A.Kind /= Kind_Number) + then raise Argument_Error with Name & ": expects numbers" + else (Kind_Number, Ada_Operator (Args (Args'First).Ada_Number, + Args (Args'Last).Ada_Number))); generic - Kind : Kind_Type; - function Generic_Kind_Test (Args : in Mal_Type_Array) return Mal_Type; - function Generic_Kind_Test (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Boolean, Atoms.No_Element, Args (Args'First).Kind = Kind); - function Is_Atom is new Generic_Kind_Test (Kind_Atom); - function Is_Keyword is new Generic_Kind_Test (Kind_Keyword); - function Is_List is new Generic_Kind_Test (Kind_List); - function Is_Map is new Generic_Kind_Test (Kind_Map); - function Is_Nil is new Generic_Kind_Test (Kind_Nil); - function Is_String is new Generic_Kind_Test (Kind_String); - function Is_Symbol is new Generic_Kind_Test (Kind_Symbol); - function Is_Vector is new Generic_Kind_Test (Kind_Vector); + with function Ada_Operator (Left, Right : in Integer) return Boolean; + Name : in String; + function Generic_Comparison (Args : in Mal.T_Array) return Mal.T; + function Generic_Comparison (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 2 + then raise Argument_Error with Name & ": expects 2 arguments" + elsif (for some A of Args => A.Kind /= Kind_Number) + then raise Argument_Error with Name & ": expects numbers" + else (Kind_Boolean, Ada_Operator (Args (Args'First).Ada_Number, + Args (Args'Last).Ada_Number))); + + -- Built-in functions from this package. + function Addition is new Generic_Mal_Operator ("+", "+"); + function Apply (Args : in Mal.T_Array) return Mal.T; + function Division is new Generic_Mal_Operator ("/", "/"); + function Equals (Args : in Mal.T_Array) return Mal.T; + function Eval (Args : in Mal.T_Array) return Mal.T; + function Greater_Equal is new Generic_Comparison (">=", ">="); + function Greater_Than is new Generic_Comparison (">", ">"); + function Is_Atom is new Generic_Kind_Test (Kind_Atom, "atom?"); + function Is_False (Args : in Mal.T_Array) return Mal.T; + function Is_Function (Args : in Mal.T_Array) return Mal.T; + function Is_Keyword is new Generic_Kind_Test (Kind_Keyword, "keyword?"); + function Is_List is new Generic_Kind_Test (Kind_List, "list?"); + function Is_Macro is new Generic_Kind_Test (Kind_Macro, "macro?"); + function Is_Map is new Generic_Kind_Test (Kind_Map, "map?"); + function Is_Nil is new Generic_Kind_Test (Kind_Nil, "nil?"); + function Is_Number is new Generic_Kind_Test (Kind_Number, "number?"); + function Is_Sequential (Args : in Mal.T_Array) return Mal.T; + function Is_String is new Generic_Kind_Test (Kind_String, "string?"); + function Is_Symbol is new Generic_Kind_Test (Kind_Symbol, "symbol?"); + function Is_True (Args : in Mal.T_Array) return Mal.T; + function Is_Vector is new Generic_Kind_Test (Kind_Vector, "vector?"); + function Keyword (Args : in Mal.T_Array) return Mal.T; + function Less_Equal is new Generic_Comparison ("<=", "<="); + function Less_Than is new Generic_Comparison ("<", "<"); + function Map (Args : in Mal.T_Array) return Mal.T; + function Meta (Args : in Mal.T_Array) return Mal.T; + function Pr_Str (Args : in Mal.T_Array) return Mal.T; + function Println (Args : in Mal.T_Array) return Mal.T; + function Prn (Args : in Mal.T_Array) return Mal.T; + function Product is new Generic_Mal_Operator ("*", "*"); + function Read_String (Args : in Mal.T_Array) return Mal.T; + function Readline (Args : in Mal.T_Array) return Mal.T; + function Seq (Args : in Mal.T_Array) return Mal.T; + function Slurp (Args : in Mal.T_Array) return Mal.T; + function Str (Args : in Mal.T_Array) return Mal.T; + function Subtraction is new Generic_Mal_Operator ("-", "-"); + function Swap (Args : in Mal.T_Array) return Mal.T; + function Symbol (Args : in Mal.T_Array) return Mal.T; + function Throw (Args : in Mal.T_Array) return Mal.T; + function Time_Ms (Args : in Mal.T_Array) return Mal.T; + function With_Meta (Args : in Mal.T_Array) return Mal.T; ---------------------------------------------------------------------- - procedure Add_Built_In_Functions - (Repl : in Environments.Ptr; - Eval_Callback : in not null Eval_Callback_Type) - is - function N (N : in Native_Function_Access) return Mal_Type - is (Kind_Native, Atoms.No_Element, N) with Inline; + function Apply (Func : in Mal.T; + Args : in Mal.T_Array; + Name : in String) + return Mal.T is begin - Eval := Eval_Callback; - - Repl.Increase_Capacity (57); - - Repl.Set (Names.Apply, N (Apply'Access)); - Repl.Set (Names.Assoc, N (Assoc'Access)); - Repl.Set (Names.Asterisk, N (Product'Access)); - Repl.Set (Names.Atom, N (Atom'Access)); - Repl.Set (Names.Concat, N (Concat'Access)); - Repl.Set (Names.Conj, N (Conj'Access)); - Repl.Set (Names.Cons, N (Cons'Access)); - Repl.Set (Names.Contains, N (Contains'Access)); - Repl.Set (Names.Count, N (Count'Access)); - Repl.Set (Names.Deref, N (Deref'Access)); - Repl.Set (Names.Dissoc, N (Dissoc'Access)); - Repl.Set (Names.Equals, N (Equals'Access)); - Repl.Set (Names.First, N (First'Access)); - Repl.Set (Names.Get, N (Get'Access)); - Repl.Set (Names.Greater_Equal, N (Greater_Equal'Access)); - Repl.Set (Names.Greater_Than, N (Greater_Than'Access)); - Repl.Set (Names.Hash_Map, N (Hash_Map'Access)); - Repl.Set (Names.Is_Atom, N (Is_Atom'Access)); - Repl.Set (Names.Is_Empty, N (Is_Empty'Access)); - Repl.Set (Names.Is_False, N (Is_False'Access)); - Repl.Set (Names.Is_Keyword, N (Is_Keyword'Access)); - Repl.Set (Names.Is_List, N (Is_List'Access)); - Repl.Set (Names.Is_Map, N (Is_Map'Access)); - Repl.Set (Names.Is_Nil, N (Is_Nil'Access)); - Repl.Set (Names.Is_Sequential, N (Is_Sequential'Access)); - Repl.Set (Names.Is_String, N (Is_String'Access)); - Repl.Set (Names.Is_Symbol, N (Is_Symbol'Access)); - Repl.Set (Names.Is_True, N (Is_True'Access)); - Repl.Set (Names.Is_Vector, N (Is_Vector'Access)); - Repl.Set (Names.Keys, N (Keys'Access)); - Repl.Set (Names.Keyword, N (Keyword'Access)); - Repl.Set (Names.Less_Equal, N (Less_Equal'Access)); - Repl.Set (Names.Less_Than, N (Less_Than'Access)); - Repl.Set (Names.List, N (List'Access)); - Repl.Set (Names.Map, N (Map'Access)); - Repl.Set (Names.Meta, N (Meta'Access)); - Repl.Set (Names.Minus, N (Subtraction'Access)); - Repl.Set (Names.Nth, N (Nth'Access)); - Repl.Set (Names.Plus, N (Addition'Access)); - Repl.Set (Names.Pr_Str, N (Pr_Str'Access)); - Repl.Set (Names.Println, N (Println'Access)); - Repl.Set (Names.Prn, N (Prn'Access)); - Repl.Set (Names.Read_String, N (Read_String'Access)); - Repl.Set (Names.Readline, N (Readline'Access)); - Repl.Set (Names.Reset, N (Reset'Access)); - Repl.Set (Names.Rest, N (Rest'Access)); - Repl.Set (Names.Seq, N (Seq'Access)); - Repl.Set (Names.Slash, N (Division'Access)); - Repl.Set (Names.Slurp, N (Slurp'Access)); - Repl.Set (Names.Str, N (Str'Access)); - Repl.Set (Names.Swap, N (Swap'Access)); - Repl.Set (Names.Symbol, N (Symbol'Access)); - Repl.Set (Names.Throw, N (Throw'Access)); - Repl.Set (Names.Time_Ms, N (Time_Ms'Access)); - Repl.Set (Names.Vals, N (Vals'Access)); - Repl.Set (Names.Vector, N (Vector'Access)); - Repl.Set (Names.With_Meta, N (With_Meta'Access)); - end Add_Built_In_Functions; - - function Apply (Args : in Mal_Type_Array) return Mal_Type - is - Func : Mal_Type renames Args (Args'First); - List : Lists.Ptr renames Args (Args'Last).L; - Actuals : Mal_Type_Array (1 .. Args'Length - 2 + List.Length); - begin - Actuals (1 .. Args'Length - 2) := Args (Args'First + 1 .. Args'Last - 1); - for I in 1 .. List.Length loop - Actuals (Args'Length - 2 + I) := List.Element (I); - end loop; - if Func.Kind = Kind_Native then - return Func.Native.all (Actuals); - else + case Func.Kind is + when Kind_Builtin => + return Func.Builtin.all (Args); + when Kind_Builtin_With_Meta => + return Func.Builtin_With_Meta.Data.all (Args); + when Kind_Function => declare - Env : constant Environments.Ptr - := Environments.Alloc (Outer => Func.Environment); + Env : constant Environments.Ptr := Func.Function_Value.Closure.Sub; begin - Env.Set_Binds (Func.Formals, Actuals); - return Eval.all (Func.Expression.Deref, Env); + Func.Function_Value.Set_Binds (Env, Args); + return Eval_Ref.all (Func.Function_Value.Expression, Env); end; - end if; + when others => + raise Argument_Error with Name & ": cannot execute " + & ASU.To_String (Printer.Pr_Str (Func)); + end case; end Apply; - function Assoc (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Map, Atoms.No_Element, - Args (Args'First).Map.Assoc (Args (Args'First + 1 .. Args'Last))); - - function Atom (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Atom, Atoms.No_Element, Atoms.Alloc (Args (Args'First))); - - function Concat (Args : in Mal_Type_Array) return Mal_Type - is - L : array (Args'Range) of Lists.Ptr; - Sum : Natural := 0; - Result : Lists.Ptr; - begin - for I in Args'Range loop - L (I) := Args (I).L; - Sum := Sum + L (I).Length; - end loop; - Result := Lists.Alloc (Sum); - Sum := 0; - for LI of L loop - for J in 1 .. LI.Length loop - Sum := Sum + 1; - Result.Replace_Element (Sum, LI.Element (J)); - end loop; - end loop; - return (Kind_List, Atoms.No_Element, Result); - end Concat; - - function Concatenation_Of_Pr_Str - (Args : in Mal_Type_Array; - Print_Readably : in Boolean := True; - Separator : in String := " ") - return Ada.Strings.Unbounded.Unbounded_String - is - use Ada.Strings.Unbounded; - Result : Unbounded_String; + function Apply (Args : in Mal.T_Array) return Mal.T + is (if Args'Length < 2 then + raise Argument_Error with "apply: expects at least 2 arguments" + elsif Args (Args'Last).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "apply: last arg must a be list or vector" + else + Apply (Args (Args'First), + Args (Args'First + 1 .. Args'Last - 1) & Args (Args'Last).L, + "apply")); + + function Equals (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 2 then + raise Argument_Error with "=: expects 2 arguments" + else + (Kind_Boolean, Args (Args'First) = Args (Args'Last))); + + function Eval (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 1 then + raise Argument_Error with "eval: expects 1 argument" + else + (Eval_Ref.all (Args (Args'First), Environments.Repl))); + + function Is_False (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 1 then + raise Argument_Error with "false?: expects 1 argument" + else (Kind_Boolean, Args (Args'First).Kind = Kind_Boolean + and then not Args (Args'First).Ada_Boolean)); + + function Is_Function (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 1 then + raise Argument_Error with "count: expects 1 argument" + else + (Kind_Boolean, Args (Args'First).Kind in + Kind_Function | Kind_Builtin | Kind_Builtin_With_Meta)); + + function Is_Sequential (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 1 then + raise Argument_Error with "sequential?: expects 1 argument" + else + (Kind_Boolean, Args (Args'First).Kind in Kind_List | Kind_Vector)); + + function Is_True (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 1 then + raise Argument_Error with "true?: expects 1 argument" + else + (Kind_Boolean, Args (Args'First).Kind = Kind_Boolean + and then Args (Args'First).Ada_Boolean)); + + function Keyword (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 1 then + raise Argument_Error with "keyword: expects 1 argument" + elsif Args (Args'First).Kind not in Kind_Keyword | Kind_String then + raise Argument_Error with "keyword: expects a keyword or a string" + else + (Kind_Keyword, Args (Args'First).S)); + + function Map (Args : in Mal.T_Array) return Mal.T is begin - if 1 <= Args'Length then - Append (Result, Printer.Pr_Str (Args (Args'First), Print_Readably)); - for I in Args'First + 1 .. Args'Last loop - Append (Result, Separator); - Append (Result, Printer.Pr_Str (Args (I), Print_Readably)); - end loop; + if Args'Length /= 2 then + raise Argument_Error with "map: expects 2 arguments"; + elsif Args (Args'Last).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "map: arg 2 must be a list or vector"; end if; - return Result; - end Concatenation_Of_Pr_Str; - - function Conj (Args : in Mal_Type_Array) return Mal_Type - is - List : Lists.Ptr renames Args (Args'First).L; - Result : constant Lists.Ptr - := Lists.Alloc (List.Length + Args'Length - 1); - begin - if Args (Args'First).Kind = Kind_List then - for I in Args'First + 1 .. Args'Last loop - Result.Replace_Element (Args'Last + 1 - I, Args (I)); - end loop; - for I in 1 .. List.Length loop - Result.Replace_Element (Args'Length + I - 1, List.Element (I)); - end loop; - return (Kind_List, Atoms.No_Element, Result); - else - for I in 1 .. Args'Length - 1 loop - Result.Replace_Element (List.Length + I, Args (Args'First + I)); - end loop; - for I in 1 .. List.Length loop - Result.Replace_Element (I, List.Element (I)); + declare + R : Mal.T_Array (1 .. Args (Args'Last).L.Length); + begin + for I in R'Range loop + R (I) := Apply (Args (Args'First), + Mal.T_Array'(1 => Args (Args'Last).L.Element (I)), + "map"); end loop; - return (Kind_Vector, Atoms.No_Element, Result); - end if; - end Conj; + return Lists.List (R); + end; + end Map; - function Cons (Args : in Mal_Type_Array) return Mal_Type - is - List : Lists.Ptr renames Args (Args'First + 1).L; - Result : constant Lists.Ptr := Lists.Alloc (1 + List.Length); - begin - Result.Replace_Element (1, Args (Args'First)); - for I in 1 .. List.Length loop - Result.Replace_Element (I + 1, List.Element (I)); - end loop; - return (Kind_List, Atoms.No_Element, Result); - end Cons; - - function Contains (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Boolean, Atoms.No_Element, - Args (Args'First).Map.Contains (Args (Args'First + 1))); - - function Count (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Number, Atoms.No_Element, - (if Args (Args'First).Kind = Kind_Nil - then 0 - else Args (Args'First).L.Length)); - - function Deref (Args : in Mal_Type_Array) return Mal_Type - is (Args (Args'First).Reference.Deref); - - function Dissoc (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Map, Atoms.No_Element, - Args (Args'First).Map.Dissoc (Args (Args'First + 1 .. Args'Last))); - - function Equals (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Boolean, Atoms.No_Element, - Args (Args'First) = Args (Args'First + 1)); - - function First (Args : in Mal_Type_Array) return Mal_Type - is (if Args (Args'First).Kind = Kind_Nil - or else Args (Args'First).L.Length = 0 - then (Kind_Nil, Atoms.No_Element) - else Args (Args'First).L.Element (1)); - - function Get (Args : in Mal_Type_Array) return Mal_Type is - begin - if Args (Args'First).Kind = Kind_Nil then - return (Kind_Nil, Atoms.No_Element); - else - return Args (Args'First).Map.Get (Args (Args'First + 1)); - end if; - exception - when Maps.Unknown_Key => - return (Kind_Nil, Atoms.No_Element); - end Get; - - function Hash_Map (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Map, Atoms.No_Element, Maps.Hash_Map (Args)); - - function Is_Empty (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Boolean, Atoms.No_Element, Args (Args'First).L.Length = 0); - - function Is_False (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Boolean, Atoms.No_Element, - Args (Args'First).Kind = Kind_Boolean - and then not Args (Args'First).Boolean_Value); - - function Is_True (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Boolean, Atoms.No_Element, - Args (Args'First).Kind = Kind_Boolean - and then Args (Args'First).Boolean_Value); - - function Is_Sequential (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Boolean, Atoms.No_Element, - Args (Args'First).Kind in Kind_List | Kind_Vector); - - function Keyword (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Keyword, Atoms.No_Element, Args (Args'First).S); - - function Keys (Args : in Mal_Type_Array) return Mal_Type - is - M : Maps.Ptr renames Args (Args'First).Map; - Result : constant Mal_Type := (Kind_List, Atoms.No_Element, - Lists.Alloc (M.Length)); - I : Natural := 0; - procedure Process (Key, Element : in Mal_Type); - procedure Process (Key, Element : in Mal_Type) is - begin - I := I + 1; - Result.L.Replace_Element (I, Key); - pragma Unreferenced (Element); - end Process; + function Meta (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 1 then + raise Argument_Error with "meta: expects 1 argument" + else + (case Args (Args'First).Kind is + when Kind_List | Kind_Vector => + Args (Args'First).L.Meta, + when Kind_Map => + Args (Args'First).Map.Meta, + when Kind_Function => + Args (Args'First).Function_Value.Meta, + when Kind_Builtin_With_Meta => + Args (Args'First).Builtin_With_Meta.Meta, + when others => + Mal.Nil)); + + function Pr_Str (Args : in Mal.T_Array) return Mal.T is begin - M.Iterate (Process'Access); - return Result; - end Keys; - - function List (Args : in Mal_Type_Array) return Mal_Type - is (Kind_List, Atoms.No_Element, Lists.Alloc (Args)); - - function Map (Args : in Mal_Type_Array) return Mal_Type - is - Func : Mal_Type renames Args (Args'First); - List : Lists.Ptr renames Args (Args'First + 1).L; - Actuals : Mal_Type_Array (1 .. 1); - Result : constant Lists.Ptr := Lists.Alloc (List.Length); - begin - for I in 1 .. List.Length loop - Actuals (1) := List.Element (I); - if Func.Kind = Kind_Native then - Result.Replace_Element (I, Func.Native.all (Actuals)); - else - declare - Env : constant Environments.Ptr - := Environments.Alloc (Func.Environment); - begin - Env.Set_Binds (Func.Formals, Actuals); - Result.Replace_Element (I, Eval.all (Func.Expression.Deref, - Env)); - end; + return R : Mal.T := (Kind_String, ASU.Null_Unbounded_String) do + if 0 < Args'Length then + ASU.Append (R.S, Printer.Pr_Str (Args (Args'First))); + for I in Args'First + 1 .. Args'Last loop + ASU.Append (R.S, ' '); + ASU.Append (R.S, Printer.Pr_Str (Args (I))); + end loop; end if; - end loop; - return (Kind_List, Atoms.No_Element, Result); - end Map; - - function Meta (Args : in Mal_Type_Array) return Mal_Type - is (if Args (Args'First).Meta = Atoms.No_Element - then (Kind_Nil, Atoms.No_Element) - else Args (Args'First).Meta.Deref); - - function Nth (Args : in Mal_Type_Array) return Mal_Type - is (Args (Args'First).L.Element (1 + Args (Args'First + 1).Integer_Value)); - - function Pr_Str (Args : in Mal_Type_Array) return Mal_Type - is (Kind_String, Atoms.No_Element, Strings.Alloc - (Ada.Strings.Unbounded.To_String (Concatenation_Of_Pr_Str (Args)))); + end return; + end Pr_Str; - function Println (Args : in Mal_Type_Array) return Mal_Type is + function Println (Args : in Mal.T_Array) return Mal.T is + use Ada.Text_IO.Unbounded_IO; begin - Ada.Text_IO.Unbounded_IO.Put_Line (Concatenation_Of_Pr_Str - (Args, Print_Readably => False)); - return (Kind_Nil, Atoms.No_Element); + if 0 < Args'Length then + Put (Printer.Pr_Str (Args (Args'First), Readably => False)); + for I in Args'First + 1 .. Args'Last loop + Ada.Text_IO.Put (' '); + Put (Printer.Pr_Str (Args (I), Readably => False)); + end loop; + end if; + Ada.Text_IO.New_Line; + return Mal.Nil; end Println; - function Prn (Args : in Mal_Type_Array) return Mal_Type is + function Prn (Args : in Mal.T_Array) return Mal.T is begin - Ada.Text_IO.Unbounded_IO.Put_Line (Concatenation_Of_Pr_Str (Args)); - return (Kind_Nil, Atoms.No_Element); + if 0 < Args'Length then + Ada.Text_IO.Unbounded_IO.Put (Printer.Pr_Str (Args (Args'First))); + for I in Args'First + 1 .. Args'Last loop + Ada.Text_IO.Put (' '); + Ada.Text_IO.Unbounded_IO.Put (Printer.Pr_Str (Args (I))); + end loop; + end if; + Ada.Text_IO.New_Line; + return Mal.Nil; end Prn; - function Readline (Args : in Mal_Type_Array) return Mal_Type is + function Readline (Args : in Mal.T_Array) return Mal.T is begin - Ada.Text_IO.Put (Args (Args'First).S.Deref); - return (Kind_String, Atoms.No_Element, - Strings.Alloc (Ada.Text_IO.Get_Line)); + if Args'Length /= 1 then + raise Argument_Error with "readline: expects 1 argument"; + elsif Args (Args'First).Kind not in Kind_Keyword | Kind_String then + raise Argument_Error with "readline: expects a keyword or string"; + else + Ada.Text_IO.Unbounded_IO.Put (Args (Args'First).S); + return (Kind_String, Ada.Text_IO.Unbounded_IO.Get_Line); + end if; exception when Ada.Text_IO.End_Error => - return (Kind_Nil, Atoms.No_Element); + return Mal.Nil; end Readline; - function Read_String (Args : in Mal_Type_Array) return Mal_Type - is (Reader.Read_Str (Args (Args'First).S.Deref)); + function Read_String (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 1 then + raise Argument_Error with "read-string: expects 1 argument" + elsif Args (Args'First).Kind /= Kind_String then + raise Argument_Error with "read-string: expects a string" + else + Reader.Read_Str (ASU.To_String (Args (Args'First).S))); - function Reset (Args : in Mal_Type_Array) return Mal_Type is + function Seq (Args : in Mal.T_Array) return Mal.T is begin - Args (Args'First).Reference.Set (Args (Args'Last)); - return Args (Args'Last); - end Reset; - - function Rest (Args : in Mal_Type_Array) return Mal_Type - is - List : Mal_Type renames Args (Args'First); - Len : Natural; - begin - return Result : Mal_Type (Kind_List) do - if List.Kind /= Kind_Nil then - Len := List.L.Length; - if 0 < Len then - Len := Len - 1; - Result.L := Lists.Alloc (Len); - for I in 1 .. Len loop - Result.L.Replace_Element (I, List.L.Element (I + 1)); - end loop; + if Args'Length /= 1 then + raise Argument_Error with "seq: expects 1 argument"; + end if; + case Args (Args'First).Kind is + when Kind_Nil => + return Mal.Nil; + when Kind_String => + if ASU.Length (Args (Args'First).S) = 0 then + return Mal.Nil; + else + declare + A1 : constant ASU.Unbounded_String := Args (Args'First).S; + R : Mal.T_Array (1 .. ASU.Length (A1)); + begin + for I in R'Range loop + R (I) := (Kind_String, ASU.Unbounded_Slice (A1, I, I)); + end loop; + return Lists.List (R); + end; end if; - end if; - end return; - end Rest; - - function Seq (Args : in Mal_Type_Array) return Mal_Type is - begin - if Args (Args'First).Kind = Kind_String then - declare - S : constant String := Args (Args'First).S.Deref; - Result : Lists.Ptr; - begin - if S'Length = 0 then - return (Kind_Nil, Atoms.No_Element); + when Kind_List | Kind_Vector => + if Args (Args'First).L.Length = 0 then + return Mal.Nil; else - Result := Lists.Alloc (S'Length); - for I in S'Range loop - Result.Replace_Element (I - S'First + 1, Mal_Type' - (Kind_String, Atoms.No_Element, - Strings.Alloc (S (I .. I)))); - end loop; - return (Kind_List, Atoms.No_Element, Result); + return (Kind_List, Args (Args'First).L); end if; - end; - elsif Args (Args'First).Kind = Kind_Nil - or else Args (Args'First).L.Length = 0 - then - return (Kind_Nil, Atoms.No_Element); - else - return (Kind_List, Atoms.No_Element, Args (Args'First).L); - end if; + when others => + raise Argument_Error with "seq: expects a string, list or vector"; + end case; end Seq; - function Slurp (Args : in Mal_Type_Array) return Mal_Type - is - use Ada.Strings.Unbounded; + function Slurp (Args : in Mal.T_Array) return Mal.T is use Ada.Text_IO; File : File_Type; - Buffer : Unbounded_String; + Buffer : ASU.Unbounded_String; begin - Open (File, In_File, Args (Args'First).S.Deref); - while not End_Of_File (File) loop - Append (Buffer, Get_Line (File)); - Append (Buffer, Ada.Characters.Latin_1.LF); - end loop; - Close (File); - return (Kind_String, Atoms.No_Element, - Strings.Alloc (To_String (Buffer))); + if Args'Length /= 1 then + raise Argument_Error with "slurp: expects 1 argument"; + elsif Args (Args'First).Kind /= Kind_String then + raise Argument_Error with "slurp: expects a string"; + else + Open (File, In_File, ASU.To_String (Args (Args'First).S)); + while not End_Of_File (File) loop + ASU.Append (Buffer, Get_Line (File)); + ASU.Append (Buffer, Ada.Characters.Latin_1.LF); + end loop; + Close (File); + return (Kind_String, Buffer); + end if; exception when others => Close (File); raise; end Slurp; - function Str (Args : in Mal_Type_Array) return Mal_Type - is (Kind_String, Atoms.No_Element, Strings.Alloc - (Ada.Strings.Unbounded.To_String - (Concatenation_Of_Pr_Str (Args, - Print_Readably => False, - Separator => "")))); - - function Swap (Args : in Mal_Type_Array) return Mal_Type - is - Atom : Mal_Type renames Args (Args'First); - Func : Mal_Type renames Args (Args'First + 1); - Actuals : Mal_Type_Array (Args'First + 1 .. Args'Last); - Result : Mal_Type; + function Str (Args : in Mal.T_Array) return Mal.T is begin - Actuals (Actuals'First) := Atom.Reference.Deref; - for I in Actuals'First + 1 .. Args'Last loop - Actuals (I) := Args (I); - end loop; - if Func.Kind = Kind_Native then - Result := Func.Native.all (Actuals); - else - declare - Env : constant Environments.Ptr - := Environments.Alloc (Outer => Func.Environment); - begin - Env.Set_Binds (Func.Formals, Actuals); - Result := Eval.all (Func.Expression.Deref, Env); - end; - end if; - Atom.Reference.Set (Result); - return Result; - end Swap; - - function Symbol (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Symbol, Atoms.No_Element, Args (Args'First).S); + return R : Mal.T := (Kind_String, ASU.Null_Unbounded_String) do + for Arg of Args loop + ASU.Append (R.S, Printer.Pr_Str (Arg, Readably => False)); + end loop; + end return; + end Str; - function Throw (Args : in Mal_Type_Array) return Mal_Type is + function Swap (Args : in Mal.T_Array) return Mal.T is begin - Last_Exception := Args (Args'First); - raise Exception_Throwed; - return (Kind_Nil, Atoms.No_Element); -- GNAT wants a return. - end Throw; - - function Time_Ms (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Number, Atoms.No_Element, - Integer (1000.0 * (Ada.Calendar.Clock - Start_Time))); - - function Vals (Args : in Mal_Type_Array) return Mal_Type - is - M : Maps.Ptr renames Args (Args'First).Map; - Result : constant Mal_Type := (Kind_List, Atoms.No_Element, - Lists.Alloc (M.Length)); - I : Natural := 0; - procedure Process (Key, Element : in Mal_Type); - procedure Process (Key, Element : in Mal_Type) is + if Args'Length < 2 then + raise Argument_Error with "swap!: expects at least 2 arguments"; + elsif Args (Args'First).Kind /= Kind_Atom then + raise Argument_Error with "swap!: arg 1 must be an atom"; + end if; + declare + X : Mal.T renames Atoms.Deref (Args (Args'First .. Args'First)); + FX : Mal.T renames Apply (Args (Args'First + 1), + X & Args (Args'First + 2 .. Args'Last), + "swap!"); begin - I := I + 1; - Result.L.Replace_Element (I, Element); - pragma Unreferenced (Key); - end Process; - begin - M.Iterate (Process'Access); - return Result; - end Vals; + return Atoms.Reset (Mal.T_Array'(Args (Args'First), FX)); + end; + end Swap; - function Vector (Args : in Mal_Type_Array) return Mal_Type - is (Kind_Vector, Atoms.No_Element, Lists.Alloc (Args)); + function Symbol (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 1 then + raise Argument_Error with "symbol?: expects 1 argument" + else + (Kind_Symbol, + Symbols.Constructor (ASU.To_String (Args (Args'First).S)))); - function With_Meta (Args : in Mal_Type_Array) return Mal_Type is + function Throw (Args : in Mal.T_Array) return Mal.T is begin - return Result : Mal_Type := Args (Args'First) do - Result.Meta := Atoms.Alloc (Args (Args'First + 1)); - end return; - end With_Meta; + if Args'Length /= 1 then + raise Argument_Error with "throw: expects 1 argument"; + else + Last_Exception := Args (Args'First); + raise Exception_Throwed; + return Mal.Nil; -- GNAT wants a return. + end if; + end Throw; + function Time_Ms (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 0 then + raise Argument_Error with "time: expects no argument" + else + (Kind_Number, Integer (1000.0 * (Ada.Calendar.Clock - Start_Time)))); + + function With_Meta (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 2 then + raise Argument_Error with "with-meta: expects 2 arguments" + else (case Args (Args'First).Kind is + when Kind_Builtin_With_Meta => + Args (Args'First).Builtin_With_Meta.With_Meta (Args (Args'Last)), + when Kind_Builtin => + Builtins.With_Meta (Args (Args'First).Builtin, Args (Args'Last)), + when Kind_List => + (Kind_List, Args (Args'First).L.With_Meta (Args (Args'Last))), + when Kind_Vector => + (Kind_Vector, Args (Args'First).L.With_Meta (Args (Args'Last))), + when Kind_Map => + Args (Args'First).Map.With_Meta (Args (Args'Last)), + when Kind_Function => + Args (Args'First).Function_Value.With_Meta (Args (Args'Last)), + when others => + Args (Args'First))); + + use Symbols; + R : Environments.Ptr renames Environments.Repl; + B : Kind_Type renames Kind_Builtin; +begin + R.Set (Constructor ("+"), (B, Addition'Access)); + R.Set (Constructor ("apply"), (B, Apply'Access)); + R.Set (Constructor ("assoc"), (B, Maps.Assoc'Access)); + R.Set (Constructor ("atom"), (B, Atoms.Atom'Access)); + R.Set (Constructor ("concat"), (B, Lists.Concat'Access)); + R.Set (Constructor ("conj"), (B, Lists.Conj'Access)); + R.Set (Constructor ("cons"), (B, Lists.Cons'Access)); + R.Set (Constructor ("contains?"), (B, Maps.Contains'Access)); + R.Set (Constructor ("count"), (B, Lists.Count'Access)); + R.Set (Names.Deref, (B, Atoms.Deref'Access)); + R.Set (Constructor ("dissoc"), (B, Maps.Dissoc'Access)); + R.Set (Constructor ("/"), (B, Division'Access)); + R.Set (Constructor ("="), (B, Equals'Access)); + R.Set (Constructor ("eval"), (B, Eval'Access)); + R.Set (Constructor ("first"), (B, Lists.First'Access)); + R.Set (Constructor ("get"), (B, Maps.Get'Access)); + R.Set (Constructor (">="), (B, Greater_Equal'Access)); + R.Set (Constructor (">"), (B, Greater_Than'Access)); + R.Set (Constructor ("hash-map"), (B, Maps.Hash_Map'Access)); + R.Set (Constructor ("atom?"), (B, Is_Atom'Access)); + R.Set (Constructor ("empty?"), (B, Lists.Is_Empty'Access)); + R.Set (Constructor ("false?"), (B, Is_False'Access)); + R.Set (Constructor ("fn?"), (B, Is_Function'Access)); + R.Set (Constructor ("keyword?"), (B, Is_Keyword'Access)); + R.Set (Constructor ("list?"), (B, Is_List'Access)); + R.Set (Constructor ("macro?"), (B, Is_Macro'Access)); + R.Set (Constructor ("map?"), (B, Is_Map'Access)); + R.Set (Constructor ("nil?"), (B, Is_Nil'Access)); + R.Set (Constructor ("number?"), (B, Is_Number'Access)); + R.Set (Constructor ("sequential?"), (B, Is_Sequential'Access)); + R.Set (Constructor ("string?"), (B, Is_String'Access)); + R.Set (Constructor ("symbol?"), (B, Is_Symbol'Access)); + R.Set (Constructor ("true?"), (B, Is_True'Access)); + R.Set (Constructor ("vector?"), (B, Is_Vector'Access)); + R.Set (Constructor ("keys"), (B, Maps.Keys'Access)); + R.Set (Constructor ("keyword"), (B, Keyword'Access)); + R.Set (Constructor ("<="), (B, Less_Equal'Access)); + R.Set (Constructor ("<"), (B, Less_Than'Access)); + R.Set (Constructor ("list"), (B, Lists.List'Access)); + R.Set (Constructor ("map"), (B, Map'Access)); + R.Set (Constructor ("meta"), (B, Meta'Access)); + R.Set (Constructor ("nth"), (B, Lists.Nth'Access)); + R.Set (Constructor ("pr-str"), (B, Pr_Str'Access)); + R.Set (Constructor ("println"), (B, Println'Access)); + R.Set (Constructor ("prn"), (B, Prn'Access)); + R.Set (Constructor ("*"), (B, Product'Access)); + R.Set (Constructor ("read-string"), (B, Read_String'Access)); + R.Set (Constructor ("readline"), (B, Readline'Access)); + R.Set (Constructor ("reset!"), (B, Atoms.Reset'Access)); + R.Set (Constructor ("rest"), (B, Lists.Rest'Access)); + R.Set (Constructor ("seq"), (B, Seq'Access)); + R.Set (Constructor ("slurp"), (B, Slurp'Access)); + R.Set (Constructor ("str"), (B, Str'Access)); + R.Set (Constructor ("-"), (B, Subtraction'Access)); + R.Set (Constructor ("swap!"), (B, Swap'Access)); + R.Set (Constructor ("symbol"), (B, Symbol'Access)); + R.Set (Constructor ("throw"), (B, Throw'Access)); + R.Set (Constructor ("time-ms"), (B, Time_Ms'Access)); + R.Set (Constructor ("vals"), (B, Maps.Vals'Access)); + R.Set (Constructor ("vector"), (B, Lists.Vector'Access)); + R.Set (Names.With_Meta, (B, With_Meta'Access)); end Core; diff --git a/ada2/core.ads b/ada2/core.ads index 89d5414cc8..eb1d8dd008 100644 --- a/ada2/core.ads +++ b/ada2/core.ads @@ -1,17 +1,19 @@ -with Environments; -with Types; pragma Elaborate_All (Types); +limited with Environments; +with Types.Mal; -package Core is +package Core with Elaborate_Body is - type Eval_Callback_Type is access - function (Ast : in Types.Mal_Type; - Env : in Environments.Ptr) return Types.Mal_Type; + -- Initialization of this package fills Environments.Repl with + -- built-in functions. - procedure Add_Built_In_Functions - (Repl : in Environments.Ptr; - Eval_Callback : in not null Eval_Callback_Type); + Eval_Ref : access function (Ast : in Types.Mal.T; + Env : in Environments.Ptr) + return Types.Mal.T; + -- Set by the main program at startup. Exception_Throwed : exception; - Last_Exception : Types.Mal_Type; + Last_Exception : Types.Mal.T; + -- When the exception is throwed, Last_Exception is set with the + -- related Data. end Core; diff --git a/ada2/environments.adb b/ada2/environments.adb index 79c09bcd7f..75fef4b67c 100644 --- a/ada2/environments.adb +++ b/ada2/environments.adb @@ -1,166 +1,348 @@ -with Ada.Containers.Hashed_Maps; use type Ada.Containers.Count_Type; +with Ada.Containers.Hashed_Maps; with Ada.Unchecked_Deallocation; -with Atoms; -with Names; -with Strings; use type Strings.Ptr; -with Types; use type Types.Kind_Type; package body Environments is - -- There must be a reference level so that functions may keep - -- track of their initial environment, and another one for - -- reallocations. + use Types; - package Maps is new Ada.Containers.Hashed_Maps - (Key_Type => Strings.Ptr, - Element_Type => Types.Mal_Type, - Hash => Strings.Hash, - Equivalent_Keys => Strings."=", - "=" => Types."="); + -- The Eval built-in uses the REPL root environment (index 1), + -- all others parameters only repeat the top index. - type Env_Record is limited record - Data : Maps.Map; - Outer : Env_Access; - Refs : Positive; - end record; + package HM is new Ada.Containers.Hashed_Maps + (Key_Type => Symbols.Ptr, + Element_Type => Mal.T, + Hash => Symbols.Hash, + Equivalent_Keys => Symbols."=", + "=" => Mal."="); - procedure Free is new Ada.Unchecked_Deallocation (Object => Env_Record, - Name => Env_Access); + type Stack_Record + (Outer_On_Stack : Boolean := True) is record + Data : HM.Map := HM.Empty_Map; + Refs : Natural := 1; + -- Only references via the Ptr type. + -- References from the stack or Alias are not counted here. + Alias : Heap_Access := null; + -- Used by the closures and heap records to refer to this stack + -- record, so that if it moves to the heap we only need to + -- update the alias. + case Outer_On_Stack is + when True => + Outer_Index : Stack_Index := 0; + when False => + Outer_Ref : Heap_Access := null; + end case; + end record + with Dynamic_Predicate => 0 < Refs + and (Alias = null or else Alias.all.Outer = null) + and (if Outer_On_Stack + then Outer_Index <= Top + else Outer_Ref /= null); + + -- It is forbidden to change the discriminant of an access type, + -- so we cannot use a discriminant here. + type Heap_Record is limited record + Refs : Natural := 1; + Data : HM.Map := HM.Empty_Map; + Index : Stack_Index; + Outer : Heap_Access := null; + end record + with Dynamic_Predicate => + (if Outer = null + then Index in 1 .. Top and Data.Is_Empty + else 0 < Refs); + -- Either an alias for a stack element or an actual environment. + + -- There could be one single type, but this would enlarge the + -- stack without simplifying the code, and prevent some more + -- static type checking. + + Stack : array (Stack_Index range 1 .. Stack_Index'Last) of Stack_Record; + -- The default value gives a consistent value to Stack (1), + -- compatible with the Repl constant. + + procedure Free is new Ada.Unchecked_Deallocation (Heap_Record, Heap_Access); + procedure Unreference (Reference : in out Heap_Access); ---------------------------------------------------------------------- - procedure Adjust (Object : in out Ptr) is + procedure Adjust (Object : in out Closure_Ptr) is begin - Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + if Object.Ref /= null then + Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + end if; end Adjust; - function Alloc return Ptr - is (Ada.Finalization.Controlled with - Ref => new Env_Record'(Data => Maps.Empty_Map, - Outer => null, - Refs => 1)); + function Copy_Pointer (Env : in Ptr) return Ptr is + begin + Stack (Env.Index).Refs := Stack (Env.Index).Refs + 1; + return (Ada.Finalization.Limited_Controlled with Env.Index); + end Copy_Pointer; + + -- procedure Dump_Stack (Long : Boolean := False) is + -- use Ada.Text_IO; + -- use Ada.Text_IO.Unbounded_IO; + -- begin + -- for I in 1 .. Top loop + -- if Long then + -- Put ("Level"); + -- end if; + -- Put (I'Img); + -- if Long then + -- New_Line; + -- Put_Line (" refs=" & Stack (I).Refs'Img); + -- if Stack (I).Alias = null then + -- Put_Line (" no alias"); + -- else + -- Put_Line (" an alias with" & Stack (I).Alias.all.Refs'Img + -- & " refs"); + -- end if; + -- end if; + -- if Long then + -- Put (" outer="); + -- else + -- Put (" (->"); + -- end if; + -- if Stack (I).Outer_On_Stack then + -- Put (Stack (I).Outer_Index'Img); + -- elsif Stack (I).Outer_Ref.all.Outer = null then + -- if Long then + -- Put ("alias for "); + -- end if; + -- Put (Stack (I).Outer_Ref.all.Index'Img); + -- else + -- Put (" closure for ex " & Stack (I).Outer_Ref.all.Index'Img); + -- end if; + -- if Long then + -- New_Line; + -- else + -- Put ("):"); + -- end if; + -- for P in Stack (I).Data.Iterate loop + -- if HM.Element (P).Kind /= Kind_Builtin then -- skip built-ins. + -- if Long then + -- Put (" "); + -- else + -- Put (' '); + -- end if; + -- Put (HM.Key (P).To_String); + -- Put (':'); + -- Put (Printer.Pr_Str (HM.Element (P))); + -- if Long then + -- New_Line; + -- end if; + -- end if; + -- end loop; + -- if Long then + -- Put (" ... built-ins"); + -- else + -- New_Line; + -- end if; + -- end loop; + -- if Long then + -- New_Line; + -- end if; + -- end Dump_Stack; - function Alloc (Outer : in Ptr) return Ptr is + procedure Finalize (Object : in out Closure_Ptr) is begin - Outer.Ref.all.Refs := Outer.Ref.all.Refs + 1; - return (Ada.Finalization.Controlled with - Ref => new Env_Record'(Data => Maps.Empty_Map, - Outer => Outer.Ref, - Refs => 1)); - end Alloc; - - procedure Finalize (Object : in out Ptr) - is - Ref : Env_Access; - Refs : Positive; + Unreference (Object.Ref); + end Finalize; + + procedure Finalize (Object : in out Ptr) is begin - if Object.Ref /= null then - Ref := Object.Ref; - Object.Ref := null; + if 0 < Object.Index then + if 0 < Stack (Object.Index).Refs then + Stack (Object.Index).Refs := Stack (Object.Index).Refs - 1; + end if; + Object.Index := 0; + + -- If Index = Top and there are no more references. loop - Refs := Ref.all.Refs; - if 1 < Refs then - Ref.all.Refs := Refs - 1; - exit; - end if; + pragma Assert (0 < Top); declare - Tmp : Env_Access := Ref; + R : Stack_Record renames Stack (Top); begin - Ref := Ref.all.Outer; - Free (Tmp); + exit when 0 < R.Refs; + + if Top = 1 then + R.Data.Clear; + if R.Alias /= null then + pragma Assert (R.Alias.all.Outer = null); + pragma Assert (R.Alias.all.Refs = 0); + Free (R.Alias); + end if; + exit; + elsif R.Alias = null then + R.Data.Clear; + if not R.Outer_On_Stack then + Unreference (R.Outer_Ref); + end if; + elsif R.Alias.all.Refs = 0 then + pragma Assert (R.Alias.all.Outer = null); + Free (R.Alias); + R.Data.Clear; + if not R.Outer_On_Stack then + Unreference (R.Outer_Ref); + end if; + else + -- Detach this environment from the stack. + + -- The reference count is already correct. + + -- Copy the hashmap contents without reallocation.. + R.Alias.all.Data.Move (R.Data); + + -- The Index will not be used anymore. + + -- We need the parent to have an alias, in case it + -- must be detached later. + if R.Outer_On_Stack then + declare + O : Stack_Record renames Stack (R.Outer_Index); + begin + if O.Alias = null then + O.Alias := new Heap_Record'(Index => R.Outer_Index, + others => <>); + else + O.Alias.all.Refs := O.Alias.all.Refs + 1; + end if; + R.Alias.all.Outer := O.Alias; + end; + else + R.Alias.all.Outer := R.Outer_Ref; + end if; + R.Alias := null; + end if; end; - exit when Ref = null; + Top := Top - 1; end loop; end if; end Finalize; - function Get (Container : in Ptr; - Key : in Strings.Ptr) return Types.Mal_Type - is - Ref : Env_Access := Container.Ref; - Position : Maps.Cursor; + function Get (Env : in Ptr; + Key : in Symbols.Ptr) + return Mal.T is + Index : Stack_Index := Env.Index; + Ref : Heap_Access; + Definition : HM.Cursor; begin - loop - Position := Ref.all.Data.Find (Key); - if Maps.Has_Element (Position) then - return Ref.all.Data (Position); - end if; - Ref := Ref.all.Outer; - exit when Ref = null; - end loop; - raise Unknown_Key with "'" & Key.Deref & "' not found"; + Main_Loop : loop + Index_Loop : loop + Definition := Stack (Index).Data.Find (Key); + if HM.Has_Element (Definition) then + return HM.Element (Definition); + end if; + exit Index_Loop when not Stack (Index).Outer_On_Stack; + Index := Stack (Index).Outer_Index; + exit Main_Loop when Index = 0; + end loop Index_Loop; + Ref := Stack (Index).Outer_Ref; + Ref_Loop : loop + Definition := Ref.all.Data.Find (Key); + if HM.Has_Element (Definition) then + return HM.Element (Definition); + end if; + exit Ref_Loop when Ref.all.Outer = null; + Ref := Ref.all.Outer; + end loop Ref_Loop; + Index := Ref.all.Index; + end loop Main_Loop; + raise Unknown_Key with "'" & Key.To_String & "' not found"; end Get; - procedure Increase_Capacity (Container : in Ptr; - Capacity : in Natural) - is - New_Capacity : constant Ada.Containers.Count_Type - := Container.Ref.all.Data.Length - + Ada.Containers.Count_Type (Capacity); + function New_Closure (Env : in Ptr'Class) return Closure_Ptr is + Alias : Heap_Access renames Stack (Env.Index).Alias; begin - if Container.Ref.all.Data.Capacity < New_Capacity then - Container.Ref.all.Data.Reserve_Capacity (New_Capacity); + if Alias = null then + Alias := new Heap_Record'(Index => Env.Index, others => <>); + else + Alias.all.Refs := Alias.all.Refs + 1; end if; - end Increase_Capacity; + return (Ada.Finalization.Controlled with Alias); + end New_Closure; - procedure Replace_With_Subenv (Item : in out Ptr) is + procedure Replace_With_Sub (Env : in out Ptr) is + R : Stack_Record renames Stack (Env.Index); begin - if 1 < Item.Ref.all.Refs then - Item.Ref := new Env_Record'(Data => Maps.Empty_Map, - Outer => Item.Ref, - Refs => 1); + if Env.Index < Top or 1 < R.Refs + or (R.Alias /= null and then 0 < R.Alias.all.Refs) + then + R.Refs := R.Refs - 1; + Top := Top + 1; + pragma Assert (Stack (Top).Data.Is_Empty); + pragma Assert (Stack (Top).Alias = null); + Stack (Top) := (Outer_Index => Env.Index, + others => <>); + Env.Index := Top; end if; - end Replace_With_Subenv; + -- Else reuse the top stack record, including its map and its + -- unreferenced alias if any. + end Replace_With_Sub; - procedure Set (Container : in Ptr; - Key : in Strings.Ptr; - New_Item : in Types.Mal_Type) is + procedure Replace_With_Sub (Env : in out Ptr; + Outer : in Closure_Ptr'Class) is begin - Container.Ref.all.Data.Include (Key, New_Item); + Finalize (Env); + Outer.Ref.all.Refs := Outer.Ref.all.Refs + 1; + Top := Top + 1; + pragma Assert (Stack (Top).Data.Is_Empty); + pragma Assert (Stack (Top).Alias = null); + Stack (Top) := (Outer_On_Stack => False, + Outer_Ref => Outer.Ref, + others => <>); + Env.Index := Top; + end Replace_With_Sub; + + procedure Set (Env : in Ptr; + Key : in Symbols.Ptr; + New_Element : in Mal.T) is + begin + Stack (Env.Index).Data.Include (Key, New_Element); end Set; - procedure Set_Binds (Container : in Ptr; - Formals : in Lists.Ptr; - Actuals : in Types.Mal_Type_Array) - is - -- The assertions should be a precondition, but cannot be - -- expressed with a "limited with" view on Types. + function Sub (Outer : in Closure_Ptr'Class) return Ptr is begin - if Formals.Length <= 1 - or else Formals.Element (Formals.Length - 1).S /= Names.Ampersand - then - pragma Assert (Formals.Length = Actuals'Length); - pragma Assert (for all I in 1 .. Formals.Length => - Formals.Element (I).Kind = Types.Kind_Symbol - and then Formals.Element (I).S /= Names.Ampersand); - Increase_Capacity (Container, Formals.Length); - for I in 1 .. Formals.Length loop - Container.Ref.all.Data.Include (Formals.Element (I).S, - Actuals (Actuals'First + I - 1)); - end loop; - else + Outer.Ref.all.Refs := Outer.Ref.all.Refs + 1; + Top := Top + 1; + pragma Assert (Stack (Top).Data.Is_Empty); + pragma Assert (Stack (Top).Alias = null); + Stack (Top) := (Outer_On_Stack => False, + Outer_Ref => Outer.Ref, + others => <>); + return (Ada.Finalization.Limited_Controlled with Top); + end Sub; + + function Sub (Outer : in Ptr) return Ptr is + R : Stack_Record renames Stack (Outer.Index); + begin + R.Refs := R.Refs + 1; + Top := Top + 1; + pragma Assert (Stack (Top).Data.Is_Empty); + pragma Assert (Stack (Top).Alias = null); + Stack (Top) := (Outer_Index => Outer.Index, + others => <>); + return (Ada.Finalization.Limited_Controlled with Top); + end Sub; + + procedure Unreference (Reference : in out Heap_Access) is + Ref : Heap_Access := Reference; + begin + Reference := null; + loop + exit when Ref = null; + exit when Ref.all.Refs = 0; + Ref.all.Refs := Ref.all.Refs - 1; + exit when 0 < Ref.all.Refs; + exit when Ref.all.Outer = null; -- An alias. Do not free it + -- now, it may be useful for another closure. declare - Len : constant Natural := Formals.Length - 2; + Tmp : Heap_Access := Ref; begin - pragma Assert (Len <= Actuals'Length); - pragma Assert (for all I in 1 .. Len => - Formals.Element (I).Kind = Types.Kind_Symbol - and then Formals.Element (I).S /= Names.Ampersand); - pragma Assert (Formals.Element (Len + 1).Kind = Types.Kind_Symbol); - pragma Assert (Formals.Element (Len + 1).S = Names.Ampersand); - pragma Assert (Formals.Element (Len + 2).Kind = Types.Kind_Symbol); - pragma Assert (Formals.Element (Len + 2).S /= Names.Ampersand); - Increase_Capacity (Container, Len + 1); - for I in 1 .. Len loop - Container.Ref.all.Data.Include - (Formals.Element (I).S, Actuals (Actuals'First + I - 1)); - end loop; - Container.Ref.all.Data.Include - (Formals.Element (Formals.Length).S, - (Types.Kind_List, Atoms.No_Element, - Lists.Alloc (Actuals (Actuals'First + Len .. Actuals'Last)))); + Ref := Ref.all.Outer; + Free (Tmp); + pragma Unreferenced (Tmp); end; - end if; - end Set_Binds; + end loop; + end Unreference; end Environments; diff --git a/ada2/environments.ads b/ada2/environments.ads index c1584be4b9..5ef4561372 100644 --- a/ada2/environments.ads +++ b/ada2/environments.ads @@ -1,54 +1,115 @@ private with Ada.Finalization; -with Lists; -with Strings; -limited with Types; -package Environments is +with Types.Mal; +with Types.Symbols; - type Ptr is tagged private; - -- Any variable must be assigned immediately with one of the two - -- following functions. - function Alloc return Ptr - with Inline; - function Alloc (Outer : in Ptr) return Ptr - with Inline; - -- A hidden invariant ensures this when assertions are enabled. +package Environments with Elaborate_Body is - procedure Increase_Capacity (Container : in Ptr; - Capacity : in Natural) - with Inline; + -- This implementation relies on the fact that the caller only + -- ever references environments in its execution stack. - procedure Replace_With_Subenv (Item : in out Ptr) - with Inline; - -- Equivalent to Item := Alloc (Outer => Item, Capacity), but - -- faster when Item was the last reference to its environment, as - -- the storage and maps are then reused. + -- When a function closure references an environment that the + -- execution leaves behind, a dynamically allocated block is used + -- instead. - procedure Set (Container : in Ptr; - Key : in Strings.Ptr; - New_Item : in Types.Mal_Type) - with Inline; + -- The eval built-in requires REPL (see the implementation of + -- load-file), so we cannot assume that the caller only sees the + -- current environment. + + type Ptr (<>) is tagged limited private; + -- This type is controlled in order count the references to a + -- given environment, even during exception propagation. + -- Since Ptr is limited with a hidden discriminant, any variable + -- must immediately be assigned with one of + -- * Repl (in which case a renaming is probably better), + -- * Copy_Pointer, + -- * Sub (either from a Ptr or from a Closure_Ptr). + -- Usual assignment with reference counting is not provided + -- because we want to enforce the use of the more efficient + -- Replace_With_Sub. - procedure Set_Binds (Container : in Ptr; - Formals : in Lists.Ptr; - Actuals : in Types.Mal_Type_Array); + Repl : constant Ptr; + -- The top environment. - function Get (Container : in Ptr; - Key : in Strings.Ptr) return Types.Mal_Type; + function Copy_Pointer (Env : in Ptr) return Ptr with Inline; + + function Sub (Outer : in Ptr) return Ptr with Inline; + + procedure Replace_With_Sub (Env : in out Ptr) with Inline; + -- Like Env := Sub (Outer => Env); except that Env is finalized + -- *before* the assignement, so its memory may be reused by the + -- new environment. + + procedure Set (Env : in Ptr; + Key : in Types.Symbols.Ptr; + New_Element : in Types.Mal.T) + with Inline; + + function Get (Env : in Ptr; + Key : in Types.Symbols.Ptr) + return Types.Mal.T; Unknown_Key : exception; - -- procedure Dump; + -- Function closures. + + type Closure_Ptr is tagged private; + Null_Closure : constant Closure_Ptr; + + function Sub (Outer : in Closure_Ptr'Class) return Ptr; + + procedure Replace_With_Sub (Env : in out Ptr; + Outer : in Closure_Ptr'Class); + -- Like Env := Sub (Outer => Outer); except that Env is finalized + -- *before* the assignement, so its memory can be reused by the + -- new environment. This is important for tail call optimization. + + function New_Closure (Env : in Ptr'Class) return Closure_Ptr; + -- The class-wide argument does not make much sense, but avoids + -- the compiler wondering on which type is should dispatch. private - type Env_Record; - type Env_Access is access Env_Record; - type Ptr is new Ada.Finalization.Controlled with record - Ref : Env_Access := null; + -- There must be a reference level so that functions may keep + -- track of their initial environment, and another one for + -- reallocations. The second one is delegated to a predefined Ada + -- container. + + -- MAL maps may be tempting, but we do not want to copy the whole + -- map for each addition or removal. + + -- Some tests seem to show that a hashmap is three times faster + -- than a vector with (key, value) couples. + + -- We allow the null value so that the empty environment in a + -- macro does not trigger an allocation. + + -- 300 for normal tests + -- 7_500 for make ada2 && make MAL_IMPL=ada2 test^mal + -- 150_000 for make ada2 && make perf^ada2 + type Stack_Index is range 0 .. 150_000; + + -- See README for the implementation of reference counting. + + type Ptr is new Ada.Finalization.Limited_Controlled with record + Index : Stack_Index := 0; end record - with Invariant => Ptr.Ref /= null; - overriding procedure Adjust (Object : in out Ptr) with Inline; - overriding procedure Finalize (Object : in out Ptr); - -- Predefined equality is fine. + with Invariant => Index in 1 .. Top; + overriding procedure Finalize (Object : in out Ptr) with Inline; + pragma Finalize_Storage_Only (Ptr); + + Top : Stack_Index := 1; + Repl : constant Ptr := (Ada.Finalization.Limited_Controlled with 1); + + type Heap_Record; + type Heap_Access is access Heap_Record; + type Closure_Ptr is new Ada.Finalization.Controlled with record + Ref : Heap_Access := null; + end record; + overriding procedure Adjust (Object : in out Closure_Ptr) with Inline; + overriding procedure Finalize (Object : in out Closure_Ptr) with Inline; + pragma Finalize_Storage_Only (Closure_Ptr); + + Null_Closure : constant Closure_Ptr + := (Ada.Finalization.Controlled with null); end Environments; diff --git a/ada2/lists.adb b/ada2/lists.adb deleted file mode 100644 index 6aed887521..0000000000 --- a/ada2/lists.adb +++ /dev/null @@ -1,95 +0,0 @@ -with Ada.Unchecked_Deallocation; -with Atoms; -with Types; - -package body Lists is - - type List_Record (Last : Positive) is limited record - Data : Types.Mal_Type_Array (1 .. Last); - Refs : Positive; - end record; - -- The invariant for Ptr is: - -- Ptr.Ref = null or else Ptr.First <= Ptr.Ref.all.Last - -- but we cannot express this in the specification because the limited - -- view on Types forbids to define List_Record there. - - procedure Free is new Ada.Unchecked_Deallocation (Object => List_Record, - Name => List_Access); - - ---------------------------------------------------------------------- - - function "=" (Left, Right : in Ptr) return Boolean is - (if Left.Ref = null - then Right.Ref = null - else - -- As strange as it may seem, this assertion fails when - -- running "(= [(list)] (list []))". - -- pragma Assert - -- ((Left.Ref.all.Data (1) = Right.Ref.all.Data (1)) - -- = - -- (Left.Ref.all.Data (1 .. 1) = Right.Ref.all.Data (1 .. 1))); - -- This may be a compiler bug. - Right.Ref /= null - and then Left.Ref.all.Last = Right.Ref.all.Last - and then (for all I in 1 .. Left.Ref.all.Last => - Types."=" (Left.Ref.all.Data (I), - Right.Ref.all.Data (I)))); - - procedure Adjust (Object : in out Ptr) is - begin - if Object.Ref /= null then - Object.Ref.all.Refs := Object.Ref.all.Refs + 1; - end if; - end Adjust; - - function Element (Container : in Ptr; - Index : in Positive) return Types.Mal_Type is - (Container.Ref.all.Data (Index)); - - procedure Finalize (Object : in out Ptr) - is - Refs : Positive; - begin - -- Ensure that we can be called twice in a row (7.6.1(24)). - if Object.Ref /= null then - Refs := Object.Ref.all.Refs; - if 1 < Refs then - Object.Ref.all.Refs := Refs - 1; - Object.Ref := null; - else - -- pragma Assert (Ptr (Object.Ref.all.Id) = Object.Ref); - -- Ptr (Object.Ref.all.Id) := null; - Free (Object.Ref); - end if; - end if; - end Finalize; - - function Length (Source : in Ptr) return Natural is - (if Source.Ref = null then 0 else Source.Ref.all.Last); - - function Alloc (Source : in Types.Mal_Type_Array) return Ptr is - (if Source'Length = 0 - then Empty_List - else (Ada.Finalization.Controlled with - Ref => new List_Record'(Data => Source, - Last => Source'Length, - Refs => 1))); - - function Alloc (Length : in Natural) return Ptr is - (if Length = 0 - then Empty_List - else (Ada.Finalization.Controlled with - Ref => new List_Record' - (Data => (1 .. Length => (Types.Kind_Nil, Atoms.No_Element)), - Last => Length, - Refs => 1))); - - procedure Replace_Element (Source : in Ptr; - Index : in Positive; - New_Value : in Types.Mal_Type) is - begin - pragma Assert (Source.Ref.all.Refs = 1); - Source.Ref.all.Data (Index) := New_Value; - end Replace_Element; - -end Lists; diff --git a/ada2/lists.ads b/ada2/lists.ads deleted file mode 100644 index 28c76cfe39..0000000000 --- a/ada2/lists.ads +++ /dev/null @@ -1,47 +0,0 @@ -private with Ada.Finalization; -limited with Types; - -package Lists is - - -- A pointer to an array of Mal_Type elements. It differs from - -- Ada.Containers.Vectors because assignment give another pointer - -- to the same storage and does not copy contents. - - type Ptr is tagged private; - Empty_List : constant Ptr; -- The default value. - - function Length (Source : in Ptr) return Natural - with Inline; - - function Element (Container : in Ptr; - Index : in Positive) return Types.Mal_Type - with Inline, Pre => Index <= Container.Length; - - function Alloc (Length : in Natural) return Ptr - with Inline; - -- All elements are Nil, the default value for Mal_Type. - - function Alloc (Source : in Types.Mal_Type_Array) return Ptr - with Inline; - - procedure Replace_Element (Source : in Ptr; - Index : in Positive; - New_Value : in Types.Mal_Type) - with Inline, Pre => Index <= Source.Length; - -- An assertion checks that Source is the only reference to its - -- storage. - -private - - type List_Record; - type List_Access is access List_Record; - type Ptr is new Ada.Finalization.Controlled with record - Ref : List_Access := null; - end record; - overriding procedure Adjust (Object : in out Ptr) with Inline; - overriding procedure Finalize (Object : in out Ptr) with Inline; - overriding function "=" (Left, Right : in Ptr) return Boolean; - - Empty_List : constant Ptr := (Ada.Finalization.Controlled with Ref => null); - -end Lists; diff --git a/ada2/maps.adb b/ada2/maps.adb deleted file mode 100644 index f1e1eacc85..0000000000 --- a/ada2/maps.adb +++ /dev/null @@ -1,160 +0,0 @@ -with Ada.Containers.Hashed_Maps; -with Ada.Unchecked_Deallocation; -with Strings; -with Types; - -package body Maps is - - function Hash (Item : in Types.Mal_Type) return Ada.Containers.Hash_Type - with Inline, Pre => Item.Kind in Types.Kind_String | Types.Kind_Keyword; - - package Hashed_Maps is new Ada.Containers.Hashed_Maps - (Key_Type => Types.Mal_Type, - Element_Type => Types.Mal_Type, - Hash => Hash, - Equivalent_Keys => Types."=", - "=" => Types."="); - - type Map_Record is limited record - Data : Hashed_Maps.Map; - Refs : Positive; - end record; - - procedure Free is new Ada.Unchecked_Deallocation (Object => Map_Record, - Name => Map_Access); - - use type Ada.Containers.Count_Type; - - ---------------------------------------------------------------------- - - function "=" (Left, Right : in Ptr) return Boolean is - (Hashed_Maps."=" (Left.Ref.all.Data, Right.Ref.all.Data)); - - procedure Adjust (Object : in out Ptr) is - begin - Object.Ref.all.Refs := Object.Ref.all.Refs + 1; - end Adjust; - - function Assoc (Container : in Ptr; - Pairs : in Types.Mal_Type_Array) return Ptr - is - pragma Assert (Pairs'Length mod 2 = 0); - Pair_Count : constant Ada.Containers.Count_Type - := Ada.Containers.Count_Type (Pairs'Length) / 2; - Result : Ptr; - begin - Result.Ref.all.Data.Reserve_Capacity (Pair_Count - + Container.Ref.all.Data.Length); - Result.Ref.all.Data.Assign (Container.Ref.all.Data); - for I in 0 .. Pairs'Length / 2 - 1 loop - pragma Assert (Pairs (Pairs'First + 2 * I).Kind in Types.Kind_String - | Types.Kind_Keyword); - Result.Ref.all.Data.Include (Pairs (Pairs'First + 2 * I), - Pairs (Pairs'First + 2 * I + 1)); - end loop; - return Result; - end Assoc; - - function Contains (Container : in Ptr; - Key : in Types.Mal_Type) return Boolean is - (Container.Ref.all.Data.Contains (Key)); - - function Dissoc (Source : in Ptr; - Keys : in Types.Mal_Type_Array) return Ptr - is - Result : Ptr; - begin - Result.Ref.all.Data.Assign (Source.Ref.all.Data); - for I in Keys'Range loop - pragma Assert (Keys (I).Kind in Types.Kind_String - | Types.Kind_Keyword); - Result.Ref.all.Data.Exclude (Keys (I)); - end loop; - return Result; - end Dissoc; - - procedure Finalize (Object : in out Ptr) - is - Refs : Positive; - begin - -- Finalize may be called twice. - if Object.Ref /= null then - Refs := Object.Ref.all.Refs; - if 1 < Refs then - Object.Ref.all.Refs := Refs - 1; - Object.Ref := null; - else - Free (Object.Ref); - end if; - end if; - end Finalize; - - procedure Iterate - (Container : in Ptr; - Process : not null access procedure (Key : in Types.Mal_Type; - Element : in Types.Mal_Type)) is - begin - for Position in Container.Ref.all.Data.Iterate loop - Process.all (Hashed_Maps.Key (Position), - Hashed_Maps.Element (Position)); - end loop; - end Iterate; - - function Get (Container : in Ptr; - Key : in Types.Mal_Type) return Types.Mal_Type - is - Position : Hashed_Maps.Cursor; - begin - Position := Container.Ref.all.Data.Find (Key); - if Hashed_Maps.Has_Element (Position) then - return Hashed_Maps.Element (Position); - end if; - raise Unknown_Key with "'" & Key.S.Deref & "' not found"; - end Get; - - function Hash (Item : in Types.Mal_Type) return Ada.Containers.Hash_Type is - (Item.S.Hash); - - function Hash_Map (Pairs : in Types.Mal_Type_Array) return Ptr - is - pragma Assert (Pairs'Length mod 2 = 0); - Pair_Count : constant Ada.Containers.Count_Type - := Ada.Containers.Count_Type (Pairs'Length) / 2; - Result : Ptr; - begin - Result.Ref.all.Data.Reserve_Capacity (Pair_Count); - for I in 0 .. Pairs'Length / 2 - 1 loop - pragma Assert (Pairs (Pairs'First + 2 * I).Kind in Types.Kind_String - | Types.Kind_Keyword); - Result.Ref.all.Data.Include (Pairs (Pairs'First + 2 * I), - Pairs (Pairs'First + 2 * I + 1)); - end loop; - return Result; - end Hash_Map; - - procedure Initialize (Object : in out Ptr) is - begin - Object.Ref := new Map_Record'(Data => Hashed_Maps.Empty_Map, - Refs => 1); - end Initialize; - - function Length (Container : in Ptr) return Natural - is (Natural (Container.Ref.all.Data.Length)); - - function Map (Container : in Ptr; - F : not null access function (X : in Types.Mal_Type) - return Types.Mal_Type) - return Ptr - is - Result : Ptr; - begin - Result.Ref.all.Data.Assign (Container.Ref.all.Data); - -- Ensure the invariants before calling F, in case it raises exceptions. - for Position in Result.Ref.all.Data.Iterate loop - Result.Ref.all.Data.Replace_Element - (Position, F.all (Hashed_Maps.Element (Position))); - end loop; - return Result; - end Map; - -end Maps; diff --git a/ada2/maps.ads b/ada2/maps.ads deleted file mode 100644 index 5bf81efa14..0000000000 --- a/ada2/maps.ads +++ /dev/null @@ -1,59 +0,0 @@ -private with Ada.Finalization; -with Lists; -limited with Types; - -package Maps is - - -- A pointer to an Ada.Containers.Hashed_Maps.Map of - -- Types.Mal_Type. Keys must be Strings or Keywords. We can - -- probably not state this with a limited with, so this will - -- become an assertion. - - type Ptr is tagged private; - -- The default value is empty. - - function Length (Container : in Ptr) return Natural - with Inline; - - function Hash_Map (Pairs : in Types.Mal_Type_Array) return Ptr; - - function Assoc (Container : in Ptr; - Pairs : in Types.Mal_Type_Array) return Ptr; - - function Dissoc (Source : in Ptr; - Keys : in Types.Mal_Type_Array) return Ptr; - - function Map (Container : in Ptr; - F : not null access function (X : in Types.Mal_Type) - return Types.Mal_Type) - return Ptr; - - procedure Iterate - (Container : in Ptr; - Process : not null access procedure (Key : in Types.Mal_Type; - Element : in Types.Mal_Type)) - with Inline; - - function Contains (Container : in Ptr; - Key : in Types.Mal_Type) return Boolean - with Inline; - - function Get (Container : in Ptr; - Key : in Types.Mal_Type) return Types.Mal_Type - with Inline; - Unknown_Key : exception; - -private - - type Map_Record; - type Map_Access is access Map_Record; - type Ptr is new Ada.Finalization.Controlled with record - Ref : Map_Access := null; - end record - with Invariant => Ptr.Ref /= null; - overriding procedure Initialize (Object : in out Ptr) with Inline; - overriding procedure Adjust (Object : in out Ptr) with Inline; - overriding procedure Finalize (Object : in out Ptr) with Inline; - overriding function "=" (Left, Right : in Ptr) return Boolean with Inline; - -end Maps; diff --git a/ada2/names.ads b/ada2/names.ads deleted file mode 100644 index 40613a160c..0000000000 --- a/ada2/names.ads +++ /dev/null @@ -1,87 +0,0 @@ -with Strings; use Strings; - -package Names is - - -- Symbols known at compile time are allocated at program - -- start, in order to avoid repeated allocations and - -- deallocations during each Read and /Eval/Print cycle. The - -- reference is kept so each usage does not trigger a search in - -- the global hash map. - - Ada2 : constant Ptr := Alloc ("ada2"); - Ampersand : constant Ptr := Alloc ("&"); - Apply : constant Ptr := Alloc ("apply"); - Argv : constant Ptr := Alloc ("*ARGV*"); - Assoc : constant Ptr := Alloc ("assoc"); - Asterisk : constant Ptr := Alloc ("*"); - Atom : constant Ptr := Alloc ("atom"); - Catch : constant Ptr := Alloc ("catch*"); - Concat : constant Ptr := Alloc ("concat"); - Conj : constant Ptr := Alloc ("conj"); - Cons : constant Ptr := Alloc ("cons"); - Contains : constant Ptr := Alloc ("contains?"); - Count : constant Ptr := Alloc ("count"); - Def : constant Ptr := Alloc ("def!"); - Defmacro : constant Ptr := Alloc ("defmacro!"); - Deref : constant Ptr := Alloc ("deref"); - Dissoc : constant Ptr := Alloc ("dissoc"); - Equals : constant Ptr := Alloc ("="); - Eval : constant Ptr := Alloc ("eval"); - First : constant Ptr := Alloc ("first"); - Fn : constant Ptr := Alloc ("fn*"); - Get : constant Ptr := Alloc ("get"); - Greater_Equal : constant Ptr := Alloc (">="); - Greater_Than : constant Ptr := Alloc (">"); - Hash_Map : constant Ptr := Alloc ("hash-map"); - Host_Language : constant Ptr := Alloc ("*host-language*"); - Is_Atom : constant Ptr := Alloc ("atom?"); - Is_Empty : constant Ptr := Alloc ("empty?"); - Is_False : constant Ptr := Alloc ("false?"); - Is_Keyword : constant Ptr := Alloc ("keyword?"); - Is_List : constant Ptr := Alloc ("list?"); - Is_Map : constant Ptr := Alloc ("map?"); - Is_Nil : constant Ptr := Alloc ("nil?"); - Is_Sequential : constant Ptr := Alloc ("sequential?"); - Is_String : constant Ptr := Alloc ("string?"); - Is_Symbol : constant Ptr := Alloc ("symbol?"); - Is_True : constant Ptr := Alloc ("true?"); - Is_Vector : constant Ptr := Alloc ("vector?"); - Keys : constant Ptr := Alloc ("keys"); - Keyword : constant Ptr := Alloc ("keyword"); - Less_Equal : constant Ptr := Alloc ("<="); - Less_Than : constant Ptr := Alloc ("<"); - Let : constant Ptr := Alloc ("let*"); - List : constant Ptr := Alloc ("list"); - Macroexpand : constant Ptr := Alloc ("macroexpand"); - Mal_Do : constant Ptr := Alloc ("do"); - Mal_If : constant Ptr := Alloc ("if"); - Map : constant Ptr := Alloc ("map"); - Meta : constant Ptr := Alloc ("meta"); - Minus : constant Ptr := Alloc ("-"); - Nth : constant Ptr := Alloc ("nth"); - Plus : constant Ptr := Alloc ("+"); - Pr_Str : constant Ptr := Alloc ("pr-str"); - Println : constant Ptr := Alloc ("println"); - Prn : constant Ptr := Alloc ("prn"); - Quasiquote : constant Ptr := Alloc ("quasiquote"); - Quote : constant Ptr := Alloc ("quote"); - Read_String : constant Ptr := Alloc ("read-string"); - Readline : constant Ptr := Alloc ("readline"); - Reset : constant Ptr := Alloc ("reset!"); - Rest : constant Ptr := Alloc ("rest"); - Seq : constant Ptr := Alloc ("seq"); - Slash : constant Ptr := Alloc ("/"); - Slurp : constant Ptr := Alloc ("slurp"); - Splice_Unquote : constant Ptr := Alloc ("splice-unquote"); - Str : constant Ptr := Alloc ("str"); - Swap : constant Ptr := Alloc ("swap!"); - Symbol : constant Ptr := Alloc ("symbol"); - Throw : constant Ptr := Alloc ("throw"); - Time_Ms : constant Ptr := Alloc ("time-ms"); - Try : constant Ptr := Alloc ("try*"); - Unquote : constant Ptr := Alloc ("unquote"); - Vals : constant Ptr := Alloc ("vals"); - Vector : constant Ptr := Alloc ("vector"); - With_Meta : constant Ptr := Alloc ("with-meta"); - -end Names; diff --git a/ada2/printer.adb b/ada2/printer.adb index 6b21c4a7ef..e768f05bbd 100644 --- a/ada2/printer.adb +++ b/ada2/printer.adb @@ -1,55 +1,48 @@ with Ada.Characters.Latin_1; -with Atoms; -with Lists; -with Maps; -with Strings; + +with Types.Atoms; +with Types.Functions; +with Types.Lists; +with Types.Maps; package body Printer is use Ada.Strings.Unbounded; use Types; - procedure Print_Form (Buffer : in out Unbounded_String; - Ast : in Mal_Type; - Print_Readably : in Boolean); - procedure Print_List (Buffer : in out Unbounded_String; - List : in Lists.Ptr; - Print_Readably : in Boolean) - with Inline; - procedure Print_Function (Buffer : in out Unbounded_String; - Formals : in Lists.Ptr; - Expression : in Atoms.Ptr; - Print_Readably : in Boolean) - with Inline; - procedure Print_Map (Buffer : in out Unbounded_String; - Map : in Maps.Ptr; - Print_Readably : in Boolean) - with Inline; + procedure Print_Form (Buffer : in out Unbounded_String; + Ast : in Mal.T; + Readably : in Boolean); + procedure Print_List (Buffer : in out Unbounded_String; + List : in Lists.Ptr; + Readably : in Boolean) with Inline; + procedure Print_Function (Buffer : in out Unbounded_String; + Fn : in Functions.Ptr; + Readably : in Boolean) with Inline; + procedure Print_Map (Buffer : in out Unbounded_String; + Map : in Maps.Ptr; + Readably : in Boolean) with Inline; ---------------------------------------------------------------------- - procedure Print_Form (Buffer : in out Unbounded_String; - Ast : in Mal_Type; - Print_Readably : in Boolean) is + procedure Print_Form (Buffer : in out Unbounded_String; + Ast : in Mal.T; + Readably : in Boolean) is begin case Ast.Kind is - when Kind_Nil => Append (Buffer, "nil"); - when Kind_Boolean => - if Ast.Boolean_Value then + if Ast.Ada_Boolean then Append (Buffer, "true"); else Append (Buffer, "false"); end if; - when Kind_Symbol => - Append (Buffer, Ast.S.Deref); - + Append (Buffer, Ast.Symbol.To_String); when Kind_Number => declare - Img : constant String := Integer'Image (Ast.Integer_Value); + Img : constant String := Ast.Ada_Number'Img; F : Positive := Img'First; begin if Img (F) = ' ' then @@ -57,121 +50,113 @@ package body Printer is end if; Append (Buffer, Img (F .. Img'Last)); end; - when Kind_Keyword => Append (Buffer, ':'); - Append (Buffer, Ast.S.Deref); - + Append (Buffer, Ast.S); when Kind_String => - if Print_Readably then + if Readably then Append (Buffer, '"'); - for C of Ast.S.Deref loop - case C is - when '"' | '\' => - Append (Buffer, '\'); - Append (Buffer, C); - when Ada.Characters.Latin_1.LF => - Append (Buffer, "\n"); - when others => - Append (Buffer, C); - end case; - end loop; + declare + C : Character; + begin + for I in 1 .. Length (Ast.S) loop + C := Element (Ast.S, I); + case C is + when '"' | '\' => + Append (Buffer, '\'); + Append (Buffer, C); + when Ada.Characters.Latin_1.LF => + Append (Buffer, "\n"); + when others => + Append (Buffer, C); + end case; + end loop; + end; Append (Buffer, '"'); else - Append (Buffer, Ast.S.Deref); + Append (Buffer, Ast.S); end if; - when Kind_List => Append (Buffer, '('); - Print_List (Buffer, Ast.L, Print_Readably); + Print_List (Buffer, Ast.L, Readably); Append (Buffer, ')'); when Kind_Vector => Append (Buffer, '['); - Print_List (Buffer, Ast.L, Print_Readably); + Print_List (Buffer, Ast.L, Readably); Append (Buffer, ']'); - when Kind_Map => - Print_Map (Buffer, Ast.Map, Print_Readably); - - when Kind_Native => + Print_Map (Buffer, Ast.Map, Readably); + when Kind_Builtin | Kind_Builtin_With_Meta => Append (Buffer, "#"); when Kind_Function => Append (Buffer, "#'); when Kind_Macro => Append (Buffer, "#'); - when Kind_Atom => Append (Buffer, "(atom "); - Print_Form (Buffer, Ast.Reference.Deref, Print_Readably); + Print_Form (Buffer, Atoms.Deref (Mal.T_Array'(1 => Ast)), + Readably); Append (Buffer, ')'); - end case; end Print_Form; - procedure Print_Function (Buffer : in out Unbounded_String; - Formals : in Lists.Ptr; - Expression : in Atoms.Ptr; - Print_Readably : in Boolean) is + procedure Print_Function (Buffer : in out Unbounded_String; + Fn : in Functions.Ptr; + Readably : in Boolean) is begin - if 0 < Formals.Length then - Print_List (Buffer, Formals, Print_Readably); - Append (Buffer, " -> "); - Print_Form (Buffer, Expression.Deref, Print_Readably); - end if; + Print_List (Buffer, Fn.Formals, Readably); + Append (Buffer, " -> "); + Print_Form (Buffer, Fn.Expression, Readably); end Print_Function; - procedure Print_List (Buffer : in out Unbounded_String; - List : in Lists.Ptr; - Print_Readably : in Boolean) is + procedure Print_List (Buffer : in out Unbounded_String; + List : in Lists.Ptr; + Readably : in Boolean) is begin - if 1 <= List.Length then - Print_Form (Buffer, List.Element (1), Print_Readably); + if 0 < List.Length then + Print_Form (Buffer, List.Element (1), Readably); for I in 2 .. List.Length loop Append (Buffer, ' '); - Print_Form (Buffer, List.Element (I), Print_Readably); + Print_Form (Buffer, List.Element (I), Readably); end loop; end if; end Print_List; - procedure Print_Map (Buffer : in out Unbounded_String; - Map : in Maps.Ptr; - Print_Readably : in Boolean) - is + procedure Print_Map (Buffer : in out Unbounded_String; + Map : in Maps.Ptr; + Readably : in Boolean) is Is_First : Boolean := True; - procedure Process (Key : in Mal_Type; - Element : in Mal_Type); - procedure Process (Key : in Mal_Type; - Element : in Mal_Type) is + procedure Process (Key : in Mal.T; + Element : in Mal.T); + procedure Iterate is new Maps.Iterate (Process); + procedure Process (Key : in Mal.T; + Element : in Mal.T) is begin if Is_First then Is_First := False; else Append (Buffer, ' '); end if; - Print_Form (Buffer, Key, Print_Readably); + Print_Form (Buffer, Key, Readably); Append (Buffer, ' '); - Print_Form (Buffer, Element, Print_Readably); + Print_Form (Buffer, Element, Readably); end Process; begin Append (Buffer, '{'); - Map.Iterate (Process'Access); + Iterate (Map); Append (Buffer, '}'); end Print_Map; - function Pr_Str (Ast : in Mal_Type; - Print_Readably : in Boolean := True) - return Unbounded_String - is - Result : Unbounded_String; + function Pr_Str (Ast : in Mal.T; + Readably : in Boolean := True) return Unbounded_String is begin - Print_Form (Result, Ast, Print_Readably); - return Result; + return Buffer : Unbounded_String do + Print_Form (Buffer, Ast, Readably); + end return; end Pr_Str; end Printer; diff --git a/ada2/printer.ads b/ada2/printer.ads index 5071a801f7..219f682dae 100644 --- a/ada2/printer.ads +++ b/ada2/printer.ads @@ -1,12 +1,11 @@ with Ada.Strings.Unbounded; -with Types; -package Printer is +with Types.Mal; - pragma Elaborate_Body; +package Printer with Elaborate_Body is - function Pr_Str (Ast : in Types.Mal_Type; - Print_Readably : in Boolean := True) + function Pr_Str (Ast : in Types.Mal.T; + Readably : in Boolean := True) return Ada.Strings.Unbounded.Unbounded_String; end Printer; diff --git a/ada2/reader.adb b/ada2/reader.adb index d72af57749..36521eef21 100644 --- a/ada2/reader.adb +++ b/ada2/reader.adb @@ -1,18 +1,19 @@ with Ada.Characters.Latin_1; -with Atoms; -with Lists; -with Maps; -with Names; -with Strings; +with Ada.Strings.Unbounded; + +with Types.Lists; +with Types.Maps; +with Types.Symbols.Names; package body Reader is - function Read_Str (Source : in String) return Types.Mal_Type - is + use Types; + + function Read_Str (Source : in String) return Mal.T is First : Positive; Last : Natural := Source'First - 1; - function Read_Form return Types.Mal_Type; + function Read_Form return Mal.T; procedure Find_Next_Token; -- Search next token from index Last + 1. @@ -24,10 +25,9 @@ package body Reader is -- Read_Atom has been merged into the same case/switch -- statement, for clarity and efficiency. - function Read_List (Ending : in Character) return Types.Mal_Type_Array - with Inline; - function Read_Quote (Symbol : in Strings.Ptr) return Types.Mal_Type + function Read_List (Ending : in Character) return Mal.T_Array with Inline; + function Read_Quote (Symbol : in Symbols.Ptr) return Mal.T with Inline; ---------------------------------------------------------------------- procedure Find_Next_Token @@ -61,7 +61,7 @@ package body Reader is Last := First + 1; loop if Source'Last < Last then - raise Reader_Error with "expected '""'"; + raise Reader_Error with "unbalanced '""'"; end if; exit when Source (Last) = '"'; if Source (Last) = '\' then @@ -96,126 +96,108 @@ package body Reader is end loop; end Find_Next_Token; - function Read_Form return Types.Mal_Type - is - use Types; + function Read_Form return Mal.T is + use Ada.Strings.Unbounded; begin case Source (First) is - when '(' => - return (Kind_List, Atoms.No_Element, - Lists.Alloc (Read_List (')'))); + return Lists.List (Read_List (')')); when '[' => - return (Kind_Vector, Atoms.No_Element, - Lists.Alloc (Read_List (']'))); + return Lists.Vector (Read_List (']')); when '{' => - return (Kind_Map, Atoms.No_Element, - Maps.Hash_Map (Read_List ('}'))); - + return Maps.Hash_Map (Read_List ('}')); when '"' => declare - Buffer : String (First .. Last); - B_Last : Natural := Buffer'First - 1; + Buffer : Unbounded_String; I : Positive := First + 1; begin - while I <= Last - 1 loop - if Source (I) /= '\' or else I = Last - 1 then - B_Last := B_Last + 1; - Buffer (B_Last) := Source (I); + while I < Last loop + if Source (I) /= '\' or else I + 1 = Last then + Append (Buffer, Source (I)); else case Source (I + 1) is when '\' | '"' => - B_Last := B_Last + 1; - Buffer (B_Last) := Source (I + 1); I := I + 1; + Append (Buffer, Source (I)); when 'n' => - B_Last := B_Last + 1; - Buffer (B_Last) := Ada.Characters.Latin_1.LF; I := I + 1; + Append (Buffer, Ada.Characters.Latin_1.LF); when others => - B_Last := B_Last + 1; - Buffer (B_Last) := Source (I); + Append (Buffer, Source (I)); end case; end if; I := I + 1; end loop; - return (Kind_String, Atoms.No_Element, - Strings.Alloc (Buffer (Buffer'First .. B_Last))); + return (Kind_String, Buffer); end; when ':' => - return (Kind_Keyword, Atoms.No_Element, - Strings.Alloc (Source (First + 1 .. Last))); - + return (Kind_Keyword, + To_Unbounded_String (Source (First + 1 .. Last))); when '-' => if First < Last and then (for all C of Source (First + 1 .. Last) => C in '0' .. '9') then - return (Kind_Number, Atoms.No_Element, - Integer'Value (Source (First .. Last))); + return (Kind_Number, Integer'Value (Source (First .. Last))); else - return (Kind_Symbol, Atoms.No_Element, - Strings.Alloc (Source (First .. Last))); + return (Kind_Symbol, + Symbols.Constructor (Source (First .. Last))); end if; when '0' .. '9' => - return (Kind_Number, Atoms.No_Element, - Integer'Value (Source (First .. Last))); - + return (Kind_Number, Integer'Value (Source (First .. Last))); when ''' => - return Read_Quote (Names.Quote); + return Read_Quote (Symbols.Names.Quote); when '`' => - return Read_Quote (Names.Quasiquote); + return Read_Quote (Symbols.Names.Quasiquote); when '@' => - return Read_Quote (Names.Deref); + return Read_Quote (Symbols.Names.Deref); when '~' => if First = Last then - return Read_Quote (Names.Unquote); + return Read_Quote (Symbols.Names.Unquote); else - return Read_Quote (Names.Splice_Unquote); + return Read_Quote (Symbols.Names.Splice_Unquote); end if; when '^' => - return Result : constant Mal_Type - := (Kind_List, Atoms.No_Element, Lists.Alloc (3)) - do - Result.L.Replace_Element (1, Mal_Type' - (Kind_Symbol, Atoms.No_Element, Names.With_Meta)); + declare + Args : Mal.T_Array (1 .. 3); + begin + Args (1) := (Kind_Symbol, Symbols.Names.With_Meta); Find_Next_Token; if Source'Last < First then raise Reader_Error with "Unfinished 'with-meta'"; end if; - Result.L.Replace_Element (3, Read_Form); + Args (3) := Read_Form; Find_Next_Token; if Source'Last < First then raise Reader_Error with "Unfinished 'with-meta'"; end if; - Result.L.Replace_Element (2, Read_Form); - end return; - + Args (2) := Read_Form; + return Lists.List (Args); + end; when others => - if Source (First .. Last) = "nil" then - return (Kind_Nil, Atoms.No_Element); + if Source (First .. Last) = "false" then + return (Kind_Boolean, False); + elsif Source (First .. Last) = "nil" then + return Mal.Nil; elsif Source (First .. Last) = "true" then - return (Kind_Boolean, Atoms.No_Element, True); - elsif Source (First .. Last) = "false" then - return (Kind_Boolean, Atoms.No_Element, False); + return (Kind_Boolean, True); else - return (Kind_Symbol, Atoms.No_Element, - Strings.Alloc (Source (First .. Last))); + return (Kind_Symbol, + Symbols.Constructor (Source (First .. Last))); end if; end case; end Read_Form; - function Read_List (Ending : in Character) return Types.Mal_Type_Array - is + function Read_List (Ending : in Character) return Mal.T_Array is -- Using big arrays on the stack is faster than doing -- repeated dynamic reallocations. - Buffer : Types.Mal_Type_Array (First + 1 .. Source'Last); + Buffer : Mal.T_Array (First + 1 .. Source'Last); B_Last : Natural := Buffer'First - 1; begin loop Find_Next_Token; if Source'Last < First then - raise Reader_Error with "expected '" & Ending & "'"; + raise Reader_Error with "unbalanced '" & Ending & "'"; end if; exit when Source (First) = Ending; B_Last := B_Last + 1; @@ -224,19 +206,13 @@ package body Reader is return Buffer (Buffer'First .. B_Last); end Read_List; - function Read_Quote (Symbol : in Strings.Ptr) return Types.Mal_Type is - use Types; - Result : constant Mal_Type - := (Kind_List, Atoms.No_Element, Lists.Alloc (2)); + function Read_Quote (Symbol : in Symbols.Ptr) return Mal.T is begin - Result.L.Replace_Element (1, - Mal_Type'(Kind_Symbol, Atoms.No_Element, Symbol)); Find_Next_Token; if Source'Last < First then - raise Reader_Error with "Unfinished '" & Symbol.Deref & "'"; + raise Reader_Error with "Unfinished '" & Symbol.To_String & "'"; end if; - Result.L.Replace_Element (2, Read_Form); - return Result; + return Lists.List (Mal.T_Array'((Kind_Symbol, Symbol), Read_Form)); end Read_Quote; ---------------------------------------------------------------------- @@ -244,7 +220,7 @@ package body Reader is begin Find_Next_Token; if Source'Last < First then - raise Empty_Source; + raise Empty_Source with "attempting to read an empty line"; end if; return Read_Form; end Read_Str; diff --git a/ada2/reader.ads b/ada2/reader.ads index d5e85b36bd..0d9d185b91 100644 --- a/ada2/reader.ads +++ b/ada2/reader.ads @@ -1,10 +1,8 @@ -with Types; +with Types.Mal; -package Reader is +package Reader with Elaborate_Body is - pragma Elaborate_Body; - - function Read_Str (Source : in String) return Types.Mal_Type; + function Read_Str (Source : in String) return Types.Mal.T; Empty_Source : exception; Reader_Error : exception; diff --git a/ada2/step0_repl.adb b/ada2/step0_repl.adb index bf8d73ab17..f318731343 100644 --- a/ada2/step0_repl.adb +++ b/ada2/step0_repl.adb @@ -1,57 +1,48 @@ -with Ada.Exceptions; with Ada.Text_IO; -with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; +with Interfaces.C.Strings; procedure Step0_Repl is - function Read (Source : in String) return String - is (Source); + subtype Mal_Type is String; - function Eval (Ast : in String) return String - is (Ast); + function Read (Source : in String) return Mal_Type + is (Source); - function Print (Ast : in String) return String - is (Ast); + function Eval (Ast : in Mal_Type) return Mal_Type + is (Ast); + + function Print (Ast : in Mal_Type) return String + is (Ast); function Rep (Source : in String) return String - is (Print (Eval (Read (Source)))); + is (Print (Eval (Read (Source)))) with Inline; procedure Interactive_Loop; ---------------------------------------------------------------------- - procedure Interactive_Loop - is - - function Readline (Prompt : in Interfaces.C.char_array) - return Interfaces.C.Strings.chars_ptr + procedure Interactive_Loop is + use Interfaces.C, Interfaces.C.Strings; + function Readline (Prompt : in char_array) return chars_ptr with Import, Convention => C, External_Name => "readline"; - - procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + procedure Add_History (Line : in chars_ptr) with Import, Convention => C, External_Name => "add_history"; - - procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + procedure Free (Line : in chars_ptr) with Import, Convention => C, External_Name => "free"; - - Prompt : constant Interfaces.C.char_array - := Interfaces.C.To_C ("user> "); - C_Line : Interfaces.C.Strings.chars_ptr; + Prompt : constant char_array := To_C ("user> "); + C_Line : chars_ptr; begin loop C_Line := Readline (Prompt); - exit when C_Line = Interfaces.C.Strings.Null_Ptr; + exit when C_Line = Null_Ptr; declare - Line : constant String := Interfaces.C.Strings.Value (C_Line); + Line : constant String := Value (C_Line); begin if Line /= "" then Add_History (C_Line); end if; Free (C_Line); Ada.Text_IO.Put_Line (Rep (Line)); - exception - when E : others => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- but go on proceeding. end; end loop; Ada.Text_IO.New_Line; diff --git a/ada2/step1_read_print.adb b/ada2/step1_read_print.adb index b68f1197c2..06f49a3005 100644 --- a/ada2/step1_read_print.adb +++ b/ada2/step1_read_print.adb @@ -1,56 +1,50 @@ with Ada.Exceptions; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; +with Interfaces.C.Strings; + with Printer; with Reader; -with Types; +with Types.Mal; procedure Step1_Read_Print is - function Read (Source : in String) return Types.Mal_Type + package ASU renames Ada.Strings.Unbounded; + use Types; + + function Read (Source : in String) return Mal.T renames Reader.Read_Str; - function Eval (Ast : in Types.Mal_Type) return Types.Mal_Type - is (Ast); + function Eval (Ast : in Mal.T) return Mal.T + is (Ast); - function Print (Ast : in Types.Mal_Type; - Print_Readably : in Boolean := True) - return Ada.Strings.Unbounded.Unbounded_String + function Print (Ast : in Mal.T; + Readably : in Boolean := True) return ASU.Unbounded_String renames Printer.Pr_Str; - function Rep (Source : in String) - return Ada.Strings.Unbounded.Unbounded_String - is (Print (Eval (Read (Source)))) - with Inline; + function Rep (Source : in String) return ASU.Unbounded_String + is (Print (Eval (Read (Source)))) with Inline; - procedure Interactive_Loop - with Inline; + procedure Interactive_Loop; ---------------------------------------------------------------------- - procedure Interactive_Loop - is - - function Readline (Prompt : in Interfaces.C.char_array) - return Interfaces.C.Strings.chars_ptr + procedure Interactive_Loop is + use Interfaces.C, Interfaces.C.Strings; + function Readline (Prompt : in char_array) return chars_ptr with Import, Convention => C, External_Name => "readline"; - - procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + procedure Add_History (Line : in chars_ptr) with Import, Convention => C, External_Name => "add_history"; - - procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + procedure Free (Line : in chars_ptr) with Import, Convention => C, External_Name => "free"; - - Prompt : constant Interfaces.C.char_array - := Interfaces.C.To_C ("user> "); - C_Line : Interfaces.C.Strings.chars_ptr; + Prompt : constant char_array := To_C ("user> "); + C_Line : chars_ptr; begin loop C_Line := Readline (Prompt); - exit when C_Line = Interfaces.C.Strings.Null_Ptr; + exit when C_Line = Null_Ptr; declare - Line : constant String := Interfaces.C.Strings.Value (C_Line); + Line : constant String := Value (C_Line); begin if Line /= "" then Add_History (C_Line); @@ -60,9 +54,9 @@ procedure Step1_Read_Print is exception when Reader.Empty_Source => null; - when E : others => + when E : Reader.Reader_Error => Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- but go on proceeding. + -- Other exceptions are unexpected. end; end loop; Ada.Text_IO.New_Line; diff --git a/ada2/step2_eval.adb b/ada2/step2_eval.adb index 3c72b8c7d9..e37de05889 100644 --- a/ada2/step2_eval.adb +++ b/ada2/step2_eval.adb @@ -3,142 +3,122 @@ with Ada.Exceptions; with Ada.Strings.Hash; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; -with Atoms; -with Lists; +with Interfaces.C.Strings; + with Printer; with Reader; -with Types; +with Types.Builtins; +with Types.Lists; +with Types.Mal; +with Types.Maps; procedure Step2_Eval is + package ASU renames Ada.Strings.Unbounded; + use Types; + package Environments is new Ada.Containers.Indefinite_Hashed_Maps (Key_Type => String, - Element_Type => Types.Native_Function_Access, + Element_Type => Builtins.Ptr, Hash => Ada.Strings.Hash, Equivalent_Keys => "=", - "=" => Types."="); + "=" => Builtins."="); + Unknown_Symbol : exception; - function Read (Source : in String) return Types.Mal_Type + function Read (Source : in String) return Mal.T renames Reader.Read_Str; - function Eval (Ast : in Types.Mal_Type; - Env : in out Environments.Map) return Types.Mal_Type; - Unable_To_Call : exception; - Unknown_Symbol : exception; + function Eval (Ast : in Mal.T; + Env : in Environments.Map) return Mal.T; - function Print (Ast : in Types.Mal_Type; - Print_Readably : in Boolean := True) - return Ada.Strings.Unbounded.Unbounded_String + function Print (Ast : in Mal.T; + Readably : in Boolean := True) return ASU.Unbounded_String renames Printer.Pr_Str; - function Rep (Source : in String; - Env : in out Environments.Map) - return Ada.Strings.Unbounded.Unbounded_String - is (Print (Eval (Read (Source), Env))) - with Inline; + function Rep (Source : in String; + Env : in Environments.Map) return ASU.Unbounded_String + is (Print (Eval (Read (Source), Env))) with Inline; - procedure Interactive_Loop (Repl : in out Environments.Map) - with Inline; + procedure Interactive_Loop (Repl : in Environments.Map); generic with function Ada_Operator (Left, Right : in Integer) return Integer; - function Generic_Mal_Operator (Args : in Types.Mal_Type_Array) - return Types.Mal_Type; + function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T; + + function Eval_Elements is new Lists.Generic_Eval (Environments.Map, Eval); + function Eval_Elements is new Maps.Generic_Eval (Environments.Map, Eval); ---------------------------------------------------------------------- - function Eval (Ast : in Types.Mal_Type; - Env : in out Environments.Map) return Types.Mal_Type - is - use Types; + function Eval (Ast : in Mal.T; + Env : in Environments.Map) return Mal.T is + First : Mal.T; begin + -- Ada.Text_IO.New_Line; + -- Ada.Text_IO.Put ("EVAL: "); + -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => - return Ast; - when Kind_Symbol => declare - S : constant String := Ast.S.Deref; + S : constant String := Ast.Symbol.To_String; C : constant Environments.Cursor := Env.Find (S); begin if Environments.Has_Element (C) then - return (Kind_Native, Atoms.No_Element, - Environments.Element (C)); + return (Kind_Builtin, Environments.Element (C)); else + -- The predefined message does not pass tests. raise Unknown_Symbol with "'" & S & "' not found"; end if; end; - when Kind_Map => - declare - function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); - begin - return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); - end; - + return Eval_Elements (Ast.Map, Env); when Kind_Vector => - return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, - Lists.Alloc (Ast.L.Length)) - do - for I in 1 .. Ast.L.Length loop - R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); - end loop; - end return; - + return (Kind_Vector, Eval_Elements (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; end if; - - -- Apply phase - declare - First : constant Mal_Type := Eval (Ast.L.Element (1), Env); - Args : Mal_Type_Array (2 .. Ast.L.Length); - begin - for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); - end loop; - case First.Kind is - when Kind_Native => - return First.Native.all (Args); - when others => - raise Unable_To_Call - with Ada.Strings.Unbounded.To_String (Print (First)); - end case; - end; + First := Eval (Ast.L.Element (1), Env); + -- Apply phase. + case First.Kind is + when Kind_Builtin => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + return First.Builtin.all (Args); + end; + when others => + raise Argument_Error + with "cannot execute " & ASU.To_String (Print (First)); + end case; + when others => + return Ast; end case; end Eval; - function Generic_Mal_Operator (Args : in Types.Mal_Type_Array) - return Types.Mal_Type - is (Types.Kind_Number, Atoms.No_Element, - Ada_Operator (Args (Args'First).Integer_Value, - Args (Args'First + 1).Integer_Value)); - - procedure Interactive_Loop (Repl : in out Environments.Map) - is + function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T + is (Kind_Number, Ada_Operator (Args (Args'First).Ada_Number, + Args (Args'Last).Ada_Number)); - function Readline (Prompt : in Interfaces.C.char_array) - return Interfaces.C.Strings.chars_ptr + procedure Interactive_Loop (Repl : in Environments.Map) is + use Interfaces.C, Interfaces.C.Strings; + function Readline (Prompt : in char_array) return chars_ptr with Import, Convention => C, External_Name => "readline"; - - procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + procedure Add_History (Line : in chars_ptr) with Import, Convention => C, External_Name => "add_history"; - - procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + procedure Free (Line : in chars_ptr) with Import, Convention => C, External_Name => "free"; - - Prompt : constant Interfaces.C.char_array - := Interfaces.C.To_C ("user> "); - C_Line : Interfaces.C.Strings.chars_ptr; + Prompt : constant char_array := To_C ("user> "); + C_Line : chars_ptr; begin loop C_Line := Readline (Prompt); - exit when C_Line = Interfaces.C.Strings.Null_Ptr; + exit when C_Line = Null_Ptr; declare - Line : constant String := Interfaces.C.Strings.Value (C_Line); + Line : constant String := Value (C_Line); begin if Line /= "" then Add_History (C_Line); @@ -148,9 +128,10 @@ procedure Step2_Eval is exception when Reader.Empty_Source => null; - when E : others => + when E : Argument_Error | Reader.Reader_Error + | Unknown_Symbol => Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- but go on proceeding. + -- Other exceptions are unexpected. end; end loop; Ada.Text_IO.New_Line; @@ -165,11 +146,10 @@ procedure Step2_Eval is Repl : Environments.Map; begin - Repl.Include ("+", Addition 'Unrestricted_Access); - Repl.Include ("-", Subtraction'Unrestricted_Access); - Repl.Include ("*", Product 'Unrestricted_Access); - Repl.Include ("/", Division 'Unrestricted_Access); + Repl.Insert ("+", Addition 'Unrestricted_Access); + Repl.Insert ("-", Subtraction'Unrestricted_Access); + Repl.Insert ("*", Product 'Unrestricted_Access); + Repl.Insert ("/", Division 'Unrestricted_Access); Interactive_Loop (Repl); - pragma Unreferenced (Repl); end Step2_Eval; diff --git a/ada2/step3_env.adb b/ada2/step3_env.adb index b19d660b6a..224248e0ac 100644 --- a/ada2/step3_env.adb +++ b/ada2/step3_env.adb @@ -1,163 +1,149 @@ with Ada.Exceptions; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; -with Atoms; +with Interfaces.C.Strings; + with Environments; -with Lists; -with Names; with Printer; with Reader; -with Strings; use type Strings.Ptr; -with Types; +with Types.Lists; +with Types.Mal; +with Types.Maps; +with Types.Symbols.Names; procedure Step3_Env is - function Read (Source : in String) return Types.Mal_Type + package ASU renames Ada.Strings.Unbounded; + use Types; + use type Symbols.Ptr; + + function Read (Source : in String) return Mal.T renames Reader.Read_Str; - function Eval (Ast : in Types.Mal_Type; - Env : in Environments.Ptr) return Types.Mal_Type; - Unable_To_Call : exception; + function Eval (Ast : in Mal.T; + Env : in Environments.Ptr) return Mal.T; - function Print (Ast : in Types.Mal_Type; - Print_Readably : in Boolean := True) - return Ada.Strings.Unbounded.Unbounded_String + function Print (Ast : in Mal.T; + Readably : in Boolean := True) return ASU.Unbounded_String renames Printer.Pr_Str; function Rep (Source : in String; - Env : in Environments.Ptr) - return Ada.Strings.Unbounded.Unbounded_String - is (Print (Eval (Read (Source), Env))) - with Inline; + Env : in Environments.Ptr) return ASU.Unbounded_String + is (Print (Eval (Read (Source), Env))) with Inline; - procedure Interactive_Loop (Repl : in Environments.Ptr) - with Inline; + procedure Interactive_Loop (Repl : in Environments.Ptr); generic with function Ada_Operator (Left, Right : in Integer) return Integer; - function Generic_Mal_Operator (Args : in Types.Mal_Type_Array) - return Types.Mal_Type; + function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T; + + function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); ---------------------------------------------------------------------- - function Eval (Ast : in Types.Mal_Type; - Env : in Environments.Ptr) return Types.Mal_Type - is - use Types; - First : Mal_Type; + function Eval (Ast : in Mal.T; + Env : in Environments.Ptr) return Mal.T is + First : Mal.T; begin + -- Ada.Text_IO.New_Line; + -- Ada.Text_IO.Put ("EVAL: "); + -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Environments.Dump_Stack; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => - return Ast; - when Kind_Symbol => - return Env.Get (Ast.S); - + return Env.Get (Ast.Symbol); when Kind_Map => - declare - function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); - begin - return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); - end; - + return Eval_Elements (Ast.Map, Env); when Kind_Vector => - return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, - Lists.Alloc (Ast.L.Length)) - do - for I in 1 .. Ast.L.Length loop - R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); - end loop; - end return; - + return (Kind_Vector, Eval_Elements (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); - -- Special forms - if First.Kind = Kind_Symbol then - - if First.S = Names.Def then - pragma Assert (Ast.L.Length = 3); - pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); - return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).S, R); - end return; - - elsif First.S = Names.Let then - declare - pragma Assert (Ast.L.Length = 3); - pragma Assert - (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; - pragma Assert (Bindings.Length mod 2 = 0); - New_Env : constant Environments.Ptr - := Environments.Alloc (Outer => Env); - begin - New_Env.Increase_Capacity (Bindings.Length / 2); - for I in 1 .. Bindings.Length / 2 loop - pragma Assert - (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); - New_Env.Set (Bindings.Element (2 * I - 1).S, - Eval (Bindings.Element (2 * I), New_Env)); - end loop; - return Eval (Ast.L.Element (3), New_Env); - end; + if First.Kind /= Kind_Symbol then + -- Evaluate First, in the less frequent case where it is + -- not a symbol. + First := Eval (First, Env); + elsif First.Symbol = Symbols.Names.Def then + if Ast.L.Length /= 3 then + raise Argument_Error with "def!: expects 2 arguments"; + elsif Ast.L.Element (2).Kind /= Kind_Symbol then + raise Argument_Error with "def!: arg 1 must be a symbol"; + end if; + return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).Symbol, R); + end return; + elsif First.Symbol = Symbols.Names.Let then + if Ast.L.Length /= 3 then + raise Argument_Error with "let*: expects 3 arguments"; + elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "let*: expects a list or vector"; end if; + declare + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + New_Env : constant Environments.Ptr := Env.Sub; + begin + if Bindings.Length mod 2 /= 0 then + raise Argument_Error with "let*: odd number of bindings"; + end if; + for I in 1 .. Bindings.Length / 2 loop + if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then + raise Argument_Error with "let*: keys must be symbols"; + end if; + New_Env.Set (Bindings.Element (2 * I - 1).Symbol, + Eval (Bindings.Element (2 * I), New_Env)); + end loop; + return Eval (Ast.L.Element (3), New_Env); + end; + else + -- Equivalent to First := Eval (First, Env), except that + -- we already know enough to spare a recursive call in + -- this frequent case. + First := Env.Get (First.Symbol); end if; - - -- No special form has been found, attempt to apply the - -- first element to the rest of the list. - declare - Args : Mal_Type_Array (2 .. Ast.L.Length); - begin - First := Eval (First, Env); - for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); - end loop; - case First.Kind is - when Kind_Native => - return First.Native.all (Args); - when others => - raise Unable_To_Call - with Ada.Strings.Unbounded.To_String (Print (First)); - end case; - end; + -- Apply phase. + case First.Kind is + when Kind_Builtin => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + return First.Builtin.all (Args); + end; + when others => + raise Argument_Error + with "cannot execute " & ASU.To_String (Print (First)); + end case; + when others => + return Ast; end case; end Eval; - function Generic_Mal_Operator (Args : in Types.Mal_Type_Array) - return Types.Mal_Type - is (Types.Kind_Number, Atoms.No_Element, - Ada_Operator (Args (Args'First).Integer_Value, - Args (Args'First + 1).Integer_Value)); - - procedure Interactive_Loop (Repl : in Environments.Ptr) - is + function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T + is (Kind_Number, Ada_Operator (Args (Args'First).Ada_Number, + Args (Args'Last).Ada_Number)); - function Readline (Prompt : in Interfaces.C.char_array) - return Interfaces.C.Strings.chars_ptr + procedure Interactive_Loop (Repl : in Environments.Ptr) is + use Interfaces.C, Interfaces.C.Strings; + function Readline (Prompt : in char_array) return chars_ptr with Import, Convention => C, External_Name => "readline"; - - procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + procedure Add_History (Line : in chars_ptr) with Import, Convention => C, External_Name => "add_history"; - - procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + procedure Free (Line : in chars_ptr) with Import, Convention => C, External_Name => "free"; - - Prompt : constant Interfaces.C.char_array - := Interfaces.C.To_C ("user> "); - C_Line : Interfaces.C.Strings.chars_ptr; + Prompt : constant char_array := To_C ("user> "); + C_Line : chars_ptr; begin loop C_Line := Readline (Prompt); - exit when C_Line = Interfaces.C.Strings.Null_Ptr; + exit when C_Line = Null_Ptr; declare - Line : constant String := Interfaces.C.Strings.Value (C_Line); + Line : constant String := Value (C_Line); begin if Line /= "" then Add_History (C_Line); @@ -167,9 +153,10 @@ procedure Step3_Env is exception when Reader.Empty_Source => null; - when E : others => + when E : Argument_Error | Reader.Reader_Error + | Environments.Unknown_Key => Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- but go on proceeding. + -- Other exceptions are unexpected. end; end loop; Ada.Text_IO.New_Line; @@ -182,18 +169,14 @@ procedure Step3_Env is function Product is new Generic_Mal_Operator ("*"); function Division is new Generic_Mal_Operator ("/"); - use Types; - Repl : constant Environments.Ptr := Environments.Alloc; + function S (Source : in String) return Symbols.Ptr + renames Symbols.Constructor; + Repl : Environments.Ptr renames Environments.Repl; begin - Repl.Increase_Capacity (4); - Repl.Set (Names.Plus, Types.Mal_Type' - (Types.Kind_Native, Atoms.No_Element, Addition'Unrestricted_Access)); - Repl.Set (Names.Minus, Types.Mal_Type' - (Types.Kind_Native, Atoms.No_Element, Subtraction'Unrestricted_Access)); - Repl.Set (Names.Asterisk, Types.Mal_Type' - (Types.Kind_Native, Atoms.No_Element, Product'Unrestricted_Access)); - Repl.Set (Names.Slash, Types.Mal_Type' - (Types.Kind_Native, Atoms.No_Element, Division'Unrestricted_Access)); + Repl.Set (S ("+"), (Kind_Builtin, Addition 'Unrestricted_Access)); + Repl.Set (S ("-"), (Kind_Builtin, Subtraction'Unrestricted_Access)); + Repl.Set (S ("*"), (Kind_Builtin, Product 'Unrestricted_Access)); + Repl.Set (S ("/"), (Kind_Builtin, Division 'Unrestricted_Access)); Interactive_Loop (Repl); end Step3_Env; diff --git a/ada2/step4_if_fn_do.adb b/ada2/step4_if_fn_do.adb index c554a6ec12..b4ed93798c 100644 --- a/ada2/step4_if_fn_do.adb +++ b/ada2/step4_if_fn_do.adb @@ -1,208 +1,197 @@ with Ada.Exceptions; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; -with Atoms; +with Interfaces.C.Strings; + with Core; with Environments; -with Lists; -with Names; with Printer; with Reader; -with Strings; use type Strings.Ptr; -with Types; +with Types.Functions; +with Types.Lists; +with Types.Mal; +with Types.Maps; +with Types.Symbols.Names; procedure Step4_If_Fn_Do is - function Read (Source : in String) return Types.Mal_Type + package ASU renames Ada.Strings.Unbounded; + use Types; + use type Symbols.Ptr; + + function Read (Source : in String) return Mal.T renames Reader.Read_Str; - function Eval (Ast : in Types.Mal_Type; - Env : in Environments.Ptr) return Types.Mal_Type; - Unable_To_Call : exception; + function Eval (Ast : in Mal.T; + Env : in Environments.Ptr) return Mal.T; - function Print (Ast : in Types.Mal_Type; - Print_Readably : in Boolean := True) - return Ada.Strings.Unbounded.Unbounded_String + function Print (Ast : in Mal.T; + Readably : in Boolean := True) return ASU.Unbounded_String renames Printer.Pr_Str; function Rep (Source : in String; - Env : in Environments.Ptr) - return Ada.Strings.Unbounded.Unbounded_String - is (Print (Eval (Read (Source), Env))) - with Inline; + Env : in Environments.Ptr) return ASU.Unbounded_String + is (Print (Eval (Read (Source), Env))) with Inline; + + procedure Interactive_Loop (Repl : in Environments.Ptr); - procedure Interactive_Loop (Repl : in Environments.Ptr) - with Inline; + function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); -- Convenient when the result of eval is of no interest. - procedure Discard (Ast : in Types.Mal_Type) is null; + procedure Discard (Ast : in Mal.T) is null; ---------------------------------------------------------------------- - function Eval (Ast : in Types.Mal_Type; - Env : in Environments.Ptr) return Types.Mal_Type - is - use Types; - First : Mal_Type; + function Eval (Ast : in Mal.T; + Env : in Environments.Ptr) return Mal.T is + First : Mal.T; begin + -- Ada.Text_IO.New_Line; + -- Ada.Text_IO.Put ("EVAL: "); + -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Environments.Dump_Stack; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => - return Ast; - when Kind_Symbol => - return Env.Get (Ast.S); - + return Env.Get (Ast.Symbol); when Kind_Map => - declare - function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); - begin - return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); - end; - + return Eval_Elements (Ast.Map, Env); when Kind_Vector => - return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, - Lists.Alloc (Ast.L.Length)) - do - for I in 1 .. Ast.L.Length loop - R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); - end loop; - end return; - + return (Kind_Vector, Eval_Elements (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); - -- Special forms - if First.Kind = Kind_Symbol then - - if First.S = Names.Def then - pragma Assert (Ast.L.Length = 3); - pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); - return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).S, R); - end return; - - elsif First.S = Names.Mal_Do then - for I in 2 .. Ast.L.Length - 1 loop - Discard (Eval (Ast.L.Element (I), Env)); - end loop; - return Eval (Ast.L.Element (Ast.L.Length), Env); - - elsif First.S = Names.Fn then - pragma Assert (Ast.L.Length = 3); - pragma Assert - (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); - pragma Assert - (for all I in 1 .. Ast.L.Element (2).L.Length => - Ast.L.Element (2).L.Element (I).Kind = Kind_Symbol); - pragma Assert - (Ast.L.Element (2).L.Length < 1 - or else Names.Ampersand /= - Ast.L.Element (2).L.Element (Ast.L.Element (2).L.Length).S); - pragma Assert - (for all I in 1 .. Ast.L.Element (2).L.Length - 2 => - Ast.L.Element (2).L.Element (I).S /= Names.Ampersand); - return (Kind => Kind_Function, - Meta => Atoms.No_Element, - Formals => Ast.L.Element (2).L, - Expression => Atoms.Alloc (Ast.L.Element (3)), - Environment => Env); - - elsif First.S = Names.Mal_If then - declare - pragma Assert (Ast.L.Length in 3 .. 4); - Test : constant Mal_Type := Eval (Ast.L.Element (2), Env); - begin - if (case Test.Kind is - when Kind_Nil => False, - when Kind_Boolean => Test.Boolean_Value, - when others => True) - then - return Eval (Ast.L.Element (3), Env); - elsif Ast.L.Length = 3 then - return (Kind_Nil, Atoms.No_Element); - else - return Eval (Ast.L.Element (4), Env); - end if; - end; - - elsif First.S = Names.Let then - declare - pragma Assert (Ast.L.Length = 3); - pragma Assert - (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; - pragma Assert (Bindings.Length mod 2 = 0); - New_Env : constant Environments.Ptr - := Environments.Alloc (Outer => Env); - begin - New_Env.Increase_Capacity (Bindings.Length / 2); - for I in 1 .. Bindings.Length / 2 loop - pragma Assert - (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); - New_Env.Set (Bindings.Element (2 * I - 1).S, - Eval (Bindings.Element (2 * I), New_Env)); - end loop; - return Eval (Ast.L.Element (3), New_Env); - end; - end if; - end if; - - -- No special form has been found, attempt to apply the - -- first element to the rest of the list. - declare - Args : Mal_Type_Array (2 .. Ast.L.Length); - begin + if First.Kind /= Kind_Symbol then + -- Evaluate First, in the less frequent case where it is + -- not a symbol. First := Eval (First, Env); - for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + elsif First.Symbol = Symbols.Names.Def then + if Ast.L.Length /= 3 then + raise Argument_Error with "def!: expects 2 arguments"; + elsif Ast.L.Element (2).Kind /= Kind_Symbol then + raise Argument_Error with "def!: arg 1 must be a symbol"; + end if; + return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).Symbol, R); + end return; + elsif First.Symbol = Symbols.Names.Mal_Do then + if Ast.L.Length = 1 then + raise Argument_Error with "do: expects at least 1 argument"; + end if; + for I in 2 .. Ast.L.Length - 1 loop + Discard (Eval (Ast.L.Element (I), Env)); end loop; - case First.Kind is - when Kind_Native => - return First.Native.all (Args); - when Kind_Function => - declare - New_Env : constant Environments.Ptr - := Environments.Alloc (Outer => First.Environment); - begin - New_Env.Set_Binds (First.Formals, Args); - return Eval (First.Expression.Deref, New_Env); - end; - when others => - raise Unable_To_Call - with Ada.Strings.Unbounded.To_String (Print (First)); - end case; - end; + return Eval (Ast.L.Element (Ast.L.Length), Env); + elsif First.Symbol = Symbols.Names.Fn then + if Ast.L.Length /= 3 then + raise Argument_Error with "fn*: expects 3 arguments"; + elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "fn*: arg 1 must be a list or vector"; + elsif (for some F in 1 .. Ast.L.Element (2).L.Length => + Ast.L.Element (2).L.Element (F).Kind /= Kind_Symbol) + then + raise Argument_Error with "fn*: arg 2 must contain symbols"; + end if; + return Functions.New_Function (Ast.L.Element (2).L, + Ast.L.Element (3), Env.New_Closure); + elsif First.Symbol = Symbols.Names.Mal_If then + if Ast.L.Length not in 3 .. 4 then + raise Argument_Error with "if: expects 2 or 3 arguments"; + end if; + declare + Test : constant Mal.T := Eval (Ast.L.Element (2), Env); + begin + if (case Test.Kind is + when Kind_Nil => False, + when Kind_Boolean => Test.Ada_Boolean, + when others => True) + then + return Eval (Ast.L.Element (3), Env); + elsif Ast.L.Length = 3 then + return Mal.Nil; + else + return Eval (Ast.L.Element (4), Env); + end if; + end; + elsif First.Symbol = Symbols.Names.Let then + if Ast.L.Length /= 3 then + raise Argument_Error with "let*: expects 3 arguments"; + elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "let*: expects a list or vector"; + end if; + declare + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + New_Env : constant Environments.Ptr := Env.Sub; + begin + if Bindings.Length mod 2 /= 0 then + raise Argument_Error with "let*: odd number of bindings"; + end if; + for I in 1 .. Bindings.Length / 2 loop + if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then + raise Argument_Error with "let*: keys must be symbols"; + end if; + New_Env.Set (Bindings.Element (2 * I - 1).Symbol, + Eval (Bindings.Element (2 * I), New_Env)); + end loop; + return Eval (Ast.L.Element (3), New_Env); + end; + else + -- Equivalent to First := Eval (First, Env), except that + -- we already know enough to spare a recursive call in + -- this frequent case. + First := Env.Get (First.Symbol); + end if; + -- Apply phase. + case First.Kind is + when Kind_Builtin => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + return First.Builtin.all (Args); + end; + when Kind_Function => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + New_Env : constant Environments.Ptr + := First.Function_Value.Closure.Sub; + begin + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + First.Function_Value.Set_Binds (New_Env, Args); + return Eval (First.Function_Value.Expression, New_Env); + end; + when others => + raise Argument_Error + with "cannot execute " & ASU.To_String (Print (First)); + end case; + when others => + return Ast; end case; end Eval; - procedure Interactive_Loop (Repl : in Environments.Ptr) - is - - function Readline (Prompt : in Interfaces.C.char_array) - return Interfaces.C.Strings.chars_ptr + procedure Interactive_Loop (Repl : in Environments.Ptr) is + use Interfaces.C, Interfaces.C.Strings; + function Readline (Prompt : in char_array) return chars_ptr with Import, Convention => C, External_Name => "readline"; - - procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + procedure Add_History (Line : in chars_ptr) with Import, Convention => C, External_Name => "add_history"; - - procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + procedure Free (Line : in chars_ptr) with Import, Convention => C, External_Name => "free"; - - Prompt : constant Interfaces.C.char_array - := Interfaces.C.To_C ("user> "); - C_Line : Interfaces.C.Strings.chars_ptr; + Prompt : constant char_array := To_C ("user> "); + C_Line : chars_ptr; begin loop C_Line := Readline (Prompt); - exit when C_Line = Interfaces.C.Strings.Null_Ptr; + exit when C_Line = Null_Ptr; declare - Line : constant String := Interfaces.C.Strings.Value (C_Line); + Line : constant String := Value (C_Line); begin if Line /= "" then Add_History (C_Line); @@ -212,9 +201,10 @@ procedure Step4_If_Fn_Do is exception when Reader.Empty_Source => null; - when E : others => + when E : Argument_Error | Reader.Reader_Error + | Environments.Unknown_Key => Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- but go on proceeding. + -- Other exceptions are unexpected. end; end loop; Ada.Text_IO.New_Line; @@ -222,10 +212,12 @@ procedure Step4_If_Fn_Do is ---------------------------------------------------------------------- - Repl : constant Environments.Ptr := Environments.Alloc; + Startup : constant String := "(do" + & "(def! not (fn* (a) (if a false true)))" + & ")"; + Repl : Environments.Ptr renames Environments.Repl; begin - Core.Add_Built_In_Functions (Repl, Eval'Unrestricted_Access); - Discard (Eval (Read ("(def! not (fn* (a) (if a false true)))"), Repl)); - + Core.Eval_Ref := Eval'Unrestricted_Access; + Discard (Eval (Read (Startup), Repl)); Interactive_Loop (Repl); end Step4_If_Fn_Do; diff --git a/ada2/step5_tco.adb b/ada2/step5_tco.adb index 3838b6b3ad..1a865079cf 100644 --- a/ada2/step5_tco.adb +++ b/ada2/step5_tco.adb @@ -1,211 +1,206 @@ with Ada.Exceptions; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; -with Atoms; +with Interfaces.C.Strings; + with Core; with Environments; -with Lists; -with Names; with Printer; with Reader; -with Strings; use type Strings.Ptr; -with Types; +with Types.Functions; +with Types.Lists; +with Types.Mal; +with Types.Maps; +with Types.Symbols.Names; procedure Step5_Tco is - function Read (Source : in String) return Types.Mal_Type + package ASU renames Ada.Strings.Unbounded; + use Types; + use type Symbols.Ptr; + + function Read (Source : in String) return Mal.T renames Reader.Read_Str; - function Eval (Rec_Ast : in Types.Mal_Type; - Rec_Env : in Environments.Ptr) return Types.Mal_Type; - Unable_To_Call : exception; + function Eval (Ast0 : in Mal.T; + Env0 : in Environments.Ptr) return Mal.T; - function Print (Ast : in Types.Mal_Type; - Print_Readably : in Boolean := True) - return Ada.Strings.Unbounded.Unbounded_String + function Print (Ast : in Mal.T; + Readably : in Boolean := True) return ASU.Unbounded_String renames Printer.Pr_Str; function Rep (Source : in String; - Env : in Environments.Ptr) - return Ada.Strings.Unbounded.Unbounded_String - is (Print (Eval (Read (Source), Env))) - with Inline; + Env : in Environments.Ptr) return ASU.Unbounded_String + is (Print (Eval (Read (Source), Env))) with Inline; + + procedure Interactive_Loop (Repl : in Environments.Ptr); - procedure Interactive_Loop (Repl : in Environments.Ptr) - with Inline; + function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); -- Convenient when the result of eval is of no interest. - procedure Discard (Ast : in Types.Mal_Type) is null; + procedure Discard (Ast : in Mal.T) is null; ---------------------------------------------------------------------- - function Eval (Rec_Ast : in Types.Mal_Type; - Rec_Env : in Environments.Ptr) return Types.Mal_Type - is - use Types; - Ast : Types.Mal_Type := Rec_Ast; - Env : Environments.Ptr := Rec_Env; - First : Mal_Type; + function Eval (Ast0 : in Mal.T; + Env0 : in Environments.Ptr) return Mal.T is + -- Use local variables, that can be rewritten when tail call + -- optimization goes to <>. + Ast : Mal.T := Ast0; + Env : Environments.Ptr := Env0.Copy_Pointer; + First : Mal.T; begin - <> + <> + -- Ada.Text_IO.New_Line; + -- Ada.Text_IO.Put ("EVAL: "); + -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Environments.Dump_Stack; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => - return Ast; - when Kind_Symbol => - return Env.Get (Ast.S); - + return Env.Get (Ast.Symbol); when Kind_Map => - declare - function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); - begin - return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); - end; - + return Eval_Elements (Ast.Map, Env); when Kind_Vector => - return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, - Lists.Alloc (Ast.L.Length)) - do - for I in 1 .. Ast.L.Length loop - R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); - end loop; - end return; - + return (Kind_Vector, Eval_Elements (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); - -- Special forms - if First.Kind = Kind_Symbol then - - if First.S = Names.Def then - pragma Assert (Ast.L.Length = 3); - pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); - return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).S, R); - end return; - - elsif First.S = Names.Mal_Do then - for I in 2 .. Ast.L.Length - 1 loop - Discard (Eval (Ast.L.Element (I), Env)); - end loop; - Ast := Ast.L.Element (Ast.L.Length); - goto Restart; - - elsif First.S = Names.Fn then - pragma Assert (Ast.L.Length = 3); - pragma Assert - (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); - pragma Assert - (for all I in 1 .. Ast.L.Element (2).L.Length => - Ast.L.Element (2).L.Element (I).Kind = Kind_Symbol); - pragma Assert - (Ast.L.Element (2).L.Length < 1 - or else Names.Ampersand /= - Ast.L.Element (2).L.Element (Ast.L.Element (2).L.Length).S); - pragma Assert - (for all I in 1 .. Ast.L.Element (2).L.Length - 2 => - Ast.L.Element (2).L.Element (I).S /= Names.Ampersand); - return (Kind => Kind_Function, - Meta => Atoms.No_Element, - Formals => Ast.L.Element (2).L, - Expression => Atoms.Alloc (Ast.L.Element (3)), - Environment => Env); - - elsif First.S = Names.Mal_If then - declare - pragma Assert (Ast.L.Length in 3 .. 4); - Test : constant Mal_Type := Eval (Ast.L.Element (2), Env); - begin - if (case Test.Kind is - when Kind_Nil => False, - when Kind_Boolean => Test.Boolean_Value, - when others => True) - then - Ast := Ast.L.Element (3); - goto Restart; - elsif Ast.L.Length = 3 then - return (Kind_Nil, Atoms.No_Element); - else - Ast := Ast.L.Element (4); - goto Restart; - end if; - end; - - elsif First.S = Names.Let then - declare - pragma Assert (Ast.L.Length = 3); - pragma Assert - (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; - pragma Assert (Bindings.Length mod 2 = 0); - begin - Env.Replace_With_Subenv; - Env.Increase_Capacity (Bindings.Length / 2); - for I in 1 .. Bindings.Length / 2 loop - pragma Assert - (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); - Env.Set (Bindings.Element (2 * I - 1).S, - Eval (Bindings.Element (2 * I), Env)); - end loop; + if First.Kind /= Kind_Symbol then + -- Evaluate First, in the less frequent case where it is + -- not a symbol. + First := Eval (First, Env); + elsif First.Symbol = Symbols.Names.Def then + if Ast.L.Length /= 3 then + raise Argument_Error with "def!: expects 2 arguments"; + elsif Ast.L.Element (2).Kind /= Kind_Symbol then + raise Argument_Error with "def!: arg 1 must be a symbol"; + end if; + return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).Symbol, R); + end return; + elsif First.Symbol = Symbols.Names.Mal_Do then + if Ast.L.Length = 1 then + raise Argument_Error with "do: expects at least 1 argument"; + end if; + for I in 2 .. Ast.L.Length - 1 loop + Discard (Eval (Ast.L.Element (I), Env)); + end loop; + Ast := Ast.L.Element (Ast.L.Length); + goto Restart; + elsif First.Symbol = Symbols.Names.Fn then + if Ast.L.Length /= 3 then + raise Argument_Error with "fn*: expects 3 arguments"; + elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "fn*: arg 1 must be a list or vector"; + elsif (for some F in 1 .. Ast.L.Element (2).L.Length => + Ast.L.Element (2).L.Element (F).Kind /= Kind_Symbol) + then + raise Argument_Error with "fn*: arg 2 must contain symbols"; + end if; + return Functions.New_Function (Ast.L.Element (2).L, + Ast.L.Element (3), Env.New_Closure); + elsif First.Symbol = Symbols.Names.Mal_If then + if Ast.L.Length not in 3 .. 4 then + raise Argument_Error with "if: expects 2 or 3 arguments"; + end if; + declare + Test : constant Mal.T := Eval (Ast.L.Element (2), Env); + begin + if (case Test.Kind is + when Kind_Nil => False, + when Kind_Boolean => Test.Ada_Boolean, + when others => True) + then Ast := Ast.L.Element (3); goto Restart; - end; + elsif Ast.L.Length = 3 then + return Mal.Nil; + else + Ast := Ast.L.Element (4); + goto Restart; + end if; + end; + elsif First.Symbol = Symbols.Names.Let then + if Ast.L.Length /= 3 then + raise Argument_Error with "let*: expects 3 arguments"; + elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "let*: expects a list or vector"; end if; + declare + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + begin + if Bindings.Length mod 2 /= 0 then + raise Argument_Error with "let*: odd number of bindings"; + end if; + Env.Replace_With_Sub; + for I in 1 .. Bindings.Length / 2 loop + if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then + raise Argument_Error with "let*: keys must be symbols"; + end if; + Env.Set (Bindings.Element (2 * I - 1).Symbol, + Eval (Bindings.Element (2 * I), Env)); + end loop; + Ast := Ast.L.Element (3); + goto Restart; + end; + else + -- Equivalent to First := Eval (First, Env), except that + -- we already know enough to spare a recursive call in + -- this frequent case. + First := Env.Get (First.Symbol); end if; - - -- No special form has been found, attempt to apply the - -- first element to the rest of the list. - declare - Args : Mal_Type_Array (2 .. Ast.L.Length); - begin - First := Eval (First, Env); - for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); - end loop; - case First.Kind is - when Kind_Native => - return First.Native.all (Args); - when Kind_Function => - Env := Environments.Alloc (Outer => First.Environment); - Env.Set_Binds (First.Formals, Args); - Ast := First.Expression.Deref; + -- Apply phase. + case First.Kind is + when Kind_Builtin => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + return First.Builtin.all (Args); + end; + when Kind_Function => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + Env.Replace_With_Sub (First.Function_Value.Closure); + First.Function_Value.Set_Binds (Env, Args); + Ast := First.Function_Value.Expression; goto Restart; - when others => - raise Unable_To_Call - with Ada.Strings.Unbounded.To_String (Print (First)); - end case; - end; + end; + when others => + raise Argument_Error + with "cannot execute " & ASU.To_String (Print (First)); + end case; + when others => + return Ast; end case; end Eval; - procedure Interactive_Loop (Repl : in Environments.Ptr) - is - - function Readline (Prompt : in Interfaces.C.char_array) - return Interfaces.C.Strings.chars_ptr + procedure Interactive_Loop (Repl : in Environments.Ptr) is + use Interfaces.C, Interfaces.C.Strings; + function Readline (Prompt : in char_array) return chars_ptr with Import, Convention => C, External_Name => "readline"; - - procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + procedure Add_History (Line : in chars_ptr) with Import, Convention => C, External_Name => "add_history"; - - procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + procedure Free (Line : in chars_ptr) with Import, Convention => C, External_Name => "free"; - - Prompt : constant Interfaces.C.char_array - := Interfaces.C.To_C ("user> "); - C_Line : Interfaces.C.Strings.chars_ptr; + Prompt : constant char_array := To_C ("user> "); + C_Line : chars_ptr; begin loop C_Line := Readline (Prompt); - exit when C_Line = Interfaces.C.Strings.Null_Ptr; + exit when C_Line = Null_Ptr; declare - Line : constant String := Interfaces.C.Strings.Value (C_Line); + Line : constant String := Value (C_Line); begin if Line /= "" then Add_History (C_Line); @@ -215,9 +210,10 @@ procedure Step5_Tco is exception when Reader.Empty_Source => null; - when E : others => + when E : Argument_Error | Reader.Reader_Error + | Environments.Unknown_Key => Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- but go on proceeding. + -- Other exceptions are unexpected. end; end loop; Ada.Text_IO.New_Line; @@ -225,10 +221,12 @@ procedure Step5_Tco is ---------------------------------------------------------------------- - Repl : constant Environments.Ptr := Environments.Alloc; + Startup : constant String := "(do" + & "(def! not (fn* (a) (if a false true)))" + & ")"; + Repl : Environments.Ptr renames Environments.Repl; begin - Core.Add_Built_In_Functions (Repl, Eval'Unrestricted_Access); - Discard (Eval (Read ("(def! not (fn* (a) (if a false true)))"), Repl)); - + Core.Eval_Ref := Eval'Unrestricted_Access; + Discard (Eval (Read (Startup), Repl)); Interactive_Loop (Repl); end Step5_Tco; diff --git a/ada2/step6_file.adb b/ada2/step6_file.adb index 7670995b73..85795d77bb 100644 --- a/ada2/step6_file.adb +++ b/ada2/step6_file.adb @@ -2,214 +2,206 @@ with Ada.Command_Line; with Ada.Exceptions; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; -with Atoms; +with Interfaces.C.Strings; + with Core; with Environments; -with Lists; -with Names; with Printer; with Reader; -with Strings; use type Strings.Ptr; -with Types; +with Types.Functions; +with Types.Lists; +with Types.Mal; +with Types.Maps; +with Types.Symbols.Names; procedure Step6_File is - function Read (Source : in String) return Types.Mal_Type + package ASU renames Ada.Strings.Unbounded; + use Types; + use type Symbols.Ptr; + + function Read (Source : in String) return Mal.T renames Reader.Read_Str; - function Eval (Rec_Ast : in Types.Mal_Type; - Rec_Env : in Environments.Ptr) return Types.Mal_Type; - Unable_To_Call : exception; + function Eval (Ast0 : in Mal.T; + Env0 : in Environments.Ptr) return Mal.T; - function Print (Ast : in Types.Mal_Type; - Print_Readably : in Boolean := True) - return Ada.Strings.Unbounded.Unbounded_String + function Print (Ast : in Mal.T; + Readably : in Boolean := True) return ASU.Unbounded_String renames Printer.Pr_Str; function Rep (Source : in String; - Env : in Environments.Ptr) - return Ada.Strings.Unbounded.Unbounded_String - is (Print (Eval (Read (Source), Env))) - with Inline; + Env : in Environments.Ptr) return ASU.Unbounded_String + is (Print (Eval (Read (Source), Env))) with Inline; - procedure Interactive_Loop (Repl : in Environments.Ptr) - with Inline; + procedure Interactive_Loop (Repl : in Environments.Ptr); - -- Convenient when the result of eval is of no interest. - procedure Discard (Ast : in Types.Mal_Type) is null; + function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); - -- Eval, with a profile compatible with Native_Function_Access. - function Eval_Native (Args : in Types.Mal_Type_Array) return Types.Mal_Type; + -- Convenient when the result of eval is of no interest. + procedure Discard (Ast : in Mal.T) is null; ---------------------------------------------------------------------- - function Eval (Rec_Ast : in Types.Mal_Type; - Rec_Env : in Environments.Ptr) return Types.Mal_Type - is - use Types; - Ast : Types.Mal_Type := Rec_Ast; - Env : Environments.Ptr := Rec_Env; - First : Mal_Type; + function Eval (Ast0 : in Mal.T; + Env0 : in Environments.Ptr) return Mal.T is + -- Use local variables, that can be rewritten when tail call + -- optimization goes to <>. + Ast : Mal.T := Ast0; + Env : Environments.Ptr := Env0.Copy_Pointer; + First : Mal.T; begin - <> + <> + -- Ada.Text_IO.New_Line; + -- Ada.Text_IO.Put ("EVAL: "); + -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Environments.Dump_Stack; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => - return Ast; - when Kind_Symbol => - return Env.Get (Ast.S); - + return Env.Get (Ast.Symbol); when Kind_Map => - declare - function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); - begin - return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); - end; - + return Eval_Elements (Ast.Map, Env); when Kind_Vector => - return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, - Lists.Alloc (Ast.L.Length)) - do - for I in 1 .. Ast.L.Length loop - R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); - end loop; - end return; - + return (Kind_Vector, Eval_Elements (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); - -- Special forms - if First.Kind = Kind_Symbol then - - if First.S = Names.Def then - pragma Assert (Ast.L.Length = 3); - pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); - return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).S, R); - end return; - - elsif First.S = Names.Mal_Do then - for I in 2 .. Ast.L.Length - 1 loop - Discard (Eval (Ast.L.Element (I), Env)); - end loop; - Ast := Ast.L.Element (Ast.L.Length); - goto Restart; - - elsif First.S = Names.Fn then - pragma Assert (Ast.L.Length = 3); - pragma Assert - (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); - pragma Assert - (for all I in 1 .. Ast.L.Element (2).L.Length => - Ast.L.Element (2).L.Element (I).Kind = Kind_Symbol); - pragma Assert - (Ast.L.Element (2).L.Length < 1 - or else Names.Ampersand /= - Ast.L.Element (2).L.Element (Ast.L.Element (2).L.Length).S); - pragma Assert - (for all I in 1 .. Ast.L.Element (2).L.Length - 2 => - Ast.L.Element (2).L.Element (I).S /= Names.Ampersand); - return (Kind => Kind_Function, - Meta => Atoms.No_Element, - Formals => Ast.L.Element (2).L, - Expression => Atoms.Alloc (Ast.L.Element (3)), - Environment => Env); - - elsif First.S = Names.Mal_If then - declare - pragma Assert (Ast.L.Length in 3 .. 4); - Test : constant Mal_Type := Eval (Ast.L.Element (2), Env); - begin - if (case Test.Kind is - when Kind_Nil => False, - when Kind_Boolean => Test.Boolean_Value, - when others => True) - then - Ast := Ast.L.Element (3); - goto Restart; - elsif Ast.L.Length = 3 then - return (Kind_Nil, Atoms.No_Element); - else - Ast := Ast.L.Element (4); - goto Restart; - end if; - end; - - elsif First.S = Names.Let then - declare - pragma Assert (Ast.L.Length = 3); - pragma Assert - (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; - pragma Assert (Bindings.Length mod 2 = 0); - begin - Env.Replace_With_Subenv; - Env.Increase_Capacity (Bindings.Length / 2); - for I in 1 .. Bindings.Length / 2 loop - pragma Assert - (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); - Env.Set (Bindings.Element (2 * I - 1).S, - Eval (Bindings.Element (2 * I), Env)); - end loop; + if First.Kind /= Kind_Symbol then + -- Evaluate First, in the less frequent case where it is + -- not a symbol. + First := Eval (First, Env); + elsif First.Symbol = Symbols.Names.Def then + if Ast.L.Length /= 3 then + raise Argument_Error with "def!: expects 2 arguments"; + elsif Ast.L.Element (2).Kind /= Kind_Symbol then + raise Argument_Error with "def!: arg 1 must be a symbol"; + end if; + return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).Symbol, R); + end return; + elsif First.Symbol = Symbols.Names.Mal_Do then + if Ast.L.Length = 1 then + raise Argument_Error with "do: expects at least 1 argument"; + end if; + for I in 2 .. Ast.L.Length - 1 loop + Discard (Eval (Ast.L.Element (I), Env)); + end loop; + Ast := Ast.L.Element (Ast.L.Length); + goto Restart; + elsif First.Symbol = Symbols.Names.Fn then + if Ast.L.Length /= 3 then + raise Argument_Error with "fn*: expects 3 arguments"; + elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "fn*: arg 1 must be a list or vector"; + elsif (for some F in 1 .. Ast.L.Element (2).L.Length => + Ast.L.Element (2).L.Element (F).Kind /= Kind_Symbol) + then + raise Argument_Error with "fn*: arg 2 must contain symbols"; + end if; + return Functions.New_Function (Ast.L.Element (2).L, + Ast.L.Element (3), Env.New_Closure); + elsif First.Symbol = Symbols.Names.Mal_If then + if Ast.L.Length not in 3 .. 4 then + raise Argument_Error with "if: expects 2 or 3 arguments"; + end if; + declare + Test : constant Mal.T := Eval (Ast.L.Element (2), Env); + begin + if (case Test.Kind is + when Kind_Nil => False, + when Kind_Boolean => Test.Ada_Boolean, + when others => True) + then Ast := Ast.L.Element (3); goto Restart; - end; + elsif Ast.L.Length = 3 then + return Mal.Nil; + else + Ast := Ast.L.Element (4); + goto Restart; + end if; + end; + elsif First.Symbol = Symbols.Names.Let then + if Ast.L.Length /= 3 then + raise Argument_Error with "let*: expects 3 arguments"; + elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "let*: expects a list or vector"; end if; + declare + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + begin + if Bindings.Length mod 2 /= 0 then + raise Argument_Error with "let*: odd number of bindings"; + end if; + Env.Replace_With_Sub; + for I in 1 .. Bindings.Length / 2 loop + if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then + raise Argument_Error with "let*: keys must be symbols"; + end if; + Env.Set (Bindings.Element (2 * I - 1).Symbol, + Eval (Bindings.Element (2 * I), Env)); + end loop; + Ast := Ast.L.Element (3); + goto Restart; + end; + else + -- Equivalent to First := Eval (First, Env), except that + -- we already know enough to spare a recursive call in + -- this frequent case. + First := Env.Get (First.Symbol); end if; - - -- No special form has been found, attempt to apply the - -- first element to the rest of the list. - declare - Args : Mal_Type_Array (2 .. Ast.L.Length); - begin - First := Eval (First, Env); - for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); - end loop; - case First.Kind is - when Kind_Native => - return First.Native.all (Args); - when Kind_Function => - Env := Environments.Alloc (Outer => First.Environment); - Env.Set_Binds (First.Formals, Args); - Ast := First.Expression.Deref; + -- Apply phase. + case First.Kind is + when Kind_Builtin => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + return First.Builtin.all (Args); + end; + when Kind_Function => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + Env.Replace_With_Sub (First.Function_Value.Closure); + First.Function_Value.Set_Binds (Env, Args); + Ast := First.Function_Value.Expression; goto Restart; - when others => - raise Unable_To_Call - with Ada.Strings.Unbounded.To_String (Print (First)); - end case; - end; + end; + when others => + raise Argument_Error + with "cannot execute " & ASU.To_String (Print (First)); + end case; + when others => + return Ast; end case; end Eval; - procedure Interactive_Loop (Repl : in Environments.Ptr) - is - - function Readline (Prompt : in Interfaces.C.char_array) - return Interfaces.C.Strings.chars_ptr + procedure Interactive_Loop (Repl : in Environments.Ptr) is + use Interfaces.C, Interfaces.C.Strings; + function Readline (Prompt : in char_array) return chars_ptr with Import, Convention => C, External_Name => "readline"; - - procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + procedure Add_History (Line : in chars_ptr) with Import, Convention => C, External_Name => "add_history"; - - procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + procedure Free (Line : in chars_ptr) with Import, Convention => C, External_Name => "free"; - - Prompt : constant Interfaces.C.char_array - := Interfaces.C.To_C ("user> "); - C_Line : Interfaces.C.Strings.chars_ptr; + Prompt : constant char_array := To_C ("user> "); + C_Line : chars_ptr; begin loop C_Line := Readline (Prompt); - exit when C_Line = Interfaces.C.Strings.Null_Ptr; + exit when C_Line = Null_Ptr; declare - Line : constant String := Interfaces.C.Strings.Value (C_Line); + Line : constant String := Value (C_Line); begin if Line /= "" then Add_History (C_Line); @@ -219,9 +211,10 @@ procedure Step6_File is exception when Reader.Empty_Source => null; - when E : others => + when E : Argument_Error | Reader.Reader_Error + | Environments.Unknown_Key => Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- but go on proceeding. + -- Other exceptions are unexpected. end; end loop; Ada.Text_IO.New_Line; @@ -229,32 +222,27 @@ procedure Step6_File is ---------------------------------------------------------------------- - use Types; - Argv : Mal_Type (Kind_List); - Repl : constant Environments.Ptr := Environments.Alloc; - function Eval_Native (Args : in Mal_Type_Array) return Mal_Type is - (Eval (Args (Args'First), Repl)); + Startup : constant String := "(do" + & "(def! not (fn* (a) (if a false true)))" + & "(def! load-file (fn* (f)" + & " (eval (read-string (str ""(do "" (slurp f) "")"")))))" + & ")"; + Repl : Environments.Ptr renames Environments.Repl; + use Ada.Command_Line; begin - Core.Add_Built_In_Functions (Repl, Eval'Unrestricted_Access); - Repl.Set (Names.Eval, Mal_Type'(Kind_Native, Atoms.No_Element, - Eval_Native'Unrestricted_Access)); - - Discard (Eval (Read ("(def! not (fn* (a) (if a false true)))"), Repl)); - Discard (Eval (Read ("(def! load-file (fn* (f) " - & "(eval (read-string (str ""(do "" (slurp f) "")"")))))"), Repl)); - - if Ada.Command_Line.Argument_Count = 0 then - Repl.Set (Names.Argv, Argv); + Core.Eval_Ref := Eval'Unrestricted_Access; + Discard (Eval (Read (Startup), Repl)); + declare + Args : Mal.T_Array (2 .. Argument_Count); + begin + for I in Args'Range loop + Args (I) := (Kind_String, ASU.To_Unbounded_String (Argument (I))); + end loop; + Repl.Set (Symbols.Constructor ("*ARGV*"), Lists.List (Args)); + end; + if Argument_Count = 0 then Interactive_Loop (Repl); else - Argv.L := Lists.Alloc (Ada.Command_Line.Argument_Count - 1); - for I in 2 .. Ada.Command_Line.Argument_Count loop - Argv.L.Replace_Element (I - 1, - Mal_Type'(Kind_String, Atoms.No_Element, - Strings.Alloc (Ada.Command_Line.Argument (I)))); - end loop; - Repl.Set (Names.Argv, Argv); - Discard (Eval (Read ("(load-file """ & Ada.Command_Line.Argument (1) - & """)"), Repl)); + Discard (Eval (Read ("(load-file """ & Argument (1) & """)"), Repl)); end if; end Step6_File; diff --git a/ada2/step7_quote.adb b/ada2/step7_quote.adb index 9286169aee..c2b5438ff8 100644 --- a/ada2/step7_quote.adb +++ b/ada2/step7_quote.adb @@ -1,236 +1,227 @@ -with Ada.Containers.Vectors; with Ada.Command_Line; with Ada.Exceptions; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; -with Atoms; +with Interfaces.C.Strings; + with Core; with Environments; -with Lists; -with Names; with Printer; with Reader; -with Strings; use type Strings.Ptr; -with Types; use type Types.Kind_Type; +with Types.Functions; +with Types.Lists; +with Types.Mal; +with Types.Maps; +with Types.Symbols.Names; procedure Step7_Quote is - function Read (Source : in String) return Types.Mal_Type + package ASU renames Ada.Strings.Unbounded; + use Types; + use type Symbols.Ptr; + + function Read (Source : in String) return Mal.T renames Reader.Read_Str; - function Eval (Rec_Ast : in Types.Mal_Type; - Rec_Env : in Environments.Ptr) return Types.Mal_Type; - Unable_To_Call : exception; + function Eval (Ast0 : in Mal.T; + Env0 : in Environments.Ptr) return Mal.T; - function Quasiquote (Ast : in Types.Mal_Type; - Env : in Environments.Ptr) return Types.Mal_Type; + function Quasiquote (Ast : in Mal.T; + Env : in Environments.Ptr) return Mal.T; function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Lists.Ptr - with Inline; + Env : in Environments.Ptr) return Mal.T with Inline; -- Handle vectors and lists not starting with unquote. + -- Mergeing quote and quasiquote into eval with a flag triggering + -- a different behaviour as done for macros in step8 would improve + -- the performances significantly, but Kanaka finds that it breaks + -- too much the step structure shared by all implementations. - function Print (Ast : in Types.Mal_Type; - Print_Readably : in Boolean := True) - return Ada.Strings.Unbounded.Unbounded_String + function Print (Ast : in Mal.T; + Readably : in Boolean := True) return ASU.Unbounded_String renames Printer.Pr_Str; function Rep (Source : in String; - Env : in Environments.Ptr) - return Ada.Strings.Unbounded.Unbounded_String - is (Print (Eval (Read (Source), Env))) - with Inline; - - procedure Interactive_Loop (Repl : in Environments.Ptr) - with Inline; + Env : in Environments.Ptr) return ASU.Unbounded_String + is (Print (Eval (Read (Source), Env))) with Inline; - -- Convenient when the result of eval is of no interest. - procedure Discard (Ast : in Types.Mal_Type) is null; + procedure Interactive_Loop (Repl : in Environments.Ptr); - -- Eval, with a profile compatible with Native_Function_Access. - function Eval_Native (Args : in Types.Mal_Type_Array) return Types.Mal_Type; + function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); - package Mal_Type_Vectors is new Ada.Containers.Vectors - (Index_Type => Positive, - Element_Type => Types.Mal_Type, - "=" => Types."="); + -- Convenient when the result of eval is of no interest. + procedure Discard (Ast : in Mal.T) is null; ---------------------------------------------------------------------- - function Eval (Rec_Ast : in Types.Mal_Type; - Rec_Env : in Environments.Ptr) return Types.Mal_Type - is - use Types; - Ast : Types.Mal_Type := Rec_Ast; - Env : Environments.Ptr := Rec_Env; - First : Mal_Type; + function Eval (Ast0 : in Mal.T; + Env0 : in Environments.Ptr) return Mal.T is + -- Use local variables, that can be rewritten when tail call + -- optimization goes to <>. + Ast : Mal.T := Ast0; + Env : Environments.Ptr := Env0.Copy_Pointer; + First : Mal.T; begin - <> + <> + -- Ada.Text_IO.New_Line; + -- Ada.Text_IO.Put ("EVAL: "); + -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Environments.Dump_Stack; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => - return Ast; - when Kind_Symbol => - return Env.Get (Ast.S); - + return Env.Get (Ast.Symbol); when Kind_Map => - declare - function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); - begin - return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); - end; - + return Eval_Elements (Ast.Map, Env); when Kind_Vector => - return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, - Lists.Alloc (Ast.L.Length)) - do - for I in 1 .. Ast.L.Length loop - R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); - end loop; - end return; - + return (Kind_Vector, Eval_Elements (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); - -- Special forms - if First.Kind = Kind_Symbol then - - if First.S = Names.Def then - pragma Assert (Ast.L.Length = 3); - pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); - return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).S, R); - end return; - - elsif First.S = Names.Mal_Do then - for I in 2 .. Ast.L.Length - 1 loop - Discard (Eval (Ast.L.Element (I), Env)); - end loop; - Ast := Ast.L.Element (Ast.L.Length); - goto Restart; - - elsif First.S = Names.Fn then - pragma Assert (Ast.L.Length = 3); - pragma Assert - (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); - pragma Assert - (for all I in 1 .. Ast.L.Element (2).L.Length => - Ast.L.Element (2).L.Element (I).Kind = Kind_Symbol); - pragma Assert - (Ast.L.Element (2).L.Length < 1 - or else Names.Ampersand /= - Ast.L.Element (2).L.Element (Ast.L.Element (2).L.Length).S); - pragma Assert - (for all I in 1 .. Ast.L.Element (2).L.Length - 2 => - Ast.L.Element (2).L.Element (I).S /= Names.Ampersand); - return (Kind => Kind_Function, - Meta => Atoms.No_Element, - Formals => Ast.L.Element (2).L, - Expression => Atoms.Alloc (Ast.L.Element (3)), - Environment => Env); - - elsif First.S = Names.Mal_If then - declare - pragma Assert (Ast.L.Length in 3 .. 4); - Test : constant Mal_Type := Eval (Ast.L.Element (2), Env); - begin - if (case Test.Kind is - when Kind_Nil => False, - when Kind_Boolean => Test.Boolean_Value, - when others => True) - then - Ast := Ast.L.Element (3); - goto Restart; - elsif Ast.L.Length = 3 then - return (Kind_Nil, Atoms.No_Element); - else - Ast := Ast.L.Element (4); - goto Restart; - end if; - end; - - elsif First.S = Names.Let then - declare - pragma Assert (Ast.L.Length = 3); - pragma Assert - (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; - pragma Assert (Bindings.Length mod 2 = 0); - begin - Env.Replace_With_Subenv; - Env.Increase_Capacity (Bindings.Length / 2); - for I in 1 .. Bindings.Length / 2 loop - pragma Assert - (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); - Env.Set (Bindings.Element (2 * I - 1).S, - Eval (Bindings.Element (2 * I), Env)); - end loop; + if First.Kind /= Kind_Symbol then + -- Evaluate First, in the less frequent case where it is + -- not a symbol. + First := Eval (First, Env); + elsif First.Symbol = Symbols.Names.Def then + if Ast.L.Length /= 3 then + raise Argument_Error with "def!: expects 2 arguments"; + elsif Ast.L.Element (2).Kind /= Kind_Symbol then + raise Argument_Error with "def!: arg 1 must be a symbol"; + end if; + return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).Symbol, R); + end return; + elsif First.Symbol = Symbols.Names.Mal_Do then + if Ast.L.Length = 1 then + raise Argument_Error with "do: expects at least 1 argument"; + end if; + for I in 2 .. Ast.L.Length - 1 loop + Discard (Eval (Ast.L.Element (I), Env)); + end loop; + Ast := Ast.L.Element (Ast.L.Length); + goto Restart; + elsif First.Symbol = Symbols.Names.Fn then + if Ast.L.Length /= 3 then + raise Argument_Error with "fn*: expects 3 arguments"; + elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "fn*: arg 1 must be a list or vector"; + elsif (for some F in 1 .. Ast.L.Element (2).L.Length => + Ast.L.Element (2).L.Element (F).Kind /= Kind_Symbol) + then + raise Argument_Error with "fn*: arg 2 must contain symbols"; + end if; + return Functions.New_Function (Ast.L.Element (2).L, + Ast.L.Element (3), Env.New_Closure); + elsif First.Symbol = Symbols.Names.Mal_If then + if Ast.L.Length not in 3 .. 4 then + raise Argument_Error with "if: expects 2 or 3 arguments"; + end if; + declare + Test : constant Mal.T := Eval (Ast.L.Element (2), Env); + begin + if (case Test.Kind is + when Kind_Nil => False, + when Kind_Boolean => Test.Ada_Boolean, + when others => True) + then Ast := Ast.L.Element (3); goto Restart; - end; - - elsif First.S = Names.Quote then - pragma Assert (Ast.L.Length = 2); - return Ast.L.Element (2); - - elsif First.S = Names.Quasiquote then - pragma Assert (Ast.L.Length = 2); - return Quasiquote (Ast.L.Element (2), Env); + elsif Ast.L.Length = 3 then + return Mal.Nil; + else + Ast := Ast.L.Element (4); + goto Restart; + end if; + end; + elsif First.Symbol = Symbols.Names.Let then + if Ast.L.Length /= 3 then + raise Argument_Error with "let*: expects 3 arguments"; + elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "let*: expects a list or vector"; + end if; + declare + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + begin + if Bindings.Length mod 2 /= 0 then + raise Argument_Error with "let*: odd number of bindings"; + end if; + Env.Replace_With_Sub; + for I in 1 .. Bindings.Length / 2 loop + if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then + raise Argument_Error with "let*: keys must be symbols"; + end if; + Env.Set (Bindings.Element (2 * I - 1).Symbol, + Eval (Bindings.Element (2 * I), Env)); + end loop; + Ast := Ast.L.Element (3); + goto Restart; + end; + elsif First.Symbol = Symbols.Names.Quasiquote then + if Ast.L.Length /= 2 then + raise Argument_Error with "quasiquote: expects 1 argument"; + end if; + return Quasiquote (Ast.L.Element (2), Env); + elsif First.Symbol = Symbols.Names.Quote then + if Ast.L.Length /= 2 then + raise Argument_Error with "quote: expects 1 argument"; end if; + return Ast.L.Element (2); + else + -- Equivalent to First := Eval (First, Env), except that + -- we already know enough to spare a recursive call in + -- this frequent case. + First := Env.Get (First.Symbol); end if; - - -- No special form has been found, attempt to apply the - -- first element to the rest of the list. - declare - Args : Mal_Type_Array (2 .. Ast.L.Length); - begin - First := Eval (First, Env); - for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); - end loop; - case First.Kind is - when Kind_Native => - return First.Native.all (Args); - when Kind_Function => - Env := Environments.Alloc (Outer => First.Environment); - Env.Set_Binds (First.Formals, Args); - Ast := First.Expression.Deref; + -- Apply phase. + case First.Kind is + when Kind_Builtin => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + return First.Builtin.all (Args); + end; + when Kind_Function => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin + for I in Args'Range loop + Args (I) := Eval (Ast.L.Element (I), Env); + end loop; + Env.Replace_With_Sub (First.Function_Value.Closure); + First.Function_Value.Set_Binds (Env, Args); + Ast := First.Function_Value.Expression; goto Restart; - when others => - raise Unable_To_Call - with Ada.Strings.Unbounded.To_String (Print (First)); - end case; - end; + end; + when others => + raise Argument_Error + with "cannot execute " & ASU.To_String (Print (First)); + end case; + when others => + return Ast; end case; end Eval; - procedure Interactive_Loop (Repl : in Environments.Ptr) - is - - function Readline (Prompt : in Interfaces.C.char_array) - return Interfaces.C.Strings.chars_ptr + procedure Interactive_Loop (Repl : in Environments.Ptr) is + use Interfaces.C, Interfaces.C.Strings; + function Readline (Prompt : in char_array) return chars_ptr with Import, Convention => C, External_Name => "readline"; - - procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + procedure Add_History (Line : in chars_ptr) with Import, Convention => C, External_Name => "add_history"; - - procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + procedure Free (Line : in chars_ptr) with Import, Convention => C, External_Name => "free"; - - Prompt : constant Interfaces.C.char_array - := Interfaces.C.To_C ("user> "); - C_Line : Interfaces.C.Strings.chars_ptr; + Prompt : constant char_array := To_C ("user> "); + C_Line : chars_ptr; begin loop C_Line := Readline (Prompt); - exit when C_Line = Interfaces.C.Strings.Null_Ptr; + exit when C_Line = Null_Ptr; declare - Line : constant String := Interfaces.C.Strings.Value (C_Line); + Line : constant String := Value (C_Line); begin if Line /= "" then Add_History (C_Line); @@ -240,87 +231,77 @@ procedure Step7_Quote is exception when Reader.Empty_Source => null; - when E : others => + when E : Argument_Error | Reader.Reader_Error + | Environments.Unknown_Key => Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- but go on proceeding. + -- Other exceptions are unexpected. end; end loop; Ada.Text_IO.New_Line; end Interactive_Loop; - function Quasiquote (Ast : in Types.Mal_Type; - Env : in Environments.Ptr) return Types.Mal_Type + function Quasiquote (Ast : in Mal.T; + Env : in Environments.Ptr) return Mal.T is (case Ast.Kind is - when Types.Kind_Vector => - (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env)), - -- When the test is updated, replace Kind_List with Kind_Vector. - when Types.Kind_List => + when Kind_Vector => Quasiquote (Ast.L, Env), + -- When the test is updated, replace Kind_List with Kind_Vector. + when Kind_List => (if 0 < Ast.L.Length - and then Ast.L.Element (1).Kind = Types.Kind_Symbol - and then Ast.L.Element (1).S = Names.Unquote + and then Ast.L.Element (1).Kind = Kind_Symbol + and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote then Eval (Ast.L.Element (2), Env) - else (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env))), + else Quasiquote (Ast.L, Env)), when others => Ast); function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Lists.Ptr - is - use Types; - Buffer : Mal_Type_Vectors.Vector; - Elt : Mal_Type; + Env : in Environments.Ptr) return Mal.T is + -- The final return concatenates these lists. + R : Mal.T_Array (1 .. List.Length); begin - for I in 1 .. List.Length loop - Elt := List.Element (I); - if Elt.Kind in Kind_List | Kind_Vector - and then 0 < Elt.L.Length - and then Elt.L.Element (1).Kind = Kind_Symbol - and then Elt.L.Element (1).S = Names.Splice_Unquote + for I in R'Range loop + R (I) := List.Element (I); + if R (I).Kind in Kind_List | Kind_Vector + and then 0 < R (I).L.Length + and then R (I).L.Element (1).Kind = Kind_Symbol + and then R (I).L.Element (1).Symbol = Symbols.Names.Splice_Unquote then - pragma Assert (Elt.L.Length = 2); - Elt := Eval (Elt.L.Element (2), Env); - pragma Assert (Elt.Kind = Kind_List); - for J in 1 .. Elt.L.Length loop - Buffer.Append (Elt.L.Element (J)); - end loop; + if R (I).L.Length /= 2 then + raise Argument_Error with "splice-unquote: expects 1 argument"; + end if; + R (I) := Eval (R (I).L.Element (2), Env); + if R (I).Kind /= Kind_List then + raise Argument_Error with "splice-unquote: expects a list"; + end if; else - Buffer.Append (Quasiquote (Elt, Env)); + R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), Env))); end if; end loop; - return R : constant Lists.Ptr := Lists.Alloc (Natural (Buffer.Length)) do - for I in 1 .. R.Length loop - R.Replace_Element (I, Buffer.Element (I)); - end loop; - end return; + return Lists.Concat (R); end Quasiquote; ---------------------------------------------------------------------- - use Types; - Argv : Mal_Type (Kind_List); - Repl : constant Environments.Ptr := Environments.Alloc; - function Eval_Native (Args : in Mal_Type_Array) return Mal_Type is - (Eval (Args (Args'First), Repl)); + Startup : constant String := "(do" + & "(def! not (fn* (a) (if a false true)))" + & "(def! load-file (fn* (f)" + & " (eval (read-string (str ""(do "" (slurp f) "")"")))))" + & ")"; + Repl : Environments.Ptr renames Environments.Repl; + use Ada.Command_Line; begin - Core.Add_Built_In_Functions (Repl, Eval'Unrestricted_Access); - Repl.Set (Names.Eval, Mal_Type'(Kind_Native, Atoms.No_Element, - Eval_Native'Unrestricted_Access)); - - Discard (Eval (Read ("(def! not (fn* (a) (if a false true)))"), Repl)); - Discard (Eval (Read ("(def! load-file (fn* (f) " - & "(eval (read-string (str ""(do "" (slurp f) "")"")))))"), Repl)); - - if Ada.Command_Line.Argument_Count = 0 then - Repl.Set (Names.Argv, Argv); + Core.Eval_Ref := Eval'Unrestricted_Access; + Discard (Eval (Read (Startup), Repl)); + declare + Args : Mal.T_Array (2 .. Argument_Count); + begin + for I in Args'Range loop + Args (I) := (Kind_String, ASU.To_Unbounded_String (Argument (I))); + end loop; + Repl.Set (Symbols.Constructor ("*ARGV*"), Lists.List (Args)); + end; + if Argument_Count = 0 then Interactive_Loop (Repl); else - Argv.L := Lists.Alloc (Ada.Command_Line.Argument_Count - 1); - for I in 2 .. Ada.Command_Line.Argument_Count loop - Argv.L.Replace_Element (I - 1, - Mal_Type'(Kind_String, Atoms.No_Element, - Strings.Alloc (Ada.Command_Line.Argument (I)))); - end loop; - Repl.Set (Names.Argv, Argv); - Discard (Eval (Read ("(load-file """ & Ada.Command_Line.Argument (1) - & """)"), Repl)); + Discard (Eval (Read ("(load-file """ & Argument (1) & """)"), Repl)); end if; end Step7_Quote; diff --git a/ada2/step8_macros.adb b/ada2/step8_macros.adb index c85b0a3222..c73935b16d 100644 --- a/ada2/step8_macros.adb +++ b/ada2/step8_macros.adb @@ -1,282 +1,262 @@ -with Ada.Containers.Vectors; with Ada.Command_Line; with Ada.Exceptions; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; -with Atoms; +with Interfaces.C.Strings; + with Core; with Environments; -with Lists; -with Names; with Printer; with Reader; -with Strings; use type Strings.Ptr; -with Types; use type Types.Kind_Type; +with Types.Functions; +with Types.Lists; +with Types.Mal; +with Types.Maps; +with Types.Symbols.Names; procedure Step8_Macros is - function Read (Source : in String) return Types.Mal_Type + package ASU renames Ada.Strings.Unbounded; + use Types; + use type Symbols.Ptr; + + function Read (Source : in String) return Mal.T renames Reader.Read_Str; - function Eval (Rec_Ast : in Types.Mal_Type; - Rec_Env : in Environments.Ptr) return Types.Mal_Type; - Unable_To_Call : exception; + function Eval (Ast0 : in Mal.T; + Env0 : in Environments.Ptr) return Mal.T; - function Quasiquote (Ast : in Types.Mal_Type; - Env : in Environments.Ptr) return Types.Mal_Type; + function Quasiquote (Ast : in Mal.T; + Env : in Environments.Ptr) return Mal.T; function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Lists.Ptr - with Inline; + Env : in Environments.Ptr) return Mal.T with Inline; -- Handle vectors and lists not starting with unquote. + -- Mergeing quote and quasiquote into eval with a flag triggering + -- a different behaviour as done for macros in step8 would improve + -- the performances significantly, but Kanaka finds that it breaks + -- too much the step structure shared by all implementations. - function Print (Ast : in Types.Mal_Type; - Print_Readably : in Boolean := True) - return Ada.Strings.Unbounded.Unbounded_String + function Print (Ast : in Mal.T; + Readably : in Boolean := True) return ASU.Unbounded_String renames Printer.Pr_Str; function Rep (Source : in String; - Env : in Environments.Ptr) - return Ada.Strings.Unbounded.Unbounded_String - is (Print (Eval (Read (Source), Env))) - with Inline; + Env : in Environments.Ptr) return ASU.Unbounded_String + is (Print (Eval (Read (Source), Env))) with Inline; - procedure Interactive_Loop (Repl : in Environments.Ptr) - with Inline; - - -- Convenient when the result of eval is of no interest. - procedure Discard (Ast : in Types.Mal_Type) is null; + procedure Interactive_Loop (Repl : in Environments.Ptr); - -- Eval, with a profile compatible with Native_Function_Access. - function Eval_Native (Args : in Types.Mal_Type_Array) return Types.Mal_Type; + function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); - package Mal_Type_Vectors is new Ada.Containers.Vectors - (Index_Type => Positive, - Element_Type => Types.Mal_Type, - "=" => Types."="); + -- Convenient when the result of eval is of no interest. + procedure Discard (Ast : in Mal.T) is null; ---------------------------------------------------------------------- - function Eval (Rec_Ast : in Types.Mal_Type; - Rec_Env : in Environments.Ptr) return Types.Mal_Type - is - use Types; - Ast : Types.Mal_Type := Rec_Ast; - Env : Environments.Ptr := Rec_Env; + function Eval (Ast0 : in Mal.T; + Env0 : in Environments.Ptr) return Mal.T is + -- Use local variables, that can be rewritten when tail call + -- optimization goes to <>. + Ast : Mal.T := Ast0; + Env : Environments.Ptr := Env0.Copy_Pointer; Macroexpanding : Boolean := False; - First : Mal_Type; + First : Mal.T; begin - <> + <> + -- Ada.Text_IO.New_Line; + -- Ada.Text_IO.Put ("EVAL: "); + -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Environments.Dump_Stack; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => - return Ast; - when Kind_Symbol => - return Env.Get (Ast.S); - + return Env.Get (Ast.Symbol); when Kind_Map => - declare - function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); - begin - return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); - end; - + return Eval_Elements (Ast.Map, Env); when Kind_Vector => - return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, - Lists.Alloc (Ast.L.Length)) - do - for I in 1 .. Ast.L.Length loop - R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); - end loop; - end return; - + return (Kind_Vector, Eval_Elements (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); - -- Special forms - if First.Kind = Kind_Symbol then - - if First.S = Names.Def then - pragma Assert (Ast.L.Length = 3); - pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); - return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).S, R); + if First.Kind /= Kind_Symbol then + -- Evaluate First, in the less frequent case where it is + -- not a symbol. + First := Eval (First, Env); + elsif First.Symbol = Symbols.Names.Def then + if Ast.L.Length /= 3 then + raise Argument_Error with "def!: expects 2 arguments"; + elsif Ast.L.Element (2).Kind /= Kind_Symbol then + raise Argument_Error with "def!: arg 1 must be a symbol"; + end if; + return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).Symbol, R); + end return; + elsif First.Symbol = Symbols.Names.Defmacro then + if Ast.L.Length /= 3 then + raise Argument_Error with "defmacro!: expects 2 arguments"; + elsif Ast.L.Element (2).Kind /= Kind_Symbol then + raise Argument_Error with "defmacro!: arg 1 must be a symbol"; + end if; + declare + F : constant Mal.T := Eval (Ast.L.Element (3), Env); + begin + if F.Kind /= Kind_Function then + raise Argument_Error with "defmacro!: expects a function"; + end if; + return R : constant Mal.T := F.Function_Value.New_Macro do + Env.Set (Ast.L.Element (2).Symbol, R); end return; - - elsif First.S = Names.Defmacro then - declare - pragma Assert (Ast.L.Length = 3); - pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); - F : constant Mal_Type := Eval (Ast.L.Element (3), Env); - pragma Assert (F.Kind = Kind_Function); - begin - return R : constant Mal_Type - := (Kind => Kind_Macro, - Meta => Atoms.No_Element, - Mac_Formals => F.Formals, - Mac_Expression => F.Expression) - do - Env.Set (Ast.L.Element (2).S, R); - end return; - end; - - elsif First.S = Names.Mal_Do then - for I in 2 .. Ast.L.Length - 1 loop - Discard (Eval (Ast.L.Element (I), Env)); - end loop; - Ast := Ast.L.Element (Ast.L.Length); - goto Restart; - - elsif First.S = Names.Fn then - pragma Assert (Ast.L.Length = 3); - pragma Assert - (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); - pragma Assert - (for all I in 1 .. Ast.L.Element (2).L.Length => - Ast.L.Element (2).L.Element (I).Kind = Kind_Symbol); - pragma Assert - (Ast.L.Element (2).L.Length < 1 - or else Names.Ampersand /= - Ast.L.Element (2).L.Element (Ast.L.Element (2).L.Length).S); - pragma Assert - (for all I in 1 .. Ast.L.Element (2).L.Length - 2 => - Ast.L.Element (2).L.Element (I).S /= Names.Ampersand); - return (Kind => Kind_Function, - Meta => Atoms.No_Element, - Formals => Ast.L.Element (2).L, - Expression => Atoms.Alloc (Ast.L.Element (3)), - Environment => Env); - - elsif First.S = Names.Mal_If then - declare - pragma Assert (Ast.L.Length in 3 .. 4); - Test : constant Mal_Type := Eval (Ast.L.Element (2), Env); - begin - if (case Test.Kind is - when Kind_Nil => False, - when Kind_Boolean => Test.Boolean_Value, - when others => True) - then - Ast := Ast.L.Element (3); - goto Restart; - elsif Ast.L.Length = 3 then - return (Kind_Nil, Atoms.No_Element); - else - Ast := Ast.L.Element (4); - goto Restart; - end if; - end; - - elsif First.S = Names.Let then - declare - pragma Assert (Ast.L.Length = 3); - pragma Assert - (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; - pragma Assert (Bindings.Length mod 2 = 0); - begin - Env.Replace_With_Subenv; - Env.Increase_Capacity (Bindings.Length / 2); - for I in 1 .. Bindings.Length / 2 loop - pragma Assert - (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); - Env.Set (Bindings.Element (2 * I - 1).S, - Eval (Bindings.Element (2 * I), Env)); - end loop; + end; + elsif First.Symbol = Symbols.Names.Mal_Do then + if Ast.L.Length = 1 then + raise Argument_Error with "do: expects at least 1 argument"; + end if; + for I in 2 .. Ast.L.Length - 1 loop + Discard (Eval (Ast.L.Element (I), Env)); + end loop; + Ast := Ast.L.Element (Ast.L.Length); + goto Restart; + elsif First.Symbol = Symbols.Names.Fn then + if Ast.L.Length /= 3 then + raise Argument_Error with "fn*: expects 3 arguments"; + elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "fn*: arg 1 must be a list or vector"; + elsif (for some F in 1 .. Ast.L.Element (2).L.Length => + Ast.L.Element (2).L.Element (F).Kind /= Kind_Symbol) + then + raise Argument_Error with "fn*: arg 2 must contain symbols"; + end if; + return Functions.New_Function (Ast.L.Element (2).L, + Ast.L.Element (3), Env.New_Closure); + elsif First.Symbol = Symbols.Names.Mal_If then + if Ast.L.Length not in 3 .. 4 then + raise Argument_Error with "if: expects 2 or 3 arguments"; + end if; + declare + Test : constant Mal.T := Eval (Ast.L.Element (2), Env); + begin + if (case Test.Kind is + when Kind_Nil => False, + when Kind_Boolean => Test.Ada_Boolean, + when others => True) + then Ast := Ast.L.Element (3); goto Restart; - end; - - elsif First.S = Names.Macroexpand then - pragma Assert (Ast.L.Length = 2); - Macroexpanding := True; - Ast := Ast.L.Element (2); + elsif Ast.L.Length = 3 then + return Mal.Nil; + else + Ast := Ast.L.Element (4); + goto Restart; + end if; + end; + elsif First.Symbol = Symbols.Names.Let then + if Ast.L.Length /= 3 then + raise Argument_Error with "let*: expects 3 arguments"; + elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "let*: expects a list or vector"; + end if; + declare + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + begin + if Bindings.Length mod 2 /= 0 then + raise Argument_Error with "let*: odd number of bindings"; + end if; + Env.Replace_With_Sub; + for I in 1 .. Bindings.Length / 2 loop + if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then + raise Argument_Error with "let*: keys must be symbols"; + end if; + Env.Set (Bindings.Element (2 * I - 1).Symbol, + Eval (Bindings.Element (2 * I), Env)); + end loop; + Ast := Ast.L.Element (3); goto Restart; - - elsif First.S = Names.Quote then - pragma Assert (Ast.L.Length = 2); - return Ast.L.Element (2); - - elsif First.S = Names.Quasiquote then - pragma Assert (Ast.L.Length = 2); - return Quasiquote (Ast.L.Element (2), Env); + end; + elsif First.Symbol = Symbols.Names.Macroexpand then + if Ast.L.Length /= 2 then + raise Argument_Error with "macroexpand: expects 1 argument"; + end if; + Macroexpanding := True; + Ast := Ast.L.Element (2); + goto Restart; + elsif First.Symbol = Symbols.Names.Quasiquote then + if Ast.L.Length /= 2 then + raise Argument_Error with "quasiquote: expects 1 argument"; + end if; + return Quasiquote (Ast.L.Element (2), Env); + elsif First.Symbol = Symbols.Names.Quote then + if Ast.L.Length /= 2 then + raise Argument_Error with "quote: expects 1 argument"; end if; + return Ast.L.Element (2); + else + -- Equivalent to First := Eval (First, Env), except that + -- we already know enough to spare a recursive call in + -- this frequent case. + First := Env.Get (First.Symbol); end if; - - -- No special form has been found, attempt to apply the - -- first element to the rest of the list. - declare - Args : Mal_Type_Array (2 .. Ast.L.Length); - begin - First := Eval (First, Env); - case First.Kind is - - when Kind_Native => + -- Apply phase. + case First.Kind is + when Kind_Builtin => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin for I in Args'Range loop Args (I) := Eval (Ast.L.Element (I), Env); end loop; - return First.Native.all (Args); - - when Kind_Function => + return First.Builtin.all (Args); + end; + when Kind_Function => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin for I in Args'Range loop Args (I) := Eval (Ast.L.Element (I), Env); end loop; - Env := Environments.Alloc (Outer => First.Environment); - Env.Set_Binds (First.Formals, Args); - Ast := First.Expression.Deref; + Env.Replace_With_Sub (First.Function_Value.Closure); + First.Function_Value.Set_Binds (Env, Args); + Ast := First.Function_Value.Expression; goto Restart; - - when Kind_Macro => - for I in Args'Range loop - Args (I) := Ast.L.Element (I); - end loop; - declare - New_Env : constant Environments.Ptr - := Environments.Alloc (Outer => Env); - begin - New_Env.Set_Binds (First.Mac_Formals, Args); - Ast := Eval (First.Mac_Expression.Deref, New_Env); - end; - if Macroexpanding then - return Ast; - end if; - goto Restart; - - when others => - raise Unable_To_Call - with Ada.Strings.Unbounded.To_String (Print (First)); - end case; - end; + end; + when Kind_Macro => + declare + New_Env : constant Environments.Ptr := Env.Sub; + begin + First.Function_Value.Set_Binds (New_Env, Ast.L); + Ast := Eval (First.Function_Value.Expression, New_Env); + end; + if Macroexpanding then + return Ast; + end if; + goto Restart; + when others => + raise Argument_Error + with "cannot execute " & ASU.To_String (Print (First)); + end case; + when others => + return Ast; end case; end Eval; - procedure Interactive_Loop (Repl : in Environments.Ptr) - is - - function Readline (Prompt : in Interfaces.C.char_array) - return Interfaces.C.Strings.chars_ptr + procedure Interactive_Loop (Repl : in Environments.Ptr) is + use Interfaces.C, Interfaces.C.Strings; + function Readline (Prompt : in char_array) return chars_ptr with Import, Convention => C, External_Name => "readline"; - - procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + procedure Add_History (Line : in chars_ptr) with Import, Convention => C, External_Name => "add_history"; - - procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + procedure Free (Line : in chars_ptr) with Import, Convention => C, External_Name => "free"; - - Prompt : constant Interfaces.C.char_array - := Interfaces.C.To_C ("user> "); - C_Line : Interfaces.C.Strings.chars_ptr; + Prompt : constant char_array := To_C ("user> "); + C_Line : chars_ptr; begin loop C_Line := Readline (Prompt); - exit when C_Line = Interfaces.C.Strings.Null_Ptr; + exit when C_Line = Null_Ptr; declare - Line : constant String := Interfaces.C.Strings.Value (C_Line); + Line : constant String := Value (C_Line); begin if Line /= "" then Add_History (C_Line); @@ -286,98 +266,88 @@ procedure Step8_Macros is exception when Reader.Empty_Source => null; - when E : others => + when E : Argument_Error | Reader.Reader_Error + | Environments.Unknown_Key => Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- but go on proceeding. + -- Other exceptions are unexpected. end; end loop; Ada.Text_IO.New_Line; end Interactive_Loop; - function Quasiquote (Ast : in Types.Mal_Type; - Env : in Environments.Ptr) return Types.Mal_Type + function Quasiquote (Ast : in Mal.T; + Env : in Environments.Ptr) return Mal.T is (case Ast.Kind is - when Types.Kind_Vector => - (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env)), - -- When the test is updated, replace Kind_List with Kind_Vector. - when Types.Kind_List => + when Kind_Vector => Quasiquote (Ast.L, Env), + -- When the test is updated, replace Kind_List with Kind_Vector. + when Kind_List => (if 0 < Ast.L.Length - and then Ast.L.Element (1).Kind = Types.Kind_Symbol - and then Ast.L.Element (1).S = Names.Unquote + and then Ast.L.Element (1).Kind = Kind_Symbol + and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote then Eval (Ast.L.Element (2), Env) - else (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env))), + else Quasiquote (Ast.L, Env)), when others => Ast); function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Lists.Ptr - is - use Types; - Buffer : Mal_Type_Vectors.Vector; - Elt : Mal_Type; + Env : in Environments.Ptr) return Mal.T is + -- The final return concatenates these lists. + R : Mal.T_Array (1 .. List.Length); begin - for I in 1 .. List.Length loop - Elt := List.Element (I); - if Elt.Kind in Kind_List | Kind_Vector - and then 0 < Elt.L.Length - and then Elt.L.Element (1).Kind = Kind_Symbol - and then Elt.L.Element (1).S = Names.Splice_Unquote + for I in R'Range loop + R (I) := List.Element (I); + if R (I).Kind in Kind_List | Kind_Vector + and then 0 < R (I).L.Length + and then R (I).L.Element (1).Kind = Kind_Symbol + and then R (I).L.Element (1).Symbol = Symbols.Names.Splice_Unquote then - pragma Assert (Elt.L.Length = 2); - Elt := Eval (Elt.L.Element (2), Env); - pragma Assert (Elt.Kind = Kind_List); - for J in 1 .. Elt.L.Length loop - Buffer.Append (Elt.L.Element (J)); - end loop; + if R (I).L.Length /= 2 then + raise Argument_Error with "splice-unquote: expects 1 argument"; + end if; + R (I) := Eval (R (I).L.Element (2), Env); + if R (I).Kind /= Kind_List then + raise Argument_Error with "splice-unquote: expects a list"; + end if; else - Buffer.Append (Quasiquote (Elt, Env)); + R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), Env))); end if; end loop; - return R : constant Lists.Ptr := Lists.Alloc (Natural (Buffer.Length)) do - for I in 1 .. R.Length loop - R.Replace_Element (I, Buffer.Element (I)); - end loop; - end return; + return Lists.Concat (R); end Quasiquote; ---------------------------------------------------------------------- - use Types; - Argv : Mal_Type (Kind_List); - Repl : constant Environments.Ptr := Environments.Alloc; - function Eval_Native (Args : in Mal_Type_Array) return Mal_Type is - (Eval (Args (Args'First), Repl)); + Startup : constant String := "(do" + & "(def! not (fn* (a) (if a false true)))" + & "(def! load-file (fn* (f)" + & " (eval (read-string (str ""(do "" (slurp f) "")"")))))" + & "(defmacro! cond (fn* (& xs)" + & " (if (> (count xs) 0)" + & " (list 'if (first xs)" + & " (if (> (count xs) 1) (nth xs 1)" + & " (throw ""odd number of forms to cond""))" + & " (cons 'cond (rest (rest xs)))))))" + & "(defmacro! or (fn* (& xs)" + & " (if (empty? xs) nil" + & " (if (= 1 (count xs)) (first xs)" + & " `(let* (or_FIXME ~(first xs))" + & " (if or_FIXME or_FIXME (or ~@(rest xs))))))))" + & ")"; + Repl : Environments.Ptr renames Environments.Repl; + use Ada.Command_Line; begin - Core.Add_Built_In_Functions (Repl, Eval'Unrestricted_Access); - Repl.Set (Names.Eval, Mal_Type'(Kind_Native, Atoms.No_Element, - Eval_Native'Unrestricted_Access)); - - Discard (Eval (Read ("(def! not (fn* (a) (if a false true)))"), Repl)); - Discard (Eval (Read ("(def! load-file (fn* (f) " - & "(eval (read-string (str ""(do "" (slurp f) "")"")))))"), Repl)); - Discard (Eval (Read ("(defmacro! cond " - & "(fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) " - & "(if (> (count xs) 1) (nth xs 1) " - & "(throw ""odd number of forms to cond"")) " - & "(cons 'cond (rest (rest xs)))))))"), Repl)); - Discard (Eval (Read ("(defmacro! or (fn* (& xs) " - & "(if (empty? xs) nil " - & "(if (= 1 (count xs)) (first xs) " - & "`(let* (or_FIXME ~(first xs)) " - & "(if or_FIXME or_FIXME " - & "(or ~@(rest xs))))))))"), Repl)); - - if Ada.Command_Line.Argument_Count = 0 then - Repl.Set (Names.Argv, Argv); + Core.Eval_Ref := Eval'Unrestricted_Access; + Discard (Eval (Read (Startup), Repl)); + declare + Args : Mal.T_Array (2 .. Argument_Count); + begin + for I in Args'Range loop + Args (I) := (Kind_String, ASU.To_Unbounded_String (Argument (I))); + end loop; + Repl.Set (Symbols.Constructor ("*ARGV*"), Lists.List (Args)); + end; + if Argument_Count = 0 then Interactive_Loop (Repl); else - Argv.L := Lists.Alloc (Ada.Command_Line.Argument_Count - 1); - for I in 2 .. Ada.Command_Line.Argument_Count loop - Argv.L.Replace_Element (I - 1, - Mal_Type'(Kind_String, Atoms.No_Element, - Strings.Alloc (Ada.Command_Line.Argument (I)))); - end loop; - Repl.Set (Names.Argv, Argv); - Discard (Eval (Read ("(load-file """ & Ada.Command_Line.Argument (1) - & """)"), Repl)); + Discard (Eval (Read ("(load-file """ & Argument (1) & """)"), Repl)); end if; end Step8_Macros; diff --git a/ada2/step9_try.adb b/ada2/step9_try.adb index 6651b5ba30..33ba83923d 100644 --- a/ada2/step9_try.adb +++ b/ada2/step9_try.adb @@ -1,310 +1,303 @@ -with Ada.Containers.Vectors; with Ada.Command_Line; -with Ada.Exceptions; use type Ada.Exceptions.Exception_Id; +with Ada.Exceptions; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; -with Atoms; +with Interfaces.C.Strings; + with Core; with Environments; -with Lists; -with Names; with Printer; with Reader; -with Strings; use type Strings.Ptr; -with Types; use type Types.Kind_Type; +with Types.Functions; +with Types.Lists; +with Types.Mal; +with Types.Maps; +with Types.Symbols.Names; procedure Step9_Try is - function Read (Source : in String) return Types.Mal_Type + package ASU renames Ada.Strings.Unbounded; + use Types; + use type Symbols.Ptr; + + function Read (Source : in String) return Mal.T renames Reader.Read_Str; - function Eval (Rec_Ast : in Types.Mal_Type; - Rec_Env : in Environments.Ptr) return Types.Mal_Type; - Unable_To_Call : exception; + function Eval (Ast0 : in Mal.T; + Env0 : in Environments.Ptr) return Mal.T; - function Quasiquote (Ast : in Types.Mal_Type; - Env : in Environments.Ptr) return Types.Mal_Type; + function Quasiquote (Ast : in Mal.T; + Env : in Environments.Ptr) return Mal.T; function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Lists.Ptr - with Inline; + Env : in Environments.Ptr) return Mal.T with Inline; -- Handle vectors and lists not starting with unquote. + -- Mergeing quote and quasiquote into eval with a flag triggering + -- a different behaviour as done for macros in step8 would improve + -- the performances significantly, but Kanaka finds that it breaks + -- too much the step structure shared by all implementations. - function Print (Ast : in Types.Mal_Type; - Print_Readably : in Boolean := True) - return Ada.Strings.Unbounded.Unbounded_String + function Print (Ast : in Mal.T; + Readably : in Boolean := True) return ASU.Unbounded_String renames Printer.Pr_Str; function Rep (Source : in String; - Env : in Environments.Ptr) - return Ada.Strings.Unbounded.Unbounded_String - is (Print (Eval (Read (Source), Env))) - with Inline; + Env : in Environments.Ptr) return ASU.Unbounded_String + is (Print (Eval (Read (Source), Env))) with Inline; - procedure Interactive_Loop (Repl : in Environments.Ptr) - with Inline; + procedure Interactive_Loop (Repl : in Environments.Ptr); - -- Convenient when the result of eval is of no interest. - procedure Discard (Ast : in Types.Mal_Type) is null; - - -- Eval, with a profile compatible with Native_Function_Access. - function Eval_Native (Args : in Types.Mal_Type_Array) return Types.Mal_Type; + function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); - package Mal_Type_Vectors is new Ada.Containers.Vectors - (Index_Type => Positive, - Element_Type => Types.Mal_Type, - "=" => Types."="); + -- Convenient when the result of eval is of no interest. + procedure Discard (Ast : in Mal.T) is null; ---------------------------------------------------------------------- - function Eval (Rec_Ast : in Types.Mal_Type; - Rec_Env : in Environments.Ptr) return Types.Mal_Type - is - use Types; - Ast : Types.Mal_Type := Rec_Ast; - Env : Environments.Ptr := Rec_Env; + function Eval (Ast0 : in Mal.T; + Env0 : in Environments.Ptr) return Mal.T is + -- Use local variables, that can be rewritten when tail call + -- optimization goes to <>. + Ast : Mal.T := Ast0; + Env : Environments.Ptr := Env0.Copy_Pointer; Macroexpanding : Boolean := False; - First : Mal_Type; + First : Mal.T; begin - <> + <> + -- Ada.Text_IO.New_Line; + -- Ada.Text_IO.Put ("EVAL: "); + -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Environments.Dump_Stack; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => - return Ast; - when Kind_Symbol => - return Env.Get (Ast.S); - + return Env.Get (Ast.Symbol); when Kind_Map => - declare - function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); - begin - return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); - end; - + return Eval_Elements (Ast.Map, Env); when Kind_Vector => - return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, - Lists.Alloc (Ast.L.Length)) - do - for I in 1 .. Ast.L.Length loop - R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); - end loop; - end return; - + return (Kind_Vector, Eval_Elements (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); - -- Special forms - if First.Kind = Kind_Symbol then - - if First.S = Names.Def then - pragma Assert (Ast.L.Length = 3); - pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); - return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).S, R); + if First.Kind /= Kind_Symbol then + -- Evaluate First, in the less frequent case where it is + -- not a symbol. + First := Eval (First, Env); + elsif First.Symbol = Symbols.Names.Def then + if Ast.L.Length /= 3 then + raise Argument_Error with "def!: expects 2 arguments"; + elsif Ast.L.Element (2).Kind /= Kind_Symbol then + raise Argument_Error with "def!: arg 1 must be a symbol"; + end if; + return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).Symbol, R); + end return; + elsif First.Symbol = Symbols.Names.Defmacro then + if Ast.L.Length /= 3 then + raise Argument_Error with "defmacro!: expects 2 arguments"; + elsif Ast.L.Element (2).Kind /= Kind_Symbol then + raise Argument_Error with "defmacro!: arg 1 must be a symbol"; + end if; + declare + F : constant Mal.T := Eval (Ast.L.Element (3), Env); + begin + if F.Kind /= Kind_Function then + raise Argument_Error with "defmacro!: expects a function"; + end if; + return R : constant Mal.T := F.Function_Value.New_Macro do + Env.Set (Ast.L.Element (2).Symbol, R); end return; - - elsif First.S = Names.Defmacro then - declare - pragma Assert (Ast.L.Length = 3); - pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); - F : constant Mal_Type := Eval (Ast.L.Element (3), Env); - pragma Assert (F.Kind = Kind_Function); - begin - return R : constant Mal_Type - := (Kind => Kind_Macro, - Meta => Atoms.No_Element, - Mac_Formals => F.Formals, - Mac_Expression => F.Expression) - do - Env.Set (Ast.L.Element (2).S, R); - end return; - end; - - elsif First.S = Names.Mal_Do then - for I in 2 .. Ast.L.Length - 1 loop - Discard (Eval (Ast.L.Element (I), Env)); - end loop; - Ast := Ast.L.Element (Ast.L.Length); - goto Restart; - - elsif First.S = Names.Fn then - pragma Assert (Ast.L.Length = 3); - pragma Assert - (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); - pragma Assert - (for all I in 1 .. Ast.L.Element (2).L.Length => - Ast.L.Element (2).L.Element (I).Kind = Kind_Symbol); - pragma Assert - (Ast.L.Element (2).L.Length < 1 - or else Names.Ampersand /= - Ast.L.Element (2).L.Element (Ast.L.Element (2).L.Length).S); - pragma Assert - (for all I in 1 .. Ast.L.Element (2).L.Length - 2 => - Ast.L.Element (2).L.Element (I).S /= Names.Ampersand); - return (Kind => Kind_Function, - Meta => Atoms.No_Element, - Formals => Ast.L.Element (2).L, - Expression => Atoms.Alloc (Ast.L.Element (3)), - Environment => Env); - - elsif First.S = Names.Mal_If then - declare - pragma Assert (Ast.L.Length in 3 .. 4); - Test : constant Mal_Type := Eval (Ast.L.Element (2), Env); - begin - if (case Test.Kind is - when Kind_Nil => False, - when Kind_Boolean => Test.Boolean_Value, - when others => True) - then - Ast := Ast.L.Element (3); - goto Restart; - elsif Ast.L.Length = 3 then - return (Kind_Nil, Atoms.No_Element); - else - Ast := Ast.L.Element (4); - goto Restart; - end if; - end; - - elsif First.S = Names.Let then - declare - pragma Assert (Ast.L.Length = 3); - pragma Assert - (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; - pragma Assert (Bindings.Length mod 2 = 0); - begin - Env.Replace_With_Subenv; - Env.Increase_Capacity (Bindings.Length / 2); - for I in 1 .. Bindings.Length / 2 loop - pragma Assert - (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); - Env.Set (Bindings.Element (2 * I - 1).S, - Eval (Bindings.Element (2 * I), Env)); - end loop; + end; + elsif First.Symbol = Symbols.Names.Mal_Do then + if Ast.L.Length = 1 then + raise Argument_Error with "do: expects at least 1 argument"; + end if; + for I in 2 .. Ast.L.Length - 1 loop + Discard (Eval (Ast.L.Element (I), Env)); + end loop; + Ast := Ast.L.Element (Ast.L.Length); + goto Restart; + elsif First.Symbol = Symbols.Names.Fn then + if Ast.L.Length /= 3 then + raise Argument_Error with "fn*: expects 3 arguments"; + elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "fn*: arg 1 must be a list or vector"; + elsif (for some F in 1 .. Ast.L.Element (2).L.Length => + Ast.L.Element (2).L.Element (F).Kind /= Kind_Symbol) + then + raise Argument_Error with "fn*: arg 2 must contain symbols"; + end if; + return Functions.New_Function (Ast.L.Element (2).L, + Ast.L.Element (3), Env.New_Closure); + elsif First.Symbol = Symbols.Names.Mal_If then + if Ast.L.Length not in 3 .. 4 then + raise Argument_Error with "if: expects 2 or 3 arguments"; + end if; + declare + Test : constant Mal.T := Eval (Ast.L.Element (2), Env); + begin + if (case Test.Kind is + when Kind_Nil => False, + when Kind_Boolean => Test.Ada_Boolean, + when others => True) + then Ast := Ast.L.Element (3); goto Restart; - end; - - elsif First.S = Names.Macroexpand then - pragma Assert (Ast.L.Length = 2); - Macroexpanding := True; + elsif Ast.L.Length = 3 then + return Mal.Nil; + else + Ast := Ast.L.Element (4); + goto Restart; + end if; + end; + elsif First.Symbol = Symbols.Names.Let then + if Ast.L.Length /= 3 then + raise Argument_Error with "let*: expects 3 arguments"; + elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "let*: expects a list or vector"; + end if; + declare + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + begin + if Bindings.Length mod 2 /= 0 then + raise Argument_Error with "let*: odd number of bindings"; + end if; + Env.Replace_With_Sub; + for I in 1 .. Bindings.Length / 2 loop + if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then + raise Argument_Error with "let*: keys must be symbols"; + end if; + Env.Set (Bindings.Element (2 * I - 1).Symbol, + Eval (Bindings.Element (2 * I), Env)); + end loop; + Ast := Ast.L.Element (3); + goto Restart; + end; + elsif First.Symbol = Symbols.Names.Macroexpand then + if Ast.L.Length /= 2 then + raise Argument_Error with "macroexpand: expects 1 argument"; + end if; + Macroexpanding := True; + Ast := Ast.L.Element (2); + goto Restart; + elsif First.Symbol = Symbols.Names.Quasiquote then + if Ast.L.Length /= 2 then + raise Argument_Error with "quasiquote: expects 1 argument"; + end if; + return Quasiquote (Ast.L.Element (2), Env); + elsif First.Symbol = Symbols.Names.Quote then + if Ast.L.Length /= 2 then + raise Argument_Error with "quote: expects 1 argument"; + end if; + return Ast.L.Element (2); + elsif First.Symbol = Symbols.Names.Try then + if Ast.L.Length = 2 then Ast := Ast.L.Element (2); goto Restart; - - elsif First.S = Names.Quote then - pragma Assert (Ast.L.Length = 2); - return Ast.L.Element (2); - - elsif First.S = Names.Quasiquote then - pragma Assert (Ast.L.Length = 2); - return Quasiquote (Ast.L.Element (2), Env); - - elsif First.S = Names.Try then - declare - pragma Assert (Ast.L.Length = 3); - pragma Assert (Ast.L.Element (3).Kind = Kind_List); - A3 : constant Lists.Ptr := Ast.L.Element (3).L; - pragma Assert (A3.Length = 3); - pragma Assert (A3.Element (1).Kind = Kind_Symbol); - pragma Assert (A3.Element (1).S = Names.Catch); - pragma Assert (A3.Element (2).Kind = Kind_Symbol); + elsif Ast.L.Length /= 3 then + raise Argument_Error with "try*: expects 1 or 2 arguments"; + elsif Ast.L.Element (3).Kind /= Kind_List then + raise Argument_Error with "try*: argument 2 must be a list"; + end if; + declare + A3 : constant Lists.Ptr := Ast.L.Element (3).L; + begin + if A3.Length /= 3 then + raise Argument_Error with "try*: arg 2 must have 3 elements"; + elsif A3.Element (1).Kind /= Kind_Symbol + or else A3.Element (1).Symbol /= Symbols.Names.Catch + then + raise Argument_Error with "try*: arg 2 must be a catch*"; + elsif A3.Element (2).Kind /= Kind_Symbol then + raise Argument_Error with "catch*: expects a symbol"; + end if; begin return Eval (Ast.L.Element (2), Env); exception - when E : others => - Env.Replace_With_Subenv; - if Ada.Exceptions.Exception_Identity (E) - = Core.Exception_Throwed'Identity - then - Env.Set (A3.Element (2).S, Core.Last_Exception); - Core.Last_Exception := (Kind_Nil, Atoms.No_Element); - else - Env.Set (A3.Element (2).S, Mal_Type' - (Kind_String, Atoms.No_Element, Strings.Alloc - (Ada.Exceptions.Exception_Message (E)))); - end if; + when E : Reader.Empty_Source | Argument_Error + | Reader.Reader_Error | Environments.Unknown_Key => + Env.Replace_With_Sub; + Env.Set (A3.Element (2).Symbol, + Mal.T'(Kind_String, ASU.To_Unbounded_String + (Ada.Exceptions.Exception_Message (E)))); Ast := A3.Element (3); goto Restart; + when Core.Exception_Throwed => + Env.Replace_With_Sub; + Env.Set (A3.Element (2).Symbol, Core.Last_Exception); + Core.Last_Exception := Mal.Nil; + Ast := A3.Element (3); + goto Restart; + -- Other exceptions are unexpected. end; - end if; + end; + else + -- Equivalent to First := Eval (First, Env), except that + -- we already know enough to spare a recursive call in + -- this frequent case. + First := Env.Get (First.Symbol); end if; - - -- No special form has been found, attempt to apply the - -- first element to the rest of the list. - declare - Args : Mal_Type_Array (2 .. Ast.L.Length); - begin - First := Eval (First, Env); - case First.Kind is - - when Kind_Native => + -- Apply phase. + case First.Kind is + when Kind_Builtin => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin for I in Args'Range loop Args (I) := Eval (Ast.L.Element (I), Env); end loop; - return First.Native.all (Args); - - when Kind_Function => + return First.Builtin.all (Args); + end; + when Kind_Function => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin for I in Args'Range loop Args (I) := Eval (Ast.L.Element (I), Env); end loop; - Env := Environments.Alloc (Outer => First.Environment); - Env.Set_Binds (First.Formals, Args); - Ast := First.Expression.Deref; - goto Restart; - - when Kind_Macro => - for I in Args'Range loop - Args (I) := Ast.L.Element (I); - end loop; - declare - New_Env : constant Environments.Ptr - := Environments.Alloc (Outer => Env); - begin - New_Env.Set_Binds (First.Mac_Formals, Args); - Ast := Eval (First.Mac_Expression.Deref, New_Env); - end; - if Macroexpanding then - return Ast; - end if; + Env.Replace_With_Sub (First.Function_Value.Closure); + First.Function_Value.Set_Binds (Env, Args); + Ast := First.Function_Value.Expression; goto Restart; - - when others => - raise Unable_To_Call - with Ada.Strings.Unbounded.To_String (Print (First)); - end case; - end; + end; + when Kind_Macro => + declare + New_Env : constant Environments.Ptr := Env.Sub; + begin + First.Function_Value.Set_Binds (New_Env, Ast.L); + Ast := Eval (First.Function_Value.Expression, New_Env); + end; + if Macroexpanding then + return Ast; + end if; + goto Restart; + when others => + raise Argument_Error + with "cannot execute " & ASU.To_String (Print (First)); + end case; + when others => + return Ast; end case; end Eval; - procedure Interactive_Loop (Repl : in Environments.Ptr) - is - - function Readline (Prompt : in Interfaces.C.char_array) - return Interfaces.C.Strings.chars_ptr + procedure Interactive_Loop (Repl : in Environments.Ptr) is + use Interfaces.C, Interfaces.C.Strings; + function Readline (Prompt : in char_array) return chars_ptr with Import, Convention => C, External_Name => "readline"; - - procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + procedure Add_History (Line : in chars_ptr) with Import, Convention => C, External_Name => "add_history"; - - procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + procedure Free (Line : in chars_ptr) with Import, Convention => C, External_Name => "free"; - - Prompt : constant Interfaces.C.char_array - := Interfaces.C.To_C ("user> "); - C_Line : Interfaces.C.Strings.chars_ptr; + Prompt : constant char_array := To_C ("user> "); + C_Line : chars_ptr; begin loop C_Line := Readline (Prompt); - exit when C_Line = Interfaces.C.Strings.Null_Ptr; + exit when C_Line = Null_Ptr; declare - Line : constant String := Interfaces.C.Strings.Value (C_Line); + Line : constant String := Value (C_Line); begin if Line /= "" then Add_History (C_Line); @@ -314,98 +307,92 @@ procedure Step9_Try is exception when Reader.Empty_Source => null; - when E : others => + when E : Argument_Error | Reader.Reader_Error + | Environments.Unknown_Key => Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- but go on proceeding. + when Core.Exception_Throwed => + Ada.Text_IO.Put ("User exception: "); + Ada.Text_IO.Unbounded_IO.Put_Line (Print (Core.Last_Exception)); + Core.Last_Exception := Mal.Nil; + -- Other exceptions are unexpected. end; end loop; Ada.Text_IO.New_Line; end Interactive_Loop; - function Quasiquote (Ast : in Types.Mal_Type; - Env : in Environments.Ptr) return Types.Mal_Type + function Quasiquote (Ast : in Mal.T; + Env : in Environments.Ptr) return Mal.T is (case Ast.Kind is - when Types.Kind_Vector => - (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env)), - -- When the test is updated, replace Kind_List with Kind_Vector. - when Types.Kind_List => + when Kind_Vector => Quasiquote (Ast.L, Env), + -- When the test is updated, replace Kind_List with Kind_Vector. + when Kind_List => (if 0 < Ast.L.Length - and then Ast.L.Element (1).Kind = Types.Kind_Symbol - and then Ast.L.Element (1).S = Names.Unquote + and then Ast.L.Element (1).Kind = Kind_Symbol + and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote then Eval (Ast.L.Element (2), Env) - else (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env))), + else Quasiquote (Ast.L, Env)), when others => Ast); function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Lists.Ptr - is - use Types; - Buffer : Mal_Type_Vectors.Vector; - Elt : Mal_Type; + Env : in Environments.Ptr) return Mal.T is + -- The final return concatenates these lists. + R : Mal.T_Array (1 .. List.Length); begin - for I in 1 .. List.Length loop - Elt := List.Element (I); - if Elt.Kind in Kind_List | Kind_Vector - and then 0 < Elt.L.Length - and then Elt.L.Element (1).Kind = Kind_Symbol - and then Elt.L.Element (1).S = Names.Splice_Unquote + for I in R'Range loop + R (I) := List.Element (I); + if R (I).Kind in Kind_List | Kind_Vector + and then 0 < R (I).L.Length + and then R (I).L.Element (1).Kind = Kind_Symbol + and then R (I).L.Element (1).Symbol = Symbols.Names.Splice_Unquote then - pragma Assert (Elt.L.Length = 2); - Elt := Eval (Elt.L.Element (2), Env); - pragma Assert (Elt.Kind = Kind_List); - for J in 1 .. Elt.L.Length loop - Buffer.Append (Elt.L.Element (J)); - end loop; + if R (I).L.Length /= 2 then + raise Argument_Error with "splice-unquote: expects 1 argument"; + end if; + R (I) := Eval (R (I).L.Element (2), Env); + if R (I).Kind /= Kind_List then + raise Argument_Error with "splice-unquote: expects a list"; + end if; else - Buffer.Append (Quasiquote (Elt, Env)); + R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), Env))); end if; end loop; - return R : constant Lists.Ptr := Lists.Alloc (Natural (Buffer.Length)) do - for I in 1 .. R.Length loop - R.Replace_Element (I, Buffer.Element (I)); - end loop; - end return; + return Lists.Concat (R); end Quasiquote; ---------------------------------------------------------------------- - use Types; - Argv : Mal_Type (Kind_List); - Repl : constant Environments.Ptr := Environments.Alloc; - function Eval_Native (Args : in Mal_Type_Array) return Mal_Type is - (Eval (Args (Args'First), Repl)); + Startup : constant String := "(do" + & "(def! not (fn* (a) (if a false true)))" + & "(def! load-file (fn* (f)" + & " (eval (read-string (str ""(do "" (slurp f) "")"")))))" + & "(defmacro! cond (fn* (& xs)" + & " (if (> (count xs) 0)" + & " (list 'if (first xs)" + & " (if (> (count xs) 1) (nth xs 1)" + & " (throw ""odd number of forms to cond""))" + & " (cons 'cond (rest (rest xs)))))))" + & "(defmacro! or (fn* (& xs)" + & " (if (empty? xs) nil" + & " (if (= 1 (count xs)) (first xs)" + & " `(let* (or_FIXME ~(first xs))" + & " (if or_FIXME or_FIXME (or ~@(rest xs))))))))" + & ")"; + Repl : Environments.Ptr renames Environments.Repl; + use Ada.Command_Line; begin - Core.Add_Built_In_Functions (Repl, Eval'Unrestricted_Access); - Repl.Set (Names.Eval, Mal_Type'(Kind_Native, Atoms.No_Element, - Eval_Native'Unrestricted_Access)); - - Discard (Eval (Read ("(def! not (fn* (a) (if a false true)))"), Repl)); - Discard (Eval (Read ("(def! load-file (fn* (f) " - & "(eval (read-string (str ""(do "" (slurp f) "")"")))))"), Repl)); - Discard (Eval (Read ("(defmacro! cond " - & "(fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) " - & "(if (> (count xs) 1) (nth xs 1) " - & "(throw ""odd number of forms to cond"")) " - & "(cons 'cond (rest (rest xs)))))))"), Repl)); - Discard (Eval (Read ("(defmacro! or (fn* (& xs) " - & "(if (empty? xs) nil " - & "(if (= 1 (count xs)) (first xs) " - & "`(let* (or_FIXME ~(first xs)) " - & "(if or_FIXME or_FIXME " - & "(or ~@(rest xs))))))))"), Repl)); - - if Ada.Command_Line.Argument_Count = 0 then - Repl.Set (Names.Argv, Argv); + Core.Eval_Ref := Eval'Unrestricted_Access; + Discard (Eval (Read (Startup), Repl)); + declare + Args : Mal.T_Array (2 .. Argument_Count); + begin + for I in Args'Range loop + Args (I) := (Kind_String, ASU.To_Unbounded_String (Argument (I))); + end loop; + Repl.Set (Symbols.Constructor ("*ARGV*"), Lists.List (Args)); + end; + if Argument_Count = 0 then Interactive_Loop (Repl); else - Argv.L := Lists.Alloc (Ada.Command_Line.Argument_Count - 1); - for I in 2 .. Ada.Command_Line.Argument_Count loop - Argv.L.Replace_Element (I - 1, - Mal_Type'(Kind_String, Atoms.No_Element, - Strings.Alloc (Ada.Command_Line.Argument (I)))); - end loop; - Repl.Set (Names.Argv, Argv); - Discard (Eval (Read ("(load-file """ & Ada.Command_Line.Argument (1) - & """)"), Repl)); + Discard (Eval (Read ("(load-file """ & Argument (1) & """)"), Repl)); end if; end Step9_Try; diff --git a/ada2/stepa_mal.adb b/ada2/stepa_mal.adb index a695b6e52e..5cbae9b21a 100644 --- a/ada2/stepa_mal.adb +++ b/ada2/stepa_mal.adb @@ -1,310 +1,312 @@ -with Ada.Containers.Vectors; with Ada.Command_Line; -with Ada.Exceptions; use type Ada.Exceptions.Exception_Id; +with Ada.Exceptions; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; use type Interfaces.C.Strings.chars_ptr; -with Atoms; +with Interfaces.C.Strings; + with Core; with Environments; -with Lists; -with Names; with Printer; with Reader; -with Strings; use type Strings.Ptr; -with Types; use type Types.Kind_Type; +with Types.Functions; +with Types.Lists; +with Types.Mal; +with Types.Maps; +with Types.Symbols.Names; procedure StepA_Mal is - function Read (Source : in String) return Types.Mal_Type + package ASU renames Ada.Strings.Unbounded; + use Types; + use type Symbols.Ptr; + + function Read (Source : in String) return Mal.T renames Reader.Read_Str; - function Eval (Rec_Ast : in Types.Mal_Type; - Rec_Env : in Environments.Ptr) return Types.Mal_Type; - Unable_To_Call : exception; + function Eval (Ast0 : in Mal.T; + Env0 : in Environments.Ptr) return Mal.T; - function Quasiquote (Ast : in Types.Mal_Type; - Env : in Environments.Ptr) return Types.Mal_Type; + function Quasiquote (Ast : in Mal.T; + Env : in Environments.Ptr) return Mal.T; function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Lists.Ptr - with Inline; + Env : in Environments.Ptr) return Mal.T with Inline; -- Handle vectors and lists not starting with unquote. + -- Mergeing quote and quasiquote into eval with a flag triggering + -- a different behaviour as done for macros in step8 would improve + -- the performances significantly, but Kanaka finds that it breaks + -- too much the step structure shared by all implementations. - function Print (Ast : in Types.Mal_Type; - Print_Readably : in Boolean := True) - return Ada.Strings.Unbounded.Unbounded_String + function Print (Ast : in Mal.T; + Readably : in Boolean := True) return ASU.Unbounded_String renames Printer.Pr_Str; function Rep (Source : in String; - Env : in Environments.Ptr) - return Ada.Strings.Unbounded.Unbounded_String - is (Print (Eval (Read (Source), Env))) - with Inline; + Env : in Environments.Ptr) return ASU.Unbounded_String + is (Print (Eval (Read (Source), Env))) with Inline; - procedure Interactive_Loop (Repl : in Environments.Ptr) - with Inline; + procedure Interactive_Loop (Repl : in Environments.Ptr); - -- Convenient when the result of eval is of no interest. - procedure Discard (Ast : in Types.Mal_Type) is null; - - -- Eval, with a profile compatible with Native_Function_Access. - function Eval_Native (Args : in Types.Mal_Type_Array) return Types.Mal_Type; + function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); - package Mal_Type_Vectors is new Ada.Containers.Vectors - (Index_Type => Positive, - Element_Type => Types.Mal_Type, - "=" => Types."="); + -- Convenient when the result of eval is of no interest. + procedure Discard (Ast : in Mal.T) is null; ---------------------------------------------------------------------- - function Eval (Rec_Ast : in Types.Mal_Type; - Rec_Env : in Environments.Ptr) return Types.Mal_Type - is - use Types; - Ast : Types.Mal_Type := Rec_Ast; - Env : Environments.Ptr := Rec_Env; + function Eval (Ast0 : in Mal.T; + Env0 : in Environments.Ptr) return Mal.T is + -- Use local variables, that can be rewritten when tail call + -- optimization goes to <>. + Ast : Mal.T := Ast0; + Env : Environments.Ptr := Env0.Copy_Pointer; Macroexpanding : Boolean := False; - First : Mal_Type; + First : Mal.T; begin - <> + <> + -- Ada.Text_IO.New_Line; + -- Ada.Text_IO.Put ("EVAL: "); + -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Environments.Dump_Stack; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function | Kind_Native => - return Ast; - when Kind_Symbol => - return Env.Get (Ast.S); - + return Env.Get (Ast.Symbol); when Kind_Map => - declare - function F (X : Mal_Type) return Mal_Type is (Eval (X, Env)); - begin - return (Kind_Map, Atoms.No_Element, Ast.Map.Map (F'Access)); - end; - + return Eval_Elements (Ast.Map, Env); when Kind_Vector => - return R : constant Mal_Type := (Kind_Vector, Atoms.No_Element, - Lists.Alloc (Ast.L.Length)) - do - for I in 1 .. Ast.L.Length loop - R.L.Replace_Element (I, Eval (Ast.L.Element (I), Env)); - end loop; - end return; - + return (Kind_Vector, Eval_Elements (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); - -- Special forms - if First.Kind = Kind_Symbol then - - if First.S = Names.Def then - pragma Assert (Ast.L.Length = 3); - pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); - return R : constant Mal_Type := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).S, R); + if First.Kind /= Kind_Symbol then + -- Evaluate First, in the less frequent case where it is + -- not a symbol. + First := Eval (First, Env); + elsif First.Symbol = Symbols.Names.Def then + if Ast.L.Length /= 3 then + raise Argument_Error with "def!: expects 2 arguments"; + elsif Ast.L.Element (2).Kind /= Kind_Symbol then + raise Argument_Error with "def!: arg 1 must be a symbol"; + end if; + return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do + Env.Set (Ast.L.Element (2).Symbol, R); + end return; + elsif First.Symbol = Symbols.Names.Defmacro then + if Ast.L.Length /= 3 then + raise Argument_Error with "defmacro!: expects 2 arguments"; + elsif Ast.L.Element (2).Kind /= Kind_Symbol then + raise Argument_Error with "defmacro!: arg 1 must be a symbol"; + end if; + declare + F : constant Mal.T := Eval (Ast.L.Element (3), Env); + begin + if F.Kind /= Kind_Function then + raise Argument_Error with "defmacro!: expects a function"; + end if; + return R : constant Mal.T := F.Function_Value.New_Macro do + Env.Set (Ast.L.Element (2).Symbol, R); end return; - - elsif First.S = Names.Defmacro then - declare - pragma Assert (Ast.L.Length = 3); - pragma Assert (Ast.L.Element (2).Kind = Kind_Symbol); - F : constant Mal_Type := Eval (Ast.L.Element (3), Env); - pragma Assert (F.Kind = Kind_Function); - begin - return R : constant Mal_Type - := (Kind => Kind_Macro, - Meta => Atoms.No_Element, - Mac_Formals => F.Formals, - Mac_Expression => F.Expression) - do - Env.Set (Ast.L.Element (2).S, R); - end return; - end; - - elsif First.S = Names.Mal_Do then - for I in 2 .. Ast.L.Length - 1 loop - Discard (Eval (Ast.L.Element (I), Env)); - end loop; - Ast := Ast.L.Element (Ast.L.Length); - goto Restart; - - elsif First.S = Names.Fn then - pragma Assert (Ast.L.Length = 3); - pragma Assert - (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); - pragma Assert - (for all I in 1 .. Ast.L.Element (2).L.Length => - Ast.L.Element (2).L.Element (I).Kind = Kind_Symbol); - pragma Assert - (Ast.L.Element (2).L.Length < 1 - or else Names.Ampersand /= - Ast.L.Element (2).L.Element (Ast.L.Element (2).L.Length).S); - pragma Assert - (for all I in 1 .. Ast.L.Element (2).L.Length - 2 => - Ast.L.Element (2).L.Element (I).S /= Names.Ampersand); - return (Kind => Kind_Function, - Meta => Atoms.No_Element, - Formals => Ast.L.Element (2).L, - Expression => Atoms.Alloc (Ast.L.Element (3)), - Environment => Env); - - elsif First.S = Names.Mal_If then - declare - pragma Assert (Ast.L.Length in 3 .. 4); - Test : constant Mal_Type := Eval (Ast.L.Element (2), Env); - begin - if (case Test.Kind is - when Kind_Nil => False, - when Kind_Boolean => Test.Boolean_Value, - when others => True) - then - Ast := Ast.L.Element (3); - goto Restart; - elsif Ast.L.Length = 3 then - return (Kind_Nil, Atoms.No_Element); - else - Ast := Ast.L.Element (4); - goto Restart; - end if; - end; - - elsif First.S = Names.Let then - declare - pragma Assert (Ast.L.Length = 3); - pragma Assert - (Ast.L.Element (2).Kind in Kind_List | Kind_Vector); - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; - pragma Assert (Bindings.Length mod 2 = 0); - begin - Env.Replace_With_Subenv; - Env.Increase_Capacity (Bindings.Length / 2); - for I in 1 .. Bindings.Length / 2 loop - pragma Assert - (Bindings.Element (2 * I - 1).Kind = Kind_Symbol); - Env.Set (Bindings.Element (2 * I - 1).S, - Eval (Bindings.Element (2 * I), Env)); - end loop; + end; + elsif First.Symbol = Symbols.Names.Mal_Do then + if Ast.L.Length = 1 then + raise Argument_Error with "do: expects at least 1 argument"; + end if; + for I in 2 .. Ast.L.Length - 1 loop + Discard (Eval (Ast.L.Element (I), Env)); + end loop; + Ast := Ast.L.Element (Ast.L.Length); + goto Restart; + elsif First.Symbol = Symbols.Names.Fn then + if Ast.L.Length /= 3 then + raise Argument_Error with "fn*: expects 3 arguments"; + elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "fn*: arg 1 must be a list or vector"; + elsif (for some F in 1 .. Ast.L.Element (2).L.Length => + Ast.L.Element (2).L.Element (F).Kind /= Kind_Symbol) + then + raise Argument_Error with "fn*: arg 2 must contain symbols"; + end if; + return Functions.New_Function (Ast.L.Element (2).L, + Ast.L.Element (3), Env.New_Closure); + elsif First.Symbol = Symbols.Names.Mal_If then + if Ast.L.Length not in 3 .. 4 then + raise Argument_Error with "if: expects 2 or 3 arguments"; + end if; + declare + Test : constant Mal.T := Eval (Ast.L.Element (2), Env); + begin + if (case Test.Kind is + when Kind_Nil => False, + when Kind_Boolean => Test.Ada_Boolean, + when others => True) + then Ast := Ast.L.Element (3); goto Restart; - end; - - elsif First.S = Names.Macroexpand then - pragma Assert (Ast.L.Length = 2); - Macroexpanding := True; + elsif Ast.L.Length = 3 then + return Mal.Nil; + else + Ast := Ast.L.Element (4); + goto Restart; + end if; + end; + elsif First.Symbol = Symbols.Names.Let then + if Ast.L.Length /= 3 then + raise Argument_Error with "let*: expects 3 arguments"; + elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "let*: expects a list or vector"; + end if; + declare + Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + begin + if Bindings.Length mod 2 /= 0 then + raise Argument_Error with "let*: odd number of bindings"; + end if; + Env.Replace_With_Sub; + for I in 1 .. Bindings.Length / 2 loop + if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then + raise Argument_Error with "let*: keys must be symbols"; + end if; + Env.Set (Bindings.Element (2 * I - 1).Symbol, + Eval (Bindings.Element (2 * I), Env)); + end loop; + Ast := Ast.L.Element (3); + goto Restart; + end; + elsif First.Symbol = Symbols.Names.Macroexpand then + if Ast.L.Length /= 2 then + raise Argument_Error with "macroexpand: expects 1 argument"; + end if; + Macroexpanding := True; + Ast := Ast.L.Element (2); + goto Restart; + elsif First.Symbol = Symbols.Names.Quasiquote then + if Ast.L.Length /= 2 then + raise Argument_Error with "quasiquote: expects 1 argument"; + end if; + return Quasiquote (Ast.L.Element (2), Env); + elsif First.Symbol = Symbols.Names.Quote then + if Ast.L.Length /= 2 then + raise Argument_Error with "quote: expects 1 argument"; + end if; + return Ast.L.Element (2); + elsif First.Symbol = Symbols.Names.Try then + if Ast.L.Length = 2 then Ast := Ast.L.Element (2); goto Restart; - - elsif First.S = Names.Quote then - pragma Assert (Ast.L.Length = 2); - return Ast.L.Element (2); - - elsif First.S = Names.Quasiquote then - pragma Assert (Ast.L.Length = 2); - return Quasiquote (Ast.L.Element (2), Env); - - elsif First.S = Names.Try then - declare - pragma Assert (Ast.L.Length = 3); - pragma Assert (Ast.L.Element (3).Kind = Kind_List); - A3 : constant Lists.Ptr := Ast.L.Element (3).L; - pragma Assert (A3.Length = 3); - pragma Assert (A3.Element (1).Kind = Kind_Symbol); - pragma Assert (A3.Element (1).S = Names.Catch); - pragma Assert (A3.Element (2).Kind = Kind_Symbol); + elsif Ast.L.Length /= 3 then + raise Argument_Error with "try*: expects 1 or 2 arguments"; + elsif Ast.L.Element (3).Kind /= Kind_List then + raise Argument_Error with "try*: argument 2 must be a list"; + end if; + declare + A3 : constant Lists.Ptr := Ast.L.Element (3).L; + begin + if A3.Length /= 3 then + raise Argument_Error with "try*: arg 2 must have 3 elements"; + elsif A3.Element (1).Kind /= Kind_Symbol + or else A3.Element (1).Symbol /= Symbols.Names.Catch + then + raise Argument_Error with "try*: arg 2 must be a catch*"; + elsif A3.Element (2).Kind /= Kind_Symbol then + raise Argument_Error with "catch*: expects a symbol"; + end if; begin return Eval (Ast.L.Element (2), Env); exception - when E : others => - Env.Replace_With_Subenv; - if Ada.Exceptions.Exception_Identity (E) - = Core.Exception_Throwed'Identity - then - Env.Set (A3.Element (2).S, Core.Last_Exception); - Core.Last_Exception := (Kind_Nil, Atoms.No_Element); - else - Env.Set (A3.Element (2).S, Mal_Type' - (Kind_String, Atoms.No_Element, Strings.Alloc - (Ada.Exceptions.Exception_Message (E)))); - end if; + when E : Reader.Empty_Source | Argument_Error + | Reader.Reader_Error | Environments.Unknown_Key => + Env.Replace_With_Sub; + Env.Set (A3.Element (2).Symbol, + Mal.T'(Kind_String, ASU.To_Unbounded_String + (Ada.Exceptions.Exception_Message (E)))); Ast := A3.Element (3); goto Restart; + when Core.Exception_Throwed => + Env.Replace_With_Sub; + Env.Set (A3.Element (2).Symbol, Core.Last_Exception); + Core.Last_Exception := Mal.Nil; + Ast := A3.Element (3); + goto Restart; + -- Other exceptions are unexpected. end; - end if; + end; + else + -- Equivalent to First := Eval (First, Env), except that + -- we already know enough to spare a recursive call in + -- this frequent case. + First := Env.Get (First.Symbol); end if; - - -- No special form has been found, attempt to apply the - -- first element to the rest of the list. - declare - Args : Mal_Type_Array (2 .. Ast.L.Length); - begin - First := Eval (First, Env); - case First.Kind is - - when Kind_Native => + -- Apply phase. + case First.Kind is + when Kind_Builtin => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin for I in Args'Range loop Args (I) := Eval (Ast.L.Element (I), Env); end loop; - return First.Native.all (Args); - - when Kind_Function => + return First.Builtin.all (Args); + end; + when Kind_Builtin_With_Meta => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin for I in Args'Range loop Args (I) := Eval (Ast.L.Element (I), Env); end loop; - Env := Environments.Alloc (Outer => First.Environment); - Env.Set_Binds (First.Formals, Args); - Ast := First.Expression.Deref; - goto Restart; - - when Kind_Macro => + return First.Builtin_With_Meta.Data.all (Args); + end; + when Kind_Function => + declare + Args : Mal.T_Array (2 .. Ast.L.Length); + begin for I in Args'Range loop - Args (I) := Ast.L.Element (I); + Args (I) := Eval (Ast.L.Element (I), Env); end loop; - declare - New_Env : constant Environments.Ptr - := Environments.Alloc (Outer => Env); - begin - New_Env.Set_Binds (First.Mac_Formals, Args); - Ast := Eval (First.Mac_Expression.Deref, New_Env); - end; - if Macroexpanding then - return Ast; - end if; + Env.Replace_With_Sub (First.Function_Value.Closure); + First.Function_Value.Set_Binds (Env, Args); + Ast := First.Function_Value.Expression; goto Restart; - - when others => - raise Unable_To_Call - with Ada.Strings.Unbounded.To_String (Print (First)); - end case; - end; + end; + when Kind_Macro => + declare + New_Env : constant Environments.Ptr := Env.Sub; + begin + First.Function_Value.Set_Binds (New_Env, Ast.L); + Ast := Eval (First.Function_Value.Expression, New_Env); + end; + if Macroexpanding then + return Ast; + end if; + goto Restart; + when others => + raise Argument_Error + with "cannot execute " & ASU.To_String (Print (First)); + end case; + when others => + return Ast; end case; end Eval; - procedure Interactive_Loop (Repl : in Environments.Ptr) - is - - function Readline (Prompt : in Interfaces.C.char_array) - return Interfaces.C.Strings.chars_ptr + procedure Interactive_Loop (Repl : in Environments.Ptr) is + use Interfaces.C, Interfaces.C.Strings; + function Readline (Prompt : in char_array) return chars_ptr with Import, Convention => C, External_Name => "readline"; - - procedure Add_History (Line : in Interfaces.C.Strings.chars_ptr) + procedure Add_History (Line : in chars_ptr) with Import, Convention => C, External_Name => "add_history"; - - procedure Free (Line : in Interfaces.C.Strings.chars_ptr) + procedure Free (Line : in chars_ptr) with Import, Convention => C, External_Name => "free"; - - Prompt : constant Interfaces.C.char_array - := Interfaces.C.To_C ("user> "); - C_Line : Interfaces.C.Strings.chars_ptr; + Prompt : constant char_array := To_C ("user> "); + C_Line : chars_ptr; begin loop C_Line := Readline (Prompt); - exit when C_Line = Interfaces.C.Strings.Null_Ptr; + exit when C_Line = Null_Ptr; declare - Line : constant String := Interfaces.C.Strings.Value (C_Line); + Line : constant String := Value (C_Line); begin if Line /= "" then Add_History (C_Line); @@ -314,104 +316,99 @@ procedure StepA_Mal is exception when Reader.Empty_Source => null; - when E : others => + when E : Argument_Error | Reader.Reader_Error + | Environments.Unknown_Key => Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- but go on proceeding. + when Core.Exception_Throwed => + Ada.Text_IO.Put ("User exception: "); + Ada.Text_IO.Unbounded_IO.Put_Line (Print (Core.Last_Exception)); + Core.Last_Exception := Mal.Nil; + -- Other exceptions are unexpected. end; end loop; Ada.Text_IO.New_Line; end Interactive_Loop; - function Quasiquote (Ast : in Types.Mal_Type; - Env : in Environments.Ptr) return Types.Mal_Type + function Quasiquote (Ast : in Mal.T; + Env : in Environments.Ptr) return Mal.T is (case Ast.Kind is - when Types.Kind_Vector => - (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env)), - -- When the test is updated, replace Kind_List with Kind_Vector. - when Types.Kind_List => + when Kind_Vector => Quasiquote (Ast.L, Env), + -- When the test is updated, replace Kind_List with Kind_Vector. + when Kind_List => (if 0 < Ast.L.Length - and then Ast.L.Element (1).Kind = Types.Kind_Symbol - and then Ast.L.Element (1).S = Names.Unquote + and then Ast.L.Element (1).Kind = Kind_Symbol + and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote then Eval (Ast.L.Element (2), Env) - else (Types.Kind_List, Atoms.No_Element, Quasiquote (Ast.L, Env))), + else Quasiquote (Ast.L, Env)), when others => Ast); function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Lists.Ptr - is - use Types; - Buffer : Mal_Type_Vectors.Vector; - Elt : Mal_Type; + Env : in Environments.Ptr) return Mal.T is + -- The final return concatenates these lists. + R : Mal.T_Array (1 .. List.Length); begin - for I in 1 .. List.Length loop - Elt := List.Element (I); - if Elt.Kind in Kind_List | Kind_Vector - and then 0 < Elt.L.Length - and then Elt.L.Element (1).Kind = Kind_Symbol - and then Elt.L.Element (1).S = Names.Splice_Unquote + for I in R'Range loop + R (I) := List.Element (I); + if R (I).Kind in Kind_List | Kind_Vector + and then 0 < R (I).L.Length + and then R (I).L.Element (1).Kind = Kind_Symbol + and then R (I).L.Element (1).Symbol = Symbols.Names.Splice_Unquote then - pragma Assert (Elt.L.Length = 2); - Elt := Eval (Elt.L.Element (2), Env); - pragma Assert (Elt.Kind = Kind_List); - for J in 1 .. Elt.L.Length loop - Buffer.Append (Elt.L.Element (J)); - end loop; + if R (I).L.Length /= 2 then + raise Argument_Error with "splice-unquote: expects 1 argument"; + end if; + R (I) := Eval (R (I).L.Element (2), Env); + if R (I).Kind /= Kind_List then + raise Argument_Error with "splice-unquote: expects a list"; + end if; else - Buffer.Append (Quasiquote (Elt, Env)); + R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), Env))); end if; end loop; - return R : constant Lists.Ptr := Lists.Alloc (Natural (Buffer.Length)) do - for I in 1 .. R.Length loop - R.Replace_Element (I, Buffer.Element (I)); - end loop; - end return; + return Lists.Concat (R); end Quasiquote; ---------------------------------------------------------------------- - use Types; - Argv : Mal_Type (Kind_List); - Repl : constant Environments.Ptr := Environments.Alloc; - function Eval_Native (Args : in Mal_Type_Array) return Mal_Type is - (Eval (Args (Args'First), Repl)); + Startup : constant String := "(do" + & "(def! not (fn* (a) (if a false true)))" + & "(def! load-file (fn* (f)" + & " (eval (read-string (str ""(do "" (slurp f) "")"")))))" + & "(defmacro! cond (fn* (& xs)" + & " (if (> (count xs) 0)" + & " (list 'if (first xs)" + & " (if (> (count xs) 1) (nth xs 1)" + & " (throw ""odd number of forms to cond""))" + & " (cons 'cond (rest (rest xs)))))))" + & "(def! *gensym-counter* (atom 0))" + & "(def! gensym (fn* [] " + & " (symbol (str ""G__"" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))" + & "(defmacro! or (fn* (& xs)" + & " (if (empty? xs) nil" + & " (if (= 1 (count xs)) (first xs)" + & " (let* (condvar (gensym))" + & " `(let* (~condvar ~(first xs))" + & " (if ~condvar ~condvar (or ~@(rest xs)))))))))" + & "(def! *host-language* ""ada2"")" + & ")"; + Repl : Environments.Ptr renames Environments.Repl; + use Ada.Command_Line; begin - Core.Add_Built_In_Functions (Repl, Eval'Unrestricted_Access); - Repl.Set (Names.Eval, Mal_Type'(Kind_Native, Atoms.No_Element, - Eval_Native'Unrestricted_Access)); - - Discard (Eval (Read ("(def! not (fn* (a) (if a false true)))"), Repl)); - Discard (Eval (Read ("(def! load-file (fn* (f) " - & "(eval (read-string (str ""(do "" (slurp f) "")"")))))"), Repl)); - Discard (Eval (Read ("(defmacro! cond " - & "(fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) " - & "(if (> (count xs) 1) (nth xs 1) " - & "(throw ""odd number of forms to cond"")) " - & "(cons 'cond (rest (rest xs)))))))"), Repl)); - Discard (Eval (Read ("(def! *gensym-counter* (atom 0))"), Repl)); - Discard (Eval (Read ("(def! gensym (fn* [] (symbol (str ""G__"" " - & "(swap! *gensym-counter* (fn* [x] (+ 1 x)))))))"), Repl)); - Discard (Eval (Read ("(defmacro! or (fn* (& xs) (if (empty? xs) nil " - & "(if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) " - & "`(let* (~condvar ~(first xs)) (if ~condvar ~condvar " - & "(or ~@(rest xs)))))))))"), Repl)); - - Repl.Set (Names.Host_Language, - Mal_Type'(Kind_Symbol, Atoms.No_Element, Names.Ada2)); - - if Ada.Command_Line.Argument_Count = 0 then - Repl.Set (Names.Argv, Argv); + Core.Eval_Ref := Eval'Unrestricted_Access; + Discard (Eval (Read (Startup), Repl)); + declare + Args : Mal.T_Array (2 .. Argument_Count); + begin + for I in Args'Range loop + Args (I) := (Kind_String, ASU.To_Unbounded_String (Argument (I))); + end loop; + Repl.Set (Symbols.Constructor ("*ARGV*"), Lists.List (Args)); + end; + if Argument_Count = 0 then Discard (Eval (Read ("(println (str ""Mal ["" *host-language* ""]""))"), Repl)); Interactive_Loop (Repl); else - Argv.L := Lists.Alloc (Ada.Command_Line.Argument_Count - 1); - for I in 2 .. Ada.Command_Line.Argument_Count loop - Argv.L.Replace_Element (I - 1, - Mal_Type'(Kind_String, Atoms.No_Element, - Strings.Alloc (Ada.Command_Line.Argument (I)))); - end loop; - Repl.Set (Names.Argv, Argv); - Discard (Eval (Read ("(load-file """ & Ada.Command_Line.Argument (1) - & """)"), Repl)); + Discard (Eval (Read ("(load-file """ & Argument (1) & """)"), Repl)); end if; end StepA_Mal; diff --git a/ada2/strings.adb b/ada2/strings.adb deleted file mode 100644 index 6cee7acf03..0000000000 --- a/ada2/strings.adb +++ /dev/null @@ -1,62 +0,0 @@ -with Ada.Strings.Hash; - -package body Strings is - - Dict : Sets.Set; - Empty_Hash : constant Ada.Containers.Hash_Type := Ada.Strings.Hash (""); - - ---------------------------------------------------------------------- - - procedure Adjust (Object : in out Ptr) is - begin - if Sets.Has_Element (Object.Position) then - Dict (Object.Position).Refs := Dict (Object.Position).Refs + 1; - end if; - end Adjust; - - function Alloc (Source : in String) return Ptr - is - Inserted : Boolean; - Position : Sets.Cursor; - begin - if Source /= "" then - Sets.Insert (Dict, - (Data => Source, - Hash => Ada.Strings.Hash (Source), - Last => Source'Length, - Refs => 1), - Position, - Inserted); - if not Inserted then - Dict (Position).Refs := Dict (Position).Refs + 1; - end if; - end if; - return (Ada.Finalization.Controlled with Position => Position); - end Alloc; - - function Deref (Source : in Ptr) return String is - (if Sets.Has_Element (Source.Position) - then Dict (Source.Position).Data - else ""); - - procedure Finalize (Object : in out Ptr) - is - Refs : Positive; - begin - if Sets.Has_Element (Object.Position) then - Refs := Dict (Object.Position).Refs; - if 1 < Refs then - Dict (Object.Position).Refs := Refs - 1; - Object.Position := Sets.No_Element; - else - Sets.Delete (Dict, Object.Position); - end if; - end if; - end Finalize; - - function Hash (Source : in Ptr) return Ada.Containers.Hash_Type is - (if Sets.Has_Element (Source.Position) - then Dict (Source.Position).Hash - else Empty_Hash); - -end Strings; diff --git a/ada2/strings.ads b/ada2/strings.ads deleted file mode 100644 index aeb8222725..0000000000 --- a/ada2/strings.ads +++ /dev/null @@ -1,65 +0,0 @@ -with Ada.Containers; -private with Ada.Containers.Indefinite_Hashed_Sets; -private with Ada.Finalization; - -package Strings is - - pragma Elaborate_Body; - - -- An abstraction similar to Ada.Strings.Unbounded, except that - -- the type is immutable, and that only one instance is allocated - -- with a given content. This avoids many allocations and - -- deallocations, since symbols and keywords are expected to be - -- used many times. Using this for all strings even if they are - -- not used as keys in maps should not hurt. - - -- As a side effect, some frequent string comparisons (with "def!" - -- or "fn*" for example) will become a bit more efficient because - -- comparing pointers is faster than comparing strings. - - type Ptr is tagged private; - Empty_String : constant Ptr; -- The default value. - - function Alloc (Source : in String) return Ptr; - - function Deref (Source : in Ptr) return String - with Inline; - - -- We make the hash value visible so that environments and maps do - -- not need to recompute it. - function Hash (Source : in Ptr) return Ada.Containers.Hash_Type - with Inline; - -private - - type Element_Type (Last : Positive) is record - Data : String (1 .. Last); - Hash : Ada.Containers.Hash_Type; - Refs : Positive; - end record; - - function Hash (Element : Element_Type) return Ada.Containers.Hash_Type - is (Element.Hash) - with Inline; - - function Equivalent_Elements (Left, Right : Element_Type) return Boolean - is (Left.Data = Right.Data) - with Inline; - - package Sets is new Ada.Containers.Indefinite_Hashed_Sets - (Element_Type => Element_Type, - Hash => Hash, - Equivalent_Elements => Equivalent_Elements, - "=" => "="); - - type Ptr is new Ada.Finalization.Controlled with record - Position : Sets.Cursor := Sets.No_Element; - end record; - overriding procedure Adjust (Object : in out Ptr) with Inline; - overriding procedure Finalize (Object : in out Ptr) with Inline; - -- Predefined equality is fine. - - Empty_String : constant Ptr - := (Ada.Finalization.Controlled with Position => Sets.No_Element); - -end Strings; diff --git a/ada2/types-atoms.adb b/ada2/types-atoms.adb new file mode 100644 index 0000000000..6f0afb76ed --- /dev/null +++ b/ada2/types-atoms.adb @@ -0,0 +1,62 @@ +with Ada.Unchecked_Deallocation; + +with Types.Mal; + +package body Types.Atoms is + + type Rec is limited record + Refs : Natural; + Data : Mal.T; + end record; + + procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); + + ---------------------------------------------------------------------- + + procedure Adjust (Object : in out Ptr) is + begin + Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + end Adjust; + + function Atom (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 1 then + raise Argument_Error with "atom: expects 1 argument" + else + (Kind => Kind_Atom, + Atom => (Ada.Finalization.Controlled with + Ref => new Rec'(Data => Args (Args'First), + Refs => 1)))); + + function Deref (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 1 then + raise Argument_Error with "deref: expects 1 argument" + elsif Args (Args'First).Kind /= Kind_Atom then + raise Argument_Error with "deref: expects an atom" + else + (Args (Args'First).Atom.Ref.all.Data)); + + procedure Finalize (Object : in out Ptr) is + begin + if Object.Ref /= null and then 0 < Object.Ref.all.Refs then + Object.Ref.all.Refs := Object.Ref.all.Refs - 1; + if 0 < Object.Ref.all.Refs then + Object.Ref := null; + else + Free (Object.Ref); + end if; + end if; + end Finalize; + + function Reset (Args : in Mal.T_Array) return Mal.T is + begin + if Args'Length /= 2 then + raise Argument_Error with "reset: expects 2 arguments"; + elsif Args (Args'First).Kind /= Kind_Atom then + raise Argument_Error with "reset: first argument must be an atom"; + else + Args (Args'First).Atom.Ref.all.Data := Args (Args'Last); + return Args (Args'Last); + end if; + end Reset; + +end Types.Atoms; diff --git a/ada2/types-atoms.ads b/ada2/types-atoms.ads new file mode 100644 index 0000000000..2ed928abc0 --- /dev/null +++ b/ada2/types-atoms.ads @@ -0,0 +1,35 @@ +private with Ada.Finalization; + +limited with Types.Mal; + +package Types.Atoms is + + type Ptr is private; + -- A wrapper for a pointer counting references. + + -- The default value is invalid, new variables must be assigned + -- immediately (a hidden discriminant would prevent this type to + -- become a field inside Types.Mal.T, so we check this with a + -- private invariant a fallback, an invariant in the private part + -- checks that any created object is affected before use. + + -- Assignment give another reference to the same storage. + + -- Built-in functions. + function Atom (Args : in Mal.T_Array) return Mal.T; + function Deref (Args : in Mal.T_Array) return Mal.T; + function Reset (Args : in Mal.T_Array) return Mal.T; + +private + + type Rec; + type Acc is access Rec; + type Ptr is new Ada.Finalization.Controlled with record + Ref : Acc := null; + end record + with Invariant => Ref /= null; + overriding procedure Adjust (Object : in out Ptr) with Inline; + overriding procedure Finalize (Object : in out Ptr) with Inline; + pragma Finalize_Storage_Only (Ptr); + +end Types.Atoms; diff --git a/ada2/types-builtins.adb b/ada2/types-builtins.adb new file mode 100644 index 0000000000..fa939a7da2 --- /dev/null +++ b/ada2/types-builtins.adb @@ -0,0 +1,53 @@ +with Ada.Unchecked_Deallocation; + +with Types.Mal; + +package body Types.Builtins is + + type Rec is limited record + Data : Ptr; + Refs : Natural; + Meta : Mal.T; + end record; + + procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); + + ---------------------------------------------------------------------- + + procedure Adjust (Object : in out Ptr_With_Meta) is + begin + Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + end Adjust; + + function Data (Item : in Ptr_With_Meta) return Ptr + is (Item.Ref.all.Data); + + procedure Finalize (Object : in out Ptr_With_Meta) is + begin + if Object.Ref /= null and then 0 < Object.Ref.all.Refs then + Object.Ref.all.Refs := Object.Ref.all.Refs - 1; + if 0 < Object.Ref.all.Refs then + Object.Ref := null; + else + Free (Object.Ref); + end if; + end if; + end Finalize; + + function Meta (Item : in Ptr_With_Meta) return Mal.T + is (Item.Ref.all.Meta); + + function With_Meta (Data : in Ptr; + Meta : in Mal.T) return Mal.T + is (Kind_Builtin_With_Meta, (Ada.Finalization.Controlled with new Rec' + (Data => Data, + Meta => Meta, + Refs => 1))); + + function With_Meta (Data : in Ptr_With_Meta; + Meta : in Mal.T) return Mal.T + -- Do not try to reuse the memory. We can hope that this kind of + -- nonsense will be rare. + is (With_Meta (Data.Data, Meta)); + +end Types.Builtins; diff --git a/ada2/types-builtins.ads b/ada2/types-builtins.ads new file mode 100644 index 0000000000..abb118e30b --- /dev/null +++ b/ada2/types-builtins.ads @@ -0,0 +1,46 @@ +private with Ada.Finalization; + +limited with Types.Mal; + +package Types.Builtins is + + type Ptr is access function (Args : in Mal.T_Array) return Mal.T; + -- This access type is efficient and sufficient for most purposes, + -- as counting references is a waste of time for native functions, + -- which are often used as atomic elements. The controlled type + -- below is only useful when one has the silly idea to add + -- metadata to a built-in. + + type Ptr_With_Meta is tagged private; + -- A wrapper for a pointer counting references. + + -- The default value is invalid, new variables must be assigned + -- immediately (a hidden discriminant would prevent this type to + -- become a field inside Types.Mal.T, so we check this with a + -- private invariant a fallback, an invariant in the private part + -- checks that any created object is affected before use. + + -- Assignment give another reference to the same storage. + + function With_Meta (Data : in Ptr; + Meta : in Mal.T) return Mal.T with Inline; + function With_Meta (Data : in Ptr_With_Meta; + Meta : in Mal.T) return Mal.T with Inline; + function Meta (Item : in Ptr_With_Meta) return Mal.T with Inline; + function Data (Item : in Ptr_With_Meta) return Ptr with Inline; + +private + + -- See README for the implementation of reference counting. + + type Rec; + type Acc is access Rec; + type Ptr_With_Meta is new Ada.Finalization.Controlled with record + Ref : Acc := null; + end record + with Invariant => Ref /= null; + overriding procedure Adjust (Object : in out Ptr_With_Meta) with Inline; + overriding procedure Finalize (Object : in out Ptr_With_Meta) with Inline; + pragma Finalize_Storage_Only (Ptr_With_Meta); + +end Types.Builtins; diff --git a/ada2/types-functions.adb b/ada2/types-functions.adb new file mode 100644 index 0000000000..5360d83144 --- /dev/null +++ b/ada2/types-functions.adb @@ -0,0 +1,171 @@ +with Ada.Strings.Unbounded; +with Ada.Unchecked_Deallocation; + +with Environments; +with Printer; +with Types.Lists; +with Types.Mal; +with Types.Symbols.Names; + +package body Types.Functions is + + subtype AFC is Ada.Finalization.Controlled; + package ASU renames Ada.Strings.Unbounded; + use type Types.Symbols.Ptr; + + type Rec is limited record + Refs : Natural := 1; + Args : Lists.Ptr; + Expr : Mal.T; + Env : Environments.Closure_Ptr; + Varargs : Boolean; + Meta : Mal.T := Mal.Nil; + end record; + + procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); + + ---------------------------------------------------------------------- + + procedure Adjust (Object : in out Ptr) is + begin + Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + end Adjust; + + function Closure (Item : in Ptr) return Environments.Closure_Ptr + is (Item.Ref.all.Env); + + function Expression (Item : in Ptr) return Mal.T + is (Item.Ref.all.Expr); + + procedure Finalize (Object : in out Ptr) is + begin + if Object.Ref /= null and then 0 < Object.Ref.all.Refs then + Object.Ref.all.Refs := Object.Ref.all.Refs - 1; + if 0 < Object.Ref.all.Refs then + Object.Ref := null; + else + Free (Object.Ref); + end if; + end if; + end Finalize; + + function Formals (Item : in Ptr) return Lists.Ptr + is (Item.Ref.all.Args); + + function Meta (Item : in Ptr) return Mal.T + is (Item.Ref.all.Meta); + + function New_Function (Formals : in Lists.Ptr; + Expression : in Mal.T; + Environment : in Environments.Closure_Ptr) + return Mal.T + is (Kind_Function, + (AFC with new Rec' + (Args => Formals, + Expr => Expression, + Env => Environment, + Varargs => 1 < Formals.Length + and then Formals.Element (Formals.Length - 1).Symbol + = Symbols.Names.Ampersand, + others => <>))); + + function New_Macro (Item : in Ptr) return Mal.T is + Old : Rec renames Item.Ref.all; + Ref : Acc; + begin + pragma Assert (0 < Old.Refs); + if Old.Refs = 1 then + Ref := Item.Ref; + Old.Refs := 2; + Old.Env := Environments.Null_Closure; + -- Finalize the previous closure. + Old.Meta := Mal.Nil; + else + Ref := new Rec'(Args => Item.Ref.all.Args, + Expr => Item.Ref.all.Expr, + Varargs => Item.Ref.all.Varargs, + others => <>); + end if; + return (Kind_Macro, (AFC with Ref)); + end New_Macro; + + procedure Set_Binds (Item : in Ptr; + Env : in Environments.Ptr; + Args : in Mal.T_Array) is + R : Rec renames Item.Ref.all; + begin + if R.Varargs then + if Args'Length < R.Args.Length - 2 then + raise Argument_Error with "expected " + & ASU.To_String (Printer.Pr_Str ((Kind_List, R.Args))) + & ", got" & Args'Length'Img; + end if; + for I in 1 .. R.Args.Length - 2 loop + Env.Set (R.Args.Element (I).Symbol, Args (Args'First + I - 1)); + end loop; + Env.Set (R.Args.Element (R.Args.Length).Symbol, + Lists.List (Args (Args'First + R.Args.Length - 2 .. Args'Last))); + else + if Args'Length /= R.Args.Length then + raise Argument_Error with "expected " + & ASU.To_String (Printer.Pr_Str ((Kind_List, R.Args))) + & ", got" & Args'Length'Img; + end if; + for I in 1 .. R.Args.Length loop + Env.Set (R.Args.Element (I).Symbol, Args (Args'First + I - 1)); + end loop; + end if; + end Set_Binds; + + procedure Set_Binds (Item : in Ptr; + Env : in Environments.Ptr; + Args : in Lists.Ptr) is + R : Rec renames Item.Ref.all; + begin + if R.Varargs then + if Args.Length - 1 < R.Args.Length - 2 then + raise Argument_Error with "expected " + & ASU.To_String (Printer.Pr_Str ((Kind_List, R.Args))) + & ", got" & Natural'Image (Args.Length - 1); + end if; + for I in 1 .. R.Args.Length - 2 loop + Env.Set (R.Args.Element (I).Symbol, Args.Element (1 + I)); + end loop; + Env.Set (R.Args.Element (R.Args.Length).Symbol, + Lists.Slice (Args, R.Args.Length)); + else + if Args.Length - 1 /= R.Args.Length then + raise Argument_Error with "expected " + & ASU.To_String (Printer.Pr_Str ((Kind_List, R.Args))) + & ", got" & Natural'Image (Args.Length - 1); + end if; + for I in 1 .. R.Args.Length loop + Env.Set (R.Args.Element (I).Symbol, Args.Element (1 + I)); + end loop; + end if; + end Set_Binds; + + function With_Meta (Data : in Ptr; + Meta : in Mal.T) + return Mal.T is + Old : Rec renames Data.Ref.all; + Ref : Acc; + begin + pragma Assert (0 < Old.Refs); + if Old.Refs = 1 then + Ref := Data.Ref; + Old.Refs := 2; + Old.Meta := Meta; + else + Ref := new Rec'(Args => Data.Ref.all.Args, + Expr => Data.Ref.all.Expr, + Env => Data.Ref.all.Env, + Varargs => Data.Ref.all.Varargs, + Meta => Meta, + others => <>); + + end if; + return (Kind_Function, (AFC with Ref)); + end With_Meta; + +end Types.Functions; diff --git a/ada2/types-functions.ads b/ada2/types-functions.ads new file mode 100644 index 0000000000..35aabee2c3 --- /dev/null +++ b/ada2/types-functions.ads @@ -0,0 +1,67 @@ +private with Ada.Finalization; + +limited with Environments; +limited with Types.Lists; +limited with Types.Mal; + +package Types.Functions is + + type Ptr is tagged private; + -- A wrapper for a pointer counting references. + + -- The default value is invalid, new variables must be assigned + -- immediately (a hidden discriminant would prevent this type to + -- become a field inside Types.Mal.T, so we check this with a + -- private invariant a fallback, an invariant in the private part + -- checks that any created object is affected before use. + + -- Assignment give another reference to the same storage. + + function New_Function (Formals : in Lists.Ptr; + Expression : in Mal.T; + Environment : in Environments.Closure_Ptr) + return Mal.T + with Inline; + + -- Equivalent to a sequence of Set with the formal parameters and + -- Args elements, except for the handling of "&". + -- May raise Argument_Count. + -- For functions. + procedure Set_Binds (Item : in Ptr; + Env : in Environments.Ptr; + Args : in Mal.T_Array); + + function New_Macro (Item : in Ptr) return Mal.T with Inline; + -- Set_Binds for macros. + -- It skips the first element of Args. + procedure Set_Binds (Item : in Ptr; + Env : in Environments.Ptr; + Args : in Lists.Ptr); + + -- Used when printing, or applying with specific requirements, + -- like allowing tail call optimization or macros. + function Formals (Item : in Ptr) return Lists.Ptr with Inline; + function Expression (Item : in Ptr) return Mal.T with Inline; + function Closure (Item : in Ptr) return Environments.Closure_Ptr + with Inline; + + function Meta (Item : in Ptr) return Mal.T with inline; + function With_Meta (Data : in Ptr; + Meta : in Mal.T) + return Mal.T with Inline; + +private + + -- See README for the implementation of reference counting. + + type Rec; + type Acc is access Rec; + type Ptr is new Ada.Finalization.Controlled with record + Ref : Acc := null; + end record + with Invariant => Ref /= null; + overriding procedure Adjust (Object : in out Ptr) with Inline; + overriding procedure Finalize (Object : in out Ptr) with Inline; + pragma Finalize_Storage_Only (Ptr); + +end Types.Functions; diff --git a/ada2/types-lists.adb b/ada2/types-lists.adb new file mode 100644 index 0000000000..a20ba3e8a6 --- /dev/null +++ b/ada2/types-lists.adb @@ -0,0 +1,260 @@ +with Ada.Unchecked_Deallocation; + +with Types.Mal; + +package body Types.Lists is + + subtype AFC is Ada.Finalization.Controlled; + use type Mal.T_Array; + + type Rec (Last : Natural) is limited record + Refs : Natural := 1; + Meta : Mal.T := Mal.Nil; + Data : Mal.T_Array (1 .. Last) := (others => Mal.Nil); + end record; + + procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); + + ---------------------------------------------------------------------- + + function "=" (Left, Right : in Ptr) return Boolean is + -- Should become Left.Ref.all.Data = Right.Ref.all.Data when + -- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89178 is fixed. + use type Mal.T; + L : Rec renames Left.Ref.all; + R : Rec renames Right.Ref.all; + begin + return L.Last = R.Last + and then (for all I in 1 .. L.Last => L.Data (I) = R.Data (I)); + end "="; + + function "&" (Left : in Mal.T_Array; + Right : in Ptr) return Mal.T_Array + is (Left & Right.Ref.all.Data); + + procedure Adjust (Object : in out Ptr) is + begin + Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + end Adjust; + + function Concat (Args : in Mal.T_Array) return Mal.T is + Sum : Natural := 0; + Ref : Acc; + begin + for Arg of Args loop + if Arg.Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "concat: expects lists or vectors"; + end if; + Sum := Sum + Arg.L.Ref.all.Last; + end loop; + Ref := new Rec (Sum); + for Arg of reverse Args loop + Ref.all.Data (Sum - Arg.L.Ref.all.Last + 1 .. Sum) + := Arg.L.Ref.all.Data; + Sum := Sum - Arg.L.Ref.all.Last; + end loop; + pragma Assert (Sum = 0); + return (Kind_List, (AFC with Ref)); + end Concat; + + function Conj (Args : in Mal.T_Array) return Mal.T is + Ref : Acc; + begin + if Args'Length = 0 then + raise Argument_Error with "conj: expects at least 1 argument"; + end if; + case Args (Args'First).Kind is + when Kind_List => + Ref := new Rec + (Args'Length - 1 + Args (Args'First).L.Ref.all.Last); + Ref.all.Data (Args'Length .. Ref.all.Last) + := Args (Args'First).L.Ref.all.Data; + for I in 1 .. Args'Length - 1 loop + Ref.all.Data (I) := Args (Args'Last - I + 1); + end loop; + return (Kind_List, (AFC with Ref)); + when Kind_Vector => + return (Kind_Vector, (AFC with new Rec' + (Last => Args'Length - 1 + Args (Args'First).L.Ref.all.Last, + Data => Args (Args'First).L.Ref.all.Data + & Args (Args'First + 1 .. Args'Last), + others => <>))); + when others => + raise Argument_Error with "conj: first arg must be list or vector"; + end case; + end Conj; + + function Cons (Args : in Mal.T_Array) return Mal.T is + begin + if Args'Length /= 2 then + raise Argument_Error with "cons: expects 2 arguments"; + end if; + case Args (Args'Last).Kind is + when Kind_List | Kind_Vector => + return (Kind_List, (AFC with new Rec' + (Last => 1 + Args (Args'Last).L.Ref.all.Last, + Data => Args (Args'First) & Args (Args'Last).L.Ref.all.Data, + others => <>))); + when others => + raise Argument_Error with "cons: last arg must be list or vector"; + end case; + end Cons; + + function Count (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 1 then + raise Argument_Error with "count: expects 1 argument" + else + (case Args (Args'First).Kind is + when Kind_Nil => + (Kind_Number, 0), + when Kind_List | Kind_Vector => + (Kind_Number, Args (Args'First).L.Ref.all.Last), + when others => + raise Argument_Error with "count: expects a list or vector")); + + function Element (Container : in Ptr; + Index : in Positive) return Mal.T + is (Container.Ref.all.Data (Index)); + + procedure Finalize (Object : in out Ptr) is + begin + if Object.Ref /= null and then 0 < Object.Ref.all.Refs then + Object.Ref.all.Refs := Object.Ref.all.Refs - 1; + if 0 < Object.Ref.all.Refs then + Object.Ref := null; + else + Free (Object.Ref); + end if; + end if; + end Finalize; + + function First (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 1 then + raise Argument_Error with "first: expects 1 argument" + else + (case Args (Args'First).Kind is + when Kind_Nil => + Mal.Nil, + when Kind_List | Kind_Vector => + (if Args (Args'First).L.Ref.all.Last = 0 then + Mal.Nil + else + Args (Args'First).L.Ref.all.Data (1)), + when others => + raise Argument_Error with "first: expects a list or vector")); + + function Generic_Eval (Container : in Ptr; + Env : in Env_Type) + return Ptr is + -- Take care that automatic deallocation happens if an + -- exception is propagated by user code. + Old : Rec renames Container.Ref.all; + Ref : Acc; + begin + pragma Assert (0 < Old.Refs); + if Old.Refs = 1 then + Ref := Container.Ref; + Old.Refs := 2; + Old.Meta := Mal.Nil; + else + Ref := new Rec (Old.Last); + end if; + return R : constant Ptr := (AFC with Ref) do + for I in Old.Data'Range loop + Ref.all.Data (I) := Eval (Old.Data (I), Env); + end loop; + end return; + end Generic_Eval; + + function Is_Empty (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 1 then + raise Argument_Error with "empty?: expects 1 argument" + else + (case Args (Args'First).Kind is + when Kind_List | Kind_Vector => + (Kind_Boolean, Args (Args'First).L.Ref.all.Last = 0), + when others => + raise Argument_Error with "empty?: expects a list or vector")); + + function Length (Source : in Ptr) return Natural + is (Source.Ref.all.Last); + + function List (Args : in Mal.T_Array) return Mal.T + is (Kind_List, (AFC with new Rec'(Data => Args, + Last => Args'Length, + others => <>))); + + function Meta (Item : in Ptr) return Mal.T + is (Item.Ref.all.Meta); + + function Nth (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 2 then + raise Argument_Error with "nth: expects 2 arguments" + else + (case Args (Args'First).Kind is + when Kind_List | Kind_Vector => + (if Args (Args'First + 1).Kind /= Kind_Number then + raise Argument_Error with "nth: last arg must be a number" + elsif 1 + Args (Args'Last).Ada_Number + in Args (Args'First).L.Ref.all.Data'Range + then + Args (Args'First).L.Ref.all.Data + (1 + Args (Args'Last).Ada_Number) + else + raise Argument_Error with "nth: index out of bounds"), + when others => + raise Argument_Error with "nth: expects a list or vector")); + + function Rest (Args : in Mal.T_Array) return Mal.T + is (Kind_List, (AFC with + (if Args'Length /= 1 then + raise Argument_Error with "rest: expects 1 argument" + else + (case Args (Args'First).Kind is + when Kind_Nil => + new Rec (0), + when Kind_List | Kind_Vector => + (if Args (Args'First).L.Ref.all.Last = 0 then + new Rec (0) + else + new Rec'(Last => Args (Args'First).L.Ref.all.Last - 1, + Data => Args (Args'First).L.Ref.all.Data + (2 .. Args (Args'First).L.Ref.all.Last), + others => <>)), + when others => + raise Argument_Error with "rest: expects a list or vector")))); + + function Slice (Item : in Ptr; + Start : in Positive) + return Mal.T + is (Kind_List, (AFC with new Rec' + (Last => Item.Ref.all.Last - Start + 1, + Data => Item.Ref.all.Data (Start .. Item.Ref.all.Last), + others => <>))); + + function Vector (Args : in Mal.T_Array) return Mal.T + is (Kind_Vector, (AFC with new Rec'(Data => Args, + Last => Args'Length, + others => <>))); + + function With_Meta (Data : in Ptr; + Meta : in Mal.T) + return Ptr is + Old : Rec renames Data.Ref.all; + Ref : Acc; + begin + pragma Assert (0 < Old.Refs); + if Old.Refs = 1 then + Ref := Data.Ref; + Old.Refs := 2; + Old.Meta := Meta; + else + Ref := new Rec'(Last => Old.Last, + Data => Old.Data, + Meta => Meta, + others => <>); + end if; + return (AFC with Ref); + end With_Meta; + +end Types.Lists; diff --git a/ada2/types-lists.ads b/ada2/types-lists.ads new file mode 100644 index 0000000000..b8678f2f1c --- /dev/null +++ b/ada2/types-lists.ads @@ -0,0 +1,83 @@ +private with Ada.Finalization; + +limited with Types.Mal; + +package Types.Lists is + + type Ptr is tagged private; + -- A wrapper for a pointer counting references. + + -- The default value is invalid, new variables must be assigned + -- immediately (a hidden discriminant would prevent this type to + -- become a field inside Types.Mal.T, so we check this with a + -- private invariant a fallback, an invariant in the private part + -- checks that any created object is affected before use. + + -- Assignment give another reference to the same storage. + + -- Built-in functions. + function Concat (Args : in Mal.T_Array) return Mal.T; + function Conj (Args : in Mal.T_Array) return Mal.T; + function Cons (Args : in Mal.T_Array) return Mal.T; + function Count (Args : in Mal.T_Array) return Mal.T; + function First (Args : in Mal.T_Array) return Mal.T; + function Is_Empty (Args : in Mal.T_Array) return Mal.T; + function List (Args : in Mal.T_Array) return Mal.T; + function Nth (Args : in Mal.T_Array) return Mal.T; + function Rest (Args : in Mal.T_Array) return Mal.T; + function Vector (Args : in Mal.T_Array) return Mal.T; + + function Length (Source : in Ptr) return Natural with Inline; + + function Element (Container : in Ptr; + Index : in Positive) return Mal.T + with Inline; + Index_Error : exception; + + function "&" (Left : in Mal.T_Array; + Right : in Ptr) return Mal.T_Array; + -- Used to implement Core.Apply. + + -- Used to evaluate each element of a list/vector. + -- Eval is generic because units cannot depend on each other. + generic + type Env_Type (<>) is limited private; + with function Eval (Ast : in Mal.T; + Env : in Env_Type) + return Mal.T; + function Generic_Eval (Container : in Ptr; + Env : in Env_Type) + return Ptr; + + -- Used to spare an intermediate copy for & in macro arguments. + function Slice (Item : in Ptr; + Start : in Positive) + return Mal.T; + + function Meta (Item : in Ptr) return Mal.T with Inline; + function With_Meta (Data : in Ptr; + Meta : in Mal.T) + return Ptr; + +private + + -- It is tempting to use null to represent an empty list, but the + -- performance is not improved much, and the code is more complex. + -- In addition, the empty list may want to carry metadata. + + -- Similarly, always providing a default value like a pointer to a + -- static empty list would not gain much, and probably hide some + -- bugs. + + type Rec; + type Acc is access Rec; + type Ptr is new Ada.Finalization.Controlled with record + Ref : Acc := null; + end record + with Invariant => Ref /= null; + overriding procedure Adjust (Object : in out Ptr) with Inline; + overriding procedure Finalize (Object : in out Ptr) with Inline; + overriding function "=" (Left, Right : in Ptr) return Boolean; + pragma Finalize_Storage_Only (Ptr); + +end Types.Lists; diff --git a/ada2/types-mal.adb b/ada2/types-mal.adb new file mode 100644 index 0000000000..7e613d4172 --- /dev/null +++ b/ada2/types-mal.adb @@ -0,0 +1,31 @@ +package body Types.Mal is + + use type Ada.Strings.Unbounded.Unbounded_String; + use type Lists.Ptr; + use type Maps.Ptr; + use type Symbols.Ptr; + + ---------------------------------------------------------------------- + + function "=" (Left, Right : in T) return Boolean + is (case Left.Kind is + when Kind_Nil => + Right.Kind = Kind_Nil, + when Kind_Boolean => + Right.Kind = Kind_Boolean + and then Left.Ada_Boolean = Right.Ada_Boolean, + when Kind_Number => + Right.Kind = Kind_Number and then Left.Ada_Number = Right.Ada_Number, + when Kind_Symbol => + Right.Kind = Kind_Symbol and then Left.Symbol = Right.Symbol, + -- Here is the part that differs from the predefined equality. + when Kind_Keyword | Kind_String => + Right.Kind = Left.Kind and then Left.S = Right.S, + when Kind_List | Kind_Vector => + Right.Kind in Kind_List | Kind_Vector and then Left.L = Right.L, + when Kind_Map => + Right.Kind = Kind_Map and then Left.Map = Right.Map, + when others => + False); + +end Types.Mal; diff --git a/ada2/types-mal.ads b/ada2/types-mal.ads new file mode 100644 index 0000000000..d7aec36428 --- /dev/null +++ b/ada2/types-mal.ads @@ -0,0 +1,84 @@ +with Ada.Strings.Unbounded; + +with Types.Atoms; +with Types.Builtins; +with Types.Functions; +with Types.Lists; +with Types.Maps; +with Types.Symbols; + +package Types.Mal is + + -- A type with a default value for the discriminant is the Ada + -- equivalent of a C union. It uses a fixed size, and allows + -- efficient arrays. A class hierarchy would make this impossible, + -- for little gain. + -- Native types may seem to consume too much memory, but + -- 1/ they require no allocation/deallocation. + -- 2/ the overhead would actually be higher with an intermediate + -- reference (the size of the pointer plus the size of the native + -- type, while an union uses the minimum of both and a single + -- memory area ). + -- Each instance has the size required for the largest possible + -- value, so subtypes should attempt to reduce their size when + -- possible (see Types.Symbols for such a compromise). + + -- The idea is inspired from the Haskell and OCaml interpreters, + -- which use a bit to distinguish pointers from integers. Ada + -- allows to specify the bit position of each component, but + -- generating such architecture-dependent definitions seems a lot + -- of work for MAL. + + -- The Ada tradition is to give explicit names to types, but this + -- one will be used very often, and almost each package declares + -- an "use Types;" clause, so Mal.T will do. + + -- The only problem with a hidden discriminant is that "in out" + -- parameters cannot be reaffected with a different discriminant. + -- Eval would be more efficient with "in out" parameters than with + -- "in" parameters and a result, because lots of reference + -- counting would be spared, and the implementation would be able + -- to reuse dynamic memory more efficiently. Environments, and + -- some list/map operations already attempt such reuse behind the + -- curtain. + + -- This would obfuscate the implementation of a functional + -- language, and require deep changes (the discriminant can be + -- changed for an in out or access parameter). + + type T (Kind : Kind_Type := Kind_Nil) is record + case Kind is + when Kind_Nil => + null; + when Kind_Boolean => + Ada_Boolean : Boolean; + when Kind_Number => + Ada_Number : Integer; + when Kind_Atom => + Atom : Atoms.Ptr; + when Kind_Keyword | Kind_String => + S : Ada.Strings.Unbounded.Unbounded_String; + when Kind_Symbol => + Symbol : Symbols.Ptr; + when Kind_List | Kind_Vector => + L : Lists.Ptr; + when Kind_Map => + Map : Maps.Ptr; + when Kind_Builtin => + Builtin : Builtins.Ptr; + when Kind_Builtin_With_Meta => + Builtin_With_Meta : Builtins.Ptr_With_Meta; + when Kind_Function | Kind_Macro => + Function_Value : Functions.Ptr; + end case; + end record; + + -- Useful for recursive automatic definition of equality for + -- composite types like the array type below. + function "=" (Left, Right : in T) return Boolean with Inline; + + Nil : constant T := (Kind => Kind_Nil); + + type T_Array is array (Positive range <>) of T; + +end Types.Mal; diff --git a/ada2/types-maps.adb b/ada2/types-maps.adb new file mode 100644 index 0000000000..9f70440b50 --- /dev/null +++ b/ada2/types-maps.adb @@ -0,0 +1,266 @@ +with Ada.Containers.Hashed_Maps; +with Ada.Strings.Unbounded.Hash; +with Ada.Unchecked_Deallocation; + +with Types.Lists; +with Types.Mal; + +package body Types.Maps is + + subtype AFC is Ada.Finalization.Controlled; + use type Ada.Containers.Count_Type; + + function Hash (Item : in Mal.T) return Ada.Containers.Hash_Type + with Inline, Pre => Item.Kind in Kind_Keyword | Kind_String; + + package HM is new Ada.Containers.Hashed_Maps (Key_Type => Mal.T, + Element_Type => Mal.T, + Hash => Hash, + Equivalent_Keys => Mal."=", + "=" => Mal."="); + use type HM.Map; + + type Rec is limited record + Refs : Natural := 1; + Data : HM.Map := HM.Empty_Map; + Meta : Mal.T := Mal.Nil; + end record; + + procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); + + ---------------------------------------------------------------------- + + function "=" (Left, Right : in Ptr) return Boolean + is (Left.Ref.all.Data = Right.Ref.all.Data); + + procedure Adjust (Object : in out Ptr) is + begin + Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + end Adjust; + + function Assoc (Args : in Mal.T_Array) return Mal.T is + Binds : constant Natural := Args'Length / 2; + begin + if Args'Length mod 2 /= 1 then + raise Argument_Error with "assoc: expects an odd argument count"; + elsif Args (Args'First).Kind /= Kind_Map then + raise Argument_Error with "assoc: first argument must be a map"; + elsif (for some I in 1 .. Binds => Args (Args'First + 2 * I - 1).Kind + not in Kind_Keyword | Kind_String) + then + raise Argument_Error with "assoc: keys must be strings or symbols"; + end if; + declare + Old : Rec renames Args (Args'First).Map.Ref.all; + Ref : Acc; + begin + pragma Assert (0 < Old.Refs); + if Old.Refs = 1 then + Ref := Args (Args'First).Map.Ref; + Old.Refs := 2; + Old.Meta := Mal.Nil; + else + Ref := new Rec'(Data => Old.Data, others => <>); + end if; + for I in 1 .. Binds loop + Ref.all.Data.Include (Key => Args (Args'First + 2 * I - 1), + New_Item => Args (Args'First + 2 * I)); + end loop; + return (Kind_Map, (AFC with Ref)); + end; + end Assoc; + + function Contains (Args : in Mal.T_Array) return Mal.T + is (if Args'Length /= 2 then + raise Argument_Error with "contains: expects 2 arguments" + elsif Args (Args'First).Kind /= Kind_Map then + raise Argument_Error with "contains: first arguement must be a map" + else (Kind_Boolean, + Args (Args'First).Map.Ref.all.Data.Contains (Args (Args'Last)))); + + function Dissoc (Args : in Mal.T_Array) return Mal.T is + begin + if Args'Length = 0 then + raise Argument_Error with "dissoc: expects at least 1 argument"; + elsif Args (Args'First).Kind /= Kind_Map then + raise Argument_Error with "dissoc: first argument must be a map"; + elsif (for some I in Args'First + 1 .. Args'Last => + Args (I).Kind not in Kind_Keyword | Kind_String) + then + raise Argument_Error with "dissoc: keys must be strings or symbols"; + end if; + declare + Old : Rec renames Args (Args'First).Map.Ref.all; + Ref : Acc; + begin + pragma Assert (0 < Old.Refs); + if Old.Refs = 1 then + Ref := Args (Args'First).Map.Ref; + Old.Refs := 2; + Old.Meta := Mal.Nil; + else + Ref := new Rec'(Data => Old.Data, others => <>); + end if; + for I in Args'First + 1 .. Args'Last loop + Ref.all.Data.Exclude (Args (I)); + end loop; + return (Kind_Map, (AFC with Ref)); + end; + end Dissoc; + + procedure Finalize (Object : in out Ptr) is + begin + if Object.Ref /= null and then 0 < Object.Ref.all.Refs then + Object.Ref.all.Refs := Object.Ref.all.Refs - 1; + if 0 < Object.Ref.all.Refs then + Object.Ref := null; + else + Free (Object.Ref); + end if; + end if; + end Finalize; + + function Generic_Eval (Container : in Ptr; + Env : in Env_Type) + return Mal.T is + -- Copy the whole hash in order to avoid recomputing the hash + -- for each key, even if it implies unneeded calls to adjust + -- and finalize for Mal_Type values. + Old : Rec renames Container.Ref.all; + Ref : Acc; + begin + pragma Assert (0 < Old.Refs); + if Old.Refs = 1 then + Ref := Container.Ref; + Old.Refs := 2; + Old.Meta := Mal.Nil; + else + Ref := new Rec'(Data => Container.Ref.all.Data, others => <>); + end if; + -- Prepare a valid structure before running user code. In case + -- an exception is raised, we want memory to be deallocated. + return R : constant Mal.T := (Kind_Map, (AFC with Ref)) do + for Position in Ref.all.Data.Iterate loop + Ref.all.Data.Replace_Element (Position, + Eval (HM.Element (Position), Env)); + end loop; + end return; + end Generic_Eval; + + function Get (Args : in Mal.T_Array) return Mal.T is + Position : HM.Cursor; + begin + if Args'Length /= 2 then + raise Argument_Error with "get: expects 2 arguments"; + elsif Args (Args'Last).Kind not in Kind_Keyword | Kind_String then + raise Argument_Error with "get: key must be a keyword or string"; + end if; + case Args (Args'First).Kind is + when Kind_Nil => + return Mal.Nil; + when Kind_Map => + Position + := Args (Args'First).Map.Ref.all.Data.Find (Args (Args'Last)); + if HM.Has_Element (Position) then + return HM.Element (Position); + else + return Mal.Nil; + end if; + when others => + raise Argument_Error with "get: first argument must be a map"; + end case; + end Get; + + function Hash (Item : in Mal.T) return Ada.Containers.Hash_Type + is (Ada.Strings.Unbounded.Hash (Item.S)); + + function Hash_Map (Args : in Mal.T_Array) return Mal.T is + Binds : constant Natural := Args'Length / 2; + Ref : Acc; + begin + if Args'Length mod 2 /= 0 then + raise Argument_Error with "hash-map: expects an even argument count"; + elsif (for some I in 0 .. Binds - 1 => Args (Args'First + 2 * I).Kind + not in Kind_Keyword | Kind_String) + then + raise Argument_Error with "hash-map: keys must be strings or symbols"; + end if; + Ref := new Rec; + Ref.all.Data.Reserve_Capacity (Ada.Containers.Count_Type (Binds)); + for I in 0 .. Binds - 1 loop + Ref.all.Data.Include (Key => Args (Args'First + 2 * I), + New_Item => Args (Args'First + 2 * I + 1)); + end loop; + return (Kind_Map, (AFC with Ref)); + end Hash_Map; + + procedure Iterate (Container : in Ptr) is + begin + for Position in Container.Ref.all.Data.Iterate loop + Process (HM.Key (Position), HM.Element (Position)); + end loop; + end Iterate; + + function Keys (Args : in Mal.T_Array) return Mal.T is + begin + if Args'Length /= 1 then + raise Argument_Error with "keys: expects 1 argument"; + elsif Args (Args'First).Kind /= Kind_Map then + raise Argument_Error with "keys: first argument must a map"; + end if; + declare + A1 : HM.Map renames Args (Args'First).Map.Ref.all.Data; + R : Mal.T_Array (1 .. Natural (A1.Length)); + I : Positive := 1; + begin + for Position in A1.Iterate loop + R (I) := HM.Key (Position); + I := I + 1; + end loop; + return Lists.List (R); + end; + end Keys; + + function Meta (Container : in Ptr) return Mal.T + is (Container.Ref.all.Meta); + + function Vals (Args : in Mal.T_Array) return Mal.T is + begin + if Args'Length /= 1 then + raise Argument_Error with "vals: expects 1 argument"; + elsif Args (Args'First).Kind /= Kind_Map then + raise Argument_Error with "vals: first argument must be a map"; + end if; + declare + A1 : HM.Map renames Args (Args'First).Map.Ref.all.Data; + R : Mal.T_Array (1 .. Natural (A1.Length)); + I : Positive := 1; + begin + for Element of A1 loop + R (I) := Element; + I := I + 1; + end loop; + return Lists.List (R); + end; + end Vals; + + function With_Meta (Data : in Ptr; + Meta : in Mal.T) + return Mal.T is + Old : Rec renames Data.Ref.all; + Ref : Acc; + begin + pragma Assert (0 < Old.Refs); + if Old.Refs = 1 then + Ref := Data.Ref; + Old.Refs := 2; + Old.Meta := Meta; + else + Ref := new Rec'(Data => Old.Data, + Meta => Meta, + others => <>); + end if; + return (Kind_Map, (AFC with Ref)); + end With_Meta; + +end Types.Maps; diff --git a/ada2/types-maps.ads b/ada2/types-maps.ads new file mode 100644 index 0000000000..7de5d954ba --- /dev/null +++ b/ada2/types-maps.ads @@ -0,0 +1,68 @@ +private with Ada.Finalization; + +limited with Types.Mal; + +package Types.Maps is + + type Ptr is tagged private; + -- A wrapper for a pointer counting references. + + -- The default value is invalid, new variables must be assigned + -- immediately (a hidden discriminant would prevent this type to + -- become a field inside Types.Mal.T, so we check this with a + -- private invariant a fallback, an invariant in the private part + -- checks that any created object is affected before use. + + -- Assignment give another reference to the same storage. + + -- Built-in functions. + function Assoc (Args : in Mal.T_Array) return Mal.T; + function Contains (Args : in Mal.T_Array) return Mal.T; + function Dissoc (Args : in Mal.T_Array) return Mal.T; + function Get (Args : in Mal.T_Array) return Mal.T; + function Hash_Map (Args : in Mal.T_Array) return Mal.T; + function Keys (Args : in Mal.T_Array) return Mal.T; + function Vals (Args : in Mal.T_Array) return Mal.T; + + -- A generic is better than an access to function because of + -- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89159 + + -- Used to evaluate each element of a map. + -- Eval is generic because units cannot depend on each other. + generic + type Env_Type (<>) is limited private; + with function Eval (Ast : in Mal.T; + Env : in Env_Type) + return Mal.T; + function Generic_Eval (Container : in Ptr; + Env : in Env_Type) + return Mal.T; + + -- Used to print a map. + generic + with procedure Process (Key : in Mal.T; + Element : in Mal.T); + procedure Iterate (Container : in Ptr); + + function Meta (Container : in Ptr) return Mal.T with Inline; + + function With_Meta (Data : in Ptr; + Meta : in Mal.T) + return Mal.T; + +private + + -- See README for the implementation of reference counting. + + type Rec; + type Acc is access Rec; + type Ptr is new Ada.Finalization.Controlled with record + Ref : Acc := null; + end record + with Invariant => Ref /= null; + overriding procedure Adjust (Object : in out Ptr) with Inline; + overriding procedure Finalize (Object : in out Ptr) with Inline; + overriding function "=" (Left, Right : in Ptr) return Boolean with Inline; + pragma Finalize_Storage_Only (Ptr); + +end Types.Maps; diff --git a/ada2/types-symbols-names.ads b/ada2/types-symbols-names.ads new file mode 100644 index 0000000000..543f5e2d73 --- /dev/null +++ b/ada2/types-symbols-names.ads @@ -0,0 +1,31 @@ +package Types.Symbols.Names is + + -- These symbols are used once by Read/Eval/Print cycle. Declare + -- them here in order to avoid an allocation and a desallocation + -- during each call of eval. + -- The built-in functions declared in Core will remain allocated + -- during the lifetime of the program and do not require this. + + -- A separate package is required because the constructor must be + -- callable, and a child package makes sense because without this + -- problem, these definition would be in Symbols. + Ampersand : constant Ptr := Constructor ("&"); + Catch : constant Ptr := Constructor ("catch*"); + Def : constant Ptr := Constructor ("def!"); + Defmacro : constant Ptr := Constructor ("defmacro!"); + Fn : constant Ptr := Constructor ("fn*"); + Let : constant Ptr := Constructor ("let*"); + Macroexpand : constant Ptr := Constructor ("macroexpand"); + Mal_Do : constant Ptr := Constructor ("do"); + Mal_If : constant Ptr := Constructor ("if"); + Quasiquote : constant Ptr := Constructor ("quasiquote"); + Quote : constant Ptr := Constructor ("quote"); + Splice_Unquote : constant Ptr := Constructor ("splice-unquote"); + Try : constant Ptr := Constructor ("try*"); + Unquote : constant Ptr := Constructor ("unquote"); + + -- These are used by both Core and Reader. Spare a search. + Deref : constant Ptr := Constructor ("deref"); + With_Meta : constant Ptr := Constructor ("with-meta"); + +end Types.Symbols.Names; diff --git a/ada2/types-symbols.adb b/ada2/types-symbols.adb new file mode 100644 index 0000000000..91c013b249 --- /dev/null +++ b/ada2/types-symbols.adb @@ -0,0 +1,90 @@ +with Ada.Containers.Ordered_Sets; +with Ada.Strings.Hash; +with Ada.Unchecked_Deallocation; + +package body Types.Symbols is + + -- For the global dictionnary of symbols, an ordered set seems + -- better than a hash map. + + type Rec (Last : Positive) is limited record + Refs : Natural; + Hash : Ada.Containers.Hash_Type; + Data : String (1 .. Last); + end record; + procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); + + function "<" (Left, Right : in Acc) return Boolean with Inline; + function Eq (Left, Right : in Acc) return Boolean with Inline; + -- It would be unwise to name this function "=" and override the + -- predefined equality for Acc. + -- We only search by key and insert new elements, so this should + -- always return False. + package Sets is new Ada.Containers.Ordered_Sets (Element_Type => Acc, + "<" => "<", + "=" => Eq); + + function Key (Item : in Acc) return String with Inline; + package Keys is new Sets.Generic_Keys (Key_Type => String, + Key => Key, + "<" => Standard."<"); + + Dict : Sets.Set; + + ---------------------------------------------------------------------- + + function "<" (Left, Right : in Acc) return Boolean + is (Left.all.Data < Right.all.Data); + + procedure Adjust (Object : in out Ptr) is + begin + Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + end Adjust; + + function Constructor (Source : in String) return Ptr is + Position : constant Sets.Cursor := Keys.Find (Dict, Source); + Ref : Acc; + begin + if Sets.Has_Element (Position) then + Ref := Sets.Element (Position); + Ref.all.Refs := Ref.all.Refs + 1; + else + Ref := new Rec'(Data => Source, + Hash => Ada.Strings.Hash (Source), + Last => Source'Length, + Refs => 1); + Dict.Insert (Ref); + end if; + return (Ada.Finalization.Controlled with Ref); + end Constructor; + + function Eq (Left, Right : in Acc) return Boolean is + begin + pragma Assert (Left /= Right); + pragma Assert (Left.all.Data /= Right.all.Data); + return False; + end Eq; + + procedure Finalize (Object : in out Ptr) is + begin + if Object.Ref /= null and then 0 < Object.Ref.all.Refs then + Object.Ref.all.Refs := Object.Ref.all.Refs - 1; + if 0 < Object.Ref.all.Refs then + Object.Ref := null; + else + Dict.Delete (Object.Ref); + Free (Object.Ref); + end if; + end if; + end Finalize; + + function Hash (Item : in Ptr) return Ada.Containers.Hash_Type + is (Item.Ref.all.Hash); + + function Key (Item : in Acc) return String + is (Item.all.Data); + + function To_String (Item : in Ptr) return String + is (Item.Ref.all.Data); + +end Types.Symbols; diff --git a/ada2/types-symbols.ads b/ada2/types-symbols.ads new file mode 100644 index 0000000000..b1368f1479 --- /dev/null +++ b/ada2/types-symbols.ads @@ -0,0 +1,59 @@ +with Ada.Containers; +private with Ada.Finalization; + +package Types.Symbols with Preelaborate is + + type Ptr is tagged private; + -- A wrapper for a pointer counting references. + + -- The default value is invalid, new variables must be assigned + -- immediately (a hidden discriminant would prevent this type to + -- become a field inside Types.Mal.T, so we check this with a + -- private invariant a fallback, an invariant in the private part + -- checks that any created object is affected before use. + + -- Assignment give another reference to the same storage. + + function Constructor (Source : in String) return Ptr with Inline; + -- The only way to assign a valid value. + + function To_String (Item : in Ptr) return String with Inline; + + -- The hash value is made available because symbols have a high + -- probability to end up as keys in an environment. + function Hash (Item : in Ptr) return Ada.Containers.Hash_Type with Inline; + + -- Equality compares the contents. + +private + + -- Only one instance is allocated with a given content. This + -- avoids many allocations and deallocations, since symbols are + -- expected to be used many times. + + -- Tests seem to show that this solution is a few percents faster + -- than Ada.Strings.Unbounded. + + -- As a side effect, some frequent string comparisons (with "def!" + -- or "fn*" for example) will become a bit more efficient because + -- comparing pointers is faster than comparing strings. + + -- It would be natural to store a Cursor from the global + -- dictionnary into Ptr, but this actually reduces the speed, + -- probably because it significantly increases the size of + -- Mal_Type. + + -- See README for the implementation of reference counting. + + type Rec; + type Acc is access Rec; + type Ptr is new Ada.Finalization.Controlled with record + Ref : Acc := null; + end record + with Invariant => Ref /= null; + overriding procedure Adjust (Object : in out Ptr) with Inline; + overriding procedure Finalize (Object : in out Ptr) with Inline; + -- Predefined equality is fine. + pragma Finalize_Storage_Only (Ptr); + +end Types.Symbols; diff --git a/ada2/types.adb b/ada2/types.adb deleted file mode 100644 index d87297f3c8..0000000000 --- a/ada2/types.adb +++ /dev/null @@ -1,37 +0,0 @@ -package body Types is - - function "=" (Left, Right : in Mal_Type) return Boolean is - (case Left.Kind is - when Kind_Nil => - Right.Kind = Kind_Nil, - when Kind_Atom => - Right.Kind = Kind_Atom - and then Atoms."=" (Left.Reference, Right.Reference), - when Kind_Boolean => - Right.Kind = Kind_Boolean - and then Left.Boolean_Value = Right.Boolean_Value, - when Kind_Number => - Right.Kind = Kind_Number - and then Left.Integer_Value = Right.Integer_Value, - when Kind_String | Kind_Keyword | Kind_Symbol => - Right.Kind = Left.Kind - and then Strings."=" (Left.S, Right.S), - when Kind_List | Kind_Vector => - Right.Kind in Kind_List | Kind_Vector - and then Lists."=" (Left.L, Right.L), - when Kind_Map => - Right.Kind = Kind_Map - and then Maps."=" (Left.Map, Right.Map), - when Kind_Function => - Right.Kind = Kind_Function - and then Lists."=" (Left.Formals, Right.Formals) - and then Atoms."=" (Left.Expression, Right.Expression) - and then Environments."=" (Left.Environment, Right.Environment), - when Kind_Native => - Right.Kind = Kind_Native and then Left.Native = Right.Native, - when Kind_Macro => - Right.Kind = Kind_Macro - and then Atoms."=" (Left.Mac_Expression, Right.Mac_Expression) - and then Lists."=" (Left.Mac_Formals, Right.Mac_Formals)); - -end Types; diff --git a/ada2/types.ads b/ada2/types.ads index 24c4d51e03..3e549a9b24 100644 --- a/ada2/types.ads +++ b/ada2/types.ads @@ -1,17 +1,7 @@ -with Atoms; -with Environments; -with Lists; -with Maps; -with Strings; +package Types with Pure is -package Types is - - type Mal_Type; - type Mal_Type_Array; - type Native_Function_Access is not null access - function (Arguments : in Mal_Type_Array) return Mal_Type; - - -- Make similar kinds consecutive for efficient case statements. + -- Similar kinds should be consecutive for efficient case + -- statements. type Kind_Type is (Kind_Nil, Kind_Atom, @@ -20,40 +10,11 @@ package Types is Kind_String, Kind_Symbol, Kind_Keyword, Kind_List, Kind_Vector, Kind_Map, - Kind_Macro, Kind_Function, Kind_Native); - - type Mal_Type (Kind : Kind_Type := Kind_Nil) is record - Meta : Atoms.Ptr; - case Kind is - when Kind_Nil => - null; - when Kind_Boolean => - Boolean_Value : Boolean; - when Kind_Number => - Integer_Value : Integer; - when Kind_Atom => - Reference : Atoms.Ptr; - when Kind_String | Kind_Keyword | Kind_Symbol => - S : Strings.Ptr; - when Kind_List | Kind_Vector => - L : Lists.Ptr; - when Kind_Map => - Map : Maps.Ptr; - when Kind_Native => - Native : Native_Function_Access; - when Kind_Function => - Formals : Lists.Ptr; - Expression : Atoms.Ptr; - Environment : Environments.Ptr; - when Kind_Macro => - Mac_Formals : Lists.Ptr; - Mac_Expression : Atoms.Ptr; - end case; - end record; - - function "=" (Left, Right : in Mal_Type) return Boolean; - -- By default, a list /= a vector. + Kind_Macro, Kind_Function, Kind_Builtin_With_Meta, Kind_Builtin); - type Mal_Type_Array is array (Positive range <>) of Types.Mal_Type; + -- Raised when a program attempts to execute something else than a + -- function or a macro, or when a builtin receives a bad argument + -- count, type or value. + Argument_Error : exception; end Types; From 6e2b7ddffea6e23d9e94654c7c2b69241a653a3f Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Sun, 3 Mar 2019 18:07:18 +0100 Subject: [PATCH 0796/2308] Various cosmetic improvements inspired by adacontrol. In Printer, Reader and Quasiquote: move subsubprogram into the subprogram calling it: this improves the readability and spares some adjust/finalizations. Force elaboration of Environments and Symbols before Core. Move some use clauses to the minimal scope requiring them. Remove some name clashes. Format more consistently. Add comments to distant 'begin' keywords. Give explicit list of remaining choices in crucial case statements. Remove unneeded parenthesis. Avoid unneeded exceptions. Explicit some initial values. --- ada2/Makefile | 2 +- ada2/core.adb | 112 +++++++++------- ada2/core.ads | 2 +- ada2/environments.adb | 30 ++--- ada2/environments.ads | 10 +- ada2/printer.adb | 269 +++++++++++++++++++-------------------- ada2/reader.adb | 79 ++++++------ ada2/step2_eval.adb | 14 +- ada2/step3_env.adb | 14 +- ada2/step4_if_fn_do.adb | 16 ++- ada2/step5_tco.adb | 19 +-- ada2/step6_file.adb | 19 +-- ada2/step7_quote.adb | 100 ++++++++------- ada2/step8_macros.adb | 103 ++++++++------- ada2/step9_try.adb | 103 ++++++++------- ada2/stepa_mal.adb | 103 ++++++++------- ada2/types-builtins.adb | 12 +- ada2/types-builtins.ads | 8 +- ada2/types-functions.adb | 18 +-- ada2/types-functions.ads | 6 +- ada2/types-lists.adb | 13 +- ada2/types-lists.ads | 4 +- ada2/types-maps.adb | 14 +- ada2/types-maps.ads | 4 +- 24 files changed, 578 insertions(+), 496 deletions(-) diff --git a/ada2/Makefile b/ada2/Makefile index a74b81ff2d..20cc5692aa 100644 --- a/ada2/Makefile +++ b/ada2/Makefile @@ -30,7 +30,7 @@ steps := $(step0) $(step13) $(step49) $(stepa) .PHONY: all clean all: $(steps) clean: - $(RM) *~ *.ali *.o b~*.ad[bs] gmon.out $(steps) + $(RM) *~ *.adt *.ali *.o b~*.ad[bs] gmon.out $(steps) # Tell Make how to detect out-of-date executables, and let gnatmake do # the rest when it must be executed. diff --git a/ada2/core.adb b/ada2/core.adb index 5027457f7f..cbbef7f4d5 100644 --- a/ada2/core.adb +++ b/ada2/core.adb @@ -1,31 +1,30 @@ -with Ada.Calendar; use type Ada.Calendar.Time; +with Ada.Calendar; with Ada.Characters.Latin_1; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Environments; +with Environments; pragma Elaborate_All (Environments); with Types.Atoms; with Types.Builtins; with Types.Functions; with Types.Lists; with Types.Maps; -with Types.Symbols.Names; +with Types.Symbols.Names; pragma Elaborate_All (Types.Symbols); with Printer; with Reader; package body Core is - package ASU renames Ada.Strings.Unbounded; use Types; - use Types.Lists; use type Mal.T; - use type Mal.T_Array; + + package ASU renames Ada.Strings.Unbounded; Start_Time : constant Ada.Calendar.Time := Ada.Calendar.Clock; - function Apply (Func : in Mal.T; - Args : in Mal.T_Array; - Name : in String) return Mal.T with Inline; + function Apply_Helper (Func : in Mal.T; + Args : in Mal.T_Array; + Name : in String) return Mal.T with Inline; -- If Func is not executable, report an exception using "name" as -- the built-in function name. @@ -107,10 +106,10 @@ package body Core is ---------------------------------------------------------------------- - function Apply (Func : in Mal.T; - Args : in Mal.T_Array; - Name : in String) - return Mal.T is + function Apply_Helper (Func : in Mal.T; + Args : in Mal.T_Array; + Name : in String) return Mal.T + is begin case Func.Kind is when Kind_Builtin => @@ -119,26 +118,34 @@ package body Core is return Func.Builtin_With_Meta.Data.all (Args); when Kind_Function => declare - Env : constant Environments.Ptr := Func.Function_Value.Closure.Sub; + Env : constant Environments.Ptr + := Func.Function_Value.Closure.Closure_Sub; begin Func.Function_Value.Set_Binds (Env, Args); return Eval_Ref.all (Func.Function_Value.Expression, Env); end; - when others => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Symbol | Kind_Keyword | Kind_List | Kind_Vector | Kind_Map + | Kind_Macro => raise Argument_Error with Name & ": cannot execute " & ASU.To_String (Printer.Pr_Str (Func)); end case; - end Apply; + end Apply_Helper; - function Apply (Args : in Mal.T_Array) return Mal.T - is (if Args'Length < 2 then - raise Argument_Error with "apply: expects at least 2 arguments" - elsif Args (Args'Last).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "apply: last arg must a be list or vector" - else - Apply (Args (Args'First), - Args (Args'First + 1 .. Args'Last - 1) & Args (Args'Last).L, - "apply")); + function Apply (Args : in Mal.T_Array) return Mal.T is + use type Lists.Ptr; + begin + if Args'Length < 2 then + raise Argument_Error with "apply: expects at least 2 arguments"; + elsif Args (Args'Last).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "apply: last arg must a be list or vector"; + else + return Apply_Helper (Args (Args'First), + Args (Args'First + 1 .. Args'Last - 1) + & Args (Args'Last).L, + "apply"); + end if; + end Apply; function Equals (Args : in Mal.T_Array) return Mal.T is (if Args'Length /= 2 then @@ -150,7 +157,7 @@ package body Core is is (if Args'Length /= 1 then raise Argument_Error with "eval: expects 1 argument" else - (Eval_Ref.all (Args (Args'First), Environments.Repl))); + Eval_Ref.all (Args (Args'First), Environments.Repl)); function Is_False (Args : in Mal.T_Array) return Mal.T is (if Args'Length /= 1 then @@ -197,9 +204,9 @@ package body Core is R : Mal.T_Array (1 .. Args (Args'Last).L.Length); begin for I in R'Range loop - R (I) := Apply (Args (Args'First), - Mal.T_Array'(1 => Args (Args'Last).L.Element (I)), - "map"); + R (I) := Apply_Helper (Args (Args'First), + Mal.T_Array'(1 => Args (Args'Last).L.Element (I)), + "map"); end loop; return Lists.List (R); end; @@ -218,8 +225,12 @@ package body Core is Args (Args'First).Function_Value.Meta, when Kind_Builtin_With_Meta => Args (Args'First).Builtin_With_Meta.Meta, - when others => - Mal.Nil)); + when Kind_Builtin => + Mal.Nil, + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | + Kind_String | Kind_Symbol | Kind_Keyword | Kind_Macro => + raise Argument_Error + with "meta: expects a list, vector, map or function")); function Pr_Str (Args : in Mal.T_Array) return Mal.T is begin @@ -269,11 +280,12 @@ package body Core is raise Argument_Error with "readline: expects a keyword or string"; else Ada.Text_IO.Unbounded_IO.Put (Args (Args'First).S); - return (Kind_String, Ada.Text_IO.Unbounded_IO.Get_Line); + if Ada.Text_IO.End_Of_File then + return Mal.Nil; + else + return (Kind_String, Ada.Text_IO.Unbounded_IO.Get_Line); + end if; end if; - exception - when Ada.Text_IO.End_Error => - return Mal.Nil; end Readline; function Read_String (Args : in Mal.T_Array) return Mal.T @@ -358,10 +370,11 @@ package body Core is raise Argument_Error with "swap!: arg 1 must be an atom"; end if; declare + use type Mal.T_Array; X : Mal.T renames Atoms.Deref (Args (Args'First .. Args'First)); - FX : Mal.T renames Apply (Args (Args'First + 1), - X & Args (Args'First + 2 .. Args'Last), - "swap!"); + FX : Mal.T renames Apply_Helper (Args (Args'First + 1), + X & Args (Args'First + 2 .. Args'Last), + "swap!"); begin return Atoms.Reset (Mal.T_Array'(Args (Args'First), FX)); end; @@ -378,18 +391,21 @@ package body Core is begin if Args'Length /= 1 then raise Argument_Error with "throw: expects 1 argument"; - else - Last_Exception := Args (Args'First); - raise Exception_Throwed; - return Mal.Nil; -- GNAT wants a return. end if; + Last_Exception := Args (Args'First); + raise Exception_Throwed; + return Mal.Nil; -- GNAT wants a return end Throw; - function Time_Ms (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 0 then - raise Argument_Error with "time: expects no argument" - else - (Kind_Number, Integer (1000.0 * (Ada.Calendar.Clock - Start_Time)))); + function Time_Ms (Args : in Mal.T_Array) return Mal.T is + use type Ada.Calendar.Time; + begin + if 0 < Args'Length then + raise Argument_Error with "time: expects no argument"; + end if; + return (Kind_Number, + Integer (1000.0 * (Ada.Calendar.Clock - Start_Time))); + end Time_Ms; function With_Meta (Args : in Mal.T_Array) return Mal.T is (if Args'Length /= 2 then @@ -413,7 +429,7 @@ package body Core is use Symbols; R : Environments.Ptr renames Environments.Repl; B : Kind_Type renames Kind_Builtin; -begin +begin -- Core R.Set (Constructor ("+"), (B, Addition'Access)); R.Set (Constructor ("apply"), (B, Apply'Access)); R.Set (Constructor ("assoc"), (B, Maps.Assoc'Access)); diff --git a/ada2/core.ads b/ada2/core.ads index eb1d8dd008..1326d71857 100644 --- a/ada2/core.ads +++ b/ada2/core.ads @@ -12,7 +12,7 @@ package Core with Elaborate_Body is -- Set by the main program at startup. Exception_Throwed : exception; - Last_Exception : Types.Mal.T; + Last_Exception : Types.Mal.T := (Kind => Types.Kind_Nil); -- When the exception is throwed, Last_Exception is set with the -- related Data. diff --git a/ada2/environments.adb b/ada2/environments.adb index 75fef4b67c..d0e0802c69 100644 --- a/ada2/environments.adb +++ b/ada2/environments.adb @@ -72,6 +72,18 @@ package body Environments is end if; end Adjust; + function Closure_Sub (Outer : in Closure_Ptr'Class) return Ptr is + begin + Outer.Ref.all.Refs := Outer.Ref.all.Refs + 1; + Top := Top + 1; + pragma Assert (Stack (Top).Data.Is_Empty); + pragma Assert (Stack (Top).Alias = null); + Stack (Top) := (Outer_On_Stack => False, + Outer_Ref => Outer.Ref, + others => <>); + return (Ada.Finalization.Limited_Controlled with Top); + end Closure_Sub; + function Copy_Pointer (Env : in Ptr) return Ptr is begin Stack (Env.Index).Refs := Stack (Env.Index).Refs + 1; @@ -279,8 +291,8 @@ package body Environments is -- unreferenced alias if any. end Replace_With_Sub; - procedure Replace_With_Sub (Env : in out Ptr; - Outer : in Closure_Ptr'Class) is + procedure Replace_With_Closure_Sub (Env : in out Ptr; + Outer : in Closure_Ptr'Class) is begin Finalize (Env); Outer.Ref.all.Refs := Outer.Ref.all.Refs + 1; @@ -291,7 +303,7 @@ package body Environments is Outer_Ref => Outer.Ref, others => <>); Env.Index := Top; - end Replace_With_Sub; + end Replace_With_Closure_Sub; procedure Set (Env : in Ptr; Key : in Symbols.Ptr; @@ -300,18 +312,6 @@ package body Environments is Stack (Env.Index).Data.Include (Key, New_Element); end Set; - function Sub (Outer : in Closure_Ptr'Class) return Ptr is - begin - Outer.Ref.all.Refs := Outer.Ref.all.Refs + 1; - Top := Top + 1; - pragma Assert (Stack (Top).Data.Is_Empty); - pragma Assert (Stack (Top).Alias = null); - Stack (Top) := (Outer_On_Stack => False, - Outer_Ref => Outer.Ref, - others => <>); - return (Ada.Finalization.Limited_Controlled with Top); - end Sub; - function Sub (Outer : in Ptr) return Ptr is R : Stack_Record renames Stack (Outer.Index); begin diff --git a/ada2/environments.ads b/ada2/environments.ads index 5ef4561372..c49c9cd78f 100644 --- a/ada2/environments.ads +++ b/ada2/environments.ads @@ -55,13 +55,11 @@ package Environments with Elaborate_Body is type Closure_Ptr is tagged private; Null_Closure : constant Closure_Ptr; - function Sub (Outer : in Closure_Ptr'Class) return Ptr; + function Closure_Sub (Outer : in Closure_Ptr'Class) return Ptr; - procedure Replace_With_Sub (Env : in out Ptr; - Outer : in Closure_Ptr'Class); - -- Like Env := Sub (Outer => Outer); except that Env is finalized - -- *before* the assignement, so its memory can be reused by the - -- new environment. This is important for tail call optimization. + procedure Replace_With_Closure_Sub (Env : in out Ptr; + Outer : in Closure_Ptr'Class); + -- Like Env := Closure_Sub (Outer); except that the type is limited. function New_Closure (Env : in Ptr'Class) return Closure_Ptr; -- The class-wide argument does not make much sense, but avoids diff --git a/ada2/printer.adb b/ada2/printer.adb index e768f05bbd..f9bee1745c 100644 --- a/ada2/printer.adb +++ b/ada2/printer.adb @@ -7,156 +7,145 @@ with Types.Maps; package body Printer is - use Ada.Strings.Unbounded; - use Types; + function Pr_Str (Ast : in Types.Mal.T; + Readably : in Boolean := True) + return Ada.Strings.Unbounded.Unbounded_String + is - procedure Print_Form (Buffer : in out Unbounded_String; - Ast : in Mal.T; - Readably : in Boolean); - procedure Print_List (Buffer : in out Unbounded_String; - List : in Lists.Ptr; - Readably : in Boolean) with Inline; - procedure Print_Function (Buffer : in out Unbounded_String; - Fn : in Functions.Ptr; - Readably : in Boolean) with Inline; - procedure Print_Map (Buffer : in out Unbounded_String; - Map : in Maps.Ptr; - Readably : in Boolean) with Inline; + use Ada.Strings.Unbounded; + use Types; - ---------------------------------------------------------------------- + Buffer : Unbounded_String := Null_Unbounded_String; + -- is appended the result character after character. - procedure Print_Form (Buffer : in out Unbounded_String; - Ast : in Mal.T; - Readably : in Boolean) is - begin - case Ast.Kind is - when Kind_Nil => - Append (Buffer, "nil"); - when Kind_Boolean => - if Ast.Ada_Boolean then - Append (Buffer, "true"); - else - Append (Buffer, "false"); + procedure Print_Form (Form_Ast : in Mal.T); + -- The recursive function traversing Ast for Pr_Str. + -- Form_Ast is the current node. + + ---------------------------------------------------------------------- + + procedure Print_Form (Form_Ast : in Mal.T) is + + procedure Print_List (List : in Lists.Ptr) with Inline; + -- An helper for Print_Form. + + procedure Print_List (List : in Lists.Ptr) is + begin + if 0 < List.Length then + Print_Form (List.Element (1)); + for I in 2 .. List.Length loop + Append (Buffer, ' '); + Print_Form (List.Element (I)); + end loop; end if; - when Kind_Symbol => - Append (Buffer, Ast.Symbol.To_String); - when Kind_Number => - declare - Img : constant String := Ast.Ada_Number'Img; - F : Positive := Img'First; - begin - if Img (F) = ' ' then - F := F + 1; + end Print_List; + + begin -- Print_Form + case Form_Ast.Kind is + when Kind_Nil => + Append (Buffer, "nil"); + when Kind_Boolean => + if Form_Ast.Ada_Boolean then + Append (Buffer, "true"); + else + Append (Buffer, "false"); end if; - Append (Buffer, Img (F .. Img'Last)); - end; - when Kind_Keyword => - Append (Buffer, ':'); - Append (Buffer, Ast.S); - when Kind_String => - if Readably then - Append (Buffer, '"'); + when Kind_Symbol => + Append (Buffer, Form_Ast.Symbol.To_String); + when Kind_Number => declare - C : Character; + Img : constant String := Integer'Image (Form_Ast.Ada_Number); + F : Positive := Img'First; begin - for I in 1 .. Length (Ast.S) loop - C := Element (Ast.S, I); - case C is - when '"' | '\' => - Append (Buffer, '\'); - Append (Buffer, C); - when Ada.Characters.Latin_1.LF => - Append (Buffer, "\n"); - when others => - Append (Buffer, C); - end case; - end loop; + if Img (F) = ' ' then + F := F + 1; + end if; + Append (Buffer, Img (F .. Img'Last)); end; - Append (Buffer, '"'); - else - Append (Buffer, Ast.S); - end if; - when Kind_List => - Append (Buffer, '('); - Print_List (Buffer, Ast.L, Readably); - Append (Buffer, ')'); - when Kind_Vector => - Append (Buffer, '['); - Print_List (Buffer, Ast.L, Readably); - Append (Buffer, ']'); - when Kind_Map => - Print_Map (Buffer, Ast.Map, Readably); - when Kind_Builtin | Kind_Builtin_With_Meta => - Append (Buffer, "#"); - when Kind_Function => - Append (Buffer, "#'); - when Kind_Macro => - Append (Buffer, "#'); - when Kind_Atom => - Append (Buffer, "(atom "); - Print_Form (Buffer, Atoms.Deref (Mal.T_Array'(1 => Ast)), - Readably); - Append (Buffer, ')'); - end case; - end Print_Form; - - procedure Print_Function (Buffer : in out Unbounded_String; - Fn : in Functions.Ptr; - Readably : in Boolean) is - begin - Print_List (Buffer, Fn.Formals, Readably); - Append (Buffer, " -> "); - Print_Form (Buffer, Fn.Expression, Readably); - end Print_Function; - - procedure Print_List (Buffer : in out Unbounded_String; - List : in Lists.Ptr; - Readably : in Boolean) is - begin - if 0 < List.Length then - Print_Form (Buffer, List.Element (1), Readably); - for I in 2 .. List.Length loop - Append (Buffer, ' '); - Print_Form (Buffer, List.Element (I), Readably); - end loop; - end if; - end Print_List; + when Kind_Keyword => + Append (Buffer, ':'); + Append (Buffer, Form_Ast.S); + when Kind_String => + if Readably then + declare + C : Character; + begin + Append (Buffer, '"'); + for I in 1 .. Length (Form_Ast.S) loop + C := Element (Form_Ast.S, I); + case C is + when '"' | '\' => + Append (Buffer, '\'); + Append (Buffer, C); + when Ada.Characters.Latin_1.LF => + Append (Buffer, "\n"); + when others => + Append (Buffer, C); + end case; + end loop; + Append (Buffer, '"'); + end; + else + Append (Buffer, Form_Ast.S); + end if; + when Kind_List => + Append (Buffer, '('); + Print_List (Form_Ast.L); + Append (Buffer, ')'); + when Kind_Vector => + Append (Buffer, '['); + Print_List (Form_Ast.L); + Append (Buffer, ']'); + when Kind_Map => + Append (Buffer, '{'); + declare + Is_First : Boolean := True; + procedure Process (Key : in Mal.T; + Element : in Mal.T); + procedure Iterate is new Maps.Iterate (Process); + procedure Process (Key : in Mal.T; + Element : in Mal.T) + is + begin + if Is_First then + Is_First := False; + else + Append (Buffer, ' '); + end if; + Print_Form (Key); + Append (Buffer, ' '); + Print_Form (Element); + end Process; + begin + Iterate (Form_Ast.Map); + end; + Append (Buffer, '}'); + when Kind_Builtin | Kind_Builtin_With_Meta => + Append (Buffer, "#"); + when Kind_Function => + Append (Buffer, "# "); + Print_Form (Form_Ast.Function_Value.Expression); + Append (Buffer, '>'); + when Kind_Macro => + Append (Buffer, "# "); + Print_Form (Form_Ast.Function_Value.Expression); + Append (Buffer, '>'); + when Kind_Atom => + Append (Buffer, "(atom "); + Print_Form (Atoms.Deref (Mal.T_Array'(1 => Form_Ast))); + Append (Buffer, ')'); + end case; + end Print_Form; - procedure Print_Map (Buffer : in out Unbounded_String; - Map : in Maps.Ptr; - Readably : in Boolean) is - Is_First : Boolean := True; - procedure Process (Key : in Mal.T; - Element : in Mal.T); - procedure Iterate is new Maps.Iterate (Process); - procedure Process (Key : in Mal.T; - Element : in Mal.T) is - begin - if Is_First then - Is_First := False; - else - Append (Buffer, ' '); - end if; - Print_Form (Buffer, Key, Readably); - Append (Buffer, ' '); - Print_Form (Buffer, Element, Readably); - end Process; - begin - Append (Buffer, '{'); - Iterate (Map); - Append (Buffer, '}'); - end Print_Map; + ---------------------------------------------------------------------- - function Pr_Str (Ast : in Mal.T; - Readably : in Boolean := True) return Unbounded_String is - begin - return Buffer : Unbounded_String do - Print_Form (Buffer, Ast, Readably); - end return; + begin -- Pr_Str + Print_Form (Form_Ast => Ast); + return Buffer; end Pr_Str; end Printer; diff --git a/ada2/reader.adb b/ada2/reader.adb index 36521eef21..776f60f9b5 100644 --- a/ada2/reader.adb +++ b/ada2/reader.adb @@ -7,13 +7,15 @@ with Types.Symbols.Names; package body Reader is - use Types; + function Read_Str (Source : in String) return Types.Mal.T is + + use Types; - function Read_Str (Source : in String) return Mal.T is First : Positive; Last : Natural := Source'First - 1; function Read_Form return Mal.T; + -- The recursive part of Read_Str. procedure Find_Next_Token; -- Search next token from index Last + 1. @@ -21,15 +23,8 @@ package body Reader is -- Find_Next_Token is normally invoked right before Read_Form, -- allowing the caller to check whether First <= Source'Last. - -- Helpers: - - -- Read_Atom has been merged into the same case/switch - -- statement, for clarity and efficiency. - function Read_List (Ending : in Character) return Mal.T_Array - with Inline; - function Read_Quote (Symbol : in Symbols.Ptr) return Mal.T with Inline; - ---------------------------------------------------------------------- + procedure Find_Next_Token is use Ada.Characters.Latin_1; @@ -97,8 +92,43 @@ package body Reader is end Find_Next_Token; function Read_Form return Mal.T is + + -- Read_Atom has been merged into the same case/switch + -- statement, for clarity and efficiency. + function Read_List (Ending : in Character) return Mal.T_Array + with Inline; + function Read_Quote (Symbol : in Symbols.Ptr) return Mal.T + with Inline; + + function Read_List (Ending : in Character) return Mal.T_Array is + -- Using big arrays on the stack is faster than doing + -- repeated dynamic reallocations. + Buffer : Mal.T_Array (First + 1 .. Source'Last); + B_Last : Natural := Buffer'First - 1; + begin + loop + Find_Next_Token; + if Source'Last < First then + raise Reader_Error with "unbalanced '" & Ending & "'"; + end if; + exit when Source (First) = Ending; + B_Last := B_Last + 1; + Buffer (B_Last) := Read_Form; + end loop; + return Buffer (Buffer'First .. B_Last); + end Read_List; + + function Read_Quote (Symbol : in Symbols.Ptr) return Mal.T is + begin + Find_Next_Token; + if Source'Last < First then + raise Reader_Error with "Unfinished '" & Symbol.To_String & "'"; + end if; + return Lists.List (Mal.T_Array'((Kind_Symbol, Symbol), Read_Form)); + end Read_Quote; + use Ada.Strings.Unbounded; - begin + begin -- Read_Form. case Source (First) is when '(' => return Lists.List (Read_List (')')); @@ -188,33 +218,6 @@ package body Reader is end case; end Read_Form; - function Read_List (Ending : in Character) return Mal.T_Array is - -- Using big arrays on the stack is faster than doing - -- repeated dynamic reallocations. - Buffer : Mal.T_Array (First + 1 .. Source'Last); - B_Last : Natural := Buffer'First - 1; - begin - loop - Find_Next_Token; - if Source'Last < First then - raise Reader_Error with "unbalanced '" & Ending & "'"; - end if; - exit when Source (First) = Ending; - B_Last := B_Last + 1; - Buffer (B_Last) := Read_Form; - end loop; - return Buffer (Buffer'First .. B_Last); - end Read_List; - - function Read_Quote (Symbol : in Symbols.Ptr) return Mal.T is - begin - Find_Next_Token; - if Source'Last < First then - raise Reader_Error with "Unfinished '" & Symbol.To_String & "'"; - end if; - return Lists.List (Mal.T_Array'((Kind_Symbol, Symbol), Read_Form)); - end Read_Quote; - ---------------------------------------------------------------------- begin diff --git a/ada2/step2_eval.adb b/ada2/step2_eval.adb index e37de05889..9010bb2c4f 100644 --- a/ada2/step2_eval.adb +++ b/ada2/step2_eval.adb @@ -45,8 +45,8 @@ procedure Step2_Eval is with function Ada_Operator (Left, Right : in Integer) return Integer; function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T; - function Eval_Elements is new Lists.Generic_Eval (Environments.Map, Eval); - function Eval_Elements is new Maps.Generic_Eval (Environments.Map, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Environments.Map, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Map, Eval); ---------------------------------------------------------------------- @@ -58,6 +58,10 @@ procedure Step2_Eval is -- Ada.Text_IO.Put ("EVAL: "); -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function + | Kind_Builtin_With_Meta | Kind_Builtin => + return Ast; when Kind_Symbol => declare S : constant String := Ast.Symbol.To_String; @@ -71,9 +75,9 @@ procedure Step2_Eval is end if; end; when Kind_Map => - return Eval_Elements (Ast.Map, Env); + return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_Elements (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; @@ -94,8 +98,6 @@ procedure Step2_Eval is raise Argument_Error with "cannot execute " & ASU.To_String (Print (First)); end case; - when others => - return Ast; end case; end Eval; diff --git a/ada2/step3_env.adb b/ada2/step3_env.adb index 224248e0ac..1b4448bfe2 100644 --- a/ada2/step3_env.adb +++ b/ada2/step3_env.adb @@ -37,8 +37,8 @@ procedure Step3_Env is with function Ada_Operator (Left, Right : in Integer) return Integer; function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T; - function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); ---------------------------------------------------------------------- @@ -51,12 +51,16 @@ procedure Step3_Env is -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); -- Environments.Dump_Stack; case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function + | Kind_Builtin_With_Meta | Kind_Builtin => + return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => - return Eval_Elements (Ast.Map, Env); + return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_Elements (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; @@ -119,8 +123,6 @@ procedure Step3_Env is raise Argument_Error with "cannot execute " & ASU.To_String (Print (First)); end case; - when others => - return Ast; end case; end Eval; diff --git a/ada2/step4_if_fn_do.adb b/ada2/step4_if_fn_do.adb index b4ed93798c..bce010d3dd 100644 --- a/ada2/step4_if_fn_do.adb +++ b/ada2/step4_if_fn_do.adb @@ -35,8 +35,8 @@ procedure Step4_If_Fn_Do is procedure Interactive_Loop (Repl : in Environments.Ptr); - function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); -- Convenient when the result of eval is of no interest. procedure Discard (Ast : in Mal.T) is null; @@ -52,12 +52,16 @@ procedure Step4_If_Fn_Do is -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); -- Environments.Dump_Stack; case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function + | Kind_Builtin_With_Meta | Kind_Builtin => + return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => - return Eval_Elements (Ast.Map, Env); + return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_Elements (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; @@ -159,7 +163,7 @@ procedure Step4_If_Fn_Do is declare Args : Mal.T_Array (2 .. Ast.L.Length); New_Env : constant Environments.Ptr - := First.Function_Value.Closure.Sub; + := First.Function_Value.Closure.Closure_Sub; begin for I in Args'Range loop Args (I) := Eval (Ast.L.Element (I), Env); @@ -171,8 +175,6 @@ procedure Step4_If_Fn_Do is raise Argument_Error with "cannot execute " & ASU.To_String (Print (First)); end case; - when others => - return Ast; end case; end Eval; diff --git a/ada2/step5_tco.adb b/ada2/step5_tco.adb index 1a865079cf..c9182356b3 100644 --- a/ada2/step5_tco.adb +++ b/ada2/step5_tco.adb @@ -35,8 +35,8 @@ procedure Step5_Tco is procedure Interactive_Loop (Repl : in Environments.Ptr); - function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); -- Convenient when the result of eval is of no interest. procedure Discard (Ast : in Mal.T) is null; @@ -44,7 +44,8 @@ procedure Step5_Tco is ---------------------------------------------------------------------- function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T is + Env0 : in Environments.Ptr) return Mal.T + is -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. Ast : Mal.T := Ast0; @@ -57,12 +58,16 @@ procedure Step5_Tco is -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); -- Environments.Dump_Stack; case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function + | Kind_Builtin_With_Meta | Kind_Builtin => + return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => - return Eval_Elements (Ast.Map, Env); + return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_Elements (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; @@ -171,7 +176,7 @@ procedure Step5_Tco is for I in Args'Range loop Args (I) := Eval (Ast.L.Element (I), Env); end loop; - Env.Replace_With_Sub (First.Function_Value.Closure); + Env.Replace_With_Closure_Sub (First.Function_Value.Closure); First.Function_Value.Set_Binds (Env, Args); Ast := First.Function_Value.Expression; goto Restart; @@ -180,8 +185,6 @@ procedure Step5_Tco is raise Argument_Error with "cannot execute " & ASU.To_String (Print (First)); end case; - when others => - return Ast; end case; end Eval; diff --git a/ada2/step6_file.adb b/ada2/step6_file.adb index 85795d77bb..b5051634f5 100644 --- a/ada2/step6_file.adb +++ b/ada2/step6_file.adb @@ -36,8 +36,8 @@ procedure Step6_File is procedure Interactive_Loop (Repl : in Environments.Ptr); - function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); -- Convenient when the result of eval is of no interest. procedure Discard (Ast : in Mal.T) is null; @@ -45,7 +45,8 @@ procedure Step6_File is ---------------------------------------------------------------------- function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T is + Env0 : in Environments.Ptr) return Mal.T + is -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. Ast : Mal.T := Ast0; @@ -58,12 +59,16 @@ procedure Step6_File is -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); -- Environments.Dump_Stack; case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function + | Kind_Builtin_With_Meta | Kind_Builtin => + return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => - return Eval_Elements (Ast.Map, Env); + return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_Elements (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; @@ -172,7 +177,7 @@ procedure Step6_File is for I in Args'Range loop Args (I) := Eval (Ast.L.Element (I), Env); end loop; - Env.Replace_With_Sub (First.Function_Value.Closure); + Env.Replace_With_Closure_Sub (First.Function_Value.Closure); First.Function_Value.Set_Binds (Env, Args); Ast := First.Function_Value.Expression; goto Restart; @@ -181,8 +186,6 @@ procedure Step6_File is raise Argument_Error with "cannot execute " & ASU.To_String (Print (First)); end case; - when others => - return Ast; end case; end Eval; diff --git a/ada2/step7_quote.adb b/ada2/step7_quote.adb index c2b5438ff8..819ee88dc1 100644 --- a/ada2/step7_quote.adb +++ b/ada2/step7_quote.adb @@ -28,9 +28,6 @@ procedure Step7_Quote is function Quasiquote (Ast : in Mal.T; Env : in Environments.Ptr) return Mal.T; - function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Mal.T with Inline; - -- Handle vectors and lists not starting with unquote. -- Mergeing quote and quasiquote into eval with a flag triggering -- a different behaviour as done for macros in step8 would improve -- the performances significantly, but Kanaka finds that it breaks @@ -46,8 +43,8 @@ procedure Step7_Quote is procedure Interactive_Loop (Repl : in Environments.Ptr); - function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); -- Convenient when the result of eval is of no interest. procedure Discard (Ast : in Mal.T) is null; @@ -55,7 +52,8 @@ procedure Step7_Quote is ---------------------------------------------------------------------- function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T is + Env0 : in Environments.Ptr) return Mal.T + is -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. Ast : Mal.T := Ast0; @@ -68,12 +66,16 @@ procedure Step7_Quote is -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); -- Environments.Dump_Stack; case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function + | Kind_Builtin_With_Meta | Kind_Builtin => + return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => - return Eval_Elements (Ast.Map, Env); + return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_Elements (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; @@ -192,7 +194,7 @@ procedure Step7_Quote is for I in Args'Range loop Args (I) := Eval (Ast.L.Element (I), Env); end loop; - Env.Replace_With_Sub (First.Function_Value.Closure); + Env.Replace_With_Closure_Sub (First.Function_Value.Closure); First.Function_Value.Set_Binds (Env, Args); Ast := First.Function_Value.Expression; goto Restart; @@ -201,8 +203,6 @@ procedure Step7_Quote is raise Argument_Error with "cannot execute " & ASU.To_String (Print (First)); end case; - when others => - return Ast; end case; end Eval; @@ -242,41 +242,55 @@ procedure Step7_Quote is function Quasiquote (Ast : in Mal.T; Env : in Environments.Ptr) return Mal.T - is (case Ast.Kind is - when Kind_Vector => Quasiquote (Ast.L, Env), - -- When the test is updated, replace Kind_List with Kind_Vector. - when Kind_List => - (if 0 < Ast.L.Length - and then Ast.L.Element (1).Kind = Kind_Symbol - and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote - then Eval (Ast.L.Element (2), Env) - else Quasiquote (Ast.L, Env)), - when others => Ast); + is - function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Mal.T is - -- The final return concatenates these lists. - R : Mal.T_Array (1 .. List.Length); - begin - for I in R'Range loop - R (I) := List.Element (I); - if R (I).Kind in Kind_List | Kind_Vector - and then 0 < R (I).L.Length - and then R (I).L.Element (1).Kind = Kind_Symbol - and then R (I).L.Element (1).Symbol = Symbols.Names.Splice_Unquote - then - if R (I).L.Length /= 2 then - raise Argument_Error with "splice-unquote: expects 1 argument"; + function Quasiquote_List (List : in Lists.Ptr) return Mal.T with Inline; + -- Handle vectors and lists not starting with unquote. + + function Quasiquote_List (List : in Lists.Ptr) return Mal.T is + -- The final return concatenates these lists. + R : Mal.T_Array (1 .. List.Length); + begin + for I in R'Range loop + R (I) := List.Element (I); + if R (I).Kind in Kind_List | Kind_Vector + and then 0 < R (I).L.Length + and then R (I).L.Element (1).Kind = Kind_Symbol + and then R (I).L.Element (1).Symbol + = Symbols.Names.Splice_Unquote + then + if R (I).L.Length /= 2 then + raise Argument_Error with "splice-unquote: expects 1 arg"; + end if; + R (I) := Eval (R (I).L.Element (2), Env); + if R (I).Kind /= Kind_List then + raise Argument_Error with "splice-unquote: expects a list"; + end if; + else + R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), + Env))); end if; - R (I) := Eval (R (I).L.Element (2), Env); - if R (I).Kind /= Kind_List then - raise Argument_Error with "splice-unquote: expects a list"; + end loop; + return Lists.Concat (R); + end Quasiquote_List; + + begin -- Quasiquote + case Ast.Kind is + when Kind_Vector => + -- When the test is updated, replace Kind_List with Kind_Vector. + return Quasiquote_List (Ast.L); + when Kind_List => + if 0 < Ast.L.Length + and then Ast.L.Element (1).Kind = Kind_Symbol + and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote + then + return Eval (Ast.L.Element (2), Env); + else + return Quasiquote_List (Ast.L); end if; - else - R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), Env))); - end if; - end loop; - return Lists.Concat (R); + when others => + return Ast; + end case; end Quasiquote; ---------------------------------------------------------------------- diff --git a/ada2/step8_macros.adb b/ada2/step8_macros.adb index c73935b16d..c8712face3 100644 --- a/ada2/step8_macros.adb +++ b/ada2/step8_macros.adb @@ -28,9 +28,6 @@ procedure Step8_Macros is function Quasiquote (Ast : in Mal.T; Env : in Environments.Ptr) return Mal.T; - function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Mal.T with Inline; - -- Handle vectors and lists not starting with unquote. -- Mergeing quote and quasiquote into eval with a flag triggering -- a different behaviour as done for macros in step8 would improve -- the performances significantly, but Kanaka finds that it breaks @@ -46,8 +43,8 @@ procedure Step8_Macros is procedure Interactive_Loop (Repl : in Environments.Ptr); - function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); -- Convenient when the result of eval is of no interest. procedure Discard (Ast : in Mal.T) is null; @@ -55,7 +52,8 @@ procedure Step8_Macros is ---------------------------------------------------------------------- function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T is + Env0 : in Environments.Ptr) return Mal.T + is -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. Ast : Mal.T := Ast0; @@ -69,12 +67,16 @@ procedure Step8_Macros is -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); -- Environments.Dump_Stack; case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function + | Kind_Builtin_With_Meta | Kind_Builtin => + return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => - return Eval_Elements (Ast.Map, Env); + return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_Elements (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; @@ -216,7 +218,7 @@ procedure Step8_Macros is for I in Args'Range loop Args (I) := Eval (Ast.L.Element (I), Env); end loop; - Env.Replace_With_Sub (First.Function_Value.Closure); + Env.Replace_With_Closure_Sub (First.Function_Value.Closure); First.Function_Value.Set_Binds (Env, Args); Ast := First.Function_Value.Expression; goto Restart; @@ -230,14 +232,13 @@ procedure Step8_Macros is end; if Macroexpanding then return Ast; + else + goto Restart; end if; - goto Restart; when others => raise Argument_Error with "cannot execute " & ASU.To_String (Print (First)); end case; - when others => - return Ast; end case; end Eval; @@ -277,41 +278,55 @@ procedure Step8_Macros is function Quasiquote (Ast : in Mal.T; Env : in Environments.Ptr) return Mal.T - is (case Ast.Kind is - when Kind_Vector => Quasiquote (Ast.L, Env), - -- When the test is updated, replace Kind_List with Kind_Vector. - when Kind_List => - (if 0 < Ast.L.Length - and then Ast.L.Element (1).Kind = Kind_Symbol - and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote - then Eval (Ast.L.Element (2), Env) - else Quasiquote (Ast.L, Env)), - when others => Ast); + is - function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Mal.T is - -- The final return concatenates these lists. - R : Mal.T_Array (1 .. List.Length); - begin - for I in R'Range loop - R (I) := List.Element (I); - if R (I).Kind in Kind_List | Kind_Vector - and then 0 < R (I).L.Length - and then R (I).L.Element (1).Kind = Kind_Symbol - and then R (I).L.Element (1).Symbol = Symbols.Names.Splice_Unquote - then - if R (I).L.Length /= 2 then - raise Argument_Error with "splice-unquote: expects 1 argument"; + function Quasiquote_List (List : in Lists.Ptr) return Mal.T with Inline; + -- Handle vectors and lists not starting with unquote. + + function Quasiquote_List (List : in Lists.Ptr) return Mal.T is + -- The final return concatenates these lists. + R : Mal.T_Array (1 .. List.Length); + begin + for I in R'Range loop + R (I) := List.Element (I); + if R (I).Kind in Kind_List | Kind_Vector + and then 0 < R (I).L.Length + and then R (I).L.Element (1).Kind = Kind_Symbol + and then R (I).L.Element (1).Symbol + = Symbols.Names.Splice_Unquote + then + if R (I).L.Length /= 2 then + raise Argument_Error with "splice-unquote: expects 1 arg"; + end if; + R (I) := Eval (R (I).L.Element (2), Env); + if R (I).Kind /= Kind_List then + raise Argument_Error with "splice-unquote: expects a list"; + end if; + else + R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), + Env))); end if; - R (I) := Eval (R (I).L.Element (2), Env); - if R (I).Kind /= Kind_List then - raise Argument_Error with "splice-unquote: expects a list"; + end loop; + return Lists.Concat (R); + end Quasiquote_List; + + begin -- Quasiquote + case Ast.Kind is + when Kind_Vector => + -- When the test is updated, replace Kind_List with Kind_Vector. + return Quasiquote_List (Ast.L); + when Kind_List => + if 0 < Ast.L.Length + and then Ast.L.Element (1).Kind = Kind_Symbol + and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote + then + return Eval (Ast.L.Element (2), Env); + else + return Quasiquote_List (Ast.L); end if; - else - R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), Env))); - end if; - end loop; - return Lists.Concat (R); + when others => + return Ast; + end case; end Quasiquote; ---------------------------------------------------------------------- diff --git a/ada2/step9_try.adb b/ada2/step9_try.adb index 33ba83923d..2fa414f0ef 100644 --- a/ada2/step9_try.adb +++ b/ada2/step9_try.adb @@ -28,9 +28,6 @@ procedure Step9_Try is function Quasiquote (Ast : in Mal.T; Env : in Environments.Ptr) return Mal.T; - function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Mal.T with Inline; - -- Handle vectors and lists not starting with unquote. -- Mergeing quote and quasiquote into eval with a flag triggering -- a different behaviour as done for macros in step8 would improve -- the performances significantly, but Kanaka finds that it breaks @@ -46,8 +43,8 @@ procedure Step9_Try is procedure Interactive_Loop (Repl : in Environments.Ptr); - function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); -- Convenient when the result of eval is of no interest. procedure Discard (Ast : in Mal.T) is null; @@ -55,7 +52,8 @@ procedure Step9_Try is ---------------------------------------------------------------------- function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T is + Env0 : in Environments.Ptr) return Mal.T + is -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. Ast : Mal.T := Ast0; @@ -69,12 +67,16 @@ procedure Step9_Try is -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); -- Environments.Dump_Stack; case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function + | Kind_Builtin_With_Meta | Kind_Builtin => + return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => - return Eval_Elements (Ast.Map, Env); + return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_Elements (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; @@ -257,7 +259,7 @@ procedure Step9_Try is for I in Args'Range loop Args (I) := Eval (Ast.L.Element (I), Env); end loop; - Env.Replace_With_Sub (First.Function_Value.Closure); + Env.Replace_With_Closure_Sub (First.Function_Value.Closure); First.Function_Value.Set_Binds (Env, Args); Ast := First.Function_Value.Expression; goto Restart; @@ -271,14 +273,13 @@ procedure Step9_Try is end; if Macroexpanding then return Ast; + else + goto Restart; end if; - goto Restart; when others => raise Argument_Error with "cannot execute " & ASU.To_String (Print (First)); end case; - when others => - return Ast; end case; end Eval; @@ -322,41 +323,55 @@ procedure Step9_Try is function Quasiquote (Ast : in Mal.T; Env : in Environments.Ptr) return Mal.T - is (case Ast.Kind is - when Kind_Vector => Quasiquote (Ast.L, Env), - -- When the test is updated, replace Kind_List with Kind_Vector. - when Kind_List => - (if 0 < Ast.L.Length - and then Ast.L.Element (1).Kind = Kind_Symbol - and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote - then Eval (Ast.L.Element (2), Env) - else Quasiquote (Ast.L, Env)), - when others => Ast); + is - function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Mal.T is - -- The final return concatenates these lists. - R : Mal.T_Array (1 .. List.Length); - begin - for I in R'Range loop - R (I) := List.Element (I); - if R (I).Kind in Kind_List | Kind_Vector - and then 0 < R (I).L.Length - and then R (I).L.Element (1).Kind = Kind_Symbol - and then R (I).L.Element (1).Symbol = Symbols.Names.Splice_Unquote - then - if R (I).L.Length /= 2 then - raise Argument_Error with "splice-unquote: expects 1 argument"; + function Quasiquote_List (List : in Lists.Ptr) return Mal.T with Inline; + -- Handle vectors and lists not starting with unquote. + + function Quasiquote_List (List : in Lists.Ptr) return Mal.T is + -- The final return concatenates these lists. + R : Mal.T_Array (1 .. List.Length); + begin + for I in R'Range loop + R (I) := List.Element (I); + if R (I).Kind in Kind_List | Kind_Vector + and then 0 < R (I).L.Length + and then R (I).L.Element (1).Kind = Kind_Symbol + and then R (I).L.Element (1).Symbol + = Symbols.Names.Splice_Unquote + then + if R (I).L.Length /= 2 then + raise Argument_Error with "splice-unquote: expects 1 arg"; + end if; + R (I) := Eval (R (I).L.Element (2), Env); + if R (I).Kind /= Kind_List then + raise Argument_Error with "splice-unquote: expects a list"; + end if; + else + R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), + Env))); end if; - R (I) := Eval (R (I).L.Element (2), Env); - if R (I).Kind /= Kind_List then - raise Argument_Error with "splice-unquote: expects a list"; + end loop; + return Lists.Concat (R); + end Quasiquote_List; + + begin -- Quasiquote + case Ast.Kind is + when Kind_Vector => + -- When the test is updated, replace Kind_List with Kind_Vector. + return Quasiquote_List (Ast.L); + when Kind_List => + if 0 < Ast.L.Length + and then Ast.L.Element (1).Kind = Kind_Symbol + and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote + then + return Eval (Ast.L.Element (2), Env); + else + return Quasiquote_List (Ast.L); end if; - else - R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), Env))); - end if; - end loop; - return Lists.Concat (R); + when others => + return Ast; + end case; end Quasiquote; ---------------------------------------------------------------------- diff --git a/ada2/stepa_mal.adb b/ada2/stepa_mal.adb index 5cbae9b21a..b660e17753 100644 --- a/ada2/stepa_mal.adb +++ b/ada2/stepa_mal.adb @@ -28,9 +28,6 @@ procedure StepA_Mal is function Quasiquote (Ast : in Mal.T; Env : in Environments.Ptr) return Mal.T; - function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Mal.T with Inline; - -- Handle vectors and lists not starting with unquote. -- Mergeing quote and quasiquote into eval with a flag triggering -- a different behaviour as done for macros in step8 would improve -- the performances significantly, but Kanaka finds that it breaks @@ -46,8 +43,8 @@ procedure StepA_Mal is procedure Interactive_Loop (Repl : in Environments.Ptr); - function Eval_Elements is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Elements is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); -- Convenient when the result of eval is of no interest. procedure Discard (Ast : in Mal.T) is null; @@ -55,7 +52,8 @@ procedure StepA_Mal is ---------------------------------------------------------------------- function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T is + Env0 : in Environments.Ptr) return Mal.T + is -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. Ast : Mal.T := Ast0; @@ -69,12 +67,16 @@ procedure StepA_Mal is -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); -- Environments.Dump_Stack; case Ast.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function + | Kind_Builtin_With_Meta | Kind_Builtin => + return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => - return Eval_Elements (Ast.Map, Env); + return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_Elements (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); when Kind_List => if Ast.L.Length = 0 then return Ast; @@ -266,7 +268,7 @@ procedure StepA_Mal is for I in Args'Range loop Args (I) := Eval (Ast.L.Element (I), Env); end loop; - Env.Replace_With_Sub (First.Function_Value.Closure); + Env.Replace_With_Closure_Sub (First.Function_Value.Closure); First.Function_Value.Set_Binds (Env, Args); Ast := First.Function_Value.Expression; goto Restart; @@ -280,14 +282,13 @@ procedure StepA_Mal is end; if Macroexpanding then return Ast; + else + goto Restart; end if; - goto Restart; when others => raise Argument_Error with "cannot execute " & ASU.To_String (Print (First)); end case; - when others => - return Ast; end case; end Eval; @@ -331,41 +332,55 @@ procedure StepA_Mal is function Quasiquote (Ast : in Mal.T; Env : in Environments.Ptr) return Mal.T - is (case Ast.Kind is - when Kind_Vector => Quasiquote (Ast.L, Env), - -- When the test is updated, replace Kind_List with Kind_Vector. - when Kind_List => - (if 0 < Ast.L.Length - and then Ast.L.Element (1).Kind = Kind_Symbol - and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote - then Eval (Ast.L.Element (2), Env) - else Quasiquote (Ast.L, Env)), - when others => Ast); + is - function Quasiquote (List : in Lists.Ptr; - Env : in Environments.Ptr) return Mal.T is - -- The final return concatenates these lists. - R : Mal.T_Array (1 .. List.Length); - begin - for I in R'Range loop - R (I) := List.Element (I); - if R (I).Kind in Kind_List | Kind_Vector - and then 0 < R (I).L.Length - and then R (I).L.Element (1).Kind = Kind_Symbol - and then R (I).L.Element (1).Symbol = Symbols.Names.Splice_Unquote - then - if R (I).L.Length /= 2 then - raise Argument_Error with "splice-unquote: expects 1 argument"; + function Quasiquote_List (List : in Lists.Ptr) return Mal.T with Inline; + -- Handle vectors and lists not starting with unquote. + + function Quasiquote_List (List : in Lists.Ptr) return Mal.T is + -- The final return concatenates these lists. + R : Mal.T_Array (1 .. List.Length); + begin + for I in R'Range loop + R (I) := List.Element (I); + if R (I).Kind in Kind_List | Kind_Vector + and then 0 < R (I).L.Length + and then R (I).L.Element (1).Kind = Kind_Symbol + and then R (I).L.Element (1).Symbol + = Symbols.Names.Splice_Unquote + then + if R (I).L.Length /= 2 then + raise Argument_Error with "splice-unquote: expects 1 arg"; + end if; + R (I) := Eval (R (I).L.Element (2), Env); + if R (I).Kind /= Kind_List then + raise Argument_Error with "splice-unquote: expects a list"; + end if; + else + R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), + Env))); end if; - R (I) := Eval (R (I).L.Element (2), Env); - if R (I).Kind /= Kind_List then - raise Argument_Error with "splice-unquote: expects a list"; + end loop; + return Lists.Concat (R); + end Quasiquote_List; + + begin -- Quasiquote + case Ast.Kind is + when Kind_Vector => + -- When the test is updated, replace Kind_List with Kind_Vector. + return Quasiquote_List (Ast.L); + when Kind_List => + if 0 < Ast.L.Length + and then Ast.L.Element (1).Kind = Kind_Symbol + and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote + then + return Eval (Ast.L.Element (2), Env); + else + return Quasiquote_List (Ast.L); end if; - else - R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), Env))); - end if; - end loop; - return Lists.Concat (R); + when others => + return Ast; + end case; end Quasiquote; ---------------------------------------------------------------------- diff --git a/ada2/types-builtins.adb b/ada2/types-builtins.adb index fa939a7da2..7f1dd79db0 100644 --- a/ada2/types-builtins.adb +++ b/ada2/types-builtins.adb @@ -37,17 +37,17 @@ package body Types.Builtins is function Meta (Item : in Ptr_With_Meta) return Mal.T is (Item.Ref.all.Meta); - function With_Meta (Data : in Ptr; - Meta : in Mal.T) return Mal.T + function With_Meta (Data : in Ptr; + Metadata : in Mal.T) return Mal.T is (Kind_Builtin_With_Meta, (Ada.Finalization.Controlled with new Rec' (Data => Data, - Meta => Meta, + Meta => Metadata, Refs => 1))); - function With_Meta (Data : in Ptr_With_Meta; - Meta : in Mal.T) return Mal.T + function With_Meta (Data : in Ptr_With_Meta; + Metadata : in Mal.T) return Mal.T -- Do not try to reuse the memory. We can hope that this kind of -- nonsense will be rare. - is (With_Meta (Data.Data, Meta)); + is (With_Meta (Data.Data, Metadata)); end Types.Builtins; diff --git a/ada2/types-builtins.ads b/ada2/types-builtins.ads index abb118e30b..7ad35db427 100644 --- a/ada2/types-builtins.ads +++ b/ada2/types-builtins.ads @@ -22,10 +22,10 @@ package Types.Builtins is -- Assignment give another reference to the same storage. - function With_Meta (Data : in Ptr; - Meta : in Mal.T) return Mal.T with Inline; - function With_Meta (Data : in Ptr_With_Meta; - Meta : in Mal.T) return Mal.T with Inline; + function With_Meta (Data : in Ptr; + Metadata : in Mal.T) return Mal.T with Inline; + function With_Meta (Data : in Ptr_With_Meta; + Metadata : in Mal.T) return Mal.T with Inline; function Meta (Item : in Ptr_With_Meta) return Mal.T with Inline; function Data (Item : in Ptr_With_Meta) return Ptr with Inline; diff --git a/ada2/types-functions.adb b/ada2/types-functions.adb index 5360d83144..b40914767c 100644 --- a/ada2/types-functions.adb +++ b/ada2/types-functions.adb @@ -17,7 +17,7 @@ package body Types.Functions is Refs : Natural := 1; Args : Lists.Ptr; Expr : Mal.T; - Env : Environments.Closure_Ptr; + Env : Environments.Closure_Ptr := Environments.Null_Closure; Varargs : Boolean; Meta : Mal.T := Mal.Nil; end record; @@ -91,7 +91,8 @@ package body Types.Functions is procedure Set_Binds (Item : in Ptr; Env : in Environments.Ptr; - Args : in Mal.T_Array) is + Args : in Mal.T_Array) + is R : Rec renames Item.Ref.all; begin if R.Varargs then @@ -119,7 +120,8 @@ package body Types.Functions is procedure Set_Binds (Item : in Ptr; Env : in Environments.Ptr; - Args : in Lists.Ptr) is + Args : in Lists.Ptr) + is R : Rec renames Item.Ref.all; begin if R.Varargs then @@ -145,9 +147,9 @@ package body Types.Functions is end if; end Set_Binds; - function With_Meta (Data : in Ptr; - Meta : in Mal.T) - return Mal.T is + function With_Meta (Data : in Ptr; + Metadata : in Mal.T) return Mal.T + is Old : Rec renames Data.Ref.all; Ref : Acc; begin @@ -155,13 +157,13 @@ package body Types.Functions is if Old.Refs = 1 then Ref := Data.Ref; Old.Refs := 2; - Old.Meta := Meta; + Old.Meta := Metadata; else Ref := new Rec'(Args => Data.Ref.all.Args, Expr => Data.Ref.all.Expr, Env => Data.Ref.all.Env, Varargs => Data.Ref.all.Varargs, - Meta => Meta, + Meta => Metadata, others => <>); end if; diff --git a/ada2/types-functions.ads b/ada2/types-functions.ads index 35aabee2c3..ec996b38be 100644 --- a/ada2/types-functions.ads +++ b/ada2/types-functions.ads @@ -45,9 +45,9 @@ package Types.Functions is function Closure (Item : in Ptr) return Environments.Closure_Ptr with Inline; - function Meta (Item : in Ptr) return Mal.T with inline; - function With_Meta (Data : in Ptr; - Meta : in Mal.T) + function Meta (Item : in Ptr) return Mal.T with Inline; + function With_Meta (Data : in Ptr; + Metadata : in Mal.T) return Mal.T with Inline; private diff --git a/ada2/types-lists.adb b/ada2/types-lists.adb index a20ba3e8a6..7f25702915 100644 --- a/ada2/types-lists.adb +++ b/ada2/types-lists.adb @@ -145,7 +145,8 @@ package body Types.Lists is function Generic_Eval (Container : in Ptr; Env : in Env_Type) - return Ptr is + return Ptr + is -- Take care that automatic deallocation happens if an -- exception is propagated by user code. Old : Rec renames Container.Ref.all; @@ -237,9 +238,9 @@ package body Types.Lists is Last => Args'Length, others => <>))); - function With_Meta (Data : in Ptr; - Meta : in Mal.T) - return Ptr is + function With_Meta (Data : in Ptr; + Metadata : in Mal.T) return Ptr + is Old : Rec renames Data.Ref.all; Ref : Acc; begin @@ -247,11 +248,11 @@ package body Types.Lists is if Old.Refs = 1 then Ref := Data.Ref; Old.Refs := 2; - Old.Meta := Meta; + Old.Meta := Metadata; else Ref := new Rec'(Last => Old.Last, Data => Old.Data, - Meta => Meta, + Meta => Metadata, others => <>); end if; return (AFC with Ref); diff --git a/ada2/types-lists.ads b/ada2/types-lists.ads index b8678f2f1c..1e9906315d 100644 --- a/ada2/types-lists.ads +++ b/ada2/types-lists.ads @@ -55,8 +55,8 @@ package Types.Lists is return Mal.T; function Meta (Item : in Ptr) return Mal.T with Inline; - function With_Meta (Data : in Ptr; - Meta : in Mal.T) + function With_Meta (Data : in Ptr; + Metadata : in Mal.T) return Ptr; private diff --git a/ada2/types-maps.adb b/ada2/types-maps.adb index 9f70440b50..b0c333ff4a 100644 --- a/ada2/types-maps.adb +++ b/ada2/types-maps.adb @@ -122,7 +122,8 @@ package body Types.Maps is function Generic_Eval (Container : in Ptr; Env : in Env_Type) - return Mal.T is + return Mal.T + is -- Copy the whole hash in order to avoid recomputing the hash -- for each key, even if it implies unneeded calls to adjust -- and finalize for Mal_Type values. @@ -244,9 +245,10 @@ package body Types.Maps is end; end Vals; - function With_Meta (Data : in Ptr; - Meta : in Mal.T) - return Mal.T is + function With_Meta (Data : in Ptr; + Metadata : in Mal.T) + return Mal.T + is Old : Rec renames Data.Ref.all; Ref : Acc; begin @@ -254,10 +256,10 @@ package body Types.Maps is if Old.Refs = 1 then Ref := Data.Ref; Old.Refs := 2; - Old.Meta := Meta; + Old.Meta := Metadata; else Ref := new Rec'(Data => Old.Data, - Meta => Meta, + Meta => Metadata, others => <>); end if; return (Kind_Map, (AFC with Ref)); diff --git a/ada2/types-maps.ads b/ada2/types-maps.ads index 7de5d954ba..3fd07b459e 100644 --- a/ada2/types-maps.ads +++ b/ada2/types-maps.ads @@ -46,8 +46,8 @@ package Types.Maps is function Meta (Container : in Ptr) return Mal.T with Inline; - function With_Meta (Data : in Ptr; - Meta : in Mal.T) + function With_Meta (Data : in Ptr; + Metadata : in Mal.T) return Mal.T; private From 11932a6c8928b0776c360509eece5a5d0042afbc Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Wed, 6 Mar 2019 19:48:51 +0100 Subject: [PATCH 0797/2308] Improve conformity with the MAL process, performance and readability. Conformity: Move readline to readline.ads and drop the Interactive_Loop subprogram. Let the main Read and Print functions do input/output. Rename environments.ads to envs.ads (the name of the eval parameter seems more important). Move association of formal and actual parameters to the env "constructor". Use the documentation names whenever possible (especially, make calls to Eval where they are expected and explicit the parameters). Iterate on a NS structure provided by Core, as per the process. Use similar method names for Envs.Ptr and Envs.Closure_Ptr, as the difference is an implementation detail. Performance: Move Map into list methods, swap into atom methods. Pass formal parameters as an array of symbols on the stack, instead of a MAL list. Readability: Replace some one-letter names. Use renamings when the lines become too long. Split Pr_Str in small subprograms. Declare the access to built-in functions in Types.Mal. Consistent names. Move redundant comments into README. --- ada2/Makefile | 23 +- ada2/README | 20 +- ada2/core.adb | 430 +++++++++++++--------------- ada2/core.ads | 24 +- ada2/{environments.adb => envs.adb} | 131 +++++++-- ada2/{environments.ads => envs.ads} | 56 ++-- ada2/eval_cb.ads | 11 + ada2/printer.adb | 194 +++++++------ ada2/printer.ads | 5 + ada2/reader.adb | 3 +- ada2/readline.adb | 32 +++ ada2/readline.ads | 7 + ada2/step0_repl.adb | 64 ++--- ada2/step1_read_print.adb | 77 ++--- ada2/step2_eval.adb | 124 ++++---- ada2/step3_env.adb | 145 +++++----- ada2/step4_if_fn_do.adb | 189 ++++++------ ada2/step5_tco.adb | 190 ++++++------ ada2/step6_file.adb | 194 +++++++------ ada2/step7_quote.adb | 230 +++++++-------- ada2/step8_macros.adb | 256 +++++++++-------- ada2/step9_try.adb | 279 +++++++++--------- ada2/stepa_mal.adb | 289 ++++++++++--------- ada2/types-atoms.adb | 48 +++- ada2/types-atoms.ads | 13 +- ada2/types-builtins.adb | 28 +- ada2/types-builtins.ads | 39 +-- ada2/types-functions.adb | 173 +++++------ ada2/types-functions.ads | 58 ++-- ada2/types-lists.adb | 170 +++++++---- ada2/types-lists.ads | 10 +- ada2/types-mal.adb | 4 +- ada2/types-mal.ads | 14 +- ada2/types-maps.ads | 11 - ada2/types-symbols.adb | 22 ++ ada2/types-symbols.ads | 19 +- 36 files changed, 1858 insertions(+), 1724 deletions(-) rename ada2/{environments.adb => envs.adb} (74%) rename ada2/{environments.ads => envs.ads} (65%) create mode 100644 ada2/eval_cb.ads create mode 100644 ada2/readline.adb create mode 100644 ada2/readline.ads diff --git a/ada2/Makefile b/ada2/Makefile index 20cc5692aa..4bbee5d0c9 100644 --- a/ada2/Makefile +++ b/ada2/Makefile @@ -35,9 +35,11 @@ clean: # Tell Make how to detect out-of-date executables, and let gnatmake do # the rest when it must be executed. TYPES := \ - environments.ads environments.adb \ + envs.ads envs.adb \ + eval_cb.ads \ printer.ads printer.adb \ reader.ads reader.adb \ + readline.ads \ types-atoms.ads types-atoms.adb \ types-builtins.ads types-builtins.adb \ types-functions.ads types-functions.adb \ @@ -59,12 +61,13 @@ $(steps) : .PHONY: steps.diff steps.diff: - diff -u step1*.adb step2*.adb; \ - diff -u step2*.adb step3*.adb; \ - diff -u step3*.adb step4*.adb; \ - diff -u step4*.adb step5*.adb; \ - diff -u step5*.adb step6*.adb; \ - diff -u step6*.adb step7*.adb; \ - diff -u step7*.adb step8*.adb; \ - diff -u step8*.adb step9*.adb; \ - diff -u step9*.adb stepa*.adb || true + diff -u step0_*.adb step1_*.adb || true + diff -u step1_*.adb step2_*.adb || true + diff -u step2_*.adb step3_*.adb || true + diff -u step3_*.adb step4_*.adb || true + diff -u step4_*.adb step5_*.adb || true + diff -u step5_*.adb step6_*.adb || true + diff -u step6_*.adb step7_*.adb || true + diff -u step7_*.adb step8_*.adb || true + diff -u step8_*.adb step9_*.adb || true + diff -u step9_*.adb stepa_*.adb || true diff --git a/ada2/README b/ada2/README index 1593347ac3..c2d8e560b7 100644 --- a/ada2/README +++ b/ada2/README @@ -1,15 +1,15 @@ Comparison with the first Ada implementation. The first implementation was deliberately compatible with all Ada -compilers, while this one illustrates various Ada 2012 features, like +compilers, while this one illustrates various Ada 2012 features: assertions, preconditions, invariants, initial assignment for limited types, limited imports... The variant MAL type is implemented with a discriminant instead of object-style dispatching. This allows more static and dynamic checks, but also two crucial performance improvements: -* Nil, boolean and integers are passed by value without dynamic - allocation. +* Nil, boolean, integers and built-in functions are passed by value + without dynamic allocation. * Lists are implemented as C-style arrays, and most of them can be allocated on the stack. @@ -24,18 +24,24 @@ bounds and discriminant consistency are only enabled during tests). There are also similarities with the first implementation. For example, both rely on user-defined finalization to handle recursive -structures without garbage collecting. +structures without garbage collecting. Also, most pointer types are +wrapped into a finalized type counting references. +Some remarks if anyone works on this. -About reference reference counting. +* The default value for such wrapped pointers is invalid, new + variables must be assigned immediately. This is usually enforced by + a hidden discriminant, but this would prevent the type to become a + field inside Types.Mal.T. So we usse a private invariant as a a + fallback. * The finalize procedure may be called twice, so it does nothing when the reference count is zero, meaning that we are reaching Finalize recursively. + * In implementations, a consistent object (that will be deallocated automatically) must be built before any exception is raised by user - code (for example 'map' may run user functions). - + code (for example the 'map' built-in function may run user code). Known bugs: the third step of the perf^ada2 target fails during the final storage deallocation when the executable is built with -gnatp. I diff --git a/ada2/core.adb b/ada2/core.adb index cbbef7f4d5..03e2ccc150 100644 --- a/ada2/core.adb +++ b/ada2/core.adb @@ -3,65 +3,63 @@ with Ada.Characters.Latin_1; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Environments; pragma Elaborate_All (Environments); +with Envs; +with Eval_Cb; with Types.Atoms; with Types.Builtins; with Types.Functions; with Types.Lists; with Types.Maps; -with Types.Symbols.Names; pragma Elaborate_All (Types.Symbols); +with Types.Symbols.Names; with Printer; with Reader; package body Core is use Types; - use type Mal.T; - package ASU renames Ada.Strings.Unbounded; + -- Used by time_ms. Start_Time : constant Ada.Calendar.Time := Ada.Calendar.Clock; - function Apply_Helper (Func : in Mal.T; - Args : in Mal.T_Array; - Name : in String) return Mal.T with Inline; - -- If Func is not executable, report an exception using "name" as - -- the built-in function name. + -- In the following helpers, "name" is the one reported by error + -- messages. generic Kind : in Kind_Type; Name : in String; function Generic_Kind_Test (Args : in Mal.T_Array) return Mal.T; function Generic_Kind_Test (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 - then raise Argument_Error with Name & ": expects 1 argument" - else (Kind_Boolean, Args (Args'First).Kind = Kind)); + is (if Args'Length /= 1 then + raise Argument_Error with Name & ": expects 1 argument" + else + (Kind_Boolean, Args (Args'First).Kind = Kind)); generic with function Ada_Operator (Left, Right : in Integer) return Integer; Name : in String; function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T; function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 2 - then raise Argument_Error with Name & ": expects 2 arguments" - elsif (for some A of Args => A.Kind /= Kind_Number) - then raise Argument_Error with Name & ": expects numbers" - else (Kind_Number, Ada_Operator (Args (Args'First).Ada_Number, - Args (Args'Last).Ada_Number))); - + is (if Args'Length /= 2 then + raise Argument_Error with Name & ": expects 2 arguments" + elsif (for some A of Args => A.Kind /= Kind_Number) then + raise Argument_Error with Name & ": expects numbers" + else + (Kind_Number, Ada_Operator (Args (Args'First).Number, + Args (Args'Last).Number))); generic with function Ada_Operator (Left, Right : in Integer) return Boolean; Name : in String; function Generic_Comparison (Args : in Mal.T_Array) return Mal.T; function Generic_Comparison (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 2 - then raise Argument_Error with Name & ": expects 2 arguments" - elsif (for some A of Args => A.Kind /= Kind_Number) - then raise Argument_Error with Name & ": expects numbers" - else (Kind_Boolean, Ada_Operator (Args (Args'First).Ada_Number, - Args (Args'Last).Ada_Number))); - - -- Built-in functions from this package. + is (if Args'Length /= 2 then + raise Argument_Error with Name & ": expects 2 arguments" + elsif (for some A of Args => A.Kind /= Kind_Number) then + raise Argument_Error with Name & ": expects numbers" + else + (Kind_Boolean, Ada_Operator (Args (Args'First).Number, + Args (Args'Last).Number))); + function Addition is new Generic_Mal_Operator ("+", "+"); function Apply (Args : in Mal.T_Array) return Mal.T; function Division is new Generic_Mal_Operator ("/", "/"); @@ -86,7 +84,6 @@ package body Core is function Keyword (Args : in Mal.T_Array) return Mal.T; function Less_Equal is new Generic_Comparison ("<=", "<="); function Less_Than is new Generic_Comparison ("<", "<"); - function Map (Args : in Mal.T_Array) return Mal.T; function Meta (Args : in Mal.T_Array) return Mal.T; function Pr_Str (Args : in Mal.T_Array) return Mal.T; function Println (Args : in Mal.T_Array) return Mal.T; @@ -98,7 +95,6 @@ package body Core is function Slurp (Args : in Mal.T_Array) return Mal.T; function Str (Args : in Mal.T_Array) return Mal.T; function Subtraction is new Generic_Mal_Operator ("-", "-"); - function Swap (Args : in Mal.T_Array) return Mal.T; function Symbol (Args : in Mal.T_Array) return Mal.T; function Throw (Args : in Mal.T_Array) return Mal.T; function Time_Ms (Args : in Mal.T_Array) return Mal.T; @@ -106,58 +102,49 @@ package body Core is ---------------------------------------------------------------------- - function Apply_Helper (Func : in Mal.T; - Args : in Mal.T_Array; - Name : in String) return Mal.T - is - begin - case Func.Kind is - when Kind_Builtin => - return Func.Builtin.all (Args); - when Kind_Builtin_With_Meta => - return Func.Builtin_With_Meta.Data.all (Args); - when Kind_Function => - declare - Env : constant Environments.Ptr - := Func.Function_Value.Closure.Closure_Sub; - begin - Func.Function_Value.Set_Binds (Env, Args); - return Eval_Ref.all (Func.Function_Value.Expression, Env); - end; - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Symbol | Kind_Keyword | Kind_List | Kind_Vector | Kind_Map - | Kind_Macro => - raise Argument_Error with Name & ": cannot execute " - & ASU.To_String (Printer.Pr_Str (Func)); - end case; - end Apply_Helper; - function Apply (Args : in Mal.T_Array) return Mal.T is use type Lists.Ptr; begin if Args'Length < 2 then raise Argument_Error with "apply: expects at least 2 arguments"; elsif Args (Args'Last).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "apply: last arg must a be list or vector"; - else - return Apply_Helper (Args (Args'First), - Args (Args'First + 1 .. Args'Last - 1) - & Args (Args'Last).L, - "apply"); + raise Argument_Error with "apply: last arg must be a list or vector"; end if; + declare + F : Mal.T renames Args (Args'First); + A : constant Mal.T_Array + := Args (Args'First + 1 .. Args'Last - 1) & Args (Args'Last).List; + begin + case F.Kind is + when Kind_Builtin => + return F.Builtin.all (A); + when Kind_Builtin_With_Meta => + return F.Builtin_With_Meta.Builtin.all (A); + when Kind_Function => + return F.Fn.Apply (A); + when others => + raise Argument_Error + with "apply: cannot call " & Printer.Img (F); + end case; + end; end Apply; - function Equals (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 2 then - raise Argument_Error with "=: expects 2 arguments" - else - (Kind_Boolean, Args (Args'First) = Args (Args'Last))); + function Equals (Args : in Mal.T_Array) return Mal.T is + use type Mal.T; + begin + if Args'Length /= 2 then + raise Argument_Error with "=: expects 2 arguments"; + else + return (Kind_Boolean, Args (Args'First) = Args (Args'Last)); + end if; + end Equals; function Eval (Args : in Mal.T_Array) return Mal.T is (if Args'Length /= 1 then raise Argument_Error with "eval: expects 1 argument" else - Eval_Ref.all (Args (Args'First), Environments.Repl)); + Eval_Cb.Cb.all (Ast => Args (Args'First), + Env => Envs.Repl)); function Is_False (Args : in Mal.T_Array) return Mal.T is (if Args'Length /= 1 then @@ -193,81 +180,136 @@ package body Core is else (Kind_Keyword, Args (Args'First).S)); - function Map (Args : in Mal.T_Array) return Mal.T is + function Meta (Args : in Mal.T_Array) return Mal.T is begin - if Args'Length /= 2 then - raise Argument_Error with "map: expects 2 arguments"; - elsif Args (Args'Last).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "map: arg 2 must be a list or vector"; + if Args'Length /= 1 then + raise Argument_Error with "meta: expects 1 argument"; end if; declare - R : Mal.T_Array (1 .. Args (Args'Last).L.Length); + A1 : Mal.T renames Args (Args'First); begin - for I in R'Range loop - R (I) := Apply_Helper (Args (Args'First), - Mal.T_Array'(1 => Args (Args'Last).L.Element (I)), - "map"); - end loop; - return Lists.List (R); - end; - end Map; - - function Meta (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 then - raise Argument_Error with "meta: expects 1 argument" - else - (case Args (Args'First).Kind is + case A1.Kind is when Kind_List | Kind_Vector => - Args (Args'First).L.Meta, + return A1.List.Meta; when Kind_Map => - Args (Args'First).Map.Meta, + return A1.Map.Meta; when Kind_Function => - Args (Args'First).Function_Value.Meta, + return A1.Fn.Meta; when Kind_Builtin_With_Meta => - Args (Args'First).Builtin_With_Meta.Meta, + return A1.Builtin_With_Meta.Meta; when Kind_Builtin => - Mal.Nil, - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | - Kind_String | Kind_Symbol | Kind_Keyword | Kind_Macro => + return Mal.Nil; + when others => raise Argument_Error - with "meta: expects a list, vector, map or function")); + with "meta: expects a list, vector, map or function"; + end case; + end; + end Meta; + + function Ns return Binding_List + is ((Symbols.Constructor ("+"), Addition'Access), + (Symbols.Constructor ("apply"), Apply'Access), + (Symbols.Constructor ("assoc"), Maps.Assoc'Access), + (Symbols.Constructor ("atom"), Atoms.Atom'Access), + (Symbols.Constructor ("concat"), Lists.Concat'Access), + (Symbols.Constructor ("conj"), Lists.Conj'Access), + (Symbols.Constructor ("cons"), Lists.Cons'Access), + (Symbols.Constructor ("contains?"), Maps.Contains'Access), + (Symbols.Constructor ("count"), Lists.Count'Access), + (Symbols.Names.Deref, Atoms.Deref'Access), + (Symbols.Constructor ("dissoc"), Maps.Dissoc'Access), + (Symbols.Constructor ("/"), Division'Access), + (Symbols.Constructor ("="), Equals'Access), + (Symbols.Constructor ("eval"), Eval'Access), + (Symbols.Constructor ("first"), Lists.First'Access), + (Symbols.Constructor ("get"), Maps.Get'Access), + (Symbols.Constructor (">="), Greater_Equal'Access), + (Symbols.Constructor (">"), Greater_Than'Access), + (Symbols.Constructor ("hash-map"), Maps.Hash_Map'Access), + (Symbols.Constructor ("atom?"), Is_Atom'Access), + (Symbols.Constructor ("empty?"), Lists.Is_Empty'Access), + (Symbols.Constructor ("false?"), Is_False'Access), + (Symbols.Constructor ("fn?"), Is_Function'Access), + (Symbols.Constructor ("keyword?"), Is_Keyword'Access), + (Symbols.Constructor ("list?"), Is_List'Access), + (Symbols.Constructor ("macro?"), Is_Macro'Access), + (Symbols.Constructor ("map?"), Is_Map'Access), + (Symbols.Constructor ("nil?"), Is_Nil'Access), + (Symbols.Constructor ("number?"), Is_Number'Access), + (Symbols.Constructor ("sequential?"), Is_Sequential'Access), + (Symbols.Constructor ("string?"), Is_String'Access), + (Symbols.Constructor ("symbol?"), Is_Symbol'Access), + (Symbols.Constructor ("true?"), Is_True'Access), + (Symbols.Constructor ("vector?"), Is_Vector'Access), + (Symbols.Constructor ("keys"), Maps.Keys'Access), + (Symbols.Constructor ("keyword"), Keyword'Access), + (Symbols.Constructor ("<="), Less_Equal'Access), + (Symbols.Constructor ("<"), Less_Than'Access), + (Symbols.Constructor ("list"), Lists.List'Access), + (Symbols.Constructor ("map"), Lists.Map'Access), + (Symbols.Constructor ("meta"), Meta'Access), + (Symbols.Constructor ("nth"), Lists.Nth'Access), + (Symbols.Constructor ("pr-str"), Pr_Str'Access), + (Symbols.Constructor ("println"), Println'Access), + (Symbols.Constructor ("prn"), Prn'Access), + (Symbols.Constructor ("*"), Product'Access), + (Symbols.Constructor ("read-string"), Read_String'Access), + (Symbols.Constructor ("readline"), Readline'Access), + (Symbols.Constructor ("reset!"), Atoms.Reset'Access), + (Symbols.Constructor ("rest"), Lists.Rest'Access), + (Symbols.Constructor ("seq"), Seq'Access), + (Symbols.Constructor ("slurp"), Slurp'Access), + (Symbols.Constructor ("str"), Str'Access), + (Symbols.Constructor ("-"), Subtraction'Access), + (Symbols.Constructor ("swap!"), Atoms.Swap'Access), + (Symbols.Constructor ("symbol"), Symbol'Access), + (Symbols.Constructor ("throw"), Throw'Access), + (Symbols.Constructor ("time-ms"), Time_Ms'Access), + (Symbols.Constructor ("vals"), Maps.Vals'Access), + (Symbols.Constructor ("vector"), Lists.Vector'Access), + (Symbols.Names.With_Meta, With_Meta'Access)); function Pr_Str (Args : in Mal.T_Array) return Mal.T is + R : ASU.Unbounded_String := ASU.Null_Unbounded_String; + Started : Boolean := False; begin - return R : Mal.T := (Kind_String, ASU.Null_Unbounded_String) do - if 0 < Args'Length then - ASU.Append (R.S, Printer.Pr_Str (Args (Args'First))); - for I in Args'First + 1 .. Args'Last loop - ASU.Append (R.S, ' '); - ASU.Append (R.S, Printer.Pr_Str (Args (I))); - end loop; + for A of Args loop + if Started then + ASU.Append (R, ' '); + else + Started := True; end if; - end return; + ASU.Append (R, Printer.Pr_Str (A)); + end loop; + return (Kind_String, R); end Pr_Str; function Println (Args : in Mal.T_Array) return Mal.T is - use Ada.Text_IO.Unbounded_IO; + Started : Boolean := False; begin - if 0 < Args'Length then - Put (Printer.Pr_Str (Args (Args'First), Readably => False)); - for I in Args'First + 1 .. Args'Last loop + for A of Args loop + if Started then Ada.Text_IO.Put (' '); - Put (Printer.Pr_Str (Args (I), Readably => False)); - end loop; - end if; + else + Started := True; + end if; + Ada.Text_IO.Unbounded_IO.Put (Printer.Pr_Str (A, Readably => False)); + end loop; Ada.Text_IO.New_Line; return Mal.Nil; end Println; function Prn (Args : in Mal.T_Array) return Mal.T is + Started : Boolean := False; begin - if 0 < Args'Length then - Ada.Text_IO.Unbounded_IO.Put (Printer.Pr_Str (Args (Args'First))); - for I in Args'First + 1 .. Args'Last loop + for A of Args loop + if Started then Ada.Text_IO.Put (' '); - Ada.Text_IO.Unbounded_IO.Put (Printer.Pr_Str (Args (I))); - end loop; - end if; + else + Started := True; + end if; + Ada.Text_IO.Unbounded_IO.Put (Printer.Pr_Str (A)); + end loop; Ada.Text_IO.New_Line; return Mal.Nil; end Prn; @@ -278,13 +320,12 @@ package body Core is raise Argument_Error with "readline: expects 1 argument"; elsif Args (Args'First).Kind not in Kind_Keyword | Kind_String then raise Argument_Error with "readline: expects a keyword or string"; + end if; + Ada.Text_IO.Unbounded_IO.Put (Args (Args'First).S); + if Ada.Text_IO.End_Of_File then + return Mal.Nil; else - Ada.Text_IO.Unbounded_IO.Put (Args (Args'First).S); - if Ada.Text_IO.End_Of_File then - return Mal.Nil; - else - return (Kind_String, Ada.Text_IO.Unbounded_IO.Get_Line); - end if; + return (Kind_String, Ada.Text_IO.Unbounded_IO.Get_Line); end if; end Readline; @@ -319,10 +360,10 @@ package body Core is end; end if; when Kind_List | Kind_Vector => - if Args (Args'First).L.Length = 0 then + if Args (Args'First).List.Length = 0 then return Mal.Nil; else - return (Kind_List, Args (Args'First).L); + return (Kind_List, Args (Args'First).List); end if; when others => raise Argument_Error with "seq: expects a string, list or vector"; @@ -354,32 +395,14 @@ package body Core is end Slurp; function Str (Args : in Mal.T_Array) return Mal.T is + R : ASU.Unbounded_String := ASU.Null_Unbounded_String; begin - return R : Mal.T := (Kind_String, ASU.Null_Unbounded_String) do - for Arg of Args loop - ASU.Append (R.S, Printer.Pr_Str (Arg, Readably => False)); - end loop; - end return; + for A of Args loop + ASU.Append (R, Printer.Pr_Str (A, Readably => False)); + end loop; + return (Kind_String, R); end Str; - function Swap (Args : in Mal.T_Array) return Mal.T is - begin - if Args'Length < 2 then - raise Argument_Error with "swap!: expects at least 2 arguments"; - elsif Args (Args'First).Kind /= Kind_Atom then - raise Argument_Error with "swap!: arg 1 must be an atom"; - end if; - declare - use type Mal.T_Array; - X : Mal.T renames Atoms.Deref (Args (Args'First .. Args'First)); - FX : Mal.T renames Apply_Helper (Args (Args'First + 1), - X & Args (Args'First + 2 .. Args'Last), - "swap!"); - begin - return Atoms.Reset (Mal.T_Array'(Args (Args'First), FX)); - end; - end Swap; - function Symbol (Args : in Mal.T_Array) return Mal.T is (if Args'Length /= 1 then raise Argument_Error with "symbol?: expects 1 argument" @@ -407,88 +430,33 @@ package body Core is Integer (1000.0 * (Ada.Calendar.Clock - Start_Time))); end Time_Ms; - function With_Meta (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 2 then - raise Argument_Error with "with-meta: expects 2 arguments" - else (case Args (Args'First).Kind is - when Kind_Builtin_With_Meta => - Args (Args'First).Builtin_With_Meta.With_Meta (Args (Args'Last)), - when Kind_Builtin => - Builtins.With_Meta (Args (Args'First).Builtin, Args (Args'Last)), - when Kind_List => - (Kind_List, Args (Args'First).L.With_Meta (Args (Args'Last))), - when Kind_Vector => - (Kind_Vector, Args (Args'First).L.With_Meta (Args (Args'Last))), - when Kind_Map => - Args (Args'First).Map.With_Meta (Args (Args'Last)), - when Kind_Function => - Args (Args'First).Function_Value.With_Meta (Args (Args'Last)), - when others => - Args (Args'First))); - - use Symbols; - R : Environments.Ptr renames Environments.Repl; - B : Kind_Type renames Kind_Builtin; -begin -- Core - R.Set (Constructor ("+"), (B, Addition'Access)); - R.Set (Constructor ("apply"), (B, Apply'Access)); - R.Set (Constructor ("assoc"), (B, Maps.Assoc'Access)); - R.Set (Constructor ("atom"), (B, Atoms.Atom'Access)); - R.Set (Constructor ("concat"), (B, Lists.Concat'Access)); - R.Set (Constructor ("conj"), (B, Lists.Conj'Access)); - R.Set (Constructor ("cons"), (B, Lists.Cons'Access)); - R.Set (Constructor ("contains?"), (B, Maps.Contains'Access)); - R.Set (Constructor ("count"), (B, Lists.Count'Access)); - R.Set (Names.Deref, (B, Atoms.Deref'Access)); - R.Set (Constructor ("dissoc"), (B, Maps.Dissoc'Access)); - R.Set (Constructor ("/"), (B, Division'Access)); - R.Set (Constructor ("="), (B, Equals'Access)); - R.Set (Constructor ("eval"), (B, Eval'Access)); - R.Set (Constructor ("first"), (B, Lists.First'Access)); - R.Set (Constructor ("get"), (B, Maps.Get'Access)); - R.Set (Constructor (">="), (B, Greater_Equal'Access)); - R.Set (Constructor (">"), (B, Greater_Than'Access)); - R.Set (Constructor ("hash-map"), (B, Maps.Hash_Map'Access)); - R.Set (Constructor ("atom?"), (B, Is_Atom'Access)); - R.Set (Constructor ("empty?"), (B, Lists.Is_Empty'Access)); - R.Set (Constructor ("false?"), (B, Is_False'Access)); - R.Set (Constructor ("fn?"), (B, Is_Function'Access)); - R.Set (Constructor ("keyword?"), (B, Is_Keyword'Access)); - R.Set (Constructor ("list?"), (B, Is_List'Access)); - R.Set (Constructor ("macro?"), (B, Is_Macro'Access)); - R.Set (Constructor ("map?"), (B, Is_Map'Access)); - R.Set (Constructor ("nil?"), (B, Is_Nil'Access)); - R.Set (Constructor ("number?"), (B, Is_Number'Access)); - R.Set (Constructor ("sequential?"), (B, Is_Sequential'Access)); - R.Set (Constructor ("string?"), (B, Is_String'Access)); - R.Set (Constructor ("symbol?"), (B, Is_Symbol'Access)); - R.Set (Constructor ("true?"), (B, Is_True'Access)); - R.Set (Constructor ("vector?"), (B, Is_Vector'Access)); - R.Set (Constructor ("keys"), (B, Maps.Keys'Access)); - R.Set (Constructor ("keyword"), (B, Keyword'Access)); - R.Set (Constructor ("<="), (B, Less_Equal'Access)); - R.Set (Constructor ("<"), (B, Less_Than'Access)); - R.Set (Constructor ("list"), (B, Lists.List'Access)); - R.Set (Constructor ("map"), (B, Map'Access)); - R.Set (Constructor ("meta"), (B, Meta'Access)); - R.Set (Constructor ("nth"), (B, Lists.Nth'Access)); - R.Set (Constructor ("pr-str"), (B, Pr_Str'Access)); - R.Set (Constructor ("println"), (B, Println'Access)); - R.Set (Constructor ("prn"), (B, Prn'Access)); - R.Set (Constructor ("*"), (B, Product'Access)); - R.Set (Constructor ("read-string"), (B, Read_String'Access)); - R.Set (Constructor ("readline"), (B, Readline'Access)); - R.Set (Constructor ("reset!"), (B, Atoms.Reset'Access)); - R.Set (Constructor ("rest"), (B, Lists.Rest'Access)); - R.Set (Constructor ("seq"), (B, Seq'Access)); - R.Set (Constructor ("slurp"), (B, Slurp'Access)); - R.Set (Constructor ("str"), (B, Str'Access)); - R.Set (Constructor ("-"), (B, Subtraction'Access)); - R.Set (Constructor ("swap!"), (B, Swap'Access)); - R.Set (Constructor ("symbol"), (B, Symbol'Access)); - R.Set (Constructor ("throw"), (B, Throw'Access)); - R.Set (Constructor ("time-ms"), (B, Time_Ms'Access)); - R.Set (Constructor ("vals"), (B, Maps.Vals'Access)); - R.Set (Constructor ("vector"), (B, Lists.Vector'Access)); - R.Set (Names.With_Meta, (B, With_Meta'Access)); + function With_Meta (Args : in Mal.T_Array) return Mal.T is + begin + if Args'Length /= 2 then + raise Argument_Error with "with-meta: expects 2 arguments"; + end if; + declare + A1 : Mal.T renames Args (Args'First); + A2 : Mal.T renames Args (Args'Last); + begin + case A1.Kind is + when Kind_Builtin_With_Meta => + return A1.Builtin_With_Meta.With_Meta (A2); + when Kind_Builtin => + return Builtins.With_Meta (A1.Builtin, A2); + when Kind_List => + return (Kind_List, A1.List.With_Meta (A2)); + when Kind_Vector => + return (Kind_Vector, A1.List.With_Meta (A2)); + when Kind_Map => + return A1.Map.With_Meta (A2); + when Kind_Function => + return A1.Fn.With_Meta (A2); + when others => + raise Argument_Error + with "with-meta: expects a list, vector, map or function"; + end case; + end; + end With_Meta; + end Core; diff --git a/ada2/core.ads b/ada2/core.ads index 1326d71857..b276093cd0 100644 --- a/ada2/core.ads +++ b/ada2/core.ads @@ -1,19 +1,23 @@ -limited with Environments; +with Types.Symbols; with Types.Mal; package Core with Elaborate_Body is - -- Initialization of this package fills Environments.Repl with - -- built-in functions. + type Binding is record + Symbol : Types.Symbols.Ptr; + Builtin : Types.Mal.Builtin_Ptr; + end record; - Eval_Ref : access function (Ast : in Types.Mal.T; - Env : in Environments.Ptr) - return Types.Mal.T; - -- Set by the main program at startup. + type Binding_List is array (Positive range <>) of Binding; + + function Ns return Binding_List; + -- A list of built-in symbols and functionse. + -- A constant would make sense, but + -- * implementing it in the private part Exception_Throwed : exception; - Last_Exception : Types.Mal.T := (Kind => Types.Kind_Nil); - -- When the exception is throwed, Last_Exception is set with the - -- related Data. + Last_Exception : Types.Mal.T := Types.Mal.Nil; + -- When the "throw" builtin is executed, it assigns its argument + -- to Last_Exception, then raises this Ada exception. end Core; diff --git a/ada2/environments.adb b/ada2/envs.adb similarity index 74% rename from ada2/environments.adb rename to ada2/envs.adb index d0e0802c69..ae197eea1d 100644 --- a/ada2/environments.adb +++ b/ada2/envs.adb @@ -1,7 +1,9 @@ with Ada.Containers.Hashed_Maps; with Ada.Unchecked_Deallocation; -package body Environments is +with Types.Symbols.Names; + +package body Envs is use Types; @@ -63,6 +65,18 @@ package body Environments is procedure Free is new Ada.Unchecked_Deallocation (Heap_Record, Heap_Access); procedure Unreference (Reference : in out Heap_Access); + procedure Set_Binds (M : in out HM.Map; + Binds : in Symbols.Symbol_Array; + Exprs : in Mal.T_Array) + with Inline; + procedure Set_Binds_Macro (M : in out HM.Map; + Binds : in Symbols.Symbol_Array; + Exprs : in Lists.Ptr) + with Inline; + -- These two procedures are redundant, but sharing the code would + -- be ugly or inefficient. They are separated as inline procedures + -- in order to ease comparison, though. + ---------------------------------------------------------------------- procedure Adjust (Object : in out Closure_Ptr) is @@ -72,18 +86,6 @@ package body Environments is end if; end Adjust; - function Closure_Sub (Outer : in Closure_Ptr'Class) return Ptr is - begin - Outer.Ref.all.Refs := Outer.Ref.all.Refs + 1; - Top := Top + 1; - pragma Assert (Stack (Top).Data.Is_Empty); - pragma Assert (Stack (Top).Alias = null); - Stack (Top) := (Outer_On_Stack => False, - Outer_Ref => Outer.Ref, - others => <>); - return (Ada.Finalization.Limited_Controlled with Top); - end Closure_Sub; - function Copy_Pointer (Env : in Ptr) return Ptr is begin Stack (Env.Index).Refs := Stack (Env.Index).Refs + 1; @@ -231,10 +233,10 @@ package body Environments is end if; end Finalize; - function Get (Env : in Ptr; - Key : in Symbols.Ptr) - return Mal.T is - Index : Stack_Index := Env.Index; + function Get (Evt : in Ptr; + Key : in Symbols.Ptr) return Mal.T + is + Index : Stack_Index := Evt.Index; Ref : Heap_Access; Definition : HM.Cursor; begin @@ -291,9 +293,16 @@ package body Environments is -- unreferenced alias if any. end Replace_With_Sub; - procedure Replace_With_Closure_Sub (Env : in out Ptr; - Outer : in Closure_Ptr'Class) is + procedure Replace_With_Sub (Env : in out Ptr; + Outer : in Closure_Ptr'Class; + Binds : in Symbols.Symbol_Array; + Exprs : in Mal.T_Array) + is begin + -- Finalize Env before creating the new environment, in case + -- this is the last reference and it can be forgotten. + -- Automatic assignment would construct the new value before + -- finalizing the old one (because this is safer in general). Finalize (Env); Outer.Ref.all.Refs := Outer.Ref.all.Refs + 1; Top := Top + 1; @@ -303,7 +312,9 @@ package body Environments is Outer_Ref => Outer.Ref, others => <>); Env.Index := Top; - end Replace_With_Closure_Sub; + -- Now we can afford raising exceptions. + Set_Binds (Stack (Top).Data, Binds, Exprs); + end Replace_With_Sub; procedure Set (Env : in Ptr; Key : in Symbols.Ptr; @@ -312,7 +323,61 @@ package body Environments is Stack (Env.Index).Data.Include (Key, New_Element); end Set; - function Sub (Outer : in Ptr) return Ptr is + procedure Set_Binds (M : in out HM.Map; + Binds : in Symbols.Symbol_Array; + Exprs : in Mal.T_Array) + is + use type Symbols.Ptr; + Varargs : constant Boolean := 1 < Binds'Length and then + Binds (Binds'Last - 1) = Symbols.Names.Ampersand; + begin + if (if Varargs then + Exprs'Length < Binds'Length - 2 + else + Exprs'Length /= Binds'Length) + then + raise Argument_Error with "user function expected " + & Symbols.To_String (Binds) & ", got" + & Integer'Image (Exprs'Length) & " actual parameters"; + end if; + for I in 0 .. Binds'Length - (if Varargs then 3 else 1) loop + M.Include (Binds (Binds'First + I), Exprs (Exprs'First + I)); + end loop; + if Varargs then + M.Include (Binds (Binds'Last), + Lists.List (Exprs (Exprs'First + Binds'Length - 2 .. Exprs'Last))); + end if; + end Set_Binds; + + procedure Set_Binds_Macro (M : in out HM.Map; + Binds : in Symbols.Symbol_Array; + Exprs : in Lists.Ptr) + is + use type Symbols.Ptr; + Varargs : constant Boolean := 1 < Binds'Length and then + Binds (Binds'Last - 1) = Symbols.Names.Ampersand; + begin + if (if Varargs then + Exprs.Length - 1 < Binds'Length - 2 + else + Exprs.Length - 1 /= Binds'Length) + then + raise Argument_Error with "macro expected " + & Symbols.To_String (Binds) & ", got" + & Integer'Image (Exprs.Length - 1) & "actual parameters"; + end if; + for I in 0 .. Binds'Length - (if Varargs then 3 else 1) loop + M.Include (Binds (Binds'First + I), Exprs.Element (2 + I)); + end loop; + if Varargs then + M.Include (Binds (Binds'Last), Exprs.Slice (Start => Binds'Length)); + end if; + end Set_Binds_Macro; + + function Sub (Outer : in Ptr; + Binds : in Symbols.Symbol_Array; + Exprs : in Lists.Ptr) return Ptr + is R : Stack_Record renames Stack (Outer.Index); begin R.Refs := R.Refs + 1; @@ -321,9 +386,31 @@ package body Environments is pragma Assert (Stack (Top).Alias = null); Stack (Top) := (Outer_Index => Outer.Index, others => <>); + Set_Binds_Macro (Stack (Top).Data, Binds, Exprs); return (Ada.Finalization.Limited_Controlled with Top); end Sub; + function Sub (Outer : in Closure_Ptr'Class; + Binds : in Symbols.Symbol_Array; + Exprs : in Mal.T_Array) return Ptr + is + begin + Outer.Ref.all.Refs := Outer.Ref.all.Refs + 1; + Top := Top + 1; + pragma Assert (Stack (Top).Data.Is_Empty); + pragma Assert (Stack (Top).Alias = null); + Stack (Top) := (Outer_On_Stack => False, + Outer_Ref => Outer.Ref, + others => <>); + -- Take care to construct the result before raising any + -- exception, so that it is finalized correctly. + return R : constant Ptr := (Ada.Finalization.Limited_Controlled with Top) + do + -- Now we can afford raising exceptions. + Set_Binds (Stack (Top).Data, Binds, Exprs); + end return; + end Sub; + procedure Unreference (Reference : in out Heap_Access) is Ref : Heap_Access := Reference; begin @@ -345,4 +432,4 @@ package body Environments is end loop; end Unreference; -end Environments; +end Envs; diff --git a/ada2/environments.ads b/ada2/envs.ads similarity index 65% rename from ada2/environments.ads rename to ada2/envs.ads index c49c9cd78f..b7082a6c17 100644 --- a/ada2/environments.ads +++ b/ada2/envs.ads @@ -1,9 +1,14 @@ private with Ada.Finalization; +with Types.Lists; with Types.Mal; with Types.Symbols; -package Environments with Elaborate_Body is +package Envs with Elaborate_Body is + + -- This package should be named Env, but Ada does not allow formal + -- parameters to be named like a package dependency, and it seems + -- that readability inside Eval is more important. -- This implementation relies on the fact that the caller only -- ever references environments in its execution stack. @@ -32,22 +37,24 @@ package Environments with Elaborate_Body is -- The top environment. function Copy_Pointer (Env : in Ptr) return Ptr with Inline; - - function Sub (Outer : in Ptr) return Ptr with Inline; + -- Allows assignment to a freshly created variable. This is + -- required for tail call optimization, but should be avoided + -- elsewhere. procedure Replace_With_Sub (Env : in out Ptr) with Inline; - -- Like Env := Sub (Outer => Env); except that Env is finalized - -- *before* the assignement, so its memory may be reused by the - -- new environment. + -- Equivalent to Env := Sub (Outer => Env, empty Binds and Exprs), + -- except that such an assignment is forbidden for performance + -- reasons. procedure Set (Env : in Ptr; Key : in Types.Symbols.Ptr; New_Element : in Types.Mal.T) with Inline; - function Get (Env : in Ptr; - Key : in Types.Symbols.Ptr) - return Types.Mal.T; + -- The Find method is merged into the Get method. + + function Get (Evt : in Ptr; + Key : in Types.Symbols.Ptr) return Types.Mal.T; Unknown_Key : exception; -- Function closures. @@ -55,16 +62,33 @@ package Environments with Elaborate_Body is type Closure_Ptr is tagged private; Null_Closure : constant Closure_Ptr; - function Closure_Sub (Outer : in Closure_Ptr'Class) return Ptr; - - procedure Replace_With_Closure_Sub (Env : in out Ptr; - Outer : in Closure_Ptr'Class); - -- Like Env := Closure_Sub (Outer); except that the type is limited. - function New_Closure (Env : in Ptr'Class) return Closure_Ptr; -- The class-wide argument does not make much sense, but avoids -- the compiler wondering on which type is should dispatch. + function Sub (Outer : in Closure_Ptr'Class; + Binds : in Types.Symbols.Symbol_Array; + Exprs : in Types.Mal.T_Array) return Ptr; + -- Construct a new environment with the given closure as outer parent. + -- Then call Set with the paired elements of Binds and Exprs, + -- handling the "&" special formal parameter if present. + -- May raise Argument_Count. + + procedure Replace_With_Sub (Env : in out Ptr; + Outer : in Closure_Ptr'Class; + Binds : in Types.Symbols.Symbol_Array; + Exprs : in Types.Mal.T_Array); + -- Equivalent to Env := Sub (Outer, Binds, Expr); except that such + -- an assignment is forbidden for performance reasons. + + function Sub (Outer : in Ptr; + Binds : in Types.Symbols.Symbol_Array; + Exprs : in Types.Lists.Ptr) return Ptr; + -- Like Sub above, but dedicated to macros. + -- * The Outer parameter is the current environment, not a closure. + -- * The Exprs argument is a list. + -- * Its first element is skipped. + private -- There must be a reference level so that functions may keep @@ -110,4 +134,4 @@ private Null_Closure : constant Closure_Ptr := (Ada.Finalization.Controlled with null); -end Environments; +end Envs; diff --git a/ada2/eval_cb.ads b/ada2/eval_cb.ads new file mode 100644 index 0000000000..9319f3b2a2 --- /dev/null +++ b/ada2/eval_cb.ads @@ -0,0 +1,11 @@ +with Envs; +with Types.Mal; + +package Eval_Cb is + + Cb : access function (Ast : in Types.Mal.T; + Env : in Envs.Ptr) return Types.Mal.T; + -- The main program must register this global callback to the main + -- eval function before some built-in functions are executed. + +end Eval_Cb; diff --git a/ada2/printer.adb b/ada2/printer.adb index f9bee1745c..11b9b9b5e2 100644 --- a/ada2/printer.adb +++ b/ada2/printer.adb @@ -2,45 +2,37 @@ with Ada.Characters.Latin_1; with Types.Atoms; with Types.Functions; +with Types.Symbols; with Types.Lists; with Types.Maps; package body Printer is - function Pr_Str (Ast : in Types.Mal.T; - Readably : in Boolean := True) - return Ada.Strings.Unbounded.Unbounded_String - is - - use Ada.Strings.Unbounded; - use Types; + use Ada.Strings.Unbounded; + use Types; - Buffer : Unbounded_String := Null_Unbounded_String; - -- is appended the result character after character. + function Pr_Str (Ast : in Mal.T; + Readably : in Boolean := True) return Unbounded_String + is procedure Print_Form (Form_Ast : in Mal.T); -- The recursive function traversing Ast for Pr_Str. -- Form_Ast is the current node. - ---------------------------------------------------------------------- - - procedure Print_Form (Form_Ast : in Mal.T) is + -- Helpers for Print_Form. + procedure Print_Number (Number : in Integer) with Inline; + procedure Print_List (List : in Lists.Ptr) with Inline; + procedure Print_Map (Map : in Maps.Ptr) with Inline; + procedure Print_Readably (S : in Unbounded_String) with Inline; + procedure Print_Symbols (List : in Symbols.Symbol_Array) with Inline; - procedure Print_List (List : in Lists.Ptr) with Inline; - -- An helper for Print_Form. + Buffer : Unbounded_String := Null_Unbounded_String; + -- is appended the result character after character. - procedure Print_List (List : in Lists.Ptr) is - begin - if 0 < List.Length then - Print_Form (List.Element (1)); - for I in 2 .. List.Length loop - Append (Buffer, ' '); - Print_Form (List.Element (I)); - end loop; - end if; - end Print_List; + ---------------------------------------------------------------------- - begin -- Print_Form + procedure Print_Form (Form_Ast : in Mal.T) is + begin case Form_Ast.Kind is when Kind_Nil => Append (Buffer, "nil"); @@ -53,94 +45,128 @@ package body Printer is when Kind_Symbol => Append (Buffer, Form_Ast.Symbol.To_String); when Kind_Number => - declare - Img : constant String := Integer'Image (Form_Ast.Ada_Number); - F : Positive := Img'First; - begin - if Img (F) = ' ' then - F := F + 1; - end if; - Append (Buffer, Img (F .. Img'Last)); - end; + Print_Number (Form_Ast.Number); when Kind_Keyword => Append (Buffer, ':'); Append (Buffer, Form_Ast.S); when Kind_String => if Readably then - declare - C : Character; - begin - Append (Buffer, '"'); - for I in 1 .. Length (Form_Ast.S) loop - C := Element (Form_Ast.S, I); - case C is - when '"' | '\' => - Append (Buffer, '\'); - Append (Buffer, C); - when Ada.Characters.Latin_1.LF => - Append (Buffer, "\n"); - when others => - Append (Buffer, C); - end case; - end loop; - Append (Buffer, '"'); - end; + Append (Buffer, '"'); + Print_Readably (Form_Ast.S); + Append (Buffer, '"'); else Append (Buffer, Form_Ast.S); end if; when Kind_List => Append (Buffer, '('); - Print_List (Form_Ast.L); + Print_List (Form_Ast.List); Append (Buffer, ')'); when Kind_Vector => Append (Buffer, '['); - Print_List (Form_Ast.L); + Print_List (Form_Ast.List); Append (Buffer, ']'); when Kind_Map => Append (Buffer, '{'); - declare - Is_First : Boolean := True; - procedure Process (Key : in Mal.T; - Element : in Mal.T); - procedure Iterate is new Maps.Iterate (Process); - procedure Process (Key : in Mal.T; - Element : in Mal.T) - is - begin - if Is_First then - Is_First := False; - else - Append (Buffer, ' '); - end if; - Print_Form (Key); - Append (Buffer, ' '); - Print_Form (Element); - end Process; - begin - Iterate (Form_Ast.Map); - end; + Print_Map (Form_Ast.Map); Append (Buffer, '}'); when Kind_Builtin | Kind_Builtin_With_Meta => Append (Buffer, "#"); when Kind_Function => - Append (Buffer, "# "); - Print_Form (Form_Ast.Function_Value.Expression); + Append (Buffer, "# "); + Print_Form (Form_Ast.Fn.Ast); Append (Buffer, '>'); when Kind_Macro => - Append (Buffer, "# "); - Print_Form (Form_Ast.Function_Value.Expression); + Append (Buffer, "# "); + Print_Form (Form_Ast.Fn.Ast); Append (Buffer, '>'); when Kind_Atom => Append (Buffer, "(atom "); - Print_Form (Atoms.Deref (Mal.T_Array'(1 => Form_Ast))); + Print_Form (Atoms.Deref (Form_Ast.Atom)); Append (Buffer, ')'); end case; end Print_Form; + procedure Print_List (List : in Lists.Ptr) is + Started : Boolean := False; + begin + for I in 1 .. List.Length loop + if Started then + Append (Buffer, ' '); + else + Started := True; + end if; + Print_Form (List.Element (I)); + end loop; + end Print_List; + + procedure Print_Map (Map : in Maps.Ptr) is + procedure Process (Key : in Mal.T; + Element : in Mal.T); + procedure Iterate is new Maps.Iterate (Process); + Started : Boolean := False; + procedure Process (Key : in Mal.T; + Element : in Mal.T) + is + begin + if Started then + Append (Buffer, ' '); + else + Started := True; + end if; + Print_Form (Key); + Append (Buffer, ' '); + Print_Form (Element); + end Process; + begin + Iterate (Map); + end Print_Map; + + procedure Print_Number (Number : in Integer) is + Image : constant String := Integer'Image (Number); + First : Positive := Image'First; + begin + if Image (First) = ' ' then + First := First + 1; + end if; + Append (Buffer, Image (First .. Image'Last)); + end Print_Number; + + procedure Print_Readably (S : in Unbounded_String) is + begin + for I in 1 .. Length (S) loop + declare + C : constant Character := Element (S, I); + begin + case C is + when '"' | '\' => + Append (Buffer, '\'); + Append (Buffer, C); + when Ada.Characters.Latin_1.LF => + Append (Buffer, "\n"); + when others => + Append (Buffer, C); + end case; + end; + end loop; + end Print_Readably; + + procedure Print_Symbols (List : in Symbols.Symbol_Array) is + Started : Boolean := False; + begin + for S of List loop + if Started then + Append (Buffer, ' '); + else + Started := True; + end if; + Append (Buffer, S.To_String); + end loop; + end Print_Symbols; + ---------------------------------------------------------------------- begin -- Pr_Str diff --git a/ada2/printer.ads b/ada2/printer.ads index 219f682dae..b50d32a60d 100644 --- a/ada2/printer.ads +++ b/ada2/printer.ads @@ -8,4 +8,9 @@ package Printer with Elaborate_Body is Readably : in Boolean := True) return Ada.Strings.Unbounded.Unbounded_String; + function Img (Ast : in Types.Mal.T) return String + is (Ada.Strings.Unbounded.To_String (Pr_Str (Ast))) with Inline; + -- This form is convenient for reporting errors, but the + -- conversion should be avoided when possible. + end Printer; diff --git a/ada2/reader.adb b/ada2/reader.adb index 776f60f9b5..33d0c3c4a0 100644 --- a/ada2/reader.adb +++ b/ada2/reader.adb @@ -25,8 +25,7 @@ package body Reader is ---------------------------------------------------------------------- - procedure Find_Next_Token - is + procedure Find_Next_Token is use Ada.Characters.Latin_1; begin First := Last + 1; diff --git a/ada2/readline.adb b/ada2/readline.adb new file mode 100644 index 0000000000..882b347387 --- /dev/null +++ b/ada2/readline.adb @@ -0,0 +1,32 @@ +with Interfaces.C.Strings; + +package body Readline is + + function Input (Prompt : in String) return String is + + use Interfaces.C; + use Interfaces.C.Strings; + + function C_Readline (Prompt : in char_array) return chars_ptr + with Import, Convention => C, External_Name => "readline"; + + procedure Add_History (Line : in chars_ptr) + with Import, Convention => C, External_Name => "add_history"; + + procedure Free (Line : in chars_ptr) + with Import, Convention => C, External_Name => "free"; + + C_Line : constant chars_ptr := C_Readline (To_C (Prompt)); + begin + if C_Line = Null_Ptr then + raise End_Of_File; + end if; + return Ada_Line : constant String := Value (C_Line) do + if Ada_Line /= "" then + Add_History (C_Line); + end if; + Free (C_Line); + end return; + end Input; + +end Readline; diff --git a/ada2/readline.ads b/ada2/readline.ads new file mode 100644 index 0000000000..534ee7f7db --- /dev/null +++ b/ada2/readline.ads @@ -0,0 +1,7 @@ +package Readline with Preelaborate is + + function Input (Prompt : in String) return String; + + End_Of_File : exception; + +end Readline; diff --git a/ada2/step0_repl.adb b/ada2/step0_repl.adb index f318731343..9eda48659d 100644 --- a/ada2/step0_repl.adb +++ b/ada2/step0_repl.adb @@ -1,55 +1,43 @@ with Ada.Text_IO; -with Interfaces.C.Strings; + +with Readline; procedure Step0_Repl is - subtype Mal_Type is String; + function Read return String with Inline; - function Read (Source : in String) return Mal_Type - is (Source); + function Eval (Ast : in String) return String; - function Eval (Ast : in Mal_Type) return Mal_Type - is (Ast); + procedure Print (Ast : in String) with Inline; - function Print (Ast : in Mal_Type) return String - is (Ast); + procedure Rep with Inline; - function Rep (Source : in String) return String - is (Print (Eval (Read (Source)))) with Inline; + ---------------------------------------------------------------------- - procedure Interactive_Loop; + function Eval (Ast : in String) return String is (Ast); - ---------------------------------------------------------------------- + procedure Print (Ast : in String) is + begin + Ada.Text_IO.Put_Line (Ast); + end Print; + + function Read return String is (Readline.Input ("user> ")); - procedure Interactive_Loop is - use Interfaces.C, Interfaces.C.Strings; - function Readline (Prompt : in char_array) return chars_ptr - with Import, Convention => C, External_Name => "readline"; - procedure Add_History (Line : in chars_ptr) - with Import, Convention => C, External_Name => "add_history"; - procedure Free (Line : in chars_ptr) - with Import, Convention => C, External_Name => "free"; - Prompt : constant char_array := To_C ("user> "); - C_Line : chars_ptr; + procedure Rep is begin - loop - C_Line := Readline (Prompt); - exit when C_Line = Null_Ptr; - declare - Line : constant String := Value (C_Line); - begin - if Line /= "" then - Add_History (C_Line); - end if; - Free (C_Line); - Ada.Text_IO.Put_Line (Rep (Line)); - end; - end loop; - Ada.Text_IO.New_Line; - end Interactive_Loop; + Print (Eval (Read)); + end Rep; ---------------------------------------------------------------------- begin - Interactive_Loop; + loop + begin + Rep; + exception + when Readline.End_Of_File => + exit; + end; + end loop; + Ada.Text_IO.New_Line; end Step0_Repl; diff --git a/ada2/step1_read_print.adb b/ada2/step1_read_print.adb index 06f49a3005..67b3277621 100644 --- a/ada2/step1_read_print.adb +++ b/ada2/step1_read_print.adb @@ -1,69 +1,54 @@ with Ada.Exceptions; -with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; with Printer; with Reader; +with Readline; with Types.Mal; procedure Step1_Read_Print is - package ASU renames Ada.Strings.Unbounded; use Types; - function Read (Source : in String) return Mal.T - renames Reader.Read_Str; + function Read return Mal.T with Inline; - function Eval (Ast : in Mal.T) return Mal.T - is (Ast); + function Eval (Ast : in Mal.T) return Mal.T; - function Print (Ast : in Mal.T; - Readably : in Boolean := True) return ASU.Unbounded_String - renames Printer.Pr_Str; + procedure Print (Ast : in Mal.T) with Inline; - function Rep (Source : in String) return ASU.Unbounded_String - is (Print (Eval (Read (Source)))) with Inline; - - procedure Interactive_Loop; + procedure Rep with Inline; ---------------------------------------------------------------------- - procedure Interactive_Loop is - use Interfaces.C, Interfaces.C.Strings; - function Readline (Prompt : in char_array) return chars_ptr - with Import, Convention => C, External_Name => "readline"; - procedure Add_History (Line : in chars_ptr) - with Import, Convention => C, External_Name => "add_history"; - procedure Free (Line : in chars_ptr) - with Import, Convention => C, External_Name => "free"; - Prompt : constant char_array := To_C ("user> "); - C_Line : chars_ptr; + function Eval (Ast : in Mal.T) return Mal.T is (Ast); + + procedure Print (Ast : in Mal.T) is + begin + Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); + end Print; + + function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + + procedure Rep is begin - loop - C_Line := Readline (Prompt); - exit when C_Line = Null_Ptr; - declare - Line : constant String := Value (C_Line); - begin - if Line /= "" then - Add_History (C_Line); - end if; - Free (C_Line); - Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line)); - exception - when Reader.Empty_Source => - null; - when E : Reader.Reader_Error => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. - end; - end loop; - Ada.Text_IO.New_Line; - end Interactive_Loop; + Print (Eval (Read)); + end Rep; ---------------------------------------------------------------------- begin - Interactive_Loop; + loop + begin + Rep; + exception + when Readline.End_Of_File => + exit; + when Reader.Empty_Source => + null; + when E : Reader.Reader_Error => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- Other exceptions are unexpected. + end; + end loop; + Ada.Text_IO.New_Line; end Step1_Read_Print; diff --git a/ada2/step2_eval.adb b/ada2/step2_eval.adb index 9010bb2c4f..7a5f9f72d3 100644 --- a/ada2/step2_eval.adb +++ b/ada2/step2_eval.adb @@ -1,57 +1,48 @@ with Ada.Containers.Indefinite_Hashed_Maps; with Ada.Exceptions; with Ada.Strings.Hash; -with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; with Printer; with Reader; -with Types.Builtins; +with Readline; with Types.Lists; with Types.Mal; with Types.Maps; procedure Step2_Eval is - package ASU renames Ada.Strings.Unbounded; use Types; - package Environments is new Ada.Containers.Indefinite_Hashed_Maps + package Envs is new Ada.Containers.Indefinite_Hashed_Maps (Key_Type => String, - Element_Type => Builtins.Ptr, + Element_Type => Mal.Builtin_Ptr, Hash => Ada.Strings.Hash, Equivalent_Keys => "=", - "=" => Builtins."="); - Unknown_Symbol : exception; + "=" => Mal."="); + Unknown_Key : exception; - function Read (Source : in String) return Mal.T - renames Reader.Read_Str; + function Read return Mal.T with Inline; function Eval (Ast : in Mal.T; - Env : in Environments.Map) return Mal.T; + Env : in Envs.Map) return Mal.T; - function Print (Ast : in Mal.T; - Readably : in Boolean := True) return ASU.Unbounded_String - renames Printer.Pr_Str; + procedure Print (Ast : in Mal.T) with Inline; - function Rep (Source : in String; - Env : in Environments.Map) return ASU.Unbounded_String - is (Print (Eval (Read (Source), Env))) with Inline; - - procedure Interactive_Loop (Repl : in Environments.Map); + procedure Rep (Env : in Envs.Map) with Inline; generic with function Ada_Operator (Left, Right : in Integer) return Integer; function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T; - function Eval_List_Elts is new Lists.Generic_Eval (Environments.Map, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Map, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Envs.Map, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Map, Eval); ---------------------------------------------------------------------- function Eval (Ast : in Mal.T; - Env : in Environments.Map) return Mal.T is + Env : in Envs.Map) return Mal.T + is First : Mal.T; begin -- Ada.Text_IO.New_Line; @@ -64,80 +55,57 @@ procedure Step2_Eval is return Ast; when Kind_Symbol => declare - S : constant String := Ast.Symbol.To_String; - C : constant Environments.Cursor := Env.Find (S); + S : constant String := Ast.Symbol.To_String; + C : constant Envs.Cursor := Env.Find (S); begin - if Environments.Has_Element (C) then - return (Kind_Builtin, Environments.Element (C)); + if Envs.Has_Element (C) then + return (Kind_Builtin, Envs.Element (C)); else -- The predefined message does not pass tests. - raise Unknown_Symbol with "'" & S & "' not found"; + raise Unknown_Key with "'" & S & "' not found"; end if; end; when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.L.Length = 0 then + if Ast.List.Length = 0 then return Ast; end if; - First := Eval (Ast.L.Element (1), Env); + First := Eval (Ast.List.Element (1), Env); -- Apply phase. case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; return First.Builtin.all (Args); end; when others => - raise Argument_Error - with "cannot execute " & ASU.To_String (Print (First)); + raise Argument_Error with "cannot call " & Printer.Img (First); end case; end case; end Eval; function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T - is (Kind_Number, Ada_Operator (Args (Args'First).Ada_Number, - Args (Args'Last).Ada_Number)); - - procedure Interactive_Loop (Repl : in Environments.Map) is - use Interfaces.C, Interfaces.C.Strings; - function Readline (Prompt : in char_array) return chars_ptr - with Import, Convention => C, External_Name => "readline"; - procedure Add_History (Line : in chars_ptr) - with Import, Convention => C, External_Name => "add_history"; - procedure Free (Line : in chars_ptr) - with Import, Convention => C, External_Name => "free"; - Prompt : constant char_array := To_C ("user> "); - C_Line : chars_ptr; + is (Kind_Number, Ada_Operator (Args (Args'First).Number, + Args (Args'Last).Number)); + + procedure Print (Ast : in Mal.T) is begin - loop - C_Line := Readline (Prompt); - exit when C_Line = Null_Ptr; - declare - Line : constant String := Value (C_Line); - begin - if Line /= "" then - Add_History (C_Line); - end if; - Free (C_Line); - Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); - exception - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error - | Unknown_Symbol => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. - end; - end loop; - Ada.Text_IO.New_Line; - end Interactive_Loop; + Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); + end Print; + + function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + + procedure Rep (Env : in Envs.Map) is + begin + Print (Eval (Read, Env)); + end Rep; ---------------------------------------------------------------------- @@ -146,12 +114,24 @@ procedure Step2_Eval is function Product is new Generic_Mal_Operator ("*"); function Division is new Generic_Mal_Operator ("/"); - Repl : Environments.Map; + Repl : Envs.Map; begin Repl.Insert ("+", Addition 'Unrestricted_Access); Repl.Insert ("-", Subtraction'Unrestricted_Access); Repl.Insert ("*", Product 'Unrestricted_Access); Repl.Insert ("/", Division 'Unrestricted_Access); - - Interactive_Loop (Repl); + loop + begin + Rep (Repl); + exception + when Readline.End_Of_File => + exit; + when Reader.Empty_Source => + null; + when E : Reader.Reader_Error | Unknown_Key => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- Other exceptions are unexpected. + end; + end loop; + Ada.Text_IO.New_Line; end Step2_Eval; diff --git a/ada2/step3_env.adb b/ada2/step3_env.adb index 1b4448bfe2..79290d58da 100644 --- a/ada2/step3_env.adb +++ b/ada2/step3_env.adb @@ -1,11 +1,10 @@ with Ada.Exceptions; -with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; -with Environments; +with Envs; with Printer; with Reader; +with Readline; with Types.Lists; with Types.Mal; with Types.Maps; @@ -13,43 +12,36 @@ with Types.Symbols.Names; procedure Step3_Env is - package ASU renames Ada.Strings.Unbounded; use Types; - use type Symbols.Ptr; - function Read (Source : in String) return Mal.T - renames Reader.Read_Str; + function Read return Mal.T with Inline; function Eval (Ast : in Mal.T; - Env : in Environments.Ptr) return Mal.T; + Env : in Envs.Ptr) return Mal.T; - function Print (Ast : in Mal.T; - Readably : in Boolean := True) return ASU.Unbounded_String - renames Printer.Pr_Str; + procedure Print (Ast : in Mal.T) with Inline; - function Rep (Source : in String; - Env : in Environments.Ptr) return ASU.Unbounded_String - is (Print (Eval (Read (Source), Env))) with Inline; - - procedure Interactive_Loop (Repl : in Environments.Ptr); + procedure Rep (Env : in Envs.Ptr) with Inline; generic with function Ada_Operator (Left, Right : in Integer) return Integer; function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T; - function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); ---------------------------------------------------------------------- function Eval (Ast : in Mal.T; - Env : in Environments.Ptr) return Mal.T is + Env : in Envs.Ptr) return Mal.T + is + use type Symbols.Ptr; First : Mal.T; begin -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); - -- Environments.Dump_Stack; + -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String | Kind_Keyword | Kind_Macro | Kind_Function @@ -60,36 +52,38 @@ procedure Step3_Env is when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.L.Length = 0 then + if Ast.List.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); + First := Ast.List.Element (1); -- Special forms if First.Kind /= Kind_Symbol then -- Evaluate First, in the less frequent case where it is -- not a symbol. First := Eval (First, Env); elsif First.Symbol = Symbols.Names.Def then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.L.Element (2).Kind /= Kind_Symbol then + elsif Ast.List.Element (2).Kind /= Kind_Symbol then raise Argument_Error with "def!: arg 1 must be a symbol"; end if; - return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).Symbol, R); + return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do + Env.Set (Ast.List.Element (2).Symbol, R); end return; elsif First.Symbol = Symbols.Names.Let then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then raise Argument_Error with "let*: expects a list or vector"; end if; declare - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; - New_Env : constant Environments.Ptr := Env.Sub; + Bindings : constant Lists.Ptr := Ast.List.Element (2).List; + -- This curious syntax is useful for later steps. + New_Env : Envs.Ptr := Env.Copy_Pointer; begin + New_Env.Replace_With_Sub; if Bindings.Length mod 2 /= 0 then raise Argument_Error with "let*: odd number of bindings"; end if; @@ -100,7 +94,7 @@ procedure Step3_Env is New_Env.Set (Bindings.Element (2 * I - 1).Symbol, Eval (Bindings.Element (2 * I), New_Env)); end loop; - return Eval (Ast.L.Element (3), New_Env); + return Eval (Ast.List.Element (3), New_Env); end; else -- Equivalent to First := Eval (First, Env), except that @@ -112,57 +106,34 @@ procedure Step3_Env is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; return First.Builtin.all (Args); end; when others => - raise Argument_Error - with "cannot execute " & ASU.To_String (Print (First)); + raise Argument_Error with "cannot call " & Printer.Img (First); end case; end case; end Eval; function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T - is (Kind_Number, Ada_Operator (Args (Args'First).Ada_Number, - Args (Args'Last).Ada_Number)); - - procedure Interactive_Loop (Repl : in Environments.Ptr) is - use Interfaces.C, Interfaces.C.Strings; - function Readline (Prompt : in char_array) return chars_ptr - with Import, Convention => C, External_Name => "readline"; - procedure Add_History (Line : in chars_ptr) - with Import, Convention => C, External_Name => "add_history"; - procedure Free (Line : in chars_ptr) - with Import, Convention => C, External_Name => "free"; - Prompt : constant char_array := To_C ("user> "); - C_Line : chars_ptr; + is (Kind_Number, Ada_Operator (Args (Args'First).Number, + Args (Args'Last).Number)); + + procedure Print (Ast : in Mal.T) is begin - loop - C_Line := Readline (Prompt); - exit when C_Line = Null_Ptr; - declare - Line : constant String := Value (C_Line); - begin - if Line /= "" then - Add_History (C_Line); - end if; - Free (C_Line); - Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); - exception - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error - | Environments.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. - end; - end loop; - Ada.Text_IO.New_Line; - end Interactive_Loop; + Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); + end Print; + + function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + + procedure Rep (Env : in Envs.Ptr) is + begin + Print (Eval (Read, Env)); + end Rep; ---------------------------------------------------------------------- @@ -171,14 +142,28 @@ procedure Step3_Env is function Product is new Generic_Mal_Operator ("*"); function Division is new Generic_Mal_Operator ("/"); - function S (Source : in String) return Symbols.Ptr - renames Symbols.Constructor; - Repl : Environments.Ptr renames Environments.Repl; + Repl : Envs.Ptr renames Envs.Repl; begin - Repl.Set (S ("+"), (Kind_Builtin, Addition 'Unrestricted_Access)); - Repl.Set (S ("-"), (Kind_Builtin, Subtraction'Unrestricted_Access)); - Repl.Set (S ("*"), (Kind_Builtin, Product 'Unrestricted_Access)); - Repl.Set (S ("/"), (Kind_Builtin, Division 'Unrestricted_Access)); - - Interactive_Loop (Repl); + Repl.Set (Symbols.Constructor ("+"), + (Kind_Builtin, Addition 'Unrestricted_Access)); + Repl.Set (Symbols.Constructor ("-"), + (Kind_Builtin, Subtraction'Unrestricted_Access)); + Repl.Set (Symbols.Constructor ("*"), + (Kind_Builtin, Product 'Unrestricted_Access)); + Repl.Set (Symbols.Constructor ("/"), + (Kind_Builtin, Division 'Unrestricted_Access)); + loop + begin + Rep (Repl); + exception + when Readline.End_Of_File => + exit; + when Reader.Empty_Source => + null; + when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- Other exceptions are unexpected. + end; + end loop; + Ada.Text_IO.New_Line; end Step3_Env; diff --git a/ada2/step4_if_fn_do.adb b/ada2/step4_if_fn_do.adb index bce010d3dd..d96d28c7bf 100644 --- a/ada2/step4_if_fn_do.adb +++ b/ada2/step4_if_fn_do.adb @@ -1,12 +1,12 @@ with Ada.Exceptions; -with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; with Core; -with Environments; +with Envs; +with Eval_Cb; with Printer; with Reader; +with Readline; with Types.Functions; with Types.Lists; with Types.Mal; @@ -15,42 +15,37 @@ with Types.Symbols.Names; procedure Step4_If_Fn_Do is - package ASU renames Ada.Strings.Unbounded; use Types; - use type Symbols.Ptr; - function Read (Source : in String) return Mal.T - renames Reader.Read_Str; + function Read return Mal.T with Inline; function Eval (Ast : in Mal.T; - Env : in Environments.Ptr) return Mal.T; + Env : in Envs.Ptr) return Mal.T; - function Print (Ast : in Mal.T; - Readably : in Boolean := True) return ASU.Unbounded_String - renames Printer.Pr_Str; + procedure Print (Ast : in Mal.T) with Inline; - function Rep (Source : in String; - Env : in Environments.Ptr) return ASU.Unbounded_String - is (Print (Eval (Read (Source), Env))) with Inline; + procedure Rep (Env : in Envs.Ptr) with Inline; - procedure Interactive_Loop (Repl : in Environments.Ptr); - - function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + -- Procedural form of Eval. -- Convenient when the result of eval is of no interest. - procedure Discard (Ast : in Mal.T) is null; + procedure Eval_P (Ast : in Mal.T; + Env : in Envs.Ptr) with Inline; ---------------------------------------------------------------------- function Eval (Ast : in Mal.T; - Env : in Environments.Ptr) return Mal.T is + Env : in Envs.Ptr) return Mal.T + is + use type Symbols.Ptr; First : Mal.T; begin -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); - -- Environments.Dump_Stack; + -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String | Kind_Keyword | Kind_Macro | Kind_Function @@ -61,75 +56,78 @@ procedure Step4_If_Fn_Do is when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.L.Length = 0 then + if Ast.List.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); + First := Ast.List.Element (1); -- Special forms if First.Kind /= Kind_Symbol then -- Evaluate First, in the less frequent case where it is -- not a symbol. First := Eval (First, Env); elsif First.Symbol = Symbols.Names.Def then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.L.Element (2).Kind /= Kind_Symbol then + elsif Ast.List.Element (2).Kind /= Kind_Symbol then raise Argument_Error with "def!: arg 1 must be a symbol"; end if; - return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).Symbol, R); + return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do + Env.Set (Ast.List.Element (2).Symbol, R); end return; elsif First.Symbol = Symbols.Names.Mal_Do then - if Ast.L.Length = 1 then + if Ast.List.Length = 1 then raise Argument_Error with "do: expects at least 1 argument"; end if; - for I in 2 .. Ast.L.Length - 1 loop - Discard (Eval (Ast.L.Element (I), Env)); + for I in 2 .. Ast.List.Length - 1 loop + Eval_P (Ast.List.Element (I), Env); end loop; - return Eval (Ast.L.Element (Ast.L.Length), Env); + return Eval (Ast.List.Element (Ast.List.Length), Env); elsif First.Symbol = Symbols.Names.Fn then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "fn*: expects 3 arguments"; - elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then raise Argument_Error with "fn*: arg 1 must be a list or vector"; - elsif (for some F in 1 .. Ast.L.Element (2).L.Length => - Ast.L.Element (2).L.Element (F).Kind /= Kind_Symbol) + elsif (for some F in 1 .. Ast.List.Element (2).List.Length => + Ast.List.Element (2).List.Element (F).Kind /= Kind_Symbol) then raise Argument_Error with "fn*: arg 2 must contain symbols"; end if; - return Functions.New_Function (Ast.L.Element (2).L, - Ast.L.Element (3), Env.New_Closure); + return Functions.New_Function (Params => Ast.List.Element (2).List, + Ast => Ast.List.Element (3), + Env => Env.New_Closure); elsif First.Symbol = Symbols.Names.Mal_If then - if Ast.L.Length not in 3 .. 4 then + if Ast.List.Length not in 3 .. 4 then raise Argument_Error with "if: expects 2 or 3 arguments"; end if; declare - Test : constant Mal.T := Eval (Ast.L.Element (2), Env); + Test : constant Mal.T := Eval (Ast.List.Element (2), Env); begin if (case Test.Kind is when Kind_Nil => False, when Kind_Boolean => Test.Ada_Boolean, when others => True) then - return Eval (Ast.L.Element (3), Env); - elsif Ast.L.Length = 3 then + return Eval (Ast.List.Element (3), Env); + elsif Ast.List.Length = 3 then return Mal.Nil; else - return Eval (Ast.L.Element (4), Env); + return Eval (Ast.List.Element (4), Env); end if; end; elsif First.Symbol = Symbols.Names.Let then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then raise Argument_Error with "let*: expects a list or vector"; end if; declare - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; - New_Env : constant Environments.Ptr := Env.Sub; + Bindings : constant Lists.Ptr := Ast.List.Element (2).List; + -- This curious syntax is useful for later steps. + New_Env : Envs.Ptr := Env.Copy_Pointer; begin + New_Env.Replace_With_Sub; if Bindings.Length mod 2 /= 0 then raise Argument_Error with "let*: odd number of bindings"; end if; @@ -140,7 +138,7 @@ procedure Step4_If_Fn_Do is New_Env.Set (Bindings.Element (2 * I - 1).Symbol, Eval (Bindings.Element (2 * I), New_Env)); end loop; - return Eval (Ast.L.Element (3), New_Env); + return Eval (Ast.List.Element (3), New_Env); end; else -- Equivalent to First := Eval (First, Env), except that @@ -152,74 +150,75 @@ procedure Step4_If_Fn_Do is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; return First.Builtin.all (Args); end; when Kind_Function => declare - Args : Mal.T_Array (2 .. Ast.L.Length); - New_Env : constant Environments.Ptr - := First.Function_Value.Closure.Closure_Sub; + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; - First.Function_Value.Set_Binds (New_Env, Args); - return Eval (First.Function_Value.Expression, New_Env); + return First.Fn.Apply (Args); end; when others => - raise Argument_Error - with "cannot execute " & ASU.To_String (Print (First)); + raise Argument_Error with "cannot call " & Printer.Img (First); end case; end case; end Eval; - procedure Interactive_Loop (Repl : in Environments.Ptr) is - use Interfaces.C, Interfaces.C.Strings; - function Readline (Prompt : in char_array) return chars_ptr - with Import, Convention => C, External_Name => "readline"; - procedure Add_History (Line : in chars_ptr) - with Import, Convention => C, External_Name => "add_history"; - procedure Free (Line : in chars_ptr) - with Import, Convention => C, External_Name => "free"; - Prompt : constant char_array := To_C ("user> "); - C_Line : chars_ptr; + procedure Eval_P (Ast : in Mal.T; + Env : in Envs.Ptr) + is + Result : constant Mal.T := Eval (Ast, Env); begin - loop - C_Line := Readline (Prompt); - exit when C_Line = Null_Ptr; - declare - Line : constant String := Value (C_Line); - begin - if Line /= "" then - Add_History (C_Line); - end if; - Free (C_Line); - Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); - exception - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error - | Environments.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. - end; - end loop; - Ada.Text_IO.New_Line; - end Interactive_Loop; + pragma Unreferenced (Result); + end Eval_P; + + procedure Print (Ast : in Mal.T) is + begin + Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); + end Print; + + function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + + procedure Rep (Env : in Envs.Ptr) is + begin + Print (Eval (Read, Env)); + end Rep; ---------------------------------------------------------------------- - Startup : constant String := "(do" + Startup : constant String := "(do " & "(def! not (fn* (a) (if a false true)))" & ")"; - Repl : Environments.Ptr renames Environments.Repl; + Repl : Envs.Ptr renames Envs.Repl; begin - Core.Eval_Ref := Eval'Unrestricted_Access; - Discard (Eval (Read (Startup), Repl)); - Interactive_Loop (Repl); + -- Show the Eval function to other packages. + Eval_Cb.Cb := Eval'Unrestricted_Access; + -- Add Core functions into the top environment. + for Binding of Core.Ns loop + Repl.Set (Binding.Symbol, (Kind_Builtin, Binding.Builtin)); + end loop; + -- Native startup procedure. + Eval_P (Reader.Read_Str (Startup), Repl); + loop + begin + Rep (Repl); + exception + when Readline.End_Of_File => + exit; + when Reader.Empty_Source => + null; + when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- Other exceptions are unexpected. + end; + end loop; + Ada.Text_IO.New_Line; end Step4_If_Fn_Do; diff --git a/ada2/step5_tco.adb b/ada2/step5_tco.adb index c9182356b3..b6079454a6 100644 --- a/ada2/step5_tco.adb +++ b/ada2/step5_tco.adb @@ -1,12 +1,12 @@ with Ada.Exceptions; -with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; with Core; -with Environments; +with Envs; +with Eval_Cb; with Printer; with Reader; +with Readline; with Types.Functions; with Types.Lists; with Types.Mal; @@ -15,48 +15,42 @@ with Types.Symbols.Names; procedure Step5_Tco is - package ASU renames Ada.Strings.Unbounded; use Types; - use type Symbols.Ptr; - function Read (Source : in String) return Mal.T - renames Reader.Read_Str; + function Read return Mal.T with Inline; function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T; + Env0 : in Envs.Ptr) return Mal.T; - function Print (Ast : in Mal.T; - Readably : in Boolean := True) return ASU.Unbounded_String - renames Printer.Pr_Str; + procedure Print (Ast : in Mal.T) with Inline; - function Rep (Source : in String; - Env : in Environments.Ptr) return ASU.Unbounded_String - is (Print (Eval (Read (Source), Env))) with Inline; + procedure Rep (Env : in Envs.Ptr) with Inline; - procedure Interactive_Loop (Repl : in Environments.Ptr); - - function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + -- Procedural form of Eval. -- Convenient when the result of eval is of no interest. - procedure Discard (Ast : in Mal.T) is null; + procedure Eval_P (Ast : in Mal.T; + Env : in Envs.Ptr) with Inline; ---------------------------------------------------------------------- function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T + Env0 : in Envs.Ptr) return Mal.T is + use type Symbols.Ptr; -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. - Ast : Mal.T := Ast0; - Env : Environments.Ptr := Env0.Copy_Pointer; + Ast : Mal.T := Ast0; + Env : Envs.Ptr := Env0.Copy_Pointer; First : Mal.T; begin <> -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); - -- Environments.Dump_Stack; + -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String | Kind_Keyword | Kind_Macro | Kind_Function @@ -67,76 +61,77 @@ procedure Step5_Tco is when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.L.Length = 0 then + if Ast.List.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); + First := Ast.List.Element (1); -- Special forms if First.Kind /= Kind_Symbol then -- Evaluate First, in the less frequent case where it is -- not a symbol. First := Eval (First, Env); elsif First.Symbol = Symbols.Names.Def then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.L.Element (2).Kind /= Kind_Symbol then + elsif Ast.List.Element (2).Kind /= Kind_Symbol then raise Argument_Error with "def!: arg 1 must be a symbol"; end if; - return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).Symbol, R); + return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do + Env.Set (Ast.List.Element (2).Symbol, R); end return; elsif First.Symbol = Symbols.Names.Mal_Do then - if Ast.L.Length = 1 then + if Ast.List.Length = 1 then raise Argument_Error with "do: expects at least 1 argument"; end if; - for I in 2 .. Ast.L.Length - 1 loop - Discard (Eval (Ast.L.Element (I), Env)); + for I in 2 .. Ast.List.Length - 1 loop + Eval_P (Ast.List.Element (I), Env); end loop; - Ast := Ast.L.Element (Ast.L.Length); + Ast := Ast.List.Element (Ast.List.Length); goto Restart; elsif First.Symbol = Symbols.Names.Fn then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "fn*: expects 3 arguments"; - elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then raise Argument_Error with "fn*: arg 1 must be a list or vector"; - elsif (for some F in 1 .. Ast.L.Element (2).L.Length => - Ast.L.Element (2).L.Element (F).Kind /= Kind_Symbol) + elsif (for some F in 1 .. Ast.List.Element (2).List.Length => + Ast.List.Element (2).List.Element (F).Kind /= Kind_Symbol) then raise Argument_Error with "fn*: arg 2 must contain symbols"; end if; - return Functions.New_Function (Ast.L.Element (2).L, - Ast.L.Element (3), Env.New_Closure); + return Functions.New_Function (Params => Ast.List.Element (2).List, + Ast => Ast.List.Element (3), + Env => Env.New_Closure); elsif First.Symbol = Symbols.Names.Mal_If then - if Ast.L.Length not in 3 .. 4 then + if Ast.List.Length not in 3 .. 4 then raise Argument_Error with "if: expects 2 or 3 arguments"; end if; declare - Test : constant Mal.T := Eval (Ast.L.Element (2), Env); + Test : constant Mal.T := Eval (Ast.List.Element (2), Env); begin if (case Test.Kind is when Kind_Nil => False, when Kind_Boolean => Test.Ada_Boolean, when others => True) then - Ast := Ast.L.Element (3); + Ast := Ast.List.Element (3); goto Restart; - elsif Ast.L.Length = 3 then + elsif Ast.List.Length = 3 then return Mal.Nil; else - Ast := Ast.L.Element (4); + Ast := Ast.List.Element (4); goto Restart; end if; end; elsif First.Symbol = Symbols.Names.Let then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then raise Argument_Error with "let*: expects a list or vector"; end if; declare - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + Bindings : constant Lists.Ptr := Ast.List.Element (2).List; begin if Bindings.Length mod 2 /= 0 then raise Argument_Error with "let*: odd number of bindings"; @@ -149,7 +144,7 @@ procedure Step5_Tco is Env.Set (Bindings.Element (2 * I - 1).Symbol, Eval (Bindings.Element (2 * I), Env)); end loop; - Ast := Ast.L.Element (3); + Ast := Ast.List.Element (3); goto Restart; end; else @@ -162,74 +157,79 @@ procedure Step5_Tco is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; return First.Builtin.all (Args); end; when Kind_Function => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; - Env.Replace_With_Closure_Sub (First.Function_Value.Closure); - First.Function_Value.Set_Binds (Env, Args); - Ast := First.Function_Value.Expression; + Env.Replace_With_Sub (Outer => First.Fn.Env, + Binds => First.Fn.Params, + Exprs => Args); + Ast := First.Fn.Ast; goto Restart; end; when others => - raise Argument_Error - with "cannot execute " & ASU.To_String (Print (First)); + raise Argument_Error with "cannot call " & Printer.Img (First); end case; end case; end Eval; - procedure Interactive_Loop (Repl : in Environments.Ptr) is - use Interfaces.C, Interfaces.C.Strings; - function Readline (Prompt : in char_array) return chars_ptr - with Import, Convention => C, External_Name => "readline"; - procedure Add_History (Line : in chars_ptr) - with Import, Convention => C, External_Name => "add_history"; - procedure Free (Line : in chars_ptr) - with Import, Convention => C, External_Name => "free"; - Prompt : constant char_array := To_C ("user> "); - C_Line : chars_ptr; + procedure Eval_P (Ast : in Mal.T; + Env : in Envs.Ptr) + is + Result : constant Mal.T := Eval (Ast, Env); begin - loop - C_Line := Readline (Prompt); - exit when C_Line = Null_Ptr; - declare - Line : constant String := Value (C_Line); - begin - if Line /= "" then - Add_History (C_Line); - end if; - Free (C_Line); - Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); - exception - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error - | Environments.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. - end; - end loop; - Ada.Text_IO.New_Line; - end Interactive_Loop; + pragma Unreferenced (Result); + end Eval_P; + + procedure Print (Ast : in Mal.T) is + begin + Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); + end Print; + + function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + + procedure Rep (Env : in Envs.Ptr) is + begin + Print (Eval (Read, Env)); + end Rep; ---------------------------------------------------------------------- - Startup : constant String := "(do" + Startup : constant String := "(do " & "(def! not (fn* (a) (if a false true)))" & ")"; - Repl : Environments.Ptr renames Environments.Repl; + Repl : Envs.Ptr renames Envs.Repl; begin - Core.Eval_Ref := Eval'Unrestricted_Access; - Discard (Eval (Read (Startup), Repl)); - Interactive_Loop (Repl); + -- Show the Eval function to other packages. + Eval_Cb.Cb := Eval'Unrestricted_Access; + -- Add Core functions into the top environment. + for Binding of Core.Ns loop + Repl.Set (Binding.Symbol, (Kind_Builtin, Binding.Builtin)); + end loop; + -- Native startup procedure. + Eval_P (Reader.Read_Str (Startup), Repl); + loop + begin + Rep (Repl); + exception + when Readline.End_Of_File => + exit; + when Reader.Empty_Source => + null; + when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- Other exceptions are unexpected. + end; + end loop; + Ada.Text_IO.New_Line; end Step5_Tco; diff --git a/ada2/step6_file.adb b/ada2/step6_file.adb index b5051634f5..57988f7df6 100644 --- a/ada2/step6_file.adb +++ b/ada2/step6_file.adb @@ -2,12 +2,13 @@ with Ada.Command_Line; with Ada.Exceptions; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; with Core; -with Environments; +with Envs; +with Eval_Cb; with Printer; with Reader; +with Readline; with Types.Functions; with Types.Lists; with Types.Mal; @@ -18,46 +19,41 @@ procedure Step6_File is package ASU renames Ada.Strings.Unbounded; use Types; - use type Symbols.Ptr; - function Read (Source : in String) return Mal.T - renames Reader.Read_Str; + function Read return Mal.T with Inline; function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T; + Env0 : in Envs.Ptr) return Mal.T; - function Print (Ast : in Mal.T; - Readably : in Boolean := True) return ASU.Unbounded_String - renames Printer.Pr_Str; + procedure Print (Ast : in Mal.T) with Inline; - function Rep (Source : in String; - Env : in Environments.Ptr) return ASU.Unbounded_String - is (Print (Eval (Read (Source), Env))) with Inline; + procedure Rep (Env : in Envs.Ptr) with Inline; - procedure Interactive_Loop (Repl : in Environments.Ptr); - - function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + -- Procedural form of Eval. -- Convenient when the result of eval is of no interest. - procedure Discard (Ast : in Mal.T) is null; + procedure Eval_P (Ast : in Mal.T; + Env : in Envs.Ptr) with Inline; ---------------------------------------------------------------------- function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T + Env0 : in Envs.Ptr) return Mal.T is + use type Symbols.Ptr; -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. - Ast : Mal.T := Ast0; - Env : Environments.Ptr := Env0.Copy_Pointer; + Ast : Mal.T := Ast0; + Env : Envs.Ptr := Env0.Copy_Pointer; First : Mal.T; begin <> -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); - -- Environments.Dump_Stack; + -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String | Kind_Keyword | Kind_Macro | Kind_Function @@ -68,76 +64,77 @@ procedure Step6_File is when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.L.Length = 0 then + if Ast.List.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); + First := Ast.List.Element (1); -- Special forms if First.Kind /= Kind_Symbol then -- Evaluate First, in the less frequent case where it is -- not a symbol. First := Eval (First, Env); elsif First.Symbol = Symbols.Names.Def then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.L.Element (2).Kind /= Kind_Symbol then + elsif Ast.List.Element (2).Kind /= Kind_Symbol then raise Argument_Error with "def!: arg 1 must be a symbol"; end if; - return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).Symbol, R); + return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do + Env.Set (Ast.List.Element (2).Symbol, R); end return; elsif First.Symbol = Symbols.Names.Mal_Do then - if Ast.L.Length = 1 then + if Ast.List.Length = 1 then raise Argument_Error with "do: expects at least 1 argument"; end if; - for I in 2 .. Ast.L.Length - 1 loop - Discard (Eval (Ast.L.Element (I), Env)); + for I in 2 .. Ast.List.Length - 1 loop + Eval_P (Ast.List.Element (I), Env); end loop; - Ast := Ast.L.Element (Ast.L.Length); + Ast := Ast.List.Element (Ast.List.Length); goto Restart; elsif First.Symbol = Symbols.Names.Fn then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "fn*: expects 3 arguments"; - elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then raise Argument_Error with "fn*: arg 1 must be a list or vector"; - elsif (for some F in 1 .. Ast.L.Element (2).L.Length => - Ast.L.Element (2).L.Element (F).Kind /= Kind_Symbol) + elsif (for some F in 1 .. Ast.List.Element (2).List.Length => + Ast.List.Element (2).List.Element (F).Kind /= Kind_Symbol) then raise Argument_Error with "fn*: arg 2 must contain symbols"; end if; - return Functions.New_Function (Ast.L.Element (2).L, - Ast.L.Element (3), Env.New_Closure); + return Functions.New_Function (Params => Ast.List.Element (2).List, + Ast => Ast.List.Element (3), + Env => Env.New_Closure); elsif First.Symbol = Symbols.Names.Mal_If then - if Ast.L.Length not in 3 .. 4 then + if Ast.List.Length not in 3 .. 4 then raise Argument_Error with "if: expects 2 or 3 arguments"; end if; declare - Test : constant Mal.T := Eval (Ast.L.Element (2), Env); + Test : constant Mal.T := Eval (Ast.List.Element (2), Env); begin if (case Test.Kind is when Kind_Nil => False, when Kind_Boolean => Test.Ada_Boolean, when others => True) then - Ast := Ast.L.Element (3); + Ast := Ast.List.Element (3); goto Restart; - elsif Ast.L.Length = 3 then + elsif Ast.List.Length = 3 then return Mal.Nil; else - Ast := Ast.L.Element (4); + Ast := Ast.List.Element (4); goto Restart; end if; end; elsif First.Symbol = Symbols.Names.Let then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then raise Argument_Error with "let*: expects a list or vector"; end if; declare - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + Bindings : constant Lists.Ptr := Ast.List.Element (2).List; begin if Bindings.Length mod 2 /= 0 then raise Argument_Error with "let*: odd number of bindings"; @@ -150,7 +147,7 @@ procedure Step6_File is Env.Set (Bindings.Element (2 * I - 1).Symbol, Eval (Bindings.Element (2 * I), Env)); end loop; - Ast := Ast.L.Element (3); + Ast := Ast.List.Element (3); goto Restart; end; else @@ -163,78 +160,71 @@ procedure Step6_File is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; return First.Builtin.all (Args); end; when Kind_Function => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; - Env.Replace_With_Closure_Sub (First.Function_Value.Closure); - First.Function_Value.Set_Binds (Env, Args); - Ast := First.Function_Value.Expression; + Env.Replace_With_Sub (Outer => First.Fn.Env, + Binds => First.Fn.Params, + Exprs => Args); + Ast := First.Fn.Ast; goto Restart; end; when others => - raise Argument_Error - with "cannot execute " & ASU.To_String (Print (First)); + raise Argument_Error with "cannot call " & Printer.Img (First); end case; end case; end Eval; - procedure Interactive_Loop (Repl : in Environments.Ptr) is - use Interfaces.C, Interfaces.C.Strings; - function Readline (Prompt : in char_array) return chars_ptr - with Import, Convention => C, External_Name => "readline"; - procedure Add_History (Line : in chars_ptr) - with Import, Convention => C, External_Name => "add_history"; - procedure Free (Line : in chars_ptr) - with Import, Convention => C, External_Name => "free"; - Prompt : constant char_array := To_C ("user> "); - C_Line : chars_ptr; + procedure Eval_P (Ast : in Mal.T; + Env : in Envs.Ptr) + is + Result : constant Mal.T := Eval (Ast, Env); begin - loop - C_Line := Readline (Prompt); - exit when C_Line = Null_Ptr; - declare - Line : constant String := Value (C_Line); - begin - if Line /= "" then - Add_History (C_Line); - end if; - Free (C_Line); - Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); - exception - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error - | Environments.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. - end; - end loop; - Ada.Text_IO.New_Line; - end Interactive_Loop; + pragma Unreferenced (Result); + end Eval_P; + + procedure Print (Ast : in Mal.T) is + begin + Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); + end Print; + + function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + + procedure Rep (Env : in Envs.Ptr) is + begin + Print (Eval (Read, Env)); + end Rep; ---------------------------------------------------------------------- - Startup : constant String := "(do" + Startup : constant String := "(do " & "(def! not (fn* (a) (if a false true)))" & "(def! load-file (fn* (f)" & " (eval (read-string (str ""(do "" (slurp f) "")"")))))" & ")"; - Repl : Environments.Ptr renames Environments.Repl; + Repl : Envs.Ptr renames Envs.Repl; use Ada.Command_Line; begin - Core.Eval_Ref := Eval'Unrestricted_Access; - Discard (Eval (Read (Startup), Repl)); + -- Show the Eval function to other packages. + Eval_Cb.Cb := Eval'Unrestricted_Access; + -- Add Core functions into the top environment. + for Binding of Core.Ns loop + Repl.Set (Binding.Symbol, (Kind_Builtin, Binding.Builtin)); + end loop; + -- Native startup procedure. + Eval_P (Reader.Read_Str (Startup), Repl); + -- Define ARGV from command line arguments. declare Args : Mal.T_Array (2 .. Argument_Count); begin @@ -243,9 +233,23 @@ begin end loop; Repl.Set (Symbols.Constructor ("*ARGV*"), Lists.List (Args)); end; - if Argument_Count = 0 then - Interactive_Loop (Repl); + -- Script? + if 0 < Argument_Count then + Eval_P (Reader.Read_Str ("(load-file """ & Argument (1) & """)"), Repl); else - Discard (Eval (Read ("(load-file """ & Argument (1) & """)"), Repl)); + loop + begin + Rep (Repl); + exception + when Readline.End_Of_File => + exit; + when Reader.Empty_Source => + null; + when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- Other exceptions are unexpected. + end; + end loop; + Ada.Text_IO.New_Line; end if; end Step6_File; diff --git a/ada2/step7_quote.adb b/ada2/step7_quote.adb index 819ee88dc1..9424e615da 100644 --- a/ada2/step7_quote.adb +++ b/ada2/step7_quote.adb @@ -2,12 +2,13 @@ with Ada.Command_Line; with Ada.Exceptions; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; with Core; -with Environments; +with Envs; +with Eval_Cb; with Printer; with Reader; +with Readline; with Types.Functions; with Types.Lists; with Types.Mal; @@ -18,53 +19,48 @@ procedure Step7_Quote is package ASU renames Ada.Strings.Unbounded; use Types; - use type Symbols.Ptr; - function Read (Source : in String) return Mal.T - renames Reader.Read_Str; + function Read return Mal.T with Inline; function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T; + Env0 : in Envs.Ptr) return Mal.T; function Quasiquote (Ast : in Mal.T; - Env : in Environments.Ptr) return Mal.T; + Env : in Envs.Ptr) return Mal.T; -- Mergeing quote and quasiquote into eval with a flag triggering -- a different behaviour as done for macros in step8 would improve -- the performances significantly, but Kanaka finds that it breaks -- too much the step structure shared by all implementations. - function Print (Ast : in Mal.T; - Readably : in Boolean := True) return ASU.Unbounded_String - renames Printer.Pr_Str; + procedure Print (Ast : in Mal.T) with Inline; - function Rep (Source : in String; - Env : in Environments.Ptr) return ASU.Unbounded_String - is (Print (Eval (Read (Source), Env))) with Inline; + procedure Rep (Env : in Envs.Ptr) with Inline; - procedure Interactive_Loop (Repl : in Environments.Ptr); - - function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + -- Procedural form of Eval. -- Convenient when the result of eval is of no interest. - procedure Discard (Ast : in Mal.T) is null; + procedure Eval_P (Ast : in Mal.T; + Env : in Envs.Ptr) with Inline; ---------------------------------------------------------------------- function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T + Env0 : in Envs.Ptr) return Mal.T is + use type Symbols.Ptr; -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. - Ast : Mal.T := Ast0; - Env : Environments.Ptr := Env0.Copy_Pointer; + Ast : Mal.T := Ast0; + Env : Envs.Ptr := Env0.Copy_Pointer; First : Mal.T; begin <> -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); - -- Environments.Dump_Stack; + -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String | Kind_Keyword | Kind_Macro | Kind_Function @@ -75,76 +71,77 @@ procedure Step7_Quote is when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.L.Length = 0 then + if Ast.List.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); + First := Ast.List.Element (1); -- Special forms if First.Kind /= Kind_Symbol then -- Evaluate First, in the less frequent case where it is -- not a symbol. First := Eval (First, Env); elsif First.Symbol = Symbols.Names.Def then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.L.Element (2).Kind /= Kind_Symbol then + elsif Ast.List.Element (2).Kind /= Kind_Symbol then raise Argument_Error with "def!: arg 1 must be a symbol"; end if; - return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).Symbol, R); + return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do + Env.Set (Ast.List.Element (2).Symbol, R); end return; elsif First.Symbol = Symbols.Names.Mal_Do then - if Ast.L.Length = 1 then + if Ast.List.Length = 1 then raise Argument_Error with "do: expects at least 1 argument"; end if; - for I in 2 .. Ast.L.Length - 1 loop - Discard (Eval (Ast.L.Element (I), Env)); + for I in 2 .. Ast.List.Length - 1 loop + Eval_P (Ast.List.Element (I), Env); end loop; - Ast := Ast.L.Element (Ast.L.Length); + Ast := Ast.List.Element (Ast.List.Length); goto Restart; elsif First.Symbol = Symbols.Names.Fn then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "fn*: expects 3 arguments"; - elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then raise Argument_Error with "fn*: arg 1 must be a list or vector"; - elsif (for some F in 1 .. Ast.L.Element (2).L.Length => - Ast.L.Element (2).L.Element (F).Kind /= Kind_Symbol) + elsif (for some F in 1 .. Ast.List.Element (2).List.Length => + Ast.List.Element (2).List.Element (F).Kind /= Kind_Symbol) then raise Argument_Error with "fn*: arg 2 must contain symbols"; end if; - return Functions.New_Function (Ast.L.Element (2).L, - Ast.L.Element (3), Env.New_Closure); + return Functions.New_Function (Params => Ast.List.Element (2).List, + Ast => Ast.List.Element (3), + Env => Env.New_Closure); elsif First.Symbol = Symbols.Names.Mal_If then - if Ast.L.Length not in 3 .. 4 then + if Ast.List.Length not in 3 .. 4 then raise Argument_Error with "if: expects 2 or 3 arguments"; end if; declare - Test : constant Mal.T := Eval (Ast.L.Element (2), Env); + Test : constant Mal.T := Eval (Ast.List.Element (2), Env); begin if (case Test.Kind is when Kind_Nil => False, when Kind_Boolean => Test.Ada_Boolean, when others => True) then - Ast := Ast.L.Element (3); + Ast := Ast.List.Element (3); goto Restart; - elsif Ast.L.Length = 3 then + elsif Ast.List.Length = 3 then return Mal.Nil; else - Ast := Ast.L.Element (4); + Ast := Ast.List.Element (4); goto Restart; end if; end; elsif First.Symbol = Symbols.Names.Let then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then raise Argument_Error with "let*: expects a list or vector"; end if; declare - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + Bindings : constant Lists.Ptr := Ast.List.Element (2).List; begin if Bindings.Length mod 2 /= 0 then raise Argument_Error with "let*: odd number of bindings"; @@ -157,19 +154,19 @@ procedure Step7_Quote is Env.Set (Bindings.Element (2 * I - 1).Symbol, Eval (Bindings.Element (2 * I), Env)); end loop; - Ast := Ast.L.Element (3); + Ast := Ast.List.Element (3); goto Restart; end; elsif First.Symbol = Symbols.Names.Quasiquote then - if Ast.L.Length /= 2 then + if Ast.List.Length /= 2 then raise Argument_Error with "quasiquote: expects 1 argument"; end if; - return Quasiquote (Ast.L.Element (2), Env); + return Quasiquote (Ast.List.Element (2), Env); elsif First.Symbol = Symbols.Names.Quote then - if Ast.L.Length /= 2 then + if Ast.List.Length /= 2 then raise Argument_Error with "quote: expects 1 argument"; end if; - return Ast.L.Element (2); + return Ast.List.Element (2); else -- Equivalent to First := Eval (First, Env), except that -- we already know enough to spare a recursive call in @@ -180,70 +177,51 @@ procedure Step7_Quote is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; return First.Builtin.all (Args); end; when Kind_Function => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; - Env.Replace_With_Closure_Sub (First.Function_Value.Closure); - First.Function_Value.Set_Binds (Env, Args); - Ast := First.Function_Value.Expression; + Env.Replace_With_Sub (Outer => First.Fn.Env, + Binds => First.Fn.Params, + Exprs => Args); + Ast := First.Fn.Ast; goto Restart; end; when others => - raise Argument_Error - with "cannot execute " & ASU.To_String (Print (First)); + raise Argument_Error with "cannot call " & Printer.Img (First); end case; end case; end Eval; - procedure Interactive_Loop (Repl : in Environments.Ptr) is - use Interfaces.C, Interfaces.C.Strings; - function Readline (Prompt : in char_array) return chars_ptr - with Import, Convention => C, External_Name => "readline"; - procedure Add_History (Line : in chars_ptr) - with Import, Convention => C, External_Name => "add_history"; - procedure Free (Line : in chars_ptr) - with Import, Convention => C, External_Name => "free"; - Prompt : constant char_array := To_C ("user> "); - C_Line : chars_ptr; + procedure Eval_P (Ast : in Mal.T; + Env : in Envs.Ptr) + is + Result : constant Mal.T := Eval (Ast, Env); begin - loop - C_Line := Readline (Prompt); - exit when C_Line = Null_Ptr; - declare - Line : constant String := Value (C_Line); - begin - if Line /= "" then - Add_History (C_Line); - end if; - Free (C_Line); - Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); - exception - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error - | Environments.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. - end; - end loop; - Ada.Text_IO.New_Line; - end Interactive_Loop; + pragma Unreferenced (Result); + end Eval_P; + + procedure Print (Ast : in Mal.T) is + begin + Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); + end Print; function Quasiquote (Ast : in Mal.T; - Env : in Environments.Ptr) return Mal.T + Env : in Envs.Ptr) return Mal.T is + use type Symbols.Ptr; + function Quasiquote_List (List : in Lists.Ptr) return Mal.T with Inline; -- Handle vectors and lists not starting with unquote. @@ -254,15 +232,15 @@ procedure Step7_Quote is for I in R'Range loop R (I) := List.Element (I); if R (I).Kind in Kind_List | Kind_Vector - and then 0 < R (I).L.Length - and then R (I).L.Element (1).Kind = Kind_Symbol - and then R (I).L.Element (1).Symbol + and then 0 < R (I).List.Length + and then R (I).List.Element (1).Kind = Kind_Symbol + and then R (I).List.Element (1).Symbol = Symbols.Names.Splice_Unquote then - if R (I).L.Length /= 2 then + if R (I).List.Length /= 2 then raise Argument_Error with "splice-unquote: expects 1 arg"; end if; - R (I) := Eval (R (I).L.Element (2), Env); + R (I) := Eval (R (I).List.Element (2), Env); if R (I).Kind /= Kind_List then raise Argument_Error with "splice-unquote: expects a list"; end if; @@ -278,33 +256,47 @@ procedure Step7_Quote is case Ast.Kind is when Kind_Vector => -- When the test is updated, replace Kind_List with Kind_Vector. - return Quasiquote_List (Ast.L); + return Quasiquote_List (Ast.List); when Kind_List => - if 0 < Ast.L.Length - and then Ast.L.Element (1).Kind = Kind_Symbol - and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote + if 0 < Ast.List.Length + and then Ast.List.Element (1).Kind = Kind_Symbol + and then Ast.List.Element (1).Symbol = Symbols.Names.Unquote then - return Eval (Ast.L.Element (2), Env); + return Eval (Ast.List.Element (2), Env); else - return Quasiquote_List (Ast.L); + return Quasiquote_List (Ast.List); end if; when others => return Ast; end case; end Quasiquote; + function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + + procedure Rep (Env : in Envs.Ptr) is + begin + Print (Eval (Read, Env)); + end Rep; + ---------------------------------------------------------------------- - Startup : constant String := "(do" + Startup : constant String := "(do " & "(def! not (fn* (a) (if a false true)))" & "(def! load-file (fn* (f)" & " (eval (read-string (str ""(do "" (slurp f) "")"")))))" & ")"; - Repl : Environments.Ptr renames Environments.Repl; + Repl : Envs.Ptr renames Envs.Repl; use Ada.Command_Line; begin - Core.Eval_Ref := Eval'Unrestricted_Access; - Discard (Eval (Read (Startup), Repl)); + -- Show the Eval function to other packages. + Eval_Cb.Cb := Eval'Unrestricted_Access; + -- Add Core functions into the top environment. + for Binding of Core.Ns loop + Repl.Set (Binding.Symbol, (Kind_Builtin, Binding.Builtin)); + end loop; + -- Native startup procedure. + Eval_P (Reader.Read_Str (Startup), Repl); + -- Define ARGV from command line arguments. declare Args : Mal.T_Array (2 .. Argument_Count); begin @@ -313,9 +305,23 @@ begin end loop; Repl.Set (Symbols.Constructor ("*ARGV*"), Lists.List (Args)); end; - if Argument_Count = 0 then - Interactive_Loop (Repl); + -- Script? + if 0 < Argument_Count then + Eval_P (Reader.Read_Str ("(load-file """ & Argument (1) & """)"), Repl); else - Discard (Eval (Read ("(load-file """ & Argument (1) & """)"), Repl)); + loop + begin + Rep (Repl); + exception + when Readline.End_Of_File => + exit; + when Reader.Empty_Source => + null; + when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- Other exceptions are unexpected. + end; + end loop; + Ada.Text_IO.New_Line; end if; end Step7_Quote; diff --git a/ada2/step8_macros.adb b/ada2/step8_macros.adb index c8712face3..5240b589f2 100644 --- a/ada2/step8_macros.adb +++ b/ada2/step8_macros.adb @@ -2,12 +2,13 @@ with Ada.Command_Line; with Ada.Exceptions; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; with Core; -with Environments; +with Envs; +with Eval_Cb; with Printer; with Reader; +with Readline; with Types.Functions; with Types.Lists; with Types.Mal; @@ -18,54 +19,49 @@ procedure Step8_Macros is package ASU renames Ada.Strings.Unbounded; use Types; - use type Symbols.Ptr; - function Read (Source : in String) return Mal.T - renames Reader.Read_Str; + function Read return Mal.T with Inline; function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T; + Env0 : in Envs.Ptr) return Mal.T; function Quasiquote (Ast : in Mal.T; - Env : in Environments.Ptr) return Mal.T; + Env : in Envs.Ptr) return Mal.T; -- Mergeing quote and quasiquote into eval with a flag triggering -- a different behaviour as done for macros in step8 would improve -- the performances significantly, but Kanaka finds that it breaks -- too much the step structure shared by all implementations. - function Print (Ast : in Mal.T; - Readably : in Boolean := True) return ASU.Unbounded_String - renames Printer.Pr_Str; + procedure Print (Ast : in Mal.T) with Inline; - function Rep (Source : in String; - Env : in Environments.Ptr) return ASU.Unbounded_String - is (Print (Eval (Read (Source), Env))) with Inline; + procedure Rep (Env : in Envs.Ptr) with Inline; - procedure Interactive_Loop (Repl : in Environments.Ptr); - - function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + -- Procedural form of Eval. -- Convenient when the result of eval is of no interest. - procedure Discard (Ast : in Mal.T) is null; + procedure Eval_P (Ast : in Mal.T; + Env : in Envs.Ptr) with Inline; ---------------------------------------------------------------------- function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T + Env0 : in Envs.Ptr) return Mal.T is + use type Symbols.Ptr; -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. - Ast : Mal.T := Ast0; - Env : Environments.Ptr := Env0.Copy_Pointer; - Macroexpanding : Boolean := False; + Ast : Mal.T := Ast0; + Env : Envs.Ptr := Env0.Copy_Pointer; + Macroexpanding : Boolean := False; First : Mal.T; begin <> -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); - -- Environments.Dump_Stack; + -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String | Kind_Keyword | Kind_Macro | Kind_Function @@ -76,92 +72,93 @@ procedure Step8_Macros is when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.L.Length = 0 then + if Ast.List.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); + First := Ast.List.Element (1); -- Special forms if First.Kind /= Kind_Symbol then -- Evaluate First, in the less frequent case where it is -- not a symbol. First := Eval (First, Env); elsif First.Symbol = Symbols.Names.Def then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.L.Element (2).Kind /= Kind_Symbol then + elsif Ast.List.Element (2).Kind /= Kind_Symbol then raise Argument_Error with "def!: arg 1 must be a symbol"; end if; - return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).Symbol, R); + return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do + Env.Set (Ast.List.Element (2).Symbol, R); end return; elsif First.Symbol = Symbols.Names.Defmacro then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "defmacro!: expects 2 arguments"; - elsif Ast.L.Element (2).Kind /= Kind_Symbol then + elsif Ast.List.Element (2).Kind /= Kind_Symbol then raise Argument_Error with "defmacro!: arg 1 must be a symbol"; end if; declare - F : constant Mal.T := Eval (Ast.L.Element (3), Env); + F : constant Mal.T := Eval (Ast.List.Element (3), Env); begin if F.Kind /= Kind_Function then raise Argument_Error with "defmacro!: expects a function"; end if; - return R : constant Mal.T := F.Function_Value.New_Macro do - Env.Set (Ast.L.Element (2).Symbol, R); + return R : constant Mal.T := F.Fn.New_Macro do + Env.Set (Ast.List.Element (2).Symbol, R); end return; end; elsif First.Symbol = Symbols.Names.Mal_Do then - if Ast.L.Length = 1 then + if Ast.List.Length = 1 then raise Argument_Error with "do: expects at least 1 argument"; end if; - for I in 2 .. Ast.L.Length - 1 loop - Discard (Eval (Ast.L.Element (I), Env)); + for I in 2 .. Ast.List.Length - 1 loop + Eval_P (Ast.List.Element (I), Env); end loop; - Ast := Ast.L.Element (Ast.L.Length); + Ast := Ast.List.Element (Ast.List.Length); goto Restart; elsif First.Symbol = Symbols.Names.Fn then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "fn*: expects 3 arguments"; - elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then raise Argument_Error with "fn*: arg 1 must be a list or vector"; - elsif (for some F in 1 .. Ast.L.Element (2).L.Length => - Ast.L.Element (2).L.Element (F).Kind /= Kind_Symbol) + elsif (for some F in 1 .. Ast.List.Element (2).List.Length => + Ast.List.Element (2).List.Element (F).Kind /= Kind_Symbol) then raise Argument_Error with "fn*: arg 2 must contain symbols"; end if; - return Functions.New_Function (Ast.L.Element (2).L, - Ast.L.Element (3), Env.New_Closure); + return Functions.New_Function (Params => Ast.List.Element (2).List, + Ast => Ast.List.Element (3), + Env => Env.New_Closure); elsif First.Symbol = Symbols.Names.Mal_If then - if Ast.L.Length not in 3 .. 4 then + if Ast.List.Length not in 3 .. 4 then raise Argument_Error with "if: expects 2 or 3 arguments"; end if; declare - Test : constant Mal.T := Eval (Ast.L.Element (2), Env); + Test : constant Mal.T := Eval (Ast.List.Element (2), Env); begin if (case Test.Kind is when Kind_Nil => False, when Kind_Boolean => Test.Ada_Boolean, when others => True) then - Ast := Ast.L.Element (3); + Ast := Ast.List.Element (3); goto Restart; - elsif Ast.L.Length = 3 then + elsif Ast.List.Length = 3 then return Mal.Nil; else - Ast := Ast.L.Element (4); + Ast := Ast.List.Element (4); goto Restart; end if; end; elsif First.Symbol = Symbols.Names.Let then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then raise Argument_Error with "let*: expects a list or vector"; end if; declare - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + Bindings : constant Lists.Ptr := Ast.List.Element (2).List; begin if Bindings.Length mod 2 /= 0 then raise Argument_Error with "let*: odd number of bindings"; @@ -174,26 +171,26 @@ procedure Step8_Macros is Env.Set (Bindings.Element (2 * I - 1).Symbol, Eval (Bindings.Element (2 * I), Env)); end loop; - Ast := Ast.L.Element (3); + Ast := Ast.List.Element (3); goto Restart; end; elsif First.Symbol = Symbols.Names.Macroexpand then - if Ast.L.Length /= 2 then + if Ast.List.Length /= 2 then raise Argument_Error with "macroexpand: expects 1 argument"; end if; Macroexpanding := True; - Ast := Ast.L.Element (2); + Ast := Ast.List.Element (2); goto Restart; elsif First.Symbol = Symbols.Names.Quasiquote then - if Ast.L.Length /= 2 then + if Ast.List.Length /= 2 then raise Argument_Error with "quasiquote: expects 1 argument"; end if; - return Quasiquote (Ast.L.Element (2), Env); + return Quasiquote (Ast.List.Element (2), Env); elsif First.Symbol = Symbols.Names.Quote then - if Ast.L.Length /= 2 then + if Ast.List.Length /= 2 then raise Argument_Error with "quote: expects 1 argument"; end if; - return Ast.L.Element (2); + return Ast.List.Element (2); else -- Equivalent to First := Eval (First, Env), except that -- we already know enough to spare a recursive call in @@ -204,82 +201,61 @@ procedure Step8_Macros is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; return First.Builtin.all (Args); end; when Kind_Function => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; - Env.Replace_With_Closure_Sub (First.Function_Value.Closure); - First.Function_Value.Set_Binds (Env, Args); - Ast := First.Function_Value.Expression; + Env.Replace_With_Sub (Outer => First.Fn.Env, + Binds => First.Fn.Params, + Exprs => Args); + Ast := First.Fn.Ast; goto Restart; end; when Kind_Macro => - declare - New_Env : constant Environments.Ptr := Env.Sub; - begin - First.Function_Value.Set_Binds (New_Env, Ast.L); - Ast := Eval (First.Function_Value.Expression, New_Env); - end; + Ast := Eval (Ast0 => First.Fn.Ast, + Env0 => Envs.Sub (Outer => Env, + Binds => First.Fn.Params, + Exprs => Ast.List)); if Macroexpanding then return Ast; else goto Restart; end if; when others => - raise Argument_Error - with "cannot execute " & ASU.To_String (Print (First)); + raise Argument_Error with "cannot call " & Printer.Img (First); end case; end case; end Eval; - procedure Interactive_Loop (Repl : in Environments.Ptr) is - use Interfaces.C, Interfaces.C.Strings; - function Readline (Prompt : in char_array) return chars_ptr - with Import, Convention => C, External_Name => "readline"; - procedure Add_History (Line : in chars_ptr) - with Import, Convention => C, External_Name => "add_history"; - procedure Free (Line : in chars_ptr) - with Import, Convention => C, External_Name => "free"; - Prompt : constant char_array := To_C ("user> "); - C_Line : chars_ptr; + procedure Eval_P (Ast : in Mal.T; + Env : in Envs.Ptr) + is + Result : constant Mal.T := Eval (Ast, Env); begin - loop - C_Line := Readline (Prompt); - exit when C_Line = Null_Ptr; - declare - Line : constant String := Value (C_Line); - begin - if Line /= "" then - Add_History (C_Line); - end if; - Free (C_Line); - Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); - exception - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error - | Environments.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. - end; - end loop; - Ada.Text_IO.New_Line; - end Interactive_Loop; + pragma Unreferenced (Result); + end Eval_P; + + procedure Print (Ast : in Mal.T) is + begin + Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); + end Print; function Quasiquote (Ast : in Mal.T; - Env : in Environments.Ptr) return Mal.T + Env : in Envs.Ptr) return Mal.T is + use type Symbols.Ptr; + function Quasiquote_List (List : in Lists.Ptr) return Mal.T with Inline; -- Handle vectors and lists not starting with unquote. @@ -290,15 +266,15 @@ procedure Step8_Macros is for I in R'Range loop R (I) := List.Element (I); if R (I).Kind in Kind_List | Kind_Vector - and then 0 < R (I).L.Length - and then R (I).L.Element (1).Kind = Kind_Symbol - and then R (I).L.Element (1).Symbol + and then 0 < R (I).List.Length + and then R (I).List.Element (1).Kind = Kind_Symbol + and then R (I).List.Element (1).Symbol = Symbols.Names.Splice_Unquote then - if R (I).L.Length /= 2 then + if R (I).List.Length /= 2 then raise Argument_Error with "splice-unquote: expects 1 arg"; end if; - R (I) := Eval (R (I).L.Element (2), Env); + R (I) := Eval (R (I).List.Element (2), Env); if R (I).Kind /= Kind_List then raise Argument_Error with "splice-unquote: expects a list"; end if; @@ -314,24 +290,31 @@ procedure Step8_Macros is case Ast.Kind is when Kind_Vector => -- When the test is updated, replace Kind_List with Kind_Vector. - return Quasiquote_List (Ast.L); + return Quasiquote_List (Ast.List); when Kind_List => - if 0 < Ast.L.Length - and then Ast.L.Element (1).Kind = Kind_Symbol - and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote + if 0 < Ast.List.Length + and then Ast.List.Element (1).Kind = Kind_Symbol + and then Ast.List.Element (1).Symbol = Symbols.Names.Unquote then - return Eval (Ast.L.Element (2), Env); + return Eval (Ast.List.Element (2), Env); else - return Quasiquote_List (Ast.L); + return Quasiquote_List (Ast.List); end if; when others => return Ast; end case; end Quasiquote; + function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + + procedure Rep (Env : in Envs.Ptr) is + begin + Print (Eval (Read, Env)); + end Rep; + ---------------------------------------------------------------------- - Startup : constant String := "(do" + Startup : constant String := "(do " & "(def! not (fn* (a) (if a false true)))" & "(def! load-file (fn* (f)" & " (eval (read-string (str ""(do "" (slurp f) "")"")))))" @@ -347,11 +330,18 @@ procedure Step8_Macros is & " `(let* (or_FIXME ~(first xs))" & " (if or_FIXME or_FIXME (or ~@(rest xs))))))))" & ")"; - Repl : Environments.Ptr renames Environments.Repl; + Repl : Envs.Ptr renames Envs.Repl; use Ada.Command_Line; begin - Core.Eval_Ref := Eval'Unrestricted_Access; - Discard (Eval (Read (Startup), Repl)); + -- Show the Eval function to other packages. + Eval_Cb.Cb := Eval'Unrestricted_Access; + -- Add Core functions into the top environment. + for Binding of Core.Ns loop + Repl.Set (Binding.Symbol, (Kind_Builtin, Binding.Builtin)); + end loop; + -- Native startup procedure. + Eval_P (Reader.Read_Str (Startup), Repl); + -- Define ARGV from command line arguments. declare Args : Mal.T_Array (2 .. Argument_Count); begin @@ -360,9 +350,23 @@ begin end loop; Repl.Set (Symbols.Constructor ("*ARGV*"), Lists.List (Args)); end; - if Argument_Count = 0 then - Interactive_Loop (Repl); + -- Script? + if 0 < Argument_Count then + Eval_P (Reader.Read_Str ("(load-file """ & Argument (1) & """)"), Repl); else - Discard (Eval (Read ("(load-file """ & Argument (1) & """)"), Repl)); + loop + begin + Rep (Repl); + exception + when Readline.End_Of_File => + exit; + when Reader.Empty_Source => + null; + when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + -- Other exceptions are unexpected. + end; + end loop; + Ada.Text_IO.New_Line; end if; end Step8_Macros; diff --git a/ada2/step9_try.adb b/ada2/step9_try.adb index 2fa414f0ef..8e90bd663d 100644 --- a/ada2/step9_try.adb +++ b/ada2/step9_try.adb @@ -2,12 +2,13 @@ with Ada.Command_Line; with Ada.Exceptions; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; with Core; -with Environments; +with Envs; +with Eval_Cb; with Printer; with Reader; +with Readline; with Types.Functions; with Types.Lists; with Types.Mal; @@ -18,54 +19,49 @@ procedure Step9_Try is package ASU renames Ada.Strings.Unbounded; use Types; - use type Symbols.Ptr; - function Read (Source : in String) return Mal.T - renames Reader.Read_Str; + function Read return Mal.T with Inline; function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T; + Env0 : in Envs.Ptr) return Mal.T; function Quasiquote (Ast : in Mal.T; - Env : in Environments.Ptr) return Mal.T; + Env : in Envs.Ptr) return Mal.T; -- Mergeing quote and quasiquote into eval with a flag triggering -- a different behaviour as done for macros in step8 would improve -- the performances significantly, but Kanaka finds that it breaks -- too much the step structure shared by all implementations. - function Print (Ast : in Mal.T; - Readably : in Boolean := True) return ASU.Unbounded_String - renames Printer.Pr_Str; + procedure Print (Ast : in Mal.T) with Inline; - function Rep (Source : in String; - Env : in Environments.Ptr) return ASU.Unbounded_String - is (Print (Eval (Read (Source), Env))) with Inline; + procedure Rep (Env : in Envs.Ptr) with Inline; - procedure Interactive_Loop (Repl : in Environments.Ptr); - - function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + -- Procedural form of Eval. -- Convenient when the result of eval is of no interest. - procedure Discard (Ast : in Mal.T) is null; + procedure Eval_P (Ast : in Mal.T; + Env : in Envs.Ptr) with Inline; ---------------------------------------------------------------------- function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T + Env0 : in Envs.Ptr) return Mal.T is + use type Symbols.Ptr; -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. - Ast : Mal.T := Ast0; - Env : Environments.Ptr := Env0.Copy_Pointer; - Macroexpanding : Boolean := False; + Ast : Mal.T := Ast0; + Env : Envs.Ptr := Env0.Copy_Pointer; + Macroexpanding : Boolean := False; First : Mal.T; begin <> -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); - -- Environments.Dump_Stack; + -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String | Kind_Keyword | Kind_Macro | Kind_Function @@ -76,92 +72,93 @@ procedure Step9_Try is when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.L.Length = 0 then + if Ast.List.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); + First := Ast.List.Element (1); -- Special forms if First.Kind /= Kind_Symbol then -- Evaluate First, in the less frequent case where it is -- not a symbol. First := Eval (First, Env); elsif First.Symbol = Symbols.Names.Def then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.L.Element (2).Kind /= Kind_Symbol then + elsif Ast.List.Element (2).Kind /= Kind_Symbol then raise Argument_Error with "def!: arg 1 must be a symbol"; end if; - return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).Symbol, R); + return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do + Env.Set (Ast.List.Element (2).Symbol, R); end return; elsif First.Symbol = Symbols.Names.Defmacro then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "defmacro!: expects 2 arguments"; - elsif Ast.L.Element (2).Kind /= Kind_Symbol then + elsif Ast.List.Element (2).Kind /= Kind_Symbol then raise Argument_Error with "defmacro!: arg 1 must be a symbol"; end if; declare - F : constant Mal.T := Eval (Ast.L.Element (3), Env); + F : constant Mal.T := Eval (Ast.List.Element (3), Env); begin if F.Kind /= Kind_Function then raise Argument_Error with "defmacro!: expects a function"; end if; - return R : constant Mal.T := F.Function_Value.New_Macro do - Env.Set (Ast.L.Element (2).Symbol, R); + return R : constant Mal.T := F.Fn.New_Macro do + Env.Set (Ast.List.Element (2).Symbol, R); end return; end; elsif First.Symbol = Symbols.Names.Mal_Do then - if Ast.L.Length = 1 then + if Ast.List.Length = 1 then raise Argument_Error with "do: expects at least 1 argument"; end if; - for I in 2 .. Ast.L.Length - 1 loop - Discard (Eval (Ast.L.Element (I), Env)); + for I in 2 .. Ast.List.Length - 1 loop + Eval_P (Ast.List.Element (I), Env); end loop; - Ast := Ast.L.Element (Ast.L.Length); + Ast := Ast.List.Element (Ast.List.Length); goto Restart; elsif First.Symbol = Symbols.Names.Fn then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "fn*: expects 3 arguments"; - elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then raise Argument_Error with "fn*: arg 1 must be a list or vector"; - elsif (for some F in 1 .. Ast.L.Element (2).L.Length => - Ast.L.Element (2).L.Element (F).Kind /= Kind_Symbol) + elsif (for some F in 1 .. Ast.List.Element (2).List.Length => + Ast.List.Element (2).List.Element (F).Kind /= Kind_Symbol) then raise Argument_Error with "fn*: arg 2 must contain symbols"; end if; - return Functions.New_Function (Ast.L.Element (2).L, - Ast.L.Element (3), Env.New_Closure); + return Functions.New_Function (Params => Ast.List.Element (2).List, + Ast => Ast.List.Element (3), + Env => Env.New_Closure); elsif First.Symbol = Symbols.Names.Mal_If then - if Ast.L.Length not in 3 .. 4 then + if Ast.List.Length not in 3 .. 4 then raise Argument_Error with "if: expects 2 or 3 arguments"; end if; declare - Test : constant Mal.T := Eval (Ast.L.Element (2), Env); + Test : constant Mal.T := Eval (Ast.List.Element (2), Env); begin if (case Test.Kind is when Kind_Nil => False, when Kind_Boolean => Test.Ada_Boolean, when others => True) then - Ast := Ast.L.Element (3); + Ast := Ast.List.Element (3); goto Restart; - elsif Ast.L.Length = 3 then + elsif Ast.List.Length = 3 then return Mal.Nil; else - Ast := Ast.L.Element (4); + Ast := Ast.List.Element (4); goto Restart; end if; end; elsif First.Symbol = Symbols.Names.Let then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then raise Argument_Error with "let*: expects a list or vector"; end if; declare - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + Bindings : constant Lists.Ptr := Ast.List.Element (2).List; begin if Bindings.Length mod 2 /= 0 then raise Argument_Error with "let*: odd number of bindings"; @@ -174,37 +171,37 @@ procedure Step9_Try is Env.Set (Bindings.Element (2 * I - 1).Symbol, Eval (Bindings.Element (2 * I), Env)); end loop; - Ast := Ast.L.Element (3); + Ast := Ast.List.Element (3); goto Restart; end; elsif First.Symbol = Symbols.Names.Macroexpand then - if Ast.L.Length /= 2 then + if Ast.List.Length /= 2 then raise Argument_Error with "macroexpand: expects 1 argument"; end if; Macroexpanding := True; - Ast := Ast.L.Element (2); + Ast := Ast.List.Element (2); goto Restart; elsif First.Symbol = Symbols.Names.Quasiquote then - if Ast.L.Length /= 2 then + if Ast.List.Length /= 2 then raise Argument_Error with "quasiquote: expects 1 argument"; end if; - return Quasiquote (Ast.L.Element (2), Env); + return Quasiquote (Ast.List.Element (2), Env); elsif First.Symbol = Symbols.Names.Quote then - if Ast.L.Length /= 2 then + if Ast.List.Length /= 2 then raise Argument_Error with "quote: expects 1 argument"; end if; - return Ast.L.Element (2); + return Ast.List.Element (2); elsif First.Symbol = Symbols.Names.Try then - if Ast.L.Length = 2 then - Ast := Ast.L.Element (2); + if Ast.List.Length = 2 then + Ast := Ast.List.Element (2); goto Restart; - elsif Ast.L.Length /= 3 then + elsif Ast.List.Length /= 3 then raise Argument_Error with "try*: expects 1 or 2 arguments"; - elsif Ast.L.Element (3).Kind /= Kind_List then + elsif Ast.List.Element (3).Kind /= Kind_List then raise Argument_Error with "try*: argument 2 must be a list"; end if; declare - A3 : constant Lists.Ptr := Ast.L.Element (3).L; + A3 : constant Lists.Ptr := Ast.List.Element (3).List; begin if A3.Length /= 3 then raise Argument_Error with "try*: arg 2 must have 3 elements"; @@ -216,10 +213,10 @@ procedure Step9_Try is raise Argument_Error with "catch*: expects a symbol"; end if; begin - return Eval (Ast.L.Element (2), Env); + return Eval (Ast.List.Element (2), Env); exception when E : Reader.Empty_Source | Argument_Error - | Reader.Reader_Error | Environments.Unknown_Key => + | Reader.Reader_Error | Envs.Unknown_Key => Env.Replace_With_Sub; Env.Set (A3.Element (2).Symbol, Mal.T'(Kind_String, ASU.To_Unbounded_String @@ -229,7 +226,6 @@ procedure Step9_Try is when Core.Exception_Throwed => Env.Replace_With_Sub; Env.Set (A3.Element (2).Symbol, Core.Last_Exception); - Core.Last_Exception := Mal.Nil; Ast := A3.Element (3); goto Restart; -- Other exceptions are unexpected. @@ -245,86 +241,61 @@ procedure Step9_Try is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; return First.Builtin.all (Args); end; when Kind_Function => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; - Env.Replace_With_Closure_Sub (First.Function_Value.Closure); - First.Function_Value.Set_Binds (Env, Args); - Ast := First.Function_Value.Expression; + Env.Replace_With_Sub (Outer => First.Fn.Env, + Binds => First.Fn.Params, + Exprs => Args); + Ast := First.Fn.Ast; goto Restart; end; when Kind_Macro => - declare - New_Env : constant Environments.Ptr := Env.Sub; - begin - First.Function_Value.Set_Binds (New_Env, Ast.L); - Ast := Eval (First.Function_Value.Expression, New_Env); - end; + Ast := Eval (Ast0 => First.Fn.Ast, + Env0 => Envs.Sub (Outer => Env, + Binds => First.Fn.Params, + Exprs => Ast.List)); if Macroexpanding then return Ast; else goto Restart; end if; when others => - raise Argument_Error - with "cannot execute " & ASU.To_String (Print (First)); + raise Argument_Error with "cannot call " & Printer.Img (First); end case; end case; end Eval; - procedure Interactive_Loop (Repl : in Environments.Ptr) is - use Interfaces.C, Interfaces.C.Strings; - function Readline (Prompt : in char_array) return chars_ptr - with Import, Convention => C, External_Name => "readline"; - procedure Add_History (Line : in chars_ptr) - with Import, Convention => C, External_Name => "add_history"; - procedure Free (Line : in chars_ptr) - with Import, Convention => C, External_Name => "free"; - Prompt : constant char_array := To_C ("user> "); - C_Line : chars_ptr; + procedure Eval_P (Ast : in Mal.T; + Env : in Envs.Ptr) + is + Result : constant Mal.T := Eval (Ast, Env); begin - loop - C_Line := Readline (Prompt); - exit when C_Line = Null_Ptr; - declare - Line : constant String := Value (C_Line); - begin - if Line /= "" then - Add_History (C_Line); - end if; - Free (C_Line); - Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); - exception - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error - | Environments.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - when Core.Exception_Throwed => - Ada.Text_IO.Put ("User exception: "); - Ada.Text_IO.Unbounded_IO.Put_Line (Print (Core.Last_Exception)); - Core.Last_Exception := Mal.Nil; - -- Other exceptions are unexpected. - end; - end loop; - Ada.Text_IO.New_Line; - end Interactive_Loop; + pragma Unreferenced (Result); + end Eval_P; + + procedure Print (Ast : in Mal.T) is + begin + Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); + end Print; function Quasiquote (Ast : in Mal.T; - Env : in Environments.Ptr) return Mal.T + Env : in Envs.Ptr) return Mal.T is + use type Symbols.Ptr; + function Quasiquote_List (List : in Lists.Ptr) return Mal.T with Inline; -- Handle vectors and lists not starting with unquote. @@ -335,15 +306,15 @@ procedure Step9_Try is for I in R'Range loop R (I) := List.Element (I); if R (I).Kind in Kind_List | Kind_Vector - and then 0 < R (I).L.Length - and then R (I).L.Element (1).Kind = Kind_Symbol - and then R (I).L.Element (1).Symbol + and then 0 < R (I).List.Length + and then R (I).List.Element (1).Kind = Kind_Symbol + and then R (I).List.Element (1).Symbol = Symbols.Names.Splice_Unquote then - if R (I).L.Length /= 2 then + if R (I).List.Length /= 2 then raise Argument_Error with "splice-unquote: expects 1 arg"; end if; - R (I) := Eval (R (I).L.Element (2), Env); + R (I) := Eval (R (I).List.Element (2), Env); if R (I).Kind /= Kind_List then raise Argument_Error with "splice-unquote: expects a list"; end if; @@ -359,24 +330,31 @@ procedure Step9_Try is case Ast.Kind is when Kind_Vector => -- When the test is updated, replace Kind_List with Kind_Vector. - return Quasiquote_List (Ast.L); + return Quasiquote_List (Ast.List); when Kind_List => - if 0 < Ast.L.Length - and then Ast.L.Element (1).Kind = Kind_Symbol - and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote + if 0 < Ast.List.Length + and then Ast.List.Element (1).Kind = Kind_Symbol + and then Ast.List.Element (1).Symbol = Symbols.Names.Unquote then - return Eval (Ast.L.Element (2), Env); + return Eval (Ast.List.Element (2), Env); else - return Quasiquote_List (Ast.L); + return Quasiquote_List (Ast.List); end if; when others => return Ast; end case; end Quasiquote; + function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + + procedure Rep (Env : in Envs.Ptr) is + begin + Print (Eval (Read, Env)); + end Rep; + ---------------------------------------------------------------------- - Startup : constant String := "(do" + Startup : constant String := "(do " & "(def! not (fn* (a) (if a false true)))" & "(def! load-file (fn* (f)" & " (eval (read-string (str ""(do "" (slurp f) "")"")))))" @@ -392,11 +370,18 @@ procedure Step9_Try is & " `(let* (or_FIXME ~(first xs))" & " (if or_FIXME or_FIXME (or ~@(rest xs))))))))" & ")"; - Repl : Environments.Ptr renames Environments.Repl; + Repl : Envs.Ptr renames Envs.Repl; use Ada.Command_Line; begin - Core.Eval_Ref := Eval'Unrestricted_Access; - Discard (Eval (Read (Startup), Repl)); + -- Show the Eval function to other packages. + Eval_Cb.Cb := Eval'Unrestricted_Access; + -- Add Core functions into the top environment. + for Binding of Core.Ns loop + Repl.Set (Binding.Symbol, (Kind_Builtin, Binding.Builtin)); + end loop; + -- Native startup procedure. + Eval_P (Reader.Read_Str (Startup), Repl); + -- Define ARGV from command line arguments. declare Args : Mal.T_Array (2 .. Argument_Count); begin @@ -405,9 +390,27 @@ begin end loop; Repl.Set (Symbols.Constructor ("*ARGV*"), Lists.List (Args)); end; - if Argument_Count = 0 then - Interactive_Loop (Repl); + -- Script? + if 0 < Argument_Count then + Eval_P (Reader.Read_Str ("(load-file """ & Argument (1) & """)"), Repl); else - Discard (Eval (Read ("(load-file """ & Argument (1) & """)"), Repl)); + loop + begin + Rep (Repl); + exception + when Readline.End_Of_File => + exit; + when Reader.Empty_Source => + null; + when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + when Core.Exception_Throwed => + Ada.Text_IO.Put ("User exception: "); + Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str + (Core.Last_Exception)); + -- Other exceptions are unexpected. + end; + end loop; + Ada.Text_IO.New_Line; end if; end Step9_Try; diff --git a/ada2/stepa_mal.adb b/ada2/stepa_mal.adb index b660e17753..355b4be173 100644 --- a/ada2/stepa_mal.adb +++ b/ada2/stepa_mal.adb @@ -2,12 +2,13 @@ with Ada.Command_Line; with Ada.Exceptions; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; -with Interfaces.C.Strings; with Core; -with Environments; +with Envs; +with Eval_Cb; with Printer; with Reader; +with Readline; with Types.Functions; with Types.Lists; with Types.Mal; @@ -18,54 +19,49 @@ procedure StepA_Mal is package ASU renames Ada.Strings.Unbounded; use Types; - use type Symbols.Ptr; - function Read (Source : in String) return Mal.T - renames Reader.Read_Str; + function Read return Mal.T with Inline; function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T; + Env0 : in Envs.Ptr) return Mal.T; function Quasiquote (Ast : in Mal.T; - Env : in Environments.Ptr) return Mal.T; + Env : in Envs.Ptr) return Mal.T; -- Mergeing quote and quasiquote into eval with a flag triggering -- a different behaviour as done for macros in step8 would improve -- the performances significantly, but Kanaka finds that it breaks -- too much the step structure shared by all implementations. - function Print (Ast : in Mal.T; - Readably : in Boolean := True) return ASU.Unbounded_String - renames Printer.Pr_Str; + procedure Print (Ast : in Mal.T) with Inline; - function Rep (Source : in String; - Env : in Environments.Ptr) return ASU.Unbounded_String - is (Print (Eval (Read (Source), Env))) with Inline; + procedure Rep (Env : in Envs.Ptr) with Inline; - procedure Interactive_Loop (Repl : in Environments.Ptr); - - function Eval_List_Elts is new Lists.Generic_Eval (Environments.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Environments.Ptr, Eval); + function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + -- Procedural form of Eval. -- Convenient when the result of eval is of no interest. - procedure Discard (Ast : in Mal.T) is null; + procedure Eval_P (Ast : in Mal.T; + Env : in Envs.Ptr) with Inline; ---------------------------------------------------------------------- function Eval (Ast0 : in Mal.T; - Env0 : in Environments.Ptr) return Mal.T + Env0 : in Envs.Ptr) return Mal.T is + use type Symbols.Ptr; -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. - Ast : Mal.T := Ast0; - Env : Environments.Ptr := Env0.Copy_Pointer; - Macroexpanding : Boolean := False; + Ast : Mal.T := Ast0; + Env : Envs.Ptr := Env0.Copy_Pointer; + Macroexpanding : Boolean := False; First : Mal.T; begin <> -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); - -- Environments.Dump_Stack; + -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String | Kind_Keyword | Kind_Macro | Kind_Function @@ -76,92 +72,93 @@ procedure StepA_Mal is when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.L, Env)); + return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.L.Length = 0 then + if Ast.List.Length = 0 then return Ast; end if; - First := Ast.L.Element (1); + First := Ast.List.Element (1); -- Special forms if First.Kind /= Kind_Symbol then -- Evaluate First, in the less frequent case where it is -- not a symbol. First := Eval (First, Env); elsif First.Symbol = Symbols.Names.Def then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.L.Element (2).Kind /= Kind_Symbol then + elsif Ast.List.Element (2).Kind /= Kind_Symbol then raise Argument_Error with "def!: arg 1 must be a symbol"; end if; - return R : constant Mal.T := Eval (Ast.L.Element (3), Env) do - Env.Set (Ast.L.Element (2).Symbol, R); + return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do + Env.Set (Ast.List.Element (2).Symbol, R); end return; elsif First.Symbol = Symbols.Names.Defmacro then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "defmacro!: expects 2 arguments"; - elsif Ast.L.Element (2).Kind /= Kind_Symbol then + elsif Ast.List.Element (2).Kind /= Kind_Symbol then raise Argument_Error with "defmacro!: arg 1 must be a symbol"; end if; declare - F : constant Mal.T := Eval (Ast.L.Element (3), Env); + F : constant Mal.T := Eval (Ast.List.Element (3), Env); begin if F.Kind /= Kind_Function then raise Argument_Error with "defmacro!: expects a function"; end if; - return R : constant Mal.T := F.Function_Value.New_Macro do - Env.Set (Ast.L.Element (2).Symbol, R); + return R : constant Mal.T := F.Fn.New_Macro do + Env.Set (Ast.List.Element (2).Symbol, R); end return; end; elsif First.Symbol = Symbols.Names.Mal_Do then - if Ast.L.Length = 1 then + if Ast.List.Length = 1 then raise Argument_Error with "do: expects at least 1 argument"; end if; - for I in 2 .. Ast.L.Length - 1 loop - Discard (Eval (Ast.L.Element (I), Env)); + for I in 2 .. Ast.List.Length - 1 loop + Eval_P (Ast.List.Element (I), Env); end loop; - Ast := Ast.L.Element (Ast.L.Length); + Ast := Ast.List.Element (Ast.List.Length); goto Restart; elsif First.Symbol = Symbols.Names.Fn then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "fn*: expects 3 arguments"; - elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then raise Argument_Error with "fn*: arg 1 must be a list or vector"; - elsif (for some F in 1 .. Ast.L.Element (2).L.Length => - Ast.L.Element (2).L.Element (F).Kind /= Kind_Symbol) + elsif (for some F in 1 .. Ast.List.Element (2).List.Length => + Ast.List.Element (2).List.Element (F).Kind /= Kind_Symbol) then raise Argument_Error with "fn*: arg 2 must contain symbols"; end if; - return Functions.New_Function (Ast.L.Element (2).L, - Ast.L.Element (3), Env.New_Closure); + return Functions.New_Function (Params => Ast.List.Element (2).List, + Ast => Ast.List.Element (3), + Env => Env.New_Closure); elsif First.Symbol = Symbols.Names.Mal_If then - if Ast.L.Length not in 3 .. 4 then + if Ast.List.Length not in 3 .. 4 then raise Argument_Error with "if: expects 2 or 3 arguments"; end if; declare - Test : constant Mal.T := Eval (Ast.L.Element (2), Env); + Test : constant Mal.T := Eval (Ast.List.Element (2), Env); begin if (case Test.Kind is when Kind_Nil => False, when Kind_Boolean => Test.Ada_Boolean, when others => True) then - Ast := Ast.L.Element (3); + Ast := Ast.List.Element (3); goto Restart; - elsif Ast.L.Length = 3 then + elsif Ast.List.Length = 3 then return Mal.Nil; else - Ast := Ast.L.Element (4); + Ast := Ast.List.Element (4); goto Restart; end if; end; elsif First.Symbol = Symbols.Names.Let then - if Ast.L.Length /= 3 then + if Ast.List.Length /= 3 then raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.L.Element (2).Kind not in Kind_List | Kind_Vector then + elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then raise Argument_Error with "let*: expects a list or vector"; end if; declare - Bindings : constant Lists.Ptr := Ast.L.Element (2).L; + Bindings : constant Lists.Ptr := Ast.List.Element (2).List; begin if Bindings.Length mod 2 /= 0 then raise Argument_Error with "let*: odd number of bindings"; @@ -174,37 +171,37 @@ procedure StepA_Mal is Env.Set (Bindings.Element (2 * I - 1).Symbol, Eval (Bindings.Element (2 * I), Env)); end loop; - Ast := Ast.L.Element (3); + Ast := Ast.List.Element (3); goto Restart; end; elsif First.Symbol = Symbols.Names.Macroexpand then - if Ast.L.Length /= 2 then + if Ast.List.Length /= 2 then raise Argument_Error with "macroexpand: expects 1 argument"; end if; Macroexpanding := True; - Ast := Ast.L.Element (2); + Ast := Ast.List.Element (2); goto Restart; elsif First.Symbol = Symbols.Names.Quasiquote then - if Ast.L.Length /= 2 then + if Ast.List.Length /= 2 then raise Argument_Error with "quasiquote: expects 1 argument"; end if; - return Quasiquote (Ast.L.Element (2), Env); + return Quasiquote (Ast.List.Element (2), Env); elsif First.Symbol = Symbols.Names.Quote then - if Ast.L.Length /= 2 then + if Ast.List.Length /= 2 then raise Argument_Error with "quote: expects 1 argument"; end if; - return Ast.L.Element (2); + return Ast.List.Element (2); elsif First.Symbol = Symbols.Names.Try then - if Ast.L.Length = 2 then - Ast := Ast.L.Element (2); + if Ast.List.Length = 2 then + Ast := Ast.List.Element (2); goto Restart; - elsif Ast.L.Length /= 3 then + elsif Ast.List.Length /= 3 then raise Argument_Error with "try*: expects 1 or 2 arguments"; - elsif Ast.L.Element (3).Kind /= Kind_List then + elsif Ast.List.Element (3).Kind /= Kind_List then raise Argument_Error with "try*: argument 2 must be a list"; end if; declare - A3 : constant Lists.Ptr := Ast.L.Element (3).L; + A3 : constant Lists.Ptr := Ast.List.Element (3).List; begin if A3.Length /= 3 then raise Argument_Error with "try*: arg 2 must have 3 elements"; @@ -216,10 +213,10 @@ procedure StepA_Mal is raise Argument_Error with "catch*: expects a symbol"; end if; begin - return Eval (Ast.L.Element (2), Env); + return Eval (Ast.List.Element (2), Env); exception when E : Reader.Empty_Source | Argument_Error - | Reader.Reader_Error | Environments.Unknown_Key => + | Reader.Reader_Error | Envs.Unknown_Key => Env.Replace_With_Sub; Env.Set (A3.Element (2).Symbol, Mal.T'(Kind_String, ASU.To_Unbounded_String @@ -229,7 +226,6 @@ procedure StepA_Mal is when Core.Exception_Throwed => Env.Replace_With_Sub; Env.Set (A3.Element (2).Symbol, Core.Last_Exception); - Core.Last_Exception := Mal.Nil; Ast := A3.Element (3); goto Restart; -- Other exceptions are unexpected. @@ -245,95 +241,70 @@ procedure StepA_Mal is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; return First.Builtin.all (Args); end; when Kind_Builtin_With_Meta => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; - return First.Builtin_With_Meta.Data.all (Args); + return First.Builtin_With_Meta.Builtin.all (Args); end; when Kind_Function => declare - Args : Mal.T_Array (2 .. Ast.L.Length); + Args : Mal.T_Array (2 .. Ast.List.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.L.Element (I), Env); + Args (I) := Eval (Ast.List.Element (I), Env); end loop; - Env.Replace_With_Closure_Sub (First.Function_Value.Closure); - First.Function_Value.Set_Binds (Env, Args); - Ast := First.Function_Value.Expression; + Env.Replace_With_Sub (Outer => First.Fn.Env, + Binds => First.Fn.Params, + Exprs => Args); + Ast := First.Fn.Ast; goto Restart; end; when Kind_Macro => - declare - New_Env : constant Environments.Ptr := Env.Sub; - begin - First.Function_Value.Set_Binds (New_Env, Ast.L); - Ast := Eval (First.Function_Value.Expression, New_Env); - end; + Ast := Eval (Ast0 => First.Fn.Ast, + Env0 => Envs.Sub (Outer => Env, + Binds => First.Fn.Params, + Exprs => Ast.List)); if Macroexpanding then return Ast; else goto Restart; end if; when others => - raise Argument_Error - with "cannot execute " & ASU.To_String (Print (First)); + raise Argument_Error with "cannot call " & Printer.Img (First); end case; end case; end Eval; - procedure Interactive_Loop (Repl : in Environments.Ptr) is - use Interfaces.C, Interfaces.C.Strings; - function Readline (Prompt : in char_array) return chars_ptr - with Import, Convention => C, External_Name => "readline"; - procedure Add_History (Line : in chars_ptr) - with Import, Convention => C, External_Name => "add_history"; - procedure Free (Line : in chars_ptr) - with Import, Convention => C, External_Name => "free"; - Prompt : constant char_array := To_C ("user> "); - C_Line : chars_ptr; + procedure Eval_P (Ast : in Mal.T; + Env : in Envs.Ptr) + is + Result : constant Mal.T := Eval (Ast, Env); begin - loop - C_Line := Readline (Prompt); - exit when C_Line = Null_Ptr; - declare - Line : constant String := Value (C_Line); - begin - if Line /= "" then - Add_History (C_Line); - end if; - Free (C_Line); - Ada.Text_IO.Unbounded_IO.Put_Line (Rep (Line, Repl)); - exception - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error - | Environments.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - when Core.Exception_Throwed => - Ada.Text_IO.Put ("User exception: "); - Ada.Text_IO.Unbounded_IO.Put_Line (Print (Core.Last_Exception)); - Core.Last_Exception := Mal.Nil; - -- Other exceptions are unexpected. - end; - end loop; - Ada.Text_IO.New_Line; - end Interactive_Loop; + pragma Unreferenced (Result); + end Eval_P; + + procedure Print (Ast : in Mal.T) is + begin + Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); + end Print; function Quasiquote (Ast : in Mal.T; - Env : in Environments.Ptr) return Mal.T + Env : in Envs.Ptr) return Mal.T is + use type Symbols.Ptr; + function Quasiquote_List (List : in Lists.Ptr) return Mal.T with Inline; -- Handle vectors and lists not starting with unquote. @@ -344,15 +315,15 @@ procedure StepA_Mal is for I in R'Range loop R (I) := List.Element (I); if R (I).Kind in Kind_List | Kind_Vector - and then 0 < R (I).L.Length - and then R (I).L.Element (1).Kind = Kind_Symbol - and then R (I).L.Element (1).Symbol + and then 0 < R (I).List.Length + and then R (I).List.Element (1).Kind = Kind_Symbol + and then R (I).List.Element (1).Symbol = Symbols.Names.Splice_Unquote then - if R (I).L.Length /= 2 then + if R (I).List.Length /= 2 then raise Argument_Error with "splice-unquote: expects 1 arg"; end if; - R (I) := Eval (R (I).L.Element (2), Env); + R (I) := Eval (R (I).List.Element (2), Env); if R (I).Kind /= Kind_List then raise Argument_Error with "splice-unquote: expects a list"; end if; @@ -368,24 +339,31 @@ procedure StepA_Mal is case Ast.Kind is when Kind_Vector => -- When the test is updated, replace Kind_List with Kind_Vector. - return Quasiquote_List (Ast.L); + return Quasiquote_List (Ast.List); when Kind_List => - if 0 < Ast.L.Length - and then Ast.L.Element (1).Kind = Kind_Symbol - and then Ast.L.Element (1).Symbol = Symbols.Names.Unquote + if 0 < Ast.List.Length + and then Ast.List.Element (1).Kind = Kind_Symbol + and then Ast.List.Element (1).Symbol = Symbols.Names.Unquote then - return Eval (Ast.L.Element (2), Env); + return Eval (Ast.List.Element (2), Env); else - return Quasiquote_List (Ast.L); + return Quasiquote_List (Ast.List); end if; when others => return Ast; end case; end Quasiquote; + function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + + procedure Rep (Env : in Envs.Ptr) is + begin + Print (Eval (Read, Env)); + end Rep; + ---------------------------------------------------------------------- - Startup : constant String := "(do" + Startup : constant String := "(do " & "(def! not (fn* (a) (if a false true)))" & "(def! load-file (fn* (f)" & " (eval (read-string (str ""(do "" (slurp f) "")"")))))" @@ -406,11 +384,18 @@ procedure StepA_Mal is & " (if ~condvar ~condvar (or ~@(rest xs)))))))))" & "(def! *host-language* ""ada2"")" & ")"; - Repl : Environments.Ptr renames Environments.Repl; + Repl : Envs.Ptr renames Envs.Repl; use Ada.Command_Line; begin - Core.Eval_Ref := Eval'Unrestricted_Access; - Discard (Eval (Read (Startup), Repl)); + -- Show the Eval function to other packages. + Eval_Cb.Cb := Eval'Unrestricted_Access; + -- Add Core functions into the top environment. + for Binding of Core.Ns loop + Repl.Set (Binding.Symbol, (Kind_Builtin, Binding.Builtin)); + end loop; + -- Native startup procedure. + Eval_P (Reader.Read_Str (Startup), Repl); + -- Define ARGV from command line arguments. declare Args : Mal.T_Array (2 .. Argument_Count); begin @@ -419,11 +404,29 @@ begin end loop; Repl.Set (Symbols.Constructor ("*ARGV*"), Lists.List (Args)); end; - if Argument_Count = 0 then - Discard (Eval (Read ("(println (str ""Mal ["" *host-language* ""]""))"), - Repl)); - Interactive_Loop (Repl); + -- Script? + if 0 < Argument_Count then + Eval_P (Reader.Read_Str ("(load-file """ & Argument (1) & """)"), Repl); else - Discard (Eval (Read ("(load-file """ & Argument (1) & """)"), Repl)); + Eval_P (Reader.Read_Str + ("(println (str ""Mal ["" *host-language* ""]""))"), Repl); + loop + begin + Rep (Repl); + exception + when Readline.End_Of_File => + exit; + when Reader.Empty_Source => + null; + when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + when Core.Exception_Throwed => + Ada.Text_IO.Put ("User exception: "); + Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str + (Core.Last_Exception)); + -- Other exceptions are unexpected. + end; + end loop; + Ada.Text_IO.New_Line; end if; end StepA_Mal; diff --git a/ada2/types-atoms.adb b/ada2/types-atoms.adb index 6f0afb76ed..952504a1e5 100644 --- a/ada2/types-atoms.adb +++ b/ada2/types-atoms.adb @@ -1,5 +1,6 @@ with Ada.Unchecked_Deallocation; +with Printer; with Types.Mal; package body Types.Atoms is @@ -21,11 +22,10 @@ package body Types.Atoms is function Atom (Args : in Mal.T_Array) return Mal.T is (if Args'Length /= 1 then raise Argument_Error with "atom: expects 1 argument" - else - (Kind => Kind_Atom, - Atom => (Ada.Finalization.Controlled with - Ref => new Rec'(Data => Args (Args'First), - Refs => 1)))); + else + (Kind_Atom, (Ada.Finalization.Controlled with new Rec' + (Refs => 1, + Data => Args (Args'First))))); function Deref (Args : in Mal.T_Array) return Mal.T is (if Args'Length /= 1 then @@ -33,7 +33,10 @@ package body Types.Atoms is elsif Args (Args'First).Kind /= Kind_Atom then raise Argument_Error with "deref: expects an atom" else - (Args (Args'First).Atom.Ref.all.Data)); + Args (Args'First).Atom.Ref.all.Data); + + function Deref (Item : in Ptr) return Mal.T + is (Item.Ref.all.Data); procedure Finalize (Object : in out Ptr) is begin @@ -53,10 +56,37 @@ package body Types.Atoms is raise Argument_Error with "reset: expects 2 arguments"; elsif Args (Args'First).Kind /= Kind_Atom then raise Argument_Error with "reset: first argument must be an atom"; - else - Args (Args'First).Atom.Ref.all.Data := Args (Args'Last); - return Args (Args'Last); end if; + Args (Args'First).Atom.Ref.all.Data := Args (Args'Last); + return Args (Args'Last); end Reset; + function Swap (Args : in Mal.T_Array) return Mal.T is + begin + if Args'Length < 2 then + raise Argument_Error with "swap!: expects at least 2 arguments"; + elsif Args (Args'First).Kind /= Kind_Atom then + raise Argument_Error with "swap!: first argument must be an atom"; + end if; + declare + use type Mal.T_Array; + X : Mal.T renames Args (Args'First).Atom.Ref.all.Data; + F : Mal.T renames Args (Args'First + 1); + A : constant Mal.T_Array := X & Args (Args'First + 2 .. Args'Last); + begin + case F.Kind is + when Kind_Builtin => + X := F.Builtin.all (A); + when Kind_Builtin_With_Meta => + X := F.Builtin_With_Meta.Builtin.all (A); + when Kind_Function => + X := F.Fn.Apply (A); + when others => + raise Argument_Error + with "swap!: cannot call " & Printer.Img (F); + end case; + return X; + end; + end Swap; + end Types.Atoms; diff --git a/ada2/types-atoms.ads b/ada2/types-atoms.ads index 2ed928abc0..19ef44b372 100644 --- a/ada2/types-atoms.ads +++ b/ada2/types-atoms.ads @@ -5,20 +5,15 @@ limited with Types.Mal; package Types.Atoms is type Ptr is private; - -- A wrapper for a pointer counting references. - - -- The default value is invalid, new variables must be assigned - -- immediately (a hidden discriminant would prevent this type to - -- become a field inside Types.Mal.T, so we check this with a - -- private invariant a fallback, an invariant in the private part - -- checks that any created object is affected before use. - - -- Assignment give another reference to the same storage. -- Built-in functions. function Atom (Args : in Mal.T_Array) return Mal.T; function Deref (Args : in Mal.T_Array) return Mal.T; function Reset (Args : in Mal.T_Array) return Mal.T; + function Swap (Args : in Mal.T_Array) return Mal.T; + + -- Helper for print. + function Deref (Item : in Ptr) return Mal.T with Inline; private diff --git a/ada2/types-builtins.adb b/ada2/types-builtins.adb index 7f1dd79db0..450574795e 100644 --- a/ada2/types-builtins.adb +++ b/ada2/types-builtins.adb @@ -5,24 +5,24 @@ with Types.Mal; package body Types.Builtins is type Rec is limited record - Data : Ptr; - Refs : Natural; - Meta : Mal.T; + Builtin : Mal.Builtin_Ptr; + Refs : Natural; + Meta : Mal.T; end record; procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); ---------------------------------------------------------------------- - procedure Adjust (Object : in out Ptr_With_Meta) is + procedure Adjust (Object : in out Ptr) is begin Object.Ref.all.Refs := Object.Ref.all.Refs + 1; end Adjust; - function Data (Item : in Ptr_With_Meta) return Ptr - is (Item.Ref.all.Data); + function Builtin (Item : in Ptr) return Mal.Builtin_Ptr + is (Item.Ref.all.Builtin); - procedure Finalize (Object : in out Ptr_With_Meta) is + procedure Finalize (Object : in out Ptr) is begin if Object.Ref /= null and then 0 < Object.Ref.all.Refs then Object.Ref.all.Refs := Object.Ref.all.Refs - 1; @@ -34,20 +34,20 @@ package body Types.Builtins is end if; end Finalize; - function Meta (Item : in Ptr_With_Meta) return Mal.T + function Meta (Item : in Ptr) return Mal.T is (Item.Ref.all.Meta); - function With_Meta (Data : in Ptr; + function With_Meta (Builtin : in Mal.Builtin_Ptr; Metadata : in Mal.T) return Mal.T is (Kind_Builtin_With_Meta, (Ada.Finalization.Controlled with new Rec' - (Data => Data, - Meta => Metadata, - Refs => 1))); + (Builtin => Builtin, + Meta => Metadata, + Refs => 1))); - function With_Meta (Data : in Ptr_With_Meta; + function With_Meta (Item : in Ptr; Metadata : in Mal.T) return Mal.T -- Do not try to reuse the memory. We can hope that this kind of -- nonsense will be rare. - is (With_Meta (Data.Data, Metadata)); + is (With_Meta (Item.Ref.all.Builtin, Metadata)); end Types.Builtins; diff --git a/ada2/types-builtins.ads b/ada2/types-builtins.ads index 7ad35db427..2bd05be061 100644 --- a/ada2/types-builtins.ads +++ b/ada2/types-builtins.ads @@ -4,43 +4,30 @@ limited with Types.Mal; package Types.Builtins is - type Ptr is access function (Args : in Mal.T_Array) return Mal.T; - -- This access type is efficient and sufficient for most purposes, - -- as counting references is a waste of time for native functions, - -- which are often used as atomic elements. The controlled type - -- below is only useful when one has the silly idea to add - -- metadata to a built-in. + -- Types.Mal.Builtin_Ptr is efficient and sufficient for most + -- purposes, as counting references is a waste of time for native + -- functions. The controlled type below is only useful when one + -- has the silly idea to add metadata to a built-in. - type Ptr_With_Meta is tagged private; - -- A wrapper for a pointer counting references. + type Ptr is tagged private; - -- The default value is invalid, new variables must be assigned - -- immediately (a hidden discriminant would prevent this type to - -- become a field inside Types.Mal.T, so we check this with a - -- private invariant a fallback, an invariant in the private part - -- checks that any created object is affected before use. - - -- Assignment give another reference to the same storage. - - function With_Meta (Data : in Ptr; + function With_Meta (Builtin : in Mal.Builtin_Ptr; Metadata : in Mal.T) return Mal.T with Inline; - function With_Meta (Data : in Ptr_With_Meta; + function With_Meta (Item : in Ptr; Metadata : in Mal.T) return Mal.T with Inline; - function Meta (Item : in Ptr_With_Meta) return Mal.T with Inline; - function Data (Item : in Ptr_With_Meta) return Ptr with Inline; + function Meta (Item : in Ptr) return Mal.T with Inline; + function Builtin (Item : in Ptr) return Mal.Builtin_Ptr with Inline; private - -- See README for the implementation of reference counting. - type Rec; type Acc is access Rec; - type Ptr_With_Meta is new Ada.Finalization.Controlled with record + type Ptr is new Ada.Finalization.Controlled with record Ref : Acc := null; end record with Invariant => Ref /= null; - overriding procedure Adjust (Object : in out Ptr_With_Meta) with Inline; - overriding procedure Finalize (Object : in out Ptr_With_Meta) with Inline; - pragma Finalize_Storage_Only (Ptr_With_Meta); + overriding procedure Adjust (Object : in out Ptr) with Inline; + overriding procedure Finalize (Object : in out Ptr) with Inline; + pragma Finalize_Storage_Only (Ptr); end Types.Builtins; diff --git a/ada2/types-functions.adb b/ada2/types-functions.adb index b40914767c..2446849341 100644 --- a/ada2/types-functions.adb +++ b/ada2/types-functions.adb @@ -1,25 +1,22 @@ -with Ada.Strings.Unbounded; with Ada.Unchecked_Deallocation; -with Environments; -with Printer; +with Envs; +with Eval_Cb; with Types.Lists; with Types.Mal; -with Types.Symbols.Names; +with Types.Symbols; package body Types.Functions is subtype AFC is Ada.Finalization.Controlled; - package ASU renames Ada.Strings.Unbounded; - use type Types.Symbols.Ptr; - - type Rec is limited record - Refs : Natural := 1; - Args : Lists.Ptr; - Expr : Mal.T; - Env : Environments.Closure_Ptr := Environments.Null_Closure; - Varargs : Boolean; - Meta : Mal.T := Mal.Nil; + use type Envs.Closure_Ptr; + + type Rec (Params_Last : Natural) is limited record + Ast : Mal.T; + Refs : Natural := 1; + Env : Envs.Closure_Ptr := Envs.Null_Closure; + Meta : Mal.T := Mal.Nil; + Params : Symbols.Symbol_Array (1 .. Params_Last); end record; procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); @@ -31,11 +28,24 @@ package body Types.Functions is Object.Ref.all.Refs := Object.Ref.all.Refs + 1; end Adjust; - function Closure (Item : in Ptr) return Environments.Closure_Ptr - is (Item.Ref.all.Env); + function Apply (Item : in Ptr; + Args : in Mal.T_Array) return Mal.T is + begin + pragma Assert (Item.Ref.all.Env /= Envs.Null_Closure); + return Eval_Cb.Cb.all (Ast => Item.Ref.all.Ast, + Env => Envs.Sub (Outer => Item.Ref.all.Env, + Binds => Item.Ref.all.Params, + Exprs => Args)); + end Apply; + + function Ast (Item : in Ptr) return Mal.T + is (Item.Ref.all.Ast); - function Expression (Item : in Ptr) return Mal.T - is (Item.Ref.all.Expr); + function Env (Item : in Ptr) return Envs.Closure_Ptr is + begin + pragma Assert (Item.Ref.all.Env /= Envs.Null_Closure); + return Item.Ref.all.Env; + end Env; procedure Finalize (Object : in out Ptr) is begin @@ -49,25 +59,30 @@ package body Types.Functions is end if; end Finalize; - function Formals (Item : in Ptr) return Lists.Ptr - is (Item.Ref.all.Args); + function Params (Item : in Ptr) return Symbols.Symbol_Array + is (Item.Ref.all.Params); - function Meta (Item : in Ptr) return Mal.T - is (Item.Ref.all.Meta); + function Meta (Item : in Ptr) return Mal.T is + begin + pragma Assert (Item.Ref.all.Env /= Envs.Null_Closure); + return Item.Ref.all.Meta; + end Meta; - function New_Function (Formals : in Lists.Ptr; - Expression : in Mal.T; - Environment : in Environments.Closure_Ptr) + function New_Function (Params : in Lists.Ptr; + Ast : in Mal.T; + Env : in Envs.Closure_Ptr) return Mal.T - is (Kind_Function, - (AFC with new Rec' - (Args => Formals, - Expr => Expression, - Env => Environment, - Varargs => 1 < Formals.Length - and then Formals.Element (Formals.Length - 1).Symbol - = Symbols.Names.Ampersand, - others => <>))); + is + Ref : constant Acc := new Rec'(Params_Last => Params.Length, + Ast => Ast, + Env => Env, + others => <>); + begin + for I in 1 .. Params.Length loop + Ref.all.Params (I) := Params.Element (I).Symbol; + end loop; + return (Kind_Function, (AFC with Ref)); + end New_Function; function New_Macro (Item : in Ptr) return Mal.T is Old : Rec renames Item.Ref.all; @@ -77,95 +92,37 @@ package body Types.Functions is if Old.Refs = 1 then Ref := Item.Ref; Old.Refs := 2; - Old.Env := Environments.Null_Closure; - -- Finalize the previous closure. + Old.Env := Envs.Null_Closure; + -- Finalize the environment, it will not be used anymore. Old.Meta := Mal.Nil; else - Ref := new Rec'(Args => Item.Ref.all.Args, - Expr => Item.Ref.all.Expr, - Varargs => Item.Ref.all.Varargs, - others => <>); + Ref := new Rec'(Params_Last => Old.Params_Last, + Params => Old.Params, + Ast => Old.Ast, + others => <>); end if; return (Kind_Macro, (AFC with Ref)); end New_Macro; - procedure Set_Binds (Item : in Ptr; - Env : in Environments.Ptr; - Args : in Mal.T_Array) - is - R : Rec renames Item.Ref.all; - begin - if R.Varargs then - if Args'Length < R.Args.Length - 2 then - raise Argument_Error with "expected " - & ASU.To_String (Printer.Pr_Str ((Kind_List, R.Args))) - & ", got" & Args'Length'Img; - end if; - for I in 1 .. R.Args.Length - 2 loop - Env.Set (R.Args.Element (I).Symbol, Args (Args'First + I - 1)); - end loop; - Env.Set (R.Args.Element (R.Args.Length).Symbol, - Lists.List (Args (Args'First + R.Args.Length - 2 .. Args'Last))); - else - if Args'Length /= R.Args.Length then - raise Argument_Error with "expected " - & ASU.To_String (Printer.Pr_Str ((Kind_List, R.Args))) - & ", got" & Args'Length'Img; - end if; - for I in 1 .. R.Args.Length loop - Env.Set (R.Args.Element (I).Symbol, Args (Args'First + I - 1)); - end loop; - end if; - end Set_Binds; - - procedure Set_Binds (Item : in Ptr; - Env : in Environments.Ptr; - Args : in Lists.Ptr) - is - R : Rec renames Item.Ref.all; - begin - if R.Varargs then - if Args.Length - 1 < R.Args.Length - 2 then - raise Argument_Error with "expected " - & ASU.To_String (Printer.Pr_Str ((Kind_List, R.Args))) - & ", got" & Natural'Image (Args.Length - 1); - end if; - for I in 1 .. R.Args.Length - 2 loop - Env.Set (R.Args.Element (I).Symbol, Args.Element (1 + I)); - end loop; - Env.Set (R.Args.Element (R.Args.Length).Symbol, - Lists.Slice (Args, R.Args.Length)); - else - if Args.Length - 1 /= R.Args.Length then - raise Argument_Error with "expected " - & ASU.To_String (Printer.Pr_Str ((Kind_List, R.Args))) - & ", got" & Natural'Image (Args.Length - 1); - end if; - for I in 1 .. R.Args.Length loop - Env.Set (R.Args.Element (I).Symbol, Args.Element (1 + I)); - end loop; - end if; - end Set_Binds; - - function With_Meta (Data : in Ptr; + function With_Meta (Item : in Ptr; Metadata : in Mal.T) return Mal.T is - Old : Rec renames Data.Ref.all; + Old : Rec renames Item.Ref.all; Ref : Acc; begin + pragma Assert (Old.Env /= Envs.Null_Closure); pragma Assert (0 < Old.Refs); if Old.Refs = 1 then - Ref := Data.Ref; + Ref := Item.Ref; Old.Refs := 2; Old.Meta := Metadata; else - Ref := new Rec'(Args => Data.Ref.all.Args, - Expr => Data.Ref.all.Expr, - Env => Data.Ref.all.Env, - Varargs => Data.Ref.all.Varargs, - Meta => Metadata, - others => <>); - + Ref := new Rec'(Params_Last => Old.Params_Last, + Params => Old.Params, + Ast => Old.Ast, + Env => Old.Env, + Meta => Metadata, + others => <>); end if; return (Kind_Function, (AFC with Ref)); end With_Meta; diff --git a/ada2/types-functions.ads b/ada2/types-functions.ads index ec996b38be..99a342edea 100644 --- a/ada2/types-functions.ads +++ b/ada2/types-functions.ads @@ -1,59 +1,41 @@ private with Ada.Finalization; -limited with Environments; +limited with Envs; limited with Types.Lists; limited with Types.Mal; +limited with Types.Symbols; package Types.Functions is type Ptr is tagged private; - -- A wrapper for a pointer counting references. + -- A pointer to an user-defined function or macro. - -- The default value is invalid, new variables must be assigned - -- immediately (a hidden discriminant would prevent this type to - -- become a field inside Types.Mal.T, so we check this with a - -- private invariant a fallback, an invariant in the private part - -- checks that any created object is affected before use. - - -- Assignment give another reference to the same storage. - - function New_Function (Formals : in Lists.Ptr; - Expression : in Mal.T; - Environment : in Environments.Closure_Ptr) - return Mal.T + function New_Function (Params : in Lists.Ptr; + Ast : in Mal.T; + Env : in Envs.Closure_Ptr) return Mal.T with Inline; - -- Equivalent to a sequence of Set with the formal parameters and - -- Args elements, except for the handling of "&". - -- May raise Argument_Count. - -- For functions. - procedure Set_Binds (Item : in Ptr; - Env : in Environments.Ptr; - Args : in Mal.T_Array); - function New_Macro (Item : in Ptr) return Mal.T with Inline; - -- Set_Binds for macros. - -- It skips the first element of Args. - procedure Set_Binds (Item : in Ptr; - Env : in Environments.Ptr; - Args : in Lists.Ptr); - -- Used when printing, or applying with specific requirements, - -- like allowing tail call optimization or macros. - function Formals (Item : in Ptr) return Lists.Ptr with Inline; - function Expression (Item : in Ptr) return Mal.T with Inline; - function Closure (Item : in Ptr) return Environments.Closure_Ptr - with Inline; + function Params (Item : in Ptr) return Symbols.Symbol_Array with Inline; + function Ast (Item : in Ptr) return Mal.T with Inline; + -- Useful to print. + + function Apply (Item : in Ptr; + Args : in Mal.T_Array) return Mal.T with Inline; + -- Fails for macros. + + function Env (Item : in Ptr) return Envs.Closure_Ptr with Inline; + -- Fails for macros. Required for TCO, instead of Apply. function Meta (Item : in Ptr) return Mal.T with Inline; - function With_Meta (Data : in Ptr; - Metadata : in Mal.T) - return Mal.T with Inline; + -- Fails for macros. + function With_Meta (Item : in Ptr; + Metadata : in Mal.T) return Mal.T with Inline; + -- Fails for macros. private - -- See README for the implementation of reference counting. - type Rec; type Acc is access Rec; type Ptr is new Ada.Finalization.Controlled with record diff --git a/ada2/types-lists.adb b/ada2/types-lists.adb index 7f25702915..37ce66df78 100644 --- a/ada2/types-lists.adb +++ b/ada2/types-lists.adb @@ -1,5 +1,6 @@ with Ada.Unchecked_Deallocation; +with Printer; with Types.Mal; package body Types.Lists is @@ -45,59 +46,58 @@ package body Types.Lists is if Arg.Kind not in Kind_List | Kind_Vector then raise Argument_Error with "concat: expects lists or vectors"; end if; - Sum := Sum + Arg.L.Ref.all.Last; + Sum := Sum + Arg.List.Ref.all.Last; end loop; Ref := new Rec (Sum); for Arg of reverse Args loop - Ref.all.Data (Sum - Arg.L.Ref.all.Last + 1 .. Sum) - := Arg.L.Ref.all.Data; - Sum := Sum - Arg.L.Ref.all.Last; + Ref.all.Data (Sum - Arg.List.Ref.all.Last + 1 .. Sum) + := Arg.List.Ref.all.Data; + Sum := Sum - Arg.List.Ref.all.Last; end loop; pragma Assert (Sum = 0); return (Kind_List, (AFC with Ref)); end Concat; function Conj (Args : in Mal.T_Array) return Mal.T is - Ref : Acc; begin if Args'Length = 0 then raise Argument_Error with "conj: expects at least 1 argument"; end if; - case Args (Args'First).Kind is - when Kind_List => - Ref := new Rec - (Args'Length - 1 + Args (Args'First).L.Ref.all.Last); - Ref.all.Data (Args'Length .. Ref.all.Last) - := Args (Args'First).L.Ref.all.Data; - for I in 1 .. Args'Length - 1 loop - Ref.all.Data (I) := Args (Args'Last - I + 1); - end loop; - return (Kind_List, (AFC with Ref)); - when Kind_Vector => - return (Kind_Vector, (AFC with new Rec' - (Last => Args'Length - 1 + Args (Args'First).L.Ref.all.Last, - Data => Args (Args'First).L.Ref.all.Data - & Args (Args'First + 1 .. Args'Last), - others => <>))); - when others => - raise Argument_Error with "conj: first arg must be list or vector"; - end case; + declare + A1 : Mal.T renames Args (Args'First); + Last : constant Natural := Args'Length - 1 + A1.List.Ref.all.Last; + Ref : constant Acc := new Rec (Last); + Data : Mal.T_Array renames Ref.all.Data; + begin + case A1.Kind is + when Kind_List => + Data (Args'Length .. Ref.all.Last) := A1.List.Ref.all.Data; + for I in 1 .. Args'Length - 1 loop + Data (I) := Args (Args'Last - I + 1); + end loop; + return (Kind_List, (AFC with Ref)); + when Kind_Vector => + Data := A1.List.Ref.all.Data + & Args (Args'First + 1 .. Args'Last); + return (Kind_Vector, (AFC with Ref)); + when others => + raise Argument_Error + with "conj: first argument must be a list or vector"; + end case; + end; end Conj; function Cons (Args : in Mal.T_Array) return Mal.T is begin if Args'Length /= 2 then raise Argument_Error with "cons: expects 2 arguments"; + elsif Args (Args'Last).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "cons: last arg must be a list or vector"; end if; - case Args (Args'Last).Kind is - when Kind_List | Kind_Vector => - return (Kind_List, (AFC with new Rec' - (Last => 1 + Args (Args'Last).L.Ref.all.Last, - Data => Args (Args'First) & Args (Args'Last).L.Ref.all.Data, - others => <>))); - when others => - raise Argument_Error with "cons: last arg must be list or vector"; - end case; + return (Kind_List, (AFC with new Rec' + (Last => 1 + Args (Args'Last).List.Ref.all.Last, + Data => Args (Args'First) & Args (Args'Last).List.Ref.all.Data, + others => <>))); end Cons; function Count (Args : in Mal.T_Array) return Mal.T @@ -108,7 +108,7 @@ package body Types.Lists is when Kind_Nil => (Kind_Number, 0), when Kind_List | Kind_Vector => - (Kind_Number, Args (Args'First).L.Ref.all.Last), + (Kind_Number, Args (Args'First).List.Ref.all.Last), when others => raise Argument_Error with "count: expects a list or vector")); @@ -136,10 +136,10 @@ package body Types.Lists is when Kind_Nil => Mal.Nil, when Kind_List | Kind_Vector => - (if Args (Args'First).L.Ref.all.Last = 0 then + (if Args (Args'First).List.Ref.all.Last = 0 then Mal.Nil else - Args (Args'First).L.Ref.all.Data (1)), + Args (Args'First).List.Ref.all.Data (1)), when others => raise Argument_Error with "first: expects a list or vector")); @@ -173,7 +173,7 @@ package body Types.Lists is else (case Args (Args'First).Kind is when Kind_List | Kind_Vector => - (Kind_Boolean, Args (Args'First).L.Ref.all.Last = 0), + (Kind_Boolean, Args (Args'First).List.Ref.all.Last = 0), when others => raise Argument_Error with "empty?: expects a list or vector")); @@ -185,6 +185,49 @@ package body Types.Lists is Last => Args'Length, others => <>))); + function Map (Args : in Mal.T_Array) return Mal.T is + begin + if Args'Length /= 2 then + raise Argument_Error with "map: expects 2 arguments"; + elsif Args (Args'Last).Kind not in Kind_List | Kind_Vector then + raise Argument_Error with "map: argument 2 must be a list or vector"; + end if; + declare + F : Mal.T renames Args (Args'First); + Old : Rec renames Args (Args'Last).List.Ref.all; + Ref : Acc; + begin + pragma Assert (0 < Old.Refs); + if Old.Refs = 1 then + Ref := Args (Args'Last).List.Ref; + Old.Refs := 2; + Old.Meta := Mal.Nil; + else + Ref := new Rec (Old.Last); + end if; + return R : constant Mal.T := (Kind_List, (AFC with Ref)) do + -- Now we can afford raising an exception. + case F.Kind is + when Kind_Builtin => + for I in Old.Data'Range loop + Ref.all.Data (I) := F.Builtin.all (Old.Data (I .. I)); + end loop; + when Kind_Builtin_With_Meta => + for I in Old.Data'Range loop + Ref.all.Data (I) + := F.Builtin_With_Meta.Builtin.all (Old.Data (I .. I)); + end loop; + when Kind_Function => + for I in Old.Data'Range loop + Ref.all.Data (I) := F.Fn.Apply (Old.Data (I .. I)); + end loop; + when others => + raise Argument_Error with "map: cannot call " & Printer.Img (F); + end case; + end return; + end; + end Map; + function Meta (Item : in Ptr) return Mal.T is (Item.Ref.all.Meta); @@ -196,34 +239,43 @@ package body Types.Lists is when Kind_List | Kind_Vector => (if Args (Args'First + 1).Kind /= Kind_Number then raise Argument_Error with "nth: last arg must be a number" - elsif 1 + Args (Args'Last).Ada_Number - in Args (Args'First).L.Ref.all.Data'Range + elsif 1 + Args (Args'Last).Number + in Args (Args'First).List.Ref.all.Data'Range then - Args (Args'First).L.Ref.all.Data - (1 + Args (Args'Last).Ada_Number) + Args (Args'First).List.Ref.all.Data + (1 + Args (Args'Last).Number) else raise Argument_Error with "nth: index out of bounds"), when others => raise Argument_Error with "nth: expects a list or vector")); - function Rest (Args : in Mal.T_Array) return Mal.T - is (Kind_List, (AFC with - (if Args'Length /= 1 then - raise Argument_Error with "rest: expects 1 argument" - else - (case Args (Args'First).Kind is - when Kind_Nil => - new Rec (0), - when Kind_List | Kind_Vector => - (if Args (Args'First).L.Ref.all.Last = 0 then - new Rec (0) - else - new Rec'(Last => Args (Args'First).L.Ref.all.Last - 1, - Data => Args (Args'First).L.Ref.all.Data - (2 .. Args (Args'First).L.Ref.all.Last), - others => <>)), - when others => - raise Argument_Error with "rest: expects a list or vector")))); + function Rest (Args : in Mal.T_Array) return Mal.T is + begin + if Args'Length /= 1 then + raise Argument_Error with "rest: expects 1 argument"; + end if; + declare + A1 : Mal.T renames Args (Args'First); + Ref : Acc; + begin + case A1.Kind is + when Kind_Nil => + Ref := new Rec (0); + when Kind_List | Kind_Vector => + if A1.List.Ref.all.Last = 0 then + Ref := new Rec (0); + else + Ref := new Rec' + (Last => A1.List.Ref.all.Last - 1, + Data => A1.List.Ref.all.Data (2 .. A1.List.Ref.all.Last), + others => <>); + end if; + when others => + raise Argument_Error with "rest: expects a list or vector"; + end case; + return (Kind_List, (AFC with Ref)); + end; + end Rest; function Slice (Item : in Ptr; Start : in Positive) diff --git a/ada2/types-lists.ads b/ada2/types-lists.ads index 1e9906315d..c33d3ca47f 100644 --- a/ada2/types-lists.ads +++ b/ada2/types-lists.ads @@ -5,15 +5,6 @@ limited with Types.Mal; package Types.Lists is type Ptr is tagged private; - -- A wrapper for a pointer counting references. - - -- The default value is invalid, new variables must be assigned - -- immediately (a hidden discriminant would prevent this type to - -- become a field inside Types.Mal.T, so we check this with a - -- private invariant a fallback, an invariant in the private part - -- checks that any created object is affected before use. - - -- Assignment give another reference to the same storage. -- Built-in functions. function Concat (Args : in Mal.T_Array) return Mal.T; @@ -23,6 +14,7 @@ package Types.Lists is function First (Args : in Mal.T_Array) return Mal.T; function Is_Empty (Args : in Mal.T_Array) return Mal.T; function List (Args : in Mal.T_Array) return Mal.T; + function Map (Args : in Mal.T_Array) return Mal.T; function Nth (Args : in Mal.T_Array) return Mal.T; function Rest (Args : in Mal.T_Array) return Mal.T; function Vector (Args : in Mal.T_Array) return Mal.T; diff --git a/ada2/types-mal.adb b/ada2/types-mal.adb index 7e613d4172..cc8914ced5 100644 --- a/ada2/types-mal.adb +++ b/ada2/types-mal.adb @@ -15,14 +15,14 @@ package body Types.Mal is Right.Kind = Kind_Boolean and then Left.Ada_Boolean = Right.Ada_Boolean, when Kind_Number => - Right.Kind = Kind_Number and then Left.Ada_Number = Right.Ada_Number, + Right.Kind = Kind_Number and then Left.Number = Right.Number, when Kind_Symbol => Right.Kind = Kind_Symbol and then Left.Symbol = Right.Symbol, -- Here is the part that differs from the predefined equality. when Kind_Keyword | Kind_String => Right.Kind = Left.Kind and then Left.S = Right.S, when Kind_List | Kind_Vector => - Right.Kind in Kind_List | Kind_Vector and then Left.L = Right.L, + Right.Kind in Kind_List | Kind_Vector and then Left.List = Right.List, when Kind_Map => Right.Kind = Kind_Map and then Left.Map = Right.Map, when others => diff --git a/ada2/types-mal.ads b/ada2/types-mal.ads index d7aec36428..ca35f67a52 100644 --- a/ada2/types-mal.ads +++ b/ada2/types-mal.ads @@ -46,6 +46,10 @@ package Types.Mal is -- language, and require deep changes (the discriminant can be -- changed for an in out or access parameter). + type T_Array; + type T; + type Builtin_Ptr is access function (Args : in T_Array) return T; + type T (Kind : Kind_Type := Kind_Nil) is record case Kind is when Kind_Nil => @@ -53,7 +57,7 @@ package Types.Mal is when Kind_Boolean => Ada_Boolean : Boolean; when Kind_Number => - Ada_Number : Integer; + Number : Integer; when Kind_Atom => Atom : Atoms.Ptr; when Kind_Keyword | Kind_String => @@ -61,15 +65,15 @@ package Types.Mal is when Kind_Symbol => Symbol : Symbols.Ptr; when Kind_List | Kind_Vector => - L : Lists.Ptr; + List : Lists.Ptr; when Kind_Map => Map : Maps.Ptr; when Kind_Builtin => - Builtin : Builtins.Ptr; + Builtin : Builtin_Ptr; when Kind_Builtin_With_Meta => - Builtin_With_Meta : Builtins.Ptr_With_Meta; + Builtin_With_Meta : Builtins.Ptr; when Kind_Function | Kind_Macro => - Function_Value : Functions.Ptr; + Fn : Functions.Ptr; end case; end record; diff --git a/ada2/types-maps.ads b/ada2/types-maps.ads index 3fd07b459e..de9cc9a4c8 100644 --- a/ada2/types-maps.ads +++ b/ada2/types-maps.ads @@ -5,15 +5,6 @@ limited with Types.Mal; package Types.Maps is type Ptr is tagged private; - -- A wrapper for a pointer counting references. - - -- The default value is invalid, new variables must be assigned - -- immediately (a hidden discriminant would prevent this type to - -- become a field inside Types.Mal.T, so we check this with a - -- private invariant a fallback, an invariant in the private part - -- checks that any created object is affected before use. - - -- Assignment give another reference to the same storage. -- Built-in functions. function Assoc (Args : in Mal.T_Array) return Mal.T; @@ -52,8 +43,6 @@ package Types.Maps is private - -- See README for the implementation of reference counting. - type Rec; type Acc is access Rec; type Ptr is new Ada.Finalization.Controlled with record diff --git a/ada2/types-symbols.adb b/ada2/types-symbols.adb index 91c013b249..7aa5f530d9 100644 --- a/ada2/types-symbols.adb +++ b/ada2/types-symbols.adb @@ -87,4 +87,26 @@ package body Types.Symbols is function To_String (Item : in Ptr) return String is (Item.Ref.all.Data); + function To_String (Item : in Symbol_Array) return String is + I : Natural := Item'Length + 1; + begin + for S of Item loop + I := I + S.Ref.all.Last; + end loop; + return R : String (1 .. I) do + R (1) := '('; + I := 2; + for S of Item loop + if 2 < I then + R (I) := ' '; + I := I + 1; + end if; + R (I .. I + S.Ref.all.Last - 1) := S.Ref.all.Data; + I := I + S.Ref.all.Last; + end loop; + pragma Assert (I = R'Last); + R (R'Last) := ')'; + end return; + end To_String; + end Types.Symbols; diff --git a/ada2/types-symbols.ads b/ada2/types-symbols.ads index b1368f1479..fea08366b2 100644 --- a/ada2/types-symbols.ads +++ b/ada2/types-symbols.ads @@ -4,18 +4,8 @@ private with Ada.Finalization; package Types.Symbols with Preelaborate is type Ptr is tagged private; - -- A wrapper for a pointer counting references. - - -- The default value is invalid, new variables must be assigned - -- immediately (a hidden discriminant would prevent this type to - -- become a field inside Types.Mal.T, so we check this with a - -- private invariant a fallback, an invariant in the private part - -- checks that any created object is affected before use. - - -- Assignment give another reference to the same storage. function Constructor (Source : in String) return Ptr with Inline; - -- The only way to assign a valid value. function To_String (Item : in Ptr) return String with Inline; @@ -25,6 +15,13 @@ package Types.Symbols with Preelaborate is -- Equality compares the contents. + type Symbol_Array is array (Positive range <>) of Symbols.Ptr; + + function To_String (Item : in Symbols.Symbol_Array) return String; + -- Returns something like "(a b)". Convenient for error + -- reporting, but redundant with Printer (where it is more + -- efficient to concatenate directly to an unbounded buffer). + private -- Only one instance is allocated with a given content. This @@ -43,8 +40,6 @@ private -- probably because it significantly increases the size of -- Mal_Type. - -- See README for the implementation of reference counting. - type Rec; type Acc is access Rec; type Ptr is new Ada.Finalization.Controlled with record From a5d17b88f4b7d037b1c87e51beae60467a4257cb Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Sun, 10 Mar 2019 00:14:59 +0100 Subject: [PATCH 0798/2308] ada2: fix formatting and typos, add comments, check more errors. Only attempt to spare a recursion after step5, this is more consistent with the process. --- ada2/core.adb | 7 +++--- ada2/envs.adb | 10 +++++--- ada2/envs.ads | 3 +++ ada2/step2_eval.adb | 22 +++++++++++------ ada2/step3_env.adb | 42 +++++++++++++++++-------------- ada2/step4_if_fn_do.adb | 42 +++++++++++++++++-------------- ada2/step5_tco.adb | 50 ++++++++++++++++++++++++------------- ada2/step6_file.adb | 50 ++++++++++++++++++++++++------------- ada2/step7_quote.adb | 53 ++++++++++++++++++++++++++------------- ada2/step8_macros.adb | 53 ++++++++++++++++++++++++++------------- ada2/step9_try.adb | 55 +++++++++++++++++++++++++++-------------- ada2/stepa_mal.adb | 55 +++++++++++++++++++++++++++-------------- ada2/types-mal.adb | 2 +- ada2/types-mal.ads | 2 +- ada2/types-maps.adb | 5 ++-- 15 files changed, 292 insertions(+), 159 deletions(-) diff --git a/ada2/core.adb b/ada2/core.adb index 03e2ccc150..c4db4ef609 100644 --- a/ada2/core.adb +++ b/ada2/core.adb @@ -149,8 +149,9 @@ package body Core is function Is_False (Args : in Mal.T_Array) return Mal.T is (if Args'Length /= 1 then raise Argument_Error with "false?: expects 1 argument" - else (Kind_Boolean, Args (Args'First).Kind = Kind_Boolean - and then not Args (Args'First).Ada_Boolean)); + else + (Kind_Boolean, Args (Args'First).Kind = Kind_Boolean + and then not Args (Args'First).Ada_Boolean)); function Is_Function (Args : in Mal.T_Array) return Mal.T is (if Args'Length /= 1 then @@ -405,7 +406,7 @@ package body Core is function Symbol (Args : in Mal.T_Array) return Mal.T is (if Args'Length /= 1 then - raise Argument_Error with "symbol?: expects 1 argument" + raise Argument_Error with "symbol: expects 1 argument" else (Kind_Symbol, Symbols.Constructor (ASU.To_String (Args (Args'First).S)))); diff --git a/ada2/envs.adb b/ada2/envs.adb index ae197eea1d..cd585aec5a 100644 --- a/ada2/envs.adb +++ b/ada2/envs.adb @@ -1,6 +1,8 @@ with Ada.Containers.Hashed_Maps; +-- with Ada.Text_IO.Unbounded_IO; with Ada.Unchecked_Deallocation; +-- with Printer; with Types.Symbols.Names; package body Envs is @@ -92,7 +94,7 @@ package body Envs is return (Ada.Finalization.Limited_Controlled with Env.Index); end Copy_Pointer; - -- procedure Dump_Stack (Long : Boolean := False) is + -- procedure Dump_Stack (Long : in Boolean := False) is -- use Ada.Text_IO; -- use Ada.Text_IO.Unbounded_IO; -- begin @@ -336,9 +338,9 @@ package body Envs is else Exprs'Length /= Binds'Length) then - raise Argument_Error with "user function expected " + raise Argument_Error with "function expected " & Symbols.To_String (Binds) & ", got" - & Integer'Image (Exprs'Length) & " actual parameters"; + & Integer'Image (Exprs'Length) & " actual parameter(s)"; end if; for I in 0 .. Binds'Length - (if Varargs then 3 else 1) loop M.Include (Binds (Binds'First + I), Exprs (Exprs'First + I)); @@ -364,7 +366,7 @@ package body Envs is then raise Argument_Error with "macro expected " & Symbols.To_String (Binds) & ", got" - & Integer'Image (Exprs.Length - 1) & "actual parameters"; + & Integer'Image (Exprs.Length - 1) & " actual parameter(s)"; end if; for I in 0 .. Binds'Length - (if Varargs then 3 else 1) loop M.Include (Binds (Binds'First + I), Exprs.Element (2 + I)); diff --git a/ada2/envs.ads b/ada2/envs.ads index b7082a6c17..b5b931b8fe 100644 --- a/ada2/envs.ads +++ b/ada2/envs.ads @@ -89,6 +89,9 @@ package Envs with Elaborate_Body is -- * The Exprs argument is a list. -- * Its first element is skipped. + -- procedure Dump_Stack (Long : in Boolean := False); + -- For debugging. + private -- There must be a reference level so that functions may keep diff --git a/ada2/step2_eval.adb b/ada2/step2_eval.adb index 7a5f9f72d3..101734b46c 100644 --- a/ada2/step2_eval.adb +++ b/ada2/step2_eval.adb @@ -47,7 +47,7 @@ procedure Step2_Eval is begin -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); - -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Print (Ast); case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String | Kind_Keyword | Kind_Macro | Kind_Function @@ -70,12 +70,19 @@ procedure Step2_Eval is when Kind_Vector => return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.List.Length = 0 then - return Ast; - end if; - First := Eval (Ast.List.Element (1), Env); - -- Apply phase. - case First.Kind is + null; + end case; + + -- Ast is a list. + if Ast.List.Length = 0 then + return Ast; + end if; + First := Eval (Ast.List.Element (1), Env); + + -- Apply phase. + -- Ast is a non-empty list, + -- First is its non-special evaluated first element. + case First.Kind is when Kind_Builtin => declare Args : Mal.T_Array (2 .. Ast.List.Length); @@ -87,7 +94,6 @@ procedure Step2_Eval is end; when others => raise Argument_Error with "cannot call " & Printer.Img (First); - end case; end case; end Eval; diff --git a/ada2/step3_env.adb b/ada2/step3_env.adb index 79290d58da..9a4294f31c 100644 --- a/ada2/step3_env.adb +++ b/ada2/step3_env.adb @@ -40,7 +40,7 @@ procedure Step3_Env is begin -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); - -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Print (Ast); -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String @@ -54,16 +54,20 @@ procedure Step3_Env is when Kind_Vector => return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.List.Length = 0 then - return Ast; - end if; - First := Ast.List.Element (1); - -- Special forms - if First.Kind /= Kind_Symbol then - -- Evaluate First, in the less frequent case where it is - -- not a symbol. - First := Eval (First, Env); - elsif First.Symbol = Symbols.Names.Def then + null; + end case; + + -- Ast is a list. + if Ast.List.Length = 0 then + return Ast; + end if; + First := Ast.List.Element (1); + + -- Special forms + -- Ast is a non-empty list, First is its first element. + case First.Kind is + when Kind_Symbol => + if First.Symbol = Symbols.Names.Def then if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; elsif Ast.List.Element (2).Kind /= Kind_Symbol then @@ -97,13 +101,16 @@ procedure Step3_Env is return Eval (Ast.List.Element (3), New_Env); end; else - -- Equivalent to First := Eval (First, Env), except that - -- we already know enough to spare a recursive call in - -- this frequent case. - First := Env.Get (First.Symbol); + First := Eval (First, Env); end if; - -- Apply phase. - case First.Kind is + when others => + First := Eval (First, Env); + end case; + + -- Apply phase. + -- Ast is a non-empty list, + -- First is its non-special evaluated first element. + case First.Kind is when Kind_Builtin => declare Args : Mal.T_Array (2 .. Ast.List.Length); @@ -115,7 +122,6 @@ procedure Step3_Env is end; when others => raise Argument_Error with "cannot call " & Printer.Img (First); - end case; end case; end Eval; diff --git a/ada2/step4_if_fn_do.adb b/ada2/step4_if_fn_do.adb index d96d28c7bf..342f9dd737 100644 --- a/ada2/step4_if_fn_do.adb +++ b/ada2/step4_if_fn_do.adb @@ -44,7 +44,7 @@ procedure Step4_If_Fn_Do is begin -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); - -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Print (Ast); -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String @@ -58,16 +58,20 @@ procedure Step4_If_Fn_Do is when Kind_Vector => return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.List.Length = 0 then - return Ast; - end if; - First := Ast.List.Element (1); - -- Special forms - if First.Kind /= Kind_Symbol then - -- Evaluate First, in the less frequent case where it is - -- not a symbol. - First := Eval (First, Env); - elsif First.Symbol = Symbols.Names.Def then + null; + end case; + + -- Ast is a list. + if Ast.List.Length = 0 then + return Ast; + end if; + First := Ast.List.Element (1); + + -- Special forms + -- Ast is a non-empty list, First is its first element. + case First.Kind is + when Kind_Symbol => + if First.Symbol = Symbols.Names.Def then if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; elsif Ast.List.Element (2).Kind /= Kind_Symbol then @@ -141,13 +145,16 @@ procedure Step4_If_Fn_Do is return Eval (Ast.List.Element (3), New_Env); end; else - -- Equivalent to First := Eval (First, Env), except that - -- we already know enough to spare a recursive call in - -- this frequent case. - First := Env.Get (First.Symbol); + First := Eval (First, Env); end if; - -- Apply phase. - case First.Kind is + when others => + First := Eval (First, Env); + end case; + + -- Apply phase. + -- Ast is a non-empty list, + -- First is its non-special evaluated first element. + case First.Kind is when Kind_Builtin => declare Args : Mal.T_Array (2 .. Ast.List.Length); @@ -168,7 +175,6 @@ procedure Step4_If_Fn_Do is end; when others => raise Argument_Error with "cannot call " & Printer.Img (First); - end case; end case; end Eval; diff --git a/ada2/step5_tco.adb b/ada2/step5_tco.adb index b6079454a6..2e109e393a 100644 --- a/ada2/step5_tco.adb +++ b/ada2/step5_tco.adb @@ -49,7 +49,7 @@ procedure Step5_Tco is <> -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); - -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Print (Ast); -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String @@ -63,16 +63,20 @@ procedure Step5_Tco is when Kind_Vector => return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.List.Length = 0 then - return Ast; - end if; - First := Ast.List.Element (1); - -- Special forms - if First.Kind /= Kind_Symbol then - -- Evaluate First, in the less frequent case where it is - -- not a symbol. - First := Eval (First, Env); - elsif First.Symbol = Symbols.Names.Def then + null; + end case; + + -- Ast is a list. + if Ast.List.Length = 0 then + return Ast; + end if; + First := Ast.List.Element (1); + + -- Special forms + -- Ast is a non-empty list, First is its first element. + case First.Kind is + when Kind_Symbol => + if First.Symbol = Symbols.Names.Def then if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; elsif Ast.List.Element (2).Kind /= Kind_Symbol then @@ -148,13 +152,26 @@ procedure Step5_Tco is goto Restart; end; else - -- Equivalent to First := Eval (First, Env), except that - -- we already know enough to spare a recursive call in - -- this frequent case. + -- Equivalent to First := Eval (First, Env) + -- except that we already know enough to spare a recursive call. First := Env.Get (First.Symbol); end if; - -- Apply phase. - case First.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function + | Kind_Builtin_With_Meta | Kind_Builtin => + -- Equivalent to First := Eval (First, Env) + -- except that we already know enough to spare a recursive call. + null; + when Kind_List | Kind_Vector | Kind_Map => + -- Lists are definitely worth a recursion, and the two other + -- cases should be rare (they will report an error later). + First := Eval (First, Env); + end case; + + -- Apply phase. + -- Ast is a non-empty list, + -- First is its non-special evaluated first element. + case First.Kind is when Kind_Builtin => declare Args : Mal.T_Array (2 .. Ast.List.Length); @@ -179,7 +196,6 @@ procedure Step5_Tco is end; when others => raise Argument_Error with "cannot call " & Printer.Img (First); - end case; end case; end Eval; diff --git a/ada2/step6_file.adb b/ada2/step6_file.adb index 57988f7df6..42294855b9 100644 --- a/ada2/step6_file.adb +++ b/ada2/step6_file.adb @@ -52,7 +52,7 @@ procedure Step6_File is <> -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); - -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Print (Ast); -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String @@ -66,16 +66,20 @@ procedure Step6_File is when Kind_Vector => return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.List.Length = 0 then - return Ast; - end if; - First := Ast.List.Element (1); - -- Special forms - if First.Kind /= Kind_Symbol then - -- Evaluate First, in the less frequent case where it is - -- not a symbol. - First := Eval (First, Env); - elsif First.Symbol = Symbols.Names.Def then + null; + end case; + + -- Ast is a list. + if Ast.List.Length = 0 then + return Ast; + end if; + First := Ast.List.Element (1); + + -- Special forms + -- Ast is a non-empty list, First is its first element. + case First.Kind is + when Kind_Symbol => + if First.Symbol = Symbols.Names.Def then if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; elsif Ast.List.Element (2).Kind /= Kind_Symbol then @@ -151,13 +155,26 @@ procedure Step6_File is goto Restart; end; else - -- Equivalent to First := Eval (First, Env), except that - -- we already know enough to spare a recursive call in - -- this frequent case. + -- Equivalent to First := Eval (First, Env) + -- except that we already know enough to spare a recursive call. First := Env.Get (First.Symbol); end if; - -- Apply phase. - case First.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function + | Kind_Builtin_With_Meta | Kind_Builtin => + -- Equivalent to First := Eval (First, Env) + -- except that we already know enough to spare a recursive call. + null; + when Kind_List | Kind_Vector | Kind_Map => + -- Lists are definitely worth a recursion, and the two other + -- cases should be rare (they will report an error later). + First := Eval (First, Env); + end case; + + -- Apply phase. + -- Ast is a non-empty list, + -- First is its non-special evaluated first element. + case First.Kind is when Kind_Builtin => declare Args : Mal.T_Array (2 .. Ast.List.Length); @@ -182,7 +199,6 @@ procedure Step6_File is end; when others => raise Argument_Error with "cannot call " & Printer.Img (First); - end case; end case; end Eval; diff --git a/ada2/step7_quote.adb b/ada2/step7_quote.adb index 9424e615da..2d517a65e4 100644 --- a/ada2/step7_quote.adb +++ b/ada2/step7_quote.adb @@ -59,7 +59,7 @@ procedure Step7_Quote is <> -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); - -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Print (Ast); -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String @@ -73,16 +73,20 @@ procedure Step7_Quote is when Kind_Vector => return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.List.Length = 0 then - return Ast; - end if; - First := Ast.List.Element (1); - -- Special forms - if First.Kind /= Kind_Symbol then - -- Evaluate First, in the less frequent case where it is - -- not a symbol. - First := Eval (First, Env); - elsif First.Symbol = Symbols.Names.Def then + null; + end case; + + -- Ast is a list. + if Ast.List.Length = 0 then + return Ast; + end if; + First := Ast.List.Element (1); + + -- Special forms + -- Ast is a non-empty list, First is its first element. + case First.Kind is + when Kind_Symbol => + if First.Symbol = Symbols.Names.Def then if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; elsif Ast.List.Element (2).Kind /= Kind_Symbol then @@ -168,13 +172,26 @@ procedure Step7_Quote is end if; return Ast.List.Element (2); else - -- Equivalent to First := Eval (First, Env), except that - -- we already know enough to spare a recursive call in - -- this frequent case. + -- Equivalent to First := Eval (First, Env) + -- except that we already know enough to spare a recursive call. First := Env.Get (First.Symbol); end if; - -- Apply phase. - case First.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function + | Kind_Builtin_With_Meta | Kind_Builtin => + -- Equivalent to First := Eval (First, Env) + -- except that we already know enough to spare a recursive call. + null; + when Kind_List | Kind_Vector | Kind_Map => + -- Lists are definitely worth a recursion, and the two other + -- cases should be rare (they will report an error later). + First := Eval (First, Env); + end case; + + -- Apply phase. + -- Ast is a non-empty list, + -- First is its non-special evaluated first element. + case First.Kind is when Kind_Builtin => declare Args : Mal.T_Array (2 .. Ast.List.Length); @@ -199,7 +216,6 @@ procedure Step7_Quote is end; when others => raise Argument_Error with "cannot call " & Printer.Img (First); - end case; end case; end Eval; @@ -262,6 +278,9 @@ procedure Step7_Quote is and then Ast.List.Element (1).Kind = Kind_Symbol and then Ast.List.Element (1).Symbol = Symbols.Names.Unquote then + if 2 < Ast.List.Length then + raise Argument_Error with "unquote: expects 1 argument"; + end if; return Eval (Ast.List.Element (2), Env); else return Quasiquote_List (Ast.List); diff --git a/ada2/step8_macros.adb b/ada2/step8_macros.adb index 5240b589f2..ac46883274 100644 --- a/ada2/step8_macros.adb +++ b/ada2/step8_macros.adb @@ -60,7 +60,7 @@ procedure Step8_Macros is <> -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); - -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Print (Ast); -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String @@ -74,16 +74,20 @@ procedure Step8_Macros is when Kind_Vector => return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.List.Length = 0 then - return Ast; - end if; - First := Ast.List.Element (1); - -- Special forms - if First.Kind /= Kind_Symbol then - -- Evaluate First, in the less frequent case where it is - -- not a symbol. - First := Eval (First, Env); - elsif First.Symbol = Symbols.Names.Def then + null; + end case; + + -- Ast is a list. + if Ast.List.Length = 0 then + return Ast; + end if; + First := Ast.List.Element (1); + + -- Special forms + -- Ast is a non-empty list, First is its first element. + case First.Kind is + when Kind_Symbol => + if First.Symbol = Symbols.Names.Def then if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; elsif Ast.List.Element (2).Kind /= Kind_Symbol then @@ -192,13 +196,26 @@ procedure Step8_Macros is end if; return Ast.List.Element (2); else - -- Equivalent to First := Eval (First, Env), except that - -- we already know enough to spare a recursive call in - -- this frequent case. + -- Equivalent to First := Eval (First, Env) + -- except that we already know enough to spare a recursive call. First := Env.Get (First.Symbol); end if; - -- Apply phase. - case First.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function + | Kind_Builtin_With_Meta | Kind_Builtin => + -- Equivalent to First := Eval (First, Env) + -- except that we already know enough to spare a recursive call. + null; + when Kind_List | Kind_Vector | Kind_Map => + -- Lists are definitely worth a recursion, and the two other + -- cases should be rare (they will report an error later). + First := Eval (First, Env); + end case; + + -- Apply phase. + -- Ast is a non-empty list, + -- First is its non-special evaluated first element. + case First.Kind is when Kind_Builtin => declare Args : Mal.T_Array (2 .. Ast.List.Length); @@ -233,7 +250,6 @@ procedure Step8_Macros is end if; when others => raise Argument_Error with "cannot call " & Printer.Img (First); - end case; end case; end Eval; @@ -296,6 +312,9 @@ procedure Step8_Macros is and then Ast.List.Element (1).Kind = Kind_Symbol and then Ast.List.Element (1).Symbol = Symbols.Names.Unquote then + if 2 < Ast.List.Length then + raise Argument_Error with "unquote: expects 1 argument"; + end if; return Eval (Ast.List.Element (2), Env); else return Quasiquote_List (Ast.List); diff --git a/ada2/step9_try.adb b/ada2/step9_try.adb index 8e90bd663d..e7684390da 100644 --- a/ada2/step9_try.adb +++ b/ada2/step9_try.adb @@ -60,7 +60,7 @@ procedure Step9_Try is <> -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); - -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Print (Ast); -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String @@ -74,16 +74,20 @@ procedure Step9_Try is when Kind_Vector => return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.List.Length = 0 then - return Ast; - end if; - First := Ast.List.Element (1); - -- Special forms - if First.Kind /= Kind_Symbol then - -- Evaluate First, in the less frequent case where it is - -- not a symbol. - First := Eval (First, Env); - elsif First.Symbol = Symbols.Names.Def then + null; + end case; + + -- Ast is a list. + if Ast.List.Length = 0 then + return Ast; + end if; + First := Ast.List.Element (1); + + -- Special forms + -- Ast is a non-empty list, First is its first element. + case First.Kind is + when Kind_Symbol => + if First.Symbol = Symbols.Names.Def then if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; elsif Ast.List.Element (2).Kind /= Kind_Symbol then @@ -208,7 +212,7 @@ procedure Step9_Try is elsif A3.Element (1).Kind /= Kind_Symbol or else A3.Element (1).Symbol /= Symbols.Names.Catch then - raise Argument_Error with "try*: arg 2 must be a catch*"; + raise Argument_Error with "try*: arg 2 must be 'catch*'"; elsif A3.Element (2).Kind /= Kind_Symbol then raise Argument_Error with "catch*: expects a symbol"; end if; @@ -232,13 +236,26 @@ procedure Step9_Try is end; end; else - -- Equivalent to First := Eval (First, Env), except that - -- we already know enough to spare a recursive call in - -- this frequent case. + -- Equivalent to First := Eval (First, Env) + -- except that we already know enough to spare a recursive call. First := Env.Get (First.Symbol); end if; - -- Apply phase. - case First.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function + | Kind_Builtin_With_Meta | Kind_Builtin => + -- Equivalent to First := Eval (First, Env) + -- except that we already know enough to spare a recursive call. + null; + when Kind_List | Kind_Vector | Kind_Map => + -- Lists are definitely worth a recursion, and the two other + -- cases should be rare (they will report an error later). + First := Eval (First, Env); + end case; + + -- Apply phase. + -- Ast is a non-empty list, + -- First is its non-special evaluated first element. + case First.Kind is when Kind_Builtin => declare Args : Mal.T_Array (2 .. Ast.List.Length); @@ -273,7 +290,6 @@ procedure Step9_Try is end if; when others => raise Argument_Error with "cannot call " & Printer.Img (First); - end case; end case; end Eval; @@ -336,6 +352,9 @@ procedure Step9_Try is and then Ast.List.Element (1).Kind = Kind_Symbol and then Ast.List.Element (1).Symbol = Symbols.Names.Unquote then + if 2 < Ast.List.Length then + raise Argument_Error with "unquote: expects 1 argument"; + end if; return Eval (Ast.List.Element (2), Env); else return Quasiquote_List (Ast.List); diff --git a/ada2/stepa_mal.adb b/ada2/stepa_mal.adb index 355b4be173..75977ddf14 100644 --- a/ada2/stepa_mal.adb +++ b/ada2/stepa_mal.adb @@ -60,7 +60,7 @@ procedure StepA_Mal is <> -- Ada.Text_IO.New_Line; -- Ada.Text_IO.Put ("EVAL: "); - -- Ada.Text_IO.Unbounded_IO.Put_Line (Print (Ast)); + -- Print (Ast); -- Envs.Dump_Stack; case Ast.Kind is when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String @@ -74,16 +74,20 @@ procedure StepA_Mal is when Kind_Vector => return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); when Kind_List => - if Ast.List.Length = 0 then - return Ast; - end if; - First := Ast.List.Element (1); - -- Special forms - if First.Kind /= Kind_Symbol then - -- Evaluate First, in the less frequent case where it is - -- not a symbol. - First := Eval (First, Env); - elsif First.Symbol = Symbols.Names.Def then + null; + end case; + + -- Ast is a list. + if Ast.List.Length = 0 then + return Ast; + end if; + First := Ast.List.Element (1); + + -- Special forms + -- Ast is a non-empty list, First is its first element. + case First.Kind is + when Kind_Symbol => + if First.Symbol = Symbols.Names.Def then if Ast.List.Length /= 3 then raise Argument_Error with "def!: expects 2 arguments"; elsif Ast.List.Element (2).Kind /= Kind_Symbol then @@ -208,7 +212,7 @@ procedure StepA_Mal is elsif A3.Element (1).Kind /= Kind_Symbol or else A3.Element (1).Symbol /= Symbols.Names.Catch then - raise Argument_Error with "try*: arg 2 must be a catch*"; + raise Argument_Error with "try*: arg 2 must be 'catch*'"; elsif A3.Element (2).Kind /= Kind_Symbol then raise Argument_Error with "catch*: expects a symbol"; end if; @@ -232,13 +236,26 @@ procedure StepA_Mal is end; end; else - -- Equivalent to First := Eval (First, Env), except that - -- we already know enough to spare a recursive call in - -- this frequent case. + -- Equivalent to First := Eval (First, Env) + -- except that we already know enough to spare a recursive call. First := Env.Get (First.Symbol); end if; - -- Apply phase. - case First.Kind is + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String + | Kind_Keyword | Kind_Macro | Kind_Function + | Kind_Builtin_With_Meta | Kind_Builtin => + -- Equivalent to First := Eval (First, Env) + -- except that we already know enough to spare a recursive call. + null; + when Kind_List | Kind_Vector | Kind_Map => + -- Lists are definitely worth a recursion, and the two other + -- cases should be rare (they will report an error later). + First := Eval (First, Env); + end case; + + -- Apply phase. + -- Ast is a non-empty list, + -- First is its non-special evaluated first element. + case First.Kind is when Kind_Builtin => declare Args : Mal.T_Array (2 .. Ast.List.Length); @@ -282,7 +299,6 @@ procedure StepA_Mal is end if; when others => raise Argument_Error with "cannot call " & Printer.Img (First); - end case; end case; end Eval; @@ -345,6 +361,9 @@ procedure StepA_Mal is and then Ast.List.Element (1).Kind = Kind_Symbol and then Ast.List.Element (1).Symbol = Symbols.Names.Unquote then + if 2 < Ast.List.Length then + raise Argument_Error with "unquote: expects 1 argument"; + end if; return Eval (Ast.List.Element (2), Env); else return Quasiquote_List (Ast.List); diff --git a/ada2/types-mal.adb b/ada2/types-mal.adb index cc8914ced5..93c41409dd 100644 --- a/ada2/types-mal.adb +++ b/ada2/types-mal.adb @@ -13,7 +13,7 @@ package body Types.Mal is Right.Kind = Kind_Nil, when Kind_Boolean => Right.Kind = Kind_Boolean - and then Left.Ada_Boolean = Right.Ada_Boolean, + and then Left.Ada_Boolean = Right.Ada_Boolean, when Kind_Number => Right.Kind = Kind_Number and then Left.Number = Right.Number, when Kind_Symbol => diff --git a/ada2/types-mal.ads b/ada2/types-mal.ads index ca35f67a52..955d7a44e3 100644 --- a/ada2/types-mal.ads +++ b/ada2/types-mal.ads @@ -46,8 +46,8 @@ package Types.Mal is -- language, and require deep changes (the discriminant can be -- changed for an in out or access parameter). - type T_Array; type T; + type T_Array; type Builtin_Ptr is access function (Args : in T_Array) return T; type T (Kind : Kind_Type := Kind_Nil) is record diff --git a/ada2/types-maps.adb b/ada2/types-maps.adb index b0c333ff4a..6e70d5929a 100644 --- a/ada2/types-maps.adb +++ b/ada2/types-maps.adb @@ -75,8 +75,9 @@ package body Types.Maps is raise Argument_Error with "contains: expects 2 arguments" elsif Args (Args'First).Kind /= Kind_Map then raise Argument_Error with "contains: first arguement must be a map" - else (Kind_Boolean, - Args (Args'First).Map.Ref.all.Data.Contains (Args (Args'Last)))); + else + (Kind_Boolean, + Args (Args'First).Map.Ref.all.Data.Contains (Args (Args'Last)))); function Dissoc (Args : in Mal.T_Array) return Mal.T is begin From 2ddc3cd1a83bf40890a8878c8d1b5092abfd6e36 Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Sun, 10 Mar 2019 00:14:59 +0100 Subject: [PATCH 0799/2308] ada2: replace a tail call with a jump in macroexpansion. --- ada2/envs.adb | 9 +++++++++ ada2/envs.ads | 8 ++++++++ ada2/step8_macros.adb | 16 +++++++++++----- ada2/step9_try.adb | 16 +++++++++++----- ada2/stepa_mal.adb | 16 +++++++++++----- 5 files changed, 50 insertions(+), 15 deletions(-) diff --git a/ada2/envs.adb b/ada2/envs.adb index cd585aec5a..cab3d403fc 100644 --- a/ada2/envs.adb +++ b/ada2/envs.adb @@ -318,6 +318,15 @@ package body Envs is Set_Binds (Stack (Top).Data, Binds, Exprs); end Replace_With_Sub; + procedure Replace_With_Sub_Macro (Env : in out Ptr; + Binds : in Symbols.Symbol_Array; + Exprs : in Lists.Ptr) + is + begin + Replace_With_Sub (Env); + Set_Binds_Macro (Stack (Top).Data, Binds, Exprs); + end Replace_With_Sub_Macro; + procedure Set (Env : in Ptr; Key : in Symbols.Ptr; New_Element : in Mal.T) is diff --git a/ada2/envs.ads b/ada2/envs.ads index b5b931b8fe..fdf73944df 100644 --- a/ada2/envs.ads +++ b/ada2/envs.ads @@ -46,6 +46,14 @@ package Envs with Elaborate_Body is -- except that such an assignment is forbidden for performance -- reasons. + procedure Replace_With_Sub_Macro (Env : in out Ptr; + Binds : in Types.Symbols.Symbol_Array; + Exprs : in Types.Lists.Ptr); + -- Equivalent to Env := Sub (Outer => Env, Binds, Expr), except + -- that such an assignment is forbidden for performance reasons. + -- This version is intended for macros: the Exprs argument is a + -- list, and its first element is skipped. + procedure Set (Env : in Ptr; Key : in Types.Symbols.Ptr; New_Element : in Types.Mal.T) diff --git a/ada2/step8_macros.adb b/ada2/step8_macros.adb index ac46883274..a1b00ed02a 100644 --- a/ada2/step8_macros.adb +++ b/ada2/step8_macros.adb @@ -239,13 +239,19 @@ procedure Step8_Macros is goto Restart; end; when Kind_Macro => - Ast := Eval (Ast0 => First.Fn.Ast, - Env0 => Envs.Sub (Outer => Env, - Binds => First.Fn.Params, - Exprs => Ast.List)); if Macroexpanding then - return Ast; + -- Evaluate the macro with tail call optimization. + Env.Replace_With_Sub_Macro (Binds => First.Fn.Params, + Exprs => Ast.List); + Ast := First.Fn.Ast; + goto Restart; else + -- Evaluate the macro normally. + Ast := Eval (Ast0 => First.Fn.Ast, + Env0 => Envs.Sub (Outer => Env, + Binds => First.Fn.Params, + Exprs => Ast.List)); + -- Then evaluate the result with TCO. goto Restart; end if; when others => diff --git a/ada2/step9_try.adb b/ada2/step9_try.adb index e7684390da..8291002fff 100644 --- a/ada2/step9_try.adb +++ b/ada2/step9_try.adb @@ -279,13 +279,19 @@ procedure Step9_Try is goto Restart; end; when Kind_Macro => - Ast := Eval (Ast0 => First.Fn.Ast, - Env0 => Envs.Sub (Outer => Env, - Binds => First.Fn.Params, - Exprs => Ast.List)); if Macroexpanding then - return Ast; + -- Evaluate the macro with tail call optimization. + Env.Replace_With_Sub_Macro (Binds => First.Fn.Params, + Exprs => Ast.List); + Ast := First.Fn.Ast; + goto Restart; else + -- Evaluate the macro normally. + Ast := Eval (Ast0 => First.Fn.Ast, + Env0 => Envs.Sub (Outer => Env, + Binds => First.Fn.Params, + Exprs => Ast.List)); + -- Then evaluate the result with TCO. goto Restart; end if; when others => diff --git a/ada2/stepa_mal.adb b/ada2/stepa_mal.adb index 75977ddf14..d3b44b0d95 100644 --- a/ada2/stepa_mal.adb +++ b/ada2/stepa_mal.adb @@ -288,13 +288,19 @@ procedure StepA_Mal is goto Restart; end; when Kind_Macro => - Ast := Eval (Ast0 => First.Fn.Ast, - Env0 => Envs.Sub (Outer => Env, - Binds => First.Fn.Params, - Exprs => Ast.List)); if Macroexpanding then - return Ast; + -- Evaluate the macro with tail call optimization. + Env.Replace_With_Sub_Macro (Binds => First.Fn.Params, + Exprs => Ast.List); + Ast := First.Fn.Ast; + goto Restart; else + -- Evaluate the macro normally. + Ast := Eval (Ast0 => First.Fn.Ast, + Env0 => Envs.Sub (Outer => Env, + Binds => First.Fn.Params, + Exprs => Ast.List)); + -- Then evaluate the result with TCO. goto Restart; end if; when others => From e2b412f9285f0b26586af9de87fa5a0096117993 Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Sun, 10 Mar 2019 00:31:51 +0100 Subject: [PATCH 0800/2308] ada.2: rewrite the reader, for readability and efficiency. Use character sets and other language tools to delimit tokens. When possible, only test each input character once. Give verbose descriptions to subprograms. Simplify with one global variable instead of two. Improve reporting of ignored trailing items in input. --- ada2/reader.adb | 383 ++++++++++++++++++++++++++---------------------- 1 file changed, 204 insertions(+), 179 deletions(-) diff --git a/ada2/reader.adb b/ada2/reader.adb index 33d0c3c4a0..b2ec6b365b 100644 --- a/ada2/reader.adb +++ b/ada2/reader.adb @@ -1,4 +1,6 @@ +with Ada.Characters.Handling; with Ada.Characters.Latin_1; +with Ada.Strings.Maps.Constants; with Ada.Strings.Unbounded; with Types.Lists; @@ -7,224 +9,247 @@ with Types.Symbols.Names; package body Reader is - function Read_Str (Source : in String) return Types.Mal.T is + use Types; + use type Ada.Strings.Maps.Character_Set; + + Ignored_Set : constant Ada.Strings.Maps.Character_Set + := Ada.Strings.Maps.Constants.Control_Set + or Ada.Strings.Maps.To_Set (" ,;"); - use Types; + Symbol_Set : constant Ada.Strings.Maps.Character_Set + := not (Ignored_Set or Ada.Strings.Maps.To_Set ("""'()@[]^`{}~")); - First : Positive; - Last : Natural := Source'First - 1; + function Read_Str (Source : in String) return Types.Mal.T is + + I : Positive := Source'First; + -- Index of the currently considered character. function Read_Form return Mal.T; -- The recursive part of Read_Str. - procedure Find_Next_Token; - -- Search next token from index Last + 1. - -- If none is found, set First to Source'Last + 1. - -- Find_Next_Token is normally invoked right before Read_Form, - -- allowing the caller to check whether First <= Source'Last. - - ---------------------------------------------------------------------- + -- Helpers for Read_Form: - procedure Find_Next_Token is - use Ada.Characters.Latin_1; - begin - First := Last + 1; - while First <= Source'Last loop + procedure Skip_Ignored with Inline; + -- Check if the current character is ignorable or a comment. + -- Increment I until it exceeds Source'Last or designates + -- an interesting character. - case Source (First) is + procedure Skip_Digits with Inline; + -- Increment I at least once, until I exceeds Source'Last or + -- designates something else than a decimal digit. - when ' ' | ',' | HT | VT | LF | CR => - First := First + 1; + procedure Skip_Symbol with Inline; + -- Check if the current character is allowed in a symbol name. + -- Increment I uuntil it exceeds Source'Last or stops + -- designating an allowed character. - when '[' | ']' | '{' | '}' | '(' | ')' | ''' | '`' | '^' | '@' - => - Last := First; - exit; + -- Read_Atom has been merged into the same case/switch + -- statement, for clarity and efficiency. + function Read_List (Ending : in Character) return Mal.T_Array + with Inline; + function Read_Quote (Symbol : in Symbols.Ptr) return Mal.T with Inline; + function Read_String return Mal.T with Inline; + function Read_With_Meta return Mal.T with Inline; - when '~' => - if First + 1 <= Source'Last - and then Source (First + 1) = '@' - then - Last := First + 1; - else - Last := First; - end if; - exit; - - when '"' => - Last := First + 1; - loop - if Source'Last < Last then - raise Reader_Error with "unbalanced '""'"; - end if; - exit when Source (Last) = '"'; - if Source (Last) = '\' then - Last := Last + 1; - end if; - Last := Last + 1; - end loop; - exit; - - when ';' => - First := First + 1; - while First <= Source'Last loop - if Source (First) = LF then - First := First + 1; - exit; - end if; - First := First + 1; - end loop; - - when others => - Last := First; - while Last + 1 <= Source'Last - and then Source (Last + 1) not in - ' ' | ',' | HT | VT | LF | CR | '[' | ']' | '{' | '}' - | '(' | ')' | ''' | '`' | '^' | '@' | '~' | '"' | ';' - loop - Last := Last + 1; - end loop; - exit; + ---------------------------------------------------------------------- - end case; + function Read_List (Ending : in Character) return Mal.T_Array is + -- Big arrays on the stack are faster than repeated + -- dynamic reallocations. + Opening : constant Character := Source (I); + Buffer : Mal.T_Array (I + 1 .. Source'Last); + B_Last : Natural := I; + begin + I := I + 1; -- Skip (, [ or {. + loop + Skip_Ignored; + if Source'Last < I then + raise Reader_Error with "unbalanced '" & Opening & "'"; + end if; + exit when Source (I) = Ending; + B_Last := B_Last + 1; + Buffer (B_Last) := Read_Form; end loop; - end Find_Next_Token; + I := I + 1; -- Skip ), ] or }. + return Buffer (Buffer'First .. B_Last); + end Read_List; - function Read_Form return Mal.T is - - -- Read_Atom has been merged into the same case/switch - -- statement, for clarity and efficiency. - function Read_List (Ending : in Character) return Mal.T_Array - with Inline; - function Read_Quote (Symbol : in Symbols.Ptr) return Mal.T - with Inline; - - function Read_List (Ending : in Character) return Mal.T_Array is - -- Using big arrays on the stack is faster than doing - -- repeated dynamic reallocations. - Buffer : Mal.T_Array (First + 1 .. Source'Last); - B_Last : Natural := Buffer'First - 1; - begin - loop - Find_Next_Token; - if Source'Last < First then - raise Reader_Error with "unbalanced '" & Ending & "'"; - end if; - exit when Source (First) = Ending; - B_Last := B_Last + 1; - Buffer (B_Last) := Read_Form; - end loop; - return Buffer (Buffer'First .. B_Last); - end Read_List; - - function Read_Quote (Symbol : in Symbols.Ptr) return Mal.T is - begin - Find_Next_Token; - if Source'Last < First then - raise Reader_Error with "Unfinished '" & Symbol.To_String & "'"; - end if; - return Lists.List (Mal.T_Array'((Kind_Symbol, Symbol), Read_Form)); - end Read_Quote; + function Read_Quote (Symbol : in Symbols.Ptr) return Mal.T is + begin + I := I + 1; -- Skip the initial ' or similar. + Skip_Ignored; + if Source'Last < I then + raise Reader_Error with "Incomplete '" & Symbol.To_String & "'"; + end if; + return Lists.List (Mal.T_Array'((Kind_Symbol, Symbol), Read_Form)); + end Read_Quote; - use Ada.Strings.Unbounded; - begin -- Read_Form. - case Source (First) is - when '(' => - return Lists.List (Read_List (')')); - when '[' => - return Lists.Vector (Read_List (']')); - when '{' => - return Maps.Hash_Map (Read_List ('}')); + function Read_Form return Mal.T is + F : Positive; + begin + case Source (I) is + when ')' | ']' | '}' => + raise Reader_Error with "unbalanced '" & Source (I) & "'"; when '"' => - declare - Buffer : Unbounded_String; - I : Positive := First + 1; - begin - while I < Last loop - if Source (I) /= '\' or else I + 1 = Last then - Append (Buffer, Source (I)); - else - case Source (I + 1) is - when '\' | '"' => - I := I + 1; - Append (Buffer, Source (I)); - when 'n' => - I := I + 1; - Append (Buffer, Ada.Characters.Latin_1.LF); - when others => - Append (Buffer, Source (I)); - end case; - end if; - I := I + 1; - end loop; - return (Kind_String, Buffer); - end; + return Read_String; when ':' => - return (Kind_Keyword, - To_Unbounded_String (Source (First + 1 .. Last))); + I := I + 1; + F := I; + Skip_Symbol; + return (Kind_Keyword, Ada.Strings.Unbounded.To_Unbounded_String + (Source (F .. I - 1))); when '-' => - if First < Last - and then (for all C of Source (First + 1 .. Last) => - C in '0' .. '9') - then - return (Kind_Number, Integer'Value (Source (First .. Last))); - else - return (Kind_Symbol, - Symbols.Constructor (Source (First .. Last))); + F := I; + Skip_Digits; + if F + 1 < I then + return (Kind_Number, Integer'Value (Source (F .. I - 1))); + end if; + Skip_Symbol; + return (Kind_Symbol, Symbols.Constructor (Source (F .. I - 1))); + when '~' => + if I < Source'Last and then Source (I + 1) = '@' then + I := I + 1; + return Read_Quote (Symbols.Names.Splice_Unquote); end if; + return Read_Quote (Symbols.Names.Unquote); when '0' .. '9' => - return (Kind_Number, Integer'Value (Source (First .. Last))); + F := I; + Skip_Digits; + return (Kind_Number, Integer'Value (Source (F .. I - 1))); when ''' => return Read_Quote (Symbols.Names.Quote); when '`' => return Read_Quote (Symbols.Names.Quasiquote); when '@' => return Read_Quote (Symbols.Names.Deref); - when '~' => - if First = Last then - return Read_Quote (Symbols.Names.Unquote); - else - return Read_Quote (Symbols.Names.Splice_Unquote); - end if; when '^' => - declare - Args : Mal.T_Array (1 .. 3); - begin - Args (1) := (Kind_Symbol, Symbols.Names.With_Meta); - Find_Next_Token; - if Source'Last < First then - raise Reader_Error with "Unfinished 'with-meta'"; - end if; - Args (3) := Read_Form; - Find_Next_Token; - if Source'Last < First then - raise Reader_Error with "Unfinished 'with-meta'"; - end if; - Args (2) := Read_Form; - return Lists.List (Args); - end; + return Read_With_Meta; + when '(' => + return Lists.List (Read_List (')')); + when '[' => + return Lists.Vector (Read_List (']')); + when '{' => + return Maps.Hash_Map (Read_List ('}')); when others => - if Source (First .. Last) = "false" then + F := I; + Skip_Symbol; + if Source (F .. I - 1) = "false" then return (Kind_Boolean, False); - elsif Source (First .. Last) = "nil" then + elsif Source (F .. I - 1) = "nil" then return Mal.Nil; - elsif Source (First .. Last) = "true" then + elsif Source (F .. I - 1) = "true" then return (Kind_Boolean, True); - else - return (Kind_Symbol, - Symbols.Constructor (Source (First .. Last))); end if; + return (Kind_Symbol, Symbols.Constructor (Source (F .. I - 1))); end case; end Read_Form; + function Read_String return Mal.T is + use Ada.Strings.Unbounded; + S : Unbounded_String; + begin + loop + I := I + 1; + if Source'Last < I then + raise Reader_Error with "unbalanced '""'"; + end if; + case Source (I) is + when '"' => + exit; + when '\' => + I := I + 1; + if Source'Last < I then + raise Reader_Error with "unbalanced '""'"; + end if; + case Source (I) is + when '\' | '"' => + Append (S, Source (I)); + when 'n' => + Append (S, Ada.Characters.Latin_1.LF); + when others => + Append (S, Source (I - 1 .. I)); + end case; + when others => + Append (S, Source (I)); + end case; + end loop; + I := I + 1; -- Skip closing double quote. + return (Kind_String, S); + end Read_String; + + function Read_With_Meta return Mal.T is + Args : Mal.T_Array (1 .. 3); + begin + Args (1) := (Kind_Symbol, Symbols.Names.With_Meta); + + I := I + 1; -- Skip the initial ^. + + Skip_Ignored; + if Source'Last < I then + raise Reader_Error with "incomplete 'with-meta'"; + end if; + Args (3) := Read_Form; + + Skip_Ignored; + if Source'Last < I then + raise Reader_Error with "incomplete 'with-meta'"; + end if; + Args (2) := Read_Form; + + return Lists.List (Args); + end Read_With_Meta; + + procedure Skip_Digits is + use Ada.Characters.Handling; + begin + loop + I := I + 1; + exit when Source'Last < I or else not Is_Digit (Source (I)); + end loop; + end Skip_Digits; + + procedure Skip_Ignored is + use Ada.Characters.Handling; + use Ada.Strings.Maps; + begin + Ignored : while I <= Source'Last + and then Is_In (Source (I), Ignored_Set) + loop + if Source (I) = ';' then + Comment : loop + I := I + 1; + exit Ignored when Source'Last < I; + exit Comment when Is_Line_Terminator (Source (I)); + end loop Comment; + end if; + I := I + 1; + end loop Ignored; + end Skip_Ignored; + + procedure Skip_Symbol is + use Ada.Strings.Maps; + begin + while I <= Source'Last and then Is_In (Source (I), Symbol_Set) loop + I := I + 1; + end loop; + end Skip_Symbol; + ---------------------------------------------------------------------- - begin - Find_Next_Token; - if Source'Last < First then + Result : Mal.T; + begin -- Read_Str + Skip_Ignored; + if Source'Last < I then raise Empty_Source with "attempting to read an empty line"; end if; - return Read_Form; + Result := Read_Form; + Skip_Ignored; + if I <= Source'Last then + raise Reader_Error + with "unexpected characters '" & Source (I .. Source'Last) + & "' after '" & Source (Source'First .. I - 1) & '''; + end if; + return Result; end Read_Str; end Reader; From 1dc35df4871ed7200c239d492771fffd420907f5 Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Sun, 10 Mar 2019 01:07:53 +0100 Subject: [PATCH 0801/2308] ada2: give an explicit prefix to fields in record invariants. This hopefully fixes the build with gnat-4.9. --- ada2/envs.ads | 2 +- ada2/types-atoms.ads | 2 +- ada2/types-builtins.ads | 2 +- ada2/types-functions.ads | 2 +- ada2/types-lists.ads | 2 +- ada2/types-maps.ads | 2 +- ada2/types-symbols.ads | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ada2/envs.ads b/ada2/envs.ads index fdf73944df..00c58102a7 100644 --- a/ada2/envs.ads +++ b/ada2/envs.ads @@ -126,7 +126,7 @@ private type Ptr is new Ada.Finalization.Limited_Controlled with record Index : Stack_Index := 0; end record - with Invariant => Index in 1 .. Top; + with Invariant => Ptr.Index in 1 .. Top; overriding procedure Finalize (Object : in out Ptr) with Inline; pragma Finalize_Storage_Only (Ptr); diff --git a/ada2/types-atoms.ads b/ada2/types-atoms.ads index 19ef44b372..057ef036cb 100644 --- a/ada2/types-atoms.ads +++ b/ada2/types-atoms.ads @@ -22,7 +22,7 @@ private type Ptr is new Ada.Finalization.Controlled with record Ref : Acc := null; end record - with Invariant => Ref /= null; + with Invariant => Ptr.Ref /= null; overriding procedure Adjust (Object : in out Ptr) with Inline; overriding procedure Finalize (Object : in out Ptr) with Inline; pragma Finalize_Storage_Only (Ptr); diff --git a/ada2/types-builtins.ads b/ada2/types-builtins.ads index 2bd05be061..e5d99fbdc7 100644 --- a/ada2/types-builtins.ads +++ b/ada2/types-builtins.ads @@ -25,7 +25,7 @@ private type Ptr is new Ada.Finalization.Controlled with record Ref : Acc := null; end record - with Invariant => Ref /= null; + with Invariant => Ptr.Ref /= null; overriding procedure Adjust (Object : in out Ptr) with Inline; overriding procedure Finalize (Object : in out Ptr) with Inline; pragma Finalize_Storage_Only (Ptr); diff --git a/ada2/types-functions.ads b/ada2/types-functions.ads index 99a342edea..8326245dcc 100644 --- a/ada2/types-functions.ads +++ b/ada2/types-functions.ads @@ -41,7 +41,7 @@ private type Ptr is new Ada.Finalization.Controlled with record Ref : Acc := null; end record - with Invariant => Ref /= null; + with Invariant => Ptr.Ref /= null; overriding procedure Adjust (Object : in out Ptr) with Inline; overriding procedure Finalize (Object : in out Ptr) with Inline; pragma Finalize_Storage_Only (Ptr); diff --git a/ada2/types-lists.ads b/ada2/types-lists.ads index c33d3ca47f..7eefc0eb3e 100644 --- a/ada2/types-lists.ads +++ b/ada2/types-lists.ads @@ -66,7 +66,7 @@ private type Ptr is new Ada.Finalization.Controlled with record Ref : Acc := null; end record - with Invariant => Ref /= null; + with Invariant => Ptr.Ref /= null; overriding procedure Adjust (Object : in out Ptr) with Inline; overriding procedure Finalize (Object : in out Ptr) with Inline; overriding function "=" (Left, Right : in Ptr) return Boolean; diff --git a/ada2/types-maps.ads b/ada2/types-maps.ads index de9cc9a4c8..aa67b5f479 100644 --- a/ada2/types-maps.ads +++ b/ada2/types-maps.ads @@ -48,7 +48,7 @@ private type Ptr is new Ada.Finalization.Controlled with record Ref : Acc := null; end record - with Invariant => Ref /= null; + with Invariant => Ptr.Ref /= null; overriding procedure Adjust (Object : in out Ptr) with Inline; overriding procedure Finalize (Object : in out Ptr) with Inline; overriding function "=" (Left, Right : in Ptr) return Boolean with Inline; diff --git a/ada2/types-symbols.ads b/ada2/types-symbols.ads index fea08366b2..91be3a00b5 100644 --- a/ada2/types-symbols.ads +++ b/ada2/types-symbols.ads @@ -45,7 +45,7 @@ private type Ptr is new Ada.Finalization.Controlled with record Ref : Acc := null; end record - with Invariant => Ref /= null; + with Invariant => Ptr.Ref /= null; overriding procedure Adjust (Object : in out Ptr) with Inline; overriding procedure Finalize (Object : in out Ptr) with Inline; -- Predefined equality is fine. From e3b1335caa155273d1dde9ec8e6530ca1748ae79 Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Sun, 10 Mar 2019 01:13:57 +0100 Subject: [PATCH 0802/2308] ada2: rename to ada.2 --- Makefile | 4 ++-- {ada2 => ada.2}/Makefile | 0 {ada2 => ada.2}/README | 0 {ada2 => ada.2}/core.adb | 0 {ada2 => ada.2}/core.ads | 0 {ada2 => ada.2}/envs.adb | 0 {ada2 => ada.2}/envs.ads | 0 {ada2 => ada.2}/eval_cb.ads | 0 {ada2 => ada.2}/printer.adb | 0 {ada2 => ada.2}/printer.ads | 0 {ada2 => ada.2}/reader.adb | 0 {ada2 => ada.2}/reader.ads | 0 {ada2 => ada.2}/readline.adb | 0 {ada2 => ada.2}/readline.ads | 0 {ada2 => ada.2}/run | 0 {ada2 => ada.2}/step0_repl.adb | 0 {ada2 => ada.2}/step1_read_print.adb | 0 {ada2 => ada.2}/step2_eval.adb | 0 {ada2 => ada.2}/step3_env.adb | 0 {ada2 => ada.2}/step4_if_fn_do.adb | 0 {ada2 => ada.2}/step5_tco.adb | 0 {ada2 => ada.2}/step6_file.adb | 0 {ada2 => ada.2}/step7_quote.adb | 0 {ada2 => ada.2}/step8_macros.adb | 0 {ada2 => ada.2}/step9_try.adb | 0 {ada2 => ada.2}/stepa_mal.adb | 0 {ada2 => ada.2}/types-atoms.adb | 0 {ada2 => ada.2}/types-atoms.ads | 0 {ada2 => ada.2}/types-builtins.adb | 0 {ada2 => ada.2}/types-builtins.ads | 0 {ada2 => ada.2}/types-functions.adb | 0 {ada2 => ada.2}/types-functions.ads | 0 {ada2 => ada.2}/types-lists.adb | 0 {ada2 => ada.2}/types-lists.ads | 0 {ada2 => ada.2}/types-mal.adb | 0 {ada2 => ada.2}/types-mal.ads | 0 {ada2 => ada.2}/types-maps.adb | 0 {ada2 => ada.2}/types-maps.ads | 0 {ada2 => ada.2}/types-symbols-names.ads | 0 {ada2 => ada.2}/types-symbols.adb | 0 {ada2 => ada.2}/types-symbols.ads | 0 {ada2 => ada.2}/types.ads | 0 42 files changed, 2 insertions(+), 2 deletions(-) rename {ada2 => ada.2}/Makefile (100%) rename {ada2 => ada.2}/README (100%) rename {ada2 => ada.2}/core.adb (100%) rename {ada2 => ada.2}/core.ads (100%) rename {ada2 => ada.2}/envs.adb (100%) rename {ada2 => ada.2}/envs.ads (100%) rename {ada2 => ada.2}/eval_cb.ads (100%) rename {ada2 => ada.2}/printer.adb (100%) rename {ada2 => ada.2}/printer.ads (100%) rename {ada2 => ada.2}/reader.adb (100%) rename {ada2 => ada.2}/reader.ads (100%) rename {ada2 => ada.2}/readline.adb (100%) rename {ada2 => ada.2}/readline.ads (100%) rename {ada2 => ada.2}/run (100%) rename {ada2 => ada.2}/step0_repl.adb (100%) rename {ada2 => ada.2}/step1_read_print.adb (100%) rename {ada2 => ada.2}/step2_eval.adb (100%) rename {ada2 => ada.2}/step3_env.adb (100%) rename {ada2 => ada.2}/step4_if_fn_do.adb (100%) rename {ada2 => ada.2}/step5_tco.adb (100%) rename {ada2 => ada.2}/step6_file.adb (100%) rename {ada2 => ada.2}/step7_quote.adb (100%) rename {ada2 => ada.2}/step8_macros.adb (100%) rename {ada2 => ada.2}/step9_try.adb (100%) rename {ada2 => ada.2}/stepa_mal.adb (100%) rename {ada2 => ada.2}/types-atoms.adb (100%) rename {ada2 => ada.2}/types-atoms.ads (100%) rename {ada2 => ada.2}/types-builtins.adb (100%) rename {ada2 => ada.2}/types-builtins.ads (100%) rename {ada2 => ada.2}/types-functions.adb (100%) rename {ada2 => ada.2}/types-functions.ads (100%) rename {ada2 => ada.2}/types-lists.adb (100%) rename {ada2 => ada.2}/types-lists.ads (100%) rename {ada2 => ada.2}/types-mal.adb (100%) rename {ada2 => ada.2}/types-mal.ads (100%) rename {ada2 => ada.2}/types-maps.adb (100%) rename {ada2 => ada.2}/types-maps.ads (100%) rename {ada2 => ada.2}/types-symbols-names.ads (100%) rename {ada2 => ada.2}/types-symbols.adb (100%) rename {ada2 => ada.2}/types-symbols.ads (100%) rename {ada2 => ada.2}/types.ads (100%) diff --git a/Makefile b/Makefile index d80bd7d2c6..08607e6ed9 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ DOCKERIZE = # Implementation specific settings # -IMPLS = ada ada2 awk bash basic c chuck clojure coffee common-lisp cpp crystal cs d dart \ +IMPLS = ada ada.2 awk bash basic c chuck clojure coffee common-lisp cpp crystal cs d dart \ elisp elixir elm erlang es6 factor fantom forth fsharp go groovy gnu-smalltalk \ guile haskell haxe hy io java js julia kotlin livescript logo lua make mal \ matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp plpgsql \ @@ -173,7 +173,7 @@ scheme_STEP_TO_PROG_foment = scheme/$($(1)).scm # Map of step (e.g. "step8") to executable file for that step ada_STEP_TO_PROG = ada/$($(1)) -ada2_STEP_TO_PROG = ada2/$($(1)) +ada.2_STEP_TO_PROG = ada.2/$($(1)) awk_STEP_TO_PROG = awk/$($(1)).awk bash_STEP_TO_PROG = bash/$($(1)).sh basic_STEP_TO_PROG = $(basic_STEP_TO_PROG_$(basic_MODE)) diff --git a/ada2/Makefile b/ada.2/Makefile similarity index 100% rename from ada2/Makefile rename to ada.2/Makefile diff --git a/ada2/README b/ada.2/README similarity index 100% rename from ada2/README rename to ada.2/README diff --git a/ada2/core.adb b/ada.2/core.adb similarity index 100% rename from ada2/core.adb rename to ada.2/core.adb diff --git a/ada2/core.ads b/ada.2/core.ads similarity index 100% rename from ada2/core.ads rename to ada.2/core.ads diff --git a/ada2/envs.adb b/ada.2/envs.adb similarity index 100% rename from ada2/envs.adb rename to ada.2/envs.adb diff --git a/ada2/envs.ads b/ada.2/envs.ads similarity index 100% rename from ada2/envs.ads rename to ada.2/envs.ads diff --git a/ada2/eval_cb.ads b/ada.2/eval_cb.ads similarity index 100% rename from ada2/eval_cb.ads rename to ada.2/eval_cb.ads diff --git a/ada2/printer.adb b/ada.2/printer.adb similarity index 100% rename from ada2/printer.adb rename to ada.2/printer.adb diff --git a/ada2/printer.ads b/ada.2/printer.ads similarity index 100% rename from ada2/printer.ads rename to ada.2/printer.ads diff --git a/ada2/reader.adb b/ada.2/reader.adb similarity index 100% rename from ada2/reader.adb rename to ada.2/reader.adb diff --git a/ada2/reader.ads b/ada.2/reader.ads similarity index 100% rename from ada2/reader.ads rename to ada.2/reader.ads diff --git a/ada2/readline.adb b/ada.2/readline.adb similarity index 100% rename from ada2/readline.adb rename to ada.2/readline.adb diff --git a/ada2/readline.ads b/ada.2/readline.ads similarity index 100% rename from ada2/readline.ads rename to ada.2/readline.ads diff --git a/ada2/run b/ada.2/run similarity index 100% rename from ada2/run rename to ada.2/run diff --git a/ada2/step0_repl.adb b/ada.2/step0_repl.adb similarity index 100% rename from ada2/step0_repl.adb rename to ada.2/step0_repl.adb diff --git a/ada2/step1_read_print.adb b/ada.2/step1_read_print.adb similarity index 100% rename from ada2/step1_read_print.adb rename to ada.2/step1_read_print.adb diff --git a/ada2/step2_eval.adb b/ada.2/step2_eval.adb similarity index 100% rename from ada2/step2_eval.adb rename to ada.2/step2_eval.adb diff --git a/ada2/step3_env.adb b/ada.2/step3_env.adb similarity index 100% rename from ada2/step3_env.adb rename to ada.2/step3_env.adb diff --git a/ada2/step4_if_fn_do.adb b/ada.2/step4_if_fn_do.adb similarity index 100% rename from ada2/step4_if_fn_do.adb rename to ada.2/step4_if_fn_do.adb diff --git a/ada2/step5_tco.adb b/ada.2/step5_tco.adb similarity index 100% rename from ada2/step5_tco.adb rename to ada.2/step5_tco.adb diff --git a/ada2/step6_file.adb b/ada.2/step6_file.adb similarity index 100% rename from ada2/step6_file.adb rename to ada.2/step6_file.adb diff --git a/ada2/step7_quote.adb b/ada.2/step7_quote.adb similarity index 100% rename from ada2/step7_quote.adb rename to ada.2/step7_quote.adb diff --git a/ada2/step8_macros.adb b/ada.2/step8_macros.adb similarity index 100% rename from ada2/step8_macros.adb rename to ada.2/step8_macros.adb diff --git a/ada2/step9_try.adb b/ada.2/step9_try.adb similarity index 100% rename from ada2/step9_try.adb rename to ada.2/step9_try.adb diff --git a/ada2/stepa_mal.adb b/ada.2/stepa_mal.adb similarity index 100% rename from ada2/stepa_mal.adb rename to ada.2/stepa_mal.adb diff --git a/ada2/types-atoms.adb b/ada.2/types-atoms.adb similarity index 100% rename from ada2/types-atoms.adb rename to ada.2/types-atoms.adb diff --git a/ada2/types-atoms.ads b/ada.2/types-atoms.ads similarity index 100% rename from ada2/types-atoms.ads rename to ada.2/types-atoms.ads diff --git a/ada2/types-builtins.adb b/ada.2/types-builtins.adb similarity index 100% rename from ada2/types-builtins.adb rename to ada.2/types-builtins.adb diff --git a/ada2/types-builtins.ads b/ada.2/types-builtins.ads similarity index 100% rename from ada2/types-builtins.ads rename to ada.2/types-builtins.ads diff --git a/ada2/types-functions.adb b/ada.2/types-functions.adb similarity index 100% rename from ada2/types-functions.adb rename to ada.2/types-functions.adb diff --git a/ada2/types-functions.ads b/ada.2/types-functions.ads similarity index 100% rename from ada2/types-functions.ads rename to ada.2/types-functions.ads diff --git a/ada2/types-lists.adb b/ada.2/types-lists.adb similarity index 100% rename from ada2/types-lists.adb rename to ada.2/types-lists.adb diff --git a/ada2/types-lists.ads b/ada.2/types-lists.ads similarity index 100% rename from ada2/types-lists.ads rename to ada.2/types-lists.ads diff --git a/ada2/types-mal.adb b/ada.2/types-mal.adb similarity index 100% rename from ada2/types-mal.adb rename to ada.2/types-mal.adb diff --git a/ada2/types-mal.ads b/ada.2/types-mal.ads similarity index 100% rename from ada2/types-mal.ads rename to ada.2/types-mal.ads diff --git a/ada2/types-maps.adb b/ada.2/types-maps.adb similarity index 100% rename from ada2/types-maps.adb rename to ada.2/types-maps.adb diff --git a/ada2/types-maps.ads b/ada.2/types-maps.ads similarity index 100% rename from ada2/types-maps.ads rename to ada.2/types-maps.ads diff --git a/ada2/types-symbols-names.ads b/ada.2/types-symbols-names.ads similarity index 100% rename from ada2/types-symbols-names.ads rename to ada.2/types-symbols-names.ads diff --git a/ada2/types-symbols.adb b/ada.2/types-symbols.adb similarity index 100% rename from ada2/types-symbols.adb rename to ada.2/types-symbols.adb diff --git a/ada2/types-symbols.ads b/ada.2/types-symbols.ads similarity index 100% rename from ada2/types-symbols.ads rename to ada.2/types-symbols.ads diff --git a/ada2/types.ads b/ada.2/types.ads similarity index 100% rename from ada2/types.ads rename to ada.2/types.ads From 0b4ab1a5262efc26d22f7df33fba41917c68ad03 Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Sun, 10 Mar 2019 01:14:23 +0100 Subject: [PATCH 0803/2308] ada.2: add to .travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 77a921a4da..aba4459fe3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ sudo: required matrix: include: - {env: IMPL=ada, services: [docker]} + - {env: IMPL=ada.2, services: [docker]} - {env: IMPL=awk, services: [docker]} - {env: IMPL=bash, services: [docker]} - {env: IMPL=basic basic_MODE=cbm, services: [docker]} From 73b7847ef6cb6db645a6504aa5ecf013c1cfee6b Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 13 Mar 2019 17:31:57 -0500 Subject: [PATCH 0804/2308] rexx, ruby: remove extraneous mal files. --- rexx/t.mal | 1 - ruby/suggest.mal | 7 ------- 2 files changed, 8 deletions(-) delete mode 100644 rexx/t.mal delete mode 100644 ruby/suggest.mal diff --git a/rexx/t.mal b/rexx/t.mal deleted file mode 100644 index 0c27e43547..0000000000 --- a/rexx/t.mal +++ /dev/null @@ -1 +0,0 @@ -(prn (+ 4 5)) diff --git a/ruby/suggest.mal b/ruby/suggest.mal deleted file mode 100644 index 685a5d1db2..0000000000 --- a/ruby/suggest.mal +++ /dev/null @@ -1,7 +0,0 @@ -(def! read-args (fn* [args] - (let* [arg (readline "arg> ")] - (if (or (nil? arg) (empty? arg)) - args - (read-args (conj args arg)))))) - -(prn "The args you entered are:" (read-args [])) From 00c3a3c33da116e5622bd596b002de6c110af3fc Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Sun, 17 Mar 2019 11:24:03 +0100 Subject: [PATCH 0805/2308] ada.2: spring cleaning before final pull request. Two changes require approval. * The 'do' special becomes a built-in function similar to first. This small change reduces the complexity of eval. The last evaluation cannot benefit from TCO, but the performance change seems invisible. * read/eval/print acts on each item found in the input string, as if they were enclosed with (do ..). The guide does not specify what should happen to text following the first AST, and this change actually simplifies some things (like dealing with zero AST). The read-string built-in function only returns the first AST, as changing this would be much more intrusive. Other changes seem straightforward. Global: * Ada 2020 target assignments (like +=, but more general). * Use Constant_Indexing aspect for sequences, so that they can be indexed in source code like native arrays. * consistency renamings. 'fn' does not include built-in functions, 'function' does. 'list' does not include vectors, 'sequence' does. Move error handling to a separate package. * This simplifies code everywhere else. * Uncaught expressions now report a stack trace. Types: * Count allocations and deallocations, check that counts match. * Share more code between functions and macros. Core: * Replace the Core.Ns function returning an array with a procedure (The intermediate object was preventing the reference counting code from deallocating some unused objects). * Implement Prn with Pr_Str. Printer: * Change the profile so that the caller spares some allocations. Reader: * Share a single buffer of mal values between all recursions. This significantly reduces the stack footprint. Steps: * Fix implementation name (ada.2) in the startup script. * Let environment variables trigger debugging information. --- ada.2/Makefile | 7 +- ada.2/README | 60 ++- ada.2/core.adb | 484 +++++++++--------- ada.2/core.ads | 21 +- ada.2/envs.adb | 247 +++++---- ada.2/envs.ads | 45 +- ada.2/err.adb | 53 ++ ada.2/err.ads | 40 ++ ada.2/printer.adb | 77 +-- ada.2/printer.ads | 12 +- ada.2/reader.adb | 196 +++---- ada.2/reader.ads | 8 +- ada.2/step1_read_print.adb | 33 +- ada.2/step2_eval.adb | 79 +-- ada.2/step3_env.adb | 115 +++-- ada.2/step4_if_fn_do.adb | 198 +++---- ada.2/step5_tco.adb | 206 ++++---- ada.2/step6_file.adb | 218 ++++---- ada.2/step7_quote.adb | 284 +++++----- ada.2/step8_macros.adb | 345 ++++++------- ada.2/step9_try.adb | 397 +++++++------- ada.2/stepa_mal.adb | 404 +++++++-------- ada.2/types-atoms.adb | 62 +-- ada.2/types-atoms.ads | 3 + ada.2/types-builtins.adb | 24 +- ada.2/types-builtins.ads | 2 + ada.2/{types-functions.adb => types-fns.adb} | 90 ++-- ada.2/{types-functions.ads => types-fns.ads} | 11 +- ada.2/types-lists.adb | 313 ----------- ada.2/types-mal.adb | 11 +- ada.2/types-mal.ads | 14 +- ada.2/types-maps.adb | 211 ++++---- ada.2/types-maps.ads | 3 + ada.2/types-sequences.adb | 335 ++++++++++++ .../{types-lists.ads => types-sequences.ads} | 21 +- ada.2/types-symbols-names.ads | 1 - ada.2/types-symbols.adb | 36 +- ada.2/types-symbols.ads | 6 +- ada.2/types.ads | 14 +- 39 files changed, 2402 insertions(+), 2284 deletions(-) create mode 100644 ada.2/err.adb create mode 100644 ada.2/err.ads rename ada.2/{types-functions.adb => types-fns.adb} (57%) rename ada.2/{types-functions.ads => types-fns.ads} (86%) delete mode 100644 ada.2/types-lists.adb create mode 100644 ada.2/types-sequences.adb rename ada.2/{types-lists.ads => types-sequences.ads} (85%) diff --git a/ada.2/Makefile b/ada.2/Makefile index 4bbee5d0c9..342e56a917 100644 --- a/ada.2/Makefile +++ b/ada.2/Makefile @@ -10,7 +10,7 @@ else endif # Compiler arguments. -CARGS = -gnat2012 $(OPT) $(ADAFLAGS) +CARGS = -gnat2020 $(OPT) $(ADAFLAGS) # Linker arguments. LARGS = $(LDFLAGS) -lreadline @@ -36,14 +36,15 @@ clean: # the rest when it must be executed. TYPES := \ envs.ads envs.adb \ + err.ads err.adb \ eval_cb.ads \ printer.ads printer.adb \ reader.ads reader.adb \ readline.ads \ types-atoms.ads types-atoms.adb \ types-builtins.ads types-builtins.adb \ - types-functions.ads types-functions.adb \ - types-lists.ads types-lists.adb \ + types-fns.ads types-fns.adb \ + types-sequences.ads types-sequences.adb \ types-mal.ads types-mal.adb \ types-maps.ads types-maps.adb \ types-symbols-names.ads \ diff --git a/ada.2/README b/ada.2/README index c2d8e560b7..21a1292423 100644 --- a/ada.2/README +++ b/ada.2/README @@ -1,21 +1,22 @@ Comparison with the first Ada implementation. +-- The first implementation was deliberately compatible with all Ada -compilers, while this one illustrates various Ada 2012 features: +compilers, while this one illustrates various Ada 2020 features: assertions, preconditions, invariants, initial assignment for limited -types, limited imports... +types, limited imports, indexing aspects... The variant MAL type is implemented with a discriminant instead of object-style dispatching. This allows more static and dynamic checks, but also two crucial performance improvements: -* Nil, boolean, integers and built-in functions are passed by value - without dynamic allocation. +* Nil, boolean, integers and pointers to built-in functions are passed + by value without dynamic allocation. * Lists are implemented as C-style arrays, and most of them can be allocated on the stack. Once each component has an explicit interface, various optimizations have been added: unique allocation of symbols, stack-style allocation -of environments in the current execution path, reuse of existing +of environments in the current execution path, reuse of allocated memory when the reference count reaches 1... The eventual performances compete with C-style languages, allthough @@ -23,17 +24,21 @@ all user input is checked (implicit language-defined checks like array bounds and discriminant consistency are only enabled during tests). There are also similarities with the first implementation. For -example, both rely on user-defined finalization to handle recursive -structures without garbage collecting. Also, most pointer types are -wrapped into a finalized type counting references. +example, both rely on user-defined finalization to count references in +recursive structures instead of a posteriori garbage collection. -Some remarks if anyone works on this. +Notes for contributors that do not fit in a specific package. +-- -* The default value for such wrapped pointers is invalid, new - variables must be assigned immediately. This is usually enforced by - a hidden discriminant, but this would prevent the type to become a - field inside Types.Mal.T. So we usse a private invariant as a a - fallback. +* All packages can call Eval back via a reference in the Eval_Cb + package, set during startup. I am interested in a prettier solution + ensuring a valid value during elaboration. + Note that generic packages cannot export access values. + +* All wrapped pointers are non null, new variables must be assigned + immediately. This is usually enforced by a hidden discriminant, but + here we want the type to become a field inside Types.Mal.T. So the + check happens at run time with a private invariant. * The finalize procedure may be called twice, so it does nothing when the reference count is zero, meaning that we are reaching Finalize @@ -43,6 +48,27 @@ Some remarks if anyone works on this. automatically) must be built before any exception is raised by user code (for example the 'map' built-in function may run user code). -Known bugs: the third step of the perf^ada2 target fails during the -final storage deallocation when the executable is built with -gnatp. I -have failed to understand why so far. +* Each module encapsulating dynamic allocation counts allocations and + deallocations. With debugging options, a failure is reported if + - too many deallocation happen (via a numeric range check) + - all storage is not freed (via a dedicated call from the step file) + +Known bugs +-- + +The third step of the perf^ada2 target fails during the final storage +deallocation when the executable is built with -gnatp. I have failed +to understand why so far. + +Debugging +-- + +Uncaught exceptions are reported with an execution trace (excluding +TCO cycles). This has become possible in step9, but has been +backported to former steps as this is really handy for debugging. + +Some environment variables increase verbosity. +# dbg_reader= ./stepAmal trace reader recursion +# dbgeval= ./stepAmal eval recursion (or TCO) +# dbgenv0= ./stepAmal eval recursion and environments contents +# dbgenv1= ./stepAmal eval recursion and environment internals diff --git a/ada.2/core.adb b/ada.2/core.adb index c4db4ef609..00c9e5aa5e 100644 --- a/ada.2/core.adb +++ b/ada.2/core.adb @@ -4,12 +4,13 @@ with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; with Envs; +with Err; with Eval_Cb; with Types.Atoms; with Types.Builtins; -with Types.Functions; -with Types.Lists; +with Types.Mal; with Types.Maps; +with Types.Sequences; with Types.Symbols.Names; with Printer; with Reader; @@ -22,109 +23,106 @@ package body Core is -- Used by time_ms. Start_Time : constant Ada.Calendar.Time := Ada.Calendar.Clock; - -- In the following helpers, "name" is the one reported by error - -- messages. - generic Kind : in Kind_Type; - Name : in String; function Generic_Kind_Test (Args : in Mal.T_Array) return Mal.T; - function Generic_Kind_Test (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 then - raise Argument_Error with Name & ": expects 1 argument" - else - (Kind_Boolean, Args (Args'First).Kind = Kind)); + function Generic_Kind_Test (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 1, "expected 1 parameter"); + return (Kind_Boolean, Args (Args'First).Kind = Kind); + end Generic_Kind_Test; generic with function Ada_Operator (Left, Right : in Integer) return Integer; - Name : in String; function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T; - function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 2 then - raise Argument_Error with Name & ": expects 2 arguments" - elsif (for some A of Args => A.Kind /= Kind_Number) then - raise Argument_Error with Name & ": expects numbers" - else - (Kind_Number, Ada_Operator (Args (Args'First).Number, - Args (Args'Last).Number))); + function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 2, "expected 2 parameters"); + Err.Check (Args (Args'First).Kind = Kind_Number, + "parameter 1 must be a number"); + Err.Check (Args (Args'Last).Kind = Kind_Number, + "parameter 2 must be a number"); + return (Kind_Number, Ada_Operator (Args (Args'First).Number, + Args (Args'Last).Number)); + end Generic_Mal_Operator; + generic with function Ada_Operator (Left, Right : in Integer) return Boolean; - Name : in String; function Generic_Comparison (Args : in Mal.T_Array) return Mal.T; - function Generic_Comparison (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 2 then - raise Argument_Error with Name & ": expects 2 arguments" - elsif (for some A of Args => A.Kind /= Kind_Number) then - raise Argument_Error with Name & ": expects numbers" - else - (Kind_Boolean, Ada_Operator (Args (Args'First).Number, - Args (Args'Last).Number))); - - function Addition is new Generic_Mal_Operator ("+", "+"); + function Generic_Comparison (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 2, "expected 2 parameters"); + Err.Check (Args (Args'First).Kind = Kind_Number, + "parameter 1 must be a number"); + Err.Check (Args (Args'Last).Kind = Kind_Number, + "parameter 2 must be a number"); + return (Kind_Boolean, Ada_Operator (Args (Args'First).Number, + Args (Args'Last).Number)); + end Generic_Comparison; + + function Addition is new Generic_Mal_Operator ("+"); function Apply (Args : in Mal.T_Array) return Mal.T; - function Division is new Generic_Mal_Operator ("/", "/"); + function Division is new Generic_Mal_Operator ("/"); function Equals (Args : in Mal.T_Array) return Mal.T; function Eval (Args : in Mal.T_Array) return Mal.T; - function Greater_Equal is new Generic_Comparison (">=", ">="); - function Greater_Than is new Generic_Comparison (">", ">"); - function Is_Atom is new Generic_Kind_Test (Kind_Atom, "atom?"); + function Greater_Equal is new Generic_Comparison (">="); + function Greater_Than is new Generic_Comparison (">"); + function Is_Atom is new Generic_Kind_Test (Kind_Atom); function Is_False (Args : in Mal.T_Array) return Mal.T; function Is_Function (Args : in Mal.T_Array) return Mal.T; - function Is_Keyword is new Generic_Kind_Test (Kind_Keyword, "keyword?"); - function Is_List is new Generic_Kind_Test (Kind_List, "list?"); - function Is_Macro is new Generic_Kind_Test (Kind_Macro, "macro?"); - function Is_Map is new Generic_Kind_Test (Kind_Map, "map?"); - function Is_Nil is new Generic_Kind_Test (Kind_Nil, "nil?"); - function Is_Number is new Generic_Kind_Test (Kind_Number, "number?"); + function Is_Keyword is new Generic_Kind_Test (Kind_Keyword); + function Is_List is new Generic_Kind_Test (Kind_List); + function Is_Macro is new Generic_Kind_Test (Kind_Macro); + function Is_Map is new Generic_Kind_Test (Kind_Map); + function Is_Nil is new Generic_Kind_Test (Kind_Nil); + function Is_Number is new Generic_Kind_Test (Kind_Number); function Is_Sequential (Args : in Mal.T_Array) return Mal.T; - function Is_String is new Generic_Kind_Test (Kind_String, "string?"); - function Is_Symbol is new Generic_Kind_Test (Kind_Symbol, "symbol?"); + function Is_String is new Generic_Kind_Test (Kind_String); + function Is_Symbol is new Generic_Kind_Test (Kind_Symbol); function Is_True (Args : in Mal.T_Array) return Mal.T; - function Is_Vector is new Generic_Kind_Test (Kind_Vector, "vector?"); + function Is_Vector is new Generic_Kind_Test (Kind_Vector); function Keyword (Args : in Mal.T_Array) return Mal.T; - function Less_Equal is new Generic_Comparison ("<=", "<="); - function Less_Than is new Generic_Comparison ("<", "<"); + function Less_Equal is new Generic_Comparison ("<="); + function Less_Than is new Generic_Comparison ("<"); + function Mal_Do (Args : in Mal.T_Array) return Mal.T; function Meta (Args : in Mal.T_Array) return Mal.T; function Pr_Str (Args : in Mal.T_Array) return Mal.T; function Println (Args : in Mal.T_Array) return Mal.T; function Prn (Args : in Mal.T_Array) return Mal.T; - function Product is new Generic_Mal_Operator ("*", "*"); + function Product is new Generic_Mal_Operator ("*"); function Read_String (Args : in Mal.T_Array) return Mal.T; function Readline (Args : in Mal.T_Array) return Mal.T; function Seq (Args : in Mal.T_Array) return Mal.T; function Slurp (Args : in Mal.T_Array) return Mal.T; function Str (Args : in Mal.T_Array) return Mal.T; - function Subtraction is new Generic_Mal_Operator ("-", "-"); + function Subtraction is new Generic_Mal_Operator ("-"); function Symbol (Args : in Mal.T_Array) return Mal.T; - function Throw (Args : in Mal.T_Array) return Mal.T; function Time_Ms (Args : in Mal.T_Array) return Mal.T; function With_Meta (Args : in Mal.T_Array) return Mal.T; ---------------------------------------------------------------------- function Apply (Args : in Mal.T_Array) return Mal.T is - use type Lists.Ptr; begin - if Args'Length < 2 then - raise Argument_Error with "apply: expects at least 2 arguments"; - elsif Args (Args'Last).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "apply: last arg must be a list or vector"; - end if; + Err.Check (2 <= Args'Length, "expected at least 2 parameters"); + Err.Check (Args (Args'Last).Kind in Kind_Sequence, + "last parameter must be a sequence"); declare + use type Sequences.Ptr; F : Mal.T renames Args (Args'First); A : constant Mal.T_Array - := Args (Args'First + 1 .. Args'Last - 1) & Args (Args'Last).List; + := Args (Args'First + 1 .. Args'Last - 1) + & Args (Args'Last).Sequence; begin case F.Kind is when Kind_Builtin => return F.Builtin.all (A); when Kind_Builtin_With_Meta => return F.Builtin_With_Meta.Builtin.all (A); - when Kind_Function => + when Kind_Fn => return F.Fn.Apply (A); when others => - raise Argument_Error - with "apply: cannot call " & Printer.Img (F); + Err.Raise_With ("parameter 1 must be a function"); end case; end; end Apply; @@ -132,146 +130,155 @@ package body Core is function Equals (Args : in Mal.T_Array) return Mal.T is use type Mal.T; begin - if Args'Length /= 2 then - raise Argument_Error with "=: expects 2 arguments"; - else - return (Kind_Boolean, Args (Args'First) = Args (Args'Last)); - end if; + Err.Check (Args'Length = 2, "expected 2 parameters"); + return (Kind_Boolean, Args (Args'First) = Args (Args'Last)); end Equals; - function Eval (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 then - raise Argument_Error with "eval: expects 1 argument" - else - Eval_Cb.Cb.all (Ast => Args (Args'First), - Env => Envs.Repl)); - - function Is_False (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 then - raise Argument_Error with "false?: expects 1 argument" - else - (Kind_Boolean, Args (Args'First).Kind = Kind_Boolean - and then not Args (Args'First).Ada_Boolean)); - - function Is_Function (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 then - raise Argument_Error with "count: expects 1 argument" - else - (Kind_Boolean, Args (Args'First).Kind in - Kind_Function | Kind_Builtin | Kind_Builtin_With_Meta)); - - function Is_Sequential (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 then - raise Argument_Error with "sequential?: expects 1 argument" - else - (Kind_Boolean, Args (Args'First).Kind in Kind_List | Kind_Vector)); - - function Is_True (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 then - raise Argument_Error with "true?: expects 1 argument" - else - (Kind_Boolean, Args (Args'First).Kind = Kind_Boolean - and then Args (Args'First).Ada_Boolean)); - - function Keyword (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 then - raise Argument_Error with "keyword: expects 1 argument" - elsif Args (Args'First).Kind not in Kind_Keyword | Kind_String then - raise Argument_Error with "keyword: expects a keyword or a string" - else - (Kind_Keyword, Args (Args'First).S)); + function Eval (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 1, "expected 1 parameter"); + return Eval_Cb.Cb.all (Ast => Args (Args'First), + Env => Envs.Repl); + end Eval; + + function Is_False (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 1, "expected 1 parameter"); + return (Kind_Boolean, Args (Args'First).Kind = Kind_Boolean + and then not Args (Args'First).Ada_Boolean); + end Is_False; + + function Is_Function (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 1, "expected 1 parameter"); + return (Kind_Boolean, Args (Args'First).Kind in Kind_Function); + end Is_Function; + + function Is_Sequential (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 1, "expected 1 parameter"); + return (Kind_Boolean, Args (Args'First).Kind in Kind_Sequence); + end Is_Sequential; + + function Is_True (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 1, "expected 1 parameter"); + return (Kind_Boolean, Args (Args'First).Kind = Kind_Boolean + and then Args (Args'First).Ada_Boolean); + end Is_True; + + function Keyword (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 1, "expected 1 parameter"); + Err.Check (Args (Args'First).Kind = Kind_String, "expected a string"); + return (Kind_Keyword, Args (Args'First).S); + end Keyword; + + function Mal_Do (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (0 < Args'Length, "expected at least 1 parameter"); + return Args (Args'Last); + end Mal_Do; function Meta (Args : in Mal.T_Array) return Mal.T is begin - if Args'Length /= 1 then - raise Argument_Error with "meta: expects 1 argument"; - end if; + Err.Check (Args'Length = 1, "expected 1 parameter"); declare A1 : Mal.T renames Args (Args'First); begin case A1.Kind is - when Kind_List | Kind_Vector => - return A1.List.Meta; + when Kind_Sequence => + return A1.Sequence.Meta; when Kind_Map => return A1.Map.Meta; - when Kind_Function => + when Kind_Fn => return A1.Fn.Meta; when Kind_Builtin_With_Meta => return A1.Builtin_With_Meta.Meta; when Kind_Builtin => return Mal.Nil; when others => - raise Argument_Error - with "meta: expects a list, vector, map or function"; + Err.Raise_With ("expected a function, map or sequence"); end case; end; end Meta; - function Ns return Binding_List - is ((Symbols.Constructor ("+"), Addition'Access), - (Symbols.Constructor ("apply"), Apply'Access), - (Symbols.Constructor ("assoc"), Maps.Assoc'Access), - (Symbols.Constructor ("atom"), Atoms.Atom'Access), - (Symbols.Constructor ("concat"), Lists.Concat'Access), - (Symbols.Constructor ("conj"), Lists.Conj'Access), - (Symbols.Constructor ("cons"), Lists.Cons'Access), - (Symbols.Constructor ("contains?"), Maps.Contains'Access), - (Symbols.Constructor ("count"), Lists.Count'Access), - (Symbols.Names.Deref, Atoms.Deref'Access), - (Symbols.Constructor ("dissoc"), Maps.Dissoc'Access), - (Symbols.Constructor ("/"), Division'Access), - (Symbols.Constructor ("="), Equals'Access), - (Symbols.Constructor ("eval"), Eval'Access), - (Symbols.Constructor ("first"), Lists.First'Access), - (Symbols.Constructor ("get"), Maps.Get'Access), - (Symbols.Constructor (">="), Greater_Equal'Access), - (Symbols.Constructor (">"), Greater_Than'Access), - (Symbols.Constructor ("hash-map"), Maps.Hash_Map'Access), - (Symbols.Constructor ("atom?"), Is_Atom'Access), - (Symbols.Constructor ("empty?"), Lists.Is_Empty'Access), - (Symbols.Constructor ("false?"), Is_False'Access), - (Symbols.Constructor ("fn?"), Is_Function'Access), - (Symbols.Constructor ("keyword?"), Is_Keyword'Access), - (Symbols.Constructor ("list?"), Is_List'Access), - (Symbols.Constructor ("macro?"), Is_Macro'Access), - (Symbols.Constructor ("map?"), Is_Map'Access), - (Symbols.Constructor ("nil?"), Is_Nil'Access), - (Symbols.Constructor ("number?"), Is_Number'Access), - (Symbols.Constructor ("sequential?"), Is_Sequential'Access), - (Symbols.Constructor ("string?"), Is_String'Access), - (Symbols.Constructor ("symbol?"), Is_Symbol'Access), - (Symbols.Constructor ("true?"), Is_True'Access), - (Symbols.Constructor ("vector?"), Is_Vector'Access), - (Symbols.Constructor ("keys"), Maps.Keys'Access), - (Symbols.Constructor ("keyword"), Keyword'Access), - (Symbols.Constructor ("<="), Less_Equal'Access), - (Symbols.Constructor ("<"), Less_Than'Access), - (Symbols.Constructor ("list"), Lists.List'Access), - (Symbols.Constructor ("map"), Lists.Map'Access), - (Symbols.Constructor ("meta"), Meta'Access), - (Symbols.Constructor ("nth"), Lists.Nth'Access), - (Symbols.Constructor ("pr-str"), Pr_Str'Access), - (Symbols.Constructor ("println"), Println'Access), - (Symbols.Constructor ("prn"), Prn'Access), - (Symbols.Constructor ("*"), Product'Access), - (Symbols.Constructor ("read-string"), Read_String'Access), - (Symbols.Constructor ("readline"), Readline'Access), - (Symbols.Constructor ("reset!"), Atoms.Reset'Access), - (Symbols.Constructor ("rest"), Lists.Rest'Access), - (Symbols.Constructor ("seq"), Seq'Access), - (Symbols.Constructor ("slurp"), Slurp'Access), - (Symbols.Constructor ("str"), Str'Access), - (Symbols.Constructor ("-"), Subtraction'Access), - (Symbols.Constructor ("swap!"), Atoms.Swap'Access), - (Symbols.Constructor ("symbol"), Symbol'Access), - (Symbols.Constructor ("throw"), Throw'Access), - (Symbols.Constructor ("time-ms"), Time_Ms'Access), - (Symbols.Constructor ("vals"), Maps.Vals'Access), - (Symbols.Constructor ("vector"), Lists.Vector'Access), - (Symbols.Names.With_Meta, With_Meta'Access)); + procedure NS_Add_To_Repl is + procedure P (S : in Symbols.Ptr; + B : in Mal.Builtin_Ptr) with Inline; + procedure P (S : in Symbols.Ptr; + B : in Mal.Builtin_Ptr) + is + begin + Envs.Repl.Set (S, (Kind_Builtin, B)); + end P; + begin + P (Symbols.Constructor ("+"), Addition'Access); + P (Symbols.Constructor ("apply"), Apply'Access); + P (Symbols.Constructor ("assoc"), Maps.Assoc'Access); + P (Symbols.Constructor ("atom"), Atoms.Atom'Access); + P (Symbols.Constructor ("concat"), Sequences.Concat'Access); + P (Symbols.Constructor ("conj"), Sequences.Conj'Access); + P (Symbols.Constructor ("cons"), Sequences.Cons'Access); + P (Symbols.Constructor ("contains?"), Maps.Contains'Access); + P (Symbols.Constructor ("count"), Sequences.Count'Access); + P (Symbols.Names.Deref, Atoms.Deref'Access); + P (Symbols.Constructor ("dissoc"), Maps.Dissoc'Access); + P (Symbols.Constructor ("/"), Division'Access); + P (Symbols.Constructor ("do"), Mal_Do'Access); + P (Symbols.Constructor ("="), Equals'Access); + P (Symbols.Constructor ("eval"), Eval'Access); + P (Symbols.Constructor ("first"), Sequences.First'Access); + P (Symbols.Constructor ("get"), Maps.Get'Access); + P (Symbols.Constructor (">="), Greater_Equal'Access); + P (Symbols.Constructor (">"), Greater_Than'Access); + P (Symbols.Constructor ("hash-map"), Maps.Hash_Map'Access); + P (Symbols.Constructor ("atom?"), Is_Atom'Access); + P (Symbols.Constructor ("empty?"), Sequences.Is_Empty'Access); + P (Symbols.Constructor ("false?"), Is_False'Access); + P (Symbols.Constructor ("fn?"), Is_Function'Access); + P (Symbols.Constructor ("keyword?"), Is_Keyword'Access); + P (Symbols.Constructor ("list?"), Is_List'Access); + P (Symbols.Constructor ("macro?"), Is_Macro'Access); + P (Symbols.Constructor ("map?"), Is_Map'Access); + P (Symbols.Constructor ("nil?"), Is_Nil'Access); + P (Symbols.Constructor ("number?"), Is_Number'Access); + P (Symbols.Constructor ("sequential?"), Is_Sequential'Access); + P (Symbols.Constructor ("string?"), Is_String'Access); + P (Symbols.Constructor ("symbol?"), Is_Symbol'Access); + P (Symbols.Constructor ("true?"), Is_True'Access); + P (Symbols.Constructor ("vector?"), Is_Vector'Access); + P (Symbols.Constructor ("keys"), Maps.Keys'Access); + P (Symbols.Constructor ("keyword"), Keyword'Access); + P (Symbols.Constructor ("<="), Less_Equal'Access); + P (Symbols.Constructor ("<"), Less_Than'Access); + P (Symbols.Constructor ("list"), Sequences.List'Access); + P (Symbols.Constructor ("map"), Sequences.Map'Access); + P (Symbols.Constructor ("meta"), Meta'Access); + P (Symbols.Constructor ("nth"), Sequences.Nth'Access); + P (Symbols.Constructor ("pr-str"), Pr_Str'Access); + P (Symbols.Constructor ("println"), Println'Access); + P (Symbols.Constructor ("prn"), Prn'Access); + P (Symbols.Constructor ("*"), Product'Access); + P (Symbols.Constructor ("read-string"), Read_String'Access); + P (Symbols.Constructor ("readline"), Readline'Access); + P (Symbols.Constructor ("reset!"), Atoms.Reset'Access); + P (Symbols.Constructor ("rest"), Sequences.Rest'Access); + P (Symbols.Constructor ("seq"), Seq'Access); + P (Symbols.Constructor ("slurp"), Slurp'Access); + P (Symbols.Constructor ("str"), Str'Access); + P (Symbols.Constructor ("-"), Subtraction'Access); + P (Symbols.Constructor ("swap!"), Atoms.Swap'Access); + P (Symbols.Constructor ("symbol"), Symbol'Access); + P (Symbols.Constructor ("throw"), Err.Throw'Access); + P (Symbols.Constructor ("time-ms"), Time_Ms'Access); + P (Symbols.Constructor ("vals"), Maps.Vals'Access); + P (Symbols.Constructor ("vector"), Sequences.Vector'Access); + P (Symbols.Names.With_Meta, With_Meta'Access); + end NS_Add_To_Repl; function Pr_Str (Args : in Mal.T_Array) return Mal.T is - R : ASU.Unbounded_String := ASU.Null_Unbounded_String; + R : ASU.Unbounded_String; Started : Boolean := False; begin for A of Args loop @@ -280,48 +287,37 @@ package body Core is else Started := True; end if; - ASU.Append (R, Printer.Pr_Str (A)); + Printer.Pr_Str (R, A); end loop; return (Kind_String, R); end Pr_Str; function Println (Args : in Mal.T_Array) return Mal.T is Started : Boolean := False; + Buffer : ASU.Unbounded_String; begin for A of Args loop if Started then - Ada.Text_IO.Put (' '); + ASU.Append (Buffer, ' '); else Started := True; end if; - Ada.Text_IO.Unbounded_IO.Put (Printer.Pr_Str (A, Readably => False)); + Printer.Pr_Str (Buffer, A, Readably => False); end loop; - Ada.Text_IO.New_Line; + Ada.Text_IO.Unbounded_IO.Put_Line (Buffer); return Mal.Nil; end Println; function Prn (Args : in Mal.T_Array) return Mal.T is - Started : Boolean := False; begin - for A of Args loop - if Started then - Ada.Text_IO.Put (' '); - else - Started := True; - end if; - Ada.Text_IO.Unbounded_IO.Put (Printer.Pr_Str (A)); - end loop; - Ada.Text_IO.New_Line; + Ada.Text_IO.Unbounded_IO.Put_Line (Pr_Str (Args).S); return Mal.Nil; end Prn; function Readline (Args : in Mal.T_Array) return Mal.T is begin - if Args'Length /= 1 then - raise Argument_Error with "readline: expects 1 argument"; - elsif Args (Args'First).Kind not in Kind_Keyword | Kind_String then - raise Argument_Error with "readline: expects a keyword or string"; - end if; + Err.Check (Args'Length = 1, "expected 1 parameter"); + Err.Check (Args (Args'First).Kind = Kind_String, "expected a string"); Ada.Text_IO.Unbounded_IO.Put (Args (Args'First).S); if Ada.Text_IO.End_Of_File then return Mal.Nil; @@ -330,19 +326,22 @@ package body Core is end if; end Readline; - function Read_String (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 then - raise Argument_Error with "read-string: expects 1 argument" - elsif Args (Args'First).Kind /= Kind_String then - raise Argument_Error with "read-string: expects a string" - else - Reader.Read_Str (ASU.To_String (Args (Args'First).S))); + function Read_String (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 1, "expected 1 parameter"); + Err.Check (Args (Args'First).Kind = Kind_String, "expected a string"); + declare + R : constant Mal.T_Array + := Reader.Read_Str (ASU.To_String (Args (Args'First).S)); + begin + Err.Check (R'Length = 1, "parameter must contain 1 expression"); + return R (R'First); + end; + end Read_String; function Seq (Args : in Mal.T_Array) return Mal.T is begin - if Args'Length /= 1 then - raise Argument_Error with "seq: expects 1 argument"; - end if; + Err.Check (Args'Length = 1, "expected 1 parameter"); case Args (Args'First).Kind is when Kind_Nil => return Mal.Nil; @@ -357,17 +356,17 @@ package body Core is for I in R'Range loop R (I) := (Kind_String, ASU.Unbounded_Slice (A1, I, I)); end loop; - return Lists.List (R); + return Sequences.List (R); end; end if; - when Kind_List | Kind_Vector => - if Args (Args'First).List.Length = 0 then + when Kind_Sequence => + if Args (Args'First).Sequence.Length = 0 then return Mal.Nil; else - return (Kind_List, Args (Args'First).List); + return (Kind_List, Args (Args'First).Sequence); end if; when others => - raise Argument_Error with "seq: expects a string, list or vector"; + Err.Raise_With ("expected nil, a sequence or a string"); end case; end Seq; @@ -376,19 +375,15 @@ package body Core is File : File_Type; Buffer : ASU.Unbounded_String; begin - if Args'Length /= 1 then - raise Argument_Error with "slurp: expects 1 argument"; - elsif Args (Args'First).Kind /= Kind_String then - raise Argument_Error with "slurp: expects a string"; - else - Open (File, In_File, ASU.To_String (Args (Args'First).S)); - while not End_Of_File (File) loop - ASU.Append (Buffer, Get_Line (File)); - ASU.Append (Buffer, Ada.Characters.Latin_1.LF); - end loop; - Close (File); - return (Kind_String, Buffer); - end if; + Err.Check (Args'Length = 1, "expected 1 parameter"); + Err.Check (Args (Args'First).Kind = Kind_String, "expected a string"); + Open (File, In_File, ASU.To_String (Args (Args'First).S)); + while not End_Of_File (File) loop + ASU.Append (Buffer, Get_Line (File)); + ASU.Append (Buffer, Ada.Characters.Latin_1.LF); + end loop; + Close (File); + return (Kind_String, Buffer); exception when others => Close (File); @@ -396,46 +391,33 @@ package body Core is end Slurp; function Str (Args : in Mal.T_Array) return Mal.T is - R : ASU.Unbounded_String := ASU.Null_Unbounded_String; + R : ASU.Unbounded_String; begin for A of Args loop - ASU.Append (R, Printer.Pr_Str (A, Readably => False)); + Printer.Pr_Str (R, A, Readably => False); end loop; return (Kind_String, R); end Str; - function Symbol (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 then - raise Argument_Error with "symbol: expects 1 argument" - else - (Kind_Symbol, - Symbols.Constructor (ASU.To_String (Args (Args'First).S)))); - - function Throw (Args : in Mal.T_Array) return Mal.T is + function Symbol (Args : in Mal.T_Array) return Mal.T is begin - if Args'Length /= 1 then - raise Argument_Error with "throw: expects 1 argument"; - end if; - Last_Exception := Args (Args'First); - raise Exception_Throwed; - return Mal.Nil; -- GNAT wants a return - end Throw; + Err.Check (Args'Length = 1, "expected 1 parameter"); + Err.Check (Args (Args'First).Kind = Kind_String, "expected a string"); + return (Kind_Symbol, + Symbols.Constructor (ASU.To_String (Args (Args'First).S))); + end Symbol; function Time_Ms (Args : in Mal.T_Array) return Mal.T is use type Ada.Calendar.Time; begin - if 0 < Args'Length then - raise Argument_Error with "time: expects no argument"; - end if; + Err.Check (Args'Length = 0, "expected no parameter"); return (Kind_Number, Integer (1000.0 * (Ada.Calendar.Clock - Start_Time))); end Time_Ms; function With_Meta (Args : in Mal.T_Array) return Mal.T is begin - if Args'Length /= 2 then - raise Argument_Error with "with-meta: expects 2 arguments"; - end if; + Err.Check (Args'Length = 2, "expected 2 parameters"); declare A1 : Mal.T renames Args (Args'First); A2 : Mal.T renames Args (Args'Last); @@ -446,16 +428,16 @@ package body Core is when Kind_Builtin => return Builtins.With_Meta (A1.Builtin, A2); when Kind_List => - return (Kind_List, A1.List.With_Meta (A2)); + return (Kind_List, A1.Sequence.With_Meta (A2)); when Kind_Vector => - return (Kind_Vector, A1.List.With_Meta (A2)); + return (Kind_Vector, A1.Sequence.With_Meta (A2)); when Kind_Map => return A1.Map.With_Meta (A2); - when Kind_Function => + when Kind_Fn => return A1.Fn.With_Meta (A2); when others => - raise Argument_Error - with "with-meta: expects a list, vector, map or function"; + Err.Raise_With + ("parameter 1 must be a function, map or sequence"); end case; end; end With_Meta; diff --git a/ada.2/core.ads b/ada.2/core.ads index b276093cd0..faa8abb4d9 100644 --- a/ada.2/core.ads +++ b/ada.2/core.ads @@ -1,23 +1,6 @@ -with Types.Symbols; -with Types.Mal; - package Core with Elaborate_Body is - type Binding is record - Symbol : Types.Symbols.Ptr; - Builtin : Types.Mal.Builtin_Ptr; - end record; - - type Binding_List is array (Positive range <>) of Binding; - - function Ns return Binding_List; - -- A list of built-in symbols and functionse. - -- A constant would make sense, but - -- * implementing it in the private part - - Exception_Throwed : exception; - Last_Exception : Types.Mal.T := Types.Mal.Nil; - -- When the "throw" builtin is executed, it assigns its argument - -- to Last_Exception, then raises this Ada exception. + procedure NS_Add_To_Repl; + -- Add built-in functions to Envs.Repl. end Core; diff --git a/ada.2/envs.adb b/ada.2/envs.adb index cab3d403fc..6e51c74d83 100644 --- a/ada.2/envs.adb +++ b/ada.2/envs.adb @@ -1,8 +1,10 @@ with Ada.Containers.Hashed_Maps; --- with Ada.Text_IO.Unbounded_IO; +with Ada.Text_IO.Unbounded_IO; with Ada.Unchecked_Deallocation; --- with Printer; +with Err; +with Printer; +with Types.Sequences; with Types.Symbols.Names; package body Envs is @@ -65,99 +67,110 @@ package body Envs is -- compatible with the Repl constant. procedure Free is new Ada.Unchecked_Deallocation (Heap_Record, Heap_Access); + Allocations : Natural := 0; + procedure Unreference (Reference : in out Heap_Access); procedure Set_Binds (M : in out HM.Map; Binds : in Symbols.Symbol_Array; - Exprs : in Mal.T_Array) - with Inline; - procedure Set_Binds_Macro (M : in out HM.Map; - Binds : in Symbols.Symbol_Array; - Exprs : in Lists.Ptr) - with Inline; - -- These two procedures are redundant, but sharing the code would - -- be ugly or inefficient. They are separated as inline procedures - -- in order to ease comparison, though. + Exprs : in Mal.T_Array); ---------------------------------------------------------------------- procedure Adjust (Object : in out Closure_Ptr) is begin if Object.Ref /= null then - Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + Object.Ref.all.Refs := @ + 1; end if; end Adjust; + procedure Clear_And_Check_Allocations is + begin + pragma Assert (Top = 1); + pragma Assert (Stack (1).Refs = 1); + Stack (1).Data.Clear; + if Stack (1).Alias /= null then + pragma Assert (Stack (1).Alias.all.Refs = 0); + Allocations := Allocations - 1; + Free (Stack (1).Alias); + end if; + pragma Assert (Allocations = 0); + end Clear_And_Check_Allocations; + function Copy_Pointer (Env : in Ptr) return Ptr is begin - Stack (Env.Index).Refs := Stack (Env.Index).Refs + 1; + Stack (Env.Index).Refs := @ + 1; return (Ada.Finalization.Limited_Controlled with Env.Index); end Copy_Pointer; - -- procedure Dump_Stack (Long : in Boolean := False) is - -- use Ada.Text_IO; - -- use Ada.Text_IO.Unbounded_IO; - -- begin - -- for I in 1 .. Top loop - -- if Long then - -- Put ("Level"); - -- end if; - -- Put (I'Img); - -- if Long then - -- New_Line; - -- Put_Line (" refs=" & Stack (I).Refs'Img); - -- if Stack (I).Alias = null then - -- Put_Line (" no alias"); - -- else - -- Put_Line (" an alias with" & Stack (I).Alias.all.Refs'Img - -- & " refs"); - -- end if; - -- end if; - -- if Long then - -- Put (" outer="); - -- else - -- Put (" (->"); - -- end if; - -- if Stack (I).Outer_On_Stack then - -- Put (Stack (I).Outer_Index'Img); - -- elsif Stack (I).Outer_Ref.all.Outer = null then - -- if Long then - -- Put ("alias for "); - -- end if; - -- Put (Stack (I).Outer_Ref.all.Index'Img); - -- else - -- Put (" closure for ex " & Stack (I).Outer_Ref.all.Index'Img); - -- end if; - -- if Long then - -- New_Line; - -- else - -- Put ("):"); - -- end if; - -- for P in Stack (I).Data.Iterate loop - -- if HM.Element (P).Kind /= Kind_Builtin then -- skip built-ins. - -- if Long then - -- Put (" "); - -- else - -- Put (' '); - -- end if; - -- Put (HM.Key (P).To_String); - -- Put (':'); - -- Put (Printer.Pr_Str (HM.Element (P))); - -- if Long then - -- New_Line; - -- end if; - -- end if; - -- end loop; - -- if Long then - -- Put (" ... built-ins"); - -- else - -- New_Line; - -- end if; - -- end loop; - -- if Long then - -- New_Line; - -- end if; - -- end Dump_Stack; + procedure Dump_Stack (Long : in Boolean) is + use Ada.Text_IO; + Builtins : Natural := 0; + begin + for I in 1 .. Top loop + if Long then + Put ("Level"); + end if; + Put (I'Img); + if Long then + New_Line; + Put_Line (" refs=" & Stack (I).Refs'Img); + if Stack (I).Alias = null then + Put_Line (" no alias"); + else + Put_Line (" an alias with" & Stack (I).Alias.all.Refs'Img + & " refs"); + end if; + end if; + if Long then + Put (" outer="); + else + Put (" (->"); + end if; + if Stack (I).Outer_On_Stack then + Put (Stack (I).Outer_Index'Img); + elsif Stack (I).Outer_Ref.all.Outer = null then + if Long then + Put ("alias for "); + end if; + Put (Stack (I).Outer_Ref.all.Index'Img); + else + Put (" closure for ex " & Stack (I).Outer_Ref.all.Index'Img); + end if; + if Long then + New_Line; + else + Put ("):"); + end if; + for P in Stack (I).Data.Iterate loop + if HM.Element (P).Kind = Kind_Builtin then + Builtins := Builtins + 1; + else + if Long then + Put (" "); + else + Put (' '); + end if; + Put (HM.Key (P).To_String); + Put (':'); + Unbounded_IO.Put (Printer.Pr_Str (HM.Element (P))); + if Long then + New_Line; + end if; + end if; + end loop; + if Long then + Put (" ..."); + Put (Integer'Image (Builtins)); + Put (" built-ins"); + else + New_Line; + end if; + end loop; + if Long then + New_Line; + end if; + end Dump_Stack; procedure Finalize (Object : in out Closure_Ptr) is begin @@ -168,7 +181,7 @@ package body Envs is begin if 0 < Object.Index then if 0 < Stack (Object.Index).Refs then - Stack (Object.Index).Refs := Stack (Object.Index).Refs - 1; + Stack (Object.Index).Refs := @ - 1; end if; Object.Index := 0; @@ -185,6 +198,7 @@ package body Envs is if R.Alias /= null then pragma Assert (R.Alias.all.Outer = null); pragma Assert (R.Alias.all.Refs = 0); + Allocations := Allocations - 1; Free (R.Alias); end if; exit; @@ -195,6 +209,7 @@ package body Envs is end if; elsif R.Alias.all.Refs = 0 then pragma Assert (R.Alias.all.Outer = null); + Allocations := Allocations - 1; Free (R.Alias); R.Data.Clear; if not R.Outer_On_Stack then @@ -217,10 +232,11 @@ package body Envs is O : Stack_Record renames Stack (R.Outer_Index); begin if O.Alias = null then + Allocations := Allocations + 1; O.Alias := new Heap_Record'(Index => R.Outer_Index, others => <>); else - O.Alias.all.Refs := O.Alias.all.Refs + 1; + O.Alias.all.Refs := @ + 1; end if; R.Alias.all.Outer := O.Alias; end; @@ -263,16 +279,17 @@ package body Envs is end loop Ref_Loop; Index := Ref.all.Index; end loop Main_Loop; - raise Unknown_Key with "'" & Key.To_String & "' not found"; + Err.Raise_With ("'" & Key.To_String & "' not found"); end Get; function New_Closure (Env : in Ptr'Class) return Closure_Ptr is Alias : Heap_Access renames Stack (Env.Index).Alias; begin if Alias = null then + Allocations := Allocations + 1; Alias := new Heap_Record'(Index => Env.Index, others => <>); else - Alias.all.Refs := Alias.all.Refs + 1; + Alias.all.Refs := @ + 1; end if; return (Ada.Finalization.Controlled with Alias); end New_Closure; @@ -283,7 +300,7 @@ package body Envs is if Env.Index < Top or 1 < R.Refs or (R.Alias /= null and then 0 < R.Alias.all.Refs) then - R.Refs := R.Refs - 1; + R.Refs := @ - 1; Top := Top + 1; pragma Assert (Stack (Top).Data.Is_Empty); pragma Assert (Stack (Top).Alias = null); @@ -304,9 +321,9 @@ package body Envs is -- Finalize Env before creating the new environment, in case -- this is the last reference and it can be forgotten. -- Automatic assignment would construct the new value before - -- finalizing the old one (because this is safer in general). + -- finalizing the old one. Finalize (Env); - Outer.Ref.all.Refs := Outer.Ref.all.Refs + 1; + Outer.Ref.all.Refs := @ + 1; Top := Top + 1; pragma Assert (Stack (Top).Data.Is_Empty); pragma Assert (Stack (Top).Alias = null); @@ -318,14 +335,14 @@ package body Envs is Set_Binds (Stack (Top).Data, Binds, Exprs); end Replace_With_Sub; - procedure Replace_With_Sub_Macro (Env : in out Ptr; - Binds : in Symbols.Symbol_Array; - Exprs : in Lists.Ptr) + procedure Replace_With_Sub (Env : in out Ptr; + Binds : in Symbols.Symbol_Array; + Exprs : in Mal.T_Array) is begin Replace_With_Sub (Env); - Set_Binds_Macro (Stack (Top).Data, Binds, Exprs); - end Replace_With_Sub_Macro; + Set_Binds (Stack (Top).Data, Binds, Exprs); + end Replace_With_Sub; procedure Set (Env : in Ptr; Key : in Symbols.Ptr; @@ -342,62 +359,31 @@ package body Envs is Varargs : constant Boolean := 1 < Binds'Length and then Binds (Binds'Last - 1) = Symbols.Names.Ampersand; begin - if (if Varargs then - Exprs'Length < Binds'Length - 2 - else - Exprs'Length /= Binds'Length) - then - raise Argument_Error with "function expected " - & Symbols.To_String (Binds) & ", got" - & Integer'Image (Exprs'Length) & " actual parameter(s)"; - end if; + Err.Check ((if Varargs then Binds'Length - 2 <= Exprs'Length + else Exprs'Length = Binds'Length), + "actual parameters do not match formal parameters"); for I in 0 .. Binds'Length - (if Varargs then 3 else 1) loop M.Include (Binds (Binds'First + I), Exprs (Exprs'First + I)); end loop; if Varargs then - M.Include (Binds (Binds'Last), - Lists.List (Exprs (Exprs'First + Binds'Length - 2 .. Exprs'Last))); + M.Include (Binds (Binds'Last), Sequences.List + (Exprs (Exprs'First + Binds'Length - 2 .. Exprs'Last))); end if; end Set_Binds; - procedure Set_Binds_Macro (M : in out HM.Map; - Binds : in Symbols.Symbol_Array; - Exprs : in Lists.Ptr) - is - use type Symbols.Ptr; - Varargs : constant Boolean := 1 < Binds'Length and then - Binds (Binds'Last - 1) = Symbols.Names.Ampersand; - begin - if (if Varargs then - Exprs.Length - 1 < Binds'Length - 2 - else - Exprs.Length - 1 /= Binds'Length) - then - raise Argument_Error with "macro expected " - & Symbols.To_String (Binds) & ", got" - & Integer'Image (Exprs.Length - 1) & " actual parameter(s)"; - end if; - for I in 0 .. Binds'Length - (if Varargs then 3 else 1) loop - M.Include (Binds (Binds'First + I), Exprs.Element (2 + I)); - end loop; - if Varargs then - M.Include (Binds (Binds'Last), Exprs.Slice (Start => Binds'Length)); - end if; - end Set_Binds_Macro; - function Sub (Outer : in Ptr; Binds : in Symbols.Symbol_Array; - Exprs : in Lists.Ptr) return Ptr + Exprs : in Mal.T_Array) return Ptr is R : Stack_Record renames Stack (Outer.Index); begin - R.Refs := R.Refs + 1; + R.Refs := @ + 1; Top := Top + 1; pragma Assert (Stack (Top).Data.Is_Empty); pragma Assert (Stack (Top).Alias = null); Stack (Top) := (Outer_Index => Outer.Index, others => <>); - Set_Binds_Macro (Stack (Top).Data, Binds, Exprs); + Set_Binds (Stack (Top).Data, Binds, Exprs); return (Ada.Finalization.Limited_Controlled with Top); end Sub; @@ -406,7 +392,7 @@ package body Envs is Exprs : in Mal.T_Array) return Ptr is begin - Outer.Ref.all.Refs := Outer.Ref.all.Refs + 1; + Outer.Ref.all.Refs := @ + 1; Top := Top + 1; pragma Assert (Stack (Top).Data.Is_Empty); pragma Assert (Stack (Top).Alias = null); @@ -429,7 +415,7 @@ package body Envs is loop exit when Ref = null; exit when Ref.all.Refs = 0; - Ref.all.Refs := Ref.all.Refs - 1; + Ref.all.Refs := @ - 1; exit when 0 < Ref.all.Refs; exit when Ref.all.Outer = null; -- An alias. Do not free it -- now, it may be useful for another closure. @@ -437,6 +423,7 @@ package body Envs is Tmp : Heap_Access := Ref; begin Ref := Ref.all.Outer; + Allocations := Allocations - 1; Free (Tmp); pragma Unreferenced (Tmp); end; diff --git a/ada.2/envs.ads b/ada.2/envs.ads index 00c58102a7..59bee9c39b 100644 --- a/ada.2/envs.ads +++ b/ada.2/envs.ads @@ -1,6 +1,5 @@ private with Ada.Finalization; -with Types.Lists; with Types.Mal; with Types.Symbols; @@ -26,7 +25,6 @@ package Envs with Elaborate_Body is -- given environment, even during exception propagation. -- Since Ptr is limited with a hidden discriminant, any variable -- must immediately be assigned with one of - -- * Repl (in which case a renaming is probably better), -- * Copy_Pointer, -- * Sub (either from a Ptr or from a Closure_Ptr). -- Usual assignment with reference counting is not provided @@ -42,17 +40,12 @@ package Envs with Elaborate_Body is -- elsewhere. procedure Replace_With_Sub (Env : in out Ptr) with Inline; - -- Equivalent to Env := Sub (Outer => Env, empty Binds and Exprs), - -- except that such an assignment is forbidden for performance - -- reasons. - - procedure Replace_With_Sub_Macro (Env : in out Ptr; - Binds : in Types.Symbols.Symbol_Array; - Exprs : in Types.Lists.Ptr); - -- Equivalent to Env := Sub (Outer => Env, Binds, Expr), except - -- that such an assignment is forbidden for performance reasons. - -- This version is intended for macros: the Exprs argument is a - -- list, and its first element is skipped. + -- for let* + + procedure Replace_With_Sub (Env : in out Ptr; + Binds : in Types.Symbols.Symbol_Array; + Exprs : in Types.Mal.T_Array) with Inline; + -- when expanding macros. procedure Set (Env : in Ptr; Key : in Types.Symbols.Ptr; @@ -63,7 +56,7 @@ package Envs with Elaborate_Body is function Get (Evt : in Ptr; Key : in Types.Symbols.Ptr) return Types.Mal.T; - Unknown_Key : exception; + -- Raises Core.Error_Exception if the key is not found. -- Function closures. @@ -77,28 +70,28 @@ package Envs with Elaborate_Body is function Sub (Outer : in Closure_Ptr'Class; Binds : in Types.Symbols.Symbol_Array; Exprs : in Types.Mal.T_Array) return Ptr; - -- Construct a new environment with the given closure as outer parent. + -- when applying functions without tail call optimization. + -- Construct a new environment with the given outer parent. -- Then call Set with the paired elements of Binds and Exprs, -- handling the "&" special formal parameter if present. - -- May raise Argument_Count. + -- May raise Error. procedure Replace_With_Sub (Env : in out Ptr; Outer : in Closure_Ptr'Class; Binds : in Types.Symbols.Symbol_Array; Exprs : in Types.Mal.T_Array); - -- Equivalent to Env := Sub (Outer, Binds, Expr); except that such - -- an assignment is forbidden for performance reasons. + -- when applying functions with tail call optimization. + -- Equivalent to Env := Sub (Env, Binds, Exprs), except that such + -- an assignment is forbidden or discouraged for performance reasons. function Sub (Outer : in Ptr; Binds : in Types.Symbols.Symbol_Array; - Exprs : in Types.Lists.Ptr) return Ptr; - -- Like Sub above, but dedicated to macros. - -- * The Outer parameter is the current environment, not a closure. - -- * The Exprs argument is a list. - -- * Its first element is skipped. - - -- procedure Dump_Stack (Long : in Boolean := False); - -- For debugging. + Exprs : in Types.Mal.T_Array) return Ptr; + -- when applying macros + + -- Debugging. + procedure Dump_Stack (Long : in Boolean); + procedure Clear_And_Check_Allocations; private diff --git a/ada.2/err.adb b/ada.2/err.adb new file mode 100644 index 0000000000..4d579cc30b --- /dev/null +++ b/ada.2/err.adb @@ -0,0 +1,53 @@ +with Ada.Characters.Latin_1; + +with Printer; + +package body Err is + + use Ada.Strings.Unbounded; + use Types; + + ---------------------------------------------------------------------- + + procedure Add_Trace_Line (Action : in String; + Ast : in Types.Mal.T) + is + begin + Append (Trace, " in "); + Append (Trace, Action); + Append (Trace, ": "); + Printer.Pr_Str (Trace, Ast); + Append (Trace, Ada.Characters.Latin_1.LF); + end Add_Trace_Line; + + procedure Check (Condition : in Boolean; + Message : in String) + is + begin + if not Condition then + Raise_With (Message); + end if; + end Check; + + procedure Raise_With (Message : in String) is + begin + Data := (Kind_String, To_Unbounded_String (Message)); + Set_Unbounded_String (Trace, "Uncaught exception: "); + Append (Trace, Message); + Append (Trace, Ada.Characters.Latin_1.LF); + raise Error; + end Raise_With; + + function Throw (Args : in Mal.T_Array) return Mal.T is + begin + Check (Args'Length = 1, "expected 1 parameter"); + Data := Args (Args'First); + Set_Unbounded_String (Trace, "Uncaught exception: "); + Printer.Pr_Str (Trace, Data); + Append (Trace, Ada.Characters.Latin_1.LF); + -- A raise value is equivalent to a raise statement, but + -- silents a compiler warning. + return raise Error; + end Throw; + +end Err; diff --git a/ada.2/err.ads b/ada.2/err.ads new file mode 100644 index 0000000000..126295c673 --- /dev/null +++ b/ada.2/err.ads @@ -0,0 +1,40 @@ +with Ada.Strings.Unbounded; + +with Types.Mal; + +-- We declare a variable of type Types.Mal.T. +pragma Elaborate (Types.Mal); + +package Err with Elaborate_Body is + + -- Error handling. + + -- Built-in function. + function Throw (Args : in Types.Mal.T_Array) return Types.Mal.T; + + -- Ada exceptions can only carry an immutable String in each + -- occurence, so we require a global variable to store the last + -- exception as a Mal object anyway, and may as well use it for + -- simple string messages. + + Error : exception; + Data : Types.Mal.T; + Trace : Ada.Strings.Unbounded.Unbounded_String; + + -- Convenient shortcuts. + + procedure Raise_With (Message : in String) with Inline, No_Return; + -- Similar to a "raise with Message" Ada statement. + -- Store the message into Data, + -- store the message and "Uncaught exception: " into Trace, + -- then raise Error. + + procedure Add_Trace_Line (Action : in String; + Ast : in Types.Mal.T) with Inline; + -- Appends a line like "Action: Ast" to Trace. + + procedure Check (Condition : in Boolean; + Message : in String) with Inline; + -- Raise_With if Condition fails. + +end Err; diff --git a/ada.2/printer.adb b/ada.2/printer.adb index 11b9b9b5e2..f5ddb4deba 100644 --- a/ada.2/printer.adb +++ b/ada.2/printer.adb @@ -1,9 +1,9 @@ with Ada.Characters.Latin_1; with Types.Atoms; -with Types.Functions; +with Types.Fns; +with Types.Sequences; with Types.Symbols; -with Types.Lists; with Types.Maps; package body Printer is @@ -11,8 +11,9 @@ package body Printer is use Ada.Strings.Unbounded; use Types; - function Pr_Str (Ast : in Mal.T; - Readably : in Boolean := True) return Unbounded_String + procedure Pr_Str (Buffer : in out Unbounded_String; + Ast : in Mal.T; + Readably : in Boolean := True) is procedure Print_Form (Form_Ast : in Mal.T); @@ -21,13 +22,10 @@ package body Printer is -- Helpers for Print_Form. procedure Print_Number (Number : in Integer) with Inline; - procedure Print_List (List : in Lists.Ptr) with Inline; + procedure Print_List (List : in Sequences.Ptr) with Inline; procedure Print_Map (Map : in Maps.Ptr) with Inline; procedure Print_Readably (S : in Unbounded_String) with Inline; - procedure Print_Symbols (List : in Symbols.Symbol_Array) with Inline; - - Buffer : Unbounded_String := Null_Unbounded_String; - -- is appended the result character after character. + procedure Print_Function (Fn : in Fns.Ptr) with Inline; ---------------------------------------------------------------------- @@ -59,11 +57,11 @@ package body Printer is end if; when Kind_List => Append (Buffer, '('); - Print_List (Form_Ast.List); + Print_List (Form_Ast.Sequence); Append (Buffer, ')'); when Kind_Vector => Append (Buffer, '['); - Print_List (Form_Ast.List); + Print_List (Form_Ast.Sequence); Append (Buffer, ']'); when Kind_Map => Append (Buffer, '{'); @@ -71,17 +69,13 @@ package body Printer is Append (Buffer, '}'); when Kind_Builtin | Kind_Builtin_With_Meta => Append (Buffer, "#"); - when Kind_Function => + when Kind_Fn => Append (Buffer, "# "); - Print_Form (Form_Ast.Fn.Ast); + Print_Function (Form_Ast.Fn); Append (Buffer, '>'); when Kind_Macro => Append (Buffer, "# "); - Print_Form (Form_Ast.Fn.Ast); + Print_Function (Form_Ast.Fn); Append (Buffer, '>'); when Kind_Atom => Append (Buffer, "(atom "); @@ -90,22 +84,36 @@ package body Printer is end case; end Print_Form; - procedure Print_List (List : in Lists.Ptr) is + procedure Print_Function (Fn : in Fns.Ptr) is Started : Boolean := False; begin - for I in 1 .. List.Length loop + Append (Buffer, '('); + for Param of Fn.Params loop if Started then Append (Buffer, ' '); else Started := True; end if; - Print_Form (List.Element (I)); + Append (Buffer, Param.To_String); end loop; + Append (Buffer, ") -> "); + Print_Form (Fn.Ast); + end Print_Function; + + procedure Print_List (List : in Sequences.Ptr) is + begin + if 0 < List.Length then + Print_Form (List (1)); + for I in 2 .. List.Length loop + Append (Buffer, ' '); + Print_Form (List (I)); + end loop; + end if; end Print_List; procedure Print_Map (Map : in Maps.Ptr) is procedure Process (Key : in Mal.T; - Element : in Mal.T); + Element : in Mal.T) with Inline; procedure Iterate is new Maps.Iterate (Process); Started : Boolean := False; procedure Process (Key : in Mal.T; @@ -154,24 +162,19 @@ package body Printer is end loop; end Print_Readably; - procedure Print_Symbols (List : in Symbols.Symbol_Array) is - Started : Boolean := False; - begin - for S of List loop - if Started then - Append (Buffer, ' '); - else - Started := True; - end if; - Append (Buffer, S.To_String); - end loop; - end Print_Symbols; - ---------------------------------------------------------------------- begin -- Pr_Str - Print_Form (Form_Ast => Ast); - return Buffer; + Print_Form (Ast); + end Pr_Str; + + function Pr_Str (Ast : in Mal.T; + Readably : in Boolean := True) return Unbounded_String + is + begin + return Buffer : Unbounded_String do + Pr_Str (Buffer, Ast, Readably); + end return; end Pr_Str; end Printer; diff --git a/ada.2/printer.ads b/ada.2/printer.ads index b50d32a60d..02cf6036d5 100644 --- a/ada.2/printer.ads +++ b/ada.2/printer.ads @@ -4,13 +4,15 @@ with Types.Mal; package Printer with Elaborate_Body is + procedure Pr_Str + (Buffer : in out Ada.Strings.Unbounded.Unbounded_String; + Ast : in Types.Mal.T; + Readably : in Boolean := True); + -- Append the text to Buffer. + function Pr_Str (Ast : in Types.Mal.T; Readably : in Boolean := True) return Ada.Strings.Unbounded.Unbounded_String; - - function Img (Ast : in Types.Mal.T) return String - is (Ada.Strings.Unbounded.To_String (Pr_Str (Ast))) with Inline; - -- This form is convenient for reporting errors, but the - -- conversion should be avoided when possible. + -- Return a freshly created unbounded string. end Printer; diff --git a/ada.2/reader.adb b/ada.2/reader.adb index b2ec6b365b..99d5c57c74 100644 --- a/ada.2/reader.adb +++ b/ada.2/reader.adb @@ -1,14 +1,20 @@ with Ada.Characters.Handling; with Ada.Characters.Latin_1; +with Ada.Environment_Variables; with Ada.Strings.Maps.Constants; with Ada.Strings.Unbounded; +with Ada.Text_IO.Unbounded_IO; -with Types.Lists; +with Err; +with Printer; with Types.Maps; +with Types.Sequences; with Types.Symbols.Names; package body Reader is + Debug : constant Boolean := Ada.Environment_Variables.Exists ("dbg_reader"); + use Types; use type Ada.Strings.Maps.Character_Set; @@ -19,12 +25,19 @@ package body Reader is Symbol_Set : constant Ada.Strings.Maps.Character_Set := not (Ignored_Set or Ada.Strings.Maps.To_Set ("""'()@[]^`{}~")); - function Read_Str (Source : in String) return Types.Mal.T is + function Read_Str (Source : in String) return Types.Mal.T_Array is I : Positive := Source'First; - -- Index of the currently considered character. + -- Index in Source of the currently read character. + + -- Big arrays on the stack are faster than repeated dynamic + -- reallocations. This single buffer is used by all Read_List + -- recursive invocations, and by Read_Str. + Buffer : Mal.T_Array (1 .. Source'Length); + B_Last : Natural := Buffer'First - 1; + -- Index in Buffer of the currently written MAL expression. - function Read_Form return Mal.T; + procedure Read_Form; -- The recursive part of Read_Str. -- Helpers for Read_Form: @@ -40,163 +53,167 @@ package body Reader is procedure Skip_Symbol with Inline; -- Check if the current character is allowed in a symbol name. - -- Increment I uuntil it exceeds Source'Last or stops + -- Increment I until it exceeds Source'Last or stops -- designating an allowed character. -- Read_Atom has been merged into the same case/switch -- statement, for clarity and efficiency. - function Read_List (Ending : in Character) return Mal.T_Array + procedure Read_List (Ending : in Character; + Constructor : in not null Mal.Builtin_Ptr) with Inline; - function Read_Quote (Symbol : in Symbols.Ptr) return Mal.T with Inline; - function Read_String return Mal.T with Inline; - function Read_With_Meta return Mal.T with Inline; + procedure Read_Quote (Symbol : in Symbols.Ptr) with Inline; + procedure Read_String with Inline; + procedure Read_With_Meta with Inline; ---------------------------------------------------------------------- - function Read_List (Ending : in Character) return Mal.T_Array is - -- Big arrays on the stack are faster than repeated - -- dynamic reallocations. + procedure Read_List (Ending : in Character; + Constructor : in not null Mal.Builtin_Ptr) is Opening : constant Character := Source (I); - Buffer : Mal.T_Array (I + 1 .. Source'Last); - B_Last : Natural := I; + B_First : constant Positive := B_Last; begin I := I + 1; -- Skip (, [ or {. loop Skip_Ignored; - if Source'Last < I then - raise Reader_Error with "unbalanced '" & Opening & "'"; - end if; + Err.Check (I <= Source'Last, "unbalanced '" & Opening & "'"); exit when Source (I) = Ending; + Read_Form; B_Last := B_Last + 1; - Buffer (B_Last) := Read_Form; end loop; I := I + 1; -- Skip ), ] or }. - return Buffer (Buffer'First .. B_Last); + Buffer (B_First) := Constructor.all (Buffer (B_First .. B_Last - 1)); + B_Last := B_First; end Read_List; - function Read_Quote (Symbol : in Symbols.Ptr) return Mal.T is + procedure Read_Quote (Symbol : in Symbols.Ptr) is begin + Buffer (B_Last) := (Kind_Symbol, Symbol); I := I + 1; -- Skip the initial ' or similar. Skip_Ignored; - if Source'Last < I then - raise Reader_Error with "Incomplete '" & Symbol.To_String & "'"; - end if; - return Lists.List (Mal.T_Array'((Kind_Symbol, Symbol), Read_Form)); + Err.Check (I <= Source'Last, "Incomplete '" & Symbol.To_String & "'"); + B_Last := B_Last + 1; + Read_Form; + B_Last := B_Last - 1; + Buffer (B_Last) := Sequences.List (Buffer (B_Last .. B_Last + 1)); end Read_Quote; - function Read_Form return Mal.T is + procedure Read_Form is + -- After I has been increased, current token is be + -- Source (F .. I - 1). F : Positive; begin case Source (I) is when ')' | ']' | '}' => - raise Reader_Error with "unbalanced '" & Source (I) & "'"; + Err.Raise_With ("unbalanced '" & Source (I) & "'"); when '"' => - return Read_String; + Read_String; when ':' => I := I + 1; F := I; Skip_Symbol; - return (Kind_Keyword, Ada.Strings.Unbounded.To_Unbounded_String - (Source (F .. I - 1))); + Buffer (B_Last) := (Kind_Keyword, + Ada.Strings.Unbounded.To_Unbounded_String + (Source (F .. I - 1))); when '-' => F := I; Skip_Digits; if F + 1 < I then - return (Kind_Number, Integer'Value (Source (F .. I - 1))); + Buffer (B_Last) := (Kind_Number, + Integer'Value (Source (F .. I - 1))); + else + Skip_Symbol; + Buffer (B_Last) := (Kind_Symbol, + Symbols.Constructor (Source (F .. I - 1))); end if; - Skip_Symbol; - return (Kind_Symbol, Symbols.Constructor (Source (F .. I - 1))); when '~' => if I < Source'Last and then Source (I + 1) = '@' then I := I + 1; - return Read_Quote (Symbols.Names.Splice_Unquote); + Read_Quote (Symbols.Names.Splice_Unquote); + else + Read_Quote (Symbols.Names.Unquote); end if; - return Read_Quote (Symbols.Names.Unquote); when '0' .. '9' => F := I; Skip_Digits; - return (Kind_Number, Integer'Value (Source (F .. I - 1))); + Buffer (B_Last) := (Kind_Number, + Integer'Value (Source (F .. I - 1))); when ''' => - return Read_Quote (Symbols.Names.Quote); + Read_Quote (Symbols.Names.Quote); when '`' => - return Read_Quote (Symbols.Names.Quasiquote); + Read_Quote (Symbols.Names.Quasiquote); when '@' => - return Read_Quote (Symbols.Names.Deref); + Read_Quote (Symbols.Names.Deref); when '^' => - return Read_With_Meta; + Read_With_Meta; when '(' => - return Lists.List (Read_List (')')); + Read_List (')', Sequences.List'Access); when '[' => - return Lists.Vector (Read_List (']')); + Read_List (']', Sequences.Vector'Access); when '{' => - return Maps.Hash_Map (Read_List ('}')); + Read_List ('}', Maps.Hash_Map'Access); when others => F := I; Skip_Symbol; if Source (F .. I - 1) = "false" then - return (Kind_Boolean, False); + Buffer (B_Last) := (Kind_Boolean, False); elsif Source (F .. I - 1) = "nil" then - return Mal.Nil; + Buffer (B_Last) := Mal.Nil; elsif Source (F .. I - 1) = "true" then - return (Kind_Boolean, True); + Buffer (B_Last) := (Kind_Boolean, True); + else + Buffer (B_Last) := (Kind_Symbol, + Symbols.Constructor (Source (F .. I - 1))); end if; - return (Kind_Symbol, Symbols.Constructor (Source (F .. I - 1))); end case; + if Debug then + Ada.Text_IO.Put ("reader: "); + Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Buffer + (B_Last))); + end if; end Read_Form; - function Read_String return Mal.T is + procedure Read_String is use Ada.Strings.Unbounded; - S : Unbounded_String; begin + Buffer (B_Last) := (Kind_String, Null_Unbounded_String); loop I := I + 1; - if Source'Last < I then - raise Reader_Error with "unbalanced '""'"; - end if; + Err.Check (I <= Source'Last, "unbalanced '""'"); case Source (I) is when '"' => exit; when '\' => I := I + 1; - if Source'Last < I then - raise Reader_Error with "unbalanced '""'"; - end if; + Err.Check (I <= Source'Last, "unbalanced '""'"); case Source (I) is when '\' | '"' => - Append (S, Source (I)); + Append (Buffer (B_Last).S, Source (I)); when 'n' => - Append (S, Ada.Characters.Latin_1.LF); + Append (Buffer (B_Last).S, Ada.Characters.Latin_1.LF); when others => - Append (S, Source (I - 1 .. I)); + Append (Buffer (B_Last).S, Source (I - 1 .. I)); end case; when others => - Append (S, Source (I)); + Append (Buffer (B_Last).S, Source (I)); end case; end loop; I := I + 1; -- Skip closing double quote. - return (Kind_String, S); end Read_String; - function Read_With_Meta return Mal.T is - Args : Mal.T_Array (1 .. 3); + procedure Read_With_Meta is begin - Args (1) := (Kind_Symbol, Symbols.Names.With_Meta); - - I := I + 1; -- Skip the initial ^. - - Skip_Ignored; - if Source'Last < I then - raise Reader_Error with "incomplete 'with-meta'"; - end if; - Args (3) := Read_Form; - - Skip_Ignored; - if Source'Last < I then - raise Reader_Error with "incomplete 'with-meta'"; - end if; - Args (2) := Read_Form; - - return Lists.List (Args); + I := I + 1; -- Skip the initial ^. + for Argument in 1 .. 2 loop + Skip_Ignored; + Err.Check (I <= Source'Last, "Incomplete 'with-meta'"); + Read_Form; + B_Last := B_Last + 1; + end loop; + -- Replace (metadata data) with (with-meta data metadata). + B_Last := B_Last - 2; + Buffer (B_Last + 2) := Buffer (B_Last); + Buffer (B_Last) := (Kind_Symbol, Symbols.Names.With_Meta); + Buffer (B_Last) := Sequences.List (Buffer (B_Last .. B_Last + 2)); end Read_With_Meta; procedure Skip_Digits is @@ -204,7 +221,8 @@ package body Reader is begin loop I := I + 1; - exit when Source'Last < I or else not Is_Digit (Source (I)); + exit when Source'Last < I; + exit when not Is_Digit (Source (I)); end loop; end Skip_Digits; @@ -236,20 +254,14 @@ package body Reader is ---------------------------------------------------------------------- - Result : Mal.T; begin -- Read_Str - Skip_Ignored; - if Source'Last < I then - raise Empty_Source with "attempting to read an empty line"; - end if; - Result := Read_Form; - Skip_Ignored; - if I <= Source'Last then - raise Reader_Error - with "unexpected characters '" & Source (I .. Source'Last) - & "' after '" & Source (Source'First .. I - 1) & '''; - end if; - return Result; + loop + Skip_Ignored; + exit when Source'Last < I; + B_Last := B_Last + 1; + Read_Form; + end loop; + return Buffer (Buffer'First .. B_Last); end Read_Str; end Reader; diff --git a/ada.2/reader.ads b/ada.2/reader.ads index 0d9d185b91..88a6ca4df6 100644 --- a/ada.2/reader.ads +++ b/ada.2/reader.ads @@ -2,9 +2,9 @@ with Types.Mal; package Reader with Elaborate_Body is - function Read_Str (Source : in String) return Types.Mal.T; - - Empty_Source : exception; - Reader_Error : exception; + function Read_Str (Source : in String) return Types.Mal.T_Array; + -- The language does not explicitly define what happens when the + -- input string contains more than one expression. + -- This implementation returns all of them. end Reader; diff --git a/ada.2/step1_read_print.adb b/ada.2/step1_read_print.adb index 67b3277621..f0e3ad6878 100644 --- a/ada.2/step1_read_print.adb +++ b/ada.2/step1_read_print.adb @@ -1,16 +1,22 @@ -with Ada.Exceptions; with Ada.Text_IO.Unbounded_IO; +with Err; with Printer; with Reader; with Readline; +with Types.Atoms; +with Types.Builtins; +with Types.Fns; with Types.Mal; +with Types.Maps; +with Types.Sequences; +with Types.Symbols; procedure Step1_Read_Print is use Types; - function Read return Mal.T with Inline; + function Read return Mal.T_Array with Inline; function Eval (Ast : in Mal.T) return Mal.T; @@ -27,11 +33,14 @@ procedure Step1_Read_Print is Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + function Read return Mal.T_Array + is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep is begin - Print (Eval (Read)); + for Expression of Read loop + Print (Eval (Expression)); + end loop; end Rep; ---------------------------------------------------------------------- @@ -43,12 +52,18 @@ begin exception when Readline.End_Of_File => exit; - when Reader.Empty_Source => - null; - when E : Reader.Reader_Error => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. + when Err.Error => + Ada.Text_IO.Unbounded_IO.Put (Err.Trace); end; + -- Other exceptions are really unexpected. end loop; Ada.Text_IO.New_Line; + -- If assertions are enabled, check deallocations. + Err.Data := Mal.Nil; -- Remove references to other packages + pragma Debug (Atoms.Check_Allocations); + pragma Debug (Builtins.Check_Allocations); + pragma Debug (Fns.Check_Allocations); + pragma Debug (Maps.Check_Allocations); + pragma Debug (Sequences.Check_Allocations); + pragma Debug (Symbols.Check_Allocations); end Step1_Read_Print; diff --git a/ada.2/step2_eval.adb b/ada.2/step2_eval.adb index 101734b46c..a8928f855b 100644 --- a/ada.2/step2_eval.adb +++ b/ada.2/step2_eval.adb @@ -1,17 +1,24 @@ +with Ada.Environment_Variables; with Ada.Containers.Indefinite_Hashed_Maps; -with Ada.Exceptions; with Ada.Strings.Hash; with Ada.Text_IO.Unbounded_IO; +with Err; with Printer; with Reader; with Readline; -with Types.Lists; +with Types.Atoms; +with Types.Builtins; +with Types.Fns; with Types.Mal; with Types.Maps; +with Types.Sequences; +with Types.Symbols; procedure Step2_Eval is + Dbgeval : constant Boolean := Ada.Environment_Variables.Exists ("dbgeval"); + use Types; package Envs is new Ada.Containers.Indefinite_Hashed_Maps @@ -20,9 +27,8 @@ procedure Step2_Eval is Hash => Ada.Strings.Hash, Equivalent_Keys => "=", "=" => Mal."="); - Unknown_Key : exception; - function Read return Mal.T with Inline; + function Read return Mal.T_Array with Inline; function Eval (Ast : in Mal.T; Env : in Envs.Map) return Mal.T; @@ -35,8 +41,8 @@ procedure Step2_Eval is with function Ada_Operator (Left, Right : in Integer) return Integer; function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T; - function Eval_List_Elts is new Lists.Generic_Eval (Envs.Map, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Map, Eval); + function Eval_Seq_Elts is new Sequences.Generic_Eval (Envs.Map, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Map, Eval); ---------------------------------------------------------------------- @@ -45,39 +51,37 @@ procedure Step2_Eval is is First : Mal.T; begin - -- Ada.Text_IO.New_Line; - -- Ada.Text_IO.Put ("EVAL: "); - -- Print (Ast); + if Dbgeval then + Ada.Text_IO.New_Line; + Ada.Text_IO.Put ("EVAL: "); + Print (Ast); + end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function - | Kind_Builtin_With_Meta | Kind_Builtin => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key + | Kind_Macro | Kind_Function => return Ast; when Kind_Symbol => declare S : constant String := Ast.Symbol.To_String; C : constant Envs.Cursor := Env.Find (S); begin - if Envs.Has_Element (C) then - return (Kind_Builtin, Envs.Element (C)); - else - -- The predefined message does not pass tests. - raise Unknown_Key with "'" & S & "' not found"; - end if; + -- The predefined error message does not pass tests. + Err.Check (Envs.Has_Element (C), "'" & S & "' not found"); + return (Kind_Builtin, Envs.Element (C)); end; when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); + return (Kind_Vector, Eval_Seq_Elts (Ast.Sequence, Env)); when Kind_List => null; end case; -- Ast is a list. - if Ast.List.Length = 0 then + if Ast.Sequence.Length = 0 then return Ast; end if; - First := Eval (Ast.List.Element (1), Env); + First := Eval (Ast.Sequence (1), Env); -- Apply phase. -- Ast is a non-empty list, @@ -85,16 +89,20 @@ procedure Step2_Eval is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; return First.Builtin.all (Args); end; when others => - raise Argument_Error with "cannot call " & Printer.Img (First); + Err.Raise_With ("first element must be a function"); end case; + exception + when Err.Error => + Err.Add_Trace_Line ("eval", Ast); + raise; end Eval; function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T @@ -106,11 +114,14 @@ procedure Step2_Eval is Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + function Read return Mal.T_Array + is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Map) is begin - Print (Eval (Read, Env)); + for Expression of Read loop + Print (Eval (Expression, Env)); + end loop; end Rep; ---------------------------------------------------------------------- @@ -132,12 +143,18 @@ begin exception when Readline.End_Of_File => exit; - when Reader.Empty_Source => - null; - when E : Reader.Reader_Error | Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. + when Err.Error => + Ada.Text_IO.Unbounded_IO.Put (Err.Trace); end; + -- Other exceptions are really unexpected. end loop; Ada.Text_IO.New_Line; + -- If assertions are enabled, check deallocations. + Err.Data := Mal.Nil; -- Remove references to other packages + pragma Debug (Atoms.Check_Allocations); + pragma Debug (Builtins.Check_Allocations); + pragma Debug (Fns.Check_Allocations); + pragma Debug (Maps.Check_Allocations); + pragma Debug (Sequences.Check_Allocations); + pragma Debug (Symbols.Check_Allocations); end Step2_Eval; diff --git a/ada.2/step3_env.adb b/ada.2/step3_env.adb index 9a4294f31c..1585dee9b1 100644 --- a/ada.2/step3_env.adb +++ b/ada.2/step3_env.adb @@ -1,20 +1,30 @@ -with Ada.Exceptions; +with Ada.Environment_Variables; with Ada.Text_IO.Unbounded_IO; with Envs; +with Err; with Printer; with Reader; with Readline; -with Types.Lists; +with Types.Atoms; +with Types.Builtins; +with Types.Fns; with Types.Mal; with Types.Maps; +with Types.Sequences; with Types.Symbols.Names; procedure Step3_Env is + Dbgenv1 : constant Boolean := Ada.Environment_Variables.Exists ("dbgenv1"); + Dbgenv0 : constant Boolean + := Dbgenv1 or Ada.Environment_Variables.Exists ("dbgenv0"); + Dbgeval : constant Boolean + := Dbgenv0 or Ada.Environment_Variables.Exists ("dbgeval"); + use Types; - function Read return Mal.T with Inline; + function Read return Mal.T_Array with Inline; function Eval (Ast : in Mal.T; Env : in Envs.Ptr) return Mal.T; @@ -27,8 +37,8 @@ procedure Step3_Env is with function Ada_Operator (Left, Right : in Integer) return Integer; function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T; - function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + function Eval_Seq_Elts is new Sequences.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); ---------------------------------------------------------------------- @@ -38,67 +48,64 @@ procedure Step3_Env is use type Symbols.Ptr; First : Mal.T; begin - -- Ada.Text_IO.New_Line; - -- Ada.Text_IO.Put ("EVAL: "); - -- Print (Ast); - -- Envs.Dump_Stack; + if Dbgeval then + Ada.Text_IO.New_Line; + Ada.Text_IO.Put ("EVAL: "); + Print (Ast); + if Dbgenv0 then + Envs.Dump_Stack (Dbgenv1); + end if; + end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function - | Kind_Builtin_With_Meta | Kind_Builtin => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key + | Kind_Macro | Kind_Function => return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); + return (Kind_Vector, Eval_Seq_Elts (Ast.Sequence, Env)); when Kind_List => null; end case; -- Ast is a list. - if Ast.List.Length = 0 then + if Ast.Sequence.Length = 0 then return Ast; end if; - First := Ast.List.Element (1); + First := Ast.Sequence (1); -- Special forms -- Ast is a non-empty list, First is its first element. case First.Kind is when Kind_Symbol => if First.Symbol = Symbols.Names.Def then - if Ast.List.Length /= 3 then - raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.List.Element (2).Kind /= Kind_Symbol then - raise Argument_Error with "def!: arg 1 must be a symbol"; - end if; - return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do - Env.Set (Ast.List.Element (2).Symbol, R); + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind = Kind_Symbol, + "parameter 1 must be a symbol"); + return R : constant Mal.T := Eval (Ast.Sequence (3), Env) do + Env.Set (Ast.Sequence (2).Symbol, R); end return; elsif First.Symbol = Symbols.Names.Let then - if Ast.List.Length /= 3 then - raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "let*: expects a list or vector"; - end if; + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind in Kind_Sequence, + "parameter 1 must be a sequence"); declare - Bindings : constant Lists.Ptr := Ast.List.Element (2).List; + Bindings : constant Sequences.Ptr := Ast.Sequence (2).Sequence; -- This curious syntax is useful for later steps. New_Env : Envs.Ptr := Env.Copy_Pointer; begin + Err.Check (Bindings.Length mod 2 = 0, + "parameter 1 must have an even length"); New_Env.Replace_With_Sub; - if Bindings.Length mod 2 /= 0 then - raise Argument_Error with "let*: odd number of bindings"; - end if; for I in 1 .. Bindings.Length / 2 loop - if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then - raise Argument_Error with "let*: keys must be symbols"; - end if; - New_Env.Set (Bindings.Element (2 * I - 1).Symbol, - Eval (Bindings.Element (2 * I), New_Env)); + Err.Check (Bindings (2 * I - 1).Kind = Kind_Symbol, + "binding keys must be symbols"); + New_Env.Set (Bindings (2 * I - 1).Symbol, + Eval (Bindings (2 * I), New_Env)); end loop; - return Eval (Ast.List.Element (3), New_Env); + return Eval (Ast.Sequence (3), New_Env); end; else First := Eval (First, Env); @@ -113,16 +120,20 @@ procedure Step3_Env is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; return First.Builtin.all (Args); end; when others => - raise Argument_Error with "cannot call " & Printer.Img (First); + Err.Raise_With ("first element must be a function"); end case; + exception + when Err.Error => + Err.Add_Trace_Line ("eval", Ast); + raise; end Eval; function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T @@ -134,11 +145,14 @@ procedure Step3_Env is Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + function Read return Mal.T_Array + is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is begin - Print (Eval (Read, Env)); + for Expression of Read loop + Print (Eval (Expression, Env)); + end loop; end Rep; ---------------------------------------------------------------------- @@ -164,12 +178,19 @@ begin exception when Readline.End_Of_File => exit; - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. + when Err.Error => + Ada.Text_IO.Unbounded_IO.Put (Err.Trace); end; + -- Other exceptions are really unexpected. end loop; Ada.Text_IO.New_Line; + -- If assertions are enabled, check deallocations. + Err.Data := Mal.Nil; -- Remove references to other packages + pragma Debug (Envs.Clear_And_Check_Allocations); + pragma Debug (Atoms.Check_Allocations); + pragma Debug (Builtins.Check_Allocations); + pragma Debug (Fns.Check_Allocations); + pragma Debug (Maps.Check_Allocations); + pragma Debug (Sequences.Check_Allocations); + pragma Debug (Symbols.Check_Allocations); end Step3_Env; diff --git a/ada.2/step4_if_fn_do.adb b/ada.2/step4_if_fn_do.adb index 342f9dd737..d8c1ca2a2f 100644 --- a/ada.2/step4_if_fn_do.adb +++ b/ada.2/step4_if_fn_do.adb @@ -1,23 +1,33 @@ -with Ada.Exceptions; +with Ada.Environment_Variables; with Ada.Text_IO.Unbounded_IO; with Core; with Envs; +with Err; with Eval_Cb; with Printer; with Reader; with Readline; -with Types.Functions; -with Types.Lists; +with Types.Atoms; +with Types.Builtins; +with Types.Fns; with Types.Mal; with Types.Maps; +with Types.Sequences; with Types.Symbols.Names; procedure Step4_If_Fn_Do is + Dbgenv1 : constant Boolean := Ada.Environment_Variables.Exists ("dbgenv1"); + Dbgenv0 : constant Boolean + := Dbgenv1 or Ada.Environment_Variables.Exists ("dbgenv0"); + Dbgeval : constant Boolean + := Dbgenv0 or Ada.Environment_Variables.Exists ("dbgeval"); + use Types; + use type Mal.T; - function Read return Mal.T with Inline; + function Read return Mal.T_Array with Inline; function Eval (Ast : in Mal.T; Env : in Envs.Ptr) return Mal.T; @@ -26,13 +36,12 @@ procedure Step4_If_Fn_Do is procedure Rep (Env : in Envs.Ptr) with Inline; - function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + function Eval_Seq_Elts is new Sequences.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); - -- Procedural form of Eval. - -- Convenient when the result of eval is of no interest. - procedure Eval_P (Ast : in Mal.T; - Env : in Envs.Ptr) with Inline; + procedure Exec (Script : in String; + Env : in Envs.Ptr) with Inline; + -- Read the script, eval its elements, but ignore the result. ---------------------------------------------------------------------- @@ -42,107 +51,86 @@ procedure Step4_If_Fn_Do is use type Symbols.Ptr; First : Mal.T; begin - -- Ada.Text_IO.New_Line; - -- Ada.Text_IO.Put ("EVAL: "); - -- Print (Ast); - -- Envs.Dump_Stack; + if Dbgeval then + Ada.Text_IO.New_Line; + Ada.Text_IO.Put ("EVAL: "); + Print (Ast); + if Dbgenv0 then + Envs.Dump_Stack (Dbgenv1); + end if; + end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function - | Kind_Builtin_With_Meta | Kind_Builtin => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key + | Kind_Macro | Kind_Function => return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); + return (Kind_Vector, Eval_Seq_Elts (Ast.Sequence, Env)); when Kind_List => null; end case; -- Ast is a list. - if Ast.List.Length = 0 then + if Ast.Sequence.Length = 0 then return Ast; end if; - First := Ast.List.Element (1); + First := Ast.Sequence (1); -- Special forms -- Ast is a non-empty list, First is its first element. case First.Kind is when Kind_Symbol => if First.Symbol = Symbols.Names.Def then - if Ast.List.Length /= 3 then - raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.List.Element (2).Kind /= Kind_Symbol then - raise Argument_Error with "def!: arg 1 must be a symbol"; - end if; - return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do - Env.Set (Ast.List.Element (2).Symbol, R); + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind = Kind_Symbol, + "parameter 1 must be a symbol"); + return R : constant Mal.T := Eval (Ast.Sequence (3), Env) do + Env.Set (Ast.Sequence (2).Symbol, R); end return; - elsif First.Symbol = Symbols.Names.Mal_Do then - if Ast.List.Length = 1 then - raise Argument_Error with "do: expects at least 1 argument"; - end if; - for I in 2 .. Ast.List.Length - 1 loop - Eval_P (Ast.List.Element (I), Env); - end loop; - return Eval (Ast.List.Element (Ast.List.Length), Env); + -- do is a built-in function, shortening this test cascade. elsif First.Symbol = Symbols.Names.Fn then - if Ast.List.Length /= 3 then - raise Argument_Error with "fn*: expects 3 arguments"; - elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "fn*: arg 1 must be a list or vector"; - elsif (for some F in 1 .. Ast.List.Element (2).List.Length => - Ast.List.Element (2).List.Element (F).Kind /= Kind_Symbol) - then - raise Argument_Error with "fn*: arg 2 must contain symbols"; - end if; - return Functions.New_Function (Params => Ast.List.Element (2).List, - Ast => Ast.List.Element (3), - Env => Env.New_Closure); + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind in Kind_Sequence, + "parameter 1 must be a sequence"); + return Fns.New_Function (Params => Ast.Sequence (2).Sequence, + Ast => Ast.Sequence (3), + Env => Env.New_Closure); elsif First.Symbol = Symbols.Names.Mal_If then - if Ast.List.Length not in 3 .. 4 then - raise Argument_Error with "if: expects 2 or 3 arguments"; - end if; + Err.Check (Ast.Sequence.Length in 3 .. 4, + "expected 2 or 3 parameters"); declare - Test : constant Mal.T := Eval (Ast.List.Element (2), Env); + Test : constant Mal.T := Eval (Ast.Sequence (2), Env); begin - if (case Test.Kind is - when Kind_Nil => False, - when Kind_Boolean => Test.Ada_Boolean, - when others => True) - then - return Eval (Ast.List.Element (3), Env); - elsif Ast.List.Length = 3 then + if Test /= Mal.Nil and Test /= (Kind_Boolean, False) then + return Eval (Ast.Sequence (3), Env); + elsif Ast.Sequence.Length = 3 then return Mal.Nil; else - return Eval (Ast.List.Element (4), Env); + return Eval (Ast.Sequence (4), Env); end if; end; elsif First.Symbol = Symbols.Names.Let then - if Ast.List.Length /= 3 then - raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "let*: expects a list or vector"; - end if; + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind in Kind_Sequence, + "parameter 1 must be a sequence"); declare - Bindings : constant Lists.Ptr := Ast.List.Element (2).List; + Bindings : constant Sequences.Ptr := Ast.Sequence (2).Sequence; -- This curious syntax is useful for later steps. New_Env : Envs.Ptr := Env.Copy_Pointer; begin + Err.Check (Bindings.Length mod 2 = 0, + "parameter 1 must have an even length"); New_Env.Replace_With_Sub; - if Bindings.Length mod 2 /= 0 then - raise Argument_Error with "let*: odd number of bindings"; - end if; for I in 1 .. Bindings.Length / 2 loop - if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then - raise Argument_Error with "let*: keys must be symbols"; - end if; - New_Env.Set (Bindings.Element (2 * I - 1).Symbol, - Eval (Bindings.Element (2 * I), New_Env)); + Err.Check (Bindings (2 * I - 1).Kind = Kind_Symbol, + "binding keys must be symbols"); + New_Env.Set (Bindings (2 * I - 1).Symbol, + Eval (Bindings (2 * I), New_Env)); end loop; - return Eval (Ast.List.Element (3), New_Env); + return Eval (Ast.Sequence (3), New_Env); end; else First := Eval (First, Env); @@ -157,74 +145,88 @@ procedure Step4_If_Fn_Do is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; return First.Builtin.all (Args); end; - when Kind_Function => + when Kind_Fn => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; return First.Fn.Apply (Args); end; when others => - raise Argument_Error with "cannot call " & Printer.Img (First); + Err.Raise_With ("first element must be a function"); end case; + exception + when Err.Error => + Err.Add_Trace_Line ("eval", Ast); + raise; end Eval; - procedure Eval_P (Ast : in Mal.T; - Env : in Envs.Ptr) + procedure Exec (Script : in String; + Env : in Envs.Ptr) is - Result : constant Mal.T := Eval (Ast, Env); + Result : Mal.T; begin + for Expression of Reader.Read_Str (Script) loop + Result := Eval (Expression, Env); + end loop; pragma Unreferenced (Result); - end Eval_P; + end Exec; procedure Print (Ast : in Mal.T) is begin Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + function Read return Mal.T_Array + is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is begin - Print (Eval (Read, Env)); + for Expression of Read loop + Print (Eval (Expression, Env)); + end loop; end Rep; ---------------------------------------------------------------------- - Startup : constant String := "(do " - & "(def! not (fn* (a) (if a false true)))" - & ")"; + Startup : constant String + := "(def! not (fn* (a) (if a false true)))"; Repl : Envs.Ptr renames Envs.Repl; begin -- Show the Eval function to other packages. Eval_Cb.Cb := Eval'Unrestricted_Access; -- Add Core functions into the top environment. - for Binding of Core.Ns loop - Repl.Set (Binding.Symbol, (Kind_Builtin, Binding.Builtin)); - end loop; + Core.NS_Add_To_Repl; -- Native startup procedure. - Eval_P (Reader.Read_Str (Startup), Repl); + Exec (Startup, Repl); loop begin Rep (Repl); exception when Readline.End_Of_File => exit; - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. + when Err.Error => + Ada.Text_IO.Unbounded_IO.Put (Err.Trace); end; + -- Other exceptions are really unexpected. end loop; Ada.Text_IO.New_Line; + -- If assertions are enabled, check deallocations. + Err.Data := Mal.Nil; -- Remove references to other packages + pragma Debug (Envs.Clear_And_Check_Allocations); + pragma Debug (Atoms.Check_Allocations); + pragma Debug (Builtins.Check_Allocations); + pragma Debug (Fns.Check_Allocations); + pragma Debug (Maps.Check_Allocations); + pragma Debug (Sequences.Check_Allocations); + pragma Debug (Symbols.Check_Allocations); end Step4_If_Fn_Do; diff --git a/ada.2/step5_tco.adb b/ada.2/step5_tco.adb index 2e109e393a..540e5a4f49 100644 --- a/ada.2/step5_tco.adb +++ b/ada.2/step5_tco.adb @@ -1,23 +1,33 @@ -with Ada.Exceptions; +with Ada.Environment_Variables; with Ada.Text_IO.Unbounded_IO; with Core; with Envs; +with Err; with Eval_Cb; with Printer; with Reader; with Readline; -with Types.Functions; -with Types.Lists; +with Types.Atoms; +with Types.Builtins; +with Types.Fns; with Types.Mal; with Types.Maps; +with Types.Sequences; with Types.Symbols.Names; procedure Step5_Tco is + Dbgenv1 : constant Boolean := Ada.Environment_Variables.Exists ("dbgenv1"); + Dbgenv0 : constant Boolean + := Dbgenv1 or Ada.Environment_Variables.Exists ("dbgenv0"); + Dbgeval : constant Boolean + := Dbgenv0 or Ada.Environment_Variables.Exists ("dbgeval"); + use Types; + use type Mal.T; - function Read return Mal.T with Inline; + function Read return Mal.T_Array with Inline; function Eval (Ast0 : in Mal.T; Env0 : in Envs.Ptr) return Mal.T; @@ -26,13 +36,12 @@ procedure Step5_Tco is procedure Rep (Env : in Envs.Ptr) with Inline; - function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + function Eval_Seq_Elts is new Sequences.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); - -- Procedural form of Eval. - -- Convenient when the result of eval is of no interest. - procedure Eval_P (Ast : in Mal.T; - Env : in Envs.Ptr) with Inline; + procedure Exec (Script : in String; + Env : in Envs.Ptr) with Inline; + -- Read the script, eval its elements, but ignore the result. ---------------------------------------------------------------------- @@ -47,108 +56,86 @@ procedure Step5_Tco is First : Mal.T; begin <> - -- Ada.Text_IO.New_Line; - -- Ada.Text_IO.Put ("EVAL: "); - -- Print (Ast); - -- Envs.Dump_Stack; + if Dbgeval then + Ada.Text_IO.New_Line; + Ada.Text_IO.Put ("EVAL: "); + Print (Ast); + if Dbgenv0 then + Envs.Dump_Stack (Dbgenv1); + end if; + end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function - | Kind_Builtin_With_Meta | Kind_Builtin => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key + | Kind_Macro | Kind_Function => return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); + return (Kind_Vector, Eval_Seq_Elts (Ast.Sequence, Env)); when Kind_List => null; end case; -- Ast is a list. - if Ast.List.Length = 0 then + if Ast.Sequence.Length = 0 then return Ast; end if; - First := Ast.List.Element (1); + First := Ast.Sequence (1); -- Special forms -- Ast is a non-empty list, First is its first element. case First.Kind is when Kind_Symbol => if First.Symbol = Symbols.Names.Def then - if Ast.List.Length /= 3 then - raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.List.Element (2).Kind /= Kind_Symbol then - raise Argument_Error with "def!: arg 1 must be a symbol"; - end if; - return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do - Env.Set (Ast.List.Element (2).Symbol, R); + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind = Kind_Symbol, + "parameter 1 must be a symbol"); + return R : constant Mal.T := Eval (Ast.Sequence (3), Env) do + Env.Set (Ast.Sequence (2).Symbol, R); end return; - elsif First.Symbol = Symbols.Names.Mal_Do then - if Ast.List.Length = 1 then - raise Argument_Error with "do: expects at least 1 argument"; - end if; - for I in 2 .. Ast.List.Length - 1 loop - Eval_P (Ast.List.Element (I), Env); - end loop; - Ast := Ast.List.Element (Ast.List.Length); - goto Restart; + -- do is a built-in function, shortening this test cascade. elsif First.Symbol = Symbols.Names.Fn then - if Ast.List.Length /= 3 then - raise Argument_Error with "fn*: expects 3 arguments"; - elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "fn*: arg 1 must be a list or vector"; - elsif (for some F in 1 .. Ast.List.Element (2).List.Length => - Ast.List.Element (2).List.Element (F).Kind /= Kind_Symbol) - then - raise Argument_Error with "fn*: arg 2 must contain symbols"; - end if; - return Functions.New_Function (Params => Ast.List.Element (2).List, - Ast => Ast.List.Element (3), - Env => Env.New_Closure); + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind in Kind_Sequence, + "parameter 1 must be a sequence"); + return Fns.New_Function (Params => Ast.Sequence (2).Sequence, + Ast => Ast.Sequence (3), + Env => Env.New_Closure); elsif First.Symbol = Symbols.Names.Mal_If then - if Ast.List.Length not in 3 .. 4 then - raise Argument_Error with "if: expects 2 or 3 arguments"; - end if; + Err.Check (Ast.Sequence.Length in 3 .. 4, + "expected 2 or 3 parameters"); declare - Test : constant Mal.T := Eval (Ast.List.Element (2), Env); + Test : constant Mal.T := Eval (Ast.Sequence (2), Env); begin - if (case Test.Kind is - when Kind_Nil => False, - when Kind_Boolean => Test.Ada_Boolean, - when others => True) - then - Ast := Ast.List.Element (3); + if Test /= Mal.Nil and Test /= (Kind_Boolean, False) then + Ast := Ast.Sequence (3); goto Restart; - elsif Ast.List.Length = 3 then + elsif Ast.Sequence.Length = 3 then return Mal.Nil; else - Ast := Ast.List.Element (4); + Ast := Ast.Sequence (4); goto Restart; end if; end; elsif First.Symbol = Symbols.Names.Let then - if Ast.List.Length /= 3 then - raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "let*: expects a list or vector"; - end if; + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind in Kind_Sequence, + "parameter 1 must be a sequence"); declare - Bindings : constant Lists.Ptr := Ast.List.Element (2).List; + Bindings : constant Sequences.Ptr := Ast.Sequence (2).Sequence; begin - if Bindings.Length mod 2 /= 0 then - raise Argument_Error with "let*: odd number of bindings"; - end if; + Err.Check (Bindings.Length mod 2 = 0, + "parameter 1 must have an even length"); Env.Replace_With_Sub; for I in 1 .. Bindings.Length / 2 loop - if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then - raise Argument_Error with "let*: keys must be symbols"; - end if; - Env.Set (Bindings.Element (2 * I - 1).Symbol, - Eval (Bindings.Element (2 * I), Env)); + Err.Check (Bindings (2 * I - 1).Kind = Kind_Symbol, + "binding keys must be symbols"); + Env.Set (Bindings (2 * I - 1).Symbol, + Eval (Bindings (2 * I), Env)); end loop; - Ast := Ast.List.Element (3); + Ast := Ast.Sequence (3); goto Restart; end; else @@ -156,13 +143,12 @@ procedure Step5_Tco is -- except that we already know enough to spare a recursive call. First := Env.Get (First.Symbol); end if; - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function - | Kind_Builtin_With_Meta | Kind_Builtin => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key + | Kind_Macro | Kind_Function => -- Equivalent to First := Eval (First, Env) -- except that we already know enough to spare a recursive call. null; - when Kind_List | Kind_Vector | Kind_Map => + when Kind_Sequence | Kind_Map => -- Lists are definitely worth a recursion, and the two other -- cases should be rare (they will report an error later). First := Eval (First, Env); @@ -174,19 +160,19 @@ procedure Step5_Tco is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; return First.Builtin.all (Args); end; - when Kind_Function => + when Kind_Fn => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; Env.Replace_With_Sub (Outer => First.Fn.Env, Binds => First.Fn.Params, @@ -195,57 +181,71 @@ procedure Step5_Tco is goto Restart; end; when others => - raise Argument_Error with "cannot call " & Printer.Img (First); + Err.Raise_With ("first element must be a function"); end case; + exception + when Err.Error => + Err.Add_Trace_Line ("eval", Ast); + raise; end Eval; - procedure Eval_P (Ast : in Mal.T; - Env : in Envs.Ptr) + procedure Exec (Script : in String; + Env : in Envs.Ptr) is - Result : constant Mal.T := Eval (Ast, Env); + Result : Mal.T; begin + for Expression of Reader.Read_Str (Script) loop + Result := Eval (Expression, Env); + end loop; pragma Unreferenced (Result); - end Eval_P; + end Exec; procedure Print (Ast : in Mal.T) is begin Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + function Read return Mal.T_Array + is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is begin - Print (Eval (Read, Env)); + for Expression of Read loop + Print (Eval (Expression, Env)); + end loop; end Rep; ---------------------------------------------------------------------- - Startup : constant String := "(do " - & "(def! not (fn* (a) (if a false true)))" - & ")"; + Startup : constant String + := "(def! not (fn* (a) (if a false true)))"; Repl : Envs.Ptr renames Envs.Repl; begin -- Show the Eval function to other packages. Eval_Cb.Cb := Eval'Unrestricted_Access; -- Add Core functions into the top environment. - for Binding of Core.Ns loop - Repl.Set (Binding.Symbol, (Kind_Builtin, Binding.Builtin)); - end loop; + Core.NS_Add_To_Repl; -- Native startup procedure. - Eval_P (Reader.Read_Str (Startup), Repl); + Exec (Startup, Repl); loop begin Rep (Repl); exception when Readline.End_Of_File => exit; - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. + when Err.Error => + Ada.Text_IO.Unbounded_IO.Put (Err.Trace); end; + -- Other exceptions are really unexpected. end loop; Ada.Text_IO.New_Line; + -- If assertions are enabled, check deallocations. + Err.Data := Mal.Nil; -- Remove references to other packages + pragma Debug (Envs.Clear_And_Check_Allocations); + pragma Debug (Atoms.Check_Allocations); + pragma Debug (Builtins.Check_Allocations); + pragma Debug (Fns.Check_Allocations); + pragma Debug (Maps.Check_Allocations); + pragma Debug (Sequences.Check_Allocations); + pragma Debug (Symbols.Check_Allocations); end Step5_Tco; diff --git a/ada.2/step6_file.adb b/ada.2/step6_file.adb index 42294855b9..37da996794 100644 --- a/ada.2/step6_file.adb +++ b/ada.2/step6_file.adb @@ -1,26 +1,36 @@ with Ada.Command_Line; -with Ada.Exceptions; +with Ada.Environment_Variables; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; with Core; with Envs; +with Err; with Eval_Cb; with Printer; with Reader; with Readline; -with Types.Functions; -with Types.Lists; +with Types.Atoms; +with Types.Builtins; +with Types.Fns; with Types.Mal; with Types.Maps; +with Types.Sequences; with Types.Symbols.Names; procedure Step6_File is - package ASU renames Ada.Strings.Unbounded; + Dbgenv1 : constant Boolean := Ada.Environment_Variables.Exists ("dbgenv1"); + Dbgenv0 : constant Boolean + := Dbgenv1 or Ada.Environment_Variables.Exists ("dbgenv0"); + Dbgeval : constant Boolean + := Dbgenv0 or Ada.Environment_Variables.Exists ("dbgeval"); + use Types; + use type Mal.T; + package ASU renames Ada.Strings.Unbounded; - function Read return Mal.T with Inline; + function Read return Mal.T_Array with Inline; function Eval (Ast0 : in Mal.T; Env0 : in Envs.Ptr) return Mal.T; @@ -29,13 +39,12 @@ procedure Step6_File is procedure Rep (Env : in Envs.Ptr) with Inline; - function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + function Eval_Seq_Elts is new Sequences.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); - -- Procedural form of Eval. - -- Convenient when the result of eval is of no interest. - procedure Eval_P (Ast : in Mal.T; - Env : in Envs.Ptr) with Inline; + procedure Exec (Script : in String; + Env : in Envs.Ptr) with Inline; + -- Read the script, eval its elements, but ignore the result. ---------------------------------------------------------------------- @@ -50,108 +59,86 @@ procedure Step6_File is First : Mal.T; begin <> - -- Ada.Text_IO.New_Line; - -- Ada.Text_IO.Put ("EVAL: "); - -- Print (Ast); - -- Envs.Dump_Stack; + if Dbgeval then + Ada.Text_IO.New_Line; + Ada.Text_IO.Put ("EVAL: "); + Print (Ast); + if Dbgenv0 then + Envs.Dump_Stack (Dbgenv1); + end if; + end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function - | Kind_Builtin_With_Meta | Kind_Builtin => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key + | Kind_Macro | Kind_Function => return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); + return (Kind_Vector, Eval_Seq_Elts (Ast.Sequence, Env)); when Kind_List => null; end case; -- Ast is a list. - if Ast.List.Length = 0 then + if Ast.Sequence.Length = 0 then return Ast; end if; - First := Ast.List.Element (1); + First := Ast.Sequence (1); -- Special forms -- Ast is a non-empty list, First is its first element. case First.Kind is when Kind_Symbol => if First.Symbol = Symbols.Names.Def then - if Ast.List.Length /= 3 then - raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.List.Element (2).Kind /= Kind_Symbol then - raise Argument_Error with "def!: arg 1 must be a symbol"; - end if; - return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do - Env.Set (Ast.List.Element (2).Symbol, R); + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind = Kind_Symbol, + "parameter 1 must be a symbol"); + return R : constant Mal.T := Eval (Ast.Sequence (3), Env) do + Env.Set (Ast.Sequence (2).Symbol, R); end return; - elsif First.Symbol = Symbols.Names.Mal_Do then - if Ast.List.Length = 1 then - raise Argument_Error with "do: expects at least 1 argument"; - end if; - for I in 2 .. Ast.List.Length - 1 loop - Eval_P (Ast.List.Element (I), Env); - end loop; - Ast := Ast.List.Element (Ast.List.Length); - goto Restart; + -- do is a built-in function, shortening this test cascade. elsif First.Symbol = Symbols.Names.Fn then - if Ast.List.Length /= 3 then - raise Argument_Error with "fn*: expects 3 arguments"; - elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "fn*: arg 1 must be a list or vector"; - elsif (for some F in 1 .. Ast.List.Element (2).List.Length => - Ast.List.Element (2).List.Element (F).Kind /= Kind_Symbol) - then - raise Argument_Error with "fn*: arg 2 must contain symbols"; - end if; - return Functions.New_Function (Params => Ast.List.Element (2).List, - Ast => Ast.List.Element (3), - Env => Env.New_Closure); + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind in Kind_Sequence, + "parameter 1 must be a sequence"); + return Fns.New_Function (Params => Ast.Sequence (2).Sequence, + Ast => Ast.Sequence (3), + Env => Env.New_Closure); elsif First.Symbol = Symbols.Names.Mal_If then - if Ast.List.Length not in 3 .. 4 then - raise Argument_Error with "if: expects 2 or 3 arguments"; - end if; + Err.Check (Ast.Sequence.Length in 3 .. 4, + "expected 2 or 3 parameters"); declare - Test : constant Mal.T := Eval (Ast.List.Element (2), Env); + Test : constant Mal.T := Eval (Ast.Sequence (2), Env); begin - if (case Test.Kind is - when Kind_Nil => False, - when Kind_Boolean => Test.Ada_Boolean, - when others => True) - then - Ast := Ast.List.Element (3); + if Test /= Mal.Nil and Test /= (Kind_Boolean, False) then + Ast := Ast.Sequence (3); goto Restart; - elsif Ast.List.Length = 3 then + elsif Ast.Sequence.Length = 3 then return Mal.Nil; else - Ast := Ast.List.Element (4); + Ast := Ast.Sequence (4); goto Restart; end if; end; elsif First.Symbol = Symbols.Names.Let then - if Ast.List.Length /= 3 then - raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "let*: expects a list or vector"; - end if; + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind in Kind_Sequence, + "parameter 1 must be a sequence"); declare - Bindings : constant Lists.Ptr := Ast.List.Element (2).List; + Bindings : constant Sequences.Ptr := Ast.Sequence (2).Sequence; begin - if Bindings.Length mod 2 /= 0 then - raise Argument_Error with "let*: odd number of bindings"; - end if; + Err.Check (Bindings.Length mod 2 = 0, + "parameter 1 must have an even length"); Env.Replace_With_Sub; for I in 1 .. Bindings.Length / 2 loop - if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then - raise Argument_Error with "let*: keys must be symbols"; - end if; - Env.Set (Bindings.Element (2 * I - 1).Symbol, - Eval (Bindings.Element (2 * I), Env)); + Err.Check (Bindings (2 * I - 1).Kind = Kind_Symbol, + "binding keys must be symbols"); + Env.Set (Bindings (2 * I - 1).Symbol, + Eval (Bindings (2 * I), Env)); end loop; - Ast := Ast.List.Element (3); + Ast := Ast.Sequence (3); goto Restart; end; else @@ -159,13 +146,12 @@ procedure Step6_File is -- except that we already know enough to spare a recursive call. First := Env.Get (First.Symbol); end if; - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function - | Kind_Builtin_With_Meta | Kind_Builtin => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key + | Kind_Macro | Kind_Function => -- Equivalent to First := Eval (First, Env) -- except that we already know enough to spare a recursive call. null; - when Kind_List | Kind_Vector | Kind_Map => + when Kind_Sequence | Kind_Map => -- Lists are definitely worth a recursion, and the two other -- cases should be rare (they will report an error later). First := Eval (First, Env); @@ -177,19 +163,19 @@ procedure Step6_File is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; return First.Builtin.all (Args); end; - when Kind_Function => + when Kind_Fn => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; Env.Replace_With_Sub (Outer => First.Fn.Env, Binds => First.Fn.Params, @@ -198,60 +184,67 @@ procedure Step6_File is goto Restart; end; when others => - raise Argument_Error with "cannot call " & Printer.Img (First); + Err.Raise_With ("first element must be a function"); end case; + exception + when Err.Error => + Err.Add_Trace_Line ("eval", Ast); + raise; end Eval; - procedure Eval_P (Ast : in Mal.T; - Env : in Envs.Ptr) + procedure Exec (Script : in String; + Env : in Envs.Ptr) is - Result : constant Mal.T := Eval (Ast, Env); + Result : Mal.T; begin + for Expression of Reader.Read_Str (Script) loop + Result := Eval (Expression, Env); + end loop; pragma Unreferenced (Result); - end Eval_P; + end Exec; procedure Print (Ast : in Mal.T) is begin Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + function Read return Mal.T_Array + is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is begin - Print (Eval (Read, Env)); + for Expression of Read loop + Print (Eval (Expression, Env)); + end loop; end Rep; ---------------------------------------------------------------------- - Startup : constant String := "(do " - & "(def! not (fn* (a) (if a false true)))" + Startup : constant String + := "(def! not (fn* (a) (if a false true)))" & "(def! load-file (fn* (f)" - & " (eval (read-string (str ""(do "" (slurp f) "")"")))))" - & ")"; + & " (eval (read-string (str ""(do "" (slurp f) "")"")))))"; Repl : Envs.Ptr renames Envs.Repl; - use Ada.Command_Line; begin -- Show the Eval function to other packages. Eval_Cb.Cb := Eval'Unrestricted_Access; -- Add Core functions into the top environment. - for Binding of Core.Ns loop - Repl.Set (Binding.Symbol, (Kind_Builtin, Binding.Builtin)); - end loop; + Core.NS_Add_To_Repl; -- Native startup procedure. - Eval_P (Reader.Read_Str (Startup), Repl); + Exec (Startup, Repl); -- Define ARGV from command line arguments. declare + use Ada.Command_Line; Args : Mal.T_Array (2 .. Argument_Count); begin for I in Args'Range loop Args (I) := (Kind_String, ASU.To_Unbounded_String (Argument (I))); end loop; - Repl.Set (Symbols.Constructor ("*ARGV*"), Lists.List (Args)); + Repl.Set (Symbols.Constructor ("*ARGV*"), Sequences.List (Args)); end; -- Script? - if 0 < Argument_Count then - Eval_P (Reader.Read_Str ("(load-file """ & Argument (1) & """)"), Repl); + if 0 < Ada.Command_Line.Argument_Count then + Exec ("(load-file """ & Ada.Command_Line.Argument (1) & """)", Repl); else loop begin @@ -259,13 +252,20 @@ begin exception when Readline.End_Of_File => exit; - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. + when Err.Error => + Ada.Text_IO.Unbounded_IO.Put (Err.Trace); end; + -- Other exceptions are really unexpected. end loop; Ada.Text_IO.New_Line; end if; + -- If assertions are enabled, check deallocations. + Err.Data := Mal.Nil; -- Remove references to other packages + pragma Debug (Envs.Clear_And_Check_Allocations); + pragma Debug (Atoms.Check_Allocations); + pragma Debug (Builtins.Check_Allocations); + pragma Debug (Fns.Check_Allocations); + pragma Debug (Maps.Check_Allocations); + pragma Debug (Sequences.Check_Allocations); + pragma Debug (Symbols.Check_Allocations); end Step6_File; diff --git a/ada.2/step7_quote.adb b/ada.2/step7_quote.adb index 2d517a65e4..35cca20666 100644 --- a/ada.2/step7_quote.adb +++ b/ada.2/step7_quote.adb @@ -1,26 +1,36 @@ with Ada.Command_Line; -with Ada.Exceptions; +with Ada.Environment_Variables; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; with Core; with Envs; +with Err; with Eval_Cb; with Printer; with Reader; with Readline; -with Types.Functions; -with Types.Lists; +with Types.Atoms; +with Types.Builtins; +with Types.Fns; with Types.Mal; with Types.Maps; +with Types.Sequences; with Types.Symbols.Names; procedure Step7_Quote is - package ASU renames Ada.Strings.Unbounded; + Dbgenv1 : constant Boolean := Ada.Environment_Variables.Exists ("dbgenv1"); + Dbgenv0 : constant Boolean + := Dbgenv1 or Ada.Environment_Variables.Exists ("dbgenv0"); + Dbgeval : constant Boolean + := Dbgenv0 or Ada.Environment_Variables.Exists ("dbgeval"); + use Types; + use type Mal.T; + package ASU renames Ada.Strings.Unbounded; - function Read return Mal.T with Inline; + function Read return Mal.T_Array with Inline; function Eval (Ast0 : in Mal.T; Env0 : in Envs.Ptr) return Mal.T; @@ -36,13 +46,12 @@ procedure Step7_Quote is procedure Rep (Env : in Envs.Ptr) with Inline; - function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + function Eval_Seq_Elts is new Sequences.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); - -- Procedural form of Eval. - -- Convenient when the result of eval is of no interest. - procedure Eval_P (Ast : in Mal.T; - Env : in Envs.Ptr) with Inline; + procedure Exec (Script : in String; + Env : in Envs.Ptr) with Inline; + -- Read the script, eval its elements, but ignore the result. ---------------------------------------------------------------------- @@ -57,132 +66,105 @@ procedure Step7_Quote is First : Mal.T; begin <> - -- Ada.Text_IO.New_Line; - -- Ada.Text_IO.Put ("EVAL: "); - -- Print (Ast); - -- Envs.Dump_Stack; + if Dbgeval then + Ada.Text_IO.New_Line; + Ada.Text_IO.Put ("EVAL: "); + Print (Ast); + if Dbgenv0 then + Envs.Dump_Stack (Dbgenv1); + end if; + end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function - | Kind_Builtin_With_Meta | Kind_Builtin => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key + | Kind_Macro | Kind_Function => return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); + return (Kind_Vector, Eval_Seq_Elts (Ast.Sequence, Env)); when Kind_List => null; end case; -- Ast is a list. - if Ast.List.Length = 0 then + if Ast.Sequence.Length = 0 then return Ast; end if; - First := Ast.List.Element (1); + First := Ast.Sequence (1); -- Special forms -- Ast is a non-empty list, First is its first element. case First.Kind is when Kind_Symbol => if First.Symbol = Symbols.Names.Def then - if Ast.List.Length /= 3 then - raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.List.Element (2).Kind /= Kind_Symbol then - raise Argument_Error with "def!: arg 1 must be a symbol"; - end if; - return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do - Env.Set (Ast.List.Element (2).Symbol, R); + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind = Kind_Symbol, + "parameter 1 must be a symbol"); + return R : constant Mal.T := Eval (Ast.Sequence (3), Env) do + Env.Set (Ast.Sequence (2).Symbol, R); end return; - elsif First.Symbol = Symbols.Names.Mal_Do then - if Ast.List.Length = 1 then - raise Argument_Error with "do: expects at least 1 argument"; - end if; - for I in 2 .. Ast.List.Length - 1 loop - Eval_P (Ast.List.Element (I), Env); - end loop; - Ast := Ast.List.Element (Ast.List.Length); - goto Restart; + -- do is a built-in function, shortening this test cascade. elsif First.Symbol = Symbols.Names.Fn then - if Ast.List.Length /= 3 then - raise Argument_Error with "fn*: expects 3 arguments"; - elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "fn*: arg 1 must be a list or vector"; - elsif (for some F in 1 .. Ast.List.Element (2).List.Length => - Ast.List.Element (2).List.Element (F).Kind /= Kind_Symbol) - then - raise Argument_Error with "fn*: arg 2 must contain symbols"; - end if; - return Functions.New_Function (Params => Ast.List.Element (2).List, - Ast => Ast.List.Element (3), - Env => Env.New_Closure); + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind in Kind_Sequence, + "parameter 1 must be a sequence"); + return Fns.New_Function (Params => Ast.Sequence (2).Sequence, + Ast => Ast.Sequence (3), + Env => Env.New_Closure); elsif First.Symbol = Symbols.Names.Mal_If then - if Ast.List.Length not in 3 .. 4 then - raise Argument_Error with "if: expects 2 or 3 arguments"; - end if; + Err.Check (Ast.Sequence.Length in 3 .. 4, + "expected 2 or 3 parameters"); declare - Test : constant Mal.T := Eval (Ast.List.Element (2), Env); + Test : constant Mal.T := Eval (Ast.Sequence (2), Env); begin - if (case Test.Kind is - when Kind_Nil => False, - when Kind_Boolean => Test.Ada_Boolean, - when others => True) - then - Ast := Ast.List.Element (3); + if Test /= Mal.Nil and Test /= (Kind_Boolean, False) then + Ast := Ast.Sequence (3); goto Restart; - elsif Ast.List.Length = 3 then + elsif Ast.Sequence.Length = 3 then return Mal.Nil; else - Ast := Ast.List.Element (4); + Ast := Ast.Sequence (4); goto Restart; end if; end; elsif First.Symbol = Symbols.Names.Let then - if Ast.List.Length /= 3 then - raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "let*: expects a list or vector"; - end if; + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind in Kind_Sequence, + "parameter 1 must be a sequence"); declare - Bindings : constant Lists.Ptr := Ast.List.Element (2).List; + Bindings : constant Sequences.Ptr := Ast.Sequence (2).Sequence; begin - if Bindings.Length mod 2 /= 0 then - raise Argument_Error with "let*: odd number of bindings"; - end if; + Err.Check (Bindings.Length mod 2 = 0, + "parameter 1 must have an even length"); Env.Replace_With_Sub; for I in 1 .. Bindings.Length / 2 loop - if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then - raise Argument_Error with "let*: keys must be symbols"; - end if; - Env.Set (Bindings.Element (2 * I - 1).Symbol, - Eval (Bindings.Element (2 * I), Env)); + Err.Check (Bindings (2 * I - 1).Kind = Kind_Symbol, + "binding keys must be symbols"); + Env.Set (Bindings (2 * I - 1).Symbol, + Eval (Bindings (2 * I), Env)); end loop; - Ast := Ast.List.Element (3); + Ast := Ast.Sequence (3); goto Restart; end; elsif First.Symbol = Symbols.Names.Quasiquote then - if Ast.List.Length /= 2 then - raise Argument_Error with "quasiquote: expects 1 argument"; - end if; - return Quasiquote (Ast.List.Element (2), Env); + Err.Check (Ast.Sequence.Length = 2, "expected 1 parameter"); + return Quasiquote (Ast.Sequence (2), Env); elsif First.Symbol = Symbols.Names.Quote then - if Ast.List.Length /= 2 then - raise Argument_Error with "quote: expects 1 argument"; - end if; - return Ast.List.Element (2); + Err.Check (Ast.Sequence.Length = 2, "expected 1 parameter"); + return Ast.Sequence (2); else -- Equivalent to First := Eval (First, Env) -- except that we already know enough to spare a recursive call. First := Env.Get (First.Symbol); end if; - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function - | Kind_Builtin_With_Meta | Kind_Builtin => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key + | Kind_Macro | Kind_Function => -- Equivalent to First := Eval (First, Env) -- except that we already know enough to spare a recursive call. null; - when Kind_List | Kind_Vector | Kind_Map => + when Kind_Sequence | Kind_Map => -- Lists are definitely worth a recursion, and the two other -- cases should be rare (they will report an error later). First := Eval (First, Env); @@ -194,19 +176,19 @@ procedure Step7_Quote is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; return First.Builtin.all (Args); end; - when Kind_Function => + when Kind_Fn => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; Env.Replace_With_Sub (Outer => First.Fn.Env, Binds => First.Fn.Params, @@ -215,17 +197,24 @@ procedure Step7_Quote is goto Restart; end; when others => - raise Argument_Error with "cannot call " & Printer.Img (First); + Err.Raise_With ("first element must be a function"); end case; + exception + when Err.Error => + Err.Add_Trace_Line ("eval", Ast); + raise; end Eval; - procedure Eval_P (Ast : in Mal.T; - Env : in Envs.Ptr) + procedure Exec (Script : in String; + Env : in Envs.Ptr) is - Result : constant Mal.T := Eval (Ast, Env); + Result : Mal.T; begin + for Expression of Reader.Read_Str (Script) loop + Result := Eval (Expression, Env); + end loop; pragma Unreferenced (Result); - end Eval_P; + end Exec; procedure Print (Ast : in Mal.T) is begin @@ -236,97 +225,93 @@ procedure Step7_Quote is Env : in Envs.Ptr) return Mal.T is - use type Symbols.Ptr; - - function Quasiquote_List (List : in Lists.Ptr) return Mal.T with Inline; + function Quasiquote_List (List : in Sequences.Ptr) return Mal.T + with Inline; -- Handle vectors and lists not starting with unquote. - function Quasiquote_List (List : in Lists.Ptr) return Mal.T is + function Quasiquote_List (List : in Sequences.Ptr) return Mal.T is -- The final return concatenates these lists. R : Mal.T_Array (1 .. List.Length); begin for I in R'Range loop - R (I) := List.Element (I); - if R (I).Kind in Kind_List | Kind_Vector - and then 0 < R (I).List.Length - and then R (I).List.Element (1).Kind = Kind_Symbol - and then R (I).List.Element (1).Symbol - = Symbols.Names.Splice_Unquote + R (I) := List (I); + if R (I).Kind in Kind_List and then 0 < R (I).Sequence.Length + and then R (I).Sequence (1) = (Kind_Symbol, + Symbols.Names.Splice_Unquote) then - if R (I).List.Length /= 2 then - raise Argument_Error with "splice-unquote: expects 1 arg"; - end if; - R (I) := Eval (R (I).List.Element (2), Env); - if R (I).Kind /= Kind_List then - raise Argument_Error with "splice-unquote: expects a list"; - end if; + Err.Check (R (I).Sequence.Length = 2, + "splice-unquote expects 1 parameter"); + R (I) := Eval (@.Sequence (2), Env); + Err.Check (R (I).Kind = Kind_List, + "splice_unquote expects a list"); else - R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), - Env))); + R (I) := Sequences.List + (Mal.T_Array'(1 => Quasiquote (@, Env))); end if; end loop; - return Lists.Concat (R); + return Sequences.Concat (R); end Quasiquote_List; begin -- Quasiquote case Ast.Kind is when Kind_Vector => -- When the test is updated, replace Kind_List with Kind_Vector. - return Quasiquote_List (Ast.List); + return Quasiquote_List (Ast.Sequence); when Kind_List => - if 0 < Ast.List.Length - and then Ast.List.Element (1).Kind = Kind_Symbol - and then Ast.List.Element (1).Symbol = Symbols.Names.Unquote + if 0 < Ast.Sequence.Length + and then Ast.Sequence (1) = (Kind_Symbol, Symbols.Names.Unquote) then - if 2 < Ast.List.Length then - raise Argument_Error with "unquote: expects 1 argument"; - end if; - return Eval (Ast.List.Element (2), Env); + Err.Check (Ast.Sequence.Length = 2, "expected 1 parameter"); + return Eval (Ast.Sequence (2), Env); else - return Quasiquote_List (Ast.List); + return Quasiquote_List (Ast.Sequence); end if; when others => return Ast; end case; + exception + when Err.Error => + Err.Add_Trace_Line ("quasiquote", Ast); + raise; end Quasiquote; - function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + function Read return Mal.T_Array + is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is begin - Print (Eval (Read, Env)); + for Expression of Read loop + Print (Eval (Expression, Env)); + end loop; end Rep; ---------------------------------------------------------------------- - Startup : constant String := "(do " - & "(def! not (fn* (a) (if a false true)))" + Startup : constant String + := "(def! not (fn* (a) (if a false true)))" & "(def! load-file (fn* (f)" - & " (eval (read-string (str ""(do "" (slurp f) "")"")))))" - & ")"; + & " (eval (read-string (str ""(do "" (slurp f) "")"")))))"; Repl : Envs.Ptr renames Envs.Repl; - use Ada.Command_Line; begin -- Show the Eval function to other packages. Eval_Cb.Cb := Eval'Unrestricted_Access; -- Add Core functions into the top environment. - for Binding of Core.Ns loop - Repl.Set (Binding.Symbol, (Kind_Builtin, Binding.Builtin)); - end loop; + Core.NS_Add_To_Repl; -- Native startup procedure. - Eval_P (Reader.Read_Str (Startup), Repl); + Exec (Startup, Repl); -- Define ARGV from command line arguments. declare + use Ada.Command_Line; Args : Mal.T_Array (2 .. Argument_Count); begin for I in Args'Range loop Args (I) := (Kind_String, ASU.To_Unbounded_String (Argument (I))); end loop; - Repl.Set (Symbols.Constructor ("*ARGV*"), Lists.List (Args)); + Repl.Set (Symbols.Constructor ("*ARGV*"), Sequences.List (Args)); end; -- Script? - if 0 < Argument_Count then - Eval_P (Reader.Read_Str ("(load-file """ & Argument (1) & """)"), Repl); + if 0 < Ada.Command_Line.Argument_Count then + Exec ("(load-file """ & Ada.Command_Line.Argument (1) & """)", Repl); else loop begin @@ -334,13 +319,20 @@ begin exception when Readline.End_Of_File => exit; - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. + when Err.Error => + Ada.Text_IO.Unbounded_IO.Put (Err.Trace); end; + -- Other exceptions are really unexpected. end loop; Ada.Text_IO.New_Line; end if; + -- If assertions are enabled, check deallocations. + Err.Data := Mal.Nil; -- Remove references to other packages + pragma Debug (Envs.Clear_And_Check_Allocations); + pragma Debug (Atoms.Check_Allocations); + pragma Debug (Builtins.Check_Allocations); + pragma Debug (Fns.Check_Allocations); + pragma Debug (Maps.Check_Allocations); + pragma Debug (Sequences.Check_Allocations); + pragma Debug (Symbols.Check_Allocations); end Step7_Quote; diff --git a/ada.2/step8_macros.adb b/ada.2/step8_macros.adb index a1b00ed02a..e969e48a7f 100644 --- a/ada.2/step8_macros.adb +++ b/ada.2/step8_macros.adb @@ -1,26 +1,36 @@ with Ada.Command_Line; -with Ada.Exceptions; +with Ada.Environment_Variables; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; with Core; with Envs; +with Err; with Eval_Cb; with Printer; with Reader; with Readline; -with Types.Functions; -with Types.Lists; +with Types.Atoms; +with Types.Builtins; +with Types.Fns; with Types.Mal; with Types.Maps; +with Types.Sequences; with Types.Symbols.Names; procedure Step8_Macros is - package ASU renames Ada.Strings.Unbounded; + Dbgenv1 : constant Boolean := Ada.Environment_Variables.Exists ("dbgenv1"); + Dbgenv0 : constant Boolean + := Dbgenv1 or Ada.Environment_Variables.Exists ("dbgenv0"); + Dbgeval : constant Boolean + := Dbgenv0 or Ada.Environment_Variables.Exists ("dbgeval"); + use Types; + use type Mal.T; + package ASU renames Ada.Strings.Unbounded; - function Read return Mal.T with Inline; + function Read return Mal.T_Array with Inline; function Eval (Ast0 : in Mal.T; Env0 : in Envs.Ptr) return Mal.T; @@ -36,13 +46,12 @@ procedure Step8_Macros is procedure Rep (Env : in Envs.Ptr) with Inline; - function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + function Eval_Seq_Elts is new Sequences.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); - -- Procedural form of Eval. - -- Convenient when the result of eval is of no interest. - procedure Eval_P (Ast : in Mal.T; - Env : in Envs.Ptr) with Inline; + procedure Exec (Script : in String; + Env : in Envs.Ptr) with Inline; + -- Read the script, eval its elements, but ignore the result. ---------------------------------------------------------------------- @@ -58,155 +67,122 @@ procedure Step8_Macros is First : Mal.T; begin <> - -- Ada.Text_IO.New_Line; - -- Ada.Text_IO.Put ("EVAL: "); - -- Print (Ast); - -- Envs.Dump_Stack; + if Dbgeval then + Ada.Text_IO.New_Line; + Ada.Text_IO.Put ("EVAL: "); + Print (Ast); + if Dbgenv0 then + Envs.Dump_Stack (Dbgenv1); + end if; + end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function - | Kind_Builtin_With_Meta | Kind_Builtin => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key + | Kind_Macro | Kind_Function => return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); + return (Kind_Vector, Eval_Seq_Elts (Ast.Sequence, Env)); when Kind_List => null; end case; -- Ast is a list. - if Ast.List.Length = 0 then + if Ast.Sequence.Length = 0 then return Ast; end if; - First := Ast.List.Element (1); + First := Ast.Sequence (1); -- Special forms -- Ast is a non-empty list, First is its first element. case First.Kind is when Kind_Symbol => if First.Symbol = Symbols.Names.Def then - if Ast.List.Length /= 3 then - raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.List.Element (2).Kind /= Kind_Symbol then - raise Argument_Error with "def!: arg 1 must be a symbol"; - end if; - return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do - Env.Set (Ast.List.Element (2).Symbol, R); + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind = Kind_Symbol, + "parameter 1 must be a symbol"); + return R : constant Mal.T := Eval (Ast.Sequence (3), Env) do + Env.Set (Ast.Sequence (2).Symbol, R); end return; elsif First.Symbol = Symbols.Names.Defmacro then - if Ast.List.Length /= 3 then - raise Argument_Error with "defmacro!: expects 2 arguments"; - elsif Ast.List.Element (2).Kind /= Kind_Symbol then - raise Argument_Error with "defmacro!: arg 1 must be a symbol"; - end if; + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind = Kind_Symbol, + "parameter 1 must be a symbol"); declare - F : constant Mal.T := Eval (Ast.List.Element (3), Env); + F : constant Mal.T := Eval (Ast.Sequence (3), Env); begin - if F.Kind /= Kind_Function then - raise Argument_Error with "defmacro!: expects a function"; - end if; + Err.Check (F.Kind = Kind_Fn, "parameter 2 must be a function"); return R : constant Mal.T := F.Fn.New_Macro do - Env.Set (Ast.List.Element (2).Symbol, R); + Env.Set (Ast.Sequence (2).Symbol, R); end return; end; - elsif First.Symbol = Symbols.Names.Mal_Do then - if Ast.List.Length = 1 then - raise Argument_Error with "do: expects at least 1 argument"; - end if; - for I in 2 .. Ast.List.Length - 1 loop - Eval_P (Ast.List.Element (I), Env); - end loop; - Ast := Ast.List.Element (Ast.List.Length); - goto Restart; + -- do is a built-in function, shortening this test cascade. elsif First.Symbol = Symbols.Names.Fn then - if Ast.List.Length /= 3 then - raise Argument_Error with "fn*: expects 3 arguments"; - elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "fn*: arg 1 must be a list or vector"; - elsif (for some F in 1 .. Ast.List.Element (2).List.Length => - Ast.List.Element (2).List.Element (F).Kind /= Kind_Symbol) - then - raise Argument_Error with "fn*: arg 2 must contain symbols"; - end if; - return Functions.New_Function (Params => Ast.List.Element (2).List, - Ast => Ast.List.Element (3), - Env => Env.New_Closure); + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind in Kind_Sequence, + "parameter 1 must be a sequence"); + return Fns.New_Function (Params => Ast.Sequence (2).Sequence, + Ast => Ast.Sequence (3), + Env => Env.New_Closure); elsif First.Symbol = Symbols.Names.Mal_If then - if Ast.List.Length not in 3 .. 4 then - raise Argument_Error with "if: expects 2 or 3 arguments"; - end if; + Err.Check (Ast.Sequence.Length in 3 .. 4, + "expected 2 or 3 parameters"); declare - Test : constant Mal.T := Eval (Ast.List.Element (2), Env); + Test : constant Mal.T := Eval (Ast.Sequence (2), Env); begin - if (case Test.Kind is - when Kind_Nil => False, - when Kind_Boolean => Test.Ada_Boolean, - when others => True) - then - Ast := Ast.List.Element (3); + if Test /= Mal.Nil and Test /= (Kind_Boolean, False) then + Ast := Ast.Sequence (3); goto Restart; - elsif Ast.List.Length = 3 then + elsif Ast.Sequence.Length = 3 then return Mal.Nil; else - Ast := Ast.List.Element (4); + Ast := Ast.Sequence (4); goto Restart; end if; end; elsif First.Symbol = Symbols.Names.Let then - if Ast.List.Length /= 3 then - raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "let*: expects a list or vector"; - end if; + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind in Kind_Sequence, + "parameter 1 must be a sequence"); declare - Bindings : constant Lists.Ptr := Ast.List.Element (2).List; + Bindings : constant Sequences.Ptr := Ast.Sequence (2).Sequence; begin - if Bindings.Length mod 2 /= 0 then - raise Argument_Error with "let*: odd number of bindings"; - end if; + Err.Check (Bindings.Length mod 2 = 0, + "parameter 1 must have an even length"); Env.Replace_With_Sub; for I in 1 .. Bindings.Length / 2 loop - if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then - raise Argument_Error with "let*: keys must be symbols"; - end if; - Env.Set (Bindings.Element (2 * I - 1).Symbol, - Eval (Bindings.Element (2 * I), Env)); + Err.Check (Bindings (2 * I - 1).Kind = Kind_Symbol, + "binding keys must be symbols"); + Env.Set (Bindings (2 * I - 1).Symbol, + Eval (Bindings (2 * I), Env)); end loop; - Ast := Ast.List.Element (3); + Ast := Ast.Sequence (3); goto Restart; end; elsif First.Symbol = Symbols.Names.Macroexpand then - if Ast.List.Length /= 2 then - raise Argument_Error with "macroexpand: expects 1 argument"; - end if; + Err.Check (Ast.Sequence.Length = 2, "expected 1 parameter"); Macroexpanding := True; - Ast := Ast.List.Element (2); + Ast := Ast.Sequence (2); goto Restart; elsif First.Symbol = Symbols.Names.Quasiquote then - if Ast.List.Length /= 2 then - raise Argument_Error with "quasiquote: expects 1 argument"; - end if; - return Quasiquote (Ast.List.Element (2), Env); + Err.Check (Ast.Sequence.Length = 2, "expected 1 parameter"); + return Quasiquote (Ast.Sequence (2), Env); elsif First.Symbol = Symbols.Names.Quote then - if Ast.List.Length /= 2 then - raise Argument_Error with "quote: expects 1 argument"; - end if; - return Ast.List.Element (2); + Err.Check (Ast.Sequence.Length = 2, "expected 1 parameter"); + return Ast.Sequence (2); else -- Equivalent to First := Eval (First, Env) -- except that we already know enough to spare a recursive call. First := Env.Get (First.Symbol); end if; - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function - | Kind_Builtin_With_Meta | Kind_Builtin => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key + | Kind_Macro | Kind_Function => -- Equivalent to First := Eval (First, Env) -- except that we already know enough to spare a recursive call. null; - when Kind_List | Kind_Vector | Kind_Map => + when Kind_Sequence | Kind_Map => -- Lists are definitely worth a recursion, and the two other -- cases should be rare (they will report an error later). First := Eval (First, Env); @@ -218,19 +194,19 @@ procedure Step8_Macros is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; return First.Builtin.all (Args); end; - when Kind_Function => + when Kind_Fn => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; Env.Replace_With_Sub (Outer => First.Fn.Env, Binds => First.Fn.Params, @@ -239,33 +215,49 @@ procedure Step8_Macros is goto Restart; end; when Kind_Macro => - if Macroexpanding then - -- Evaluate the macro with tail call optimization. - Env.Replace_With_Sub_Macro (Binds => First.Fn.Params, - Exprs => Ast.List); - Ast := First.Fn.Ast; - goto Restart; - else - -- Evaluate the macro normally. - Ast := Eval (Ast0 => First.Fn.Ast, - Env0 => Envs.Sub (Outer => Env, - Binds => First.Fn.Params, - Exprs => Ast.List)); - -- Then evaluate the result with TCO. - goto Restart; - end if; + declare + Args : constant Mal.T_Array + := Ast.Sequence.Tail (Ast.Sequence.Length - 1); + begin + if Macroexpanding then + -- Evaluate the macro with tail call optimization. + Env.Replace_With_Sub (Binds => First.Fn.Params, + Exprs => Args); + Ast := First.Fn.Ast; + goto Restart; + else + -- Evaluate the macro normally. + Ast := Eval (First.Fn.Ast, Envs.Sub + (Outer => Env, + Binds => First.Fn.Params, + Exprs => Args)); + -- Then evaluate the result with TCO. + goto Restart; + end if; + end; when others => - raise Argument_Error with "cannot call " & Printer.Img (First); + Err.Raise_With ("first element must be a function or macro"); end case; + exception + when Err.Error => + if Macroexpanding then + Err.Add_Trace_Line ("macroexpand", Ast); + else + Err.Add_Trace_Line ("eval", Ast); + end if; + raise; end Eval; - procedure Eval_P (Ast : in Mal.T; - Env : in Envs.Ptr) + procedure Exec (Script : in String; + Env : in Envs.Ptr) is - Result : constant Mal.T := Eval (Ast, Env); + Result : Mal.T; begin + for Expression of Reader.Read_Str (Script) loop + Result := Eval (Expression, Env); + end loop; pragma Unreferenced (Result); - end Eval_P; + end Exec; procedure Print (Ast : in Mal.T) is begin @@ -276,71 +268,70 @@ procedure Step8_Macros is Env : in Envs.Ptr) return Mal.T is - use type Symbols.Ptr; - - function Quasiquote_List (List : in Lists.Ptr) return Mal.T with Inline; + function Quasiquote_List (List : in Sequences.Ptr) return Mal.T + with Inline; -- Handle vectors and lists not starting with unquote. - function Quasiquote_List (List : in Lists.Ptr) return Mal.T is + function Quasiquote_List (List : in Sequences.Ptr) return Mal.T is -- The final return concatenates these lists. R : Mal.T_Array (1 .. List.Length); begin for I in R'Range loop - R (I) := List.Element (I); - if R (I).Kind in Kind_List | Kind_Vector - and then 0 < R (I).List.Length - and then R (I).List.Element (1).Kind = Kind_Symbol - and then R (I).List.Element (1).Symbol - = Symbols.Names.Splice_Unquote + R (I) := List (I); + if R (I).Kind in Kind_List and then 0 < R (I).Sequence.Length + and then R (I).Sequence (1) = (Kind_Symbol, + Symbols.Names.Splice_Unquote) then - if R (I).List.Length /= 2 then - raise Argument_Error with "splice-unquote: expects 1 arg"; - end if; - R (I) := Eval (R (I).List.Element (2), Env); - if R (I).Kind /= Kind_List then - raise Argument_Error with "splice-unquote: expects a list"; - end if; + Err.Check (R (I).Sequence.Length = 2, + "splice-unquote expects 1 parameter"); + R (I) := Eval (@.Sequence (2), Env); + Err.Check (R (I).Kind = Kind_List, + "splice_unquote expects a list"); else - R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), - Env))); + R (I) := Sequences.List + (Mal.T_Array'(1 => Quasiquote (@, Env))); end if; end loop; - return Lists.Concat (R); + return Sequences.Concat (R); end Quasiquote_List; begin -- Quasiquote case Ast.Kind is when Kind_Vector => -- When the test is updated, replace Kind_List with Kind_Vector. - return Quasiquote_List (Ast.List); + return Quasiquote_List (Ast.Sequence); when Kind_List => - if 0 < Ast.List.Length - and then Ast.List.Element (1).Kind = Kind_Symbol - and then Ast.List.Element (1).Symbol = Symbols.Names.Unquote + if 0 < Ast.Sequence.Length + and then Ast.Sequence (1) = (Kind_Symbol, Symbols.Names.Unquote) then - if 2 < Ast.List.Length then - raise Argument_Error with "unquote: expects 1 argument"; - end if; - return Eval (Ast.List.Element (2), Env); + Err.Check (Ast.Sequence.Length = 2, "expected 1 parameter"); + return Eval (Ast.Sequence (2), Env); else - return Quasiquote_List (Ast.List); + return Quasiquote_List (Ast.Sequence); end if; when others => return Ast; end case; + exception + when Err.Error => + Err.Add_Trace_Line ("quasiquote", Ast); + raise; end Quasiquote; - function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + function Read return Mal.T_Array + is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is begin - Print (Eval (Read, Env)); + for Expression of Read loop + Print (Eval (Expression, Env)); + end loop; end Rep; ---------------------------------------------------------------------- - Startup : constant String := "(do " - & "(def! not (fn* (a) (if a false true)))" + Startup : constant String + := "(def! not (fn* (a) (if a false true)))" & "(def! load-file (fn* (f)" & " (eval (read-string (str ""(do "" (slurp f) "")"")))))" & "(defmacro! cond (fn* (& xs)" @@ -353,31 +344,28 @@ procedure Step8_Macros is & " (if (empty? xs) nil" & " (if (= 1 (count xs)) (first xs)" & " `(let* (or_FIXME ~(first xs))" - & " (if or_FIXME or_FIXME (or ~@(rest xs))))))))" - & ")"; + & " (if or_FIXME or_FIXME (or ~@(rest xs))))))))"; Repl : Envs.Ptr renames Envs.Repl; - use Ada.Command_Line; begin -- Show the Eval function to other packages. Eval_Cb.Cb := Eval'Unrestricted_Access; -- Add Core functions into the top environment. - for Binding of Core.Ns loop - Repl.Set (Binding.Symbol, (Kind_Builtin, Binding.Builtin)); - end loop; + Core.NS_Add_To_Repl; -- Native startup procedure. - Eval_P (Reader.Read_Str (Startup), Repl); + Exec (Startup, Repl); -- Define ARGV from command line arguments. declare + use Ada.Command_Line; Args : Mal.T_Array (2 .. Argument_Count); begin for I in Args'Range loop Args (I) := (Kind_String, ASU.To_Unbounded_String (Argument (I))); end loop; - Repl.Set (Symbols.Constructor ("*ARGV*"), Lists.List (Args)); + Repl.Set (Symbols.Constructor ("*ARGV*"), Sequences.List (Args)); end; -- Script? - if 0 < Argument_Count then - Eval_P (Reader.Read_Str ("(load-file """ & Argument (1) & """)"), Repl); + if 0 < Ada.Command_Line.Argument_Count then + Exec ("(load-file """ & Ada.Command_Line.Argument (1) & """)", Repl); else loop begin @@ -385,13 +373,20 @@ begin exception when Readline.End_Of_File => exit; - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - -- Other exceptions are unexpected. + when Err.Error => + Ada.Text_IO.Unbounded_IO.Put (Err.Trace); end; + -- Other exceptions are really unexpected. end loop; Ada.Text_IO.New_Line; end if; + -- If assertions are enabled, check deallocations. + Err.Data := Mal.Nil; -- Remove references to other packages + pragma Debug (Envs.Clear_And_Check_Allocations); + pragma Debug (Atoms.Check_Allocations); + pragma Debug (Builtins.Check_Allocations); + pragma Debug (Fns.Check_Allocations); + pragma Debug (Maps.Check_Allocations); + pragma Debug (Sequences.Check_Allocations); + pragma Debug (Symbols.Check_Allocations); end Step8_Macros; diff --git a/ada.2/step9_try.adb b/ada.2/step9_try.adb index 8291002fff..71b065ba09 100644 --- a/ada.2/step9_try.adb +++ b/ada.2/step9_try.adb @@ -1,26 +1,36 @@ with Ada.Command_Line; -with Ada.Exceptions; +with Ada.Environment_Variables; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; with Core; with Envs; +with Err; with Eval_Cb; with Printer; with Reader; with Readline; -with Types.Functions; -with Types.Lists; +with Types.Atoms; +with Types.Builtins; +with Types.Fns; with Types.Mal; with Types.Maps; +with Types.Sequences; with Types.Symbols.Names; procedure Step9_Try is - package ASU renames Ada.Strings.Unbounded; + Dbgenv1 : constant Boolean := Ada.Environment_Variables.Exists ("dbgenv1"); + Dbgenv0 : constant Boolean + := Dbgenv1 or Ada.Environment_Variables.Exists ("dbgenv0"); + Dbgeval : constant Boolean + := Dbgenv0 or Ada.Environment_Variables.Exists ("dbgeval"); + use Types; + use type Mal.T; + package ASU renames Ada.Strings.Unbounded; - function Read return Mal.T with Inline; + function Read return Mal.T_Array with Inline; function Eval (Ast0 : in Mal.T; Env0 : in Envs.Ptr) return Mal.T; @@ -36,13 +46,12 @@ procedure Step9_Try is procedure Rep (Env : in Envs.Ptr) with Inline; - function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + function Eval_Seq_Elts is new Sequences.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); - -- Procedural form of Eval. - -- Convenient when the result of eval is of no interest. - procedure Eval_P (Ast : in Mal.T; - Env : in Envs.Ptr) with Inline; + procedure Exec (Script : in String; + Env : in Envs.Ptr) with Inline; + -- Read the script, eval its elements, but ignore the result. ---------------------------------------------------------------------- @@ -58,181 +67,135 @@ procedure Step9_Try is First : Mal.T; begin <> - -- Ada.Text_IO.New_Line; - -- Ada.Text_IO.Put ("EVAL: "); - -- Print (Ast); - -- Envs.Dump_Stack; + if Dbgeval then + Ada.Text_IO.New_Line; + Ada.Text_IO.Put ("EVAL: "); + Print (Ast); + if Dbgenv0 then + Envs.Dump_Stack (Dbgenv1); + end if; + end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function - | Kind_Builtin_With_Meta | Kind_Builtin => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key + | Kind_Macro | Kind_Function => return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); + return (Kind_Vector, Eval_Seq_Elts (Ast.Sequence, Env)); when Kind_List => null; end case; -- Ast is a list. - if Ast.List.Length = 0 then + if Ast.Sequence.Length = 0 then return Ast; end if; - First := Ast.List.Element (1); + First := Ast.Sequence (1); -- Special forms -- Ast is a non-empty list, First is its first element. case First.Kind is when Kind_Symbol => if First.Symbol = Symbols.Names.Def then - if Ast.List.Length /= 3 then - raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.List.Element (2).Kind /= Kind_Symbol then - raise Argument_Error with "def!: arg 1 must be a symbol"; - end if; - return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do - Env.Set (Ast.List.Element (2).Symbol, R); + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind = Kind_Symbol, + "parameter 1 must be a symbol"); + return R : constant Mal.T := Eval (Ast.Sequence (3), Env) do + Env.Set (Ast.Sequence (2).Symbol, R); end return; elsif First.Symbol = Symbols.Names.Defmacro then - if Ast.List.Length /= 3 then - raise Argument_Error with "defmacro!: expects 2 arguments"; - elsif Ast.List.Element (2).Kind /= Kind_Symbol then - raise Argument_Error with "defmacro!: arg 1 must be a symbol"; - end if; + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind = Kind_Symbol, + "parameter 1 must be a symbol"); declare - F : constant Mal.T := Eval (Ast.List.Element (3), Env); + F : constant Mal.T := Eval (Ast.Sequence (3), Env); begin - if F.Kind /= Kind_Function then - raise Argument_Error with "defmacro!: expects a function"; - end if; + Err.Check (F.Kind = Kind_Fn, "parameter 2 must be a function"); return R : constant Mal.T := F.Fn.New_Macro do - Env.Set (Ast.List.Element (2).Symbol, R); + Env.Set (Ast.Sequence (2).Symbol, R); end return; end; - elsif First.Symbol = Symbols.Names.Mal_Do then - if Ast.List.Length = 1 then - raise Argument_Error with "do: expects at least 1 argument"; - end if; - for I in 2 .. Ast.List.Length - 1 loop - Eval_P (Ast.List.Element (I), Env); - end loop; - Ast := Ast.List.Element (Ast.List.Length); - goto Restart; + -- do is a built-in function, shortening this test cascade. elsif First.Symbol = Symbols.Names.Fn then - if Ast.List.Length /= 3 then - raise Argument_Error with "fn*: expects 3 arguments"; - elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "fn*: arg 1 must be a list or vector"; - elsif (for some F in 1 .. Ast.List.Element (2).List.Length => - Ast.List.Element (2).List.Element (F).Kind /= Kind_Symbol) - then - raise Argument_Error with "fn*: arg 2 must contain symbols"; - end if; - return Functions.New_Function (Params => Ast.List.Element (2).List, - Ast => Ast.List.Element (3), - Env => Env.New_Closure); + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind in Kind_Sequence, + "parameter 1 must be a sequence"); + return Fns.New_Function (Params => Ast.Sequence (2).Sequence, + Ast => Ast.Sequence (3), + Env => Env.New_Closure); elsif First.Symbol = Symbols.Names.Mal_If then - if Ast.List.Length not in 3 .. 4 then - raise Argument_Error with "if: expects 2 or 3 arguments"; - end if; + Err.Check (Ast.Sequence.Length in 3 .. 4, + "expected 2 or 3 parameters"); declare - Test : constant Mal.T := Eval (Ast.List.Element (2), Env); + Test : constant Mal.T := Eval (Ast.Sequence (2), Env); begin - if (case Test.Kind is - when Kind_Nil => False, - when Kind_Boolean => Test.Ada_Boolean, - when others => True) - then - Ast := Ast.List.Element (3); + if Test /= Mal.Nil and Test /= (Kind_Boolean, False) then + Ast := Ast.Sequence (3); goto Restart; - elsif Ast.List.Length = 3 then + elsif Ast.Sequence.Length = 3 then return Mal.Nil; else - Ast := Ast.List.Element (4); + Ast := Ast.Sequence (4); goto Restart; end if; end; elsif First.Symbol = Symbols.Names.Let then - if Ast.List.Length /= 3 then - raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "let*: expects a list or vector"; - end if; + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind in Kind_Sequence, + "parameter 1 must be a sequence"); declare - Bindings : constant Lists.Ptr := Ast.List.Element (2).List; + Bindings : constant Sequences.Ptr := Ast.Sequence (2).Sequence; begin - if Bindings.Length mod 2 /= 0 then - raise Argument_Error with "let*: odd number of bindings"; - end if; + Err.Check (Bindings.Length mod 2 = 0, + "parameter 1 must have an even length"); Env.Replace_With_Sub; for I in 1 .. Bindings.Length / 2 loop - if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then - raise Argument_Error with "let*: keys must be symbols"; - end if; - Env.Set (Bindings.Element (2 * I - 1).Symbol, - Eval (Bindings.Element (2 * I), Env)); + Err.Check (Bindings (2 * I - 1).Kind = Kind_Symbol, + "binding keys must be symbols"); + Env.Set (Bindings (2 * I - 1).Symbol, + Eval (Bindings (2 * I), Env)); end loop; - Ast := Ast.List.Element (3); + Ast := Ast.Sequence (3); goto Restart; end; elsif First.Symbol = Symbols.Names.Macroexpand then - if Ast.List.Length /= 2 then - raise Argument_Error with "macroexpand: expects 1 argument"; - end if; + Err.Check (Ast.Sequence.Length = 2, "expected 1 parameter"); Macroexpanding := True; - Ast := Ast.List.Element (2); + Ast := Ast.Sequence (2); goto Restart; elsif First.Symbol = Symbols.Names.Quasiquote then - if Ast.List.Length /= 2 then - raise Argument_Error with "quasiquote: expects 1 argument"; - end if; - return Quasiquote (Ast.List.Element (2), Env); + Err.Check (Ast.Sequence.Length = 2, "expected 1 parameter"); + return Quasiquote (Ast.Sequence (2), Env); elsif First.Symbol = Symbols.Names.Quote then - if Ast.List.Length /= 2 then - raise Argument_Error with "quote: expects 1 argument"; - end if; - return Ast.List.Element (2); + Err.Check (Ast.Sequence.Length = 2, "expected 1 parameter"); + return Ast.Sequence (2); elsif First.Symbol = Symbols.Names.Try then - if Ast.List.Length = 2 then - Ast := Ast.List.Element (2); + if Ast.Sequence.Length = 2 then + Ast := Ast.Sequence (2); goto Restart; - elsif Ast.List.Length /= 3 then - raise Argument_Error with "try*: expects 1 or 2 arguments"; - elsif Ast.List.Element (3).Kind /= Kind_List then - raise Argument_Error with "try*: argument 2 must be a list"; end if; + Err.Check (Ast.Sequence.Length = 3, "expected 1 or 2 parameters"); + Err.Check (Ast.Sequence (3).Kind = Kind_List, + "parameter 2 must be a list"); declare - A3 : constant Lists.Ptr := Ast.List.Element (3).List; + A3 : constant Sequences.Ptr := Ast.Sequence (3).Sequence; begin - if A3.Length /= 3 then - raise Argument_Error with "try*: arg 2 must have 3 elements"; - elsif A3.Element (1).Kind /= Kind_Symbol - or else A3.Element (1).Symbol /= Symbols.Names.Catch - then - raise Argument_Error with "try*: arg 2 must be 'catch*'"; - elsif A3.Element (2).Kind /= Kind_Symbol then - raise Argument_Error with "catch*: expects a symbol"; - end if; + Err.Check (A3.Length = 3, "length of parameter 2 must be 3"); + Err.Check (A3 (1) = (Kind_Symbol, Symbols.Names.Catch), + "parameter 3 must start with 'catch*'"); + Err.Check (A3 (2).Kind = Kind_Symbol, + "a symbol must follow catch*"); begin - return Eval (Ast.List.Element (2), Env); + return Eval (Ast.Sequence (2), Env); exception - when E : Reader.Empty_Source | Argument_Error - | Reader.Reader_Error | Envs.Unknown_Key => - Env.Replace_With_Sub; - Env.Set (A3.Element (2).Symbol, - Mal.T'(Kind_String, ASU.To_Unbounded_String - (Ada.Exceptions.Exception_Message (E)))); - Ast := A3.Element (3); - goto Restart; - when Core.Exception_Throwed => - Env.Replace_With_Sub; - Env.Set (A3.Element (2).Symbol, Core.Last_Exception); - Ast := A3.Element (3); - goto Restart; - -- Other exceptions are unexpected. + when Err.Error => + Env.Replace_With_Sub; + Env.Set (A3 (2).Symbol, Err.Data); + Ast := A3 (3); + goto Restart; end; end; else @@ -240,13 +203,12 @@ procedure Step9_Try is -- except that we already know enough to spare a recursive call. First := Env.Get (First.Symbol); end if; - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function - | Kind_Builtin_With_Meta | Kind_Builtin => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key + | Kind_Macro | Kind_Function => -- Equivalent to First := Eval (First, Env) -- except that we already know enough to spare a recursive call. null; - when Kind_List | Kind_Vector | Kind_Map => + when Kind_Sequence | Kind_Map => -- Lists are definitely worth a recursion, and the two other -- cases should be rare (they will report an error later). First := Eval (First, Env); @@ -258,19 +220,19 @@ procedure Step9_Try is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; return First.Builtin.all (Args); end; - when Kind_Function => + when Kind_Fn => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; Env.Replace_With_Sub (Outer => First.Fn.Env, Binds => First.Fn.Params, @@ -279,33 +241,49 @@ procedure Step9_Try is goto Restart; end; when Kind_Macro => - if Macroexpanding then - -- Evaluate the macro with tail call optimization. - Env.Replace_With_Sub_Macro (Binds => First.Fn.Params, - Exprs => Ast.List); - Ast := First.Fn.Ast; - goto Restart; - else - -- Evaluate the macro normally. - Ast := Eval (Ast0 => First.Fn.Ast, - Env0 => Envs.Sub (Outer => Env, - Binds => First.Fn.Params, - Exprs => Ast.List)); - -- Then evaluate the result with TCO. - goto Restart; - end if; + declare + Args : constant Mal.T_Array + := Ast.Sequence.Tail (Ast.Sequence.Length - 1); + begin + if Macroexpanding then + -- Evaluate the macro with tail call optimization. + Env.Replace_With_Sub (Binds => First.Fn.Params, + Exprs => Args); + Ast := First.Fn.Ast; + goto Restart; + else + -- Evaluate the macro normally. + Ast := Eval (First.Fn.Ast, Envs.Sub + (Outer => Env, + Binds => First.Fn.Params, + Exprs => Args)); + -- Then evaluate the result with TCO. + goto Restart; + end if; + end; when others => - raise Argument_Error with "cannot call " & Printer.Img (First); + Err.Raise_With ("first element must be a function or macro"); end case; + exception + when Err.Error => + if Macroexpanding then + Err.Add_Trace_Line ("macroexpand", Ast); + else + Err.Add_Trace_Line ("eval", Ast); + end if; + raise; end Eval; - procedure Eval_P (Ast : in Mal.T; - Env : in Envs.Ptr) + procedure Exec (Script : in String; + Env : in Envs.Ptr) is - Result : constant Mal.T := Eval (Ast, Env); + Result : Mal.T; begin + for Expression of Reader.Read_Str (Script) loop + Result := Eval (Expression, Env); + end loop; pragma Unreferenced (Result); - end Eval_P; + end Exec; procedure Print (Ast : in Mal.T) is begin @@ -316,71 +294,70 @@ procedure Step9_Try is Env : in Envs.Ptr) return Mal.T is - use type Symbols.Ptr; - - function Quasiquote_List (List : in Lists.Ptr) return Mal.T with Inline; + function Quasiquote_List (List : in Sequences.Ptr) return Mal.T + with Inline; -- Handle vectors and lists not starting with unquote. - function Quasiquote_List (List : in Lists.Ptr) return Mal.T is + function Quasiquote_List (List : in Sequences.Ptr) return Mal.T is -- The final return concatenates these lists. R : Mal.T_Array (1 .. List.Length); begin for I in R'Range loop - R (I) := List.Element (I); - if R (I).Kind in Kind_List | Kind_Vector - and then 0 < R (I).List.Length - and then R (I).List.Element (1).Kind = Kind_Symbol - and then R (I).List.Element (1).Symbol - = Symbols.Names.Splice_Unquote + R (I) := List (I); + if R (I).Kind in Kind_List and then 0 < R (I).Sequence.Length + and then R (I).Sequence (1) = (Kind_Symbol, + Symbols.Names.Splice_Unquote) then - if R (I).List.Length /= 2 then - raise Argument_Error with "splice-unquote: expects 1 arg"; - end if; - R (I) := Eval (R (I).List.Element (2), Env); - if R (I).Kind /= Kind_List then - raise Argument_Error with "splice-unquote: expects a list"; - end if; + Err.Check (R (I).Sequence.Length = 2, + "splice-unquote expects 1 parameter"); + R (I) := Eval (@.Sequence (2), Env); + Err.Check (R (I).Kind = Kind_List, + "splice_unquote expects a list"); else - R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), - Env))); + R (I) := Sequences.List + (Mal.T_Array'(1 => Quasiquote (@, Env))); end if; end loop; - return Lists.Concat (R); + return Sequences.Concat (R); end Quasiquote_List; begin -- Quasiquote case Ast.Kind is when Kind_Vector => -- When the test is updated, replace Kind_List with Kind_Vector. - return Quasiquote_List (Ast.List); + return Quasiquote_List (Ast.Sequence); when Kind_List => - if 0 < Ast.List.Length - and then Ast.List.Element (1).Kind = Kind_Symbol - and then Ast.List.Element (1).Symbol = Symbols.Names.Unquote + if 0 < Ast.Sequence.Length + and then Ast.Sequence (1) = (Kind_Symbol, Symbols.Names.Unquote) then - if 2 < Ast.List.Length then - raise Argument_Error with "unquote: expects 1 argument"; - end if; - return Eval (Ast.List.Element (2), Env); + Err.Check (Ast.Sequence.Length = 2, "expected 1 parameter"); + return Eval (Ast.Sequence (2), Env); else - return Quasiquote_List (Ast.List); + return Quasiquote_List (Ast.Sequence); end if; when others => return Ast; end case; + exception + when Err.Error => + Err.Add_Trace_Line ("quasiquote", Ast); + raise; end Quasiquote; - function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + function Read return Mal.T_Array + is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is begin - Print (Eval (Read, Env)); + for Expression of Read loop + Print (Eval (Expression, Env)); + end loop; end Rep; ---------------------------------------------------------------------- - Startup : constant String := "(do " - & "(def! not (fn* (a) (if a false true)))" + Startup : constant String + := "(def! not (fn* (a) (if a false true)))" & "(def! load-file (fn* (f)" & " (eval (read-string (str ""(do "" (slurp f) "")"")))))" & "(defmacro! cond (fn* (& xs)" @@ -393,31 +370,28 @@ procedure Step9_Try is & " (if (empty? xs) nil" & " (if (= 1 (count xs)) (first xs)" & " `(let* (or_FIXME ~(first xs))" - & " (if or_FIXME or_FIXME (or ~@(rest xs))))))))" - & ")"; + & " (if or_FIXME or_FIXME (or ~@(rest xs))))))))"; Repl : Envs.Ptr renames Envs.Repl; - use Ada.Command_Line; begin -- Show the Eval function to other packages. Eval_Cb.Cb := Eval'Unrestricted_Access; -- Add Core functions into the top environment. - for Binding of Core.Ns loop - Repl.Set (Binding.Symbol, (Kind_Builtin, Binding.Builtin)); - end loop; + Core.NS_Add_To_Repl; -- Native startup procedure. - Eval_P (Reader.Read_Str (Startup), Repl); + Exec (Startup, Repl); -- Define ARGV from command line arguments. declare + use Ada.Command_Line; Args : Mal.T_Array (2 .. Argument_Count); begin for I in Args'Range loop Args (I) := (Kind_String, ASU.To_Unbounded_String (Argument (I))); end loop; - Repl.Set (Symbols.Constructor ("*ARGV*"), Lists.List (Args)); + Repl.Set (Symbols.Constructor ("*ARGV*"), Sequences.List (Args)); end; -- Script? - if 0 < Argument_Count then - Eval_P (Reader.Read_Str ("(load-file """ & Argument (1) & """)"), Repl); + if 0 < Ada.Command_Line.Argument_Count then + Exec ("(load-file """ & Ada.Command_Line.Argument (1) & """)", Repl); else loop begin @@ -425,17 +399,20 @@ begin exception when Readline.End_Of_File => exit; - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - when Core.Exception_Throwed => - Ada.Text_IO.Put ("User exception: "); - Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str - (Core.Last_Exception)); - -- Other exceptions are unexpected. + when Err.Error => + Ada.Text_IO.Unbounded_IO.Put (Err.Trace); end; + -- Other exceptions are really unexpected. end loop; Ada.Text_IO.New_Line; end if; + -- If assertions are enabled, check deallocations. + Err.Data := Mal.Nil; -- Remove references to other packages + pragma Debug (Envs.Clear_And_Check_Allocations); + pragma Debug (Atoms.Check_Allocations); + pragma Debug (Builtins.Check_Allocations); + pragma Debug (Fns.Check_Allocations); + pragma Debug (Maps.Check_Allocations); + pragma Debug (Sequences.Check_Allocations); + pragma Debug (Symbols.Check_Allocations); end Step9_Try; diff --git a/ada.2/stepa_mal.adb b/ada.2/stepa_mal.adb index d3b44b0d95..9c24a4b6aa 100644 --- a/ada.2/stepa_mal.adb +++ b/ada.2/stepa_mal.adb @@ -1,26 +1,36 @@ with Ada.Command_Line; -with Ada.Exceptions; +with Ada.Environment_Variables; with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; with Core; with Envs; +with Err; with Eval_Cb; with Printer; with Reader; with Readline; -with Types.Functions; -with Types.Lists; +with Types.Atoms; +with Types.Builtins; +with Types.Fns; with Types.Mal; with Types.Maps; +with Types.Sequences; with Types.Symbols.Names; procedure StepA_Mal is - package ASU renames Ada.Strings.Unbounded; + Dbgenv1 : constant Boolean := Ada.Environment_Variables.Exists ("dbgenv1"); + Dbgenv0 : constant Boolean + := Dbgenv1 or Ada.Environment_Variables.Exists ("dbgenv0"); + Dbgeval : constant Boolean + := Dbgenv0 or Ada.Environment_Variables.Exists ("dbgeval"); + use Types; + use type Mal.T; + package ASU renames Ada.Strings.Unbounded; - function Read return Mal.T with Inline; + function Read return Mal.T_Array with Inline; function Eval (Ast0 : in Mal.T; Env0 : in Envs.Ptr) return Mal.T; @@ -36,13 +46,12 @@ procedure StepA_Mal is procedure Rep (Env : in Envs.Ptr) with Inline; - function Eval_List_Elts is new Lists.Generic_Eval (Envs.Ptr, Eval); - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + function Eval_Seq_Elts is new Sequences.Generic_Eval (Envs.Ptr, Eval); + function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); - -- Procedural form of Eval. - -- Convenient when the result of eval is of no interest. - procedure Eval_P (Ast : in Mal.T; - Env : in Envs.Ptr) with Inline; + procedure Exec (Script : in String; + Env : in Envs.Ptr) with Inline; + -- Read the script, eval its elements, but ignore the result. ---------------------------------------------------------------------- @@ -58,181 +67,135 @@ procedure StepA_Mal is First : Mal.T; begin <> - -- Ada.Text_IO.New_Line; - -- Ada.Text_IO.Put ("EVAL: "); - -- Print (Ast); - -- Envs.Dump_Stack; + if Dbgeval then + Ada.Text_IO.New_Line; + Ada.Text_IO.Put ("EVAL: "); + Print (Ast); + if Dbgenv0 then + Envs.Dump_Stack (Dbgenv1); + end if; + end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function - | Kind_Builtin_With_Meta | Kind_Builtin => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key + | Kind_Macro | Kind_Function => return Ast; when Kind_Symbol => return Env.Get (Ast.Symbol); when Kind_Map => return Eval_Map_Elts (Ast.Map, Env); when Kind_Vector => - return (Kind_Vector, Eval_List_Elts (Ast.List, Env)); + return (Kind_Vector, Eval_Seq_Elts (Ast.Sequence, Env)); when Kind_List => null; end case; -- Ast is a list. - if Ast.List.Length = 0 then + if Ast.Sequence.Length = 0 then return Ast; end if; - First := Ast.List.Element (1); + First := Ast.Sequence (1); -- Special forms -- Ast is a non-empty list, First is its first element. case First.Kind is when Kind_Symbol => if First.Symbol = Symbols.Names.Def then - if Ast.List.Length /= 3 then - raise Argument_Error with "def!: expects 2 arguments"; - elsif Ast.List.Element (2).Kind /= Kind_Symbol then - raise Argument_Error with "def!: arg 1 must be a symbol"; - end if; - return R : constant Mal.T := Eval (Ast.List.Element (3), Env) do - Env.Set (Ast.List.Element (2).Symbol, R); + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind = Kind_Symbol, + "parameter 1 must be a symbol"); + return R : constant Mal.T := Eval (Ast.Sequence (3), Env) do + Env.Set (Ast.Sequence (2).Symbol, R); end return; elsif First.Symbol = Symbols.Names.Defmacro then - if Ast.List.Length /= 3 then - raise Argument_Error with "defmacro!: expects 2 arguments"; - elsif Ast.List.Element (2).Kind /= Kind_Symbol then - raise Argument_Error with "defmacro!: arg 1 must be a symbol"; - end if; + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind = Kind_Symbol, + "parameter 1 must be a symbol"); declare - F : constant Mal.T := Eval (Ast.List.Element (3), Env); + F : constant Mal.T := Eval (Ast.Sequence (3), Env); begin - if F.Kind /= Kind_Function then - raise Argument_Error with "defmacro!: expects a function"; - end if; + Err.Check (F.Kind = Kind_Fn, "parameter 2 must be a function"); return R : constant Mal.T := F.Fn.New_Macro do - Env.Set (Ast.List.Element (2).Symbol, R); + Env.Set (Ast.Sequence (2).Symbol, R); end return; end; - elsif First.Symbol = Symbols.Names.Mal_Do then - if Ast.List.Length = 1 then - raise Argument_Error with "do: expects at least 1 argument"; - end if; - for I in 2 .. Ast.List.Length - 1 loop - Eval_P (Ast.List.Element (I), Env); - end loop; - Ast := Ast.List.Element (Ast.List.Length); - goto Restart; + -- do is a built-in function, shortening this test cascade. elsif First.Symbol = Symbols.Names.Fn then - if Ast.List.Length /= 3 then - raise Argument_Error with "fn*: expects 3 arguments"; - elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "fn*: arg 1 must be a list or vector"; - elsif (for some F in 1 .. Ast.List.Element (2).List.Length => - Ast.List.Element (2).List.Element (F).Kind /= Kind_Symbol) - then - raise Argument_Error with "fn*: arg 2 must contain symbols"; - end if; - return Functions.New_Function (Params => Ast.List.Element (2).List, - Ast => Ast.List.Element (3), - Env => Env.New_Closure); + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind in Kind_Sequence, + "parameter 1 must be a sequence"); + return Fns.New_Function (Params => Ast.Sequence (2).Sequence, + Ast => Ast.Sequence (3), + Env => Env.New_Closure); elsif First.Symbol = Symbols.Names.Mal_If then - if Ast.List.Length not in 3 .. 4 then - raise Argument_Error with "if: expects 2 or 3 arguments"; - end if; + Err.Check (Ast.Sequence.Length in 3 .. 4, + "expected 2 or 3 parameters"); declare - Test : constant Mal.T := Eval (Ast.List.Element (2), Env); + Test : constant Mal.T := Eval (Ast.Sequence (2), Env); begin - if (case Test.Kind is - when Kind_Nil => False, - when Kind_Boolean => Test.Ada_Boolean, - when others => True) - then - Ast := Ast.List.Element (3); + if Test /= Mal.Nil and Test /= (Kind_Boolean, False) then + Ast := Ast.Sequence (3); goto Restart; - elsif Ast.List.Length = 3 then + elsif Ast.Sequence.Length = 3 then return Mal.Nil; else - Ast := Ast.List.Element (4); + Ast := Ast.Sequence (4); goto Restart; end if; end; elsif First.Symbol = Symbols.Names.Let then - if Ast.List.Length /= 3 then - raise Argument_Error with "let*: expects 3 arguments"; - elsif Ast.List.Element (2).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "let*: expects a list or vector"; - end if; + Err.Check (Ast.Sequence.Length = 3, "expected 2 parameters"); + Err.Check (Ast.Sequence (2).Kind in Kind_Sequence, + "parameter 1 must be a sequence"); declare - Bindings : constant Lists.Ptr := Ast.List.Element (2).List; + Bindings : constant Sequences.Ptr := Ast.Sequence (2).Sequence; begin - if Bindings.Length mod 2 /= 0 then - raise Argument_Error with "let*: odd number of bindings"; - end if; + Err.Check (Bindings.Length mod 2 = 0, + "parameter 1 must have an even length"); Env.Replace_With_Sub; for I in 1 .. Bindings.Length / 2 loop - if Bindings.Element (2 * I - 1).Kind /= Kind_Symbol then - raise Argument_Error with "let*: keys must be symbols"; - end if; - Env.Set (Bindings.Element (2 * I - 1).Symbol, - Eval (Bindings.Element (2 * I), Env)); + Err.Check (Bindings (2 * I - 1).Kind = Kind_Symbol, + "binding keys must be symbols"); + Env.Set (Bindings (2 * I - 1).Symbol, + Eval (Bindings (2 * I), Env)); end loop; - Ast := Ast.List.Element (3); + Ast := Ast.Sequence (3); goto Restart; end; elsif First.Symbol = Symbols.Names.Macroexpand then - if Ast.List.Length /= 2 then - raise Argument_Error with "macroexpand: expects 1 argument"; - end if; + Err.Check (Ast.Sequence.Length = 2, "expected 1 parameter"); Macroexpanding := True; - Ast := Ast.List.Element (2); + Ast := Ast.Sequence (2); goto Restart; elsif First.Symbol = Symbols.Names.Quasiquote then - if Ast.List.Length /= 2 then - raise Argument_Error with "quasiquote: expects 1 argument"; - end if; - return Quasiquote (Ast.List.Element (2), Env); + Err.Check (Ast.Sequence.Length = 2, "expected 1 parameter"); + return Quasiquote (Ast.Sequence (2), Env); elsif First.Symbol = Symbols.Names.Quote then - if Ast.List.Length /= 2 then - raise Argument_Error with "quote: expects 1 argument"; - end if; - return Ast.List.Element (2); + Err.Check (Ast.Sequence.Length = 2, "expected 1 parameter"); + return Ast.Sequence (2); elsif First.Symbol = Symbols.Names.Try then - if Ast.List.Length = 2 then - Ast := Ast.List.Element (2); + if Ast.Sequence.Length = 2 then + Ast := Ast.Sequence (2); goto Restart; - elsif Ast.List.Length /= 3 then - raise Argument_Error with "try*: expects 1 or 2 arguments"; - elsif Ast.List.Element (3).Kind /= Kind_List then - raise Argument_Error with "try*: argument 2 must be a list"; end if; + Err.Check (Ast.Sequence.Length = 3, "expected 1 or 2 parameters"); + Err.Check (Ast.Sequence (3).Kind = Kind_List, + "parameter 2 must be a list"); declare - A3 : constant Lists.Ptr := Ast.List.Element (3).List; + A3 : constant Sequences.Ptr := Ast.Sequence (3).Sequence; begin - if A3.Length /= 3 then - raise Argument_Error with "try*: arg 2 must have 3 elements"; - elsif A3.Element (1).Kind /= Kind_Symbol - or else A3.Element (1).Symbol /= Symbols.Names.Catch - then - raise Argument_Error with "try*: arg 2 must be 'catch*'"; - elsif A3.Element (2).Kind /= Kind_Symbol then - raise Argument_Error with "catch*: expects a symbol"; - end if; + Err.Check (A3.Length = 3, "length of parameter 2 must be 3"); + Err.Check (A3 (1) = (Kind_Symbol, Symbols.Names.Catch), + "parameter 3 must start with 'catch*'"); + Err.Check (A3 (2).Kind = Kind_Symbol, + "a symbol must follow catch*"); begin - return Eval (Ast.List.Element (2), Env); + return Eval (Ast.Sequence (2), Env); exception - when E : Reader.Empty_Source | Argument_Error - | Reader.Reader_Error | Envs.Unknown_Key => - Env.Replace_With_Sub; - Env.Set (A3.Element (2).Symbol, - Mal.T'(Kind_String, ASU.To_Unbounded_String - (Ada.Exceptions.Exception_Message (E)))); - Ast := A3.Element (3); - goto Restart; - when Core.Exception_Throwed => - Env.Replace_With_Sub; - Env.Set (A3.Element (2).Symbol, Core.Last_Exception); - Ast := A3.Element (3); - goto Restart; - -- Other exceptions are unexpected. + when Err.Error => + Env.Replace_With_Sub; + Env.Set (A3 (2).Symbol, Err.Data); + Ast := A3 (3); + goto Restart; end; end; else @@ -240,13 +203,12 @@ procedure StepA_Mal is -- except that we already know enough to spare a recursive call. First := Env.Get (First.Symbol); end if; - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_String - | Kind_Keyword | Kind_Macro | Kind_Function - | Kind_Builtin_With_Meta | Kind_Builtin => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key + | Kind_Macro | Kind_Function => -- Equivalent to First := Eval (First, Env) -- except that we already know enough to spare a recursive call. null; - when Kind_List | Kind_Vector | Kind_Map => + when Kind_Sequence | Kind_Map => -- Lists are definitely worth a recursion, and the two other -- cases should be rare (they will report an error later). First := Eval (First, Env); @@ -258,28 +220,28 @@ procedure StepA_Mal is case First.Kind is when Kind_Builtin => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; return First.Builtin.all (Args); end; when Kind_Builtin_With_Meta => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; return First.Builtin_With_Meta.Builtin.all (Args); end; - when Kind_Function => + when Kind_Fn => declare - Args : Mal.T_Array (2 .. Ast.List.Length); + Args : Mal.T_Array (2 .. Ast.Sequence.Length); begin for I in Args'Range loop - Args (I) := Eval (Ast.List.Element (I), Env); + Args (I) := Eval (Ast.Sequence (I), Env); end loop; Env.Replace_With_Sub (Outer => First.Fn.Env, Binds => First.Fn.Params, @@ -288,33 +250,49 @@ procedure StepA_Mal is goto Restart; end; when Kind_Macro => - if Macroexpanding then - -- Evaluate the macro with tail call optimization. - Env.Replace_With_Sub_Macro (Binds => First.Fn.Params, - Exprs => Ast.List); - Ast := First.Fn.Ast; - goto Restart; - else - -- Evaluate the macro normally. - Ast := Eval (Ast0 => First.Fn.Ast, - Env0 => Envs.Sub (Outer => Env, - Binds => First.Fn.Params, - Exprs => Ast.List)); - -- Then evaluate the result with TCO. - goto Restart; - end if; + declare + Args : constant Mal.T_Array + := Ast.Sequence.Tail (Ast.Sequence.Length - 1); + begin + if Macroexpanding then + -- Evaluate the macro with tail call optimization. + Env.Replace_With_Sub (Binds => First.Fn.Params, + Exprs => Args); + Ast := First.Fn.Ast; + goto Restart; + else + -- Evaluate the macro normally. + Ast := Eval (First.Fn.Ast, Envs.Sub + (Outer => Env, + Binds => First.Fn.Params, + Exprs => Args)); + -- Then evaluate the result with TCO. + goto Restart; + end if; + end; when others => - raise Argument_Error with "cannot call " & Printer.Img (First); + Err.Raise_With ("first element must be a function or macro"); end case; + exception + when Err.Error => + if Macroexpanding then + Err.Add_Trace_Line ("macroexpand", Ast); + else + Err.Add_Trace_Line ("eval", Ast); + end if; + raise; end Eval; - procedure Eval_P (Ast : in Mal.T; - Env : in Envs.Ptr) + procedure Exec (Script : in String; + Env : in Envs.Ptr) is - Result : constant Mal.T := Eval (Ast, Env); + Result : Mal.T; begin + for Expression of Reader.Read_Str (Script) loop + Result := Eval (Expression, Env); + end loop; pragma Unreferenced (Result); - end Eval_P; + end Exec; procedure Print (Ast : in Mal.T) is begin @@ -325,71 +303,70 @@ procedure StepA_Mal is Env : in Envs.Ptr) return Mal.T is - use type Symbols.Ptr; - - function Quasiquote_List (List : in Lists.Ptr) return Mal.T with Inline; + function Quasiquote_List (List : in Sequences.Ptr) return Mal.T + with Inline; -- Handle vectors and lists not starting with unquote. - function Quasiquote_List (List : in Lists.Ptr) return Mal.T is + function Quasiquote_List (List : in Sequences.Ptr) return Mal.T is -- The final return concatenates these lists. R : Mal.T_Array (1 .. List.Length); begin for I in R'Range loop - R (I) := List.Element (I); - if R (I).Kind in Kind_List | Kind_Vector - and then 0 < R (I).List.Length - and then R (I).List.Element (1).Kind = Kind_Symbol - and then R (I).List.Element (1).Symbol - = Symbols.Names.Splice_Unquote + R (I) := List (I); + if R (I).Kind in Kind_List and then 0 < R (I).Sequence.Length + and then R (I).Sequence (1) = (Kind_Symbol, + Symbols.Names.Splice_Unquote) then - if R (I).List.Length /= 2 then - raise Argument_Error with "splice-unquote: expects 1 arg"; - end if; - R (I) := Eval (R (I).List.Element (2), Env); - if R (I).Kind /= Kind_List then - raise Argument_Error with "splice-unquote: expects a list"; - end if; + Err.Check (R (I).Sequence.Length = 2, + "splice-unquote expects 1 parameter"); + R (I) := Eval (@.Sequence (2), Env); + Err.Check (R (I).Kind = Kind_List, + "splice_unquote expects a list"); else - R (I) := Lists.List (Mal.T_Array'(1 => Quasiquote (R (I), - Env))); + R (I) := Sequences.List + (Mal.T_Array'(1 => Quasiquote (@, Env))); end if; end loop; - return Lists.Concat (R); + return Sequences.Concat (R); end Quasiquote_List; begin -- Quasiquote case Ast.Kind is when Kind_Vector => -- When the test is updated, replace Kind_List with Kind_Vector. - return Quasiquote_List (Ast.List); + return Quasiquote_List (Ast.Sequence); when Kind_List => - if 0 < Ast.List.Length - and then Ast.List.Element (1).Kind = Kind_Symbol - and then Ast.List.Element (1).Symbol = Symbols.Names.Unquote + if 0 < Ast.Sequence.Length + and then Ast.Sequence (1) = (Kind_Symbol, Symbols.Names.Unquote) then - if 2 < Ast.List.Length then - raise Argument_Error with "unquote: expects 1 argument"; - end if; - return Eval (Ast.List.Element (2), Env); + Err.Check (Ast.Sequence.Length = 2, "expected 1 parameter"); + return Eval (Ast.Sequence (2), Env); else - return Quasiquote_List (Ast.List); + return Quasiquote_List (Ast.Sequence); end if; when others => return Ast; end case; + exception + when Err.Error => + Err.Add_Trace_Line ("quasiquote", Ast); + raise; end Quasiquote; - function Read return Mal.T is (Reader.Read_Str (Readline.Input ("user> "))); + function Read return Mal.T_Array + is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is begin - Print (Eval (Read, Env)); + for Expression of Read loop + Print (Eval (Expression, Env)); + end loop; end Rep; ---------------------------------------------------------------------- - Startup : constant String := "(do " - & "(def! not (fn* (a) (if a false true)))" + Startup : constant String + := "(def! not (fn* (a) (if a false true)))" & "(def! load-file (fn* (f)" & " (eval (read-string (str ""(do "" (slurp f) "")"")))))" & "(defmacro! cond (fn* (& xs)" @@ -407,51 +384,50 @@ procedure StepA_Mal is & " (let* (condvar (gensym))" & " `(let* (~condvar ~(first xs))" & " (if ~condvar ~condvar (or ~@(rest xs)))))))))" - & "(def! *host-language* ""ada2"")" - & ")"; + & "(def! *host-language* ""ada.2"")"; Repl : Envs.Ptr renames Envs.Repl; - use Ada.Command_Line; begin -- Show the Eval function to other packages. Eval_Cb.Cb := Eval'Unrestricted_Access; -- Add Core functions into the top environment. - for Binding of Core.Ns loop - Repl.Set (Binding.Symbol, (Kind_Builtin, Binding.Builtin)); - end loop; + Core.NS_Add_To_Repl; -- Native startup procedure. - Eval_P (Reader.Read_Str (Startup), Repl); + Exec (Startup, Repl); -- Define ARGV from command line arguments. declare + use Ada.Command_Line; Args : Mal.T_Array (2 .. Argument_Count); begin for I in Args'Range loop Args (I) := (Kind_String, ASU.To_Unbounded_String (Argument (I))); end loop; - Repl.Set (Symbols.Constructor ("*ARGV*"), Lists.List (Args)); + Repl.Set (Symbols.Constructor ("*ARGV*"), Sequences.List (Args)); end; -- Script? - if 0 < Argument_Count then - Eval_P (Reader.Read_Str ("(load-file """ & Argument (1) & """)"), Repl); + if 0 < Ada.Command_Line.Argument_Count then + Exec ("(load-file """ & Ada.Command_Line.Argument (1) & """)", Repl); else - Eval_P (Reader.Read_Str - ("(println (str ""Mal ["" *host-language* ""]""))"), Repl); + Exec ("(println (str ""Mal ["" *host-language* ""]""))", Repl); loop begin Rep (Repl); exception when Readline.End_Of_File => exit; - when Reader.Empty_Source => - null; - when E : Argument_Error | Reader.Reader_Error | Envs.Unknown_Key => - Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); - when Core.Exception_Throwed => - Ada.Text_IO.Put ("User exception: "); - Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str - (Core.Last_Exception)); - -- Other exceptions are unexpected. + when Err.Error => + Ada.Text_IO.Unbounded_IO.Put (Err.Trace); end; + -- Other exceptions are really unexpected. end loop; Ada.Text_IO.New_Line; end if; + -- If assertions are enabled, check deallocations. + Err.Data := Mal.Nil; -- Remove references to other packages + pragma Debug (Envs.Clear_And_Check_Allocations); + pragma Debug (Atoms.Check_Allocations); + pragma Debug (Builtins.Check_Allocations); + pragma Debug (Fns.Check_Allocations); + pragma Debug (Maps.Check_Allocations); + pragma Debug (Sequences.Check_Allocations); + pragma Debug (Symbols.Check_Allocations); end StepA_Mal; diff --git a/ada.2/types-atoms.adb b/ada.2/types-atoms.adb index 952504a1e5..54591a5922 100644 --- a/ada.2/types-atoms.adb +++ b/ada.2/types-atoms.adb @@ -1,6 +1,6 @@ with Ada.Unchecked_Deallocation; -with Printer; +with Err; with Types.Mal; package body Types.Atoms is @@ -11,29 +11,35 @@ package body Types.Atoms is end record; procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); + Allocations : Natural := 0; ---------------------------------------------------------------------- procedure Adjust (Object : in out Ptr) is begin - Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + Object.Ref.all.Refs := @ + 1; end Adjust; - function Atom (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 then - raise Argument_Error with "atom: expects 1 argument" - else - (Kind_Atom, (Ada.Finalization.Controlled with new Rec' - (Refs => 1, - Data => Args (Args'First))))); + function Atom (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 1, "expected 1 parameter"); + Allocations := Allocations + 1; + return (Kind_Atom, (Ada.Finalization.Controlled with new Rec' + (Refs => 1, + Data => Args (Args'First)))); + end Atom; + + procedure Check_Allocations is + begin + pragma Assert (Allocations = 0); + end Check_Allocations; - function Deref (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 then - raise Argument_Error with "deref: expects 1 argument" - elsif Args (Args'First).Kind /= Kind_Atom then - raise Argument_Error with "deref: expects an atom" - else - Args (Args'First).Atom.Ref.all.Data); + function Deref (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 1, "expected 1 parameter"); + Err.Check (Args (Args'First).Kind = Kind_Atom, "expected an atom"); + return Args (Args'First).Atom.Ref.all.Data; + end Deref; function Deref (Item : in Ptr) return Mal.T is (Item.Ref.all.Data); @@ -41,10 +47,11 @@ package body Types.Atoms is procedure Finalize (Object : in out Ptr) is begin if Object.Ref /= null and then 0 < Object.Ref.all.Refs then - Object.Ref.all.Refs := Object.Ref.all.Refs - 1; + Object.Ref.all.Refs := @ - 1; if 0 < Object.Ref.all.Refs then Object.Ref := null; else + Allocations := Allocations - 1; Free (Object.Ref); end if; end if; @@ -52,22 +59,18 @@ package body Types.Atoms is function Reset (Args : in Mal.T_Array) return Mal.T is begin - if Args'Length /= 2 then - raise Argument_Error with "reset: expects 2 arguments"; - elsif Args (Args'First).Kind /= Kind_Atom then - raise Argument_Error with "reset: first argument must be an atom"; - end if; + Err.Check (Args'Length = 2, "expected 2 parameters"); + Err.Check (Args (Args'First).Kind = Kind_Atom, + "parameter 1 must be an atom"); Args (Args'First).Atom.Ref.all.Data := Args (Args'Last); return Args (Args'Last); end Reset; function Swap (Args : in Mal.T_Array) return Mal.T is begin - if Args'Length < 2 then - raise Argument_Error with "swap!: expects at least 2 arguments"; - elsif Args (Args'First).Kind /= Kind_Atom then - raise Argument_Error with "swap!: first argument must be an atom"; - end if; + Err.Check (2 <= Args'Length, "expected at least 2 parameters"); + Err.Check (Args (Args'First).Kind = Kind_Atom, + "parameter 1 must be an atom"); declare use type Mal.T_Array; X : Mal.T renames Args (Args'First).Atom.Ref.all.Data; @@ -79,11 +82,10 @@ package body Types.Atoms is X := F.Builtin.all (A); when Kind_Builtin_With_Meta => X := F.Builtin_With_Meta.Builtin.all (A); - when Kind_Function => + when Kind_Fn => X := F.Fn.Apply (A); when others => - raise Argument_Error - with "swap!: cannot call " & Printer.Img (F); + Err.Raise_With ("parameter 2 must be a function"); end case; return X; end; diff --git a/ada.2/types-atoms.ads b/ada.2/types-atoms.ads index 057ef036cb..c9a4f09a57 100644 --- a/ada.2/types-atoms.ads +++ b/ada.2/types-atoms.ads @@ -15,6 +15,9 @@ package Types.Atoms is -- Helper for print. function Deref (Item : in Ptr) return Mal.T with Inline; + -- Debug. + procedure Check_Allocations; + private type Rec; diff --git a/ada.2/types-builtins.adb b/ada.2/types-builtins.adb index 450574795e..9506aa89b0 100644 --- a/ada.2/types-builtins.adb +++ b/ada.2/types-builtins.adb @@ -11,24 +11,31 @@ package body Types.Builtins is end record; procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); + Allocations : Natural := 0; ---------------------------------------------------------------------- procedure Adjust (Object : in out Ptr) is begin - Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + Object.Ref.all.Refs := @ + 1; end Adjust; function Builtin (Item : in Ptr) return Mal.Builtin_Ptr is (Item.Ref.all.Builtin); + procedure Check_Allocations is + begin + pragma Assert (Allocations = 0); + end Check_Allocations; + procedure Finalize (Object : in out Ptr) is begin if Object.Ref /= null and then 0 < Object.Ref.all.Refs then - Object.Ref.all.Refs := Object.Ref.all.Refs - 1; + Object.Ref.all.Refs := @ - 1; if 0 < Object.Ref.all.Refs then Object.Ref := null; else + Allocations := Allocations - 1; Free (Object.Ref); end if; end if; @@ -38,11 +45,14 @@ package body Types.Builtins is is (Item.Ref.all.Meta); function With_Meta (Builtin : in Mal.Builtin_Ptr; - Metadata : in Mal.T) return Mal.T - is (Kind_Builtin_With_Meta, (Ada.Finalization.Controlled with new Rec' - (Builtin => Builtin, - Meta => Metadata, - Refs => 1))); + Metadata : in Mal.T) return Mal.T is + begin + Allocations := Allocations + 1; + return (Kind_Builtin_With_Meta, + (Ada.Finalization.Controlled with new Rec'(Builtin => Builtin, + Meta => Metadata, + Refs => 1))); + end With_Meta; function With_Meta (Item : in Ptr; Metadata : in Mal.T) return Mal.T diff --git a/ada.2/types-builtins.ads b/ada.2/types-builtins.ads index e5d99fbdc7..a5a4af9901 100644 --- a/ada.2/types-builtins.ads +++ b/ada.2/types-builtins.ads @@ -18,6 +18,8 @@ package Types.Builtins is function Meta (Item : in Ptr) return Mal.T with Inline; function Builtin (Item : in Ptr) return Mal.Builtin_Ptr with Inline; + procedure Check_Allocations; + private type Rec; diff --git a/ada.2/types-functions.adb b/ada.2/types-fns.adb similarity index 57% rename from ada.2/types-functions.adb rename to ada.2/types-fns.adb index 2446849341..d96790970d 100644 --- a/ada.2/types-functions.adb +++ b/ada.2/types-fns.adb @@ -1,12 +1,13 @@ with Ada.Unchecked_Deallocation; with Envs; +with Err; with Eval_Cb; -with Types.Lists; with Types.Mal; +with Types.Sequences; with Types.Symbols; -package body Types.Functions is +package body Types.Fns is subtype AFC is Ada.Finalization.Controlled; use type Envs.Closure_Ptr; @@ -20,12 +21,13 @@ package body Types.Functions is end record; procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); + Allocations : Natural := 0; ---------------------------------------------------------------------- procedure Adjust (Object : in out Ptr) is begin - Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + Object.Ref.all.Refs := @ + 1; end Adjust; function Apply (Item : in Ptr; @@ -41,6 +43,11 @@ package body Types.Functions is function Ast (Item : in Ptr) return Mal.T is (Item.Ref.all.Ast); + procedure Check_Allocations is + begin + pragma Assert (Allocations = 0); + end Check_Allocations; + function Env (Item : in Ptr) return Envs.Closure_Ptr is begin pragma Assert (Item.Ref.all.Env /= Envs.Null_Closure); @@ -50,10 +57,11 @@ package body Types.Functions is procedure Finalize (Object : in out Ptr) is begin if Object.Ref /= null and then 0 < Object.Ref.all.Refs then - Object.Ref.all.Refs := Object.Ref.all.Refs - 1; + Object.Ref.all.Refs := @ - 1; if 0 < Object.Ref.all.Refs then Object.Ref := null; else + Allocations := Allocations - 1; Free (Object.Ref); end if; end if; @@ -68,37 +76,43 @@ package body Types.Functions is return Item.Ref.all.Meta; end Meta; - function New_Function (Params : in Lists.Ptr; + function New_Function (Params : in Sequences.Ptr; Ast : in Mal.T; Env : in Envs.Closure_Ptr) return Mal.T is - Ref : constant Acc := new Rec'(Params_Last => Params.Length, - Ast => Ast, - Env => Env, - others => <>); + Ref : Acc; begin - for I in 1 .. Params.Length loop - Ref.all.Params (I) := Params.Element (I).Symbol; - end loop; - return (Kind_Function, (AFC with Ref)); + Allocations := Allocations + 1; + -- Avoid exceptions until Ref is controlled. + Ref := new Rec'(Params_Last => Params.Length, + Ast => Ast, + Env => Env, + others => <>); + return R : constant Mal.T := (Kind_Fn, (AFC with Ref)) do + for I in 1 .. Params.Length loop + Err.Check (Params (I).Kind = Kind_Symbol, + "formal parameters must be symbols"); + Ref.all.Params (I) := Params (I).Symbol; + end loop; + end return; end New_Function; function New_Macro (Item : in Ptr) return Mal.T is - Old : Rec renames Item.Ref.all; - Ref : Acc; + -- Avoid raising an exception until Ref is controlled. + Ref : Acc := Item.Ref; begin - pragma Assert (0 < Old.Refs); - if Old.Refs = 1 then - Ref := Item.Ref; - Old.Refs := 2; - Old.Env := Envs.Null_Closure; + pragma Assert (0 < Ref.all.Refs); + if Ref.all.Refs = 1 then + Ref.all.Refs := 2; + Ref.all.Env := Envs.Null_Closure; -- Finalize the environment, it will not be used anymore. - Old.Meta := Mal.Nil; + Ref.all.Meta := Mal.Nil; else - Ref := new Rec'(Params_Last => Old.Params_Last, - Params => Old.Params, - Ast => Old.Ast, + Allocations := Allocations + 1; + Ref := new Rec'(Params_Last => Ref.all.Params_Last, + Params => Ref.all.Params, + Ast => Ref.all.Ast, others => <>); end if; return (Kind_Macro, (AFC with Ref)); @@ -107,24 +121,24 @@ package body Types.Functions is function With_Meta (Item : in Ptr; Metadata : in Mal.T) return Mal.T is - Old : Rec renames Item.Ref.all; - Ref : Acc; + -- Avoid raising an exception until Ref is controlled. + Ref : Acc := Item.Ref; begin - pragma Assert (Old.Env /= Envs.Null_Closure); - pragma Assert (0 < Old.Refs); - if Old.Refs = 1 then - Ref := Item.Ref; - Old.Refs := 2; - Old.Meta := Metadata; + pragma Assert (Ref.all.Env /= Envs.Null_Closure); + pragma Assert (0 < Ref.all.Refs); + if Ref.all.Refs = 1 then + Ref.all.Refs := 2; + Ref.all.Meta := Metadata; else - Ref := new Rec'(Params_Last => Old.Params_Last, - Params => Old.Params, - Ast => Old.Ast, - Env => Old.Env, + Allocations := Allocations + 1; + Ref := new Rec'(Params_Last => Ref.all.Params_Last, + Params => Ref.all.Params, + Ast => Ref.all.Ast, + Env => Ref.all.Env, Meta => Metadata, others => <>); end if; - return (Kind_Function, (AFC with Ref)); + return (Kind_Fn, (AFC with Ref)); end With_Meta; -end Types.Functions; +end Types.Fns; diff --git a/ada.2/types-functions.ads b/ada.2/types-fns.ads similarity index 86% rename from ada.2/types-functions.ads rename to ada.2/types-fns.ads index 8326245dcc..d3ba90fafc 100644 --- a/ada.2/types-functions.ads +++ b/ada.2/types-fns.ads @@ -1,19 +1,20 @@ private with Ada.Finalization; limited with Envs; -limited with Types.Lists; limited with Types.Mal; +limited with Types.Sequences; limited with Types.Symbols; -package Types.Functions is +package Types.Fns is type Ptr is tagged private; -- A pointer to an user-defined function or macro. - function New_Function (Params : in Lists.Ptr; + function New_Function (Params : in Sequences.Ptr; Ast : in Mal.T; Env : in Envs.Closure_Ptr) return Mal.T with Inline; + -- Raise an exception if Params contains something else than symbols. function New_Macro (Item : in Ptr) return Mal.T with Inline; @@ -34,6 +35,8 @@ package Types.Functions is Metadata : in Mal.T) return Mal.T with Inline; -- Fails for macros. + procedure Check_Allocations; + private type Rec; @@ -46,4 +49,4 @@ private overriding procedure Finalize (Object : in out Ptr) with Inline; pragma Finalize_Storage_Only (Ptr); -end Types.Functions; +end Types.Fns; diff --git a/ada.2/types-lists.adb b/ada.2/types-lists.adb deleted file mode 100644 index 37ce66df78..0000000000 --- a/ada.2/types-lists.adb +++ /dev/null @@ -1,313 +0,0 @@ -with Ada.Unchecked_Deallocation; - -with Printer; -with Types.Mal; - -package body Types.Lists is - - subtype AFC is Ada.Finalization.Controlled; - use type Mal.T_Array; - - type Rec (Last : Natural) is limited record - Refs : Natural := 1; - Meta : Mal.T := Mal.Nil; - Data : Mal.T_Array (1 .. Last) := (others => Mal.Nil); - end record; - - procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); - - ---------------------------------------------------------------------- - - function "=" (Left, Right : in Ptr) return Boolean is - -- Should become Left.Ref.all.Data = Right.Ref.all.Data when - -- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89178 is fixed. - use type Mal.T; - L : Rec renames Left.Ref.all; - R : Rec renames Right.Ref.all; - begin - return L.Last = R.Last - and then (for all I in 1 .. L.Last => L.Data (I) = R.Data (I)); - end "="; - - function "&" (Left : in Mal.T_Array; - Right : in Ptr) return Mal.T_Array - is (Left & Right.Ref.all.Data); - - procedure Adjust (Object : in out Ptr) is - begin - Object.Ref.all.Refs := Object.Ref.all.Refs + 1; - end Adjust; - - function Concat (Args : in Mal.T_Array) return Mal.T is - Sum : Natural := 0; - Ref : Acc; - begin - for Arg of Args loop - if Arg.Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "concat: expects lists or vectors"; - end if; - Sum := Sum + Arg.List.Ref.all.Last; - end loop; - Ref := new Rec (Sum); - for Arg of reverse Args loop - Ref.all.Data (Sum - Arg.List.Ref.all.Last + 1 .. Sum) - := Arg.List.Ref.all.Data; - Sum := Sum - Arg.List.Ref.all.Last; - end loop; - pragma Assert (Sum = 0); - return (Kind_List, (AFC with Ref)); - end Concat; - - function Conj (Args : in Mal.T_Array) return Mal.T is - begin - if Args'Length = 0 then - raise Argument_Error with "conj: expects at least 1 argument"; - end if; - declare - A1 : Mal.T renames Args (Args'First); - Last : constant Natural := Args'Length - 1 + A1.List.Ref.all.Last; - Ref : constant Acc := new Rec (Last); - Data : Mal.T_Array renames Ref.all.Data; - begin - case A1.Kind is - when Kind_List => - Data (Args'Length .. Ref.all.Last) := A1.List.Ref.all.Data; - for I in 1 .. Args'Length - 1 loop - Data (I) := Args (Args'Last - I + 1); - end loop; - return (Kind_List, (AFC with Ref)); - when Kind_Vector => - Data := A1.List.Ref.all.Data - & Args (Args'First + 1 .. Args'Last); - return (Kind_Vector, (AFC with Ref)); - when others => - raise Argument_Error - with "conj: first argument must be a list or vector"; - end case; - end; - end Conj; - - function Cons (Args : in Mal.T_Array) return Mal.T is - begin - if Args'Length /= 2 then - raise Argument_Error with "cons: expects 2 arguments"; - elsif Args (Args'Last).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "cons: last arg must be a list or vector"; - end if; - return (Kind_List, (AFC with new Rec' - (Last => 1 + Args (Args'Last).List.Ref.all.Last, - Data => Args (Args'First) & Args (Args'Last).List.Ref.all.Data, - others => <>))); - end Cons; - - function Count (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 then - raise Argument_Error with "count: expects 1 argument" - else - (case Args (Args'First).Kind is - when Kind_Nil => - (Kind_Number, 0), - when Kind_List | Kind_Vector => - (Kind_Number, Args (Args'First).List.Ref.all.Last), - when others => - raise Argument_Error with "count: expects a list or vector")); - - function Element (Container : in Ptr; - Index : in Positive) return Mal.T - is (Container.Ref.all.Data (Index)); - - procedure Finalize (Object : in out Ptr) is - begin - if Object.Ref /= null and then 0 < Object.Ref.all.Refs then - Object.Ref.all.Refs := Object.Ref.all.Refs - 1; - if 0 < Object.Ref.all.Refs then - Object.Ref := null; - else - Free (Object.Ref); - end if; - end if; - end Finalize; - - function First (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 then - raise Argument_Error with "first: expects 1 argument" - else - (case Args (Args'First).Kind is - when Kind_Nil => - Mal.Nil, - when Kind_List | Kind_Vector => - (if Args (Args'First).List.Ref.all.Last = 0 then - Mal.Nil - else - Args (Args'First).List.Ref.all.Data (1)), - when others => - raise Argument_Error with "first: expects a list or vector")); - - function Generic_Eval (Container : in Ptr; - Env : in Env_Type) - return Ptr - is - -- Take care that automatic deallocation happens if an - -- exception is propagated by user code. - Old : Rec renames Container.Ref.all; - Ref : Acc; - begin - pragma Assert (0 < Old.Refs); - if Old.Refs = 1 then - Ref := Container.Ref; - Old.Refs := 2; - Old.Meta := Mal.Nil; - else - Ref := new Rec (Old.Last); - end if; - return R : constant Ptr := (AFC with Ref) do - for I in Old.Data'Range loop - Ref.all.Data (I) := Eval (Old.Data (I), Env); - end loop; - end return; - end Generic_Eval; - - function Is_Empty (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 1 then - raise Argument_Error with "empty?: expects 1 argument" - else - (case Args (Args'First).Kind is - when Kind_List | Kind_Vector => - (Kind_Boolean, Args (Args'First).List.Ref.all.Last = 0), - when others => - raise Argument_Error with "empty?: expects a list or vector")); - - function Length (Source : in Ptr) return Natural - is (Source.Ref.all.Last); - - function List (Args : in Mal.T_Array) return Mal.T - is (Kind_List, (AFC with new Rec'(Data => Args, - Last => Args'Length, - others => <>))); - - function Map (Args : in Mal.T_Array) return Mal.T is - begin - if Args'Length /= 2 then - raise Argument_Error with "map: expects 2 arguments"; - elsif Args (Args'Last).Kind not in Kind_List | Kind_Vector then - raise Argument_Error with "map: argument 2 must be a list or vector"; - end if; - declare - F : Mal.T renames Args (Args'First); - Old : Rec renames Args (Args'Last).List.Ref.all; - Ref : Acc; - begin - pragma Assert (0 < Old.Refs); - if Old.Refs = 1 then - Ref := Args (Args'Last).List.Ref; - Old.Refs := 2; - Old.Meta := Mal.Nil; - else - Ref := new Rec (Old.Last); - end if; - return R : constant Mal.T := (Kind_List, (AFC with Ref)) do - -- Now we can afford raising an exception. - case F.Kind is - when Kind_Builtin => - for I in Old.Data'Range loop - Ref.all.Data (I) := F.Builtin.all (Old.Data (I .. I)); - end loop; - when Kind_Builtin_With_Meta => - for I in Old.Data'Range loop - Ref.all.Data (I) - := F.Builtin_With_Meta.Builtin.all (Old.Data (I .. I)); - end loop; - when Kind_Function => - for I in Old.Data'Range loop - Ref.all.Data (I) := F.Fn.Apply (Old.Data (I .. I)); - end loop; - when others => - raise Argument_Error with "map: cannot call " & Printer.Img (F); - end case; - end return; - end; - end Map; - - function Meta (Item : in Ptr) return Mal.T - is (Item.Ref.all.Meta); - - function Nth (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 2 then - raise Argument_Error with "nth: expects 2 arguments" - else - (case Args (Args'First).Kind is - when Kind_List | Kind_Vector => - (if Args (Args'First + 1).Kind /= Kind_Number then - raise Argument_Error with "nth: last arg must be a number" - elsif 1 + Args (Args'Last).Number - in Args (Args'First).List.Ref.all.Data'Range - then - Args (Args'First).List.Ref.all.Data - (1 + Args (Args'Last).Number) - else - raise Argument_Error with "nth: index out of bounds"), - when others => - raise Argument_Error with "nth: expects a list or vector")); - - function Rest (Args : in Mal.T_Array) return Mal.T is - begin - if Args'Length /= 1 then - raise Argument_Error with "rest: expects 1 argument"; - end if; - declare - A1 : Mal.T renames Args (Args'First); - Ref : Acc; - begin - case A1.Kind is - when Kind_Nil => - Ref := new Rec (0); - when Kind_List | Kind_Vector => - if A1.List.Ref.all.Last = 0 then - Ref := new Rec (0); - else - Ref := new Rec' - (Last => A1.List.Ref.all.Last - 1, - Data => A1.List.Ref.all.Data (2 .. A1.List.Ref.all.Last), - others => <>); - end if; - when others => - raise Argument_Error with "rest: expects a list or vector"; - end case; - return (Kind_List, (AFC with Ref)); - end; - end Rest; - - function Slice (Item : in Ptr; - Start : in Positive) - return Mal.T - is (Kind_List, (AFC with new Rec' - (Last => Item.Ref.all.Last - Start + 1, - Data => Item.Ref.all.Data (Start .. Item.Ref.all.Last), - others => <>))); - - function Vector (Args : in Mal.T_Array) return Mal.T - is (Kind_Vector, (AFC with new Rec'(Data => Args, - Last => Args'Length, - others => <>))); - - function With_Meta (Data : in Ptr; - Metadata : in Mal.T) return Ptr - is - Old : Rec renames Data.Ref.all; - Ref : Acc; - begin - pragma Assert (0 < Old.Refs); - if Old.Refs = 1 then - Ref := Data.Ref; - Old.Refs := 2; - Old.Meta := Metadata; - else - Ref := new Rec'(Last => Old.Last, - Data => Old.Data, - Meta => Metadata, - others => <>); - end if; - return (AFC with Ref); - end With_Meta; - -end Types.Lists; diff --git a/ada.2/types-mal.adb b/ada.2/types-mal.adb index 93c41409dd..47bcc9059f 100644 --- a/ada.2/types-mal.adb +++ b/ada.2/types-mal.adb @@ -1,8 +1,8 @@ package body Types.Mal is use type Ada.Strings.Unbounded.Unbounded_String; - use type Lists.Ptr; use type Maps.Ptr; + use type Sequences.Ptr; use type Symbols.Ptr; ---------------------------------------------------------------------- @@ -18,13 +18,14 @@ package body Types.Mal is Right.Kind = Kind_Number and then Left.Number = Right.Number, when Kind_Symbol => Right.Kind = Kind_Symbol and then Left.Symbol = Right.Symbol, - -- Here is the part that differs from the predefined equality. - when Kind_Keyword | Kind_String => + when Kind_Key => Right.Kind = Left.Kind and then Left.S = Right.S, - when Kind_List | Kind_Vector => - Right.Kind in Kind_List | Kind_Vector and then Left.List = Right.List, + -- Here comes the part that differs from the predefined equality. + when Kind_Sequence => + Right.Kind in Kind_Sequence and then Left.Sequence = Right.Sequence, when Kind_Map => Right.Kind = Kind_Map and then Left.Map = Right.Map, + -- Also, comparing functions is an interesting problem. when others => False); diff --git a/ada.2/types-mal.ads b/ada.2/types-mal.ads index 955d7a44e3..33f5a2c813 100644 --- a/ada.2/types-mal.ads +++ b/ada.2/types-mal.ads @@ -2,9 +2,9 @@ with Ada.Strings.Unbounded; with Types.Atoms; with Types.Builtins; -with Types.Functions; -with Types.Lists; +with Types.Fns; with Types.Maps; +with Types.Sequences; with Types.Symbols; package Types.Mal is @@ -60,20 +60,20 @@ package Types.Mal is Number : Integer; when Kind_Atom => Atom : Atoms.Ptr; - when Kind_Keyword | Kind_String => + when Kind_Key => S : Ada.Strings.Unbounded.Unbounded_String; when Kind_Symbol => Symbol : Symbols.Ptr; - when Kind_List | Kind_Vector => - List : Lists.Ptr; + when Kind_Sequence => + Sequence : Sequences.Ptr; when Kind_Map => Map : Maps.Ptr; when Kind_Builtin => Builtin : Builtin_Ptr; when Kind_Builtin_With_Meta => Builtin_With_Meta : Builtins.Ptr; - when Kind_Function | Kind_Macro => - Fn : Functions.Ptr; + when Kind_Fn | Kind_Macro => + Fn : Fns.Ptr; end case; end record; diff --git a/ada.2/types-maps.adb b/ada.2/types-maps.adb index 6e70d5929a..f56ae48405 100644 --- a/ada.2/types-maps.adb +++ b/ada.2/types-maps.adb @@ -2,7 +2,8 @@ with Ada.Containers.Hashed_Maps; with Ada.Strings.Unbounded.Hash; with Ada.Unchecked_Deallocation; -with Types.Lists; +with Err; +with Types.Sequences; with Types.Mal; package body Types.Maps is @@ -11,7 +12,9 @@ package body Types.Maps is use type Ada.Containers.Count_Type; function Hash (Item : in Mal.T) return Ada.Containers.Hash_Type - with Inline, Pre => Item.Kind in Kind_Keyword | Kind_String; + with Inline; + -- This function also checks the kind of the key, and raise an + -- error in case of problem. package HM is new Ada.Containers.Hashed_Maps (Key_Type => Mal.T, Element_Type => Mal.T, @@ -27,6 +30,7 @@ package body Types.Maps is end record; procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); + Allocations : Natural := 0; ---------------------------------------------------------------------- @@ -35,87 +39,82 @@ package body Types.Maps is procedure Adjust (Object : in out Ptr) is begin - Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + Object.Ref.all.Refs := @ + 1; end Adjust; function Assoc (Args : in Mal.T_Array) return Mal.T is - Binds : constant Natural := Args'Length / 2; + Ref : Acc; begin - if Args'Length mod 2 /= 1 then - raise Argument_Error with "assoc: expects an odd argument count"; - elsif Args (Args'First).Kind /= Kind_Map then - raise Argument_Error with "assoc: first argument must be a map"; - elsif (for some I in 1 .. Binds => Args (Args'First + 2 * I - 1).Kind - not in Kind_Keyword | Kind_String) - then - raise Argument_Error with "assoc: keys must be strings or symbols"; + Err.Check (Args'Length mod 2 = 1, "expected an odd parameter count"); + Err.Check (Args (Args'First).Kind = Kind_Map, + "parameter 1 must be a map"); + -- Avoid exceptions until Ref is controlled. + Ref := Args (Args'First).Map.Ref; + pragma Assert (0 < Ref.all.Refs); + if Ref.all.Refs = 1 then + Ref.all.Refs := 2; + Ref.all.Meta := Mal.Nil; + else + Allocations := Allocations + 1; + Ref := new Rec'(Data => Ref.all.Data, + others => <>); end if; - declare - Old : Rec renames Args (Args'First).Map.Ref.all; - Ref : Acc; - begin - pragma Assert (0 < Old.Refs); - if Old.Refs = 1 then - Ref := Args (Args'First).Map.Ref; - Old.Refs := 2; - Old.Meta := Mal.Nil; - else - Ref := new Rec'(Data => Old.Data, others => <>); - end if; - for I in 1 .. Binds loop + return R : constant Mal.T := (Kind_Map, (AFC with Ref)) do + for I in 1 .. Args'Length / 2 loop Ref.all.Data.Include (Key => Args (Args'First + 2 * I - 1), New_Item => Args (Args'First + 2 * I)); + -- This call checks the kind of the key. end loop; - return (Kind_Map, (AFC with Ref)); - end; + end return; end Assoc; - function Contains (Args : in Mal.T_Array) return Mal.T - is (if Args'Length /= 2 then - raise Argument_Error with "contains: expects 2 arguments" - elsif Args (Args'First).Kind /= Kind_Map then - raise Argument_Error with "contains: first arguement must be a map" - else - (Kind_Boolean, - Args (Args'First).Map.Ref.all.Data.Contains (Args (Args'Last)))); + procedure Check_Allocations is + begin + pragma Assert (Allocations = 0); + end Check_Allocations; + + function Contains (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 2, "expected 2 parameters"); + Err.Check (Args (Args'First).Kind = Kind_Map, + "parameter 1 must be a map"); + return (Kind_Boolean, + Args (Args'First).Map.Ref.all.Data.Contains (Args (Args'Last))); + end Contains; function Dissoc (Args : in Mal.T_Array) return Mal.T is + Ref : Acc; begin - if Args'Length = 0 then - raise Argument_Error with "dissoc: expects at least 1 argument"; - elsif Args (Args'First).Kind /= Kind_Map then - raise Argument_Error with "dissoc: first argument must be a map"; - elsif (for some I in Args'First + 1 .. Args'Last => - Args (I).Kind not in Kind_Keyword | Kind_String) - then - raise Argument_Error with "dissoc: keys must be strings or symbols"; + Err.Check (0 < Args'Length, "expected at least 1 parameter"); + Err.Check (Args (Args'First).Kind = Kind_Map, + "parameter 1 must be a map"); + -- Avoid exceptions until Ref is controlled. + Ref := Args (Args'First).Map.Ref; + pragma Assert (0 < Ref.all.Refs); + if Ref.all.Refs = 1 then + Ref.all.Refs := 2; + Ref.all.Meta := Mal.Nil; + else + Allocations := Allocations + 1; + Ref := new Rec'(Data => Ref.all.Data, + others => <>); end if; - declare - Old : Rec renames Args (Args'First).Map.Ref.all; - Ref : Acc; - begin - pragma Assert (0 < Old.Refs); - if Old.Refs = 1 then - Ref := Args (Args'First).Map.Ref; - Old.Refs := 2; - Old.Meta := Mal.Nil; - else - Ref := new Rec'(Data => Old.Data, others => <>); - end if; + return R : constant Mal.T := (Kind_Map, (AFC with Ref)) do for I in Args'First + 1 .. Args'Last loop Ref.all.Data.Exclude (Args (I)); + -- This call checks the kind of the key. end loop; - return (Kind_Map, (AFC with Ref)); - end; + end return; end Dissoc; procedure Finalize (Object : in out Ptr) is begin if Object.Ref /= null and then 0 < Object.Ref.all.Refs then - Object.Ref.all.Refs := Object.Ref.all.Refs - 1; + Object.Ref.all.Refs := @ - 1; if 0 < Object.Ref.all.Refs then Object.Ref := null; else + Allocations := Allocations - 1; Free (Object.Ref); end if; end if; @@ -128,23 +127,23 @@ package body Types.Maps is -- Copy the whole hash in order to avoid recomputing the hash -- for each key, even if it implies unneeded calls to adjust -- and finalize for Mal_Type values. - Old : Rec renames Container.Ref.all; - Ref : Acc; + -- Avoid exceptions until Ref is controlled. + Ref : Acc := Container.Ref; begin - pragma Assert (0 < Old.Refs); - if Old.Refs = 1 then - Ref := Container.Ref; - Old.Refs := 2; - Old.Meta := Mal.Nil; + pragma Assert (0 < Ref.all.Refs); + if Ref.all.Refs = 1 then + Ref.all.Refs := 2; + Ref.all.Meta := Mal.Nil; else - Ref := new Rec'(Data => Container.Ref.all.Data, others => <>); + Allocations := Allocations + 1; + Ref := new Rec'(Data => Ref.all.Data, + others => <>); end if; - -- Prepare a valid structure before running user code. In case - -- an exception is raised, we want memory to be deallocated. return R : constant Mal.T := (Kind_Map, (AFC with Ref)) do for Position in Ref.all.Data.Iterate loop Ref.all.Data.Replace_Element (Position, Eval (HM.Element (Position), Env)); + -- This call may raise exceptions. end loop; end return; end Generic_Eval; @@ -152,48 +151,48 @@ package body Types.Maps is function Get (Args : in Mal.T_Array) return Mal.T is Position : HM.Cursor; begin - if Args'Length /= 2 then - raise Argument_Error with "get: expects 2 arguments"; - elsif Args (Args'Last).Kind not in Kind_Keyword | Kind_String then - raise Argument_Error with "get: key must be a keyword or string"; - end if; + Err.Check (Args'Length = 2, "expected 2 parameters"); case Args (Args'First).Kind is when Kind_Nil => + Err.Check (Args (Args'Last).Kind in Kind_Key, + "key must be a keyword or string"); return Mal.Nil; when Kind_Map => Position := Args (Args'First).Map.Ref.all.Data.Find (Args (Args'Last)); + -- This call checks the kind of the key. if HM.Has_Element (Position) then return HM.Element (Position); else return Mal.Nil; end if; when others => - raise Argument_Error with "get: first argument must be a map"; + Err.Raise_With ("parameter 1 must be nil or a map"); end case; end Get; - function Hash (Item : in Mal.T) return Ada.Containers.Hash_Type - is (Ada.Strings.Unbounded.Hash (Item.S)); + function Hash (Item : in Mal.T) return Ada.Containers.Hash_Type is + begin + Err.Check (Item.Kind in Kind_Key, "keys must be keywords or strings"); + return (Ada.Strings.Unbounded.Hash (Item.S)); + end Hash; function Hash_Map (Args : in Mal.T_Array) return Mal.T is Binds : constant Natural := Args'Length / 2; Ref : Acc; begin - if Args'Length mod 2 /= 0 then - raise Argument_Error with "hash-map: expects an even argument count"; - elsif (for some I in 0 .. Binds - 1 => Args (Args'First + 2 * I).Kind - not in Kind_Keyword | Kind_String) - then - raise Argument_Error with "hash-map: keys must be strings or symbols"; - end if; + Err.Check (Args'Length mod 2 = 0, "expected an even parameter count"); + Allocations := Allocations + 1; + -- Avoid exceptions until Ref is controlled. Ref := new Rec; Ref.all.Data.Reserve_Capacity (Ada.Containers.Count_Type (Binds)); - for I in 0 .. Binds - 1 loop - Ref.all.Data.Include (Key => Args (Args'First + 2 * I), - New_Item => Args (Args'First + 2 * I + 1)); - end loop; - return (Kind_Map, (AFC with Ref)); + return R : constant Mal.T := (Kind_Map, (AFC with Ref)) do + for I in 0 .. Binds - 1 loop + Ref.all.Data.Include (Key => Args (Args'First + 2 * I), + New_Item => Args (Args'First + 2 * I + 1)); + -- This call checks the kind of the key. + end loop; + end return; end Hash_Map; procedure Iterate (Container : in Ptr) is @@ -205,11 +204,9 @@ package body Types.Maps is function Keys (Args : in Mal.T_Array) return Mal.T is begin - if Args'Length /= 1 then - raise Argument_Error with "keys: expects 1 argument"; - elsif Args (Args'First).Kind /= Kind_Map then - raise Argument_Error with "keys: first argument must a map"; - end if; + Err.Check (Args'Length = 1, "expected 1 parameter"); + Err.Check (Args (Args'First).Kind = Kind_Map, + "parameter 1 must be a map"); declare A1 : HM.Map renames Args (Args'First).Map.Ref.all.Data; R : Mal.T_Array (1 .. Natural (A1.Length)); @@ -219,7 +216,7 @@ package body Types.Maps is R (I) := HM.Key (Position); I := I + 1; end loop; - return Lists.List (R); + return Sequences.List (R); end; end Keys; @@ -228,11 +225,9 @@ package body Types.Maps is function Vals (Args : in Mal.T_Array) return Mal.T is begin - if Args'Length /= 1 then - raise Argument_Error with "vals: expects 1 argument"; - elsif Args (Args'First).Kind /= Kind_Map then - raise Argument_Error with "vals: first argument must be a map"; - end if; + Err.Check (Args'Length = 1, "expected 1 parameter"); + Err.Check (Args (Args'First).Kind = Kind_Map, + "parameter 1 must be a map"); declare A1 : HM.Map renames Args (Args'First).Map.Ref.all.Data; R : Mal.T_Array (1 .. Natural (A1.Length)); @@ -242,7 +237,7 @@ package body Types.Maps is R (I) := Element; I := I + 1; end loop; - return Lists.List (R); + return Sequences.List (R); end; end Vals; @@ -250,16 +245,16 @@ package body Types.Maps is Metadata : in Mal.T) return Mal.T is - Old : Rec renames Data.Ref.all; - Ref : Acc; + -- Avoid exceptions until Ref is controlled. + Ref : Acc := Data.Ref; begin - pragma Assert (0 < Old.Refs); - if Old.Refs = 1 then - Ref := Data.Ref; - Old.Refs := 2; - Old.Meta := Metadata; + pragma Assert (0 < Ref.all.Refs); + if Ref.all.Refs = 1 then + Ref.all.Refs := 2; + Ref.all.Meta := Metadata; else - Ref := new Rec'(Data => Old.Data, + Allocations := Allocations + 1; + Ref := new Rec'(Data => Ref.all.Data, Meta => Metadata, others => <>); end if; diff --git a/ada.2/types-maps.ads b/ada.2/types-maps.ads index aa67b5f479..58d8f450fb 100644 --- a/ada.2/types-maps.ads +++ b/ada.2/types-maps.ads @@ -41,6 +41,9 @@ package Types.Maps is Metadata : in Mal.T) return Mal.T; + -- Debug + procedure Check_Allocations; + private type Rec; diff --git a/ada.2/types-sequences.adb b/ada.2/types-sequences.adb new file mode 100644 index 0000000000..751f762308 --- /dev/null +++ b/ada.2/types-sequences.adb @@ -0,0 +1,335 @@ +with Ada.Unchecked_Deallocation; + +with Err; +with Types.Mal; + +package body Types.Sequences is + + subtype AFC is Ada.Finalization.Controlled; + use type Mal.T_Array; + + type Rec (Last : Natural) is limited record + Refs : Natural := 1; + Meta : Mal.T := Mal.Nil; + Data : Mal.T_Array (1 .. Last) := (others => Mal.Nil); + end record; + + procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); + Allocations : Natural := 0; + + ---------------------------------------------------------------------- + + function "=" (Left, Right : in Ptr) return Boolean is + -- Should become Left.Ref.all.Data = Right.Ref.all.Data when + -- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89178 is fixed. + use type Mal.T; + L : Rec renames Left.Ref.all; + R : Rec renames Right.Ref.all; + begin + return L.Last = R.Last + and then (for all I in 1 .. L.Last => L.Data (I) = R.Data (I)); + end "="; + + function "&" (Left : in Mal.T_Array; + Right : in Ptr) return Mal.T_Array + is (Left & Right.Ref.all.Data); + + procedure Adjust (Object : in out Ptr) is + begin + Object.Ref.all.Refs := @ + 1; + end Adjust; + + procedure Check_Allocations is + begin + pragma Assert (Allocations = 0); + end Check_Allocations; + + function Concat (Args : in Mal.T_Array) return Mal.T is + Sum : Natural := 0; + First : Positive := 1; + Last : Natural; + Ref : Acc; + begin + for Arg of Args loop + Err.Check (Arg.Kind in Kind_Sequence, "expected sequences"); + Sum := Sum + Arg.Sequence.Ref.all.Data'Length; + end loop; + Allocations := Allocations + 1; + -- Avoid exceptions until Ref is controlled. + Ref := new Rec (Sum); + for Arg of Args loop + Last := First - 1 + Arg.Sequence.Ref.all.Data'Length; + Ref.all.Data (First .. Last) := Arg.Sequence.Ref.all.Data; + First := Last + 1; + end loop; + return (Kind_List, (AFC with Ref)); + end Concat; + + function Conj (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (0 < Args'Length, "expected at least 1 parameter"); + case Args (Args'First).Kind is + when Kind_Sequence => + declare + Data : Mal.T_Array renames Args (Args'First).Sequence.Ref.all.Data; + Last : constant Natural := Args'Length - 1 + Data'Length; + -- Avoid exceptions until Ref is controlled. + Ref : constant Acc := new Rec (Last); + begin + Allocations := Allocations + 1; + if Args (Args'First).Kind = Kind_List then + for I in 1 .. Args'Length - 1 loop + Ref.all.Data (I) := Args (Args'Last - I + 1); + end loop; + Ref.all.Data (Args'Length .. Last) := Data; + return (Kind_List, (AFC with Ref)); + else + Ref.all.Data := Data & Args (Args'First + 1 .. Args'Last); + return (Kind_Vector, (AFC with Ref)); + end if; + end; + when others => + Err.Raise_With ("parameter 1 must be a sequence"); + end case; + end Conj; + + function Cons (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 2, "expected 2 parameters"); + Err.Check (Args (Args'Last).Kind in Kind_Sequence, + "parameter 2 must be a sequence"); + declare + Head : Mal.T renames Args (Args'First); + Tail : Mal.T_Array renames Args (Args'Last).Sequence.Ref.all.Data; + begin + Allocations := Allocations + 1; + return (Kind_List, (AFC with new Rec'(Last => 1 + Tail'Length, + Data => Head & Tail, + others => <>))); + end; + end Cons; + + function Count (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 1, "expected 1 parameter"); + case Args (Args'First).Kind is + when Kind_Nil => + return (Kind_Number, 0); + when Kind_Sequence => + return (Kind_Number, Args (Args'First).Sequence.Ref.all.Data'Length); + when others => + Err.Raise_With ("parameter must be nil or a sequence"); + end case; + end Count; + + function Element (Container : in Ptr; + Index : in Positive) return Mal.T + is (Container.Ref.all.Data (Index)); + + procedure Finalize (Object : in out Ptr) is + begin + if Object.Ref /= null and then 0 < Object.Ref.all.Refs then + Object.Ref.all.Refs := @ - 1; + if 0 < Object.Ref.all.Refs then + Object.Ref := null; + else + Allocations := Allocations - 1; + Free (Object.Ref); + end if; + end if; + end Finalize; + + function First (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 1, "expected 1 parameter"); + case Args (Args'First).Kind is + when Kind_Nil => + return Mal.Nil; + when Kind_Sequence => + declare + Data : Mal.T_Array renames Args (Args'First).Sequence.Ref.all.Data; + begin + if Data'Length = 0 then + return Mal.Nil; + else + return Data (Data'First); + end if; + end; + when others => + Err.Raise_With ("parameter must be nil or a sequence"); + end case; + end First; + + function Generic_Eval (Container : in Ptr; + Env : in Env_Type) + return Ptr + is + -- Avoid exceptions until Ref is controlled. + Ref : Acc := Container.Ref; + begin + pragma Assert (0 < Ref.all.Refs); + if Ref.all.Refs = 1 then + Ref.all.Refs := 2; + Ref.all.Meta := Mal.Nil; + else + Allocations := Allocations + 1; + Ref := new Rec (Ref.all.Last); + end if; + return R : constant Ptr := (AFC with Ref) do + for I in Container.Ref.all.Data'Range loop + Ref.all.Data (I) := Eval (Container.Ref.all.Data (I), Env); + -- This call may raise exceptions. + -- The target may be the source. + end loop; + end return; + end Generic_Eval; + + function Is_Empty (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 1, "expected 1 parameter"); + Err.Check (Args (Args'First).Kind in Kind_Sequence, + "parameter must be a sequence"); + return (Kind_Boolean, + Args (Args'First).Sequence.Ref.all.Data'Length = 0); + end Is_Empty; + + function Length (Source : in Ptr) return Natural + is (Source.Ref.all.Data'Length); + + function List (Args : in Mal.T_Array) return Mal.T is + begin + Allocations := Allocations + 1; + return (Kind_List, (AFC with new Rec'(Data => Args, + Last => Args'Length, + others => <>))); + end List; + + function Map (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 2, "expected 2 parameters"); + Err.Check (Args (Args'Last).Kind in Kind_Sequence, + "parameter 2 must be a sequence"); + declare + F : Mal.T renames Args (Args'First); + Src : Mal.T_Array renames Args (Args'Last).Sequence.Ref.all.Data; + -- Avoid exceptions until Ref is controlled. + Ref : Acc := Args (Args'Last).Sequence.Ref; + begin + pragma Assert (0 < Ref.all.Refs); + if Ref.all.Refs = 1 then + Ref.all.Refs := 2; + Ref.all.Meta := Mal.Nil; + else + Allocations := Allocations + 1; + Ref := new Rec (Ref.all.Last); + end if; + return R : constant Mal.T := (Kind_List, (AFC with Ref)) do + case F.Kind is + when Kind_Builtin => + for I in Src'Range loop + Ref.all.Data (I) := F.Builtin.all (Src (I .. I)); + -- This call may raise exceptions. + -- The target may be the same storage than the source. + end loop; + when Kind_Builtin_With_Meta => + for I in Src'Range loop + Ref.all.Data (I) + := F.Builtin_With_Meta.Builtin.all (Src (I .. I)); + end loop; + when Kind_Fn => + for I in Src'Range loop + Ref.all.Data (I) := F.Fn.Apply (Src (I .. I)); + end loop; + when others => + Err.Raise_With ("parameter 1 must be a function"); + end case; + end return; + end; + end Map; + + function Meta (Item : in Ptr) return Mal.T + is (Item.Ref.all.Meta); + + function Nth (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 2, "expected 2 parameters"); + Err.Check (Args (Args'First).Kind in Kind_Sequence, + "paramater 1 must be a sequence"); + Err.Check (Args (Args'Last).Kind = Kind_Number, + "parameter 2 must be a number"); + declare + L : Mal.T_Array renames Args (Args'First).Sequence.Ref.all.Data; + I : constant Integer := Args (Args'Last).Number + 1; + begin + Err.Check (I in L'Range, "index out of bounds"); + return L (I); + end; + end Nth; + + function Rest (Args : in Mal.T_Array) return Mal.T is + begin + Err.Check (Args'Length = 1, "expected 1 parameter"); + declare + A1 : Mal.T renames Args (Args'First); + Ref : Acc; + begin + -- Avoid exceptions until Ref is controlled. + case A1.Kind is + when Kind_Nil => + Allocations := Allocations + 1; + Ref := new Rec (0); + when Kind_Sequence => + Allocations := Allocations + 1; + if A1.Sequence.Ref.all.Last = 0 then + Ref := new Rec (0); + else + Ref := new Rec' + (Last => A1.Sequence.Ref.all.Last - 1, + Data => A1.Sequence.Ref.all.Data + (2 .. A1.Sequence.Ref.all.Data'Last), + others => <>); + end if; + when others => + Err.Raise_With ("parameter must be nil or a sequence"); + end case; + return (Kind_List, (AFC with Ref)); + end; + end Rest; + + function Tail (Source : in Ptr; + Count : in Natural) return Mal.T_Array is + Data : Mal.T_Array renames Source.Ref.all.Data; + begin + return Data (Data'Last - Count + 1 .. Data'Last); + end Tail; + + function Vector (Args : in Mal.T_Array) return Mal.T is + begin + Allocations := Allocations + 1; + return (Kind_Vector, (AFC with new Rec'(Data => Args, + Last => Args'Length, + others => <>))); + end Vector; + + function With_Meta (Data : in Ptr; + Metadata : in Mal.T) return Ptr + is + -- Avoid exceptions until Ref is controlled. + Ref : Acc := Data.Ref; + + begin + pragma Assert (0 < Ref.all.Refs); + if Ref.all.Refs = 1 then + Ref.all.Refs := 2; + Ref.all.Meta := Metadata; + else + Allocations := Allocations + 1; + Ref := new Rec'(Last => Ref.all.Last, + Data => Ref.all.Data, + Meta => Metadata, + others => <>); + end if; + return (AFC with Ref); + end With_Meta; + +end Types.Sequences; diff --git a/ada.2/types-lists.ads b/ada.2/types-sequences.ads similarity index 85% rename from ada.2/types-lists.ads rename to ada.2/types-sequences.ads index 7eefc0eb3e..e195dee269 100644 --- a/ada.2/types-lists.ads +++ b/ada.2/types-sequences.ads @@ -2,9 +2,10 @@ private with Ada.Finalization; limited with Types.Mal; -package Types.Lists is +package Types.Sequences is - type Ptr is tagged private; + type Ptr is tagged private + with Constant_Indexing => Element; -- Built-in functions. function Concat (Args : in Mal.T_Array) return Mal.T; @@ -23,8 +24,7 @@ package Types.Lists is function Element (Container : in Ptr; Index : in Positive) return Mal.T - with Inline; - Index_Error : exception; + with Inline, Pre => Index <= Length (Container); function "&" (Left : in Mal.T_Array; Right : in Ptr) return Mal.T_Array; @@ -41,16 +41,19 @@ package Types.Lists is Env : in Env_Type) return Ptr; - -- Used to spare an intermediate copy for & in macro arguments. - function Slice (Item : in Ptr; - Start : in Positive) - return Mal.T; + -- Used in macro implementation. + function Tail (Source : in Ptr; + Count : in Natural) return Mal.T_Array + with Inline, Pre => Count <= Length (Source); function Meta (Item : in Ptr) return Mal.T with Inline; function With_Meta (Data : in Ptr; Metadata : in Mal.T) return Ptr; + -- Debug. + procedure Check_Allocations; + private -- It is tempting to use null to represent an empty list, but the @@ -72,4 +75,4 @@ private overriding function "=" (Left, Right : in Ptr) return Boolean; pragma Finalize_Storage_Only (Ptr); -end Types.Lists; +end Types.Sequences; diff --git a/ada.2/types-symbols-names.ads b/ada.2/types-symbols-names.ads index 543f5e2d73..1478ab387a 100644 --- a/ada.2/types-symbols-names.ads +++ b/ada.2/types-symbols-names.ads @@ -16,7 +16,6 @@ package Types.Symbols.Names is Fn : constant Ptr := Constructor ("fn*"); Let : constant Ptr := Constructor ("let*"); Macroexpand : constant Ptr := Constructor ("macroexpand"); - Mal_Do : constant Ptr := Constructor ("do"); Mal_If : constant Ptr := Constructor ("if"); Quasiquote : constant Ptr := Constructor ("quasiquote"); Quote : constant Ptr := Constructor ("quote"); diff --git a/ada.2/types-symbols.adb b/ada.2/types-symbols.adb index 7aa5f530d9..4ca03c5132 100644 --- a/ada.2/types-symbols.adb +++ b/ada.2/types-symbols.adb @@ -13,6 +13,7 @@ package body Types.Symbols is Data : String (1 .. Last); end record; procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); + Allocations : Natural := 0; function "<" (Left, Right : in Acc) return Boolean with Inline; function Eq (Left, Right : in Acc) return Boolean with Inline; @@ -38,17 +39,25 @@ package body Types.Symbols is procedure Adjust (Object : in out Ptr) is begin - Object.Ref.all.Refs := Object.Ref.all.Refs + 1; + Object.Ref.all.Refs := @ + 1; end Adjust; + procedure Check_Allocations is + begin + -- See Types.Symbols.Names. + pragma Assert (Allocations = 15); + end Check_Allocations; + function Constructor (Source : in String) return Ptr is Position : constant Sets.Cursor := Keys.Find (Dict, Source); Ref : Acc; begin + -- Avoid exceptions until Ref is controlled. if Sets.Has_Element (Position) then Ref := Sets.Element (Position); Ref.all.Refs := Ref.all.Refs + 1; else + Allocations := Allocations + 1; Ref := new Rec'(Data => Source, Hash => Ada.Strings.Hash (Source), Last => Source'Length, @@ -68,11 +77,12 @@ package body Types.Symbols is procedure Finalize (Object : in out Ptr) is begin if Object.Ref /= null and then 0 < Object.Ref.all.Refs then - Object.Ref.all.Refs := Object.Ref.all.Refs - 1; + Object.Ref.all.Refs := @ - 1; if 0 < Object.Ref.all.Refs then Object.Ref := null; else Dict.Delete (Object.Ref); + Allocations := Allocations - 1; Free (Object.Ref); end if; end if; @@ -87,26 +97,4 @@ package body Types.Symbols is function To_String (Item : in Ptr) return String is (Item.Ref.all.Data); - function To_String (Item : in Symbol_Array) return String is - I : Natural := Item'Length + 1; - begin - for S of Item loop - I := I + S.Ref.all.Last; - end loop; - return R : String (1 .. I) do - R (1) := '('; - I := 2; - for S of Item loop - if 2 < I then - R (I) := ' '; - I := I + 1; - end if; - R (I .. I + S.Ref.all.Last - 1) := S.Ref.all.Data; - I := I + S.Ref.all.Last; - end loop; - pragma Assert (I = R'Last); - R (R'Last) := ')'; - end return; - end To_String; - end Types.Symbols; diff --git a/ada.2/types-symbols.ads b/ada.2/types-symbols.ads index 91be3a00b5..7b620a81c7 100644 --- a/ada.2/types-symbols.ads +++ b/ada.2/types-symbols.ads @@ -17,10 +17,8 @@ package Types.Symbols with Preelaborate is type Symbol_Array is array (Positive range <>) of Symbols.Ptr; - function To_String (Item : in Symbols.Symbol_Array) return String; - -- Returns something like "(a b)". Convenient for error - -- reporting, but redundant with Printer (where it is more - -- efficient to concatenate directly to an unbounded buffer). + -- Debug. + procedure Check_Allocations; private diff --git a/ada.2/types.ads b/ada.2/types.ads index 3e549a9b24..f01c830b77 100644 --- a/ada.2/types.ads +++ b/ada.2/types.ads @@ -1,20 +1,18 @@ package Types with Pure is - -- Similar kinds should be consecutive for efficient case - -- statements. type Kind_Type is (Kind_Nil, Kind_Atom, Kind_Boolean, Kind_Number, - Kind_String, Kind_Symbol, Kind_Keyword, + Kind_Symbol, + Kind_Keyword, Kind_String, Kind_List, Kind_Vector, Kind_Map, - Kind_Macro, Kind_Function, Kind_Builtin_With_Meta, Kind_Builtin); + Kind_Macro, Kind_Fn, Kind_Builtin_With_Meta, Kind_Builtin); - -- Raised when a program attempts to execute something else than a - -- function or a macro, or when a builtin receives a bad argument - -- count, type or value. - Argument_Error : exception; + subtype Kind_Key is Kind_Type range Kind_Keyword .. Kind_String; + subtype Kind_Sequence is Kind_Type range Kind_List .. Kind_Vector; + subtype Kind_Function is Kind_Type range Kind_Fn .. Kind_Builtin; end Types; From 265dd4e9efc1dfd3694c5da538aad4af1fed8791 Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Sun, 17 Mar 2019 14:15:41 +0100 Subject: [PATCH 0806/2308] ada.2: add Dockerfile from kanaka --- ada.2/Dockerfile | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 ada.2/Dockerfile diff --git a/ada.2/Dockerfile b/ada.2/Dockerfile new file mode 100644 index 0000000000..b05b41362e --- /dev/null +++ b/ada.2/Dockerfile @@ -0,0 +1,25 @@ +FROM ubuntu:18.04 +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# GNU Ada compiler +RUN apt-get -y install gnat-8 From a4820e976b1101fe1da1c93ddfe5811845456ea1 Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Sun, 17 Mar 2019 14:56:28 +0100 Subject: [PATCH 0807/2308] ada.2: remove -gnatp optimization, which crashes the perf test. --- ada.2/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ada.2/Makefile b/ada.2/Makefile index 342e56a917..e5de51b805 100644 --- a/ada.2/Makefile +++ b/ada.2/Makefile @@ -6,7 +6,7 @@ else # -O3 is not recommended as the default by the GCC documentation, # and -O2 seems to produce slightly better performances. # See README for a discussion of -gnatp. - ADAFLAGS := -O2 -gnatnp + ADAFLAGS := -O2 -gnatn endif # Compiler arguments. From c4269f9bf50a952571ae09aa3340c9ac07b65cb3 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 20 Mar 2019 23:32:35 -0500 Subject: [PATCH 0808/2308] Convert to loccount based stats calculation. --- Makefile | 22 ++++++++++++++++++---- ada/Makefile | 17 ----------------- awk/Makefile | 20 -------------------- bash/Makefile | 9 --------- basic/Makefile | 15 +-------------- c/Makefile | 26 -------------------------- chuck/Makefile | 15 +-------------- clojure/Makefile | 9 --------- coffee/Makefile | 11 ----------- common-lisp/Makefile | 11 ----------- cpp/Makefile | 13 ------------- crystal/Makefile | 9 +-------- cs/Makefile | 17 ----------------- d/Makefile | 23 ----------------------- dart/Makefile | 13 +------------ elisp/Makefile | 14 -------------- elixir/Makefile | 10 +--------- elm/Makefile | 10 ---------- erlang/Makefile | 9 +-------- es6/Makefile | 15 --------------- factor/Makefile | 12 ------------ fantom/Makefile | 13 ------------- forth/Makefile | 9 --------- fsharp/Makefile | 17 ----------------- gnu-smalltalk/Makefile | 14 -------------- go/Makefile | 12 ------------ groovy/Makefile | 13 ------------- guile/Makefile | 9 --------- haskell/Makefile | 15 --------------- haxe/Makefile | 12 ------------ hy/Makefile | 14 -------------- io/Makefile | 22 +++------------------- java/Makefile | 13 ------------- js/Makefile | 9 +-------- julia/Makefile | 13 ------------- kotlin/Makefile | 11 ----------- livescript/Makefile | 9 --------- logo/Makefile | 9 +-------- lua/Makefile | 12 ------------ make/Makefile | 9 +-------- mal/Makefile | 23 ----------------------- matlab/Makefile | 16 ---------------- miniMAL/Makefile | 9 --------- nasm/Makefile | 15 --------------- nim/Makefile | 11 ----------- objc/Makefile | 12 ------------ objpascal/Makefile | 14 -------------- ocaml/Makefile | 9 +-------- perl/Makefile | 17 ----------------- perl6/Makefile | 16 +--------------- php/Makefile | 18 ------------------ picolisp/Makefile | 14 -------------- plpgsql/Makefile | 13 +------------ plsql/Makefile | 13 ++----------- powershell/Makefile | 13 +------------ ps/Makefile | 19 ------------------- python/Makefile | 19 ------------------- r/Makefile | 12 ------------ racket/Makefile | 9 --------- rexx/Makefile | 21 +-------------------- rpython/Makefile | 13 +------------ ruby/Makefile | 17 ----------------- rust/Makefile | 14 +------------- scala/Makefile | 17 +---------------- scheme/Makefile | 12 +----------- skew/Makefile | 11 +---------- swift/Makefile | 13 ------------- swift3/Makefile | 11 ----------- swift4/Makefile | 12 ------------ tcl/Makefile | 10 ---------- ts/Makefile | 13 +------------ vb/Makefile | 17 ----------------- vhdl/Makefile | 23 ----------------------- vimscript/Makefile | 9 +-------- wasm/Makefile | 13 ------------- yorick/Makefile | 9 +-------- 76 files changed, 45 insertions(+), 997 deletions(-) diff --git a/Makefile b/Makefile index 9843ed9c3f..75a08b77ff 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,9 @@ scheme_MODE = chibi # js wace_libc wace_fooboot wasm_MODE = wace_libc +# Path to loccount for counting LOC stats +LOCCOUNT = loccount + # Extra options to pass to runtest.py TEST_OPTS = @@ -337,6 +340,8 @@ DOCKER_SHELL = $(foreach impl,$(DO_IMPLS),docker-shell^$(impl)) IMPL_PERF = $(foreach impl,$(filter-out $(perf_EXCLUDES),$(DO_IMPLS)),perf^$(impl)) +IMPL_STATS = $(foreach impl,$(DO_IMPLS),stats^$(impl)) + IMPL_REPL = $(foreach impl,$(DO_IMPLS),repl^$(impl)) ALL_REPL = $(strip $(sort \ $(foreach impl,$(DO_IMPLS),\ @@ -453,6 +458,19 @@ $(ALL_REPL): $$(call $$(word 2,$$(subst ^, ,$$(@)))_STEP_TO_PROG,$$(word 3,$$(su # Allow repl^IMPL^STEP and repl^IMPL (which starts REPL of stepA) $(IMPL_REPL): $$@^stepA +# +# Stats test rules +# + +# For a concise summary: +# make stats | egrep -A1 "^Stats for|^all" | egrep -v "^all|^--" +stats: $(IMPL_STATS) + +$(IMPL_STATS): + @$(foreach impl,$(word 2,$(subst ^, ,$(@))),\ + echo "Stats for $(impl):"; \ + $(LOCCOUNT) -x "Makefile|node_modules" $(impl)) + # # Utility functions # @@ -481,9 +499,5 @@ recur_impls_ = $(filter-out $(foreach impl,$($(1)_EXCLUDES),$(1)^$(impl)),$(fore # recursive clean $(eval $(call recur_template,clean,$(call recur_impls_,clean))) -# recursive stats -$(eval $(call recur_template,stats,$(call recur_impls_,stats))) -$(eval $(call recur_template,stats-lisp,$(call recur_impls_,stats-lisp))) - # recursive dist $(eval $(call recur_template,dist,$(call recur_impls_,dist))) diff --git a/ada/Makefile b/ada/Makefile index 02e02edba9..7ee47a99d2 100644 --- a/ada/Makefile +++ b/ada/Makefile @@ -2,11 +2,6 @@ DIRS=obj PROGS=step0_repl step1_read_print step2_eval step3_env step4_if_fn_do \ step5_tco step6_file step7_quote step8_macros step9_try -SOURCES_LISP=envs.ad[bs] eval_callback.ads core.ad[bs] stepa_mal.adb -SOURCES=$(SOURCES_LISP) \ - types.ad[bs] types-vector.ad[bs] types-hash_map.ad[bs] \ - reader.ad[bs] printer.ad[bs] smart_pointers.ad[bs] - all: ${DIRS} ${PROGS} stepA_mal ${DIRS}: @@ -23,15 +18,3 @@ stepA_mal: stepa_mal clean: rm -f ${PROGS} rm -rf obj - -.PHONY: stats stats-lisp force - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*--|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*--|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -force: diff --git a/awk/Makefile b/awk/Makefile index 1136bc0bf6..ce864e0845 100644 --- a/awk/Makefile +++ b/awk/Makefile @@ -1,7 +1,3 @@ - -TESTS = - - SOURCES_BASE = types.awk reader.awk printer.awk SOURCES_LISP = env.awk core.awk stepA_mal.awk SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) @@ -22,19 +18,3 @@ mal: mal.awk clean: rm -f mal.awk mal - - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - python $@ || exit 1; \ diff --git a/bash/Makefile b/bash/Makefile index 488a4ffe4e..0f7bc0396a 100644 --- a/bash/Makefile +++ b/bash/Makefile @@ -17,12 +17,3 @@ mal: mal.sh clean: rm -f mal.sh mal - -.PHONY: stats - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/basic/Makefile b/basic/Makefile index e2cd4bb3f5..03b655809b 100644 --- a/basic/Makefile +++ b/basic/Makefile @@ -54,21 +54,8 @@ mal.d64: mal.prg .args.mal.prg core.mal.prg # Clean and Stats rules -.PHONY: clean stats +.PHONY: clean clean: rm -f $(STEPS0_A) $(subst .bas,,$(STEPS0_A)) *.d64 *.prg qb64 rm -rf ./internal - - -SOURCES_LISP = env.in.bas core.in.bas stepA_mal.in.bas -SOURCES = readline.in.bas readline_line.in.bas readline_char.in.bas \ - types.in.bas reader.in.bas printer.in.bas $(SOURCES_LISP) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*REM |^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*REM |^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - diff --git a/c/Makefile b/c/Makefile index 98ceee60b7..4c11393748 100644 --- a/c/Makefile +++ b/c/Makefile @@ -3,17 +3,6 @@ USE_GC ?= 1 CFLAGS += -g -O2 LDFLAGS += -g -##################### - -TESTS = - -SOURCES_BASE = readline.h readline.c types.h types.c \ - reader.h reader.c printer.h printer.c \ - interop.h interop.c -SOURCES_LISP = env.c core.h core.c stepA_mal.c -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - - ##################### SRCS = step0_repl.c step1_read_print.c step2_eval.c step3_env.c \ @@ -69,18 +58,3 @@ $(BINS): %: %.o clean: rm -f $(OBJS) $(BINS) $(OTHER_OBJS) mal - -.PHONY: stats stats-lisp tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - ./$@ || exit 1; \ diff --git a/chuck/Makefile b/chuck/Makefile index f851639feb..bee2d7baae 100644 --- a/chuck/Makefile +++ b/chuck/Makefile @@ -1,18 +1,5 @@ -SOURCES_BASE = readline.ck reader.ck printer.ck \ - types/MalObject.ck types/MalSubr.ck \ - types/**/*.ck util/*.ck -SOURCES_LISP = env.ck core.ck stepA_mal.ck -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - all: clean: -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -.PHONY: all clean stats stats-lisp +.PHONY: all clean diff --git a/clojure/Makefile b/clojure/Makefile index f5b4f20ccc..55a0948c3b 100644 --- a/clojure/Makefile +++ b/clojure/Makefile @@ -34,12 +34,3 @@ node_modules: clean: rm -rf target/ mal.jar mal - -.PHONY: stats - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/coffee/Makefile b/coffee/Makefile index 0c2b36737a..74b3c96c35 100644 --- a/coffee/Makefile +++ b/coffee/Makefile @@ -1,5 +1,3 @@ -TESTS = - SOURCES_BASE = node_readline.coffee types.coffee \ reader.coffee printer.coffee SOURCES_LISP = env.coffee core.coffee stepA_mal.coffee @@ -23,12 +21,3 @@ mal: mal.coffee clean: rm -f mal.coffee mal - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `egrep "^\w*#|^\w*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `egrep "^\w*#|^\w*$$" $^ | wc` "[comments/blanks]" diff --git a/common-lisp/Makefile b/common-lisp/Makefile index a80f34f58c..7572f4f878 100644 --- a/common-lisp/Makefile +++ b/common-lisp/Makefile @@ -20,8 +20,6 @@ MKCL ?= mkcl STANDALONE_EXE = sbcl clisp ccl ecl cmucl ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) -SOURCES_LISP := src/env.lisp src/core.lisp src/stepA_mal.lisp -SOURCES := src/utils.lisp src/types.lisp src/reader.lisp src/printer.lisp $(SOURCES_LISP) # Record the Common Lisp implementation used for all steps built in this # invocation This is used in the targets to rebuild the step if the @@ -29,7 +27,6 @@ SOURCES := src/utils.lisp src/types.lisp src/reader.lisp src/printer.lisp $(SOUR $(foreach step, $(call steps), $(call record_lisp,$(patsubst step%,%,$(step)),$(LISP))) .PRECIOUS: hist/%_impl -.PHONY: stats all : stepA_mal @@ -68,11 +65,3 @@ endif clean: find . -maxdepth 1 -name 'step*' -executable -delete rm -f *.lib *.fas[l] images/* hist/*_impl - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/cpp/Makefile b/cpp/Makefile index ad20eb8360..2377031883 100644 --- a/cpp/Makefile +++ b/cpp/Makefile @@ -52,16 +52,3 @@ clean: rm -rf *.o $(TARGETS) libmal.a .deps mal -include .deps - - -### Stats - -.PHONY: stats stats-lisp - -stats: $(LIBSOURCES) stepA_mal.cpp - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -stats-lisp: Core.cpp Environment.cpp stepA_mal.cpp - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/crystal/Makefile b/crystal/Makefile index 8373692259..42739be52c 100644 --- a/crystal/Makefile +++ b/crystal/Makefile @@ -29,12 +29,5 @@ step4_if_fn_do step5_tco step6_file step7_quote step8_macros step9_try stepA_mal clean: rm -rf $(STEP_BINS) mal .crystal -stats: types.cr error.cr reader.cr printer.cr env.cr core.cr stepA_mal.cr - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: env.cr core.cr stepA_mal.cr - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -.PHONY: all clean stats stats-lisp +.PHONY: all clean diff --git a/cs/Makefile b/cs/Makefile index 8431f704c1..52529a915b 100644 --- a/cs/Makefile +++ b/cs/Makefile @@ -2,8 +2,6 @@ DEBUG = -TESTS = - SOURCES_BASE = readline.cs types.cs reader.cs printer.cs SOURCES_LISP = env.cs core.cs stepA_mal.cs SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) @@ -43,18 +41,3 @@ mal.dll: $(LIB_SRCS) clean: rm -f mal *.dll *.exe *.mdb - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - ./$@ || exit 1; \ diff --git a/d/Makefile b/d/Makefile index 57d4803f3a..75a19fb7dd 100644 --- a/d/Makefile +++ b/d/Makefile @@ -3,14 +3,6 @@ LDFLAGS += -lreadline ##################### -TESTS = - -SOURCES_BASE = readline.d types.d reader.d printer.d -SOURCES_LISP = env.d mal_core.d stepA_mal.d -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - -##################### - EARLY_SRCS = step0_repl.d step1_read_print.d step2_eval.d LATE_SRCS = step3_env.d step4_if_fn_do.d step5_tco.d step6_file.d \ step7_quote.d step8_macros.d step9_try.d stepA_mal.d @@ -42,18 +34,3 @@ $(BINS): %: %.o clean: rm -f $(OBJS) $(BINS) $(OTHER_OBJS) mal - -.PHONY: stats stats-lisp tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - ./$@ || exit 1; \ diff --git a/dart/Makefile b/dart/Makefile index 25bd0e6d6f..b3c660f49d 100644 --- a/dart/Makefile +++ b/dart/Makefile @@ -1,16 +1,5 @@ all: @true -SOURCES_BASE = types.dart reader.dart printer.dart -SOURCES_LISP = env.dart core.dart stepA_mal.dart -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +clean: diff --git a/elisp/Makefile b/elisp/Makefile index f554c38b3d..7af3113c71 100644 --- a/elisp/Makefile +++ b/elisp/Makefile @@ -1,17 +1,3 @@ -SOURCES_BASE = mal/reader.el mal/printer.el mal/types.el -SOURCES_LISP = mal/env.el mal/func.el mal/core.el stepA_mal.el -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - all: clean: - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - diff --git a/elixir/Makefile b/elixir/Makefile index 7f922cc097..7bae647688 100644 --- a/elixir/Makefile +++ b/elixir/Makefile @@ -14,12 +14,4 @@ clean: mix clean rm -f mal -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -.PHONY: clean stats stats-lisp +.PHONY: clean diff --git a/elm/Makefile b/elm/Makefile index 1ad56ff655..0850dce973 100644 --- a/elm/Makefile +++ b/elm/Makefile @@ -2,8 +2,6 @@ SOURCES = step0_repl.elm step1_read_print.elm step2_eval.elm \ step3_env.elm step4_if_fn_do.elm step5_tco.elm step6_file.elm \ step7_quote.elm step8_macros.elm step9_try.elm stepA_mal.elm -SOURCES_LISP = Env.elm Core.elm Eval.elm stepA_mal.elm - BINS = $(SOURCES:%.elm=%.js) ELM_MAKE = node_modules/.bin/elm-make @@ -40,11 +38,3 @@ stepA_mal.js: $(STEP4_SOURCES) clean: rm -f $(BINS) - -stats: $(STEP4_SOURCES) stepA_mal.elm - @wc $^ - @printf "%5s %5s %5s %s\n" `egrep "^\w*(--|\{-|-\})|^\w*$$" $^ | wc` "[comments/blanks]" - -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `egrep "^\w*(--|\{-|-\})|^\w*$$" $^ | wc` "[comments/blanks]" diff --git a/erlang/Makefile b/erlang/Makefile index 808a2a9019..5ad84cca76 100644 --- a/erlang/Makefile +++ b/erlang/Makefile @@ -12,7 +12,7 @@ BINS = $(SRCS:%.erl=%) ##################### -.PHONY: all dist clean stats stats-lisp +.PHONY: all dist clean all: $(BINS) @@ -35,10 +35,3 @@ $(foreach b,$(BINS),$(eval $(call dep_template,$(b)))) clean: rebar clean rm -f mal - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*%|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*%|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/es6/Makefile b/es6/Makefile index b88fd2a0d1..15ffe6aa49 100644 --- a/es6/Makefile +++ b/es6/Makefile @@ -27,18 +27,3 @@ mal: mal.js clean: rm -f mal.js mal rm -rf node_modules - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - node $@ || exit 1; \ diff --git a/factor/Makefile b/factor/Makefile index b11d591f32..e4cabeff1d 100644 --- a/factor/Makefile +++ b/factor/Makefile @@ -1,5 +1,3 @@ -TESTS = - SOURCES_BASE = lib/types/types.factor lib/reader/reader.factor lib/printer/printer.factor SOURCES_LISP = lib/env/env.factor lib/core/core.factor stepA_mal/stepA_mal.factor SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) @@ -31,13 +29,3 @@ mal: mal.factor clean: rm -f mal.factor - -.PHONY: stats stats-lisp - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\!|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\!|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - diff --git a/fantom/Makefile b/fantom/Makefile index 97ebecc100..2b95720ab0 100644 --- a/fantom/Makefile +++ b/fantom/Makefile @@ -1,7 +1,3 @@ -SOURCES_BASE = src/mallib/fan/interop.fan src/mallib/fan/reader.fan src/mallib/fan/types.fan -SOURCES_LISP = src/mallib/fan/env.fan src/mallib/fan/core.fan src/stepA_mal/fan/main.fan -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - all: dist dist: lib/fan/mal.pod @@ -20,12 +16,3 @@ lib/fan/mallib.pod: src/mallib/build.fan src/mallib/fan/*.fan lib/fan clean: rm -rf lib - -.PHONY: stats - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/forth/Makefile b/forth/Makefile index c619d19684..70617396d2 100644 --- a/forth/Makefile +++ b/forth/Makefile @@ -17,12 +17,3 @@ mal: mal.fs clean: rm -f mal.fs mal - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*[\\]|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*[\\]|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/fsharp/Makefile b/fsharp/Makefile index 9c56a1e021..505d3ee5d3 100644 --- a/fsharp/Makefile +++ b/fsharp/Makefile @@ -2,8 +2,6 @@ DEBUG = -TESTS = - SOURCES_BASE = types.fs error.fs node.fs printer.fs tokenizer.fs reader.fs \ readline.fs SOURCES_LISP = core.fs env.fs stepA_mal.fs @@ -46,18 +44,3 @@ mal.dll: $(DLL_SOURCES) Mono.Terminal.dll clean: rm -f mal *.dll *.exe *.mdb - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - ./$@ || exit 1; \ diff --git a/gnu-smalltalk/Makefile b/gnu-smalltalk/Makefile index c876270e97..7af3113c71 100644 --- a/gnu-smalltalk/Makefile +++ b/gnu-smalltalk/Makefile @@ -1,17 +1,3 @@ -SOURCES_BASE = readline.st reader.st printer.st types.st util.st -SOURCES_LISP = env.st func.st core.st stepA_mal.st -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - all: clean: - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - diff --git a/go/Makefile b/go/Makefile index f35976a37d..f2094e8e14 100644 --- a/go/Makefile +++ b/go/Makefile @@ -5,9 +5,6 @@ export GOPATH := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) SOURCES_BASE = src/types/types.go src/readline/readline.go \ src/reader/reader.go src/printer/printer.go \ src/env/env.go src/core/core.go -SOURCES_LISP = src/env/env.go src/core/core.go \ - src/stepA_mal/stepA_mal.go -SOURCES = $(SOURCES_BASE) $(word $(words $(SOURCES_LISP)),${SOURCES_LISP}) ##################### @@ -34,12 +31,3 @@ $(foreach b,$(BINS),$(eval $(call dep_template,$(b)))) clean: rm -f $(BINS) mal - -.PHONY: stats stats-lisp - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/groovy/Makefile b/groovy/Makefile index b76e71f45f..888eb9fc24 100644 --- a/groovy/Makefile +++ b/groovy/Makefile @@ -1,9 +1,5 @@ CLASSES = types.class reader.class printer.class env.class core.class -SOURCES_BASE = types.groovy reader.groovy printer.groovy -SOURCES_LISP = env.groovy core.groovy stepA_mal.groovy -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - all: ${CLASSES} dist: mal.jar @@ -40,12 +36,3 @@ mal: mal.jar clean: rm -f *.class classes/* mal.jar mal rmdir classes || true - -.PHONY: stats tests - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/guile/Makefile b/guile/Makefile index e7e6f345a7..993bd8cdd8 100644 --- a/guile/Makefile +++ b/guile/Makefile @@ -15,12 +15,3 @@ mal.scm: $(SOURCES) clean: rm -f mal.scm - -.PHONY: stats - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/haskell/Makefile b/haskell/Makefile index b5912c6927..6b1fd07a4f 100644 --- a/haskell/Makefile +++ b/haskell/Makefile @@ -1,9 +1,3 @@ -SOURCES_BASE = Readline.hs Types.hs Reader.hs Printer.hs -SOURCES_LISP = Env.hs Core.hs step9_try.hs -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - -##################### - SRCS = step0_repl.hs step1_read_print.hs step2_eval.hs step3_env.hs \ step4_if_fn_do.hs step5_tco.hs step6_file.hs step7_quote.hs \ step8_macros.hs step9_try.hs stepA_mal.hs @@ -24,12 +18,3 @@ $(BINS): %: %.hs $(OTHER_SRCS) clean: rm -f $(BINS) mal *.hi *.o - -.PHONY: stats stats-lisp tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*--|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*--|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/haxe/Makefile b/haxe/Makefile index 0602c9edf7..4d2133ab33 100644 --- a/haxe/Makefile +++ b/haxe/Makefile @@ -1,8 +1,6 @@ STEP1_DEPS = Compat.hx types/Types.hx reader/Reader.hx printer/Printer.hx STEP3_DEPS = $(STEP1_DEPS) env/Env.hx STEP4_DEPS = $(STEP3_DEPS) core/Core.hx -SOURCES = $(STEP4_DEPS) StepA_mal.hx -SOURCES_LISP = env/Env.hx core/Core.hx StepA_mal.hx STEPS = step0_repl step1_read_print step2_eval step3_env \ step4_if_fn_do step5_tco step6_file step7_quote \ @@ -104,13 +102,3 @@ clean: rm -f mal.n mal.py cpp/mal mal.js mal rm -f step*.py step*.js step*.n [ -e cpp/ ] && rm -r cpp/ || true - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - diff --git a/hy/Makefile b/hy/Makefile index 2e6a5d7b64..47f487eba1 100644 --- a/hy/Makefile +++ b/hy/Makefile @@ -1,7 +1,3 @@ -SOURCES_BASE = mal_types.hy reader.hy printer.hy -SOURCES_LISP = env.hy core.hy stepA_mal.hy -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - all: mal.hy mal.hy: stepA_mal.hy @@ -9,13 +5,3 @@ mal.hy: stepA_mal.hy clean: rm -f mal.hy *.pyc - -#.PHONY: stats tests $(TESTS) -.PHONY: stats - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/io/Makefile b/io/Makefile index 5da1e5e5f7..d2e469ecd4 100644 --- a/io/Makefile +++ b/io/Makefile @@ -1,20 +1,4 @@ -TESTS = +all: + @true -SOURCES_BASE = MalReadline.io MalTypes.io MalReader.io -SOURCES_LISP = Env.io MalCore.io stepA_mal.io -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - ruby $@ || exit 1; \ +clean: diff --git a/java/Makefile b/java/Makefile index 8e256e5822..34d38feb7d 100644 --- a/java/Makefile +++ b/java/Makefile @@ -1,7 +1,4 @@ -TESTS = - - SOURCES_BASE = src/main/java/mal/readline.java src/main/java/mal/types.java \ src/main/java/mal/reader.java src/main/java/mal/printer.java SOURCES_LISP = src/main/java/mal/env.java src/main/java/mal/core.java \ @@ -31,13 +28,3 @@ target/classes/mal/step%.class: src/main/java/mal/step%.java ${SOURCES} clean: mvn clean rm -f mal.jar mal - -#.PHONY: stats tests $(TESTS) -.PHONY: stats - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/js/Makefile b/js/Makefile index 8e4ad35bf1..faef0bfe28 100644 --- a/js/Makefile +++ b/js/Makefile @@ -34,14 +34,7 @@ clean: rm -f mal.js web/mal.js rm -rf node_modules -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +.PHONY: tests $(TESTS) tests: $(TESTS) diff --git a/julia/Makefile b/julia/Makefile index 21f501f230..82fa2ef848 100644 --- a/julia/Makefile +++ b/julia/Makefile @@ -1,17 +1,4 @@ - -SOURCES_BASE = reader.jl printer.jl readline_mod.jl types.jl -SOURCES_LISP = env.jl core.jl stepA_mal.jl -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - all: clean: -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/kotlin/Makefile b/kotlin/Makefile index 8e729689cc..1a9a6dfee1 100644 --- a/kotlin/Makefile +++ b/kotlin/Makefile @@ -2,7 +2,6 @@ SOURCES_BASE = reader.kt printer.kt types.kt env.kt core.kt readline.kt SOURCES_LISP = step0_repl.kt step1_read_print.kt step2_eval.kt step3_env.kt step4_if_fn_do.kt \ step5_tco.kt step6_file.kt step7_quote.kt step8_macros.kt step9_try.kt stepA_mal.kt -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) JARS = $(SOURCES_LISP:%.kt=%.jar) all: $(JARS) @@ -22,13 +21,3 @@ clean: $(JARS): %.jar: src/mal/%.kt $(SOURCES_BASE:%.kt=src/mal/%.kt) kotlinc src/mal/$(@:%.jar=%.kt) $(SOURCES_BASE:%.kt=src/mal/%.kt) -include-runtime -d $@ - -.PHONY: stats - -stats: $(SOURCES:%.kt=src/mal/%.kt) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -stats-lisp: $(SOURCES_LISP:%.kt=src/mal/%.kt) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/livescript/Makefile b/livescript/Makefile index faec18c5a6..07c91d250a 100644 --- a/livescript/Makefile +++ b/livescript/Makefile @@ -2,7 +2,6 @@ SOURCES_BASE = reader.ls printer.ls env.ls core.ls utils.ls SOURCES_STEPS = step0_repl.ls step1_read_print.ls step2_eval.ls \ step4_if_fn_do.ls step5_tco.ls step6_file.ls step7_quote.ls \ step8_macros.ls step9_try.ls stepA_mal.ls -SOURCES_LISP = env.ls core.ls stepA_mal.ls SOURCES = $(SOURCES_BASE) $(SOURCES_STEPS) BINS = $(SOURCES:%.ls=%.js) @@ -30,11 +29,3 @@ stepA_mal.js: utils.js reader.js printer.js env.js core.js clean: rm -f $(BINS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `egrep "^\w*#|^\w*$$" $^ | wc` "[comments/blanks]" - -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `egrep "^\w*#|^\w*$$" $^ | wc` "[comments/blanks]" diff --git a/logo/Makefile b/logo/Makefile index 2beda63bde..bb1b747413 100644 --- a/logo/Makefile +++ b/logo/Makefile @@ -2,7 +2,7 @@ SOURCES_BASE = readline.lg types.lg reader.lg printer.lg SOURCES_LISP = env.lg core.lg stepA_mal.lg SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -.PHONY: all dist clean stats stats-lisp +.PHONY: all dist clean all: @true @@ -19,10 +19,3 @@ mal: mal.lg clean: rm -f mal.lg mal - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/lua/Makefile b/lua/Makefile index 042e3b0cde..44778cf75b 100644 --- a/lua/Makefile +++ b/lua/Makefile @@ -1,5 +1,3 @@ -TESTS = - SOURCES_BASE = utils.lua types.lua reader.lua printer.lua SOURCES_LISP = env.lua core.lua stepA_mal.lua SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) @@ -28,16 +26,6 @@ clean: rm -f linenoise.so mal.lua mal rm -rf lib/lua/5.1 -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*--|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*--|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - - .PHONY: libs libs: linenoise.so diff --git a/make/Makefile b/make/Makefile index 913ae19dd1..f8a16ebb1c 100644 --- a/make/Makefile +++ b/make/Makefile @@ -22,14 +22,7 @@ mal: mal.mk clean: rm -f mal.mk mal -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +.PHONY: tests $(TESTS) tests: $(TESTS) diff --git a/mal/Makefile b/mal/Makefile index 322c9cad5c..e54f29c8de 100644 --- a/mal/Makefile +++ b/mal/Makefile @@ -1,10 +1,3 @@ - -TESTS = - -SOURCES_BASE = -SOURCES_LISP = env.mal core.mal stepA_mal.mal -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - all: mal.mal mal.mal: stepA_mal.mal @@ -12,19 +5,3 @@ mal.mal: stepA_mal.mal clean: rm -f mal.mal - -#.PHONY: stats tests $(TESTS) -.PHONY: stats - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -#tests: $(TESTS) -# -#$(TESTS): -# @echo "Running $@"; \ -# python $@ || exit 1; \ diff --git a/matlab/Makefile b/matlab/Makefile index 1363956831..82fa2ef848 100644 --- a/matlab/Makefile +++ b/matlab/Makefile @@ -1,20 +1,4 @@ -SOURCES_BASE = type_utils.m Dict.m types/Nil.m types/MalException.m \ - types/Symbol.m types/List.m types/Vector.m \ - types/HashMap.m types/Function.m types/Atom.m \ - types/Reader.m reader.m printer.m -SOURCES_LISP = Env.m core.m stepA_mal.m -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - all: clean: -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*%|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*%|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/miniMAL/Makefile b/miniMAL/Makefile index 71c717e956..4d5a808de2 100644 --- a/miniMAL/Makefile +++ b/miniMAL/Makefile @@ -28,12 +28,3 @@ mal: mal.json chmod +x $@ clean: - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/nasm/Makefile b/nasm/Makefile index bb690ea120..a3cf08c0aa 100644 --- a/nasm/Makefile +++ b/nasm/Makefile @@ -15,18 +15,3 @@ all: $(STEPS) .PHONY: clean clean: rm -f $(STEPS) $(STEPS:%=%.o) - -###################### - -SOURCES_BASE = reader.asm printer.asm types.asm system.asm exceptions.asm -SOURCES_LISP = env.asm core.asm stepA_mal.asm -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - -.PHONY: stats stats-lisp - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/nim/Makefile b/nim/Makefile index 9b145d653f..11fd6cb91f 100644 --- a/nim/Makefile +++ b/nim/Makefile @@ -1,8 +1,6 @@ ##################### SOURCES_BASE = types.nim reader.nim printer.nim -SOURCES_LISP = env.nim core.nim stepA_mal.nim -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) SOURCES_REBUILD = $(SOURCES_BASE) env.nim core.nim ##################### @@ -27,12 +25,3 @@ $(BINS): %: %.nim $(SOURCES_REBUILD) clean: rm -rf nimcache-*/ $(BINS) rm -f mal - -.PHONY: stats stats-lisp - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/objc/Makefile b/objc/Makefile index 4afffc62ae..7e6fa2a07c 100644 --- a/objc/Makefile +++ b/objc/Makefile @@ -4,9 +4,6 @@ STEP2_DEPS = $(STEP1_DEPS) STEP3_DEPS = $(STEP2_DEPS) env.m STEP4_DEPS = $(STEP3_DEPS) malfunc.h malfunc.m core.h core.m -SOURCES = $(STEP4_DEPS) stepA_mal.m -SOURCES_LISP = env.h env.m core.h core.m stepA_mal.m - STEPS = step0_repl step1_read_print step2_eval step3_env \ step4_if_fn_do step5_tco step6_file step7_quote \ step8_macros step9_try stepA_mal @@ -51,12 +48,3 @@ step%: step%.m clean: rm -f $(STEPS) *.o *.d mal - -.PHONY: stats tests - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/objpascal/Makefile b/objpascal/Makefile index af938fbfd5..b8efc48c43 100644 --- a/objpascal/Makefile +++ b/objpascal/Makefile @@ -8,10 +8,6 @@ STEP1_DEPS = $(STEP0_DEPS) mal_types.pas reader.pas printer.pas STEP3_DEPS = $(STEP1_DEPS) mal_env.pas STEP4_DEPS = $(STEP3_DEPS) core.pas -SOURCES = mal_readline.pas mal_types.pas mal_func.pas \ - reader.pas printer.pas mal_env.pas core.pas stepA_mal.pas -SOURCES_LISP = mal_env.pas core.pas stepA_mal.pas - ##################### DEBUG = -gl @@ -33,13 +29,3 @@ step4_if_fn_do step5_tco step6_file step7_quote step8_macros step9_try stepA_mal clean: rm -f $(STEPS:%.pas=%) *.o *.ppu regexpr/Source/*.o regexpr/Source/*.ppu mal - -.PHONY: stats stats-lisp - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - diff --git a/ocaml/Makefile b/ocaml/Makefile index 6a2bb69ae1..a12f3fc162 100644 --- a/ocaml/Makefile +++ b/ocaml/Makefile @@ -30,11 +30,4 @@ $(STEP_BINS): %: %.ml $(MAL_LIB) clean: rm -f $(STEP_BINS) mal mal_lib.* *.cmo *.cmx *.cmi *.o -stats: $(MODULES) stepA_mal.ml - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\(\*|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: env.ml core.ml stepA_mal.ml - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\(\*|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -.PHONY: all repl clean stats stats-lisp +.PHONY: all repl clean diff --git a/perl/Makefile b/perl/Makefile index 947edc46ef..ec18d485f0 100644 --- a/perl/Makefile +++ b/perl/Makefile @@ -1,5 +1,3 @@ -TESTS = - SOURCES_BASE = readline.pm types.pm reader.pm printer.pm \ interop.pm SOURCES_LISP = env.pm core.pm stepA_mal.pl @@ -25,18 +23,3 @@ mal: mal.pl clean: rm -f mal.pl mal fatpacker.trace packlists fatlib/* [ -d fatlib ] && rmdir fatlib || true - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - ruby $@ || exit 1; \ diff --git a/perl6/Makefile b/perl6/Makefile index bd88eee87c..d2e469ecd4 100644 --- a/perl6/Makefile +++ b/perl6/Makefile @@ -1,18 +1,4 @@ - -SOURCES_BASE = types.pm reader.pm printer.pm -SOURCES_LISP = env.pm core.pm stepA_mal.pl -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - all: @true -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - - +clean: diff --git a/php/Makefile b/php/Makefile index fbf83b0a3b..1682d6c40f 100644 --- a/php/Makefile +++ b/php/Makefile @@ -1,6 +1,3 @@ - -TESTS = - SOURCES_BASE = readline.php types.php reader.php printer.php interop.php SOURCES_LISP = env.php core.php stepA_mal.php SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) @@ -22,18 +19,3 @@ mal-web.php: mal.php clean: rm -f mal.php mal mal-web.php - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*//|^[[:space:]]/\*|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*//|^[[:space:]]/\*|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - php $@ || exit 1; \ diff --git a/picolisp/Makefile b/picolisp/Makefile index 2ee2780a22..7af3113c71 100644 --- a/picolisp/Makefile +++ b/picolisp/Makefile @@ -1,17 +1,3 @@ -SOURCES_BASE = readline.l reader.l printer.l types.l -SOURCES_LISP = env.l func.l core.l stepA_mal.l -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - all: clean: - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - diff --git a/plpgsql/Makefile b/plpgsql/Makefile index c7eb4a47ae..7af3113c71 100644 --- a/plpgsql/Makefile +++ b/plpgsql/Makefile @@ -1,14 +1,3 @@ -SOURCES_LISP = envs.sql core.sql stepA_mal.sql -SOURCES = wrap.sh io.sql init.sql types.sql reader.sql printer.sql $(SOURCES_LISP) - all: -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*--|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*--|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - +clean: diff --git a/plsql/Makefile b/plsql/Makefile index 2660df0919..7af3113c71 100644 --- a/plsql/Makefile +++ b/plsql/Makefile @@ -1,12 +1,3 @@ -SOURCES_LISP = env.sql core.sql stepA_mal.sql -SOURCES = wrap.sh login.sql io.sql types.sql reader.sql printer.sql $(SOURCES_LISP) - -.PHONY: stats - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*--|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*--|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +all: +clean: diff --git a/powershell/Makefile b/powershell/Makefile index 24dd3614d6..b8722e6d92 100644 --- a/powershell/Makefile +++ b/powershell/Makefile @@ -1,15 +1,4 @@ -SOURCES_BASE = types.psm1 reader.psm1 printer.psm1 -SOURCES_LISP = env.psm1 core.psm1 stepA_mal.ps1 -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - all: true -.PHONY: stats - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" +clean: diff --git a/ps/Makefile b/ps/Makefile index 67741380c3..98a0d37408 100644 --- a/ps/Makefile +++ b/ps/Makefile @@ -1,6 +1,3 @@ - -TESTS = - SOURCES_BASE = types.ps reader.ps printer.ps SOURCES_LISP = env.ps core.ps stepA_mal.ps SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) @@ -21,19 +18,3 @@ mal: mal.ps clean: rm -f mal.ps mal - - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*%|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*%|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - gs -q -dNODISPLAY -- $@ || exit 1; \ diff --git a/python/Makefile b/python/Makefile index c16b83ffe9..2403ecd25f 100644 --- a/python/Makefile +++ b/python/Makefile @@ -1,7 +1,3 @@ - -TESTS = - - SOURCES_BASE = mal_readline.py mal_types.py reader.py printer.py SOURCES_LISP = env.py core.py stepA_mal.py SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) @@ -24,18 +20,3 @@ mal: mal.pyz clean: rm -f mal.pyz mal - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - python $@ || exit 1; \ diff --git a/r/Makefile b/r/Makefile index 85e3247fac..f9ec4a7491 100644 --- a/r/Makefile +++ b/r/Makefile @@ -1,5 +1,3 @@ -TESTS = - SOURCES_BASE = readline.r types.r reader.r printer.r SOURCES_LISP = env.r core.r stepA_mal.r SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) @@ -19,16 +17,6 @@ mal: mal.r clean: rm -f mal.r mal -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - - .PHONY: libs: lib/rdyncall diff --git a/racket/Makefile b/racket/Makefile index 17b07dcd28..89bcac4ae0 100644 --- a/racket/Makefile +++ b/racket/Makefile @@ -12,12 +12,3 @@ mal: $(SOURCES) clean: rm -f mal - -.PHONY: stats - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/rexx/Makefile b/rexx/Makefile index 151ff038d6..b5a49b3b94 100644 --- a/rexx/Makefile +++ b/rexx/Makefile @@ -1,9 +1,3 @@ -TESTS = - -SOURCES_BASE = readline.rexx types.rexx reader.rexx printer.rexx -SOURCES_LISP = env.rexx core.rexx stepA_mal.rexx -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - SRCS = step0_repl.rexx step1_read_print.rexx step2_eval.rexx step3_env.rexx \ step4_if_fn_do.rexx step5_tco.rexx step6_file.rexx step7_quote.rexx \ step8_macros.rexx step9_try.rexx stepA_mal.rexx @@ -27,17 +21,4 @@ $(PREPROCESSED): %.rexxpp: %.rexx readline.rexx types.rexx reader.rexx printer.r clean: rm -f mal.rexx mal *.rexxpp -.PHONY: all dist clean stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - rexx $@ || exit 1; \ +.PHONY: all dist clean diff --git a/rpython/Makefile b/rpython/Makefile index a86b4dd499..95a38adfd6 100644 --- a/rpython/Makefile +++ b/rpython/Makefile @@ -4,10 +4,6 @@ RPYTHON = rpython UPPER_STEPS = step4_if_fn_do step5_tco step6_file step7_quote step8_macros step9_try stepA_mal STEPS = step0_repl step1_read_print step2_eval step3_env $(UPPER_STEPS) -SOURCES_BASE = mal_readline.py mal_types.py reader.py printer.py -SOURCES_LISP = env.py core.py stepA_mal.py -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - all: $(STEPS) dist: mal @@ -28,16 +24,9 @@ step1_read_print step2_eval: $(STEP1_DEPS) step3_env: $(STEP3_DEPS) $(UPPER_STEPS): $(STEP4_DEPS) -.PHONY: clean stats stats-lisp +.PHONY: clean clean: rm -f mal $(STEPS) *.pyc rm -rf __pycache__ -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - diff --git a/ruby/Makefile b/ruby/Makefile index c677e1a237..13470a4e3d 100644 --- a/ruby/Makefile +++ b/ruby/Makefile @@ -1,5 +1,3 @@ -TESTS = - SOURCES_BASE = mal_readline.rb types.rb reader.rb printer.rb SOURCES_LISP = env.rb core.rb stepA_mal.rb SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) @@ -19,18 +17,3 @@ mal: mal.rb clean: rm -f mal.rb mal - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - ruby $@ || exit 1; \ diff --git a/rust/Makefile b/rust/Makefile index 49fc80fb21..107815fb88 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -2,10 +2,6 @@ UPPER_STEPS = step4_if_fn_do step5_tco step6_file step7_quote step8_macros step9_try stepA_mal STEPS = step0_repl step1_read_print step2_eval step3_env $(UPPER_STEPS) -SOURCES_BASE = types.rs reader.rs printer.rs -SOURCES_LISP = env.rs core.rs stepA_mal.rs -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - all: $(STEPS) dist: mal @@ -27,17 +23,9 @@ step1_read_print step2_eval: $(STEP1_DEPS) step3_env: $(STEP3_DEPS) $(UPPER_STEPS): $(STEP4_DEPS) -.PHONY: clean stats stats-lisp +.PHONY: clean clean: cargo clean rm -f $(STEPS) rm -f mal - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - diff --git a/scala/Makefile b/scala/Makefile index 1713ad5feb..12ca5b834b 100644 --- a/scala/Makefile +++ b/scala/Makefile @@ -1,5 +1,3 @@ -TESTS = - SOURCES_BASE = types.scala reader.scala printer.scala SOURCES_LISP = env.scala core.scala stepA_mal.scala SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) @@ -22,17 +20,4 @@ $(TARGET_DIR)/classes/step%.class: step%.scala $(SOURCES) clean: rm -rf mal target -.PHONY: all dist clean stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - ruby $@ || exit 1; \ +.PHONY: all dist clean diff --git a/scheme/Makefile b/scheme/Makefile index 2239360349..a1a1d62dbb 100644 --- a/scheme/Makefile +++ b/scheme/Makefile @@ -1,6 +1,3 @@ -SOURCES_BASE = lib/util.sld lib/reader.sld lib/printer.sld lib/types.sld -SOURCES_LISP = lib/env.sld lib/core.sld stepA_mal.scm -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) BINS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do step5_tco BINS += step6_file step7_quote step8_macros step9_try stepA_mal scheme_MODE ?= chibi @@ -62,7 +59,7 @@ RMR = rm -rf all: $(STEPS) -.PHONY: clean stats stats-lisp +.PHONY: clean .PRECIOUS: lib/%.scm eggs/lib.%.scm eggs/r7rs.so: @@ -105,10 +102,3 @@ clean: $(RM) lib.*.scm *.so *.c *.o $(BINS) $(RM) eggs/* $(RMR) out - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/skew/Makefile b/skew/Makefile index b13d5f5427..58bd6641fc 100644 --- a/skew/Makefile +++ b/skew/Makefile @@ -2,8 +2,6 @@ STEPS = step0_repl step1_read_print step2_eval step3_env step4_if_fn_do \ step5_tco step6_file step7_quote step8_macros step9_try stepA_mal SOURCES_BASE = util.sk types.sk reader.sk printer.sk -SOURCES_LISP = env.sk core.sk stepA_mal.sk -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) STEP3_DEPS = $(SOURCES_BASE) env.sk STEP4_DEPS = $(STEP3_DEPS) core.sk @@ -26,11 +24,4 @@ mal: stepA_mal.js clean: rm -rf step*.js mal -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -.PHONY: all dist clean stats stats-lisp +.PHONY: all dist clean diff --git a/swift/Makefile b/swift/Makefile index 472d7a0aff..6372e0e043 100644 --- a/swift/Makefile +++ b/swift/Makefile @@ -226,16 +226,3 @@ dump: @echo " SWIFT = $(SWIFT)" @echo "SDKROOT = $(SDKROOT)" @echo " STEPS = $(call get_all_step_numbers)" - -# -# Display source stats -# -.PHONY: stats tests $(TESTS) - -stats: bridging-header.h $(UTIL_SRC) ./stepA_mal.swift - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: ./env.swift ./core.swift ./stepA_mal.swift - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - diff --git a/swift3/Makefile b/swift3/Makefile index 1a16beae7c..a76309c52c 100644 --- a/swift3/Makefile +++ b/swift3/Makefile @@ -7,9 +7,6 @@ endif STEP3_DEPS = Sources/types.swift Sources/reader.swift Sources/printer.swift Sources/env.swift STEP4_DEPS = $(STEP3_DEPS) Sources/core.swift -SOURCES = $(STEP4_DEPS) Sources/stepA_mal/main.swift -SOURCES_LISP = Sources/env.swift Sources/core.swift Sources/stepA_mal/main.swift - STEPS = step0_repl step1_read_print step2_eval step3_env \ step4_if_fn_do step5_tco step6_file step7_quote \ step8_macros step9_try stepA_mal @@ -30,11 +27,3 @@ step%: Sources/step%/main.swift clean: rm -f $(STEPS) mal -.PHONY: stats tests - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/swift4/Makefile b/swift4/Makefile index 1a16beae7c..5bb375446d 100644 --- a/swift4/Makefile +++ b/swift4/Makefile @@ -7,9 +7,6 @@ endif STEP3_DEPS = Sources/types.swift Sources/reader.swift Sources/printer.swift Sources/env.swift STEP4_DEPS = $(STEP3_DEPS) Sources/core.swift -SOURCES = $(STEP4_DEPS) Sources/stepA_mal/main.swift -SOURCES_LISP = Sources/env.swift Sources/core.swift Sources/stepA_mal/main.swift - STEPS = step0_repl step1_read_print step2_eval step3_env \ step4_if_fn_do step5_tco step6_file step7_quote \ step8_macros step9_try stepA_mal @@ -29,12 +26,3 @@ step%: Sources/step%/main.swift clean: rm -f $(STEPS) mal - -.PHONY: stats tests - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/tcl/Makefile b/tcl/Makefile index 5105437e71..ba4ddbb379 100644 --- a/tcl/Makefile +++ b/tcl/Makefile @@ -2,8 +2,6 @@ SOURCES_BASE = mal_readline.tcl types.tcl reader.tcl printer.tcl SOURCES_LISP = env.tcl core.tcl stepA_mal.tcl SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -.PHONY: stats stats-lisp - all: true @@ -19,11 +17,3 @@ mal: mal.tcl clean: rm -f mal.tcl mal - - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/ts/Makefile b/ts/Makefile index 5b36261c10..1ea5a79e59 100644 --- a/ts/Makefile +++ b/ts/Makefile @@ -1,7 +1,3 @@ -SOURCES_BASE = types.ts reader.ts printer.ts -SOURCES_LISP = env.ts core.ts stepA_mal.ts -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - STEPS = step0_repl step1_read_print step2_eval step3_env \ step4_if_fn_do step5_tco step6_file step7_quote \ step8_macros step9_try stepA_mal @@ -15,16 +11,9 @@ step%.js: node_modules types.ts reader.ts printer.ts env.ts core.ts step%.ts ./node_modules/.bin/tsc -p ./ -.PHONY: ts clean stats tests $(TESTS) +.PHONY: ts clean ts: $(foreach s,$(STEPS),$(s).js) clean: rm -f *.js - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" diff --git a/vb/Makefile b/vb/Makefile index ce5145a08f..4f269959e1 100644 --- a/vb/Makefile +++ b/vb/Makefile @@ -2,8 +2,6 @@ DEBUG = -TESTS = - SOURCES_BASE = readline.vb types.vb reader.vb printer.vb SOURCES_LISP = env.vb core.vb stepA_mal.vb SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) @@ -39,18 +37,3 @@ mal_vb.dll: mal_cs.dll $(LIB_VB_SRCS) clean: rm -f *.dll *.exe *.mdb - -.PHONY: stats tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*'|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*'|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - ./$@ || exit 1; \ diff --git a/vhdl/Makefile b/vhdl/Makefile index dcca4a11f8..e76b8e104a 100644 --- a/vhdl/Makefile +++ b/vhdl/Makefile @@ -1,11 +1,3 @@ -TESTS = - -SOURCES_BASE = pkg_readline.vhdl types.vhdl printer.vhdl reader.vhdl -SOURCES_LISP = env.vhdl core.vhdl stepA_mal.vhdl -SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) - -##################### - SRCS = step0_repl.vhdl step1_read_print.vhdl step2_eval.vhdl step3_env.vhdl \ step4_if_fn_do.vhdl step5_tco.vhdl step6_file.vhdl step7_quote.vhdl \ step8_macros.vhdl step9_try.vhdl stepA_mal.vhdl @@ -41,18 +33,3 @@ $(BINS): %: %.o clean: rm -f $(OBJS) $(BINS) $(OTHER_OBJS) work-obj93.cf mal - -.PHONY: stats stats-lisp tests $(TESTS) - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -tests: $(TESTS) - -$(TESTS): - @echo "Running $@"; \ - ./$@ || exit 1; \ diff --git a/vimscript/Makefile b/vimscript/Makefile index 5062dafb31..da5409c3bf 100644 --- a/vimscript/Makefile +++ b/vimscript/Makefile @@ -27,11 +27,4 @@ vimextras.o: vimextras.c clean: rm -f vimextras.o libvimextras.so mal.vim mal -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*\"|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - -.PHONY: stats stats-lisp clean +.PHONY: clean diff --git a/wasm/Makefile b/wasm/Makefile index 02d4ea1654..cd30392b34 100644 --- a/wasm/Makefile +++ b/wasm/Makefile @@ -31,16 +31,3 @@ step7_quote.wasm step8_macros.wasm step9_try.wasm stepA_mal.wasm: $(STEP4_DEPS) clean: rm -f *.wat *.wasm - -.PHONY: stats tests - -SOURCES_ALL = $(filter %.wam,$(STEP4_DEPS)) stepA_mal.wam -SOURCES_LISP = $(filter-out $(STEP1_DEPS),$(SOURCES_ALL)) - -stats: $(SOURCES_ALL) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*;;|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" - diff --git a/yorick/Makefile b/yorick/Makefile index ccc3d68740..04f60c4e65 100644 --- a/yorick/Makefile +++ b/yorick/Makefile @@ -2,7 +2,7 @@ SOURCES_BASE = hash.i types.i reader.i printer.i SOURCES_LISP = env.i core.i stepA_mal.i SOURCES = $(SOURCES_BASE) $(SOURCES_LISP) -.PHONY: all dist clean stats stats-lisp +.PHONY: all dist clean all: dist @@ -15,10 +15,3 @@ mal: $(SOURCES) clean: rm -f mal - -stats: $(SOURCES) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" -stats-lisp: $(SOURCES_LISP) - @wc $^ - @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]" From 833fc8cc19b67c5805bed722d5b7d9bbcf574e33 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 20 Mar 2019 22:50:03 -0500 Subject: [PATCH 0809/2308] README: order and names updates. --- README.md | 74 +++++++++++++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 9fc40d3c33..8506574d34 100644 --- a/README.md +++ b/README.md @@ -11,16 +11,16 @@ | Language | Creator | | -------- | ------- | | [Ada](#ada) | [Chris Moore](https://github.com/zmower) | -| [GNU awk](#gnu-awk) | [Miutsuru Kariya](https://github.com/kariya-mitsuru) | +| [GNU Awk](#gnu-awk) | [Miutsuru Kariya](https://github.com/kariya-mitsuru) | | [Bash 4](#bash-4) | [Joel Martin](https://github.com/kanaka) | | [BASIC](#basic-c64-and-qbasic) (C64 & QBasic) | [Joel Martin](https://github.com/kanaka) | | [C](#c) | [Joel Martin](https://github.com/kanaka) | | [C++](#c-1) | [Stephen Thirlwall](https://github.com/sdt) | | [C#](#c-2) | [Joel Martin](https://github.com/kanaka) | | [ChucK](#chuck) | [Vasilij Schneidermann](https://github.com/wasamasa) | -| [Common Lisp](#common-lisp) | [Iqbal Ansari](https://github.com/iqbalansari) | | [Clojure](#clojure) (Clojure & ClojureScript) | [Joel Martin](https://github.com/kanaka) | | [CoffeeScript](#coffeescript) | [Joel Martin](https://github.com/kanaka) | +| [Common Lisp](#common-lisp) | [Iqbal Ansari](https://github.com/iqbalansari) | | [Crystal](#crystal) | [Linda_pp](https://github.com/rhysd) | | [D](#d) | [Dov Murik](https://github.com/dubek) | | [Dart](#dart) | [Harry Terkelsen](https://github.com/hterkelsen) | @@ -33,10 +33,10 @@ | [Factor](#factor) | [Jordan Lewis](https://github.com/jordanlewis) | | [Fantom](#fantom) | [Dov Murik](https://github.com/dubek) | | [Forth](#forth) | [Chris Houser](https://github.com/chouser) | -| [Go](#go) | [Joel Martin](https://github.com/kanaka) | -| [Groovy](#groovy) | [Joel Martin](https://github.com/kanaka) | | [GNU Guile](#gnu-guile-21) | [Mu Lei](https://github.com/NalaGinrut) | | [GNU Smalltalk](#gnu-smalltalk) | [Vasilij Schneidermann](https://github.com/wasamasa) | +| [Go](#go) | [Joel Martin](https://github.com/kanaka) | +| [Groovy](#groovy) | [Joel Martin](https://github.com/kanaka) | | [Haskell](#haskell) | [Joel Martin](https://github.com/kanaka) | | [Haxe](#haxe-neko-python-c-and-javascript) (Neko, Python, C++, & JS) | [Joel Martin](https://github.com/kanaka) | | [Hy](#hy) | [Joel Martin](https://github.com/kanaka) | @@ -50,7 +50,7 @@ | [Lua](#lua) | [Joel Martin](https://github.com/kanaka) | | [GNU Make](#gnu-make-381) | [Joel Martin](https://github.com/kanaka) | | [mal itself](#mal) | [Joel Martin](https://github.com/kanaka) | -| [Matlab](#matlab-gnu-octave-and-matlab) (GNU Octave & MATLAB) | [Joel Martin](https://github.com/kanaka) | +| [MATLAB](#matlab-gnu-octave-and-matlab) (GNU Octave & MATLAB) | [Joel Martin](https://github.com/kanaka) | | [miniMAL](#minimal) ([Repo](https://github.com/kanaka/miniMAL), [Demo](https://kanaka.github.io/miniMAL/)) | [Joel Martin](https://github.com/kanaka) | | [NASM](#nasm) | [Ben Dudson](https://github.com/bendudson) | | [Nim](#nim-0170) | [Dennis Felsing](https://github.com/def-) | @@ -63,7 +63,7 @@ | [Picolisp](#picolisp) | [Vasilij Schneidermann](https://github.com/wasamasa) | | [PL/pgSQL](#plpgsql-postgres-sql-procedural-language) (Postgres) | [Joel Martin](https://github.com/kanaka) | | [PL/SQL](#plsql-oracle-sql-procedural-language) (Oracle) | [Joel Martin](https://github.com/kanaka) | -| [Postscript](#postscript-level-23) | [Joel Martin](https://github.com/kanaka) | +| [PostScript](#postscript-level-23) | [Joel Martin](https://github.com/kanaka) | | [PowerShell](#powershell) | [Joel Martin](https://github.com/kanaka) | | [Python](#python-2x-and-3x) (2.X & 3.X) | [Joel Martin](https://github.com/kanaka) | | [RPython](#rpython) | [Joel Martin](https://github.com/kanaka) | @@ -75,7 +75,7 @@ | [Scala](#scala) | [Joel Martin](https://github.com/kanaka) | | [Scheme (R7RS)](#scheme-r7rs) | [Vasilij Schneidermann](https://github.com/wasamasa) | | [Skew](#skew) | [Dov Murik](https://github.com/dubek) | -| [Swift](#swift) | [Keith Rollin](https://github.com/keith-rollin) | +| [Swift 2](#swift) | [Keith Rollin](https://github.com/keith-rollin) | | [Swift 3](#swift-3) | [Joel Martin](https://github.com/kanaka) | | [Swift 4](#swift-4) | [陆é¥](https://github.com/LispLY) | | [Tcl](#tcl-86) | [Dov Murik](https://github.com/dubek) | @@ -263,19 +263,6 @@ cd chuck ./run ``` -### Common Lisp - -The implementation has been tested with SBCL, CCL, CMUCL, GNU CLISP, ECL and -Allegro CL on Ubuntu 16.04 and Ubuntu 12.04, see -the [README](common-lisp/README.org) for more details. Provided you have the -dependencies mentioned installed, do the following to run the implementation - -``` -cd common-lisp -make -./run -``` - ### Clojure For the most part the Clojure implementation requires Clojure 1.5, @@ -294,6 +281,19 @@ cd coffee coffee ./stepX_YYY ``` +### Common Lisp + +The implementation has been tested with SBCL, CCL, CMUCL, GNU CLISP, ECL and +Allegro CL on Ubuntu 16.04 and Ubuntu 12.04, see +the [README](common-lisp/README.org) for more details. Provided you have the +dependencies mentioned installed, do the following to run the implementation + +``` +cd common-lisp +make +./run +``` + ### Crystal The Crystal implementation of mal has been tested with Crystal 0.26.1. @@ -427,6 +427,22 @@ cd forth gforth stepX_YYY.fs ``` +### GNU Guile 2.1+ + +``` +cd guile +guile -L ./ stepX_YYY.scm +``` + +### GNU Smalltalk + +The Smalltalk implementation of mal has been tested with GNU Smalltalk 3.2.91. + +``` +cd gnu-smalltalk +./run +``` + ### Go The Go implementation of mal requires that go is installed on on the @@ -450,22 +466,6 @@ make groovy ./stepX_YYY.groovy ``` -### GNU Guile 2.1+ - -``` -cd guile -guile -L ./ stepX_YYY.scm -``` - -### GNU Smalltalk - -The Smalltalk implementation of mal has been tested with GNU Smalltalk 3.2.91. - -``` -cd gnu-smalltalk -./run -``` - ### Haskell The Haskell implementation requires the ghc compiler version 7.10.1 or @@ -660,7 +660,7 @@ make ./stepX_YYY ``` -### MatLab (GNU Octave and MATLAB) +### MATLAB (GNU Octave and MATLAB) The MatLab implementation has been tested with GNU Octave 4.2.1. It has also been tested with MATLAB version R2014a on Linux. Note that From 1eff25447a0a0078b272502e15937c721c27d721 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Mon, 18 Mar 2019 00:31:02 -0500 Subject: [PATCH 0810/2308] Stats collection script, dynamic scatter plot page --- docs/graph/all_data.json | 1303 +++++++++++++++++++++++++++++++++ docs/graph/base_data.yaml | 79 ++ docs/graph/collect_data.js | 222 ++++++ docs/graph/graph_languages.js | 214 ++++++ docs/graph/index.html | 99 +++ docs/graph/package.json | 10 + 6 files changed, 1927 insertions(+) create mode 100644 docs/graph/all_data.json create mode 100644 docs/graph/base_data.yaml create mode 100755 docs/graph/collect_data.js create mode 100644 docs/graph/graph_languages.js create mode 100644 docs/graph/index.html create mode 100644 docs/graph/package.json diff --git a/docs/graph/all_data.json b/docs/graph/all_data.json new file mode 100644 index 0000000000..9126bc9f2d --- /dev/null +++ b/docs/graph/all_data.json @@ -0,0 +1,1303 @@ +{ + "ada": { + "dir": "ada", + "name": "Ada", + "syntax": "Algol", + "type_check": "Static", + "modes": [], + "perf1": 9, + "perf2": 28, + "perf3": 1089, + "star_count": null, + "rank": 68, + "sloc": 5942, + "files": 29, + "author_name": "Chris Moore", + "author_url": "https://github.com/zmower", + "lloc": 3759 + }, + "awk": { + "dir": "awk", + "name": "GNU Awk", + "syntax": "C", + "type_check": "Dynamic", + "modes": [], + "perf1": 14, + "perf2": 47, + "perf3": 702, + "star_count": 355, + "rank": 50, + "sloc": 5213, + "files": 17, + "author_name": "Miutsuru Kariya", + "author_url": "https://github.com/kariya-mitsuru", + "lloc": 0 + }, + "bash": { + "dir": "bash", + "name": "Bash 4", + "syntax": "OTHER", + "type_check": "Dynamic", + "modes": [], + "perf1": 1681, + "perf2": 7748, + "perf3": 5, + "star_count": 59906, + "rank": 14, + "sloc": 2392, + "files": 18, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "basic": { + "dir": "basic", + "name": "BASIC", + "syntax": "OTHER", + "type_check": "Dynamic", + "modes": [ + "cbm", + "qbasic" + ], + "perf1": 11, + "perf2": 49, + "perf3": 871, + "star_count": null, + "rank": 68, + "sloc": 3963, + "files": 23, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 3956 + }, + "c": { + "dir": "c", + "name": "C", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 0.9, + "perf2": 2, + "perf3": 17211, + "star_count": 90804, + "rank": 9, + "sloc": 3701, + "files": 26, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 2012 + }, + "cpp": { + "dir": "cpp", + "name": "C++", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 0.9, + "perf2": 1, + "perf3": 16045, + "star_count": 139366, + "rank": 6, + "sloc": 3532, + "files": 29, + "author_name": "Stephen Thirlwall", + "author_url": "https://github.com/sdt", + "lloc": 1617 + }, + "cs": { + "dir": "cs", + "name": "C#", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 9, + "perf2": 10, + "perf3": 14958, + "star_count": 76670, + "rank": 10, + "sloc": 3396, + "files": 20, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 1760 + }, + "chuck": { + "dir": "chuck", + "name": "ChucK", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 35, + "perf2": 113, + "perf3": 132, + "star_count": null, + "rank": 68, + "sloc": 4962, + "files": 98, + "author_name": "Vasilij Schneidermann", + "author_url": "https://github.com/wasamasa", + "lloc": 1796 + }, + "clojure": { + "dir": "clojure", + "name": "Clojure", + "syntax": "Lisp", + "type_check": "Dynamic", + "modes": [ + "clj", + "cljs" + ], + "perf1": 25, + "perf2": 66, + "perf3": 2373, + "star_count": 5502, + "rank": 27, + "sloc": 1207, + "files": 23, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "coffee": { + "dir": "coffee", + "name": "CoffeeScript", + "syntax": "OTHER", + "type_check": "Dynamic", + "modes": [], + "perf1": 7, + "perf2": 33, + "perf3": 18111, + "star_count": 8569, + "rank": 22, + "sloc": 1073, + "files": 21, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "common-lisp": { + "dir": "common-lisp", + "name": "Common Lisp", + "syntax": "Lisp", + "type_check": "Dynamic", + "modes": [], + "perf1": 1, + "perf2": 2, + "perf3": 14665, + "star_count": 1146, + "rank": 38, + "sloc": 2607, + "files": 22, + "author_name": "Iqbal Ansari", + "author_url": "https://github.com/iqbalansari", + "lloc": 0 + }, + "crystal": { + "dir": "crystal", + "name": "Crystal", + "syntax": "OTHER", + "type_check": "Static", + "modes": [], + "perf1": 1, + "perf2": 2, + "perf3": 28255, + "star_count": 938, + "rank": 41, + "sloc": 2157, + "files": 18, + "author_name": "Linda_pp", + "author_url": "https://github.com/rhysd", + "lloc": 0 + }, + "d": { + "dir": "d", + "name": "D", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 0.9, + "perf2": 2, + "perf3": 15621, + "star_count": 653, + "rank": 45, + "sloc": 2979, + "files": 18, + "author_name": "Dov Murik", + "author_url": "https://github.com/dubek", + "lloc": 1434 + }, + "dart": { + "dir": "dart", + "name": "Dart", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 14, + "perf2": 38, + "perf3": 509, + "star_count": 2498, + "rank": 34, + "sloc": 2279, + "files": 18, + "author_name": "Harry Terkelsen", + "author_url": "https://github.com/hterkelsen", + "lloc": 1142 + }, + "elixir": { + "dir": "elixir", + "name": "Elixir", + "syntax": "OTHER", + "type_check": "Dynamic", + "modes": [], + "perf1": 14, + "perf2": 52, + "perf3": 765, + "star_count": 6115, + "rank": 24, + "sloc": 1824, + "files": 20, + "author_name": "Martin Ek", + "author_url": "https://github.com/ekmartin", + "lloc": 0 + }, + "elm": { + "dir": "elm", + "name": "Elm", + "syntax": "ML", + "type_check": "Static", + "modes": [], + "perf1": 69, + "perf2": 167, + "perf3": 801, + "star_count": 809, + "rank": 43, + "sloc": 5775, + "files": 24, + "author_name": "Jos van Bakel", + "author_url": "https://github.com/c0deaddict", + "lloc": 0 + }, + "elisp": { + "dir": "elisp", + "name": "Emacs Lisp", + "syntax": "Lisp", + "type_check": "Dynamic", + "modes": [], + "perf1": 16, + "perf2": 77, + "perf3": 593, + "star_count": 5689, + "rank": 26, + "sloc": 1982, + "files": 20, + "author_name": "Vasilij Schneidermann", + "author_url": "https://github.com/wasamasa", + "lloc": 0 + }, + "erlang": { + "dir": "erlang", + "name": "Erlang", + "syntax": "OTHER", + "type_check": "Dynamic", + "modes": [], + "perf1": 53, + "perf2": 175, + "perf3": 175, + "star_count": 2942, + "rank": 31, + "sloc": 2215, + "files": 18, + "author_name": "Nathan Fiedler", + "author_url": "https://github.com/nlfiedler", + "lloc": 0 + }, + "es6": { + "dir": "es6", + "name": "ES6", + "syntax": "C", + "type_check": "Dynamic", + "modes": [], + "perf1": 3, + "perf2": 21, + "perf3": 12079, + "star_count": 668062, + "rank": 2, + "sloc": 1230, + "files": 20, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "fsharp": { + "dir": "fsharp", + "name": "F#", + "syntax": "ML", + "type_check": "Static", + "modes": [], + "perf1": 35, + "perf2": 29, + "perf3": 20634, + "star_count": 950, + "rank": 39, + "sloc": 3033, + "files": 22, + "author_name": "Peter Stephens", + "author_url": "https://github.com/pstephens", + "lloc": 404 + }, + "factor": { + "dir": "factor", + "name": "Factor", + "syntax": "Stack", + "type_check": "Dynamic", + "modes": [], + "perf1": 2, + "perf2": 1, + "perf3": 27766, + "star_count": null, + "rank": 68, + "sloc": 1305, + "files": 33, + "author_name": "Jordan Lewis", + "author_url": "https://github.com/jordanlewis", + "lloc": 0 + }, + "fantom": { + "dir": "fantom", + "name": "Fantom", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 14, + "perf2": 31, + "perf3": 40400, + "star_count": null, + "rank": 68, + "sloc": 1768, + "files": 31, + "author_name": "Dov Murik", + "author_url": "https://github.com/dubek", + "lloc": 0 + }, + "forth": { + "dir": "forth", + "name": "Forth", + "syntax": "Stack", + "type_check": "OTHER", + "modes": [], + "perf1": 4, + "perf2": 14, + "perf3": 2448, + "star_count": null, + "rank": 68, + "sloc": 3351, + "files": 21, + "author_name": "Chris Houser", + "author_url": "https://github.com/chouser", + "lloc": 0 + }, + "guile": { + "dir": "guile", + "name": "GNU Guile", + "syntax": "Lisp", + "type_check": "Dynamic", + "modes": [], + "perf1": 3, + "perf2": 9, + "perf3": 3962, + "star_count": null, + "rank": 68, + "sloc": 1547, + "files": 20, + "author_name": "Mu Lei", + "author_url": "https://github.com/NalaGinrut", + "lloc": 0 + }, + "gnu-smalltalk": { + "dir": "gnu-smalltalk", + "name": "GNU Smalltalk", + "syntax": "OTHER", + "type_check": "Dynamic", + "modes": [], + "perf1": 14, + "perf2": 45, + "perf3": 787, + "star_count": 152, + "rank": 55, + "sloc": 2444, + "files": 21, + "author_name": "Vasilij Schneidermann", + "author_url": "https://github.com/wasamasa", + "lloc": 0 + }, + "go": { + "dir": "go", + "name": "Go", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 1, + "perf2": 3, + "perf3": 10334, + "star_count": 215511, + "rank": 5, + "sloc": 3451, + "files": 20, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 1792 + }, + "groovy": { + "dir": "groovy", + "name": "Groovy", + "syntax": "C", + "type_check": "Dynamic", + "modes": [], + "perf1": 477, + "perf2": 746, + "perf3": 324, + "star_count": 4302, + "rank": 29, + "sloc": 1598, + "files": 19, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "haskell": { + "dir": "haskell", + "name": "Haskell", + "syntax": "ML", + "type_check": "Static", + "modes": [], + "perf1": 3, + "perf2": 8, + "perf3": 4338, + "star_count": 6312, + "rank": 23, + "sloc": 2011, + "files": 18, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "haxe": { + "dir": "haxe", + "name": "Haxe", + "syntax": "C", + "type_check": "Static", + "modes": [ + "neko", + "python", + "cpp", + "js" + ], + "perf1": 5, + "perf2": 26, + "perf3": 15377, + "star_count": 580, + "rank": 46, + "sloc": 2270, + "files": 23, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 1045 + }, + "hy": { + "dir": "hy", + "name": "Hy", + "syntax": "Lisp", + "type_check": "Dynamic", + "modes": [], + "perf1": 17, + "perf2": 67, + "perf3": 585, + "star_count": null, + "rank": 68, + "sloc": 1215, + "files": 18, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "io": { + "dir": "io", + "name": "Io", + "syntax": "OTHER", + "type_check": "Dynamic", + "modes": [], + "perf1": 357, + "perf2": 1333, + "perf3": 29, + "star_count": null, + "rank": 68, + "sloc": 1262, + "files": 18, + "author_name": "Dov Murik", + "author_url": "https://github.com/dubek", + "lloc": 0 + }, + "java": { + "dir": "java", + "name": "Java", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 7, + "perf2": 24, + "perf3": 57275, + "star_count": 255160, + "rank": 4, + "sloc": 3061, + "files": 20, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 1537 + }, + "js": { + "dir": "js", + "name": "JavaScript", + "syntax": "C", + "type_check": "Dynamic", + "modes": [], + "perf1": 3, + "perf2": 22, + "perf3": 14550, + "star_count": 668062, + "rank": 1, + "sloc": 2779, + "files": 33, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "julia": { + "dir": "julia", + "name": "Julia", + "syntax": "Algol", + "type_check": "Dynamic", + "modes": [], + "perf1": 253, + "perf2": 32, + "perf3": 2975, + "star_count": 948, + "rank": 40, + "sloc": 1413, + "files": 19, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "kotlin": { + "dir": "kotlin", + "name": "Kotlin", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 19, + "perf2": 75, + "perf3": 33381, + "star_count": 20958, + "rank": 17, + "sloc": 1571, + "files": 19, + "author_name": "Javier Fernandez-Ivern", + "author_url": "https://github.com/ivern", + "lloc": 0 + }, + "livescript": { + "dir": "livescript", + "name": "LiveScript", + "syntax": "ML", + "type_check": "Dynamic", + "modes": [], + "perf1": 6, + "perf2": 26, + "perf3": 7570, + "star_count": 116, + "rank": 57, + "sloc": 2128, + "files": 19, + "author_name": "Jos van Bakel", + "author_url": "https://github.com/c0deaddict", + "lloc": 0 + }, + "logo": { + "dir": "logo", + "name": "Logo", + "syntax": "OTHER", + "type_check": "Dynamic", + "modes": [], + "perf1": 19182, + "perf2": 53948, + "perf3": 0.01, + "star_count": null, + "rank": 68, + "sloc": 2139, + "files": 20, + "author_name": "Dov Murik", + "author_url": "https://github.com/dubek", + "lloc": 0 + }, + "lua": { + "dir": "lua", + "name": "Lua", + "syntax": "Algol", + "type_check": "Dynamic", + "modes": [], + "perf1": 10, + "perf2": 44, + "perf3": 955, + "star_count": 8769, + "rank": 21, + "sloc": 1909, + "files": 21, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "make": { + "dir": "make", + "name": "GNU Make", + "syntax": "OTHER", + "type_check": "OTHER", + "modes": [], + "perf1": 5050, + "perf2": 31077, + "perf3": 1, + "star_count": 2034, + "rank": 35, + "sloc": 1821, + "files": 22, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "mal": { + "dir": "mal", + "name": "mal itself", + "syntax": "Lisp", + "type_check": "Dynamic", + "modes": [], + "perf1": 277, + "perf2": 1346, + "perf3": 40, + "star_count": null, + "rank": 68, + "sloc": 890, + "files": 13, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "matlab": { + "dir": "matlab", + "name": "MATLAB", + "syntax": "Algol", + "type_check": "Dynamic", + "modes": [], + "perf1": 1709, + "perf2": 6295, + "perf3": 5, + "star_count": 2867, + "rank": 32, + "sloc": 2204, + "files": 27, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "miniMAL": { + "dir": "miniMAL", + "name": "miniMAL", + "syntax": "JSON", + "type_check": "Dynamic", + "modes": [], + "perf1": 1340, + "perf2": 5383, + "perf3": 7, + "star_count": null, + "rank": 68, + "sloc": 1691, + "files": 20, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "nasm": { + "dir": "nasm", + "name": "NASM", + "syntax": "OTHER", + "type_check": "OTHER", + "modes": [], + "perf1": 0.9, + "perf2": 2, + "perf3": 24147, + "star_count": 1195, + "rank": 37, + "sloc": 14483, + "files": 19, + "author_name": "Ben Dudson", + "author_url": "https://github.com/bendudson", + "lloc": 0 + }, + "nim": { + "dir": "nim", + "name": "Nim", + "syntax": "Python", + "type_check": "Static", + "modes": [], + "perf1": 1, + "perf2": 2, + "perf3": 30900, + "star_count": 529, + "rank": 48, + "sloc": 1423, + "files": 17, + "author_name": "Dennis Felsing", + "author_url": "https://github.com/def-", + "lloc": 0 + }, + "objpascal": { + "dir": "objpascal", + "name": "Object Pascal", + "syntax": "Algol", + "type_check": "Static", + "modes": [], + "perf1": 4, + "perf2": 12, + "perf3": 2830, + "star_count": 1324, + "rank": 36, + "sloc": 6179, + "files": 20, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 3971 + }, + "objc": { + "dir": "objc", + "name": "Objective C", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 6, + "perf2": 28, + "perf3": 1599, + "star_count": 48248, + "rank": 16, + "sloc": 2363, + "files": 26, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 1059 + }, + "ocaml": { + "dir": "ocaml", + "name": "OCaml", + "syntax": "ML", + "type_check": "Static", + "modes": [], + "perf1": 1, + "perf2": 2, + "perf3": 23267, + "star_count": 3646, + "rank": 30, + "sloc": 1269, + "files": 17, + "author_name": "Chris Houser", + "author_url": "https://github.com/chouser", + "lloc": 0 + }, + "perl": { + "dir": "perl", + "name": "Perl", + "syntax": "C", + "type_check": "Dynamic", + "modes": [], + "perf1": 13, + "perf2": 50, + "perf3": 790, + "star_count": 4679, + "rank": 28, + "sloc": 2265, + "files": 22, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 1107 + }, + "perl6": { + "dir": "perl6", + "name": "Perl 6", + "syntax": "C", + "type_check": "Dynamic", + "modes": [], + "perf1": 441, + "perf2": 1182, + "perf3": 23, + "star_count": 137, + "rank": 56, + "sloc": 1238, + "files": 18, + "author_name": "Hinrik Örn Sigurðsson", + "author_url": "https://github.com/hinrik", + "lloc": 484 + }, + "php": { + "dir": "php", + "name": "PHP", + "syntax": "C", + "type_check": "Dynamic", + "modes": [], + "perf1": 8, + "perf2": 28, + "perf3": 1268, + "star_count": 135579, + "rank": 7, + "sloc": 1998, + "files": 21, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 1074 + }, + "picolisp": { + "dir": "picolisp", + "name": "Picolisp", + "syntax": "Lisp", + "type_check": "Dynamic", + "modes": [], + "perf1": 2, + "perf2": 7, + "perf3": 6005, + "star_count": null, + "rank": 68, + "sloc": 1366, + "files": 20, + "author_name": "Vasilij Schneidermann", + "author_url": "https://github.com/wasamasa", + "lloc": 0 + }, + "plpgsql": { + "dir": "plpgsql", + "name": "PL/pgSQL", + "syntax": "Algol", + "type_check": "Static", + "modes": [], + "perf1": 478, + "perf2": 3830, + "perf3": 20, + "star_count": 897, + "rank": 42, + "sloc": 3679, + "files": 21, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "plsql": { + "dir": "plsql", + "name": "PL/SQL", + "syntax": "Algol", + "type_check": "Static", + "modes": [], + "perf1": 19182, + "perf2": 1921, + "perf3": 0.01, + "star_count": 199, + "rank": 53, + "sloc": 4180, + "files": 21, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "powershell": { + "dir": "powershell", + "name": "PowerShell", + "syntax": "OTHER", + "type_check": "Dynamic", + "modes": [], + "perf1": 1921, + "perf2": 7832, + "perf3": 5, + "star_count": 5795, + "rank": 25, + "sloc": 1147, + "files": 12, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "ps": { + "dir": "ps", + "name": "PostScript", + "syntax": "Stack", + "type_check": "Dynamic", + "modes": [], + "perf1": 38, + "perf2": 274, + "perf3": 198, + "star_count": null, + "rank": 68, + "sloc": 2482, + "files": 20, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "python": { + "dir": "python", + "name": "Python", + "syntax": "Python", + "type_check": "Dynamic", + "modes": [ + "python2", + "python3" + ], + "perf1": 7, + "perf2": 29, + "perf3": 1371, + "star_count": 336045, + "rank": 3, + "sloc": 1358, + "files": 20, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 1344 + }, + "rpython": { + "dir": "rpython", + "name": "RPython", + "syntax": "Python", + "type_check": "Static", + "modes": [], + "perf1": 0.9, + "perf2": 1, + "perf3": 69848, + "star_count": null, + "rank": 68, + "sloc": 2102, + "files": 19, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 2096 + }, + "r": { + "dir": "r", + "name": "R", + "syntax": "C", + "type_check": "Dynamic", + "modes": [], + "perf1": 78, + "perf2": 295, + "perf3": 117, + "star_count": 2644, + "rank": 33, + "sloc": 1625, + "files": 19, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "racket": { + "dir": "racket", + "name": "Racket", + "syntax": "Lisp", + "type_check": "Dynamic", + "modes": [], + "perf1": 2, + "perf2": 7, + "perf3": 4865, + "star_count": 281, + "rank": 51, + "sloc": 1193, + "files": 18, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "rexx": { + "dir": "rexx", + "name": "Rexx", + "syntax": "OTHER", + "type_check": "Dynamic", + "modes": [], + "perf1": 160, + "perf2": 660, + "perf3": 61, + "star_count": null, + "rank": 68, + "sloc": 2747, + "files": 19, + "author_name": "Dov Murik", + "author_url": "https://github.com/dubek", + "lloc": 0 + }, + "ruby": { + "dir": "ruby", + "name": "Ruby", + "syntax": "OTHER", + "type_check": "Dynamic", + "modes": [], + "perf1": 2, + "perf2": 8, + "perf3": 4163, + "star_count": 54043, + "rank": 15, + "sloc": 1291, + "files": 20, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "rust": { + "dir": "rust", + "name": "Rust", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 0.9, + "perf2": 1, + "perf3": 16720, + "star_count": 19178, + "rank": 18, + "sloc": 2862, + "files": 18, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 714 + }, + "scala": { + "dir": "scala", + "name": "Scala", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 27, + "perf2": 66, + "perf3": 25785, + "star_count": 13634, + "rank": 19, + "sloc": 1849, + "files": 18, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "scheme": { + "dir": "scheme", + "name": "Scheme (R7RS)", + "syntax": "Lisp", + "type_check": "Dynamic", + "modes": [ + "chibi", + "kawa", + "gauche", + "chicken", + "sagittarius", + "cyclone", + "foment" + ], + "perf1": 6, + "perf2": 19, + "perf3": 2231, + "star_count": 718, + "rank": 44, + "sloc": 1269, + "files": 13, + "author_name": "Vasilij Schneidermann", + "author_url": "https://github.com/wasamasa", + "lloc": 0 + }, + "skew": { + "dir": "skew", + "name": "Skew", + "syntax": "OTHER", + "type_check": "Static", + "modes": [], + "perf1": 6, + "perf2": 11, + "perf3": 3608, + "star_count": null, + "rank": 68, + "sloc": 1638, + "files": 19, + "author_name": "Dov Murik", + "author_url": "https://github.com/dubek", + "lloc": 0 + }, + "swift": { + "dir": "swift", + "name": "Swift 2", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 2, + "perf2": 6, + "perf3": 5322, + "star_count": 66012, + "rank": 11, + "sloc": 2963, + "files": 14, + "author_name": "Keith Rollin", + "author_url": "https://github.com/keith-rollin", + "lloc": 0 + }, + "swift3": { + "dir": "swift3", + "name": "Swift 3", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 8, + "perf2": 27, + "perf3": 1234, + "star_count": 66012, + "rank": 12, + "sloc": 2362, + "files": 17, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "swift4": { + "dir": "swift4", + "name": "Swift 4", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 5, + "perf2": 16, + "perf3": 2065, + "star_count": 66012, + "rank": 13, + "sloc": 1767, + "files": 17, + "author_name": "陆é¥", + "author_url": "https://github.com/LispLY", + "lloc": 0 + }, + "tcl": { + "dir": "tcl", + "name": "Tcl", + "syntax": "OTHER", + "type_check": "Dynamic", + "modes": [], + "perf1": 24, + "perf2": 103, + "perf3": 373, + "star_count": 171, + "rank": 54, + "sloc": 2468, + "files": 20, + "author_name": "Dov Murik", + "author_url": "https://github.com/dubek", + "lloc": 0 + }, + "ts": { + "dir": "ts", + "name": "TypeScript", + "syntax": "C", + "type_check": "Static", + "modes": [], + "perf1": 4, + "perf2": 14, + "perf3": 26534, + "star_count": 93555, + "rank": 8, + "sloc": 2836, + "files": 20, + "author_name": "Masahiro Wakame", + "author_url": "https://github.com/vvakame", + "lloc": 0 + }, + "vhdl": { + "dir": "vhdl", + "name": "VHDL", + "syntax": "Algol", + "type_check": "Static", + "modes": [], + "perf1": 3, + "perf2": 9, + "perf3": 3342, + "star_count": 217, + "rank": 52, + "sloc": 4258, + "files": 19, + "author_name": "Dov Murik", + "author_url": "https://github.com/dubek", + "lloc": 0 + }, + "vimscript": { + "dir": "vimscript", + "name": "Vimscript", + "syntax": "Algol", + "type_check": "Dynamic", + "modes": [], + "perf1": 219, + "perf2": 1055, + "perf3": 43, + "star_count": 10096, + "rank": 20, + "sloc": 2147, + "files": 22, + "author_name": "Dov Murik", + "author_url": "https://github.com/dubek", + "lloc": 12 + }, + "vb": { + "dir": "vb", + "name": "Visual Basic.NET", + "syntax": "Algol", + "type_check": "Static", + "modes": [], + "perf1": 14, + "perf2": 14, + "perf3": 10121, + "star_count": 578, + "rank": 47, + "sloc": 3839, + "files": 19, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 394 + }, + "wasm": { + "dir": "wasm", + "name": "WebAssembly", + "syntax": "Lisp", + "type_check": "Static", + "modes": [ + "wace_libc", + "node", + "warpy" + ], + "perf1": 2, + "perf2": 10, + "perf3": 3742, + "star_count": 365, + "rank": 49, + "sloc": 5270, + "files": 26, + "author_name": "Joel Martin", + "author_url": "https://github.com/kanaka", + "lloc": 0 + }, + "yorick": { + "dir": "yorick", + "name": "Yorick", + "syntax": "C", + "type_check": "Dynamic", + "modes": [], + "perf1": 67, + "perf2": 288, + "perf3": 147, + "star_count": null, + "rank": 68, + "sloc": 2398, + "files": 19, + "author_name": "Dov Murik", + "author_url": "https://github.com/dubek", + "lloc": 210 + } +} \ No newline at end of file diff --git a/docs/graph/base_data.yaml b/docs/graph/base_data.yaml new file mode 100644 index 0000000000..161e47feab --- /dev/null +++ b/docs/graph/base_data.yaml @@ -0,0 +1,79 @@ +headers: + - [dir , name , syntax , type_check , modes] + +languages: + - [ada , Ada , Algol , Static , []] + - [awk , GNU Awk , C , Dynamic , []] + - [bash , Bash 4 , OTHER , Dynamic , []] + - [basic , BASIC , OTHER , Dynamic , [cbm, qbasic]] + - [c , C , C , Static , []] + - [cpp , C++ , C , Static , []] + - [cs , C# , C , Static , []] + - [chuck , ChucK , C , Static , []] + - [clojure , Clojure , Lisp , Dynamic , [clj, cljs]] + - [coffee , CoffeeScript , OTHER , Dynamic , []] + - [common-lisp , Common Lisp , Lisp , Dynamic , []] + - [crystal , Crystal , OTHER , Static , []] + - [d , D , C , Static , []] + - [dart , Dart , C , Static , []] + - [elixir , Elixir , OTHER , Dynamic , []] + - [elm , Elm , ML , Static , []] + - [elisp , Emacs Lisp , Lisp , Dynamic , []] + - [erlang , Erlang , OTHER , Dynamic , []] + - [es6 , ES6 , C , Dynamic , []] + - [fsharp , F# , ML , Static , []] + - [factor , Factor , Stack , Dynamic , []] + - [fantom , Fantom , C , Static , []] + - [forth , Forth , Stack , OTHER , []] + - [guile , GNU Guile , Lisp , Dynamic , []] + - [gnu-smalltalk , GNU Smalltalk , OTHER , Dynamic , []] + - [go , Go , C , Static , []] + - [groovy , Groovy , C , Dynamic , []] + - [haskell , Haskell , ML , Static , []] + - [haxe , Haxe , C , Static , [neko,python,cpp,js]] + - [hy , Hy , Lisp , Dynamic , []] + - [io , Io , OTHER , Dynamic , []] + - [java , Java , C , Static , []] + - [js , JavaScript , C , Dynamic , []] + - [julia , Julia , Algol , Dynamic , []] + - [kotlin , Kotlin , C , Static , []] + - [livescript , LiveScript , ML , Dynamic , []] + - [logo , Logo , OTHER , Dynamic , []] + - [lua , Lua , Algol , Dynamic , []] + - [make , GNU Make , OTHER , OTHER , []] + - [mal , mal itself , Lisp , Dynamic , []] + - [matlab , MATLAB , Algol , Dynamic , []] + - [miniMAL , miniMAL , JSON , Dynamic , []] + - [nasm , NASM , OTHER , OTHER , []] + - [nim , Nim , Python , Static , []] + - [objpascal , Object Pascal , Algol , Static , []] + - [objc , Objective C , C , Static , []] + - [ocaml , OCaml , ML , Static , []] + - [perl , Perl , C , Dynamic , []] + - [perl6 , Perl 6 , C , Dynamic , []] + - [php , PHP , C , Dynamic , []] + - [picolisp , Picolisp , Lisp , Dynamic , []] + - [plpgsql , PL/pgSQL , Algol , Static , []] + - [plsql , PL/SQL , Algol , Static , []] + - [powershell , PowerShell , OTHER , Dynamic , []] + - [ps , PostScript , Stack , Dynamic , []] + - [python , Python , Python , Dynamic , [python2,python3]] + - [rpython , RPython , Python , Static , []] + - [r , R , C , Dynamic , []] + - [racket , Racket , Lisp , Dynamic , []] + - [rexx , Rexx , OTHER , Dynamic , []] + - [ruby , Ruby , OTHER , Dynamic , []] + - [rust , Rust , C , Static , []] + - [scala , Scala , C , Static , []] + - [scheme , Scheme (R7RS) , Lisp , Dynamic , [chibi,kawa,gauche,chicken,sagittarius,cyclone,foment]] + - [skew , Skew , OTHER , Static , []] + - [swift , Swift 2 , C , Static , []] + - [swift3 , Swift 3 , C , Static , []] + - [swift4 , Swift 4 , C , Static , []] + - [tcl , Tcl , OTHER , Dynamic , []] + - [ts , TypeScript , C , Static , []] + - [vhdl , VHDL , Algol , Static , []] + - [vimscript , Vimscript , Algol , Dynamic , []] + - [vb , Visual Basic.NET , Algol , Static , []] + - [wasm , WebAssembly , Lisp , Static , [wace_libc,node,warpy]] + - [yorick , Yorick , C , Dynamic , []] diff --git a/docs/graph/collect_data.js b/docs/graph/collect_data.js new file mode 100755 index 0000000000..efca8a07f6 --- /dev/null +++ b/docs/graph/collect_data.js @@ -0,0 +1,222 @@ +#!/usr/bin/env python + +const { promisify } = require('util') +const readFile = promisify(require('fs').readFile) +const writeFile = promisify(require('fs').writeFile) +const readdir = promisify(require('fs').readdir) +const path = require('path') +const yaml = require('js-yaml') +const request = require('request-promise-native') +const exec = promisify(require('child_process').exec) + +const VERBOSE = process.env['VERBOSE'] || false +const BASE_PATH = process.env['BASE_PATH'] || 'base_data.yaml' +const README_PATH = process.env['README_PATH'] || '../../README.md' +// GitHut Pushes +//const GITHUT_URL = 'https://raw.githubusercontent.com/madnight/githut/gh-pages/gh-push-event_eb2696.json' +// GitHut Stars +const GITHUT_URL = process.env['GITHUT_URL'] || 'https://raw.githubusercontent.com/madnight/githut/gh-pages/gh-star-event_e61175.json' +const MAL_PATH = process.env['MAL_PATH'] || '../../' + + +const githutToNames = { + 'Awk': ['GNU Awk'], + 'Shell': ['Bash 4'], + 'JavaScript': ['JavaScript', 'ES6'], + 'Makefile': ['GNU Make'], + 'Matlab': ['MATLAB'], + 'Assembly': ['NASM'], + 'Pascal': ['Object Pascal'], + 'Objective-C': ['Objective C'], + 'PLpgSQL': ['PL/pgSQL'], + 'PLSQL': ['PL/SQL'], + 'Scheme': ['Scheme (R7RS)'], + 'Smalltalk': ['GNU Smalltalk'], + 'Swift': ['Swift 2', 'Swift 3', 'Swift 4'], + 'Vim script': ['Vimscript'], + 'Visual Basic': ['Visual Basic.NET'], +} + +function vlog(...args) { + if (VERBOSE) { + console.log(...args) + } +} + +function die(code, ...args) { + console.error(...args) + process.exit(code) +} + +async function main() { + const logsPath = path.resolve(process.argv[2]) + const outPath = path.resolve(process.argv[3]) + + vlog(`Loading base data yaml from '${BASE_PATH}`) + const baseYaml = yaml.safeLoad(await readFile(BASE_PATH, 'utf8')) + vlog(`Loading README text from '${README_PATH}`) + const readmeLines = (await readFile(README_PATH, 'utf8')).split(/\n/) + vlog(`Downloading GitHut HTML from '${GITHUT_URL}`) + const githutText = (await request(GITHUT_URL)) + vlog(`Loading log data from '${logsPath}'`) + const logFiles = (await readdir(logsPath)) + .map(x => parseInt(x)) + .sort((a, b) => a - b) + let logData = [] + for (const f of logFiles) { + if (!(/^[0-9]+$/.exec(f))) { continue } + const path = logsPath + "/" + f + logData.push([await readFile(path, 'utf8'), path]) + } + + let dirs = [] + let names = [] + let dataList = [] + let dataByDir = {} + let dataByName = {} + + vlog(`Processing base data`) + for (let d of baseYaml['languages']) { + let data = {'dir': d[0], + 'name': d[1], + 'syntax': d[2], + 'type_check': d[3], + 'modes': d[4], + 'perf1': null, + 'perf2': null, + 'perf3': 0, + 'star_count': null, + 'rank': null, + 'sloc': 0, + 'files': 0} + dirs.push(d[0]) + names.push(d[1]) + dataList.push(data) + dataByDir[d[0]] = data + dataByName[d[1]] = data + } + + vlog(`Processing README implementations table`) + const readme_re = /^\| \[([^\[]*)\].* \| \[([^|]*)\]\(([^|]*)\) *\| *$/ + for (let row of readmeLines.filter(l => /^\| [\[]/.exec(l))) { + t = readme_re.exec(row) + if (t) { + if (t[1] in dataByName) { + let data = dataByName[t[1]] + data.author_name = t[2] + data.author_url = t[3] + } else { + die(1, `README language '${t[1]}' not found in base data`) + } + } else { + die(1, `No match for README table row: ${row}`) + } + } + + vlog(`Processing GitHut data`) + const gdata = githutText.split(/\n/) + .map(JSON.parse) + .filter(d => d.year === "2018" && d.quarter === '4') + .map(d => (d.count = parseInt(d.count), d)) + .sort((a,b) => (a.count > b.count) ? -1 : a.count < b.count ? 1 : 0) + let curRank = 1 + for (let gitem of gdata) { + const names = githutToNames[gitem.name] || [gitem.name] + for (let name of names) { + if (name in dataByName) { + dataByName[name].star_count = gitem.count + dataByName[name].rank = curRank + vlog(` ${dataByName[name].dir} stars: ${gitem.count}, rank: ${curRank}`) + curRank += 1 + } else { + vlog(` ignoring GitHut language ${name}`) + } + } + } + + vlog(`Processing log file data`) + const perf_run_re = /Running:.*\.\.\/tests\/(perf[0-9])\.mal/ + const perf_num_re = /Elapsed time: ([0-9.]+) msecs|iters over 10 seconds: ([0-9]+)/ + for (let [log, file] of logData) { + const dir_match = (/export IMPL=(\S+)/i).exec(log) + if (!dir_match) { die(1, `no IMPL found in ${file}`) } + const dir = dir_match[1] + const data = dataByDir[dir] +// if (data.perf1 !== null) { +// vlog(` ${dir} already has perf data, ignoring ${file}`) +// continue +// } + const perfs = {} + const logLines = log.split(/\n/) + for (let i = 0; i < logLines.length; i++) { + const match_run = perf_run_re.exec(logLines[i]) + if (match_run) { + // Find the result line + let match_num = null + do { + i += 1 + match_num = perf_num_re.exec(logLines[i]) + if (match_num) { + num = parseFloat(match_num[1] || match_num[2], 10) + perfs[match_run[1]] = num + } + } while ((!match_num) && i < logLines.length) + } + } + if ((perfs.perf3 > data.perf3) || !data.perf3) { + data.perf1 = perfs.perf1 + data.perf2 = perfs.perf2 + data.perf3 = perfs.perf3 + vlog(` ${dir}: ${perfs.perf1}, ${perfs.perf2}, ${perfs.perf3}`) + } else { + vlog(` ${dir}: ${perfs.perf1}, ${perfs.perf2}, ${perfs.perf3} (perf3 is worse, ignoring ${file})`) + } + } + + vlog(`Gathering LOC stats`) + const stat_re = /SLOC=([0-9]+).*LLOC=([0-9]+).*in ([0-9]+) files/ + process.chdir(MAL_PATH) + for (let data of dataList) { + vlog(` gathering stats information for ${data.dir}`) + const { stdout, stderr } = await exec(`make "stats^${data.dir}"`) + const match = stat_re.exec(stdout.split(/\n/)[1]) + data.sloc = parseInt(match[1], 10) + data.lloc = parseInt(match[2], 10) + data.files = parseInt(match[3], 10) + } + + vlog(`Filling in missing attributes`) + // leave a gap between actual ranked implementations and those + // with no rankings + maxRank = curRank + 10 + maxPerf1 = dataList.reduce((a, d) => d.perf1 > a ? d.perf1 : a, 0) + maxPerf2 = dataList.reduce((a, d) => d.perf2 > a ? d.perf1 : a, 0) + for (let d of dataList) { + if (d.rank === null) { + vlog(` setting rank to ${maxRank} for ${d.dir}`) + d.rank = maxRank + } + if (d.perf1 === null) { + vlog(` setting perf1 to ${maxPerf1} for ${d.dir}`) + d.perf1 = maxPerf1 + } + if (d.perf2 === null) { + vlog(` setting perf2 to ${maxPerf2} for ${d.dir}`) + d.perf2 = maxPerf2 + } + } + + vlog(`Adjusting perf numbers to avoid 0`) + for (let d of dataList) { + if (d.perf1 === 0) { d.perf1 = 0.9 } + if (d.perf2 === 0) { d.perf2 = 0.9 } + if (d.perf3 === 0) { d.perf3 = 0.01 } + } + + vlog(`Writing full lanaguage data to ${outPath}`) + await writeFile(outPath, JSON.stringify(dataByDir, null, 2)) + + process.exit(0) +} + +main() diff --git a/docs/graph/graph_languages.js b/docs/graph/graph_languages.js new file mode 100644 index 0000000000..3dd3ff67ca --- /dev/null +++ b/docs/graph/graph_languages.js @@ -0,0 +1,214 @@ +const malColors = [ + "#1f77b4","#bf7f0e","#4cb00c","#b62728","#9467bd","#bc664b","#b377c2","#0fbf5f","#bcbd22","#17beef", + "#1f6784","#8f7f0e","#4c800c","#862728","#54678d","#8c564b","#8377c2","#0f8f5f","#8c8d22","#178eef", + "#1f97d4","#ff7f0e","#4cf00c","#f62728","#c467fd","#fc764b","#f377c2","#0fff5f","#fcfd22","#17feef", +] + +const axisSet = new Set(['perf1', 'perf2', 'perf3', 'rank', 'sloc', 'files']) +const colorSet = new Set(['type_check', 'syntax', 'author_name']) +const perfSet = new Set(['perf1', 'perf2', 'perf3']) +const invertSet = new Set(['rank', 'perf1', 'perf2']) +const perfLogSet = new Set(['perf1', 'perf2', 'sloc', 'files']) + +let cfg = { + ckey: 'syntax', + xkey: 'rank', + ykey: 'perf3', + skey: 'sloc', + + xlog: false, + ylog: true, +} + +let allData +let graphData = [] +let chart + +// +// Util functions +// + +function malExtent(data, key) { + let extent = d3.extent(Object.values(data), d => d[key]) + // pad the bottom rank so it's not on the opposite axis line + if (key === 'rank') { + extent[0] = 0.99 // Setting this to 1 breaks log scale render + extent[extent.length-1] += 1 + } + // Replace 0's with 0.01 to prevent divide by zero errors + if (extent[0] === 0) { extent[0] = 0.0001 } + if (extent[extent.length-1] === 0) { extent[extent.length-1] = 0.0001 } + // For rank, perf1 and perf2 reverse the Axis range + if (invertSet.has(key)) { + extent.reverse() + } + return extent +} + +function malScale(log) { + return log ? d3.scale.log() : d3.scale.linear() +} + +function malTickValues(key, log) { + if (log && perfSet.has(key)) { + return [1, 10, 100, 1000, 10000, 100000] + } else { + return null + } +} + +function malCircleSize(key, min, max, val) { + let size = (val || 0.01) - (min - 0.01) + if (invertSet.has(key)) { + size = (max + 0.01) - size + } +// if (perfLogSet.has(key)) { +// size = Math.log(size) +// } +// console.log(key, max, val, size) + return size +} + + +// +// UI / Axis Data / query parameters +// + +// Parser query string and update cfg map with valid config options +(function parseQuery(q) { + const pairs = (q[0] === '?' ? q.substr(1) : q).split('&') + for (const [p1, p2] of pairs.map(p => p.split('='))) { + let k = decodeURIComponent(p1).toLowerCase() + let v = p2 ? decodeURIComponent(p2) : true + if (v in {"true":1,"1":1,"yes":1}) { v = true } + if (v in {"false":1,"0":1,"no":1}) { v = false } + if (k in cfg && (axisSet.has(v) || colorSet.has(v))) { + cfg[k] = v + } + if ((new Set(['xlog', 'ylog'])).has(k) && typeof v === 'boolean') { + cfg[k] = v + } + } +})(location.search) + +// Set the checked elements based on the the cfg +for (const key of Object.keys(cfg)) { + for (const node of document.getElementsByName(key)) { + let val = node.value + if (val in {"true":1,"1":1,"yes":1}) { val = true } + if (val in {"false":1,"0":1,"no":1}) { val = false } + if (val === cfg[key]) { + node.checked = true + } else { + node.checked = false + } + } +} + +// Add onchange to all selector radio buttons/check boxes +for (let input of document.getElementsByClassName('selects')) { + input.addEventListener('change', function(evt) { + if (new Set(['xlog', 'ylog']).has(evt.target.name)) { + cfg[evt.target.name] = evt.target.checked + } else { + cfg[evt.target.name] = evt.target.value + } + const query = Object.entries(cfg).map(([k,v]) => k + "=" + v).join('&') + history.pushState(null, '', '?' + query) + updateGraphData() + }) +} + + +// +// Graph rendering / updating +// + +function updateGraphData() { + let xMax = 0 + let yMax = 0 + let sMin = null + let sMax = null + const colorSet = new Set(Object.values(allData).map(d => d[cfg.ckey])) + const colorList = Array.from(colorSet.values()) + // empty the graphData without recreating it + while (graphData.length > 0) { graphData.pop() } + graphData.push(...colorList.map(t => ({key: t, values: []}))) + for (var dir of Object.keys(allData)) { + const impl = allData[dir] + if (impl[cfg.xkey] > xMax) { xMax = impl[cfg.xkey] } + if (impl[cfg.ykey] > yMax) { yMax = impl[cfg.ykey] } + if (sMin === null) { sMin = impl[cfg.skey] } + if (impl[cfg.skey] < sMin) { sMin = impl[cfg.skey] } + if (impl[cfg.skey] > sMax) { sMax = impl[cfg.skey] } + } + for (var dir of Object.keys(allData)) { + const impl = allData[dir] + // Invert size for inverted data + graphData[colorList.indexOf(impl[cfg.ckey])].values.push({ + x: impl[cfg.xkey] || 0, + y: impl[cfg.ykey] || 0, + size: malCircleSize(cfg.skey, sMin, sMax, impl[cfg.skey]), + shape: 'circle', + label: impl.name, + impl: impl, + }) + } + + // Update the axes domain, scale and tick values + chart.xDomain(malExtent(allData, cfg.xkey)) + chart.yDomain(malExtent(allData, cfg.ykey)) + chart.xScale(malScale(cfg.xlog)) + chart.yScale(malScale(cfg.ylog)) + chart.xAxis.tickValues(malTickValues(cfg.xkey, cfg.xlog)) + chart.yAxis.tickValues(malTickValues(cfg.ykey, cfg.ylog)) + + // Update the graph + d3.select('#mal svg') + .data([graphData]) + .transition().duration(350).ease('linear') + .call(chart) + + chart.update() + + nv.utils.windowResize(chart.update) +} + +nv.addGraph(function() { + chart = nv.models.scatterChart() + .showDistX(true) + .showDistY(true) + .showLabels(true) + .duration(300) + .color(malColors) + chart.dispatch.on('renderEnd', function() { + console.log('render complete') + }) + chart.dispatch.on('stateChange', function(e) { + nv.log('New State:', JSON.stringify(e)) + }) + chart.tooltip.contentGenerator(function(obj) { + const i = obj.point.impl + return '

' + i.name + '

' + + '

' + + 'Syntax Style: ' + i.syntax + '
' + + 'Type Discipline: ' + i.type_check + '
' + + 'Github Stars: ' + (i.star_count || 'unknown') + '
' + + 'GitHut Relative Rank: ' + i.rank + '
' + + '
' + + 'Perf 1: ' + i.perf1 + ' ms
' + + 'Perf 2: ' + i.perf2 + ' ms
' + + 'Perf 3: ' + i.perf3 + ' iters / 10 sec
' + + 'SLOC: ' + i.sloc + ' lines
' + + //'Author: ' + + //i.author_name + '
' + + 'Author: ' + i.author_name + '
' + + '

' + }) + d3.json("all_data.json", function (error, data) { + allData = data + updateGraphData() + }) + return chart +}) + diff --git a/docs/graph/index.html b/docs/graph/index.html new file mode 100644 index 0000000000..5a5af98a37 --- /dev/null +++ b/docs/graph/index.html @@ -0,0 +1,99 @@ + + + + + + + + + + + +
+

Mal Implementation Stats

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Color data: + Syntax Style + Type Discipline + Author +   
X-Axis data: + Perf 1 + Perf 2 + Perf 3 + Popularity + SLOC size + File count +  Logarithmic
Y-Axis data: + Perf 1 + Perf 2 + Perf 3 + Popularity + SLOC size + File count +  Logarithmic
Circle size: + Perf 1 + Perf 2 + Perf 3 + Popularity + SLOC size + File count +   
+
+ +
+ +
+ + + + + diff --git a/docs/graph/package.json b/docs/graph/package.json new file mode 100644 index 0000000000..eb72dbe413 --- /dev/null +++ b/docs/graph/package.json @@ -0,0 +1,10 @@ +{ + "name": "mal_graph", + "version": "0.0.1", + "description": "Graph Mal Languages", + "dependencies": { + "js-yaml": "3.12.2", + "request": "2.88.0", + "request-promise-native": "1.0.7" + } +} From e1bc7804ad77442a259a151f9dea58d574e78c3a Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 20 Mar 2019 22:46:47 -0500 Subject: [PATCH 0811/2308] Popup caveats/notes. Dynamically generate controls. --- docs/graph/collect_data.js | 4 +- docs/graph/graph_languages.js | 94 +++++++++----- docs/graph/index.html | 236 ++++++++++++++++++++++------------ 3 files changed, 218 insertions(+), 116 deletions(-) diff --git a/docs/graph/collect_data.js b/docs/graph/collect_data.js index efca8a07f6..acc6f8632f 100755 --- a/docs/graph/collect_data.js +++ b/docs/graph/collect_data.js @@ -189,8 +189,8 @@ async function main() { // leave a gap between actual ranked implementations and those // with no rankings maxRank = curRank + 10 - maxPerf1 = dataList.reduce((a, d) => d.perf1 > a ? d.perf1 : a, 0) - maxPerf2 = dataList.reduce((a, d) => d.perf2 > a ? d.perf1 : a, 0) + maxPerf1 = dataList.reduce((a, d) => d.perf1 > a ? d.perf1 : a, 0) + maxPerf2 = dataList.reduce((a, d) => d.perf2 > a ? d.perf1 : a, 0) for (let d of dataList) { if (d.rank === null) { vlog(` setting rank to ${maxRank} for ${d.dir}`) diff --git a/docs/graph/graph_languages.js b/docs/graph/graph_languages.js index 3dd3ff67ca..3b806e76ba 100644 --- a/docs/graph/graph_languages.js +++ b/docs/graph/graph_languages.js @@ -1,11 +1,25 @@ const malColors = [ - "#1f77b4","#bf7f0e","#4cb00c","#b62728","#9467bd","#bc664b","#b377c2","#0fbf5f","#bcbd22","#17beef", - "#1f6784","#8f7f0e","#4c800c","#862728","#54678d","#8c564b","#8377c2","#0f8f5f","#8c8d22","#178eef", - "#1f97d4","#ff7f0e","#4cf00c","#f62728","#c467fd","#fc764b","#f377c2","#0fff5f","#fcfd22","#17feef", + "#1f77b4","#bf7f0e","#4cb00c","#b62728","#9467bd","#bc664b","#b377c2","#0fbf6f","#bcbd22","#17beef", + "#1f6784","#8f7f0e","#4c800c","#862728","#54678d","#8c564b","#8377c2","#0f8f6f","#8c8d22","#178eef", + "#1f97d4","#ff7f0e","#4cf00c","#f62728","#c467fd","#fc764b","#f377c2","#0fff6f","#fcfd22","#17feef", ] -const axisSet = new Set(['perf1', 'perf2', 'perf3', 'rank', 'sloc', 'files']) -const colorSet = new Set(['type_check', 'syntax', 'author_name']) +const axisMap = { + 'perf1': 'Perf 1', + 'perf2': 'Perf 2', + 'perf3': 'Perf 3', + 'rank': 'Popularity', + 'sloc': 'SLOC size', + 'files': 'File count', +} +const colorMap = { + 'syntax': 'Syntax Style', + 'type_check': 'Type Discipline', + 'author_name': 'Author', +} +const axisKeySet = new Set(Object.keys(axisMap)) +const colorKeySet = new Set(['type_check', 'syntax', 'author_name']) + const perfSet = new Set(['perf1', 'perf2', 'perf3']) const invertSet = new Set(['rank', 'perf1', 'perf2']) const perfLogSet = new Set(['perf1', 'perf2', 'sloc', 'files']) @@ -82,7 +96,7 @@ function malCircleSize(key, min, max, val) { let v = p2 ? decodeURIComponent(p2) : true if (v in {"true":1,"1":1,"yes":1}) { v = true } if (v in {"false":1,"0":1,"no":1}) { v = false } - if (k in cfg && (axisSet.has(v) || colorSet.has(v))) { + if (k in cfg && (axisKeySet.has(v) || colorKeySet.has(v))) { cfg[k] = v } if ((new Set(['xlog', 'ylog'])).has(k) && typeof v === 'boolean') { @@ -91,35 +105,42 @@ function malCircleSize(key, min, max, val) { } })(location.search) -// Set the checked elements based on the the cfg -for (const key of Object.keys(cfg)) { - for (const node of document.getElementsByName(key)) { - let val = node.value - if (val in {"true":1,"1":1,"yes":1}) { val = true } - if (val in {"false":1,"0":1,"no":1}) { val = false } - if (val === cfg[key]) { - node.checked = true - } else { - node.checked = false - } +// Generate the control buttons and set the checked elements based on +// the cfg +function ctlChange(evt) { + if (new Set(['xlog', 'ylog']).has(evt.target.name)) { + cfg[evt.target.name] = evt.target.checked + } else { + cfg[evt.target.name] = evt.target.value } + const query = Object.entries(cfg).map(([k,v]) => k + "=" + v).join('&') + history.pushState(null, '', '?' + query) + updateGraphData() } - -// Add onchange to all selector radio buttons/check boxes -for (let input of document.getElementsByClassName('selects')) { - input.addEventListener('change', function(evt) { - if (new Set(['xlog', 'ylog']).has(evt.target.name)) { - cfg[evt.target.name] = evt.target.checked - } else { - cfg[evt.target.name] = evt.target.value +for (let key of ['ckey', 'xkey', 'ykey', 'skey']) { + const parent = document.getElementById(key + '-controls') + const ctlMap = ({ + 'ckey': colorMap, + 'xkey': Object.assign({}, axisMap, {'xlog': 'Logarithmic'}), + 'ykey': Object.assign({}, axisMap, {'ylog': 'Logarithmic'}), + 'skey': axisMap, + })[key] + for (let [val, name] of Object.entries(ctlMap)) { + const log = (new Set(['xlog', 'ylog']).has(val)) ? val : false + const ctl = document.createElement('input') + ctl.class = 'selects' + ctl.type = log ? 'checkbox' : 'radio' + ctl.name = log ? log : key + ctl.value = log ? true : val + if ((log && cfg[val] === true) || cfg[key] === val) { + ctl.checked = true } - const query = Object.entries(cfg).map(([k,v]) => k + "=" + v).join('&') - history.pushState(null, '', '?' + query) - updateGraphData() - }) + ctl.addEventListener('change', ctlChange) + parent.appendChild(ctl) + parent.appendChild(document.createTextNode(name)) + } } - // // Graph rendering / updating // @@ -134,7 +155,7 @@ function updateGraphData() { // empty the graphData without recreating it while (graphData.length > 0) { graphData.pop() } graphData.push(...colorList.map(t => ({key: t, values: []}))) - for (var dir of Object.keys(allData)) { + for (let dir of Object.keys(allData)) { const impl = allData[dir] if (impl[cfg.xkey] > xMax) { xMax = impl[cfg.xkey] } if (impl[cfg.ykey] > yMax) { yMax = impl[cfg.ykey] } @@ -142,7 +163,7 @@ function updateGraphData() { if (impl[cfg.skey] < sMin) { sMin = impl[cfg.skey] } if (impl[cfg.skey] > sMax) { sMax = impl[cfg.skey] } } - for (var dir of Object.keys(allData)) { + for (let dir of Object.keys(allData)) { const impl = allData[dir] // Invert size for inverted data graphData[colorList.indexOf(impl[cfg.ckey])].values.push({ @@ -162,6 +183,8 @@ function updateGraphData() { chart.yScale(malScale(cfg.ylog)) chart.xAxis.tickValues(malTickValues(cfg.xkey, cfg.xlog)) chart.yAxis.tickValues(malTickValues(cfg.ykey, cfg.ylog)) + chart.xAxis.axisLabel(axisMap[cfg.xkey]) + chart.yAxis.axisLabel(axisMap[cfg.ykey]) // Update the graph d3.select('#mal svg') @@ -182,7 +205,7 @@ nv.addGraph(function() { .duration(300) .color(malColors) chart.dispatch.on('renderEnd', function() { - console.log('render complete') + //console.log('render complete') }) chart.dispatch.on('stateChange', function(e) { nv.log('New State:', JSON.stringify(e)) @@ -203,10 +226,15 @@ nv.addGraph(function() { //'Author: ' + //i.author_name + '
' + 'Author: ' + i.author_name + '
' + + '    ' + i.author_url.replace(/https?:\/\//, '') + '
' + '

' }) d3.json("all_data.json", function (error, data) { allData = data + // NOTE: TODO: major hack to workaround bug with switching + // to/from logarithmic mode. Seems to require at least one + // value to be less than 1 for it to work + allData.rpython.perf2 = 0.9 updateGraphData() }) return chart diff --git a/docs/graph/index.html b/docs/graph/index.html index 5a5af98a37..e1a1fe9e05 100644 --- a/docs/graph/index.html +++ b/docs/graph/index.html @@ -1,98 +1,172 @@ - - - - + + + + - +
-

Mal Implementation Stats

+

Mal Implementation Stats

-
- - - - - - - - - - - - - - - - - - - - - - - - - -
Color data: - Syntax Style - Type Discipline - Author -   
X-Axis data: - Perf 1 - Perf 2 - Perf 3 - Popularity - SLOC size - File count -  Logarithmic
Y-Axis data: - Perf 1 - Perf 2 - Perf 3 - Popularity - SLOC size - File count -  Logarithmic
Circle size: - Perf 1 - Perf 2 - Perf 3 - Popularity - SLOC size - File count -   
+
+ + + + + + + + + + + + + + + + + + +
+
+ + +
+

Important Caveats:

+

The data on this graph is very specific to Mal. + Do not use this data to directly compare programming + languages.

+
    +
  • Bad takeaway: "Language X is faster than + language Y"
  • +
  • Good takeway: "The mal impl in + language X is faster than the one + in language Y for the 'perf 3' microbenchmark"
  • +
+

Here are some reasons (non-exhaustive) why this data + should be taken with a grain of salt:

+
    +
  • The focus of the make-a-lisp process is on learning + (either Lisp or the target language). The resulting + implementations have a common structure that is + intended for understandability and consistency + between implementations. They are not structured or + intended to have optimal runtime performance or code + concision.
  • +
  • While the overall structure of each mal + implementation is similar, the implementation details + are up to the author.
  • +
  • Mal implementations are created by different + authors and the authors have varying levels of + experience with the target language and they often + created a mal implementation with the goal of learning + the target language.
  • +
  • There are hundreds of tests that each implementation + must pass before being accepted into the tree. + However, the mal language has no formal + specification so authors make choices + about whether and how to handle edge cases that are + not covered by the tests. For example, mal + implementations have different levels of runtime error + checking.
  • +
  • The performance benchmarks are very narrow in + focus and these numbers should not be extrapolated + casually. For example, the 'perf 3' microbenchmark + repeats a macro and data structure manipulation test + repeatedly for 10 seconds and counts the number of + iterations through the test. Languages with runtime + optimization (e.g. JIT) tend to do particularly well + at this benchmark (RPython, JVM-based, etc).
  • +
+

Other Notes:

+
    +
  • Syntax style and type discipline are best effort + and based on Wikipedia information and personal + experience. There are also other aspects to type + discipline (strong, gradual, duck, etc) that are not + currently included.
  • +
  • The language popularity measure is based on the + number of stars that the implementation language + received on GitHub for the most recent quarter. The + information was gathered by the GitHut + 2.0 project and then translated into a ordinal + ranking of implementations relative to each other. + Not all languages have data so a gap of 10 ticks + is introduced between the ranked languages and the + languages with no data.
  • +
+
+
+
Color data:
X-Axis data:
Y-Axis data:
Circle size:
- +
+ + From b0e8fd0703438451cf54b45d21f8383566b35898 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 21 Mar 2019 00:14:56 -0500 Subject: [PATCH 0812/2308] Move web files into docs/. Build docs/web/mal.js To update the docs/web/mal.js script run `make -C js web/mal.js` from the top-level (js/web is a symlink to docs/web). --- mal.html => docs/index.html | 22 +- {js => docs}/web/ansi.css | 0 {js => docs}/web/base.css | 0 {js => docs}/web/bg-body.png | Bin {js => docs}/web/bg-rule.png | Bin {js => docs}/web/console.css | 0 {js => docs}/web/fonts/exo-black-webfont.eot | Bin {js => docs}/web/fonts/exo-black-webfont.svg | 0 {js => docs}/web/fonts/exo-black-webfont.ttf | Bin {js => docs}/web/fonts/exo-black-webfont.woff | Bin {js => docs}/web/fonts/exo-bold-webfont.eot | Bin {js => docs}/web/fonts/exo-bold-webfont.svg | 0 {js => docs}/web/fonts/exo-bold-webfont.ttf | Bin {js => docs}/web/fonts/exo-bold-webfont.woff | Bin .../web/fonts/exo-regular-webfont.eot | Bin .../web/fonts/exo-regular-webfont.svg | 0 .../web/fonts/exo-regular-webfont.ttf | Bin .../web/fonts/exo-regular-webfont.woff | Bin {js => docs}/web/github-icon.png | Bin {js => docs}/web/himera.css | 0 {js => docs}/web/jqconsole.min.js | 0 {js => docs}/web/layout.css | 0 docs/web/mal.js | 976 ++++++++++++++++++ {js => docs}/web/skeleton.css | 0 js/web | 1 + 25 files changed, 988 insertions(+), 11 deletions(-) rename mal.html => docs/index.html (93%) rename {js => docs}/web/ansi.css (100%) rename {js => docs}/web/base.css (100%) rename {js => docs}/web/bg-body.png (100%) rename {js => docs}/web/bg-rule.png (100%) rename {js => docs}/web/console.css (100%) rename {js => docs}/web/fonts/exo-black-webfont.eot (100%) rename {js => docs}/web/fonts/exo-black-webfont.svg (100%) rename {js => docs}/web/fonts/exo-black-webfont.ttf (100%) rename {js => docs}/web/fonts/exo-black-webfont.woff (100%) rename {js => docs}/web/fonts/exo-bold-webfont.eot (100%) rename {js => docs}/web/fonts/exo-bold-webfont.svg (100%) rename {js => docs}/web/fonts/exo-bold-webfont.ttf (100%) rename {js => docs}/web/fonts/exo-bold-webfont.woff (100%) rename {js => docs}/web/fonts/exo-regular-webfont.eot (100%) rename {js => docs}/web/fonts/exo-regular-webfont.svg (100%) rename {js => docs}/web/fonts/exo-regular-webfont.ttf (100%) rename {js => docs}/web/fonts/exo-regular-webfont.woff (100%) rename {js => docs}/web/github-icon.png (100%) rename {js => docs}/web/himera.css (100%) rename {js => docs}/web/jqconsole.min.js (100%) rename {js => docs}/web/layout.css (100%) create mode 100644 docs/web/mal.js rename {js => docs}/web/skeleton.css (100%) create mode 120000 js/web diff --git a/mal.html b/docs/index.html similarity index 93% rename from mal.html rename to docs/index.html index 2601d07680..c3f9809962 100644 --- a/mal.html +++ b/docs/index.html @@ -1,6 +1,6 @@
@@ -214,14 +214,14 @@

JavaScript Interop

- - + +